From f051a9c4dc70cd1b6eafa61aec8f3b9344e02e85 Mon Sep 17 00:00:00 2001 From: William Roche Date: Wed, 22 Jan 2025 19:40:53 +0000 Subject: [PATCH 0001/1179] system/physmem: take into account fd_offset for file fallocate Punching a hole in a file with fallocate needs to take into account the fd_offset value for a correct file location. But guest_memfd internal use doesn't currently consider fd_offset. Fixes: 4b870dc4d0c0 ("hostmem-file: add offset option") Signed-off-by: William Roche Reviewed-by: Peter Xu Reviewed-by: David Hildenbrand Link: https://lore.kernel.org/r/20250122194053.3103617-2-william.roche@oracle.com Signed-off-by: Peter Xu --- system/physmem.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/system/physmem.c b/system/physmem.c index 67c9db9daadb..235015f3eaca 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -3797,18 +3797,19 @@ int ram_block_discard_range(RAMBlock *rb, uint64_t start, size_t length) } ret = fallocate(rb->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, - start, length); + start + rb->fd_offset, length); if (ret) { ret = -errno; - error_report("%s: Failed to fallocate %s:%" PRIx64 " +%zx (%d)", - __func__, rb->idstr, start, length, ret); + error_report("%s: Failed to fallocate %s:%" PRIx64 "+%" PRIx64 + " +%zx (%d)", __func__, rb->idstr, start, + rb->fd_offset, length, ret); goto err; } #else ret = -ENOSYS; error_report("%s: fallocate not available/file" - "%s:%" PRIx64 " +%zx (%d)", - __func__, rb->idstr, start, length, ret); + "%s:%" PRIx64 "+%" PRIx64 " +%zx (%d)", __func__, + rb->idstr, start, rb->fd_offset, length, ret); goto err; #endif } @@ -3855,6 +3856,7 @@ int ram_block_discard_guest_memfd_range(RAMBlock *rb, uint64_t start, int ret = -1; #ifdef CONFIG_FALLOCATE_PUNCH_HOLE + /* ignore fd_offset with guest_memfd */ ret = fallocate(rb->guest_memfd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, start, length); From 9976be3911a2d0503f026ae37c17077273bf30ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 6 Dec 2024 11:45:24 +0000 Subject: [PATCH 0002/1179] scripts: improve error from qemu-trace-stap on missing 'stap' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the 'stap' binary is missing in $PATH, a huge trace is thrown $ qemu-trace-stap list /usr/bin/qemu-system-x86_64 Traceback (most recent call last): File "/usr/bin/qemu-trace-stap", line 169, in main() File "/usr/bin/qemu-trace-stap", line 165, in main args.func(args) File "/usr/bin/qemu-trace-stap", line 83, in cmd_run subprocess.call(stapargs) File "/usr/lib64/python3.12/subprocess.py", line 389, in call with Popen(*popenargs, **kwargs) as p: ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib64/python3.12/subprocess.py", line 1026, in {}init{} self._execute_child(args, executable, preexec_fn, close_fds, File "/usr/lib64/python3.12/subprocess.py", line 1955, in _execute_child raise child_exception_type(errno_num, err_msg, err_filename) FileNotFoundError: [Errno 2] No such file or directory: 'stap' With this change the user now gets $ qemu-trace-stap list /usr/bin/qemu-system-x86_64 Unable to find 'stap' in $PATH Signed-off-by: Daniel P. Berrangé Reviewed-by: Philippe Mathieu-Daudé Message-id: 20241206114524.1666664-1-berrange@redhat.com Signed-off-by: Stefan Hajnoczi --- scripts/qemu-trace-stap | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/qemu-trace-stap b/scripts/qemu-trace-stap index eb6e951ff235..e983460ee758 100755 --- a/scripts/qemu-trace-stap +++ b/scripts/qemu-trace-stap @@ -56,6 +56,7 @@ def tapset_dir(binary): def cmd_run(args): + stap = which("stap") prefix = probe_prefix(args.binary) tapsets = tapset_dir(args.binary) @@ -76,7 +77,7 @@ def cmd_run(args): # We request an 8MB buffer, since the stap default 1MB buffer # can be easily overflowed by frequently firing QEMU traces - stapargs = ["stap", "-s", "8", "-I", tapsets ] + stapargs = [stap, "-s", "8", "-I", tapsets ] if args.pid is not None: stapargs.extend(["-x", args.pid]) stapargs.extend(["-e", script]) @@ -84,6 +85,7 @@ def cmd_run(args): def cmd_list(args): + stap = which("stap") tapsets = tapset_dir(args.binary) if args.verbose: @@ -96,7 +98,7 @@ def cmd_list(args): if verbose: print("Listing probes with name '%s'" % script) - proc = subprocess.Popen(["stap", "-I", tapsets, "-l", script], + proc = subprocess.Popen([stap, "-I", tapsets, "-l", script], stdout=subprocess.PIPE, universal_newlines=True) out, err = proc.communicate() From 52012209e1802e67aa186459e3e965f669e553df Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 10 Feb 2025 09:46:42 +0100 Subject: [PATCH 0003/1179] physmem: factor out memory_region_is_ram_device() check in memory_access_is_direct() As documented in commit 4a2e242bbb306 ("memory: Don't use memcpy for ram_device regions"), we disallow direct access to RAM DEVICE regions. Let's make this clearer to prepare for further changes. Note that romd regions will never be RAM DEVICE at the same time. Reviewed-by: Peter Xu Signed-off-by: David Hildenbrand Link: https://lore.kernel.org/r/20250210084648.33798-2-david@redhat.com Signed-off-by: Peter Xu --- include/exec/memory.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/include/exec/memory.h b/include/exec/memory.h index 9f73b5986726..5cd7574c60a3 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -2997,12 +2997,19 @@ bool prepare_mmio_access(MemoryRegion *mr); static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) { + /* + * RAM DEVICE regions can be accessed directly using memcpy, but it might + * be MMIO and access using mempy can be wrong (e.g., using instructions not + * intended for MMIO access). So we treat this as IO. + */ + if (memory_region_is_ram_device(mr)) { + return false; + } if (is_write) { return memory_region_is_ram(mr) && !mr->readonly && - !mr->rom_device && !memory_region_is_ram_device(mr); + !mr->rom_device; } else { - return (memory_region_is_ram(mr) && !memory_region_is_ram_device(mr)) || - memory_region_is_romd(mr); + return memory_region_is_ram(mr) || memory_region_is_romd(mr); } } From e76d7b6b8cd564d4d5ea6e7c7daea541e100caa4 Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 10 Feb 2025 09:46:43 +0100 Subject: [PATCH 0004/1179] physmem: factor out RAM/ROMD check in memory_access_is_direct() Let's factor more of the generic "is this directly accessible" check, independent of the "write" condition out. Note that the "!mr->rom_device" check in the write case essentially disallows the memory_region_is_romd() condition again. Further note that RAM DEVICE regions are also RAM regions, so we can check for RAM+ROMD first. This is a preparation for further changes. Reviewed-by: Peter Xu Signed-off-by: David Hildenbrand Link: https://lore.kernel.org/r/20250210084648.33798-3-david@redhat.com Signed-off-by: Peter Xu --- include/exec/memory.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/exec/memory.h b/include/exec/memory.h index 5cd7574c60a3..cb35c38402b8 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -2997,6 +2997,10 @@ bool prepare_mmio_access(MemoryRegion *mr); static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) { + /* ROM DEVICE regions only allow direct access if in ROMD mode. */ + if (!memory_region_is_ram(mr) && !memory_region_is_romd(mr)) { + return false; + } /* * RAM DEVICE regions can be accessed directly using memcpy, but it might * be MMIO and access using mempy can be wrong (e.g., using instructions not @@ -3006,11 +3010,9 @@ static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) return false; } if (is_write) { - return memory_region_is_ram(mr) && !mr->readonly && - !mr->rom_device; - } else { - return memory_region_is_ram(mr) || memory_region_is_romd(mr); + return !mr->readonly && !mr->rom_device; } + return true; } /** From 7fd970a7d35af543992bf85e77b75de6b8125eb1 Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 10 Feb 2025 09:46:44 +0100 Subject: [PATCH 0005/1179] physmem: factor out direct access check into memory_region_supports_direct_access() Let's factor the complete "directly accessible" check independent of the "write" condition out so we can reuse it next. We can now split up the checks RAM and ROMD check, so we really only check for RAM DEVICE in case of RAM -- ROM DEVICE is neither RAM not RAM DEVICE. Reviewed-by: Peter Xu Signed-off-by: David Hildenbrand Link: https://lore.kernel.org/r/20250210084648.33798-4-david@redhat.com Signed-off-by: Peter Xu --- include/exec/memory.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/include/exec/memory.h b/include/exec/memory.h index cb35c38402b8..4e2cf95ab66d 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -2995,10 +2995,13 @@ MemTxResult address_space_write_cached_slow(MemoryRegionCache *cache, int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr); bool prepare_mmio_access(MemoryRegion *mr); -static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) +static inline bool memory_region_supports_direct_access(MemoryRegion *mr) { /* ROM DEVICE regions only allow direct access if in ROMD mode. */ - if (!memory_region_is_ram(mr) && !memory_region_is_romd(mr)) { + if (memory_region_is_romd(mr)) { + return true; + } + if (!memory_region_is_ram(mr)) { return false; } /* @@ -3006,7 +3009,12 @@ static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) * be MMIO and access using mempy can be wrong (e.g., using instructions not * intended for MMIO access). So we treat this as IO. */ - if (memory_region_is_ram_device(mr)) { + return !memory_region_is_ram_device(mr); +} + +static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) +{ + if (!memory_region_supports_direct_access(mr)) { return false; } if (is_write) { From d4337aa8e222802d342b9f58440ca8e005b8bf91 Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 10 Feb 2025 09:46:45 +0100 Subject: [PATCH 0006/1179] physmem: disallow direct access to RAM DEVICE in address_space_write_rom() As documented in commit 4a2e242bbb306 ("memory: Don't use memcpy for ram_device regions"), we disallow direct access to RAM DEVICE regions. This change implies that address_space_write_rom() and cpu_memory_rw_debug() won't be able to write to RAM DEVICE regions. It will also affect cpu_flush_icache_range(), but it's only used by hw/core/loader.c after writing to ROM, so it is expected to not apply here with RAM DEVICE. This fixes direct access to these regions where we don't want direct access. We'll extend cpu_memory_rw_debug() next to also be able to write to these (and IO) regions. This is a preparation for further changes. Cc: Alex Williamson Reviewed-by: Peter Xu Signed-off-by: David Hildenbrand Link: https://lore.kernel.org/r/20250210084648.33798-5-david@redhat.com Signed-off-by: Peter Xu --- system/physmem.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/physmem.c b/system/physmem.c index 235015f3eaca..cff15ca1df75 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -3137,8 +3137,7 @@ static inline MemTxResult address_space_write_rom_internal(AddressSpace *as, l = len; mr = address_space_translate(as, addr, &addr1, &l, true, attrs); - if (!(memory_region_is_ram(mr) || - memory_region_is_romd(mr))) { + if (!memory_region_supports_direct_access(mr)) { l = memory_access_size(mr, l, addr1); } else { /* ROM/RAM case */ From d732b5a4ac3e8222e9527654f067bb766fdaecb6 Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 10 Feb 2025 09:46:46 +0100 Subject: [PATCH 0007/1179] memory: pass MemTxAttrs to memory_access_is_direct() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to pass another flag that will be stored in MemTxAttrs. So pass MemTxAttrs directly. Reviewed-by: Peter Xu Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: David Hildenbrand Link: https://lore.kernel.org/r/20250210084648.33798-6-david@redhat.com [peterx: Fix MacOS builds] Signed-off-by: Peter Xu --- hw/core/loader.c | 2 +- hw/display/apple-gfx.m | 3 ++- hw/remote/vfio-user-obj.c | 2 +- include/exec/memory.h | 5 +++-- system/memory_ldst.c.inc | 18 +++++++++--------- system/physmem.c | 12 ++++++------ 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/hw/core/loader.c b/hw/core/loader.c index fd25c5e01bd9..332b879a0bf0 100644 --- a/hw/core/loader.c +++ b/hw/core/loader.c @@ -144,7 +144,7 @@ ssize_t load_image_mr(const char *filename, MemoryRegion *mr) { ssize_t size; - if (!memory_access_is_direct(mr, false)) { + if (!memory_access_is_direct(mr, false, MEMTXATTRS_UNSPECIFIED)) { /* Can only load an image into RAM or ROM */ return -1; } diff --git a/hw/display/apple-gfx.m b/hw/display/apple-gfx.m index aa1455b62955..1554f3b8016b 100644 --- a/hw/display/apple-gfx.m +++ b/hw/display/apple-gfx.m @@ -137,7 +137,8 @@ static void apple_gfx_destroy_task(AppleGFXState *s, PGTask_t *task) MEMTXATTRS_UNSPECIFIED); if (!ram_region || ram_region_length < length || - !memory_access_is_direct(ram_region, !read_only)) { + !memory_access_is_direct(ram_region, !read_only, + MEMTXATTRS_UNSPECIFIED)) { return NULL; } diff --git a/hw/remote/vfio-user-obj.c b/hw/remote/vfio-user-obj.c index 9e5ff6d87a9d..6e51a92856fb 100644 --- a/hw/remote/vfio-user-obj.c +++ b/hw/remote/vfio-user-obj.c @@ -358,7 +358,7 @@ static int vfu_object_mr_rw(MemoryRegion *mr, uint8_t *buf, hwaddr offset, int access_size; uint64_t val; - if (memory_access_is_direct(mr, is_write)) { + if (memory_access_is_direct(mr, is_write, MEMTXATTRS_UNSPECIFIED)) { /** * Some devices expose a PCI expansion ROM, which could be buffer * based as compared to other regions which are primarily based on diff --git a/include/exec/memory.h b/include/exec/memory.h index 4e2cf95ab66d..b18ecf933eca 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -3012,7 +3012,8 @@ static inline bool memory_region_supports_direct_access(MemoryRegion *mr) return !memory_region_is_ram_device(mr); } -static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write) +static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write, + MemTxAttrs attrs) { if (!memory_region_supports_direct_access(mr)) { return false; @@ -3053,7 +3054,7 @@ MemTxResult address_space_read(AddressSpace *as, hwaddr addr, fv = address_space_to_flatview(as); l = len; mr = flatview_translate(fv, addr, &addr1, &l, false, attrs); - if (len == l && memory_access_is_direct(mr, false)) { + if (len == l && memory_access_is_direct(mr, false, attrs)) { ptr = qemu_map_ram_ptr(mr->ram_block, addr1); memcpy(buf, ptr, len); } else { diff --git a/system/memory_ldst.c.inc b/system/memory_ldst.c.inc index 0e6f3940a9a1..7f32d3d9ff39 100644 --- a/system/memory_ldst.c.inc +++ b/system/memory_ldst.c.inc @@ -34,7 +34,7 @@ static inline uint32_t glue(address_space_ldl_internal, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, false, attrs); - if (l < 4 || !memory_access_is_direct(mr, false)) { + if (l < 4 || !memory_access_is_direct(mr, false, attrs)) { release_lock |= prepare_mmio_access(mr); /* I/O case */ @@ -103,7 +103,7 @@ static inline uint64_t glue(address_space_ldq_internal, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, false, attrs); - if (l < 8 || !memory_access_is_direct(mr, false)) { + if (l < 8 || !memory_access_is_direct(mr, false, attrs)) { release_lock |= prepare_mmio_access(mr); /* I/O case */ @@ -170,7 +170,7 @@ uint8_t glue(address_space_ldub, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, false, attrs); - if (!memory_access_is_direct(mr, false)) { + if (!memory_access_is_direct(mr, false, attrs)) { release_lock |= prepare_mmio_access(mr); /* I/O case */ @@ -207,7 +207,7 @@ static inline uint16_t glue(address_space_lduw_internal, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, false, attrs); - if (l < 2 || !memory_access_is_direct(mr, false)) { + if (l < 2 || !memory_access_is_direct(mr, false, attrs)) { release_lock |= prepare_mmio_access(mr); /* I/O case */ @@ -277,7 +277,7 @@ void glue(address_space_stl_notdirty, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, true, attrs); - if (l < 4 || !memory_access_is_direct(mr, true)) { + if (l < 4 || !memory_access_is_direct(mr, true, attrs)) { release_lock |= prepare_mmio_access(mr); r = memory_region_dispatch_write(mr, addr1, val, MO_32, attrs); @@ -314,7 +314,7 @@ static inline void glue(address_space_stl_internal, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, true, attrs); - if (l < 4 || !memory_access_is_direct(mr, true)) { + if (l < 4 || !memory_access_is_direct(mr, true, attrs)) { release_lock |= prepare_mmio_access(mr); r = memory_region_dispatch_write(mr, addr1, val, MO_32 | devend_memop(endian), attrs); @@ -377,7 +377,7 @@ void glue(address_space_stb, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, true, attrs); - if (!memory_access_is_direct(mr, true)) { + if (!memory_access_is_direct(mr, true, attrs)) { release_lock |= prepare_mmio_access(mr); r = memory_region_dispatch_write(mr, addr1, val, MO_8, attrs); } else { @@ -410,7 +410,7 @@ static inline void glue(address_space_stw_internal, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, true, attrs); - if (l < 2 || !memory_access_is_direct(mr, true)) { + if (l < 2 || !memory_access_is_direct(mr, true, attrs)) { release_lock |= prepare_mmio_access(mr); r = memory_region_dispatch_write(mr, addr1, val, MO_16 | devend_memop(endian), attrs); @@ -474,7 +474,7 @@ static void glue(address_space_stq_internal, SUFFIX)(ARG1_DECL, RCU_READ_LOCK(); mr = TRANSLATE(addr, &addr1, &l, true, attrs); - if (l < 8 || !memory_access_is_direct(mr, true)) { + if (l < 8 || !memory_access_is_direct(mr, true, attrs)) { release_lock |= prepare_mmio_access(mr); r = memory_region_dispatch_write(mr, addr1, val, MO_64 | devend_memop(endian), attrs); diff --git a/system/physmem.c b/system/physmem.c index cff15ca1df75..8745c10c9d68 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -573,7 +573,7 @@ MemoryRegion *flatview_translate(FlatView *fv, hwaddr addr, hwaddr *xlat, is_write, true, &as, attrs); mr = section.mr; - if (xen_enabled() && memory_access_is_direct(mr, is_write)) { + if (xen_enabled() && memory_access_is_direct(mr, is_write, attrs)) { hwaddr page = ((addr & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE) - addr; *plen = MIN(page, *plen); } @@ -2869,7 +2869,7 @@ static MemTxResult flatview_write_continue_step(MemTxAttrs attrs, return MEMTX_ACCESS_ERROR; } - if (!memory_access_is_direct(mr, true)) { + if (!memory_access_is_direct(mr, true, attrs)) { uint64_t val; MemTxResult result; bool release_lock = prepare_mmio_access(mr); @@ -2965,7 +2965,7 @@ static MemTxResult flatview_read_continue_step(MemTxAttrs attrs, uint8_t *buf, return MEMTX_ACCESS_ERROR; } - if (!memory_access_is_direct(mr, false)) { + if (!memory_access_is_direct(mr, false, attrs)) { /* I/O case */ uint64_t val; MemTxResult result; @@ -3274,7 +3274,7 @@ static bool flatview_access_valid(FlatView *fv, hwaddr addr, hwaddr len, while (len > 0) { l = len; mr = flatview_translate(fv, addr, &xlat, &l, is_write, attrs); - if (!memory_access_is_direct(mr, is_write)) { + if (!memory_access_is_direct(mr, is_write, attrs)) { l = memory_access_size(mr, l, addr); if (!memory_region_access_valid(mr, xlat, l, is_write, attrs)) { return false; @@ -3354,7 +3354,7 @@ void *address_space_map(AddressSpace *as, fv = address_space_to_flatview(as); mr = flatview_translate(fv, addr, &xlat, &l, is_write, attrs); - if (!memory_access_is_direct(mr, is_write)) { + if (!memory_access_is_direct(mr, is_write, attrs)) { size_t used = qatomic_read(&as->bounce_buffer_size); for (;;) { hwaddr alloc = MIN(as->max_bounce_buffer_size - used, l); @@ -3487,7 +3487,7 @@ int64_t address_space_cache_init(MemoryRegionCache *cache, mr = cache->mrs.mr; memory_region_ref(mr); - if (memory_access_is_direct(mr, is_write)) { + if (memory_access_is_direct(mr, is_write, MEMTXATTRS_UNSPECIFIED)) { /* We don't care about the memory attributes here as we're only * doing this if we found actual RAM, which behaves the same * regardless of attributes; so UNSPECIFIED is fine. From 425ce9b37b98799b46cd0bed0df3dc3af25ba57a Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 10 Feb 2025 09:46:47 +0100 Subject: [PATCH 0008/1179] hmp: use cpu_get_phys_page_debug() in hmp_gva2gpa() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't need the MemTxAttrs, so let's simply use the simpler function variant. Reviewed-by: Peter Xu Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: David Hildenbrand Link: https://lore.kernel.org/r/20250210084648.33798-7-david@redhat.com Signed-off-by: Peter Xu --- monitor/hmp-cmds-target.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monitor/hmp-cmds-target.c b/monitor/hmp-cmds-target.c index 27ffe61818db..239c2a61a451 100644 --- a/monitor/hmp-cmds-target.c +++ b/monitor/hmp-cmds-target.c @@ -301,7 +301,6 @@ void hmp_gpa2hva(Monitor *mon, const QDict *qdict) void hmp_gva2gpa(Monitor *mon, const QDict *qdict) { target_ulong addr = qdict_get_int(qdict, "addr"); - MemTxAttrs attrs; CPUState *cs = mon_get_cpu(mon); hwaddr gpa; @@ -310,7 +309,7 @@ void hmp_gva2gpa(Monitor *mon, const QDict *qdict) return; } - gpa = cpu_get_phys_page_attrs_debug(cs, addr & TARGET_PAGE_MASK, &attrs); + gpa = cpu_get_phys_page_debug(cs, addr & TARGET_PAGE_MASK); if (gpa == -1) { monitor_printf(mon, "Unmapped\n"); } else { From 1cceedd7726556052d3d3bcf08a07b7762f8aa7c Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 10 Feb 2025 09:46:48 +0100 Subject: [PATCH 0009/1179] physmem: teach cpu_memory_rw_debug() to write to more memory regions Right now, we only allow for writing to memory regions that allow direct access using memcpy etc; all other writes are simply ignored. This implies that debugging guests will not work as expected when writing to MMIO device regions. Let's extend cpu_memory_rw_debug() to write to more memory regions, including MMIO device regions. Reshuffle the condition in memory_access_is_direct() to make it easier to read and add a comment. While this change implies that debug access can now also write to MMIO devices, we now are also permit ELF image loads and similar users of cpu_memory_rw_debug() to write to MMIO devices; currently we ignore these writes. Peter assumes [1] that there's probably a class of guest images, which will start writing junk (likely zeroes) into device model registers; we previously would silently ignore any such bogus ELF sections. Likely these images are of questionable correctness and this can be ignored. If ever a problem, we could make these cases use address_space_write_rom() instead, which is left unchanged for now. This patch is based on previous work by Stefan Zabka. [1] https://lore.kernel.org/all/CAFEAcA_2CEJKFyjvbwmpt=on=GgMVamQ5hiiVt+zUr6AY3X=Xg@mail.gmail.com/ Resolves: https://gitlab.com/qemu-project/qemu/-/issues/213 Reviewed-by: Peter Xu Signed-off-by: David Hildenbrand Link: https://lore.kernel.org/r/20250210084648.33798-8-david@redhat.com Signed-off-by: Peter Xu --- hw/core/cpu-system.c | 13 +++++++++---- include/exec/memattrs.h | 5 ++++- include/exec/memory.h | 3 ++- system/physmem.c | 9 ++------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index 6aae28a349a7..6e307c89597f 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -51,13 +51,18 @@ hwaddr cpu_get_phys_page_attrs_debug(CPUState *cpu, vaddr addr, MemTxAttrs *attrs) { CPUClass *cc = CPU_GET_CLASS(cpu); + hwaddr paddr; if (cc->sysemu_ops->get_phys_page_attrs_debug) { - return cc->sysemu_ops->get_phys_page_attrs_debug(cpu, addr, attrs); + paddr = cc->sysemu_ops->get_phys_page_attrs_debug(cpu, addr, attrs); + } else { + /* Fallback for CPUs which don't implement the _attrs_ hook */ + *attrs = MEMTXATTRS_UNSPECIFIED; + paddr = cc->sysemu_ops->get_phys_page_debug(cpu, addr); } - /* Fallback for CPUs which don't implement the _attrs_ hook */ - *attrs = MEMTXATTRS_UNSPECIFIED; - return cc->sysemu_ops->get_phys_page_debug(cpu, addr); + /* Indicate that this is a debug access. */ + attrs->debug = 1; + return paddr; } hwaddr cpu_get_phys_page_debug(CPUState *cpu, vaddr addr) diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h index 060b7e713149..8db1d3046479 100644 --- a/include/exec/memattrs.h +++ b/include/exec/memattrs.h @@ -44,6 +44,8 @@ typedef struct MemTxAttrs { * (see MEMTX_ACCESS_ERROR). */ unsigned int memory:1; + /* Debug access that can even write to ROM. */ + unsigned int debug:1; /* Requester ID (for MSI for example) */ unsigned int requester_id:16; @@ -56,7 +58,8 @@ typedef struct MemTxAttrs { * Bus masters which don't specify any attributes will get this * (via the MEMTXATTRS_UNSPECIFIED constant), so that we can * distinguish "all attributes deliberately clear" from - * "didn't specify" if necessary. + * "didn't specify" if necessary. "debug" can be set alongside + * "unspecified". */ bool unspecified; diff --git a/include/exec/memory.h b/include/exec/memory.h index b18ecf933eca..78c4e0aec8d1 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -3018,7 +3018,8 @@ static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write, if (!memory_region_supports_direct_access(mr)) { return false; } - if (is_write) { + /* Debug access can write to ROM. */ + if (is_write && !attrs.debug) { return !mr->readonly && !mr->rom_device; } return true; diff --git a/system/physmem.c b/system/physmem.c index 8745c10c9d68..d3efdf13d3dd 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -3680,13 +3680,8 @@ int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, if (l > len) l = len; phys_addr += (addr & ~TARGET_PAGE_MASK); - if (is_write) { - res = address_space_write_rom(cpu->cpu_ases[asidx].as, phys_addr, - attrs, buf, l); - } else { - res = address_space_read(cpu->cpu_ases[asidx].as, phys_addr, - attrs, buf, l); - } + res = address_space_rw(cpu->cpu_ases[asidx].as, phys_addr, attrs, buf, + l, is_write); if (res != MEMTX_OK) { return -1; } From c1cda1c5f8faf18994dacb8c733ad22e22c2318f Mon Sep 17 00:00:00 2001 From: William Roche Date: Tue, 11 Feb 2025 21:27:05 +0000 Subject: [PATCH 0010/1179] system/physmem: handle hugetlb correctly in qemu_ram_remap() The list of hwpoison pages used to remap the memory on reset is based on the backend real page size. To correctly handle hugetlb, we must mmap(MAP_FIXED) a complete hugetlb page; hugetlb pages cannot be partially mapped. Signed-off-by: William Roche Co-developed-by: David Hildenbrand Acked-by: David Hildenbrand Reviewed-by: Peter Xu Link: https://lore.kernel.org/r/20250211212707.302391-2-william.roche@oracle.com Signed-off-by: Peter Xu --- accel/kvm/kvm-all.c | 2 +- include/exec/cpu-common.h | 2 +- system/physmem.c | 38 +++++++++++++++++++++++++++++--------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index c65b790433cb..f89568bfa397 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -1288,7 +1288,7 @@ static void kvm_unpoison_all(void *param) QLIST_FOREACH_SAFE(page, &hwpoison_page_list, list, next_page) { QLIST_REMOVE(page, list); - qemu_ram_remap(page->ram_addr, TARGET_PAGE_SIZE); + qemu_ram_remap(page->ram_addr); g_free(page); } } diff --git a/include/exec/cpu-common.h b/include/exec/cpu-common.h index b1d76d698508..3771b2130c26 100644 --- a/include/exec/cpu-common.h +++ b/include/exec/cpu-common.h @@ -67,7 +67,7 @@ typedef uintptr_t ram_addr_t; /* memory API */ -void qemu_ram_remap(ram_addr_t addr, ram_addr_t length); +void qemu_ram_remap(ram_addr_t addr); /* This should not be used by devices. */ ram_addr_t qemu_ram_addr_from_host(void *ptr); ram_addr_t qemu_ram_addr_from_host_nofail(void *ptr); diff --git a/system/physmem.c b/system/physmem.c index d3efdf13d3dd..af1175a57ce5 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -2275,17 +2275,35 @@ void qemu_ram_free(RAMBlock *block) } #ifndef _WIN32 -void qemu_ram_remap(ram_addr_t addr, ram_addr_t length) +/* + * qemu_ram_remap - remap a single RAM page + * + * @addr: address in ram_addr_t address space. + * + * This function will try remapping a single page of guest RAM identified by + * @addr, essentially discarding memory to recover from previously poisoned + * memory (MCE). The page size depends on the RAMBlock (i.e., hugetlb). @addr + * does not have to point at the start of the page. + * + * This function is only to be used during system resets; it will kill the + * VM if remapping failed. + */ +void qemu_ram_remap(ram_addr_t addr) { RAMBlock *block; - ram_addr_t offset; + uint64_t offset; int flags; void *area, *vaddr; int prot; + size_t page_size; RAMBLOCK_FOREACH(block) { offset = addr - block->offset; if (offset < block->max_length) { + /* Respect the pagesize of our RAMBlock */ + page_size = qemu_ram_pagesize(block); + offset = QEMU_ALIGN_DOWN(offset, page_size); + vaddr = ramblock_ptr(block, offset); if (block->flags & RAM_PREALLOC) { ; @@ -2299,21 +2317,23 @@ void qemu_ram_remap(ram_addr_t addr, ram_addr_t length) prot = PROT_READ; prot |= block->flags & RAM_READONLY ? 0 : PROT_WRITE; if (block->fd >= 0) { - area = mmap(vaddr, length, prot, flags, block->fd, + area = mmap(vaddr, page_size, prot, flags, block->fd, offset + block->fd_offset); } else { flags |= MAP_ANONYMOUS; - area = mmap(vaddr, length, prot, flags, -1, 0); + area = mmap(vaddr, page_size, prot, flags, -1, 0); } if (area != vaddr) { - error_report("Could not remap addr: " - RAM_ADDR_FMT "@" RAM_ADDR_FMT "", - length, addr); + error_report("Could not remap RAM %s:%" PRIx64 "+%" PRIx64 + " +%zx", block->idstr, offset, + block->fd_offset, page_size); exit(1); } - memory_try_enable_merging(vaddr, length); - qemu_ram_setup_dump(vaddr, length); + memory_try_enable_merging(vaddr, page_size); + qemu_ram_setup_dump(vaddr, page_size); } + + break; } } } From 30943e496f2b0a49357581af480bdcd74fb338f5 Mon Sep 17 00:00:00 2001 From: William Roche Date: Tue, 11 Feb 2025 21:27:06 +0000 Subject: [PATCH 0011/1179] system/physmem: poisoned memory discard on reboot Repair poisoned memory location(s), calling ram_block_discard_range(): punching a hole in the backend file when necessary and regenerating a usable memory. If the kernel doesn't support the madvise calls used by this function and we are dealing with anonymous memory, fall back to remapping the location(s). Signed-off-by: William Roche Acked-by: David Hildenbrand Link: https://lore.kernel.org/r/20250211212707.302391-3-william.roche@oracle.com Signed-off-by: Peter Xu --- system/physmem.c | 57 ++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/system/physmem.c b/system/physmem.c index af1175a57ce5..67bdf631e60c 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -2275,6 +2275,23 @@ void qemu_ram_free(RAMBlock *block) } #ifndef _WIN32 +/* Simply remap the given VM memory location from start to start+length */ +static int qemu_ram_remap_mmap(RAMBlock *block, uint64_t start, size_t length) +{ + int flags, prot; + void *area; + void *host_startaddr = block->host + start; + + assert(block->fd < 0); + flags = MAP_FIXED | MAP_ANONYMOUS; + flags |= block->flags & RAM_SHARED ? MAP_SHARED : MAP_PRIVATE; + flags |= block->flags & RAM_NORESERVE ? MAP_NORESERVE : 0; + prot = PROT_READ; + prot |= block->flags & RAM_READONLY ? 0 : PROT_WRITE; + area = mmap(host_startaddr, length, prot, flags, -1, 0); + return area != host_startaddr ? -errno : 0; +} + /* * qemu_ram_remap - remap a single RAM page * @@ -2292,9 +2309,7 @@ void qemu_ram_remap(ram_addr_t addr) { RAMBlock *block; uint64_t offset; - int flags; - void *area, *vaddr; - int prot; + void *vaddr; size_t page_size; RAMBLOCK_FOREACH(block) { @@ -2310,24 +2325,24 @@ void qemu_ram_remap(ram_addr_t addr) } else if (xen_enabled()) { abort(); } else { - flags = MAP_FIXED; - flags |= block->flags & RAM_SHARED ? - MAP_SHARED : MAP_PRIVATE; - flags |= block->flags & RAM_NORESERVE ? MAP_NORESERVE : 0; - prot = PROT_READ; - prot |= block->flags & RAM_READONLY ? 0 : PROT_WRITE; - if (block->fd >= 0) { - area = mmap(vaddr, page_size, prot, flags, block->fd, - offset + block->fd_offset); - } else { - flags |= MAP_ANONYMOUS; - area = mmap(vaddr, page_size, prot, flags, -1, 0); - } - if (area != vaddr) { - error_report("Could not remap RAM %s:%" PRIx64 "+%" PRIx64 - " +%zx", block->idstr, offset, - block->fd_offset, page_size); - exit(1); + if (ram_block_discard_range(block, offset, page_size) != 0) { + /* + * Fall back to using mmap() only for anonymous mapping, + * as if a backing file is associated we may not be able + * to recover the memory in all cases. + * So don't take the risk of using only mmap and fail now. + */ + if (block->fd >= 0) { + error_report("Could not remap RAM %s:%" PRIx64 "+%" + PRIx64 " +%zx", block->idstr, offset, + block->fd_offset, page_size); + exit(1); + } + if (qemu_ram_remap_mmap(block, offset, page_size) != 0) { + error_report("Could not remap RAM %s:%" PRIx64 " +%zx", + block->idstr, offset, page_size); + exit(1); + } } memory_try_enable_merging(vaddr, page_size); qemu_ram_setup_dump(vaddr, page_size); From e76fadf93e4d64492206b34fd3d434b515450d2c Mon Sep 17 00:00:00 2001 From: Daniil Tatianin Date: Wed, 12 Feb 2025 17:39:17 +0300 Subject: [PATCH 0012/1179] os: add an ability to lock memory on_fault This will be used in the following commits to make it possible to only lock memory on fault instead of right away. Signed-off-by: Daniil Tatianin Reviewed-by: Vladimir Sementsov-Ogievskiy Link: https://lore.kernel.org/r/20250212143920.1269754-2-d-tatianin@yandex-team.ru [peterx: fail os_mlock(on_fault=1) when not supported] [peterx: use G_GNUC_UNUSED instead of "(void)on_fault", per Dan] Signed-off-by: Peter Xu --- include/system/os-posix.h | 2 +- include/system/os-win32.h | 2 +- meson.build | 6 ++++++ migration/postcopy-ram.c | 2 +- os-posix.c | 15 +++++++++++++-- system/vl.c | 2 +- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/system/os-posix.h b/include/system/os-posix.h index b881ac6c6f74..ce5b3bccf8db 100644 --- a/include/system/os-posix.h +++ b/include/system/os-posix.h @@ -53,7 +53,7 @@ bool os_set_runas(const char *user_id); void os_set_chroot(const char *path); void os_setup_limits(void); void os_setup_post(void); -int os_mlock(void); +int os_mlock(bool on_fault); /** * qemu_alloc_stack: diff --git a/include/system/os-win32.h b/include/system/os-win32.h index b82a5d3ad93c..bc623061d821 100644 --- a/include/system/os-win32.h +++ b/include/system/os-win32.h @@ -123,7 +123,7 @@ static inline bool is_daemonized(void) return false; } -static inline int os_mlock(void) +static inline int os_mlock(bool on_fault G_GNUC_UNUSED) { return -ENOSYS; } diff --git a/meson.build b/meson.build index 18cf9e2913b9..59953cbe6b1a 100644 --- a/meson.build +++ b/meson.build @@ -2885,6 +2885,12 @@ config_host_data.set('HAVE_MLOCKALL', cc.links(gnu_source_prefix + ''' return mlockall(MCL_FUTURE); }''')) +config_host_data.set('HAVE_MLOCK_ONFAULT', cc.links(gnu_source_prefix + ''' + #include + int main(void) { + return mlockall(MCL_FUTURE | MCL_ONFAULT); + }''')) + have_l2tpv3 = false if get_option('l2tpv3').allowed() and have_system have_l2tpv3 = cc.has_type('struct mmsghdr', diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c index 6a6da6ba7f3a..fc4d8a10df71 100644 --- a/migration/postcopy-ram.c +++ b/migration/postcopy-ram.c @@ -652,7 +652,7 @@ int postcopy_ram_incoming_cleanup(MigrationIncomingState *mis) } if (enable_mlock) { - if (os_mlock() < 0) { + if (os_mlock(false) < 0) { error_report("mlock: %s", strerror(errno)); /* * It doesn't feel right to fail at this point, we have a valid diff --git a/os-posix.c b/os-posix.c index 9cce55ff2f7e..52925c23d3d9 100644 --- a/os-posix.c +++ b/os-posix.c @@ -327,18 +327,29 @@ void os_set_line_buffering(void) setvbuf(stdout, NULL, _IOLBF, 0); } -int os_mlock(void) +int os_mlock(bool on_fault) { #ifdef HAVE_MLOCKALL int ret = 0; + int flags = MCL_CURRENT | MCL_FUTURE; - ret = mlockall(MCL_CURRENT | MCL_FUTURE); + if (on_fault) { +#ifdef HAVE_MLOCK_ONFAULT + flags |= MCL_ONFAULT; +#else + error_report("mlockall: on_fault not supported"); + return -EINVAL; +#endif + } + + ret = mlockall(flags); if (ret < 0) { error_report("mlockall: %s", strerror(errno)); } return ret; #else + (void)on_fault; return -ENOSYS; #endif } diff --git a/system/vl.c b/system/vl.c index 9c6942c6cfcc..e94fc7ea354e 100644 --- a/system/vl.c +++ b/system/vl.c @@ -797,7 +797,7 @@ static QemuOptsList qemu_run_with_opts = { static void realtime_init(void) { if (enable_mlock) { - if (os_mlock() < 0) { + if (os_mlock(false) < 0) { error_report("locking memory failed"); exit(1); } From cb74f2b8a65cde2eadbcb5574327ac3f49983d8a Mon Sep 17 00:00:00 2001 From: Daniil Tatianin Date: Wed, 12 Feb 2025 17:39:18 +0300 Subject: [PATCH 0013/1179] system/vl: extract overcommit option parsing into a helper This will be extended in the future commits, let's move it out of line right away so that it's easier to read. Reviewed-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Peter Xu Signed-off-by: Daniil Tatianin Link: https://lore.kernel.org/r/20250212143920.1269754-3-d-tatianin@yandex-team.ru Signed-off-by: Peter Xu --- system/vl.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/system/vl.c b/system/vl.c index e94fc7ea354e..72a40985f563 100644 --- a/system/vl.c +++ b/system/vl.c @@ -1875,6 +1875,19 @@ static void object_option_parse(const char *str) visit_free(v); } +static void overcommit_parse(const char *str) +{ + QemuOpts *opts; + + opts = qemu_opts_parse_noisily(qemu_find_opts("overcommit"), + str, false); + if (!opts) { + exit(1); + } + enable_mlock = qemu_opt_get_bool(opts, "mem-lock", enable_mlock); + enable_cpu_pm = qemu_opt_get_bool(opts, "cpu-pm", enable_cpu_pm); +} + /* * Very early object creation, before the sandbox options have been activated. */ @@ -3575,13 +3588,7 @@ void qemu_init(int argc, char **argv) object_option_parse(optarg); break; case QEMU_OPTION_overcommit: - opts = qemu_opts_parse_noisily(qemu_find_opts("overcommit"), - optarg, false); - if (!opts) { - exit(1); - } - enable_mlock = qemu_opt_get_bool(opts, "mem-lock", enable_mlock); - enable_cpu_pm = qemu_opt_get_bool(opts, "cpu-pm", enable_cpu_pm); + overcommit_parse(optarg); break; case QEMU_OPTION_compat: { From cd2e472e54a49c13b0a728cdda7c10c50421e23d Mon Sep 17 00:00:00 2001 From: Daniil Tatianin Date: Wed, 12 Feb 2025 17:39:19 +0300 Subject: [PATCH 0014/1179] system: introduce a new MlockState enum Replace the boolean value enable_mlock with an enum and add a helper to decide whether we should be calling os_mlock. This is a stepping stone towards introducing a new mlock mode, which will be the third possible state of this enum. Reviewed-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Peter Xu Signed-off-by: Daniil Tatianin Link: https://lore.kernel.org/r/20250212143920.1269754-4-d-tatianin@yandex-team.ru Signed-off-by: Peter Xu --- hw/virtio/virtio-mem.c | 2 +- include/system/system.h | 10 +++++++++- migration/postcopy-ram.c | 2 +- system/globals.c | 7 ++++++- system/vl.c | 9 +++++++-- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index b1a003736b0a..7b140add765c 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -991,7 +991,7 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp) return; } - if (enable_mlock) { + if (should_mlock(mlock_state)) { error_setg(errp, "Incompatible with mlock"); return; } diff --git a/include/system/system.h b/include/system/system.h index 0cbb43ec303b..dc7628357abf 100644 --- a/include/system/system.h +++ b/include/system/system.h @@ -44,10 +44,18 @@ extern int display_opengl; extern const char *keyboard_layout; extern int old_param; extern uint8_t *boot_splash_filedata; -extern bool enable_mlock; extern bool enable_cpu_pm; extern QEMUClockType rtc_clock; +typedef enum { + MLOCK_OFF = 0, + MLOCK_ON, +} MlockState; + +bool should_mlock(MlockState); + +extern MlockState mlock_state; + #define MAX_OPTION_ROMS 16 typedef struct QEMUOptionRom { const char *name; diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c index fc4d8a10df71..04068ee0394d 100644 --- a/migration/postcopy-ram.c +++ b/migration/postcopy-ram.c @@ -651,7 +651,7 @@ int postcopy_ram_incoming_cleanup(MigrationIncomingState *mis) mis->have_fault_thread = false; } - if (enable_mlock) { + if (should_mlock(mlock_state)) { if (os_mlock(false) < 0) { error_report("mlock: %s", strerror(errno)); /* diff --git a/system/globals.c b/system/globals.c index 4867c93ca6b9..adeff3834846 100644 --- a/system/globals.c +++ b/system/globals.c @@ -31,10 +31,15 @@ #include "system/cpus.h" #include "system/system.h" +bool should_mlock(MlockState state) +{ + return state == MLOCK_ON; +} + enum vga_retrace_method vga_retrace_method = VGA_RETRACE_DUMB; int display_opengl; const char* keyboard_layout; -bool enable_mlock; +MlockState mlock_state; bool enable_cpu_pm; int autostart = 1; int vga_interface_type = VGA_NONE; diff --git a/system/vl.c b/system/vl.c index 72a40985f563..2895824c1ad6 100644 --- a/system/vl.c +++ b/system/vl.c @@ -796,7 +796,7 @@ static QemuOptsList qemu_run_with_opts = { static void realtime_init(void) { - if (enable_mlock) { + if (should_mlock(mlock_state)) { if (os_mlock(false) < 0) { error_report("locking memory failed"); exit(1); @@ -1878,13 +1878,18 @@ static void object_option_parse(const char *str) static void overcommit_parse(const char *str) { QemuOpts *opts; + bool enable_mlock; opts = qemu_opts_parse_noisily(qemu_find_opts("overcommit"), str, false); if (!opts) { exit(1); } - enable_mlock = qemu_opt_get_bool(opts, "mem-lock", enable_mlock); + + enable_mlock = qemu_opt_get_bool(opts, "mem-lock", + should_mlock(mlock_state)); + mlock_state = enable_mlock ? MLOCK_ON : MLOCK_OFF; + enable_cpu_pm = qemu_opt_get_bool(opts, "cpu-pm", enable_cpu_pm); } From 13057e064a3edae7abf9ca2c207cdf48b82c5aad Mon Sep 17 00:00:00 2001 From: Daniil Tatianin Date: Wed, 12 Feb 2025 17:39:20 +0300 Subject: [PATCH 0015/1179] overcommit: introduce mem-lock=on-fault Locking the memory without MCL_ONFAULT instantly prefaults any mmaped anonymous memory with a write-fault, which introduces a lot of extra overhead in terms of memory usage when all you want to do is to prevent kcompactd from migrating and compacting QEMU pages. Add an option to only lock pages lazily as they're faulted by the process by using MCL_ONFAULT if asked. Reviewed-by: Vladimir Sementsov-Ogievskiy Reviewed-by: Peter Xu Signed-off-by: Daniil Tatianin Link: https://lore.kernel.org/r/20250212143920.1269754-5-d-tatianin@yandex-team.ru Signed-off-by: Peter Xu --- include/system/system.h | 2 ++ migration/postcopy-ram.c | 2 +- qemu-options.hx | 14 +++++++++----- system/globals.c | 7 ++++++- system/vl.c | 34 +++++++++++++++++++++++++++------- 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/include/system/system.h b/include/system/system.h index dc7628357abf..a7effe7dfd8b 100644 --- a/include/system/system.h +++ b/include/system/system.h @@ -50,9 +50,11 @@ extern QEMUClockType rtc_clock; typedef enum { MLOCK_OFF = 0, MLOCK_ON, + MLOCK_ON_FAULT, } MlockState; bool should_mlock(MlockState); +bool is_mlock_on_fault(MlockState); extern MlockState mlock_state; diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c index 04068ee0394d..5d3edfcfec73 100644 --- a/migration/postcopy-ram.c +++ b/migration/postcopy-ram.c @@ -652,7 +652,7 @@ int postcopy_ram_incoming_cleanup(MigrationIncomingState *mis) } if (should_mlock(mlock_state)) { - if (os_mlock(false) < 0) { + if (os_mlock(is_mlock_on_fault(mlock_state)) < 0) { error_report("mlock: %s", strerror(errno)); /* * It doesn't feel right to fail at this point, we have a valid diff --git a/qemu-options.hx b/qemu-options.hx index 1b26ad53bda7..61270e320670 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -4632,21 +4632,25 @@ SRST ERST DEF("overcommit", HAS_ARG, QEMU_OPTION_overcommit, - "-overcommit [mem-lock=on|off][cpu-pm=on|off]\n" + "-overcommit [mem-lock=on|off|on-fault][cpu-pm=on|off]\n" " run qemu with overcommit hints\n" - " mem-lock=on|off controls memory lock support (default: off)\n" + " mem-lock=on|off|on-fault controls memory lock support (default: off)\n" " cpu-pm=on|off controls cpu power management (default: off)\n", QEMU_ARCH_ALL) SRST -``-overcommit mem-lock=on|off`` +``-overcommit mem-lock=on|off|on-fault`` \ ``-overcommit cpu-pm=on|off`` Run qemu with hints about host resource overcommit. The default is to assume that host overcommits all resources. Locking qemu and guest memory can be enabled via ``mem-lock=on`` - (disabled by default). This works when host memory is not - overcommitted and reduces the worst-case latency for guest. + or ``mem-lock=on-fault`` (disabled by default). This works when + host memory is not overcommitted and reduces the worst-case latency for + guest. The on-fault option is better for reducing the memory footprint + since it makes allocations lazy, but the pages still get locked in place + once faulted by the guest or QEMU. Note that the two options are mutually + exclusive. Guest ability to manage power state of host cpus (increasing latency for other processes on the same host cpu, but decreasing latency for diff --git a/system/globals.c b/system/globals.c index adeff3834846..316623bd20af 100644 --- a/system/globals.c +++ b/system/globals.c @@ -33,7 +33,12 @@ bool should_mlock(MlockState state) { - return state == MLOCK_ON; + return state == MLOCK_ON || state == MLOCK_ON_FAULT; +} + +bool is_mlock_on_fault(MlockState state) +{ + return state == MLOCK_ON_FAULT; } enum vga_retrace_method vga_retrace_method = VGA_RETRACE_DUMB; diff --git a/system/vl.c b/system/vl.c index 2895824c1ad6..3c0fa2ff642c 100644 --- a/system/vl.c +++ b/system/vl.c @@ -351,7 +351,7 @@ static QemuOptsList qemu_overcommit_opts = { .desc = { { .name = "mem-lock", - .type = QEMU_OPT_BOOL, + .type = QEMU_OPT_STRING, }, { .name = "cpu-pm", @@ -797,7 +797,7 @@ static QemuOptsList qemu_run_with_opts = { static void realtime_init(void) { if (should_mlock(mlock_state)) { - if (os_mlock(false) < 0) { + if (os_mlock(is_mlock_on_fault(mlock_state)) < 0) { error_report("locking memory failed"); exit(1); } @@ -1878,7 +1878,7 @@ static void object_option_parse(const char *str) static void overcommit_parse(const char *str) { QemuOpts *opts; - bool enable_mlock; + const char *mem_lock_opt; opts = qemu_opts_parse_noisily(qemu_find_opts("overcommit"), str, false); @@ -1886,11 +1886,31 @@ static void overcommit_parse(const char *str) exit(1); } - enable_mlock = qemu_opt_get_bool(opts, "mem-lock", - should_mlock(mlock_state)); - mlock_state = enable_mlock ? MLOCK_ON : MLOCK_OFF; - enable_cpu_pm = qemu_opt_get_bool(opts, "cpu-pm", enable_cpu_pm); + + mem_lock_opt = qemu_opt_get(opts, "mem-lock"); + if (!mem_lock_opt) { + return; + } + + if (strcmp(mem_lock_opt, "on") == 0) { + mlock_state = MLOCK_ON; + return; + } + + if (strcmp(mem_lock_opt, "off") == 0) { + mlock_state = MLOCK_OFF; + return; + } + + if (strcmp(mem_lock_opt, "on-fault") == 0) { + mlock_state = MLOCK_ON_FAULT; + return; + } + + error_report("parameter 'mem-lock' expects one of " + "'on', 'off', 'on-fault'"); + exit(1); } /* From df45e26a81022f4f8f976b603cb0466b1cd64baf Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 13 Feb 2025 12:12:43 +0100 Subject: [PATCH 0016/1179] rust: docs: document naming convention As agreed in the "vtables and procedural macros" thread on the mailing list. Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 390aae438669..8cccca7a734d 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -194,6 +194,50 @@ module status interface either. Also, ``unsafe`` interfaces may be replaced by safe interfaces later. +Naming convention +''''''''''''''''' + +C function names usually are prefixed according to the data type that they +apply to, for example ``timer_mod`` or ``sysbus_connect_irq``. Furthermore, +both function and structs sometimes have a ``qemu_`` or ``QEMU`` prefix. +Generally speaking, these are all removed in the corresponding Rust functions: +``QEMUTimer`` becomes ``timer::Timer``, ``timer_mod`` becomes ``Timer::modify``, +``sysbus_connect_irq`` becomes ``SysBusDeviceMethods::connect_irq``. + +Sometimes however a name appears multiple times in the QOM class hierarchy, +and the only difference is in the prefix. An example is ``qdev_realize`` and +``sysbus_realize``. In such cases, whenever a name is not unique in +the hierarchy, always add the prefix to the classes that are lower in +the hierarchy; for the top class, decide on a case by case basis. + +For example: + +========================== ========================================= +``device_cold_reset()`` ``DeviceMethods::cold_reset()`` +``pci_device_reset()`` ``PciDeviceMethods::pci_device_reset()`` +``pci_bridge_reset()`` ``PciBridgeMethods::pci_bridge_reset()`` +========================== ========================================= + +Here, the name is not exactly the same, but nevertheless ``PciDeviceMethods`` +adds the prefix to avoid confusion, because the functionality of +``device_cold_reset()`` and ``pci_device_reset()`` is subtly different. + +In this case, however, no prefix is needed: + +========================== ========================================= +``device_realize()`` ``DeviceMethods::realize()`` +``sysbus_realize()`` ``SysbusDeviceMethods::sysbus_realize()`` +``pci_realize()`` ``PciDeviceMethods::pci_realize()`` +========================== ========================================= + +Here, the lower classes do not add any functionality, and mostly +provide extra compile-time checking; the basic *realize* functionality +is the same for all devices. Therefore, ``DeviceMethods`` does not +add the prefix. + +Whenever a name is unique in the hierarchy, instead, you should +always remove the class name prefix. + Common pitfalls ''''''''''''''' From 0fcccf3ff04a54d597bffcb7a42668c52a7dcec0 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 17 Jan 2025 12:00:01 +0100 Subject: [PATCH 0017/1179] rust: qom: add reference counting functionality Add a smart pointer that allows to add and remove references from QOM objects. It's important to note that while all QOM objects have a reference count, in practice not all of them have their lifetime guarded by it. Embedded objects, specifically, are confined to the lifetime of the owner. When writing Rust bindings this is important, because embedded objects are *never* used through the "Owned<>" smart pointer that is introduced here. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/qom.rs | 166 +++++++++++++++++++++++++++++++++-- rust/qemu-api/src/vmstate.rs | 6 +- rust/qemu-api/tests/tests.rs | 13 ++- 3 files changed, 178 insertions(+), 7 deletions(-) diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index f50ee371aacc..404446d57fc2 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -56,6 +56,7 @@ use std::{ ffi::CStr, fmt, + mem::ManuallyDrop, ops::{Deref, DerefMut}, os::raw::c_void, ptr::NonNull, @@ -63,7 +64,13 @@ use std::{ pub use bindings::{Object, ObjectClass}; -use crate::bindings::{self, object_dynamic_cast, object_get_class, object_get_typename, TypeInfo}; +use crate::{ + bindings::{ + self, object_dynamic_cast, object_get_class, object_get_typename, object_ref, object_unref, + TypeInfo, + }, + cell::bql_locked, +}; /// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct /// or indirect parent of `Self`). @@ -280,10 +287,10 @@ where /// /// # Safety /// - /// This method is unsafe because it overrides const-ness of `&self`. - /// Bindings to C APIs will use it a lot, but otherwise it should not - /// be necessary. - unsafe fn as_mut_ptr(&self) -> *mut U + /// This method is safe because only the actual dereference of the pointer + /// has to be unsafe. Bindings to C APIs will use it a lot, but care has + /// to be taken because it overrides the const-ness of `&self`. + fn as_mut_ptr(&self) -> *mut U where Self::Target: IsA, { @@ -610,6 +617,148 @@ unsafe impl ObjectType for Object { unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_OBJECT) }; } +/// A reference-counted pointer to a QOM object. +/// +/// `Owned` wraps `T` with automatic reference counting. It increases the +/// reference count when created via [`Owned::from`] or cloned, and decreases +/// it when dropped. This ensures that the reference count remains elevated +/// as long as any `Owned` references to it exist. +/// +/// `Owned` can be used for two reasons: +/// * because the lifetime of the QOM object is unknown and someone else could +/// take a reference (similar to `Arc`, for example): in this case, the +/// object can escape and outlive the Rust struct that contains the `Owned` +/// field; +/// +/// * to ensure that the object stays alive until after `Drop::drop` is called +/// on the Rust struct: in this case, the object will always die together with +/// the Rust struct that contains the `Owned` field. +/// +/// Child properties are an example of the second case: in C, an object that +/// is created with `object_initialize_child` will die *before* +/// `instance_finalize` is called, whereas Rust expects the struct to have valid +/// contents when `Drop::drop` is called. Therefore Rust structs that have +/// child properties need to keep a reference to the child object. Right now +/// this can be done with `Owned`; in the future one might have a separate +/// `Child<'parent, T>` smart pointer that keeps a reference to a `T`, like +/// `Owned`, but does not allow cloning. +/// +/// Note that dropping an `Owned` requires the big QEMU lock to be taken. +#[repr(transparent)] +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Owned(NonNull); + +// The following rationale for safety is taken from Linux's kernel::sync::Arc. + +// SAFETY: It is safe to send `Owned` to another thread when the underlying +// `T` is `Sync` because it effectively means sharing `&T` (which is safe +// because `T` is `Sync`); additionally, it needs `T` to be `Send` because any +// thread that has an `Owned` may ultimately access `T` using a +// mutable reference when the reference count reaches zero and `T` is dropped. +unsafe impl Send for Owned {} + +// SAFETY: It is safe to send `&Owned` to another thread when the underlying +// `T` is `Sync` because it effectively means sharing `&T` (which is safe +// because `T` is `Sync`); additionally, it needs `T` to be `Send` because any +// thread that has a `&Owned` may clone it and get an `Owned` on that +// thread, so the thread may ultimately access `T` using a mutable reference +// when the reference count reaches zero and `T` is dropped. +unsafe impl Sync for Owned {} + +impl Owned { + /// Convert a raw C pointer into an owned reference to the QOM + /// object it points to. The object's reference count will be + /// decreased when the `Owned` is dropped. + /// + /// # Panics + /// + /// Panics if `ptr` is NULL. + /// + /// # Safety + /// + /// The caller must indeed own a reference to the QOM object. + /// The object must not be embedded in another unless the outer + /// object is guaranteed to have a longer lifetime. + /// + /// A raw pointer obtained via [`Owned::into_raw()`] can always be passed + /// back to `from_raw()` (assuming the original `Owned` was valid!), + /// since the owned reference remains there between the calls to + /// `into_raw()` and `from_raw()`. + pub unsafe fn from_raw(ptr: *const T) -> Self { + // SAFETY NOTE: while NonNull requires a mutable pointer, only + // Deref is implemented so the pointer passed to from_raw + // remains const + Owned(NonNull::new(ptr as *mut T).unwrap()) + } + + /// Obtain a raw C pointer from a reference. `src` is consumed + /// and the reference is leaked. + #[allow(clippy::missing_const_for_fn)] + pub fn into_raw(src: Owned) -> *mut T { + let src = ManuallyDrop::new(src); + src.0.as_ptr() + } + + /// Increase the reference count of a QOM object and return + /// a new owned reference to it. + /// + /// # Safety + /// + /// The object must not be embedded in another, unless the outer + /// object is guaranteed to have a longer lifetime. + pub unsafe fn from(obj: &T) -> Self { + unsafe { + object_ref(obj.as_object_mut_ptr().cast::()); + + // SAFETY NOTE: while NonNull requires a mutable pointer, only + // Deref is implemented so the reference passed to from_raw + // remains shared + Owned(NonNull::new_unchecked(obj.as_mut_ptr())) + } + } +} + +impl Clone for Owned { + fn clone(&self) -> Self { + // SAFETY: creation method is unsafe; whoever calls it has + // responsibility that the pointer is valid, and remains valid + // throughout the lifetime of the `Owned` and its clones. + unsafe { Owned::from(self.deref()) } + } +} + +impl Deref for Owned { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: creation method is unsafe; whoever calls it has + // responsibility that the pointer is valid, and remains valid + // throughout the lifetime of the `Owned` and its clones. + // With that guarantee, reference counting ensures that + // the object remains alive. + unsafe { &*self.0.as_ptr() } + } +} +impl ObjectDeref for Owned {} + +impl Drop for Owned { + fn drop(&mut self) { + assert!(bql_locked()); + // SAFETY: creation method is unsafe, and whoever calls it has + // responsibility that the pointer is valid, and remains valid + // throughout the lifetime of the `Owned` and its clones. + unsafe { + object_unref(self.as_object_mut_ptr().cast::()); + } + } +} + +impl> fmt::Debug for Owned { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.deref().debug_fmt(f) + } +} + /// Trait for methods exposed by the Object class. The methods can be /// called on all objects that have the trait `IsA`. /// @@ -641,6 +790,13 @@ where klass } + + /// Convenience function for implementing the Debug trait + fn debug_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple(&self.typename()) + .field(&(self as *const Self)) + .finish() + } } impl ObjectMethods for R where R::Target: IsA {} diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 6ac432cf52f2..11d21b8791ce 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -29,6 +29,8 @@ use core::{marker::PhantomData, mem, ptr::NonNull}; pub use crate::bindings::{VMStateDescription, VMStateField}; use crate::{ bindings::{self, VMStateFlags}, + prelude::*, + qom::Owned, zeroable::Zeroable, }; @@ -191,7 +193,8 @@ pub const fn vmstate_varray_flag(_: PhantomData) -> VMStateFlags /// * a transparent wrapper for any of the above (`Cell`, `UnsafeCell`, /// [`BqlCell`](crate::cell::BqlCell), [`BqlRefCell`](crate::cell::BqlRefCell) /// * a raw pointer to any of the above -/// * a `NonNull` pointer or a `Box` for any of the above +/// * a `NonNull` pointer, a `Box` or an [`Owned`](crate::qom::Owned) for any of +/// the above /// * an array of any of the above /// /// In order to support other types, the trait `VMState` must be implemented @@ -398,6 +401,7 @@ impl_vmstate_pointer!(NonNull where T: VMState); // Unlike C pointers, Box is always non-null therefore there is no need // to specify VMS_ALLOC. impl_vmstate_pointer!(Box where T: VMState); +impl_vmstate_pointer!(Owned where T: VMState + ObjectType); // Arrays using the underlying type's VMState plus // VMS_ARRAY/VMS_ARRAY_OF_POINTER diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 5c3e75ed3d54..5f6096a572e6 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -15,7 +15,7 @@ use qemu_api::{ declare_properties, define_property, prelude::*, qdev::{DeviceClass, DeviceImpl, DeviceState, Property}, - qom::{ClassInitImpl, ObjectImpl, ParentField}, + qom::{ClassInitImpl, ObjectImpl, Owned, ParentField}, vmstate::VMStateDescription, zeroable::Zeroable, }; @@ -138,6 +138,17 @@ fn test_object_new() { } } +#[test] +#[allow(clippy::redundant_clone)] +/// Create, clone and then drop an instance. +fn test_clone() { + init_qom(); + let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; + let p = unsafe { Owned::from_raw(p) }; + assert_eq!(p.clone().typename(), "dummy"); + drop(p); +} + #[test] /// Try invoking a method on an object. fn test_typename() { From ec3eba98967014f942bafb4307303d853d96e7e7 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 31 Oct 2024 14:27:36 +0100 Subject: [PATCH 0018/1179] rust: qom: add object creation functionality The basic object lifecycle test can now be implemented using safe code! Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 23 ++++++++++++--------- rust/qemu-api/src/prelude.rs | 1 + rust/qemu-api/src/qom.rs | 23 +++++++++++++++++++-- rust/qemu-api/tests/tests.rs | 35 ++++++++++++-------------------- 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 8050ede9c85b..f5db114b0c7b 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -10,11 +10,11 @@ use std::{ use qemu_api::{ bindings::{ - error_fatal, hwaddr, memory_region_init_io, qdev_init_clock_in, qdev_new, - qdev_prop_set_chr, qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, - qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, sysbus_mmio_map, - sysbus_realize_and_unref, CharBackend, Chardev, Clock, ClockEvent, MemoryRegion, - QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, + error_fatal, hwaddr, memory_region_init_io, qdev_init_clock_in, qdev_prop_set_chr, + qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, + qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, sysbus_mmio_map, sysbus_realize, + CharBackend, Chardev, Clock, ClockEvent, MemoryRegion, QEMUChrEvent, + CHR_IOCTL_SERIAL_SET_BREAK, }, c_str, impl_vmstate_forward, irq::InterruptSource, @@ -705,15 +705,18 @@ pub unsafe extern "C" fn pl011_create( irq: qemu_irq, chr: *mut Chardev, ) -> *mut DeviceState { + let pl011 = PL011State::new(); unsafe { - let dev: *mut DeviceState = qdev_new(PL011State::TYPE_NAME.as_ptr()); - let sysbus: *mut SysBusDevice = dev.cast::(); - + let dev = pl011.as_mut_ptr::(); qdev_prop_set_chr(dev, c_str!("chardev").as_ptr(), chr); - sysbus_realize_and_unref(sysbus, addr_of_mut!(error_fatal)); + + let sysbus = pl011.as_mut_ptr::(); + sysbus_realize(sysbus, addr_of_mut!(error_fatal)); sysbus_mmio_map(sysbus, 0, addr); sysbus_connect_irq(sysbus, 0, irq); - dev + + // return the pointer, which is kept alive by the QOM tree; drop owned ref + pl011.as_mut_ptr() } } diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 2dc86e19b29f..3df6a5c21ec7 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -12,6 +12,7 @@ pub use crate::qom::Object; pub use crate::qom::ObjectCast; pub use crate::qom::ObjectCastMut; pub use crate::qom::ObjectDeref; +pub use crate::qom::ObjectClassMethods; pub use crate::qom::ObjectMethods; pub use crate::qom::ObjectType; diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 404446d57fc2..3e63cb30ca6b 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -66,8 +66,8 @@ pub use bindings::{Object, ObjectClass}; use crate::{ bindings::{ - self, object_dynamic_cast, object_get_class, object_get_typename, object_ref, object_unref, - TypeInfo, + self, object_dynamic_cast, object_get_class, object_get_typename, object_new, object_ref, + object_unref, TypeInfo, }, cell::bql_locked, }; @@ -759,6 +759,24 @@ impl> fmt::Debug for Owned { } } +/// Trait for class methods exposed by the Object class. The methods can be +/// called on all objects that have the trait `IsA`. +/// +/// The trait should only be used through the blanket implementation, +/// which guarantees safety via `IsA` +pub trait ObjectClassMethods: IsA { + /// Return a new reference counted instance of this class + fn new() -> Owned { + assert!(bql_locked()); + // SAFETY: the object created by object_new is allocated on + // the heap and has a reference count of 1 + unsafe { + let obj = &*object_new(Self::TYPE_NAME.as_ptr()); + Owned::from_raw(obj.unsafe_cast::()) + } + } +} + /// Trait for methods exposed by the Object class. The methods can be /// called on all objects that have the trait `IsA`. /// @@ -799,4 +817,5 @@ where } } +impl ObjectClassMethods for T where T: IsA {} impl ObjectMethods for R where R::Target: IsA {} diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 5f6096a572e6..10748fba1970 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -3,8 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use std::{ - ffi::CStr, - os::raw::c_void, + ffi::{c_void, CStr}, ptr::{addr_of, addr_of_mut}, }; @@ -15,7 +14,7 @@ use qemu_api::{ declare_properties, define_property, prelude::*, qdev::{DeviceClass, DeviceImpl, DeviceState, Property}, - qom::{ClassInitImpl, ObjectImpl, Owned, ParentField}, + qom::{ClassInitImpl, ObjectImpl, ParentField}, vmstate::VMStateDescription, zeroable::Zeroable, }; @@ -132,10 +131,8 @@ fn init_qom() { /// Create and immediately drop an instance. fn test_object_new() { init_qom(); - unsafe { - object_unref(object_new(DummyState::TYPE_NAME.as_ptr()).cast()); - object_unref(object_new(DummyChildState::TYPE_NAME.as_ptr()).cast()); - } + drop(DummyState::new()); + drop(DummyChildState::new()); } #[test] @@ -143,8 +140,7 @@ fn test_object_new() { /// Create, clone and then drop an instance. fn test_clone() { init_qom(); - let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; - let p = unsafe { Owned::from_raw(p) }; + let p = DummyState::new(); assert_eq!(p.clone().typename(), "dummy"); drop(p); } @@ -153,12 +149,8 @@ fn test_clone() { /// Try invoking a method on an object. fn test_typename() { init_qom(); - let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; - let p_ref: &DummyState = unsafe { &*p }; - assert_eq!(p_ref.typename(), "dummy"); - unsafe { - object_unref(p_ref.as_object_mut_ptr().cast::()); - } + let p = DummyState::new(); + assert_eq!(p.typename(), "dummy"); } // a note on all "cast" tests: usually, especially for downcasts the desired @@ -173,24 +165,23 @@ fn test_typename() { /// Test casts on shared references. fn test_cast() { init_qom(); - let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; + let p = DummyState::new(); + let p_ptr: *mut DummyState = p.as_mut_ptr(); + let p_ref: &mut DummyState = unsafe { &mut *p_ptr }; - let p_ref: &DummyState = unsafe { &*p }; let obj_ref: &Object = p_ref.upcast(); - assert_eq!(addr_of!(*obj_ref), p.cast()); + assert_eq!(addr_of!(*obj_ref), p_ptr.cast()); let sbd_ref: Option<&SysBusDevice> = obj_ref.dynamic_cast(); assert!(sbd_ref.is_none()); let dev_ref: Option<&DeviceState> = obj_ref.downcast(); - assert_eq!(addr_of!(*dev_ref.unwrap()), p.cast()); + assert_eq!(addr_of!(*dev_ref.unwrap()), p_ptr.cast()); // SAFETY: the cast is wrong, but the value is only used for comparison unsafe { let sbd_ref: &SysBusDevice = obj_ref.unsafe_cast(); - assert_eq!(addr_of!(*sbd_ref), p.cast()); - - object_unref(p_ref.as_object_mut_ptr().cast::()); + assert_eq!(addr_of!(*sbd_ref), p_ptr.cast()); } } From 66bcc554d27f693f89bf04df24d474463a90a894 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 6 Dec 2024 17:08:49 +0100 Subject: [PATCH 0019/1179] rust: callbacks: allow passing optional callbacks as () In some cases, callbacks are optional. Using "Some(function)" and "None" does not work well, because when someone writes "None" the compiler does not know what to use for "F" in "Option". Therefore, adopt () to mean a "null" callback. It is possible to enforce that a callback is valid by adding a "let _: () = F::ASSERT_IS_SOME" before the invocation of F::call. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/callbacks.rs | 97 ++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/rust/qemu-api/src/callbacks.rs b/rust/qemu-api/src/callbacks.rs index 314f9dce9622..9642a16eb89b 100644 --- a/rust/qemu-api/src/callbacks.rs +++ b/rust/qemu-api/src/callbacks.rs @@ -79,6 +79,31 @@ use std::{mem, ptr::NonNull}; /// call_it(&move |_| String::from(x), "hello workd"); /// ``` /// +/// `()` can be used to indicate "no function": +/// +/// ``` +/// # use qemu_api::callbacks::FnCall; +/// fn optional FnCall<(&'a str,), String>>(_f: &F, s: &str) -> Option { +/// if F::IS_SOME { +/// Some(F::call((s,))) +/// } else { +/// None +/// } +/// } +/// +/// assert!(optional(&(), "hello world").is_none()); +/// ``` +/// +/// Invoking `F::call` will then be a run-time error. +/// +/// ```should_panic +/// # use qemu_api::callbacks::FnCall; +/// # fn call_it FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { +/// # F::call((s,)) +/// # } +/// let s: String = call_it(&(), "hello world"); // panics +/// ``` +/// /// # Safety /// /// Because `Self` is a zero-sized type, all instances of the type are @@ -93,10 +118,70 @@ pub unsafe trait FnCall: 'static + Sync + Sized { /// Rust 1.79.0+. const ASSERT_ZERO_SIZED: () = { assert!(mem::size_of::() == 0) }; + /// Referring to this constant asserts that the `Self` type is an actual + /// function type, which can be used to catch incorrect use of `()` + /// at compile time. + /// + /// # Examples + /// + /// ```compile_fail + /// # use qemu_api::callbacks::FnCall; + /// fn call_it FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { + /// let _: () = F::ASSERT_IS_SOME; + /// F::call((s,)) + /// } + /// + /// let s: String = call_it((), "hello world"); // does not compile + /// ``` + /// + /// Note that this can be more simply `const { assert!(F::IS_SOME) }` in + /// Rust 1.79.0 or newer. + const ASSERT_IS_SOME: () = { assert!(Self::IS_SOME) }; + + /// `true` if `Self` is an actual function type and not `()`. + /// + /// # Examples + /// + /// You can use `IS_SOME` to catch this at compile time: + /// + /// ```compile_fail + /// # use qemu_api::callbacks::FnCall; + /// fn call_it FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { + /// const { assert!(F::IS_SOME) } + /// F::call((s,)) + /// } + /// + /// let s: String = call_it((), "hello world"); // does not compile + /// ``` + const IS_SOME: bool; + + /// `false` if `Self` is an actual function type, `true` if it is `()`. + fn is_none() -> bool { + !Self::IS_SOME + } + + /// `true` if `Self` is an actual function type, `false` if it is `()`. + fn is_some() -> bool { + Self::IS_SOME + } + /// Call the function with the arguments in args. fn call(a: Args) -> R; } +/// `()` acts as a "null" callback. Using `()` and `function` is nicer +/// than `None` and `Some(function)`, because the compiler is unable to +/// infer the type of just `None`. Therefore, the trait itself acts as the +/// option type, with functions [`FnCall::is_some`] and [`FnCall::is_none`]. +unsafe impl FnCall for () { + const IS_SOME: bool = false; + + /// Call the function with the arguments in args. + fn call(_a: Args) -> R { + panic!("callback not specified") + } +} + macro_rules! impl_call { ($($args:ident,)* ) => ( // SAFETY: because each function is treated as a separate type, @@ -106,6 +191,8 @@ macro_rules! impl_call { where F: 'static + Sync + Sized + Fn($($args, )*) -> R, { + const IS_SOME: bool = true; + #[inline(always)] fn call(a: ($($args,)*)) -> R { let _: () = Self::ASSERT_ZERO_SIZED; @@ -141,4 +228,14 @@ mod tests { fn test_call() { assert_eq!(do_test_call(&str::to_owned), "hello world") } + + // The `_f` parameter is unused but it helps the compiler infer `F`. + fn do_test_is_some<'a, F: FnCall<(&'a str,), String>>(_f: &F) { + assert!(F::is_some()); + } + + #[test] + fn test_is_some() { + do_test_is_some(&str::to_owned); + } } From 201ef001dd40fdb11c83f3e47604219c374590ec Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 17 Jan 2025 11:21:26 +0100 Subject: [PATCH 0020/1179] rust: qdev: add clock creation Add a Rust version of qdev_init_clock_in, which can be used in instance_init. There are a couple differences with the C version: - in Rust the object keeps its own reference to the clock (in addition to the one embedded in the NamedClockList), and the reference is dropped automatically by instance_finalize(); this is encoded in the signature of DeviceClassMethods::init_clock_in, which makes the lifetime of the clock independent of that of the object it holds. This goes unnoticed in the C version and is due to the existence of aliases. - also, anything that happens during instance_init uses the pinned_init framework to operate on a partially initialized object, and is done through class methods (i.e. through DeviceClassMethods rather than DeviceMethods) because the device does not exist yet. Therefore, Rust code *must* create clocks from instance_init, which is stricter than C. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 43 +++++-------- rust/qemu-api/src/prelude.rs | 2 + rust/qemu-api/src/qdev.rs | 107 ++++++++++++++++++++++++++++++- rust/qemu-api/src/vmstate.rs | 4 +- 4 files changed, 125 insertions(+), 31 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index f5db114b0c7b..37936a328b81 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -10,17 +10,16 @@ use std::{ use qemu_api::{ bindings::{ - error_fatal, hwaddr, memory_region_init_io, qdev_init_clock_in, qdev_prop_set_chr, - qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, - qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, sysbus_mmio_map, sysbus_realize, - CharBackend, Chardev, Clock, ClockEvent, MemoryRegion, QEMUChrEvent, - CHR_IOCTL_SERIAL_SET_BREAK, + error_fatal, hwaddr, memory_region_init_io, qdev_prop_set_chr, qemu_chr_fe_accept_input, + qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, qemu_chr_fe_write_all, qemu_irq, + sysbus_connect_irq, sysbus_mmio_map, sysbus_realize, CharBackend, Chardev, MemoryRegion, + QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, }, c_str, impl_vmstate_forward, irq::InterruptSource, prelude::*, - qdev::{DeviceImpl, DeviceState, Property}, - qom::{ClassInitImpl, ObjectImpl, ParentField}, + qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property}, + qom::{ClassInitImpl, ObjectImpl, Owned, ParentField}, sysbus::{SysBusDevice, SysBusDeviceClass}, vmstate::VMStateDescription, }; @@ -131,7 +130,7 @@ pub struct PL011State { #[doc(alias = "irq")] pub interrupts: [InterruptSource; IRQMASK.len()], #[doc(alias = "clk")] - pub clock: NonNull, + pub clock: Owned, #[doc(alias = "migrate_clk")] pub migrate_clock: bool, } @@ -485,8 +484,6 @@ impl PL011State { /// location/instance. All its fields are expected to hold unitialized /// values with the sole exception of `parent_obj`. unsafe fn init(&mut self) { - const CLK_NAME: &CStr = c_str!("clk"); - // SAFETY: // // self and self.iomem are guaranteed to be valid at this point since callers @@ -506,22 +503,16 @@ impl PL011State { // SAFETY: // - // self.clock is not initialized at this point; but since `NonNull<_>` is Copy, - // we can overwrite the undefined value without side effects. This is - // safe since all PL011State instances are created by QOM code which - // calls this function to initialize the fields; therefore no code is - // able to access an invalid self.clock value. - unsafe { - let dev: &mut DeviceState = self.upcast_mut(); - self.clock = NonNull::new(qdev_init_clock_in( - dev, - CLK_NAME.as_ptr(), - None, /* pl011_clock_update */ - addr_of_mut!(*self).cast::(), - ClockEvent::ClockUpdate.0, - )) - .unwrap(); - } + // self.clock is not initialized at this point; but since `Owned<_>` is + // not Drop, we can overwrite the undefined value without side effects; + // it's not sound but, because for all PL011State instances are created + // by QOM code which calls this function to initialize the fields, at + // leastno code is able to access an invalid self.clock value. + self.clock = self.init_clock_in("clk", &Self::clock_update, ClockEvent::ClockUpdate); + } + + const fn clock_update(&self, _event: ClockEvent) { + /* pl011_trace_baudrate_change(s); */ } fn post_init(&self) { diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 3df6a5c21ec7..87e3ce90f26e 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -7,6 +7,8 @@ pub use crate::bitops::IntegerExt; pub use crate::cell::BqlCell; pub use crate::cell::BqlRefCell; +pub use crate::qdev::DeviceMethods; + pub use crate::qom::IsA; pub use crate::qom::Object; pub use crate::qom::ObjectCast; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index f4c75c752f17..176c69a56001 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -4,14 +4,20 @@ //! Bindings to create devices and access device functionality from Rust. -use std::{ffi::CStr, ptr::NonNull}; +use std::{ + ffi::{CStr, CString}, + os::raw::c_void, + ptr::NonNull, +}; -pub use bindings::{DeviceClass, DeviceState, Property}; +pub use bindings::{Clock, ClockEvent, DeviceClass, DeviceState, Property}; use crate::{ bindings::{self, Error}, + callbacks::FnCall, + cell::bql_locked, prelude::*, - qom::{ClassInitImpl, ObjectClass}, + qom::{ClassInitImpl, ObjectClass, Owned}, vmstate::VMStateDescription, }; @@ -143,3 +149,98 @@ unsafe impl ObjectType for DeviceState { unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; } qom_isa!(DeviceState: Object); + +/// Trait for methods exposed by the [`DeviceState`] class. The methods can be +/// called on all objects that have the trait `IsA`. +/// +/// The trait should only be used through the blanket implementation, +/// which guarantees safety via `IsA`. +pub trait DeviceMethods: ObjectDeref +where + Self::Target: IsA, +{ + /// Add an input clock named `name`. Invoke the callback with + /// `self` as the first parameter for the events that are requested. + /// + /// The resulting clock is added as a child of `self`, but it also + /// stays alive until after `Drop::drop` is called because C code + /// keeps an extra reference to it until `device_finalize()` calls + /// `qdev_finalize_clocklist()`. Therefore (unlike most cases in + /// which Rust code has a reference to a child object) it would be + /// possible for this function to return a `&Clock` too. + #[inline] + fn init_clock_in FnCall<(&'a Self::Target, ClockEvent)>>( + &self, + name: &str, + _cb: &F, + events: ClockEvent, + ) -> Owned { + fn do_init_clock_in( + dev: *mut DeviceState, + name: &str, + cb: Option, + events: ClockEvent, + ) -> Owned { + assert!(bql_locked()); + + // SAFETY: the clock is heap allocated, but qdev_init_clock_in() + // does not gift the reference to its caller; so use Owned::from to + // add one. The callback is disabled automatically when the clock + // is unparented, which happens before the device is finalized. + unsafe { + let cstr = CString::new(name).unwrap(); + let clk = bindings::qdev_init_clock_in( + dev, + cstr.as_ptr(), + cb, + dev.cast::(), + events.0, + ); + + Owned::from(&*clk) + } + } + + let cb: Option = if F::is_some() { + unsafe extern "C" fn rust_clock_cb FnCall<(&'a T, ClockEvent)>>( + opaque: *mut c_void, + event: ClockEvent, + ) { + // SAFETY: the opaque is "this", which is indeed a pointer to T + F::call((unsafe { &*(opaque.cast::()) }, event)) + } + Some(rust_clock_cb::) + } else { + None + }; + + do_init_clock_in(self.as_mut_ptr(), name, cb, events) + } + + /// Add an output clock named `name`. + /// + /// The resulting clock is added as a child of `self`, but it also + /// stays alive until after `Drop::drop` is called because C code + /// keeps an extra reference to it until `device_finalize()` calls + /// `qdev_finalize_clocklist()`. Therefore (unlike most cases in + /// which Rust code has a reference to a child object) it would be + /// possible for this function to return a `&Clock` too. + #[inline] + fn init_clock_out(&self, name: &str) -> Owned { + unsafe { + let cstr = CString::new(name).unwrap(); + let clk = bindings::qdev_init_clock_out(self.as_mut_ptr(), cstr.as_ptr()); + + Owned::from(&*clk) + } + } +} + +impl DeviceMethods for R where R::Target: IsA {} + +unsafe impl ObjectType for Clock { + type Class = ObjectClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_CLOCK) }; +} +qom_isa!(Clock: Object); diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 11d21b8791ce..164effc65539 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -470,11 +470,11 @@ macro_rules! vmstate_clock { $crate::assert_field_type!( $struct_name, $field_name, - core::ptr::NonNull<$crate::bindings::Clock> + $crate::qom::Owned<$crate::bindings::Clock> ); $crate::offset_of!($struct_name, $field_name) }, - size: ::core::mem::size_of::<*const $crate::bindings::Clock>(), + size: ::core::mem::size_of::<*const $crate::qdev::Clock>(), flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0), vmsd: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) }, ..$crate::zeroable::Zeroable::ZERO From 688c67415858684a2feef4477e6bc8159ac090bd Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 17 Jan 2025 11:23:14 +0100 Subject: [PATCH 0021/1179] rust: qom: allow initializing interface vtables Unlike regular classes, interface vtables can only be obtained via object_class_dynamic_cast. Provide a wrapper that allows accessing the vtable and pass it to a ClassInitImpl implementation, for example ClassInitImpl. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/prelude.rs | 1 + rust/qemu-api/src/qom.rs | 45 ++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 87e3ce90f26e..254edb476dd1 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -9,6 +9,7 @@ pub use crate::cell::BqlRefCell; pub use crate::qdev::DeviceMethods; +pub use crate::qom::InterfaceType; pub use crate::qom::IsA; pub use crate::qom::Object; pub use crate::qom::ObjectCast; diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 3e63cb30ca6b..3d5ab2d90186 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -66,8 +66,8 @@ pub use bindings::{Object, ObjectClass}; use crate::{ bindings::{ - self, object_dynamic_cast, object_get_class, object_get_typename, object_new, object_ref, - object_unref, TypeInfo, + self, object_class_dynamic_cast, object_dynamic_cast, object_get_class, + object_get_typename, object_new, object_ref, object_unref, TypeInfo, }, cell::bql_locked, }; @@ -263,6 +263,47 @@ pub unsafe trait ObjectType: Sized { } } +/// Trait exposed by all structs corresponding to QOM interfaces. +/// Unlike `ObjectType`, it is implemented on the class type (which provides +/// the vtable for the interfaces). +/// +/// # Safety +/// +/// `TYPE` must match the contents of the `TypeInfo` as found in the C code; +/// right now, interfaces can only be declared in C. +pub unsafe trait InterfaceType: Sized { + /// The name of the type, which can be passed to + /// `object_class_dynamic_cast()` to obtain the pointer to the vtable + /// for this interface. + const TYPE_NAME: &'static CStr; + + /// Initialize the vtable for the interface; the generic argument `T` is the + /// type being initialized, while the generic argument `U` is the type that + /// lists the interface in its `TypeInfo`. + /// + /// # Panics + /// + /// Panic if the incoming argument if `T` does not implement the interface. + fn interface_init< + T: ObjectType + ClassInitImpl + ClassInitImpl, + U: ObjectType, + >( + klass: &mut U::Class, + ) { + unsafe { + // SAFETY: upcasting to ObjectClass is always valid, and the + // return type is either NULL or the argument itself + let result: *mut Self = object_class_dynamic_cast( + (klass as *mut U::Class).cast(), + Self::TYPE_NAME.as_ptr(), + ) + .cast(); + + >::class_init(result.as_mut().unwrap()) + } + } +} + /// This trait provides safe casting operations for QOM objects to raw pointers, /// to be used for example for FFI. The trait can be applied to any kind of /// reference or smart pointers, and enforces correctness through the [`IsA`] From 68da5402df003a855c581563acc6f5f8c5d563f0 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 7 Jan 2025 12:01:18 +0100 Subject: [PATCH 0022/1179] rust: qdev: make ObjectImpl a supertrait of DeviceImpl In practice it has to be implemented always in order to access an implementation of ClassInitImpl. Make the relationship explicit in the code. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/qdev.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 176c69a56001..34d24da4b637 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -17,12 +17,12 @@ use crate::{ callbacks::FnCall, cell::bql_locked, prelude::*, - qom::{ClassInitImpl, ObjectClass, Owned}, + qom::{ClassInitImpl, ObjectClass, ObjectImpl, Owned}, vmstate::VMStateDescription, }; /// Trait providing the contents of [`DeviceClass`]. -pub trait DeviceImpl { +pub trait DeviceImpl: ObjectImpl { /// _Realization_ is the second stage of device creation. It contains /// all operations that depend on device properties and can fail (note: /// this is not yet supported for Rust devices). From 5472a38cb9e10bda897fc29d4841c00476f22585 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 17 Jan 2025 11:26:48 +0100 Subject: [PATCH 0023/1179] rust: qdev: switch from legacy reset to Resettable Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- meson.build | 1 + rust/hw/char/pl011/src/device.rs | 10 ++- rust/qemu-api/src/qdev.rs | 111 ++++++++++++++++++++++++------- rust/qemu-api/tests/tests.rs | 5 +- 4 files changed, 99 insertions(+), 28 deletions(-) diff --git a/meson.build b/meson.build index 18cf9e2913b9..16c76c493f3d 100644 --- a/meson.build +++ b/meson.build @@ -4073,6 +4073,7 @@ if have_rust 'MigrationPriority', 'QEMUChrEvent', 'QEMUClockType', + 'ResetType', 'device_endian', 'module_init_type', ] diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 37936a328b81..1d0390b4fbeb 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -18,7 +18,7 @@ use qemu_api::{ c_str, impl_vmstate_forward, irq::InterruptSource, prelude::*, - qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property}, + qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, qom::{ClassInitImpl, ObjectImpl, Owned, ParentField}, sysbus::{SysBusDevice, SysBusDeviceClass}, vmstate::VMStateDescription, @@ -171,7 +171,10 @@ impl DeviceImpl for PL011State { Some(&device_class::VMSTATE_PL011) } const REALIZE: Option = Some(Self::realize); - const RESET: Option = Some(Self::reset); +} + +impl ResettablePhasesImpl for PL011State { + const HOLD: Option = Some(Self::reset_hold); } impl PL011Registers { @@ -622,7 +625,7 @@ impl PL011State { } } - pub fn reset(&self) { + pub fn reset_hold(&self, _type: ResetType) { self.regs.borrow_mut().reset(); } @@ -737,3 +740,4 @@ impl ObjectImpl for PL011Luminary { } impl DeviceImpl for PL011Luminary {} +impl ResettablePhasesImpl for PL011Luminary {} diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 34d24da4b637..64ba3d909818 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -10,10 +10,10 @@ use std::{ ptr::NonNull, }; -pub use bindings::{Clock, ClockEvent, DeviceClass, DeviceState, Property}; +pub use bindings::{Clock, ClockEvent, DeviceClass, DeviceState, Property, ResetType}; use crate::{ - bindings::{self, Error}, + bindings::{self, Error, ResettableClass}, callbacks::FnCall, cell::bql_locked, prelude::*, @@ -21,8 +21,70 @@ use crate::{ vmstate::VMStateDescription, }; +/// Trait providing the contents of the `ResettablePhases` struct, +/// which is part of the QOM `Resettable` interface. +pub trait ResettablePhasesImpl { + /// If not None, this is called when the object enters reset. It + /// can reset local state of the object, but it must not do anything that + /// has a side-effect on other objects, such as raising or lowering an + /// [`InterruptSource`](crate::irq::InterruptSource), or reading or + /// writing guest memory. It takes the reset's type as argument. + const ENTER: Option = None; + + /// If not None, this is called when the object for entry into reset, once + /// every object in the system which is being reset has had its + /// `ResettablePhasesImpl::ENTER` method called. At this point devices + /// can do actions that affect other objects. + /// + /// If in doubt, implement this method. + const HOLD: Option = None; + + /// If not None, this phase is called when the object leaves the reset + /// state. Actions affecting other objects are permitted. + const EXIT: Option = None; +} + +/// # Safety +/// +/// We expect the FFI user of this function to pass a valid pointer that +/// can be downcasted to type `T`. We also expect the device is +/// readable/writeable from one thread at any time. +unsafe extern "C" fn rust_resettable_enter_fn( + obj: *mut Object, + typ: ResetType, +) { + let state = NonNull::new(obj).unwrap().cast::(); + T::ENTER.unwrap()(unsafe { state.as_ref() }, typ); +} + +/// # Safety +/// +/// We expect the FFI user of this function to pass a valid pointer that +/// can be downcasted to type `T`. We also expect the device is +/// readable/writeable from one thread at any time. +unsafe extern "C" fn rust_resettable_hold_fn( + obj: *mut Object, + typ: ResetType, +) { + let state = NonNull::new(obj).unwrap().cast::(); + T::HOLD.unwrap()(unsafe { state.as_ref() }, typ); +} + +/// # Safety +/// +/// We expect the FFI user of this function to pass a valid pointer that +/// can be downcasted to type `T`. We also expect the device is +/// readable/writeable from one thread at any time. +unsafe extern "C" fn rust_resettable_exit_fn( + obj: *mut Object, + typ: ResetType, +) { + let state = NonNull::new(obj).unwrap().cast::(); + T::EXIT.unwrap()(unsafe { state.as_ref() }, typ); +} + /// Trait providing the contents of [`DeviceClass`]. -pub trait DeviceImpl: ObjectImpl { +pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl { /// _Realization_ is the second stage of device creation. It contains /// all operations that depend on device properties and can fail (note: /// this is not yet supported for Rust devices). @@ -31,13 +93,6 @@ pub trait DeviceImpl: ObjectImpl { /// with the function pointed to by `REALIZE`. const REALIZE: Option = None; - /// If not `None`, the parent class's `reset` method is overridden - /// with the function pointed to by `RESET`. - /// - /// Rust does not yet support the three-phase reset protocol; this is - /// usually okay for leaf classes. - const RESET: Option = None; - /// An array providing the properties that the user can set on the /// device. Not a `const` because referencing statics in constants /// is unstable until Rust 1.83.0. @@ -65,29 +120,36 @@ unsafe extern "C" fn rust_realize_fn(dev: *mut DeviceState, _errp T::REALIZE.unwrap()(unsafe { state.as_ref() }); } -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer that -/// can be downcasted to type `T`. We also expect the device is -/// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_reset_fn(dev: *mut DeviceState) { - let mut state = NonNull::new(dev).unwrap().cast::(); - T::RESET.unwrap()(unsafe { state.as_mut() }); +unsafe impl InterfaceType for ResettableClass { + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_RESETTABLE_INTERFACE) }; +} + +impl ClassInitImpl for T +where + T: ResettablePhasesImpl, +{ + fn class_init(rc: &mut ResettableClass) { + if ::ENTER.is_some() { + rc.phases.enter = Some(rust_resettable_enter_fn::); + } + if ::HOLD.is_some() { + rc.phases.hold = Some(rust_resettable_hold_fn::); + } + if ::EXIT.is_some() { + rc.phases.exit = Some(rust_resettable_exit_fn::); + } + } } impl ClassInitImpl for T where - T: ClassInitImpl + DeviceImpl, + T: ClassInitImpl + ClassInitImpl + DeviceImpl, { fn class_init(dc: &mut DeviceClass) { if ::REALIZE.is_some() { dc.realize = Some(rust_realize_fn::); } - if ::RESET.is_some() { - unsafe { - bindings::device_class_set_legacy_reset(dc, Some(rust_reset_fn::)); - } - } if let Some(vmsd) = ::vmsd() { dc.vmsd = vmsd; } @@ -98,6 +160,7 @@ where } } + ResettableClass::interface_init::(dc); >::class_init(&mut dc.parent_class); } } diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 10748fba1970..92dbfb8a0c86 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -13,7 +13,7 @@ use qemu_api::{ cell::{self, BqlCell}, declare_properties, define_property, prelude::*, - qdev::{DeviceClass, DeviceImpl, DeviceState, Property}, + qdev::{DeviceClass, DeviceImpl, DeviceState, Property, ResettablePhasesImpl}, qom::{ClassInitImpl, ObjectImpl, ParentField}, vmstate::VMStateDescription, zeroable::Zeroable, @@ -61,6 +61,8 @@ impl ObjectImpl for DummyState { const ABSTRACT: bool = false; } +impl ResettablePhasesImpl for DummyState {} + impl DeviceImpl for DummyState { fn properties() -> &'static [Property] { &DUMMY_PROPERTIES @@ -101,6 +103,7 @@ impl ObjectImpl for DummyChildState { const ABSTRACT: bool = false; } +impl ResettablePhasesImpl for DummyChildState {} impl DeviceImpl for DummyChildState {} impl ClassInitImpl for DummyChildState { From d449d29a99dc132d4a49351e3501b6bff7500784 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 13 Dec 2024 17:09:35 +0100 Subject: [PATCH 0024/1179] rust: bindings: add Send and Sync markers for types that have bindings This is needed for the MemoryRegionOps to be declared as static; Rust requires static elements to be Sync. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 46 +++++++++++++++++++++++++++++++++++ rust/qemu-api/src/irq.rs | 3 +++ 2 files changed, 49 insertions(+) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index 8a9b821bb918..b71220113ef5 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -21,9 +21,55 @@ include!("bindings.inc.rs"); #[cfg(not(MESON))] include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); +// SAFETY: these are implemented in C; the bindings need to assert that the +// BQL is taken, either directly or via `BqlCell` and `BqlRefCell`. +unsafe impl Send for BusState {} +unsafe impl Sync for BusState {} + +unsafe impl Send for CharBackend {} +unsafe impl Sync for CharBackend {} + +unsafe impl Send for Chardev {} +unsafe impl Sync for Chardev {} + +unsafe impl Send for Clock {} +unsafe impl Sync for Clock {} + +unsafe impl Send for DeviceState {} +unsafe impl Sync for DeviceState {} + +unsafe impl Send for MemoryRegion {} +unsafe impl Sync for MemoryRegion {} + +unsafe impl Send for ObjectClass {} +unsafe impl Sync for ObjectClass {} + +unsafe impl Send for Object {} +unsafe impl Sync for Object {} + +unsafe impl Send for SysBusDevice {} +unsafe impl Sync for SysBusDevice {} + +// SAFETY: this is a pure data struct +unsafe impl Send for CoalescedMemoryRange {} +unsafe impl Sync for CoalescedMemoryRange {} + +// SAFETY: these are constants and vtables; the Send and Sync requirements +// are deferred to the unsafe callbacks that they contain +unsafe impl Send for MemoryRegionOps {} +unsafe impl Sync for MemoryRegionOps {} + unsafe impl Send for Property {} unsafe impl Sync for Property {} + +unsafe impl Send for TypeInfo {} unsafe impl Sync for TypeInfo {} + +unsafe impl Send for VMStateDescription {} unsafe impl Sync for VMStateDescription {} + +unsafe impl Send for VMStateField {} unsafe impl Sync for VMStateField {} + +unsafe impl Send for VMStateInfo {} unsafe impl Sync for VMStateInfo {} diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 378e5202951a..638545c3a649 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -43,6 +43,9 @@ where _marker: PhantomData, } +// SAFETY: the implementation asserts via `BqlCell` that the BQL is taken +unsafe impl Sync for InterruptSource where c_int: From {} + impl InterruptSource { /// Send a low (`false`) value to the interrupt sink. pub fn lower(&self) { From 590faa03ee64b4221d1be39949190e82e361efb7 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 17 Jan 2025 18:14:25 +0100 Subject: [PATCH 0025/1179] rust: bindings for MemoryRegionOps Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 1 + rust/hw/char/pl011/src/device.rs | 51 +++---- rust/hw/char/pl011/src/lib.rs | 1 - rust/hw/char/pl011/src/memory_ops.rs | 34 ----- rust/qemu-api/meson.build | 1 + rust/qemu-api/src/lib.rs | 1 + rust/qemu-api/src/memory.rs | 191 +++++++++++++++++++++++++++ rust/qemu-api/src/sysbus.rs | 7 +- rust/qemu-api/src/zeroable.rs | 1 + 9 files changed, 227 insertions(+), 61 deletions(-) delete mode 100644 rust/hw/char/pl011/src/memory_ops.rs create mode 100644 rust/qemu-api/src/memory.rs diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 8cccca7a734d..a5399db50b58 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -180,6 +180,7 @@ module status ``cell`` stable ``c_str`` complete ``irq`` complete +``memory`` stable ``module`` complete ``offset_of`` stable ``qdev`` stable diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 1d0390b4fbeb..5e4e75133c84 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -10,13 +10,14 @@ use std::{ use qemu_api::{ bindings::{ - error_fatal, hwaddr, memory_region_init_io, qdev_prop_set_chr, qemu_chr_fe_accept_input, - qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, qemu_chr_fe_write_all, qemu_irq, - sysbus_connect_irq, sysbus_mmio_map, sysbus_realize, CharBackend, Chardev, MemoryRegion, - QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, + error_fatal, qdev_prop_set_chr, qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, + qemu_chr_fe_set_handlers, qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, + sysbus_mmio_map, sysbus_realize, CharBackend, Chardev, QEMUChrEvent, + CHR_IOCTL_SERIAL_SET_BREAK, }, c_str, impl_vmstate_forward, irq::InterruptSource, + memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, prelude::*, qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, qom::{ClassInitImpl, ObjectImpl, Owned, ParentField}, @@ -26,7 +27,6 @@ use qemu_api::{ use crate::{ device_class, - memory_ops::PL011_OPS, registers::{self, Interrupt}, RegisterOffset, }; @@ -487,20 +487,24 @@ impl PL011State { /// location/instance. All its fields are expected to hold unitialized /// values with the sole exception of `parent_obj`. unsafe fn init(&mut self) { + static PL011_OPS: MemoryRegionOps = MemoryRegionOpsBuilder::::new() + .read(&PL011State::read) + .write(&PL011State::write) + .native_endian() + .impl_sizes(4, 4) + .build(); + // SAFETY: // // self and self.iomem are guaranteed to be valid at this point since callers // must make sure the `self` reference is valid. - unsafe { - memory_region_init_io( - addr_of_mut!(self.iomem), - addr_of_mut!(*self).cast::(), - &PL011_OPS, - addr_of_mut!(*self).cast::(), - Self::TYPE_NAME.as_ptr(), - 0x1000, - ); - } + MemoryRegion::init_io( + unsafe { &mut *addr_of_mut!(self.iomem) }, + addr_of_mut!(*self), + &PL011_OPS, + "pl011", + 0x1000, + ); self.regs = Default::default(); @@ -525,7 +529,7 @@ impl PL011State { } } - pub fn read(&mut self, offset: hwaddr, _size: u32) -> u64 { + pub fn read(&self, offset: hwaddr, _size: u32) -> u64 { match RegisterOffset::try_from(offset) { Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => { let device_id = self.get_class().device_id; @@ -540,7 +544,7 @@ impl PL011State { if update_irq { self.update(); unsafe { - qemu_chr_fe_accept_input(&mut self.char_backend); + qemu_chr_fe_accept_input(addr_of!(self.char_backend) as *mut _); } } result.into() @@ -548,7 +552,7 @@ impl PL011State { } } - pub fn write(&mut self, offset: hwaddr, value: u64) { + pub fn write(&self, offset: hwaddr, value: u64, _size: u32) { let mut update_irq = false; if let Ok(field) = RegisterOffset::try_from(offset) { // qemu_chr_fe_write_all() calls into the can_receive @@ -561,14 +565,15 @@ impl PL011State { // XXX this blocks entire thread. Rewrite to use // qemu_chr_fe_write and background I/O callbacks unsafe { - qemu_chr_fe_write_all(&mut self.char_backend, &ch, 1); + qemu_chr_fe_write_all(addr_of!(self.char_backend) as *mut _, &ch, 1); } } - update_irq = self - .regs - .borrow_mut() - .write(field, value as u32, &mut self.char_backend); + update_irq = self.regs.borrow_mut().write( + field, + value as u32, + addr_of!(self.char_backend) as *mut _, + ); } else { eprintln!("write bad offset {offset} value {value}"); } diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 3c72f1221ffd..1bf46c65af24 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -18,7 +18,6 @@ use qemu_api::c_str; mod device; mod device_class; -mod memory_ops; pub use device::pl011_create; diff --git a/rust/hw/char/pl011/src/memory_ops.rs b/rust/hw/char/pl011/src/memory_ops.rs deleted file mode 100644 index 432d32638989..000000000000 --- a/rust/hw/char/pl011/src/memory_ops.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis -// SPDX-License-Identifier: GPL-2.0-or-later - -use core::ptr::NonNull; -use std::os::raw::{c_uint, c_void}; - -use qemu_api::{bindings::*, zeroable::Zeroable}; - -use crate::device::PL011State; - -pub static PL011_OPS: MemoryRegionOps = MemoryRegionOps { - read: Some(pl011_read), - write: Some(pl011_write), - read_with_attrs: None, - write_with_attrs: None, - endianness: device_endian::DEVICE_NATIVE_ENDIAN, - valid: Zeroable::ZERO, - impl_: MemoryRegionOps__bindgen_ty_2 { - min_access_size: 4, - max_access_size: 4, - ..Zeroable::ZERO - }, -}; - -unsafe extern "C" fn pl011_read(opaque: *mut c_void, addr: hwaddr, size: c_uint) -> u64 { - let mut state = NonNull::new(opaque).unwrap().cast::(); - unsafe { state.as_mut() }.read(addr, size) -} - -unsafe extern "C" fn pl011_write(opaque: *mut c_void, addr: hwaddr, data: u64, _size: c_uint) { - let mut state = NonNull::new(opaque).unwrap().cast::(); - unsafe { state.as_mut() }.write(addr, data); -} diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 60944a657de5..80eafc7f6bd8 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -22,6 +22,7 @@ _qemu_api_rs = static_library( 'src/cell.rs', 'src/c_str.rs', 'src/irq.rs', + 'src/memory.rs', 'src/module.rs', 'src/offset_of.rs', 'src/prelude.rs', diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 3cf9371cff05..e4316b21cf24 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -19,6 +19,7 @@ pub mod c_str; pub mod callbacks; pub mod cell; pub mod irq; +pub mod memory; pub mod module; pub mod offset_of; pub mod qdev; diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs new file mode 100644 index 000000000000..963d689c27d4 --- /dev/null +++ b/rust/qemu-api/src/memory.rs @@ -0,0 +1,191 @@ +// Copyright 2024 Red Hat, Inc. +// Author(s): Paolo Bonzini +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings for `MemoryRegion` and `MemoryRegionOps` + +use std::{ + ffi::{CStr, CString}, + marker::{PhantomData, PhantomPinned}, + os::raw::{c_uint, c_void}, + ptr::addr_of, +}; + +pub use bindings::hwaddr; + +use crate::{ + bindings::{self, device_endian, memory_region_init_io}, + callbacks::FnCall, + prelude::*, + zeroable::Zeroable, +}; + +pub struct MemoryRegionOps( + bindings::MemoryRegionOps, + // Note: quite often you'll see PhantomData mentioned when discussing + // covariance and contravariance; you don't need any of those to understand + // this usage of PhantomData. Quite simply, MemoryRegionOps *logically* + // holds callbacks that take an argument of type &T, except the type is erased + // before the callback is stored in the bindings::MemoryRegionOps field. + // The argument of PhantomData is a function pointer in order to represent + // that relationship; while that will also provide desirable and safe variance + // for T, variance is not the point but just a consequence. + PhantomData, +); + +// SAFETY: When a *const T is passed to the callbacks, the call itself +// is done in a thread-safe manner. The invocation is okay as long as +// T itself is `Sync`. +unsafe impl Sync for MemoryRegionOps {} + +#[derive(Clone)] +pub struct MemoryRegionOpsBuilder(bindings::MemoryRegionOps, PhantomData); + +unsafe extern "C" fn memory_region_ops_read_cb FnCall<(&'a T, hwaddr, u32), u64>>( + opaque: *mut c_void, + addr: hwaddr, + size: c_uint, +) -> u64 { + F::call((unsafe { &*(opaque.cast::()) }, addr, size)) +} + +unsafe extern "C" fn memory_region_ops_write_cb FnCall<(&'a T, hwaddr, u64, u32)>>( + opaque: *mut c_void, + addr: hwaddr, + data: u64, + size: c_uint, +) { + F::call((unsafe { &*(opaque.cast::()) }, addr, data, size)) +} + +impl MemoryRegionOpsBuilder { + #[must_use] + pub const fn read FnCall<(&'a T, hwaddr, u32), u64>>(mut self, _f: &F) -> Self { + self.0.read = Some(memory_region_ops_read_cb::); + self + } + + #[must_use] + pub const fn write FnCall<(&'a T, hwaddr, u64, u32)>>(mut self, _f: &F) -> Self { + self.0.write = Some(memory_region_ops_write_cb::); + self + } + + #[must_use] + pub const fn big_endian(mut self) -> Self { + self.0.endianness = device_endian::DEVICE_BIG_ENDIAN; + self + } + + #[must_use] + pub const fn little_endian(mut self) -> Self { + self.0.endianness = device_endian::DEVICE_LITTLE_ENDIAN; + self + } + + #[must_use] + pub const fn native_endian(mut self) -> Self { + self.0.endianness = device_endian::DEVICE_NATIVE_ENDIAN; + self + } + + #[must_use] + pub const fn valid_sizes(mut self, min: u32, max: u32) -> Self { + self.0.valid.min_access_size = min; + self.0.valid.max_access_size = max; + self + } + + #[must_use] + pub const fn valid_unaligned(mut self) -> Self { + self.0.valid.unaligned = true; + self + } + + #[must_use] + pub const fn impl_sizes(mut self, min: u32, max: u32) -> Self { + self.0.impl_.min_access_size = min; + self.0.impl_.max_access_size = max; + self + } + + #[must_use] + pub const fn impl_unaligned(mut self) -> Self { + self.0.impl_.unaligned = true; + self + } + + #[must_use] + pub const fn build(self) -> MemoryRegionOps { + MemoryRegionOps::(self.0, PhantomData) + } + + #[must_use] + pub const fn new() -> Self { + Self(bindings::MemoryRegionOps::ZERO, PhantomData) + } +} + +impl Default for MemoryRegionOpsBuilder { + fn default() -> Self { + Self::new() + } +} + +/// A safe wrapper around [`bindings::MemoryRegion`]. Compared to the +/// underlying C struct it is marked as pinned because the QOM tree +/// contains a pointer to it. +pub struct MemoryRegion { + inner: bindings::MemoryRegion, + _pin: PhantomPinned, +} + +impl MemoryRegion { + // inline to ensure that it is not included in tests, which only + // link to hwcore and qom. FIXME: inlining is actually the opposite + // of what we want, since this is the type-erased version of the + // init_io function below. Look into splitting the qemu_api crate. + #[inline(always)] + unsafe fn do_init_io( + slot: *mut bindings::MemoryRegion, + owner: *mut Object, + ops: &'static bindings::MemoryRegionOps, + name: &'static str, + size: u64, + ) { + unsafe { + let cstr = CString::new(name).unwrap(); + memory_region_init_io( + slot, + owner.cast::(), + ops, + owner.cast::(), + cstr.as_ptr(), + size, + ); + } + } + + pub fn init_io>( + &mut self, + owner: *mut T, + ops: &'static MemoryRegionOps, + name: &'static str, + size: u64, + ) { + unsafe { + Self::do_init_io(&mut self.inner, owner.cast::(), &ops.0, name, size); + } + } + + pub(crate) const fn as_mut_ptr(&self) -> *mut bindings::MemoryRegion { + addr_of!(self.inner) as *mut _ + } +} + +unsafe impl ObjectType for MemoryRegion { + type Class = bindings::MemoryRegionClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_MEMORY_REGION) }; +} +qom_isa!(MemoryRegion: Object); diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index e6762b5c1455..c27dbf79e439 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -2,7 +2,7 @@ // Author(s): Paolo Bonzini // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ffi::CStr, ptr::addr_of}; +use std::ffi::CStr; pub use bindings::{SysBusDevice, SysBusDeviceClass}; @@ -10,6 +10,7 @@ use crate::{ bindings, cell::bql_locked, irq::InterruptSource, + memory::MemoryRegion, prelude::*, qdev::{DeviceClass, DeviceState}, qom::ClassInitImpl, @@ -42,10 +43,10 @@ where /// important, since whoever creates the sysbus device will refer to the /// region with a number that corresponds to the order of calls to /// `init_mmio`. - fn init_mmio(&self, iomem: &bindings::MemoryRegion) { + fn init_mmio(&self, iomem: &MemoryRegion) { assert!(bql_locked()); unsafe { - bindings::sysbus_init_mmio(self.as_mut_ptr(), addr_of!(*iomem) as *mut _); + bindings::sysbus_init_mmio(self.as_mut_ptr(), iomem.as_mut_ptr()); } } diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index 7b04947cb6c1..75742b50d4e3 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -100,3 +100,4 @@ impl_zeroable!(crate::bindings::VMStateField); impl_zeroable!(crate::bindings::VMStateDescription); impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1); impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2); +impl_zeroable!(crate::bindings::MemoryRegionOps); From 61faf6ac7b25b9a743817f0c5fc935a6cdfa7dfb Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 3 Feb 2025 11:04:07 +0100 Subject: [PATCH 0026/1179] rust: irq: define ObjectType for IRQState This is a small preparation in order to use an Owned for the argument to sysbus_connect_irq. Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/irq.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 638545c3a649..835b027d5e5a 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -5,11 +5,12 @@ //! Bindings for interrupt sources use core::ptr; -use std::{marker::PhantomData, os::raw::c_int}; +use std::{ffi::CStr, marker::PhantomData, os::raw::c_int}; use crate::{ - bindings::{qemu_set_irq, IRQState}, + bindings::{self, qemu_set_irq}, prelude::*, + qom::ObjectClass, }; /// Interrupt sources are used by devices to pass changes to a value (typically @@ -21,7 +22,8 @@ use crate::{ /// method sends a `true` value to the sink. If the guest has to see a /// different polarity, that change is performed by the board between the /// device and the interrupt controller. -/// +pub type IRQState = bindings::IRQState; + /// Interrupts are implemented as a pointer to the interrupt "sink", which has /// type [`IRQState`]. A device exposes its source as a QOM link property using /// a function such as [`SysBusDeviceMethods::init_irq`], and @@ -91,3 +93,10 @@ impl Default for InterruptSource { } } } + +unsafe impl ObjectType for IRQState { + type Class = ObjectClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_IRQ) }; +} +qom_isa!(IRQState: Object); From a22bd55ffd889f3027c3158d0014c76f204c69dd Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 3 Feb 2025 11:04:07 +0100 Subject: [PATCH 0027/1179] rust: chardev, qdev: add bindings to qdev_prop_set_chr Because the argument to the function is an Owned, this also adds an ObjectType implementation to Chardev. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 3 ++- rust/qemu-api/meson.build | 1 + rust/qemu-api/src/chardev.rs | 19 +++++++++++++++++++ rust/qemu-api/src/lib.rs | 1 + rust/qemu-api/src/qdev.rs | 9 +++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 rust/qemu-api/src/chardev.rs diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 5e4e75133c84..4e9590737110 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -12,9 +12,10 @@ use qemu_api::{ bindings::{ error_fatal, qdev_prop_set_chr, qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, - sysbus_mmio_map, sysbus_realize, CharBackend, Chardev, QEMUChrEvent, + sysbus_mmio_map, sysbus_realize, CharBackend, QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, }, + chardev::Chardev, c_str, impl_vmstate_forward, irq::InterruptSource, memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 80eafc7f6bd8..45e30324b295 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -20,6 +20,7 @@ _qemu_api_rs = static_library( 'src/bitops.rs', 'src/callbacks.rs', 'src/cell.rs', + 'src/chardev.rs', 'src/c_str.rs', 'src/irq.rs', 'src/memory.rs', diff --git a/rust/qemu-api/src/chardev.rs b/rust/qemu-api/src/chardev.rs new file mode 100644 index 000000000000..74cfb634e5fe --- /dev/null +++ b/rust/qemu-api/src/chardev.rs @@ -0,0 +1,19 @@ +// Copyright 2024 Red Hat, Inc. +// Author(s): Paolo Bonzini +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings for character devices + +use std::ffi::CStr; + +use crate::{bindings, prelude::*}; + +pub type Chardev = bindings::Chardev; +pub type ChardevClass = bindings::ChardevClass; + +unsafe impl ObjectType for Chardev { + type Class = ChardevClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_CHARDEV) }; +} +qom_isa!(Chardev: Object); diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index e4316b21cf24..2a338a888a0c 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -18,6 +18,7 @@ pub mod bitops; pub mod c_str; pub mod callbacks; pub mod cell; +pub mod chardev; pub mod irq; pub mod memory; pub mod module; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 64ba3d909818..73343e10b964 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -16,6 +16,7 @@ use crate::{ bindings::{self, Error, ResettableClass}, callbacks::FnCall, cell::bql_locked, + chardev::Chardev, prelude::*, qom::{ClassInitImpl, ObjectClass, ObjectImpl, Owned}, vmstate::VMStateDescription, @@ -297,6 +298,14 @@ where Owned::from(&*clk) } } + + fn prop_set_chr(&self, propname: &str, chr: &Owned) { + assert!(bql_locked()); + let c_propname = CString::new(propname).unwrap(); + unsafe { + bindings::qdev_prop_set_chr(self.as_mut_ptr(), c_propname.as_ptr(), chr.as_mut_ptr()); + } + } } impl DeviceMethods for R where R::Target: IsA {} From 7630ca2a701a0f79728996e660cda06518c97b9b Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 10 Feb 2025 16:11:58 +0100 Subject: [PATCH 0028/1179] rust: pl011: convert pl011_create to safe Rust Not a major change but, as a small but significant step in creating qdev bindings, show how pl011_create can be written without "unsafe" calls (apart from converting pointers to references). This also provides a starting point for creating Error** bindings. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 41 ++++++++++++++++---------------- rust/qemu-api/src/sysbus.rs | 34 +++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 4e9590737110..fe73771021e7 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -10,14 +10,12 @@ use std::{ use qemu_api::{ bindings::{ - error_fatal, qdev_prop_set_chr, qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, - qemu_chr_fe_set_handlers, qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, - sysbus_mmio_map, sysbus_realize, CharBackend, QEMUChrEvent, - CHR_IOCTL_SERIAL_SET_BREAK, + qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, + qemu_chr_fe_write_all, CharBackend, QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, }, chardev::Chardev, - c_str, impl_vmstate_forward, - irq::InterruptSource, + impl_vmstate_forward, + irq::{IRQState, InterruptSource}, memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, prelude::*, qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, @@ -698,26 +696,27 @@ pub unsafe extern "C" fn pl011_event(opaque: *mut c_void, event: QEMUChrEvent) { /// # Safety /// -/// We expect the FFI user of this function to pass a valid pointer for `chr`. +/// We expect the FFI user of this function to pass a valid pointer for `chr` +/// and `irq`. #[no_mangle] pub unsafe extern "C" fn pl011_create( addr: u64, - irq: qemu_irq, + irq: *mut IRQState, chr: *mut Chardev, ) -> *mut DeviceState { - let pl011 = PL011State::new(); - unsafe { - let dev = pl011.as_mut_ptr::(); - qdev_prop_set_chr(dev, c_str!("chardev").as_ptr(), chr); - - let sysbus = pl011.as_mut_ptr::(); - sysbus_realize(sysbus, addr_of_mut!(error_fatal)); - sysbus_mmio_map(sysbus, 0, addr); - sysbus_connect_irq(sysbus, 0, irq); - - // return the pointer, which is kept alive by the QOM tree; drop owned ref - pl011.as_mut_ptr() - } + // SAFETY: The callers promise that they have owned references. + // They do not gift them to pl011_create, so use `Owned::from`. + let irq = unsafe { Owned::::from(&*irq) }; + let chr = unsafe { Owned::::from(&*chr) }; + + let dev = PL011State::new(); + dev.prop_set_chr("chardev", &chr); + dev.sysbus_realize(); + dev.mmio_map(0, addr); + dev.connect_irq(0, &irq); + + // The pointer is kept alive by the QOM tree; drop the owned ref + dev.as_mut_ptr() } #[repr(C)] diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index c27dbf79e439..1f66a5f1e094 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -2,18 +2,18 @@ // Author(s): Paolo Bonzini // SPDX-License-Identifier: GPL-2.0-or-later -use std::ffi::CStr; +use std::{ffi::CStr, ptr::addr_of_mut}; pub use bindings::{SysBusDevice, SysBusDeviceClass}; use crate::{ bindings, cell::bql_locked, - irq::InterruptSource, + irq::{IRQState, InterruptSource}, memory::MemoryRegion, prelude::*, qdev::{DeviceClass, DeviceState}, - qom::ClassInitImpl, + qom::{ClassInitImpl, Owned}, }; unsafe impl ObjectType for SysBusDevice { @@ -60,6 +60,34 @@ where bindings::sysbus_init_irq(self.as_mut_ptr(), irq.as_ptr()); } } + + // TODO: do we want a type like GuestAddress here? + fn mmio_map(&self, id: u32, addr: u64) { + assert!(bql_locked()); + let id: i32 = id.try_into().unwrap(); + unsafe { + bindings::sysbus_mmio_map(self.as_mut_ptr(), id, addr); + } + } + + // Owned<> is used here because sysbus_connect_irq (via + // object_property_set_link) adds a reference to the IRQState, + // which can prolong its life + fn connect_irq(&self, id: u32, irq: &Owned) { + assert!(bql_locked()); + let id: i32 = id.try_into().unwrap(); + unsafe { + bindings::sysbus_connect_irq(self.as_mut_ptr(), id, irq.as_mut_ptr()); + } + } + + fn sysbus_realize(&self) { + // TODO: return an Error + assert!(bql_locked()); + unsafe { + bindings::sysbus_realize(self.as_mut_ptr(), addr_of_mut!(bindings::error_fatal)); + } + } } impl SysBusDeviceMethods for R where R::Target: IsA {} From f32352ff9ea1ce3bdf432e29a587ccf84b1ec57a Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:42 +0800 Subject: [PATCH 0029/1179] i386/fw_cfg: move hpet_cfg definition to hpet.c HPET device needs to access and update hpet_cfg variable, but now it is defined in hw/i386/fw_cfg.c and Rust code can't access it. Move hpet_cfg definition to hpet.c (and rename it to hpet_fw_cfg). This allows Rust HPET device implements its own global hpet_fw_cfg variable, and will further reduce the use of unsafe C code access and calls in the Rust HPET implementation. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-2-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- hw/i386/fw_cfg.c | 6 ++++-- hw/timer/hpet.c | 16 +++++++++------- include/hw/timer/hpet.h | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/hw/i386/fw_cfg.c b/hw/i386/fw_cfg.c index 91bf1df0f2e4..d08aefa02915 100644 --- a/hw/i386/fw_cfg.c +++ b/hw/i386/fw_cfg.c @@ -26,7 +26,9 @@ #include CONFIG_DEVICES #include "target/i386/cpu.h" -struct hpet_fw_config hpet_cfg = {.count = UINT8_MAX}; +#if !defined(CONFIG_HPET) && !defined(CONFIG_X_HPET_RUST) +struct hpet_fw_config hpet_fw_cfg = {.count = UINT8_MAX}; +#endif const char *fw_cfg_arch_key_name(uint16_t key) { @@ -149,7 +151,7 @@ FWCfgState *fw_cfg_arch_create(MachineState *ms, #endif fw_cfg_add_i32(fw_cfg, FW_CFG_IRQ0_OVERRIDE, 1); - fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, &hpet_cfg, sizeof(hpet_cfg)); + fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, &hpet_fw_cfg, sizeof(hpet_fw_cfg)); /* allocate memory for the NUMA channel: one (64bit) word for the number * of nodes, one word for each VCPU->node and one word for each node to * hold the amount of memory. diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c index 1c8c6c69ef5d..dcff18a9871d 100644 --- a/hw/timer/hpet.c +++ b/hw/timer/hpet.c @@ -40,6 +40,8 @@ #include "qom/object.h" #include "trace.h" +struct hpet_fw_config hpet_fw_cfg = {.count = UINT8_MAX}; + #define HPET_MSI_SUPPORT 0 OBJECT_DECLARE_SIMPLE_TYPE(HPETState, HPET) @@ -278,7 +280,7 @@ static int hpet_post_load(void *opaque, int version_id) /* Push number of timers into capability returned via HPET_ID */ s->capability &= ~HPET_ID_NUM_TIM_MASK; s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT; - hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; + hpet_fw_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; /* Derive HPET_MSI_SUPPORT from the capability of the first timer. */ s->flags &= ~(1 << HPET_MSI_SUPPORT); @@ -665,8 +667,8 @@ static void hpet_reset(DeviceState *d) s->hpet_counter = 0ULL; s->hpet_offset = 0ULL; s->config = 0ULL; - hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; - hpet_cfg.hpet[s->hpet_id].address = sbd->mmio[0].addr; + hpet_fw_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; + hpet_fw_cfg.hpet[s->hpet_id].address = sbd->mmio[0].addr; /* to document that the RTC lowers its output on reset as well */ s->rtc_irq_level = 0; @@ -708,17 +710,17 @@ static void hpet_realize(DeviceState *dev, Error **errp) if (!s->intcap) { warn_report("Hpet's intcap not initialized"); } - if (hpet_cfg.count == UINT8_MAX) { + if (hpet_fw_cfg.count == UINT8_MAX) { /* first instance */ - hpet_cfg.count = 0; + hpet_fw_cfg.count = 0; } - if (hpet_cfg.count == 8) { + if (hpet_fw_cfg.count == 8) { error_setg(errp, "Only 8 instances of HPET is allowed"); return; } - s->hpet_id = hpet_cfg.count++; + s->hpet_id = hpet_fw_cfg.count++; for (i = 0; i < HPET_NUM_IRQ_ROUTES; i++) { sysbus_init_irq(sbd, &s->irqs[i]); diff --git a/include/hw/timer/hpet.h b/include/hw/timer/hpet.h index 71e8c62453d1..c2656f7f0bef 100644 --- a/include/hw/timer/hpet.h +++ b/include/hw/timer/hpet.h @@ -73,7 +73,7 @@ struct hpet_fw_config struct hpet_fw_entry hpet[8]; } QEMU_PACKED; -extern struct hpet_fw_config hpet_cfg; +extern struct hpet_fw_config hpet_fw_cfg; #define TYPE_HPET "hpet" From 7f2d4181a3efc3c1fd9de4bdca81317a1116239a Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:43 +0800 Subject: [PATCH 0030/1179] rust/qdev: add the macro to define bit property HPET device (Rust device) needs to define the bit type property. Add a variant of define_property macro to define bit type property. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-3-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/qdev.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 73343e10b964..c44a22876b9d 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -168,6 +168,18 @@ where #[macro_export] macro_rules! define_property { + ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, bit = $bitnr:expr, default = $defval:expr$(,)*) => { + $crate::bindings::Property { + // use associated function syntax for type checking + name: ::std::ffi::CStr::as_ptr($name), + info: $prop, + offset: $crate::offset_of!($state, $field) as isize, + bitnr: $bitnr, + set_default: true, + defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, + ..$crate::zeroable::Zeroable::ZERO + } + }; ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => { $crate::bindings::Property { // use associated function syntax for type checking From e6f1195f55427bf246bb85b1bcbbfd8fbdc51889 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:44 +0800 Subject: [PATCH 0031/1179] rust/irq: Add a helper to convert [InterruptSource] to pointer This is useful when taking an InterruptSource slice and passing it to C function. Suggested-by: Paolo Bonzini Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-4-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/irq.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 835b027d5e5a..672eec1430ff 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -83,6 +83,12 @@ where pub(crate) const fn as_ptr(&self) -> *mut *mut IRQState { self.cell.as_ptr() } + + #[allow(dead_code)] + pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut IRQState { + assert!(!slice.is_empty()); + slice[0].as_ptr() + } } impl Default for InterruptSource { From 9a96d410073df04808c6757fd4aab6cb8684b301 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:45 +0800 Subject: [PATCH 0032/1179] rust: add bindings for gpio_{in|out} initialization Wrap qdev_init_gpio_{in|out} as methods in DeviceMethods. And for qdev_init_gpio_in, based on FnCall, it can support idiomatic Rust callback without the need for C style wrapper. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-5-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/irq.rs | 1 - rust/qemu-api/src/qdev.rs | 47 +++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 672eec1430ff..d1c9dc96eff3 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -84,7 +84,6 @@ where self.cell.as_ptr() } - #[allow(dead_code)] pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut IRQState { assert!(!slice.is_empty()); slice[0].as_ptr() diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index c44a22876b9d..3a7aa4def62d 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -6,17 +6,18 @@ use std::{ ffi::{CStr, CString}, - os::raw::c_void, + os::raw::{c_int, c_void}, ptr::NonNull, }; pub use bindings::{Clock, ClockEvent, DeviceClass, DeviceState, Property, ResetType}; use crate::{ - bindings::{self, Error, ResettableClass}, + bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, Error, ResettableClass}, callbacks::FnCall, cell::bql_locked, chardev::Chardev, + irq::InterruptSource, prelude::*, qom::{ClassInitImpl, ObjectClass, ObjectImpl, Owned}, vmstate::VMStateDescription, @@ -28,8 +29,8 @@ pub trait ResettablePhasesImpl { /// If not None, this is called when the object enters reset. It /// can reset local state of the object, but it must not do anything that /// has a side-effect on other objects, such as raising or lowering an - /// [`InterruptSource`](crate::irq::InterruptSource), or reading or - /// writing guest memory. It takes the reset's type as argument. + /// [`InterruptSource`], or reading or writing guest memory. It takes the + /// reset's type as argument. const ENTER: Option = None; /// If not None, this is called when the object for entry into reset, once @@ -318,6 +319,44 @@ where bindings::qdev_prop_set_chr(self.as_mut_ptr(), c_propname.as_ptr(), chr.as_mut_ptr()); } } + + fn init_gpio_in FnCall<(&'a Self::Target, u32, u32)>>( + &self, + num_lines: u32, + _cb: F, + ) { + let _: () = F::ASSERT_IS_SOME; + + unsafe extern "C" fn rust_irq_handler FnCall<(&'a T, u32, u32)>>( + opaque: *mut c_void, + line: c_int, + level: c_int, + ) { + // SAFETY: the opaque was passed as a reference to `T` + F::call((unsafe { &*(opaque.cast::()) }, line as u32, level as u32)) + } + + let gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int) = + rust_irq_handler::; + + unsafe { + qdev_init_gpio_in( + self.as_mut_ptr::(), + Some(gpio_in_cb), + num_lines as c_int, + ); + } + } + + fn init_gpio_out(&self, pins: &[InterruptSource]) { + unsafe { + qdev_init_gpio_out( + self.as_mut_ptr::(), + InterruptSource::slice_as_ptr(pins), + pins.len() as c_int, + ); + } + } } impl DeviceMethods for R where R::Target: IsA {} From d015d4cbb4d16cf8adc8c10cbd2d0a45f014dad1 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Sat, 25 Jan 2025 20:51:32 +0800 Subject: [PATCH 0033/1179] rust: add bindings for memattrs The MemTxAttrs structure contains bitfield members, and bindgen is unable to generate an equivalent macro definition for MEMTXATTRS_UNSPECIFIED. Therefore, manually define a global constant variable MEMTXATTRS_UNSPECIFIED to support calls from Rust code. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250125125137.1223277-6-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/memory.rs | 16 ++++++++++++++-- rust/qemu-api/src/zeroable.rs | 1 + rust/wrapper.h | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs index 963d689c27d4..682951ab44e1 100644 --- a/rust/qemu-api/src/memory.rs +++ b/rust/qemu-api/src/memory.rs @@ -2,7 +2,7 @@ // Author(s): Paolo Bonzini // SPDX-License-Identifier: GPL-2.0-or-later -//! Bindings for `MemoryRegion` and `MemoryRegionOps` +//! Bindings for `MemoryRegion`, `MemoryRegionOps` and `MemTxAttrs` use std::{ ffi::{CStr, CString}, @@ -11,7 +11,7 @@ use std::{ ptr::addr_of, }; -pub use bindings::hwaddr; +pub use bindings::{hwaddr, MemTxAttrs}; use crate::{ bindings::{self, device_endian, memory_region_init_io}, @@ -189,3 +189,15 @@ unsafe impl ObjectType for MemoryRegion { unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_MEMORY_REGION) }; } qom_isa!(MemoryRegion: Object); + +/// A special `MemTxAttrs` constant, used to indicate that no memory +/// attributes are specified. +/// +/// Bus masters which don't specify any attributes will get this, +/// which has all attribute bits clear except the topmost one +/// (so that we can distinguish "all attributes deliberately clear" +/// from "didn't specify" if necessary). +pub const MEMTXATTRS_UNSPECIFIED: MemTxAttrs = MemTxAttrs { + unspecified: true, + ..Zeroable::ZERO +}; diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index 75742b50d4e3..9f009606b1ab 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -101,3 +101,4 @@ impl_zeroable!(crate::bindings::VMStateDescription); impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1); impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2); impl_zeroable!(crate::bindings::MemoryRegionOps); +impl_zeroable!(crate::bindings::MemTxAttrs); diff --git a/rust/wrapper.h b/rust/wrapper.h index a9bc67af0d5f..54839ce0f510 100644 --- a/rust/wrapper.h +++ b/rust/wrapper.h @@ -62,3 +62,4 @@ typedef enum memory_order { #include "qapi/error.h" #include "migration/vmstate.h" #include "chardev/char-serial.h" +#include "exec/memattrs.h" From eadb83f9a3722045e291b6a73994004007dc4657 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:47 +0800 Subject: [PATCH 0034/1179] rust: add bindings for timer Add timer bindings to help handle idiomatic Rust callbacks. Additionally, wrap QEMUClockType in ClockType binding to avoid unsafe calls in device code. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-7-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 1 + meson.build | 7 +++ rust/qemu-api/meson.build | 1 + rust/qemu-api/src/lib.rs | 1 + rust/qemu-api/src/timer.rs | 98 ++++++++++++++++++++++++++++++++++++++ rust/wrapper.h | 1 + 6 files changed, 109 insertions(+) create mode 100644 rust/qemu-api/src/timer.rs diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index a5399db50b58..90958e5a306c 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -186,6 +186,7 @@ module status ``qdev`` stable ``qom`` stable ``sysbus`` stable +``timer`` stable ``vmstate`` proof of concept ``zeroable`` stable ================ ====================== diff --git a/meson.build b/meson.build index 16c76c493f3d..8ed10b6624e7 100644 --- a/meson.build +++ b/meson.build @@ -4087,6 +4087,13 @@ if have_rust foreach enum : c_bitfields bindgen_args += ['--bitfield-enum', enum] endforeach + c_nocopy = [ + 'QEMUTimer', + ] + # Used to customize Drop trait + foreach struct : c_nocopy + bindgen_args += ['--no-copy', struct] + endforeach # TODO: Remove this comment when the clang/libclang mismatch issue is solved. # diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 45e30324b295..2e9c1078b9b2 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -30,6 +30,7 @@ _qemu_api_rs = static_library( 'src/qdev.rs', 'src/qom.rs', 'src/sysbus.rs', + 'src/timer.rs', 'src/vmstate.rs', 'src/zeroable.rs', ], diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 2a338a888a0c..ed1a8f9a2b43 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -26,6 +26,7 @@ pub mod offset_of; pub mod qdev; pub mod qom; pub mod sysbus; +pub mod timer; pub mod vmstate; pub mod zeroable; diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs new file mode 100644 index 000000000000..a593538917ac --- /dev/null +++ b/rust/qemu-api/src/timer.rs @@ -0,0 +1,98 @@ +// Copyright (C) 2024 Intel Corporation. +// Author(s): Zhao Liu +// SPDX-License-Identifier: GPL-2.0-or-later + +use std::os::raw::{c_int, c_void}; + +use crate::{ + bindings::{self, qemu_clock_get_ns, timer_del, timer_init_full, timer_mod, QEMUClockType}, + callbacks::FnCall, +}; + +pub type Timer = bindings::QEMUTimer; +pub type TimerListGroup = bindings::QEMUTimerListGroup; + +impl Timer { + pub const MS: u32 = bindings::SCALE_MS; + pub const US: u32 = bindings::SCALE_US; + pub const NS: u32 = bindings::SCALE_NS; + + pub fn new() -> Self { + Default::default() + } + + const fn as_mut_ptr(&self) -> *mut Self { + self as *const Timer as *mut _ + } + + pub fn init_full<'timer, 'opaque: 'timer, T, F>( + &'timer mut self, + timer_list_group: Option<&TimerListGroup>, + clk_type: ClockType, + scale: u32, + attributes: u32, + _cb: F, + opaque: &'opaque T, + ) where + F: for<'a> FnCall<(&'a T,)>, + { + let _: () = F::ASSERT_IS_SOME; + + /// timer expiration callback + unsafe extern "C" fn rust_timer_handler FnCall<(&'a T,)>>( + opaque: *mut c_void, + ) { + // SAFETY: the opaque was passed as a reference to `T`. + F::call((unsafe { &*(opaque.cast::()) },)) + } + + let timer_cb: unsafe extern "C" fn(*mut c_void) = rust_timer_handler::; + + // SAFETY: the opaque outlives the timer + unsafe { + timer_init_full( + self, + if let Some(g) = timer_list_group { + g as *const TimerListGroup as *mut _ + } else { + ::core::ptr::null_mut() + }, + clk_type.id, + scale as c_int, + attributes as c_int, + Some(timer_cb), + (opaque as *const T).cast::() as *mut c_void, + ) + } + } + + pub fn modify(&self, expire_time: u64) { + unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) } + } + + pub fn delete(&self) { + unsafe { timer_del(self.as_mut_ptr()) } + } +} + +impl Drop for Timer { + fn drop(&mut self) { + self.delete() + } +} + +pub struct ClockType { + id: QEMUClockType, +} + +impl ClockType { + pub fn get_ns(&self) -> u64 { + // SAFETY: cannot be created outside this module, therefore id + // is valid + (unsafe { qemu_clock_get_ns(self.id) }) as u64 + } +} + +pub const CLOCK_VIRTUAL: ClockType = ClockType { + id: QEMUClockType::QEMU_CLOCK_VIRTUAL, +}; diff --git a/rust/wrapper.h b/rust/wrapper.h index 54839ce0f510..a35bfbd1760d 100644 --- a/rust/wrapper.h +++ b/rust/wrapper.h @@ -63,3 +63,4 @@ typedef enum memory_order { #include "migration/vmstate.h" #include "chardev/char-serial.h" #include "exec/memattrs.h" +#include "qemu/timer.h" From 0534248a6b515cb4dea29a6fd6c256dc77f2a953 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:48 +0800 Subject: [PATCH 0035/1179] rust/timer/hpet: define hpet_fw_cfg Define HPETFwEntry structure with the same memory layout as hpet_fw_entry in C. Further, define the global hpet_cfg variable in Rust which is the same as the C version. This hpet_cfg variable in Rust will replace the C version one and allows both Rust code and C code to access it. The Rust version of hpet_cfg is self-contained, avoiding unsafe access to C code. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-8-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/Cargo.lock | 8 ++++ rust/Cargo.toml | 1 + rust/hw/meson.build | 1 + rust/hw/timer/hpet/Cargo.toml | 18 ++++++++ rust/hw/timer/hpet/meson.build | 18 ++++++++ rust/hw/timer/hpet/src/fw_cfg.rs | 71 ++++++++++++++++++++++++++++++++ rust/hw/timer/hpet/src/lib.rs | 10 +++++ rust/hw/timer/meson.build | 1 + rust/qemu-api/src/zeroable.rs | 6 ++- 9 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 rust/hw/timer/hpet/Cargo.toml create mode 100644 rust/hw/timer/hpet/meson.build create mode 100644 rust/hw/timer/hpet/src/fw_cfg.rs create mode 100644 rust/hw/timer/hpet/src/lib.rs create mode 100644 rust/hw/timer/meson.build diff --git a/rust/Cargo.lock b/rust/Cargo.lock index c0c6069247a8..79e142723b83 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -37,6 +37,14 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "hpet" +version = "0.1.0" +dependencies = [ + "qemu_api", + "qemu_api_macros", +] + [[package]] name = "itertools" version = "0.11.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5b0cb5592867..5041d6291fd1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -4,6 +4,7 @@ members = [ "qemu-api-macros", "qemu-api", "hw/char/pl011", + "hw/timer/hpet", ] [workspace.lints.rust] diff --git a/rust/hw/meson.build b/rust/hw/meson.build index 860196645e71..9749d4adfc96 100644 --- a/rust/hw/meson.build +++ b/rust/hw/meson.build @@ -1 +1,2 @@ subdir('char') +subdir('timer') diff --git a/rust/hw/timer/hpet/Cargo.toml b/rust/hw/timer/hpet/Cargo.toml new file mode 100644 index 000000000000..147f216e7257 --- /dev/null +++ b/rust/hw/timer/hpet/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "hpet" +version = "0.1.0" +edition = "2021" +authors = ["Zhao Liu "] +license = "GPL-2.0-or-later" +description = "IA-PC High Precision Event Timer emulation in Rust" +rust-version = "1.63.0" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +qemu_api = { path = "../../../qemu-api" } +qemu_api_macros = { path = "../../../qemu-api-macros" } + +[lints] +workspace = true diff --git a/rust/hw/timer/hpet/meson.build b/rust/hw/timer/hpet/meson.build new file mode 100644 index 000000000000..c2d7c0532ca4 --- /dev/null +++ b/rust/hw/timer/hpet/meson.build @@ -0,0 +1,18 @@ +_libhpet_rs = static_library( + 'hpet', + files('src/lib.rs'), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + dependencies: [ + qemu_api, + qemu_api_macros, + ], +) + +rust_devices_ss.add(when: 'CONFIG_X_HPET_RUST', if_true: [declare_dependency( + link_whole: [_libhpet_rs], + # Putting proc macro crates in `dependencies` is necessary for Meson to find + # them when compiling the root per-target static rust lib. + dependencies: [qemu_api_macros], + variables: {'crate': 'hpet'}, +)]) diff --git a/rust/hw/timer/hpet/src/fw_cfg.rs b/rust/hw/timer/hpet/src/fw_cfg.rs new file mode 100644 index 000000000000..849e277d483d --- /dev/null +++ b/rust/hw/timer/hpet/src/fw_cfg.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2024 Intel Corporation. +// Author(s): Zhao Liu +// SPDX-License-Identifier: GPL-2.0-or-later + +#![allow(dead_code)] + +use std::ptr::addr_of_mut; + +use qemu_api::{cell::bql_locked, impl_zeroable, zeroable::Zeroable}; + +/// Each `HPETState` represents a Event Timer Block. The v1 spec supports +/// up to 8 blocks. QEMU only uses 1 block (in PC machine). +const HPET_MAX_NUM_EVENT_TIMER_BLOCK: usize = 8; + +#[repr(C, packed)] +#[derive(Copy, Clone, Default)] +pub struct HPETFwEntry { + pub event_timer_block_id: u32, + pub address: u64, + pub min_tick: u16, + pub page_prot: u8, +} +impl_zeroable!(HPETFwEntry); + +#[repr(C, packed)] +#[derive(Copy, Clone, Default)] +pub struct HPETFwConfig { + pub count: u8, + pub hpet: [HPETFwEntry; HPET_MAX_NUM_EVENT_TIMER_BLOCK], +} +impl_zeroable!(HPETFwConfig); + +#[allow(non_upper_case_globals)] +#[no_mangle] +pub static mut hpet_fw_cfg: HPETFwConfig = HPETFwConfig { + count: u8::MAX, + ..Zeroable::ZERO +}; + +impl HPETFwConfig { + pub(crate) fn assign_hpet_id() -> usize { + assert!(bql_locked()); + // SAFETY: all accesses go through these methods, which guarantee + // that the accesses are protected by the BQL. + let mut fw_cfg = unsafe { *addr_of_mut!(hpet_fw_cfg) }; + + if fw_cfg.count == u8::MAX { + // first instance + fw_cfg.count = 0; + } + + if fw_cfg.count == 8 { + // TODO: Add error binding: error_setg() + panic!("Only 8 instances of HPET is allowed"); + } + + let id: usize = fw_cfg.count.into(); + fw_cfg.count += 1; + id + } + + pub(crate) fn update_hpet_cfg(hpet_id: usize, timer_block_id: u32, address: u64) { + assert!(bql_locked()); + // SAFETY: all accesses go through these methods, which guarantee + // that the accesses are protected by the BQL. + let mut fw_cfg = unsafe { *addr_of_mut!(hpet_fw_cfg) }; + + fw_cfg.hpet[hpet_id].event_timer_block_id = timer_block_id; + fw_cfg.hpet[hpet_id].address = address; + } +} diff --git a/rust/hw/timer/hpet/src/lib.rs b/rust/hw/timer/hpet/src/lib.rs new file mode 100644 index 000000000000..44e9f3fb8ab5 --- /dev/null +++ b/rust/hw/timer/hpet/src/lib.rs @@ -0,0 +1,10 @@ +// Copyright (C) 2024 Intel Corporation. +// Author(s): Zhao Liu +// SPDX-License-Identifier: GPL-2.0-or-later + +//! # HPET QEMU Device Model +//! +//! This library implements a device model for the IA-PC HPET (High +//! Precision Event Timers) device in QEMU. + +pub mod fw_cfg; diff --git a/rust/hw/timer/meson.build b/rust/hw/timer/meson.build new file mode 100644 index 000000000000..22a84f15536b --- /dev/null +++ b/rust/hw/timer/meson.build @@ -0,0 +1 @@ +subdir('hpet') diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index 9f009606b1ab..cd424e6ea050 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -56,6 +56,7 @@ pub unsafe trait Zeroable: Default { /// ## Differences with `core::mem::zeroed` /// /// `const_zero` zeroes padding bits, while `core::mem::zeroed` doesn't +#[macro_export] macro_rules! const_zero { // This macro to produce a type-generic zero constant is taken from the // const_zero crate (v0.1.1): @@ -77,10 +78,11 @@ macro_rules! const_zero { } /// A wrapper to implement the `Zeroable` trait through the `const_zero` macro. +#[macro_export] macro_rules! impl_zeroable { ($type:ty) => { - unsafe impl Zeroable for $type { - const ZERO: Self = unsafe { const_zero!($type) }; + unsafe impl $crate::zeroable::Zeroable for $type { + const ZERO: Self = unsafe { $crate::const_zero!($type) }; } }; } From 269a8f155c7265488945e60ef0cae77556017ddd Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:49 +0800 Subject: [PATCH 0036/1179] rust/timer/hpet: add basic HPET timer and HPETState Add the HPETTimer and HPETState (HPET timer block), along with their basic methods and register definitions. This is in preparation for supporting the QAPI interfaces. Note, wrap all items in HPETState that may be changed in the callback called by C code into the BqlCell/BqlRefCell. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-9-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/hw/timer/hpet/src/hpet.rs | 629 +++++++++++++++++++++++++++++++++ rust/hw/timer/hpet/src/lib.rs | 1 + rust/wrapper.h | 1 + 3 files changed, 631 insertions(+) create mode 100644 rust/hw/timer/hpet/src/hpet.rs diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs new file mode 100644 index 000000000000..795610f8e88d --- /dev/null +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -0,0 +1,629 @@ +// Copyright (C) 2024 Intel Corporation. +// Author(s): Zhao Liu +// SPDX-License-Identifier: GPL-2.0-or-later + +#![allow(dead_code)] + +use std::ptr::{addr_of_mut, null_mut, NonNull}; + +use qemu_api::{ + bindings::{address_space_memory, address_space_stl_le}, + cell::{BqlCell, BqlRefCell}, + irq::InterruptSource, + memory::{MemoryRegion, MEMTXATTRS_UNSPECIFIED}, + prelude::*, + qom::ParentField, + sysbus::SysBusDevice, + timer::{Timer, CLOCK_VIRTUAL}, +}; + +/// Register space for each timer block (`HPET_BASE` is defined in hpet.h). +const HPET_REG_SPACE_LEN: u64 = 0x400; // 1024 bytes + +/// Minimum recommended hardware implementation. +const HPET_MIN_TIMERS: usize = 3; +/// Maximum timers in each timer block. +const HPET_MAX_TIMERS: usize = 32; + +/// Flags that HPETState.flags supports. +const HPET_FLAG_MSI_SUPPORT_SHIFT: usize = 0; + +const HPET_NUM_IRQ_ROUTES: usize = 32; +const HPET_LEGACY_PIT_INT: u32 = 0; // HPET_LEGACY_RTC_INT isn't defined here. +const RTC_ISA_IRQ: usize = 8; + +const HPET_CLK_PERIOD: u64 = 10; // 10 ns +const FS_PER_NS: u64 = 1000000; // 1000000 femtoseconds == 1 ns + +/// General Capabilities and ID Register +const HPET_CAP_REG: u64 = 0x000; +/// Revision ID (bits 0:7). Revision 1 is implemented (refer to v1.0a spec). +const HPET_CAP_REV_ID_VALUE: u64 = 0x1; +const HPET_CAP_REV_ID_SHIFT: usize = 0; +/// Number of Timers (bits 8:12) +const HPET_CAP_NUM_TIM_SHIFT: usize = 8; +/// Counter Size (bit 13) +const HPET_CAP_COUNT_SIZE_CAP_SHIFT: usize = 13; +/// Legacy Replacement Route Capable (bit 15) +const HPET_CAP_LEG_RT_CAP_SHIFT: usize = 15; +/// Vendor ID (bits 16:31) +const HPET_CAP_VENDER_ID_VALUE: u64 = 0x8086; +const HPET_CAP_VENDER_ID_SHIFT: usize = 16; +/// Main Counter Tick Period (bits 32:63) +const HPET_CAP_CNT_CLK_PERIOD_SHIFT: usize = 32; + +/// General Configuration Register +const HPET_CFG_REG: u64 = 0x010; +/// Overall Enable (bit 0) +const HPET_CFG_ENABLE_SHIFT: usize = 0; +/// Legacy Replacement Route (bit 1) +const HPET_CFG_LEG_RT_SHIFT: usize = 1; +/// Other bits are reserved. +const HPET_CFG_WRITE_MASK: u64 = 0x003; + +/// General Interrupt Status Register +const HPET_INT_STATUS_REG: u64 = 0x020; + +/// Main Counter Value Register +const HPET_COUNTER_REG: u64 = 0x0f0; + +/// Timer N Configuration and Capability Register (masked by 0x18) +const HPET_TN_CFG_REG: u64 = 0x000; +/// bit 0, 7, and bits 16:31 are reserved. +/// bit 4, 5, 15, and bits 32:64 are read-only. +const HPET_TN_CFG_WRITE_MASK: u64 = 0x7f4e; +/// Timer N Interrupt Type (bit 1) +const HPET_TN_CFG_INT_TYPE_SHIFT: usize = 1; +/// Timer N Interrupt Enable (bit 2) +const HPET_TN_CFG_INT_ENABLE_SHIFT: usize = 2; +/// Timer N Type (Periodic enabled or not, bit 3) +const HPET_TN_CFG_PERIODIC_SHIFT: usize = 3; +/// Timer N Periodic Interrupt Capable (support Periodic or not, bit 4) +const HPET_TN_CFG_PERIODIC_CAP_SHIFT: usize = 4; +/// Timer N Size (timer size is 64-bits or 32 bits, bit 5) +const HPET_TN_CFG_SIZE_CAP_SHIFT: usize = 5; +/// Timer N Value Set (bit 6) +const HPET_TN_CFG_SETVAL_SHIFT: usize = 6; +/// Timer N 32-bit Mode (bit 8) +const HPET_TN_CFG_32BIT_SHIFT: usize = 8; +/// Timer N Interrupt Rout (bits 9:13) +const HPET_TN_CFG_INT_ROUTE_MASK: u64 = 0x3e00; +const HPET_TN_CFG_INT_ROUTE_SHIFT: usize = 9; +/// Timer N FSB Interrupt Enable (bit 14) +const HPET_TN_CFG_FSB_ENABLE_SHIFT: usize = 14; +/// Timer N FSB Interrupt Delivery (bit 15) +const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15; +/// Timer N Interrupt Routing Capability (bits 32:63) +const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32; + +/// Timer N Comparator Value Register (masked by 0x18) +const HPET_TN_CMP_REG: u64 = 0x008; + +/// Timer N FSB Interrupt Route Register (masked by 0x18) +const HPET_TN_FSB_ROUTE_REG: u64 = 0x010; + +const fn hpet_next_wrap(cur_tick: u64) -> u64 { + (cur_tick | 0xffffffff) + 1 +} + +const fn hpet_time_after(a: u64, b: u64) -> bool { + ((b - a) as i64) < 0 +} + +const fn ticks_to_ns(value: u64) -> u64 { + value * HPET_CLK_PERIOD +} + +const fn ns_to_ticks(value: u64) -> u64 { + value / HPET_CLK_PERIOD +} + +// Avoid touching the bits that cannot be written. +const fn hpet_fixup_reg(new: u64, old: u64, mask: u64) -> u64 { + (new & mask) | (old & !mask) +} + +const fn activating_bit(old: u64, new: u64, shift: usize) -> bool { + let mask: u64 = 1 << shift; + (old & mask == 0) && (new & mask != 0) +} + +const fn deactivating_bit(old: u64, new: u64, shift: usize) -> bool { + let mask: u64 = 1 << shift; + (old & mask != 0) && (new & mask == 0) +} + +fn timer_handler(timer_cell: &BqlRefCell) { + timer_cell.borrow_mut().callback() +} + +/// HPET Timer Abstraction +#[repr(C)] +#[derive(Debug, Default)] +#[cfg_attr(has_offset_of, derive(qemu_api_macros::offsets))] +pub struct HPETTimer { + /// timer N index within the timer block (`HPETState`) + #[doc(alias = "tn")] + index: usize, + qemu_timer: Option>, + /// timer block abstraction containing this timer + state: Option>, + + // Memory-mapped, software visible timer registers + /// Timer N Configuration and Capability Register + config: u64, + /// Timer N Comparator Value Register + cmp: u64, + /// Timer N FSB Interrupt Route Register + fsb: u64, + + // Hidden register state + /// comparator (extended to counter width) + cmp64: u64, + /// Last value written to comparator + period: u64, + /// timer pop will indicate wrap for one-shot 32-bit + /// mode. Next pop will be actual timer expiration. + wrap_flag: u8, + /// last value armed, to avoid timer storms + last: u64, +} + +impl HPETTimer { + fn init(&mut self, index: usize, state_ptr: *mut HPETState) -> &mut Self { + *self = HPETTimer::default(); + self.index = index; + self.state = NonNull::new(state_ptr); + self + } + + fn init_timer_with_state(&mut self) { + self.qemu_timer = Some(Box::new({ + let mut t = Timer::new(); + t.init_full( + None, + CLOCK_VIRTUAL, + Timer::NS, + 0, + timer_handler, + &self.get_state().timers[self.index], + ); + t + })); + } + + fn get_state(&self) -> &HPETState { + // SAFETY: + // the pointer is convertible to a reference + unsafe { self.state.unwrap().as_ref() } + } + + fn is_int_active(&self) -> bool { + self.get_state().is_timer_int_active(self.index) + } + + const fn is_fsb_route_enabled(&self) -> bool { + self.config & (1 << HPET_TN_CFG_FSB_ENABLE_SHIFT) != 0 + } + + const fn is_periodic(&self) -> bool { + self.config & (1 << HPET_TN_CFG_PERIODIC_SHIFT) != 0 + } + + const fn is_int_enabled(&self) -> bool { + self.config & (1 << HPET_TN_CFG_INT_ENABLE_SHIFT) != 0 + } + + const fn is_32bit_mod(&self) -> bool { + self.config & (1 << HPET_TN_CFG_32BIT_SHIFT) != 0 + } + + const fn is_valset_enabled(&self) -> bool { + self.config & (1 << HPET_TN_CFG_SETVAL_SHIFT) != 0 + } + + fn clear_valset(&mut self) { + self.config &= !(1 << HPET_TN_CFG_SETVAL_SHIFT); + } + + /// True if timer interrupt is level triggered; otherwise, edge triggered. + const fn is_int_level_triggered(&self) -> bool { + self.config & (1 << HPET_TN_CFG_INT_TYPE_SHIFT) != 0 + } + + /// calculate next value of the general counter that matches the + /// target (either entirely, or the low 32-bit only depending on + /// the timer mode). + fn calculate_cmp64(&self, cur_tick: u64, target: u64) -> u64 { + if self.is_32bit_mod() { + let mut result: u64 = cur_tick.deposit(0, 32, target); + if result < cur_tick { + result += 0x100000000; + } + result + } else { + target + } + } + + const fn get_individual_route(&self) -> usize { + ((self.config & HPET_TN_CFG_INT_ROUTE_MASK) >> HPET_TN_CFG_INT_ROUTE_SHIFT) as usize + } + + fn get_int_route(&self) -> usize { + if self.index <= 1 && self.get_state().is_legacy_mode() { + // If LegacyReplacement Route bit is set, HPET specification requires + // timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC, + // timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC. + // + // If the LegacyReplacement Route bit is set, the individual routing + // bits for timers 0 and 1 (APIC or FSB) will have no impact. + // + // FIXME: Consider I/O APIC case. + if self.index == 0 { + 0 + } else { + RTC_ISA_IRQ + } + } else { + // (If the LegacyReplacement Route bit is set) Timer 2-n will be + // routed as per the routing in the timer n config registers. + // ... + // If the LegacyReplacement Route bit is not set, the individual + // routing bits for each of the timers are used. + self.get_individual_route() + } + } + + fn set_irq(&mut self, set: bool) { + let route = self.get_int_route(); + + if set && self.is_int_enabled() && self.get_state().is_hpet_enabled() { + if self.is_fsb_route_enabled() { + // SAFETY: + // the parameters are valid. + unsafe { + address_space_stl_le( + addr_of_mut!(address_space_memory), + self.fsb >> 32, // Timer N FSB int addr + self.fsb as u32, // Timer N FSB int value, truncate! + MEMTXATTRS_UNSPECIFIED, + null_mut(), + ); + } + } else if self.is_int_level_triggered() { + self.get_state().irqs[route].raise(); + } else { + self.get_state().irqs[route].pulse(); + } + } else if !self.is_fsb_route_enabled() { + self.get_state().irqs[route].lower(); + } + } + + fn update_irq(&mut self, set: bool) { + // If Timer N Interrupt Enable bit is 0, "the timer will + // still operate and generate appropriate status bits, but + // will not cause an interrupt" + self.get_state() + .update_int_status(self.index as u32, set && self.is_int_level_triggered()); + self.set_irq(set); + } + + fn arm_timer(&mut self, tick: u64) { + let mut ns = self.get_state().get_ns(tick); + + // Clamp period to reasonable min value (1 us) + if self.is_periodic() && ns - self.last < 1000 { + ns = self.last + 1000; + } + + self.last = ns; + self.qemu_timer.as_ref().unwrap().modify(self.last); + } + + fn set_timer(&mut self) { + let cur_tick: u64 = self.get_state().get_ticks(); + + self.wrap_flag = 0; + self.cmp64 = self.calculate_cmp64(cur_tick, self.cmp); + if self.is_32bit_mod() { + // HPET spec says in one-shot 32-bit mode, generate an interrupt when + // counter wraps in addition to an interrupt with comparator match. + if !self.is_periodic() && self.cmp64 > hpet_next_wrap(cur_tick) { + self.wrap_flag = 1; + self.arm_timer(hpet_next_wrap(cur_tick)); + return; + } + } + self.arm_timer(self.cmp64); + } + + fn del_timer(&mut self) { + // Just remove the timer from the timer_list without destroying + // this timer instance. + self.qemu_timer.as_ref().unwrap().delete(); + + if self.is_int_active() { + // For level-triggered interrupt, this leaves interrupt status + // register set but lowers irq. + self.update_irq(true); + } + } + + /// Configuration and Capability Register + fn set_tn_cfg_reg(&mut self, shift: u32, len: u32, val: u64) { + // TODO: Add trace point - trace_hpet_ram_write_tn_cfg(addr & 4) + let old_val: u64 = self.config; + let mut new_val: u64 = old_val.deposit(shift, len, val); + new_val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK); + + // Switch level-type interrupt to edge-type. + if deactivating_bit(old_val, new_val, HPET_TN_CFG_INT_TYPE_SHIFT) { + // Do this before changing timer.config; otherwise, if + // HPET_TN_FSB is set, update_irq will not lower the qemu_irq. + self.update_irq(false); + } + + self.config = new_val; + + if activating_bit(old_val, new_val, HPET_TN_CFG_INT_ENABLE_SHIFT) && self.is_int_active() { + self.update_irq(true); + } + + if self.is_32bit_mod() { + self.cmp = u64::from(self.cmp as u32); // truncate! + self.period = u64::from(self.period as u32); // truncate! + } + + if self.get_state().is_hpet_enabled() { + self.set_timer(); + } + } + + /// Comparator Value Register + fn set_tn_cmp_reg(&mut self, shift: u32, len: u32, val: u64) { + let mut length = len; + let mut value = val; + + // TODO: Add trace point - trace_hpet_ram_write_tn_cmp(addr & 4) + if self.is_32bit_mod() { + // High 32-bits are zero, leave them untouched. + if shift != 0 { + // TODO: Add trace point - trace_hpet_ram_write_invalid_tn_cmp() + return; + } + length = 64; + value = u64::from(value as u32); // truncate! + } + + if !self.is_periodic() || self.is_valset_enabled() { + self.cmp = self.cmp.deposit(shift, length, value); + } + + if self.is_periodic() { + self.period = self.period.deposit(shift, length, value); + } + + self.clear_valset(); + if self.get_state().is_hpet_enabled() { + self.set_timer(); + } + } + + /// FSB Interrupt Route Register + fn set_tn_fsb_route_reg(&mut self, shift: u32, len: u32, val: u64) { + self.fsb = self.fsb.deposit(shift, len, val); + } + + fn reset(&mut self) { + self.del_timer(); + self.cmp = u64::MAX; // Comparator Match Registers reset to all 1's. + self.config = (1 << HPET_TN_CFG_PERIODIC_CAP_SHIFT) | (1 << HPET_TN_CFG_SIZE_CAP_SHIFT); + if self.get_state().has_msi_flag() { + self.config |= 1 << HPET_TN_CFG_FSB_CAP_SHIFT; + } + // advertise availability of ioapic int + self.config |= + (u64::from(self.get_state().int_route_cap)) << HPET_TN_CFG_INT_ROUTE_CAP_SHIFT; + self.period = 0; + self.wrap_flag = 0; + } + + /// timer expiration callback + fn callback(&mut self) { + let period: u64 = self.period; + let cur_tick: u64 = self.get_state().get_ticks(); + + if self.is_periodic() && period != 0 { + while hpet_time_after(cur_tick, self.cmp64) { + self.cmp64 += period; + } + if self.is_32bit_mod() { + self.cmp = u64::from(self.cmp64 as u32); // truncate! + } else { + self.cmp = self.cmp64; + } + self.arm_timer(self.cmp64); + } else if self.wrap_flag != 0 { + self.wrap_flag = 0; + self.arm_timer(self.cmp64); + } + self.update_irq(true); + } +} + +/// HPET Event Timer Block Abstraction +#[repr(C)] +#[derive(qemu_api_macros::offsets)] +pub struct HPETState { + parent_obj: ParentField, + iomem: MemoryRegion, + + // HPET block Registers: Memory-mapped, software visible registers + /// General Capabilities and ID Register + capability: BqlCell, + /// General Configuration Register + config: BqlCell, + /// General Interrupt Status Register + #[doc(alias = "isr")] + int_status: BqlCell, + /// Main Counter Value Register + #[doc(alias = "hpet_counter")] + counter: BqlCell, + + // Internal state + /// Capabilities that QEMU HPET supports. + /// bit 0: MSI (or FSB) support. + flags: u32, + + /// Offset of main counter relative to qemu clock. + hpet_offset: BqlCell, + hpet_offset_saved: bool, + + irqs: [InterruptSource; HPET_NUM_IRQ_ROUTES], + rtc_irq_level: BqlCell, + pit_enabled: InterruptSource, + + /// Interrupt Routing Capability. + /// This field indicates to which interrupts in the I/O (x) APIC + /// the timers' interrupt can be routed, and is encoded in the + /// bits 32:64 of timer N's config register: + #[doc(alias = "intcap")] + int_route_cap: u32, + + /// HPET timer array managed by this timer block. + #[doc(alias = "timer")] + timers: [BqlRefCell; HPET_MAX_TIMERS], + num_timers: BqlCell, + + /// Instance id (HPET timer block ID). + hpet_id: BqlCell, +} + +impl HPETState { + const fn has_msi_flag(&self) -> bool { + self.flags & (1 << HPET_FLAG_MSI_SUPPORT_SHIFT) != 0 + } + + fn is_legacy_mode(&self) -> bool { + self.config.get() & (1 << HPET_CFG_LEG_RT_SHIFT) != 0 + } + + fn is_hpet_enabled(&self) -> bool { + self.config.get() & (1 << HPET_CFG_ENABLE_SHIFT) != 0 + } + + fn is_timer_int_active(&self, index: usize) -> bool { + self.int_status.get() & (1 << index) != 0 + } + + fn get_ticks(&self) -> u64 { + ns_to_ticks(CLOCK_VIRTUAL.get_ns() + self.hpet_offset.get()) + } + + fn get_ns(&self, tick: u64) -> u64 { + ticks_to_ns(tick) - self.hpet_offset.get() + } + + fn handle_legacy_irq(&self, irq: u32, level: u32) { + if irq == HPET_LEGACY_PIT_INT { + if !self.is_legacy_mode() { + self.irqs[0].set(level != 0); + } + } else { + self.rtc_irq_level.set(level); + if !self.is_legacy_mode() { + self.irqs[RTC_ISA_IRQ].set(level != 0); + } + } + } + + fn init_timer(&self) { + let raw_ptr: *mut HPETState = self as *const HPETState as *mut HPETState; + + for (index, timer) in self.timers.iter().enumerate() { + timer + .borrow_mut() + .init(index, raw_ptr) + .init_timer_with_state(); + } + } + + fn update_int_status(&self, index: u32, level: bool) { + self.int_status + .set(self.int_status.get().deposit(index, 1, u64::from(level))); + } + + /// General Configuration Register + fn set_cfg_reg(&self, shift: u32, len: u32, val: u64) { + let old_val = self.config.get(); + let mut new_val = old_val.deposit(shift, len, val); + + new_val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK); + self.config.set(new_val); + + if activating_bit(old_val, new_val, HPET_CFG_ENABLE_SHIFT) { + // Enable main counter and interrupt generation. + self.hpet_offset + .set(ticks_to_ns(self.counter.get()) - CLOCK_VIRTUAL.get_ns()); + + for timer in self.timers.iter().take(self.num_timers.get()) { + let mut t = timer.borrow_mut(); + + if t.is_int_enabled() && t.is_int_active() { + t.update_irq(true); + } + t.set_timer(); + } + } else if deactivating_bit(old_val, new_val, HPET_CFG_ENABLE_SHIFT) { + // Halt main counter and disable interrupt generation. + self.counter.set(self.get_ticks()); + + for timer in self.timers.iter().take(self.num_timers.get()) { + timer.borrow_mut().del_timer(); + } + } + + // i8254 and RTC output pins are disabled when HPET is in legacy mode + if activating_bit(old_val, new_val, HPET_CFG_LEG_RT_SHIFT) { + self.pit_enabled.set(false); + self.irqs[0].lower(); + self.irqs[RTC_ISA_IRQ].lower(); + } else if deactivating_bit(old_val, new_val, HPET_CFG_LEG_RT_SHIFT) { + // TODO: Add irq binding: qemu_irq_lower(s->irqs[0]) + self.irqs[0].lower(); + self.pit_enabled.set(true); + self.irqs[RTC_ISA_IRQ].set(self.rtc_irq_level.get() != 0); + } + } + + /// General Interrupt Status Register: Read/Write Clear + fn set_int_status_reg(&self, shift: u32, _len: u32, val: u64) { + let new_val = val << shift; + let cleared = new_val & self.int_status.get(); + + for (index, timer) in self.timers.iter().take(self.num_timers.get()).enumerate() { + if cleared & (1 << index) != 0 { + timer.borrow_mut().update_irq(false); + } + } + } + + /// Main Counter Value Register + fn set_counter_reg(&self, shift: u32, len: u32, val: u64) { + if self.is_hpet_enabled() { + // TODO: Add trace point - + // trace_hpet_ram_write_counter_write_while_enabled() + // + // HPET spec says that writes to this register should only be + // done while the counter is halted. So this is an undefined + // behavior. There's no need to forbid it, but when HPET is + // enabled, the changed counter value will not affect the + // tick count (i.e., the previously calculated offset will + // not be changed as well). + } + self.counter + .set(self.counter.get().deposit(shift, len, val)); + } +} diff --git a/rust/hw/timer/hpet/src/lib.rs b/rust/hw/timer/hpet/src/lib.rs index 44e9f3fb8ab5..d6ac0b2521a2 100644 --- a/rust/hw/timer/hpet/src/lib.rs +++ b/rust/hw/timer/hpet/src/lib.rs @@ -8,3 +8,4 @@ //! Precision Event Timers) device in QEMU. pub mod fw_cfg; +pub mod hpet; diff --git a/rust/wrapper.h b/rust/wrapper.h index a35bfbd1760d..d927ad6799da 100644 --- a/rust/wrapper.h +++ b/rust/wrapper.h @@ -64,3 +64,4 @@ typedef enum memory_order { #include "chardev/char-serial.h" #include "exec/memattrs.h" #include "qemu/timer.h" +#include "exec/address-spaces.h" From 6e90a8f8136f65273fe7320904e06b27a8a40eda Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:50 +0800 Subject: [PATCH 0037/1179] rust/timer/hpet: add qom and qdev APIs support Implement QOM & QAPI support for HPET device. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-10-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/hw/timer/hpet/src/fw_cfg.rs | 2 - rust/hw/timer/hpet/src/hpet.rs | 278 ++++++++++++++++++++++++++++++- rust/hw/timer/hpet/src/lib.rs | 4 + 3 files changed, 273 insertions(+), 11 deletions(-) diff --git a/rust/hw/timer/hpet/src/fw_cfg.rs b/rust/hw/timer/hpet/src/fw_cfg.rs index 849e277d483d..bef03727ea3c 100644 --- a/rust/hw/timer/hpet/src/fw_cfg.rs +++ b/rust/hw/timer/hpet/src/fw_cfg.rs @@ -2,8 +2,6 @@ // Author(s): Zhao Liu // SPDX-License-Identifier: GPL-2.0-or-later -#![allow(dead_code)] - use std::ptr::addr_of_mut; use qemu_api::{cell::bql_locked, impl_zeroable, zeroable::Zeroable}; diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index 795610f8e88d..75ff5b3e8d6c 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -2,21 +2,33 @@ // Author(s): Zhao Liu // SPDX-License-Identifier: GPL-2.0-or-later -#![allow(dead_code)] - -use std::ptr::{addr_of_mut, null_mut, NonNull}; +use std::{ + ffi::CStr, + ptr::{addr_of_mut, null_mut, NonNull}, + slice::from_ref, +}; use qemu_api::{ - bindings::{address_space_memory, address_space_stl_le}, + bindings::{ + address_space_memory, address_space_stl_le, qdev_prop_bit, qdev_prop_bool, + qdev_prop_uint32, qdev_prop_uint8, + }, + c_str, cell::{BqlCell, BqlRefCell}, irq::InterruptSource, - memory::{MemoryRegion, MEMTXATTRS_UNSPECIFIED}, + memory::{ + hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED, + }, prelude::*, - qom::ParentField, + qdev::{DeviceImpl, DeviceMethods, DeviceState, Property, ResetType, ResettablePhasesImpl}, + qom::{ObjectImpl, ObjectType, ParentField}, + qom_isa, sysbus::SysBusDevice, timer::{Timer, CLOCK_VIRTUAL}, }; +use crate::fw_cfg::HPETFwConfig; + /// Register space for each timer block (`HPET_BASE` is defined in hpet.h). const HPET_REG_SPACE_LEN: u64 = 0x400; // 1024 bytes @@ -139,8 +151,7 @@ fn timer_handler(timer_cell: &BqlRefCell) { /// HPET Timer Abstraction #[repr(C)] -#[derive(Debug, Default)] -#[cfg_attr(has_offset_of, derive(qemu_api_macros::offsets))] +#[derive(Debug, Default, qemu_api_macros::offsets)] pub struct HPETTimer { /// timer N index within the timer block (`HPETState`) #[doc(alias = "tn")] @@ -451,11 +462,41 @@ impl HPETTimer { } self.update_irq(true); } + + const fn read(&self, addr: hwaddr, _size: u32) -> u64 { + let shift: u64 = (addr & 4) * 8; + + match addr & !4 { + HPET_TN_CFG_REG => self.config >> shift, // including interrupt capabilities + HPET_TN_CMP_REG => self.cmp >> shift, // comparator register + HPET_TN_FSB_ROUTE_REG => self.fsb >> shift, + _ => { + // TODO: Add trace point - trace_hpet_ram_read_invalid() + // Reserved. + 0 + } + } + } + + fn write(&mut self, addr: hwaddr, value: u64, size: u32) { + let shift = ((addr & 4) * 8) as u32; + let len = std::cmp::min(size * 8, 64 - shift); + + match addr & !4 { + HPET_TN_CFG_REG => self.set_tn_cfg_reg(shift, len, value), + HPET_TN_CMP_REG => self.set_tn_cmp_reg(shift, len, value), + HPET_TN_FSB_ROUTE_REG => self.set_tn_fsb_route_reg(shift, len, value), + _ => { + // TODO: Add trace point - trace_hpet_ram_write_invalid() + // Reserved. + } + } + } } /// HPET Event Timer Block Abstraction #[repr(C)] -#[derive(qemu_api_macros::offsets)] +#[derive(qemu_api_macros::Object, qemu_api_macros::offsets)] pub struct HPETState { parent_obj: ParentField, iomem: MemoryRegion, @@ -626,4 +667,223 @@ impl HPETState { self.counter .set(self.counter.get().deposit(shift, len, val)); } + + unsafe fn init(&mut self) { + static HPET_RAM_OPS: MemoryRegionOps = + MemoryRegionOpsBuilder::::new() + .read(&HPETState::read) + .write(&HPETState::write) + .native_endian() + .valid_sizes(4, 8) + .impl_sizes(4, 8) + .build(); + + // SAFETY: + // self and self.iomem are guaranteed to be valid at this point since callers + // must make sure the `self` reference is valid. + MemoryRegion::init_io( + unsafe { &mut *addr_of_mut!(self.iomem) }, + addr_of_mut!(*self), + &HPET_RAM_OPS, + "hpet", + HPET_REG_SPACE_LEN, + ); + } + + fn post_init(&self) { + self.init_mmio(&self.iomem); + for irq in self.irqs.iter() { + self.init_irq(irq); + } + } + + fn realize(&self) { + if self.int_route_cap == 0 { + // TODO: Add error binding: warn_report() + println!("Hpet's hpet-intcap property not initialized"); + } + + self.hpet_id.set(HPETFwConfig::assign_hpet_id()); + + if self.num_timers.get() < HPET_MIN_TIMERS { + self.num_timers.set(HPET_MIN_TIMERS); + } else if self.num_timers.get() > HPET_MAX_TIMERS { + self.num_timers.set(HPET_MAX_TIMERS); + } + + self.init_timer(); + // 64-bit General Capabilities and ID Register; LegacyReplacementRoute. + self.capability.set( + HPET_CAP_REV_ID_VALUE << HPET_CAP_REV_ID_SHIFT | + 1 << HPET_CAP_COUNT_SIZE_CAP_SHIFT | + 1 << HPET_CAP_LEG_RT_CAP_SHIFT | + HPET_CAP_VENDER_ID_VALUE << HPET_CAP_VENDER_ID_SHIFT | + ((self.num_timers.get() - 1) as u64) << HPET_CAP_NUM_TIM_SHIFT | // indicate the last timer + (HPET_CLK_PERIOD * FS_PER_NS) << HPET_CAP_CNT_CLK_PERIOD_SHIFT, // 10 ns + ); + + self.init_gpio_in(2, HPETState::handle_legacy_irq); + self.init_gpio_out(from_ref(&self.pit_enabled)); + } + + fn reset_hold(&self, _type: ResetType) { + let sbd = self.upcast::(); + + for timer in self.timers.iter().take(self.num_timers.get()) { + timer.borrow_mut().reset(); + } + + self.counter.set(0); + self.config.set(0); + self.pit_enabled.set(true); + self.hpet_offset.set(0); + + HPETFwConfig::update_hpet_cfg( + self.hpet_id.get(), + self.capability.get() as u32, + sbd.mmio[0].addr, + ); + + // to document that the RTC lowers its output on reset as well + self.rtc_irq_level.set(0); + } + + fn timer_and_addr(&self, addr: hwaddr) -> Option<(&BqlRefCell, hwaddr)> { + let timer_id: usize = ((addr - 0x100) / 0x20) as usize; + + // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) + if timer_id > self.num_timers.get() { + // TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id) + None + } else { + // Keep the complete address so that HPETTimer's read and write could + // detect the invalid access. + Some((&self.timers[timer_id], addr & 0x1F)) + } + } + + fn read(&self, addr: hwaddr, size: u32) -> u64 { + let shift: u64 = (addr & 4) * 8; + + // address range of all TN regs + // TODO: Add trace point - trace_hpet_ram_read(addr) + if (0x100..=0x3ff).contains(&addr) { + match self.timer_and_addr(addr) { + None => 0, // Reserved, + Some((timer, tn_addr)) => timer.borrow_mut().read(tn_addr, size), + } + } else { + match addr & !4 { + HPET_CAP_REG => self.capability.get() >> shift, /* including HPET_PERIOD 0x004 */ + // (CNT_CLK_PERIOD field) + HPET_CFG_REG => self.config.get() >> shift, + HPET_COUNTER_REG => { + let cur_tick: u64 = if self.is_hpet_enabled() { + self.get_ticks() + } else { + self.counter.get() + }; + + // TODO: Add trace point - trace_hpet_ram_read_reading_counter(addr & 4, + // cur_tick) + cur_tick >> shift + } + HPET_INT_STATUS_REG => self.int_status.get() >> shift, + _ => { + // TODO: Add trace point- trace_hpet_ram_read_invalid() + // Reserved. + 0 + } + } + } + } + + fn write(&self, addr: hwaddr, value: u64, size: u32) { + let shift = ((addr & 4) * 8) as u32; + let len = std::cmp::min(size * 8, 64 - shift); + + // TODO: Add trace point - trace_hpet_ram_write(addr, value) + if (0x100..=0x3ff).contains(&addr) { + match self.timer_and_addr(addr) { + None => (), // Reserved. + Some((timer, tn_addr)) => timer.borrow_mut().write(tn_addr, value, size), + } + } else { + match addr & !0x4 { + HPET_CAP_REG => {} // General Capabilities and ID Register: Read Only + HPET_CFG_REG => self.set_cfg_reg(shift, len, value), + HPET_INT_STATUS_REG => self.set_int_status_reg(shift, len, value), + HPET_COUNTER_REG => self.set_counter_reg(shift, len, value), + _ => { + // TODO: Add trace point - trace_hpet_ram_write_invalid() + // Reserved. + } + } + } + } +} + +qom_isa!(HPETState: SysBusDevice, DeviceState, Object); + +unsafe impl ObjectType for HPETState { + // No need for HPETClass. Just like OBJECT_DECLARE_SIMPLE_TYPE in C. + type Class = ::Class; + const TYPE_NAME: &'static CStr = crate::TYPE_HPET; +} + +impl ObjectImpl for HPETState { + type ParentType = SysBusDevice; + + const INSTANCE_INIT: Option = Some(Self::init); + const INSTANCE_POST_INIT: Option = Some(Self::post_init); +} + +// TODO: Make these properties user-configurable! +qemu_api::declare_properties! { + HPET_PROPERTIES, + qemu_api::define_property!( + c_str!("timers"), + HPETState, + num_timers, + unsafe { &qdev_prop_uint8 }, + u8, + default = HPET_MIN_TIMERS + ), + qemu_api::define_property!( + c_str!("msi"), + HPETState, + flags, + unsafe { &qdev_prop_bit }, + u32, + bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8, + default = false, + ), + qemu_api::define_property!( + c_str!("hpet-intcap"), + HPETState, + int_route_cap, + unsafe { &qdev_prop_uint32 }, + u32, + default = 0 + ), + qemu_api::define_property!( + c_str!("hpet-offset-saved"), + HPETState, + hpet_offset_saved, + unsafe { &qdev_prop_bool }, + bool, + default = true + ), +} + +impl DeviceImpl for HPETState { + fn properties() -> &'static [Property] { + &HPET_PROPERTIES + } + + const REALIZE: Option = Some(Self::realize); +} + +impl ResettablePhasesImpl for HPETState { + const HOLD: Option = Some(Self::reset_hold); } diff --git a/rust/hw/timer/hpet/src/lib.rs b/rust/hw/timer/hpet/src/lib.rs index d6ac0b2521a2..5e7c961c2894 100644 --- a/rust/hw/timer/hpet/src/lib.rs +++ b/rust/hw/timer/hpet/src/lib.rs @@ -7,5 +7,9 @@ //! This library implements a device model for the IA-PC HPET (High //! Precision Event Timers) device in QEMU. +use qemu_api::c_str; + pub mod fw_cfg; pub mod hpet; + +pub const TYPE_HPET: &::std::ffi::CStr = c_str!("hpet"); From d128c341a744ba3e92fa67d9f1b02dd9a7bd68b9 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 10 Feb 2025 11:00:51 +0800 Subject: [PATCH 0038/1179] i386: enable rust hpet for pc when rust is enabled Add HPET configuration in PC's Kconfig options, and select HPET device (Rust version) if Rust is supported. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250210030051.2562726-11-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- configs/devices/i386-softmmu/default.mak | 1 + hw/i386/pc.c | 2 +- hw/timer/Kconfig | 2 +- rust/hw/Kconfig | 1 + rust/hw/timer/Kconfig | 2 ++ tests/qtest/meson.build | 3 ++- 6 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 rust/hw/timer/Kconfig diff --git a/configs/devices/i386-softmmu/default.mak b/configs/devices/i386-softmmu/default.mak index 4faf2f0315e2..9ef343cace06 100644 --- a/configs/devices/i386-softmmu/default.mak +++ b/configs/devices/i386-softmmu/default.mak @@ -6,6 +6,7 @@ #CONFIG_APPLESMC=n #CONFIG_FDC=n #CONFIG_HPET=n +#CONFIG_X_HPET_RUST=n #CONFIG_HYPERV=n #CONFIG_ISA_DEBUG=n #CONFIG_ISA_IPMI_BT=n diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 0eb52d315bb7..22641e6ddca5 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1701,7 +1701,7 @@ static void pc_machine_initfn(Object *obj) pcms->sata_enabled = true; pcms->i8042_enabled = true; pcms->max_fw_size = 8 * MiB; -#ifdef CONFIG_HPET +#if defined(CONFIG_HPET) || defined(CONFIG_X_HPET_RUST) pcms->hpet_enabled = true; #endif pcms->fd_bootchk = true; diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig index c96fd5d97ae8..9ac008453408 100644 --- a/hw/timer/Kconfig +++ b/hw/timer/Kconfig @@ -11,7 +11,7 @@ config A9_GTIMER config HPET bool - default y if PC + default y if PC && !HAVE_RUST config I8254 bool diff --git a/rust/hw/Kconfig b/rust/hw/Kconfig index 4d934f30afe1..36f92ec02874 100644 --- a/rust/hw/Kconfig +++ b/rust/hw/Kconfig @@ -1,2 +1,3 @@ # devices Kconfig source char/Kconfig +source timer/Kconfig diff --git a/rust/hw/timer/Kconfig b/rust/hw/timer/Kconfig new file mode 100644 index 000000000000..afd980335037 --- /dev/null +++ b/rust/hw/timer/Kconfig @@ -0,0 +1,2 @@ +config X_HPET_RUST + bool diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 68316dbdc1a6..8a6243382a18 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -103,7 +103,8 @@ qtests_i386 = \ config_all_devices.has_key('CONFIG_VIRTIO_PCI') and \ slirp.found() ? ['virtio-net-failover'] : []) + \ (unpack_edk2_blobs and \ - config_all_devices.has_key('CONFIG_HPET') and \ + (config_all_devices.has_key('CONFIG_HPET') or \ + config_all_devices.has_key('CONFIG_X_HPET_RUST')) and \ config_all_devices.has_key('CONFIG_PARALLEL') ? ['bios-tables-test'] : []) + \ qtests_pci + \ qtests_cxl + \ From ebacd14a6f97b6235e078d9a9ac8a342a3be7c96 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 30 Jan 2025 11:11:18 +0100 Subject: [PATCH 0039/1179] rust: qemu_api: add a documentation header for all modules Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/assertions.rs | 4 ++++ rust/qemu-api/src/bindings.rs | 2 ++ rust/qemu-api/src/c_str.rs | 8 ++++++++ rust/qemu-api/src/offset_of.rs | 7 +++++++ rust/qemu-api/src/prelude.rs | 2 ++ rust/qemu-api/src/sysbus.rs | 2 ++ rust/qemu-api/src/zeroable.rs | 2 ++ 7 files changed, 27 insertions(+) diff --git a/rust/qemu-api/src/assertions.rs b/rust/qemu-api/src/assertions.rs index 6e420469806e..fa1a18de6fe9 100644 --- a/rust/qemu-api/src/assertions.rs +++ b/rust/qemu-api/src/assertions.rs @@ -2,9 +2,13 @@ // Author(s): Paolo Bonzini // SPDX-License-Identifier: GPL-2.0-or-later +#![doc(hidden)] //! This module provides macros to check the equality of types and //! the type of `struct` fields. This can be useful to ensure that //! types match the expectations of C code. +//! +//! Documentation is hidden because it only exposes macros, which +//! are exported directly from `qemu_api`. // Based on https://stackoverflow.com/questions/64251852/x/70978292#70978292 // (stackoverflow answers are released under MIT license). diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index b71220113ef5..d2868639ff60 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -15,6 +15,8 @@ clippy::missing_safety_doc )] +//! `bindgen`-generated declarations. + #[cfg(MESON)] include!("bindings.inc.rs"); diff --git a/rust/qemu-api/src/c_str.rs b/rust/qemu-api/src/c_str.rs index 4cd96da0b45d..3fa61b59c768 100644 --- a/rust/qemu-api/src/c_str.rs +++ b/rust/qemu-api/src/c_str.rs @@ -2,6 +2,14 @@ // Author(s): Paolo Bonzini // SPDX-License-Identifier: GPL-2.0-or-later +#![doc(hidden)] +//! This module provides a macro to define a constant of type +//! [`CStr`](std::ffi::CStr), for compatibility with versions of +//! Rust that lack `c""` literals. +//! +//! Documentation is hidden because it only exposes macros, which +//! are exported directly from `qemu_api`. + #[macro_export] /// Given a string constant _without_ embedded or trailing NULs, return /// a `CStr`. diff --git a/rust/qemu-api/src/offset_of.rs b/rust/qemu-api/src/offset_of.rs index 075e98f986ba..373229bbde96 100644 --- a/rust/qemu-api/src/offset_of.rs +++ b/rust/qemu-api/src/offset_of.rs @@ -1,5 +1,12 @@ // SPDX-License-Identifier: MIT +#![doc(hidden)] +//! This module provides macros that emulate the functionality of +//! `core::mem::offset_of` on older versions of Rust. +//! +//! Documentation is hidden because it only exposes macros, which +//! are exported directly from `qemu_api`. + /// This macro provides the same functionality as `core::mem::offset_of`, /// except that only one level of field access is supported. The declaration /// of the struct must be wrapped with `with_offsets! { }`. diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 254edb476dd1..fbf0ee23e0b9 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -2,6 +2,8 @@ // Author(s): Paolo Bonzini // SPDX-License-Identifier: GPL-2.0-or-later +//! Commonly used traits and types for QEMU. + pub use crate::bitops::IntegerExt; pub use crate::cell::BqlCell; diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 1f66a5f1e094..fa36e12178f1 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -2,6 +2,8 @@ // Author(s): Paolo Bonzini // SPDX-License-Identifier: GPL-2.0-or-later +//! Bindings to access `sysbus` functionality from Rust. + use std::{ffi::CStr, ptr::addr_of_mut}; pub use bindings::{SysBusDevice, SysBusDeviceClass}; diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index cd424e6ea050..a2356cb2f24c 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +//! Defines a trait for structs that can be safely initialized with zero bytes. + /// Encapsulates the requirement that /// `MaybeUninit::::zeroed().assume_init()` does not cause undefined /// behavior. This trait in principle could be implemented as just: From ee7d3aec54a32ce53c9b5ca86c75c945a877db19 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 30 Jan 2025 10:55:16 +0100 Subject: [PATCH 0040/1179] rust: vmstate: remove redundant link targets Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/vmstate.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 164effc65539..c6dfb6093568 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -191,10 +191,9 @@ pub const fn vmstate_varray_flag(_: PhantomData) -> VMStateFlags /// * scalar types (integer and `bool`) /// * the C struct `QEMUTimer` /// * a transparent wrapper for any of the above (`Cell`, `UnsafeCell`, -/// [`BqlCell`](crate::cell::BqlCell), [`BqlRefCell`](crate::cell::BqlRefCell) +/// [`BqlCell`], [`BqlRefCell`] /// * a raw pointer to any of the above -/// * a `NonNull` pointer, a `Box` or an [`Owned`](crate::qom::Owned) for any of -/// the above +/// * a `NonNull` pointer, a `Box` or an [`Owned`] for any of the above /// * an array of any of the above /// /// In order to support other types, the trait `VMState` must be implemented From 16534af51bc0e9c3db94097ab37ebd3ed50e1c0f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 11 Feb 2025 13:55:53 +0100 Subject: [PATCH 0041/1179] rust: fix doctests Doctests were not being run by CI, and have broken. Fix them. Signed-off-by: Paolo Bonzini --- .gitlab-ci.d/buildtest.yml | 6 ++++++ rust/qemu-api/src/vmstate.rs | 2 +- rust/qemu-api/src/zeroable.rs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml index 4265a5778343..00f4bfcd9f35 100644 --- a/.gitlab-ci.d/buildtest.yml +++ b/.gitlab-ci.d/buildtest.yml @@ -131,6 +131,12 @@ build-system-fedora-rust-nightly: CONFIGURE_ARGS: --disable-docs --enable-rust --enable-strict-rust-lints TARGETS: aarch64-softmmu MAKE_CHECK_ARGS: check-build + after_script: + - source scripts/ci/gitlab-ci-section + - section_start test "Running Rust doctests" + - cd build + - pyvenv/bin/meson devenv -w ../rust ${CARGO-cargo} test --doc -p qemu_api + allow_failure: true check-system-fedora: diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index c6dfb6093568..24a4dc81e7fd 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -294,7 +294,7 @@ impl VMStateField { /// # Examples /// /// ``` -/// # use qemu_api::vmstate::impl_vmstate_forward; +/// # use qemu_api::impl_vmstate_forward; /// pub struct Fifo([u8; 16]); /// impl_vmstate_forward!(Fifo); /// ``` diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index a2356cb2f24c..47b6977828da 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -7,7 +7,7 @@ /// behavior. This trait in principle could be implemented as just: /// /// ``` -/// pub unsafe trait Zeroable { +/// pub unsafe trait Zeroable: Default { /// const ZERO: Self = unsafe { ::core::mem::MaybeUninit::::zeroed().assume_init() }; /// } /// ``` From 4dafba778aa3e5f5fd3b2c6333afd7650dcf54e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volker=20R=C3=BCmelin?= Date: Tue, 31 Dec 2024 12:59:50 +0100 Subject: [PATCH 0042/1179] ui/sdl2: reenable the SDL2 Windows keyboard hook procedure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows only: The libSDL2 Windows message loop needs the libSDL2 Windows low level keyboard hook procedure to grab the left and right Windows keys correctly. Reenable the SDL2 Windows keyboard hook procedure. Since SDL2 2.30.4 the SDL2 keyboard hook procedure also filters out the special left Control key event for every Alt Gr key event on keyboards with an international layout. This means the QEMU low level keyboard hook procedure is no longer needed. Remove the QEMU Windows keyboard hook procedure. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2139 Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2323 Signed-off-by: Volker Rümelin Link: https://lore.kernel.org/r/20241231115950.6732-1-vr_qemu@t-online.de Signed-off-by: Paolo Bonzini --- ui/meson.build | 4 ---- ui/sdl2.c | 26 -------------------------- 2 files changed, 30 deletions(-) diff --git a/ui/meson.build b/ui/meson.build index 28c7381dd102..35fb04cadf3a 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -120,10 +120,6 @@ if gtk.found() endif if sdl.found() - if host_os == 'windows' - system_ss.add(files('win32-kbd-hook.c')) - endif - sdl_ss = ss.source_set() sdl_ss.add(sdl, sdl_image, pixman, glib, files( 'sdl2-2d.c', diff --git a/ui/sdl2.c b/ui/sdl2.c index 445eb1dd9f9a..cda4293a53e6 100644 --- a/ui/sdl2.c +++ b/ui/sdl2.c @@ -32,7 +32,6 @@ #include "system/runstate.h" #include "system/runstate-action.h" #include "system/system.h" -#include "ui/win32-kbd-hook.h" #include "qemu/log.h" #include "qemu-main.h" @@ -263,7 +262,6 @@ static void sdl_grab_start(struct sdl2_console *scon) } SDL_SetWindowGrab(scon->real_window, SDL_TRUE); gui_grab = 1; - win32_kbd_set_grab(true); sdl_update_caption(scon); } @@ -271,7 +269,6 @@ static void sdl_grab_end(struct sdl2_console *scon) { SDL_SetWindowGrab(scon->real_window, SDL_FALSE); gui_grab = 0; - win32_kbd_set_grab(false); sdl_show_cursor(scon); sdl_update_caption(scon); } @@ -372,19 +369,6 @@ static int get_mod_state(void) } } -static void *sdl2_win32_get_hwnd(struct sdl2_console *scon) -{ -#ifdef CONFIG_WIN32 - SDL_SysWMinfo info; - - SDL_VERSION(&info.version); - if (SDL_GetWindowWMInfo(scon->real_window, &info)) { - return info.info.win.window; - } -#endif - return NULL; -} - static void handle_keydown(SDL_Event *ev) { int win; @@ -609,10 +593,6 @@ static void handle_windowevent(SDL_Event *ev) sdl2_redraw(scon); break; case SDL_WINDOWEVENT_FOCUS_GAINED: - win32_kbd_set_grab(gui_grab); - if (qemu_console_is_graphic(scon->dcl.con)) { - win32_kbd_set_window(sdl2_win32_get_hwnd(scon)); - } /* fall through */ case SDL_WINDOWEVENT_ENTER: if (!gui_grab && (qemu_input_is_absolute(scon->dcl.con) || absolute_enabled)) { @@ -628,9 +608,6 @@ static void handle_windowevent(SDL_Event *ev) scon->ignore_hotkeys = get_mod_state(); break; case SDL_WINDOWEVENT_FOCUS_LOST: - if (qemu_console_is_graphic(scon->dcl.con)) { - win32_kbd_set_window(NULL); - } if (gui_grab && !gui_fullscreen) { sdl_grab_end(scon); } @@ -870,10 +847,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */ SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); #endif -#ifndef CONFIG_WIN32 - /* QEMU uses its own low level keyboard hook procedure on Windows */ SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); -#endif #ifdef SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); #endif From 9038ac0c5c4ce8f6cf49e1146a79b633dc534b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 30 Jan 2025 11:57:43 +0100 Subject: [PATCH 0043/1179] overall: Remove unnecessary g_strdup_printf() calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace g_strdup_printf("%s", value) -> g_strdup(value) to avoid unnecessary string formatting. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Daniel P. Berrangé Reviewed-by: Stefan Hajnoczi Reviewed-by: Michael Tokarev Signed-off-by: Michael Tokarev --- crypto/hash-afalg.c | 2 +- hw/ppc/spapr_caps.c | 2 +- plugins/loader.c | 2 +- target/i386/cpu.c | 2 +- trace/simple.c | 2 +- ui/console.c | 4 +--- ui/gtk.c | 3 +-- util/module.c | 2 +- 8 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crypto/hash-afalg.c b/crypto/hash-afalg.c index 8c0ce5b52000..bd3fe3b42723 100644 --- a/crypto/hash-afalg.c +++ b/crypto/hash-afalg.c @@ -59,7 +59,7 @@ qcrypto_afalg_hash_format_name(QCryptoHashAlgo alg, if (is_hmac) { name = g_strdup_printf("hmac(%s)", alg_name); } else { - name = g_strdup_printf("%s", alg_name); + name = g_strdup(alg_name); } return name; diff --git a/hw/ppc/spapr_caps.c b/hw/ppc/spapr_caps.c index 7edd13836015..904bff87ce12 100644 --- a/hw/ppc/spapr_caps.c +++ b/hw/ppc/spapr_caps.c @@ -1034,7 +1034,7 @@ void spapr_caps_add_properties(SpaprMachineClass *smc) for (i = 0; i < ARRAY_SIZE(capability_table); i++) { SpaprCapabilityInfo *cap = &capability_table[i]; g_autofree char *name = g_strdup_printf("cap-%s", cap->name); - g_autofree char *desc = g_strdup_printf("%s", cap->description); + g_autofree char *desc = g_strdup(cap->description); object_class_property_add(klass, name, cap->type, cap->get, cap->set, diff --git a/plugins/loader.c b/plugins/loader.c index ebc01da9c6ec..99686b546666 100644 --- a/plugins/loader.c +++ b/plugins/loader.c @@ -128,7 +128,7 @@ static int plugin_add(void *opaque, const char *name, const char *value, /* Will treat arg="argname" as "argname=on" */ fullarg = g_strdup_printf("%s=%s", value, "on"); } else { - fullarg = g_strdup_printf("%s", value); + fullarg = g_strdup(value); } warn_report("using 'arg=%s' is deprecated", value); error_printf("Please use '%s' directly\n", fullarg); diff --git a/target/i386/cpu.c b/target/i386/cpu.c index b5dd60d2812e..72ab147e851a 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6166,7 +6166,7 @@ static void x86_cpu_list_entry(gpointer data, gpointer user_data) desc = g_strdup_printf("%s [%s]", model_id, cc->model->note); } if (!desc) { - desc = g_strdup_printf("%s", model_id); + desc = g_strdup(model_id); } if (cc->model && cc->model->cpudef->deprecation_note) { diff --git a/trace/simple.c b/trace/simple.c index 18af590cf7b1..c0aba00cb7f8 100644 --- a/trace/simple.c +++ b/trace/simple.c @@ -366,7 +366,7 @@ void st_set_trace_file(const char *file) /* Type cast needed for Windows where getpid() returns an int. */ trace_file_name = g_strdup_printf(CONFIG_TRACE_FILE "-" FMT_pid, (pid_t)getpid()); } else { - trace_file_name = g_strdup_printf("%s", file); + trace_file_name = g_strdup(file); } st_set_trace_file_enabled(saved_enable); diff --git a/ui/console.c b/ui/console.c index 914ed2cc76bf..6456e8dd9086 100644 --- a/ui/console.c +++ b/ui/console.c @@ -1386,9 +1386,7 @@ char *qemu_console_get_label(QemuConsole *con) object_get_typename(c->device), c->head); } else { - return g_strdup_printf("%s", dev->id ? - dev->id : - object_get_typename(c->device)); + return g_strdup(dev->id ? : object_get_typename(c->device)); } } return g_strdup("VGA"); diff --git a/ui/gtk.c b/ui/gtk.c index c02374314893..59bda83da657 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -1944,8 +1944,7 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, vcd->console = vc; snprintf(buffer, sizeof(buffer), "vc%d", idx); - vc->label = g_strdup_printf("%s", vc->vte.chr->label - ? vc->vte.chr->label : buffer); + vc->label = g_strdup(vc->vte.chr->label ? : buffer); group = gd_vc_menu_init(s, vc, idx, group, view_menu); vc->vte.terminal = vte_terminal_new(); diff --git a/util/module.c b/util/module.c index 3eb0f06df165..1aa2079d0132 100644 --- a/util/module.c +++ b/util/module.c @@ -234,7 +234,7 @@ int module_load(const char *prefix, const char *name, Error **errp) search_dir = getenv("QEMU_MODULE_DIR"); if (search_dir != NULL) { - dirs[n_dirs++] = g_strdup_printf("%s", search_dir); + dirs[n_dirs++] = g_strdup(search_dir); } dirs[n_dirs++] = get_relocated_path(CONFIG_QEMU_MODDIR); From c996dacfa1fd090910f8614c06df2a350b15211a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 19:24:24 +0100 Subject: [PATCH 0044/1179] qemu/timer: Clarify timer_new*() must be freed with timer_free() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was not mention QEMUTimer created with timer_new*() must be released with timer_free() instead of g_free(), because then active timers are removed from the active list. Update the documentation mentioning timer_free(). Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Daniel P. Berrangé Reviewed-by: Michael Tokarev Signed-off-by: Michael Tokarev --- include/qemu/timer.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/qemu/timer.h b/include/qemu/timer.h index cc167bd825ba..abd2204f3bea 100644 --- a/include/qemu/timer.h +++ b/include/qemu/timer.h @@ -507,6 +507,8 @@ static inline void timer_init_ms(QEMUTimer *ts, QEMUClockType type, * with an AioContext---each of them runs its timer callbacks in its own * AioContext thread. * + * The timer returned must be freed using timer_free(). + * * Returns: a pointer to the timer */ static inline QEMUTimer *timer_new_full(QEMUTimerListGroup *timer_list_group, @@ -530,6 +532,8 @@ static inline QEMUTimer *timer_new_full(QEMUTimerListGroup *timer_list_group, * and associate it with the default timer list for the clock type @type. * See timer_new_full for details. * + * The timer returned must be freed using timer_free(). + * * Returns: a pointer to the timer */ static inline QEMUTimer *timer_new(QEMUClockType type, int scale, @@ -548,6 +552,8 @@ static inline QEMUTimer *timer_new(QEMUClockType type, int scale, * associated with the clock. * See timer_new_full for details. * + * The timer returned must be freed using timer_free(). + * * Returns: a pointer to the newly created timer */ static inline QEMUTimer *timer_new_ns(QEMUClockType type, QEMUTimerCB *cb, @@ -566,6 +572,8 @@ static inline QEMUTimer *timer_new_ns(QEMUClockType type, QEMUTimerCB *cb, * associated with the clock. * See timer_new_full for details. * + * The timer returned must be freed using timer_free(). + * * Returns: a pointer to the newly created timer */ static inline QEMUTimer *timer_new_us(QEMUClockType type, QEMUTimerCB *cb, @@ -584,6 +592,8 @@ static inline QEMUTimer *timer_new_us(QEMUClockType type, QEMUTimerCB *cb, * associated with the clock. * See timer_new_full for details. * + * The timer returned must be freed using timer_free(). + * * Returns: a pointer to the newly created timer */ static inline QEMUTimer *timer_new_ms(QEMUClockType type, QEMUTimerCB *cb, From 0f35d854d25f70b6cf37b3c122e11b83e4832516 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 7 Feb 2025 15:28:23 +0000 Subject: [PATCH 0045/1179] target/riscv: Fix minor whitespace issue in riscv_cpu_properties The mvendorid/mimpid/marchid properties have the wrong amount of whitespace ahead of them. Signed-off-by: Rob Bradford Reviewed-by: Daniel Henrique Barboza Signed-off-by: Michael Tokarev --- target/riscv/cpu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 3d4bd157d2cc..cca24b9f1fc9 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -2844,9 +2844,9 @@ static const Property riscv_cpu_properties[] = { {.name = "cbop_blocksize", .info = &prop_cbop_blksize}, {.name = "cboz_blocksize", .info = &prop_cboz_blksize}, - {.name = "mvendorid", .info = &prop_mvendorid}, - {.name = "mimpid", .info = &prop_mimpid}, - {.name = "marchid", .info = &prop_marchid}, + {.name = "mvendorid", .info = &prop_mvendorid}, + {.name = "mimpid", .info = &prop_mimpid}, + {.name = "marchid", .info = &prop_marchid}, #ifndef CONFIG_USER_ONLY DEFINE_PROP_UINT64("resetvec", RISCVCPU, env.resetvec, DEFAULT_RSTVEC), From b79b05d1a06a013447ea93b81c07612766b735f2 Mon Sep 17 00:00:00 2001 From: Michael Roth Date: Thu, 13 Feb 2025 17:53:20 -0600 Subject: [PATCH 0046/1179] make-release: don't rely on $CWD when excluding subproject directories The current logic scans qemu.git/subprojects/ from *.wrap files to determine whether or not to include the associated directories in the release tarballs. However, the script assumes that it is being run from the top-level of the source directory, which may not always be the case. In particular, when generating releases via, e.g.: make qemu-9.2.1.tar.xz the $CWD will either be an arbitrary external build directory, or qemu.git/build, and the exclusions will not be processed as expected. Fix this by using the $src parameter passed to the script as the root directory for the various subproject/ paths referenced by this logic. Also, the error case at the beginning of the subproject_dir() will not result in the error message being printed, and will instead produce an error message about "error" not being a valid command. Fix this by using basic shell commands. Fixes: be27b5149c86 ("make-release: only leave tarball of wrap-file subprojects") Cc: Paolo Bonzini Cc: Michael Tokarev Cc: qemu-stable@nongnu.org Signed-off-by: Michael Roth Signed-off-by: Michael Tokarev --- scripts/make-release | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/make-release b/scripts/make-release index 2885e872109a..1b89b3423a89 100755 --- a/scripts/make-release +++ b/scripts/make-release @@ -11,8 +11,9 @@ # See the COPYING file in the top-level directory. function subproject_dir() { - if test ! -f "subprojects/$1.wrap"; then - error "scripts/archive-source.sh should only process wrap subprojects" + if test ! -f "$src/subprojects/$1.wrap"; then + echo "scripts/archive-source.sh should only process wrap subprojects" + exit 1 fi # Print the directory key of the wrap file, defaulting to the @@ -26,7 +27,7 @@ function subproject_dir() { -e 's///p' \ -e 'q' \ -e '}' \ - "subprojects/$1.wrap") + "$src/subprojects/$1.wrap") echo "${dir:-$1}" } @@ -76,7 +77,7 @@ popd exclude=(--exclude=.git) # include the tarballs in subprojects/packagecache but not their expansion for sp in $SUBPROJECTS; do - if grep -xqF "[wrap-file]" subprojects/$sp.wrap; then + if grep -xqF "[wrap-file]" $src/subprojects/$sp.wrap; then exclude+=(--exclude=subprojects/"$(subproject_dir $sp)") fi done From ab1cb3683bd0462695a75aa9a8c1d07731caf304 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Wed, 5 Feb 2025 13:13:53 -0300 Subject: [PATCH 0047/1179] crypto: Allow gracefully ending the TLS session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QEMU's TLS session code provides no way to call gnutls_bye() to terminate a TLS session. Callers of qcrypto_tls_session_read() can choose to ignore a GNUTLS_E_PREMATURE_TERMINATION error by setting the gracefulTermination argument. The QIOChannelTLS ignores the premature termination error whenever shutdown() has already been issued. This was found to be not enough for the migration code because shutdown() might not have been issued before the connection is terminated. Add support for calling gnutls_bye() in the tlssession layer so users of QIOChannelTLS can clearly identify the end of a TLS session. Reviewed-by: Daniel P. Berrangé Acked-by: Daniel P. Berrangé Signed-off-by: Fabiano Rosas --- crypto/tlssession.c | 41 +++++++++++++++++++++++++++++++++++++ include/crypto/tlssession.h | 22 ++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/crypto/tlssession.c b/crypto/tlssession.c index 77286e23f469..d769d7a30424 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -585,6 +585,40 @@ qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) } } +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) +{ + int ret; + + if (!session->handshakeComplete) { + return 0; + } + + ret = gnutls_bye(session->handle, GNUTLS_SHUT_WR); + + if (!ret) { + return QCRYPTO_TLS_BYE_COMPLETE; + } + + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + int direction = gnutls_record_get_direction(session->handle); + return direction ? QCRYPTO_TLS_BYE_SENDING : QCRYPTO_TLS_BYE_RECVING; + } + + if (session->rerr || session->werr) { + error_setg(errp, "TLS termination failed: %s: %s", gnutls_strerror(ret), + error_get_pretty(session->rerr ? + session->rerr : session->werr)); + } else { + error_setg(errp, "TLS termination failed: %s", gnutls_strerror(ret)); + } + + error_free(session->rerr); + error_free(session->werr); + session->rerr = session->werr = NULL; + + return -1; +} int qcrypto_tls_session_get_key_size(QCryptoTLSSession *session, @@ -699,6 +733,13 @@ qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) } +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) +{ + return QCRYPTO_TLS_BYE_COMPLETE; +} + + int qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess, Error **errp) diff --git a/include/crypto/tlssession.h b/include/crypto/tlssession.h index f694a5c3c557..c0f64ce98944 100644 --- a/include/crypto/tlssession.h +++ b/include/crypto/tlssession.h @@ -323,6 +323,28 @@ typedef enum { QCryptoTLSSessionHandshakeStatus qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess); +typedef enum { + QCRYPTO_TLS_BYE_COMPLETE, + QCRYPTO_TLS_BYE_SENDING, + QCRYPTO_TLS_BYE_RECVING, +} QCryptoTLSSessionByeStatus; + +/** + * qcrypto_tls_session_bye: + * @session: the TLS session object + * @errp: pointer to a NULL-initialized error object + * + * Start, or continue, a TLS termination sequence. If the underlying + * data channel is non-blocking, then this method may return control + * before the termination is complete. The return value will indicate + * whether the termination has completed, or is waiting to send or + * receive data. In the latter cases, the caller should setup an event + * loop watch and call this method again once the underlying data + * channel is ready to read or write again. + */ +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp); + /** * qcrypto_tls_session_get_key_size: * @sess: the TLS session object From 30ee88622edfa962154222b4a674361488ed823b Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Wed, 5 Feb 2025 13:15:00 -0300 Subject: [PATCH 0048/1179] io: tls: Add qio_channel_tls_bye MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a task dispatcher for gnutls_bye similar to the qio_channel_tls_handshake_task(). The gnutls_bye() call might be interrupted and so it needs to be rescheduled. The migration code will make use of this to help the migration destination identify a premature EOF. Once the session termination is in place, any EOF that happens before the source issued gnutls_bye() will be considered an error. Reviewed-by: Daniel P. Berrangé Acked-by: Daniel P. Berrangé Signed-off-by: Fabiano Rosas --- include/io/channel-tls.h | 12 ++++++ io/channel-tls.c | 84 ++++++++++++++++++++++++++++++++++++++++ io/trace-events | 5 +++ 3 files changed, 101 insertions(+) diff --git a/include/io/channel-tls.h b/include/io/channel-tls.h index 26c67f17e2d3..7e9023570d89 100644 --- a/include/io/channel-tls.h +++ b/include/io/channel-tls.h @@ -49,8 +49,20 @@ struct QIOChannelTLS { QCryptoTLSSession *session; QIOChannelShutdown shutdown; guint hs_ioc_tag; + guint bye_ioc_tag; }; +/** + * qio_channel_tls_bye: + * @ioc: the TLS channel object + * @errp: pointer to a NULL-initialized error object + * + * Perform the TLS session termination. This method will return + * immediately and the termination will continue in the background, + * provided the main loop is running. + */ +void qio_channel_tls_bye(QIOChannelTLS *ioc, Error **errp); + /** * qio_channel_tls_new_server: * @master: the underlying channel object diff --git a/io/channel-tls.c b/io/channel-tls.c index aab630e5ae32..517ce190a433 100644 --- a/io/channel-tls.c +++ b/io/channel-tls.c @@ -247,6 +247,85 @@ void qio_channel_tls_handshake(QIOChannelTLS *ioc, qio_channel_tls_handshake_task(ioc, task, context); } +static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, + gpointer user_data); + +static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, + GMainContext *context) +{ + GIOCondition condition; + QIOChannelTLSData *data; + int status; + Error *err = NULL; + + status = qcrypto_tls_session_bye(ioc->session, &err); + + if (status < 0) { + trace_qio_channel_tls_bye_fail(ioc); + qio_task_set_error(task, err); + qio_task_complete(task); + return; + } + + if (status == QCRYPTO_TLS_BYE_COMPLETE) { + qio_task_complete(task); + return; + } + + data = g_new0(typeof(*data), 1); + data->task = task; + data->context = context; + + if (context) { + g_main_context_ref(context); + } + + if (status == QCRYPTO_TLS_BYE_SENDING) { + condition = G_IO_OUT; + } else { + condition = G_IO_IN; + } + + trace_qio_channel_tls_bye_pending(ioc, status); + ioc->bye_ioc_tag = qio_channel_add_watch_full(ioc->master, condition, + qio_channel_tls_bye_io, + data, NULL, context); +} + + +static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, + gpointer user_data) +{ + QIOChannelTLSData *data = user_data; + QIOTask *task = data->task; + GMainContext *context = data->context; + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(qio_task_get_source(task)); + + tioc->bye_ioc_tag = 0; + g_free(data); + qio_channel_tls_bye_task(tioc, task, context); + + if (context) { + g_main_context_unref(context); + } + + return FALSE; +} + +static void propagate_error(QIOTask *task, gpointer opaque) +{ + qio_task_propagate_error(task, opaque); +} + +void qio_channel_tls_bye(QIOChannelTLS *ioc, Error **errp) +{ + QIOTask *task; + + task = qio_task_new(OBJECT(ioc), propagate_error, errp, NULL); + + trace_qio_channel_tls_bye_start(ioc); + qio_channel_tls_bye_task(ioc, task, NULL); +} static void qio_channel_tls_init(Object *obj G_GNUC_UNUSED) { @@ -379,6 +458,11 @@ static int qio_channel_tls_close(QIOChannel *ioc, g_clear_handle_id(&tioc->hs_ioc_tag, g_source_remove); } + if (tioc->bye_ioc_tag) { + trace_qio_channel_tls_bye_cancel(ioc); + g_clear_handle_id(&tioc->bye_ioc_tag, g_source_remove); + } + return qio_channel_close(tioc->master, errp); } diff --git a/io/trace-events b/io/trace-events index d4c0f84a9a2f..dc3a63ba1fb2 100644 --- a/io/trace-events +++ b/io/trace-events @@ -44,6 +44,11 @@ qio_channel_tls_handshake_pending(void *ioc, int status) "TLS handshake pending qio_channel_tls_handshake_fail(void *ioc) "TLS handshake fail ioc=%p" qio_channel_tls_handshake_complete(void *ioc) "TLS handshake complete ioc=%p" qio_channel_tls_handshake_cancel(void *ioc) "TLS handshake cancel ioc=%p" +qio_channel_tls_bye_start(void *ioc) "TLS termination start ioc=%p" +qio_channel_tls_bye_pending(void *ioc, int status) "TLS termination pending ioc=%p status=%d" +qio_channel_tls_bye_fail(void *ioc) "TLS termination fail ioc=%p" +qio_channel_tls_bye_complete(void *ioc) "TLS termination complete ioc=%p" +qio_channel_tls_bye_cancel(void *ioc) "TLS termination cancel ioc=%p" qio_channel_tls_credentials_allow(void *ioc) "TLS credentials allow ioc=%p" qio_channel_tls_credentials_deny(void *ioc) "TLS credentials deny ioc=%p" From 0b8a70d70f65fbcf3ad62c975a64a356779095a9 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 6 Feb 2025 15:34:08 -0300 Subject: [PATCH 0049/1179] crypto: Remove qcrypto_tls_session_get_handshake_status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The correct way of calling qcrypto_tls_session_handshake() requires calling qcrypto_tls_session_get_handshake_status() right after it so there's no reason to have a separate method. Refactor qcrypto_tls_session_handshake() to inform the status in its own return value and alter the callers accordingly. No functional change. Suggested-by: Daniel P. Berrangé Reviewed-by: Daniel P. Berrangé Acked-by: Daniel P. Berrangé Signed-off-by: Fabiano Rosas --- crypto/tlssession.c | 55 ++++++++++------------------- include/crypto/tlssession.h | 32 +++++------------ io/channel-tls.c | 7 ++-- tests/unit/test-crypto-tlssession.c | 12 +++---- 4 files changed, 35 insertions(+), 71 deletions(-) diff --git a/crypto/tlssession.c b/crypto/tlssession.c index d769d7a30424..6d8f8df62323 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -546,45 +546,35 @@ qcrypto_tls_session_handshake(QCryptoTLSSession *session, Error **errp) { int ret = gnutls_handshake(session->handle); - if (ret == 0) { + if (!ret) { session->handshakeComplete = true; + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; + } + + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + int direction = gnutls_record_get_direction(session->handle); + return direction ? QCRYPTO_TLS_HANDSHAKE_SENDING : + QCRYPTO_TLS_HANDSHAKE_RECVING; + } + + if (session->rerr || session->werr) { + error_setg(errp, "TLS handshake failed: %s: %s", + gnutls_strerror(ret), + error_get_pretty(session->rerr ? + session->rerr : session->werr)); } else { - if (ret == GNUTLS_E_INTERRUPTED || - ret == GNUTLS_E_AGAIN) { - ret = 1; - } else { - if (session->rerr || session->werr) { - error_setg(errp, "TLS handshake failed: %s: %s", - gnutls_strerror(ret), - error_get_pretty(session->rerr ? - session->rerr : session->werr)); - } else { - error_setg(errp, "TLS handshake failed: %s", - gnutls_strerror(ret)); - } - ret = -1; - } + error_setg(errp, "TLS handshake failed: %s", + gnutls_strerror(ret)); } + error_free(session->rerr); error_free(session->werr); session->rerr = session->werr = NULL; - return ret; + return -1; } -QCryptoTLSSessionHandshakeStatus -qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) -{ - if (session->handshakeComplete) { - return QCRYPTO_TLS_HANDSHAKE_COMPLETE; - } else if (gnutls_record_get_direction(session->handle) == 0) { - return QCRYPTO_TLS_HANDSHAKE_RECVING; - } else { - return QCRYPTO_TLS_HANDSHAKE_SENDING; - } -} - int qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) { @@ -726,13 +716,6 @@ qcrypto_tls_session_handshake(QCryptoTLSSession *sess, } -QCryptoTLSSessionHandshakeStatus -qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) -{ - return QCRYPTO_TLS_HANDSHAKE_COMPLETE; -} - - int qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) { diff --git a/include/crypto/tlssession.h b/include/crypto/tlssession.h index c0f64ce98944..d77ae0d42370 100644 --- a/include/crypto/tlssession.h +++ b/include/crypto/tlssession.h @@ -75,12 +75,14 @@ * GINT_TO_POINTER(fd)); * * while (1) { - * if (qcrypto_tls_session_handshake(sess, errp) < 0) { + * int ret = qcrypto_tls_session_handshake(sess, errp); + * + * if (ret < 0) { * qcrypto_tls_session_free(sess); * return -1; * } * - * switch(qcrypto_tls_session_get_handshake_status(sess)) { + * switch(ret) { * case QCRYPTO_TLS_HANDSHAKE_COMPLETE: * if (qcrypto_tls_session_check_credentials(sess, errp) < )) { * qcrypto_tls_session_free(sess); @@ -170,7 +172,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(QCryptoTLSSession, qcrypto_tls_session_free) * * Validate the peer's credentials after a successful * TLS handshake. It is an error to call this before - * qcrypto_tls_session_get_handshake_status() returns + * qcrypto_tls_session_handshake() returns * QCRYPTO_TLS_HANDSHAKE_COMPLETE * * Returns 0 if the credentials validated, -1 on error @@ -226,7 +228,7 @@ void qcrypto_tls_session_set_callbacks(QCryptoTLSSession *sess, * registered with qcrypto_tls_session_set_callbacks() * * It is an error to call this before - * qcrypto_tls_session_get_handshake_status() returns + * qcrypto_tls_session_handshake() returns * QCRYPTO_TLS_HANDSHAKE_COMPLETE * * Returns: the number of bytes sent, @@ -256,7 +258,7 @@ ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess, * opposed to an error. * * It is an error to call this before - * qcrypto_tls_session_get_handshake_status() returns + * qcrypto_tls_session_handshake() returns * QCRYPTO_TLS_HANDSHAKE_COMPLETE * * Returns: the number of bytes received, @@ -289,8 +291,7 @@ size_t qcrypto_tls_session_check_pending(QCryptoTLSSession *sess); * the underlying data channel is non-blocking, then * this method may return control before the handshake * is complete. On non-blocking channels the - * qcrypto_tls_session_get_handshake_status() method - * should be used to determine whether the handshake + * return value determines whether the handshake * has completed, or is waiting to send or receive * data. In the latter cases, the caller should setup * an event loop watch and call this method again @@ -306,23 +307,6 @@ typedef enum { QCRYPTO_TLS_HANDSHAKE_RECVING, } QCryptoTLSSessionHandshakeStatus; -/** - * qcrypto_tls_session_get_handshake_status: - * @sess: the TLS session object - * - * Check the status of the TLS handshake. This - * is used with non-blocking data channels to - * determine whether the handshake is waiting - * to send or receive further data to/from the - * remote peer. - * - * Once this returns QCRYPTO_TLS_HANDSHAKE_COMPLETE - * it is permitted to send/receive payload data on - * the channel - */ -QCryptoTLSSessionHandshakeStatus -qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess); - typedef enum { QCRYPTO_TLS_BYE_COMPLETE, QCRYPTO_TLS_BYE_SENDING, diff --git a/io/channel-tls.c b/io/channel-tls.c index 517ce190a433..ecde6b57bfa4 100644 --- a/io/channel-tls.c +++ b/io/channel-tls.c @@ -162,16 +162,17 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, GMainContext *context) { Error *err = NULL; - QCryptoTLSSessionHandshakeStatus status; + int status; + + status = qcrypto_tls_session_handshake(ioc->session, &err); - if (qcrypto_tls_session_handshake(ioc->session, &err) < 0) { + if (status < 0) { trace_qio_channel_tls_handshake_fail(ioc); qio_task_set_error(task, err); qio_task_complete(task); return; } - status = qcrypto_tls_session_get_handshake_status(ioc->session); if (status == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { trace_qio_channel_tls_handshake_complete(ioc); if (qcrypto_tls_session_check_credentials(ioc->session, diff --git a/tests/unit/test-crypto-tlssession.c b/tests/unit/test-crypto-tlssession.c index 3395f73560fb..554054e9344c 100644 --- a/tests/unit/test-crypto-tlssession.c +++ b/tests/unit/test-crypto-tlssession.c @@ -158,8 +158,7 @@ static void test_crypto_tls_session_psk(void) rv = qcrypto_tls_session_handshake(serverSess, &error_abort); g_assert(rv >= 0); - if (qcrypto_tls_session_get_handshake_status(serverSess) == - QCRYPTO_TLS_HANDSHAKE_COMPLETE) { + if (rv == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { serverShake = true; } } @@ -167,8 +166,7 @@ static void test_crypto_tls_session_psk(void) rv = qcrypto_tls_session_handshake(clientSess, &error_abort); g_assert(rv >= 0); - if (qcrypto_tls_session_get_handshake_status(clientSess) == - QCRYPTO_TLS_HANDSHAKE_COMPLETE) { + if (rv == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { clientShake = true; } } @@ -352,8 +350,7 @@ static void test_crypto_tls_session_x509(const void *opaque) rv = qcrypto_tls_session_handshake(serverSess, &error_abort); g_assert(rv >= 0); - if (qcrypto_tls_session_get_handshake_status(serverSess) == - QCRYPTO_TLS_HANDSHAKE_COMPLETE) { + if (rv == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { serverShake = true; } } @@ -361,8 +358,7 @@ static void test_crypto_tls_session_x509(const void *opaque) rv = qcrypto_tls_session_handshake(clientSess, &error_abort); g_assert(rv >= 0); - if (qcrypto_tls_session_get_handshake_status(clientSess) == - QCRYPTO_TLS_HANDSHAKE_COMPLETE) { + if (rv == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { clientShake = true; } } From a25b013019672ab456ef8b51912eadcdda418b73 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Fri, 7 Feb 2025 10:46:17 -0300 Subject: [PATCH 0050/1179] io: Add flags argument to qio_channel_readv_full_all_eof MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to pass flags into qio_channel_tls_readv() but qio_channel_readv_full_all_eof() doesn't take a flags argument. No functional change. Reviewed-by: Daniel P. Berrangé Acked-by: Daniel P. Berrangé Signed-off-by: Fabiano Rosas --- hw/remote/mpqemu-link.c | 2 +- include/io/channel.h | 2 ++ io/channel.c | 9 ++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/hw/remote/mpqemu-link.c b/hw/remote/mpqemu-link.c index e25f97680d2d..49885a1db6e2 100644 --- a/hw/remote/mpqemu-link.c +++ b/hw/remote/mpqemu-link.c @@ -110,7 +110,7 @@ static ssize_t mpqemu_read(QIOChannel *ioc, void *buf, size_t len, int **fds, bql_unlock(); } - ret = qio_channel_readv_full_all_eof(ioc, &iov, 1, fds, nfds, errp); + ret = qio_channel_readv_full_all_eof(ioc, &iov, 1, fds, nfds, 0, errp); if (drop_bql && !iothread && !qemu_in_coroutine()) { bql_lock(); diff --git a/include/io/channel.h b/include/io/channel.h index bdf0bca92ae2..58940eead5f0 100644 --- a/include/io/channel.h +++ b/include/io/channel.h @@ -885,6 +885,7 @@ void qio_channel_set_aio_fd_handler(QIOChannel *ioc, * @niov: the length of the @iov array * @fds: an array of file handles to read * @nfds: number of file handles in @fds + * @flags: read flags (QIO_CHANNEL_READ_FLAG_*) * @errp: pointer to a NULL-initialized error object * * @@ -903,6 +904,7 @@ int coroutine_mixed_fn qio_channel_readv_full_all_eof(QIOChannel *ioc, const struct iovec *iov, size_t niov, int **fds, size_t *nfds, + int flags, Error **errp); /** diff --git a/io/channel.c b/io/channel.c index e3f17c24a00f..ebd93227651f 100644 --- a/io/channel.c +++ b/io/channel.c @@ -115,7 +115,8 @@ int coroutine_mixed_fn qio_channel_readv_all_eof(QIOChannel *ioc, size_t niov, Error **errp) { - return qio_channel_readv_full_all_eof(ioc, iov, niov, NULL, NULL, errp); + return qio_channel_readv_full_all_eof(ioc, iov, niov, NULL, NULL, 0, + errp); } int coroutine_mixed_fn qio_channel_readv_all(QIOChannel *ioc, @@ -130,6 +131,7 @@ int coroutine_mixed_fn qio_channel_readv_full_all_eof(QIOChannel *ioc, const struct iovec *iov, size_t niov, int **fds, size_t *nfds, + int flags, Error **errp) { int ret = -1; @@ -155,7 +157,7 @@ int coroutine_mixed_fn qio_channel_readv_full_all_eof(QIOChannel *ioc, while ((nlocal_iov > 0) || local_fds) { ssize_t len; len = qio_channel_readv_full(ioc, local_iov, nlocal_iov, local_fds, - local_nfds, 0, errp); + local_nfds, flags, errp); if (len == QIO_CHANNEL_ERR_BLOCK) { if (qemu_in_coroutine()) { qio_channel_yield(ioc, G_IO_IN); @@ -222,7 +224,8 @@ int coroutine_mixed_fn qio_channel_readv_full_all(QIOChannel *ioc, int **fds, size_t *nfds, Error **errp) { - int ret = qio_channel_readv_full_all_eof(ioc, iov, niov, fds, nfds, errp); + int ret = qio_channel_readv_full_all_eof(ioc, iov, niov, fds, nfds, 0, + errp); if (ret == 0) { error_setg(errp, "Unexpected end-of-file before all data were read"); From 322d873b634dc515220f154e29626a33f528bbfb Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Fri, 7 Feb 2025 10:48:21 -0300 Subject: [PATCH 0051/1179] io: Add a read flag for relaxed EOF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a read flag that can inform a channel that it's ok to receive an EOF at any moment. Channels that have some form of strict EOF tracking, such as TLS session termination, may choose to ignore EOF errors with the use of this flag. This is being added for compatibility with older migration streams that do not include a TLS termination step. Reviewed-by: Daniel P. Berrangé Signed-off-by: Fabiano Rosas --- include/io/channel.h | 1 + io/channel-tls.c | 1 + 2 files changed, 2 insertions(+) diff --git a/include/io/channel.h b/include/io/channel.h index 58940eead5f0..62b657109c7d 100644 --- a/include/io/channel.h +++ b/include/io/channel.h @@ -35,6 +35,7 @@ OBJECT_DECLARE_TYPE(QIOChannel, QIOChannelClass, #define QIO_CHANNEL_WRITE_FLAG_ZERO_COPY 0x1 #define QIO_CHANNEL_READ_FLAG_MSG_PEEK 0x1 +#define QIO_CHANNEL_READ_FLAG_RELAXED_EOF 0x2 typedef enum QIOChannelFeature QIOChannelFeature; diff --git a/io/channel-tls.c b/io/channel-tls.c index ecde6b57bfa4..caf8301a9e31 100644 --- a/io/channel-tls.c +++ b/io/channel-tls.c @@ -359,6 +359,7 @@ static ssize_t qio_channel_tls_readv(QIOChannel *ioc, tioc->session, iov[i].iov_base, iov[i].iov_len, + flags & QIO_CHANNEL_READ_FLAG_RELAXED_EOF || qatomic_load_acquire(&tioc->shutdown) & QIO_CHANNEL_SHUTDOWN_READ, errp); if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) { From 48796f6b44df1dd0f78d18757889d5ac478c33e4 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Wed, 5 Feb 2025 13:17:22 -0300 Subject: [PATCH 0052/1179] migration/multifd: Terminate the TLS connection The multifd recv side has been getting a TLS error of GNUTLS_E_PREMATURE_TERMINATION at the end of migration when the send side closes the sockets without ending the TLS session. This has been masked by the code not checking the migration error after loadvm. Start ending the TLS session at multifd_send_shutdown() so the recv side always sees a clean termination (EOF) and we can start to differentiate that from an actual premature termination that might possibly happen in the middle of the migration. There's nothing to be done if a previous migration error has already broken the connection, so add a comment explaining it and ignore any errors coming from gnutls_bye(). This doesn't break compat with older recv-side QEMUs because EOF has always caused the recv thread to exit cleanly. Reviewed-by: Peter Xu Signed-off-by: Fabiano Rosas --- migration/multifd.c | 38 +++++++++++++++++++++++++++++++++++++- migration/tls.c | 5 +++++ migration/tls.h | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/migration/multifd.c b/migration/multifd.c index ab73d6d984cf..0296758c0820 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -490,6 +490,36 @@ void multifd_send_shutdown(void) return; } + for (i = 0; i < migrate_multifd_channels(); i++) { + MultiFDSendParams *p = &multifd_send_state->params[i]; + + /* thread_created implies the TLS handshake has succeeded */ + if (p->tls_thread_created && p->thread_created) { + Error *local_err = NULL; + /* + * The destination expects the TLS session to always be + * properly terminated. This helps to detect a premature + * termination in the middle of the stream. Note that + * older QEMUs always break the connection on the source + * and the destination always sees + * GNUTLS_E_PREMATURE_TERMINATION. + */ + migration_tls_channel_end(p->c, &local_err); + + /* + * The above can return an error in case the migration has + * already failed. If the migration succeeded, errors are + * not expected but there's no need to kill the source. + */ + if (local_err && !migration_has_failed(migrate_get_current())) { + warn_report( + "multifd_send_%d: Failed to terminate TLS connection: %s", + p->id, error_get_pretty(local_err)); + break; + } + } + } + multifd_send_terminate_threads(); for (i = 0; i < migrate_multifd_channels(); i++) { @@ -1141,7 +1171,13 @@ static void *multifd_recv_thread(void *opaque) ret = qio_channel_read_all_eof(p->c, (void *)p->packet, p->packet_len, &local_err); - if (ret == 0 || ret == -1) { /* 0: EOF -1: Error */ + if (!ret) { + /* EOF */ + assert(!local_err); + break; + } + + if (ret == -1) { break; } diff --git a/migration/tls.c b/migration/tls.c index fa03d9136ca3..5cbf95238369 100644 --- a/migration/tls.c +++ b/migration/tls.c @@ -156,6 +156,11 @@ void migration_tls_channel_connect(MigrationState *s, NULL); } +void migration_tls_channel_end(QIOChannel *ioc, Error **errp) +{ + qio_channel_tls_bye(QIO_CHANNEL_TLS(ioc), errp); +} + bool migrate_channel_requires_tls_upgrade(QIOChannel *ioc) { if (!migrate_tls()) { diff --git a/migration/tls.h b/migration/tls.h index 5797d153cb01..58b25e12281c 100644 --- a/migration/tls.h +++ b/migration/tls.h @@ -36,7 +36,7 @@ void migration_tls_channel_connect(MigrationState *s, QIOChannel *ioc, const char *hostname, Error **errp); - +void migration_tls_channel_end(QIOChannel *ioc, Error **errp); /* Whether the QIO channel requires further TLS handshake? */ bool migrate_channel_requires_tls_upgrade(QIOChannel *ioc); From 9b3b192f65b1cf635719a2981dd2d4b70892d2ec Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Fri, 7 Feb 2025 10:50:49 -0300 Subject: [PATCH 0053/1179] migration/multifd: Add a compat property for TLS termination We're currently changing the way the source multifd migration handles the shutdown of the multifd channels when TLS is in use to perform a clean termination by calling gnutls_bye(). Older src QEMUs will always close the channel without terminating the TLS session. New dst QEMUs treat an unclean termination as an error. Add multifd_clean_tls_termination (default true) that can be switched on the destination whenever a src QEMU <= 9.2 is in use. (Note that the compat property is only strictly necessary for src QEMUs older than 9.1. Due to synchronization coincidences, src QEMUs 9.1 and 9.2 can put the destination in a condition where it doesn't see the unclean termination. Still, make the property more inclusive to facilitate potential backports.) Reviewed-by: Peter Xu Signed-off-by: Fabiano Rosas --- hw/core/machine.c | 1 + migration/migration.h | 33 +++++++++++++++++++++++++++++++++ migration/multifd.c | 14 ++++++++++++-- migration/multifd.h | 2 ++ migration/options.c | 2 ++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/hw/core/machine.c b/hw/core/machine.c index 254cc20c4cb8..02cff735b3fb 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -42,6 +42,7 @@ GlobalProperty hw_compat_9_2[] = { { "virtio-balloon-pci-transitional", "vectors", "0" }, { "virtio-balloon-pci-non-transitional", "vectors", "0" }, { "virtio-mem-pci", "vectors", "0" }, + { "migration", "multifd-clean-tls-termination", "false" }, }; const size_t hw_compat_9_2_len = G_N_ELEMENTS(hw_compat_9_2); diff --git a/migration/migration.h b/migration/migration.h index eaebcc2042dd..eb84f75b4a34 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -443,6 +443,39 @@ struct MigrationState { * Default value is false. (since 8.1) */ bool multifd_flush_after_each_section; + + /* + * This variable only makes sense when set on the machine that is + * the destination of a multifd migration with TLS enabled. It + * affects the behavior of the last send->recv iteration with + * regards to termination of the TLS session. + * + * When set: + * + * - the destination QEMU instance can expect to never get a + * GNUTLS_E_PREMATURE_TERMINATION error. Manifested as the error + * message: "The TLS connection was non-properly terminated". + * + * When clear: + * + * - the destination QEMU instance can expect to see a + * GNUTLS_E_PREMATURE_TERMINATION error in any multifd channel + * whenever the last recv() call of that channel happens after + * the source QEMU instance has already issued shutdown() on the + * channel. + * + * Commit 637280aeb2 (since 9.1) introduced a side effect that + * causes the destination instance to not be affected by the + * premature termination, while commit 1d457daf86 (since 10.0) + * causes the premature termination condition to be once again + * reachable. + * + * NOTE: Regardless of the state of this option, a premature + * termination of the TLS connection might happen due to error at + * any moment prior to the last send->recv iteration. + */ + bool multifd_clean_tls_termination; + /* * This decides the size of guest memory chunk that will be used * to track dirty bitmap clearing. The size of memory chunk will diff --git a/migration/multifd.c b/migration/multifd.c index 0296758c0820..554035e095f5 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -1151,6 +1151,7 @@ void multifd_recv_sync_main(void) static void *multifd_recv_thread(void *opaque) { + MigrationState *s = migrate_get_current(); MultiFDRecvParams *p = opaque; Error *local_err = NULL; bool use_packets = multifd_use_packets(); @@ -1159,18 +1160,27 @@ static void *multifd_recv_thread(void *opaque) trace_multifd_recv_thread_start(p->id); rcu_register_thread(); + if (!s->multifd_clean_tls_termination) { + p->read_flags = QIO_CHANNEL_READ_FLAG_RELAXED_EOF; + } + while (true) { uint32_t flags = 0; bool has_data = false; p->normal_num = 0; if (use_packets) { + struct iovec iov = { + .iov_base = (void *)p->packet, + .iov_len = p->packet_len + }; + if (multifd_recv_should_exit()) { break; } - ret = qio_channel_read_all_eof(p->c, (void *)p->packet, - p->packet_len, &local_err); + ret = qio_channel_readv_full_all_eof(p->c, &iov, 1, NULL, NULL, + p->read_flags, &local_err); if (!ret) { /* EOF */ assert(!local_err); diff --git a/migration/multifd.h b/migration/multifd.h index bd785b987315..cf408ff72140 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -244,6 +244,8 @@ typedef struct { uint32_t zero_num; /* used for de-compression methods */ void *compress_data; + /* Flags for the QIOChannel */ + int read_flags; } MultiFDRecvParams; typedef struct { diff --git a/migration/options.c b/migration/options.c index 4db340b502b0..bb259d192a93 100644 --- a/migration/options.c +++ b/migration/options.c @@ -99,6 +99,8 @@ const Property migration_properties[] = { clear_bitmap_shift, CLEAR_BITMAP_SHIFT_DEFAULT), DEFINE_PROP_BOOL("x-preempt-pre-7-2", MigrationState, preempt_pre_7_2, false), + DEFINE_PROP_BOOL("multifd-clean-tls-termination", MigrationState, + multifd_clean_tls_termination, true), /* Migration parameters */ DEFINE_PROP_UINT8("x-throttle-trigger-threshold", MigrationState, From e0ad300fe1a0e0b12b90994bab6e4df77dd1ee8a Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Wed, 5 Feb 2025 13:23:55 -0300 Subject: [PATCH 0054/1179] migration: Check migration error after loadvm We're currently only checking the QEMUFile error after qemu_loadvm_state(). This was causing a TLS termination error from multifd recv threads to be ignored. Start checking the migration error as well to avoid missing further errors. Regarding compatibility concerning the TLS termination error that was being ignored, for QEMUs <= 9.2 - if the old QEMU is being used as migration source - the recently added migration property multifd-tls-clean-termination needs to be set to OFF in the *destination* machine. Reviewed-by: Peter Xu Signed-off-by: Fabiano Rosas --- migration/savevm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/migration/savevm.c b/migration/savevm.c index bc375db282c2..4046faf0091e 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -2940,7 +2940,11 @@ int qemu_loadvm_state(QEMUFile *f) /* When reaching here, it must be precopy */ if (ret == 0) { - ret = qemu_file_get_error(f); + if (migrate_has_error(migrate_get_current())) { + ret = -EINVAL; + } else { + ret = qemu_file_get_error(f); + } } /* From a47f0cfba8d33f7001bcc4616f96e42dbd553135 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:19 -0300 Subject: [PATCH 0055/1179] migration: Set migration error outside of migrate_cancel There's no point passing the error into migration cancel only for it to call migrate_set_error(). Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-2-farosas@suse.de> Signed-off-by: Fabiano Rosas --- migration/migration.c | 7 ++----- migration/migration.h | 2 +- migration/ram.c | 4 +++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/migration/migration.c b/migration/migration.c index 396928513ab0..7728f52aefb8 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -342,11 +342,8 @@ void migration_bh_schedule(QEMUBHFunc *cb, void *opaque) qemu_bh_schedule(bh); } -void migration_cancel(const Error *error) +void migration_cancel() { - if (error) { - migrate_set_error(current_migration, error); - } if (migrate_dirty_limit()) { qmp_cancel_vcpu_dirty_limit(false, -1, NULL); } @@ -365,7 +362,7 @@ void migration_shutdown(void) * Cancel the current migration - that will (eventually) * stop the migration using this structure */ - migration_cancel(NULL); + migration_cancel(); object_unref(OBJECT(current_migration)); /* diff --git a/migration/migration.h b/migration/migration.h index eb84f75b4a34..f083f4f87e50 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -563,7 +563,7 @@ void migration_make_urgent_request(void); void migration_consume_urgent_request(void); bool migration_rate_limit(void); void migration_bh_schedule(QEMUBHFunc *cb, void *opaque); -void migration_cancel(const Error *error); +void migration_cancel(void); void migration_populate_vfio_info(MigrationInfo *info); void migration_reset_vfio_bytes_transferred(void); diff --git a/migration/ram.c b/migration/ram.c index 6f460fd22d20..589b6505eb22 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -4465,8 +4465,10 @@ static void ram_mig_ram_block_resized(RAMBlockNotifier *n, void *host, * Abort and indicate a proper reason. */ error_setg(&err, "RAM block '%s' resized during precopy.", rb->idstr); - migration_cancel(err); + migrate_set_error(migrate_get_current(), err); error_free(err); + + migration_cancel(); } switch (ps) { From 8444d0938112b7da8d88cc6a7481b9eb33654997 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:20 -0300 Subject: [PATCH 0056/1179] migration: Unify migration_cancel and migrate_fd_cancel There's no need for two separate functions and this _fd_ is a historic artifact that makes little sense nowadays. Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-3-farosas@suse.de> Signed-off-by: Fabiano Rosas --- migration/migration.c | 18 +++++++----------- migration/trace-events | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/migration/migration.c b/migration/migration.c index 7728f52aefb8..e37842fdd2d9 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -106,7 +106,6 @@ static GSList *migration_blockers[MIG_MODE__MAX]; static bool migration_object_check(MigrationState *ms, Error **errp); static bool migration_switchover_start(MigrationState *s, Error **errp); -static void migrate_fd_cancel(MigrationState *s); static bool close_return_path_on_source(MigrationState *s); static void migration_completion_end(MigrationState *s); static void migrate_hup_delete(MigrationState *s); @@ -342,14 +341,6 @@ void migration_bh_schedule(QEMUBHFunc *cb, void *opaque) qemu_bh_schedule(bh); } -void migration_cancel() -{ - if (migrate_dirty_limit()) { - qmp_cancel_vcpu_dirty_limit(false, -1, NULL); - } - migrate_fd_cancel(current_migration); -} - void migration_shutdown(void) { /* @@ -1555,12 +1546,17 @@ static void migrate_fd_error(MigrationState *s, const Error *error) migrate_set_error(s, error); } -static void migrate_fd_cancel(MigrationState *s) +void migration_cancel(void) { + MigrationState *s = migrate_get_current(); int old_state ; bool setup = (s->state == MIGRATION_STATUS_SETUP); - trace_migrate_fd_cancel(); + trace_migration_cancel(); + + if (migrate_dirty_limit()) { + qmp_cancel_vcpu_dirty_limit(false, -1, NULL); + } WITH_QEMU_LOCK_GUARD(&s->qemu_file_lock) { if (s->rp_state.from_dst_file) { diff --git a/migration/trace-events b/migration/trace-events index 12b262f8ee75..d22600abe68b 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -156,7 +156,7 @@ multifd_set_outgoing_channel(void *ioc, const char *ioctype, const char *hostnam migrate_set_state(const char *new_state) "new state %s" migrate_fd_cleanup(void) "" migrate_error(const char *error_desc) "error=%s" -migrate_fd_cancel(void) "" +migration_cancel(void) "" migrate_handle_rp_req_pages(const char *rbname, size_t start, size_t len) "in %s at 0x%zx len 0x%zx" migrate_pending_exact(uint64_t size, uint64_t pre, uint64_t post) "exact pending size %" PRIu64 " (pre = %" PRIu64 " post=%" PRIu64 ")" migrate_pending_estimate(uint64_t size, uint64_t pre, uint64_t post) "estimate pending size %" PRIu64 " (pre = %" PRIu64 " post=%" PRIu64 ")" From 4bbadfc55e6ec608df75911b4360e6e995daa28c Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:21 -0300 Subject: [PATCH 0057/1179] migration: Change migrate_fd_ to migration_ Remove all instances of _fd_ from the migration generic code. These functions have grown over time and the _fd_ part is now just confusing. migration_fd_error() -> migration_error() makes it a little vague. Since it's only used for migration_connect() failures, change it to migration_connect_set_error(). Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-4-farosas@suse.de> Signed-off-by: Fabiano Rosas --- migration/channel.c | 4 ++-- migration/migration.c | 30 +++++++++++++++--------------- migration/migration.h | 2 +- migration/multifd.c | 2 +- migration/rdma.c | 2 +- migration/trace-events | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/migration/channel.c b/migration/channel.c index f9de064f3b13..24a91ef91180 100644 --- a/migration/channel.c +++ b/migration/channel.c @@ -74,7 +74,7 @@ void migration_channel_connect(MigrationState *s, if (!error) { /* tls_channel_connect will call back to this * function after the TLS handshake, - * so we mustn't call migrate_fd_connect until then + * so we mustn't call migration_connect until then */ return; @@ -89,7 +89,7 @@ void migration_channel_connect(MigrationState *s, qemu_mutex_unlock(&s->qemu_file_lock); } } - migrate_fd_connect(s, error); + migration_connect(s, error); error_free(error); } diff --git a/migration/migration.c b/migration/migration.c index e37842fdd2d9..c39cedef3b56 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -1423,12 +1423,12 @@ static void migration_cleanup_json_writer(MigrationState *s) g_clear_pointer(&s->vmdesc, json_writer_free); } -static void migrate_fd_cleanup(MigrationState *s) +static void migration_cleanup(MigrationState *s) { MigrationEventType type; QEMUFile *tmp = NULL; - trace_migrate_fd_cleanup(); + trace_migration_cleanup(); migration_cleanup_json_writer(s); @@ -1485,9 +1485,9 @@ static void migrate_fd_cleanup(MigrationState *s) yank_unregister_instance(MIGRATION_YANK_INSTANCE); } -static void migrate_fd_cleanup_bh(void *opaque) +static void migration_cleanup_bh(void *opaque) { - migrate_fd_cleanup(opaque); + migration_cleanup(opaque); } void migrate_set_error(MigrationState *s, const Error *error) @@ -1517,7 +1517,7 @@ static void migrate_error_free(MigrationState *s) } } -static void migrate_fd_error(MigrationState *s, const Error *error) +static void migration_connect_set_error(MigrationState *s, const Error *error) { MigrationStatus current = s->state; MigrationStatus next; @@ -2198,7 +2198,7 @@ void qmp_migrate(const char *uri, bool has_channels, out: if (local_err) { - migrate_fd_error(s, local_err); + migration_connect_set_error(s, local_err); error_propagate(errp, local_err); } } @@ -2243,7 +2243,7 @@ static void qmp_migrate_finish(MigrationAddress *addr, bool resume_requested, if (!resume_requested) { yank_unregister_instance(MIGRATION_YANK_INSTANCE); } - migrate_fd_error(s, local_err); + migration_connect_set_error(s, local_err); error_propagate(errp, local_err); return; } @@ -3427,7 +3427,7 @@ static void migration_iteration_finish(MigrationState *s) break; } - migration_bh_schedule(migrate_fd_cleanup_bh, s); + migration_bh_schedule(migration_cleanup_bh, s); bql_unlock(); } @@ -3455,7 +3455,7 @@ static void bg_migration_iteration_finish(MigrationState *s) break; } - migration_bh_schedule(migrate_fd_cleanup_bh, s); + migration_bh_schedule(migration_cleanup_bh, s); bql_unlock(); } @@ -3837,7 +3837,7 @@ static void *bg_migration_thread(void *opaque) return NULL; } -void migrate_fd_connect(MigrationState *s, Error *error_in) +void migration_connect(MigrationState *s, Error *error_in) { Error *local_err = NULL; uint64_t rate_limit; @@ -3847,24 +3847,24 @@ void migrate_fd_connect(MigrationState *s, Error *error_in) /* * If there's a previous error, free it and prepare for another one. * Meanwhile if migration completes successfully, there won't have an error - * dumped when calling migrate_fd_cleanup(). + * dumped when calling migration_cleanup(). */ migrate_error_free(s); s->expected_downtime = migrate_downtime_limit(); if (error_in) { - migrate_fd_error(s, error_in); + migration_connect_set_error(s, error_in); if (resume) { /* * Don't do cleanup for resume if channel is invalid, but only dump * the error. We wait for another channel connect from the user. * The error_report still gives HMP user a hint on what failed. - * It's normally done in migrate_fd_cleanup(), but call it here + * It's normally done in migration_cleanup(), but call it here * explicitly. */ error_report_err(error_copy(s->error)); } else { - migrate_fd_cleanup(s); + migration_cleanup(s); } return; } @@ -3944,7 +3944,7 @@ void migrate_fd_connect(MigrationState *s, Error *error_in) migrate_set_error(s, local_err); migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); error_report_err(local_err); - migrate_fd_cleanup(s); + migration_cleanup(s); } static void migration_class_init(ObjectClass *klass, void *data) diff --git a/migration/migration.h b/migration/migration.h index f083f4f87e50..4639e2a7e42f 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -517,7 +517,7 @@ bool migration_has_all_channels(void); void migrate_set_error(MigrationState *s, const Error *error); bool migrate_has_error(MigrationState *s); -void migrate_fd_connect(MigrationState *s, Error *error_in); +void migration_connect(MigrationState *s, Error *error_in); int migration_call_notifiers(MigrationState *s, MigrationEventType type, Error **errp); diff --git a/migration/multifd.c b/migration/multifd.c index 554035e095f5..215ad0414a79 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -444,7 +444,7 @@ static bool multifd_send_cleanup_channel(MultiFDSendParams *p, Error **errp) * channels have no I/O handler callback registered when reaching * here, because migration thread will wait for all multifd channel * establishments to complete during setup. Since - * migrate_fd_cleanup() will be scheduled in main thread too, all + * migration_cleanup() will be scheduled in main thread too, all * previous callbacks should guarantee to be completed when * reaching here. See multifd_send_state.channels_created and its * usage. In the future, we could replace this with an assert diff --git a/migration/rdma.c b/migration/rdma.c index 855753c67191..76fb0349238a 100644 --- a/migration/rdma.c +++ b/migration/rdma.c @@ -4174,7 +4174,7 @@ void rdma_start_outgoing_migration(void *opaque, s->to_dst_file = rdma_new_output(rdma); s->rdma_migration = true; - migrate_fd_connect(s, NULL); + migration_connect(s, NULL); return; return_path_err: qemu_rdma_cleanup(rdma); diff --git a/migration/trace-events b/migration/trace-events index d22600abe68b..58c0f07f5b2d 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -154,7 +154,7 @@ multifd_set_outgoing_channel(void *ioc, const char *ioctype, const char *hostnam # migration.c migrate_set_state(const char *new_state) "new state %s" -migrate_fd_cleanup(void) "" +migration_cleanup(void) "" migrate_error(const char *error_desc) "error=%s" migration_cancel(void) "" migrate_handle_rp_req_pages(const char *rbname, size_t start, size_t len) "in %s at 0x%zx len 0x%zx" From 2b667a8c0f7ad423c9141b3a487898c50a6ff5e0 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:22 -0300 Subject: [PATCH 0058/1179] migration: Fix hang after error in destination setup phase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the destination side fails at migration_ioc_process_incoming() before starting the coroutine, it will report the error but QEMU will not exit. Set the migration state to FAILED and exit the process if exit-on-error allows. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2633 Reported-by: Daniel P. Berrangé Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-5-farosas@suse.de> Signed-off-by: Fabiano Rosas --- migration/channel.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/migration/channel.c b/migration/channel.c index 24a91ef91180..a547b1fbfebe 100644 --- a/migration/channel.c +++ b/migration/channel.c @@ -33,6 +33,7 @@ void migration_channel_process_incoming(QIOChannel *ioc) { MigrationState *s = migrate_get_current(); + MigrationIncomingState *mis = migration_incoming_get_current(); Error *local_err = NULL; trace_migration_set_incoming_channel( @@ -47,6 +48,10 @@ void migration_channel_process_incoming(QIOChannel *ioc) if (local_err) { error_report_err(local_err); + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); + if (mis->exit_on_error) { + exit(EXIT_FAILURE); + } } } From 646119088f8a1d9925239e70b0a7b426bfb6e58a Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:23 -0300 Subject: [PATCH 0059/1179] migration: Reject qmp_migrate_cancel after postcopy After postcopy has started, it's not possible to recover the source machine in case a migration error occurs because the destination has already been changing the state of the machine. For that same reason, it doesn't make sense to try to cancel the migration after postcopy has started. Reject the cancel command during postcopy. Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-6-farosas@suse.de> Signed-off-by: Fabiano Rosas --- migration/migration.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/migration/migration.c b/migration/migration.c index c39cedef3b56..48c9ad3c9680 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -2251,7 +2251,18 @@ static void qmp_migrate_finish(MigrationAddress *addr, bool resume_requested, void qmp_migrate_cancel(Error **errp) { - migration_cancel(NULL); + /* + * After postcopy migration has started, the source machine is not + * recoverable in case of a migration error. This also means the + * cancel command cannot be used as cancel should allow the + * machine to continue operation. + */ + if (migration_in_postcopy()) { + error_setg(errp, "Postcopy migration in progress, cannot cancel."); + return; + } + + migration_cancel(); } void qmp_migrate_continue(MigrationStatus state, Error **errp) From 4a228bcc994ea8ab05d4927e23e7916f32cc1168 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:24 -0300 Subject: [PATCH 0060/1179] migration: Don't set FAILED state when cancelling The expected outcome from qmp_migrate_cancel() is that the source migration goes to the terminal state MIGRATION_STATUS_CANCELLED. Anything different from this is a bug when cancelling. Make sure there is never a state transition from an unspecified state into FAILED. Code that sets FAILED, should always either make sure that the old state is not CANCELLING or specify the old state. Note that the destination is allowed to go into FAILED, so there's no issue there. (I don't think this is relevant as a backport because cancelling does work, it just doesn't show the right state at the end) Fixes: 3dde8fdbad ("migration: Merge precopy/postcopy on switchover start") Fixes: d0edb8a173 ("migration: Create the postcopy preempt channel asynchronously") Fixes: 8518278a6a ("migration: implementation of background snapshot thread") Fixes: bf78a046b9 ("migration: refactor migrate_fd_connect failures") Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-7-farosas@suse.de> Signed-off-by: Fabiano Rosas --- migration/migration.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/migration/migration.c b/migration/migration.c index 48c9ad3c9680..c597aa707e57 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -2648,7 +2648,10 @@ static int postcopy_start(MigrationState *ms, Error **errp) if (migrate_postcopy_preempt()) { migration_wait_main_channel(ms); if (postcopy_preempt_establish_channel(ms)) { - migrate_set_state(&ms->state, ms->state, MIGRATION_STATUS_FAILED); + if (ms->state != MIGRATION_STATUS_CANCELLING) { + migrate_set_state(&ms->state, ms->state, + MIGRATION_STATUS_FAILED); + } error_setg(errp, "%s: Failed to establish preempt channel", __func__); return -1; @@ -2986,7 +2989,9 @@ static void migration_completion(MigrationState *s) error_free(local_err); } - migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); + if (s->state != MIGRATION_STATUS_CANCELLING) { + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); + } } /** @@ -3009,7 +3014,7 @@ static void bg_migration_completion(MigrationState *s) qemu_put_buffer(s->to_dst_file, s->bioc->data, s->bioc->usage); qemu_fflush(s->to_dst_file); } else if (s->state == MIGRATION_STATUS_CANCELLING) { - goto fail; + return; } if (qemu_file_get_error(s->to_dst_file)) { @@ -3953,7 +3958,9 @@ void migration_connect(MigrationState *s, Error *error_in) fail: migrate_set_error(s, local_err); - migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); + if (s->state != MIGRATION_STATUS_CANCELLING) { + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); + } error_report_err(local_err); migration_cleanup(s); } From aabb2a5b5d54acde0992d933b647a306e59362b4 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:25 -0300 Subject: [PATCH 0061/1179] tests/qtest/migration: Introduce migration_test_add_suffix Introduce a new migration_test_add_suffix to allow programmatic creation of tests based on a suffix. Pass the test name into the test so it can know which variant to run. Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-8-farosas@suse.de> Signed-off-by: Fabiano Rosas --- tests/qtest/migration/migration-util.c | 24 ++++++++++++++++++++++++ tests/qtest/migration/migration-util.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/tests/qtest/migration/migration-util.c b/tests/qtest/migration/migration-util.c index 6261d80e4ac9..642cf50c8d80 100644 --- a/tests/qtest/migration/migration-util.c +++ b/tests/qtest/migration/migration-util.c @@ -236,6 +236,7 @@ char *resolve_machine_version(const char *alias, const char *var1, typedef struct { char *name; void (*func)(void); + void (*func_full)(void *); } MigrationTest; static void migration_test_destroy(gpointer data) @@ -265,6 +266,29 @@ void migration_test_add(const char *path, void (*fn)(void)) migration_test_destroy); } +static void migration_test_wrapper_full(const void *data) +{ + MigrationTest *test = (MigrationTest *)data; + + g_test_message("Running /%s%s", qtest_get_arch(), test->name); + test->func_full(test->name); +} + +void migration_test_add_suffix(const char *path, const char *suffix, + void (*fn)(void *)) +{ + MigrationTest *test = g_new0(MigrationTest, 1); + + g_assert(g_str_has_suffix(path, "/")); + g_assert(!g_str_has_prefix(suffix, "/")); + + test->func_full = fn; + test->name = g_strconcat(path, suffix, NULL); + + qtest_add_data_func_full(test->name, test, migration_test_wrapper_full, + migration_test_destroy); +} + #ifdef O_DIRECT /* * Probe for O_DIRECT support on the filesystem. Since this is used diff --git a/tests/qtest/migration/migration-util.h b/tests/qtest/migration/migration-util.h index f5f2e4650e76..44815e9c42dc 100644 --- a/tests/qtest/migration/migration-util.h +++ b/tests/qtest/migration/migration-util.h @@ -51,6 +51,8 @@ static inline bool probe_o_direct_support(const char *tmpfs) bool ufd_version_check(bool *uffd_feature_thread_id); bool kvm_dirty_ring_supported(void); void migration_test_add(const char *path, void (*fn)(void)); +void migration_test_add_suffix(const char *path, const char *suffix, + void (*fn)(void *)); char *migrate_get_connect_uri(QTestState *who); void migrate_set_ports(QTestState *to, QList *channel_list); From 538e03d28001a325a93154df2313d215721b2241 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:26 -0300 Subject: [PATCH 0062/1179] tests/qtest/migration: Add a cancel test The qmp_migrate_cancel() command is poorly tested and code inspection reveals that there might be concurrency issues with its usage. Add a test that runs a migration and calls qmp_migrate_cancel() at specific moments. In order to make the test more deterministic, instead of calling qmp_migrate_cancel() at random moments during migration, do it after the migration status change events are seen. The expected result is that qmp_migrate_cancel() on the source ends migration on the source with the "cancelled" state and ends migration on the destination with the "failed" state. The only exception is that a failed migration should continue in the failed state. Cancelling is not allowed during postcopy (no test is added for this because it's a trivial check in the code). Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-9-farosas@suse.de> Signed-off-by: Fabiano Rosas --- tests/qtest/migration/precopy-tests.c | 176 ++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c index 162fa695318e..ba273d10b9a7 100644 --- a/tests/qtest/migration/precopy-tests.c +++ b/tests/qtest/migration/precopy-tests.c @@ -20,6 +20,7 @@ #include "migration/migration-util.h" #include "ppc-util.h" #include "qobject/qlist.h" +#include "qapi-types-migration.h" #include "qemu/module.h" #include "qemu/option.h" #include "qemu/range.h" @@ -536,6 +537,161 @@ static void test_multifd_tcp_cancel(void) migrate_end(from, to2, true); } +static void test_cancel_src_after_failed(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + /* + * No migrate_incoming_qmp() at the start to force source into + * failed state during migrate_qmp(). + */ + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + migration_event_wait(from, phase); + migrate_cancel(from); + + /* cancelling will not move the migration out of 'failed' */ + + wait_for_migration_status(from, "failed", + (const char * []) { "completed", NULL }); + + /* + * Not waiting for the destination because it never started + * migration. + */ +} + +static void test_cancel_src_after_cancelled(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }"); + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + /* To move to cancelled/cancelling */ + migrate_cancel(from); + migration_event_wait(from, phase); + + /* The migrate_cancel under test */ + migrate_cancel(from); + + wait_for_migration_status(from, "cancelled", + (const char * []) { "completed", NULL }); + + wait_for_migration_status(to, "failed", + (const char * []) { "completed", NULL }); +} + +static void test_cancel_src_after_complete(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }"); + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + migration_event_wait(from, phase); + migrate_cancel(from); + + /* + * qmp_migrate_cancel() exits early if migration is not running + * anymore, the status will not change to cancelled. + */ + wait_for_migration_complete(from); + wait_for_migration_complete(to); +} + +static void test_cancel_src_after_none(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + /* + * Test that cancelling without a migration happening does not + * affect subsequent migrations + */ + migrate_cancel(to); + + wait_for_serial("src_serial"); + migrate_cancel(from); + + migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }"); + + migrate_ensure_converge(from); + migrate_qmp(from, to, uri, NULL, "{}"); + + wait_for_migration_complete(from); + wait_for_migration_complete(to); +} + +static void test_cancel_src_pre_switchover(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + migrate_set_capability(from, "pause-before-switchover", true); + migrate_set_capability(to, "pause-before-switchover", true); + + migrate_set_capability(from, "multifd", true); + migrate_set_capability(to, "multifd", true); + + migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }"); + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + migration_event_wait(from, phase); + migrate_cancel(from); + migration_event_wait(from, "cancelling"); + + wait_for_migration_status(from, "cancelled", + (const char * []) { "completed", NULL }); + + wait_for_migration_status(to, "failed", + (const char * []) { "completed", NULL }); +} + +static void test_cancel_src_after_status(void *opaque) +{ + const char *test_path = opaque; + g_autofree char *phase = g_path_get_basename(test_path); + g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs); + QTestState *from, *to; + MigrateStart args = { + .hide_stderr = true, + }; + + if (migrate_start(&from, &to, "defer", &args)) { + return; + } + + if (g_str_equal(phase, "cancelling") || + g_str_equal(phase, "cancelled")) { + test_cancel_src_after_cancelled(from, to, uri, phase); + + } else if (g_str_equal(phase, "completed")) { + test_cancel_src_after_complete(from, to, uri, phase); + + } else if (g_str_equal(phase, "failed")) { + test_cancel_src_after_failed(from, to, uri, phase); + + } else if (g_str_equal(phase, "none")) { + test_cancel_src_after_none(from, to, uri, phase); + + } else { + /* any state that comes before pre-switchover */ + test_cancel_src_pre_switchover(from, to, uri, phase); + } + + migrate_end(from, to, false); +} + static void calc_dirty_rate(QTestState *who, uint64_t calc_time) { qtest_qmp_assert_success(who, @@ -1018,4 +1174,24 @@ void migration_test_add_precopy(MigrationTestEnv *env) test_vcpu_dirty_limit); } } + + /* ensure new status don't go unnoticed */ + assert(MIGRATION_STATUS__MAX == 15); + + for (int i = MIGRATION_STATUS_NONE; i < MIGRATION_STATUS__MAX; i++) { + switch (i) { + case MIGRATION_STATUS_DEVICE: /* happens too fast */ + case MIGRATION_STATUS_WAIT_UNPLUG: /* no support in tests */ + case MIGRATION_STATUS_COLO: /* no support in tests */ + case MIGRATION_STATUS_POSTCOPY_ACTIVE: /* postcopy can't be cancelled */ + case MIGRATION_STATUS_POSTCOPY_PAUSED: + case MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP: + case MIGRATION_STATUS_POSTCOPY_RECOVER: + continue; + default: + migration_test_add_suffix("/migration/cancel/src/after/", + MigrationStatus_str(i), + test_cancel_src_after_status); + } + } } From 24f4c80cfc31ab4ed4cb6553c9289e3cf8ca63ac Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 14:59:27 -0300 Subject: [PATCH 0063/1179] migration: Update migrate_cancel documentation Update the migrate_cancel command documentation with a few words about postcopy and the expected state of the machine after migration. Acked-by: Markus Armbruster Reviewed-by: Peter Xu Message-ID: <20250213175927.19642-10-farosas@suse.de> Signed-off-by: Fabiano Rosas --- qapi/migration.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qapi/migration.json b/qapi/migration.json index 43babd1df417..8b9c53595c4c 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -1524,7 +1524,9 @@ ## # @migrate_cancel: # -# Cancel the current executing migration process. +# Cancel the currently executing migration process. Allows a new +# migration to be started right after. When postcopy-ram is in use, +# cancelling is not allowed after the postcopy phase has started. # # .. note:: This command succeeds even if there is no migration # process running. From b451705e3b90e55c6070338fa97aaae274721a5c Mon Sep 17 00:00:00 2001 From: Steve Sistare Date: Wed, 5 Feb 2025 12:54:01 -0800 Subject: [PATCH 0064/1179] migration: use parameters.mode in cpr_state_save qmp_migrate guarantees that cpr_channel is not null for MIG_MODE_CPR_TRANSFER when cpr_state_save is called: qmp_migrate() if (s->parameters.mode == MIG_MODE_CPR_TRANSFER && !cpr_channel) { return; } cpr_state_save(cpr_channel) but cpr_state_save checks for mode differently before using channel, and Coverity cannot infer that they are equivalent in outgoing QEMU, and warns that channel may be NULL: cpr_state_save(channel) MigMode mode = migrate_mode(); if (mode == MIG_MODE_CPR_TRANSFER) { f = cpr_transfer_output(channel, errp); To make Coverity happy, assert that channel != NULL in cpr_state_save. Resolves: Coverity CID 1590980 Reported-by: Peter Maydell Signed-off-by: Steve Sistare Message-ID: <1738788841-211843-1-git-send-email-steven.sistare@oracle.com> [assert instead of using parameters.mode in cpr_state_save] Signed-off-by: Fabiano Rosas --- migration/cpr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/migration/cpr.c b/migration/cpr.c index 584b0b98f79b..180faab247f4 100644 --- a/migration/cpr.c +++ b/migration/cpr.c @@ -137,6 +137,7 @@ int cpr_state_save(MigrationChannel *channel, Error **errp) trace_cpr_state_save(MigMode_str(mode)); if (mode == MIG_MODE_CPR_TRANSFER) { + g_assert(channel); f = cpr_transfer_output(channel, errp); } else { return 0; From 32a1bb21c6f4d569427099e4e495f1d07d017fdb Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Fri, 14 Feb 2025 18:55:23 +0800 Subject: [PATCH 0065/1179] guestperf: Support deferred migration for multifd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The way to enable multifd migration has been changed by commit, 82137e6c8c (migration: enforce multifd and postcopy preempt to be set before incoming), and guestperf has not made the necessary changes. If multifd migration had been enabled in the previous manner, the following error would have occurred: Multifd must be set before incoming starts Supporting deferred migration will fix it. Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Message-ID: <8874e170f890ce0bc6f25cb0d9b9ae307ce2e070.1739530098.git.yong.huang@smartx.com> Signed-off-by: Fabiano Rosas --- tests/migration-stress/guestperf/engine.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/migration-stress/guestperf/engine.py b/tests/migration-stress/guestperf/engine.py index 608d7270f65b..4b15322e8d07 100644 --- a/tests/migration-stress/guestperf/engine.py +++ b/tests/migration-stress/guestperf/engine.py @@ -106,7 +106,8 @@ def _migrate_progress(self, vm): info.get("dirty-limit-ring-full-time", 0), ) - def _migrate(self, hardware, scenario, src, dst, connect_uri): + def _migrate(self, hardware, scenario, src, + dst, connect_uri, defer_migrate): src_qemu_time = [] src_vcpu_time = [] src_pid = src.get_pid() @@ -220,6 +221,8 @@ def _migrate(self, hardware, scenario, src, dst, connect_uri): resp = src.cmd("migrate-set-parameters", vcpu_dirty_limit=scenario._vcpu_dirty_limit) + if defer_migrate: + resp = dst.cmd("migrate-incoming", uri=connect_uri) resp = src.cmd("migrate", uri=connect_uri) post_copy = False @@ -373,11 +376,14 @@ def _get_common_args(self, hardware, tunnelled=False): def _get_src_args(self, hardware): return self._get_common_args(hardware) - def _get_dst_args(self, hardware, uri): + def _get_dst_args(self, hardware, uri, defer_migrate): tunnelled = False if self._dst_host != "localhost": tunnelled = True argv = self._get_common_args(hardware, tunnelled) + + if defer_migrate: + return argv + ["-incoming", "defer"] return argv + ["-incoming", uri] @staticmethod @@ -424,6 +430,7 @@ def _get_timings(self, vm): def run(self, hardware, scenario, result_dir=os.getcwd()): abs_result_dir = os.path.join(result_dir, scenario._name) + defer_migrate = False if self._transport == "tcp": uri = "tcp:%s:9000" % self._dst_host @@ -439,6 +446,9 @@ def run(self, hardware, scenario, result_dir=os.getcwd()): except: pass + if scenario._multifd: + defer_migrate = True + if self._dst_host != "localhost": dstmonaddr = ("localhost", 9001) else: @@ -452,7 +462,7 @@ def run(self, hardware, scenario, result_dir=os.getcwd()): monitor_address=srcmonaddr) dst = QEMUMachine(self._binary, - args=self._get_dst_args(hardware, uri), + args=self._get_dst_args(hardware, uri, defer_migrate), wrapper=self._get_dst_wrapper(hardware), name="qemu-dst-%d" % os.getpid(), monitor_address=dstmonaddr) @@ -461,7 +471,8 @@ def run(self, hardware, scenario, result_dir=os.getcwd()): src.launch() dst.launch() - ret = self._migrate(hardware, scenario, src, dst, uri) + ret = self._migrate(hardware, scenario, src, + dst, uri, defer_migrate) progress_history = ret[0] qemu_timings = ret[1] vcpu_timings = ret[2] From 42f5975cd84eed96661468ae0b895eed0e16074b Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Fri, 14 Feb 2025 18:55:24 +0800 Subject: [PATCH 0066/1179] guestperf: Nitpick the inconsistent parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hyman Huang Reviewed-by: Fabiano Rosas Reviewed-by: Daniel P. Berrangé Message-ID: Signed-off-by: Fabiano Rosas --- tests/migration-stress/guestperf/comparison.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/migration-stress/guestperf/comparison.py b/tests/migration-stress/guestperf/comparison.py index 42cc0372d119..40e9d2eb1dc1 100644 --- a/tests/migration-stress/guestperf/comparison.py +++ b/tests/migration-stress/guestperf/comparison.py @@ -127,7 +127,7 @@ def __init__(self, name, scenarios): # varying numbers of channels Comparison("compr-multifd", scenarios = [ Scenario("compr-multifd-channels-4", - multifd=True, multifd_channels=2), + multifd=True, multifd_channels=4), Scenario("compr-multifd-channels-8", multifd=True, multifd_channels=8), Scenario("compr-multifd-channels-32", From 45f34156e4d9c3f4215402b34d7da32f00073066 Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Fri, 14 Feb 2025 18:55:25 +0800 Subject: [PATCH 0067/1179] guestperf: Introduce multifd compression option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guestperf tool does not cover the multifd compression option currently, it is worth supporting so that developers can analysis the migration performance with different compression algorithms. Multifd support 4 compression algorithms currently: zlib, zstd, qpl, uadk To request that multifd with the specified compression algorithm such as zlib: $ ./tests/migration-stress/guestperf.py \ --multifd --multifd-channels 4 --multifd-compression zlib \ --output output.json To run the entire standardized set of multifd compression comparisons, with unix migration: $ ./tests/migration-stress/guestperf-batch.py \ --dst-host localhost --transport unix \ --filter compr-multifd-compression* --output outputdir Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Message-ID: Signed-off-by: Fabiano Rosas --- tests/migration-stress/guestperf/comparison.py | 13 +++++++++++++ tests/migration-stress/guestperf/engine.py | 14 ++++++++++++++ tests/migration-stress/guestperf/scenario.py | 7 +++++-- tests/migration-stress/guestperf/shell.py | 3 +++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/tests/migration-stress/guestperf/comparison.py b/tests/migration-stress/guestperf/comparison.py index 40e9d2eb1dc1..dee3ac25e462 100644 --- a/tests/migration-stress/guestperf/comparison.py +++ b/tests/migration-stress/guestperf/comparison.py @@ -158,4 +158,17 @@ def __init__(self, name, scenarios): Scenario("compr-dirty-limit-50MB", dirty_limit=True, vcpu_dirty_limit=50), ]), + + # Looking at effect of multifd with + # different compression algorithms + Comparison("compr-multifd-compression", scenarios = [ + Scenario("compr-multifd-compression-zlib", + multifd=True, multifd_channels=2, multifd_compression="zlib"), + Scenario("compr-multifd-compression-zstd", + multifd=True, multifd_channels=2, multifd_compression="zstd"), + Scenario("compr-multifd-compression-qpl", + multifd=True, multifd_channels=2, multifd_compression="qpl"), + Scenario("compr-multifd-compression-uadk", + multifd=True, multifd_channels=2, multifd_compression="uadk"), + ]), ] diff --git a/tests/migration-stress/guestperf/engine.py b/tests/migration-stress/guestperf/engine.py index 4b15322e8d07..e11f6a84960b 100644 --- a/tests/migration-stress/guestperf/engine.py +++ b/tests/migration-stress/guestperf/engine.py @@ -31,6 +31,8 @@ '..', '..', '..', 'python')) from qemu.machine import QEMUMachine +# multifd supported compression algorithms +MULTIFD_CMP_ALGS = ("zlib", "zstd", "qpl", "uadk") class Engine(object): @@ -191,6 +193,12 @@ def _migrate(self, hardware, scenario, src, scenario._compression_xbzrle_cache)) if scenario._multifd: + if (scenario._multifd_compression and + (scenario._multifd_compression not in MULTIFD_CMP_ALGS)): + raise Exception("unsupported multifd compression " + "algorithm: %s" % + scenario._multifd_compression) + resp = src.cmd("migrate-set-capabilities", capabilities = [ { "capability": "multifd", @@ -206,6 +214,12 @@ def _migrate(self, hardware, scenario, src, resp = dst.cmd("migrate-set-parameters", multifd_channels=scenario._multifd_channels) + if scenario._multifd_compression: + resp = src.cmd("migrate-set-parameters", + multifd_compression=scenario._multifd_compression) + resp = dst.cmd("migrate-set-parameters", + multifd_compression=scenario._multifd_compression) + if scenario._dirty_limit: if not hardware._dirty_ring_size: raise Exception("dirty ring size must be configured when " diff --git a/tests/migration-stress/guestperf/scenario.py b/tests/migration-stress/guestperf/scenario.py index 154c4f5d5fad..4be7fafebf9b 100644 --- a/tests/migration-stress/guestperf/scenario.py +++ b/tests/migration-stress/guestperf/scenario.py @@ -30,7 +30,7 @@ def __init__(self, name, auto_converge=False, auto_converge_step=10, compression_mt=False, compression_mt_threads=1, compression_xbzrle=False, compression_xbzrle_cache=10, - multifd=False, multifd_channels=2, + multifd=False, multifd_channels=2, multifd_compression="", dirty_limit=False, x_vcpu_dirty_limit_period=500, vcpu_dirty_limit=1): @@ -61,6 +61,7 @@ def __init__(self, name, self._multifd = multifd self._multifd_channels = multifd_channels + self._multifd_compression = multifd_compression self._dirty_limit = dirty_limit self._x_vcpu_dirty_limit_period = x_vcpu_dirty_limit_period @@ -85,6 +86,7 @@ def serialize(self): "compression_xbzrle_cache": self._compression_xbzrle_cache, "multifd": self._multifd, "multifd_channels": self._multifd_channels, + "multifd_compression": self._multifd_compression, "dirty_limit": self._dirty_limit, "x_vcpu_dirty_limit_period": self._x_vcpu_dirty_limit_period, "vcpu_dirty_limit": self._vcpu_dirty_limit, @@ -109,4 +111,5 @@ def deserialize(cls, data): data["compression_xbzrle"], data["compression_xbzrle_cache"], data["multifd"], - data["multifd_channels"]) + data["multifd_channels"], + data["multifd_compression"]) diff --git a/tests/migration-stress/guestperf/shell.py b/tests/migration-stress/guestperf/shell.py index 046afeb84eb1..63bbe3226c6d 100644 --- a/tests/migration-stress/guestperf/shell.py +++ b/tests/migration-stress/guestperf/shell.py @@ -131,6 +131,8 @@ def __init__(self): action="store_true") parser.add_argument("--multifd-channels", dest="multifd_channels", default=2, type=int) + parser.add_argument("--multifd-compression", dest="multifd_compression", + default="") parser.add_argument("--dirty-limit", dest="dirty_limit", default=False, action="store_true") @@ -167,6 +169,7 @@ def get_scenario(self, args): multifd=args.multifd, multifd_channels=args.multifd_channels, + multifd_compression=args.multifd_compression, dirty_limit=args.dirty_limit, x_vcpu_dirty_limit_period=\ From 5984870e02aa6cf471bc9225ae91640b544b31c8 Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Fri, 14 Feb 2025 18:55:26 +0800 Subject: [PATCH 0068/1179] guestperf: Add test result data into report The migration result data is not included in the guestperf report information; include the result as a report entry so the developer can check whether the migration was successful after running guestperf. Signed-off-by: Hyman Huang Message-ID: <6303400c2983ffe5647f07caa6406f00ceae4581.1739530098.git.yong.huang@smartx.com> Signed-off-by: Fabiano Rosas --- tests/migration-stress/guestperf/engine.py | 10 ++++++++-- tests/migration-stress/guestperf/report.py | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/migration-stress/guestperf/engine.py b/tests/migration-stress/guestperf/engine.py index e11f6a84960b..d8462db76534 100644 --- a/tests/migration-stress/guestperf/engine.py +++ b/tests/migration-stress/guestperf/engine.py @@ -24,7 +24,7 @@ import time from guestperf.progress import Progress, ProgressStats -from guestperf.report import Report +from guestperf.report import Report, ReportResult from guestperf.timings import TimingRecord, Timings sys.path.append(os.path.join(os.path.dirname(__file__), @@ -276,7 +276,11 @@ def _migrate(self, hardware, scenario, src, src_vcpu_time.extend(self._vcpu_timing(src_pid, src_threads)) sleep_secs -= 1 - return [progress_history, src_qemu_time, src_vcpu_time] + result = ReportResult() + if progress._status == "completed" and not paused: + result = ReportResult(True) + + return [progress_history, src_qemu_time, src_vcpu_time, result] if self._verbose and (loop % 20) == 0: print("Iter %d: remain %5dMB of %5dMB (total %5dMB @ %5dMb/sec)" % ( @@ -490,6 +494,7 @@ def run(self, hardware, scenario, result_dir=os.getcwd()): progress_history = ret[0] qemu_timings = ret[1] vcpu_timings = ret[2] + result = ret[3] if uri[0:5] == "unix:" and os.path.exists(uri[5:]): os.remove(uri[5:]) @@ -509,6 +514,7 @@ def run(self, hardware, scenario, result_dir=os.getcwd()): Timings(self._get_timings(src) + self._get_timings(dst)), Timings(qemu_timings), Timings(vcpu_timings), + result, self._binary, self._dst_host, self._kernel, self._initrd, self._transport, self._sleep) except Exception as e: diff --git a/tests/migration-stress/guestperf/report.py b/tests/migration-stress/guestperf/report.py index 1efd40c86803..e135e01be6ee 100644 --- a/tests/migration-stress/guestperf/report.py +++ b/tests/migration-stress/guestperf/report.py @@ -24,6 +24,22 @@ from guestperf.progress import Progress from guestperf.timings import Timings +class ReportResult(object): + + def __init__(self, success=False): + self._success = success + + def serialize(self): + return { + "success": self._success, + } + + @classmethod + def deserialize(cls, data): + return cls( + data["success"]) + + class Report(object): def __init__(self, @@ -33,6 +49,7 @@ def __init__(self, guest_timings, qemu_timings, vcpu_timings, + result, binary, dst_host, kernel, @@ -46,6 +63,7 @@ def __init__(self, self._guest_timings = guest_timings self._qemu_timings = qemu_timings self._vcpu_timings = vcpu_timings + self._result = result self._binary = binary self._dst_host = dst_host self._kernel = kernel @@ -61,6 +79,7 @@ def serialize(self): "guest_timings": self._guest_timings.serialize(), "qemu_timings": self._qemu_timings.serialize(), "vcpu_timings": self._vcpu_timings.serialize(), + "result": self._result.serialize(), "binary": self._binary, "dst_host": self._dst_host, "kernel": self._kernel, @@ -78,6 +97,7 @@ def deserialize(cls, data): Timings.deserialize(data["guest_timings"]), Timings.deserialize(data["qemu_timings"]), Timings.deserialize(data["vcpu_timings"]), + ReportResult.deserialize(data["result"]), data["binary"], data["dst_host"], data["kernel"], From 1e0d4eb4ee7c909323bffc39bc348eb3174b426b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 12 Apr 2024 00:33:30 -0700 Subject: [PATCH 0069/1179] backends/tpm: Use qemu_hexdump_line() to avoid sprintf() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sprintf() is deprecated on Darwin since macOS 13.0 / XCode 14.1. Using qemu_hexdump_line() both fixes the deprecation warning and simplifies the code base. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Stefan Berger [rth: Keep the linebreaks every 16 bytes] Signed-off-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20240412073346.458116-12-richard.henderson@linaro.org> [PMD: Rebased] --- backends/tpm/tpm_util.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/backends/tpm/tpm_util.c b/backends/tpm/tpm_util.c index 329462510600..0a428eaf756d 100644 --- a/backends/tpm/tpm_util.c +++ b/backends/tpm/tpm_util.c @@ -21,6 +21,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qemu/cutils.h" #include "qapi/error.h" #include "qapi/visitor.h" #include "tpm_int.h" @@ -336,8 +337,8 @@ void tpm_sized_buffer_reset(TPMSizedBuffer *tsb) void tpm_util_show_buffer(const unsigned char *buffer, size_t buffer_size, const char *string) { - size_t len, i; - char *line_buffer, *p; + g_autoptr(GString) str = NULL; + size_t len, i, l; if (!trace_event_get_state_backends(TRACE_TPM_UTIL_SHOW_BUFFER_CONTENT)) { return; @@ -345,19 +346,14 @@ void tpm_util_show_buffer(const unsigned char *buffer, len = MIN(tpm_cmd_get_size(buffer), buffer_size); trace_tpm_util_show_buffer_header(string, len); - /* - * allocate enough room for 3 chars per buffer entry plus a - * newline after every 16 chars and a final null terminator. - */ - line_buffer = g_malloc(len * 3 + (len / 16) + 1); - - for (i = 0, p = line_buffer; i < len; i++) { - if (i && !(i % 16)) { - p += sprintf(p, "\n"); + for (i = 0; i < len; i += l) { + if (str) { + g_string_append_c(str, '\n'); } - p += sprintf(p, "%.2X ", buffer[i]); + l = MIN(len, 16); + str = qemu_hexdump_line(str, buffer, l, 1, 0); } - trace_tpm_util_show_buffer_content(line_buffer); - g_free(line_buffer); + g_string_ascii_up(str); + trace_tpm_util_show_buffer_content(str->str); } From 7f2626dc24198700683d264c265a2d337fff980b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 8 Nov 2024 15:24:50 +0000 Subject: [PATCH 0070/1179] hw/arm/xlnx-zynqmp: Use &error_abort for programming errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a property value is static (not provided by QMP or CLI), error shouldn't happen, otherwise it is a programming error. Therefore simplify and use &error_abort as this can't fail. Reported-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Anton Johansson Message-Id: <20241108154317.12129-11-philmd@linaro.org> --- hw/arm/xlnx-zynqmp.c | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c index bd5b0dd5e762..d6022ff2d3d6 100644 --- a/hw/arm/xlnx-zynqmp.c +++ b/hw/arm/xlnx-zynqmp.c @@ -689,16 +689,10 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) * - SDIO Specification Version 3.0 * - eMMC Specification Version 4.51 */ - if (!object_property_set_uint(sdhci, "sd-spec-version", 3, errp)) { - return; - } - if (!object_property_set_uint(sdhci, "capareg", SDHCI_CAPABILITIES, - errp)) { - return; - } - if (!object_property_set_uint(sdhci, "uhs", UHS_I, errp)) { - return; - } + object_property_set_uint(sdhci, "sd-spec-version", 3, &error_abort); + object_property_set_uint(sdhci, "capareg", SDHCI_CAPABILITIES, + &error_abort); + object_property_set_uint(sdhci, "uhs", UHS_I, &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(sdhci), errp)) { return; } @@ -763,14 +757,10 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) xlnx_zynqmp_create_unimp_mmio(s); for (i = 0; i < XLNX_ZYNQMP_NUM_GDMA_CH; i++) { - if (!object_property_set_uint(OBJECT(&s->gdma[i]), "bus-width", 128, - errp)) { - return; - } - if (!object_property_set_link(OBJECT(&s->gdma[i]), "dma", - OBJECT(system_memory), errp)) { - return; - } + object_property_set_uint(OBJECT(&s->gdma[i]), "bus-width", 128, + &error_abort); + object_property_set_link(OBJECT(&s->gdma[i]), "dma", + OBJECT(system_memory), &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->gdma[i]), errp)) { return; } @@ -811,10 +801,8 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->qspi_dma), 0, qdev_get_gpio_in(DEVICE(&s->qspi_irq_orgate), 0)); - if (!object_property_set_link(OBJECT(&s->qspi), "stream-connected-dma", - OBJECT(&s->qspi_dma), errp)) { - return; - } + object_property_set_link(OBJECT(&s->qspi), "stream-connected-dma", + OBJECT(&s->qspi_dma), &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->qspi), errp)) { return; } @@ -833,10 +821,8 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) } for (i = 0; i < XLNX_ZYNQMP_NUM_USB; i++) { - if (!object_property_set_link(OBJECT(&s->usb[i].sysbus_xhci), "dma", - OBJECT(system_memory), errp)) { - return; - } + object_property_set_link(OBJECT(&s->usb[i].sysbus_xhci), "dma", + OBJECT(system_memory), &error_abort); qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "intrs", 4); qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "slots", 2); From 1e71a9b1147145c9904b2ce5350c5591d5badb23 Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Mon, 9 Dec 2024 21:36:28 +0100 Subject: [PATCH 0071/1179] hw/intc/apic: Fixes magic number use, removes outdated comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes replaces the use of an explicit literal constant for the APIC base address mask with the existing symbolic constant intended for this purpose. Additionally, we remove the comment about not being able to re-enable the APIC after disabling it. This is no longer the case after the APIC implementation's state machine was modified in 9.0. Signed-off-by: Phil Dennis-Jordan Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20241209203629.74436-11-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- hw/intc/apic.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/intc/apic.c b/hw/intc/apic.c index d1d343d42169..d18c1dbf2cb7 100644 --- a/hw/intc/apic.c +++ b/hw/intc/apic.c @@ -350,9 +350,8 @@ static int apic_set_base(APICCommonState *s, uint64_t val) return -1; } - s->apicbase = (val & 0xfffff000) | + s->apicbase = (val & MSR_IA32_APICBASE_BASE) | (s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE)); - /* if disabled, cannot be enabled again */ if (!(val & MSR_IA32_APICBASE_ENABLE)) { s->apicbase &= ~MSR_IA32_APICBASE_ENABLE; cpu_clear_apic_feature(&s->cpu->env); From 0d2d00e57a55d3d8205923e60c2553d83d288ebb Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 10 Jan 2025 22:51:11 +0800 Subject: [PATCH 0072/1179] hw/core/machine: Reject thread level cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, neither i386 nor ARM have real hardware support for per- thread cache, and there is no clear demand for this specific cache topology. Additionally, since ARM even can't support this special cache topology in device tree, it is unnecessary to support it at this moment, even though per-thread cache might have potential scheduling benefits for VMs without CPU affinity. Therefore, disable thread-level cache topology in the general machine part. At present, i386 has not enabled SMP cache, so disabling the thread parameter does not pose compatibility issues. In the future, if there is a clear demand for this feature, the correct approach would be to add a new control field in MachineClass.smp_props and enable it only for the machines that require it. Signed-off-by: Zhao Liu Reviewed-by: Michael S. Tsirkin Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250110145115.1574345-2-zhao1.liu@intel.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/core/machine-smp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hw/core/machine-smp.c b/hw/core/machine-smp.c index b954eb849027..4e020c358b66 100644 --- a/hw/core/machine-smp.c +++ b/hw/core/machine-smp.c @@ -321,6 +321,13 @@ bool machine_parse_smp_cache(MachineState *ms, return false; } + if (props->topology == CPU_TOPOLOGY_LEVEL_THREAD) { + error_setg(errp, + "%s level cache not supported by this machine", + CpuTopologyLevel_str(props->topology)); + return false; + } + if (!machine_check_topo_support(ms, props->topology, errp)) { return false; } From d4194e19cc202774251ae03a1658077fd954797f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 18:24:39 +0100 Subject: [PATCH 0073/1179] hw/sysbus: Use sizeof(BusState) in main_system_bus_create() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than using the obscure system_bus_info.instance_size, directly use sizeof(BusState). Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Reviewed-by: Clément Mathieu--Drif Message-Id: <20250125181343.59151-2-philmd@linaro.org> --- hw/core/sysbus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/core/sysbus.c b/hw/core/sysbus.c index 9355849ff0a4..f713bbfe04f1 100644 --- a/hw/core/sysbus.c +++ b/hw/core/sysbus.c @@ -323,8 +323,8 @@ static void main_system_bus_create(void) * assign main_system_bus before qbus_init() * in order to make "if (bus != sysbus_get_default())" work */ - main_system_bus = g_malloc0(system_bus_info.instance_size); - qbus_init(main_system_bus, system_bus_info.instance_size, + main_system_bus = g_new0(BusState, 1); + qbus_init(main_system_bus, sizeof(BusState), TYPE_SYSTEM_BUS, NULL, "main-system-bus"); OBJECT(main_system_bus)->free = g_free; } From 45683d1e7c622b1e9c0a41054847f519cd2aaa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 18:24:31 +0100 Subject: [PATCH 0074/1179] hw/sysbus: Declare QOM types using DEFINE_TYPES() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When multiple QOM types are registered in the same file, it is simpler to use the the DEFINE_TYPES() macro. In particular because type array declared with such macro are easier to review. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Reviewed-by: Clément Mathieu--Drif Reviewed-by: Bernhard Beschow Message-Id: <20250125181343.59151-3-philmd@linaro.org> --- hw/core/sysbus.c | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/hw/core/sysbus.c b/hw/core/sysbus.c index f713bbfe04f1..075c7dfd69b2 100644 --- a/hw/core/sysbus.c +++ b/hw/core/sysbus.c @@ -19,7 +19,6 @@ #include "qemu/osdep.h" #include "qapi/error.h" -#include "qemu/module.h" #include "hw/sysbus.h" #include "monitor/monitor.h" #include "exec/address-spaces.h" @@ -80,13 +79,6 @@ static void system_bus_class_init(ObjectClass *klass, void *data) k->get_fw_dev_path = sysbus_get_fw_dev_path; } -static const TypeInfo system_bus_info = { - .name = TYPE_SYSTEM_BUS, - .parent = TYPE_BUS, - .instance_size = sizeof(BusState), - .class_init = system_bus_class_init, -}; - /* Check whether an IRQ source exists */ bool sysbus_has_irq(SysBusDevice *dev, int n) { @@ -306,15 +298,6 @@ static void sysbus_device_class_init(ObjectClass *klass, void *data) k->user_creatable = false; } -static const TypeInfo sysbus_device_type_info = { - .name = TYPE_SYS_BUS_DEVICE, - .parent = TYPE_DEVICE, - .instance_size = sizeof(SysBusDevice), - .abstract = true, - .class_size = sizeof(SysBusDeviceClass), - .class_init = sysbus_device_class_init, -}; - static BusState *main_system_bus; static void main_system_bus_create(void) @@ -337,10 +320,21 @@ BusState *sysbus_get_default(void) return main_system_bus; } -static void sysbus_register_types(void) -{ - type_register_static(&system_bus_info); - type_register_static(&sysbus_device_type_info); -} +static const TypeInfo sysbus_types[] = { + { + .name = TYPE_SYSTEM_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(BusState), + .class_init = system_bus_class_init, + }, + { + .name = TYPE_SYS_BUS_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(SysBusDevice), + .abstract = true, + .class_size = sizeof(SysBusDeviceClass), + .class_init = sysbus_device_class_init, + }, +}; -type_init(sysbus_register_types) +DEFINE_TYPES(sysbus_types) From 47dfd350fbf80bdfc7dcc102974fad328bf3e993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 14:36:42 +0100 Subject: [PATCH 0075/1179] hw/sysbus: Introduce TYPE_DYNAMIC_SYS_BUS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some TYPE_SYS_BUS_DEVICEs can be optionally dynamically plugged on the TYPE_PLATFORM_BUS_DEVICE. Rather than sometimes noting that with comment around the 'user_creatable = true' line in each DeviceRealize handler, introduce an abstract TYPE_DYNAMIC_SYS_BUS_DEVICE class. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Reviewed-by: Clément Mathieu--Drif Message-Id: <20250125181343.59151-4-philmd@linaro.org> --- hw/core/sysbus.c | 14 ++++++++++++++ include/hw/sysbus.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/hw/core/sysbus.c b/hw/core/sysbus.c index 075c7dfd69b2..98819d5dc61e 100644 --- a/hw/core/sysbus.c +++ b/hw/core/sysbus.c @@ -320,6 +320,14 @@ BusState *sysbus_get_default(void) return main_system_bus; } +static void dynamic_sysbus_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + + k->user_creatable = true; + k->hotpluggable = false; +} + static const TypeInfo sysbus_types[] = { { .name = TYPE_SYSTEM_BUS, @@ -335,6 +343,12 @@ static const TypeInfo sysbus_types[] = { .class_size = sizeof(SysBusDeviceClass), .class_init = sysbus_device_class_init, }, + { + .name = TYPE_DYNAMIC_SYS_BUS_DEVICE, + .parent = TYPE_SYS_BUS_DEVICE, + .class_init = dynamic_sysbus_device_class_init, + .abstract = true, + } }; DEFINE_TYPES(sysbus_types) diff --git a/include/hw/sysbus.h b/include/hw/sysbus.h index c9b1e0e90e3a..81bbda10d372 100644 --- a/include/hw/sysbus.h +++ b/include/hw/sysbus.h @@ -19,6 +19,8 @@ DECLARE_INSTANCE_CHECKER(BusState, SYSTEM_BUS, OBJECT_DECLARE_TYPE(SysBusDevice, SysBusDeviceClass, SYS_BUS_DEVICE) +#define TYPE_DYNAMIC_SYS_BUS_DEVICE "dynamic-sysbus-device" + /** * SysBusDeviceClass: * From 341df541dce236ab8f68ac9a3a0d63897767215b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 13:48:29 +0100 Subject: [PATCH 0076/1179] hw/vfio: Have VFIO_PLATFORM devices inherit from DYNAMIC_SYS_BUS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not explain why VFIO_PLATFORM devices are user_creatable, have them inherit TYPE_DYNAMIC_SYS_BUS_DEVICE, to make explicit that they can optionally be plugged on TYPE_PLATFORM_BUS_DEVICE. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Message-Id: <20250125181343.59151-5-philmd@linaro.org> --- hw/vfio/amd-xgbe.c | 2 -- hw/vfio/calxeda-xgmac.c | 2 -- hw/vfio/platform.c | 4 +--- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/hw/vfio/amd-xgbe.c b/hw/vfio/amd-xgbe.c index 96bd608b8dd0..aaa96903db0a 100644 --- a/hw/vfio/amd-xgbe.c +++ b/hw/vfio/amd-xgbe.c @@ -41,8 +41,6 @@ static void vfio_amd_xgbe_class_init(ObjectClass *klass, void *data) &vcxc->parent_realize); dc->desc = "VFIO AMD XGBE"; dc->vmsd = &vfio_platform_amd_xgbe_vmstate; - /* Supported by TYPE_VIRT_MACHINE */ - dc->user_creatable = true; } static const TypeInfo vfio_amd_xgbe_dev_info = { diff --git a/hw/vfio/calxeda-xgmac.c b/hw/vfio/calxeda-xgmac.c index 87c382e73610..b016d42b4961 100644 --- a/hw/vfio/calxeda-xgmac.c +++ b/hw/vfio/calxeda-xgmac.c @@ -41,8 +41,6 @@ static void vfio_calxeda_xgmac_class_init(ObjectClass *klass, void *data) &vcxc->parent_realize); dc->desc = "VFIO Calxeda XGMAC"; dc->vmsd = &vfio_platform_calxeda_xgmac_vmstate; - /* Supported by TYPE_VIRT_MACHINE */ - dc->user_creatable = true; } static const TypeInfo vfio_calxeda_xgmac_dev_info = { diff --git a/hw/vfio/platform.c b/hw/vfio/platform.c index 1070a2113a17..f491f4dc9543 100644 --- a/hw/vfio/platform.c +++ b/hw/vfio/platform.c @@ -672,13 +672,11 @@ static void vfio_platform_class_init(ObjectClass *klass, void *data) dc->desc = "VFIO-based platform device assignment"; sbc->connect_irq_notifier = vfio_start_irqfd_injection; set_bit(DEVICE_CATEGORY_MISC, dc->categories); - /* Supported by TYPE_VIRT_MACHINE */ - dc->user_creatable = true; } static const TypeInfo vfio_platform_dev_info = { .name = TYPE_VFIO_PLATFORM, - .parent = TYPE_SYS_BUS_DEVICE, + .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE, .instance_size = sizeof(VFIOPlatformDevice), .instance_init = vfio_platform_instance_init, .class_init = vfio_platform_class_init, From 8abda739f35373ba0525be4bf6df4f69e31241b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 18:36:43 +0100 Subject: [PATCH 0077/1179] hw/display: Have RAMFB device inherit from DYNAMIC_SYS_BUS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the RAM FB device can be optionally plugged on the TYPE_PLATFORM_BUS_DEVICE, have it inherit TYPE_DYNAMIC_SYS_BUS_DEVICE. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Reviewed-by: Clément Mathieu--Drif Message-Id: <20250125181343.59151-6-philmd@linaro.org> --- hw/display/ramfb-standalone.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/display/ramfb-standalone.c b/hw/display/ramfb-standalone.c index 6c35028965d6..1be106b57f20 100644 --- a/hw/display/ramfb-standalone.c +++ b/hw/display/ramfb-standalone.c @@ -72,13 +72,12 @@ static void ramfb_class_initfn(ObjectClass *klass, void *data) dc->vmsd = &ramfb_dev_vmstate; dc->realize = ramfb_realizefn; dc->desc = "ram framebuffer standalone device"; - dc->user_creatable = true; device_class_set_props(dc, ramfb_properties); } static const TypeInfo ramfb_info = { .name = TYPE_RAMFB_DEVICE, - .parent = TYPE_SYS_BUS_DEVICE, + .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE, .instance_size = sizeof(RAMFBStandaloneState), .class_init = ramfb_class_initfn, }; From 1e2f32bf71cd93788aa71859d0145d9a56667310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 18:36:50 +0100 Subject: [PATCH 0078/1179] hw/i386: Have X86_IOMMU devices inherit from DYNAMIC_SYS_BUS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not explain why _X86_IOMMU devices are user_creatable, have them inherit TYPE_DYNAMIC_SYS_BUS_DEVICE, to explicit they can optionally be plugged on TYPE_PLATFORM_BUS_DEVICE. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Reviewed-by: Clément Mathieu--Drif Message-Id: <20250125181343.59151-7-philmd@linaro.org> --- hw/i386/amd_iommu.c | 2 -- hw/i386/intel_iommu.c | 2 -- hw/i386/x86-iommu.c | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/hw/i386/amd_iommu.c b/hw/i386/amd_iommu.c index 6b13ce894b1a..e8e084c7cf87 100644 --- a/hw/i386/amd_iommu.c +++ b/hw/i386/amd_iommu.c @@ -1687,8 +1687,6 @@ static void amdvi_sysbus_class_init(ObjectClass *klass, void *data) dc->hotpluggable = false; dc_class->realize = amdvi_sysbus_realize; dc_class->int_remap = amdvi_int_remap; - /* Supported by the pc-q35-* machine types */ - dc->user_creatable = true; set_bit(DEVICE_CATEGORY_MISC, dc->categories); dc->desc = "AMD IOMMU (AMD-Vi) DMA Remapping device"; device_class_set_props(dc, amdvi_properties); diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index f366c223d0e1..7fde0603bfef 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -4871,8 +4871,6 @@ static void vtd_class_init(ObjectClass *klass, void *data) dc->hotpluggable = false; x86_class->realize = vtd_realize; x86_class->int_remap = vtd_int_remap; - /* Supported by the pc-q35-* machine types */ - dc->user_creatable = true; set_bit(DEVICE_CATEGORY_MISC, dc->categories); dc->desc = "Intel IOMMU (VT-d) DMA Remapping device"; } diff --git a/hw/i386/x86-iommu.c b/hw/i386/x86-iommu.c index fed34b2fcfa7..5cdd165af0db 100644 --- a/hw/i386/x86-iommu.c +++ b/hw/i386/x86-iommu.c @@ -146,7 +146,7 @@ bool x86_iommu_ir_supported(X86IOMMUState *s) static const TypeInfo x86_iommu_info = { .name = TYPE_X86_IOMMU_DEVICE, - .parent = TYPE_SYS_BUS_DEVICE, + .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE, .instance_size = sizeof(X86IOMMUState), .class_init = x86_iommu_class_init, .class_size = sizeof(X86IOMMUClass), From 4b2e34d9ef2d66811e7bc36a637a0701efc05d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 18:36:57 +0100 Subject: [PATCH 0079/1179] hw/net: Have eTSEC device inherit from DYNAMIC_SYS_BUS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the network eTSEC device can be optionally plugged on the TYPE_PLATFORM_BUS_DEVICE, have it inherit TYPE_DYNAMIC_SYS_BUS_DEVICE. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Reviewed-by: Clément Mathieu--Drif Tested-by: Bernhard Beschow Acked-by: Bernhard Beschow Message-Id: <20250125181343.59151-8-philmd@linaro.org> --- hw/net/fsl_etsec/etsec.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hw/net/fsl_etsec/etsec.c b/hw/net/fsl_etsec/etsec.c index 781b9003954b..3ce4fa2662d6 100644 --- a/hw/net/fsl_etsec/etsec.c +++ b/hw/net/fsl_etsec/etsec.c @@ -425,14 +425,12 @@ static void etsec_class_init(ObjectClass *klass, void *data) dc->realize = etsec_realize; device_class_set_legacy_reset(dc, etsec_reset); device_class_set_props(dc, etsec_properties); - /* Supported by ppce500 machine */ - dc->user_creatable = true; } static const TypeInfo etsec_types[] = { { .name = TYPE_ETSEC_COMMON, - .parent = TYPE_SYS_BUS_DEVICE, + .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE, .instance_size = sizeof(eTSEC), .class_init = etsec_class_init, .instance_init = etsec_instance_init, From c10f4c744a7cac8be38158b0793ccf8d754cecd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 14:43:24 +0100 Subject: [PATCH 0080/1179] hw/tpm: Have TPM TIS sysbus device inherit from DYNAMIC_SYS_BUS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the TPM TIS sysbus device can be optionally plugged on the TYPE_PLATFORM_BUS_DEVICE, have it inherit TYPE_DYNAMIC_SYS_BUS_DEVICE. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alexander Graf Reviewed-by: Clément Mathieu--Drif Reviewed-by: Stefan Berger Message-Id: <20250125181343.59151-9-philmd@linaro.org> --- hw/tpm/tpm_tis_sysbus.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/tpm/tpm_tis_sysbus.c b/hw/tpm/tpm_tis_sysbus.c index ee0bfe9538e2..4f187690a283 100644 --- a/hw/tpm/tpm_tis_sysbus.c +++ b/hw/tpm/tpm_tis_sysbus.c @@ -133,7 +133,6 @@ static void tpm_tis_sysbus_class_init(ObjectClass *klass, void *data) dc->vmsd = &vmstate_tpm_tis_sysbus; tc->model = TPM_MODEL_TPM_TIS; dc->realize = tpm_tis_sysbus_realizefn; - dc->user_creatable = true; device_class_set_legacy_reset(dc, tpm_tis_sysbus_reset); tc->request_completed = tpm_tis_sysbus_request_completed; tc->get_version = tpm_tis_sysbus_get_tpm_version; @@ -142,7 +141,7 @@ static void tpm_tis_sysbus_class_init(ObjectClass *klass, void *data) static const TypeInfo tpm_tis_sysbus_info = { .name = TYPE_TPM_TIS_SYSBUS, - .parent = TYPE_SYS_BUS_DEVICE, + .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE, .instance_size = sizeof(TPMStateSysBus), .instance_init = tpm_tis_sysbus_initfn, .class_init = tpm_tis_sysbus_class_init, From 83f0f363e4a24b40142079ab1b328ed653f7d14b Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Mon, 27 Jan 2025 10:41:29 +0100 Subject: [PATCH 0081/1179] hw/xen: Prefer QOM cast for XenLegacyDevice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes the code less sensitive regarding changes in the class hierarchy which will be performed in the next patch. Signed-off-by: Bernhard Beschow Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250127094129.15941-1-shentey@gmail.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/usb/xen-usb.c | 6 +++--- hw/xen/xen-legacy-backend.c | 2 +- hw/xen/xen_pvdev.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hw/usb/xen-usb.c b/hw/usb/xen-usb.c index 13b065b0faa4..fa46a7da01c1 100644 --- a/hw/usb/xen-usb.c +++ b/hw/usb/xen-usb.c @@ -755,10 +755,10 @@ static void usbback_portid_add(struct usbback_info *usbif, unsigned port, qdict = qdict_new(); qdict_put_str(qdict, "driver", "usb-host"); - tmp = g_strdup_printf("%s.0", usbif->xendev.qdev.id); + tmp = g_strdup_printf("%s.0", DEVICE(&usbif->xendev)->id); qdict_put_str(qdict, "bus", tmp); g_free(tmp); - tmp = g_strdup_printf("%s-%u", usbif->xendev.qdev.id, port); + tmp = g_strdup_printf("%s-%u", DEVICE(&usbif->xendev)->id, port); qdict_put_str(qdict, "id", tmp); g_free(tmp); qdict_put_int(qdict, "port", port); @@ -1022,7 +1022,7 @@ static void usbback_alloc(struct XenLegacyDevice *xendev) usbif = container_of(xendev, struct usbback_info, xendev); usb_bus_new(&usbif->bus, sizeof(usbif->bus), &xen_usb_bus_ops, - DEVICE(&xendev->qdev)); + DEVICE(xendev)); for (i = 0; i < USBBACK_MAXPORTS; i++) { p = &(usbif->ports[i].port); usb_register_port(&usbif->bus, p, usbif, i, &xen_usb_port_ops, diff --git a/hw/xen/xen-legacy-backend.c b/hw/xen/xen-legacy-backend.c index 118c571b3a74..ca2fe0e6b36b 100644 --- a/hw/xen/xen-legacy-backend.c +++ b/hw/xen/xen-legacy-backend.c @@ -163,7 +163,7 @@ static struct XenLegacyDevice *xen_be_get_xendev(const char *type, int dom, /* init new xendev */ xendev = g_malloc0(ops->size); - object_initialize(&xendev->qdev, ops->size, TYPE_XENBACKEND); + object_initialize(xendev, ops->size, TYPE_XENBACKEND); OBJECT(xendev)->free = g_free; qdev_set_id(DEVICE(xendev), g_strdup_printf("xen-%s-%d", type, dev), &error_fatal); diff --git a/hw/xen/xen_pvdev.c b/hw/xen/xen_pvdev.c index c9143ba259eb..fe95b62d1331 100644 --- a/hw/xen/xen_pvdev.c +++ b/hw/xen/xen_pvdev.c @@ -273,7 +273,7 @@ void xen_pv_del_xendev(struct XenLegacyDevice *xendev) QTAILQ_REMOVE(&xendevs, xendev, next); - qdev_unplug(&xendev->qdev, NULL); + qdev_unplug(DEVICE(xendev), NULL); } void xen_pv_insert_xendev(struct XenLegacyDevice *xendev) From 250e797ceadad60ee7ebfdb92c76ba2057687597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 25 Jan 2025 14:52:12 +0100 Subject: [PATCH 0082/1179] hw/xen: Have legacy Xen backend inherit from DYNAMIC_SYS_BUS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the legacy Xen backend devices can optionally be plugged on the TYPE_PLATFORM_BUS_DEVICE, have it inherit TYPE_DYNAMIC_SYS_BUS_DEVICE. Remove the implicit TYPE_XENSYSDEV instance_size. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Alexander Graf Tested-by: Bernhard Beschow Reviewed-by: Bernhard Beschow Message-Id: <20250125181343.59151-10-philmd@linaro.org> --- hw/xen/xen-legacy-backend.c | 7 ++----- include/hw/xen/xen_pvdev.h | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/hw/xen/xen-legacy-backend.c b/hw/xen/xen-legacy-backend.c index ca2fe0e6b36b..bf58db0ca68e 100644 --- a/hw/xen/xen-legacy-backend.c +++ b/hw/xen/xen-legacy-backend.c @@ -640,16 +640,14 @@ static void xendev_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); set_bit(DEVICE_CATEGORY_MISC, dc->categories); - /* xen-backend devices can be plugged/unplugged dynamically */ - dc->user_creatable = true; dc->bus_type = TYPE_XENSYSBUS; } static const TypeInfo xendev_type_info = { .name = TYPE_XENBACKEND, - .parent = TYPE_DEVICE, + .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE, .class_init = xendev_class_init, - .instance_size = sizeof(struct XenLegacyDevice), + .instance_size = sizeof(XenLegacyDevice), }; static void xen_sysbus_class_init(ObjectClass *klass, void *data) @@ -672,7 +670,6 @@ static const TypeInfo xensysbus_info = { static const TypeInfo xensysdev_info = { .name = TYPE_XENSYSDEV, .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(SysBusDevice), }; static void xenbe_register_types(void) diff --git a/include/hw/xen/xen_pvdev.h b/include/hw/xen/xen_pvdev.h index 0c9844404760..629bec90d092 100644 --- a/include/hw/xen/xen_pvdev.h +++ b/include/hw/xen/xen_pvdev.h @@ -1,7 +1,7 @@ #ifndef QEMU_HW_XEN_PVDEV_H #define QEMU_HW_XEN_PVDEV_H -#include "hw/qdev-core.h" +#include "hw/sysbus.h" #include "hw/xen/xen_backend_ops.h" /* ------------------------------------------------------------- */ @@ -32,7 +32,8 @@ struct XenDevOps { }; struct XenLegacyDevice { - DeviceState qdev; + SysBusDevice parent_obj; + const char *type; int dom; int dev; From e3660f60dca85de599c913fda1d156f20495b45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 25 Nov 2024 15:16:54 +0100 Subject: [PATCH 0083/1179] hw/boards: Convert no_sdcard flag to OnOffAuto tri-state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MachineClass::no_sdcard is initialized as false by default. To catch all uses, convert it to a tri-state, having the current default (false) becoming AUTO. No logical change intended. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250204200934.65279-2-philmd@linaro.org> --- hw/arm/xilinx_zynq.c | 2 +- hw/core/null-machine.c | 2 +- hw/s390x/s390-virtio-ccw.c | 2 +- include/hw/boards.h | 2 +- system/vl.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index 8477b8287450..12418094f9d8 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -463,7 +463,7 @@ static void zynq_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Xilinx Zynq Platform Baseboard for Cortex-A9"; mc->init = zynq_init; mc->max_cpus = ZYNQ_MAX_CPUS; - mc->no_sdcard = 1; + mc->no_sdcard = ON_OFF_AUTO_ON; mc->ignore_memory_transaction_failures = true; mc->valid_cpu_types = valid_cpu_types; mc->default_ram_id = "zynq.ext_ram"; diff --git a/hw/core/null-machine.c b/hw/core/null-machine.c index f586a4bef543..b93056c0f7b3 100644 --- a/hw/core/null-machine.c +++ b/hw/core/null-machine.c @@ -53,7 +53,7 @@ static void machine_none_machine_init(MachineClass *mc) mc->no_parallel = 1; mc->no_floppy = 1; mc->no_cdrom = 1; - mc->no_sdcard = 1; + mc->no_sdcard = ON_OFF_AUTO_ON; } DEFINE_MACHINE("none", machine_none_machine_init) diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index d9e683c5b497..5f78c8d20ff9 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c @@ -817,7 +817,7 @@ static void ccw_machine_class_init(ObjectClass *oc, void *data) mc->no_cdrom = 1; mc->no_floppy = 1; mc->no_parallel = 1; - mc->no_sdcard = 1; + mc->no_sdcard = ON_OFF_AUTO_ON; mc->max_cpus = S390_MAX_CPUS; mc->has_hotpluggable_cpus = true; mc->smp_props.books_supported = true; diff --git a/include/hw/boards.h b/include/hw/boards.h index e1f41b2a5333..d61b0a477805 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -283,9 +283,9 @@ struct MachineClass { no_parallel:1, no_floppy:1, no_cdrom:1, - no_sdcard:1, pci_allow_0_address:1, legacy_fw_cfg_order:1; + OnOffAuto no_sdcard; bool is_default; const char *default_machine_opts; const char *default_boot_order; diff --git a/system/vl.c b/system/vl.c index 9c6942c6cfcc..04ce290997c2 100644 --- a/system/vl.c +++ b/system/vl.c @@ -1346,7 +1346,7 @@ static void qemu_disable_default_devices(void) if (!has_defaults || machine_class->no_cdrom) { default_cdrom = 0; } - if (!has_defaults || machine_class->no_sdcard) { + if (!has_defaults || machine_class->no_sdcard == ON_OFF_AUTO_ON) { default_sdcard = 0; } if (!has_defaults) { From 8a2f1f921cc84cae3aa54c29e24e8c1defc9ef34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 25 Nov 2024 15:26:15 +0100 Subject: [PATCH 0084/1179] hw/boards: Explicit no_sdcard=false as ON_OFF_AUTO_OFF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update MachineClass::no_sdcard default implicit AUTO initialization to explicit OFF. This flag is consumed in system/vl.c::qemu_disable_default_devices(). Use this place to assert we don't have anymore AUTO state. In hw/ppc/e500.c we add the ppce500_machine_class_init() method to initialize once all the inherited classes. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250204200934.65279-3-philmd@linaro.org> --- hw/alpha/dp264.c | 1 + hw/arm/aspeed.c | 21 +++++++++++++++++++++ hw/arm/b-l475e-iot01a.c | 1 + hw/arm/bananapi_m2u.c | 1 + hw/arm/collie.c | 1 + hw/arm/cubieboard.c | 1 + hw/arm/digic_boards.c | 1 + hw/arm/exynos4_boards.c | 2 ++ hw/arm/fby35.c | 1 + hw/arm/highbank.c | 2 ++ hw/arm/imx25_pdk.c | 1 + hw/arm/integratorcp.c | 1 + hw/arm/kzm.c | 1 + hw/arm/mcimx6ul-evk.c | 1 + hw/arm/mcimx7d-sabre.c | 1 + hw/arm/microbit.c | 1 + hw/arm/mps2-tz.c | 4 ++++ hw/arm/mps2.c | 4 ++++ hw/arm/mps3r.c | 1 + hw/arm/msf2-som.c | 1 + hw/arm/musca.c | 2 ++ hw/arm/musicpal.c | 1 + hw/arm/netduino2.c | 1 + hw/arm/netduinoplus2.c | 1 + hw/arm/npcm7xx_boards.c | 5 +++++ hw/arm/olimex-stm32-h405.c | 1 + hw/arm/omap_sx1.c | 2 ++ hw/arm/orangepi.c | 1 + hw/arm/raspi.c | 5 +++++ hw/arm/raspi4b.c | 1 + hw/arm/realview.c | 4 ++++ hw/arm/sabrelite.c | 1 + hw/arm/sbsa-ref.c | 1 + hw/arm/stellaris.c | 2 ++ hw/arm/stm32vldiscovery.c | 1 + hw/arm/versatilepb.c | 2 ++ hw/arm/vexpress.c | 2 ++ hw/arm/virt.c | 1 + hw/arm/xen-pvh.c | 1 + hw/arm/xlnx-versal-virt.c | 1 + hw/arm/xlnx-zcu102.c | 1 + hw/avr/arduino.c | 1 + hw/hppa/machine.c | 2 ++ hw/i386/pc.c | 1 + hw/i386/x86.c | 1 + hw/i386/xen/xen-pvh.c | 1 + hw/loongarch/virt.c | 1 + hw/m68k/an5206.c | 1 + hw/m68k/mcf5208.c | 1 + hw/m68k/next-cube.c | 1 + hw/m68k/q800.c | 1 + hw/m68k/virt.c | 1 + hw/microblaze/petalogix_ml605_mmu.c | 1 + hw/microblaze/petalogix_s3adsp1800_mmu.c | 1 + hw/microblaze/xlnx-zynqmp-pmu.c | 1 + hw/mips/boston.c | 1 + hw/mips/fuloong2e.c | 1 + hw/mips/jazz.c | 2 ++ hw/mips/loongson3_virt.c | 1 + hw/mips/malta.c | 1 + hw/mips/mipssim.c | 1 + hw/openrisc/openrisc_sim.c | 1 + hw/openrisc/virt.c | 1 + hw/ppc/amigaone.c | 1 + hw/ppc/e500plat.c | 1 + hw/ppc/mac_newworld.c | 1 + hw/ppc/mac_oldworld.c | 1 + hw/ppc/mpc8544ds.c | 1 + hw/ppc/pegasos2.c | 1 + hw/ppc/pnv.c | 1 + hw/ppc/ppc405_boards.c | 1 + hw/ppc/ppc440_bamboo.c | 1 + hw/ppc/prep.c | 1 + hw/ppc/sam460ex.c | 1 + hw/ppc/spapr.c | 1 + hw/ppc/virtex_ml507.c | 1 + hw/remote/machine.c | 1 + hw/riscv/microchip_pfsoc.c | 1 + hw/riscv/opentitan.c | 1 + hw/riscv/shakti_c.c | 1 + hw/riscv/sifive_e.c | 1 + hw/riscv/sifive_u.c | 1 + hw/riscv/spike.c | 1 + hw/riscv/virt.c | 1 + hw/rx/rx-gdbsim.c | 1 + hw/sh4/r2d.c | 1 + hw/sparc/leon3.c | 1 + hw/sparc/sun4m.c | 1 + hw/sparc64/niagara.c | 1 + hw/sparc64/sun4u.c | 2 ++ hw/tricore/triboard.c | 1 + hw/tricore/tricore_testboard.c | 1 + hw/xen/xen-pvh-common.c | 1 + hw/xenpv/xen_machine_pv.c | 1 + hw/xtensa/sim.c | 1 + hw/xtensa/virt.c | 1 + hw/xtensa/xtfpga.c | 8 ++++++++ system/vl.c | 1 + 98 files changed, 152 insertions(+) diff --git a/hw/alpha/dp264.c b/hw/alpha/dp264.c index 570ea9edf24b..b11e527be936 100644 --- a/hw/alpha/dp264.c +++ b/hw/alpha/dp264.c @@ -213,6 +213,7 @@ static void clipper_machine_init(MachineClass *mc) mc->default_cpu_type = ALPHA_CPU_TYPE_NAME("ev67"); mc->default_ram_id = "ram"; mc->default_nic = "e1000"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("clipper", clipper_machine_init) diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index d9418e2b9f2c..9d9c55adcdcb 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -1253,6 +1253,7 @@ static void aspeed_machine_palmetto_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635f"; amc->num_cs = 1; amc->i2c_init = palmetto_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 256 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1269,6 +1270,7 @@ static void aspeed_machine_quanta_q71l_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635e"; amc->num_cs = 1; amc->i2c_init = quanta_q71l_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 128 * MiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1287,6 +1289,7 @@ static void aspeed_machine_supermicrox11_bmc_class_init(ObjectClass *oc, amc->num_cs = 1; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = palmetto_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 256 * MiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1305,6 +1308,7 @@ static void aspeed_machine_supermicro_x11spi_bmc_class_init(ObjectClass *oc, amc->num_cs = 1; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = palmetto_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1321,6 +1325,7 @@ static void aspeed_machine_ast2500_evb_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635f"; amc->num_cs = 1; amc->i2c_init = ast2500_evb_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1338,6 +1343,7 @@ static void aspeed_machine_yosemitev2_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635e"; amc->num_cs = 2; amc->i2c_init = yosemitev2_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1354,6 +1360,7 @@ static void aspeed_machine_romulus_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx66l1g45g"; amc->num_cs = 2; amc->i2c_init = romulus_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1371,6 +1378,7 @@ static void aspeed_machine_tiogapass_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635e"; amc->num_cs = 2; amc->i2c_init = tiogapass_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1387,6 +1395,7 @@ static void aspeed_machine_sonorapass_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx66l1g45g"; amc->num_cs = 2; amc->i2c_init = sonorapass_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1403,6 +1412,7 @@ static void aspeed_machine_witherspoon_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx66l1g45g"; amc->num_cs = 2; amc->i2c_init = witherspoon_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1423,6 +1433,7 @@ static void aspeed_machine_ast2600_evb_class_init(ObjectClass *oc, void *data) ASPEED_MAC3_ON; amc->sdhci_wp_inverted = true; amc->i2c_init = ast2600_evb_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); aspeed_machine_ast2600_class_emmc_init(oc); @@ -1441,6 +1452,7 @@ static void aspeed_machine_g220a_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = g220a_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1024 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1458,6 +1470,7 @@ static void aspeed_machine_fp5280g2_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = fp5280g2_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1476,6 +1489,7 @@ static void aspeed_machine_rainier_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON | ASPEED_MAC3_ON; amc->i2c_init = rainier_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); aspeed_machine_ast2600_class_emmc_init(oc); @@ -1498,6 +1512,7 @@ static void aspeed_machine_fuji_class_init(ObjectClass *oc, void *data) amc->macs_mask = ASPEED_MAC3_ON; amc->i2c_init = fuji_bmc_i2c_init; amc->uart_default = ASPEED_DEV_UART1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = FUJI_BMC_RAM_SIZE; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1518,6 +1533,7 @@ static void aspeed_machine_bletchley_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON; amc->i2c_init = bletchley_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = BLETCHLEY_BMC_RAM_SIZE; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1559,6 +1575,7 @@ static void aspeed_machine_fby35_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC3_ON; amc->i2c_init = fby35_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; /* FIXME: Replace this macro with something more general */ mc->default_ram_size = FUJI_BMC_RAM_SIZE; aspeed_machine_class_init_cpus_defaults(mc); @@ -1641,6 +1658,7 @@ static void aspeed_minibmc_machine_ast1030_evb_class_init(ObjectClass *oc, amc->spi_model = "w25q256"; amc->num_cs = 2; amc->macs_mask = 0; + mc->no_sdcard = ON_OFF_AUTO_OFF; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1669,6 +1687,7 @@ static void aspeed_machine_ast2700_evb_class_init(ObjectClass *oc, void *data) amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON; amc->uart_default = ASPEED_DEV_UART12; amc->i2c_init = ast2700_evb_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1689,6 +1708,7 @@ static void aspeed_machine_qcom_dc_scm_v1_class_init(ObjectClass *oc, amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON | ASPEED_MAC3_ON; amc->i2c_init = qcom_dc_scm_bmc_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1708,6 +1728,7 @@ static void aspeed_machine_qcom_firework_class_init(ObjectClass *oc, amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON | ASPEED_MAC3_ON; amc->i2c_init = qcom_dc_scm_firework_i2c_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); }; diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c index c9a5209216ca..d43c84435b02 100644 --- a/hw/arm/b-l475e-iot01a.c +++ b/hw/arm/b-l475e-iot01a.c @@ -120,6 +120,7 @@ static void bl475e_machine_init(ObjectClass *oc, void *data) mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)"; mc->init = bl475e_init; mc->valid_cpu_types = machine_valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; /* SRAM pre-allocated as part of the SoC instantiation */ mc->default_ram_size = 0; diff --git a/hw/arm/bananapi_m2u.c b/hw/arm/bananapi_m2u.c index 0a4b6f29b1cd..3da6ec4a03ce 100644 --- a/hw/arm/bananapi_m2u.c +++ b/hw/arm/bananapi_m2u.c @@ -141,6 +141,7 @@ static void bpim2u_machine_init(MachineClass *mc) mc->valid_cpu_types = valid_cpu_types; mc->default_ram_size = 1 * GiB; mc->default_ram_id = "bpim2u.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("bpim2u", bpim2u_machine_init) diff --git a/hw/arm/collie.c b/hw/arm/collie.c index eaa5c52d45a6..80bf12246a9e 100644 --- a/hw/arm/collie.c +++ b/hw/arm/collie.c @@ -79,6 +79,7 @@ static void collie_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("sa1110"); mc->default_ram_size = RAM_SIZE; mc->default_ram_id = "strongarm.sdram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo collie_machine_typeinfo = { diff --git a/hw/arm/cubieboard.c b/hw/arm/cubieboard.c index b976727eefda..11d896f8322a 100644 --- a/hw/arm/cubieboard.c +++ b/hw/arm/cubieboard.c @@ -122,6 +122,7 @@ static void cubieboard_machine_init(MachineClass *mc) mc->units_per_default_bus = 1; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "cubieboard.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("cubieboard", cubieboard_machine_init) diff --git a/hw/arm/digic_boards.c b/hw/arm/digic_boards.c index 2492fafeb852..a6ccf7ef9b64 100644 --- a/hw/arm/digic_boards.c +++ b/hw/arm/digic_boards.c @@ -143,6 +143,7 @@ static void canon_a1100_machine_init(MachineClass *mc) mc->ignore_memory_transaction_failures = true; mc->default_ram_size = 64 * MiB; mc->default_ram_id = "ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("canon-a1100", canon_a1100_machine_init) diff --git a/hw/arm/exynos4_boards.c b/hw/arm/exynos4_boards.c index 2410e2a28e81..63e86e2c6094 100644 --- a/hw/arm/exynos4_boards.c +++ b/hw/arm/exynos4_boards.c @@ -165,6 +165,7 @@ static void nuri_class_init(ObjectClass *oc, void *data) mc->min_cpus = EXYNOS4210_NCPUS; mc->default_cpus = EXYNOS4210_NCPUS; mc->ignore_memory_transaction_failures = true; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo nuri_type = { @@ -184,6 +185,7 @@ static void smdkc210_class_init(ObjectClass *oc, void *data) mc->min_cpus = EXYNOS4210_NCPUS; mc->default_cpus = EXYNOS4210_NCPUS; mc->ignore_memory_transaction_failures = true; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo smdkc210_type = { diff --git a/hw/arm/fby35.c b/hw/arm/fby35.c index 83d08e578b72..9b448bf764d2 100644 --- a/hw/arm/fby35.c +++ b/hw/arm/fby35.c @@ -170,6 +170,7 @@ static void fby35_class_init(ObjectClass *oc, void *data) mc->init = fby35_init; mc->no_floppy = 1; mc->no_cdrom = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->min_cpus = mc->max_cpus = mc->default_cpus = 3; object_class_property_add_bool(oc, "execute-in-place", diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c index 495704d97265..97477571e62e 100644 --- a/hw/arm/highbank.c +++ b/hw/arm/highbank.c @@ -357,6 +357,7 @@ static void highbank_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "highbank.dram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo highbank_type = { @@ -381,6 +382,7 @@ static void midway_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "highbank.dram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo midway_type = { diff --git a/hw/arm/imx25_pdk.c b/hw/arm/imx25_pdk.c index c9c2e5dd3b1b..8f89e03332f2 100644 --- a/hw/arm/imx25_pdk.c +++ b/hw/arm/imx25_pdk.c @@ -147,6 +147,7 @@ static void imx25_pdk_machine_init(MachineClass *mc) mc->init = imx25_pdk_init; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "imx25.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("imx25-pdk", imx25_pdk_machine_init) diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c index 4e1b8627d324..905a7c2aecfa 100644 --- a/hw/arm/integratorcp.c +++ b/hw/arm/integratorcp.c @@ -688,6 +688,7 @@ static void integratorcp_machine_init(MachineClass *mc) mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_id = "integrator.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } diff --git a/hw/arm/kzm.c b/hw/arm/kzm.c index 08d2b3025cf8..45b3b08eb80f 100644 --- a/hw/arm/kzm.c +++ b/hw/arm/kzm.c @@ -137,6 +137,7 @@ static void kzm_machine_init(MachineClass *mc) mc->init = kzm_init; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "kzm.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("kzm", kzm_machine_init) diff --git a/hw/arm/mcimx6ul-evk.c b/hw/arm/mcimx6ul-evk.c index 690cb64ef361..49520b47f186 100644 --- a/hw/arm/mcimx6ul-evk.c +++ b/hw/arm/mcimx6ul-evk.c @@ -74,5 +74,6 @@ static void mcimx6ul_evk_machine_init(MachineClass *mc) mc->init = mcimx6ul_evk_init; mc->max_cpus = FSL_IMX6UL_NUM_CPUS; mc->default_ram_id = "mcimx6ul-evk.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("mcimx6ul-evk", mcimx6ul_evk_machine_init) diff --git a/hw/arm/mcimx7d-sabre.c b/hw/arm/mcimx7d-sabre.c index b3e8e50779ff..da32fdd29d08 100644 --- a/hw/arm/mcimx7d-sabre.c +++ b/hw/arm/mcimx7d-sabre.c @@ -74,5 +74,6 @@ static void mcimx7d_sabre_machine_init(MachineClass *mc) mc->init = mcimx7d_sabre_init; mc->max_cpus = FSL_IMX7_NUM_CPUS; mc->default_ram_id = "mcimx7d-sabre.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("mcimx7d-sabre", mcimx7d_sabre_machine_init) diff --git a/hw/arm/microbit.c b/hw/arm/microbit.c index 3f56fb45ce18..9d32ae5bd517 100644 --- a/hw/arm/microbit.c +++ b/hw/arm/microbit.c @@ -67,6 +67,7 @@ static void microbit_machine_class_init(ObjectClass *oc, void *data) mc->desc = "BBC micro:bit (Cortex-M0)"; mc->init = microbit_init; mc->max_cpus = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo microbit_info = { diff --git a/hw/arm/mps2-tz.c b/hw/arm/mps2-tz.c index 13ed868b6b92..106261203685 100644 --- a/hw/arm/mps2-tz.c +++ b/hw/arm/mps2-tz.c @@ -1320,6 +1320,7 @@ static void mps2tz_an505_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN505; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41045050; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1354,6 +1355,7 @@ static void mps2tz_an521_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN521; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41045210; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1388,6 +1390,7 @@ static void mps3tz_an524_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN524; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41045240; mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1427,6 +1430,7 @@ static void mps3tz_an547_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN547; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m55"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41055470; mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ mmc->apb_periph_frq = 25 * 1000 * 1000; /* 25MHz */ diff --git a/hw/arm/mps2.c b/hw/arm/mps2.c index 3f8db0cab604..2bce37644e29 100644 --- a/hw/arm/mps2.c +++ b/hw/arm/mps2.c @@ -487,6 +487,7 @@ static void mps2_an385_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN385; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41043850; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; @@ -506,6 +507,7 @@ static void mps2_an386_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN386; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m4"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41043860; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; @@ -525,6 +527,7 @@ static void mps2_an500_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN500; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m7"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41045000; mmc->psram_base = 0x60000000; mmc->ethernet_base = 0xa0000000; @@ -544,6 +547,7 @@ static void mps2_an511_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN511; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->scc_id = 0x41045110; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; diff --git a/hw/arm/mps3r.c b/hw/arm/mps3r.c index 1bddb5e822f7..ae70ebe86046 100644 --- a/hw/arm/mps3r.c +++ b/hw/arm/mps3r.c @@ -618,6 +618,7 @@ static void mps3r_an536_class_init(ObjectClass *oc, void *data) mc->max_cpus = 2; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-r52"); mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->raminfo = an536_raminfo; mps3r_set_default_ram_info(mmc); } diff --git a/hw/arm/msf2-som.c b/hw/arm/msf2-som.c index 9b20f1e2c988..349a96a0b5d9 100644 --- a/hw/arm/msf2-som.c +++ b/hw/arm/msf2-som.c @@ -106,6 +106,7 @@ static void emcraft_sf2_machine_init(MachineClass *mc) mc->desc = "SmartFusion2 SOM kit from Emcraft (M2S010)"; mc->init = emcraft_sf2_s2s010_init; mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("emcraft-sf2", emcraft_sf2_machine_init) diff --git a/hw/arm/musca.c b/hw/arm/musca.c index e9c092abc3d4..89451b968438 100644 --- a/hw/arm/musca.c +++ b/hw/arm/musca.c @@ -615,6 +615,7 @@ static void musca_a_class_init(ObjectClass *oc, void *data) MuscaMachineClass *mmc = MUSCA_MACHINE_CLASS(oc); mc->desc = "ARM Musca-A board (dual Cortex-M33)"; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->type = MUSCA_A; mmc->init_svtor = 0x10200000; mmc->sram_addr_width = 15; @@ -629,6 +630,7 @@ static void musca_b1_class_init(ObjectClass *oc, void *data) MuscaMachineClass *mmc = MUSCA_MACHINE_CLASS(oc); mc->desc = "ARM Musca-B1 board (dual Cortex-M33)"; + mc->no_sdcard = ON_OFF_AUTO_OFF; mmc->type = MUSCA_B1; /* * This matches the DAPlink firmware which boots from QSPI. There diff --git a/hw/arm/musicpal.c b/hw/arm/musicpal.c index 48a32c24079f..d5ebfabe3f99 100644 --- a/hw/arm/musicpal.c +++ b/hw/arm/musicpal.c @@ -1342,6 +1342,7 @@ static void musicpal_machine_init(MachineClass *mc) mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_size = MP_RAM_DEFAULT_SIZE; mc->default_ram_id = "musicpal.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } diff --git a/hw/arm/netduino2.c b/hw/arm/netduino2.c index df793c77fe1b..9ca0ee6a345e 100644 --- a/hw/arm/netduino2.c +++ b/hw/arm/netduino2.c @@ -63,6 +63,7 @@ static void netduino2_machine_init(MachineClass *mc) mc->init = netduino2_init; mc->valid_cpu_types = valid_cpu_types; mc->ignore_memory_transaction_failures = true; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("netduino2", netduino2_machine_init) diff --git a/hw/arm/netduinoplus2.c b/hw/arm/netduinoplus2.c index 81b6334cf727..abe606076582 100644 --- a/hw/arm/netduinoplus2.c +++ b/hw/arm/netduinoplus2.c @@ -63,6 +63,7 @@ static void netduinoplus2_machine_init(MachineClass *mc) mc->desc = "Netduino Plus 2 Machine (Cortex-M4)"; mc->init = netduinoplus2_init; mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("netduinoplus2", netduinoplus2_machine_init) diff --git a/hw/arm/npcm7xx_boards.c b/hw/arm/npcm7xx_boards.c index 7727e0dc4bba..38b8f02364f1 100644 --- a/hw/arm/npcm7xx_boards.c +++ b/hw/arm/npcm7xx_boards.c @@ -481,6 +481,7 @@ static void npcm750_evb_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Nuvoton NPCM750 Evaluation Board (Cortex-A9)"; mc->init = npcm750_evb_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; }; @@ -493,6 +494,7 @@ static void gsj_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Quanta GSJ (Cortex-A9)"; mc->init = quanta_gsj_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 512 * MiB; }; @@ -505,6 +507,7 @@ static void gbs_bmc_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Quanta GBS (Cortex-A9)"; mc->init = quanta_gbs_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; } @@ -517,6 +520,7 @@ static void kudo_bmc_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Kudo BMC (Cortex-A9)"; mc->init = kudo_bmc_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; }; @@ -529,6 +533,7 @@ static void mori_bmc_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Mori BMC (Cortex-A9)"; mc->init = mori_bmc_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_size = 1 * GiB; } diff --git a/hw/arm/olimex-stm32-h405.c b/hw/arm/olimex-stm32-h405.c index 1f15620f9fda..01ae12fa4acf 100644 --- a/hw/arm/olimex-stm32-h405.c +++ b/hw/arm/olimex-stm32-h405.c @@ -66,6 +66,7 @@ static void olimex_stm32_h405_machine_init(MachineClass *mc) mc->desc = "Olimex STM32-H405 (Cortex-M4)"; mc->init = olimex_stm32_h405_init; mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; /* SRAM pre-allocated as part of the SoC instantiation */ mc->default_ram_size = 0; diff --git a/hw/arm/omap_sx1.c b/hw/arm/omap_sx1.c index 623ebd66395f..8170669db1fc 100644 --- a/hw/arm/omap_sx1.c +++ b/hw/arm/omap_sx1.c @@ -216,6 +216,7 @@ static void sx1_machine_v2_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("ti925t"); mc->default_ram_size = SDRAM_SIZE; mc->default_ram_id = "omap1.dram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo sx1_machine_v2_type = { @@ -234,6 +235,7 @@ static void sx1_machine_v1_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("ti925t"); mc->default_ram_size = SDRAM_SIZE; mc->default_ram_id = "omap1.dram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo sx1_machine_v1_type = { diff --git a/hw/arm/orangepi.c b/hw/arm/orangepi.c index 77e328191d73..76ab214853dc 100644 --- a/hw/arm/orangepi.c +++ b/hw/arm/orangepi.c @@ -121,6 +121,7 @@ static void orangepi_machine_init(MachineClass *mc) mc->valid_cpu_types = valid_cpu_types; mc->default_ram_size = 1 * GiB; mc->default_ram_id = "orangepi.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("orangepi-pc", orangepi_machine_init) diff --git a/hw/arm/raspi.c b/hw/arm/raspi.c index a7a662f40db4..176c324cf82e 100644 --- a/hw/arm/raspi.c +++ b/hw/arm/raspi.c @@ -342,6 +342,7 @@ static void raspi0_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); + mc->no_sdcard = ON_OFF_AUTO_OFF; rmc->board_rev = 0x920092; /* Revision 1.2 */ raspi_machine_class_init(mc, rmc->board_rev); }; @@ -351,6 +352,7 @@ static void raspi1ap_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); + mc->no_sdcard = ON_OFF_AUTO_OFF; rmc->board_rev = 0x900021; /* Revision 1.1 */ raspi_machine_class_init(mc, rmc->board_rev); }; @@ -360,6 +362,7 @@ static void raspi2b_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); + mc->no_sdcard = ON_OFF_AUTO_OFF; rmc->board_rev = 0xa21041; raspi_machine_class_init(mc, rmc->board_rev); }; @@ -370,6 +373,7 @@ static void raspi3ap_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); + mc->no_sdcard = ON_OFF_AUTO_OFF; rmc->board_rev = 0x9020e0; /* Revision 1.0 */ raspi_machine_class_init(mc, rmc->board_rev); }; @@ -379,6 +383,7 @@ static void raspi3b_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); + mc->no_sdcard = ON_OFF_AUTO_OFF; rmc->board_rev = 0xa02082; raspi_machine_class_init(mc, rmc->board_rev); }; diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c index 1264e0d6eedb..37eef378888f 100644 --- a/hw/arm/raspi4b.c +++ b/hw/arm/raspi4b.c @@ -118,6 +118,7 @@ static void raspi4b_machine_class_init(ObjectClass *oc, void *data) rmc->board_rev = 0xb03115; /* Revision 1.5, 2 Gb RAM */ #endif raspi_machine_class_common_init(mc, rmc->board_rev); + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->init = raspi4b_machine_init; } diff --git a/hw/arm/realview.c b/hw/arm/realview.c index 9900a98f3b8c..4bc8f3956f3d 100644 --- a/hw/arm/realview.c +++ b/hw/arm/realview.c @@ -415,6 +415,7 @@ static void realview_eb_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } @@ -435,6 +436,7 @@ static void realview_eb_mpcore_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm11mpcore"); + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } @@ -453,6 +455,7 @@ static void realview_pb_a8_class_init(ObjectClass *oc, void *data) mc->init = realview_pb_a8_init; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a8"); + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } @@ -472,6 +475,7 @@ static void realview_pbx_a9_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a9"); + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } diff --git a/hw/arm/sabrelite.c b/hw/arm/sabrelite.c index 1eb47042eca7..8d57653ab344 100644 --- a/hw/arm/sabrelite.c +++ b/hw/arm/sabrelite.c @@ -110,6 +110,7 @@ static void sabrelite_machine_init(MachineClass *mc) mc->max_cpus = FSL_IMX6_NUM_CPUS; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "sabrelite.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("sabrelite", sabrelite_machine_init) diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c index e720de306419..41dba85f44c2 100644 --- a/hw/arm/sbsa-ref.c +++ b/hw/arm/sbsa-ref.c @@ -900,6 +900,7 @@ static void sbsa_ref_class_init(ObjectClass *oc, void *data) mc->minimum_page_bits = 12; mc->block_default_type = IF_IDE; mc->no_cdrom = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_nic = "e1000e"; mc->default_ram_size = 1 * GiB; mc->default_ram_id = "sbsa-ref.ram"; diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c index 47c1cfa04855..ef3fd508ba10 100644 --- a/hw/arm/stellaris.c +++ b/hw/arm/stellaris.c @@ -1421,6 +1421,7 @@ static void lm3s811evb_class_init(ObjectClass *oc, void *data) mc->init = lm3s811evb_init; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo lm3s811evb_type = { @@ -1441,6 +1442,7 @@ static void lm3s6965evb_class_init(ObjectClass *oc, void *data) mc->init = lm3s6965evb_init; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo lm3s6965evb_type = { diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c index e6c1f5b8d7d9..01b4afcb1a06 100644 --- a/hw/arm/stm32vldiscovery.c +++ b/hw/arm/stm32vldiscovery.c @@ -66,6 +66,7 @@ static void stm32vldiscovery_machine_init(MachineClass *mc) mc->desc = "ST STM32VLDISCOVERY (Cortex-M3)"; mc->init = stm32vldiscovery_init; mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("stm32vldiscovery", stm32vldiscovery_machine_init) diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c index bc4522989ecd..def3da4a344b 100644 --- a/hw/arm/versatilepb.c +++ b/hw/arm/versatilepb.c @@ -419,6 +419,7 @@ static void versatilepb_class_init(ObjectClass *oc, void *data) mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_id = "versatile.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } @@ -439,6 +440,7 @@ static void versatileab_class_init(ObjectClass *oc, void *data) mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_id = "versatile.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index b886d16c0239..06ec78e6eb81 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -803,6 +803,7 @@ static void vexpress_a9_class_init(ObjectClass *oc, void *data) mc->desc = "ARM Versatile Express for Cortex-A9"; mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; vmc->daughterboard = &a9_daughterboard; } @@ -818,6 +819,7 @@ static void vexpress_a15_class_init(ObjectClass *oc, void *data) mc->desc = "ARM Versatile Express for Cortex-A15"; mc->valid_cpu_types = valid_cpu_types; + mc->no_sdcard = ON_OFF_AUTO_OFF; vmc->daughterboard = &a15_daughterboard; diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 4a5a9666e916..e2ac6ce4649c 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -3125,6 +3125,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) #endif mc->block_default_type = IF_VIRTIO; mc->no_cdrom = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->pci_allow_0_address = true; /* We know we will never create a pre-ARMv7 CPU which needs 1K pages */ mc->minimum_page_bits = 12; diff --git a/hw/arm/xen-pvh.c b/hw/arm/xen-pvh.c index d1509bd235df..e49ab0e7f57d 100644 --- a/hw/arm/xen-pvh.c +++ b/hw/arm/xen-pvh.c @@ -75,6 +75,7 @@ static void xen_arm_machine_class_init(ObjectClass *oc, void *data) * mc->max_cpus, QEMU will bail out with an error message. */ mc->max_cpus = GUEST_MAX_VCPUS; + mc->no_sdcard = ON_OFF_AUTO_OFF; /* Xen/ARM does not use buffered IOREQs. */ xpc->handle_bufioreq = HVM_IOREQSRV_BUFIOREQ_OFF; diff --git a/hw/arm/xlnx-versal-virt.c b/hw/arm/xlnx-versal-virt.c index 1401d37959e4..8a1cdb037ca0 100644 --- a/hw/arm/xlnx-versal-virt.c +++ b/hw/arm/xlnx-versal-virt.c @@ -818,6 +818,7 @@ static void versal_virt_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = XLNX_VERSAL_NR_ACPUS + XLNX_VERSAL_NR_RCPUS; mc->default_cpus = XLNX_VERSAL_NR_ACPUS + XLNX_VERSAL_NR_RCPUS; mc->no_cdrom = true; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_id = "ddr"; object_class_property_add_str(oc, "ospi-flash", versal_get_ospi_model, versal_set_ospi_model); diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c index 70fb444bbd9d..311d8f1cad45 100644 --- a/hw/arm/xlnx-zcu102.c +++ b/hw/arm/xlnx-zcu102.c @@ -280,6 +280,7 @@ static void xlnx_zcu102_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = XLNX_ZYNQMP_NUM_APU_CPUS + XLNX_ZYNQMP_NUM_RPU_CPUS; mc->default_cpus = XLNX_ZYNQMP_NUM_APU_CPUS; mc->default_ram_id = "ddr-ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); object_class_property_add_bool(oc, "secure", zcu102_get_secure, diff --git a/hw/avr/arduino.c b/hw/avr/arduino.c index 48ef478346e9..1801074a4219 100644 --- a/hw/avr/arduino.c +++ b/hw/avr/arduino.c @@ -67,6 +67,7 @@ static void arduino_machine_class_init(ObjectClass *oc, void *data) mc->no_floppy = 1; mc->no_cdrom = 1; mc->no_parallel = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static void arduino_duemilanove_class_init(ObjectClass *oc, void *data) diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index c5f247633eb4..363ca89ca1c4 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -704,6 +704,7 @@ static void HP_B160L_machine_init_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_ram_id = "ram"; mc->default_nic = "tulip"; + mc->no_sdcard = ON_OFF_AUTO_OFF; nc->nmi_monitor_handler = hppa_nmi; } @@ -740,6 +741,7 @@ static void HP_C3700_machine_init_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_ram_id = "ram"; mc->default_nic = "tulip"; + mc->no_sdcard = ON_OFF_AUTO_OFF; nc->nmi_monitor_handler = hppa_nmi; } diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 22641e6ddca5..9d8b7389e4b3 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1799,6 +1799,7 @@ static void pc_machine_class_init(ObjectClass *oc, void *data) mc->smp_props.dies_supported = true; mc->smp_props.modules_supported = true; mc->default_ram_id = "pc.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; pcmc->default_smbios_ep_type = SMBIOS_ENTRY_POINT_TYPE_AUTO; object_class_property_add(oc, PC_MACHINE_MAX_RAM_BELOW_4G, "size", diff --git a/hw/i386/x86.c b/hw/i386/x86.c index 69bfc00b9a5a..e3b92fcb7445 100644 --- a/hw/i386/x86.c +++ b/hw/i386/x86.c @@ -382,6 +382,7 @@ static void x86_machine_class_init(ObjectClass *oc, void *data) mc->get_default_cpu_node_id = x86_get_default_cpu_node_id; mc->possible_cpu_arch_ids = x86_possible_cpu_arch_ids; mc->kvm_type = x86_kvm_type; + mc->no_sdcard = ON_OFF_AUTO_OFF; x86mc->save_tsc_khz = true; x86mc->fwcfg_dma_enabled = true; nc->nmi_monitor_handler = x86_nmi; diff --git a/hw/i386/xen/xen-pvh.c b/hw/i386/xen/xen-pvh.c index 33c102797639..33e5882c2d33 100644 --- a/hw/i386/xen/xen-pvh.c +++ b/hw/i386/xen/xen-pvh.c @@ -82,6 +82,7 @@ static void xen_pvh_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Xen PVH x86 machine"; mc->default_cpu_type = TARGET_DEFAULT_CPU_TYPE; + mc->no_sdcard = ON_OFF_AUTO_OFF; /* mc->max_cpus holds the MAX value allowed in the -smp cmd-line opts. */ mc->max_cpus = HVM_MAX_VCPUS; diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index f2aa0a9782e7..7732547a6f82 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -954,6 +954,7 @@ static void virt_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_VIRTIO; mc->default_boot_order = "c"; mc->no_cdrom = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->possible_cpu_arch_ids = virt_possible_cpu_arch_ids; mc->cpu_index_to_instance_props = virt_cpu_index_to_props; mc->get_default_cpu_node_id = virt_get_default_cpu_node_id; diff --git a/hw/m68k/an5206.c b/hw/m68k/an5206.c index d97399b882b9..286c3bac2ad1 100644 --- a/hw/m68k/an5206.c +++ b/hw/m68k/an5206.c @@ -99,6 +99,7 @@ static void an5206_machine_init(MachineClass *mc) mc->init = an5206_init; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5206"); mc->default_ram_id = "an5206.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("an5206", an5206_machine_init) diff --git a/hw/m68k/mcf5208.c b/hw/m68k/mcf5208.c index 75cc076f787a..a0c90d111e6b 100644 --- a/hw/m68k/mcf5208.c +++ b/hw/m68k/mcf5208.c @@ -398,6 +398,7 @@ static void mcf5208evb_machine_init(MachineClass *mc) mc->is_default = true; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5208"); mc->default_ram_id = "mcf5208.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("mcf5208evb", mcf5208evb_machine_init) diff --git a/hw/m68k/next-cube.c b/hw/m68k/next-cube.c index 0570e4a76f1b..06a4d825e714 100644 --- a/hw/m68k/next-cube.c +++ b/hw/m68k/next-cube.c @@ -1359,6 +1359,7 @@ static void next_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_id = "next.ram"; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040"); mc->no_cdrom = true; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo next_typeinfo = { diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c index aeed4c8ddb8a..21fa56e7a9c4 100644 --- a/hw/m68k/q800.c +++ b/hw/m68k/q800.c @@ -743,6 +743,7 @@ static void q800_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = 1; mc->block_default_type = IF_SCSI; mc->default_ram_id = "m68k_mac.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); compat_props_add(mc->compat_props, hw_compat_q800, hw_compat_q800_len); diff --git a/hw/m68k/virt.c b/hw/m68k/virt.c index d967bdd7438e..a1bd9c432d23 100644 --- a/hw/m68k/virt.c +++ b/hw/m68k/virt.c @@ -318,6 +318,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = 1; mc->no_floppy = 1; mc->no_parallel = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_ram_id = "m68k_virt.ram"; } diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c index 8b44be75a220..8d0e6c948f4e 100644 --- a/hw/microblaze/petalogix_ml605_mmu.c +++ b/hw/microblaze/petalogix_ml605_mmu.c @@ -220,6 +220,7 @@ static void petalogix_ml605_machine_init(MachineClass *mc) mc->desc = "PetaLogix linux refdesign for xilinx ml605 (little endian)"; #endif mc->init = petalogix_ml605_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("petalogix-ml605", petalogix_ml605_machine_init) diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c index 2c0d8c34cd23..5b06e7d82cd5 100644 --- a/hw/microblaze/petalogix_s3adsp1800_mmu.c +++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c @@ -142,6 +142,7 @@ static void petalogix_s3adsp1800_machine_class_init(ObjectClass *oc, void *data) mc->desc = "PetaLogix linux refdesign for xilinx Spartan 3ADSP1800"; mc->init = petalogix_s3adsp1800_init; mc->is_default = true; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo petalogix_s3adsp1800_machine_types[] = { diff --git a/hw/microblaze/xlnx-zynqmp-pmu.c b/hw/microblaze/xlnx-zynqmp-pmu.c index bdbf7328bf44..14386785f85e 100644 --- a/hw/microblaze/xlnx-zynqmp-pmu.c +++ b/hw/microblaze/xlnx-zynqmp-pmu.c @@ -188,6 +188,7 @@ static void xlnx_zynqmp_pmu_machine_init(MachineClass *mc) mc->desc = "Xilinx ZynqMP PMU machine (little endian)"; #endif mc->init = xlnx_zynqmp_pmu_init; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("xlnx-zynqmp-pmu", xlnx_zynqmp_pmu_machine_init) diff --git a/hw/mips/boston.c b/hw/mips/boston.c index 364c328032a1..bd9059a2070d 100644 --- a/hw/mips/boston.c +++ b/hw/mips/boston.c @@ -842,6 +842,7 @@ static void boston_mach_class_init(MachineClass *mc) mc->default_ram_id = "boston.ddr"; mc->max_cpus = 16; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("I6400"); + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("boston", boston_mach_class_init) diff --git a/hw/mips/fuloong2e.c b/hw/mips/fuloong2e.c index 646044e27497..83e95c3b20ec 100644 --- a/hw/mips/fuloong2e.c +++ b/hw/mips/fuloong2e.c @@ -335,6 +335,7 @@ static void mips_fuloong2e_machine_init(MachineClass *mc) mc->default_ram_size = 256 * MiB; mc->default_ram_id = "fuloong2e.ram"; mc->minimum_page_bits = 14; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } diff --git a/hw/mips/jazz.c b/hw/mips/jazz.c index c89610639a9e..326f60c448bb 100644 --- a/hw/mips/jazz.c +++ b/hw/mips/jazz.c @@ -424,6 +424,7 @@ static void mips_magnum_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("R4000"); mc->default_ram_id = "mips_jazz.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo mips_magnum_type = { @@ -441,6 +442,7 @@ static void mips_pica61_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("R4000"); mc->default_ram_id = "mips_jazz.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo mips_pica61_type = { diff --git a/hw/mips/loongson3_virt.c b/hw/mips/loongson3_virt.c index 831fddb1bd74..f44932b331ef 100644 --- a/hw/mips/loongson3_virt.c +++ b/hw/mips/loongson3_virt.c @@ -679,6 +679,7 @@ static void loongson3v_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 1600 * MiB; mc->minimum_page_bits = 14; mc->default_nic = "virtio-net-pci"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo loongson3_machine_types[] = { diff --git a/hw/mips/malta.c b/hw/mips/malta.c index 8e9cea70b135..13811f89f916 100644 --- a/hw/mips/malta.c +++ b/hw/mips/malta.c @@ -1306,6 +1306,7 @@ static void mips_malta_machine_init(MachineClass *mc) mc->default_cpu_type = MIPS_CPU_TYPE_NAME("24Kf"); #endif mc->default_ram_id = "mips_malta.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; compat_props_add(mc->compat_props, malta_compat, malta_compat_len); } diff --git a/hw/mips/mipssim.c b/hw/mips/mipssim.c index c530688e7699..f0d06ab549c4 100644 --- a/hw/mips/mipssim.c +++ b/hw/mips/mipssim.c @@ -247,6 +247,7 @@ static void mips_mipssim_machine_init(MachineClass *mc) mc->default_cpu_type = MIPS_CPU_TYPE_NAME("24Kf"); #endif mc->default_ram_id = "mips_mipssim.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("mipssim", mips_mipssim_machine_init) diff --git a/hw/openrisc/openrisc_sim.c b/hw/openrisc/openrisc_sim.c index e0da4067ba39..b9d71ea0c3b8 100644 --- a/hw/openrisc/openrisc_sim.c +++ b/hw/openrisc/openrisc_sim.c @@ -368,6 +368,7 @@ static void openrisc_sim_machine_init(ObjectClass *oc, void *data) mc->max_cpus = OR1KSIM_CPUS_MAX; mc->is_default = true; mc->default_cpu_type = OPENRISC_CPU_TYPE_NAME("or1200"); + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo or1ksim_machine_typeinfo = { diff --git a/hw/openrisc/virt.c b/hw/openrisc/virt.c index 7b60bf85094c..d250e2ab21f9 100644 --- a/hw/openrisc/virt.c +++ b/hw/openrisc/virt.c @@ -554,6 +554,7 @@ static void openrisc_virt_machine_init(ObjectClass *oc, void *data) mc->max_cpus = VIRT_CPUS_MAX; mc->is_default = false; mc->default_cpu_type = OPENRISC_CPU_TYPE_NAME("or1200"); + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo or1ksim_machine_typeinfo = { diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index b02792221cc5..39449e3632dc 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -173,6 +173,7 @@ static void amigaone_machine_init(MachineClass *mc) mc->default_display = "std"; mc->default_ram_id = "ram"; mc->default_ram_size = 512 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("amigaone", amigaone_machine_init) diff --git a/hw/ppc/e500plat.c b/hw/ppc/e500plat.c index 70a803337332..0b91d422aa1a 100644 --- a/hw/ppc/e500plat.c +++ b/hw/ppc/e500plat.c @@ -100,6 +100,7 @@ static void e500plat_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("e500v2_v30"); mc->default_ram_id = "mpc8544ds.ram"; mc->default_nic = "virtio-net-pci"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_class_allow_dynamic_sysbus_dev(mc, TYPE_ETSEC_COMMON); } diff --git a/hw/ppc/mac_newworld.c b/hw/ppc/mac_newworld.c index cb3dc3ab482d..da53eb11ed9e 100644 --- a/hw/ppc/mac_newworld.c +++ b/hw/ppc/mac_newworld.c @@ -580,6 +580,7 @@ static void core99_machine_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_display = "std"; mc->default_nic = "sungem"; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->kvm_type = core99_kvm_type; #ifdef TARGET_PPC64 mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("970fx_v3.1"); diff --git a/hw/ppc/mac_oldworld.c b/hw/ppc/mac_oldworld.c index 0dbcea035c3f..b23bfff696e5 100644 --- a/hw/ppc/mac_oldworld.c +++ b/hw/ppc/mac_oldworld.c @@ -427,6 +427,7 @@ static void heathrow_class_init(ObjectClass *oc, void *data) mc->default_nic = "ne2k_pci"; mc->ignore_boot_device_suffixes = true; mc->default_ram_id = "ppc_heathrow.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; fwc->get_dev_path = heathrow_fw_dev_path; } diff --git a/hw/ppc/mpc8544ds.c b/hw/ppc/mpc8544ds.c index d74af766eeda..0fa3a1b77674 100644 --- a/hw/ppc/mpc8544ds.c +++ b/hw/ppc/mpc8544ds.c @@ -62,6 +62,7 @@ static void mpc8544ds_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("e500v2_v30"); mc->default_ram_id = "mpc8544ds.ram"; mc->default_nic = "virtio-net-pci"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } #define TYPE_MPC8544DS_MACHINE MACHINE_TYPE_NAME("mpc8544ds") diff --git a/hw/ppc/pegasos2.c b/hw/ppc/pegasos2.c index b057672e829d..03ddbb96b66c 100644 --- a/hw/ppc/pegasos2.c +++ b/hw/ppc/pegasos2.c @@ -604,6 +604,7 @@ static void pegasos2_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("7457_v1.2"); mc->default_ram_id = "pegasos2.ram"; mc->default_ram_size = 512 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); vhc->cpu_in_nested = pegasos2_cpu_in_nested; diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 11fd477b71be..70203449fda9 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -2878,6 +2878,7 @@ static void pnv_machine_class_init(ObjectClass *oc, void *data) /* Pnv provides a AHCI device for storage */ mc->block_default_type = IF_IDE; mc->no_parallel = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_boot_order = NULL; /* * RAM defaults to less than 2048 for 32-bit hosts, and large diff --git a/hw/ppc/ppc405_boards.c b/hw/ppc/ppc405_boards.c index 969cac345ac8..c602d6081765 100644 --- a/hw/ppc/ppc405_boards.c +++ b/hw/ppc/ppc405_boards.c @@ -351,6 +351,7 @@ static void ppc405_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 128 * MiB; mc->default_ram_id = "ppc405.ram"; mc->deprecation_reason = "machine is old and unmaintained"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo ppc405_machine_type = { diff --git a/hw/ppc/ppc440_bamboo.c b/hw/ppc/ppc440_bamboo.c index 099fda390928..5b2d52032f78 100644 --- a/hw/ppc/ppc440_bamboo.c +++ b/hw/ppc/ppc440_bamboo.c @@ -268,6 +268,7 @@ static void bamboo_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("440epb"); mc->default_ram_id = "ppc4xx.sdram"; mc->default_nic = "e1000"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("bamboo", bamboo_machine_init) diff --git a/hw/ppc/prep.c b/hw/ppc/prep.c index 3e68d8e6e209..998e8ecd426d 100644 --- a/hw/ppc/prep.c +++ b/hw/ppc/prep.c @@ -428,6 +428,7 @@ static void ibm_40p_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("604"); mc->default_display = "std"; mc->default_nic = "pcnet"; + mc->no_sdcard = ON_OFF_AUTO_OFF; machine_add_audiodev_property(mc); } diff --git a/hw/ppc/sam460ex.c b/hw/ppc/sam460ex.c index 3ecae6a95048..a1b1fc8724af 100644 --- a/hw/ppc/sam460ex.c +++ b/hw/ppc/sam460ex.c @@ -524,6 +524,7 @@ static void sam460ex_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("460exb"); mc->default_ram_size = 512 * MiB; mc->default_ram_id = "ppc4xx.sdram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("sam460ex", sam460ex_machine_init) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index f3a4b4235d43..fa030d52767d 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -4594,6 +4594,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = SPAPR_IRQ_NR_IPIS; mc->no_parallel = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->default_boot_order = ""; mc->default_ram_size = 512 * MiB; mc->default_ram_id = "ppc_spapr.ram"; diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c index 232381192731..23d115d99765 100644 --- a/hw/ppc/virtex_ml507.c +++ b/hw/ppc/virtex_ml507.c @@ -288,6 +288,7 @@ static void virtex_machine_init(MachineClass *mc) mc->init = virtex_init; mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("440-xilinx"); mc->default_ram_id = "ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("virtex-ml507", virtex_machine_init) diff --git a/hw/remote/machine.c b/hw/remote/machine.c index fdc6c441bbd1..1f7b0b96dd46 100644 --- a/hw/remote/machine.c +++ b/hw/remote/machine.c @@ -128,6 +128,7 @@ static void remote_machine_class_init(ObjectClass *oc, void *data) mc->init = remote_machine_init; mc->desc = "Experimental remote machine"; + mc->no_sdcard = ON_OFF_AUTO_OFF; hc->unplug = remote_machine_dev_unplug_cb; diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c index ec7e2e42264e..2417342a717d 100644 --- a/hw/riscv/microchip_pfsoc.c +++ b/hw/riscv/microchip_pfsoc.c @@ -650,6 +650,7 @@ static void microchip_icicle_kit_machine_class_init(ObjectClass *oc, void *data) mc->min_cpus = MICROCHIP_PFSOC_MANAGEMENT_CPU_COUNT + 1; mc->default_cpus = mc->min_cpus; mc->default_ram_id = "microchip.icicle.kit.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; /* * Map 513 MiB high memory, the minimum required high memory size, because diff --git a/hw/riscv/opentitan.c b/hw/riscv/opentitan.c index b9e56235d87b..3b26e1f53bcd 100644 --- a/hw/riscv/opentitan.c +++ b/hw/riscv/opentitan.c @@ -121,6 +121,7 @@ static void opentitan_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_IBEX; mc->default_ram_id = "riscv.lowrisc.ibex.ram"; mc->default_ram_size = ibex_memmap[IBEX_DEV_RAM].size; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static void lowrisc_ibex_soc_init(Object *obj) diff --git a/hw/riscv/shakti_c.c b/hw/riscv/shakti_c.c index e2242b97d0c0..2f59c86bf0c5 100644 --- a/hw/riscv/shakti_c.c +++ b/hw/riscv/shakti_c.c @@ -84,6 +84,7 @@ static void shakti_c_machine_class_init(ObjectClass *klass, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_SHAKTI_C; mc->valid_cpu_types = valid_cpu_types; mc->default_ram_id = "riscv.shakti.c.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo shakti_c_machine_type_info = { diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c index 73d3b74281c3..56a2ca7cabba 100644 --- a/hw/riscv/sifive_e.c +++ b/hw/riscv/sifive_e.c @@ -153,6 +153,7 @@ static void sifive_e_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = SIFIVE_E_CPU; mc->default_ram_id = "riscv.sifive.e.ram"; mc->default_ram_size = sifive_e_memmap[SIFIVE_E_DEV_DTIM].size; + mc->no_sdcard = ON_OFF_AUTO_OFF; object_class_property_add_bool(oc, "revb", sifive_e_machine_get_revb, sifive_e_machine_set_revb); diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c index 9a20bcbf7fbd..fd57d02dca82 100644 --- a/hw/riscv/sifive_u.c +++ b/hw/riscv/sifive_u.c @@ -724,6 +724,7 @@ static void sifive_u_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = SIFIVE_U_CPU; mc->default_cpus = mc->min_cpus; mc->default_ram_id = "riscv.sifive.u.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; object_class_property_add_bool(oc, "start-in-flash", sifive_u_machine_get_start_in_flash, diff --git a/hw/riscv/spike.c b/hw/riscv/spike.c index 74a20016f149..a0fa727f6bfe 100644 --- a/hw/riscv/spike.c +++ b/hw/riscv/spike.c @@ -358,6 +358,7 @@ static void spike_machine_class_init(ObjectClass *oc, void *data) /* platform instead of architectural choice */ mc->cpu_cluster_has_numa_boundary = true; mc->default_ram_id = "riscv.spike.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; object_class_property_add_str(oc, "signature", NULL, spike_set_signature); object_class_property_set_description(oc, "signature", "File to write ACT test signature"); diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 241389d72f8a..d9fc7cdf9ac8 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -1918,6 +1918,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_BASE; mc->block_default_type = IF_VIRTIO; mc->no_cdrom = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; mc->pci_allow_0_address = true; mc->possible_cpu_arch_ids = riscv_numa_possible_cpu_arch_ids; mc->cpu_index_to_instance_props = riscv_numa_cpu_index_to_props; diff --git a/hw/rx/rx-gdbsim.c b/hw/rx/rx-gdbsim.c index 88c8f12c1019..9c3ae60bf806 100644 --- a/hw/rx/rx-gdbsim.c +++ b/hw/rx/rx-gdbsim.c @@ -166,6 +166,7 @@ static void rx_gdbsim_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RX62N_CPU; mc->default_ram_size = 16 * MiB; mc->default_ram_id = "ext-sdram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static void rx62n7_class_init(ObjectClass *oc, void *data) diff --git a/hw/sh4/r2d.c b/hw/sh4/r2d.c index d68c94e82ef8..6ef552ae5368 100644 --- a/hw/sh4/r2d.c +++ b/hw/sh4/r2d.c @@ -383,6 +383,7 @@ static void r2d_machine_init(MachineClass *mc) mc->block_default_type = IF_IDE; mc->default_cpu_type = TYPE_SH7751R_CPU; mc->default_nic = "rtl8139"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("r2d", r2d_machine_init) diff --git a/hw/sparc/leon3.c b/hw/sparc/leon3.c index 0aeaad3becc9..e99d6d71e8f7 100644 --- a/hw/sparc/leon3.c +++ b/hw/sparc/leon3.c @@ -440,6 +440,7 @@ static void leon3_generic_machine_init(MachineClass *mc) mc->default_cpu_type = SPARC_CPU_TYPE_NAME("LEON3"); mc->default_ram_id = "leon3.ram"; mc->max_cpus = MAX_CPUS; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("leon3_generic", leon3_generic_machine_init) diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c index a48d3622c5a8..19a2a9f2ff6e 100644 --- a/hw/sparc/sun4m.c +++ b/hw/sparc/sun4m.c @@ -1113,6 +1113,7 @@ static void sun4m_machine_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "c"; mc->default_display = "tcx"; mc->default_ram_id = "sun4m.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static void ss5_class_init(ObjectClass *oc, void *data) diff --git a/hw/sparc64/niagara.c b/hw/sparc64/niagara.c index 805ba6b1e3dd..409c67b1b4e0 100644 --- a/hw/sparc64/niagara.c +++ b/hw/sparc64/niagara.c @@ -167,6 +167,7 @@ static void niagara_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "c"; mc->default_cpu_type = SPARC_CPU_TYPE_NAME("Sun-UltraSparc-T1"); mc->default_ram_id = "sun4v-partition.ram"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo niagara_type = { diff --git a/hw/sparc64/sun4u.c b/hw/sparc64/sun4u.c index 8ab5cf0461f8..eaf3d42bd0ab 100644 --- a/hw/sparc64/sun4u.c +++ b/hw/sparc64/sun4u.c @@ -809,6 +809,7 @@ static void sun4u_class_init(ObjectClass *oc, void *data) mc->default_display = "std"; mc->default_nic = "sunhme"; mc->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); + mc->no_sdcard = ON_OFF_AUTO_OFF; fwc->get_dev_path = sun4u_fw_dev_path; compat_props_add(mc->compat_props, hw_compat_sparc64, hw_compat_sparc64_len); } @@ -836,6 +837,7 @@ static void sun4v_class_init(ObjectClass *oc, void *data) mc->default_display = "std"; mc->default_nic = "sunhme"; mc->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo sun4v_type = { diff --git a/hw/tricore/triboard.c b/hw/tricore/triboard.c index f5baa8ccbb3e..3cd93daf6a6c 100644 --- a/hw/tricore/triboard.c +++ b/hw/tricore/triboard.c @@ -73,6 +73,7 @@ static void triboard_machine_tc277d_class_init(ObjectClass *oc, mc->init = triboard_machine_init; mc->desc = "Infineon AURIX TriBoard TC277 (D-Step)"; mc->max_cpus = 1; + mc->no_sdcard = ON_OFF_AUTO_OFF; amc->soc_name = "tc277d-soc"; }; diff --git a/hw/tricore/tricore_testboard.c b/hw/tricore/tricore_testboard.c index 3facfdfd611a..29718051702f 100644 --- a/hw/tricore/tricore_testboard.c +++ b/hw/tricore/tricore_testboard.c @@ -111,6 +111,7 @@ static void ttb_machine_init(MachineClass *mc) mc->desc = "a minimal TriCore board"; mc->init = tricoreboard_init; mc->default_cpu_type = TRICORE_CPU_TYPE_NAME("tc1796"); + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("tricore_testboard", ttb_machine_init) diff --git a/hw/xen/xen-pvh-common.c b/hw/xen/xen-pvh-common.c index 9c21fa858d34..ed42e4b624c8 100644 --- a/hw/xen/xen-pvh-common.c +++ b/hw/xen/xen-pvh-common.c @@ -381,6 +381,7 @@ static void xen_pvh_class_init(ObjectClass *oc, void *data) mc->default_machine_opts = "accel=xen"; /* Set to zero to make sure that the real ram size is passed. */ mc->default_ram_size = 0; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xen_pvh_info = { diff --git a/hw/xenpv/xen_machine_pv.c b/hw/xenpv/xen_machine_pv.c index 99c02492ef91..a05713e5daa6 100644 --- a/hw/xenpv/xen_machine_pv.c +++ b/hw/xenpv/xen_machine_pv.c @@ -67,6 +67,7 @@ static void xenpv_machine_init(MachineClass *mc) mc->init = xen_init_pv; mc->max_cpus = 1; mc->default_machine_opts = "accel=xen"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("xenpv", xenpv_machine_init) diff --git a/hw/xtensa/sim.c b/hw/xtensa/sim.c index 1cea29c66d43..6c86d4939f42 100644 --- a/hw/xtensa/sim.c +++ b/hw/xtensa/sim.c @@ -125,6 +125,7 @@ static void xtensa_sim_machine_init(MachineClass *mc) mc->max_cpus = 4; mc->no_serial = 1; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("sim", xtensa_sim_machine_init) diff --git a/hw/xtensa/virt.c b/hw/xtensa/virt.c index b08404fc17c6..ffa6f210637a 100644 --- a/hw/xtensa/virt.c +++ b/hw/xtensa/virt.c @@ -122,6 +122,7 @@ static void xtensa_virt_machine_init(MachineClass *mc) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_nic = "virtio-net-pci"; + mc->no_sdcard = ON_OFF_AUTO_OFF; } DEFINE_MACHINE("virt", xtensa_virt_machine_init) diff --git a/hw/xtensa/xtfpga.c b/hw/xtensa/xtfpga.c index 3f3677f1c9a4..2b5f20acefc9 100644 --- a/hw/xtensa/xtfpga.c +++ b/hw/xtensa/xtfpga.c @@ -594,6 +594,7 @@ static void xtfpga_lx60_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 64 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_lx60_type = { @@ -611,6 +612,7 @@ static void xtfpga_lx60_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 64 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_lx60_nommu_type = { @@ -628,6 +630,7 @@ static void xtfpga_lx200_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 96 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_lx200_type = { @@ -645,6 +648,7 @@ static void xtfpga_lx200_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 96 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_lx200_nommu_type = { @@ -662,6 +666,7 @@ static void xtfpga_ml605_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 512 * MiB - XTFPGA_MMU_RESERVED_MEMORY_SIZE; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_ml605_type = { @@ -679,6 +684,7 @@ static void xtfpga_ml605_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 256 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_ml605_nommu_type = { @@ -696,6 +702,7 @@ static void xtfpga_kc705_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 1 * GiB - XTFPGA_MMU_RESERVED_MEMORY_SIZE; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_kc705_type = { @@ -713,6 +720,7 @@ static void xtfpga_kc705_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 256 * MiB; + mc->no_sdcard = ON_OFF_AUTO_OFF; } static const TypeInfo xtfpga_kc705_nommu_type = { diff --git a/system/vl.c b/system/vl.c index 04ce290997c2..3904d0d1581c 100644 --- a/system/vl.c +++ b/system/vl.c @@ -1346,6 +1346,7 @@ static void qemu_disable_default_devices(void) if (!has_defaults || machine_class->no_cdrom) { default_cdrom = 0; } + assert(machine_class->no_sdcard != ON_OFF_AUTO_AUTO); if (!has_defaults || machine_class->no_sdcard == ON_OFF_AUTO_ON) { default_sdcard = 0; } From cdc8d7cadaac33ca103791a6ebb535a3ad9fa05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 25 Nov 2024 18:26:48 +0100 Subject: [PATCH 0085/1179] hw/boards: Rename no_sdcard -> auto_create_sdcard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Invert the 'no_sdcard' logic, renaming it as the more explicit "auto_create_sdcard". Machines are supposed to create a SD Card drive when this flag is set. In many cases it doesn't make much sense (as boards don't expose SD Card host controller), but this is patch only aims to expose that nonsense; so no logical change intended (mechanical patch using gsed). Most of the changes are: - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; Except in . hw/core/null-machine.c . hw/arm/xilinx_zynq.c . hw/s390x/s390-virtio-ccw.c where the disabled option is manually removed (since default): - mc->no_sdcard = ON_OFF_AUTO_ON; + mc->auto_create_sdcard = false; - mc->auto_create_sdcard = false; and in system/vl.c we change the 'default_sdcard' type to boolean. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250204200934.65279-4-philmd@linaro.org> --- hw/alpha/dp264.c | 2 +- hw/arm/aspeed.c | 42 ++++++++++++------------ hw/arm/b-l475e-iot01a.c | 2 +- hw/arm/bananapi_m2u.c | 2 +- hw/arm/collie.c | 2 +- hw/arm/cubieboard.c | 2 +- hw/arm/digic_boards.c | 2 +- hw/arm/exynos4_boards.c | 4 +-- hw/arm/fby35.c | 2 +- hw/arm/highbank.c | 4 +-- hw/arm/imx25_pdk.c | 2 +- hw/arm/integratorcp.c | 2 +- hw/arm/kzm.c | 2 +- hw/arm/mcimx6ul-evk.c | 2 +- hw/arm/mcimx7d-sabre.c | 2 +- hw/arm/microbit.c | 2 +- hw/arm/mps2-tz.c | 8 ++--- hw/arm/mps2.c | 8 ++--- hw/arm/mps3r.c | 2 +- hw/arm/msf2-som.c | 2 +- hw/arm/musca.c | 4 +-- hw/arm/musicpal.c | 2 +- hw/arm/netduino2.c | 2 +- hw/arm/netduinoplus2.c | 2 +- hw/arm/npcm7xx_boards.c | 10 +++--- hw/arm/olimex-stm32-h405.c | 2 +- hw/arm/omap_sx1.c | 4 +-- hw/arm/orangepi.c | 2 +- hw/arm/raspi.c | 10 +++--- hw/arm/raspi4b.c | 2 +- hw/arm/realview.c | 8 ++--- hw/arm/sabrelite.c | 2 +- hw/arm/sbsa-ref.c | 2 +- hw/arm/stellaris.c | 4 +-- hw/arm/stm32vldiscovery.c | 2 +- hw/arm/versatilepb.c | 4 +-- hw/arm/vexpress.c | 4 +-- hw/arm/virt.c | 2 +- hw/arm/xen-pvh.c | 2 +- hw/arm/xilinx_zynq.c | 1 - hw/arm/xlnx-versal-virt.c | 2 +- hw/arm/xlnx-zcu102.c | 2 +- hw/avr/arduino.c | 2 +- hw/core/null-machine.c | 1 - hw/hppa/machine.c | 4 +-- hw/i386/pc.c | 2 +- hw/i386/x86.c | 2 +- hw/i386/xen/xen-pvh.c | 2 +- hw/loongarch/virt.c | 2 +- hw/m68k/an5206.c | 2 +- hw/m68k/mcf5208.c | 2 +- hw/m68k/next-cube.c | 2 +- hw/m68k/q800.c | 2 +- hw/m68k/virt.c | 2 +- hw/microblaze/petalogix_ml605_mmu.c | 2 +- hw/microblaze/petalogix_s3adsp1800_mmu.c | 2 +- hw/microblaze/xlnx-zynqmp-pmu.c | 2 +- hw/mips/boston.c | 2 +- hw/mips/fuloong2e.c | 2 +- hw/mips/jazz.c | 4 +-- hw/mips/loongson3_virt.c | 2 +- hw/mips/malta.c | 2 +- hw/mips/mipssim.c | 2 +- hw/openrisc/openrisc_sim.c | 2 +- hw/openrisc/virt.c | 2 +- hw/ppc/amigaone.c | 2 +- hw/ppc/e500plat.c | 2 +- hw/ppc/mac_newworld.c | 2 +- hw/ppc/mac_oldworld.c | 2 +- hw/ppc/mpc8544ds.c | 2 +- hw/ppc/pegasos2.c | 2 +- hw/ppc/pnv.c | 2 +- hw/ppc/ppc405_boards.c | 2 +- hw/ppc/ppc440_bamboo.c | 2 +- hw/ppc/prep.c | 2 +- hw/ppc/sam460ex.c | 2 +- hw/ppc/spapr.c | 2 +- hw/ppc/virtex_ml507.c | 2 +- hw/remote/machine.c | 2 +- hw/riscv/microchip_pfsoc.c | 2 +- hw/riscv/opentitan.c | 2 +- hw/riscv/shakti_c.c | 2 +- hw/riscv/sifive_e.c | 2 +- hw/riscv/sifive_u.c | 2 +- hw/riscv/spike.c | 2 +- hw/riscv/virt.c | 2 +- hw/rx/rx-gdbsim.c | 2 +- hw/s390x/s390-virtio-ccw.c | 1 - hw/sh4/r2d.c | 2 +- hw/sparc/leon3.c | 2 +- hw/sparc/sun4m.c | 2 +- hw/sparc64/niagara.c | 2 +- hw/sparc64/sun4u.c | 4 +-- hw/tricore/triboard.c | 2 +- hw/tricore/tricore_testboard.c | 2 +- hw/xen/xen-pvh-common.c | 2 +- hw/xenpv/xen_machine_pv.c | 2 +- hw/xtensa/sim.c | 2 +- hw/xtensa/virt.c | 2 +- hw/xtensa/xtfpga.c | 16 ++++----- include/hw/boards.h | 2 +- system/vl.c | 9 +++-- 102 files changed, 156 insertions(+), 160 deletions(-) diff --git a/hw/alpha/dp264.c b/hw/alpha/dp264.c index b11e527be936..14b942fd5a7e 100644 --- a/hw/alpha/dp264.c +++ b/hw/alpha/dp264.c @@ -213,7 +213,7 @@ static void clipper_machine_init(MachineClass *mc) mc->default_cpu_type = ALPHA_CPU_TYPE_NAME("ev67"); mc->default_ram_id = "ram"; mc->default_nic = "e1000"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("clipper", clipper_machine_init) diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index 9d9c55adcdcb..f3ba90896c01 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -1253,7 +1253,7 @@ static void aspeed_machine_palmetto_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635f"; amc->num_cs = 1; amc->i2c_init = palmetto_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 256 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1270,7 +1270,7 @@ static void aspeed_machine_quanta_q71l_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635e"; amc->num_cs = 1; amc->i2c_init = quanta_q71l_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 128 * MiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1289,7 +1289,7 @@ static void aspeed_machine_supermicrox11_bmc_class_init(ObjectClass *oc, amc->num_cs = 1; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = palmetto_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 256 * MiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1308,7 +1308,7 @@ static void aspeed_machine_supermicro_x11spi_bmc_class_init(ObjectClass *oc, amc->num_cs = 1; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = palmetto_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1325,7 +1325,7 @@ static void aspeed_machine_ast2500_evb_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635f"; amc->num_cs = 1; amc->i2c_init = ast2500_evb_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1343,7 +1343,7 @@ static void aspeed_machine_yosemitev2_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635e"; amc->num_cs = 2; amc->i2c_init = yosemitev2_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1360,7 +1360,7 @@ static void aspeed_machine_romulus_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx66l1g45g"; amc->num_cs = 2; amc->i2c_init = romulus_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1378,7 +1378,7 @@ static void aspeed_machine_tiogapass_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx25l25635e"; amc->num_cs = 2; amc->i2c_init = tiogapass_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1395,7 +1395,7 @@ static void aspeed_machine_sonorapass_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx66l1g45g"; amc->num_cs = 2; amc->i2c_init = sonorapass_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1412,7 +1412,7 @@ static void aspeed_machine_witherspoon_class_init(ObjectClass *oc, void *data) amc->spi_model = "mx66l1g45g"; amc->num_cs = 2; amc->i2c_init = witherspoon_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1433,7 +1433,7 @@ static void aspeed_machine_ast2600_evb_class_init(ObjectClass *oc, void *data) ASPEED_MAC3_ON; amc->sdhci_wp_inverted = true; amc->i2c_init = ast2600_evb_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); aspeed_machine_ast2600_class_emmc_init(oc); @@ -1452,7 +1452,7 @@ static void aspeed_machine_g220a_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = g220a_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1024 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1470,7 +1470,7 @@ static void aspeed_machine_fp5280g2_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON; amc->i2c_init = fp5280g2_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1489,7 +1489,7 @@ static void aspeed_machine_rainier_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON | ASPEED_MAC3_ON; amc->i2c_init = rainier_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); aspeed_machine_ast2600_class_emmc_init(oc); @@ -1512,7 +1512,7 @@ static void aspeed_machine_fuji_class_init(ObjectClass *oc, void *data) amc->macs_mask = ASPEED_MAC3_ON; amc->i2c_init = fuji_bmc_i2c_init; amc->uart_default = ASPEED_DEV_UART1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = FUJI_BMC_RAM_SIZE; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1533,7 +1533,7 @@ static void aspeed_machine_bletchley_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON; amc->i2c_init = bletchley_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = BLETCHLEY_BMC_RAM_SIZE; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1575,7 +1575,7 @@ static void aspeed_machine_fby35_class_init(ObjectClass *oc, void *data) amc->num_cs = 2; amc->macs_mask = ASPEED_MAC3_ON; amc->i2c_init = fby35_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; /* FIXME: Replace this macro with something more general */ mc->default_ram_size = FUJI_BMC_RAM_SIZE; aspeed_machine_class_init_cpus_defaults(mc); @@ -1658,7 +1658,7 @@ static void aspeed_minibmc_machine_ast1030_evb_class_init(ObjectClass *oc, amc->spi_model = "w25q256"; amc->num_cs = 2; amc->macs_mask = 0; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1687,7 +1687,7 @@ static void aspeed_machine_ast2700_evb_class_init(ObjectClass *oc, void *data) amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON; amc->uart_default = ASPEED_DEV_UART12; amc->i2c_init = ast2700_evb_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); } @@ -1708,7 +1708,7 @@ static void aspeed_machine_qcom_dc_scm_v1_class_init(ObjectClass *oc, amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON | ASPEED_MAC3_ON; amc->i2c_init = qcom_dc_scm_bmc_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); }; @@ -1728,7 +1728,7 @@ static void aspeed_machine_qcom_firework_class_init(ObjectClass *oc, amc->num_cs = 2; amc->macs_mask = ASPEED_MAC2_ON | ASPEED_MAC3_ON; amc->i2c_init = qcom_dc_scm_firework_i2c_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); }; diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c index d43c84435b02..f05ee0fee0f3 100644 --- a/hw/arm/b-l475e-iot01a.c +++ b/hw/arm/b-l475e-iot01a.c @@ -120,7 +120,7 @@ static void bl475e_machine_init(ObjectClass *oc, void *data) mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)"; mc->init = bl475e_init; mc->valid_cpu_types = machine_valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; /* SRAM pre-allocated as part of the SoC instantiation */ mc->default_ram_size = 0; diff --git a/hw/arm/bananapi_m2u.c b/hw/arm/bananapi_m2u.c index 3da6ec4a03ce..4d84d10d24c1 100644 --- a/hw/arm/bananapi_m2u.c +++ b/hw/arm/bananapi_m2u.c @@ -141,7 +141,7 @@ static void bpim2u_machine_init(MachineClass *mc) mc->valid_cpu_types = valid_cpu_types; mc->default_ram_size = 1 * GiB; mc->default_ram_id = "bpim2u.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("bpim2u", bpim2u_machine_init) diff --git a/hw/arm/collie.c b/hw/arm/collie.c index 80bf12246a9e..864c66193b1f 100644 --- a/hw/arm/collie.c +++ b/hw/arm/collie.c @@ -79,7 +79,7 @@ static void collie_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("sa1110"); mc->default_ram_size = RAM_SIZE; mc->default_ram_id = "strongarm.sdram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo collie_machine_typeinfo = { diff --git a/hw/arm/cubieboard.c b/hw/arm/cubieboard.c index 11d896f8322a..d665d4edd976 100644 --- a/hw/arm/cubieboard.c +++ b/hw/arm/cubieboard.c @@ -122,7 +122,7 @@ static void cubieboard_machine_init(MachineClass *mc) mc->units_per_default_bus = 1; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "cubieboard.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("cubieboard", cubieboard_machine_init) diff --git a/hw/arm/digic_boards.c b/hw/arm/digic_boards.c index a6ccf7ef9b64..f334c1fb02cc 100644 --- a/hw/arm/digic_boards.c +++ b/hw/arm/digic_boards.c @@ -143,7 +143,7 @@ static void canon_a1100_machine_init(MachineClass *mc) mc->ignore_memory_transaction_failures = true; mc->default_ram_size = 64 * MiB; mc->default_ram_id = "ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("canon-a1100", canon_a1100_machine_init) diff --git a/hw/arm/exynos4_boards.c b/hw/arm/exynos4_boards.c index 63e86e2c6094..43dc89d902e9 100644 --- a/hw/arm/exynos4_boards.c +++ b/hw/arm/exynos4_boards.c @@ -165,7 +165,7 @@ static void nuri_class_init(ObjectClass *oc, void *data) mc->min_cpus = EXYNOS4210_NCPUS; mc->default_cpus = EXYNOS4210_NCPUS; mc->ignore_memory_transaction_failures = true; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo nuri_type = { @@ -185,7 +185,7 @@ static void smdkc210_class_init(ObjectClass *oc, void *data) mc->min_cpus = EXYNOS4210_NCPUS; mc->default_cpus = EXYNOS4210_NCPUS; mc->ignore_memory_transaction_failures = true; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo smdkc210_type = { diff --git a/hw/arm/fby35.c b/hw/arm/fby35.c index 9b448bf764d2..6d3663f14a1e 100644 --- a/hw/arm/fby35.c +++ b/hw/arm/fby35.c @@ -170,7 +170,7 @@ static void fby35_class_init(ObjectClass *oc, void *data) mc->init = fby35_init; mc->no_floppy = 1; mc->no_cdrom = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->min_cpus = mc->max_cpus = mc->default_cpus = 3; object_class_property_add_bool(oc, "execute-in-place", diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c index 97477571e62e..975fd7a094af 100644 --- a/hw/arm/highbank.c +++ b/hw/arm/highbank.c @@ -357,7 +357,7 @@ static void highbank_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "highbank.dram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo highbank_type = { @@ -382,7 +382,7 @@ static void midway_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "highbank.dram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo midway_type = { diff --git a/hw/arm/imx25_pdk.c b/hw/arm/imx25_pdk.c index 8f89e03332f2..e95ea5e4e189 100644 --- a/hw/arm/imx25_pdk.c +++ b/hw/arm/imx25_pdk.c @@ -147,7 +147,7 @@ static void imx25_pdk_machine_init(MachineClass *mc) mc->init = imx25_pdk_init; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "imx25.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("imx25-pdk", imx25_pdk_machine_init) diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c index 905a7c2aecfa..8aa2e6e98e39 100644 --- a/hw/arm/integratorcp.c +++ b/hw/arm/integratorcp.c @@ -688,7 +688,7 @@ static void integratorcp_machine_init(MachineClass *mc) mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_id = "integrator.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/arm/kzm.c b/hw/arm/kzm.c index 45b3b08eb80f..736eabab664f 100644 --- a/hw/arm/kzm.c +++ b/hw/arm/kzm.c @@ -137,7 +137,7 @@ static void kzm_machine_init(MachineClass *mc) mc->init = kzm_init; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "kzm.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("kzm", kzm_machine_init) diff --git a/hw/arm/mcimx6ul-evk.c b/hw/arm/mcimx6ul-evk.c index 49520b47f186..86982cb0772a 100644 --- a/hw/arm/mcimx6ul-evk.c +++ b/hw/arm/mcimx6ul-evk.c @@ -74,6 +74,6 @@ static void mcimx6ul_evk_machine_init(MachineClass *mc) mc->init = mcimx6ul_evk_init; mc->max_cpus = FSL_IMX6UL_NUM_CPUS; mc->default_ram_id = "mcimx6ul-evk.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("mcimx6ul-evk", mcimx6ul_evk_machine_init) diff --git a/hw/arm/mcimx7d-sabre.c b/hw/arm/mcimx7d-sabre.c index da32fdd29d08..331196101137 100644 --- a/hw/arm/mcimx7d-sabre.c +++ b/hw/arm/mcimx7d-sabre.c @@ -74,6 +74,6 @@ static void mcimx7d_sabre_machine_init(MachineClass *mc) mc->init = mcimx7d_sabre_init; mc->max_cpus = FSL_IMX7_NUM_CPUS; mc->default_ram_id = "mcimx7d-sabre.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("mcimx7d-sabre", mcimx7d_sabre_machine_init) diff --git a/hw/arm/microbit.c b/hw/arm/microbit.c index 9d32ae5bd517..fb099508324d 100644 --- a/hw/arm/microbit.c +++ b/hw/arm/microbit.c @@ -67,7 +67,7 @@ static void microbit_machine_class_init(ObjectClass *oc, void *data) mc->desc = "BBC micro:bit (Cortex-M0)"; mc->init = microbit_init; mc->max_cpus = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo microbit_info = { diff --git a/hw/arm/mps2-tz.c b/hw/arm/mps2-tz.c index 106261203685..91b8ae6d3846 100644 --- a/hw/arm/mps2-tz.c +++ b/hw/arm/mps2-tz.c @@ -1320,7 +1320,7 @@ static void mps2tz_an505_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN505; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41045050; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1355,7 +1355,7 @@ static void mps2tz_an521_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN521; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41045210; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1390,7 +1390,7 @@ static void mps3tz_an524_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN524; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41045240; mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1430,7 +1430,7 @@ static void mps3tz_an547_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN547; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m55"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41055470; mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ mmc->apb_periph_frq = 25 * 1000 * 1000; /* 25MHz */ diff --git a/hw/arm/mps2.c b/hw/arm/mps2.c index 2bce37644e29..40eb5d161874 100644 --- a/hw/arm/mps2.c +++ b/hw/arm/mps2.c @@ -487,7 +487,7 @@ static void mps2_an385_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN385; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41043850; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; @@ -507,7 +507,7 @@ static void mps2_an386_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN386; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m4"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41043860; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; @@ -527,7 +527,7 @@ static void mps2_an500_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN500; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m7"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41045000; mmc->psram_base = 0x60000000; mmc->ethernet_base = 0xa0000000; @@ -547,7 +547,7 @@ static void mps2_an511_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN511; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->scc_id = 0x41045110; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; diff --git a/hw/arm/mps3r.c b/hw/arm/mps3r.c index ae70ebe86046..f26d1cfb2c49 100644 --- a/hw/arm/mps3r.c +++ b/hw/arm/mps3r.c @@ -618,7 +618,7 @@ static void mps3r_an536_class_init(ObjectClass *oc, void *data) mc->max_cpus = 2; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-r52"); mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->raminfo = an536_raminfo; mps3r_set_default_ram_info(mmc); } diff --git a/hw/arm/msf2-som.c b/hw/arm/msf2-som.c index 349a96a0b5d9..6d3f0a89e0b8 100644 --- a/hw/arm/msf2-som.c +++ b/hw/arm/msf2-som.c @@ -106,7 +106,7 @@ static void emcraft_sf2_machine_init(MachineClass *mc) mc->desc = "SmartFusion2 SOM kit from Emcraft (M2S010)"; mc->init = emcraft_sf2_s2s010_init; mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("emcraft-sf2", emcraft_sf2_machine_init) diff --git a/hw/arm/musca.c b/hw/arm/musca.c index 89451b968438..6f19b7d58a07 100644 --- a/hw/arm/musca.c +++ b/hw/arm/musca.c @@ -615,7 +615,7 @@ static void musca_a_class_init(ObjectClass *oc, void *data) MuscaMachineClass *mmc = MUSCA_MACHINE_CLASS(oc); mc->desc = "ARM Musca-A board (dual Cortex-M33)"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->type = MUSCA_A; mmc->init_svtor = 0x10200000; mmc->sram_addr_width = 15; @@ -630,7 +630,7 @@ static void musca_b1_class_init(ObjectClass *oc, void *data) MuscaMachineClass *mmc = MUSCA_MACHINE_CLASS(oc); mc->desc = "ARM Musca-B1 board (dual Cortex-M33)"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mmc->type = MUSCA_B1; /* * This matches the DAPlink firmware which boots from QSPI. There diff --git a/hw/arm/musicpal.c b/hw/arm/musicpal.c index d5ebfabe3f99..fd2975753ee2 100644 --- a/hw/arm/musicpal.c +++ b/hw/arm/musicpal.c @@ -1342,7 +1342,7 @@ static void musicpal_machine_init(MachineClass *mc) mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_size = MP_RAM_DEFAULT_SIZE; mc->default_ram_id = "musicpal.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/arm/netduino2.c b/hw/arm/netduino2.c index 9ca0ee6a345e..fca32d459246 100644 --- a/hw/arm/netduino2.c +++ b/hw/arm/netduino2.c @@ -63,7 +63,7 @@ static void netduino2_machine_init(MachineClass *mc) mc->init = netduino2_init; mc->valid_cpu_types = valid_cpu_types; mc->ignore_memory_transaction_failures = true; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("netduino2", netduino2_machine_init) diff --git a/hw/arm/netduinoplus2.c b/hw/arm/netduinoplus2.c index abe606076582..e1a59fb9e238 100644 --- a/hw/arm/netduinoplus2.c +++ b/hw/arm/netduinoplus2.c @@ -63,7 +63,7 @@ static void netduinoplus2_machine_init(MachineClass *mc) mc->desc = "Netduino Plus 2 Machine (Cortex-M4)"; mc->init = netduinoplus2_init; mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("netduinoplus2", netduinoplus2_machine_init) diff --git a/hw/arm/npcm7xx_boards.c b/hw/arm/npcm7xx_boards.c index 38b8f02364f1..eb28b97ad83e 100644 --- a/hw/arm/npcm7xx_boards.c +++ b/hw/arm/npcm7xx_boards.c @@ -481,7 +481,7 @@ static void npcm750_evb_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Nuvoton NPCM750 Evaluation Board (Cortex-A9)"; mc->init = npcm750_evb_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; }; @@ -494,7 +494,7 @@ static void gsj_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Quanta GSJ (Cortex-A9)"; mc->init = quanta_gsj_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 512 * MiB; }; @@ -507,7 +507,7 @@ static void gbs_bmc_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Quanta GBS (Cortex-A9)"; mc->init = quanta_gbs_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; } @@ -520,7 +520,7 @@ static void kudo_bmc_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Kudo BMC (Cortex-A9)"; mc->init = kudo_bmc_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; }; @@ -533,7 +533,7 @@ static void mori_bmc_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Mori BMC (Cortex-A9)"; mc->init = mori_bmc_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_size = 1 * GiB; } diff --git a/hw/arm/olimex-stm32-h405.c b/hw/arm/olimex-stm32-h405.c index 01ae12fa4acf..23f686de876a 100644 --- a/hw/arm/olimex-stm32-h405.c +++ b/hw/arm/olimex-stm32-h405.c @@ -66,7 +66,7 @@ static void olimex_stm32_h405_machine_init(MachineClass *mc) mc->desc = "Olimex STM32-H405 (Cortex-M4)"; mc->init = olimex_stm32_h405_init; mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; /* SRAM pre-allocated as part of the SoC instantiation */ mc->default_ram_size = 0; diff --git a/hw/arm/omap_sx1.c b/hw/arm/omap_sx1.c index 8170669db1fc..c6b0bed07960 100644 --- a/hw/arm/omap_sx1.c +++ b/hw/arm/omap_sx1.c @@ -216,7 +216,7 @@ static void sx1_machine_v2_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("ti925t"); mc->default_ram_size = SDRAM_SIZE; mc->default_ram_id = "omap1.dram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo sx1_machine_v2_type = { @@ -235,7 +235,7 @@ static void sx1_machine_v1_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("ti925t"); mc->default_ram_size = SDRAM_SIZE; mc->default_ram_id = "omap1.dram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo sx1_machine_v1_type = { diff --git a/hw/arm/orangepi.c b/hw/arm/orangepi.c index 76ab214853dc..634af9b0a10f 100644 --- a/hw/arm/orangepi.c +++ b/hw/arm/orangepi.c @@ -121,7 +121,7 @@ static void orangepi_machine_init(MachineClass *mc) mc->valid_cpu_types = valid_cpu_types; mc->default_ram_size = 1 * GiB; mc->default_ram_id = "orangepi.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("orangepi-pc", orangepi_machine_init) diff --git a/hw/arm/raspi.c b/hw/arm/raspi.c index 176c324cf82e..dce35ca11aa5 100644 --- a/hw/arm/raspi.c +++ b/hw/arm/raspi.c @@ -342,7 +342,7 @@ static void raspi0_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; rmc->board_rev = 0x920092; /* Revision 1.2 */ raspi_machine_class_init(mc, rmc->board_rev); }; @@ -352,7 +352,7 @@ static void raspi1ap_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; rmc->board_rev = 0x900021; /* Revision 1.1 */ raspi_machine_class_init(mc, rmc->board_rev); }; @@ -362,7 +362,7 @@ static void raspi2b_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; rmc->board_rev = 0xa21041; raspi_machine_class_init(mc, rmc->board_rev); }; @@ -373,7 +373,7 @@ static void raspi3ap_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; rmc->board_rev = 0x9020e0; /* Revision 1.0 */ raspi_machine_class_init(mc, rmc->board_rev); }; @@ -383,7 +383,7 @@ static void raspi3b_machine_class_init(ObjectClass *oc, void *data) MachineClass *mc = MACHINE_CLASS(oc); RaspiBaseMachineClass *rmc = RASPI_BASE_MACHINE_CLASS(oc); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; rmc->board_rev = 0xa02082; raspi_machine_class_init(mc, rmc->board_rev); }; diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c index 37eef378888f..f6de103a3e1b 100644 --- a/hw/arm/raspi4b.c +++ b/hw/arm/raspi4b.c @@ -118,7 +118,7 @@ static void raspi4b_machine_class_init(ObjectClass *oc, void *data) rmc->board_rev = 0xb03115; /* Revision 1.5, 2 Gb RAM */ #endif raspi_machine_class_common_init(mc, rmc->board_rev); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->init = raspi4b_machine_init; } diff --git a/hw/arm/realview.c b/hw/arm/realview.c index 4bc8f3956f3d..436eef816ed2 100644 --- a/hw/arm/realview.c +++ b/hw/arm/realview.c @@ -415,7 +415,7 @@ static void realview_eb_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } @@ -436,7 +436,7 @@ static void realview_eb_mpcore_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm11mpcore"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } @@ -455,7 +455,7 @@ static void realview_pb_a8_class_init(ObjectClass *oc, void *data) mc->init = realview_pb_a8_init; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a8"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } @@ -475,7 +475,7 @@ static void realview_pbx_a9_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a9"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/arm/sabrelite.c b/hw/arm/sabrelite.c index 8d57653ab344..df60d47c6fd1 100644 --- a/hw/arm/sabrelite.c +++ b/hw/arm/sabrelite.c @@ -110,7 +110,7 @@ static void sabrelite_machine_init(MachineClass *mc) mc->max_cpus = FSL_IMX6_NUM_CPUS; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "sabrelite.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("sabrelite", sabrelite_machine_init) diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c index 41dba85f44c2..02c72a62a37d 100644 --- a/hw/arm/sbsa-ref.c +++ b/hw/arm/sbsa-ref.c @@ -900,7 +900,7 @@ static void sbsa_ref_class_init(ObjectClass *oc, void *data) mc->minimum_page_bits = 12; mc->block_default_type = IF_IDE; mc->no_cdrom = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_nic = "e1000e"; mc->default_ram_size = 1 * GiB; mc->default_ram_id = "sbsa-ref.ram"; diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c index ef3fd508ba10..25283fd6233a 100644 --- a/hw/arm/stellaris.c +++ b/hw/arm/stellaris.c @@ -1421,7 +1421,7 @@ static void lm3s811evb_class_init(ObjectClass *oc, void *data) mc->init = lm3s811evb_init; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo lm3s811evb_type = { @@ -1442,7 +1442,7 @@ static void lm3s6965evb_class_init(ObjectClass *oc, void *data) mc->init = lm3s6965evb_init; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo lm3s6965evb_type = { diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c index 01b4afcb1a06..a71da292b843 100644 --- a/hw/arm/stm32vldiscovery.c +++ b/hw/arm/stm32vldiscovery.c @@ -66,7 +66,7 @@ static void stm32vldiscovery_machine_init(MachineClass *mc) mc->desc = "ST STM32VLDISCOVERY (Cortex-M3)"; mc->init = stm32vldiscovery_init; mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("stm32vldiscovery", stm32vldiscovery_machine_init) diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c index def3da4a344b..941616cd25bc 100644 --- a/hw/arm/versatilepb.c +++ b/hw/arm/versatilepb.c @@ -419,7 +419,7 @@ static void versatilepb_class_init(ObjectClass *oc, void *data) mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_id = "versatile.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } @@ -440,7 +440,7 @@ static void versatileab_class_init(ObjectClass *oc, void *data) mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_id = "versatile.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index 06ec78e6eb81..48e18a49d542 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -803,7 +803,7 @@ static void vexpress_a9_class_init(ObjectClass *oc, void *data) mc->desc = "ARM Versatile Express for Cortex-A9"; mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; vmc->daughterboard = &a9_daughterboard; } @@ -819,7 +819,7 @@ static void vexpress_a15_class_init(ObjectClass *oc, void *data) mc->desc = "ARM Versatile Express for Cortex-A15"; mc->valid_cpu_types = valid_cpu_types; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; vmc->daughterboard = &a15_daughterboard; diff --git a/hw/arm/virt.c b/hw/arm/virt.c index e2ac6ce4649c..9aea06b707fa 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -3125,7 +3125,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) #endif mc->block_default_type = IF_VIRTIO; mc->no_cdrom = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->pci_allow_0_address = true; /* We know we will never create a pre-ARMv7 CPU which needs 1K pages */ mc->minimum_page_bits = 12; diff --git a/hw/arm/xen-pvh.c b/hw/arm/xen-pvh.c index e49ab0e7f57d..ce4cc4fce93f 100644 --- a/hw/arm/xen-pvh.c +++ b/hw/arm/xen-pvh.c @@ -75,7 +75,7 @@ static void xen_arm_machine_class_init(ObjectClass *oc, void *data) * mc->max_cpus, QEMU will bail out with an error message. */ mc->max_cpus = GUEST_MAX_VCPUS; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; /* Xen/ARM does not use buffered IOREQs. */ xpc->handle_bufioreq = HVM_IOREQSRV_BUFIOREQ_OFF; diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index 12418094f9d8..3c6a4604cc96 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -463,7 +463,6 @@ static void zynq_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Xilinx Zynq Platform Baseboard for Cortex-A9"; mc->init = zynq_init; mc->max_cpus = ZYNQ_MAX_CPUS; - mc->no_sdcard = ON_OFF_AUTO_ON; mc->ignore_memory_transaction_failures = true; mc->valid_cpu_types = valid_cpu_types; mc->default_ram_id = "zynq.ext_ram"; diff --git a/hw/arm/xlnx-versal-virt.c b/hw/arm/xlnx-versal-virt.c index 8a1cdb037ca0..0c6f0359e3d9 100644 --- a/hw/arm/xlnx-versal-virt.c +++ b/hw/arm/xlnx-versal-virt.c @@ -818,7 +818,7 @@ static void versal_virt_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = XLNX_VERSAL_NR_ACPUS + XLNX_VERSAL_NR_RCPUS; mc->default_cpus = XLNX_VERSAL_NR_ACPUS + XLNX_VERSAL_NR_RCPUS; mc->no_cdrom = true; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_id = "ddr"; object_class_property_add_str(oc, "ospi-flash", versal_get_ospi_model, versal_set_ospi_model); diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c index 311d8f1cad45..4fdb153e4d8b 100644 --- a/hw/arm/xlnx-zcu102.c +++ b/hw/arm/xlnx-zcu102.c @@ -280,7 +280,7 @@ static void xlnx_zcu102_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = XLNX_ZYNQMP_NUM_APU_CPUS + XLNX_ZYNQMP_NUM_RPU_CPUS; mc->default_cpus = XLNX_ZYNQMP_NUM_APU_CPUS; mc->default_ram_id = "ddr-ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); object_class_property_add_bool(oc, "secure", zcu102_get_secure, diff --git a/hw/avr/arduino.c b/hw/avr/arduino.c index 1801074a4219..5f30a7d5850d 100644 --- a/hw/avr/arduino.c +++ b/hw/avr/arduino.c @@ -67,7 +67,7 @@ static void arduino_machine_class_init(ObjectClass *oc, void *data) mc->no_floppy = 1; mc->no_cdrom = 1; mc->no_parallel = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static void arduino_duemilanove_class_init(ObjectClass *oc, void *data) diff --git a/hw/core/null-machine.c b/hw/core/null-machine.c index b93056c0f7b3..7f1fb562bebc 100644 --- a/hw/core/null-machine.c +++ b/hw/core/null-machine.c @@ -53,7 +53,6 @@ static void machine_none_machine_init(MachineClass *mc) mc->no_parallel = 1; mc->no_floppy = 1; mc->no_cdrom = 1; - mc->no_sdcard = ON_OFF_AUTO_ON; } DEFINE_MACHINE("none", machine_none_machine_init) diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index 363ca89ca1c4..b4b238ed2c79 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -704,7 +704,7 @@ static void HP_B160L_machine_init_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_ram_id = "ram"; mc->default_nic = "tulip"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; nc->nmi_monitor_handler = hppa_nmi; } @@ -741,7 +741,7 @@ static void HP_C3700_machine_init_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_ram_id = "ram"; mc->default_nic = "tulip"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; nc->nmi_monitor_handler = hppa_nmi; } diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 9d8b7389e4b3..a5324a124610 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1799,7 +1799,7 @@ static void pc_machine_class_init(ObjectClass *oc, void *data) mc->smp_props.dies_supported = true; mc->smp_props.modules_supported = true; mc->default_ram_id = "pc.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; pcmc->default_smbios_ep_type = SMBIOS_ENTRY_POINT_TYPE_AUTO; object_class_property_add(oc, PC_MACHINE_MAX_RAM_BELOW_4G, "size", diff --git a/hw/i386/x86.c b/hw/i386/x86.c index e3b92fcb7445..790b1863f534 100644 --- a/hw/i386/x86.c +++ b/hw/i386/x86.c @@ -382,7 +382,7 @@ static void x86_machine_class_init(ObjectClass *oc, void *data) mc->get_default_cpu_node_id = x86_get_default_cpu_node_id; mc->possible_cpu_arch_ids = x86_possible_cpu_arch_ids; mc->kvm_type = x86_kvm_type; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; x86mc->save_tsc_khz = true; x86mc->fwcfg_dma_enabled = true; nc->nmi_monitor_handler = x86_nmi; diff --git a/hw/i386/xen/xen-pvh.c b/hw/i386/xen/xen-pvh.c index 33e5882c2d33..6f5b6a2b8f5d 100644 --- a/hw/i386/xen/xen-pvh.c +++ b/hw/i386/xen/xen-pvh.c @@ -82,7 +82,7 @@ static void xen_pvh_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Xen PVH x86 machine"; mc->default_cpu_type = TARGET_DEFAULT_CPU_TYPE; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; /* mc->max_cpus holds the MAX value allowed in the -smp cmd-line opts. */ mc->max_cpus = HVM_MAX_VCPUS; diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 7732547a6f82..a7f1c8acee42 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -954,7 +954,7 @@ static void virt_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_VIRTIO; mc->default_boot_order = "c"; mc->no_cdrom = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->possible_cpu_arch_ids = virt_possible_cpu_arch_ids; mc->cpu_index_to_instance_props = virt_cpu_index_to_props; mc->get_default_cpu_node_id = virt_get_default_cpu_node_id; diff --git a/hw/m68k/an5206.c b/hw/m68k/an5206.c index 286c3bac2ad1..194787051295 100644 --- a/hw/m68k/an5206.c +++ b/hw/m68k/an5206.c @@ -99,7 +99,7 @@ static void an5206_machine_init(MachineClass *mc) mc->init = an5206_init; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5206"); mc->default_ram_id = "an5206.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("an5206", an5206_machine_init) diff --git a/hw/m68k/mcf5208.c b/hw/m68k/mcf5208.c index a0c90d111e6b..9a8c551224fe 100644 --- a/hw/m68k/mcf5208.c +++ b/hw/m68k/mcf5208.c @@ -398,7 +398,7 @@ static void mcf5208evb_machine_init(MachineClass *mc) mc->is_default = true; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5208"); mc->default_ram_id = "mcf5208.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("mcf5208evb", mcf5208evb_machine_init) diff --git a/hw/m68k/next-cube.c b/hw/m68k/next-cube.c index 06a4d825e714..ee06a5e2e07c 100644 --- a/hw/m68k/next-cube.c +++ b/hw/m68k/next-cube.c @@ -1359,7 +1359,7 @@ static void next_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_id = "next.ram"; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040"); mc->no_cdrom = true; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo next_typeinfo = { diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c index 21fa56e7a9c4..f3cb8541b375 100644 --- a/hw/m68k/q800.c +++ b/hw/m68k/q800.c @@ -743,7 +743,7 @@ static void q800_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = 1; mc->block_default_type = IF_SCSI; mc->default_ram_id = "m68k_mac.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); compat_props_add(mc->compat_props, hw_compat_q800, hw_compat_q800_len); diff --git a/hw/m68k/virt.c b/hw/m68k/virt.c index a1bd9c432d23..69e8f53482d5 100644 --- a/hw/m68k/virt.c +++ b/hw/m68k/virt.c @@ -318,7 +318,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = 1; mc->no_floppy = 1; mc->no_parallel = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_ram_id = "m68k_virt.ram"; } diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c index 8d0e6c948f4e..1e3d55dd3b45 100644 --- a/hw/microblaze/petalogix_ml605_mmu.c +++ b/hw/microblaze/petalogix_ml605_mmu.c @@ -220,7 +220,7 @@ static void petalogix_ml605_machine_init(MachineClass *mc) mc->desc = "PetaLogix linux refdesign for xilinx ml605 (little endian)"; #endif mc->init = petalogix_ml605_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("petalogix-ml605", petalogix_ml605_machine_init) diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c index 5b06e7d82cd5..95b1b6483fde 100644 --- a/hw/microblaze/petalogix_s3adsp1800_mmu.c +++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c @@ -142,7 +142,7 @@ static void petalogix_s3adsp1800_machine_class_init(ObjectClass *oc, void *data) mc->desc = "PetaLogix linux refdesign for xilinx Spartan 3ADSP1800"; mc->init = petalogix_s3adsp1800_init; mc->is_default = true; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo petalogix_s3adsp1800_machine_types[] = { diff --git a/hw/microblaze/xlnx-zynqmp-pmu.c b/hw/microblaze/xlnx-zynqmp-pmu.c index 14386785f85e..b40d82b396fc 100644 --- a/hw/microblaze/xlnx-zynqmp-pmu.c +++ b/hw/microblaze/xlnx-zynqmp-pmu.c @@ -188,7 +188,7 @@ static void xlnx_zynqmp_pmu_machine_init(MachineClass *mc) mc->desc = "Xilinx ZynqMP PMU machine (little endian)"; #endif mc->init = xlnx_zynqmp_pmu_init; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("xlnx-zynqmp-pmu", xlnx_zynqmp_pmu_machine_init) diff --git a/hw/mips/boston.c b/hw/mips/boston.c index bd9059a2070d..66cdad639e8a 100644 --- a/hw/mips/boston.c +++ b/hw/mips/boston.c @@ -842,7 +842,7 @@ static void boston_mach_class_init(MachineClass *mc) mc->default_ram_id = "boston.ddr"; mc->max_cpus = 16; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("I6400"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("boston", boston_mach_class_init) diff --git a/hw/mips/fuloong2e.c b/hw/mips/fuloong2e.c index 83e95c3b20ec..e10b5a277430 100644 --- a/hw/mips/fuloong2e.c +++ b/hw/mips/fuloong2e.c @@ -335,7 +335,7 @@ static void mips_fuloong2e_machine_init(MachineClass *mc) mc->default_ram_size = 256 * MiB; mc->default_ram_id = "fuloong2e.ram"; mc->minimum_page_bits = 14; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/mips/jazz.c b/hw/mips/jazz.c index 326f60c448bb..ad4561a51e69 100644 --- a/hw/mips/jazz.c +++ b/hw/mips/jazz.c @@ -424,7 +424,7 @@ static void mips_magnum_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("R4000"); mc->default_ram_id = "mips_jazz.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo mips_magnum_type = { @@ -442,7 +442,7 @@ static void mips_pica61_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("R4000"); mc->default_ram_id = "mips_jazz.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo mips_pica61_type = { diff --git a/hw/mips/loongson3_virt.c b/hw/mips/loongson3_virt.c index f44932b331ef..46c2e1e9de5b 100644 --- a/hw/mips/loongson3_virt.c +++ b/hw/mips/loongson3_virt.c @@ -679,7 +679,7 @@ static void loongson3v_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 1600 * MiB; mc->minimum_page_bits = 14; mc->default_nic = "virtio-net-pci"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo loongson3_machine_types[] = { diff --git a/hw/mips/malta.c b/hw/mips/malta.c index 13811f89f916..31ff279b4cbe 100644 --- a/hw/mips/malta.c +++ b/hw/mips/malta.c @@ -1306,7 +1306,7 @@ static void mips_malta_machine_init(MachineClass *mc) mc->default_cpu_type = MIPS_CPU_TYPE_NAME("24Kf"); #endif mc->default_ram_id = "mips_malta.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; compat_props_add(mc->compat_props, malta_compat, malta_compat_len); } diff --git a/hw/mips/mipssim.c b/hw/mips/mipssim.c index f0d06ab549c4..ff2b9050700e 100644 --- a/hw/mips/mipssim.c +++ b/hw/mips/mipssim.c @@ -247,7 +247,7 @@ static void mips_mipssim_machine_init(MachineClass *mc) mc->default_cpu_type = MIPS_CPU_TYPE_NAME("24Kf"); #endif mc->default_ram_id = "mips_mipssim.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("mipssim", mips_mipssim_machine_init) diff --git a/hw/openrisc/openrisc_sim.c b/hw/openrisc/openrisc_sim.c index b9d71ea0c3b8..5e4686ba7a24 100644 --- a/hw/openrisc/openrisc_sim.c +++ b/hw/openrisc/openrisc_sim.c @@ -368,7 +368,7 @@ static void openrisc_sim_machine_init(ObjectClass *oc, void *data) mc->max_cpus = OR1KSIM_CPUS_MAX; mc->is_default = true; mc->default_cpu_type = OPENRISC_CPU_TYPE_NAME("or1200"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo or1ksim_machine_typeinfo = { diff --git a/hw/openrisc/virt.c b/hw/openrisc/virt.c index d250e2ab21f9..2764e398a982 100644 --- a/hw/openrisc/virt.c +++ b/hw/openrisc/virt.c @@ -554,7 +554,7 @@ static void openrisc_virt_machine_init(ObjectClass *oc, void *data) mc->max_cpus = VIRT_CPUS_MAX; mc->is_default = false; mc->default_cpu_type = OPENRISC_CPU_TYPE_NAME("or1200"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo or1ksim_machine_typeinfo = { diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index 39449e3632dc..296c30da92f4 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -173,7 +173,7 @@ static void amigaone_machine_init(MachineClass *mc) mc->default_display = "std"; mc->default_ram_id = "ram"; mc->default_ram_size = 512 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("amigaone", amigaone_machine_init) diff --git a/hw/ppc/e500plat.c b/hw/ppc/e500plat.c index 0b91d422aa1a..afad4802caec 100644 --- a/hw/ppc/e500plat.c +++ b/hw/ppc/e500plat.c @@ -100,7 +100,7 @@ static void e500plat_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("e500v2_v30"); mc->default_ram_id = "mpc8544ds.ram"; mc->default_nic = "virtio-net-pci"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_class_allow_dynamic_sysbus_dev(mc, TYPE_ETSEC_COMMON); } diff --git a/hw/ppc/mac_newworld.c b/hw/ppc/mac_newworld.c index da53eb11ed9e..869f3f7104bd 100644 --- a/hw/ppc/mac_newworld.c +++ b/hw/ppc/mac_newworld.c @@ -580,7 +580,7 @@ static void core99_machine_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_display = "std"; mc->default_nic = "sungem"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->kvm_type = core99_kvm_type; #ifdef TARGET_PPC64 mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("970fx_v3.1"); diff --git a/hw/ppc/mac_oldworld.c b/hw/ppc/mac_oldworld.c index b23bfff696e5..08e30a4a4e93 100644 --- a/hw/ppc/mac_oldworld.c +++ b/hw/ppc/mac_oldworld.c @@ -427,7 +427,7 @@ static void heathrow_class_init(ObjectClass *oc, void *data) mc->default_nic = "ne2k_pci"; mc->ignore_boot_device_suffixes = true; mc->default_ram_id = "ppc_heathrow.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; fwc->get_dev_path = heathrow_fw_dev_path; } diff --git a/hw/ppc/mpc8544ds.c b/hw/ppc/mpc8544ds.c index 0fa3a1b77674..38bdf4531658 100644 --- a/hw/ppc/mpc8544ds.c +++ b/hw/ppc/mpc8544ds.c @@ -62,7 +62,7 @@ static void mpc8544ds_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("e500v2_v30"); mc->default_ram_id = "mpc8544ds.ram"; mc->default_nic = "virtio-net-pci"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } #define TYPE_MPC8544DS_MACHINE MACHINE_TYPE_NAME("mpc8544ds") diff --git a/hw/ppc/pegasos2.c b/hw/ppc/pegasos2.c index 03ddbb96b66c..479dcfe809f9 100644 --- a/hw/ppc/pegasos2.c +++ b/hw/ppc/pegasos2.c @@ -604,7 +604,7 @@ static void pegasos2_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("7457_v1.2"); mc->default_ram_id = "pegasos2.ram"; mc->default_ram_size = 512 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); vhc->cpu_in_nested = pegasos2_cpu_in_nested; diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 70203449fda9..15fbbf6c157b 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -2878,7 +2878,7 @@ static void pnv_machine_class_init(ObjectClass *oc, void *data) /* Pnv provides a AHCI device for storage */ mc->block_default_type = IF_IDE; mc->no_parallel = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_boot_order = NULL; /* * RAM defaults to less than 2048 for 32-bit hosts, and large diff --git a/hw/ppc/ppc405_boards.c b/hw/ppc/ppc405_boards.c index c602d6081765..8946b5173c5d 100644 --- a/hw/ppc/ppc405_boards.c +++ b/hw/ppc/ppc405_boards.c @@ -351,7 +351,7 @@ static void ppc405_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 128 * MiB; mc->default_ram_id = "ppc405.ram"; mc->deprecation_reason = "machine is old and unmaintained"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo ppc405_machine_type = { diff --git a/hw/ppc/ppc440_bamboo.c b/hw/ppc/ppc440_bamboo.c index 5b2d52032f78..081a993ef021 100644 --- a/hw/ppc/ppc440_bamboo.c +++ b/hw/ppc/ppc440_bamboo.c @@ -268,7 +268,7 @@ static void bamboo_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("440epb"); mc->default_ram_id = "ppc4xx.sdram"; mc->default_nic = "e1000"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("bamboo", bamboo_machine_init) diff --git a/hw/ppc/prep.c b/hw/ppc/prep.c index 998e8ecd426d..85bfc2fd4f01 100644 --- a/hw/ppc/prep.c +++ b/hw/ppc/prep.c @@ -428,7 +428,7 @@ static void ibm_40p_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("604"); mc->default_display = "std"; mc->default_nic = "pcnet"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/ppc/sam460ex.c b/hw/ppc/sam460ex.c index a1b1fc8724af..d9c871ef205a 100644 --- a/hw/ppc/sam460ex.c +++ b/hw/ppc/sam460ex.c @@ -524,7 +524,7 @@ static void sam460ex_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("460exb"); mc->default_ram_size = 512 * MiB; mc->default_ram_id = "ppc4xx.sdram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("sam460ex", sam460ex_machine_init) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index fa030d52767d..42b07fadd9de 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -4594,7 +4594,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = SPAPR_IRQ_NR_IPIS; mc->no_parallel = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->default_boot_order = ""; mc->default_ram_size = 512 * MiB; mc->default_ram_id = "ppc_spapr.ram"; diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c index 23d115d99765..22184fb6989b 100644 --- a/hw/ppc/virtex_ml507.c +++ b/hw/ppc/virtex_ml507.c @@ -288,7 +288,7 @@ static void virtex_machine_init(MachineClass *mc) mc->init = virtex_init; mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("440-xilinx"); mc->default_ram_id = "ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("virtex-ml507", virtex_machine_init) diff --git a/hw/remote/machine.c b/hw/remote/machine.c index 1f7b0b96dd46..fa4a1bb815aa 100644 --- a/hw/remote/machine.c +++ b/hw/remote/machine.c @@ -128,7 +128,7 @@ static void remote_machine_class_init(ObjectClass *oc, void *data) mc->init = remote_machine_init; mc->desc = "Experimental remote machine"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; hc->unplug = remote_machine_dev_unplug_cb; diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c index 2417342a717d..9c846f9b5baf 100644 --- a/hw/riscv/microchip_pfsoc.c +++ b/hw/riscv/microchip_pfsoc.c @@ -650,7 +650,7 @@ static void microchip_icicle_kit_machine_class_init(ObjectClass *oc, void *data) mc->min_cpus = MICROCHIP_PFSOC_MANAGEMENT_CPU_COUNT + 1; mc->default_cpus = mc->min_cpus; mc->default_ram_id = "microchip.icicle.kit.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; /* * Map 513 MiB high memory, the minimum required high memory size, because diff --git a/hw/riscv/opentitan.c b/hw/riscv/opentitan.c index 3b26e1f53bcd..d78a96c53542 100644 --- a/hw/riscv/opentitan.c +++ b/hw/riscv/opentitan.c @@ -121,7 +121,7 @@ static void opentitan_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_IBEX; mc->default_ram_id = "riscv.lowrisc.ibex.ram"; mc->default_ram_size = ibex_memmap[IBEX_DEV_RAM].size; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static void lowrisc_ibex_soc_init(Object *obj) diff --git a/hw/riscv/shakti_c.c b/hw/riscv/shakti_c.c index 2f59c86bf0c5..efe814b5868a 100644 --- a/hw/riscv/shakti_c.c +++ b/hw/riscv/shakti_c.c @@ -84,7 +84,7 @@ static void shakti_c_machine_class_init(ObjectClass *klass, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_SHAKTI_C; mc->valid_cpu_types = valid_cpu_types; mc->default_ram_id = "riscv.shakti.c.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo shakti_c_machine_type_info = { diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c index 56a2ca7cabba..164eb3ab83b1 100644 --- a/hw/riscv/sifive_e.c +++ b/hw/riscv/sifive_e.c @@ -153,7 +153,7 @@ static void sifive_e_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = SIFIVE_E_CPU; mc->default_ram_id = "riscv.sifive.e.ram"; mc->default_ram_size = sifive_e_memmap[SIFIVE_E_DEV_DTIM].size; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; object_class_property_add_bool(oc, "revb", sifive_e_machine_get_revb, sifive_e_machine_set_revb); diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c index fd57d02dca82..679f2024bc6a 100644 --- a/hw/riscv/sifive_u.c +++ b/hw/riscv/sifive_u.c @@ -724,7 +724,7 @@ static void sifive_u_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = SIFIVE_U_CPU; mc->default_cpus = mc->min_cpus; mc->default_ram_id = "riscv.sifive.u.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; object_class_property_add_bool(oc, "start-in-flash", sifive_u_machine_get_start_in_flash, diff --git a/hw/riscv/spike.c b/hw/riscv/spike.c index a0fa727f6bfe..1ea35937e154 100644 --- a/hw/riscv/spike.c +++ b/hw/riscv/spike.c @@ -358,7 +358,7 @@ static void spike_machine_class_init(ObjectClass *oc, void *data) /* platform instead of architectural choice */ mc->cpu_cluster_has_numa_boundary = true; mc->default_ram_id = "riscv.spike.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; object_class_property_add_str(oc, "signature", NULL, spike_set_signature); object_class_property_set_description(oc, "signature", "File to write ACT test signature"); diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index d9fc7cdf9ac8..2aa420f6e55b 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -1918,7 +1918,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_BASE; mc->block_default_type = IF_VIRTIO; mc->no_cdrom = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; mc->pci_allow_0_address = true; mc->possible_cpu_arch_ids = riscv_numa_possible_cpu_arch_ids; mc->cpu_index_to_instance_props = riscv_numa_cpu_index_to_props; diff --git a/hw/rx/rx-gdbsim.c b/hw/rx/rx-gdbsim.c index 9c3ae60bf806..de3b708bc57c 100644 --- a/hw/rx/rx-gdbsim.c +++ b/hw/rx/rx-gdbsim.c @@ -166,7 +166,7 @@ static void rx_gdbsim_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RX62N_CPU; mc->default_ram_size = 16 * MiB; mc->default_ram_id = "ext-sdram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static void rx62n7_class_init(ObjectClass *oc, void *data) diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index 5f78c8d20ff9..51ae0c133d8e 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c @@ -817,7 +817,6 @@ static void ccw_machine_class_init(ObjectClass *oc, void *data) mc->no_cdrom = 1; mc->no_floppy = 1; mc->no_parallel = 1; - mc->no_sdcard = ON_OFF_AUTO_ON; mc->max_cpus = S390_MAX_CPUS; mc->has_hotpluggable_cpus = true; mc->smp_props.books_supported = true; diff --git a/hw/sh4/r2d.c b/hw/sh4/r2d.c index 6ef552ae5368..e34deb33dce6 100644 --- a/hw/sh4/r2d.c +++ b/hw/sh4/r2d.c @@ -383,7 +383,7 @@ static void r2d_machine_init(MachineClass *mc) mc->block_default_type = IF_IDE; mc->default_cpu_type = TYPE_SH7751R_CPU; mc->default_nic = "rtl8139"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("r2d", r2d_machine_init) diff --git a/hw/sparc/leon3.c b/hw/sparc/leon3.c index e99d6d71e8f7..3bd244957580 100644 --- a/hw/sparc/leon3.c +++ b/hw/sparc/leon3.c @@ -440,7 +440,7 @@ static void leon3_generic_machine_init(MachineClass *mc) mc->default_cpu_type = SPARC_CPU_TYPE_NAME("LEON3"); mc->default_ram_id = "leon3.ram"; mc->max_cpus = MAX_CPUS; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("leon3_generic", leon3_generic_machine_init) diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c index 19a2a9f2ff6e..d555548a1cad 100644 --- a/hw/sparc/sun4m.c +++ b/hw/sparc/sun4m.c @@ -1113,7 +1113,7 @@ static void sun4m_machine_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "c"; mc->default_display = "tcx"; mc->default_ram_id = "sun4m.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static void ss5_class_init(ObjectClass *oc, void *data) diff --git a/hw/sparc64/niagara.c b/hw/sparc64/niagara.c index 409c67b1b4e0..37004b99c4ef 100644 --- a/hw/sparc64/niagara.c +++ b/hw/sparc64/niagara.c @@ -167,7 +167,7 @@ static void niagara_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "c"; mc->default_cpu_type = SPARC_CPU_TYPE_NAME("Sun-UltraSparc-T1"); mc->default_ram_id = "sun4v-partition.ram"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo niagara_type = { diff --git a/hw/sparc64/sun4u.c b/hw/sparc64/sun4u.c index eaf3d42bd0ab..6e9a3c5a2e54 100644 --- a/hw/sparc64/sun4u.c +++ b/hw/sparc64/sun4u.c @@ -809,7 +809,7 @@ static void sun4u_class_init(ObjectClass *oc, void *data) mc->default_display = "std"; mc->default_nic = "sunhme"; mc->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; fwc->get_dev_path = sun4u_fw_dev_path; compat_props_add(mc->compat_props, hw_compat_sparc64, hw_compat_sparc64_len); } @@ -837,7 +837,7 @@ static void sun4v_class_init(ObjectClass *oc, void *data) mc->default_display = "std"; mc->default_nic = "sunhme"; mc->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo sun4v_type = { diff --git a/hw/tricore/triboard.c b/hw/tricore/triboard.c index 3cd93daf6a6c..d4550507a99c 100644 --- a/hw/tricore/triboard.c +++ b/hw/tricore/triboard.c @@ -73,7 +73,7 @@ static void triboard_machine_tc277d_class_init(ObjectClass *oc, mc->init = triboard_machine_init; mc->desc = "Infineon AURIX TriBoard TC277 (D-Step)"; mc->max_cpus = 1; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; amc->soc_name = "tc277d-soc"; }; diff --git a/hw/tricore/tricore_testboard.c b/hw/tricore/tricore_testboard.c index 29718051702f..9299cd5394a0 100644 --- a/hw/tricore/tricore_testboard.c +++ b/hw/tricore/tricore_testboard.c @@ -111,7 +111,7 @@ static void ttb_machine_init(MachineClass *mc) mc->desc = "a minimal TriCore board"; mc->init = tricoreboard_init; mc->default_cpu_type = TRICORE_CPU_TYPE_NAME("tc1796"); - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("tricore_testboard", ttb_machine_init) diff --git a/hw/xen/xen-pvh-common.c b/hw/xen/xen-pvh-common.c index ed42e4b624c8..9df50cd5382e 100644 --- a/hw/xen/xen-pvh-common.c +++ b/hw/xen/xen-pvh-common.c @@ -381,7 +381,7 @@ static void xen_pvh_class_init(ObjectClass *oc, void *data) mc->default_machine_opts = "accel=xen"; /* Set to zero to make sure that the real ram size is passed. */ mc->default_ram_size = 0; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xen_pvh_info = { diff --git a/hw/xenpv/xen_machine_pv.c b/hw/xenpv/xen_machine_pv.c index a05713e5daa6..abdc5bc9a310 100644 --- a/hw/xenpv/xen_machine_pv.c +++ b/hw/xenpv/xen_machine_pv.c @@ -67,7 +67,7 @@ static void xenpv_machine_init(MachineClass *mc) mc->init = xen_init_pv; mc->max_cpus = 1; mc->default_machine_opts = "accel=xen"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("xenpv", xenpv_machine_init) diff --git a/hw/xtensa/sim.c b/hw/xtensa/sim.c index 6c86d4939f42..989cfd49182e 100644 --- a/hw/xtensa/sim.c +++ b/hw/xtensa/sim.c @@ -125,7 +125,7 @@ static void xtensa_sim_machine_init(MachineClass *mc) mc->max_cpus = 4; mc->no_serial = 1; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("sim", xtensa_sim_machine_init) diff --git a/hw/xtensa/virt.c b/hw/xtensa/virt.c index ffa6f210637a..0a78ab3a6f97 100644 --- a/hw/xtensa/virt.c +++ b/hw/xtensa/virt.c @@ -122,7 +122,7 @@ static void xtensa_virt_machine_init(MachineClass *mc) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_nic = "virtio-net-pci"; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } DEFINE_MACHINE("virt", xtensa_virt_machine_init) diff --git a/hw/xtensa/xtfpga.c b/hw/xtensa/xtfpga.c index 2b5f20acefc9..e00ae9d2e211 100644 --- a/hw/xtensa/xtfpga.c +++ b/hw/xtensa/xtfpga.c @@ -594,7 +594,7 @@ static void xtfpga_lx60_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 64 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx60_type = { @@ -612,7 +612,7 @@ static void xtfpga_lx60_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 64 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx60_nommu_type = { @@ -630,7 +630,7 @@ static void xtfpga_lx200_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 96 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx200_type = { @@ -648,7 +648,7 @@ static void xtfpga_lx200_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 96 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx200_nommu_type = { @@ -666,7 +666,7 @@ static void xtfpga_ml605_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 512 * MiB - XTFPGA_MMU_RESERVED_MEMORY_SIZE; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_ml605_type = { @@ -684,7 +684,7 @@ static void xtfpga_ml605_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 256 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_ml605_nommu_type = { @@ -702,7 +702,7 @@ static void xtfpga_kc705_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 1 * GiB - XTFPGA_MMU_RESERVED_MEMORY_SIZE; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_kc705_type = { @@ -720,7 +720,7 @@ static void xtfpga_kc705_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 256 * MiB; - mc->no_sdcard = ON_OFF_AUTO_OFF; + mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_kc705_nommu_type = { diff --git a/include/hw/boards.h b/include/hw/boards.h index d61b0a477805..9360d1ce394a 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -285,7 +285,7 @@ struct MachineClass { no_cdrom:1, pci_allow_0_address:1, legacy_fw_cfg_order:1; - OnOffAuto no_sdcard; + bool auto_create_sdcard; bool is_default; const char *default_machine_opts; const char *default_boot_order; diff --git a/system/vl.c b/system/vl.c index 3904d0d1581c..f9a0526d5fbe 100644 --- a/system/vl.c +++ b/system/vl.c @@ -194,7 +194,7 @@ static int default_parallel = 1; static int default_monitor = 1; static int default_floppy = 1; static int default_cdrom = 1; -static int default_sdcard = 1; +static bool auto_create_sdcard = true; static int default_vga = 1; static int default_net = 1; @@ -718,7 +718,7 @@ static void configure_blockdev(BlockdevOptionsQueue *bdo_queue, default_drive(default_cdrom, snapshot, machine_class->block_default_type, 2, CDROM_OPTS); default_drive(default_floppy, snapshot, IF_FLOPPY, 0, FD_OPTS); - default_drive(default_sdcard, snapshot, IF_SD, 0, SD_OPTS); + default_drive(auto_create_sdcard, snapshot, IF_SD, 0, SD_OPTS); } @@ -1346,9 +1346,8 @@ static void qemu_disable_default_devices(void) if (!has_defaults || machine_class->no_cdrom) { default_cdrom = 0; } - assert(machine_class->no_sdcard != ON_OFF_AUTO_AUTO); - if (!has_defaults || machine_class->no_sdcard == ON_OFF_AUTO_ON) { - default_sdcard = 0; + if (!has_defaults || !machine_class->auto_create_sdcard) { + auto_create_sdcard = false; } if (!has_defaults) { default_audio = 0; From 5824fad4e92e3d10de1ce86d900dcde8f8dfaf76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Feb 2025 13:17:43 +0100 Subject: [PATCH 0086/1179] hw/boards: Do not create unusable default if=sd drives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A number of machines create an if=sd drive by default even though they lack an SD bus, and therefore cannot use the drive. This drive is created when the machine sets flag @auto_create_sdcard. See for example running HMP "info block" on the HPPA C3700 machine: $ qemu-system-hppa -M C3700 -monitor stdio -S (qemu) info block floppy0: [not inserted] Removable device: not locked, tray closed sd0: [not inserted] Removable device: not locked, tray closed $ qemu-system-hppa -M C3700 -sd /bin/sh qemu-system-hppa: -sd /bin/sh: machine type does not support if=sd,bus=0,unit=0 Delete that from machines that lack an SD bus. Note, only the ARM and RISCV targets use such feature: $ git grep -wl IF_SD hw | cut -d/ -f-2 | sort -u hw/arm hw/riscv $ Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250204200934.65279-5-philmd@linaro.org> --- hw/alpha/dp264.c | 1 - hw/avr/arduino.c | 1 - hw/hppa/machine.c | 2 - hw/i386/pc.c | 1 - hw/i386/x86.c | 1 - hw/i386/xen/xen-pvh.c | 1 - hw/loongarch/virt.c | 1 - hw/m68k/an5206.c | 1 - hw/m68k/mcf5208.c | 1 - hw/m68k/next-cube.c | 1 - hw/m68k/q800.c | 1 - hw/m68k/virt.c | 1 - hw/microblaze/petalogix_ml605_mmu.c | 1 - hw/microblaze/petalogix_s3adsp1800_mmu.c | 1 - hw/microblaze/xlnx-zynqmp-pmu.c | 1 - hw/mips/boston.c | 1 - hw/mips/fuloong2e.c | 1 - hw/mips/jazz.c | 2 - hw/mips/loongson3_virt.c | 1 - hw/mips/malta.c | 1 - hw/mips/mipssim.c | 1 - hw/openrisc/openrisc_sim.c | 1 - hw/openrisc/virt.c | 1 - hw/ppc/amigaone.c | 1 - hw/ppc/e500plat.c | 1 - hw/ppc/mac_newworld.c | 1 - hw/ppc/mac_oldworld.c | 1 - hw/ppc/mpc8544ds.c | 1 - hw/ppc/pegasos2.c | 1 - hw/ppc/pnv.c | 1 - hw/ppc/ppc405_boards.c | 1 - hw/ppc/ppc440_bamboo.c | 1 - hw/ppc/prep.c | 1 - hw/ppc/sam460ex.c | 1 - hw/ppc/spapr.c | 1 - hw/ppc/virtex_ml507.c | 1 - hw/remote/machine.c | 1 - hw/rx/rx-gdbsim.c | 1 - hw/sh4/r2d.c | 1 - hw/sparc/leon3.c | 1 - hw/sparc/sun4m.c | 1 - hw/sparc64/niagara.c | 1 - hw/sparc64/sun4u.c | 2 - hw/tricore/triboard.c | 1 - hw/tricore/tricore_testboard.c | 1 - hw/xen/xen-pvh-common.c | 1 - hw/xenpv/xen_machine_pv.c | 1 - hw/xtensa/sim.c | 1 - hw/xtensa/virt.c | 1 - hw/xtensa/xtfpga.c | 8 ---- tests/qemu-iotests/172.out | 60 ------------------------ 51 files changed, 120 deletions(-) diff --git a/hw/alpha/dp264.c b/hw/alpha/dp264.c index 14b942fd5a7e..570ea9edf24b 100644 --- a/hw/alpha/dp264.c +++ b/hw/alpha/dp264.c @@ -213,7 +213,6 @@ static void clipper_machine_init(MachineClass *mc) mc->default_cpu_type = ALPHA_CPU_TYPE_NAME("ev67"); mc->default_ram_id = "ram"; mc->default_nic = "e1000"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("clipper", clipper_machine_init) diff --git a/hw/avr/arduino.c b/hw/avr/arduino.c index 5f30a7d5850d..48ef478346e9 100644 --- a/hw/avr/arduino.c +++ b/hw/avr/arduino.c @@ -67,7 +67,6 @@ static void arduino_machine_class_init(ObjectClass *oc, void *data) mc->no_floppy = 1; mc->no_cdrom = 1; mc->no_parallel = 1; - mc->auto_create_sdcard = true; } static void arduino_duemilanove_class_init(ObjectClass *oc, void *data) diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index b4b238ed2c79..c5f247633eb4 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -704,7 +704,6 @@ static void HP_B160L_machine_init_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_ram_id = "ram"; mc->default_nic = "tulip"; - mc->auto_create_sdcard = true; nc->nmi_monitor_handler = hppa_nmi; } @@ -741,7 +740,6 @@ static void HP_C3700_machine_init_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_ram_id = "ram"; mc->default_nic = "tulip"; - mc->auto_create_sdcard = true; nc->nmi_monitor_handler = hppa_nmi; } diff --git a/hw/i386/pc.c b/hw/i386/pc.c index a5324a124610..22641e6ddca5 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1799,7 +1799,6 @@ static void pc_machine_class_init(ObjectClass *oc, void *data) mc->smp_props.dies_supported = true; mc->smp_props.modules_supported = true; mc->default_ram_id = "pc.ram"; - mc->auto_create_sdcard = true; pcmc->default_smbios_ep_type = SMBIOS_ENTRY_POINT_TYPE_AUTO; object_class_property_add(oc, PC_MACHINE_MAX_RAM_BELOW_4G, "size", diff --git a/hw/i386/x86.c b/hw/i386/x86.c index 790b1863f534..69bfc00b9a5a 100644 --- a/hw/i386/x86.c +++ b/hw/i386/x86.c @@ -382,7 +382,6 @@ static void x86_machine_class_init(ObjectClass *oc, void *data) mc->get_default_cpu_node_id = x86_get_default_cpu_node_id; mc->possible_cpu_arch_ids = x86_possible_cpu_arch_ids; mc->kvm_type = x86_kvm_type; - mc->auto_create_sdcard = true; x86mc->save_tsc_khz = true; x86mc->fwcfg_dma_enabled = true; nc->nmi_monitor_handler = x86_nmi; diff --git a/hw/i386/xen/xen-pvh.c b/hw/i386/xen/xen-pvh.c index 6f5b6a2b8f5d..33c102797639 100644 --- a/hw/i386/xen/xen-pvh.c +++ b/hw/i386/xen/xen-pvh.c @@ -82,7 +82,6 @@ static void xen_pvh_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Xen PVH x86 machine"; mc->default_cpu_type = TARGET_DEFAULT_CPU_TYPE; - mc->auto_create_sdcard = true; /* mc->max_cpus holds the MAX value allowed in the -smp cmd-line opts. */ mc->max_cpus = HVM_MAX_VCPUS; diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index a7f1c8acee42..f2aa0a9782e7 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -954,7 +954,6 @@ static void virt_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_VIRTIO; mc->default_boot_order = "c"; mc->no_cdrom = 1; - mc->auto_create_sdcard = true; mc->possible_cpu_arch_ids = virt_possible_cpu_arch_ids; mc->cpu_index_to_instance_props = virt_cpu_index_to_props; mc->get_default_cpu_node_id = virt_get_default_cpu_node_id; diff --git a/hw/m68k/an5206.c b/hw/m68k/an5206.c index 194787051295..d97399b882b9 100644 --- a/hw/m68k/an5206.c +++ b/hw/m68k/an5206.c @@ -99,7 +99,6 @@ static void an5206_machine_init(MachineClass *mc) mc->init = an5206_init; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5206"); mc->default_ram_id = "an5206.ram"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("an5206", an5206_machine_init) diff --git a/hw/m68k/mcf5208.c b/hw/m68k/mcf5208.c index 9a8c551224fe..75cc076f787a 100644 --- a/hw/m68k/mcf5208.c +++ b/hw/m68k/mcf5208.c @@ -398,7 +398,6 @@ static void mcf5208evb_machine_init(MachineClass *mc) mc->is_default = true; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5208"); mc->default_ram_id = "mcf5208.ram"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("mcf5208evb", mcf5208evb_machine_init) diff --git a/hw/m68k/next-cube.c b/hw/m68k/next-cube.c index ee06a5e2e07c..0570e4a76f1b 100644 --- a/hw/m68k/next-cube.c +++ b/hw/m68k/next-cube.c @@ -1359,7 +1359,6 @@ static void next_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_id = "next.ram"; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040"); mc->no_cdrom = true; - mc->auto_create_sdcard = true; } static const TypeInfo next_typeinfo = { diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c index f3cb8541b375..aeed4c8ddb8a 100644 --- a/hw/m68k/q800.c +++ b/hw/m68k/q800.c @@ -743,7 +743,6 @@ static void q800_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = 1; mc->block_default_type = IF_SCSI; mc->default_ram_id = "m68k_mac.ram"; - mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); compat_props_add(mc->compat_props, hw_compat_q800, hw_compat_q800_len); diff --git a/hw/m68k/virt.c b/hw/m68k/virt.c index 69e8f53482d5..d967bdd7438e 100644 --- a/hw/m68k/virt.c +++ b/hw/m68k/virt.c @@ -318,7 +318,6 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = 1; mc->no_floppy = 1; mc->no_parallel = 1; - mc->auto_create_sdcard = true; mc->default_ram_id = "m68k_virt.ram"; } diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c index 1e3d55dd3b45..8b44be75a220 100644 --- a/hw/microblaze/petalogix_ml605_mmu.c +++ b/hw/microblaze/petalogix_ml605_mmu.c @@ -220,7 +220,6 @@ static void petalogix_ml605_machine_init(MachineClass *mc) mc->desc = "PetaLogix linux refdesign for xilinx ml605 (little endian)"; #endif mc->init = petalogix_ml605_init; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("petalogix-ml605", petalogix_ml605_machine_init) diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c index 95b1b6483fde..2c0d8c34cd23 100644 --- a/hw/microblaze/petalogix_s3adsp1800_mmu.c +++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c @@ -142,7 +142,6 @@ static void petalogix_s3adsp1800_machine_class_init(ObjectClass *oc, void *data) mc->desc = "PetaLogix linux refdesign for xilinx Spartan 3ADSP1800"; mc->init = petalogix_s3adsp1800_init; mc->is_default = true; - mc->auto_create_sdcard = true; } static const TypeInfo petalogix_s3adsp1800_machine_types[] = { diff --git a/hw/microblaze/xlnx-zynqmp-pmu.c b/hw/microblaze/xlnx-zynqmp-pmu.c index b40d82b396fc..bdbf7328bf44 100644 --- a/hw/microblaze/xlnx-zynqmp-pmu.c +++ b/hw/microblaze/xlnx-zynqmp-pmu.c @@ -188,7 +188,6 @@ static void xlnx_zynqmp_pmu_machine_init(MachineClass *mc) mc->desc = "Xilinx ZynqMP PMU machine (little endian)"; #endif mc->init = xlnx_zynqmp_pmu_init; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("xlnx-zynqmp-pmu", xlnx_zynqmp_pmu_machine_init) diff --git a/hw/mips/boston.c b/hw/mips/boston.c index 66cdad639e8a..364c328032a1 100644 --- a/hw/mips/boston.c +++ b/hw/mips/boston.c @@ -842,7 +842,6 @@ static void boston_mach_class_init(MachineClass *mc) mc->default_ram_id = "boston.ddr"; mc->max_cpus = 16; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("I6400"); - mc->auto_create_sdcard = true; } DEFINE_MACHINE("boston", boston_mach_class_init) diff --git a/hw/mips/fuloong2e.c b/hw/mips/fuloong2e.c index e10b5a277430..646044e27497 100644 --- a/hw/mips/fuloong2e.c +++ b/hw/mips/fuloong2e.c @@ -335,7 +335,6 @@ static void mips_fuloong2e_machine_init(MachineClass *mc) mc->default_ram_size = 256 * MiB; mc->default_ram_id = "fuloong2e.ram"; mc->minimum_page_bits = 14; - mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/mips/jazz.c b/hw/mips/jazz.c index ad4561a51e69..c89610639a9e 100644 --- a/hw/mips/jazz.c +++ b/hw/mips/jazz.c @@ -424,7 +424,6 @@ static void mips_magnum_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("R4000"); mc->default_ram_id = "mips_jazz.ram"; - mc->auto_create_sdcard = true; } static const TypeInfo mips_magnum_type = { @@ -442,7 +441,6 @@ static void mips_pica61_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; mc->default_cpu_type = MIPS_CPU_TYPE_NAME("R4000"); mc->default_ram_id = "mips_jazz.ram"; - mc->auto_create_sdcard = true; } static const TypeInfo mips_pica61_type = { diff --git a/hw/mips/loongson3_virt.c b/hw/mips/loongson3_virt.c index 46c2e1e9de5b..831fddb1bd74 100644 --- a/hw/mips/loongson3_virt.c +++ b/hw/mips/loongson3_virt.c @@ -679,7 +679,6 @@ static void loongson3v_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 1600 * MiB; mc->minimum_page_bits = 14; mc->default_nic = "virtio-net-pci"; - mc->auto_create_sdcard = true; } static const TypeInfo loongson3_machine_types[] = { diff --git a/hw/mips/malta.c b/hw/mips/malta.c index 31ff279b4cbe..8e9cea70b135 100644 --- a/hw/mips/malta.c +++ b/hw/mips/malta.c @@ -1306,7 +1306,6 @@ static void mips_malta_machine_init(MachineClass *mc) mc->default_cpu_type = MIPS_CPU_TYPE_NAME("24Kf"); #endif mc->default_ram_id = "mips_malta.ram"; - mc->auto_create_sdcard = true; compat_props_add(mc->compat_props, malta_compat, malta_compat_len); } diff --git a/hw/mips/mipssim.c b/hw/mips/mipssim.c index ff2b9050700e..c530688e7699 100644 --- a/hw/mips/mipssim.c +++ b/hw/mips/mipssim.c @@ -247,7 +247,6 @@ static void mips_mipssim_machine_init(MachineClass *mc) mc->default_cpu_type = MIPS_CPU_TYPE_NAME("24Kf"); #endif mc->default_ram_id = "mips_mipssim.ram"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("mipssim", mips_mipssim_machine_init) diff --git a/hw/openrisc/openrisc_sim.c b/hw/openrisc/openrisc_sim.c index 5e4686ba7a24..e0da4067ba39 100644 --- a/hw/openrisc/openrisc_sim.c +++ b/hw/openrisc/openrisc_sim.c @@ -368,7 +368,6 @@ static void openrisc_sim_machine_init(ObjectClass *oc, void *data) mc->max_cpus = OR1KSIM_CPUS_MAX; mc->is_default = true; mc->default_cpu_type = OPENRISC_CPU_TYPE_NAME("or1200"); - mc->auto_create_sdcard = true; } static const TypeInfo or1ksim_machine_typeinfo = { diff --git a/hw/openrisc/virt.c b/hw/openrisc/virt.c index 2764e398a982..7b60bf85094c 100644 --- a/hw/openrisc/virt.c +++ b/hw/openrisc/virt.c @@ -554,7 +554,6 @@ static void openrisc_virt_machine_init(ObjectClass *oc, void *data) mc->max_cpus = VIRT_CPUS_MAX; mc->is_default = false; mc->default_cpu_type = OPENRISC_CPU_TYPE_NAME("or1200"); - mc->auto_create_sdcard = true; } static const TypeInfo or1ksim_machine_typeinfo = { diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index 296c30da92f4..b02792221cc5 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -173,7 +173,6 @@ static void amigaone_machine_init(MachineClass *mc) mc->default_display = "std"; mc->default_ram_id = "ram"; mc->default_ram_size = 512 * MiB; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("amigaone", amigaone_machine_init) diff --git a/hw/ppc/e500plat.c b/hw/ppc/e500plat.c index afad4802caec..70a803337332 100644 --- a/hw/ppc/e500plat.c +++ b/hw/ppc/e500plat.c @@ -100,7 +100,6 @@ static void e500plat_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("e500v2_v30"); mc->default_ram_id = "mpc8544ds.ram"; mc->default_nic = "virtio-net-pci"; - mc->auto_create_sdcard = true; machine_class_allow_dynamic_sysbus_dev(mc, TYPE_ETSEC_COMMON); } diff --git a/hw/ppc/mac_newworld.c b/hw/ppc/mac_newworld.c index 869f3f7104bd..cb3dc3ab482d 100644 --- a/hw/ppc/mac_newworld.c +++ b/hw/ppc/mac_newworld.c @@ -580,7 +580,6 @@ static void core99_machine_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "cd"; mc->default_display = "std"; mc->default_nic = "sungem"; - mc->auto_create_sdcard = true; mc->kvm_type = core99_kvm_type; #ifdef TARGET_PPC64 mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("970fx_v3.1"); diff --git a/hw/ppc/mac_oldworld.c b/hw/ppc/mac_oldworld.c index 08e30a4a4e93..0dbcea035c3f 100644 --- a/hw/ppc/mac_oldworld.c +++ b/hw/ppc/mac_oldworld.c @@ -427,7 +427,6 @@ static void heathrow_class_init(ObjectClass *oc, void *data) mc->default_nic = "ne2k_pci"; mc->ignore_boot_device_suffixes = true; mc->default_ram_id = "ppc_heathrow.ram"; - mc->auto_create_sdcard = true; fwc->get_dev_path = heathrow_fw_dev_path; } diff --git a/hw/ppc/mpc8544ds.c b/hw/ppc/mpc8544ds.c index 38bdf4531658..d74af766eeda 100644 --- a/hw/ppc/mpc8544ds.c +++ b/hw/ppc/mpc8544ds.c @@ -62,7 +62,6 @@ static void mpc8544ds_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("e500v2_v30"); mc->default_ram_id = "mpc8544ds.ram"; mc->default_nic = "virtio-net-pci"; - mc->auto_create_sdcard = true; } #define TYPE_MPC8544DS_MACHINE MACHINE_TYPE_NAME("mpc8544ds") diff --git a/hw/ppc/pegasos2.c b/hw/ppc/pegasos2.c index 479dcfe809f9..b057672e829d 100644 --- a/hw/ppc/pegasos2.c +++ b/hw/ppc/pegasos2.c @@ -604,7 +604,6 @@ static void pegasos2_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("7457_v1.2"); mc->default_ram_id = "pegasos2.ram"; mc->default_ram_size = 512 * MiB; - mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); vhc->cpu_in_nested = pegasos2_cpu_in_nested; diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 15fbbf6c157b..11fd477b71be 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -2878,7 +2878,6 @@ static void pnv_machine_class_init(ObjectClass *oc, void *data) /* Pnv provides a AHCI device for storage */ mc->block_default_type = IF_IDE; mc->no_parallel = 1; - mc->auto_create_sdcard = true; mc->default_boot_order = NULL; /* * RAM defaults to less than 2048 for 32-bit hosts, and large diff --git a/hw/ppc/ppc405_boards.c b/hw/ppc/ppc405_boards.c index 8946b5173c5d..969cac345ac8 100644 --- a/hw/ppc/ppc405_boards.c +++ b/hw/ppc/ppc405_boards.c @@ -351,7 +351,6 @@ static void ppc405_machine_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 128 * MiB; mc->default_ram_id = "ppc405.ram"; mc->deprecation_reason = "machine is old and unmaintained"; - mc->auto_create_sdcard = true; } static const TypeInfo ppc405_machine_type = { diff --git a/hw/ppc/ppc440_bamboo.c b/hw/ppc/ppc440_bamboo.c index 081a993ef021..099fda390928 100644 --- a/hw/ppc/ppc440_bamboo.c +++ b/hw/ppc/ppc440_bamboo.c @@ -268,7 +268,6 @@ static void bamboo_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("440epb"); mc->default_ram_id = "ppc4xx.sdram"; mc->default_nic = "e1000"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("bamboo", bamboo_machine_init) diff --git a/hw/ppc/prep.c b/hw/ppc/prep.c index 85bfc2fd4f01..3e68d8e6e209 100644 --- a/hw/ppc/prep.c +++ b/hw/ppc/prep.c @@ -428,7 +428,6 @@ static void ibm_40p_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("604"); mc->default_display = "std"; mc->default_nic = "pcnet"; - mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/ppc/sam460ex.c b/hw/ppc/sam460ex.c index d9c871ef205a..3ecae6a95048 100644 --- a/hw/ppc/sam460ex.c +++ b/hw/ppc/sam460ex.c @@ -524,7 +524,6 @@ static void sam460ex_machine_init(MachineClass *mc) mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("460exb"); mc->default_ram_size = 512 * MiB; mc->default_ram_id = "ppc4xx.sdram"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("sam460ex", sam460ex_machine_init) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 42b07fadd9de..f3a4b4235d43 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -4594,7 +4594,6 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = SPAPR_IRQ_NR_IPIS; mc->no_parallel = 1; - mc->auto_create_sdcard = true; mc->default_boot_order = ""; mc->default_ram_size = 512 * MiB; mc->default_ram_id = "ppc_spapr.ram"; diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c index 22184fb6989b..232381192731 100644 --- a/hw/ppc/virtex_ml507.c +++ b/hw/ppc/virtex_ml507.c @@ -288,7 +288,6 @@ static void virtex_machine_init(MachineClass *mc) mc->init = virtex_init; mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("440-xilinx"); mc->default_ram_id = "ram"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("virtex-ml507", virtex_machine_init) diff --git a/hw/remote/machine.c b/hw/remote/machine.c index fa4a1bb815aa..fdc6c441bbd1 100644 --- a/hw/remote/machine.c +++ b/hw/remote/machine.c @@ -128,7 +128,6 @@ static void remote_machine_class_init(ObjectClass *oc, void *data) mc->init = remote_machine_init; mc->desc = "Experimental remote machine"; - mc->auto_create_sdcard = true; hc->unplug = remote_machine_dev_unplug_cb; diff --git a/hw/rx/rx-gdbsim.c b/hw/rx/rx-gdbsim.c index de3b708bc57c..88c8f12c1019 100644 --- a/hw/rx/rx-gdbsim.c +++ b/hw/rx/rx-gdbsim.c @@ -166,7 +166,6 @@ static void rx_gdbsim_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RX62N_CPU; mc->default_ram_size = 16 * MiB; mc->default_ram_id = "ext-sdram"; - mc->auto_create_sdcard = true; } static void rx62n7_class_init(ObjectClass *oc, void *data) diff --git a/hw/sh4/r2d.c b/hw/sh4/r2d.c index e34deb33dce6..d68c94e82ef8 100644 --- a/hw/sh4/r2d.c +++ b/hw/sh4/r2d.c @@ -383,7 +383,6 @@ static void r2d_machine_init(MachineClass *mc) mc->block_default_type = IF_IDE; mc->default_cpu_type = TYPE_SH7751R_CPU; mc->default_nic = "rtl8139"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("r2d", r2d_machine_init) diff --git a/hw/sparc/leon3.c b/hw/sparc/leon3.c index 3bd244957580..0aeaad3becc9 100644 --- a/hw/sparc/leon3.c +++ b/hw/sparc/leon3.c @@ -440,7 +440,6 @@ static void leon3_generic_machine_init(MachineClass *mc) mc->default_cpu_type = SPARC_CPU_TYPE_NAME("LEON3"); mc->default_ram_id = "leon3.ram"; mc->max_cpus = MAX_CPUS; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("leon3_generic", leon3_generic_machine_init) diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c index d555548a1cad..a48d3622c5a8 100644 --- a/hw/sparc/sun4m.c +++ b/hw/sparc/sun4m.c @@ -1113,7 +1113,6 @@ static void sun4m_machine_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "c"; mc->default_display = "tcx"; mc->default_ram_id = "sun4m.ram"; - mc->auto_create_sdcard = true; } static void ss5_class_init(ObjectClass *oc, void *data) diff --git a/hw/sparc64/niagara.c b/hw/sparc64/niagara.c index 37004b99c4ef..805ba6b1e3dd 100644 --- a/hw/sparc64/niagara.c +++ b/hw/sparc64/niagara.c @@ -167,7 +167,6 @@ static void niagara_class_init(ObjectClass *oc, void *data) mc->default_boot_order = "c"; mc->default_cpu_type = SPARC_CPU_TYPE_NAME("Sun-UltraSparc-T1"); mc->default_ram_id = "sun4v-partition.ram"; - mc->auto_create_sdcard = true; } static const TypeInfo niagara_type = { diff --git a/hw/sparc64/sun4u.c b/hw/sparc64/sun4u.c index 6e9a3c5a2e54..8ab5cf0461f8 100644 --- a/hw/sparc64/sun4u.c +++ b/hw/sparc64/sun4u.c @@ -809,7 +809,6 @@ static void sun4u_class_init(ObjectClass *oc, void *data) mc->default_display = "std"; mc->default_nic = "sunhme"; mc->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); - mc->auto_create_sdcard = true; fwc->get_dev_path = sun4u_fw_dev_path; compat_props_add(mc->compat_props, hw_compat_sparc64, hw_compat_sparc64_len); } @@ -837,7 +836,6 @@ static void sun4v_class_init(ObjectClass *oc, void *data) mc->default_display = "std"; mc->default_nic = "sunhme"; mc->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); - mc->auto_create_sdcard = true; } static const TypeInfo sun4v_type = { diff --git a/hw/tricore/triboard.c b/hw/tricore/triboard.c index d4550507a99c..f5baa8ccbb3e 100644 --- a/hw/tricore/triboard.c +++ b/hw/tricore/triboard.c @@ -73,7 +73,6 @@ static void triboard_machine_tc277d_class_init(ObjectClass *oc, mc->init = triboard_machine_init; mc->desc = "Infineon AURIX TriBoard TC277 (D-Step)"; mc->max_cpus = 1; - mc->auto_create_sdcard = true; amc->soc_name = "tc277d-soc"; }; diff --git a/hw/tricore/tricore_testboard.c b/hw/tricore/tricore_testboard.c index 9299cd5394a0..3facfdfd611a 100644 --- a/hw/tricore/tricore_testboard.c +++ b/hw/tricore/tricore_testboard.c @@ -111,7 +111,6 @@ static void ttb_machine_init(MachineClass *mc) mc->desc = "a minimal TriCore board"; mc->init = tricoreboard_init; mc->default_cpu_type = TRICORE_CPU_TYPE_NAME("tc1796"); - mc->auto_create_sdcard = true; } DEFINE_MACHINE("tricore_testboard", ttb_machine_init) diff --git a/hw/xen/xen-pvh-common.c b/hw/xen/xen-pvh-common.c index 9df50cd5382e..9c21fa858d34 100644 --- a/hw/xen/xen-pvh-common.c +++ b/hw/xen/xen-pvh-common.c @@ -381,7 +381,6 @@ static void xen_pvh_class_init(ObjectClass *oc, void *data) mc->default_machine_opts = "accel=xen"; /* Set to zero to make sure that the real ram size is passed. */ mc->default_ram_size = 0; - mc->auto_create_sdcard = true; } static const TypeInfo xen_pvh_info = { diff --git a/hw/xenpv/xen_machine_pv.c b/hw/xenpv/xen_machine_pv.c index abdc5bc9a310..99c02492ef91 100644 --- a/hw/xenpv/xen_machine_pv.c +++ b/hw/xenpv/xen_machine_pv.c @@ -67,7 +67,6 @@ static void xenpv_machine_init(MachineClass *mc) mc->init = xen_init_pv; mc->max_cpus = 1; mc->default_machine_opts = "accel=xen"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("xenpv", xenpv_machine_init) diff --git a/hw/xtensa/sim.c b/hw/xtensa/sim.c index 989cfd49182e..1cea29c66d43 100644 --- a/hw/xtensa/sim.c +++ b/hw/xtensa/sim.c @@ -125,7 +125,6 @@ static void xtensa_sim_machine_init(MachineClass *mc) mc->max_cpus = 4; mc->no_serial = 1; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("sim", xtensa_sim_machine_init) diff --git a/hw/xtensa/virt.c b/hw/xtensa/virt.c index 0a78ab3a6f97..b08404fc17c6 100644 --- a/hw/xtensa/virt.c +++ b/hw/xtensa/virt.c @@ -122,7 +122,6 @@ static void xtensa_virt_machine_init(MachineClass *mc) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_nic = "virtio-net-pci"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("virt", xtensa_virt_machine_init) diff --git a/hw/xtensa/xtfpga.c b/hw/xtensa/xtfpga.c index e00ae9d2e211..3f3677f1c9a4 100644 --- a/hw/xtensa/xtfpga.c +++ b/hw/xtensa/xtfpga.c @@ -594,7 +594,6 @@ static void xtfpga_lx60_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 64 * MiB; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx60_type = { @@ -612,7 +611,6 @@ static void xtfpga_lx60_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 64 * MiB; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx60_nommu_type = { @@ -630,7 +628,6 @@ static void xtfpga_lx200_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 96 * MiB; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx200_type = { @@ -648,7 +645,6 @@ static void xtfpga_lx200_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 96 * MiB; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_lx200_nommu_type = { @@ -666,7 +662,6 @@ static void xtfpga_ml605_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 512 * MiB - XTFPGA_MMU_RESERVED_MEMORY_SIZE; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_ml605_type = { @@ -684,7 +679,6 @@ static void xtfpga_ml605_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 256 * MiB; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_ml605_nommu_type = { @@ -702,7 +696,6 @@ static void xtfpga_kc705_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_TYPE; mc->default_ram_size = 1 * GiB - XTFPGA_MMU_RESERVED_MEMORY_SIZE; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_kc705_type = { @@ -720,7 +713,6 @@ static void xtfpga_kc705_nommu_class_init(ObjectClass *oc, void *data) mc->max_cpus = 32; mc->default_cpu_type = XTENSA_DEFAULT_CPU_NOMMU_TYPE; mc->default_ram_size = 256 * MiB; - mc->auto_create_sdcard = true; } static const TypeInfo xtfpga_kc705_nommu_type = { diff --git a/tests/qemu-iotests/172.out b/tests/qemu-iotests/172.out index 07eebf358368..146fc7238835 100644 --- a/tests/qemu-iotests/172.out +++ b/tests/qemu-iotests/172.out @@ -68,9 +68,6 @@ floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -125,9 +122,6 @@ ide1-cd0: [not inserted] floppy0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -183,9 +177,6 @@ floppy1 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -265,9 +256,6 @@ floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -322,9 +310,6 @@ ide1-cd0: [not inserted] floppy0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -380,9 +365,6 @@ floppy1 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -422,9 +404,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -461,9 +440,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -519,9 +495,6 @@ none1 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -586,9 +559,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -644,9 +614,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -702,9 +669,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -760,9 +724,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -827,9 +788,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -885,9 +843,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2.2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -930,9 +885,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -1106,9 +1058,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -1145,9 +1094,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -1187,9 +1133,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit @@ -1226,9 +1169,6 @@ none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) ide1-cd0: [not inserted] Attached to: /machine/unattached/device[N] Removable device: not locked, tray closed - -sd0: [not inserted] - Removable device: not locked, tray closed (qemu) quit From f208970a06e2af65c351dd6e12df4ca4f382acdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Feb 2025 16:59:18 +0100 Subject: [PATCH 0087/1179] hw/arm: Remove all invalid uses of auto_create_sdcard=true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MachineClass::auto_create_sdcard is only useful to automatically create a SD card, attach a IF_SD block drive to it and plug the card onto a SD bus. None of the ARM machines modified by this commit try to use the IF_SD interface. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250204200934.65279-6-philmd@linaro.org> --- hw/arm/aspeed.c | 1 - hw/arm/b-l475e-iot01a.c | 1 - hw/arm/collie.c | 1 - hw/arm/digic_boards.c | 1 - hw/arm/highbank.c | 2 -- hw/arm/kzm.c | 1 - hw/arm/microbit.c | 1 - hw/arm/mps2-tz.c | 4 ---- hw/arm/mps2.c | 4 ---- hw/arm/mps3r.c | 1 - hw/arm/msf2-som.c | 1 - hw/arm/musca.c | 2 -- hw/arm/musicpal.c | 1 - hw/arm/netduino2.c | 1 - hw/arm/netduinoplus2.c | 1 - hw/arm/olimex-stm32-h405.c | 1 - hw/arm/sbsa-ref.c | 1 - hw/arm/stellaris.c | 1 - hw/arm/stm32vldiscovery.c | 1 - hw/arm/virt.c | 1 - hw/arm/xen-pvh.c | 1 - 21 files changed, 29 deletions(-) diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index f3ba90896c01..98bf071139b9 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -1658,7 +1658,6 @@ static void aspeed_minibmc_machine_ast1030_evb_class_init(ObjectClass *oc, amc->spi_model = "w25q256"; amc->num_cs = 2; amc->macs_mask = 0; - mc->auto_create_sdcard = true; aspeed_machine_class_init_cpus_defaults(mc); } diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c index f05ee0fee0f3..c9a5209216ca 100644 --- a/hw/arm/b-l475e-iot01a.c +++ b/hw/arm/b-l475e-iot01a.c @@ -120,7 +120,6 @@ static void bl475e_machine_init(ObjectClass *oc, void *data) mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)"; mc->init = bl475e_init; mc->valid_cpu_types = machine_valid_cpu_types; - mc->auto_create_sdcard = true; /* SRAM pre-allocated as part of the SoC instantiation */ mc->default_ram_size = 0; diff --git a/hw/arm/collie.c b/hw/arm/collie.c index 864c66193b1f..eaa5c52d45a6 100644 --- a/hw/arm/collie.c +++ b/hw/arm/collie.c @@ -79,7 +79,6 @@ static void collie_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("sa1110"); mc->default_ram_size = RAM_SIZE; mc->default_ram_id = "strongarm.sdram"; - mc->auto_create_sdcard = true; } static const TypeInfo collie_machine_typeinfo = { diff --git a/hw/arm/digic_boards.c b/hw/arm/digic_boards.c index f334c1fb02cc..2492fafeb852 100644 --- a/hw/arm/digic_boards.c +++ b/hw/arm/digic_boards.c @@ -143,7 +143,6 @@ static void canon_a1100_machine_init(MachineClass *mc) mc->ignore_memory_transaction_failures = true; mc->default_ram_size = 64 * MiB; mc->default_ram_id = "ram"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("canon-a1100", canon_a1100_machine_init) diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c index 975fd7a094af..495704d97265 100644 --- a/hw/arm/highbank.c +++ b/hw/arm/highbank.c @@ -357,7 +357,6 @@ static void highbank_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "highbank.dram"; - mc->auto_create_sdcard = true; } static const TypeInfo highbank_type = { @@ -382,7 +381,6 @@ static void midway_class_init(ObjectClass *oc, void *data) mc->max_cpus = 4; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "highbank.dram"; - mc->auto_create_sdcard = true; } static const TypeInfo midway_type = { diff --git a/hw/arm/kzm.c b/hw/arm/kzm.c index 736eabab664f..08d2b3025cf8 100644 --- a/hw/arm/kzm.c +++ b/hw/arm/kzm.c @@ -137,7 +137,6 @@ static void kzm_machine_init(MachineClass *mc) mc->init = kzm_init; mc->ignore_memory_transaction_failures = true; mc->default_ram_id = "kzm.ram"; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("kzm", kzm_machine_init) diff --git a/hw/arm/microbit.c b/hw/arm/microbit.c index fb099508324d..3f56fb45ce18 100644 --- a/hw/arm/microbit.c +++ b/hw/arm/microbit.c @@ -67,7 +67,6 @@ static void microbit_machine_class_init(ObjectClass *oc, void *data) mc->desc = "BBC micro:bit (Cortex-M0)"; mc->init = microbit_init; mc->max_cpus = 1; - mc->auto_create_sdcard = true; } static const TypeInfo microbit_info = { diff --git a/hw/arm/mps2-tz.c b/hw/arm/mps2-tz.c index 91b8ae6d3846..13ed868b6b92 100644 --- a/hw/arm/mps2-tz.c +++ b/hw/arm/mps2-tz.c @@ -1320,7 +1320,6 @@ static void mps2tz_an505_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN505; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41045050; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1355,7 +1354,6 @@ static void mps2tz_an521_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN521; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41045210; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1390,7 +1388,6 @@ static void mps3tz_an524_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN524; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41045240; mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ mmc->apb_periph_frq = mmc->sysclk_frq; @@ -1430,7 +1427,6 @@ static void mps3tz_an547_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN547; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m55"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41055470; mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ mmc->apb_periph_frq = 25 * 1000 * 1000; /* 25MHz */ diff --git a/hw/arm/mps2.c b/hw/arm/mps2.c index 40eb5d161874..3f8db0cab604 100644 --- a/hw/arm/mps2.c +++ b/hw/arm/mps2.c @@ -487,7 +487,6 @@ static void mps2_an385_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN385; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41043850; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; @@ -507,7 +506,6 @@ static void mps2_an386_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN386; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m4"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41043860; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; @@ -527,7 +525,6 @@ static void mps2_an500_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN500; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m7"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41045000; mmc->psram_base = 0x60000000; mmc->ethernet_base = 0xa0000000; @@ -547,7 +544,6 @@ static void mps2_an511_class_init(ObjectClass *oc, void *data) mmc->fpga_type = FPGA_AN511; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->scc_id = 0x41045110; mmc->psram_base = 0x21000000; mmc->ethernet_base = 0x40200000; diff --git a/hw/arm/mps3r.c b/hw/arm/mps3r.c index f26d1cfb2c49..1bddb5e822f7 100644 --- a/hw/arm/mps3r.c +++ b/hw/arm/mps3r.c @@ -618,7 +618,6 @@ static void mps3r_an536_class_init(ObjectClass *oc, void *data) mc->max_cpus = 2; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-r52"); mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; mmc->raminfo = an536_raminfo; mps3r_set_default_ram_info(mmc); } diff --git a/hw/arm/msf2-som.c b/hw/arm/msf2-som.c index 6d3f0a89e0b8..9b20f1e2c988 100644 --- a/hw/arm/msf2-som.c +++ b/hw/arm/msf2-som.c @@ -106,7 +106,6 @@ static void emcraft_sf2_machine_init(MachineClass *mc) mc->desc = "SmartFusion2 SOM kit from Emcraft (M2S010)"; mc->init = emcraft_sf2_s2s010_init; mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("emcraft-sf2", emcraft_sf2_machine_init) diff --git a/hw/arm/musca.c b/hw/arm/musca.c index 6f19b7d58a07..e9c092abc3d4 100644 --- a/hw/arm/musca.c +++ b/hw/arm/musca.c @@ -615,7 +615,6 @@ static void musca_a_class_init(ObjectClass *oc, void *data) MuscaMachineClass *mmc = MUSCA_MACHINE_CLASS(oc); mc->desc = "ARM Musca-A board (dual Cortex-M33)"; - mc->auto_create_sdcard = true; mmc->type = MUSCA_A; mmc->init_svtor = 0x10200000; mmc->sram_addr_width = 15; @@ -630,7 +629,6 @@ static void musca_b1_class_init(ObjectClass *oc, void *data) MuscaMachineClass *mmc = MUSCA_MACHINE_CLASS(oc); mc->desc = "ARM Musca-B1 board (dual Cortex-M33)"; - mc->auto_create_sdcard = true; mmc->type = MUSCA_B1; /* * This matches the DAPlink firmware which boots from QSPI. There diff --git a/hw/arm/musicpal.c b/hw/arm/musicpal.c index fd2975753ee2..48a32c24079f 100644 --- a/hw/arm/musicpal.c +++ b/hw/arm/musicpal.c @@ -1342,7 +1342,6 @@ static void musicpal_machine_init(MachineClass *mc) mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm926"); mc->default_ram_size = MP_RAM_DEFAULT_SIZE; mc->default_ram_id = "musicpal.ram"; - mc->auto_create_sdcard = true; machine_add_audiodev_property(mc); } diff --git a/hw/arm/netduino2.c b/hw/arm/netduino2.c index fca32d459246..df793c77fe1b 100644 --- a/hw/arm/netduino2.c +++ b/hw/arm/netduino2.c @@ -63,7 +63,6 @@ static void netduino2_machine_init(MachineClass *mc) mc->init = netduino2_init; mc->valid_cpu_types = valid_cpu_types; mc->ignore_memory_transaction_failures = true; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("netduino2", netduino2_machine_init) diff --git a/hw/arm/netduinoplus2.c b/hw/arm/netduinoplus2.c index e1a59fb9e238..81b6334cf727 100644 --- a/hw/arm/netduinoplus2.c +++ b/hw/arm/netduinoplus2.c @@ -63,7 +63,6 @@ static void netduinoplus2_machine_init(MachineClass *mc) mc->desc = "Netduino Plus 2 Machine (Cortex-M4)"; mc->init = netduinoplus2_init; mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("netduinoplus2", netduinoplus2_machine_init) diff --git a/hw/arm/olimex-stm32-h405.c b/hw/arm/olimex-stm32-h405.c index 23f686de876a..1f15620f9fda 100644 --- a/hw/arm/olimex-stm32-h405.c +++ b/hw/arm/olimex-stm32-h405.c @@ -66,7 +66,6 @@ static void olimex_stm32_h405_machine_init(MachineClass *mc) mc->desc = "Olimex STM32-H405 (Cortex-M4)"; mc->init = olimex_stm32_h405_init; mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; /* SRAM pre-allocated as part of the SoC instantiation */ mc->default_ram_size = 0; diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c index 02c72a62a37d..e720de306419 100644 --- a/hw/arm/sbsa-ref.c +++ b/hw/arm/sbsa-ref.c @@ -900,7 +900,6 @@ static void sbsa_ref_class_init(ObjectClass *oc, void *data) mc->minimum_page_bits = 12; mc->block_default_type = IF_IDE; mc->no_cdrom = 1; - mc->auto_create_sdcard = true; mc->default_nic = "e1000e"; mc->default_ram_size = 1 * GiB; mc->default_ram_id = "sbsa-ref.ram"; diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c index 25283fd6233a..33611113602d 100644 --- a/hw/arm/stellaris.c +++ b/hw/arm/stellaris.c @@ -1421,7 +1421,6 @@ static void lm3s811evb_class_init(ObjectClass *oc, void *data) mc->init = lm3s811evb_init; mc->ignore_memory_transaction_failures = true; mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); - mc->auto_create_sdcard = true; } static const TypeInfo lm3s811evb_type = { diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c index a71da292b843..e6c1f5b8d7d9 100644 --- a/hw/arm/stm32vldiscovery.c +++ b/hw/arm/stm32vldiscovery.c @@ -66,7 +66,6 @@ static void stm32vldiscovery_machine_init(MachineClass *mc) mc->desc = "ST STM32VLDISCOVERY (Cortex-M3)"; mc->init = stm32vldiscovery_init; mc->valid_cpu_types = valid_cpu_types; - mc->auto_create_sdcard = true; } DEFINE_MACHINE("stm32vldiscovery", stm32vldiscovery_machine_init) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 9aea06b707fa..4a5a9666e916 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -3125,7 +3125,6 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) #endif mc->block_default_type = IF_VIRTIO; mc->no_cdrom = 1; - mc->auto_create_sdcard = true; mc->pci_allow_0_address = true; /* We know we will never create a pre-ARMv7 CPU which needs 1K pages */ mc->minimum_page_bits = 12; diff --git a/hw/arm/xen-pvh.c b/hw/arm/xen-pvh.c index ce4cc4fce93f..d1509bd235df 100644 --- a/hw/arm/xen-pvh.c +++ b/hw/arm/xen-pvh.c @@ -75,7 +75,6 @@ static void xen_arm_machine_class_init(ObjectClass *oc, void *data) * mc->max_cpus, QEMU will bail out with an error message. */ mc->max_cpus = GUEST_MAX_VCPUS; - mc->auto_create_sdcard = true; /* Xen/ARM does not use buffered IOREQs. */ xpc->handle_bufioreq = HVM_IOREQSRV_BUFIOREQ_OFF; From b5d5edc1d9564b5156230860e494e1279099587d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Feb 2025 16:47:50 +0100 Subject: [PATCH 0088/1179] hw/riscv: Remove all invalid uses of auto_create_sdcard=true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MachineClass::auto_create_sdcard is only useful to automatically create a SD card, attach a IF_SD block drive to it and plug the card onto a SD bus. None of the RISCV machines modified by this commit try to use the IF_SD interface. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250204200934.65279-7-philmd@linaro.org> --- hw/riscv/opentitan.c | 1 - hw/riscv/shakti_c.c | 1 - hw/riscv/sifive_e.c | 1 - hw/riscv/spike.c | 1 - hw/riscv/virt.c | 1 - 5 files changed, 5 deletions(-) diff --git a/hw/riscv/opentitan.c b/hw/riscv/opentitan.c index d78a96c53542..b9e56235d87b 100644 --- a/hw/riscv/opentitan.c +++ b/hw/riscv/opentitan.c @@ -121,7 +121,6 @@ static void opentitan_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_IBEX; mc->default_ram_id = "riscv.lowrisc.ibex.ram"; mc->default_ram_size = ibex_memmap[IBEX_DEV_RAM].size; - mc->auto_create_sdcard = true; } static void lowrisc_ibex_soc_init(Object *obj) diff --git a/hw/riscv/shakti_c.c b/hw/riscv/shakti_c.c index efe814b5868a..e2242b97d0c0 100644 --- a/hw/riscv/shakti_c.c +++ b/hw/riscv/shakti_c.c @@ -84,7 +84,6 @@ static void shakti_c_machine_class_init(ObjectClass *klass, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_SHAKTI_C; mc->valid_cpu_types = valid_cpu_types; mc->default_ram_id = "riscv.shakti.c.ram"; - mc->auto_create_sdcard = true; } static const TypeInfo shakti_c_machine_type_info = { diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c index 164eb3ab83b1..73d3b74281c3 100644 --- a/hw/riscv/sifive_e.c +++ b/hw/riscv/sifive_e.c @@ -153,7 +153,6 @@ static void sifive_e_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = SIFIVE_E_CPU; mc->default_ram_id = "riscv.sifive.e.ram"; mc->default_ram_size = sifive_e_memmap[SIFIVE_E_DEV_DTIM].size; - mc->auto_create_sdcard = true; object_class_property_add_bool(oc, "revb", sifive_e_machine_get_revb, sifive_e_machine_set_revb); diff --git a/hw/riscv/spike.c b/hw/riscv/spike.c index 1ea35937e154..74a20016f149 100644 --- a/hw/riscv/spike.c +++ b/hw/riscv/spike.c @@ -358,7 +358,6 @@ static void spike_machine_class_init(ObjectClass *oc, void *data) /* platform instead of architectural choice */ mc->cpu_cluster_has_numa_boundary = true; mc->default_ram_id = "riscv.spike.ram"; - mc->auto_create_sdcard = true; object_class_property_add_str(oc, "signature", NULL, spike_set_signature); object_class_property_set_description(oc, "signature", "File to write ACT test signature"); diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 2aa420f6e55b..241389d72f8a 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -1918,7 +1918,6 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = TYPE_RISCV_CPU_BASE; mc->block_default_type = IF_VIRTIO; mc->no_cdrom = 1; - mc->auto_create_sdcard = true; mc->pci_allow_0_address = true; mc->possible_cpu_arch_ids = riscv_numa_possible_cpu_arch_ids; mc->cpu_index_to_instance_props = riscv_numa_cpu_index_to_props; From 2be22bc72d18d92ca9e0c16e56bf309839360f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Feb 2025 14:42:01 +0100 Subject: [PATCH 0089/1179] hw/boards: Ensure machine setting auto_create_sdcard expose a SD Bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using the auto_create_sdcard feature without SD Bus is irrelevant. Reviewed-by: Thomas Huth Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20250204200934.65279-8-philmd@linaro.org> --- system/vl.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/system/vl.c b/system/vl.c index f9a0526d5fbe..2a570ed9ff4f 100644 --- a/system/vl.c +++ b/system/vl.c @@ -53,6 +53,7 @@ #include "hw/usb.h" #include "hw/isa/isa.h" #include "hw/scsi/scsi.h" +#include "hw/sd/sd.h" #include "hw/display/vga.h" #include "hw/firmware/smbios.h" #include "hw/acpi/acpi.h" @@ -2661,12 +2662,27 @@ static void qemu_init_displays(void) static void qemu_init_board(void) { + MachineClass *machine_class = MACHINE_GET_CLASS(current_machine); + /* process plugin before CPUs are created, but once -smp has been parsed */ qemu_plugin_load_list(&plugin_list, &error_fatal); /* From here on we enter MACHINE_PHASE_INITIALIZED. */ machine_run_board_init(current_machine, mem_path, &error_fatal); + if (machine_class->auto_create_sdcard) { + bool ambigous; + + /* Ensure there is a SD bus available to create SD card on */ + Object *obj = object_resolve_path_type("", TYPE_SD_BUS, &ambigous); + if (!obj && !ambigous) { + fprintf(stderr, "Can not create sd-card on '%s' machine" + " because it lacks a sd-bus\n", + machine_class->name); + abort(); + } + } + drive_check_orphaned(); realtime_init(); From bb741c4f405cf5ade6d2cf3cbbf776a6f26016dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 6 Feb 2025 16:24:25 +0100 Subject: [PATCH 0090/1179] hw/riscv/opentitan: Include missing 'exec/address-spaces.h' header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit opentitan_machine_init() calls get_system_memory(), which is declared in "exec/address-spaces.h". Include it in order to avoid when refactoring unrelated headers: hw/riscv/opentitan.c:83:29: error: call to undeclared function 'get_system_memory' 83 | MemoryRegion *sys_mem = get_system_memory(); | ^ Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alistair Francis Message-Id: <20250206181827.41557-4-philmd@linaro.org> --- hw/riscv/opentitan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/riscv/opentitan.c b/hw/riscv/opentitan.c index b9e56235d87b..98a67fe52a88 100644 --- a/hw/riscv/opentitan.c +++ b/hw/riscv/opentitan.c @@ -28,6 +28,7 @@ #include "hw/riscv/boot.h" #include "qemu/units.h" #include "system/system.h" +#include "exec/address-spaces.h" /* * This version of the OpenTitan machine currently supports From 937df81af6757638a7f1908747560dd342947213 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Feb 2025 15:11:57 +0000 Subject: [PATCH 0091/1179] hw/net/smc91c111: Ignore attempt to pop from empty RX fifo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SMC91C111 includes an MMU Command register which permits the guest to remove entries from the RX FIFO. The datasheet does not specify what happens if the guest tries to do this when the FIFO is already empty; there are no status registers containing error bits which might be applicable. Currently we don't guard at all against pop of an empty RX FIFO, with the result that we allow the guest to drive the rx_fifo_len index to negative values, which will cause smc91c111_receive() to write to the rx_fifo[] array out of bounds when we receive the next packet. Instead ignore attempts to pop an empty RX FIFO. Cc: qemu-stable@nongnu.org Fixes: 80337b66a8e7 ("NIC emulation for qemu arm-softmmu") Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2780 Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250207151157.3151776-1-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/net/smc91c111.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c index b18d5c23c39a..0e13dfa18b28 100644 --- a/hw/net/smc91c111.c +++ b/hw/net/smc91c111.c @@ -182,6 +182,15 @@ static void smc91c111_pop_rx_fifo(smc91c111_state *s) { int i; + if (s->rx_fifo_len == 0) { + /* + * The datasheet doesn't document what the behaviour is if the + * guest tries to pop an empty RX FIFO, and there's no obvious + * error status register to report it. Just ignore the attempt. + */ + return; + } + s->rx_fifo_len--; if (s->rx_fifo_len) { for (i = 0; i < s->rx_fifo_len; i++) From a029fe842f39af07ff5a203a34ebd5243df0e396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 25 Sep 2024 22:16:12 +0200 Subject: [PATCH 0092/1179] tests/functional: Explicit endianness of microblaze assets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The archive used in test_microblaze_s3adsp1800.py (testing a big-endian target) contains a big-endian kernel. Rename using the _BE suffix. Similarly, the archive in test_microblazeel_s3adsp1800 (testing a little-endian target) contains a little-endian kernel. Rename using _LE suffix. These changes will help when adding cross-endian kernel tests. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Thomas Huth Message-Id: <20250206131052.30207-13-philmd@linaro.org> --- tests/functional/test_microblaze_s3adsp1800.py | 6 +++--- tests/functional/test_microblazeel_s3adsp1800.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional/test_microblaze_s3adsp1800.py b/tests/functional/test_microblaze_s3adsp1800.py index 2c4464bd05a8..fac364b1ea91 100755 --- a/tests/functional/test_microblaze_s3adsp1800.py +++ b/tests/functional/test_microblaze_s3adsp1800.py @@ -15,14 +15,14 @@ class MicroblazeMachine(QemuSystemTest): timeout = 90 - ASSET_IMAGE = Asset( + ASSET_IMAGE_BE = Asset( ('https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/' 'day17.tar.xz'), '3ba7439dfbea7af4876662c97f8e1f0cdad9231fc166e4861d17042489270057') - def test_microblaze_s3adsp1800(self): + def test_microblaze_s3adsp1800_be(self): self.set_machine('petalogix-s3adsp1800') - self.archive_extract(self.ASSET_IMAGE) + self.archive_extract(self.ASSET_IMAGE_BE) self.vm.set_console() self.vm.add_args('-kernel', self.scratch_file('day17', 'ballerina.bin')) diff --git a/tests/functional/test_microblazeel_s3adsp1800.py b/tests/functional/test_microblazeel_s3adsp1800.py index c382afe6bfa5..5d353dba5d2c 100755 --- a/tests/functional/test_microblazeel_s3adsp1800.py +++ b/tests/functional/test_microblazeel_s3adsp1800.py @@ -17,14 +17,14 @@ class MicroblazeelMachine(QemuSystemTest): timeout = 90 - ASSET_IMAGE = Asset( + ASSET_IMAGE_LE = Asset( ('http://www.qemu-advent-calendar.org/2023/download/day13.tar.gz'), 'b9b3d43c5dd79db88ada495cc6e0d1f591153fe41355e925d791fbf44de50c22') - def test_microblazeel_s3adsp1800(self): + def test_microblazeel_s3adsp1800_le(self): self.require_netdev('user') self.set_machine('petalogix-s3adsp1800') - self.archive_extract(self.ASSET_IMAGE) + self.archive_extract(self.ASSET_IMAGE_LE) self.vm.set_console() self.vm.add_args('-kernel', self.scratch_file('day13', 'xmaton.bin')) tftproot = self.scratch_file('day13') From fe52b090c03bfb93a76883b8234fd42e975cb930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 6 Feb 2025 11:47:13 +0100 Subject: [PATCH 0093/1179] tests/functional: Allow microblaze tests to take a machine name argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make microblaze tests a bit more generic. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250206131052.30207-14-philmd@linaro.org> --- tests/functional/test_microblaze_s3adsp1800.py | 7 +++++-- tests/functional/test_microblazeel_s3adsp1800.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/functional/test_microblaze_s3adsp1800.py b/tests/functional/test_microblaze_s3adsp1800.py index fac364b1ea91..c4226f49cf33 100755 --- a/tests/functional/test_microblaze_s3adsp1800.py +++ b/tests/functional/test_microblaze_s3adsp1800.py @@ -20,8 +20,8 @@ class MicroblazeMachine(QemuSystemTest): 'day17.tar.xz'), '3ba7439dfbea7af4876662c97f8e1f0cdad9231fc166e4861d17042489270057') - def test_microblaze_s3adsp1800_be(self): - self.set_machine('petalogix-s3adsp1800') + def do_ballerina_be_test(self, machine): + self.set_machine(machine) self.archive_extract(self.ASSET_IMAGE_BE) self.vm.set_console() self.vm.add_args('-kernel', @@ -34,5 +34,8 @@ def test_microblaze_s3adsp1800_be(self): # message, that's why we don't test for a later string here. This # needs some investigation by a microblaze wizard one day... + def test_microblaze_s3adsp1800_legacy_be(self): + self.do_ballerina_be_test('petalogix-s3adsp1800') + if __name__ == '__main__': QemuSystemTest.main() diff --git a/tests/functional/test_microblazeel_s3adsp1800.py b/tests/functional/test_microblazeel_s3adsp1800.py index 5d353dba5d2c..715ef3f79ac6 100755 --- a/tests/functional/test_microblazeel_s3adsp1800.py +++ b/tests/functional/test_microblazeel_s3adsp1800.py @@ -21,9 +21,9 @@ class MicroblazeelMachine(QemuSystemTest): ('http://www.qemu-advent-calendar.org/2023/download/day13.tar.gz'), 'b9b3d43c5dd79db88ada495cc6e0d1f591153fe41355e925d791fbf44de50c22') - def test_microblazeel_s3adsp1800_le(self): + def do_xmaton_le_test(self, machine): self.require_netdev('user') - self.set_machine('petalogix-s3adsp1800') + self.set_machine(machine) self.archive_extract(self.ASSET_IMAGE_LE) self.vm.set_console() self.vm.add_args('-kernel', self.scratch_file('day13', 'xmaton.bin')) @@ -38,5 +38,8 @@ def test_microblazeel_s3adsp1800_le(self): 'tftp -g -r xmaton.png 10.0.2.2 ; md5sum xmaton.png', '821cd3cab8efd16ad6ee5acc3642a8ea') + def test_microblaze_s3adsp1800_legacy_le(self): + self.do_xmaton_le_test('petalogix-s3adsp1800') + if __name__ == '__main__': QemuSystemTest.main() From 94dbecb994a38cafa057e5bfa515cef6faadcea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 6 Feb 2025 13:58:38 +0100 Subject: [PATCH 0094/1179] tests/functional: Remove sleep() kludges from microblaze tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit f0ec14c78c4 ("tests/avocado: Fix console data loss") fixed QEMUMachine's problem with console, we don't need to use the sleep() kludges. Suggested-by: Thomas Huth Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250206131052.30207-15-philmd@linaro.org> --- tests/functional/test_microblazeel_s3adsp1800.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/functional/test_microblazeel_s3adsp1800.py b/tests/functional/test_microblazeel_s3adsp1800.py index 715ef3f79ac6..60aab4a45e83 100755 --- a/tests/functional/test_microblazeel_s3adsp1800.py +++ b/tests/functional/test_microblazeel_s3adsp1800.py @@ -7,8 +7,7 @@ # This work is licensed under the terms of the GNU GPL, version 2 or # later. See the COPYING file in the top-level directory. -import time -from qemu_test import exec_command, exec_command_and_wait_for_pattern +from qemu_test import exec_command_and_wait_for_pattern from qemu_test import QemuSystemTest, Asset from qemu_test import wait_for_console_pattern @@ -31,9 +30,8 @@ def do_xmaton_le_test(self, machine): self.vm.add_args('-nic', f'user,tftp={tftproot}') self.vm.launch() wait_for_console_pattern(self, 'QEMU Advent Calendar 2023') - time.sleep(0.1) - exec_command(self, 'root') - time.sleep(0.1) + wait_for_console_pattern(self, 'buildroot login:') + exec_command_and_wait_for_pattern(self, 'root', '#') exec_command_and_wait_for_pattern(self, 'tftp -g -r xmaton.png 10.0.2.2 ; md5sum xmaton.png', '821cd3cab8efd16ad6ee5acc3642a8ea') From d31f1185fb029b44c439a6961a6cb087df6567d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 10 Feb 2025 09:49:30 +0100 Subject: [PATCH 0095/1179] hw: Declare various const data as 'const' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210133134.90879-7-philmd@linaro.org> --- hw/isa/vt82c686.c | 2 +- hw/rtc/m48t59-isa.c | 2 +- hw/rtc/m48t59.c | 2 +- hw/sensor/tmp421.c | 2 +- hw/usb/hcd-ehci-pci.c | 2 +- hw/usb/hcd-uhci.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/isa/vt82c686.c b/hw/isa/vt82c686.c index 6f44b381a5f7..43bd67eeef25 100644 --- a/hw/isa/vt82c686.c +++ b/hw/isa/vt82c686.c @@ -224,7 +224,7 @@ static void via_pm_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - ViaPMInitInfo *info = data; + const ViaPMInitInfo *info = data; k->realize = via_pm_realize; k->config_write = pm_write_config; diff --git a/hw/rtc/m48t59-isa.c b/hw/rtc/m48t59-isa.c index 38bc8dcf100f..9c3855a3ef16 100644 --- a/hw/rtc/m48t59-isa.c +++ b/hw/rtc/m48t59-isa.c @@ -129,7 +129,7 @@ static void m48txx_isa_class_init(ObjectClass *klass, void *data) static void m48txx_isa_concrete_class_init(ObjectClass *klass, void *data) { M48txxISADeviceClass *u = M48TXX_ISA_CLASS(klass); - M48txxInfo *info = data; + const M48txxInfo *info = data; u->info = *info; } diff --git a/hw/rtc/m48t59.c b/hw/rtc/m48t59.c index c9bd6f878fe5..3fb2f27d9d16 100644 --- a/hw/rtc/m48t59.c +++ b/hw/rtc/m48t59.c @@ -639,7 +639,7 @@ static void m48txx_sysbus_class_init(ObjectClass *klass, void *data) static void m48txx_sysbus_concrete_class_init(ObjectClass *klass, void *data) { M48txxSysBusDeviceClass *u = M48TXX_SYS_BUS_CLASS(klass); - M48txxInfo *info = data; + const M48txxInfo *info = data; u->info = *info; } diff --git a/hw/sensor/tmp421.c b/hw/sensor/tmp421.c index 82e604279c5a..007f7cd018b7 100644 --- a/hw/sensor/tmp421.c +++ b/hw/sensor/tmp421.c @@ -68,7 +68,7 @@ struct TMP421State { struct TMP421Class { I2CSlaveClass parent_class; - DeviceInfo *dev; + const DeviceInfo *dev; }; #define TYPE_TMP421 "tmp421-generic" diff --git a/hw/usb/hcd-ehci-pci.c b/hw/usb/hcd-ehci-pci.c index d410c38a8a28..e00316721ac2 100644 --- a/hw/usb/hcd-ehci-pci.c +++ b/hw/usb/hcd-ehci-pci.c @@ -182,7 +182,7 @@ static void ehci_data_class_init(ObjectClass *klass, void *data) { PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - EHCIPCIInfo *i = data; + const EHCIPCIInfo *i = data; k->vendor_id = i->vendor_id; k->device_id = i->device_id; diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c index 8528d493d63c..0561a6d801ac 100644 --- a/hw/usb/hcd-uhci.c +++ b/hw/usb/hcd-uhci.c @@ -1289,7 +1289,7 @@ void uhci_data_class_init(ObjectClass *klass, void *data) PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); UHCIPCIDeviceClass *u = UHCI_CLASS(klass); - UHCIInfo *info = data; + const UHCIInfo *info = data; k->realize = info->realize ? info->realize : usb_uhci_common_realize; k->exit = info->unplug ? usb_uhci_exit : NULL; From 788369f477a3c89023f5ab19590baee4239623bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 10 Feb 2025 09:49:38 +0100 Subject: [PATCH 0096/1179] hw: Make class data 'const' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the %data argument is not modified, we can declare it const. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210133134.90879-8-philmd@linaro.org> --- hw/sd/sdhci-internal.h | 2 +- hw/sd/sdhci.c | 2 +- hw/sensor/emc141x.c | 2 +- hw/sensor/isl_pmbus_vr.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h index 5f3765f12d2d..9f768c418e08 100644 --- a/hw/sd/sdhci-internal.h +++ b/hw/sd/sdhci-internal.h @@ -322,6 +322,6 @@ void sdhci_initfn(SDHCIState *s); void sdhci_uninitfn(SDHCIState *s); void sdhci_common_realize(SDHCIState *s, Error **errp); void sdhci_common_unrealize(SDHCIState *s); -void sdhci_common_class_init(ObjectClass *klass, void *data); +void sdhci_common_class_init(ObjectClass *klass, const void *data); #endif diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 99dd4a4e9528..1f45a77566c0 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -1542,7 +1542,7 @@ const VMStateDescription sdhci_vmstate = { }, }; -void sdhci_common_class_init(ObjectClass *klass, void *data) +void sdhci_common_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); diff --git a/hw/sensor/emc141x.c b/hw/sensor/emc141x.c index aeccd2a3c945..33c1bd330fd0 100644 --- a/hw/sensor/emc141x.c +++ b/hw/sensor/emc141x.c @@ -265,7 +265,7 @@ static void emc141x_initfn(Object *obj) emc141x_set_temperature, NULL, NULL); } -static void emc141x_class_init(ObjectClass *klass, void *data) +static void emc141x_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); diff --git a/hw/sensor/isl_pmbus_vr.c b/hw/sensor/isl_pmbus_vr.c index 304a66ea8b04..c60282cfe771 100644 --- a/hw/sensor/isl_pmbus_vr.c +++ b/hw/sensor/isl_pmbus_vr.c @@ -233,7 +233,7 @@ static void raa228000_init(Object *obj) isl_pmbus_vr_add_props(obj, flags, 1); } -static void isl_pmbus_vr_class_init(ObjectClass *klass, void *data, +static void isl_pmbus_vr_class_init(ObjectClass *klass, const void *data, uint8_t pages) { PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass); From 4ec96630f93ec2a1fd8bf9c9150cdae330531de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 10:56:26 +0100 Subject: [PATCH 0097/1179] hw/qdev-properties-system: Introduce EndianMode QAPI enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the EndianMode type and the DEFINE_PROP_ENDIAN() macros. Endianness can be BIG, LITTLE or unspecified (default). Reviewed-by: Thomas Huth Acked-by: Markus Armbruster Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250213122217.62654-2-philmd@linaro.org> --- hw/core/qdev-properties-system.c | 11 +++++++++++ include/hw/qdev-properties-system.h | 7 +++++++ qapi/common.json | 14 ++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index a96675beb0dc..89f954f569ef 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -1283,3 +1283,14 @@ const PropertyInfo qdev_prop_iothread_vq_mapping_list = { .set = set_iothread_vq_mapping_list, .release = release_iothread_vq_mapping_list, }; + +/* --- Endian modes */ + +const PropertyInfo qdev_prop_endian_mode = { + .name = "EndianMode", + .description = "Endian mode, big/little/unspecified", + .enum_table = &EndianMode_lookup, + .get = qdev_propinfo_get_enum, + .set = qdev_propinfo_set_enum, + .set_default_value = qdev_propinfo_set_default_value_enum, +}; diff --git a/include/hw/qdev-properties-system.h b/include/hw/qdev-properties-system.h index 7ec37f6316c3..ead4dfc2f028 100644 --- a/include/hw/qdev-properties-system.h +++ b/include/hw/qdev-properties-system.h @@ -30,6 +30,7 @@ extern const PropertyInfo qdev_prop_pcie_link_speed; extern const PropertyInfo qdev_prop_pcie_link_width; extern const PropertyInfo qdev_prop_cpus390entitlement; extern const PropertyInfo qdev_prop_iothread_vq_mapping_list; +extern const PropertyInfo qdev_prop_endian_mode; #define DEFINE_PROP_PCI_DEVFN(_n, _s, _f, _d) \ DEFINE_PROP_SIGNED(_n, _s, _f, _d, qdev_prop_pci_devfn, int32_t) @@ -97,4 +98,10 @@ extern const PropertyInfo qdev_prop_iothread_vq_mapping_list; DEFINE_PROP(_name, _state, _field, qdev_prop_iothread_vq_mapping_list, \ IOThreadVirtQueueMappingList *) +#define DEFINE_PROP_ENDIAN(_name, _state, _field, _default) \ + DEFINE_PROP_UNSIGNED(_name, _state, _field, _default, \ + qdev_prop_endian_mode, EndianMode) +#define DEFINE_PROP_ENDIAN_NODEFAULT(_name, _state, _field) \ + DEFINE_PROP_ENDIAN(_name, _state, _field, ENDIAN_MODE_UNSPECIFIED) + #endif diff --git a/qapi/common.json b/qapi/common.json index 6ffc7a378905..0e3a0bbbfb0b 100644 --- a/qapi/common.json +++ b/qapi/common.json @@ -212,3 +212,17 @@ ## { 'struct': 'HumanReadableText', 'data': { 'human-readable-text': 'str' } } + +## +# @EndianMode: +# +# @unspecified: Endianness not specified +# +# @little: Little endianness +# +# @big: Big endianness +# +# Since: 10.0 +## +{ 'enum': 'EndianMode', + 'data': [ 'unspecified', 'little', 'big' ] } From 2cdf693b197db6c6c27ff2bf02fce1c0bb384786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 25 Sep 2024 23:15:04 +0200 Subject: [PATCH 0098/1179] hw/intc/xilinx_intc: Make device endianness configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the DEVICE_NATIVE_ENDIAN MemoryRegionOps by a pair of DEVICE_LITTLE_ENDIAN / DEVICE_BIG_ENDIAN. Add the "endianness" property to select the device endianness. This property is unspecified by default, and machines need to set it explicitly. Set the proper endianness for each machine using the device. Reviewed-by: Thomas Huth Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250213122217.62654-3-philmd@linaro.org> --- hw/intc/xilinx_intc.c | 57 ++++++++++++++++++------ hw/microblaze/petalogix_ml605_mmu.c | 3 ++ hw/microblaze/petalogix_s3adsp1800_mmu.c | 3 ++ hw/ppc/virtex_ml507.c | 1 + hw/riscv/microblaze-v-generic.c | 1 + 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/hw/intc/xilinx_intc.c b/hw/intc/xilinx_intc.c index 6930f83907ac..ab1c4a322217 100644 --- a/hw/intc/xilinx_intc.c +++ b/hw/intc/xilinx_intc.c @@ -3,6 +3,9 @@ * * Copyright (c) 2009 Edgar E. Iglesias. * + * https://docs.amd.com/v/u/en-US/xps_intc + * DS572: LogiCORE IP XPS Interrupt Controller (v2.01a) + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -23,10 +26,12 @@ */ #include "qemu/osdep.h" +#include "qapi/error.h" #include "hw/sysbus.h" #include "qemu/module.h" #include "hw/irq.h" #include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" #include "qom/object.h" #define D(x) @@ -49,6 +54,7 @@ struct XpsIntc { SysBusDevice parent_obj; + EndianMode model_endianness; MemoryRegion mmio; qemu_irq parent_irq; @@ -140,18 +146,28 @@ static void pic_write(void *opaque, hwaddr addr, update_irq(p); } -static const MemoryRegionOps pic_ops = { - .read = pic_read, - .write = pic_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .impl = { - .min_access_size = 4, - .max_access_size = 4, +static const MemoryRegionOps pic_ops[2] = { + [0 ... 1] = { + .read = pic_read, + .write = pic_write, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + /* + * All XPS INTC registers are accessed through the PLB interface. + * The base address for these registers is provided by the + * configuration parameter, C_BASEADDR. Each register is 32 bits + * although some bits may be unused and is accessed on a 4-byte + * boundary offset from the base address. + */ + .min_access_size = 4, + .max_access_size = 4, + }, }, - .valid = { - .min_access_size = 4, - .max_access_size = 4 - } + [0].endianness = DEVICE_LITTLE_ENDIAN, + [1].endianness = DEVICE_BIG_ENDIAN, }; static void irq_handler(void *opaque, int irq, int level) @@ -174,13 +190,27 @@ static void xilinx_intc_init(Object *obj) qdev_init_gpio_in(DEVICE(obj), irq_handler, 32); sysbus_init_irq(SYS_BUS_DEVICE(obj), &p->parent_irq); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio); +} - memory_region_init_io(&p->mmio, obj, &pic_ops, p, "xlnx.xps-intc", +static void xilinx_intc_realize(DeviceState *dev, Error **errp) +{ + XpsIntc *p = XILINX_INTC(dev); + + if (p->model_endianness == ENDIAN_MODE_UNSPECIFIED) { + error_setg(errp, TYPE_XILINX_INTC " property 'endianness'" + " must be set to 'big' or 'little'"); + return; + } + + memory_region_init_io(&p->mmio, OBJECT(dev), + &pic_ops[p->model_endianness == ENDIAN_MODE_BIG], + p, "xlnx.xps-intc", R_MAX * 4); - sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio); } static const Property xilinx_intc_properties[] = { + DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XpsIntc, model_endianness), DEFINE_PROP_UINT32("kind-of-intr", XpsIntc, c_kind_of_intr, 0), }; @@ -188,6 +218,7 @@ static void xilinx_intc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + dc->realize = xilinx_intc_realize; device_class_set_props(dc, xilinx_intc_properties); } diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c index 8b44be75a220..a876aeb0bbad 100644 --- a/hw/microblaze/petalogix_ml605_mmu.c +++ b/hw/microblaze/petalogix_ml605_mmu.c @@ -80,6 +80,8 @@ petalogix_ml605_init(MachineState *machine) MemoryRegion *phys_lmb_bram = g_new(MemoryRegion, 1); MemoryRegion *phys_ram = g_new(MemoryRegion, 1); qemu_irq irq[32]; + EndianMode endianness = TARGET_BIG_ENDIAN ? ENDIAN_MODE_BIG + : ENDIAN_MODE_LITTLE; /* init CPUs */ cpu = MICROBLAZE_CPU(object_new(TYPE_MICROBLAZE_CPU)); @@ -111,6 +113,7 @@ petalogix_ml605_init(MachineState *machine) dev = qdev_new("xlnx.xps-intc"); + qdev_prop_set_enum(dev, "endianness", endianness); qdev_prop_set_uint32(dev, "kind-of-intr", 1 << TIMER_IRQ); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, INTC_BASEADDR); diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c index 2c0d8c34cd23..15cabe117777 100644 --- a/hw/microblaze/petalogix_s3adsp1800_mmu.c +++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c @@ -71,6 +71,8 @@ petalogix_s3adsp1800_init(MachineState *machine) MemoryRegion *phys_ram = g_new(MemoryRegion, 1); qemu_irq irq[32]; MemoryRegion *sysmem = get_system_memory(); + EndianMode endianness = TARGET_BIG_ENDIAN ? ENDIAN_MODE_BIG + : ENDIAN_MODE_LITTLE; cpu = MICROBLAZE_CPU(object_new(TYPE_MICROBLAZE_CPU)); object_property_set_str(OBJECT(cpu), "version", "7.10.d", &error_abort); @@ -95,6 +97,7 @@ petalogix_s3adsp1800_init(MachineState *machine) 64 * KiB, 1, 0x89, 0x18, 0x0000, 0x0, 1); dev = qdev_new("xlnx.xps-intc"); + qdev_prop_set_enum(dev, "endianness", endianness); qdev_prop_set_uint32(dev, "kind-of-intr", 1 << ETHLITE_IRQ | 1 << UARTLITE_IRQ); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c index 232381192731..df8f96448291 100644 --- a/hw/ppc/virtex_ml507.c +++ b/hw/ppc/virtex_ml507.c @@ -217,6 +217,7 @@ static void virtex_init(MachineState *machine) cpu_irq = qdev_get_gpio_in(DEVICE(cpu), PPC40x_INPUT_INT); dev = qdev_new("xlnx.xps-intc"); + qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_BIG); qdev_prop_set_uint32(dev, "kind-of-intr", 0); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, INTC_BASEADDR); diff --git a/hw/riscv/microblaze-v-generic.c b/hw/riscv/microblaze-v-generic.c index 26788a1824aa..ebdd461ae98c 100644 --- a/hw/riscv/microblaze-v-generic.c +++ b/hw/riscv/microblaze-v-generic.c @@ -79,6 +79,7 @@ static void mb_v_generic_init(MachineState *machine) memory_region_add_subregion(sysmem, ddr_base, phys_ram); dev = qdev_new("xlnx.xps-intc"); + qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_LITTLE); qdev_prop_set_uint32(dev, "kind-of-intr", 1 << UARTLITE_IRQ); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); From 644276db5d707eba7dd89cc8550b3639dbd29f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 25 Sep 2024 23:15:58 +0200 Subject: [PATCH 0099/1179] hw/net/xilinx_ethlite: Make device endianness configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the DEVICE_NATIVE_ENDIAN MemoryRegionOps by a pair of DEVICE_LITTLE_ENDIAN / DEVICE_BIG_ENDIAN. Add the "endianness" property to select the device endianness. This property is unspecified by default, and machines need to set it explicitly. Set the proper endianness for each machine using the device. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250213122217.62654-4-philmd@linaro.org> --- hw/microblaze/petalogix_s3adsp1800_mmu.c | 1 + hw/net/xilinx_ethlite.c | 29 +++++++++++++++++++----- hw/riscv/microblaze-v-generic.c | 1 + 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c index 15cabe117777..d419dc49a253 100644 --- a/hw/microblaze/petalogix_s3adsp1800_mmu.c +++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c @@ -123,6 +123,7 @@ petalogix_s3adsp1800_init(MachineState *machine) sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq[TIMER_IRQ]); dev = qdev_new("xlnx.xps-ethernetlite"); + qdev_prop_set_enum(dev, "endianness", endianness); qemu_configure_nic_device(dev, true, NULL); qdev_prop_set_uint32(dev, "tx-ping-pong", 0); qdev_prop_set_uint32(dev, "rx-ping-pong", 0); diff --git a/hw/net/xilinx_ethlite.c b/hw/net/xilinx_ethlite.c index 14bf2b2e17a4..15d9b95aa80c 100644 --- a/hw/net/xilinx_ethlite.c +++ b/hw/net/xilinx_ethlite.c @@ -34,6 +34,7 @@ #include "hw/sysbus.h" #include "hw/irq.h" #include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" #include "hw/misc/unimp.h" #include "net/net.h" #include "trace.h" @@ -85,6 +86,7 @@ struct XlnxXpsEthLite { SysBusDevice parent_obj; + EndianMode model_endianness; MemoryRegion container; qemu_irq irq; NICState *nic; @@ -183,10 +185,10 @@ static void port_tx_write(void *opaque, hwaddr addr, uint64_t value, } } -static const MemoryRegionOps eth_porttx_ops = { +static const MemoryRegionOps eth_porttx_ops[2] = { + [0 ... 1] = { .read = port_tx_read, .write = port_tx_write, - .endianness = DEVICE_NATIVE_ENDIAN, .impl = { .min_access_size = 4, .max_access_size = 4, @@ -195,6 +197,9 @@ static const MemoryRegionOps eth_porttx_ops = { .min_access_size = 4, .max_access_size = 4, }, + }, + [0].endianness = DEVICE_LITTLE_ENDIAN, + [1].endianness = DEVICE_BIG_ENDIAN, }; static uint64_t port_rx_read(void *opaque, hwaddr addr, unsigned int size) @@ -232,10 +237,10 @@ static void port_rx_write(void *opaque, hwaddr addr, uint64_t value, } } -static const MemoryRegionOps eth_portrx_ops = { +static const MemoryRegionOps eth_portrx_ops[2] = { + [0 ... 1] = { .read = port_rx_read, .write = port_rx_write, - .endianness = DEVICE_NATIVE_ENDIAN, .impl = { .min_access_size = 4, .max_access_size = 4, @@ -244,6 +249,9 @@ static const MemoryRegionOps eth_portrx_ops = { .min_access_size = 4, .max_access_size = 4, }, + }, + [0].endianness = DEVICE_LITTLE_ENDIAN, + [1].endianness = DEVICE_BIG_ENDIAN, }; static bool eth_can_rx(NetClientState *nc) @@ -300,6 +308,14 @@ static NetClientInfo net_xilinx_ethlite_info = { static void xilinx_ethlite_realize(DeviceState *dev, Error **errp) { XlnxXpsEthLite *s = XILINX_ETHLITE(dev); + unsigned ops_index; + + if (s->model_endianness == ENDIAN_MODE_UNSPECIFIED) { + error_setg(errp, TYPE_XILINX_ETHLITE " property 'endianness'" + " must be set to 'big' or 'little'"); + return; + } + ops_index = s->model_endianness == ENDIAN_MODE_BIG ? 1 : 0; memory_region_init(&s->container, OBJECT(dev), "xlnx.xps-ethernetlite", 0x2000); @@ -328,7 +344,7 @@ static void xilinx_ethlite_realize(DeviceState *dev, Error **errp) BUFSZ_MAX, &error_abort); memory_region_add_subregion(&s->container, 0x0800 * i, &s->port[i].txbuf); memory_region_init_io(&s->port[i].txio, OBJECT(dev), - ð_porttx_ops, s, + ð_porttx_ops[ops_index], s, i ? "ethlite.tx[1]io" : "ethlite.tx[0]io", 4 * TX_MAX); memory_region_add_subregion(&s->container, i ? A_TX_BASE1 : A_TX_BASE0, @@ -340,7 +356,7 @@ static void xilinx_ethlite_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(&s->container, 0x1000 + 0x0800 * i, &s->port[i].rxbuf); memory_region_init_io(&s->port[i].rxio, OBJECT(dev), - ð_portrx_ops, s, + ð_portrx_ops[ops_index], s, i ? "ethlite.rx[1]io" : "ethlite.rx[0]io", 4 * RX_MAX); memory_region_add_subregion(&s->container, i ? A_RX_BASE1 : A_RX_BASE0, @@ -363,6 +379,7 @@ static void xilinx_ethlite_init(Object *obj) } static const Property xilinx_ethlite_properties[] = { + DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XlnxXpsEthLite, model_endianness), DEFINE_PROP_UINT32("tx-ping-pong", XlnxXpsEthLite, c_tx_pingpong, 1), DEFINE_PROP_UINT32("rx-ping-pong", XlnxXpsEthLite, c_rx_pingpong, 1), DEFINE_NIC_PROPERTIES(XlnxXpsEthLite, conf), diff --git a/hw/riscv/microblaze-v-generic.c b/hw/riscv/microblaze-v-generic.c index ebdd461ae98c..a21fdfbe6dbf 100644 --- a/hw/riscv/microblaze-v-generic.c +++ b/hw/riscv/microblaze-v-generic.c @@ -120,6 +120,7 @@ static void mb_v_generic_init(MachineState *machine) /* Emaclite */ dev = qdev_new("xlnx.xps-ethernetlite"); + qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_LITTLE); qemu_configure_nic_device(dev, true, NULL); qdev_prop_set_uint32(dev, "tx-ping-pong", 0); qdev_prop_set_uint32(dev, "rx-ping-pong", 0); From df1f35ab67e50f572934b7ea705764b77cf6d525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 25 Sep 2024 23:16:21 +0200 Subject: [PATCH 0100/1179] hw/timer/xilinx_timer: Make device endianness configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the DEVICE_NATIVE_ENDIAN MemoryRegionOps by a pair of DEVICE_LITTLE_ENDIAN / DEVICE_BIG_ENDIAN. Add the "endianness" property to select the device endianness. This property is unspecified by default, and machines need to set it explicitly. Set the proper endianness for each machine using the device. Reviewed-by: Thomas Huth Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250213122217.62654-5-philmd@linaro.org> --- hw/microblaze/petalogix_ml605_mmu.c | 1 + hw/microblaze/petalogix_s3adsp1800_mmu.c | 1 + hw/ppc/virtex_ml507.c | 1 + hw/riscv/microblaze-v-generic.c | 2 ++ hw/timer/xilinx_timer.c | 43 +++++++++++++++++------- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c index a876aeb0bbad..984287fdc536 100644 --- a/hw/microblaze/petalogix_ml605_mmu.c +++ b/hw/microblaze/petalogix_ml605_mmu.c @@ -129,6 +129,7 @@ petalogix_ml605_init(MachineState *machine) /* 2 timers at irq 2 @ 100 Mhz. */ dev = qdev_new("xlnx.xps-timer"); + qdev_prop_set_enum(dev, "endianness", endianness); qdev_prop_set_uint32(dev, "one-timer-only", 0); qdev_prop_set_uint32(dev, "clock-frequency", 100 * 1000000); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c index d419dc49a253..caaea222a8c6 100644 --- a/hw/microblaze/petalogix_s3adsp1800_mmu.c +++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c @@ -116,6 +116,7 @@ petalogix_s3adsp1800_init(MachineState *machine) /* 2 timers at irq 2 @ 62 Mhz. */ dev = qdev_new("xlnx.xps-timer"); + qdev_prop_set_enum(dev, "endianness", endianness); qdev_prop_set_uint32(dev, "one-timer-only", 0); qdev_prop_set_uint32(dev, "clock-frequency", 62 * 1000000); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c index df8f96448291..a01354d991d7 100644 --- a/hw/ppc/virtex_ml507.c +++ b/hw/ppc/virtex_ml507.c @@ -231,6 +231,7 @@ static void virtex_init(MachineState *machine) /* 2 timers at irq 2 @ 62 Mhz. */ dev = qdev_new("xlnx.xps-timer"); + qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_BIG); qdev_prop_set_uint32(dev, "one-timer-only", 0); qdev_prop_set_uint32(dev, "clock-frequency", 62 * 1000000); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); diff --git a/hw/riscv/microblaze-v-generic.c b/hw/riscv/microblaze-v-generic.c index a21fdfbe6dbf..3c79f5733b2d 100644 --- a/hw/riscv/microblaze-v-generic.c +++ b/hw/riscv/microblaze-v-generic.c @@ -104,6 +104,7 @@ static void mb_v_generic_init(MachineState *machine) /* 2 timers at irq 0 @ 100 Mhz. */ dev = qdev_new("xlnx.xps-timer"); + qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_LITTLE); qdev_prop_set_uint32(dev, "one-timer-only", 0); qdev_prop_set_uint32(dev, "clock-frequency", 100000000); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); @@ -112,6 +113,7 @@ static void mb_v_generic_init(MachineState *machine) /* 2 timers at irq 3 @ 100 Mhz. */ dev = qdev_new("xlnx.xps-timer"); + qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_LITTLE); qdev_prop_set_uint32(dev, "one-timer-only", 0); qdev_prop_set_uint32(dev, "clock-frequency", 100000000); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); diff --git a/hw/timer/xilinx_timer.c b/hw/timer/xilinx_timer.c index 6595cf5f5174..4620528f9857 100644 --- a/hw/timer/xilinx_timer.c +++ b/hw/timer/xilinx_timer.c @@ -3,6 +3,9 @@ * * Copyright (c) 2009 Edgar E. Iglesias. * + * DS573: https://docs.amd.com/v/u/en-US/xps_timer + * LogiCORE IP XPS Timer/Counter (v1.02a) + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -23,10 +26,12 @@ */ #include "qemu/osdep.h" +#include "qapi/error.h" #include "hw/sysbus.h" #include "hw/irq.h" #include "hw/ptimer.h" #include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" #include "qemu/log.h" #include "qemu/module.h" #include "qom/object.h" @@ -69,6 +74,7 @@ struct XpsTimerState { SysBusDevice parent_obj; + EndianMode model_endianness; MemoryRegion mmio; qemu_irq irq; uint8_t one_timer_only; @@ -189,18 +195,21 @@ timer_write(void *opaque, hwaddr addr, timer_update_irq(t); } -static const MemoryRegionOps timer_ops = { - .read = timer_read, - .write = timer_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .impl = { - .min_access_size = 4, - .max_access_size = 4, +static const MemoryRegionOps timer_ops[2] = { + [0 ... 1] = { + .read = timer_read, + .write = timer_write, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, }, - .valid = { - .min_access_size = 4, - .max_access_size = 4 - } + [0].endianness = DEVICE_LITTLE_ENDIAN, + [1].endianness = DEVICE_BIG_ENDIAN, }; static void timer_hit(void *opaque) @@ -220,6 +229,12 @@ static void xilinx_timer_realize(DeviceState *dev, Error **errp) XpsTimerState *t = XILINX_TIMER(dev); unsigned int i; + if (t->model_endianness == ENDIAN_MODE_UNSPECIFIED) { + error_setg(errp, TYPE_XILINX_TIMER " property 'endianness'" + " must be set to 'big' or 'little'"); + return; + } + /* Init all the ptimers. */ t->timers = g_malloc0(sizeof t->timers[0] * num_timers(t)); for (i = 0; i < num_timers(t); i++) { @@ -233,8 +248,9 @@ static void xilinx_timer_realize(DeviceState *dev, Error **errp) ptimer_transaction_commit(xt->ptimer); } - memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t, "xlnx.xps-timer", - R_MAX * 4 * num_timers(t)); + memory_region_init_io(&t->mmio, OBJECT(t), + &timer_ops[t->model_endianness == ENDIAN_MODE_BIG], + t, "xlnx.xps-timer", R_MAX * 4 * num_timers(t)); sysbus_init_mmio(SYS_BUS_DEVICE(dev), &t->mmio); } @@ -247,6 +263,7 @@ static void xilinx_timer_init(Object *obj) } static const Property xilinx_timer_properties[] = { + DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XpsTimerState, model_endianness), DEFINE_PROP_UINT32("clock-frequency", XpsTimerState, freq_hz, 62 * 1000000), DEFINE_PROP_UINT8("one-timer-only", XpsTimerState, one_timer_only, 0), }; From 8a8c92c8afbb8a153968a72dd4ce504884a3209d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 6 Nov 2024 23:24:04 +0000 Subject: [PATCH 0101/1179] hw/char/xilinx_uartlite: Make device endianness configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the DEVICE_NATIVE_ENDIAN MemoryRegionOps by a pair of DEVICE_LITTLE_ENDIAN / DEVICE_BIG_ENDIAN. Add the "endianness" property to select the device endianness. This property is unspecified by default, and machines need to set it explicitly. Set the proper endianness for each machine using the device. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250213122217.62654-6-philmd@linaro.org> --- hw/char/xilinx_uartlite.c | 34 ++++++++++++++++-------- hw/microblaze/petalogix_s3adsp1800_mmu.c | 1 + hw/riscv/microblaze-v-generic.c | 1 + 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/hw/char/xilinx_uartlite.c b/hw/char/xilinx_uartlite.c index 56955e0d74af..4037c937eebb 100644 --- a/hw/char/xilinx_uartlite.c +++ b/hw/char/xilinx_uartlite.c @@ -24,6 +24,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" +#include "qapi/error.h" #include "hw/char/xilinx_uartlite.h" #include "hw/irq.h" #include "hw/qdev-properties.h" @@ -57,6 +58,7 @@ struct XilinxUARTLite { SysBusDevice parent_obj; + EndianMode model_endianness; MemoryRegion mmio; CharBackend chr; qemu_irq irq; @@ -166,17 +168,21 @@ uart_write(void *opaque, hwaddr addr, uart_update_irq(s); } -static const MemoryRegionOps uart_ops = { - .read = uart_read, - .write = uart_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .valid = { - .min_access_size = 1, - .max_access_size = 4 - } +static const MemoryRegionOps uart_ops[2] = { + [0 ... 1] = { + .read = uart_read, + .write = uart_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + }, + [0].endianness = DEVICE_LITTLE_ENDIAN, + [1].endianness = DEVICE_BIG_ENDIAN, }; static const Property xilinx_uartlite_properties[] = { + DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XilinxUARTLite, model_endianness), DEFINE_PROP_CHR("chardev", XilinxUARTLite, chr), }; @@ -214,6 +220,15 @@ static void xilinx_uartlite_realize(DeviceState *dev, Error **errp) { XilinxUARTLite *s = XILINX_UARTLITE(dev); + if (s->model_endianness == ENDIAN_MODE_UNSPECIFIED) { + error_setg(errp, TYPE_XILINX_UARTLITE " property 'endianness'" + " must be set to 'big' or 'little'"); + return; + } + + memory_region_init_io(&s->mmio, OBJECT(dev), + &uart_ops[s->model_endianness == ENDIAN_MODE_BIG], + s, "xlnx.xps-uartlite", R_MAX * 4); qemu_chr_fe_set_handlers(&s->chr, uart_can_rx, uart_rx, uart_event, NULL, s, NULL, true); } @@ -223,9 +238,6 @@ static void xilinx_uartlite_init(Object *obj) XilinxUARTLite *s = XILINX_UARTLITE(obj); sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); - - memory_region_init_io(&s->mmio, obj, &uart_ops, s, - "xlnx.xps-uartlite", R_MAX * 4); sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); } diff --git a/hw/microblaze/petalogix_s3adsp1800_mmu.c b/hw/microblaze/petalogix_s3adsp1800_mmu.c index caaea222a8c6..bdba2006b722 100644 --- a/hw/microblaze/petalogix_s3adsp1800_mmu.c +++ b/hw/microblaze/petalogix_s3adsp1800_mmu.c @@ -109,6 +109,7 @@ petalogix_s3adsp1800_init(MachineState *machine) } dev = qdev_new(TYPE_XILINX_UARTLITE); + qdev_prop_set_enum(dev, "endianness", endianness); qdev_prop_set_chr(dev, "chardev", serial_hd(0)); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, UARTLITE_BASEADDR); diff --git a/hw/riscv/microblaze-v-generic.c b/hw/riscv/microblaze-v-generic.c index 3c79f5733b2d..d8e67906d267 100644 --- a/hw/riscv/microblaze-v-generic.c +++ b/hw/riscv/microblaze-v-generic.c @@ -92,6 +92,7 @@ static void mb_v_generic_init(MachineState *machine) /* Uartlite */ dev = qdev_new(TYPE_XILINX_UARTLITE); + qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_LITTLE); qdev_prop_set_chr(dev, "chardev", serial_hd(0)); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, UARTLITE_BASEADDR); From e87c93df1134516166ff3d8f9a56e168ff7e1c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 6 Nov 2024 23:24:27 +0000 Subject: [PATCH 0102/1179] hw/ssi/xilinx_spi: Make device endianness configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the DEVICE_NATIVE_ENDIAN MemoryRegionOps by a pair of DEVICE_LITTLE_ENDIAN / DEVICE_BIG_ENDIAN. Add the "endianness" property to select the device endianness. This property is unspecified by default, and machines need to set it explicitly. Set the proper endianness on the single machine using the device. Reviewed-by: Thomas Huth Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250213122217.62654-7-philmd@linaro.org> --- hw/microblaze/petalogix_ml605_mmu.c | 1 + hw/ssi/xilinx_spi.c | 32 +++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c index 984287fdc536..21ad215e442d 100644 --- a/hw/microblaze/petalogix_ml605_mmu.c +++ b/hw/microblaze/petalogix_ml605_mmu.c @@ -177,6 +177,7 @@ petalogix_ml605_init(MachineState *machine) SSIBus *spi; dev = qdev_new("xlnx.xps-spi"); + qdev_prop_set_enum(dev, "endianness", endianness); qdev_prop_set_uint8(dev, "num-ss-bits", NUM_SPI_FLASHES); busdev = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(busdev, &error_fatal); diff --git a/hw/ssi/xilinx_spi.c b/hw/ssi/xilinx_spi.c index fd1ff12eb1dc..be5baa6b3504 100644 --- a/hw/ssi/xilinx_spi.c +++ b/hw/ssi/xilinx_spi.c @@ -25,6 +25,7 @@ */ #include "qemu/osdep.h" +#include "qapi/error.h" #include "hw/sysbus.h" #include "migration/vmstate.h" #include "qemu/module.h" @@ -32,6 +33,7 @@ #include "hw/irq.h" #include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" #include "hw/ssi/ssi.h" #include "qom/object.h" @@ -83,6 +85,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(XilinxSPI, XILINX_SPI) struct XilinxSPI { SysBusDevice parent_obj; + EndianMode model_endianness; MemoryRegion mmio; qemu_irq irq; @@ -313,14 +316,17 @@ spi_write(void *opaque, hwaddr addr, xlx_spi_update_irq(s); } -static const MemoryRegionOps spi_ops = { - .read = spi_read, - .write = spi_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .valid = { - .min_access_size = 4, - .max_access_size = 4 - } +static const MemoryRegionOps spi_ops[2] = { + [0 ... 1] = { + .read = spi_read, + .write = spi_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + }, + [0].endianness = DEVICE_LITTLE_ENDIAN, + [1].endianness = DEVICE_BIG_ENDIAN, }; static void xilinx_spi_realize(DeviceState *dev, Error **errp) @@ -329,6 +335,12 @@ static void xilinx_spi_realize(DeviceState *dev, Error **errp) XilinxSPI *s = XILINX_SPI(dev); int i; + if (s->model_endianness == ENDIAN_MODE_UNSPECIFIED) { + error_setg(errp, TYPE_XILINX_SPI " property 'endianness'" + " must be set to 'big' or 'little'"); + return; + } + DB_PRINT("\n"); s->spi = ssi_create_bus(dev, "spi"); @@ -339,7 +351,8 @@ static void xilinx_spi_realize(DeviceState *dev, Error **errp) sysbus_init_irq(sbd, &s->cs_lines[i]); } - memory_region_init_io(&s->mmio, OBJECT(s), &spi_ops, s, + memory_region_init_io(&s->mmio, OBJECT(s), + &spi_ops[s->model_endianness == ENDIAN_MODE_BIG], s, "xilinx-spi", R_MAX * 4); sysbus_init_mmio(sbd, &s->mmio); @@ -362,6 +375,7 @@ static const VMStateDescription vmstate_xilinx_spi = { }; static const Property xilinx_spi_properties[] = { + DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XilinxSPI, model_endianness), DEFINE_PROP_UINT8("num-ss-bits", XilinxSPI, num_cs, 1), }; From ba26f1477735a5ad7dd40a3227ac2a54cf82014d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 09:15:48 +0100 Subject: [PATCH 0103/1179] hw/arm: Mark Allwinner Technology devices as little-endian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These devices are only used by the ARM targets, which are only built as little-endian. Therefore the DEVICE_NATIVE_ENDIAN definition expand to DEVICE_LITTLE_ENDIAN (besides, the DEVICE_BIG_ENDIAN case isn't tested). Simplify directly using DEVICE_LITTLE_ENDIAN. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250212113938.38692-2-philmd@linaro.org> --- hw/arm/allwinner-a10.c | 2 +- hw/arm/allwinner-h3.c | 8 ++++---- hw/arm/allwinner-r40.c | 2 +- hw/i2c/allwinner-i2c.c | 2 +- hw/intc/allwinner-a10-pic.c | 2 +- hw/misc/allwinner-a10-ccm.c | 2 +- hw/misc/allwinner-a10-dramc.c | 2 +- hw/misc/allwinner-cpucfg.c | 2 +- hw/misc/allwinner-h3-ccu.c | 2 +- hw/misc/allwinner-h3-dramc.c | 6 +++--- hw/misc/allwinner-h3-sysctrl.c | 2 +- hw/misc/allwinner-r40-ccu.c | 2 +- hw/misc/allwinner-r40-dramc.c | 10 +++++----- hw/misc/allwinner-sid.c | 2 +- hw/misc/allwinner-sramc.c | 2 +- hw/net/allwinner-sun8i-emac.c | 2 +- hw/net/allwinner_emac.c | 2 +- hw/rtc/allwinner-rtc.c | 2 +- hw/sd/allwinner-sdhost.c | 2 +- hw/ssi/allwinner-a10-spi.c | 2 +- hw/timer/allwinner-a10-pit.c | 2 +- hw/watchdog/allwinner-wdt.c | 2 +- 22 files changed, 31 insertions(+), 31 deletions(-) diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c index a829913f1b5b..f1b399759a17 100644 --- a/hw/arm/allwinner-a10.c +++ b/hw/arm/allwinner-a10.c @@ -158,7 +158,7 @@ static void aw_a10_realize(DeviceState *dev, Error **errp) /* FIXME use a qdev chardev prop instead of serial_hd() */ serial_mm_init(get_system_memory(), AW_A10_UART0_REG_BASE, 2, qdev_get_gpio_in(dev, 1), - 115200, serial_hd(0), DEVICE_NATIVE_ENDIAN); + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); for (size_t i = 0; i < AW_A10_NUM_USB; i++) { g_autofree char *bus = g_strdup_printf("usb-bus.%zu", i); diff --git a/hw/arm/allwinner-h3.c b/hw/arm/allwinner-h3.c index 2efced3f66a2..1b1afa4fb6f2 100644 --- a/hw/arm/allwinner-h3.c +++ b/hw/arm/allwinner-h3.c @@ -408,19 +408,19 @@ static void allwinner_h3_realize(DeviceState *dev, Error **errp) /* UART0. For future clocktree API: All UARTS are connected to APB2_CLK. */ serial_mm_init(get_system_memory(), s->memmap[AW_H3_DEV_UART0], 2, qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART0), - 115200, serial_hd(0), DEVICE_NATIVE_ENDIAN); + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); /* UART1 */ serial_mm_init(get_system_memory(), s->memmap[AW_H3_DEV_UART1], 2, qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART1), - 115200, serial_hd(1), DEVICE_NATIVE_ENDIAN); + 115200, serial_hd(1), DEVICE_LITTLE_ENDIAN); /* UART2 */ serial_mm_init(get_system_memory(), s->memmap[AW_H3_DEV_UART2], 2, qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART2), - 115200, serial_hd(2), DEVICE_NATIVE_ENDIAN); + 115200, serial_hd(2), DEVICE_LITTLE_ENDIAN); /* UART3 */ serial_mm_init(get_system_memory(), s->memmap[AW_H3_DEV_UART3], 2, qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART3), - 115200, serial_hd(3), DEVICE_NATIVE_ENDIAN); + 115200, serial_hd(3), DEVICE_LITTLE_ENDIAN); /* DRAMC */ sysbus_realize(SYS_BUS_DEVICE(&s->dramc), &error_fatal); diff --git a/hw/arm/allwinner-r40.c b/hw/arm/allwinner-r40.c index 47b3180f0ec0..cef6e4d18c26 100644 --- a/hw/arm/allwinner-r40.c +++ b/hw/arm/allwinner-r40.c @@ -492,7 +492,7 @@ static void allwinner_r40_realize(DeviceState *dev, Error **errp) serial_mm_init(get_system_memory(), addr, 2, qdev_get_gpio_in(DEVICE(&s->gic), uart_irqs[i]), - 115200, serial_hd(i), DEVICE_NATIVE_ENDIAN); + 115200, serial_hd(i), DEVICE_LITTLE_ENDIAN); } /* I2C */ diff --git a/hw/i2c/allwinner-i2c.c b/hw/i2c/allwinner-i2c.c index 16f1d6d40e7b..66d6431c5083 100644 --- a/hw/i2c/allwinner-i2c.c +++ b/hw/i2c/allwinner-i2c.c @@ -407,7 +407,7 @@ static const MemoryRegionOps allwinner_i2c_ops = { .write = allwinner_i2c_write, .valid.min_access_size = 1, .valid.max_access_size = 4, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static const VMStateDescription allwinner_i2c_vmstate = { diff --git a/hw/intc/allwinner-a10-pic.c b/hw/intc/allwinner-a10-pic.c index c0f30092cd60..93a604f7a040 100644 --- a/hw/intc/allwinner-a10-pic.c +++ b/hw/intc/allwinner-a10-pic.c @@ -135,7 +135,7 @@ static void aw_a10_pic_write(void *opaque, hwaddr offset, uint64_t value, static const MemoryRegionOps aw_a10_pic_ops = { .read = aw_a10_pic_read, .write = aw_a10_pic_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static const VMStateDescription vmstate_aw_a10_pic = { diff --git a/hw/misc/allwinner-a10-ccm.c b/hw/misc/allwinner-a10-ccm.c index 575b0189524a..6ca1daaff8a3 100644 --- a/hw/misc/allwinner-a10-ccm.c +++ b/hw/misc/allwinner-a10-ccm.c @@ -147,7 +147,7 @@ static void allwinner_a10_ccm_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_a10_ccm_ops = { .read = allwinner_a10_ccm_read, .write = allwinner_a10_ccm_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-a10-dramc.c b/hw/misc/allwinner-a10-dramc.c index a7c58fa6d060..badc4c56eb73 100644 --- a/hw/misc/allwinner-a10-dramc.c +++ b/hw/misc/allwinner-a10-dramc.c @@ -114,7 +114,7 @@ static void allwinner_a10_dramc_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_a10_dramc_ops = { .read = allwinner_a10_dramc_read, .write = allwinner_a10_dramc_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-cpucfg.c b/hw/misc/allwinner-cpucfg.c index 022f63ddf34f..a4f7a011419b 100644 --- a/hw/misc/allwinner-cpucfg.c +++ b/hw/misc/allwinner-cpucfg.c @@ -217,7 +217,7 @@ static void allwinner_cpucfg_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_cpucfg_ops = { .read = allwinner_cpucfg_read, .write = allwinner_cpucfg_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-h3-ccu.c b/hw/misc/allwinner-h3-ccu.c index 92e579a99181..e765f4c54b41 100644 --- a/hw/misc/allwinner-h3-ccu.c +++ b/hw/misc/allwinner-h3-ccu.c @@ -155,7 +155,7 @@ static void allwinner_h3_ccu_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_h3_ccu_ops = { .read = allwinner_h3_ccu_read, .write = allwinner_h3_ccu_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-h3-dramc.c b/hw/misc/allwinner-h3-dramc.c index 13bba26d0e4e..c4f3eb92747b 100644 --- a/hw/misc/allwinner-h3-dramc.c +++ b/hw/misc/allwinner-h3-dramc.c @@ -219,7 +219,7 @@ static void allwinner_h3_dramphy_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_h3_dramcom_ops = { .read = allwinner_h3_dramcom_read, .write = allwinner_h3_dramcom_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, @@ -230,7 +230,7 @@ static const MemoryRegionOps allwinner_h3_dramcom_ops = { static const MemoryRegionOps allwinner_h3_dramctl_ops = { .read = allwinner_h3_dramctl_read, .write = allwinner_h3_dramctl_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, @@ -241,7 +241,7 @@ static const MemoryRegionOps allwinner_h3_dramctl_ops = { static const MemoryRegionOps allwinner_h3_dramphy_ops = { .read = allwinner_h3_dramphy_read, .write = allwinner_h3_dramphy_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-h3-sysctrl.c b/hw/misc/allwinner-h3-sysctrl.c index 40059e8cb0c0..32a0ceb01a3e 100644 --- a/hw/misc/allwinner-h3-sysctrl.c +++ b/hw/misc/allwinner-h3-sysctrl.c @@ -78,7 +78,7 @@ static void allwinner_h3_sysctrl_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_h3_sysctrl_ops = { .read = allwinner_h3_sysctrl_read, .write = allwinner_h3_sysctrl_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-r40-ccu.c b/hw/misc/allwinner-r40-ccu.c index 005a15b2daeb..8f37a9213c03 100644 --- a/hw/misc/allwinner-r40-ccu.c +++ b/hw/misc/allwinner-r40-ccu.c @@ -129,7 +129,7 @@ static void allwinner_r40_ccu_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_r40_ccu_ops = { .read = allwinner_r40_ccu_read, .write = allwinner_r40_ccu_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-r40-dramc.c b/hw/misc/allwinner-r40-dramc.c index 97c3664e3a3c..96e1848c21fc 100644 --- a/hw/misc/allwinner-r40-dramc.c +++ b/hw/misc/allwinner-r40-dramc.c @@ -297,7 +297,7 @@ static void allwinner_r40_dramphy_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_r40_dramcom_ops = { .read = allwinner_r40_dramcom_read, .write = allwinner_r40_dramcom_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, @@ -308,7 +308,7 @@ static const MemoryRegionOps allwinner_r40_dramcom_ops = { static const MemoryRegionOps allwinner_r40_dramctl_ops = { .read = allwinner_r40_dramctl_read, .write = allwinner_r40_dramctl_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, @@ -319,7 +319,7 @@ static const MemoryRegionOps allwinner_r40_dramctl_ops = { static const MemoryRegionOps allwinner_r40_dramphy_ops = { .read = allwinner_r40_dramphy_read, .write = allwinner_r40_dramphy_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, @@ -358,7 +358,7 @@ static void allwinner_r40_detect_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_r40_detect_ops = { .read = allwinner_r40_detect_read, .write = allwinner_r40_detect_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, @@ -393,7 +393,7 @@ static uint64_t allwinner_r40_dualrank_detect_read(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_r40_dualrank_detect_ops = { .read = allwinner_r40_dualrank_detect_read, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-sid.c b/hw/misc/allwinner-sid.c index 042b747f30b7..2bb81f9c540a 100644 --- a/hw/misc/allwinner-sid.c +++ b/hw/misc/allwinner-sid.c @@ -99,7 +99,7 @@ static void allwinner_sid_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_sid_ops = { .read = allwinner_sid_read, .write = allwinner_sid_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/misc/allwinner-sramc.c b/hw/misc/allwinner-sramc.c index a20b0b4c5cb1..51df5e45aa29 100644 --- a/hw/misc/allwinner-sramc.c +++ b/hw/misc/allwinner-sramc.c @@ -104,7 +104,7 @@ static void allwinner_sramc_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_sramc_ops = { .read = allwinner_sramc_read, .write = allwinner_sramc_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/net/allwinner-sun8i-emac.c b/hw/net/allwinner-sun8i-emac.c index ff44554e9571..5adb41dc4690 100644 --- a/hw/net/allwinner-sun8i-emac.c +++ b/hw/net/allwinner-sun8i-emac.c @@ -784,7 +784,7 @@ static void allwinner_sun8i_emac_set_link(NetClientState *nc) static const MemoryRegionOps allwinner_sun8i_emac_mem_ops = { .read = allwinner_sun8i_emac_read, .write = allwinner_sun8i_emac_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/net/allwinner_emac.c b/hw/net/allwinner_emac.c index 3eb9e09dc5c3..47f1e7f086c7 100644 --- a/hw/net/allwinner_emac.c +++ b/hw/net/allwinner_emac.c @@ -421,7 +421,7 @@ static void aw_emac_set_link(NetClientState *nc) static const MemoryRegionOps aw_emac_mem_ops = { .read = aw_emac_read, .write = aw_emac_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/rtc/allwinner-rtc.c b/hw/rtc/allwinner-rtc.c index a19e4310bb1b..fd8355a86763 100644 --- a/hw/rtc/allwinner-rtc.c +++ b/hw/rtc/allwinner-rtc.c @@ -259,7 +259,7 @@ static void allwinner_rtc_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_rtc_ops = { .read = allwinner_rtc_read, .write = allwinner_rtc_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/sd/allwinner-sdhost.c b/hw/sd/allwinner-sdhost.c index ee5c5c78a81e..03980d271682 100644 --- a/hw/sd/allwinner-sdhost.c +++ b/hw/sd/allwinner-sdhost.c @@ -761,7 +761,7 @@ static void allwinner_sdhost_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_sdhost_ops = { .read = allwinner_sdhost_read, .write = allwinner_sdhost_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, diff --git a/hw/ssi/allwinner-a10-spi.c b/hw/ssi/allwinner-a10-spi.c index 3eb50b44ac55..d2f6bb9cdc7e 100644 --- a/hw/ssi/allwinner-a10-spi.c +++ b/hw/ssi/allwinner-a10-spi.c @@ -502,7 +502,7 @@ static const MemoryRegionOps allwinner_a10_spi_ops = { .write = allwinner_a10_spi_write, .valid.min_access_size = 1, .valid.max_access_size = 4, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static const VMStateDescription allwinner_a10_spi_vmstate = { diff --git a/hw/timer/allwinner-a10-pit.c b/hw/timer/allwinner-a10-pit.c index ddaf2128c2d9..da3d7173ef56 100644 --- a/hw/timer/allwinner-a10-pit.c +++ b/hw/timer/allwinner-a10-pit.c @@ -185,7 +185,7 @@ static void a10_pit_write(void *opaque, hwaddr offset, uint64_t value, static const MemoryRegionOps a10_pit_ops = { .read = a10_pit_read, .write = a10_pit_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static const Property a10_pit_properties[] = { diff --git a/hw/watchdog/allwinner-wdt.c b/hw/watchdog/allwinner-wdt.c index 1bfec41ff8b4..78f4f9d6f679 100644 --- a/hw/watchdog/allwinner-wdt.c +++ b/hw/watchdog/allwinner-wdt.c @@ -275,7 +275,7 @@ static void allwinner_wdt_write(void *opaque, hwaddr offset, static const MemoryRegionOps allwinner_wdt_ops = { .read = allwinner_wdt_read, .write = allwinner_wdt_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, From 7830a2ea8053e4dbe2466dba6a13204873a0ef87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 09:38:17 +0100 Subject: [PATCH 0104/1179] hw/mips: Mark Boston machine devices as little-endian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Boston machine is only built as little-endian. Therefore the DEVICE_NATIVE_ENDIAN definition expand to DEVICE_LITTLE_ENDIAN (besides, the DEVICE_BIG_ENDIAN case isn't tested). Simplify directly using DEVICE_LITTLE_ENDIAN. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250212113938.38692-3-philmd@linaro.org> --- hw/mips/boston.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hw/mips/boston.c b/hw/mips/boston.c index 364c328032a1..4690b254dda2 100644 --- a/hw/mips/boston.c +++ b/hw/mips/boston.c @@ -220,7 +220,7 @@ static void boston_lcd_write(void *opaque, hwaddr addr, static const MemoryRegionOps boston_lcd_ops = { .read = boston_lcd_read, .write = boston_lcd_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static uint64_t boston_platreg_read(void *opaque, hwaddr addr, @@ -299,7 +299,7 @@ static void boston_platreg_write(void *opaque, hwaddr addr, static const MemoryRegionOps boston_platreg_ops = { .read = boston_platreg_read, .write = boston_platreg_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static void mips_boston_instance_init(Object *obj) @@ -758,7 +758,7 @@ static void boston_mach_init(MachineState *machine) s->uart = serial_mm_init(sys_mem, boston_memmap[BOSTON_UART].base, 2, get_cps_irq(&s->cps, 3), 10000000, - serial_hd(0), DEVICE_NATIVE_ENDIAN); + serial_hd(0), DEVICE_LITTLE_ENDIAN); lcd = g_new(MemoryRegion, 1); memory_region_init_io(lcd, NULL, &boston_lcd_ops, s, "boston-lcd", 0x8); From 8970e2ea01f91c3162c36c731ea9720cb0df9bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 09:38:26 +0100 Subject: [PATCH 0105/1179] hw/mips: Mark Loonson3 Virt machine devices as little-endian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Loonson3 Virt machine is only built as little-endian. Therefore the DEVICE_NATIVE_ENDIAN definition expand to DEVICE_LITTLE_ENDIAN (besides, the DEVICE_BIG_ENDIAN case isn't tested). Simplify directly using DEVICE_LITTLE_ENDIAN. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250212113938.38692-4-philmd@linaro.org> --- hw/mips/loongson3_virt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/mips/loongson3_virt.c b/hw/mips/loongson3_virt.c index 831fddb1bd74..db1cc5131471 100644 --- a/hw/mips/loongson3_virt.c +++ b/hw/mips/loongson3_virt.c @@ -144,7 +144,7 @@ static void loongson3_pm_write(void *opaque, hwaddr addr, static const MemoryRegionOps loongson3_pm_ops = { .read = loongson3_pm_read, .write = loongson3_pm_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 1 @@ -560,7 +560,7 @@ static void mips_loongson3_virt_init(MachineState *machine) serial_mm_init(address_space_mem, virt_memmap[VIRT_UART].base, 0, qdev_get_gpio_in(liointc, UART_IRQ), 115200, serial_hd(0), - DEVICE_NATIVE_ENDIAN); + DEVICE_LITTLE_ENDIAN); sysbus_create_simple("goldfish_rtc", virt_memmap[VIRT_RTC].base, qdev_get_gpio_in(liointc, RTC_IRQ)); From 62fb8ec35b7d7de7bfd4bd008026d49a5f52f946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 09:21:41 +0100 Subject: [PATCH 0106/1179] hw/pci-host: Mark versatile regions as little-endian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This device is only used by the ARM targets, which are only built as little-endian. Therefore the DEVICE_NATIVE_ENDIAN definition expand to DEVICE_LITTLE_ENDIAN (besides, the DEVICE_BIG_ENDIAN case isn't tested). Simplify directly using DEVICE_LITTLE_ENDIAN. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250212113938.38692-5-philmd@linaro.org> --- hw/pci-host/versatile.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/pci-host/versatile.c b/hw/pci-host/versatile.c index c3fbf4cbf943..33a8ceb3b54e 100644 --- a/hw/pci-host/versatile.c +++ b/hw/pci-host/versatile.c @@ -246,7 +246,7 @@ static uint64_t pci_vpb_reg_read(void *opaque, hwaddr addr, static const MemoryRegionOps pci_vpb_reg_ops = { .read = pci_vpb_reg_read, .write = pci_vpb_reg_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, @@ -312,7 +312,7 @@ static uint64_t pci_vpb_config_read(void *opaque, hwaddr addr, static const MemoryRegionOps pci_vpb_config_ops = { .read = pci_vpb_config_read, .write = pci_vpb_config_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static int pci_vpb_map_irq(PCIDevice *d, int irq_num) From 5bf24ec9c4d4771a9469cadd19cf534e9a32a9db Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Fri, 14 Feb 2025 18:16:50 -0800 Subject: [PATCH 0107/1179] hw/rx: Allow execution without either bios or kernel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users can use -device loader to get an ELF file loaded to memory, so we don't need to require one of these options. Signed-off-by: Keith Packard Reviewed-by: Richard Henderson Message-ID: <20250215021654.1786679-2-keithp@keithp.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/rx/rx-gdbsim.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/hw/rx/rx-gdbsim.c b/hw/rx/rx-gdbsim.c index 88c8f12c1019..4afd77efd569 100644 --- a/hw/rx/rx-gdbsim.c +++ b/hw/rx/rx-gdbsim.c @@ -110,9 +110,6 @@ static void rx_gdbsim_init(MachineState *machine) if (!kernel_filename) { if (machine->firmware) { rom_add_file_fixed(machine->firmware, RX62N_CFLASH_BASE, 0); - } else if (!qtest_enabled()) { - error_report("No bios or kernel specified"); - exit(1); } } From f8af22afec5092ce641fb5e5305f2bb9b232f206 Mon Sep 17 00:00:00 2001 From: Jeuk Kim Date: Tue, 7 Jan 2025 16:26:42 +0900 Subject: [PATCH 0108/1179] hw/ufs: Fix legacy single doorbell support bit QEMU UFS has supported both legacy single doorbell and MCQ, but the LSDBS value was incorrectly set. This change corrects the LSDBS value to 0. Signed-off-by: Jeuk Kim --- hw/ufs/ufs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 428fe927ad08..1ccd6f88b69b 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -1635,7 +1635,7 @@ static void ufs_init_hc(UfsHc *u) cap = FIELD_DP32(cap, CAP, OODDS, 0); cap = FIELD_DP32(cap, CAP, UICDMETMS, 0); cap = FIELD_DP32(cap, CAP, CS, 0); - cap = FIELD_DP32(cap, CAP, LSDBS, 1); + cap = FIELD_DP32(cap, CAP, LSDBS, 0); cap = FIELD_DP32(cap, CAP, MCQS, u->params.mcq); u->reg.cap = cap; From e041d3d2165994311a6ee4bee6d1c7864ff81916 Mon Sep 17 00:00:00 2001 From: Jeuk Kim Date: Wed, 12 Feb 2025 14:04:19 +0900 Subject: [PATCH 0109/1179] tests/qtest/ufs-test: Cleanup unused code Removed dead code related to the unimplemented task management request. Acked-by: Fabiano Rosas Signed-off-by: Jeuk Kim --- tests/qtest/ufs-test.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index 1f860b41c062..ce8b398c6b3f 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -8,7 +8,6 @@ #include "qemu/osdep.h" #include "qemu/module.h" -#include "qemu/units.h" #include "libqtest.h" #include "libqos/qgraph.h" #include "libqos/pci.h" @@ -35,7 +34,6 @@ struct QUfs { QPCIBar bar; uint64_t utrlba; - uint64_t utmrlba; uint64_t cmd_desc_addr; uint64_t data_buffer_addr; @@ -257,7 +255,7 @@ static void ufs_send_scsi_command(QUfs *ufs, uint8_t slot, uint8_t lun, static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) { uint64_t end_time; - uint32_t nutrs, nutmrs; + uint32_t nutrs; uint32_t hcs, is, ucmdarg2, cap; uint32_t hce = 0, ie = 0; UtpTransferReqDesc utrd; @@ -305,7 +303,6 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) hcs = ufs_rreg(ufs, A_HCS); g_assert_true(FIELD_EX32(hcs, HCS, DP)); g_assert_true(FIELD_EX32(hcs, HCS, UTRLRDY)); - g_assert_true(FIELD_EX32(hcs, HCS, UTMRLRDY)); g_assert_true(FIELD_EX32(hcs, HCS, UCRDY)); /* Enable all interrupt functions */ @@ -326,20 +323,15 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) /* Enable transfer request and task management request */ cap = ufs_rreg(ufs, A_CAP); nutrs = FIELD_EX32(cap, CAP, NUTRS) + 1; - nutmrs = FIELD_EX32(cap, CAP, NUTMRS) + 1; ufs->cmd_desc_addr = guest_alloc(alloc, nutrs * UTP_COMMAND_DESCRIPTOR_SIZE); ufs->data_buffer_addr = guest_alloc(alloc, MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE); ufs->utrlba = guest_alloc(alloc, nutrs * sizeof(UtpTransferReqDesc)); - ufs->utmrlba = guest_alloc(alloc, nutmrs * sizeof(UtpTaskReqDesc)); ufs_wreg(ufs, A_UTRLBA, ufs->utrlba & 0xffffffff); ufs_wreg(ufs, A_UTRLBAU, ufs->utrlba >> 32); - ufs_wreg(ufs, A_UTMRLBA, ufs->utmrlba & 0xffffffff); - ufs_wreg(ufs, A_UTMRLBAU, ufs->utmrlba >> 32); ufs_wreg(ufs, A_UTRLRSR, 1); - ufs_wreg(ufs, A_UTMRLRSR, 1); /* Send nop out to test transfer request */ ufs_send_nop_out(ufs, 0, &utrd, &rsp_upiu); @@ -370,7 +362,6 @@ static void ufs_exit(QUfs *ufs, QGuestAllocator *alloc) { if (ufs->enabled) { guest_free(alloc, ufs->utrlba); - guest_free(alloc, ufs->utmrlba); guest_free(alloc, ufs->cmd_desc_addr); guest_free(alloc, ufs->data_buffer_addr); } From 5cb3566a5860f35a8871277748616b9ab11f5cd2 Mon Sep 17 00:00:00 2001 From: Jeuk Kim Date: Wed, 12 Feb 2025 14:04:20 +0900 Subject: [PATCH 0110/1179] tests/qtest/ufs-test: Prepare for MCQ test In legacy doorbell mode, the command descriptor slot matched the UTRD slot. To maintain consistency in MCQ mode, command descriptor slot allocation and deallocation now use a bitmap-based approach. Acked-by: Fabiano Rosas Signed-off-by: Jeuk Kim --- tests/qtest/ufs-test.c | 562 +++++++++++++++++++++-------------------- 1 file changed, 295 insertions(+), 267 deletions(-) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index ce8b398c6b3f..f5b311554b30 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -13,6 +13,7 @@ #include "libqos/pci.h" #include "scsi/constants.h" #include "block/ufs.h" +#include "qemu/bitmap.h" /* Test images sizes in Bytes */ #define TEST_IMAGE_SIZE (64 * 1024 * 1024) @@ -25,6 +26,8 @@ #define UTP_COMMAND_DESCRIPTOR_SIZE 4096 #define UTP_RESPONSE_UPIU_OFFSET 1024 #define UTP_PRDT_UPIU_OFFSET 2048 +#define UTRD_TEST_SLOT 0 +#define UFS_MAX_CMD_DESC 32 typedef struct QUfs QUfs; @@ -34,6 +37,7 @@ struct QUfs { QPCIBar bar; uint64_t utrlba; + DECLARE_BITMAP(cmd_desc_bitmap, UFS_MAX_CMD_DESC); uint64_t cmd_desc_addr; uint64_t data_buffer_addr; @@ -50,6 +54,24 @@ static inline void ufs_wreg(QUfs *ufs, size_t offset, uint32_t value) qpci_io_writel(&ufs->dev, ufs->bar, offset, value); } +static int alloc_cmd_desc_slot(QUfs *ufs) +{ + int slot = find_first_zero_bit(ufs->cmd_desc_bitmap, UFS_MAX_CMD_DESC); + if (slot == UFS_MAX_CMD_DESC) { + g_assert_not_reached(); + } + set_bit(slot, ufs->cmd_desc_bitmap); + return slot; +} + +static void release_cmd_desc_slot(QUfs *ufs, int slot) +{ + if (!test_bit(slot, ufs->cmd_desc_bitmap)) { + g_assert_not_reached(); + } + clear_bit(slot, ufs->cmd_desc_bitmap); +} + static void ufs_wait_for_irq(QUfs *ufs) { uint64_t end_time; @@ -62,14 +84,11 @@ static void ufs_wait_for_irq(QUfs *ufs) } while (is == 0 && g_get_monotonic_time() < end_time); } -static UtpTransferReqDesc ufs_build_req_utrd(uint64_t cmd_desc_addr, - uint8_t slot, +static UtpTransferReqDesc ufs_build_req_utrd(uint64_t command_desc_base_addr, uint32_t data_direction, uint16_t prd_table_length) { UtpTransferReqDesc req = { 0 }; - uint64_t command_desc_base_addr = - cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; req.header.dword_0 = cpu_to_le32(1 << 28 | data_direction | UFS_UTP_REQ_DESC_INT_CMD); @@ -86,54 +105,73 @@ static UtpTransferReqDesc ufs_build_req_utrd(uint64_t cmd_desc_addr, return req; } -static void ufs_send_nop_out(QUfs *ufs, uint8_t slot, - UtpTransferReqDesc *utrd_out, UtpUpiuRsp *rsp_out) +static enum UtpOcsCodes +ufs_send_transfer_request_sync(QUfs *ufs, uint8_t lun, + const UtpTransferReqDesc *utrd) { - /* Build up utp transfer request descriptor */ - UtpTransferReqDesc utrd = ufs_build_req_utrd(ufs->cmd_desc_addr, slot, - UFS_UTP_NO_DATA_TRANSFER, 0); - uint64_t utrd_addr = ufs->utrlba + slot * sizeof(UtpTransferReqDesc); + UtpTransferReqDesc utrd_result; + /* + * Currently, the transfer request is sent synchronously, so UTRD_TEST_SLOT + * is fixed to 0. If asynchronous testing is added in the future, this value + * should be adjusted dynamically. + */ + uint64_t utrd_addr = + ufs->utrlba + UTRD_TEST_SLOT * sizeof(UtpTransferReqDesc); + qtest_memwrite(ufs->dev.bus->qts, utrd_addr, utrd, sizeof(*utrd)); + + /* Ring Doorbell */ + ufs_wreg(ufs, A_UTRLDBR, 1); + ufs_wait_for_irq(ufs); + g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); + ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + + qtest_memread(ufs->dev.bus->qts, utrd_addr, &utrd_result, + sizeof(utrd_result)); + + return le32_to_cpu(utrd_result.header.dword_2) & 0xf; +} + +static enum UtpOcsCodes ufs_send_nop_out(QUfs *ufs, UtpUpiuRsp *rsp_out) +{ + int cmd_desc_slot = alloc_cmd_desc_slot(ufs); uint64_t req_upiu_addr = - ufs->cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; + ufs->cmd_desc_addr + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE; uint64_t rsp_upiu_addr = req_upiu_addr + UTP_RESPONSE_UPIU_OFFSET; - qtest_memwrite(ufs->dev.bus->qts, utrd_addr, &utrd, sizeof(utrd)); /* Build up request upiu */ UtpUpiuReq req_upiu = { 0 }; req_upiu.header.trans_type = UFS_UPIU_TRANSACTION_NOP_OUT; - req_upiu.header.task_tag = slot; + req_upiu.header.task_tag = cmd_desc_slot; qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu, sizeof(req_upiu)); - /* Ring Doorbell */ - ufs_wreg(ufs, A_UTRLDBR, 1); - ufs_wait_for_irq(ufs); - g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); - ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + /* Build up utp transfer request descriptor */ + UtpTransferReqDesc utrd = + ufs_build_req_utrd(req_upiu_addr, UFS_UTP_NO_DATA_TRANSFER, 0); + + /* Send Transfer Request */ + enum UtpOcsCodes ret = ufs_send_transfer_request_sync(ufs, 0, &utrd); - qtest_memread(ufs->dev.bus->qts, utrd_addr, utrd_out, sizeof(*utrd_out)); qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, rsp_out, sizeof(*rsp_out)); + release_cmd_desc_slot(ufs, cmd_desc_slot); + return ret; } -static void ufs_send_query(QUfs *ufs, uint8_t slot, uint8_t query_function, - uint8_t query_opcode, uint8_t idn, uint8_t index, - uint8_t selector, uint32_t attr_value, - UtpTransferReqDesc *utrd_out, UtpUpiuRsp *rsp_out) +static enum UtpOcsCodes ufs_send_query(QUfs *ufs, uint8_t query_function, + uint8_t query_opcode, uint8_t idn, + uint8_t index, uint8_t selector, + uint32_t attr_value, UtpUpiuRsp *rsp_out) { - /* Build up utp transfer request descriptor */ - UtpTransferReqDesc utrd = ufs_build_req_utrd(ufs->cmd_desc_addr, slot, - UFS_UTP_NO_DATA_TRANSFER, 0); - uint64_t utrd_addr = ufs->utrlba + slot * sizeof(UtpTransferReqDesc); + int cmd_desc_slot = alloc_cmd_desc_slot(ufs); uint64_t req_upiu_addr = - ufs->cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; + ufs->cmd_desc_addr + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE; uint64_t rsp_upiu_addr = req_upiu_addr + UTP_RESPONSE_UPIU_OFFSET; - qtest_memwrite(ufs->dev.bus->qts, utrd_addr, &utrd, sizeof(utrd)); /* Build up request upiu */ UtpUpiuReq req_upiu = { 0 }; req_upiu.header.trans_type = UFS_UPIU_TRANSACTION_QUERY_REQ; req_upiu.header.query_func = query_function; - req_upiu.header.task_tag = slot; + req_upiu.header.task_tag = cmd_desc_slot; /* * QEMU UFS does not currently support Write descriptor, * so the value of data_segment_length is always 0. @@ -148,22 +186,23 @@ static void ufs_send_query(QUfs *ufs, uint8_t slot, uint8_t query_function, qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu, sizeof(req_upiu)); - /* Ring Doorbell */ - ufs_wreg(ufs, A_UTRLDBR, 1); - ufs_wait_for_irq(ufs); - g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); - ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + /* Build up utp transfer request descriptor */ + UtpTransferReqDesc utrd = + ufs_build_req_utrd(req_upiu_addr, UFS_UTP_NO_DATA_TRANSFER, 0); + + /* Send Transfer Request */ + enum UtpOcsCodes ret = ufs_send_transfer_request_sync(ufs, 0, &utrd); - qtest_memread(ufs->dev.bus->qts, utrd_addr, utrd_out, sizeof(*utrd_out)); qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, rsp_out, sizeof(*rsp_out)); + release_cmd_desc_slot(ufs, cmd_desc_slot); + return ret; } -static void ufs_send_scsi_command(QUfs *ufs, uint8_t slot, uint8_t lun, - const uint8_t *cdb, const uint8_t *data_in, - size_t data_in_len, uint8_t *data_out, - size_t data_out_len, - UtpTransferReqDesc *utrd_out, - UtpUpiuRsp *rsp_out) +static enum UtpOcsCodes +ufs_send_scsi_command(QUfs *ufs, uint8_t lun, const uint8_t *cdb, + const uint8_t *data_in, size_t data_in_len, + uint8_t *data_out, size_t data_out_len, + UtpUpiuRsp *rsp_out) { /* Build up PRDT */ @@ -173,8 +212,9 @@ static void ufs_send_scsi_command(QUfs *ufs, uint8_t slot, uint8_t lun, uint8_t flags; uint16_t prd_table_length, i; uint32_t data_direction, data_len; + int cmd_desc_slot = alloc_cmd_desc_slot(ufs); uint64_t req_upiu_addr = - ufs->cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; + ufs->cmd_desc_addr + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE; uint64_t prdt_addr = req_upiu_addr + UTP_PRDT_UPIU_OFFSET; g_assert_true(data_in_len < MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE); @@ -216,36 +256,33 @@ static void ufs_send_scsi_command(QUfs *ufs, uint8_t slot, uint8_t lun, qtest_memwrite(ufs->dev.bus->qts, prdt_addr, entries, prd_table_length * sizeof(UfshcdSgEntry)); - /* Build up utp transfer request descriptor */ - UtpTransferReqDesc utrd = ufs_build_req_utrd( - ufs->cmd_desc_addr, slot, data_direction, prd_table_length); - uint64_t utrd_addr = ufs->utrlba + slot * sizeof(UtpTransferReqDesc); uint64_t rsp_upiu_addr = req_upiu_addr + UTP_RESPONSE_UPIU_OFFSET; - qtest_memwrite(ufs->dev.bus->qts, utrd_addr, &utrd, sizeof(utrd)); /* Build up request upiu */ UtpUpiuReq req_upiu = { 0 }; req_upiu.header.trans_type = UFS_UPIU_TRANSACTION_COMMAND; req_upiu.header.flags = flags; req_upiu.header.lun = lun; - req_upiu.header.task_tag = slot; + req_upiu.header.task_tag = cmd_desc_slot; req_upiu.sc.exp_data_transfer_len = cpu_to_be32(data_len); memcpy(req_upiu.sc.cdb, cdb, UFS_CDB_SIZE); qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu, sizeof(req_upiu)); - /* Ring Doorbell */ - ufs_wreg(ufs, A_UTRLDBR, 1); - ufs_wait_for_irq(ufs); - g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); - ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + /* Build up utp transfer request descriptor */ + UtpTransferReqDesc utrd = + ufs_build_req_utrd(req_upiu_addr, data_direction, prd_table_length); + + /* Send Transfer Request */ + enum UtpOcsCodes ret = ufs_send_transfer_request_sync(ufs, lun, &utrd); - qtest_memread(ufs->dev.bus->qts, utrd_addr, utrd_out, sizeof(*utrd_out)); qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, rsp_out, sizeof(*rsp_out)); if (data_out_len) { qtest_memread(ufs->dev.bus->qts, ufs->data_buffer_addr, data_out, data_out_len); } + release_cmd_desc_slot(ufs, cmd_desc_slot); + return ret; } /** @@ -258,7 +295,7 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) uint32_t nutrs; uint32_t hcs, is, ucmdarg2, cap; uint32_t hce = 0, ie = 0; - UtpTransferReqDesc utrd; + enum UtpOcsCodes ocs; UtpUpiuRsp rsp_upiu; ufs->bar = qpci_iomap(&ufs->dev, 0, NULL); @@ -320,11 +357,11 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) ufs_wreg(ufs, A_IE, ie); ufs_wreg(ufs, A_UTRIACR, 0); - /* Enable transfer request and task management request */ + /* Enable transfer request */ cap = ufs_rreg(ufs, A_CAP); nutrs = FIELD_EX32(cap, CAP, NUTRS) + 1; ufs->cmd_desc_addr = - guest_alloc(alloc, nutrs * UTP_COMMAND_DESCRIPTOR_SIZE); + guest_alloc(alloc, UFS_MAX_CMD_DESC * UTP_COMMAND_DESCRIPTOR_SIZE); ufs->data_buffer_addr = guest_alloc(alloc, MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE); ufs->utrlba = guest_alloc(alloc, nutrs * sizeof(UtpTransferReqDesc)); @@ -334,23 +371,27 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) ufs_wreg(ufs, A_UTRLRSR, 1); /* Send nop out to test transfer request */ - ufs_send_nop_out(ufs, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_nop_out(ufs, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); /* Set fDeviceInit flag via query request */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_SET_FLAG, - UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_SET_FLAG, + UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); /* Wait for device to reset */ end_time = g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SECOND; do { qtest_clock_step(ufs->dev.bus->qts, 100); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_FLAG, - UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, 0, 0, &utrd, - &rsp_upiu); + ocs = + ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_FLAG, + UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, + UFS_COMMAND_RESULT_SUCCESS); } while (be32_to_cpu(rsp_upiu.qr.value) != 0 && g_get_monotonic_time() < end_time); g_assert_cmpuint(be32_to_cpu(rsp_upiu.qr.value), ==, 0); @@ -424,15 +465,15 @@ static void ufstest_init(void *obj, void *data, QGuestAllocator *alloc) const uint8_t request_sense_cdb[UFS_CDB_SIZE] = { REQUEST_SENSE, }; - UtpTransferReqDesc utrd; + enum UtpOcsCodes ocs; UtpUpiuRsp rsp_upiu; ufs_init(ufs, alloc); /* Check REPORT_LUNS */ - ufs_send_scsi_command(ufs, 0, 0, report_luns_cdb, NULL, 0, buf, sizeof(buf), - &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_scsi_command(ufs, 0, report_luns_cdb, NULL, 0, buf, + sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD); /* LUN LIST LENGTH should be 8, in big endian */ g_assert_cmpuint(buf[3], ==, 8); @@ -440,15 +481,15 @@ static void ufstest_init(void *obj, void *data, QGuestAllocator *alloc) g_assert_cmpuint(buf[9], ==, 0); /* Clear Unit Attention */ - ufs_send_scsi_command(ufs, 0, 0, request_sense_cdb, NULL, 0, buf, - sizeof(buf), &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_scsi_command(ufs, 0, request_sense_cdb, NULL, 0, buf, + sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, CHECK_CONDITION); /* Check TEST_UNIT_READY */ - ufs_send_scsi_command(ufs, 0, 0, test_unit_ready_cdb, NULL, 0, NULL, 0, - &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_scsi_command(ufs, 0, test_unit_ready_cdb, NULL, 0, NULL, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD); ufs_exit(ufs, alloc); @@ -490,22 +531,22 @@ static void ufstest_read_write(void *obj, void *data, QGuestAllocator *alloc) WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; uint32_t block_size; - UtpTransferReqDesc utrd; + enum UtpOcsCodes ocs; UtpUpiuRsp rsp_upiu; const int test_lun = 1; ufs_init(ufs, alloc); /* Clear Unit Attention */ - ufs_send_scsi_command(ufs, 0, test_lun, request_sense_cdb, NULL, 0, - read_buf, sizeof(read_buf), &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_scsi_command(ufs, test_lun, request_sense_cdb, NULL, 0, + read_buf, sizeof(read_buf), &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, CHECK_CONDITION); /* Read capacity */ - ufs_send_scsi_command(ufs, 0, test_lun, read_capacity_cdb, NULL, 0, - read_buf, sizeof(read_buf), &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_scsi_command(ufs, test_lun, read_capacity_cdb, NULL, 0, + read_buf, sizeof(read_buf), &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, UFS_COMMAND_RESULT_SUCCESS); block_size = ldl_be_p(&read_buf[8]); @@ -513,16 +554,16 @@ static void ufstest_read_write(void *obj, void *data, QGuestAllocator *alloc) /* Write data */ memset(write_buf, 0xab, block_size); - ufs_send_scsi_command(ufs, 0, test_lun, write_cdb, write_buf, block_size, - NULL, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_scsi_command(ufs, test_lun, write_cdb, write_buf, block_size, + NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, UFS_COMMAND_RESULT_SUCCESS); /* Read data and verify */ - ufs_send_scsi_command(ufs, 0, test_lun, read_cdb, NULL, 0, read_buf, - block_size, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_scsi_command(ufs, test_lun, read_cdb, NULL, 0, read_buf, + block_size, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpint(memcmp(read_buf, write_buf, block_size), ==, 0); @@ -535,76 +576,74 @@ static void ufstest_query_flag_request(void *obj, void *data, { QUfs *ufs = obj; - UtpTransferReqDesc utrd; + enum UtpOcsCodes ocs; UtpUpiuRsp rsp_upiu; ufs_init(ufs, alloc); /* Read read-only flag */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_FLAG, - UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_FLAG, + UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.opcode, ==, UFS_UPIU_QUERY_OPCODE_READ_FLAG); g_assert_cmpuint(rsp_upiu.qr.idn, ==, UFS_QUERY_FLAG_IDN_FDEVICEINIT); g_assert_cmpuint(rsp_upiu.qr.value, ==, be32_to_cpu(0)); /* Flag Set, Clear, Toggle Test with fDeviceLifeSpanModeEn */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_FLAG, - UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_FLAG, + UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, be32_to_cpu(0)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_SET_FLAG, - UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_SET_FLAG, + UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, be32_to_cpu(1)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_CLEAR_FLAG, - UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_CLEAR_FLAG, + UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, be32_to_cpu(0)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_TOGGLE_FLAG, - UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_TOGGLE_FLAG, + UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, be32_to_cpu(1)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_TOGGLE_FLAG, - UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_TOGGLE_FLAG, + UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, be32_to_cpu(0)); /* Read Write-only Flag (Intended Failure) */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_FLAG, - UFS_QUERY_FLAG_IDN_PURGE_ENABLE, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_FLAG, + UFS_QUERY_FLAG_IDN_PURGE_ENABLE, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_NOT_READABLE); /* Write Read-Only Flag (Intended Failure) */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_SET_FLAG, UFS_QUERY_FLAG_IDN_BUSY_RTC, - 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_SET_FLAG, + UFS_QUERY_FLAG_IDN_BUSY_RTC, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_NOT_WRITEABLE); @@ -616,130 +655,122 @@ static void ufstest_query_attr_request(void *obj, void *data, { QUfs *ufs = obj; - UtpTransferReqDesc utrd; + enum UtpOcsCodes ocs; UtpUpiuRsp rsp_upiu; ufs_init(ufs, alloc); /* Read Readable Attributes*/ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_BOOT_LU_EN, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_BOOT_LU_EN, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.opcode, ==, UFS_UPIU_QUERY_OPCODE_READ_ATTR); g_assert_cmpuint(rsp_upiu.qr.idn, ==, UFS_QUERY_ATTR_IDN_BOOT_LU_EN); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_BKOPS_STATUS, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_BKOPS_STATUS, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); /* Write Writable Attributes & Read Again */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, - UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0x03, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, + UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0x03, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x03)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, - UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0x07, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, + UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0x07, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x07)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x03)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x07)); /* Write Invalid Value (Intended Error) */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, - UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0x10, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, + UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0x10, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_INVALID_VALUE); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x03)); /* Read Write-Only Attribute (Intended Error) */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_SECONDS_PASSED, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_SECONDS_PASSED, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_NOT_READABLE); /* Write Read-Only Attribute (Intended Error) */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, - UFS_QUERY_ATTR_IDN_POWER_MODE, 0, 0, 0x01, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, + UFS_QUERY_ATTR_IDN_POWER_MODE, 0, 0, 0x01, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_NOT_WRITEABLE); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_POWER_MODE, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_POWER_MODE, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); /* Reset Written Attributes */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, - UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, + UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, - UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, - UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, + UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &utrd, - &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_ATTR, - UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); @@ -751,17 +782,17 @@ static void ufstest_query_desc_request(void *obj, void *data, { QUfs *ufs = obj; - UtpTransferReqDesc utrd; + enum UtpOcsCodes ocs; UtpUpiuRsp rsp_upiu; ufs_init(ufs, alloc); /* Write Descriptor is not supported yet */ /* Read Device Descriptor */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_DEVICE, - 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_DEVICE, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.opcode, ==, UFS_UPIU_QUERY_OPCODE_READ_DESC); g_assert_cmpuint(rsp_upiu.qr.idn, ==, UFS_QUERY_DESC_IDN_DEVICE); @@ -771,126 +802,123 @@ static void ufstest_query_desc_request(void *obj, void *data, /* Read Configuration Descriptor is not supported yet*/ /* Read Unit Descriptor */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_UNIT, 0, - 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_UNIT, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, sizeof(UnitDescriptor)); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_UNIT); g_assert_cmpuint(rsp_upiu.qr.data[2], ==, 0); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_UNIT, 1, - 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_UNIT, 1, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, sizeof(UnitDescriptor)); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_UNIT); g_assert_cmpuint(rsp_upiu.qr.data[2], ==, 1); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_UNIT, - UFS_UPIU_RPMB_WLUN, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = + ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_UNIT, + UFS_UPIU_RPMB_WLUN, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, sizeof(RpmbUnitDescriptor)); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_UNIT); g_assert_cmpuint(rsp_upiu.qr.data[2], ==, UFS_UPIU_RPMB_WLUN); /* Read Interconnect Descriptor */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, - UFS_QUERY_DESC_IDN_INTERCONNECT, 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_INTERCONNECT, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, sizeof(InterconnectDescriptor)); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_INTERCONNECT); /* Read String Descriptor */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_STRING, - 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_STRING, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, 0x12); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_STRING); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_STRING, - 1, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_STRING, 1, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, 0x22); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_STRING); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_STRING, - 4, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_STRING, 4, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, 0x0a); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_STRING); /* Read Geometry Descriptor */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_GEOMETRY, - 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_GEOMETRY, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, sizeof(GeometryDescriptor)); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_GEOMETRY); /* Read Power Descriptor */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_POWER, 0, - 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_POWER, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, sizeof(PowerParametersDescriptor)); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_POWER); /* Read Health Descriptor */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_HEALTH, - 0, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, UFS_OCS_SUCCESS); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_HEALTH, 0, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.data[0], ==, sizeof(DeviceHealthDescriptor)); g_assert_cmpuint(rsp_upiu.qr.data[1], ==, UFS_QUERY_DESC_IDN_HEALTH); /* Invalid Index (Intended Failure) */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_UNIT, 4, - 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_UNIT, 4, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_INVALID_INDEX); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_STRING, - 5, 0, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_STRING, 5, 0, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_INVALID_INDEX); /* Invalid Selector (Intended Failure) */ - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_DEVICE, - 0, 1, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_DEVICE, 0, 1, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_INVALID_SELECTOR); - ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, - UFS_UPIU_QUERY_OPCODE_READ_DESC, UFS_QUERY_DESC_IDN_STRING, - 0, 1, 0, &utrd, &rsp_upiu); - g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), ==, - UFS_OCS_INVALID_CMD_TABLE_ATTR); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_DESC, + UFS_QUERY_DESC_IDN_STRING, 0, 1, 0, &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_INVALID_CMD_TABLE_ATTR); g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_QUERY_RESULT_INVALID_SELECTOR); From a54596a96006096798b172a368ae952a231f9f72 Mon Sep 17 00:00:00 2001 From: Jeuk Kim Date: Wed, 12 Feb 2025 14:04:21 +0900 Subject: [PATCH 0111/1179] tests/qtest/ufs-test: Add test code for MCQ functionality This patch tests whether MCQ initialization and basic read-write operations work correctly when the MCQ parameter of hw/ufs is enabled. Acked-by: Fabiano Rosas Signed-off-by: Jeuk Kim --- tests/qtest/ufs-test.c | 171 ++++++++++++++++++++++++++++++++++------- 1 file changed, 142 insertions(+), 29 deletions(-) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index f5b311554b30..d5076bdeb549 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -15,6 +15,7 @@ #include "block/ufs.h" #include "qemu/bitmap.h" +#define DWORD_BYTE 4 /* Test images sizes in Bytes */ #define TEST_IMAGE_SIZE (64 * 1024 * 1024) /* Timeout for various operations, in seconds. */ @@ -28,6 +29,10 @@ #define UTP_PRDT_UPIU_OFFSET 2048 #define UTRD_TEST_SLOT 0 #define UFS_MAX_CMD_DESC 32 +/* Constants for MCQ */ +#define TEST_QID 0 +#define QUEUE_SIZE 32 +#define UFS_MCQ_MAX_QNUM 32 typedef struct QUfs QUfs; @@ -36,12 +41,22 @@ struct QUfs { QPCIDevice dev; QPCIBar bar; - uint64_t utrlba; DECLARE_BITMAP(cmd_desc_bitmap, UFS_MAX_CMD_DESC); uint64_t cmd_desc_addr; uint64_t data_buffer_addr; bool enabled; + bool support_mcq; + + /* for legacy doorbell mode */ + uint64_t utrlba; + + /* for mcq mode */ + uint32_t maxq; + uint64_t sqlba[UFS_MCQ_MAX_QNUM]; + uint64_t cqlba[UFS_MCQ_MAX_QNUM]; + uint64_t sqdao[UFS_MCQ_MAX_QNUM]; + uint64_t cqdao[UFS_MCQ_MAX_QNUM]; }; static inline uint32_t ufs_rreg(QUfs *ufs, size_t offset) @@ -106,31 +121,67 @@ static UtpTransferReqDesc ufs_build_req_utrd(uint64_t command_desc_base_addr, } static enum UtpOcsCodes -ufs_send_transfer_request_sync(QUfs *ufs, uint8_t lun, - const UtpTransferReqDesc *utrd) +__ufs_send_transfer_request_doorbell(QUfs *ufs, uint8_t lun, + const UtpTransferReqDesc *utrd) { - UtpTransferReqDesc utrd_result; - /* - * Currently, the transfer request is sent synchronously, so UTRD_TEST_SLOT - * is fixed to 0. If asynchronous testing is added in the future, this value - * should be adjusted dynamically. - */ uint64_t utrd_addr = ufs->utrlba + UTRD_TEST_SLOT * sizeof(UtpTransferReqDesc); + UtpTransferReqDesc utrd_result; + qtest_memwrite(ufs->dev.bus->qts, utrd_addr, utrd, sizeof(*utrd)); - /* Ring Doorbell */ + /* Ring the doorbell */ ufs_wreg(ufs, A_UTRLDBR, 1); ufs_wait_for_irq(ufs); g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + /* Handle completed command */ qtest_memread(ufs->dev.bus->qts, utrd_addr, &utrd_result, sizeof(utrd_result)); - return le32_to_cpu(utrd_result.header.dword_2) & 0xf; } +static enum UtpOcsCodes +__ufs_send_transfer_request_mcq(QUfs *ufs, uint8_t lun, + const UtpTransferReqDesc *utrd) +{ + uint32_t sqtp = ufs_rreg(ufs, ufs->sqdao[TEST_QID] + 0x4); + uint64_t utrd_addr = ufs->sqlba[TEST_QID] + sqtp; + uint32_t cqhp; + uint64_t cqentry_addr; + UfsCqEntry cqentry; + + qtest_memwrite(ufs->dev.bus->qts, utrd_addr, utrd, sizeof(*utrd)); + + /* Insert a new entry into the submission queue */ + sqtp = ufs_rreg(ufs, ufs->sqdao[TEST_QID] + 0x4); + sqtp = (sqtp + sizeof(UfsSqEntry)) % (QUEUE_SIZE * sizeof(UfsSqEntry)); + ufs_wreg(ufs, ufs->sqdao[TEST_QID] + 0x4, sqtp); + ufs_wait_for_irq(ufs); + g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, CQES)); + ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1)); + + /* Handle the completed command from the completion queue */ + cqhp = ufs_rreg(ufs, ufs->cqdao[TEST_QID]); + cqentry_addr = ufs->cqlba[TEST_QID] + cqhp; + qtest_memread(ufs->dev.bus->qts, cqentry_addr, &cqentry, sizeof(cqentry)); + ufs_wreg(ufs, ufs->cqdao[TEST_QID], cqhp); + + return cqentry.status; +} + +static enum UtpOcsCodes +ufs_send_transfer_request_sync(QUfs *ufs, uint8_t lun, + const UtpTransferReqDesc *utrd) +{ + if (ufs->support_mcq) { + return __ufs_send_transfer_request_mcq(ufs, lun, utrd); + } + + return __ufs_send_transfer_request_doorbell(ufs, lun, utrd); +} + static enum UtpOcsCodes ufs_send_nop_out(QUfs *ufs, UtpUpiuRsp *rsp_out) { int cmd_desc_slot = alloc_cmd_desc_slot(ufs); @@ -342,6 +393,10 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) g_assert_true(FIELD_EX32(hcs, HCS, UTRLRDY)); g_assert_true(FIELD_EX32(hcs, HCS, UCRDY)); + /* Check MCQ support */ + cap = ufs_rreg(ufs, A_CAP); + ufs->support_mcq = FIELD_EX32(cap, CAP, MCQS); + /* Enable all interrupt functions */ ie = FIELD_DP32(ie, IE, UTRCE, 1); ie = FIELD_DP32(ie, IE, UEE, 1); @@ -354,21 +409,66 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) ie = FIELD_DP32(ie, IE, HCFEE, 1); ie = FIELD_DP32(ie, IE, SBFEE, 1); ie = FIELD_DP32(ie, IE, CEFEE, 1); + if (ufs->support_mcq) { + ie = FIELD_DP32(ie, IE, CQEE, 1); + } ufs_wreg(ufs, A_IE, ie); ufs_wreg(ufs, A_UTRIACR, 0); /* Enable transfer request */ - cap = ufs_rreg(ufs, A_CAP); - nutrs = FIELD_EX32(cap, CAP, NUTRS) + 1; ufs->cmd_desc_addr = guest_alloc(alloc, UFS_MAX_CMD_DESC * UTP_COMMAND_DESCRIPTOR_SIZE); ufs->data_buffer_addr = guest_alloc(alloc, MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE); - ufs->utrlba = guest_alloc(alloc, nutrs * sizeof(UtpTransferReqDesc)); - ufs_wreg(ufs, A_UTRLBA, ufs->utrlba & 0xffffffff); - ufs_wreg(ufs, A_UTRLBAU, ufs->utrlba >> 32); - ufs_wreg(ufs, A_UTRLRSR, 1); + if (ufs->support_mcq) { + uint32_t mcqcap, qid, qcfgptr, mcq_reg_offset; + uint32_t cqattr = 0, sqattr = 0; + + mcqcap = ufs_rreg(ufs, A_MCQCAP); + qcfgptr = FIELD_EX32(mcqcap, MCQCAP, QCFGPTR); + ufs->maxq = FIELD_EX32(mcqcap, MCQCAP, MAXQ) + 1; + for (qid = 0; qid < ufs->maxq; ++qid) { + ufs->sqlba[qid] = + guest_alloc(alloc, QUEUE_SIZE * sizeof(UtpTransferReqDesc)); + ufs->cqlba[qid] = + guest_alloc(alloc, QUEUE_SIZE * sizeof(UtpTransferReqDesc)); + mcq_reg_offset = qcfgptr * 0x200 + qid * 0x40; + + ufs_wreg(ufs, mcq_reg_offset + A_SQLBA, + ufs->sqlba[qid] & 0xffffffff); + ufs_wreg(ufs, mcq_reg_offset + A_SQUBA, ufs->sqlba[qid] >> 32); + ufs_wreg(ufs, mcq_reg_offset + A_CQLBA, + ufs->cqlba[qid] & 0xffffffff); + ufs_wreg(ufs, mcq_reg_offset + A_CQUBA, ufs->cqlba[qid] >> 32); + + /* Enable Completion Queue */ + cqattr = FIELD_DP32(cqattr, CQATTR, CQEN, 1); + cqattr = FIELD_DP32(cqattr, CQATTR, SIZE, + QUEUE_SIZE * sizeof(UtpTransferReqDesc) / + DWORD_BYTE); + ufs_wreg(ufs, mcq_reg_offset + A_CQATTR, cqattr); + + /* Enable Submission Queue */ + sqattr = FIELD_DP32(sqattr, SQATTR, SQEN, 1); + sqattr = FIELD_DP32(sqattr, SQATTR, SIZE, + QUEUE_SIZE * sizeof(UtpTransferReqDesc) / + DWORD_BYTE); + sqattr = FIELD_DP32(sqattr, SQATTR, CQID, qid); + ufs_wreg(ufs, mcq_reg_offset + A_SQATTR, sqattr); + + /* Cache head & tail pointer */ + ufs->sqdao[qid] = ufs_rreg(ufs, mcq_reg_offset + A_SQDAO); + ufs->cqdao[qid] = ufs_rreg(ufs, mcq_reg_offset + A_CQDAO); + } + } else { + nutrs = FIELD_EX32(cap, CAP, NUTRS) + 1; + ufs->utrlba = guest_alloc(alloc, nutrs * sizeof(UtpTransferReqDesc)); + + ufs_wreg(ufs, A_UTRLBA, ufs->utrlba & 0xffffffff); + ufs_wreg(ufs, A_UTRLBAU, ufs->utrlba >> 32); + ufs_wreg(ufs, A_UTRLRSR, 1); + } /* Send nop out to test transfer request */ ocs = ufs_send_nop_out(ufs, &rsp_upiu); @@ -402,7 +502,15 @@ static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) static void ufs_exit(QUfs *ufs, QGuestAllocator *alloc) { if (ufs->enabled) { - guest_free(alloc, ufs->utrlba); + if (ufs->support_mcq) { + for (uint32_t qid = 0; qid < ufs->maxq; ++qid) { + guest_free(alloc, ufs->sqlba[qid]); + guest_free(alloc, ufs->cqlba[qid]); + } + } else { + guest_free(alloc, ufs->utrlba); + } + guest_free(alloc, ufs->cmd_desc_addr); guest_free(alloc, ufs->data_buffer_addr); } @@ -966,12 +1074,16 @@ static void ufs_register_nodes(void) QOSGraphEdgeOptions edge_opts = { .before_cmd_line = "-blockdev null-co,node-name=drv0,read-zeroes=on", .after_cmd_line = "-device ufs-lu,bus=ufs0,drive=drv0,lun=0", - .extra_device_opts = "addr=04.0,id=ufs0,nutrs=32,nutmrs=8" + .extra_device_opts = "addr=04.0,id=ufs0" }; - QOSGraphTestOptions io_test_opts = { - .before = ufs_blk_test_setup, - }; + QOSGraphTestOptions io_test_opts = { .before = ufs_blk_test_setup, + .edge.extra_device_opts = + "mcq=false,nutrs=32,nutmrs=8" }; + + QOSGraphTestOptions mcq_test_opts = { .before = ufs_blk_test_setup, + .edge.extra_device_opts = + "mcq=true,mcq-maxq=1" }; add_qpci_address(&edge_opts, &(QPCIAddress){ .devfn = QPCI_DEVFN(4, 0) }); @@ -991,13 +1103,14 @@ static void ufs_register_nodes(void) return; } qos_add_test("init", "ufs", ufstest_init, NULL); - qos_add_test("read-write", "ufs", ufstest_read_write, &io_test_opts); - qos_add_test("flag read-write", "ufs", - ufstest_query_flag_request, &io_test_opts); - qos_add_test("attr read-write", "ufs", - ufstest_query_attr_request, &io_test_opts); - qos_add_test("desc read-write", "ufs", - ufstest_query_desc_request, &io_test_opts); + qos_add_test("legacy-read-write", "ufs", ufstest_read_write, &io_test_opts); + qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_opts); + qos_add_test("query-flag", "ufs", ufstest_query_flag_request, + &io_test_opts); + qos_add_test("query-attribute", "ufs", ufstest_query_attr_request, + &io_test_opts); + qos_add_test("query-desciptor", "ufs", ufstest_query_desc_request, + &io_test_opts); } libqos_init(ufs_register_nodes); From 3a3b282879e83efdee1cb752e75351725e07e90a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sat, 15 Feb 2025 11:45:21 -0800 Subject: [PATCH 0112/1179] tcg: Remove last traces of TCG_TARGET_NEED_POOL_LABELS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These should have been removed with the rest. There are a couple of hosts which can emit guest_base into the constant pool: aarch64, mips64, ppc64, riscv64. Fixes: a417ef835058 ("tcg: Remove TCG_TARGET_NEED_LDST_LABELS and TCG_TARGET_NEED_POOL_LABELS") Signed-off-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé --- tcg/tcg.c | 4 ---- tcg/tci/tcg-target.h | 1 - 2 files changed, 5 deletions(-) diff --git a/tcg/tcg.c b/tcg/tcg.c index 43b6712286c3..53de13df7171 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -1598,21 +1598,17 @@ void tcg_prologue_init(void) tcg_qemu_tb_exec = (tcg_prologue_fn *)tcg_splitwx_to_rx(s->code_ptr); #endif -#ifdef TCG_TARGET_NEED_POOL_LABELS s->pool_labels = NULL; -#endif qemu_thread_jit_write(); /* Generate the prologue. */ tcg_target_qemu_prologue(s); -#ifdef TCG_TARGET_NEED_POOL_LABELS /* Allow the prologue to put e.g. guest_base into a pool entry. */ { int result = tcg_out_pool_finalize(s); tcg_debug_assert(result == 0); } -#endif prologue_size = tcg_current_code_size(s); perf_report_prologue(s->code_gen_ptr, prologue_size); diff --git a/tcg/tci/tcg-target.h b/tcg/tci/tcg-target.h index a9ca493d20f6..bd03aa1bc4a2 100644 --- a/tcg/tci/tcg-target.h +++ b/tcg/tci/tcg-target.h @@ -72,6 +72,5 @@ typedef enum { } TCGReg; #define HAVE_TCG_QEMU_TB_EXEC -#define TCG_TARGET_NEED_POOL_LABELS #endif /* TCG_TARGET_H */ From f441b4d19b289f55a378b8d033994f45a333b581 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 2 Feb 2025 18:03:51 -0800 Subject: [PATCH 0113/1179] tcg: Remove TCG_OVERSIZED_GUEST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is now prohibited in configuration. Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson --- accel/tcg/cputlb.c | 7 ------- accel/tcg/tcg-all.c | 9 ++++----- docs/devel/multi-thread-tcg.rst | 1 - include/qemu/atomic.h | 18 +++-------------- include/tcg/oversized-guest.h | 23 ---------------------- target/arm/ptw.c | 34 --------------------------------- target/riscv/cpu_helper.c | 13 +------------ 7 files changed, 8 insertions(+), 97 deletions(-) delete mode 100644 include/tcg/oversized-guest.h diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index b4ccf0cdcb72..17e225169516 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -47,7 +47,6 @@ #include "qemu/plugin-memory.h" #endif #include "tcg/tcg-ldst.h" -#include "tcg/oversized-guest.h" /* DEBUG defines, enable DEBUG_TLB_LOG to log to the CPU_LOG_MMU target */ /* #define DEBUG_TLB */ @@ -118,12 +117,8 @@ static inline uint64_t tlb_read_idx(const CPUTLBEntry *entry, return qatomic_read(ptr); #else const uint64_t *ptr = &entry->addr_idx[access_type]; -# if TCG_OVERSIZED_GUEST - return *ptr; -# else /* ofs might correspond to .addr_write, so use qatomic_read */ return qatomic_read(ptr); -# endif #endif } @@ -908,8 +903,6 @@ static void tlb_reset_dirty_range_locked(CPUTLBEntry *tlb_entry, uint32_t *ptr_write = (uint32_t *)&tlb_entry->addr_write; ptr_write += HOST_BIG_ENDIAN; qatomic_set(ptr_write, *ptr_write | TLB_NOTDIRTY); -#elif TCG_OVERSIZED_GUEST - tlb_entry->addr_write |= TLB_NOTDIRTY; #else qatomic_set(&tlb_entry->addr_write, tlb_entry->addr_write | TLB_NOTDIRTY); diff --git a/accel/tcg/tcg-all.c b/accel/tcg/tcg-all.c index 95adaacee82a..c1a30b01219e 100644 --- a/accel/tcg/tcg-all.c +++ b/accel/tcg/tcg-all.c @@ -28,7 +28,6 @@ #include "exec/replay-core.h" #include "system/cpu-timers.h" #include "tcg/startup.h" -#include "tcg/oversized-guest.h" #include "qapi/error.h" #include "qemu/error-report.h" #include "qemu/accel.h" @@ -41,6 +40,8 @@ #include "hw/boards.h" #endif #include "internal-common.h" +#include "cpu-param.h" + struct TCGState { AccelState parent_obj; @@ -72,7 +73,7 @@ DECLARE_INSTANCE_CHECKER(TCGState, TCG_STATE, static bool default_mttcg_enabled(void) { - if (icount_enabled() || TCG_OVERSIZED_GUEST) { + if (icount_enabled()) { return false; } #ifdef TARGET_SUPPORTS_MTTCG @@ -145,9 +146,7 @@ static void tcg_set_thread(Object *obj, const char *value, Error **errp) TCGState *s = TCG_STATE(obj); if (strcmp(value, "multi") == 0) { - if (TCG_OVERSIZED_GUEST) { - error_setg(errp, "No MTTCG when guest word size > hosts"); - } else if (icount_enabled()) { + if (icount_enabled()) { error_setg(errp, "No MTTCG when icount is enabled"); } else { #ifndef TARGET_SUPPORTS_MTTCG diff --git a/docs/devel/multi-thread-tcg.rst b/docs/devel/multi-thread-tcg.rst index 7fd0a07633dc..b0f473961dd1 100644 --- a/docs/devel/multi-thread-tcg.rst +++ b/docs/devel/multi-thread-tcg.rst @@ -37,7 +37,6 @@ if: * forced by --accel tcg,thread=single * enabling --icount mode -* 64 bit guests on 32 bit hosts (TCG_OVERSIZED_GUEST) In the general case of running translated code there should be no inter-vCPU dependencies and all vCPUs should be able to run at full diff --git a/include/qemu/atomic.h b/include/qemu/atomic.h index 7a3f2e6576b3..f80cba24cf78 100644 --- a/include/qemu/atomic.h +++ b/include/qemu/atomic.h @@ -56,25 +56,13 @@ */ #define signal_barrier() __atomic_signal_fence(__ATOMIC_SEQ_CST) -/* Sanity check that the size of an atomic operation isn't "overly large". +/* + * Sanity check that the size of an atomic operation isn't "overly large". * Despite the fact that e.g. i686 has 64-bit atomic operations, we do not * want to use them because we ought not need them, and this lets us do a * bit of sanity checking that other 32-bit hosts might build. - * - * That said, we have a problem on 64-bit ILP32 hosts in that in order to - * sync with TCG_OVERSIZED_GUEST, this must match TCG_TARGET_REG_BITS. - * We'd prefer not want to pull in everything else TCG related, so handle - * those few cases by hand. - * - * Note that x32 is fully detected with __x86_64__ + _ILP32, and that for - * Sparc we always force the use of sparcv9 in configure. MIPS n32 (ILP32) & - * n64 (LP64) ABIs are both detected using __mips64. */ -#if defined(__x86_64__) || defined(__sparc__) || defined(__mips64) -# define ATOMIC_REG_SIZE 8 -#else -# define ATOMIC_REG_SIZE sizeof(void *) -#endif +#define ATOMIC_REG_SIZE sizeof(void *) /* Weak atomic operations prevent the compiler moving other * loads/stores past the atomic operation load/store. However there is diff --git a/include/tcg/oversized-guest.h b/include/tcg/oversized-guest.h deleted file mode 100644 index 641b9749ffcd..000000000000 --- a/include/tcg/oversized-guest.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* - * Define TCG_OVERSIZED_GUEST - * Copyright (c) 2008 Fabrice Bellard - */ - -#ifndef EXEC_TCG_OVERSIZED_GUEST_H -#define EXEC_TCG_OVERSIZED_GUEST_H - -#include "tcg-target-reg-bits.h" -#include "cpu-param.h" - -/* - * Oversized TCG guests make things like MTTCG hard - * as we can't use atomics for cputlb updates. - */ -#if TARGET_LONG_BITS > TCG_TARGET_REG_BITS -#define TCG_OVERSIZED_GUEST 1 -#else -#define TCG_OVERSIZED_GUEST 0 -#endif - -#endif diff --git a/target/arm/ptw.c b/target/arm/ptw.c index 64bb6878a48a..43309003486b 100644 --- a/target/arm/ptw.c +++ b/target/arm/ptw.c @@ -16,9 +16,6 @@ #include "internals.h" #include "cpu-features.h" #include "idau.h" -#ifdef CONFIG_TCG -# include "tcg/oversized-guest.h" -#endif typedef struct S1Translate { /* @@ -840,7 +837,6 @@ static uint64_t arm_casq_ptw(CPUARMState *env, uint64_t old_val, ptw->out_rw = true; } -#ifdef CONFIG_ATOMIC64 if (ptw->out_be) { old_val = cpu_to_be64(old_val); new_val = cpu_to_be64(new_val); @@ -852,36 +848,6 @@ static uint64_t arm_casq_ptw(CPUARMState *env, uint64_t old_val, cur_val = qatomic_cmpxchg__nocheck((uint64_t *)host, old_val, new_val); cur_val = le64_to_cpu(cur_val); } -#else - /* - * We can't support the full 64-bit atomic cmpxchg on the host. - * Because this is only used for FEAT_HAFDBS, which is only for AA64, - * we know that TCG_OVERSIZED_GUEST is set, which means that we are - * running in round-robin mode and could only race with dma i/o. - */ -#if !TCG_OVERSIZED_GUEST -# error "Unexpected configuration" -#endif - bool locked = bql_locked(); - if (!locked) { - bql_lock(); - } - if (ptw->out_be) { - cur_val = ldq_be_p(host); - if (cur_val == old_val) { - stq_be_p(host, new_val); - } - } else { - cur_val = ldq_le_p(host); - if (cur_val == old_val) { - stq_le_p(host, new_val); - } - } - if (!locked) { - bql_unlock(); - } -#endif - return cur_val; #else /* AArch32 does not have FEAT_HADFS; non-TCG guests only use debug-mode. */ diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index e1dfc4ecbfc5..8ff6d900f2c2 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -32,7 +32,6 @@ #include "system/cpu-timers.h" #include "cpu_bits.h" #include "debug.h" -#include "tcg/oversized-guest.h" #include "pmp.h" int riscv_env_mmu_index(CPURISCVState *env, bool ifetch) @@ -1167,9 +1166,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, hwaddr pte_addr; int i; -#if !TCG_OVERSIZED_GUEST -restart: -#endif + restart: for (i = 0; i < levels; i++, ptshift -= ptidxbits) { target_ulong idx; if (i == 0) { @@ -1388,13 +1385,6 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, false, MEMTXATTRS_UNSPECIFIED); if (memory_region_is_ram(mr)) { target_ulong *pte_pa = qemu_map_ram_ptr(mr->ram_block, addr1); -#if TCG_OVERSIZED_GUEST - /* - * MTTCG is not enabled on oversized TCG guests so - * page table updates do not need to be atomic - */ - *pte_pa = pte = updated_pte; -#else target_ulong old_pte; if (riscv_cpu_sxl(env) == MXL_RV32) { old_pte = qatomic_cmpxchg((uint32_t *)pte_pa, pte, updated_pte); @@ -1405,7 +1395,6 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, goto restart; } pte = updated_pte; -#endif } else { /* * Misconfigured PTE in ROM (AD bits are not preset) or From a0ecb8e49418ab0bb01d47493fafe6a0a357e952 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 11:06:26 -0800 Subject: [PATCH 0114/1179] tcg: Drop support for two address registers in gen_ldst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/tcg-op-ldst.c | 21 +++------------------ tcg/tcg.c | 4 +--- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/tcg/tcg-op-ldst.c b/tcg/tcg-op-ldst.c index 77271e019350..7ba9a3ef7ea7 100644 --- a/tcg/tcg-op-ldst.c +++ b/tcg/tcg-op-ldst.c @@ -91,25 +91,10 @@ static MemOp tcg_canonicalize_memop(MemOp op, bool is64, bool st) static void gen_ldst(TCGOpcode opc, TCGType type, TCGTemp *vl, TCGTemp *vh, TCGTemp *addr, MemOpIdx oi) { - if (TCG_TARGET_REG_BITS == 64 || tcg_ctx->addr_type == TCG_TYPE_I32) { - if (vh) { - tcg_gen_op4(opc, type, temp_arg(vl), temp_arg(vh), - temp_arg(addr), oi); - } else { - tcg_gen_op3(opc, type, temp_arg(vl), temp_arg(addr), oi); - } + if (vh) { + tcg_gen_op4(opc, type, temp_arg(vl), temp_arg(vh), temp_arg(addr), oi); } else { - /* See TCGV_LOW/HIGH. */ - TCGTemp *al = addr + HOST_BIG_ENDIAN; - TCGTemp *ah = addr + !HOST_BIG_ENDIAN; - - if (vh) { - tcg_gen_op5(opc, type, temp_arg(vl), temp_arg(vh), - temp_arg(al), temp_arg(ah), oi); - } else { - tcg_gen_op4(opc, type, temp_arg(vl), - temp_arg(al), temp_arg(ah), oi); - } + tcg_gen_op3(opc, type, temp_arg(vl), temp_arg(addr), oi); } } diff --git a/tcg/tcg.c b/tcg/tcg.c index 53de13df7171..14c2d3816011 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -1690,9 +1690,7 @@ void tcg_func_start(TCGContext *s) s->emit_before_op = NULL; QSIMPLEQ_INIT(&s->labels); - tcg_debug_assert(s->addr_type == TCG_TYPE_I32 || - s->addr_type == TCG_TYPE_I64); - + tcg_debug_assert(s->addr_type <= TCG_TYPE_REG); tcg_debug_assert(s->insn_start_words > 0); } From 50b7a197e1d1782f9366d5e43d1f94700f6236c8 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 13:46:09 -0800 Subject: [PATCH 0115/1179] tcg: Merge INDEX_op_qemu_*_{a32,a64}_* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 64-on-32 is now unsupported, guest addresses always fit in one host register. Drop the replication of opcodes. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- include/tcg/tcg-opc.h | 28 ++------ tcg/aarch64/tcg-target.c.inc | 36 ++++------ tcg/arm/tcg-target.c.inc | 40 +++-------- tcg/i386/tcg-target.c.inc | 69 ++++--------------- tcg/loongarch64/tcg-target.c.inc | 36 ++++------ tcg/mips/tcg-target.c.inc | 51 +++----------- tcg/optimize.c | 21 ++---- tcg/ppc/tcg-target.c.inc | 68 ++++-------------- tcg/riscv/tcg-target.c.inc | 24 +++---- tcg/s390x/tcg-target.c.inc | 36 ++++------ tcg/sparc64/tcg-target.c.inc | 24 +++---- tcg/tcg-op-ldst.c | 82 +++++----------------- tcg/tcg.c | 42 ++++------- tcg/tci.c | 115 +++++-------------------------- tcg/tci/tcg-target.c.inc | 60 ++++------------ 15 files changed, 175 insertions(+), 557 deletions(-) diff --git a/include/tcg/tcg-opc.h b/include/tcg/tcg-opc.h index 9383e295f474..5bf78b076462 100644 --- a/include/tcg/tcg-opc.h +++ b/include/tcg/tcg-opc.h @@ -188,36 +188,22 @@ DEF(goto_ptr, 0, 1, 0, TCG_OPF_BB_EXIT | TCG_OPF_BB_END) DEF(plugin_cb, 0, 0, 1, TCG_OPF_NOT_PRESENT) DEF(plugin_mem_cb, 0, 1, 1, TCG_OPF_NOT_PRESENT) -/* Replicate ld/st ops for 32 and 64-bit guest addresses. */ -DEF(qemu_ld_a32_i32, 1, 1, 1, +DEF(qemu_ld_i32, 1, 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_st_a32_i32, 0, 1 + 1, 1, +DEF(qemu_st_i32, 0, 1 + 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_ld_a32_i64, DATA64_ARGS, 1, 1, +DEF(qemu_ld_i64, DATA64_ARGS, 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_st_a32_i64, 0, DATA64_ARGS + 1, 1, - TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) - -DEF(qemu_ld_a64_i32, 1, DATA64_ARGS, 1, - TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_st_a64_i32, 0, 1 + DATA64_ARGS, 1, - TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_ld_a64_i64, DATA64_ARGS, DATA64_ARGS, 1, - TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_st_a64_i64, 0, DATA64_ARGS + DATA64_ARGS, 1, +DEF(qemu_st_i64, 0, DATA64_ARGS + 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) /* Only used by i386 to cope with stupid register constraints. */ -DEF(qemu_st8_a32_i32, 0, 1 + 1, 1, - TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_st8_a64_i32, 0, 1 + DATA64_ARGS, 1, +DEF(qemu_st8_i32, 0, 1 + 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) /* Only for 64-bit hosts at the moment. */ -DEF(qemu_ld_a32_i128, 2, 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_ld_a64_i128, 2, 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_st_a32_i128, 0, 3, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) -DEF(qemu_st_a64_i128, 0, 3, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) +DEF(qemu_ld_i128, 2, 1, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) +DEF(qemu_st_i128, 0, 3, 1, TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS) /* Host vector support. */ diff --git a/tcg/aarch64/tcg-target.c.inc b/tcg/aarch64/tcg-target.c.inc index 66eb4b73b5ad..45dc2c649b57 100644 --- a/tcg/aarch64/tcg-target.c.inc +++ b/tcg/aarch64/tcg-target.c.inc @@ -2398,24 +2398,18 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, tcg_out_insn(s, 3506, CSEL, ext, a0, REG0(3), REG0(4), args[5]); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_ld_i64: tcg_out_qemu_ld(s, a0, a1, a2, ext); break; - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st_i64: tcg_out_qemu_st(s, REG0(0), a1, a2, ext); break; - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: tcg_out_qemu_ldst_i128(s, a0, a1, a2, args[3], true); break; - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: tcg_out_qemu_ldst_i128(s, REG0(0), REG0(1), a2, args[3], false); break; @@ -3084,21 +3078,15 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_movcond_i64: return C_O1_I4(r, r, rC, rZ, rZ); - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_ld_i64: return C_O1_I1(r, r); - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: return C_O2_I1(r, r, r); - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st_i64: return C_O0_I2(rZ, r); - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: return C_O0_I3(rZ, rZ, r); case INDEX_op_deposit_i32: diff --git a/tcg/arm/tcg-target.c.inc b/tcg/arm/tcg-target.c.inc index 12dad7307f56..05bb367a3971 100644 --- a/tcg/arm/tcg-target.c.inc +++ b/tcg/arm/tcg-target.c.inc @@ -2071,37 +2071,21 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, ARITH_MOV, args[0], 0, 0); break; - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a64_i32: - tcg_out_qemu_ld(s, args[0], -1, args[1], args[2], - args[3], TCG_TYPE_I32); - break; - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: tcg_out_qemu_ld(s, args[0], args[1], args[2], -1, args[3], TCG_TYPE_I64); break; - case INDEX_op_qemu_ld_a64_i64: - tcg_out_qemu_ld(s, args[0], args[1], args[2], args[3], - args[4], TCG_TYPE_I64); - break; - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: tcg_out_qemu_st(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a64_i32: - tcg_out_qemu_st(s, args[0], -1, args[1], args[2], - args[3], TCG_TYPE_I32); - break; - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: tcg_out_qemu_st(s, args[0], args[1], args[2], -1, args[3], TCG_TYPE_I64); break; - case INDEX_op_qemu_st_a64_i64: - tcg_out_qemu_st(s, args[0], args[1], args[2], args[3], - args[4], TCG_TYPE_I64); - break; case INDEX_op_bswap16_i32: tcg_out_bswap16(s, COND_AL, args[0], args[1], args[2]); @@ -2243,22 +2227,14 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_setcond2_i32: return C_O1_I4(r, r, r, rI, rI); - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: return C_O1_I1(r, q); - case INDEX_op_qemu_ld_a64_i32: - return C_O1_I2(r, q, q); - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: return C_O2_I1(e, p, q); - case INDEX_op_qemu_ld_a64_i64: - return C_O2_I2(e, p, q, q); - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: return C_O0_I2(q, q); - case INDEX_op_qemu_st_a64_i32: - return C_O0_I3(q, q, q); - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: return C_O0_I3(Q, p, q); - case INDEX_op_qemu_st_a64_i64: - return C_O0_I4(Q, p, q, q); case INDEX_op_st_vec: return C_O0_I2(w, r); diff --git a/tcg/i386/tcg-target.c.inc b/tcg/i386/tcg-target.c.inc index 2cac15133162..ca6e8abc57ce 100644 --- a/tcg/i386/tcg-target.c.inc +++ b/tcg/i386/tcg-target.c.inc @@ -2879,62 +2879,33 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, tcg_out_modrm(s, OPC_GRP3_Ev + rexw, EXT3_NOT, a0); break; - case INDEX_op_qemu_ld_a64_i32: - if (TCG_TARGET_REG_BITS == 32) { - tcg_out_qemu_ld(s, a0, -1, a1, a2, args[3], TCG_TYPE_I32); - break; - } - /* fall through */ - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, a0, -1, a1, -1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { tcg_out_qemu_ld(s, a0, -1, a1, -1, a2, TCG_TYPE_I64); } else { tcg_out_qemu_ld(s, a0, a1, a2, -1, args[3], TCG_TYPE_I64); } break; - case INDEX_op_qemu_ld_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_ld(s, a0, -1, a1, -1, a2, TCG_TYPE_I64); - } else { - tcg_out_qemu_ld(s, a0, a1, a2, args[3], args[4], TCG_TYPE_I64); - } - break; - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); tcg_out_qemu_ld(s, a0, a1, a2, -1, args[3], TCG_TYPE_I128); break; - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st8_a64_i32: - if (TCG_TARGET_REG_BITS == 32) { - tcg_out_qemu_st(s, a0, -1, a1, a2, args[3], TCG_TYPE_I32); - break; - } - /* fall through */ - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st8_a32_i32: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st8_i32: tcg_out_qemu_st(s, a0, -1, a1, -1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: if (TCG_TARGET_REG_BITS == 64) { tcg_out_qemu_st(s, a0, -1, a1, -1, a2, TCG_TYPE_I64); } else { tcg_out_qemu_st(s, a0, a1, a2, -1, args[3], TCG_TYPE_I64); } break; - case INDEX_op_qemu_st_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_st(s, a0, -1, a1, -1, a2, TCG_TYPE_I64); - } else { - tcg_out_qemu_st(s, a0, a1, a2, args[3], args[4], TCG_TYPE_I64); - } - break; - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); tcg_out_qemu_st(s, a0, a1, a2, -1, args[3], TCG_TYPE_I128); break; @@ -3824,36 +3795,24 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_clz_i64: return have_lzcnt ? C_N1_I2(r, r, rW) : C_N1_I2(r, r, r); - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: return C_O1_I1(r, L); - case INDEX_op_qemu_ld_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, L) : C_O1_I2(r, L, L); - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: return C_O0_I2(L, L); - case INDEX_op_qemu_st_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(L, L) : C_O0_I3(L, L, L); - case INDEX_op_qemu_st8_a32_i32: + case INDEX_op_qemu_st8_i32: return C_O0_I2(s, L); - case INDEX_op_qemu_st8_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(s, L) : C_O0_I3(s, L, L); - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, L) : C_O2_I1(r, r, L); - case INDEX_op_qemu_ld_a64_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, L) : C_O2_I2(r, r, L, L); - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(L, L) : C_O0_I3(L, L, L); - case INDEX_op_qemu_st_a64_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(L, L) : C_O0_I4(L, L, L, L); - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); return C_O2_I1(r, r, L); - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); return C_O0_I3(L, L, L); diff --git a/tcg/loongarch64/tcg-target.c.inc b/tcg/loongarch64/tcg-target.c.inc index cebe8dd354e6..4f32bf3e97b1 100644 --- a/tcg/loongarch64/tcg-target.c.inc +++ b/tcg/loongarch64/tcg-target.c.inc @@ -1675,28 +1675,22 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, tcg_out_ldst(s, OPC_ST_D, a0, a1, a2); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, a0, a1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i64: tcg_out_qemu_ld(s, a0, a1, a2, TCG_TYPE_I64); break; - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: tcg_out_qemu_ldst_i128(s, a0, a1, a2, a3, true); break; - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: + case INDEX_op_qemu_st_i32: tcg_out_qemu_st(s, a0, a1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i64: tcg_out_qemu_st(s, a0, a1, a2, TCG_TYPE_I64); break; - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: tcg_out_qemu_ldst_i128(s, a0, a1, a2, a3, false); break; @@ -2233,18 +2227,14 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_st32_i64: case INDEX_op_st_i32: case INDEX_op_st_i64: - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st_i64: return C_O0_I2(rZ, r); - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: return C_N2_I1(r, r, r); - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: return C_O0_I3(r, r, r); case INDEX_op_brcond_i32: @@ -2290,10 +2280,8 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_ld32u_i64: case INDEX_op_ld_i32: case INDEX_op_ld_i64: - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_ld_i64: return C_O1_I1(r, r); case INDEX_op_andc_i32: diff --git a/tcg/mips/tcg-target.c.inc b/tcg/mips/tcg-target.c.inc index 99f6ef6c766e..b1d512ca2ab7 100644 --- a/tcg/mips/tcg-target.c.inc +++ b/tcg/mips/tcg-target.c.inc @@ -2095,53 +2095,27 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, tcg_out_setcond2(s, args[5], a0, a1, a2, args[3], args[4]); break; - case INDEX_op_qemu_ld_a64_i32: - if (TCG_TARGET_REG_BITS == 32) { - tcg_out_qemu_ld(s, a0, 0, a1, a2, args[3], TCG_TYPE_I32); - break; - } - /* fall through */ - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, a0, 0, a1, 0, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { tcg_out_qemu_ld(s, a0, 0, a1, 0, a2, TCG_TYPE_I64); } else { tcg_out_qemu_ld(s, a0, a1, a2, 0, args[3], TCG_TYPE_I64); } break; - case INDEX_op_qemu_ld_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_ld(s, a0, 0, a1, 0, a2, TCG_TYPE_I64); - } else { - tcg_out_qemu_ld(s, a0, a1, a2, args[3], args[4], TCG_TYPE_I64); - } - break; - case INDEX_op_qemu_st_a64_i32: - if (TCG_TARGET_REG_BITS == 32) { - tcg_out_qemu_st(s, a0, 0, a1, a2, args[3], TCG_TYPE_I32); - break; - } - /* fall through */ - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: tcg_out_qemu_st(s, a0, 0, a1, 0, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: if (TCG_TARGET_REG_BITS == 64) { tcg_out_qemu_st(s, a0, 0, a1, 0, a2, TCG_TYPE_I64); } else { tcg_out_qemu_st(s, a0, a1, a2, 0, args[3], TCG_TYPE_I64); } break; - case INDEX_op_qemu_st_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_st(s, a0, 0, a1, 0, a2, TCG_TYPE_I64); - } else { - tcg_out_qemu_st(s, a0, a1, a2, args[3], args[4], TCG_TYPE_I64); - } - break; case INDEX_op_add2_i32: tcg_out_addsub2(s, a0, a1, a2, args[3], args[4], args[5], @@ -2301,23 +2275,14 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_brcond2_i32: return C_O0_I4(rZ, rZ, rZ, rZ); - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: return C_O1_I1(r, r); - case INDEX_op_qemu_ld_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O1_I2(r, r, r); - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: return C_O0_I2(rZ, r); - case INDEX_op_qemu_st_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(rZ, r) : C_O0_I3(rZ, r, r); - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O2_I1(r, r, r); - case INDEX_op_qemu_ld_a64_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O2_I2(r, r, r, r); - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(rZ, r) : C_O0_I3(rZ, rZ, r); - case INDEX_op_qemu_st_a64_i64: - return (TCG_TARGET_REG_BITS == 64 ? C_O0_I2(rZ, r) - : C_O0_I4(rZ, rZ, r, r)); default: return C_NotImplemented; diff --git a/tcg/optimize.c b/tcg/optimize.c index bca11cc427b0..f922f86a1d1c 100644 --- a/tcg/optimize.c +++ b/tcg/optimize.c @@ -3011,29 +3011,22 @@ void tcg_optimize(TCGContext *s) CASE_OP_32_64_VEC(orc): done = fold_orc(&ctx, op); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: + case INDEX_op_qemu_ld_i32: done = fold_qemu_ld_1reg(&ctx, op); break; - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { done = fold_qemu_ld_1reg(&ctx, op); break; } QEMU_FALLTHROUGH; - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: done = fold_qemu_ld_2reg(&ctx, op); break; - case INDEX_op_qemu_st8_a32_i32: - case INDEX_op_qemu_st8_a64_i32: - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st8_i32: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st_i64: + case INDEX_op_qemu_st_i128: done = fold_qemu_st(&ctx, op); break; CASE_OP_32_64(rem): diff --git a/tcg/ppc/tcg-target.c.inc b/tcg/ppc/tcg-target.c.inc index 6e711cd53f5f..801cb6f3cb28 100644 --- a/tcg/ppc/tcg-target.c.inc +++ b/tcg/ppc/tcg-target.c.inc @@ -3308,17 +3308,10 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, tcg_out32(s, MODUD | TAB(args[0], args[1], args[2])); break; - case INDEX_op_qemu_ld_a64_i32: - if (TCG_TARGET_REG_BITS == 32) { - tcg_out_qemu_ld(s, args[0], -1, args[1], args[2], - args[3], TCG_TYPE_I32); - break; - } - /* fall through */ - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { tcg_out_qemu_ld(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I64); @@ -3327,32 +3320,15 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, args[3], TCG_TYPE_I64); } break; - case INDEX_op_qemu_ld_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_ld(s, args[0], -1, args[1], -1, - args[2], TCG_TYPE_I64); - } else { - tcg_out_qemu_ld(s, args[0], args[1], args[2], args[3], - args[4], TCG_TYPE_I64); - } - break; - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); tcg_out_qemu_ldst_i128(s, args[0], args[1], args[2], args[3], true); break; - case INDEX_op_qemu_st_a64_i32: - if (TCG_TARGET_REG_BITS == 32) { - tcg_out_qemu_st(s, args[0], -1, args[1], args[2], - args[3], TCG_TYPE_I32); - break; - } - /* fall through */ - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: tcg_out_qemu_st(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: if (TCG_TARGET_REG_BITS == 64) { tcg_out_qemu_st(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I64); @@ -3361,17 +3337,7 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, args[3], TCG_TYPE_I64); } break; - case INDEX_op_qemu_st_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_st(s, args[0], -1, args[1], -1, - args[2], TCG_TYPE_I64); - } else { - tcg_out_qemu_st(s, args[0], args[1], args[2], args[3], - args[4], TCG_TYPE_I64); - } - break; - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); tcg_out_qemu_ldst_i128(s, args[0], args[1], args[2], args[3], false); break; @@ -4306,29 +4272,19 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_sub2_i32: return C_O2_I4(r, r, rI, rZM, r, r); - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: return C_O1_I1(r, r); - case INDEX_op_qemu_ld_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O1_I2(r, r, r); - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O2_I1(r, r, r); - case INDEX_op_qemu_ld_a64_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O2_I2(r, r, r, r); - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: return C_O0_I2(r, r); - case INDEX_op_qemu_st_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(r, r) : C_O0_I3(r, r, r); - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(r, r) : C_O0_I3(r, r, r); - case INDEX_op_qemu_st_a64_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(r, r) : C_O0_I4(r, r, r, r); - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: return C_N1O1_I1(o, m, r); - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: return C_O0_I3(o, m, r); case INDEX_op_add_vec: diff --git a/tcg/riscv/tcg-target.c.inc b/tcg/riscv/tcg-target.c.inc index 61dc310c1aa6..55a3398712f3 100644 --- a/tcg/riscv/tcg-target.c.inc +++ b/tcg/riscv/tcg-target.c.inc @@ -2309,20 +2309,16 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, args[3], const_args[3], args[4], const_args[4]); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, a0, a1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i64: tcg_out_qemu_ld(s, a0, a1, a2, TCG_TYPE_I64); break; - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: + case INDEX_op_qemu_st_i32: tcg_out_qemu_st(s, a0, a1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i64: tcg_out_qemu_st(s, a0, a1, a2, TCG_TYPE_I64); break; @@ -2761,15 +2757,11 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_sub2_i64: return C_O2_I4(r, r, rZ, rZ, rM, rM); - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_ld_i64: return C_O1_I1(r, r); - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st_i64: return C_O0_I2(rZ, r); case INDEX_op_st_vec: diff --git a/tcg/s390x/tcg-target.c.inc b/tcg/s390x/tcg-target.c.inc index dc7722dc31c5..6786e7b316e6 100644 --- a/tcg/s390x/tcg-target.c.inc +++ b/tcg/s390x/tcg-target.c.inc @@ -2455,28 +2455,22 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, args[2], const_args[2], args[3], const_args[3], args[4]); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, args[0], args[1], args[2], TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i64: tcg_out_qemu_ld(s, args[0], args[1], args[2], TCG_TYPE_I64); break; - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: + case INDEX_op_qemu_st_i32: tcg_out_qemu_st(s, args[0], args[1], args[2], TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i64: tcg_out_qemu_st(s, args[0], args[1], args[2], TCG_TYPE_I64); break; - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: tcg_out_qemu_ldst_i128(s, args[0], args[1], args[2], args[3], true); break; - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: tcg_out_qemu_ldst_i128(s, args[0], args[1], args[2], args[3], false); break; @@ -3366,21 +3360,15 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_ctpop_i64: return C_O1_I1(r, r); - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_ld_i64: return C_O1_I1(r, r); - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: + case INDEX_op_qemu_st_i64: + case INDEX_op_qemu_st_i32: return C_O0_I2(r, r); - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: + case INDEX_op_qemu_ld_i128: return C_O2_I1(o, m, r); - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_st_i128: return C_O0_I3(o, m, r); case INDEX_op_deposit_i32: diff --git a/tcg/sparc64/tcg-target.c.inc b/tcg/sparc64/tcg-target.c.inc index 733cb516512b..ea0a3b8692da 100644 --- a/tcg/sparc64/tcg-target.c.inc +++ b/tcg/sparc64/tcg-target.c.inc @@ -1426,20 +1426,16 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, tcg_out_arithi(s, a1, a0, 32, SHIFT_SRLX); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: + case INDEX_op_qemu_ld_i32: tcg_out_qemu_ld(s, a0, a1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i64: tcg_out_qemu_ld(s, a0, a1, a2, TCG_TYPE_I64); break; - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: + case INDEX_op_qemu_st_i32: tcg_out_qemu_st(s, a0, a1, a2, TCG_TYPE_I32); break; - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i64: tcg_out_qemu_st(s, a0, a1, a2, TCG_TYPE_I64); break; @@ -1570,10 +1566,8 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_extu_i32_i64: case INDEX_op_extract_i64: case INDEX_op_sextract_i64: - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_ld_i64: return C_O1_I1(r, r); case INDEX_op_st8_i32: @@ -1583,10 +1577,8 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_st_i32: case INDEX_op_st32_i64: case INDEX_op_st_i64: - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st_i64: return C_O0_I2(rZ, r); case INDEX_op_add_i32: diff --git a/tcg/tcg-op-ldst.c b/tcg/tcg-op-ldst.c index 7ba9a3ef7ea7..73838e27015a 100644 --- a/tcg/tcg-op-ldst.c +++ b/tcg/tcg-op-ldst.c @@ -217,7 +217,6 @@ static void tcg_gen_qemu_ld_i32_int(TCGv_i32 val, TCGTemp *addr, MemOp orig_memop; MemOpIdx orig_oi, oi; TCGv_i64 copy_addr; - TCGOpcode opc; tcg_gen_req_mo(TCG_MO_LD_LD | TCG_MO_ST_LD); orig_memop = memop = tcg_canonicalize_memop(memop, 0, 0); @@ -233,12 +232,8 @@ static void tcg_gen_qemu_ld_i32_int(TCGv_i32 val, TCGTemp *addr, } copy_addr = plugin_maybe_preserve_addr(addr); - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_ld_a32_i32; - } else { - opc = INDEX_op_qemu_ld_a64_i32; - } - gen_ldst(opc, TCG_TYPE_I32, tcgv_i32_temp(val), NULL, addr, oi); + gen_ldst(INDEX_op_qemu_ld_i32, TCG_TYPE_I32, + tcgv_i32_temp(val), NULL, addr, oi); plugin_gen_mem_callbacks_i32(val, copy_addr, addr, orig_oi, QEMU_PLUGIN_MEM_R); @@ -295,17 +290,9 @@ static void tcg_gen_qemu_st_i32_int(TCGv_i32 val, TCGTemp *addr, } if (TCG_TARGET_HAS_qemu_st8_i32 && (memop & MO_SIZE) == MO_8) { - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_st8_a32_i32; - } else { - opc = INDEX_op_qemu_st8_a64_i32; - } + opc = INDEX_op_qemu_st8_i32; } else { - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_st_a32_i32; - } else { - opc = INDEX_op_qemu_st_a64_i32; - } + opc = INDEX_op_qemu_st_i32; } gen_ldst(opc, TCG_TYPE_I32, tcgv_i32_temp(val), NULL, addr, oi); plugin_gen_mem_callbacks_i32(val, NULL, addr, orig_oi, QEMU_PLUGIN_MEM_W); @@ -329,7 +316,6 @@ static void tcg_gen_qemu_ld_i64_int(TCGv_i64 val, TCGTemp *addr, MemOp orig_memop; MemOpIdx orig_oi, oi; TCGv_i64 copy_addr; - TCGOpcode opc; if (TCG_TARGET_REG_BITS == 32 && (memop & MO_SIZE) < MO_64) { tcg_gen_qemu_ld_i32_int(TCGV_LOW(val), addr, idx, memop); @@ -355,12 +341,7 @@ static void tcg_gen_qemu_ld_i64_int(TCGv_i64 val, TCGTemp *addr, } copy_addr = plugin_maybe_preserve_addr(addr); - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_ld_a32_i64; - } else { - opc = INDEX_op_qemu_ld_a64_i64; - } - gen_ldst_i64(opc, val, addr, oi); + gen_ldst_i64(INDEX_op_qemu_ld_i64, val, addr, oi); plugin_gen_mem_callbacks_i64(val, copy_addr, addr, orig_oi, QEMU_PLUGIN_MEM_R); @@ -397,7 +378,6 @@ static void tcg_gen_qemu_st_i64_int(TCGv_i64 val, TCGTemp *addr, { TCGv_i64 swap = NULL; MemOpIdx orig_oi, oi; - TCGOpcode opc; if (TCG_TARGET_REG_BITS == 32 && (memop & MO_SIZE) < MO_64) { tcg_gen_qemu_st_i32_int(TCGV_LOW(val), addr, idx, memop); @@ -428,12 +408,7 @@ static void tcg_gen_qemu_st_i64_int(TCGv_i64 val, TCGTemp *addr, oi = make_memop_idx(memop, idx); } - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_st_a32_i64; - } else { - opc = INDEX_op_qemu_st_a64_i64; - } - gen_ldst_i64(opc, val, addr, oi); + gen_ldst_i64(INDEX_op_qemu_st_i64, val, addr, oi); plugin_gen_mem_callbacks_i64(val, NULL, addr, orig_oi, QEMU_PLUGIN_MEM_W); if (swap) { @@ -545,7 +520,6 @@ static void tcg_gen_qemu_ld_i128_int(TCGv_i128 val, TCGTemp *addr, { MemOpIdx orig_oi; TCGv_i64 ext_addr = NULL; - TCGOpcode opc; check_max_alignment(memop_alignment_bits(memop)); tcg_gen_req_mo(TCG_MO_LD_LD | TCG_MO_ST_LD); @@ -573,12 +547,7 @@ static void tcg_gen_qemu_ld_i128_int(TCGv_i128 val, TCGTemp *addr, hi = TCGV128_HIGH(val); } - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_ld_a32_i128; - } else { - opc = INDEX_op_qemu_ld_a64_i128; - } - gen_ldst(opc, TCG_TYPE_I128, tcgv_i64_temp(lo), + gen_ldst(INDEX_op_qemu_ld_i128, TCG_TYPE_I128, tcgv_i64_temp(lo), tcgv_i64_temp(hi), addr, oi); if (need_bswap) { @@ -594,12 +563,6 @@ static void tcg_gen_qemu_ld_i128_int(TCGv_i128 val, TCGTemp *addr, canonicalize_memop_i128_as_i64(mop, memop); need_bswap = (mop[0] ^ memop) & MO_BSWAP; - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_ld_a32_i64; - } else { - opc = INDEX_op_qemu_ld_a64_i64; - } - /* * Since there are no global TCGv_i128, there is no visible state * changed if the second load faults. Load directly into the two @@ -613,7 +576,8 @@ static void tcg_gen_qemu_ld_i128_int(TCGv_i128 val, TCGTemp *addr, y = TCGV128_LOW(val); } - gen_ldst_i64(opc, x, addr, make_memop_idx(mop[0], idx)); + gen_ldst_i64(INDEX_op_qemu_ld_i64, x, addr, + make_memop_idx(mop[0], idx)); if (need_bswap) { tcg_gen_bswap64_i64(x, x); @@ -629,7 +593,8 @@ static void tcg_gen_qemu_ld_i128_int(TCGv_i128 val, TCGTemp *addr, addr_p8 = tcgv_i64_temp(t); } - gen_ldst_i64(opc, y, addr_p8, make_memop_idx(mop[1], idx)); + gen_ldst_i64(INDEX_op_qemu_ld_i64, y, addr_p8, + make_memop_idx(mop[1], idx)); tcg_temp_free_internal(addr_p8); if (need_bswap) { @@ -663,7 +628,6 @@ static void tcg_gen_qemu_st_i128_int(TCGv_i128 val, TCGTemp *addr, { MemOpIdx orig_oi; TCGv_i64 ext_addr = NULL; - TCGOpcode opc; check_max_alignment(memop_alignment_bits(memop)); tcg_gen_req_mo(TCG_MO_ST_LD | TCG_MO_ST_ST); @@ -694,13 +658,8 @@ static void tcg_gen_qemu_st_i128_int(TCGv_i128 val, TCGTemp *addr, hi = TCGV128_HIGH(val); } - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_st_a32_i128; - } else { - opc = INDEX_op_qemu_st_a64_i128; - } - gen_ldst(opc, TCG_TYPE_I128, tcgv_i64_temp(lo), - tcgv_i64_temp(hi), addr, oi); + gen_ldst(INDEX_op_qemu_st_i128, TCG_TYPE_I128, + tcgv_i64_temp(lo), tcgv_i64_temp(hi), addr, oi); if (need_bswap) { tcg_temp_free_i64(lo); @@ -713,12 +672,6 @@ static void tcg_gen_qemu_st_i128_int(TCGv_i128 val, TCGTemp *addr, canonicalize_memop_i128_as_i64(mop, memop); - if (tcg_ctx->addr_type == TCG_TYPE_I32) { - opc = INDEX_op_qemu_st_a32_i64; - } else { - opc = INDEX_op_qemu_st_a64_i64; - } - if ((memop & MO_BSWAP) == MO_LE) { x = TCGV128_LOW(val); y = TCGV128_HIGH(val); @@ -733,7 +686,8 @@ static void tcg_gen_qemu_st_i128_int(TCGv_i128 val, TCGTemp *addr, x = b; } - gen_ldst_i64(opc, x, addr, make_memop_idx(mop[0], idx)); + gen_ldst_i64(INDEX_op_qemu_st_i64, x, addr, + make_memop_idx(mop[0], idx)); if (tcg_ctx->addr_type == TCG_TYPE_I32) { TCGv_i32 t = tcg_temp_ebb_new_i32(); @@ -747,10 +701,12 @@ static void tcg_gen_qemu_st_i128_int(TCGv_i128 val, TCGTemp *addr, if (b) { tcg_gen_bswap64_i64(b, y); - gen_ldst_i64(opc, b, addr_p8, make_memop_idx(mop[1], idx)); + gen_ldst_i64(INDEX_op_qemu_st_i64, b, addr_p8, + make_memop_idx(mop[1], idx)); tcg_temp_free_i64(b); } else { - gen_ldst_i64(opc, y, addr_p8, make_memop_idx(mop[1], idx)); + gen_ldst_i64(INDEX_op_qemu_st_i64, y, addr_p8, + make_memop_idx(mop[1], idx)); } tcg_temp_free_internal(addr_p8); } else { diff --git a/tcg/tcg.c b/tcg/tcg.c index 14c2d3816011..fef93b25ffb2 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -2147,24 +2147,17 @@ bool tcg_op_supported(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_exit_tb: case INDEX_op_goto_tb: case INDEX_op_goto_ptr: - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_ld_i64: + case INDEX_op_qemu_st_i64: return true; - case INDEX_op_qemu_st8_a32_i32: - case INDEX_op_qemu_st8_a64_i32: + case INDEX_op_qemu_st8_i32: return TCG_TARGET_HAS_qemu_st8_i32; - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_ld_i128: + case INDEX_op_qemu_st_i128: return TCG_TARGET_HAS_qemu_ldst_i128; case INDEX_op_mov_i32: @@ -2862,20 +2855,13 @@ void tcg_dump_ops(TCGContext *s, FILE *f, bool have_prefs) } i = 1; break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_st_a32_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_st8_a32_i32: - case INDEX_op_qemu_st8_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_ld_a64_i64: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_st_a64_i64: - case INDEX_op_qemu_ld_a32_i128: - case INDEX_op_qemu_ld_a64_i128: - case INDEX_op_qemu_st_a32_i128: - case INDEX_op_qemu_st_a64_i128: + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_st_i32: + case INDEX_op_qemu_st8_i32: + case INDEX_op_qemu_ld_i64: + case INDEX_op_qemu_st_i64: + case INDEX_op_qemu_ld_i128: + case INDEX_op_qemu_st_i128: { const char *s_al, *s_op, *s_at; MemOpIdx oi = op->args[k++]; diff --git a/tcg/tci.c b/tcg/tci.c index 8c1c53424dbd..d223258efe0d 100644 --- a/tcg/tci.c +++ b/tcg/tci.c @@ -154,16 +154,6 @@ static void tci_args_rrrbb(uint32_t insn, TCGReg *r0, TCGReg *r1, *i4 = extract32(insn, 26, 6); } -static void tci_args_rrrrr(uint32_t insn, TCGReg *r0, TCGReg *r1, - TCGReg *r2, TCGReg *r3, TCGReg *r4) -{ - *r0 = extract32(insn, 8, 4); - *r1 = extract32(insn, 12, 4); - *r2 = extract32(insn, 16, 4); - *r3 = extract32(insn, 20, 4); - *r4 = extract32(insn, 24, 4); -} - static void tci_args_rrrr(uint32_t insn, TCGReg *r0, TCGReg *r1, TCGReg *r2, TCGReg *r3) { @@ -912,43 +902,21 @@ uintptr_t QEMU_DISABLE_CFI tcg_qemu_tb_exec(CPUArchState *env, tb_ptr = ptr; break; - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: tci_args_rrm(insn, &r0, &r1, &oi); - taddr = (uint32_t)regs[r1]; - goto do_ld_i32; - case INDEX_op_qemu_ld_a64_i32: - if (TCG_TARGET_REG_BITS == 64) { - tci_args_rrm(insn, &r0, &r1, &oi); - taddr = regs[r1]; - } else { - tci_args_rrrr(insn, &r0, &r1, &r2, &r3); - taddr = tci_uint64(regs[r2], regs[r1]); - oi = regs[r3]; - } - do_ld_i32: + taddr = regs[r1]; regs[r0] = tci_qemu_ld(env, taddr, oi, tb_ptr); break; - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { tci_args_rrm(insn, &r0, &r1, &oi); - taddr = (uint32_t)regs[r1]; + taddr = regs[r1]; } else { tci_args_rrrr(insn, &r0, &r1, &r2, &r3); - taddr = (uint32_t)regs[r2]; + taddr = regs[r2]; oi = regs[r3]; } - goto do_ld_i64; - case INDEX_op_qemu_ld_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tci_args_rrm(insn, &r0, &r1, &oi); - taddr = regs[r1]; - } else { - tci_args_rrrrr(insn, &r0, &r1, &r2, &r3, &r4); - taddr = tci_uint64(regs[r3], regs[r2]); - oi = regs[r4]; - } - do_ld_i64: tmp64 = tci_qemu_ld(env, taddr, oi, tb_ptr); if (TCG_TARGET_REG_BITS == 32) { tci_write_reg64(regs, r1, r0, tmp64); @@ -957,47 +925,23 @@ uintptr_t QEMU_DISABLE_CFI tcg_qemu_tb_exec(CPUArchState *env, } break; - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: tci_args_rrm(insn, &r0, &r1, &oi); - taddr = (uint32_t)regs[r1]; - goto do_st_i32; - case INDEX_op_qemu_st_a64_i32: - if (TCG_TARGET_REG_BITS == 64) { - tci_args_rrm(insn, &r0, &r1, &oi); - taddr = regs[r1]; - } else { - tci_args_rrrr(insn, &r0, &r1, &r2, &r3); - taddr = tci_uint64(regs[r2], regs[r1]); - oi = regs[r3]; - } - do_st_i32: + taddr = regs[r1]; tci_qemu_st(env, taddr, regs[r0], oi, tb_ptr); break; - case INDEX_op_qemu_st_a32_i64: + case INDEX_op_qemu_st_i64: if (TCG_TARGET_REG_BITS == 64) { tci_args_rrm(insn, &r0, &r1, &oi); tmp64 = regs[r0]; - taddr = (uint32_t)regs[r1]; + taddr = regs[r1]; } else { tci_args_rrrr(insn, &r0, &r1, &r2, &r3); tmp64 = tci_uint64(regs[r1], regs[r0]); - taddr = (uint32_t)regs[r2]; + taddr = regs[r2]; oi = regs[r3]; } - goto do_st_i64; - case INDEX_op_qemu_st_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tci_args_rrm(insn, &r0, &r1, &oi); - tmp64 = regs[r0]; - taddr = regs[r1]; - } else { - tci_args_rrrrr(insn, &r0, &r1, &r2, &r3, &r4); - tmp64 = tci_uint64(regs[r1], regs[r0]); - taddr = tci_uint64(regs[r3], regs[r2]); - oi = regs[r4]; - } - do_st_i64: tci_qemu_st(env, taddr, tmp64, oi, tb_ptr); break; @@ -1269,42 +1213,21 @@ int print_insn_tci(bfd_vma addr, disassemble_info *info) str_r(r3), str_r(r4), str_r(r5)); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_st_a32_i32: - len = 1 + 1; - goto do_qemu_ldst; - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_st_a32_i64: - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_st_a64_i32: - len = 1 + DIV_ROUND_UP(64, TCG_TARGET_REG_BITS); - goto do_qemu_ldst; - case INDEX_op_qemu_ld_a64_i64: - case INDEX_op_qemu_st_a64_i64: - len = 2 * DIV_ROUND_UP(64, TCG_TARGET_REG_BITS); - goto do_qemu_ldst; - do_qemu_ldst: - switch (len) { - case 2: - tci_args_rrm(insn, &r0, &r1, &oi); - info->fprintf_func(info->stream, "%-12s %s, %s, %x", - op_name, str_r(r0), str_r(r1), oi); - break; - case 3: + case INDEX_op_qemu_ld_i64: + case INDEX_op_qemu_st_i64: + if (TCG_TARGET_REG_BITS == 32) { tci_args_rrrr(insn, &r0, &r1, &r2, &r3); info->fprintf_func(info->stream, "%-12s %s, %s, %s, %s", op_name, str_r(r0), str_r(r1), str_r(r2), str_r(r3)); break; - case 4: - tci_args_rrrrr(insn, &r0, &r1, &r2, &r3, &r4); - info->fprintf_func(info->stream, "%-12s %s, %s, %s, %s, %s", - op_name, str_r(r0), str_r(r1), - str_r(r2), str_r(r3), str_r(r4)); - break; - default: - g_assert_not_reached(); } + /* fall through */ + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_st_i32: + tci_args_rrm(insn, &r0, &r1, &oi); + info->fprintf_func(info->stream, "%-12s %s, %s, %x", + op_name, str_r(r0), str_r(r1), oi); break; case 0: diff --git a/tcg/tci/tcg-target.c.inc b/tcg/tci/tcg-target.c.inc index d6c77325a3ea..36e018dd1911 100644 --- a/tcg/tci/tcg-target.c.inc +++ b/tcg/tci/tcg-target.c.inc @@ -169,22 +169,14 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_setcond2_i32: return C_O1_I4(r, r, r, r, r); - case INDEX_op_qemu_ld_a32_i32: + case INDEX_op_qemu_ld_i32: return C_O1_I1(r, r); - case INDEX_op_qemu_ld_a64_i32: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O1_I2(r, r, r); - case INDEX_op_qemu_ld_a32_i64: + case INDEX_op_qemu_ld_i64: return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O2_I1(r, r, r); - case INDEX_op_qemu_ld_a64_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O2_I2(r, r, r, r); - case INDEX_op_qemu_st_a32_i32: + case INDEX_op_qemu_st_i32: return C_O0_I2(r, r); - case INDEX_op_qemu_st_a64_i32: + case INDEX_op_qemu_st_i64: return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(r, r) : C_O0_I3(r, r, r); - case INDEX_op_qemu_st_a32_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(r, r) : C_O0_I3(r, r, r); - case INDEX_op_qemu_st_a64_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(r, r) : C_O0_I4(r, r, r, r); default: return C_NotImplemented; @@ -422,20 +414,6 @@ static void tcg_out_op_rrrbb(TCGContext *s, TCGOpcode op, TCGReg r0, tcg_out32(s, insn); } -static void tcg_out_op_rrrrr(TCGContext *s, TCGOpcode op, TCGReg r0, - TCGReg r1, TCGReg r2, TCGReg r3, TCGReg r4) -{ - tcg_insn_unit insn = 0; - - insn = deposit32(insn, 0, 8, op); - insn = deposit32(insn, 8, 4, r0); - insn = deposit32(insn, 12, 4, r1); - insn = deposit32(insn, 16, 4, r2); - insn = deposit32(insn, 20, 4, r3); - insn = deposit32(insn, 24, 4, r4); - tcg_out32(s, insn); -} - static void tcg_out_op_rrrr(TCGContext *s, TCGOpcode op, TCGReg r0, TCGReg r1, TCGReg r2, TCGReg r3) { @@ -833,29 +811,21 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, tcg_out_op_rrrr(s, opc, args[0], args[1], args[2], args[3]); break; - case INDEX_op_qemu_ld_a32_i32: - case INDEX_op_qemu_st_a32_i32: - tcg_out_op_rrm(s, opc, args[0], args[1], args[2]); - break; - case INDEX_op_qemu_ld_a64_i32: - case INDEX_op_qemu_st_a64_i32: - case INDEX_op_qemu_ld_a32_i64: - case INDEX_op_qemu_st_a32_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_op_rrm(s, opc, args[0], args[1], args[2]); - } else { + case INDEX_op_qemu_ld_i64: + case INDEX_op_qemu_st_i64: + if (TCG_TARGET_REG_BITS == 32) { tcg_out_movi(s, TCG_TYPE_I32, TCG_REG_TMP, args[3]); tcg_out_op_rrrr(s, opc, args[0], args[1], args[2], TCG_REG_TMP); + break; } - break; - case INDEX_op_qemu_ld_a64_i64: - case INDEX_op_qemu_st_a64_i64: - if (TCG_TARGET_REG_BITS == 64) { - tcg_out_op_rrm(s, opc, args[0], args[1], args[2]); + /* fall through */ + case INDEX_op_qemu_ld_i32: + case INDEX_op_qemu_st_i32: + if (TCG_TARGET_REG_BITS == 64 && s->addr_type == TCG_TYPE_I32) { + tcg_out_ext32u(s, TCG_REG_TMP, args[1]); + tcg_out_op_rrm(s, opc, args[0], TCG_REG_TMP, args[2]); } else { - tcg_out_movi(s, TCG_TYPE_I32, TCG_REG_TMP, args[4]); - tcg_out_op_rrrrr(s, opc, args[0], args[1], - args[2], args[3], TCG_REG_TMP); + tcg_out_op_rrm(s, opc, args[0], args[1], args[2]); } break; From d9a8889f6d10f586e28f4bfb8611cf0bc36fcaa9 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 17:20:55 -0800 Subject: [PATCH 0116/1179] tcg/arm: Drop addrhi from prepare_host_addr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guest address will now always be TCG_TYPE_I32. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/arm/tcg-target.c.inc | 73 +++++++++++++--------------------------- 1 file changed, 23 insertions(+), 50 deletions(-) diff --git a/tcg/arm/tcg-target.c.inc b/tcg/arm/tcg-target.c.inc index 05bb367a3971..93a3ccaf664e 100644 --- a/tcg/arm/tcg-target.c.inc +++ b/tcg/arm/tcg-target.c.inc @@ -676,14 +676,8 @@ static void tcg_out_ldrd_r(TCGContext *s, ARMCond cond, TCGReg rt, tcg_out_memop_r(s, cond, INSN_LDRD_REG, rt, rn, rm, 1, 1, 0); } -static void __attribute__((unused)) -tcg_out_ldrd_rwb(TCGContext *s, ARMCond cond, TCGReg rt, TCGReg rn, TCGReg rm) -{ - tcg_out_memop_r(s, cond, INSN_LDRD_REG, rt, rn, rm, 1, 1, 1); -} - -static void __attribute__((unused)) -tcg_out_strd_8(TCGContext *s, ARMCond cond, TCGReg rt, TCGReg rn, int imm8) +static void tcg_out_strd_8(TCGContext *s, ARMCond cond, TCGReg rt, + TCGReg rn, int imm8) { tcg_out_memop_8(s, cond, INSN_STRD_IMM, rt, rn, imm8, 1, 0); } @@ -1455,8 +1449,7 @@ static bool tcg_out_qemu_st_slow_path(TCGContext *s, TCGLabelQemuLdst *lb) #define MIN_TLB_MASK_TABLE_OFS -256 static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, bool is_ld) + TCGReg addr, MemOpIdx oi, bool is_ld) { TCGLabelQemuLdst *ldst = NULL; MemOp opc = get_memop(oi); @@ -1465,14 +1458,14 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, if (tcg_use_softmmu) { *h = (HostAddress){ .cond = COND_AL, - .base = addrlo, + .base = addr, .index = TCG_REG_R1, .index_scratch = true, }; } else { *h = (HostAddress){ .cond = COND_AL, - .base = addrlo, + .base = addr, .index = guest_base ? TCG_REG_GUEST_BASE : -1, .index_scratch = false, }; @@ -1492,8 +1485,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; /* Load cpu->neg.tlb.f[mmu_idx].{mask,table} into {r0,r1}. */ QEMU_BUILD_BUG_ON(offsetof(CPUTLBDescFast, mask) != 0); @@ -1501,30 +1493,20 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, tcg_out_ldrd_8(s, COND_AL, TCG_REG_R0, TCG_AREG0, fast_off); /* Extract the tlb index from the address into R0. */ - tcg_out_dat_reg(s, COND_AL, ARITH_AND, TCG_REG_R0, TCG_REG_R0, addrlo, + tcg_out_dat_reg(s, COND_AL, ARITH_AND, TCG_REG_R0, TCG_REG_R0, addr, SHIFT_IMM_LSR(s->page_bits - CPU_TLB_ENTRY_BITS)); /* * Add the tlb_table pointer, creating the CPUTLBEntry address in R1. - * Load the tlb comparator into R2/R3 and the fast path addend into R1. + * Load the tlb comparator into R2 and the fast path addend into R1. */ QEMU_BUILD_BUG_ON(HOST_BIG_ENDIAN); if (cmp_off == 0) { - if (s->addr_type == TCG_TYPE_I32) { - tcg_out_ld32_rwb(s, COND_AL, TCG_REG_R2, - TCG_REG_R1, TCG_REG_R0); - } else { - tcg_out_ldrd_rwb(s, COND_AL, TCG_REG_R2, - TCG_REG_R1, TCG_REG_R0); - } + tcg_out_ld32_rwb(s, COND_AL, TCG_REG_R2, TCG_REG_R1, TCG_REG_R0); } else { tcg_out_dat_reg(s, COND_AL, ARITH_ADD, TCG_REG_R1, TCG_REG_R1, TCG_REG_R0, 0); - if (s->addr_type == TCG_TYPE_I32) { - tcg_out_ld32_12(s, COND_AL, TCG_REG_R2, TCG_REG_R1, cmp_off); - } else { - tcg_out_ldrd_8(s, COND_AL, TCG_REG_R2, TCG_REG_R1, cmp_off); - } + tcg_out_ld32_12(s, COND_AL, TCG_REG_R2, TCG_REG_R1, cmp_off); } /* Load the tlb addend. */ @@ -1543,11 +1525,11 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, * This leaves the least significant alignment bits unchanged, and of * course must be zero. */ - t_addr = addrlo; + t_addr = addr; if (a_mask < s_mask) { t_addr = TCG_REG_R0; tcg_out_dat_imm(s, COND_AL, ARITH_ADD, t_addr, - addrlo, s_mask - a_mask); + addr, s_mask - a_mask); } if (use_armv7_instructions && s->page_bits <= 16) { tcg_out_movi32(s, COND_AL, TCG_REG_TMP, ~(s->page_mask | a_mask)); @@ -1558,7 +1540,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, } else { if (a_mask) { tcg_debug_assert(a_mask <= 0xff); - tcg_out_dat_imm(s, COND_AL, ARITH_TST, 0, addrlo, a_mask); + tcg_out_dat_imm(s, COND_AL, ARITH_TST, 0, addr, a_mask); } tcg_out_dat_reg(s, COND_AL, ARITH_MOV, TCG_REG_TMP, 0, t_addr, SHIFT_IMM_LSR(s->page_bits)); @@ -1566,21 +1548,16 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, 0, TCG_REG_R2, TCG_REG_TMP, SHIFT_IMM_LSL(s->page_bits)); } - - if (s->addr_type != TCG_TYPE_I32) { - tcg_out_dat_reg(s, COND_EQ, ARITH_CMP, 0, TCG_REG_R3, addrhi, 0); - } } else if (a_mask) { ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; /* We are expecting alignment to max out at 7 */ tcg_debug_assert(a_mask <= 0xff); /* tst addr, #mask */ - tcg_out_dat_imm(s, COND_AL, ARITH_TST, 0, addrlo, a_mask); + tcg_out_dat_imm(s, COND_AL, ARITH_TST, 0, addr, a_mask); } return ldst; @@ -1678,14 +1655,13 @@ static void tcg_out_qemu_ld_direct(TCGContext *s, MemOp opc, TCGReg datalo, } static void tcg_out_qemu_ld(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { MemOp opc = get_memop(oi); TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, true); + ldst = prepare_host_addr(s, &h, addr, oi, true); if (ldst) { ldst->type = data_type; ldst->datalo_reg = datalo; @@ -1764,14 +1740,13 @@ static void tcg_out_qemu_st_direct(TCGContext *s, MemOp opc, TCGReg datalo, } static void tcg_out_qemu_st(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { MemOp opc = get_memop(oi); TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, false); + ldst = prepare_host_addr(s, &h, addr, oi, false); if (ldst) { ldst->type = data_type; ldst->datalo_reg = datalo; @@ -2072,19 +2047,17 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, break; case INDEX_op_qemu_ld_i32: - tcg_out_qemu_ld(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); + tcg_out_qemu_ld(s, args[0], -1, args[1], args[2], TCG_TYPE_I32); break; case INDEX_op_qemu_ld_i64: - tcg_out_qemu_ld(s, args[0], args[1], args[2], -1, - args[3], TCG_TYPE_I64); + tcg_out_qemu_ld(s, args[0], args[1], args[2], args[3], TCG_TYPE_I64); break; case INDEX_op_qemu_st_i32: - tcg_out_qemu_st(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); + tcg_out_qemu_st(s, args[0], -1, args[1], args[2], TCG_TYPE_I32); break; case INDEX_op_qemu_st_i64: - tcg_out_qemu_st(s, args[0], args[1], args[2], -1, - args[3], TCG_TYPE_I64); + tcg_out_qemu_st(s, args[0], args[1], args[2], args[3], TCG_TYPE_I64); break; case INDEX_op_bswap16_i32: From dc8e2f8f7840835e51f23b891b75a79101efc91c Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 17:36:35 -0800 Subject: [PATCH 0117/1179] tcg/i386: Drop addrhi from prepare_host_addr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guest address will now always fit in one register. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/i386/tcg-target.c.inc | 56 ++++++++++++++------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/tcg/i386/tcg-target.c.inc b/tcg/i386/tcg-target.c.inc index ca6e8abc57ce..b33fe7fe2346 100644 --- a/tcg/i386/tcg-target.c.inc +++ b/tcg/i386/tcg-target.c.inc @@ -2169,8 +2169,7 @@ static inline int setup_guest_base_seg(void) * is required and fill in @h with the host address for the fast path. */ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, bool is_ld) + TCGReg addr, MemOpIdx oi, bool is_ld) { TCGLabelQemuLdst *ldst = NULL; MemOp opc = get_memop(oi); @@ -2184,7 +2183,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, } else { *h = x86_guest_base; } - h->base = addrlo; + h->base = addr; h->aa = atom_and_align_for_opc(s, opc, MO_ATOM_IFALIGN, s_bits == MO_128); a_mask = (1 << h->aa.align) - 1; @@ -2202,8 +2201,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; if (TCG_TARGET_REG_BITS == 64) { ttype = s->addr_type; @@ -2217,7 +2215,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, } } - tcg_out_mov(s, tlbtype, TCG_REG_L0, addrlo); + tcg_out_mov(s, tlbtype, TCG_REG_L0, addr); tcg_out_shifti(s, SHIFT_SHR + tlbrexw, TCG_REG_L0, s->page_bits - CPU_TLB_ENTRY_BITS); @@ -2233,10 +2231,10 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, * check that we don't cross pages for the complete access. */ if (a_mask >= s_mask) { - tcg_out_mov(s, ttype, TCG_REG_L1, addrlo); + tcg_out_mov(s, ttype, TCG_REG_L1, addr); } else { tcg_out_modrm_offset(s, OPC_LEA + trexw, TCG_REG_L1, - addrlo, s_mask - a_mask); + addr, s_mask - a_mask); } tlb_mask = s->page_mask | a_mask; tgen_arithi(s, ARITH_AND + trexw, TCG_REG_L1, tlb_mask, 0); @@ -2250,17 +2248,6 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst->label_ptr[0] = s->code_ptr; s->code_ptr += 4; - if (TCG_TARGET_REG_BITS == 32 && s->addr_type == TCG_TYPE_I64) { - /* cmp 4(TCG_REG_L0), addrhi */ - tcg_out_modrm_offset(s, OPC_CMP_GvEv, addrhi, - TCG_REG_L0, cmp_ofs + 4); - - /* jne slow_path */ - tcg_out_opc(s, OPC_JCC_long + JCC_JNE, 0, 0, 0); - ldst->label_ptr[1] = s->code_ptr; - s->code_ptr += 4; - } - /* TLB Hit. */ tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_L0, TCG_REG_L0, offsetof(CPUTLBEntry, addend)); @@ -2270,11 +2257,10 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; /* jne slow_path */ - jcc = tcg_out_cmp(s, TCG_COND_TSTNE, addrlo, a_mask, true, false); + jcc = tcg_out_cmp(s, TCG_COND_TSTNE, addr, a_mask, true, false); tcg_out_opc(s, OPC_JCC_long + jcc, 0, 0, 0); ldst->label_ptr[0] = s->code_ptr; s->code_ptr += 4; @@ -2446,13 +2432,12 @@ static void tcg_out_qemu_ld_direct(TCGContext *s, TCGReg datalo, TCGReg datahi, } static void tcg_out_qemu_ld(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, true); + ldst = prepare_host_addr(s, &h, addr, oi, true); tcg_out_qemu_ld_direct(s, datalo, datahi, h, data_type, get_memop(oi)); if (ldst) { @@ -2574,13 +2559,12 @@ static void tcg_out_qemu_st_direct(TCGContext *s, TCGReg datalo, TCGReg datahi, } static void tcg_out_qemu_st(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, false); + ldst = prepare_host_addr(s, &h, addr, oi, false); tcg_out_qemu_st_direct(s, datalo, datahi, h, get_memop(oi)); if (ldst) { @@ -2880,34 +2864,34 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, break; case INDEX_op_qemu_ld_i32: - tcg_out_qemu_ld(s, a0, -1, a1, -1, a2, TCG_TYPE_I32); + tcg_out_qemu_ld(s, a0, -1, a1, a2, TCG_TYPE_I32); break; case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_ld(s, a0, -1, a1, -1, a2, TCG_TYPE_I64); + tcg_out_qemu_ld(s, a0, -1, a1, a2, TCG_TYPE_I64); } else { - tcg_out_qemu_ld(s, a0, a1, a2, -1, args[3], TCG_TYPE_I64); + tcg_out_qemu_ld(s, a0, a1, a2, args[3], TCG_TYPE_I64); } break; case INDEX_op_qemu_ld_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); - tcg_out_qemu_ld(s, a0, a1, a2, -1, args[3], TCG_TYPE_I128); + tcg_out_qemu_ld(s, a0, a1, a2, args[3], TCG_TYPE_I128); break; case INDEX_op_qemu_st_i32: case INDEX_op_qemu_st8_i32: - tcg_out_qemu_st(s, a0, -1, a1, -1, a2, TCG_TYPE_I32); + tcg_out_qemu_st(s, a0, -1, a1, a2, TCG_TYPE_I32); break; case INDEX_op_qemu_st_i64: if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_st(s, a0, -1, a1, -1, a2, TCG_TYPE_I64); + tcg_out_qemu_st(s, a0, -1, a1, a2, TCG_TYPE_I64); } else { - tcg_out_qemu_st(s, a0, a1, a2, -1, args[3], TCG_TYPE_I64); + tcg_out_qemu_st(s, a0, a1, a2, args[3], TCG_TYPE_I64); } break; case INDEX_op_qemu_st_i128: tcg_debug_assert(TCG_TARGET_REG_BITS == 64); - tcg_out_qemu_st(s, a0, a1, a2, -1, args[3], TCG_TYPE_I128); + tcg_out_qemu_st(s, a0, a1, a2, args[3], TCG_TYPE_I128); break; OP_32_64(mulu2): From 0d000618d92344e0575018dcaa8ebc01e2d589b3 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 17:43:23 -0800 Subject: [PATCH 0118/1179] tcg/mips: Drop addrhi from prepare_host_addr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guest address will now always fit in one register. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/mips/tcg-target.c.inc | 62 ++++++++++++++------------------------- 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/tcg/mips/tcg-target.c.inc b/tcg/mips/tcg-target.c.inc index b1d512ca2ab7..153ce1f3c3ca 100644 --- a/tcg/mips/tcg-target.c.inc +++ b/tcg/mips/tcg-target.c.inc @@ -1217,8 +1217,7 @@ bool tcg_target_has_memory_bswap(MemOp memop) * is required and fill in @h with the host address for the fast path. */ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, bool is_ld) + TCGReg addr, MemOpIdx oi, bool is_ld) { TCGType addr_type = s->addr_type; TCGLabelQemuLdst *ldst = NULL; @@ -1245,8 +1244,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; /* Load tlb_mask[mmu_idx] and tlb_table[mmu_idx]. */ tcg_out_ld(s, TCG_TYPE_PTR, TCG_TMP0, TCG_AREG0, mask_off); @@ -1254,11 +1252,10 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, /* Extract the TLB index from the address into TMP3. */ if (TCG_TARGET_REG_BITS == 32 || addr_type == TCG_TYPE_I32) { - tcg_out_opc_sa(s, OPC_SRL, TCG_TMP3, addrlo, + tcg_out_opc_sa(s, OPC_SRL, TCG_TMP3, addr, s->page_bits - CPU_TLB_ENTRY_BITS); } else { - tcg_out_dsrl(s, TCG_TMP3, addrlo, - s->page_bits - CPU_TLB_ENTRY_BITS); + tcg_out_dsrl(s, TCG_TMP3, addr, s->page_bits - CPU_TLB_ENTRY_BITS); } tcg_out_opc_reg(s, OPC_AND, TCG_TMP3, TCG_TMP3, TCG_TMP0); @@ -1288,48 +1285,35 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, tcg_out_opc_imm(s, (TCG_TARGET_REG_BITS == 32 || addr_type == TCG_TYPE_I32 ? OPC_ADDIU : OPC_DADDIU), - TCG_TMP2, addrlo, s_mask - a_mask); + TCG_TMP2, addr, s_mask - a_mask); tcg_out_opc_reg(s, OPC_AND, TCG_TMP1, TCG_TMP1, TCG_TMP2); } else { - tcg_out_opc_reg(s, OPC_AND, TCG_TMP1, TCG_TMP1, addrlo); + tcg_out_opc_reg(s, OPC_AND, TCG_TMP1, TCG_TMP1, addr); } /* Zero extend a 32-bit guest address for a 64-bit host. */ if (TCG_TARGET_REG_BITS == 64 && addr_type == TCG_TYPE_I32) { - tcg_out_ext32u(s, TCG_TMP2, addrlo); - addrlo = TCG_TMP2; + tcg_out_ext32u(s, TCG_TMP2, addr); + addr = TCG_TMP2; } ldst->label_ptr[0] = s->code_ptr; tcg_out_opc_br(s, OPC_BNE, TCG_TMP1, TCG_TMP0); - /* Load and test the high half tlb comparator. */ - if (TCG_TARGET_REG_BITS == 32 && addr_type != TCG_TYPE_I32) { - /* delay slot */ - tcg_out_ldst(s, OPC_LW, TCG_TMP0, TCG_TMP3, cmp_off + HI_OFF); - - /* Load the tlb addend for the fast path. */ - tcg_out_ld(s, TCG_TYPE_PTR, TCG_TMP3, TCG_TMP3, add_off); - - ldst->label_ptr[1] = s->code_ptr; - tcg_out_opc_br(s, OPC_BNE, addrhi, TCG_TMP0); - } - /* delay slot */ base = TCG_TMP3; - tcg_out_opc_reg(s, ALIAS_PADD, base, TCG_TMP3, addrlo); + tcg_out_opc_reg(s, ALIAS_PADD, base, TCG_TMP3, addr); } else { if (a_mask && (use_mips32r6_instructions || a_bits != s_bits)) { ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; /* We are expecting a_bits to max out at 7, much lower than ANDI. */ tcg_debug_assert(a_bits < 16); - tcg_out_opc_imm(s, OPC_ANDI, TCG_TMP0, addrlo, a_mask); + tcg_out_opc_imm(s, OPC_ANDI, TCG_TMP0, addr, a_mask); ldst->label_ptr[0] = s->code_ptr; if (use_mips32r6_instructions) { @@ -1340,7 +1324,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, } } - base = addrlo; + base = addr; if (TCG_TARGET_REG_BITS == 64 && addr_type == TCG_TYPE_I32) { tcg_out_ext32u(s, TCG_REG_A0, base); base = TCG_REG_A0; @@ -1460,14 +1444,13 @@ static void tcg_out_qemu_ld_unalign(TCGContext *s, TCGReg lo, TCGReg hi, } static void tcg_out_qemu_ld(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { MemOp opc = get_memop(oi); TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, true); + ldst = prepare_host_addr(s, &h, addr, oi, true); if (use_mips32r6_instructions || h.aa.align >= (opc & MO_SIZE)) { tcg_out_qemu_ld_direct(s, datalo, datahi, h.base, opc, data_type); @@ -1547,14 +1530,13 @@ static void tcg_out_qemu_st_unalign(TCGContext *s, TCGReg lo, TCGReg hi, } static void tcg_out_qemu_st(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { MemOp opc = get_memop(oi); TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, false); + ldst = prepare_host_addr(s, &h, addr, oi, false); if (use_mips32r6_instructions || h.aa.align >= (opc & MO_SIZE)) { tcg_out_qemu_st_direct(s, datalo, datahi, h.base, opc); @@ -2096,24 +2078,24 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, break; case INDEX_op_qemu_ld_i32: - tcg_out_qemu_ld(s, a0, 0, a1, 0, a2, TCG_TYPE_I32); + tcg_out_qemu_ld(s, a0, 0, a1, a2, TCG_TYPE_I32); break; case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_ld(s, a0, 0, a1, 0, a2, TCG_TYPE_I64); + tcg_out_qemu_ld(s, a0, 0, a1, a2, TCG_TYPE_I64); } else { - tcg_out_qemu_ld(s, a0, a1, a2, 0, args[3], TCG_TYPE_I64); + tcg_out_qemu_ld(s, a0, a1, a2, args[3], TCG_TYPE_I64); } break; case INDEX_op_qemu_st_i32: - tcg_out_qemu_st(s, a0, 0, a1, 0, a2, TCG_TYPE_I32); + tcg_out_qemu_st(s, a0, 0, a1, a2, TCG_TYPE_I32); break; case INDEX_op_qemu_st_i64: if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_st(s, a0, 0, a1, 0, a2, TCG_TYPE_I64); + tcg_out_qemu_st(s, a0, 0, a1, a2, TCG_TYPE_I64); } else { - tcg_out_qemu_st(s, a0, a1, a2, 0, args[3], TCG_TYPE_I64); + tcg_out_qemu_st(s, a0, a1, a2, args[3], TCG_TYPE_I64); } break; From 7a967f34660c06f8d304fecd118f046fe21cb261 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 17:49:19 -0800 Subject: [PATCH 0119/1179] tcg/ppc: Drop addrhi from prepare_host_addr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guest address will now always fit in one register. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/ppc/tcg-target.c.inc | 75 ++++++++++++---------------------------- 1 file changed, 23 insertions(+), 52 deletions(-) diff --git a/tcg/ppc/tcg-target.c.inc b/tcg/ppc/tcg-target.c.inc index 801cb6f3cb28..74b93f4b57d1 100644 --- a/tcg/ppc/tcg-target.c.inc +++ b/tcg/ppc/tcg-target.c.inc @@ -2438,8 +2438,7 @@ bool tcg_target_has_memory_bswap(MemOp memop) * is required and fill in @h with the host address for the fast path. */ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, bool is_ld) + TCGReg addr, MemOpIdx oi, bool is_ld) { TCGType addr_type = s->addr_type; TCGLabelQemuLdst *ldst = NULL; @@ -2474,8 +2473,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; /* Load tlb_mask[mmu_idx] and tlb_table[mmu_idx]. */ tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP1, TCG_AREG0, mask_off); @@ -2483,10 +2481,10 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, /* Extract the page index, shifted into place for tlb index. */ if (TCG_TARGET_REG_BITS == 32) { - tcg_out_shri32(s, TCG_REG_R0, addrlo, + tcg_out_shri32(s, TCG_REG_R0, addr, s->page_bits - CPU_TLB_ENTRY_BITS); } else { - tcg_out_shri64(s, TCG_REG_R0, addrlo, + tcg_out_shri64(s, TCG_REG_R0, addr, s->page_bits - CPU_TLB_ENTRY_BITS); } tcg_out32(s, AND | SAB(TCG_REG_TMP1, TCG_REG_TMP1, TCG_REG_R0)); @@ -2534,10 +2532,10 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, if (a_bits < s_bits) { a_bits = s_bits; } - tcg_out_rlw(s, RLWINM, TCG_REG_R0, addrlo, 0, + tcg_out_rlw(s, RLWINM, TCG_REG_R0, addr, 0, (32 - a_bits) & 31, 31 - s->page_bits); } else { - TCGReg t = addrlo; + TCGReg t = addr; /* * If the access is unaligned, we need to make sure we fail if we @@ -2566,30 +2564,8 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, } } - if (TCG_TARGET_REG_BITS == 32 && addr_type != TCG_TYPE_I32) { - /* Low part comparison into cr7. */ - tcg_out_cmp(s, TCG_COND_EQ, TCG_REG_R0, TCG_REG_TMP2, - 0, 7, TCG_TYPE_I32); - - /* Load the high part TLB comparator into TMP2. */ - tcg_out_ld(s, TCG_TYPE_I32, TCG_REG_TMP2, TCG_REG_TMP1, - cmp_off + 4 * !HOST_BIG_ENDIAN); - - /* Load addend, deferred for this case. */ - tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP1, TCG_REG_TMP1, - offsetof(CPUTLBEntry, addend)); - - /* High part comparison into cr6. */ - tcg_out_cmp(s, TCG_COND_EQ, addrhi, TCG_REG_TMP2, - 0, 6, TCG_TYPE_I32); - - /* Combine comparisons into cr0. */ - tcg_out32(s, CRAND | BT(0, CR_EQ) | BA(6, CR_EQ) | BB(7, CR_EQ)); - } else { - /* Full comparison into cr0. */ - tcg_out_cmp(s, TCG_COND_EQ, TCG_REG_R0, TCG_REG_TMP2, - 0, 0, addr_type); - } + /* Full comparison into cr0. */ + tcg_out_cmp(s, TCG_COND_EQ, TCG_REG_R0, TCG_REG_TMP2, 0, 0, addr_type); /* Load a pointer into the current opcode w/conditional branch-link. */ ldst->label_ptr[0] = s->code_ptr; @@ -2601,12 +2577,11 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addrlo; - ldst->addrhi_reg = addrhi; + ldst->addrlo_reg = addr; /* We are expecting a_bits to max out at 7, much lower than ANDI. */ tcg_debug_assert(a_bits < 16); - tcg_out32(s, ANDI | SAI(addrlo, TCG_REG_R0, (1 << a_bits) - 1)); + tcg_out32(s, ANDI | SAI(addr, TCG_REG_R0, (1 << a_bits) - 1)); ldst->label_ptr[0] = s->code_ptr; tcg_out32(s, BC | BI(0, CR_EQ) | BO_COND_FALSE | LK); @@ -2617,24 +2592,23 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, if (TCG_TARGET_REG_BITS == 64 && addr_type == TCG_TYPE_I32) { /* Zero-extend the guest address for use in the host address. */ - tcg_out_ext32u(s, TCG_REG_TMP2, addrlo); + tcg_out_ext32u(s, TCG_REG_TMP2, addr); h->index = TCG_REG_TMP2; } else { - h->index = addrlo; + h->index = addr; } return ldst; } static void tcg_out_qemu_ld(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { MemOp opc = get_memop(oi); TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, true); + ldst = prepare_host_addr(s, &h, addr, oi, true); if (TCG_TARGET_REG_BITS == 32 && (opc & MO_SIZE) == MO_64) { if (opc & MO_BSWAP) { @@ -2678,14 +2652,13 @@ static void tcg_out_qemu_ld(TCGContext *s, TCGReg datalo, TCGReg datahi, } static void tcg_out_qemu_st(TCGContext *s, TCGReg datalo, TCGReg datahi, - TCGReg addrlo, TCGReg addrhi, - MemOpIdx oi, TCGType data_type) + TCGReg addr, MemOpIdx oi, TCGType data_type) { MemOp opc = get_memop(oi); TCGLabelQemuLdst *ldst; HostAddress h; - ldst = prepare_host_addr(s, &h, addrlo, addrhi, oi, false); + ldst = prepare_host_addr(s, &h, addr, oi, false); if (TCG_TARGET_REG_BITS == 32 && (opc & MO_SIZE) == MO_64) { if (opc & MO_BSWAP) { @@ -2729,7 +2702,7 @@ static void tcg_out_qemu_ldst_i128(TCGContext *s, TCGReg datalo, TCGReg datahi, uint32_t insn; TCGReg index; - ldst = prepare_host_addr(s, &h, addr_reg, -1, oi, is_ld); + ldst = prepare_host_addr(s, &h, addr_reg, oi, is_ld); /* Compose the final address, as LQ/STQ have no indexing. */ index = h.index; @@ -3309,14 +3282,13 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, break; case INDEX_op_qemu_ld_i32: - tcg_out_qemu_ld(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); + tcg_out_qemu_ld(s, args[0], -1, args[1], args[2], TCG_TYPE_I32); break; case INDEX_op_qemu_ld_i64: if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_ld(s, args[0], -1, args[1], -1, - args[2], TCG_TYPE_I64); + tcg_out_qemu_ld(s, args[0], -1, args[1], args[2], TCG_TYPE_I64); } else { - tcg_out_qemu_ld(s, args[0], args[1], args[2], -1, + tcg_out_qemu_ld(s, args[0], args[1], args[2], args[3], TCG_TYPE_I64); } break; @@ -3326,14 +3298,13 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, break; case INDEX_op_qemu_st_i32: - tcg_out_qemu_st(s, args[0], -1, args[1], -1, args[2], TCG_TYPE_I32); + tcg_out_qemu_st(s, args[0], -1, args[1], args[2], TCG_TYPE_I32); break; case INDEX_op_qemu_st_i64: if (TCG_TARGET_REG_BITS == 64) { - tcg_out_qemu_st(s, args[0], -1, args[1], -1, - args[2], TCG_TYPE_I64); + tcg_out_qemu_st(s, args[0], -1, args[1], args[2], TCG_TYPE_I64); } else { - tcg_out_qemu_st(s, args[0], args[1], args[2], -1, + tcg_out_qemu_st(s, args[0], args[1], args[2], args[3], TCG_TYPE_I64); } break; From 0cd38379a8f47f3bbfb0b0c8419a7de28f8c9b8c Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 18:08:19 -0800 Subject: [PATCH 0120/1179] tcg: Replace addr{lo,hi}_reg with addr_reg in TCGLabelQemuLdst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is now always only one guest address register. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/aarch64/tcg-target.c.inc | 4 ++-- tcg/arm/tcg-target.c.inc | 4 ++-- tcg/i386/tcg-target.c.inc | 4 ++-- tcg/loongarch64/tcg-target.c.inc | 4 ++-- tcg/mips/tcg-target.c.inc | 4 ++-- tcg/ppc/tcg-target.c.inc | 4 ++-- tcg/riscv/tcg-target.c.inc | 4 ++-- tcg/s390x/tcg-target.c.inc | 4 ++-- tcg/sparc64/tcg-target.c.inc | 4 ++-- tcg/tcg.c | 18 +++++++++--------- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tcg/aarch64/tcg-target.c.inc b/tcg/aarch64/tcg-target.c.inc index 45dc2c649b57..6f383c1592a2 100644 --- a/tcg/aarch64/tcg-target.c.inc +++ b/tcg/aarch64/tcg-target.c.inc @@ -1775,7 +1775,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; mask_type = (s->page_bits + s->tlb_dyn_max_bits > 32 ? TCG_TYPE_I64 : TCG_TYPE_I32); @@ -1837,7 +1837,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; /* tst addr, #mask */ tcg_out_logicali(s, I3404_ANDSI, 0, TCG_REG_XZR, addr_reg, a_mask); diff --git a/tcg/arm/tcg-target.c.inc b/tcg/arm/tcg-target.c.inc index 93a3ccaf664e..83f6eb6099ff 100644 --- a/tcg/arm/tcg-target.c.inc +++ b/tcg/arm/tcg-target.c.inc @@ -1485,7 +1485,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; /* Load cpu->neg.tlb.f[mmu_idx].{mask,table} into {r0,r1}. */ QEMU_BUILD_BUG_ON(offsetof(CPUTLBDescFast, mask) != 0); @@ -1552,7 +1552,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; /* We are expecting alignment to max out at 7 */ tcg_debug_assert(a_mask <= 0xff); diff --git a/tcg/i386/tcg-target.c.inc b/tcg/i386/tcg-target.c.inc index b33fe7fe2346..cfea4c496dd0 100644 --- a/tcg/i386/tcg-target.c.inc +++ b/tcg/i386/tcg-target.c.inc @@ -2201,7 +2201,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; if (TCG_TARGET_REG_BITS == 64) { ttype = s->addr_type; @@ -2257,7 +2257,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; /* jne slow_path */ jcc = tcg_out_cmp(s, TCG_COND_TSTNE, addr, a_mask, true, false); diff --git a/tcg/loongarch64/tcg-target.c.inc b/tcg/loongarch64/tcg-target.c.inc index 4f32bf3e97b1..dd67e8f6bcb7 100644 --- a/tcg/loongarch64/tcg-target.c.inc +++ b/tcg/loongarch64/tcg-target.c.inc @@ -1010,7 +1010,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP0, TCG_AREG0, mask_ofs); tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP1, TCG_AREG0, table_ofs); @@ -1055,7 +1055,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; /* * Without micro-architecture details, we don't know which of diff --git a/tcg/mips/tcg-target.c.inc b/tcg/mips/tcg-target.c.inc index 153ce1f3c3ca..d744b853cdc7 100644 --- a/tcg/mips/tcg-target.c.inc +++ b/tcg/mips/tcg-target.c.inc @@ -1244,7 +1244,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; /* Load tlb_mask[mmu_idx] and tlb_table[mmu_idx]. */ tcg_out_ld(s, TCG_TYPE_PTR, TCG_TMP0, TCG_AREG0, mask_off); @@ -1309,7 +1309,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; /* We are expecting a_bits to max out at 7, much lower than ANDI. */ tcg_debug_assert(a_bits < 16); diff --git a/tcg/ppc/tcg-target.c.inc b/tcg/ppc/tcg-target.c.inc index 74b93f4b57d1..2d16807ec79b 100644 --- a/tcg/ppc/tcg-target.c.inc +++ b/tcg/ppc/tcg-target.c.inc @@ -2473,7 +2473,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; /* Load tlb_mask[mmu_idx] and tlb_table[mmu_idx]. */ tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP1, TCG_AREG0, mask_off); @@ -2577,7 +2577,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr; + ldst->addr_reg = addr; /* We are expecting a_bits to max out at 7, much lower than ANDI. */ tcg_debug_assert(a_bits < 16); diff --git a/tcg/riscv/tcg-target.c.inc b/tcg/riscv/tcg-target.c.inc index 55a3398712f3..689fbea0df81 100644 --- a/tcg/riscv/tcg-target.c.inc +++ b/tcg/riscv/tcg-target.c.inc @@ -1727,7 +1727,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, TCGReg *pbase, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; init_setting_vtype(s); @@ -1790,7 +1790,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, TCGReg *pbase, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; init_setting_vtype(s); diff --git a/tcg/s390x/tcg-target.c.inc b/tcg/s390x/tcg-target.c.inc index 6786e7b316e6..b2e1cd60ffae 100644 --- a/tcg/s390x/tcg-target.c.inc +++ b/tcg/s390x/tcg-target.c.inc @@ -1920,7 +1920,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; tcg_out_sh64(s, RSY_SRLG, TCG_TMP0, addr_reg, TCG_REG_NONE, s->page_bits - CPU_TLB_ENTRY_BITS); @@ -1974,7 +1974,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; tcg_debug_assert(a_mask <= 0xffff); tcg_out_insn(s, RI, TMLL, addr_reg, a_mask); diff --git a/tcg/sparc64/tcg-target.c.inc b/tcg/sparc64/tcg-target.c.inc index ea0a3b8692da..527af5665d57 100644 --- a/tcg/sparc64/tcg-target.c.inc +++ b/tcg/sparc64/tcg-target.c.inc @@ -1127,7 +1127,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; ldst->label_ptr[0] = s->code_ptr; /* bne,pn %[xi]cc, label0 */ @@ -1147,7 +1147,7 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, ldst = new_ldst_label(s); ldst->is_ld = is_ld; ldst->oi = oi; - ldst->addrlo_reg = addr_reg; + ldst->addr_reg = addr_reg; ldst->label_ptr[0] = s->code_ptr; /* bne,pn %icc, label0 */ diff --git a/tcg/tcg.c b/tcg/tcg.c index fef93b25ffb2..55cb9b3ac7a1 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -100,8 +100,7 @@ struct TCGLabelQemuLdst { bool is_ld; /* qemu_ld: true, qemu_st: false */ MemOpIdx oi; TCGType type; /* result type of a load */ - TCGReg addrlo_reg; /* reg index for low word of guest virtual addr */ - TCGReg addrhi_reg; /* reg index for high word of guest virtual addr */ + TCGReg addr_reg; /* reg index for guest virtual addr */ TCGReg datalo_reg; /* reg index for low word to be loaded or stored */ TCGReg datahi_reg; /* reg index for high word to be loaded or stored */ const tcg_insn_unit *raddr; /* addr of the next IR of qemu_ld/st IR */ @@ -6061,7 +6060,7 @@ static void tcg_out_ld_helper_args(TCGContext *s, const TCGLabelQemuLdst *ldst, */ tcg_out_helper_add_mov(mov, loc + HOST_BIG_ENDIAN, TCG_TYPE_I32, TCG_TYPE_I32, - ldst->addrlo_reg, -1); + ldst->addr_reg, -1); tcg_out_helper_load_slots(s, 1, mov, parm); tcg_out_helper_load_imm(s, loc[!HOST_BIG_ENDIAN].arg_slot, @@ -6069,7 +6068,7 @@ static void tcg_out_ld_helper_args(TCGContext *s, const TCGLabelQemuLdst *ldst, next_arg += 2; } else { nmov = tcg_out_helper_add_mov(mov, loc, TCG_TYPE_I64, s->addr_type, - ldst->addrlo_reg, ldst->addrhi_reg); + ldst->addr_reg, -1); tcg_out_helper_load_slots(s, nmov, mov, parm); next_arg += nmov; } @@ -6226,21 +6225,22 @@ static void tcg_out_st_helper_args(TCGContext *s, const TCGLabelQemuLdst *ldst, /* Handle addr argument. */ loc = &info->in[next_arg]; - if (TCG_TARGET_REG_BITS == 32 && s->addr_type == TCG_TYPE_I32) { + tcg_debug_assert(s->addr_type <= TCG_TYPE_REG); + if (TCG_TARGET_REG_BITS == 32) { /* - * 32-bit host with 32-bit guest: zero-extend the guest address + * 32-bit host (and thus 32-bit guest): zero-extend the guest address * to 64-bits for the helper by storing the low part. Later, * after we have processed the register inputs, we will load a * zero for the high part. */ tcg_out_helper_add_mov(mov, loc + HOST_BIG_ENDIAN, TCG_TYPE_I32, TCG_TYPE_I32, - ldst->addrlo_reg, -1); + ldst->addr_reg, -1); next_arg += 2; nmov += 1; } else { n = tcg_out_helper_add_mov(mov, loc, TCG_TYPE_I64, s->addr_type, - ldst->addrlo_reg, ldst->addrhi_reg); + ldst->addr_reg, -1); next_arg += n; nmov += n; } @@ -6288,7 +6288,7 @@ static void tcg_out_st_helper_args(TCGContext *s, const TCGLabelQemuLdst *ldst, g_assert_not_reached(); } - if (TCG_TARGET_REG_BITS == 32 && s->addr_type == TCG_TYPE_I32) { + if (TCG_TARGET_REG_BITS == 32) { /* Zero extend the address by loading a zero for the high part. */ loc = &info->in[1 + !HOST_BIG_ENDIAN]; tcg_out_helper_load_imm(s, loc->arg_slot, TCG_TYPE_I32, 0, parm); From 09ac62682b8d2a8bac36d068f63a31331cc6259a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 18:49:40 -0800 Subject: [PATCH 0121/1179] plugins: Fix qemu_plugin_read_memory_vaddr parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The declaration uses uint64_t for addr. Fixes: 595cd9ce2ec ("plugins: add plugin API to read guest memory") Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- plugins/api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/api.c b/plugins/api.c index 4110cfaa2379..cf8cdf076a0f 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -561,7 +561,7 @@ GArray *qemu_plugin_get_registers(void) return create_register_handles(regs); } -bool qemu_plugin_read_memory_vaddr(vaddr addr, GByteArray *data, size_t len) +bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) { g_assert(current_cpu); From 252394c95baddf3d61f95cdd0c4697298de425d4 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 18:51:30 -0800 Subject: [PATCH 0122/1179] accel/tcg: Fix tlb_set_page_with_attrs, tlb_set_page The declarations use vaddr for size. Signed-off-by: Richard Henderson --- accel/tcg/cputlb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index 17e225169516..75d075d04492 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -1193,7 +1193,7 @@ void tlb_set_page_full(CPUState *cpu, int mmu_idx, void tlb_set_page_with_attrs(CPUState *cpu, vaddr addr, hwaddr paddr, MemTxAttrs attrs, int prot, - int mmu_idx, uint64_t size) + int mmu_idx, vaddr size) { CPUTLBEntryFull full = { .phys_addr = paddr, @@ -1208,7 +1208,7 @@ void tlb_set_page_with_attrs(CPUState *cpu, vaddr addr, void tlb_set_page(CPUState *cpu, vaddr addr, hwaddr paddr, int prot, - int mmu_idx, uint64_t size) + int mmu_idx, vaddr size) { tlb_set_page_with_attrs(cpu, addr, paddr, MEMTXATTRS_UNSPECIFIED, prot, mmu_idx, size); From a0ea8654e525a2bd5c4c197948d3ba179ded90c7 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Mon, 17 Feb 2025 08:21:36 -0800 Subject: [PATCH 0123/1179] target/loongarch: Use VADDR_PRIx for logging pc_next DisasContextBase.pc_next has type vaddr; use the correct log format. Fixes: 85c19af63e7 ("include/exec: Use vaddr in DisasContextBase for virtual addresses") Signed-off-by: Richard Henderson --- target/loongarch/tcg/insn_trans/trans_atomic.c.inc | 2 +- target/loongarch/tcg/translate.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/target/loongarch/tcg/insn_trans/trans_atomic.c.inc b/target/loongarch/tcg/insn_trans/trans_atomic.c.inc index 974bc2a70fed..3d70d7594171 100644 --- a/target/loongarch/tcg/insn_trans/trans_atomic.c.inc +++ b/target/loongarch/tcg/insn_trans/trans_atomic.c.inc @@ -56,7 +56,7 @@ static bool gen_am(DisasContext *ctx, arg_rrr *a, if (a->rd != 0 && (a->rj == a->rd || a->rk == a->rd)) { qemu_log_mask(LOG_GUEST_ERROR, "Warning: source register overlaps destination register" - "in atomic insn at pc=0x" TARGET_FMT_lx "\n", + "in atomic insn at pc=0x%" VADDR_PRIx "\n", ctx->base.pc_next - 4); return false; } diff --git a/target/loongarch/tcg/translate.c b/target/loongarch/tcg/translate.c index 3480f54c7109..e59e4ed25b13 100644 --- a/target/loongarch/tcg/translate.c +++ b/target/loongarch/tcg/translate.c @@ -289,7 +289,7 @@ static void loongarch_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) if (!decode(ctx, ctx->opcode)) { qemu_log_mask(LOG_UNIMP, "Error: unknown opcode. " - TARGET_FMT_lx ": 0x%x\n", + "0x%" VADDR_PRIx ": 0x%x\n", ctx->base.pc_next, ctx->opcode); generate_exception(ctx, EXCCODE_INE); } From a630055df39e1960275d0e273af036f794b15662 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 18 Feb 2025 08:26:40 -0800 Subject: [PATCH 0124/1179] target/mips: Use VADDR_PRIx for logging pc_next DisasContextBase.pc_next has type vaddr; use the correct log format. Fixes: 85c19af63e7 ("include/exec: Use vaddr in DisasContextBase for virtual addresses") Signed-off-by: Richard Henderson --- target/mips/tcg/octeon_translate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/mips/tcg/octeon_translate.c b/target/mips/tcg/octeon_translate.c index e25c4cbaa064..d9eb43716e2a 100644 --- a/target/mips/tcg/octeon_translate.c +++ b/target/mips/tcg/octeon_translate.c @@ -18,8 +18,8 @@ static bool trans_BBIT(DisasContext *ctx, arg_BBIT *a) TCGv p; if (ctx->hflags & MIPS_HFLAG_BMASK) { - LOG_DISAS("Branch in delay / forbidden slot at PC 0x" - TARGET_FMT_lx "\n", ctx->base.pc_next); + LOG_DISAS("Branch in delay / forbidden slot at PC 0x%" VADDR_PRIx "\n", + ctx->base.pc_next); generate_exception_end(ctx, EXCP_RI); return true; } From a70af12addd9060fdf8f3dbd42b42e3072c3914f Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 18:56:07 -0800 Subject: [PATCH 0125/1179] include/exec: Change vaddr to uintptr_t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we no longer support 64-bit guests on 32-bit hosts, we can use a 32-bit type on a 32-bit host. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- include/exec/vaddr.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/include/exec/vaddr.h b/include/exec/vaddr.h index b9844afc7739..28bec632fbbf 100644 --- a/include/exec/vaddr.h +++ b/include/exec/vaddr.h @@ -6,13 +6,15 @@ /** * vaddr: * Type wide enough to contain any #target_ulong virtual address. + * We do not support 64-bit guest on 32-host and detect at configure time. + * Therefore, a host pointer width will always fit a guest pointer. */ -typedef uint64_t vaddr; -#define VADDR_PRId PRId64 -#define VADDR_PRIu PRIu64 -#define VADDR_PRIo PRIo64 -#define VADDR_PRIx PRIx64 -#define VADDR_PRIX PRIX64 -#define VADDR_MAX UINT64_MAX +typedef uintptr_t vaddr; +#define VADDR_PRId PRIdPTR +#define VADDR_PRIu PRIuPTR +#define VADDR_PRIo PRIoPTR +#define VADDR_PRIx PRIxPTR +#define VADDR_PRIX PRIXPTR +#define VADDR_MAX UINTPTR_MAX #endif From bf455ec50b6fea15b4d2493059365bf94c706273 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 4 Feb 2025 19:34:51 -0800 Subject: [PATCH 0126/1179] include/exec: Use uintptr_t in CPUTLBEntry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we no longer support 64-bit guests on 32-bit hosts, we can use a 32-bit type on a 32-bit host. This shrinks the size of the structure to 16 bytes on a 32-bit host. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- accel/tcg/cputlb.c | 21 ++++----------------- include/exec/tlb-common.h | 10 +++++----- tcg/arm/tcg-target.c.inc | 1 - tcg/mips/tcg-target.c.inc | 12 +++++------- tcg/ppc/tcg-target.c.inc | 21 +++++---------------- 5 files changed, 19 insertions(+), 46 deletions(-) diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index 75d075d04492..ad158050a138 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -104,22 +104,15 @@ static inline uint64_t tlb_read_idx(const CPUTLBEntry *entry, { /* Do not rearrange the CPUTLBEntry structure members. */ QEMU_BUILD_BUG_ON(offsetof(CPUTLBEntry, addr_read) != - MMU_DATA_LOAD * sizeof(uint64_t)); + MMU_DATA_LOAD * sizeof(uintptr_t)); QEMU_BUILD_BUG_ON(offsetof(CPUTLBEntry, addr_write) != - MMU_DATA_STORE * sizeof(uint64_t)); + MMU_DATA_STORE * sizeof(uintptr_t)); QEMU_BUILD_BUG_ON(offsetof(CPUTLBEntry, addr_code) != - MMU_INST_FETCH * sizeof(uint64_t)); + MMU_INST_FETCH * sizeof(uintptr_t)); -#if TARGET_LONG_BITS == 32 - /* Use qatomic_read, in case of addr_write; only care about low bits. */ - const uint32_t *ptr = (uint32_t *)&entry->addr_idx[access_type]; - ptr += HOST_BIG_ENDIAN; - return qatomic_read(ptr); -#else - const uint64_t *ptr = &entry->addr_idx[access_type]; + const uintptr_t *ptr = &entry->addr_idx[access_type]; /* ofs might correspond to .addr_write, so use qatomic_read */ return qatomic_read(ptr); -#endif } static inline uint64_t tlb_addr_write(const CPUTLBEntry *entry) @@ -899,14 +892,8 @@ static void tlb_reset_dirty_range_locked(CPUTLBEntry *tlb_entry, addr &= TARGET_PAGE_MASK; addr += tlb_entry->addend; if ((addr - start) < length) { -#if TARGET_LONG_BITS == 32 - uint32_t *ptr_write = (uint32_t *)&tlb_entry->addr_write; - ptr_write += HOST_BIG_ENDIAN; - qatomic_set(ptr_write, *ptr_write | TLB_NOTDIRTY); -#else qatomic_set(&tlb_entry->addr_write, tlb_entry->addr_write | TLB_NOTDIRTY); -#endif } } } diff --git a/include/exec/tlb-common.h b/include/exec/tlb-common.h index dc5a5faa0b31..03b5a8ffc71f 100644 --- a/include/exec/tlb-common.h +++ b/include/exec/tlb-common.h @@ -19,14 +19,14 @@ #ifndef EXEC_TLB_COMMON_H #define EXEC_TLB_COMMON_H 1 -#define CPU_TLB_ENTRY_BITS 5 +#define CPU_TLB_ENTRY_BITS (HOST_LONG_BITS == 32 ? 4 : 5) /* Minimalized TLB entry for use by TCG fast path. */ typedef union CPUTLBEntry { struct { - uint64_t addr_read; - uint64_t addr_write; - uint64_t addr_code; + uintptr_t addr_read; + uintptr_t addr_write; + uintptr_t addr_code; /* * Addend to virtual address to get host address. IO accesses * use the corresponding iotlb value. @@ -37,7 +37,7 @@ typedef union CPUTLBEntry { * Padding to get a power of two size, as well as index * access to addr_{read,write,code}. */ - uint64_t addr_idx[(1 << CPU_TLB_ENTRY_BITS) / sizeof(uint64_t)]; + uintptr_t addr_idx[(1 << CPU_TLB_ENTRY_BITS) / sizeof(uintptr_t)]; } CPUTLBEntry; QEMU_BUILD_BUG_ON(sizeof(CPUTLBEntry) != (1 << CPU_TLB_ENTRY_BITS)); diff --git a/tcg/arm/tcg-target.c.inc b/tcg/arm/tcg-target.c.inc index 83f6eb6099ff..cec3d761d45f 100644 --- a/tcg/arm/tcg-target.c.inc +++ b/tcg/arm/tcg-target.c.inc @@ -1500,7 +1500,6 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, * Add the tlb_table pointer, creating the CPUTLBEntry address in R1. * Load the tlb comparator into R2 and the fast path addend into R1. */ - QEMU_BUILD_BUG_ON(HOST_BIG_ENDIAN); if (cmp_off == 0) { tcg_out_ld32_rwb(s, COND_AL, TCG_REG_R2, TCG_REG_R1, TCG_REG_R0); } else { diff --git a/tcg/mips/tcg-target.c.inc b/tcg/mips/tcg-target.c.inc index d744b853cdc7..14b3cb1eba6b 100644 --- a/tcg/mips/tcg-target.c.inc +++ b/tcg/mips/tcg-target.c.inc @@ -1262,18 +1262,16 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, /* Add the tlb_table pointer, creating the CPUTLBEntry address. */ tcg_out_opc_reg(s, ALIAS_PADD, TCG_TMP3, TCG_TMP3, TCG_TMP1); - if (TCG_TARGET_REG_BITS == 32 || addr_type == TCG_TYPE_I32) { - /* Load the (low half) tlb comparator. */ + /* Load the tlb comparator. */ + if (TCG_TARGET_REG_BITS == 64 && addr_type == TCG_TYPE_I32) { tcg_out_ld(s, TCG_TYPE_I32, TCG_TMP0, TCG_TMP3, cmp_off + HOST_BIG_ENDIAN * 4); } else { - tcg_out_ld(s, TCG_TYPE_I64, TCG_TMP0, TCG_TMP3, cmp_off); + tcg_out_ld(s, TCG_TYPE_REG, TCG_TMP0, TCG_TMP3, cmp_off); } - if (TCG_TARGET_REG_BITS == 64 || addr_type == TCG_TYPE_I32) { - /* Load the tlb addend for the fast path. */ - tcg_out_ld(s, TCG_TYPE_PTR, TCG_TMP3, TCG_TMP3, add_off); - } + /* Load the tlb addend for the fast path. */ + tcg_out_ld(s, TCG_TYPE_PTR, TCG_TMP3, TCG_TMP3, add_off); /* * Mask the page bits, keeping the alignment bits to compare against. diff --git a/tcg/ppc/tcg-target.c.inc b/tcg/ppc/tcg-target.c.inc index 2d16807ec79b..822925a19b39 100644 --- a/tcg/ppc/tcg-target.c.inc +++ b/tcg/ppc/tcg-target.c.inc @@ -2490,27 +2490,16 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, tcg_out32(s, AND | SAB(TCG_REG_TMP1, TCG_REG_TMP1, TCG_REG_R0)); /* - * Load the (low part) TLB comparator into TMP2. + * Load the TLB comparator into TMP2. * For 64-bit host, always load the entire 64-bit slot for simplicity. * We will ignore the high bits with tcg_out_cmp(..., addr_type). */ - if (TCG_TARGET_REG_BITS == 64) { - if (cmp_off == 0) { - tcg_out32(s, LDUX | TAB(TCG_REG_TMP2, - TCG_REG_TMP1, TCG_REG_TMP2)); - } else { - tcg_out32(s, ADD | TAB(TCG_REG_TMP1, - TCG_REG_TMP1, TCG_REG_TMP2)); - tcg_out_ld(s, TCG_TYPE_I64, TCG_REG_TMP2, - TCG_REG_TMP1, cmp_off); - } - } else if (cmp_off == 0 && !HOST_BIG_ENDIAN) { - tcg_out32(s, LWZUX | TAB(TCG_REG_TMP2, - TCG_REG_TMP1, TCG_REG_TMP2)); + if (cmp_off == 0) { + tcg_out32(s, (TCG_TARGET_REG_BITS == 64 ? LDUX : LWZUX) + | TAB(TCG_REG_TMP2, TCG_REG_TMP1, TCG_REG_TMP2)); } else { tcg_out32(s, ADD | TAB(TCG_REG_TMP1, TCG_REG_TMP1, TCG_REG_TMP2)); - tcg_out_ld(s, TCG_TYPE_I32, TCG_REG_TMP2, TCG_REG_TMP1, - cmp_off + 4 * HOST_BIG_ENDIAN); + tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP2, TCG_REG_TMP1, cmp_off); } /* From 6b8abd244b9355bc840bc14182aae9043f86f2f6 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 9 Feb 2025 16:01:38 -0800 Subject: [PATCH 0127/1179] tcg: Introduce the 'z' constraint for a hardware zero register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For loongarch, mips, riscv and sparc, a zero register is available all the time. For aarch64, register index 31 depends on context: sometimes it is the stack pointer, and sometimes it is the zero register. Introduce a new general-purpose constraint which maps 0 to TCG_REG_ZERO, if defined. This differs from existing constant constraints in that const_arg[*] is recorded as false, indicating that the value is in a register. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- docs/devel/tcg-ops.rst | 4 +++- include/tcg/tcg.h | 3 ++- tcg/aarch64/tcg-target.h | 2 ++ tcg/loongarch64/tcg-target.h | 2 ++ tcg/mips/tcg-target.h | 2 ++ tcg/riscv/tcg-target.h | 2 ++ tcg/sparc64/tcg-target.h | 3 ++- tcg/tcg.c | 29 ++++++++++++++++++++++------- 8 files changed, 37 insertions(+), 10 deletions(-) diff --git a/docs/devel/tcg-ops.rst b/docs/devel/tcg-ops.rst index 6608a293763b..688984fd3912 100644 --- a/docs/devel/tcg-ops.rst +++ b/docs/devel/tcg-ops.rst @@ -927,7 +927,9 @@ operation uses a constant input constraint which does not allow all constants, it must also accept registers in order to have a fallback. The constraint '``i``' is defined generically to accept any constant. The constraint '``r``' is not defined generically, but is consistently -used by each backend to indicate all registers. +used by each backend to indicate all registers. If ``TCG_REG_ZERO`` +is defined by the backend, the constraint '``z``' is defined generically +to map constant 0 to the hardware zero register. The movi_i32 and movi_i64 operations must accept any constants. diff --git a/include/tcg/tcg.h b/include/tcg/tcg.h index 1d1d668f527b..84d99508b652 100644 --- a/include/tcg/tcg.h +++ b/include/tcg/tcg.h @@ -713,7 +713,8 @@ void tb_target_set_jmp_target(const TranslationBlock *, int, void tcg_set_frame(TCGContext *s, TCGReg reg, intptr_t start, intptr_t size); -#define TCG_CT_CONST 1 /* any constant of register size */ +#define TCG_CT_CONST 1 /* any constant of register size */ +#define TCG_CT_REG_ZERO 2 /* zero, in TCG_REG_ZERO */ typedef struct TCGArgConstraint { unsigned ct : 16; diff --git a/tcg/aarch64/tcg-target.h b/tcg/aarch64/tcg-target.h index 0dd6e1f06953..3f3df5176d91 100644 --- a/tcg/aarch64/tcg-target.h +++ b/tcg/aarch64/tcg-target.h @@ -45,6 +45,8 @@ typedef enum { TCG_AREG0 = TCG_REG_X19, } TCGReg; +#define TCG_REG_ZERO TCG_REG_XZR + #define TCG_TARGET_NB_REGS 64 #endif /* AARCH64_TCG_TARGET_H */ diff --git a/tcg/loongarch64/tcg-target.h b/tcg/loongarch64/tcg-target.h index 853328463139..6a206fb97e6e 100644 --- a/tcg/loongarch64/tcg-target.h +++ b/tcg/loongarch64/tcg-target.h @@ -85,4 +85,6 @@ typedef enum { TCG_VEC_TMP0 = TCG_REG_V23, } TCGReg; +#define TCG_REG_ZERO TCG_REG_ZERO + #endif /* LOONGARCH_TCG_TARGET_H */ diff --git a/tcg/mips/tcg-target.h b/tcg/mips/tcg-target.h index 3090acc4f5cb..bd4ca5f852d7 100644 --- a/tcg/mips/tcg-target.h +++ b/tcg/mips/tcg-target.h @@ -70,4 +70,6 @@ typedef enum { TCG_AREG0 = TCG_REG_S8, } TCGReg; +#define TCG_REG_ZERO TCG_REG_ZERO + #endif diff --git a/tcg/riscv/tcg-target.h b/tcg/riscv/tcg-target.h index db5f3d8b7294..6dc77d944ba3 100644 --- a/tcg/riscv/tcg-target.h +++ b/tcg/riscv/tcg-target.h @@ -57,4 +57,6 @@ typedef enum { TCG_REG_TMP2 = TCG_REG_T4, } TCGReg; +#define TCG_REG_ZERO TCG_REG_ZERO + #endif diff --git a/tcg/sparc64/tcg-target.h b/tcg/sparc64/tcg-target.h index f7d75d580654..1b9adccd85f4 100644 --- a/tcg/sparc64/tcg-target.h +++ b/tcg/sparc64/tcg-target.h @@ -64,6 +64,7 @@ typedef enum { TCG_REG_I7, } TCGReg; -#define TCG_AREG0 TCG_REG_I0 +#define TCG_AREG0 TCG_REG_I0 +#define TCG_REG_ZERO TCG_REG_G0 #endif diff --git a/tcg/tcg.c b/tcg/tcg.c index 55cb9b3ac7a1..e8950df2ad3b 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -3223,6 +3223,11 @@ static void process_constraint_sets(void) case 'i': args_ct[i].ct |= TCG_CT_CONST; break; +#ifdef TCG_REG_ZERO + case 'z': + args_ct[i].ct |= TCG_CT_REG_ZERO; + break; +#endif /* Include all of the target-specific constraints. */ @@ -5074,13 +5079,23 @@ static void tcg_reg_alloc_op(TCGContext *s, const TCGOp *op) arg_ct = &args_ct[i]; ts = arg_temp(arg); - if (ts->val_type == TEMP_VAL_CONST - && tcg_target_const_match(ts->val, arg_ct->ct, ts->type, - op_cond, TCGOP_VECE(op))) { - /* constant is OK for instruction */ - const_args[i] = 1; - new_args[i] = ts->val; - continue; + if (ts->val_type == TEMP_VAL_CONST) { +#ifdef TCG_REG_ZERO + if (ts->val == 0 && (arg_ct->ct & TCG_CT_REG_ZERO)) { + /* Hardware zero register: indicate register via non-const. */ + const_args[i] = 0; + new_args[i] = TCG_REG_ZERO; + continue; + } +#endif + + if (tcg_target_const_match(ts->val, arg_ct->ct, ts->type, + op_cond, TCGOP_VECE(op))) { + /* constant is OK for instruction */ + const_args[i] = 1; + new_args[i] = ts->val; + continue; + } } reg = ts->reg; From 3d5939e57f959a30eaf13a7897e56a2388121cc2 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 9 Feb 2025 16:15:31 -0800 Subject: [PATCH 0128/1179] tcg/aarch64: Use 'z' constraint Note that 'Z' is still used for addsub2. Signed-off-by: Richard Henderson --- tcg/aarch64/tcg-target-con-set.h | 12 ++++----- tcg/aarch64/tcg-target.c.inc | 46 ++++++++++++++------------------ 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/tcg/aarch64/tcg-target-con-set.h b/tcg/aarch64/tcg-target-con-set.h index 44fcc1206e0a..1281e5efc01c 100644 --- a/tcg/aarch64/tcg-target-con-set.h +++ b/tcg/aarch64/tcg-target-con-set.h @@ -11,27 +11,27 @@ */ C_O0_I1(r) C_O0_I2(r, rC) -C_O0_I2(rZ, r) +C_O0_I2(rz, r) C_O0_I2(w, r) -C_O0_I3(rZ, rZ, r) +C_O0_I3(rz, rz, r) C_O1_I1(r, r) C_O1_I1(w, r) C_O1_I1(w, w) C_O1_I1(w, wr) -C_O1_I2(r, 0, rZ) +C_O1_I2(r, 0, rz) C_O1_I2(r, r, r) C_O1_I2(r, r, rA) C_O1_I2(r, r, rAL) C_O1_I2(r, r, rC) C_O1_I2(r, r, ri) C_O1_I2(r, r, rL) -C_O1_I2(r, rZ, rZ) +C_O1_I2(r, rz, rz) C_O1_I2(w, 0, w) C_O1_I2(w, w, w) C_O1_I2(w, w, wN) C_O1_I2(w, w, wO) C_O1_I2(w, w, wZ) C_O1_I3(w, w, w, w) -C_O1_I4(r, r, rC, rZ, rZ) +C_O1_I4(r, r, rC, rz, rz) C_O2_I1(r, r, r) -C_O2_I4(r, r, rZ, rZ, rA, rMZ) +C_O2_I4(r, r, rz, rz, rA, rMZ) diff --git a/tcg/aarch64/tcg-target.c.inc b/tcg/aarch64/tcg-target.c.inc index 6f383c1592a2..4645242d85ea 100644 --- a/tcg/aarch64/tcg-target.c.inc +++ b/tcg/aarch64/tcg-target.c.inc @@ -2125,10 +2125,6 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, TCGArg a2 = args[2]; int c2 = const_args[2]; - /* Some operands are defined with "rZ" constraint, a register or - the zero register. These need not actually test args[I] == 0. */ -#define REG0(I) (const_args[I] ? TCG_REG_XZR : (TCGReg)args[I]) - switch (opc) { case INDEX_op_goto_ptr: tcg_out_insn(s, 3207, BR, a0); @@ -2171,18 +2167,18 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, case INDEX_op_st8_i32: case INDEX_op_st8_i64: - tcg_out_ldst(s, I3312_STRB, REG0(0), a1, a2, 0); + tcg_out_ldst(s, I3312_STRB, a0, a1, a2, 0); break; case INDEX_op_st16_i32: case INDEX_op_st16_i64: - tcg_out_ldst(s, I3312_STRH, REG0(0), a1, a2, 1); + tcg_out_ldst(s, I3312_STRH, a0, a1, a2, 1); break; case INDEX_op_st_i32: case INDEX_op_st32_i64: - tcg_out_ldst(s, I3312_STRW, REG0(0), a1, a2, 2); + tcg_out_ldst(s, I3312_STRW, a0, a1, a2, 2); break; case INDEX_op_st_i64: - tcg_out_ldst(s, I3312_STRX, REG0(0), a1, a2, 3); + tcg_out_ldst(s, I3312_STRX, a0, a1, a2, 3); break; case INDEX_op_add_i32: @@ -2395,7 +2391,7 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, /* FALLTHRU */ case INDEX_op_movcond_i64: tcg_out_cmp(s, ext, args[5], a1, a2, c2); - tcg_out_insn(s, 3506, CSEL, ext, a0, REG0(3), REG0(4), args[5]); + tcg_out_insn(s, 3506, CSEL, ext, a0, args[3], args[4], args[5]); break; case INDEX_op_qemu_ld_i32: @@ -2404,13 +2400,13 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, break; case INDEX_op_qemu_st_i32: case INDEX_op_qemu_st_i64: - tcg_out_qemu_st(s, REG0(0), a1, a2, ext); + tcg_out_qemu_st(s, a0, a1, a2, ext); break; case INDEX_op_qemu_ld_i128: tcg_out_qemu_ldst_i128(s, a0, a1, a2, args[3], true); break; case INDEX_op_qemu_st_i128: - tcg_out_qemu_ldst_i128(s, REG0(0), REG0(1), a2, args[3], false); + tcg_out_qemu_ldst_i128(s, a0, a1, a2, args[3], false); break; case INDEX_op_bswap64_i64: @@ -2439,7 +2435,7 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, case INDEX_op_deposit_i64: case INDEX_op_deposit_i32: - tcg_out_dep(s, ext, a0, REG0(2), args[3], args[4]); + tcg_out_dep(s, ext, a0, a2, args[3], args[4]); break; case INDEX_op_extract_i64: @@ -2459,25 +2455,25 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, case INDEX_op_extract2_i64: case INDEX_op_extract2_i32: - tcg_out_extr(s, ext, a0, REG0(2), REG0(1), args[3]); + tcg_out_extr(s, ext, a0, a2, a1, args[3]); break; case INDEX_op_add2_i32: - tcg_out_addsub2(s, TCG_TYPE_I32, a0, a1, REG0(2), REG0(3), + tcg_out_addsub2(s, TCG_TYPE_I32, a0, a1, a2, args[3], (int32_t)args[4], args[5], const_args[4], const_args[5], false); break; case INDEX_op_add2_i64: - tcg_out_addsub2(s, TCG_TYPE_I64, a0, a1, REG0(2), REG0(3), args[4], + tcg_out_addsub2(s, TCG_TYPE_I64, a0, a1, a2, args[3], args[4], args[5], const_args[4], const_args[5], false); break; case INDEX_op_sub2_i32: - tcg_out_addsub2(s, TCG_TYPE_I32, a0, a1, REG0(2), REG0(3), + tcg_out_addsub2(s, TCG_TYPE_I32, a0, a1, a2, args[3], (int32_t)args[4], args[5], const_args[4], const_args[5], true); break; case INDEX_op_sub2_i64: - tcg_out_addsub2(s, TCG_TYPE_I64, a0, a1, REG0(2), REG0(3), args[4], + tcg_out_addsub2(s, TCG_TYPE_I64, a0, a1, a2, args[3], args[4], args[5], const_args[4], const_args[5], true); break; @@ -2513,8 +2509,6 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType ext, default: g_assert_not_reached(); } - -#undef REG0 } static void tcg_out_vec_op(TCGContext *s, TCGOpcode opc, @@ -3010,7 +3004,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_st16_i64: case INDEX_op_st32_i64: case INDEX_op_st_i64: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_add_i32: case INDEX_op_add_i64: @@ -3076,7 +3070,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_movcond_i32: case INDEX_op_movcond_i64: - return C_O1_I4(r, r, rC, rZ, rZ); + return C_O1_I4(r, r, rC, rz, rz); case INDEX_op_qemu_ld_i32: case INDEX_op_qemu_ld_i64: @@ -3085,23 +3079,23 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) return C_O2_I1(r, r, r); case INDEX_op_qemu_st_i32: case INDEX_op_qemu_st_i64: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_qemu_st_i128: - return C_O0_I3(rZ, rZ, r); + return C_O0_I3(rz, rz, r); case INDEX_op_deposit_i32: case INDEX_op_deposit_i64: - return C_O1_I2(r, 0, rZ); + return C_O1_I2(r, 0, rz); case INDEX_op_extract2_i32: case INDEX_op_extract2_i64: - return C_O1_I2(r, rZ, rZ); + return C_O1_I2(r, rz, rz); case INDEX_op_add2_i32: case INDEX_op_add2_i64: case INDEX_op_sub2_i32: case INDEX_op_sub2_i64: - return C_O2_I4(r, r, rZ, rZ, rA, rMZ); + return C_O2_I4(r, r, rz, rz, rA, rMZ); case INDEX_op_add_vec: case INDEX_op_sub_vec: From 305370e78d31f742d0b5db4809cf00ef3eeea2a2 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 9 Feb 2025 17:01:29 -0800 Subject: [PATCH 0129/1179] tcg/loongarch64: Use 'z' constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace target-specific 'Z' with generic 'z'. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/loongarch64/tcg-target-con-set.h | 15 ++++++------- tcg/loongarch64/tcg-target-con-str.h | 1 - tcg/loongarch64/tcg-target.c.inc | 32 ++++++++++++---------------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/tcg/loongarch64/tcg-target-con-set.h b/tcg/loongarch64/tcg-target-con-set.h index cae6c2aad646..8afaee9476c1 100644 --- a/tcg/loongarch64/tcg-target-con-set.h +++ b/tcg/loongarch64/tcg-target-con-set.h @@ -15,8 +15,8 @@ * tcg-target-con-str.h; the constraint combination is inclusive or. */ C_O0_I1(r) -C_O0_I2(rZ, r) -C_O0_I2(rZ, rZ) +C_O0_I2(rz, r) +C_O0_I2(rz, rz) C_O0_I2(w, r) C_O0_I3(r, r, r) C_O1_I1(r, r) @@ -28,14 +28,13 @@ C_O1_I2(r, r, rI) C_O1_I2(r, r, rJ) C_O1_I2(r, r, rU) C_O1_I2(r, r, rW) -C_O1_I2(r, r, rZ) -C_O1_I2(r, 0, rZ) -C_O1_I2(r, rZ, ri) -C_O1_I2(r, rZ, rJ) -C_O1_I2(r, rZ, rZ) +C_O1_I2(r, 0, rz) +C_O1_I2(r, rz, ri) +C_O1_I2(r, rz, rJ) +C_O1_I2(r, rz, rz) C_O1_I2(w, w, w) C_O1_I2(w, w, wM) C_O1_I2(w, w, wA) C_O1_I3(w, w, w, w) -C_O1_I4(r, rZ, rJ, rZ, rZ) +C_O1_I4(r, rz, rJ, rz, rz) C_N2_I1(r, r, r) diff --git a/tcg/loongarch64/tcg-target-con-str.h b/tcg/loongarch64/tcg-target-con-str.h index 2ba9c135ac19..99759120b470 100644 --- a/tcg/loongarch64/tcg-target-con-str.h +++ b/tcg/loongarch64/tcg-target-con-str.h @@ -23,7 +23,6 @@ REGS('w', ALL_VECTOR_REGS) CONST('I', TCG_CT_CONST_S12) CONST('J', TCG_CT_CONST_S32) CONST('U', TCG_CT_CONST_U12) -CONST('Z', TCG_CT_CONST_ZERO) CONST('C', TCG_CT_CONST_C12) CONST('W', TCG_CT_CONST_WSZ) CONST('M', TCG_CT_CONST_VCMP) diff --git a/tcg/loongarch64/tcg-target.c.inc b/tcg/loongarch64/tcg-target.c.inc index dd67e8f6bcb7..cbd7642b58ac 100644 --- a/tcg/loongarch64/tcg-target.c.inc +++ b/tcg/loongarch64/tcg-target.c.inc @@ -173,14 +173,13 @@ static TCGReg tcg_target_call_oarg_reg(TCGCallReturnKind kind, int slot) #define TCG_GUEST_BASE_REG TCG_REG_S1 -#define TCG_CT_CONST_ZERO 0x100 -#define TCG_CT_CONST_S12 0x200 -#define TCG_CT_CONST_S32 0x400 -#define TCG_CT_CONST_U12 0x800 -#define TCG_CT_CONST_C12 0x1000 -#define TCG_CT_CONST_WSZ 0x2000 -#define TCG_CT_CONST_VCMP 0x4000 -#define TCG_CT_CONST_VADD 0x8000 +#define TCG_CT_CONST_S12 0x100 +#define TCG_CT_CONST_S32 0x200 +#define TCG_CT_CONST_U12 0x400 +#define TCG_CT_CONST_C12 0x800 +#define TCG_CT_CONST_WSZ 0x1000 +#define TCG_CT_CONST_VCMP 0x2000 +#define TCG_CT_CONST_VADD 0x4000 #define ALL_GENERAL_REGS MAKE_64BIT_MASK(0, 32) #define ALL_VECTOR_REGS MAKE_64BIT_MASK(32, 32) @@ -197,9 +196,6 @@ static bool tcg_target_const_match(int64_t val, int ct, if (ct & TCG_CT_CONST) { return true; } - if ((ct & TCG_CT_CONST_ZERO) && val == 0) { - return true; - } if ((ct & TCG_CT_CONST_S12) && val == sextreg(val, 0, 12)) { return true; } @@ -2229,7 +2225,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_st_i64: case INDEX_op_qemu_st_i32: case INDEX_op_qemu_st_i64: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_qemu_ld_i128: return C_N2_I1(r, r, r); @@ -2239,7 +2235,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_brcond_i32: case INDEX_op_brcond_i64: - return C_O0_I2(rZ, rZ); + return C_O0_I2(rz, rz); case INDEX_op_ext8s_i32: case INDEX_op_ext8s_i64: @@ -2332,14 +2328,14 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_deposit_i32: case INDEX_op_deposit_i64: /* Must deposit into the same register as input */ - return C_O1_I2(r, 0, rZ); + return C_O1_I2(r, 0, rz); case INDEX_op_sub_i32: case INDEX_op_setcond_i32: - return C_O1_I2(r, rZ, ri); + return C_O1_I2(r, rz, ri); case INDEX_op_sub_i64: case INDEX_op_setcond_i64: - return C_O1_I2(r, rZ, rJ); + return C_O1_I2(r, rz, rJ); case INDEX_op_mul_i32: case INDEX_op_mul_i64: @@ -2355,11 +2351,11 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_rem_i64: case INDEX_op_remu_i32: case INDEX_op_remu_i64: - return C_O1_I2(r, rZ, rZ); + return C_O1_I2(r, rz, rz); case INDEX_op_movcond_i32: case INDEX_op_movcond_i64: - return C_O1_I4(r, rZ, rJ, rZ, rZ); + return C_O1_I4(r, rz, rJ, rz, rz); case INDEX_op_ld_vec: case INDEX_op_dupm_vec: From 065c8f64161b4b1334e4d244dfb4e9d66551e4c2 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 9 Feb 2025 17:21:48 -0800 Subject: [PATCH 0130/1179] tcg/mips: Use 'z' constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace target-specific 'Z' with generic 'z'. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/mips/tcg-target-con-set.h | 26 ++++++++++----------- tcg/mips/tcg-target-con-str.h | 1 - tcg/mips/tcg-target.c.inc | 44 ++++++++++++++--------------------- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/tcg/mips/tcg-target-con-set.h b/tcg/mips/tcg-target-con-set.h index 864034f46878..06ab04cc4dd9 100644 --- a/tcg/mips/tcg-target-con-set.h +++ b/tcg/mips/tcg-target-con-set.h @@ -10,24 +10,24 @@ * tcg-target-con-str.h; the constraint combination is inclusive or. */ C_O0_I1(r) -C_O0_I2(rZ, r) -C_O0_I2(rZ, rZ) -C_O0_I3(rZ, r, r) -C_O0_I3(rZ, rZ, r) -C_O0_I4(rZ, rZ, rZ, rZ) -C_O0_I4(rZ, rZ, r, r) +C_O0_I2(rz, r) +C_O0_I2(rz, rz) +C_O0_I3(rz, r, r) +C_O0_I3(rz, rz, r) +C_O0_I4(rz, rz, rz, rz) +C_O0_I4(rz, rz, r, r) C_O1_I1(r, r) -C_O1_I2(r, 0, rZ) +C_O1_I2(r, 0, rz) C_O1_I2(r, r, r) C_O1_I2(r, r, ri) C_O1_I2(r, r, rI) C_O1_I2(r, r, rIK) C_O1_I2(r, r, rJ) -C_O1_I2(r, r, rWZ) -C_O1_I2(r, rZ, rN) -C_O1_I2(r, rZ, rZ) -C_O1_I4(r, rZ, rZ, rZ, 0) -C_O1_I4(r, rZ, rZ, rZ, rZ) +C_O1_I2(r, r, rzW) +C_O1_I2(r, rz, rN) +C_O1_I2(r, rz, rz) +C_O1_I4(r, rz, rz, rz, 0) +C_O1_I4(r, rz, rz, rz, rz) C_O2_I1(r, r, r) C_O2_I2(r, r, r, r) -C_O2_I4(r, r, rZ, rZ, rN, rN) +C_O2_I4(r, r, rz, rz, rN, rN) diff --git a/tcg/mips/tcg-target-con-str.h b/tcg/mips/tcg-target-con-str.h index 413c280a7a90..dfe2b156df54 100644 --- a/tcg/mips/tcg-target-con-str.h +++ b/tcg/mips/tcg-target-con-str.h @@ -19,4 +19,3 @@ CONST('J', TCG_CT_CONST_S16) CONST('K', TCG_CT_CONST_P2M1) CONST('N', TCG_CT_CONST_N16) CONST('W', TCG_CT_CONST_WSZ) -CONST('Z', TCG_CT_CONST_ZERO) diff --git a/tcg/mips/tcg-target.c.inc b/tcg/mips/tcg-target.c.inc index 14b3cb1eba6b..f8c105ba37c6 100644 --- a/tcg/mips/tcg-target.c.inc +++ b/tcg/mips/tcg-target.c.inc @@ -184,12 +184,11 @@ static bool patch_reloc(tcg_insn_unit *code_ptr, int type, g_assert_not_reached(); } -#define TCG_CT_CONST_ZERO 0x100 -#define TCG_CT_CONST_U16 0x200 /* Unsigned 16-bit: 0 - 0xffff. */ -#define TCG_CT_CONST_S16 0x400 /* Signed 16-bit: -32768 - 32767 */ -#define TCG_CT_CONST_P2M1 0x800 /* Power of 2 minus 1. */ -#define TCG_CT_CONST_N16 0x1000 /* "Negatable" 16-bit: -32767 - 32767 */ -#define TCG_CT_CONST_WSZ 0x2000 /* word size */ +#define TCG_CT_CONST_U16 0x100 /* Unsigned 16-bit: 0 - 0xffff. */ +#define TCG_CT_CONST_S16 0x200 /* Signed 16-bit: -32768 - 32767 */ +#define TCG_CT_CONST_P2M1 0x400 /* Power of 2 minus 1. */ +#define TCG_CT_CONST_N16 0x800 /* "Negatable" 16-bit: -32767 - 32767 */ +#define TCG_CT_CONST_WSZ 0x1000 /* word size */ #define ALL_GENERAL_REGS 0xffffffffu @@ -204,8 +203,6 @@ static bool tcg_target_const_match(int64_t val, int ct, { if (ct & TCG_CT_CONST) { return 1; - } else if ((ct & TCG_CT_CONST_ZERO) && val == 0) { - return 1; } else if ((ct & TCG_CT_CONST_U16) && val == (uint16_t)val) { return 1; } else if ((ct & TCG_CT_CONST_S16) && val == (int16_t)val) { @@ -1666,11 +1663,6 @@ static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, TCGArg a0, a1, a2; int c2; - /* - * Note that many operands use the constraint set "rZ". - * We make use of the fact that 0 is the ZERO register, - * and hence such cases need not check for const_args. - */ a0 = args[0]; a1 = args[1]; a2 = args[2]; @@ -2181,14 +2173,14 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_st16_i64: case INDEX_op_st32_i64: case INDEX_op_st_i64: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_add_i32: case INDEX_op_add_i64: return C_O1_I2(r, r, rJ); case INDEX_op_sub_i32: case INDEX_op_sub_i64: - return C_O1_I2(r, rZ, rN); + return C_O1_I2(r, rz, rN); case INDEX_op_mul_i32: case INDEX_op_mulsh_i32: case INDEX_op_muluh_i32: @@ -2207,7 +2199,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_remu_i64: case INDEX_op_nor_i64: case INDEX_op_setcond_i64: - return C_O1_I2(r, rZ, rZ); + return C_O1_I2(r, rz, rz); case INDEX_op_muls2_i32: case INDEX_op_mulu2_i32: case INDEX_op_muls2_i64: @@ -2234,35 +2226,35 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) return C_O1_I2(r, r, ri); case INDEX_op_clz_i32: case INDEX_op_clz_i64: - return C_O1_I2(r, r, rWZ); + return C_O1_I2(r, r, rzW); case INDEX_op_deposit_i32: case INDEX_op_deposit_i64: - return C_O1_I2(r, 0, rZ); + return C_O1_I2(r, 0, rz); case INDEX_op_brcond_i32: case INDEX_op_brcond_i64: - return C_O0_I2(rZ, rZ); + return C_O0_I2(rz, rz); case INDEX_op_movcond_i32: case INDEX_op_movcond_i64: return (use_mips32r6_instructions - ? C_O1_I4(r, rZ, rZ, rZ, rZ) - : C_O1_I4(r, rZ, rZ, rZ, 0)); + ? C_O1_I4(r, rz, rz, rz, rz) + : C_O1_I4(r, rz, rz, rz, 0)); case INDEX_op_add2_i32: case INDEX_op_sub2_i32: - return C_O2_I4(r, r, rZ, rZ, rN, rN); + return C_O2_I4(r, r, rz, rz, rN, rN); case INDEX_op_setcond2_i32: - return C_O1_I4(r, rZ, rZ, rZ, rZ); + return C_O1_I4(r, rz, rz, rz, rz); case INDEX_op_brcond2_i32: - return C_O0_I4(rZ, rZ, rZ, rZ); + return C_O0_I4(rz, rz, rz, rz); case INDEX_op_qemu_ld_i32: return C_O1_I1(r, r); case INDEX_op_qemu_st_i32: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_qemu_ld_i64: return TCG_TARGET_REG_BITS == 64 ? C_O1_I1(r, r) : C_O2_I1(r, r, r); case INDEX_op_qemu_st_i64: - return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(rZ, r) : C_O0_I3(rZ, rZ, r); + return TCG_TARGET_REG_BITS == 64 ? C_O0_I2(rz, r) : C_O0_I3(rz, rz, r); default: return C_NotImplemented; From f466db1e27131e58d1cfac0d7ce2eb5b28ed22a3 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 9 Feb 2025 17:26:28 -0800 Subject: [PATCH 0131/1179] tcg/riscv: Use 'z' constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace target-specific 'Z' with generic 'z'. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/riscv/tcg-target-con-set.h | 10 +++++----- tcg/riscv/tcg-target-con-str.h | 1 - tcg/riscv/tcg-target.c.inc | 28 ++++++++++++---------------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/tcg/riscv/tcg-target-con-set.h b/tcg/riscv/tcg-target-con-set.h index 3c4ef44eb0a8..e92e81549161 100644 --- a/tcg/riscv/tcg-target-con-set.h +++ b/tcg/riscv/tcg-target-con-set.h @@ -10,17 +10,17 @@ * tcg-target-con-str.h; the constraint combination is inclusive or. */ C_O0_I1(r) -C_O0_I2(rZ, r) -C_O0_I2(rZ, rZ) +C_O0_I2(rz, r) +C_O0_I2(rz, rz) C_O1_I1(r, r) C_O1_I2(r, r, ri) C_O1_I2(r, r, rI) C_O1_I2(r, r, rJ) -C_O1_I2(r, rZ, rN) -C_O1_I2(r, rZ, rZ) +C_O1_I2(r, rz, rN) +C_O1_I2(r, rz, rz) C_N1_I2(r, r, rM) C_O1_I4(r, r, rI, rM, rM) -C_O2_I4(r, r, rZ, rZ, rM, rM) +C_O2_I4(r, r, rz, rz, rM, rM) C_O0_I2(v, r) C_O1_I1(v, r) C_O1_I1(v, v) diff --git a/tcg/riscv/tcg-target-con-str.h b/tcg/riscv/tcg-target-con-str.h index 089efe96ca8a..2f9700638c44 100644 --- a/tcg/riscv/tcg-target-con-str.h +++ b/tcg/riscv/tcg-target-con-str.h @@ -21,4 +21,3 @@ CONST('K', TCG_CT_CONST_S5) CONST('L', TCG_CT_CONST_CMP_VI) CONST('N', TCG_CT_CONST_N12) CONST('M', TCG_CT_CONST_M12) -CONST('Z', TCG_CT_CONST_ZERO) diff --git a/tcg/riscv/tcg-target.c.inc b/tcg/riscv/tcg-target.c.inc index 689fbea0df81..f7e1ca5a56f0 100644 --- a/tcg/riscv/tcg-target.c.inc +++ b/tcg/riscv/tcg-target.c.inc @@ -112,13 +112,12 @@ static TCGReg tcg_target_call_oarg_reg(TCGCallReturnKind kind, int slot) return TCG_REG_A0 + slot; } -#define TCG_CT_CONST_ZERO 0x100 -#define TCG_CT_CONST_S12 0x200 -#define TCG_CT_CONST_N12 0x400 -#define TCG_CT_CONST_M12 0x800 -#define TCG_CT_CONST_J12 0x1000 -#define TCG_CT_CONST_S5 0x2000 -#define TCG_CT_CONST_CMP_VI 0x4000 +#define TCG_CT_CONST_S12 0x100 +#define TCG_CT_CONST_N12 0x200 +#define TCG_CT_CONST_M12 0x400 +#define TCG_CT_CONST_J12 0x800 +#define TCG_CT_CONST_S5 0x1000 +#define TCG_CT_CONST_CMP_VI 0x2000 #define ALL_GENERAL_REGS MAKE_64BIT_MASK(0, 32) #define ALL_VECTOR_REGS MAKE_64BIT_MASK(32, 32) @@ -391,9 +390,6 @@ static bool tcg_target_const_match(int64_t val, int ct, if (ct & TCG_CT_CONST) { return 1; } - if ((ct & TCG_CT_CONST_ZERO) && val == 0) { - return 1; - } if (type >= TCG_TYPE_V64) { /* Val is replicated by VECE; extract the highest element. */ val >>= (-8 << vece) & 63; @@ -2681,7 +2677,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_st16_i64: case INDEX_op_st32_i64: case INDEX_op_st_i64: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_add_i32: case INDEX_op_and_i32: @@ -2707,7 +2703,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_sub_i32: case INDEX_op_sub_i64: - return C_O1_I2(r, rZ, rN); + return C_O1_I2(r, rz, rN); case INDEX_op_mul_i32: case INDEX_op_mulsh_i32: @@ -2723,7 +2719,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_divu_i64: case INDEX_op_rem_i64: case INDEX_op_remu_i64: - return C_O1_I2(r, rZ, rZ); + return C_O1_I2(r, rz, rz); case INDEX_op_shl_i32: case INDEX_op_shr_i32: @@ -2745,7 +2741,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_brcond_i32: case INDEX_op_brcond_i64: - return C_O0_I2(rZ, rZ); + return C_O0_I2(rz, rz); case INDEX_op_movcond_i32: case INDEX_op_movcond_i64: @@ -2755,14 +2751,14 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_add2_i64: case INDEX_op_sub2_i32: case INDEX_op_sub2_i64: - return C_O2_I4(r, r, rZ, rZ, rM, rM); + return C_O2_I4(r, r, rz, rz, rM, rM); case INDEX_op_qemu_ld_i32: case INDEX_op_qemu_ld_i64: return C_O1_I1(r, r); case INDEX_op_qemu_st_i32: case INDEX_op_qemu_st_i64: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_st_vec: return C_O0_I2(v, r); From 1bbcae5adaad2d8f026194002f54913be5ee0933 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 9 Feb 2025 18:11:20 -0800 Subject: [PATCH 0132/1179] tcg/sparc64: Use 'z' constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace target-specific 'Z' with generic 'z'. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/sparc64/tcg-target-con-set.h | 12 ++++++------ tcg/sparc64/tcg-target-con-str.h | 1 - tcg/sparc64/tcg-target.c.inc | 17 +++++++---------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/tcg/sparc64/tcg-target-con-set.h b/tcg/sparc64/tcg-target-con-set.h index 434bf25072d5..61f9fa3d9fc9 100644 --- a/tcg/sparc64/tcg-target-con-set.h +++ b/tcg/sparc64/tcg-target-con-set.h @@ -10,11 +10,11 @@ * tcg-target-con-str.h; the constraint combination is inclusive or. */ C_O0_I1(r) -C_O0_I2(rZ, r) -C_O0_I2(rZ, rJ) +C_O0_I2(rz, r) +C_O0_I2(rz, rJ) C_O1_I1(r, r) C_O1_I2(r, r, r) -C_O1_I2(r, rZ, rJ) -C_O1_I4(r, rZ, rJ, rI, 0) -C_O2_I2(r, r, rZ, rJ) -C_O2_I4(r, r, rZ, rZ, rJ, rJ) +C_O1_I2(r, rz, rJ) +C_O1_I4(r, rz, rJ, rI, 0) +C_O2_I2(r, r, rz, rJ) +C_O2_I4(r, r, rz, rz, rJ, rJ) diff --git a/tcg/sparc64/tcg-target-con-str.h b/tcg/sparc64/tcg-target-con-str.h index 0577ec494205..2f033b3ac24a 100644 --- a/tcg/sparc64/tcg-target-con-str.h +++ b/tcg/sparc64/tcg-target-con-str.h @@ -16,4 +16,3 @@ REGS('r', ALL_GENERAL_REGS) */ CONST('I', TCG_CT_CONST_S11) CONST('J', TCG_CT_CONST_S13) -CONST('Z', TCG_CT_CONST_ZERO) diff --git a/tcg/sparc64/tcg-target.c.inc b/tcg/sparc64/tcg-target.c.inc index 527af5665d57..7c722f59a8ab 100644 --- a/tcg/sparc64/tcg-target.c.inc +++ b/tcg/sparc64/tcg-target.c.inc @@ -76,7 +76,6 @@ static const char * const tcg_target_reg_names[TCG_TARGET_NB_REGS] = { #define TCG_CT_CONST_S11 0x100 #define TCG_CT_CONST_S13 0x200 -#define TCG_CT_CONST_ZERO 0x400 #define ALL_GENERAL_REGS MAKE_64BIT_MASK(0, 32) @@ -340,9 +339,7 @@ static bool tcg_target_const_match(int64_t val, int ct, val = (int32_t)val; } - if ((ct & TCG_CT_CONST_ZERO) && val == 0) { - return 1; - } else if ((ct & TCG_CT_CONST_S11) && check_fit_tl(val, 11)) { + if ((ct & TCG_CT_CONST_S11) && check_fit_tl(val, 11)) { return 1; } else if ((ct & TCG_CT_CONST_S13) && check_fit_tl(val, 13)) { return 1; @@ -1579,7 +1576,7 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_st_i64: case INDEX_op_qemu_st_i32: case INDEX_op_qemu_st_i64: - return C_O0_I2(rZ, r); + return C_O0_I2(rz, r); case INDEX_op_add_i32: case INDEX_op_add_i64: @@ -1611,22 +1608,22 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_setcond_i64: case INDEX_op_negsetcond_i32: case INDEX_op_negsetcond_i64: - return C_O1_I2(r, rZ, rJ); + return C_O1_I2(r, rz, rJ); case INDEX_op_brcond_i32: case INDEX_op_brcond_i64: - return C_O0_I2(rZ, rJ); + return C_O0_I2(rz, rJ); case INDEX_op_movcond_i32: case INDEX_op_movcond_i64: - return C_O1_I4(r, rZ, rJ, rI, 0); + return C_O1_I4(r, rz, rJ, rI, 0); case INDEX_op_add2_i32: case INDEX_op_add2_i64: case INDEX_op_sub2_i32: case INDEX_op_sub2_i64: - return C_O2_I4(r, r, rZ, rZ, rJ, rJ); + return C_O2_I4(r, r, rz, rz, rJ, rJ); case INDEX_op_mulu2_i32: case INDEX_op_muls2_i32: - return C_O2_I2(r, r, rZ, rJ); + return C_O2_I2(r, r, rz, rJ); case INDEX_op_muluh_i64: return C_O1_I2(r, r, r); From 4b7b20a3b72c5000ea71bef505c16e6e628268b6 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Thu, 13 Feb 2025 11:35:58 -0300 Subject: [PATCH 0133/1179] elfload: Fix alignment when unmapping excess reservation When complying with the alignment requested in the ELF and unmapping the excess reservation, having align_end not aligned to the guest page causes the unmap to be rejected by the alignment check at target_munmap and later brk adjustments hit an EEXIST. Fix by aligning the start of region to be unmapped. Fixes: c81d1fafa6 ("linux-user: Honor elf alignment when placing images") Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1913 Signed-off-by: Fabiano Rosas [rth: Align load_end as well.] Signed-off-by: Richard Henderson Message-ID: <20250213143558.10504-1-farosas@suse.de> --- linux-user/elfload.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index a2c152e5ad11..8799e4ea27df 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -3351,8 +3351,8 @@ static void load_elf_image(const char *image_name, const ImageSource *src, if (align_size != reserve_size) { abi_ulong align_addr = ROUND_UP(load_addr, align); - abi_ulong align_end = align_addr + reserve_size; - abi_ulong load_end = load_addr + align_size; + abi_ulong align_end = TARGET_PAGE_ALIGN(align_addr + reserve_size); + abi_ulong load_end = TARGET_PAGE_ALIGN(load_addr + align_size); if (align_addr != load_addr) { target_munmap(load_addr, align_addr - load_addr); From 513823e7521a09ed7ad1e32e6454bac3b2cbf52d Mon Sep 17 00:00:00 2001 From: Andreas Schwab Date: Mon, 10 Feb 2025 13:59:34 +0100 Subject: [PATCH 0134/1179] linux-user: Move TARGET_SA_RESTORER out of generic/signal.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SA_RESTORER and the associated sa_restorer field of struct sigaction are an obsolete feature, not expected to be used by future architectures. They are also absent on RISC-V, LoongArch, Hexagon and OpenRISC, but defined due to their use of generic/signal.h. This leads to corrupted data and out-of-bounds accesses. Move the definition of TARGET_SA_RESTORER out of generic/signal.h into the target_signal.h files that need it. Note that m68k has the sa_restorer field, but does not use it and does not define SA_RESTORER. Reported-by: Thomas Weißschuh Signed-off-by: Andreas Schwab Reviewed-by: Thomas Weißschuh Reviewed-by: Richard Henderson Signed-off-by: Richard Henderson Message-ID: --- linux-user/aarch64/target_signal.h | 2 ++ linux-user/arm/target_signal.h | 2 ++ linux-user/generic/signal.h | 1 - linux-user/i386/target_signal.h | 2 ++ linux-user/m68k/target_signal.h | 1 + linux-user/microblaze/target_signal.h | 2 ++ linux-user/ppc/target_signal.h | 2 ++ linux-user/s390x/target_signal.h | 2 ++ linux-user/sh4/target_signal.h | 2 ++ linux-user/x86_64/target_signal.h | 2 ++ linux-user/xtensa/target_signal.h | 2 ++ 11 files changed, 19 insertions(+), 1 deletion(-) diff --git a/linux-user/aarch64/target_signal.h b/linux-user/aarch64/target_signal.h index 40e399d9908d..6f66a50bfd2a 100644 --- a/linux-user/aarch64/target_signal.h +++ b/linux-user/aarch64/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #define TARGET_SEGV_MTEAERR 8 /* Asynchronous ARM MTE error */ #define TARGET_SEGV_MTESERR 9 /* Synchronous ARM MTE exception */ diff --git a/linux-user/arm/target_signal.h b/linux-user/arm/target_signal.h index 0e6351d9f789..ff1810b1fe02 100644 --- a/linux-user/arm/target_signal.h +++ b/linux-user/arm/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #define TARGET_ARCH_HAS_SETUP_FRAME #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 1 diff --git a/linux-user/generic/signal.h b/linux-user/generic/signal.h index 6fd05b77bb3e..b34740258edb 100644 --- a/linux-user/generic/signal.h +++ b/linux-user/generic/signal.h @@ -15,7 +15,6 @@ #define TARGET_SA_RESTART 0x10000000 #define TARGET_SA_NODEFER 0x40000000 #define TARGET_SA_RESETHAND 0x80000000 -#define TARGET_SA_RESTORER 0x04000000 #define TARGET_SIGHUP 1 #define TARGET_SIGINT 2 diff --git a/linux-user/i386/target_signal.h b/linux-user/i386/target_signal.h index 9315cba241cf..eee792ef637e 100644 --- a/linux-user/i386/target_signal.h +++ b/linux-user/i386/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #define TARGET_ARCH_HAS_SETUP_FRAME #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 1 diff --git a/linux-user/m68k/target_signal.h b/linux-user/m68k/target_signal.h index 6e0f4b74e391..b05b9303b0d5 100644 --- a/linux-user/m68k/target_signal.h +++ b/linux-user/m68k/target_signal.h @@ -3,6 +3,7 @@ #include "../generic/signal.h" +#define TARGET_ARCH_HAS_SA_RESTORER 1 #define TARGET_ARCH_HAS_SETUP_FRAME #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 1 diff --git a/linux-user/microblaze/target_signal.h b/linux-user/microblaze/target_signal.h index 7dc5c45f00ac..ffe4442213a9 100644 --- a/linux-user/microblaze/target_signal.h +++ b/linux-user/microblaze/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 1 #endif /* MICROBLAZE_TARGET_SIGNAL_H */ diff --git a/linux-user/ppc/target_signal.h b/linux-user/ppc/target_signal.h index 5be24e152b7e..53fae473f3c3 100644 --- a/linux-user/ppc/target_signal.h +++ b/linux-user/ppc/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #if !defined(TARGET_PPC64) #define TARGET_ARCH_HAS_SETUP_FRAME #endif diff --git a/linux-user/s390x/target_signal.h b/linux-user/s390x/target_signal.h index 41e0e34a55d7..738e0673f4a3 100644 --- a/linux-user/s390x/target_signal.h +++ b/linux-user/s390x/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #define TARGET_ARCH_HAS_SETUP_FRAME #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 1 diff --git a/linux-user/sh4/target_signal.h b/linux-user/sh4/target_signal.h index eee6a1a7cda4..0bde417fd1fd 100644 --- a/linux-user/sh4/target_signal.h +++ b/linux-user/sh4/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #define TARGET_ARCH_HAS_SETUP_FRAME #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 1 diff --git a/linux-user/x86_64/target_signal.h b/linux-user/x86_64/target_signal.h index 9d9717406f3b..0af100c66110 100644 --- a/linux-user/x86_64/target_signal.h +++ b/linux-user/x86_64/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + /* For x86_64, use of SA_RESTORER is mandatory. */ #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 0 diff --git a/linux-user/xtensa/target_signal.h b/linux-user/xtensa/target_signal.h index e4b1bea5cb5d..8a198bf8ac88 100644 --- a/linux-user/xtensa/target_signal.h +++ b/linux-user/xtensa/target_signal.h @@ -3,6 +3,8 @@ #include "../generic/signal.h" +#define TARGET_SA_RESTORER 0x04000000 + #define TARGET_ARCH_HAS_SIGTRAMP_PAGE 1 #endif From 807c3ebd1e3fc2a1be6cdfc702ccea3fa0d2d9b2 Mon Sep 17 00:00:00 2001 From: Mikael Szreder Date: Wed, 5 Feb 2025 10:03:32 +0100 Subject: [PATCH 0135/1179] target/sparc: Fix register selection for all F*TOx and FxTO* instructions A bug was introduced in commit 0bba7572d40d which causes the fdtox and fqtox instructions to incorrectly select the destination registers. More information and a test program can be found in issue #2802. Cc: qemu-stable@nongnu.org Fixes: 0bba7572d40d ("target/sparc: Perform DFPREG/QFPREG in decodetree") Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2802 Signed-off-by: Mikael Szreder Acked-by: Artyom Tarasenko [rth: Squash patches together, since the second fixes a typo in the first.] Signed-off-by: Richard Henderson Message-ID: <20250205090333.19626-3-git@miszr.win> --- target/sparc/insns.decode | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/target/sparc/insns.decode b/target/sparc/insns.decode index 989c20b44add..cfcdf6690e6e 100644 --- a/target/sparc/insns.decode +++ b/target/sparc/insns.decode @@ -321,12 +321,12 @@ FdMULq 10 ..... 110100 ..... 0 0110 1110 ..... @q_d_d FNHADDs 10 ..... 110100 ..... 0 0111 0001 ..... @r_r_r FNHADDd 10 ..... 110100 ..... 0 0111 0010 ..... @d_d_d FNsMULd 10 ..... 110100 ..... 0 0111 1001 ..... @d_r_r -FsTOx 10 ..... 110100 00000 0 1000 0001 ..... @r_r2 -FdTOx 10 ..... 110100 00000 0 1000 0010 ..... @r_d2 -FqTOx 10 ..... 110100 00000 0 1000 0011 ..... @r_q2 -FxTOs 10 ..... 110100 00000 0 1000 0100 ..... @r_r2 -FxTOd 10 ..... 110100 00000 0 1000 1000 ..... @d_r2 -FxTOq 10 ..... 110100 00000 0 1000 1100 ..... @q_r2 +FsTOx 10 ..... 110100 00000 0 1000 0001 ..... @d_r2 +FdTOx 10 ..... 110100 00000 0 1000 0010 ..... @d_d2 +FqTOx 10 ..... 110100 00000 0 1000 0011 ..... @d_q2 +FxTOs 10 ..... 110100 00000 0 1000 0100 ..... @r_d2 +FxTOd 10 ..... 110100 00000 0 1000 1000 ..... @d_d2 +FxTOq 10 ..... 110100 00000 0 1000 1100 ..... @q_d2 FiTOs 10 ..... 110100 00000 0 1100 0100 ..... @r_r2 FdTOs 10 ..... 110100 00000 0 1100 0110 ..... @r_d2 FqTOs 10 ..... 110100 00000 0 1100 0111 ..... @r_q2 From 7a74e468089a58756b438d31a2a9a97f183780d7 Mon Sep 17 00:00:00 2001 From: Mikael Szreder Date: Fri, 14 Feb 2025 08:03:43 +0100 Subject: [PATCH 0136/1179] target/sparc: Fix gdbstub incorrectly handling registers f32-f62 The gdbstub implementation for the Sparc architecture would incorrectly calculate the the floating point register offset. This resulted in, for example, registers f32 and f34 to point to the same value. The issue was caused by the confusion between even register numbers and even register indexes. For example, the register index of f32 is 64 and f34 is 65. Cc: qemu-stable@nongnu.org Fixes: 30038fd81808 ("target-sparc: Change fpr representation to doubles.") Signed-off-by: Mikael Szreder Reviewed-by: Richard Henderson Signed-off-by: Richard Henderson Message-ID: <20250214070343.11501-1-git@miszr.win> --- target/sparc/gdbstub.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/target/sparc/gdbstub.c b/target/sparc/gdbstub.c index ec0036e9ef64..134617fb232b 100644 --- a/target/sparc/gdbstub.c +++ b/target/sparc/gdbstub.c @@ -79,8 +79,13 @@ int sparc_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) } } if (n < 80) { - /* f32-f62 (double width, even numbers only) */ - return gdb_get_reg64(mem_buf, env->fpr[(n - 32) / 2].ll); + /* f32-f62 (16 double width registers, even register numbers only) + * n == 64: f32 : env->fpr[16] + * n == 65: f34 : env->fpr[17] + * etc... + * n == 79: f62 : env->fpr[31] + */ + return gdb_get_reg64(mem_buf, env->fpr[(n - 64) + 16].ll); } switch (n) { case 80: @@ -173,8 +178,13 @@ int sparc_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) } return 4; } else if (n < 80) { - /* f32-f62 (double width, even numbers only) */ - env->fpr[(n - 32) / 2].ll = tmp; + /* f32-f62 (16 double width registers, even register numbers only) + * n == 64: f32 : env->fpr[16] + * n == 65: f34 : env->fpr[17] + * etc... + * n == 79: f62 : env->fpr[31] + */ + env->fpr[(n - 64) + 16].ll = tmp; } else { switch (n) { case 80: From 172e7644f336dd82cb8d56cfb964477731f34f43 Mon Sep 17 00:00:00 2001 From: Artyom Tarasenko Date: Sun, 9 Feb 2025 22:12:48 +0100 Subject: [PATCH 0137/1179] target/sparc: fake UltraSPARC T1 PCR and PIC registers Fake access to PCR Performance Control Register and PIC Performance Instrumentation Counter. Ignore writes in privileged mode, and return 0 on reads. This allows booting Tribblix, MilaX and v9os under Niagara target. Signed-off-by: Artyom Tarasenko Reviewed-by: Richard Henderson Signed-off-by: Richard Henderson Message-ID: <20250209211248.50383-1-atar4qemu@gmail.com> --- target/sparc/insns.decode | 7 ++++++- target/sparc/translate.c | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/target/sparc/insns.decode b/target/sparc/insns.decode index cfcdf6690e6e..9e39d232735e 100644 --- a/target/sparc/insns.decode +++ b/target/sparc/insns.decode @@ -96,7 +96,10 @@ CALL 01 i:s30 RDTICK 10 rd:5 101000 00100 0 0000000000000 RDPC 10 rd:5 101000 00101 0 0000000000000 RDFPRS 10 rd:5 101000 00110 0 0000000000000 - RDASR17 10 rd:5 101000 10001 0 0000000000000 + { + RDASR17 10 rd:5 101000 10001 0 0000000000000 + RDPIC 10 rd:5 101000 10001 0 0000000000000 + } RDGSR 10 rd:5 101000 10011 0 0000000000000 RDSOFTINT 10 rd:5 101000 10110 0 0000000000000 RDTICK_CMPR 10 rd:5 101000 10111 0 0000000000000 @@ -114,6 +117,8 @@ CALL 01 i:s30 WRCCR 10 00010 110000 ..... . ............. @n_r_ri WRASI 10 00011 110000 ..... . ............. @n_r_ri WRFPRS 10 00110 110000 ..... . ............. @n_r_ri + WRPCR 10 10000 110000 01000 0 0000000000000 + WRPIC 10 10001 110000 01000 0 0000000000000 { WRGSR 10 10011 110000 ..... . ............. @n_r_ri WRPOWERDOWN 10 10011 110000 ..... . ............. @n_r_ri diff --git a/target/sparc/translate.c b/target/sparc/translate.c index 7e5c7351cb32..bfe63649db24 100644 --- a/target/sparc/translate.c +++ b/target/sparc/translate.c @@ -2882,6 +2882,14 @@ static TCGv do_rd_leon3_config(DisasContext *dc, TCGv dst) TRANS(RDASR17, ASR17, do_rd_special, true, a->rd, do_rd_leon3_config) +static TCGv do_rdpic(DisasContext *dc, TCGv dst) +{ + return tcg_constant_tl(0); +} + +TRANS(RDPIC, HYPV, do_rd_special, supervisor(dc), a->rd, do_rdpic) + + static TCGv do_rdccr(DisasContext *dc, TCGv dst) { gen_helper_rdccr(dst, tcg_env); @@ -3315,6 +3323,17 @@ static void do_wrfprs(DisasContext *dc, TCGv src) TRANS(WRFPRS, 64, do_wr_special, a, true, do_wrfprs) +static bool do_priv_nop(DisasContext *dc, bool priv) +{ + if (!priv) { + return raise_priv(dc); + } + return advance_pc(dc); +} + +TRANS(WRPCR, HYPV, do_priv_nop, supervisor(dc)) +TRANS(WRPIC, HYPV, do_priv_nop, supervisor(dc)) + static void do_wrgsr(DisasContext *dc, TCGv src) { gen_trap_ifnofpu(dc); From 175aa36668d6e91157d5e5b092b441f96f46b05e Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 10 Jan 2025 10:08:20 -0800 Subject: [PATCH 0138/1179] tcg/i386: Use tcg_{high,unsigned}_cond in tcg_out_brcond2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminate code repetition by using the appropriate helpers. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/i386/tcg-target.c.inc | 63 +++++---------------------------------- 1 file changed, 7 insertions(+), 56 deletions(-) diff --git a/tcg/i386/tcg-target.c.inc b/tcg/i386/tcg-target.c.inc index cfea4c496dd0..33d303a12357 100644 --- a/tcg/i386/tcg-target.c.inc +++ b/tcg/i386/tcg-target.c.inc @@ -1658,6 +1658,7 @@ static void tcg_out_brcond2(TCGContext *s, const TCGArg *args, tcg_out_brcond(s, 0, cond, args[1], args[3], const_args[3], label_this, small); break; + case TCG_COND_NE: case TCG_COND_TSTNE: tcg_out_brcond(s, 0, cond, args[0], args[2], const_args[2], @@ -1665,64 +1666,14 @@ static void tcg_out_brcond2(TCGContext *s, const TCGArg *args, tcg_out_brcond(s, 0, cond, args[1], args[3], const_args[3], label_this, small); break; - case TCG_COND_LT: - tcg_out_brcond(s, 0, TCG_COND_LT, args[1], args[3], const_args[3], - label_this, small); - tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_LTU, args[0], args[2], const_args[2], - label_this, small); - break; - case TCG_COND_LE: - tcg_out_brcond(s, 0, TCG_COND_LT, args[1], args[3], const_args[3], - label_this, small); - tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_LEU, args[0], args[2], const_args[2], - label_this, small); - break; - case TCG_COND_GT: - tcg_out_brcond(s, 0, TCG_COND_GT, args[1], args[3], const_args[3], - label_this, small); - tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_GTU, args[0], args[2], const_args[2], - label_this, small); - break; - case TCG_COND_GE: - tcg_out_brcond(s, 0, TCG_COND_GT, args[1], args[3], const_args[3], - label_this, small); - tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_GEU, args[0], args[2], const_args[2], - label_this, small); - break; - case TCG_COND_LTU: - tcg_out_brcond(s, 0, TCG_COND_LTU, args[1], args[3], const_args[3], - label_this, small); - tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_LTU, args[0], args[2], const_args[2], - label_this, small); - break; - case TCG_COND_LEU: - tcg_out_brcond(s, 0, TCG_COND_LTU, args[1], args[3], const_args[3], - label_this, small); - tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_LEU, args[0], args[2], const_args[2], - label_this, small); - break; - case TCG_COND_GTU: - tcg_out_brcond(s, 0, TCG_COND_GTU, args[1], args[3], const_args[3], - label_this, small); - tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_GTU, args[0], args[2], const_args[2], - label_this, small); - break; - case TCG_COND_GEU: - tcg_out_brcond(s, 0, TCG_COND_GTU, args[1], args[3], const_args[3], - label_this, small); + + default: + tcg_out_brcond(s, 0, tcg_high_cond(cond), args[1], + args[3], const_args[3], label_this, small); tcg_out_jxx(s, JCC_JNE, label_next, 1); - tcg_out_brcond(s, 0, TCG_COND_GEU, args[0], args[2], const_args[2], - label_this, small); + tcg_out_brcond(s, 0, tcg_unsigned_cond(cond), args[0], + args[2], const_args[2], label_this, small); break; - default: - g_assert_not_reached(); } tcg_out_label(s, label_next); } From e726f65867087d86436de05e9f372a86ec1381a6 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 10 Jan 2025 15:01:43 -0800 Subject: [PATCH 0139/1179] tcg: Remove TCG_TARGET_HAS_{br,set}cond2 from riscv and loongarch64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These defines never should have been added as they were never used. Only 32-bit hosts may have these opcodes and they have them unconditionally. Fixes: 6cb14e4de29 ("tcg/loongarch64: Add the tcg-target.h file") Fixes: fb1f70f3685 ("tcg/riscv: Add the tcg-target.h file") Acked-by: Alistair Francis Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson --- tcg/loongarch64/tcg-target-has.h | 2 -- tcg/riscv/tcg-target-has.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/tcg/loongarch64/tcg-target-has.h b/tcg/loongarch64/tcg-target-has.h index ac88522eef25..188b00799fbe 100644 --- a/tcg/loongarch64/tcg-target-has.h +++ b/tcg/loongarch64/tcg-target-has.h @@ -37,8 +37,6 @@ #define TCG_TARGET_HAS_clz_i32 1 #define TCG_TARGET_HAS_ctz_i32 1 #define TCG_TARGET_HAS_ctpop_i32 0 -#define TCG_TARGET_HAS_brcond2 0 -#define TCG_TARGET_HAS_setcond2 0 #define TCG_TARGET_HAS_qemu_st8_i32 0 /* 64-bit operations */ diff --git a/tcg/riscv/tcg-target-has.h b/tcg/riscv/tcg-target-has.h index f35f9b31f572..98081084f279 100644 --- a/tcg/riscv/tcg-target-has.h +++ b/tcg/riscv/tcg-target-has.h @@ -37,8 +37,6 @@ #define TCG_TARGET_HAS_clz_i32 (cpuinfo & CPUINFO_ZBB) #define TCG_TARGET_HAS_ctz_i32 (cpuinfo & CPUINFO_ZBB) #define TCG_TARGET_HAS_ctpop_i32 (cpuinfo & CPUINFO_ZBB) -#define TCG_TARGET_HAS_brcond2 1 -#define TCG_TARGET_HAS_setcond2 1 #define TCG_TARGET_HAS_qemu_st8_i32 0 #define TCG_TARGET_HAS_negsetcond_i64 1 From b819fd6994243aee6f9613edbbacedce4f511c32 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:22:56 +0000 Subject: [PATCH 0140/1179] target/arm: Report correct syndrome for UNDEFINED CNTPS_*_EL1 from EL2 and NS EL1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The access pseudocode for the CNTPS_TVAL_EL1, CNTPS_CTL_EL1 and CNTPS_CVAL_EL1 secure timer registers says that they are UNDEFINED from EL2 or NS EL1. We incorrectly return CP_ACCESS_TRAP from the access function in these cases, which means that we report the wrong syndrome value to the target EL. Use CP_ACCESS_TRAP_UNCATEGORIZED, which reports the correct syndrome value for an UNDEFINED instruction. Cc: qemu-stable@nongnu.org Fixes: b4d3978c2fd ("target-arm: Add the AArch64 view of the Secure physical timer") Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-2-peter.maydell@linaro.org --- target/arm/helper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index 7d95eae99711..b7d6afe0a1ae 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2385,7 +2385,7 @@ static CPAccessResult gt_stimer_access(CPUARMState *env, switch (arm_current_el(env)) { case 1: if (!arm_is_secure(env)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_UNCATEGORIZED; } if (!(env->cp15.scr_el3 & SCR_ST)) { return CP_ACCESS_TRAP_EL3; @@ -2393,7 +2393,7 @@ static CPAccessResult gt_stimer_access(CPUARMState *env, return CP_ACCESS_OK; case 0: case 2: - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_UNCATEGORIZED; case 3: return CP_ACCESS_OK; default: From 1960d9701ef7ed8d24e98def767bbf05d63e6992 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:22:57 +0000 Subject: [PATCH 0141/1179] target/arm: Report correct syndrome for UNDEFINED AT ops with wrong NSE, NS R_NYXTL says that these AT insns should be UNDEFINED if they would operate on an EL lower than EL3 and SCR_EL3.{NSE,NS} is set to the Reserved {1, 0}. We were incorrectly reporting them with the wrong syndrome; use CP_ACCESS_TRAP_UNCATEGORIZED so they are reported as UNDEFINED. Cc: qemu-stable@nongnu.org Fixes: 1acd00ef1410 ("target/arm/helper: Check SCR_EL3.{NSE, NS} encoding for AT instructions") Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-3-peter.maydell@linaro.org --- target/arm/helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index b7d6afe0a1ae..9ed1a67b7675 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -3601,7 +3601,7 @@ static CPAccessResult at_e012_access(CPUARMState *env, const ARMCPRegInfo *ri, * scr_write() ensures that the NSE bit is not set otherwise. */ if ((env->cp15.scr_el3 & (SCR_NSE | SCR_NS)) == SCR_NSE) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_UNCATEGORIZED; } return CP_ACCESS_OK; } From ccda792945d650bce4609c8dbce8814a220df1bb Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:22:58 +0000 Subject: [PATCH 0142/1179] target/arm: Report correct syndrome for UNDEFINED S1E2 AT ops at EL3 The pseudocode for AT S1E2R and AT S1E2W says that they should be UNDEFINED if executed at EL3 when EL2 is not enabled. We were incorrectly using CP_ACCESS_TRAP and reporting the wrong exception syndrome as a result. Use CP_ACCESS_TRAP_UNCATEGORIZED. Cc: qemu-stable@nongnu.org Fixes: 2a47df953202e1 ("target-arm: Wire up AArch64 EL2 and EL3 address translation ops") Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-4-peter.maydell@linaro.org --- target/arm/helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index 9ed1a67b7675..f4af2d2de540 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -3611,7 +3611,7 @@ static CPAccessResult at_s1e2_access(CPUARMState *env, const ARMCPRegInfo *ri, { if (arm_current_el(env) == 3 && !(env->cp15.scr_el3 & (SCR_NS | SCR_EEL2))) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_UNCATEGORIZED; } return at_e012_access(env, ri, isread); } From 707d478ed8f2da6f2327e5af780890c1fd9c371a Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:22:59 +0000 Subject: [PATCH 0143/1179] target/arm: Report correct syndrome for UNDEFINED LOR sysregs when NS=0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pseudocode for the accessors for the LOR sysregs says they are UNDEFINED if SCR_EL3.NS is 0. We were reporting the wrong syndrome value here; use CP_ACCESS_TRAP_UNCATEGORIZED. Cc: qemu-stable@nongnu.org Fixes: 2d7137c10faf ("target/arm: Implement the ARMv8.1-LOR extension") Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-5-peter.maydell@linaro.org --- target/arm/helper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index f4af2d2de540..4a0db087dd06 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -6750,8 +6750,8 @@ static CPAccessResult access_lor_other(CPUARMState *env, const ARMCPRegInfo *ri, bool isread) { if (arm_is_secure_below_el3(env)) { - /* Access denied in secure mode. */ - return CP_ACCESS_TRAP; + /* UNDEF if SCR_EL3.NS == 0 */ + return CP_ACCESS_TRAP_UNCATEGORIZED; } return access_lor_ns(env, ri, isread); } From 4cf4948651615181c5bc3d0e4a9f5c46be576bb2 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:00 +0000 Subject: [PATCH 0144/1179] target/arm: Make CP_ACCESS_TRAPs to AArch32 EL3 be Monitor traps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In system register access pseudocode the common pattern for AArch32 registers with access traps to EL3 is: at EL1 and EL2: if HaveEL(EL3) && !ELUsingAArch32(EL3) && (SCR_EL3.TERR == 1) then AArch64.AArch32SystemAccessTrap(EL3, 0x03); elsif HaveEL(EL3) && ELUsingAArch32(EL3) && (SCR.TERR == 1) then AArch32.TakeMonitorTrapException(); at EL3: if (PSTATE.M != M32_Monitor) && (SCR.TERR == 1) then AArch32.TakeMonitorTrapException(); (taking as an example the ERRIDR access pseudocode). This implements the behaviour of (in this case) SCR.TERR that "Accesses to the specified registers from modes other than Monitor mode generate a Monitor Trap exception" and of SCR_EL3.TERR that "Accesses of the specified Error Record registers at EL2 and EL1 are trapped to EL3, unless the instruction generates a higher priority exception". In QEMU we don't implement this pattern correctly in two ways: * in access_check_cp_reg() we turn the CP_ACCESS_TRAP_EL3 into an UNDEF, not a trap to Monitor mode * in the access functions, we check trap bits like SCR.TERR only when arm_current_el(env) < 3 -- this is correct for AArch64 EL3, but misses the "trap non-Monitor-mode execution at EL3 into Monitor mode" case for AArch32 EL3 In this commit we fix the first of these two issues, by making access_check_cp_reg() handle CP_ACCESS_TRAP_EL3 as a Monitor trap. This is a kind of exception that we haven't yet implemented(!), so we need a new EXCP_MON_TRAP for it. This diverges from the pseudocode approach, where every access check function explicitly checks for "if EL3 is AArch32" and takes a monitor trap; if we wanted to be closer to the pseudocode we could add a new CP_ACCESS_TRAP_MONITOR and make all the accessfns use it when appropriate. But because there are no non-standard cases in the pseudocode (i.e. where either it raises a Monitor trap that doesn't correspond to an AArch64 SystemAccessTrap or where it raises a SystemAccessTrap that doesn't correspond to a Monitor trap), handling this all in one place seems less likely to result in future bugs where we forgot again about this special case when writing an accessor. (The cc of stable here is because "hw/intc/arm_gicv3_cpuif: Don't downgrade monitor traps for AArch32 EL3" which is also cc:stable will implicitly use the new EXCP_MON_TRAP code path.) Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-6-peter.maydell@linaro.org --- target/arm/cpu.h | 1 + target/arm/helper.c | 11 +++++++++++ target/arm/tcg/op_helper.c | 13 ++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 6f6cf5c888bf..83ceaa58c2c4 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -62,6 +62,7 @@ #define EXCP_NMI 26 #define EXCP_VINMI 27 #define EXCP_VFNMI 28 +#define EXCP_MON_TRAP 29 /* AArch32 trap to Monitor mode */ /* NB: add new EXCP_ defines to the array in arm_log_exception() too */ #define ARMV7M_EXCP_RESET 1 diff --git a/target/arm/helper.c b/target/arm/helper.c index 4a0db087dd06..2bf39a2051d2 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -9684,6 +9684,7 @@ void arm_log_exception(CPUState *cs) [EXCP_NMI] = "NMI", [EXCP_VINMI] = "Virtual IRQ NMI", [EXCP_VFNMI] = "Virtual FIQ NMI", + [EXCP_MON_TRAP] = "Monitor Trap", }; if (idx >= 0 && idx < ARRAY_SIZE(excnames)) { @@ -10250,6 +10251,16 @@ static void arm_cpu_do_interrupt_aarch32(CPUState *cs) mask = CPSR_A | CPSR_I | CPSR_F; offset = 0; break; + case EXCP_MON_TRAP: + new_mode = ARM_CPU_MODE_MON; + addr = 0x04; + mask = CPSR_A | CPSR_I | CPSR_F; + if (env->thumb) { + offset = 2; + } else { + offset = 4; + } + break; default: cpu_abort(cs, "Unhandled exception 0x%x\n", cs->exception_index); return; /* Never happens. Keep compiler happy. */ diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index 1161d301b710..1ba727e8e9fd 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -758,6 +758,7 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, const ARMCPRegInfo *ri = get_arm_cp_reginfo(cpu->cp_regs, key); CPAccessResult res = CP_ACCESS_OK; int target_el; + uint32_t excp; assert(ri != NULL); @@ -851,8 +852,18 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, } fail: + excp = EXCP_UDEF; switch (res & ~CP_ACCESS_EL_MASK) { case CP_ACCESS_TRAP: + /* + * If EL3 is AArch32 then there's no syndrome register; the cases + * where we would raise a SystemAccessTrap to AArch64 EL3 all become + * raising a Monitor trap exception. (Because there's no visible + * syndrome it doesn't matter what we pass to raise_exception().) + */ + if ((res & CP_ACCESS_EL_MASK) == 3 && !arm_el_is_aa64(env, 3)) { + excp = EXCP_MON_TRAP; + } break; case CP_ACCESS_TRAP_UNCATEGORIZED: /* Only CP_ACCESS_TRAP traps are direct to a specified EL */ @@ -888,7 +899,7 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, g_assert_not_reached(); } - raise_exception(env, EXCP_UDEF, syndrome, target_el); + raise_exception(env, excp, syndrome, target_el); } const void *HELPER(lookup_cp_reg)(CPUARMState *env, uint32_t key) From d04c6c3c000ab3e588a2b91641310aeea89408f7 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:01 +0000 Subject: [PATCH 0145/1179] hw/intc/arm_gicv3_cpuif: Don't downgrade monitor traps for AArch32 EL3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the gicv3_{irq,fiq,irqfiq}_access() functions, there is a check which downgrades a CP_ACCESS_TRAP_EL3 to CP_ACCESS_TRAP if EL3 is not AArch64. This has been there since the GIC was first implemented, but it isn't right: if we are trapping because of SCR.IRQ or SCR.FIQ then we definitely want to be going to EL3 (doing AArch32.TakeMonitorTrapException() in pseudocode terms). We might want to not take a trap at all, but we don't ever want to go to the default target EL, because that would mean, for instance, taking a trap to Hyp mode if the trapped access was made from Hyp mode. (This might have been an attempt to work around our failure to properly implement Monitor Traps.) Remove the bogus check. Cc: qemu-stable@nongnu.org Fixes: 359fbe65e01e ("hw/intc/arm_gicv3: Implement GICv3 CPU interface registers") Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-7-peter.maydell@linaro.org --- hw/intc/arm_gicv3_cpuif.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hw/intc/arm_gicv3_cpuif.c b/hw/intc/arm_gicv3_cpuif.c index 9cad8313a3ac..8a715b3510b3 100644 --- a/hw/intc/arm_gicv3_cpuif.c +++ b/hw/intc/arm_gicv3_cpuif.c @@ -2300,9 +2300,6 @@ static CPAccessResult gicv3_irqfiq_access(CPUARMState *env, } } - if (r == CP_ACCESS_TRAP_EL3 && !arm_el_is_aa64(env, 3)) { - r = CP_ACCESS_TRAP; - } return r; } @@ -2365,9 +2362,6 @@ static CPAccessResult gicv3_fiq_access(CPUARMState *env, } } - if (r == CP_ACCESS_TRAP_EL3 && !arm_el_is_aa64(env, 3)) { - r = CP_ACCESS_TRAP; - } return r; } @@ -2404,9 +2398,6 @@ static CPAccessResult gicv3_irq_access(CPUARMState *env, } } - if (r == CP_ACCESS_TRAP_EL3 && !arm_el_is_aa64(env, 3)) { - r = CP_ACCESS_TRAP; - } return r; } From 4d436fb05c2a1fff7befc815ebcbb04a14977448 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:02 +0000 Subject: [PATCH 0146/1179] target/arm: Honour SDCR.TDCC and SCR.TERR in AArch32 EL3 non-Monitor modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are not many traps in AArch32 which should trap to Monitor mode, but these trap bits should trap not just lower ELs to Monitor mode but also the non-Monitor modes running at EL3 (i.e. Secure System, Secure Undef, etc). We get this wrong because the relevant access functions implement the AArch64-style logic of if (el < 3 && trap_bit_set) { return CP_ACCESS_TRAP_EL3; } which won't trap the non-Monitor modes at EL3. Correct this error by using arm_is_el3_or_mon() instead, which returns true when the CPU is at AArch64 EL3 or AArch32 Monitor mode. (Since the new callsites are compiled also for the linux-user mode, we need to provide a dummy implementation for CONFIG_USER_ONLY.) This affects only: * trapping of ERRIDR via SCR.TERR * trapping of the debug channel registers via SDCR.TDCC * trapping of GICv3 registers via SCR.IRQ and SCR.FIQ (which we already used arm_is_el3_or_mon() for) This patch changes the handling of SCR.TERR and SDCR.TDCC. This patch only changes guest-visible behaviour for "-cpu max" on the qemu-system-arm binary, because SCR.TERR and SDCR.TDCC (and indeed the entire SDCR register) only arrived in Armv8, and the only guest CPU we support which has any v8 features and also starts in AArch32 EL3 is the 32-bit 'max'. Other uses of CP_ACCESS_TRAP_EL3 don't need changing: * uses in code paths that can't happen when EL3 is AArch32: access_trap_aa32s_el1, cpacr_access, cptr_access, nsacr_access * uses which are in accessfns for AArch64-only registers: gt_stimer_access, gt_cntpoff_access, access_hxen, access_tpidr2, access_smpri, access_smprimap, access_lor_ns, access_pauth, access_mte, access_tfsr_el2, access_scxtnum, access_fgt * trap bits which exist only in the AArch64 version of the trap register, not the AArch32 one: access_tpm, pmreg_access, access_dbgvcr32, access_tdra, access_tda, access_tdosa (TPM, TDA and TDOSA exist only in MDCR_EL3, not in SDCR, and we enforce this in sdcr_write()) Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-8-peter.maydell@linaro.org --- target/arm/cpu.h | 5 +++++ target/arm/debug_helper.c | 3 ++- target/arm/helper.c | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 83ceaa58c2c4..215845c7e256 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -2596,6 +2596,11 @@ static inline bool arm_is_secure_below_el3(CPUARMState *env) return false; } +static inline bool arm_is_el3_or_mon(CPUARMState *env) +{ + return false; +} + static inline ARMSecuritySpace arm_security_space(CPUARMState *env) { return ARMSS_NonSecure; diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c index 2212ef4a3b94..c3c1eb5f6288 100644 --- a/target/arm/debug_helper.c +++ b/target/arm/debug_helper.c @@ -880,7 +880,8 @@ static CPAccessResult access_tdcc(CPUARMState *env, const ARMCPRegInfo *ri, if (el < 2 && (mdcr_el2_tda || mdcr_el2_tdcc)) { return CP_ACCESS_TRAP_EL2; } - if (el < 3 && ((env->cp15.mdcr_el3 & MDCR_TDA) || mdcr_el3_tdcc)) { + if (!arm_is_el3_or_mon(env) && + ((env->cp15.mdcr_el3 & MDCR_TDA) || mdcr_el3_tdcc)) { return CP_ACCESS_TRAP_EL3; } return CP_ACCESS_OK; diff --git a/target/arm/helper.c b/target/arm/helper.c index 2bf39a2051d2..535870f69a6a 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -6103,7 +6103,7 @@ static CPAccessResult access_terr(CPUARMState *env, const ARMCPRegInfo *ri, if (el < 2 && (arm_hcr_el2_eff(env) & HCR_TERR)) { return CP_ACCESS_TRAP_EL2; } - if (el < 3 && (env->cp15.scr_el3 & SCR_TERR)) { + if (!arm_is_el3_or_mon(env) && (env->cp15.scr_el3 & SCR_TERR)) { return CP_ACCESS_TRAP_EL3; } return CP_ACCESS_OK; From ff8b906a00494601687763446e470ff9b3580be7 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:03 +0000 Subject: [PATCH 0147/1179] hw/intc/arm_gicv3_cpuif(): Remove redundant tests of is_a64() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the gicv3_{irq,fiq,irqfiq}_access() functions, in the arm_current_el(env) == 3 case we do the following test: if (!is_a64(env) && !arm_is_el3_or_mon(env)) { r = CP_ACCESS_TRAP_EL3; } In this check, the "!is_a64(env)" is redundant, because if we are at EL3 and in AArch64 then arm_is_el3_or_mon() will return true and we will skip the if() body anyway. Remove the unnecessary tests. Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-9-peter.maydell@linaro.org --- hw/intc/arm_gicv3_cpuif.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hw/intc/arm_gicv3_cpuif.c b/hw/intc/arm_gicv3_cpuif.c index 8a715b3510b3..7f1d071c198b 100644 --- a/hw/intc/arm_gicv3_cpuif.c +++ b/hw/intc/arm_gicv3_cpuif.c @@ -2291,7 +2291,7 @@ static CPAccessResult gicv3_irqfiq_access(CPUARMState *env, r = CP_ACCESS_TRAP_EL3; break; case 3: - if (!is_a64(env) && !arm_is_el3_or_mon(env)) { + if (!arm_is_el3_or_mon(env)) { r = CP_ACCESS_TRAP_EL3; } break; @@ -2353,7 +2353,7 @@ static CPAccessResult gicv3_fiq_access(CPUARMState *env, r = CP_ACCESS_TRAP_EL3; break; case 3: - if (!is_a64(env) && !arm_is_el3_or_mon(env)) { + if (!arm_is_el3_or_mon(env)) { r = CP_ACCESS_TRAP_EL3; } break; @@ -2389,7 +2389,7 @@ static CPAccessResult gicv3_irq_access(CPUARMState *env, r = CP_ACCESS_TRAP_EL3; break; case 3: - if (!is_a64(env) && !arm_is_el3_or_mon(env)) { + if (!arm_is_el3_or_mon(env)) { r = CP_ACCESS_TRAP_EL3; } break; From 273d0e84ccd1f0a94f893d2f1ab750f812dfa219 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:04 +0000 Subject: [PATCH 0148/1179] target/arm: Support CP_ACCESS_TRAP_EL1 as a CPAccessResult In the CPAccessResult enum, the CP_ACCESS_TRAP* values indicate the equivalent of the pseudocode AArch64.SystemAccessTrap(..., 0x18), causing a trap to a specified exception level with a syndrome value giving information about the failing instructions. In the pseudocode, such traps are always taken to a specified target EL. We support that for target EL of 2 or 3 via CP_ACCESS_TRAP_EL2 and CP_ACCESS_TRAP_EL3, but the only way to take the access trap to EL1 currently is to use CP_ACCESS_TRAP, which takes the trap to the "usual target EL" (EL1 if in EL0, otherwise to the current EL). Add CP_ACCESS_TRAP_EL1 so that access functions can follow the pseudocode more closely. (Note that for the common case in the pseudocode of "trap to EL2 if HCR_EL2.TGE is set, otherwise trap to EL1", we handle this in raise_exception(), so access functions don't need to special case it and can use CP_ACCESS_TRAP_EL1.) Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-10-peter.maydell@linaro.org --- target/arm/cpregs.h | 1 + target/arm/tcg/op_helper.c | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/target/arm/cpregs.h b/target/arm/cpregs.h index 1759d9defbee..fbf5798069d3 100644 --- a/target/arm/cpregs.h +++ b/target/arm/cpregs.h @@ -331,6 +331,7 @@ typedef enum CPAccessResult { * 0xc or 0x18). */ CP_ACCESS_TRAP = (1 << 2), + CP_ACCESS_TRAP_EL1 = CP_ACCESS_TRAP | 1, CP_ACCESS_TRAP_EL2 = CP_ACCESS_TRAP | 2, CP_ACCESS_TRAP_EL3 = CP_ACCESS_TRAP | 3, diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index 1ba727e8e9fd..c427118655d7 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -781,7 +781,7 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, * the other trap takes priority. So we take the "check HSTR_EL2" path * for all of those cases.) */ - if (res != CP_ACCESS_OK && ((res & CP_ACCESS_EL_MASK) == 0) && + if (res != CP_ACCESS_OK && ((res & CP_ACCESS_EL_MASK) < 2) && arm_current_el(env) == 0) { goto fail; } @@ -887,6 +887,9 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, case 0: target_el = exception_target_el(env); break; + case 1: + assert(arm_current_el(env) < 2); + break; case 2: assert(arm_current_el(env) != 3); assert(arm_is_el2_enabled(env)); @@ -895,7 +898,6 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, assert(arm_feature(env, ARM_FEATURE_EL3)); break; default: - /* No "direct" traps to EL1 */ g_assert_not_reached(); } From 2d60f1acdb950e85335b018bcaf4ba0f042a350c Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:05 +0000 Subject: [PATCH 0149/1179] target/arm: Use CP_ACCESS_TRAP_EL1 for traps that are always to EL1 We currently use CP_ACCESS_TRAP in a number of access functions where we know we're currently at EL0; in this case the "usual target EL" is EL1, so CP_ACCESS_TRAP and CP_ACCESS_TRAP_EL1 behave the same. Use CP_ACCESS_TRAP_EL1 to more closely match the pseudocode for this sort of check. Note that in the case of the access functions foc cacheop to PoC or PoU, the code was correct but the comment was wrong: SCTLR_EL1.UCI traps for DC CVAC, DC CIVAC, DC CVAP, DC CVADP, DC CVAU and IC IVAU should be system access traps, not UNDEFs. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-11-peter.maydell@linaro.org --- target/arm/debug_helper.c | 2 +- target/arm/helper.c | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c index c3c1eb5f6288..36bffde74e94 100644 --- a/target/arm/debug_helper.c +++ b/target/arm/debug_helper.c @@ -875,7 +875,7 @@ static CPAccessResult access_tdcc(CPUARMState *env, const ARMCPRegInfo *ri, (env->cp15.mdcr_el3 & MDCR_TDCC); if (el < 1 && mdscr_el1_tdcc) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } if (el < 2 && (mdcr_el2_tda || mdcr_el2_tdcc)) { return CP_ACCESS_TRAP_EL2; diff --git a/target/arm/helper.c b/target/arm/helper.c index 535870f69a6a..aacb53d31a2a 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -881,7 +881,7 @@ static CPAccessResult pmreg_access(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t mdcr_el2 = arm_mdcr_el2_eff(env); if (el == 0 && !(env->cp15.c9_pmuserenr & 1)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } if (el < 2 && (mdcr_el2 & MDCR_TPM)) { return CP_ACCESS_TRAP_EL2; @@ -2159,7 +2159,7 @@ static CPAccessResult teehbr_access(CPUARMState *env, const ARMCPRegInfo *ri, bool isread) { if (arm_current_el(env) == 0 && (env->teecr & 1)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } return teecr_access(env, ri, isread); } @@ -2239,7 +2239,7 @@ static CPAccessResult gt_cntfrq_access(CPUARMState *env, const ARMCPRegInfo *ri, cntkctl = env->cp15.c14_cntkctl; } if (!extract32(cntkctl, 0, 2)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } break; case 1: @@ -2278,7 +2278,7 @@ static CPAccessResult gt_counter_access(CPUARMState *env, int timeridx, /* CNT[PV]CT: not visible from PL0 if EL0[PV]CTEN is zero */ if (!extract32(env->cp15.c14_cntkctl, timeridx, 1)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } /* fall through */ case 1: @@ -2319,7 +2319,7 @@ static CPAccessResult gt_timer_access(CPUARMState *env, int timeridx, * EL0 if EL0[PV]TEN is zero. */ if (!extract32(env->cp15.c14_cntkctl, 9 - timeridx, 1)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } /* fall through */ @@ -4499,7 +4499,7 @@ static CPAccessResult aa64_daif_access(CPUARMState *env, const ARMCPRegInfo *ri, bool isread) { if (arm_current_el(env) == 0 && !(arm_sctlr(env, 0) & SCTLR_UMA)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } return CP_ACCESS_OK; } @@ -4589,9 +4589,9 @@ static CPAccessResult aa64_cacheop_poc_access(CPUARMState *env, /* Cache invalidate/clean to Point of Coherency or Persistence... */ switch (arm_current_el(env)) { case 0: - /* ... EL0 must UNDEF unless SCTLR_EL1.UCI is set. */ + /* ... EL0 must trap to EL1 unless SCTLR_EL1.UCI is set. */ if (!(arm_sctlr(env, 0) & SCTLR_UCI)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } /* fall through */ case 1: @@ -4609,9 +4609,9 @@ static CPAccessResult do_cacheop_pou_access(CPUARMState *env, uint64_t hcrflags) /* Cache invalidate/clean to Point of Unification... */ switch (arm_current_el(env)) { case 0: - /* ... EL0 must UNDEF unless SCTLR_EL1.UCI is set. */ + /* ... EL0 must trap to EL1 unless SCTLR_EL1.UCI is set. */ if (!(arm_sctlr(env, 0) & SCTLR_UCI)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } /* fall through */ case 1: @@ -4651,7 +4651,7 @@ static CPAccessResult aa64_zva_access(CPUARMState *env, const ARMCPRegInfo *ri, } } else { if (!(env->cp15.sctlr_el[1] & SCTLR_DZE)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } if (hcr & HCR_TDZ) { return CP_ACCESS_TRAP_EL2; @@ -6073,7 +6073,7 @@ static CPAccessResult ctr_el0_access(CPUARMState *env, const ARMCPRegInfo *ri, } } else { if (!(env->cp15.sctlr_el[1] & SCTLR_UCT)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } if (hcr & HCR_TID2) { return CP_ACCESS_TRAP_EL2; @@ -6372,7 +6372,7 @@ static CPAccessResult access_tpidr2(CPUARMState *env, const ARMCPRegInfo *ri, if (el == 0) { uint64_t sctlr = arm_sctlr(env, el); if (!(sctlr & SCTLR_EnTP2)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } } /* TODO: FEAT_FGT */ @@ -7172,7 +7172,7 @@ static CPAccessResult access_scxtnum(CPUARMState *env, const ARMCPRegInfo *ri, if (hcr & HCR_TGE) { return CP_ACCESS_TRAP_EL2; } - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } } else if (el < 2 && (env->cp15.sctlr_el[2] & SCTLR_TSCXT)) { return CP_ACCESS_TRAP_EL2; @@ -7292,7 +7292,7 @@ static CPAccessResult access_predinv(CPUARMState *env, const ARMCPRegInfo *ri, if (el == 0) { uint64_t sctlr = arm_sctlr(env, el); if (!(sctlr & SCTLR_EnRCTX)) { - return CP_ACCESS_TRAP; + return CP_ACCESS_TRAP_EL1; } } else if (el == 1) { uint64_t hcr = arm_hcr_el2_eff(env); From f706b67da61aecc54bbdad16bea3fc69e9fd844b Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:06 +0000 Subject: [PATCH 0150/1179] target/arm: Use TRAP_UNCATEGORIZED for XScale CPAR traps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On XScale CPUs, there is no EL2 or AArch64, so no syndrome register. These traps are just UNDEFs in the traditional AArch32 sense, so CP_ACCESS_TRAP_UNCATEGORIZED is more accurate than CP_ACCESS_TRAP. This has no visible behavioural change, because the guest doesn't have a way to see the syndrome value we generate. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-12-peter.maydell@linaro.org --- target/arm/tcg/op_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index c427118655d7..c69d2ac643ff 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -764,7 +764,7 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, if (arm_feature(env, ARM_FEATURE_XSCALE) && ri->cp < 14 && extract32(env->cp15.c15_cpar, ri->cp, 1) == 0) { - res = CP_ACCESS_TRAP; + res = CP_ACCESS_TRAP_UNCATEGORIZED; goto fail; } From fc0ea471ec26cdc5639809c4ea4b70a80567f432 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:07 +0000 Subject: [PATCH 0151/1179] target/arm: Remove CP_ACCESS_TRAP handling There are no longer any uses of CP_ACCESS_TRAP in access functions, because we have converted them all to use either CP_ACCESS_TRAP_EL1 or CP_ACCESS_TRAP_UNCATEGORIZED, as appropriate. Remove the handling of bare CP_ACCESS_TRAP from the access_check_cp_reg() helper, so that it now asserts if an access function returns a value requesting a trap without a target EL. Rename CP_ACCESS_TRAP to CP_ACCESS_TRAP_BIT, to make it clearer that this is an internal-only definition, not something that it makes sense to return from an access function. This should help to avoid future bugs where we return the wrong syndrome value by mistake. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-13-peter.maydell@linaro.org --- target/arm/cpregs.h | 11 ++++++----- target/arm/tcg/op_helper.c | 13 ++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/target/arm/cpregs.h b/target/arm/cpregs.h index fbf5798069d3..fb3b84baa1e7 100644 --- a/target/arm/cpregs.h +++ b/target/arm/cpregs.h @@ -328,12 +328,13 @@ typedef enum CPAccessResult { * Access fails due to a configurable trap or enable which would * result in a categorized exception syndrome giving information about * the failing instruction (ie syndrome category 0x3, 0x4, 0x5, 0x6, - * 0xc or 0x18). + * 0xc or 0x18). These traps are always to a specified target EL, + * never to the usual target EL. */ - CP_ACCESS_TRAP = (1 << 2), - CP_ACCESS_TRAP_EL1 = CP_ACCESS_TRAP | 1, - CP_ACCESS_TRAP_EL2 = CP_ACCESS_TRAP | 2, - CP_ACCESS_TRAP_EL3 = CP_ACCESS_TRAP | 3, + CP_ACCESS_TRAP_BIT = (1 << 2), + CP_ACCESS_TRAP_EL1 = CP_ACCESS_TRAP_BIT | 1, + CP_ACCESS_TRAP_EL2 = CP_ACCESS_TRAP_BIT | 2, + CP_ACCESS_TRAP_EL3 = CP_ACCESS_TRAP_BIT | 3, /* * Access fails and results in an exception syndrome 0x0 ("uncategorized"). diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index c69d2ac643ff..fcee11e29adb 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -853,21 +853,24 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, fail: excp = EXCP_UDEF; - switch (res & ~CP_ACCESS_EL_MASK) { - case CP_ACCESS_TRAP: + switch (res) { + /* CP_ACCESS_TRAP* traps are always direct to a specified EL */ + case CP_ACCESS_TRAP_EL3: /* * If EL3 is AArch32 then there's no syndrome register; the cases * where we would raise a SystemAccessTrap to AArch64 EL3 all become * raising a Monitor trap exception. (Because there's no visible * syndrome it doesn't matter what we pass to raise_exception().) */ - if ((res & CP_ACCESS_EL_MASK) == 3 && !arm_el_is_aa64(env, 3)) { + if (!arm_el_is_aa64(env, 3)) { excp = EXCP_MON_TRAP; } break; + case CP_ACCESS_TRAP_EL2: + case CP_ACCESS_TRAP_EL1: + break; case CP_ACCESS_TRAP_UNCATEGORIZED: - /* Only CP_ACCESS_TRAP traps are direct to a specified EL */ - assert((res & CP_ACCESS_EL_MASK) == 0); + /* CP_ACCESS_TRAP_UNCATEGORIZED is never direct to a specified EL */ if (cpu_isar_feature(aa64_ids, cpu) && isread && arm_cpreg_in_idspace(ri)) { /* From 86d44c215e91da43555a2cfd58b7c6b725f036fb Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:08 +0000 Subject: [PATCH 0152/1179] target/arm: Rename CP_ACCESS_TRAP_UNCATEGORIZED to CP_ACCESS_UNDEFINED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CP_ACCESS_TRAP_UNCATEGORIZED is technically an accurate description of what this return value from a cpreg accessfn does, but it's liable to confusion because it doesn't match how the Arm ARM pseudocode indicates this case. What it does is an EXCP_UDEF with a zero ("uncategorized") syndrome value, which is what an UNDEFINED instruction does. The pseudocode uses "UNDEFINED" to show this; rename our constant to CP_ACCESS_UNDEFINED to make the parallel clearer. Commit created with sed -i -e 's/CP_ACCESS_TRAP_UNCATEGORIZED/CP_ACCESS_UNDEFINED/' $(git grep -l CP_ACCESS_TRAP_UNCATEGORIZED) plus manual editing of the comment. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-14-peter.maydell@linaro.org --- target/arm/cpregs.h | 5 +++-- target/arm/helper.c | 30 +++++++++++++++--------------- target/arm/tcg/op_helper.c | 6 +++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/target/arm/cpregs.h b/target/arm/cpregs.h index fb3b84baa1e7..52377c6eb50f 100644 --- a/target/arm/cpregs.h +++ b/target/arm/cpregs.h @@ -337,13 +337,14 @@ typedef enum CPAccessResult { CP_ACCESS_TRAP_EL3 = CP_ACCESS_TRAP_BIT | 3, /* - * Access fails and results in an exception syndrome 0x0 ("uncategorized"). + * Access fails with UNDEFINED, i.e. an exception syndrome 0x0 + * ("uncategorized"), which is what an undefined insn produces. * Note that this is not a catch-all case -- the set of cases which may * result in this failure is specifically defined by the architecture. * This trap is always to the usual target EL, never directly to a * specified target EL. */ - CP_ACCESS_TRAP_UNCATEGORIZED = (2 << 2), + CP_ACCESS_UNDEFINED = (2 << 2), } CPAccessResult; /* Indexes into fgt_read[] */ diff --git a/target/arm/helper.c b/target/arm/helper.c index aacb53d31a2a..71dead7241b4 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -285,7 +285,7 @@ static CPAccessResult access_el3_aa32ns(CPUARMState *env, { if (!is_a64(env) && arm_current_el(env) == 3 && arm_is_secure_below_el3(env)) { - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } return CP_ACCESS_OK; } @@ -310,7 +310,7 @@ static CPAccessResult access_trap_aa32s_el1(CPUARMState *env, return CP_ACCESS_TRAP_EL3; } /* This will be EL1 NS and EL2 NS, which just UNDEF */ - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } /* @@ -2246,7 +2246,7 @@ static CPAccessResult gt_cntfrq_access(CPUARMState *env, const ARMCPRegInfo *ri, if (!isread && ri->state == ARM_CP_STATE_AA32 && arm_is_secure_below_el3(env)) { /* Accesses from 32-bit Secure EL1 UNDEF (*not* trap to EL3!) */ - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } break; case 2: @@ -2255,7 +2255,7 @@ static CPAccessResult gt_cntfrq_access(CPUARMState *env, const ARMCPRegInfo *ri, } if (!isread && el < arm_highest_el(env)) { - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } return CP_ACCESS_OK; @@ -2385,7 +2385,7 @@ static CPAccessResult gt_stimer_access(CPUARMState *env, switch (arm_current_el(env)) { case 1: if (!arm_is_secure(env)) { - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } if (!(env->cp15.scr_el3 & SCR_ST)) { return CP_ACCESS_TRAP_EL3; @@ -2393,7 +2393,7 @@ static CPAccessResult gt_stimer_access(CPUARMState *env, return CP_ACCESS_OK; case 0: case 2: - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; case 3: return CP_ACCESS_OK; default: @@ -3304,7 +3304,7 @@ static CPAccessResult ats_access(CPUARMState *env, const ARMCPRegInfo *ri, } return CP_ACCESS_TRAP_EL3; } - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } } return CP_ACCESS_OK; @@ -3601,7 +3601,7 @@ static CPAccessResult at_e012_access(CPUARMState *env, const ARMCPRegInfo *ri, * scr_write() ensures that the NSE bit is not set otherwise. */ if ((env->cp15.scr_el3 & (SCR_NSE | SCR_NS)) == SCR_NSE) { - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } return CP_ACCESS_OK; } @@ -3611,7 +3611,7 @@ static CPAccessResult at_s1e2_access(CPUARMState *env, const ARMCPRegInfo *ri, { if (arm_current_el(env) == 3 && !(env->cp15.scr_el3 & (SCR_NS | SCR_EEL2))) { - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } return at_e012_access(env, ri, isread); } @@ -4684,7 +4684,7 @@ static CPAccessResult sp_el0_access(CPUARMState *env, const ARMCPRegInfo *ri, * Access to SP_EL0 is undefined if it's being used as * the stack pointer. */ - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } return CP_ACCESS_OK; } @@ -5674,7 +5674,7 @@ static CPAccessResult sel2_access(CPUARMState *env, const ARMCPRegInfo *ri, if (arm_current_el(env) == 3 || arm_is_secure_below_el3(env)) { return CP_ACCESS_OK; } - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } static const ARMCPRegInfo el2_sec_cp_reginfo[] = { @@ -5710,7 +5710,7 @@ static CPAccessResult nsacr_access(CPUARMState *env, const ARMCPRegInfo *ri, if (isread) { return CP_ACCESS_OK; } - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } static const ARMCPRegInfo el3_cp_reginfo[] = { @@ -5798,7 +5798,7 @@ static CPAccessResult e2h_access(CPUARMState *env, const ARMCPRegInfo *ri, return CP_ACCESS_OK; } if (!(arm_hcr_el2_eff(env) & HCR_E2H)) { - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } return CP_ACCESS_OK; } @@ -5896,7 +5896,7 @@ static CPAccessResult el2_e2h_e12_access(CPUARMState *env, } /* FOO_EL12 aliases only exist when E2H is 1; otherwise they UNDEF */ if (!(arm_hcr_el2_eff(env) & HCR_E2H)) { - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } if (ri->orig_accessfn) { return ri->orig_accessfn(env, ri->opaque, isread); @@ -6751,7 +6751,7 @@ static CPAccessResult access_lor_other(CPUARMState *env, { if (arm_is_secure_below_el3(env)) { /* UNDEF if SCR_EL3.NS == 0 */ - return CP_ACCESS_TRAP_UNCATEGORIZED; + return CP_ACCESS_UNDEFINED; } return access_lor_ns(env, ri, isread); } diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index fcee11e29adb..2230351a8f41 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -764,7 +764,7 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, if (arm_feature(env, ARM_FEATURE_XSCALE) && ri->cp < 14 && extract32(env->cp15.c15_cpar, ri->cp, 1) == 0) { - res = CP_ACCESS_TRAP_UNCATEGORIZED; + res = CP_ACCESS_UNDEFINED; goto fail; } @@ -869,8 +869,8 @@ const void *HELPER(access_check_cp_reg)(CPUARMState *env, uint32_t key, case CP_ACCESS_TRAP_EL2: case CP_ACCESS_TRAP_EL1: break; - case CP_ACCESS_TRAP_UNCATEGORIZED: - /* CP_ACCESS_TRAP_UNCATEGORIZED is never direct to a specified EL */ + case CP_ACCESS_UNDEFINED: + /* CP_ACCESS_UNDEFINED is never direct to a specified EL */ if (cpu_isar_feature(aa64_ids, cpu) && isread && arm_cpreg_in_idspace(ri)) { /* From 2b95a2d01b04afadf510a49ac14b38a59be8c5f5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 30 Jan 2025 18:23:09 +0000 Subject: [PATCH 0153/1179] target/arm: Correct errors in WFI/WFE trapping The code for WFI/WFE trapping has several errors: * it wasn't using arm_sctlr(), so it would look at SCTLR_EL1 even if the CPU was in the EL2&0 translation regime * it was raising UNDEF, not Monitor Trap, for traps to AArch32 EL3 because of SCR.{TWE,TWI} * it was not honouring SCR.{TWE,TWI} when running in AArch32 at EL3 not in Monitor mode * it checked SCR.{TWE,TWI} even on v7 CPUs which don't have those bits Fix these bugs. Cc: qemu-stable@nongnu.org Fixes: b1eced713d99 ("target-arm: Add WFx instruction trap support") Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250130182309.717346-15-peter.maydell@linaro.org --- target/arm/tcg/op_helper.c | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index 2230351a8f41..02c375d196da 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -313,15 +313,19 @@ void HELPER(check_bxj_trap)(CPUARMState *env, uint32_t rm) } #ifndef CONFIG_USER_ONLY -/* Function checks whether WFx (WFI/WFE) instructions are set up to be trapped. +/* + * Function checks whether WFx (WFI/WFE) instructions are set up to be trapped. * The function returns the target EL (1-3) if the instruction is to be trapped; * otherwise it returns 0 indicating it is not trapped. + * For a trap, *excp is updated with the EXCP_* trap type to use. */ -static inline int check_wfx_trap(CPUARMState *env, bool is_wfe) +static inline int check_wfx_trap(CPUARMState *env, bool is_wfe, uint32_t *excp) { int cur_el = arm_current_el(env); uint64_t mask; + *excp = EXCP_UDEF; + if (arm_feature(env, ARM_FEATURE_M)) { /* M profile cores can never trap WFI/WFE. */ return 0; @@ -331,18 +335,9 @@ static inline int check_wfx_trap(CPUARMState *env, bool is_wfe) * WFx instructions being trapped to EL1. These trap bits don't exist in v7. */ if (cur_el < 1 && arm_feature(env, ARM_FEATURE_V8)) { - int target_el; - mask = is_wfe ? SCTLR_nTWE : SCTLR_nTWI; - if (arm_is_secure_below_el3(env) && !arm_el_is_aa64(env, 3)) { - /* Secure EL0 and Secure PL1 is at EL3 */ - target_el = 3; - } else { - target_el = 1; - } - - if (!(env->cp15.sctlr_el[target_el] & mask)) { - return target_el; + if (!(arm_sctlr(env, cur_el) & mask)) { + return exception_target_el(env); } } @@ -358,9 +353,12 @@ static inline int check_wfx_trap(CPUARMState *env, bool is_wfe) } /* We are not trapping to EL1 or EL2; trap to EL3 if SCR_EL3 requires it */ - if (cur_el < 3) { + if (arm_feature(env, ARM_FEATURE_V8) && !arm_is_el3_or_mon(env)) { mask = (is_wfe) ? SCR_TWE : SCR_TWI; if (env->cp15.scr_el3 & mask) { + if (!arm_el_is_aa64(env, 3)) { + *excp = EXCP_MON_TRAP; + } return 3; } } @@ -383,7 +381,8 @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) return; #else CPUState *cs = env_cpu(env); - int target_el = check_wfx_trap(env, false); + uint32_t excp; + int target_el = check_wfx_trap(env, false, &excp); if (cpu_has_work(cs)) { /* Don't bother to go into our "low power state" if @@ -399,7 +398,7 @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) env->regs[15] -= insn_len; } - raise_exception(env, EXCP_UDEF, syn_wfx(1, 0xe, 0, insn_len == 2), + raise_exception(env, excp, syn_wfx(1, 0xe, 0, insn_len == 2), target_el); } @@ -424,7 +423,8 @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) #else ARMCPU *cpu = env_archcpu(env); CPUState *cs = env_cpu(env); - int target_el = check_wfx_trap(env, false); + uint32_t excp; + int target_el = check_wfx_trap(env, false, &excp); /* The WFIT should time out when CNTVCT_EL0 >= the specified value. */ uint64_t cntval = gt_get_countervalue(env); uint64_t offset = gt_virt_cnt_offset(env); @@ -441,8 +441,7 @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) if (target_el) { env->pc -= 4; - raise_exception(env, EXCP_UDEF, syn_wfx(1, 0xe, 0, false), - target_el); + raise_exception(env, excp, syn_wfx(1, 0xe, 0, false), target_el); } if (uadd64_overflow(timeout, offset, &nexttick)) { From 4ac4d6e77613ccbb9aa55675429dc0b9ae1f8ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:26 +0100 Subject: [PATCH 0154/1179] hw/arm/exynos4210: Replace magic 32 by proper 'GIC_INTERNAL' definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 32 IRQ lines skipped are the GIC internal ones. Use the GIC_INTERNAL definition for clarity. No logical change. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-2-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/arm/exynos4210.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c index dd0edc81d5c8..b6537a2d64a2 100644 --- a/hw/arm/exynos4210.c +++ b/hw/arm/exynos4210.c @@ -394,7 +394,8 @@ static void exynos4210_init_board_irqs(Exynos4210State *s) } if (irq_id) { qdev_connect_gpio_out(splitter, splitin, - qdev_get_gpio_in(extgicdev, irq_id - 32)); + qdev_get_gpio_in(extgicdev, + irq_id - GIC_INTERNAL)); } } for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) { @@ -421,7 +422,8 @@ static void exynos4210_init_board_irqs(Exynos4210State *s) s->irq_table[n] = qdev_get_gpio_in(splitter, 0); qdev_connect_gpio_out(splitter, 0, qdev_get_gpio_in(intcdev, n)); qdev_connect_gpio_out(splitter, 1, - qdev_get_gpio_in(extgicdev, irq_id - 32)); + qdev_get_gpio_in(extgicdev, + irq_id - GIC_INTERNAL)); } else { s->irq_table[n] = qdev_get_gpio_in(intcdev, n); } From 284e354566c31687cce260401549a616cf513c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:27 +0100 Subject: [PATCH 0155/1179] hw/arm/exynos4210: Specify explicitly the GIC has 64 external IRQs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When not specified, Cortex-A9MP configures its GIC with 64 external IRQs (see commit a32134aad89 "arm:make the number of GIC interrupts configurable"). Add the GIC_EXT_IRQS definition (with a comment) to make that explicit. Except explicitly setting a property value to its same implicit value, there is no logical change intended. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-3-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/arm/exynos4210.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c index b6537a2d64a2..b452470598b6 100644 --- a/hw/arm/exynos4210.c +++ b/hw/arm/exynos4210.c @@ -103,6 +103,8 @@ #define EXYNOS4210_PL330_BASE1_ADDR 0x12690000 #define EXYNOS4210_PL330_BASE2_ADDR 0x12850000 +#define GIC_EXT_IRQS 64 /* FIXME: verify for this SoC */ + enum ExtGicId { EXT_GIC_ID_MDMA_LCD0 = 66, EXT_GIC_ID_PDMA0, @@ -588,6 +590,8 @@ static void exynos4210_realize(DeviceState *socdev, Error **errp) /* Private memory region and Internal GIC */ qdev_prop_set_uint32(DEVICE(&s->a9mpcore), "num-cpu", EXYNOS4210_NCPUS); + qdev_prop_set_uint32(DEVICE(&s->a9mpcore), "num-irq", + GIC_EXT_IRQS + GIC_INTERNAL); busdev = SYS_BUS_DEVICE(&s->a9mpcore); sysbus_realize(busdev, &error_fatal); sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR); From 6621cf52509b796f3c32a18b74248cf404dbe56f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:28 +0100 Subject: [PATCH 0156/1179] hw/arm/realview: Specify explicitly the GIC has 64 external IRQs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When not specified, Cortex-A9MP configures its GIC with 64 external IRQs (see commit a32134aad89 "arm:make the number of GIC interrupts configurable"). Add the GIC_EXT_IRQS definition (with a comment) to make that explicit. Except explicitly setting a property value to its same implicit value, there is no logical change intended. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-4-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/arm/realview.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/hw/arm/realview.c b/hw/arm/realview.c index 436eef816ed2..008eeaf049a7 100644 --- a/hw/arm/realview.c +++ b/hw/arm/realview.c @@ -35,6 +35,8 @@ #define SMP_BOOT_ADDR 0xe0000000 #define SMP_BOOTREG_ADDR 0x10000030 +#define GIC_EXT_IRQS 64 /* Realview PBX-A9 development board */ + /* Board init. */ static struct arm_boot_info realview_binfo = { @@ -185,7 +187,12 @@ static void realview_init(MachineState *machine, sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, 0x10000000); if (is_mpcore) { - dev = qdev_new(is_pb ? TYPE_A9MPCORE_PRIV : "realview_mpcore"); + if (is_pb) { + dev = qdev_new(TYPE_A9MPCORE_PRIV); + qdev_prop_set_uint32(dev, "num-irq", GIC_EXT_IRQS + GIC_INTERNAL); + } else { + dev = qdev_new("realview_mpcore"); + } qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); busdev = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(busdev, &error_fatal); @@ -201,7 +208,7 @@ static void realview_init(MachineState *machine, /* For now just create the nIRQ GIC, and ignore the others. */ dev = sysbus_create_simple(TYPE_REALVIEW_GIC, gic_addr, cpu_irq[0]); } - for (n = 0; n < 64; n++) { + for (n = 0; n < GIC_EXT_IRQS; n++) { pic[n] = qdev_get_gpio_in(dev, n); } From 2d269a8bbb7fc7148f429b13720c7b5d07bba3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:29 +0100 Subject: [PATCH 0157/1179] hw/arm/xilinx_zynq: Replace IRQ_OFFSET -> GIC_INTERNAL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We already have a definition to distinct GIC internal IRQs versus external ones, use it. No logical changes. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-5-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/arm/xilinx_zynq.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index 3c6a4604cc96..22c0bb172620 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -54,8 +54,6 @@ OBJECT_DECLARE_SIMPLE_TYPE(ZynqMachineState, ZYNQ_MACHINE) #define FLASH_SIZE (64 * 1024 * 1024) #define FLASH_SECTOR_SIZE (128 * 1024) -#define IRQ_OFFSET 32 /* pic interrupts start from index 32 */ - #define MPCORE_PERIPHBASE 0xF8F00000 #define ZYNQ_BOARD_MIDR 0x413FC090 @@ -281,12 +279,12 @@ static void zynq_init(MachineState *machine) pic[n] = qdev_get_gpio_in(dev, n); } - n = zynq_init_spi_flashes(0xE0006000, pic[58 - IRQ_OFFSET], false, 0); - n = zynq_init_spi_flashes(0xE0007000, pic[81 - IRQ_OFFSET], false, n); - n = zynq_init_spi_flashes(0xE000D000, pic[51 - IRQ_OFFSET], true, n); + n = zynq_init_spi_flashes(0xE0006000, pic[58 - GIC_INTERNAL], false, 0); + n = zynq_init_spi_flashes(0xE0007000, pic[81 - GIC_INTERNAL], false, n); + n = zynq_init_spi_flashes(0xE000D000, pic[51 - GIC_INTERNAL], true, n); - sysbus_create_simple(TYPE_CHIPIDEA, 0xE0002000, pic[53 - IRQ_OFFSET]); - sysbus_create_simple(TYPE_CHIPIDEA, 0xE0003000, pic[76 - IRQ_OFFSET]); + sysbus_create_simple(TYPE_CHIPIDEA, 0xE0002000, pic[53 - GIC_INTERNAL]); + sysbus_create_simple(TYPE_CHIPIDEA, 0xE0003000, pic[76 - GIC_INTERNAL]); dev = qdev_new(TYPE_CADENCE_UART); busdev = SYS_BUS_DEVICE(dev); @@ -295,7 +293,7 @@ static void zynq_init(MachineState *machine) qdev_get_clock_out(slcr, "uart0_ref_clk")); sysbus_realize_and_unref(busdev, &error_fatal); sysbus_mmio_map(busdev, 0, 0xE0000000); - sysbus_connect_irq(busdev, 0, pic[59 - IRQ_OFFSET]); + sysbus_connect_irq(busdev, 0, pic[59 - GIC_INTERNAL]); dev = qdev_new(TYPE_CADENCE_UART); busdev = SYS_BUS_DEVICE(dev); qdev_prop_set_chr(dev, "chardev", serial_hd(1)); @@ -303,15 +301,15 @@ static void zynq_init(MachineState *machine) qdev_get_clock_out(slcr, "uart1_ref_clk")); sysbus_realize_and_unref(busdev, &error_fatal); sysbus_mmio_map(busdev, 0, 0xE0001000); - sysbus_connect_irq(busdev, 0, pic[82 - IRQ_OFFSET]); + sysbus_connect_irq(busdev, 0, pic[82 - GIC_INTERNAL]); sysbus_create_varargs("cadence_ttc", 0xF8001000, - pic[42-IRQ_OFFSET], pic[43-IRQ_OFFSET], pic[44-IRQ_OFFSET], NULL); + pic[42-GIC_INTERNAL], pic[43-GIC_INTERNAL], pic[44-GIC_INTERNAL], NULL); sysbus_create_varargs("cadence_ttc", 0xF8002000, - pic[69-IRQ_OFFSET], pic[70-IRQ_OFFSET], pic[71-IRQ_OFFSET], NULL); + pic[69-GIC_INTERNAL], pic[70-GIC_INTERNAL], pic[71-GIC_INTERNAL], NULL); - gem_init(0xE000B000, pic[54 - IRQ_OFFSET]); - gem_init(0xE000C000, pic[77 - IRQ_OFFSET]); + gem_init(0xE000B000, pic[54 - GIC_INTERNAL]); + gem_init(0xE000C000, pic[77 - GIC_INTERNAL]); for (n = 0; n < 2; n++) { int hci_irq = n ? 79 : 56; @@ -330,7 +328,7 @@ static void zynq_init(MachineState *machine) qdev_prop_set_uint64(dev, "capareg", ZYNQ_SDHCI_CAPABILITIES); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, hci_addr); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[hci_irq - IRQ_OFFSET]); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[hci_irq - GIC_INTERNAL]); di = drive_get(IF_SD, 0, n); blk = di ? blk_by_legacy_dinfo(di) : NULL; @@ -343,7 +341,7 @@ static void zynq_init(MachineState *machine) dev = qdev_new(TYPE_ZYNQ_XADC); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xF8007100); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[39-IRQ_OFFSET]); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[39-GIC_INTERNAL]); dev = qdev_new("pl330"); object_property_set_link(OBJECT(dev), "memory", @@ -363,15 +361,15 @@ static void zynq_init(MachineState *machine) busdev = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(busdev, &error_fatal); sysbus_mmio_map(busdev, 0, 0xF8003000); - sysbus_connect_irq(busdev, 0, pic[45-IRQ_OFFSET]); /* abort irq line */ + sysbus_connect_irq(busdev, 0, pic[45-GIC_INTERNAL]); /* abort irq line */ for (n = 0; n < ARRAY_SIZE(dma_irqs); ++n) { /* event irqs */ - sysbus_connect_irq(busdev, n + 1, pic[dma_irqs[n] - IRQ_OFFSET]); + sysbus_connect_irq(busdev, n + 1, pic[dma_irqs[n] - GIC_INTERNAL]); } dev = qdev_new("xlnx.ps7-dev-cfg"); busdev = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(busdev, &error_fatal); - sysbus_connect_irq(busdev, 0, pic[40 - IRQ_OFFSET]); + sysbus_connect_irq(busdev, 0, pic[40 - GIC_INTERNAL]); sysbus_mmio_map(busdev, 0, 0xF8007000); /* From 92fea7f2e7818d3019b5c29eb8379049a3b1f0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:30 +0100 Subject: [PATCH 0158/1179] hw/arm/xilinx_zynq: Specify explicitly the GIC has 64 external IRQs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looking at the Zynq 7000 SoC Technical Reference Manual (UG585 v1.14) on Appendix A: Register Details, the mpcore Interrupt Controller Type Register (ICDICTR) has the IT_Lines_Number field read-only with value 0x2, described as: IT_Lines_Number b00010 = the distributor provides 96 interrupts, 64 external interrupt lines. Add a GIC_EXT_IRQS definition (with a comment) to make the number of GIC external IRQs explicit. Except explicitly setting a property value to its same implicit value, there is no logical change intended. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-6-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/arm/xilinx_zynq.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index 22c0bb172620..b8916665ed6b 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -57,6 +57,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(ZynqMachineState, ZYNQ_MACHINE) #define MPCORE_PERIPHBASE 0xF8F00000 #define ZYNQ_BOARD_MIDR 0x413FC090 +#define GIC_EXT_IRQS 64 /* Zynq 7000 SoC */ + static const int dma_irqs[8] = { 46, 47, 48, 49, 72, 73, 74, 75 }; @@ -205,7 +207,7 @@ static void zynq_init(MachineState *machine) MemoryRegion *ocm_ram = g_new(MemoryRegion, 1); DeviceState *dev, *slcr; SysBusDevice *busdev; - qemu_irq pic[64]; + qemu_irq pic[GIC_EXT_IRQS]; int n; unsigned int smp_cpus = machine->smp.cpus; @@ -261,6 +263,7 @@ static void zynq_init(MachineState *machine) dev = qdev_new(TYPE_A9MPCORE_PRIV); qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_prop_set_uint32(dev, "num-irq", GIC_EXT_IRQS + GIC_INTERNAL); busdev = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(busdev, &error_fatal); sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE); @@ -275,7 +278,7 @@ static void zynq_init(MachineState *machine) qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); } - for (n = 0; n < 64; n++) { + for (n = 0; n < GIC_EXT_IRQS; n++) { pic[n] = qdev_get_gpio_in(dev, n); } @@ -458,7 +461,7 @@ static void zynq_machine_class_init(ObjectClass *oc, void *data) }; MachineClass *mc = MACHINE_CLASS(oc); ObjectProperty *prop; - mc->desc = "Xilinx Zynq Platform Baseboard for Cortex-A9"; + mc->desc = "Xilinx Zynq 7000 Platform Baseboard for Cortex-A9"; mc->init = zynq_init; mc->max_cpus = ZYNQ_MAX_CPUS; mc->ignore_memory_transaction_failures = true; From e2e5266c4555ed24a4727c9ce6e34eb5213a9ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:31 +0100 Subject: [PATCH 0159/1179] hw/arm/vexpress: Specify explicitly the GIC has 64 external IRQs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When not specified, Cortex-A9MP configures its GIC with 64 external IRQs, (see commit a32134aad89 "arm:make the number of GIC interrupts configurable"), and Cortex-15MP to 128 (see commit 528622421eb "hw/cpu/a15mpcore: Correct default value for num-irq"). The Versatile Express board however expects a fixed set of 64 interrupts (see the fixed IRQ length when this board was added in commit 2055283bcc8 ("hw/vexpress: Add model of ARM Versatile Express board"). Add the GIC_EXT_IRQS definition (with a comment) to make that explicit. Except explicitly setting a property value to its same implicit value, there is no logical change intended. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-7-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/arm/vexpress.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index 48e18a49d542..76c6107766cf 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -51,6 +51,8 @@ #define VEXPRESS_FLASH_SIZE (64 * 1024 * 1024) #define VEXPRESS_FLASH_SECT_SIZE (256 * 1024) +#define GIC_EXT_IRQS 64 /* Versatile Express A9 development board */ + /* Number of virtio transports to create (0..8; limited by * number of available IRQ lines). */ @@ -241,6 +243,7 @@ static void init_cpus(MachineState *ms, const char *cpu_type, */ dev = qdev_new(privdev); qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_prop_set_uint32(dev, "num-irq", GIC_EXT_IRQS + GIC_INTERNAL); busdev = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(busdev, &error_fatal); sysbus_mmio_map(busdev, 0, periphbase); @@ -251,7 +254,7 @@ static void init_cpus(MachineState *ms, const char *cpu_type, * external interrupts starting from 32 (because there * are internal interrupts 0..31). */ - for (n = 0; n < 64; n++) { + for (n = 0; n < GIC_EXT_IRQS; n++) { pic[n] = qdev_get_gpio_in(dev, n); } @@ -543,7 +546,7 @@ static void vexpress_common_init(MachineState *machine) VexpressMachineClass *vmc = VEXPRESS_MACHINE_GET_CLASS(machine); VEDBoardInfo *daughterboard = vmc->daughterboard; DeviceState *dev, *sysctl, *pl041; - qemu_irq pic[64]; + qemu_irq pic[GIC_EXT_IRQS]; uint32_t sys_id; DriveInfo *dinfo; PFlashCFI01 *pflash0; From 2bf8bdcbb4510f206b3d0203c9bb8fb433387a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:32 +0100 Subject: [PATCH 0160/1179] hw/arm/highbank: Specify explicitly the GIC has 128 external IRQs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When not specified, Cortex-A9MP configures its GIC with 64 external IRQs, (see commit a32134aad89 "arm:make the number of GIC interrupts configurable"), and Cortex-15MP to 128 (see commit 528622421eb "hw/cpu/a15mpcore: Correct default value for num-irq"). The Caldexa Highbank board however expects a fixed set of 128 interrupts (see the fixed IRQ length when this board was added in commit 2488514cef2 ("arm: SoC model for Calxeda Highbank"). Add the GIC_EXT_IRQS definition (with a comment) to make that explicit. Except explicitly setting a property value to its same implicit value, there is no logical change intended. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-8-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/arm/highbank.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c index 495704d97265..0f3c207d5489 100644 --- a/hw/arm/highbank.c +++ b/hw/arm/highbank.c @@ -45,7 +45,7 @@ #define MVBAR_ADDR 0x200 #define BOARD_SETUP_ADDR (MVBAR_ADDR + 8 * sizeof(uint32_t)) -#define NIRQ_GIC 160 +#define GIC_EXT_IRQS 128 /* EnergyCore ECX-1000 & ECX-2000 */ /* Board init. */ @@ -180,7 +180,7 @@ static void calxeda_init(MachineState *machine, enum cxmachines machine_id) { DeviceState *dev = NULL; SysBusDevice *busdev; - qemu_irq pic[128]; + qemu_irq pic[GIC_EXT_IRQS]; int n; unsigned int smp_cpus = machine->smp.cpus; qemu_irq cpu_irq[4]; @@ -260,7 +260,7 @@ static void calxeda_init(MachineState *machine, enum cxmachines machine_id) break; } qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); - qdev_prop_set_uint32(dev, "num-irq", NIRQ_GIC); + qdev_prop_set_uint32(dev, "num-irq", GIC_EXT_IRQS + GIC_INTERNAL); busdev = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(busdev, &error_fatal); sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE); @@ -271,7 +271,7 @@ static void calxeda_init(MachineState *machine, enum cxmachines machine_id) sysbus_connect_irq(busdev, n + 3 * smp_cpus, cpu_vfiq[n]); } - for (n = 0; n < 128; n++) { + for (n = 0; n < GIC_EXT_IRQS; n++) { pic[n] = qdev_get_gpio_in(dev, n); } From 262f4ab3d5e3fcb6e85e40d9e29d9914c17972ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 16:43:33 +0100 Subject: [PATCH 0161/1179] hw/cpu/arm_mpcore: Remove default values for GIC external IRQs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implicit default values are often hard to figure out, better be explicit. Now that all boards explicitly set the number of GIC external IRQs, remove the default values (displaying an error message if it is out of range). Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250212154333.28644-9-philmd@linaro.org Signed-off-by: Peter Maydell --- hw/cpu/a15mpcore.c | 18 ++++++++++++------ hw/cpu/a9mpcore.c | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/hw/cpu/a15mpcore.c b/hw/cpu/a15mpcore.c index d24ab0a6ab25..676f65a0af4f 100644 --- a/hw/cpu/a15mpcore.c +++ b/hw/cpu/a15mpcore.c @@ -58,6 +58,11 @@ static void a15mp_priv_realize(DeviceState *dev, Error **errp) bool has_el2 = false; Object *cpuobj; + if (s->num_irq < 32 || s->num_irq > 256) { + error_setg(errp, "Property 'num-irq' must be between 32 and 256"); + return; + } + gicdev = DEVICE(&s->gic); qdev_prop_set_uint32(gicdev, "num-cpu", s->num_cpu); qdev_prop_set_uint32(gicdev, "num-irq", s->num_irq); @@ -146,13 +151,14 @@ static void a15mp_priv_realize(DeviceState *dev, Error **errp) static const Property a15mp_priv_properties[] = { DEFINE_PROP_UINT32("num-cpu", A15MPPrivState, num_cpu, 1), - /* The Cortex-A15MP may have anything from 0 to 224 external interrupt - * IRQ lines (with another 32 internal). We default to 128+32, which - * is the number provided by the Cortex-A15MP test chip in the - * Versatile Express A15 development board. - * Other boards may differ and should set this property appropriately. + /* + * The Cortex-A15MP may have anything from 0 to 224 external interrupt + * lines, plus always 32 internal IRQs. This property sets the total + * of internal + external, so the valid range is from 32 to 256. + * The board model must set this to whatever the configuration + * used for the CPU on that board or SoC is. */ - DEFINE_PROP_UINT32("num-irq", A15MPPrivState, num_irq, 160), + DEFINE_PROP_UINT32("num-irq", A15MPPrivState, num_irq, 0), }; static void a15mp_priv_class_init(ObjectClass *klass, void *data) diff --git a/hw/cpu/a9mpcore.c b/hw/cpu/a9mpcore.c index 25416c5032b4..1b9f2bef93c3 100644 --- a/hw/cpu/a9mpcore.c +++ b/hw/cpu/a9mpcore.c @@ -56,6 +56,11 @@ static void a9mp_priv_realize(DeviceState *dev, Error **errp) CPUState *cpu0; Object *cpuobj; + if (s->num_irq < 32 || s->num_irq > 256) { + error_setg(errp, "Property 'num-irq' must be between 32 and 256"); + return; + } + cpu0 = qemu_get_cpu(0); cpuobj = OBJECT(cpu0); if (strcmp(object_get_typename(cpuobj), ARM_CPU_TYPE_NAME("cortex-a9"))) { @@ -160,13 +165,14 @@ static void a9mp_priv_realize(DeviceState *dev, Error **errp) static const Property a9mp_priv_properties[] = { DEFINE_PROP_UINT32("num-cpu", A9MPPrivState, num_cpu, 1), - /* The Cortex-A9MP may have anything from 0 to 224 external interrupt - * IRQ lines (with another 32 internal). We default to 64+32, which - * is the number provided by the Cortex-A9MP test chip in the - * Realview PBX-A9 and Versatile Express A9 development boards. - * Other boards may differ and should set this property appropriately. + /* + * The Cortex-A9MP may have anything from 0 to 224 external interrupt + * lines, plus always 32 internal IRQs. This property sets the total + * of internal + external, so the valid range is from 32 to 256. + * The board model must set this to whatever the configuration + * used for the CPU on that board or SoC is. */ - DEFINE_PROP_UINT32("num-irq", A9MPPrivState, num_irq, 96), + DEFINE_PROP_UINT32("num-irq", A9MPPrivState, num_irq, 0), }; static void a9mp_priv_class_init(ObjectClass *klass, void *data) From 464ce71a963b3dfc290cd59c3d1bfedf11c004df Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 9 Feb 2025 11:36:04 +0100 Subject: [PATCH 0162/1179] Kconfig: Extract CONFIG_USB_CHIPIDEA from CONFIG_IMX TYPE_CHIPIDEA models an IP block which is also used in TYPE_ZYNQ_MACHINE which itself is not an IMX device. CONFIG_ZYNQ selects CONFIG_USB_EHCI_SYSBUS while TYPE_CHIPIDEA is a separate compilation unit, so only works by accident if CONFIG_IMX is given. Fix that by extracting CONFIG_USB_CHIPIDEA from CONFIG_IMX. cc: qemu-stable@nongnu.org Fixes: 616ec12d0fcc "hw/arm/xilinx_zynq: Fix USB port instantiation" Signed-off-by: Bernhard Beschow Message-id: 20250209103604.29545-1-shentey@gmail.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/arm/Kconfig | 6 +++++- hw/usb/Kconfig | 4 ++++ hw/usb/meson.build | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 256013ca8081..7eab3914d4be 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -303,7 +303,7 @@ config ZYNQ select PL330 select SDHCI select SSI_M25P80 - select USB_EHCI_SYSBUS + select USB_CHIPIDEA select XILINX # UART select XILINX_AXI select XILINX_SPI @@ -489,6 +489,7 @@ config FSL_IMX25 select IMX select IMX_FEC select IMX_I2C + select USB_CHIPIDEA select WDT_IMX2 select SDHCI @@ -516,6 +517,7 @@ config FSL_IMX6 select PL310 # cache controller select PCI_EXPRESS_DESIGNWARE select SDHCI + select USB_CHIPIDEA select OR_IRQ config ASPEED_SOC @@ -576,6 +578,7 @@ config FSL_IMX7 select SDHCI select OR_IRQ select UNIMP + select USB_CHIPIDEA config ARM_SMMUV3 bool @@ -591,6 +594,7 @@ config FSL_IMX6UL select IMX_I2C select WDT_IMX2 select SDHCI + select USB_CHIPIDEA select UNIMP config MICROBIT diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index 5fbecd2f43bf..69c663be52fb 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -143,3 +143,7 @@ config USB_DWC3 config XLNX_USB_SUBSYS bool select USB_DWC3 + +config USB_CHIPIDEA + bool + select USB_EHCI_SYSBUS diff --git a/hw/usb/meson.build b/hw/usb/meson.build index 1b4d1507e412..17360a5b5a49 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -25,8 +25,8 @@ system_ss.add(when: 'CONFIG_USB_XHCI_SYSBUS', if_true: files('hcd-xhci-sysbus.c' system_ss.add(when: 'CONFIG_USB_XHCI_NEC', if_true: files('hcd-xhci-nec.c')) system_ss.add(when: 'CONFIG_USB_DWC2', if_true: files('hcd-dwc2.c')) system_ss.add(when: 'CONFIG_USB_DWC3', if_true: files('hcd-dwc3.c')) +system_ss.add(when: 'CONFIG_USB_CHIPIDEA', if_true: files('chipidea.c')) -system_ss.add(when: 'CONFIG_IMX', if_true: files('chipidea.c')) system_ss.add(when: 'CONFIG_IMX_USBPHY', if_true: files('imx-usb-phy.c')) system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c')) system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-usb2-ctrl-regs.c')) From b2ba5ff272e0738c6b82197fe61f73344e5edcfb Mon Sep 17 00:00:00 2001 From: Stephen Longfield Date: Wed, 19 Feb 2025 16:55:34 +0000 Subject: [PATCH 0163/1179] target/arm: Use uint32_t in t32_expandimm_imm() In t32_expandimm_imm(), we take an 8 bit value XY and construct a 32-bit value which might be of the form XY, 00XY00XY, XY00XY00, or XYXYXYXY. We do this with multiplications, and we use an 'int' type. For the cases where we're setting the high byte of the 32-bit value to XY, this means that we do an integer multiplication that might overflow, and rely on the -fwrapv semantics to keep this from being undefined behaviour. It's clearer to use an unsigned type here, because we're really doing operations on the value considered as a set of bits. The result is the same. The return value from the function remains 'int', because this is a decodetree !function function, and follows the API for those functions. Signed-off-by: Stephen Longfield Signed-off-by: Roque Arcudia Hernandez Message-id: 20250219165534.3387376-1-slongfield@google.com [PMM: Rewrote the commit message] Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- target/arm/tcg/translate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c index 68ac39341530..d8225b77c8cc 100644 --- a/target/arm/tcg/translate.c +++ b/target/arm/tcg/translate.c @@ -3510,7 +3510,7 @@ static int t32_expandimm_rot(DisasContext *s, int x) /* Return the unrotated immediate from T32ExpandImm. */ static int t32_expandimm_imm(DisasContext *s, int x) { - int imm = extract32(x, 0, 8); + uint32_t imm = extract32(x, 0, 8); switch (extract32(x, 8, 4)) { case 0: /* XY */ From 70ce076fa6dff60585c229a4b641b13e64bf03cf Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:52 -0800 Subject: [PATCH 0164/1179] roms: Update vbootrom to 1287b6e This newer vbootrom supports NPCM8xx. Similar to the NPCM7XX one it supports loading the UBoot from the SPI device and not more. We updated the npcm7xx bootrom to be compiled from this version. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-2-wuhaotsh@google.com Signed-off-by: Peter Maydell --- pc-bios/npcm7xx_bootrom.bin | Bin 768 -> 768 bytes roms/vbootrom | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pc-bios/npcm7xx_bootrom.bin b/pc-bios/npcm7xx_bootrom.bin index 38f89d1b97b0c2e133af2a9fbed0521be132065b..903f126636f9ef5d1100c056656ccfb2b32e5e10 100644 GIT binary patch delta 90 zcmZo*Yhc^(l+nU*!D9x6DNkDr=09a-2ztoGz`#|*F#jn7L;r()|Np;c0m>C1$z?$0 Ywog`Ma%Vh0Ig_b-VgU<}A_D>d06Rh+WdHyG delta 69 zcmZo*Yhc^(lu^NO!D9x2$xoRb7CdZGnE#ZCA@Cs+0|QqL!~CZV4E+!GPG)41X52Pe SmdTy*+~icIZXQJj1ONb5*AzJb diff --git a/roms/vbootrom b/roms/vbootrom index 0c37a43527f0..1287b6e42e83 160000 --- a/roms/vbootrom +++ b/roms/vbootrom @@ -1 +1 @@ -Subproject commit 0c37a43527f0ee2b9584e7fb2fdc805e902635ac +Subproject commit 1287b6e42e839ba2ab0f06268c5b53ae60df3537 From 269b7effd906d6b22071971b7f5b4cb344403b86 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:53 -0800 Subject: [PATCH 0165/1179] pc-bios: Add NPCM8XX vBootrom The bootrom is a minimal bootrom used to load an NPCM8XX image. The source code is located in the same repo as the NPCM7XX one: github.com/google/vbootrom/tree/master/npcm8xx. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-3-wuhaotsh@google.com Signed-off-by: Peter Maydell --- MAINTAINERS | 1 + pc-bios/README | 8 ++++---- pc-bios/meson.build | 1 + pc-bios/npcm8xx_bootrom.bin | Bin 0 -> 608 bytes roms/Makefile | 6 ++++++ 5 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 pc-bios/npcm8xx_bootrom.bin diff --git a/MAINTAINERS b/MAINTAINERS index 3848d37a38d2..e145017d5331 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -878,6 +878,7 @@ F: include/hw/*/npcm* F: tests/qtest/npcm* F: tests/qtest/adm1266-test.c F: pc-bios/npcm7xx_bootrom.bin +F: pc-bios/npcm8xx_bootrom.bin F: roms/vbootrom F: docs/system/arm/nuvoton.rst F: tests/functional/test_arm_quanta_gsj.py diff --git a/pc-bios/README b/pc-bios/README index 7ffb2f43a460..700dcaab523f 100644 --- a/pc-bios/README +++ b/pc-bios/README @@ -70,10 +70,10 @@ source code also contains code reused from other projects described here: https://github.com/riscv/opensbi/blob/master/ThirdPartyNotices.md. -- npcm7xx_bootrom.bin is a simplified, free (Apache 2.0) boot ROM for Nuvoton - NPCM7xx BMC devices. It currently implements the bare minimum to load, parse, - initialize and run boot images stored in SPI flash, but may grow more - features over time as needed. The source code is available at: +- npcm{7xx,8xx}_bootrom.bin is a simplified, free (Apache 2.0) boot ROM for + Nuvoton NPCM7xx/8xx BMC devices. It currently implements the bare minimum to + load, parse, initialize and run boot images stored in SPI flash, but may grow + more features over time as needed. The source code is available at: https://github.com/google/vbootrom - hppa-firmware.img (32-bit) and hppa-firmware64.img (64-bit) are firmware diff --git a/pc-bios/meson.build b/pc-bios/meson.build index b68b29cc7d1f..51e95cc90316 100644 --- a/pc-bios/meson.build +++ b/pc-bios/meson.build @@ -80,6 +80,7 @@ blobs = [ 'opensbi-riscv32-generic-fw_dynamic.bin', 'opensbi-riscv64-generic-fw_dynamic.bin', 'npcm7xx_bootrom.bin', + 'npcm8xx_bootrom.bin', 'vof.bin', 'vof-nvram.bin', ] diff --git a/pc-bios/npcm8xx_bootrom.bin b/pc-bios/npcm8xx_bootrom.bin new file mode 100644 index 0000000000000000000000000000000000000000..6370d6475635c4d445d2b927311edcd591949c82 GIT binary patch literal 608 zcmdUrKTE?<6vfX=0{*3B5ET?nwWA^;qEk()n=Xb9-4dxoSBrz#p|QJQL~zokn{Eyc z?PBXUkU+aB?k?IbNQftG5ej|*FC2c{bKkr7zLy3jhNxj`gc_y5h&V=Ru)PgZC)Y`f zTqA9Am28qLHlr*^&hT#;re-)dpxT0U42|O+cWOcx=B;{6xXH04vx?cjm z+%U{oFx!aPpV3>ZKz0i$XA-yq{f}x4;|pbw;l#@9zGd|z-rs*H@V-o%PEV)D-)8n2%DyH5@w_^Y8 LH5R3RMV#gjxYTW} literal 0 HcmV?d00001 diff --git a/roms/Makefile b/roms/Makefile index 31e4b97c983b..beff58d9d50c 100644 --- a/roms/Makefile +++ b/roms/Makefile @@ -34,6 +34,7 @@ find-cross-gcc = $(firstword $(wildcard $(patsubst %ld,%gcc,$(call find-cross-ld # finally strip off path + toolname so we get the prefix find-cross-prefix = $(subst gcc,,$(notdir $(call find-cross-gcc,$(1)))) +aarch64_cross_prefix := $(call find-cross-prefix,aarch64) arm_cross_prefix := $(call find-cross-prefix,arm) powerpc64_cross_prefix := $(call find-cross-prefix,powerpc64) powerpc_cross_prefix := $(call find-cross-prefix,powerpc) @@ -66,6 +67,7 @@ default help: @echo " u-boot.e500 -- update u-boot.e500" @echo " u-boot.sam460 -- update u-boot.sam460" @echo " npcm7xx_bootrom -- update vbootrom for npcm7xx" + @echo " npcm8xx_bootrom -- update vbootrom for npcm8xx" @echo " efi -- update UEFI (edk2) platform firmware" @echo " opensbi32-generic -- update OpenSBI for 32-bit generic machine" @echo " opensbi64-generic -- update OpenSBI for 64-bit generic machine" @@ -194,6 +196,10 @@ npcm7xx_bootrom: $(MAKE) -C vbootrom CROSS_COMPILE=$(arm_cross_prefix) cp vbootrom/npcm7xx_bootrom.bin ../pc-bios/npcm7xx_bootrom.bin +npcm8xx_bootrom: + $(MAKE) -C vbootrom CROSS_COMPILE=$(aarch64_cross_prefix) + cp vbootrom/npcm8xx_bootrom.bin ../pc-bios/npcm8xx_bootrom.bin + hppa-firmware: $(MAKE) -C seabios-hppa parisc cp seabios-hppa/out/hppa-firmware.img ../pc-bios/ From e9be8467b42f7e2af70694422f4b4d8afe82bf4e Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:54 -0800 Subject: [PATCH 0166/1179] hw/ssi: Make flash size a property in NPCM7XX FIU This allows different FIUs to have different flash sizes, useful in NPCM8XX which has multiple different sized FIU modules. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Reviewed-by: Philippe Mathieu-Daude Message-id: 20250219184609.1839281-4-wuhaotsh@google.com [PMM: flash_size must be a uint64_t to build on 32-bit hosts] Signed-off-by: Peter Maydell --- hw/arm/npcm7xx.c | 6 ++++++ hw/ssi/npcm7xx_fiu.c | 16 ++++++++++++++-- include/hw/ssi/npcm7xx_fiu.h | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index 386b2c35e9b8..2d6e08b72ba0 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -292,17 +292,21 @@ static const struct { hwaddr regs_addr; int cs_count; const hwaddr *flash_addr; + size_t flash_size; } npcm7xx_fiu[] = { { .name = "fiu0", .regs_addr = 0xfb000000, .cs_count = ARRAY_SIZE(npcm7xx_fiu0_flash_addr), .flash_addr = npcm7xx_fiu0_flash_addr, + .flash_size = 128 * MiB, + }, { .name = "fiu3", .regs_addr = 0xc0000000, .cs_count = ARRAY_SIZE(npcm7xx_fiu3_flash_addr), .flash_addr = npcm7xx_fiu3_flash_addr, + .flash_size = 128 * MiB, }, }; @@ -735,6 +739,8 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) object_property_set_int(OBJECT(sbd), "cs-count", npcm7xx_fiu[i].cs_count, &error_abort); + object_property_set_int(OBJECT(sbd), "flash-size", + npcm7xx_fiu[i].flash_size, &error_abort); sysbus_realize(sbd, &error_abort); sysbus_mmio_map(sbd, 0, npcm7xx_fiu[i].regs_addr); diff --git a/hw/ssi/npcm7xx_fiu.c b/hw/ssi/npcm7xx_fiu.c index 21fc48903839..8df4bec3f172 100644 --- a/hw/ssi/npcm7xx_fiu.c +++ b/hw/ssi/npcm7xx_fiu.c @@ -29,7 +29,7 @@ #include "trace.h" /* Up to 128 MiB of flash may be accessed directly as memory. */ -#define NPCM7XX_FIU_FLASH_WINDOW_SIZE (128 * MiB) +#define NPCM7XX_FIU_MAX_FLASH_WINDOW_SIZE (128 * MiB) /* Each module has 4 KiB of register space. Only a fraction of it is used. */ #define NPCM7XX_FIU_CTRL_REGS_SIZE (4 * KiB) @@ -507,6 +507,17 @@ static void npcm7xx_fiu_realize(DeviceState *dev, Error **errp) return; } + if (s->flash_size == 0) { + error_setg(errp, "%s: flash size must be set", dev->canonical_path); + return; + } + + if (s->flash_size > NPCM7XX_FIU_MAX_FLASH_WINDOW_SIZE) { + error_setg(errp, "%s: flash size should not exceed 128 MiB", + dev->canonical_path); + return; + } + s->spi = ssi_create_bus(dev, "spi"); s->cs_lines = g_new0(qemu_irq, s->cs_count); qdev_init_gpio_out_named(DEVICE(s), s->cs_lines, "cs", s->cs_count); @@ -525,7 +536,7 @@ static void npcm7xx_fiu_realize(DeviceState *dev, Error **errp) flash->fiu = s; memory_region_init_io(&flash->direct_access, OBJECT(s), &npcm7xx_fiu_flash_ops, &s->flash[i], "flash", - NPCM7XX_FIU_FLASH_WINDOW_SIZE); + s->flash_size); sysbus_init_mmio(sbd, &flash->direct_access); } } @@ -543,6 +554,7 @@ static const VMStateDescription vmstate_npcm7xx_fiu = { static const Property npcm7xx_fiu_properties[] = { DEFINE_PROP_INT32("cs-count", NPCM7xxFIUState, cs_count, 0), + DEFINE_PROP_SIZE("flash-size", NPCM7xxFIUState, flash_size, 0), }; static void npcm7xx_fiu_class_init(ObjectClass *klass, void *data) diff --git a/include/hw/ssi/npcm7xx_fiu.h b/include/hw/ssi/npcm7xx_fiu.h index a3a170428962..7ebd422ca6c0 100644 --- a/include/hw/ssi/npcm7xx_fiu.h +++ b/include/hw/ssi/npcm7xx_fiu.h @@ -60,6 +60,7 @@ struct NPCM7xxFIUState { int32_t cs_count; int32_t active_cs; qemu_irq *cs_lines; + uint64_t flash_size; NPCM7xxFIUFlash *flash; SSIBus *spi; From 506af2330cd0ef684a48aad12640a7bea8e95247 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:55 -0800 Subject: [PATCH 0167/1179] hw/misc: Rename npcm7xx_gcr to npcm_gcr NPCM7XX and NPCM8XX have a different set of GCRs and the GCR module needs to fit both. This commit changes the name of the GCR module. Future commits will add the support for NPCM8XX GCRs. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-5-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/meson.build | 2 +- hw/misc/{npcm7xx_gcr.c => npcm_gcr.c} | 2 +- include/hw/arm/npcm7xx.h | 2 +- include/hw/misc/{npcm7xx_gcr.h => npcm_gcr.h} | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) rename hw/misc/{npcm7xx_gcr.c => npcm_gcr.c} (99%) rename include/hw/misc/{npcm7xx_gcr.h => npcm_gcr.h} (96%) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 55f493521bec..554eb8df5bc7 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -70,7 +70,7 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files( )) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files( 'npcm7xx_clk.c', - 'npcm7xx_gcr.c', + 'npcm_gcr.c', 'npcm7xx_mft.c', 'npcm7xx_pwm.c', 'npcm7xx_rng.c', diff --git a/hw/misc/npcm7xx_gcr.c b/hw/misc/npcm_gcr.c similarity index 99% rename from hw/misc/npcm7xx_gcr.c rename to hw/misc/npcm_gcr.c index 07464a4dc93e..826fd41123b0 100644 --- a/hw/misc/npcm7xx_gcr.c +++ b/hw/misc/npcm_gcr.c @@ -16,7 +16,7 @@ #include "qemu/osdep.h" -#include "hw/misc/npcm7xx_gcr.h" +#include "hw/misc/npcm_gcr.h" #include "hw/qdev-properties.h" #include "migration/vmstate.h" #include "qapi/error.h" diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index 4e0d2101885c..510170471e07 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -24,7 +24,7 @@ #include "hw/i2c/npcm7xx_smbus.h" #include "hw/mem/npcm7xx_mc.h" #include "hw/misc/npcm7xx_clk.h" -#include "hw/misc/npcm7xx_gcr.h" +#include "hw/misc/npcm_gcr.h" #include "hw/misc/npcm7xx_mft.h" #include "hw/misc/npcm7xx_pwm.h" #include "hw/misc/npcm7xx_rng.h" diff --git a/include/hw/misc/npcm7xx_gcr.h b/include/hw/misc/npcm_gcr.h similarity index 96% rename from include/hw/misc/npcm7xx_gcr.h rename to include/hw/misc/npcm_gcr.h index c0bbdda77e59..9b4998950cc1 100644 --- a/include/hw/misc/npcm7xx_gcr.h +++ b/include/hw/misc/npcm_gcr.h @@ -13,8 +13,8 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ -#ifndef NPCM7XX_GCR_H -#define NPCM7XX_GCR_H +#ifndef NPCM_GCR_H +#define NPCM_GCR_H #include "exec/memory.h" #include "hw/sysbus.h" @@ -70,4 +70,4 @@ struct NPCM7xxGCRState { #define TYPE_NPCM7XX_GCR "npcm7xx-gcr" OBJECT_DECLARE_SIMPLE_TYPE(NPCM7xxGCRState, NPCM7XX_GCR) -#endif /* NPCM7XX_GCR_H */ +#endif /* NPCM_GCR_H */ From c99064e63748f993457f1fe658d05c662c0134f1 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:56 -0800 Subject: [PATCH 0168/1179] hw/misc: Move NPCM7XX GCR to NPCM GCR A lot of NPCM7XX and NPCM8XX GCR modules share the same code, this commit moves the NPCM7XX GCR to NPCM GCR for these properties. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-6-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_gcr.c | 92 +++++++++++++++++++++----------------- hw/misc/trace-events | 6 +-- include/hw/arm/npcm7xx.h | 2 +- include/hw/misc/npcm_gcr.h | 7 +-- 4 files changed, 59 insertions(+), 48 deletions(-) diff --git a/hw/misc/npcm_gcr.c b/hw/misc/npcm_gcr.c index 826fd41123b0..0959f2e5c4b7 100644 --- a/hw/misc/npcm_gcr.c +++ b/hw/misc/npcm_gcr.c @@ -84,10 +84,10 @@ static const uint32_t cold_reset_values[NPCM7XX_GCR_NR_REGS] = { [NPCM7XX_GCR_USB2PHYCTL] = 0x034730e4, }; -static uint64_t npcm7xx_gcr_read(void *opaque, hwaddr offset, unsigned size) +static uint64_t npcm_gcr_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); - NPCM7xxGCRState *s = opaque; + NPCMGCRState *s = opaque; if (reg >= NPCM7XX_GCR_NR_REGS) { qemu_log_mask(LOG_GUEST_ERROR, @@ -96,19 +96,19 @@ static uint64_t npcm7xx_gcr_read(void *opaque, hwaddr offset, unsigned size) return 0; } - trace_npcm7xx_gcr_read(offset, s->regs[reg]); + trace_npcm_gcr_read(offset, s->regs[reg]); return s->regs[reg]; } -static void npcm7xx_gcr_write(void *opaque, hwaddr offset, +static void npcm_gcr_write(void *opaque, hwaddr offset, uint64_t v, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); - NPCM7xxGCRState *s = opaque; + NPCMGCRState *s = opaque; uint32_t value = v; - trace_npcm7xx_gcr_write(offset, value); + trace_npcm_gcr_write(offset, value); if (reg >= NPCM7XX_GCR_NR_REGS) { qemu_log_mask(LOG_GUEST_ERROR, @@ -142,9 +142,9 @@ static void npcm7xx_gcr_write(void *opaque, hwaddr offset, s->regs[reg] = value; } -static const struct MemoryRegionOps npcm7xx_gcr_ops = { - .read = npcm7xx_gcr_read, - .write = npcm7xx_gcr_write, +static const struct MemoryRegionOps npcm_gcr_ops = { + .read = npcm_gcr_read, + .write = npcm_gcr_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, @@ -155,7 +155,7 @@ static const struct MemoryRegionOps npcm7xx_gcr_ops = { static void npcm7xx_gcr_enter_reset(Object *obj, ResetType type) { - NPCM7xxGCRState *s = NPCM7XX_GCR(obj); + NPCMGCRState *s = NPCM_GCR(obj); QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values)); @@ -165,10 +165,10 @@ static void npcm7xx_gcr_enter_reset(Object *obj, ResetType type) s->regs[NPCM7XX_GCR_INTCR3] = s->reset_intcr3; } -static void npcm7xx_gcr_realize(DeviceState *dev, Error **errp) +static void npcm_gcr_realize(DeviceState *dev, Error **errp) { ERRP_GUARD(); - NPCM7xxGCRState *s = NPCM7XX_GCR(dev); + NPCMGCRState *s = NPCM_GCR(dev); uint64_t dram_size; Object *obj; @@ -210,55 +210,65 @@ static void npcm7xx_gcr_realize(DeviceState *dev, Error **errp) s->reset_intcr3 |= ctz64(dram_size / NPCM7XX_GCR_MIN_DRAM_SIZE) << 8; } -static void npcm7xx_gcr_init(Object *obj) +static void npcm_gcr_init(Object *obj) { - NPCM7xxGCRState *s = NPCM7XX_GCR(obj); + NPCMGCRState *s = NPCM_GCR(obj); - memory_region_init_io(&s->iomem, obj, &npcm7xx_gcr_ops, s, - TYPE_NPCM7XX_GCR, 4 * KiB); + memory_region_init_io(&s->iomem, obj, &npcm_gcr_ops, s, + TYPE_NPCM_GCR, 4 * KiB); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); } -static const VMStateDescription vmstate_npcm7xx_gcr = { - .name = "npcm7xx-gcr", - .version_id = 0, - .minimum_version_id = 0, +static const VMStateDescription vmstate_npcm_gcr = { + .name = "npcm-gcr", + .version_id = 1, + .minimum_version_id = 1, .fields = (const VMStateField[]) { - VMSTATE_UINT32_ARRAY(regs, NPCM7xxGCRState, NPCM7XX_GCR_NR_REGS), + VMSTATE_UINT32_ARRAY(regs, NPCMGCRState, NPCM7XX_GCR_NR_REGS), VMSTATE_END_OF_LIST(), }, }; -static const Property npcm7xx_gcr_properties[] = { - DEFINE_PROP_UINT32("disabled-modules", NPCM7xxGCRState, reset_mdlr, 0), - DEFINE_PROP_UINT32("power-on-straps", NPCM7xxGCRState, reset_pwron, 0), +static const Property npcm_gcr_properties[] = { + DEFINE_PROP_UINT32("disabled-modules", NPCMGCRState, reset_mdlr, 0), + DEFINE_PROP_UINT32("power-on-straps", NPCMGCRState, reset_pwron, 0), }; -static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data) +static void npcm_gcr_class_init(ObjectClass *klass, void *data) { - ResettableClass *rc = RESETTABLE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); QEMU_BUILD_BUG_ON(NPCM7XX_GCR_REGS_END > NPCM7XX_GCR_NR_REGS); + dc->realize = npcm_gcr_realize; + dc->vmsd = &vmstate_npcm_gcr; + + device_class_set_props(dc, npcm_gcr_properties); +} + +static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + QEMU_BUILD_BUG_ON(NPCM7XX_GCR_REGS_END != NPCM7XX_GCR_NR_REGS); dc->desc = "NPCM7xx System Global Control Registers"; - dc->realize = npcm7xx_gcr_realize; - dc->vmsd = &vmstate_npcm7xx_gcr; rc->phases.enter = npcm7xx_gcr_enter_reset; - device_class_set_props(dc, npcm7xx_gcr_properties); } -static const TypeInfo npcm7xx_gcr_info = { - .name = TYPE_NPCM7XX_GCR, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(NPCM7xxGCRState), - .instance_init = npcm7xx_gcr_init, - .class_init = npcm7xx_gcr_class_init, +static const TypeInfo npcm_gcr_info[] = { + { + .name = TYPE_NPCM_GCR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCMGCRState), + .instance_init = npcm_gcr_init, + .class_init = npcm_gcr_class_init, + .abstract = true, + }, + { + .name = TYPE_NPCM7XX_GCR, + .parent = TYPE_NPCM_GCR, + .class_init = npcm7xx_gcr_class_init, + }, }; - -static void npcm7xx_gcr_register_type(void) -{ - type_register_static(&npcm7xx_gcr_info); -} -type_init(npcm7xx_gcr_register_type); +DEFINE_TYPES(npcm_gcr_info) diff --git a/hw/misc/trace-events b/hw/misc/trace-events index b35b0e77f7db..0f7204a237ee 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -134,9 +134,9 @@ mos6522_read(uint64_t addr, const char *name, unsigned val) "reg=0x%"PRIx64 " [% npcm7xx_clk_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 npcm7xx_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 -# npcm7xx_gcr.c -npcm7xx_gcr_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 -npcm7xx_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +# npcm_gcr.c +npcm_gcr_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +npcm_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 # npcm7xx_mft.c npcm7xx_mft_read(const char *name, uint64_t offset, uint16_t value) "%s: offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index 510170471e07..2e708471ece4 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -89,7 +89,7 @@ struct NPCM7xxState { MemoryRegion ram3; MemoryRegion *dram; - NPCM7xxGCRState gcr; + NPCMGCRState gcr; NPCM7xxCLKState clk; NPCM7xxTimerCtrlState tim[3]; NPCM7xxADCState adc; diff --git a/include/hw/misc/npcm_gcr.h b/include/hw/misc/npcm_gcr.h index 9b4998950cc1..6d3d00d2602a 100644 --- a/include/hw/misc/npcm_gcr.h +++ b/include/hw/misc/npcm_gcr.h @@ -55,7 +55,7 @@ */ #define NPCM7XX_GCR_NR_REGS (0x148 / sizeof(uint32_t)) -struct NPCM7xxGCRState { +typedef struct NPCMGCRState { SysBusDevice parent; MemoryRegion iomem; @@ -65,9 +65,10 @@ struct NPCM7xxGCRState { uint32_t reset_pwron; uint32_t reset_mdlr; uint32_t reset_intcr3; -}; +} NPCMGCRState; +#define TYPE_NPCM_GCR "npcm-gcr" #define TYPE_NPCM7XX_GCR "npcm7xx-gcr" -OBJECT_DECLARE_SIMPLE_TYPE(NPCM7xxGCRState, NPCM7XX_GCR) +OBJECT_DECLARE_SIMPLE_TYPE(NPCMGCRState, NPCM_GCR) #endif /* NPCM_GCR_H */ From 8ca2021b9d3aa3e9ef276bdbf04f89677341955c Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:57 -0800 Subject: [PATCH 0169/1179] hw/misc: Add nr_regs and cold_reset_values to NPCM GCR These 2 values are different between NPCM7XX and NPCM8XX GCRs. So we add them to the class and assign different values to them. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-7-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_gcr.c | 27 ++++++++++++++++----------- include/hw/misc/npcm_gcr.h | 13 +++++++++++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/hw/misc/npcm_gcr.c b/hw/misc/npcm_gcr.c index 0959f2e5c4b7..d89e8c2c3bf8 100644 --- a/hw/misc/npcm_gcr.c +++ b/hw/misc/npcm_gcr.c @@ -66,10 +66,9 @@ enum NPCM7xxGCRRegisters { NPCM7XX_GCR_SCRPAD = 0x013c / sizeof(uint32_t), NPCM7XX_GCR_USB1PHYCTL, NPCM7XX_GCR_USB2PHYCTL, - NPCM7XX_GCR_REGS_END, }; -static const uint32_t cold_reset_values[NPCM7XX_GCR_NR_REGS] = { +static const uint32_t npcm7xx_cold_reset_values[NPCM7XX_GCR_NR_REGS] = { [NPCM7XX_GCR_PDID] = 0x04a92750, /* Poleg A1 */ [NPCM7XX_GCR_MISCPE] = 0x0000ffff, [NPCM7XX_GCR_SPSWC] = 0x00000003, @@ -88,8 +87,9 @@ static uint64_t npcm_gcr_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); NPCMGCRState *s = opaque; + NPCMGCRClass *c = NPCM_GCR_GET_CLASS(s); - if (reg >= NPCM7XX_GCR_NR_REGS) { + if (reg >= c->nr_regs) { qemu_log_mask(LOG_GUEST_ERROR, "%s: offset 0x%04" HWADDR_PRIx " out of range\n", __func__, offset); @@ -106,11 +106,12 @@ static void npcm_gcr_write(void *opaque, hwaddr offset, { uint32_t reg = offset / sizeof(uint32_t); NPCMGCRState *s = opaque; + NPCMGCRClass *c = NPCM_GCR_GET_CLASS(s); uint32_t value = v; - trace_npcm_gcr_write(offset, value); + trace_npcm_gcr_write(offset, v); - if (reg >= NPCM7XX_GCR_NR_REGS) { + if (reg >= c->nr_regs) { qemu_log_mask(LOG_GUEST_ERROR, "%s: offset 0x%04" HWADDR_PRIx " out of range\n", __func__, offset); @@ -156,10 +157,12 @@ static const struct MemoryRegionOps npcm_gcr_ops = { static void npcm7xx_gcr_enter_reset(Object *obj, ResetType type) { NPCMGCRState *s = NPCM_GCR(obj); + NPCMGCRClass *c = NPCM_GCR_GET_CLASS(obj); - QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values)); - - memcpy(s->regs, cold_reset_values, sizeof(s->regs)); + g_assert(sizeof(s->regs) >= sizeof(c->cold_reset_values)); + g_assert(sizeof(s->regs) >= c->nr_regs * sizeof(uint32_t)); + memcpy(s->regs, c->cold_reset_values, c->nr_regs * sizeof(uint32_t)); + /* These 3 registers are at the same location in both 7xx and 8xx. */ s->regs[NPCM7XX_GCR_PWRON] = s->reset_pwron; s->regs[NPCM7XX_GCR_MDLR] = s->reset_mdlr; s->regs[NPCM7XX_GCR_INTCR3] = s->reset_intcr3; @@ -224,7 +227,7 @@ static const VMStateDescription vmstate_npcm_gcr = { .version_id = 1, .minimum_version_id = 1, .fields = (const VMStateField[]) { - VMSTATE_UINT32_ARRAY(regs, NPCMGCRState, NPCM7XX_GCR_NR_REGS), + VMSTATE_UINT32_ARRAY(regs, NPCMGCRState, NPCM_GCR_MAX_NR_REGS), VMSTATE_END_OF_LIST(), }, }; @@ -238,7 +241,6 @@ static void npcm_gcr_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - QEMU_BUILD_BUG_ON(NPCM7XX_GCR_REGS_END > NPCM7XX_GCR_NR_REGS); dc->realize = npcm_gcr_realize; dc->vmsd = &vmstate_npcm_gcr; @@ -247,13 +249,15 @@ static void npcm_gcr_class_init(ObjectClass *klass, void *data) static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data) { + NPCMGCRClass *c = NPCM_GCR_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); ResettableClass *rc = RESETTABLE_CLASS(klass); - QEMU_BUILD_BUG_ON(NPCM7XX_GCR_REGS_END != NPCM7XX_GCR_NR_REGS); dc->desc = "NPCM7xx System Global Control Registers"; rc->phases.enter = npcm7xx_gcr_enter_reset; + c->nr_regs = NPCM7XX_GCR_NR_REGS; + c->cold_reset_values = npcm7xx_cold_reset_values; } static const TypeInfo npcm_gcr_info[] = { @@ -262,6 +266,7 @@ static const TypeInfo npcm_gcr_info[] = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(NPCMGCRState), .instance_init = npcm_gcr_init, + .class_size = sizeof(NPCMGCRClass), .class_init = npcm_gcr_class_init, .abstract = true, }, diff --git a/include/hw/misc/npcm_gcr.h b/include/hw/misc/npcm_gcr.h index 6d3d00d2602a..9af24e5cdc8b 100644 --- a/include/hw/misc/npcm_gcr.h +++ b/include/hw/misc/npcm_gcr.h @@ -18,6 +18,7 @@ #include "exec/memory.h" #include "hw/sysbus.h" +#include "qom/object.h" /* * NPCM7XX PWRON STRAP bit fields @@ -53,6 +54,7 @@ * Number of registers in our device state structure. Don't change this without * incrementing the version_id in the vmstate. */ +#define NPCM_GCR_MAX_NR_REGS NPCM7XX_GCR_NR_REGS #define NPCM7XX_GCR_NR_REGS (0x148 / sizeof(uint32_t)) typedef struct NPCMGCRState { @@ -60,15 +62,22 @@ typedef struct NPCMGCRState { MemoryRegion iomem; - uint32_t regs[NPCM7XX_GCR_NR_REGS]; + uint32_t regs[NPCM_GCR_MAX_NR_REGS]; uint32_t reset_pwron; uint32_t reset_mdlr; uint32_t reset_intcr3; } NPCMGCRState; +typedef struct NPCMGCRClass { + SysBusDeviceClass parent; + + size_t nr_regs; + const uint32_t *cold_reset_values; +} NPCMGCRClass; + #define TYPE_NPCM_GCR "npcm-gcr" #define TYPE_NPCM7XX_GCR "npcm7xx-gcr" -OBJECT_DECLARE_SIMPLE_TYPE(NPCMGCRState, NPCM_GCR) +OBJECT_DECLARE_TYPE(NPCMGCRState, NPCMGCRClass, NPCM_GCR) #endif /* NPCM_GCR_H */ From d9ffb75f2a85a2271dc928849f02f7bada4d1507 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:58 -0800 Subject: [PATCH 0170/1179] hw/misc: Add support for NPCM8XX GCR Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-8-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_gcr.c | 133 ++++++++++++++++++++++++++++++++++++- include/hw/misc/npcm_gcr.h | 6 +- 2 files changed, 134 insertions(+), 5 deletions(-) diff --git a/hw/misc/npcm_gcr.c b/hw/misc/npcm_gcr.c index d89e8c2c3bf8..ac22fb08cb20 100644 --- a/hw/misc/npcm_gcr.c +++ b/hw/misc/npcm_gcr.c @@ -1,5 +1,5 @@ /* - * Nuvoton NPCM7xx System Global Control Registers. + * Nuvoton NPCM7xx/8xx System Global Control Registers. * * Copyright 2020 Google LLC * @@ -83,6 +83,118 @@ static const uint32_t npcm7xx_cold_reset_values[NPCM7XX_GCR_NR_REGS] = { [NPCM7XX_GCR_USB2PHYCTL] = 0x034730e4, }; +enum NPCM8xxGCRRegisters { + NPCM8XX_GCR_PDID, + NPCM8XX_GCR_PWRON, + NPCM8XX_GCR_MISCPE = 0x014 / sizeof(uint32_t), + NPCM8XX_GCR_FLOCKR2 = 0x020 / sizeof(uint32_t), + NPCM8XX_GCR_FLOCKR3, + NPCM8XX_GCR_A35_MODE = 0x034 / sizeof(uint32_t), + NPCM8XX_GCR_SPSWC, + NPCM8XX_GCR_INTCR, + NPCM8XX_GCR_INTSR, + NPCM8XX_GCR_HIFCR = 0x050 / sizeof(uint32_t), + NPCM8XX_GCR_INTCR2 = 0x060 / sizeof(uint32_t), + NPCM8XX_GCR_SRCNT = 0x068 / sizeof(uint32_t), + NPCM8XX_GCR_RESSR, + NPCM8XX_GCR_RLOCKR1, + NPCM8XX_GCR_FLOCKR1, + NPCM8XX_GCR_DSCNT, + NPCM8XX_GCR_MDLR, + NPCM8XX_GCR_SCRPAD_C = 0x080 / sizeof(uint32_t), + NPCM8XX_GCR_SCRPAD_B, + NPCM8XX_GCR_DAVCLVLR = 0x098 / sizeof(uint32_t), + NPCM8XX_GCR_INTCR3, + NPCM8XX_GCR_PCIRCTL = 0x0a0 / sizeof(uint32_t), + NPCM8XX_GCR_VSINTR, + NPCM8XX_GCR_SD2SUR1 = 0x0b4 / sizeof(uint32_t), + NPCM8XX_GCR_SD2SUR2, + NPCM8XX_GCR_INTCR4 = 0x0c0 / sizeof(uint32_t), + NPCM8XX_GCR_CPCTL = 0x0d0 / sizeof(uint32_t), + NPCM8XX_GCR_CP2BST, + NPCM8XX_GCR_B2CPNT, + NPCM8XX_GCR_CPPCTL, + NPCM8XX_GCR_I2CSEGSEL = 0x0e0 / sizeof(uint32_t), + NPCM8XX_GCR_I2CSEGCTL, + NPCM8XX_GCR_VSRCR, + NPCM8XX_GCR_MLOCKR, + NPCM8XX_GCR_SCRPAD = 0x13c / sizeof(uint32_t), + NPCM8XX_GCR_USB1PHYCTL, + NPCM8XX_GCR_USB2PHYCTL, + NPCM8XX_GCR_USB3PHYCTL, + NPCM8XX_GCR_MFSEL1 = 0x260 / sizeof(uint32_t), + NPCM8XX_GCR_MFSEL2, + NPCM8XX_GCR_MFSEL3, + NPCM8XX_GCR_MFSEL4, + NPCM8XX_GCR_MFSEL5, + NPCM8XX_GCR_MFSEL6, + NPCM8XX_GCR_MFSEL7, + NPCM8XX_GCR_MFSEL_LK1 = 0x280 / sizeof(uint32_t), + NPCM8XX_GCR_MFSEL_LK2, + NPCM8XX_GCR_MFSEL_LK3, + NPCM8XX_GCR_MFSEL_LK4, + NPCM8XX_GCR_MFSEL_LK5, + NPCM8XX_GCR_MFSEL_LK6, + NPCM8XX_GCR_MFSEL_LK7, + NPCM8XX_GCR_MFSEL_SET1 = 0x2a0 / sizeof(uint32_t), + NPCM8XX_GCR_MFSEL_SET2, + NPCM8XX_GCR_MFSEL_SET3, + NPCM8XX_GCR_MFSEL_SET4, + NPCM8XX_GCR_MFSEL_SET5, + NPCM8XX_GCR_MFSEL_SET6, + NPCM8XX_GCR_MFSEL_SET7, + NPCM8XX_GCR_MFSEL_CLR1 = 0x2c0 / sizeof(uint32_t), + NPCM8XX_GCR_MFSEL_CLR2, + NPCM8XX_GCR_MFSEL_CLR3, + NPCM8XX_GCR_MFSEL_CLR4, + NPCM8XX_GCR_MFSEL_CLR5, + NPCM8XX_GCR_MFSEL_CLR6, + NPCM8XX_GCR_MFSEL_CLR7, + NPCM8XX_GCR_WD0RCRLK = 0x400 / sizeof(uint32_t), + NPCM8XX_GCR_WD1RCRLK, + NPCM8XX_GCR_WD2RCRLK, + NPCM8XX_GCR_SWRSTC1LK, + NPCM8XX_GCR_SWRSTC2LK, + NPCM8XX_GCR_SWRSTC3LK, + NPCM8XX_GCR_TIPRSTCLK, + NPCM8XX_GCR_CORSTCLK, + NPCM8XX_GCR_WD0RCRBLK, + NPCM8XX_GCR_WD1RCRBLK, + NPCM8XX_GCR_WD2RCRBLK, + NPCM8XX_GCR_SWRSTC1BLK, + NPCM8XX_GCR_SWRSTC2BLK, + NPCM8XX_GCR_SWRSTC3BLK, + NPCM8XX_GCR_TIPRSTCBLK, + NPCM8XX_GCR_CORSTCBLK, + /* 64 scratch pad registers start here. 0xe00 ~ 0xefc */ + NPCM8XX_GCR_SCRPAD_00 = 0xe00 / sizeof(uint32_t), + /* 32 semaphore registers start here. 0xf00 ~ 0xf7c */ + NPCM8XX_GCR_GP_SEMFR_00 = 0xf00 / sizeof(uint32_t), + NPCM8XX_GCR_GP_SEMFR_31 = 0xf7c / sizeof(uint32_t), +}; + +static const uint32_t npcm8xx_cold_reset_values[NPCM8XX_GCR_NR_REGS] = { + [NPCM8XX_GCR_PDID] = 0x04a35850, /* Arbel A1 */ + [NPCM8XX_GCR_MISCPE] = 0x0000ffff, + [NPCM8XX_GCR_A35_MODE] = 0xfff4ff30, + [NPCM8XX_GCR_SPSWC] = 0x00000003, + [NPCM8XX_GCR_INTCR] = 0x0010035e, + [NPCM8XX_GCR_HIFCR] = 0x0000004e, + [NPCM8XX_GCR_SD2SUR1] = 0xfdc80000, + [NPCM8XX_GCR_SD2SUR2] = 0x5200b130, + [NPCM8XX_GCR_INTCR2] = (1U << 19), /* DDR initialized */ + [NPCM8XX_GCR_RESSR] = 0x80000000, + [NPCM8XX_GCR_DAVCLVLR] = 0x5a00f3cf, + [NPCM8XX_GCR_INTCR3] = 0x5e001002, + [NPCM8XX_GCR_VSRCR] = 0x00004800, + [NPCM8XX_GCR_SCRPAD] = 0x00000008, + [NPCM8XX_GCR_USB1PHYCTL] = 0x034730e4, + [NPCM8XX_GCR_USB2PHYCTL] = 0x034730e4, + [NPCM8XX_GCR_USB3PHYCTL] = 0x034730e4, + /* All 32 semaphores should be initialized to 1. */ + [NPCM8XX_GCR_GP_SEMFR_00...NPCM8XX_GCR_GP_SEMFR_31] = 0x00000001, +}; + static uint64_t npcm_gcr_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); @@ -224,8 +336,8 @@ static void npcm_gcr_init(Object *obj) static const VMStateDescription vmstate_npcm_gcr = { .name = "npcm-gcr", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (const VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, NPCMGCRState, NPCM_GCR_MAX_NR_REGS), VMSTATE_END_OF_LIST(), @@ -260,6 +372,16 @@ static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data) c->cold_reset_values = npcm7xx_cold_reset_values; } +static void npcm8xx_gcr_class_init(ObjectClass *klass, void *data) +{ + NPCMGCRClass *c = NPCM_GCR_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM8xx System Global Control Registers"; + c->nr_regs = NPCM8XX_GCR_NR_REGS; + c->cold_reset_values = npcm8xx_cold_reset_values; +} + static const TypeInfo npcm_gcr_info[] = { { .name = TYPE_NPCM_GCR, @@ -275,5 +397,10 @@ static const TypeInfo npcm_gcr_info[] = { .parent = TYPE_NPCM_GCR, .class_init = npcm7xx_gcr_class_init, }, + { + .name = TYPE_NPCM8XX_GCR, + .parent = TYPE_NPCM_GCR, + .class_init = npcm8xx_gcr_class_init, + }, }; DEFINE_TYPES(npcm_gcr_info) diff --git a/include/hw/misc/npcm_gcr.h b/include/hw/misc/npcm_gcr.h index 9af24e5cdc8b..9ac76ca9abbe 100644 --- a/include/hw/misc/npcm_gcr.h +++ b/include/hw/misc/npcm_gcr.h @@ -1,5 +1,5 @@ /* - * Nuvoton NPCM7xx System Global Control Registers. + * Nuvoton NPCM7xx/8xx System Global Control Registers. * * Copyright 2020 Google LLC * @@ -54,8 +54,9 @@ * Number of registers in our device state structure. Don't change this without * incrementing the version_id in the vmstate. */ -#define NPCM_GCR_MAX_NR_REGS NPCM7XX_GCR_NR_REGS +#define NPCM_GCR_MAX_NR_REGS NPCM8XX_GCR_NR_REGS #define NPCM7XX_GCR_NR_REGS (0x148 / sizeof(uint32_t)) +#define NPCM8XX_GCR_NR_REGS (0xf80 / sizeof(uint32_t)) typedef struct NPCMGCRState { SysBusDevice parent; @@ -78,6 +79,7 @@ typedef struct NPCMGCRClass { #define TYPE_NPCM_GCR "npcm-gcr" #define TYPE_NPCM7XX_GCR "npcm7xx-gcr" +#define TYPE_NPCM8XX_GCR "npcm8xx-gcr" OBJECT_DECLARE_TYPE(NPCMGCRState, NPCMGCRClass, NPCM_GCR) #endif /* NPCM_GCR_H */ From 0ad46bbb56585fc3900f803747c485529869ca22 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:45:59 -0800 Subject: [PATCH 0171/1179] hw/misc: Store DRAM size in NPCM8XX GCR Module NPCM8XX boot block stores the DRAM size in SCRPAD_B register in GCR module. Since we don't simulate a detailed memory controller, we need to store this information directly similar to the NPCM7XX's INCTR3 register. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-9-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_gcr.c | 24 ++++++++++++++++++++++++ include/hw/misc/npcm_gcr.h | 1 + 2 files changed, 25 insertions(+) diff --git a/hw/misc/npcm_gcr.c b/hw/misc/npcm_gcr.c index ac22fb08cb20..9e4a6aee6114 100644 --- a/hw/misc/npcm_gcr.c +++ b/hw/misc/npcm_gcr.c @@ -280,6 +280,19 @@ static void npcm7xx_gcr_enter_reset(Object *obj, ResetType type) s->regs[NPCM7XX_GCR_INTCR3] = s->reset_intcr3; } +static void npcm8xx_gcr_enter_reset(Object *obj, ResetType type) +{ + NPCMGCRState *s = NPCM_GCR(obj); + NPCMGCRClass *c = NPCM_GCR_GET_CLASS(obj); + + memcpy(s->regs, c->cold_reset_values, c->nr_regs * sizeof(uint32_t)); + /* These 3 registers are at the same location in both 7xx and 8xx. */ + s->regs[NPCM8XX_GCR_PWRON] = s->reset_pwron; + s->regs[NPCM8XX_GCR_MDLR] = s->reset_mdlr; + s->regs[NPCM8XX_GCR_INTCR3] = s->reset_intcr3; + s->regs[NPCM8XX_GCR_SCRPAD_B] = s->reset_scrpad_b; +} + static void npcm_gcr_realize(DeviceState *dev, Error **errp) { ERRP_GUARD(); @@ -323,6 +336,14 @@ static void npcm_gcr_realize(DeviceState *dev, Error **errp) * https://github.com/Nuvoton-Israel/u-boot/blob/2aef993bd2aafeb5408dbaad0f3ce099ee40c4aa/board/nuvoton/poleg/poleg.c#L244 */ s->reset_intcr3 |= ctz64(dram_size / NPCM7XX_GCR_MIN_DRAM_SIZE) << 8; + + /* + * The boot block starting from 0.0.6 for NPCM8xx SoCs stores the DRAM size + * in the SCRPAD2 registers. We need to set this field correctly since + * the initialization is skipped as we mentioned above. + * https://github.com/Nuvoton-Israel/u-boot/blob/npcm8mnx-v2019.01_tmp/board/nuvoton/arbel/arbel.c#L737 + */ + s->reset_scrpad_b = dram_size; } static void npcm_gcr_init(Object *obj) @@ -370,16 +391,19 @@ static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data) c->nr_regs = NPCM7XX_GCR_NR_REGS; c->cold_reset_values = npcm7xx_cold_reset_values; + rc->phases.enter = npcm7xx_gcr_enter_reset; } static void npcm8xx_gcr_class_init(ObjectClass *klass, void *data) { NPCMGCRClass *c = NPCM_GCR_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); dc->desc = "NPCM8xx System Global Control Registers"; c->nr_regs = NPCM8XX_GCR_NR_REGS; c->cold_reset_values = npcm8xx_cold_reset_values; + rc->phases.enter = npcm8xx_gcr_enter_reset; } static const TypeInfo npcm_gcr_info[] = { diff --git a/include/hw/misc/npcm_gcr.h b/include/hw/misc/npcm_gcr.h index 9ac76ca9abbe..d81bb9afb263 100644 --- a/include/hw/misc/npcm_gcr.h +++ b/include/hw/misc/npcm_gcr.h @@ -68,6 +68,7 @@ typedef struct NPCMGCRState { uint32_t reset_pwron; uint32_t reset_mdlr; uint32_t reset_intcr3; + uint32_t reset_scrpad_b; } NPCMGCRState; typedef struct NPCMGCRClass { From ca2fd966ea90b2ca02a4eff1afc2b89e963680a1 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:00 -0800 Subject: [PATCH 0172/1179] hw/misc: Support 8-bytes memop in NPCM GCR module The NPCM8xx GCR device can be accessed with 64-bit memory operations. This patch supports that. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Reviewed-by: Philippe Mathieu-Daude Message-id: 20250219184609.1839281-10-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_gcr.c | 94 +++++++++++++++++++++++++++++++++----------- hw/misc/trace-events | 4 +- 2 files changed, 74 insertions(+), 24 deletions(-) diff --git a/hw/misc/npcm_gcr.c b/hw/misc/npcm_gcr.c index 9e4a6aee6114..ec16ea620eb2 100644 --- a/hw/misc/npcm_gcr.c +++ b/hw/misc/npcm_gcr.c @@ -200,6 +200,7 @@ static uint64_t npcm_gcr_read(void *opaque, hwaddr offset, unsigned size) uint32_t reg = offset / sizeof(uint32_t); NPCMGCRState *s = opaque; NPCMGCRClass *c = NPCM_GCR_GET_CLASS(s); + uint64_t value; if (reg >= c->nr_regs) { qemu_log_mask(LOG_GUEST_ERROR, @@ -208,9 +209,21 @@ static uint64_t npcm_gcr_read(void *opaque, hwaddr offset, unsigned size) return 0; } - trace_npcm_gcr_read(offset, s->regs[reg]); + switch (size) { + case 4: + value = s->regs[reg]; + break; + + case 8: + value = deposit64(s->regs[reg], 32, 32, s->regs[reg + 1]); + break; + + default: + g_assert_not_reached(); + } - return s->regs[reg]; + trace_npcm_gcr_read(offset, value); + return value; } static void npcm_gcr_write(void *opaque, hwaddr offset, @@ -230,29 +243,65 @@ static void npcm_gcr_write(void *opaque, hwaddr offset, return; } - switch (reg) { - case NPCM7XX_GCR_PDID: - case NPCM7XX_GCR_PWRON: - case NPCM7XX_GCR_INTSR: - qemu_log_mask(LOG_GUEST_ERROR, - "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", - __func__, offset); - return; - - case NPCM7XX_GCR_RESSR: - case NPCM7XX_GCR_CP2BST: - /* Write 1 to clear */ - value = s->regs[reg] & ~value; + switch (size) { + case 4: + switch (reg) { + case NPCM7XX_GCR_PDID: + case NPCM7XX_GCR_PWRON: + case NPCM7XX_GCR_INTSR: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", + __func__, offset); + return; + + case NPCM7XX_GCR_RESSR: + case NPCM7XX_GCR_CP2BST: + /* Write 1 to clear */ + value = s->regs[reg] & ~value; + break; + + case NPCM7XX_GCR_RLOCKR1: + case NPCM7XX_GCR_MDLR: + /* Write 1 to set */ + value |= s->regs[reg]; + break; + }; + s->regs[reg] = value; break; - case NPCM7XX_GCR_RLOCKR1: - case NPCM7XX_GCR_MDLR: - /* Write 1 to set */ - value |= s->regs[reg]; + case 8: + s->regs[reg] = value; + s->regs[reg + 1] = extract64(v, 32, 32); break; - }; - s->regs[reg] = value; + default: + g_assert_not_reached(); + } +} + +static bool npcm_gcr_check_mem_op(void *opaque, hwaddr offset, + unsigned size, bool is_write, + MemTxAttrs attrs) +{ + NPCMGCRClass *c = NPCM_GCR_GET_CLASS(opaque); + + if (offset >= c->nr_regs * sizeof(uint32_t)) { + return false; + } + + switch (size) { + case 4: + return true; + case 8: + if (offset >= NPCM8XX_GCR_SCRPAD_00 * sizeof(uint32_t) && + offset < (NPCM8XX_GCR_NR_REGS - 1) * sizeof(uint32_t)) { + return true; + } else { + return false; + } + default: + return false; + } } static const struct MemoryRegionOps npcm_gcr_ops = { @@ -261,7 +310,8 @@ static const struct MemoryRegionOps npcm_gcr_ops = { .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, - .max_access_size = 4, + .max_access_size = 8, + .accepts = npcm_gcr_check_mem_op, .unaligned = false, }, }; diff --git a/hw/misc/trace-events b/hw/misc/trace-events index 0f7204a237ee..f25dbd6030c3 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -135,8 +135,8 @@ npcm7xx_clk_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " valu npcm7xx_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 # npcm_gcr.c -npcm_gcr_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 -npcm_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +npcm_gcr_read(uint64_t offset, uint64_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx64 +npcm_gcr_write(uint64_t offset, uint64_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx64 # npcm7xx_mft.c npcm7xx_mft_read(const char *name, uint64_t offset, uint16_t value) "%s: offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 From c8283b0f4a7e9397da141d70e73e79341c5df2d7 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:01 -0800 Subject: [PATCH 0173/1179] hw/misc: Rename npcm7xx_clk to npcm_clk NPCM7XX and NPCM8XX have a different set of CLK registers. This commit changes the name of the clk files to be used by both NPCM7XX and NPCM8XX CLK modules. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-11-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/meson.build | 2 +- hw/misc/{npcm7xx_clk.c => npcm_clk.c} | 2 +- include/hw/arm/npcm7xx.h | 2 +- include/hw/misc/{npcm7xx_clk.h => npcm_clk.h} | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) rename hw/misc/{npcm7xx_clk.c => npcm_clk.c} (99%) rename include/hw/misc/{npcm7xx_clk.h => npcm_clk.h} (98%) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 554eb8df5bc7..edd36a334d73 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -69,7 +69,7 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files( 'imx_rngc.c', )) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files( - 'npcm7xx_clk.c', + 'npcm_clk.c', 'npcm_gcr.c', 'npcm7xx_mft.c', 'npcm7xx_pwm.c', diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm_clk.c similarity index 99% rename from hw/misc/npcm7xx_clk.c rename to hw/misc/npcm_clk.c index 46f907b61c2c..2bcb731099ed 100644 --- a/hw/misc/npcm7xx_clk.c +++ b/hw/misc/npcm_clk.c @@ -16,7 +16,7 @@ #include "qemu/osdep.h" -#include "hw/misc/npcm7xx_clk.h" +#include "hw/misc/npcm_clk.h" #include "hw/timer/npcm7xx_timer.h" #include "hw/qdev-clock.h" #include "migration/vmstate.h" diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index 2e708471ece4..e80fd91f204c 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -23,7 +23,7 @@ #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/npcm_clk.h" #include "hw/misc/npcm_gcr.h" #include "hw/misc/npcm7xx_mft.h" #include "hw/misc/npcm7xx_pwm.h" diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm_clk.h similarity index 98% rename from include/hw/misc/npcm7xx_clk.h rename to include/hw/misc/npcm_clk.h index 5ed4a4672b36..0aef81e10c83 100644 --- a/include/hw/misc/npcm7xx_clk.h +++ b/include/hw/misc/npcm_clk.h @@ -13,8 +13,8 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ -#ifndef NPCM7XX_CLK_H -#define NPCM7XX_CLK_H +#ifndef NPCM_CLK_H +#define NPCM_CLK_H #include "exec/memory.h" #include "hw/clock.h" @@ -177,4 +177,4 @@ struct NPCM7xxCLKState { #define TYPE_NPCM7XX_CLK "npcm7xx-clk" OBJECT_DECLARE_SIMPLE_TYPE(NPCM7xxCLKState, NPCM7XX_CLK) -#endif /* NPCM7XX_CLK_H */ +#endif /* NPCM_CLK_H */ From ca6d6a94f450f5fba92626704a5758cf4bb4a210 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:02 -0800 Subject: [PATCH 0174/1179] hw/misc: Move NPCM7XX CLK to NPCM CLK A lot of NPCM7XX and NPCM8XX CLK modules share the same code, this commit moves the NPCM7XX CLK to NPCM CLK for these properties. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-12-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_clk.c | 106 +++++++++++++++++++++---------------- hw/misc/trace-events | 6 +-- include/hw/arm/npcm7xx.h | 2 +- include/hw/misc/npcm_clk.h | 22 ++++---- 4 files changed, 76 insertions(+), 60 deletions(-) diff --git a/hw/misc/npcm_clk.c b/hw/misc/npcm_clk.c index 2bcb731099ed..0ecf0df3bbfc 100644 --- a/hw/misc/npcm_clk.c +++ b/hw/misc/npcm_clk.c @@ -198,7 +198,7 @@ static NPCM7xxClockPLL find_pll_by_reg(enum NPCM7xxCLKRegisters reg) } } -static void npcm7xx_clk_update_all_plls(NPCM7xxCLKState *clk) +static void npcm7xx_clk_update_all_plls(NPCMCLKState *clk) { int i; @@ -207,7 +207,7 @@ static void npcm7xx_clk_update_all_plls(NPCM7xxCLKState *clk) } } -static void npcm7xx_clk_update_all_sels(NPCM7xxCLKState *clk) +static void npcm7xx_clk_update_all_sels(NPCMCLKState *clk) { int i; @@ -216,7 +216,7 @@ static void npcm7xx_clk_update_all_sels(NPCM7xxCLKState *clk) } } -static void npcm7xx_clk_update_all_dividers(NPCM7xxCLKState *clk) +static void npcm7xx_clk_update_all_dividers(NPCMCLKState *clk) { int i; @@ -225,7 +225,7 @@ static void npcm7xx_clk_update_all_dividers(NPCM7xxCLKState *clk) } } -static void npcm7xx_clk_update_all_clocks(NPCM7xxCLKState *clk) +static void npcm7xx_clk_update_all_clocks(NPCMCLKState *clk) { clock_update_hz(clk->clkref, NPCM7XX_CLOCK_REF_HZ); npcm7xx_clk_update_all_plls(clk); @@ -635,7 +635,7 @@ static void npcm7xx_clk_divider_init(Object *obj) } static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll, - NPCM7xxCLKState *clk, const PLLInitInfo *init_info) + NPCMCLKState *clk, const PLLInitInfo *init_info) { pll->name = init_info->name; pll->clk = clk; @@ -647,7 +647,7 @@ static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll, } static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel, - NPCM7xxCLKState *clk, const SELInitInfo *init_info) + NPCMCLKState *clk, const SELInitInfo *init_info) { int input_size = init_info->input_size; @@ -664,7 +664,7 @@ static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel, } static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div, - NPCM7xxCLKState *clk, const DividerInitInfo *init_info) + NPCMCLKState *clk, const DividerInitInfo *init_info) { div->name = init_info->name; div->clk = clk; @@ -683,7 +683,7 @@ static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div, } } -static Clock *npcm7xx_get_clock(NPCM7xxCLKState *clk, ClockSrcType type, +static Clock *npcm7xx_get_clock(NPCMCLKState *clk, ClockSrcType type, int index) { switch (type) { @@ -700,7 +700,7 @@ static Clock *npcm7xx_get_clock(NPCM7xxCLKState *clk, ClockSrcType type, } } -static void npcm7xx_connect_clocks(NPCM7xxCLKState *clk) +static void npcm7xx_connect_clocks(NPCMCLKState *clk) { int i, j; Clock *src; @@ -724,10 +724,10 @@ static void npcm7xx_connect_clocks(NPCM7xxCLKState *clk) } } -static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) +static uint64_t npcm_clk_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); - NPCM7xxCLKState *s = opaque; + NPCMCLKState *s = opaque; int64_t now_ns; uint32_t value = 0; @@ -766,19 +766,19 @@ static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) break; }; - trace_npcm7xx_clk_read(offset, value); + trace_npcm_clk_read(offset, value); return value; } -static void npcm7xx_clk_write(void *opaque, hwaddr offset, +static void npcm_clk_write(void *opaque, hwaddr offset, uint64_t v, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); - NPCM7xxCLKState *s = opaque; + NPCMCLKState *s = opaque; uint32_t value = v; - trace_npcm7xx_clk_write(offset, value); + trace_npcm_clk_write(offset, value); if (reg >= NPCM7XX_CLK_NR_REGS) { qemu_log_mask(LOG_GUEST_ERROR, @@ -842,7 +842,7 @@ static void npcm7xx_clk_write(void *opaque, hwaddr offset, static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n, int level) { - NPCM7xxCLKState *clk = NPCM7XX_CLK(opaque); + NPCMCLKState *clk = NPCM_CLK(opaque); uint32_t rcr; g_assert(n >= 0 && n <= NPCM7XX_NR_WATCHDOGS); @@ -856,9 +856,9 @@ static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n, } } -static const struct MemoryRegionOps npcm7xx_clk_ops = { - .read = npcm7xx_clk_read, - .write = npcm7xx_clk_write, +static const struct MemoryRegionOps npcm_clk_ops = { + .read = npcm_clk_read, + .write = npcm_clk_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, @@ -867,9 +867,9 @@ static const struct MemoryRegionOps npcm7xx_clk_ops = { }, }; -static void npcm7xx_clk_enter_reset(Object *obj, ResetType type) +static void npcm_clk_enter_reset(Object *obj, ResetType type) { - NPCM7xxCLKState *s = NPCM7XX_CLK(obj); + NPCMCLKState *s = NPCM_CLK(obj); QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values)); @@ -882,7 +882,7 @@ static void npcm7xx_clk_enter_reset(Object *obj, ResetType type) */ } -static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s) +static void npcm7xx_clk_init_clock_hierarchy(NPCMCLKState *s) { int i; @@ -918,19 +918,19 @@ static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s) clock_update_hz(s->clkref, NPCM7XX_CLOCK_REF_HZ); } -static void npcm7xx_clk_init(Object *obj) +static void npcm_clk_init(Object *obj) { - NPCM7xxCLKState *s = NPCM7XX_CLK(obj); + NPCMCLKState *s = NPCM_CLK(obj); - memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s, - TYPE_NPCM7XX_CLK, 4 * KiB); + memory_region_init_io(&s->iomem, obj, &npcm_clk_ops, s, + TYPE_NPCM_CLK, 4 * KiB); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); } -static int npcm7xx_clk_post_load(void *opaque, int version_id) +static int npcm_clk_post_load(void *opaque, int version_id) { if (version_id >= 1) { - NPCM7xxCLKState *clk = opaque; + NPCMCLKState *clk = opaque; npcm7xx_clk_update_all_clocks(clk); } @@ -938,10 +938,10 @@ static int npcm7xx_clk_post_load(void *opaque, int version_id) return 0; } -static void npcm7xx_clk_realize(DeviceState *dev, Error **errp) +static void npcm_clk_realize(DeviceState *dev, Error **errp) { int i; - NPCM7xxCLKState *s = NPCM7XX_CLK(dev); + NPCMCLKState *s = NPCM_CLK(dev); qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset, NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS); @@ -996,15 +996,15 @@ static const VMStateDescription vmstate_npcm7xx_clk_divider = { }, }; -static const VMStateDescription vmstate_npcm7xx_clk = { - .name = "npcm7xx-clk", - .version_id = 1, - .minimum_version_id = 1, - .post_load = npcm7xx_clk_post_load, +static const VMStateDescription vmstate_npcm_clk = { + .name = "npcm-clk", + .version_id = 2, + .minimum_version_id = 2, + .post_load = npcm_clk_post_load, .fields = (const VMStateField[]) { - VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS), - VMSTATE_INT64(ref_ns, NPCM7xxCLKState), - VMSTATE_CLOCK(clkref, NPCM7xxCLKState), + VMSTATE_UINT32_ARRAY(regs, NPCMCLKState, NPCM_CLK_MAX_NR_REGS), + VMSTATE_INT64(ref_ns, NPCMCLKState), + VMSTATE_CLOCK(clkref, NPCMCLKState), VMSTATE_END_OF_LIST(), }, }; @@ -1033,17 +1033,23 @@ static void npcm7xx_clk_divider_class_init(ObjectClass *klass, void *data) dc->vmsd = &vmstate_npcm7xx_clk_divider; } -static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) +static void npcm_clk_class_init(ObjectClass *klass, void *data) { ResettableClass *rc = RESETTABLE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END > NPCM7XX_CLK_NR_REGS); + dc->vmsd = &vmstate_npcm_clk; + dc->realize = npcm_clk_realize; + rc->phases.enter = npcm_clk_enter_reset; +} + +static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END > NPCM_CLK_MAX_NR_REGS); + QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END != NPCM7XX_CLK_NR_REGS); dc->desc = "NPCM7xx Clock Control Registers"; - dc->vmsd = &vmstate_npcm7xx_clk; - dc->realize = npcm7xx_clk_realize; - rc->phases.enter = npcm7xx_clk_enter_reset; } static const TypeInfo npcm7xx_clk_pll_info = { @@ -1070,11 +1076,18 @@ static const TypeInfo npcm7xx_clk_divider_info = { .class_init = npcm7xx_clk_divider_class_init, }; +static const TypeInfo npcm_clk_info = { + .name = TYPE_NPCM_CLK, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCMCLKState), + .instance_init = npcm_clk_init, + .class_init = npcm_clk_class_init, + .abstract = true, +}; + static const TypeInfo npcm7xx_clk_info = { .name = TYPE_NPCM7XX_CLK, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(NPCM7xxCLKState), - .instance_init = npcm7xx_clk_init, + .parent = TYPE_NPCM_CLK, .class_init = npcm7xx_clk_class_init, }; @@ -1083,6 +1096,7 @@ static void npcm7xx_clk_register_type(void) type_register_static(&npcm7xx_clk_pll_info); type_register_static(&npcm7xx_clk_sel_info); type_register_static(&npcm7xx_clk_divider_info); + type_register_static(&npcm_clk_info); type_register_static(&npcm7xx_clk_info); } type_init(npcm7xx_clk_register_type); diff --git a/hw/misc/trace-events b/hw/misc/trace-events index f25dbd6030c3..4383808d7ad0 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -130,9 +130,9 @@ mos6522_set_sr_int(void) "set sr_int" mos6522_write(uint64_t addr, const char *name, uint64_t val) "reg=0x%"PRIx64 " [%s] val=0x%"PRIx64 mos6522_read(uint64_t addr, const char *name, unsigned val) "reg=0x%"PRIx64 " [%s] val=0x%x" -# npcm7xx_clk.c -npcm7xx_clk_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 -npcm7xx_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +# npcm_clk.c +npcm_clk_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +npcm_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 # npcm_gcr.c npcm_gcr_read(uint64_t offset, uint64_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx64 diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index e80fd91f204c..56536565b748 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -90,7 +90,7 @@ struct NPCM7xxState { MemoryRegion *dram; NPCMGCRState gcr; - NPCM7xxCLKState clk; + NPCMCLKState clk; NPCM7xxTimerCtrlState tim[3]; NPCM7xxADCState adc; NPCM7xxPWMState pwm[NPCM7XX_NR_PWM_MODULES]; diff --git a/include/hw/misc/npcm_clk.h b/include/hw/misc/npcm_clk.h index 0aef81e10c83..db03b46a52ba 100644 --- a/include/hw/misc/npcm_clk.h +++ b/include/hw/misc/npcm_clk.h @@ -20,11 +20,12 @@ #include "hw/clock.h" #include "hw/sysbus.h" +#define NPCM7XX_CLK_NR_REGS (0x70 / sizeof(uint32_t)) /* - * Number of registers in our device state structure. Don't change this without - * incrementing the version_id in the vmstate. + * Number of maximum registers in NPCM device state structure. Don't change + * this without incrementing the version_id in the vmstate. */ -#define NPCM7XX_CLK_NR_REGS (0x70 / sizeof(uint32_t)) +#define NPCM_CLK_MAX_NR_REGS NPCM7XX_CLK_NR_REGS #define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in" @@ -80,7 +81,7 @@ typedef enum NPCM7xxClockDivider { NPCM7XX_CLOCK_NR_DIVIDERS, } NPCM7xxClockConverter; -typedef struct NPCM7xxCLKState NPCM7xxCLKState; +typedef struct NPCMCLKState NPCMCLKState; /** * struct NPCM7xxClockPLLState - A PLL module in CLK module. @@ -94,7 +95,7 @@ typedef struct NPCM7xxClockPLLState { DeviceState parent; const char *name; - NPCM7xxCLKState *clk; + NPCMCLKState *clk; Clock *clock_in; Clock *clock_out; @@ -115,7 +116,7 @@ typedef struct NPCM7xxClockSELState { DeviceState parent; const char *name; - NPCM7xxCLKState *clk; + NPCMCLKState *clk; uint8_t input_size; Clock *clock_in[NPCM7XX_CLK_SEL_MAX_INPUT]; Clock *clock_out; @@ -140,7 +141,7 @@ typedef struct NPCM7xxClockDividerState { DeviceState parent; const char *name; - NPCM7xxCLKState *clk; + NPCMCLKState *clk; Clock *clock_in; Clock *clock_out; @@ -155,7 +156,7 @@ typedef struct NPCM7xxClockDividerState { }; } NPCM7xxClockDividerState; -struct NPCM7xxCLKState { +struct NPCMCLKState { SysBusDevice parent; MemoryRegion iomem; @@ -165,7 +166,7 @@ struct NPCM7xxCLKState { NPCM7xxClockSELState sels[NPCM7XX_CLOCK_NR_SELS]; NPCM7xxClockDividerState dividers[NPCM7XX_CLOCK_NR_DIVIDERS]; - uint32_t regs[NPCM7XX_CLK_NR_REGS]; + uint32_t regs[NPCM_CLK_MAX_NR_REGS]; /* Time reference for SECCNT and CNTR25M, initialized by power on reset */ int64_t ref_ns; @@ -174,7 +175,8 @@ struct NPCM7xxCLKState { Clock *clkref; }; +#define TYPE_NPCM_CLK "npcm-clk" +OBJECT_DECLARE_SIMPLE_TYPE(NPCMCLKState, NPCM_CLK) #define TYPE_NPCM7XX_CLK "npcm7xx-clk" -OBJECT_DECLARE_SIMPLE_TYPE(NPCM7xxCLKState, NPCM7XX_CLK) #endif /* NPCM_CLK_H */ From cf76c4e174e128397763c4ec98b0aa3345bab3be Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:03 -0800 Subject: [PATCH 0175/1179] hw/misc: Add nr_regs and cold_reset_values to NPCM CLK These 2 values are different between NPCM7XX and NPCM8XX CLKs. So we add them to the class and assign different values to them. Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-13-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_clk.c | 18 ++++++++++++------ hw/misc/npcm_gcr.c | 2 ++ include/hw/misc/npcm_clk.h | 9 ++++++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/hw/misc/npcm_clk.c b/hw/misc/npcm_clk.c index 0ecf0df3bbfc..78144b14e37b 100644 --- a/hw/misc/npcm_clk.c +++ b/hw/misc/npcm_clk.c @@ -81,7 +81,7 @@ enum NPCM7xxCLKRegisters { * All are loaded on power-up reset. CLKENx and SWRSTR should also be loaded on * core domain reset, but this reset type is not yet supported by QEMU. */ -static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = { +static const uint32_t npcm7xx_cold_reset_values[NPCM7XX_CLK_NR_REGS] = { [NPCM7XX_CLK_CLKEN1] = 0xffffffff, [NPCM7XX_CLK_CLKSEL] = 0x004aaaaa, [NPCM7XX_CLK_CLKDIV1] = 0x5413f855, @@ -728,10 +728,11 @@ static uint64_t npcm_clk_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); NPCMCLKState *s = opaque; + NPCMCLKClass *c = NPCM_CLK_GET_CLASS(s); int64_t now_ns; uint32_t value = 0; - if (reg >= NPCM7XX_CLK_NR_REGS) { + if (reg >= c->nr_regs) { qemu_log_mask(LOG_GUEST_ERROR, "%s: offset 0x%04" HWADDR_PRIx " out of range\n", __func__, offset); @@ -776,11 +777,12 @@ static void npcm_clk_write(void *opaque, hwaddr offset, { uint32_t reg = offset / sizeof(uint32_t); NPCMCLKState *s = opaque; + NPCMCLKClass *c = NPCM_CLK_GET_CLASS(s); uint32_t value = v; trace_npcm_clk_write(offset, value); - if (reg >= NPCM7XX_CLK_NR_REGS) { + if (reg >= c->nr_regs) { qemu_log_mask(LOG_GUEST_ERROR, "%s: offset 0x%04" HWADDR_PRIx " out of range\n", __func__, offset); @@ -870,10 +872,10 @@ static const struct MemoryRegionOps npcm_clk_ops = { static void npcm_clk_enter_reset(Object *obj, ResetType type) { NPCMCLKState *s = NPCM_CLK(obj); + NPCMCLKClass *c = NPCM_CLK_GET_CLASS(s); - QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values)); - - memcpy(s->regs, cold_reset_values, sizeof(cold_reset_values)); + g_assert(sizeof(s->regs) >= c->nr_regs * sizeof(uint32_t)); + memcpy(s->regs, c->cold_reset_values, sizeof(s->regs)); s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); npcm7xx_clk_update_all_clocks(s); /* @@ -1045,11 +1047,14 @@ static void npcm_clk_class_init(ObjectClass *klass, void *data) static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) { + NPCMCLKClass *c = NPCM_CLK_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END > NPCM_CLK_MAX_NR_REGS); QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END != NPCM7XX_CLK_NR_REGS); dc->desc = "NPCM7xx Clock Control Registers"; + c->nr_regs = NPCM7XX_CLK_NR_REGS; + c->cold_reset_values = npcm7xx_cold_reset_values; } static const TypeInfo npcm7xx_clk_pll_info = { @@ -1081,6 +1086,7 @@ static const TypeInfo npcm_clk_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(NPCMCLKState), .instance_init = npcm_clk_init, + .class_size = sizeof(NPCMCLKClass), .class_init = npcm_clk_class_init, .abstract = true, }; diff --git a/hw/misc/npcm_gcr.c b/hw/misc/npcm_gcr.c index ec16ea620eb2..4e8ce2cb89dd 100644 --- a/hw/misc/npcm_gcr.c +++ b/hw/misc/npcm_gcr.c @@ -215,6 +215,7 @@ static uint64_t npcm_gcr_read(void *opaque, hwaddr offset, unsigned size) break; case 8: + g_assert(!(reg & 1)); value = deposit64(s->regs[reg], 32, 32, s->regs[reg + 1]); break; @@ -270,6 +271,7 @@ static void npcm_gcr_write(void *opaque, hwaddr offset, break; case 8: + g_assert(!(reg & 1)); s->regs[reg] = value; s->regs[reg + 1] = extract64(v, 32, 32); break; diff --git a/include/hw/misc/npcm_clk.h b/include/hw/misc/npcm_clk.h index db03b46a52ba..f47614ac8da4 100644 --- a/include/hw/misc/npcm_clk.h +++ b/include/hw/misc/npcm_clk.h @@ -175,8 +175,15 @@ struct NPCMCLKState { Clock *clkref; }; +typedef struct NPCMCLKClass { + SysBusDeviceClass parent; + + size_t nr_regs; + const uint32_t *cold_reset_values; +} NPCMCLKClass; + #define TYPE_NPCM_CLK "npcm-clk" -OBJECT_DECLARE_SIMPLE_TYPE(NPCMCLKState, NPCM_CLK) +OBJECT_DECLARE_TYPE(NPCMCLKState, NPCMCLKClass, NPCM_CLK) #define TYPE_NPCM7XX_CLK "npcm7xx-clk" #endif /* NPCM_CLK_H */ From 4e67d50deaf3132f392266e7251cf7ce17be8fa4 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:04 -0800 Subject: [PATCH 0176/1179] hw/misc: Support NPCM8XX CLK Module Registers NPCM8XX adds a few new registers and have a different set of reset values to the CLK modules. This patch supports them. This patch doesn't support the new clock values generated by these registers. Currently no modules use these new clock values so they are not necessary at this point. Implementation of these clocks might be required when implementing these modules. Reviewed-by: Titus Rwantare Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-14-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/misc/npcm_clk.c | 117 +++++++++++++++++++++++++++++++++++-- include/hw/misc/npcm_clk.h | 10 +++- 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/hw/misc/npcm_clk.c b/hw/misc/npcm_clk.c index 78144b14e37b..d1f29759d594 100644 --- a/hw/misc/npcm_clk.c +++ b/hw/misc/npcm_clk.c @@ -1,5 +1,5 @@ /* - * Nuvoton NPCM7xx Clock Control Registers. + * Nuvoton NPCM7xx/8xx Clock Control Registers. * * Copyright 2020 Google LLC * @@ -72,7 +72,57 @@ enum NPCM7xxCLKRegisters { NPCM7XX_CLK_AHBCKFI, NPCM7XX_CLK_SECCNT, NPCM7XX_CLK_CNTR25M, - NPCM7XX_CLK_REGS_END, +}; + +enum NPCM8xxCLKRegisters { + NPCM8XX_CLK_CLKEN1, + NPCM8XX_CLK_CLKSEL, + NPCM8XX_CLK_CLKDIV1, + NPCM8XX_CLK_PLLCON0, + NPCM8XX_CLK_PLLCON1, + NPCM8XX_CLK_SWRSTR, + NPCM8XX_CLK_IPSRST1 = 0x20 / sizeof(uint32_t), + NPCM8XX_CLK_IPSRST2, + NPCM8XX_CLK_CLKEN2, + NPCM8XX_CLK_CLKDIV2, + NPCM8XX_CLK_CLKEN3, + NPCM8XX_CLK_IPSRST3, + NPCM8XX_CLK_WD0RCR, + NPCM8XX_CLK_WD1RCR, + NPCM8XX_CLK_WD2RCR, + NPCM8XX_CLK_SWRSTC1, + NPCM8XX_CLK_SWRSTC2, + NPCM8XX_CLK_SWRSTC3, + NPCM8XX_CLK_TIPRSTC, + NPCM8XX_CLK_PLLCON2, + NPCM8XX_CLK_CLKDIV3, + NPCM8XX_CLK_CORSTC, + NPCM8XX_CLK_PLLCONG, + NPCM8XX_CLK_AHBCKFI, + NPCM8XX_CLK_SECCNT, + NPCM8XX_CLK_CNTR25M, + /* Registers unique to NPCM8XX SoC */ + NPCM8XX_CLK_CLKEN4, + NPCM8XX_CLK_IPSRST4, + NPCM8XX_CLK_BUSTO, + NPCM8XX_CLK_CLKDIV4, + NPCM8XX_CLK_WD0RCRB, + NPCM8XX_CLK_WD1RCRB, + NPCM8XX_CLK_WD2RCRB, + NPCM8XX_CLK_SWRSTC1B, + NPCM8XX_CLK_SWRSTC2B, + NPCM8XX_CLK_SWRSTC3B, + NPCM8XX_CLK_TIPRSTCB, + NPCM8XX_CLK_CORSTCB, + NPCM8XX_CLK_IPSRSTDIS1, + NPCM8XX_CLK_IPSRSTDIS2, + NPCM8XX_CLK_IPSRSTDIS3, + NPCM8XX_CLK_IPSRSTDIS4, + NPCM8XX_CLK_CLKENDIS1, + NPCM8XX_CLK_CLKENDIS2, + NPCM8XX_CLK_CLKENDIS3, + NPCM8XX_CLK_CLKENDIS4, + NPCM8XX_CLK_THRTL_CNT, }; /* @@ -103,6 +153,46 @@ static const uint32_t npcm7xx_cold_reset_values[NPCM7XX_CLK_NR_REGS] = { [NPCM7XX_CLK_AHBCKFI] = 0x000000c8, }; +/* + * These reset values were taken from version 0.92 of the NPCM8xx data sheet. + */ +static const uint32_t npcm8xx_cold_reset_values[NPCM8XX_CLK_NR_REGS] = { + [NPCM8XX_CLK_CLKEN1] = 0xffffffff, + [NPCM8XX_CLK_CLKSEL] = 0x154aaaaa, + [NPCM8XX_CLK_CLKDIV1] = 0x5413f855, + [NPCM8XX_CLK_PLLCON0] = 0x00222101 | PLLCON_LOKI, + [NPCM8XX_CLK_PLLCON1] = 0x00202101 | PLLCON_LOKI, + [NPCM8XX_CLK_IPSRST1] = 0x00001000, + [NPCM8XX_CLK_IPSRST2] = 0x80000000, + [NPCM8XX_CLK_CLKEN2] = 0xffffffff, + [NPCM8XX_CLK_CLKDIV2] = 0xaa4f8f9f, + [NPCM8XX_CLK_CLKEN3] = 0xffffffff, + [NPCM8XX_CLK_IPSRST3] = 0x03000000, + [NPCM8XX_CLK_WD0RCR] = 0xffffffff, + [NPCM8XX_CLK_WD1RCR] = 0xffffffff, + [NPCM8XX_CLK_WD2RCR] = 0xffffffff, + [NPCM8XX_CLK_SWRSTC1] = 0x00000003, + [NPCM8XX_CLK_SWRSTC2] = 0x00000001, + [NPCM8XX_CLK_SWRSTC3] = 0x00000001, + [NPCM8XX_CLK_TIPRSTC] = 0x00000001, + [NPCM8XX_CLK_PLLCON2] = 0x00c02105 | PLLCON_LOKI, + [NPCM8XX_CLK_CLKDIV3] = 0x00009100, + [NPCM8XX_CLK_CORSTC] = 0x04000003, + [NPCM8XX_CLK_PLLCONG] = 0x01228606 | PLLCON_LOKI, + [NPCM8XX_CLK_AHBCKFI] = 0x000000c8, + [NPCM8XX_CLK_CLKEN4] = 0xffffffff, + [NPCM8XX_CLK_CLKDIV4] = 0x70009000, + [NPCM8XX_CLK_IPSRST4] = 0x02000000, + [NPCM8XX_CLK_WD0RCRB] = 0xfffffe71, + [NPCM8XX_CLK_WD1RCRB] = 0xfffffe71, + [NPCM8XX_CLK_WD2RCRB] = 0xfffffe71, + [NPCM8XX_CLK_SWRSTC1B] = 0xfffffe71, + [NPCM8XX_CLK_SWRSTC2B] = 0xfffffe71, + [NPCM8XX_CLK_SWRSTC3B] = 0xfffffe71, + [NPCM8XX_CLK_TIPRSTCB] = 0xfffffe71, + [NPCM8XX_CLK_CORSTCB] = 0xfffffe71, +}; + /* The number of watchdogs that can trigger a reset. */ #define NPCM7XX_NR_WATCHDOGS (3) @@ -1000,8 +1090,8 @@ static const VMStateDescription vmstate_npcm7xx_clk_divider = { static const VMStateDescription vmstate_npcm_clk = { .name = "npcm-clk", - .version_id = 2, - .minimum_version_id = 2, + .version_id = 3, + .minimum_version_id = 3, .post_load = npcm_clk_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, NPCMCLKState, NPCM_CLK_MAX_NR_REGS), @@ -1050,13 +1140,21 @@ static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) NPCMCLKClass *c = NPCM_CLK_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END > NPCM_CLK_MAX_NR_REGS); - QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END != NPCM7XX_CLK_NR_REGS); dc->desc = "NPCM7xx Clock Control Registers"; c->nr_regs = NPCM7XX_CLK_NR_REGS; c->cold_reset_values = npcm7xx_cold_reset_values; } +static void npcm8xx_clk_class_init(ObjectClass *klass, void *data) +{ + NPCMCLKClass *c = NPCM_CLK_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM8xx Clock Control Registers"; + c->nr_regs = NPCM8XX_CLK_NR_REGS; + c->cold_reset_values = npcm8xx_cold_reset_values; +} + static const TypeInfo npcm7xx_clk_pll_info = { .name = TYPE_NPCM7XX_CLOCK_PLL, .parent = TYPE_DEVICE, @@ -1097,6 +1195,12 @@ static const TypeInfo npcm7xx_clk_info = { .class_init = npcm7xx_clk_class_init, }; +static const TypeInfo npcm8xx_clk_info = { + .name = TYPE_NPCM8XX_CLK, + .parent = TYPE_NPCM_CLK, + .class_init = npcm8xx_clk_class_init, +}; + static void npcm7xx_clk_register_type(void) { type_register_static(&npcm7xx_clk_pll_info); @@ -1104,5 +1208,6 @@ static void npcm7xx_clk_register_type(void) type_register_static(&npcm7xx_clk_divider_info); type_register_static(&npcm_clk_info); type_register_static(&npcm7xx_clk_info); + type_register_static(&npcm8xx_clk_info); } type_init(npcm7xx_clk_register_type); diff --git a/include/hw/misc/npcm_clk.h b/include/hw/misc/npcm_clk.h index f47614ac8da4..8fa1e14bddfb 100644 --- a/include/hw/misc/npcm_clk.h +++ b/include/hw/misc/npcm_clk.h @@ -1,5 +1,5 @@ /* - * Nuvoton NPCM7xx Clock Control Registers. + * Nuvoton NPCM7xx/8xx Clock Control Registers. * * Copyright 2020 Google LLC * @@ -21,11 +21,12 @@ #include "hw/sysbus.h" #define NPCM7XX_CLK_NR_REGS (0x70 / sizeof(uint32_t)) +#define NPCM8XX_CLK_NR_REGS (0xc4 / sizeof(uint32_t)) /* * Number of maximum registers in NPCM device state structure. Don't change * this without incrementing the version_id in the vmstate. */ -#define NPCM_CLK_MAX_NR_REGS NPCM7XX_CLK_NR_REGS +#define NPCM_CLK_MAX_NR_REGS NPCM8XX_CLK_NR_REGS #define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in" @@ -162,6 +163,10 @@ struct NPCMCLKState { MemoryRegion iomem; /* Clock converters */ + /* + * TODO: Implement unique clock converters for NPCM8xx. + * NPCM8xx adds a few more clock outputs. + */ NPCM7xxClockPLLState plls[NPCM7XX_CLOCK_NR_PLLS]; NPCM7xxClockSELState sels[NPCM7XX_CLOCK_NR_SELS]; NPCM7xxClockDividerState dividers[NPCM7XX_CLOCK_NR_DIVIDERS]; @@ -185,5 +190,6 @@ typedef struct NPCMCLKClass { #define TYPE_NPCM_CLK "npcm-clk" OBJECT_DECLARE_TYPE(NPCMCLKState, NPCMCLKClass, NPCM_CLK) #define TYPE_NPCM7XX_CLK "npcm7xx-clk" +#define TYPE_NPCM8XX_CLK "npcm8xx-clk" #endif /* NPCM_CLK_H */ From 3d107d36f99c6eec8c8b3cb6bc387b0fcc69d7d9 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:05 -0800 Subject: [PATCH 0177/1179] hw/net: Add NPCM8XX PCS Module The PCS exists in NPCM8XX's GMAC1 and is used to control the SGMII PHY. This implementation contains all the default registers and the soft reset feature that are required to load the Linux kernel driver. Further features have not been implemented yet. Signed-off-by: Hao Wu Reviewed-by: Peter Maydell Message-id: 20250219184609.1839281-15-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/net/meson.build | 1 + hw/net/npcm_pcs.c | 410 ++++++++++++++++++++++++++++++++++++++ hw/net/trace-events | 4 +- include/hw/net/npcm_pcs.h | 42 ++++ 4 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 hw/net/npcm_pcs.c create mode 100644 include/hw/net/npcm_pcs.h diff --git a/hw/net/meson.build b/hw/net/meson.build index 3bb5d749a835..e6759e26ca6c 100644 --- a/hw/net/meson.build +++ b/hw/net/meson.build @@ -40,6 +40,7 @@ system_ss.add(when: 'CONFIG_SUNHME', if_true: files('sunhme.c')) system_ss.add(when: 'CONFIG_FTGMAC100', if_true: files('ftgmac100.c')) system_ss.add(when: 'CONFIG_SUNGEM', if_true: files('sungem.c')) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_emc.c', 'npcm_gmac.c')) +system_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm_pcs.c')) system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_fec.c')) specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_llan.c')) diff --git a/hw/net/npcm_pcs.c b/hw/net/npcm_pcs.c new file mode 100644 index 000000000000..ce5034e23435 --- /dev/null +++ b/hw/net/npcm_pcs.c @@ -0,0 +1,410 @@ +/* + * Nuvoton NPCM8xx PCS Module + * + * Copyright 2022 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. + */ + +/* + * Disclaimer: + * Currently we only implemented the default values of the registers and + * the soft reset feature. These are required to boot up the GMAC module + * in Linux kernel for NPCM845 boards. Other functionalities are not modeled. + */ + +#include "qemu/osdep.h" + +#include "exec/hwaddr.h" +#include "hw/registerfields.h" +#include "hw/net/npcm_pcs.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/units.h" +#include "trace.h" + +#define NPCM_PCS_IND_AC_BA 0x1fe +#define NPCM_PCS_IND_SR_CTL 0x1e00 +#define NPCM_PCS_IND_SR_MII 0x1f00 +#define NPCM_PCS_IND_SR_TIM 0x1f07 +#define NPCM_PCS_IND_VR_MII 0x1f80 + +REG16(NPCM_PCS_SR_CTL_ID1, 0x08) +REG16(NPCM_PCS_SR_CTL_ID2, 0x0a) +REG16(NPCM_PCS_SR_CTL_STS, 0x10) + +REG16(NPCM_PCS_SR_MII_CTRL, 0x00) +REG16(NPCM_PCS_SR_MII_STS, 0x02) +REG16(NPCM_PCS_SR_MII_DEV_ID1, 0x04) +REG16(NPCM_PCS_SR_MII_DEV_ID2, 0x06) +REG16(NPCM_PCS_SR_MII_AN_ADV, 0x08) +REG16(NPCM_PCS_SR_MII_LP_BABL, 0x0a) +REG16(NPCM_PCS_SR_MII_AN_EXPN, 0x0c) +REG16(NPCM_PCS_SR_MII_EXT_STS, 0x1e) + +REG16(NPCM_PCS_SR_TIM_SYNC_ABL, 0x10) +REG16(NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_LWR, 0x12) +REG16(NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_UPR, 0x14) +REG16(NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_LWR, 0x16) +REG16(NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_UPR, 0x18) +REG16(NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_LWR, 0x1a) +REG16(NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_UPR, 0x1c) +REG16(NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_LWR, 0x1e) +REG16(NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_UPR, 0x20) + +REG16(NPCM_PCS_VR_MII_MMD_DIG_CTRL1, 0x000) +REG16(NPCM_PCS_VR_MII_AN_CTRL, 0x002) +REG16(NPCM_PCS_VR_MII_AN_INTR_STS, 0x004) +REG16(NPCM_PCS_VR_MII_TC, 0x006) +REG16(NPCM_PCS_VR_MII_DBG_CTRL, 0x00a) +REG16(NPCM_PCS_VR_MII_EEE_MCTRL0, 0x00c) +REG16(NPCM_PCS_VR_MII_EEE_TXTIMER, 0x010) +REG16(NPCM_PCS_VR_MII_EEE_RXTIMER, 0x012) +REG16(NPCM_PCS_VR_MII_LINK_TIMER_CTRL, 0x014) +REG16(NPCM_PCS_VR_MII_EEE_MCTRL1, 0x016) +REG16(NPCM_PCS_VR_MII_DIG_STS, 0x020) +REG16(NPCM_PCS_VR_MII_ICG_ERRCNT1, 0x022) +REG16(NPCM_PCS_VR_MII_MISC_STS, 0x030) +REG16(NPCM_PCS_VR_MII_RX_LSTS, 0x040) +REG16(NPCM_PCS_VR_MII_MP_TX_BSTCTRL0, 0x070) +REG16(NPCM_PCS_VR_MII_MP_TX_LVLCTRL0, 0x074) +REG16(NPCM_PCS_VR_MII_MP_TX_GENCTRL0, 0x07a) +REG16(NPCM_PCS_VR_MII_MP_TX_GENCTRL1, 0x07c) +REG16(NPCM_PCS_VR_MII_MP_TX_STS, 0x090) +REG16(NPCM_PCS_VR_MII_MP_RX_GENCTRL0, 0x0b0) +REG16(NPCM_PCS_VR_MII_MP_RX_GENCTRL1, 0x0b2) +REG16(NPCM_PCS_VR_MII_MP_RX_LOS_CTRL0, 0x0ba) +REG16(NPCM_PCS_VR_MII_MP_MPLL_CTRL0, 0x0f0) +REG16(NPCM_PCS_VR_MII_MP_MPLL_CTRL1, 0x0f2) +REG16(NPCM_PCS_VR_MII_MP_MPLL_STS, 0x110) +REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL2, 0x126) +REG16(NPCM_PCS_VR_MII_MP_LVL_CTRL, 0x130) +REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL0, 0x132) +REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL1, 0x134) +REG16(NPCM_PCS_VR_MII_DIG_CTRL2, 0x1c2) +REG16(NPCM_PCS_VR_MII_DIG_ERRCNT_SEL, 0x1c4) + +/* Register Fields */ +#define NPCM_PCS_SR_MII_CTRL_RST BIT(15) + +static const uint16_t npcm_pcs_sr_ctl_cold_reset_values[NPCM_PCS_NR_SR_CTLS] = { + [R_NPCM_PCS_SR_CTL_ID1] = 0x699e, + [R_NPCM_PCS_SR_CTL_STS] = 0x8000, +}; + +static const uint16_t npcm_pcs_sr_mii_cold_reset_values[NPCM_PCS_NR_SR_MIIS] = { + [R_NPCM_PCS_SR_MII_CTRL] = 0x1140, + [R_NPCM_PCS_SR_MII_STS] = 0x0109, + [R_NPCM_PCS_SR_MII_DEV_ID1] = 0x699e, + [R_NPCM_PCS_SR_MII_DEV_ID2] = 0xced0, + [R_NPCM_PCS_SR_MII_AN_ADV] = 0x0020, + [R_NPCM_PCS_SR_MII_EXT_STS] = 0xc000, +}; + +static const uint16_t npcm_pcs_sr_tim_cold_reset_values[NPCM_PCS_NR_SR_TIMS] = { + [R_NPCM_PCS_SR_TIM_SYNC_ABL] = 0x0003, + [R_NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_LWR] = 0x0038, + [R_NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_LWR] = 0x0038, + [R_NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_LWR] = 0x0058, + [R_NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_LWR] = 0x0048, +}; + +static const uint16_t npcm_pcs_vr_mii_cold_reset_values[NPCM_PCS_NR_VR_MIIS] = { + [R_NPCM_PCS_VR_MII_MMD_DIG_CTRL1] = 0x2400, + [R_NPCM_PCS_VR_MII_AN_INTR_STS] = 0x000a, + [R_NPCM_PCS_VR_MII_EEE_MCTRL0] = 0x899c, + [R_NPCM_PCS_VR_MII_DIG_STS] = 0x0010, + [R_NPCM_PCS_VR_MII_MP_TX_BSTCTRL0] = 0x000a, + [R_NPCM_PCS_VR_MII_MP_TX_LVLCTRL0] = 0x007f, + [R_NPCM_PCS_VR_MII_MP_TX_GENCTRL0] = 0x0001, + [R_NPCM_PCS_VR_MII_MP_RX_GENCTRL0] = 0x0100, + [R_NPCM_PCS_VR_MII_MP_RX_GENCTRL1] = 0x1100, + [R_NPCM_PCS_VR_MII_MP_RX_LOS_CTRL0] = 0x000e, + [R_NPCM_PCS_VR_MII_MP_MPLL_CTRL0] = 0x0100, + [R_NPCM_PCS_VR_MII_MP_MPLL_CTRL1] = 0x0032, + [R_NPCM_PCS_VR_MII_MP_MPLL_STS] = 0x0001, + [R_NPCM_PCS_VR_MII_MP_LVL_CTRL] = 0x0019, +}; + +static void npcm_pcs_soft_reset(NPCMPCSState *s) +{ + memcpy(s->sr_ctl, npcm_pcs_sr_ctl_cold_reset_values, + NPCM_PCS_NR_SR_CTLS * sizeof(uint16_t)); + memcpy(s->sr_mii, npcm_pcs_sr_mii_cold_reset_values, + NPCM_PCS_NR_SR_MIIS * sizeof(uint16_t)); + memcpy(s->sr_tim, npcm_pcs_sr_tim_cold_reset_values, + NPCM_PCS_NR_SR_TIMS * sizeof(uint16_t)); + memcpy(s->vr_mii, npcm_pcs_vr_mii_cold_reset_values, + NPCM_PCS_NR_VR_MIIS * sizeof(uint16_t)); +} + +static uint16_t npcm_pcs_read_sr_ctl(NPCMPCSState *s, hwaddr offset) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_SR_CTLS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SR_CTL read offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return 0; + } + + return s->sr_ctl[regno]; +} + +static uint16_t npcm_pcs_read_sr_mii(NPCMPCSState *s, hwaddr offset) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_SR_MIIS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SR_MII read offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return 0; + } + + return s->sr_mii[regno]; +} + +static uint16_t npcm_pcs_read_sr_tim(NPCMPCSState *s, hwaddr offset) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_SR_TIMS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SR_TIM read offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return 0; + } + + return s->sr_tim[regno]; +} + +static uint16_t npcm_pcs_read_vr_mii(NPCMPCSState *s, hwaddr offset) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_VR_MIIS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: VR_MII read offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return 0; + } + + return s->vr_mii[regno]; +} + +static void npcm_pcs_write_sr_ctl(NPCMPCSState *s, hwaddr offset, uint16_t v) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_SR_CTLS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SR_CTL write offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return; + } + + s->sr_ctl[regno] = v; +} + +static void npcm_pcs_write_sr_mii(NPCMPCSState *s, hwaddr offset, uint16_t v) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_SR_MIIS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SR_MII write offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return; + } + + s->sr_mii[regno] = v; + + if ((offset == A_NPCM_PCS_SR_MII_CTRL) && (v & NPCM_PCS_SR_MII_CTRL_RST)) { + /* Trigger a soft reset */ + npcm_pcs_soft_reset(s); + } +} + +static void npcm_pcs_write_sr_tim(NPCMPCSState *s, hwaddr offset, uint16_t v) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_SR_TIMS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SR_TIM write offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return; + } + + s->sr_tim[regno] = v; +} + +static void npcm_pcs_write_vr_mii(NPCMPCSState *s, hwaddr offset, uint16_t v) +{ + hwaddr regno = offset / sizeof(uint16_t); + + if (regno >= NPCM_PCS_NR_VR_MIIS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: VR_MII write offset 0x%04" HWADDR_PRIx + " is out of range.\n", + DEVICE(s)->canonical_path, offset); + return; + } + + s->vr_mii[regno] = v; +} + +static uint64_t npcm_pcs_read(void *opaque, hwaddr offset, unsigned size) +{ + NPCMPCSState *s = opaque; + uint16_t v = 0; + + if (offset == NPCM_PCS_IND_AC_BA) { + v = s->indirect_access_base; + } else { + switch (s->indirect_access_base) { + case NPCM_PCS_IND_SR_CTL: + v = npcm_pcs_read_sr_ctl(s, offset); + break; + + case NPCM_PCS_IND_SR_MII: + v = npcm_pcs_read_sr_mii(s, offset); + break; + + case NPCM_PCS_IND_SR_TIM: + v = npcm_pcs_read_sr_tim(s, offset); + break; + + case NPCM_PCS_IND_VR_MII: + v = npcm_pcs_read_vr_mii(s, offset); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Read with invalid indirect address base: 0x%" + PRIx16 "\n", DEVICE(s)->canonical_path, + s->indirect_access_base); + } + } + + trace_npcm_pcs_reg_read(DEVICE(s)->canonical_path, s->indirect_access_base, + offset, v); + return v; +} + +static void npcm_pcs_write(void *opaque, hwaddr offset, + uint64_t v, unsigned size) +{ + NPCMPCSState *s = opaque; + + trace_npcm_pcs_reg_write(DEVICE(s)->canonical_path, s->indirect_access_base, + offset, v); + if (offset == NPCM_PCS_IND_AC_BA) { + s->indirect_access_base = v; + } else { + switch (s->indirect_access_base) { + case NPCM_PCS_IND_SR_CTL: + npcm_pcs_write_sr_ctl(s, offset, v); + break; + + case NPCM_PCS_IND_SR_MII: + npcm_pcs_write_sr_mii(s, offset, v); + break; + + case NPCM_PCS_IND_SR_TIM: + npcm_pcs_write_sr_tim(s, offset, v); + break; + + case NPCM_PCS_IND_VR_MII: + npcm_pcs_write_vr_mii(s, offset, v); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Write with invalid indirect address base: 0x%02" + PRIx16 "\n", DEVICE(s)->canonical_path, + s->indirect_access_base); + } + } +} + +static void npcm_pcs_enter_reset(Object *obj, ResetType type) +{ + NPCMPCSState *s = NPCM_PCS(obj); + + npcm_pcs_soft_reset(s); +} + +static const struct MemoryRegionOps npcm_pcs_ops = { + .read = npcm_pcs_read, + .write = npcm_pcs_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 2, + .max_access_size = 2, + .unaligned = false, + }, +}; + +static void npcm_pcs_realize(DeviceState *dev, Error **errp) +{ + NPCMPCSState *pcs = NPCM_PCS(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init_io(&pcs->iomem, OBJECT(pcs), &npcm_pcs_ops, pcs, + TYPE_NPCM_PCS, 8 * KiB); + sysbus_init_mmio(sbd, &pcs->iomem); +} + +static const VMStateDescription vmstate_npcm_pcs = { + .name = TYPE_NPCM_PCS, + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT16(indirect_access_base, NPCMPCSState), + VMSTATE_UINT16_ARRAY(sr_ctl, NPCMPCSState, NPCM_PCS_NR_SR_CTLS), + VMSTATE_UINT16_ARRAY(sr_mii, NPCMPCSState, NPCM_PCS_NR_SR_MIIS), + VMSTATE_UINT16_ARRAY(sr_tim, NPCMPCSState, NPCM_PCS_NR_SR_TIMS), + VMSTATE_UINT16_ARRAY(vr_mii, NPCMPCSState, NPCM_PCS_NR_VR_MIIS), + VMSTATE_END_OF_LIST(), + }, +}; + +static void npcm_pcs_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->desc = "NPCM PCS Controller"; + dc->realize = npcm_pcs_realize; + dc->vmsd = &vmstate_npcm_pcs; + rc->phases.enter = npcm_pcs_enter_reset; +} + +static const TypeInfo npcm_pcs_types[] = { + { + .name = TYPE_NPCM_PCS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCMPCSState), + .class_init = npcm_pcs_class_init, + }, +}; +DEFINE_TYPES(npcm_pcs_types) diff --git a/hw/net/trace-events b/hw/net/trace-events index c35bfb2eb8d0..72b69c4a8bb8 100644 --- a/hw/net/trace-events +++ b/hw/net/trace-events @@ -483,8 +483,8 @@ npcm_gmac_packet_tx_desc_data(const char* name, uint32_t tdes0, uint32_t tdes1) npcm_gmac_tx_desc_owner(const char* name, uint32_t desc_addr) "%s: TX Descriptor @0x%04" PRIX32 " is owned by software" # npcm_pcs.c -npcm_pcs_reg_read(const char *name, uint16_t indirect_access_baes, uint64_t offset, uint16_t value) "%s: IND: 0x%02" PRIx16 " offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 -npcm_pcs_reg_write(const char *name, uint16_t indirect_access_baes, uint64_t offset, uint16_t value) "%s: IND: 0x%02" PRIx16 " offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 +npcm_pcs_reg_read(const char *name, uint16_t indirect_access_base, uint64_t offset, uint16_t value) "%s: IND: 0x%02" PRIx16 " offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 +npcm_pcs_reg_write(const char *name, uint16_t indirect_access_base, uint64_t offset, uint16_t value) "%s: IND: 0x%02" PRIx16 " offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 # dp8398x.c dp8393x_raise_irq(int isr) "raise irq, isr is 0x%04x" diff --git a/include/hw/net/npcm_pcs.h b/include/hw/net/npcm_pcs.h new file mode 100644 index 000000000000..d5c481ad70d2 --- /dev/null +++ b/include/hw/net/npcm_pcs.h @@ -0,0 +1,42 @@ +/* + * Nuvoton NPCM8xx PCS Module + * + * Copyright 2022 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 NPCM_PCS_H +#define NPCM_PCS_H + +#include "hw/sysbus.h" + +#define NPCM_PCS_NR_SR_CTLS (0x12 / sizeof(uint16_t)) +#define NPCM_PCS_NR_SR_MIIS (0x20 / sizeof(uint16_t)) +#define NPCM_PCS_NR_SR_TIMS (0x22 / sizeof(uint16_t)) +#define NPCM_PCS_NR_VR_MIIS (0x1c6 / sizeof(uint16_t)) + +struct NPCMPCSState { + SysBusDevice parent; + + MemoryRegion iomem; + + uint16_t indirect_access_base; + uint16_t sr_ctl[NPCM_PCS_NR_SR_CTLS]; + uint16_t sr_mii[NPCM_PCS_NR_SR_MIIS]; + uint16_t sr_tim[NPCM_PCS_NR_SR_TIMS]; + uint16_t vr_mii[NPCM_PCS_NR_VR_MIIS]; +}; + +#define TYPE_NPCM_PCS "npcm-pcs" +OBJECT_DECLARE_SIMPLE_TYPE(NPCMPCSState, NPCM_PCS) + +#endif /* NPCM_PCS_H */ From ae0c4d1a12900fdb1d853fe0505e4ba96d4bffef Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:06 -0800 Subject: [PATCH 0178/1179] hw/arm: Add NPCM8XX SoC Signed-off-by: Hao Wu Reviewed-by: Peter Maydell Message-id: 20250219184609.1839281-16-wuhaotsh@google.com Signed-off-by: Peter Maydell --- configs/devices/aarch64-softmmu/default.mak | 1 + hw/arm/Kconfig | 13 + hw/arm/meson.build | 1 + hw/arm/npcm8xx.c | 805 ++++++++++++++++++++ include/hw/arm/npcm8xx.h | 106 +++ 5 files changed, 926 insertions(+) create mode 100644 hw/arm/npcm8xx.c create mode 100644 include/hw/arm/npcm8xx.h diff --git a/configs/devices/aarch64-softmmu/default.mak b/configs/devices/aarch64-softmmu/default.mak index f82a04c27d1b..93f4022ad62e 100644 --- a/configs/devices/aarch64-softmmu/default.mak +++ b/configs/devices/aarch64-softmmu/default.mak @@ -8,3 +8,4 @@ include ../arm-softmmu/default.mak # CONFIG_XLNX_ZYNQMP_ARM=n # CONFIG_XLNX_VERSAL=n # CONFIG_SBSA_REF=n +# CONFIG_NPCM8XX=n diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 7eab3914d4be..504841ccab40 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -481,6 +481,19 @@ config NPCM7XX select PCA954X select USB_OHCI_SYSBUS +config NPCM8XX + bool + default y + depends on TCG && AARCH64 + select ARM_GIC + select SMBUS + select PL310 # cache controller + select NPCM7XX + select SERIAL + select SSI + select UNIMP + + config FSL_IMX25 bool default y diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 490234b3b847..d7813c089c50 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -12,6 +12,7 @@ arm_ss.add(when: 'CONFIG_MUSICPAL', if_true: files('musicpal.c')) arm_ss.add(when: 'CONFIG_NETDUINOPLUS2', if_true: files('netduinoplus2.c')) arm_ss.add(when: 'CONFIG_OLIMEX_STM32_H405', if_true: files('olimex-stm32-h405.c')) arm_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx.c', 'npcm7xx_boards.c')) +arm_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm8xx.c')) arm_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c')) arm_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c')) arm_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c')) diff --git a/hw/arm/npcm8xx.c b/hw/arm/npcm8xx.c new file mode 100644 index 000000000000..f182accc47cf --- /dev/null +++ b/hw/arm/npcm8xx.c @@ -0,0 +1,805 @@ +/* + * Nuvoton NPCM8xx SoC family. + * + * Copyright 2022 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/boards.h" +#include "hw/arm/boot.h" +#include "hw/arm/bsa.h" +#include "hw/arm/npcm8xx.h" +#include "hw/char/serial-mm.h" +#include "hw/intc/arm_gic.h" +#include "hw/loader.h" +#include "hw/misc/unimp.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/units.h" +#include "system/system.h" + +/* + * This covers the whole MMIO space. We'll use this to catch any MMIO accesses + * that aren't handled by a device. + */ +#define NPCM8XX_MMIO_BA 0x80000000 +#define NPCM8XX_MMIO_SZ 0x7ffd0000 + +/* OTP fuse array */ +#define NPCM8XX_OTP_BA 0xf0189000 + +/* GIC Distributor */ +#define NPCM8XX_GICD_BA 0xdfff9000 +#define NPCM8XX_GICC_BA 0xdfffa000 + +/* Core system modules. */ +#define NPCM8XX_CPUP_BA 0xf03fe000 +#define NPCM8XX_GCR_BA 0xf0800000 +#define NPCM8XX_CLK_BA 0xf0801000 +#define NPCM8XX_MC_BA 0xf0824000 +#define NPCM8XX_RNG_BA 0xf000b000 + +/* ADC Module */ +#define NPCM8XX_ADC_BA 0xf000c000 + +/* Internal AHB SRAM */ +#define NPCM8XX_RAM3_BA 0xc0008000 +#define NPCM8XX_RAM3_SZ (4 * KiB) + +/* Memory blocks at the end of the address space */ +#define NPCM8XX_RAM2_BA 0xfffb0000 +#define NPCM8XX_RAM2_SZ (256 * KiB) +#define NPCM8XX_ROM_BA 0xffff0100 +#define NPCM8XX_ROM_SZ (64 * KiB) + +/* SDHCI Modules */ +#define NPCM8XX_MMC_BA 0xf0842000 + +/* Run PLL1 at 1600 MHz */ +#define NPCM8XX_PLLCON1_FIXUP_VAL 0x00402101 +/* Run the CPU from PLL1 and UART from PLL2 */ +#define NPCM8XX_CLKSEL_FIXUP_VAL 0x004aaba9 + +/* Clock configuration values to be fixed up when bypassing bootloader */ + +/* + * Interrupt lines going into the GIC. This does not include internal Cortex-A35 + * interrupts. + */ +enum NPCM8xxInterrupt { + NPCM8XX_ADC_IRQ = 0, + NPCM8XX_PECI_IRQ = 6, + NPCM8XX_KCS_HIB_IRQ = 9, + NPCM8XX_MMC_IRQ = 26, + NPCM8XX_TIMER0_IRQ = 32, /* Timer Module 0 */ + NPCM8XX_TIMER1_IRQ, + NPCM8XX_TIMER2_IRQ, + NPCM8XX_TIMER3_IRQ, + NPCM8XX_TIMER4_IRQ, + NPCM8XX_TIMER5_IRQ, /* Timer Module 1 */ + NPCM8XX_TIMER6_IRQ, + NPCM8XX_TIMER7_IRQ, + NPCM8XX_TIMER8_IRQ, + NPCM8XX_TIMER9_IRQ, + NPCM8XX_TIMER10_IRQ, /* Timer Module 2 */ + NPCM8XX_TIMER11_IRQ, + NPCM8XX_TIMER12_IRQ, + NPCM8XX_TIMER13_IRQ, + NPCM8XX_TIMER14_IRQ, + NPCM8XX_WDG0_IRQ = 47, /* Timer Module 0 Watchdog */ + NPCM8XX_WDG1_IRQ, /* Timer Module 1 Watchdog */ + NPCM8XX_WDG2_IRQ, /* Timer Module 2 Watchdog */ + NPCM8XX_EHCI1_IRQ = 61, + NPCM8XX_OHCI1_IRQ, + NPCM8XX_EHCI2_IRQ, + NPCM8XX_OHCI2_IRQ, + NPCM8XX_PWM0_IRQ = 93, /* PWM module 0 */ + NPCM8XX_PWM1_IRQ, /* PWM module 1 */ + NPCM8XX_MFT0_IRQ = 96, /* MFT module 0 */ + NPCM8XX_MFT1_IRQ, /* MFT module 1 */ + NPCM8XX_MFT2_IRQ, /* MFT module 2 */ + NPCM8XX_MFT3_IRQ, /* MFT module 3 */ + NPCM8XX_MFT4_IRQ, /* MFT module 4 */ + NPCM8XX_MFT5_IRQ, /* MFT module 5 */ + NPCM8XX_MFT6_IRQ, /* MFT module 6 */ + NPCM8XX_MFT7_IRQ, /* MFT module 7 */ + NPCM8XX_PCI_MBOX1_IRQ = 105, + NPCM8XX_PCI_MBOX2_IRQ, + NPCM8XX_GPIO0_IRQ = 116, + NPCM8XX_GPIO1_IRQ, + NPCM8XX_GPIO2_IRQ, + NPCM8XX_GPIO3_IRQ, + NPCM8XX_GPIO4_IRQ, + NPCM8XX_GPIO5_IRQ, + NPCM8XX_GPIO6_IRQ, + NPCM8XX_GPIO7_IRQ, + NPCM8XX_SMBUS0_IRQ = 128, + NPCM8XX_SMBUS1_IRQ, + NPCM8XX_SMBUS2_IRQ, + NPCM8XX_SMBUS3_IRQ, + NPCM8XX_SMBUS4_IRQ, + NPCM8XX_SMBUS5_IRQ, + NPCM8XX_SMBUS6_IRQ, + NPCM8XX_SMBUS7_IRQ, + NPCM8XX_SMBUS8_IRQ, + NPCM8XX_SMBUS9_IRQ, + NPCM8XX_SMBUS10_IRQ, + NPCM8XX_SMBUS11_IRQ, + NPCM8XX_SMBUS12_IRQ, + NPCM8XX_SMBUS13_IRQ, + NPCM8XX_SMBUS14_IRQ, + NPCM8XX_SMBUS15_IRQ, + NPCM8XX_SMBUS16_IRQ, + NPCM8XX_SMBUS17_IRQ, + NPCM8XX_SMBUS18_IRQ, + NPCM8XX_SMBUS19_IRQ, + NPCM8XX_SMBUS20_IRQ, + NPCM8XX_SMBUS21_IRQ, + NPCM8XX_SMBUS22_IRQ, + NPCM8XX_SMBUS23_IRQ, + NPCM8XX_SMBUS24_IRQ, + NPCM8XX_SMBUS25_IRQ, + NPCM8XX_SMBUS26_IRQ, + NPCM8XX_UART0_IRQ = 192, + NPCM8XX_UART1_IRQ, + NPCM8XX_UART2_IRQ, + NPCM8XX_UART3_IRQ, + NPCM8XX_UART4_IRQ, + NPCM8XX_UART5_IRQ, + NPCM8XX_UART6_IRQ, +}; + +/* Total number of GIC interrupts, including internal Cortex-A35 interrupts. */ +#define NPCM8XX_NUM_IRQ (288) +#define NPCM8XX_PPI_BASE(cpu) \ + ((NPCM8XX_NUM_IRQ - GIC_INTERNAL) + (cpu) * GIC_INTERNAL) + +/* Register base address for each Timer Module */ +static const hwaddr npcm8xx_tim_addr[] = { + 0xf0008000, + 0xf0009000, + 0xf000a000, +}; + +/* Register base address for each 16550 UART */ +static const hwaddr npcm8xx_uart_addr[] = { + 0xf0000000, + 0xf0001000, + 0xf0002000, + 0xf0003000, + 0xf0004000, + 0xf0005000, + 0xf0006000, +}; + +/* Direct memory-mapped access to SPI0 CS0-1. */ +static const hwaddr npcm8xx_fiu0_flash_addr[] = { + 0x80000000, /* CS0 */ + 0x88000000, /* CS1 */ +}; + +/* Direct memory-mapped access to SPI1 CS0-3. */ +static const hwaddr npcm8xx_fiu1_flash_addr[] = { + 0x90000000, /* CS0 */ + 0x91000000, /* CS1 */ + 0x92000000, /* CS2 */ + 0x93000000, /* CS3 */ +}; + +/* Direct memory-mapped access to SPI3 CS0-3. */ +static const hwaddr npcm8xx_fiu3_flash_addr[] = { + 0xa0000000, /* CS0 */ + 0xa8000000, /* CS1 */ + 0xb0000000, /* CS2 */ + 0xb8000000, /* CS3 */ +}; + +/* Register base address for each PWM Module */ +static const hwaddr npcm8xx_pwm_addr[] = { + 0xf0103000, + 0xf0104000, + 0xf0105000, +}; + +/* Register base address for each MFT Module */ +static const hwaddr npcm8xx_mft_addr[] = { + 0xf0180000, + 0xf0181000, + 0xf0182000, + 0xf0183000, + 0xf0184000, + 0xf0185000, + 0xf0186000, + 0xf0187000, +}; + +/* Direct memory-mapped access to each SMBus Module. */ +static const hwaddr npcm8xx_smbus_addr[] = { + 0xf0080000, + 0xf0081000, + 0xf0082000, + 0xf0083000, + 0xf0084000, + 0xf0085000, + 0xf0086000, + 0xf0087000, + 0xf0088000, + 0xf0089000, + 0xf008a000, + 0xf008b000, + 0xf008c000, + 0xf008d000, + 0xf008e000, + 0xf008f000, + 0xfff00000, + 0xfff01000, + 0xfff02000, + 0xfff03000, + 0xfff04000, + 0xfff05000, + 0xfff06000, + 0xfff07000, + 0xfff08000, + 0xfff09000, + 0xfff0a000, +}; + +/* Register base address for each USB host EHCI registers */ +static const hwaddr npcm8xx_ehci_addr[] = { + 0xf0828100, + 0xf082a100, +}; + +/* Register base address for each USB host OHCI registers */ +static const hwaddr npcm8xx_ohci_addr[] = { + 0xf0829000, + 0xf082b000, +}; + +static const struct { + hwaddr regs_addr; + uint32_t reset_pu; + uint32_t reset_pd; + uint32_t reset_osrc; + uint32_t reset_odsc; +} npcm8xx_gpio[] = { + { + .regs_addr = 0xf0010000, + .reset_pu = 0x00000300, + .reset_pd = 0x000f0000, + }, { + .regs_addr = 0xf0011000, + .reset_pu = 0xe0fefe01, + .reset_pd = 0x07000000, + }, { + .regs_addr = 0xf0012000, + .reset_pu = 0xc00fffff, + .reset_pd = 0x3ff00000, + }, { + .regs_addr = 0xf0013000, + .reset_pd = 0x00003000, + }, { + .regs_addr = 0xf0014000, + .reset_pu = 0xffff0000, + }, { + .regs_addr = 0xf0015000, + .reset_pu = 0xff8387fe, + .reset_pd = 0x007c0001, + .reset_osrc = 0x08000000, + }, { + .regs_addr = 0xf0016000, + .reset_pu = 0x00000801, + .reset_pd = 0x00000302, + }, { + .regs_addr = 0xf0017000, + .reset_pu = 0x000002ff, + .reset_pd = 0x00000c00, + }, +}; + +static const struct { + const char *name; + hwaddr regs_addr; + int cs_count; + const hwaddr *flash_addr; + size_t flash_size; +} npcm8xx_fiu[] = { + { + .name = "fiu0", + .regs_addr = 0xfb000000, + .cs_count = ARRAY_SIZE(npcm8xx_fiu0_flash_addr), + .flash_addr = npcm8xx_fiu0_flash_addr, + .flash_size = 128 * MiB, + }, + { + .name = "fiu1", + .regs_addr = 0xfb002000, + .cs_count = ARRAY_SIZE(npcm8xx_fiu1_flash_addr), + .flash_addr = npcm8xx_fiu1_flash_addr, + .flash_size = 16 * MiB, + }, { + .name = "fiu3", + .regs_addr = 0xc0000000, + .cs_count = ARRAY_SIZE(npcm8xx_fiu3_flash_addr), + .flash_addr = npcm8xx_fiu3_flash_addr, + .flash_size = 128 * MiB, + }, +}; + +static struct arm_boot_info npcm8xx_binfo = { + .loader_start = NPCM8XX_LOADER_START, + .smp_loader_start = NPCM8XX_SMP_LOADER_START, + .smp_bootreg_addr = NPCM8XX_SMP_BOOTREG_ADDR, + .gic_cpu_if_addr = NPCM8XX_GICC_BA, + .secure_boot = false, + .board_id = -1, + .board_setup_addr = NPCM8XX_BOARD_SETUP_ADDR, +}; + +void npcm8xx_load_kernel(MachineState *machine, NPCM8xxState *soc) +{ + npcm8xx_binfo.ram_size = machine->ram_size; + + arm_load_kernel(&soc->cpu[0], machine, &npcm8xx_binfo); +} + +static void npcm8xx_init_fuses(NPCM8xxState *s) +{ + NPCM8xxClass *nc = NPCM8XX_GET_CLASS(s); + uint32_t value; + + /* + * The initial mask of disabled modules indicates the chip derivative (e.g. + * NPCM750 or NPCM730). + */ + value = cpu_to_le32(nc->disabled_modules); + npcm7xx_otp_array_write(&s->fuse_array, &value, NPCM7XX_FUSE_DERIVATIVE, + sizeof(value)); +} + +static void npcm8xx_write_adc_calibration(NPCM8xxState *s) +{ + /* Both ADC and the fuse array must have realized. */ + QEMU_BUILD_BUG_ON(sizeof(s->adc.calibration_r_values) != 4); + npcm7xx_otp_array_write(&s->fuse_array, s->adc.calibration_r_values, + NPCM7XX_FUSE_ADC_CALIB, sizeof(s->adc.calibration_r_values)); +} + +static qemu_irq npcm8xx_irq(NPCM8xxState *s, int n) +{ + return qdev_get_gpio_in(DEVICE(&s->gic), n); +} + +static void npcm8xx_init(Object *obj) +{ + NPCM8xxState *s = NPCM8XX(obj); + int i; + + object_initialize_child(obj, "cpu-cluster", &s->cpu_cluster, + TYPE_CPU_CLUSTER); + for (i = 0; i < NPCM8XX_MAX_NUM_CPUS; i++) { + object_initialize_child(OBJECT(&s->cpu_cluster), "cpu[*]", &s->cpu[i], + ARM_CPU_TYPE_NAME("cortex-a35")); + } + object_initialize_child(obj, "gic", &s->gic, TYPE_ARM_GIC); + object_initialize_child(obj, "gcr", &s->gcr, TYPE_NPCM8XX_GCR); + object_property_add_alias(obj, "power-on-straps", OBJECT(&s->gcr), + "power-on-straps"); + object_initialize_child(obj, "clk", &s->clk, TYPE_NPCM8XX_CLK); + object_initialize_child(obj, "otp", &s->fuse_array, + TYPE_NPCM7XX_FUSE_ARRAY); + object_initialize_child(obj, "mc", &s->mc, TYPE_NPCM7XX_MC); + object_initialize_child(obj, "rng", &s->rng, TYPE_NPCM7XX_RNG); + object_initialize_child(obj, "adc", &s->adc, TYPE_NPCM7XX_ADC); + + for (i = 0; i < ARRAY_SIZE(s->tim); i++) { + object_initialize_child(obj, "tim[*]", &s->tim[i], TYPE_NPCM7XX_TIMER); + } + + for (i = 0; i < ARRAY_SIZE(s->gpio); i++) { + 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); + DEVICE(&s->smbus[i])->id = g_strdup_printf("smbus[%d]", i); + } + + for (i = 0; i < ARRAY_SIZE(s->ehci); i++) { + object_initialize_child(obj, "ehci[*]", &s->ehci[i], TYPE_NPCM7XX_EHCI); + } + for (i = 0; i < ARRAY_SIZE(s->ohci); i++) { + object_initialize_child(obj, "ohci[*]", &s->ohci[i], TYPE_SYSBUS_OHCI); + } + + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_fiu) != ARRAY_SIZE(s->fiu)); + for (i = 0; i < ARRAY_SIZE(s->fiu); i++) { + object_initialize_child(obj, npcm8xx_fiu[i].name, &s->fiu[i], + TYPE_NPCM7XX_FIU); + } + + 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->mft); i++) { + object_initialize_child(obj, "mft[*]", &s->mft[i], TYPE_NPCM7XX_MFT); + } + + object_initialize_child(obj, "mmc", &s->mmc, TYPE_NPCM7XX_SDHCI); +} + +static void npcm8xx_realize(DeviceState *dev, Error **errp) +{ + NPCM8xxState *s = NPCM8XX(dev); + NPCM8xxClass *nc = NPCM8XX_GET_CLASS(s); + int i; + + if (memory_region_size(s->dram) > NPCM8XX_DRAM_SZ) { + error_setg(errp, "%s: NPCM8xx cannot address more than %" PRIu64 + " MiB of DRAM", __func__, NPCM8XX_DRAM_SZ / MiB); + return; + } + + /* CPUs */ + for (i = 0; i < nc->num_cpus; i++) { + object_property_set_int(OBJECT(&s->cpu[i]), "mp-affinity", + arm_build_mp_affinity(i, NPCM8XX_MAX_NUM_CPUS), + &error_abort); + object_property_set_bool(OBJECT(&s->cpu[i]), "reset-hivecs", true, + &error_abort); + object_property_set_int(OBJECT(&s->cpu[i]), "core-count", + nc->num_cpus, &error_abort); + + /* Disable security extensions. */ + object_property_set_bool(OBJECT(&s->cpu[i]), "has_el3", false, + &error_abort); + + if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) { + return; + } + } + + /* ARM GIC for Cortex A35. Can only fail if we pass bad parameters here. */ + object_property_set_uint(OBJECT(&s->gic), "num-cpu", nc->num_cpus, errp); + object_property_set_uint(OBJECT(&s->gic), "num-irq", NPCM8XX_NUM_IRQ, errp); + object_property_set_uint(OBJECT(&s->gic), "revision", 2, errp); + object_property_set_bool(OBJECT(&s->gic), "has-security-extensions", true, + errp); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gic), errp)) { + return; + } + for (i = 0; i < nc->num_cpus; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i, + qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + nc->num_cpus, + qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_FIQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + nc->num_cpus * 2, + qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_VIRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + nc->num_cpus * 3, + qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_VFIQ)); + + qdev_connect_gpio_out(DEVICE(&s->cpu[i]), GTIMER_PHYS, + qdev_get_gpio_in(DEVICE(&s->gic), + NPCM8XX_PPI_BASE(i) + ARCH_TIMER_NS_EL1_IRQ)); + qdev_connect_gpio_out(DEVICE(&s->cpu[i]), GTIMER_VIRT, + qdev_get_gpio_in(DEVICE(&s->gic), + NPCM8XX_PPI_BASE(i) + ARCH_TIMER_VIRT_IRQ)); + qdev_connect_gpio_out(DEVICE(&s->cpu[i]), GTIMER_HYP, + qdev_get_gpio_in(DEVICE(&s->gic), + NPCM8XX_PPI_BASE(i) + ARCH_TIMER_NS_EL2_IRQ)); + qdev_connect_gpio_out(DEVICE(&s->cpu[i]), GTIMER_SEC, + qdev_get_gpio_in(DEVICE(&s->gic), + NPCM8XX_PPI_BASE(i) + ARCH_TIMER_S_EL1_IRQ)); + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 0, NPCM8XX_GICD_BA); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 1, NPCM8XX_GICC_BA); + + /* CPU cluster */ + qdev_prop_set_uint32(DEVICE(&s->cpu_cluster), "cluster-id", 0); + qdev_realize(DEVICE(&s->cpu_cluster), NULL, &error_fatal); + + /* System Global Control Registers (GCR). Can fail due to user input. */ + object_property_set_int(OBJECT(&s->gcr), "disabled-modules", + nc->disabled_modules, &error_abort); + object_property_add_const_link(OBJECT(&s->gcr), "dram-mr", OBJECT(s->dram)); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gcr), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gcr), 0, NPCM8XX_GCR_BA); + + /* Clock Control Registers (CLK). Cannot fail. */ + sysbus_realize(SYS_BUS_DEVICE(&s->clk), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->clk), 0, NPCM8XX_CLK_BA); + + /* OTP fuse strap array. Cannot fail. */ + sysbus_realize(SYS_BUS_DEVICE(&s->fuse_array), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->fuse_array), 0, NPCM8XX_OTP_BA); + npcm8xx_init_fuses(s); + + /* Fake Memory Controller (MC). Cannot fail. */ + sysbus_realize(SYS_BUS_DEVICE(&s->mc), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->mc), 0, NPCM8XX_MC_BA); + + /* ADC Modules. Cannot fail. */ + qdev_connect_clock_in(DEVICE(&s->adc), "clock", qdev_get_clock_out( + DEVICE(&s->clk), "adc-clock")); + sysbus_realize(SYS_BUS_DEVICE(&s->adc), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->adc), 0, NPCM8XX_ADC_BA); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->adc), 0, + npcm8xx_irq(s, NPCM8XX_ADC_IRQ)); + npcm8xx_write_adc_calibration(s); + + /* Timer Modules (TIM). Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_tim_addr) != ARRAY_SIZE(s->tim)); + for (i = 0; i < ARRAY_SIZE(s->tim); i++) { + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->tim[i]); + int first_irq; + int j; + + /* Connect the timer clock. */ + qdev_connect_clock_in(DEVICE(&s->tim[i]), "clock", qdev_get_clock_out( + DEVICE(&s->clk), "timer-clock")); + + sysbus_realize(sbd, &error_abort); + sysbus_mmio_map(sbd, 0, npcm8xx_tim_addr[i]); + + first_irq = NPCM8XX_TIMER0_IRQ + i * NPCM7XX_TIMERS_PER_CTRL; + for (j = 0; j < NPCM7XX_TIMERS_PER_CTRL; j++) { + qemu_irq irq = npcm8xx_irq(s, first_irq + j); + sysbus_connect_irq(sbd, j, irq); + } + + /* IRQ for watchdogs */ + sysbus_connect_irq(sbd, NPCM7XX_TIMERS_PER_CTRL, + npcm8xx_irq(s, NPCM8XX_WDG0_IRQ + i)); + /* GPIO that connects clk module with watchdog */ + qdev_connect_gpio_out_named(DEVICE(&s->tim[i]), + NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 0, + qdev_get_gpio_in_named(DEVICE(&s->clk), + NPCM7XX_WATCHDOG_RESET_GPIO_IN, i)); + } + + /* UART0..6 (16550 compatible) */ + for (i = 0; i < ARRAY_SIZE(npcm8xx_uart_addr); i++) { + serial_mm_init(get_system_memory(), npcm8xx_uart_addr[i], 2, + npcm8xx_irq(s, NPCM8XX_UART0_IRQ + i), 115200, + serial_hd(i), DEVICE_LITTLE_ENDIAN); + } + + /* Random Number Generator. Cannot fail. */ + sysbus_realize(SYS_BUS_DEVICE(&s->rng), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->rng), 0, NPCM8XX_RNG_BA); + + /* GPIO modules. Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_gpio) != ARRAY_SIZE(s->gpio)); + for (i = 0; i < ARRAY_SIZE(s->gpio); i++) { + Object *obj = OBJECT(&s->gpio[i]); + + object_property_set_uint(obj, "reset-pullup", + npcm8xx_gpio[i].reset_pu, &error_abort); + object_property_set_uint(obj, "reset-pulldown", + npcm8xx_gpio[i].reset_pd, &error_abort); + object_property_set_uint(obj, "reset-osrc", + npcm8xx_gpio[i].reset_osrc, &error_abort); + object_property_set_uint(obj, "reset-odsc", + npcm8xx_gpio[i].reset_odsc, &error_abort); + sysbus_realize(SYS_BUS_DEVICE(obj), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(obj), 0, npcm8xx_gpio[i].regs_addr); + sysbus_connect_irq(SYS_BUS_DEVICE(obj), 0, + npcm8xx_irq(s, NPCM8XX_GPIO0_IRQ + i)); + } + + /* SMBus modules. Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_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, npcm8xx_smbus_addr[i]); + sysbus_connect_irq(SYS_BUS_DEVICE(obj), 0, + npcm8xx_irq(s, NPCM8XX_SMBUS0_IRQ + i)); + } + + /* USB Host */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(s->ohci) != ARRAY_SIZE(s->ehci)); + for (i = 0; i < ARRAY_SIZE(s->ehci); i++) { + object_property_set_bool(OBJECT(&s->ehci[i]), "companion-enable", true, + &error_abort); + sysbus_realize(SYS_BUS_DEVICE(&s->ehci[i]), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ehci[i]), 0, npcm8xx_ehci_addr[i]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->ehci[i]), 0, + npcm8xx_irq(s, NPCM8XX_EHCI1_IRQ + 2 * i)); + } + for (i = 0; i < ARRAY_SIZE(s->ohci); i++) { + object_property_set_str(OBJECT(&s->ohci[i]), "masterbus", "usb-bus.0", + &error_abort); + object_property_set_uint(OBJECT(&s->ohci[i]), "num-ports", 1, + &error_abort); + object_property_set_uint(OBJECT(&s->ohci[i]), "firstport", i, + &error_abort); + sysbus_realize(SYS_BUS_DEVICE(&s->ohci[i]), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ohci[i]), 0, npcm8xx_ohci_addr[i]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->ohci[i]), 0, + npcm8xx_irq(s, NPCM8XX_OHCI1_IRQ + 2 * i)); + } + + /* PWM Modules. Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_pwm_addr) != ARRAY_SIZE(s->pwm)); + for (i = 0; i < ARRAY_SIZE(s->pwm); i++) { + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->pwm[i]); + + qdev_connect_clock_in(DEVICE(&s->pwm[i]), "clock", qdev_get_clock_out( + DEVICE(&s->clk), "apb3-clock")); + sysbus_realize(sbd, &error_abort); + sysbus_mmio_map(sbd, 0, npcm8xx_pwm_addr[i]); + sysbus_connect_irq(sbd, i, npcm8xx_irq(s, NPCM8XX_PWM0_IRQ + i)); + } + + /* MFT Modules. Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_mft_addr) != ARRAY_SIZE(s->mft)); + for (i = 0; i < ARRAY_SIZE(s->mft); i++) { + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->mft[i]); + + qdev_connect_clock_in(DEVICE(&s->mft[i]), "clock-in", + qdev_get_clock_out(DEVICE(&s->clk), + "apb4-clock")); + sysbus_realize(sbd, &error_abort); + sysbus_mmio_map(sbd, 0, npcm8xx_mft_addr[i]); + sysbus_connect_irq(sbd, 0, npcm8xx_irq(s, NPCM8XX_MFT0_IRQ + i)); + } + + /* + * Flash Interface Unit (FIU). Can fail if incorrect number of chip selects + * specified, but this is a programming error. + */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_fiu) != ARRAY_SIZE(s->fiu)); + for (i = 0; i < ARRAY_SIZE(s->fiu); i++) { + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->fiu[i]); + int j; + + object_property_set_int(OBJECT(sbd), "cs-count", + npcm8xx_fiu[i].cs_count, &error_abort); + object_property_set_int(OBJECT(sbd), "flash-size", + npcm8xx_fiu[i].flash_size, &error_abort); + sysbus_realize(sbd, &error_abort); + + sysbus_mmio_map(sbd, 0, npcm8xx_fiu[i].regs_addr); + for (j = 0; j < npcm8xx_fiu[i].cs_count; j++) { + sysbus_mmio_map(sbd, j + 1, npcm8xx_fiu[i].flash_addr[j]); + } + } + + /* RAM2 (SRAM) */ + memory_region_init_ram(&s->sram, OBJECT(dev), "ram2", + NPCM8XX_RAM2_SZ, &error_abort); + memory_region_add_subregion(get_system_memory(), NPCM8XX_RAM2_BA, &s->sram); + + /* RAM3 (SRAM) */ + memory_region_init_ram(&s->ram3, OBJECT(dev), "ram3", + NPCM8XX_RAM3_SZ, &error_abort); + memory_region_add_subregion(get_system_memory(), NPCM8XX_RAM3_BA, &s->ram3); + + /* Internal ROM */ + memory_region_init_rom(&s->irom, OBJECT(dev), "irom", NPCM8XX_ROM_SZ, + &error_abort); + memory_region_add_subregion(get_system_memory(), NPCM8XX_ROM_BA, &s->irom); + + /* SDHCI */ + sysbus_realize(SYS_BUS_DEVICE(&s->mmc), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->mmc), 0, NPCM8XX_MMC_BA); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->mmc), 0, + npcm8xx_irq(s, NPCM8XX_MMC_IRQ)); + + + create_unimplemented_device("npcm8xx.shm", 0xc0001000, 4 * KiB); + create_unimplemented_device("npcm8xx.gicextra", 0xdfffa000, 24 * KiB); + create_unimplemented_device("npcm8xx.vdmx", 0xe0800000, 4 * KiB); + create_unimplemented_device("npcm8xx.pcierc", 0xe1000000, 64 * KiB); + create_unimplemented_device("npcm8xx.rootc", 0xe8000000, 128 * MiB); + create_unimplemented_device("npcm8xx.kcs", 0xf0007000, 4 * KiB); + create_unimplemented_device("npcm8xx.gfxi", 0xf000e000, 4 * KiB); + create_unimplemented_device("npcm8xx.fsw", 0xf000f000, 4 * KiB); + create_unimplemented_device("npcm8xx.bt", 0xf0030000, 4 * KiB); + create_unimplemented_device("npcm8xx.espi", 0xf009f000, 4 * KiB); + create_unimplemented_device("npcm8xx.peci", 0xf0100000, 4 * KiB); + create_unimplemented_device("npcm8xx.siox[1]", 0xf0101000, 4 * KiB); + create_unimplemented_device("npcm8xx.siox[2]", 0xf0102000, 4 * KiB); + create_unimplemented_device("npcm8xx.tmps", 0xf0188000, 4 * KiB); + create_unimplemented_device("npcm8xx.pspi", 0xf0201000, 4 * KiB); + create_unimplemented_device("npcm8xx.viru1", 0xf0204000, 4 * KiB); + create_unimplemented_device("npcm8xx.viru2", 0xf0205000, 4 * KiB); + create_unimplemented_device("npcm8xx.jtm1", 0xf0208000, 4 * KiB); + create_unimplemented_device("npcm8xx.jtm2", 0xf0209000, 4 * KiB); + create_unimplemented_device("npcm8xx.flm0", 0xf0210000, 4 * KiB); + create_unimplemented_device("npcm8xx.flm1", 0xf0211000, 4 * KiB); + create_unimplemented_device("npcm8xx.flm2", 0xf0212000, 4 * KiB); + create_unimplemented_device("npcm8xx.flm3", 0xf0213000, 4 * KiB); + create_unimplemented_device("npcm8xx.ahbpci", 0xf0400000, 1 * MiB); + create_unimplemented_device("npcm8xx.dap", 0xf0500000, 960 * KiB); + create_unimplemented_device("npcm8xx.mcphy", 0xf05f0000, 64 * KiB); + create_unimplemented_device("npcm8xx.pcs", 0xf0780000, 256 * KiB); + create_unimplemented_device("npcm8xx.tsgen", 0xf07fc000, 8 * KiB); + create_unimplemented_device("npcm8xx.gmac1", 0xf0802000, 8 * KiB); + create_unimplemented_device("npcm8xx.gmac2", 0xf0804000, 8 * KiB); + create_unimplemented_device("npcm8xx.gmac3", 0xf0806000, 8 * KiB); + create_unimplemented_device("npcm8xx.gmac4", 0xf0808000, 8 * KiB); + create_unimplemented_device("npcm8xx.copctl", 0xf080c000, 4 * KiB); + create_unimplemented_device("npcm8xx.tipctl", 0xf080d000, 4 * KiB); + create_unimplemented_device("npcm8xx.rst", 0xf080e000, 4 * KiB); + create_unimplemented_device("npcm8xx.vcd", 0xf0810000, 64 * KiB); + create_unimplemented_device("npcm8xx.ece", 0xf0820000, 8 * KiB); + create_unimplemented_device("npcm8xx.vdma", 0xf0822000, 8 * KiB); + create_unimplemented_device("npcm8xx.usbd[0]", 0xf0830000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[1]", 0xf0831000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[2]", 0xf0832000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[3]", 0xf0833000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[4]", 0xf0834000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[5]", 0xf0835000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[6]", 0xf0836000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[7]", 0xf0837000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[8]", 0xf0838000, 4 * KiB); + create_unimplemented_device("npcm8xx.usbd[9]", 0xf0839000, 4 * KiB); + create_unimplemented_device("npcm8xx.pci_mbox1", 0xf0848000, 64 * KiB); + create_unimplemented_device("npcm8xx.gdma0", 0xf0850000, 4 * KiB); + create_unimplemented_device("npcm8xx.gdma1", 0xf0851000, 4 * KiB); + create_unimplemented_device("npcm8xx.gdma2", 0xf0852000, 4 * KiB); + create_unimplemented_device("npcm8xx.aes", 0xf0858000, 4 * KiB); + create_unimplemented_device("npcm8xx.des", 0xf0859000, 4 * KiB); + create_unimplemented_device("npcm8xx.sha", 0xf085a000, 4 * KiB); + create_unimplemented_device("npcm8xx.pci_mbox2", 0xf0868000, 64 * KiB); + create_unimplemented_device("npcm8xx.i3c0", 0xfff10000, 4 * KiB); + create_unimplemented_device("npcm8xx.i3c1", 0xfff11000, 4 * KiB); + create_unimplemented_device("npcm8xx.i3c2", 0xfff12000, 4 * KiB); + create_unimplemented_device("npcm8xx.i3c3", 0xfff13000, 4 * KiB); + create_unimplemented_device("npcm8xx.i3c4", 0xfff14000, 4 * KiB); + create_unimplemented_device("npcm8xx.i3c5", 0xfff15000, 4 * KiB); + create_unimplemented_device("npcm8xx.spixcs0", 0xf8000000, 16 * MiB); + create_unimplemented_device("npcm8xx.spixcs1", 0xf9000000, 16 * MiB); + create_unimplemented_device("npcm8xx.spix", 0xfb001000, 4 * KiB); + create_unimplemented_device("npcm8xx.vect", 0xffff0000, 256); +} + +static const Property npcm8xx_properties[] = { + DEFINE_PROP_LINK("dram-mr", NPCM8xxState, dram, TYPE_MEMORY_REGION, + MemoryRegion *), +}; + +static void npcm8xx_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + NPCM8xxClass *nc = NPCM8XX_CLASS(oc); + + dc->realize = npcm8xx_realize; + dc->user_creatable = false; + nc->disabled_modules = 0x00000000; + nc->num_cpus = NPCM8XX_MAX_NUM_CPUS; + device_class_set_props(dc, npcm8xx_properties); +} + +static const TypeInfo npcm8xx_soc_types[] = { + { + .name = TYPE_NPCM8XX, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NPCM8xxState), + .instance_init = npcm8xx_init, + .class_size = sizeof(NPCM8xxClass), + .class_init = npcm8xx_class_init, + }, +}; + +DEFINE_TYPES(npcm8xx_soc_types); diff --git a/include/hw/arm/npcm8xx.h b/include/hw/arm/npcm8xx.h new file mode 100644 index 000000000000..a32fbb035b5b --- /dev/null +++ b/include/hw/arm/npcm8xx.h @@ -0,0 +1,106 @@ +/* + * Nuvoton NPCM8xx SoC family. + * + * Copyright 2022 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 NPCM8XX_H +#define NPCM8XX_H + +#include "hw/adc/npcm7xx_adc.h" +#include "hw/core/split-irq.h" +#include "hw/cpu/cluster.h" +#include "hw/gpio/npcm7xx_gpio.h" +#include "hw/i2c/npcm7xx_smbus.h" +#include "hw/intc/arm_gic_common.h" +#include "hw/mem/npcm7xx_mc.h" +#include "hw/misc/npcm_clk.h" +#include "hw/misc/npcm_gcr.h" +#include "hw/misc/npcm7xx_mft.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/sd/npcm7xx_sdhci.h" +#include "hw/timer/npcm7xx_timer.h" +#include "hw/ssi/npcm7xx_fiu.h" +#include "hw/usb/hcd-ehci.h" +#include "hw/usb/hcd-ohci.h" +#include "target/arm/cpu.h" + +#define NPCM8XX_MAX_NUM_CPUS (4) + +/* The first half of the address space is reserved for DDR4 DRAM. */ +#define NPCM8XX_DRAM_BA (0x00000000) +#define NPCM8XX_DRAM_SZ (2 * GiB) + +/* Magic addresses for setting up direct kernel booting and SMP boot stubs. */ +#define NPCM8XX_LOADER_START (0x00000000) /* Start of SDRAM */ +#define NPCM8XX_SMP_LOADER_START (0xffff0000) /* Boot ROM */ +#define NPCM8XX_SMP_BOOTREG_ADDR (0xf080013c) /* GCR.SCRPAD */ +#define NPCM8XX_BOARD_SETUP_ADDR (0xffff1000) /* Boot ROM */ + +#define NPCM8XX_NR_PWM_MODULES 3 + +struct NPCM8xxState { + DeviceState parent_obj; + + ARMCPU cpu[NPCM8XX_MAX_NUM_CPUS]; + CPUClusterState cpu_cluster; + GICState gic; + + MemoryRegion sram; + MemoryRegion irom; + MemoryRegion ram3; + MemoryRegion *dram; + + NPCMGCRState gcr; + NPCMCLKState clk; + NPCM7xxTimerCtrlState tim[3]; + NPCM7xxADCState adc; + NPCM7xxPWMState pwm[NPCM8XX_NR_PWM_MODULES]; + NPCM7xxMFTState mft[8]; + NPCM7xxOTPState fuse_array; + NPCM7xxMCState mc; + NPCM7xxRNGState rng; + NPCM7xxGPIOState gpio[8]; + NPCM7xxSMBusState smbus[27]; + EHCISysBusState ehci[2]; + OHCISysBusState ohci[2]; + NPCM7xxFIUState fiu[3]; + NPCM7xxSDHCIState mmc; +}; + +struct NPCM8xxClass { + DeviceClass parent_class; + + /* Bitmask of modules that are permanently disabled on this chip. */ + uint32_t disabled_modules; + /* Number of CPU cores enabled in this SoC class. */ + uint32_t num_cpus; +}; + +#define TYPE_NPCM8XX "npcm8xx" +OBJECT_DECLARE_TYPE(NPCM8xxState, NPCM8xxClass, NPCM8XX) + +/** + * npcm8xx_load_kernel - Loads memory with everything needed to boot + * @machine - The machine containing the SoC to be booted. + * @soc - The SoC containing the CPU to be booted. + * + * This will set up the ARM boot info structure for the specific NPCM8xx + * derivative and call arm_load_kernel() to set up loading of the kernel, etc. + * into memory, if requested by the user. + */ +void npcm8xx_load_kernel(MachineState *machine, NPCM8xxState *soc); + +#endif /* NPCM8XX_H */ From 7e70eb3cad7c835f2ba447306927721101c0788f Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:07 -0800 Subject: [PATCH 0179/1179] hw/arm: Add NPCM845 Evaluation board Reviewed-by: Peter Maydell Signed-off-by: Hao Wu Message-id: 20250219184609.1839281-17-wuhaotsh@google.com Signed-off-by: Peter Maydell --- hw/arm/meson.build | 2 +- hw/arm/npcm8xx_boards.c | 253 +++++++++++++++++++++++++++++++++++++++ include/hw/arm/npcm8xx.h | 21 ++++ 3 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 hw/arm/npcm8xx_boards.c diff --git a/hw/arm/meson.build b/hw/arm/meson.build index d7813c089c50..465c757f976b 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -12,7 +12,7 @@ arm_ss.add(when: 'CONFIG_MUSICPAL', if_true: files('musicpal.c')) arm_ss.add(when: 'CONFIG_NETDUINOPLUS2', if_true: files('netduinoplus2.c')) arm_ss.add(when: 'CONFIG_OLIMEX_STM32_H405', if_true: files('olimex-stm32-h405.c')) arm_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx.c', 'npcm7xx_boards.c')) -arm_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm8xx.c')) +arm_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm8xx.c', 'npcm8xx_boards.c')) arm_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c')) arm_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c')) arm_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c')) diff --git a/hw/arm/npcm8xx_boards.c b/hw/arm/npcm8xx_boards.c new file mode 100644 index 000000000000..19610483f934 --- /dev/null +++ b/hw/arm/npcm8xx_boards.c @@ -0,0 +1,253 @@ +/* + * Machine definitions for boards featuring an NPCM8xx SoC. + * + * Copyright 2021 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 "chardev/char.h" +#include "hw/arm/npcm8xx.h" +#include "hw/core/cpu.h" +#include "hw/loader.h" +#include "hw/qdev-core.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/datadir.h" +#include "qemu/units.h" + +#define NPCM845_EVB_POWER_ON_STRAPS 0x000017ff + +static const char npcm8xx_default_bootrom[] = "npcm8xx_bootrom.bin"; + +static void npcm8xx_load_bootrom(MachineState *machine, NPCM8xxState *soc) +{ + const char *bios_name = machine->firmware ?: npcm8xx_default_bootrom; + g_autofree char *filename = NULL; + int ret; + + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (!filename) { + error_report("Could not find ROM image '%s'", bios_name); + if (!machine->kernel_filename) { + /* We can't boot without a bootrom or a kernel image. */ + exit(1); + } + return; + } + ret = load_image_mr(filename, machine->ram); + if (ret < 0) { + error_report("Failed to load ROM image '%s'", filename); + exit(1); + } +} + +static void npcm8xx_connect_flash(NPCM7xxFIUState *fiu, int cs_no, + const char *flash_type, DriveInfo *dinfo) +{ + DeviceState *flash; + qemu_irq flash_cs; + + flash = qdev_new(flash_type); + if (dinfo) { + qdev_prop_set_drive(flash, "drive", blk_by_legacy_dinfo(dinfo)); + } + qdev_realize_and_unref(flash, BUS(fiu->spi), &error_fatal); + + flash_cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0); + qdev_connect_gpio_out_named(DEVICE(fiu), "cs", cs_no, flash_cs); +} + +static void npcm8xx_connect_dram(NPCM8xxState *soc, MemoryRegion *dram) +{ + memory_region_add_subregion(get_system_memory(), NPCM8XX_DRAM_BA, dram); + + object_property_set_link(OBJECT(soc), "dram-mr", OBJECT(dram), + &error_abort); +} + +static NPCM8xxState *npcm8xx_create_soc(MachineState *machine, + uint32_t hw_straps) +{ + NPCM8xxMachineClass *nmc = NPCM8XX_MACHINE_GET_CLASS(machine); + Object *obj; + + obj = object_new_with_props(nmc->soc_type, OBJECT(machine), "soc", + &error_abort, NULL); + object_property_set_uint(obj, "power-on-straps", hw_straps, &error_abort); + + return NPCM8XX(obj); +} + +static I2CBus *npcm8xx_i2c_get_bus(NPCM8xxState *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 npcm8xx_init_pwm_splitter(NPCM8xxMachine *machine, + NPCM8xxState *soc, const int *fan_counts) +{ + SplitIRQ *splitters = machine->fan_splitter; + + /* + * PWM 0~3 belong to module 0 output 0~3. + * PWM 4~7 belong to module 1 output 0~3. + */ + for (int i = 0; i < NPCM8XX_NR_PWM_MODULES; ++i) { + for (int j = 0; j < NPCM7XX_PWM_PER_MODULE; ++j) { + int splitter_no = i * NPCM7XX_PWM_PER_MODULE + j; + DeviceState *splitter; + + if (fan_counts[splitter_no] < 1) { + continue; + } + object_initialize_child(OBJECT(machine), "fan-splitter[*]", + &splitters[splitter_no], TYPE_SPLIT_IRQ); + splitter = DEVICE(&splitters[splitter_no]); + qdev_prop_set_uint16(splitter, "num-lines", + fan_counts[splitter_no]); + qdev_realize(splitter, NULL, &error_abort); + qdev_connect_gpio_out_named(DEVICE(&soc->pwm[i]), "duty-gpio-out", + j, qdev_get_gpio_in(splitter, 0)); + } + } +} + +static void npcm8xx_connect_pwm_fan(NPCM8xxState *soc, SplitIRQ *splitter, + int fan_no, int output_no) +{ + DeviceState *fan; + int fan_input; + qemu_irq fan_duty_gpio; + + g_assert(fan_no >= 0 && fan_no <= NPCM7XX_MFT_MAX_FAN_INPUT); + /* + * Fan 0~1 belong to module 0 input 0~1. + * Fan 2~3 belong to module 1 input 0~1. + * ... + * Fan 14~15 belong to module 7 input 0~1. + * Fan 16~17 belong to module 0 input 2~3. + * Fan 18~19 belong to module 1 input 2~3. + */ + if (fan_no < 16) { + fan = DEVICE(&soc->mft[fan_no / 2]); + fan_input = fan_no % 2; + } else { + fan = DEVICE(&soc->mft[(fan_no - 16) / 2]); + fan_input = fan_no % 2 + 2; + } + + /* Connect the Fan to PWM module */ + fan_duty_gpio = qdev_get_gpio_in_named(fan, "duty", fan_input); + qdev_connect_gpio_out(DEVICE(splitter), output_no, fan_duty_gpio); +} + +static void npcm845_evb_i2c_init(NPCM8xxState *soc) +{ + /* tmp100 temperature sensor on SVB, tmp105 is compatible */ + i2c_slave_create_simple(npcm8xx_i2c_get_bus(soc, 6), "tmp105", 0x48); +} + +static void npcm845_evb_fan_init(NPCM8xxMachine *machine, NPCM8xxState *soc) +{ + SplitIRQ *splitter = machine->fan_splitter; + static const int fan_counts[] = {2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0}; + + npcm8xx_init_pwm_splitter(machine, soc, fan_counts); + npcm8xx_connect_pwm_fan(soc, &splitter[0], 0x00, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[0], 0x01, 1); + npcm8xx_connect_pwm_fan(soc, &splitter[1], 0x02, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[1], 0x03, 1); + npcm8xx_connect_pwm_fan(soc, &splitter[2], 0x04, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[2], 0x05, 1); + npcm8xx_connect_pwm_fan(soc, &splitter[3], 0x06, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[3], 0x07, 1); + npcm8xx_connect_pwm_fan(soc, &splitter[4], 0x08, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[4], 0x09, 1); + npcm8xx_connect_pwm_fan(soc, &splitter[5], 0x0a, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[5], 0x0b, 1); + npcm8xx_connect_pwm_fan(soc, &splitter[6], 0x0c, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[6], 0x0d, 1); + npcm8xx_connect_pwm_fan(soc, &splitter[7], 0x0e, 0); + npcm8xx_connect_pwm_fan(soc, &splitter[7], 0x0f, 1); +} + +static void npcm845_evb_init(MachineState *machine) +{ + NPCM8xxState *soc; + + soc = npcm8xx_create_soc(machine, NPCM845_EVB_POWER_ON_STRAPS); + npcm8xx_connect_dram(soc, machine->ram); + qdev_realize(DEVICE(soc), NULL, &error_fatal); + + npcm8xx_load_bootrom(machine, soc); + npcm8xx_connect_flash(&soc->fiu[0], 0, "w25q256", drive_get(IF_MTD, 0, 0)); + npcm845_evb_i2c_init(soc); + npcm845_evb_fan_init(NPCM8XX_MACHINE(machine), soc); + npcm8xx_load_kernel(machine, soc); +} + +static void npcm8xx_set_soc_type(NPCM8xxMachineClass *nmc, const char *type) +{ + NPCM8xxClass *sc = NPCM8XX_CLASS(object_class_by_name(type)); + MachineClass *mc = MACHINE_CLASS(nmc); + + nmc->soc_type = type; + mc->default_cpus = mc->min_cpus = mc->max_cpus = sc->num_cpus; +} + +static void npcm8xx_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + static const char * const valid_cpu_types[] = { + ARM_CPU_TYPE_NAME("cortex-a9"), + NULL + }; + + mc->no_floppy = 1; + mc->no_cdrom = 1; + mc->no_parallel = 1; + mc->default_ram_id = "ram"; + mc->valid_cpu_types = valid_cpu_types; +} + +static void npcm845_evb_machine_class_init(ObjectClass *oc, void *data) +{ + NPCM8xxMachineClass *nmc = NPCM8XX_MACHINE_CLASS(oc); + MachineClass *mc = MACHINE_CLASS(oc); + + npcm8xx_set_soc_type(nmc, TYPE_NPCM8XX); + + mc->desc = "Nuvoton NPCM845 Evaluation Board (Cortex-A35)"; + mc->init = npcm845_evb_init; + mc->default_ram_size = 1 * GiB; +}; + +static const TypeInfo npcm8xx_machine_types[] = { + { + .name = TYPE_NPCM8XX_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(NPCM8xxMachine), + .class_size = sizeof(NPCM8xxMachineClass), + .class_init = npcm8xx_machine_class_init, + .abstract = true, + }, { + .name = MACHINE_TYPE_NAME("npcm845-evb"), + .parent = TYPE_NPCM8XX_MACHINE, + .class_init = npcm845_evb_machine_class_init, + }, +}; + +DEFINE_TYPES(npcm8xx_machine_types) diff --git a/include/hw/arm/npcm8xx.h b/include/hw/arm/npcm8xx.h index a32fbb035b5b..9812e6fa7ec6 100644 --- a/include/hw/arm/npcm8xx.h +++ b/include/hw/arm/npcm8xx.h @@ -51,6 +51,27 @@ #define NPCM8XX_NR_PWM_MODULES 3 +struct NPCM8xxMachine { + MachineState parent_obj; + + /* + * PWM fan splitter. each splitter connects to one PWM output and + * multiple MFT inputs. + */ + SplitIRQ fan_splitter[NPCM8XX_NR_PWM_MODULES * + NPCM7XX_PWM_PER_MODULE]; +}; + + +struct NPCM8xxMachineClass { + MachineClass parent_class; + + const char *soc_type; +}; + +#define TYPE_NPCM8XX_MACHINE MACHINE_TYPE_NAME("npcm8xx") +OBJECT_DECLARE_TYPE(NPCM8xxMachine, NPCM8xxMachineClass, NPCM8XX_MACHINE) + struct NPCM8xxState { DeviceState parent_obj; From 1c3169179b8242866316108386800379c4e22974 Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 19 Feb 2025 10:46:08 -0800 Subject: [PATCH 0180/1179] docs/system/arm: Add Description for NPCM8XX SoC NPCM8XX SoC is the successor of the NPCM7XX. It features quad-core Cortex-A35 (Armv8, 64-bit) CPUs and some additional peripherals. This document describes the NPCM8XX SoC and an evaluation board (NPCM 845 EVB). Signed-off-by: Hao Wu Reviewed-by: Peter Maydell Message-id: 20250219184609.1839281-18-wuhaotsh@google.com Signed-off-by: Peter Maydell --- docs/system/arm/nuvoton.rst | 27 ++++++++++++++++++++------- hw/arm/npcm8xx_boards.c | 1 + 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/system/arm/nuvoton.rst b/docs/system/arm/nuvoton.rst index 05059378e559..e4827fb43a10 100644 --- a/docs/system/arm/nuvoton.rst +++ b/docs/system/arm/nuvoton.rst @@ -1,12 +1,13 @@ -Nuvoton iBMC boards (``kudo-bmc``, ``mori-bmc``, ``npcm750-evb``, ``quanta-gbs-bmc``, ``quanta-gsj``) -===================================================================================================== +Nuvoton iBMC boards (``kudo-bmc``, ``mori-bmc``, ``npcm750-evb``, ``quanta-gbs-bmc``, ``quanta-gsj``, ``npcm845-evb``) +====================================================================================================================== -The `Nuvoton iBMC`_ chips (NPCM7xx) are a family of ARM-based SoCs that are +The `Nuvoton iBMC`_ chips are a family of Arm-based SoCs that are designed to be used as Baseboard Management Controllers (BMCs) in various -servers. They all feature one or two ARM Cortex-A9 CPU cores, as well as an -assortment of peripherals targeted for either Enterprise or Data Center / -Hyperscale applications. The former is a superset of the latter, so NPCM750 has -all the peripherals of NPCM730 and more. +servers. Currently there are two families: NPCM7XX series and +NPCM8XX series. NPCM7XX series feature one or two Arm Cortex-A9 CPU cores, +while NPCM8XX feature 4 Arm Cortex-A35 CPU cores. Both series contain a +different assortment of peripherals targeted for either Enterprise or Data +Center / Hyperscale applications. .. _Nuvoton iBMC: https://www.nuvoton.com/products/cloud-computing/ibmc/ @@ -27,6 +28,11 @@ There are also two more SoCs, NPCM710 and NPCM705, which are single-core variants of NPCM750 and NPCM730, respectively. These are currently not supported by QEMU. +The NPCM8xx SoC is the successor of the NPCM7xx SoC. It has 4 Cortex-A35 cores. +The following machines are based on this chip : + +- ``npcm845-evb`` Nuvoton NPCM845 Evaluation board + Supported devices ----------------- @@ -62,6 +68,8 @@ Missing devices * System Wake-up Control (SWC) * Shared memory (SHM) * eSPI slave interface + * Block-transfer interface (8XX only) + * Virtual UART (8XX only) * Ethernet controller (GMAC) * USB device (USBD) @@ -76,6 +84,11 @@ Missing devices * Video capture * Encoding compression engine * Security features + * I3C buses (8XX only) + * Temperature sensor interface (8XX only) + * Virtual UART (8XX only) + * Flash monitor (8XX only) + * JTAG master (8XX only) Boot options ------------ diff --git a/hw/arm/npcm8xx_boards.c b/hw/arm/npcm8xx_boards.c index 19610483f934..3fb8478e72e7 100644 --- a/hw/arm/npcm8xx_boards.c +++ b/hw/arm/npcm8xx_boards.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" #include "chardev/char.h" +#include "hw/boards.h" #include "hw/arm/npcm8xx.h" #include "hw/core/cpu.h" #include "hw/loader.h" From 6f9a1a01433738811c9795dcf29f299c60835558 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 16 Jan 2025 07:46:44 +0100 Subject: [PATCH 0181/1179] docs/about: Change notes on x86 machine type deprecation into a general one We now have a general note about versioned machine types getting deprecated and removed at the beginning of the deprecated.rst file, so we should also have a general note about this in removed-features.rst (which will also apply to versioned non-x86 machine types) instead of listing individual old machine types in the document. Signed-off-by: Thomas Huth Message-Id: <20250116064644.65670-1-thuth@redhat.com> Reviewed-by: Zhao Liu Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- docs/about/deprecated.rst | 7 ------- docs/about/removed-features.rst | 11 +++++------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 4a3c302962a0..7b42d6eeccae 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -236,13 +236,6 @@ deprecated; use the new name ``dtb-randomness`` instead. The new name better reflects the way this property affects all random data within the device tree blob, not just the ``kaslr-seed`` node. -``pc-i440fx-2.4`` up to ``pc-i440fx-2.12`` (since 9.1) -'''''''''''''''''''''''''''''''''''''''''''''''''''''' - -These old machine types are quite neglected nowadays and thus might have -various pitfalls with regards to live migration. Use a newer machine type -instead. - PPC 405 ``ref405ep`` machine (since 9.1) '''''''''''''''''''''''''''''''''''''''' diff --git a/docs/about/removed-features.rst b/docs/about/removed-features.rst index c6616ce05e59..156c0c253c82 100644 --- a/docs/about/removed-features.rst +++ b/docs/about/removed-features.rst @@ -972,6 +972,11 @@ from Linux in 2021, and is not supported anymore by QEMU either. System emulator machines ------------------------ +Note: Versioned machine types that have been introduced in a QEMU version +that has initially been released more than 6 years before are considered +obsolete and will be removed without further notice in this document. +Please use newer machine types instead. + ``s390-virtio`` (removed in 2.6) '''''''''''''''''''''''''''''''' @@ -1006,12 +1011,6 @@ mips ``fulong2e`` machine alias (removed in 6.0) This machine has been renamed ``fuloong2e``. -``pc-0.10`` up to ``pc-i440fx-2.3`` (removed in 4.0 up to 9.0) -'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' - -These machine types were very old and likely could not be used for live -migration from old QEMU versions anymore. Use a newer machine type instead. - Raspberry Pi ``raspi2`` and ``raspi3`` machines (removed in 6.2) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' From bc82af6b0dcb0933e72640851fdd2594f822b23e Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 16:30:43 +0900 Subject: [PATCH 0182/1179] hw/net: Fix NULL dereference with software RSS When an eBPF program cannot be attached, virtio_net_load_ebpf() returns false, and virtio_net_device_realize() enters the code path to handle errors because of this, but it causes NULL dereference because no error is generated. Change virtio_net_load_ebpf() to return false only when a fatal error occurred. Fixes: b5900dff14e5 ("hw/net: report errors from failing to use eBPF RSS FDs") Signed-off-by: Akihiko Odaki Message-Id: <20250116-software-v1-1-9e5161b534d8@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/net/virtio-net.c | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index 85e14b788cf2..d64941bf8e84 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -1352,18 +1352,25 @@ static bool virtio_net_load_ebpf_fds(VirtIONet *n, Error **errp) static bool virtio_net_load_ebpf(VirtIONet *n, Error **errp) { - bool ret = false; + if (!virtio_net_attach_ebpf_to_backend(n->nic, -1)) { + return true; + } - if (virtio_net_attach_ebpf_to_backend(n->nic, -1)) { - trace_virtio_net_rss_load(n, n->nr_ebpf_rss_fds, n->ebpf_rss_fds); - if (n->ebpf_rss_fds) { - ret = virtio_net_load_ebpf_fds(n, errp); - } else { - ret = ebpf_rss_load(&n->ebpf_rss, errp); - } + trace_virtio_net_rss_load(n, n->nr_ebpf_rss_fds, n->ebpf_rss_fds); + + /* + * If user explicitly gave QEMU RSS FDs to use, then + * failing to use them must be considered a fatal + * error. If no RSS FDs were provided, QEMU is trying + * eBPF on a "best effort" basis only, so report a + * warning and allow fallback to software RSS. + */ + if (n->ebpf_rss_fds) { + return virtio_net_load_ebpf_fds(n, errp); } - return ret; + ebpf_rss_load(&n->ebpf_rss, &error_warn); + return true; } static void virtio_net_unload_ebpf(VirtIONet *n) @@ -3913,23 +3920,7 @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp) net_rx_pkt_init(&n->rx_pkt); if (virtio_has_feature(n->host_features, VIRTIO_NET_F_RSS)) { - Error *err = NULL; - if (!virtio_net_load_ebpf(n, &err)) { - /* - * If user explicitly gave QEMU RSS FDs to use, then - * failing to use them must be considered a fatal - * error. If no RSS FDs were provided, QEMU is trying - * eBPF on a "best effort" basis only, so report a - * warning and allow fallback to software RSS. - */ - if (n->ebpf_rss_fds) { - error_propagate(errp, err); - } else { - warn_report("unable to load eBPF RSS: %s", - error_get_pretty(err)); - error_free(err); - } - } + virtio_net_load_ebpf(n, errp); } } From 5731b005246297ab5b91975f52fcd525e08507cd Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:52 +0900 Subject: [PATCH 0183/1179] hw/ppc/spapr_pci: Do not create DT for disabled PCI device Disabled means it is a disabled SR-IOV VF and hidden from the guest. Do not create DT when starting the system and also keep the disabled PCI device not linked to DRC, which generates DT in case of hotplug. Signed-off-by: Akihiko Odaki Reviewed-by: Shivaprasad G Bhat Tested-by: Shivaprasad G Bhat Message-Id: <20250116-reuse-v20-1-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/ppc/spapr_pci.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hw/ppc/spapr_pci.c b/hw/ppc/spapr_pci.c index 904227d9aa1f..b94e4ba1314f 100644 --- a/hw/ppc/spapr_pci.c +++ b/hw/ppc/spapr_pci.c @@ -1283,8 +1283,7 @@ static void spapr_dt_pci_device_cb(PCIBus *bus, PCIDevice *pdev, PciWalkFdt *p = opaque; int err; - if (p->err) { - /* Something's already broken, don't keep going */ + if (p->err || !pdev->enabled) { return; } @@ -1572,6 +1571,14 @@ static void spapr_pci_plug(HotplugHandler *plug_handler, SpaprDrc *drc = drc_from_dev(phb, pdev); uint32_t slotnr = PCI_SLOT(pdev->devfn); + /* + * If DR or the PCI device is disabled we don't need to do anything + * in the case of hotplug or coldplug callbacks. + */ + if (!pdev->enabled) { + return; + } + g_assert(drc); if (IS_PCI_BRIDGE(plugged_dev)) { @@ -1647,6 +1654,11 @@ static void spapr_pci_unplug_request(HotplugHandler *plug_handler, SpaprDrc *drc = drc_from_dev(phb, pdev); g_assert(drc); + + if (!drc->dev) { + return; + } + g_assert(drc->dev == plugged_dev); if (!spapr_drc_unplug_requested(drc)) { From 3bc31441ba31c94f12b7b96b1960abb4c1f21ee8 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:53 +0900 Subject: [PATCH 0184/1179] hw/ppc/spapr_pci: Do not reject VFs created after a PF A PF may automatically create VFs and the PF may be function 0. Signed-off-by: Akihiko Odaki Reviewed-by: Shivaprasad G Bhat Tested-by: Shivaprasad G Bhat Message-Id: <20250116-reuse-v20-2-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/ppc/spapr_pci.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hw/ppc/spapr_pci.c b/hw/ppc/spapr_pci.c index b94e4ba1314f..e0a9d50edc3d 100644 --- a/hw/ppc/spapr_pci.c +++ b/hw/ppc/spapr_pci.c @@ -1549,7 +1549,9 @@ static void spapr_pci_pre_plug(HotplugHandler *plug_handler, * hotplug, we do not allow functions to be hotplugged to a * slot that already has function 0 present */ - if (plugged_dev->hotplugged && bus->devices[PCI_DEVFN(slotnr, 0)] && + if (plugged_dev->hotplugged && + !pci_is_vf(pdev) && + bus->devices[PCI_DEVFN(slotnr, 0)] && PCI_FUNC(pdev->devfn) != 0) { error_setg(errp, "PCI: slot %d function 0 already occupied by %s," " additional functions can no longer be exposed to guest.", From b77a2778d9bbefc6119331ce3083d85d69d7a0c1 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:54 +0900 Subject: [PATCH 0185/1179] s390x/pci: Avoid creating zpci for VFs VFs are automatically created by PF, and creating zpci for them will result in unexpected usage of fids. Currently QEMU does not support multifunction for s390x so we don't need zpci for VFs anyway. Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-3-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/s390x/s390-pci-bus.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index eead269cc285..8c5eb69f7d76 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -1080,6 +1080,16 @@ static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev, pbdev = s390_pci_find_dev_by_target(s, dev->id); if (!pbdev) { + /* + * VFs are automatically created by PF, and creating zpci for them + * will result in unexpected usage of fids. Currently QEMU does not + * support multifunction for s390x so we don't need zpci for VFs + * anyway. + */ + if (pci_is_vf(pdev)) { + return; + } + pbdev = s390_pci_device_new(s, dev->id, errp); if (!pbdev) { return; @@ -1167,7 +1177,10 @@ static void s390_pcihost_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, int32_t devfn; pbdev = s390_pci_find_dev_by_pci(s, PCI_DEVICE(dev)); - g_assert(pbdev); + if (!pbdev) { + g_assert(pci_is_vf(pci_dev)); + return; + } s390_pci_generate_plug_event(HP_EVENT_STANDBY_TO_RESERVED, pbdev->fh, pbdev->fid); @@ -1206,7 +1219,11 @@ static void s390_pcihost_unplug_request(HotplugHandler *hotplug_dev, * we've checked the PCI device already (to prevent endless recursion). */ pbdev = s390_pci_find_dev_by_pci(s, PCI_DEVICE(dev)); - g_assert(pbdev); + if (!pbdev) { + g_assert(pci_is_vf(PCI_DEVICE(dev))); + return; + } + pbdev->pci_unplug_request_processed = true; qdev_unplug(DEVICE(pbdev), errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_S390_PCI_DEVICE)) { From d966250e93c1194c0a4c775e08a8e23f2ef66cf8 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:55 +0900 Subject: [PATCH 0186/1179] s390x/pci: Allow plugging SR-IOV devices The guest cannot use VFs due to the lack of multifunction support but can use PFs. Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-4-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/s390x/s390-pci-bus.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index 8c5eb69f7d76..c396d55c7240 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -974,7 +974,14 @@ static void s390_pcihost_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { PCIDevice *pdev = PCI_DEVICE(dev); - if (pdev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION) { + /* + * Multifunction is not supported due to the lack of CLP. However, + * do not check for multifunction capability for SR-IOV devices because + * SR-IOV devices automatically add the multifunction capability whether + * the user intends to use the functions other than the PF. + */ + if (pdev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION && + !pdev->exp.sriov_cap) { error_setg(errp, "multifunction not supported in s390"); return; } From 2c968f465392316d09e44f4a9f7e378999e5c011 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:56 +0900 Subject: [PATCH 0187/1179] s390x/pci: Check for multifunction after device realization The SR-IOV PFs set the multifunction bit during device realization so check them after that. There is no functional change because we explicitly ignore the multifunction bit for SR-IOV devices. Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-5-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/s390x/s390-pci-bus.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index c396d55c7240..913d72cc7480 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -971,21 +971,7 @@ static void s390_pcihost_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, "this device"); } - if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { - PCIDevice *pdev = PCI_DEVICE(dev); - - /* - * Multifunction is not supported due to the lack of CLP. However, - * do not check for multifunction capability for SR-IOV devices because - * SR-IOV devices automatically add the multifunction capability whether - * the user intends to use the functions other than the PF. - */ - if (pdev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION && - !pdev->exp.sriov_cap) { - error_setg(errp, "multifunction not supported in s390"); - return; - } - } else if (object_dynamic_cast(OBJECT(dev), TYPE_S390_PCI_DEVICE)) { + if (object_dynamic_cast(OBJECT(dev), TYPE_S390_PCI_DEVICE)) { S390PCIBusDevice *pbdev = S390_PCI_DEVICE(dev); if (!s390_pci_alloc_idx(s, pbdev)) { @@ -1076,6 +1062,18 @@ static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev, } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { pdev = PCI_DEVICE(dev); + /* + * Multifunction is not supported due to the lack of CLP. However, + * do not check for multifunction capability for SR-IOV devices because + * SR-IOV devices automatically add the multifunction capability whether + * the user intends to use the functions other than the PF. + */ + if (pdev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION && + !pdev->exp.sriov_cap) { + error_setg(errp, "multifunction not supported in s390"); + return; + } + if (!dev->id) { /* In the case the PCI device does not define an id */ /* we generate one based on the PCI address */ From e8b827ce7c4bf9a334c46282779f234d1a011cd1 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:57 +0900 Subject: [PATCH 0188/1179] pcie_sriov: Do not manually unrealize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A device gets automatically unrealized when being unparented. Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-6-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Reviewed-by: Philippe Mathieu-Daudé --- hw/pci/pcie_sriov.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index e9b23221d713..499becd5273f 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -204,11 +204,7 @@ static void unregister_vfs(PCIDevice *dev) trace_sriov_unregister_vfs(dev->name, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn), num_vfs); for (i = 0; i < num_vfs; i++) { - Error *err = NULL; PCIDevice *vf = dev->exp.sriov_pf.vf[i]; - if (!object_property_set_bool(OBJECT(vf), "realized", false, &err)) { - error_reportf_err(err, "Failed to unplug: "); - } object_unparent(OBJECT(vf)); object_unref(OBJECT(vf)); } From 3391d68e906114c364c173c7f3f7389d47d15a11 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:58 +0900 Subject: [PATCH 0189/1179] pcie_sriov: Ensure VF addr does not overflow pci_new() aborts when creating a VF with addr >= PCI_DEVFN_MAX. Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-7-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- docs/pcie_sriov.txt | 8 +++++--- hw/net/igb.c | 10 +++++++--- hw/nvme/ctrl.c | 22 ++++++++++++++-------- hw/pci/pcie_sriov.c | 14 ++++++++++++-- include/hw/pci/pcie_sriov.h | 5 +++-- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/docs/pcie_sriov.txt b/docs/pcie_sriov.txt index a47aad0bfab0..ab2142807f79 100644 --- a/docs/pcie_sriov.txt +++ b/docs/pcie_sriov.txt @@ -52,9 +52,11 @@ setting up a BAR for a VF. ... /* Add and initialize the SR/IOV capability */ - pcie_sriov_pf_init(d, 0x200, "your_virtual_dev", - vf_devid, initial_vfs, total_vfs, - fun_offset, stride); + if (!pcie_sriov_pf_init(d, 0x200, "your_virtual_dev", + vf_devid, initial_vfs, total_vfs, + fun_offset, stride, errp)) { + return; + } /* Set up individual VF BARs (parameters as for normal BARs) */ pcie_sriov_pf_init_vf_bar( ... ) diff --git a/hw/net/igb.c b/hw/net/igb.c index 4d93ce629f95..c965fc2fb68a 100644 --- a/hw/net/igb.c +++ b/hw/net/igb.c @@ -446,9 +446,13 @@ static void igb_pci_realize(PCIDevice *pci_dev, Error **errp) pcie_ari_init(pci_dev, 0x150); - pcie_sriov_pf_init(pci_dev, IGB_CAP_SRIOV_OFFSET, TYPE_IGBVF, - IGB_82576_VF_DEV_ID, IGB_MAX_VF_FUNCTIONS, IGB_MAX_VF_FUNCTIONS, - IGB_VF_OFFSET, IGB_VF_STRIDE); + if (!pcie_sriov_pf_init(pci_dev, IGB_CAP_SRIOV_OFFSET, TYPE_IGBVF, + IGB_82576_VF_DEV_ID, IGB_MAX_VF_FUNCTIONS, + IGB_MAX_VF_FUNCTIONS, IGB_VF_OFFSET, IGB_VF_STRIDE, + errp)) { + igb_cleanup_msix(s); + return; + } pcie_sriov_pf_init_vf_bar(pci_dev, IGBVF_MMIO_BAR_IDX, PCI_BASE_ADDRESS_MEM_TYPE_64 | PCI_BASE_ADDRESS_MEM_PREFETCH, diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 68903d1d7067..8175751518f8 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -8481,7 +8481,8 @@ static uint64_t nvme_mbar_size(unsigned total_queues, unsigned total_irqs, return pow2ceil(bar_size); } -static void nvme_init_sriov(NvmeCtrl *n, PCIDevice *pci_dev, uint16_t offset) +static bool nvme_init_sriov(NvmeCtrl *n, PCIDevice *pci_dev, uint16_t offset, + Error **errp) { uint16_t vf_dev_id = n->params.use_intel_id ? PCI_DEVICE_ID_INTEL_NVME : PCI_DEVICE_ID_REDHAT_NVME; @@ -8490,12 +8491,16 @@ static void nvme_init_sriov(NvmeCtrl *n, PCIDevice *pci_dev, uint16_t offset) le16_to_cpu(cap->vifrsm), NULL, NULL); - pcie_sriov_pf_init(pci_dev, offset, "nvme", vf_dev_id, - n->params.sriov_max_vfs, n->params.sriov_max_vfs, - NVME_VF_OFFSET, NVME_VF_STRIDE); + if (!pcie_sriov_pf_init(pci_dev, offset, "nvme", vf_dev_id, + n->params.sriov_max_vfs, n->params.sriov_max_vfs, + NVME_VF_OFFSET, NVME_VF_STRIDE, errp)) { + return false; + } pcie_sriov_pf_init_vf_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64, bar_size); + + return true; } static int nvme_add_pm_capability(PCIDevice *pci_dev, uint8_t offset) @@ -8620,6 +8625,11 @@ static bool nvme_init_pci(NvmeCtrl *n, PCIDevice *pci_dev, Error **errp) return false; } + if (!pci_is_vf(pci_dev) && n->params.sriov_max_vfs && + !nvme_init_sriov(n, pci_dev, 0x120, errp)) { + return false; + } + nvme_update_msixcap_ts(pci_dev, n->conf_msix_qsize); pcie_cap_deverr_init(pci_dev); @@ -8649,10 +8659,6 @@ static bool nvme_init_pci(NvmeCtrl *n, PCIDevice *pci_dev, Error **errp) nvme_init_pmr(n, pci_dev); } - if (!pci_is_vf(pci_dev) && n->params.sriov_max_vfs) { - nvme_init_sriov(n, pci_dev, 0x120); - } - return true; } diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index 499becd5273f..91c64c988eb4 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -24,14 +24,22 @@ static PCIDevice *register_vf(PCIDevice *pf, int devfn, const char *name, uint16_t vf_num); static void unregister_vfs(PCIDevice *dev); -void pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, +bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, const char *vfname, uint16_t vf_dev_id, uint16_t init_vfs, uint16_t total_vfs, - uint16_t vf_offset, uint16_t vf_stride) + uint16_t vf_offset, uint16_t vf_stride, + Error **errp) { + int32_t devfn = dev->devfn + vf_offset; uint8_t *cfg = dev->config + offset; uint8_t *wmask; + if (total_vfs && + (uint32_t)devfn + (uint32_t)(total_vfs - 1) * vf_stride >= PCI_DEVFN_MAX) { + error_setg(errp, "VF addr overflows"); + return false; + } + pcie_add_capability(dev, PCI_EXT_CAP_ID_SRIOV, 1, offset, PCI_EXT_CAP_SRIOV_SIZEOF); dev->exp.sriov_cap = offset; @@ -69,6 +77,8 @@ void pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, pci_set_word(wmask + PCI_SRIOV_SYS_PGSIZE, 0x553); qdev_prop_set_bit(&dev->qdev, "multifunction", true); + + return true; } void pcie_sriov_pf_exit(PCIDevice *dev) diff --git a/include/hw/pci/pcie_sriov.h b/include/hw/pci/pcie_sriov.h index 450cbef6c201..aa704e8f9d9f 100644 --- a/include/hw/pci/pcie_sriov.h +++ b/include/hw/pci/pcie_sriov.h @@ -27,10 +27,11 @@ typedef struct PCIESriovVF { uint16_t vf_number; /* Logical VF number of this function */ } PCIESriovVF; -void pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, +bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, const char *vfname, uint16_t vf_dev_id, uint16_t init_vfs, uint16_t total_vfs, - uint16_t vf_offset, uint16_t vf_stride); + uint16_t vf_offset, uint16_t vf_stride, + Error **errp); void pcie_sriov_pf_exit(PCIDevice *dev); /* Set up a VF bar in the SR/IOV bar area */ From cab1398a60eb0cb2d2d1998c9b46aaa5e0bf3ee8 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:00:59 +0900 Subject: [PATCH 0190/1179] pcie_sriov: Reuse SR-IOV VF device instances Disable SR-IOV VF devices by reusing code to power down PCI devices instead of removing them when the guest requests to disable VFs. This allows to realize devices and report VF realization errors at PF realization time. Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-8-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/pci/pci.c | 14 +++++- hw/pci/pcie_sriov.c | 94 +++++++++++++++---------------------- include/hw/pci/pcie_sriov.h | 1 - 3 files changed, 51 insertions(+), 58 deletions(-) diff --git a/hw/pci/pci.c b/hw/pci/pci.c index 2afa423925c5..3e29b30d5588 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -2963,7 +2963,17 @@ MSIMessage pci_get_msi_message(PCIDevice *dev, int vector) void pci_set_power(PCIDevice *d, bool state) { - pci_set_enabled(d, state); + /* + * Don't change the enabled state of VFs when powering on/off the device. + * + * When powering on, VFs must not be enabled immediately but they must + * wait until the guest configures SR-IOV. + * When powering off, their corresponding PFs will be reset and disable + * VFs. + */ + if (!pci_is_vf(d)) { + pci_set_enabled(d, state); + } } void pci_set_enabled(PCIDevice *d, bool state) @@ -2977,7 +2987,7 @@ void pci_set_enabled(PCIDevice *d, bool state) memory_region_set_enabled(&d->bus_master_enable_region, (pci_get_word(d->config + PCI_COMMAND) & PCI_COMMAND_MASTER) && d->enabled); - if (!d->enabled) { + if (qdev_is_realized(&d->qdev)) { pci_device_reset(d); } } diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index 91c64c988eb4..f1993bc553c0 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -20,9 +20,16 @@ #include "qapi/error.h" #include "trace.h" -static PCIDevice *register_vf(PCIDevice *pf, int devfn, - const char *name, uint16_t vf_num); -static void unregister_vfs(PCIDevice *dev); +static void unparent_vfs(PCIDevice *dev, uint16_t total_vfs) +{ + for (uint16_t i = 0; i < total_vfs; i++) { + PCIDevice *vf = dev->exp.sriov_pf.vf[i]; + object_unparent(OBJECT(vf)); + object_unref(OBJECT(vf)); + } + g_free(dev->exp.sriov_pf.vf); + dev->exp.sriov_pf.vf = NULL; +} bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, const char *vfname, uint16_t vf_dev_id, @@ -30,6 +37,7 @@ bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, uint16_t vf_offset, uint16_t vf_stride, Error **errp) { + BusState *bus = qdev_get_parent_bus(&dev->qdev); int32_t devfn = dev->devfn + vf_offset; uint8_t *cfg = dev->config + offset; uint8_t *wmask; @@ -44,7 +52,6 @@ bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, offset, PCI_EXT_CAP_SRIOV_SIZEOF); dev->exp.sriov_cap = offset; dev->exp.sriov_pf.num_vfs = 0; - dev->exp.sriov_pf.vfname = g_strdup(vfname); dev->exp.sriov_pf.vf = NULL; pci_set_word(cfg + PCI_SRIOV_VF_OFFSET, vf_offset); @@ -78,14 +85,34 @@ bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, qdev_prop_set_bit(&dev->qdev, "multifunction", true); + dev->exp.sriov_pf.vf = g_new(PCIDevice *, total_vfs); + + for (uint16_t i = 0; i < total_vfs; i++) { + PCIDevice *vf = pci_new(devfn, vfname); + vf->exp.sriov_vf.pf = dev; + vf->exp.sriov_vf.vf_number = i; + + if (!qdev_realize(&vf->qdev, bus, errp)) { + unparent_vfs(dev, i); + return false; + } + + /* set vid/did according to sr/iov spec - they are not used */ + pci_config_set_vendor_id(vf->config, 0xffff); + pci_config_set_device_id(vf->config, 0xffff); + + dev->exp.sriov_pf.vf[i] = vf; + devfn += vf_stride; + } + return true; } void pcie_sriov_pf_exit(PCIDevice *dev) { - unregister_vfs(dev); - g_free((char *)dev->exp.sriov_pf.vfname); - dev->exp.sriov_pf.vfname = NULL; + uint8_t *cfg = dev->config + dev->exp.sriov_cap; + + unparent_vfs(dev, pci_get_word(cfg + PCI_SRIOV_TOTAL_VF)); } void pcie_sriov_pf_init_vf_bar(PCIDevice *dev, int region_num, @@ -151,38 +178,11 @@ void pcie_sriov_vf_register_bar(PCIDevice *dev, int region_num, } } -static PCIDevice *register_vf(PCIDevice *pf, int devfn, const char *name, - uint16_t vf_num) -{ - PCIDevice *dev = pci_new(devfn, name); - dev->exp.sriov_vf.pf = pf; - dev->exp.sriov_vf.vf_number = vf_num; - PCIBus *bus = pci_get_bus(pf); - Error *local_err = NULL; - - qdev_realize(&dev->qdev, &bus->qbus, &local_err); - if (local_err) { - error_report_err(local_err); - return NULL; - } - - /* set vid/did according to sr/iov spec - they are not used */ - pci_config_set_vendor_id(dev->config, 0xffff); - pci_config_set_device_id(dev->config, 0xffff); - - return dev; -} - static void register_vfs(PCIDevice *dev) { uint16_t num_vfs; uint16_t i; uint16_t sriov_cap = dev->exp.sriov_cap; - uint16_t vf_offset = - pci_get_word(dev->config + sriov_cap + PCI_SRIOV_VF_OFFSET); - uint16_t vf_stride = - pci_get_word(dev->config + sriov_cap + PCI_SRIOV_VF_STRIDE); - int32_t devfn = dev->devfn + vf_offset; assert(sriov_cap > 0); num_vfs = pci_get_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF); @@ -190,18 +190,10 @@ static void register_vfs(PCIDevice *dev) return; } - dev->exp.sriov_pf.vf = g_new(PCIDevice *, num_vfs); - trace_sriov_register_vfs(dev->name, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn), num_vfs); for (i = 0; i < num_vfs; i++) { - dev->exp.sriov_pf.vf[i] = register_vf(dev, devfn, - dev->exp.sriov_pf.vfname, i); - if (!dev->exp.sriov_pf.vf[i]) { - num_vfs = i; - break; - } - devfn += vf_stride; + pci_set_enabled(dev->exp.sriov_pf.vf[i], true); } dev->exp.sriov_pf.num_vfs = num_vfs; } @@ -214,12 +206,8 @@ static void unregister_vfs(PCIDevice *dev) trace_sriov_unregister_vfs(dev->name, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn), num_vfs); for (i = 0; i < num_vfs; i++) { - PCIDevice *vf = dev->exp.sriov_pf.vf[i]; - object_unparent(OBJECT(vf)); - object_unref(OBJECT(vf)); + pci_set_enabled(dev->exp.sriov_pf.vf[i], false); } - g_free(dev->exp.sriov_pf.vf); - dev->exp.sriov_pf.vf = NULL; dev->exp.sriov_pf.num_vfs = 0; } @@ -241,14 +229,10 @@ void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, PCI_FUNC(dev->devfn), off, val, len); if (range_covers_byte(off, len, PCI_SRIOV_CTRL)) { - if (dev->exp.sriov_pf.num_vfs) { - if (!(val & PCI_SRIOV_CTRL_VFE)) { - unregister_vfs(dev); - } + if (val & PCI_SRIOV_CTRL_VFE) { + register_vfs(dev); } else { - if (val & PCI_SRIOV_CTRL_VFE) { - register_vfs(dev); - } + unregister_vfs(dev); } } } diff --git a/include/hw/pci/pcie_sriov.h b/include/hw/pci/pcie_sriov.h index aa704e8f9d9f..70649236c18a 100644 --- a/include/hw/pci/pcie_sriov.h +++ b/include/hw/pci/pcie_sriov.h @@ -18,7 +18,6 @@ typedef struct PCIESriovPF { uint16_t num_vfs; /* Number of virtual functions created */ uint8_t vf_bar_type[PCI_NUM_REGIONS]; /* Store type for each VF bar */ - const char *vfname; /* Reference to the device type used for the VFs */ PCIDevice **vf; /* Pointer to an array of num_vfs VF devices */ } PCIESriovPF; From b85901e728d048c931d110bfd5ff8f119cc97df7 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:01:00 +0900 Subject: [PATCH 0191/1179] pcie_sriov: Release VFs failed to realize Release VFs failed to realize just as we do in unregister_vfs(). Fixes: 7c0fa8dff811 ("pcie: Add support for Single Root I/O Virtualization (SR/IOV)") Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-9-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/pci/pcie_sriov.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index f1993bc553c0..db087bb9330c 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -93,6 +93,8 @@ bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, vf->exp.sriov_vf.vf_number = i; if (!qdev_realize(&vf->qdev, bus, errp)) { + object_unparent(OBJECT(vf)); + object_unref(vf); unparent_vfs(dev, i); return false; } From 5e7dd17e43486f41d4f88dbefd7219f0524f424c Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:01:01 +0900 Subject: [PATCH 0192/1179] pcie_sriov: Remove num_vfs from PCIESriovPF num_vfs is not migrated so use PCI_SRIOV_CTRL_VFE and PCI_SRIOV_NUM_VF instead. Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-10-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/pci/pcie_sriov.c | 38 ++++++++++++++++++++++++++----------- hw/pci/trace-events | 2 +- include/hw/pci/pcie_sriov.h | 1 - 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index db087bb9330c..69609c112e31 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -51,7 +51,6 @@ bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, pcie_add_capability(dev, PCI_EXT_CAP_ID_SRIOV, 1, offset, PCI_EXT_CAP_SRIOV_SIZEOF); dev->exp.sriov_cap = offset; - dev->exp.sriov_pf.num_vfs = 0; dev->exp.sriov_pf.vf = NULL; pci_set_word(cfg + PCI_SRIOV_VF_OFFSET, vf_offset); @@ -188,29 +187,28 @@ static void register_vfs(PCIDevice *dev) assert(sriov_cap > 0); num_vfs = pci_get_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF); - if (num_vfs > pci_get_word(dev->config + sriov_cap + PCI_SRIOV_TOTAL_VF)) { - return; - } trace_sriov_register_vfs(dev->name, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn), num_vfs); for (i = 0; i < num_vfs; i++) { pci_set_enabled(dev->exp.sriov_pf.vf[i], true); } - dev->exp.sriov_pf.num_vfs = num_vfs; + + pci_set_word(dev->wmask + sriov_cap + PCI_SRIOV_NUM_VF, 0); } static void unregister_vfs(PCIDevice *dev) { - uint16_t num_vfs = dev->exp.sriov_pf.num_vfs; + uint8_t *cfg = dev->config + dev->exp.sriov_cap; uint16_t i; trace_sriov_unregister_vfs(dev->name, PCI_SLOT(dev->devfn), - PCI_FUNC(dev->devfn), num_vfs); - for (i = 0; i < num_vfs; i++) { + PCI_FUNC(dev->devfn)); + for (i = 0; i < pci_get_word(cfg + PCI_SRIOV_TOTAL_VF); i++) { pci_set_enabled(dev->exp.sriov_pf.vf[i], false); } - dev->exp.sriov_pf.num_vfs = 0; + + pci_set_word(dev->wmask + dev->exp.sriov_cap + PCI_SRIOV_NUM_VF, 0xffff); } void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, @@ -236,6 +234,17 @@ void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, } else { unregister_vfs(dev); } + } else if (range_covers_byte(off, len, PCI_SRIOV_NUM_VF)) { + uint8_t *cfg = dev->config + sriov_cap; + uint8_t *wmask = dev->wmask + sriov_cap; + uint16_t num_vfs = pci_get_word(cfg + PCI_SRIOV_NUM_VF); + uint16_t wmask_val = PCI_SRIOV_CTRL_MSE | PCI_SRIOV_CTRL_ARI; + + if (num_vfs <= pci_get_word(cfg + PCI_SRIOV_TOTAL_VF)) { + wmask_val |= PCI_SRIOV_CTRL_VFE; + } + + pci_set_word(wmask + PCI_SRIOV_CTRL, wmask_val); } } @@ -252,6 +261,8 @@ void pcie_sriov_pf_reset(PCIDevice *dev) unregister_vfs(dev); pci_set_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF, 0); + pci_set_word(dev->wmask + sriov_cap + PCI_SRIOV_CTRL, + PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE | PCI_SRIOV_CTRL_ARI); /* * Default is to use 4K pages, software can modify it @@ -298,7 +309,7 @@ PCIDevice *pcie_sriov_get_pf(PCIDevice *dev) PCIDevice *pcie_sriov_get_vf_at_index(PCIDevice *dev, int n) { assert(!pci_is_vf(dev)); - if (n < dev->exp.sriov_pf.num_vfs) { + if (n < pcie_sriov_num_vfs(dev)) { return dev->exp.sriov_pf.vf[n]; } return NULL; @@ -306,5 +317,10 @@ PCIDevice *pcie_sriov_get_vf_at_index(PCIDevice *dev, int n) uint16_t pcie_sriov_num_vfs(PCIDevice *dev) { - return dev->exp.sriov_pf.num_vfs; + uint16_t sriov_cap = dev->exp.sriov_cap; + uint8_t *cfg = dev->config + sriov_cap; + + return sriov_cap && + (pci_get_word(cfg + PCI_SRIOV_CTRL) & PCI_SRIOV_CTRL_VFE) ? + pci_get_word(cfg + PCI_SRIOV_NUM_VF) : 0; } diff --git a/hw/pci/trace-events b/hw/pci/trace-events index 19643aa8c6b0..e98f575a9d19 100644 --- a/hw/pci/trace-events +++ b/hw/pci/trace-events @@ -14,7 +14,7 @@ msix_write_config(char *name, bool enabled, bool masked) "dev %s enabled %d mask # hw/pci/pcie_sriov.c sriov_register_vfs(const char *name, int slot, int function, int num_vfs) "%s %02x:%x: creating %d vf devs" -sriov_unregister_vfs(const char *name, int slot, int function, int num_vfs) "%s %02x:%x: Unregistering %d vf devs" +sriov_unregister_vfs(const char *name, int slot, int function) "%s %02x:%x: Unregistering vf devs" sriov_config_write(const char *name, int slot, int fun, uint32_t offset, uint32_t val, uint32_t len) "%s %02x:%x: sriov offset 0x%x val 0x%x len %d" # pcie.c diff --git a/include/hw/pci/pcie_sriov.h b/include/hw/pci/pcie_sriov.h index 70649236c18a..5148c5b77dd1 100644 --- a/include/hw/pci/pcie_sriov.h +++ b/include/hw/pci/pcie_sriov.h @@ -16,7 +16,6 @@ #include "hw/pci/pci.h" typedef struct PCIESriovPF { - uint16_t num_vfs; /* Number of virtual functions created */ uint8_t vf_bar_type[PCI_NUM_REGIONS]; /* Store type for each VF bar */ PCIDevice **vf; /* Pointer to an array of num_vfs VF devices */ } PCIESriovPF; From f9efcd47110de43dd841ada5bd1a40ec169eabca Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Thu, 16 Jan 2025 18:01:02 +0900 Subject: [PATCH 0193/1179] pcie_sriov: Register VFs after migration pcie_sriov doesn't have code to restore its state after migration, but igb, which uses pcie_sriov, naively claimed its migration capability. Add code to register VFs after migration and fix igb migration. Fixes: 3a977deebe6b ("Intrdocue igb device emulation") Signed-off-by: Akihiko Odaki Message-Id: <20250116-reuse-v20-11-7cb370606368@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/pci/pci.c | 7 +++++++ hw/pci/pcie_sriov.c | 7 +++++++ include/hw/pci/pcie_sriov.h | 2 ++ 3 files changed, 16 insertions(+) diff --git a/hw/pci/pci.c b/hw/pci/pci.c index 3e29b30d5588..69a1b8c298fc 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -803,10 +803,17 @@ static bool migrate_is_not_pcie(void *opaque, int version_id) return !pci_is_express((PCIDevice *)opaque); } +static int pci_post_load(void *opaque, int version_id) +{ + pcie_sriov_pf_post_load(opaque); + return 0; +} + const VMStateDescription vmstate_pci_device = { .name = "PCIDevice", .version_id = 2, .minimum_version_id = 1, + .post_load = pci_post_load, .fields = (const VMStateField[]) { VMSTATE_INT32_POSITIVE_LE(version_id, PCIDevice), VMSTATE_BUFFER_UNSAFE_INFO_TEST(config, PCIDevice, diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index 69609c112e31..1eb4358256de 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -248,6 +248,13 @@ void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, } } +void pcie_sriov_pf_post_load(PCIDevice *dev) +{ + if (dev->exp.sriov_cap) { + register_vfs(dev); + } +} + /* Reset SR/IOV */ void pcie_sriov_pf_reset(PCIDevice *dev) diff --git a/include/hw/pci/pcie_sriov.h b/include/hw/pci/pcie_sriov.h index 5148c5b77dd1..c5d2d318d330 100644 --- a/include/hw/pci/pcie_sriov.h +++ b/include/hw/pci/pcie_sriov.h @@ -57,6 +57,8 @@ void pcie_sriov_pf_add_sup_pgsize(PCIDevice *dev, uint16_t opt_sup_pgsize); void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, uint32_t val, int len); +void pcie_sriov_pf_post_load(PCIDevice *dev); + /* Reset SR/IOV */ void pcie_sriov_pf_reset(PCIDevice *dev); From 9e837c961a883392f8c4707a8d3d2e6c6aa793b6 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 18 Jan 2025 03:22:40 +1000 Subject: [PATCH 0194/1179] qtest/libqos/pci: Do not write to PBA memory The PCI Local Bus Specification says the result of writes to MSI-X PBA memory is undefined. QEMU implements them as no-ops, so remove the pointless write from qpci_msix_pending(). Signed-off-by: Nicholas Piggin Message-Id: <20250117172244.406206-2-npiggin@gmail.com> Reviewed-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- tests/qtest/libqos/pci.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/qtest/libqos/pci.c b/tests/qtest/libqos/pci.c index b23d72346b6e..a59197b9922e 100644 --- a/tests/qtest/libqos/pci.c +++ b/tests/qtest/libqos/pci.c @@ -328,8 +328,6 @@ bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry) g_assert(dev->msix_enabled); pba_entry = qpci_io_readl(dev, dev->msix_pba_bar, dev->msix_pba_off + off); - qpci_io_writel(dev, dev->msix_pba_bar, dev->msix_pba_off + off, - pba_entry & ~(1 << bit_n)); return (pba_entry & (1 << bit_n)) != 0; } From 44ed44aefec571041fe3b3a8b6849613a74b520a Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 18 Jan 2025 03:28:40 +1000 Subject: [PATCH 0195/1179] hw/pci/msix: Warn on PBA writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Of the MSI-X PBA pending bits, the PCI Local Bus Specification says: Software should never write, and should only read Pending Bits. If software writes to Pending Bits, the result is undefined. Log a GUEST_ERROR message if the PBA is written to by software. Cc: Marcel Apfelbaum Cc: Dmitry Fleytman Cc: Sriram Yagnaraman Signed-off-by: Nicholas Piggin Message-Id: <20250117172842.406338-2-npiggin@gmail.com> Reviewed-by: Phil Dennis-Jordan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/pci/msix.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hw/pci/msix.c b/hw/pci/msix.c index 57ec7084a475..66f27b9d7120 100644 --- a/hw/pci/msix.c +++ b/hw/pci/msix.c @@ -15,6 +15,7 @@ */ #include "qemu/osdep.h" +#include "qemu/log.h" #include "hw/pci/msi.h" #include "hw/pci/msix.h" #include "hw/pci/pci.h" @@ -260,6 +261,14 @@ static uint64_t msix_pba_mmio_read(void *opaque, hwaddr addr, static void msix_pba_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { + PCIDevice *dev = opaque; + + qemu_log_mask(LOG_GUEST_ERROR, + "PCI [%s:%02x:%02x.%x] attempt to write to MSI-X " + "PBA at 0x%" FMT_PCIBUS ", ignoring.\n", + pci_root_bus_path(dev), pci_dev_bus_num(dev), + PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn), + addr); } static const MemoryRegionOps msix_pba_mmio_ops = { From f6fc01c7866639649b9af58ad50a7367b2d18eae Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 18 Jan 2025 03:28:41 +1000 Subject: [PATCH 0196/1179] hw/pci: Assert a bar is not registered multiple times Nothing should be doing this, but it doesn't get caught by pci_register_bar(). Add an assertion to prevent misuse. Signed-off-by: Nicholas Piggin Message-Id: <20250117172842.406338-3-npiggin@gmail.com> Reviewed-by: Phil Dennis-Jordan Signed-off-by: Nicholas Piggin Reviewed-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/pci/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/pci/pci.c b/hw/pci/pci.c index 69a1b8c298fc..1d42847ef044 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -1398,6 +1398,7 @@ void pci_register_bar(PCIDevice *pci_dev, int region_num, assert(hdr_type != PCI_HEADER_TYPE_BRIDGE || region_num < 2); r = &pci_dev->io_regions[region_num]; + assert(!r->size); r->addr = PCI_BAR_UNMAPPED; r->size = size; r->type = type; From de538288e4dac21332cc94ba9727ed8ec8fe5ea1 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 17 Jan 2025 20:21:06 +0100 Subject: [PATCH 0197/1179] hw/i386/pc: Fix crash that occurs when introspecting TYPE_PC_MACHINE machines QEMU currently crashes when you try to inspect the machines based on TYPE_PC_MACHINE for their properties: $ echo '{ "execute": "qmp_capabilities" } { "execute": "qom-list-properties","arguments": { "typename": "pc-q35-10.0-machine"}}' \ | ./qemu-system-x86_64 -M pc -qmp stdio {"QMP": {"version": {"qemu": {"micro": 50, "minor": 2, "major": 9}, "package": "v9.2.0-1070-g87e115c122-dirty"}, "capabilities": ["oob"]}} {"return": {}} Segmentation fault (core dumped) This happens because TYPE_PC_MACHINE machines add a machine_init- done_notifier in their instance_init function - but instance_init of machines are not only called for machines that are realized, but also for machines that are introspected, so in this case the listener is added for a q35 machine that is never realized. But since there is already a running pc machine, the listener function is triggered immediately, causing a crash since it was not for the right machine it was meant for. Such listener functions must never be installed from an instance_init function. Let's do it from pc_basic_device_init() instead - this function is called from the MachineClass->init() function instead, i.e. guaranteed to be only called once in the lifetime of a QEMU process. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2779 Signed-off-by: Thomas Huth Message-Id: <20250117192106.471029-1-thuth@redhat.com> Reviewed-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/i386/pc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hw/i386/pc.c b/hw/i386/pc.c index b46975c8a4db..85b8a764554b 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1241,6 +1241,9 @@ void pc_basic_device_init(struct PCMachineState *pcms, /* Super I/O */ pc_superio_init(isa_bus, create_fdctrl, pcms->i8042_enabled, pcms->vmport != ON_OFF_AUTO_ON, &error_fatal); + + pcms->machine_done.notify = pc_machine_done; + qemu_add_machine_init_done_notifier(&pcms->machine_done); } void pc_nic_init(PCMachineClass *pcmc, ISABus *isa_bus, PCIBus *pci_bus) @@ -1714,9 +1717,6 @@ static void pc_machine_initfn(Object *obj) if (pcmc->pci_enabled) { cxl_machine_init(obj, &pcms->cxl_devices_state); } - - pcms->machine_done.notify = pc_machine_done; - qemu_add_machine_init_done_notifier(&pcms->machine_done); } static void pc_machine_reset(MachineState *machine, ResetType type) From 38ef383073b8ee59d598643160f206a19a46237f Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 23 Jan 2025 21:47:08 +0100 Subject: [PATCH 0198/1179] hw/i386/microvm: Fix crash that occurs when introspecting the microvm machine QEMU currently crashes when you try to inspect the properties of the microvm machine: $ echo '{ "execute": "qmp_capabilities" } { "execute": "qom-list-properties","arguments": { "typename": "microvm-machine"}}' | \ ./qemu-system-x86_64 -qmp stdio {"QMP": {"version": {"qemu": {"micro": 50, "minor": 2, "major": 9}, "package": "v9.2.0-1072-g60af367187-dirty"}, "capabilities": ["oob"]}} {"return": {}} qemu-system-x86_64: ../qemu/hw/i386/acpi-microvm.c:250: void acpi_setup_microvm(MicrovmMachineState *): Assertion `x86ms->fw_cfg' failed. Aborted (core dumped) This happens because the microvm machine adds a machine_done (and a powerdown_req) notifier in their instance_init function - however, the instance_init of machines are not only called for machines that are realized, but also for machines that are introspected, so in this case the listener is added for a microvm machine that is never realized. And since there is already a running machine, the listener function is triggered immediately, causing a crash since it was not for the right machine it was meant for. Such listener functions must never be installed from an instance_init function. Let's do it from microvm_machine_state_init() instead - this function is the MachineClass->init() function instead, i.e. guaranteed to be only called once in the lifetime of a QEMU process. Since the microvm_machine_done() and microvm_powerdown_req() were defined quite late in the microvm.c file, we have to move them now also earlier, so that we can get their function pointers from microvm_machine_state_init() without having to introduce a separate prototype for those functions earlier. Reviewed-by: Sergio Lopez Signed-off-by: Thomas Huth Message-Id: <20250123204708.1560305-1-thuth@redhat.com> Reviewed-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/i386/microvm.c | 66 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/hw/i386/microvm.c b/hw/i386/microvm.c index a8d354aabe53..d0a236c74f36 100644 --- a/hw/i386/microvm.c +++ b/hw/i386/microvm.c @@ -451,11 +451,44 @@ static HotplugHandler *microvm_get_hotplug_handler(MachineState *machine, return NULL; } +static void microvm_machine_done(Notifier *notifier, void *data) +{ + MicrovmMachineState *mms = container_of(notifier, MicrovmMachineState, + machine_done); + X86MachineState *x86ms = X86_MACHINE(mms); + + acpi_setup_microvm(mms); + dt_setup_microvm(mms); + fw_cfg_add_e820(x86ms->fw_cfg); +} + +static void microvm_powerdown_req(Notifier *notifier, void *data) +{ + MicrovmMachineState *mms = container_of(notifier, MicrovmMachineState, + powerdown_req); + X86MachineState *x86ms = X86_MACHINE(mms); + + if (x86ms->acpi_dev) { + Object *obj = OBJECT(x86ms->acpi_dev); + AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(obj); + adevc->send_event(ACPI_DEVICE_IF(x86ms->acpi_dev), + ACPI_POWER_DOWN_STATUS); + } +} + static void microvm_machine_state_init(MachineState *machine) { MicrovmMachineState *mms = MICROVM_MACHINE(machine); X86MachineState *x86ms = X86_MACHINE(machine); + /* State */ + mms->kernel_cmdline_fixed = false; + + mms->machine_done.notify = microvm_machine_done; + qemu_add_machine_init_done_notifier(&mms->machine_done); + mms->powerdown_req.notify = microvm_powerdown_req; + qemu_register_powerdown_notifier(&mms->powerdown_req); + microvm_memory_init(mms); x86_cpus_init(x86ms, CPU_VERSION_LATEST); @@ -581,31 +614,6 @@ static void microvm_machine_set_auto_kernel_cmdline(Object *obj, bool value, mms->auto_kernel_cmdline = value; } -static void microvm_machine_done(Notifier *notifier, void *data) -{ - MicrovmMachineState *mms = container_of(notifier, MicrovmMachineState, - machine_done); - X86MachineState *x86ms = X86_MACHINE(mms); - - acpi_setup_microvm(mms); - dt_setup_microvm(mms); - fw_cfg_add_e820(x86ms->fw_cfg); -} - -static void microvm_powerdown_req(Notifier *notifier, void *data) -{ - MicrovmMachineState *mms = container_of(notifier, MicrovmMachineState, - powerdown_req); - X86MachineState *x86ms = X86_MACHINE(mms); - - if (x86ms->acpi_dev) { - Object *obj = OBJECT(x86ms->acpi_dev); - AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(obj); - adevc->send_event(ACPI_DEVICE_IF(x86ms->acpi_dev), - ACPI_POWER_DOWN_STATUS); - } -} - static void microvm_machine_initfn(Object *obj) { MicrovmMachineState *mms = MICROVM_MACHINE(obj); @@ -617,14 +625,6 @@ static void microvm_machine_initfn(Object *obj) mms->isa_serial = true; mms->option_roms = true; mms->auto_kernel_cmdline = true; - - /* State */ - mms->kernel_cmdline_fixed = false; - - mms->machine_done.notify = microvm_machine_done; - qemu_add_machine_init_done_notifier(&mms->machine_done); - mms->powerdown_req.notify = microvm_powerdown_req; - qemu_register_powerdown_notifier(&mms->powerdown_req); } GlobalProperty microvm_properties[] = { From 14998e50c3638b07946ebe5972a327cc26e9f03c Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 3 Feb 2025 13:43:46 +0100 Subject: [PATCH 0199/1179] tests/qtest/vhost-user-test: Use modern virtio for vhost-user tests All other vhost-user tests here use modern virtio, too, so let's adjust the vhost-user-net test accordingly. Signed-off-by: Thomas Huth Message-Id: <20250203124346.169607-1-thuth@redhat.com> Reviewed-by: Fabiano Rosas Reviewed-by: Stefano Garzarella Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- tests/qtest/vhost-user-test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/qtest/vhost-user-test.c b/tests/qtest/vhost-user-test.c index 76d142a1584b..bd977ef28d54 100644 --- a/tests/qtest/vhost-user-test.c +++ b/tests/qtest/vhost-user-test.c @@ -1043,7 +1043,8 @@ static void test_multiqueue(void *obj, void *arg, QGuestAllocator *alloc) static uint64_t vu_net_get_features(TestServer *s) { - uint64_t features = 0x1ULL << VHOST_F_LOG_ALL | + uint64_t features = 0x1ULL << VIRTIO_F_VERSION_1 | + 0x1ULL << VHOST_F_LOG_ALL | 0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES; if (s->queues > 1) { From 8f90a54cfafe8c93a71930a96a63ccbd074f4142 Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Mon, 3 Feb 2025 16:19:04 +0000 Subject: [PATCH 0200/1179] hw/cxl: Introduce CXL_T3_MSIX_VECTOR enumeration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the `CXL_T3_MSIX_VECTOR` enumeration to specify MSIX vector assignments specific to the Type 3 (T3) CXL device. The primary goal of this change is to encapsulate the MSIX vector uses that are unique to the T3 device within an enumeration, improving code readability and maintenance by avoiding magic numbers. This organizational change allows for more explicit references to each vector’s role, thereby reducing the potential for misconfiguration. It also modified `mailbox_reg_init_common` to accept the `msi_n` parameter, reflecting the new MSIX vector setup. This pertains to the T3 device privately; other endpoints should refrain from using it, despite its public accessibility to all of them. Signed-off-by: Li Zhijian Signed-off-by: Jonathan Cameron Message-Id: <20250203161908.145406-2-Jonathan.Cameron@huawei.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/cxl/cxl-device-utils.c | 12 +++++------- hw/cxl/switch-mailbox-cci.c | 4 +++- hw/mem/cxl_type3.c | 20 ++++++++++++++------ include/hw/cxl/cxl_device.h | 4 ++-- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/hw/cxl/cxl-device-utils.c b/hw/cxl/cxl-device-utils.c index 035d034f6dd8..52ad1e4c3f7a 100644 --- a/hw/cxl/cxl-device-utils.c +++ b/hw/cxl/cxl-device-utils.c @@ -352,10 +352,8 @@ static void device_reg_init_common(CXLDeviceState *cxl_dstate) } } -static void mailbox_reg_init_common(CXLDeviceState *cxl_dstate) +static void mailbox_reg_init_common(CXLDeviceState *cxl_dstate, int msi_n) { - const uint8_t msi_n = 9; - /* 2048 payload size */ ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CAP, PAYLOAD_SIZE, CXL_MAILBOX_PAYLOAD_SHIFT); @@ -382,7 +380,7 @@ static void memdev_reg_init_common(CXLDeviceState *cxl_dstate) cxl_dstate->memdev_status = memdev_status_reg; } -void cxl_device_register_init_t3(CXLType3Dev *ct3d) +void cxl_device_register_init_t3(CXLType3Dev *ct3d, int msi_n) { CXLDeviceState *cxl_dstate = &ct3d->cxl_dstate; uint64_t *cap_h = cxl_dstate->caps_reg_state64; @@ -398,7 +396,7 @@ void cxl_device_register_init_t3(CXLType3Dev *ct3d) device_reg_init_common(cxl_dstate); cxl_device_cap_init(cxl_dstate, MAILBOX, 2, CXL_DEV_MAILBOX_VERSION); - mailbox_reg_init_common(cxl_dstate); + mailbox_reg_init_common(cxl_dstate, msi_n); cxl_device_cap_init(cxl_dstate, MEMORY_DEVICE, 0x4000, CXL_MEM_DEV_STATUS_VERSION); @@ -408,7 +406,7 @@ void cxl_device_register_init_t3(CXLType3Dev *ct3d) CXL_MAILBOX_MAX_PAYLOAD_SIZE); } -void cxl_device_register_init_swcci(CSWMBCCIDev *sw) +void cxl_device_register_init_swcci(CSWMBCCIDev *sw, int msi_n) { CXLDeviceState *cxl_dstate = &sw->cxl_dstate; uint64_t *cap_h = cxl_dstate->caps_reg_state64; @@ -423,7 +421,7 @@ void cxl_device_register_init_swcci(CSWMBCCIDev *sw) device_reg_init_common(cxl_dstate); cxl_device_cap_init(cxl_dstate, MAILBOX, 2, 1); - mailbox_reg_init_common(cxl_dstate); + mailbox_reg_init_common(cxl_dstate, msi_n); cxl_device_cap_init(cxl_dstate, MEMORY_DEVICE, 0x4000, 1); memdev_reg_init_common(cxl_dstate); diff --git a/hw/cxl/switch-mailbox-cci.c b/hw/cxl/switch-mailbox-cci.c index 65cdac6cc139..833b8246195c 100644 --- a/hw/cxl/switch-mailbox-cci.c +++ b/hw/cxl/switch-mailbox-cci.c @@ -17,10 +17,12 @@ #include "hw/qdev-properties.h" #include "hw/cxl/cxl.h" +#define CXL_SWCCI_MSIX_MBOX 3 + static void cswmbcci_reset(DeviceState *dev) { CSWMBCCIDev *cswmb = CXL_SWITCH_MAILBOX_CCI(dev); - cxl_device_register_init_swcci(cswmb); + cxl_device_register_init_swcci(cswmb, CXL_SWCCI_MSIX_MBOX); } static void cswbcci_realize(PCIDevice *pci_dev, Error **errp) diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c index 0ae1704a345c..ebc0ec536e45 100644 --- a/hw/mem/cxl_type3.c +++ b/hw/mem/cxl_type3.c @@ -30,6 +30,14 @@ #include "hw/cxl/cxl.h" #include "hw/pci/msix.h" +/* type3 device private */ +enum CXL_T3_MSIX_VECTOR { + CXL_T3_MSIX_PCIE_DOE_TABLE_ACCESS = 0, + CXL_T3_MSIX_EVENT_START = 2, + CXL_T3_MSIX_MBOX = CXL_T3_MSIX_EVENT_START + CXL_EVENT_TYPE_MAX, + CXL_T3_MSIX_VECTOR_NR +}; + #define DWORD_BYTE 4 #define CXL_CAPACITY_MULTIPLIER (256 * MiB) @@ -843,7 +851,6 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) ComponentRegisters *regs = &cxl_cstate->crb; MemoryRegion *mr = ®s->component_registers; uint8_t *pci_conf = pci_dev->config; - unsigned short msix_num = 10; int i, rc; uint16_t count; @@ -884,16 +891,17 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) &ct3d->cxl_dstate.device_registers); /* MSI(-X) Initialization */ - rc = msix_init_exclusive_bar(pci_dev, msix_num, 4, NULL); + rc = msix_init_exclusive_bar(pci_dev, CXL_T3_MSIX_VECTOR_NR, 4, NULL); if (rc) { goto err_address_space_free; } - for (i = 0; i < msix_num; i++) { + for (i = 0; i < CXL_T3_MSIX_VECTOR_NR; i++) { msix_vector_use(pci_dev, i); } /* DOE Initialization */ - pcie_doe_init(pci_dev, &ct3d->doe_cdat, 0x190, doe_cdat_prot, true, 0); + pcie_doe_init(pci_dev, &ct3d->doe_cdat, 0x190, doe_cdat_prot, true, + CXL_T3_MSIX_PCIE_DOE_TABLE_ACCESS); cxl_cstate->cdat.build_cdat_table = ct3_build_cdat_table; cxl_cstate->cdat.free_cdat_table = ct3_free_cdat_table; @@ -908,7 +916,7 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) if (rc) { goto err_release_cdat; } - cxl_event_init(&ct3d->cxl_dstate, 2); + cxl_event_init(&ct3d->cxl_dstate, CXL_T3_MSIX_EVENT_START); /* Set default value for patrol scrub attributes */ ct3d->patrol_scrub_attrs.scrub_cycle_cap = @@ -1202,7 +1210,7 @@ static void ct3d_reset(DeviceState *dev) pcie_cap_fill_link_ep_usp(PCI_DEVICE(dev), ct3d->width, ct3d->speed); cxl_component_register_init_common(reg_state, write_msk, CXL2_TYPE3_DEVICE); - cxl_device_register_init_t3(ct3d); + cxl_device_register_init_t3(ct3d, CXL_T3_MSIX_MBOX); /* * Bring up an endpoint to target with MCTP over VDM. diff --git a/include/hw/cxl/cxl_device.h b/include/hw/cxl/cxl_device.h index 561b375dc86d..3a0ee7e8e799 100644 --- a/include/hw/cxl/cxl_device.h +++ b/include/hw/cxl/cxl_device.h @@ -264,8 +264,8 @@ void cxl_device_register_block_init(Object *obj, CXLDeviceState *dev, typedef struct CXLType3Dev CXLType3Dev; typedef struct CSWMBCCIDev CSWMBCCIDev; /* Set up default values for the register block */ -void cxl_device_register_init_t3(CXLType3Dev *ct3d); -void cxl_device_register_init_swcci(CSWMBCCIDev *sw); +void cxl_device_register_init_t3(CXLType3Dev *ct3d, int msi_n); +void cxl_device_register_init_swcci(CSWMBCCIDev *sw, int msi_n); /* * CXL r3.1 Section 8.2.8.1: CXL Device Capabilities Array Register From 0401c4328f4d18be540fd432c2bbacce4531d14f Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Mon, 3 Feb 2025 16:19:05 +0000 Subject: [PATCH 0201/1179] hw/mem/cxl_type3: Add paired msix_uninit_exclusive_bar() call msix_uninit_exclusive_bar() should be paired with msix_init_exclusive_bar() Ensure proper resource cleanup by adding the missing `msix_uninit_exclusive_bar()` call for the Type3 CXL device. Signed-off-by: Li Zhijian Signed-off-by: Jonathan Cameron Message-Id: <20250203161908.145406-3-Jonathan.Cameron@huawei.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/mem/cxl_type3.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c index ebc0ec536e45..4775aab0d619 100644 --- a/hw/mem/cxl_type3.c +++ b/hw/mem/cxl_type3.c @@ -944,6 +944,7 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) err_release_cdat: cxl_doe_cdat_release(cxl_cstate); err_free_special_ops: + msix_uninit_exclusive_bar(pci_dev); g_free(regs->special_ops); err_address_space_free: if (ct3d->dc.host_dc) { @@ -967,6 +968,7 @@ static void ct3_exit(PCIDevice *pci_dev) pcie_aer_exit(pci_dev); cxl_doe_cdat_release(cxl_cstate); + msix_uninit_exclusive_bar(pci_dev); g_free(regs->special_ops); if (ct3d->dc.host_dc) { cxl_destroy_dc_regions(ct3d); From 06953e7e5ea5e8fa0b7b704bdb66ab1e62f1f378 Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Mon, 3 Feb 2025 16:19:06 +0000 Subject: [PATCH 0202/1179] hw/mem/cxl_type3: Fix special_ops memory leak on msix_init_exclusive_bar() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address a memory leak issue by ensuring `regs->special_ops` is freed when `msix_init_exclusive_bar()` encounters an error during CXL Type3 device initialization. Additionally, this patch renames err_address_space_free to err_msix_uninit for better clarity and logical flow Signed-off-by: Li Zhijian Signed-off-by: Jonathan Cameron Message-Id: <20250203161908.145406-4-Jonathan.Cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/mem/cxl_type3.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c index 4775aab0d619..ff6861889b54 100644 --- a/hw/mem/cxl_type3.c +++ b/hw/mem/cxl_type3.c @@ -893,7 +893,7 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) /* MSI(-X) Initialization */ rc = msix_init_exclusive_bar(pci_dev, CXL_T3_MSIX_VECTOR_NR, 4, NULL); if (rc) { - goto err_address_space_free; + goto err_free_special_ops; } for (i = 0; i < CXL_T3_MSIX_VECTOR_NR; i++) { msix_vector_use(pci_dev, i); @@ -907,7 +907,7 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) cxl_cstate->cdat.free_cdat_table = ct3_free_cdat_table; cxl_cstate->cdat.private = ct3d; if (!cxl_doe_cdat_init(cxl_cstate, errp)) { - goto err_free_special_ops; + goto err_msix_uninit; } pcie_cap_deverr_init(pci_dev); @@ -943,10 +943,10 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) err_release_cdat: cxl_doe_cdat_release(cxl_cstate); -err_free_special_ops: +err_msix_uninit: msix_uninit_exclusive_bar(pci_dev); +err_free_special_ops: g_free(regs->special_ops); -err_address_space_free: if (ct3d->dc.host_dc) { cxl_destroy_dc_regions(ct3d); address_space_destroy(&ct3d->dc.host_dc_as); From d3c92cf6dcab028d05f306d4d50511aa805d2385 Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Mon, 3 Feb 2025 16:19:07 +0000 Subject: [PATCH 0203/1179] hw/mem/cxl_type3: Ensure errp is set on realization failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simply pass the errp to its callee which will set errp if needed, to enhance error reporting for CXL Type 3 device initialization by setting the errp when realization functions fail. Previously, failing to set `errp` could result in errors being overlooked, causing the system to mistakenly treat failure scenarios as successful and potentially leading to redundant cleanup operations in ct3_exit(). Signed-off-by: Li Zhijian Signed-off-by: Jonathan Cameron Message-Id: <20250203161908.145406-5-Jonathan.Cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/mem/cxl_type3.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c index ff6861889b54..d8b45f9bd168 100644 --- a/hw/mem/cxl_type3.c +++ b/hw/mem/cxl_type3.c @@ -891,7 +891,7 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) &ct3d->cxl_dstate.device_registers); /* MSI(-X) Initialization */ - rc = msix_init_exclusive_bar(pci_dev, CXL_T3_MSIX_VECTOR_NR, 4, NULL); + rc = msix_init_exclusive_bar(pci_dev, CXL_T3_MSIX_VECTOR_NR, 4, errp); if (rc) { goto err_free_special_ops; } @@ -912,7 +912,7 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) pcie_cap_deverr_init(pci_dev); /* Leave a bit of room for expansion */ - rc = pcie_aer_init(pci_dev, PCI_ERR_VER, 0x200, PCI_ERR_SIZEOF, NULL); + rc = pcie_aer_init(pci_dev, PCI_ERR_VER, 0x200, PCI_ERR_SIZEOF, errp); if (rc) { goto err_release_cdat; } From 9ac2c42f43a536f53b3d4cad8a601ccb8640cbd8 Mon Sep 17 00:00:00 2001 From: Yao Xingtao Date: Mon, 3 Feb 2025 16:19:08 +0000 Subject: [PATCH 0204/1179] mem/cxl_type3: support 3, 6, 12 and 16 interleave ways Since the kernel does not check the interleave capability, a 3-way, 6-way, 12-way or 16-way region can be create normally. Applications can access the memory of 16-way region normally because qemu can convert hpa to dpa correctly for the power of 2 interleave ways, after kernel implementing the check, this kind of region will not be created any more. For non power of 2 interleave ways, applications could not access the memory normally and may occur some unexpected behaviors, such as segmentation fault. So implements this feature is needed. Link: https://lore.kernel.org/linux-cxl/3e84b919-7631-d1db-3e1d-33000f3f3868@fujitsu.com/ Signed-off-by: Yao Xingtao Signed-off-by: Jonathan Cameron Message-Id: <20250203161908.145406-6-Jonathan.Cameron@huawei.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/cxl/cxl-component-utils.c | 9 +++++++-- hw/mem/cxl_type3.c | 15 +++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/hw/cxl/cxl-component-utils.c b/hw/cxl/cxl-component-utils.c index cd116c040124..473895948b3d 100644 --- a/hw/cxl/cxl-component-utils.c +++ b/hw/cxl/cxl-component-utils.c @@ -243,8 +243,13 @@ static void hdm_init_common(uint32_t *reg_state, uint32_t *write_msk, ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, INTERLEAVE_4K, 1); ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, POISON_ON_ERR_CAP, 0); - ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, 3_6_12_WAY, 0); - ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, 16_WAY, 0); + if (type == CXL2_TYPE3_DEVICE) { + ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, 3_6_12_WAY, 1); + ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, 16_WAY, 1); + } else { + ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, 3_6_12_WAY, 0); + ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, 16_WAY, 0); + } ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, UIO, 0); ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, UIO_DECODER_COUNT, 0); diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c index d8b45f9bd168..6fffa21ead1f 100644 --- a/hw/mem/cxl_type3.c +++ b/hw/mem/cxl_type3.c @@ -1100,10 +1100,17 @@ static bool cxl_type3_dpa(CXLType3Dev *ct3d, hwaddr host_addr, uint64_t *dpa) continue; } - *dpa = dpa_base + - ((MAKE_64BIT_MASK(0, 8 + ig) & hpa_offset) | - ((MAKE_64BIT_MASK(8 + ig + iw, 64 - 8 - ig - iw) & hpa_offset) - >> iw)); + if (iw < 8) { + *dpa = dpa_base + + ((MAKE_64BIT_MASK(0, 8 + ig) & hpa_offset) | + ((MAKE_64BIT_MASK(8 + ig + iw, 64 - 8 - ig - iw) & hpa_offset) + >> iw)); + } else { + *dpa = dpa_base + + ((MAKE_64BIT_MASK(0, 8 + ig) & hpa_offset) | + ((((MAKE_64BIT_MASK(ig + iw, 64 - ig - iw) & hpa_offset) + >> (ig + iw)) / 3) << (ig + 8))); + } return true; } From 1456e90653c46aceb3dd83a7b9889a32aad7700d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Tue, 4 Feb 2025 09:42:02 +0000 Subject: [PATCH 0205/1179] hw/virtio: reset virtio balloon stats on machine reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a machine is first booted, all virtio balloon stats are initialized to their default value -1 (18446744073709551615 when represented as unsigned). They remain that way while the firmware is loading, and early phase of guest OS boot, until the virtio-balloon driver is activated. Thereafter the reported stats reflect the guest OS activity. When a machine reset is performed, however, the virtio-balloon stats are left unchanged by QEMU, despite the guest OS no longer updating them, nor indeed even still existing. IOW, the mgmt app keeps getting stale stats until the guest OS starts once more and loads the virtio-balloon driver (if ever). At that point the app will see a discontinuity in the reported values as they sudden jump from the stale value to the new value. This jump is indigituishable from a valid data update. While there is an "last-updated" field to report on the freshness of the stats, that does not unambiguously tell the mgmt app whether the stats are still conceptually relevant to the current running workload. It is more conceptually useful to reset the stats to their default values on machine reset, given that the previous guest workload the stats reflect no longer exists. The mgmt app can now clearly identify that there are is no stats information available from the current executing workload. The 'last-updated' time is also reset back to 0. IOW, on every machine reset, the virtio stats are in the same clean state they were when the macine first powered on. A functional test is added to validate this behaviour with a real world guest OS. Signed-off-by: Daniel P. Berrangé Message-Id: <20250204094202.2183262-1-berrange@redhat.com> Acked-by: David Hildenbrand Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- MAINTAINERS | 1 + hw/virtio/virtio-balloon.c | 30 ++++- include/hw/virtio/virtio-balloon.h | 4 + tests/functional/meson.build | 2 + tests/functional/test_virtio_balloon.py | 161 ++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100755 tests/functional/test_virtio_balloon.py diff --git a/MAINTAINERS b/MAINTAINERS index a928ce3e418d..013a57d5bf77 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2246,6 +2246,7 @@ F: include/hw/virtio/virtio-balloon.h F: system/balloon.c F: include/system/balloon.h F: tests/qtest/virtio-balloon-test.c +F: tests/functional/test_virtio_balloon.py virtio-9p M: Greg Kurz diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c index ad05768dedc4..2eb5a14fa2ce 100644 --- a/hw/virtio/virtio-balloon.c +++ b/hw/virtio/virtio-balloon.c @@ -31,7 +31,7 @@ #include "trace.h" #include "qemu/error-report.h" #include "migration/misc.h" - +#include "system/reset.h" #include "hw/virtio/virtio-bus.h" #include "hw/virtio/virtio-access.h" @@ -910,6 +910,8 @@ static void virtio_balloon_device_realize(DeviceState *dev, Error **errp) } reset_stats(s); + s->stats_last_update = 0; + qemu_register_resettable(OBJECT(dev)); } static void virtio_balloon_device_unrealize(DeviceState *dev) @@ -917,6 +919,7 @@ static void virtio_balloon_device_unrealize(DeviceState *dev) VirtIODevice *vdev = VIRTIO_DEVICE(dev); VirtIOBalloon *s = VIRTIO_BALLOON(dev); + qemu_unregister_resettable(OBJECT(dev)); if (s->free_page_bh) { qemu_bh_delete(s->free_page_bh); object_unref(OBJECT(s->iothread)); @@ -987,6 +990,27 @@ static void virtio_balloon_set_status(VirtIODevice *vdev, uint8_t status) } } +static ResettableState *virtio_balloon_get_reset_state(Object *obj) +{ + VirtIOBalloon *s = VIRTIO_BALLOON(obj); + return &s->reset_state; +} + +static void virtio_balloon_reset_enter(Object *obj, ResetType type) +{ + VirtIOBalloon *s = VIRTIO_BALLOON(obj); + + /* + * When waking up from standby/suspend-to-ram, do not reset stats. + */ + if (type == RESET_TYPE_WAKEUP) { + return; + } + + reset_stats(s); + s->stats_last_update = 0; +} + static void virtio_balloon_instance_init(Object *obj) { VirtIOBalloon *s = VIRTIO_BALLOON(obj); @@ -1038,6 +1062,7 @@ static void virtio_balloon_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); device_class_set_props(dc, virtio_balloon_properties); dc->vmsd = &vmstate_virtio_balloon; @@ -1050,6 +1075,9 @@ static void virtio_balloon_class_init(ObjectClass *klass, void *data) vdc->get_features = virtio_balloon_get_features; vdc->set_status = virtio_balloon_set_status; vdc->vmsd = &vmstate_virtio_balloon_device; + + rc->get_state = virtio_balloon_get_reset_state; + rc->phases.enter = virtio_balloon_reset_enter; } static const TypeInfo virtio_balloon_info = { diff --git a/include/hw/virtio/virtio-balloon.h b/include/hw/virtio/virtio-balloon.h index b12c18a43b69..0456c211c6e6 100644 --- a/include/hw/virtio/virtio-balloon.h +++ b/include/hw/virtio/virtio-balloon.h @@ -16,6 +16,7 @@ #define QEMU_VIRTIO_BALLOON_H #include "standard-headers/linux/virtio_balloon.h" +#include "hw/resettable.h" #include "hw/virtio/virtio.h" #include "system/iothread.h" #include "qom/object.h" @@ -71,6 +72,9 @@ struct VirtIOBalloon { bool qemu_4_0_config_size; uint32_t poison_val; + + /* State of the resettable container */ + ResettableState reset_state; }; #endif diff --git a/tests/functional/meson.build b/tests/functional/meson.build index cf80924ddc3a..2d399cc46447 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -44,6 +44,7 @@ test_timeouts = { 'riscv64_tuxrun' : 120, 's390x_ccw_virtio' : 420, 'sh4_tuxrun' : 240, + 'virtio_balloon': 120, } tests_generic_system = [ @@ -242,6 +243,7 @@ tests_x86_64_system_thorough = [ 'linux_initrd', 'multiprocess', 'netdev_ethtool', + 'virtio_balloon', 'virtio_gpu', 'x86_64_hotplug_cpu', 'x86_64_tuxrun', diff --git a/tests/functional/test_virtio_balloon.py b/tests/functional/test_virtio_balloon.py new file mode 100755 index 000000000000..67b48e1b4e8e --- /dev/null +++ b/tests/functional/test_virtio_balloon.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# +# virtio-balloon tests +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + +import time + +from qemu_test import QemuSystemTest, Asset +from qemu_test import wait_for_console_pattern +from qemu_test import exec_command_and_wait_for_pattern + +UNSET_STATS_VALUE = 18446744073709551615 + + +class VirtioBalloonx86(QemuSystemTest): + + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' + '/31/Server/x86_64/os/images/pxeboot/vmlinuz'), + 'd4738d03dbbe083ca610d0821d0a8f1488bebbdccef54ce33e3adb35fda00129') + + ASSET_INITRD = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' + '/31/Server/x86_64/os/images/pxeboot/initrd.img'), + '277cd6c7adf77c7e63d73bbb2cded8ef9e2d3a2f100000e92ff1f8396513cd8b') + + ASSET_DISKIMAGE = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' + '/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2'), + 'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0') + + DEFAULT_KERNEL_PARAMS = ('root=/dev/vda1 console=ttyS0 net.ifnames=0 ' + 'rd.rescue') + + def wait_for_console_pattern(self, success_message, vm=None): + wait_for_console_pattern( + self, + success_message, + failure_message="Kernel panic - not syncing", + vm=vm, + ) + + def mount_root(self): + self.wait_for_console_pattern('Entering emergency mode.') + prompt = '# ' + self.wait_for_console_pattern(prompt) + + exec_command_and_wait_for_pattern(self, 'mount /dev/vda1 /sysroot', + prompt) + exec_command_and_wait_for_pattern(self, 'chroot /sysroot', + prompt) + exec_command_and_wait_for_pattern(self, "modprobe virtio-balloon", + prompt) + + def assert_initial_stats(self): + ret = self.vm.qmp('qom-get', + {'path': '/machine/peripheral/balloon', + 'property': 'guest-stats'})['return'] + when = ret.get('last-update') + assert when == 0 + stats = ret.get('stats') + for name, val in stats.items(): + assert val == UNSET_STATS_VALUE + + def assert_running_stats(self, then): + ret = self.vm.qmp('qom-get', + {'path': '/machine/peripheral/balloon', + 'property': 'guest-stats'})['return'] + when = ret.get('last-update') + now = time.time() + + assert when > then and when < now + stats = ret.get('stats') + # Stat we expect this particular Kernel to have set + expectData = [ + "stat-available-memory", + "stat-disk-caches", + "stat-free-memory", + "stat-htlb-pgalloc", + "stat-htlb-pgfail", + "stat-major-faults", + "stat-minor-faults", + "stat-swap-in", + "stat-swap-out", + "stat-total-memory", + ] + for name, val in stats.items(): + if name in expectData: + assert val != UNSET_STATS_VALUE + else: + assert val == UNSET_STATS_VALUE + + def test_virtio_balloon_stats(self): + self.set_machine('q35') + kernel_path = self.ASSET_KERNEL.fetch() + initrd_path = self.ASSET_INITRD.fetch() + diskimage_path = self.ASSET_DISKIMAGE.fetch() + + self.vm.set_console() + self.vm.add_args("-S") + self.vm.add_args("-cpu", "max") + self.vm.add_args("-m", "2G") + # Slow down BIOS phase with boot menu, so that after a system + # reset, we can reliably catch the clean stats again in BIOS + # phase before the guest OS launches + self.vm.add_args("-boot", "menu=on") + self.vm.add_args("-machine", "q35,accel=kvm:tcg") + self.vm.add_args("-device", "virtio-balloon,id=balloon") + self.vm.add_args('-drive', + f'file={diskimage_path},if=none,id=drv0,snapshot=on') + self.vm.add_args('-device', 'virtio-blk-pci,bus=pcie.0,' + + 'drive=drv0,id=virtio-disk0,bootindex=1') + + self.vm.add_args( + "-kernel", + kernel_path, + "-initrd", + initrd_path, + "-append", + self.DEFAULT_KERNEL_PARAMS + ) + self.vm.launch() + + # Poll stats at 100ms + self.vm.qmp('qom-set', + {'path': '/machine/peripheral/balloon', + 'property': 'guest-stats-polling-interval', + 'value': 100 }) + + # We've not run any guest code yet, neither BIOS or guest, + # so stats should be all default values + self.assert_initial_stats() + + self.vm.qmp('cont') + + then = time.time() + self.mount_root() + self.assert_running_stats(then) + + # Race window between these two commands, where we + # rely on '-boot menu=on' to (hopefully) ensure we're + # still executing the BIOS when QEMU processes the + # 'stop', and thus have not loaded the virtio-balloon + # driver in the guest + self.vm.qmp('system_reset') + self.vm.qmp('stop') + + # If the above assumption held, we're in BIOS now and + # stats should be all back at their default values + self.assert_initial_stats() + self.vm.qmp('cont') + + then = time.time() + self.mount_root() + self.assert_running_stats(then) + + +if __name__ == '__main__': + QemuSystemTest.main() From 63dc0b8647391b372f3bb38ff1066f6b4a5e6ea1 Mon Sep 17 00:00:00 2001 From: Sairaj Kodilkar Date: Fri, 7 Feb 2025 10:23:53 +0530 Subject: [PATCH 0206/1179] amd_iommu: Use correct DTE field for interrupt passthrough Interrupt passthrough is determine by the bits 191,190,187-184. These bits are part of the 3rd quad word (i.e. index 2) in DTE. Hence replace dte[3] by dte[2]. Fixes: b44159fe0 ("x86_iommu/amd: Add interrupt remap support when VAPIC is not enabled") Signed-off-by: Sairaj Kodilkar Reviewed-by: Vasant Hegde Message-Id: <20250207045354.27329-2-sarunkod@amd.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/i386/amd_iommu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hw/i386/amd_iommu.c b/hw/i386/amd_iommu.c index 6b13ce894b1a..98f1209a3818 100644 --- a/hw/i386/amd_iommu.c +++ b/hw/i386/amd_iommu.c @@ -1309,15 +1309,15 @@ static int amdvi_int_remap_msi(AMDVIState *iommu, ret = -AMDVI_IR_ERR; break; case AMDVI_IOAPIC_INT_TYPE_NMI: - pass = dte[3] & AMDVI_DEV_NMI_PASS_MASK; + pass = dte[2] & AMDVI_DEV_NMI_PASS_MASK; trace_amdvi_ir_delivery_mode("nmi"); break; case AMDVI_IOAPIC_INT_TYPE_INIT: - pass = dte[3] & AMDVI_DEV_INT_PASS_MASK; + pass = dte[2] & AMDVI_DEV_INT_PASS_MASK; trace_amdvi_ir_delivery_mode("init"); break; case AMDVI_IOAPIC_INT_TYPE_EINT: - pass = dte[3] & AMDVI_DEV_EINT_PASS_MASK; + pass = dte[2] & AMDVI_DEV_EINT_PASS_MASK; trace_amdvi_ir_delivery_mode("eint"); break; default: From 3684717b7407cc395dc9bf522e193dbc85293dee Mon Sep 17 00:00:00 2001 From: Sairaj Kodilkar Date: Fri, 7 Feb 2025 10:23:54 +0530 Subject: [PATCH 0207/1179] amd_iommu: Use correct bitmask to set capability BAR AMD IOMMU provides the base address of control registers through IVRS table and PCI capability. Since this base address is of 64 bit, use 32 bits mask (instead of 16 bits) to set BAR low and high. Fixes: d29a09ca68 ("hw/i386: Introduce AMD IOMMU") Signed-off-by: Sairaj Kodilkar Reviewed-by: Vasant Hegde Message-Id: <20250207045354.27329-3-sarunkod@amd.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/i386/amd_iommu.c | 4 ++-- hw/i386/amd_iommu.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hw/i386/amd_iommu.c b/hw/i386/amd_iommu.c index 98f1209a3818..044fe432567d 100644 --- a/hw/i386/amd_iommu.c +++ b/hw/i386/amd_iommu.c @@ -1593,9 +1593,9 @@ static void amdvi_pci_realize(PCIDevice *pdev, Error **errp) /* reset AMDVI specific capabilities, all r/o */ pci_set_long(pdev->config + s->capab_offset, AMDVI_CAPAB_FEATURES); pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_BAR_LOW, - AMDVI_BASE_ADDR & ~(0xffff0000)); + AMDVI_BASE_ADDR & MAKE_64BIT_MASK(14, 18)); pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_BAR_HIGH, - (AMDVI_BASE_ADDR & ~(0xffff)) >> 16); + AMDVI_BASE_ADDR >> 32); pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_RANGE, 0xff000000); pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_MISC, 0); diff --git a/hw/i386/amd_iommu.h b/hw/i386/amd_iommu.h index e0dac4d9a96c..28125130c6fc 100644 --- a/hw/i386/amd_iommu.h +++ b/hw/i386/amd_iommu.h @@ -187,7 +187,7 @@ AMDVI_CAPAB_FLAG_HTTUNNEL | AMDVI_CAPAB_EFR_SUP) /* AMDVI default address */ -#define AMDVI_BASE_ADDR 0xfed80000 +#define AMDVI_BASE_ADDR 0xfed80000ULL /* page management constants */ #define AMDVI_PAGE_SHIFT 12 From 92cf61e70838c20adc82daa3170fdbb9d174b508 Mon Sep 17 00:00:00 2001 From: Jonah Palmer Date: Mon, 17 Feb 2025 09:49:32 -0500 Subject: [PATCH 0208/1179] vhost-iova-tree: Implement an IOVA-only tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates and supports an IOVA-only tree to support a SVQ IOVA->HVA and GPA->IOVA tree for host-only and guest-backed memory, respectively, in the next patch. The IOVA allocator still allocates an IOVA range but now adds this range to the IOVA-only tree as well as to the full IOVA->HVA tree. In the next patch, the full IOVA->HVA tree will be split into a partial SVQ IOVA->HVA tree and a GPA->IOVA tree. The motivation behind having an IOVA-only tree was to have a single tree that would keep track of all allocated IOVA ranges between the partial SVQ IOVA->HVA and GPA->IOVA trees. Signed-off-by: Jonah Palmer Acked-by: Eugenio Pérez Tested-by: Lei Yang Message-Id: <20250217144936.3589907-2-jonah.palmer@oracle.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/vhost-iova-tree.c | 26 ++++++++++++++++++++------ hw/virtio/vhost-iova-tree.h | 3 ++- hw/virtio/vhost-vdpa.c | 29 +++++++++++++++++++++-------- net/vhost-vdpa.c | 10 ++++++++-- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/hw/virtio/vhost-iova-tree.c b/hw/virtio/vhost-iova-tree.c index 3d03395a77b5..216885aa3c1b 100644 --- a/hw/virtio/vhost-iova-tree.c +++ b/hw/virtio/vhost-iova-tree.c @@ -28,6 +28,9 @@ struct VhostIOVATree { /* IOVA address to qemu memory maps. */ IOVATree *iova_taddr_map; + + /* Allocated IOVA addresses */ + IOVATree *iova_map; }; /** @@ -44,6 +47,7 @@ VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last) tree->iova_last = iova_last; tree->iova_taddr_map = iova_tree_new(); + tree->iova_map = iova_tree_new(); return tree; } @@ -53,6 +57,7 @@ VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last) void vhost_iova_tree_delete(VhostIOVATree *iova_tree) { iova_tree_destroy(iova_tree->iova_taddr_map); + iova_tree_destroy(iova_tree->iova_map); g_free(iova_tree); } @@ -75,6 +80,7 @@ const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree, * * @tree: The iova tree * @map: The iova map + * @taddr: The translated address (HVA) * * Returns: * - IOVA_OK if the map fits in the container @@ -83,19 +89,26 @@ const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree, * * It returns assignated iova in map->iova if return value is VHOST_DMA_MAP_OK. */ -int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map) +int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map, hwaddr taddr) { + int ret; + /* Some vhost devices do not like addr 0. Skip first page */ hwaddr iova_first = tree->iova_first ?: qemu_real_host_page_size(); - if (map->translated_addr + map->size < map->translated_addr || - map->perm == IOMMU_NONE) { + if (taddr + map->size < taddr || map->perm == IOMMU_NONE) { return IOVA_ERR_INVALID; } - /* Allocate a node in IOVA address */ - return iova_tree_alloc_map(tree->iova_taddr_map, map, iova_first, - tree->iova_last); + /* Allocate a node in the IOVA-only tree */ + ret = iova_tree_alloc_map(tree->iova_map, map, iova_first, tree->iova_last); + if (unlikely(ret != IOVA_OK)) { + return ret; + } + + /* Insert a node in the IOVA->HVA tree */ + map->translated_addr = taddr; + return iova_tree_insert(tree->iova_taddr_map, map); } /** @@ -107,4 +120,5 @@ int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map) void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map) { iova_tree_remove(iova_tree->iova_taddr_map, map); + iova_tree_remove(iova_tree->iova_map, map); } diff --git a/hw/virtio/vhost-iova-tree.h b/hw/virtio/vhost-iova-tree.h index 4adfd79ff039..525ce72b1d73 100644 --- a/hw/virtio/vhost-iova-tree.h +++ b/hw/virtio/vhost-iova-tree.h @@ -21,7 +21,8 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(VhostIOVATree, vhost_iova_tree_delete); const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *iova_tree, const DMAMap *map); -int vhost_iova_tree_map_alloc(VhostIOVATree *iova_tree, DMAMap *map); +int vhost_iova_tree_map_alloc(VhostIOVATree *iova_tree, DMAMap *map, + hwaddr taddr); void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map); #endif diff --git a/hw/virtio/vhost-vdpa.c b/hw/virtio/vhost-vdpa.c index 3cdaa12ed5ec..703dcfc9299f 100644 --- a/hw/virtio/vhost-vdpa.c +++ b/hw/virtio/vhost-vdpa.c @@ -360,14 +360,20 @@ static void vhost_vdpa_listener_region_add(MemoryListener *listener, llsize = int128_sub(llend, int128_make64(iova)); if (s->shadow_data) { int r; + hwaddr hw_vaddr = (hwaddr)(uintptr_t)vaddr; - mem_region.translated_addr = (hwaddr)(uintptr_t)vaddr, mem_region.size = int128_get64(llsize) - 1, mem_region.perm = IOMMU_ACCESS_FLAG(true, section->readonly), - r = vhost_iova_tree_map_alloc(s->iova_tree, &mem_region); + r = vhost_iova_tree_map_alloc(s->iova_tree, &mem_region, hw_vaddr); if (unlikely(r != IOVA_OK)) { error_report("Can't allocate a mapping (%d)", r); + + if (mem_region.translated_addr == hw_vaddr) { + error_report("Insertion to IOVA->HVA tree failed"); + /* Remove the mapping from the IOVA-only tree */ + goto fail_map; + } goto fail; } @@ -1142,16 +1148,23 @@ static void vhost_vdpa_svq_unmap_rings(struct vhost_dev *dev, * * @v: Vhost-vdpa device * @needle: The area to search iova + * @taddr: The translated address (HVA) * @errorp: Error pointer */ static bool vhost_vdpa_svq_map_ring(struct vhost_vdpa *v, DMAMap *needle, - Error **errp) + hwaddr taddr, Error **errp) { int r; - r = vhost_iova_tree_map_alloc(v->shared->iova_tree, needle); + r = vhost_iova_tree_map_alloc(v->shared->iova_tree, needle, taddr); if (unlikely(r != IOVA_OK)) { error_setg(errp, "Cannot allocate iova (%d)", r); + + if (needle->translated_addr == taddr) { + error_append_hint(errp, "Insertion to IOVA->HVA tree failed"); + /* Remove the mapping from the IOVA-only tree */ + vhost_iova_tree_remove(v->shared->iova_tree, *needle); + } return false; } @@ -1192,11 +1205,11 @@ static bool vhost_vdpa_svq_map_rings(struct vhost_dev *dev, vhost_svq_get_vring_addr(svq, &svq_addr); driver_region = (DMAMap) { - .translated_addr = svq_addr.desc_user_addr, .size = driver_size - 1, .perm = IOMMU_RO, }; - ok = vhost_vdpa_svq_map_ring(v, &driver_region, errp); + ok = vhost_vdpa_svq_map_ring(v, &driver_region, svq_addr.desc_user_addr, + errp); if (unlikely(!ok)) { error_prepend(errp, "Cannot create vq driver region: "); return false; @@ -1206,11 +1219,11 @@ static bool vhost_vdpa_svq_map_rings(struct vhost_dev *dev, addr->avail_user_addr = driver_region.iova + avail_offset; device_region = (DMAMap) { - .translated_addr = svq_addr.used_user_addr, .size = device_size - 1, .perm = IOMMU_RW, }; - ok = vhost_vdpa_svq_map_ring(v, &device_region, errp); + ok = vhost_vdpa_svq_map_ring(v, &device_region, svq_addr.used_user_addr, + errp); if (unlikely(!ok)) { error_prepend(errp, "Cannot create vq device region: "); vhost_vdpa_svq_unmap_ring(v, driver_region.translated_addr); diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index 231b45246c66..5a3a57203d07 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -510,14 +510,20 @@ static int vhost_vdpa_cvq_map_buf(struct vhost_vdpa *v, void *buf, size_t size, bool write) { DMAMap map = {}; + hwaddr taddr = (hwaddr)(uintptr_t)buf; int r; - map.translated_addr = (hwaddr)(uintptr_t)buf; map.size = size - 1; map.perm = write ? IOMMU_RW : IOMMU_RO, - r = vhost_iova_tree_map_alloc(v->shared->iova_tree, &map); + r = vhost_iova_tree_map_alloc(v->shared->iova_tree, &map, taddr); if (unlikely(r != IOVA_OK)) { error_report("Cannot map injected element"); + + if (map.translated_addr == taddr) { + error_report("Insertion to IOVA->HVA tree failed"); + /* Remove the mapping from the IOVA-only tree */ + goto dma_map_err; + } return r; } From 05063f55841babae7216d36105440ed8ba632938 Mon Sep 17 00:00:00 2001 From: Jonah Palmer Date: Mon, 17 Feb 2025 09:49:33 -0500 Subject: [PATCH 0209/1179] vhost-iova-tree, svq: Implement GPA->IOVA & partial IOVA->HVA trees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates and supports a GPA->IOVA tree and a partial IOVA->HVA tree by splitting up guest-backed memory maps and host-only memory maps from the full IOVA->HVA tree. That is, any guest-backed memory maps are now stored in the GPA->IOVA tree and host-only memory maps stay in the IOVA->HVA tree. Also propagates the GPAs (in_addr/out_addr) of a VirtQueueElement to vhost_svq_translate_addr() to translate GPAs to IOVAs via the GPA->IOVA tree (when descriptors are backed by guest memory). For descriptors backed by host-only memory, the existing partial SVQ IOVA->HVA tree is used. GPAs are unique in the guest's address space, ensuring unambiguous IOVA translations. This avoids the issue where different GPAs map to the same HVA, causing the original HVA->IOVA translation to potentially return an IOVA associated with the wrong intended GPA. Signed-off-by: Jonah Palmer Acked-by: Eugenio Pérez Message-Id: <20250217144936.3589907-3-jonah.palmer@oracle.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/vhost-iova-tree.c | 67 ++++++++++++++++++++++++++++++ hw/virtio/vhost-iova-tree.h | 5 +++ hw/virtio/vhost-shadow-virtqueue.c | 55 ++++++++++++++++-------- hw/virtio/vhost-shadow-virtqueue.h | 5 ++- hw/virtio/vhost-vdpa.c | 19 ++++----- include/qemu/iova-tree.h | 22 ++++++++++ net/vhost-vdpa.c | 2 +- util/iova-tree.c | 46 ++++++++++++++++++++ 8 files changed, 190 insertions(+), 31 deletions(-) diff --git a/hw/virtio/vhost-iova-tree.c b/hw/virtio/vhost-iova-tree.c index 216885aa3c1b..9d2d6a7af228 100644 --- a/hw/virtio/vhost-iova-tree.c +++ b/hw/virtio/vhost-iova-tree.c @@ -31,6 +31,9 @@ struct VhostIOVATree { /* Allocated IOVA addresses */ IOVATree *iova_map; + + /* GPA->IOVA address memory maps */ + IOVATree *gpa_iova_map; }; /** @@ -48,6 +51,7 @@ VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last) tree->iova_taddr_map = iova_tree_new(); tree->iova_map = iova_tree_new(); + tree->gpa_iova_map = gpa_tree_new(); return tree; } @@ -58,6 +62,7 @@ void vhost_iova_tree_delete(VhostIOVATree *iova_tree) { iova_tree_destroy(iova_tree->iova_taddr_map); iova_tree_destroy(iova_tree->iova_map); + iova_tree_destroy(iova_tree->gpa_iova_map); g_free(iova_tree); } @@ -122,3 +127,65 @@ void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map) iova_tree_remove(iova_tree->iova_taddr_map, map); iova_tree_remove(iova_tree->iova_map, map); } + +/** + * Find the IOVA address stored from a guest memory address (GPA) + * + * @tree: The VhostIOVATree + * @map: The map with the guest memory address + * + * Returns the stored GPA->IOVA mapping, or NULL if not found. + */ +const DMAMap *vhost_iova_tree_find_gpa(const VhostIOVATree *tree, + const DMAMap *map) +{ + return iova_tree_find_iova(tree->gpa_iova_map, map); +} + +/** + * Allocate a new IOVA range and add the mapping to the GPA->IOVA tree + * + * @tree: The VhostIOVATree + * @map: The IOVA mapping + * @taddr: The translated address (GPA) + * + * Returns: + * - IOVA_OK if the map fits both containers + * - IOVA_ERR_INVALID if the map does not make sense (like size overflow) + * - IOVA_ERR_NOMEM if the IOVA-only tree cannot allocate more space + * + * It returns an assigned IOVA in map->iova if the return value is IOVA_OK. + */ +int vhost_iova_tree_map_alloc_gpa(VhostIOVATree *tree, DMAMap *map, hwaddr taddr) +{ + int ret; + + /* Some vhost devices don't like addr 0. Skip first page */ + hwaddr iova_first = tree->iova_first ?: qemu_real_host_page_size(); + + if (taddr + map->size < taddr || map->perm == IOMMU_NONE) { + return IOVA_ERR_INVALID; + } + + /* Allocate a node in the IOVA-only tree */ + ret = iova_tree_alloc_map(tree->iova_map, map, iova_first, tree->iova_last); + if (unlikely(ret != IOVA_OK)) { + return ret; + } + + /* Insert a node in the GPA->IOVA tree */ + map->translated_addr = taddr; + return gpa_tree_insert(tree->gpa_iova_map, map); +} + +/** + * Remove existing mappings from the IOVA-only and GPA->IOVA trees + * + * @tree: The VhostIOVATree + * @map: The map to remove + */ +void vhost_iova_tree_remove_gpa(VhostIOVATree *iova_tree, DMAMap map) +{ + iova_tree_remove(iova_tree->gpa_iova_map, map); + iova_tree_remove(iova_tree->iova_map, map); +} diff --git a/hw/virtio/vhost-iova-tree.h b/hw/virtio/vhost-iova-tree.h index 525ce72b1d73..0c4ba5abd520 100644 --- a/hw/virtio/vhost-iova-tree.h +++ b/hw/virtio/vhost-iova-tree.h @@ -24,5 +24,10 @@ const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *iova_tree, int vhost_iova_tree_map_alloc(VhostIOVATree *iova_tree, DMAMap *map, hwaddr taddr); void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map); +const DMAMap *vhost_iova_tree_find_gpa(const VhostIOVATree *iova_tree, + const DMAMap *map); +int vhost_iova_tree_map_alloc_gpa(VhostIOVATree *iova_tree, DMAMap *map, + hwaddr taddr); +void vhost_iova_tree_remove_gpa(VhostIOVATree *iova_tree, DMAMap map); #endif diff --git a/hw/virtio/vhost-shadow-virtqueue.c b/hw/virtio/vhost-shadow-virtqueue.c index 37aca8b43186..30ba565f034c 100644 --- a/hw/virtio/vhost-shadow-virtqueue.c +++ b/hw/virtio/vhost-shadow-virtqueue.c @@ -78,24 +78,39 @@ uint16_t vhost_svq_available_slots(const VhostShadowVirtqueue *svq) * @vaddr: Translated IOVA addresses * @iovec: Source qemu's VA addresses * @num: Length of iovec and minimum length of vaddr + * @gpas: Descriptors' GPAs, if backed by guest memory */ static bool vhost_svq_translate_addr(const VhostShadowVirtqueue *svq, hwaddr *addrs, const struct iovec *iovec, - size_t num) + size_t num, const hwaddr *gpas) { if (num == 0) { return true; } for (size_t i = 0; i < num; ++i) { - DMAMap needle = { - .translated_addr = (hwaddr)(uintptr_t)iovec[i].iov_base, - .size = iovec[i].iov_len, - }; Int128 needle_last, map_last; size_t off; + const DMAMap *map; + DMAMap needle; + + /* Check if the descriptor is backed by guest memory */ + if (gpas) { + /* Search the GPA->IOVA tree */ + needle = (DMAMap) { + .translated_addr = gpas[i], + .size = iovec[i].iov_len, + }; + map = vhost_iova_tree_find_gpa(svq->iova_tree, &needle); + } else { + /* Search the IOVA->HVA tree */ + needle = (DMAMap) { + .translated_addr = (hwaddr)(uintptr_t)iovec[i].iov_base, + .size = iovec[i].iov_len, + }; + map = vhost_iova_tree_find_iova(svq->iova_tree, &needle); + } - const DMAMap *map = vhost_iova_tree_find_iova(svq->iova_tree, &needle); /* * Map cannot be NULL since iova map contains all guest space and * qemu already has a physical address mapped @@ -130,6 +145,7 @@ static bool vhost_svq_translate_addr(const VhostShadowVirtqueue *svq, * @sg: Cache for hwaddr * @iovec: The iovec from the guest * @num: iovec length + * @addr: Descriptors' GPAs, if backed by guest memory * @more_descs: True if more descriptors come in the chain * @write: True if they are writeable descriptors * @@ -137,7 +153,8 @@ static bool vhost_svq_translate_addr(const VhostShadowVirtqueue *svq, */ static bool vhost_svq_vring_write_descs(VhostShadowVirtqueue *svq, hwaddr *sg, const struct iovec *iovec, size_t num, - bool more_descs, bool write) + const hwaddr *addr, bool more_descs, + bool write) { uint16_t i = svq->free_head, last = svq->free_head; unsigned n; @@ -149,7 +166,7 @@ static bool vhost_svq_vring_write_descs(VhostShadowVirtqueue *svq, hwaddr *sg, return true; } - ok = vhost_svq_translate_addr(svq, sg, iovec, num); + ok = vhost_svq_translate_addr(svq, sg, iovec, num, addr); if (unlikely(!ok)) { return false; } @@ -174,8 +191,9 @@ static bool vhost_svq_vring_write_descs(VhostShadowVirtqueue *svq, hwaddr *sg, static bool vhost_svq_add_split(VhostShadowVirtqueue *svq, const struct iovec *out_sg, size_t out_num, + const hwaddr *out_addr, const struct iovec *in_sg, size_t in_num, - unsigned *head) + const hwaddr *in_addr, unsigned *head) { unsigned avail_idx; vring_avail_t *avail = svq->vring.avail; @@ -191,13 +209,14 @@ static bool vhost_svq_add_split(VhostShadowVirtqueue *svq, return false; } - ok = vhost_svq_vring_write_descs(svq, sgs, out_sg, out_num, in_num > 0, - false); + ok = vhost_svq_vring_write_descs(svq, sgs, out_sg, out_num, out_addr, + in_num > 0, false); if (unlikely(!ok)) { return false; } - ok = vhost_svq_vring_write_descs(svq, sgs, in_sg, in_num, false, true); + ok = vhost_svq_vring_write_descs(svq, sgs, in_sg, in_num, in_addr, false, + true); if (unlikely(!ok)) { return false; } @@ -247,8 +266,9 @@ static void vhost_svq_kick(VhostShadowVirtqueue *svq) * Return -EINVAL if element is invalid, -ENOSPC if dev queue is full */ int vhost_svq_add(VhostShadowVirtqueue *svq, const struct iovec *out_sg, - size_t out_num, const struct iovec *in_sg, size_t in_num, - VirtQueueElement *elem) + size_t out_num, const hwaddr *out_addr, + const struct iovec *in_sg, size_t in_num, + const hwaddr *in_addr, VirtQueueElement *elem) { unsigned qemu_head; unsigned ndescs = in_num + out_num; @@ -258,7 +278,8 @@ int vhost_svq_add(VhostShadowVirtqueue *svq, const struct iovec *out_sg, return -ENOSPC; } - ok = vhost_svq_add_split(svq, out_sg, out_num, in_sg, in_num, &qemu_head); + ok = vhost_svq_add_split(svq, out_sg, out_num, out_addr, in_sg, in_num, + in_addr, &qemu_head); if (unlikely(!ok)) { return -EINVAL; } @@ -274,8 +295,8 @@ int vhost_svq_add(VhostShadowVirtqueue *svq, const struct iovec *out_sg, static int vhost_svq_add_element(VhostShadowVirtqueue *svq, VirtQueueElement *elem) { - return vhost_svq_add(svq, elem->out_sg, elem->out_num, elem->in_sg, - elem->in_num, elem); + return vhost_svq_add(svq, elem->out_sg, elem->out_num, elem->out_addr, + elem->in_sg, elem->in_num, elem->in_addr, elem); } /** diff --git a/hw/virtio/vhost-shadow-virtqueue.h b/hw/virtio/vhost-shadow-virtqueue.h index 19c842a15b72..9c273739d6df 100644 --- a/hw/virtio/vhost-shadow-virtqueue.h +++ b/hw/virtio/vhost-shadow-virtqueue.h @@ -118,8 +118,9 @@ uint16_t vhost_svq_available_slots(const VhostShadowVirtqueue *svq); void vhost_svq_push_elem(VhostShadowVirtqueue *svq, const VirtQueueElement *elem, uint32_t len); int vhost_svq_add(VhostShadowVirtqueue *svq, const struct iovec *out_sg, - size_t out_num, const struct iovec *in_sg, size_t in_num, - VirtQueueElement *elem); + size_t out_num, const hwaddr *out_addr, + const struct iovec *in_sg, size_t in_num, + const hwaddr *in_addr, VirtQueueElement *elem); size_t vhost_svq_poll(VhostShadowVirtqueue *svq, size_t num); void vhost_svq_set_svq_kick_fd(VhostShadowVirtqueue *svq, int svq_kick_fd); diff --git a/hw/virtio/vhost-vdpa.c b/hw/virtio/vhost-vdpa.c index 703dcfc9299f..7efbde3d4c1c 100644 --- a/hw/virtio/vhost-vdpa.c +++ b/hw/virtio/vhost-vdpa.c @@ -360,17 +360,17 @@ static void vhost_vdpa_listener_region_add(MemoryListener *listener, llsize = int128_sub(llend, int128_make64(iova)); if (s->shadow_data) { int r; - hwaddr hw_vaddr = (hwaddr)(uintptr_t)vaddr; + hwaddr gpa = section->offset_within_address_space; mem_region.size = int128_get64(llsize) - 1, mem_region.perm = IOMMU_ACCESS_FLAG(true, section->readonly), - r = vhost_iova_tree_map_alloc(s->iova_tree, &mem_region, hw_vaddr); + r = vhost_iova_tree_map_alloc_gpa(s->iova_tree, &mem_region, gpa); if (unlikely(r != IOVA_OK)) { error_report("Can't allocate a mapping (%d)", r); - if (mem_region.translated_addr == hw_vaddr) { - error_report("Insertion to IOVA->HVA tree failed"); + if (mem_region.translated_addr == gpa) { + error_report("Insertion to GPA->IOVA tree failed"); /* Remove the mapping from the IOVA-only tree */ goto fail_map; } @@ -392,7 +392,7 @@ static void vhost_vdpa_listener_region_add(MemoryListener *listener, fail_map: if (s->shadow_data) { - vhost_iova_tree_remove(s->iova_tree, mem_region); + vhost_iova_tree_remove_gpa(s->iova_tree, mem_region); } fail: @@ -446,21 +446,18 @@ static void vhost_vdpa_listener_region_del(MemoryListener *listener, if (s->shadow_data) { const DMAMap *result; - const void *vaddr = memory_region_get_ram_ptr(section->mr) + - section->offset_within_region + - (iova - section->offset_within_address_space); DMAMap mem_region = { - .translated_addr = (hwaddr)(uintptr_t)vaddr, + .translated_addr = section->offset_within_address_space, .size = int128_get64(llsize) - 1, }; - result = vhost_iova_tree_find_iova(s->iova_tree, &mem_region); + result = vhost_iova_tree_find_gpa(s->iova_tree, &mem_region); if (!result) { /* The memory listener map wasn't mapped */ return; } iova = result->iova; - vhost_iova_tree_remove(s->iova_tree, *result); + vhost_iova_tree_remove_gpa(s->iova_tree, *result); } vhost_vdpa_iotlb_batch_begin_once(s); /* diff --git a/include/qemu/iova-tree.h b/include/qemu/iova-tree.h index 44a45931d58c..16d354a81488 100644 --- a/include/qemu/iova-tree.h +++ b/include/qemu/iova-tree.h @@ -40,6 +40,28 @@ typedef struct DMAMap { } QEMU_PACKED DMAMap; typedef gboolean (*iova_tree_iterator)(DMAMap *map); +/** + * gpa_tree_new: + * + * Create a new GPA->IOVA tree. + * + * Returns: the tree point on success, or NULL otherwise. + */ +IOVATree *gpa_tree_new(void); + +/** + * gpa_tree_insert: + * + * @tree: The GPA->IOVA tree we're inserting the mapping to + * @map: The GPA->IOVA mapping to insert + * + * Inserts a GPA range to the GPA->IOVA tree. If there are overlapped + * ranges, IOVA_ERR_OVERLAP will be returned. + * + * Return: 0 if successful, < 0 otherwise. + */ +int gpa_tree_insert(IOVATree *tree, const DMAMap *map); + /** * iova_tree_new: * diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index 5a3a57203d07..bd0186687878 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -649,7 +649,7 @@ static ssize_t vhost_vdpa_net_cvq_add(VhostVDPAState *s, VhostShadowVirtqueue *svq = g_ptr_array_index(s->vhost_vdpa.shadow_vqs, 0); int r; - r = vhost_svq_add(svq, out_sg, out_num, in_sg, in_num, NULL); + r = vhost_svq_add(svq, out_sg, out_num, NULL, in_sg, in_num, NULL, NULL); if (unlikely(r != 0)) { if (unlikely(r == -ENOSPC)) { qemu_log_mask(LOG_GUEST_ERROR, "%s: No space on device queue\n", diff --git a/util/iova-tree.c b/util/iova-tree.c index 06295e275502..5b0c95ff1583 100644 --- a/util/iova-tree.c +++ b/util/iova-tree.c @@ -257,3 +257,49 @@ void iova_tree_destroy(IOVATree *tree) g_tree_destroy(tree->tree); g_free(tree); } + +static int gpa_tree_compare(gconstpointer a, gconstpointer b, gpointer data) +{ + const DMAMap *m1 = a, *m2 = b; + + if (m1->translated_addr > m2->translated_addr + m2->size) { + return 1; + } + + if (m1->translated_addr + m1->size < m2->translated_addr) { + return -1; + } + + /* Overlapped */ + return 0; +} + +IOVATree *gpa_tree_new(void) +{ + IOVATree *gpa_tree = g_new0(IOVATree, 1); + + gpa_tree->tree = g_tree_new_full(gpa_tree_compare, NULL, g_free, NULL); + + return gpa_tree; +} + +int gpa_tree_insert(IOVATree *tree, const DMAMap *map) +{ + DMAMap *new; + + if (map->translated_addr + map->size < map->translated_addr || + map->perm == IOMMU_NONE) { + return IOVA_ERR_INVALID; + } + + /* We don't allow inserting ranges that overlap with existing ones */ + if (iova_tree_find(tree, map)) { + return IOVA_ERR_OVERLAP; + } + + new = g_new0(DMAMap, 1); + memcpy(new, map, sizeof(*new)); + iova_tree_insert_internal(tree->tree, new); + + return IOVA_OK; +} From 332859dd597b78f7d1ebfaefd6195e3a2b3e5906 Mon Sep 17 00:00:00 2001 From: Jonah Palmer Date: Mon, 17 Feb 2025 09:49:34 -0500 Subject: [PATCH 0210/1179] vhost-iova-tree: Update documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor update to some of the documentation / comments in hw/virtio/vhost-iova-tree.c. Signed-off-by: Jonah Palmer Reviewed-by: Eugenio Pérez Tested-by: Lei Yang Message-Id: <20250217144936.3589907-4-jonah.palmer@oracle.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/vhost-iova-tree.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/hw/virtio/vhost-iova-tree.c b/hw/virtio/vhost-iova-tree.c index 9d2d6a7af228..fa4147b77328 100644 --- a/hw/virtio/vhost-iova-tree.c +++ b/hw/virtio/vhost-iova-tree.c @@ -37,9 +37,9 @@ struct VhostIOVATree { }; /** - * Create a new IOVA tree + * Create a new VhostIOVATree * - * Returns the new IOVA tree + * Returns the new VhostIOVATree. */ VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last) { @@ -56,7 +56,7 @@ VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last) } /** - * Delete an iova tree + * Delete a VhostIOVATree */ void vhost_iova_tree_delete(VhostIOVATree *iova_tree) { @@ -69,10 +69,10 @@ void vhost_iova_tree_delete(VhostIOVATree *iova_tree) /** * Find the IOVA address stored from a memory address * - * @tree: The iova tree + * @tree: The VhostIOVATree * @map: The map with the memory address * - * Return the stored mapping, or NULL if not found. + * Returns the stored IOVA->HVA mapping, or NULL if not found. */ const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree, const DMAMap *map) @@ -81,10 +81,10 @@ const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree, } /** - * Allocate a new mapping + * Allocate a new IOVA range and add the mapping to the IOVA->HVA tree * - * @tree: The iova tree - * @map: The iova map + * @tree: The VhostIOVATree + * @map: The IOVA mapping * @taddr: The translated address (HVA) * * Returns: @@ -92,7 +92,7 @@ const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree, * - IOVA_ERR_INVALID if the map does not make sense (like size overflow) * - IOVA_ERR_NOMEM if tree cannot allocate more space. * - * It returns assignated iova in map->iova if return value is VHOST_DMA_MAP_OK. + * It returns an assigned IOVA in map->iova if the return value is IOVA_OK. */ int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map, hwaddr taddr) { @@ -117,9 +117,9 @@ int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map, hwaddr taddr) } /** - * Remove existing mappings from iova tree + * Remove existing mappings from the IOVA-only and IOVA->HVA trees * - * @iova_tree: The vhost iova tree + * @iova_tree: The VhostIOVATree * @map: The map to remove */ void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map) From 83cb18ac4500f3a14067b19408705068647cb0c5 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Tue, 11 Feb 2025 14:55:23 +0100 Subject: [PATCH 0211/1179] cryptodev/vhost: allocate CryptoDevBackendVhost using g_mem0() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function `vhost_dev_init()` expects the `struct vhost_dev` (passed as a parameter) to be fully initialized. This is important because some parts of the code check whether `vhost_dev->config_ops` is NULL to determine if it has been set (e.g. later via `vhost_dev_set_config_notifier`). To ensure this initialization, it’s better to allocate the entire `CryptoDevBackendVhost` structure (which includes `vhost_dev`) using `g_mem0()`, following the same approach used for other vhost devices, such as in `vhost_net_init()`. Fixes: 042cea274c ("cryptodev: add vhost-user as a new cryptodev backend") Cc: qemu-stable@nongnu.org Reported-by: myluo24@m.fudan.edu.cn Signed-off-by: Stefano Garzarella Message-Id: <20250211135523.101203-1-sgarzare@redhat.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- backends/cryptodev-vhost.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/cryptodev-vhost.c b/backends/cryptodev-vhost.c index 8718c9732625..943680a23afa 100644 --- a/backends/cryptodev-vhost.c +++ b/backends/cryptodev-vhost.c @@ -53,7 +53,7 @@ cryptodev_vhost_init( CryptoDevBackendVhost *crypto; Error *local_err = NULL; - crypto = g_new(CryptoDevBackendVhost, 1); + crypto = g_new0(CryptoDevBackendVhost, 1); crypto->dev.max_queues = 1; crypto->dev.nvqs = 1; crypto->dev.vqs = crypto->vqs; From 590de17b829d2ccd8067f19c71a53e31bec7bf00 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Tue, 11 Feb 2025 15:42:57 +0100 Subject: [PATCH 0212/1179] MAINTAINERS: add more files to `vhost` While sending a patch for backends/cryptodev-vhost.c I noticed that Michael wasn`t in CC so I took a look at the files listed under `vhost` and tried to fix it increasing the coverage by adding new files. Signed-off-by: Stefano Garzarella Message-Id: <20250211144259.117910-1-sgarzare@redhat.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- MAINTAINERS | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 013a57d5bf77..2d07d729332e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2212,12 +2212,16 @@ M: Michael S. Tsirkin R: Stefano Garzarella S: Supported F: hw/*/*vhost* -F: docs/interop/vhost-user.json -F: docs/interop/vhost-user.rst +F: docs/interop/vhost-user* +F: docs/system/devices/vhost-user* F: contrib/vhost-user-*/ -F: backends/vhost-user.c +F: backends/*vhost* F: include/system/vhost-user-backend.h +F: include/hw/virtio/vhost* +F: include/*/vhost* F: subprojects/libvhost-user/ +F: block/export/vhost-user* +F: util/vhost-user-server.c vhost-shadow-virtqueue R: Eugenio Pérez From 50e9754149066dc91f58405d3378b589098cb408 Mon Sep 17 00:00:00 2001 From: Konstantin Shkolnyy Date: Wed, 12 Feb 2025 10:49:23 -0600 Subject: [PATCH 0213/1179] vdpa: Fix endian bugs in shadow virtqueue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VDPA didn't work on a big-endian machine due to missing/incorrect CPU<->LE data format conversions. Signed-off-by: Konstantin Shkolnyy Message-Id: <20250212164923.1971538-1-kshk@linux.ibm.com> Fixes: 10857ec0ad ("vhost: Add VhostShadowVirtqueue") Acked-by: Eugenio Pérez Tested-by: Lei Yang Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/vhost-shadow-virtqueue.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/hw/virtio/vhost-shadow-virtqueue.c b/hw/virtio/vhost-shadow-virtqueue.c index 30ba565f034c..2481d49345dd 100644 --- a/hw/virtio/vhost-shadow-virtqueue.c +++ b/hw/virtio/vhost-shadow-virtqueue.c @@ -182,10 +182,10 @@ static bool vhost_svq_vring_write_descs(VhostShadowVirtqueue *svq, hwaddr *sg, descs[i].len = cpu_to_le32(iovec[n].iov_len); last = i; - i = cpu_to_le16(svq->desc_next[i]); + i = svq->desc_next[i]; } - svq->free_head = le16_to_cpu(svq->desc_next[last]); + svq->free_head = svq->desc_next[last]; return true; } @@ -247,10 +247,12 @@ static void vhost_svq_kick(VhostShadowVirtqueue *svq) smp_mb(); if (virtio_vdev_has_feature(svq->vdev, VIRTIO_RING_F_EVENT_IDX)) { - uint16_t avail_event = *(uint16_t *)(&svq->vring.used->ring[svq->vring.num]); + uint16_t avail_event = le16_to_cpu( + *(uint16_t *)(&svq->vring.used->ring[svq->vring.num])); needs_kick = vring_need_event(avail_event, svq->shadow_avail_idx, svq->shadow_avail_idx - 1); } else { - needs_kick = !(svq->vring.used->flags & VRING_USED_F_NO_NOTIFY); + needs_kick = + !(svq->vring.used->flags & cpu_to_le16(VRING_USED_F_NO_NOTIFY)); } if (!needs_kick) { @@ -386,7 +388,7 @@ static bool vhost_svq_more_used(VhostShadowVirtqueue *svq) return true; } - svq->shadow_used_idx = cpu_to_le16(*(volatile uint16_t *)used_idx); + svq->shadow_used_idx = le16_to_cpu(*(volatile uint16_t *)used_idx); return svq->last_used_idx != svq->shadow_used_idx; } @@ -404,7 +406,7 @@ static bool vhost_svq_enable_notification(VhostShadowVirtqueue *svq) { if (virtio_vdev_has_feature(svq->vdev, VIRTIO_RING_F_EVENT_IDX)) { uint16_t *used_event = (uint16_t *)&svq->vring.avail->ring[svq->vring.num]; - *used_event = svq->shadow_used_idx; + *used_event = cpu_to_le16(svq->shadow_used_idx); } else { svq->vring.avail->flags &= ~cpu_to_le16(VRING_AVAIL_F_NO_INTERRUPT); } @@ -429,7 +431,7 @@ static uint16_t vhost_svq_last_desc_of_chain(const VhostShadowVirtqueue *svq, uint16_t num, uint16_t i) { for (uint16_t j = 0; j < (num - 1); ++j) { - i = le16_to_cpu(svq->desc_next[i]); + i = svq->desc_next[i]; } return i; @@ -704,7 +706,7 @@ void vhost_svq_start(VhostShadowVirtqueue *svq, VirtIODevice *vdev, svq->desc_state = g_new0(SVQDescState, svq->vring.num); svq->desc_next = g_new0(uint16_t, svq->vring.num); for (unsigned i = 0; i < svq->vring.num - 1; i++) { - svq->desc_next[i] = cpu_to_le16(i + 1); + svq->desc_next[i] = i + 1; } } From 131fe64e63c88ec52c45a5946a478c0edeb31b78 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Thu, 13 Feb 2025 11:45:41 +0000 Subject: [PATCH 0214/1179] hw/virtio/virtio-nsm: Respond with correct length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we return a response packet from NSM, we need to indicate its length according to the content of the response. Prior to this patch, we returned the length of the source buffer, which may confuse guest code that relies on the response size. Fix it by returning the response payload size instead. Fixes: bb154e3e0cc715 ("device/virtio-nsm: Support for Nitro Secure Module device") Reported-by: Vikrant Garg Signed-off-by: Alexander Graf Message-Id: <20250213114541.67515-1-graf@amazon.com> Reviewed-by: Dorjoy Chowdhury Fixes: bb154e3e0cc715 ("device/virtio-nsm: Support for Nitro Secure Module device")
Reported-by: Vikrant Garg Signed-off-by: Alexander Graf Reviewed-by: Philippe Mathieu-Daudé Tested-by: Vikrant Garg Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/virtio-nsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/virtio/virtio-nsm.c b/hw/virtio/virtio-nsm.c index 098e1aeac6e2..b22aa74e34d2 100644 --- a/hw/virtio/virtio-nsm.c +++ b/hw/virtio/virtio-nsm.c @@ -1596,7 +1596,7 @@ static void handle_input(VirtIODevice *vdev, VirtQueue *vq) g_free(req.iov_base); g_free(res.iov_base); virtqueue_push(vq, out_elem, 0); - virtqueue_push(vq, in_elem, in_elem->in_sg->iov_len); + virtqueue_push(vq, in_elem, sz); virtio_notify(vdev, vq); return; From 02fd9f8aeeb184276b283ae2f404bc3acf1e7b7a Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Mon, 17 Feb 2025 10:25:50 +0100 Subject: [PATCH 0215/1179] net: vhost-user: add QAPI events to report connection state The netdev reports NETDEV_VHOST_USER_CONNECTED event when the chardev is connected, and NETDEV_VHOST_USER_DISCONNECTED when it is disconnected. The NETDEV_VHOST_USER_CONNECTED event includes the chardev id. This allows a system manager like libvirt to detect when the server fails. For instance with passt: { 'execute': 'qmp_capabilities' } { "return": { } } [killing passt here] { "timestamp": { "seconds": 1739538634, "microseconds": 920450 }, "event": "NETDEV_VHOST_USER_DISCONNECTED", "data": { "netdev-id": "netdev0" } } [automatic reconnection with reconnect-ms] { "timestamp": { "seconds": 1739538638, "microseconds": 354181 }, "event": "NETDEV_VHOST_USER_CONNECTED", "data": { "netdev-id": "netdev0", "chardev-id": "chr0" } } Tested-by: Stefano Brivio Signed-off-by: Laurent Vivier Message-Id: <20250217092550.1172055-1-lvivier@redhat.com> Acked-by: Markus Armbruster Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- net/vhost-user.c | 3 +++ qapi/net.json | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/net/vhost-user.c b/net/vhost-user.c index 12555518e838..0b235e50c650 100644 --- a/net/vhost-user.c +++ b/net/vhost-user.c @@ -16,6 +16,7 @@ #include "chardev/char-fe.h" #include "qapi/error.h" #include "qapi/qapi-commands-net.h" +#include "qapi/qapi-events-net.h" #include "qemu/config-file.h" #include "qemu/error-report.h" #include "qemu/option.h" @@ -271,6 +272,7 @@ static void chr_closed_bh(void *opaque) if (err) { error_report_err(err); } + qapi_event_send_netdev_vhost_user_disconnected(name); } static void net_vhost_user_event(void *opaque, QEMUChrEvent event) @@ -300,6 +302,7 @@ static void net_vhost_user_event(void *opaque, QEMUChrEvent event) net_vhost_user_watch, s); qmp_set_link(name, true, &err); s->started = true; + qapi_event_send_netdev_vhost_user_connected(name, chr->label); break; case CHR_EVENT_CLOSED: /* a close event may happen during a read/write, but vhost diff --git a/qapi/net.json b/qapi/net.json index 2739a2f42332..310cc4fd1907 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -1031,3 +1031,43 @@ ## { 'event': 'NETDEV_STREAM_DISCONNECTED', 'data': { 'netdev-id': 'str' } } + +## +# @NETDEV_VHOST_USER_CONNECTED: +# +# Emitted when the vhost-user chardev is connected +# +# @netdev-id: QEMU netdev id that is connected +# +# @chardev-id: The character device id used by the QEMU netdev +# +# Since: 10.0 +# +# .. qmp-example:: +# +# <- { "timestamp": {"seconds": 1739538638, "microseconds": 354181 }, +# "event": "NETDEV_VHOST_USER_CONNECTED", +# "data": { "netdev-id": "netdev0", "chardev-id": "chr0" } } +# +## +{ 'event': 'NETDEV_VHOST_USER_CONNECTED', + 'data': { 'netdev-id': 'str', 'chardev-id': 'str' } } + +## +# @NETDEV_VHOST_USER_DISCONNECTED: +# +# Emitted when the vhost-user chardev is disconnected +# +# @netdev-id: QEMU netdev id that is disconnected +# +# Since: 10.0 +# +# .. qmp-example:: +# +# <- { "timestamp": { "seconds": 1739538634, "microseconds": 920450 }, +# "event": "NETDEV_VHOST_USER_DISCONNECTED", +# "data": { "netdev-id": "netdev0" } } +# +## +{ 'event': 'NETDEV_VHOST_USER_DISCONNECTED', + 'data': { 'netdev-id': 'str' } } From e87b6efb11be9f5ff213461f5ecdbae47d9402b9 Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Mon, 17 Feb 2025 14:12:55 +0100 Subject: [PATCH 0216/1179] vhost-user-snd: correct the calculation of config_size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use virtio_get_config_size() rather than sizeof(struct virtio_snd_config) for the config_size in the vhost-user-snd frontend. The frontend shall rely on device features for the size of the device configuration space. The presence of `controls` in the config space depends on VIRTIO_SND_F_CTLS according to the specification (v1.3): ` 5.14.4 Device Configuration Layout ... controls (driver-read-only) indicates a total number of all available control elements if VIRTIO_SND_F_CTLS has been negotiated. ` This fixes an issue introduced by commit ab0c7fb2 ("linux-headers: update to current kvm/next") in which the optional field `controls` is added to the virtio_snd_config structure. This breaks vhost-user-device backends that do not implement the `controls` field. Fixes: ab0c7fb22b ("linux-headers: update to current kvm/next") Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2805 Reviewed-by: Philippe Mathieu-Daudé Suggested-by: Stefano Garzarella Signed-off-by: Matias Ezequiel Vara Larsen Message-Id: <20250217131255.829892-1-mvaralar@redhat.com> Cc: qemu-stable@nongnu.org Reviewed-by: Stefano Garzarella Reviewed-by: Dorinda Bassey Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/vhost-user-snd.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/hw/virtio/vhost-user-snd.c b/hw/virtio/vhost-user-snd.c index 8610370af806..b414c75c0619 100644 --- a/hw/virtio/vhost-user-snd.c +++ b/hw/virtio/vhost-user-snd.c @@ -16,6 +16,18 @@ #include "standard-headers/linux/virtio_ids.h" #include "standard-headers/linux/virtio_snd.h" +static const VirtIOFeature feature_sizes[] = { + {.flags = 1ULL << VIRTIO_SND_F_CTLS, + .end = endof(struct virtio_snd_config, controls)}, + {} +}; + +static const VirtIOConfigSizeParams cfg_size_params = { + .min_size = endof(struct virtio_snd_config, chmaps), + .max_size = sizeof(struct virtio_snd_config), + .feature_sizes = feature_sizes +}; + static const VMStateDescription vu_snd_vmstate = { .name = "vhost-user-snd", .unmigratable = 1, @@ -23,16 +35,20 @@ static const VMStateDescription vu_snd_vmstate = { static const Property vsnd_properties[] = { DEFINE_PROP_CHR("chardev", VHostUserBase, chardev), + DEFINE_PROP_BIT64("controls", VHostUserBase, + parent_obj.host_features, VIRTIO_SND_F_CTLS, false), }; static void vu_snd_base_realize(DeviceState *dev, Error **errp) { VHostUserBase *vub = VHOST_USER_BASE(dev); VHostUserBaseClass *vubs = VHOST_USER_BASE_GET_CLASS(dev); + VirtIODevice *vdev = &vub->parent_obj; vub->virtio_id = VIRTIO_ID_SOUND; vub->num_vqs = 4; - vub->config_size = sizeof(struct virtio_snd_config); + vub->config_size = virtio_get_config_size(&cfg_size_params, + vdev->host_features); vub->vq_size = 64; vubs->parent_realize(dev, errp); From d261b84d354a41a38336af813f92f636d3fb3f78 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Tue, 18 Feb 2025 19:25:31 +0100 Subject: [PATCH 0217/1179] hw/virtio/virtio-iommu: Migrate to 3-phase reset Currently the iommu may be reset before the devices it protects. For example this happens with virtio-net. Let's use 3-phase reset mechanism and reset the IOMMU on exit phase after all DMA capable devices have been reset during the 'enter' or 'hold' phase. Signed-off-by: Eric Auger Acked-by: Michael S. Tsirkin Reviewed-by: Zhenzhong Duan Acked-by: Jason Wang Message-Id: <20250218182737.76722-2-eric.auger@redhat.com> Reviewed-by: Peter Xu Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/trace-events | 2 +- hw/virtio/virtio-iommu.c | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events index 04e36ae047db..76f0d458b29c 100644 --- a/hw/virtio/trace-events +++ b/hw/virtio/trace-events @@ -108,7 +108,7 @@ virtio_pci_notify_write(uint64_t addr, uint64_t val, unsigned int size) "0x%" PR virtio_pci_notify_write_pio(uint64_t addr, uint64_t val, unsigned int size) "0x%" PRIx64" = 0x%" PRIx64 " (%d)" # hw/virtio/virtio-iommu.c -virtio_iommu_device_reset(void) "reset!" +virtio_iommu_device_reset_exit(void) "reset!" virtio_iommu_system_reset(void) "system reset!" virtio_iommu_get_features(uint64_t features) "device supports features=0x%"PRIx64 virtio_iommu_device_status(uint8_t status) "driver status = %d" diff --git a/hw/virtio/virtio-iommu.c b/hw/virtio/virtio-iommu.c index f41104a95236..b6e7e01ef742 100644 --- a/hw/virtio/virtio-iommu.c +++ b/hw/virtio/virtio-iommu.c @@ -1504,11 +1504,11 @@ static void virtio_iommu_device_unrealize(DeviceState *dev) virtio_cleanup(vdev); } -static void virtio_iommu_device_reset(VirtIODevice *vdev) +static void virtio_iommu_device_reset_exit(Object *obj, ResetType type) { - VirtIOIOMMU *s = VIRTIO_IOMMU(vdev); + VirtIOIOMMU *s = VIRTIO_IOMMU(obj); - trace_virtio_iommu_device_reset(); + trace_virtio_iommu_device_reset_exit(); if (s->domains) { g_tree_destroy(s->domains); @@ -1668,6 +1668,7 @@ static void virtio_iommu_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); device_class_set_props(dc, virtio_iommu_properties); dc->vmsd = &vmstate_virtio_iommu; @@ -1675,7 +1676,12 @@ static void virtio_iommu_class_init(ObjectClass *klass, void *data) set_bit(DEVICE_CATEGORY_MISC, dc->categories); vdc->realize = virtio_iommu_device_realize; vdc->unrealize = virtio_iommu_device_unrealize; - vdc->reset = virtio_iommu_device_reset; + + /* + * Use 'exit' reset phase to make sure all DMA requests + * have been quiesced during 'enter' or 'hold' phase + */ + rc->phases.exit = virtio_iommu_device_reset_exit; vdc->get_config = virtio_iommu_get_config; vdc->set_config = virtio_iommu_set_config; vdc->get_features = virtio_iommu_get_features; From 2aaf48bcf27d8b3da5b30af6c1ced464d3df30f7 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Tue, 18 Feb 2025 19:25:32 +0100 Subject: [PATCH 0218/1179] hw/i386/intel-iommu: Migrate to 3-phase reset Currently the IOMMU may be reset before the devices it protects. For example this happens with virtio devices but also with VFIO devices. In this latter case this produces spurious translation faults on host. Let's use 3-phase reset mechanism and reset the IOMMU on exit phase after all DMA capable devices have been reset on 'enter' or 'hold' phase. Signed-off-by: Eric Auger Acked-by: Michael S. Tsirkin Acked-by: Jason Wang Zhenzhong Duan Message-Id: <20250218182737.76722-3-eric.auger@redhat.com> Reviewed-by: Peter Xu Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/i386/intel_iommu.c | 12 +++++++++--- hw/i386/trace-events | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index f366c223d0e1..a5cf2d0e81af 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -4697,10 +4697,11 @@ static void vtd_init(IntelIOMMUState *s) /* Should not reset address_spaces when reset because devices will still use * the address space they got at first (won't ask the bus again). */ -static void vtd_reset(DeviceState *dev) +static void vtd_reset_exit(Object *obj, ResetType type) { - IntelIOMMUState *s = INTEL_IOMMU_DEVICE(dev); + IntelIOMMUState *s = INTEL_IOMMU_DEVICE(obj); + trace_vtd_reset_exit(); vtd_init(s); vtd_address_space_refresh_all(s); } @@ -4864,8 +4865,13 @@ static void vtd_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); X86IOMMUClass *x86_class = X86_IOMMU_DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); - device_class_set_legacy_reset(dc, vtd_reset); + /* + * Use 'exit' reset phase to make sure all DMA requests + * have been quiesced during 'enter' or 'hold' phase + */ + rc->phases.exit = vtd_reset_exit; dc->vmsd = &vtd_vmstate; device_class_set_props(dc, vtd_properties); dc->hotpluggable = false; diff --git a/hw/i386/trace-events b/hw/i386/trace-events index 53c02d7ac85a..ac9e1a10aa45 100644 --- a/hw/i386/trace-events +++ b/hw/i386/trace-events @@ -68,6 +68,7 @@ vtd_frr_new(int index, uint64_t hi, uint64_t lo) "index %d high 0x%"PRIx64" low vtd_warn_invalid_qi_tail(uint16_t tail) "tail 0x%"PRIx16 vtd_warn_ir_vector(uint16_t sid, int index, int vec, int target) "sid 0x%"PRIx16" index %d vec %d (should be: %d)" vtd_warn_ir_trigger(uint16_t sid, int index, int trig, int target) "sid 0x%"PRIx16" index %d trigger %d (should be: %d)" +vtd_reset_exit(void) "" # amd_iommu.c amdvi_evntlog_fail(uint64_t addr, uint32_t head) "error: fail to write at addr 0x%"PRIx64" + offset 0x%"PRIx32 From e39e3f8b8dea856f141e9945167d2b18021ef445 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Tue, 18 Feb 2025 19:25:33 +0100 Subject: [PATCH 0219/1179] hw/arm/smmuv3: Move reset to exit phase Currently the iommu may be reset before the devices it protects. For example this happens with virtio-scsi-pci. when system_reset is issued from qmp monitor: spurious "virtio: zero sized buffers are not allowed" warnings can be observed. This happens because outstanding DMA requests are still happening while the SMMU gets reset. This can also happen with VFIO devices. In that case spurious DMA translation faults can be observed on host. Make sure the SMMU is reset in the 'exit' phase after all DMA capable devices have been reset during the 'enter' or 'hold' phase. Signed-off-by: Eric Auger Reviewed-by: Zhenzhong Duan Message-Id: <20250218182737.76722-4-eric.auger@redhat.com> Reviewed-by: Peter Xu Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/arm/smmu-common.c | 9 +++++++-- hw/arm/smmuv3.c | 14 ++++++++++---- hw/arm/trace-events | 1 + 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/hw/arm/smmu-common.c b/hw/arm/smmu-common.c index dd74c2e5583e..8c1b407b824c 100644 --- a/hw/arm/smmu-common.c +++ b/hw/arm/smmu-common.c @@ -924,7 +924,12 @@ static void smmu_base_realize(DeviceState *dev, Error **errp) } } -static void smmu_base_reset_hold(Object *obj, ResetType type) +/* + * Make sure the IOMMU is reset in 'exit' phase after + * all outstanding DMA requests have been quiesced during + * the 'enter' or 'hold' reset phases + */ +static void smmu_base_reset_exit(Object *obj, ResetType type) { SMMUState *s = ARM_SMMU(obj); @@ -949,7 +954,7 @@ static void smmu_base_class_init(ObjectClass *klass, void *data) device_class_set_props(dc, smmu_dev_properties); device_class_set_parent_realize(dc, smmu_base_realize, &sbc->parent_realize); - rc->phases.hold = smmu_base_reset_hold; + rc->phases.exit = smmu_base_reset_exit; } static const TypeInfo smmu_base_info = { diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c index c0cf5df0f6e4..b49a59b64c2c 100644 --- a/hw/arm/smmuv3.c +++ b/hw/arm/smmuv3.c @@ -1870,13 +1870,19 @@ static void smmu_init_irq(SMMUv3State *s, SysBusDevice *dev) } } -static void smmu_reset_hold(Object *obj, ResetType type) +/* + * Make sure the IOMMU is reset in 'exit' phase after + * all outstanding DMA requests have been quiesced during + * the 'enter' or 'hold' reset phases + */ +static void smmu_reset_exit(Object *obj, ResetType type) { SMMUv3State *s = ARM_SMMUV3(obj); SMMUv3Class *c = ARM_SMMUV3_GET_CLASS(s); - if (c->parent_phases.hold) { - c->parent_phases.hold(obj, type); + trace_smmu_reset_exit(); + if (c->parent_phases.exit) { + c->parent_phases.exit(obj, type); } smmuv3_init_regs(s); @@ -1999,7 +2005,7 @@ static void smmuv3_class_init(ObjectClass *klass, void *data) SMMUv3Class *c = ARM_SMMUV3_CLASS(klass); dc->vmsd = &vmstate_smmuv3; - resettable_class_set_parent_phases(rc, NULL, smmu_reset_hold, NULL, + resettable_class_set_parent_phases(rc, NULL, NULL, smmu_reset_exit, &c->parent_phases); device_class_set_parent_realize(dc, smmu_realize, &c->parent_realize); diff --git a/hw/arm/trace-events b/hw/arm/trace-events index c64ad344bd11..7790db780e00 100644 --- a/hw/arm/trace-events +++ b/hw/arm/trace-events @@ -56,6 +56,7 @@ smmuv3_config_cache_inv(uint32_t sid) "Config cache INV for sid=0x%x" smmuv3_notify_flag_add(const char *iommu) "ADD SMMUNotifier node for iommu mr=%s" smmuv3_notify_flag_del(const char *iommu) "DEL SMMUNotifier node for iommu mr=%s" smmuv3_inv_notifiers_iova(const char *name, int asid, int vmid, uint64_t iova, uint8_t tg, uint64_t num_pages, int stage) "iommu mr=%s asid=%d vmid=%d iova=0x%"PRIx64" tg=%d num_pages=0x%"PRIx64" stage=%d" +smmu_reset_exit(void) "" # strongarm.c strongarm_uart_update_parameters(const char *label, int speed, char parity, int data_bits, int stop_bits) "%s speed=%d parity=%c data=%d stop=%d" From d410e709526d1cd4aa9085c6e254a622594a02a5 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Tue, 18 Feb 2025 19:25:34 +0100 Subject: [PATCH 0220/1179] hw/vfio/common: Add a trace point in vfio_reset_handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To ease the debug of reset sequence, let's add a trace point in vfio_reset_handler() Signed-off-by: Eric Auger Reviewed-by: Cédric Le Goater Acked-by: Michael S. Tsirkin Reviewed-by: Zhenzhong Duan Message-Id: <20250218182737.76722-5-eric.auger@redhat.com> Reviewed-by: Peter Xu Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/vfio/common.c | 1 + hw/vfio/trace-events | 1 + 2 files changed, 2 insertions(+) diff --git a/hw/vfio/common.c b/hw/vfio/common.c index f7499a9b7447..173fb3a997e2 100644 --- a/hw/vfio/common.c +++ b/hw/vfio/common.c @@ -1386,6 +1386,7 @@ void vfio_reset_handler(void *opaque) { VFIODevice *vbasedev; + trace_vfio_reset_handler(); QLIST_FOREACH(vbasedev, &vfio_device_list, global_next) { if (vbasedev->dev->realized) { vbasedev->ops->vfio_compute_needs_reset(vbasedev); diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index cab1cf1de0a2..c5385e1a4f98 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -120,6 +120,7 @@ vfio_get_dev_region(const char *name, int index, uint32_t type, uint32_t subtype vfio_legacy_dma_unmap_overflow_workaround(void) "" vfio_get_dirty_bitmap(uint64_t iova, uint64_t size, uint64_t bitmap_size, uint64_t start, uint64_t dirty_pages) "iova=0x%"PRIx64" size= 0x%"PRIx64" bitmap_size=0x%"PRIx64" start=0x%"PRIx64" dirty_pages=%"PRIu64 vfio_iommu_map_dirty_notify(uint64_t iova_start, uint64_t iova_end) "iommu dirty @ 0x%"PRIx64" - 0x%"PRIx64 +vfio_reset_handler(void) "" # platform.c vfio_platform_realize(char *name, char *compat) "vfio device %s, compat = %s" From dd6d545e8f2d9a0e8a8c287ec16469f03ef5c198 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Tue, 18 Feb 2025 19:25:35 +0100 Subject: [PATCH 0221/1179] docs/devel/reset: Document reset expectations for DMA and IOMMU To avoid any translation faults, the IOMMUs are expected to be reset after the devices they protect. Document that we expect DMA requests to be stopped during the 'enter' or 'hold' phase while IOMMUs should be reset during the 'exit' phase. Signed-off-by: Eric Auger Reviewed-by: Zhenzhong Duan Message-Id: <20250218182737.76722-6-eric.auger@redhat.com> Reviewed-by: Peter Xu Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- docs/devel/reset.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/devel/reset.rst b/docs/devel/reset.rst index adefd59ef97c..0b8b2fa5f40d 100644 --- a/docs/devel/reset.rst +++ b/docs/devel/reset.rst @@ -143,6 +143,11 @@ The *exit* phase is executed only when the last reset operation ends. Therefore the object does not need to care how many of reset controllers it has and how many of them have started a reset. +DMA capable devices are expected to cancel all outstanding DMA operations +during either 'enter' or 'hold' phases. IOMMUs are expected to reset during +the 'exit' phase and this sequencing makes sure no outstanding DMA request +will fault. + Handling reset in a resettable object ------------------------------------- From 4098c6b7b62c6a11a15c74fa275e754af8970317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 13:36:59 +0100 Subject: [PATCH 0222/1179] tests/functional: Have microblaze tests inherit common parent class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the MicroblazeMachine class being common to both MicroblazeBigEndianMachine and MicroblazeLittleEndianMachine classes. Move the xmaton and ballerina tests to the parent class. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-ID: <20250212123659.52764-11-philmd@linaro.org> [thuth: Add missing ASSET statements to the leaf classes] Signed-off-by: Thomas Huth --- .../functional/test_microblaze_s3adsp1800.py | 27 ++++++++++++++++ .../test_microblazeel_s3adsp1800.py | 31 +++---------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/tests/functional/test_microblaze_s3adsp1800.py b/tests/functional/test_microblaze_s3adsp1800.py index c4226f49cf33..c93fa14232b3 100755 --- a/tests/functional/test_microblaze_s3adsp1800.py +++ b/tests/functional/test_microblaze_s3adsp1800.py @@ -7,6 +7,7 @@ # This work is licensed under the terms of the GNU GPL, version 2 or # later. See the COPYING file in the top-level directory. +from qemu_test import exec_command_and_wait_for_pattern from qemu_test import QemuSystemTest, Asset from qemu_test import wait_for_console_pattern @@ -20,6 +21,10 @@ class MicroblazeMachine(QemuSystemTest): 'day17.tar.xz'), '3ba7439dfbea7af4876662c97f8e1f0cdad9231fc166e4861d17042489270057') + ASSET_IMAGE_LE = Asset( + ('http://www.qemu-advent-calendar.org/2023/download/day13.tar.gz'), + 'b9b3d43c5dd79db88ada495cc6e0d1f591153fe41355e925d791fbf44de50c22') + def do_ballerina_be_test(self, machine): self.set_machine(machine) self.archive_extract(self.ASSET_IMAGE_BE) @@ -34,8 +39,30 @@ def do_ballerina_be_test(self, machine): # message, that's why we don't test for a later string here. This # needs some investigation by a microblaze wizard one day... + def do_xmaton_le_test(self, machine): + self.require_netdev('user') + self.set_machine(machine) + self.archive_extract(self.ASSET_IMAGE_LE) + self.vm.set_console() + self.vm.add_args('-kernel', self.scratch_file('day13', 'xmaton.bin')) + tftproot = self.scratch_file('day13') + self.vm.add_args('-nic', f'user,tftp={tftproot}') + self.vm.launch() + wait_for_console_pattern(self, 'QEMU Advent Calendar 2023') + wait_for_console_pattern(self, 'buildroot login:') + exec_command_and_wait_for_pattern(self, 'root', '#') + exec_command_and_wait_for_pattern(self, + 'tftp -g -r xmaton.png 10.0.2.2 ; md5sum xmaton.png', + '821cd3cab8efd16ad6ee5acc3642a8ea') + + +class MicroblazeBigEndianMachine(MicroblazeMachine): + + ASSET_IMAGE_BE = MicroblazeMachine.ASSET_IMAGE_BE + def test_microblaze_s3adsp1800_legacy_be(self): self.do_ballerina_be_test('petalogix-s3adsp1800') + if __name__ == '__main__': QemuSystemTest.main() diff --git a/tests/functional/test_microblazeel_s3adsp1800.py b/tests/functional/test_microblazeel_s3adsp1800.py index 60aab4a45e83..ab59941d57ae 100755 --- a/tests/functional/test_microblazeel_s3adsp1800.py +++ b/tests/functional/test_microblazeel_s3adsp1800.py @@ -7,37 +7,16 @@ # This work is licensed under the terms of the GNU GPL, version 2 or # later. See the COPYING file in the top-level directory. -from qemu_test import exec_command_and_wait_for_pattern -from qemu_test import QemuSystemTest, Asset -from qemu_test import wait_for_console_pattern +from test_microblaze_s3adsp1800 import MicroblazeMachine -class MicroblazeelMachine(QemuSystemTest): +class MicroblazeLittleEndianMachine(MicroblazeMachine): - timeout = 90 - - ASSET_IMAGE_LE = Asset( - ('http://www.qemu-advent-calendar.org/2023/download/day13.tar.gz'), - 'b9b3d43c5dd79db88ada495cc6e0d1f591153fe41355e925d791fbf44de50c22') - - def do_xmaton_le_test(self, machine): - self.require_netdev('user') - self.set_machine(machine) - self.archive_extract(self.ASSET_IMAGE_LE) - self.vm.set_console() - self.vm.add_args('-kernel', self.scratch_file('day13', 'xmaton.bin')) - tftproot = self.scratch_file('day13') - self.vm.add_args('-nic', f'user,tftp={tftproot}') - self.vm.launch() - wait_for_console_pattern(self, 'QEMU Advent Calendar 2023') - wait_for_console_pattern(self, 'buildroot login:') - exec_command_and_wait_for_pattern(self, 'root', '#') - exec_command_and_wait_for_pattern(self, - 'tftp -g -r xmaton.png 10.0.2.2 ; md5sum xmaton.png', - '821cd3cab8efd16ad6ee5acc3642a8ea') + ASSET_IMAGE_LE = MicroblazeMachine.ASSET_IMAGE_LE def test_microblaze_s3adsp1800_legacy_le(self): self.do_xmaton_le_test('petalogix-s3adsp1800') + if __name__ == '__main__': - QemuSystemTest.main() + MicroblazeMachine.main() From 51beb350bbc489b705714033ebb2b4dfa86064a1 Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 19 Feb 2025 10:06:03 +0100 Subject: [PATCH 0223/1179] qapi/char.json: minor doc rewording for `hub` device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refine documentation for the hub device, specify the maximum. Signed-off-by: Roman Penyaev Cc: Marc-André Lureau Cc: Markus Armbruster Cc: qemu-devel@nongnu.org Message-ID: <20250219090607.559887-1-r.peniaev@gmail.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- qapi/char.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qapi/char.json b/qapi/char.json index f02b66c06b3e..dde2f9538f81 100644 --- a/qapi/char.json +++ b/qapi/char.json @@ -337,7 +337,7 @@ # # Configuration info for hub chardevs. # -# @chardevs: List of chardev IDs, which should be added to this hub +# @chardevs: IDs to be added to this hub (maximum 4 devices). # # Since: 10.0 ## From ab544de12787035edb7ad4994a80f9cd6a6b55d7 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 20 Feb 2025 13:38:31 -0800 Subject: [PATCH 0224/1179] hw/arm/smmuv3: Fill u.f_cd_fetch.addr for SMMU_EVT_F_CD_FETCH When we fill in the SMMUEventInfo for SMMU_EVT_F_CD_FETCH we write the address into the f_ste_fetch member of the union, but then when we come to read it back in smmuv3_record_event() we will (correctly) be using the f_cd_fetch member. This is more like a cosmetics fix since the f_cd_fetch and f_ste_fetch are basically the same field since they are in the exact same union with exact same type, but it's conceptually wrong. Use the correct union member. Signed-off-by: Nicolin Chen Message-id: 20250220213832.80289-1-nicolinc@nvidia.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/arm/smmuv3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c index b49a59b64c2c..b40acbe02455 100644 --- a/hw/arm/smmuv3.c +++ b/hw/arm/smmuv3.c @@ -377,7 +377,7 @@ static int smmu_get_cd(SMMUv3State *s, STE *ste, SMMUTransCfg *cfg, qemu_log_mask(LOG_GUEST_ERROR, "Cannot fetch pte at address=0x%"PRIx64"\n", addr); event->type = SMMU_EVT_F_CD_FETCH; - event->u.f_ste_fetch.addr = addr; + event->u.f_cd_fetch.addr = addr; return -EINVAL; } for (i = 0; i < ARRAY_SIZE(buf->word); i++) { From f10104aeae3a17f181d5bb37b7fd7dad7fe86cba Mon Sep 17 00:00:00 2001 From: "Matthew R. Ochs" Date: Fri, 21 Feb 2025 06:54:19 -0800 Subject: [PATCH 0225/1179] hw/arm/virt: Support larger highmem MMIO regions The MMIO region size required to support virtualized environments with large PCI BAR regions can exceed the hardcoded limit configured in QEMU. For example, a VM with multiple NVIDIA Grace-Hopper GPUs passed through requires more MMIO memory than the amount provided by VIRT_HIGH_PCIE_MMIO (currently 512GB). Instead of updating VIRT_HIGH_PCIE_MMIO, introduce a new parameter, highmem-mmio-size, that specifies the MMIO size required to support the VM configuration. Example usage with 1TB MMIO region size: -machine virt,gic-version=3,highmem-mmio-size=1T Signed-off-by: Matthew R. Ochs Reviewed-by: Gavin Shan Reviewed-by: Shameer Kolothum Reviewed-by: Eric Auger Reviewed-by: Nicolin Chen Message-id: 20250221145419.1281890-1-mochs@nvidia.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- docs/system/arm/virt.rst | 4 ++++ hw/arm/virt.c | 52 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/system/arm/virt.rst b/docs/system/arm/virt.rst index 0c9c2ce0351c..adf446c0a295 100644 --- a/docs/system/arm/virt.rst +++ b/docs/system/arm/virt.rst @@ -144,6 +144,10 @@ highmem-mmio Set ``on``/``off`` to enable/disable the high memory region for PCI MMIO. The default is ``on``. +highmem-mmio-size + Set the high memory region size for PCI MMIO. Must be a power of 2 and + greater than or equal to the default size (512G). + gic-version Specify the version of the Generic Interrupt Controller (GIC) to provide. Valid values are: diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 4a5a9666e916..ee69081ef421 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -53,6 +53,7 @@ #include "hw/loader.h" #include "qapi/error.h" #include "qemu/bitops.h" +#include "qemu/cutils.h" #include "qemu/error-report.h" #include "qemu/module.h" #include "hw/pci-host/gpex.h" @@ -192,6 +193,10 @@ static const MemMapEntry base_memmap[] = { [VIRT_MEM] = { GiB, LEGACY_RAMLIMIT_BYTES }, }; +/* Update the docs for highmem-mmio-size when changing this default */ +#define DEFAULT_HIGH_PCIE_MMIO_SIZE_GB 512 +#define DEFAULT_HIGH_PCIE_MMIO_SIZE (DEFAULT_HIGH_PCIE_MMIO_SIZE_GB * GiB) + /* * Highmem IO Regions: This memory map is floating, located after the RAM. * Each MemMapEntry base (GPA) will be dynamically computed, depending on the @@ -207,13 +212,16 @@ static const MemMapEntry base_memmap[] = { * PA space for one specific region is always reserved, even if the region * has been disabled or doesn't fit into the PA space. However, the PA space * for the region won't be reserved in these circumstances with compact layout. + * + * Note that the highmem-mmio-size property will update the high PCIE MMIO size + * field in this array. */ static MemMapEntry extended_memmap[] = { /* Additional 64 MB redist region (can contain up to 512 redistributors) */ [VIRT_HIGH_GIC_REDIST2] = { 0x0, 64 * MiB }, [VIRT_HIGH_PCIE_ECAM] = { 0x0, 256 * MiB }, /* Second PCIe window */ - [VIRT_HIGH_PCIE_MMIO] = { 0x0, 512 * GiB }, + [VIRT_HIGH_PCIE_MMIO] = { 0x0, DEFAULT_HIGH_PCIE_MMIO_SIZE }, }; static const int a15irqmap[] = { @@ -2550,6 +2558,40 @@ static void virt_set_highmem_mmio(Object *obj, bool value, Error **errp) vms->highmem_mmio = value; } +static void virt_get_highmem_mmio_size(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + uint64_t size = extended_memmap[VIRT_HIGH_PCIE_MMIO].size; + + visit_type_size(v, name, &size, errp); +} + +static void virt_set_highmem_mmio_size(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + uint64_t size; + + if (!visit_type_size(v, name, &size, errp)) { + return; + } + + if (!is_power_of_2(size)) { + error_setg(errp, "highmem-mmio-size is not a power of 2"); + return; + } + + if (size < DEFAULT_HIGH_PCIE_MMIO_SIZE) { + char *sz = size_to_str(DEFAULT_HIGH_PCIE_MMIO_SIZE); + error_setg(errp, "highmem-mmio-size cannot be set to a lower value " + "than the default (%s)", sz); + g_free(sz); + return; + } + + extended_memmap[VIRT_HIGH_PCIE_MMIO].size = size; +} static bool virt_get_its(Object *obj, Error **errp) { @@ -3207,6 +3249,14 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) "Set on/off to enable/disable high " "memory region for PCI MMIO"); + object_class_property_add(oc, "highmem-mmio-size", "size", + virt_get_highmem_mmio_size, + virt_set_highmem_mmio_size, + NULL, NULL); + object_class_property_set_description(oc, "highmem-mmio-size", + "Set the high memory region size " + "for PCI MMIO"); + object_class_property_add_str(oc, "gic-version", virt_get_gic_version, virt_set_gic_version); object_class_property_set_description(oc, "gic-version", From cacf4cb2516aa4de94aa80fecb08be4dafa5ed44 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 6 Feb 2025 15:12:09 +0000 Subject: [PATCH 0226/1179] monitor/hmp-cmds.c: Clean up hmp_dumpdtb printf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In hmp_dumpdtb(), we print a message when the command succeeds. This message is missing the trailing \n, so the HMP command prompt is printed immediately after it. We also weren't capitalizing 'DTB', or quoting the filename in the message. Fix these nits. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250206151214.2947842-2-peter.maydell@linaro.org --- monitor/hmp-cmds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c index 3825ff40a9b3..7ded3378cfca 100644 --- a/monitor/hmp-cmds.c +++ b/monitor/hmp-cmds.c @@ -431,6 +431,6 @@ void hmp_dumpdtb(Monitor *mon, const QDict *qdict) return; } - monitor_printf(mon, "dtb dumped to %s", filename); + monitor_printf(mon, "DTB dumped to '%s'\n", filename); } #endif From 3c25f487bc0672bf13473f4a7235c3ef592c954c Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 6 Feb 2025 15:12:10 +0000 Subject: [PATCH 0227/1179] hw/openrisc: Support monitor dumpdtb command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The openrisc machines don't set MachineState::fdt to point to their DTB blob. This means that although the command line '-machine dumpdtb=file.dtb' option works, the equivalent QMP and HMP monitor commands do not, but instead produce the error "This machine doesn't have a FDT". Set MachineState::fdt in openrisc_load_fdt(), when we write it to guest memory. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250206151214.2947842-3-peter.maydell@linaro.org --- hw/openrisc/boot.c | 7 +++++-- hw/openrisc/openrisc_sim.c | 2 +- hw/openrisc/virt.c | 2 +- include/hw/openrisc/boot.h | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/hw/openrisc/boot.c b/hw/openrisc/boot.c index 0f08df812dcf..72e2756af05f 100644 --- a/hw/openrisc/boot.c +++ b/hw/openrisc/boot.c @@ -90,8 +90,8 @@ hwaddr openrisc_load_initrd(void *fdt, const char *filename, return start + size; } -uint32_t openrisc_load_fdt(void *fdt, hwaddr load_start, - uint64_t mem_size) +uint32_t openrisc_load_fdt(MachineState *ms, void *fdt, + hwaddr load_start, uint64_t mem_size) { uint32_t fdt_addr; int ret; @@ -111,6 +111,9 @@ uint32_t openrisc_load_fdt(void *fdt, hwaddr load_start, /* copy in the device tree */ qemu_fdt_dumpdtb(fdt, fdtsize); + /* Save FDT for dumpdtb monitor command */ + ms->fdt = fdt; + rom_add_blob_fixed_as("fdt", fdt, fdtsize, fdt_addr, &address_space_memory); qemu_register_reset_nosnapshotload(qemu_fdt_randomize_seeds, diff --git a/hw/openrisc/openrisc_sim.c b/hw/openrisc/openrisc_sim.c index e0da4067ba39..d9e0744922a8 100644 --- a/hw/openrisc/openrisc_sim.c +++ b/hw/openrisc/openrisc_sim.c @@ -354,7 +354,7 @@ static void openrisc_sim_init(MachineState *machine) machine->initrd_filename, load_addr, machine->ram_size); } - boot_info.fdt_addr = openrisc_load_fdt(state->fdt, load_addr, + boot_info.fdt_addr = openrisc_load_fdt(machine, state->fdt, load_addr, machine->ram_size); } } diff --git a/hw/openrisc/virt.c b/hw/openrisc/virt.c index 7b60bf85094c..9afe407b00a7 100644 --- a/hw/openrisc/virt.c +++ b/hw/openrisc/virt.c @@ -540,7 +540,7 @@ static void openrisc_virt_init(MachineState *machine) machine->initrd_filename, load_addr, machine->ram_size); } - boot_info.fdt_addr = openrisc_load_fdt(state->fdt, load_addr, + boot_info.fdt_addr = openrisc_load_fdt(machine, state->fdt, load_addr, machine->ram_size); } } diff --git a/include/hw/openrisc/boot.h b/include/hw/openrisc/boot.h index 25a313d63a1c..9b4d88072c41 100644 --- a/include/hw/openrisc/boot.h +++ b/include/hw/openrisc/boot.h @@ -20,6 +20,7 @@ #define OPENRISC_BOOT_H #include "exec/cpu-defs.h" +#include "hw/boards.h" hwaddr openrisc_load_kernel(ram_addr_t ram_size, const char *kernel_filename, @@ -28,7 +29,7 @@ hwaddr openrisc_load_kernel(ram_addr_t ram_size, hwaddr openrisc_load_initrd(void *fdt, const char *filename, hwaddr load_start, uint64_t mem_size); -uint32_t openrisc_load_fdt(void *fdt, hwaddr load_start, +uint32_t openrisc_load_fdt(MachineState *ms, void *fdt, hwaddr load_start, uint64_t mem_size); #endif /* OPENRISC_BOOT_H */ From dfd0de718662a58ef2f2ef051939ed4b1a4d5ea7 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 6 Feb 2025 15:12:11 +0000 Subject: [PATCH 0228/1179] hw/mips/boston: Check for error return from boston_fdt_filter() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function boston_fdt_filter() can return NULL on errors (in which case it will print an error message). When we call this from the non-FIT-image codepath, we aren't checking the return value, so we will plough on with a NULL pointer, and segfault in fdt_totalsize(). Check for errors here. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250206151214.2947842-4-peter.maydell@linaro.org --- hw/mips/boston.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/mips/boston.c b/hw/mips/boston.c index 4690b254dda2..de6ce1f163d1 100644 --- a/hw/mips/boston.c +++ b/hw/mips/boston.c @@ -810,6 +810,10 @@ static void boston_mach_init(MachineState *machine) dtb_load_data = boston_fdt_filter(s, dtb_file_data, NULL, &dtb_vaddr); + if (!dtb_load_data) { + /* boston_fdt_filter() already printed the error for us */ + exit(1); + } /* Calculate real fdt size after filter */ dt_size = fdt_totalsize(dtb_load_data); From db0dd33559ee97a1fe84a1272258646279aca2e2 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 6 Feb 2025 15:12:12 +0000 Subject: [PATCH 0229/1179] hw/mips/boston: Support dumpdtb monitor commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boston machine doesn't set MachineState::fdt to the DTB blob that it has loaded or created, which means that the QMP/HMP dumpdtb monitor commands don't work. Setting MachineState::fdt is easy in the non-FIT codepath: we can simply do so immediately before loading the DTB into guest memory. The FIT codepath is a bit more awkward as currently the FIT loader throws away the memory that the FDT was in after it loads it into guest memory. So we add a void *pfdt argument to load_fit() for it to store the FDT pointer into. There is some readjustment required of the pointer handling in loader-fit.c, so that it applies 'const' only where it should (e.g. the data pointer we get back from fdt_getprop() is const, because it's into the middle of the input FDT data, but the pointer that fit_load_image_alloc() should not be const, because it's freshly allocated memory that the caller can change if it likes). Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250206151214.2947842-5-peter.maydell@linaro.org --- hw/core/loader-fit.c | 38 +++++++++++++++++++++----------------- hw/mips/boston.c | 11 +++++++---- include/hw/loader-fit.h | 21 ++++++++++++++++++--- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/hw/core/loader-fit.c b/hw/core/loader-fit.c index 9bdd4fa17c6a..6eb66406b073 100644 --- a/hw/core/loader-fit.c +++ b/hw/core/loader-fit.c @@ -32,8 +32,8 @@ #define FIT_LOADER_MAX_PATH (128) -static const void *fit_load_image_alloc(const void *itb, const char *name, - int *poff, size_t *psz, Error **errp) +static void *fit_load_image_alloc(const void *itb, const char *name, + int *poff, size_t *psz, Error **errp) { const void *data; const char *comp; @@ -80,11 +80,11 @@ static const void *fit_load_image_alloc(const void *itb, const char *name, return NULL; } - data = g_realloc(uncomp_data, uncomp_len); + uncomp_data = g_realloc(uncomp_data, uncomp_len); if (psz) { *psz = uncomp_len; } - return data; + return uncomp_data; } error_setg(errp, "unknown compression '%s'", comp); @@ -177,13 +177,12 @@ static int fit_load_kernel(const struct fit_loader *ldr, const void *itb, static int fit_load_fdt(const struct fit_loader *ldr, const void *itb, int cfg, void *opaque, const void *match_data, - hwaddr kernel_end, Error **errp) + hwaddr kernel_end, void **pfdt, Error **errp) { ERRP_GUARD(); Error *err = NULL; const char *name; - const void *data; - const void *load_data; + void *data; hwaddr load_addr; int img_off; size_t sz; @@ -194,7 +193,7 @@ static int fit_load_fdt(const struct fit_loader *ldr, const void *itb, return 0; } - load_data = data = fit_load_image_alloc(itb, name, &img_off, &sz, errp); + data = fit_load_image_alloc(itb, name, &img_off, &sz, errp); if (!data) { error_prepend(errp, "unable to load FDT image from FIT: "); return -EINVAL; @@ -211,19 +210,23 @@ static int fit_load_fdt(const struct fit_loader *ldr, const void *itb, } if (ldr->fdt_filter) { - load_data = ldr->fdt_filter(opaque, data, match_data, &load_addr); + void *filtered_data; + + filtered_data = ldr->fdt_filter(opaque, data, match_data, &load_addr); + if (filtered_data != data) { + g_free(data); + data = filtered_data; + } } load_addr = ldr->addr_to_phys(opaque, load_addr); - sz = fdt_totalsize(load_data); - rom_add_blob_fixed(name, load_data, sz, load_addr); + sz = fdt_totalsize(data); + rom_add_blob_fixed(name, data, sz, load_addr); - ret = 0; + *pfdt = data; + return 0; out: g_free((void *) data); - if (data != load_data) { - g_free((void *) load_data); - } return ret; } @@ -259,7 +262,8 @@ static bool fit_cfg_compatible(const void *itb, int cfg, const char *compat) return ret; } -int load_fit(const struct fit_loader *ldr, const char *filename, void *opaque) +int load_fit(const struct fit_loader *ldr, const char *filename, + void **pfdt, void *opaque) { Error *err = NULL; const struct fit_loader_match *match; @@ -323,7 +327,7 @@ int load_fit(const struct fit_loader *ldr, const char *filename, void *opaque) goto out; } - ret = fit_load_fdt(ldr, itb, cfg_off, opaque, match_data, kernel_end, + ret = fit_load_fdt(ldr, itb, cfg_off, opaque, match_data, kernel_end, pfdt, &err); if (ret) { error_report_err(err); diff --git a/hw/mips/boston.c b/hw/mips/boston.c index de6ce1f163d1..79410dabe7f4 100644 --- a/hw/mips/boston.c +++ b/hw/mips/boston.c @@ -358,8 +358,8 @@ static void gen_firmware(void *p, hwaddr kernel_entry, hwaddr fdt_addr) kernel_entry); } -static const void *boston_fdt_filter(void *opaque, const void *fdt_orig, - const void *match_data, hwaddr *load_addr) +static void *boston_fdt_filter(void *opaque, const void *fdt_orig, + const void *match_data, hwaddr *load_addr) { BostonState *s = BOSTON(opaque); MachineState *machine = s->mach; @@ -797,7 +797,7 @@ static void boston_mach_init(MachineState *machine) if (kernel_size > 0) { int dt_size; g_autofree const void *dtb_file_data = NULL; - g_autofree const void *dtb_load_data = NULL; + void *dtb_load_data = NULL; hwaddr dtb_paddr = QEMU_ALIGN_UP(kernel_high, 64 * KiB); hwaddr dtb_vaddr = cpu_mips_phys_to_kseg0(NULL, dtb_paddr); @@ -815,6 +815,8 @@ static void boston_mach_init(MachineState *machine) exit(1); } + machine->fdt = dtb_load_data; + /* Calculate real fdt size after filter */ dt_size = fdt_totalsize(dtb_load_data); rom_add_blob_fixed("dtb", dtb_load_data, dt_size, dtb_paddr); @@ -822,7 +824,8 @@ static void boston_mach_init(MachineState *machine) rom_ptr(dtb_paddr, dt_size)); } else { /* Try to load file as FIT */ - fit_err = load_fit(&boston_fit_loader, machine->kernel_filename, s); + fit_err = load_fit(&boston_fit_loader, machine->kernel_filename, + &machine->fdt, s); if (fit_err) { error_report("unable to load kernel image"); exit(1); diff --git a/include/hw/loader-fit.h b/include/hw/loader-fit.h index 0832e379dc90..9a43490ed637 100644 --- a/include/hw/loader-fit.h +++ b/include/hw/loader-fit.h @@ -30,12 +30,27 @@ struct fit_loader_match { struct fit_loader { const struct fit_loader_match *matches; hwaddr (*addr_to_phys)(void *opaque, uint64_t addr); - const void *(*fdt_filter)(void *opaque, const void *fdt, - const void *match_data, hwaddr *load_addr); + void *(*fdt_filter)(void *opaque, const void *fdt, + const void *match_data, hwaddr *load_addr); const void *(*kernel_filter)(void *opaque, const void *kernel, hwaddr *load_addr, hwaddr *entry_addr); }; -int load_fit(const struct fit_loader *ldr, const char *filename, void *opaque); +/** + * load_fit: load a FIT format image + * @ldr: structure defining board specific properties and hooks + * @filename: image to load + * @pfdt: pointer to update with address of FDT blob + * @opaque: opaque value passed back to the hook functions in @ldr + * Returns: 0 on success, or a negative errno on failure + * + * @pfdt is used to tell the caller about the FDT blob. On return, it + * has been set to point to the FDT blob, and it is now the caller's + * responsibility to free that memory with g_free(). Usually the caller + * will want to pass in &machine->fdt here, to record the FDT blob for + * the dumpdtb option and QMP/HMP commands. + */ +int load_fit(const struct fit_loader *ldr, const char *filename, void **pfdt, + void *opaque); #endif /* HW_LOADER_FIT_H */ From 8fd2518ef2f8d34dc9ee53d6915a2a610eb1a659 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 6 Feb 2025 15:12:13 +0000 Subject: [PATCH 0230/1179] hw: Centralize handling of -machine dumpdtb option Currently we handle the 'dumpdtb' machine sub-option ad-hoc in every board model that has an FDT. It's up to the board code to make sure it calls qemu_fdt_dumpdtb() in the right place. This means we're inconsistent and often just ignore the user's command line argument: * if the board doesn't have an FDT at all * if the board supports FDT, but there happens not to be one present (usually because of a missing -fdt option) This isn't very helpful because it gives the user no clue why their option was ignored. However, in order to support the QMP/HMP dumpdtb commands we require now that every FDT machine stores a pointer to the FDT in MachineState::fdt. This means we can handle -machine dumpdtb centrally by calling the qmp_dumpdtb() function, unifying its handling with the QMP/HMP commands. All the board code calls to qemu_fdt_dumpdtb() can then be removed. For this commit we retain the existing behaviour that if there is no FDT we silently ignore the -machine dumpdtb option. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- hw/arm/boot.c | 2 -- hw/core/machine.c | 25 +++++++++++++++++++++++++ hw/loongarch/virt-fdt-build.c | 1 - hw/mips/boston.c | 1 - hw/openrisc/boot.c | 1 - hw/ppc/e500.c | 1 - hw/ppc/pegasos2.c | 1 - hw/ppc/pnv.c | 1 - hw/ppc/spapr.c | 1 - hw/riscv/boot.c | 2 -- include/system/device_tree.h | 2 -- system/device_tree.c | 15 --------------- 12 files changed, 25 insertions(+), 28 deletions(-) diff --git a/hw/arm/boot.c b/hw/arm/boot.c index 42c18355e852..e296b62fa12c 100644 --- a/hw/arm/boot.c +++ b/hw/arm/boot.c @@ -661,8 +661,6 @@ int arm_load_dtb(hwaddr addr, const struct arm_boot_info *binfo, binfo->modify_dtb(binfo, fdt); } - qemu_fdt_dumpdtb(fdt, size); - /* Put the DTB into the memory map as a ROM image: this will ensure * the DTB is copied again upon reset, even if addr points into RAM. */ diff --git a/hw/core/machine.c b/hw/core/machine.c index 02cff735b3fb..61c22f723a0f 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -19,6 +19,7 @@ #include "qemu/error-report.h" #include "qapi/error.h" #include "qapi/qapi-visit-machine.h" +#include "qapi/qapi-commands-machine.h" #include "qemu/madvise.h" #include "qom/object_interfaces.h" #include "system/cpus.h" @@ -1696,6 +1697,24 @@ void qemu_remove_machine_init_done_notifier(Notifier *notify) notifier_remove(notify); } +static void handle_machine_dumpdtb(MachineState *ms) +{ + if (!ms->dumpdtb) { + return; + } + if (!ms->fdt) { + /* Silently ignore dumpdtb option if there is nothing to dump */ + return; + } +#ifdef CONFIG_FDT + qmp_dumpdtb(ms->dumpdtb, &error_fatal); + exit(0); +#else + error_report("This machine doesn't have an FDT"); + exit(1); +#endif +} + void qdev_machine_creation_done(void) { cpu_synchronize_all_post_init(); @@ -1712,6 +1731,12 @@ void qdev_machine_creation_done(void) phase_advance(PHASE_MACHINE_READY); qdev_assert_realized_properly(); + /* + * If the user used -machine dumpdtb=file.dtb to request that we + * dump the DTB to a file, do it now, and exit. + */ + handle_machine_dumpdtb(current_machine); + /* TODO: once all bus devices are qdevified, this should be done * when bus is created by qdev.c */ /* diff --git a/hw/loongarch/virt-fdt-build.c b/hw/loongarch/virt-fdt-build.c index dbc269afba4f..728ce466996f 100644 --- a/hw/loongarch/virt-fdt-build.c +++ b/hw/loongarch/virt-fdt-build.c @@ -527,7 +527,6 @@ void virt_fdt_setup(LoongArchVirtMachineState *lvms) * Put the FDT into the memory map as a ROM image: this will ensure * the FDT is copied again upon reset, even if addr points into RAM. */ - qemu_fdt_dumpdtb(machine->fdt, lvms->fdt_size); rom_add_blob_fixed_as("fdt", machine->fdt, lvms->fdt_size, FDT_BASE, &address_space_memory); qemu_register_reset_nosnapshotload(qemu_fdt_randomize_seeds, diff --git a/hw/mips/boston.c b/hw/mips/boston.c index 79410dabe7f4..149a263bd5aa 100644 --- a/hw/mips/boston.c +++ b/hw/mips/boston.c @@ -395,7 +395,6 @@ static void *boston_fdt_filter(void *opaque, const void *fdt_orig, 1, ram_high_sz); fdt = g_realloc(fdt, fdt_totalsize(fdt)); - qemu_fdt_dumpdtb(fdt, fdt_sz); s->fdt_base = *load_addr; diff --git a/hw/openrisc/boot.c b/hw/openrisc/boot.c index 72e2756af05f..0a5881be314f 100644 --- a/hw/openrisc/boot.c +++ b/hw/openrisc/boot.c @@ -109,7 +109,6 @@ uint32_t openrisc_load_fdt(MachineState *ms, void *fdt, /* Should only fail if we've built a corrupted tree */ g_assert(ret == 0); /* copy in the device tree */ - qemu_fdt_dumpdtb(fdt, fdtsize); /* Save FDT for dumpdtb monitor command */ ms->fdt = fdt; diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c index 26933e0457e6..fe8b9f79621b 100644 --- a/hw/ppc/e500.c +++ b/hw/ppc/e500.c @@ -658,7 +658,6 @@ static int ppce500_load_device_tree(PPCE500MachineState *pms, done: if (!dry_run) { - qemu_fdt_dumpdtb(fdt, fdt_size); cpu_physical_memory_write(addr, fdt, fdt_size); /* Set machine->fdt for 'dumpdtb' QMP/HMP command */ diff --git a/hw/ppc/pegasos2.c b/hw/ppc/pegasos2.c index b057672e829d..246d6d633b56 100644 --- a/hw/ppc/pegasos2.c +++ b/hw/ppc/pegasos2.c @@ -417,7 +417,6 @@ static void pegasos2_machine_reset(MachineState *machine, ResetType type) d[1] = cpu_to_be64(pm->kernel_size - (pm->kernel_entry - pm->kernel_addr)); qemu_fdt_setprop(fdt, "/chosen", "qemu,boot-kernel", d, sizeof(d)); - qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt)); g_free(pm->fdt_blob); pm->fdt_blob = fdt; diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 11fd477b71be..87607508c768 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -744,7 +744,6 @@ static void pnv_reset(MachineState *machine, ResetType type) _FDT((fdt_pack(fdt))); } - qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt)); cpu_physical_memory_write(PNV_FDT_ADDR, fdt, fdt_totalsize(fdt)); /* Update machine->fdt with latest fdt */ diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index f3a4b4235d43..c15340a58d81 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1760,7 +1760,6 @@ static void spapr_machine_reset(MachineState *machine, ResetType type) 0, fdt_addr, 0); cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt)); } - qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt)); g_free(spapr->fdt_blob); spapr->fdt_size = fdt_totalsize(fdt); diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c index c309441b7d88..765b9e2b1ab3 100644 --- a/hw/riscv/boot.c +++ b/hw/riscv/boot.c @@ -374,8 +374,6 @@ void riscv_load_fdt(hwaddr fdt_addr, void *fdt) uint32_t fdtsize = fdt_totalsize(fdt); /* copy in the device tree */ - qemu_fdt_dumpdtb(fdt, fdtsize); - rom_add_blob_fixed_as("fdt", fdt, fdtsize, fdt_addr, &address_space_memory); qemu_register_reset_nosnapshotload(qemu_fdt_randomize_seeds, diff --git a/include/system/device_tree.h b/include/system/device_tree.h index eb601522f888..49d8482ed4e1 100644 --- a/include/system/device_tree.h +++ b/include/system/device_tree.h @@ -133,8 +133,6 @@ int qemu_fdt_add_path(void *fdt, const char *path); sizeof(qdt_tmp)); \ } while (0) -void qemu_fdt_dumpdtb(void *fdt, int size); - /** * qemu_fdt_setprop_sized_cells_from_array: * @fdt: device tree blob diff --git a/system/device_tree.c b/system/device_tree.c index 4bc2d61b934a..d605ed2a2176 100644 --- a/system/device_tree.c +++ b/system/device_tree.c @@ -594,21 +594,6 @@ int qemu_fdt_add_path(void *fdt, const char *path) return retval; } -void qemu_fdt_dumpdtb(void *fdt, int size) -{ - const char *dumpdtb = current_machine->dumpdtb; - - if (dumpdtb) { - /* Dump the dtb to a file and quit */ - if (g_file_set_contents(dumpdtb, fdt, size, NULL)) { - info_report("dtb dumped to %s. Exiting.", dumpdtb); - exit(0); - } - error_report("%s: Failed dumping dtb to %s", __func__, dumpdtb); - exit(1); - } -} - int qemu_fdt_setprop_sized_cells_from_array(void *fdt, const char *node_path, const char *property, From b61b9d891305abf8fe37f07280ca5a99a10da6cf Mon Sep 17 00:00:00 2001 From: Xianglai Li Date: Wed, 12 Feb 2025 09:56:19 +0800 Subject: [PATCH 0231/1179] target/loongarch: fix vcpu reset command word issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the KVM_REG_LOONGARCH_VCPU_RESET command word is sent to the kernel through the kvm_set_one_reg interface, the parameter source needs to be a legal address, otherwise the kernel will return an error and the command word will fail to be sent. Signed-off-by: Xianglai Li Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Bibo Mao --- target/loongarch/kvm/kvm.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index a3f55155b030..27df02fa3a44 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -581,9 +581,16 @@ static int kvm_loongarch_get_lbt(CPUState *cs) void kvm_arch_reset_vcpu(CPUState *cs) { CPULoongArchState *env = cpu_env(cs); + int ret = 0; + uint64_t unused = 0; env->mp_state = KVM_MP_STATE_RUNNABLE; - kvm_set_one_reg(cs, KVM_REG_LOONGARCH_VCPU_RESET, 0); + ret = kvm_set_one_reg(cs, KVM_REG_LOONGARCH_VCPU_RESET, &unused); + if (ret) { + error_report("Failed to set KVM_REG_LOONGARCH_VCPU_RESET: %s", + strerror(errno)); + exit(EXIT_FAILURE); + } } static int kvm_loongarch_get_mpstate(CPUState *cs) From 7bd4eaa847fcdbc4505d9ab95dafa21791d8302a Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 18 Feb 2025 11:20:27 +0800 Subject: [PATCH 0232/1179] target/loongarch/gdbstub: Fix gdbstub incorrectly handling some registers Write operation with R32 (orig_a0) and R34 (CSR_BADV) is discarded on gdbstub implementation for LoongArch system. And return value should be register size rather than 0, since it is used to calculate offset of next register such as R33 (PC) in function handle_write_all_regs(). Cc: qemu-stable@nongnu.org Fixes: ca61e75071c6 ("target/loongarch: Add gdb support.") Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/gdbstub.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/target/loongarch/gdbstub.c b/target/loongarch/gdbstub.c index dafa4feb75d9..471eda28c730 100644 --- a/target/loongarch/gdbstub.c +++ b/target/loongarch/gdbstub.c @@ -63,23 +63,24 @@ int loongarch_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) { CPULoongArchState *env = cpu_env(cs); target_ulong tmp; - int read_length; int length = 0; + if (n < 0 || n > 34) { + return 0; + } + if (is_la64(env)) { tmp = ldq_le_p(mem_buf); - read_length = 8; + length = 8; } else { tmp = ldl_le_p(mem_buf); - read_length = 4; + length = 4; } if (0 <= n && n < 32) { env->gpr[n] = tmp; - length = read_length; } else if (n == 33) { set_pc(env, tmp); - length = read_length; } return length; } From 0262c8075e9dc62a53a4bd15ea8d92a4c9adf018 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Wed, 19 Feb 2025 15:07:00 +0800 Subject: [PATCH 0233/1179] target/loongarch: Correct maximum physical address in KVM mode On 3A5000 system, the physical address space width for host is 48, however 47 bit for KVM VM. For KVM VM, size of physical address space is the same with that of virtual user space address. Here modify physical address space width with 47 bit in KVM mode. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/cpu.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index e91f4a5239c7..f203fcc7d7e1 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -406,7 +406,7 @@ static void loongarch_la464_initfn(Object *obj) { LoongArchCPU *cpu = LOONGARCH_CPU(obj); CPULoongArchState *env = &cpu->env; - uint32_t data = 0; + uint32_t data = 0, field; int i; for (i = 0; i < 21; i++) { @@ -419,7 +419,13 @@ static void loongarch_la464_initfn(Object *obj) data = FIELD_DP32(data, CPUCFG1, ARCH, 2); data = FIELD_DP32(data, CPUCFG1, PGMMU, 1); data = FIELD_DP32(data, CPUCFG1, IOCSR, 1); - data = FIELD_DP32(data, CPUCFG1, PALEN, 0x2f); + if (kvm_enabled()) { + /* GPA address width of VM is 47, field value is 47 - 1 */ + field = 0x2e; + } else { + field = 0x2f; /* 48 bit - 1 */ + } + data = FIELD_DP32(data, CPUCFG1, PALEN, field); data = FIELD_DP32(data, CPUCFG1, VALEN, 0x2f); data = FIELD_DP32(data, CPUCFG1, UAL, 1); data = FIELD_DP32(data, CPUCFG1, RI, 1); From 3406b001e6e5992a8cc9b2442216de312b111c07 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Fri, 14 Feb 2025 16:30:50 +0800 Subject: [PATCH 0234/1179] target/loongarch: Add post init function for kvm mode Some features such as LBT and PMU are implemented in kvm mode, With paravirt features in future, post init function is added for kvm mode, so that property for these features will be created in kvm post init function. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/cpu.c | 2 +- target/loongarch/cpu.h | 8 ++++++++ target/loongarch/kvm/kvm.c | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index f203fcc7d7e1..012fcfbfdaef 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -765,7 +765,7 @@ void loongarch_cpu_post_init(Object *obj) loongarch_set_pmu); object_property_set_description(obj, "pmu", "Set off to performance monitor unit."); - + kvm_loongarch_cpu_post_init(cpu); } else { cpu->lbt = ON_OFF_AUTO_OFF; cpu->pmu = ON_OFF_AUTO_OFF; diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index f2a23b7a4343..74dffcb5527e 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -491,4 +491,12 @@ static inline void cpu_get_tb_cpu_state(CPULoongArchState *env, vaddr *pc, void loongarch_cpu_post_init(Object *obj); +#ifdef CONFIG_KVM +void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu); +#else +static inline void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu) +{ +} +#endif + #endif /* LOONGARCH_CPU_H */ diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index 27df02fa3a44..2d2fb1e26117 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -1016,6 +1016,10 @@ int kvm_arch_init_vcpu(CPUState *cs) return ret; } +void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu) +{ +} + int kvm_arch_destroy_vcpu(CPUState *cs) { return 0; From 780a65bd955dee4e58b2cefb3312e85731eace6e Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Fri, 14 Feb 2025 17:10:48 +0800 Subject: [PATCH 0235/1179] target/loongarch: Move kvm specified vCPU property to kvm directory LBT and PMU feature is supported only in kvm mode, move property about these two features to function kvm_loongarch_cpu_post_init(). Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/cpu.c | 40 ++------------------------------------ target/loongarch/kvm/kvm.c | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index 012fcfbfdaef..3788f895c157 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -718,34 +718,12 @@ static void loongarch_set_lasx(Object *obj, bool value, Error **errp) cpu->env.cpucfg[2] = FIELD_DP32(val, CPUCFG2, LASX, value); } -static bool loongarch_get_lbt(Object *obj, Error **errp) -{ - return LOONGARCH_CPU(obj)->lbt != ON_OFF_AUTO_OFF; -} - -static void loongarch_set_lbt(Object *obj, bool value, Error **errp) -{ - LoongArchCPU *cpu = LOONGARCH_CPU(obj); - - cpu->lbt = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; -} - -static bool loongarch_get_pmu(Object *obj, Error **errp) -{ - return LOONGARCH_CPU(obj)->pmu != ON_OFF_AUTO_OFF; -} - -static void loongarch_set_pmu(Object *obj, bool value, Error **errp) -{ - LoongArchCPU *cpu = LOONGARCH_CPU(obj); - - cpu->pmu = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; -} - void loongarch_cpu_post_init(Object *obj) { LoongArchCPU *cpu = LOONGARCH_CPU(obj); + cpu->lbt = ON_OFF_AUTO_OFF; + cpu->pmu = ON_OFF_AUTO_OFF; cpu->lsx = ON_OFF_AUTO_AUTO; cpu->lasx = ON_OFF_AUTO_AUTO; object_property_add_bool(obj, "lsx", loongarch_get_lsx, @@ -754,21 +732,7 @@ void loongarch_cpu_post_init(Object *obj) loongarch_set_lasx); /* lbt is enabled only in kvm mode, not supported in tcg mode */ if (kvm_enabled()) { - cpu->lbt = ON_OFF_AUTO_AUTO; - object_property_add_bool(obj, "lbt", loongarch_get_lbt, - loongarch_set_lbt); - object_property_set_description(obj, "lbt", - "Set off to disable Binary Tranlation."); - - cpu->pmu = ON_OFF_AUTO_AUTO; - object_property_add_bool(obj, "pmu", loongarch_get_pmu, - loongarch_set_pmu); - object_property_set_description(obj, "pmu", - "Set off to performance monitor unit."); kvm_loongarch_cpu_post_init(cpu); - } else { - cpu->lbt = ON_OFF_AUTO_OFF; - cpu->pmu = ON_OFF_AUTO_OFF; } } diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index 2d2fb1e26117..b02824356a7f 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -1016,8 +1016,43 @@ int kvm_arch_init_vcpu(CPUState *cs) return ret; } +static bool loongarch_get_lbt(Object *obj, Error **errp) +{ + return LOONGARCH_CPU(obj)->lbt != ON_OFF_AUTO_OFF; +} + +static void loongarch_set_lbt(Object *obj, bool value, Error **errp) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(obj); + + cpu->lbt = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; +} + +static bool loongarch_get_pmu(Object *obj, Error **errp) +{ + return LOONGARCH_CPU(obj)->pmu != ON_OFF_AUTO_OFF; +} + +static void loongarch_set_pmu(Object *obj, bool value, Error **errp) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(obj); + + cpu->pmu = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; +} + void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu) { + cpu->lbt = ON_OFF_AUTO_AUTO; + object_property_add_bool(OBJECT(cpu), "lbt", loongarch_get_lbt, + loongarch_set_lbt); + object_property_set_description(OBJECT(cpu), "lbt", + "Set off to disable Binary Tranlation."); + + cpu->pmu = ON_OFF_AUTO_AUTO; + object_property_add_bool(OBJECT(cpu), "pmu", loongarch_get_pmu, + loongarch_set_pmu); + object_property_set_description(OBJECT(cpu), "pmu", + "Set off to disable performance monitor unit."); } int kvm_arch_destroy_vcpu(CPUState *cs) From 5b0502c56412c115f9ff37fad56ff53674676a22 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 24 Feb 2025 10:30:47 +0800 Subject: [PATCH 0236/1179] target/loongarch: Add vCPU property for paravirt ipi feature Property kvm-pv-ipi is added to paravirt ipi feature, it is specially for kvm mode. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/cpu.h | 1 + target/loongarch/kvm/kvm.c | 18 ++++++++++++++++++ target/loongarch/loongarch-qmp-cmds.c | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 74dffcb5527e..447192bfe0fe 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -406,6 +406,7 @@ struct ArchCPU { OnOffAuto pmu; OnOffAuto lsx; OnOffAuto lasx; + OnOffAuto kvm_pv_ipi; /* 'compatible' string for this CPU for Linux device trees */ const char *dtb_compatible; diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index b02824356a7f..83a6887fe8d1 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -1040,6 +1040,18 @@ static void loongarch_set_pmu(Object *obj, bool value, Error **errp) cpu->pmu = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; } +static bool kvm_pv_ipi_get(Object *obj, Error **errp) +{ + return LOONGARCH_CPU(obj)->kvm_pv_ipi != ON_OFF_AUTO_OFF; +} + +static void kvm_pv_ipi_set(Object *obj, bool value, Error **errp) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(obj); + + cpu->kvm_pv_ipi = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; +} + void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu) { cpu->lbt = ON_OFF_AUTO_AUTO; @@ -1053,6 +1065,12 @@ void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu) loongarch_set_pmu); object_property_set_description(OBJECT(cpu), "pmu", "Set off to disable performance monitor unit."); + + cpu->kvm_pv_ipi = ON_OFF_AUTO_AUTO; + object_property_add_bool(OBJECT(cpu), "kvm-pv-ipi", kvm_pv_ipi_get, + kvm_pv_ipi_set); + object_property_set_description(OBJECT(cpu), "kvm-pv-ipi", + "Set off to disable KVM paravirt IPI."); } int kvm_arch_destroy_vcpu(CPUState *cs) diff --git a/target/loongarch/loongarch-qmp-cmds.c b/target/loongarch/loongarch-qmp-cmds.c index 3fde5a5a2080..4f94a39833db 100644 --- a/target/loongarch/loongarch-qmp-cmds.c +++ b/target/loongarch/loongarch-qmp-cmds.c @@ -40,7 +40,7 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp) } static const char *cpu_model_advertised_features[] = { - "lsx", "lasx", "lbt", "pmu", NULL + "lsx", "lasx", "lbt", "pmu", "kvm-pv-ipi", NULL }; CpuModelExpansionInfo *qmp_query_cpu_model_expansion(CpuModelExpansionType type, From 620d9bd0022e011147c1133ef542c45a0da962e4 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 17 Feb 2025 09:52:40 +0800 Subject: [PATCH 0237/1179] target/loongarch: Add paravirt ipi feature detection Paravirt ipi feature is OnOffAuto type, feature detection is added to check whether it is supported by KVM host. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/cpu.h | 2 ++ target/loongarch/kvm/kvm.c | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 447192bfe0fe..3a8e45d9d5d7 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -287,6 +287,7 @@ enum loongarch_features { LOONGARCH_FEATURE_LASX, LOONGARCH_FEATURE_LBT, /* loongson binary translation extension */ LOONGARCH_FEATURE_PMU, + LOONGARCH_FEATURE_PV_IPI, }; typedef struct LoongArchBT { @@ -310,6 +311,7 @@ typedef struct CPUArchState { lbt_t lbt; uint32_t cpucfg[21]; + uint32_t pv_features; /* LoongArch CSRs */ uint64_t CSR_CRMD; diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index 83a6887fe8d1..3117441f53ba 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -8,7 +8,7 @@ #include "qemu/osdep.h" #include #include - +#include "asm-loongarch/kvm_para.h" #include "qapi/error.h" #include "qemu/timer.h" #include "qemu/error-report.h" @@ -882,6 +882,12 @@ static bool kvm_feature_supported(CPUState *cs, enum loongarch_features feature) ret = kvm_vm_ioctl(kvm_state, KVM_HAS_DEVICE_ATTR, &attr); return (ret == 0); + case LOONGARCH_FEATURE_PV_IPI: + attr.group = KVM_LOONGARCH_VM_FEAT_CTRL; + attr.attr = KVM_LOONGARCH_VM_FEAT_PV_IPI; + ret = kvm_vm_ioctl(kvm_state, KVM_HAS_DEVICE_ATTR, &attr); + return (ret == 0); + default: return false; } @@ -980,6 +986,29 @@ static int kvm_cpu_check_pmu(CPUState *cs, Error **errp) return 0; } +static int kvm_cpu_check_pv_features(CPUState *cs, Error **errp) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(cs); + CPULoongArchState *env = cpu_env(cs); + bool kvm_supported; + + kvm_supported = kvm_feature_supported(cs, LOONGARCH_FEATURE_PV_IPI); + if (cpu->kvm_pv_ipi == ON_OFF_AUTO_ON) { + if (!kvm_supported) { + error_setg(errp, "'pv_ipi' feature not supported by KVM host"); + return -ENOTSUP; + } + } else if (cpu->kvm_pv_ipi != ON_OFF_AUTO_AUTO) { + kvm_supported = false; + } + + if (kvm_supported) { + env->pv_features |= BIT(KVM_FEATURE_IPI); + } + + return 0; +} + int kvm_arch_init_vcpu(CPUState *cs) { uint64_t val; @@ -1013,6 +1042,11 @@ int kvm_arch_init_vcpu(CPUState *cs) error_report_err(local_err); } + ret = kvm_cpu_check_pv_features(cs, &local_err); + if (ret < 0) { + error_report_err(local_err); + } + return ret; } From 2698cc7c99b50cf4bb127c56e5c90f7f3cba6f0d Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 17 Feb 2025 09:37:11 +0800 Subject: [PATCH 0238/1179] target/loongarch: Enable paravirt ipi feature The similiar with cpucfg register, paravirt ipi feature is set in function kvm_arch_put_registers(). Instead the paravirt feature can be enabled only once, it cannot be changed dynamically. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/kvm/kvm.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index 3117441f53ba..ed0706e8e01e 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -83,6 +83,33 @@ static int kvm_set_stealtime(CPUState *cs) return 0; } +static int kvm_set_pv_features(CPUState *cs) +{ + CPULoongArchState *env = cpu_env(cs); + int err; + uint64_t val; + struct kvm_device_attr attr = { + .group = KVM_LOONGARCH_VCPU_CPUCFG, + .attr = CPUCFG_KVM_FEATURE, + .addr = (uint64_t)&val, + }; + + err = kvm_vcpu_ioctl(cs, KVM_HAS_DEVICE_ATTR, attr); + if (err) { + return 0; + } + + val = env->pv_features; + err = kvm_vcpu_ioctl(cs, KVM_SET_DEVICE_ATTR, attr); + if (err) { + error_report("Fail to set pv feature "TARGET_FMT_lx " with error %s", + val, strerror(errno)); + return err; + } + + return 0; +} + static int kvm_loongarch_get_regs_core(CPUState *cs) { int ret = 0; @@ -738,6 +765,7 @@ int kvm_arch_get_registers(CPUState *cs, Error **errp) int kvm_arch_put_registers(CPUState *cs, int level, Error **errp) { int ret; + static int once; ret = kvm_loongarch_put_regs_core(cs); if (ret) { @@ -764,6 +792,14 @@ int kvm_arch_put_registers(CPUState *cs, int level, Error **errp) return ret; } + if (!once) { + ret = kvm_set_pv_features(cs); + if (ret) { + return ret; + } + once = 1; + } + if (level >= KVM_PUT_FULL_STATE) { /* * only KVM_PUT_FULL_STATE is required, kvm kernel will clear From 610babce1ed83a0fd3af14f5195114d6c5338610 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Sat, 15 Feb 2025 17:01:21 +0800 Subject: [PATCH 0239/1179] target/loongarch: Add vCPU property for kvm steal time feature Property kvm-steal-time is added for kvm steal time feature, it is specially for kvm mode. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/cpu.h | 1 + target/loongarch/kvm/kvm.c | 18 ++++++++++++++++++ target/loongarch/loongarch-qmp-cmds.c | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 3a8e45d9d5d7..3e8a91748dfe 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -409,6 +409,7 @@ struct ArchCPU { OnOffAuto lsx; OnOffAuto lasx; OnOffAuto kvm_pv_ipi; + OnOffAuto kvm_steal_time; /* 'compatible' string for this CPU for Linux device trees */ const char *dtb_compatible; diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index ed0706e8e01e..def745122414 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -1122,6 +1122,18 @@ static void kvm_pv_ipi_set(Object *obj, bool value, Error **errp) cpu->kvm_pv_ipi = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; } +static bool kvm_steal_time_get(Object *obj, Error **errp) +{ + return LOONGARCH_CPU(obj)->kvm_steal_time != ON_OFF_AUTO_OFF; +} + +static void kvm_steal_time_set(Object *obj, bool value, Error **errp) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(obj); + + cpu->kvm_steal_time = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; +} + void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu) { cpu->lbt = ON_OFF_AUTO_AUTO; @@ -1141,6 +1153,12 @@ void kvm_loongarch_cpu_post_init(LoongArchCPU *cpu) kvm_pv_ipi_set); object_property_set_description(OBJECT(cpu), "kvm-pv-ipi", "Set off to disable KVM paravirt IPI."); + + cpu->kvm_steal_time = ON_OFF_AUTO_AUTO; + object_property_add_bool(OBJECT(cpu), "kvm-steal-time", kvm_steal_time_get, + kvm_steal_time_set); + object_property_set_description(OBJECT(cpu), "kvm-steal-time", + "Set off to disable KVM steal time."); } int kvm_arch_destroy_vcpu(CPUState *cs) diff --git a/target/loongarch/loongarch-qmp-cmds.c b/target/loongarch/loongarch-qmp-cmds.c index 4f94a39833db..6f732d80f3f8 100644 --- a/target/loongarch/loongarch-qmp-cmds.c +++ b/target/loongarch/loongarch-qmp-cmds.c @@ -40,7 +40,7 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp) } static const char *cpu_model_advertised_features[] = { - "lsx", "lasx", "lbt", "pmu", "kvm-pv-ipi", NULL + "lsx", "lasx", "lbt", "pmu", "kvm-pv-ipi", "kvm-steal-time", NULL }; CpuModelExpansionInfo *qmp_query_cpu_model_expansion(CpuModelExpansionType type, From 954cc5c311cd4459ce16a3302ff8611d98473d7d Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 17 Feb 2025 09:55:32 +0800 Subject: [PATCH 0240/1179] target/loongarch: Add kvm steal time feature detection Paravirt steal time feature is OnOffAuto type, feature detection is added to check whether it is supported on KVM host. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- target/loongarch/cpu.h | 1 + target/loongarch/kvm/kvm.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 3e8a91748dfe..83183a33ab8f 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -288,6 +288,7 @@ enum loongarch_features { LOONGARCH_FEATURE_LBT, /* loongson binary translation extension */ LOONGARCH_FEATURE_PMU, LOONGARCH_FEATURE_PV_IPI, + LOONGARCH_FEATURE_STEALTIME, }; typedef struct LoongArchBT { diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index def745122414..59a5f84161d1 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -924,6 +924,12 @@ static bool kvm_feature_supported(CPUState *cs, enum loongarch_features feature) ret = kvm_vm_ioctl(kvm_state, KVM_HAS_DEVICE_ATTR, &attr); return (ret == 0); + case LOONGARCH_FEATURE_STEALTIME: + attr.group = KVM_LOONGARCH_VM_FEAT_CTRL; + attr.attr = KVM_LOONGARCH_VM_FEAT_PV_STEALTIME; + ret = kvm_vm_ioctl(kvm_state, KVM_HAS_DEVICE_ATTR, &attr); + return (ret == 0); + default: return false; } @@ -1042,6 +1048,20 @@ static int kvm_cpu_check_pv_features(CPUState *cs, Error **errp) env->pv_features |= BIT(KVM_FEATURE_IPI); } + kvm_supported = kvm_feature_supported(cs, LOONGARCH_FEATURE_STEALTIME); + if (cpu->kvm_steal_time == ON_OFF_AUTO_ON) { + if (!kvm_supported) { + error_setg(errp, "'kvm stealtime' feature not supported by KVM host"); + return -ENOTSUP; + } + } else if (cpu->kvm_steal_time != ON_OFF_AUTO_AUTO) { + kvm_supported = false; + } + + if (kvm_supported) { + env->pv_features |= BIT(KVM_FEATURE_STEAL_TIME); + } + return 0; } From db369c11c90b35f3a6ab59ad78564aea5b30c3da Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 17 Feb 2025 09:56:13 +0800 Subject: [PATCH 0241/1179] target/loongarch: Enable virtual extioi feature Feature virtual extioi is loongArch virt machine property rather than vCPU property in qemu side. However it is vCPU property in KVM kernel side, here add loongArch virt machine property checking and enable virt extioi feature when vCPU is created. Signed-off-by: Bibo Mao Reviewed-by: Bibo Mao --- hw/loongarch/virt.c | 8 -------- include/hw/loongarch/virt.h | 9 +++++++++ target/loongarch/kvm/kvm.c | 10 ++++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index f2aa0a9782e7..59533b058b88 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -45,14 +45,6 @@ #include "hw/virtio/virtio-iommu.h" #include "qemu/error-report.h" -static bool virt_is_veiointc_enabled(LoongArchVirtMachineState *lvms) -{ - if (lvms->veiointc == ON_OFF_AUTO_OFF) { - return false; - } - return true; -} - static void virt_get_veiointc(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) { diff --git a/include/hw/loongarch/virt.h b/include/hw/loongarch/virt.h index 661efae61dfa..2e7cdfaef054 100644 --- a/include/hw/loongarch/virt.h +++ b/include/hw/loongarch/virt.h @@ -68,4 +68,13 @@ struct LoongArchVirtMachineState { OBJECT_DECLARE_SIMPLE_TYPE(LoongArchVirtMachineState, LOONGARCH_VIRT_MACHINE) void virt_acpi_setup(LoongArchVirtMachineState *lvms); void virt_fdt_setup(LoongArchVirtMachineState *lvms); + +static inline bool virt_is_veiointc_enabled(LoongArchVirtMachineState *lvms) +{ + if (lvms->veiointc == ON_OFF_AUTO_OFF) { + return false; + } + return true; +} + #endif diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c index 59a5f84161d1..28735c80be08 100644 --- a/target/loongarch/kvm/kvm.c +++ b/target/loongarch/kvm/kvm.c @@ -21,6 +21,7 @@ #include "exec/address-spaces.h" #include "hw/boards.h" #include "hw/irq.h" +#include "hw/loongarch/virt.h" #include "qemu/log.h" #include "hw/loader.h" #include "system/runstate.h" @@ -1030,6 +1031,7 @@ static int kvm_cpu_check_pmu(CPUState *cs, Error **errp) static int kvm_cpu_check_pv_features(CPUState *cs, Error **errp) { + MachineState *ms = MACHINE(qdev_get_machine()); LoongArchCPU *cpu = LOONGARCH_CPU(cs); CPULoongArchState *env = cpu_env(cs); bool kvm_supported; @@ -1062,6 +1064,14 @@ static int kvm_cpu_check_pv_features(CPUState *cs, Error **errp) env->pv_features |= BIT(KVM_FEATURE_STEAL_TIME); } + if (object_dynamic_cast(OBJECT(ms), TYPE_LOONGARCH_VIRT_MACHINE)) { + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(ms); + + if (virt_is_veiointc_enabled(lvms)) { + env->pv_features |= BIT(KVM_FEATURE_VIRT_EXTIOI); + } + } + return 0; } From d8b913f7c75cd38ad07276033faa51fee68514b4 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 23 Jan 2025 21:49:56 +0100 Subject: [PATCH 0242/1179] tests/qtest/qom-test: Test retrieval of machine class properties There were recently some crashes that occurred when trying to retrieve the properties of machines. Let's add a test to avoid regression here. Message-ID: <20250123204956.1561463-1-thuth@redhat.com> Reviewed-by: Fabiano Rosas Signed-off-by: Thomas Huth --- tests/qtest/qom-test.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/qtest/qom-test.c b/tests/qtest/qom-test.c index 1e30a5bfe8d2..27d70bc11ce2 100644 --- a/tests/qtest/qom-test.c +++ b/tests/qtest/qom-test.c @@ -88,6 +88,17 @@ static void test_machine(gconstpointer data) qts = qtest_initf("-machine %s", machine); + if (g_test_slow()) { + /* Make sure we can get the machine class properties: */ + g_autofree char *qom_machine = g_strdup_printf("%s-machine", machine); + + response = qtest_qmp(qts, "{ 'execute': 'qom-list-properties'," + " 'arguments': { 'typename': %s } }", + qom_machine); + g_assert(response); + qobject_unref(response); + } + test_properties(qts, "/machine", true); response = qtest_qmp(qts, "{ 'execute': 'quit' }"); From 36324c6774d2da16edc97cd3c9b30aa34d3f7a83 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Fri, 7 Feb 2025 14:53:43 +0900 Subject: [PATCH 0243/1179] qom: Use command line syntax for default values in help object_property_help() uses the conventional command line syntax instead of the JSON syntax. In particular, - Key-value pairs are written in the command line syntax. - bool description passed to the function says on/off instead of true/false. However, there is one exception: default values are formatted into JSON. While the command line and JSON syntaxes are consistent in many cases, there are two types where they disagree: string: The command line syntax omits quotes while JSON requires them. bool: JSON only accepts true/false for bool but the command line syntax accepts on/off too, and on/off are also more popular than true/false. For example, the docs directory has 2045 "on" occurances while it has only 194 "true" occurances. on/off are also accepted by OnOffAuto so users do not have to remember the type is bool or OnOffAuto to use the values. Omit quotes for strings and use on/off for bools when formatting default values for better consistency. Signed-off-by: Akihiko Odaki Link: https://lore.kernel.org/r/20250207-bool-v1-1-5749d5d6df24@daynix.com Signed-off-by: Paolo Bonzini --- qom/object_interfaces.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c index f35d33133175..1ffea1a72887 100644 --- a/qom/object_interfaces.c +++ b/qom/object_interfaces.c @@ -4,9 +4,11 @@ #include "qapi/error.h" #include "qapi/qapi-visit-qom.h" #include "qobject/qobject.h" +#include "qobject/qbool.h" #include "qobject/qdict.h" #include "qapi/qmp/qerror.h" #include "qobject/qjson.h" +#include "qobject/qstring.h" #include "qapi/qobject-input-visitor.h" #include "qapi/qobject-output-visitor.h" #include "qom/object_interfaces.h" @@ -177,9 +179,25 @@ char *object_property_help(const char *name, const char *type, g_string_append(str, description); } if (defval) { - g_autofree char *def_json = g_string_free(qobject_to_json(defval), - false); - g_string_append_printf(str, " (default: %s)", def_json); + g_autofree char *def_json = NULL; + const char *def; + + switch (qobject_type(defval)) { + case QTYPE_QSTRING: + def = qstring_get_str(qobject_to(QString, defval)); + break; + + case QTYPE_QBOOL: + def = qbool_get_bool(qobject_to(QBool, defval)) ? "on" : "off"; + break; + + default: + def_json = g_string_free(qobject_to_json(defval), false); + def = def_json; + break; + } + + g_string_append_printf(str, " (default: %s)", def); } return g_string_free(str, false); From 1433e38cc840957bafe6bc17a241c57cf93c90cd Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 5 Dec 2024 21:25:39 +0100 Subject: [PATCH 0244/1179] hpet: do not overwrite properties on post_load Migration relies on having the same device configuration on the source and destination. Therefore, there is no need to modify flags, timer capabilities and the fw_cfg HPET block id on migration; it was set to exactly the same values by realize. Reviewed-by: Zhao Liu (hpet_post_load only) Signed-off-by: Paolo Bonzini --- hw/timer/hpet.c | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c index dcff18a9871d..ccb97b680665 100644 --- a/hw/timer/hpet.c +++ b/hw/timer/hpet.c @@ -77,6 +77,7 @@ struct HPETState { uint8_t rtc_irq_level; qemu_irq pit_enabled; uint8_t num_timers; + uint8_t num_timers_save; uint32_t intcap; HPETTimer timer[HPET_MAX_TIMERS]; @@ -237,15 +238,12 @@ static int hpet_pre_save(void *opaque) s->hpet_counter = hpet_get_ticks(s); } - return 0; -} - -static int hpet_pre_load(void *opaque) -{ - HPETState *s = opaque; - - /* version 1 only supports 3, later versions will load the actual value */ - s->num_timers = HPET_MIN_TIMERS; + /* + * The number of timers must match on source and destination, but it was + * also added to the migration stream. Check that it matches the value + * that was configured. + */ + s->num_timers_save = s->num_timers; return 0; } @@ -253,12 +251,7 @@ static bool hpet_validate_num_timers(void *opaque, int version_id) { HPETState *s = opaque; - if (s->num_timers < HPET_MIN_TIMERS) { - return false; - } else if (s->num_timers > HPET_MAX_TIMERS) { - return false; - } - return true; + return s->num_timers == s->num_timers_save; } static int hpet_post_load(void *opaque, int version_id) @@ -277,16 +270,6 @@ static int hpet_post_load(void *opaque, int version_id) - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); } - /* Push number of timers into capability returned via HPET_ID */ - s->capability &= ~HPET_ID_NUM_TIM_MASK; - s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT; - hpet_fw_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; - - /* Derive HPET_MSI_SUPPORT from the capability of the first timer. */ - s->flags &= ~(1 << HPET_MSI_SUPPORT); - if (s->timer[0].config & HPET_TN_FSB_CAP) { - s->flags |= 1 << HPET_MSI_SUPPORT; - } return 0; } @@ -347,14 +330,13 @@ static const VMStateDescription vmstate_hpet = { .version_id = 2, .minimum_version_id = 1, .pre_save = hpet_pre_save, - .pre_load = hpet_pre_load, .post_load = hpet_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT64(config, HPETState), VMSTATE_UINT64(isr, HPETState), VMSTATE_UINT64(hpet_counter, HPETState), - VMSTATE_UINT8_V(num_timers, HPETState, 2), - VMSTATE_VALIDATE("num_timers in range", hpet_validate_num_timers), + VMSTATE_UINT8_V(num_timers_save, HPETState, 2), + VMSTATE_VALIDATE("num_timers must match", hpet_validate_num_timers), VMSTATE_STRUCT_VARRAY_UINT8(timer, HPETState, num_timers, 0, vmstate_hpet_timer, HPETTimer), VMSTATE_END_OF_LIST() From 9ee488670066b5fdeebf0015e9b4a40bdc3eccbf Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 17 Feb 2025 23:44:16 +0800 Subject: [PATCH 0245/1179] i386: Fix the missing Rust HPET configuration option The configuration option of Rust HPET is missing, so that PC machine can't boot with "hpet=on" when QEMU Rust support is enabled. Add the Rust HPET configuration option. Fixes: d128c341a744 ("i386: enable rust hpet for pc when rust is enabled") Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250217154416.3144571-1-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/hw/timer/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/hw/timer/Kconfig b/rust/hw/timer/Kconfig index afd980335037..42e421317a59 100644 --- a/rust/hw/timer/Kconfig +++ b/rust/hw/timer/Kconfig @@ -1,2 +1,3 @@ config X_HPET_RUST bool + default y if PC && HAVE_RUST From 4cfe9edb1b1961af9cda74351f73b0abb3159b67 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 16 Dec 2024 09:42:35 +0100 Subject: [PATCH 0246/1179] rust: subprojects: add libc crate This allows access to errno values. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/Cargo.lock | 7 ++++ rust/qemu-api/Cargo.toml | 1 + scripts/archive-source.sh | 2 +- scripts/make-release | 2 +- subprojects/.gitignore | 1 + subprojects/libc-0.2-rs.wrap | 7 ++++ .../packagefiles/libc-0.2-rs/meson.build | 37 +++++++++++++++++++ 7 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 subprojects/libc-0.2-rs.wrap create mode 100644 subprojects/packagefiles/libc-0.2-rs/meson.build diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 79e142723b83..2ebf0a11ea47 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -54,6 +54,12 @@ dependencies = [ "either", ] +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + [[package]] name = "pl011" version = "0.1.0" @@ -100,6 +106,7 @@ dependencies = [ name = "qemu_api" version = "0.1.0" dependencies = [ + "libc", "qemu_api_macros", "version_check", ] diff --git a/rust/qemu-api/Cargo.toml b/rust/qemu-api/Cargo.toml index a51dd1428522..57747bc93414 100644 --- a/rust/qemu-api/Cargo.toml +++ b/rust/qemu-api/Cargo.toml @@ -16,6 +16,7 @@ rust-version = "1.63.0" [dependencies] qemu_api_macros = { path = "../qemu-api-macros" } +libc = "0.2.162" [build-dependencies] version_check = "~0.9" diff --git a/scripts/archive-source.sh b/scripts/archive-source.sh index 30677c3ec903..e461c1531ed6 100755 --- a/scripts/archive-source.sh +++ b/scripts/archive-source.sh @@ -28,7 +28,7 @@ sub_file="${sub_tdir}/submodule.tar" # different to the host OS. subprojects="keycodemapdb libvfio-user berkeley-softfloat-3 berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs - bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs + bilge-impl-0.2-rs either-1-rs itertools-0.11-rs libc-0.2-rs proc-macro2-1-rs proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs syn-2-rs unicode-ident-1-rs" sub_deinit="" diff --git a/scripts/make-release b/scripts/make-release index 1b89b3423a89..8c3594a1a470 100755 --- a/scripts/make-release +++ b/scripts/make-release @@ -41,7 +41,7 @@ fi # Only include wraps that are invoked with subproject() SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3 berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs - bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs + bilge-impl-0.2-rs either-1-rs itertools-0.11-rs libc-0.2-rs proc-macro2-1-rs proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs syn-2-rs unicode-ident-1-rs" diff --git a/subprojects/.gitignore b/subprojects/.gitignore index 50f173f90dbe..d12d34618cc2 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -11,6 +11,7 @@ /bilge-impl-0.2.0 /either-1.12.0 /itertools-0.11.0 +/libc-0.2.162 /proc-macro-error-1.0.4 /proc-macro-error-attr-1.0.4 /proc-macro2-1.0.84 diff --git a/subprojects/libc-0.2-rs.wrap b/subprojects/libc-0.2-rs.wrap new file mode 100644 index 000000000000..bbe08f878834 --- /dev/null +++ b/subprojects/libc-0.2-rs.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory = libc-0.2.162 +source_url = https://crates.io/api/v1/crates/libc/0.2.162/download +source_filename = libc-0.2.162.tar.gz +source_hash = 18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398 +#method = cargo +patch_directory = libc-0.2-rs diff --git a/subprojects/packagefiles/libc-0.2-rs/meson.build b/subprojects/packagefiles/libc-0.2-rs/meson.build new file mode 100644 index 000000000000..ac4f80dba98e --- /dev/null +++ b/subprojects/packagefiles/libc-0.2-rs/meson.build @@ -0,0 +1,37 @@ +project('libc-0.2-rs', 'rust', + meson_version: '>=1.5.0', + version: '0.2.162', + license: 'MIT OR Apache-2.0', + default_options: []) + +_libc_rs = static_library( + 'libc', + files('src/lib.rs'), + gnu_symbol_visibility: 'hidden', + override_options: ['rust_std=2015', 'build.rust_std=2015'], + rust_abi: 'rust', + rust_args: [ + '--cap-lints', 'allow', + '--cfg', 'freebsd11', + '--cfg', 'libc_priv_mod_use', + '--cfg', 'libc_union', + '--cfg', 'libc_const_size_of', + '--cfg', 'libc_align', + '--cfg', 'libc_int128', + '--cfg', 'libc_core_cvoid', + '--cfg', 'libc_packedN', + '--cfg', 'libc_cfg_target_vendor', + '--cfg', 'libc_non_exhaustive', + '--cfg', 'libc_long_array', + '--cfg', 'libc_ptr_addr_of', + '--cfg', 'libc_underscore_const_names', + '--cfg', 'libc_const_extern_fn', + ], + dependencies: [], +) + +libc_dep = declare_dependency( + link_with: _libc_rs, +) + +meson.override_dependency('libc-0.2-rs', libc_dep) From 23a4b3ebc74bbc6b6f44edf2255864042f122b82 Mon Sep 17 00:00:00 2001 From: Stephen Bates Date: Fri, 29 Nov 2024 09:27:39 -0700 Subject: [PATCH 0247/1179] hw/nvme: Add OCP SMART / Health Information Extended Log Page The Open Compute Project [1] includes a Datacenter NVMe SSD Specification [2]. The most recent version of this specification (as of November 2024) is 2.6.1. This specification layers on top of the NVM Express specifications [3] to provide additional functionality. A key part of of this is the 512 Byte OCP SMART / Health Information Extended log page that is defined in Section 4.8.6 of the specification. We add a controller argument (ocp) that toggles on/off the SMART log extended structure. To accommodate different vendor specific specifications like OCP, we add a multiplexing function (nvme_vendor_specific_log) which will route to the different log functions based on arguments and log ids. We only return the OCP extended SMART log when the command is 0xC0 and ocp has been turned on in the nvme argumants. Though we add the whole nvme SMART log extended structure, we only populate the physical_media_units_{read,written}, log_page_version and log_page_uuid. This patch is based on work done by Joel but has been modified enough that he requested a co-developed-by tag rather than a signed-off-by. [1]: https://www.opencompute.org/ [2]: https://www.opencompute.org/documents/datacenter-nvme-ssd-specification-v2-6-1-pdf [3]: https://nvmexpress.org/specifications/ Signed-off-by: Stephen Bates Co-developed-by: Joel Granados Reviewed-by: Klaus Jensen Signed-off-by: Klaus Jensen --- docs/system/devices/nvme.rst | 7 +++++ hw/nvme/ctrl.c | 59 ++++++++++++++++++++++++++++++++++++ hw/nvme/nvme.h | 1 + include/block/nvme.h | 41 +++++++++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/docs/system/devices/nvme.rst b/docs/system/devices/nvme.rst index d2b1ca96455f..6509b35fcb4e 100644 --- a/docs/system/devices/nvme.rst +++ b/docs/system/devices/nvme.rst @@ -53,6 +53,13 @@ parameters. Vendor ID. Set this to ``on`` to revert to the unallocated Intel ID previously used. +``ocp`` (default: ``off``) + The Open Compute Project defines the Datacenter NVMe SSD Specification that + sits on top of NVMe. It describes additional commands and NVMe behaviors + specific for the Datacenter. When this option is ``on`` OCP features such as + the SMART / Health information extended log become available in the + controller. We emulate version 5 of this log page. + Additional Namespaces --------------------- diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 8175751518f8..11687e597a11 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -4917,6 +4917,45 @@ static void nvme_set_blk_stats(NvmeNamespace *ns, struct nvme_stats *stats) stats->write_commands += s->nr_ops[BLOCK_ACCT_WRITE]; } +static uint16_t nvme_ocp_extended_smart_info(NvmeCtrl *n, uint8_t rae, + uint32_t buf_len, uint64_t off, + NvmeRequest *req) +{ + NvmeNamespace *ns = NULL; + NvmeSmartLogExtended smart_l = { 0 }; + struct nvme_stats stats = { 0 }; + uint32_t trans_len; + + if (off >= sizeof(smart_l)) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + /* accumulate all stats from all namespaces */ + for (int i = 1; i <= NVME_MAX_NAMESPACES; i++) { + ns = nvme_ns(n, i); + if (ns) { + nvme_set_blk_stats(ns, &stats); + } + } + + smart_l.physical_media_units_written[0] = cpu_to_le64(stats.units_written); + smart_l.physical_media_units_read[0] = cpu_to_le64(stats.units_read); + smart_l.log_page_version = 0x0005; + + static const uint8_t guid[16] = { + 0xC5, 0xAF, 0x10, 0x28, 0xEA, 0xBF, 0xF2, 0xA4, + 0x9C, 0x4F, 0x6F, 0x7C, 0xC9, 0x14, 0xD5, 0xAF + }; + memcpy(smart_l.log_page_guid, guid, sizeof(smart_l.log_page_guid)); + + if (!rae) { + nvme_clear_events(n, NVME_AER_TYPE_SMART); + } + + trans_len = MIN(sizeof(smart_l) - off, buf_len); + return nvme_c2h(n, (uint8_t *) &smart_l + off, trans_len, req); +} + static uint16_t nvme_smart_info(NvmeCtrl *n, uint8_t rae, uint32_t buf_len, uint64_t off, NvmeRequest *req) { @@ -5146,6 +5185,23 @@ static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len, return nvme_c2h(n, ((uint8_t *)&log) + off, trans_len, req); } +static uint16_t nvme_vendor_specific_log(NvmeCtrl *n, uint8_t rae, + uint32_t buf_len, uint64_t off, + NvmeRequest *req, uint8_t lid) +{ + switch (lid) { + case NVME_OCP_EXTENDED_SMART_INFO: + if (n->params.ocp) { + return nvme_ocp_extended_smart_info(n, rae, buf_len, off, req); + } + break; + /* add a case for each additional vendor specific log id */ + } + + trace_pci_nvme_err_invalid_log_page(nvme_cid(req), lid); + return NVME_INVALID_FIELD | NVME_DNR; +} + static size_t sizeof_fdp_conf_descr(size_t nruh, size_t vss) { size_t entry_siz = sizeof(NvmeFdpDescrHdr) + nruh * sizeof(NvmeRuhDescr) @@ -5396,6 +5452,8 @@ static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req) return nvme_smart_info(n, rae, len, off, req); case NVME_LOG_FW_SLOT_INFO: return nvme_fw_log_info(n, len, off, req); + case NVME_LOG_VENDOR_START...NVME_LOG_VENDOR_END: + return nvme_vendor_specific_log(n, rae, len, off, req, lid); case NVME_LOG_CHANGED_NSLIST: return nvme_changed_nslist(n, rae, len, off, req); case NVME_LOG_CMD_EFFECTS: @@ -8971,6 +9029,7 @@ static const Property nvme_props[] = { DEFINE_PROP_BOOL("atomic.dn", NvmeCtrl, params.atomic_dn, 0), DEFINE_PROP_UINT16("atomic.awun", NvmeCtrl, params.atomic_awun, 0), DEFINE_PROP_UINT16("atomic.awupf", NvmeCtrl, params.atomic_awupf, 0), + DEFINE_PROP_BOOL("ocp", NvmeCtrl, params.ocp, false), }; static void nvme_get_smart_warning(Object *obj, Visitor *v, const char *name, diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h index 724220691057..e307e733e46a 100644 --- a/hw/nvme/nvme.h +++ b/hw/nvme/nvme.h @@ -545,6 +545,7 @@ typedef struct NvmeParams { uint32_t sriov_max_vq_per_vf; uint32_t sriov_max_vi_per_vf; bool msix_exclusive_bar; + bool ocp; struct { bool mem; diff --git a/include/block/nvme.h b/include/block/nvme.h index f4d108841bf5..975d321c5c08 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -1015,6 +1015,40 @@ typedef struct QEMU_PACKED NvmeSmartLog { uint8_t reserved2[320]; } NvmeSmartLog; +typedef struct QEMU_PACKED NvmeSmartLogExtended { + uint64_t physical_media_units_written[2]; + uint64_t physical_media_units_read[2]; + uint64_t bad_user_blocks; + uint64_t bad_system_nand_blocks; + uint64_t xor_recovery_count; + uint64_t uncorrectable_read_error_count; + uint64_t soft_ecc_error_count; + uint64_t end2end_correction_counts; + uint8_t system_data_percent_used; + uint8_t refresh_counts[7]; + uint64_t user_data_erase_counts; + uint16_t thermal_throttling_stat_and_count; + uint16_t dssd_spec_version[3]; + uint64_t pcie_correctable_error_count; + uint32_t incomplete_shutdowns; + uint32_t rsvd116; + uint8_t percent_free_blocks; + uint8_t rsvd121[7]; + uint16_t capacity_health; + uint8_t nvme_errata_ver; + uint8_t rsvd131[5]; + uint64_t unaligned_io; + uint64_t security_ver_num; + uint64_t total_nuse; + uint64_t plp_start_count[2]; + uint64_t endurance_estimate[2]; + uint64_t pcie_retraining_count; + uint64_t power_state_change_count; + uint8_t rsvd208[286]; + uint16_t log_page_version; + uint64_t log_page_guid[2]; +} NvmeSmartLogExtended; + #define NVME_SMART_WARN_MAX 6 enum NvmeSmartWarn { NVME_SMART_SPARE = 1 << 0, @@ -1052,6 +1086,12 @@ enum NvmeLogIdentifier { NVME_LOG_FDP_RUH_USAGE = 0x21, NVME_LOG_FDP_STATS = 0x22, NVME_LOG_FDP_EVENTS = 0x23, + NVME_LOG_VENDOR_START = 0xc0, + NVME_LOG_VENDOR_END = 0xff, +}; + +enum NvmeOcpLogIdentifier { + NVME_OCP_EXTENDED_SMART_INFO = 0xc0, }; typedef struct QEMU_PACKED NvmePSD { @@ -1899,6 +1939,7 @@ static inline void _nvme_check_size(void) QEMU_BUILD_BUG_ON(sizeof(NvmeErrorLog) != 64); QEMU_BUILD_BUG_ON(sizeof(NvmeFwSlotInfoLog) != 512); QEMU_BUILD_BUG_ON(sizeof(NvmeSmartLog) != 512); + QEMU_BUILD_BUG_ON(sizeof(NvmeSmartLogExtended) != 512); QEMU_BUILD_BUG_ON(sizeof(NvmeEffectsLog) != 4096); QEMU_BUILD_BUG_ON(sizeof(NvmeIdCtrl) != 4096); QEMU_BUILD_BUG_ON(sizeof(NvmeIdCtrlZoned) != 4096); From cd59f50ab017183805a0dd82f5e85159ecc355ce Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:02 +0100 Subject: [PATCH 0248/1179] hw/nvme: always initialize a subsystem If no nvme-subsys is explicitly configured, instantiate one. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 36 +++++++++++++++------------- hw/nvme/ns.c | 64 +++++++++++++++++--------------------------------- 2 files changed, 42 insertions(+), 58 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 11687e597a11..5a7ccbcc1b80 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -8823,15 +8823,13 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) id->psd[0].enlat = cpu_to_le32(0x10); id->psd[0].exlat = cpu_to_le32(0x4); - if (n->subsys) { - id->cmic |= NVME_CMIC_MULTI_CTRL; - ctratt |= NVME_CTRATT_ENDGRPS; + id->cmic |= NVME_CMIC_MULTI_CTRL; + ctratt |= NVME_CTRATT_ENDGRPS; - id->endgidmax = cpu_to_le16(0x1); + id->endgidmax = cpu_to_le16(0x1); - if (n->subsys->endgrp.fdp.enabled) { - ctratt |= NVME_CTRATT_FDPS; - } + if (n->subsys->endgrp.fdp.enabled) { + ctratt |= NVME_CTRATT_FDPS; } id->ctratt = cpu_to_le32(ctratt); @@ -8860,7 +8858,15 @@ static int nvme_init_subsys(NvmeCtrl *n, Error **errp) int cntlid; if (!n->subsys) { - return 0; + DeviceState *dev = qdev_new(TYPE_NVME_SUBSYS); + + qdev_prop_set_string(dev, "nqn", n->params.serial); + + if (!qdev_realize(dev, NULL, errp)) { + return -1; + } + + n->subsys = NVME_SUBSYS(dev); } cntlid = nvme_subsys_register_ctrl(n, errp); @@ -8950,17 +8956,15 @@ static void nvme_exit(PCIDevice *pci_dev) nvme_ctrl_reset(n, NVME_RESET_FUNCTION); - if (n->subsys) { - for (i = 1; i <= NVME_MAX_NAMESPACES; i++) { - ns = nvme_ns(n, i); - if (ns) { - ns->attached--; - } + for (i = 1; i <= NVME_MAX_NAMESPACES; i++) { + ns = nvme_ns(n, i); + if (ns) { + ns->attached--; } - - nvme_subsys_unregister_ctrl(n->subsys, n); } + nvme_subsys_unregister_ctrl(n->subsys, n); + g_free(n->cq); g_free(n->sq); g_free(n->aer_reqs); diff --git a/hw/nvme/ns.c b/hw/nvme/ns.c index 410df2959192..94cabc6a5b8d 100644 --- a/hw/nvme/ns.c +++ b/hw/nvme/ns.c @@ -727,25 +727,14 @@ static void nvme_ns_realize(DeviceState *dev, Error **errp) uint32_t nsid = ns->params.nsid; int i; - if (!n->subsys) { - /* If no subsys, the ns cannot be attached to more than one ctrl. */ - ns->params.shared = false; - if (ns->params.detached) { - error_setg(errp, "detached requires that the nvme device is " - "linked to an nvme-subsys device"); - return; - } - } else { - /* - * If this namespace belongs to a subsystem (through a link on the - * controller device), reparent the device. - */ - if (!qdev_set_parent_bus(dev, &subsys->bus.parent_bus, errp)) { - return; - } - ns->subsys = subsys; - ns->endgrp = &subsys->endgrp; + assert(subsys); + + /* reparent to subsystem bus */ + if (!qdev_set_parent_bus(dev, &subsys->bus.parent_bus, errp)) { + return; } + ns->subsys = subsys; + ns->endgrp = &subsys->endgrp; if (nvme_ns_setup(ns, errp)) { return; @@ -753,7 +742,7 @@ static void nvme_ns_realize(DeviceState *dev, Error **errp) if (!nsid) { for (i = 1; i <= NVME_MAX_NAMESPACES; i++) { - if (nvme_ns(n, i) || nvme_subsys_ns(subsys, i)) { + if (nvme_subsys_ns(subsys, i)) { continue; } @@ -765,38 +754,29 @@ static void nvme_ns_realize(DeviceState *dev, Error **errp) error_setg(errp, "no free namespace id"); return; } - } else { - if (nvme_ns(n, nsid) || nvme_subsys_ns(subsys, nsid)) { - error_setg(errp, "namespace id '%d' already allocated", nsid); - return; - } + } else if (nvme_subsys_ns(subsys, nsid)) { + error_setg(errp, "namespace id '%d' already allocated", nsid); + return; } - if (subsys) { - subsys->namespaces[nsid] = ns; + subsys->namespaces[nsid] = ns; - ns->id_ns.endgid = cpu_to_le16(0x1); - ns->id_ns_ind.endgrpid = cpu_to_le16(0x1); + ns->id_ns.endgid = cpu_to_le16(0x1); + ns->id_ns_ind.endgrpid = cpu_to_le16(0x1); - if (ns->params.detached) { - return; - } + if (ns->params.detached) { + return; + } - if (ns->params.shared) { - for (i = 0; i < ARRAY_SIZE(subsys->ctrls); i++) { - NvmeCtrl *ctrl = subsys->ctrls[i]; + if (ns->params.shared) { + for (i = 0; i < ARRAY_SIZE(subsys->ctrls); i++) { + NvmeCtrl *ctrl = subsys->ctrls[i]; - if (ctrl && ctrl != SUBSYS_SLOT_RSVD) { - nvme_attach_ns(ctrl, ns); - } + if (ctrl && ctrl != SUBSYS_SLOT_RSVD) { + nvme_attach_ns(ctrl, ns); } - - return; } - } - - nvme_attach_ns(n, ns); } static const Property nvme_ns_props[] = { From e7047adf1ebf4b5ad63e42c799d8334dcd3d139d Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:03 +0100 Subject: [PATCH 0249/1179] hw/nvme: make oacs dynamic Virtualization Management needs sriov-related parameters. Only report support for the command when that conditions are true. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 25 ++++++++++++++++++------- hw/nvme/nvme.h | 4 ++++ include/block/nvme.h | 3 ++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 5a7ccbcc1b80..4ee8588ca9ae 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -266,7 +266,7 @@ static const uint32_t nvme_feature_cap[NVME_FID_MAX] = { [NVME_FDP_EVENTS] = NVME_FEAT_CAP_CHANGE | NVME_FEAT_CAP_NS, }; -static const uint32_t nvme_cse_acs[256] = { +static const uint32_t nvme_cse_acs_default[256] = { [NVME_ADM_CMD_DELETE_SQ] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_CREATE_SQ] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_GET_LOG_PAGE] = NVME_CMD_EFF_CSUPP, @@ -278,7 +278,6 @@ static const uint32_t nvme_cse_acs[256] = { [NVME_ADM_CMD_GET_FEATURES] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_ASYNC_EV_REQ] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_NS_ATTACHMENT] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC, - [NVME_ADM_CMD_VIRT_MNGMT] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_DBBUF_CONFIG] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_FORMAT_NVM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_ADM_CMD_DIRECTIVE_RECV] = NVME_CMD_EFF_CSUPP, @@ -5174,7 +5173,7 @@ static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len, } } - memcpy(log.acs, nvme_cse_acs, sizeof(nvme_cse_acs)); + memcpy(log.acs, n->cse.acs, sizeof(log.acs)); if (src_iocs) { memcpy(log.iocs, src_iocs, sizeof(log.iocs)); @@ -7300,7 +7299,7 @@ static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req) trace_pci_nvme_admin_cmd(nvme_cid(req), nvme_sqid(req), req->cmd.opcode, nvme_adm_opc_str(req->cmd.opcode)); - if (!(nvme_cse_acs[req->cmd.opcode] & NVME_CMD_EFF_CSUPP)) { + if (!(n->cse.acs[req->cmd.opcode] & NVME_CMD_EFF_CSUPP)) { trace_pci_nvme_err_invalid_admin_opc(req->cmd.opcode); return NVME_INVALID_OPCODE | NVME_DNR; } @@ -8740,6 +8739,9 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) uint64_t cap = ldq_le_p(&n->bar.cap); NvmeSecCtrlEntry *sctrl = nvme_sctrl(n); uint32_t ctratt; + uint16_t oacs; + + memcpy(n->cse.acs, nvme_cse_acs_default, sizeof(n->cse.acs)); id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID)); id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID)); @@ -8770,9 +8772,18 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) id->mdts = n->params.mdts; id->ver = cpu_to_le32(NVME_SPEC_VER); - id->oacs = - cpu_to_le16(NVME_OACS_NS_MGMT | NVME_OACS_FORMAT | NVME_OACS_DBBUF | - NVME_OACS_DIRECTIVES); + + oacs = NVME_OACS_NMS | NVME_OACS_FORMAT | NVME_OACS_DBBUF | + NVME_OACS_DIRECTIVES; + + if (n->params.sriov_max_vfs) { + oacs |= NVME_OACS_VMS; + + n->cse.acs[NVME_ADM_CMD_VIRT_MNGMT] = NVME_CMD_EFF_CSUPP; + } + + id->oacs = cpu_to_le16(oacs); + id->cntrltype = 0x1; /* diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h index e307e733e46a..b86cad388f5a 100644 --- a/hw/nvme/nvme.h +++ b/hw/nvme/nvme.h @@ -584,6 +584,10 @@ typedef struct NvmeCtrl { uint64_t dbbuf_eis; bool dbbuf_enabled; + struct { + uint32_t acs[256]; + } cse; + struct { MemoryRegion mem; uint8_t *buf; diff --git a/include/block/nvme.h b/include/block/nvme.h index 975d321c5c08..80fbcb420d48 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -1232,8 +1232,9 @@ enum NvmeIdCtrlOacs { NVME_OACS_SECURITY = 1 << 0, NVME_OACS_FORMAT = 1 << 1, NVME_OACS_FW = 1 << 2, - NVME_OACS_NS_MGMT = 1 << 3, + NVME_OACS_NMS = 1 << 3, NVME_OACS_DIRECTIVES = 1 << 5, + NVME_OACS_VMS = 1 << 7, NVME_OACS_DBBUF = 1 << 8, }; From 9cf6ec06592dea1973e66cd5cedf96fc59639047 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:04 +0100 Subject: [PATCH 0250/1179] hw/nvme: add knob for doorbell buffer config support Add a 'dbcs' knob to allow Doorbell Buffer Config command to be disabled. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 11 ++++++++--- hw/nvme/nvme.h | 1 + include/block/nvme.h | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 4ee8588ca9ae..1ad76da943a6 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -278,7 +278,6 @@ static const uint32_t nvme_cse_acs_default[256] = { [NVME_ADM_CMD_GET_FEATURES] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_ASYNC_EV_REQ] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_NS_ATTACHMENT] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC, - [NVME_ADM_CMD_DBBUF_CONFIG] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_FORMAT_NVM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_ADM_CMD_DIRECTIVE_RECV] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_DIRECTIVE_SEND] = NVME_CMD_EFF_CSUPP, @@ -8773,8 +8772,13 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) id->mdts = n->params.mdts; id->ver = cpu_to_le32(NVME_SPEC_VER); - oacs = NVME_OACS_NMS | NVME_OACS_FORMAT | NVME_OACS_DBBUF | - NVME_OACS_DIRECTIVES; + oacs = NVME_OACS_NMS | NVME_OACS_FORMAT | NVME_OACS_DIRECTIVES; + + if (n->params.dbcs) { + oacs |= NVME_OACS_DBCS; + + n->cse.acs[NVME_ADM_CMD_DBBUF_CONFIG] = NVME_CMD_EFF_CSUPP; + } if (n->params.sriov_max_vfs) { oacs |= NVME_OACS_VMS; @@ -9024,6 +9028,7 @@ static const Property nvme_props[] = { DEFINE_PROP_BOOL("use-intel-id", NvmeCtrl, params.use_intel_id, false), DEFINE_PROP_BOOL("legacy-cmb", NvmeCtrl, params.legacy_cmb, false), DEFINE_PROP_BOOL("ioeventfd", NvmeCtrl, params.ioeventfd, false), + DEFINE_PROP_BOOL("dbcs", NvmeCtrl, params.dbcs, true), DEFINE_PROP_UINT8("zoned.zasl", NvmeCtrl, params.zasl, 0), DEFINE_PROP_BOOL("zoned.auto_transition", NvmeCtrl, params.auto_transition_zones, true), diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h index b86cad388f5a..b8d063a027a9 100644 --- a/hw/nvme/nvme.h +++ b/hw/nvme/nvme.h @@ -539,6 +539,7 @@ typedef struct NvmeParams { bool auto_transition_zones; bool legacy_cmb; bool ioeventfd; + bool dbcs; uint16_t sriov_max_vfs; uint16_t sriov_vq_flexible; uint16_t sriov_vi_flexible; diff --git a/include/block/nvme.h b/include/block/nvme.h index 80fbcb420d48..63eb74460eac 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -1235,7 +1235,7 @@ enum NvmeIdCtrlOacs { NVME_OACS_NMS = 1 << 3, NVME_OACS_DIRECTIVES = 1 << 5, NVME_OACS_VMS = 1 << 7, - NVME_OACS_DBBUF = 1 << 8, + NVME_OACS_DBCS = 1 << 8, }; enum NvmeIdCtrlOncs { From b202fb549dc487c5611564e5d03286748586aa34 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:05 +0100 Subject: [PATCH 0251/1179] nvme: fix iocs status code values The status codes related to I/O Command Sets are in the wrong group. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 4 ++-- include/block/nvme.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 1ad76da943a6..2b73f601608f 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -5681,7 +5681,7 @@ static uint16_t nvme_identify_ns(NvmeCtrl *n, NvmeRequest *req, bool active) return nvme_c2h(n, (uint8_t *)&ns->id_ns, sizeof(NvmeIdNs), req); } - return NVME_INVALID_CMD_SET | NVME_DNR; + return NVME_INVALID_IOCS | NVME_DNR; } static uint16_t nvme_identify_ctrl_list(NvmeCtrl *n, NvmeRequest *req, @@ -6647,7 +6647,7 @@ static uint16_t nvme_set_feature(NvmeCtrl *n, NvmeRequest *req) case NVME_COMMAND_SET_PROFILE: if (dw11 & 0x1ff) { trace_pci_nvme_err_invalid_iocsci(dw11 & 0x1ff); - return NVME_CMD_SET_CMB_REJECTED | NVME_DNR; + return NVME_IOCS_COMBINATION_REJECTED | NVME_DNR; } break; case NVME_FDP_MODE: diff --git a/include/block/nvme.h b/include/block/nvme.h index 63eb74460eac..aecfc9ce66b4 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -906,8 +906,6 @@ enum NvmeStatusCodes { NVME_SGL_DESCR_TYPE_INVALID = 0x0011, NVME_INVALID_USE_OF_CMB = 0x0012, NVME_INVALID_PRP_OFFSET = 0x0013, - NVME_CMD_SET_CMB_REJECTED = 0x002b, - NVME_INVALID_CMD_SET = 0x002c, NVME_FDP_DISABLED = 0x0029, NVME_INVALID_PHID_LIST = 0x002a, NVME_LBA_RANGE = 0x0080, @@ -940,6 +938,8 @@ enum NvmeStatusCodes { NVME_INVALID_SEC_CTRL_STATE = 0x0120, NVME_INVALID_NUM_RESOURCES = 0x0121, NVME_INVALID_RESOURCE_ID = 0x0122, + NVME_IOCS_COMBINATION_REJECTED = 0x012b, + NVME_INVALID_IOCS = 0x012c, NVME_CONFLICTING_ATTRS = 0x0180, NVME_INVALID_PROT_INFO = 0x0181, NVME_WRITE_TO_RO = 0x0182, From d96a32de3fd0880a9340590fd279288e42e983c1 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:06 +0100 Subject: [PATCH 0252/1179] hw/nvme: be compliant wrt. dsm processing limits The specification states that, > The controller shall set all three processing limit fields (i.e., the > DMRL, DMRSL and DMSL fields) to non-zero values or shall clear all > three processing limit fields to 0h. So, set the DMRL and DMSL fields in addition to DMRSL. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 24 +++++++++++++++--------- include/block/nvme.h | 2 ++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 2b73f601608f..86e1c48fab82 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -5639,7 +5639,9 @@ static uint16_t nvme_identify_ctrl_csi(NvmeCtrl *n, NvmeRequest *req) switch (c->csi) { case NVME_CSI_NVM: id_nvm->vsl = n->params.vsl; + id_nvm->dmrl = NVME_ID_CTRL_NVM_DMRL_MAX; id_nvm->dmrsl = cpu_to_le32(n->dmrsl); + id_nvm->dmsl = NVME_ID_CTRL_NVM_DMRL_MAX * n->dmrsl; break; case NVME_CSI_ZONED: @@ -6696,18 +6698,23 @@ static uint16_t nvme_aer(NvmeCtrl *n, NvmeRequest *req) return NVME_NO_COMPLETE; } -static void nvme_update_dmrsl(NvmeCtrl *n) +static void nvme_update_dsm_limits(NvmeCtrl *n, NvmeNamespace *ns) { - int nsid; + if (ns) { + n->dmrsl = + MIN_NON_ZERO(n->dmrsl, BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1)); - for (nsid = 1; nsid <= NVME_MAX_NAMESPACES; nsid++) { - NvmeNamespace *ns = nvme_ns(n, nsid); + return; + } + + for (uint32_t nsid = 1; nsid <= NVME_MAX_NAMESPACES; nsid++) { + ns = nvme_ns(n, nsid); if (!ns) { continue; } - n->dmrsl = MIN_NON_ZERO(n->dmrsl, - BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1)); + n->dmrsl = + MIN_NON_ZERO(n->dmrsl, BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1)); } } @@ -6795,7 +6802,7 @@ static uint16_t nvme_ns_attachment(NvmeCtrl *n, NvmeRequest *req) ctrl->namespaces[nsid] = NULL; ns->attached--; - nvme_update_dmrsl(ctrl); + nvme_update_dsm_limits(ctrl, NULL); break; @@ -8902,8 +8909,7 @@ void nvme_attach_ns(NvmeCtrl *n, NvmeNamespace *ns) n->namespaces[nsid] = ns; ns->attached++; - n->dmrsl = MIN_NON_ZERO(n->dmrsl, - BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1)); + nvme_update_dsm_limits(n, ns); } static void nvme_realize(PCIDevice *pci_dev, Error **errp) diff --git a/include/block/nvme.h b/include/block/nvme.h index aecfc9ce66b4..763b2b2f0ec7 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -1207,6 +1207,8 @@ typedef struct NvmeIdCtrlZoned { uint8_t rsvd1[4095]; } NvmeIdCtrlZoned; +#define NVME_ID_CTRL_NVM_DMRL_MAX 255 + typedef struct NvmeIdCtrlNvm { uint8_t vsl; uint8_t wzsl; From 8a420dd109b9e4e2244cfa32bc92829093268b3e Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 14 Nov 2024 09:05:38 +0100 Subject: [PATCH 0253/1179] rust: add module to convert between success/-errno and io::Result It is a common convention in QEMU to return a positive value in case of success, and a negated errno value in case of error. Unfortunately, using errno portably in Rust is a bit complicated; on Unix the errno values are supported natively by io::Error, but on Windows they are not; so, use the libc crate. This is a set of utility functions that are used by both chardev and block layer bindings. Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 1 + rust/qemu-api/meson.build | 4 + rust/qemu-api/src/assertions.rs | 28 +++ rust/qemu-api/src/errno.rs | 345 ++++++++++++++++++++++++++++++++ rust/qemu-api/src/lib.rs | 1 + rust/qemu-api/src/prelude.rs | 2 + 6 files changed, 381 insertions(+) create mode 100644 rust/qemu-api/src/errno.rs diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 90958e5a306c..c75dccdbb7c1 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -179,6 +179,7 @@ module status ``callbacks`` complete ``cell`` stable ``c_str`` complete +``errno`` complete ``irq`` complete ``memory`` stable ``module`` complete diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 2e9c1078b9b2..bcf1cf780f38 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -2,6 +2,8 @@ _qemu_api_cfg = run_command(rustc_args, '--config-headers', config_host_h, '--features', files('Cargo.toml'), capture: true, check: true).stdout().strip().splitlines() +libc_dep = dependency('libc-0.2-rs') + # _qemu_api_cfg += ['--cfg', 'feature="allocator"'] if rustc.version().version_compare('>=1.77.0') _qemu_api_cfg += ['--cfg', 'has_offset_of'] @@ -22,6 +24,7 @@ _qemu_api_rs = static_library( 'src/cell.rs', 'src/chardev.rs', 'src/c_str.rs', + 'src/errno.rs', 'src/irq.rs', 'src/memory.rs', 'src/module.rs', @@ -39,6 +42,7 @@ _qemu_api_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', rust_args: _qemu_api_cfg, + dependencies: libc_dep, ) rust.test('rust-qemu-api-tests', _qemu_api_rs, diff --git a/rust/qemu-api/src/assertions.rs b/rust/qemu-api/src/assertions.rs index fa1a18de6fe9..104dec39774e 100644 --- a/rust/qemu-api/src/assertions.rs +++ b/rust/qemu-api/src/assertions.rs @@ -92,3 +92,31 @@ macro_rules! assert_field_type { }; }; } + +/// Assert that an expression matches a pattern. This can also be +/// useful to compare enums that do not implement `Eq`. +/// +/// # Examples +/// +/// ``` +/// # use qemu_api::assert_match; +/// // JoinHandle does not implement `Eq`, therefore the result +/// // does not either. +/// let result: Result, u32> = Err(42); +/// assert_match!(result, Err(42)); +/// ``` +#[macro_export] +macro_rules! assert_match { + ($a:expr, $b:pat) => { + assert!( + match $a { + $b => true, + _ => false, + }, + "{} = {:?} does not match {}", + stringify!($a), + $a, + stringify!($b) + ); + }; +} diff --git a/rust/qemu-api/src/errno.rs b/rust/qemu-api/src/errno.rs new file mode 100644 index 000000000000..18d101448b93 --- /dev/null +++ b/rust/qemu-api/src/errno.rs @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Utility functions to convert `errno` to and from +//! [`io::Error`]/[`io::Result`] +//! +//! QEMU C functions often have a "positive success/negative `errno`" calling +//! convention. This module provides functions to portably convert an integer +//! into an [`io::Result`] and back. + +use std::{convert::TryFrom, io, io::ErrorKind}; + +/// An `errno` value that can be converted into an [`io::Error`] +pub struct Errno(pub u16); + +// On Unix, from_raw_os_error takes an errno value and OS errors +// are printed using strerror. On Windows however it takes a +// GetLastError() value; therefore we need to convert errno values +// into io::Error by hand. This is the same mapping that the +// standard library uses to retrieve the kind of OS errors +// (`std::sys::pal::unix::decode_error_kind`). +impl From for ErrorKind { + fn from(value: Errno) -> ErrorKind { + use ErrorKind::*; + let Errno(errno) = value; + match i32::from(errno) { + libc::EPERM | libc::EACCES => PermissionDenied, + libc::ENOENT => NotFound, + libc::EINTR => Interrupted, + x if x == libc::EAGAIN || x == libc::EWOULDBLOCK => WouldBlock, + libc::ENOMEM => OutOfMemory, + libc::EEXIST => AlreadyExists, + libc::EINVAL => InvalidInput, + libc::EPIPE => BrokenPipe, + libc::EADDRINUSE => AddrInUse, + libc::EADDRNOTAVAIL => AddrNotAvailable, + libc::ECONNABORTED => ConnectionAborted, + libc::ECONNREFUSED => ConnectionRefused, + libc::ECONNRESET => ConnectionReset, + libc::ENOTCONN => NotConnected, + libc::ENOTSUP => Unsupported, + libc::ETIMEDOUT => TimedOut, + _ => Other, + } + } +} + +// This is used on Windows for all io::Errors, but also on Unix if the +// io::Error does not have a raw OS error. This is the reversed +// mapping of the above; EIO is returned for unknown ErrorKinds. +impl From for Errno { + fn from(value: io::ErrorKind) -> Errno { + use ErrorKind::*; + let errno = match value { + // can be both EPERM or EACCES :( pick one + PermissionDenied => libc::EPERM, + NotFound => libc::ENOENT, + Interrupted => libc::EINTR, + WouldBlock => libc::EAGAIN, + OutOfMemory => libc::ENOMEM, + AlreadyExists => libc::EEXIST, + InvalidInput => libc::EINVAL, + BrokenPipe => libc::EPIPE, + AddrInUse => libc::EADDRINUSE, + AddrNotAvailable => libc::EADDRNOTAVAIL, + ConnectionAborted => libc::ECONNABORTED, + ConnectionRefused => libc::ECONNREFUSED, + ConnectionReset => libc::ECONNRESET, + NotConnected => libc::ENOTCONN, + Unsupported => libc::ENOTSUP, + TimedOut => libc::ETIMEDOUT, + _ => libc::EIO, + }; + Errno(errno as u16) + } +} + +impl From for io::Error { + #[cfg(unix)] + fn from(value: Errno) -> io::Error { + let Errno(errno) = value; + io::Error::from_raw_os_error(errno.into()) + } + + #[cfg(windows)] + fn from(value: Errno) -> io::Error { + let error_kind: ErrorKind = value.into(); + error_kind.into() + } +} + +impl From for Errno { + fn from(value: io::Error) -> Errno { + if cfg!(unix) { + if let Some(errno) = value.raw_os_error() { + return Errno(u16::try_from(errno).unwrap()); + } + } + value.kind().into() + } +} + +/// Internal traits; used to enable [`into_io_result`] and [`into_neg_errno`] +/// for the "right" set of types. +mod traits { + use super::Errno; + + /// A signed type that can be converted into an + /// [`io::Result`](std::io::Result) + pub trait GetErrno { + /// Unsigned variant of `Self`, used as the type for the `Ok` case. + type Out; + + /// Return `Ok(self)` if positive, `Err(Errno(-self))` if negative + fn into_errno_result(self) -> Result; + } + + /// A type that can be taken out of an [`io::Result`](std::io::Result) and + /// converted into "positive success/negative `errno`" convention. + pub trait MergeErrno { + /// Signed variant of `Self`, used as the return type of + /// [`into_neg_errno`](super::into_neg_errno). + type Out: From + std::ops::Neg; + + /// Return `self`, asserting that it is in range + fn map_ok(self) -> Self::Out; + } + + macro_rules! get_errno { + ($t:ty, $out:ty) => { + impl GetErrno for $t { + type Out = $out; + fn into_errno_result(self) -> Result { + match self { + 0.. => Ok(self as $out), + -65535..=-1 => Err(Errno(-self as u16)), + _ => panic!("{self} is not a negative errno"), + } + } + } + }; + } + + get_errno!(i32, u32); + get_errno!(i64, u64); + get_errno!(isize, usize); + + macro_rules! merge_errno { + ($t:ty, $out:ty) => { + impl MergeErrno for $t { + type Out = $out; + fn map_ok(self) -> Self::Out { + self.try_into().unwrap() + } + } + }; + } + + merge_errno!(u8, i32); + merge_errno!(u16, i32); + merge_errno!(u32, i32); + merge_errno!(u64, i64); + + impl MergeErrno for () { + type Out = i32; + fn map_ok(self) -> i32 { + 0 + } + } +} + +use traits::{GetErrno, MergeErrno}; + +/// Convert an integer value into a [`io::Result`]. +/// +/// Positive values are turned into an `Ok` result; negative values +/// are interpreted as negated `errno` and turned into an `Err`. +/// +/// ``` +/// # use qemu_api::errno::into_io_result; +/// # use std::io::ErrorKind; +/// let ok = into_io_result(1i32).unwrap(); +/// assert_eq!(ok, 1u32); +/// +/// let err = into_io_result(-1i32).unwrap_err(); // -EPERM +/// assert_eq!(err.kind(), ErrorKind::PermissionDenied); +/// ``` +/// +/// # Panics +/// +/// Since the result is an unsigned integer, negative values must +/// be close to 0; values that are too far away are considered +/// likely overflows and will panic: +/// +/// ```should_panic +/// # use qemu_api::errno::into_io_result; +/// # #[allow(dead_code)] +/// let err = into_io_result(-0x1234_5678i32); // panic +/// ``` +pub fn into_io_result(value: T) -> io::Result { + value.into_errno_result().map_err(Into::into) +} + +/// Convert a [`Result`] into an integer value, using negative `errno` +/// values to report errors. +/// +/// ``` +/// # use qemu_api::errno::into_neg_errno; +/// # use std::io::{self, ErrorKind}; +/// let ok: io::Result<()> = Ok(()); +/// assert_eq!(into_neg_errno(ok), 0); +/// +/// let err: io::Result<()> = Err(ErrorKind::InvalidInput.into()); +/// assert_eq!(into_neg_errno(err), -22); // -EINVAL +/// ``` +/// +/// Since this module also provides the ability to convert [`io::Error`] +/// to an `errno` value, [`io::Result`] is the most commonly used type +/// for the argument of this function: +/// +/// # Panics +/// +/// Since the result is a signed integer, integer `Ok` values must remain +/// positive: +/// +/// ```should_panic +/// # use qemu_api::errno::into_neg_errno; +/// # use std::io; +/// let err: io::Result = Ok(0x8899_AABB); +/// into_neg_errno(err) // panic +/// # ; +/// ``` +pub fn into_neg_errno>(value: Result) -> T::Out { + match value { + Ok(x) => x.map_ok(), + Err(err) => -T::Out::from(err.into().0), + } +} + +#[cfg(test)] +mod tests { + use std::io::ErrorKind; + + use super::*; + use crate::assert_match; + + #[test] + pub fn test_from_u8() { + let ok: io::Result<_> = Ok(42u8); + assert_eq!(into_neg_errno(ok), 42); + + let err: io::Result = Err(io::ErrorKind::PermissionDenied.into()); + assert_eq!(into_neg_errno(err), -1); + + if cfg!(unix) { + let os_err: io::Result = Err(io::Error::from_raw_os_error(10)); + assert_eq!(into_neg_errno(os_err), -10); + } + } + + #[test] + pub fn test_from_u16() { + let ok: io::Result<_> = Ok(1234u16); + assert_eq!(into_neg_errno(ok), 1234); + + let err: io::Result = Err(io::ErrorKind::PermissionDenied.into()); + assert_eq!(into_neg_errno(err), -1); + + if cfg!(unix) { + let os_err: io::Result = Err(io::Error::from_raw_os_error(10)); + assert_eq!(into_neg_errno(os_err), -10); + } + } + + #[test] + pub fn test_i32() { + assert_match!(into_io_result(1234i32), Ok(1234)); + + let err = into_io_result(-1i32).unwrap_err(); + #[cfg(unix)] + assert_match!(err.raw_os_error(), Some(1)); + assert_match!(err.kind(), ErrorKind::PermissionDenied); + } + + #[test] + pub fn test_from_u32() { + let ok: io::Result<_> = Ok(1234u32); + assert_eq!(into_neg_errno(ok), 1234); + + let err: io::Result = Err(io::ErrorKind::PermissionDenied.into()); + assert_eq!(into_neg_errno(err), -1); + + if cfg!(unix) { + let os_err: io::Result = Err(io::Error::from_raw_os_error(10)); + assert_eq!(into_neg_errno(os_err), -10); + } + } + + #[test] + pub fn test_i64() { + assert_match!(into_io_result(1234i64), Ok(1234)); + + let err = into_io_result(-22i64).unwrap_err(); + #[cfg(unix)] + assert_match!(err.raw_os_error(), Some(22)); + assert_match!(err.kind(), ErrorKind::InvalidInput); + } + + #[test] + pub fn test_from_u64() { + let ok: io::Result<_> = Ok(1234u64); + assert_eq!(into_neg_errno(ok), 1234); + + let err: io::Result = Err(io::ErrorKind::InvalidInput.into()); + assert_eq!(into_neg_errno(err), -22); + + if cfg!(unix) { + let os_err: io::Result = Err(io::Error::from_raw_os_error(6)); + assert_eq!(into_neg_errno(os_err), -6); + } + } + + #[test] + pub fn test_isize() { + assert_match!(into_io_result(1234isize), Ok(1234)); + + let err = into_io_result(-4isize).unwrap_err(); + #[cfg(unix)] + assert_match!(err.raw_os_error(), Some(4)); + assert_match!(err.kind(), ErrorKind::Interrupted); + } + + #[test] + pub fn test_from_unit() { + let ok: io::Result<_> = Ok(()); + assert_eq!(into_neg_errno(ok), 0); + + let err: io::Result<()> = Err(io::ErrorKind::OutOfMemory.into()); + assert_eq!(into_neg_errno(err), -12); + + if cfg!(unix) { + let os_err: io::Result<()> = Err(io::Error::from_raw_os_error(2)); + assert_eq!(into_neg_errno(os_err), -2); + } + } +} diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index ed1a8f9a2b43..05f38b51d304 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -19,6 +19,7 @@ pub mod c_str; pub mod callbacks; pub mod cell; pub mod chardev; +pub mod errno; pub mod irq; pub mod memory; pub mod module; diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index fbf0ee23e0b9..634acf37a850 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -9,6 +9,8 @@ pub use crate::bitops::IntegerExt; pub use crate::cell::BqlCell; pub use crate::cell::BqlRefCell; +pub use crate::errno; + pub use crate::qdev::DeviceMethods; pub use crate::qom::InterfaceType; From 4cb7040d851cdd7b2622f83fd7d95a922225386b Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:10:33 +0100 Subject: [PATCH 0254/1179] rust: tests: do not import bindings::* Similar to the devices, spell the exact set of C functions that are called directly. Signed-off-by: Paolo Bonzini --- rust/qemu-api/tests/tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 92dbfb8a0c86..03569e4a44c4 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -8,13 +8,14 @@ use std::{ }; use qemu_api::{ - bindings::*, + bindings::{module_call_init, module_init_type, object_new, object_unref, qdev_prop_bool}, c_str, cell::{self, BqlCell}, declare_properties, define_property, prelude::*, qdev::{DeviceClass, DeviceImpl, DeviceState, Property, ResettablePhasesImpl}, qom::{ClassInitImpl, ObjectImpl, ParentField}, + sysbus::SysBusDevice, vmstate::VMStateDescription, zeroable::Zeroable, }; From c48700e86d91004424e3a6496f194decb036dccb Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Tue, 18 Feb 2025 16:08:35 +0800 Subject: [PATCH 0255/1179] rust: prefer importing std::ptr over core::ptr The std::ptr is same as core::ptr, but std has already been used in many cases and there's no need to choose non-std library. So, use std::ptr directly to make the used ptr library as consistent as possible. Signed-off-by: Zhao Liu Link: https://lore.kernel.org/r/20250218080835.3341082-1-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 2 +- rust/hw/char/pl011/src/device_class.rs | 6 ++++-- rust/qemu-api/src/irq.rs | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index fe73771021e7..59a689fdcd77 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -2,10 +2,10 @@ // Author(s): Manos Pitsidianakis // SPDX-License-Identifier: GPL-2.0-or-later -use core::ptr::{addr_of, addr_of_mut, NonNull}; use std::{ ffi::CStr, os::raw::{c_int, c_void}, + ptr::{addr_of, addr_of_mut, NonNull}, }; use qemu_api::{ diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs index dbef93f6cb3e..0b2076ddaa0f 100644 --- a/rust/hw/char/pl011/src/device_class.rs +++ b/rust/hw/char/pl011/src/device_class.rs @@ -2,8 +2,10 @@ // Author(s): Manos Pitsidianakis // SPDX-License-Identifier: GPL-2.0-or-later -use core::ptr::NonNull; -use std::os::raw::{c_int, c_void}; +use std::{ + os::raw::{c_int, c_void}, + ptr::NonNull, +}; use qemu_api::{ bindings::*, c_str, prelude::*, vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index d1c9dc96eff3..34c19263c233 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -4,8 +4,7 @@ //! Bindings for interrupt sources -use core::ptr; -use std::{ffi::CStr, marker::PhantomData, os::raw::c_int}; +use std::{ffi::CStr, marker::PhantomData, os::raw::c_int, ptr}; use crate::{ bindings::{self, qemu_set_irq}, From 7a2e40866cf45a016858c73b9a5699b72be8ce38 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 13 Feb 2025 17:18:00 +0100 Subject: [PATCH 0256/1179] docs: rust: fix typos Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index c75dccdbb7c1..d68701c9c879 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -294,7 +294,7 @@ to a Rust mutable reference, and use a shared reference instead. Rust code will then have to use QEMU's ``BqlRefCell`` and ``BqlCell`` type, which enforce that locking rules for the "Big QEMU Lock" are respected. These cell types are also known to the ``vmstate`` crate, which is able to "look inside" -them when building an in-memory representation of a ``struct``s layout. +them when building an in-memory representation of a ``struct``'s layout. Note that the same is not true of a ``RefCell`` or ``Mutex``. In the future, similar cell types might also be provided for ``AioContext``-based @@ -350,7 +350,7 @@ Writing procedural macros ''''''''''''''''''''''''' By conventions, procedural macros are split in two functions, one -returning ``Result` with the body of +returning ``Result`` with the body of the procedural macro, and the second returning ``proc_macro::TokenStream`` which is the actual procedural macro. The former's name is the same as the latter with the ``_or_error`` suffix. The code for the latter is more From 29b9a66f9186f028ec46b58c9914d2da68c25c2c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 13:25:05 +0100 Subject: [PATCH 0257/1179] docs: rust: update description of crates Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index d68701c9c879..5d8aa3a45bca 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -139,16 +139,22 @@ anymore. Writing Rust code in QEMU ------------------------- -Right now QEMU includes three crates: +QEMU includes four crates: * ``qemu_api`` for bindings to C code and useful functionality * ``qemu_api_macros`` defines several procedural macros that are useful when writing C code -* ``pl011`` (under ``rust/hw/char/pl011``) is the sample device that is being - used to further develop ``qemu_api`` and ``qemu_api_macros``. It is a functional - replacement for the ``hw/char/pl011.c`` file. +* ``pl011`` (under ``rust/hw/char/pl011``) and ``hpet`` (under ``rust/hw/timer/hpet``) + are sample devices that demonstrate ``qemu_api`` and ``qemu_api_macros``, and are + used to further develop them. These two crates are functional\ [#issues]_ replacements + for the ``hw/char/pl011.c`` and ``hw/timer/hpet.c`` files. + +.. [#issues] The ``pl011`` crate is synchronized with ``hw/char/pl011.c`` + as of commit 02b1f7f61928. The ``hpet`` crate is synchronized as of + commit f32352ff9e. Both are lacking tracing functionality; ``hpet`` + is also lacking support for migration. This section explains how to work with them. From 5384d92e22577306408cefff887bc5a4b154f470 Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Mon, 17 Feb 2025 11:48:49 +0100 Subject: [PATCH 0258/1179] stub: Remove monitor-fd.c Both monitor-fd.c and monitor-internal.c contain a stub for monitor_get_fd(), which causes a duplicate symbol linker error when linking rust-qemu-api-integration. Use monitor-internal.c instead of monitor-fd.c and remove the latter. Reported-by: Zhao Liu Suggested-by: Zhao Liu Fixes: fccb744f41c6 ("gdbstub: Try unlinking the unix socket before binding") Signed-off-by: Ilya Leoshkevich Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250217104900.230122-1-iii@linux.ibm.com Signed-off-by: Paolo Bonzini --- stubs/meson.build | 2 +- stubs/monitor-fd.c | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 stubs/monitor-fd.c diff --git a/stubs/meson.build b/stubs/meson.build index b0fee37e0593..63392f5e7852 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -62,7 +62,7 @@ if have_user stub_ss.add(files('qdev.c')) endif - stub_ss.add(files('monitor-fd.c')) + stub_ss.add(files('monitor-internal.c')) endif if have_system diff --git a/stubs/monitor-fd.c b/stubs/monitor-fd.c deleted file mode 100644 index 9bb67498850b..000000000000 --- a/stubs/monitor-fd.c +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "qemu/osdep.h" -#include "monitor/monitor.h" - -int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp) -{ - abort(); -} From 6debfb2cb1795427d2dc6a741c7430a233c76695 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 17 Feb 2025 13:08:12 +0100 Subject: [PATCH 0259/1179] physmem: replace assertion with error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is possible to start QEMU with a confidential-guest-support object even in TCG mode. While there is already a check in qemu_machine_creation_done: if (machine->cgs && !machine->cgs->ready) { error_setg(errp, "accelerator does not support confidential guest %s", object_get_typename(OBJECT(machine->cgs))); exit(1); } the creation of RAMBlocks happens earlier, in qemu_init_board(), if the command line does not override the default memory backend with -M memdev. Then the RAMBlock will try to use guest_memfd (because machine_require_guest_memfd correctly returns true; at least correctly according to the current implementation) and trigger the assertion failure for kvm_enabled(). This happend with a command line as simple as the following: qemu-system-x86_64 -m 512 -nographic -object sev-snp-guest,reduced-phys-bits=48,id=sev0 \ -M q35,kernel-irqchip=split,confidential-guest-support=sev0 qemu-system-x86_64: ../system/physmem.c:1871: ram_block_add: Assertion `kvm_enabled()' failed. Cc: Xiaoyao Li Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini Reviewed-by: Daniel P. Berrangé Reviewed-by: David Hildenbrand Reviewed-by: Pankaj Gupta Reviewed-by: Xiaoyao Li Link: https://lore.kernel.org/r/20250217120812.396522-1-pbonzini@redhat.com Signed-off-by: Paolo Bonzini --- system/physmem.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/physmem.c b/system/physmem.c index 67bdf631e60c..eff8b55c2dda 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -1882,7 +1882,11 @@ static void ram_block_add(RAMBlock *new_block, Error **errp) if (new_block->flags & RAM_GUEST_MEMFD) { int ret; - assert(kvm_enabled()); + if (!kvm_enabled()) { + error_setg(errp, "cannot set up private guest memory for %s: KVM required", + object_get_typename(OBJECT(current_machine->cgs))); + goto out_free; + } assert(new_block->guest_memfd < 0); ret = ram_block_discard_require(true); From ae3a420fea8bfc545a4ca4b899d2fe6a3031aefa Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 19 Feb 2025 11:18:28 +0100 Subject: [PATCH 0260/1179] pvg: do not enable it on cross-architecture targets PVG is not cross-architecture; the PVG guest drivers with x86-64 macOS do not give useful results with the aarch64 macOS host PVG framework, and vice versa. To express this repurpose CONFIG_MAC_PVG, making it true only if the target has the same architecture as the host. Furthermore, remove apple-gfx.m unless one of the devices is actually present. Signed-off-by: Paolo Bonzini --- Kconfig.host | 3 +++ hw/display/Kconfig | 4 ---- hw/display/meson.build | 9 +++------ meson.build | 6 ++++++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Kconfig.host b/Kconfig.host index 842cbe0d6c59..933425c74b47 100644 --- a/Kconfig.host +++ b/Kconfig.host @@ -61,3 +61,6 @@ config HV_BALLOON_POSSIBLE config HAVE_RUST bool + +config MAC_PVG + bool diff --git a/hw/display/Kconfig b/hw/display/Kconfig index 2b53dfd7d26c..1e95ab28ef40 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -141,10 +141,6 @@ config XLNX_DISPLAYPORT config DM163 bool -config MAC_PVG - bool - default y - config MAC_PVG_MMIO bool depends on MAC_PVG && AARCH64 diff --git a/hw/display/meson.build b/hw/display/meson.build index 94f4f05d36f4..b9bdf2191037 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -61,12 +61,9 @@ system_ss.add(when: 'CONFIG_ARTIST', if_true: files('artist.c')) system_ss.add(when: 'CONFIG_ATI_VGA', if_true: [files('ati.c', 'ati_2d.c', 'ati_dbg.c'), pixman]) -if host_os == 'darwin' - system_ss.add(when: 'CONFIG_MAC_PVG', if_true: [files('apple-gfx.m'), pvg, metal]) - system_ss.add(when: 'CONFIG_MAC_PVG_PCI', if_true: [files('apple-gfx-pci.m'), pvg, metal]) - if cpu == 'aarch64' - system_ss.add(when: 'CONFIG_MAC_PVG_MMIO', if_true: [files('apple-gfx-mmio.m'), pvg, metal]) - endif +if pvg.found() + system_ss.add(when: 'CONFIG_MAC_PVG_PCI', if_true: [files('apple-gfx.m', 'apple-gfx-pci.m'), pvg, metal]) + system_ss.add(when: 'CONFIG_MAC_PVG_MMIO', if_true: [files('apple-gfx.m', 'apple-gfx-mmio.m'), pvg, metal]) endif if config_all_devices.has_key('CONFIG_VIRTIO_GPU') diff --git a/meson.build b/meson.build index 0ee79c664d39..ad2c6b61930a 100644 --- a/meson.build +++ b/meson.build @@ -3367,6 +3367,12 @@ foreach target : target_dirs target_kconfig += 'CONFIG_' + config_target['TARGET_ARCH'].to_upper() + '=y' target_kconfig += 'CONFIG_TARGET_BIG_ENDIAN=' + config_target['TARGET_BIG_ENDIAN'] + # PVG is not cross-architecture. Use accelerator_targets as a proxy to + # figure out which target can support PVG on this host + if pvg.found() and target in accelerator_targets.get('CONFIG_HVF', []) + target_kconfig += 'CONFIG_MAC_PVG=y' + endif + config_input = meson.get_external_property(target, 'default') config_devices_mak = target + '-config-devices.mak' config_devices_mak = configure_file( From d50ea7f0e6fd2b0631abb61d213a396e3df32d7e Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 20 Feb 2025 14:20:27 +0100 Subject: [PATCH 0261/1179] pvg: add option to configure it out ... and also to require it (--enable-pvg). While at it, unify the dependency() call for pvg and metal, which simplifies the logic a bit. Note that all other Apple frameworks are either required or always-present, therefore do not add them to the summary in the same way as PVG. Signed-off-by: Paolo Bonzini --- hw/display/meson.build | 6 ++---- meson.build | 8 +++++--- meson_options.txt | 2 ++ scripts/meson-buildoptions.sh | 3 +++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/hw/display/meson.build b/hw/display/meson.build index b9bdf2191037..90e6c041bdbc 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -61,10 +61,8 @@ system_ss.add(when: 'CONFIG_ARTIST', if_true: files('artist.c')) system_ss.add(when: 'CONFIG_ATI_VGA', if_true: [files('ati.c', 'ati_2d.c', 'ati_dbg.c'), pixman]) -if pvg.found() - system_ss.add(when: 'CONFIG_MAC_PVG_PCI', if_true: [files('apple-gfx.m', 'apple-gfx-pci.m'), pvg, metal]) - system_ss.add(when: 'CONFIG_MAC_PVG_MMIO', if_true: [files('apple-gfx.m', 'apple-gfx-mmio.m'), pvg, metal]) -endif +system_ss.add(when: [pvg, 'CONFIG_MAC_PVG_PCI'], if_true: [files('apple-gfx.m', 'apple-gfx-pci.m')]) +system_ss.add(when: [pvg, 'CONFIG_MAC_PVG_MMIO'], if_true: [files('apple-gfx.m', 'apple-gfx-mmio.m')]) if config_all_devices.has_key('CONFIG_VIRTIO_GPU') virtio_gpu_ss = ss.source_set() diff --git a/meson.build b/meson.build index ad2c6b61930a..0a2c61d2bfa0 100644 --- a/meson.build +++ b/meson.build @@ -821,7 +821,6 @@ version_res = [] coref = [] iokit = [] pvg = not_found -metal = [] emulator_link_args = [] midl = not_found widl = not_found @@ -843,8 +842,8 @@ elif host_os == 'darwin' coref = dependency('appleframeworks', modules: 'CoreFoundation') iokit = dependency('appleframeworks', modules: 'IOKit', required: false) host_dsosuf = '.dylib' - pvg = dependency('appleframeworks', modules: 'ParavirtualizedGraphics') - metal = dependency('appleframeworks', modules: 'Metal') + pvg = dependency('appleframeworks', modules: ['ParavirtualizedGraphics', 'Metal'], + required: get_option('pvg')) elif host_os == 'sunos' socket = [cc.find_library('socket'), cc.find_library('nsl'), @@ -4846,6 +4845,9 @@ summary_info += {'libdw': libdw} if host_os == 'freebsd' summary_info += {'libinotify-kqueue': inotify} endif +if host_os == 'darwin' + summary_info += {'ParavirtualizedGraphics support': pvg} +endif summary(summary_info, bool_yn: true, section: 'Dependencies') if host_arch == 'unknown' diff --git a/meson_options.txt b/meson_options.txt index 5eeaf3eee5cb..59d973bca00f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -198,6 +198,8 @@ option('lzfse', type : 'feature', value : 'auto', description: 'lzfse support for DMG images') option('lzo', type : 'feature', value : 'auto', description: 'lzo compression support') +option('pvg', type: 'feature', value: 'auto', + description: 'macOS paravirtualized graphics support') option('rbd', type : 'feature', value : 'auto', description: 'Ceph block device driver') option('opengl', type : 'feature', value : 'auto', diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index a8066aab0379..3e8e00852b2c 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -168,6 +168,7 @@ meson_options_help() { printf "%s\n" ' pixman pixman support' printf "%s\n" ' plugins TCG plugins via shared library loading' printf "%s\n" ' png PNG support with libpng' + printf "%s\n" ' pvg macOS paravirtualized graphics support' printf "%s\n" ' qatzip QATzip compression support' printf "%s\n" ' qcow1 qcow1 image format support' printf "%s\n" ' qed qed image format support' @@ -436,6 +437,8 @@ _meson_option_parse() { --enable-png) printf "%s" -Dpng=enabled ;; --disable-png) printf "%s" -Dpng=disabled ;; --prefix=*) quote_sh "-Dprefix=$2" ;; + --enable-pvg) printf "%s" -Dpvg=enabled ;; + --disable-pvg) printf "%s" -Dpvg=disabled ;; --enable-qatzip) printf "%s" -Dqatzip=enabled ;; --disable-qatzip) printf "%s" -Dqatzip=disabled ;; --enable-qcow1) printf "%s" -Dqcow1=enabled ;; From 2540917285872ab08f3ce66990983edd19ef4ec0 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 21 Feb 2025 00:36:09 -0800 Subject: [PATCH 0262/1179] target/i386/hvf: fix a typo in a type name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prefix x68 is wrong. Change it to x86. Signed-off-by: Wei Liu Reviewed-by: Philippe Mathieu-Daudé Link: https://lore.kernel.org/r/1740126987-8483-2-git-send-email-liuwe@linux.microsoft.com Signed-off-by: Paolo Bonzini --- target/i386/hvf/hvf.c | 2 +- target/i386/hvf/x86.c | 4 ++-- target/i386/hvf/x86.h | 8 ++++---- target/i386/hvf/x86_descr.c | 8 ++++---- target/i386/hvf/x86_descr.h | 6 +++--- target/i386/hvf/x86_task.c | 22 +++++++++++----------- target/i386/hvf/x86_task.h | 2 +- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index ca08f0753f0d..353549fa7796 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -674,7 +674,7 @@ int hvf_vcpu_exec(CPUState *cpu) } case EXIT_REASON_TASK_SWITCH: { uint64_t vinfo = rvmcs(cpu->accel->fd, VMCS_IDT_VECTORING_INFO); - x68_segment_selector sel = {.sel = exit_qual & 0xffff}; + x86_segment_selector sel = {.sel = exit_qual & 0xffff}; vmx_handle_task_switch(cpu, sel, (exit_qual >> 30) & 0x3, vinfo & VMCS_INTR_VALID, vinfo & VECTORING_INFO_VECTOR_MASK, vinfo & VMCS_INTR_T_MASK); diff --git a/target/i386/hvf/x86.c b/target/i386/hvf/x86.c index 80e36136d045..a0ede138865e 100644 --- a/target/i386/hvf/x86.c +++ b/target/i386/hvf/x86.c @@ -48,7 +48,7 @@ bool x86_read_segment_descriptor(CPUState *cpu, struct x86_segment_descriptor *desc, - x68_segment_selector sel) + x86_segment_selector sel) { target_ulong base; uint32_t limit; @@ -78,7 +78,7 @@ bool x86_read_segment_descriptor(CPUState *cpu, bool x86_write_segment_descriptor(CPUState *cpu, struct x86_segment_descriptor *desc, - x68_segment_selector sel) + x86_segment_selector sel) { target_ulong base; uint32_t limit; diff --git a/target/i386/hvf/x86.h b/target/i386/hvf/x86.h index 3570f29aa9d2..063cd0b83ec9 100644 --- a/target/i386/hvf/x86.h +++ b/target/i386/hvf/x86.h @@ -183,7 +183,7 @@ static inline uint32_t x86_call_gate_offset(x86_call_gate *gate) #define GDT_SEL 0 #define LDT_SEL 1 -typedef struct x68_segment_selector { +typedef struct x86_segment_selector { union { uint16_t sel; struct { @@ -192,7 +192,7 @@ typedef struct x68_segment_selector { uint16_t index:13; }; }; -} __attribute__ ((__packed__)) x68_segment_selector; +} __attribute__ ((__packed__)) x86_segment_selector; /* useful register access macros */ #define x86_reg(cpu, reg) ((x86_register *) &cpu->regs[reg]) @@ -250,10 +250,10 @@ typedef struct x68_segment_selector { /* deal with GDT/LDT descriptors in memory */ bool x86_read_segment_descriptor(CPUState *cpu, struct x86_segment_descriptor *desc, - x68_segment_selector sel); + x86_segment_selector sel); bool x86_write_segment_descriptor(CPUState *cpu, struct x86_segment_descriptor *desc, - x68_segment_selector sel); + x86_segment_selector sel); bool x86_read_call_gate(CPUState *cpu, struct x86_call_gate *idt_desc, int gate); diff --git a/target/i386/hvf/x86_descr.c b/target/i386/hvf/x86_descr.c index f33836d6cbaa..7b599c903777 100644 --- a/target/i386/hvf/x86_descr.c +++ b/target/i386/hvf/x86_descr.c @@ -60,14 +60,14 @@ uint64_t vmx_read_segment_base(CPUState *cpu, X86Seg seg) return rvmcs(cpu->accel->fd, vmx_segment_fields[seg].base); } -x68_segment_selector vmx_read_segment_selector(CPUState *cpu, X86Seg seg) +x86_segment_selector vmx_read_segment_selector(CPUState *cpu, X86Seg seg) { - x68_segment_selector sel; + x86_segment_selector sel; sel.sel = rvmcs(cpu->accel->fd, vmx_segment_fields[seg].selector); return sel; } -void vmx_write_segment_selector(CPUState *cpu, x68_segment_selector selector, X86Seg seg) +void vmx_write_segment_selector(CPUState *cpu, x86_segment_selector selector, X86Seg seg) { wvmcs(cpu->accel->fd, vmx_segment_fields[seg].selector, selector.sel); } @@ -90,7 +90,7 @@ void vmx_write_segment_descriptor(CPUState *cpu, struct vmx_segment *desc, X86Se wvmcs(cpu->accel->fd, sf->ar_bytes, desc->ar); } -void x86_segment_descriptor_to_vmx(CPUState *cpu, x68_segment_selector selector, +void x86_segment_descriptor_to_vmx(CPUState *cpu, x86_segment_selector selector, struct x86_segment_descriptor *desc, struct vmx_segment *vmx_desc) { diff --git a/target/i386/hvf/x86_descr.h b/target/i386/hvf/x86_descr.h index 9f06014b56a5..ce5de9834973 100644 --- a/target/i386/hvf/x86_descr.h +++ b/target/i386/hvf/x86_descr.h @@ -34,10 +34,10 @@ void vmx_read_segment_descriptor(CPUState *cpu, void vmx_write_segment_descriptor(CPUState *cpu, struct vmx_segment *desc, enum X86Seg seg); -x68_segment_selector vmx_read_segment_selector(CPUState *cpu, +x86_segment_selector vmx_read_segment_selector(CPUState *cpu, enum X86Seg seg); void vmx_write_segment_selector(CPUState *cpu, - x68_segment_selector selector, + x86_segment_selector selector, enum X86Seg seg); uint64_t vmx_read_segment_base(CPUState *cpu, enum X86Seg seg); @@ -45,7 +45,7 @@ void vmx_write_segment_base(CPUState *cpu, enum X86Seg seg, uint64_t base); void x86_segment_descriptor_to_vmx(CPUState *cpu, - x68_segment_selector selector, + x86_segment_selector selector, struct x86_segment_descriptor *desc, struct vmx_segment *vmx_desc); diff --git a/target/i386/hvf/x86_task.c b/target/i386/hvf/x86_task.c index bcd844cff606..287fe11cf704 100644 --- a/target/i386/hvf/x86_task.c +++ b/target/i386/hvf/x86_task.c @@ -76,16 +76,16 @@ static void load_state_from_tss32(CPUState *cpu, struct x86_tss_segment32 *tss) RSI(env) = tss->esi; RDI(env) = tss->edi; - vmx_write_segment_selector(cpu, (x68_segment_selector){{tss->ldt}}, R_LDTR); - vmx_write_segment_selector(cpu, (x68_segment_selector){{tss->es}}, R_ES); - vmx_write_segment_selector(cpu, (x68_segment_selector){{tss->cs}}, R_CS); - vmx_write_segment_selector(cpu, (x68_segment_selector){{tss->ss}}, R_SS); - vmx_write_segment_selector(cpu, (x68_segment_selector){{tss->ds}}, R_DS); - vmx_write_segment_selector(cpu, (x68_segment_selector){{tss->fs}}, R_FS); - vmx_write_segment_selector(cpu, (x68_segment_selector){{tss->gs}}, R_GS); + vmx_write_segment_selector(cpu, (x86_segment_selector){{tss->ldt}}, R_LDTR); + vmx_write_segment_selector(cpu, (x86_segment_selector){{tss->es}}, R_ES); + vmx_write_segment_selector(cpu, (x86_segment_selector){{tss->cs}}, R_CS); + vmx_write_segment_selector(cpu, (x86_segment_selector){{tss->ss}}, R_SS); + vmx_write_segment_selector(cpu, (x86_segment_selector){{tss->ds}}, R_DS); + vmx_write_segment_selector(cpu, (x86_segment_selector){{tss->fs}}, R_FS); + vmx_write_segment_selector(cpu, (x86_segment_selector){{tss->gs}}, R_GS); } -static int task_switch_32(CPUState *cpu, x68_segment_selector tss_sel, x68_segment_selector old_tss_sel, +static int task_switch_32(CPUState *cpu, x86_segment_selector tss_sel, x86_segment_selector old_tss_sel, uint64_t old_tss_base, struct x86_segment_descriptor *new_desc) { struct x86_tss_segment32 tss_seg; @@ -108,7 +108,7 @@ static int task_switch_32(CPUState *cpu, x68_segment_selector tss_sel, x68_segme return 0; } -void vmx_handle_task_switch(CPUState *cpu, x68_segment_selector tss_sel, int reason, bool gate_valid, uint8_t gate, uint64_t gate_type) +void vmx_handle_task_switch(CPUState *cpu, x86_segment_selector tss_sel, int reason, bool gate_valid, uint8_t gate, uint64_t gate_type) { uint64_t rip = rreg(cpu->accel->fd, HV_X86_RIP); if (!gate_valid || (gate_type != VMCS_INTR_T_HWEXCEPTION && @@ -122,7 +122,7 @@ void vmx_handle_task_switch(CPUState *cpu, x68_segment_selector tss_sel, int rea load_regs(cpu); struct x86_segment_descriptor curr_tss_desc, next_tss_desc; - x68_segment_selector old_tss_sel = vmx_read_segment_selector(cpu, R_TR); + x86_segment_selector old_tss_sel = vmx_read_segment_selector(cpu, R_TR); uint64_t old_tss_base = vmx_read_segment_base(cpu, R_TR); uint32_t desc_limit; struct x86_call_gate task_gate_desc; @@ -140,7 +140,7 @@ void vmx_handle_task_switch(CPUState *cpu, x68_segment_selector tss_sel, int rea x86_read_call_gate(cpu, &task_gate_desc, gate); dpl = task_gate_desc.dpl; - x68_segment_selector cs = vmx_read_segment_selector(cpu, R_CS); + x86_segment_selector cs = vmx_read_segment_selector(cpu, R_CS); if (tss_sel.rpl > dpl || cs.rpl > dpl) ;//DPRINTF("emulate_gp"); } diff --git a/target/i386/hvf/x86_task.h b/target/i386/hvf/x86_task.h index 4eaa61a7dee6..b9afac6a47bb 100644 --- a/target/i386/hvf/x86_task.h +++ b/target/i386/hvf/x86_task.h @@ -15,6 +15,6 @@ #ifndef HVF_X86_TASK_H #define HVF_X86_TASK_H -void vmx_handle_task_switch(CPUState *cpu, x68_segment_selector tss_sel, +void vmx_handle_task_switch(CPUState *cpu, x86_segment_selector tss_sel, int reason, bool gate_valid, uint8_t gate, uint64_t gate_type); #endif From bc4fa8c3c9b5e2ad945617b667362b71b13495ad Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 21 Feb 2025 00:36:10 -0800 Subject: [PATCH 0263/1179] target/i386/hvf: fix the declaration of hvf_handle_io There is a conflicting declaration for hvf_handle_io in x86_emu.c. The type of the first argument is wrong. There has never been a problem because the first argument is not used in hvf_handle_io. That being said, the code shouldn't contain such an error. Use the proper declaration from hvf-i386.h. Take the chance to change the first argument's type to be CPUState. Signed-off-by: Wei Liu Link: https://lore.kernel.org/r/1740126987-8483-3-git-send-email-liuwe@linux.microsoft.com Signed-off-by: Paolo Bonzini --- target/i386/hvf/hvf-i386.h | 2 +- target/i386/hvf/hvf.c | 6 +++--- target/i386/hvf/x86_emu.c | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/target/i386/hvf/hvf-i386.h b/target/i386/hvf/hvf-i386.h index e99c02cd4bfe..046b681d134e 100644 --- a/target/i386/hvf/hvf-i386.h +++ b/target/i386/hvf/hvf-i386.h @@ -18,7 +18,7 @@ uint32_t hvf_get_supported_cpuid(uint32_t func, uint32_t idx, int reg); -void hvf_handle_io(CPUArchState *, uint16_t, void *, int, int, int); +void hvf_handle_io(CPUState *, uint16_t, void *, int, int, int); /* Host specific functions */ int hvf_inject_interrupt(CPUArchState *env, int vector); diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index 353549fa7796..1ecb6993ba19 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -103,7 +103,7 @@ static void update_apic_tpr(CPUState *cpu) #define VECTORING_INFO_VECTOR_MASK 0xff -void hvf_handle_io(CPUArchState *env, uint16_t port, void *buffer, +void hvf_handle_io(CPUState *env, uint16_t port, void *buffer, int direction, int size, int count) { int i; @@ -536,7 +536,7 @@ int hvf_vcpu_exec(CPUState *cpu) if (!string && in) { uint64_t val = 0; load_regs(cpu); - hvf_handle_io(env, port, &val, 0, size, 1); + hvf_handle_io(env_cpu(env), port, &val, 0, size, 1); if (size == 1) { AL(env) = val; } else if (size == 2) { @@ -551,7 +551,7 @@ int hvf_vcpu_exec(CPUState *cpu) break; } else if (!string && !in) { RAX(env) = rreg(cpu->accel->fd, HV_X86_RAX); - hvf_handle_io(env, port, &RAX(env), 1, size, 1); + hvf_handle_io(env_cpu(env), port, &RAX(env), 1, size, 1); macvm_set_rip(cpu, rip + ins_len); break; } diff --git a/target/i386/hvf/x86_emu.c b/target/i386/hvf/x86_emu.c index 69c61c9c0737..2c7da10c1d2e 100644 --- a/target/i386/hvf/x86_emu.c +++ b/target/i386/hvf/x86_emu.c @@ -44,9 +44,7 @@ #include "x86_flags.h" #include "vmcs.h" #include "vmx.h" - -void hvf_handle_io(CPUState *cs, uint16_t port, void *data, - int direction, int size, uint32_t count); +#include "hvf-i386.h" #define EXEC_2OP_FLAGS_CMD(env, decode, cmd, FLAGS_FUNC, save_res) \ { \ From d54d3346b86d7c08b7fb2dac2d9a889854c7d3ba Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 21 Feb 2025 00:36:11 -0800 Subject: [PATCH 0264/1179] target/i386/hvf: use x86_segment in x86_decode.c Make the code to rely on the segment definition for checking cs.db. This allows removing HVF specific VMX related definition from the decoder. Introduce a function for retrieving the CS descriptor. No functional change intended. Signed-off-by: Wei Liu Link: https://lore.kernel.org/r/1740126987-8483-4-git-send-email-liuwe@linux.microsoft.com Signed-off-by: Paolo Bonzini --- target/i386/hvf/x86_decode.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/target/i386/hvf/x86_decode.c b/target/i386/hvf/x86_decode.c index a4a28f113fdc..d6d5894e54b4 100644 --- a/target/i386/hvf/x86_decode.c +++ b/target/i386/hvf/x86_decode.c @@ -1893,6 +1893,16 @@ static void decode_prefix(CPUX86State *env, struct x86_decode *decode) } } +static struct x86_segment_descriptor get_cs_descriptor(CPUState *s) +{ + struct vmx_segment vmx_cs; + x86_segment_descriptor cs; + vmx_read_segment_descriptor(s, &vmx_cs, R_CS); + vmx_segment_to_x86_descriptor(s, &vmx_cs, &cs); + + return cs; +} + void set_addressing_size(CPUX86State *env, struct x86_decode *decode) { decode->addressing_size = -1; @@ -1904,10 +1914,9 @@ void set_addressing_size(CPUX86State *env, struct x86_decode *decode) } } else if (!x86_is_long_mode(env_cpu(env))) { /* protected */ - struct vmx_segment cs; - vmx_read_segment_descriptor(env_cpu(env), &cs, R_CS); + x86_segment_descriptor cs = get_cs_descriptor(env_cpu(env)); /* check db */ - if ((cs.ar >> 14) & 1) { + if (cs.db) { if (decode->addr_size_override) { decode->addressing_size = 2; } else { @@ -1941,10 +1950,9 @@ void set_operand_size(CPUX86State *env, struct x86_decode *decode) } } else if (!x86_is_long_mode(env_cpu(env))) { /* protected */ - struct vmx_segment cs; - vmx_read_segment_descriptor(env_cpu(env), &cs, R_CS); + x86_segment_descriptor cs = get_cs_descriptor(env_cpu(env)); /* check db */ - if ((cs.ar >> 14) & 1) { + if (cs.db) { if (decode->op_size_override) { decode->operand_size = 2; } else{ From dbccd48df0954069c12e13883ee8b2929a57ac6a Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 21 Feb 2025 00:36:14 -0800 Subject: [PATCH 0265/1179] target/i386/hvf: move and rename {load, store}_regs They contain HVF specific code. Move them to a better location and add "hvf_" prefix. Fix up all the call sites. No functional change. Signed-off-by: Wei Liu Link: https://lore.kernel.org/r/1740126987-8483-7-git-send-email-liuwe@linux.microsoft.com Signed-off-by: Paolo Bonzini --- target/i386/hvf/hvf.c | 71 +++++++++++++++++++++++++++++++------- target/i386/hvf/x86_emu.c | 46 ------------------------ target/i386/hvf/x86_emu.h | 3 -- target/i386/hvf/x86_task.c | 4 +-- target/i386/hvf/x86hvf.h | 3 ++ 5 files changed, 64 insertions(+), 63 deletions(-) diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index 1ecb6993ba19..3ab3c0043dbf 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -61,6 +61,7 @@ #include "vmx.h" #include "x86.h" #include "x86_descr.h" +#include "x86_flags.h" #include "x86_mmu.h" #include "x86_decode.h" #include "x86_emu.h" @@ -434,6 +435,52 @@ static void hvf_cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } } +void hvf_load_regs(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + CPUX86State *env = &cpu->env; + + int i = 0; + RRX(env, R_EAX) = rreg(cs->accel->fd, HV_X86_RAX); + RRX(env, R_EBX) = rreg(cs->accel->fd, HV_X86_RBX); + RRX(env, R_ECX) = rreg(cs->accel->fd, HV_X86_RCX); + RRX(env, R_EDX) = rreg(cs->accel->fd, HV_X86_RDX); + RRX(env, R_ESI) = rreg(cs->accel->fd, HV_X86_RSI); + RRX(env, R_EDI) = rreg(cs->accel->fd, HV_X86_RDI); + RRX(env, R_ESP) = rreg(cs->accel->fd, HV_X86_RSP); + RRX(env, R_EBP) = rreg(cs->accel->fd, HV_X86_RBP); + for (i = 8; i < 16; i++) { + RRX(env, i) = rreg(cs->accel->fd, HV_X86_RAX + i); + } + + env->eflags = rreg(cs->accel->fd, HV_X86_RFLAGS); + rflags_to_lflags(env); + env->eip = rreg(cs->accel->fd, HV_X86_RIP); +} + +void hvf_store_regs(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + CPUX86State *env = &cpu->env; + + int i = 0; + wreg(cs->accel->fd, HV_X86_RAX, RAX(env)); + wreg(cs->accel->fd, HV_X86_RBX, RBX(env)); + wreg(cs->accel->fd, HV_X86_RCX, RCX(env)); + wreg(cs->accel->fd, HV_X86_RDX, RDX(env)); + wreg(cs->accel->fd, HV_X86_RSI, RSI(env)); + wreg(cs->accel->fd, HV_X86_RDI, RDI(env)); + wreg(cs->accel->fd, HV_X86_RBP, RBP(env)); + wreg(cs->accel->fd, HV_X86_RSP, RSP(env)); + for (i = 8; i < 16; i++) { + wreg(cs->accel->fd, HV_X86_RAX + i, RRX(env, i)); + } + + lflags_to_rflags(env); + wreg(cs->accel->fd, HV_X86_RFLAGS, env->eflags); + macvm_set_rip(cs, env->eip); +} + int hvf_vcpu_exec(CPUState *cpu) { X86CPU *x86_cpu = X86_CPU(cpu); @@ -517,10 +564,10 @@ int hvf_vcpu_exec(CPUState *cpu) if (ept_emulation_fault(slot, gpa, exit_qual)) { struct x86_decode decode; - load_regs(cpu); + hvf_load_regs(cpu); decode_instruction(env, &decode); exec_instruction(env, &decode); - store_regs(cpu); + hvf_store_regs(cpu); break; } break; @@ -535,7 +582,7 @@ int hvf_vcpu_exec(CPUState *cpu) if (!string && in) { uint64_t val = 0; - load_regs(cpu); + hvf_load_regs(cpu); hvf_handle_io(env_cpu(env), port, &val, 0, size, 1); if (size == 1) { AL(env) = val; @@ -547,7 +594,7 @@ int hvf_vcpu_exec(CPUState *cpu) RAX(env) = (uint64_t)val; } env->eip += ins_len; - store_regs(cpu); + hvf_store_regs(cpu); break; } else if (!string && !in) { RAX(env) = rreg(cpu->accel->fd, HV_X86_RAX); @@ -557,11 +604,11 @@ int hvf_vcpu_exec(CPUState *cpu) } struct x86_decode decode; - load_regs(cpu); + hvf_load_regs(cpu); decode_instruction(env, &decode); assert(ins_len == decode.len); exec_instruction(env, &decode); - store_regs(cpu); + hvf_store_regs(cpu); break; } @@ -614,21 +661,21 @@ int hvf_vcpu_exec(CPUState *cpu) case EXIT_REASON_RDMSR: case EXIT_REASON_WRMSR: { - load_regs(cpu); + hvf_load_regs(cpu); if (exit_reason == EXIT_REASON_RDMSR) { simulate_rdmsr(env); } else { simulate_wrmsr(env); } env->eip += ins_len; - store_regs(cpu); + hvf_store_regs(cpu); break; } case EXIT_REASON_CR_ACCESS: { int cr; int reg; - load_regs(cpu); + hvf_load_regs(cpu); cr = exit_qual & 15; reg = (exit_qual >> 8) & 15; @@ -656,16 +703,16 @@ int hvf_vcpu_exec(CPUState *cpu) abort(); } env->eip += ins_len; - store_regs(cpu); + hvf_store_regs(cpu); break; } case EXIT_REASON_APIC_ACCESS: { /* TODO */ struct x86_decode decode; - load_regs(cpu); + hvf_load_regs(cpu); decode_instruction(env, &decode); exec_instruction(env, &decode); - store_regs(cpu); + hvf_store_regs(cpu); break; } case EXIT_REASON_TPR: { diff --git a/target/i386/hvf/x86_emu.c b/target/i386/hvf/x86_emu.c index 2c7da10c1d2e..8b0d54d69a00 100644 --- a/target/i386/hvf/x86_emu.c +++ b/target/i386/hvf/x86_emu.c @@ -1452,52 +1452,6 @@ static void init_cmd_handler(void) } } -void load_regs(CPUState *cs) -{ - X86CPU *cpu = X86_CPU(cs); - CPUX86State *env = &cpu->env; - - int i = 0; - RRX(env, R_EAX) = rreg(cs->accel->fd, HV_X86_RAX); - RRX(env, R_EBX) = rreg(cs->accel->fd, HV_X86_RBX); - RRX(env, R_ECX) = rreg(cs->accel->fd, HV_X86_RCX); - RRX(env, R_EDX) = rreg(cs->accel->fd, HV_X86_RDX); - RRX(env, R_ESI) = rreg(cs->accel->fd, HV_X86_RSI); - RRX(env, R_EDI) = rreg(cs->accel->fd, HV_X86_RDI); - RRX(env, R_ESP) = rreg(cs->accel->fd, HV_X86_RSP); - RRX(env, R_EBP) = rreg(cs->accel->fd, HV_X86_RBP); - for (i = 8; i < 16; i++) { - RRX(env, i) = rreg(cs->accel->fd, HV_X86_RAX + i); - } - - env->eflags = rreg(cs->accel->fd, HV_X86_RFLAGS); - rflags_to_lflags(env); - env->eip = rreg(cs->accel->fd, HV_X86_RIP); -} - -void store_regs(CPUState *cs) -{ - X86CPU *cpu = X86_CPU(cs); - CPUX86State *env = &cpu->env; - - int i = 0; - wreg(cs->accel->fd, HV_X86_RAX, RAX(env)); - wreg(cs->accel->fd, HV_X86_RBX, RBX(env)); - wreg(cs->accel->fd, HV_X86_RCX, RCX(env)); - wreg(cs->accel->fd, HV_X86_RDX, RDX(env)); - wreg(cs->accel->fd, HV_X86_RSI, RSI(env)); - wreg(cs->accel->fd, HV_X86_RDI, RDI(env)); - wreg(cs->accel->fd, HV_X86_RBP, RBP(env)); - wreg(cs->accel->fd, HV_X86_RSP, RSP(env)); - for (i = 8; i < 16; i++) { - wreg(cs->accel->fd, HV_X86_RAX + i, RRX(env, i)); - } - - lflags_to_rflags(env); - wreg(cs->accel->fd, HV_X86_RFLAGS, env->eflags); - macvm_set_rip(cs, env->eip); -} - bool exec_instruction(CPUX86State *env, struct x86_decode *ins) { /*if (hvf_vcpu_id(cs)) diff --git a/target/i386/hvf/x86_emu.h b/target/i386/hvf/x86_emu.h index 8bd97608c42e..cd953849c99c 100644 --- a/target/i386/hvf/x86_emu.h +++ b/target/i386/hvf/x86_emu.h @@ -26,9 +26,6 @@ void init_emu(void); bool exec_instruction(CPUX86State *env, struct x86_decode *ins); -void load_regs(CPUState *cpu); -void store_regs(CPUState *cpu); - void simulate_rdmsr(CPUX86State *env); void simulate_wrmsr(CPUX86State *env); diff --git a/target/i386/hvf/x86_task.c b/target/i386/hvf/x86_task.c index 287fe11cf704..161217991fc0 100644 --- a/target/i386/hvf/x86_task.c +++ b/target/i386/hvf/x86_task.c @@ -119,7 +119,7 @@ void vmx_handle_task_switch(CPUState *cpu, x86_segment_selector tss_sel, int rea return; } - load_regs(cpu); + hvf_load_regs(cpu); struct x86_segment_descriptor curr_tss_desc, next_tss_desc; x86_segment_selector old_tss_sel = vmx_read_segment_selector(cpu, R_TR); @@ -178,7 +178,7 @@ void vmx_handle_task_switch(CPUState *cpu, x86_segment_selector tss_sel, int rea x86_segment_descriptor_to_vmx(cpu, tss_sel, &next_tss_desc, &vmx_seg); vmx_write_segment_descriptor(cpu, &vmx_seg, R_TR); - store_regs(cpu); + hvf_store_regs(cpu); hv_vcpu_invalidate_tlb(cpu->accel->fd); } diff --git a/target/i386/hvf/x86hvf.h b/target/i386/hvf/x86hvf.h index 423a89b6ad30..8c46ce8ad02f 100644 --- a/target/i386/hvf/x86hvf.h +++ b/target/i386/hvf/x86hvf.h @@ -31,4 +31,7 @@ void hvf_get_xsave(CPUState *cs); void hvf_get_msrs(CPUState *cs); void vmx_clear_int_window_exiting(CPUState *cs); void vmx_update_tpr(CPUState *cs); + +void hvf_load_regs(CPUState *cpu); +void hvf_store_regs(CPUState *cpu); #endif From 99e5aaf9afeed3e0437f6dbc7672e3028d2b2f4b Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 21 Feb 2025 00:36:19 -0800 Subject: [PATCH 0266/1179] target/i386/hvf: move and rename simulate_{rdmsr, wrmsr} This requires making raise_exception non-static. That function needs to be renamed to avoid clashing with a function in TCG. Mostly code movement. No functional change. Signed-off-by: Wei Liu Link: https://lore.kernel.org/r/1740126987-8483-12-git-send-email-liuwe@linux.microsoft.com Signed-off-by: Paolo Bonzini --- target/i386/hvf/hvf-i386.h | 2 + target/i386/hvf/hvf.c | 216 +++++++++++++++++++++++++++++++++++- target/i386/hvf/x86_emu.c | 219 +------------------------------------ target/i386/hvf/x86_emu.h | 4 +- 4 files changed, 220 insertions(+), 221 deletions(-) diff --git a/target/i386/hvf/hvf-i386.h b/target/i386/hvf/hvf-i386.h index 046b681d134e..044ad236ae80 100644 --- a/target/i386/hvf/hvf-i386.h +++ b/target/i386/hvf/hvf-i386.h @@ -19,6 +19,8 @@ uint32_t hvf_get_supported_cpuid(uint32_t func, uint32_t idx, int reg); void hvf_handle_io(CPUState *, uint16_t, void *, int, int, int); +void hvf_simulate_rdmsr(CPUX86State *env); +void hvf_simulate_wrmsr(CPUX86State *env); /* Host specific functions */ int hvf_inject_interrupt(CPUArchState *env, int vector); diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index 3ab3c0043dbf..9ba0e04ac756 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -481,6 +481,218 @@ void hvf_store_regs(CPUState *cs) macvm_set_rip(cs, env->eip); } +void hvf_simulate_rdmsr(CPUX86State *env) +{ + X86CPU *cpu = env_archcpu(env); + CPUState *cs = env_cpu(env); + uint32_t msr = ECX(env); + uint64_t val = 0; + + switch (msr) { + case MSR_IA32_TSC: + val = rdtscp() + rvmcs(cs->accel->fd, VMCS_TSC_OFFSET); + break; + case MSR_IA32_APICBASE: + val = cpu_get_apic_base(cpu->apic_state); + break; + case MSR_APIC_START ... MSR_APIC_END: { + int ret; + int index = (uint32_t)env->regs[R_ECX] - MSR_APIC_START; + + ret = apic_msr_read(index, &val); + if (ret < 0) { + x86_emul_raise_exception(env, EXCP0D_GPF, 0); + } + + break; + } + case MSR_IA32_UCODE_REV: + val = cpu->ucode_rev; + break; + case MSR_EFER: + val = rvmcs(cs->accel->fd, VMCS_GUEST_IA32_EFER); + break; + case MSR_FSBASE: + val = rvmcs(cs->accel->fd, VMCS_GUEST_FS_BASE); + break; + case MSR_GSBASE: + val = rvmcs(cs->accel->fd, VMCS_GUEST_GS_BASE); + break; + case MSR_KERNELGSBASE: + val = rvmcs(cs->accel->fd, VMCS_HOST_FS_BASE); + break; + case MSR_STAR: + abort(); + break; + case MSR_LSTAR: + abort(); + break; + case MSR_CSTAR: + abort(); + break; + case MSR_IA32_MISC_ENABLE: + val = env->msr_ia32_misc_enable; + break; + case MSR_MTRRphysBase(0): + case MSR_MTRRphysBase(1): + case MSR_MTRRphysBase(2): + case MSR_MTRRphysBase(3): + case MSR_MTRRphysBase(4): + case MSR_MTRRphysBase(5): + case MSR_MTRRphysBase(6): + case MSR_MTRRphysBase(7): + val = env->mtrr_var[(ECX(env) - MSR_MTRRphysBase(0)) / 2].base; + break; + case MSR_MTRRphysMask(0): + case MSR_MTRRphysMask(1): + case MSR_MTRRphysMask(2): + case MSR_MTRRphysMask(3): + case MSR_MTRRphysMask(4): + case MSR_MTRRphysMask(5): + case MSR_MTRRphysMask(6): + case MSR_MTRRphysMask(7): + val = env->mtrr_var[(ECX(env) - MSR_MTRRphysMask(0)) / 2].mask; + break; + case MSR_MTRRfix64K_00000: + val = env->mtrr_fixed[0]; + break; + case MSR_MTRRfix16K_80000: + case MSR_MTRRfix16K_A0000: + val = env->mtrr_fixed[ECX(env) - MSR_MTRRfix16K_80000 + 1]; + break; + case MSR_MTRRfix4K_C0000: + case MSR_MTRRfix4K_C8000: + case MSR_MTRRfix4K_D0000: + case MSR_MTRRfix4K_D8000: + case MSR_MTRRfix4K_E0000: + case MSR_MTRRfix4K_E8000: + case MSR_MTRRfix4K_F0000: + case MSR_MTRRfix4K_F8000: + val = env->mtrr_fixed[ECX(env) - MSR_MTRRfix4K_C0000 + 3]; + break; + case MSR_MTRRdefType: + val = env->mtrr_deftype; + break; + case MSR_CORE_THREAD_COUNT: + val = cpu_x86_get_msr_core_thread_count(cpu); + break; + default: + /* fprintf(stderr, "%s: unknown msr 0x%x\n", __func__, msr); */ + val = 0; + break; + } + + RAX(env) = (uint32_t)val; + RDX(env) = (uint32_t)(val >> 32); +} + +void hvf_simulate_wrmsr(CPUX86State *env) +{ + X86CPU *cpu = env_archcpu(env); + CPUState *cs = env_cpu(env); + uint32_t msr = ECX(env); + uint64_t data = ((uint64_t)EDX(env) << 32) | EAX(env); + + switch (msr) { + case MSR_IA32_TSC: + break; + case MSR_IA32_APICBASE: { + int r; + + r = cpu_set_apic_base(cpu->apic_state, data); + if (r < 0) { + x86_emul_raise_exception(env, EXCP0D_GPF, 0); + } + + break; + } + case MSR_APIC_START ... MSR_APIC_END: { + int ret; + int index = (uint32_t)env->regs[R_ECX] - MSR_APIC_START; + + ret = apic_msr_write(index, data); + if (ret < 0) { + x86_emul_raise_exception(env, EXCP0D_GPF, 0); + } + + break; + } + case MSR_FSBASE: + wvmcs(cs->accel->fd, VMCS_GUEST_FS_BASE, data); + break; + case MSR_GSBASE: + wvmcs(cs->accel->fd, VMCS_GUEST_GS_BASE, data); + break; + case MSR_KERNELGSBASE: + wvmcs(cs->accel->fd, VMCS_HOST_FS_BASE, data); + break; + case MSR_STAR: + abort(); + break; + case MSR_LSTAR: + abort(); + break; + case MSR_CSTAR: + abort(); + break; + case MSR_EFER: + /*printf("new efer %llx\n", EFER(cs));*/ + wvmcs(cs->accel->fd, VMCS_GUEST_IA32_EFER, data); + if (data & MSR_EFER_NXE) { + hv_vcpu_invalidate_tlb(cs->accel->fd); + } + break; + case MSR_MTRRphysBase(0): + case MSR_MTRRphysBase(1): + case MSR_MTRRphysBase(2): + case MSR_MTRRphysBase(3): + case MSR_MTRRphysBase(4): + case MSR_MTRRphysBase(5): + case MSR_MTRRphysBase(6): + case MSR_MTRRphysBase(7): + env->mtrr_var[(ECX(env) - MSR_MTRRphysBase(0)) / 2].base = data; + break; + case MSR_MTRRphysMask(0): + case MSR_MTRRphysMask(1): + case MSR_MTRRphysMask(2): + case MSR_MTRRphysMask(3): + case MSR_MTRRphysMask(4): + case MSR_MTRRphysMask(5): + case MSR_MTRRphysMask(6): + case MSR_MTRRphysMask(7): + env->mtrr_var[(ECX(env) - MSR_MTRRphysMask(0)) / 2].mask = data; + break; + case MSR_MTRRfix64K_00000: + env->mtrr_fixed[ECX(env) - MSR_MTRRfix64K_00000] = data; + break; + case MSR_MTRRfix16K_80000: + case MSR_MTRRfix16K_A0000: + env->mtrr_fixed[ECX(env) - MSR_MTRRfix16K_80000 + 1] = data; + break; + case MSR_MTRRfix4K_C0000: + case MSR_MTRRfix4K_C8000: + case MSR_MTRRfix4K_D0000: + case MSR_MTRRfix4K_D8000: + case MSR_MTRRfix4K_E0000: + case MSR_MTRRfix4K_E8000: + case MSR_MTRRfix4K_F0000: + case MSR_MTRRfix4K_F8000: + env->mtrr_fixed[ECX(env) - MSR_MTRRfix4K_C0000 + 3] = data; + break; + case MSR_MTRRdefType: + env->mtrr_deftype = data; + break; + default: + break; + } + + /* Related to support known hypervisor interface */ + /* if (g_hypervisor_iface) + g_hypervisor_iface->wrmsr_handler(cs, msr, data); + + printf("write msr %llx\n", RCX(cs));*/ +} + int hvf_vcpu_exec(CPUState *cpu) { X86CPU *x86_cpu = X86_CPU(cpu); @@ -663,9 +875,9 @@ int hvf_vcpu_exec(CPUState *cpu) { hvf_load_regs(cpu); if (exit_reason == EXIT_REASON_RDMSR) { - simulate_rdmsr(env); + hvf_simulate_rdmsr(env); } else { - simulate_wrmsr(env); + hvf_simulate_wrmsr(env); } env->eip += ins_len; hvf_store_regs(cpu); diff --git a/target/i386/hvf/x86_emu.c b/target/i386/hvf/x86_emu.c index 8b0d54d69a00..df8159411561 100644 --- a/target/i386/hvf/x86_emu.c +++ b/target/i386/hvf/x86_emu.c @@ -661,8 +661,7 @@ static void exec_lods(CPUX86State *env, struct x86_decode *decode) env->eip += decode->len; } -static void raise_exception(CPUX86State *env, int exception_index, - int error_code) +void x86_emul_raise_exception(CPUX86State *env, int exception_index, int error_code) { env->exception_nr = exception_index; env->error_code = error_code; @@ -670,227 +669,15 @@ static void raise_exception(CPUX86State *env, int exception_index, env->exception_injected = 1; } -void simulate_rdmsr(CPUX86State *env) -{ - X86CPU *cpu = env_archcpu(env); - CPUState *cs = env_cpu(env); - uint32_t msr = ECX(env); - uint64_t val = 0; - - switch (msr) { - case MSR_IA32_TSC: - val = rdtscp() + rvmcs(cs->accel->fd, VMCS_TSC_OFFSET); - break; - case MSR_IA32_APICBASE: - val = cpu_get_apic_base(cpu->apic_state); - break; - case MSR_APIC_START ... MSR_APIC_END: { - int ret; - int index = (uint32_t)env->regs[R_ECX] - MSR_APIC_START; - - ret = apic_msr_read(index, &val); - if (ret < 0) { - raise_exception(env, EXCP0D_GPF, 0); - } - - break; - } - case MSR_IA32_UCODE_REV: - val = cpu->ucode_rev; - break; - case MSR_EFER: - val = rvmcs(cs->accel->fd, VMCS_GUEST_IA32_EFER); - break; - case MSR_FSBASE: - val = rvmcs(cs->accel->fd, VMCS_GUEST_FS_BASE); - break; - case MSR_GSBASE: - val = rvmcs(cs->accel->fd, VMCS_GUEST_GS_BASE); - break; - case MSR_KERNELGSBASE: - val = rvmcs(cs->accel->fd, VMCS_HOST_FS_BASE); - break; - case MSR_STAR: - abort(); - break; - case MSR_LSTAR: - abort(); - break; - case MSR_CSTAR: - abort(); - break; - case MSR_IA32_MISC_ENABLE: - val = env->msr_ia32_misc_enable; - break; - case MSR_MTRRphysBase(0): - case MSR_MTRRphysBase(1): - case MSR_MTRRphysBase(2): - case MSR_MTRRphysBase(3): - case MSR_MTRRphysBase(4): - case MSR_MTRRphysBase(5): - case MSR_MTRRphysBase(6): - case MSR_MTRRphysBase(7): - val = env->mtrr_var[(ECX(env) - MSR_MTRRphysBase(0)) / 2].base; - break; - case MSR_MTRRphysMask(0): - case MSR_MTRRphysMask(1): - case MSR_MTRRphysMask(2): - case MSR_MTRRphysMask(3): - case MSR_MTRRphysMask(4): - case MSR_MTRRphysMask(5): - case MSR_MTRRphysMask(6): - case MSR_MTRRphysMask(7): - val = env->mtrr_var[(ECX(env) - MSR_MTRRphysMask(0)) / 2].mask; - break; - case MSR_MTRRfix64K_00000: - val = env->mtrr_fixed[0]; - break; - case MSR_MTRRfix16K_80000: - case MSR_MTRRfix16K_A0000: - val = env->mtrr_fixed[ECX(env) - MSR_MTRRfix16K_80000 + 1]; - break; - case MSR_MTRRfix4K_C0000: - case MSR_MTRRfix4K_C8000: - case MSR_MTRRfix4K_D0000: - case MSR_MTRRfix4K_D8000: - case MSR_MTRRfix4K_E0000: - case MSR_MTRRfix4K_E8000: - case MSR_MTRRfix4K_F0000: - case MSR_MTRRfix4K_F8000: - val = env->mtrr_fixed[ECX(env) - MSR_MTRRfix4K_C0000 + 3]; - break; - case MSR_MTRRdefType: - val = env->mtrr_deftype; - break; - case MSR_CORE_THREAD_COUNT: - val = cpu_x86_get_msr_core_thread_count(cpu); - break; - default: - /* fprintf(stderr, "%s: unknown msr 0x%x\n", __func__, msr); */ - val = 0; - break; - } - - RAX(env) = (uint32_t)val; - RDX(env) = (uint32_t)(val >> 32); -} - static void exec_rdmsr(CPUX86State *env, struct x86_decode *decode) { - simulate_rdmsr(env); + hvf_simulate_rdmsr(env); env->eip += decode->len; } -void simulate_wrmsr(CPUX86State *env) -{ - X86CPU *cpu = env_archcpu(env); - CPUState *cs = env_cpu(env); - uint32_t msr = ECX(env); - uint64_t data = ((uint64_t)EDX(env) << 32) | EAX(env); - - switch (msr) { - case MSR_IA32_TSC: - break; - case MSR_IA32_APICBASE: { - int r; - - r = cpu_set_apic_base(cpu->apic_state, data); - if (r < 0) { - raise_exception(env, EXCP0D_GPF, 0); - } - - break; - } - case MSR_APIC_START ... MSR_APIC_END: { - int ret; - int index = (uint32_t)env->regs[R_ECX] - MSR_APIC_START; - - ret = apic_msr_write(index, data); - if (ret < 0) { - raise_exception(env, EXCP0D_GPF, 0); - } - - break; - } - case MSR_FSBASE: - wvmcs(cs->accel->fd, VMCS_GUEST_FS_BASE, data); - break; - case MSR_GSBASE: - wvmcs(cs->accel->fd, VMCS_GUEST_GS_BASE, data); - break; - case MSR_KERNELGSBASE: - wvmcs(cs->accel->fd, VMCS_HOST_FS_BASE, data); - break; - case MSR_STAR: - abort(); - break; - case MSR_LSTAR: - abort(); - break; - case MSR_CSTAR: - abort(); - break; - case MSR_EFER: - /*printf("new efer %llx\n", EFER(cs));*/ - wvmcs(cs->accel->fd, VMCS_GUEST_IA32_EFER, data); - if (data & MSR_EFER_NXE) { - hv_vcpu_invalidate_tlb(cs->accel->fd); - } - break; - case MSR_MTRRphysBase(0): - case MSR_MTRRphysBase(1): - case MSR_MTRRphysBase(2): - case MSR_MTRRphysBase(3): - case MSR_MTRRphysBase(4): - case MSR_MTRRphysBase(5): - case MSR_MTRRphysBase(6): - case MSR_MTRRphysBase(7): - env->mtrr_var[(ECX(env) - MSR_MTRRphysBase(0)) / 2].base = data; - break; - case MSR_MTRRphysMask(0): - case MSR_MTRRphysMask(1): - case MSR_MTRRphysMask(2): - case MSR_MTRRphysMask(3): - case MSR_MTRRphysMask(4): - case MSR_MTRRphysMask(5): - case MSR_MTRRphysMask(6): - case MSR_MTRRphysMask(7): - env->mtrr_var[(ECX(env) - MSR_MTRRphysMask(0)) / 2].mask = data; - break; - case MSR_MTRRfix64K_00000: - env->mtrr_fixed[ECX(env) - MSR_MTRRfix64K_00000] = data; - break; - case MSR_MTRRfix16K_80000: - case MSR_MTRRfix16K_A0000: - env->mtrr_fixed[ECX(env) - MSR_MTRRfix16K_80000 + 1] = data; - break; - case MSR_MTRRfix4K_C0000: - case MSR_MTRRfix4K_C8000: - case MSR_MTRRfix4K_D0000: - case MSR_MTRRfix4K_D8000: - case MSR_MTRRfix4K_E0000: - case MSR_MTRRfix4K_E8000: - case MSR_MTRRfix4K_F0000: - case MSR_MTRRfix4K_F8000: - env->mtrr_fixed[ECX(env) - MSR_MTRRfix4K_C0000 + 3] = data; - break; - case MSR_MTRRdefType: - env->mtrr_deftype = data; - break; - default: - break; - } - - /* Related to support known hypervisor interface */ - /* if (g_hypervisor_iface) - g_hypervisor_iface->wrmsr_handler(cs, msr, data); - - printf("write msr %llx\n", RCX(cs));*/ -} - static void exec_wrmsr(CPUX86State *env, struct x86_decode *decode) { - simulate_wrmsr(env); + hvf_simulate_wrmsr(env); env->eip += decode->len; } diff --git a/target/i386/hvf/x86_emu.h b/target/i386/hvf/x86_emu.h index cd953849c99c..bc0fc72c761b 100644 --- a/target/i386/hvf/x86_emu.h +++ b/target/i386/hvf/x86_emu.h @@ -25,9 +25,7 @@ void init_emu(void); bool exec_instruction(CPUX86State *env, struct x86_decode *ins); - -void simulate_rdmsr(CPUX86State *env); -void simulate_wrmsr(CPUX86State *env); +void x86_emul_raise_exception(CPUX86State *env, int exception_index, int error_code); target_ulong read_reg(CPUX86State *env, int reg, int size); void write_reg(CPUX86State *env, int reg, target_ulong val, int size); From 646140dfebd42a9a9df8f15a22027b7efcb072cf Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 21 Feb 2025 00:36:23 -0800 Subject: [PATCH 0267/1179] target/i386/hvf: drop some dead code Signed-off-by: Wei Liu Link: https://lore.kernel.org/r/1740126987-8483-16-git-send-email-liuwe@linux.microsoft.com Signed-off-by: Paolo Bonzini --- target/i386/hvf/x86_emu.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/target/i386/hvf/x86_emu.c b/target/i386/hvf/x86_emu.c index df8159411561..ebba80a36b50 100644 --- a/target/i386/hvf/x86_emu.c +++ b/target/i386/hvf/x86_emu.c @@ -1241,10 +1241,6 @@ static void init_cmd_handler(void) bool exec_instruction(CPUX86State *env, struct x86_decode *ins) { - /*if (hvf_vcpu_id(cs)) - printf("%d, %llx: exec_instruction %s\n", hvf_vcpu_id(cs), env->eip, - decode_cmd_to_string(ins->cmd));*/ - if (!_cmd_handler[ins->cmd].handler) { printf("Unimplemented handler (%llx) for %d (%x %x) \n", env->eip, ins->cmd, ins->opcode[0], From ac5699c5da51fa9d39bc964e81645953796f7ad1 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 12 Feb 2025 12:18:10 +0100 Subject: [PATCH 0268/1179] rust: add IsA bounds to QOM implementation traits Check that the right bounds are provided to the qom_isa! macro whenever the class is defined to implement a certain class. This removes the need to add IsA<> bounds together with the *Impl trait bounds. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/qdev.rs | 2 +- rust/qemu-api/src/qom.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 3a7aa4def62d..c4dd26b582cf 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -86,7 +86,7 @@ unsafe extern "C" fn rust_resettable_exit_fn( } /// Trait providing the contents of [`DeviceClass`]. -pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl { +pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA { /// _Realization_ is the second stage of device creation. It contains /// all operations that depend on device properties and can fail (note: /// this is not yet supported for Rust devices). diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 3d5ab2d90186..10ce359becb4 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -37,6 +37,8 @@ //! * a trait for virtual method implementations, for example `DeviceImpl`. //! Child classes implement this trait to provide their own behavior for //! virtual methods. The trait's methods take `&self` to access instance data. +//! The traits have the appropriate specialization of `IsA<>` as a supertrait, +//! for example `IsA` for `DeviceImpl`. //! //! * an implementation of [`ClassInitImpl`], for example //! `ClassInitImpl`. This fills the vtable in the class struct; @@ -497,7 +499,7 @@ impl ObjectDeref for &mut T {} impl ObjectCastMut for &mut T {} /// Trait a type must implement to be registered with QEMU. -pub trait ObjectImpl: ObjectType + ClassInitImpl { +pub trait ObjectImpl: ObjectType + ClassInitImpl + IsA { /// The parent of the type. This should match the first field of the /// struct that implements `ObjectImpl`, minus the `ParentField<_>` wrapper. type ParentType: ObjectType; From 3212da0033530ae896d31d90d5e81a772fc37088 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 12 Feb 2025 12:23:59 +0100 Subject: [PATCH 0269/1179] rust: add SysBusDeviceImpl The only function, right now, is to ensure that anything with a SysBusDeviceClass class is a SysBusDevice. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 5 ++++- rust/hw/timer/hpet/src/hpet.rs | 4 +++- rust/qemu-api/src/sysbus.rs | 8 +++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 59a689fdcd77..bea9723aed85 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -20,7 +20,7 @@ use qemu_api::{ prelude::*, qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, qom::{ClassInitImpl, ObjectImpl, Owned, ParentField}, - sysbus::{SysBusDevice, SysBusDeviceClass}, + sysbus::{SysBusDevice, SysBusDeviceClass, SysBusDeviceImpl}, vmstate::VMStateDescription, }; @@ -176,6 +176,8 @@ impl ResettablePhasesImpl for PL011State { const HOLD: Option = Some(Self::reset_hold); } +impl SysBusDeviceImpl for PL011State {} + impl PL011Registers { pub(self) fn read(&mut self, offset: RegisterOffset) -> (bool, u32) { use RegisterOffset::*; @@ -746,3 +748,4 @@ impl ObjectImpl for PL011Luminary { impl DeviceImpl for PL011Luminary {} impl ResettablePhasesImpl for PL011Luminary {} +impl SysBusDeviceImpl for PL011Luminary {} diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index 75ff5b3e8d6c..b4ffccf815f9 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -23,7 +23,7 @@ use qemu_api::{ qdev::{DeviceImpl, DeviceMethods, DeviceState, Property, ResetType, ResettablePhasesImpl}, qom::{ObjectImpl, ObjectType, ParentField}, qom_isa, - sysbus::SysBusDevice, + sysbus::{SysBusDevice, SysBusDeviceImpl}, timer::{Timer, CLOCK_VIRTUAL}, }; @@ -887,3 +887,5 @@ impl DeviceImpl for HPETState { impl ResettablePhasesImpl for HPETState { const HOLD: Option = Some(Self::reset_hold); } + +impl SysBusDeviceImpl for HPETState {} diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index fa36e12178f1..fee2e3d478f7 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -14,7 +14,7 @@ use crate::{ irq::{IRQState, InterruptSource}, memory::MemoryRegion, prelude::*, - qdev::{DeviceClass, DeviceState}, + qdev::{DeviceClass, DeviceImpl, DeviceState}, qom::{ClassInitImpl, Owned}, }; @@ -25,10 +25,12 @@ unsafe impl ObjectType for SysBusDevice { } qom_isa!(SysBusDevice: DeviceState, Object); -// TODO: add SysBusDeviceImpl +// TODO: add virtual methods +pub trait SysBusDeviceImpl: DeviceImpl + IsA {} + impl ClassInitImpl for T where - T: ClassInitImpl, + T: SysBusDeviceImpl + ClassInitImpl, { fn class_init(sdc: &mut SysBusDeviceClass) { >::class_init(&mut sdc.parent_class); From 4551f342fed66af7f5e2b099fa06f4007db356e6 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 12 Feb 2025 11:49:32 +0100 Subject: [PATCH 0270/1179] rust: qom: add ObjectImpl::CLASS_INIT As shown in the PL011 device, the orphan rules required a manual implementation of ClassInitImpl for anything not in the qemu_api crate; this gets in the way of moving system emulation-specific code (including DeviceClass, which as a blanket ClassInitImpl implementation) into its own crate. Make ClassInitImpl optional, at the cost of having to specify the CLASS_INIT member by hand in every implementation of ObjectImpl. The next commits will get rid of it, replacing all the "impl ClassInitImpl for T" blocks with a generic class_init method on Class. Right now the definition is always the same, but do not provide a default as that will not be true once ClassInitImpl goes away. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 3 +++ rust/hw/timer/hpet/src/hpet.rs | 3 ++- rust/qemu-api/src/qom.rs | 14 +++++++++++--- rust/qemu-api/tests/tests.rs | 3 +++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index bea9723aed85..ead361b3f528 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -160,6 +160,7 @@ impl ObjectImpl for PL011State { const INSTANCE_INIT: Option = Some(Self::init); const INSTANCE_POST_INIT: Option = Some(Self::post_init); + const CLASS_INIT: fn(&mut Self::Class) = >::class_init; } impl DeviceImpl for PL011State { @@ -744,6 +745,8 @@ unsafe impl ObjectType for PL011Luminary { impl ObjectImpl for PL011Luminary { type ParentType = PL011State; + + const CLASS_INIT: fn(&mut Self::Class) = >::class_init; } impl DeviceImpl for PL011Luminary {} diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index b4ffccf815f9..e01b4b670645 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -21,7 +21,7 @@ use qemu_api::{ }, prelude::*, qdev::{DeviceImpl, DeviceMethods, DeviceState, Property, ResetType, ResettablePhasesImpl}, - qom::{ObjectImpl, ObjectType, ParentField}, + qom::{ClassInitImpl, ObjectImpl, ObjectType, ParentField}, qom_isa, sysbus::{SysBusDevice, SysBusDeviceImpl}, timer::{Timer, CLOCK_VIRTUAL}, @@ -836,6 +836,7 @@ impl ObjectImpl for HPETState { const INSTANCE_INIT: Option = Some(Self::init); const INSTANCE_POST_INIT: Option = Some(Self::post_init); + const CLASS_INIT: fn(&mut Self::Class) = >::class_init; } // TODO: Make these properties user-configurable! diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 10ce359becb4..d821ac25acc9 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -180,7 +180,7 @@ unsafe extern "C" fn rust_instance_post_init(obj: *mut Object) { T::INSTANCE_POST_INIT.unwrap()(unsafe { state.as_ref() }); } -unsafe extern "C" fn rust_class_init>( +unsafe extern "C" fn rust_class_init( klass: *mut ObjectClass, _data: *mut c_void, ) { @@ -190,7 +190,7 @@ unsafe extern "C" fn rust_class_init>( // SAFETY: klass is a T::Class, since rust_class_init // is called from QOM core as the class_init function // for class T - T::class_init(unsafe { klass.as_mut() }) + ::CLASS_INIT(unsafe { klass.as_mut() }) } unsafe extern "C" fn drop_object(obj: *mut Object) { @@ -499,7 +499,7 @@ impl ObjectDeref for &mut T {} impl ObjectCastMut for &mut T {} /// Trait a type must implement to be registered with QEMU. -pub trait ObjectImpl: ObjectType + ClassInitImpl + IsA { +pub trait ObjectImpl: ObjectType + IsA { /// The parent of the type. This should match the first field of the /// struct that implements `ObjectImpl`, minus the `ParentField<_>` wrapper. type ParentType: ObjectType; @@ -552,6 +552,14 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl + IsA { // methods on ObjectClass const UNPARENT: Option = None; + + /// Store into the argument the virtual method implementations + /// for `Self`. On entry, the virtual method pointers are set to + /// the default values coming from the parent classes; the function + /// can change them to override virtual methods of a parent class. + /// + /// Usually defined as `::class_init`. + const CLASS_INIT: fn(&mut Self::Class); } /// Internal trait used to automatically fill in a class struct. diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 03569e4a44c4..9546e9d7963f 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -60,6 +60,7 @@ unsafe impl ObjectType for DummyState { impl ObjectImpl for DummyState { type ParentType = DeviceState; const ABSTRACT: bool = false; + const CLASS_INIT: fn(&mut DummyClass) = >::class_init; } impl ResettablePhasesImpl for DummyState {} @@ -102,6 +103,8 @@ unsafe impl ObjectType for DummyChildState { impl ObjectImpl for DummyChildState { type ParentType = DummyState; const ABSTRACT: bool = false; + const CLASS_INIT: fn(&mut DummyChildClass) = + >::class_init; } impl ResettablePhasesImpl for DummyChildState {} From 567c0c41a6700be72eb9e040ba0b8d7bf0cc5919 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 13 Feb 2025 12:36:42 +0100 Subject: [PATCH 0271/1179] rust: pl011, qemu_api tests: do not use ClassInitImpl Outside the qemu_api crate, orphan rules make the usage of ClassInitImpl unwieldy. Now that it is optional, do not use it. For PL011Class, this makes it easier to provide a PL011Impl trait similar to the ones in the qemu_api crate. The device id consts are moved there. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 38 ++++++++++++++++---------------- rust/qemu-api/tests/tests.rs | 33 ++++++++++----------------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index ead361b3f528..094049cb9e6f 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -50,11 +50,6 @@ impl std::ops::Index for DeviceId { } } -impl DeviceId { - const ARM: Self = Self(&[0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]); - const LUMINARY: Self = Self(&[0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1]); -} - // FIFOs use 32-bit indices instead of usize, for compatibility with // the migration stream produced by the C version of this device. #[repr(transparent)] @@ -143,16 +138,24 @@ pub struct PL011Class { device_id: DeviceId, } +trait PL011Impl: SysBusDeviceImpl + IsA { + const DEVICE_ID: DeviceId; +} + +impl PL011Class { + fn class_init(&mut self) { + self.device_id = T::DEVICE_ID; + >::class_init(&mut self.parent_class); + } +} + unsafe impl ObjectType for PL011State { type Class = PL011Class; const TYPE_NAME: &'static CStr = crate::TYPE_PL011; } -impl ClassInitImpl for PL011State { - fn class_init(klass: &mut PL011Class) { - klass.device_id = DeviceId::ARM; - >::class_init(&mut klass.parent_class); - } +impl PL011Impl for PL011State { + const DEVICE_ID: DeviceId = DeviceId(&[0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]); } impl ObjectImpl for PL011State { @@ -160,7 +163,7 @@ impl ObjectImpl for PL011State { const INSTANCE_INIT: Option = Some(Self::init); const INSTANCE_POST_INIT: Option = Some(Self::post_init); - const CLASS_INIT: fn(&mut Self::Class) = >::class_init; + const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::; } impl DeviceImpl for PL011State { @@ -729,13 +732,6 @@ pub struct PL011Luminary { parent_obj: ParentField, } -impl ClassInitImpl for PL011Luminary { - fn class_init(klass: &mut PL011Class) { - klass.device_id = DeviceId::LUMINARY; - >::class_init(&mut klass.parent_class); - } -} - qom_isa!(PL011Luminary : PL011State, SysBusDevice, DeviceState, Object); unsafe impl ObjectType for PL011Luminary { @@ -746,7 +742,11 @@ unsafe impl ObjectType for PL011Luminary { impl ObjectImpl for PL011Luminary { type ParentType = PL011State; - const CLASS_INIT: fn(&mut Self::Class) = >::class_init; + const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::; +} + +impl PL011Impl for PL011Luminary { + const DEVICE_ID: DeviceId = DeviceId(&[0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1]); } impl DeviceImpl for PL011Luminary {} diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 9546e9d7963f..93c5637bbc32 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -13,7 +13,7 @@ use qemu_api::{ cell::{self, BqlCell}, declare_properties, define_property, prelude::*, - qdev::{DeviceClass, DeviceImpl, DeviceState, Property, ResettablePhasesImpl}, + qdev::{DeviceImpl, DeviceState, Property, ResettablePhasesImpl}, qom::{ClassInitImpl, ObjectImpl, ParentField}, sysbus::SysBusDevice, vmstate::VMStateDescription, @@ -41,6 +41,12 @@ pub struct DummyClass { parent_class: ::Class, } +impl DummyClass { + pub fn class_init(self: &mut DummyClass) { + >::class_init(&mut self.parent_class); + } +} + declare_properties! { DUMMY_PROPERTIES, define_property!( @@ -60,7 +66,7 @@ unsafe impl ObjectType for DummyState { impl ObjectImpl for DummyState { type ParentType = DeviceState; const ABSTRACT: bool = false; - const CLASS_INIT: fn(&mut DummyClass) = >::class_init; + const CLASS_INIT: fn(&mut DummyClass) = DummyClass::class_init::; } impl ResettablePhasesImpl for DummyState {} @@ -74,14 +80,6 @@ impl DeviceImpl for DummyState { } } -// `impl ClassInitImpl for T` doesn't work since it violates -// orphan rule. -impl ClassInitImpl for DummyState { - fn class_init(klass: &mut DummyClass) { - >::class_init(&mut klass.parent_class); - } -} - #[derive(qemu_api_macros::offsets)] #[repr(C)] #[derive(qemu_api_macros::Object)] @@ -103,22 +101,15 @@ unsafe impl ObjectType for DummyChildState { impl ObjectImpl for DummyChildState { type ParentType = DummyState; const ABSTRACT: bool = false; - const CLASS_INIT: fn(&mut DummyChildClass) = - >::class_init; + const CLASS_INIT: fn(&mut DummyChildClass) = DummyChildClass::class_init::; } impl ResettablePhasesImpl for DummyChildState {} impl DeviceImpl for DummyChildState {} -impl ClassInitImpl for DummyChildState { - fn class_init(klass: &mut DummyClass) { - >::class_init(&mut klass.parent_class); - } -} - -impl ClassInitImpl for DummyChildState { - fn class_init(klass: &mut DummyChildClass) { - >::class_init(&mut klass.parent_class); +impl DummyChildClass { + pub fn class_init(self: &mut DummyChildClass) { + self.parent_class.class_init::(); } } From d556226d6965738e06a1d75faaf271b769bb5880 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 13 Feb 2025 12:37:43 +0100 Subject: [PATCH 0272/1179] rust: qom: get rid of ClassInitImpl Complete the conversion from the ClassInitImpl trait to class_init() methods. This will provide more freedom to split the qemu_api crate in separate parts. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 6 +- rust/hw/timer/hpet/src/hpet.rs | 4 +- rust/qemu-api/src/qdev.rs | 38 ++++--- rust/qemu-api/src/qom.rs | 164 +++++++++++++------------------ rust/qemu-api/src/sysbus.rs | 15 ++- rust/qemu-api/tests/tests.rs | 4 +- 6 files changed, 101 insertions(+), 130 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 094049cb9e6f..d0857b470c95 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -19,8 +19,8 @@ use qemu_api::{ memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, prelude::*, qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, - qom::{ClassInitImpl, ObjectImpl, Owned, ParentField}, - sysbus::{SysBusDevice, SysBusDeviceClass, SysBusDeviceImpl}, + qom::{ObjectImpl, Owned, ParentField}, + sysbus::{SysBusDevice, SysBusDeviceImpl}, vmstate::VMStateDescription, }; @@ -145,7 +145,7 @@ trait PL011Impl: SysBusDeviceImpl + IsA { impl PL011Class { fn class_init(&mut self) { self.device_id = T::DEVICE_ID; - >::class_init(&mut self.parent_class); + self.parent_class.class_init::(); } } diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index e01b4b670645..be27eb0eff40 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -21,7 +21,7 @@ use qemu_api::{ }, prelude::*, qdev::{DeviceImpl, DeviceMethods, DeviceState, Property, ResetType, ResettablePhasesImpl}, - qom::{ClassInitImpl, ObjectImpl, ObjectType, ParentField}, + qom::{ObjectImpl, ObjectType, ParentField}, qom_isa, sysbus::{SysBusDevice, SysBusDeviceImpl}, timer::{Timer, CLOCK_VIRTUAL}, @@ -836,7 +836,7 @@ impl ObjectImpl for HPETState { const INSTANCE_INIT: Option = Some(Self::init); const INSTANCE_POST_INIT: Option = Some(Self::post_init); - const CLASS_INIT: fn(&mut Self::Class) = >::class_init; + const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::; } // TODO: Make these properties user-configurable! diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index c4dd26b582cf..c136457090cf 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -19,7 +19,7 @@ use crate::{ chardev::Chardev, irq::InterruptSource, prelude::*, - qom::{ClassInitImpl, ObjectClass, ObjectImpl, Owned}, + qom::{ObjectClass, ObjectImpl, Owned}, vmstate::VMStateDescription, }; @@ -113,7 +113,7 @@ pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA { /// # Safety /// /// This function is only called through the QOM machinery and -/// used by the `ClassInitImpl` trait. +/// used by `DeviceClass::class_init`. /// We expect the FFI user of this function to pass a valid pointer that /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. @@ -127,43 +127,41 @@ unsafe impl InterfaceType for ResettableClass { unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_RESETTABLE_INTERFACE) }; } -impl ClassInitImpl for T -where - T: ResettablePhasesImpl, -{ - fn class_init(rc: &mut ResettableClass) { +impl ResettableClass { + /// Fill in the virtual methods of `ResettableClass` based on the + /// definitions in the `ResettablePhasesImpl` trait. + pub fn class_init(&mut self) { if ::ENTER.is_some() { - rc.phases.enter = Some(rust_resettable_enter_fn::); + self.phases.enter = Some(rust_resettable_enter_fn::); } if ::HOLD.is_some() { - rc.phases.hold = Some(rust_resettable_hold_fn::); + self.phases.hold = Some(rust_resettable_hold_fn::); } if ::EXIT.is_some() { - rc.phases.exit = Some(rust_resettable_exit_fn::); + self.phases.exit = Some(rust_resettable_exit_fn::); } } } -impl ClassInitImpl for T -where - T: ClassInitImpl + ClassInitImpl + DeviceImpl, -{ - fn class_init(dc: &mut DeviceClass) { +impl DeviceClass { + /// Fill in the virtual methods of `DeviceClass` based on the definitions in + /// the `DeviceImpl` trait. + pub fn class_init(&mut self) { if ::REALIZE.is_some() { - dc.realize = Some(rust_realize_fn::); + self.realize = Some(rust_realize_fn::); } if let Some(vmsd) = ::vmsd() { - dc.vmsd = vmsd; + self.vmsd = vmsd; } let prop = ::properties(); if !prop.is_empty() { unsafe { - bindings::device_class_set_props_n(dc, prop.as_ptr(), prop.len()); + bindings::device_class_set_props_n(self, prop.as_ptr(), prop.len()); } } - ResettableClass::interface_init::(dc); - >::class_init(&mut dc.parent_class); + ResettableClass::cast::(self).class_init::(); + self.parent_class.class_init::(); } } diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index d821ac25acc9..5488643a2fdf 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -40,11 +40,6 @@ //! The traits have the appropriate specialization of `IsA<>` as a supertrait, //! for example `IsA` for `DeviceImpl`. //! -//! * an implementation of [`ClassInitImpl`], for example -//! `ClassInitImpl`. This fills the vtable in the class struct; -//! the source for this is the `*Impl` trait; the associated consts and -//! functions if needed are wrapped to map C types into Rust types. -//! //! * a trait for instance methods, for example `DeviceMethods`. This trait is //! automatically implemented for any reference or smart pointer to a device //! instance. It calls into the vtable provides access across all subclasses @@ -54,6 +49,48 @@ //! This provides access to class-wide functionality that doesn't depend on //! instance data. Like instance methods, these are automatically inherited by //! child classes. +//! +//! # Class structures +//! +//! Each QOM class that has virtual methods describes them in a +//! _class struct_. Class structs include a parent field corresponding +//! to the vtable of the parent class, all the way up to [`ObjectClass`]. +//! +//! As mentioned above, virtual methods are defined via traits such as +//! `DeviceImpl`. Class structs do not define any trait but, conventionally, +//! all of them have a `class_init` method to initialize the virtual methods +//! based on the trait and then call the same method on the superclass. +//! +//! ```ignore +//! impl YourSubclassClass +//! { +//! pub fn class_init(&mut self) { +//! ... +//! klass.parent_class::class_init(); +//! } +//! } +//! ``` +//! +//! If a class implements a QOM interface. In that case, the function must +//! contain, for each interface, an extra forwarding call as follows: +//! +//! ```ignore +//! ResettableClass::cast::(self).class_init::(); +//! ``` +//! +//! These `class_init` functions are methods on the class rather than a trait, +//! because the bound on `T` (`DeviceImpl` in this case), will change for every +//! class struct. The functions are pointed to by the +//! [`ObjectImpl::CLASS_INIT`] function pointer. While there is no default +//! implementation, in most cases it will be enough to write it as follows: +//! +//! ```ignore +//! const CLASS_INIT: fn(&mut Self::Class)> = Self::Class::class_init::; +//! ``` +//! +//! This design incurs a small amount of code duplication but, by not using +//! traits, it allows the flexibility of implementing bindings in any crate, +//! without incurring into violations of orphan rules for traits. use std::{ ffi::CStr, @@ -279,19 +316,25 @@ pub unsafe trait InterfaceType: Sized { /// for this interface. const TYPE_NAME: &'static CStr; - /// Initialize the vtable for the interface; the generic argument `T` is the - /// type being initialized, while the generic argument `U` is the type that + /// Return the vtable for the interface; `U` is the type that /// lists the interface in its `TypeInfo`. /// + /// # Examples + /// + /// This function is usually called by a `class_init` method in `U::Class`. + /// For example, `DeviceClass::class_init` initializes its `Resettable` + /// interface as follows: + /// + /// ```ignore + /// ResettableClass::cast::(self).class_init::(); + /// ``` + /// + /// where `T` is the concrete subclass that is being initialized. + /// /// # Panics /// /// Panic if the incoming argument if `T` does not implement the interface. - fn interface_init< - T: ObjectType + ClassInitImpl + ClassInitImpl, - U: ObjectType, - >( - klass: &mut U::Class, - ) { + fn cast(klass: &mut U::Class) -> &mut Self { unsafe { // SAFETY: upcasting to ObjectClass is always valid, and the // return type is either NULL or the argument itself @@ -300,8 +343,7 @@ pub unsafe trait InterfaceType: Sized { Self::TYPE_NAME.as_ptr(), ) .cast(); - - >::class_init(result.as_mut().unwrap()) + result.as_mut().unwrap() } } } @@ -558,87 +600,20 @@ pub trait ObjectImpl: ObjectType + IsA { /// the default values coming from the parent classes; the function /// can change them to override virtual methods of a parent class. /// - /// Usually defined as `::class_init`. - const CLASS_INIT: fn(&mut Self::Class); -} - -/// Internal trait used to automatically fill in a class struct. -/// -/// Each QOM class that has virtual methods describes them in a -/// _class struct_. Class structs include a parent field corresponding -/// to the vtable of the parent class, all the way up to [`ObjectClass`]. -/// Each QOM type has one such class struct; this trait takes care of -/// initializing the `T` part of the class struct, for the type that -/// implements the trait. -/// -/// Each struct will implement this trait with `T` equal to each -/// superclass. For example, a device should implement at least -/// `ClassInitImpl<`[`DeviceClass`](crate::qdev::DeviceClass)`>` and -/// `ClassInitImpl<`[`ObjectClass`]`>`. Such implementations are made -/// in one of two ways. -/// -/// For most superclasses, `ClassInitImpl` is provided by the `qemu-api` -/// crate itself. The Rust implementation of methods will come from a -/// trait like [`ObjectImpl`] or [`DeviceImpl`](crate::qdev::DeviceImpl), -/// and `ClassInitImpl` is provided by blanket implementations that -/// operate on all implementors of the `*Impl`* trait. For example: -/// -/// ```ignore -/// impl ClassInitImpl for T -/// where -/// T: ClassInitImpl + DeviceImpl, -/// ``` -/// -/// The bound on `ClassInitImpl` is needed so that, -/// after initializing the `DeviceClass` part of the class struct, -/// the parent [`ObjectClass`] is initialized as well. -/// -/// The other case is when manual implementation of the trait is needed. -/// This covers the following cases: -/// -/// * if a class implements a QOM interface, the Rust code _has_ to define its -/// own class struct `FooClass` and implement `ClassInitImpl`. -/// `ClassInitImpl`'s `class_init` method will then forward to -/// multiple other `class_init`s, for the interfaces as well as the -/// superclass. (Note that there is no Rust example yet for using interfaces). -/// -/// * for classes implemented outside the ``qemu-api`` crate, it's not possible -/// to add blanket implementations like the above one, due to orphan rules. In -/// that case, the easiest solution is to implement -/// `ClassInitImpl` for each subclass and not have a -/// `YourSuperclassImpl` trait at all. -/// -/// ```ignore -/// impl ClassInitImpl for YourSubclass { -/// fn class_init(klass: &mut YourSuperclass) { -/// klass.some_method = Some(Self::some_method); -/// >::class_init(&mut klass.parent_class); -/// } -/// } -/// ``` -/// -/// While this method incurs a small amount of code duplication, -/// it is generally limited to the recursive call on the last line. -/// This is because classes defined in Rust do not need the same -/// glue code that is needed when the classes are defined in C code. -/// You may consider using a macro if you have many subclasses. -pub trait ClassInitImpl { - /// Initialize `klass` to point to the virtual method implementations - /// for `Self`. On entry, the virtual method pointers are set to - /// the default values coming from the parent classes; the function - /// can change them to override virtual methods of a parent class. + /// Usually defined simply as `Self::Class::class_init::`; + /// however a default implementation cannot be included here, because the + /// bounds that the `Self::Class::class_init` method places on `Self` are + /// not known in advance. /// - /// The virtual method implementations usually come from another - /// trait, for example [`DeviceImpl`](crate::qdev::DeviceImpl) - /// when `T` is [`DeviceClass`](crate::qdev::DeviceClass). + /// # Safety /// - /// On entry, `klass`'s parent class is initialized, while the other fields + /// While `klass`'s parent class is initialized on entry, the other fields /// are all zero; it is therefore assumed that all fields in `T` can be /// zeroed, otherwise it would not be possible to provide the class as a /// `&mut T`. TODO: add a bound of [`Zeroable`](crate::zeroable::Zeroable) /// to T; this is more easily done once Zeroable does not require a manual /// implementation (Rust 1.75.0). - fn class_init(klass: &mut T); + const CLASS_INIT: fn(&mut Self::Class); } /// # Safety @@ -651,13 +626,12 @@ unsafe extern "C" fn rust_unparent_fn(dev: *mut Object) { T::UNPARENT.unwrap()(unsafe { state.as_ref() }); } -impl ClassInitImpl for T -where - T: ObjectImpl, -{ - fn class_init(oc: &mut ObjectClass) { +impl ObjectClass { + /// Fill in the virtual methods of `ObjectClass` based on the definitions in + /// the `ObjectImpl` trait. + pub fn class_init(&mut self) { if ::UNPARENT.is_some() { - oc.unparent = Some(rust_unparent_fn::); + self.unparent = Some(rust_unparent_fn::); } } } diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index fee2e3d478f7..04821a2b9b38 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -14,8 +14,8 @@ use crate::{ irq::{IRQState, InterruptSource}, memory::MemoryRegion, prelude::*, - qdev::{DeviceClass, DeviceImpl, DeviceState}, - qom::{ClassInitImpl, Owned}, + qdev::{DeviceImpl, DeviceState}, + qom::Owned, }; unsafe impl ObjectType for SysBusDevice { @@ -28,12 +28,11 @@ qom_isa!(SysBusDevice: DeviceState, Object); // TODO: add virtual methods pub trait SysBusDeviceImpl: DeviceImpl + IsA {} -impl ClassInitImpl for T -where - T: SysBusDeviceImpl + ClassInitImpl, -{ - fn class_init(sdc: &mut SysBusDeviceClass) { - >::class_init(&mut sdc.parent_class); +impl SysBusDeviceClass { + /// Fill in the virtual methods of `SysBusDeviceClass` based on the + /// definitions in the `SysBusDeviceImpl` trait. + pub fn class_init(self: &mut SysBusDeviceClass) { + self.parent_class.class_init::(); } } diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 93c5637bbc32..e3985782a388 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -14,7 +14,7 @@ use qemu_api::{ declare_properties, define_property, prelude::*, qdev::{DeviceImpl, DeviceState, Property, ResettablePhasesImpl}, - qom::{ClassInitImpl, ObjectImpl, ParentField}, + qom::{ObjectImpl, ParentField}, sysbus::SysBusDevice, vmstate::VMStateDescription, zeroable::Zeroable, @@ -43,7 +43,7 @@ pub struct DummyClass { impl DummyClass { pub fn class_init(self: &mut DummyClass) { - >::class_init(&mut self.parent_class); + self.parent_class.class_init::(); } } From 2152b4bfcd91d7d8b99cd502ed049b0ab8e38649 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 10 Jan 2025 22:51:12 +0800 Subject: [PATCH 0273/1179] i386/cpu: Support module level cache topology Allow cache to be defined at the module level. This increases flexibility for x86 users to customize their cache topology. Signed-off-by: Zhao Liu Tested-by: Yongwei Ma Reviewed-by: Jonathan Cameron Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/r/20250110145115.1574345-3-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 72ab147e851a..8799e22ed455 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -247,6 +247,9 @@ static uint32_t max_thread_ids_for_cache(X86CPUTopoInfo *topo_info, case CPU_TOPOLOGY_LEVEL_CORE: num_ids = 1 << apicid_core_offset(topo_info); break; + case CPU_TOPOLOGY_LEVEL_MODULE: + num_ids = 1 << apicid_module_offset(topo_info); + break; case CPU_TOPOLOGY_LEVEL_DIE: num_ids = 1 << apicid_die_offset(topo_info); break; @@ -255,7 +258,7 @@ static uint32_t max_thread_ids_for_cache(X86CPUTopoInfo *topo_info, break; default: /* - * Currently there is no use case for THREAD and MODULE, so use + * Currently there is no use case for THREAD, so use * assert directly to facilitate debugging. */ g_assert_not_reached(); From 5ca9282d25157004601c520ed59dcb380177f728 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 10 Jan 2025 22:51:13 +0800 Subject: [PATCH 0274/1179] i386/cpu: Update cache topology with machine's configuration User will configure smp cache topology via -machine smp-cache. For this case, update the x86 CPUs' cache topology with user's configuration in MachineState. Signed-off-by: Zhao Liu Tested-by: Yongwei Ma Reviewed-by: Jonathan Cameron Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/r/20250110145115.1574345-4-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 8799e22ed455..005ca4235df8 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7906,6 +7906,64 @@ static void x86_cpu_hyperv_realize(X86CPU *cpu) cpu->hyperv_limits[2] = 0; } +#ifndef CONFIG_USER_ONLY +static bool x86_cpu_update_smp_cache_topo(MachineState *ms, X86CPU *cpu, + Error **errp) +{ + CPUX86State *env = &cpu->env; + CpuTopologyLevel level; + + level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D); + if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { + env->cache_info_cpuid4.l1d_cache->share_level = level; + env->cache_info_amd.l1d_cache->share_level = level; + } else { + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D, + env->cache_info_cpuid4.l1d_cache->share_level); + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D, + env->cache_info_amd.l1d_cache->share_level); + } + + level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I); + if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { + env->cache_info_cpuid4.l1i_cache->share_level = level; + env->cache_info_amd.l1i_cache->share_level = level; + } else { + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I, + env->cache_info_cpuid4.l1i_cache->share_level); + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I, + env->cache_info_amd.l1i_cache->share_level); + } + + level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2); + if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { + env->cache_info_cpuid4.l2_cache->share_level = level; + env->cache_info_amd.l2_cache->share_level = level; + } else { + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2, + env->cache_info_cpuid4.l2_cache->share_level); + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2, + env->cache_info_amd.l2_cache->share_level); + } + + level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3); + if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { + env->cache_info_cpuid4.l3_cache->share_level = level; + env->cache_info_amd.l3_cache->share_level = level; + } else { + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3, + env->cache_info_cpuid4.l3_cache->share_level); + machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3, + env->cache_info_amd.l3_cache->share_level); + } + + if (!machine_check_smp_cache(ms, errp)) { + return false; + } + return true; +} +#endif + static void x86_cpu_realizefn(DeviceState *dev, Error **errp) { CPUState *cs = CPU(dev); @@ -8145,6 +8203,15 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) #ifndef CONFIG_USER_ONLY MachineState *ms = MACHINE(qdev_get_machine()); + + /* + * TODO: Add a SMPCompatProps.has_caches flag to avoid useless updates + * if user didn't set smp_cache. + */ + if (!x86_cpu_update_smp_cache_topo(ms, cpu, errp)) { + return; + } + qemu_register_reset(x86_cpu_machine_reset_cb, cpu); if (cpu->env.features[FEAT_1_EDX] & CPUID_APIC || ms->smp.cpus > 1) { From 90df2cac3700188acadd12948fdad8e9b1459646 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Fri, 10 Jan 2025 22:51:14 +0800 Subject: [PATCH 0275/1179] i386/pc: Support cache topology in -machine for PC machine Allow user to configure l1d, l1i, l2 and l3 cache topologies for PC machine. Additionally, add the document of "-machine smp-cache" in qemu-options.hx. Signed-off-by: Zhao Liu Tested-by: Yongwei Ma Reviewed-by: Jonathan Cameron Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/r/20250110145115.1574345-5-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- hw/i386/pc.c | 4 ++++ qemu-options.hx | 30 +++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/hw/i386/pc.c b/hw/i386/pc.c index f199a8c7ad19..63a96cd23f84 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1798,6 +1798,10 @@ static void pc_machine_class_init(ObjectClass *oc, void *data) mc->nvdimm_supported = true; mc->smp_props.dies_supported = true; mc->smp_props.modules_supported = true; + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L1D] = true; + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L1I] = true; + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L2] = true; + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L3] = true; mc->default_ram_id = "pc.ram"; pcmc->default_smbios_ep_type = SMBIOS_ENTRY_POINT_TYPE_AUTO; diff --git a/qemu-options.hx b/qemu-options.hx index 61270e320670..dc694a99a30a 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -42,7 +42,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ " aux-ram-share=on|off allocate auxiliary guest RAM as shared (default: off)\n" #endif " memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n" - " cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n", + " cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n" + " smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n", QEMU_ARCH_ALL) SRST ``-machine [type=]name[,prop=value[,...]]`` @@ -172,6 +173,33 @@ SRST :: -machine cxl-fmw.0.targets.0=cxl.0,cxl-fmw.0.targets.1=cxl.1,cxl-fmw.0.size=128G,cxl-fmw.0.interleave-granularity=512 + + ``smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel`` + Define cache properties for SMP system. + + ``cache=cachename`` specifies the cache that the properties will be + applied on. This field is the combination of cache level and cache + type. It supports ``l1d`` (L1 data cache), ``l1i`` (L1 instruction + cache), ``l2`` (L2 unified cache) and ``l3`` (L3 unified cache). + + ``topology=topologylevel`` sets the cache topology level. It accepts + CPU topology levels including ``core``, ``module``, ``cluster``, ``die``, + ``socket``, ``book``, ``drawer`` and a special value ``default``. If + ``default`` is set, then the cache topology will follow the architecture's + default cache topology model. If another topology level is set, the cache + will be shared at corresponding CPU topology level. For example, + ``topology=core`` makes the cache shared by all threads within a core. + The omitting cache will default to using the ``default`` level. + + The default cache topology model for an i386 PC machine is as follows: + ``l1d``, ``l1i``, and ``l2`` caches are per ``core``, while the ``l3`` + cache is per ``die``. + + Example: + + :: + + -machine smp-cache.0.cache=l1d,smp-cache.0.topology=core,smp-cache.1.cache=l1i,smp-cache.1.topology=core ERST DEF("M", HAS_ARG, QEMU_OPTION_M, From 47fc56f36d333263a5865caad306336e3e61e348 Mon Sep 17 00:00:00 2001 From: Alireza Sanaee Date: Fri, 10 Jan 2025 22:51:15 +0800 Subject: [PATCH 0276/1179] i386/cpu: add has_caches flag to check smp_cache configuration Add has_caches flag to SMPCompatProps, which helps in avoiding extra checks for every single layer of caches in x86 (and ARM in future). Signed-off-by: Alireza Sanaee Signed-off-by: Zhao Liu Reviewed-by: Jonathan Cameron Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/r/20250110145115.1574345-6-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- hw/core/machine-smp.c | 2 ++ include/hw/boards.h | 3 +++ target/i386/cpu.c | 11 +++++------ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/hw/core/machine-smp.c b/hw/core/machine-smp.c index 4e020c358b66..0be0ac044c22 100644 --- a/hw/core/machine-smp.c +++ b/hw/core/machine-smp.c @@ -332,6 +332,8 @@ bool machine_parse_smp_cache(MachineState *ms, return false; } } + + mc->smp_props.has_caches = true; return true; } diff --git a/include/hw/boards.h b/include/hw/boards.h index 9360d1ce394a..f22b2e7fc75b 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -156,6 +156,8 @@ typedef struct { * @modules_supported - whether modules are supported by the machine * @cache_supported - whether cache (l1d, l1i, l2 and l3) configuration are * supported by the machine + * @has_caches - whether cache properties are explicitly specified in the + * user provided smp-cache configuration */ typedef struct { bool prefer_sockets; @@ -166,6 +168,7 @@ typedef struct { bool drawers_supported; bool modules_supported; bool cache_supported[CACHE_LEVEL_AND_TYPE__MAX]; + bool has_caches; } SMPCompatProps; /** diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 005ca4235df8..2f9c604552b2 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8203,13 +8203,12 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) #ifndef CONFIG_USER_ONLY MachineState *ms = MACHINE(qdev_get_machine()); + MachineClass *mc = MACHINE_GET_CLASS(ms); - /* - * TODO: Add a SMPCompatProps.has_caches flag to avoid useless updates - * if user didn't set smp_cache. - */ - if (!x86_cpu_update_smp_cache_topo(ms, cpu, errp)) { - return; + if (mc->smp_props.has_caches) { + if (!x86_cpu_update_smp_cache_topo(ms, cpu, errp)) { + return; + } } qemu_register_reset(x86_cpu_machine_reset_cb, cpu); From 4044f46978ca6c55e5fcdda84310d7435c7c26ac Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 6 Feb 2025 17:44:20 +0100 Subject: [PATCH 0277/1179] target/riscv: remove unused macro DEFINE_CPU Signed-off-by: Paolo Bonzini --- target/riscv/cpu.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index cca24b9f1fc9..0dcde546e472 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -3051,15 +3051,6 @@ void riscv_isa_write_fdt(RISCVCPU *cpu, void *fdt, char *nodename) } #endif -#define DEFINE_CPU(type_name, misa_mxl_max, initfn) \ - { \ - .name = (type_name), \ - .parent = TYPE_RISCV_CPU, \ - .instance_init = (initfn), \ - .class_init = riscv_cpu_class_init, \ - .class_data = (void *)(misa_mxl_max) \ - } - #define DEFINE_DYNAMIC_CPU(type_name, misa_mxl_max, initfn) \ { \ .name = (type_name), \ From aeb7969cba971472aba7a3bf1e0df1bcc1b6f44c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 6 Feb 2025 14:54:50 +0100 Subject: [PATCH 0278/1179] target/riscv: move 128-bit check to TCG realize Besides removing non-declarative code in instance_init, this also fixes an issue with query-cpu-model-expansion. Just invoking it for the x-rv128 CPU model causes QEMU to exit immediately. With this patch it is possible to do {'execute': 'query-cpu-model-expansion', 'arguments':{'type': 'full', 'model': {'name': 'x-rv128'}}} Signed-off-by: Paolo Bonzini --- target/riscv/cpu.c | 7 ------- target/riscv/tcg/tcg-cpu.c | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 0dcde546e472..d7ecf729d05b 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -700,13 +700,6 @@ static void rv128_base_cpu_init(Object *obj) RISCVCPU *cpu = RISCV_CPU(obj); CPURISCVState *env = &cpu->env; - if (qemu_tcg_mttcg_enabled()) { - /* Missing 128-bit aligned atomics */ - error_report("128-bit RISC-V currently does not work with Multi " - "Threaded TCG. Please use: -accel tcg,thread=single"); - exit(EXIT_FAILURE); - } - cpu->cfg.mmu = true; cpu->cfg.pmp = true; diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index 0a137281de15..d7e694fdb3d7 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -1014,6 +1014,7 @@ static bool riscv_cpu_is_generic(Object *cpu_obj) static bool riscv_tcg_cpu_realize(CPUState *cs, Error **errp) { RISCVCPU *cpu = RISCV_CPU(cs); + RISCVCPUClass *mcc = RISCV_CPU_GET_CLASS(cpu); if (!riscv_cpu_tcg_compatible(cpu)) { g_autofree char *name = riscv_cpu_get_name(cpu); @@ -1022,6 +1023,14 @@ static bool riscv_tcg_cpu_realize(CPUState *cs, Error **errp) return false; } + if (mcc->misa_mxl_max >= MXL_RV128 && qemu_tcg_mttcg_enabled()) { + /* Missing 128-bit aligned atomics */ + error_setg(errp, + "128-bit RISC-V currently does not work with Multi " + "Threaded TCG. Please use: -accel tcg,thread=single"); + return false; + } + #ifndef CONFIG_USER_ONLY CPURISCVState *env = &cpu->env; From bb09b7bfd37024381970744c71646e0239428897 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 6 Feb 2025 15:12:14 +0000 Subject: [PATCH 0279/1179] hw/core/machine.c: Make -machine dumpdtb=file.dtb with no DTB an error Currently if the user requests via -machine dumpdtb=file.dtb that we dump the DTB, but the machine doesn't have a DTB, we silently ignore the option. This is confusing to users, and is a legacy of the old board-specific implementation of the option, where if the execution codepath didn't go via a call to qemu_fdt_dumpdtb() we would never handle the option. Now we handle the option in one place in machine.c, we can provide the user with a useful message if they asked us to dump a DTB when none exists. qmp_dumpdtb() already produces this error; remove the logic in handle_machine_dumpdtb() that was there specifically to avoid hitting it. While we're here, beef up the error message a bit with a hint, and make it consistent about "an FDT" rather than "a FDT". (In the qmp_dumpdtb() case this needs an ERRP_GUARD to make error_append_hint() work when the caller passes error_fatal.) Note that the three places where we might report "doesn't have an FDT" are hit in different situations: (1) in handle_machine_dumpdtb(), if CONFIG_FDT is not set: this is because the QEMU binary was built without libfdt at all. The build system will not let you build with a machine type that needs an FDT but no libfdt, so here we know both that the machine doesn't use FDT and that QEMU doesn't have the support: (2) in the device_tree-stub.c qmp_dumpdtb(): this is used when we had libfdt at build time but the target architecture didn't enable any machines which did "select DEVICE_TREE", so here we know that the machine doesn't use FDT. (3) in qmp_dumpdtb(), if current_machine->fdt is NULL all we know is that this machine never set it. That might be because it doesn't use FDT, or it might be because the user didn't pass an FDT on the command line and the machine doesn't autogenerate an FDT. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2733 Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250206151214.2947842-7-peter.maydell@linaro.org --- hw/core/machine.c | 6 ++---- system/device_tree-stub.c | 5 ++++- system/device_tree.c | 7 ++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/hw/core/machine.c b/hw/core/machine.c index 61c22f723a0f..b68b8b94a3c4 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -1702,15 +1702,13 @@ static void handle_machine_dumpdtb(MachineState *ms) if (!ms->dumpdtb) { return; } - if (!ms->fdt) { - /* Silently ignore dumpdtb option if there is nothing to dump */ - return; - } #ifdef CONFIG_FDT qmp_dumpdtb(ms->dumpdtb, &error_fatal); exit(0); #else error_report("This machine doesn't have an FDT"); + error_printf("(this machine type definitely doesn't use FDT, and " + "this QEMU doesn't have FDT support compiled in)\n"); exit(1); #endif } diff --git a/system/device_tree-stub.c b/system/device_tree-stub.c index bddda6fa37a7..428330b0feca 100644 --- a/system/device_tree-stub.c +++ b/system/device_tree-stub.c @@ -5,6 +5,9 @@ #ifdef CONFIG_FDT void qmp_dumpdtb(const char *filename, Error **errp) { - error_setg(errp, "This machine doesn't have a FDT"); + ERRP_GUARD(); + + error_setg(errp, "This machine doesn't have an FDT"); + error_append_hint(errp, "(this machine type definitely doesn't use FDT)\n"); } #endif diff --git a/system/device_tree.c b/system/device_tree.c index d605ed2a2176..aa3fe9516f36 100644 --- a/system/device_tree.c +++ b/system/device_tree.c @@ -635,11 +635,16 @@ int qemu_fdt_setprop_sized_cells_from_array(void *fdt, void qmp_dumpdtb(const char *filename, Error **errp) { + ERRP_GUARD(); + g_autoptr(GError) err = NULL; uint32_t size; if (!current_machine->fdt) { - error_setg(errp, "This machine doesn't have a FDT"); + error_setg(errp, "This machine doesn't have an FDT"); + error_append_hint(errp, + "(Perhaps it doesn't support FDT at all, or perhaps " + "you need to provide an FDT with the -fdt option?)\n"); return; } From 9b6e986e280f43e3df3baf7aae2069d599b5056c Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:13 +0000 Subject: [PATCH 0280/1179] fpu: Make targets specify floatx80 default Inf at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we hardcode at compile time whether the floatx80 default Infinity value has the explicit integer bit set or not (x86 sets it; m68k does not). To be able to compile softfloat once for all targets we'd like to move this setting to runtime. Define a new FloatX80Behaviour enum which is a set of flags that define the target's floatx80 handling. Initially we define just one flag, for whether the default Infinity has the Integer bit set or not, but we will expand this in future commits to cover the other floatx80 target specifics that we currently make compile-time settings. Define a new function floatx80_default_inf() which returns the appropriate default Infinity value of the given sign, and use it in the code that was previously directly using the compile-time constant floatx80_infinity_{low,high} values when packing an infinity into a floatx80. Since floatx80 is highly unlikely to be supported in any new architecture, and the existing code is generally written as "default to like x87, with an ifdef for m68k", we make the default value for the floatx80 behaviour flags be "what x87 does". This means we only need to change the m68k target to specify the behaviour flags. (Other users of floatx80 are the Arm NWFPE emulation, which is obsolete and probably not actually doing the right thing anyway, and the PPC xsrqpxp insn. Making the default be "like x87" avoids our needing to review and test for behaviour changes there.) We will clean up the remaining uses of the floatx80_infinity global constant in subsequent commits. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-2-peter.maydell@linaro.org Message-id: 20250217125055.160887-2-peter.maydell@linaro.org --- fpu/softfloat-specialize.c.inc | 10 ++++++++++ fpu/softfloat.c | 7 +++---- include/fpu/softfloat-helpers.h | 12 ++++++++++++ include/fpu/softfloat-types.h | 13 +++++++++++++ include/fpu/softfloat.h | 1 + target/m68k/cpu.c | 6 ++++++ 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/fpu/softfloat-specialize.c.inc b/fpu/softfloat-specialize.c.inc index cbbbab52ba39..73789e97d773 100644 --- a/fpu/softfloat-specialize.c.inc +++ b/fpu/softfloat-specialize.c.inc @@ -227,6 +227,16 @@ floatx80 floatx80_default_nan(float_status *status) | The pattern for a default generated extended double-precision inf. *----------------------------------------------------------------------------*/ +floatx80 floatx80_default_inf(bool zSign, float_status *status) +{ + /* + * Whether the Integer bit is set in the default Infinity is + * target dependent. + */ + bool z = status->floatx80_behaviour & floatx80_default_inf_int_bit_is_zero; + return packFloatx80(zSign, 0x7fff, z ? 0 : (1ULL << 63)); +} + #define floatx80_infinity_high 0x7FFF #if defined(TARGET_M68K) #define floatx80_infinity_low UINT64_C(0x0000000000000000) diff --git a/fpu/softfloat.c b/fpu/softfloat.c index f4fed9bfda92..b12ad2b42a97 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -1860,7 +1860,8 @@ static floatx80 floatx80_round_pack_canonical(FloatParts128 *p, case float_class_inf: /* x86 and m68k differ in the setting of the integer bit. */ - frac = floatx80_infinity_low; + frac = s->floatx80_behaviour & floatx80_default_inf_int_bit_is_zero ? + 0 : (1ULL << 63); exp = fmt->exp_max; break; @@ -5144,9 +5145,7 @@ floatx80 roundAndPackFloatx80(FloatX80RoundPrec roundingPrecision, bool zSign, ) { return packFloatx80( zSign, 0x7FFE, ~ roundMask ); } - return packFloatx80(zSign, - floatx80_infinity_high, - floatx80_infinity_low); + return floatx80_default_inf(zSign, status); } if ( zExp <= 0 ) { isTiny = status->tininess_before_rounding diff --git a/include/fpu/softfloat-helpers.h b/include/fpu/softfloat-helpers.h index 8983c2748ecd..90862f5cd227 100644 --- a/include/fpu/softfloat-helpers.h +++ b/include/fpu/softfloat-helpers.h @@ -75,6 +75,12 @@ static inline void set_floatx80_rounding_precision(FloatX80RoundPrec val, status->floatx80_rounding_precision = val; } +static inline void set_floatx80_behaviour(FloatX80Behaviour b, + float_status *status) +{ + status->floatx80_behaviour = b; +} + static inline void set_float_2nan_prop_rule(Float2NaNPropRule rule, float_status *status) { @@ -151,6 +157,12 @@ get_floatx80_rounding_precision(const float_status *status) return status->floatx80_rounding_precision; } +static inline FloatX80Behaviour +get_floatx80_behaviour(const float_status *status) +{ + return status->floatx80_behaviour; +} + static inline Float2NaNPropRule get_float_2nan_prop_rule(const float_status *status) { diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h index 53d5eb85210e..dd22ecdbe60c 100644 --- a/include/fpu/softfloat-types.h +++ b/include/fpu/softfloat-types.h @@ -320,6 +320,18 @@ typedef enum __attribute__((__packed__)) { float_ftz_before_rounding = 1, } FloatFTZDetection; +/* + * floatx80 is primarily used by x86 and m68k, and there are + * differences in the handling, largely related to the explicit + * Integer bit which floatx80 has and the other float formats do not. + * These flag values allow specification of the target's requirements + * and can be ORed together to set floatx80_behaviour. + */ +typedef enum __attribute__((__packed__)) { + /* In the default Infinity value, is the Integer bit 0 ? */ + floatx80_default_inf_int_bit_is_zero = 1, +} FloatX80Behaviour; + /* * Floating Point Status. Individual architectures may maintain * several versions of float_status for different functions. The @@ -331,6 +343,7 @@ typedef struct float_status { uint16_t float_exception_flags; FloatRoundMode float_rounding_mode; FloatX80RoundPrec floatx80_rounding_precision; + FloatX80Behaviour floatx80_behaviour; Float2NaNPropRule float_2nan_prop_rule; Float3NaNPropRule float_3nan_prop_rule; FloatInfZeroNaNRule float_infzeronan_rule; diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h index 09a40b431063..afae3906024b 100644 --- a/include/fpu/softfloat.h +++ b/include/fpu/softfloat.h @@ -961,6 +961,7 @@ float128 floatx80_to_float128(floatx80, float_status *status); | The pattern for an extended double-precision inf. *----------------------------------------------------------------------------*/ extern const floatx80 floatx80_infinity; +floatx80 floatx80_default_inf(bool zSign, float_status *status); /*---------------------------------------------------------------------------- | Software IEC/IEEE extended double-precision operations. diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index 41dfdf580451..df66e8ba22ab 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -107,6 +107,12 @@ static void m68k_cpu_reset_hold(Object *obj, ResetType type) set_float_2nan_prop_rule(float_2nan_prop_ab, &env->fp_status); /* Default NaN: sign bit clear, all frac bits set */ set_float_default_nan_pattern(0b01111111, &env->fp_status); + /* + * m68k-specific floatx80 behaviour: + * * default Infinity values have a zero Integer bit + */ + set_floatx80_behaviour(floatx80_default_inf_int_bit_is_zero, + &env->fp_status); nan = floatx80_default_nan(&env->fp_status); for (i = 0; i < 8; i++) { From e456d4465b630ab2eed08611c6193f0a880e0ea3 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:14 +0000 Subject: [PATCH 0281/1179] target/m68k: Avoid using floatx80_infinity global const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global const floatx80_infinity is (unlike all the other float*_infinity values) target-specific, because whether the explicit Integer bit is set or not varies between m68k and i386. We want to be able to compile softfloat once for multiple targets, so we can't continue to use a single global whose value needs to be different between targets. Replace the direct uses of floatx80_infinity in target/m68k with calls to the new floatx80_default_inf() function. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-3-peter.maydell@linaro.org Message-id: 20250217125055.160887-3-peter.maydell@linaro.org --- target/m68k/softfloat.c | 47 ++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/target/m68k/softfloat.c b/target/m68k/softfloat.c index 02dcc03d15d8..d1f150e641fd 100644 --- a/target/m68k/softfloat.c +++ b/target/m68k/softfloat.c @@ -142,8 +142,7 @@ floatx80 floatx80_scale(floatx80 a, floatx80 b, float_status *status) if ((uint64_t) (aSig << 1)) { return propagateFloatx80NaN(a, b, status); } - return packFloatx80(aSign, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(aSign, status); } if (aExp == 0) { if (aSig == 0) { @@ -245,7 +244,7 @@ floatx80 floatx80_lognp1(floatx80 a, float_status *status) float_raise(float_flag_invalid, status); return floatx80_default_nan(status); } - return packFloatx80(0, floatx80_infinity.high, floatx80_infinity.low); + return floatx80_default_inf(0, status); } if (aExp == 0 && aSig == 0) { @@ -255,8 +254,7 @@ floatx80 floatx80_lognp1(floatx80 a, float_status *status) if (aSign && aExp >= one_exp) { if (aExp == one_exp && aSig == one_sig) { float_raise(float_flag_divbyzero, status); - return packFloatx80(aSign, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(aSign, status); } float_raise(float_flag_invalid, status); return floatx80_default_nan(status); @@ -442,8 +440,7 @@ floatx80 floatx80_logn(floatx80 a, float_status *status) propagateFloatx80NaNOneArg(a, status); } if (aSign == 0) { - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } } @@ -452,8 +449,7 @@ floatx80 floatx80_logn(floatx80 a, float_status *status) if (aExp == 0) { if (aSig == 0) { /* zero */ float_raise(float_flag_divbyzero, status); - return packFloatx80(1, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(1, status); } if ((aSig & one_sig) == 0) { /* denormal */ normalizeFloatx80Subnormal(aSig, &aExp, &aSig); @@ -610,15 +606,13 @@ floatx80 floatx80_log10(floatx80 a, float_status *status) propagateFloatx80NaNOneArg(a, status); } if (aSign == 0) { - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } } if (aExp == 0 && aSig == 0) { float_raise(float_flag_divbyzero, status); - return packFloatx80(1, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(1, status); } if (aSign) { @@ -668,16 +662,14 @@ floatx80 floatx80_log2(floatx80 a, float_status *status) propagateFloatx80NaNOneArg(a, status); } if (aSign == 0) { - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } } if (aExp == 0) { if (aSig == 0) { float_raise(float_flag_divbyzero, status); - return packFloatx80(1, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(1, status); } normalizeFloatx80Subnormal(aSig, &aExp, &aSig); } @@ -740,8 +732,7 @@ floatx80 floatx80_etox(floatx80 a, float_status *status) if (aSign) { return packFloatx80(0, 0, 0); } - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } if (aExp == 0 && aSig == 0) { @@ -924,8 +915,7 @@ floatx80 floatx80_twotox(floatx80 a, float_status *status) if (aSign) { return packFloatx80(0, 0, 0); } - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } if (aExp == 0 && aSig == 0) { @@ -1075,8 +1065,7 @@ floatx80 floatx80_tentox(floatx80 a, float_status *status) if (aSign) { return packFloatx80(0, 0, 0); } - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } if (aExp == 0 && aSig == 0) { @@ -2260,8 +2249,7 @@ floatx80 floatx80_atanh(floatx80 a, float_status *status) if (compact >= 0x3FFF8000) { /* |X| >= 1 */ if (aExp == one_exp && aSig == one_sig) { /* |X| == 1 */ float_raise(float_flag_divbyzero, status); - return packFloatx80(aSign, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(aSign, status); } else { /* |X| > 1 */ float_raise(float_flag_invalid, status); return floatx80_default_nan(status); @@ -2320,8 +2308,7 @@ floatx80 floatx80_etoxm1(floatx80 a, float_status *status) if (aSign) { return packFloatx80(aSign, one_exp, one_sig); } - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } if (aExp == 0 && aSig == 0) { @@ -2687,8 +2674,7 @@ floatx80 floatx80_sinh(floatx80 a, float_status *status) if ((uint64_t) (aSig << 1)) { return propagateFloatx80NaNOneArg(a, status); } - return packFloatx80(aSign, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(aSign, status); } if (aExp == 0 && aSig == 0) { @@ -2774,8 +2760,7 @@ floatx80 floatx80_cosh(floatx80 a, float_status *status) if ((uint64_t) (aSig << 1)) { return propagateFloatx80NaNOneArg(a, status); } - return packFloatx80(0, floatx80_infinity.high, - floatx80_infinity.low); + return floatx80_default_inf(0, status); } if (aExp == 0 && aSig == 0) { From 165ce008d734bc0024dabdbfd1c41738bc5b834f Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:15 +0000 Subject: [PATCH 0282/1179] target/i386: Avoid using floatx80_infinity global const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global const floatx80_infinity is (unlike all the other float*_infinity values) target-specific, because whether the explicit Integer bit is set or not varies between m68k and i386. We want to be able to compile softfloat once for multiple targets, so we can't continue to use a single global whose value needs to be different between targets. Replace the direct uses of floatx80_infinity in target/i386 with calls to the new floatx80_default_inf() function. Note that because we can ask the function for either a negative or positive infinity, we don't need to change the sign of a positive infinity via floatx80_chs() for the negative-Inf case. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-4-peter.maydell@linaro.org Message-id: 20250217125055.160887-4-peter.maydell@linaro.org --- target/i386/tcg/fpu_helper.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index f112c6c67376..741af09f9084 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -1832,7 +1832,7 @@ void helper_fxtract(CPUX86State *env) } else if (floatx80_is_infinity(ST0)) { fpush(env); ST0 = ST1; - ST1 = floatx80_infinity; + ST1 = floatx80_default_inf(0, &env->fp_status); } else { int expdif; @@ -2358,9 +2358,8 @@ void helper_fscale(CPUX86State *env) float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_default_nan(&env->fp_status); } else { - ST0 = (floatx80_is_neg(ST0) ? - floatx80_chs(floatx80_infinity) : - floatx80_infinity); + ST0 = floatx80_default_inf(floatx80_is_neg(ST0), + &env->fp_status); } } } else { From 9ea6d1f141426a7da91f1c7ba3d693472f0550a4 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:16 +0000 Subject: [PATCH 0283/1179] fpu: Pass float_status to floatx80_is_infinity() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike the other float formats, whether a floatx80 value is considered to be an Infinity is target-dependent. (On x86 if the explicit integer bit is clear this is a "pseudo-infinity" and not a valid infinity; m68k does not care about the value of the integer bit.) Currently we select this target-specific logic at compile time with an ifdef. We're going to want to do this at runtime, so change the floatx80_is_infinity() function to take a float_status. This commit doesn't change any logic; we'll do that in the next commit. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-5-peter.maydell@linaro.org --- include/fpu/softfloat.h | 2 +- target/i386/tcg/fpu_helper.c | 20 +++++++++++--------- target/m68k/fpu_helper.c | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h index afae3906024b..3c83d703baf8 100644 --- a/include/fpu/softfloat.h +++ b/include/fpu/softfloat.h @@ -996,7 +996,7 @@ static inline floatx80 floatx80_chs(floatx80 a) return a; } -static inline bool floatx80_is_infinity(floatx80 a) +static inline bool floatx80_is_infinity(floatx80 a, float_status *status) { #if defined(TARGET_M68K) return (a.high & 0x7fff) == floatx80_infinity.high && !(a.low << 1); diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index 741af09f9084..3b79bc049d13 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -1393,7 +1393,8 @@ void helper_fpatan(CPUX86State *env) /* Pass this NaN through. */ } else if (floatx80_is_zero(ST1) && !arg0_sign) { /* Pass this zero through. */ - } else if (((floatx80_is_infinity(ST0) && !floatx80_is_infinity(ST1)) || + } else if (((floatx80_is_infinity(ST0, &env->fp_status) && + !floatx80_is_infinity(ST1, &env->fp_status)) || arg0_exp - arg1_exp >= 80) && !arg0_sign) { /* @@ -1442,8 +1443,8 @@ void helper_fpatan(CPUX86State *env) rexp = pi_exp; rsig0 = pi_sig_high; rsig1 = pi_sig_low; - } else if (floatx80_is_infinity(ST1)) { - if (floatx80_is_infinity(ST0)) { + } else if (floatx80_is_infinity(ST1, &env->fp_status)) { + if (floatx80_is_infinity(ST0, &env->fp_status)) { if (arg0_sign) { rexp = pi_34_exp; rsig0 = pi_34_sig_high; @@ -1462,7 +1463,8 @@ void helper_fpatan(CPUX86State *env) rexp = pi_2_exp; rsig0 = pi_2_sig_high; rsig1 = pi_2_sig_low; - } else if (floatx80_is_infinity(ST0) || arg0_exp - arg1_exp >= 80) { + } else if (floatx80_is_infinity(ST0, &env->fp_status) || + arg0_exp - arg1_exp >= 80) { /* ST0 is negative. */ rexp = pi_exp; rsig0 = pi_sig_high; @@ -1829,7 +1831,7 @@ void helper_fxtract(CPUX86State *env) } fpush(env); ST0 = ST1; - } else if (floatx80_is_infinity(ST0)) { + } else if (floatx80_is_infinity(ST0, &env->fp_status)) { fpush(env); ST0 = ST1; ST1 = floatx80_default_inf(0, &env->fp_status); @@ -2173,7 +2175,7 @@ void helper_fyl2x(CPUX86State *env) } else if (arg0_sign && !floatx80_is_zero(ST0)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_default_nan(&env->fp_status); - } else if (floatx80_is_infinity(ST1)) { + } else if (floatx80_is_infinity(ST1, &env->fp_status)) { FloatRelation cmp = floatx80_compare(ST0, floatx80_one, &env->fp_status); switch (cmp) { @@ -2188,7 +2190,7 @@ void helper_fyl2x(CPUX86State *env) ST1 = floatx80_default_nan(&env->fp_status); break; } - } else if (floatx80_is_infinity(ST0)) { + } else if (floatx80_is_infinity(ST0, &env->fp_status)) { if (floatx80_is_zero(ST1)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_default_nan(&env->fp_status); @@ -2341,11 +2343,11 @@ void helper_fscale(CPUX86State *env) float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_silence_nan(ST0, &env->fp_status); } - } else if (floatx80_is_infinity(ST1) && + } else if (floatx80_is_infinity(ST1, &env->fp_status) && !floatx80_invalid_encoding(ST0) && !floatx80_is_any_nan(ST0)) { if (floatx80_is_neg(ST1)) { - if (floatx80_is_infinity(ST0)) { + if (floatx80_is_infinity(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_default_nan(&env->fp_status); } else { diff --git a/target/m68k/fpu_helper.c b/target/m68k/fpu_helper.c index 339b73ad7dc1..eb1cb8c6872c 100644 --- a/target/m68k/fpu_helper.c +++ b/target/m68k/fpu_helper.c @@ -455,7 +455,7 @@ void HELPER(ftst)(CPUM68KState *env, FPReg *val) if (floatx80_is_any_nan(val->d)) { cc |= FPSR_CC_A; - } else if (floatx80_is_infinity(val->d)) { + } else if (floatx80_is_infinity(val->d, &env->fp_status)) { cc |= FPSR_CC_I; } else if (floatx80_is_zero(val->d)) { cc |= FPSR_CC_Z; From 44eb32a9835fe2feb19503e93476eee602daee0b Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:17 +0000 Subject: [PATCH 0284/1179] fpu: Make targets specify whether floatx80 Inf can have Int bit clear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Intel terminology, a floatx80 Infinity with the explicit integer bit clear is a "pseudo-infinity"; for x86 these are not valid infinity values. m68k is looser and does not care whether the Integer bit is set or clear in an infinity. Move this setting to runtime rather than using an ifdef in floatx80_is_infinity(). Since this was the last use of the floatx80_infinity global constant, we remove it and its definition here. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-6-peter.maydell@linaro.org Message-id: 20250217125055.160887-5-peter.maydell@linaro.org --- fpu/softfloat-specialize.c.inc | 10 ---------- include/fpu/softfloat-types.h | 5 +++++ include/fpu/softfloat.h | 18 +++++++++++------- target/m68k/cpu.c | 4 +++- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/fpu/softfloat-specialize.c.inc b/fpu/softfloat-specialize.c.inc index 73789e97d773..8327f7278613 100644 --- a/fpu/softfloat-specialize.c.inc +++ b/fpu/softfloat-specialize.c.inc @@ -237,16 +237,6 @@ floatx80 floatx80_default_inf(bool zSign, float_status *status) return packFloatx80(zSign, 0x7fff, z ? 0 : (1ULL << 63)); } -#define floatx80_infinity_high 0x7FFF -#if defined(TARGET_M68K) -#define floatx80_infinity_low UINT64_C(0x0000000000000000) -#else -#define floatx80_infinity_low UINT64_C(0x8000000000000000) -#endif - -const floatx80 floatx80_infinity - = make_floatx80_init(floatx80_infinity_high, floatx80_infinity_low); - /*---------------------------------------------------------------------------- | Returns 1 if the half-precision floating-point value `a' is a quiet | NaN; otherwise returns 0. diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h index dd22ecdbe60c..e1732beba4f6 100644 --- a/include/fpu/softfloat-types.h +++ b/include/fpu/softfloat-types.h @@ -330,6 +330,11 @@ typedef enum __attribute__((__packed__)) { typedef enum __attribute__((__packed__)) { /* In the default Infinity value, is the Integer bit 0 ? */ floatx80_default_inf_int_bit_is_zero = 1, + /* + * Are Pseudo-infinities (Inf with the Integer bit zero) valid? + * If so, floatx80_is_infinity() will return true for them. + */ + floatx80_pseudo_inf_valid = 2, } FloatX80Behaviour; /* diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h index 3c83d703baf8..07259c593033 100644 --- a/include/fpu/softfloat.h +++ b/include/fpu/softfloat.h @@ -960,7 +960,6 @@ float128 floatx80_to_float128(floatx80, float_status *status); /*---------------------------------------------------------------------------- | The pattern for an extended double-precision inf. *----------------------------------------------------------------------------*/ -extern const floatx80 floatx80_infinity; floatx80 floatx80_default_inf(bool zSign, float_status *status); /*---------------------------------------------------------------------------- @@ -998,12 +997,17 @@ static inline floatx80 floatx80_chs(floatx80 a) static inline bool floatx80_is_infinity(floatx80 a, float_status *status) { -#if defined(TARGET_M68K) - return (a.high & 0x7fff) == floatx80_infinity.high && !(a.low << 1); -#else - return (a.high & 0x7fff) == floatx80_infinity.high && - a.low == floatx80_infinity.low; -#endif + /* + * It's target-specific whether the Integer bit is permitted + * to be 0 in a valid Infinity value. (x86 says no, m68k says yes). + */ + bool intbit = a.low >> 63; + + if (!intbit && + !(status->floatx80_behaviour & floatx80_pseudo_inf_valid)) { + return false; + } + return (a.high & 0x7fff) == 0x7fff && !(a.low << 1); } static inline bool floatx80_is_neg(floatx80 a) diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index df66e8ba22ab..56b23de21fe2 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -110,8 +110,10 @@ static void m68k_cpu_reset_hold(Object *obj, ResetType type) /* * m68k-specific floatx80 behaviour: * * default Infinity values have a zero Integer bit + * * input Infinities may have the Integer bit either 0 or 1 */ - set_floatx80_behaviour(floatx80_default_inf_int_bit_is_zero, + set_floatx80_behaviour(floatx80_default_inf_int_bit_is_zero | + floatx80_pseudo_inf_valid, &env->fp_status); nan = floatx80_default_nan(&env->fp_status); From 765fe845ccb953b77b1b7e0557b13a7b760067b0 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:18 +0000 Subject: [PATCH 0285/1179] fpu: Pass float_status to floatx80_invalid_encoding() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The definition of which floatx80 encodings are invalid is target-specific. Currently we handle this with an ifdef, but we would like to defer this decision to runtime. In preparation, pass a float_status argument to floatx80_invalid_encoding(). We will change the implementation from ifdef to looking at the status argument in the following commit. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-7-peter.maydell@linaro.org --- fpu/softfloat.c | 2 +- include/fpu/softfloat.h | 2 +- target/i386/tcg/fpu_helper.c | 24 +++++++++++++----------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/fpu/softfloat.c b/fpu/softfloat.c index b12ad2b42a97..2a20ae871eb0 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -1810,7 +1810,7 @@ static bool floatx80_unpack_canonical(FloatParts128 *p, floatx80 f, g_assert_not_reached(); } - if (unlikely(floatx80_invalid_encoding(f))) { + if (unlikely(floatx80_invalid_encoding(f, s))) { float_raise(float_flag_invalid, s); return false; } diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h index 07259c593033..1c8f3cbb78dd 100644 --- a/include/fpu/softfloat.h +++ b/include/fpu/softfloat.h @@ -1081,7 +1081,7 @@ static inline bool floatx80_unordered_quiet(floatx80 a, floatx80 b, | pseudo-denormals, which must still be correctly handled as inputs even | if they are never generated as outputs. *----------------------------------------------------------------------------*/ -static inline bool floatx80_invalid_encoding(floatx80 a) +static inline bool floatx80_invalid_encoding(floatx80 a, float_status *s) { #if defined(TARGET_M68K) /*------------------------------------------------------------------------- diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index 3b79bc049d13..4858ae9a5fb2 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -1141,7 +1141,7 @@ void helper_f2xm1(CPUX86State *env) int32_t exp = extractFloatx80Exp(ST0); bool sign = extractFloatx80Sign(ST0); - if (floatx80_invalid_encoding(ST0)) { + if (floatx80_invalid_encoding(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST0)) { @@ -1383,8 +1383,8 @@ void helper_fpatan(CPUX86State *env) } else if (floatx80_is_signaling_nan(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST1, &env->fp_status); - } else if (floatx80_invalid_encoding(ST0) || - floatx80_invalid_encoding(ST1)) { + } else if (floatx80_invalid_encoding(ST0, &env->fp_status) || + floatx80_invalid_encoding(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST0)) { @@ -1819,7 +1819,7 @@ void helper_fxtract(CPUX86State *env) &env->fp_status); fpush(env); ST0 = temp.d; - } else if (floatx80_invalid_encoding(ST0)) { + } else if (floatx80_invalid_encoding(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_default_nan(&env->fp_status); fpush(env); @@ -1870,7 +1870,8 @@ static void helper_fprem_common(CPUX86State *env, bool mod) env->fpus &= ~0x4700; /* (C3,C2,C1,C0) <-- 0000 */ if (floatx80_is_zero(ST0) || floatx80_is_zero(ST1) || exp0 == 0x7fff || exp1 == 0x7fff || - floatx80_invalid_encoding(ST0) || floatx80_invalid_encoding(ST1)) { + floatx80_invalid_encoding(ST0, &env->fp_status) || + floatx80_invalid_encoding(ST1, &env->fp_status)) { ST0 = floatx80_modrem(ST0, ST1, mod, "ient, &env->fp_status); } else { if (exp0 == 0) { @@ -2066,8 +2067,8 @@ void helper_fyl2xp1(CPUX86State *env) } else if (floatx80_is_signaling_nan(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST1, &env->fp_status); - } else if (floatx80_invalid_encoding(ST0) || - floatx80_invalid_encoding(ST1)) { + } else if (floatx80_invalid_encoding(ST0, &env->fp_status) || + floatx80_invalid_encoding(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST0)) { @@ -2164,8 +2165,8 @@ void helper_fyl2x(CPUX86State *env) } else if (floatx80_is_signaling_nan(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST1, &env->fp_status); - } else if (floatx80_invalid_encoding(ST0) || - floatx80_invalid_encoding(ST1)) { + } else if (floatx80_invalid_encoding(ST0, &env->fp_status) || + floatx80_invalid_encoding(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST0)) { @@ -2331,7 +2332,8 @@ void helper_frndint(CPUX86State *env) void helper_fscale(CPUX86State *env) { uint8_t old_flags = save_exception_flags(env); - if (floatx80_invalid_encoding(ST1) || floatx80_invalid_encoding(ST0)) { + if (floatx80_invalid_encoding(ST1, &env->fp_status) || + floatx80_invalid_encoding(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST1)) { @@ -2344,7 +2346,7 @@ void helper_fscale(CPUX86State *env) ST0 = floatx80_silence_nan(ST0, &env->fp_status); } } else if (floatx80_is_infinity(ST1, &env->fp_status) && - !floatx80_invalid_encoding(ST0) && + !floatx80_invalid_encoding(ST0, &env->fp_status) && !floatx80_is_any_nan(ST0)) { if (floatx80_is_neg(ST1)) { if (floatx80_is_infinity(ST0, &env->fp_status)) { From a261d3e331ca06f4d92e689f2bee40d0a0cdee08 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:19 +0000 Subject: [PATCH 0286/1179] fpu: Make floatx80 invalid encoding settable at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because floatx80 has an explicit integer bit, this permits some odd encodings where the integer bit is not set correctly for the floating point value type. In In Intel terminology the categories are: exp == 0, int = 0, mantissa == 0 : zeroes exp == 0, int = 0, mantissa != 0 : denormals exp == 0, int = 1 : pseudo-denormals 0 < exp < 0x7fff, int = 0 : unnormals 0 < exp < 0x7fff, int = 1 : normals exp == 0x7fff, int = 0, mantissa == 0 : pseudo-infinities exp == 0x7fff, int = 1, mantissa == 0 : infinities exp == 0x7fff, int = 0, mantissa != 0 : pseudo-NaNs exp == 0x7fff, int = 1, mantissa == 0 : NaNs The usual IEEE cases of zero, denormal, normal, inf and NaN are always valid. x87 permits as input also pseudo-denormals. m68k permits all those and also pseudo-infinities, pseudo-NaNs and unnormals. Currently we have an ifdef in floatx80_invalid_encoding() to select the x86 vs m68k behaviour. Add new floatx80_behaviour flags to select whether pseudo-NaN and unnormal are valid, and use these (plus the existing pseudo_inf_valid flag) to decide whether these encodings are invalid at runtime. We leave pseudo-denormals as always-valid, since both x86 and m68k accept them. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-8-peter.maydell@linaro.org Message-id: 20250217125055.160887-6-peter.maydell@linaro.org --- include/fpu/softfloat-types.h | 14 ++++++++ include/fpu/softfloat.h | 68 ++++++++++++++++++----------------- target/m68k/cpu.c | 28 ++++++++++++++- 3 files changed, 77 insertions(+), 33 deletions(-) diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h index e1732beba4f6..b1941384aef7 100644 --- a/include/fpu/softfloat-types.h +++ b/include/fpu/softfloat-types.h @@ -333,8 +333,22 @@ typedef enum __attribute__((__packed__)) { /* * Are Pseudo-infinities (Inf with the Integer bit zero) valid? * If so, floatx80_is_infinity() will return true for them. + * If not, floatx80_invalid_encoding will return false for them, + * and using them as inputs to a float op will raise Invalid. */ floatx80_pseudo_inf_valid = 2, + /* + * Are Pseudo-NaNs (NaNs where the Integer bit is zero) valid? + * If not, floatx80_invalid_encoding() will return false for them, + * and using them as inputs to a float op will raise Invalid. + */ + floatx80_pseudo_nan_valid = 4, + /* + * Are Unnormals (0 < exp < 0x7fff, Integer bit zero) valid? + * If not, floatx80_invalid_encoding() will return false for them, + * and using them as inputs to a float op will raise Invalid. + */ + floatx80_unnormal_valid = 8, } FloatX80Behaviour; /* diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h index 1c8f3cbb78dd..c18ab2cb6097 100644 --- a/include/fpu/softfloat.h +++ b/include/fpu/softfloat.h @@ -1073,41 +1073,45 @@ static inline bool floatx80_unordered_quiet(floatx80 a, floatx80 b, /*---------------------------------------------------------------------------- | Return whether the given value is an invalid floatx80 encoding. -| Invalid floatx80 encodings arise when the integer bit is not set, but -| the exponent is not zero. The only times the integer bit is permitted to -| be zero is in subnormal numbers and the value zero. -| This includes what the Intel software developer's manual calls pseudo-NaNs, -| pseudo-infinities and un-normal numbers. It does not include -| pseudo-denormals, which must still be correctly handled as inputs even -| if they are never generated as outputs. +| Invalid floatx80 encodings may arise when the integer bit is not set +| correctly; this is target-specific. In Intel terminology the +| categories are: +| exp == 0, int = 0, mantissa == 0 : zeroes +| exp == 0, int = 0, mantissa != 0 : denormals +| exp == 0, int = 1 : pseudo-denormals +| 0 < exp < 0x7fff, int = 0 : unnormals +| 0 < exp < 0x7fff, int = 1 : normals +| exp == 0x7fff, int = 0, mantissa == 0 : pseudo-infinities +| exp == 0x7fff, int = 1, mantissa == 0 : infinities +| exp == 0x7fff, int = 0, mantissa != 0 : pseudo-NaNs +| exp == 0x7fff, int = 1, mantissa == 0 : NaNs +| +| The usual IEEE cases of zero, denormal, normal, inf and NaN are always valid. +| x87 permits as input also pseudo-denormals. +| m68k permits all those and also pseudo-infinities, pseudo-NaNs and unnormals. +| +| Since we don't have a target that handles floatx80 but prohibits +| pseudo-denormals in input, we don't currently have a floatx80_behaviour +| flag for that case, but instead always accept it. Conveniently this +| means that all cases with either exponent 0 or the integer bit set are +| valid for all targets. *----------------------------------------------------------------------------*/ static inline bool floatx80_invalid_encoding(floatx80 a, float_status *s) { -#if defined(TARGET_M68K) - /*------------------------------------------------------------------------- - | With m68k, the explicit integer bit can be zero in the case of: - | - zeros (exp == 0, mantissa == 0) - | - denormalized numbers (exp == 0, mantissa != 0) - | - unnormalized numbers (exp != 0, exp < 0x7FFF) - | - infinities (exp == 0x7FFF, mantissa == 0) - | - not-a-numbers (exp == 0x7FFF, mantissa != 0) - | - | For infinities and NaNs, the explicit integer bit can be either one or - | zero. - | - | The IEEE 754 standard does not define a zero integer bit. Such a number - | is an unnormalized number. Hardware does not directly support - | denormalized and unnormalized numbers, but implicitly supports them by - | trapping them as unimplemented data types, allowing efficient conversion - | in software. - | - | See "M68000 FAMILY PROGRAMMER’S REFERENCE MANUAL", - | "1.6 FLOATING-POINT DATA TYPES" - *------------------------------------------------------------------------*/ - return false; -#else - return (a.low & (1ULL << 63)) == 0 && (a.high & 0x7FFF) != 0; -#endif + if ((a.low >> 63) || (a.high & 0x7fff) == 0) { + /* Anything with the Integer bit set or the exponent 0 is valid */ + return false; + } + + if ((a.high & 0x7fff) == 0x7fff) { + if (a.low) { + return !(s->floatx80_behaviour & floatx80_pseudo_nan_valid); + } else { + return !(s->floatx80_behaviour & floatx80_pseudo_inf_valid); + } + } else { + return !(s->floatx80_behaviour & floatx80_unnormal_valid); + } } #define floatx80_zero make_floatx80(0x0000, 0x0000000000000000LL) diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index 56b23de21fe2..505fa97a53f6 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -111,9 +111,35 @@ static void m68k_cpu_reset_hold(Object *obj, ResetType type) * m68k-specific floatx80 behaviour: * * default Infinity values have a zero Integer bit * * input Infinities may have the Integer bit either 0 or 1 + * * pseudo-denormals supported for input and output + * * don't raise Invalid for pseudo-NaN/pseudo-Inf/Unnormal + * + * With m68k, the explicit integer bit can be zero in the case of: + * - zeros (exp == 0, mantissa == 0) + * - denormalized numbers (exp == 0, mantissa != 0) + * - unnormalized numbers (exp != 0, exp < 0x7FFF) + * - infinities (exp == 0x7FFF, mantissa == 0) + * - not-a-numbers (exp == 0x7FFF, mantissa != 0) + * + * For infinities and NaNs, the explicit integer bit can be either one or + * zero. + * + * The IEEE 754 standard does not define a zero integer bit. Such a number + * is an unnormalized number. Hardware does not directly support + * denormalized and unnormalized numbers, but implicitly supports them by + * trapping them as unimplemented data types, allowing efficient conversion + * in software. + * + * See "M68000 FAMILY PROGRAMMER’S REFERENCE MANUAL", + * "1.6 FLOATING-POINT DATA TYPES" + * + * Note though that QEMU's fp emulation does directly handle both + * denormal and unnormal values, and does not trap to guest software. */ set_floatx80_behaviour(floatx80_default_inf_int_bit_is_zero | - floatx80_pseudo_inf_valid, + floatx80_pseudo_inf_valid | + floatx80_pseudo_nan_valid | + floatx80_unnormal_valid, &env->fp_status); nan = floatx80_default_nan(&env->fp_status); From 1e75d8247ff27a307781230898fbcb8fbb9a8298 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:20 +0000 Subject: [PATCH 0287/1179] fpu: Move m68k_denormal fmt flag into floatx80_behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we compile-time set an 'm68k_denormal' flag in the FloatFmt for floatx80 for m68k. This controls our handling of what the Intel documentation calls a "pseudo-denormal": a value where the exponent field is zero and the explicit integer bit is set. For x86, the x87 FPU is supposed to accept a pseudo-denormal as input, but never generate one on output. For m68k, these values are permitted on input and may be produced on output. Replace the flag in the FloatFmt with a flag indicating whether the float format has an explicit bit (which will be true for floatx80 for all targets, and false for every other float type). Then we can gate the handling of these pseudo-denormals on the setting of a floatx80_behaviour flag. As far as I can see from the code we don't actually handle the x86-mandated "accept on input but don't generate" behaviour, because the handling in partsN(canonicalize) looked at fmt->m68k_denormal. So I have added TODO comments to that effect. This commit doesn't change any behaviour for any target. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250224111524.1101196-9-peter.maydell@linaro.org Message-id: 20250217125055.160887-7-peter.maydell@linaro.org --- fpu/softfloat-parts.c.inc | 27 ++++++++++++++++++++++++--- fpu/softfloat.c | 9 ++++----- include/fpu/softfloat-types.h | 19 +++++++++++++++++++ target/m68k/cpu.c | 3 ++- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/fpu/softfloat-parts.c.inc b/fpu/softfloat-parts.c.inc index 1d09f066c5d8..171bfd06e3a4 100644 --- a/fpu/softfloat-parts.c.inc +++ b/fpu/softfloat-parts.c.inc @@ -195,6 +195,25 @@ static FloatPartsN *partsN(pick_nan_muladd)(FloatPartsN *a, FloatPartsN *b, static void partsN(canonicalize)(FloatPartsN *p, float_status *status, const FloatFmt *fmt) { + /* + * It's target-dependent how to handle the case of exponent 0 + * and Integer bit set. Intel calls these "pseudodenormals", + * and treats them as if the integer bit was 0, and never + * produces them on output. This is the default behaviour for QEMU. + * For m68k, the integer bit is considered validly part of the + * input value when the exponent is 0, and may be 0 or 1, + * giving extra range. They may also be generated as outputs. + * (The m68k manual actually calls these values part of the + * normalized number range, not the denormalized number range, + * but that distinction is not important for us, because + * m68k doesn't care about the input_denormal_used status flag.) + * floatx80_pseudo_denormal_valid selects the m68k behaviour, + * which changes both how we canonicalize such a value and + * how we uncanonicalize results. + */ + bool has_pseudo_denormals = fmt->has_explicit_bit && + (status->floatx80_behaviour & floatx80_pseudo_denormal_valid); + if (unlikely(p->exp == 0)) { if (likely(frac_eqz(p))) { p->cls = float_class_zero; @@ -206,7 +225,7 @@ static void partsN(canonicalize)(FloatPartsN *p, float_status *status, int shift = frac_normalize(p); p->cls = float_class_denormal; p->exp = fmt->frac_shift - fmt->exp_bias - - shift + !fmt->m68k_denormal; + - shift + !has_pseudo_denormals; } } else if (likely(p->exp < fmt->exp_max) || fmt->arm_althp) { p->cls = float_class_normal; @@ -342,13 +361,15 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, frac_clear(p); } else { bool is_tiny = s->tininess_before_rounding || exp < 0; + bool has_pseudo_denormals = fmt->has_explicit_bit && + (s->floatx80_behaviour & floatx80_pseudo_denormal_valid); if (!is_tiny) { FloatPartsN discard; is_tiny = !frac_addi(&discard, p, inc); } - frac_shrjam(p, !fmt->m68k_denormal - exp); + frac_shrjam(p, !has_pseudo_denormals - exp); if (p->frac_lo & round_mask) { /* Need to recompute round-to-even/round-to-odd. */ @@ -379,7 +400,7 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, p->frac_lo &= ~round_mask; } - exp = (p->frac_hi & DECOMPOSED_IMPLICIT_BIT) && !fmt->m68k_denormal; + exp = (p->frac_hi & DECOMPOSED_IMPLICIT_BIT) && !has_pseudo_denormals; frac_shr(p, frac_shift); if (is_tiny) { diff --git a/fpu/softfloat.c b/fpu/softfloat.c index 2a20ae871eb0..b299cfaf8606 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -537,7 +537,8 @@ typedef struct { * round_mask: bits below lsb which must be rounded * The following optional modifiers are available: * arm_althp: handle ARM Alternative Half Precision - * m68k_denormal: explicit integer bit for extended precision may be 1 + * has_explicit_bit: has an explicit integer bit; this affects whether + * the float_status floatx80_behaviour handling applies */ typedef struct { int exp_size; @@ -547,7 +548,7 @@ typedef struct { int frac_size; int frac_shift; bool arm_althp; - bool m68k_denormal; + bool has_explicit_bit; uint64_t round_mask; } FloatFmt; @@ -600,9 +601,7 @@ static const FloatFmt floatx80_params[3] = { [floatx80_precision_d] = { FLOATX80_PARAMS(52) }, [floatx80_precision_x] = { FLOATX80_PARAMS(64), -#ifdef TARGET_M68K - .m68k_denormal = true, -#endif + .has_explicit_bit = true, }, }; diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h index b1941384aef7..1af2a0cb14bc 100644 --- a/include/fpu/softfloat-types.h +++ b/include/fpu/softfloat-types.h @@ -349,6 +349,25 @@ typedef enum __attribute__((__packed__)) { * and using them as inputs to a float op will raise Invalid. */ floatx80_unnormal_valid = 8, + + /* + * If the exponent is 0 and the Integer bit is set, Intel call + * this a "pseudo-denormal"; x86 supports that only on input + * (treating them as denormals by ignoring the Integer bit). + * For m68k, the integer bit is considered validly part of the + * input value when the exponent is 0, and may be 0 or 1, + * giving extra range. They may also be generated as outputs. + * (The m68k manual actually calls these values part of the + * normalized number range, not the denormalized number range.) + * + * By default you get the Intel behaviour where the Integer + * bit is ignored; if this is set then the Integer bit value + * is honoured, m68k-style. + * + * Either way, floatx80_invalid_encoding() will always accept + * pseudo-denormals. + */ + floatx80_pseudo_denormal_valid = 16, } FloatX80Behaviour; /* diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index 505fa97a53f6..2617d8f6ede1 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -139,7 +139,8 @@ static void m68k_cpu_reset_hold(Object *obj, ResetType type) set_floatx80_behaviour(floatx80_default_inf_int_bit_is_zero | floatx80_pseudo_inf_valid | floatx80_pseudo_nan_valid | - floatx80_unnormal_valid, + floatx80_unnormal_valid | + floatx80_pseudo_denormal_valid, &env->fp_status); nan = floatx80_default_nan(&env->fp_status); From 2e01cfea0735889a1e0481fc783d621779439572 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:21 +0000 Subject: [PATCH 0288/1179] fpu: Always decide no_signaling_nans() at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we have a compile-time shortcut where we return false from no_signaling_nans() on everything except Xtensa, because we know that's the only target that might ever set status->no_signaling_nans. Remove the ifdef, so we always look at the status flag; this has no behavioural change, but will be necessary if we want to build softfloat once for all targets. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250224111524.1101196-10-peter.maydell@linaro.org Message-id: 20250217125055.160887-8-peter.maydell@linaro.org --- fpu/softfloat-specialize.c.inc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fpu/softfloat-specialize.c.inc b/fpu/softfloat-specialize.c.inc index 8327f7278613..a2c6afad5dad 100644 --- a/fpu/softfloat-specialize.c.inc +++ b/fpu/softfloat-specialize.c.inc @@ -85,11 +85,7 @@ this code that are retained. */ static inline bool no_signaling_nans(float_status *status) { -#if defined(TARGET_XTENSA) return status->no_signaling_nans; -#else - return false; -#endif } /* Define how the architecture discriminates signaling NaNs. From 3abed4d0eace62910e90c206cb9d5741c6095b12 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:22 +0000 Subject: [PATCH 0289/1179] fpu: Always decide snan_bit_is_one() at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we have a compile-time shortcut where we return a hardcode value from snan_bit_is_one() on everything except MIPS, because we know that's the only target that needs to change status->no_signaling_nans at runtime. Remove the ifdef, so we always look at the status flag. This means we must update the two targets (HPPA and SH4) that were previously hardcoded to return true so that they set the status flag correctly. This has no behavioural change, but will be necessary if we want to build softfloat once for all targets. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250224111524.1101196-11-peter.maydell@linaro.org Message-id: 20250217125055.160887-9-peter.maydell@linaro.org --- fpu/softfloat-specialize.c.inc | 7 ------- target/hppa/fpu_helper.c | 1 + target/sh4/cpu.c | 1 + 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/fpu/softfloat-specialize.c.inc b/fpu/softfloat-specialize.c.inc index a2c6afad5dad..ba4fa08b7beb 100644 --- a/fpu/softfloat-specialize.c.inc +++ b/fpu/softfloat-specialize.c.inc @@ -93,17 +93,10 @@ static inline bool no_signaling_nans(float_status *status) * In IEEE 754-1985 this was implementation defined, but in IEEE 754-2008 * the msb must be zero. MIPS is (so far) unique in supporting both the * 2008 revision and backward compatibility with their original choice. - * Thus for MIPS we must make the choice at runtime. */ static inline bool snan_bit_is_one(float_status *status) { -#if defined(TARGET_MIPS) return status->snan_bit_is_one; -#elif defined(TARGET_HPPA) || defined(TARGET_SH4) - return 1; -#else - return 0; -#endif } /*---------------------------------------------------------------------------- diff --git a/target/hppa/fpu_helper.c b/target/hppa/fpu_helper.c index 8ff4b4480490..a62d9d30831a 100644 --- a/target/hppa/fpu_helper.c +++ b/target/hppa/fpu_helper.c @@ -67,6 +67,7 @@ void HELPER(loaded_fr0)(CPUHPPAState *env) set_float_infzeronan_rule(float_infzeronan_dnan_never, &env->fp_status); /* Default NaN: sign bit clear, msb-1 frac bit set */ set_float_default_nan_pattern(0b00100000, &env->fp_status); + set_snan_bit_is_one(true, &env->fp_status); /* * "PA-RISC 2.0 Architecture" says it is IMPDEF whether the flushing * enabled by FPSR.D happens before or after rounding. We pick "before" diff --git a/target/sh4/cpu.c b/target/sh4/cpu.c index 4ac693d99bd4..ccfe222bdf31 100644 --- a/target/sh4/cpu.c +++ b/target/sh4/cpu.c @@ -128,6 +128,7 @@ static void superh_cpu_reset_hold(Object *obj, ResetType type) set_flush_to_zero(1, &env->fp_status); #endif set_default_nan_mode(1, &env->fp_status); + set_snan_bit_is_one(true, &env->fp_status); /* sign bit clear, set all frac bits other than msb */ set_float_default_nan_pattern(0b00111111, &env->fp_status); /* From c5d4173fcf04f6de9b9bb0959d1fdfc08254381a Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:23 +0000 Subject: [PATCH 0290/1179] fpu: Don't compile-time disable hardfloat for PPC targets We happen to know that for the PPC target the FP status flags (and in particular float_flag_inexact) will always be cleared before a floating point operation, and so can_use_fpu() will always return false. So we speed things up a little by forcing QEMU_NO_HARDFLOAT to true on that target. We would like to build softfloat once for all targets; that means removing target-specific ifdefs. Remove the check for TARGET_PPC; this won't change behaviour because can_use_fpu() will see that float_flag_inexact is clear and take the softfloat path anyway. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250224111524.1101196-12-peter.maydell@linaro.org Message-id: 20250217125055.160887-10-peter.maydell@linaro.org --- fpu/softfloat.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fpu/softfloat.c b/fpu/softfloat.c index b299cfaf8606..b38eea8d879c 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -220,11 +220,9 @@ GEN_INPUT_FLUSH3(float64_input_flush3, float64) * the use of hardfloat, since hardfloat relies on the inexact flag being * already set. */ -#if defined(TARGET_PPC) || defined(__FAST_MATH__) # if defined(__FAST_MATH__) # warning disabling hardfloat due to -ffast-math: hardfloat requires an exact \ IEEE implementation -# endif # define QEMU_NO_HARDFLOAT 1 # define QEMU_SOFTFLOAT_ATTR QEMU_FLATTEN #else From 5d3462b4cde8bedb33362dab0a3ae94d403899b0 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 24 Feb 2025 11:15:24 +0000 Subject: [PATCH 0291/1179] fpu: Build only once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now we have removed all the target-specifics from the softfloat code, we can switch to building it once for the whole system rather than once per target. Signed-off-by: Peter Maydell Tested-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-id: 20250224111524.1101196-13-peter.maydell@linaro.org Message-id: 20250217125055.160887-11-peter.maydell@linaro.org --- fpu/meson.build | 2 +- fpu/softfloat.c | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/fpu/meson.build b/fpu/meson.build index 1a9992ded56b..646c76f0c69e 100644 --- a/fpu/meson.build +++ b/fpu/meson.build @@ -1 +1 @@ -specific_ss.add(when: 'CONFIG_TCG', if_true: files('softfloat.c')) +common_ss.add(when: 'CONFIG_TCG', if_true: files('softfloat.c')) diff --git a/fpu/softfloat.c b/fpu/softfloat.c index b38eea8d879c..34c962d6bd98 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -79,9 +79,6 @@ this code that are retained. * version 2 or later. See the COPYING file in the top-level directory. */ -/* softfloat (and in particular the code in softfloat-specialize.h) is - * target-dependent and needs the TARGET_* macros. - */ #include "qemu/osdep.h" #include #include "qemu/bitops.h" From 1deb15c88ab3f1b0788b9e41b08217036eca3c91 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 21 Feb 2025 19:09:53 +0000 Subject: [PATCH 0292/1179] target/arm: Move TCG-only VFP code into tcg/ subdir Most of the target/arm/vfp_helper.c file is purely TCG helper code, guarded by #ifdef CONFIG_TCG. Move this into a new file in target/arm/tcg/. This leaves only the code relating to getting and setting the FPCR/FPSR/FPSCR in the original file. (Some of this also is TCG-only, but that needs more careful disentangling.) Having two vfp_helper.c files might seem a bit confusing, but once we've finished moving all the helper code out of the old file we are going to rename it to vfp_fpscr.c. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250221190957.811948-2-peter.maydell@linaro.org --- target/arm/tcg/meson.build | 1 + target/arm/tcg/vfp_helper.c | 1130 +++++++++++++++++++++++++++++++++++ target/arm/vfp_helper.c | 1109 ---------------------------------- 3 files changed, 1131 insertions(+), 1109 deletions(-) create mode 100644 target/arm/tcg/vfp_helper.c diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index 1f9077c372cb..dd12ccedb182 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -41,6 +41,7 @@ arm_ss.add(files( 'vec_helper.c', 'tlb-insns.c', 'arith_helper.c', + 'vfp_helper.c', )) arm_ss.add(when: 'TARGET_AARCH64', if_true: files( diff --git a/target/arm/tcg/vfp_helper.c b/target/arm/tcg/vfp_helper.c new file mode 100644 index 000000000000..aa580ff64c75 --- /dev/null +++ b/target/arm/tcg/vfp_helper.c @@ -0,0 +1,1130 @@ +/* + * ARM VFP floating-point operations + * + * Copyright (c) 2003 Fabrice Bellard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "exec/helper-proto.h" +#include "internals.h" +#include "cpu-features.h" +#include "fpu/softfloat.h" +#include "qemu/log.h" + +/* + * VFP support. We follow the convention used for VFP instructions: + * Single precision routines have a "s" suffix, double precision a + * "d" suffix. + */ + +#define VFP_HELPER(name, p) HELPER(glue(glue(vfp_,name),p)) + +#define VFP_BINOP(name) \ +dh_ctype_f16 VFP_HELPER(name, h)(dh_ctype_f16 a, dh_ctype_f16 b, float_status *fpst) \ +{ \ + return float16_ ## name(a, b, fpst); \ +} \ +float32 VFP_HELPER(name, s)(float32 a, float32 b, float_status *fpst) \ +{ \ + return float32_ ## name(a, b, fpst); \ +} \ +float64 VFP_HELPER(name, d)(float64 a, float64 b, float_status *fpst) \ +{ \ + return float64_ ## name(a, b, fpst); \ +} +VFP_BINOP(add) +VFP_BINOP(sub) +VFP_BINOP(mul) +VFP_BINOP(div) +VFP_BINOP(min) +VFP_BINOP(max) +VFP_BINOP(minnum) +VFP_BINOP(maxnum) +#undef VFP_BINOP + +dh_ctype_f16 VFP_HELPER(sqrt, h)(dh_ctype_f16 a, float_status *fpst) +{ + return float16_sqrt(a, fpst); +} + +float32 VFP_HELPER(sqrt, s)(float32 a, float_status *fpst) +{ + return float32_sqrt(a, fpst); +} + +float64 VFP_HELPER(sqrt, d)(float64 a, float_status *fpst) +{ + return float64_sqrt(a, fpst); +} + +static void softfloat_to_vfp_compare(CPUARMState *env, FloatRelation cmp) +{ + uint32_t flags; + switch (cmp) { + case float_relation_equal: + flags = 0x6; + break; + case float_relation_less: + flags = 0x8; + break; + case float_relation_greater: + flags = 0x2; + break; + case float_relation_unordered: + flags = 0x3; + break; + default: + g_assert_not_reached(); + } + env->vfp.fpsr = deposit64(env->vfp.fpsr, 28, 4, flags); /* NZCV */ +} + +/* XXX: check quiet/signaling case */ +#define DO_VFP_cmp(P, FLOATTYPE, ARGTYPE, FPST) \ +void VFP_HELPER(cmp, P)(ARGTYPE a, ARGTYPE b, CPUARMState *env) \ +{ \ + softfloat_to_vfp_compare(env, \ + FLOATTYPE ## _compare_quiet(a, b, &env->vfp.fp_status[FPST])); \ +} \ +void VFP_HELPER(cmpe, P)(ARGTYPE a, ARGTYPE b, CPUARMState *env) \ +{ \ + softfloat_to_vfp_compare(env, \ + FLOATTYPE ## _compare(a, b, &env->vfp.fp_status[FPST])); \ +} +DO_VFP_cmp(h, float16, dh_ctype_f16, FPST_A32_F16) +DO_VFP_cmp(s, float32, float32, FPST_A32) +DO_VFP_cmp(d, float64, float64, FPST_A32) +#undef DO_VFP_cmp + +/* Integer to float and float to integer conversions */ + +#define CONV_ITOF(name, ftype, fsz, sign) \ +ftype HELPER(name)(uint32_t x, float_status *fpst) \ +{ \ + return sign##int32_to_##float##fsz((sign##int32_t)x, fpst); \ +} + +#define CONV_FTOI(name, ftype, fsz, sign, round) \ +sign##int32_t HELPER(name)(ftype x, float_status *fpst) \ +{ \ + if (float##fsz##_is_any_nan(x)) { \ + float_raise(float_flag_invalid, fpst); \ + return 0; \ + } \ + return float##fsz##_to_##sign##int32##round(x, fpst); \ +} + +#define FLOAT_CONVS(name, p, ftype, fsz, sign) \ + CONV_ITOF(vfp_##name##to##p, ftype, fsz, sign) \ + CONV_FTOI(vfp_to##name##p, ftype, fsz, sign, ) \ + CONV_FTOI(vfp_to##name##z##p, ftype, fsz, sign, _round_to_zero) + +FLOAT_CONVS(si, h, uint32_t, 16, ) +FLOAT_CONVS(si, s, float32, 32, ) +FLOAT_CONVS(si, d, float64, 64, ) +FLOAT_CONVS(ui, h, uint32_t, 16, u) +FLOAT_CONVS(ui, s, float32, 32, u) +FLOAT_CONVS(ui, d, float64, 64, u) + +#undef CONV_ITOF +#undef CONV_FTOI +#undef FLOAT_CONVS + +/* floating point conversion */ +float64 VFP_HELPER(fcvtd, s)(float32 x, float_status *status) +{ + return float32_to_float64(x, status); +} + +float32 VFP_HELPER(fcvts, d)(float64 x, float_status *status) +{ + return float64_to_float32(x, status); +} + +uint32_t HELPER(bfcvt)(float32 x, float_status *status) +{ + return float32_to_bfloat16(x, status); +} + +uint32_t HELPER(bfcvt_pair)(uint64_t pair, float_status *status) +{ + bfloat16 lo = float32_to_bfloat16(extract64(pair, 0, 32), status); + bfloat16 hi = float32_to_bfloat16(extract64(pair, 32, 32), status); + return deposit32(lo, 16, 16, hi); +} + +/* + * VFP3 fixed point conversion. The AArch32 versions of fix-to-float + * must always round-to-nearest; the AArch64 ones honour the FPSCR + * rounding mode. (For AArch32 Neon the standard-FPSCR is set to + * round-to-nearest so either helper will work.) AArch32 float-to-fix + * must round-to-zero. + */ +#define VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ +ftype HELPER(vfp_##name##to##p)(uint##isz##_t x, uint32_t shift, \ + float_status *fpst) \ +{ return itype##_to_##float##fsz##_scalbn(x, -shift, fpst); } + +#define VFP_CONV_FIX_FLOAT_ROUND(name, p, fsz, ftype, isz, itype) \ + ftype HELPER(vfp_##name##to##p##_round_to_nearest)(uint##isz##_t x, \ + uint32_t shift, \ + float_status *fpst) \ + { \ + ftype ret; \ + FloatRoundMode oldmode = fpst->float_rounding_mode; \ + fpst->float_rounding_mode = float_round_nearest_even; \ + ret = itype##_to_##float##fsz##_scalbn(x, -shift, fpst); \ + fpst->float_rounding_mode = oldmode; \ + return ret; \ + } + +#define VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, ROUND, suff) \ +uint##isz##_t HELPER(vfp_to##name##p##suff)(ftype x, uint32_t shift, \ + float_status *fpst) \ +{ \ + if (unlikely(float##fsz##_is_any_nan(x))) { \ + float_raise(float_flag_invalid, fpst); \ + return 0; \ + } \ + return float##fsz##_to_##itype##_scalbn(x, ROUND, shift, fpst); \ +} + +#define VFP_CONV_FIX(name, p, fsz, ftype, isz, itype) \ +VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ +VFP_CONV_FIX_FLOAT_ROUND(name, p, fsz, ftype, isz, itype) \ +VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ + float_round_to_zero, _round_to_zero) \ +VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ + get_float_rounding_mode(fpst), ) + +#define VFP_CONV_FIX_A64(name, p, fsz, ftype, isz, itype) \ +VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ +VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ + get_float_rounding_mode(fpst), ) + +VFP_CONV_FIX(sh, d, 64, float64, 64, int16) +VFP_CONV_FIX(sl, d, 64, float64, 64, int32) +VFP_CONV_FIX_A64(sq, d, 64, float64, 64, int64) +VFP_CONV_FIX(uh, d, 64, float64, 64, uint16) +VFP_CONV_FIX(ul, d, 64, float64, 64, uint32) +VFP_CONV_FIX_A64(uq, d, 64, float64, 64, uint64) +VFP_CONV_FIX(sh, s, 32, float32, 32, int16) +VFP_CONV_FIX(sl, s, 32, float32, 32, int32) +VFP_CONV_FIX_A64(sq, s, 32, float32, 64, int64) +VFP_CONV_FIX(uh, s, 32, float32, 32, uint16) +VFP_CONV_FIX(ul, s, 32, float32, 32, uint32) +VFP_CONV_FIX_A64(uq, s, 32, float32, 64, uint64) +VFP_CONV_FIX(sh, h, 16, dh_ctype_f16, 32, int16) +VFP_CONV_FIX(sl, h, 16, dh_ctype_f16, 32, int32) +VFP_CONV_FIX_A64(sq, h, 16, dh_ctype_f16, 64, int64) +VFP_CONV_FIX(uh, h, 16, dh_ctype_f16, 32, uint16) +VFP_CONV_FIX(ul, h, 16, dh_ctype_f16, 32, uint32) +VFP_CONV_FIX_A64(uq, h, 16, dh_ctype_f16, 64, uint64) +VFP_CONV_FLOAT_FIX_ROUND(sq, d, 64, float64, 64, int64, + float_round_to_zero, _round_to_zero) +VFP_CONV_FLOAT_FIX_ROUND(uq, d, 64, float64, 64, uint64, + float_round_to_zero, _round_to_zero) + +#undef VFP_CONV_FIX +#undef VFP_CONV_FIX_FLOAT +#undef VFP_CONV_FLOAT_FIX_ROUND +#undef VFP_CONV_FIX_A64 + +/* Set the current fp rounding mode and return the old one. + * The argument is a softfloat float_round_ value. + */ +uint32_t HELPER(set_rmode)(uint32_t rmode, float_status *fp_status) +{ + uint32_t prev_rmode = get_float_rounding_mode(fp_status); + set_float_rounding_mode(rmode, fp_status); + + return prev_rmode; +} + +/* Half precision conversions. */ +float32 HELPER(vfp_fcvt_f16_to_f32)(uint32_t a, float_status *fpst, + uint32_t ahp_mode) +{ + /* Squash FZ16 to 0 for the duration of conversion. In this case, + * it would affect flushing input denormals. + */ + bool save = get_flush_inputs_to_zero(fpst); + set_flush_inputs_to_zero(false, fpst); + float32 r = float16_to_float32(a, !ahp_mode, fpst); + set_flush_inputs_to_zero(save, fpst); + return r; +} + +uint32_t HELPER(vfp_fcvt_f32_to_f16)(float32 a, float_status *fpst, + uint32_t ahp_mode) +{ + /* Squash FZ16 to 0 for the duration of conversion. In this case, + * it would affect flushing output denormals. + */ + bool save = get_flush_to_zero(fpst); + set_flush_to_zero(false, fpst); + float16 r = float32_to_float16(a, !ahp_mode, fpst); + set_flush_to_zero(save, fpst); + return r; +} + +float64 HELPER(vfp_fcvt_f16_to_f64)(uint32_t a, float_status *fpst, + uint32_t ahp_mode) +{ + /* Squash FZ16 to 0 for the duration of conversion. In this case, + * it would affect flushing input denormals. + */ + bool save = get_flush_inputs_to_zero(fpst); + set_flush_inputs_to_zero(false, fpst); + float64 r = float16_to_float64(a, !ahp_mode, fpst); + set_flush_inputs_to_zero(save, fpst); + return r; +} + +uint32_t HELPER(vfp_fcvt_f64_to_f16)(float64 a, float_status *fpst, + uint32_t ahp_mode) +{ + /* Squash FZ16 to 0 for the duration of conversion. In this case, + * it would affect flushing output denormals. + */ + bool save = get_flush_to_zero(fpst); + set_flush_to_zero(false, fpst); + float16 r = float64_to_float16(a, !ahp_mode, fpst); + set_flush_to_zero(save, fpst); + return r; +} + +/* NEON helpers. */ + +/* Constants 256 and 512 are used in some helpers; we avoid relying on + * int->float conversions at run-time. */ +#define float64_256 make_float64(0x4070000000000000LL) +#define float64_512 make_float64(0x4080000000000000LL) +#define float16_maxnorm make_float16(0x7bff) +#define float32_maxnorm make_float32(0x7f7fffff) +#define float64_maxnorm make_float64(0x7fefffffffffffffLL) + +/* Reciprocal functions + * + * The algorithm that must be used to calculate the estimate + * is specified by the ARM ARM, see FPRecipEstimate()/RecipEstimate + */ + +/* See RecipEstimate() + * + * input is a 9 bit fixed point number + * input range 256 .. 511 for a number from 0.5 <= x < 1.0. + * result range 256 .. 511 for a number from 1.0 to 511/256. + */ + +static int recip_estimate(int input) +{ + int a, b, r; + assert(256 <= input && input < 512); + a = (input * 2) + 1; + b = (1 << 19) / a; + r = (b + 1) >> 1; + assert(256 <= r && r < 512); + return r; +} + +/* + * Increased precision version: + * input is a 13 bit fixed point number + * input range 2048 .. 4095 for a number from 0.5 <= x < 1.0. + * result range 4096 .. 8191 for a number from 1.0 to 2.0 + */ +static int recip_estimate_incprec(int input) +{ + int a, b, r; + assert(2048 <= input && input < 4096); + a = (input * 2) + 1; + /* + * The pseudocode expresses this as an operation on infinite + * precision reals where it calculates 2^25 / a and then looks + * at the error between that and the rounded-down-to-integer + * value to see if it should instead round up. We instead + * follow the same approach as the pseudocode for the 8-bit + * precision version, and calculate (2 * (2^25 / a)) as an + * integer so we can do the "add one and halve" to round it. + * So the 1 << 26 here is correct. + */ + b = (1 << 26) / a; + r = (b + 1) >> 1; + assert(4096 <= r && r < 8192); + return r; +} + +/* + * Common wrapper to call recip_estimate + * + * The parameters are exponent and 64 bit fraction (without implicit + * bit) where the binary point is nominally at bit 52. Returns a + * float64 which can then be rounded to the appropriate size by the + * callee. + */ + +static uint64_t call_recip_estimate(int *exp, int exp_off, uint64_t frac, + bool increasedprecision) +{ + uint32_t scaled, estimate; + uint64_t result_frac; + int result_exp; + + /* Handle sub-normals */ + if (*exp == 0) { + if (extract64(frac, 51, 1) == 0) { + *exp = -1; + frac <<= 2; + } else { + frac <<= 1; + } + } + + if (increasedprecision) { + /* scaled = UInt('1':fraction<51:41>) */ + scaled = deposit32(1 << 11, 0, 11, extract64(frac, 41, 11)); + estimate = recip_estimate_incprec(scaled); + } else { + /* scaled = UInt('1':fraction<51:44>) */ + scaled = deposit32(1 << 8, 0, 8, extract64(frac, 44, 8)); + estimate = recip_estimate(scaled); + } + + result_exp = exp_off - *exp; + if (increasedprecision) { + result_frac = deposit64(0, 40, 12, estimate); + } else { + result_frac = deposit64(0, 44, 8, estimate); + } + if (result_exp == 0) { + result_frac = deposit64(result_frac >> 1, 51, 1, 1); + } else if (result_exp == -1) { + result_frac = deposit64(result_frac >> 2, 50, 2, 1); + result_exp = 0; + } + + *exp = result_exp; + + return result_frac; +} + +static bool round_to_inf(float_status *fpst, bool sign_bit) +{ + switch (fpst->float_rounding_mode) { + case float_round_nearest_even: /* Round to Nearest */ + return true; + case float_round_up: /* Round to +Inf */ + return !sign_bit; + case float_round_down: /* Round to -Inf */ + return sign_bit; + case float_round_to_zero: /* Round to Zero */ + return false; + default: + g_assert_not_reached(); + } +} + +uint32_t HELPER(recpe_f16)(uint32_t input, float_status *fpst) +{ + float16 f16 = float16_squash_input_denormal(input, fpst); + uint32_t f16_val = float16_val(f16); + uint32_t f16_sign = float16_is_neg(f16); + int f16_exp = extract32(f16_val, 10, 5); + uint32_t f16_frac = extract32(f16_val, 0, 10); + uint64_t f64_frac; + + if (float16_is_any_nan(f16)) { + float16 nan = f16; + if (float16_is_signaling_nan(f16, fpst)) { + float_raise(float_flag_invalid, fpst); + if (!fpst->default_nan_mode) { + nan = float16_silence_nan(f16, fpst); + } + } + if (fpst->default_nan_mode) { + nan = float16_default_nan(fpst); + } + return nan; + } else if (float16_is_infinity(f16)) { + return float16_set_sign(float16_zero, float16_is_neg(f16)); + } else if (float16_is_zero(f16)) { + float_raise(float_flag_divbyzero, fpst); + return float16_set_sign(float16_infinity, float16_is_neg(f16)); + } else if (float16_abs(f16) < (1 << 8)) { + /* Abs(value) < 2.0^-16 */ + float_raise(float_flag_overflow | float_flag_inexact, fpst); + if (round_to_inf(fpst, f16_sign)) { + return float16_set_sign(float16_infinity, f16_sign); + } else { + return float16_set_sign(float16_maxnorm, f16_sign); + } + } else if (f16_exp >= 29 && fpst->flush_to_zero) { + float_raise(float_flag_underflow, fpst); + return float16_set_sign(float16_zero, float16_is_neg(f16)); + } + + f64_frac = call_recip_estimate(&f16_exp, 29, + ((uint64_t) f16_frac) << (52 - 10), false); + + /* result = sign : result_exp<4:0> : fraction<51:42> */ + f16_val = deposit32(0, 15, 1, f16_sign); + f16_val = deposit32(f16_val, 10, 5, f16_exp); + f16_val = deposit32(f16_val, 0, 10, extract64(f64_frac, 52 - 10, 10)); + return make_float16(f16_val); +} + +/* + * FEAT_RPRES means the f32 FRECPE has an "increased precision" variant + * which is used when FPCR.AH == 1. + */ +static float32 do_recpe_f32(float32 input, float_status *fpst, bool rpres) +{ + float32 f32 = float32_squash_input_denormal(input, fpst); + uint32_t f32_val = float32_val(f32); + bool f32_sign = float32_is_neg(f32); + int f32_exp = extract32(f32_val, 23, 8); + uint32_t f32_frac = extract32(f32_val, 0, 23); + uint64_t f64_frac; + + if (float32_is_any_nan(f32)) { + float32 nan = f32; + if (float32_is_signaling_nan(f32, fpst)) { + float_raise(float_flag_invalid, fpst); + if (!fpst->default_nan_mode) { + nan = float32_silence_nan(f32, fpst); + } + } + if (fpst->default_nan_mode) { + nan = float32_default_nan(fpst); + } + return nan; + } else if (float32_is_infinity(f32)) { + return float32_set_sign(float32_zero, float32_is_neg(f32)); + } else if (float32_is_zero(f32)) { + float_raise(float_flag_divbyzero, fpst); + return float32_set_sign(float32_infinity, float32_is_neg(f32)); + } else if (float32_abs(f32) < (1ULL << 21)) { + /* Abs(value) < 2.0^-128 */ + float_raise(float_flag_overflow | float_flag_inexact, fpst); + if (round_to_inf(fpst, f32_sign)) { + return float32_set_sign(float32_infinity, f32_sign); + } else { + return float32_set_sign(float32_maxnorm, f32_sign); + } + } else if (f32_exp >= 253 && fpst->flush_to_zero) { + float_raise(float_flag_underflow, fpst); + return float32_set_sign(float32_zero, float32_is_neg(f32)); + } + + f64_frac = call_recip_estimate(&f32_exp, 253, + ((uint64_t) f32_frac) << (52 - 23), rpres); + + /* result = sign : result_exp<7:0> : fraction<51:29> */ + f32_val = deposit32(0, 31, 1, f32_sign); + f32_val = deposit32(f32_val, 23, 8, f32_exp); + f32_val = deposit32(f32_val, 0, 23, extract64(f64_frac, 52 - 23, 23)); + return make_float32(f32_val); +} + +float32 HELPER(recpe_f32)(float32 input, float_status *fpst) +{ + return do_recpe_f32(input, fpst, false); +} + +float32 HELPER(recpe_rpres_f32)(float32 input, float_status *fpst) +{ + return do_recpe_f32(input, fpst, true); +} + +float64 HELPER(recpe_f64)(float64 input, float_status *fpst) +{ + float64 f64 = float64_squash_input_denormal(input, fpst); + uint64_t f64_val = float64_val(f64); + bool f64_sign = float64_is_neg(f64); + int f64_exp = extract64(f64_val, 52, 11); + uint64_t f64_frac = extract64(f64_val, 0, 52); + + /* Deal with any special cases */ + if (float64_is_any_nan(f64)) { + float64 nan = f64; + if (float64_is_signaling_nan(f64, fpst)) { + float_raise(float_flag_invalid, fpst); + if (!fpst->default_nan_mode) { + nan = float64_silence_nan(f64, fpst); + } + } + if (fpst->default_nan_mode) { + nan = float64_default_nan(fpst); + } + return nan; + } else if (float64_is_infinity(f64)) { + return float64_set_sign(float64_zero, float64_is_neg(f64)); + } else if (float64_is_zero(f64)) { + float_raise(float_flag_divbyzero, fpst); + return float64_set_sign(float64_infinity, float64_is_neg(f64)); + } else if ((f64_val & ~(1ULL << 63)) < (1ULL << 50)) { + /* Abs(value) < 2.0^-1024 */ + float_raise(float_flag_overflow | float_flag_inexact, fpst); + if (round_to_inf(fpst, f64_sign)) { + return float64_set_sign(float64_infinity, f64_sign); + } else { + return float64_set_sign(float64_maxnorm, f64_sign); + } + } else if (f64_exp >= 2045 && fpst->flush_to_zero) { + float_raise(float_flag_underflow, fpst); + return float64_set_sign(float64_zero, float64_is_neg(f64)); + } + + f64_frac = call_recip_estimate(&f64_exp, 2045, f64_frac, false); + + /* result = sign : result_exp<10:0> : fraction<51:0>; */ + f64_val = deposit64(0, 63, 1, f64_sign); + f64_val = deposit64(f64_val, 52, 11, f64_exp); + f64_val = deposit64(f64_val, 0, 52, f64_frac); + return make_float64(f64_val); +} + +/* The algorithm that must be used to calculate the estimate + * is specified by the ARM ARM. + */ + +static int do_recip_sqrt_estimate(int a) +{ + int b, estimate; + + assert(128 <= a && a < 512); + if (a < 256) { + a = a * 2 + 1; + } else { + a = (a >> 1) << 1; + a = (a + 1) * 2; + } + b = 512; + while (a * (b + 1) * (b + 1) < (1 << 28)) { + b += 1; + } + estimate = (b + 1) / 2; + assert(256 <= estimate && estimate < 512); + + return estimate; +} + +static int do_recip_sqrt_estimate_incprec(int a) +{ + /* + * The Arm ARM describes the 12-bit precision version of RecipSqrtEstimate + * in terms of an infinite-precision floating point calculation of a + * square root. We implement this using the same kind of pure integer + * algorithm as the 8-bit mantissa, to get the same bit-for-bit result. + */ + int64_t b, estimate; + + assert(1024 <= a && a < 4096); + if (a < 2048) { + a = a * 2 + 1; + } else { + a = (a >> 1) << 1; + a = (a + 1) * 2; + } + b = 8192; + while (a * (b + 1) * (b + 1) < (1ULL << 39)) { + b += 1; + } + estimate = (b + 1) / 2; + + assert(4096 <= estimate && estimate < 8192); + + return estimate; +} + +static uint64_t recip_sqrt_estimate(int *exp , int exp_off, uint64_t frac, + bool increasedprecision) +{ + int estimate; + uint32_t scaled; + + if (*exp == 0) { + while (extract64(frac, 51, 1) == 0) { + frac = frac << 1; + *exp -= 1; + } + frac = extract64(frac, 0, 51) << 1; + } + + if (increasedprecision) { + if (*exp & 1) { + /* scaled = UInt('01':fraction<51:42>) */ + scaled = deposit32(1 << 10, 0, 10, extract64(frac, 42, 10)); + } else { + /* scaled = UInt('1':fraction<51:41>) */ + scaled = deposit32(1 << 11, 0, 11, extract64(frac, 41, 11)); + } + estimate = do_recip_sqrt_estimate_incprec(scaled); + } else { + if (*exp & 1) { + /* scaled = UInt('01':fraction<51:45>) */ + scaled = deposit32(1 << 7, 0, 7, extract64(frac, 45, 7)); + } else { + /* scaled = UInt('1':fraction<51:44>) */ + scaled = deposit32(1 << 8, 0, 8, extract64(frac, 44, 8)); + } + estimate = do_recip_sqrt_estimate(scaled); + } + + *exp = (exp_off - *exp) / 2; + if (increasedprecision) { + return extract64(estimate, 0, 12) << 40; + } else { + return extract64(estimate, 0, 8) << 44; + } +} + +uint32_t HELPER(rsqrte_f16)(uint32_t input, float_status *s) +{ + float16 f16 = float16_squash_input_denormal(input, s); + uint16_t val = float16_val(f16); + bool f16_sign = float16_is_neg(f16); + int f16_exp = extract32(val, 10, 5); + uint16_t f16_frac = extract32(val, 0, 10); + uint64_t f64_frac; + + if (float16_is_any_nan(f16)) { + float16 nan = f16; + if (float16_is_signaling_nan(f16, s)) { + float_raise(float_flag_invalid, s); + if (!s->default_nan_mode) { + nan = float16_silence_nan(f16, s); + } + } + if (s->default_nan_mode) { + nan = float16_default_nan(s); + } + return nan; + } else if (float16_is_zero(f16)) { + float_raise(float_flag_divbyzero, s); + return float16_set_sign(float16_infinity, f16_sign); + } else if (f16_sign) { + float_raise(float_flag_invalid, s); + return float16_default_nan(s); + } else if (float16_is_infinity(f16)) { + return float16_zero; + } + + /* Scale and normalize to a double-precision value between 0.25 and 1.0, + * preserving the parity of the exponent. */ + + f64_frac = ((uint64_t) f16_frac) << (52 - 10); + + f64_frac = recip_sqrt_estimate(&f16_exp, 44, f64_frac, false); + + /* result = sign : result_exp<4:0> : estimate<7:0> : Zeros(2) */ + val = deposit32(0, 15, 1, f16_sign); + val = deposit32(val, 10, 5, f16_exp); + val = deposit32(val, 2, 8, extract64(f64_frac, 52 - 8, 8)); + return make_float16(val); +} + +/* + * FEAT_RPRES means the f32 FRSQRTE has an "increased precision" variant + * which is used when FPCR.AH == 1. + */ +static float32 do_rsqrte_f32(float32 input, float_status *s, bool rpres) +{ + float32 f32 = float32_squash_input_denormal(input, s); + uint32_t val = float32_val(f32); + uint32_t f32_sign = float32_is_neg(f32); + int f32_exp = extract32(val, 23, 8); + uint32_t f32_frac = extract32(val, 0, 23); + uint64_t f64_frac; + + if (float32_is_any_nan(f32)) { + float32 nan = f32; + if (float32_is_signaling_nan(f32, s)) { + float_raise(float_flag_invalid, s); + if (!s->default_nan_mode) { + nan = float32_silence_nan(f32, s); + } + } + if (s->default_nan_mode) { + nan = float32_default_nan(s); + } + return nan; + } else if (float32_is_zero(f32)) { + float_raise(float_flag_divbyzero, s); + return float32_set_sign(float32_infinity, float32_is_neg(f32)); + } else if (float32_is_neg(f32)) { + float_raise(float_flag_invalid, s); + return float32_default_nan(s); + } else if (float32_is_infinity(f32)) { + return float32_zero; + } + + /* Scale and normalize to a double-precision value between 0.25 and 1.0, + * preserving the parity of the exponent. */ + + f64_frac = ((uint64_t) f32_frac) << 29; + + f64_frac = recip_sqrt_estimate(&f32_exp, 380, f64_frac, rpres); + + /* + * result = sign : result_exp<7:0> : estimate<7:0> : Zeros(15) + * or for increased precision + * result = sign : result_exp<7:0> : estimate<11:0> : Zeros(11) + */ + val = deposit32(0, 31, 1, f32_sign); + val = deposit32(val, 23, 8, f32_exp); + if (rpres) { + val = deposit32(val, 11, 12, extract64(f64_frac, 52 - 12, 12)); + } else { + val = deposit32(val, 15, 8, extract64(f64_frac, 52 - 8, 8)); + } + return make_float32(val); +} + +float32 HELPER(rsqrte_f32)(float32 input, float_status *s) +{ + return do_rsqrte_f32(input, s, false); +} + +float32 HELPER(rsqrte_rpres_f32)(float32 input, float_status *s) +{ + return do_rsqrte_f32(input, s, true); +} + +float64 HELPER(rsqrte_f64)(float64 input, float_status *s) +{ + float64 f64 = float64_squash_input_denormal(input, s); + uint64_t val = float64_val(f64); + bool f64_sign = float64_is_neg(f64); + int f64_exp = extract64(val, 52, 11); + uint64_t f64_frac = extract64(val, 0, 52); + + if (float64_is_any_nan(f64)) { + float64 nan = f64; + if (float64_is_signaling_nan(f64, s)) { + float_raise(float_flag_invalid, s); + if (!s->default_nan_mode) { + nan = float64_silence_nan(f64, s); + } + } + if (s->default_nan_mode) { + nan = float64_default_nan(s); + } + return nan; + } else if (float64_is_zero(f64)) { + float_raise(float_flag_divbyzero, s); + return float64_set_sign(float64_infinity, float64_is_neg(f64)); + } else if (float64_is_neg(f64)) { + float_raise(float_flag_invalid, s); + return float64_default_nan(s); + } else if (float64_is_infinity(f64)) { + return float64_zero; + } + + f64_frac = recip_sqrt_estimate(&f64_exp, 3068, f64_frac, false); + + /* result = sign : result_exp<4:0> : estimate<7:0> : Zeros(44) */ + val = deposit64(0, 61, 1, f64_sign); + val = deposit64(val, 52, 11, f64_exp); + val = deposit64(val, 44, 8, extract64(f64_frac, 52 - 8, 8)); + return make_float64(val); +} + +uint32_t HELPER(recpe_u32)(uint32_t a) +{ + int input, estimate; + + if ((a & 0x80000000) == 0) { + return 0xffffffff; + } + + input = extract32(a, 23, 9); + estimate = recip_estimate(input); + + return deposit32(0, (32 - 9), 9, estimate); +} + +uint32_t HELPER(rsqrte_u32)(uint32_t a) +{ + int estimate; + + if ((a & 0xc0000000) == 0) { + return 0xffffffff; + } + + estimate = do_recip_sqrt_estimate(extract32(a, 23, 9)); + + return deposit32(0, 23, 9, estimate); +} + +/* VFPv4 fused multiply-accumulate */ +dh_ctype_f16 VFP_HELPER(muladd, h)(dh_ctype_f16 a, dh_ctype_f16 b, + dh_ctype_f16 c, float_status *fpst) +{ + return float16_muladd(a, b, c, 0, fpst); +} + +float32 VFP_HELPER(muladd, s)(float32 a, float32 b, float32 c, + float_status *fpst) +{ + return float32_muladd(a, b, c, 0, fpst); +} + +float64 VFP_HELPER(muladd, d)(float64 a, float64 b, float64 c, + float_status *fpst) +{ + return float64_muladd(a, b, c, 0, fpst); +} + +/* ARMv8 round to integral */ +dh_ctype_f16 HELPER(rinth_exact)(dh_ctype_f16 x, float_status *fp_status) +{ + return float16_round_to_int(x, fp_status); +} + +float32 HELPER(rints_exact)(float32 x, float_status *fp_status) +{ + return float32_round_to_int(x, fp_status); +} + +float64 HELPER(rintd_exact)(float64 x, float_status *fp_status) +{ + return float64_round_to_int(x, fp_status); +} + +dh_ctype_f16 HELPER(rinth)(dh_ctype_f16 x, float_status *fp_status) +{ + int old_flags = get_float_exception_flags(fp_status), new_flags; + float16 ret; + + ret = float16_round_to_int(x, fp_status); + + /* Suppress any inexact exceptions the conversion produced */ + if (!(old_flags & float_flag_inexact)) { + new_flags = get_float_exception_flags(fp_status); + set_float_exception_flags(new_flags & ~float_flag_inexact, fp_status); + } + + return ret; +} + +float32 HELPER(rints)(float32 x, float_status *fp_status) +{ + int old_flags = get_float_exception_flags(fp_status), new_flags; + float32 ret; + + ret = float32_round_to_int(x, fp_status); + + /* Suppress any inexact exceptions the conversion produced */ + if (!(old_flags & float_flag_inexact)) { + new_flags = get_float_exception_flags(fp_status); + set_float_exception_flags(new_flags & ~float_flag_inexact, fp_status); + } + + return ret; +} + +float64 HELPER(rintd)(float64 x, float_status *fp_status) +{ + int old_flags = get_float_exception_flags(fp_status), new_flags; + float64 ret; + + ret = float64_round_to_int(x, fp_status); + + /* Suppress any inexact exceptions the conversion produced */ + if (!(old_flags & float_flag_inexact)) { + new_flags = get_float_exception_flags(fp_status); + set_float_exception_flags(new_flags & ~float_flag_inexact, fp_status); + } + + return ret; +} + +/* Convert ARM rounding mode to softfloat */ +const FloatRoundMode arm_rmode_to_sf_map[] = { + [FPROUNDING_TIEEVEN] = float_round_nearest_even, + [FPROUNDING_POSINF] = float_round_up, + [FPROUNDING_NEGINF] = float_round_down, + [FPROUNDING_ZERO] = float_round_to_zero, + [FPROUNDING_TIEAWAY] = float_round_ties_away, + [FPROUNDING_ODD] = float_round_to_odd, +}; + +/* + * Implement float64 to int32_t conversion without saturation; + * the result is supplied modulo 2^32. + */ +uint64_t HELPER(fjcvtzs)(float64 value, float_status *status) +{ + uint32_t frac, e_old, e_new; + bool inexact; + + e_old = get_float_exception_flags(status); + set_float_exception_flags(0, status); + frac = float64_to_int32_modulo(value, float_round_to_zero, status); + e_new = get_float_exception_flags(status); + set_float_exception_flags(e_old | e_new, status); + + /* Normal inexact, denormal with flush-to-zero, or overflow or NaN */ + inexact = e_new & (float_flag_inexact | + float_flag_input_denormal_flushed | + float_flag_invalid); + + /* While not inexact for IEEE FP, -0.0 is inexact for JavaScript. */ + inexact |= value == float64_chs(float64_zero); + + /* Pack the result and the env->ZF representation of Z together. */ + return deposit64(frac, 32, 32, inexact); +} + +uint32_t HELPER(vjcvt)(float64 value, CPUARMState *env) +{ + uint64_t pair = HELPER(fjcvtzs)(value, &env->vfp.fp_status[FPST_A32]); + uint32_t result = pair; + uint32_t z = (pair >> 32) == 0; + + /* Store Z, clear NCV, in FPSCR.NZCV. */ + env->vfp.fpsr = (env->vfp.fpsr & ~FPSR_NZCV_MASK) | (z * FPSR_Z); + + return result; +} + +/* Round a float32 to an integer that fits in int32_t or int64_t. */ +static float32 frint_s(float32 f, float_status *fpst, int intsize) +{ + int old_flags = get_float_exception_flags(fpst); + uint32_t exp = extract32(f, 23, 8); + + if (unlikely(exp == 0xff)) { + /* NaN or Inf. */ + goto overflow; + } + + /* Round and re-extract the exponent. */ + f = float32_round_to_int(f, fpst); + exp = extract32(f, 23, 8); + + /* Validate the range of the result. */ + if (exp < 126 + intsize) { + /* abs(F) <= INT{N}_MAX */ + return f; + } + if (exp == 126 + intsize) { + uint32_t sign = extract32(f, 31, 1); + uint32_t frac = extract32(f, 0, 23); + if (sign && frac == 0) { + /* F == INT{N}_MIN */ + return f; + } + } + + overflow: + /* + * Raise Invalid and return INT{N}_MIN as a float. Revert any + * inexact exception float32_round_to_int may have raised. + */ + set_float_exception_flags(old_flags | float_flag_invalid, fpst); + return (0x100u + 126u + intsize) << 23; +} + +float32 HELPER(frint32_s)(float32 f, float_status *fpst) +{ + return frint_s(f, fpst, 32); +} + +float32 HELPER(frint64_s)(float32 f, float_status *fpst) +{ + return frint_s(f, fpst, 64); +} + +/* Round a float64 to an integer that fits in int32_t or int64_t. */ +static float64 frint_d(float64 f, float_status *fpst, int intsize) +{ + int old_flags = get_float_exception_flags(fpst); + uint32_t exp = extract64(f, 52, 11); + + if (unlikely(exp == 0x7ff)) { + /* NaN or Inf. */ + goto overflow; + } + + /* Round and re-extract the exponent. */ + f = float64_round_to_int(f, fpst); + exp = extract64(f, 52, 11); + + /* Validate the range of the result. */ + if (exp < 1022 + intsize) { + /* abs(F) <= INT{N}_MAX */ + return f; + } + if (exp == 1022 + intsize) { + uint64_t sign = extract64(f, 63, 1); + uint64_t frac = extract64(f, 0, 52); + if (sign && frac == 0) { + /* F == INT{N}_MIN */ + return f; + } + } + + overflow: + /* + * Raise Invalid and return INT{N}_MIN as a float. Revert any + * inexact exception float64_round_to_int may have raised. + */ + set_float_exception_flags(old_flags | float_flag_invalid, fpst); + return (uint64_t)(0x800 + 1022 + intsize) << 52; +} + +float64 HELPER(frint32_d)(float64 f, float_status *fpst) +{ + return frint_d(f, fpst, 32); +} + +float64 HELPER(frint64_d)(float64 f, float_status *fpst) +{ + return frint_d(f, fpst, 64); +} + +void HELPER(check_hcr_el2_trap)(CPUARMState *env, uint32_t rt, uint32_t reg) +{ + uint32_t syndrome; + + switch (reg) { + case ARM_VFP_MVFR0: + case ARM_VFP_MVFR1: + case ARM_VFP_MVFR2: + if (!(arm_hcr_el2_eff(env) & HCR_TID3)) { + return; + } + break; + case ARM_VFP_FPSID: + if (!(arm_hcr_el2_eff(env) & HCR_TID0)) { + return; + } + break; + default: + g_assert_not_reached(); + } + + syndrome = ((EC_FPIDTRAP << ARM_EL_EC_SHIFT) + | ARM_EL_IL + | (1 << 24) | (0xe << 20) | (7 << 14) + | (reg << 10) | (rt << 5) | 1); + + raise_exception(env, EXCP_HYP_TRAP, syndrome, 2); +} diff --git a/target/arm/vfp_helper.c b/target/arm/vfp_helper.c index 5d424477a2d8..0e849d8d4dcc 100644 --- a/target/arm/vfp_helper.c +++ b/target/arm/vfp_helper.c @@ -23,13 +23,6 @@ #include "internals.h" #include "cpu-features.h" #include "fpu/softfloat.h" -#ifdef CONFIG_TCG -#include "qemu/log.h" -#endif - -/* VFP support. We follow the convention used for VFP instructions: - Single precision routines have a "s" suffix, double precision a - "d" suffix. */ /* * Set the float_status behaviour to match the Arm defaults: @@ -419,1105 +412,3 @@ void vfp_set_fpscr(CPUARMState *env, uint32_t val) { HELPER(vfp_set_fpscr)(env, val); } - -#ifdef CONFIG_TCG - -#define VFP_HELPER(name, p) HELPER(glue(glue(vfp_,name),p)) - -#define VFP_BINOP(name) \ -dh_ctype_f16 VFP_HELPER(name, h)(dh_ctype_f16 a, dh_ctype_f16 b, float_status *fpst) \ -{ \ - return float16_ ## name(a, b, fpst); \ -} \ -float32 VFP_HELPER(name, s)(float32 a, float32 b, float_status *fpst) \ -{ \ - return float32_ ## name(a, b, fpst); \ -} \ -float64 VFP_HELPER(name, d)(float64 a, float64 b, float_status *fpst) \ -{ \ - return float64_ ## name(a, b, fpst); \ -} -VFP_BINOP(add) -VFP_BINOP(sub) -VFP_BINOP(mul) -VFP_BINOP(div) -VFP_BINOP(min) -VFP_BINOP(max) -VFP_BINOP(minnum) -VFP_BINOP(maxnum) -#undef VFP_BINOP - -dh_ctype_f16 VFP_HELPER(sqrt, h)(dh_ctype_f16 a, float_status *fpst) -{ - return float16_sqrt(a, fpst); -} - -float32 VFP_HELPER(sqrt, s)(float32 a, float_status *fpst) -{ - return float32_sqrt(a, fpst); -} - -float64 VFP_HELPER(sqrt, d)(float64 a, float_status *fpst) -{ - return float64_sqrt(a, fpst); -} - -static void softfloat_to_vfp_compare(CPUARMState *env, FloatRelation cmp) -{ - uint32_t flags; - switch (cmp) { - case float_relation_equal: - flags = 0x6; - break; - case float_relation_less: - flags = 0x8; - break; - case float_relation_greater: - flags = 0x2; - break; - case float_relation_unordered: - flags = 0x3; - break; - default: - g_assert_not_reached(); - } - env->vfp.fpsr = deposit64(env->vfp.fpsr, 28, 4, flags); /* NZCV */ -} - -/* XXX: check quiet/signaling case */ -#define DO_VFP_cmp(P, FLOATTYPE, ARGTYPE, FPST) \ -void VFP_HELPER(cmp, P)(ARGTYPE a, ARGTYPE b, CPUARMState *env) \ -{ \ - softfloat_to_vfp_compare(env, \ - FLOATTYPE ## _compare_quiet(a, b, &env->vfp.fp_status[FPST])); \ -} \ -void VFP_HELPER(cmpe, P)(ARGTYPE a, ARGTYPE b, CPUARMState *env) \ -{ \ - softfloat_to_vfp_compare(env, \ - FLOATTYPE ## _compare(a, b, &env->vfp.fp_status[FPST])); \ -} -DO_VFP_cmp(h, float16, dh_ctype_f16, FPST_A32_F16) -DO_VFP_cmp(s, float32, float32, FPST_A32) -DO_VFP_cmp(d, float64, float64, FPST_A32) -#undef DO_VFP_cmp - -/* Integer to float and float to integer conversions */ - -#define CONV_ITOF(name, ftype, fsz, sign) \ -ftype HELPER(name)(uint32_t x, float_status *fpst) \ -{ \ - return sign##int32_to_##float##fsz((sign##int32_t)x, fpst); \ -} - -#define CONV_FTOI(name, ftype, fsz, sign, round) \ -sign##int32_t HELPER(name)(ftype x, float_status *fpst) \ -{ \ - if (float##fsz##_is_any_nan(x)) { \ - float_raise(float_flag_invalid, fpst); \ - return 0; \ - } \ - return float##fsz##_to_##sign##int32##round(x, fpst); \ -} - -#define FLOAT_CONVS(name, p, ftype, fsz, sign) \ - CONV_ITOF(vfp_##name##to##p, ftype, fsz, sign) \ - CONV_FTOI(vfp_to##name##p, ftype, fsz, sign, ) \ - CONV_FTOI(vfp_to##name##z##p, ftype, fsz, sign, _round_to_zero) - -FLOAT_CONVS(si, h, uint32_t, 16, ) -FLOAT_CONVS(si, s, float32, 32, ) -FLOAT_CONVS(si, d, float64, 64, ) -FLOAT_CONVS(ui, h, uint32_t, 16, u) -FLOAT_CONVS(ui, s, float32, 32, u) -FLOAT_CONVS(ui, d, float64, 64, u) - -#undef CONV_ITOF -#undef CONV_FTOI -#undef FLOAT_CONVS - -/* floating point conversion */ -float64 VFP_HELPER(fcvtd, s)(float32 x, float_status *status) -{ - return float32_to_float64(x, status); -} - -float32 VFP_HELPER(fcvts, d)(float64 x, float_status *status) -{ - return float64_to_float32(x, status); -} - -uint32_t HELPER(bfcvt)(float32 x, float_status *status) -{ - return float32_to_bfloat16(x, status); -} - -uint32_t HELPER(bfcvt_pair)(uint64_t pair, float_status *status) -{ - bfloat16 lo = float32_to_bfloat16(extract64(pair, 0, 32), status); - bfloat16 hi = float32_to_bfloat16(extract64(pair, 32, 32), status); - return deposit32(lo, 16, 16, hi); -} - -/* - * VFP3 fixed point conversion. The AArch32 versions of fix-to-float - * must always round-to-nearest; the AArch64 ones honour the FPSCR - * rounding mode. (For AArch32 Neon the standard-FPSCR is set to - * round-to-nearest so either helper will work.) AArch32 float-to-fix - * must round-to-zero. - */ -#define VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ -ftype HELPER(vfp_##name##to##p)(uint##isz##_t x, uint32_t shift, \ - float_status *fpst) \ -{ return itype##_to_##float##fsz##_scalbn(x, -shift, fpst); } - -#define VFP_CONV_FIX_FLOAT_ROUND(name, p, fsz, ftype, isz, itype) \ - ftype HELPER(vfp_##name##to##p##_round_to_nearest)(uint##isz##_t x, \ - uint32_t shift, \ - float_status *fpst) \ - { \ - ftype ret; \ - FloatRoundMode oldmode = fpst->float_rounding_mode; \ - fpst->float_rounding_mode = float_round_nearest_even; \ - ret = itype##_to_##float##fsz##_scalbn(x, -shift, fpst); \ - fpst->float_rounding_mode = oldmode; \ - return ret; \ - } - -#define VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, ROUND, suff) \ -uint##isz##_t HELPER(vfp_to##name##p##suff)(ftype x, uint32_t shift, \ - float_status *fpst) \ -{ \ - if (unlikely(float##fsz##_is_any_nan(x))) { \ - float_raise(float_flag_invalid, fpst); \ - return 0; \ - } \ - return float##fsz##_to_##itype##_scalbn(x, ROUND, shift, fpst); \ -} - -#define VFP_CONV_FIX(name, p, fsz, ftype, isz, itype) \ -VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ -VFP_CONV_FIX_FLOAT_ROUND(name, p, fsz, ftype, isz, itype) \ -VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ - float_round_to_zero, _round_to_zero) \ -VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ - get_float_rounding_mode(fpst), ) - -#define VFP_CONV_FIX_A64(name, p, fsz, ftype, isz, itype) \ -VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ -VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ - get_float_rounding_mode(fpst), ) - -VFP_CONV_FIX(sh, d, 64, float64, 64, int16) -VFP_CONV_FIX(sl, d, 64, float64, 64, int32) -VFP_CONV_FIX_A64(sq, d, 64, float64, 64, int64) -VFP_CONV_FIX(uh, d, 64, float64, 64, uint16) -VFP_CONV_FIX(ul, d, 64, float64, 64, uint32) -VFP_CONV_FIX_A64(uq, d, 64, float64, 64, uint64) -VFP_CONV_FIX(sh, s, 32, float32, 32, int16) -VFP_CONV_FIX(sl, s, 32, float32, 32, int32) -VFP_CONV_FIX_A64(sq, s, 32, float32, 64, int64) -VFP_CONV_FIX(uh, s, 32, float32, 32, uint16) -VFP_CONV_FIX(ul, s, 32, float32, 32, uint32) -VFP_CONV_FIX_A64(uq, s, 32, float32, 64, uint64) -VFP_CONV_FIX(sh, h, 16, dh_ctype_f16, 32, int16) -VFP_CONV_FIX(sl, h, 16, dh_ctype_f16, 32, int32) -VFP_CONV_FIX_A64(sq, h, 16, dh_ctype_f16, 64, int64) -VFP_CONV_FIX(uh, h, 16, dh_ctype_f16, 32, uint16) -VFP_CONV_FIX(ul, h, 16, dh_ctype_f16, 32, uint32) -VFP_CONV_FIX_A64(uq, h, 16, dh_ctype_f16, 64, uint64) -VFP_CONV_FLOAT_FIX_ROUND(sq, d, 64, float64, 64, int64, - float_round_to_zero, _round_to_zero) -VFP_CONV_FLOAT_FIX_ROUND(uq, d, 64, float64, 64, uint64, - float_round_to_zero, _round_to_zero) - -#undef VFP_CONV_FIX -#undef VFP_CONV_FIX_FLOAT -#undef VFP_CONV_FLOAT_FIX_ROUND -#undef VFP_CONV_FIX_A64 - -/* Set the current fp rounding mode and return the old one. - * The argument is a softfloat float_round_ value. - */ -uint32_t HELPER(set_rmode)(uint32_t rmode, float_status *fp_status) -{ - uint32_t prev_rmode = get_float_rounding_mode(fp_status); - set_float_rounding_mode(rmode, fp_status); - - return prev_rmode; -} - -/* Half precision conversions. */ -float32 HELPER(vfp_fcvt_f16_to_f32)(uint32_t a, float_status *fpst, - uint32_t ahp_mode) -{ - /* Squash FZ16 to 0 for the duration of conversion. In this case, - * it would affect flushing input denormals. - */ - bool save = get_flush_inputs_to_zero(fpst); - set_flush_inputs_to_zero(false, fpst); - float32 r = float16_to_float32(a, !ahp_mode, fpst); - set_flush_inputs_to_zero(save, fpst); - return r; -} - -uint32_t HELPER(vfp_fcvt_f32_to_f16)(float32 a, float_status *fpst, - uint32_t ahp_mode) -{ - /* Squash FZ16 to 0 for the duration of conversion. In this case, - * it would affect flushing output denormals. - */ - bool save = get_flush_to_zero(fpst); - set_flush_to_zero(false, fpst); - float16 r = float32_to_float16(a, !ahp_mode, fpst); - set_flush_to_zero(save, fpst); - return r; -} - -float64 HELPER(vfp_fcvt_f16_to_f64)(uint32_t a, float_status *fpst, - uint32_t ahp_mode) -{ - /* Squash FZ16 to 0 for the duration of conversion. In this case, - * it would affect flushing input denormals. - */ - bool save = get_flush_inputs_to_zero(fpst); - set_flush_inputs_to_zero(false, fpst); - float64 r = float16_to_float64(a, !ahp_mode, fpst); - set_flush_inputs_to_zero(save, fpst); - return r; -} - -uint32_t HELPER(vfp_fcvt_f64_to_f16)(float64 a, float_status *fpst, - uint32_t ahp_mode) -{ - /* Squash FZ16 to 0 for the duration of conversion. In this case, - * it would affect flushing output denormals. - */ - bool save = get_flush_to_zero(fpst); - set_flush_to_zero(false, fpst); - float16 r = float64_to_float16(a, !ahp_mode, fpst); - set_flush_to_zero(save, fpst); - return r; -} - -/* NEON helpers. */ - -/* Constants 256 and 512 are used in some helpers; we avoid relying on - * int->float conversions at run-time. */ -#define float64_256 make_float64(0x4070000000000000LL) -#define float64_512 make_float64(0x4080000000000000LL) -#define float16_maxnorm make_float16(0x7bff) -#define float32_maxnorm make_float32(0x7f7fffff) -#define float64_maxnorm make_float64(0x7fefffffffffffffLL) - -/* Reciprocal functions - * - * The algorithm that must be used to calculate the estimate - * is specified by the ARM ARM, see FPRecipEstimate()/RecipEstimate - */ - -/* See RecipEstimate() - * - * input is a 9 bit fixed point number - * input range 256 .. 511 for a number from 0.5 <= x < 1.0. - * result range 256 .. 511 for a number from 1.0 to 511/256. - */ - -static int recip_estimate(int input) -{ - int a, b, r; - assert(256 <= input && input < 512); - a = (input * 2) + 1; - b = (1 << 19) / a; - r = (b + 1) >> 1; - assert(256 <= r && r < 512); - return r; -} - -/* - * Increased precision version: - * input is a 13 bit fixed point number - * input range 2048 .. 4095 for a number from 0.5 <= x < 1.0. - * result range 4096 .. 8191 for a number from 1.0 to 2.0 - */ -static int recip_estimate_incprec(int input) -{ - int a, b, r; - assert(2048 <= input && input < 4096); - a = (input * 2) + 1; - /* - * The pseudocode expresses this as an operation on infinite - * precision reals where it calculates 2^25 / a and then looks - * at the error between that and the rounded-down-to-integer - * value to see if it should instead round up. We instead - * follow the same approach as the pseudocode for the 8-bit - * precision version, and calculate (2 * (2^25 / a)) as an - * integer so we can do the "add one and halve" to round it. - * So the 1 << 26 here is correct. - */ - b = (1 << 26) / a; - r = (b + 1) >> 1; - assert(4096 <= r && r < 8192); - return r; -} - -/* - * Common wrapper to call recip_estimate - * - * The parameters are exponent and 64 bit fraction (without implicit - * bit) where the binary point is nominally at bit 52. Returns a - * float64 which can then be rounded to the appropriate size by the - * callee. - */ - -static uint64_t call_recip_estimate(int *exp, int exp_off, uint64_t frac, - bool increasedprecision) -{ - uint32_t scaled, estimate; - uint64_t result_frac; - int result_exp; - - /* Handle sub-normals */ - if (*exp == 0) { - if (extract64(frac, 51, 1) == 0) { - *exp = -1; - frac <<= 2; - } else { - frac <<= 1; - } - } - - if (increasedprecision) { - /* scaled = UInt('1':fraction<51:41>) */ - scaled = deposit32(1 << 11, 0, 11, extract64(frac, 41, 11)); - estimate = recip_estimate_incprec(scaled); - } else { - /* scaled = UInt('1':fraction<51:44>) */ - scaled = deposit32(1 << 8, 0, 8, extract64(frac, 44, 8)); - estimate = recip_estimate(scaled); - } - - result_exp = exp_off - *exp; - if (increasedprecision) { - result_frac = deposit64(0, 40, 12, estimate); - } else { - result_frac = deposit64(0, 44, 8, estimate); - } - if (result_exp == 0) { - result_frac = deposit64(result_frac >> 1, 51, 1, 1); - } else if (result_exp == -1) { - result_frac = deposit64(result_frac >> 2, 50, 2, 1); - result_exp = 0; - } - - *exp = result_exp; - - return result_frac; -} - -static bool round_to_inf(float_status *fpst, bool sign_bit) -{ - switch (fpst->float_rounding_mode) { - case float_round_nearest_even: /* Round to Nearest */ - return true; - case float_round_up: /* Round to +Inf */ - return !sign_bit; - case float_round_down: /* Round to -Inf */ - return sign_bit; - case float_round_to_zero: /* Round to Zero */ - return false; - default: - g_assert_not_reached(); - } -} - -uint32_t HELPER(recpe_f16)(uint32_t input, float_status *fpst) -{ - float16 f16 = float16_squash_input_denormal(input, fpst); - uint32_t f16_val = float16_val(f16); - uint32_t f16_sign = float16_is_neg(f16); - int f16_exp = extract32(f16_val, 10, 5); - uint32_t f16_frac = extract32(f16_val, 0, 10); - uint64_t f64_frac; - - if (float16_is_any_nan(f16)) { - float16 nan = f16; - if (float16_is_signaling_nan(f16, fpst)) { - float_raise(float_flag_invalid, fpst); - if (!fpst->default_nan_mode) { - nan = float16_silence_nan(f16, fpst); - } - } - if (fpst->default_nan_mode) { - nan = float16_default_nan(fpst); - } - return nan; - } else if (float16_is_infinity(f16)) { - return float16_set_sign(float16_zero, float16_is_neg(f16)); - } else if (float16_is_zero(f16)) { - float_raise(float_flag_divbyzero, fpst); - return float16_set_sign(float16_infinity, float16_is_neg(f16)); - } else if (float16_abs(f16) < (1 << 8)) { - /* Abs(value) < 2.0^-16 */ - float_raise(float_flag_overflow | float_flag_inexact, fpst); - if (round_to_inf(fpst, f16_sign)) { - return float16_set_sign(float16_infinity, f16_sign); - } else { - return float16_set_sign(float16_maxnorm, f16_sign); - } - } else if (f16_exp >= 29 && fpst->flush_to_zero) { - float_raise(float_flag_underflow, fpst); - return float16_set_sign(float16_zero, float16_is_neg(f16)); - } - - f64_frac = call_recip_estimate(&f16_exp, 29, - ((uint64_t) f16_frac) << (52 - 10), false); - - /* result = sign : result_exp<4:0> : fraction<51:42> */ - f16_val = deposit32(0, 15, 1, f16_sign); - f16_val = deposit32(f16_val, 10, 5, f16_exp); - f16_val = deposit32(f16_val, 0, 10, extract64(f64_frac, 52 - 10, 10)); - return make_float16(f16_val); -} - -/* - * FEAT_RPRES means the f32 FRECPE has an "increased precision" variant - * which is used when FPCR.AH == 1. - */ -static float32 do_recpe_f32(float32 input, float_status *fpst, bool rpres) -{ - float32 f32 = float32_squash_input_denormal(input, fpst); - uint32_t f32_val = float32_val(f32); - bool f32_sign = float32_is_neg(f32); - int f32_exp = extract32(f32_val, 23, 8); - uint32_t f32_frac = extract32(f32_val, 0, 23); - uint64_t f64_frac; - - if (float32_is_any_nan(f32)) { - float32 nan = f32; - if (float32_is_signaling_nan(f32, fpst)) { - float_raise(float_flag_invalid, fpst); - if (!fpst->default_nan_mode) { - nan = float32_silence_nan(f32, fpst); - } - } - if (fpst->default_nan_mode) { - nan = float32_default_nan(fpst); - } - return nan; - } else if (float32_is_infinity(f32)) { - return float32_set_sign(float32_zero, float32_is_neg(f32)); - } else if (float32_is_zero(f32)) { - float_raise(float_flag_divbyzero, fpst); - return float32_set_sign(float32_infinity, float32_is_neg(f32)); - } else if (float32_abs(f32) < (1ULL << 21)) { - /* Abs(value) < 2.0^-128 */ - float_raise(float_flag_overflow | float_flag_inexact, fpst); - if (round_to_inf(fpst, f32_sign)) { - return float32_set_sign(float32_infinity, f32_sign); - } else { - return float32_set_sign(float32_maxnorm, f32_sign); - } - } else if (f32_exp >= 253 && fpst->flush_to_zero) { - float_raise(float_flag_underflow, fpst); - return float32_set_sign(float32_zero, float32_is_neg(f32)); - } - - f64_frac = call_recip_estimate(&f32_exp, 253, - ((uint64_t) f32_frac) << (52 - 23), rpres); - - /* result = sign : result_exp<7:0> : fraction<51:29> */ - f32_val = deposit32(0, 31, 1, f32_sign); - f32_val = deposit32(f32_val, 23, 8, f32_exp); - f32_val = deposit32(f32_val, 0, 23, extract64(f64_frac, 52 - 23, 23)); - return make_float32(f32_val); -} - -float32 HELPER(recpe_f32)(float32 input, float_status *fpst) -{ - return do_recpe_f32(input, fpst, false); -} - -float32 HELPER(recpe_rpres_f32)(float32 input, float_status *fpst) -{ - return do_recpe_f32(input, fpst, true); -} - -float64 HELPER(recpe_f64)(float64 input, float_status *fpst) -{ - float64 f64 = float64_squash_input_denormal(input, fpst); - uint64_t f64_val = float64_val(f64); - bool f64_sign = float64_is_neg(f64); - int f64_exp = extract64(f64_val, 52, 11); - uint64_t f64_frac = extract64(f64_val, 0, 52); - - /* Deal with any special cases */ - if (float64_is_any_nan(f64)) { - float64 nan = f64; - if (float64_is_signaling_nan(f64, fpst)) { - float_raise(float_flag_invalid, fpst); - if (!fpst->default_nan_mode) { - nan = float64_silence_nan(f64, fpst); - } - } - if (fpst->default_nan_mode) { - nan = float64_default_nan(fpst); - } - return nan; - } else if (float64_is_infinity(f64)) { - return float64_set_sign(float64_zero, float64_is_neg(f64)); - } else if (float64_is_zero(f64)) { - float_raise(float_flag_divbyzero, fpst); - return float64_set_sign(float64_infinity, float64_is_neg(f64)); - } else if ((f64_val & ~(1ULL << 63)) < (1ULL << 50)) { - /* Abs(value) < 2.0^-1024 */ - float_raise(float_flag_overflow | float_flag_inexact, fpst); - if (round_to_inf(fpst, f64_sign)) { - return float64_set_sign(float64_infinity, f64_sign); - } else { - return float64_set_sign(float64_maxnorm, f64_sign); - } - } else if (f64_exp >= 2045 && fpst->flush_to_zero) { - float_raise(float_flag_underflow, fpst); - return float64_set_sign(float64_zero, float64_is_neg(f64)); - } - - f64_frac = call_recip_estimate(&f64_exp, 2045, f64_frac, false); - - /* result = sign : result_exp<10:0> : fraction<51:0>; */ - f64_val = deposit64(0, 63, 1, f64_sign); - f64_val = deposit64(f64_val, 52, 11, f64_exp); - f64_val = deposit64(f64_val, 0, 52, f64_frac); - return make_float64(f64_val); -} - -/* The algorithm that must be used to calculate the estimate - * is specified by the ARM ARM. - */ - -static int do_recip_sqrt_estimate(int a) -{ - int b, estimate; - - assert(128 <= a && a < 512); - if (a < 256) { - a = a * 2 + 1; - } else { - a = (a >> 1) << 1; - a = (a + 1) * 2; - } - b = 512; - while (a * (b + 1) * (b + 1) < (1 << 28)) { - b += 1; - } - estimate = (b + 1) / 2; - assert(256 <= estimate && estimate < 512); - - return estimate; -} - -static int do_recip_sqrt_estimate_incprec(int a) -{ - /* - * The Arm ARM describes the 12-bit precision version of RecipSqrtEstimate - * in terms of an infinite-precision floating point calculation of a - * square root. We implement this using the same kind of pure integer - * algorithm as the 8-bit mantissa, to get the same bit-for-bit result. - */ - int64_t b, estimate; - - assert(1024 <= a && a < 4096); - if (a < 2048) { - a = a * 2 + 1; - } else { - a = (a >> 1) << 1; - a = (a + 1) * 2; - } - b = 8192; - while (a * (b + 1) * (b + 1) < (1ULL << 39)) { - b += 1; - } - estimate = (b + 1) / 2; - - assert(4096 <= estimate && estimate < 8192); - - return estimate; -} - -static uint64_t recip_sqrt_estimate(int *exp , int exp_off, uint64_t frac, - bool increasedprecision) -{ - int estimate; - uint32_t scaled; - - if (*exp == 0) { - while (extract64(frac, 51, 1) == 0) { - frac = frac << 1; - *exp -= 1; - } - frac = extract64(frac, 0, 51) << 1; - } - - if (increasedprecision) { - if (*exp & 1) { - /* scaled = UInt('01':fraction<51:42>) */ - scaled = deposit32(1 << 10, 0, 10, extract64(frac, 42, 10)); - } else { - /* scaled = UInt('1':fraction<51:41>) */ - scaled = deposit32(1 << 11, 0, 11, extract64(frac, 41, 11)); - } - estimate = do_recip_sqrt_estimate_incprec(scaled); - } else { - if (*exp & 1) { - /* scaled = UInt('01':fraction<51:45>) */ - scaled = deposit32(1 << 7, 0, 7, extract64(frac, 45, 7)); - } else { - /* scaled = UInt('1':fraction<51:44>) */ - scaled = deposit32(1 << 8, 0, 8, extract64(frac, 44, 8)); - } - estimate = do_recip_sqrt_estimate(scaled); - } - - *exp = (exp_off - *exp) / 2; - if (increasedprecision) { - return extract64(estimate, 0, 12) << 40; - } else { - return extract64(estimate, 0, 8) << 44; - } -} - -uint32_t HELPER(rsqrte_f16)(uint32_t input, float_status *s) -{ - float16 f16 = float16_squash_input_denormal(input, s); - uint16_t val = float16_val(f16); - bool f16_sign = float16_is_neg(f16); - int f16_exp = extract32(val, 10, 5); - uint16_t f16_frac = extract32(val, 0, 10); - uint64_t f64_frac; - - if (float16_is_any_nan(f16)) { - float16 nan = f16; - if (float16_is_signaling_nan(f16, s)) { - float_raise(float_flag_invalid, s); - if (!s->default_nan_mode) { - nan = float16_silence_nan(f16, s); - } - } - if (s->default_nan_mode) { - nan = float16_default_nan(s); - } - return nan; - } else if (float16_is_zero(f16)) { - float_raise(float_flag_divbyzero, s); - return float16_set_sign(float16_infinity, f16_sign); - } else if (f16_sign) { - float_raise(float_flag_invalid, s); - return float16_default_nan(s); - } else if (float16_is_infinity(f16)) { - return float16_zero; - } - - /* Scale and normalize to a double-precision value between 0.25 and 1.0, - * preserving the parity of the exponent. */ - - f64_frac = ((uint64_t) f16_frac) << (52 - 10); - - f64_frac = recip_sqrt_estimate(&f16_exp, 44, f64_frac, false); - - /* result = sign : result_exp<4:0> : estimate<7:0> : Zeros(2) */ - val = deposit32(0, 15, 1, f16_sign); - val = deposit32(val, 10, 5, f16_exp); - val = deposit32(val, 2, 8, extract64(f64_frac, 52 - 8, 8)); - return make_float16(val); -} - -/* - * FEAT_RPRES means the f32 FRSQRTE has an "increased precision" variant - * which is used when FPCR.AH == 1. - */ -static float32 do_rsqrte_f32(float32 input, float_status *s, bool rpres) -{ - float32 f32 = float32_squash_input_denormal(input, s); - uint32_t val = float32_val(f32); - uint32_t f32_sign = float32_is_neg(f32); - int f32_exp = extract32(val, 23, 8); - uint32_t f32_frac = extract32(val, 0, 23); - uint64_t f64_frac; - - if (float32_is_any_nan(f32)) { - float32 nan = f32; - if (float32_is_signaling_nan(f32, s)) { - float_raise(float_flag_invalid, s); - if (!s->default_nan_mode) { - nan = float32_silence_nan(f32, s); - } - } - if (s->default_nan_mode) { - nan = float32_default_nan(s); - } - return nan; - } else if (float32_is_zero(f32)) { - float_raise(float_flag_divbyzero, s); - return float32_set_sign(float32_infinity, float32_is_neg(f32)); - } else if (float32_is_neg(f32)) { - float_raise(float_flag_invalid, s); - return float32_default_nan(s); - } else if (float32_is_infinity(f32)) { - return float32_zero; - } - - /* Scale and normalize to a double-precision value between 0.25 and 1.0, - * preserving the parity of the exponent. */ - - f64_frac = ((uint64_t) f32_frac) << 29; - - f64_frac = recip_sqrt_estimate(&f32_exp, 380, f64_frac, rpres); - - /* - * result = sign : result_exp<7:0> : estimate<7:0> : Zeros(15) - * or for increased precision - * result = sign : result_exp<7:0> : estimate<11:0> : Zeros(11) - */ - val = deposit32(0, 31, 1, f32_sign); - val = deposit32(val, 23, 8, f32_exp); - if (rpres) { - val = deposit32(val, 11, 12, extract64(f64_frac, 52 - 12, 12)); - } else { - val = deposit32(val, 15, 8, extract64(f64_frac, 52 - 8, 8)); - } - return make_float32(val); -} - -float32 HELPER(rsqrte_f32)(float32 input, float_status *s) -{ - return do_rsqrte_f32(input, s, false); -} - -float32 HELPER(rsqrte_rpres_f32)(float32 input, float_status *s) -{ - return do_rsqrte_f32(input, s, true); -} - -float64 HELPER(rsqrte_f64)(float64 input, float_status *s) -{ - float64 f64 = float64_squash_input_denormal(input, s); - uint64_t val = float64_val(f64); - bool f64_sign = float64_is_neg(f64); - int f64_exp = extract64(val, 52, 11); - uint64_t f64_frac = extract64(val, 0, 52); - - if (float64_is_any_nan(f64)) { - float64 nan = f64; - if (float64_is_signaling_nan(f64, s)) { - float_raise(float_flag_invalid, s); - if (!s->default_nan_mode) { - nan = float64_silence_nan(f64, s); - } - } - if (s->default_nan_mode) { - nan = float64_default_nan(s); - } - return nan; - } else if (float64_is_zero(f64)) { - float_raise(float_flag_divbyzero, s); - return float64_set_sign(float64_infinity, float64_is_neg(f64)); - } else if (float64_is_neg(f64)) { - float_raise(float_flag_invalid, s); - return float64_default_nan(s); - } else if (float64_is_infinity(f64)) { - return float64_zero; - } - - f64_frac = recip_sqrt_estimate(&f64_exp, 3068, f64_frac, false); - - /* result = sign : result_exp<4:0> : estimate<7:0> : Zeros(44) */ - val = deposit64(0, 61, 1, f64_sign); - val = deposit64(val, 52, 11, f64_exp); - val = deposit64(val, 44, 8, extract64(f64_frac, 52 - 8, 8)); - return make_float64(val); -} - -uint32_t HELPER(recpe_u32)(uint32_t a) -{ - int input, estimate; - - if ((a & 0x80000000) == 0) { - return 0xffffffff; - } - - input = extract32(a, 23, 9); - estimate = recip_estimate(input); - - return deposit32(0, (32 - 9), 9, estimate); -} - -uint32_t HELPER(rsqrte_u32)(uint32_t a) -{ - int estimate; - - if ((a & 0xc0000000) == 0) { - return 0xffffffff; - } - - estimate = do_recip_sqrt_estimate(extract32(a, 23, 9)); - - return deposit32(0, 23, 9, estimate); -} - -/* VFPv4 fused multiply-accumulate */ -dh_ctype_f16 VFP_HELPER(muladd, h)(dh_ctype_f16 a, dh_ctype_f16 b, - dh_ctype_f16 c, float_status *fpst) -{ - return float16_muladd(a, b, c, 0, fpst); -} - -float32 VFP_HELPER(muladd, s)(float32 a, float32 b, float32 c, - float_status *fpst) -{ - return float32_muladd(a, b, c, 0, fpst); -} - -float64 VFP_HELPER(muladd, d)(float64 a, float64 b, float64 c, - float_status *fpst) -{ - return float64_muladd(a, b, c, 0, fpst); -} - -/* ARMv8 round to integral */ -dh_ctype_f16 HELPER(rinth_exact)(dh_ctype_f16 x, float_status *fp_status) -{ - return float16_round_to_int(x, fp_status); -} - -float32 HELPER(rints_exact)(float32 x, float_status *fp_status) -{ - return float32_round_to_int(x, fp_status); -} - -float64 HELPER(rintd_exact)(float64 x, float_status *fp_status) -{ - return float64_round_to_int(x, fp_status); -} - -dh_ctype_f16 HELPER(rinth)(dh_ctype_f16 x, float_status *fp_status) -{ - int old_flags = get_float_exception_flags(fp_status), new_flags; - float16 ret; - - ret = float16_round_to_int(x, fp_status); - - /* Suppress any inexact exceptions the conversion produced */ - if (!(old_flags & float_flag_inexact)) { - new_flags = get_float_exception_flags(fp_status); - set_float_exception_flags(new_flags & ~float_flag_inexact, fp_status); - } - - return ret; -} - -float32 HELPER(rints)(float32 x, float_status *fp_status) -{ - int old_flags = get_float_exception_flags(fp_status), new_flags; - float32 ret; - - ret = float32_round_to_int(x, fp_status); - - /* Suppress any inexact exceptions the conversion produced */ - if (!(old_flags & float_flag_inexact)) { - new_flags = get_float_exception_flags(fp_status); - set_float_exception_flags(new_flags & ~float_flag_inexact, fp_status); - } - - return ret; -} - -float64 HELPER(rintd)(float64 x, float_status *fp_status) -{ - int old_flags = get_float_exception_flags(fp_status), new_flags; - float64 ret; - - ret = float64_round_to_int(x, fp_status); - - /* Suppress any inexact exceptions the conversion produced */ - if (!(old_flags & float_flag_inexact)) { - new_flags = get_float_exception_flags(fp_status); - set_float_exception_flags(new_flags & ~float_flag_inexact, fp_status); - } - - return ret; -} - -/* Convert ARM rounding mode to softfloat */ -const FloatRoundMode arm_rmode_to_sf_map[] = { - [FPROUNDING_TIEEVEN] = float_round_nearest_even, - [FPROUNDING_POSINF] = float_round_up, - [FPROUNDING_NEGINF] = float_round_down, - [FPROUNDING_ZERO] = float_round_to_zero, - [FPROUNDING_TIEAWAY] = float_round_ties_away, - [FPROUNDING_ODD] = float_round_to_odd, -}; - -/* - * Implement float64 to int32_t conversion without saturation; - * the result is supplied modulo 2^32. - */ -uint64_t HELPER(fjcvtzs)(float64 value, float_status *status) -{ - uint32_t frac, e_old, e_new; - bool inexact; - - e_old = get_float_exception_flags(status); - set_float_exception_flags(0, status); - frac = float64_to_int32_modulo(value, float_round_to_zero, status); - e_new = get_float_exception_flags(status); - set_float_exception_flags(e_old | e_new, status); - - /* Normal inexact, denormal with flush-to-zero, or overflow or NaN */ - inexact = e_new & (float_flag_inexact | - float_flag_input_denormal_flushed | - float_flag_invalid); - - /* While not inexact for IEEE FP, -0.0 is inexact for JavaScript. */ - inexact |= value == float64_chs(float64_zero); - - /* Pack the result and the env->ZF representation of Z together. */ - return deposit64(frac, 32, 32, inexact); -} - -uint32_t HELPER(vjcvt)(float64 value, CPUARMState *env) -{ - uint64_t pair = HELPER(fjcvtzs)(value, &env->vfp.fp_status[FPST_A32]); - uint32_t result = pair; - uint32_t z = (pair >> 32) == 0; - - /* Store Z, clear NCV, in FPSCR.NZCV. */ - env->vfp.fpsr = (env->vfp.fpsr & ~FPSR_NZCV_MASK) | (z * FPSR_Z); - - return result; -} - -/* Round a float32 to an integer that fits in int32_t or int64_t. */ -static float32 frint_s(float32 f, float_status *fpst, int intsize) -{ - int old_flags = get_float_exception_flags(fpst); - uint32_t exp = extract32(f, 23, 8); - - if (unlikely(exp == 0xff)) { - /* NaN or Inf. */ - goto overflow; - } - - /* Round and re-extract the exponent. */ - f = float32_round_to_int(f, fpst); - exp = extract32(f, 23, 8); - - /* Validate the range of the result. */ - if (exp < 126 + intsize) { - /* abs(F) <= INT{N}_MAX */ - return f; - } - if (exp == 126 + intsize) { - uint32_t sign = extract32(f, 31, 1); - uint32_t frac = extract32(f, 0, 23); - if (sign && frac == 0) { - /* F == INT{N}_MIN */ - return f; - } - } - - overflow: - /* - * Raise Invalid and return INT{N}_MIN as a float. Revert any - * inexact exception float32_round_to_int may have raised. - */ - set_float_exception_flags(old_flags | float_flag_invalid, fpst); - return (0x100u + 126u + intsize) << 23; -} - -float32 HELPER(frint32_s)(float32 f, float_status *fpst) -{ - return frint_s(f, fpst, 32); -} - -float32 HELPER(frint64_s)(float32 f, float_status *fpst) -{ - return frint_s(f, fpst, 64); -} - -/* Round a float64 to an integer that fits in int32_t or int64_t. */ -static float64 frint_d(float64 f, float_status *fpst, int intsize) -{ - int old_flags = get_float_exception_flags(fpst); - uint32_t exp = extract64(f, 52, 11); - - if (unlikely(exp == 0x7ff)) { - /* NaN or Inf. */ - goto overflow; - } - - /* Round and re-extract the exponent. */ - f = float64_round_to_int(f, fpst); - exp = extract64(f, 52, 11); - - /* Validate the range of the result. */ - if (exp < 1022 + intsize) { - /* abs(F) <= INT{N}_MAX */ - return f; - } - if (exp == 1022 + intsize) { - uint64_t sign = extract64(f, 63, 1); - uint64_t frac = extract64(f, 0, 52); - if (sign && frac == 0) { - /* F == INT{N}_MIN */ - return f; - } - } - - overflow: - /* - * Raise Invalid and return INT{N}_MIN as a float. Revert any - * inexact exception float64_round_to_int may have raised. - */ - set_float_exception_flags(old_flags | float_flag_invalid, fpst); - return (uint64_t)(0x800 + 1022 + intsize) << 52; -} - -float64 HELPER(frint32_d)(float64 f, float_status *fpst) -{ - return frint_d(f, fpst, 32); -} - -float64 HELPER(frint64_d)(float64 f, float_status *fpst) -{ - return frint_d(f, fpst, 64); -} - -void HELPER(check_hcr_el2_trap)(CPUARMState *env, uint32_t rt, uint32_t reg) -{ - uint32_t syndrome; - - switch (reg) { - case ARM_VFP_MVFR0: - case ARM_VFP_MVFR1: - case ARM_VFP_MVFR2: - if (!(arm_hcr_el2_eff(env) & HCR_TID3)) { - return; - } - break; - case ARM_VFP_FPSID: - if (!(arm_hcr_el2_eff(env) & HCR_TID0)) { - return; - } - break; - default: - g_assert_not_reached(); - } - - syndrome = ((EC_FPIDTRAP << ARM_EL_EC_SHIFT) - | ARM_EL_IL - | (1 << 24) | (0xe << 20) | (7 << 14) - | (reg << 10) | (rt << 5) | 1); - - raise_exception(env, EXCP_HYP_TRAP, syndrome, 2); -} - -#endif From e34cfba5e8d7bd631398a09d658dee40b1aef085 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 21 Feb 2025 19:09:54 +0000 Subject: [PATCH 0293/1179] target/arm: Move FPSCR get/set helpers to tcg/vfp_helper.c Currently the helper_vfp_get_fpscr() and helper_vfp_set_fpscr() functions do the actual work of updating the FPSCR, and we have wrappers vfp_get_fpscr() and vfp_set_fpscr() which we use for calls from other QEMU C code. Flip these around so that it is vfp_get_fpscr() and vfp_set_fpscr() which do the actual work, and helper_vfp_get_fpscr() and helper_vfp_set_fpscr() which are the wrappers; this allows us to move them to tcg/vfp_helper.c. Since this is the last HELPER() we had in arm/vfp_helper.c, we can drop the include of helper-proto.h. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250221190957.811948-3-peter.maydell@linaro.org --- target/arm/tcg/vfp_helper.c | 10 ++++++++++ target/arm/vfp_helper.c | 15 ++------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/target/arm/tcg/vfp_helper.c b/target/arm/tcg/vfp_helper.c index aa580ff64c75..cd6e0d0edabe 100644 --- a/target/arm/tcg/vfp_helper.c +++ b/target/arm/tcg/vfp_helper.c @@ -1128,3 +1128,13 @@ void HELPER(check_hcr_el2_trap)(CPUARMState *env, uint32_t rt, uint32_t reg) raise_exception(env, EXCP_HYP_TRAP, syndrome, 2); } + +uint32_t HELPER(vfp_get_fpscr)(CPUARMState *env) +{ + return vfp_get_fpscr(env); +} + +void HELPER(vfp_set_fpscr)(CPUARMState *env, uint32_t val) +{ + vfp_set_fpscr(env, val); +} diff --git a/target/arm/vfp_helper.c b/target/arm/vfp_helper.c index 0e849d8d4dcc..0919acb7b893 100644 --- a/target/arm/vfp_helper.c +++ b/target/arm/vfp_helper.c @@ -19,7 +19,6 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "exec/helper-proto.h" #include "internals.h" #include "cpu-features.h" #include "fpu/softfloat.h" @@ -298,17 +297,12 @@ uint32_t vfp_get_fpsr(CPUARMState *env) return fpsr; } -uint32_t HELPER(vfp_get_fpscr)(CPUARMState *env) +uint32_t vfp_get_fpscr(CPUARMState *env) { return (vfp_get_fpcr(env) & FPSCR_FPCR_MASK) | (vfp_get_fpsr(env) & FPSCR_FPSR_MASK); } -uint32_t vfp_get_fpscr(CPUARMState *env) -{ - return HELPER(vfp_get_fpscr)(env); -} - void vfp_set_fpsr(CPUARMState *env, uint32_t val) { ARMCPU *cpu = env_archcpu(env); @@ -402,13 +396,8 @@ void vfp_set_fpcr(CPUARMState *env, uint32_t val) vfp_set_fpcr_masked(env, val, MAKE_64BIT_MASK(0, 32)); } -void HELPER(vfp_set_fpscr)(CPUARMState *env, uint32_t val) +void vfp_set_fpscr(CPUARMState *env, uint32_t val) { vfp_set_fpcr_masked(env, val, FPSCR_FPCR_MASK); vfp_set_fpsr(env, val & FPSCR_FPSR_MASK); } - -void vfp_set_fpscr(CPUARMState *env, uint32_t val) -{ - HELPER(vfp_set_fpscr)(env, val); -} From b9d3dc45532e696f5ee566edd227a4f46bad0f35 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 21 Feb 2025 19:09:55 +0000 Subject: [PATCH 0294/1179] target/arm: Move softfloat specific FPCR/FPSR handling to tcg/ The softfloat (i.e. TCG) specific handling for the FPCR and FPSR is abstracted behind five functions: arm_set_default_fp_behaviours arm_set_ah_fp_behaviours vfp_get_fpsr_from_host vfp_clear_float_status_exc_flags vfp_set_fpsr_to_host Currently we rely on the first two calling softfloat functions that work even in a KVM-only compile because they're defined as inline in the softfloat header file, and we provide stub versions of the last three in arm/vfp_helper.c if CONFIG_TCG isn't defined. Move the softfloat-specific versions of these functions to tcg/vfp_helper.c, and provide the non-TCG stub versions in tcg-stubs.c. This lets us drop the softfloat header include and the last set of CONFIG_TCG ifdefs from arm/vfp_helper.c. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250221190957.811948-4-peter.maydell@linaro.org --- target/arm/internals.h | 9 ++ target/arm/tcg-stubs.c | 22 ++++ target/arm/tcg/vfp_helper.c | 228 +++++++++++++++++++++++++++++++++ target/arm/vfp_helper.c | 248 ------------------------------------ 4 files changed, 259 insertions(+), 248 deletions(-) diff --git a/target/arm/internals.h b/target/arm/internals.h index b3187341456a..a6ff228f9fd2 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -1833,5 +1833,14 @@ int alle1_tlbmask(CPUARMState *env); void arm_set_default_fp_behaviours(float_status *s); /* Set the float_status behaviour to match Arm FPCR.AH=1 behaviour */ void arm_set_ah_fp_behaviours(float_status *s); +/* Read the float_status info and return the appropriate FPSR value */ +uint32_t vfp_get_fpsr_from_host(CPUARMState *env); +/* Clear the exception status flags from all float_status fields */ +void vfp_clear_float_status_exc_flags(CPUARMState *env); +/* + * Update float_status fields to handle the bits of the FPCR + * specified by mask changing to the values in val. + */ +void vfp_set_fpcr_to_host(CPUARMState *env, uint32_t val, uint32_t mask); #endif diff --git a/target/arm/tcg-stubs.c b/target/arm/tcg-stubs.c index f3f45d54f282..93a15cad6103 100644 --- a/target/arm/tcg-stubs.c +++ b/target/arm/tcg-stubs.c @@ -30,3 +30,25 @@ void assert_hflags_rebuild_correctly(CPUARMState *env) void define_tlb_insn_regs(ARMCPU *cpu) { } + +/* With KVM, we never use float_status, so these can be no-ops */ +void arm_set_default_fp_behaviours(float_status *s) +{ +} + +void arm_set_ah_fp_behaviours(float_status *s) +{ +} + +uint32_t vfp_get_fpsr_from_host(CPUARMState *env) +{ + return 0; +} + +void vfp_clear_float_status_exc_flags(CPUARMState *env) +{ +} + +void vfp_set_fpcr_to_host(CPUARMState *env, uint32_t val, uint32_t mask) +{ +} diff --git a/target/arm/tcg/vfp_helper.c b/target/arm/tcg/vfp_helper.c index cd6e0d0edabe..b32e2f4e27c6 100644 --- a/target/arm/tcg/vfp_helper.c +++ b/target/arm/tcg/vfp_helper.c @@ -25,6 +25,234 @@ #include "fpu/softfloat.h" #include "qemu/log.h" +/* + * Set the float_status behaviour to match the Arm defaults: + * * tininess-before-rounding + * * 2-input NaN propagation prefers SNaN over QNaN, and then + * operand A over operand B (see FPProcessNaNs() pseudocode) + * * 3-input NaN propagation prefers SNaN over QNaN, and then + * operand C over A over B (see FPProcessNaNs3() pseudocode, + * but note that for QEMU muladd is a * b + c, whereas for + * the pseudocode function the arguments are in the order c, a, b. + * * 0 * Inf + NaN returns the default NaN if the input NaN is quiet, + * and the input NaN if it is signalling + * * Default NaN has sign bit clear, msb frac bit set + */ +void arm_set_default_fp_behaviours(float_status *s) +{ + set_float_detect_tininess(float_tininess_before_rounding, s); + set_float_ftz_detection(float_ftz_before_rounding, s); + set_float_2nan_prop_rule(float_2nan_prop_s_ab, s); + set_float_3nan_prop_rule(float_3nan_prop_s_cab, s); + set_float_infzeronan_rule(float_infzeronan_dnan_if_qnan, s); + set_float_default_nan_pattern(0b01000000, s); +} + +/* + * Set the float_status behaviour to match the FEAT_AFP + * FPCR.AH=1 requirements: + * * tininess-after-rounding + * * 2-input NaN propagation prefers the first NaN + * * 3-input NaN propagation prefers a over b over c + * * 0 * Inf + NaN always returns the input NaN and doesn't + * set Invalid for a QNaN + * * default NaN has sign bit set, msb frac bit set + */ +void arm_set_ah_fp_behaviours(float_status *s) +{ + set_float_detect_tininess(float_tininess_after_rounding, s); + set_float_ftz_detection(float_ftz_after_rounding, s); + set_float_2nan_prop_rule(float_2nan_prop_ab, s); + set_float_3nan_prop_rule(float_3nan_prop_abc, s); + set_float_infzeronan_rule(float_infzeronan_dnan_never | + float_infzeronan_suppress_invalid, s); + set_float_default_nan_pattern(0b11000000, s); +} + +/* Convert host exception flags to vfp form. */ +static inline uint32_t vfp_exceptbits_from_host(int host_bits, bool ah) +{ + uint32_t target_bits = 0; + + if (host_bits & float_flag_invalid) { + target_bits |= FPSR_IOC; + } + if (host_bits & float_flag_divbyzero) { + target_bits |= FPSR_DZC; + } + if (host_bits & float_flag_overflow) { + target_bits |= FPSR_OFC; + } + if (host_bits & (float_flag_underflow | float_flag_output_denormal_flushed)) { + target_bits |= FPSR_UFC; + } + if (host_bits & float_flag_inexact) { + target_bits |= FPSR_IXC; + } + if (host_bits & float_flag_input_denormal_flushed) { + target_bits |= FPSR_IDC; + } + /* + * With FPCR.AH, IDC is set when an input denormal is used, + * and flushing an output denormal to zero sets both IXC and UFC. + */ + if (ah && (host_bits & float_flag_input_denormal_used)) { + target_bits |= FPSR_IDC; + } + if (ah && (host_bits & float_flag_output_denormal_flushed)) { + target_bits |= FPSR_IXC; + } + return target_bits; +} + +uint32_t vfp_get_fpsr_from_host(CPUARMState *env) +{ + uint32_t a32_flags = 0, a64_flags = 0; + + a32_flags |= get_float_exception_flags(&env->vfp.fp_status[FPST_A32]); + a32_flags |= get_float_exception_flags(&env->vfp.fp_status[FPST_STD]); + /* FZ16 does not generate an input denormal exception. */ + a32_flags |= (get_float_exception_flags(&env->vfp.fp_status[FPST_A32_F16]) + & ~float_flag_input_denormal_flushed); + a32_flags |= (get_float_exception_flags(&env->vfp.fp_status[FPST_STD_F16]) + & ~float_flag_input_denormal_flushed); + + a64_flags |= get_float_exception_flags(&env->vfp.fp_status[FPST_A64]); + a64_flags |= (get_float_exception_flags(&env->vfp.fp_status[FPST_A64_F16]) + & ~(float_flag_input_denormal_flushed | float_flag_input_denormal_used)); + /* + * We do not merge in flags from FPST_AH or FPST_AH_F16, because + * they are used for insns that must not set the cumulative exception bits. + */ + + /* + * Flushing an input denormal *only* because FPCR.FIZ == 1 does + * not set FPSR.IDC; if FPCR.FZ is also set then this takes + * precedence and IDC is set (see the FPUnpackBase pseudocode). + * So squash it unless (FPCR.AH == 0 && FPCR.FZ == 1). + * We only do this for the a64 flags because FIZ has no effect + * on AArch32 even if it is set. + */ + if ((env->vfp.fpcr & (FPCR_FZ | FPCR_AH)) != FPCR_FZ) { + a64_flags &= ~float_flag_input_denormal_flushed; + } + return vfp_exceptbits_from_host(a64_flags, env->vfp.fpcr & FPCR_AH) | + vfp_exceptbits_from_host(a32_flags, false); +} + +void vfp_clear_float_status_exc_flags(CPUARMState *env) +{ + /* + * Clear out all the exception-flag information in the float_status + * values. The caller should have arranged for env->vfp.fpsr to + * be the architecturally up-to-date exception flag information first. + */ + set_float_exception_flags(0, &env->vfp.fp_status[FPST_A32]); + set_float_exception_flags(0, &env->vfp.fp_status[FPST_A64]); + set_float_exception_flags(0, &env->vfp.fp_status[FPST_A32_F16]); + set_float_exception_flags(0, &env->vfp.fp_status[FPST_A64_F16]); + set_float_exception_flags(0, &env->vfp.fp_status[FPST_STD]); + set_float_exception_flags(0, &env->vfp.fp_status[FPST_STD_F16]); + set_float_exception_flags(0, &env->vfp.fp_status[FPST_AH]); + set_float_exception_flags(0, &env->vfp.fp_status[FPST_AH_F16]); +} + +static void vfp_sync_and_clear_float_status_exc_flags(CPUARMState *env) +{ + /* + * Synchronize any pending exception-flag information in the + * float_status values into env->vfp.fpsr, and then clear out + * the float_status data. + */ + env->vfp.fpsr |= vfp_get_fpsr_from_host(env); + vfp_clear_float_status_exc_flags(env); +} + +void vfp_set_fpcr_to_host(CPUARMState *env, uint32_t val, uint32_t mask) +{ + uint64_t changed = env->vfp.fpcr; + + changed ^= val; + changed &= mask; + if (changed & (3 << 22)) { + int i = (val >> 22) & 3; + switch (i) { + case FPROUNDING_TIEEVEN: + i = float_round_nearest_even; + break; + case FPROUNDING_POSINF: + i = float_round_up; + break; + case FPROUNDING_NEGINF: + i = float_round_down; + break; + case FPROUNDING_ZERO: + i = float_round_to_zero; + break; + } + set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A32]); + set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A64]); + set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A32_F16]); + set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A64_F16]); + } + if (changed & FPCR_FZ16) { + bool ftz_enabled = val & FPCR_FZ16; + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32_F16]); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A64_F16]); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_STD_F16]); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_AH_F16]); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32_F16]); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A64_F16]); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_STD_F16]); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_AH_F16]); + } + if (changed & FPCR_FZ) { + bool ftz_enabled = val & FPCR_FZ; + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32]); + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A64]); + /* FIZ is A64 only so FZ always makes A32 code flush inputs to zero */ + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32]); + } + if (changed & (FPCR_FZ | FPCR_AH | FPCR_FIZ)) { + /* + * A64: Flush denormalized inputs to zero if FPCR.FIZ = 1, or + * both FPCR.AH = 0 and FPCR.FZ = 1. + */ + bool fitz_enabled = (val & FPCR_FIZ) || + (val & (FPCR_FZ | FPCR_AH)) == FPCR_FZ; + set_flush_inputs_to_zero(fitz_enabled, &env->vfp.fp_status[FPST_A64]); + } + if (changed & FPCR_DN) { + bool dnan_enabled = val & FPCR_DN; + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A32]); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A64]); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A32_F16]); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A64_F16]); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_AH]); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_AH_F16]); + } + if (changed & FPCR_AH) { + bool ah_enabled = val & FPCR_AH; + + if (ah_enabled) { + /* Change behaviours for A64 FP operations */ + arm_set_ah_fp_behaviours(&env->vfp.fp_status[FPST_A64]); + arm_set_ah_fp_behaviours(&env->vfp.fp_status[FPST_A64_F16]); + } else { + arm_set_default_fp_behaviours(&env->vfp.fp_status[FPST_A64]); + arm_set_default_fp_behaviours(&env->vfp.fp_status[FPST_A64_F16]); + } + } + /* + * If any bits changed that we look at in vfp_get_fpsr_from_host(), + * we must sync the float_status flags into vfp.fpsr now (under the + * old regime) before we update vfp.fpcr. + */ + if (changed & (FPCR_FZ | FPCR_AH | FPCR_FIZ)) { + vfp_sync_and_clear_float_status_exc_flags(env); + } +} + /* * VFP support. We follow the convention used for VFP instructions: * Single precision routines have a "s" suffix, double precision a diff --git a/target/arm/vfp_helper.c b/target/arm/vfp_helper.c index 0919acb7b893..cc0f055ef0d6 100644 --- a/target/arm/vfp_helper.c +++ b/target/arm/vfp_helper.c @@ -21,254 +21,6 @@ #include "cpu.h" #include "internals.h" #include "cpu-features.h" -#include "fpu/softfloat.h" - -/* - * Set the float_status behaviour to match the Arm defaults: - * * tininess-before-rounding - * * 2-input NaN propagation prefers SNaN over QNaN, and then - * operand A over operand B (see FPProcessNaNs() pseudocode) - * * 3-input NaN propagation prefers SNaN over QNaN, and then - * operand C over A over B (see FPProcessNaNs3() pseudocode, - * but note that for QEMU muladd is a * b + c, whereas for - * the pseudocode function the arguments are in the order c, a, b. - * * 0 * Inf + NaN returns the default NaN if the input NaN is quiet, - * and the input NaN if it is signalling - * * Default NaN has sign bit clear, msb frac bit set - */ -void arm_set_default_fp_behaviours(float_status *s) -{ - set_float_detect_tininess(float_tininess_before_rounding, s); - set_float_ftz_detection(float_ftz_before_rounding, s); - set_float_2nan_prop_rule(float_2nan_prop_s_ab, s); - set_float_3nan_prop_rule(float_3nan_prop_s_cab, s); - set_float_infzeronan_rule(float_infzeronan_dnan_if_qnan, s); - set_float_default_nan_pattern(0b01000000, s); -} - -/* - * Set the float_status behaviour to match the FEAT_AFP - * FPCR.AH=1 requirements: - * * tininess-after-rounding - * * 2-input NaN propagation prefers the first NaN - * * 3-input NaN propagation prefers a over b over c - * * 0 * Inf + NaN always returns the input NaN and doesn't - * set Invalid for a QNaN - * * default NaN has sign bit set, msb frac bit set - */ -void arm_set_ah_fp_behaviours(float_status *s) -{ - set_float_detect_tininess(float_tininess_after_rounding, s); - set_float_ftz_detection(float_ftz_after_rounding, s); - set_float_2nan_prop_rule(float_2nan_prop_ab, s); - set_float_3nan_prop_rule(float_3nan_prop_abc, s); - set_float_infzeronan_rule(float_infzeronan_dnan_never | - float_infzeronan_suppress_invalid, s); - set_float_default_nan_pattern(0b11000000, s); -} - -#ifdef CONFIG_TCG - -/* Convert host exception flags to vfp form. */ -static inline uint32_t vfp_exceptbits_from_host(int host_bits, bool ah) -{ - uint32_t target_bits = 0; - - if (host_bits & float_flag_invalid) { - target_bits |= FPSR_IOC; - } - if (host_bits & float_flag_divbyzero) { - target_bits |= FPSR_DZC; - } - if (host_bits & float_flag_overflow) { - target_bits |= FPSR_OFC; - } - if (host_bits & (float_flag_underflow | float_flag_output_denormal_flushed)) { - target_bits |= FPSR_UFC; - } - if (host_bits & float_flag_inexact) { - target_bits |= FPSR_IXC; - } - if (host_bits & float_flag_input_denormal_flushed) { - target_bits |= FPSR_IDC; - } - /* - * With FPCR.AH, IDC is set when an input denormal is used, - * and flushing an output denormal to zero sets both IXC and UFC. - */ - if (ah && (host_bits & float_flag_input_denormal_used)) { - target_bits |= FPSR_IDC; - } - if (ah && (host_bits & float_flag_output_denormal_flushed)) { - target_bits |= FPSR_IXC; - } - return target_bits; -} - -static uint32_t vfp_get_fpsr_from_host(CPUARMState *env) -{ - uint32_t a32_flags = 0, a64_flags = 0; - - a32_flags |= get_float_exception_flags(&env->vfp.fp_status[FPST_A32]); - a32_flags |= get_float_exception_flags(&env->vfp.fp_status[FPST_STD]); - /* FZ16 does not generate an input denormal exception. */ - a32_flags |= (get_float_exception_flags(&env->vfp.fp_status[FPST_A32_F16]) - & ~float_flag_input_denormal_flushed); - a32_flags |= (get_float_exception_flags(&env->vfp.fp_status[FPST_STD_F16]) - & ~float_flag_input_denormal_flushed); - - a64_flags |= get_float_exception_flags(&env->vfp.fp_status[FPST_A64]); - a64_flags |= (get_float_exception_flags(&env->vfp.fp_status[FPST_A64_F16]) - & ~(float_flag_input_denormal_flushed | float_flag_input_denormal_used)); - /* - * We do not merge in flags from FPST_AH or FPST_AH_F16, because - * they are used for insns that must not set the cumulative exception bits. - */ - - /* - * Flushing an input denormal *only* because FPCR.FIZ == 1 does - * not set FPSR.IDC; if FPCR.FZ is also set then this takes - * precedence and IDC is set (see the FPUnpackBase pseudocode). - * So squash it unless (FPCR.AH == 0 && FPCR.FZ == 1). - * We only do this for the a64 flags because FIZ has no effect - * on AArch32 even if it is set. - */ - if ((env->vfp.fpcr & (FPCR_FZ | FPCR_AH)) != FPCR_FZ) { - a64_flags &= ~float_flag_input_denormal_flushed; - } - return vfp_exceptbits_from_host(a64_flags, env->vfp.fpcr & FPCR_AH) | - vfp_exceptbits_from_host(a32_flags, false); -} - -static void vfp_clear_float_status_exc_flags(CPUARMState *env) -{ - /* - * Clear out all the exception-flag information in the float_status - * values. The caller should have arranged for env->vfp.fpsr to - * be the architecturally up-to-date exception flag information first. - */ - set_float_exception_flags(0, &env->vfp.fp_status[FPST_A32]); - set_float_exception_flags(0, &env->vfp.fp_status[FPST_A64]); - set_float_exception_flags(0, &env->vfp.fp_status[FPST_A32_F16]); - set_float_exception_flags(0, &env->vfp.fp_status[FPST_A64_F16]); - set_float_exception_flags(0, &env->vfp.fp_status[FPST_STD]); - set_float_exception_flags(0, &env->vfp.fp_status[FPST_STD_F16]); - set_float_exception_flags(0, &env->vfp.fp_status[FPST_AH]); - set_float_exception_flags(0, &env->vfp.fp_status[FPST_AH_F16]); -} - -static void vfp_sync_and_clear_float_status_exc_flags(CPUARMState *env) -{ - /* - * Synchronize any pending exception-flag information in the - * float_status values into env->vfp.fpsr, and then clear out - * the float_status data. - */ - env->vfp.fpsr |= vfp_get_fpsr_from_host(env); - vfp_clear_float_status_exc_flags(env); -} - -static void vfp_set_fpcr_to_host(CPUARMState *env, uint32_t val, uint32_t mask) -{ - uint64_t changed = env->vfp.fpcr; - - changed ^= val; - changed &= mask; - if (changed & (3 << 22)) { - int i = (val >> 22) & 3; - switch (i) { - case FPROUNDING_TIEEVEN: - i = float_round_nearest_even; - break; - case FPROUNDING_POSINF: - i = float_round_up; - break; - case FPROUNDING_NEGINF: - i = float_round_down; - break; - case FPROUNDING_ZERO: - i = float_round_to_zero; - break; - } - set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A32]); - set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A64]); - set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A32_F16]); - set_float_rounding_mode(i, &env->vfp.fp_status[FPST_A64_F16]); - } - if (changed & FPCR_FZ16) { - bool ftz_enabled = val & FPCR_FZ16; - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32_F16]); - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A64_F16]); - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_STD_F16]); - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_AH_F16]); - set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32_F16]); - set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A64_F16]); - set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_STD_F16]); - set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_AH_F16]); - } - if (changed & FPCR_FZ) { - bool ftz_enabled = val & FPCR_FZ; - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32]); - set_flush_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A64]); - /* FIZ is A64 only so FZ always makes A32 code flush inputs to zero */ - set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status[FPST_A32]); - } - if (changed & (FPCR_FZ | FPCR_AH | FPCR_FIZ)) { - /* - * A64: Flush denormalized inputs to zero if FPCR.FIZ = 1, or - * both FPCR.AH = 0 and FPCR.FZ = 1. - */ - bool fitz_enabled = (val & FPCR_FIZ) || - (val & (FPCR_FZ | FPCR_AH)) == FPCR_FZ; - set_flush_inputs_to_zero(fitz_enabled, &env->vfp.fp_status[FPST_A64]); - } - if (changed & FPCR_DN) { - bool dnan_enabled = val & FPCR_DN; - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A32]); - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A64]); - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A32_F16]); - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_A64_F16]); - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_AH]); - set_default_nan_mode(dnan_enabled, &env->vfp.fp_status[FPST_AH_F16]); - } - if (changed & FPCR_AH) { - bool ah_enabled = val & FPCR_AH; - - if (ah_enabled) { - /* Change behaviours for A64 FP operations */ - arm_set_ah_fp_behaviours(&env->vfp.fp_status[FPST_A64]); - arm_set_ah_fp_behaviours(&env->vfp.fp_status[FPST_A64_F16]); - } else { - arm_set_default_fp_behaviours(&env->vfp.fp_status[FPST_A64]); - arm_set_default_fp_behaviours(&env->vfp.fp_status[FPST_A64_F16]); - } - } - /* - * If any bits changed that we look at in vfp_get_fpsr_from_host(), - * we must sync the float_status flags into vfp.fpsr now (under the - * old regime) before we update vfp.fpcr. - */ - if (changed & (FPCR_FZ | FPCR_AH | FPCR_FIZ)) { - vfp_sync_and_clear_float_status_exc_flags(env); - } -} - -#else - -static uint32_t vfp_get_fpsr_from_host(CPUARMState *env) -{ - return 0; -} - -static void vfp_clear_float_status_exc_flags(CPUARMState *env) -{ -} - -static void vfp_set_fpcr_to_host(CPUARMState *env, uint32_t val, uint32_t mask) -{ -} - -#endif uint32_t vfp_get_fpcr(CPUARMState *env) { From cb8bb8472ef83d8c8c6beac37d6db47ab3b68e18 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 21 Feb 2025 19:09:56 +0000 Subject: [PATCH 0295/1179] target/arm: Rename vfp_helper.c to vfp_fpscr.c The vfp_helper.c in the target/arm directory now only has code for handling FPSCR/FPCR/FPSR in it, and no helper functions. Rename it to vfp_fpscr.c; this helps keep it distinct from tcg/vfp_helper.c. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250221190957.811948-5-peter.maydell@linaro.org --- target/arm/meson.build | 2 +- target/arm/{vfp_helper.c => vfp_fpscr.c} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename target/arm/{vfp_helper.c => vfp_fpscr.c} (98%) diff --git a/target/arm/meson.build b/target/arm/meson.build index 2e10464dbb6b..3065081d241d 100644 --- a/target/arm/meson.build +++ b/target/arm/meson.build @@ -4,7 +4,7 @@ arm_ss.add(files( 'debug_helper.c', 'gdbstub.c', 'helper.c', - 'vfp_helper.c', + 'vfp_fpscr.c', )) arm_ss.add(zlib) diff --git a/target/arm/vfp_helper.c b/target/arm/vfp_fpscr.c similarity index 98% rename from target/arm/vfp_helper.c rename to target/arm/vfp_fpscr.c index cc0f055ef0d6..92ea60ebbf28 100644 --- a/target/arm/vfp_helper.c +++ b/target/arm/vfp_fpscr.c @@ -1,5 +1,5 @@ /* - * ARM VFP floating-point operations + * ARM VFP floating-point: handling of FPSCR/FPCR/FPSR * * Copyright (c) 2003 Fabrice Bellard * From fd207677a83087454b8afef31651985a1df0d2dd Mon Sep 17 00:00:00 2001 From: Joelle van Dyne Date: Mon, 24 Feb 2025 08:57:34 -0800 Subject: [PATCH 0296/1179] target/arm/hvf: Disable SME feature macOS 15.2's Hypervisor.framework exposes SME feature on M4 Macs. However, QEMU's hvf accelerator code does not properly support it yet, causing QEMU to fail to start when hvf accelerator is used on these systems, with the error message: qemu-aarch64-softmmu: cannot disable sme4224 All SME vector lengths are disabled. With SME enabled, at least one vector length must be enabled. Ideally we would have SME support on these hosts; however, until that point, we must suppress the SME feature in the ID registers, so that users can at least run non-SME guests. Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2665 Signed-off-by: Joelle van Dyne Message-id: 20250224165735.36792-1-j@getutm.app Reviewed-by: Peter Maydell [PMM: expanded commit message, comment] Signed-off-by: Peter Maydell --- target/arm/hvf/hvf.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index 0afd96018e06..872a25be8695 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -899,6 +899,18 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) clamp_id_aa64mmfr0_parange_to_ipa_size(&host_isar.id_aa64mmfr0); + /* + * Disable SME, which is not properly handled by QEMU hvf yet. + * To allow this through we would need to: + * - make sure that the SME state is correctly handled in the + * get_registers/put_registers functions + * - get the SME-specific CPU properties to work with accelerators + * other than TCG + * - fix any assumptions we made that SME implies SVE (since + * on the M4 there is SME but not SVE) + */ + host_isar.id_aa64pfr1 &= ~R_ID_AA64PFR1_SME_MASK; + ahcf->isar = host_isar; /* From 12c365315ab25d364cff24dfeea8d7ff1e752b9f Mon Sep 17 00:00:00 2001 From: Joelle van Dyne Date: Mon, 24 Feb 2025 10:41:23 -0800 Subject: [PATCH 0297/1179] target/arm/hvf: sign extend the data for a load operation when SSE=1 In the syndrome value for a data abort, bit 21 is SSE, which is set to indicate that the abort was on a sign-extending load. When we handle the data abort from the guest via address_space_read(), we forgot to handle this and so would return the wrong value if the guest did a sign-extending load to an MMIO region. Add the sign-extension of the returned data. Cc: qemu-stable@nongnu.org Signed-off-by: Joelle van Dyne Message-id: 20250224184123.50780-1-j@getutm.app [PMM: Drop an unnecessary check on 'len'; expand commit message] Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- target/arm/hvf/hvf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index 872a25be8695..2439af63a05a 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -1983,6 +1983,7 @@ int hvf_vcpu_exec(CPUState *cpu) bool isv = syndrome & ARM_EL_ISV; bool iswrite = (syndrome >> 6) & 1; bool s1ptw = (syndrome >> 7) & 1; + bool sse = (syndrome >> 21) & 1; uint32_t sas = (syndrome >> 22) & 3; uint32_t len = 1 << sas; uint32_t srt = (syndrome >> 16) & 0x1f; @@ -2010,6 +2011,9 @@ int hvf_vcpu_exec(CPUState *cpu) address_space_read(&address_space_memory, hvf_exit->exception.physical_address, MEMTXATTRS_UNSPECIFIED, &val, len); + if (sse) { + val = sextract64(val, 0, len * 8); + } hvf_set_reg(cpu, srt, val); } From 2cac20cbf7b01e9a4e404db2ff9bee09ee26f315 Mon Sep 17 00:00:00 2001 From: Pierrick Bouvier Date: Mon, 24 Feb 2025 12:50:53 -0800 Subject: [PATCH 0298/1179] hw/misc/npcm_clk: fix buffer-overflow Regression introduced by cf76c4 (hw/misc: Add nr_regs and cold_reset_values to NPCM CLK) cold_reset_values has a different size, depending on device used (NPCM7xx vs NPCM8xx). However, s->regs has a fixed size, which matches NPCM8xx. Thus, when initializing a NPCM7xx, we go past cold_reset_values ending. Report by asan: ==2066==ERROR: AddressSanitizer: global-buffer-overflow on address 0x55d68a3e97f0 at pc 0x7fcaf2b2d14b bp 0x7ffff0cc3890 sp 0x7ffff0cc3040 READ of size 196 at 0x55d68a3e97f0 thread T0 #0 0x7fcaf2b2d14a in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 #1 0x55d688447e0d in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29 #2 0x55d688447e0d in npcm_clk_enter_reset ../hw/misc/npcm_clk.c:968 #3 0x55d6899b7213 in resettable_phase_enter ../hw/core/resettable.c:136 #4 0x55d6899a1ef7 in bus_reset_child_foreach ../hw/core/bus.c:97 #5 0x55d6899b717d in resettable_child_foreach ../hw/core/resettable.c:92 #6 0x55d6899b717d in resettable_phase_enter ../hw/core/resettable.c:129 #7 0x55d6899b4ead in resettable_container_child_foreach ../hw/core/resetcontainer.c:54 #8 0x55d6899b717d in resettable_child_foreach ../hw/core/resettable.c:92 #9 0x55d6899b717d in resettable_phase_enter ../hw/core/resettable.c:129 #10 0x55d6899b7bfa in resettable_assert_reset ../hw/core/resettable.c:55 #11 0x55d6899b8666 in resettable_reset ../hw/core/resettable.c:45 #12 0x55d688d15cd2 in qemu_system_reset ../system/runstate.c:527 #13 0x55d687fc5edd in qdev_machine_creation_done ../hw/core/machine.c:1738 #14 0x55d688d209bd in qemu_machine_creation_done ../system/vl.c:2779 #15 0x55d688d209bd in qmp_x_exit_preconfig ../system/vl.c:2807 #16 0x55d688d281fb in qemu_init ../system/vl.c:3838 #17 0x55d687ceab12 in main ../system/main.c:68 #18 0x7fcaef006249 (/lib/x86_64-linux-gnu/libc.so.6+0x27249) #19 0x7fcaef006304 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x27304) #20 0x55d687cf0010 in _start (/home/runner/work/qemu-ci/qemu-ci/build/qemu-system-arm+0x371c010) 0x55d68a3e97f0 is located 0 bytes to the right of global variable 'npcm7xx_cold_reset_values' defined in '../hw/misc/npcm_clk.c:134:23' (0x55d68a3e9780) of size 112 Impacted tests: Summary of Failures: check: 2/747 qemu:qtest+qtest-aarch64 / qtest-aarch64/qom-test ERROR 9.28s killed by signal 6 SIGABRT 4/747 qemu:qtest+qtest-arm / qtest-arm/qom-test ERROR 7.82s killed by signal 6 SIGABRT 32/747 qemu:qtest+qtest-aarch64 / qtest-aarch64/device-introspect-test ERROR 10.91s killed by signal 6 SIGABRT 35/747 qemu:qtest+qtest-arm / qtest-arm/device-introspect-test ERROR 11.33s killed by signal 6 SIGABRT 114/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_pwm-test ERROR 0.98s killed by signal 6 SIGABRT 115/747 qemu:qtest+qtest-aarch64 / qtest-aarch64/test-hmp ERROR 2.95s killed by signal 6 SIGABRT 117/747 qemu:qtest+qtest-arm / qtest-arm/test-hmp ERROR 2.54s killed by signal 6 SIGABRT 151/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_watchdog_timer-test ERROR 0.96s killed by signal 6 SIGABRT 247/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_adc-test ERROR 0.96s killed by signal 6 SIGABRT 248/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_gpio-test ERROR 1.05s killed by signal 6 SIGABRT 249/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_rng-test ERROR 0.97s killed by signal 6 SIGABRT 250/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_sdhci-test ERROR 0.97s killed by signal 6 SIGABRT 251/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_smbus-test ERROR 0.89s killed by signal 6 SIGABRT 252/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_timer-test ERROR 1.09s killed by signal 6 SIGABRT 253/747 qemu:qtest+qtest-arm / qtest-arm/npcm_gmac-test ERROR 1.12s killed by signal 6 SIGABRT 255/747 qemu:qtest+qtest-arm / qtest-arm/npcm7xx_emc-test ERROR 1.05s killed by signal 6 SIGABRT check-functional: 22/203 qemu:func-thorough+func-arm-thorough+thorough / func-arm-arm_quanta_gsj ERROR 0.79s exit status 1 38/203 qemu:func-quick+func-aarch64 / func-aarch64-migration ERROR 1.97s exit status 1 45/203 qemu:func-quick+func-arm / func-arm-migration ERROR 1.90s exit status 1 Fixes: cf76c4e174e1 ("hw/misc: Add nr_regs and cold_reset_values to NPCM CLK") Signed-off-by: Pierrick Bouvier Reviewed-by: Hao Wu Signed-off-by: Peter Maydell --- hw/misc/npcm_clk.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hw/misc/npcm_clk.c b/hw/misc/npcm_clk.c index d1f29759d594..0e85974cf96d 100644 --- a/hw/misc/npcm_clk.c +++ b/hw/misc/npcm_clk.c @@ -964,8 +964,9 @@ static void npcm_clk_enter_reset(Object *obj, ResetType type) NPCMCLKState *s = NPCM_CLK(obj); NPCMCLKClass *c = NPCM_CLK_GET_CLASS(s); - g_assert(sizeof(s->regs) >= c->nr_regs * sizeof(uint32_t)); - memcpy(s->regs, c->cold_reset_values, sizeof(s->regs)); + size_t sizeof_regs = c->nr_regs * sizeof(uint32_t); + g_assert(sizeof(s->regs) >= sizeof_regs); + memcpy(s->regs, c->cold_reset_values, sizeof_regs); s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); npcm7xx_clk_update_all_clocks(s); /* From b513766ee968dbfca31034b185f0a0fcf99f4269 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:51 +0100 Subject: [PATCH 0299/1179] hw/usb/hcd-dwc3: Align global registers size with Linux While at it add missing GUSB2RHBCTL register as found in i.MX 8M Plus reference manual. Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-2-shentey@gmail.com Signed-off-by: Peter Maydell --- hw/usb/hcd-dwc3.c | 5 +++++ include/hw/usb/hcd-dwc3.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hw/usb/hcd-dwc3.c b/hw/usb/hcd-dwc3.c index 9ce9ba0b0465..0bceee271282 100644 --- a/hw/usb/hcd-dwc3.c +++ b/hw/usb/hcd-dwc3.c @@ -343,6 +343,8 @@ REG32(GFLADJ, 0x530) FIELD(GFLADJ, GFLADJ_REFCLK_FLADJ, 8, 14) FIELD(GFLADJ, GFLADJ_30MHZ_SDBND_SEL, 7, 1) FIELD(GFLADJ, GFLADJ_30MHZ, 0, 6) +REG32(GUSB2RHBCTL, 0x540) + FIELD(GUSB2RHBCTL, OVRD_L1TIMEOUT, 0, 4) #define DWC3_GLOBAL_OFFSET 0xC100 static void reset_csr(USBDWC3 * s) @@ -560,6 +562,9 @@ static const RegisterAccessInfo usb_dwc3_regs_info[] = { .rsvd = 0x40, .ro = 0x400040, .unimp = 0xffffffff, + },{ .name = "GUSB2RHBCTL", .addr = A_GUSB2RHBCTL, + .rsvd = 0xfffffff0, + .unimp = 0xffffffff, } }; diff --git a/include/hw/usb/hcd-dwc3.h b/include/hw/usb/hcd-dwc3.h index f752a27e9404..dbdf12b21d74 100644 --- a/include/hw/usb/hcd-dwc3.h +++ b/include/hw/usb/hcd-dwc3.h @@ -35,7 +35,7 @@ #define USB_DWC3(obj) \ OBJECT_CHECK(USBDWC3, (obj), TYPE_USB_DWC3) -#define USB_DWC3_R_MAX ((0x530 / 4) + 1) +#define USB_DWC3_R_MAX (0x600 / 4) #define DWC3_SIZE 0x10000 typedef struct USBDWC3 { From faa2150a527b1919646316dba268b71ced8762a6 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:52 +0100 Subject: [PATCH 0300/1179] hw/pci-host/designware: Prevent device attachment on internal PCIe root bus On the real device, the PCIe root bus is only connected to a PCIe bridge and does not allow for direct attachment of devices. Doing so in QEMU results in no PCI devices being detected by Linux. Instead, PCI devices should plug into the secondary PCIe bus spawned by the internal PCIe bridge. Unfortunately, QEMU defaults to plugging devices into the PCIe root bus. To work around this, every PCI device created on the command line needs an extra `bus=dw-pcie` option which is error prone. Fix that by marking the PCIe root bus as full which makes QEMU decend into the child PCIe bus. Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-3-shentey@gmail.com Signed-off-by: Peter Maydell --- hw/pci-host/designware.c | 18 +++++++++++++++++- include/hw/pci-host/designware.h | 7 +++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/hw/pci-host/designware.c b/hw/pci-host/designware.c index 3e8c36e6a76b..c07740bfaa45 100644 --- a/hw/pci-host/designware.c +++ b/hw/pci-host/designware.c @@ -55,6 +55,17 @@ #define DESIGNWARE_PCIE_ATU_DEVFN(x) (((x) >> 16) & 0xff) #define DESIGNWARE_PCIE_ATU_UPPER_TARGET 0x91C +static void designware_pcie_root_bus_class_init(ObjectClass *klass, void *data) +{ + BusClass *k = BUS_CLASS(klass); + + /* + * Designware has only a single root complex. Enforce the limit on the + * parent bus + */ + k->max_dev = 1; +} + static DesignwarePCIEHost * designware_pcie_root_to_host(DesignwarePCIERoot *root) { @@ -699,7 +710,7 @@ static void designware_pcie_host_realize(DeviceState *dev, Error **errp) &s->pci.memory, &s->pci.io, 0, 4, - TYPE_PCIE_BUS); + TYPE_DESIGNWARE_PCIE_ROOT_BUS); pci->bus->flags |= PCI_BUS_EXTENDED_CONFIG_SPACE; memory_region_init(&s->pci.address_space_root, @@ -754,6 +765,11 @@ static void designware_pcie_host_init(Object *obj) static const TypeInfo designware_pcie_types[] = { { + .name = TYPE_DESIGNWARE_PCIE_ROOT_BUS, + .parent = TYPE_PCIE_BUS, + .instance_size = sizeof(DesignwarePCIERootBus), + .class_init = designware_pcie_root_bus_class_init, + }, { .name = TYPE_DESIGNWARE_PCIE_HOST, .parent = TYPE_PCI_HOST_BRIDGE, .instance_size = sizeof(DesignwarePCIEHost), diff --git a/include/hw/pci-host/designware.h b/include/hw/pci-host/designware.h index bf8b2789787a..a35a3bd06c88 100644 --- a/include/hw/pci-host/designware.h +++ b/include/hw/pci-host/designware.h @@ -25,12 +25,19 @@ #include "hw/pci/pci_bridge.h" #include "qom/object.h" +#define TYPE_DESIGNWARE_PCIE_ROOT_BUS "designware-pcie-root-BUS" +OBJECT_DECLARE_SIMPLE_TYPE(DesignwarePCIERootBus, DESIGNWARE_PCIE_ROOT_BUS) + #define TYPE_DESIGNWARE_PCIE_HOST "designware-pcie-host" OBJECT_DECLARE_SIMPLE_TYPE(DesignwarePCIEHost, DESIGNWARE_PCIE_HOST) #define TYPE_DESIGNWARE_PCIE_ROOT "designware-pcie-root" OBJECT_DECLARE_SIMPLE_TYPE(DesignwarePCIERoot, DESIGNWARE_PCIE_ROOT) +struct DesignwarePCIERootBus { + PCIBus parent; +}; + typedef struct DesignwarePCIEViewport { DesignwarePCIERoot *root; From 0f520f0a9d9516fb3563a9b69c820ac73d2017aa Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:53 +0100 Subject: [PATCH 0301/1179] hw/gpio/pca955*: Move Kconfig switches next to implementations The move of the Kconfig bits to hw/gpio is fixing a bug in 6328d8ffa6cb9d ("misc/pca955*: Move models under hw/gpio"), which moved the code but forgot to move the Kconfig sections. Fixes: 6328d8ffa6cb9d "misc/pca955*: Move models under hw/gpio" Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-4-shentey@gmail.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/gpio/Kconfig | 8 ++++++++ hw/misc/Kconfig | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig index c423e10f59f5..a209294c20c4 100644 --- a/hw/gpio/Kconfig +++ b/hw/gpio/Kconfig @@ -16,6 +16,14 @@ config SIFIVE_GPIO config STM32L4X5_GPIO bool +config PCA9552 + bool + depends on I2C + +config PCA9554 + bool + depends on I2C + config PCF8574 bool depends on I2C diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 8f9ce2f68c3f..4271e2f4ac9f 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -30,14 +30,6 @@ config EDU default y if TEST_DEVICES depends on PCI && MSI_NONBROKEN -config PCA9552 - bool - depends on I2C - -config PCA9554 - bool - depends on I2C - config I2C_ECHO bool default y if TEST_DEVICES From a4eefc69b23713c4e5981d9d91a6e15dfd4496fe Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:54 +0100 Subject: [PATCH 0302/1179] hw/arm: Add i.MX 8M Plus EVK board As a first step, implement the bare minimum: CPUs, RAM, interrupt controller, serial. All other devices of the A53 memory map are represented as TYPE_UNIMPLEMENTED_DEVICE, i.e. the whole memory map is provided. This allows for running Linux without it crashing due to invalid memory accesses. Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-5-shentey@gmail.com Reviewed-by: Peter Maydell [PMM: drop 'static const' from serial_table[] definition to avoid compile failure on GCC 7.5] Signed-off-by: Peter Maydell --- MAINTAINERS | 9 + docs/system/arm/imx8mp-evk.rst | 54 +++++ docs/system/target-arm.rst | 1 + hw/arm/Kconfig | 12 ++ hw/arm/fsl-imx8mp.c | 367 +++++++++++++++++++++++++++++++++ hw/arm/imx8mp-evk.c | 55 +++++ hw/arm/meson.build | 2 + include/hw/arm/fsl-imx8mp.h | 189 +++++++++++++++++ 8 files changed, 689 insertions(+) create mode 100644 docs/system/arm/imx8mp-evk.rst create mode 100644 hw/arm/fsl-imx8mp.c create mode 100644 hw/arm/imx8mp-evk.c create mode 100644 include/hw/arm/fsl-imx8mp.h diff --git a/MAINTAINERS b/MAINTAINERS index 1911949526ce..374fe98724ef 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -820,6 +820,15 @@ F: hw/pci-host/designware.c F: include/hw/pci-host/designware.h F: docs/system/arm/mcimx7d-sabre.rst +MCIMX8MP-EVK / i.MX8MP +M: Bernhard Beschow +L: qemu-arm@nongnu.org +S: Maintained +F: hw/arm/imx8mp-evk.c +F: hw/arm/fsl-imx8mp.c +F: include/hw/arm/fsl-imx8mp.h +F: docs/system/arm/imx8mp-evk.rst + MPS2 / MPS3 M: Peter Maydell L: qemu-arm@nongnu.org diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst new file mode 100644 index 000000000000..b23fdcc74390 --- /dev/null +++ b/docs/system/arm/imx8mp-evk.rst @@ -0,0 +1,54 @@ +NXP i.MX 8M Plus Evaluation Kit (``imx8mp-evk``) +================================================ + +The ``imx8mp-evk`` machine models the i.MX 8M Plus Evaluation Kit, based on an +i.MX 8M Plus SoC. + +Supported devices +----------------- + +The ``imx8mp-evk`` machine implements the following devices: + + * Up to 4 Cortex-A53 cores + * Generic Interrupt Controller (GICv3) + * 4 UARTs + +Boot options +------------ + +The ``imx8mp-evk`` machine can start a Linux kernel directly using the standard +``-kernel`` functionality. + +Direct Linux Kernel Boot +'''''''''''''''''''''''' + +Probably the easiest way to get started with a whole Linux system on the machine +is to generate an image with Buildroot. Version 2024.11.1 is tested at the time +of writing and involves two steps. First run the following commands in the +toplevel directory of the Buildroot source tree: + +.. code-block:: bash + + $ echo "BR2_TARGET_ROOTFS_CPIO=y" >> configs/freescale_imx8mpevk_defconfig + $ make freescale_imx8mpevk_defconfig + $ make + +Once finished successfully there is an ``output/image`` subfolder. Navigate into +it and patch the device tree with the following commands which will remove the +``cpu-idle-states`` properties from CPU nodes: + +.. code-block:: bash + + $ dtc imx8mp-evk.dtb | sed '/cpu-idle-states/d' > imx8mp-evk-patched.dts + $ dtc imx8mp-evk-patched.dts -o imx8mp-evk-patched.dtb + +Now that everything is prepared the machine can be started as follows: + +.. code-block:: bash + + $ qemu-system-aarch64 -M imx8mp-evk -smp 4 -m 3G \ + -display none -serial null -serial stdio \ + -kernel Image \ + -dtb imx8mp-evk-patched.dtb \ + -initrd rootfs.cpio \ + -append "root=/dev/ram" diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index 9aaa9c414c9e..a43ec8f10e03 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -95,6 +95,7 @@ Board-specific documentation arm/imx25-pdk arm/mcimx6ul-evk arm/mcimx7d-sabre + arm/imx8mp-evk arm/orangepi arm/raspi arm/collie diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 504841ccab40..0a7de4086170 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -593,6 +593,18 @@ config FSL_IMX7 select UNIMP select USB_CHIPIDEA +config FSL_IMX8MP + bool + select ARM_GIC + select IMX + select UNIMP + +config FSL_IMX8MP_EVK + bool + default y + depends on TCG && AARCH64 + select FSL_IMX8MP + config ARM_SMMUV3 bool diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c new file mode 100644 index 000000000000..084b1d3bb1f4 --- /dev/null +++ b/hw/arm/fsl-imx8mp.c @@ -0,0 +1,367 @@ +/* + * i.MX 8M Plus SoC Implementation + * + * Based on hw/arm/fsl-imx6.c + * + * Copyright (c) 2024, Bernhard Beschow + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "exec/address-spaces.h" +#include "hw/arm/bsa.h" +#include "hw/arm/fsl-imx8mp.h" +#include "hw/intc/arm_gicv3.h" +#include "hw/misc/unimp.h" +#include "hw/boards.h" +#include "system/system.h" +#include "target/arm/cpu-qom.h" +#include "qapi/error.h" +#include "qobject/qlist.h" + +static const struct { + hwaddr addr; + size_t size; + const char *name; +} fsl_imx8mp_memmap[] = { + [FSL_IMX8MP_RAM] = { FSL_IMX8MP_RAM_START, FSL_IMX8MP_RAM_SIZE_MAX, "ram" }, + [FSL_IMX8MP_DDR_PHY_BROADCAST] = { 0x3dc00000, 4 * MiB, "ddr_phy_broadcast" }, + [FSL_IMX8MP_DDR_PERF_MON] = { 0x3d800000, 4 * MiB, "ddr_perf_mon" }, + [FSL_IMX8MP_DDR_CTL] = { 0x3d400000, 4 * MiB, "ddr_ctl" }, + [FSL_IMX8MP_DDR_BLK_CTRL] = { 0x3d000000, 1 * MiB, "ddr_blk_ctrl" }, + [FSL_IMX8MP_DDR_PHY] = { 0x3c000000, 16 * MiB, "ddr_phy" }, + [FSL_IMX8MP_AUDIO_DSP] = { 0x3b000000, 16 * MiB, "audio_dsp" }, + [FSL_IMX8MP_GIC_DIST] = { 0x38800000, 512 * KiB, "gic_dist" }, + [FSL_IMX8MP_GIC_REDIST] = { 0x38880000, 512 * KiB, "gic_redist" }, + [FSL_IMX8MP_NPU] = { 0x38500000, 2 * MiB, "npu" }, + [FSL_IMX8MP_VPU] = { 0x38340000, 2 * MiB, "vpu" }, + [FSL_IMX8MP_VPU_BLK_CTRL] = { 0x38330000, 2 * MiB, "vpu_blk_ctrl" }, + [FSL_IMX8MP_VPU_VC8000E_ENCODER] = { 0x38320000, 2 * MiB, "vpu_vc8000e_encoder" }, + [FSL_IMX8MP_VPU_G2_DECODER] = { 0x38310000, 2 * MiB, "vpu_g2_decoder" }, + [FSL_IMX8MP_VPU_G1_DECODER] = { 0x38300000, 2 * MiB, "vpu_g1_decoder" }, + [FSL_IMX8MP_USB2] = { 0x38200000, 1 * MiB, "usb2" }, + [FSL_IMX8MP_USB1] = { 0x38100000, 1 * MiB, "usb1" }, + [FSL_IMX8MP_GPU2D] = { 0x38008000, 32 * KiB, "gpu2d" }, + [FSL_IMX8MP_GPU3D] = { 0x38000000, 32 * KiB, "gpu3d" }, + [FSL_IMX8MP_QSPI1_RX_BUFFER] = { 0x34000000, 32 * MiB, "qspi1_rx_buffer" }, + [FSL_IMX8MP_PCIE1] = { 0x33800000, 4 * MiB, "pcie1" }, + [FSL_IMX8MP_QSPI1_TX_BUFFER] = { 0x33008000, 32 * KiB, "qspi1_tx_buffer" }, + [FSL_IMX8MP_APBH_DMA] = { 0x33000000, 32 * KiB, "apbh_dma" }, + + /* AIPS-5 Begin */ + [FSL_IMX8MP_MU_3_B] = { 0x30e90000, 64 * KiB, "mu_3_b" }, + [FSL_IMX8MP_MU_3_A] = { 0x30e80000, 64 * KiB, "mu_3_a" }, + [FSL_IMX8MP_MU_2_B] = { 0x30e70000, 64 * KiB, "mu_2_b" }, + [FSL_IMX8MP_MU_2_A] = { 0x30e60000, 64 * KiB, "mu_2_a" }, + [FSL_IMX8MP_EDMA_CHANNELS] = { 0x30e40000, 128 * KiB, "edma_channels" }, + [FSL_IMX8MP_EDMA_MANAGEMENT_PAGE] = { 0x30e30000, 64 * KiB, "edma_management_page" }, + [FSL_IMX8MP_AUDIO_BLK_CTRL] = { 0x30e20000, 64 * KiB, "audio_blk_ctrl" }, + [FSL_IMX8MP_SDMA2] = { 0x30e10000, 64 * KiB, "sdma2" }, + [FSL_IMX8MP_SDMA3] = { 0x30e00000, 64 * KiB, "sdma3" }, + [FSL_IMX8MP_AIPS5_CONFIGURATION] = { 0x30df0000, 64 * KiB, "aips5_configuration" }, + [FSL_IMX8MP_SPBA2] = { 0x30cf0000, 64 * KiB, "spba2" }, + [FSL_IMX8MP_AUDIO_XCVR_RX] = { 0x30cc0000, 64 * KiB, "audio_xcvr_rx" }, + [FSL_IMX8MP_HDMI_TX_AUDLNK_MSTR] = { 0x30cb0000, 64 * KiB, "hdmi_tx_audlnk_mstr" }, + [FSL_IMX8MP_PDM] = { 0x30ca0000, 64 * KiB, "pdm" }, + [FSL_IMX8MP_ASRC] = { 0x30c90000, 64 * KiB, "asrc" }, + [FSL_IMX8MP_SAI7] = { 0x30c80000, 64 * KiB, "sai7" }, + [FSL_IMX8MP_SAI6] = { 0x30c60000, 64 * KiB, "sai6" }, + [FSL_IMX8MP_SAI5] = { 0x30c50000, 64 * KiB, "sai5" }, + [FSL_IMX8MP_SAI3] = { 0x30c30000, 64 * KiB, "sai3" }, + [FSL_IMX8MP_SAI2] = { 0x30c20000, 64 * KiB, "sai2" }, + [FSL_IMX8MP_SAI1] = { 0x30c10000, 64 * KiB, "sai1" }, + /* AIPS-5 End */ + + /* AIPS-4 Begin */ + [FSL_IMX8MP_HDMI_TX] = { 0x32fc0000, 128 * KiB, "hdmi_tx" }, + [FSL_IMX8MP_TZASC] = { 0x32f80000, 64 * KiB, "tzasc" }, + [FSL_IMX8MP_HSIO_BLK_CTL] = { 0x32f10000, 64 * KiB, "hsio_blk_ctl" }, + [FSL_IMX8MP_PCIE_PHY1] = { 0x32f00000, 64 * KiB, "pcie_phy1" }, + [FSL_IMX8MP_MEDIA_BLK_CTL] = { 0x32ec0000, 64 * KiB, "media_blk_ctl" }, + [FSL_IMX8MP_LCDIF2] = { 0x32e90000, 64 * KiB, "lcdif2" }, + [FSL_IMX8MP_LCDIF1] = { 0x32e80000, 64 * KiB, "lcdif1" }, + [FSL_IMX8MP_MIPI_DSI1] = { 0x32e60000, 64 * KiB, "mipi_dsi1" }, + [FSL_IMX8MP_MIPI_CSI2] = { 0x32e50000, 64 * KiB, "mipi_csi2" }, + [FSL_IMX8MP_MIPI_CSI1] = { 0x32e40000, 64 * KiB, "mipi_csi1" }, + [FSL_IMX8MP_IPS_DEWARP] = { 0x32e30000, 64 * KiB, "ips_dewarp" }, + [FSL_IMX8MP_ISP2] = { 0x32e20000, 64 * KiB, "isp2" }, + [FSL_IMX8MP_ISP1] = { 0x32e10000, 64 * KiB, "isp1" }, + [FSL_IMX8MP_ISI] = { 0x32e00000, 64 * KiB, "isi" }, + [FSL_IMX8MP_AIPS4_CONFIGURATION] = { 0x32df0000, 64 * KiB, "aips4_configuration" }, + /* AIPS-4 End */ + + [FSL_IMX8MP_INTERCONNECT] = { 0x32700000, 1 * MiB, "interconnect" }, + + /* AIPS-3 Begin */ + [FSL_IMX8MP_ENET2_TSN] = { 0x30bf0000, 64 * KiB, "enet2_tsn" }, + [FSL_IMX8MP_ENET1] = { 0x30be0000, 64 * KiB, "enet1" }, + [FSL_IMX8MP_SDMA1] = { 0x30bd0000, 64 * KiB, "sdma1" }, + [FSL_IMX8MP_QSPI] = { 0x30bb0000, 64 * KiB, "qspi" }, + [FSL_IMX8MP_USDHC3] = { 0x30b60000, 64 * KiB, "usdhc3" }, + [FSL_IMX8MP_USDHC2] = { 0x30b50000, 64 * KiB, "usdhc2" }, + [FSL_IMX8MP_USDHC1] = { 0x30b40000, 64 * KiB, "usdhc1" }, + [FSL_IMX8MP_I2C6] = { 0x30ae0000, 64 * KiB, "i2c6" }, + [FSL_IMX8MP_I2C5] = { 0x30ad0000, 64 * KiB, "i2c5" }, + [FSL_IMX8MP_SEMAPHORE_HS] = { 0x30ac0000, 64 * KiB, "semaphore_hs" }, + [FSL_IMX8MP_MU_1_B] = { 0x30ab0000, 64 * KiB, "mu_1_b" }, + [FSL_IMX8MP_MU_1_A] = { 0x30aa0000, 64 * KiB, "mu_1_a" }, + [FSL_IMX8MP_AUD_IRQ_STEER] = { 0x30a80000, 64 * KiB, "aud_irq_steer" }, + [FSL_IMX8MP_UART4] = { 0x30a60000, 64 * KiB, "uart4" }, + [FSL_IMX8MP_I2C4] = { 0x30a50000, 64 * KiB, "i2c4" }, + [FSL_IMX8MP_I2C3] = { 0x30a40000, 64 * KiB, "i2c3" }, + [FSL_IMX8MP_I2C2] = { 0x30a30000, 64 * KiB, "i2c2" }, + [FSL_IMX8MP_I2C1] = { 0x30a20000, 64 * KiB, "i2c1" }, + [FSL_IMX8MP_AIPS3_CONFIGURATION] = { 0x309f0000, 64 * KiB, "aips3_configuration" }, + [FSL_IMX8MP_CAAM] = { 0x30900000, 256 * KiB, "caam" }, + [FSL_IMX8MP_SPBA1] = { 0x308f0000, 64 * KiB, "spba1" }, + [FSL_IMX8MP_FLEXCAN2] = { 0x308d0000, 64 * KiB, "flexcan2" }, + [FSL_IMX8MP_FLEXCAN1] = { 0x308c0000, 64 * KiB, "flexcan1" }, + [FSL_IMX8MP_UART2] = { 0x30890000, 64 * KiB, "uart2" }, + [FSL_IMX8MP_UART3] = { 0x30880000, 64 * KiB, "uart3" }, + [FSL_IMX8MP_UART1] = { 0x30860000, 64 * KiB, "uart1" }, + [FSL_IMX8MP_ECSPI3] = { 0x30840000, 64 * KiB, "ecspi3" }, + [FSL_IMX8MP_ECSPI2] = { 0x30830000, 64 * KiB, "ecspi2" }, + [FSL_IMX8MP_ECSPI1] = { 0x30820000, 64 * KiB, "ecspi1" }, + /* AIPS-3 End */ + + /* AIPS-2 Begin */ + [FSL_IMX8MP_QOSC] = { 0x307f0000, 64 * KiB, "qosc" }, + [FSL_IMX8MP_PERFMON2] = { 0x307d0000, 64 * KiB, "perfmon2" }, + [FSL_IMX8MP_PERFMON1] = { 0x307c0000, 64 * KiB, "perfmon1" }, + [FSL_IMX8MP_GPT4] = { 0x30700000, 64 * KiB, "gpt4" }, + [FSL_IMX8MP_GPT5] = { 0x306f0000, 64 * KiB, "gpt5" }, + [FSL_IMX8MP_GPT6] = { 0x306e0000, 64 * KiB, "gpt6" }, + [FSL_IMX8MP_SYSCNT_CTRL] = { 0x306c0000, 64 * KiB, "syscnt_ctrl" }, + [FSL_IMX8MP_SYSCNT_CMP] = { 0x306b0000, 64 * KiB, "syscnt_cmp" }, + [FSL_IMX8MP_SYSCNT_RD] = { 0x306a0000, 64 * KiB, "syscnt_rd" }, + [FSL_IMX8MP_PWM4] = { 0x30690000, 64 * KiB, "pwm4" }, + [FSL_IMX8MP_PWM3] = { 0x30680000, 64 * KiB, "pwm3" }, + [FSL_IMX8MP_PWM2] = { 0x30670000, 64 * KiB, "pwm2" }, + [FSL_IMX8MP_PWM1] = { 0x30660000, 64 * KiB, "pwm1" }, + [FSL_IMX8MP_AIPS2_CONFIGURATION] = { 0x305f0000, 64 * KiB, "aips2_configuration" }, + /* AIPS-2 End */ + + /* AIPS-1 Begin */ + [FSL_IMX8MP_CSU] = { 0x303e0000, 64 * KiB, "csu" }, + [FSL_IMX8MP_RDC] = { 0x303d0000, 64 * KiB, "rdc" }, + [FSL_IMX8MP_SEMAPHORE2] = { 0x303c0000, 64 * KiB, "semaphore2" }, + [FSL_IMX8MP_SEMAPHORE1] = { 0x303b0000, 64 * KiB, "semaphore1" }, + [FSL_IMX8MP_GPC] = { 0x303a0000, 64 * KiB, "gpc" }, + [FSL_IMX8MP_SRC] = { 0x30390000, 64 * KiB, "src" }, + [FSL_IMX8MP_CCM] = { 0x30380000, 64 * KiB, "ccm" }, + [FSL_IMX8MP_SNVS_HP] = { 0x30370000, 64 * KiB, "snvs_hp" }, + [FSL_IMX8MP_ANA_PLL] = { 0x30360000, 64 * KiB, "ana_pll" }, + [FSL_IMX8MP_OCOTP_CTRL] = { 0x30350000, 64 * KiB, "ocotp_ctrl" }, + [FSL_IMX8MP_IOMUXC_GPR] = { 0x30340000, 64 * KiB, "iomuxc_gpr" }, + [FSL_IMX8MP_IOMUXC] = { 0x30330000, 64 * KiB, "iomuxc" }, + [FSL_IMX8MP_GPT3] = { 0x302f0000, 64 * KiB, "gpt3" }, + [FSL_IMX8MP_GPT2] = { 0x302e0000, 64 * KiB, "gpt2" }, + [FSL_IMX8MP_GPT1] = { 0x302d0000, 64 * KiB, "gpt1" }, + [FSL_IMX8MP_WDOG3] = { 0x302a0000, 64 * KiB, "wdog3" }, + [FSL_IMX8MP_WDOG2] = { 0x30290000, 64 * KiB, "wdog2" }, + [FSL_IMX8MP_WDOG1] = { 0x30280000, 64 * KiB, "wdog1" }, + [FSL_IMX8MP_ANA_OSC] = { 0x30270000, 64 * KiB, "ana_osc" }, + [FSL_IMX8MP_ANA_TSENSOR] = { 0x30260000, 64 * KiB, "ana_tsensor" }, + [FSL_IMX8MP_GPIO5] = { 0x30240000, 64 * KiB, "gpio5" }, + [FSL_IMX8MP_GPIO4] = { 0x30230000, 64 * KiB, "gpio4" }, + [FSL_IMX8MP_GPIO3] = { 0x30220000, 64 * KiB, "gpio3" }, + [FSL_IMX8MP_GPIO2] = { 0x30210000, 64 * KiB, "gpio2" }, + [FSL_IMX8MP_GPIO1] = { 0x30200000, 64 * KiB, "gpio1" }, + [FSL_IMX8MP_AIPS1_CONFIGURATION] = { 0x301f0000, 64 * KiB, "aips1_configuration" }, + /* AIPS-1 End */ + + [FSL_IMX8MP_A53_DAP] = { 0x28000000, 16 * MiB, "a53_dap" }, + [FSL_IMX8MP_PCIE1_MEM] = { 0x18000000, 128 * MiB, "pcie1_mem" }, + [FSL_IMX8MP_QSPI_MEM] = { 0x08000000, 256 * MiB, "qspi_mem" }, + [FSL_IMX8MP_OCRAM] = { 0x00900000, 576 * KiB, "ocram" }, + [FSL_IMX8MP_TCM_DTCM] = { 0x00800000, 128 * KiB, "tcm_dtcm" }, + [FSL_IMX8MP_TCM_ITCM] = { 0x007e0000, 128 * KiB, "tcm_itcm" }, + [FSL_IMX8MP_OCRAM_S] = { 0x00180000, 36 * KiB, "ocram_s" }, + [FSL_IMX8MP_CAAM_MEM] = { 0x00100000, 32 * KiB, "caam_mem" }, + [FSL_IMX8MP_BOOT_ROM_PROTECTED] = { 0x0003f000, 4 * KiB, "boot_rom_protected" }, + [FSL_IMX8MP_BOOT_ROM] = { 0x00000000, 252 * KiB, "boot_rom" }, +}; + +static void fsl_imx8mp_init(Object *obj) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + FslImx8mpState *s = FSL_IMX8MP(obj); + int i; + + for (i = 0; i < MIN(ms->smp.cpus, FSL_IMX8MP_NUM_CPUS); i++) { + g_autofree char *name = g_strdup_printf("cpu%d", i); + object_initialize_child(obj, name, &s->cpu[i], + ARM_CPU_TYPE_NAME("cortex-a53")); + } + + object_initialize_child(obj, "gic", &s->gic, TYPE_ARM_GICV3); + + for (i = 0; i < FSL_IMX8MP_NUM_UARTS; i++) { + g_autofree char *name = g_strdup_printf("uart%d", i + 1); + object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); + } +} + +static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + FslImx8mpState *s = FSL_IMX8MP(dev); + DeviceState *gicdev = DEVICE(&s->gic); + int i; + + if (ms->smp.cpus > FSL_IMX8MP_NUM_CPUS) { + error_setg(errp, "%s: Only %d CPUs are supported (%d requested)", + TYPE_FSL_IMX8MP, FSL_IMX8MP_NUM_CPUS, ms->smp.cpus); + return; + } + + /* CPUs */ + for (i = 0; i < ms->smp.cpus; i++) { + /* On uniprocessor, the CBAR is set to 0 */ + if (ms->smp.cpus > 1) { + object_property_set_int(OBJECT(&s->cpu[i]), "reset-cbar", + fsl_imx8mp_memmap[FSL_IMX8MP_GIC_DIST].addr, + &error_abort); + } + + /* + * CNTFID0 base frequency in Hz of system counter + */ + object_property_set_int(OBJECT(&s->cpu[i]), "cntfrq", 8000000, + &error_abort); + + if (i) { + /* + * Secondary CPUs start in powered-down state (and can be + * powered up via the SRC system reset controller) + */ + object_property_set_bool(OBJECT(&s->cpu[i]), "start-powered-off", + true, &error_abort); + } + + if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) { + return; + } + } + + /* GIC */ + { + SysBusDevice *gicsbd = SYS_BUS_DEVICE(&s->gic); + QList *redist_region_count; + + qdev_prop_set_uint32(gicdev, "num-cpu", ms->smp.cpus); + qdev_prop_set_uint32(gicdev, "num-irq", + FSL_IMX8MP_NUM_IRQS + GIC_INTERNAL); + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, ms->smp.cpus); + qdev_prop_set_array(gicdev, "redist-region-count", redist_region_count); + object_property_set_link(OBJECT(&s->gic), "sysmem", + OBJECT(get_system_memory()), &error_fatal); + if (!sysbus_realize(gicsbd, errp)) { + return; + } + sysbus_mmio_map(gicsbd, 0, fsl_imx8mp_memmap[FSL_IMX8MP_GIC_DIST].addr); + sysbus_mmio_map(gicsbd, 1, fsl_imx8mp_memmap[FSL_IMX8MP_GIC_REDIST].addr); + + /* + * Wire the outputs from each CPU's generic timer and the GICv3 + * maintenance interrupt signal to the appropriate GIC PPI inputs, and + * the GIC's IRQ/FIQ interrupt outputs to the CPU's inputs. + */ + for (i = 0; i < ms->smp.cpus; i++) { + DeviceState *cpudev = DEVICE(&s->cpu[i]); + int intidbase = FSL_IMX8MP_NUM_IRQS + i * GIC_INTERNAL; + qemu_irq irq; + + /* + * Mapping from the output timer irq lines from the CPU to the + * GIC PPI inputs. + */ + static const int timer_irqs[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + }; + + for (int j = 0; j < ARRAY_SIZE(timer_irqs); j++) { + irq = qdev_get_gpio_in(gicdev, intidbase + timer_irqs[j]); + qdev_connect_gpio_out(cpudev, j, irq); + } + + irq = qdev_get_gpio_in(gicdev, intidbase + ARCH_GIC_MAINT_IRQ); + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", + 0, irq); + + irq = qdev_get_gpio_in(gicdev, intidbase + VIRTUAL_PMU_IRQ); + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, irq); + + sysbus_connect_irq(gicsbd, i, + qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicsbd, i + ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + } + } + + /* UARTs */ + for (i = 0; i < FSL_IMX8MP_NUM_UARTS; i++) { + struct { + hwaddr addr; + unsigned int irq; + } serial_table[FSL_IMX8MP_NUM_UARTS] = { + { fsl_imx8mp_memmap[FSL_IMX8MP_UART1].addr, FSL_IMX8MP_UART1_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_UART2].addr, FSL_IMX8MP_UART2_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_UART3].addr, FSL_IMX8MP_UART3_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_UART4].addr, FSL_IMX8MP_UART4_IRQ }, + }; + + qdev_prop_set_chr(DEVICE(&s->uart[i]), "chardev", serial_hd(i)); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, serial_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, + qdev_get_gpio_in(gicdev, serial_table[i].irq)); + } + + /* Unimplemented devices */ + for (i = 0; i < ARRAY_SIZE(fsl_imx8mp_memmap); i++) { + switch (i) { + case FSL_IMX8MP_GIC_DIST: + case FSL_IMX8MP_GIC_REDIST: + case FSL_IMX8MP_RAM: + case FSL_IMX8MP_UART1 ... FSL_IMX8MP_UART4: + /* device implemented and treated above */ + break; + + default: + create_unimplemented_device(fsl_imx8mp_memmap[i].name, + fsl_imx8mp_memmap[i].addr, + fsl_imx8mp_memmap[i].size); + break; + } + } +} + +static void fsl_imx8mp_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = fsl_imx8mp_realize; + + dc->desc = "i.MX 8M Plus SoC"; +} + +static const TypeInfo fsl_imx8mp_types[] = { + { + .name = TYPE_FSL_IMX8MP, + .parent = TYPE_DEVICE, + .instance_size = sizeof(FslImx8mpState), + .instance_init = fsl_imx8mp_init, + .class_init = fsl_imx8mp_class_init, + }, +}; + +DEFINE_TYPES(fsl_imx8mp_types) diff --git a/hw/arm/imx8mp-evk.c b/hw/arm/imx8mp-evk.c new file mode 100644 index 000000000000..2756d4c21c8c --- /dev/null +++ b/hw/arm/imx8mp-evk.c @@ -0,0 +1,55 @@ +/* + * NXP i.MX 8M Plus Evaluation Kit System Emulation + * + * Copyright (c) 2024, Bernhard Beschow + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "exec/address-spaces.h" +#include "hw/arm/boot.h" +#include "hw/arm/fsl-imx8mp.h" +#include "hw/boards.h" +#include "system/qtest.h" +#include "qemu/error-report.h" +#include "qapi/error.h" + +static void imx8mp_evk_init(MachineState *machine) +{ + static struct arm_boot_info boot_info; + FslImx8mpState *s; + + if (machine->ram_size > FSL_IMX8MP_RAM_SIZE_MAX) { + error_report("RAM size " RAM_ADDR_FMT " above max supported (%08" PRIx64 ")", + machine->ram_size, FSL_IMX8MP_RAM_SIZE_MAX); + exit(1); + } + + boot_info = (struct arm_boot_info) { + .loader_start = FSL_IMX8MP_RAM_START, + .board_id = -1, + .ram_size = machine->ram_size, + .psci_conduit = QEMU_PSCI_CONDUIT_SMC, + }; + + s = FSL_IMX8MP(object_new(TYPE_FSL_IMX8MP)); + object_property_add_child(OBJECT(machine), "soc", OBJECT(s)); + qdev_realize(DEVICE(s), NULL, &error_fatal); + + memory_region_add_subregion(get_system_memory(), FSL_IMX8MP_RAM_START, + machine->ram); + + if (!qtest_enabled()) { + arm_load_kernel(&s->cpu[0], machine, &boot_info); + } +} + +static void imx8mp_evk_machine_init(MachineClass *mc) +{ + mc->desc = "NXP i.MX 8M Plus EVK Board"; + mc->init = imx8mp_evk_init; + mc->max_cpus = FSL_IMX8MP_NUM_CPUS; + mc->default_ram_id = "imx8mp-evk.ram"; +} +DEFINE_MACHINE("imx8mp-evk", imx8mp_evk_machine_init) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 465c757f976b..ac473ce7cdac 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -54,6 +54,8 @@ arm_ss.add(when: 'CONFIG_MSF2', if_true: files('msf2-soc.c')) arm_ss.add(when: 'CONFIG_MUSCA', if_true: files('musca.c')) arm_ss.add(when: 'CONFIG_ARMSSE', if_true: files('armsse.c')) arm_ss.add(when: 'CONFIG_FSL_IMX7', if_true: files('fsl-imx7.c', 'mcimx7d-sabre.c')) +arm_ss.add(when: 'CONFIG_FSL_IMX8MP', if_true: files('fsl-imx8mp.c')) +arm_ss.add(when: 'CONFIG_FSL_IMX8MP_EVK', if_true: files('imx8mp-evk.c')) arm_ss.add(when: 'CONFIG_ARM_SMMUV3', if_true: files('smmuv3.c')) arm_ss.add(when: 'CONFIG_FSL_IMX6UL', if_true: files('fsl-imx6ul.c', 'mcimx6ul-evk.c')) arm_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_soc.c')) diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h new file mode 100644 index 000000000000..57e23d1b69f5 --- /dev/null +++ b/include/hw/arm/fsl-imx8mp.h @@ -0,0 +1,189 @@ +/* + * i.MX 8M Plus SoC Definitions + * + * Copyright (c) 2024, Bernhard Beschow + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef FSL_IMX8MP_H +#define FSL_IMX8MP_H + +#include "cpu.h" +#include "hw/char/imx_serial.h" +#include "hw/intc/arm_gicv3_common.h" +#include "qom/object.h" +#include "qemu/units.h" + +#define TYPE_FSL_IMX8MP "fsl-imx8mp" +OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mpState, FSL_IMX8MP) + +#define FSL_IMX8MP_RAM_START 0x40000000 +#define FSL_IMX8MP_RAM_SIZE_MAX (8 * GiB) + +enum FslImx8mpConfiguration { + FSL_IMX8MP_NUM_CPUS = 4, + FSL_IMX8MP_NUM_IRQS = 160, + FSL_IMX8MP_NUM_UARTS = 4, +}; + +struct FslImx8mpState { + DeviceState parent_obj; + + ARMCPU cpu[FSL_IMX8MP_NUM_CPUS]; + GICv3State gic; + IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; +}; + +enum FslImx8mpMemoryRegions { + FSL_IMX8MP_A53_DAP, + FSL_IMX8MP_AIPS1_CONFIGURATION, + FSL_IMX8MP_AIPS2_CONFIGURATION, + FSL_IMX8MP_AIPS3_CONFIGURATION, + FSL_IMX8MP_AIPS4_CONFIGURATION, + FSL_IMX8MP_AIPS5_CONFIGURATION, + FSL_IMX8MP_ANA_OSC, + FSL_IMX8MP_ANA_PLL, + FSL_IMX8MP_ANA_TSENSOR, + FSL_IMX8MP_APBH_DMA, + FSL_IMX8MP_ASRC, + FSL_IMX8MP_AUDIO_BLK_CTRL, + FSL_IMX8MP_AUDIO_DSP, + FSL_IMX8MP_AUDIO_XCVR_RX, + FSL_IMX8MP_AUD_IRQ_STEER, + FSL_IMX8MP_BOOT_ROM, + FSL_IMX8MP_BOOT_ROM_PROTECTED, + FSL_IMX8MP_CAAM, + FSL_IMX8MP_CAAM_MEM, + FSL_IMX8MP_CCM, + FSL_IMX8MP_CSU, + FSL_IMX8MP_DDR_BLK_CTRL, + FSL_IMX8MP_DDR_CTL, + FSL_IMX8MP_DDR_PERF_MON, + FSL_IMX8MP_DDR_PHY, + FSL_IMX8MP_DDR_PHY_BROADCAST, + FSL_IMX8MP_ECSPI1, + FSL_IMX8MP_ECSPI2, + FSL_IMX8MP_ECSPI3, + FSL_IMX8MP_EDMA_CHANNELS, + FSL_IMX8MP_EDMA_MANAGEMENT_PAGE, + FSL_IMX8MP_ENET1, + FSL_IMX8MP_ENET2_TSN, + FSL_IMX8MP_FLEXCAN1, + FSL_IMX8MP_FLEXCAN2, + FSL_IMX8MP_GIC_DIST, + FSL_IMX8MP_GIC_REDIST, + FSL_IMX8MP_GPC, + FSL_IMX8MP_GPIO1, + FSL_IMX8MP_GPIO2, + FSL_IMX8MP_GPIO3, + FSL_IMX8MP_GPIO4, + FSL_IMX8MP_GPIO5, + FSL_IMX8MP_GPT1, + FSL_IMX8MP_GPT2, + FSL_IMX8MP_GPT3, + FSL_IMX8MP_GPT4, + FSL_IMX8MP_GPT5, + FSL_IMX8MP_GPT6, + FSL_IMX8MP_GPU2D, + FSL_IMX8MP_GPU3D, + FSL_IMX8MP_HDMI_TX, + FSL_IMX8MP_HDMI_TX_AUDLNK_MSTR, + FSL_IMX8MP_HSIO_BLK_CTL, + FSL_IMX8MP_I2C1, + FSL_IMX8MP_I2C2, + FSL_IMX8MP_I2C3, + FSL_IMX8MP_I2C4, + FSL_IMX8MP_I2C5, + FSL_IMX8MP_I2C6, + FSL_IMX8MP_INTERCONNECT, + FSL_IMX8MP_IOMUXC, + FSL_IMX8MP_IOMUXC_GPR, + FSL_IMX8MP_IPS_DEWARP, + FSL_IMX8MP_ISI, + FSL_IMX8MP_ISP1, + FSL_IMX8MP_ISP2, + FSL_IMX8MP_LCDIF1, + FSL_IMX8MP_LCDIF2, + FSL_IMX8MP_MEDIA_BLK_CTL, + FSL_IMX8MP_MIPI_CSI1, + FSL_IMX8MP_MIPI_CSI2, + FSL_IMX8MP_MIPI_DSI1, + FSL_IMX8MP_MU_1_A, + FSL_IMX8MP_MU_1_B, + FSL_IMX8MP_MU_2_A, + FSL_IMX8MP_MU_2_B, + FSL_IMX8MP_MU_3_A, + FSL_IMX8MP_MU_3_B, + FSL_IMX8MP_NPU, + FSL_IMX8MP_OCOTP_CTRL, + FSL_IMX8MP_OCRAM, + FSL_IMX8MP_OCRAM_S, + FSL_IMX8MP_PCIE1, + FSL_IMX8MP_PCIE1_MEM, + FSL_IMX8MP_PCIE_PHY1, + FSL_IMX8MP_PDM, + FSL_IMX8MP_PERFMON1, + FSL_IMX8MP_PERFMON2, + FSL_IMX8MP_PWM1, + FSL_IMX8MP_PWM2, + FSL_IMX8MP_PWM3, + FSL_IMX8MP_PWM4, + FSL_IMX8MP_QOSC, + FSL_IMX8MP_QSPI, + FSL_IMX8MP_QSPI1_RX_BUFFER, + FSL_IMX8MP_QSPI1_TX_BUFFER, + FSL_IMX8MP_QSPI_MEM, + FSL_IMX8MP_RAM, + FSL_IMX8MP_RDC, + FSL_IMX8MP_SAI1, + FSL_IMX8MP_SAI2, + FSL_IMX8MP_SAI3, + FSL_IMX8MP_SAI5, + FSL_IMX8MP_SAI6, + FSL_IMX8MP_SAI7, + FSL_IMX8MP_SDMA1, + FSL_IMX8MP_SDMA2, + FSL_IMX8MP_SDMA3, + FSL_IMX8MP_SEMAPHORE1, + FSL_IMX8MP_SEMAPHORE2, + FSL_IMX8MP_SEMAPHORE_HS, + FSL_IMX8MP_SNVS_HP, + FSL_IMX8MP_SPBA1, + FSL_IMX8MP_SPBA2, + FSL_IMX8MP_SRC, + FSL_IMX8MP_SYSCNT_CMP, + FSL_IMX8MP_SYSCNT_CTRL, + FSL_IMX8MP_SYSCNT_RD, + FSL_IMX8MP_TCM_DTCM, + FSL_IMX8MP_TCM_ITCM, + FSL_IMX8MP_TZASC, + FSL_IMX8MP_UART1, + FSL_IMX8MP_UART2, + FSL_IMX8MP_UART3, + FSL_IMX8MP_UART4, + FSL_IMX8MP_USB1, + FSL_IMX8MP_USB2, + FSL_IMX8MP_USDHC1, + FSL_IMX8MP_USDHC2, + FSL_IMX8MP_USDHC3, + FSL_IMX8MP_VPU, + FSL_IMX8MP_VPU_BLK_CTRL, + FSL_IMX8MP_VPU_G1_DECODER, + FSL_IMX8MP_VPU_G2_DECODER, + FSL_IMX8MP_VPU_VC8000E_ENCODER, + FSL_IMX8MP_WDOG1, + FSL_IMX8MP_WDOG2, + FSL_IMX8MP_WDOG3, +}; + +enum FslImx8mpIrqs { + FSL_IMX8MP_UART1_IRQ = 26, + FSL_IMX8MP_UART2_IRQ = 27, + FSL_IMX8MP_UART3_IRQ = 28, + FSL_IMX8MP_UART4_IRQ = 29, + FSL_IMX8MP_UART5_IRQ = 30, + FSL_IMX8MP_UART6_IRQ = 16, +}; + +#endif /* FSL_IMX8MP_H */ From 86c2dff9552ad5a9b2febf329a2dbd2620bc2145 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:55 +0100 Subject: [PATCH 0303/1179] hw/arm/fsl-imx8mp: Implement clock tree Fixes quite a few stack traces during the Linux boot process. Also provides the clocks for devices added later, e.g. enet1. Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-6-shentey@gmail.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- MAINTAINERS | 2 + docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/Kconfig | 2 + hw/arm/fsl-imx8mp.c | 20 ++++ hw/misc/Kconfig | 6 ++ hw/misc/imx8mp_analog.c | 160 +++++++++++++++++++++++++++++ hw/misc/imx8mp_ccm.c | 175 ++++++++++++++++++++++++++++++++ hw/misc/meson.build | 2 + include/hw/arm/fsl-imx8mp.h | 4 + include/hw/misc/imx8mp_analog.h | 81 +++++++++++++++ include/hw/misc/imx8mp_ccm.h | 30 ++++++ 11 files changed, 483 insertions(+) create mode 100644 hw/misc/imx8mp_analog.c create mode 100644 hw/misc/imx8mp_ccm.c create mode 100644 include/hw/misc/imx8mp_analog.h create mode 100644 include/hw/misc/imx8mp_ccm.h diff --git a/MAINTAINERS b/MAINTAINERS index 374fe98724ef..8ea7fb4c7a00 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -826,7 +826,9 @@ L: qemu-arm@nongnu.org S: Maintained F: hw/arm/imx8mp-evk.c F: hw/arm/fsl-imx8mp.c +F: hw/misc/imx8mp_*.c F: include/hw/arm/fsl-imx8mp.h +F: include/hw/misc/imx8mp_*.h F: docs/system/arm/imx8mp-evk.rst MPS2 / MPS3 diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index b23fdcc74390..f0df346113fb 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -12,6 +12,7 @@ The ``imx8mp-evk`` machine implements the following devices: * Up to 4 Cortex-A53 cores * Generic Interrupt Controller (GICv3) * 4 UARTs + * Clock Tree Boot options ------------ diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 0a7de4086170..f77c451ba39d 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -596,6 +596,8 @@ config FSL_IMX7 config FSL_IMX8MP bool select ARM_GIC + select FSL_IMX8MP_ANALOG + select FSL_IMX8MP_CCM select IMX select UNIMP diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 084b1d3bb1f4..bc15b25ca164 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -197,6 +197,10 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, "gic", &s->gic, TYPE_ARM_GICV3); + object_initialize_child(obj, "ccm", &s->ccm, TYPE_IMX8MP_CCM); + + object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); + for (i = 0; i < FSL_IMX8MP_NUM_UARTS; i++) { g_autofree char *name = g_strdup_printf("uart%d", i + 1); object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); @@ -304,6 +308,20 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) } } + /* CCM */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ccm), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccm), 0, + fsl_imx8mp_memmap[FSL_IMX8MP_CCM].addr); + + /* Analog */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->analog), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->analog), 0, + fsl_imx8mp_memmap[FSL_IMX8MP_ANA_PLL].addr); + /* UARTs */ for (i = 0; i < FSL_IMX8MP_NUM_UARTS; i++) { struct { @@ -329,6 +347,8 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mp_memmap); i++) { switch (i) { + case FSL_IMX8MP_ANA_PLL: + case FSL_IMX8MP_CCM: case FSL_IMX8MP_GIC_DIST: case FSL_IMX8MP_GIC_REDIST: case FSL_IMX8MP_RAM: diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 4271e2f4ac9f..82bd68b4bb82 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -78,6 +78,12 @@ config IMX select SSI select USB_EHCI_SYSBUS +config FSL_IMX8MP_ANALOG + bool + +config FSL_IMX8MP_CCM + bool + config STM32_RCC bool diff --git a/hw/misc/imx8mp_analog.c b/hw/misc/imx8mp_analog.c new file mode 100644 index 000000000000..f7e7c83cc495 --- /dev/null +++ b/hw/misc/imx8mp_analog.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2025 Bernhard Beschow + * + * i.MX 8M Plus ANALOG IP block emulation code + * + * Based on hw/misc/imx7_ccm.c + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" + +#include "hw/misc/imx8mp_analog.h" +#include "migration/vmstate.h" + +#define ANALOG_PLL_LOCK BIT(31) + +static void imx8mp_analog_reset(DeviceState *dev) +{ + IMX8MPAnalogState *s = IMX8MP_ANALOG(dev); + + memset(s->analog, 0, sizeof(s->analog)); + + s->analog[ANALOG_AUDIO_PLL1_GEN_CTRL] = 0x00002010; + s->analog[ANALOG_AUDIO_PLL1_FDIV_CTL0] = 0x00145032; + s->analog[ANALOG_AUDIO_PLL1_FDIV_CTL1] = 0x00000000; + s->analog[ANALOG_AUDIO_PLL1_SSCG_CTRL] = 0x00000000; + s->analog[ANALOG_AUDIO_PLL1_MNIT_CTRL] = 0x00100103; + s->analog[ANALOG_AUDIO_PLL2_GEN_CTRL] = 0x00002010; + s->analog[ANALOG_AUDIO_PLL2_FDIV_CTL0] = 0x00145032; + s->analog[ANALOG_AUDIO_PLL2_FDIV_CTL1] = 0x00000000; + s->analog[ANALOG_AUDIO_PLL2_SSCG_CTRL] = 0x00000000; + s->analog[ANALOG_AUDIO_PLL2_MNIT_CTRL] = 0x00100103; + s->analog[ANALOG_VIDEO_PLL1_GEN_CTRL] = 0x00002010; + s->analog[ANALOG_VIDEO_PLL1_FDIV_CTL0] = 0x00145032; + s->analog[ANALOG_VIDEO_PLL1_FDIV_CTL1] = 0x00000000; + s->analog[ANALOG_VIDEO_PLL1_SSCG_CTRL] = 0x00000000; + s->analog[ANALOG_VIDEO_PLL1_MNIT_CTRL] = 0x00100103; + s->analog[ANALOG_DRAM_PLL_GEN_CTRL] = 0x00002010; + s->analog[ANALOG_DRAM_PLL_FDIV_CTL0] = 0x0012c032; + s->analog[ANALOG_DRAM_PLL_FDIV_CTL1] = 0x00000000; + s->analog[ANALOG_DRAM_PLL_SSCG_CTRL] = 0x00000000; + s->analog[ANALOG_DRAM_PLL_MNIT_CTRL] = 0x00100103; + s->analog[ANALOG_GPU_PLL_GEN_CTRL] = 0x00000810; + s->analog[ANALOG_GPU_PLL_FDIV_CTL0] = 0x000c8031; + s->analog[ANALOG_GPU_PLL_LOCKD_CTRL] = 0x0010003f; + s->analog[ANALOG_GPU_PLL_MNIT_CTRL] = 0x00280081; + s->analog[ANALOG_VPU_PLL_GEN_CTRL] = 0x00000810; + s->analog[ANALOG_VPU_PLL_FDIV_CTL0] = 0x0012c032; + s->analog[ANALOG_VPU_PLL_LOCKD_CTRL] = 0x0010003f; + s->analog[ANALOG_VPU_PLL_MNIT_CTRL] = 0x00280081; + s->analog[ANALOG_ARM_PLL_GEN_CTRL] = 0x00000810; + s->analog[ANALOG_ARM_PLL_FDIV_CTL0] = 0x000fa031; + s->analog[ANALOG_ARM_PLL_LOCKD_CTRL] = 0x0010003f; + s->analog[ANALOG_ARM_PLL_MNIT_CTRL] = 0x00280081; + s->analog[ANALOG_SYS_PLL1_GEN_CTRL] = 0x0aaaa810; + s->analog[ANALOG_SYS_PLL1_FDIV_CTL0] = 0x00190032; + s->analog[ANALOG_SYS_PLL1_LOCKD_CTRL] = 0x0010003f; + s->analog[ANALOG_SYS_PLL1_MNIT_CTRL] = 0x00280081; + s->analog[ANALOG_SYS_PLL2_GEN_CTRL] = 0x0aaaa810; + s->analog[ANALOG_SYS_PLL2_FDIV_CTL0] = 0x000fa031; + s->analog[ANALOG_SYS_PLL2_LOCKD_CTRL] = 0x0010003f; + s->analog[ANALOG_SYS_PLL2_MNIT_CTRL] = 0x00280081; + s->analog[ANALOG_SYS_PLL3_GEN_CTRL] = 0x00000810; + s->analog[ANALOG_SYS_PLL3_FDIV_CTL0] = 0x000fa031; + s->analog[ANALOG_SYS_PLL3_LOCKD_CTRL] = 0x0010003f; + s->analog[ANALOG_SYS_PLL3_MNIT_CTRL] = 0x00280081; + s->analog[ANALOG_OSC_MISC_CFG] = 0x00000000; + s->analog[ANALOG_ANAMIX_PLL_MNIT_CTL] = 0x00000000; + s->analog[ANALOG_DIGPROG] = 0x00824010; + + /* all PLLs need to be locked */ + s->analog[ANALOG_AUDIO_PLL1_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_AUDIO_PLL2_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_VIDEO_PLL1_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_DRAM_PLL_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_GPU_PLL_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_VPU_PLL_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_ARM_PLL_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_SYS_PLL1_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_SYS_PLL2_GEN_CTRL] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_SYS_PLL3_GEN_CTRL] |= ANALOG_PLL_LOCK; +} + +static uint64_t imx8mp_analog_read(void *opaque, hwaddr offset, unsigned size) +{ + IMX8MPAnalogState *s = opaque; + + return s->analog[offset >> 2]; +} + +static void imx8mp_analog_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + IMX8MPAnalogState *s = opaque; + + if (offset >> 2 == ANALOG_DIGPROG) { + qemu_log_mask(LOG_GUEST_ERROR, + "Guest write to read-only ANALOG_DIGPROG register\n"); + } else { + s->analog[offset >> 2] = value; + } +} + +static const struct MemoryRegionOps imx8mp_analog_ops = { + .read = imx8mp_analog_read, + .write = imx8mp_analog_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void imx8mp_analog_init(Object *obj) +{ + IMX8MPAnalogState *s = IMX8MP_ANALOG(obj); + SysBusDevice *sd = SYS_BUS_DEVICE(obj); + + memory_region_init(&s->mmio.container, obj, TYPE_IMX8MP_ANALOG, 0x10000); + + memory_region_init_io(&s->mmio.analog, obj, &imx8mp_analog_ops, s, + TYPE_IMX8MP_ANALOG, sizeof(s->analog)); + memory_region_add_subregion(&s->mmio.container, 0, &s->mmio.analog); + + sysbus_init_mmio(sd, &s->mmio.container); +} + +static const VMStateDescription imx8mp_analog_vmstate = { + .name = TYPE_IMX8MP_ANALOG, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(analog, IMX8MPAnalogState, ANALOG_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx8mp_analog_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, imx8mp_analog_reset); + dc->vmsd = &imx8mp_analog_vmstate; + dc->desc = "i.MX 8M Plus Analog Module"; +} + +static const TypeInfo imx8mp_analog_types[] = { + { + .name = TYPE_IMX8MP_ANALOG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMX8MPAnalogState), + .instance_init = imx8mp_analog_init, + .class_init = imx8mp_analog_class_init, + } +}; + +DEFINE_TYPES(imx8mp_analog_types); diff --git a/hw/misc/imx8mp_ccm.c b/hw/misc/imx8mp_ccm.c new file mode 100644 index 000000000000..1a1c932427a4 --- /dev/null +++ b/hw/misc/imx8mp_ccm.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025 Bernhard Beschow + * + * i.MX 8M Plus CCM IP block emulation code + * + * Based on hw/misc/imx7_ccm.c + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" + +#include "hw/misc/imx8mp_ccm.h" +#include "migration/vmstate.h" + +#include "trace.h" + +#define CKIH_FREQ 16000000 /* 16MHz crystal input */ + +static void imx8mp_ccm_reset(DeviceState *dev) +{ + IMX8MPCCMState *s = IMX8MP_CCM(dev); + + memset(s->ccm, 0, sizeof(s->ccm)); +} + +#define CCM_INDEX(offset) (((offset) & ~(hwaddr)0xF) / sizeof(uint32_t)) +#define CCM_BITOP(offset) ((offset) & (hwaddr)0xF) + +enum { + CCM_BITOP_NONE = 0x00, + CCM_BITOP_SET = 0x04, + CCM_BITOP_CLR = 0x08, + CCM_BITOP_TOG = 0x0C, +}; + +static uint64_t imx8mp_set_clr_tog_read(void *opaque, hwaddr offset, + unsigned size) +{ + const uint32_t *mmio = opaque; + + return mmio[CCM_INDEX(offset)]; +} + +static void imx8mp_set_clr_tog_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + const uint8_t bitop = CCM_BITOP(offset); + const uint32_t index = CCM_INDEX(offset); + uint32_t *mmio = opaque; + + switch (bitop) { + case CCM_BITOP_NONE: + mmio[index] = value; + break; + case CCM_BITOP_SET: + mmio[index] |= value; + break; + case CCM_BITOP_CLR: + mmio[index] &= ~value; + break; + case CCM_BITOP_TOG: + mmio[index] ^= value; + break; + }; +} + +static const struct MemoryRegionOps imx8mp_set_clr_tog_ops = { + .read = imx8mp_set_clr_tog_read, + .write = imx8mp_set_clr_tog_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void imx8mp_ccm_init(Object *obj) +{ + SysBusDevice *sd = SYS_BUS_DEVICE(obj); + IMX8MPCCMState *s = IMX8MP_CCM(obj); + + memory_region_init_io(&s->iomem, + obj, + &imx8mp_set_clr_tog_ops, + s->ccm, + TYPE_IMX8MP_CCM ".ccm", + sizeof(s->ccm)); + + sysbus_init_mmio(sd, &s->iomem); +} + +static const VMStateDescription imx8mp_ccm_vmstate = { + .name = TYPE_IMX8MP_CCM, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(ccm, IMX8MPCCMState, CCM_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static uint32_t imx8mp_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock) +{ + /* + * This function is "consumed" by GPT emulation code. Some clocks + * have fixed frequencies and we can provide requested frequency + * easily. However for CCM provided clocks (like IPG) each GPT + * timer can have its own clock root. + * This means we need additional information when calling this + * function to know the requester's identity. + */ + uint32_t freq = 0; + + switch (clock) { + case CLK_NONE: + break; + case CLK_32k: + freq = CKIL_FREQ; + break; + case CLK_HIGH: + freq = CKIH_FREQ; + break; + case CLK_IPG: + case CLK_IPG_HIGH: + /* + * For now we don't have a way to figure out the device this + * function is called for. Until then the IPG derived clocks + * are left unimplemented. + */ + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Clock %d Not implemented\n", + TYPE_IMX8MP_CCM, __func__, clock); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: unsupported clock %d\n", + TYPE_IMX8MP_CCM, __func__, clock); + break; + } + + trace_ccm_clock_freq(clock, freq); + + return freq; +} + +static void imx8mp_ccm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + IMXCCMClass *ccm = IMX_CCM_CLASS(klass); + + device_class_set_legacy_reset(dc, imx8mp_ccm_reset); + dc->vmsd = &imx8mp_ccm_vmstate; + dc->desc = "i.MX 8M Plus Clock Control Module"; + + ccm->get_clock_frequency = imx8mp_ccm_get_clock_frequency; +} + +static const TypeInfo imx8mp_ccm_types[] = { + { + .name = TYPE_IMX8MP_CCM, + .parent = TYPE_IMX_CCM, + .instance_size = sizeof(IMX8MPCCMState), + .instance_init = imx8mp_ccm_init, + .class_init = imx8mp_ccm_class_init, + }, +}; + +DEFINE_TYPES(imx8mp_ccm_types); diff --git a/hw/misc/meson.build b/hw/misc/meson.build index edd36a334d73..0b5187a2f746 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -55,6 +55,8 @@ system_ss.add(when: 'CONFIG_AXP2XX_PMU', if_true: files('axp2xx.c')) system_ss.add(when: 'CONFIG_REALVIEW', if_true: files('arm_sysctl.c')) system_ss.add(when: 'CONFIG_ECCMEMCTL', if_true: files('eccmemctl.c')) system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pmu.c', 'exynos4210_clk.c', 'exynos4210_rng.c')) +system_ss.add(when: 'CONFIG_FSL_IMX8MP_ANALOG', if_true: files('imx8mp_analog.c')) +system_ss.add(when: 'CONFIG_FSL_IMX8MP_CCM', if_true: files('imx8mp_ccm.c')) system_ss.add(when: 'CONFIG_IMX', if_true: files( 'imx25_ccm.c', 'imx31_ccm.c', diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 57e23d1b69f5..ce5188e7f235 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -12,6 +12,8 @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/imx8mp_analog.h" +#include "hw/misc/imx8mp_ccm.h" #include "qom/object.h" #include "qemu/units.h" @@ -32,6 +34,8 @@ struct FslImx8mpState { ARMCPU cpu[FSL_IMX8MP_NUM_CPUS]; GICv3State gic; + IMX8MPCCMState ccm; + IMX8MPAnalogState analog; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; }; diff --git a/include/hw/misc/imx8mp_analog.h b/include/hw/misc/imx8mp_analog.h new file mode 100644 index 000000000000..955f03215a03 --- /dev/null +++ b/include/hw/misc/imx8mp_analog.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025 Bernhard Beschow + * + * i.MX8MP ANALOG IP block emulation code + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef IMX8MP_ANALOG_H +#define IMX8MP_ANALOG_H + +#include "qom/object.h" +#include "hw/sysbus.h" + +enum IMX8MPAnalogRegisters { + ANALOG_AUDIO_PLL1_GEN_CTRL = 0x000 / 4, + ANALOG_AUDIO_PLL1_FDIV_CTL0 = 0x004 / 4, + ANALOG_AUDIO_PLL1_FDIV_CTL1 = 0x008 / 4, + ANALOG_AUDIO_PLL1_SSCG_CTRL = 0x00c / 4, + ANALOG_AUDIO_PLL1_MNIT_CTRL = 0x010 / 4, + ANALOG_AUDIO_PLL2_GEN_CTRL = 0x014 / 4, + ANALOG_AUDIO_PLL2_FDIV_CTL0 = 0x018 / 4, + ANALOG_AUDIO_PLL2_FDIV_CTL1 = 0x01c / 4, + ANALOG_AUDIO_PLL2_SSCG_CTRL = 0x020 / 4, + ANALOG_AUDIO_PLL2_MNIT_CTRL = 0x024 / 4, + ANALOG_VIDEO_PLL1_GEN_CTRL = 0x028 / 4, + ANALOG_VIDEO_PLL1_FDIV_CTL0 = 0x02c / 4, + ANALOG_VIDEO_PLL1_FDIV_CTL1 = 0x030 / 4, + ANALOG_VIDEO_PLL1_SSCG_CTRL = 0x034 / 4, + ANALOG_VIDEO_PLL1_MNIT_CTRL = 0x038 / 4, + ANALOG_DRAM_PLL_GEN_CTRL = 0x050 / 4, + ANALOG_DRAM_PLL_FDIV_CTL0 = 0x054 / 4, + ANALOG_DRAM_PLL_FDIV_CTL1 = 0x058 / 4, + ANALOG_DRAM_PLL_SSCG_CTRL = 0x05c / 4, + ANALOG_DRAM_PLL_MNIT_CTRL = 0x060 / 4, + ANALOG_GPU_PLL_GEN_CTRL = 0x064 / 4, + ANALOG_GPU_PLL_FDIV_CTL0 = 0x068 / 4, + ANALOG_GPU_PLL_LOCKD_CTRL = 0x06c / 4, + ANALOG_GPU_PLL_MNIT_CTRL = 0x070 / 4, + ANALOG_VPU_PLL_GEN_CTRL = 0x074 / 4, + ANALOG_VPU_PLL_FDIV_CTL0 = 0x078 / 4, + ANALOG_VPU_PLL_LOCKD_CTRL = 0x07c / 4, + ANALOG_VPU_PLL_MNIT_CTRL = 0x080 / 4, + ANALOG_ARM_PLL_GEN_CTRL = 0x084 / 4, + ANALOG_ARM_PLL_FDIV_CTL0 = 0x088 / 4, + ANALOG_ARM_PLL_LOCKD_CTRL = 0x08c / 4, + ANALOG_ARM_PLL_MNIT_CTRL = 0x090 / 4, + ANALOG_SYS_PLL1_GEN_CTRL = 0x094 / 4, + ANALOG_SYS_PLL1_FDIV_CTL0 = 0x098 / 4, + ANALOG_SYS_PLL1_LOCKD_CTRL = 0x09c / 4, + ANALOG_SYS_PLL1_MNIT_CTRL = 0x100 / 4, + ANALOG_SYS_PLL2_GEN_CTRL = 0x104 / 4, + ANALOG_SYS_PLL2_FDIV_CTL0 = 0x108 / 4, + ANALOG_SYS_PLL2_LOCKD_CTRL = 0x10c / 4, + ANALOG_SYS_PLL2_MNIT_CTRL = 0x110 / 4, + ANALOG_SYS_PLL3_GEN_CTRL = 0x114 / 4, + ANALOG_SYS_PLL3_FDIV_CTL0 = 0x118 / 4, + ANALOG_SYS_PLL3_LOCKD_CTRL = 0x11c / 4, + ANALOG_SYS_PLL3_MNIT_CTRL = 0x120 / 4, + ANALOG_OSC_MISC_CFG = 0x124 / 4, + ANALOG_ANAMIX_PLL_MNIT_CTL = 0x128 / 4, + + ANALOG_DIGPROG = 0x800 / 4, + ANALOG_MAX, +}; + +#define TYPE_IMX8MP_ANALOG "imx8mp.analog" +OBJECT_DECLARE_SIMPLE_TYPE(IMX8MPAnalogState, IMX8MP_ANALOG) + +struct IMX8MPAnalogState { + SysBusDevice parent_obj; + + struct { + MemoryRegion container; + MemoryRegion analog; + } mmio; + + uint32_t analog[ANALOG_MAX]; +}; + +#endif /* IMX8MP_ANALOG_H */ diff --git a/include/hw/misc/imx8mp_ccm.h b/include/hw/misc/imx8mp_ccm.h new file mode 100644 index 000000000000..685c8582ff85 --- /dev/null +++ b/include/hw/misc/imx8mp_ccm.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Bernhard Beschow + * + * i.MX 8M Plus CCM IP block emulation code + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef IMX8MP_CCM_H +#define IMX8MP_CCM_H + +#include "hw/misc/imx_ccm.h" +#include "qom/object.h" + +enum IMX8MPCCMRegisters { + CCM_MAX = 0xc6fc / sizeof(uint32_t) + 1, +}; + +#define TYPE_IMX8MP_CCM "imx8mp.ccm" +OBJECT_DECLARE_SIMPLE_TYPE(IMX8MPCCMState, IMX8MP_CCM) + +struct IMX8MPCCMState { + IMXCCMState parent_obj; + + MemoryRegion iomem; + + uint32_t ccm[CCM_MAX]; +}; + +#endif /* IMX8MP_CCM_H */ From 487967bed65083db33561edc1255ced422bfbff5 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:56 +0100 Subject: [PATCH 0304/1179] hw/arm/fsl-imx8mp: Add SNVS SNVS contains an RTC which allows Linux to deal correctly with time. This is particularly useful when handling persistent storage which will be done in the next patch. Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-7-shentey@gmail.com Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/fsl-imx8mp.c | 10 ++++++++++ include/hw/arm/fsl-imx8mp.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index f0df346113fb..22541c54424b 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -12,6 +12,7 @@ The ``imx8mp-evk`` machine implements the following devices: * Up to 4 Cortex-A53 cores * Generic Interrupt Controller (GICv3) * 4 UARTs + * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree Boot options diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index bc15b25ca164..18c9c54ddc63 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -201,6 +201,8 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); + object_initialize_child(obj, "snvs", &s->snvs, TYPE_IMX7_SNVS); + for (i = 0; i < FSL_IMX8MP_NUM_UARTS; i++) { g_autofree char *name = g_strdup_printf("uart%d", i + 1); object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); @@ -344,6 +346,13 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, serial_table[i].irq)); } + /* SNVS */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, + fsl_imx8mp_memmap[FSL_IMX8MP_SNVS_HP].addr); + /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mp_memmap); i++) { switch (i) { @@ -352,6 +361,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_GIC_DIST: case FSL_IMX8MP_GIC_REDIST: case FSL_IMX8MP_RAM: + case FSL_IMX8MP_SNVS_HP: case FSL_IMX8MP_UART1 ... FSL_IMX8MP_UART4: /* device implemented and treated above */ break; diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index ce5188e7f235..26e24e99a15d 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -12,6 +12,7 @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" #include "qom/object.h" @@ -36,6 +37,7 @@ struct FslImx8mpState { GICv3State gic; IMX8MPCCMState ccm; IMX8MPAnalogState analog; + IMX7SNVSState snvs; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; }; From a81193c3e9a8220862120d8d4114191f3899f4b3 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:57 +0100 Subject: [PATCH 0305/1179] hw/arm/fsl-imx8mp: Add USDHC storage controllers The USDHC emulation allows for running real-world images such as those generated by Buildroot. Convert the board documentation accordingly instead of running a Linux kernel with ephemeral storage. Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-8-shentey@gmail.com [PMM: drop 'static const' from usdhc_table[] for GCC 7.5] Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 18 ++++++++++++------ hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mp.c | 28 ++++++++++++++++++++++++++++ hw/arm/imx8mp-evk.c | 18 ++++++++++++++++++ include/hw/arm/fsl-imx8mp.h | 7 +++++++ 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 22541c54424b..879c822356f2 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -12,6 +12,7 @@ The ``imx8mp-evk`` machine implements the following devices: * Up to 4 Cortex-A53 cores * Generic Interrupt Controller (GICv3) * 4 UARTs + * 3 USDHC Storage Controllers * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree @@ -26,18 +27,23 @@ Direct Linux Kernel Boot Probably the easiest way to get started with a whole Linux system on the machine is to generate an image with Buildroot. Version 2024.11.1 is tested at the time -of writing and involves two steps. First run the following commands in the +of writing and involves three steps. First run the following commands in the toplevel directory of the Buildroot source tree: .. code-block:: bash - $ echo "BR2_TARGET_ROOTFS_CPIO=y" >> configs/freescale_imx8mpevk_defconfig $ make freescale_imx8mpevk_defconfig $ make Once finished successfully there is an ``output/image`` subfolder. Navigate into -it and patch the device tree with the following commands which will remove the -``cpu-idle-states`` properties from CPU nodes: +it and resize the SD card image to a power of two: + +.. code-block:: bash + + $ qemu-img resize sdcard.img 256M + +Finally, the device tree needs to be patched with the following commands which +will remove the ``cpu-idle-states`` properties from CPU nodes: .. code-block:: bash @@ -52,5 +58,5 @@ Now that everything is prepared the machine can be started as follows: -display none -serial null -serial stdio \ -kernel Image \ -dtb imx8mp-evk-patched.dtb \ - -initrd rootfs.cpio \ - -append "root=/dev/ram" + -append "root=/dev/mmcblk2p2" \ + -drive file=sdcard.img,if=sd,bus=2,format=raw,id=mmcblk2 diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index f77c451ba39d..d2dda3213db1 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -599,6 +599,7 @@ config FSL_IMX8MP select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select SDHCI select UNIMP config FSL_IMX8MP_EVK diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 18c9c54ddc63..da9eaeb6ff0c 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -207,6 +207,11 @@ static void fsl_imx8mp_init(Object *obj) g_autofree char *name = g_strdup_printf("uart%d", i + 1); object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + + for (i = 0; i < FSL_IMX8MP_NUM_USDHCS; i++) { + g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); + object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); + } } static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) @@ -346,6 +351,28 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, serial_table[i].irq)); } + /* USDHCs */ + for (i = 0; i < FSL_IMX8MP_NUM_USDHCS; i++) { + struct { + hwaddr addr; + unsigned int irq; + } usdhc_table[FSL_IMX8MP_NUM_USDHCS] = { + { fsl_imx8mp_memmap[FSL_IMX8MP_USDHC1].addr, FSL_IMX8MP_USDHC1_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_USDHC2].addr, FSL_IMX8MP_USDHC2_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_USDHC3].addr, FSL_IMX8MP_USDHC3_IRQ }, + }; + + object_property_set_uint(OBJECT(&s->usdhc[i]), "vendor", + SDHCI_VENDOR_IMX, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->usdhc[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->usdhc[i]), 0, usdhc_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->usdhc[i]), 0, + qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); + } + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -363,6 +390,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_RAM: case FSL_IMX8MP_SNVS_HP: case FSL_IMX8MP_UART1 ... FSL_IMX8MP_UART4: + case FSL_IMX8MP_USDHC1 ... FSL_IMX8MP_USDHC3: /* device implemented and treated above */ break; diff --git a/hw/arm/imx8mp-evk.c b/hw/arm/imx8mp-evk.c index 2756d4c21c8c..27d9e9e8eed0 100644 --- a/hw/arm/imx8mp-evk.c +++ b/hw/arm/imx8mp-evk.c @@ -11,6 +11,7 @@ #include "hw/arm/boot.h" #include "hw/arm/fsl-imx8mp.h" #include "hw/boards.h" +#include "hw/qdev-properties.h" #include "system/qtest.h" #include "qemu/error-report.h" #include "qapi/error.h" @@ -40,6 +41,23 @@ static void imx8mp_evk_init(MachineState *machine) memory_region_add_subregion(get_system_memory(), FSL_IMX8MP_RAM_START, machine->ram); + for (int i = 0; i < FSL_IMX8MP_NUM_USDHCS; i++) { + BusState *bus; + DeviceState *carddev; + BlockBackend *blk; + DriveInfo *di = drive_get(IF_SD, i, 0); + + if (!di) { + continue; + } + + blk = blk_by_legacy_dinfo(di); + bus = qdev_get_child_bus(DEVICE(&s->usdhc[i]), "sd-bus"); + carddev = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(carddev, "drive", blk, &error_fatal); + qdev_realize_and_unref(carddev, bus, &error_fatal); + } + if (!qtest_enabled()) { arm_load_kernel(&s->cpu[0], machine, &boot_info); } diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 26e24e99a15d..349d55ca88d1 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -15,6 +15,7 @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/sd/sdhci.h" #include "qom/object.h" #include "qemu/units.h" @@ -28,6 +29,7 @@ enum FslImx8mpConfiguration { FSL_IMX8MP_NUM_CPUS = 4, FSL_IMX8MP_NUM_IRQS = 160, FSL_IMX8MP_NUM_UARTS = 4, + FSL_IMX8MP_NUM_USDHCS = 3, }; struct FslImx8mpState { @@ -39,6 +41,7 @@ struct FslImx8mpState { IMX8MPAnalogState analog; IMX7SNVSState snvs; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; + SDHCIState usdhc[FSL_IMX8MP_NUM_USDHCS]; }; enum FslImx8mpMemoryRegions { @@ -184,6 +187,10 @@ enum FslImx8mpMemoryRegions { }; enum FslImx8mpIrqs { + FSL_IMX8MP_USDHC1_IRQ = 22, + FSL_IMX8MP_USDHC2_IRQ = 23, + FSL_IMX8MP_USDHC3_IRQ = 24, + FSL_IMX8MP_UART1_IRQ = 26, FSL_IMX8MP_UART2_IRQ = 27, FSL_IMX8MP_UART3_IRQ = 28, From fd1deb5301f89eb86c0eecadb670beb98aa74ac5 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:58 +0100 Subject: [PATCH 0306/1179] hw/arm/fsl-imx8mp: Add PCIe support Linux checks for the PLLs in the PHY to be locked, so implement a model emulating that. Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-9-shentey@gmail.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- MAINTAINERS | 2 + docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/Kconfig | 3 + hw/arm/fsl-imx8mp.c | 30 +++++++++ hw/pci-host/Kconfig | 3 + hw/pci-host/fsl_imx8m_phy.c | 98 +++++++++++++++++++++++++++++ hw/pci-host/meson.build | 1 + include/hw/arm/fsl-imx8mp.h | 10 +++ include/hw/pci-host/fsl_imx8m_phy.h | 28 +++++++++ 9 files changed, 176 insertions(+) create mode 100644 hw/pci-host/fsl_imx8m_phy.c create mode 100644 include/hw/pci-host/fsl_imx8m_phy.h diff --git a/MAINTAINERS b/MAINTAINERS index 8ea7fb4c7a00..2e7fc6fa912a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -827,8 +827,10 @@ S: Maintained F: hw/arm/imx8mp-evk.c F: hw/arm/fsl-imx8mp.c F: hw/misc/imx8mp_*.c +F: hw/pci-host/fsl_imx8m_phy.c F: include/hw/arm/fsl-imx8mp.h F: include/hw/misc/imx8mp_*.h +F: include/hw/pci-host/fsl_imx8m_phy.h F: docs/system/arm/imx8mp-evk.rst MPS2 / MPS3 diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 879c822356f2..18a8fdd2785e 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -13,6 +13,7 @@ The ``imx8mp-evk`` machine implements the following devices: * Generic Interrupt Controller (GICv3) * 4 UARTs * 3 USDHC Storage Controllers + * 1 Designware PCI Express Controller * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index d2dda3213db1..be5a2c02b747 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -595,10 +595,13 @@ config FSL_IMX7 config FSL_IMX8MP bool + imply PCI_DEVICES select ARM_GIC select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select PCI_EXPRESS_DESIGNWARE + select PCI_EXPRESS_FSL_IMX8M_PHY select SDHCI select UNIMP diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index da9eaeb6ff0c..1ee681ac1d7a 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -212,6 +212,10 @@ static void fsl_imx8mp_init(Object *obj) g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); + object_initialize_child(obj, "pcie_phy", &s->pcie_phy, + TYPE_FSL_IMX8M_PCIE_PHY); } static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) @@ -380,6 +384,30 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, fsl_imx8mp_memmap[FSL_IMX8MP_SNVS_HP].addr); + /* PCIe */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie), 0, + fsl_imx8mp_memmap[FSL_IMX8MP_PCIE1].addr); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MP_PCI_INTA_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 1, + qdev_get_gpio_in(gicdev, FSL_IMX8MP_PCI_INTB_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 2, + qdev_get_gpio_in(gicdev, FSL_IMX8MP_PCI_INTC_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 3, + qdev_get_gpio_in(gicdev, FSL_IMX8MP_PCI_INTD_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 4, + qdev_get_gpio_in(gicdev, FSL_IMX8MP_PCI_MSI_IRQ)); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie_phy), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie_phy), 0, + fsl_imx8mp_memmap[FSL_IMX8MP_PCIE_PHY1].addr); + /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mp_memmap); i++) { switch (i) { @@ -387,6 +415,8 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_CCM: case FSL_IMX8MP_GIC_DIST: case FSL_IMX8MP_GIC_REDIST: + case FSL_IMX8MP_PCIE1: + case FSL_IMX8MP_PCIE_PHY1: case FSL_IMX8MP_RAM: case FSL_IMX8MP_SNVS_HP: case FSL_IMX8MP_UART1 ... FSL_IMX8MP_UART4: diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig index c91880b2370e..35c041524282 100644 --- a/hw/pci-host/Kconfig +++ b/hw/pci-host/Kconfig @@ -99,6 +99,9 @@ config ASTRO bool select PCI +config PCI_EXPRESS_FSL_IMX8M_PHY + bool + config GT64120 bool select PCI diff --git a/hw/pci-host/fsl_imx8m_phy.c b/hw/pci-host/fsl_imx8m_phy.c new file mode 100644 index 000000000000..aa304b102b32 --- /dev/null +++ b/hw/pci-host/fsl_imx8m_phy.c @@ -0,0 +1,98 @@ +/* + * i.MX8 PCIe PHY emulation + * + * Copyright (c) 2025 Bernhard Beschow + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/pci-host/fsl_imx8m_phy.h" +#include "hw/resettable.h" +#include "migration/vmstate.h" + +#define CMN_REG075 0x1d4 +#define ANA_PLL_LOCK_DONE BIT(1) +#define ANA_PLL_AFC_DONE BIT(0) + +static uint64_t fsl_imx8m_pcie_phy_read(void *opaque, hwaddr offset, + unsigned size) +{ + FslImx8mPciePhyState *s = opaque; + + if (offset == CMN_REG075) { + return s->data[offset] | ANA_PLL_LOCK_DONE | ANA_PLL_AFC_DONE; + } + + return s->data[offset]; +} + +static void fsl_imx8m_pcie_phy_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + FslImx8mPciePhyState *s = opaque; + + s->data[offset] = value; +} + +static const MemoryRegionOps fsl_imx8m_pcie_phy_ops = { + .read = fsl_imx8m_pcie_phy_read, + .write = fsl_imx8m_pcie_phy_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void fsl_imx8m_pcie_phy_realize(DeviceState *dev, Error **errp) +{ + FslImx8mPciePhyState *s = FSL_IMX8M_PCIE_PHY(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &fsl_imx8m_pcie_phy_ops, s, + TYPE_FSL_IMX8M_PCIE_PHY, ARRAY_SIZE(s->data)); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void fsl_imx8m_pcie_phy_reset_hold(Object *obj, ResetType type) +{ + FslImx8mPciePhyState *s = FSL_IMX8M_PCIE_PHY(obj); + + memset(s->data, 0, sizeof(s->data)); +} + +static const VMStateDescription fsl_imx8m_pcie_phy_vmstate = { + .name = "fsl-imx8m-pcie-phy", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(data, FslImx8mPciePhyState, + FSL_IMX8M_PCIE_PHY_DATA_SIZE), + VMSTATE_END_OF_LIST() + } +}; + +static void fsl_imx8m_pcie_phy_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = fsl_imx8m_pcie_phy_realize; + dc->vmsd = &fsl_imx8m_pcie_phy_vmstate; + rc->phases.hold = fsl_imx8m_pcie_phy_reset_hold; +} + +static const TypeInfo fsl_imx8m_pcie_phy_types[] = { + { + .name = TYPE_FSL_IMX8M_PCIE_PHY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(FslImx8mPciePhyState), + .class_init = fsl_imx8m_pcie_phy_class_init, + } +}; + +DEFINE_TYPES(fsl_imx8m_pcie_phy_types) diff --git a/hw/pci-host/meson.build b/hw/pci-host/meson.build index 3001e93a4371..937a0f72acf9 100644 --- a/hw/pci-host/meson.build +++ b/hw/pci-host/meson.build @@ -28,6 +28,7 @@ pci_ss.add(when: 'CONFIG_ARTICIA', if_true: files('articia.c')) pci_ss.add(when: 'CONFIG_MV64361', if_true: files('mv64361.c')) # ARM devices +pci_ss.add(when: 'CONFIG_PCI_EXPRESS_FSL_IMX8M_PHY', if_true: files('fsl_imx8m_phy.c')) pci_ss.add(when: 'CONFIG_VERSATILE_PCI', if_true: files('versatile.c')) # HPPA devices diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 349d55ca88d1..4c70c887a89a 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -15,6 +15,8 @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/pci-host/designware.h" +#include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "qom/object.h" #include "qemu/units.h" @@ -42,6 +44,8 @@ struct FslImx8mpState { IMX7SNVSState snvs; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; SDHCIState usdhc[FSL_IMX8MP_NUM_USDHCS]; + DesignwarePCIEHost pcie; + FslImx8mPciePhyState pcie_phy; }; enum FslImx8mpMemoryRegions { @@ -197,6 +201,12 @@ enum FslImx8mpIrqs { FSL_IMX8MP_UART4_IRQ = 29, FSL_IMX8MP_UART5_IRQ = 30, FSL_IMX8MP_UART6_IRQ = 16, + + FSL_IMX8MP_PCI_INTA_IRQ = 126, + FSL_IMX8MP_PCI_INTB_IRQ = 125, + FSL_IMX8MP_PCI_INTC_IRQ = 124, + FSL_IMX8MP_PCI_INTD_IRQ = 123, + FSL_IMX8MP_PCI_MSI_IRQ = 140, }; #endif /* FSL_IMX8MP_H */ diff --git a/include/hw/pci-host/fsl_imx8m_phy.h b/include/hw/pci-host/fsl_imx8m_phy.h new file mode 100644 index 000000000000..4f4875b37d76 --- /dev/null +++ b/include/hw/pci-host/fsl_imx8m_phy.h @@ -0,0 +1,28 @@ +/* + * i.MX8 PCIe PHY emulation + * + * Copyright (c) 2025 Bernhard Beschow + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_PCIHOST_FSLIMX8MPCIEPHY_H +#define HW_PCIHOST_FSLIMX8MPCIEPHY_H + +#include "hw/sysbus.h" +#include "qom/object.h" +#include "exec/memory.h" + +#define TYPE_FSL_IMX8M_PCIE_PHY "fsl-imx8m-pcie-phy" +OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mPciePhyState, FSL_IMX8M_PCIE_PHY) + +#define FSL_IMX8M_PCIE_PHY_DATA_SIZE 0x800 + +struct FslImx8mPciePhyState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint8_t data[FSL_IMX8M_PCIE_PHY_DATA_SIZE]; +}; + +#endif From a17c1d932ec6ae1a3364eaf34c0660f01f806267 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:46:59 +0100 Subject: [PATCH 0307/1179] hw/arm/fsl-imx8mp: Add GPIO controllers Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-10-shentey@gmail.com [PMM: drop static const from gpio_table for GCC 7.5] Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/fsl-imx8mp.c | 55 ++++++++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mp.h | 14 +++++++++ 3 files changed, 70 insertions(+) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 18a8fdd2785e..37d3630d09cb 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -14,6 +14,7 @@ The ``imx8mp-evk`` machine implements the following devices: * 4 UARTs * 3 USDHC Storage Controllers * 1 Designware PCI Express Controller + * 5 GPIO Controllers * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 1ee681ac1d7a..541e4ab5b632 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -208,6 +208,11 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MP_NUM_GPIOS; i++) { + g_autofree char *name = g_strdup_printf("gpio%d", i + 1); + object_initialize_child(obj, name, &s->gpio[i], TYPE_IMX_GPIO); + } + for (i = 0; i < FSL_IMX8MP_NUM_USDHCS; i++) { g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); @@ -355,6 +360,55 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, serial_table[i].irq)); } + /* GPIOs */ + for (i = 0; i < FSL_IMX8MP_NUM_GPIOS; i++) { + struct { + hwaddr addr; + unsigned int irq_low; + unsigned int irq_high; + } gpio_table[FSL_IMX8MP_NUM_GPIOS] = { + { + fsl_imx8mp_memmap[FSL_IMX8MP_GPIO1].addr, + FSL_IMX8MP_GPIO1_LOW_IRQ, + FSL_IMX8MP_GPIO1_HIGH_IRQ + }, + { + fsl_imx8mp_memmap[FSL_IMX8MP_GPIO2].addr, + FSL_IMX8MP_GPIO2_LOW_IRQ, + FSL_IMX8MP_GPIO2_HIGH_IRQ + }, + { + fsl_imx8mp_memmap[FSL_IMX8MP_GPIO3].addr, + FSL_IMX8MP_GPIO3_LOW_IRQ, + FSL_IMX8MP_GPIO3_HIGH_IRQ + }, + { + fsl_imx8mp_memmap[FSL_IMX8MP_GPIO4].addr, + FSL_IMX8MP_GPIO4_LOW_IRQ, + FSL_IMX8MP_GPIO4_HIGH_IRQ + }, + { + fsl_imx8mp_memmap[FSL_IMX8MP_GPIO5].addr, + FSL_IMX8MP_GPIO5_LOW_IRQ, + FSL_IMX8MP_GPIO5_HIGH_IRQ + }, + }; + + object_property_set_bool(OBJECT(&s->gpio[i]), "has-edge-sel", true, + &error_abort); + object_property_set_bool(OBJECT(&s->gpio[i]), "has-upper-pin-irq", + true, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0, gpio_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0, + qdev_get_gpio_in(gicdev, gpio_table[i].irq_low)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 1, + qdev_get_gpio_in(gicdev, gpio_table[i].irq_high)); + } + /* USDHCs */ for (i = 0; i < FSL_IMX8MP_NUM_USDHCS; i++) { struct { @@ -415,6 +469,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_CCM: case FSL_IMX8MP_GIC_DIST: case FSL_IMX8MP_GIC_REDIST: + case FSL_IMX8MP_GPIO1 ... FSL_IMX8MP_GPIO5: case FSL_IMX8MP_PCIE1: case FSL_IMX8MP_PCIE_PHY1: case FSL_IMX8MP_RAM: diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 4c70c887a89a..18ea52d0839f 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -11,6 +11,7 @@ #include "cpu.h" #include "hw/char/imx_serial.h" +#include "hw/gpio/imx_gpio.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" @@ -29,6 +30,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mpState, FSL_IMX8MP) enum FslImx8mpConfiguration { FSL_IMX8MP_NUM_CPUS = 4, + FSL_IMX8MP_NUM_GPIOS = 5, FSL_IMX8MP_NUM_IRQS = 160, FSL_IMX8MP_NUM_UARTS = 4, FSL_IMX8MP_NUM_USDHCS = 3, @@ -39,6 +41,7 @@ struct FslImx8mpState { ARMCPU cpu[FSL_IMX8MP_NUM_CPUS]; GICv3State gic; + IMXGPIOState gpio[FSL_IMX8MP_NUM_GPIOS]; IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; @@ -202,6 +205,17 @@ enum FslImx8mpIrqs { FSL_IMX8MP_UART5_IRQ = 30, FSL_IMX8MP_UART6_IRQ = 16, + FSL_IMX8MP_GPIO1_LOW_IRQ = 64, + FSL_IMX8MP_GPIO1_HIGH_IRQ = 65, + FSL_IMX8MP_GPIO2_LOW_IRQ = 66, + FSL_IMX8MP_GPIO2_HIGH_IRQ = 67, + FSL_IMX8MP_GPIO3_LOW_IRQ = 68, + FSL_IMX8MP_GPIO3_HIGH_IRQ = 69, + FSL_IMX8MP_GPIO4_LOW_IRQ = 70, + FSL_IMX8MP_GPIO4_HIGH_IRQ = 71, + FSL_IMX8MP_GPIO5_LOW_IRQ = 72, + FSL_IMX8MP_GPIO5_HIGH_IRQ = 73, + FSL_IMX8MP_PCI_INTA_IRQ = 126, FSL_IMX8MP_PCI_INTB_IRQ = 125, FSL_IMX8MP_PCI_INTC_IRQ = 124, From 764f18afb2b749a9dcfd37bac5709e7a7bcd2589 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:00 +0100 Subject: [PATCH 0308/1179] hw/arm/fsl-imx8mp: Add I2C controllers Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-11-shentey@gmail.com [PMM: drop static const from i2c_table for GCC 7.5] Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/Kconfig | 2 ++ hw/arm/fsl-imx8mp.c | 29 +++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mp.h | 11 +++++++++++ 4 files changed, 43 insertions(+) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 37d3630d09cb..ef0d997250bb 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -15,6 +15,7 @@ The ``imx8mp-evk`` machine implements the following devices: * 3 USDHC Storage Controllers * 1 Designware PCI Express Controller * 5 GPIO Controllers + * 6 I2C Controllers * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index be5a2c02b747..28ae409c859f 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -595,11 +595,13 @@ config FSL_IMX7 config FSL_IMX8MP bool + imply I2C_DEVICES imply PCI_DEVICES select ARM_GIC select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select IMX_I2C select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY select SDHCI diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 541e4ab5b632..750dbf9eab98 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -208,6 +208,11 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MP_NUM_I2CS; i++) { + g_autofree char *name = g_strdup_printf("i2c%d", i + 1); + object_initialize_child(obj, name, &s->i2c[i], TYPE_IMX_I2C); + } + for (i = 0; i < FSL_IMX8MP_NUM_GPIOS; i++) { g_autofree char *name = g_strdup_printf("gpio%d", i + 1); object_initialize_child(obj, name, &s->gpio[i], TYPE_IMX_GPIO); @@ -360,6 +365,29 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, serial_table[i].irq)); } + /* I2Cs */ + for (i = 0; i < FSL_IMX8MP_NUM_I2CS; i++) { + struct { + hwaddr addr; + unsigned int irq; + } i2c_table[FSL_IMX8MP_NUM_I2CS] = { + { fsl_imx8mp_memmap[FSL_IMX8MP_I2C1].addr, FSL_IMX8MP_I2C1_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_I2C2].addr, FSL_IMX8MP_I2C2_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_I2C3].addr, FSL_IMX8MP_I2C3_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_I2C4].addr, FSL_IMX8MP_I2C4_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_I2C5].addr, FSL_IMX8MP_I2C5_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_I2C6].addr, FSL_IMX8MP_I2C6_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->i2c[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c[i]), 0, i2c_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0, + qdev_get_gpio_in(gicdev, i2c_table[i].irq)); + } + /* GPIOs */ for (i = 0; i < FSL_IMX8MP_NUM_GPIOS; i++) { struct { @@ -470,6 +498,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_GIC_DIST: case FSL_IMX8MP_GIC_REDIST: case FSL_IMX8MP_GPIO1 ... FSL_IMX8MP_GPIO5: + case FSL_IMX8MP_I2C1 ... FSL_IMX8MP_I2C6: case FSL_IMX8MP_PCIE1: case FSL_IMX8MP_PCIE_PHY1: case FSL_IMX8MP_RAM: diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 18ea52d0839f..259005662726 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -12,6 +12,7 @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/gpio/imx_gpio.h" +#include "hw/i2c/imx_i2c.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" @@ -31,6 +32,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mpState, FSL_IMX8MP) enum FslImx8mpConfiguration { FSL_IMX8MP_NUM_CPUS = 4, FSL_IMX8MP_NUM_GPIOS = 5, + FSL_IMX8MP_NUM_I2CS = 6, FSL_IMX8MP_NUM_IRQS = 160, FSL_IMX8MP_NUM_UARTS = 4, FSL_IMX8MP_NUM_USDHCS = 3, @@ -45,6 +47,7 @@ struct FslImx8mpState { IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; + IMXI2CState i2c[FSL_IMX8MP_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; SDHCIState usdhc[FSL_IMX8MP_NUM_USDHCS]; DesignwarePCIEHost pcie; @@ -205,6 +208,11 @@ enum FslImx8mpIrqs { FSL_IMX8MP_UART5_IRQ = 30, FSL_IMX8MP_UART6_IRQ = 16, + FSL_IMX8MP_I2C1_IRQ = 35, + FSL_IMX8MP_I2C2_IRQ = 36, + FSL_IMX8MP_I2C3_IRQ = 37, + FSL_IMX8MP_I2C4_IRQ = 38, + FSL_IMX8MP_GPIO1_LOW_IRQ = 64, FSL_IMX8MP_GPIO1_HIGH_IRQ = 65, FSL_IMX8MP_GPIO2_LOW_IRQ = 66, @@ -216,6 +224,9 @@ enum FslImx8mpIrqs { FSL_IMX8MP_GPIO5_LOW_IRQ = 72, FSL_IMX8MP_GPIO5_HIGH_IRQ = 73, + FSL_IMX8MP_I2C5_IRQ = 76, + FSL_IMX8MP_I2C6_IRQ = 77, + FSL_IMX8MP_PCI_INTA_IRQ = 126, FSL_IMX8MP_PCI_INTB_IRQ = 125, FSL_IMX8MP_PCI_INTC_IRQ = 124, From 06908a84f036d7cefb834f8d67cf8b80a1791838 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:01 +0100 Subject: [PATCH 0309/1179] hw/arm/fsl-imx8mp: Add SPI controllers Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-12-shentey@gmail.com [PMM: drop static const from spi_table for GCC 7.5] Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/fsl-imx8mp.c | 26 ++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mp.h | 8 ++++++++ 3 files changed, 35 insertions(+) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index ef0d997250bb..66e586510791 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -16,6 +16,7 @@ The ``imx8mp-evk`` machine implements the following devices: * 1 Designware PCI Express Controller * 5 GPIO Controllers * 6 I2C Controllers + * 3 SPI Controllers * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 750dbf9eab98..63f07eca8a4d 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -223,6 +223,11 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + for (i = 0; i < FSL_IMX8MP_NUM_ECSPIS; i++) { + g_autofree char *name = g_strdup_printf("spi%d", i + 1); + object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); + } + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -459,6 +464,26 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); } + /* ECSPIs */ + for (i = 0; i < FSL_IMX8MP_NUM_ECSPIS; i++) { + struct { + hwaddr addr; + unsigned int irq; + } spi_table[FSL_IMX8MP_NUM_ECSPIS] = { + { fsl_imx8mp_memmap[FSL_IMX8MP_ECSPI1].addr, FSL_IMX8MP_ECSPI1_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_ECSPI2].addr, FSL_IMX8MP_ECSPI2_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_ECSPI3].addr, FSL_IMX8MP_ECSPI3_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi[i]), 0, spi_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi[i]), 0, + qdev_get_gpio_in(gicdev, spi_table[i].irq)); + } + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -498,6 +523,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_GIC_DIST: case FSL_IMX8MP_GIC_REDIST: case FSL_IMX8MP_GPIO1 ... FSL_IMX8MP_GPIO5: + case FSL_IMX8MP_ECSPI1 ... FSL_IMX8MP_ECSPI3: case FSL_IMX8MP_I2C1 ... FSL_IMX8MP_I2C6: case FSL_IMX8MP_PCIE1: case FSL_IMX8MP_PCIE_PHY1: diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 259005662726..296a87eb5045 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -20,6 +20,7 @@ #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" +#include "hw/ssi/imx_spi.h" #include "qom/object.h" #include "qemu/units.h" @@ -31,6 +32,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mpState, FSL_IMX8MP) enum FslImx8mpConfiguration { FSL_IMX8MP_NUM_CPUS = 4, + FSL_IMX8MP_NUM_ECSPIS = 3, FSL_IMX8MP_NUM_GPIOS = 5, FSL_IMX8MP_NUM_I2CS = 6, FSL_IMX8MP_NUM_IRQS = 160, @@ -47,6 +49,7 @@ struct FslImx8mpState { IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; + IMXSPIState spi[FSL_IMX8MP_NUM_ECSPIS]; IMXI2CState i2c[FSL_IMX8MP_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; SDHCIState usdhc[FSL_IMX8MP_NUM_USDHCS]; @@ -208,6 +211,11 @@ enum FslImx8mpIrqs { FSL_IMX8MP_UART5_IRQ = 30, FSL_IMX8MP_UART6_IRQ = 16, + FSL_IMX8MP_ECSPI1_IRQ = 31, + FSL_IMX8MP_ECSPI2_IRQ = 32, + FSL_IMX8MP_ECSPI3_IRQ = 33, + FSL_IMX8MP_ECSPI4_IRQ = 34, + FSL_IMX8MP_I2C1_IRQ = 35, FSL_IMX8MP_I2C2_IRQ = 36, FSL_IMX8MP_I2C3_IRQ = 37, From 1ac21eb8fbb0297716a6c525e91196a247302b2b Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:02 +0100 Subject: [PATCH 0310/1179] hw/arm/fsl-imx8mp: Add watchdog support Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-13-shentey@gmail.com [PMM: drop static const from wdog_table for GCC 7.5] Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mp.c | 28 ++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mp.h | 7 +++++++ 4 files changed, 37 insertions(+) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 66e586510791..904de9aa7df3 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -17,6 +17,7 @@ The ``imx8mp-evk`` machine implements the following devices: * 5 GPIO Controllers * 6 I2C Controllers * 3 SPI Controllers + * 3 Watchdogs * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 28ae409c859f..98ac93a23fca 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -606,6 +606,7 @@ config FSL_IMX8MP select PCI_EXPRESS_FSL_IMX8M_PHY select SDHCI select UNIMP + select WDT_IMX2 config FSL_IMX8MP_EVK bool diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 63f07eca8a4d..762f2a52d8dd 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -228,6 +228,11 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); } + for (i = 0; i < FSL_IMX8MP_NUM_WDTS; i++) { + g_autofree char *name = g_strdup_printf("wdt%d", i); + object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT); + } + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -491,6 +496,28 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, fsl_imx8mp_memmap[FSL_IMX8MP_SNVS_HP].addr); + /* Watchdogs */ + for (i = 0; i < FSL_IMX8MP_NUM_WDTS; i++) { + struct { + hwaddr addr; + unsigned int irq; + } wdog_table[FSL_IMX8MP_NUM_WDTS] = { + { fsl_imx8mp_memmap[FSL_IMX8MP_WDOG1].addr, FSL_IMX8MP_WDOG1_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_WDOG2].addr, FSL_IMX8MP_WDOG2_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_WDOG3].addr, FSL_IMX8MP_WDOG3_IRQ }, + }; + + object_property_set_bool(OBJECT(&s->wdt[i]), "pretimeout-support", + true, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[i]), 0, wdog_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[i]), 0, + qdev_get_gpio_in(gicdev, wdog_table[i].irq)); + } + /* PCIe */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie), errp)) { return; @@ -531,6 +558,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_SNVS_HP: case FSL_IMX8MP_UART1 ... FSL_IMX8MP_UART4: case FSL_IMX8MP_USDHC1 ... FSL_IMX8MP_USDHC3: + case FSL_IMX8MP_WDOG1 ... FSL_IMX8MP_WDOG3: /* device implemented and treated above */ break; diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 296a87eb5045..dfbdc6ac7f2e 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -21,6 +21,7 @@ #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" +#include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -38,6 +39,7 @@ enum FslImx8mpConfiguration { FSL_IMX8MP_NUM_IRQS = 160, FSL_IMX8MP_NUM_UARTS = 4, FSL_IMX8MP_NUM_USDHCS = 3, + FSL_IMX8MP_NUM_WDTS = 3, }; struct FslImx8mpState { @@ -53,6 +55,7 @@ struct FslImx8mpState { IMXI2CState i2c[FSL_IMX8MP_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; SDHCIState usdhc[FSL_IMX8MP_NUM_USDHCS]; + IMX2WdtState wdt[FSL_IMX8MP_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; }; @@ -235,6 +238,10 @@ enum FslImx8mpIrqs { FSL_IMX8MP_I2C5_IRQ = 76, FSL_IMX8MP_I2C6_IRQ = 77, + FSL_IMX8MP_WDOG1_IRQ = 78, + FSL_IMX8MP_WDOG2_IRQ = 79, + FSL_IMX8MP_WDOG3_IRQ = 10, + FSL_IMX8MP_PCI_INTA_IRQ = 126, FSL_IMX8MP_PCI_INTB_IRQ = 125, FSL_IMX8MP_PCI_INTC_IRQ = 124, From f8b26121762c17af9869b0ec7ccbda6df4ea37f8 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:03 +0100 Subject: [PATCH 0311/1179] hw/arm/fsl-imx8mp: Implement general purpose timers Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-14-shentey@gmail.com [PMM: drop static const from gpt_attrs for GCC 7.5] Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mp.c | 53 ++++++++++++++++++++++++++++++++++ hw/timer/imx_gpt.c | 25 ++++++++++++++++ include/hw/arm/fsl-imx8mp.h | 11 +++++++ include/hw/timer/imx_gpt.h | 1 + 6 files changed, 92 insertions(+) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 904de9aa7df3..4b195c917f65 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -18,6 +18,7 @@ The ``imx8mp-evk`` machine implements the following devices: * 6 I2C Controllers * 3 SPI Controllers * 3 Watchdogs + * 6 General Purpose Timers * Secure Non-Volatile Storage (SNVS) including an RTC * Clock Tree diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 98ac93a23fca..4e83895b9153 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -602,6 +602,7 @@ config FSL_IMX8MP select FSL_IMX8MP_CCM select IMX select IMX_I2C + select OR_IRQ select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY select SDHCI diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 762f2a52d8dd..185c32ee587e 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -208,6 +208,13 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MP_NUM_GPTS; i++) { + g_autofree char *name = g_strdup_printf("gpt%d", i + 1); + object_initialize_child(obj, name, &s->gpt[i], TYPE_IMX8MP_GPT); + } + object_initialize_child(obj, "gpt5-gpt6-irq", &s->gpt5_gpt6_irq, + TYPE_OR_IRQ); + for (i = 0; i < FSL_IMX8MP_NUM_I2CS; i++) { g_autofree char *name = g_strdup_printf("i2c%d", i + 1); object_initialize_child(obj, name, &s->i2c[i], TYPE_IMX_I2C); @@ -375,6 +382,52 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, serial_table[i].irq)); } + /* GPTs */ + object_property_set_int(OBJECT(&s->gpt5_gpt6_irq), "num-lines", 2, + &error_abort); + if (!qdev_realize(DEVICE(&s->gpt5_gpt6_irq), NULL, errp)) { + return; + } + + qdev_connect_gpio_out(DEVICE(&s->gpt5_gpt6_irq), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MP_GPT5_GPT6_IRQ)); + + for (i = 0; i < FSL_IMX8MP_NUM_GPTS; i++) { + hwaddr gpt_addrs[FSL_IMX8MP_NUM_GPTS] = { + fsl_imx8mp_memmap[FSL_IMX8MP_GPT1].addr, + fsl_imx8mp_memmap[FSL_IMX8MP_GPT2].addr, + fsl_imx8mp_memmap[FSL_IMX8MP_GPT3].addr, + fsl_imx8mp_memmap[FSL_IMX8MP_GPT4].addr, + fsl_imx8mp_memmap[FSL_IMX8MP_GPT5].addr, + fsl_imx8mp_memmap[FSL_IMX8MP_GPT6].addr, + }; + + s->gpt[i].ccm = IMX_CCM(&s->ccm); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpt[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpt[i]), 0, gpt_addrs[i]); + + if (i < FSL_IMX8MP_NUM_GPTS - 2) { + static const unsigned int gpt_irqs[FSL_IMX8MP_NUM_GPTS - 2] = { + FSL_IMX8MP_GPT1_IRQ, + FSL_IMX8MP_GPT2_IRQ, + FSL_IMX8MP_GPT3_IRQ, + FSL_IMX8MP_GPT4_IRQ, + }; + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, + qdev_get_gpio_in(gicdev, gpt_irqs[i])); + } else { + int irq = i - FSL_IMX8MP_NUM_GPTS + 2; + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, + qdev_get_gpio_in(DEVICE(&s->gpt5_gpt6_irq), irq)); + } + } + /* I2Cs */ for (i = 0; i < FSL_IMX8MP_NUM_I2CS; i++) { struct { diff --git a/hw/timer/imx_gpt.c b/hw/timer/imx_gpt.c index 11eca9fa4df3..200a89225bba 100644 --- a/hw/timer/imx_gpt.c +++ b/hw/timer/imx_gpt.c @@ -126,6 +126,17 @@ static const IMXClk imx7_gpt_clocks[] = { CLK_NONE, /* 111 not defined */ }; +static const IMXClk imx8mp_gpt_clocks[] = { + CLK_NONE, /* 000 No clock source */ + CLK_IPG, /* 001 ipg_clk, 532MHz */ + CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ + CLK_EXT, /* 011 External clock */ + CLK_32k, /* 100 ipg_clk_32k */ + CLK_HIGH, /* 101 ipg_clk_16M */ + CLK_NONE, /* 110 not defined */ + CLK_NONE, /* 111 not defined */ +}; + /* Must be called from within ptimer_transaction_begin/commit block */ static void imx_gpt_set_freq(IMXGPTState *s) { @@ -552,6 +563,13 @@ static void imx7_gpt_init(Object *obj) s->clocks = imx7_gpt_clocks; } +static void imx8mp_gpt_init(Object *obj) +{ + IMXGPTState *s = IMX_GPT(obj); + + s->clocks = imx8mp_gpt_clocks; +} + static const TypeInfo imx25_gpt_info = { .name = TYPE_IMX25_GPT, .parent = TYPE_SYS_BUS_DEVICE, @@ -584,6 +602,12 @@ static const TypeInfo imx7_gpt_info = { .instance_init = imx7_gpt_init, }; +static const TypeInfo imx8mp_gpt_info = { + .name = TYPE_IMX8MP_GPT, + .parent = TYPE_IMX25_GPT, + .instance_init = imx8mp_gpt_init, +}; + static void imx_gpt_register_types(void) { type_register_static(&imx25_gpt_info); @@ -591,6 +615,7 @@ static void imx_gpt_register_types(void) type_register_static(&imx6_gpt_info); type_register_static(&imx6ul_gpt_info); type_register_static(&imx7_gpt_info); + type_register_static(&imx8mp_gpt_info); } type_init(imx_gpt_register_types) diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index dfbdc6ac7f2e..975887751bc4 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -17,10 +17,12 @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/or-irq.h" #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" +#include "hw/timer/imx_gpt.h" #include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -35,6 +37,7 @@ enum FslImx8mpConfiguration { FSL_IMX8MP_NUM_CPUS = 4, FSL_IMX8MP_NUM_ECSPIS = 3, FSL_IMX8MP_NUM_GPIOS = 5, + FSL_IMX8MP_NUM_GPTS = 6, FSL_IMX8MP_NUM_I2CS = 6, FSL_IMX8MP_NUM_IRQS = 160, FSL_IMX8MP_NUM_UARTS = 4, @@ -47,6 +50,7 @@ struct FslImx8mpState { ARMCPU cpu[FSL_IMX8MP_NUM_CPUS]; GICv3State gic; + IMXGPTState gpt[FSL_IMX8MP_NUM_GPTS]; IMXGPIOState gpio[FSL_IMX8MP_NUM_GPIOS]; IMX8MPCCMState ccm; IMX8MPAnalogState analog; @@ -58,6 +62,7 @@ struct FslImx8mpState { IMX2WdtState wdt[FSL_IMX8MP_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; + OrIRQState gpt5_gpt6_irq; }; enum FslImx8mpMemoryRegions { @@ -224,6 +229,12 @@ enum FslImx8mpIrqs { FSL_IMX8MP_I2C3_IRQ = 37, FSL_IMX8MP_I2C4_IRQ = 38, + FSL_IMX8MP_GPT1_IRQ = 55, + FSL_IMX8MP_GPT2_IRQ = 54, + FSL_IMX8MP_GPT3_IRQ = 53, + FSL_IMX8MP_GPT4_IRQ = 52, + FSL_IMX8MP_GPT5_GPT6_IRQ = 51, + FSL_IMX8MP_GPIO1_LOW_IRQ = 64, FSL_IMX8MP_GPIO1_HIGH_IRQ = 65, FSL_IMX8MP_GPIO2_LOW_IRQ = 66, diff --git a/include/hw/timer/imx_gpt.h b/include/hw/timer/imx_gpt.h index 5a1230da35e5..5488f7e4df57 100644 --- a/include/hw/timer/imx_gpt.h +++ b/include/hw/timer/imx_gpt.h @@ -80,6 +80,7 @@ #define TYPE_IMX6_GPT "imx6.gpt" #define TYPE_IMX6UL_GPT "imx6ul.gpt" #define TYPE_IMX7_GPT "imx7.gpt" +#define TYPE_IMX8MP_GPT "imx8mp.gpt" #define TYPE_IMX_GPT TYPE_IMX25_GPT From 0c105b261551a9b9fed086a7b9ecd0b6d6063bc4 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:04 +0100 Subject: [PATCH 0312/1179] hw/arm/fsl-imx8mp: Add Ethernet controller The i.MX 8M Plus SoC actually has two ethernet controllers, the usual ENET one and a Designware one. There is no device model for the latter, so only add the ENET one. Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-15-shentey@gmail.com Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mp.c | 24 ++++++++++++++++++++++++ hw/arm/imx8mp-evk.c | 1 + include/hw/arm/fsl-imx8mp.h | 8 ++++++++ 5 files changed, 35 insertions(+) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 4b195c917f65..917c1d517693 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -14,6 +14,7 @@ The ``imx8mp-evk`` machine implements the following devices: * 4 UARTs * 3 USDHC Storage Controllers * 1 Designware PCI Express Controller + * 1 Ethernet Controller * 5 GPIO Controllers * 6 I2C Controllers * 3 SPI Controllers diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 4e83895b9153..4d642db9705d 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -601,6 +601,7 @@ config FSL_IMX8MP select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select IMX_FEC select IMX_I2C select OR_IRQ select PCI_EXPRESS_DESIGNWARE diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 185c32ee587e..2dd3c97a02e9 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -240,6 +240,8 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT); } + object_initialize_child(obj, "eth0", &s->enet, TYPE_IMX_ENET); + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -542,6 +544,21 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, spi_table[i].irq)); } + /* ENET1 */ + object_property_set_uint(OBJECT(&s->enet), "phy-num", s->phy_num, + &error_abort); + object_property_set_uint(OBJECT(&s->enet), "tx-ring-num", 3, &error_abort); + qemu_configure_nic_device(DEVICE(&s->enet), true, NULL); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->enet), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->enet), 0, + fsl_imx8mp_memmap[FSL_IMX8MP_ENET1].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->enet), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MP_ENET1_MAC_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->enet), 1, + qdev_get_gpio_in(gicdev, FSL_IMX6_ENET1_MAC_1588_IRQ)); + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -604,6 +621,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_GIC_REDIST: case FSL_IMX8MP_GPIO1 ... FSL_IMX8MP_GPIO5: case FSL_IMX8MP_ECSPI1 ... FSL_IMX8MP_ECSPI3: + case FSL_IMX8MP_ENET1: case FSL_IMX8MP_I2C1 ... FSL_IMX8MP_I2C6: case FSL_IMX8MP_PCIE1: case FSL_IMX8MP_PCIE_PHY1: @@ -624,10 +642,16 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) } } +static const Property fsl_imx8mp_properties[] = { + DEFINE_PROP_UINT32("fec1-phy-num", FslImx8mpState, phy_num, 0), + DEFINE_PROP_BOOL("fec1-phy-connected", FslImx8mpState, phy_connected, true), +}; + static void fsl_imx8mp_class_init(ObjectClass *oc, void *data) { DeviceClass *dc = DEVICE_CLASS(oc); + device_class_set_props(dc, fsl_imx8mp_properties); dc->realize = fsl_imx8mp_realize; dc->desc = "i.MX 8M Plus SoC"; diff --git a/hw/arm/imx8mp-evk.c b/hw/arm/imx8mp-evk.c index 27d9e9e8eed0..e1a7892fd7ce 100644 --- a/hw/arm/imx8mp-evk.c +++ b/hw/arm/imx8mp-evk.c @@ -36,6 +36,7 @@ static void imx8mp_evk_init(MachineState *machine) s = FSL_IMX8MP(object_new(TYPE_FSL_IMX8MP)); object_property_add_child(OBJECT(machine), "soc", OBJECT(s)); + object_property_set_uint(OBJECT(s), "fec1-phy-num", 1, &error_fatal); qdev_realize(DEVICE(s), NULL, &error_fatal); memory_region_add_subregion(get_system_memory(), FSL_IMX8MP_RAM_START, diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 975887751bc4..e292c31a3d08 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -17,6 +17,7 @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/net/imx_fec.h" #include "hw/or-irq.h" #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" @@ -58,11 +59,15 @@ struct FslImx8mpState { IMXSPIState spi[FSL_IMX8MP_NUM_ECSPIS]; IMXI2CState i2c[FSL_IMX8MP_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MP_NUM_UARTS]; + IMXFECState enet; SDHCIState usdhc[FSL_IMX8MP_NUM_USDHCS]; IMX2WdtState wdt[FSL_IMX8MP_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; OrIRQState gpt5_gpt6_irq; + + uint32_t phy_num; + bool phy_connected; }; enum FslImx8mpMemoryRegions { @@ -253,6 +258,9 @@ enum FslImx8mpIrqs { FSL_IMX8MP_WDOG2_IRQ = 79, FSL_IMX8MP_WDOG3_IRQ = 10, + FSL_IMX8MP_ENET1_MAC_IRQ = 118, + FSL_IMX6_ENET1_MAC_1588_IRQ = 121, + FSL_IMX8MP_PCI_INTA_IRQ = 126, FSL_IMX8MP_PCI_INTB_IRQ = 125, FSL_IMX8MP_PCI_INTC_IRQ = 124, From 4226c39fea1490060163339ae45500bda1b1be05 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:05 +0100 Subject: [PATCH 0313/1179] hw/arm/fsl-imx8mp: Add USB support Split the USB MMIO regions to better keep track of the implemented vs. unimplemented regions. Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-16-shentey@gmail.com [PMM: drop "static const" from usb_table for GCC 7.5] Signed-off-by: Peter Maydell --- docs/system/arm/imx8mp-evk.rst | 1 + hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mp.c | 37 ++++++++++++++++++++++++++++++++-- include/hw/arm/fsl-imx8mp.h | 12 +++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8mp-evk.rst index 917c1d517693..00527b0cbedb 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8mp-evk.rst @@ -15,6 +15,7 @@ The ``imx8mp-evk`` machine implements the following devices: * 3 USDHC Storage Controllers * 1 Designware PCI Express Controller * 1 Ethernet Controller + * 2 Designware USB 3 Controllers * 5 GPIO Controllers * 6 I2C Controllers * 3 SPI Controllers diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 4d642db9705d..faa00d1db3b4 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -608,6 +608,7 @@ config FSL_IMX8MP select PCI_EXPRESS_FSL_IMX8M_PHY select SDHCI select UNIMP + select USB_DWC3 select WDT_IMX2 config FSL_IMX8MP_EVK diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 2dd3c97a02e9..0880f0c72442 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -40,8 +40,14 @@ static const struct { [FSL_IMX8MP_VPU_VC8000E_ENCODER] = { 0x38320000, 2 * MiB, "vpu_vc8000e_encoder" }, [FSL_IMX8MP_VPU_G2_DECODER] = { 0x38310000, 2 * MiB, "vpu_g2_decoder" }, [FSL_IMX8MP_VPU_G1_DECODER] = { 0x38300000, 2 * MiB, "vpu_g1_decoder" }, - [FSL_IMX8MP_USB2] = { 0x38200000, 1 * MiB, "usb2" }, - [FSL_IMX8MP_USB1] = { 0x38100000, 1 * MiB, "usb1" }, + [FSL_IMX8MP_USB2_GLUE] = { 0x382f0000, 0x100, "usb2_glue" }, + [FSL_IMX8MP_USB2_OTG] = { 0x3820cc00, 0x100, "usb2_otg" }, + [FSL_IMX8MP_USB2_DEV] = { 0x3820c700, 0x500, "usb2_dev" }, + [FSL_IMX8MP_USB2] = { 0x38200000, 0xc700, "usb2" }, + [FSL_IMX8MP_USB1_GLUE] = { 0x381f0000, 0x100, "usb1_glue" }, + [FSL_IMX8MP_USB1_OTG] = { 0x3810cc00, 0x100, "usb1_otg" }, + [FSL_IMX8MP_USB1_DEV] = { 0x3810c700, 0x500, "usb1_dev" }, + [FSL_IMX8MP_USB1] = { 0x38100000, 0xc700, "usb1" }, [FSL_IMX8MP_GPU2D] = { 0x38008000, 32 * KiB, "gpu2d" }, [FSL_IMX8MP_GPU3D] = { 0x38000000, 32 * KiB, "gpu3d" }, [FSL_IMX8MP_QSPI1_RX_BUFFER] = { 0x34000000, 32 * MiB, "qspi1_rx_buffer" }, @@ -230,6 +236,11 @@ static void fsl_imx8mp_init(Object *obj) object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + for (i = 0; i < FSL_IMX8MP_NUM_USBS; i++) { + g_autofree char *name = g_strdup_printf("usb%d", i); + object_initialize_child(obj, name, &s->usb[i], TYPE_USB_DWC3); + } + for (i = 0; i < FSL_IMX8MP_NUM_ECSPIS; i++) { g_autofree char *name = g_strdup_printf("spi%d", i + 1); object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); @@ -524,6 +535,27 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); } + /* USBs */ + for (i = 0; i < FSL_IMX8MP_NUM_USBS; i++) { + struct { + hwaddr addr; + unsigned int irq; + } usb_table[FSL_IMX8MP_NUM_USBS] = { + { fsl_imx8mp_memmap[FSL_IMX8MP_USB1].addr, FSL_IMX8MP_USB1_IRQ }, + { fsl_imx8mp_memmap[FSL_IMX8MP_USB2].addr, FSL_IMX8MP_USB2_IRQ }, + }; + + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "p2", 1); + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "p3", 1); + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "slots", 2); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->usb[i]), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->usb[i]), 0, usb_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->usb[i].sysbus_xhci), 0, + qdev_get_gpio_in(gicdev, usb_table[i].irq)); + } + /* ECSPIs */ for (i = 0; i < FSL_IMX8MP_NUM_ECSPIS; i++) { struct { @@ -628,6 +660,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_RAM: case FSL_IMX8MP_SNVS_HP: case FSL_IMX8MP_UART1 ... FSL_IMX8MP_UART4: + case FSL_IMX8MP_USB1 ... FSL_IMX8MP_USB2: case FSL_IMX8MP_USDHC1 ... FSL_IMX8MP_USDHC3: case FSL_IMX8MP_WDOG1 ... FSL_IMX8MP_WDOG3: /* device implemented and treated above */ diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index e292c31a3d08..5247e972b888 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -24,6 +24,7 @@ #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" #include "hw/timer/imx_gpt.h" +#include "hw/usb/hcd-dwc3.h" #include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -42,6 +43,7 @@ enum FslImx8mpConfiguration { FSL_IMX8MP_NUM_I2CS = 6, FSL_IMX8MP_NUM_IRQS = 160, FSL_IMX8MP_NUM_UARTS = 4, + FSL_IMX8MP_NUM_USBS = 2, FSL_IMX8MP_NUM_USDHCS = 3, FSL_IMX8MP_NUM_WDTS = 3, }; @@ -62,6 +64,7 @@ struct FslImx8mpState { IMXFECState enet; SDHCIState usdhc[FSL_IMX8MP_NUM_USDHCS]; IMX2WdtState wdt[FSL_IMX8MP_NUM_WDTS]; + USBDWC3 usb[FSL_IMX8MP_NUM_USBS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; OrIRQState gpt5_gpt6_irq; @@ -199,6 +202,12 @@ enum FslImx8mpMemoryRegions { FSL_IMX8MP_UART4, FSL_IMX8MP_USB1, FSL_IMX8MP_USB2, + FSL_IMX8MP_USB1_DEV, + FSL_IMX8MP_USB2_DEV, + FSL_IMX8MP_USB1_OTG, + FSL_IMX8MP_USB2_OTG, + FSL_IMX8MP_USB1_GLUE, + FSL_IMX8MP_USB2_GLUE, FSL_IMX8MP_USDHC1, FSL_IMX8MP_USDHC2, FSL_IMX8MP_USDHC3, @@ -234,6 +243,9 @@ enum FslImx8mpIrqs { FSL_IMX8MP_I2C3_IRQ = 37, FSL_IMX8MP_I2C4_IRQ = 38, + FSL_IMX8MP_USB1_IRQ = 40, + FSL_IMX8MP_USB2_IRQ = 41, + FSL_IMX8MP_GPT1_IRQ = 55, FSL_IMX8MP_GPT2_IRQ = 54, FSL_IMX8MP_GPT3_IRQ = 53, From 1aaf3478684ff1cd02d1b36c32a00bfac9a5dbd5 Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:07 +0100 Subject: [PATCH 0314/1179] hw/arm/fsl-imx8mp: Add on-chip RAM Reviewed-by: Peter Maydell Signed-off-by: Bernhard Beschow Message-id: 20250223114708.1780-18-shentey@gmail.com Signed-off-by: Peter Maydell --- hw/arm/fsl-imx8mp.c | 11 +++++++++++ include/hw/arm/fsl-imx8mp.h | 1 + 2 files changed, 12 insertions(+) diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 0880f0c72442..1ea98e146353 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -644,6 +644,16 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie_phy), 0, fsl_imx8mp_memmap[FSL_IMX8MP_PCIE_PHY1].addr); + /* On-Chip RAM */ + if (!memory_region_init_ram(&s->ocram, NULL, "imx8mp.ocram", + fsl_imx8mp_memmap[FSL_IMX8MP_OCRAM].size, + errp)) { + return; + } + memory_region_add_subregion(get_system_memory(), + fsl_imx8mp_memmap[FSL_IMX8MP_OCRAM].addr, + &s->ocram); + /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mp_memmap); i++) { switch (i) { @@ -655,6 +665,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) case FSL_IMX8MP_ECSPI1 ... FSL_IMX8MP_ECSPI3: case FSL_IMX8MP_ENET1: case FSL_IMX8MP_I2C1 ... FSL_IMX8MP_I2C6: + case FSL_IMX8MP_OCRAM: case FSL_IMX8MP_PCIE1: case FSL_IMX8MP_PCIE_PHY1: case FSL_IMX8MP_RAM: diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h index 5247e972b888..bc97fc416eb9 100644 --- a/include/hw/arm/fsl-imx8mp.h +++ b/include/hw/arm/fsl-imx8mp.h @@ -68,6 +68,7 @@ struct FslImx8mpState { DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; OrIRQState gpt5_gpt6_irq; + MemoryRegion ocram; uint32_t phy_num; bool phy_connected; From 006453f4003aa376086aa1faa13f96e624fdc940 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:31 +0100 Subject: [PATCH 0315/1179] tests/functional: Provide a proper name for the VMs in the replay tests With a proper name the log files get a more meaningful name. Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-2-thuth@redhat.com> --- tests/functional/replay_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/replay_kernel.py b/tests/functional/replay_kernel.py index 8e8ac7d052f5..80795eb0520e 100644 --- a/tests/functional/replay_kernel.py +++ b/tests/functional/replay_kernel.py @@ -34,7 +34,7 @@ def run_vm(self, kernel_path, kernel_command_line, console_pattern, logger = logging.getLogger('replay') start_time = time.time() - vm = self.get_vm() + vm = self.get_vm(name='recording' if record else 'replay') vm.set_console() if record: logger.info('recording the execution...') From 6e52e84df990755b5e5cb6e9804c6c4e567adb52 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:32 +0100 Subject: [PATCH 0316/1179] tests/functional: Convert the xtensa replay test to the functional framework Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-3-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 11 ---------- tests/functional/meson.build | 1 + tests/functional/test_xtensa_replay.py | 28 ++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100755 tests/functional/test_xtensa_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index b9b54a8793d1..54b8417d3d57 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -399,14 +399,3 @@ def test_sparc_ss20(self): file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) self.do_test_advcal_2018(file_path, 'zImage.elf') - def test_xtensa_lx60(self): - """ - :avocado: tags=arch:xtensa - :avocado: tags=machine:lx60 - :avocado: tags=cpu:dc233c - """ - tar_hash = '49e88d9933742f0164b60839886c9739cb7a0d34' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day02.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'santas-sleigh-ride.elf') diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 111d8bab262b..4d3d62f3fe75 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -296,6 +296,7 @@ tests_x86_64_system_thorough = [ tests_xtensa_system_thorough = [ 'xtensa_lx60', + 'xtensa_replay', ] precache_all = [] diff --git a/tests/functional/test_xtensa_replay.py b/tests/functional/test_xtensa_replay.py new file mode 100755 index 000000000000..eb00a3b0044b --- /dev/null +++ b/tests/functional/test_xtensa_replay.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on an xtensa lx650 machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class XTensaReplay(ReplayKernelBase): + + ASSET_DAY02 = Asset( + 'https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/day02.tar.xz', + '68ff07f9b3fd3df36d015eb46299ba44748e94bfbb2d5295fddc1a8d4a9fd324') + + def test_replay(self): + self.set_machine('lx60') + self.cpu = 'dc233c' + kernel_path = self.archive_extract(self.ASSET_DAY02, + member='day02/santas-sleigh-ride.elf') + self.run_rr(kernel_path, self.REPLAY_KERNEL_COMMAND_LINE, + 'QEMU advent calendar') + + +if __name__ == '__main__': + ReplayKernelBase.main() From a14dfd93b55cb9545a510ac0e008479ada25ef94 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:33 +0100 Subject: [PATCH 0317/1179] tests/functional: Convert the sparc replay avocado test While we're at it, change the machine from SS-20 to SS-10 to increase the test coverage a little bit (SS-20 is already tested in the test_sparc_sun4m.py file). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-4-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 12 ------------ tests/functional/meson.build | 1 + tests/functional/test_sparc_replay.py | 27 +++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100755 tests/functional/test_sparc_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 54b8417d3d57..412bf9e06e1f 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -387,15 +387,3 @@ def test_ppc_mac99(self): file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) self.do_test_advcal_2018(file_path, 'invaders.elf', args=('-M', 'graphics=off')) - - def test_sparc_ss20(self): - """ - :avocado: tags=arch:sparc - :avocado: tags=machine:SS-20 - """ - tar_hash = 'b18550d5d61c7615d989a06edace051017726a9f' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day11.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'zImage.elf') - diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 4d3d62f3fe75..46a97999ae39 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -258,6 +258,7 @@ tests_sparc_system_quick = [ ] tests_sparc_system_thorough = [ + 'sparc_replay', 'sparc_sun4m', ] diff --git a/tests/functional/test_sparc_replay.py b/tests/functional/test_sparc_replay.py new file mode 100755 index 000000000000..865d6486f998 --- /dev/null +++ b/tests/functional/test_sparc_replay.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on a sparc sun4m machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class SparcReplay(ReplayKernelBase): + + ASSET_DAY11 = Asset( + 'https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/day11.tar.xz', + 'c776533ba756bf4dd3f1fc4c024fb50ef0d853e05c5f5ddf0900a32d1eaa49e0') + + def test_replay(self): + self.set_machine('SS-10') + kernel_path = self.archive_extract(self.ASSET_DAY11, + member="day11/zImage.elf") + self.run_rr(kernel_path, self.REPLAY_KERNEL_COMMAND_LINE, + 'QEMU advent calendar') + + +if __name__ == '__main__': + ReplayKernelBase.main() From 9917e06cd24dc260467aeccc1d1b9b6c15a0a0d8 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:34 +0100 Subject: [PATCH 0318/1179] tests/functional: Convert the 32-bit ppc replay avocado tests Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-5-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 24 -------------------- tests/functional/meson.build | 1 + tests/functional/test_ppc_replay.py | 34 +++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 24 deletions(-) create mode 100755 tests/functional/test_ppc_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 412bf9e06e1f..89ba6bb3e8b7 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -363,27 +363,3 @@ def test_or1k_sim(self): '/qac-best-of-multiarch/download/day20.tar.xz') file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) self.do_test_advcal_2018(file_path, 'vmlinux') - - def test_ppc_g3beige(self): - """ - :avocado: tags=arch:ppc - :avocado: tags=machine:g3beige - """ - tar_hash = 'e0b872a5eb8fdc5bed19bd43ffe863900ebcedfc' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day15.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'invaders.elf', - args=('-M', 'graphics=off')) - - def test_ppc_mac99(self): - """ - :avocado: tags=arch:ppc - :avocado: tags=machine:mac99 - """ - tar_hash = 'e0b872a5eb8fdc5bed19bd43ffe863900ebcedfc' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day15.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'invaders.elf', - args=('-M', 'graphics=off')) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 46a97999ae39..63465139a079 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -198,6 +198,7 @@ tests_ppc_system_thorough = [ 'ppc_bamboo', 'ppc_mac', 'ppc_mpc8544ds', + 'ppc_replay', 'ppc_sam460ex', 'ppc_tuxrun', 'ppc_virtex_ml507', diff --git a/tests/functional/test_ppc_replay.py b/tests/functional/test_ppc_replay.py new file mode 100755 index 000000000000..8382070abd1c --- /dev/null +++ b/tests/functional/test_ppc_replay.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# +# Replay tests for ppc machines +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class PpcReplay(ReplayKernelBase): + + ASSET_DAY15 = Asset( + 'https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/day15.tar.xz', + '03e0757c131d2959decf293a3572d3b96c5a53587165bf05ce41b2818a2bccd5') + + def do_day15_test(self): + self.require_accelerator("tcg") + kernel_path = self.archive_extract(self.ASSET_DAY15, + member='day15/invaders.elf') + self.run_rr(kernel_path, self.REPLAY_KERNEL_COMMAND_LINE, + 'QEMU advent calendar', args=('-M', 'graphics=off')) + + def test_g3beige(self): + self.set_machine('g3beige') + self.do_day15_test() + + def test_mac99(self): + self.set_machine('mac99') + self.do_day15_test() + + +if __name__ == '__main__': + ReplayKernelBase.main() From 52ec5f51996b5669fadaabb336dddfec77561941 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:35 +0100 Subject: [PATCH 0319/1179] tests/functional: Convert the or1k replay avocado tests Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-6-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 11 ----------- tests/functional/meson.build | 1 + tests/functional/test_or1k_replay.py | 27 +++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 11 deletions(-) create mode 100755 tests/functional/test_or1k_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 89ba6bb3e8b7..ef72b1622e4c 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -352,14 +352,3 @@ def test_ppc64_e500(self): '/qac-best-of-multiarch/download/day19.tar.xz') file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) self.do_test_advcal_2018(file_path, 'uImage') - - def test_or1k_sim(self): - """ - :avocado: tags=arch:or1k - :avocado: tags=machine:or1k-sim - """ - tar_hash = '20334cdaf386108c530ff0badaecc955693027dd' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day20.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'vmlinux') diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 63465139a079..2062489230a7 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -183,6 +183,7 @@ tests_mips64el_system_thorough = [ ] tests_or1k_system_thorough = [ + 'or1k_replay', 'or1k_sim', ] diff --git a/tests/functional/test_or1k_replay.py b/tests/functional/test_or1k_replay.py new file mode 100755 index 000000000000..2b60a9372c5a --- /dev/null +++ b/tests/functional/test_or1k_replay.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on an OpenRISC-1000 SIM machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class Or1kReplay(ReplayKernelBase): + + ASSET_DAY20 = Asset( + 'https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/day20.tar.xz', + 'ff9d7dd7c6bdba325bd85ee85c02db61ff653e129558aeffe6aff55bffb6763a') + + def test_sim(self): + self.set_machine('or1k-sim') + kernel_path = self.archive_extract(self.ASSET_DAY20, + member='day20/vmlinux') + self.run_rr(kernel_path, self.REPLAY_KERNEL_COMMAND_LINE, + 'QEMU advent calendar') + + +if __name__ == '__main__': + ReplayKernelBase.main() From 221620b79e8f380a3a3d65bddde067ad284d08aa Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:36 +0100 Subject: [PATCH 0320/1179] tests/functional: Convert the ppc64 replay avocado tests Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-7-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 45 ------------------------ tests/functional/meson.build | 2 ++ tests/functional/test_ppc64_replay.py | 49 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 45 deletions(-) create mode 100755 tests/functional/test_ppc64_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index ef72b1622e4c..397f75004639 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -242,39 +242,6 @@ def test_alpha_clipper(self): self.run_rr(uncompressed_kernel, kernel_command_line, console_pattern, shift=9, args=('-nodefaults', )) - def test_ppc64_pseries(self): - """ - :avocado: tags=arch:ppc64 - :avocado: tags=machine:pseries - :avocado: tags=accel:tcg - """ - kernel_url = ('https://archives.fedoraproject.org/pub/archive' - '/fedora-secondary/releases/29/Everything/ppc64le/os' - '/ppc/ppc64/vmlinuz') - kernel_hash = '3fe04abfc852b66653b8c3c897a59a689270bc77' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=hvc0' - console_pattern = 'VFS: Cannot open root device' - self.run_rr(kernel_path, kernel_command_line, console_pattern) - - def test_ppc64_powernv(self): - """ - :avocado: tags=arch:ppc64 - :avocado: tags=machine:powernv - :avocado: tags=accel:tcg - """ - kernel_url = ('https://archives.fedoraproject.org/pub/archive' - '/fedora-secondary/releases/29/Everything/ppc64le/os' - '/ppc/ppc64/vmlinuz') - kernel_hash = '3fe04abfc852b66653b8c3c897a59a689270bc77' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + \ - 'console=tty0 console=hvc0' - console_pattern = 'VFS: Cannot open root device' - self.run_rr(kernel_path, kernel_command_line, console_pattern) - def test_m68k_q800(self): """ :avocado: tags=arch:m68k @@ -340,15 +307,3 @@ def test_microblaze_s3adsp1800(self): '/qac-best-of-multiarch/download/day17.tar.xz') file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) self.do_test_advcal_2018(file_path, 'ballerina.bin') - - def test_ppc64_e500(self): - """ - :avocado: tags=arch:ppc64 - :avocado: tags=machine:ppce500 - :avocado: tags=cpu:e5500 - """ - tar_hash = '6951d86d644b302898da2fd701739c9406527fe1' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day19.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'uImage') diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 2062489230a7..b68b4da6a3d5 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -41,6 +41,7 @@ test_timeouts = { 'ppc64_hv' : 1000, 'ppc64_powernv' : 480, 'ppc64_pseries' : 480, + 'ppc64_replay' : 210, 'ppc64_tuxrun' : 420, 'ppc64_mac99' : 120, 'riscv64_tuxrun' : 120, @@ -214,6 +215,7 @@ tests_ppc64_system_thorough = [ 'ppc64_hv', 'ppc64_powernv', 'ppc64_pseries', + 'ppc64_replay', 'ppc64_tuxrun', 'ppc64_mac99', ] diff --git a/tests/functional/test_ppc64_replay.py b/tests/functional/test_ppc64_replay.py new file mode 100755 index 000000000000..48ce1b7f1e19 --- /dev/null +++ b/tests/functional/test_ppc64_replay.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on ppc64 machines +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class Ppc64Replay(ReplayKernelBase): + + ASSET_DAY19 = Asset( + ('https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/' + 'day19.tar.xz'), + '20b1bb5a8488c664defbb5d283addc91a05335a936c63b3f5ff7eee74b725755') + + def test_ppc64_e500(self): + self.set_machine('ppce500') + self.cpu = 'e5500' + kernel_path = self.archive_extract(self.ASSET_DAY19, + member='day19/uImage') + self.run_rr(kernel_path, self.REPLAY_KERNEL_COMMAND_LINE, + 'QEMU advent calendar') + + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora-secondary/' + 'releases/29/Everything/ppc64le/os/ppc/ppc64/vmlinuz'), + '383c2f5c23bc0d9d32680c3924d3fd7ee25cc5ef97091ac1aa5e1d853422fc5f') + + def test_ppc64_pseries(self): + self.set_machine('pseries') + kernel_path = self.ASSET_KERNEL.fetch() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=hvc0' + console_pattern = 'VFS: Cannot open root device' + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + def test_ppc64_powernv(self): + self.set_machine('powernv') + kernel_path = self.ASSET_KERNEL.fetch() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + \ + 'console=tty0 console=hvc0' + console_pattern = 'VFS: Cannot open root device' + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + +if __name__ == '__main__': + ReplayKernelBase.main() From 6674fa9c345d95f0d15b64d3d553334e773255d7 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:37 +0100 Subject: [PATCH 0321/1179] tests/functional: Convert the microblaze replay avocado tests Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-8-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 11 --------- tests/functional/meson.build | 1 + tests/functional/test_microblaze_replay.py | 28 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100755 tests/functional/test_microblaze_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 397f75004639..b2097afc302d 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -296,14 +296,3 @@ def test_m68k_mcf5208evb(self): '/qac-best-of-multiarch/download/day07.tar.xz') file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) self.do_test_advcal_2018(file_path, 'sanity-clause.elf') - - def test_microblaze_s3adsp1800(self): - """ - :avocado: tags=arch:microblaze - :avocado: tags=machine:petalogix-s3adsp1800 - """ - tar_hash = '08bf3e3bfb6b6c7ce1e54ab65d54e189f2caf13f' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day17.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'ballerina.bin') diff --git a/tests/functional/meson.build b/tests/functional/meson.build index b68b4da6a3d5..58f12f54d14d 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -152,6 +152,7 @@ tests_m68k_system_thorough = [ ] tests_microblaze_system_thorough = [ + 'microblaze_replay', 'microblaze_s3adsp1800' ] diff --git a/tests/functional/test_microblaze_replay.py b/tests/functional/test_microblaze_replay.py new file mode 100755 index 000000000000..7484c4186f3f --- /dev/null +++ b/tests/functional/test_microblaze_replay.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on an microblaze machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class MicroblazeReplay(ReplayKernelBase): + + ASSET_DAY17 = Asset( + ('https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/' + 'day17.tar.xz'), + '3ba7439dfbea7af4876662c97f8e1f0cdad9231fc166e4861d17042489270057') + + def test_microblaze_s3adsp1800(self): + self.set_machine('petalogix-s3adsp1800') + kernel_path = self.archive_extract(self.ASSET_DAY17, + member='day17/ballerina.bin') + self.run_rr(kernel_path, self.REPLAY_KERNEL_COMMAND_LINE, + 'QEMU advent calendar') + + +if __name__ == '__main__': + ReplayKernelBase.main() From ec971d8554f620fda648d681467b1e10c086a8aa Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:38 +0100 Subject: [PATCH 0322/1179] tests/functional: Convert the m68k replay avocado tests Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-9-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 29 ------------------- tests/functional/meson.build | 1 + tests/functional/test_m68k_replay.py | 42 ++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 29 deletions(-) create mode 100755 tests/functional/test_m68k_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index b2097afc302d..4bd48878b7ef 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -242,24 +242,6 @@ def test_alpha_clipper(self): self.run_rr(uncompressed_kernel, kernel_command_line, console_pattern, shift=9, args=('-nodefaults', )) - def test_m68k_q800(self): - """ - :avocado: tags=arch:m68k - :avocado: tags=machine:q800 - """ - deb_url = ('https://snapshot.debian.org/archive/debian-ports' - '/20191021T083923Z/pool-m68k/main' - '/l/linux/kernel-image-5.3.0-1-m68k-di_5.3.7-1_m68k.udeb') - deb_hash = '044954bb9be4160a3ce81f8bc1b5e856b75cccd1' - deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) - kernel_path = self.extract_from_deb(deb_path, - '/boot/vmlinux-5.3.0-1-m68k') - - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'console=ttyS0 vga=off') - console_pattern = 'No filesystem could mount root' - self.run_rr(kernel_path, kernel_command_line, console_pattern) - def do_test_advcal_2018(self, file_path, kernel_name, args=None): archive.extract(file_path, self.workdir) @@ -285,14 +267,3 @@ def test_arm_vexpressa9(self): dtb_path = self.workdir + '/day16/vexpress-v2p-ca9.dtb' self.do_test_advcal_2018(file_path, 'winter.zImage', args=('-dtb', dtb_path)) - - def test_m68k_mcf5208evb(self): - """ - :avocado: tags=arch:m68k - :avocado: tags=machine:mcf5208evb - """ - tar_hash = 'ac688fd00561a2b6ce1359f9ff6aa2b98c9a570c' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day07.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - self.do_test_advcal_2018(file_path, 'sanity-clause.elf') diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 58f12f54d14d..995cea4f305a 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -147,6 +147,7 @@ tests_loongarch64_system_thorough = [ tests_m68k_system_thorough = [ 'm68k_mcf5208evb', 'm68k_nextcube', + 'm68k_replay', 'm68k_q800', 'm68k_tuxrun', ] diff --git a/tests/functional/test_m68k_replay.py b/tests/functional/test_m68k_replay.py new file mode 100755 index 000000000000..18c1db539c50 --- /dev/null +++ b/tests/functional/test_m68k_replay.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on an m68k machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class M68kReplay(ReplayKernelBase): + + ASSET_Q800 = Asset( + ('https://snapshot.debian.org/' + 'archive/debian-ports/20191021T083923Z/pool-m68k/main/l/linux/' + 'kernel-image-5.3.0-1-m68k-di_5.3.7-1_m68k.udeb'), + '949e50d74d4b9bc15d26c06d402717b7a4c0e32ff8100014f5930d8024de7b73') + + def test_q800(self): + self.set_machine('q800') + kernel_path = self.archive_extract(self.ASSET_Q800, + member='boot/vmlinux-5.3.0-1-m68k') + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 vga=off') + console_pattern = 'No filesystem could mount root' + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + ASSET_MCF5208 = Asset( + 'https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/day07.tar.xz', + '753c2f3837126b7c6ba92d0b1e0b156e8a2c5131d2d576bb0b9a763fae73c08a') + + def test_mcf5208evb(self): + self.set_machine('mcf5208evb') + kernel_path = self.archive_extract(self.ASSET_MCF5208, + member='day07/sanity-clause.elf') + self.run_rr(kernel_path, self.KERNEL_COMMON_COMMAND_LINE, + 'QEMU advent calendar') + + +if __name__ == '__main__': + ReplayKernelBase.main() From 8a145225c296006efc28c4c4cf90b9e0b334ec87 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:39 +0100 Subject: [PATCH 0323/1179] tests/functional: Convert the arm replay avocado tests Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-10-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 74 ----------------------------- tests/functional/meson.build | 1 + tests/functional/test_arm_replay.py | 69 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 74 deletions(-) create mode 100755 tests/functional/test_arm_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 4bd48878b7ef..02bd868a429b 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -162,54 +162,6 @@ def test_aarch64_virt(self): self.run_rr(kernel_path, kernel_command_line, console_pattern) - def test_arm_virt(self): - """ - :avocado: tags=arch:arm - :avocado: tags=machine:virt - """ - kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' - '/linux/releases/29/Everything/armhfp/os/images/pxeboot' - '/vmlinuz') - kernel_hash = 'e9826d741b4fb04cadba8d4824d1ed3b7fb8b4d4' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'console=ttyAMA0') - console_pattern = 'VFS: Cannot open root device' - - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=1) - - def test_arm_cubieboard_initrd(self): - """ - :avocado: tags=arch:arm - :avocado: tags=machine:cubieboard - """ - deb_url = ('https://apt.armbian.com/pool/main/l/' - 'linux-6.6.16/linux-image-current-sunxi_24.2.1_armhf__6.6.16-Seb3e-D6b4a-P2359-Ce96bHfe66-HK01ba-V014b-B067e-R448a.deb') - deb_hash = 'f7c3c8c5432f765445dc6e7eab02f3bbe668256b' - deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) - kernel_path = self.extract_from_deb(deb_path, - '/boot/vmlinuz-6.6.16-current-sunxi') - dtb_path = '/usr/lib/linux-image-6.6.16-current-sunxi/sun4i-a10-cubieboard.dtb' - dtb_path = self.extract_from_deb(deb_path, dtb_path) - initrd_url = ('https://github.com/groeck/linux-build-test/raw/' - '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' - 'arm/rootfs-armv5.cpio.gz') - initrd_hash = '2b50f1873e113523967806f4da2afe385462ff9b' - initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) - initrd_path = os.path.join(self.workdir, 'rootfs.cpio') - archive.gzip_uncompress(initrd_path_gz, initrd_path) - - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'console=ttyS0,115200 ' - 'usbcore.nousb ' - 'panic=-1 noreboot') - console_pattern = 'Boot successful.' - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=1, - args=('-dtb', dtb_path, - '-initrd', initrd_path, - '-no-reboot')) - def test_s390x_s390_ccw_virtio(self): """ :avocado: tags=arch:s390x @@ -241,29 +193,3 @@ def test_alpha_clipper(self): console_pattern = 'Kernel command line: %s' % kernel_command_line self.run_rr(uncompressed_kernel, kernel_command_line, console_pattern, shift=9, args=('-nodefaults', )) - - def do_test_advcal_2018(self, file_path, kernel_name, args=None): - archive.extract(file_path, self.workdir) - - for entry in os.scandir(self.workdir): - if entry.name.startswith('day') and entry.is_dir(): - kernel_path = os.path.join(entry.path, kernel_name) - break - - kernel_command_line = '' - console_pattern = 'QEMU advent calendar' - self.run_rr(kernel_path, kernel_command_line, console_pattern, - args=args) - - def test_arm_vexpressa9(self): - """ - :avocado: tags=arch:arm - :avocado: tags=machine:vexpress-a9 - """ - tar_hash = '32b7677ce8b6f1471fb0059865f451169934245b' - tar_url = ('https://qemu-advcal.gitlab.io' - '/qac-best-of-multiarch/download/day16.tar.xz') - file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) - dtb_path = self.workdir + '/day16/vexpress-v2p-ca9.dtb' - self.do_test_advcal_2018(file_path, 'winter.zImage', - args=('-dtb', dtb_path)) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 995cea4f305a..a46c4e894640 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -113,6 +113,7 @@ tests_arm_system_thorough = [ 'arm_orangepi', 'arm_quanta_gsj', 'arm_raspi2', + 'arm_replay', 'arm_smdkc210', 'arm_sx1', 'arm_vexpress', diff --git a/tests/functional/test_arm_replay.py b/tests/functional/test_arm_replay.py new file mode 100755 index 000000000000..e002e6a26475 --- /dev/null +++ b/tests/functional/test_arm_replay.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on arm machines and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class ArmReplay(ReplayKernelBase): + + ASSET_VIRT = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/' + 'releases/29/Everything/armhfp/os/images/pxeboot/vmlinuz'), + '18dd5f1a9a28bd539f9d047f7c0677211bae528e8712b40ca5a229a4ad8e2591') + + def test_virt(self): + self.set_machine('virt') + kernel_path = self.ASSET_VIRT.fetch() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + console_pattern = 'VFS: Cannot open root device' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=1) + + ASSET_CUBIE_KERNEL = Asset( + ('https://apt.armbian.com/pool/main/l/linux-6.6.16/' + 'linux-image-current-sunxi_24.2.1_armhf_' + '_6.6.16-Seb3e-D6b4a-P2359-Ce96bHfe66-HK01ba-V014b-B067e-R448a.deb'), + '3d968c15b121ede871dce49d13ee7644d6f74b6b121b84c9a40f51b0c80d6d22') + + ASSET_CUBIE_INITRD = Asset( + ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/arm/rootfs-armv5.cpio.gz'), + '334b8d256db67a3f2b3ad070aa08b5ade39624e0e7e35b02f4359a577bc8f39b') + + def test_cubieboard(self): + self.set_machine('cubieboard') + kernel_path = self.archive_extract(self.ASSET_CUBIE_KERNEL, + member='boot/vmlinuz-6.6.16-current-sunxi') + dtb_path = self.archive_extract(self.ASSET_CUBIE_KERNEL, + member='usr/lib/linux-image-6.6.16-current-sunxi/sun4i-a10-cubieboard.dtb') + initrd_path = self.uncompress(self.ASSET_CUBIE_INITRD) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'usbcore.nousb ' + 'panic=-1 noreboot') + console_pattern = 'Boot successful.' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=1, + args=('-dtb', dtb_path, + '-initrd', initrd_path, + '-no-reboot')) + + ASSET_DAY16 = Asset( + 'https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/day16.tar.xz', + '63311adb2d4c4e7a73214a86d29988add87266a909719c56acfadd026b4110a7') + + def test_vexpressa9(self): + self.set_machine('vexpress-a9') + self.archive_extract(self.ASSET_DAY16) + kernel_path = self.scratch_file('day16', 'winter.zImage') + dtb_path = self.scratch_file('day16', 'vexpress-v2p-ca9.dtb') + self.run_rr(kernel_path, self.REPLAY_KERNEL_COMMAND_LINE, + 'QEMU advent calendar', args=('-dtb', dtb_path)) + + +if __name__ == '__main__': + ReplayKernelBase.main() From 7472862965f77d10b44eba0d9efa9cfb47c94651 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:40 +0100 Subject: [PATCH 0324/1179] tests/functional: Convert the alpha replay avocado tests Put the tests into a separate file now (since in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-11-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 17 ---------------- tests/functional/meson.build | 1 + tests/functional/test_alpha_replay.py | 29 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 17 deletions(-) create mode 100755 tests/functional/test_alpha_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 02bd868a429b..4347d202742f 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -176,20 +176,3 @@ def test_s390x_s390_ccw_virtio(self): kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=sclp0' console_pattern = 'Kernel command line: %s' % kernel_command_line self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=9) - - def test_alpha_clipper(self): - """ - :avocado: tags=arch:alpha - :avocado: tags=machine:clipper - """ - kernel_url = ('http://archive.debian.org/debian/dists/lenny/main/' - 'installer-alpha/20090123lenny10/images/cdrom/vmlinuz') - kernel_hash = '3a943149335529e2ed3e74d0d787b85fb5671ba3' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - uncompressed_kernel = archive.uncompress(kernel_path, self.workdir) - - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' - console_pattern = 'Kernel command line: %s' % kernel_command_line - self.run_rr(uncompressed_kernel, kernel_command_line, console_pattern, shift=9, - args=('-nodefaults', )) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index a46c4e894640..e8b934d7f5f2 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -90,6 +90,7 @@ tests_alpha_system_quick = [ tests_alpha_system_thorough = [ 'alpha_clipper', + 'alpha_replay', ] tests_arm_system_quick = [ diff --git a/tests/functional/test_alpha_replay.py b/tests/functional/test_alpha_replay.py new file mode 100755 index 000000000000..24a17ef5904e --- /dev/null +++ b/tests/functional/test_alpha_replay.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on an Alpha machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class AlphaReplay(ReplayKernelBase): + + ASSET_KERNEL = Asset( + ('http://archive.debian.org/debian/dists/lenny/main/installer-alpha/' + '20090123lenny10/images/cdrom/vmlinuz'), + '34f53da3fa32212e4f00b03cb944b2ad81c06bc8faaf9b7193b2e544ceeca576') + + def test_clipper(self): + self.set_machine('clipper') + kernel_path = self.uncompress(self.ASSET_KERNEL, format='gz') + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=9, + args=('-nodefaults', )) + + +if __name__ == '__main__': + ReplayKernelBase.main() From 504248520316ac72f11855e14766e6658ec486d4 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:41 +0100 Subject: [PATCH 0325/1179] tests/functional: Convert the s390x replay avocado tests Put the tests into a separate file now (in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-12-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 15 -------------- tests/functional/meson.build | 1 + tests/functional/test_s390x_replay.py | 28 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 15 deletions(-) create mode 100755 tests/functional/test_s390x_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 4347d202742f..495436046137 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -161,18 +161,3 @@ def test_aarch64_virt(self): console_pattern = 'VFS: Cannot open root device' self.run_rr(kernel_path, kernel_command_line, console_pattern) - - def test_s390x_s390_ccw_virtio(self): - """ - :avocado: tags=arch:s390x - :avocado: tags=machine:s390-ccw-virtio - """ - kernel_url = ('https://archives.fedoraproject.org/pub/archive' - '/fedora-secondary/releases/29/Everything/s390x/os/images' - '/kernel.img') - kernel_hash = 'e8e8439103ef8053418ef062644ffd46a7919313' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=sclp0' - console_pattern = 'Kernel command line: %s' % kernel_command_line - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=9) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index e8b934d7f5f2..acab53642819 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -248,6 +248,7 @@ tests_rx_system_thorough = [ tests_s390x_system_thorough = [ 's390x_ccw_virtio', + 's390x_replay', 's390x_topology', 's390x_tuxrun', ] diff --git a/tests/functional/test_s390x_replay.py b/tests/functional/test_s390x_replay.py new file mode 100755 index 000000000000..33b5843adae3 --- /dev/null +++ b/tests/functional/test_s390x_replay.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on an s390x machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class S390xReplay(ReplayKernelBase): + + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora-secondary/' + 'releases/29/Everything/s390x/os/images/kernel.img'), + 'dace03b8ae0c9f670ebb9b8d6ce5eb24b62987f346de8f1300a439bb00bb99e7') + + def test_s390_ccw_virtio(self): + self.set_machine('s390-ccw-virtio') + kernel_path = self.ASSET_KERNEL.fetch() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=sclp0' + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=9) + + +if __name__ == '__main__': + ReplayKernelBase.main() From 4d75a3743a04a4a3db7f339eee159068fa8757b6 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:42 +0100 Subject: [PATCH 0326/1179] tests/functional: Convert the aarch64 replay avocado tests Put the tests into a separate file now (in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth Message-ID: <20250218152744.228335-13-thuth@redhat.com> --- tests/avocado/replay_kernel.py | 18 --------------- tests/functional/meson.build | 1 + tests/functional/test_aarch64_replay.py | 30 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 18 deletions(-) create mode 100755 tests/functional/test_aarch64_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 495436046137..dffced62aa24 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -143,21 +143,3 @@ def test_x86_64_q35(self): console_pattern = 'VFS: Cannot open root device' self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) - - def test_aarch64_virt(self): - """ - :avocado: tags=arch:aarch64 - :avocado: tags=machine:virt - :avocado: tags=cpu:cortex-a53 - """ - kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' - '/linux/releases/29/Everything/aarch64/os/images/pxeboot' - '/vmlinuz') - kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'console=ttyAMA0') - console_pattern = 'VFS: Cannot open root device' - - self.run_rr(kernel_path, kernel_command_line, console_pattern) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index acab53642819..e8e5a7757c36 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -71,6 +71,7 @@ tests_aarch64_system_thorough = [ 'aarch64_aspeed', 'aarch64_raspi3', 'aarch64_raspi4', + 'aarch64_replay', 'aarch64_rme_virt', 'aarch64_rme_sbsaref', 'aarch64_sbsaref', diff --git a/tests/functional/test_aarch64_replay.py b/tests/functional/test_aarch64_replay.py new file mode 100755 index 000000000000..04cde433bcf5 --- /dev/null +++ b/tests/functional/test_aarch64_replay.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on an aarch64 machine +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from replay_kernel import ReplayKernelBase + + +class Aarch64Replay(ReplayKernelBase): + + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/' + 'releases/29/Everything/aarch64/os/images/pxeboot/vmlinuz'), + '7e1430b81c26bdd0da025eeb8fbd77b5dc961da4364af26e771bd39f379cbbf7') + + def test_aarch64_virt(self): + self.set_machine('virt') + self.cpu = 'cortex-a53' + kernel_path = self.ASSET_KERNEL.fetch() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + console_pattern = 'VFS: Cannot open root device' + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + +if __name__ == '__main__': + ReplayKernelBase.main() From 0f31f0f53c45e248b1d361699210019ec867c22b Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 18 Feb 2025 16:27:43 +0100 Subject: [PATCH 0327/1179] tests/functional: Convert the x86_64 replay avocado tests Put the tests into a separate file now (in the functional framework, each file is run with one specific qemu-system-* binary). Signed-off-by: Thomas Huth --- tests/avocado/replay_kernel.py | 35 -------------------------- tests/functional/meson.build | 2 ++ tests/functional/test_x86_64_replay.py | 35 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 35 deletions(-) create mode 100755 tests/functional/test_x86_64_replay.py diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index dffced62aa24..35515323723b 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -108,38 +108,3 @@ def test_i386_pc(self): console_pattern = 'VFS: Cannot open root device' self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) - - # See https://gitlab.com/qemu-project/qemu/-/issues/2094 - @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'pc machine is unstable with replay') - def test_x86_64_pc(self): - """ - :avocado: tags=arch:x86_64 - :avocado: tags=machine:pc - :avocado: tags=flaky - """ - kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' - '/linux/releases/29/Everything/x86_64/os/images/pxeboot' - '/vmlinuz') - kernel_hash = '23bebd2680757891cf7adedb033532163a792495' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' - console_pattern = 'VFS: Cannot open root device' - - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) - - def test_x86_64_q35(self): - """ - :avocado: tags=arch:x86_64 - :avocado: tags=machine:q35 - """ - kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' - '/linux/releases/29/Everything/x86_64/os/images/pxeboot' - '/vmlinuz') - kernel_hash = '23bebd2680757891cf7adedb033532163a792495' - kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) - - kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' - console_pattern = 'VFS: Cannot open root device' - - self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index e8e5a7757c36..8ae70568a20e 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -49,6 +49,7 @@ test_timeouts = { 'sh4_tuxrun' : 240, 'virtio_balloon': 120, 'x86_64_kvm_xen' : 180, + 'x86_64_replay' : 480, } tests_generic_system = [ @@ -302,6 +303,7 @@ tests_x86_64_system_thorough = [ 'x86_64_hotplug_blk', 'x86_64_hotplug_cpu', 'x86_64_kvm_xen', + 'x86_64_replay', 'x86_64_tuxrun', ] diff --git a/tests/functional/test_x86_64_replay.py b/tests/functional/test_x86_64_replay.py new file mode 100755 index 000000000000..180f23a60c5d --- /dev/null +++ b/tests/functional/test_x86_64_replay.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Replay test that boots a Linux kernel on x86_64 machines +# and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, skipFlakyTest +from replay_kernel import ReplayKernelBase + + +class X86Replay(ReplayKernelBase): + + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux' + '/releases/29/Everything/x86_64/os/images/pxeboot/vmlinuz'), + '8f237d84712b1b411baf3af2aeaaee10b9aae8e345ec265b87ab3a39639eb143') + + def do_test_x86(self, machine): + self.set_machine(machine) + kernel_path = self.ASSET_KERNEL.fetch() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'VFS: Cannot open root device' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + @skipFlakyTest('https://gitlab.com/qemu-project/qemu/-/issues/2094') + def test_pc(self): + self.do_test_x86('pc') + + def test_q35(self): + self.do_test_x86('q35') + + +if __name__ == '__main__': + ReplayKernelBase.main() From fe95724da2180681b7d78e9c2b9fd8438023f9ce Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 21 Feb 2025 14:06:40 +0000 Subject: [PATCH 0328/1179] tests/functional: Bump some arm test timeouts On my local machine, for a debug build, sbsaref_alpine takes nearly 900s: $ (cd build/x86 && ./pyvenv/bin/meson test --setup thorough --suite func-thorough func-aarch64-aarch64_sbsaref_alpine ) 1/1 qemu:func-thorough+func-aarch64-thorough+thorough / func-aarch64-aarch64_sbsaref_alpine OK 896.90s arm_aspeed_rainier can also run close to its current timeout: 6/44 qemu:func-thorough+func-arm-thorough+thorough / func-arm-arm_aspeed_rainier OK 215.75s and arm_aspeed_ast2500 and arm_aspeed_ast2600 can go over: 13/44 qemu:func-thorough+func-arm-thorough+thorough / func-arm-arm_aspeed_ast2600 OK 792.94s 27/44 qemu:func-thorough+func-arm-thorough+thorough / func-arm-arm_aspeed_ast2500 TIMEOUT 480.01s The sx1 test fails not on the overall meson timeout but on the 60 second timeout in some of the subtests. Bump all these timeouts up a bit. Signed-off-by: Peter Maydell Reviewed-by: Thomas Huth Message-ID: <20250221140640.786341-1-peter.maydell@linaro.org> Signed-off-by: Thomas Huth --- tests/functional/meson.build | 8 ++++---- tests/functional/test_arm_sx1.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 8ae70568a20e..3fd2652c0782 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -15,16 +15,16 @@ test_timeouts = { 'aarch64_raspi4' : 480, 'aarch64_rme_virt' : 1200, 'aarch64_rme_sbsaref' : 1200, - 'aarch64_sbsaref_alpine' : 720, + 'aarch64_sbsaref_alpine' : 1200, 'aarch64_sbsaref_freebsd' : 720, 'aarch64_tuxrun' : 240, 'aarch64_virt' : 720, 'acpi_bits' : 420, 'arm_aspeed_palmetto' : 120, 'arm_aspeed_romulus' : 120, - 'arm_aspeed_ast2500' : 480, - 'arm_aspeed_ast2600' : 720, - 'arm_aspeed_rainier' : 240, + 'arm_aspeed_ast2500' : 720, + 'arm_aspeed_ast2600' : 1200, + 'arm_aspeed_rainier' : 480, 'arm_bpim2u' : 500, 'arm_collie' : 180, 'arm_cubieboard' : 360, diff --git a/tests/functional/test_arm_sx1.py b/tests/functional/test_arm_sx1.py index b85bfaa178f9..4dd1e1859fa5 100755 --- a/tests/functional/test_arm_sx1.py +++ b/tests/functional/test_arm_sx1.py @@ -44,7 +44,7 @@ def test_arm_sx1_initrd(self): self.vm.add_args('-no-reboot') self.launch_kernel(zimage_path, initrd=initrd_path) - self.vm.wait(timeout=60) + self.vm.wait(timeout=120) def test_arm_sx1_sd(self): self.set_machine('sx1') @@ -55,7 +55,7 @@ def test_arm_sx1_sd(self): self.vm.add_args('-snapshot') self.vm.add_args('-drive', f'format=raw,if=sd,file={sd_fs_path}') self.launch_kernel(zimage_path) - self.vm.wait(timeout=60) + self.vm.wait(timeout=120) def test_arm_sx1_flash(self): self.set_machine('sx1') @@ -66,7 +66,7 @@ def test_arm_sx1_flash(self): self.vm.add_args('-snapshot') self.vm.add_args('-drive', f'format=raw,if=pflash,file={flash_path}') self.launch_kernel(zimage_path) - self.vm.wait(timeout=60) + self.vm.wait(timeout=120) if __name__ == '__main__': LinuxKernelTest.main() From d5d028eee38d4107821c0d2cfdb0dd04b9ba5ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Tue, 25 Feb 2025 11:05:25 +0000 Subject: [PATCH 0329/1179] gitlab: use --refetch in check-patch/check-dco jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When gitlab initializes the repo checkout for a CI job, it will have done a shallow clone with only partial history. Periodically the objects that are omitted cause trouble with the check-patch/check-dco jobs. This is exhibited as reporting strange errors being unable to fetch certain objects that are known to exist. Passing the --refetch flag to 'git fetch' causes it to not assume the local checkout has all common objects and thus re-fetch everything that is needed. This appears to solve the check-patch/check-dco job failures. Signed-off-by: Daniel P. Berrangé Acked-by: Michael S. Tsirkin Message-ID: <20250225110525.2209854-1-berrange@redhat.com> Signed-off-by: Thomas Huth --- .gitlab-ci.d/check-dco.py | 2 +- .gitlab-ci.d/check-patch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.d/check-dco.py b/.gitlab-ci.d/check-dco.py index 70dec7d6ee9b..2fd56683dc6e 100755 --- a/.gitlab-ci.d/check-dco.py +++ b/.gitlab-ci.d/check-dco.py @@ -21,7 +21,7 @@ print(f"adding upstream git repo @ {repourl}") subprocess.check_call(["git", "remote", "add", "check-dco", repourl]) -subprocess.check_call(["git", "fetch", "check-dco", "master"]) +subprocess.check_call(["git", "fetch", "--refetch", "check-dco", "master"]) ancestor = subprocess.check_output(["git", "merge-base", "check-dco/master", "HEAD"], diff --git a/.gitlab-ci.d/check-patch.py b/.gitlab-ci.d/check-patch.py index 68c549a146a3..be13e6f77d72 100755 --- a/.gitlab-ci.d/check-patch.py +++ b/.gitlab-ci.d/check-patch.py @@ -24,7 +24,7 @@ # base for the user's branch. We thus need to figure out a common # ancestor between the user's branch and current git master. subprocess.check_call(["git", "remote", "add", "check-patch", repourl]) -subprocess.check_call(["git", "fetch", "check-patch", "master"]) +subprocess.check_call(["git", "fetch", "--refetch", "check-patch", "master"]) ancestor = subprocess.check_output(["git", "merge-base", "check-patch/master", "HEAD"], From 72cdd672e18c486db7c54a7b33c8b4fe7a0026e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Wed, 26 Feb 2025 07:50:13 +0100 Subject: [PATCH 0330/1179] tests/functional: Replace the ppc64 e500 advent calendar test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the advent calendar test with a buildroot image built with qemu_ppc64_e5500_defconfig. Unlike the advent calendar image, this newer buildroot image supports networking, too. Thus boot a ppce500 machine from kernel and disk, test network and poweroff. Add '-no-shutdown' to the command line to avoid exiting from QEMU as it seems to bother the functional framework. Signed-off-by: Cédric Le Goater Message-ID: <20250226065013.196052-1-clg@redhat.com> Reviewed-by: Thomas Huth [thuth: Add some wording about network support to the commit message] Signed-off-by: Thomas Huth --- tests/functional/test_ppc64_e500.py | 33 +++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/tests/functional/test_ppc64_e500.py b/tests/functional/test_ppc64_e500.py index b92fe0b0e75e..9ce7ae6c4798 100755 --- a/tests/functional/test_ppc64_e500.py +++ b/tests/functional/test_ppc64_e500.py @@ -5,20 +5,39 @@ # SPDX-License-Identifier: GPL-2.0-or-later from qemu_test import LinuxKernelTest, Asset +from qemu_test import exec_command_and_wait_for_pattern class E500Test(LinuxKernelTest): - ASSET_DAY19 = Asset( - 'https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/day19.tar.xz', - '20b1bb5a8488c664defbb5d283addc91a05335a936c63b3f5ff7eee74b725755') + ASSET_BR2_E5500_UIMAGE = Asset( + 'https://github.com/legoater/qemu-ppc-boot/raw/refs/heads/main/buildroot/qemu_ppc64_e5500-2023.11-8-gdcd9f0f6eb-20240104/uImage', + '2478187c455d6cca3984e9dfde9c635d824ea16236b85fd6b4809f744706deda') - def test_ppc64_e500(self): + ASSET_BR2_E5500_ROOTFS = Asset( + 'https://github.com/legoater/qemu-ppc-boot/raw/refs/heads/main//buildroot/qemu_ppc64_e5500-2023.11-8-gdcd9f0f6eb-20240104/rootfs.ext2', + '9035ef97237c84c7522baaff17d25cdfca4bb7a053d5e296e902919473423d76') + + def test_ppc64_e500_buildroot(self): self.set_machine('ppce500') self.cpu = 'e5500' - self.archive_extract(self.ASSET_DAY19) - self.launch_kernel(self.scratch_file('day19', 'uImage'), - wait_for='QEMU advent calendar') + + uimage_path = self.ASSET_BR2_E5500_UIMAGE.fetch() + rootfs_path = self.ASSET_BR2_E5500_ROOTFS.fetch() + + self.vm.set_console() + self.vm.add_args('-kernel', uimage_path, + '-append', 'root=/dev/vda', + '-drive', f'file={rootfs_path},if=virtio,format=raw', + '-snapshot', '-no-shutdown') + self.vm.launch() + + self.wait_for_console_pattern('Linux version') + self.wait_for_console_pattern('/init as init process') + self.wait_for_console_pattern('lease of 10.0.2.15') + self.wait_for_console_pattern('buildroot login:') + exec_command_and_wait_for_pattern(self, 'root', '#') + exec_command_and_wait_for_pattern(self, 'poweroff', 'Power down') if __name__ == '__main__': LinuxKernelTest.main() From 5d20aa540b6991c0dbeef933d2055e5372f52e0e Mon Sep 17 00:00:00 2001 From: EwanHai Date: Mon, 13 Jan 2025 02:44:10 -0500 Subject: [PATCH 0331/1179] target/i386: Add support for Zhaoxin CPU vendor identification Zhaoxin currently uses two vendors: "Shanghai" and "Centaurhauls". It is important to note that the latter now belongs to Zhaoxin. Therefore, this patch replaces CPUID_VENDOR_VIA with CPUID_VENDOR_ZHAOXIN1. The previous CPUID_VENDOR_VIA macro was only defined but never used in QEMU, making this change straightforward. Additionally, the IS_ZHAOXIN_CPU macro has been added to simplify the checks for Zhaoxin CPUs. Signed-off-by: EwanHai Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250113074413.297793-2-ewanhai-oc@zhaoxin.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.h | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index c67b42d34fc6..4279cf5cdee7 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1122,7 +1122,16 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); #define CPUID_VENDOR_AMD_3 0x444d4163 /* "cAMD" */ #define CPUID_VENDOR_AMD "AuthenticAMD" -#define CPUID_VENDOR_VIA "CentaurHauls" +#define CPUID_VENDOR_ZHAOXIN1_1 0x746E6543 /* "Cent" */ +#define CPUID_VENDOR_ZHAOXIN1_2 0x48727561 /* "aurH" */ +#define CPUID_VENDOR_ZHAOXIN1_3 0x736C7561 /* "auls" */ + +#define CPUID_VENDOR_ZHAOXIN2_1 0x68532020 /* " Sh" */ +#define CPUID_VENDOR_ZHAOXIN2_2 0x68676E61 /* "angh" */ +#define CPUID_VENDOR_ZHAOXIN2_3 0x20206961 /* "ai " */ + +#define CPUID_VENDOR_ZHAOXIN1 "CentaurHauls" +#define CPUID_VENDOR_ZHAOXIN2 " Shanghai " #define CPUID_VENDOR_HYGON "HygonGenuine" @@ -1132,6 +1141,15 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); #define IS_AMD_CPU(env) ((env)->cpuid_vendor1 == CPUID_VENDOR_AMD_1 && \ (env)->cpuid_vendor2 == CPUID_VENDOR_AMD_2 && \ (env)->cpuid_vendor3 == CPUID_VENDOR_AMD_3) +#define IS_ZHAOXIN1_CPU(env) \ + ((env)->cpuid_vendor1 == CPUID_VENDOR_ZHAOXIN1_1 && \ + (env)->cpuid_vendor2 == CPUID_VENDOR_ZHAOXIN1_2 && \ + (env)->cpuid_vendor3 == CPUID_VENDOR_ZHAOXIN1_3) +#define IS_ZHAOXIN2_CPU(env) \ + ((env)->cpuid_vendor1 == CPUID_VENDOR_ZHAOXIN2_1 && \ + (env)->cpuid_vendor2 == CPUID_VENDOR_ZHAOXIN2_2 && \ + (env)->cpuid_vendor3 == CPUID_VENDOR_ZHAOXIN2_3) +#define IS_ZHAOXIN_CPU(env) (IS_ZHAOXIN1_CPU(env) || IS_ZHAOXIN2_CPU(env)) #define CPUID_MWAIT_IBE (1U << 1) /* Interrupts can exit capability */ #define CPUID_MWAIT_EMX (1U << 0) /* enumeration supported */ From c0799e8b003713e07b546faba600363eccd179ee Mon Sep 17 00:00:00 2001 From: EwanHai Date: Mon, 13 Jan 2025 02:44:11 -0500 Subject: [PATCH 0332/1179] target/i386: Add CPUID leaf 0xC000_0001 EDX definitions Add new CPUID feature flags for various Zhaoxin PadLock extensions. These definitions will be used for Zhaoxin CPU models. Signed-off-by: EwanHai Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250113074413.297793-3-ewanhai-oc@zhaoxin.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 4279cf5cdee7..10ce019e3f82 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1110,6 +1110,27 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); /* CPUID[0x80000007].EDX flags: */ #define CPUID_APM_INVTSC (1U << 8) +/* "rng" RNG present (xstore) */ +#define CPUID_C000_0001_EDX_XSTORE (1U << 2) +/* "rng_en" RNG enabled */ +#define CPUID_C000_0001_EDX_XSTORE_EN (1U << 3) +/* "ace" on-CPU crypto (xcrypt) */ +#define CPUID_C000_0001_EDX_XCRYPT (1U << 6) +/* "ace_en" on-CPU crypto enabled */ +#define CPUID_C000_0001_EDX_XCRYPT_EN (1U << 7) +/* Advanced Cryptography Engine v2 */ +#define CPUID_C000_0001_EDX_ACE2 (1U << 8) +/* ACE v2 enabled */ +#define CPUID_C000_0001_EDX_ACE2_EN (1U << 9) +/* PadLock Hash Engine */ +#define CPUID_C000_0001_EDX_PHE (1U << 10) +/* PHE enabled */ +#define CPUID_C000_0001_EDX_PHE_EN (1U << 11) +/* PadLock Montgomery Multiplier */ +#define CPUID_C000_0001_EDX_PMM (1U << 12) +/* PMM enabled */ +#define CPUID_C000_0001_EDX_PMM_EN (1U << 13) + #define CPUID_VENDOR_SZ 12 #define CPUID_VENDOR_INTEL_1 0x756e6547 /* "Genu" */ From ff04bc1ac478656e5d6a255bf4069edb3f55bc58 Mon Sep 17 00:00:00 2001 From: EwanHai Date: Mon, 13 Jan 2025 02:44:12 -0500 Subject: [PATCH 0333/1179] target/i386: Introduce Zhaoxin Yongfeng CPU model Introduce support for the Zhaoxin Yongfeng CPU model. The Zhaoxin Yongfeng CPU is Zhaoxin's latest server CPU. This new cpu model ensure that QEMU can correctly emulate the Zhaoxin Yongfeng CPU, providing accurate functionality and performance characteristics. Signed-off-by: EwanHai Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250113074413.297793-4-ewanhai-oc@zhaoxin.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 2f9c604552b2..bd407146136f 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -5498,6 +5498,130 @@ static const X86CPUDefinition builtin_x86_defs[] = { .model_id = "AMD EPYC-Genoa Processor", .cache_info = &epyc_genoa_cache_info, }, + { + .name = "YongFeng", + .level = 0x1F, + .vendor = CPUID_VENDOR_ZHAOXIN1, + .family = 7, + .model = 11, + .stepping = 3, + /* missing: CPUID_HT, CPUID_TM, CPUID_PBE */ + .features[FEAT_1_EDX] = + CPUID_SS | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX | + CPUID_ACPI | CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | + CPUID_MCA | CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | + CPUID_CX8 | CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | + CPUID_PSE | CPUID_DE | CPUID_VME | CPUID_FP87, + /* + * missing: CPUID_EXT_OSXSAVE, CPUID_EXT_XTPR, CPUID_EXT_TM2, + * CPUID_EXT_EST, CPUID_EXT_SMX, CPUID_EXT_VMX + */ + .features[FEAT_1_ECX] = + CPUID_EXT_RDRAND | CPUID_EXT_F16C | CPUID_EXT_AVX | + CPUID_EXT_XSAVE | CPUID_EXT_AES | CPUID_EXT_TSC_DEADLINE_TIMER | + CPUID_EXT_POPCNT | CPUID_EXT_MOVBE | CPUID_EXT_X2APIC | + CPUID_EXT_SSE42 | CPUID_EXT_SSE41 | CPUID_EXT_PCID | + CPUID_EXT_CX16 | CPUID_EXT_FMA | CPUID_EXT_SSSE3 | + CPUID_EXT_MONITOR | CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3, + .features[FEAT_7_0_EBX] = + CPUID_7_0_EBX_SHA_NI | CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_ADX | + CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_INVPCID | CPUID_7_0_EBX_BMI2 | + CPUID_7_0_EBX_SMEP | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_BMI1 | + CPUID_7_0_EBX_FSGSBASE, + /* missing: CPUID_7_0_ECX_OSPKE */ + .features[FEAT_7_0_ECX] = + CPUID_7_0_ECX_RDPID | CPUID_7_0_ECX_PKU | CPUID_7_0_ECX_UMIP, + .features[FEAT_7_0_EDX] = + CPUID_7_0_EDX_ARCH_CAPABILITIES | CPUID_7_0_EDX_SPEC_CTRL, + .features[FEAT_8000_0001_EDX] = + CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_PDPE1GB | + CPUID_EXT2_NX | CPUID_EXT2_SYSCALL, + .features[FEAT_8000_0001_ECX] = + CPUID_EXT3_3DNOWPREFETCH | CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM, + .features[FEAT_8000_0007_EDX] = CPUID_APM_INVTSC, + /* + * TODO: When the Linux kernel introduces other existing definitions + * for this leaf, remember to update the definitions here. + */ + .features[FEAT_C000_0001_EDX] = + CPUID_C000_0001_EDX_PMM_EN | CPUID_C000_0001_EDX_PMM | + CPUID_C000_0001_EDX_PHE_EN | CPUID_C000_0001_EDX_PHE | + CPUID_C000_0001_EDX_ACE2 | + CPUID_C000_0001_EDX_XCRYPT_EN | CPUID_C000_0001_EDX_XCRYPT | + CPUID_C000_0001_EDX_XSTORE_EN | CPUID_C000_0001_EDX_XSTORE, + .features[FEAT_XSAVE] = + CPUID_XSAVE_XSAVEOPT, + .features[FEAT_ARCH_CAPABILITIES] = + MSR_ARCH_CAP_RDCL_NO | MSR_ARCH_CAP_SKIP_L1DFL_VMENTRY | + MSR_ARCH_CAP_MDS_NO | MSR_ARCH_CAP_PSCHANGE_MC_NO | + MSR_ARCH_CAP_SSB_NO, + .features[FEAT_VMX_PROCBASED_CTLS] = + VMX_CPU_BASED_VIRTUAL_INTR_PENDING | VMX_CPU_BASED_HLT_EXITING | + VMX_CPU_BASED_USE_TSC_OFFSETING | VMX_CPU_BASED_INVLPG_EXITING | + VMX_CPU_BASED_MWAIT_EXITING | VMX_CPU_BASED_RDPMC_EXITING | + VMX_CPU_BASED_RDTSC_EXITING | VMX_CPU_BASED_CR3_LOAD_EXITING | + VMX_CPU_BASED_CR3_STORE_EXITING | VMX_CPU_BASED_CR8_LOAD_EXITING | + VMX_CPU_BASED_CR8_STORE_EXITING | VMX_CPU_BASED_TPR_SHADOW | + VMX_CPU_BASED_VIRTUAL_NMI_PENDING | VMX_CPU_BASED_MOV_DR_EXITING | + VMX_CPU_BASED_UNCOND_IO_EXITING | VMX_CPU_BASED_USE_IO_BITMAPS | + VMX_CPU_BASED_MONITOR_TRAP_FLAG | VMX_CPU_BASED_USE_MSR_BITMAPS | + VMX_CPU_BASED_MONITOR_EXITING | VMX_CPU_BASED_PAUSE_EXITING | + VMX_CPU_BASED_ACTIVATE_SECONDARY_CONTROLS, + /* + * missing: VMX_SECONDARY_EXEC_PAUSE_LOOP_EXITING, + * VMX_SECONDARY_EXEC_TSC_SCALING + */ + .features[FEAT_VMX_SECONDARY_CTLS] = + VMX_SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES | + VMX_SECONDARY_EXEC_ENABLE_EPT | VMX_SECONDARY_EXEC_DESC | + VMX_SECONDARY_EXEC_RDTSCP | VMX_SECONDARY_EXEC_ENABLE_VPID | + VMX_SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | + VMX_SECONDARY_EXEC_WBINVD_EXITING | + VMX_SECONDARY_EXEC_UNRESTRICTED_GUEST | + VMX_SECONDARY_EXEC_APIC_REGISTER_VIRT | + VMX_SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY | + VMX_SECONDARY_EXEC_RDRAND_EXITING | + VMX_SECONDARY_EXEC_ENABLE_INVPCID | + VMX_SECONDARY_EXEC_ENABLE_VMFUNC | + VMX_SECONDARY_EXEC_SHADOW_VMCS | + VMX_SECONDARY_EXEC_ENABLE_PML, + .features[FEAT_VMX_PINBASED_CTLS] = + VMX_PIN_BASED_EXT_INTR_MASK | VMX_PIN_BASED_NMI_EXITING | + VMX_PIN_BASED_VIRTUAL_NMIS | VMX_PIN_BASED_VMX_PREEMPTION_TIMER | + VMX_PIN_BASED_POSTED_INTR, + .features[FEAT_VMX_EXIT_CTLS] = + VMX_VM_EXIT_SAVE_DEBUG_CONTROLS | VMX_VM_EXIT_HOST_ADDR_SPACE_SIZE | + VMX_VM_EXIT_LOAD_IA32_PERF_GLOBAL_CTRL | + VMX_VM_EXIT_ACK_INTR_ON_EXIT | VMX_VM_EXIT_SAVE_IA32_PAT | + VMX_VM_EXIT_LOAD_IA32_PAT | VMX_VM_EXIT_SAVE_IA32_EFER | + VMX_VM_EXIT_LOAD_IA32_EFER | VMX_VM_EXIT_SAVE_VMX_PREEMPTION_TIMER, + /* missing: VMX_VM_ENTRY_SMM, VMX_VM_ENTRY_DEACT_DUAL_MONITOR */ + .features[FEAT_VMX_ENTRY_CTLS] = + VMX_VM_ENTRY_LOAD_DEBUG_CONTROLS | VMX_VM_ENTRY_IA32E_MODE | + VMX_VM_ENTRY_LOAD_IA32_PERF_GLOBAL_CTRL | + VMX_VM_ENTRY_LOAD_IA32_PAT | VMX_VM_ENTRY_LOAD_IA32_EFER, + /* + * missing: MSR_VMX_MISC_ACTIVITY_SHUTDOWN, + * MSR_VMX_MISC_ACTIVITY_WAIT_SIPI + */ + .features[FEAT_VMX_MISC] = + MSR_VMX_MISC_STORE_LMA | MSR_VMX_MISC_ACTIVITY_HLT | + MSR_VMX_MISC_VMWRITE_VMEXIT, + /* missing: MSR_VMX_EPT_UC */ + .features[FEAT_VMX_EPT_VPID_CAPS] = + MSR_VMX_EPT_EXECONLY | MSR_VMX_EPT_PAGE_WALK_LENGTH_4 | + MSR_VMX_EPT_WB | MSR_VMX_EPT_2MB | MSR_VMX_EPT_1GB | + MSR_VMX_EPT_INVEPT | MSR_VMX_EPT_AD_BITS | + MSR_VMX_EPT_INVEPT_SINGLE_CONTEXT | MSR_VMX_EPT_INVEPT_ALL_CONTEXT | + MSR_VMX_EPT_INVVPID_SINGLE_CONTEXT | MSR_VMX_EPT_INVVPID | + MSR_VMX_EPT_INVVPID_ALL_CONTEXT | MSR_VMX_EPT_INVVPID_SINGLE_ADDR | + MSR_VMX_EPT_INVVPID_SINGLE_CONTEXT_NOGLOBALS, + .features[FEAT_VMX_BASIC] = + MSR_VMX_BASIC_INS_OUTS | MSR_VMX_BASIC_TRUE_CTLS, + .features[FEAT_VMX_VMFUNC] = MSR_VMX_VMFUNC_EPT_SWITCHING, + .xlevel = 0x80000008, + .model_id = "Zhaoxin YongFeng Processor", + }, }; /* From a4e749780bd20593c0c386612a51bf4d64a80132 Mon Sep 17 00:00:00 2001 From: EwanHai Date: Mon, 13 Jan 2025 02:44:13 -0500 Subject: [PATCH 0334/1179] target/i386: Mask CMPLegacy bit in CPUID[0x80000001].ECX for Zhaoxin CPUs Zhaoxin CPUs (including vendors "Shanghai" and "Centaurhauls") handle the CMPLegacy bit similarly to Intel CPUs. Therefore, this commit masks the CMPLegacy bit in CPUID[0x80000001].ECX for Zhaoxin CPUs, just as it is done for Intel CPUs. AMD uses the CMPLegacy bit (CPUID[0x80000001].ECX.bit1) along with other CPUID information to enumerate platform topology (e.g., the number of logical processors per package). However, for Intel and other CPUs that follow Intel's behavior, CPUID[0x80000001].ECX.bit1 is reserved. - Impact on Intel and similar CPUs: This change has no effect on Intel and similar CPUs, as the goal is to accurately emulate CPU CPUID information. - Impact on Linux Guests running on Intel (and similar) vCPUs: During boot, Linux checks if the CPU supports Hyper-Threading. For the Linux kernel before v6.9, if it detects X86_FEATURE_CMP_LEGACY, it assumes Hyper-Threading is not supported. For Intel and similar vCPUs, if the CMPLegacy bit is not masked in CPUID[0x80000001].ECX, Linux will incorrectly assume that Hyper-Threading is not supported, even if the vCPU does support it. Signed-off-by: EwanHai Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250113074413.297793-5-ewanhai-oc@zhaoxin.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index bd407146136f..0cd9b70938d7 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -7804,9 +7804,10 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) /* * The Linux kernel checks for the CMPLegacy bit and * discards multiple thread information if it is set. - * So don't set it here for Intel to make Linux guests happy. + * So don't set it here for Intel (and other processors + * following Intel's behavior) to make Linux guests happy. */ - if (!IS_INTEL_CPU(env)) { + if (!IS_INTEL_CPU(env) && !IS_ZHAOXIN_CPU(env)) { env->features[FEAT_8000_0001_ECX] |= CPUID_EXT3_CMP_LEG; } } From 6a2c7fc29ab6e8fdce4829d3229db534abf923bb Mon Sep 17 00:00:00 2001 From: John Snow Date: Sun, 23 Feb 2025 22:37:32 -0500 Subject: [PATCH 0335/1179] qapi: update pylintrc config If you've got a newer pylint, it'll whine about positional arguments separately from the regular ones. Update the configuration to ignore both categories of warning. Signed-off-by: John Snow Message-ID: <20250224033741.222749-2-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/qapi/pylintrc b/scripts/qapi/pylintrc index c028a1f9f519..d24eece74114 100644 --- a/scripts/qapi/pylintrc +++ b/scripts/qapi/pylintrc @@ -17,6 +17,7 @@ disable=consider-using-f-string, too-many-arguments, too-many-branches, too-many-instance-attributes, + too-many-positional-arguments, too-many-statements, useless-option-value, From ad1e6843632555c771dda6a9425930fa25b71fb3 Mon Sep 17 00:00:00 2001 From: Konstantin Kostiuk Date: Mon, 16 Dec 2024 17:45:52 +0200 Subject: [PATCH 0336/1179] qga: Add log to guest-fsfreeze-thaw command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Daniel P. Berrangé Message-ID: <20241216154552.213961-2-kkostiuk@redhat.com> Signed-off-by: Konstantin Kostiuk --- qga/commands-posix.c | 2 ++ qga/commands-win32.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 6e3c15f53957..12bc086d79ea 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -805,8 +805,10 @@ int64_t qmp_guest_fsfreeze_thaw(Error **errp) int ret; ret = qmp_guest_fsfreeze_do_thaw(errp); + if (ret >= 0) { ga_unset_frozen(ga_state); + slog("guest-fsthaw called"); execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp); } else { ret = 0; diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 99c026c0a02c..749fdf88952f 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -1273,6 +1273,9 @@ int64_t qmp_guest_fsfreeze_thaw(Error **errp) qga_vss_fsfreeze(&i, false, NULL, errp); ga_unset_frozen(ga_state); + + slog("guest-fsthaw called"); + return i; } From 5b567c21c6d517beeb1087399f733662d7e8ff62 Mon Sep 17 00:00:00 2001 From: Michal Privoznik Date: Tue, 7 Jan 2025 15:52:06 +0100 Subject: [PATCH 0337/1179] qga: Invert logic on return value in main() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current logic on return value ('ret' variable) in main() is error prone. The variable is initialized to EXIT_SUCCESS and then set to EXIT_FAILURE on error paths. This makes it very easy to forget to set the variable to indicate error when adding new error path, as is demonstrated by handling of initialize_agent() failure. It's simply lacking setting of the variable. There's just one case where success should be indicated: when dumping the config ('-D' cmd line argument). To resolve this, initialize the variable to failure value and set it explicitly to success value in that one specific case. Signed-off-by: Michal Privoznik Reviewed-by: Ján Tomko Reviewed-by: Konstantin Kostiuk Message-ID: <8a28265f50177a8dc4c10fcf4146e85a7fd748ee.1736261360.git.mprivozn@redhat.com> Signed-off-by: Konstantin Kostiuk --- qga/main.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qga/main.c b/qga/main.c index 531853e13d9b..eccfa3387135 100644 --- a/qga/main.c +++ b/qga/main.c @@ -1579,7 +1579,7 @@ static void stop_agent(GAState *s, bool requested) int main(int argc, char **argv) { - int ret = EXIT_SUCCESS; + int ret = EXIT_FAILURE; GAState *s; GAConfig *config = g_new0(GAConfig, 1); int socket_activation; @@ -1607,7 +1607,6 @@ int main(int argc, char **argv) socket_activation = check_socket_activation(); if (socket_activation > 1) { g_critical("qemu-ga only supports listening on one socket"); - ret = EXIT_FAILURE; goto end; } if (socket_activation) { @@ -1631,7 +1630,6 @@ int main(int argc, char **argv) if (!config->method) { g_critical("unsupported listen fd type"); - ret = EXIT_FAILURE; goto end; } } else if (config->channel_path == NULL) { @@ -1643,13 +1641,13 @@ int main(int argc, char **argv) config->channel_path = g_strdup(QGA_SERIAL_PATH_DEFAULT); } else { g_critical("must specify a path for this channel"); - ret = EXIT_FAILURE; goto end; } } if (config->dumpconf) { config_dump(config); + ret = EXIT_SUCCESS; goto end; } @@ -1664,6 +1662,7 @@ int main(int argc, char **argv) SERVICE_TABLE_ENTRY service_table[] = { { (char *)QGA_SERVICE_NAME, service_main }, { NULL, NULL } }; StartServiceCtrlDispatcher(service_table); + ret = EXIT_SUCCESS; } else { ret = run_agent(s); } From c6f5dd7ac8ef62dcdec4cdeda1467c658161afff Mon Sep 17 00:00:00 2001 From: Michal Privoznik Date: Tue, 7 Jan 2025 15:52:07 +0100 Subject: [PATCH 0338/1179] qga: Don't daemonize before channel is initialized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the agent is set to daemonize but for whatever reason fails to init the channel, the error message is lost. Worse, the agent daemonizes needlessly and returns success. For instance: # qemu-ga -m virtio-serial \ -p /dev/nonexistent_device \ -f /run/qemu-ga.pid \ -t /run \ -d # echo $? 0 This makes it needlessly hard for init scripts to detect a failure in qemu-ga startup. Though, they shouldn't pass '-d' in the first place. Let's open the channel first and only after that become a daemon. Related bug: https://bugs.gentoo.org/810628 Signed-off-by: Michal Privoznik Reviewed-by: Ján Tomko Reviewed-by: Konstantin Kostiuk Message-ID: <7a42b0cbda5c7e01cf76bc1b29a1210cd018fa78.1736261360.git.mprivozn@redhat.com> Signed-off-by: Konstantin Kostiuk --- qga/main.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/qga/main.c b/qga/main.c index eccfa3387135..72c39b042f7c 100644 --- a/qga/main.c +++ b/qga/main.c @@ -1430,7 +1430,6 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation) if (config->daemonize) { /* delay opening/locking of pidfile till filesystems are unfrozen */ s->deferred_options.pid_filepath = config->pid_filepath; - become_daemon(NULL); } if (config->log_filepath) { /* delay opening the log file till filesystems are unfrozen */ @@ -1438,9 +1437,6 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation) } ga_disable_logging(s); } else { - if (config->daemonize) { - become_daemon(config->pid_filepath); - } if (config->log_filepath) { FILE *log_file = ga_open_logfile(config->log_filepath); if (!log_file) { @@ -1487,6 +1483,20 @@ static GAState *initialize_agent(GAConfig *config, int socket_activation) ga_apply_command_filters(s); + if (!channel_init(s, s->config->method, s->config->channel_path, + s->socket_activation ? FIRST_SOCKET_ACTIVATION_FD : -1)) { + g_critical("failed to initialize guest agent channel"); + return NULL; + } + + if (config->daemonize) { + if (ga_is_frozen(s)) { + become_daemon(NULL); + } else { + become_daemon(config->pid_filepath); + } + } + ga_state = s; return s; } @@ -1513,8 +1523,9 @@ static void cleanup_agent(GAState *s) static int run_agent_once(GAState *s) { - if (!channel_init(s, s->config->method, s->config->channel_path, - s->socket_activation ? FIRST_SOCKET_ACTIVATION_FD : -1)) { + if (!s->channel && + channel_init(s, s->config->method, s->config->channel_path, + s->socket_activation ? FIRST_SOCKET_ACTIVATION_FD : -1)) { g_critical("failed to initialize guest agent channel"); return EXIT_FAILURE; } @@ -1523,6 +1534,7 @@ static int run_agent_once(GAState *s) if (s->channel) { ga_channel_free(s->channel); + s->channel = NULL; } return EXIT_SUCCESS; From 6ccca4b6bb9f994cc04e71004e1767a3476d2b23 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:07 +0100 Subject: [PATCH 0339/1179] hw/nvme: rework csi handling The controller incorrectly allows a zoned namespace to be attached even if CS.CSS is configured to only support the NVM command set for I/O queues. Rework handling of namespace command sets in general by attaching supported namespaces when the controller is started instead of, like now, statically when realized. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 213 ++++++++++++++++++++++++------------------- hw/nvme/ns.c | 14 --- hw/nvme/nvme.h | 5 +- include/block/nvme.h | 10 +- 4 files changed, 131 insertions(+), 111 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 86e1c48fab82..21496c6b6b81 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -277,15 +277,14 @@ static const uint32_t nvme_cse_acs_default[256] = { [NVME_ADM_CMD_SET_FEATURES] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_GET_FEATURES] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_ASYNC_EV_REQ] = NVME_CMD_EFF_CSUPP, - [NVME_ADM_CMD_NS_ATTACHMENT] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC, + [NVME_ADM_CMD_NS_ATTACHMENT] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC | + NVME_CMD_EFF_CCC, [NVME_ADM_CMD_FORMAT_NVM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_ADM_CMD_DIRECTIVE_RECV] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_DIRECTIVE_SEND] = NVME_CMD_EFF_CSUPP, }; -static const uint32_t nvme_cse_iocs_none[256]; - -static const uint32_t nvme_cse_iocs_nvm[256] = { +static const uint32_t nvme_cse_iocs_nvm_default[256] = { [NVME_CMD_FLUSH] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_WRITE_ZEROES] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_WRITE] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, @@ -298,7 +297,7 @@ static const uint32_t nvme_cse_iocs_nvm[256] = { [NVME_CMD_IO_MGMT_SEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, }; -static const uint32_t nvme_cse_iocs_zoned[256] = { +static const uint32_t nvme_cse_iocs_zoned_default[256] = { [NVME_CMD_FLUSH] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_WRITE_ZEROES] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_WRITE] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, @@ -307,6 +306,9 @@ static const uint32_t nvme_cse_iocs_zoned[256] = { [NVME_CMD_VERIFY] = NVME_CMD_EFF_CSUPP, [NVME_CMD_COPY] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_COMPARE] = NVME_CMD_EFF_CSUPP, + [NVME_CMD_IO_MGMT_RECV] = NVME_CMD_EFF_CSUPP, + [NVME_CMD_IO_MGMT_SEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, + [NVME_CMD_ZONE_APPEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_ZONE_MGMT_SEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_ZONE_MGMT_RECV] = NVME_CMD_EFF_CSUPP, @@ -4603,6 +4605,61 @@ static uint16_t nvme_io_mgmt_send(NvmeCtrl *n, NvmeRequest *req) }; } +static uint16_t __nvme_io_cmd_nvm(NvmeCtrl *n, NvmeRequest *req) +{ + switch (req->cmd.opcode) { + case NVME_CMD_WRITE: + return nvme_write(n, req); + case NVME_CMD_READ: + return nvme_read(n, req); + case NVME_CMD_COMPARE: + return nvme_compare(n, req); + case NVME_CMD_WRITE_ZEROES: + return nvme_write_zeroes(n, req); + case NVME_CMD_DSM: + return nvme_dsm(n, req); + case NVME_CMD_VERIFY: + return nvme_verify(n, req); + case NVME_CMD_COPY: + return nvme_copy(n, req); + case NVME_CMD_IO_MGMT_RECV: + return nvme_io_mgmt_recv(n, req); + case NVME_CMD_IO_MGMT_SEND: + return nvme_io_mgmt_send(n, req); + } + + g_assert_not_reached(); +} + +static uint16_t nvme_io_cmd_nvm(NvmeCtrl *n, NvmeRequest *req) +{ + if (!(n->cse.iocs.nvm[req->cmd.opcode] & NVME_CMD_EFF_CSUPP)) { + trace_pci_nvme_err_invalid_opc(req->cmd.opcode); + return NVME_INVALID_OPCODE | NVME_DNR; + } + + return __nvme_io_cmd_nvm(n, req); +} + +static uint16_t nvme_io_cmd_zoned(NvmeCtrl *n, NvmeRequest *req) +{ + if (!(n->cse.iocs.zoned[req->cmd.opcode] & NVME_CMD_EFF_CSUPP)) { + trace_pci_nvme_err_invalid_opc(req->cmd.opcode); + return NVME_INVALID_OPCODE | NVME_DNR; + } + + switch (req->cmd.opcode) { + case NVME_CMD_ZONE_APPEND: + return nvme_zone_append(n, req); + case NVME_CMD_ZONE_MGMT_SEND: + return nvme_zone_mgmt_send(n, req); + case NVME_CMD_ZONE_MGMT_RECV: + return nvme_zone_mgmt_recv(n, req); + } + + return __nvme_io_cmd_nvm(n, req); +} + static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeRequest *req) { NvmeNamespace *ns; @@ -4644,11 +4701,6 @@ static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeRequest *req) return NVME_INVALID_FIELD | NVME_DNR; } - if (!(ns->iocs[req->cmd.opcode] & NVME_CMD_EFF_CSUPP)) { - trace_pci_nvme_err_invalid_opc(req->cmd.opcode); - return NVME_INVALID_OPCODE | NVME_DNR; - } - if (ns->status) { return ns->status; } @@ -4659,36 +4711,14 @@ static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeRequest *req) req->ns = ns; - switch (req->cmd.opcode) { - case NVME_CMD_WRITE_ZEROES: - return nvme_write_zeroes(n, req); - case NVME_CMD_ZONE_APPEND: - return nvme_zone_append(n, req); - case NVME_CMD_WRITE: - return nvme_write(n, req); - case NVME_CMD_READ: - return nvme_read(n, req); - case NVME_CMD_COMPARE: - return nvme_compare(n, req); - case NVME_CMD_DSM: - return nvme_dsm(n, req); - case NVME_CMD_VERIFY: - return nvme_verify(n, req); - case NVME_CMD_COPY: - return nvme_copy(n, req); - case NVME_CMD_ZONE_MGMT_SEND: - return nvme_zone_mgmt_send(n, req); - case NVME_CMD_ZONE_MGMT_RECV: - return nvme_zone_mgmt_recv(n, req); - case NVME_CMD_IO_MGMT_RECV: - return nvme_io_mgmt_recv(n, req); - case NVME_CMD_IO_MGMT_SEND: - return nvme_io_mgmt_send(n, req); - default: - g_assert_not_reached(); + switch (ns->csi) { + case NVME_CSI_NVM: + return nvme_io_cmd_nvm(n, req); + case NVME_CSI_ZONED: + return nvme_io_cmd_zoned(n, req); } - return NVME_INVALID_OPCODE | NVME_DNR; + g_assert_not_reached(); } static void nvme_cq_notifier(EventNotifier *e) @@ -5147,7 +5177,7 @@ static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len, uint64_t off, NvmeRequest *req) { NvmeEffectsLog log = {}; - const uint32_t *src_iocs = NULL; + const uint32_t *iocs = NULL; uint32_t trans_len; if (off >= sizeof(log)) { @@ -5157,25 +5187,26 @@ static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len, switch (NVME_CC_CSS(ldl_le_p(&n->bar.cc))) { case NVME_CC_CSS_NVM: - src_iocs = nvme_cse_iocs_nvm; - /* fall through */ - case NVME_CC_CSS_ADMIN_ONLY: + iocs = n->cse.iocs.nvm; break; - case NVME_CC_CSS_CSI: + + case NVME_CC_CSS_ALL: switch (csi) { case NVME_CSI_NVM: - src_iocs = nvme_cse_iocs_nvm; + iocs = n->cse.iocs.nvm; break; case NVME_CSI_ZONED: - src_iocs = nvme_cse_iocs_zoned; + iocs = n->cse.iocs.zoned; break; } + + break; } memcpy(log.acs, n->cse.acs, sizeof(log.acs)); - if (src_iocs) { - memcpy(log.iocs, src_iocs, sizeof(log.iocs)); + if (iocs) { + memcpy(log.iocs, iocs, sizeof(log.iocs)); } trans_len = MIN(sizeof(log) - off, buf_len); @@ -6718,25 +6749,29 @@ static void nvme_update_dsm_limits(NvmeCtrl *n, NvmeNamespace *ns) } } -static void nvme_select_iocs_ns(NvmeCtrl *n, NvmeNamespace *ns) +static bool nvme_csi_supported(NvmeCtrl *n, uint8_t csi) { - uint32_t cc = ldl_le_p(&n->bar.cc); + uint32_t cc; - ns->iocs = nvme_cse_iocs_none; - switch (ns->csi) { + switch (csi) { case NVME_CSI_NVM: - if (NVME_CC_CSS(cc) != NVME_CC_CSS_ADMIN_ONLY) { - ns->iocs = nvme_cse_iocs_nvm; - } - break; + return true; + case NVME_CSI_ZONED: - if (NVME_CC_CSS(cc) == NVME_CC_CSS_CSI) { - ns->iocs = nvme_cse_iocs_zoned; - } else if (NVME_CC_CSS(cc) == NVME_CC_CSS_NVM) { - ns->iocs = nvme_cse_iocs_nvm; - } - break; + cc = ldl_le_p(&n->bar.cc); + + return NVME_CC_CSS(cc) == NVME_CC_CSS_ALL; } + + g_assert_not_reached(); +} + +static void nvme_detach_ns(NvmeCtrl *n, NvmeNamespace *ns) +{ + assert(ns->attached > 0); + + n->namespaces[ns->params.nsid] = NULL; + ns->attached--; } static uint16_t nvme_ns_attachment(NvmeCtrl *n, NvmeRequest *req) @@ -6781,7 +6816,7 @@ static uint16_t nvme_ns_attachment(NvmeCtrl *n, NvmeRequest *req) switch (sel) { case NVME_NS_ATTACHMENT_ATTACH: - if (nvme_ns(ctrl, nsid)) { + if (nvme_ns(n, nsid)) { return NVME_NS_ALREADY_ATTACHED | NVME_DNR; } @@ -6789,19 +6824,17 @@ static uint16_t nvme_ns_attachment(NvmeCtrl *n, NvmeRequest *req) return NVME_NS_PRIVATE | NVME_DNR; } + if (!nvme_csi_supported(n, ns->csi)) { + return NVME_IOCS_NOT_SUPPORTED | NVME_DNR; + } + nvme_attach_ns(ctrl, ns); - nvme_select_iocs_ns(ctrl, ns); + nvme_update_dsm_limits(ctrl, ns); break; case NVME_NS_ATTACHMENT_DETACH: - if (!nvme_ns(ctrl, nsid)) { - return NVME_NS_NOT_ATTACHED | NVME_DNR; - } - - ctrl->namespaces[nsid] = NULL; - ns->attached--; - + nvme_detach_ns(ctrl, ns); nvme_update_dsm_limits(ctrl, NULL); break; @@ -7652,21 +7685,6 @@ static void nvme_ctrl_shutdown(NvmeCtrl *n) } } -static void nvme_select_iocs(NvmeCtrl *n) -{ - NvmeNamespace *ns; - int i; - - for (i = 1; i <= NVME_MAX_NAMESPACES; i++) { - ns = nvme_ns(n, i); - if (!ns) { - continue; - } - - nvme_select_iocs_ns(n, ns); - } -} - static int nvme_start_ctrl(NvmeCtrl *n) { uint64_t cap = ldq_le_p(&n->bar.cap); @@ -7733,7 +7751,18 @@ static int nvme_start_ctrl(NvmeCtrl *n) nvme_set_timestamp(n, 0ULL); - nvme_select_iocs(n); + /* verify that the command sets of attached namespaces are supported */ + for (int i = 1; i <= NVME_MAX_NAMESPACES; i++) { + NvmeNamespace *ns = nvme_subsys_ns(n->subsys, i); + + if (ns && nvme_csi_supported(n, ns->csi) && !ns->params.detached) { + if (!ns->attached || ns->params.shared) { + nvme_attach_ns(n, ns); + } + } + } + + nvme_update_dsm_limits(n, NULL); return 0; } @@ -8748,6 +8777,9 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) uint16_t oacs; memcpy(n->cse.acs, nvme_cse_acs_default, sizeof(n->cse.acs)); + memcpy(n->cse.iocs.nvm, nvme_cse_iocs_nvm_default, sizeof(n->cse.iocs.nvm)); + memcpy(n->cse.iocs.zoned, nvme_cse_iocs_zoned_default, + sizeof(n->cse.iocs.zoned)); id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID)); id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID)); @@ -8859,9 +8891,8 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) NVME_CAP_SET_MQES(cap, n->params.mqes); NVME_CAP_SET_CQR(cap, 1); NVME_CAP_SET_TO(cap, 0xf); - NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_NVM); - NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_CSI_SUPP); - NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_ADMIN_ONLY); + NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_NCSS); + NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_IOCSS); NVME_CAP_SET_MPSMAX(cap, 4); NVME_CAP_SET_CMBS(cap, n->params.cmb_size_mb ? 1 : 0); NVME_CAP_SET_PMRS(cap, n->pmr.dev ? 1 : 0); @@ -8908,8 +8939,6 @@ void nvme_attach_ns(NvmeCtrl *n, NvmeNamespace *ns) n->namespaces[nsid] = ns; ns->attached++; - - nvme_update_dsm_limits(n, ns); } static void nvme_realize(PCIDevice *pci_dev, Error **errp) @@ -8965,7 +8994,7 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp) return; } - nvme_attach_ns(n, ns); + n->subsys->namespaces[ns->params.nsid] = ns; } } diff --git a/hw/nvme/ns.c b/hw/nvme/ns.c index 94cabc6a5b8d..98c1e75a5d29 100644 --- a/hw/nvme/ns.c +++ b/hw/nvme/ns.c @@ -763,20 +763,6 @@ static void nvme_ns_realize(DeviceState *dev, Error **errp) ns->id_ns.endgid = cpu_to_le16(0x1); ns->id_ns_ind.endgrpid = cpu_to_le16(0x1); - - if (ns->params.detached) { - return; - } - - if (ns->params.shared) { - for (i = 0; i < ARRAY_SIZE(subsys->ctrls); i++) { - NvmeCtrl *ctrl = subsys->ctrls[i]; - - if (ctrl && ctrl != SUBSYS_SLOT_RSVD) { - nvme_attach_ns(ctrl, ns); - } - } - } } static const Property nvme_ns_props[] = { diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h index b8d063a027a9..6f782ba18826 100644 --- a/hw/nvme/nvme.h +++ b/hw/nvme/nvme.h @@ -237,7 +237,6 @@ typedef struct NvmeNamespace { NvmeLBAF lbaf; unsigned int nlbaf; size_t lbasz; - const uint32_t *iocs; uint8_t csi; uint16_t status; int attached; @@ -587,6 +586,10 @@ typedef struct NvmeCtrl { struct { uint32_t acs[256]; + struct { + uint32_t nvm[256]; + uint32_t zoned[256]; + } iocs; } cse; struct { diff --git a/include/block/nvme.h b/include/block/nvme.h index 763b2b2f0ec7..366739f79edf 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -142,9 +142,9 @@ enum NvmeCapMask { ((cap) |= (uint64_t)((val) & CAP_CMBS_MASK) << CAP_CMBS_SHIFT) enum NvmeCapCss { - NVME_CAP_CSS_NVM = 1 << 0, - NVME_CAP_CSS_CSI_SUPP = 1 << 6, - NVME_CAP_CSS_ADMIN_ONLY = 1 << 7, + NVME_CAP_CSS_NCSS = 1 << 0, + NVME_CAP_CSS_IOCSS = 1 << 6, + NVME_CAP_CSS_NOIOCSS = 1 << 7, }; enum NvmeCcShift { @@ -177,7 +177,7 @@ enum NvmeCcMask { enum NvmeCcCss { NVME_CC_CSS_NVM = 0x0, - NVME_CC_CSS_CSI = 0x6, + NVME_CC_CSS_ALL = 0x6, NVME_CC_CSS_ADMIN_ONLY = 0x7, }; @@ -938,6 +938,8 @@ enum NvmeStatusCodes { NVME_INVALID_SEC_CTRL_STATE = 0x0120, NVME_INVALID_NUM_RESOURCES = 0x0121, NVME_INVALID_RESOURCE_ID = 0x0122, + NVME_IOCS_NOT_SUPPORTED = 0x0129, + NVME_IOCS_NOT_ENABLED = 0x012a, NVME_IOCS_COMBINATION_REJECTED = 0x012b, NVME_INVALID_IOCS = 0x012c, NVME_CONFLICTING_ATTRS = 0x0180, From 304babd9401d8ce8fe139a70fe464332eef2cee0 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:08 +0100 Subject: [PATCH 0340/1179] hw/nvme: only set command abort requested when cancelled due to Abort The Command Abort Requested status code should only be set if the command was explicitly cancelled due to an Abort command. Or, in the case the cancel was due to Submission Queue deletion, set the status code to Command Aborted due to SQ Deletion. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 21496c6b6b81..07cd63298526 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -1783,10 +1783,6 @@ static void nvme_aio_err(NvmeRequest *req, int ret) break; } - if (ret == -ECANCELED) { - status = NVME_CMD_ABORT_REQ; - } - trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), status); error_setg_errno(&local_err, -ret, "aio failed"); @@ -4827,6 +4823,7 @@ static uint16_t nvme_del_sq(NvmeCtrl *n, NvmeRequest *req) while (!QTAILQ_EMPTY(&sq->out_req_list)) { r = QTAILQ_FIRST(&sq->out_req_list); assert(r->aiocb); + r->status = NVME_CMD_ABORT_SQ_DEL; blk_aio_cancel(r->aiocb); } @@ -6137,6 +6134,7 @@ static uint16_t nvme_abort(NvmeCtrl *n, NvmeRequest *req) QTAILQ_FOREACH_SAFE(r, &sq->out_req_list, entry, next) { if (r->cqe.cid == cid) { if (r->aiocb) { + r->status = NVME_CMD_ABORT_REQ; blk_aio_cancel_async(r->aiocb); } break; From 6fc39228ffe9d54388f6d1080b502634df13bb72 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:09 +0100 Subject: [PATCH 0341/1179] hw/nvme: set error status code explicitly for misc commands The nvme_aio_err() does not handle Verify, Compare, Copy and other misc commands and defaults to setting the error status code to Internal Device Error. For some of these commands, we know better, so set it explicitly. For the commands using the nvme_misc_cb() callback (Copy, Flush, ...), if no status code has explicitly been set by the lower handlers, default to Internal Device Error as previously. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 28 ++++++++++++++++++++++------ include/block/nvme.h | 1 + 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 07cd63298526..b7222fd9ac02 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -1771,7 +1771,6 @@ static void nvme_aio_err(NvmeRequest *req, int ret) case NVME_CMD_READ: status = NVME_UNRECOVERED_READ; break; - case NVME_CMD_FLUSH: case NVME_CMD_WRITE: case NVME_CMD_WRITE_ZEROES: case NVME_CMD_ZONE_APPEND: @@ -2157,11 +2156,16 @@ static inline bool nvme_is_write(NvmeRequest *req) static void nvme_misc_cb(void *opaque, int ret) { NvmeRequest *req = opaque; + uint16_t cid = nvme_cid(req); - trace_pci_nvme_misc_cb(nvme_cid(req)); + trace_pci_nvme_misc_cb(cid); if (ret) { - nvme_aio_err(req, ret); + if (!req->status) { + req->status = NVME_INTERNAL_DEV_ERROR; + } + + trace_pci_nvme_err_aio(cid, strerror(-ret), req->status); } nvme_enqueue_req_completion(nvme_cq(req), req); @@ -2264,7 +2268,10 @@ static void nvme_verify_cb(void *opaque, int ret) if (ret) { block_acct_failed(stats, acct); - nvme_aio_err(req, ret); + req->status = NVME_UNRECOVERED_READ; + + trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), req->status); + goto out; } @@ -2363,7 +2370,10 @@ static void nvme_compare_mdata_cb(void *opaque, int ret) if (ret) { block_acct_failed(stats, acct); - nvme_aio_err(req, ret); + req->status = NVME_UNRECOVERED_READ; + + trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), req->status); + goto out; } @@ -2445,7 +2455,10 @@ static void nvme_compare_data_cb(void *opaque, int ret) if (ret) { block_acct_failed(stats, acct); - nvme_aio_err(req, ret); + req->status = NVME_UNRECOVERED_READ; + + trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), req->status); + goto out; } @@ -2924,6 +2937,7 @@ static void nvme_copy_out_completed_cb(void *opaque, int ret) if (ret < 0) { iocb->ret = ret; + req->status = NVME_WRITE_FAULT; goto out; } else if (iocb->ret < 0) { goto out; @@ -2988,6 +3002,7 @@ static void nvme_copy_in_completed_cb(void *opaque, int ret) if (ret < 0) { iocb->ret = ret; + req->status = NVME_UNRECOVERED_READ; goto out; } else if (iocb->ret < 0) { goto out; @@ -3510,6 +3525,7 @@ static void nvme_flush_ns_cb(void *opaque, int ret) if (ret < 0) { iocb->ret = ret; + iocb->req->status = NVME_WRITE_FAULT; goto out; } else if (iocb->ret < 0) { goto out; diff --git a/include/block/nvme.h b/include/block/nvme.h index 366739f79edf..358e516e38b0 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -906,6 +906,7 @@ enum NvmeStatusCodes { NVME_SGL_DESCR_TYPE_INVALID = 0x0011, NVME_INVALID_USE_OF_CMB = 0x0012, NVME_INVALID_PRP_OFFSET = 0x0013, + NVME_COMMAND_INTERRUPTED = 0x0021, NVME_FDP_DISABLED = 0x0029, NVME_INVALID_PHID_LIST = 0x002a, NVME_LBA_RANGE = 0x0080, From cad58ada8f104bf342097a7a683ef594ac949c8d Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 16 Dec 2024 13:53:10 +0100 Subject: [PATCH 0342/1179] hw/nvme: remove nvme_aio_err() nvme_rw_complete_cb() is the only remaining user of nvme_aio_err(), so open code the status code setting instead. Reviewed-by: Jesper Wendel Devantier Signed-off-by: Klaus Jensen --- hw/nvme/ctrl.c | 60 +++++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index b7222fd9ac02..e62c6a358828 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -1762,42 +1762,6 @@ static uint16_t nvme_check_dulbe(NvmeNamespace *ns, uint64_t slba, return NVME_SUCCESS; } -static void nvme_aio_err(NvmeRequest *req, int ret) -{ - uint16_t status = NVME_SUCCESS; - Error *local_err = NULL; - - switch (req->cmd.opcode) { - case NVME_CMD_READ: - status = NVME_UNRECOVERED_READ; - break; - case NVME_CMD_WRITE: - case NVME_CMD_WRITE_ZEROES: - case NVME_CMD_ZONE_APPEND: - case NVME_CMD_COPY: - status = NVME_WRITE_FAULT; - break; - default: - status = NVME_INTERNAL_DEV_ERROR; - break; - } - - trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), status); - - error_setg_errno(&local_err, -ret, "aio failed"); - error_report_err(local_err); - - /* - * Set the command status code to the first encountered error but allow a - * subsequent Internal Device Error to trump it. - */ - if (req->status && status != NVME_INTERNAL_DEV_ERROR) { - return; - } - - req->status = status; -} - static inline uint32_t nvme_zone_idx(NvmeNamespace *ns, uint64_t slba) { return ns->zone_size_log2 > 0 ? slba >> ns->zone_size_log2 : @@ -2182,8 +2146,30 @@ void nvme_rw_complete_cb(void *opaque, int ret) trace_pci_nvme_rw_complete_cb(nvme_cid(req), blk_name(blk)); if (ret) { + Error *err = NULL; + block_acct_failed(stats, acct); - nvme_aio_err(req, ret); + + switch (req->cmd.opcode) { + case NVME_CMD_READ: + req->status = NVME_UNRECOVERED_READ; + break; + + case NVME_CMD_WRITE: + case NVME_CMD_WRITE_ZEROES: + case NVME_CMD_ZONE_APPEND: + req->status = NVME_WRITE_FAULT; + break; + + default: + req->status = NVME_INTERNAL_DEV_ERROR; + break; + } + + trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), req->status); + + error_setg_errno(&err, -ret, "aio failed"); + error_report_err(err); } else { block_acct_done(stats, acct); } From fa4d79c64dae03ffa269e42e21822453856618b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Mon, 7 Oct 2024 15:39:51 +0100 Subject: [PATCH 0343/1179] scripts: mandate that new files have SPDX-License-Identifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Going forward we want all newly created source files to have an SPDX-License-Identifier tag present. Initially mandate this for C, Python, Perl, Shell source files, as well as JSON (QAPI) and Makefiles, while encouraging users to consider it for other file types. Reviewed-by: Brian Cain Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Daniel P. Berrangé --- scripts/checkpatch.pl | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 06d07e6c225c..01f25aa88df0 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -1378,6 +1378,8 @@ sub process { my $in_imported_file = 0; my $in_no_imported_file = 0; my $non_utf8_charset = 0; + my $expect_spdx = 0; + my $expect_spdx_file; our @report = (); our $cnt_lines = 0; @@ -1615,6 +1617,34 @@ sub process { WARN("added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); } +# All new files should have a SPDX-License-Identifier tag + if ($line =~ /^new file mode\s*\d+\s*$/) { + if ($expect_spdx) { + if ($expect_spdx_file =~ + /\.(c|h|py|pl|sh|json|inc|Makefile)$/) { + # source code files MUST have SPDX license declared + ERROR("New file '$expect_spdx_file' requires " . + "'SPDX-License-Identifer'"); + } else { + # Other files MAY have SPDX license if appropriate + WARNING("Does new file '$expect_spdx_file' need " . + "'SPDX-License-Identifer'?"); + } + } + $expect_spdx = 1; + $expect_spdx_file = undef; + } elsif ($expect_spdx) { + $expect_spdx_file = $realfile unless + defined $expect_spdx_file; + + # SPDX tags may occurr in comments which were + # stripped from '$line', so use '$rawline' + if ($rawline =~ /SPDX-License-Identifier/) { + $expect_spdx = 0; + $expect_spdx_file = undef; + } + } + # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("patch seems to be corrupt (line wrapped?)\n" . From 2b96c1a4931e3b4e0294761c16759bcbb2652df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Mon, 7 Oct 2024 15:40:28 +0100 Subject: [PATCH 0344/1179] scripts: validate SPDX license choices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We expect all new code to be contributed with the "GPL-2.0-or-later" license tag. Divergence is permitted if the new file is derived from pre-existing code under a different license, whether from elsewhere in QEMU codebase, or outside. Issue a warning if the declared license is not "GPL-2.0-or-later", and an error if the license is not one of the handful of the expected licenses to prevent unintended proliferation. The warning asks users to explain their unusual choice of license in the commit message. Reviewed-by: Peter Maydell Signed-off-by: Daniel P. Berrangé --- scripts/checkpatch.pl | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 01f25aa88df0..8995d2c39110 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -1353,6 +1353,70 @@ sub checkfilename { } } +sub checkspdx { + my ($file, $expr) = @_; + + # Imported Linux headers probably have SPDX tags, but if they + # don't we're not requiring contributors to fix this, as these + # files are not expected to be modified locally in QEMU. + # Also don't accidentally detect own checking code. + if ($file =~ m,include/standard-headers, || + $file =~ m,linux-headers, || + $file =~ m,checkpatch.pl,) { + return; + } + + my $origexpr = $expr; + + # Flatten sub-expressions + $expr =~ s/\(|\)/ /g; + $expr =~ s/OR|AND/ /g; + + # Merge WITH exceptions to the license + $expr =~ s/\s+WITH\s+/-WITH-/g; + + # Cull more leading/trailing whitespace + $expr =~ s/^\s*//g; + $expr =~ s/\s*$//g; + + my @bits = split / +/, $expr; + + my $prefer = "GPL-2.0-or-later"; + my @valid = qw( + GPL-2.0-only + LGPL-2.1-only + LGPL-2.1-or-later + BSD-2-Clause + BSD-3-Clause + MIT + ); + + my $nonpreferred = 0; + my @unknown = (); + foreach my $bit (@bits) { + if ($bit eq $prefer) { + next; + } + if (grep /^$bit$/, @valid) { + $nonpreferred = 1; + } else { + push @unknown, $bit; + } + } + if (@unknown) { + ERROR("Saw unacceptable licenses '" . join(',', @unknown) . + "', valid choices for QEMU are:\n" . join("\n", $prefer, @valid)); + } + + if ($nonpreferred) { + WARN("Saw acceptable license '$origexpr' but note '$prefer' is " . + "preferred for new files unless the code is derived from a " . + "source file with an existing declared license that must be " . + "retained. Please explain the license choice in the commit " . + "message."); + } +} + sub process { my $filename = shift; @@ -1645,6 +1709,11 @@ sub process { } } +# Check SPDX-License-Identifier references a permitted license + if ($rawline =~ m,SPDX-License-Identifier: (.*?)(\*/)?\s*$,) { + &checkspdx($realfile, $1); + } + # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("patch seems to be corrupt (line wrapped?)\n" . From 6b7521818b26134726b3494cd06f04e30659ce2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Mon, 7 Oct 2024 16:21:54 +0100 Subject: [PATCH 0345/1179] scripts: forbid use of arbitrary SPDX tags besides license identifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While SPDX-License-Identifier is a well known SPDX tag, there are a great many more besides that[1]. These are mostly focused on making machine readable metadata available to the 'reuse' tool and similar. They cover concepts like author names, copyright owners, and much more. It is even possible to define source file line groups and apply different SPDX tags to regions of code within a file. At this time we're only interested in adopting SPDX for recording the file global licensing info, so detect & reject any other SPDX metadata. If we want to explicitly collect extra data in SPDX format, we can evaluate each data item on its merits when someone wants to propose it at a later date. [1] https://spdx.github.io/spdx-spec/v2.2.2/file-tags/ https://spdx.github.io/spdx-spec/v2.2.2/file-information/ Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Daniel P. Berrangé --- scripts/checkpatch.pl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 8995d2c39110..83b59fb4436d 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -1714,6 +1714,18 @@ sub process { &checkspdx($realfile, $1); } + if ($rawline =~ m,(SPDX-[a-zA-Z0-9-_]+):,) { + my $tag = $1; + my @permitted = qw( + SPDX-License-Identifier + ); + + unless (grep { /^$tag$/ } @permitted) { + ERROR("Tag $tag not permitted in QEMU code, valid " . + "choices are: " . join(", ", @permitted)); + } + } + # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("patch seems to be corrupt (line wrapped?)\n" . From 661c2e1ab29cd9c4d268ae3f44712e8d421c0e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 3 Mar 2025 18:25:08 +0100 Subject: [PATCH 0346/1179] scripts/checkpatch: Fix a typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running checkpatch.pl on a commit adding a file without SPDX tag we get: Undefined subroutine &main::WARNING called at ./scripts/checkpatch.pl line 1694. The WARNING level is reported by the WARN() method. Fix the typo. Fixes: fa4d79c64da ("scripts: mandate that new files have SPDX-License-Identifier") Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Markus Armbruster Message-ID: <20250303172508.93234-1-philmd@linaro.org> Signed-off-by: Stefan Hajnoczi --- scripts/checkpatch.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 83b59fb4436d..6ae9d7febee8 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -1691,8 +1691,8 @@ sub process { "'SPDX-License-Identifer'"); } else { # Other files MAY have SPDX license if appropriate - WARNING("Does new file '$expect_spdx_file' need " . - "'SPDX-License-Identifer'?"); + WARN("Does new file '$expect_spdx_file' need " . + "'SPDX-License-Identifer'?"); } } $expect_spdx = 1; From bf031e257dea3d4d41f96d9eef77f447f56860e4 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Tue, 21 Jan 2025 15:48:43 -0300 Subject: [PATCH 0347/1179] target/riscv/csr.c: fix deadcode in rmw_xireg() Coverity found a DEADCODE issue in rmw_xireg() claiming that we can't reach 'RISCV_EXCP_VIRT_INSTRUCTION_FAULT' at the 'done' label: done: if (ret) { return (env->virt_enabled && virt) ? RISCV_EXCP_VIRT_INSTRUCTION_FAULT : RISCV_EXCP_ILLEGAL_INST; } return RISCV_EXCP_NONE; This happens because the 'virt' flag, which is only used by 'done', is set to 'false' and it will always remain 'false' in any condition where we'll jump to 'done': switch (csrno) { (...) case CSR_VSIREG: isel = env->vsiselect; virt = true; break; default: goto done; }; 'virt = true' will never reach 'done' because we have a if/else-if/else block right before the label that will always return: if (xiselect_aia_range(isel)) { return ... } else if (...) { return ... } else { return RISCV_EXCP_ILLEGAL_INST; } All this means that we can preserve the current logic by reducing the 'done' label to: done: if (ret) { return RISCV_EXCP_ILLEGAL_INST; } return RISCV_EXCP_NONE; The flag 'virt' is now unused. Remove it. Fix the 'goto done' identation while we're at it. Resolves: Coverity CID 1590359 Fixes: dc0280723d ("target/riscv: Decouple AIA processing from xiselect and xireg") Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250121184847.2109128-2-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/csr.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/target/riscv/csr.c b/target/riscv/csr.c index afb7544f0780..ab209d0cdaa3 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -2658,7 +2658,6 @@ static RISCVException rmw_xireg(CPURISCVState *env, int csrno, target_ulong *val, target_ulong new_val, target_ulong wr_mask) { - bool virt = false; int ret = -EINVAL; target_ulong isel; @@ -2680,10 +2679,9 @@ static RISCVException rmw_xireg(CPURISCVState *env, int csrno, break; case CSR_VSIREG: isel = env->vsiselect; - virt = true; break; default: - goto done; + goto done; }; /* @@ -2705,8 +2703,7 @@ static RISCVException rmw_xireg(CPURISCVState *env, int csrno, done: if (ret) { - return (env->virt_enabled && virt) ? - RISCV_EXCP_VIRT_INSTRUCTION_FAULT : RISCV_EXCP_ILLEGAL_INST; + return RISCV_EXCP_ILLEGAL_INST; } return RISCV_EXCP_NONE; } From e07a89143bb2735858a1eefeb9bcd9eb637e8e8f Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Tue, 21 Jan 2025 15:48:44 -0300 Subject: [PATCH 0348/1179] target/riscv/csr.c: fix 'ret' deadcode in rmw_xireg() Coverity found a second DEADCODE issue in rmw_xireg() claiming that we can't reach 'RISCV_EXCP_NONE' at the 'done' label: > 2706 done: > 2707 if (ret) { > 2708 return (env->virt_enabled && virt) ? > 2709 RISCV_EXCP_VIRT_INSTRUCTION_FAULT : RISCV_EXCP_ILLEGAL_INST; > 2710 } >>>> CID 1590356: Control flow issues (DEADCODE) >>>> Execution cannot reach this statement: "return RISCV_EXCP_NONE;". > 2711 return RISCV_EXCP_NONE; Our label is now reduced after fixing another deadcode in the previous patch but the problem reported here still remains: done: if (ret) { return RISCV_EXCP_ILLEGAL_INST; } return RISCV_EXCP_NONE; This happens because 'ret' changes only once at the start of the function: ret = smstateen_acc_ok(env, 0, SMSTATEEN0_SVSLCT); if (ret != RISCV_EXCP_NONE) { return ret; } So it's a guarantee that ret will be RISCV_EXCP_NONE (-1) if we ever reach the label, i.e. "if (ret)" will always be true, and the label can be even further reduced to: done: return RISCV_EXCP_ILLEGAL_INST; To make a better use of the label, remove the 'else' from the xiselect_aia_range() chain and let it fall-through to the 'done' label since they are now both returning RISCV_EXCP_ILLEGAL_INST. Resolves: Coverity CID 1590356 Fixes: dc0280723d ("target/riscv: Decouple AIA processing from xiselect and xireg") Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250121184847.2109128-3-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/csr.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/target/riscv/csr.c b/target/riscv/csr.c index ab209d0cdaa3..0e83c3b0450e 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -2697,15 +2697,10 @@ static RISCVException rmw_xireg(CPURISCVState *env, int csrno, } else if (riscv_cpu_cfg(env)->ext_smcsrind || riscv_cpu_cfg(env)->ext_sscsrind) { return rmw_xireg_csrind(env, csrno, isel, val, new_val, wr_mask); - } else { - return RISCV_EXCP_ILLEGAL_INST; } done: - if (ret) { - return RISCV_EXCP_ILLEGAL_INST; - } - return RISCV_EXCP_NONE; + return RISCV_EXCP_ILLEGAL_INST; } static RISCVException rmw_xtopei(CPURISCVState *env, int csrno, From c1edd5f756788082e70dc23a9d630bbbe8449f2a Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Tue, 21 Jan 2025 15:48:45 -0300 Subject: [PATCH 0349/1179] target/riscv/csr.c: fix deadcode in rmw_xiregi() Coverity found a DEADCODE issue in rmw_xiregi() claiming that we can't reach 'RISCV_EXCP_VIRT_INSTRUCTION_FAULT' at the 'done' label: > 2652 done: >>>> CID 1590357: Control flow issues (DEADCODE) >>>> Execution cannot reach the expression "RISCV_EXCP_VIRT_INSTRUCTION_FAULT" inside this statement: "return (env->virt_enabled &...". > 2653 return (env->virt_enabled && virt) ? > 2654 RISCV_EXCP_VIRT_INSTRUCTION_FAULT : RISCV_EXCP_ILLEGAL_INST; This happens because 'virt' is being set to 'false' and it will remain as 'false' in any code path where 'done' will be called. The label can be safely reduced to: done: return RISCV_EXCP_ILLEGAL_INST; And that will leave us with the following usage of a 'goto' skipping a single 'return' to do another single 'return': } else { goto done; } return rmw_xireg_csrind(env, csrno, isel, val, new_val, wr_mask); done: return RISCV_EXCP_ILLEGAL_INST; Which we will eliminate it and just do 'return RISCV_EXCP_ILLEGAL_INST' instead. Resolves: Coverity CID 1590357 Fixes: 5e33a20827 ("target/riscv: Support generic CSR indirect access") Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250121184847.2109128-4-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/csr.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/target/riscv/csr.c b/target/riscv/csr.c index 0e83c3b0450e..75f21ccabb0b 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -2621,7 +2621,6 @@ static int rmw_xireg_csrind(CPURISCVState *env, int csrno, static int rmw_xiregi(CPURISCVState *env, int csrno, target_ulong *val, target_ulong new_val, target_ulong wr_mask) { - bool virt = false; int ret = -EINVAL; target_ulong isel; @@ -2642,16 +2641,11 @@ static int rmw_xiregi(CPURISCVState *env, int csrno, target_ulong *val, } else if (CSR_VSIREG <= csrno && csrno <= CSR_VSIREG6 && csrno != CSR_VSIREG4 - 1) { isel = env->vsiselect; - virt = true; } else { - goto done; + return RISCV_EXCP_ILLEGAL_INST; } return rmw_xireg_csrind(env, csrno, isel, val, new_val, wr_mask); - -done: - return (env->virt_enabled && virt) ? - RISCV_EXCP_VIRT_INSTRUCTION_FAULT : RISCV_EXCP_ILLEGAL_INST; } static RISCVException rmw_xireg(CPURISCVState *env, int csrno, From 485eb79989c6f9f00103ef2e62360ad9cf6a9b23 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Tue, 21 Jan 2025 15:48:46 -0300 Subject: [PATCH 0350/1179] target/riscv/csr.c: fix deadcode in aia_smode32() Coverity reported a DEADCODE ticket in this function, as follows: >>>> CID 1590358: Control flow issues (DEADCODE) >>>> Execution cannot reach this statement: "return ret;". > 380 return ret; > 381 } The cause is that the 'if (ret != RISCV_EXCP_NONE)' conditional is duplicated: ret = smstateen_acc_ok(env, 0, SMSTATEEN0_AIA); if (ret != RISCV_EXCP_NONE) { return ret; } if (ret != RISCV_EXCP_NONE) { return ret; } Remove the duplication to fix the deadcode. Resolves: Coverity CID 1590358 Fixes: dbcb6e1ccf ("target/riscv: Enable S*stateen bits for AIA") Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250121184847.2109128-5-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/csr.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/target/riscv/csr.c b/target/riscv/csr.c index 75f21ccabb0b..dc0a88a0f085 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -376,10 +376,6 @@ static RISCVException aia_smode32(CPURISCVState *env, int csrno) return ret; } - if (ret != RISCV_EXCP_NONE) { - return ret; - } - return smode32(env, csrno); } From b55538ea22c6474e62a311f5993f0f84caeb4131 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Tue, 21 Jan 2025 15:48:47 -0300 Subject: [PATCH 0351/1179] target/riscv/cpu_helper.c: fix bad_shift in riscv_cpu_interrupt() Coverity reported a BAD_SHIFT issue in the following code: > 2097 >>>> CID 1590355: Integer handling issues (BAD_SHIFT) >>>> In expression "hdeleg >> cause", right shifting by more than 63 bits has undefined behavior. The shift amount, "cause", is at least 64. > 2098 vsmode_exc = env->virt_enabled && (((hdeleg >> cause) & 1) || vs_injected); > 2099 /* It is not clear to me how the tool guarantees that '"cause" is at least 64', but indeed there's no guarantees that it would be < 64 in the 'async = true' code path. A simple fix to avoid a potential UB is to add a 'cause < 64' guard like 'mode' is already doing right before 'vsmode_exc'. Resolves: Coverity CID 1590355 Fixes: 967760f62c ("target/riscv: Implement Ssdbltrp exception handling") Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250121184847.2109128-6-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu_helper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 8ff6d900f2c2..1de8e0e49471 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -2084,7 +2084,9 @@ void riscv_cpu_do_interrupt(CPUState *cs) mode = env->priv <= PRV_S && cause < 64 && (((deleg >> cause) & 1) || s_injected || vs_injected) ? PRV_S : PRV_M; - vsmode_exc = env->virt_enabled && (((hdeleg >> cause) & 1) || vs_injected); + vsmode_exc = env->virt_enabled && cause < 64 && + (((hdeleg >> cause) & 1) || vs_injected); + /* * Check double trap condition only if already in S-mode and targeting * S-mode From ffd455963f230c7dc04965609d6675da687a5a78 Mon Sep 17 00:00:00 2001 From: Max Chou Date: Fri, 24 Jan 2025 18:14:47 +0800 Subject: [PATCH 0352/1179] target/riscv: rvv: Fix unexpected behavior of vector reduction instructions when vl is 0 According to the Vector Reduction Operations section in the RISC-V "V" Vector Extension spec, "If vl=0, no operation is performed and the destination register is not updated." The vd should be updated when vl is larger than 0. Fixes: fe5c9ab1fc ("target/riscv: vector single-width integer reduction instructions") Fixes: f714361ed7 ("target/riscv: rvv-1.0: implement vstart CSR") Signed-off-by: Max Chou Reviewed-by: Daniel Henrique Barboza Message-ID: <20250124101452.2519171-1-max.chou@sifive.com> Signed-off-by: Alistair Francis --- target/riscv/vector_helper.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/target/riscv/vector_helper.c b/target/riscv/vector_helper.c index 5386e3b97c5d..7773df6a7c72 100644 --- a/target/riscv/vector_helper.c +++ b/target/riscv/vector_helper.c @@ -4659,7 +4659,9 @@ void HELPER(NAME)(void *vd, void *v0, void *vs1, \ } \ s1 = OP(s1, (TD)s2); \ } \ - *((TD *)vd + HD(0)) = s1; \ + if (vl > 0) { \ + *((TD *)vd + HD(0)) = s1; \ + } \ env->vstart = 0; \ /* set tail elements to 1s */ \ vext_set_elems_1s(vd, vta, esz, vlenb); \ @@ -4745,7 +4747,9 @@ void HELPER(NAME)(void *vd, void *v0, void *vs1, \ } \ s1 = OP(s1, (TD)s2, &env->fp_status); \ } \ - *((TD *)vd + HD(0)) = s1; \ + if (vl > 0) { \ + *((TD *)vd + HD(0)) = s1; \ + } \ env->vstart = 0; \ /* set tail elements to 1s */ \ vext_set_elems_1s(vd, vta, esz, vlenb); \ From bf3adf93f16730ca5aaa6c26cf969e64eeff6e7b Mon Sep 17 00:00:00 2001 From: Max Chou Date: Fri, 24 Jan 2025 17:05:38 +0800 Subject: [PATCH 0353/1179] target/riscv: rvv: Fix incorrect vlen comparison in prop_vlen_set In prop_vlen_set function, there is an incorrect comparison between vlen(bit) and vlenb(byte). This will cause unexpected error when user applies the `vlen=1024` cpu option with a vendor predefined cpu type that the default vlen is 1024(vlenb=128). Fixes: 4f6d036ccc ("target/riscv/cpu.c: remove cpu->cfg.vlen") Signed-off-by: Max Chou Reviewed-by: Daniel Henrique Barboza Message-ID: <20250124090539.2506448-1-max.chou@sifive.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index d7ecf729d05b..99436f1750f3 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -2027,6 +2027,7 @@ static void prop_vlen_set(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) { RISCVCPU *cpu = RISCV_CPU(obj); + uint16_t cpu_vlen = cpu->cfg.vlenb << 3; uint16_t value; if (!visit_type_uint16(v, name, &value, errp)) { @@ -2038,10 +2039,10 @@ static void prop_vlen_set(Object *obj, Visitor *v, const char *name, return; } - if (value != cpu->cfg.vlenb && riscv_cpu_is_vendor(obj)) { + if (value != cpu_vlen && riscv_cpu_is_vendor(obj)) { cpu_set_prop_err(cpu, name, errp); error_append_hint(errp, "Current '%s' val: %u\n", - name, cpu->cfg.vlenb << 3); + name, cpu_vlen); return; } From 3fba76e61caa46329afc399b3ecaaba70c8b0a4e Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Tue, 21 Jan 2025 14:06:25 -0300 Subject: [PATCH 0354/1179] target/riscv/debug.c: use wp size = 4 for 32-bit CPUs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mcontrol select bit (19) is always zero, meaning our triggers will always match virtual addresses. In this condition, if the user does not specify a size for the trigger, the access size defaults to XLEN. At this moment we're using def_size = 8 regardless of CPU XLEN. Use def_size = 4 in case we're running 32 bits. Fixes: 95799e36c1 ("target/riscv: Add initial support for the Sdtrig extension") Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250121170626.1992570-2-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/debug.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/target/riscv/debug.c b/target/riscv/debug.c index f6241a80be80..9db4048523e8 100644 --- a/target/riscv/debug.c +++ b/target/riscv/debug.c @@ -478,7 +478,7 @@ static void type2_breakpoint_insert(CPURISCVState *env, target_ulong index) bool enabled = type2_breakpoint_enabled(ctrl); CPUState *cs = env_cpu(env); int flags = BP_CPU | BP_STOP_BEFORE_ACCESS; - uint32_t size; + uint32_t size, def_size; if (!enabled) { return; @@ -501,7 +501,9 @@ static void type2_breakpoint_insert(CPURISCVState *env, target_ulong index) cpu_watchpoint_insert(cs, addr, size, flags, &env->cpu_watchpoint[index]); } else { - cpu_watchpoint_insert(cs, addr, 8, flags, + def_size = riscv_cpu_mxl(env) == MXL_RV64 ? 8 : 4; + + cpu_watchpoint_insert(cs, addr, def_size, flags, &env->cpu_watchpoint[index]); } } From c86edc547692d812d1dcc04220c38310be2c00c3 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Tue, 21 Jan 2025 14:06:26 -0300 Subject: [PATCH 0355/1179] target/riscv: throw debug exception before page fault In the RISC-V privileged ISA section 3.1.15 table 15, it is determined that a debug exception that is triggered from a load/store has a higher priority than a possible fault that this access might trigger. This is not the case ATM as shown in [1]. Adding a breakpoint in an address that deliberately will fault is causing a load page fault instead of a debug exception. The reason is that we're throwing in the page fault as soon as the fault occurs (end of riscv_cpu_tlb_fill(), raise_mmu_exception()), not allowing the installed watchpoints to trigger. Call cpu_check_watchpoint() in the page fault path to search and execute any watchpoints that might exist for the address, never returning back to the fault path. If no watchpoints are found cpu_check_watchpoint() will return and we'll fall-through the regular path to raise_mmu_exception(). [1] https://gitlab.com/qemu-project/qemu/-/issues/2627 Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2627 Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Reviewed-by: Richard Henderson Message-ID: <20250121170626.1992570-3-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu_helper.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 1de8e0e49471..29dc721c5dc6 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -27,6 +27,7 @@ #include "exec/page-protection.h" #include "instmap.h" #include "tcg/tcg-op.h" +#include "hw/core/tcg-cpu-ops.h" #include "trace.h" #include "semihosting/common-semi.h" #include "system/cpu-timers.h" @@ -1697,6 +1698,23 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, } else if (probe) { return false; } else { + int wp_access = 0; + + if (access_type == MMU_DATA_LOAD) { + wp_access |= BP_MEM_READ; + } else if (access_type == MMU_DATA_STORE) { + wp_access |= BP_MEM_WRITE; + } + + /* + * If a watchpoint isn't found for 'addr' this will + * be a no-op and we'll resume the mmu_exception path. + * Otherwise we'll throw a debug exception and execution + * will continue elsewhere. + */ + cpu_check_watchpoint(cs, address, size, MEMTXATTRS_UNSPECIFIED, + wp_access, retaddr); + raise_mmu_exception(env, address, access_type, pmp_violation, first_stage_error, two_stage_lookup, two_stage_indirect_error); From a680d9531e9b3726dddee94e5f49900c4b756ea6 Mon Sep 17 00:00:00 2001 From: Huang Borong Date: Wed, 15 Jan 2025 11:51:05 +0800 Subject: [PATCH 0356/1179] hw/intc/riscv_aplic: Remove redundant "hart_idx" masking Remove the redundant masking of "hart_idx", as the same operation is performed later during address calculation. This change impacts the "hart_idx" value in the final qemu_log_mask() call. The original "hart_idx" parameter should be used for logging to ensure accuracy, rather than the masked value. Signed-off-by: Huang Borong Reviewed-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Message-ID: <20250115035105.19600-1-huangborong@bosc.ac.cn> Signed-off-by: Alistair Francis --- hw/intc/riscv_aplic.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/intc/riscv_aplic.c b/hw/intc/riscv_aplic.c index 486664911580..0974c6a5db39 100644 --- a/hw/intc/riscv_aplic.c +++ b/hw/intc/riscv_aplic.c @@ -421,7 +421,6 @@ static void riscv_aplic_msi_send(RISCVAPLICState *aplic, APLIC_xMSICFGADDRH_HHXW_MASK; group_idx = hart_idx >> lhxw; - hart_idx &= APLIC_xMSICFGADDR_PPN_LHX_MASK(lhxw); addr = msicfgaddr; addr |= ((uint64_t)(msicfgaddrH & APLIC_xMSICFGADDRH_BAPPN_MASK)) << 32; From 38d0ce28dfc7a73c7527442bb3b7029b402cb056 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Wed, 15 Jan 2025 15:43:11 -0300 Subject: [PATCH 0357/1179] target/riscv: add ssu64xl ssu64xl is defined in RVA22 as: "sstatus.UXL must be capable of holding the value 2 (i.e., UXLEN=64 must be supported)." This is always true in TCG and it's mandatory for RVA23, so claim support for it. Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115184316.2344583-2-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.c | 1 + tests/data/acpi/riscv64/virt/RHCT | Bin 390 -> 398 bytes 2 files changed, 1 insertion(+) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 99436f1750f3..4f5e13a75942 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -213,6 +213,7 @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(sstc, PRIV_VERSION_1_12_0, ext_sstc), ISA_EXT_DATA_ENTRY(sstvala, PRIV_VERSION_1_12_0, has_priv_1_12), ISA_EXT_DATA_ENTRY(sstvecd, PRIV_VERSION_1_12_0, has_priv_1_12), + ISA_EXT_DATA_ENTRY(ssu64xl, PRIV_VERSION_1_12_0, has_priv_1_12), ISA_EXT_DATA_ENTRY(supm, PRIV_VERSION_1_13_0, ext_supm), ISA_EXT_DATA_ENTRY(svade, PRIV_VERSION_1_11_0, ext_svade), ISA_EXT_DATA_ENTRY(svadu, PRIV_VERSION_1_12_0, ext_svadu), diff --git a/tests/data/acpi/riscv64/virt/RHCT b/tests/data/acpi/riscv64/virt/RHCT index 695022d56c4ac16607d4c622955ad339fbbfe997..b14ec15e553200760a63aad65586913d31ea2edc 100644 GIT binary patch delta 48 zcmZo;?qlW(@^B96V`N}pOqj@Jz^cQ@$e^;(o|BQSxYW#~B4@H2qXkC_P&yBY0s9LH Ai~s-t delta 41 ucmeBUZe!*O@^B7mV`N}poG_8gfK`Q&kwIpoJtyPj07f&87@&9>5CZ_SKnGv| From e037673764b2b9db25f6e5deec8e11e7bf54c4b1 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Wed, 15 Jan 2025 15:43:12 -0300 Subject: [PATCH 0358/1179] target/riscv: use RVB in RVA22U64 From the time we added RVA22U64 until now the spec didn't declare 'RVB' as a dependency, using zba/zbb/zbs instead. Since then the RVA22 spec [1] added the following in the 'RVA22U64 Mandatory Extensions' section: "B Bit-manipulation instructions Note: The B extension comprises the Zba, Zbb, and Zbs extensions. At the time of RVA22U64's ratification, the B extension had not yet been defined, and so RVA22U64 explicitly mandated Zba, Zbb, and Zbs instead. Mandating B is equivalent." It is also equivalent to QEMU (see riscv_cpu_validate_b() in target/riscv/tcg/tcg-cpu.c). Finally, RVA23U64 [2] directly mentions RVB as a mandatory extension, not citing zba/zbb/zbs. To make it clear that RVA23U64 will extend RVA22U64 (i.e. RVA22 is a parent of RVA23), use RVB in RVA22U64 as well. (bios-tables-test change: RVB added to riscv,isa) [1] https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#61-rva22u64-profile [2] https://github.com/riscv/riscv-profiles/blob/main/src/rva23-profile.adoc#rva23u64-profile Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115184316.2344583-3-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.c | 2 +- tests/data/acpi/riscv64/virt/RHCT | Bin 398 -> 400 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 4f5e13a75942..578bc9565266 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -2345,7 +2345,7 @@ static const PropertyInfo prop_marchid = { static RISCVCPUProfile RVA22U64 = { .parent = NULL, .name = "rva22u64", - .misa_ext = RVI | RVM | RVA | RVF | RVD | RVC | RVU, + .misa_ext = RVI | RVM | RVA | RVF | RVD | RVC | RVB | RVU, .priv_spec = RISCV_PROFILE_ATTR_UNUSED, .satp_mode = RISCV_PROFILE_ATTR_UNUSED, .ext_offsets = { diff --git a/tests/data/acpi/riscv64/virt/RHCT b/tests/data/acpi/riscv64/virt/RHCT index b14ec15e553200760a63aad65586913d31ea2edc..13c8025b868051485be5ba62974a22971a07bc6a 100644 GIT binary patch delta 53 zcmeBUp1{l%JqB1j+%-qDZl;ot1UQ&#clNpsc(tv6T GfEWN^whTc4 delta 52 zcmbQh+{ern6Im@tvcKtP9)kwJyAsLaeHGdD3UC3&N_6yxMHMkS6EpprZw F1^_xF3qt?^ From 1813fc68c40448982f5700ab7a3e95b1aa6660ac Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Wed, 15 Jan 2025 15:43:13 -0300 Subject: [PATCH 0359/1179] target/riscv: add profile u_parent and s_parent The current 'parent' mechanic for profiles allows for one profile to be a child of a previous/older profile, enabling all its extensions (and the parent profile itself) and sparing us from tediously listing all extensions for every profile. This works fine for u-mode profiles. For s-mode profiles this is not enough: a s-mode profile extends not only his equivalent u-mode profile but also the previous s-mode profile. This means, for example, that RVA23S64 extends both RVA23U64 and RVA22S64. To fit this usage, rename the existing 'parent' to 'u_parent' and add a new 's_parent' attribute for profiles. Handle both like we were doing with the previous 'parent' attribute, i.e. if set, enable it. This change does nothing for the existing profiles but will make RVA23S64 simpler. Suggested-by: Andrew Jones Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115184316.2344583-4-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.c | 6 ++++-- target/riscv/cpu.h | 3 ++- target/riscv/tcg/tcg-cpu.c | 35 ++++++++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 578bc9565266..7b708bd0105a 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -2343,7 +2343,8 @@ static const PropertyInfo prop_marchid = { * doesn't need to be manually enabled by the profile. */ static RISCVCPUProfile RVA22U64 = { - .parent = NULL, + .u_parent = NULL, + .s_parent = NULL, .name = "rva22u64", .misa_ext = RVI | RVM | RVA | RVF | RVD | RVC | RVB | RVU, .priv_spec = RISCV_PROFILE_ATTR_UNUSED, @@ -2375,7 +2376,8 @@ static RISCVCPUProfile RVA22U64 = { * The remaining features/extensions comes from RVA22U64. */ static RISCVCPUProfile RVA22S64 = { - .parent = &RVA22U64, + .u_parent = &RVA22U64, + .s_parent = NULL, .name = "rva22s64", .misa_ext = RVS, .priv_spec = PRIV_VERSION_1_12_0, diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 97713681cbea..986131a19198 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -81,7 +81,8 @@ const char *riscv_get_misa_ext_description(uint32_t bit); #define CPU_CFG_OFFSET(_prop) offsetof(struct RISCVCPUConfig, _prop) typedef struct riscv_cpu_profile { - struct riscv_cpu_profile *parent; + struct riscv_cpu_profile *u_parent; + struct riscv_cpu_profile *s_parent; const char *name; uint32_t misa_ext; bool enabled; diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index d7e694fdb3d7..2b21942ef23a 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -713,13 +713,29 @@ static bool riscv_cpu_validate_profile_satp(RISCVCPU *cpu, } #endif +static void riscv_cpu_check_parent_profile(RISCVCPU *cpu, + RISCVCPUProfile *profile, + RISCVCPUProfile *parent) +{ + const char *parent_name; + bool parent_enabled; + + if (!profile->enabled || !parent) { + return; + } + + parent_name = parent->name; + parent_enabled = object_property_get_bool(OBJECT(cpu), parent_name, NULL); + profile->enabled = parent_enabled; +} + static void riscv_cpu_validate_profile(RISCVCPU *cpu, RISCVCPUProfile *profile) { CPURISCVState *env = &cpu->env; const char *warn_msg = "Profile %s mandates disabled extension %s"; bool send_warn = profile->user_set && profile->enabled; - bool parent_enabled, profile_impl = true; + bool profile_impl = true; int i; #ifndef CONFIG_USER_ONLY @@ -773,12 +789,8 @@ static void riscv_cpu_validate_profile(RISCVCPU *cpu, profile->enabled = profile_impl; - if (profile->parent != NULL) { - parent_enabled = object_property_get_bool(OBJECT(cpu), - profile->parent->name, - NULL); - profile->enabled = profile->enabled && parent_enabled; - } + riscv_cpu_check_parent_profile(cpu, profile, profile->u_parent); + riscv_cpu_check_parent_profile(cpu, profile, profile->s_parent); } static void riscv_cpu_validate_profiles(RISCVCPU *cpu) @@ -1190,8 +1202,13 @@ static void cpu_set_profile(Object *obj, Visitor *v, const char *name, profile->user_set = true; profile->enabled = value; - if (profile->parent != NULL) { - object_property_set_bool(obj, profile->parent->name, + if (profile->u_parent != NULL) { + object_property_set_bool(obj, profile->u_parent->name, + profile->enabled, NULL); + } + + if (profile->s_parent != NULL) { + object_property_set_bool(obj, profile->s_parent->name, profile->enabled, NULL); } From 08dfc194546632ee0ca0c00030b76a89b6efd298 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Wed, 15 Jan 2025 15:43:14 -0300 Subject: [PATCH 0360/1179] target/riscv: change priv_ver check in validate_profile() The S profiles do a priv_ver check during validation to see if the running priv_ver is compatible with it. This check is done by comparing if the running priv_ver is equal to the priv_ver the profile specifies. There is an universe where we added RVA23S64 support based on both RVA23U64 and RVA22S64 and this error is being thrown: qemu-system-riscv64: warning: Profile rva22s64 requires priv spec v1.12.0, but priv ver v1.13.0 was set We're enabling RVA22S64 (priv_ver 1.12) as a dependency of RVA23S64 (priv_ver 1.13) and complaining to users about what we did ourselves. There's no drawback in allowing a profile to run in an env that has a priv_ver newer than it's required by it. So, like Hiro Nakamura saves the future by changing the past, change the priv_ver check now to allow profiles to run in a newer priv_ver. This universe will have one less warning to deal with. Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115184316.2344583-5-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/tcg/tcg-cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index 2b21942ef23a..cb9b504012f3 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -746,7 +746,7 @@ static void riscv_cpu_validate_profile(RISCVCPU *cpu, #endif if (profile->priv_spec != RISCV_PROFILE_ATTR_UNUSED && - profile->priv_spec != env->priv_ver) { + profile->priv_spec > env->priv_ver) { profile_impl = false; if (send_warn) { From a876221bd301d090768bb85ab0bd7e24ec21348c Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Wed, 15 Jan 2025 15:43:15 -0300 Subject: [PATCH 0361/1179] target/riscv: add RVA23U64 profile Add RVA23U64 as described in [1]. Add it as a child of RVA22U64 since all RVA22U64 mandatory extensions are also present in RVA23U64. What's left then is to list the mandatory extensions that are RVA23 only. A new "rva23u64" CPU is also added. [1] https://github.com/riscv/riscv-profiles/blob/main/src/rva23-profile.adoc Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115184316.2344583-6-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu-qom.h | 1 + target/riscv/cpu.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h index d56b067bf248..53ead481a9db 100644 --- a/target/riscv/cpu-qom.h +++ b/target/riscv/cpu-qom.h @@ -40,6 +40,7 @@ #define TYPE_RISCV_CPU_RV64E RISCV_CPU_TYPE_NAME("rv64e") #define TYPE_RISCV_CPU_RVA22U64 RISCV_CPU_TYPE_NAME("rva22u64") #define TYPE_RISCV_CPU_RVA22S64 RISCV_CPU_TYPE_NAME("rva22s64") +#define TYPE_RISCV_CPU_RVA23U64 RISCV_CPU_TYPE_NAME("rva23u64") #define TYPE_RISCV_CPU_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex") #define TYPE_RISCV_CPU_SHAKTI_C RISCV_CPU_TYPE_NAME("shakti-c") #define TYPE_RISCV_CPU_SIFIVE_E31 RISCV_CPU_TYPE_NAME("sifive-e31") diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 7b708bd0105a..511ed1df0e83 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -2391,9 +2391,34 @@ static RISCVCPUProfile RVA22S64 = { } }; +/* + * All mandatory extensions from RVA22U64 are present + * in RVA23U64 so set RVA22 as a parent. We need to + * declare just the newly added mandatory extensions. + */ +static RISCVCPUProfile RVA23U64 = { + .u_parent = &RVA22U64, + .s_parent = NULL, + .name = "rva23u64", + .misa_ext = RVV, + .priv_spec = RISCV_PROFILE_ATTR_UNUSED, + .satp_mode = RISCV_PROFILE_ATTR_UNUSED, + .ext_offsets = { + CPU_CFG_OFFSET(ext_zvfhmin), CPU_CFG_OFFSET(ext_zvbb), + CPU_CFG_OFFSET(ext_zvkt), CPU_CFG_OFFSET(ext_zihintntl), + CPU_CFG_OFFSET(ext_zicond), CPU_CFG_OFFSET(ext_zimop), + CPU_CFG_OFFSET(ext_zcmop), CPU_CFG_OFFSET(ext_zcb), + CPU_CFG_OFFSET(ext_zfa), CPU_CFG_OFFSET(ext_zawrs), + CPU_CFG_OFFSET(ext_supm), + + RISCV_PROFILE_EXT_LIST_END + } +}; + RISCVCPUProfile *riscv_profiles[] = { &RVA22U64, &RVA22S64, + &RVA23U64, NULL, }; @@ -2880,6 +2905,13 @@ static void rva22s64_profile_cpu_init(Object *obj) RVA22S64.enabled = true; } + +static void rva23u64_profile_cpu_init(Object *obj) +{ + rv64i_bare_cpu_init(obj); + + RVA23U64.enabled = true; +} #endif static const gchar *riscv_gdb_arch_name(CPUState *cs) @@ -3150,6 +3182,7 @@ static const TypeInfo riscv_cpu_type_infos[] = { DEFINE_BARE_CPU(TYPE_RISCV_CPU_RV64E, MXL_RV64, rv64e_bare_cpu_init), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA22U64, MXL_RV64, rva22u64_profile_cpu_init), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA22S64, MXL_RV64, rva22s64_profile_cpu_init), + DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA23U64, MXL_RV64, rva23u64_profile_cpu_init), #endif /* TARGET_RISCV64 */ }; From c91f74b91df0ff4aaf4905df328480b7c1e57c20 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Wed, 15 Jan 2025 15:43:16 -0300 Subject: [PATCH 0362/1179] target/riscv: add RVA23S64 profile Add RVA23S64 as described in [1]. This profile inherits all mandatory extensions of RVA23U64 and RVA22S64, making it a child of both profiles. A new "rva23s64" profile CPU is also added. This is the generated riscv,isa for it (taken via -M dumpdtb): rv64imafdcbvh_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ ziccrse_zicond_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zimop_ zmmul_za64rs_zaamo_zalrsc_zawrs_zfa_zfhmin_zca_zcb_zcd_zcmop_zba_zbb_zbs_ zkt_zvbb_zve32f_zve32x_zve64f_zve64d_zve64x_zvfhmin_zvkb_zvkt_shcounterenw_ sha_shgatpa_shtvala_shvsatpa_shvstvala_shvstvecd_smnpm_smstateen_ssccptr_ sscofpmf_sscounterenw_ssnpm_ssstateen_sstc_sstvala_sstvecd_ssu64xl_ supm_svade_svinval_svnapot_svpbmt [1] https://github.com/riscv/riscv-profiles/blob/main/src/rva23-profile.adoc Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115184316.2344583-7-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu-qom.h | 1 + target/riscv/cpu.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h index 53ead481a9db..4cfdb74891e1 100644 --- a/target/riscv/cpu-qom.h +++ b/target/riscv/cpu-qom.h @@ -41,6 +41,7 @@ #define TYPE_RISCV_CPU_RVA22U64 RISCV_CPU_TYPE_NAME("rva22u64") #define TYPE_RISCV_CPU_RVA22S64 RISCV_CPU_TYPE_NAME("rva22s64") #define TYPE_RISCV_CPU_RVA23U64 RISCV_CPU_TYPE_NAME("rva23u64") +#define TYPE_RISCV_CPU_RVA23S64 RISCV_CPU_TYPE_NAME("rva23s64") #define TYPE_RISCV_CPU_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex") #define TYPE_RISCV_CPU_SHAKTI_C RISCV_CPU_TYPE_NAME("shakti-c") #define TYPE_RISCV_CPU_SIFIVE_E31 RISCV_CPU_TYPE_NAME("sifive-e31") diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 511ed1df0e83..3624ffb6d998 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -2415,10 +2415,41 @@ static RISCVCPUProfile RVA23U64 = { } }; +/* + * As with RVA23U64, RVA23S64 also defines 'named features'. + * + * Cache related features that we consider enabled since we don't + * implement cache: Ssccptr + * + * Other named features that we already implement: Sstvecd, Sstvala, + * Sscounterenw, Ssu64xl + * + * The remaining features/extensions comes from RVA23S64. + */ +static RISCVCPUProfile RVA23S64 = { + .u_parent = &RVA23U64, + .s_parent = &RVA22S64, + .name = "rva23s64", + .misa_ext = RVS, + .priv_spec = PRIV_VERSION_1_13_0, + .satp_mode = VM_1_10_SV39, + .ext_offsets = { + /* New in RVA23S64 */ + CPU_CFG_OFFSET(ext_svnapot), CPU_CFG_OFFSET(ext_sstc), + CPU_CFG_OFFSET(ext_sscofpmf), CPU_CFG_OFFSET(ext_ssnpm), + + /* Named features: Sha */ + CPU_CFG_OFFSET(ext_sha), + + RISCV_PROFILE_EXT_LIST_END + } +}; + RISCVCPUProfile *riscv_profiles[] = { &RVA22U64, &RVA22S64, &RVA23U64, + &RVA23S64, NULL, }; @@ -2912,6 +2943,13 @@ static void rva23u64_profile_cpu_init(Object *obj) RVA23U64.enabled = true; } + +static void rva23s64_profile_cpu_init(Object *obj) +{ + rv64i_bare_cpu_init(obj); + + RVA23S64.enabled = true; +} #endif static const gchar *riscv_gdb_arch_name(CPUState *cs) @@ -3183,6 +3221,7 @@ static const TypeInfo riscv_cpu_type_infos[] = { DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA22U64, MXL_RV64, rva22u64_profile_cpu_init), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA22S64, MXL_RV64, rva22s64_profile_cpu_init), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA23U64, MXL_RV64, rva23u64_profile_cpu_init), + DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA23S64, MXL_RV64, rva23s64_profile_cpu_init), #endif /* TARGET_RISCV64 */ }; From 9d6c2c1f10c7cd156d90daff505b2e2cf72fec89 Mon Sep 17 00:00:00 2001 From: Jason Chien Date: Wed, 15 Jan 2025 22:17:29 +0800 Subject: [PATCH 0363/1179] hw/riscv/riscv-iommu: Remove redundant struct members Initially, the IOMMU would create a thread, but this thread was removed in the merged version. The struct members for thread control should have been removed as well, but they were not removed in commit 0c54acb8243 ("hw/riscv: add RISC-V IOMMU base emulation"). Signed-off-by: Jason Chien Reviewed-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115141730.30858-1-jason.chien@sifive.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 9424989df41a..fa8a50fa243c 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -58,11 +58,6 @@ struct RISCVIOMMUState { /* interrupt notifier */ void (*notify)(RISCVIOMMUState *iommu, unsigned vector); - /* IOMMU State Machine */ - QemuThread core_proc; /* Background processing thread */ - QemuCond core_cond; /* Background processing wake up signal */ - unsigned core_exec; /* Processing thread execution actions */ - /* IOMMU target address space */ AddressSpace *target_as; MemoryRegion *target_mr; From a975e733a02ba5f1e1133c97cfe1545b08fe1fbc Mon Sep 17 00:00:00 2001 From: Jason Chien Date: Wed, 15 Jan 2025 22:17:30 +0800 Subject: [PATCH 0364/1179] hw/riscv/riscv-iommu-bits: Remove duplicate definitions The header contains duplicate macro definitions. This commit eliminates the duplicate part. Signed-off-by: Jason Chien Reviewed-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250115141730.30858-2-jason.chien@sifive.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-bits.h | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/hw/riscv/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h index 485f36b9c921..de599b80d693 100644 --- a/hw/riscv/riscv-iommu-bits.h +++ b/hw/riscv/riscv-iommu-bits.h @@ -50,8 +50,14 @@ struct riscv_iommu_pq_record { #define RISCV_IOMMU_PREQ_HDR_PRIV BIT_ULL(33) #define RISCV_IOMMU_PREQ_HDR_EXEC BIT_ULL(34) #define RISCV_IOMMU_PREQ_HDR_DID GENMASK_ULL(63, 40) + /* Payload fields */ +#define RISCV_IOMMU_PREQ_PAYLOAD_R BIT_ULL(0) +#define RISCV_IOMMU_PREQ_PAYLOAD_W BIT_ULL(1) +#define RISCV_IOMMU_PREQ_PAYLOAD_L BIT_ULL(2) #define RISCV_IOMMU_PREQ_PAYLOAD_M GENMASK_ULL(2, 0) +#define RISCV_IOMMU_PREQ_PRG_INDEX GENMASK_ULL(11, 3) +#define RISCV_IOMMU_PREQ_UADDR GENMASK_ULL(63, 12) /* Common field positions */ #define RISCV_IOMMU_PPN_FIELD GENMASK_ULL(53, 10) @@ -382,22 +388,6 @@ enum riscv_iommu_fq_ttypes { RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 9, }; -/* Header fields */ -#define RISCV_IOMMU_PREQ_HDR_PID GENMASK_ULL(31, 12) -#define RISCV_IOMMU_PREQ_HDR_PV BIT_ULL(32) -#define RISCV_IOMMU_PREQ_HDR_PRIV BIT_ULL(33) -#define RISCV_IOMMU_PREQ_HDR_EXEC BIT_ULL(34) -#define RISCV_IOMMU_PREQ_HDR_DID GENMASK_ULL(63, 40) - -/* Payload fields */ -#define RISCV_IOMMU_PREQ_PAYLOAD_R BIT_ULL(0) -#define RISCV_IOMMU_PREQ_PAYLOAD_W BIT_ULL(1) -#define RISCV_IOMMU_PREQ_PAYLOAD_L BIT_ULL(2) -#define RISCV_IOMMU_PREQ_PAYLOAD_M GENMASK_ULL(2, 0) -#define RISCV_IOMMU_PREQ_PRG_INDEX GENMASK_ULL(11, 3) -#define RISCV_IOMMU_PREQ_UADDR GENMASK_ULL(63, 12) - - /* * struct riscv_iommu_msi_pte - MSI Page Table Entry */ From 3521f9cadc29c7d68b73b325ddb46a7acebf6212 Mon Sep 17 00:00:00 2001 From: Rodrigo Dias Correa Date: Tue, 14 Jan 2025 22:21:50 +0100 Subject: [PATCH 0365/1179] goldfish_rtc: Fix tick_offset migration Instead of migrating the raw tick_offset, goldfish_rtc migrates a recalculated value based on QEMU_CLOCK_VIRTUAL. As QEMU_CLOCK_VIRTUAL stands still across a save-and-restore cycle, the guest RTC becomes out of sync with the host RTC when the VM is restored. As described in the bug description, it looks like this calculation was copied from pl031 RTC, which had its tick_offset migration fixed by Commit 032cfe6a79c8 ("pl031: Correctly migrate state when using -rtc clock=host"). Migrate the tick_offset directly, adding it as a version-dependent field to VMState. Keep the old behavior when migrating from previous versions. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2033 Signed-off-by: Rodrigo Dias Correa Reviewed-by: Alistair Francis Message-ID: <20250114212150.228241-1-r@drigo.nl> Signed-off-by: Alistair Francis --- hw/rtc/goldfish_rtc.c | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/hw/rtc/goldfish_rtc.c b/hw/rtc/goldfish_rtc.c index fa1d9051f4a5..0f1b53e0e4d3 100644 --- a/hw/rtc/goldfish_rtc.c +++ b/hw/rtc/goldfish_rtc.c @@ -178,38 +178,21 @@ static void goldfish_rtc_write(void *opaque, hwaddr offset, trace_goldfish_rtc_write(offset, value); } -static int goldfish_rtc_pre_save(void *opaque) -{ - uint64_t delta; - GoldfishRTCState *s = opaque; - - /* - * We want to migrate this offset, which sounds straightforward. - * Unfortunately, we cannot directly pass tick_offset because - * rtc_clock on destination Host might not be same source Host. - * - * To tackle, this we pass tick_offset relative to vm_clock from - * source Host and make it relative to rtc_clock at destination Host. - */ - delta = qemu_clock_get_ns(rtc_clock) - - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - s->tick_offset_vmstate = s->tick_offset + delta; - - return 0; -} - static int goldfish_rtc_post_load(void *opaque, int version_id) { - uint64_t delta; GoldfishRTCState *s = opaque; - /* - * We extract tick_offset from tick_offset_vmstate by doing - * reverse math compared to pre_save() function. - */ - delta = qemu_clock_get_ns(rtc_clock) - - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - s->tick_offset = s->tick_offset_vmstate - delta; + if (version_id < 3) { + /* + * Previous versions didn't migrate tick_offset directly. Instead, they + * migrated tick_offset_vmstate, which is a recalculation based on + * QEMU_CLOCK_VIRTUAL. We use tick_offset_vmstate when migrating from + * older versions. + */ + uint64_t delta = qemu_clock_get_ns(rtc_clock) - + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->tick_offset = s->tick_offset_vmstate - delta; + } goldfish_rtc_set_alarm(s); @@ -239,8 +222,7 @@ static const MemoryRegionOps goldfish_rtc_ops[2] = { static const VMStateDescription goldfish_rtc_vmstate = { .name = TYPE_GOLDFISH_RTC, - .version_id = 2, - .pre_save = goldfish_rtc_pre_save, + .version_id = 3, .post_load = goldfish_rtc_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT64(tick_offset_vmstate, GoldfishRTCState), @@ -249,6 +231,7 @@ static const VMStateDescription goldfish_rtc_vmstate = { VMSTATE_UINT32(irq_pending, GoldfishRTCState), VMSTATE_UINT32(irq_enabled, GoldfishRTCState), VMSTATE_UINT32(time_high, GoldfishRTCState), + VMSTATE_UINT64_V(tick_offset, GoldfishRTCState, 3), VMSTATE_END_OF_LIST() } }; From f73456781bf10a96849e2929488634f61eec8c5c Mon Sep 17 00:00:00 2001 From: Vasilis Liaskovitis Date: Thu, 16 Jan 2025 17:10:07 +0100 Subject: [PATCH 0366/1179] hw/riscv/virt: Add serial alias in DTB Add an "aliases" node with a "serial0" entry for the single UART in the riscv virt machine. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2774 Signed-off-by: Vasilis Liaskovitis Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250116161007.39710-1-vliaskovitis@suse.com> Signed-off-by: Alistair Francis --- hw/riscv/virt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 241389d72f8a..dae46f4733cd 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -971,6 +971,7 @@ static void create_fdt_uart(RISCVVirtState *s, const MemMapEntry *memmap, } qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", name); + qemu_fdt_setprop_string(ms->fdt, "/aliases", "serial0", name); } static void create_fdt_rtc(RISCVVirtState *s, const MemMapEntry *memmap, @@ -1180,6 +1181,8 @@ static void create_fdt(RISCVVirtState *s, const MemMapEntry *memmap) qemu_fdt_setprop(ms->fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed)); + qemu_fdt_add_subnode(ms->fdt, "/aliases"); + create_fdt_flash(s, memmap); create_fdt_fw_cfg(s, memmap); create_fdt_pmu(s); From 4a16a1a7addaafbe65b5a7626cbe880fd9aa0c94 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Tue, 28 Jan 2025 16:05:46 +1000 Subject: [PATCH 0367/1179] MAINTAINERS: Remove Bin Meng from RISC-V maintainers Bin Meng has been a long time contributor and maintainer for QEMU RISC-V and has been very beneficial to the RISC-V ecosystem. Unfortunately his email has started to bounce so this patch is removing them from MAINTAINERS. If in the future Bin Meng wants to return we will happily re-add them. Note that I'm not removing Bin Meng as a "SD (Secure Card)" maintainer. Signed-off-by: Alistair Francis Acked-by: Daniel Henrique Barboza Message-ID: <20250128060546.1374394-1-alistair.francis@wdc.com> Signed-off-by: Alistair Francis --- MAINTAINERS | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 2e7fc6fa912a..433cf66e8f1b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -319,7 +319,6 @@ F: tests/functional/test_ppc_74xx.py RISC-V TCG CPUs M: Palmer Dabbelt M: Alistair Francis -M: Bin Meng R: Weiwei Li R: Daniel Henrique Barboza R: Liu Zhiwei @@ -1618,7 +1617,6 @@ F: include/hw/riscv/opentitan.h F: include/hw/*/ibex_*.h Microchip PolarFire SoC Icicle Kit -M: Bin Meng L: qemu-riscv@nongnu.org S: Supported F: docs/system/riscv/microchip-icicle-kit.rst @@ -1645,7 +1643,6 @@ F: include/hw/char/shakti_uart.h SiFive Machines M: Alistair Francis -M: Bin Meng M: Palmer Dabbelt L: qemu-riscv@nongnu.org S: Supported @@ -3747,7 +3744,7 @@ S: Orphan F: hw/i386/amd_iommu.? OpenSBI Firmware -M: Bin Meng +L: qemu-riscv@nongnu.org S: Supported F: pc-bios/opensbi-* F: .gitlab-ci.d/opensbi.yml From b638f679fede4835474a16cd423cb7e77ff6e7cc Mon Sep 17 00:00:00 2001 From: Rajnesh Kanwal Date: Wed, 5 Feb 2025 11:18:45 +0000 Subject: [PATCH 0368/1179] target/riscv: Remove obsolete sfence.vm instruction Signed-off-by: Rajnesh Kanwal Reviewed-by: Alistair Francis Reviewed-by: Jason Chien Message-ID: <20250205-b4-ctr_upstream_v6-v6-1-439d8e06c8ef@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/insn32.decode | 1 - target/riscv/insn_trans/trans_privileged.c.inc | 5 ----- 2 files changed, 6 deletions(-) diff --git a/target/riscv/insn32.decode b/target/riscv/insn32.decode index 942c434c6e8c..a98dab020576 100644 --- a/target/riscv/insn32.decode +++ b/target/riscv/insn32.decode @@ -119,7 +119,6 @@ sret 0001000 00010 00000 000 00000 1110011 mret 0011000 00010 00000 000 00000 1110011 wfi 0001000 00101 00000 000 00000 1110011 sfence_vma 0001001 ..... ..... 000 00000 1110011 @sfence_vma -sfence_vm 0001000 00100 ..... 000 00000 1110011 @sfence_vm # *** NMI *** mnret 0111000 00010 00000 000 00000 1110011 diff --git a/target/riscv/insn_trans/trans_privileged.c.inc b/target/riscv/insn_trans/trans_privileged.c.inc index 73f940d40613..27d5558d63ed 100644 --- a/target/riscv/insn_trans/trans_privileged.c.inc +++ b/target/riscv/insn_trans/trans_privileged.c.inc @@ -147,8 +147,3 @@ static bool trans_sfence_vma(DisasContext *ctx, arg_sfence_vma *a) #endif return false; } - -static bool trans_sfence_vm(DisasContext *ctx, arg_sfence_vm *a) -{ - return false; -} From 3f833f8920d815caa6cd0215a5707a03426ba574 Mon Sep 17 00:00:00 2001 From: Rajnesh Kanwal Date: Wed, 5 Feb 2025 11:18:46 +0000 Subject: [PATCH 0369/1179] target/riscv: Add Control Transfer Records CSR definitions. The Control Transfer Records (CTR) extension provides a method to record a limited branch history in register-accessible internal chip storage. This extension is similar to Arch LBR in x86 and BRBE in ARM. The Extension has been stable and the latest release can be found here https://github.com/riscv/riscv-control-transfer-records/releases/tag/v1.0_rc5 Signed-off-by: Rajnesh Kanwal Acked-by: Alistair Francis Message-ID: <20250205-b4-ctr_upstream_v6-v6-2-439d8e06c8ef@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/cpu_bits.h | 145 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index f97c48a3943f..70ef443c9947 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -247,6 +247,17 @@ #define CSR_SIEH 0x114 #define CSR_SIPH 0x154 +/* Machine-Level Control transfer records CSRs */ +#define CSR_MCTRCTL 0x34e + +/* Supervisor-Level Control transfer records CSRs */ +#define CSR_SCTRCTL 0x14e +#define CSR_SCTRSTATUS 0x14f +#define CSR_SCTRDEPTH 0x15f + +/* VS-Level Control transfer records CSRs */ +#define CSR_VSCTRCTL 0x24e + /* Hpervisor CSRs */ #define CSR_HSTATUS 0x600 #define CSR_HEDELEG 0x602 @@ -344,6 +355,7 @@ #define SMSTATEEN0_CS (1ULL << 0) #define SMSTATEEN0_FCSR (1ULL << 1) #define SMSTATEEN0_JVT (1ULL << 2) +#define SMSTATEEN0_CTR (1ULL << 54) #define SMSTATEEN0_P1P13 (1ULL << 56) #define SMSTATEEN0_HSCONTXT (1ULL << 57) #define SMSTATEEN0_IMSIC (1ULL << 58) @@ -825,6 +837,139 @@ typedef enum RISCVException { #define HENVCFGH_PBMTE MENVCFGH_PBMTE #define HENVCFGH_STCE MENVCFGH_STCE +/* Offsets for every pair of control bits per each priv level */ +#define XS_OFFSET 0ULL +#define U_OFFSET 2ULL +#define S_OFFSET 5ULL +#define M_OFFSET 8ULL + +#define PM_XS_BITS (EXT_STATUS_MASK << XS_OFFSET) +#define U_PM_ENABLE (PM_ENABLE << U_OFFSET) +#define U_PM_CURRENT (PM_CURRENT << U_OFFSET) +#define U_PM_INSN (PM_INSN << U_OFFSET) +#define S_PM_ENABLE (PM_ENABLE << S_OFFSET) +#define S_PM_CURRENT (PM_CURRENT << S_OFFSET) +#define S_PM_INSN (PM_INSN << S_OFFSET) +#define M_PM_ENABLE (PM_ENABLE << M_OFFSET) +#define M_PM_CURRENT (PM_CURRENT << M_OFFSET) +#define M_PM_INSN (PM_INSN << M_OFFSET) + +/* mmte CSR bits */ +#define MMTE_PM_XS_BITS PM_XS_BITS +#define MMTE_U_PM_ENABLE U_PM_ENABLE +#define MMTE_U_PM_CURRENT U_PM_CURRENT +#define MMTE_U_PM_INSN U_PM_INSN +#define MMTE_S_PM_ENABLE S_PM_ENABLE +#define MMTE_S_PM_CURRENT S_PM_CURRENT +#define MMTE_S_PM_INSN S_PM_INSN +#define MMTE_M_PM_ENABLE M_PM_ENABLE +#define MMTE_M_PM_CURRENT M_PM_CURRENT +#define MMTE_M_PM_INSN M_PM_INSN +#define MMTE_MASK (MMTE_U_PM_ENABLE | MMTE_U_PM_CURRENT | MMTE_U_PM_INSN | \ + MMTE_S_PM_ENABLE | MMTE_S_PM_CURRENT | MMTE_S_PM_INSN | \ + MMTE_M_PM_ENABLE | MMTE_M_PM_CURRENT | MMTE_M_PM_INSN | \ + MMTE_PM_XS_BITS) + +/* (v)smte CSR bits */ +#define SMTE_PM_XS_BITS PM_XS_BITS +#define SMTE_U_PM_ENABLE U_PM_ENABLE +#define SMTE_U_PM_CURRENT U_PM_CURRENT +#define SMTE_U_PM_INSN U_PM_INSN +#define SMTE_S_PM_ENABLE S_PM_ENABLE +#define SMTE_S_PM_CURRENT S_PM_CURRENT +#define SMTE_S_PM_INSN S_PM_INSN +#define SMTE_MASK (SMTE_U_PM_ENABLE | SMTE_U_PM_CURRENT | SMTE_U_PM_INSN | \ + SMTE_S_PM_ENABLE | SMTE_S_PM_CURRENT | SMTE_S_PM_INSN | \ + SMTE_PM_XS_BITS) + +/* umte CSR bits */ +#define UMTE_U_PM_ENABLE U_PM_ENABLE +#define UMTE_U_PM_CURRENT U_PM_CURRENT +#define UMTE_U_PM_INSN U_PM_INSN +#define UMTE_MASK (UMTE_U_PM_ENABLE | MMTE_U_PM_CURRENT | UMTE_U_PM_INSN) + +/* CTR control register commom fields */ +#define XCTRCTL_U BIT_ULL(0) +#define XCTRCTL_S BIT_ULL(1) +#define XCTRCTL_RASEMU BIT_ULL(7) +#define XCTRCTL_STE BIT_ULL(8) +#define XCTRCTL_BPFRZ BIT_ULL(11) +#define XCTRCTL_LCOFIFRZ BIT_ULL(12) +#define XCTRCTL_EXCINH BIT_ULL(33) +#define XCTRCTL_INTRINH BIT_ULL(34) +#define XCTRCTL_TRETINH BIT_ULL(35) +#define XCTRCTL_NTBREN BIT_ULL(36) +#define XCTRCTL_TKBRINH BIT_ULL(37) +#define XCTRCTL_INDCALLINH BIT_ULL(40) +#define XCTRCTL_DIRCALLINH BIT_ULL(41) +#define XCTRCTL_INDJMPINH BIT_ULL(42) +#define XCTRCTL_DIRJMPINH BIT_ULL(43) +#define XCTRCTL_CORSWAPINH BIT_ULL(44) +#define XCTRCTL_RETINH BIT_ULL(45) +#define XCTRCTL_INDLJMPINH BIT_ULL(46) +#define XCTRCTL_DIRLJMPINH BIT_ULL(47) + +#define XCTRCTL_MASK (XCTRCTL_U | XCTRCTL_S | XCTRCTL_RASEMU | \ + XCTRCTL_STE | XCTRCTL_BPFRZ | XCTRCTL_LCOFIFRZ | \ + XCTRCTL_EXCINH | XCTRCTL_INTRINH | XCTRCTL_TRETINH | \ + XCTRCTL_NTBREN | XCTRCTL_TKBRINH | XCTRCTL_INDCALLINH | \ + XCTRCTL_DIRCALLINH | XCTRCTL_INDJMPINH | \ + XCTRCTL_DIRJMPINH | XCTRCTL_CORSWAPINH | \ + XCTRCTL_RETINH | XCTRCTL_INDLJMPINH | XCTRCTL_DIRLJMPINH) + +#define XCTRCTL_INH_START 32U + +/* CTR mctrctl bits */ +#define MCTRCTL_M BIT_ULL(2) +#define MCTRCTL_MTE BIT_ULL(9) + +#define MCTRCTL_MASK (XCTRCTL_MASK | MCTRCTL_M | MCTRCTL_MTE) +#define SCTRCTL_MASK XCTRCTL_MASK +#define VSCTRCTL_MASK XCTRCTL_MASK + +/* sctrstatus CSR bits. */ +#define SCTRSTATUS_WRPTR_MASK 0xFF +#define SCTRSTATUS_FROZEN BIT(31) +#define SCTRSTATUS_MASK (SCTRSTATUS_WRPTR_MASK | SCTRSTATUS_FROZEN) + +/* sctrdepth CSR bits. */ +#define SCTRDEPTH_MASK 0x7 +#define SCTRDEPTH_MIN 0U /* 16 Entries. */ +#define SCTRDEPTH_MAX 4U /* 256 Entries. */ + +#define CTR_ENTRIES_FIRST 0x200 +#define CTR_ENTRIES_LAST 0x2ff + +#define CTRSOURCE_VALID BIT(0) +#define CTRTARGET_MISP BIT(0) + +#define CTRDATA_TYPE_MASK 0xF +#define CTRDATA_CCV BIT(15) +#define CTRDATA_CCM_MASK 0xFFF0000 +#define CTRDATA_CCE_MASK 0xF0000000 + +#define CTRDATA_MASK (CTRDATA_TYPE_MASK | CTRDATA_CCV | \ + CTRDATA_CCM_MASK | CTRDATA_CCE_MASK) + +typedef enum CTRType { + CTRDATA_TYPE_NONE = 0, + CTRDATA_TYPE_EXCEPTION = 1, + CTRDATA_TYPE_INTERRUPT = 2, + CTRDATA_TYPE_EXCEP_INT_RET = 3, + CTRDATA_TYPE_NONTAKEN_BRANCH = 4, + CTRDATA_TYPE_TAKEN_BRANCH = 5, + CTRDATA_TYPE_RESERVED_0 = 6, + CTRDATA_TYPE_RESERVED_1 = 7, + CTRDATA_TYPE_INDIRECT_CALL = 8, + CTRDATA_TYPE_DIRECT_CALL = 9, + CTRDATA_TYPE_INDIRECT_JUMP = 10, + CTRDATA_TYPE_DIRECT_JUMP = 11, + CTRDATA_TYPE_CO_ROUTINE_SWAP = 12, + CTRDATA_TYPE_RETURN = 13, + CTRDATA_TYPE_OTHER_INDIRECT_JUMP = 14, + CTRDATA_TYPE_OTHER_DIRECT_JUMP = 15, +} CTRType; + /* MISELECT, SISELECT, and VSISELECT bits (AIA) */ #define ISELECT_IPRIO0 0x30 #define ISELECT_IPRIO15 0x3f From c48bd18eaeb676a7236030eb9b7984b9244d7750 Mon Sep 17 00:00:00 2001 From: Rajnesh Kanwal Date: Wed, 5 Feb 2025 11:18:47 +0000 Subject: [PATCH 0370/1179] target/riscv: Add support for Control Transfer Records extension CSRs. This commit adds support for [m|s|vs]ctrcontrol, sctrstatus and sctrdepth CSRs handling. Signed-off-by: Rajnesh Kanwal Acked-by: Alistair Francis Message-ID: <20250205-b4-ctr_upstream_v6-v6-3-439d8e06c8ef@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.h | 5 ++ target/riscv/cpu_cfg.h | 2 + target/riscv/csr.c | 144 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 986131a19198..ae355248f3c5 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -313,6 +313,11 @@ struct CPUArchState { target_ulong mcause; target_ulong mtval; /* since: priv-1.10.0 */ + uint64_t mctrctl; + uint32_t sctrdepth; + uint32_t sctrstatus; + uint64_t vsctrctl; + /* Machine and Supervisor interrupt priorities */ uint8_t miprio[64]; uint8_t siprio[64]; diff --git a/target/riscv/cpu_cfg.h b/target/riscv/cpu_cfg.h index b410b1e60383..3f3c1118c08f 100644 --- a/target/riscv/cpu_cfg.h +++ b/target/riscv/cpu_cfg.h @@ -133,6 +133,8 @@ struct RISCVCPUConfig { bool ext_zvfhmin; bool ext_smaia; bool ext_ssaia; + bool ext_smctr; + bool ext_ssctr; bool ext_sscofpmf; bool ext_smepmp; bool ext_smrnmi; diff --git a/target/riscv/csr.c b/target/riscv/csr.c index dc0a88a0f085..ab295d2ef3c3 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -635,6 +635,48 @@ static RISCVException hgatp(CPURISCVState *env, int csrno) return hmode(env, csrno); } +/* + * M-mode: + * Without ext_smctr raise illegal inst excep. + * Otherwise everything is accessible to m-mode. + * + * S-mode: + * Without ext_ssctr or mstateen.ctr raise illegal inst excep. + * Otherwise everything other than mctrctl is accessible. + * + * VS-mode: + * Without ext_ssctr or mstateen.ctr raise illegal inst excep. + * Without hstateen.ctr raise virtual illegal inst excep. + * Otherwise allow sctrctl (vsctrctl), sctrstatus, 0x200-0x2ff entry range. + * Always raise illegal instruction exception for sctrdepth. + */ +static RISCVException ctr_mmode(CPURISCVState *env, int csrno) +{ + /* Check if smctr-ext is present */ + if (riscv_cpu_cfg(env)->ext_smctr) { + return RISCV_EXCP_NONE; + } + + return RISCV_EXCP_ILLEGAL_INST; +} + +static RISCVException ctr_smode(CPURISCVState *env, int csrno) +{ + const RISCVCPUConfig *cfg = riscv_cpu_cfg(env); + + if (!cfg->ext_smctr && !cfg->ext_ssctr) { + return RISCV_EXCP_ILLEGAL_INST; + } + + RISCVException ret = smstateen_acc_ok(env, 0, SMSTATEEN0_CTR); + if (ret == RISCV_EXCP_NONE && csrno == CSR_SCTRDEPTH && + env->virt_enabled) { + return RISCV_EXCP_VIRT_INSTRUCTION_FAULT; + } + + return ret; +} + static RISCVException aia_hmode(CPURISCVState *env, int csrno) { int ret; @@ -3216,6 +3258,10 @@ static RISCVException write_mstateen0(CPURISCVState *env, int csrno, wr_mask |= (SMSTATEEN0_AIA | SMSTATEEN0_IMSIC); } + if (riscv_cpu_cfg(env)->ext_ssctr) { + wr_mask |= SMSTATEEN0_CTR; + } + return write_mstateen(env, csrno, wr_mask, new_val); } @@ -3255,6 +3301,10 @@ static RISCVException write_mstateen0h(CPURISCVState *env, int csrno, wr_mask |= SMSTATEEN0_P1P13; } + if (riscv_cpu_cfg(env)->ext_ssctr) { + wr_mask |= SMSTATEEN0_CTR; + } + return write_mstateenh(env, csrno, wr_mask, new_val); } @@ -3309,6 +3359,10 @@ static RISCVException write_hstateen0(CPURISCVState *env, int csrno, wr_mask |= (SMSTATEEN0_AIA | SMSTATEEN0_IMSIC); } + if (riscv_cpu_cfg(env)->ext_ssctr) { + wr_mask |= SMSTATEEN0_CTR; + } + return write_hstateen(env, csrno, wr_mask, new_val); } @@ -3348,6 +3402,10 @@ static RISCVException write_hstateen0h(CPURISCVState *env, int csrno, { uint64_t wr_mask = SMSTATEEN_STATEEN | SMSTATEEN0_HSENVCFG; + if (riscv_cpu_cfg(env)->ext_ssctr) { + wr_mask |= SMSTATEEN0_CTR; + } + return write_hstateenh(env, csrno, wr_mask, new_val); } @@ -4068,6 +4126,86 @@ static RISCVException write_satp(CPURISCVState *env, int csrno, return RISCV_EXCP_NONE; } +static RISCVException rmw_sctrdepth(CPURISCVState *env, int csrno, + target_ulong *ret_val, + target_ulong new_val, target_ulong wr_mask) +{ + uint64_t mask = wr_mask & SCTRDEPTH_MASK; + + if (ret_val) { + *ret_val = env->sctrdepth; + } + + env->sctrdepth = (env->sctrdepth & ~mask) | (new_val & mask); + + /* Correct depth. */ + if (mask) { + uint64_t depth = get_field(env->sctrdepth, SCTRDEPTH_MASK); + + if (depth > SCTRDEPTH_MAX) { + depth = SCTRDEPTH_MAX; + env->sctrdepth = set_field(env->sctrdepth, SCTRDEPTH_MASK, depth); + } + + /* Update sctrstatus.WRPTR with a legal value */ + depth = 16 << depth; + env->sctrstatus = + env->sctrstatus & (~SCTRSTATUS_WRPTR_MASK | (depth - 1)); + } + + return RISCV_EXCP_NONE; +} + +static RISCVException rmw_sctrstatus(CPURISCVState *env, int csrno, + target_ulong *ret_val, + target_ulong new_val, target_ulong wr_mask) +{ + uint32_t depth = 16 << get_field(env->sctrdepth, SCTRDEPTH_MASK); + uint32_t mask = wr_mask & SCTRSTATUS_MASK; + + if (ret_val) { + *ret_val = env->sctrstatus; + } + + env->sctrstatus = (env->sctrstatus & ~mask) | (new_val & mask); + + /* Update sctrstatus.WRPTR with a legal value */ + env->sctrstatus = env->sctrstatus & (~SCTRSTATUS_WRPTR_MASK | (depth - 1)); + + return RISCV_EXCP_NONE; +} + +static RISCVException rmw_xctrctl(CPURISCVState *env, int csrno, + target_ulong *ret_val, + target_ulong new_val, target_ulong wr_mask) +{ + uint64_t csr_mask, mask = wr_mask; + uint64_t *ctl_ptr = &env->mctrctl; + + if (csrno == CSR_MCTRCTL) { + csr_mask = MCTRCTL_MASK; + } else if (csrno == CSR_SCTRCTL && !env->virt_enabled) { + csr_mask = SCTRCTL_MASK; + } else { + /* + * This is for csrno == CSR_SCTRCTL and env->virt_enabled == true + * or csrno == CSR_VSCTRCTL. + */ + csr_mask = VSCTRCTL_MASK; + ctl_ptr = &env->vsctrctl; + } + + mask &= csr_mask; + + if (ret_val) { + *ret_val = *ctl_ptr & csr_mask; + } + + *ctl_ptr = (*ctl_ptr & ~mask) | (new_val & mask); + + return RISCV_EXCP_NONE; +} + static RISCVException read_vstopi(CPURISCVState *env, int csrno, target_ulong *val) { @@ -5821,6 +5959,12 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { [CSR_TINFO] = { "tinfo", debug, read_tinfo, write_ignore }, [CSR_MCONTEXT] = { "mcontext", debug, read_mcontext, write_mcontext }, + [CSR_MCTRCTL] = { "mctrctl", ctr_mmode, NULL, NULL, rmw_xctrctl }, + [CSR_SCTRCTL] = { "sctrctl", ctr_smode, NULL, NULL, rmw_xctrctl }, + [CSR_VSCTRCTL] = { "vsctrctl", ctr_smode, NULL, NULL, rmw_xctrctl }, + [CSR_SCTRDEPTH] = { "sctrdepth", ctr_smode, NULL, NULL, rmw_sctrdepth }, + [CSR_SCTRSTATUS] = { "sctrstatus", ctr_smode, NULL, NULL, rmw_sctrstatus }, + /* Performance Counters */ [CSR_HPMCOUNTER3] = { "hpmcounter3", ctr, read_hpmcounter }, [CSR_HPMCOUNTER4] = { "hpmcounter4", ctr, read_hpmcounter }, From 4ff7a27adce4c880d2137788da0fc57d75ee80be Mon Sep 17 00:00:00 2001 From: Rajnesh Kanwal Date: Wed, 5 Feb 2025 11:18:48 +0000 Subject: [PATCH 0371/1179] target/riscv: Add support to record CTR entries. This commit adds logic to records CTR entries of different types and adds required hooks in TCG and interrupt/Exception logic to record events. This commit also adds support to invoke freeze CTR logic for breakpoint exceptions and counter overflow interrupts. Signed-off-by: Rajnesh Kanwal Acked-by: Alistair Francis Message-ID: <20250205-b4-ctr_upstream_v6-v6-4-439d8e06c8ef@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.h | 7 + target/riscv/cpu_helper.c | 259 ++++++++++++++++++ target/riscv/helper.h | 1 + .../riscv/insn_trans/trans_privileged.c.inc | 2 + target/riscv/insn_trans/trans_rvi.c.inc | 75 +++++ target/riscv/insn_trans/trans_rvzce.c.inc | 21 ++ target/riscv/op_helper.c | 19 ++ target/riscv/translate.c | 46 ++++ 8 files changed, 430 insertions(+) diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index ae355248f3c5..9e92144b6133 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -318,6 +318,10 @@ struct CPUArchState { uint32_t sctrstatus; uint64_t vsctrctl; + uint64_t ctr_src[16 << SCTRDEPTH_MAX]; + uint64_t ctr_dst[16 << SCTRDEPTH_MAX]; + uint64_t ctr_data[16 << SCTRDEPTH_MAX]; + /* Machine and Supervisor interrupt priorities */ uint8_t miprio[64]; uint8_t siprio[64]; @@ -613,6 +617,9 @@ RISCVException smstateen_acc_ok(CPURISCVState *env, int index, uint64_t bit); void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv, bool virt_en); +void riscv_ctr_add_entry(CPURISCVState *env, target_long src, target_long dst, + enum CTRType type, target_ulong prev_priv, bool prev_virt); + void riscv_translate_init(void); void riscv_translate_code(CPUState *cs, TranslationBlock *tb, int *max_insns, vaddr pc, void *host_pc); diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 29dc721c5dc6..7dbdb34b17c4 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -875,6 +875,247 @@ void riscv_cpu_set_aia_ireg_rmw_fn(CPURISCVState *env, uint32_t priv, } } +static void riscv_ctr_freeze(CPURISCVState *env, uint64_t freeze_mask, + bool virt) +{ + uint64_t ctl = virt ? env->vsctrctl : env->mctrctl; + + assert((freeze_mask & (~(XCTRCTL_BPFRZ | XCTRCTL_LCOFIFRZ))) == 0); + + if (ctl & freeze_mask) { + env->sctrstatus |= SCTRSTATUS_FROZEN; + } +} + +static uint64_t riscv_ctr_priv_to_mask(target_ulong priv, bool virt) +{ + switch (priv) { + case PRV_M: + return MCTRCTL_M; + case PRV_S: + if (virt) { + return XCTRCTL_S; + } + return XCTRCTL_S; + case PRV_U: + if (virt) { + return XCTRCTL_U; + } + return XCTRCTL_U; + } + + g_assert_not_reached(); +} + +static uint64_t riscv_ctr_get_control(CPURISCVState *env, target_long priv, + bool virt) +{ + switch (priv) { + case PRV_M: + return env->mctrctl; + case PRV_S: + case PRV_U: + if (virt) { + return env->vsctrctl; + } + return env->mctrctl; + } + + g_assert_not_reached(); +} + +/* + * This function assumes that src privilege and target privilege are not same + * and src privilege is less than target privilege. This includes the virtual + * state as well. + */ +static bool riscv_ctr_check_xte(CPURISCVState *env, target_long src_prv, + bool src_virt) +{ + target_long tgt_prv = env->priv; + bool res = true; + + /* + * VS and U mode are same in terms of xTE bits required to record an + * external trap. See 6.1.2. External Traps, table 8 External Trap Enable + * Requirements. This changes VS to U to simplify the logic a bit. + */ + if (src_virt && src_prv == PRV_S) { + src_prv = PRV_U; + } else if (env->virt_enabled && tgt_prv == PRV_S) { + tgt_prv = PRV_U; + } + + /* VU mode is an outlier here. */ + if (src_virt && src_prv == PRV_U) { + res &= !!(env->vsctrctl & XCTRCTL_STE); + } + + switch (src_prv) { + case PRV_U: + if (tgt_prv == PRV_U) { + break; + } + res &= !!(env->mctrctl & XCTRCTL_STE); + /* fall-through */ + case PRV_S: + if (tgt_prv == PRV_S) { + break; + } + res &= !!(env->mctrctl & MCTRCTL_MTE); + /* fall-through */ + case PRV_M: + break; + } + + return res; +} + +/* + * Special cases for traps and trap returns: + * + * 1- Traps, and trap returns, between enabled modes are recorded as normal. + * 2- Traps from an inhibited mode to an enabled mode, and trap returns from an + * enabled mode back to an inhibited mode, are partially recorded. In such + * cases, the PC from the inhibited mode (source PC for traps, and target PC + * for trap returns) is 0. + * + * 3- Trap returns from an inhibited mode to an enabled mode are not recorded. + * Traps from an enabled mode to an inhibited mode, known as external traps, + * receive special handling. + * By default external traps are not recorded, but a handshake mechanism exists + * to allow partial recording. Software running in the target mode of the trap + * can opt-in to allowing CTR to record traps into that mode even when the mode + * is inhibited. The MTE, STE, and VSTE bits allow M-mode, S-mode, and VS-mode, + * respectively, to opt-in. When an External Trap occurs, and xTE=1, such that + * x is the target privilege mode of the trap, will CTR record the trap. In such + * cases, the target PC is 0. + */ +/* + * CTR arrays are implemented as circular buffers and new entry is stored at + * sctrstatus.WRPTR, but they are presented to software as moving circular + * buffers. Which means, software get's the illusion that whenever a new entry + * is added the whole buffer is moved by one place and the new entry is added at + * the start keeping new entry at idx 0 and older ones follow. + * + * Depth = 16. + * + * buffer [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [A] [B] [C] [D] [E] [F] + * WRPTR W + * entry 7 6 5 4 3 2 1 0 F E D C B A 9 8 + * + * When a new entry is added: + * buffer [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [A] [B] [C] [D] [E] [F] + * WRPTR W + * entry 8 7 6 5 4 3 2 1 0 F E D C B A 9 + * + * entry here denotes the logical entry number that software can access + * using ctrsource, ctrtarget and ctrdata registers. So xiselect 0x200 + * will return entry 0 i-e buffer[8] and 0x201 will return entry 1 i-e + * buffer[7]. Here is how we convert entry to buffer idx. + * + * entry = isel - CTR_ENTRIES_FIRST; + * idx = (sctrstatus.WRPTR - entry - 1) & (depth - 1); + */ +void riscv_ctr_add_entry(CPURISCVState *env, target_long src, target_long dst, + enum CTRType type, target_ulong src_priv, bool src_virt) +{ + bool tgt_virt = env->virt_enabled; + uint64_t src_mask = riscv_ctr_priv_to_mask(src_priv, src_virt); + uint64_t tgt_mask = riscv_ctr_priv_to_mask(env->priv, tgt_virt); + uint64_t src_ctrl = riscv_ctr_get_control(env, src_priv, src_virt); + uint64_t tgt_ctrl = riscv_ctr_get_control(env, env->priv, tgt_virt); + uint64_t depth, head; + bool ext_trap = false; + + /* + * Return immediately if both target and src recording is disabled or if + * CTR is in frozen state. + */ + if ((!(src_ctrl & src_mask) && !(tgt_ctrl & tgt_mask)) || + env->sctrstatus & SCTRSTATUS_FROZEN) { + return; + } + + /* + * With RAS Emul enabled, only allow Indirect, direct calls, Function + * returns and Co-routine swap types. + */ + if (tgt_ctrl & XCTRCTL_RASEMU && + type != CTRDATA_TYPE_INDIRECT_CALL && + type != CTRDATA_TYPE_DIRECT_CALL && + type != CTRDATA_TYPE_RETURN && + type != CTRDATA_TYPE_CO_ROUTINE_SWAP) { + return; + } + + if (type == CTRDATA_TYPE_EXCEPTION || type == CTRDATA_TYPE_INTERRUPT) { + /* Case 2 for traps. */ + if (!(src_ctrl & src_mask)) { + src = 0; + } else if (!(tgt_ctrl & tgt_mask)) { + /* Check if target priv-mode has allowed external trap recording. */ + if (!riscv_ctr_check_xte(env, src_priv, src_virt)) { + return; + } + + ext_trap = true; + dst = 0; + } + } else if (type == CTRDATA_TYPE_EXCEP_INT_RET) { + /* + * Case 3 for trap returns. Trap returns from inhibited mode are not + * recorded. + */ + if (!(src_ctrl & src_mask)) { + return; + } + + /* Case 2 for trap returns. */ + if (!(tgt_ctrl & tgt_mask)) { + dst = 0; + } + } + + /* Ignore filters in case of RASEMU mode or External trap. */ + if (!(tgt_ctrl & XCTRCTL_RASEMU) && !ext_trap) { + /* + * Check if the specific type is inhibited. Not taken branch filter is + * an enable bit and needs to be checked separatly. + */ + bool check = tgt_ctrl & BIT_ULL(type + XCTRCTL_INH_START); + if ((type == CTRDATA_TYPE_NONTAKEN_BRANCH && !check) || + (type != CTRDATA_TYPE_NONTAKEN_BRANCH && check)) { + return; + } + } + + head = get_field(env->sctrstatus, SCTRSTATUS_WRPTR_MASK); + + depth = 16 << get_field(env->sctrdepth, SCTRDEPTH_MASK); + if (tgt_ctrl & XCTRCTL_RASEMU && type == CTRDATA_TYPE_RETURN) { + head = (head - 1) & (depth - 1); + + env->ctr_src[head] &= ~CTRSOURCE_VALID; + env->sctrstatus = + set_field(env->sctrstatus, SCTRSTATUS_WRPTR_MASK, head); + return; + } + + /* In case of Co-routine SWAP we overwrite latest entry. */ + if (tgt_ctrl & XCTRCTL_RASEMU && type == CTRDATA_TYPE_CO_ROUTINE_SWAP) { + head = (head - 1) & (depth - 1); + } + + env->ctr_src[head] = src | CTRSOURCE_VALID; + env->ctr_dst[head] = dst & ~CTRTARGET_MISP; + env->ctr_data[head] = set_field(0, CTRDATA_TYPE_MASK, type); + + head = (head + 1) & (depth - 1); + + env->sctrstatus = set_field(env->sctrstatus, SCTRSTATUS_WRPTR_MASK, head); +} + void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv, bool virt_en) { g_assert(newpriv <= PRV_M && newpriv != PRV_RESERVED); @@ -1993,10 +2234,13 @@ void riscv_cpu_do_interrupt(CPUState *cs) !(env->mip & (1ULL << cause)); bool smode_double_trap = false; uint64_t hdeleg = async ? env->hideleg : env->hedeleg; + const bool prev_virt = env->virt_enabled; + const target_ulong prev_priv = env->priv; target_ulong tval = 0; target_ulong tinst = 0; target_ulong htval = 0; target_ulong mtval2 = 0; + target_ulong src; int sxlen = 0; int mxlen = 16 << riscv_cpu_mxl(env); bool nnmi_excep = false; @@ -2182,6 +2426,8 @@ void riscv_cpu_do_interrupt(CPUState *cs) env->pc = (env->stvec >> 2 << 2) + ((async && (env->stvec & 3) == 1) ? cause * 4 : 0); riscv_cpu_set_mode(env, PRV_S, virt); + + src = env->sepc; } else { /* * If the hart encounters an exception while executing in M-mode @@ -2266,6 +2512,19 @@ void riscv_cpu_do_interrupt(CPUState *cs) ((async && (env->mtvec & 3) == 1) ? cause * 4 : 0); } riscv_cpu_set_mode(env, PRV_M, virt); + src = env->mepc; + } + + if (riscv_cpu_cfg(env)->ext_smctr || riscv_cpu_cfg(env)->ext_ssctr) { + if (async && cause == IRQ_PMU_OVF) { + riscv_ctr_freeze(env, XCTRCTL_LCOFIFRZ, virt); + } else if (!async && cause == RISCV_EXCP_BREAKPOINT) { + riscv_ctr_freeze(env, XCTRCTL_BPFRZ, virt); + } + + riscv_ctr_add_entry(env, src, env->pc, + async ? CTRDATA_TYPE_INTERRUPT : CTRDATA_TYPE_EXCEPTION, + prev_priv, prev_virt); } /* diff --git a/target/riscv/helper.h b/target/riscv/helper.h index 16ea240d26d3..163121ade599 100644 --- a/target/riscv/helper.h +++ b/target/riscv/helper.h @@ -136,6 +136,7 @@ DEF_HELPER_1(wfi, void, env) DEF_HELPER_1(wrs_nto, void, env) DEF_HELPER_1(tlb_flush, void, env) DEF_HELPER_1(tlb_flush_all, void, env) +DEF_HELPER_4(ctr_add_entry, void, env, tl, tl, tl) /* Native Debug */ DEF_HELPER_1(itrigger_match, void, env) #endif diff --git a/target/riscv/insn_trans/trans_privileged.c.inc b/target/riscv/insn_trans/trans_privileged.c.inc index 27d5558d63ed..ca52405d7da4 100644 --- a/target/riscv/insn_trans/trans_privileged.c.inc +++ b/target/riscv/insn_trans/trans_privileged.c.inc @@ -86,6 +86,7 @@ static bool trans_sret(DisasContext *ctx, arg_sret *a) if (has_ext(ctx, RVS)) { decode_save_opc(ctx, 0); translator_io_start(&ctx->base); + gen_update_pc(ctx, 0); gen_helper_sret(cpu_pc, tcg_env); exit_tb(ctx); /* no chaining */ ctx->base.is_jmp = DISAS_NORETURN; @@ -103,6 +104,7 @@ static bool trans_mret(DisasContext *ctx, arg_mret *a) #ifndef CONFIG_USER_ONLY decode_save_opc(ctx, 0); translator_io_start(&ctx->base); + gen_update_pc(ctx, 0); gen_helper_mret(cpu_pc, tcg_env); exit_tb(ctx); /* no chaining */ ctx->base.is_jmp = DISAS_NORETURN; diff --git a/target/riscv/insn_trans/trans_rvi.c.inc b/target/riscv/insn_trans/trans_rvi.c.inc index 96c218a9d787..b55f56a5eb2c 100644 --- a/target/riscv/insn_trans/trans_rvi.c.inc +++ b/target/riscv/insn_trans/trans_rvi.c.inc @@ -93,6 +93,51 @@ static bool trans_jal(DisasContext *ctx, arg_jal *a) return true; } +#ifndef CONFIG_USER_ONLY +/* + * Indirect calls + * - jalr x1, rs where rs != x5; + * - jalr x5, rs where rs != x1; + * - c.jalr rs1 where rs1 != x5; + * + * Indirect jumps + * - jalr x0, rs where rs != x1 and rs != x5; + * - c.jr rs1 where rs1 != x1 and rs1 != x5. + * + * Returns + * - jalr rd, rs where (rs == x1 or rs == x5) and rd != x1 and rd != x5; + * - c.jr rs1 where rs1 == x1 or rs1 == x5. + * + * Co-routine swap + * - jalr x1, x5; + * - jalr x5, x1; + * - c.jalr x5. + * + * Other indirect jumps + * - jalr rd, rs where rs != x1, rs != x5, rd != x0, rd != x1 and rd != x5. + */ +static void gen_ctr_jalr(DisasContext *ctx, arg_jalr *a, TCGv dest) +{ + TCGv src = tcg_temp_new(); + TCGv type; + + if ((a->rd == 1 && a->rs1 != 5) || (a->rd == 5 && a->rs1 != 1)) { + type = tcg_constant_tl(CTRDATA_TYPE_INDIRECT_CALL); + } else if (a->rd == 0 && a->rs1 != 1 && a->rs1 != 5) { + type = tcg_constant_tl(CTRDATA_TYPE_INDIRECT_JUMP); + } else if ((a->rs1 == 1 || a->rs1 == 5) && (a->rd != 1 && a->rd != 5)) { + type = tcg_constant_tl(CTRDATA_TYPE_RETURN); + } else if ((a->rs1 == 1 && a->rd == 5) || (a->rs1 == 5 && a->rd == 1)) { + type = tcg_constant_tl(CTRDATA_TYPE_CO_ROUTINE_SWAP); + } else { + type = tcg_constant_tl(CTRDATA_TYPE_OTHER_INDIRECT_JUMP); + } + + gen_pc_plus_diff(src, ctx, 0); + gen_helper_ctr_add_entry(tcg_env, src, dest, type); +} +#endif + static bool trans_jalr(DisasContext *ctx, arg_jalr *a) { TCGLabel *misaligned = NULL; @@ -117,6 +162,12 @@ static bool trans_jalr(DisasContext *ctx, arg_jalr *a) gen_pc_plus_diff(succ_pc, ctx, ctx->cur_insn_len); gen_set_gpr(ctx, a->rd, succ_pc); +#ifndef CONFIG_USER_ONLY + if (ctx->cfg_ptr->ext_smctr || ctx->cfg_ptr->ext_ssctr) { + gen_ctr_jalr(ctx, a, target_pc); + } +#endif + tcg_gen_mov_tl(cpu_pc, target_pc); if (ctx->fcfi_enabled) { /* @@ -231,6 +282,19 @@ static bool gen_branch(DisasContext *ctx, arg_b *a, TCGCond cond) } else { tcg_gen_brcond_tl(cond, src1, src2, l); } + +#ifndef CONFIG_USER_ONLY + if (ctx->cfg_ptr->ext_smctr || ctx->cfg_ptr->ext_ssctr) { + TCGv type = tcg_constant_tl(CTRDATA_TYPE_NONTAKEN_BRANCH); + TCGv dest = tcg_temp_new(); + TCGv src = tcg_temp_new(); + + gen_pc_plus_diff(src, ctx, 0); + gen_pc_plus_diff(dest, ctx, ctx->cur_insn_len); + gen_helper_ctr_add_entry(tcg_env, src, dest, type); + } +#endif + gen_goto_tb(ctx, 1, ctx->cur_insn_len); ctx->pc_save = orig_pc_save; @@ -243,6 +307,17 @@ static bool gen_branch(DisasContext *ctx, arg_b *a, TCGCond cond) gen_pc_plus_diff(target_pc, ctx, a->imm); gen_exception_inst_addr_mis(ctx, target_pc); } else { +#ifndef CONFIG_USER_ONLY + if (ctx->cfg_ptr->ext_smctr || ctx->cfg_ptr->ext_ssctr) { + TCGv type = tcg_constant_tl(CTRDATA_TYPE_TAKEN_BRANCH); + TCGv dest = tcg_temp_new(); + TCGv src = tcg_temp_new(); + + gen_pc_plus_diff(src, ctx, 0); + gen_pc_plus_diff(dest, ctx, a->imm); + gen_helper_ctr_add_entry(tcg_env, src, dest, type); + } +#endif gen_goto_tb(ctx, 0, a->imm); } ctx->pc_save = -1; diff --git a/target/riscv/insn_trans/trans_rvzce.c.inc b/target/riscv/insn_trans/trans_rvzce.c.inc index cd234ad96072..c77c2b927b0c 100644 --- a/target/riscv/insn_trans/trans_rvzce.c.inc +++ b/target/riscv/insn_trans/trans_rvzce.c.inc @@ -203,6 +203,14 @@ static bool gen_pop(DisasContext *ctx, arg_cmpp *a, bool ret, bool ret_val) if (ret) { TCGv ret_addr = get_gpr(ctx, xRA, EXT_SIGN); +#ifndef CONFIG_USER_ONLY + if (ctx->cfg_ptr->ext_smctr || ctx->cfg_ptr->ext_ssctr) { + TCGv type = tcg_constant_tl(CTRDATA_TYPE_RETURN); + TCGv src = tcg_temp_new(); + gen_pc_plus_diff(src, ctx, 0); + gen_helper_ctr_add_entry(tcg_env, src, ret_addr, type); + } +#endif tcg_gen_mov_tl(cpu_pc, ret_addr); tcg_gen_lookup_and_goto_ptr(); ctx->base.is_jmp = DISAS_NORETURN; @@ -309,6 +317,19 @@ static bool trans_cm_jalt(DisasContext *ctx, arg_cm_jalt *a) gen_set_gpr(ctx, xRA, succ_pc); } +#ifndef CONFIG_USER_ONLY + if (ctx->cfg_ptr->ext_smctr || ctx->cfg_ptr->ext_ssctr) { + if (a->index >= 32) { + TCGv type = tcg_constant_tl(CTRDATA_TYPE_DIRECT_CALL); + gen_helper_ctr_add_entry(tcg_env, cpu_pc, addr, type); + } else { + TCGv type = tcg_constant_tl(CTRDATA_TYPE_DIRECT_JUMP); + gen_helper_ctr_add_entry(tcg_env, cpu_pc, addr, type); + } + } +#endif + + tcg_gen_mov_tl(cpu_pc, addr); tcg_gen_lookup_and_goto_ptr(); diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c index ce1256f439c6..5a99c47b128c 100644 --- a/target/riscv/op_helper.c +++ b/target/riscv/op_helper.c @@ -270,6 +270,8 @@ target_ulong helper_sret(CPURISCVState *env) { uint64_t mstatus; target_ulong prev_priv, prev_virt = env->virt_enabled; + const target_ulong src_priv = env->priv; + const bool src_virt = env->virt_enabled; if (!(env->priv >= PRV_S)) { riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC()); @@ -339,6 +341,11 @@ target_ulong helper_sret(CPURISCVState *env) } env->mstatus = set_field(env->mstatus, MSTATUS_SPELP, 0); + if (riscv_cpu_cfg(env)->ext_smctr || riscv_cpu_cfg(env)->ext_ssctr) { + riscv_ctr_add_entry(env, env->pc, retpc, CTRDATA_TYPE_EXCEP_INT_RET, + src_priv, src_virt); + } + return retpc; } @@ -416,6 +423,11 @@ target_ulong helper_mret(CPURISCVState *env) } env->mstatus = set_field(env->mstatus, MSTATUS_MPELP, 0); + if (riscv_cpu_cfg(env)->ext_smctr || riscv_cpu_cfg(env)->ext_ssctr) { + riscv_ctr_add_entry(env, env->pc, retpc, CTRDATA_TYPE_EXCEP_INT_RET, + PRV_M, false); + } + return retpc; } @@ -466,6 +478,13 @@ target_ulong helper_mnret(CPURISCVState *env) return retpc; } +void helper_ctr_add_entry(CPURISCVState *env, target_ulong src, + target_ulong dest, target_ulong type) +{ + riscv_ctr_add_entry(env, src, dest, (enum CTRType)type, + env->priv, env->virt_enabled); +} + void helper_wfi(CPURISCVState *env) { CPUState *cs = env_cpu(env); diff --git a/target/riscv/translate.c b/target/riscv/translate.c index 698b74f7a8fa..eaa5d86eaeed 100644 --- a/target/riscv/translate.c +++ b/target/riscv/translate.c @@ -561,6 +561,46 @@ static void gen_set_fpr_d(DisasContext *ctx, int reg_num, TCGv_i64 t) } } +#ifndef CONFIG_USER_ONLY +/* + * Direct calls + * - jal x1; + * - jal x5; + * - c.jal. + * - cm.jalt. + * + * Direct jumps + * - jal x0; + * - c.j; + * - cm.jt. + * + * Other direct jumps + * - jal rd where rd != x1 and rd != x5 and rd != x0; + */ +static void gen_ctr_jal(DisasContext *ctx, int rd, target_ulong imm) +{ + TCGv dest = tcg_temp_new(); + TCGv src = tcg_temp_new(); + TCGv type; + + /* + * If rd is x1 or x5 link registers, treat this as direct call otherwise + * its a direct jump. + */ + if (rd == 1 || rd == 5) { + type = tcg_constant_tl(CTRDATA_TYPE_DIRECT_CALL); + } else if (rd == 0) { + type = tcg_constant_tl(CTRDATA_TYPE_DIRECT_JUMP); + } else { + type = tcg_constant_tl(CTRDATA_TYPE_OTHER_DIRECT_JUMP); + } + + gen_pc_plus_diff(dest, ctx, imm); + gen_pc_plus_diff(src, ctx, 0); + gen_helper_ctr_add_entry(tcg_env, src, dest, type); +} +#endif + static void gen_jal(DisasContext *ctx, int rd, target_ulong imm) { TCGv succ_pc = dest_gpr(ctx, rd); @@ -575,6 +615,12 @@ static void gen_jal(DisasContext *ctx, int rd, target_ulong imm) } } +#ifndef CONFIG_USER_ONLY + if (ctx->cfg_ptr->ext_smctr || ctx->cfg_ptr->ext_ssctr) { + gen_ctr_jal(ctx, rd, imm); + } +#endif + gen_pc_plus_diff(succ_pc, ctx, ctx->cur_insn_len); gen_set_gpr(ctx, rd, succ_pc); From 9e69e760fdc34a9d13a8c434d6a9fada835a05ad Mon Sep 17 00:00:00 2001 From: Rajnesh Kanwal Date: Wed, 5 Feb 2025 11:18:49 +0000 Subject: [PATCH 0372/1179] target/riscv: Add CTR sctrclr instruction. CTR extension adds a new instruction sctrclr to quickly clear the recorded entries buffer. Signed-off-by: Rajnesh Kanwal Acked-by: Alistair Francis Message-ID: <20250205-b4-ctr_upstream_v6-v6-5-439d8e06c8ef@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.h | 1 + target/riscv/cpu_helper.c | 7 +++++ target/riscv/helper.h | 1 + target/riscv/insn32.decode | 1 + .../riscv/insn_trans/trans_privileged.c.inc | 11 +++++++ target/riscv/op_helper.c | 29 +++++++++++++++++++ 6 files changed, 50 insertions(+) diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 9e92144b6133..616c3bdc1c24 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -619,6 +619,7 @@ void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv, bool virt_en); void riscv_ctr_add_entry(CPURISCVState *env, target_long src, target_long dst, enum CTRType type, target_ulong prev_priv, bool prev_virt); +void riscv_ctr_clear(CPURISCVState *env); void riscv_translate_init(void); void riscv_translate_code(CPUState *cs, TranslationBlock *tb, diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 7dbdb34b17c4..356e84b9a248 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -887,6 +887,13 @@ static void riscv_ctr_freeze(CPURISCVState *env, uint64_t freeze_mask, } } +void riscv_ctr_clear(CPURISCVState *env) +{ + memset(env->ctr_src, 0x0, sizeof(env->ctr_src)); + memset(env->ctr_dst, 0x0, sizeof(env->ctr_dst)); + memset(env->ctr_data, 0x0, sizeof(env->ctr_data)); +} + static uint64_t riscv_ctr_priv_to_mask(target_ulong priv, bool virt) { switch (priv) { diff --git a/target/riscv/helper.h b/target/riscv/helper.h index 163121ade599..85d73e492d77 100644 --- a/target/riscv/helper.h +++ b/target/riscv/helper.h @@ -132,6 +132,7 @@ DEF_HELPER_6(csrrw_i128, tl, env, int, tl, tl, tl, tl) DEF_HELPER_1(sret, tl, env) DEF_HELPER_1(mret, tl, env) DEF_HELPER_1(mnret, tl, env) +DEF_HELPER_1(ctr_clear, void, env) DEF_HELPER_1(wfi, void, env) DEF_HELPER_1(wrs_nto, void, env) DEF_HELPER_1(tlb_flush, void, env) diff --git a/target/riscv/insn32.decode b/target/riscv/insn32.decode index a98dab020576..6d1a13c82603 100644 --- a/target/riscv/insn32.decode +++ b/target/riscv/insn32.decode @@ -114,6 +114,7 @@ # *** Privileged Instructions *** ecall 000000000000 00000 000 00000 1110011 ebreak 000000000001 00000 000 00000 1110011 +sctrclr 000100000100 00000 000 00000 1110011 uret 0000000 00010 00000 000 00000 1110011 sret 0001000 00010 00000 000 00000 1110011 mret 0011000 00010 00000 000 00000 1110011 diff --git a/target/riscv/insn_trans/trans_privileged.c.inc b/target/riscv/insn_trans/trans_privileged.c.inc index ca52405d7da4..8a62b4cfcd9f 100644 --- a/target/riscv/insn_trans/trans_privileged.c.inc +++ b/target/riscv/insn_trans/trans_privileged.c.inc @@ -75,6 +75,17 @@ static bool trans_ebreak(DisasContext *ctx, arg_ebreak *a) return true; } +static bool trans_sctrclr(DisasContext *ctx, arg_sctrclr *a) +{ +#ifndef CONFIG_USER_ONLY + if (ctx->cfg_ptr->ext_smctr || ctx->cfg_ptr->ext_ssctr) { + gen_helper_ctr_clear(tcg_env); + return true; + } +#endif + return false; +} + static bool trans_uret(DisasContext *ctx, arg_uret *a) { return false; diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c index 5a99c47b128c..f156bfab12da 100644 --- a/target/riscv/op_helper.c +++ b/target/riscv/op_helper.c @@ -485,6 +485,35 @@ void helper_ctr_add_entry(CPURISCVState *env, target_ulong src, env->priv, env->virt_enabled); } +void helper_ctr_clear(CPURISCVState *env) +{ + /* + * It's safe to call smstateen_acc_ok() for umode access regardless of the + * state of bit 54 (CTR bit in case of m/hstateen) of sstateen. If the bit + * is zero, smstateen_acc_ok() will return the correct exception code and + * if it's one, smstateen_acc_ok() will return RISCV_EXCP_NONE. In that + * scenario the U-mode check below will handle that case. + */ + RISCVException ret = smstateen_acc_ok(env, 0, SMSTATEEN0_CTR); + if (ret != RISCV_EXCP_NONE) { + riscv_raise_exception(env, ret, GETPC()); + } + + if (env->priv == PRV_U) { + /* + * One corner case is when sctrclr is executed from VU-mode and + * mstateen.CTR = 0, in which case we are supposed to raise + * RISCV_EXCP_ILLEGAL_INST. This case is already handled in + * smstateen_acc_ok(). + */ + uint32_t excep = env->virt_enabled ? RISCV_EXCP_VIRT_INSTRUCTION_FAULT : + RISCV_EXCP_ILLEGAL_INST; + riscv_raise_exception(env, excep, GETPC()); + } + + riscv_ctr_clear(env); +} + void helper_wfi(CPURISCVState *env) { CPUState *cs = env_cpu(env); From bda6522e3f9002040fc223c12457b849328a1d39 Mon Sep 17 00:00:00 2001 From: Rajnesh Kanwal Date: Wed, 5 Feb 2025 11:18:50 +0000 Subject: [PATCH 0373/1179] target/riscv: machine: Add Control Transfer Record state description Add a subsection to machine.c to migrate CTR CSR state Signed-off-by: Rajnesh Kanwal Acked-by: Alistair Francis Message-ID: <20250205-b4-ctr_upstream_v6-v6-6-439d8e06c8ef@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/machine.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/target/riscv/machine.c b/target/riscv/machine.c index d8445244ab25..889e2b657013 100644 --- a/target/riscv/machine.c +++ b/target/riscv/machine.c @@ -300,6 +300,30 @@ static const VMStateDescription vmstate_envcfg = { } }; +static bool ctr_needed(void *opaque) +{ + RISCVCPU *cpu = opaque; + + return cpu->cfg.ext_smctr || cpu->cfg.ext_ssctr; +} + +static const VMStateDescription vmstate_ctr = { + .name = "cpu/ctr", + .version_id = 1, + .minimum_version_id = 1, + .needed = ctr_needed, + .fields = (const VMStateField[]) { + VMSTATE_UINT64(env.mctrctl, RISCVCPU), + VMSTATE_UINT32(env.sctrdepth, RISCVCPU), + VMSTATE_UINT32(env.sctrstatus, RISCVCPU), + VMSTATE_UINT64(env.vsctrctl, RISCVCPU), + VMSTATE_UINT64_ARRAY(env.ctr_src, RISCVCPU, 16 << SCTRDEPTH_MAX), + VMSTATE_UINT64_ARRAY(env.ctr_dst, RISCVCPU, 16 << SCTRDEPTH_MAX), + VMSTATE_UINT64_ARRAY(env.ctr_data, RISCVCPU, 16 << SCTRDEPTH_MAX), + VMSTATE_END_OF_LIST() + } +}; + static bool pmu_needed(void *opaque) { RISCVCPU *cpu = opaque; @@ -450,6 +474,7 @@ const VMStateDescription vmstate_riscv_cpu = { &vmstate_jvt, &vmstate_elp, &vmstate_ssp, + &vmstate_ctr, NULL } }; From 50df464f8e9ddcc5242c058728e12c299c59db02 Mon Sep 17 00:00:00 2001 From: julia Date: Mon, 3 Feb 2025 17:18:52 +1100 Subject: [PATCH 0374/1179] target/riscv: log guest errors when reserved bits are set in PTEs For instance, QEMUs newer than b6ecc63c569bb88c0fcadf79fb92bf4b88aefea8 would silently treat this akin to an unmapped page (as required by the RISC-V spec, admittedly). However, not all hardware platforms do (e.g. CVA6) which leads to an apparent QEMU bug. Instead, log a guest error so that in future, incorrectly set up page tables can be debugged without bisecting QEMU. Signed-off-by: julia Reviewed-by: Daniel Henrique Barboza Message-ID: <20250203061852.2931556-1-midnight@trainwit.ch> Signed-off-by: Alistair Francis --- target/riscv/cpu_helper.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 356e84b9a248..3f5fd861a80b 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -1472,14 +1472,27 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, ppn = pte >> PTE_PPN_SHIFT; } else { if (pte & PTE_RESERVED) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: reserved bits set in PTE: " + "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", + __func__, pte_addr, pte); return TRANSLATE_FAIL; } if (!pbmte && (pte & PTE_PBMT)) { + /* Reserved without Svpbmt. */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: PBMT bits set in PTE, " + "and Svpbmt extension is disabled: " + "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", + __func__, pte_addr, pte); return TRANSLATE_FAIL; } if (!riscv_cpu_cfg(env)->ext_svnapot && (pte & PTE_N)) { + /* Reserved without Svnapot extension */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: N bit set in PTE, " + "and Svnapot extension is disabled: " + "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", + __func__, pte_addr, pte); return TRANSLATE_FAIL; } @@ -1490,14 +1503,19 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, /* Invalid PTE */ return TRANSLATE_FAIL; } + if (pte & (PTE_R | PTE_W | PTE_X)) { goto leaf; } - /* Inner PTE, continue walking */ if (pte & (PTE_D | PTE_A | PTE_U | PTE_ATTR)) { + /* D, A, and U bits are reserved in non-leaf/inner PTEs */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: D, A, or U bits set in non-leaf PTE: " + "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", + __func__, pte_addr, pte); return TRANSLATE_FAIL; } + /* Inner PTE, continue walking */ base = ppn << PGSHIFT; } @@ -1507,10 +1525,17 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, leaf: if (ppn & ((1ULL << ptshift) - 1)) { /* Misaligned PPN */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: PPN bits in PTE is misaligned: " + "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", + __func__, pte_addr, pte); return TRANSLATE_FAIL; } if (!pbmte && (pte & PTE_PBMT)) { /* Reserved without Svpbmt. */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: PBMT bits set in PTE, " + "and Svpbmt extension is disabled: " + "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", + __func__, pte_addr, pte); return TRANSLATE_FAIL; } From d30db4df5187e2b91e589ffceace59a0ae2bc30e Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 6 Feb 2025 15:34:09 +0000 Subject: [PATCH 0375/1179] disas/riscv: Fix minor whitespace issues Some extra spaces made into into the RISC-V opcode data table. Signed-off-by: Rob Bradford Reviewed-by: Alistair Francis Message-ID: <20250206153410.236636-2-rbradford@rivosinc.com> Signed-off-by: Alistair Francis --- disas/riscv.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/disas/riscv.c b/disas/riscv.c index 4075ed6bfef0..305dd40ac477 100644 --- a/disas/riscv.c +++ b/disas/riscv.c @@ -1662,7 +1662,7 @@ const rv_opcode_data rvi_opcode_data[] = { { "aes32esi", rv_codec_k_bs, rv_fmt_rs1_rs2_bs, NULL, 0, 0, 0 }, { "aes32dsmi", rv_codec_k_bs, rv_fmt_rs1_rs2_bs, NULL, 0, 0, 0 }, { "aes32dsi", rv_codec_k_bs, rv_fmt_rs1_rs2_bs, NULL, 0, 0, 0 }, - { "aes64ks1i", rv_codec_k_rnum, rv_fmt_rd_rs1_rnum, NULL, 0, 0, 0 }, + { "aes64ks1i", rv_codec_k_rnum, rv_fmt_rd_rs1_rnum, NULL, 0, 0, 0 }, { "aes64ks2", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, { "aes64im", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0 }, { "aes64esm", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, @@ -2214,11 +2214,11 @@ const rv_opcode_data rvi_opcode_data[] = { { "mop.rr.5", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, { "mop.rr.6", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, { "mop.rr.7", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, - { "c.mop.1", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, - { "c.mop.3", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, - { "c.mop.5", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, - { "c.mop.7", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, - { "c.mop.9", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, + { "c.mop.1", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, + { "c.mop.3", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, + { "c.mop.5", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, + { "c.mop.7", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, + { "c.mop.9", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, { "c.mop.11", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, { "c.mop.13", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, { "c.mop.15", rv_codec_ci_none, rv_fmt_none, NULL, 0, 0, 0 }, From 81819038d7d01c6c8c12005b5904356efc09a909 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 6 Feb 2025 15:34:10 +0000 Subject: [PATCH 0376/1179] disas/riscv: Add missing Sdtrig CSRs This reflects the latest frozen version of the RISC-V Debug specification (1.0.0-rc4) which includes the Sdtrig extension. Signed-off-by: Rob Bradford Acked-by: Alistair Francis Message-ID: <20250206153410.236636-3-rbradford@rivosinc.com> Signed-off-by: Alistair Francis --- disas/riscv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/disas/riscv.c b/disas/riscv.c index 305dd40ac477..85cd2a9c2aef 100644 --- a/disas/riscv.c +++ b/disas/riscv.c @@ -2438,9 +2438,11 @@ static const char *csr_name(int csrno) case 0x07a1: return "tdata1"; case 0x07a2: return "tdata2"; case 0x07a3: return "tdata3"; + case 0x07a4: return "tinfo"; case 0x07b0: return "dcsr"; case 0x07b1: return "dpc"; - case 0x07b2: return "dscratch"; + case 0x07b2: return "dscratch0"; + case 0x07b3: return "dscratch1"; case 0x0b00: return "mcycle"; case 0x0b01: return "mtime"; case 0x0b02: return "minstret"; From 59eaf1570456b701fe6dfa4a8f747e65633c385f Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Thu, 6 Feb 2025 01:58:46 -0800 Subject: [PATCH 0377/1179] target/riscv: Fix the hpmevent mask As per the latest privilege specification v1.13[1], the sscofpmf only reserves first 8 bits of hpmeventX. Update the corresponding masks accordingly. [1]https://github.com/riscv/riscv-isa-manual/issues/1578 Reviewed-by: Daniel Henrique Barboza Signed-off-by: Atish Patra Acked-by: Alistair Francis Message-ID: <20250206-pmu_minor_fixes-v2-1-1bb0f4aeb8b4@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/cpu_bits.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index 70ef443c9947..a30317c61781 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -1078,9 +1078,8 @@ typedef enum CTRType { MHPMEVENTH_BIT_VSINH | \ MHPMEVENTH_BIT_VUINH) -#define MHPMEVENT_SSCOF_MASK _ULL(0xFFFF000000000000) -#define MHPMEVENT_IDX_MASK 0xFFFFF -#define MHPMEVENT_SSCOF_RESVD 16 +#define MHPMEVENT_SSCOF_MASK MAKE_64BIT_MASK(63, 56) +#define MHPMEVENT_IDX_MASK (~MHPMEVENT_SSCOF_MASK) /* RISC-V-specific interrupt pending bits. */ #define CPU_INTERRUPT_RNMI CPU_INTERRUPT_TGT_EXT_0 From abe9b81ee41b607eab1928f337837a19acae3208 Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Thu, 6 Feb 2025 01:58:47 -0800 Subject: [PATCH 0378/1179] target/riscv: Mask out upper sscofpmf bits during validation As per the ISA definition, the upper 8 bits in hpmevent are defined by Sscofpmf for privilege mode filtering and overflow bits while the lower 56 bits are desginated for platform specific hpmevent values. For the reset case, mhpmevent value should have zero in lower 56 bits. Software may set the OF bit to indicate disable interrupt. Ensure that correct value is checked after masking while clearing the event encodings. Reviewed-by: Daniel Henrique Barboza Acked-by: Alistair Francis Signed-off-by: Atish Patra Message-ID: <20250206-pmu_minor_fixes-v2-2-1bb0f4aeb8b4@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/pmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/riscv/pmu.c b/target/riscv/pmu.c index cf713663ee56..0408f96e6af8 100644 --- a/target/riscv/pmu.c +++ b/target/riscv/pmu.c @@ -390,7 +390,7 @@ int riscv_pmu_update_event_map(CPURISCVState *env, uint64_t value, * Expected mhpmevent value is zero for reset case. Remove the current * mapping. */ - if (!value) { + if (!(value & MHPMEVENT_IDX_MASK)) { g_hash_table_foreach_remove(cpu->pmu_event_ctr_map, pmu_remove_event_map, GUINT_TO_POINTER(ctr_idx)); From cb0c4760263d418ea47afa9e6d88944e96e128f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20L=C3=A9ger?= Date: Thu, 13 Feb 2025 15:56:31 +0100 Subject: [PATCH 0379/1179] target/riscv: remove warnings about Smdbltrp/Smrnmi being disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As raised by Richard Henderson, these warnings are displayed in user only as well. Since they aren't really useful for the end-user, remove them and add a "TODO" note in the leading comments. Signed-off-by: Clément Léger Reviewed-by: Daniel Henrique Barboza Message-ID: <20250213145640.117275-1-cleger@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/tcg/tcg-cpu.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index cb9b504012f3..53c99985531e 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -1458,22 +1458,20 @@ static void riscv_init_max_cpu_extensions(Object *obj) } /* - * ext_smrnmi requires OpenSBI changes that our current + * TODO: ext_smrnmi requires OpenSBI changes that our current * image does not have. Disable it for now. */ if (cpu->cfg.ext_smrnmi) { isa_ext_update_enabled(cpu, CPU_CFG_OFFSET(ext_smrnmi), false); - qemu_log("Smrnmi is disabled in the 'max' type CPU\n"); } /* - * ext_smdbltrp requires the firmware to clear MSTATUS.MDT on startup to - * avoid generating a double trap. OpenSBI does not currently support it, + * TODO: ext_smdbltrp requires the firmware to clear MSTATUS.MDT on startup + * to avoid generating a double trap. OpenSBI does not currently support it, * disable it for now. */ if (cpu->cfg.ext_smdbltrp) { isa_ext_update_enabled(cpu, CPU_CFG_OFFSET(ext_smdbltrp), false); - qemu_log("Smdbltrp is disabled in the 'max' type CPU\n"); } } From 8b65852196650417532ff924c8a2cb0117e2be19 Mon Sep 17 00:00:00 2001 From: Rajnesh Kanwal Date: Wed, 12 Feb 2025 10:18:49 +0000 Subject: [PATCH 0380/1179] target/riscv: Add support to access ctrsource, ctrtarget, ctrdata regs. CTR entries are accessed using ctrsource, ctrtarget and ctrdata registers using smcsrind/sscsrind extension. This commits extends the csrind extension to support CTR registers. ctrsource is accessible through xireg CSR, ctrtarget is accessible through xireg1 and ctrdata is accessible through xireg2 CSR. CTR supports maximum depth of 256 entries which are accessed using xiselect range 0x200 to 0x2ff. This commits also adds properties to enable CTR extension. CTR can be enabled using smctr=true and ssctr=true now. Signed-off-by: Rajnesh Kanwal Acked-by: Alistair Francis Message-ID: <20250212-b4-ctr_upstream_v6-v7-1-4e8159ea33bf@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.c | 26 ++++++- target/riscv/csr.c | 150 ++++++++++++++++++++++++++++++++++++- target/riscv/tcg/tcg-cpu.c | 11 +++ 3 files changed, 185 insertions(+), 2 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 3624ffb6d998..a4ee381a07cd 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -216,6 +216,8 @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(ssu64xl, PRIV_VERSION_1_12_0, has_priv_1_12), ISA_EXT_DATA_ENTRY(supm, PRIV_VERSION_1_13_0, ext_supm), ISA_EXT_DATA_ENTRY(svade, PRIV_VERSION_1_11_0, ext_svade), + ISA_EXT_DATA_ENTRY(smctr, PRIV_VERSION_1_12_0, ext_smctr), + ISA_EXT_DATA_ENTRY(ssctr, PRIV_VERSION_1_12_0, ext_ssctr), ISA_EXT_DATA_ENTRY(svadu, PRIV_VERSION_1_12_0, ext_svadu), ISA_EXT_DATA_ENTRY(svinval, PRIV_VERSION_1_12_0, ext_svinval), ISA_EXT_DATA_ENTRY(svnapot, PRIV_VERSION_1_12_0, ext_svnapot), @@ -1592,6 +1594,8 @@ const RISCVCPUMultiExtConfig riscv_cpu_extensions[] = { MULTI_EXT_CFG_BOOL("smcdeleg", ext_smcdeleg, false), MULTI_EXT_CFG_BOOL("sscsrind", ext_sscsrind, false), MULTI_EXT_CFG_BOOL("ssccfg", ext_ssccfg, false), + MULTI_EXT_CFG_BOOL("smctr", ext_smctr, false), + MULTI_EXT_CFG_BOOL("ssctr", ext_ssctr, false), MULTI_EXT_CFG_BOOL("zifencei", ext_zifencei, true), MULTI_EXT_CFG_BOOL("zicfilp", ext_zicfilp, false), MULTI_EXT_CFG_BOOL("zicfiss", ext_zicfiss, false), @@ -2856,6 +2860,26 @@ static RISCVCPUImpliedExtsRule SSPM_IMPLIED = { }, }; +static RISCVCPUImpliedExtsRule SMCTR_IMPLIED = { + .ext = CPU_CFG_OFFSET(ext_smctr), + .implied_misa_exts = RVS, + .implied_multi_exts = { + CPU_CFG_OFFSET(ext_sscsrind), + + RISCV_IMPLIED_EXTS_RULE_END + }, +}; + +static RISCVCPUImpliedExtsRule SSCTR_IMPLIED = { + .ext = CPU_CFG_OFFSET(ext_ssctr), + .implied_misa_exts = RVS, + .implied_multi_exts = { + CPU_CFG_OFFSET(ext_sscsrind), + + RISCV_IMPLIED_EXTS_RULE_END + }, +}; + RISCVCPUImpliedExtsRule *riscv_misa_ext_implied_rules[] = { &RVA_IMPLIED, &RVD_IMPLIED, &RVF_IMPLIED, &RVM_IMPLIED, &RVV_IMPLIED, NULL @@ -2874,7 +2898,7 @@ RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[] = { &ZVFH_IMPLIED, &ZVFHMIN_IMPLIED, &ZVKN_IMPLIED, &ZVKNC_IMPLIED, &ZVKNG_IMPLIED, &ZVKNHB_IMPLIED, &ZVKS_IMPLIED, &ZVKSC_IMPLIED, &ZVKSG_IMPLIED, &SSCFG_IMPLIED, - &SUPM_IMPLIED, &SSPM_IMPLIED, + &SUPM_IMPLIED, &SSPM_IMPLIED, &SMCTR_IMPLIED, &SSCTR_IMPLIED, NULL }; diff --git a/target/riscv/csr.c b/target/riscv/csr.c index ab295d2ef3c3..0ebcca459781 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -2427,6 +2427,13 @@ static bool xiselect_cd_range(target_ulong isel) return (ISELECT_CD_FIRST <= isel && isel <= ISELECT_CD_LAST); } +static bool xiselect_ctr_range(int csrno, target_ulong isel) +{ + /* MIREG-MIREG6 for the range 0x200-0x2ff are not used by CTR. */ + return CTR_ENTRIES_FIRST <= isel && isel <= CTR_ENTRIES_LAST && + csrno < CSR_MIREG; +} + static int rmw_iprio(target_ulong xlen, target_ulong iselect, uint8_t *iprio, target_ulong *val, target_ulong new_val, @@ -2472,6 +2479,124 @@ static int rmw_iprio(target_ulong xlen, return 0; } +static int rmw_ctrsource(CPURISCVState *env, int isel, target_ulong *val, + target_ulong new_val, target_ulong wr_mask) +{ + /* + * CTR arrays are treated as circular buffers and TOS always points to next + * empty slot, keeping TOS - 1 always pointing to latest entry. Given entry + * 0 is always the latest one, traversal is a bit different here. See the + * below example. + * + * Depth = 16. + * + * idx [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [A] [B] [C] [D] [E] [F] + * TOS H + * entry 6 5 4 3 2 1 0 F E D C B A 9 8 7 + */ + const uint64_t entry = isel - CTR_ENTRIES_FIRST; + const uint64_t depth = 16 << get_field(env->sctrdepth, SCTRDEPTH_MASK); + uint64_t idx; + + /* Entry greater than depth-1 is read-only zero */ + if (entry >= depth) { + if (val) { + *val = 0; + } + return 0; + } + + idx = get_field(env->sctrstatus, SCTRSTATUS_WRPTR_MASK); + idx = (idx - entry - 1) & (depth - 1); + + if (val) { + *val = env->ctr_src[idx]; + } + + env->ctr_src[idx] = (env->ctr_src[idx] & ~wr_mask) | (new_val & wr_mask); + + return 0; +} + +static int rmw_ctrtarget(CPURISCVState *env, int isel, target_ulong *val, + target_ulong new_val, target_ulong wr_mask) +{ + /* + * CTR arrays are treated as circular buffers and TOS always points to next + * empty slot, keeping TOS - 1 always pointing to latest entry. Given entry + * 0 is always the latest one, traversal is a bit different here. See the + * below example. + * + * Depth = 16. + * + * idx [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [A] [B] [C] [D] [E] [F] + * head H + * entry 6 5 4 3 2 1 0 F E D C B A 9 8 7 + */ + const uint64_t entry = isel - CTR_ENTRIES_FIRST; + const uint64_t depth = 16 << get_field(env->sctrdepth, SCTRDEPTH_MASK); + uint64_t idx; + + /* Entry greater than depth-1 is read-only zero */ + if (entry >= depth) { + if (val) { + *val = 0; + } + return 0; + } + + idx = get_field(env->sctrstatus, SCTRSTATUS_WRPTR_MASK); + idx = (idx - entry - 1) & (depth - 1); + + if (val) { + *val = env->ctr_dst[idx]; + } + + env->ctr_dst[idx] = (env->ctr_dst[idx] & ~wr_mask) | (new_val & wr_mask); + + return 0; +} + +static int rmw_ctrdata(CPURISCVState *env, int isel, target_ulong *val, + target_ulong new_val, target_ulong wr_mask) +{ + /* + * CTR arrays are treated as circular buffers and TOS always points to next + * empty slot, keeping TOS - 1 always pointing to latest entry. Given entry + * 0 is always the latest one, traversal is a bit different here. See the + * below example. + * + * Depth = 16. + * + * idx [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [A] [B] [C] [D] [E] [F] + * head H + * entry 6 5 4 3 2 1 0 F E D C B A 9 8 7 + */ + const uint64_t entry = isel - CTR_ENTRIES_FIRST; + const uint64_t mask = wr_mask & CTRDATA_MASK; + const uint64_t depth = 16 << get_field(env->sctrdepth, SCTRDEPTH_MASK); + uint64_t idx; + + /* Entry greater than depth-1 is read-only zero */ + if (entry >= depth) { + if (val) { + *val = 0; + } + return 0; + } + + idx = get_field(env->sctrstatus, SCTRSTATUS_WRPTR_MASK); + idx = (idx - entry - 1) & (depth - 1); + + if (val) { + *val = env->ctr_data[idx]; + } + + env->ctr_data[idx] = (env->ctr_data[idx] & ~mask) | (new_val & mask); + + return 0; +} + static RISCVException rmw_xireg_aia(CPURISCVState *env, int csrno, target_ulong isel, target_ulong *val, target_ulong new_val, target_ulong wr_mask) @@ -2624,6 +2749,27 @@ static int rmw_xireg_cd(CPURISCVState *env, int csrno, return ret; } +static int rmw_xireg_ctr(CPURISCVState *env, int csrno, + target_ulong isel, target_ulong *val, + target_ulong new_val, target_ulong wr_mask) +{ + if (!riscv_cpu_cfg(env)->ext_smctr && !riscv_cpu_cfg(env)->ext_ssctr) { + return -EINVAL; + } + + if (csrno == CSR_SIREG || csrno == CSR_VSIREG) { + return rmw_ctrsource(env, isel, val, new_val, wr_mask); + } else if (csrno == CSR_SIREG2 || csrno == CSR_VSIREG2) { + return rmw_ctrtarget(env, isel, val, new_val, wr_mask); + } else if (csrno == CSR_SIREG3 || csrno == CSR_VSIREG3) { + return rmw_ctrdata(env, isel, val, new_val, wr_mask); + } else if (val) { + *val = 0; + } + + return 0; +} + /* * rmw_xireg_csrind: Perform indirect access to xireg and xireg2-xireg6 * @@ -2635,11 +2781,13 @@ static int rmw_xireg_csrind(CPURISCVState *env, int csrno, target_ulong isel, target_ulong *val, target_ulong new_val, target_ulong wr_mask) { - int ret = -EINVAL; bool virt = csrno == CSR_VSIREG ? true : false; + int ret = -EINVAL; if (xiselect_cd_range(isel)) { ret = rmw_xireg_cd(env, csrno, isel, val, new_val, wr_mask); + } else if (xiselect_ctr_range(csrno, isel)) { + ret = rmw_xireg_ctr(env, csrno, isel, val, new_val, wr_mask); } else { /* * As per the specification, access to unimplented region is undefined diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index 53c99985531e..929ed5fd2c32 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -681,6 +681,17 @@ void riscv_cpu_validate_set_extensions(RISCVCPU *cpu, Error **errp) return; } + if ((cpu->cfg.ext_smctr || cpu->cfg.ext_ssctr) && + (!riscv_has_ext(env, RVS) || !cpu->cfg.ext_sscsrind)) { + if (cpu_cfg_ext_is_user_set(CPU_CFG_OFFSET(ext_smctr)) || + cpu_cfg_ext_is_user_set(CPU_CFG_OFFSET(ext_ssctr))) { + error_setg(errp, "Smctr and Ssctr require S-mode and Sscsrind"); + return; + } + cpu->cfg.ext_smctr = false; + cpu->cfg.ext_ssctr = false; + } + /* * Disable isa extensions based on priv spec after we * validated and set everything we need. From 2c1b42144018ec5ab097e003b974e3a094d73b2f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 10 Feb 2025 15:37:13 +0000 Subject: [PATCH 0381/1179] target/riscv: Respect mseccfg.RLB bit for TOR mode PMP entry When running in TOR mode (Top of Range) the next PMP entry controls whether the entry is locked. However simply checking if the PMP_LOCK bit is set is not sufficient with the Smepmp extension which now provides a bit (mseccfg.RLB (Rule Lock Bypass)) to disregard the lock bits. In order to respect this bit use the convenience pmp_is_locked() function rather than directly checking PMP_LOCK since this function checks mseccfg.RLB. Signed-off-by: Rob Bradford Reviewed-by: Alistair Francis Reviewed-by: Daniel Henrique Barboza Message-ID: <20250210153713.343626-1-rbradford@rivosinc.com> Signed-off-by: Alistair Francis --- target/riscv/pmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index a185c246d629..85ab270dad47 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -524,7 +524,7 @@ void pmpaddr_csr_write(CPURISCVState *env, uint32_t addr_index, uint8_t pmp_cfg = env->pmp_state.pmp[addr_index + 1].cfg_reg; is_next_cfg_tor = PMP_AMATCH_TOR == pmp_get_a_field(pmp_cfg); - if (pmp_cfg & PMP_LOCK && is_next_cfg_tor) { + if (pmp_is_locked(env, addr_index + 1) && is_next_cfg_tor) { qemu_log_mask(LOG_GUEST_ERROR, "ignoring pmpaddr write - pmpcfg + 1 locked\n"); return; From 421ee1ec6f0de0b0fd96b262bda18b97e54263b4 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Fri, 21 Feb 2025 12:37:56 -0300 Subject: [PATCH 0382/1179] linux-headers: Update to Linux v6.14-rc3 Update headers to retrieve the latest KVM caps for RISC-V. Signed-off-by: Daniel Henrique Barboza Message-ID: <20250221153758.652078-2-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- include/standard-headers/linux/ethtool.h | 4 + include/standard-headers/linux/fuse.h | 76 ++++++++++++++++++- .../linux/input-event-codes.h | 1 + include/standard-headers/linux/pci_regs.h | 16 ++-- include/standard-headers/linux/virtio_pci.h | 14 ++++ linux-headers/asm-arm64/kvm.h | 3 - linux-headers/asm-loongarch/kvm_para.h | 1 + linux-headers/asm-riscv/kvm.h | 7 +- linux-headers/asm-x86/kvm.h | 1 + linux-headers/linux/iommufd.h | 35 ++++++--- linux-headers/linux/kvm.h | 8 +- linux-headers/linux/stddef.h | 13 +++- linux-headers/linux/vduse.h | 2 +- 13 files changed, 146 insertions(+), 35 deletions(-) diff --git a/include/standard-headers/linux/ethtool.h b/include/standard-headers/linux/ethtool.h index 67c47912e596..e83382531cd1 100644 --- a/include/standard-headers/linux/ethtool.h +++ b/include/standard-headers/linux/ethtool.h @@ -681,6 +681,8 @@ enum ethtool_link_ext_substate_module { * @ETH_SS_STATS_ETH_MAC: names of IEEE 802.3 MAC statistics * @ETH_SS_STATS_ETH_CTRL: names of IEEE 802.3 MAC Control statistics * @ETH_SS_STATS_RMON: names of RMON statistics + * @ETH_SS_STATS_PHY: names of PHY(dev) statistics + * @ETH_SS_TS_FLAGS: hardware timestamping flags * * @ETH_SS_COUNT: number of defined string sets */ @@ -706,6 +708,8 @@ enum ethtool_stringset { ETH_SS_STATS_ETH_MAC, ETH_SS_STATS_ETH_CTRL, ETH_SS_STATS_RMON, + ETH_SS_STATS_PHY, + ETH_SS_TS_FLAGS, /* add new constants above here */ ETH_SS_COUNT diff --git a/include/standard-headers/linux/fuse.h b/include/standard-headers/linux/fuse.h index 889e12ad1552..d303effb2a0d 100644 --- a/include/standard-headers/linux/fuse.h +++ b/include/standard-headers/linux/fuse.h @@ -220,6 +220,15 @@ * * 7.41 * - add FUSE_ALLOW_IDMAP + * 7.42 + * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data + * structures: + * - struct fuse_uring_ent_in_out + * - struct fuse_uring_req_header + * - struct fuse_uring_cmd_req + * - FUSE_URING_IN_OUT_HEADER_SZ + * - FUSE_URING_OP_IN_OUT_SZ + * - enum fuse_uring_cmd */ #ifndef _LINUX_FUSE_H @@ -251,7 +260,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 41 +#define FUSE_KERNEL_MINOR_VERSION 42 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -421,6 +430,7 @@ struct fuse_file_lock { * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit * of the request ID indicates resend requests * FUSE_ALLOW_IDMAP: allow creation of idmapped mounts + * FUSE_OVER_IO_URING: Indicate that client supports io-uring */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -467,6 +477,7 @@ struct fuse_file_lock { /* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ #define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP #define FUSE_ALLOW_IDMAP (1ULL << 40) +#define FUSE_OVER_IO_URING (1ULL << 41) /** * CUSE INIT request/reply flags @@ -1202,4 +1213,67 @@ struct fuse_supp_groups { uint32_t groups[]; }; +/** + * Size of the ring buffer header + */ +#define FUSE_URING_IN_OUT_HEADER_SZ 128 +#define FUSE_URING_OP_IN_OUT_SZ 128 + +/* Used as part of the fuse_uring_req_header */ +struct fuse_uring_ent_in_out { + uint64_t flags; + + /* + * commit ID to be used in a reply to a ring request (see also + * struct fuse_uring_cmd_req) + */ + uint64_t commit_id; + + /* size of user payload buffer */ + uint32_t payload_sz; + uint32_t padding; + + uint64_t reserved; +}; + +/** + * Header for all fuse-io-uring requests + */ +struct fuse_uring_req_header { + /* struct fuse_in_header / struct fuse_out_header */ + char in_out[FUSE_URING_IN_OUT_HEADER_SZ]; + + /* per op code header */ + char op_in[FUSE_URING_OP_IN_OUT_SZ]; + + struct fuse_uring_ent_in_out ring_ent_in_out; +}; + +/** + * sqe commands to the kernel + */ +enum fuse_uring_cmd { + FUSE_IO_URING_CMD_INVALID = 0, + + /* register the request buffer and fetch a fuse request */ + FUSE_IO_URING_CMD_REGISTER = 1, + + /* commit fuse request result and fetch next request */ + FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2, +}; + +/** + * In the 80B command area of the SQE. + */ +struct fuse_uring_cmd_req { + uint64_t flags; + + /* entry identifier for commits */ + uint64_t commit_id; + + /* queue the command is for (queue index) */ + uint16_t qid; + uint8_t padding[6]; +}; + #endif /* _LINUX_FUSE_H */ diff --git a/include/standard-headers/linux/input-event-codes.h b/include/standard-headers/linux/input-event-codes.h index 50b2b7497ef3..09ba0ad87831 100644 --- a/include/standard-headers/linux/input-event-codes.h +++ b/include/standard-headers/linux/input-event-codes.h @@ -519,6 +519,7 @@ #define KEY_NOTIFICATION_CENTER 0x1bc /* Show/hide the notification center */ #define KEY_PICKUP_PHONE 0x1bd /* Answer incoming call */ #define KEY_HANGUP_PHONE 0x1be /* Decline incoming call */ +#define KEY_LINK_PHONE 0x1bf /* AL Phone Syncing */ #define KEY_DEL_EOL 0x1c0 #define KEY_DEL_EOS 0x1c1 diff --git a/include/standard-headers/linux/pci_regs.h b/include/standard-headers/linux/pci_regs.h index 1601c7ed5fab..3445c4970e4d 100644 --- a/include/standard-headers/linux/pci_regs.h +++ b/include/standard-headers/linux/pci_regs.h @@ -533,7 +533,7 @@ #define PCI_EXP_DEVSTA_TRPND 0x0020 /* Transactions Pending */ #define PCI_CAP_EXP_RC_ENDPOINT_SIZEOF_V1 12 /* v1 endpoints without link end here */ #define PCI_EXP_LNKCAP 0x0c /* Link Capabilities */ -#define PCI_EXP_LNKCAP_SLS 0x0000000f /* Supported Link Speeds */ +#define PCI_EXP_LNKCAP_SLS 0x0000000f /* Max Link Speed (prior to PCIe r3.0: Supported Link Speeds) */ #define PCI_EXP_LNKCAP_SLS_2_5GB 0x00000001 /* LNKCAP2 SLS Vector bit 0 */ #define PCI_EXP_LNKCAP_SLS_5_0GB 0x00000002 /* LNKCAP2 SLS Vector bit 1 */ #define PCI_EXP_LNKCAP_SLS_8_0GB 0x00000003 /* LNKCAP2 SLS Vector bit 2 */ @@ -665,6 +665,7 @@ #define PCI_EXP_DEVCAP2_OBFF_MSG 0x00040000 /* New message signaling */ #define PCI_EXP_DEVCAP2_OBFF_WAKE 0x00080000 /* Re-use WAKE# for OBFF */ #define PCI_EXP_DEVCAP2_EE_PREFIX 0x00200000 /* End-End TLP Prefix */ +#define PCI_EXP_DEVCAP2_EE_PREFIX_MAX 0x00c00000 /* Max End-End TLP Prefixes */ #define PCI_EXP_DEVCTL2 0x28 /* Device Control 2 */ #define PCI_EXP_DEVCTL2_COMP_TIMEOUT 0x000f /* Completion Timeout Value */ #define PCI_EXP_DEVCTL2_COMP_TMOUT_DIS 0x0010 /* Completion Timeout Disable */ @@ -789,10 +790,11 @@ /* Same bits as above */ #define PCI_ERR_CAP 0x18 /* Advanced Error Capabilities & Ctrl*/ #define PCI_ERR_CAP_FEP(x) ((x) & 0x1f) /* First Error Pointer */ -#define PCI_ERR_CAP_ECRC_GENC 0x00000020 /* ECRC Generation Capable */ -#define PCI_ERR_CAP_ECRC_GENE 0x00000040 /* ECRC Generation Enable */ -#define PCI_ERR_CAP_ECRC_CHKC 0x00000080 /* ECRC Check Capable */ -#define PCI_ERR_CAP_ECRC_CHKE 0x00000100 /* ECRC Check Enable */ +#define PCI_ERR_CAP_ECRC_GENC 0x00000020 /* ECRC Generation Capable */ +#define PCI_ERR_CAP_ECRC_GENE 0x00000040 /* ECRC Generation Enable */ +#define PCI_ERR_CAP_ECRC_CHKC 0x00000080 /* ECRC Check Capable */ +#define PCI_ERR_CAP_ECRC_CHKE 0x00000100 /* ECRC Check Enable */ +#define PCI_ERR_CAP_PREFIX_LOG_PRESENT 0x00000800 /* TLP Prefix Log Present */ #define PCI_ERR_HEADER_LOG 0x1c /* Header Log Register (16 bytes) */ #define PCI_ERR_ROOT_COMMAND 0x2c /* Root Error Command */ #define PCI_ERR_ROOT_CMD_COR_EN 0x00000001 /* Correctable Err Reporting Enable */ @@ -808,6 +810,7 @@ #define PCI_ERR_ROOT_FATAL_RCV 0x00000040 /* Fatal Received */ #define PCI_ERR_ROOT_AER_IRQ 0xf8000000 /* Advanced Error Interrupt Message Number */ #define PCI_ERR_ROOT_ERR_SRC 0x34 /* Error Source Identification */ +#define PCI_ERR_PREFIX_LOG 0x38 /* TLP Prefix LOG Register (up to 16 bytes) */ /* Virtual Channel */ #define PCI_VC_PORT_CAP1 0x04 @@ -1001,9 +1004,6 @@ #define PCI_ACS_CTRL 0x06 /* ACS Control Register */ #define PCI_ACS_EGRESS_CTL_V 0x08 /* ACS Egress Control Vector */ -#define PCI_VSEC_HDR 4 /* extended cap - vendor-specific */ -#define PCI_VSEC_HDR_LEN_SHIFT 20 /* shift for length field */ - /* SATA capability */ #define PCI_SATA_REGS 4 /* SATA REGs specifier */ #define PCI_SATA_REGS_MASK 0xF /* location - BAR#/inline */ diff --git a/include/standard-headers/linux/virtio_pci.h b/include/standard-headers/linux/virtio_pci.h index b177ed89727f..91fec6f50296 100644 --- a/include/standard-headers/linux/virtio_pci.h +++ b/include/standard-headers/linux/virtio_pci.h @@ -116,6 +116,8 @@ #define VIRTIO_PCI_CAP_PCI_CFG 5 /* Additional shared memory capability */ #define VIRTIO_PCI_CAP_SHARED_MEMORY_CFG 8 +/* PCI vendor data configuration */ +#define VIRTIO_PCI_CAP_VENDOR_CFG 9 /* This is the PCI capability header: */ struct virtio_pci_cap { @@ -130,6 +132,18 @@ struct virtio_pci_cap { uint32_t length; /* Length of the structure, in bytes. */ }; +/* This is the PCI vendor data capability header: */ +struct virtio_pci_vndr_data { + uint8_t cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */ + uint8_t cap_next; /* Generic PCI field: next ptr. */ + uint8_t cap_len; /* Generic PCI field: capability length */ + uint8_t cfg_type; /* Identifies the structure. */ + uint16_t vendor_id; /* Identifies the vendor-specific format. */ + /* For Vendor Definition */ + /* Pads structure to a multiple of 4 bytes */ + /* Reads must not have side effects */ +}; + struct virtio_pci_cap64 { struct virtio_pci_cap cap; uint32_t offset_hi; /* Most sig 32 bits of offset */ diff --git a/linux-headers/asm-arm64/kvm.h b/linux-headers/asm-arm64/kvm.h index dccd5d965f3d..ec1e82bdc888 100644 --- a/linux-headers/asm-arm64/kvm.h +++ b/linux-headers/asm-arm64/kvm.h @@ -43,9 +43,6 @@ #define KVM_COALESCED_MMIO_PAGE_OFFSET 1 #define KVM_DIRTY_LOG_PAGE_OFFSET 64 -#define KVM_REG_SIZE(id) \ - (1U << (((id) & KVM_REG_SIZE_MASK) >> KVM_REG_SIZE_SHIFT)) - struct kvm_regs { struct user_pt_regs regs; /* sp = sp_el0 */ diff --git a/linux-headers/asm-loongarch/kvm_para.h b/linux-headers/asm-loongarch/kvm_para.h index 4ba4ad8db1d9..fd7f40713d49 100644 --- a/linux-headers/asm-loongarch/kvm_para.h +++ b/linux-headers/asm-loongarch/kvm_para.h @@ -17,5 +17,6 @@ #define KVM_FEATURE_STEAL_TIME 2 /* BIT 24 - 31 are features configurable by user space vmm */ #define KVM_FEATURE_VIRT_EXTIOI 24 +#define KVM_FEATURE_USER_HCALL 25 #endif /* _ASM_KVM_PARA_H */ diff --git a/linux-headers/asm-riscv/kvm.h b/linux-headers/asm-riscv/kvm.h index 3482c9a73d1b..f06bc5efcd79 100644 --- a/linux-headers/asm-riscv/kvm.h +++ b/linux-headers/asm-riscv/kvm.h @@ -179,6 +179,9 @@ enum KVM_RISCV_ISA_EXT_ID { KVM_RISCV_ISA_EXT_SSNPM, KVM_RISCV_ISA_EXT_SVADE, KVM_RISCV_ISA_EXT_SVADU, + KVM_RISCV_ISA_EXT_SVVPTC, + KVM_RISCV_ISA_EXT_ZABHA, + KVM_RISCV_ISA_EXT_ZICCRSE, KVM_RISCV_ISA_EXT_MAX, }; @@ -198,6 +201,7 @@ enum KVM_RISCV_SBI_EXT_ID { KVM_RISCV_SBI_EXT_VENDOR, KVM_RISCV_SBI_EXT_DBCN, KVM_RISCV_SBI_EXT_STA, + KVM_RISCV_SBI_EXT_SUSP, KVM_RISCV_SBI_EXT_MAX, }; @@ -211,9 +215,6 @@ struct kvm_riscv_sbi_sta { #define KVM_RISCV_TIMER_STATE_OFF 0 #define KVM_RISCV_TIMER_STATE_ON 1 -#define KVM_REG_SIZE(id) \ - (1U << (((id) & KVM_REG_SIZE_MASK) >> KVM_REG_SIZE_SHIFT)) - /* If you need to interpret the index values, here is the key: */ #define KVM_REG_RISCV_TYPE_MASK 0x00000000FF000000 #define KVM_REG_RISCV_TYPE_SHIFT 24 diff --git a/linux-headers/asm-x86/kvm.h b/linux-headers/asm-x86/kvm.h index 96589490c426..86f2c34e7afa 100644 --- a/linux-headers/asm-x86/kvm.h +++ b/linux-headers/asm-x86/kvm.h @@ -923,5 +923,6 @@ struct kvm_hyperv_eventfd { #define KVM_X86_SEV_VM 2 #define KVM_X86_SEV_ES_VM 3 #define KVM_X86_SNP_VM 4 +#define KVM_X86_TDX_VM 5 #endif /* _ASM_X86_KVM_H */ diff --git a/linux-headers/linux/iommufd.h b/linux-headers/linux/iommufd.h index 37aae1650204..ccbdca5e1138 100644 --- a/linux-headers/linux/iommufd.h +++ b/linux-headers/linux/iommufd.h @@ -297,7 +297,7 @@ struct iommu_ioas_unmap { * ioctl(IOMMU_OPTION_HUGE_PAGES) * @IOMMU_OPTION_RLIMIT_MODE: * Change how RLIMIT_MEMLOCK accounting works. The caller must have privilege - * to invoke this. Value 0 (default) is user based accouting, 1 uses process + * to invoke this. Value 0 (default) is user based accounting, 1 uses process * based accounting. Global option, object_id must be 0 * @IOMMU_OPTION_HUGE_PAGES: * Value 1 (default) allows contiguous pages to be combined when generating @@ -390,7 +390,7 @@ struct iommu_vfio_ioas { * @IOMMU_HWPT_ALLOC_PASID: Requests a domain that can be used with PASID. The * domain can be attached to any PASID on the device. * Any domain attached to the non-PASID part of the - * device must also be flaged, otherwise attaching a + * device must also be flagged, otherwise attaching a * PASID will blocked. * If IOMMU does not support PASID it will return * error (-EOPNOTSUPP). @@ -558,16 +558,25 @@ struct iommu_hw_info_vtd { * For the details of @idr, @iidr and @aidr, please refer to the chapters * from 6.3.1 to 6.3.6 in the SMMUv3 Spec. * - * User space should read the underlying ARM SMMUv3 hardware information for - * the list of supported features. + * This reports the raw HW capability, and not all bits are meaningful to be + * read by userspace. Only the following fields should be used: * - * Note that these values reflect the raw HW capability, without any insight if - * any required kernel driver support is present. Bits may be set indicating the - * HW has functionality that is lacking kernel software support, such as BTM. If - * a VMM is using this information to construct emulated copies of these - * registers it should only forward bits that it knows it can support. + * idr[0]: ST_LEVEL, TERM_MODEL, STALL_MODEL, TTENDIAN , CD2L, ASID16, TTF + * idr[1]: SIDSIZE, SSIDSIZE + * idr[3]: BBML, RIL + * idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K * - * In future, presence of required kernel support will be indicated in flags. + * - S1P should be assumed to be true if a NESTED HWPT can be created + * - VFIO/iommufd only support platforms with COHACC, it should be assumed to be + * true. + * - ATS is a per-device property. If the VMM describes any devices as ATS + * capable in ACPI/DT it should set the corresponding idr. + * + * This list may expand in future (eg E0PD, AIE, PBHA, D128, DS etc). It is + * important that VMMs do not read bits outside the list to allow for + * compatibility with future kernels. Several features in the SMMUv3 + * architecture are not currently supported by the kernel for nesting: HTTU, + * BTM, MPAM and others. */ struct iommu_hw_info_arm_smmuv3 { __u32 flags; @@ -766,7 +775,7 @@ struct iommu_hwpt_vtd_s1_invalidate { }; /** - * struct iommu_viommu_arm_smmuv3_invalidate - ARM SMMUv3 cahce invalidation + * struct iommu_viommu_arm_smmuv3_invalidate - ARM SMMUv3 cache invalidation * (IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3) * @cmd: 128-bit cache invalidation command that runs in SMMU CMDQ. * Must be little-endian. @@ -859,6 +868,7 @@ enum iommu_hwpt_pgfault_perm { * @pasid: Process Address Space ID * @grpid: Page Request Group Index * @perm: Combination of enum iommu_hwpt_pgfault_perm + * @__reserved: Must be 0. * @addr: Fault address * @length: a hint of how much data the requestor is expecting to fetch. For * example, if the PRI initiator knows it is going to do a 10MB @@ -874,7 +884,8 @@ struct iommu_hwpt_pgfault { __u32 pasid; __u32 grpid; __u32 perm; - __u64 addr; + __u32 __reserved; + __aligned_u64 addr; __u32 length; __u32 cookie; }; diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 3bcd4eabe324..27181b3dd8fb 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -609,10 +609,6 @@ struct kvm_ioeventfd { #define KVM_X86_DISABLE_EXITS_HLT (1 << 1) #define KVM_X86_DISABLE_EXITS_PAUSE (1 << 2) #define KVM_X86_DISABLE_EXITS_CSTATE (1 << 3) -#define KVM_X86_DISABLE_VALID_EXITS (KVM_X86_DISABLE_EXITS_MWAIT | \ - KVM_X86_DISABLE_EXITS_HLT | \ - KVM_X86_DISABLE_EXITS_PAUSE | \ - KVM_X86_DISABLE_EXITS_CSTATE) /* for KVM_ENABLE_CAP */ struct kvm_enable_cap { @@ -1062,6 +1058,10 @@ struct kvm_dirty_tlb { #define KVM_REG_SIZE_SHIFT 52 #define KVM_REG_SIZE_MASK 0x00f0000000000000ULL + +#define KVM_REG_SIZE(id) \ + (1U << (((id) & KVM_REG_SIZE_MASK) >> KVM_REG_SIZE_SHIFT)) + #define KVM_REG_SIZE_U8 0x0000000000000000ULL #define KVM_REG_SIZE_U16 0x0010000000000000ULL #define KVM_REG_SIZE_U32 0x0020000000000000ULL diff --git a/linux-headers/linux/stddef.h b/linux-headers/linux/stddef.h index 96aa341942b0..e1416f793738 100644 --- a/linux-headers/linux/stddef.h +++ b/linux-headers/linux/stddef.h @@ -8,6 +8,13 @@ #define __always_inline __inline__ #endif +/* Not all C++ standards support type declarations inside an anonymous union */ +#ifndef __cplusplus +#define __struct_group_tag(TAG) TAG +#else +#define __struct_group_tag(TAG) +#endif + /** * __struct_group() - Create a mirrored named and anonyomous struct * @@ -20,13 +27,13 @@ * and size: one anonymous and one named. The former's members can be used * normally without sub-struct naming, and the latter can be used to * reason about the start, end, and size of the group of struct members. - * The named struct can also be explicitly tagged for layer reuse, as well - * as both having struct attributes appended. + * The named struct can also be explicitly tagged for layer reuse (C only), + * as well as both having struct attributes appended. */ #define __struct_group(TAG, NAME, ATTRS, MEMBERS...) \ union { \ struct { MEMBERS } ATTRS; \ - struct TAG { MEMBERS } ATTRS NAME; \ + struct __struct_group_tag(TAG) { MEMBERS } ATTRS NAME; \ } ATTRS #ifdef __cplusplus diff --git a/linux-headers/linux/vduse.h b/linux-headers/linux/vduse.h index 6d2ca064b5d6..f46269af349a 100644 --- a/linux-headers/linux/vduse.h +++ b/linux-headers/linux/vduse.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ #ifndef _VDUSE_H_ #define _VDUSE_H_ From 93e59c8b7619c9ff0f5dc51633a95e3f77a85056 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Fri, 21 Feb 2025 12:37:57 -0300 Subject: [PATCH 0383/1179] target/riscv/cpu.c: create flag for ziccrse At this moment ziccrse is a TCG always enabled named feature for priv_ver > 1.11 that has no exclusive flag. In the next patch we'll make the KVM driver turn ziccrse off if the extension isn't available in the host, and we'll need an ext_ziccrse flag in the CPU state for that. Create an exclusive flag for it like we do with other named features. As with any named features we already have, it won't be exposed to users. Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Message-ID: <20250221153758.652078-3-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/cpu.c | 3 ++- target/riscv/cpu_cfg.h | 3 +++ target/riscv/tcg/tcg-cpu.c | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index a4ee381a07cd..47424fd5e2a0 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -105,7 +105,7 @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(ziccamoa, PRIV_VERSION_1_11_0, has_priv_1_11), ISA_EXT_DATA_ENTRY(ziccif, PRIV_VERSION_1_11_0, has_priv_1_11), ISA_EXT_DATA_ENTRY(zicclsm, PRIV_VERSION_1_11_0, has_priv_1_11), - ISA_EXT_DATA_ENTRY(ziccrse, PRIV_VERSION_1_11_0, has_priv_1_11), + ISA_EXT_DATA_ENTRY(ziccrse, PRIV_VERSION_1_11_0, ext_ziccrse), ISA_EXT_DATA_ENTRY(zicfilp, PRIV_VERSION_1_12_0, ext_zicfilp), ISA_EXT_DATA_ENTRY(zicfiss, PRIV_VERSION_1_13_0, ext_zicfiss), ISA_EXT_DATA_ENTRY(zicond, PRIV_VERSION_1_12_0, ext_zicond), @@ -1742,6 +1742,7 @@ const RISCVCPUMultiExtConfig riscv_cpu_named_features[] = { MULTI_EXT_CFG_BOOL("zic64b", ext_zic64b, true), MULTI_EXT_CFG_BOOL("ssstateen", ext_ssstateen, true), MULTI_EXT_CFG_BOOL("sha", ext_sha, true), + MULTI_EXT_CFG_BOOL("ziccrse", ext_ziccrse, true), { }, }; diff --git a/target/riscv/cpu_cfg.h b/target/riscv/cpu_cfg.h index 3f3c1118c08f..8a843482cc64 100644 --- a/target/riscv/cpu_cfg.h +++ b/target/riscv/cpu_cfg.h @@ -166,6 +166,9 @@ struct RISCVCPUConfig { bool has_priv_1_12; bool has_priv_1_11; + /* Always enabled for TCG if has_priv_1_11 */ + bool ext_ziccrse; + /* Vendor-specific custom extensions */ bool ext_xtheadba; bool ext_xtheadbb; diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index 929ed5fd2c32..f1d971eec1f3 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -360,6 +360,8 @@ static void riscv_cpu_update_named_features(RISCVCPU *cpu) cpu->cfg.ext_sha = riscv_has_ext(&cpu->env, RVH) && cpu->cfg.ext_ssstateen; + + cpu->cfg.ext_ziccrse = cpu->cfg.has_priv_1_11; } static void riscv_cpu_validate_g(RISCVCPU *cpu) From eaa910b14773403963316cfe3617eecc9e02356a Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Fri, 21 Feb 2025 12:37:58 -0300 Subject: [PATCH 0384/1179] target/riscv/kvm: add extensions after 6.14-rc3 update Expose ziccrse, zabha and svvptc. Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Message-ID: <20250221153758.652078-4-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/kvm/kvm-cpu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index 23ce77935940..471fd554b369 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -274,6 +274,7 @@ static void kvm_riscv_update_cpu_misa_ext(RISCVCPU *cpu, CPUState *cs) static KVMCPUConfig kvm_multi_ext_cfgs[] = { KVM_EXT_CFG("zicbom", ext_zicbom, KVM_RISCV_ISA_EXT_ZICBOM), KVM_EXT_CFG("zicboz", ext_zicboz, KVM_RISCV_ISA_EXT_ZICBOZ), + KVM_EXT_CFG("ziccrse", ext_ziccrse, KVM_RISCV_ISA_EXT_ZICCRSE), KVM_EXT_CFG("zicntr", ext_zicntr, KVM_RISCV_ISA_EXT_ZICNTR), KVM_EXT_CFG("zicond", ext_zicond, KVM_RISCV_ISA_EXT_ZICOND), KVM_EXT_CFG("zicsr", ext_zicsr, KVM_RISCV_ISA_EXT_ZICSR), @@ -283,6 +284,7 @@ static KVMCPUConfig kvm_multi_ext_cfgs[] = { KVM_EXT_CFG("zihpm", ext_zihpm, KVM_RISCV_ISA_EXT_ZIHPM), KVM_EXT_CFG("zimop", ext_zimop, KVM_RISCV_ISA_EXT_ZIMOP), KVM_EXT_CFG("zcmop", ext_zcmop, KVM_RISCV_ISA_EXT_ZCMOP), + KVM_EXT_CFG("zabha", ext_zabha, KVM_RISCV_ISA_EXT_ZABHA), KVM_EXT_CFG("zacas", ext_zacas, KVM_RISCV_ISA_EXT_ZACAS), KVM_EXT_CFG("zawrs", ext_zawrs, KVM_RISCV_ISA_EXT_ZAWRS), KVM_EXT_CFG("zfa", ext_zfa, KVM_RISCV_ISA_EXT_ZFA), @@ -325,6 +327,7 @@ static KVMCPUConfig kvm_multi_ext_cfgs[] = { KVM_EXT_CFG("svinval", ext_svinval, KVM_RISCV_ISA_EXT_SVINVAL), KVM_EXT_CFG("svnapot", ext_svnapot, KVM_RISCV_ISA_EXT_SVNAPOT), KVM_EXT_CFG("svpbmt", ext_svpbmt, KVM_RISCV_ISA_EXT_SVPBMT), + KVM_EXT_CFG("svvptc", ext_svvptc, KVM_RISCV_ISA_EXT_SVVPTC), }; static void *kvmconfig_get_cfg_addr(RISCVCPU *cpu, KVMCPUConfig *kvmcfg) From 5bdbbacb8c3ee1705838da9b1cddfe065e53821c Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Mon, 24 Feb 2025 16:08:16 -0300 Subject: [PATCH 0385/1179] hw/riscv/riscv-iommu.h: add missing headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This header is incomplete, i.e. it is using definitions that are being supplied by the .c files that are including it. Adding this header into a fresh .c file will result in errors: /home/danielhb/work/qemu/hw/riscv/riscv-iommu.h:30:17: error: field ‘parent_obj’ has incomplete type 30 | DeviceState parent_obj; | ^~~~~~~~~~ /home/danielhb/work/qemu/hw/riscv/riscv-iommu.h:50:5: error: unknown type name ‘dma_addr_t’; did you mean ‘in_addr_t’? 50 | dma_addr_t cq_addr; /* Command queue base physical address */ | ^~~~~~~~~~ | in_addr_t (...) /home/danielhb/work/qemu/hw/riscv/riscv-iommu.h:62:5: error: unknown type name ‘QemuThread’; did you mean ‘GThread’? 62 | QemuThread core_proc; /* Background processing thread */ | ^~~~~~~~~~ | GThread /home/danielhb/work/qemu/hw/riscv/riscv-iommu.h:63:5: error: unknown type name ‘QemuCond’ 63 | QemuCond core_cond; /* Background processing wake up signal */ | ^~~~~~~~ /home/danielhb/work/qemu/hw/riscv/riscv-iommu.h:71:18: error: field ‘trap_as’ has incomplete type 71 | AddressSpace trap_as; | ^~~~~~~ /home/danielhb/work/qemu/hw/riscv/riscv-iommu.h:72:18: error: field ‘trap_mr’ has incomplete type 72 | MemoryRegion trap_mr; | ^~~~~~~ /home/danielhb/work/qemu/hw/riscv/riscv-iommu.h:80:18: error: field ‘regs_mr’ has incomplete type 80 | MemoryRegion regs_mr; | ^~~~~~~ Fix it by adding the missing headers for these definitions. Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250224190826.1858473-2-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index fa8a50fa243c..d2608d2f9b4d 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -20,6 +20,8 @@ #define HW_RISCV_IOMMU_STATE_H #include "qom/object.h" +#include "hw/qdev-properties.h" +#include "system/dma.h" #include "hw/riscv/iommu.h" #include "hw/riscv/riscv-iommu-bits.h" From 045b19afc9889aa001bab3ae2cb1ecc47b8de790 Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:17 -0300 Subject: [PATCH 0386/1179] hw/riscv/riscv-iommu-bits.h: HPM bits Add the relevant HPM (High Performance Monitor) bits that we'll be using in the next patches. Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-3-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-bits.h | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/hw/riscv/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h index de599b80d693..b7cb1bc7367f 100644 --- a/hw/riscv/riscv-iommu-bits.h +++ b/hw/riscv/riscv-iommu-bits.h @@ -88,6 +88,7 @@ struct riscv_iommu_pq_record { #define RISCV_IOMMU_CAP_ATS BIT_ULL(25) #define RISCV_IOMMU_CAP_T2GPA BIT_ULL(26) #define RISCV_IOMMU_CAP_IGS GENMASK_ULL(29, 28) +#define RISCV_IOMMU_CAP_HPM BIT_ULL(30) #define RISCV_IOMMU_CAP_DBG BIT_ULL(31) #define RISCV_IOMMU_CAP_PAS GENMASK_ULL(37, 32) #define RISCV_IOMMU_CAP_PD8 BIT_ULL(38) @@ -197,6 +198,52 @@ enum { RISCV_IOMMU_INTR_COUNT }; +#define RISCV_IOMMU_IOCOUNT_NUM 31 + +/* 5.19 Performance monitoring counter overflow status (32bits) */ +#define RISCV_IOMMU_REG_IOCOUNTOVF 0x0058 +#define RISCV_IOMMU_IOCOUNTOVF_CY BIT(0) + +/* 5.20 Performance monitoring counter inhibits (32bits) */ +#define RISCV_IOMMU_REG_IOCOUNTINH 0x005C +#define RISCV_IOMMU_IOCOUNTINH_CY BIT(0) + +/* 5.21 Performance monitoring cycles counter (64bits) */ +#define RISCV_IOMMU_REG_IOHPMCYCLES 0x0060 +#define RISCV_IOMMU_IOHPMCYCLES_COUNTER GENMASK_ULL(62, 0) +#define RISCV_IOMMU_IOHPMCYCLES_OVF BIT_ULL(63) + +/* 5.22 Performance monitoring event counters (31 * 64bits) */ +#define RISCV_IOMMU_REG_IOHPMCTR_BASE 0x0068 +#define RISCV_IOMMU_REG_IOHPMCTR(_n) \ + (RISCV_IOMMU_REG_IOHPMCTR_BASE + (_n * 0x8)) + +/* 5.23 Performance monitoring event selectors (31 * 64bits) */ +#define RISCV_IOMMU_REG_IOHPMEVT_BASE 0x0160 +#define RISCV_IOMMU_REG_IOHPMEVT(_n) \ + (RISCV_IOMMU_REG_IOHPMEVT_BASE + (_n * 0x8)) +#define RISCV_IOMMU_IOHPMEVT_EVENT_ID GENMASK_ULL(14, 0) +#define RISCV_IOMMU_IOHPMEVT_DMASK BIT_ULL(15) +#define RISCV_IOMMU_IOHPMEVT_PID_PSCID GENMASK_ULL(35, 16) +#define RISCV_IOMMU_IOHPMEVT_DID_GSCID GENMASK_ULL(59, 36) +#define RISCV_IOMMU_IOHPMEVT_PV_PSCV BIT_ULL(60) +#define RISCV_IOMMU_IOHPMEVT_DV_GSCV BIT_ULL(61) +#define RISCV_IOMMU_IOHPMEVT_IDT BIT_ULL(62) +#define RISCV_IOMMU_IOHPMEVT_OF BIT_ULL(63) + +enum RISCV_IOMMU_HPMEVENT_id { + RISCV_IOMMU_HPMEVENT_INVALID = 0, + RISCV_IOMMU_HPMEVENT_URQ = 1, + RISCV_IOMMU_HPMEVENT_TRQ = 2, + RISCV_IOMMU_HPMEVENT_ATS_RQ = 3, + RISCV_IOMMU_HPMEVENT_TLB_MISS = 4, + RISCV_IOMMU_HPMEVENT_DD_WALK = 5, + RISCV_IOMMU_HPMEVENT_PD_WALK = 6, + RISCV_IOMMU_HPMEVENT_S_VS_WALKS = 7, + RISCV_IOMMU_HPMEVENT_G_WALKS = 8, + RISCV_IOMMU_HPMEVENT_MAX = 9 +}; + /* 5.24 Translation request IOVA (64bits) */ #define RISCV_IOMMU_REG_TR_REQ_IOVA 0x0258 From 4faea7e084dc02c6491b55e594ba5d39a75ff38f Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:18 -0300 Subject: [PATCH 0387/1179] hw/riscv/riscv-iommu: add riscv-iommu-hpm file The HPM (Hardware Performance Monitor) support consists of almost 7 hundred lines that would be put on top of the base riscv-iommu emulation. To avoid clogging riscv-iommu.c, add a separated riscv-iommu-hpm file that will contain HPM specific code. We'll start by adding riscv_iommu_hpmcycle_read(), a helper that will be called during the riscv_iommu_mmio_read() callback. This change will have no effect on the existing emulation since we're not declaring HPM feature support. Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-4-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/meson.build | 3 ++- hw/riscv/riscv-iommu-hpm.c | 54 ++++++++++++++++++++++++++++++++++++++ hw/riscv/riscv-iommu-hpm.h | 27 +++++++++++++++++++ hw/riscv/riscv-iommu.c | 24 ++++++++++++++++- hw/riscv/riscv-iommu.h | 4 +++ 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 hw/riscv/riscv-iommu-hpm.c create mode 100644 hw/riscv/riscv-iommu-hpm.h diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index 3c7e083aca1d..c22f3a721629 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -10,7 +10,8 @@ riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c')) riscv_ss.add(when: 'CONFIG_SPIKE', if_true: files('spike.c')) riscv_ss.add(when: 'CONFIG_MICROCHIP_PFSOC', if_true: files('microchip_pfsoc.c')) riscv_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c')) -riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files('riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c')) +riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files( + 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c')) riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c')) hw_arch += {'riscv': riscv_ss} diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c new file mode 100644 index 000000000000..5833ab89566e --- /dev/null +++ b/hw/riscv/riscv-iommu-hpm.c @@ -0,0 +1,54 @@ +/* + * RISC-V IOMMU - Hardware Performance Monitor (HPM) helpers + * + * Copyright (C) 2022-2023 Rivos Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * 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 . + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "cpu_bits.h" +#include "riscv-iommu-hpm.h" +#include "riscv-iommu.h" +#include "riscv-iommu-bits.h" +#include "trace.h" + +/* For now we assume IOMMU HPM frequency to be 1GHz so 1-cycle is of 1-ns. */ +static inline uint64_t get_cycles(void) +{ + return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s) +{ + const uint64_t cycle = riscv_iommu_reg_get64( + s, RISCV_IOMMU_REG_IOHPMCYCLES); + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + const uint64_t ctr_prev = s->hpmcycle_prev; + const uint64_t ctr_val = s->hpmcycle_val; + + if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) { + /* + * Counter should not increment if inhibit bit is set. We can't really + * stop the QEMU_CLOCK_VIRTUAL, so we just return the last updated + * counter value to indicate that counter was not incremented. + */ + return (ctr_val & RISCV_IOMMU_IOHPMCYCLES_COUNTER) | + (cycle & RISCV_IOMMU_IOHPMCYCLES_OVF); + } + + return (ctr_val + get_cycles() - ctr_prev) | + (cycle & RISCV_IOMMU_IOHPMCYCLES_OVF); +} diff --git a/hw/riscv/riscv-iommu-hpm.h b/hw/riscv/riscv-iommu-hpm.h new file mode 100644 index 000000000000..231c110ff2e6 --- /dev/null +++ b/hw/riscv/riscv-iommu-hpm.h @@ -0,0 +1,27 @@ +/* + * RISC-V IOMMU - Hardware Performance Monitor (HPM) helpers + * + * Copyright (C) 2022-2023 Rivos Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * 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 . + */ + +#ifndef HW_RISCV_IOMMU_HPM_H +#define HW_RISCV_IOMMU_HPM_H + +#include "qom/object.h" +#include "hw/riscv/riscv-iommu.h" + +uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s); + +#endif diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index e7568ca227a8..0fbd50bb52e2 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -29,6 +29,7 @@ #include "cpu_bits.h" #include "riscv-iommu.h" #include "riscv-iommu-bits.h" +#include "riscv-iommu-hpm.h" #include "trace.h" #define LIMIT_CACHE_CTX (1U << 7) @@ -2153,7 +2154,28 @@ static MemTxResult riscv_iommu_mmio_read(void *opaque, hwaddr addr, return MEMTX_ACCESS_ERROR; } - ptr = &s->regs_rw[addr]; + /* Compute cycle register value. */ + if ((addr & ~7) == RISCV_IOMMU_REG_IOHPMCYCLES) { + val = riscv_iommu_hpmcycle_read(s); + ptr = (uint8_t *)&val + (addr & 7); + } else if ((addr & ~3) == RISCV_IOMMU_REG_IOCOUNTOVF) { + /* + * Software can read RISCV_IOMMU_REG_IOCOUNTOVF before timer + * callback completes. In which case CY_OF bit in + * RISCV_IOMMU_IOHPMCYCLES_OVF would be 0. Here we take the + * CY_OF bit state from RISCV_IOMMU_REG_IOHPMCYCLES register as + * it's not dependent over the timer callback and is computed + * from cycle overflow. + */ + val = ldq_le_p(&s->regs_rw[addr]); + val |= (riscv_iommu_hpmcycle_read(s) & RISCV_IOMMU_IOHPMCYCLES_OVF) + ? RISCV_IOMMU_IOCOUNTOVF_CY + : 0; + ptr = (uint8_t *)&val + (addr & 3); + } else { + ptr = &s->regs_rw[addr]; + } + val = ldn_le_p(ptr, size); *data = val; diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index d2608d2f9b4d..59db3fd02a59 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -81,6 +81,10 @@ struct RISCVIOMMUState { QLIST_ENTRY(RISCVIOMMUState) iommus; QLIST_HEAD(, RISCVIOMMUSpace) spaces; + + /* HPM cycle counter */ + uint64_t hpmcycle_val; /* Current value of cycle register */ + uint64_t hpmcycle_prev; /* Saved value of QEMU_CLOCK_VIRTUAL clock */ }; void riscv_iommu_pci_setup_iommu(RISCVIOMMUState *iommu, PCIBus *bus, From 11ecf24c7eda83bb92e24a81425ac6d33a63378e Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:19 -0300 Subject: [PATCH 0388/1179] hw/riscv/riscv-iommu: add riscv_iommu_hpm_incr_ctr() This function will increment a specific counter, generating an interrupt when an overflow occurs. Some extra changes in riscv-iommu.c were required to add this new helper in riscv-iommu-hpm.c: - RISCVIOMMUContext was moved to riscv-iommu.h, making it visible in riscv-iommu-hpm.c; - riscv_iommu_notify() is now public. No behavior change is made since HPM support is not being advertised yet. Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-5-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-hpm.c | 114 +++++++++++++++++++++++++++++++++++++ hw/riscv/riscv-iommu-hpm.h | 2 + hw/riscv/riscv-iommu.c | 43 +++++++++----- hw/riscv/riscv-iommu.h | 18 ++++++ 4 files changed, 162 insertions(+), 15 deletions(-) diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 5833ab89566e..8eca5ee17e81 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -52,3 +52,117 @@ uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s) return (ctr_val + get_cycles() - ctr_prev) | (cycle & RISCV_IOMMU_IOHPMCYCLES_OVF); } + +static void hpm_incr_ctr(RISCVIOMMUState *s, uint32_t ctr_idx) +{ + const uint32_t off = ctr_idx << 3; + uint64_t cntr_val; + + cntr_val = ldq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off]); + stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off], cntr_val + 1); + + /* Handle the overflow scenario. */ + if (cntr_val == UINT64_MAX) { + /* + * Generate interrupt only if OF bit is clear. +1 to offset the cycle + * register OF bit. + */ + const uint32_t ovf = + riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IOCOUNTOVF, + BIT(ctr_idx + 1), 0); + if (!get_field(ovf, BIT(ctr_idx + 1))) { + riscv_iommu_reg_mod64(s, + RISCV_IOMMU_REG_IOHPMEVT_BASE + off, + RISCV_IOMMU_IOHPMEVT_OF, + 0); + riscv_iommu_notify(s, RISCV_IOMMU_INTR_PM); + } + } +} + +void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, + unsigned event_id) +{ + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + uint32_t did_gscid; + uint32_t pid_pscid; + uint32_t ctr_idx; + gpointer value; + uint32_t ctrs; + uint64_t evt; + + if (!(s->cap & RISCV_IOMMU_CAP_HPM)) { + return; + } + + value = g_hash_table_lookup(s->hpm_event_ctr_map, + GUINT_TO_POINTER(event_id)); + if (value == NULL) { + return; + } + + for (ctrs = GPOINTER_TO_UINT(value); ctrs != 0; ctrs &= ctrs - 1) { + ctr_idx = ctz32(ctrs); + if (get_field(inhibit, BIT(ctr_idx + 1))) { + continue; + } + + evt = riscv_iommu_reg_get64(s, + RISCV_IOMMU_REG_IOHPMEVT_BASE + (ctr_idx << 3)); + + /* + * It's quite possible that event ID has been changed in counter + * but hashtable hasn't been updated yet. We don't want to increment + * counter for the old event ID. + */ + if (event_id != get_field(evt, RISCV_IOMMU_IOHPMEVT_EVENT_ID)) { + continue; + } + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_IDT)) { + did_gscid = get_field(ctx->gatp, RISCV_IOMMU_DC_IOHGATP_GSCID); + pid_pscid = get_field(ctx->ta, RISCV_IOMMU_DC_TA_PSCID); + } else { + did_gscid = ctx->devid; + pid_pscid = ctx->process_id; + } + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_PV_PSCV)) { + /* + * If the transaction does not have a valid process_id, counter + * increments if device_id matches DID_GSCID. If the transaction + * has a valid process_id, counter increments if device_id + * matches DID_GSCID and process_id matches PID_PSCID. See + * IOMMU Specification, Chapter 5.23. Performance-monitoring + * event selector. + */ + if (ctx->process_id && + get_field(evt, RISCV_IOMMU_IOHPMEVT_PID_PSCID) != pid_pscid) { + continue; + } + } + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_DV_GSCV)) { + uint32_t mask = ~0; + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_DMASK)) { + /* + * 1001 1011 mask = GSCID + * 0000 0111 mask = mask ^ (mask + 1) + * 1111 1000 mask = ~mask; + */ + mask = get_field(evt, RISCV_IOMMU_IOHPMEVT_DID_GSCID); + mask = mask ^ (mask + 1); + mask = ~mask; + } + + if ((get_field(evt, RISCV_IOMMU_IOHPMEVT_DID_GSCID) & mask) != + (did_gscid & mask)) { + continue; + } + } + + hpm_incr_ctr(s, ctr_idx); + } +} diff --git a/hw/riscv/riscv-iommu-hpm.h b/hw/riscv/riscv-iommu-hpm.h index 231c110ff2e6..411d869dceb5 100644 --- a/hw/riscv/riscv-iommu-hpm.h +++ b/hw/riscv/riscv-iommu-hpm.h @@ -23,5 +23,7 @@ #include "hw/riscv/riscv-iommu.h" uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s); +void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, + unsigned event_id); #endif diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index 0fbd50bb52e2..0b15acf4e6ed 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -39,7 +39,6 @@ #define PPN_PHYS(ppn) ((ppn) << TARGET_PAGE_BITS) #define PPN_DOWN(phy) ((phy) >> TARGET_PAGE_BITS) -typedef struct RISCVIOMMUContext RISCVIOMMUContext; typedef struct RISCVIOMMUEntry RISCVIOMMUEntry; /* Device assigned I/O address space */ @@ -52,19 +51,6 @@ struct RISCVIOMMUSpace { QLIST_ENTRY(RISCVIOMMUSpace) list; }; -/* Device translation context state. */ -struct RISCVIOMMUContext { - uint64_t devid:24; /* Requester Id, AKA device_id */ - uint64_t process_id:20; /* Process ID. PASID for PCIe */ - uint64_t tc; /* Translation Control */ - uint64_t ta; /* Translation Attributes */ - uint64_t satp; /* S-Stage address translation and protection */ - uint64_t gatp; /* G-Stage address translation and protection */ - uint64_t msi_addr_mask; /* MSI filtering - address mask */ - uint64_t msi_addr_pattern; /* MSI filtering - address pattern */ - uint64_t msiptp; /* MSI redirection page table pointer */ -}; - typedef enum RISCVIOMMUTransTag { RISCV_IOMMU_TRANS_TAG_BY, /* Bypass */ RISCV_IOMMU_TRANS_TAG_SS, /* Single Stage */ @@ -101,7 +87,7 @@ static uint8_t riscv_iommu_get_icvec_vector(uint32_t icvec, uint32_t vec_type) } } -static void riscv_iommu_notify(RISCVIOMMUState *s, int vec_type) +void riscv_iommu_notify(RISCVIOMMUState *s, int vec_type) { uint32_t ipsr, icvec, vector; @@ -423,6 +409,13 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, } } + + if (pass == S_STAGE) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_S_VS_WALKS); + } else { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_G_WALKS); + } + /* Read page table entry */ if (sc[pass].ptesize == 4) { uint32_t pte32 = 0; @@ -941,6 +934,7 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) /* Device directory tree walk */ for (; depth-- > 0; ) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_DD_WALK); /* * Select device id index bits based on device directory tree level * and device context format. @@ -968,6 +962,8 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) addr = PPN_PHYS(get_field(de, RISCV_IOMMU_DDTE_PPN)); } + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_DD_WALK); + /* index into device context entry page */ addr |= (ctx->devid * dc_len) & ~TARGET_PAGE_MASK; @@ -1033,6 +1029,8 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) } for (depth = mode - RISCV_IOMMU_DC_FSC_PDTP_MODE_PD8; depth-- > 0; ) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_PD_WALK); + /* * Select process id index bits based on process directory tree * level. See IOMMU Specification, 2.2. Process-Directory-Table. @@ -1050,6 +1048,8 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) addr = PPN_PHYS(get_field(de, RISCV_IOMMU_PC_FSC_PPN)); } + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_PD_WALK); + /* Leaf entry in PDT */ addr |= (ctx->process_id << 4) & ~TARGET_PAGE_MASK; if (dma_memory_read(s->target_as, addr, &dc.ta, sizeof(uint64_t) * 2, @@ -1419,6 +1419,8 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, GHashTable *iot_cache; int fault; + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_URQ); + iot_cache = g_hash_table_ref(s->iot_cache); /* * TC[32] is reserved for custom extensions, used here to temporarily @@ -1429,6 +1431,7 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, /* Check for ATS request. */ if (iotlb->perm == IOMMU_NONE) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_ATS_RQ); /* Check if ATS is disabled. */ if (!(ctx->tc & RISCV_IOMMU_DC_TC_EN_ATS)) { enable_pri = false; @@ -1447,6 +1450,8 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, goto done; } + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_TLB_MISS); + /* Translate using device directory / page table information. */ fault = riscv_iommu_spa_fetch(s, ctx, iotlb); @@ -2375,6 +2380,10 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->trap_mr, OBJECT(dev), &riscv_iommu_trap_ops, s, "riscv-iommu-trap", ~0ULL); address_space_init(&s->trap_as, &s->trap_mr, "riscv-iommu-trap-as"); + + if (s->cap & RISCV_IOMMU_CAP_HPM) { + s->hpm_event_ctr_map = g_hash_table_new(g_direct_hash, g_direct_equal); + } } static void riscv_iommu_unrealize(DeviceState *dev) @@ -2383,6 +2392,10 @@ static void riscv_iommu_unrealize(DeviceState *dev) g_hash_table_unref(s->iot_cache); g_hash_table_unref(s->ctx_cache); + + if (s->cap & RISCV_IOMMU_CAP_HPM) { + g_hash_table_unref(s->hpm_event_ctr_map); + } } void riscv_iommu_reset(RISCVIOMMUState *s) diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 59db3fd02a59..4384f39515d7 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -85,12 +85,30 @@ struct RISCVIOMMUState { /* HPM cycle counter */ uint64_t hpmcycle_val; /* Current value of cycle register */ uint64_t hpmcycle_prev; /* Saved value of QEMU_CLOCK_VIRTUAL clock */ + + /* HPM event counters */ + GHashTable *hpm_event_ctr_map; /* Mapping of events to counters */ }; void riscv_iommu_pci_setup_iommu(RISCVIOMMUState *iommu, PCIBus *bus, Error **errp); void riscv_iommu_set_cap_igs(RISCVIOMMUState *s, riscv_iommu_igs_mode mode); void riscv_iommu_reset(RISCVIOMMUState *s); +void riscv_iommu_notify(RISCVIOMMUState *s, int vec_type); + +typedef struct RISCVIOMMUContext RISCVIOMMUContext; +/* Device translation context state. */ +struct RISCVIOMMUContext { + uint64_t devid:24; /* Requester Id, AKA device_id */ + uint64_t process_id:20; /* Process ID. PASID for PCIe */ + uint64_t tc; /* Translation Control */ + uint64_t ta; /* Translation Attributes */ + uint64_t satp; /* S-Stage address translation and protection */ + uint64_t gatp; /* G-Stage address translation and protection */ + uint64_t msi_addr_mask; /* MSI filtering - address mask */ + uint64_t msi_addr_pattern; /* MSI filtering - address pattern */ + uint64_t msiptp; /* MSI redirection page table pointer */ +}; /* private helpers */ From ffb37df056490b34cecc7958bf5f83fe5497b2d4 Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:20 -0300 Subject: [PATCH 0389/1179] hw/riscv/riscv-iommu: instantiate hpm_timer The next HPM related changes requires the HPM overflow timer to be initialized by the riscv-iommu base emulation. Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-6-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-hpm.c | 36 ++++++++++++++++++++++++++++++++++++ hw/riscv/riscv-iommu-hpm.h | 1 + hw/riscv/riscv-iommu.c | 3 +++ hw/riscv/riscv-iommu.h | 2 ++ 4 files changed, 42 insertions(+) diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 8eca5ee17e81..325088333e1f 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -166,3 +166,39 @@ void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, hpm_incr_ctr(s, ctr_idx); } } + +/* Timer callback for cycle counter overflow. */ +void riscv_iommu_hpm_timer_cb(void *priv) +{ + RISCVIOMMUState *s = priv; + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + uint32_t ovf; + + if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) { + return; + } + + if (s->irq_overflow_left > 0) { + uint64_t irq_trigger_at = + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->irq_overflow_left; + timer_mod_anticipate_ns(s->hpm_timer, irq_trigger_at); + s->irq_overflow_left = 0; + return; + } + + ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF); + if (!get_field(ovf, RISCV_IOMMU_IOCOUNTOVF_CY)) { + /* + * We don't need to set hpmcycle_val to zero and update hpmcycle_prev to + * current clock value. The way we calculate iohpmcycs will overflow + * and return the correct value. This avoids the need to synchronize + * timer callback and write callback. + */ + riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IOCOUNTOVF, + RISCV_IOMMU_IOCOUNTOVF_CY, 0); + riscv_iommu_reg_mod64(s, RISCV_IOMMU_REG_IOHPMCYCLES, + RISCV_IOMMU_IOHPMCYCLES_OVF, 0); + riscv_iommu_notify(s, RISCV_IOMMU_INTR_PM); + } +} diff --git a/hw/riscv/riscv-iommu-hpm.h b/hw/riscv/riscv-iommu-hpm.h index 411d869dceb5..cd896d3b7c66 100644 --- a/hw/riscv/riscv-iommu-hpm.h +++ b/hw/riscv/riscv-iommu-hpm.h @@ -25,5 +25,6 @@ uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s); void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, unsigned event_id); +void riscv_iommu_hpm_timer_cb(void *priv); #endif diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index 0b15acf4e6ed..f26aa15f5553 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -2382,6 +2382,8 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) address_space_init(&s->trap_as, &s->trap_mr, "riscv-iommu-trap-as"); if (s->cap & RISCV_IOMMU_CAP_HPM) { + s->hpm_timer = + timer_new_ns(QEMU_CLOCK_VIRTUAL, riscv_iommu_hpm_timer_cb, s); s->hpm_event_ctr_map = g_hash_table_new(g_direct_hash, g_direct_equal); } } @@ -2395,6 +2397,7 @@ static void riscv_iommu_unrealize(DeviceState *dev) if (s->cap & RISCV_IOMMU_CAP_HPM) { g_hash_table_unref(s->hpm_event_ctr_map); + timer_free(s->hpm_timer); } } diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 4384f39515d7..2fef6eed273b 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -83,8 +83,10 @@ struct RISCVIOMMUState { QLIST_HEAD(, RISCVIOMMUSpace) spaces; /* HPM cycle counter */ + QEMUTimer *hpm_timer; uint64_t hpmcycle_val; /* Current value of cycle register */ uint64_t hpmcycle_prev; /* Saved value of QEMU_CLOCK_VIRTUAL clock */ + uint64_t irq_overflow_left; /* Value beyond INT64_MAX after overflow */ /* HPM event counters */ GHashTable *hpm_event_ctr_map; /* Mapping of events to counters */ From 2cf2a6c027ba1a47be04c53d7cd8f6269007a0b1 Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:21 -0300 Subject: [PATCH 0390/1179] hw/riscv/riscv-iommu: add IOCOUNTINH mmio writes RISCV_IOMMU_REG_IOCOUNTINH is done by riscv_iommu_process_iocntinh_cy(), which is called during riscv_iommu_mmio_write() callback via a new riscv_iommu_pricess_hpm_writes() helper. Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-7-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-hpm.c | 60 ++++++++++++++++++++++++++++++++++++++ hw/riscv/riscv-iommu-hpm.h | 1 + hw/riscv/riscv-iommu.c | 38 ++++++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 325088333e1f..70814b942dfd 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -202,3 +202,63 @@ void riscv_iommu_hpm_timer_cb(void *priv) riscv_iommu_notify(s, RISCV_IOMMU_INTR_PM); } } + +static void hpm_setup_timer(RISCVIOMMUState *s, uint64_t value) +{ + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + uint64_t overflow_at, overflow_ns; + + if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) { + return; + } + + /* + * We are using INT64_MAX here instead to UINT64_MAX because cycle counter + * has 63-bit precision and INT64_MAX is the maximum it can store. + */ + if (value) { + overflow_ns = INT64_MAX - value + 1; + } else { + overflow_ns = INT64_MAX; + } + + overflow_at = (uint64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + overflow_ns; + + if (overflow_at > INT64_MAX) { + s->irq_overflow_left = overflow_at - INT64_MAX; + overflow_at = INT64_MAX; + } + + timer_mod_anticipate_ns(s->hpm_timer, overflow_at); +} + +/* Updates the internal cycle counter state when iocntinh:CY is changed. */ +void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh) +{ + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + + /* We only need to process CY bit toggle. */ + if (!(inhibit ^ prev_cy_inh)) { + return; + } + + if (!(inhibit & RISCV_IOMMU_IOCOUNTINH_CY)) { + /* + * Cycle counter is enabled. Just start the timer again and update + * the clock snapshot value to point to the current time to make + * sure iohpmcycles read is correct. + */ + s->hpmcycle_prev = get_cycles(); + hpm_setup_timer(s, s->hpmcycle_val); + } else { + /* + * Cycle counter is disabled. Stop the timer and update the cycle + * counter to record the current value which is last programmed + * value + the cycles passed so far. + */ + s->hpmcycle_val = s->hpmcycle_val + (get_cycles() - s->hpmcycle_prev); + timer_del(s->hpm_timer); + } +} diff --git a/hw/riscv/riscv-iommu-hpm.h b/hw/riscv/riscv-iommu-hpm.h index cd896d3b7c66..ee888650fbaf 100644 --- a/hw/riscv/riscv-iommu-hpm.h +++ b/hw/riscv/riscv-iommu-hpm.h @@ -26,5 +26,6 @@ uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s); void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, unsigned event_id); void riscv_iommu_hpm_timer_cb(void *priv); +void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh); #endif diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index f26aa15f5553..a4580dca0bcd 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -2024,6 +2024,27 @@ static void riscv_iommu_update_ipsr(RISCVIOMMUState *s, uint64_t data) riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IPSR, ipsr_set, ipsr_clr); } +static void riscv_iommu_process_hpm_writes(RISCVIOMMUState *s, + uint32_t regb, + bool prev_cy_inh) +{ + switch (regb) { + case RISCV_IOMMU_REG_IOCOUNTINH: + riscv_iommu_process_iocntinh_cy(s, prev_cy_inh); + break; + + case RISCV_IOMMU_REG_IOHPMCYCLES: + case RISCV_IOMMU_REG_IOHPMCYCLES + 4: + /* not yet implemented */ + break; + + case RISCV_IOMMU_REG_IOHPMEVT_BASE ... + RISCV_IOMMU_REG_IOHPMEVT(RISCV_IOMMU_IOCOUNT_NUM) + 4: + /* not yet implemented */ + break; + } +} + /* * Write the resulting value of 'data' for the reg specified * by 'reg_addr', after considering read-only/read-write/write-clear @@ -2051,6 +2072,7 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, uint32_t regb = addr & ~3; uint32_t busy = 0; uint64_t val = 0; + bool cy_inh = false; if ((addr & (size - 1)) != 0) { /* Unsupported MMIO alignment or access size */ @@ -2118,6 +2140,16 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, busy = RISCV_IOMMU_TR_REQ_CTL_GO_BUSY; break; + case RISCV_IOMMU_REG_IOCOUNTINH: + if (addr != RISCV_IOMMU_REG_IOCOUNTINH) { + break; + } + /* Store previous value of CY bit. */ + cy_inh = !!(riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTINH) & + RISCV_IOMMU_IOCOUNTINH_CY); + break; + + default: break; } @@ -2136,6 +2168,12 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, stl_le_p(&s->regs_rw[regb], rw | busy); } + /* Process HPM writes and update any internal state if needed. */ + if (regb >= RISCV_IOMMU_REG_IOCOUNTOVF && + regb <= (RISCV_IOMMU_REG_IOHPMEVT(RISCV_IOMMU_IOCOUNT_NUM) + 4)) { + riscv_iommu_process_hpm_writes(s, regb, cy_inh); + } + if (process_fn) { process_fn(s); } From 91dd0bd0216f7a70e5e30cfc24eeea455b4f6993 Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:22 -0300 Subject: [PATCH 0391/1179] hw/riscv/riscv-iommu: add IOHPMCYCLES mmio write RISCV_IOMMU_REG_IOHPMCYCLES writes are done by riscv_iommu_process_hpmcycle_write(), called by the mmio write callback via riscv_iommu_process_hpm_writes(). Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250224190826.1858473-8-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-hpm.c | 19 +++++++++++++++++++ hw/riscv/riscv-iommu-hpm.h | 1 + hw/riscv/riscv-iommu.c | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 70814b942dfd..1cea6b1df18e 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -262,3 +262,22 @@ void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh) timer_del(s->hpm_timer); } } + +void riscv_iommu_process_hpmcycle_write(RISCVIOMMUState *s) +{ + const uint64_t val = riscv_iommu_reg_get64(s, RISCV_IOMMU_REG_IOHPMCYCLES); + const uint32_t ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF); + + /* + * Clear OF bit in IOCNTOVF if it's being cleared in IOHPMCYCLES register. + */ + if (get_field(ovf, RISCV_IOMMU_IOCOUNTOVF_CY) && + !get_field(val, RISCV_IOMMU_IOHPMCYCLES_OVF)) { + riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IOCOUNTOVF, 0, + RISCV_IOMMU_IOCOUNTOVF_CY); + } + + s->hpmcycle_val = val & ~RISCV_IOMMU_IOHPMCYCLES_OVF; + s->hpmcycle_prev = get_cycles(); + hpm_setup_timer(s, s->hpmcycle_val); +} diff --git a/hw/riscv/riscv-iommu-hpm.h b/hw/riscv/riscv-iommu-hpm.h index ee888650fbaf..0cd550975d3e 100644 --- a/hw/riscv/riscv-iommu-hpm.h +++ b/hw/riscv/riscv-iommu-hpm.h @@ -27,5 +27,6 @@ void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, unsigned event_id); void riscv_iommu_hpm_timer_cb(void *priv); void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh); +void riscv_iommu_process_hpmcycle_write(RISCVIOMMUState *s); #endif diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index a4580dca0bcd..821ecba3a4c7 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -2035,7 +2035,7 @@ static void riscv_iommu_process_hpm_writes(RISCVIOMMUState *s, case RISCV_IOMMU_REG_IOHPMCYCLES: case RISCV_IOMMU_REG_IOHPMCYCLES + 4: - /* not yet implemented */ + riscv_iommu_process_hpmcycle_write(s); break; case RISCV_IOMMU_REG_IOHPMEVT_BASE ... From 4faa3e6f906c832f4c5382fbd618e368525ad2dc Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:23 -0300 Subject: [PATCH 0392/1179] hw/riscv/riscv-iommu: add hpm events mmio write To support hpm events mmio writes, done via riscv_iommu_process_hpmevt_write(), we're also adding the 'hpm-counters' IOMMU property that are used to determine the amount of counters available in the IOMMU. Note that everything we did so far didn't change any IOMMU behavior because we're still not advertising HPM capability to software. This will be done in the next patch. Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-9-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-hpm.c | 88 ++++++++++++++++++++++++++++++++++++++ hw/riscv/riscv-iommu-hpm.h | 1 + hw/riscv/riscv-iommu.c | 4 +- hw/riscv/riscv-iommu.h | 1 + 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 1cea6b1df18e..5518c287a5bd 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -281,3 +281,91 @@ void riscv_iommu_process_hpmcycle_write(RISCVIOMMUState *s) s->hpmcycle_prev = get_cycles(); hpm_setup_timer(s, s->hpmcycle_val); } + +static inline bool check_valid_event_id(unsigned event_id) +{ + return event_id > RISCV_IOMMU_HPMEVENT_INVALID && + event_id < RISCV_IOMMU_HPMEVENT_MAX; +} + +static gboolean hpm_event_equal(gpointer key, gpointer value, gpointer udata) +{ + uint32_t *pair = udata; + + if (GPOINTER_TO_UINT(value) & (1 << pair[0])) { + pair[1] = GPOINTER_TO_UINT(key); + return true; + } + + return false; +} + +/* Caller must check ctr_idx against hpm_ctrs to see if its supported or not. */ +static void update_event_map(RISCVIOMMUState *s, uint64_t value, + uint32_t ctr_idx) +{ + unsigned event_id = get_field(value, RISCV_IOMMU_IOHPMEVT_EVENT_ID); + uint32_t pair[2] = { ctr_idx, RISCV_IOMMU_HPMEVENT_INVALID }; + uint32_t new_value = 1 << ctr_idx; + gpointer data; + + /* + * If EventID field is RISCV_IOMMU_HPMEVENT_INVALID + * remove the current mapping. + */ + if (event_id == RISCV_IOMMU_HPMEVENT_INVALID) { + data = g_hash_table_find(s->hpm_event_ctr_map, hpm_event_equal, pair); + + new_value = GPOINTER_TO_UINT(data) & ~(new_value); + if (new_value != 0) { + g_hash_table_replace(s->hpm_event_ctr_map, + GUINT_TO_POINTER(pair[1]), + GUINT_TO_POINTER(new_value)); + } else { + g_hash_table_remove(s->hpm_event_ctr_map, + GUINT_TO_POINTER(pair[1])); + } + + return; + } + + /* Update the counter mask if the event is already enabled. */ + if (g_hash_table_lookup_extended(s->hpm_event_ctr_map, + GUINT_TO_POINTER(event_id), + NULL, + &data)) { + new_value |= GPOINTER_TO_UINT(data); + } + + g_hash_table_insert(s->hpm_event_ctr_map, + GUINT_TO_POINTER(event_id), + GUINT_TO_POINTER(new_value)); +} + +void riscv_iommu_process_hpmevt_write(RISCVIOMMUState *s, uint32_t evt_reg) +{ + const uint32_t ctr_idx = (evt_reg - RISCV_IOMMU_REG_IOHPMEVT_BASE) >> 3; + const uint32_t ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF); + uint64_t val = riscv_iommu_reg_get64(s, evt_reg); + + if (ctr_idx >= s->hpm_cntrs) { + return; + } + + /* Clear OF bit in IOCNTOVF if it's being cleared in IOHPMEVT register. */ + if (get_field(ovf, BIT(ctr_idx + 1)) && + !get_field(val, RISCV_IOMMU_IOHPMEVT_OF)) { + /* +1 to offset CYCLE register OF bit. */ + riscv_iommu_reg_mod32( + s, RISCV_IOMMU_REG_IOCOUNTOVF, 0, BIT(ctr_idx + 1)); + } + + if (!check_valid_event_id(get_field(val, RISCV_IOMMU_IOHPMEVT_EVENT_ID))) { + /* Reset EventID (WARL) field to invalid. */ + val = set_field(val, RISCV_IOMMU_IOHPMEVT_EVENT_ID, + RISCV_IOMMU_HPMEVENT_INVALID); + riscv_iommu_reg_set64(s, evt_reg, val); + } + + update_event_map(s, val, ctr_idx); +} diff --git a/hw/riscv/riscv-iommu-hpm.h b/hw/riscv/riscv-iommu-hpm.h index 0cd550975d3e..5fc4ef2e8bd5 100644 --- a/hw/riscv/riscv-iommu-hpm.h +++ b/hw/riscv/riscv-iommu-hpm.h @@ -28,5 +28,6 @@ void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, void riscv_iommu_hpm_timer_cb(void *priv); void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh); void riscv_iommu_process_hpmcycle_write(RISCVIOMMUState *s); +void riscv_iommu_process_hpmevt_write(RISCVIOMMUState *s, uint32_t evt_reg); #endif diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index 821ecba3a4c7..cdbb84818130 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -2040,7 +2040,7 @@ static void riscv_iommu_process_hpm_writes(RISCVIOMMUState *s, case RISCV_IOMMU_REG_IOHPMEVT_BASE ... RISCV_IOMMU_REG_IOHPMEVT(RISCV_IOMMU_IOCOUNT_NUM) + 4: - /* not yet implemented */ + riscv_iommu_process_hpmevt_write(s, regb & ~7); break; } } @@ -2487,6 +2487,8 @@ static const Property riscv_iommu_properties[] = { DEFINE_PROP_BOOL("g-stage", RISCVIOMMUState, enable_g_stage, TRUE), DEFINE_PROP_LINK("downstream-mr", RISCVIOMMUState, target_mr, TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_PROP_UINT8("hpm-counters", RISCVIOMMUState, hpm_cntrs, + RISCV_IOMMU_IOCOUNT_NUM), }; static void riscv_iommu_class_init(ObjectClass *klass, void* data) diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 2fef6eed273b..a31aa62144f4 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -90,6 +90,7 @@ struct RISCVIOMMUState { /* HPM event counters */ GHashTable *hpm_event_ctr_map; /* Mapping of events to counters */ + uint8_t hpm_cntrs; }; void riscv_iommu_pci_setup_iommu(RISCVIOMMUState *iommu, PCIBus *bus, From 3b1d07b6279defa40bf9903f420d8ddcf839794d Mon Sep 17 00:00:00 2001 From: Tomasz Jeznach Date: Mon, 24 Feb 2025 16:08:24 -0300 Subject: [PATCH 0393/1179] hw/riscv/riscv-iommu.c: add RISCV_IOMMU_CAP_HPM cap Now that we have every piece in place we can advertise CAP_HTM to software, allowing any HPM aware driver to make use of the counters. HPM is enabled/disabled via the 'hpm-counters' attribute. Default value is 31, max value is also 31. Setting it to zero will disable HPM support. Signed-off-by: Tomasz Jeznach Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-10-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index cdbb84818130..d46beb2d64ce 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -2357,6 +2357,15 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) RISCV_IOMMU_CAP_SV48X4 | RISCV_IOMMU_CAP_SV57X4; } + if (s->hpm_cntrs > 0) { + /* Clip number of HPM counters to maximum supported (31). */ + if (s->hpm_cntrs > RISCV_IOMMU_IOCOUNT_NUM) { + s->hpm_cntrs = RISCV_IOMMU_IOCOUNT_NUM; + } + /* Enable hardware performance monitor interface */ + s->cap |= RISCV_IOMMU_CAP_HPM; + } + /* Out-of-reset translation mode: OFF (DMA disabled) BARE (passthrough) */ s->ddtp = set_field(0, RISCV_IOMMU_DDTP_MODE, s->enable_off ? RISCV_IOMMU_DDTP_MODE_OFF : RISCV_IOMMU_DDTP_MODE_BARE); @@ -2404,6 +2413,18 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) RISCV_IOMMU_TR_REQ_CTL_GO_BUSY); } + /* If HPM registers are enabled. */ + if (s->cap & RISCV_IOMMU_CAP_HPM) { + /* +1 for cycle counter bit. */ + stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_IOCOUNTINH], + ~((2 << s->hpm_cntrs) - 1)); + stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_IOHPMCYCLES], 0); + memset(&s->regs_ro[RISCV_IOMMU_REG_IOHPMCTR_BASE], + 0x00, s->hpm_cntrs * 8); + memset(&s->regs_ro[RISCV_IOMMU_REG_IOHPMEVT_BASE], + 0x00, s->hpm_cntrs * 8); + } + /* Memory region for downstream access, if specified. */ if (s->target_mr) { s->target_as = g_new0(AddressSpace, 1); From 66975d9cd73642ad4bb488c2e53d5b3c857e9ce8 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Mon, 24 Feb 2025 16:08:25 -0300 Subject: [PATCH 0394/1179] hw/riscv: add IOMMU HPM trace events Add a handful of trace events to allow for an easier time debugging the HPM feature. Signed-off-by: Daniel Henrique Barboza Acked-by: Alistair Francis Message-ID: <20250224190826.1858473-11-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- hw/riscv/riscv-iommu-hpm.c | 10 ++++++++++ hw/riscv/trace-events | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 5518c287a5bd..c5034bff7953 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -39,6 +39,8 @@ uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s) const uint64_t ctr_prev = s->hpmcycle_prev; const uint64_t ctr_val = s->hpmcycle_val; + trace_riscv_iommu_hpm_read(cycle, inhibit, ctr_prev, ctr_val); + if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) { /* * Counter should not increment if inhibit bit is set. We can't really @@ -61,6 +63,8 @@ static void hpm_incr_ctr(RISCVIOMMUState *s, uint32_t ctr_idx) cntr_val = ldq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off]); stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off], cntr_val + 1); + trace_riscv_iommu_hpm_incr_ctr(cntr_val); + /* Handle the overflow scenario. */ if (cntr_val == UINT64_MAX) { /* @@ -244,6 +248,8 @@ void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh) return; } + trace_riscv_iommu_hpm_iocntinh_cy(prev_cy_inh); + if (!(inhibit & RISCV_IOMMU_IOCOUNTINH_CY)) { /* * Cycle counter is enabled. Just start the timer again and update @@ -268,6 +274,8 @@ void riscv_iommu_process_hpmcycle_write(RISCVIOMMUState *s) const uint64_t val = riscv_iommu_reg_get64(s, RISCV_IOMMU_REG_IOHPMCYCLES); const uint32_t ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF); + trace_riscv_iommu_hpm_cycle_write(ovf, val); + /* * Clear OF bit in IOCNTOVF if it's being cleared in IOHPMCYCLES register. */ @@ -352,6 +360,8 @@ void riscv_iommu_process_hpmevt_write(RISCVIOMMUState *s, uint32_t evt_reg) return; } + trace_riscv_iommu_hpm_evt_write(ctr_idx, ovf, val); + /* Clear OF bit in IOCNTOVF if it's being cleared in IOHPMEVT register. */ if (get_field(ovf, BIT(ctr_idx + 1)) && !get_field(val, RISCV_IOMMU_IOHPMEVT_OF)) { diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index 7bcbb03d0836..b50b14a65422 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -19,3 +19,8 @@ riscv_iommu_sys_irq_sent(uint32_t vector) "IRQ sent to vector %u" riscv_iommu_sys_msi_sent(uint32_t vector, uint64_t msi_addr, uint32_t msi_data, uint32_t result) "MSI sent to vector %u msi_addr 0x%"PRIx64" msi_data 0x%x result %u" riscv_iommu_sys_reset_hold(int reset_type) "reset type %d" riscv_iommu_pci_reset_hold(int reset_type) "reset type %d" +riscv_iommu_hpm_read(uint64_t cycle, uint32_t inhibit, uint64_t ctr_prev, uint64_t ctr_val) "cycle 0x%"PRIx64" inhibit 0x%x ctr_prev 0x%"PRIx64" ctr_val 0x%"PRIx64 +riscv_iommu_hpm_incr_ctr(uint64_t cntr_val) "cntr_val 0x%"PRIx64 +riscv_iommu_hpm_iocntinh_cy(bool prev_cy_inh) "prev_cy_inh %d" +riscv_iommu_hpm_cycle_write(uint32_t ovf, uint64_t val) "ovf 0x%x val 0x%"PRIx64 +riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx 0x%x ovf 0x%x val 0x%"PRIx64 From beeb56a43e8b4568c4513371349487f349df8864 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Mon, 24 Feb 2025 16:08:26 -0300 Subject: [PATCH 0395/1179] docs/specs/riscv-iommu.rst: add HPM support info Signed-off-by: Daniel Henrique Barboza Reviewed-by: Alistair Francis Message-ID: <20250224190826.1858473-12-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- docs/specs/riscv-iommu.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/specs/riscv-iommu.rst b/docs/specs/riscv-iommu.rst index b1538c9ead5f..000c7e1f57f7 100644 --- a/docs/specs/riscv-iommu.rst +++ b/docs/specs/riscv-iommu.rst @@ -82,6 +82,8 @@ Several options are available to control the capabilities of the device, namely: - "off" (Out-of-reset translation mode: 'on' for DMA disabled, 'off' for 'BARE' (passthrough)) - "s-stage": enable s-stage support - "g-stage": enable g-stage support +- "hpm-counters": number of hardware performance counters available. Maximum value is 31. + Default value is 31. Use 0 (zero) to disable HPM support riscv-iommu-sys device ---------------------- From 1c17df6fc4bbddf55c8a64a3db7fb1115ecd30f5 Mon Sep 17 00:00:00 2001 From: Quan Zhou Date: Mon, 24 Feb 2025 12:39:39 +0800 Subject: [PATCH 0396/1179] target/riscv/kvm: Add some exts support When the Sscofpmf/Svade/Svadu/Smnpm/Ssnpm exts is available expose it to the guest so that guest can use it. Signed-off-by: Quan Zhou Reviewed-by: Daniel Henrique Barboza Message-ID: <303616ccad2b5309768157b50d93b3e89fecc9cb.1740371468.git.zhouquan@iscas.ac.cn> Signed-off-by: Alistair Francis --- target/riscv/kvm/kvm-cpu.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index 471fd554b369..e436083dbb47 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -321,9 +321,14 @@ static KVMCPUConfig kvm_multi_ext_cfgs[] = { KVM_EXT_CFG("zvksed", ext_zvksed, KVM_RISCV_ISA_EXT_ZVKSED), KVM_EXT_CFG("zvksh", ext_zvksh, KVM_RISCV_ISA_EXT_ZVKSH), KVM_EXT_CFG("zvkt", ext_zvkt, KVM_RISCV_ISA_EXT_ZVKT), + KVM_EXT_CFG("smnpm", ext_smnpm, KVM_RISCV_ISA_EXT_SMNPM), KVM_EXT_CFG("smstateen", ext_smstateen, KVM_RISCV_ISA_EXT_SMSTATEEN), KVM_EXT_CFG("ssaia", ext_ssaia, KVM_RISCV_ISA_EXT_SSAIA), + KVM_EXT_CFG("sscofpmf", ext_sscofpmf, KVM_RISCV_ISA_EXT_SSCOFPMF), + KVM_EXT_CFG("ssnpm", ext_ssnpm, KVM_RISCV_ISA_EXT_SSNPM), KVM_EXT_CFG("sstc", ext_sstc, KVM_RISCV_ISA_EXT_SSTC), + KVM_EXT_CFG("svade", ext_svade, KVM_RISCV_ISA_EXT_SVADE), + KVM_EXT_CFG("svadu", ext_svadu, KVM_RISCV_ISA_EXT_SVADU), KVM_EXT_CFG("svinval", ext_svinval, KVM_RISCV_ISA_EXT_SVINVAL), KVM_EXT_CFG("svnapot", ext_svnapot, KVM_RISCV_ISA_EXT_SVNAPOT), KVM_EXT_CFG("svpbmt", ext_svpbmt, KVM_RISCV_ISA_EXT_SVPBMT), From c869f4129c8e35f3c234d757c0227c53134aca16 Mon Sep 17 00:00:00 2001 From: Andrea Bolognani Date: Mon, 27 Jan 2025 19:29:22 +0100 Subject: [PATCH 0397/1179] binfmt: Shuffle things around This should make no difference from the functional point of view and it's just preparation for upcoming changes. Signed-off-by: Andrea Bolognani Reviewed-by: Alistair Francis Reviewed-by: Laurent Vivier Message-ID: <20250127182924.103510-2-abologna@redhat.com> Signed-off-by: Alistair Francis --- scripts/qemu-binfmt-conf.sh | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/qemu-binfmt-conf.sh b/scripts/qemu-binfmt-conf.sh index 6ef9f118d9ff..426f075e31e9 100755 --- a/scripts/qemu-binfmt-conf.sh +++ b/scripts/qemu-binfmt-conf.sh @@ -318,20 +318,23 @@ qemu_set_binfmts() { mask=$(eval echo \$${cpu}_mask) family=$(eval echo \$${cpu}_family) + target="$cpu" + if [ "$cpu" = "i486" ] ; then + target="i386" + fi + + qemu="$QEMU_PATH/qemu-$target$QEMU_SUFFIX" + if [ "$magic" = "" ] || [ "$mask" = "" ] || [ "$family" = "" ] ; then echo "INTERNAL ERROR: unknown cpu $cpu" 1>&2 continue fi - qemu="$QEMU_PATH/qemu-$cpu" - if [ "$cpu" = "i486" ] ; then - qemu="$QEMU_PATH/qemu-i386" + if [ "$host_family" = "$family" ] ; then + continue fi - qemu="$qemu$QEMU_SUFFIX" - if [ "$host_family" != "$family" ] ; then - $BINFMT_SET - fi + $BINFMT_SET done } From 2770a46b20b7ccd23019a7b6b6631de933b53c8e Mon Sep 17 00:00:00 2001 From: Andrea Bolognani Date: Mon, 27 Jan 2025 19:29:23 +0100 Subject: [PATCH 0398/1179] binfmt: Normalize host CPU architecture Right now information regarding the family each CPU type belongs to is recorded in two places: the large data table at the top of the script, and the qemu_host_family() function. We can make things better by mapping host CPU architecture to QEMU target in the few cases where the two don't already match and then using the data table to look up the family, same as we're already doing for the guest CPU architecture. Being able to reason in terms of QEMU target regardless of whether we're looking at the host or guest CPU architecture will come in handy to implement upcoming changes. A couple of entries are dropped in the process: BePC and Power Macintosh. I'm quite certain neither of those have ever been reported as CPU architectures by Linux. I believe many more of the entries that are carried forward could be dropped as well, but I don't have the same level of confidence there so I decided to play it safe just in case. Signed-off-by: Andrea Bolognani Reviewed-by: Alistair Francis Reviewed-by: Laurent Vivier Message-ID: <20250127182924.103510-3-abologna@redhat.com> Signed-off-by: Alistair Francis --- scripts/qemu-binfmt-conf.sh | 44 +++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/scripts/qemu-binfmt-conf.sh b/scripts/qemu-binfmt-conf.sh index 426f075e31e9..8d9136a29f10 100755 --- a/scripts/qemu-binfmt-conf.sh +++ b/scripts/qemu-binfmt-conf.sh @@ -144,35 +144,35 @@ loongarch64_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x loongarch64_mask='\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' loongarch64_family=loongarch -qemu_get_family() { - cpu=${HOST_ARCH:-$(uname -m)} +# Converts the name of a host CPU architecture to the corresponding QEMU +# target. +# +# FIXME: This can probably be simplified a lot by dropping most entries. +# Remember that the script is only used on Linux, so we only need to +# handle the strings Linux uses to report the host CPU architecture. +qemu_normalize() { + cpu="$1" case "$cpu" in - amd64|i386|i486|i586|i686|i86pc|BePC|x86_64) + i[3-6]86) echo "i386" ;; - mips*) - echo "mips" + amd64) + echo "x86_64" ;; - "Power Macintosh"|ppc64|powerpc|ppc) + powerpc) echo "ppc" ;; - ppc64el|ppc64le) - echo "ppcle" + ppc64el) + echo "ppc64le" ;; - arm|armel|armhf|arm64|armv[4-9]*l|aarch64) + armel|armhf|armv[4-9]*l) echo "arm" ;; - armeb|armv[4-9]*b|aarch64_be) + armv[4-9]*b) echo "armeb" ;; - sparc*) - echo "sparc" - ;; - riscv*) - echo "riscv" - ;; - loongarch*) - echo "loongarch" + arm64) + echo "aarch64" ;; *) echo "$cpu" @@ -309,7 +309,13 @@ EOF qemu_set_binfmts() { # probe cpu type - host_family=$(qemu_get_family) + host_cpu=$(qemu_normalize ${HOST_ARCH:-$(uname -m)}) + host_family=$(eval echo \$${host_cpu}_family) + + if [ "$host_family" = "" ] ; then + echo "INTERNAL ERROR: unknown host cpu $host_cpu" 1>&2 + exit 1 + fi # register the interpreter for each cpu except for the native one From 1887cf2368189087fdf977fb8d09b5ad47cc7aea Mon Sep 17 00:00:00 2001 From: Andrea Bolognani Date: Mon, 27 Jan 2025 19:29:24 +0100 Subject: [PATCH 0399/1179] binfmt: Add --ignore-family option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now, the script has worked under the assumption that a host CPU can run binaries targeting any CPU in the same family. That's a fair enough assumption when it comes to running i386 binaries on x86_64, but it doesn't quite apply in the general case. For example, while riscv64 CPUs could theoretically run riscv32 applications natively, in practice there exist few (if any?) CPUs that implement the necessary silicon; moreover, even if you had one such CPU, your host OS would most likely not have enabled the necessary kernel bits. This new option gives distro packagers the ability to opt out of the assumption, likely on a per-architecture basis, and make things work out of the box for a larger fraction of their user base. As an interesting side effect, this makes it possible to enable execution of 64-bit binaries on 32-bit CPUs of the same family, which is a perfectly valid use case that apparently hadn't been considered until now. Link: https://src.fedoraproject.org/rpms/qemu/pull-request/72 Thanks: David Abdurachmanov Thanks: Daniel P. Berrangé Signed-off-by: Andrea Bolognani Reviewed-by: Alistair Francis Reviewed-by: Laurent Vivier Message-ID: <20250127182924.103510-4-abologna@redhat.com> Signed-off-by: Alistair Francis --- scripts/qemu-binfmt-conf.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/qemu-binfmt-conf.sh b/scripts/qemu-binfmt-conf.sh index 8d9136a29f10..5fd462b1d1c8 100755 --- a/scripts/qemu-binfmt-conf.sh +++ b/scripts/qemu-binfmt-conf.sh @@ -205,6 +205,9 @@ Usage: qemu-binfmt-conf.sh [--qemu-path PATH][--debian][--systemd CPU] --persistent: if yes, the interpreter is loaded when binfmt is configured and remains in memory. All future uses are cloned from the open file. + --ignore-family: if yes, it is assumed that the host CPU (e.g. riscv64) + can't natively run programs targeting a CPU that is + part of the same family (e.g. riscv32). --preserve-argv0 preserve argv[0] To import templates with update-binfmts, use : @@ -337,7 +340,12 @@ qemu_set_binfmts() { fi if [ "$host_family" = "$family" ] ; then - continue + # When --ignore-family is used, we have to generate rules even + # for targets that are in the same family as the host CPU. The + # only exception is of course when the CPU types exactly match + if [ "$target" = "$host_cpu" ] || [ "$IGNORE_FAMILY" = "no" ] ; then + continue + fi fi $BINFMT_SET @@ -355,10 +363,11 @@ CREDENTIAL=no PERSISTENT=no PRESERVE_ARG0=no QEMU_SUFFIX="" +IGNORE_FAMILY=no _longopts="debian,systemd:,qemu-path:,qemu-suffix:,exportdir:,help,credential:,\ -persistent:,preserve-argv0:" -options=$(getopt -o ds:Q:S:e:hc:p:g:F: -l ${_longopts} -- "$@") +persistent:,preserve-argv0:,ignore-family:" +options=$(getopt -o ds:Q:S:e:hc:p:g:F:i: -l ${_longopts} -- "$@") eval set -- "$options" while true ; do @@ -418,6 +427,10 @@ while true ; do shift PRESERVE_ARG0="$1" ;; + -i|--ignore-family) + shift + IGNORE_FAMILY="$1" + ;; *) break ;; From afd4f4aa7f605caf8efa2f3c590e1f2572f1ea50 Mon Sep 17 00:00:00 2001 From: Yong-Xuan Wang Date: Mon, 24 Feb 2025 10:57:18 +0800 Subject: [PATCH 0400/1179] hw/intc/imsic: refine the IMSIC realize When the IMSIC is emulated in the kernel, the GPIO output lines to CPUs and aia_ireg_rmw_fn setting can be remove. In this case the IMSIC trigger CPU interrupts by KVM APIs, and the RMW of IREG is handled in kernel. This patch also move the code that claim the CPU interrupts to the beginning of IMSIC realization. This can avoid the unnecessary resource allocation before checking failed. Signed-off-by: Yong-Xuan Wang Reviewed-by: Daniel Henrique Barboza Message-ID: <20250224025722.3999-2-yongxuan.wang@sifive.com> Signed-off-by: Alistair Francis --- hw/intc/riscv_imsic.c | 47 ++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/hw/intc/riscv_imsic.c b/hw/intc/riscv_imsic.c index dc8162c0a7c9..241b12fef09f 100644 --- a/hw/intc/riscv_imsic.c +++ b/hw/intc/riscv_imsic.c @@ -349,7 +349,19 @@ static void riscv_imsic_realize(DeviceState *dev, Error **errp) CPUState *cpu = cpu_by_arch_id(imsic->hartid); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; + /* Claim the CPU interrupt to be triggered by this IMSIC */ + if (riscv_cpu_claim_interrupts(rcpu, + (imsic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { + error_setg(errp, "%s already claimed", + (imsic->mmode) ? "MEIP" : "SEIP"); + return; + } + if (!kvm_irqchip_in_kernel()) { + /* Create output IRQ lines */ + imsic->external_irqs = g_malloc(sizeof(qemu_irq) * imsic->num_pages); + qdev_init_gpio_out(dev, imsic->external_irqs, imsic->num_pages); + imsic->num_eistate = imsic->num_pages * imsic->num_irqs; imsic->eidelivery = g_new0(uint32_t, imsic->num_pages); imsic->eithreshold = g_new0(uint32_t, imsic->num_pages); @@ -361,18 +373,6 @@ static void riscv_imsic_realize(DeviceState *dev, Error **errp) IMSIC_MMIO_SIZE(imsic->num_pages)); sysbus_init_mmio(SYS_BUS_DEVICE(dev), &imsic->mmio); - /* Claim the CPU interrupt to be triggered by this IMSIC */ - if (riscv_cpu_claim_interrupts(rcpu, - (imsic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { - error_setg(errp, "%s already claimed", - (imsic->mmode) ? "MEIP" : "SEIP"); - return; - } - - /* Create output IRQ lines */ - imsic->external_irqs = g_malloc(sizeof(qemu_irq) * imsic->num_pages); - qdev_init_gpio_out(dev, imsic->external_irqs, imsic->num_pages); - /* Force select AIA feature and setup CSR read-modify-write callback */ if (env) { if (!imsic->mmode) { @@ -381,8 +381,11 @@ static void riscv_imsic_realize(DeviceState *dev, Error **errp) } else { rcpu->cfg.ext_smaia = true; } - riscv_cpu_set_aia_ireg_rmw_fn(env, (imsic->mmode) ? PRV_M : PRV_S, - riscv_imsic_rmw, imsic); + + if (!kvm_irqchip_in_kernel()) { + riscv_cpu_set_aia_ireg_rmw_fn(env, (imsic->mmode) ? PRV_M : PRV_S, + riscv_imsic_rmw, imsic); + } } msi_nonbroken = true; @@ -464,15 +467,17 @@ DeviceState *riscv_imsic_create(hwaddr addr, uint32_t hartid, bool mmode, sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); - for (i = 0; i < num_pages; i++) { - if (!i) { - qdev_connect_gpio_out_named(dev, NULL, i, - qdev_get_gpio_in(DEVICE(cpu), + if (!kvm_irqchip_in_kernel()) { + for (i = 0; i < num_pages; i++) { + if (!i) { + qdev_connect_gpio_out_named(dev, NULL, i, + qdev_get_gpio_in(DEVICE(cpu), (mmode) ? IRQ_M_EXT : IRQ_S_EXT)); - } else { - qdev_connect_gpio_out_named(dev, NULL, i, - qdev_get_gpio_in(DEVICE(cpu), + } else { + qdev_connect_gpio_out_named(dev, NULL, i, + qdev_get_gpio_in(DEVICE(cpu), IRQ_LOCAL_MAX + i - 1)); + } } } From 489840a012866440b7fe33e421b77cd234f83583 Mon Sep 17 00:00:00 2001 From: Yong-Xuan Wang Date: Mon, 24 Feb 2025 10:57:19 +0800 Subject: [PATCH 0401/1179] hw/intc/aplic: refine the APLIC realize When the APLIC is emulated in the kernel, the GPIO output lines to CPUs can be remove. In this case the APLIC trigger CPU interrupts by KVM APIs. This patch also move the code that claim the CPU interrupts to the beginning of APLIC realization. This can avoid the unnecessary resource allocation before checking failed. Signed-off-by: Yong-Xuan Wang Reviewed-by: Daniel Henrique Barboza Message-ID: <20250224025722.3999-3-yongxuan.wang@sifive.com> Signed-off-by: Alistair Francis --- hw/intc/riscv_aplic.c | 49 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/hw/intc/riscv_aplic.c b/hw/intc/riscv_aplic.c index 0974c6a5db39..e5714267c096 100644 --- a/hw/intc/riscv_aplic.c +++ b/hw/intc/riscv_aplic.c @@ -893,6 +893,26 @@ static void riscv_aplic_realize(DeviceState *dev, Error **errp) RISCVAPLICState *aplic = RISCV_APLIC(dev); if (riscv_use_emulated_aplic(aplic->msimode)) { + /* Create output IRQ lines for non-MSI mode */ + if (!aplic->msimode) { + /* Claim the CPU interrupt to be triggered by this APLIC */ + for (i = 0; i < aplic->num_harts; i++) { + RISCVCPU *cpu; + + cpu = RISCV_CPU(cpu_by_arch_id(aplic->hartid_base + i)); + if (riscv_cpu_claim_interrupts(cpu, + (aplic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { + error_report("%s already claimed", + (aplic->mmode) ? "MEIP" : "SEIP"); + exit(1); + } + } + + aplic->external_irqs = g_malloc(sizeof(qemu_irq) * + aplic->num_harts); + qdev_init_gpio_out(dev, aplic->external_irqs, aplic->num_harts); + } + aplic->bitfield_words = (aplic->num_irqs + 31) >> 5; aplic->sourcecfg = g_new0(uint32_t, aplic->num_irqs); aplic->state = g_new0(uint32_t, aplic->num_irqs); @@ -927,23 +947,6 @@ static void riscv_aplic_realize(DeviceState *dev, Error **errp) } } - /* Create output IRQ lines for non-MSI mode */ - if (!aplic->msimode) { - aplic->external_irqs = g_malloc(sizeof(qemu_irq) * aplic->num_harts); - qdev_init_gpio_out(dev, aplic->external_irqs, aplic->num_harts); - - /* Claim the CPU interrupt to be triggered by this APLIC */ - for (i = 0; i < aplic->num_harts; i++) { - RISCVCPU *cpu = RISCV_CPU(cpu_by_arch_id(aplic->hartid_base + i)); - if (riscv_cpu_claim_interrupts(cpu, - (aplic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { - error_report("%s already claimed", - (aplic->mmode) ? "MEIP" : "SEIP"); - exit(1); - } - } - } - msi_nonbroken = true; } @@ -1067,15 +1070,15 @@ DeviceState *riscv_aplic_create(hwaddr addr, hwaddr size, if (riscv_use_emulated_aplic(msimode)) { sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); - } - if (!msimode) { - for (i = 0; i < num_harts; i++) { - CPUState *cpu = cpu_by_arch_id(hartid_base + i); + if (!msimode) { + for (i = 0; i < num_harts; i++) { + CPUState *cpu = cpu_by_arch_id(hartid_base + i); - qdev_connect_gpio_out_named(dev, NULL, i, - qdev_get_gpio_in(DEVICE(cpu), + qdev_connect_gpio_out_named(dev, NULL, i, + qdev_get_gpio_in(DEVICE(cpu), (mmode) ? IRQ_M_EXT : IRQ_S_EXT)); + } } } From f0fe655502a4dde6b24383be73dbd81e774bf657 Mon Sep 17 00:00:00 2001 From: Yong-Xuan Wang Date: Mon, 24 Feb 2025 10:57:20 +0800 Subject: [PATCH 0402/1179] hw/intc/aplic: refine kvm_msicfgaddr Let kvm_msicfgaddr use the same format with mmsicfgaddr and smsicfgaddr. Signed-off-by: Yong-Xuan Wang Reviewed-by: Daniel Henrique Barboza Message-ID: <20250224025722.3999-4-yongxuan.wang@sifive.com> Signed-off-by: Alistair Francis --- hw/intc/riscv_aplic.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/hw/intc/riscv_aplic.c b/hw/intc/riscv_aplic.c index e5714267c096..5964cde7e09e 100644 --- a/hw/intc/riscv_aplic.c +++ b/hw/intc/riscv_aplic.c @@ -181,8 +181,10 @@ void riscv_aplic_set_kvm_msicfgaddr(RISCVAPLICState *aplic, hwaddr addr) { #ifdef CONFIG_KVM if (riscv_use_emulated_aplic(aplic->msimode)) { + addr >>= APLIC_xMSICFGADDR_PPN_SHIFT; aplic->kvm_msicfgaddr = extract64(addr, 0, 32); - aplic->kvm_msicfgaddrH = extract64(addr, 32, 32); + aplic->kvm_msicfgaddrH = extract64(addr, 32, 32) & + APLIC_xMSICFGADDRH_VALID_MASK; } #endif } @@ -403,12 +405,17 @@ static void riscv_aplic_msi_send(RISCVAPLICState *aplic, } } - if (aplic->mmode) { - msicfgaddr = aplic_m->mmsicfgaddr; - msicfgaddrH = aplic_m->mmsicfgaddrH; + if (aplic->kvm_splitmode) { + msicfgaddr = aplic->kvm_msicfgaddr; + msicfgaddrH = ((uint64_t)aplic->kvm_msicfgaddrH << 32); } else { - msicfgaddr = aplic_m->smsicfgaddr; - msicfgaddrH = aplic_m->smsicfgaddrH; + if (aplic->mmode) { + msicfgaddr = aplic_m->mmsicfgaddr; + msicfgaddrH = aplic_m->mmsicfgaddrH; + } else { + msicfgaddr = aplic_m->smsicfgaddr; + msicfgaddrH = aplic_m->smsicfgaddrH; + } } lhxs = (msicfgaddrH >> APLIC_xMSICFGADDRH_LHXS_SHIFT) & @@ -431,11 +438,6 @@ static void riscv_aplic_msi_send(RISCVAPLICState *aplic, addr |= (uint64_t)(guest_idx & APLIC_xMSICFGADDR_PPN_HART(lhxs)); addr <<= APLIC_xMSICFGADDR_PPN_SHIFT; - if (aplic->kvm_splitmode) { - addr |= aplic->kvm_msicfgaddr; - addr |= ((uint64_t)aplic->kvm_msicfgaddrH << 32); - } - address_space_stl_le(&address_space_memory, addr, eiid, MEMTXATTRS_UNSPECIFIED, &result); if (result != MEMTX_OK) { From 1a65210876e79b4cbd40844f715eb24fb9abff14 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Mon, 24 Feb 2025 09:31:18 -0300 Subject: [PATCH 0403/1179] target/riscv/cpu: remove unneeded !kvm_enabled() check Remove the !kvm_enabled() check in kvm_riscv_reset_vcpu() since the function is already being gated by kvm_enabled() in riscv_cpu_reset_hold(). Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250224123120.1644186-2-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/kvm/kvm-cpu.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index e436083dbb47..e110e0b90905 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -1611,9 +1611,6 @@ void kvm_riscv_reset_vcpu(RISCVCPU *cpu) CPURISCVState *env = &cpu->env; int i; - if (!kvm_enabled()) { - return; - } for (i = 0; i < 32; i++) { env->gpr[i] = 0; } From a1e61fc44b1a5fdad08206cbd7f015d1cc146713 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Mon, 24 Feb 2025 09:31:19 -0300 Subject: [PATCH 0404/1179] target/riscv/kvm: add kvm_riscv_reset_regs_csr() We're setting reset vals for KVM csrs during kvm_riscv_reset_vcpu(), but in no particular order and missing some of them (like env->mstatus). Create a helper to do that, unclogging reset_vcpu(), and initialize env->mstatus as well. Keep the regs in the same order they appear in struct kvm_riscv_csr from the KVM UAPI, similar to what kvm_riscv_(get|put)_regs_csr are doing. This will make a bit easier to add new KVM CSRs and to verify which values we're writing back to KVM during vcpu reset. Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Reviewed-by: Alistair Francis Message-ID: <20250224123120.1644186-3-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/kvm/kvm-cpu.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index e110e0b90905..ba54eaa0b4a5 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -613,6 +613,19 @@ static int kvm_riscv_put_regs_core(CPUState *cs) return ret; } +static void kvm_riscv_reset_regs_csr(CPURISCVState *env) +{ + env->mstatus = 0; + env->mie = 0; + env->stvec = 0; + env->sscratch = 0; + env->sepc = 0; + env->scause = 0; + env->stval = 0; + env->mip = 0; + env->satp = 0; +} + static int kvm_riscv_get_regs_csr(CPUState *cs) { CPURISCVState *env = &RISCV_CPU(cs)->env; @@ -1617,14 +1630,8 @@ void kvm_riscv_reset_vcpu(RISCVCPU *cpu) env->pc = cpu->env.kernel_addr; env->gpr[10] = kvm_arch_vcpu_id(CPU(cpu)); /* a0 */ env->gpr[11] = cpu->env.fdt_addr; /* a1 */ - env->satp = 0; - env->mie = 0; - env->stvec = 0; - env->sscratch = 0; - env->sepc = 0; - env->scause = 0; - env->stval = 0; - env->mip = 0; + + kvm_riscv_reset_regs_csr(env); } void kvm_riscv_set_irq(RISCVCPU *cpu, int irq, int level) From 4db19d5b21e058e6eb3474b6be470d1184afaa9e Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Mon, 24 Feb 2025 09:31:20 -0300 Subject: [PATCH 0405/1179] target/riscv/kvm: add missing KVM CSRs We're missing scounteren and senvcfg CSRs, both already present in the KVM UAPI. Signed-off-by: Daniel Henrique Barboza Reviewed-by: Andrew Jones Acked-by: Alistair Francis Message-ID: <20250224123120.1644186-4-dbarboza@ventanamicro.com> Signed-off-by: Alistair Francis --- target/riscv/kvm/kvm-cpu.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index ba54eaa0b4a5..7f3b59cb72c9 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -624,6 +624,8 @@ static void kvm_riscv_reset_regs_csr(CPURISCVState *env) env->stval = 0; env->mip = 0; env->satp = 0; + env->scounteren = 0; + env->senvcfg = 0; } static int kvm_riscv_get_regs_csr(CPUState *cs) @@ -639,6 +641,8 @@ static int kvm_riscv_get_regs_csr(CPUState *cs) KVM_RISCV_GET_CSR(cs, env, stval, env->stval); KVM_RISCV_GET_CSR(cs, env, sip, env->mip); KVM_RISCV_GET_CSR(cs, env, satp, env->satp); + KVM_RISCV_GET_CSR(cs, env, scounteren, env->scounteren); + KVM_RISCV_GET_CSR(cs, env, senvcfg, env->senvcfg); return 0; } @@ -656,6 +660,8 @@ static int kvm_riscv_put_regs_csr(CPUState *cs) KVM_RISCV_SET_CSR(cs, env, stval, env->stval); KVM_RISCV_SET_CSR(cs, env, sip, env->mip); KVM_RISCV_SET_CSR(cs, env, satp, env->satp); + KVM_RISCV_SET_CSR(cs, env, scounteren, env->scounteren); + KVM_RISCV_SET_CSR(cs, env, senvcfg, env->senvcfg); return 0; } From b61a4eb3f32ce74c5ffe001806f9e786788a546f Mon Sep 17 00:00:00 2001 From: John Snow Date: Sun, 23 Feb 2025 22:37:35 -0500 Subject: [PATCH 0406/1179] docs/qapidoc: support header-less freeform sections The code as written crashes when a free-form documentation block doesn't start with a heading or subheading, for example: | ## | # Just text, no heading. | ## The code will attempt to use the `node` variable uninitialized. To fix, create a generic block to insert the doc text into. (This patch also removes a lingering pylint warning in the QAPIDoc implementation that prevents getting a clean baseline to use for forthcoming additions.) Fixes: 43e0d14ee09a (docs/sphinx: fix extra stuff in TOC after freeform QMP sections) Signed-off-by: John Snow Message-ID: <20250224033741.222749-5-jsnow@redhat.com> Reviewed-by: Markus Armbruster [Test updated to cover this] Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 2 ++ tests/qapi-schema/doc-good.json | 4 ++++ tests/qapi-schema/doc-good.out | 3 +++ 3 files changed, 9 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 5f96b46270bb..5a4d7388b29b 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -421,6 +421,8 @@ def freeform(self, doc): node = self._start_new_heading(heading, len(leader)) if text == '': return + else: + node = nodes.container() self._parse_text_into_node(text, node) self._cur_doc = None diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json index f64bf38d8547..0a4f139f8352 100644 --- a/tests/qapi-schema/doc-good.json +++ b/tests/qapi-schema/doc-good.json @@ -11,6 +11,10 @@ # = Section ## +## +# Just text, no heading. +## + ## # == Subsection # diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index ec277be91e92..0a9da3efdeb9 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -56,6 +56,9 @@ event EVT_BOXED Object doc freeform body= = Section +doc freeform + body= +Just text, no heading. doc freeform body= == Subsection From 5e4c466e6ad7d79ef5492b13deb110bb5e90d7c0 Mon Sep 17 00:00:00 2001 From: John Snow Date: Sun, 23 Feb 2025 22:37:37 -0500 Subject: [PATCH 0407/1179] docs/qapidoc: remove example section support Since commit 3c5f6114 (qapi: remove "Example" doc section), Example sections no longer exist, so this support in qapidoc is now dead code. Signed-off-by: John Snow Message-ID: <20250224033741.222749-7-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 5a4d7388b29b..61997fd21afe 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -254,10 +254,6 @@ def _nodes_for_features(self, doc): section += dlnode return [section] - def _nodes_for_example(self, exampletext): - """Return list of doctree nodes for a code example snippet""" - return [nodes.literal_block(exampletext, exampletext)] - def _nodes_for_sections(self, doc): """Return list of doctree nodes for additional sections""" nodelist = [] @@ -275,10 +271,7 @@ def _nodes_for_sections(self, doc): continue snode = self._make_section(section.tag) - if section.tag.startswith('Example'): - snode += self._nodes_for_example(dedent(section.text)) - else: - self._parse_text_into_node(dedent(section.text), snode) + self._parse_text_into_node(dedent(section.text), snode) nodelist.append(snode) return nodelist From dde279925c97b614e45351400bfcf9efaf732f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Mon, 24 Feb 2025 18:20:30 +0000 Subject: [PATCH 0408/1179] qapi: pluggable backend code generators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'qapi.backend.QAPIBackend' class defines an API contract for code generators. The current generator is put into a new class 'qapi.backend.QAPICBackend' and made to be the default impl. A custom generator can be requested using the '-k' arg which takes a fully qualified python class name qapi-gen.py -B the.python.module.QAPIMyBackend This allows out of tree code to use the QAPI generator infrastructure to create new language bindings for QAPI schemas. This has the caveat that the QAPI generator APIs are not guaranteed stable, so consumers of this feature may have to update their code to be compatible with future QEMU releases. Signed-off-by: Daniel P. Berrangé Message-ID: <20250224182030.2089959-1-berrange@redhat.com> Reviewed-by: Markus Armbruster [Error checking and messages tweaked] Signed-off-by: Markus Armbruster --- scripts/qapi/backend.py | 63 ++++++++++++++++++++++++++++++++ scripts/qapi/main.py | 80 +++++++++++++++++++++++------------------ 2 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 scripts/qapi/backend.py diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py new file mode 100644 index 000000000000..14e60aa67af4 --- /dev/null +++ b/scripts/qapi/backend.py @@ -0,0 +1,63 @@ +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. + +from abc import ABC, abstractmethod + +from .commands import gen_commands +from .events import gen_events +from .features import gen_features +from .introspect import gen_introspect +from .schema import QAPISchema +from .types import gen_types +from .visit import gen_visit + + +class QAPIBackend(ABC): + + @abstractmethod + def generate(self, + schema: QAPISchema, + output_dir: str, + prefix: str, + unmask: bool, + builtins: bool, + gen_tracing: bool) -> None: + """ + Generate code for the given schema into the target directory. + + :param schema: The primary QAPI schema object. + :param output_dir: The output directory to store generated code. + :param prefix: Optional C-code prefix for symbol names. + :param unmask: Expose non-ABI names through introspection? + :param builtins: Generate code for built-in types? + + :raise QAPIError: On failures. + """ + + +class QAPICBackend(QAPIBackend): + + def generate(self, + schema: QAPISchema, + output_dir: str, + prefix: str, + unmask: bool, + builtins: bool, + gen_tracing: bool) -> None: + """ + Generate C code for the given schema into the target directory. + + :param schema_file: The primary QAPI schema file. + :param output_dir: The output directory to store generated code. + :param prefix: Optional C-code prefix for symbol names. + :param unmask: Expose non-ABI names through introspection? + :param builtins: Generate code for built-in types? + + :raise QAPIError: On failures. + """ + gen_types(schema, output_dir, prefix, builtins) + gen_features(schema, output_dir, prefix) + gen_visit(schema, output_dir, prefix, builtins) + gen_commands(schema, output_dir, prefix, gen_tracing) + gen_events(schema, output_dir, prefix) + gen_introspect(schema, output_dir, prefix, unmask) diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py index 324081b9fc3d..5b4679abcf11 100644 --- a/scripts/qapi/main.py +++ b/scripts/qapi/main.py @@ -8,18 +8,14 @@ """ import argparse +from importlib import import_module import sys from typing import Optional -from .commands import gen_commands +from .backend import QAPIBackend, QAPICBackend from .common import must_match from .error import QAPIError -from .events import gen_events -from .features import gen_features -from .introspect import gen_introspect from .schema import QAPISchema -from .types import gen_types -from .visit import gen_visit def invalid_prefix_char(prefix: str) -> Optional[str]: @@ -29,32 +25,42 @@ def invalid_prefix_char(prefix: str) -> Optional[str]: return None -def generate(schema_file: str, - output_dir: str, - prefix: str, - unmask: bool = False, - builtins: bool = False, - gen_tracing: bool = False) -> None: - """ - Generate C code for the given schema into the target directory. +def create_backend(path: str) -> QAPIBackend: + if path is None: + return QAPICBackend() - :param schema_file: The primary QAPI schema file. - :param output_dir: The output directory to store generated code. - :param prefix: Optional C-code prefix for symbol names. - :param unmask: Expose non-ABI names through introspection? - :param builtins: Generate code for built-in types? + module_path, dot, class_name = path.rpartition('.') + if not dot: + print("argument of -B must be of the form MODULE.CLASS", + file=sys.stderr) + sys.exit(1) - :raise QAPIError: On failures. - """ - assert invalid_prefix_char(prefix) is None + try: + mod = import_module(module_path) + except Exception as ex: + print(f"unable to import '{module_path}': {ex}", file=sys.stderr) + sys.exit(1) + + try: + klass = getattr(mod, class_name) + except AttributeError: + print(f"module '{module_path}' has no class '{class_name}'", + file=sys.stderr) + sys.exit(1) + + try: + backend = klass() + except Exception as ex: + print(f"backend '{path}' cannot be instantiated: {ex}", + file=sys.stderr) + sys.exit(1) + + if not isinstance(backend, QAPIBackend): + print(f"backend '{path}' must be an instance of QAPIBackend", + file=sys.stderr) + sys.exit(1) - schema = QAPISchema(schema_file) - gen_types(schema, output_dir, prefix, builtins) - gen_features(schema, output_dir, prefix) - gen_visit(schema, output_dir, prefix, builtins) - gen_commands(schema, output_dir, prefix, gen_tracing) - gen_events(schema, output_dir, prefix) - gen_introspect(schema, output_dir, prefix, unmask) + return backend def main() -> int: @@ -77,6 +83,8 @@ def main() -> int: parser.add_argument('-u', '--unmask-non-abi-names', action='store_true', dest='unmask', help="expose non-ABI names in introspection") + parser.add_argument('-B', '--backend', default=None, + help="Python module name for code generator") # Option --suppress-tracing exists so we can avoid solving build system # problems. TODO Drop it when we no longer need it. @@ -93,12 +101,14 @@ def main() -> int: return 1 try: - generate(args.schema, - output_dir=args.output_dir, - prefix=args.prefix, - unmask=args.unmask, - builtins=args.builtins, - gen_tracing=not args.suppress_tracing) + schema = QAPISchema(args.schema) + backend = create_backend(args.backend) + backend.generate(schema, + output_dir=args.output_dir, + prefix=args.prefix, + unmask=args.unmask, + builtins=args.builtins, + gen_tracing=not args.suppress_tracing) except QAPIError as err: print(err, file=sys.stderr) return 1 From 8d127aa866a42b36ec6391b025999059dd47cbfd Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:05 +0100 Subject: [PATCH 0409/1179] Add support for etc/hardware-info fw_cfg file edk2 looks for the etc/hardware-info fw_cfg file to discover hardware which can not easily be found in other ways. Entries consist of a header with hardware type and entry size (HARDWARE_INFO_HEADER), followed by the actual hardware description (which is type specific). The file can have multiple entries. This patch adds the infrastructure to add entries to the file and an entry struct for simple devices (HARDWARE_INFO_SIMPLE_DEVICE) which have an mmio address only. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-2-kraxel@redhat.com> --- hw/uefi/hardware-info.c | 31 +++++++++++++++++++++++++++++ hw/uefi/meson.build | 1 + include/hw/uefi/hardware-info.h | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 hw/uefi/hardware-info.c create mode 100644 hw/uefi/meson.build create mode 100644 include/hw/uefi/hardware-info.h diff --git a/hw/uefi/hardware-info.c b/hw/uefi/hardware-info.c new file mode 100644 index 000000000000..930502a4df3a --- /dev/null +++ b/hw/uefi/hardware-info.c @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * pass hardware information to uefi + * + * see OvmfPkg/Library/HardwareInfoLib/ in edk2 + */ + +#include "qemu/osdep.h" + +#include "hw/nvram/fw_cfg.h" +#include "hw/uefi/hardware-info.h" + +static void *blob; +static uint64_t blobsize; + +void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t infosize) +{ + HARDWARE_INFO_HEADER hdr = { + .type.value = cpu_to_le64(type), + .size = cpu_to_le64(infosize), + }; + + blob = g_realloc(blob, blobsize + sizeof(hdr) + infosize); + memcpy(blob + blobsize, &hdr, sizeof(hdr)); + blobsize += sizeof(hdr); + memcpy(blob + blobsize, info, infosize); + blobsize += infosize; + + fw_cfg_modify_file(fw_cfg_find(), "etc/hardware-info", blob, blobsize); +} diff --git a/hw/uefi/meson.build b/hw/uefi/meson.build new file mode 100644 index 000000000000..a8b168941255 --- /dev/null +++ b/hw/uefi/meson.build @@ -0,0 +1 @@ +system_ss.add(files('hardware-info.c')) diff --git a/include/hw/uefi/hardware-info.h b/include/hw/uefi/hardware-info.h new file mode 100644 index 000000000000..94c38cff2007 --- /dev/null +++ b/include/hw/uefi/hardware-info.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * pass hardware information to uefi + * + * see OvmfPkg/Library/HardwareInfoLib/ in edk2 + */ +#ifndef QEMU_UEFI_HARDWARE_INFO_H +#define QEMU_UEFI_HARDWARE_INFO_H + +/* data structures */ + +typedef enum { + HardwareInfoTypeUndefined = 0, + HardwareInfoTypeHostBridge = 1, + HardwareInfoQemuUefiVars = 2, +} HARDWARE_INFO_TYPE; + +typedef struct { + union { + uint64_t uint64; + HARDWARE_INFO_TYPE value; + } type; + uint64_t size; +} HARDWARE_INFO_HEADER; + +typedef struct { + uint64_t mmio_address; +} HARDWARE_INFO_SIMPLE_DEVICE; + +/* qemu functions */ + +void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t size); + +#endif /* QEMU_UEFI_HARDWARE_INFO_H */ From 995496fd216a6bf6f709c52992c0c423565a63b7 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:06 +0100 Subject: [PATCH 0410/1179] hw/uefi: add include/hw/uefi/var-service-api.h This file defines the register interface of the uefi-vars device. It's only a handful of registers: magic value, command and status registers, location and size of the communication buffer. Reviewed-by: Laszlo Ersek Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-3-kraxel@redhat.com> --- include/hw/uefi/var-service-api.h | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 include/hw/uefi/var-service-api.h diff --git a/include/hw/uefi/var-service-api.h b/include/hw/uefi/var-service-api.h new file mode 100644 index 000000000000..0d71638f3efe --- /dev/null +++ b/include/hw/uefi/var-service-api.h @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi-vars device - API of the virtual device for guest/host communication. + */ +#ifndef QEMU_UEFI_VAR_SERVICE_API_H +#define QEMU_UEFI_VAR_SERVICE_API_H + +/* qom: device names */ +#define TYPE_UEFI_VARS_X64 "uefi-vars-x64" +#define TYPE_UEFI_VARS_SYSBUS "uefi-vars-sysbus" + +/* sysbus: fdt node path */ +#define UEFI_VARS_FDT_NODE "qemu-uefi-vars" +#define UEFI_VARS_FDT_COMPAT "qemu,uefi-vars" + +/* registers */ +#define UEFI_VARS_REG_MAGIC 0x00 /* 16 bit */ +#define UEFI_VARS_REG_CMD_STS 0x02 /* 16 bit */ +#define UEFI_VARS_REG_BUFFER_SIZE 0x04 /* 32 bit */ +#define UEFI_VARS_REG_DMA_BUFFER_ADDR_LO 0x08 /* 32 bit */ +#define UEFI_VARS_REG_DMA_BUFFER_ADDR_HI 0x0c /* 32 bit */ +#define UEFI_VARS_REG_PIO_BUFFER_TRANSFER 0x10 /* 8-64 bit */ +#define UEFI_VARS_REG_PIO_BUFFER_CRC32C 0x18 /* 32 bit (read-only) */ +#define UEFI_VARS_REG_FLAGS 0x1c /* 32 bit */ +#define UEFI_VARS_REGS_SIZE 0x20 + +/* flags register */ +#define UEFI_VARS_FLAG_USE_PIO (1 << 0) + +/* magic value */ +#define UEFI_VARS_MAGIC_VALUE 0xef1 + +/* command values */ +#define UEFI_VARS_CMD_RESET 0x01 +#define UEFI_VARS_CMD_DMA_MM 0x02 +#define UEFI_VARS_CMD_PIO_MM 0x03 +#define UEFI_VARS_CMD_PIO_ZERO_OFFSET 0x04 + +/* status values */ +#define UEFI_VARS_STS_SUCCESS 0x00 +#define UEFI_VARS_STS_BUSY 0x01 +#define UEFI_VARS_STS_ERR_UNKNOWN 0x10 +#define UEFI_VARS_STS_ERR_NOT_SUPPORTED 0x11 +#define UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE 0x12 + + +#endif /* QEMU_UEFI_VAR_SERVICE_API_H */ From 8614eb902b1a63e837e5a8fd871b8c64e700d46f Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:07 +0100 Subject: [PATCH 0411/1179] hw/uefi: add include/hw/uefi/var-service-edk2.h A bunch of #defines and structs copied over from edk2, mostly needed to decode and encode the messages in the communication buffer. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-4-kraxel@redhat.com> --- include/hw/uefi/var-service-edk2.h | 227 +++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 include/hw/uefi/var-service-edk2.h diff --git a/include/hw/uefi/var-service-edk2.h b/include/hw/uefi/var-service-edk2.h new file mode 100644 index 000000000000..c743a8df948d --- /dev/null +++ b/include/hw/uefi/var-service-edk2.h @@ -0,0 +1,227 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi-vars device - structs and defines from edk2 + * + * Note: The edk2 UINTN type has been mapped to uint64_t, + * so the structs are compatible with 64bit edk2 builds. + */ +#ifndef QEMU_UEFI_VAR_SERVICE_EDK2_H +#define QEMU_UEFI_VAR_SERVICE_EDK2_H + +#include "qemu/uuid.h" + +#define MAX_BIT 0x8000000000000000ULL +#define ENCODE_ERROR(StatusCode) (MAX_BIT | (StatusCode)) +#define EFI_SUCCESS 0 +#define EFI_INVALID_PARAMETER ENCODE_ERROR(2) +#define EFI_UNSUPPORTED ENCODE_ERROR(3) +#define EFI_BAD_BUFFER_SIZE ENCODE_ERROR(4) +#define EFI_BUFFER_TOO_SMALL ENCODE_ERROR(5) +#define EFI_WRITE_PROTECTED ENCODE_ERROR(8) +#define EFI_OUT_OF_RESOURCES ENCODE_ERROR(9) +#define EFI_NOT_FOUND ENCODE_ERROR(14) +#define EFI_ACCESS_DENIED ENCODE_ERROR(15) +#define EFI_ALREADY_STARTED ENCODE_ERROR(20) +#define EFI_SECURITY_VIOLATION ENCODE_ERROR(26) + +#define EFI_VARIABLE_NON_VOLATILE 0x01 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x02 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x04 +#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x08 +#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x10 /* deprecated */ +#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x20 +#define EFI_VARIABLE_APPEND_WRITE 0x40 + +/* SecureBootEnable */ +#define SECURE_BOOT_ENABLE 1 +#define SECURE_BOOT_DISABLE 0 + +/* SecureBoot */ +#define SECURE_BOOT_MODE_ENABLE 1 +#define SECURE_BOOT_MODE_DISABLE 0 + +/* CustomMode */ +#define CUSTOM_SECURE_BOOT_MODE 1 +#define STANDARD_SECURE_BOOT_MODE 0 + +/* SetupMode */ +#define SETUP_MODE 1 +#define USER_MODE 0 + +typedef uint64_t efi_status; +typedef struct mm_header mm_header; + +/* EFI_MM_COMMUNICATE_HEADER */ +struct mm_header { + QemuUUID guid; + uint64_t length; +}; + +/* --- EfiSmmVariableProtocol ---------------------------------------- */ + +#define SMM_VARIABLE_FUNCTION_GET_VARIABLE 1 +#define SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME 2 +#define SMM_VARIABLE_FUNCTION_SET_VARIABLE 3 +#define SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO 4 +#define SMM_VARIABLE_FUNCTION_READY_TO_BOOT 5 +#define SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE 6 +#define SMM_VARIABLE_FUNCTION_LOCK_VARIABLE 8 +#define SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE 11 + +typedef struct mm_variable mm_variable; +typedef struct mm_variable_access mm_variable_access; +typedef struct mm_next_variable mm_next_variable; +typedef struct mm_next_variable mm_lock_variable; +typedef struct mm_variable_info mm_variable_info; +typedef struct mm_get_payload_size mm_get_payload_size; + +/* SMM_VARIABLE_COMMUNICATE_HEADER */ +struct mm_variable { + uint64_t function; + uint64_t status; +}; + +/* SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE */ +struct QEMU_PACKED mm_variable_access { + QemuUUID guid; + uint64_t data_size; + uint64_t name_size; + uint32_t attributes; + /* Name */ + /* Data */ +}; + +/* SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME */ +struct mm_next_variable { + QemuUUID guid; + uint64_t name_size; + /* Name */ +}; + +/* SMM_VARIABLE_COMMUNICATE_QUERY_VARIABLE_INFO */ +struct QEMU_PACKED mm_variable_info { + uint64_t max_storage_size; + uint64_t free_storage_size; + uint64_t max_variable_size; + uint32_t attributes; +}; + +/* SMM_VARIABLE_COMMUNICATE_GET_PAYLOAD_SIZE */ +struct mm_get_payload_size { + uint64_t payload_size; +}; + +/* --- VarCheckPolicyLibMmiHandler ----------------------------------- */ + +#define VAR_CHECK_POLICY_COMMAND_DISABLE 0x01 +#define VAR_CHECK_POLICY_COMMAND_IS_ENABLED 0x02 +#define VAR_CHECK_POLICY_COMMAND_REGISTER 0x03 +#define VAR_CHECK_POLICY_COMMAND_DUMP 0x04 +#define VAR_CHECK_POLICY_COMMAND_LOCK 0x05 + +typedef struct mm_check_policy mm_check_policy; +typedef struct mm_check_policy_is_enabled mm_check_policy_is_enabled; +typedef struct mm_check_policy_dump_params mm_check_policy_dump_params; + +/* VAR_CHECK_POLICY_COMM_HEADER */ +struct QEMU_PACKED mm_check_policy { + uint32_t signature; + uint32_t revision; + uint32_t command; + uint64_t result; +}; + +/* VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS */ +struct QEMU_PACKED mm_check_policy_is_enabled { + uint8_t state; +}; + +/* VAR_CHECK_POLICY_COMM_DUMP_PARAMS */ +struct QEMU_PACKED mm_check_policy_dump_params { + uint32_t page_requested; + uint32_t total_size; + uint32_t page_size; + uint8_t has_more; +}; + +/* --- Edk2VariablePolicyProtocol ------------------------------------ */ + +#define VARIABLE_POLICY_ENTRY_REVISION 0x00010000 + +#define VARIABLE_POLICY_TYPE_NO_LOCK 0 +#define VARIABLE_POLICY_TYPE_LOCK_NOW 1 +#define VARIABLE_POLICY_TYPE_LOCK_ON_CREATE 2 +#define VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE 3 + +typedef struct variable_policy_entry variable_policy_entry; +typedef struct variable_lock_on_var_state variable_lock_on_var_state; + +/* VARIABLE_POLICY_ENTRY */ +struct variable_policy_entry { + uint32_t version; + uint16_t size; + uint16_t offset_to_name; + QemuUUID namespace; + uint32_t min_size; + uint32_t max_size; + uint32_t attributes_must_have; + uint32_t attributes_cant_have; + uint8_t lock_policy_type; + uint8_t padding[3]; + /* LockPolicy */ + /* Name */ +}; + +/* VARIABLE_LOCK_ON_VAR_STATE_POLICY */ +struct variable_lock_on_var_state { + QemuUUID namespace; + uint8_t value; + uint8_t padding; + /* Name */ +}; + +/* --- variable authentication --------------------------------------- */ + +#define WIN_CERT_TYPE_EFI_GUID 0x0EF1 + +typedef struct efi_time efi_time; +typedef struct efi_siglist efi_siglist; +typedef struct variable_auth_2 variable_auth_2; + +/* EFI_TIME */ +struct efi_time { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t pad1; + uint32_t nanosecond; + int16_t timezone; + uint8_t daylight; + uint8_t pad2; +}; + +/* EFI_SIGNATURE_LIST */ +struct efi_siglist { + QemuUUID guid_type; + uint32_t siglist_size; + uint32_t header_size; + uint32_t sig_size; +}; + +/* EFI_VARIABLE_AUTHENTICATION_2 */ +struct variable_auth_2 { + struct efi_time timestamp; + + /* WIN_CERTIFICATE_UEFI_GUID */ + uint32_t hdr_length; + uint16_t hdr_revision; + uint16_t hdr_cert_type; + QemuUUID guid_cert_type; + uint8_t cert_data[]; +}; + +#endif /* QEMU_UEFI_VAR_SERVICE_EDK2_H */ From 8b7ed5845cec8d4308707e53469d328b6ad0d9ed Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:08 +0100 Subject: [PATCH 0412/1179] hw/uefi: add include/hw/uefi/var-service.h Add state structs and function declarations for the uefi-vars device. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-5-kraxel@redhat.com> --- include/hw/uefi/var-service.h | 191 ++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 include/hw/uefi/var-service.h diff --git a/include/hw/uefi/var-service.h b/include/hw/uefi/var-service.h new file mode 100644 index 000000000000..f7ceac4ce243 --- /dev/null +++ b/include/hw/uefi/var-service.h @@ -0,0 +1,191 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi-vars device - state struct and function prototypes + */ +#ifndef QEMU_UEFI_VAR_SERVICE_H +#define QEMU_UEFI_VAR_SERVICE_H + +#include "qemu/uuid.h" +#include "qemu/queue.h" + +#include "hw/uefi/var-service-edk2.h" + +#define MAX_BUFFER_SIZE (64 * 1024) + +typedef struct uefi_variable uefi_variable; +typedef struct uefi_var_policy uefi_var_policy; +typedef struct uefi_vars_state uefi_vars_state; + +typedef struct uefi_vars_cert uefi_vars_cert; +typedef struct uefi_vars_hash uefi_vars_hash; +typedef struct uefi_vars_siglist uefi_vars_siglist; + +struct uefi_variable { + QemuUUID guid; + uint16_t *name; + uint32_t name_size; + uint32_t attributes; + void *data; + uint32_t data_size; + efi_time time; + void *digest; + uint32_t digest_size; + QTAILQ_ENTRY(uefi_variable) next; +}; + +struct uefi_var_policy { + variable_policy_entry *entry; + uint32_t entry_size; + uint16_t *name; + uint32_t name_size; + + /* number of hashmarks (wildcard character) in name */ + uint32_t hashmarks; + + QTAILQ_ENTRY(uefi_var_policy) next; +}; + +struct uefi_vars_state { + MemoryRegion mr; + uint16_t sts; + uint32_t buf_size; + uint32_t buf_addr_lo; + uint32_t buf_addr_hi; + uint8_t *buffer; + QTAILQ_HEAD(, uefi_variable) variables; + QTAILQ_HEAD(, uefi_var_policy) var_policies; + + /* pio transfer buffer */ + uint32_t pio_xfer_offset; + uint8_t *pio_xfer_buffer; + + /* boot phases */ + bool end_of_dxe; + bool ready_to_boot; + bool exit_boot_service; + bool policy_locked; + + /* storage accounting */ + uint64_t max_storage; + uint64_t used_storage; + + /* config options */ + char *jsonfile; + int jsonfd; + bool force_secure_boot; + bool disable_custom_mode; + bool use_pio; +}; + +struct uefi_vars_cert { + QTAILQ_ENTRY(uefi_vars_cert) next; + QemuUUID owner; + uint64_t size; + uint8_t data[]; +}; + +struct uefi_vars_hash { + QTAILQ_ENTRY(uefi_vars_hash) next; + QemuUUID owner; + uint8_t data[]; +}; + +struct uefi_vars_siglist { + QTAILQ_HEAD(, uefi_vars_cert) x509; + QTAILQ_HEAD(, uefi_vars_hash) sha256; +}; + +/* vars-service-guid.c */ +extern const QemuUUID EfiGlobalVariable; +extern const QemuUUID EfiImageSecurityDatabase; +extern const QemuUUID EfiCustomModeEnable; +extern const QemuUUID EfiSecureBootEnableDisable; + +extern const QemuUUID EfiCertSha256Guid; +extern const QemuUUID EfiCertSha384Guid; +extern const QemuUUID EfiCertSha512Guid; +extern const QemuUUID EfiCertRsa2048Guid; +extern const QemuUUID EfiCertX509Guid; +extern const QemuUUID EfiCertTypePkcs7Guid; + +extern const QemuUUID EfiSmmVariableProtocolGuid; +extern const QemuUUID VarCheckPolicyLibMmiHandlerGuid; + +extern const QemuUUID EfiEndOfDxeEventGroupGuid; +extern const QemuUUID EfiEventReadyToBootGuid; +extern const QemuUUID EfiEventExitBootServicesGuid; + +/* vars-service-utils.c */ +gboolean uefi_str_is_valid(const uint16_t *str, size_t len, + gboolean must_be_null_terminated); +size_t uefi_strlen(const uint16_t *str, size_t len); +gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen, + gboolean wildcards_in_a); +gboolean uefi_str_equal(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen); +char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size); +int uefi_time_compare(efi_time *a, efi_time *b); +void uefi_trace_variable(const char *action, QemuUUID guid, + const uint16_t *name, uint64_t name_size); +void uefi_trace_status(const char *action, efi_status status); + +/* vars-service-core.c */ +extern const VMStateDescription vmstate_uefi_vars; +void uefi_vars_init(Object *obj, uefi_vars_state *uv); +void uefi_vars_realize(uefi_vars_state *uv, Error **errp); +void uefi_vars_hard_reset(uefi_vars_state *uv); + +/* vars-service-json.c */ +void uefi_vars_json_init(uefi_vars_state *uv, Error **errp); +void uefi_vars_json_save(uefi_vars_state *uv); +void uefi_vars_json_load(uefi_vars_state *uv, Error **errp); + +/* vars-service-vars.c */ +extern const VMStateDescription vmstate_uefi_variable; +uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, + uint64_t name_size); +void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size, + uint32_t attributes, + void *data, uint64_t data_size); +void uefi_vars_clear_volatile(uefi_vars_state *uv); +void uefi_vars_clear_all(uefi_vars_state *uv); +void uefi_vars_update_storage(uefi_vars_state *uv); +uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv); + +/* vars-service-auth.c */ +bool uefi_vars_is_sb_pk(uefi_variable *var); +bool uefi_vars_is_sb_any(uefi_variable *var); +efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var, + mm_variable_access *va, void *data); +efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var); +void uefi_vars_auth_init(uefi_vars_state *uv); + +/* vars-service-pkcs7.c */ +efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist, + void **digest, uint32_t *digest_size, + mm_variable_access *va, void *data); + +/* vars-service-siglist.c */ +void uefi_vars_siglist_init(uefi_vars_siglist *siglist); +void uefi_vars_siglist_free(uefi_vars_siglist *siglist); +void uefi_vars_siglist_parse(uefi_vars_siglist *siglist, + void *data, uint64_t size); +uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist); +void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist, + void *data, uint64_t size); + +/* vars-service-policy.c */ +extern const VMStateDescription vmstate_uefi_var_policy; +efi_status uefi_vars_policy_check(uefi_vars_state *uv, + uefi_variable *var, + gboolean is_newvar); +void uefi_vars_policies_clear(uefi_vars_state *uv); +uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv, + variable_policy_entry *pe); +uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv); + +#endif /* QEMU_UEFI_VAR_SERVICE_H */ From 231b6c9ee8d4cf4ebaae4e4da31ff73c2ec8e6e9 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:09 +0100 Subject: [PATCH 0413/1179] hw/uefi: add var-service-guid.c Add variables for a bunch of UEFI GUIDs we will need. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-6-kraxel@redhat.com> --- hw/uefi/var-service-guid.c | 99 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 hw/uefi/var-service-guid.c diff --git a/hw/uefi/var-service-guid.c b/hw/uefi/var-service-guid.c new file mode 100644 index 000000000000..eba3655c8d30 --- /dev/null +++ b/hw/uefi/var-service-guid.c @@ -0,0 +1,99 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - GUIDs + */ + +#include "qemu/osdep.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +/* variable namespaces */ + +const QemuUUID EfiGlobalVariable = { + .data = UUID_LE(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, + 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) +}; + +const QemuUUID EfiImageSecurityDatabase = { + .data = UUID_LE(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc, + 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f) +}; + +const QemuUUID EfiCustomModeEnable = { + .data = UUID_LE(0xc076ec0c, 0x7028, 0x4399, 0xa0, 0x72, + 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f) +}; + +const QemuUUID EfiSecureBootEnableDisable = { + .data = UUID_LE(0xf0a30bc7, 0xaf08, 0x4556, 0x99, 0xc4, + 0x0, 0x10, 0x9, 0xc9, 0x3a, 0x44) +}; + +/* signatures */ + +const QemuUUID EfiCertSha256Guid = { + .data = UUID_LE(0xc1c41626, 0x504c, 0x4092, 0xac, 0xa9, + 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28) +}; + +const QemuUUID EfiCertSha384Guid = { + .data = UUID_LE(0xff3e5307, 0x9fd0, 0x48c9, 0x85, 0xf1, + 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x1) +}; + +const QemuUUID EfiCertSha512Guid = { + .data = UUID_LE(0x93e0fae, 0xa6c4, 0x4f50, 0x9f, 0x1b, + 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a) +}; + +const QemuUUID EfiCertRsa2048Guid = { + .data = UUID_LE(0x3c5766e8, 0x269c, 0x4e34, 0xaa, 0x14, + 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6) +}; + +const QemuUUID EfiCertX509Guid = { + .data = UUID_LE(0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5, + 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72) +}; + +const QemuUUID EfiCertTypePkcs7Guid = { + .data = UUID_LE(0x4aafd29d, 0x68df, 0x49ee, 0x8a, 0xa9, + 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7) +}; + +/* + * mm_header.guid values that the guest DXE/BDS phases use for + * sending requests to management mode + */ + +const QemuUUID EfiSmmVariableProtocolGuid = { + .data = UUID_LE(0xed32d533, 0x99e6, 0x4209, 0x9c, 0xc0, + 0x2d, 0x72, 0xcd, 0xd9, 0x98, 0xa7) +}; + +const QemuUUID VarCheckPolicyLibMmiHandlerGuid = { + .data = UUID_LE(0xda1b0d11, 0xd1a7, 0x46c4, 0x9d, 0xc9, + 0xf3, 0x71, 0x48, 0x75, 0xc6, 0xeb) +}; + +/* + * mm_header.guid values that the guest DXE/BDS phases use for + * reporting event groups being signaled to management mode + */ + +const QemuUUID EfiEndOfDxeEventGroupGuid = { + .data = UUID_LE(0x02ce967a, 0xdd7e, 0x4FFc, 0x9e, 0xe7, + 0x81, 0x0c, 0xF0, 0x47, 0x08, 0x80) +}; + +const QemuUUID EfiEventReadyToBootGuid = { + .data = UUID_LE(0x7ce88Fb3, 0x4bd7, 0x4679, 0x87, 0xa8, + 0xa8, 0xd8, 0xde, 0xe5, 0x0d, 0x2b) +}; + +const QemuUUID EfiEventExitBootServicesGuid = { + .data = UUID_LE(0x27abF055, 0xb1b8, 0x4c26, 0x80, 0x48, + 0x74, 0x8F, 0x37, 0xba, 0xa2, 0xdF) +}; From 1ebc319c8ca7ad8af350026662ae18fdcb8b0dac Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:10 +0100 Subject: [PATCH 0414/1179] hw/uefi: add var-service-utils.c Add utility functions. Helpers for UEFI (ucs2) string handling. Helpers for readable trace messages. Compare UEFI time stamps. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-7-kraxel@redhat.com> --- hw/uefi/var-service-utils.c | 241 ++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 hw/uefi/var-service-utils.c diff --git a/hw/uefi/var-service-utils.c b/hw/uefi/var-service-utils.c new file mode 100644 index 000000000000..c9ef46570f48 --- /dev/null +++ b/hw/uefi/var-service-utils.c @@ -0,0 +1,241 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - helper functions for ucs2 strings and tracing + */ +#include "qemu/osdep.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +#include "trace/trace-hw_uefi.h" + +/* ------------------------------------------------------------------ */ + +/* + * string helper functions. + * + * Most of the time uefi ucs2 strings are NULL-terminated, except + * sometimes when they are not (for example in variable policies). + */ + +gboolean uefi_str_is_valid(const uint16_t *str, size_t len, + gboolean must_be_null_terminated) +{ + size_t pos = 0; + + for (;;) { + if (pos == len) { + if (must_be_null_terminated) { + return false; + } else { + return true; + } + } + switch (str[pos]) { + case 0: + /* end of string */ + return true; + case 0xd800 ... 0xdfff: + /* reject surrogates */ + return false; + default: + /* char is good, check next */ + break; + } + pos++; + } +} + +size_t uefi_strlen(const uint16_t *str, size_t len) +{ + size_t pos = 0; + + for (;;) { + if (pos == len) { + return pos; + } + if (str[pos] == 0) { + return pos; + } + pos++; + } +} + +gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen, + gboolean wildcards_in_a) +{ + size_t pos = 0; + + alen = alen / 2; + blen = blen / 2; + for (;;) { + if (pos == alen && pos == blen) { + return true; + } + if (pos == alen && b[pos] == 0) { + return true; + } + if (pos == blen && a[pos] == 0) { + return true; + } + if (pos == alen || pos == blen) { + return false; + } + if (a[pos] == 0 && b[pos] == 0) { + return true; + } + + if (wildcards_in_a && a[pos] == '#') { + if (!isxdigit(b[pos])) { + return false; + } + } else { + if (a[pos] != b[pos]) { + return false; + } + } + pos++; + } +} + +gboolean uefi_str_equal(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen) +{ + return uefi_str_equal_ex(a, alen, b, blen, false); +} + +char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size) +{ + char *str = g_malloc0(ucs2_size / 2 + 1); + int i; + + for (i = 0; i * 2 < ucs2_size; i++) { + if (ucs2[i] == 0) { + break; + } + if (ucs2[i] < 128) { + str[i] = ucs2[i]; + } else { + str[i] = '?'; + } + } + str[i] = 0; + return str; +} + +/* ------------------------------------------------------------------ */ +/* time helper functions */ + +int uefi_time_compare(efi_time *a, efi_time *b) +{ + if (a->year < b->year) { + return -1; + } + if (a->year > b->year) { + return 1; + } + + if (a->month < b->month) { + return -1; + } + if (a->month > b->month) { + return 1; + } + + if (a->day < b->day) { + return -1; + } + if (a->day > b->day) { + return 1; + } + + if (a->hour < b->hour) { + return -1; + } + if (a->hour > b->hour) { + return 1; + } + + if (a->minute < b->minute) { + return -1; + } + if (a->minute > b->minute) { + return 1; + } + + if (a->second < b->second) { + return -1; + } + if (a->second > b->second) { + return 1; + } + + if (a->nanosecond < b->nanosecond) { + return -1; + } + if (a->nanosecond > b->nanosecond) { + return 1; + } + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* tracing helper functions */ + +void uefi_trace_variable(const char *action, QemuUUID guid, + const uint16_t *name, uint64_t name_size) +{ + QemuUUID be = qemu_uuid_bswap(guid); + char *str_uuid = qemu_uuid_unparse_strdup(&be); + char *str_name = uefi_ucs2_to_ascii(name, name_size); + + trace_uefi_variable(action, str_name, name_size, str_uuid); + + g_free(str_name); + g_free(str_uuid); +} + +void uefi_trace_status(const char *action, efi_status status) +{ + switch (status) { + case EFI_SUCCESS: + trace_uefi_status(action, "success"); + break; + case EFI_INVALID_PARAMETER: + trace_uefi_status(action, "invalid parameter"); + break; + case EFI_UNSUPPORTED: + trace_uefi_status(action, "unsupported"); + break; + case EFI_BAD_BUFFER_SIZE: + trace_uefi_status(action, "bad buffer size"); + break; + case EFI_BUFFER_TOO_SMALL: + trace_uefi_status(action, "buffer too small"); + break; + case EFI_WRITE_PROTECTED: + trace_uefi_status(action, "write protected"); + break; + case EFI_OUT_OF_RESOURCES: + trace_uefi_status(action, "out of resources"); + break; + case EFI_NOT_FOUND: + trace_uefi_status(action, "not found"); + break; + case EFI_ACCESS_DENIED: + trace_uefi_status(action, "access denied"); + break; + case EFI_ALREADY_STARTED: + trace_uefi_status(action, "already started"); + break; + case EFI_SECURITY_VIOLATION: + trace_uefi_status(action, "security violation"); + break; + default: + trace_uefi_status(action, "unknown error"); + break; + } +} From db1ecfb473ac58f2bd065ca6f2a50c6294ff9169 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:11 +0100 Subject: [PATCH 0415/1179] hw/uefi: add var-service-vars.c This is the uefi variable service (EfiSmmVariableProtocol), providing functions for listing, reading and updating variables. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-8-kraxel@redhat.com> --- hw/uefi/var-service-vars.c | 725 +++++++++++++++++++++++++++++++++++++ 1 file changed, 725 insertions(+) create mode 100644 hw/uefi/var-service-vars.c diff --git a/hw/uefi/var-service-vars.c b/hw/uefi/var-service-vars.c new file mode 100644 index 000000000000..7f98d77a38d1 --- /dev/null +++ b/hw/uefi/var-service-vars.c @@ -0,0 +1,725 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - EfiSmmVariableProtocol implementation + */ +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" +#include "migration/vmstate.h" + +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" +#include "hw/uefi/var-service-edk2.h" + +#include "trace/trace-hw_uefi.h" + +#define EFI_VARIABLE_ATTRIBUTE_SUPPORTED \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS | \ + EFI_VARIABLE_HARDWARE_ERROR_RECORD | \ + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \ + EFI_VARIABLE_APPEND_WRITE) + + +const VMStateDescription vmstate_uefi_time = { + .name = "uefi-time", + .fields = (VMStateField[]) { + VMSTATE_UINT16(year, efi_time), + VMSTATE_UINT8(month, efi_time), + VMSTATE_UINT8(day, efi_time), + VMSTATE_UINT8(hour, efi_time), + VMSTATE_UINT8(minute, efi_time), + VMSTATE_UINT8(second, efi_time), + VMSTATE_UINT32(nanosecond, efi_time), + VMSTATE_END_OF_LIST() + }, +}; + +const VMStateDescription vmstate_uefi_variable = { + .name = "uefi-variable", + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY_V(guid.data, uefi_variable, sizeof(QemuUUID), 0), + VMSTATE_UINT32(name_size, uefi_variable), + VMSTATE_UINT32(data_size, uefi_variable), + VMSTATE_UINT32(attributes, uefi_variable), + VMSTATE_VBUFFER_ALLOC_UINT32(name, uefi_variable, 0, NULL, name_size), + VMSTATE_VBUFFER_ALLOC_UINT32(data, uefi_variable, 0, NULL, data_size), + VMSTATE_STRUCT(time, uefi_variable, 0, vmstate_uefi_time, efi_time), + VMSTATE_END_OF_LIST() + }, +}; + +uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size) +{ + uefi_variable *var; + + QTAILQ_FOREACH(var, &uv->variables, next) { + if (!uefi_str_equal(var->name, var->name_size, + name, name_size)) { + continue; + } + if (!qemu_uuid_is_equal(&var->guid, &guid)) { + continue; + } + if (!var->data_size) { + /* in process of being created/updated */ + continue; + } + return var; + } + return NULL; +} + +static uefi_variable *add_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size, + uint32_t attributes) +{ + uefi_variable *var; + + var = g_new0(uefi_variable, 1); + var->guid = guid; + var->name = g_malloc(name_size); + memcpy(var->name, name, name_size); + var->name_size = name_size; + var->attributes = attributes; + + var->attributes &= ~EFI_VARIABLE_APPEND_WRITE; + + QTAILQ_INSERT_TAIL(&uv->variables, var, next); + return var; +} + +static void del_variable(uefi_vars_state *uv, uefi_variable *var) +{ + if (!var) { + return; + } + + QTAILQ_REMOVE(&uv->variables, var, next); + g_free(var->data); + g_free(var->name); + g_free(var->digest); + g_free(var); +} + +static size_t variable_size(uefi_variable *var) +{ + size_t size; + + size = sizeof(*var); + size += var->name_size; + size += var->data_size; + size += var->digest_size; + return size; +} + +void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size, + uint32_t attributes, + void *data, uint64_t data_size) +{ + uefi_variable *old_var, *new_var; + + uefi_trace_variable(__func__, guid, name, name_size); + + old_var = uefi_vars_find_variable(uv, guid, name, name_size); + if (old_var) { + uv->used_storage -= variable_size(old_var); + del_variable(uv, old_var); + } + + new_var = add_variable(uv, guid, name, name_size, attributes); + new_var->data = g_malloc(data_size); + new_var->data_size = data_size; + memcpy(new_var->data, data, data_size); + uv->used_storage += variable_size(new_var); +} + +void uefi_vars_clear_volatile(uefi_vars_state *uv) +{ + uefi_variable *var, *n; + + QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) { + if (var->attributes & EFI_VARIABLE_NON_VOLATILE) { + continue; + } + uv->used_storage -= variable_size(var); + del_variable(uv, var); + } +} + +void uefi_vars_clear_all(uefi_vars_state *uv) +{ + uefi_variable *var, *n; + + QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) { + del_variable(uv, var); + } + uv->used_storage = 0; +} + +void uefi_vars_update_storage(uefi_vars_state *uv) +{ + uefi_variable *var; + + uv->used_storage = 0; + QTAILQ_FOREACH(var, &uv->variables, next) { + uv->used_storage += variable_size(var); + } +} + +static gboolean check_access(uefi_vars_state *uv, uefi_variable *var) +{ + if (!uv->exit_boot_service) { + if (!(var->attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)) { + return false; + } + } else { + if (!(var->attributes & EFI_VARIABLE_RUNTIME_ACCESS)) { + return false; + } + } + return true; +} + +static efi_status check_update(uefi_vars_state *uv, uefi_variable *old_var, + uefi_variable *new_var) +{ + efi_status status; + + if (old_var) { + if (!check_access(uv, old_var)) { + return EFI_ACCESS_DENIED; + } + } + + if (new_var) { + if (new_var->attributes & ~EFI_VARIABLE_ATTRIBUTE_SUPPORTED) { + return EFI_UNSUPPORTED; + } + if (!check_access(uv, new_var)) { + return EFI_ACCESS_DENIED; + } + } + + if (old_var && new_var) { + if (old_var->attributes != new_var->attributes) { + return EFI_INVALID_PARAMETER; + } + } + + if (new_var) { + /* create + update */ + status = uefi_vars_policy_check(uv, new_var, old_var == NULL); + } else { + /* delete */ + g_assert(old_var); + status = uefi_vars_policy_check(uv, old_var, false); + } + if (status != EFI_SUCCESS) { + return status; + } + + status = uefi_vars_check_secure_boot(uv, new_var ?: old_var); + if (status != EFI_SUCCESS) { + return status; + } + + return EFI_SUCCESS; +} + +static void append_write(uefi_variable *old_var, + uefi_variable *new_var) +{ + uefi_vars_siglist siglist; + uint64_t size; + void *data; + + uefi_vars_siglist_init(&siglist); + uefi_vars_siglist_parse(&siglist, old_var->data, old_var->data_size); + uefi_vars_siglist_parse(&siglist, new_var->data, new_var->data_size); + + size = uefi_vars_siglist_blob_size(&siglist); + data = g_malloc(size); + uefi_vars_siglist_blob_generate(&siglist, data, size); + + g_free(new_var->data); + new_var->data = data; + new_var->data_size = size; + + uefi_vars_siglist_free(&siglist); +} + +static size_t uefi_vars_mm_error(mm_header *mhdr, mm_variable *mvar, + uint64_t status) +{ + mvar->status = status; + return sizeof(*mvar); +} + +static size_t uefi_vars_mm_get_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_variable_access *va = func; + uint16_t *name; + void *data; + uefi_variable *var; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*va); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (va->name_size > uv->max_storage || + va->data_size > uv->max_storage) { + return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); + } + + name = func + sizeof(*va); + if (uadd64_overflow(length, va->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (!uefi_str_is_valid(name, va->name_size, true)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + + uefi_trace_variable(__func__, va->guid, name, va->name_size); + + var = uefi_vars_find_variable(uv, va->guid, name, va->name_size); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); + } + + /* check permissions etc. */ + if (!check_access(uv, var)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_ACCESS_DENIED); + } + + data = func + sizeof(*va) + va->name_size; + if (uadd64_overflow(length, va->data_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + va->attributes = var->attributes; + if (va->data_size < var->data_size) { + va->data_size = var->data_size; + length -= va->data_size; + mvar->status = EFI_BUFFER_TOO_SMALL; + } else { + va->data_size = var->data_size; + memcpy(data, var->data, var->data_size); + mvar->status = EFI_SUCCESS; + } + return length; +} + +static size_t +uefi_vars_mm_get_next_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_next_variable *nv = func; + uefi_variable *var; + uint16_t *name; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*nv); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (nv->name_size > uv->max_storage) { + return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); + } + + name = func + sizeof(*nv); + if (uadd64_overflow(length, nv->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (!uefi_str_is_valid(name, nv->name_size, true)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + + if (uefi_strlen(name, nv->name_size) == 0) { + /* empty string -> first */ + var = QTAILQ_FIRST(&uv->variables); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); + } + } else { + var = uefi_vars_find_variable(uv, nv->guid, name, nv->name_size); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + do { + var = QTAILQ_NEXT(var, next); + } while (var && !check_access(uv, var)); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); + } + } + + length = sizeof(*mvar) + sizeof(*nv) + var->name_size; + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + nv->guid = var->guid; + nv->name_size = var->name_size; + memcpy(name, var->name, var->name_size); + mvar->status = EFI_SUCCESS; + return length; +} + +static bool uefi_vars_mm_digest_compare(uefi_variable *old_var, + uefi_variable *new_var) +{ + if (!old_var->digest || + !new_var->digest || + !old_var->digest_size || + !new_var->digest_size) { + /* should not happen */ + trace_uefi_vars_security_violation("inconsistent authvar digest state"); + return false; + } + if (old_var->digest_size != new_var->digest_size) { + trace_uefi_vars_security_violation("authvar digest size mismatch"); + return false; + } + if (memcmp(old_var->digest, new_var->digest, + old_var->digest_size) != 0) { + trace_uefi_vars_security_violation("authvar digest data mismatch"); + return false; + } + return true; +} + +static size_t uefi_vars_mm_set_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_variable_access *va = func; + uint32_t attributes = 0; + uint16_t *name; + void *data; + uefi_variable *old_var, *new_var; + uint64_t length; + size_t new_storage; + efi_status status; + + length = sizeof(*mvar) + sizeof(*va); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (va->name_size > uv->max_storage || + va->data_size > uv->max_storage) { + return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); + } + + name = func + sizeof(*va); + if (uadd64_overflow(length, va->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + data = func + sizeof(*va) + va->name_size; + if (uadd64_overflow(length, va->data_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + g_assert(va->name_size < G_MAXUINT32); + g_assert(va->data_size < G_MAXUINT32); + + if (!uefi_str_is_valid(name, va->name_size, true)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + + uefi_trace_variable(__func__, va->guid, name, va->name_size); + + old_var = uefi_vars_find_variable(uv, va->guid, name, va->name_size); + if (va->data_size) { + new_var = add_variable(uv, va->guid, name, va->name_size, + va->attributes); + if (va->attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) { + /* not implemented (deprecated in uefi spec) */ + warn_report("%s: AUTHENTICATED_WRITE_ACCESS", __func__); + mvar->status = EFI_UNSUPPORTED; + goto rollback; + } else if (va->attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + status = uefi_vars_check_auth_2(uv, new_var, va, data); + if (status != EFI_SUCCESS) { + mvar->status = status; + goto rollback; + } + if (old_var && new_var) { + if (uefi_time_compare(&old_var->time, &new_var->time) > 0) { + trace_uefi_vars_security_violation("time check failed"); + mvar->status = EFI_SECURITY_VIOLATION; + goto rollback; + } + if (old_var->digest_size || new_var->digest_size) { + if (!uefi_vars_mm_digest_compare(old_var, new_var)) { + mvar->status = EFI_SECURITY_VIOLATION; + goto rollback; + } + } + } + } else { + new_var->data = g_malloc(va->data_size); + memcpy(new_var->data, data, va->data_size); + new_var->data_size = va->data_size; + } + if (!new_var->data) { + /* we land here when deleting authenticated variables */ + del_variable(uv, new_var); + new_var = NULL; + } + } else { + new_var = NULL; + } + + if (!old_var && !new_var) { + /* delete non-existing variable -> nothing to do */ + mvar->status = EFI_SUCCESS; + return sizeof(*mvar); + } + + /* check permissions etc. */ + status = check_update(uv, old_var, new_var); + if (status != EFI_SUCCESS) { + mvar->status = status; + goto rollback; + } + + if (va->attributes & EFI_VARIABLE_APPEND_WRITE && old_var && new_var) { + /* merge signature databases */ + if (!uefi_vars_is_sb_any(new_var)) { + mvar->status = EFI_UNSUPPORTED; + goto rollback; + } + append_write(old_var, new_var); + } + + /* check storage space */ + new_storage = uv->used_storage; + if (old_var) { + new_storage -= variable_size(old_var); + } + if (new_var) { + new_storage += variable_size(new_var); + } + if (new_storage > uv->max_storage) { + mvar->status = EFI_OUT_OF_RESOURCES; + goto rollback; + } + + attributes = new_var + ? new_var->attributes + : old_var->attributes; + + /* all good, commit */ + del_variable(uv, old_var); + uv->used_storage = new_storage; + + if (attributes & EFI_VARIABLE_NON_VOLATILE) { + uefi_vars_json_save(uv); + } + + if (new_var && uefi_vars_is_sb_pk(new_var)) { + uefi_vars_auth_init(uv); + } + + mvar->status = EFI_SUCCESS; + return sizeof(*mvar); + +rollback: + del_variable(uv, new_var); + return sizeof(*mvar); +} + +static size_t uefi_vars_mm_variable_info(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_variable_info *vi = func; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*vi); + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + vi->max_storage_size = uv->max_storage; + vi->free_storage_size = uv->max_storage - uv->used_storage; + vi->max_variable_size = uv->max_storage >> 2; + vi->attributes = 0; + + mvar->status = EFI_SUCCESS; + return length; +} + +static size_t +uefi_vars_mm_get_payload_size(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_get_payload_size *ps = func; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*ps); + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + ps->payload_size = uv->buf_size; + mvar->status = EFI_SUCCESS; + return length; +} + +static size_t +uefi_vars_mm_lock_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_lock_variable *lv = func; + variable_policy_entry *pe; + uint16_t *name, *dest; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*lv); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + name = func + sizeof(*lv); + if (uadd64_overflow(length, lv->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + uefi_trace_variable(__func__, lv->guid, name, lv->name_size); + + pe = g_malloc0(sizeof(*pe) + lv->name_size); + pe->version = VARIABLE_POLICY_ENTRY_REVISION; + pe->size = sizeof(*pe) + lv->name_size; + pe->offset_to_name = sizeof(*pe); + pe->namespace = lv->guid; + pe->min_size = 0; + pe->max_size = UINT32_MAX; + pe->attributes_must_have = 0; + pe->attributes_cant_have = 0; + pe->lock_policy_type = VARIABLE_POLICY_TYPE_LOCK_NOW; + + dest = (void *)pe + pe->offset_to_name; + memcpy(dest, name, lv->name_size); + + uefi_vars_add_policy(uv, pe); + g_free(pe); + + mvar->status = EFI_SUCCESS; + return length; +} + +uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv) +{ + static const char *fnames[] = { + "zero", + "get-variable", + "get-next-variable-name", + "set-variable", + "query-variable-info", + "ready-to-boot", + "exit-boot-service", + "get-statistics", + "lock-variable", + "var-check-prop-set", + "var-check-prop-get", + "get-payload-size", + "init-runtime-cache-contect", + "sync-runtime-cache", + "get-runtime-cache-info", + }; + const char *fname; + uint64_t length; + + mm_header *mhdr = (mm_header *) uv->buffer; + mm_variable *mvar = (mm_variable *) (uv->buffer + sizeof(*mhdr)); + void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mvar)); + + if (mhdr->length < sizeof(*mvar)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + fname = mvar->function < ARRAY_SIZE(fnames) + ? fnames[mvar->function] + : "unknown"; + trace_uefi_vars_proto_cmd(fname); + + switch (mvar->function) { + case SMM_VARIABLE_FUNCTION_GET_VARIABLE: + length = uefi_vars_mm_get_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME: + length = uefi_vars_mm_get_next_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_SET_VARIABLE: + length = uefi_vars_mm_set_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO: + length = uefi_vars_mm_variable_info(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_LOCK_VARIABLE: + length = uefi_vars_mm_lock_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE: + length = uefi_vars_mm_get_payload_size(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_READY_TO_BOOT: + trace_uefi_event("ready-to-boot"); + uv->ready_to_boot = true; + length = 0; + break; + + case SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE: + trace_uefi_event("exit-boot-service"); + uv->exit_boot_service = true; + length = 0; + break; + + default: + length = uefi_vars_mm_error(mhdr, mvar, EFI_UNSUPPORTED); + break; + } + + if (mhdr->length < length) { + mvar->status = EFI_BUFFER_TOO_SMALL; + } + + uefi_trace_status(__func__, mvar->status); + return UEFI_VARS_STS_SUCCESS; +} From f1488fac0584cc095865e4d4d987f01f4e97fbe5 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:12 +0100 Subject: [PATCH 0416/1179] hw/uefi: add var-service-auth.c This implements authenticated variable handling (see AuthVariableLib in edk2). The by far most common use case for auth variables is secure boot. The secure boot certificate databases ('PK', 'KEK', 'db' and 'dbx') are authenticated variables, with update rules being specified in the UEFI specification. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-9-kraxel@redhat.com> --- hw/uefi/var-service-auth.c | 361 +++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 hw/uefi/var-service-auth.c diff --git a/hw/uefi/var-service-auth.c b/hw/uefi/var-service-auth.c new file mode 100644 index 000000000000..fba5a0956a57 --- /dev/null +++ b/hw/uefi/var-service-auth.c @@ -0,0 +1,361 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - AuthVariableLib + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +static const uint16_t name_pk[] = u"PK"; +static const uint16_t name_kek[] = u"KEK"; +static const uint16_t name_db[] = u"db"; +static const uint16_t name_dbx[] = u"dbx"; +static const uint16_t name_setup_mode[] = u"SetupMode"; +static const uint16_t name_sigs_support[] = u"SignatureSupport"; +static const uint16_t name_sb[] = u"SecureBoot"; +static const uint16_t name_sb_enable[] = u"SecureBootEnable"; +static const uint16_t name_custom_mode[] = u"CustomMode"; +static const uint16_t name_vk[] = u"VendorKeys"; +static const uint16_t name_vk_nv[] = u"VendorKeysNv"; + +static const uint32_t sigdb_attrs = + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + +static void set_secure_boot(uefi_vars_state *uv, uint8_t sb) +{ + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_sb, sizeof(name_sb), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + &sb, sizeof(sb)); +} + +static void set_secure_boot_enable(uefi_vars_state *uv, uint8_t sbe) +{ + uefi_vars_set_variable(uv, EfiSecureBootEnableDisable, + name_sb_enable, sizeof(name_sb_enable), + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + &sbe, sizeof(sbe)); +} + +static void set_setup_mode(uefi_vars_state *uv, uint8_t sm) +{ + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_setup_mode, sizeof(name_setup_mode), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + &sm, sizeof(sm)); +} + +static void set_custom_mode(uefi_vars_state *uv, uint8_t cm) +{ + uefi_vars_set_variable(uv, EfiCustomModeEnable, + name_custom_mode, sizeof(name_custom_mode), + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + &cm, sizeof(cm)); +} + +static void set_signature_support(uefi_vars_state *uv) +{ + QemuUUID sigs_support[5]; + + sigs_support[0] = EfiCertSha256Guid; + sigs_support[1] = EfiCertSha384Guid; + sigs_support[2] = EfiCertSha512Guid; + sigs_support[3] = EfiCertRsa2048Guid; + sigs_support[4] = EfiCertX509Guid; + + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_sigs_support, sizeof(name_sigs_support), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sigs_support, sizeof(sigs_support)); +} + +static bool setup_mode_is_active(uefi_vars_state *uv) +{ + uefi_variable *var; + uint8_t *value; + + var = uefi_vars_find_variable(uv, EfiGlobalVariable, + name_setup_mode, sizeof(name_setup_mode)); + if (var) { + value = var->data; + if (value[0] == SETUP_MODE) { + return true; + } + } + return false; +} + +static bool custom_mode_is_active(uefi_vars_state *uv) +{ + uefi_variable *var; + uint8_t *value; + + var = uefi_vars_find_variable(uv, EfiCustomModeEnable, + name_custom_mode, sizeof(name_custom_mode)); + if (var) { + value = var->data; + if (value[0] == CUSTOM_SECURE_BOOT_MODE) { + return true; + } + } + return false; +} + +bool uefi_vars_is_sb_pk(uefi_variable *var) +{ + if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) && + uefi_str_equal(var->name, var->name_size, name_pk, sizeof(name_pk))) { + return true; + } + return false; +} + +static bool uefi_vars_is_sb_kek(uefi_variable *var) +{ + if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) && + uefi_str_equal(var->name, var->name_size, name_kek, sizeof(name_kek))) { + return true; + } + return false; +} + +static bool uefi_vars_is_sb_db(uefi_variable *var) +{ + if (!qemu_uuid_is_equal(&var->guid, &EfiImageSecurityDatabase)) { + return false; + } + if (uefi_str_equal(var->name, var->name_size, name_db, sizeof(name_db))) { + return true; + } + if (uefi_str_equal(var->name, var->name_size, name_dbx, sizeof(name_dbx))) { + return true; + } + return false; +} + +bool uefi_vars_is_sb_any(uefi_variable *var) +{ + if (uefi_vars_is_sb_pk(var) || + uefi_vars_is_sb_kek(var) || + uefi_vars_is_sb_db(var)) { + return true; + } + return false; +} + +static uefi_variable *uefi_vars_find_siglist(uefi_vars_state *uv, + uefi_variable *var) +{ + if (uefi_vars_is_sb_pk(var)) { + return uefi_vars_find_variable(uv, EfiGlobalVariable, + name_pk, sizeof(name_pk)); + } + if (uefi_vars_is_sb_kek(var)) { + return uefi_vars_find_variable(uv, EfiGlobalVariable, + name_pk, sizeof(name_pk)); + } + if (uefi_vars_is_sb_db(var)) { + return uefi_vars_find_variable(uv, EfiGlobalVariable, + name_kek, sizeof(name_kek)); + } + + return NULL; +} + +static efi_status uefi_vars_check_auth_2_sb(uefi_vars_state *uv, + uefi_variable *var, + mm_variable_access *va, + void *data, + uint64_t data_offset) +{ + variable_auth_2 *auth = data; + uefi_variable *siglist; + + if (custom_mode_is_active(uv)) { + /* no authentication in custom mode */ + return EFI_SUCCESS; + } + + if (setup_mode_is_active(uv) && !uefi_vars_is_sb_pk(var)) { + /* no authentication in setup mode (except PK) */ + return EFI_SUCCESS; + } + + if (auth->hdr_length == 24) { + /* no signature (auth->cert_data is empty) */ + return EFI_SECURITY_VIOLATION; + } + + siglist = uefi_vars_find_siglist(uv, var); + if (!siglist && setup_mode_is_active(uv) && uefi_vars_is_sb_pk(var)) { + /* check PK is self-signed */ + uefi_variable tmp = { + .guid = EfiGlobalVariable, + .name = (uint16_t *)name_pk, + .name_size = sizeof(name_pk), + .attributes = sigdb_attrs, + .data = data + data_offset, + .data_size = va->data_size - data_offset, + }; + return uefi_vars_check_pkcs7_2(&tmp, NULL, NULL, va, data); + } + + return uefi_vars_check_pkcs7_2(siglist, NULL, NULL, va, data); +} + +efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var, + mm_variable_access *va, void *data) +{ + variable_auth_2 *auth = data; + uint64_t data_offset; + efi_status status; + + if (va->data_size < sizeof(*auth)) { + return EFI_SECURITY_VIOLATION; + } + if (uadd64_overflow(sizeof(efi_time), auth->hdr_length, &data_offset)) { + return EFI_SECURITY_VIOLATION; + } + if (va->data_size < data_offset) { + return EFI_SECURITY_VIOLATION; + } + + if (auth->hdr_revision != 0x0200 || + auth->hdr_cert_type != WIN_CERT_TYPE_EFI_GUID || + !qemu_uuid_is_equal(&auth->guid_cert_type, &EfiCertTypePkcs7Guid)) { + return EFI_UNSUPPORTED; + } + + if (uefi_vars_is_sb_any(var)) { + /* secure boot variables */ + status = uefi_vars_check_auth_2_sb(uv, var, va, data, data_offset); + if (status != EFI_SUCCESS) { + return status; + } + } else { + /* other authenticated variables */ + status = uefi_vars_check_pkcs7_2(NULL, + &var->digest, &var->digest_size, + va, data); + if (status != EFI_SUCCESS) { + return status; + } + } + + /* checks passed, set variable data */ + var->time = auth->timestamp; + if (va->data_size - data_offset > 0) { + var->data = g_malloc(va->data_size - data_offset); + memcpy(var->data, data + data_offset, va->data_size - data_offset); + var->data_size = va->data_size - data_offset; + } + + return EFI_SUCCESS; +} + +efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var) +{ + uint8_t *value = var->data; + + if (uefi_vars_is_sb_any(var)) { + if (var->attributes != sigdb_attrs) { + return EFI_INVALID_PARAMETER; + } + } + + /* reject SecureBootEnable updates if force_secure_boot is set */ + if (qemu_uuid_is_equal(&var->guid, &EfiSecureBootEnableDisable) && + uefi_str_equal(var->name, var->name_size, + name_sb_enable, sizeof(name_sb_enable)) && + uv->force_secure_boot && + value[0] != SECURE_BOOT_ENABLE) { + return EFI_WRITE_PROTECTED; + } + + /* reject CustomMode updates if disable_custom_mode is set */ + if (qemu_uuid_is_equal(&var->guid, &EfiCustomModeEnable) && + uefi_str_equal(var->name, var->name_size, + name_custom_mode, sizeof(name_custom_mode)) && + uv->disable_custom_mode) { + return EFI_WRITE_PROTECTED; + } + + return EFI_SUCCESS; +} + +/* AuthVariableLibInitialize */ +void uefi_vars_auth_init(uefi_vars_state *uv) +{ + uefi_variable *pk_var, *sbe_var; + uint8_t platform_mode, sb, sbe, vk; + + /* SetupMode */ + pk_var = uefi_vars_find_variable(uv, EfiGlobalVariable, + name_pk, sizeof(name_pk)); + if (!pk_var) { + platform_mode = SETUP_MODE; + } else { + platform_mode = USER_MODE; + } + set_setup_mode(uv, platform_mode); + + /* SignatureSupport */ + set_signature_support(uv); + + /* SecureBootEnable */ + sbe = SECURE_BOOT_DISABLE; + sbe_var = uefi_vars_find_variable(uv, EfiSecureBootEnableDisable, + name_sb_enable, sizeof(name_sb_enable)); + if (sbe_var) { + if (platform_mode == USER_MODE) { + sbe = ((uint8_t *)sbe_var->data)[0]; + } + } else if (platform_mode == USER_MODE) { + sbe = SECURE_BOOT_ENABLE; + set_secure_boot_enable(uv, sbe); + } + + if (uv->force_secure_boot && sbe != SECURE_BOOT_ENABLE) { + sbe = SECURE_BOOT_ENABLE; + set_secure_boot_enable(uv, sbe); + } + + /* SecureBoot */ + if ((sbe == SECURE_BOOT_ENABLE) && (platform_mode == USER_MODE)) { + sb = SECURE_BOOT_MODE_ENABLE; + } else { + sb = SECURE_BOOT_MODE_DISABLE; + } + set_secure_boot(uv, sb); + + /* CustomMode */ + set_custom_mode(uv, STANDARD_SECURE_BOOT_MODE); + + vk = 0; + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_vk_nv, sizeof(name_vk_nv), + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, + &vk, sizeof(vk)); + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_vk, sizeof(name_vk), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + &vk, sizeof(vk)); + + /* flush to disk */ + uefi_vars_json_save(uv); +} From 034cb968ca8fc562ea11de516828228eeb146944 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:13 +0100 Subject: [PATCH 0417/1179] hw/uefi: add var-service-policy.c Implement variable policies (Edk2VariablePolicyProtocol). This EFI protocol allows to define restrictions for variables. It also allows to lock down variables (disallow write access). Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-10-kraxel@redhat.com> --- hw/uefi/var-service-policy.c | 370 +++++++++++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 hw/uefi/var-service-policy.c diff --git a/hw/uefi/var-service-policy.c b/hw/uefi/var-service-policy.c new file mode 100644 index 000000000000..3b1155fe4ea1 --- /dev/null +++ b/hw/uefi/var-service-policy.c @@ -0,0 +1,370 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - VarCheckPolicyLibMmiHandler implementation + * + * variable policy specs: + * https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md + */ +#include "qemu/osdep.h" +#include "system/dma.h" +#include "migration/vmstate.h" + +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" +#include "hw/uefi/var-service-edk2.h" + +#include "trace/trace-hw_uefi.h" + +static void calc_policy(uefi_var_policy *pol); + +static int uefi_var_policy_post_load(void *opaque, int version_id) +{ + uefi_var_policy *pol = opaque; + + calc_policy(pol); + return 0; +} + +const VMStateDescription vmstate_uefi_var_policy = { + .name = "uefi-var-policy", + .post_load = uefi_var_policy_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(entry_size, uefi_var_policy), + VMSTATE_VBUFFER_ALLOC_UINT32(entry, uefi_var_policy, + 0, NULL, entry_size), + VMSTATE_END_OF_LIST() + }, +}; + +static void print_policy_entry(variable_policy_entry *pe) +{ + uint16_t *name = (void *)pe + pe->offset_to_name; + + fprintf(stderr, "%s:\n", __func__); + + fprintf(stderr, " name ´"); + while (*name) { + fprintf(stderr, "%c", *name); + name++; + } + fprintf(stderr, "', version=%d.%d, size=%d\n", + pe->version >> 16, pe->version & 0xffff, pe->size); + + if (pe->min_size) { + fprintf(stderr, " size min=%d\n", pe->min_size); + } + if (pe->max_size != UINT32_MAX) { + fprintf(stderr, " size max=%u\n", pe->max_size); + } + if (pe->attributes_must_have) { + fprintf(stderr, " attr must=0x%x\n", pe->attributes_must_have); + } + if (pe->attributes_cant_have) { + fprintf(stderr, " attr cant=0x%x\n", pe->attributes_cant_have); + } + if (pe->lock_policy_type) { + fprintf(stderr, " lock policy type %d\n", pe->lock_policy_type); + } +} + +static gboolean wildcard_str_equal(uefi_var_policy *pol, + uefi_variable *var) +{ + return uefi_str_equal_ex(pol->name, pol->name_size, + var->name, var->name_size, + true); +} + +static uefi_var_policy *find_policy(uefi_vars_state *uv, QemuUUID guid, + uint16_t *name, uint64_t name_size) +{ + uefi_var_policy *pol; + + QTAILQ_FOREACH(pol, &uv->var_policies, next) { + if (!qemu_uuid_is_equal(&pol->entry->namespace, &guid)) { + continue; + } + if (!uefi_str_equal(pol->name, pol->name_size, + name, name_size)) { + continue; + } + return pol; + } + return NULL; +} + +static uefi_var_policy *wildcard_find_policy(uefi_vars_state *uv, + uefi_variable *var) +{ + uefi_var_policy *pol; + + QTAILQ_FOREACH(pol, &uv->var_policies, next) { + if (!qemu_uuid_is_equal(&pol->entry->namespace, &var->guid)) { + continue; + } + if (!wildcard_str_equal(pol, var)) { + continue; + } + return pol; + } + return NULL; +} + +static void calc_policy(uefi_var_policy *pol) +{ + variable_policy_entry *pe = pol->entry; + unsigned int i; + + pol->name = (void *)pol->entry + pe->offset_to_name; + pol->name_size = pe->size - pe->offset_to_name; + + for (i = 0; i < pol->name_size / 2; i++) { + if (pol->name[i] == '#') { + pol->hashmarks++; + } + } +} + +uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv, + variable_policy_entry *pe) +{ + uefi_var_policy *pol, *p; + + pol = g_new0(uefi_var_policy, 1); + pol->entry = g_malloc(pe->size); + memcpy(pol->entry, pe, pe->size); + pol->entry_size = pe->size; + + calc_policy(pol); + + /* keep list sorted by priority, add to tail of priority group */ + QTAILQ_FOREACH(p, &uv->var_policies, next) { + if ((p->hashmarks > pol->hashmarks) || + (!p->name_size && pol->name_size)) { + QTAILQ_INSERT_BEFORE(p, pol, next); + return pol; + } + } + + QTAILQ_INSERT_TAIL(&uv->var_policies, pol, next); + return pol; +} + +efi_status uefi_vars_policy_check(uefi_vars_state *uv, + uefi_variable *var, + gboolean is_newvar) +{ + uefi_var_policy *pol; + variable_policy_entry *pe; + variable_lock_on_var_state *lvarstate; + uint16_t *lvarname; + size_t lvarnamesize; + uefi_variable *lvar; + + if (!uv->end_of_dxe) { + return EFI_SUCCESS; + } + + pol = wildcard_find_policy(uv, var); + if (!pol) { + return EFI_SUCCESS; + } + pe = pol->entry; + + uefi_trace_variable(__func__, var->guid, var->name, var->name_size); + print_policy_entry(pe); + + if ((var->attributes & pe->attributes_must_have) != pe->attributes_must_have) { + trace_uefi_vars_policy_deny("must-have-attr"); + return EFI_INVALID_PARAMETER; + } + if ((var->attributes & pe->attributes_cant_have) != 0) { + trace_uefi_vars_policy_deny("cant-have-attr"); + return EFI_INVALID_PARAMETER; + } + + if (var->data_size < pe->min_size) { + trace_uefi_vars_policy_deny("min-size"); + return EFI_INVALID_PARAMETER; + } + if (var->data_size > pe->max_size) { + trace_uefi_vars_policy_deny("max-size"); + return EFI_INVALID_PARAMETER; + } + + switch (pe->lock_policy_type) { + case VARIABLE_POLICY_TYPE_NO_LOCK: + break; + + case VARIABLE_POLICY_TYPE_LOCK_NOW: + trace_uefi_vars_policy_deny("lock-now"); + return EFI_WRITE_PROTECTED; + + case VARIABLE_POLICY_TYPE_LOCK_ON_CREATE: + if (!is_newvar) { + trace_uefi_vars_policy_deny("lock-on-create"); + return EFI_WRITE_PROTECTED; + } + break; + + case VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE: + lvarstate = (void *)pol->entry + sizeof(*pe); + lvarname = (void *)pol->entry + sizeof(*pe) + sizeof(*lvarstate); + lvarnamesize = pe->offset_to_name - sizeof(*pe) - sizeof(*lvarstate); + + uefi_trace_variable(__func__, lvarstate->namespace, + lvarname, lvarnamesize); + lvar = uefi_vars_find_variable(uv, lvarstate->namespace, + lvarname, lvarnamesize); + if (lvar && lvar->data_size == 1) { + uint8_t *value = lvar->data; + if (lvarstate->value == *value) { + return EFI_WRITE_PROTECTED; + } + } + break; + } + + return EFI_SUCCESS; +} + +void uefi_vars_policies_clear(uefi_vars_state *uv) +{ + uefi_var_policy *pol; + + while (!QTAILQ_EMPTY(&uv->var_policies)) { + pol = QTAILQ_FIRST(&uv->var_policies); + QTAILQ_REMOVE(&uv->var_policies, pol, next); + g_free(pol->entry); + g_free(pol); + } +} + +static size_t uefi_vars_mm_policy_error(mm_header *mhdr, + mm_check_policy *mchk, + uint64_t status) +{ + mchk->result = status; + return sizeof(*mchk); +} + +static uint32_t uefi_vars_mm_check_policy_is_enabled(uefi_vars_state *uv, + mm_header *mhdr, + mm_check_policy *mchk, + void *func) +{ + mm_check_policy_is_enabled *mpar = func; + size_t length; + + length = sizeof(*mchk) + sizeof(*mpar); + if (mhdr->length < length) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + mpar->state = TRUE; + mchk->result = EFI_SUCCESS; + return sizeof(*mchk); +} + +static uint32_t uefi_vars_mm_check_policy_register(uefi_vars_state *uv, + mm_header *mhdr, + mm_check_policy *mchk, + void *func) +{ + variable_policy_entry *pe = func; + uefi_var_policy *pol; + uint64_t length; + + if (uadd64_overflow(sizeof(*mchk), pe->size, &length)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + if (pe->size < sizeof(*pe)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + if (pe->offset_to_name < sizeof(*pe)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + if (pe->lock_policy_type == VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE && + pe->offset_to_name < sizeof(*pe) + sizeof(variable_lock_on_var_state)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + /* check space for minimum string length */ + if (pe->size < (size_t)pe->offset_to_name) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + if (!uefi_str_is_valid((void *)pe + pe->offset_to_name, + pe->size - pe->offset_to_name, + false)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_INVALID_PARAMETER); + } + + pol = find_policy(uv, pe->namespace, + (void *)pe + pe->offset_to_name, + pe->size - pe->offset_to_name); + if (pol) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_ALREADY_STARTED); + } + + uefi_vars_add_policy(uv, pe); + + mchk->result = EFI_SUCCESS; + return sizeof(*mchk); +} + +uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv) +{ + static const char *fnames[] = { + "zero", + "disable", + "is-enabled", + "register", + "dump", + "lock", + }; + const char *fname; + mm_header *mhdr = (mm_header *) uv->buffer; + mm_check_policy *mchk = (mm_check_policy *) (uv->buffer + sizeof(*mhdr)); + void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mchk)); + + if (mhdr->length < sizeof(*mchk)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + fname = mchk->command < ARRAY_SIZE(fnames) + ? fnames[mchk->command] + : "unknown"; + trace_uefi_vars_policy_cmd(fname); + + switch (mchk->command) { + case VAR_CHECK_POLICY_COMMAND_DISABLE: + mchk->result = EFI_UNSUPPORTED; + break; + case VAR_CHECK_POLICY_COMMAND_IS_ENABLED: + uefi_vars_mm_check_policy_is_enabled(uv, mhdr, mchk, func); + break; + case VAR_CHECK_POLICY_COMMAND_REGISTER: + if (uv->policy_locked) { + mchk->result = EFI_WRITE_PROTECTED; + } else { + uefi_vars_mm_check_policy_register(uv, mhdr, mchk, func); + } + break; + case VAR_CHECK_POLICY_COMMAND_LOCK: + uv->policy_locked = true; + mchk->result = EFI_SUCCESS; + break; + default: + mchk->result = EFI_UNSUPPORTED; + break; + } + + uefi_trace_status(__func__, mchk->result); + return UEFI_VARS_STS_SUCCESS; +} From 90ca4e03c27dc8ac821a2e1686e705ae9a93d301 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:14 +0100 Subject: [PATCH 0418/1179] hw/uefi: add var-service-core.c This is the core code for guest <-> host communication. This accepts request messages from the guest, dispatches them to the service called, and sends back the response message. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-11-kraxel@redhat.com> --- hw/uefi/var-service-core.c | 321 +++++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 hw/uefi/var-service-core.c diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c new file mode 100644 index 000000000000..8ed8378ab991 --- /dev/null +++ b/hw/uefi/var-service-core.c @@ -0,0 +1,321 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device + */ +#include "qemu/osdep.h" +#include "qemu/crc32c.h" +#include "system/dma.h" +#include "migration/vmstate.h" + +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" +#include "hw/uefi/var-service-edk2.h" + +#include "trace/trace-hw_uefi.h" + +static int uefi_vars_pre_load(void *opaque) +{ + uefi_vars_state *uv = opaque; + + uefi_vars_clear_all(uv); + uefi_vars_policies_clear(uv); + g_free(uv->buffer); + return 0; +} + +static int uefi_vars_post_load(void *opaque, int version_id) +{ + uefi_vars_state *uv = opaque; + + uefi_vars_update_storage(uv); + uv->buffer = g_malloc(uv->buf_size); + return 0; +} + +const VMStateDescription vmstate_uefi_vars = { + .name = "uefi-vars", + .pre_load = uefi_vars_pre_load, + .post_load = uefi_vars_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT16(sts, uefi_vars_state), + VMSTATE_UINT32(buf_size, uefi_vars_state), + VMSTATE_UINT32(buf_addr_lo, uefi_vars_state), + VMSTATE_UINT32(buf_addr_hi, uefi_vars_state), + VMSTATE_UINT32(pio_xfer_offset, uefi_vars_state), + VMSTATE_VBUFFER_ALLOC_UINT32(pio_xfer_buffer, uefi_vars_state, + 0, NULL, buf_size), + VMSTATE_BOOL(end_of_dxe, uefi_vars_state), + VMSTATE_BOOL(ready_to_boot, uefi_vars_state), + VMSTATE_BOOL(exit_boot_service, uefi_vars_state), + VMSTATE_BOOL(policy_locked, uefi_vars_state), + VMSTATE_UINT64(used_storage, uefi_vars_state), + VMSTATE_QTAILQ_V(variables, uefi_vars_state, 0, + vmstate_uefi_variable, uefi_variable, next), + VMSTATE_QTAILQ_V(var_policies, uefi_vars_state, 0, + vmstate_uefi_var_policy, uefi_var_policy, next), + VMSTATE_END_OF_LIST() + }, +}; + +static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv, bool dma_mode) +{ + hwaddr dma; + mm_header *mhdr; + uint64_t size; + uint32_t retval; + + dma = uv->buf_addr_lo | ((hwaddr)uv->buf_addr_hi << 32); + mhdr = (mm_header *) uv->buffer; + + if (!uv->buffer || uv->buf_size < sizeof(*mhdr)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + /* read header */ + if (dma_mode) { + dma_memory_read(&address_space_memory, dma, + uv->buffer, sizeof(*mhdr), + MEMTXATTRS_UNSPECIFIED); + } else { + memcpy(uv->buffer, uv->pio_xfer_buffer, sizeof(*mhdr)); + } + + if (uadd64_overflow(sizeof(*mhdr), mhdr->length, &size)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + if (uv->buf_size < size) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + /* read buffer (excl header) */ + if (dma_mode) { + dma_memory_read(&address_space_memory, dma + sizeof(*mhdr), + uv->buffer + sizeof(*mhdr), mhdr->length, + MEMTXATTRS_UNSPECIFIED); + } else { + memcpy(uv->buffer + sizeof(*mhdr), + uv->pio_xfer_buffer + sizeof(*mhdr), + mhdr->length); + } + memset(uv->buffer + size, 0, uv->buf_size - size); + + /* dispatch */ + if (qemu_uuid_is_equal(&mhdr->guid, &EfiSmmVariableProtocolGuid)) { + retval = uefi_vars_mm_vars_proto(uv); + + } else if (qemu_uuid_is_equal(&mhdr->guid, &VarCheckPolicyLibMmiHandlerGuid)) { + retval = uefi_vars_mm_check_policy_proto(uv); + + } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEndOfDxeEventGroupGuid)) { + trace_uefi_event("end-of-dxe"); + uv->end_of_dxe = true; + retval = UEFI_VARS_STS_SUCCESS; + + } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventReadyToBootGuid)) { + trace_uefi_event("ready-to-boot"); + uv->ready_to_boot = true; + retval = UEFI_VARS_STS_SUCCESS; + + } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventExitBootServicesGuid)) { + trace_uefi_event("exit-boot-service"); + uv->exit_boot_service = true; + retval = UEFI_VARS_STS_SUCCESS; + + } else { + retval = UEFI_VARS_STS_ERR_NOT_SUPPORTED; + } + + /* write buffer */ + if (dma_mode) { + dma_memory_write(&address_space_memory, dma, + uv->buffer, sizeof(*mhdr) + mhdr->length, + MEMTXATTRS_UNSPECIFIED); + } else { + memcpy(uv->pio_xfer_buffer + sizeof(*mhdr), + uv->buffer + sizeof(*mhdr), + sizeof(*mhdr) + mhdr->length); + } + + return retval; +} + +static void uefi_vars_soft_reset(uefi_vars_state *uv) +{ + g_free(uv->buffer); + uv->buffer = NULL; + uv->buf_size = 0; + uv->buf_addr_lo = 0; + uv->buf_addr_hi = 0; +} + +void uefi_vars_hard_reset(uefi_vars_state *uv) +{ + trace_uefi_hard_reset(); + uefi_vars_soft_reset(uv); + + uv->end_of_dxe = false; + uv->ready_to_boot = false; + uv->exit_boot_service = false; + uv->policy_locked = false; + + uefi_vars_clear_volatile(uv); + uefi_vars_policies_clear(uv); + uefi_vars_auth_init(uv); +} + +static uint32_t uefi_vars_cmd(uefi_vars_state *uv, uint32_t cmd) +{ + switch (cmd) { + case UEFI_VARS_CMD_RESET: + uefi_vars_soft_reset(uv); + return UEFI_VARS_STS_SUCCESS; + case UEFI_VARS_CMD_DMA_MM: + return uefi_vars_cmd_mm(uv, true); + case UEFI_VARS_CMD_PIO_MM: + return uefi_vars_cmd_mm(uv, false); + case UEFI_VARS_CMD_PIO_ZERO_OFFSET: + uv->pio_xfer_offset = 0; + return UEFI_VARS_STS_SUCCESS; + default: + return UEFI_VARS_STS_ERR_NOT_SUPPORTED; + } +} + +static uint64_t uefi_vars_read(void *opaque, hwaddr addr, unsigned size) +{ + uefi_vars_state *uv = opaque; + uint64_t retval = -1; + void *xfer_ptr; + + trace_uefi_reg_read(addr, size); + + switch (addr) { + case UEFI_VARS_REG_MAGIC: + retval = UEFI_VARS_MAGIC_VALUE; + break; + case UEFI_VARS_REG_CMD_STS: + retval = uv->sts; + break; + case UEFI_VARS_REG_BUFFER_SIZE: + retval = uv->buf_size; + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO: + retval = uv->buf_addr_lo; + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI: + retval = uv->buf_addr_hi; + break; + case UEFI_VARS_REG_PIO_BUFFER_TRANSFER: + if (uv->pio_xfer_offset + size > uv->buf_size) { + retval = 0; + break; + } + xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset; + switch (size) { + case 1: + retval = *(uint8_t *)xfer_ptr; + break; + case 2: + retval = *(uint16_t *)xfer_ptr; + break; + case 4: + retval = *(uint32_t *)xfer_ptr; + break; + case 8: + retval = *(uint64_t *)xfer_ptr; + break; + } + uv->pio_xfer_offset += size; + break; + case UEFI_VARS_REG_PIO_BUFFER_CRC32C: + retval = crc32c(0xffffffff, uv->pio_xfer_buffer, uv->pio_xfer_offset); + break; + case UEFI_VARS_REG_FLAGS: + retval = 0; + if (uv->use_pio) { + retval |= UEFI_VARS_FLAG_USE_PIO; + } + } + return retval; +} + +static void uefi_vars_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) +{ + uefi_vars_state *uv = opaque; + void *xfer_ptr; + + trace_uefi_reg_write(addr, val, size); + + switch (addr) { + case UEFI_VARS_REG_CMD_STS: + uv->sts = uefi_vars_cmd(uv, val); + break; + case UEFI_VARS_REG_BUFFER_SIZE: + if (val > MAX_BUFFER_SIZE) { + val = MAX_BUFFER_SIZE; + } + uv->buf_size = val; + g_free(uv->buffer); + g_free(uv->pio_xfer_buffer); + uv->buffer = g_malloc(uv->buf_size); + uv->pio_xfer_buffer = g_malloc(uv->buf_size); + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO: + uv->buf_addr_lo = val; + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI: + uv->buf_addr_hi = val; + break; + case UEFI_VARS_REG_PIO_BUFFER_TRANSFER: + if (uv->pio_xfer_offset + size > uv->buf_size) { + break; + } + xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset; + switch (size) { + case 1: + *(uint8_t *)xfer_ptr = val; + break; + case 2: + *(uint16_t *)xfer_ptr = val; + break; + case 4: + *(uint32_t *)xfer_ptr = val; + break; + case 8: + *(uint64_t *)xfer_ptr = val; + break; + } + uv->pio_xfer_offset += size; + break; + case UEFI_VARS_REG_PIO_BUFFER_CRC32C: + case UEFI_VARS_REG_FLAGS: + default: + break; + } +} + +static const MemoryRegionOps uefi_vars_ops = { + .read = uefi_vars_read, + .write = uefi_vars_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 2, + .max_access_size = 4, + }, +}; + +void uefi_vars_init(Object *obj, uefi_vars_state *uv) +{ + QTAILQ_INIT(&uv->variables); + QTAILQ_INIT(&uv->var_policies); + uv->jsonfd = -1; + memory_region_init_io(&uv->mr, obj, &uefi_vars_ops, uv, + "uefi-vars", UEFI_VARS_REGS_SIZE); +} + +void uefi_vars_realize(uefi_vars_state *uv, Error **errp) +{ + uefi_vars_json_init(uv, errp); + uefi_vars_json_load(uv, errp); +} From 3e33af2cb306311d6fa4372c6d27489c165c1bd4 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:15 +0100 Subject: [PATCH 0419/1179] hw/uefi: add var-service-pkcs7.c This implements pkcs7 signature verification using gnutls. Needed to check authenticated variable updates. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-12-kraxel@redhat.com> --- hw/uefi/var-service-pkcs7.c | 436 ++++++++++++++++++++++++++++++++++++ 1 file changed, 436 insertions(+) create mode 100644 hw/uefi/var-service-pkcs7.c diff --git a/hw/uefi/var-service-pkcs7.c b/hw/uefi/var-service-pkcs7.c new file mode 100644 index 000000000000..32accf4e44e0 --- /dev/null +++ b/hw/uefi/var-service-pkcs7.c @@ -0,0 +1,436 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - pkcs7 verification + */ +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include +#include +#include + +#include "hw/uefi/var-service.h" + +#define AUTHVAR_DIGEST_ALGO GNUTLS_DIG_SHA256 +#define AUTHVAR_DIGEST_SIZE 32 + +/* + * Replicate the signed data for signature verification. + */ +static gnutls_datum_t *build_signed_data(mm_variable_access *va, void *data) +{ + variable_auth_2 *auth = data; + uint64_t data_offset = sizeof(efi_time) + auth->hdr_length; + uint16_t *name = (void *)va + sizeof(mm_variable_access); + gnutls_datum_t *sdata; + uint64_t pos = 0; + + sdata = g_new(gnutls_datum_t, 1); + sdata->size = (va->name_size - 2 + + sizeof(QemuUUID) + + sizeof(va->attributes) + + sizeof(auth->timestamp) + + va->data_size - data_offset); + sdata->data = g_malloc(sdata->size); + + /* Variable Name (without terminating \0) */ + memcpy(sdata->data + pos, name, va->name_size - 2); + pos += va->name_size - 2; + + /* Variable Namespace Guid */ + memcpy(sdata->data + pos, &va->guid, sizeof(va->guid)); + pos += sizeof(va->guid); + + /* Attributes */ + memcpy(sdata->data + pos, &va->attributes, sizeof(va->attributes)); + pos += sizeof(va->attributes); + + /* TimeStamp */ + memcpy(sdata->data + pos, &auth->timestamp, sizeof(auth->timestamp)); + pos += sizeof(auth->timestamp); + + /* Variable Content */ + memcpy(sdata->data + pos, data + data_offset, va->data_size - data_offset); + pos += va->data_size - data_offset; + + assert(pos == sdata->size); + return sdata; +} + +/* + * See WrapPkcs7Data() in edk2. + * + * UEFI spec allows pkcs7 signatures being used without the envelope which + * identifies them as pkcs7 signatures. openssl and gnutls will not parse them + * without the envelope though. So add it if needed. + */ +static void wrap_pkcs7(gnutls_datum_t *pkcs7) +{ + static uint8_t signed_data_oid[9] = { + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 + }; + gnutls_datum_t wrap; + + if (pkcs7->data[4] == 0x06 && + pkcs7->data[5] == 0x09 && + memcmp(pkcs7->data + 6, signed_data_oid, sizeof(signed_data_oid)) == 0 && + pkcs7->data[15] == 0x0a && + pkcs7->data[16] == 0x82) { + return; + } + + wrap.size = pkcs7->size + 19; + wrap.data = g_malloc(wrap.size); + + wrap.data[0] = 0x30; + wrap.data[1] = 0x82; + wrap.data[2] = (wrap.size - 4) >> 8; + wrap.data[3] = (wrap.size - 4) & 0xff; + wrap.data[4] = 0x06; + wrap.data[5] = 0x09; + memcpy(wrap.data + 6, signed_data_oid, sizeof(signed_data_oid)); + + wrap.data[15] = 0xa0; + wrap.data[16] = 0x82; + wrap.data[17] = pkcs7->size >> 8; + wrap.data[18] = pkcs7->size & 0xff; + memcpy(wrap.data + 19, pkcs7->data, pkcs7->size); + + g_free(pkcs7->data); + *pkcs7 = wrap; +} + +static gnutls_datum_t *build_pkcs7(void *data) +{ + variable_auth_2 *auth = data; + gnutls_datum_t *pkcs7; + + pkcs7 = g_new(gnutls_datum_t, 1); + pkcs7->size = auth->hdr_length - 24; + pkcs7->data = g_malloc(pkcs7->size); + memcpy(pkcs7->data, data + 16 + 24, pkcs7->size); + + wrap_pkcs7(pkcs7); + + return pkcs7; +} + +/* + * Read UEFI signature database, store x509 all certificates found in + * gnutls_x509_trust_list_t. + */ +static gnutls_x509_trust_list_t build_trust_list_sb(uefi_variable *var) +{ + gnutls_x509_trust_list_t tlist; + gnutls_datum_t cert_data; + gnutls_x509_crt_t cert; + uefi_vars_siglist siglist; + uefi_vars_cert *c; + int rc; + + rc = gnutls_x509_trust_list_init(&tlist, 0); + if (rc < 0) { + warn_report("gnutls_x509_trust_list_init error: %s", + gnutls_strerror(rc)); + return NULL; + } + + uefi_vars_siglist_init(&siglist); + uefi_vars_siglist_parse(&siglist, var->data, var->data_size); + + QTAILQ_FOREACH(c, &siglist.x509, next) { + cert_data.size = c->size; + cert_data.data = c->data; + + rc = gnutls_x509_crt_init(&cert); + if (rc < 0) { + warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc)); + break; + } + rc = gnutls_x509_crt_import(cert, &cert_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(cert); + break; + } + rc = gnutls_x509_trust_list_add_cas(tlist, &cert, 1, 0); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(cert); + break; + } + } + + uefi_vars_siglist_free(&siglist); + + return tlist; +} + +static int build_digest_authvar(gnutls_x509_crt_t signer, + gnutls_x509_crt_t root, + uint8_t *hash_digest) +{ + char *cn; + size_t cn_size = 0; + uint8_t fp[AUTHVAR_DIGEST_SIZE]; + size_t fp_size = sizeof(fp); + gnutls_hash_hd_t hash; + int rc; + + /* get signer CN */ + rc = gnutls_x509_crt_get_dn_by_oid(signer, GNUTLS_OID_X520_COMMON_NAME, + 0, 0, NULL, &cn_size); + if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) { + warn_report("gnutls_x509_crt_get_dn_by_oid error #1: %s", + gnutls_strerror(rc)); + return rc; + } + + cn = g_malloc(cn_size); + rc = gnutls_x509_crt_get_dn_by_oid(signer, GNUTLS_OID_X520_COMMON_NAME, + 0, 0, cn, &cn_size); + if (rc < 0) { + warn_report("gnutls_x509_crt_get_dn_by_oid error #2: %s", + gnutls_strerror(rc)); + goto err; + } + + /* get root certificate fingerprint */ + rc = gnutls_x509_crt_get_fingerprint(root, AUTHVAR_DIGEST_ALGO, + fp, &fp_size); + if (rc < 0) { + warn_report("gnutls_x509_crt_get_fingerprint error: %s", + gnutls_strerror(rc)); + goto err; + } + + /* digest both items */ + rc = gnutls_hash_init(&hash, AUTHVAR_DIGEST_ALGO); + if (rc < 0) { + warn_report("gnutls_hash_init error: %s", + gnutls_strerror(rc)); + goto err; + } + rc = gnutls_hash(hash, cn, cn_size); + if (rc < 0) { + warn_report("gnutls_hash error: %s", + gnutls_strerror(rc)); + goto err; + } + rc = gnutls_hash(hash, fp, fp_size); + if (rc < 0) { + warn_report("gnutls_hash error: %s", + gnutls_strerror(rc)); + goto err; + } + gnutls_hash_deinit(hash, hash_digest); + + return 0; + +err: + g_free(cn); + return rc; +} + +/* + * uefi spec 2.9, section 8.2.2 + * + * For EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS variables which are + * NOT secure boot variables we should track the root certificate of the trust + * chain, and the subject CN of the signer certificate. + * + * So we'll go store a digest of these two items so we can verify this. Also + * create a gnutls_x509_trust_list_t with the root certificate, so + * gnutls_pkcs7_verify() will pass (assuming the signature is otherwise + * correct). + */ +static gnutls_x509_trust_list_t build_trust_list_authvar(gnutls_pkcs7_t pkcs7, + uint8_t *hash_digest) +{ + gnutls_datum_t signer_data = { 0 }; + gnutls_datum_t root_data = { 0 }; + gnutls_x509_crt_t signer = NULL; + gnutls_x509_crt_t root = NULL; + gnutls_x509_trust_list_t tlist = NULL; + int n, rc; + + n = gnutls_pkcs7_get_crt_count(pkcs7); + + /* first is signer certificate */ + rc = gnutls_pkcs7_get_crt_raw2(pkcs7, 0, &signer_data); + if (rc < 0) { + warn_report("gnutls_pkcs7_get_crt_raw2(0) error: %s", + gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_init(&signer); + if (rc < 0) { + warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_import(signer, &signer_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(signer); + goto done; + } + + /* last is root-of-trust certificate (can be identical to signer) */ + rc = gnutls_pkcs7_get_crt_raw2(pkcs7, n - 1, &root_data); + if (rc < 0) { + warn_report("gnutls_pkcs7_get_crt_raw2(%d) error: %s", + n - 1, gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_init(&root); + if (rc < 0) { + warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_import(root, &root_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + goto done; + } + + /* calc digest for signer CN + root cert */ + rc = build_digest_authvar(signer, root, hash_digest); + if (rc < 0) { + goto done; + } + + /* add root to trust list */ + rc = gnutls_x509_trust_list_init(&tlist, 0); + if (rc < 0) { + warn_report("gnutls_x509_trust_list_init error: %s", + gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_trust_list_add_cas(tlist, &root, 1, 0); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_trust_list_deinit(tlist, 1); + tlist = NULL; + goto done; + } else { + /* ownership passed to tlist */ + root = NULL; + } + +done: + if (signer_data.data) { + gnutls_free(signer_data.data); + } + if (root_data.data) { + gnutls_free(root_data.data); + } + if (signer) { + gnutls_x509_crt_deinit(signer); + } + if (root) { + gnutls_x509_crt_deinit(root); + } + return tlist; +} + +static void free_datum(gnutls_datum_t *ptr) +{ + if (!ptr) { + return; + } + g_free(ptr->data); + g_free(ptr); +} + +static void gnutls_log_stderr(int level, const char *msg) +{ + if (strncmp(msg, "ASSERT:", 7) == 0) { + return; + } + fprintf(stderr, " %d: %s", level, msg); +} + +/* + * pkcs7 signature verification (EFI_VARIABLE_AUTHENTICATION_2). + */ +efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist, + void **digest, uint32_t *digest_size, + mm_variable_access *va, void *data) +{ + gnutls_x509_trust_list_t tlist = NULL; + gnutls_datum_t *signed_data = NULL; + gnutls_datum_t *pkcs7_data = NULL; + gnutls_pkcs7_t pkcs7 = NULL; + efi_status status = EFI_SECURITY_VIOLATION; + int rc; + + if (0) { + /* gnutls debug logging */ + static bool first = true; + + if (first) { + first = false; + gnutls_global_set_log_function(gnutls_log_stderr); + gnutls_global_set_log_level(99); + } + } + + signed_data = build_signed_data(va, data); + pkcs7_data = build_pkcs7(data); + + rc = gnutls_pkcs7_init(&pkcs7); + if (rc < 0) { + warn_report("gnutls_pkcs7_init error: %s", gnutls_strerror(rc)); + goto out; + } + + rc = gnutls_pkcs7_import(pkcs7, pkcs7_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_pkcs7_import error: %s", gnutls_strerror(rc)); + goto out; + } + + if (siglist) { + /* secure boot variables */ + tlist = build_trust_list_sb(siglist); + } else if (digest && digest_size) { + /* other authenticated variables */ + *digest_size = AUTHVAR_DIGEST_SIZE; + *digest = g_malloc(*digest_size); + tlist = build_trust_list_authvar(pkcs7, *digest); + } else { + /* should not happen */ + goto out; + } + + rc = gnutls_pkcs7_verify(pkcs7, tlist, + NULL, 0, + 0, signed_data, + GNUTLS_VERIFY_DISABLE_TIME_CHECKS | + GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS); + if (rc < 0) { + warn_report("gnutls_pkcs7_verify error: %s", gnutls_strerror(rc)); + goto out; + } + + /* check passed */ + status = EFI_SUCCESS; + +out: + free_datum(signed_data); + free_datum(pkcs7_data); + if (tlist) { + gnutls_x509_trust_list_deinit(tlist, 1); + } + if (pkcs7) { + gnutls_pkcs7_deinit(pkcs7); + } + return status; +} From 4ec89b00d5bd4184455cf41af859ec08ed87d8e5 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:16 +0100 Subject: [PATCH 0420/1179] hw/uefi: add var-service-pkcs7-stub.c pkcs7 stub which is used in case gnutls is not available. It throws EFI_WRITE_PROTECTED errors unconditionally, so all authenticated variables are readonly for the guest. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-13-kraxel@redhat.com> --- hw/uefi/var-service-pkcs7-stub.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 hw/uefi/var-service-pkcs7-stub.c diff --git a/hw/uefi/var-service-pkcs7-stub.c b/hw/uefi/var-service-pkcs7-stub.c new file mode 100644 index 000000000000..118cba446d4b --- /dev/null +++ b/hw/uefi/var-service-pkcs7-stub.c @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - pkcs7 stubs + */ +#include "qemu/osdep.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist, + void **digest, uint32_t *digest_size, + mm_variable_access *va, void *data) +{ + return EFI_WRITE_PROTECTED; +} From f903e88306adfcce3b80a512a222c8551718d719 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:17 +0100 Subject: [PATCH 0421/1179] hw/uefi: add var-service-siglist.c Functions to serialize and de-serialize EFI signature databases. This is needed to merge signature databases (happens in practice when appending dbx updates) and also to extract the certificates for pkcs7 signature verification. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-14-kraxel@redhat.com> --- hw/uefi/var-service-siglist.c | 212 ++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 hw/uefi/var-service-siglist.c diff --git a/hw/uefi/var-service-siglist.c b/hw/uefi/var-service-siglist.c new file mode 100644 index 000000000000..8948f1b78471 --- /dev/null +++ b/hw/uefi/var-service-siglist.c @@ -0,0 +1,212 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - parse and generate efi signature databases + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +/* + * Add x509 certificate to list (with duplicate check). + */ +static void uefi_vars_siglist_add_x509(uefi_vars_siglist *siglist, + QemuUUID *owner, + void *data, uint64_t size) +{ + uefi_vars_cert *c; + + QTAILQ_FOREACH(c, &siglist->x509, next) { + if (c->size != size) { + continue; + } + if (memcmp(c->data, data, size) != 0) { + continue; + } + return; + } + + c = g_malloc(sizeof(*c) + size); + c->owner = *owner; + c->size = size; + memcpy(c->data, data, size); + QTAILQ_INSERT_TAIL(&siglist->x509, c, next); +} + +/* + * Add sha256 hash to list (with duplicate check). + */ +static void uefi_vars_siglist_add_sha256(uefi_vars_siglist *siglist, + QemuUUID *owner, + void *data) +{ + uefi_vars_hash *h; + + QTAILQ_FOREACH(h, &siglist->sha256, next) { + if (memcmp(h->data, data, 32) != 0) { + continue; + } + return; + } + + h = g_malloc(sizeof(*h) + 32); + h->owner = *owner; + memcpy(h->data, data, 32); + QTAILQ_INSERT_TAIL(&siglist->sha256, h, next); +} + +void uefi_vars_siglist_init(uefi_vars_siglist *siglist) +{ + memset(siglist, 0, sizeof(*siglist)); + QTAILQ_INIT(&siglist->x509); + QTAILQ_INIT(&siglist->sha256); +} + +void uefi_vars_siglist_free(uefi_vars_siglist *siglist) +{ + uefi_vars_cert *c, *cs; + uefi_vars_hash *h, *hs; + + QTAILQ_FOREACH_SAFE(c, &siglist->x509, next, cs) { + QTAILQ_REMOVE(&siglist->x509, c, next); + g_free(c); + } + QTAILQ_FOREACH_SAFE(h, &siglist->sha256, next, hs) { + QTAILQ_REMOVE(&siglist->sha256, h, next); + g_free(h); + } +} + +/* + * Parse UEFI signature list. + */ +void uefi_vars_siglist_parse(uefi_vars_siglist *siglist, + void *data, uint64_t size) +{ + efi_siglist *efilist; + uint64_t start; + + while (size) { + if (size < sizeof(*efilist)) { + break; + } + efilist = data; + if (size < efilist->siglist_size) { + break; + } + + if (uadd64_overflow(sizeof(*efilist), efilist->header_size, &start)) { + break; + } + if (efilist->sig_size <= sizeof(QemuUUID)) { + break; + } + + if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertX509Guid)) { + if (start + efilist->sig_size != efilist->siglist_size) { + break; + } + uefi_vars_siglist_add_x509(siglist, + (QemuUUID *)(data + start), + data + start + sizeof(QemuUUID), + efilist->sig_size - sizeof(QemuUUID)); + + } else if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertSha256Guid)) { + if (efilist->sig_size != sizeof(QemuUUID) + 32) { + break; + } + if (start + efilist->sig_size > efilist->siglist_size) { + break; + } + while (start <= efilist->siglist_size - efilist->sig_size) { + uefi_vars_siglist_add_sha256(siglist, + (QemuUUID *)(data + start), + data + start + sizeof(QemuUUID)); + start += efilist->sig_size; + } + + } else { + QemuUUID be = qemu_uuid_bswap(efilist->guid_type); + char *str_uuid = qemu_uuid_unparse_strdup(&be); + warn_report("%s: unknown type (%s)", __func__, str_uuid); + g_free(str_uuid); + } + + data += efilist->siglist_size; + size -= efilist->siglist_size; + } +} + +uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist) +{ + uefi_vars_cert *c; + uefi_vars_hash *h; + uint64_t size = 0; + + QTAILQ_FOREACH(c, &siglist->x509, next) { + size += sizeof(efi_siglist) + sizeof(QemuUUID) + c->size; + } + + if (!QTAILQ_EMPTY(&siglist->sha256)) { + size += sizeof(efi_siglist); + QTAILQ_FOREACH(h, &siglist->sha256, next) { + size += sizeof(QemuUUID) + 32; + } + } + + return size; +} + +/* + * Generate UEFI signature list. + */ +void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist, + void *data, uint64_t size) +{ + uefi_vars_cert *c; + uefi_vars_hash *h; + efi_siglist *efilist; + uint64_t pos = 0, start; + uint32_t i; + + QTAILQ_FOREACH(c, &siglist->x509, next) { + efilist = data + pos; + efilist->guid_type = EfiCertX509Guid; + efilist->sig_size = sizeof(QemuUUID) + c->size; + efilist->header_size = 0; + + start = pos + sizeof(efi_siglist); + memcpy(data + start, + &c->owner, sizeof(QemuUUID)); + memcpy(data + start + sizeof(QemuUUID), + c->data, c->size); + + efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size; + pos += efilist->siglist_size; + } + + if (!QTAILQ_EMPTY(&siglist->sha256)) { + efilist = data + pos; + efilist->guid_type = EfiCertSha256Guid; + efilist->sig_size = sizeof(QemuUUID) + 32; + efilist->header_size = 0; + + i = 0; + start = pos + sizeof(efi_siglist); + QTAILQ_FOREACH(h, &siglist->sha256, next) { + memcpy(data + start + efilist->sig_size * i, + &h->owner, sizeof(QemuUUID)); + memcpy(data + start + efilist->sig_size * i + sizeof(QemuUUID), + h->data, 32); + i++; + } + + efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size * i; + pos += efilist->siglist_size; + } + + assert(pos == size); +} From 12058948abdf7eed8364aee79add66b40002fd5b Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:18 +0100 Subject: [PATCH 0422/1179] hw/uefi: add var-service-json.c + qapi for NV vars. Define qapi schema for the uefi variable store state. Use it and the generated visitor helper functions to store persistent (EFI_VARIABLE_NON_VOLATILE) variables in JSON format on disk. Acked-by: Markus Armbruster Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-15-kraxel@redhat.com> [ incremental fix squashed in ] Message-ID: --- hw/uefi/var-service-json.c | 243 +++++++++++++++++++++++++++++++++++++ qapi/meson.build | 1 + qapi/qapi-schema.json | 1 + qapi/uefi.json | 64 ++++++++++ 4 files changed, 309 insertions(+) create mode 100644 hw/uefi/var-service-json.c create mode 100644 qapi/uefi.json diff --git a/hw/uefi/var-service-json.c b/hw/uefi/var-service-json.c new file mode 100644 index 000000000000..761082c11fc1 --- /dev/null +++ b/hw/uefi/var-service-json.c @@ -0,0 +1,243 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - serialize non-volatile varstore from/to json, + * using qapi + * + * tools which can read/write these json files: + * - https://gitlab.com/kraxel/virt-firmware + * - https://github.com/awslabs/python-uefivars + */ +#include "qemu/osdep.h" +#include "qemu/cutils.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +#include "qobject/qobject.h" +#include "qobject/qjson.h" + +#include "qapi/dealloc-visitor.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qobject-output-visitor.h" +#include "qapi/qapi-types-uefi.h" +#include "qapi/qapi-visit-uefi.h" + +static char *generate_hexstr(void *data, size_t len) +{ + static const char hex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + }; + uint8_t *src = data; + char *dest; + size_t i; + + dest = g_malloc(len * 2 + 1); + for (i = 0; i < len * 2;) { + dest[i++] = hex[*src >> 4]; + dest[i++] = hex[*src & 15]; + src++; + } + dest[i++] = 0; + + return dest; +} + +static UefiVarStore *uefi_vars_to_qapi(uefi_vars_state *uv) +{ + UefiVarStore *vs; + UefiVariableList **tail; + UefiVariable *v; + QemuUUID be; + uefi_variable *var; + + vs = g_new0(UefiVarStore, 1); + vs->version = 2; + tail = &vs->variables; + + QTAILQ_FOREACH(var, &uv->variables, next) { + if (!(var->attributes & EFI_VARIABLE_NON_VOLATILE)) { + continue; + } + + v = g_new0(UefiVariable, 1); + be = qemu_uuid_bswap(var->guid); + v->guid = qemu_uuid_unparse_strdup(&be); + v->name = uefi_ucs2_to_ascii(var->name, var->name_size); + v->attr = var->attributes; + + v->data = generate_hexstr(var->data, var->data_size); + + if (var->attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + v->time = generate_hexstr(&var->time, sizeof(var->time)); + if (var->digest && var->digest_size) { + v->digest = generate_hexstr(var->digest, var->digest_size); + } + } + + QAPI_LIST_APPEND(tail, v); + } + return vs; +} + +static unsigned parse_hexchar(char c) +{ + switch (c) { + case '0' ... '9': return c - '0'; + case 'a' ... 'f': return c - 'a' + 0xa; + case 'A' ... 'F': return c - 'A' + 0xA; + default: return 0; + } +} + +static void parse_hexstr(void *dest, char *src, int len) +{ + uint8_t *data = dest; + size_t i; + + for (i = 0; i < len; i += 2) { + *(data++) = + parse_hexchar(src[i]) << 4 | + parse_hexchar(src[i + 1]); + } +} + +static void uefi_vars_from_qapi(uefi_vars_state *uv, UefiVarStore *vs) +{ + UefiVariableList *item; + UefiVariable *v; + QemuUUID be; + uefi_variable *var; + uint8_t *data; + size_t i, len; + + for (item = vs->variables; item != NULL; item = item->next) { + v = item->value; + + var = g_new0(uefi_variable, 1); + var->attributes = v->attr; + qemu_uuid_parse(v->guid, &be); + var->guid = qemu_uuid_bswap(be); + + len = strlen(v->name); + var->name_size = len * 2 + 2; + var->name = g_malloc(var->name_size); + for (i = 0; i <= len; i++) { + var->name[i] = v->name[i]; + } + + len = strlen(v->data); + var->data_size = len / 2; + var->data = data = g_malloc(var->data_size); + parse_hexstr(var->data, v->data, len); + + if (v->time && strlen(v->time) == 32) { + parse_hexstr(&var->time, v->time, 32); + } + + if (v->digest) { + len = strlen(v->digest); + var->digest_size = len / 2; + var->digest = g_malloc(var->digest_size); + parse_hexstr(var->digest, v->digest, len); + } + + QTAILQ_INSERT_TAIL(&uv->variables, var, next); + } +} + +static GString *uefi_vars_to_json(uefi_vars_state *uv) +{ + UefiVarStore *vs = uefi_vars_to_qapi(uv); + QObject *qobj = NULL; + Visitor *v; + GString *gstr; + + v = qobject_output_visitor_new(&qobj); + if (visit_type_UefiVarStore(v, NULL, &vs, NULL)) { + visit_complete(v, &qobj); + } + visit_free(v); + qapi_free_UefiVarStore(vs); + + gstr = qobject_to_json_pretty(qobj, true); + qobject_unref(qobj); + + return gstr; +} + +void uefi_vars_json_init(uefi_vars_state *uv, Error **errp) +{ + if (uv->jsonfile) { + uv->jsonfd = qemu_create(uv->jsonfile, O_RDWR, 0666, errp); + } +} + +void uefi_vars_json_save(uefi_vars_state *uv) +{ + GString *gstr; + int rc; + + if (uv->jsonfd == -1) { + return; + } + + gstr = uefi_vars_to_json(uv); + + lseek(uv->jsonfd, 0, SEEK_SET); + rc = ftruncate(uv->jsonfd, 0); + if (rc != 0) { + warn_report("%s: ftruncate error", __func__); + } + rc = write(uv->jsonfd, gstr->str, gstr->len); + if (rc != gstr->len) { + warn_report("%s: write error", __func__); + } + fsync(uv->jsonfd); + + g_string_free(gstr, true); +} + +void uefi_vars_json_load(uefi_vars_state *uv, Error **errp) +{ + UefiVarStore *vs; + QObject *qobj; + Visitor *v; + char *str; + size_t len; + int rc; + + if (uv->jsonfd == -1) { + return; + } + + len = lseek(uv->jsonfd, 0, SEEK_END); + if (len == 0) { + return; + } + + str = g_malloc(len + 1); + lseek(uv->jsonfd, 0, SEEK_SET); + rc = read(uv->jsonfd, str, len); + if (rc != len) { + warn_report("%s: read error", __func__); + } + str[len] = 0; + + qobj = qobject_from_json(str, errp); + v = qobject_input_visitor_new(qobj); + visit_type_UefiVarStore(v, NULL, &vs, errp); + visit_free(v); + + if (!(*errp)) { + uefi_vars_from_qapi(uv, vs); + uefi_vars_update_storage(uv); + } + + qapi_free_UefiVarStore(vs); + qobject_unref(qobj); + g_free(str); +} diff --git a/qapi/meson.build b/qapi/meson.build index e7bc54e5d047..eadde4db307f 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -65,6 +65,7 @@ if have_system 'pci', 'rocker', 'tpm', + 'uefi', ] endif if have_system or have_tools diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index b1581988e4eb..2877aff73d0c 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -81,3 +81,4 @@ { 'include': 'vfio.json' } { 'include': 'cryptodev.json' } { 'include': 'cxl.json' } +{ 'include': 'uefi.json' } diff --git a/qapi/uefi.json b/qapi/uefi.json new file mode 100644 index 000000000000..bdfcabe1df4d --- /dev/null +++ b/qapi/uefi.json @@ -0,0 +1,64 @@ +# -*- Mode: Python -*- +# vim: filetype=python +# + +## +# = UEFI Variable Store +# +# The qemu efi variable store implementation (hw/uefi/) uses this to +# store non-volatile variables in json format on disk. +# +# This is an existing format already supported by (at least) two other +# projects, specifically https://gitlab.com/kraxel/virt-firmware and +# https://github.com/awslabs/python-uefivars. +## + +## +# @UefiVariable: +# +# UEFI Variable. Check the UEFI specifification for more detailed +# information on the fields. +# +# @guid: variable namespace GUID +# +# @name: variable name, in UTF-8 encoding. +# +# @attr: variable attributes. +# +# @data: variable value, encoded as hex string. +# +# @time: variable modification time. EFI_TIME struct, encoded as hex +# string. Used only for authenticated variables, where the +# EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute bit +# is set. +# +# @digest: variable certificate digest. Used to verify the signature +# of updates for authenticated variables. UEFI has two kinds of +# authenticated variables. The secure boot variables ('PK', +# 'KEK', 'db' and 'dbx') have hard coded signature checking rules. +# For other authenticated variables the firmware stores a digest +# of the signing certificate at variable creation time, and any +# updates must be signed with the same certificate. +# +# Since: 10.0 +## +{ 'struct' : 'UefiVariable', + 'data' : { 'guid' : 'str', + 'name' : 'str', + 'attr' : 'int', + 'data' : 'str', + '*time' : 'str', + '*digest' : 'str'}} + +## +# @UefiVarStore: +# +# @version: currently always 2 +# +# @variables: list of UEFI variables +# +# Since: 10.0 +## +{ 'struct' : 'UefiVarStore', + 'data' : { 'version' : 'int', + 'variables' : [ 'UefiVariable' ] }} From 9282bed590ab54a24659fdcff668d46d7129d946 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:19 +0100 Subject: [PATCH 0423/1179] hw/uefi: add trace-events Add trace events for debugging and trouble shooting. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-16-kraxel@redhat.com> --- hw/uefi/trace-events | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 hw/uefi/trace-events diff --git a/hw/uefi/trace-events b/hw/uefi/trace-events new file mode 100644 index 000000000000..3694712a946d --- /dev/null +++ b/hw/uefi/trace-events @@ -0,0 +1,17 @@ +# device +uefi_reg_read(uint64_t addr, unsigned size) "addr 0x%" PRIx64 ", size %u" +uefi_reg_write(uint64_t addr, uint64_t val, unsigned size) "addr 0x%" PRIx64 ", val 0x%" PRIx64 ", size %d" +uefi_hard_reset(void) "" + +# generic uefi +uefi_variable(const char *context, const char *name, uint64_t size, const char *uuid) "context %s, name %s, size %" PRIu64 ", uuid %s" +uefi_status(const char *context, const char *name) "context %s, status %s" +uefi_event(const char *name) "event %s" + +# variable protocol +uefi_vars_proto_cmd(const char *cmd) "cmd %s" +uefi_vars_security_violation(const char *reason) "reason %s" + +# variable policy protocol +uefi_vars_policy_cmd(const char *cmd) "cmd %s" +uefi_vars_policy_deny(const char *reason) "reason %s" From e8371973d7e651f9d7b82100cb9745fd2795d722 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:20 +0100 Subject: [PATCH 0424/1179] hw/uefi: add UEFI_VARS to Kconfig Add UEFI_VARS config option, enable by default for x86_64 and aarch64. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-17-kraxel@redhat.com> --- hw/Kconfig | 1 + hw/uefi/Kconfig | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 hw/uefi/Kconfig diff --git a/hw/Kconfig b/hw/Kconfig index 1b4e9bb07f7d..c4dfe2e7af7c 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -37,6 +37,7 @@ source smbios/Kconfig source ssi/Kconfig source timer/Kconfig source tpm/Kconfig +source uefi/Kconfig source ufs/Kconfig source usb/Kconfig source virtio/Kconfig diff --git a/hw/uefi/Kconfig b/hw/uefi/Kconfig new file mode 100644 index 000000000000..ca6c2bc46a96 --- /dev/null +++ b/hw/uefi/Kconfig @@ -0,0 +1,3 @@ +config UEFI_VARS + bool + default y if X86_64 || AARCH64 From 736ca80cdd18c837dcfca016a6df746a70d33bb2 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:21 +0100 Subject: [PATCH 0425/1179] hw/uefi: add to meson Wire up uefi-vars in the build system. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-18-kraxel@redhat.com> --- hw/meson.build | 1 + hw/uefi/meson.build | 19 +++++++++++++++++++ meson.build | 1 + 3 files changed, 21 insertions(+) diff --git a/hw/meson.build b/hw/meson.build index b827c82c5d7b..138f5d59e178 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -35,6 +35,7 @@ subdir('smbios') subdir('ssi') subdir('timer') subdir('tpm') +subdir('uefi') subdir('ufs') subdir('usb') subdir('vfio') diff --git a/hw/uefi/meson.build b/hw/uefi/meson.build index a8b168941255..e63708aa164f 100644 --- a/hw/uefi/meson.build +++ b/hw/uefi/meson.build @@ -1 +1,20 @@ system_ss.add(files('hardware-info.c')) + +uefi_vars_ss = ss.source_set() +if (config_all_devices.has_key('CONFIG_UEFI_VARS')) + uefi_vars_ss.add(files('var-service-core.c', + 'var-service-json.c', + 'var-service-vars.c', + 'var-service-auth.c', + 'var-service-guid.c', + 'var-service-utils.c', + 'var-service-policy.c')) + uefi_vars_ss.add(when: gnutls, + if_true: files('var-service-pkcs7.c'), + if_false: files('var-service-pkcs7-stub.c')) + uefi_vars_ss.add(files('var-service-siglist.c')) +endif + +modules += { 'hw-uefi' : { + 'vars' : uefi_vars_ss, +}} diff --git a/meson.build b/meson.build index 0a2c61d2bfa0..1c1982dac3b0 100644 --- a/meson.build +++ b/meson.build @@ -3601,6 +3601,7 @@ if have_system 'hw/ssi', 'hw/timer', 'hw/tpm', + 'hw/uefi', 'hw/ufs', 'hw/usb', 'hw/vfio', From 5bb89df2e37382d6d278d52cfb22ae61f60daf70 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:22 +0100 Subject: [PATCH 0426/1179] hw/uefi: add uefi-vars-sysbus device This adds sysbus bindings for the variable service. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-19-kraxel@redhat.com> --- hw/uefi/meson.build | 3 +- hw/uefi/var-service-sysbus.c | 91 ++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 hw/uefi/var-service-sysbus.c diff --git a/hw/uefi/meson.build b/hw/uefi/meson.build index e63708aa164f..91eb95f89e6d 100644 --- a/hw/uefi/meson.build +++ b/hw/uefi/meson.build @@ -8,7 +8,8 @@ if (config_all_devices.has_key('CONFIG_UEFI_VARS')) 'var-service-auth.c', 'var-service-guid.c', 'var-service-utils.c', - 'var-service-policy.c')) + 'var-service-policy.c', + 'var-service-sysbus.c')) uefi_vars_ss.add(when: gnutls, if_true: files('var-service-pkcs7.c'), if_false: files('var-service-pkcs7-stub.c')) diff --git a/hw/uefi/var-service-sysbus.c b/hw/uefi/var-service-sysbus.c new file mode 100644 index 000000000000..60072c8815cd --- /dev/null +++ b/hw/uefi/var-service-sysbus.c @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - sysbus variant. + */ +#include "qemu/osdep.h" +#include "migration/vmstate.h" + +#include "hw/qdev-properties.h" +#include "hw/sysbus.h" + +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" + +OBJECT_DECLARE_SIMPLE_TYPE(uefi_vars_sysbus_state, UEFI_VARS_SYSBUS) + +struct uefi_vars_sysbus_state { + SysBusDevice parent_obj; + struct uefi_vars_state state; +}; + +static const VMStateDescription vmstate_uefi_vars_sysbus = { + .name = TYPE_UEFI_VARS_SYSBUS, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(state, uefi_vars_sysbus_state, 0, + vmstate_uefi_vars, uefi_vars_state), + VMSTATE_END_OF_LIST() + } +}; + +static const Property uefi_vars_sysbus_properties[] = { + DEFINE_PROP_SIZE("size", uefi_vars_sysbus_state, state.max_storage, + 256 * 1024), + DEFINE_PROP_STRING("jsonfile", uefi_vars_sysbus_state, state.jsonfile), + DEFINE_PROP_BOOL("force-secure-boot", uefi_vars_sysbus_state, + state.force_secure_boot, false), + DEFINE_PROP_BOOL("disable-custom-mode", uefi_vars_sysbus_state, + state.disable_custom_mode, false), + DEFINE_PROP_BOOL("use-pio", uefi_vars_sysbus_state, + state.use_pio, false), +}; + +static void uefi_vars_sysbus_init(Object *obj) +{ + uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(obj); + + uefi_vars_init(obj, &uv->state); +} + +static void uefi_vars_sysbus_reset(DeviceState *dev) +{ + uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev); + + uefi_vars_hard_reset(&uv->state); +} + +static void uefi_vars_sysbus_realize(DeviceState *dev, Error **errp) +{ + uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev); + SysBusDevice *sysbus = SYS_BUS_DEVICE(dev); + + sysbus_init_mmio(sysbus, &uv->state.mr); + uefi_vars_realize(&uv->state, errp); +} + +static void uefi_vars_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = uefi_vars_sysbus_realize; + dc->vmsd = &vmstate_uefi_vars_sysbus; + device_class_set_legacy_reset(dc, uefi_vars_sysbus_reset); + device_class_set_props(dc, uefi_vars_sysbus_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo uefi_vars_sysbus_info = { + .name = TYPE_UEFI_VARS_SYSBUS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(uefi_vars_sysbus_state), + .instance_init = uefi_vars_sysbus_init, + .class_init = uefi_vars_sysbus_class_init, +}; +module_obj(TYPE_UEFI_VARS_SYSBUS); + +static void uefi_vars_sysbus_register_types(void) +{ + type_register_static(&uefi_vars_sysbus_info); +} + +type_init(uefi_vars_sysbus_register_types) From 03223b665c675f6f7d05555d9c6a69678c9ab875 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:23 +0100 Subject: [PATCH 0427/1179] hw/uefi-vars-sysbus: qemu platform bus support Add and register function to create an device tree entry when the device is added to the qemu platform bus. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-20-kraxel@redhat.com> --- hw/core/sysbus-fdt.c | 24 ++++++++++++++++++++++++ hw/uefi/var-service-sysbus.c | 1 + 2 files changed, 25 insertions(+) diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c index 774c0aed41b5..e85066b90563 100644 --- a/hw/core/sysbus-fdt.c +++ b/hw/core/sysbus-fdt.c @@ -36,6 +36,7 @@ #include "hw/vfio/vfio-calxeda-xgmac.h" #include "hw/vfio/vfio-amd-xgbe.h" #include "hw/display/ramfb.h" +#include "hw/uefi/var-service-api.h" #include "hw/arm/fdt.h" /* @@ -471,6 +472,28 @@ static int add_tpm_tis_fdt_node(SysBusDevice *sbdev, void *opaque) } #endif +static int add_uefi_vars_node(SysBusDevice *sbdev, void *opaque) +{ + PlatformBusFDTData *data = opaque; + PlatformBusDevice *pbus = data->pbus; + const char *parent_node = data->pbus_node_name; + void *fdt = data->fdt; + uint64_t mmio_base; + char *nodename; + + mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0); + nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node, + UEFI_VARS_FDT_NODE, mmio_base); + qemu_fdt_add_subnode(fdt, nodename); + qemu_fdt_setprop_string(fdt, nodename, + "compatible", UEFI_VARS_FDT_COMPAT); + qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", + 1, mmio_base, + 1, UEFI_VARS_REGS_SIZE); + g_free(nodename); + return 0; +} + static int no_fdt_node(SysBusDevice *sbdev, void *opaque) { return 0; @@ -495,6 +518,7 @@ static const BindingEntry bindings[] = { TYPE_BINDING(TYPE_TPM_TIS_SYSBUS, add_tpm_tis_fdt_node), #endif TYPE_BINDING(TYPE_RAMFB_DEVICE, no_fdt_node), + TYPE_BINDING(TYPE_UEFI_VARS_SYSBUS, add_uefi_vars_node), TYPE_BINDING("", NULL), /* last element */ }; diff --git a/hw/uefi/var-service-sysbus.c b/hw/uefi/var-service-sysbus.c index 60072c8815cd..28572981c2af 100644 --- a/hw/uefi/var-service-sysbus.c +++ b/hw/uefi/var-service-sysbus.c @@ -69,6 +69,7 @@ static void uefi_vars_sysbus_class_init(ObjectClass *klass, void *data) dc->realize = uefi_vars_sysbus_realize; dc->vmsd = &vmstate_uefi_vars_sysbus; + dc->user_creatable = true; device_class_set_legacy_reset(dc, uefi_vars_sysbus_reset); device_class_set_props(dc, uefi_vars_sysbus_properties); set_bit(DEVICE_CATEGORY_MISC, dc->categories); From 69392de9138fe6d49f96dfae8adfb9cd64e0578e Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:24 +0100 Subject: [PATCH 0428/1179] hw/uefi-vars-sysbus: add x64 variant The x86 variant of the device is mapped on the fixed address 0xfef10000 and uses etc/hardware-info instead of FDT to pass the mapping location to the edk2 firmware. The latter allows to move the device to a different location should that turn out to be necessary in the future. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-21-kraxel@redhat.com> --- hw/uefi/var-service-sysbus.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/hw/uefi/var-service-sysbus.c b/hw/uefi/var-service-sysbus.c index 28572981c2af..97da8672ee95 100644 --- a/hw/uefi/var-service-sysbus.c +++ b/hw/uefi/var-service-sysbus.c @@ -9,6 +9,7 @@ #include "hw/qdev-properties.h" #include "hw/sysbus.h" +#include "hw/uefi/hardware-info.h" #include "hw/uefi/var-service.h" #include "hw/uefi/var-service-api.h" @@ -75,6 +76,7 @@ static void uefi_vars_sysbus_class_init(ObjectClass *klass, void *data) set_bit(DEVICE_CATEGORY_MISC, dc->categories); } +/* generic: hardware discovery via FDT */ static const TypeInfo uefi_vars_sysbus_info = { .name = TYPE_UEFI_VARS_SYSBUS, .parent = TYPE_SYS_BUS_DEVICE, @@ -84,9 +86,39 @@ static const TypeInfo uefi_vars_sysbus_info = { }; module_obj(TYPE_UEFI_VARS_SYSBUS); +static void uefi_vars_x64_realize(DeviceState *dev, Error **errp) +{ + HARDWARE_INFO_SIMPLE_DEVICE hwinfo = { + .mmio_address = cpu_to_le64(0xfef10000), + }; + SysBusDevice *sysbus = SYS_BUS_DEVICE(dev); + + uefi_vars_sysbus_realize(dev, errp); + + hardware_info_register(HardwareInfoQemuUefiVars, + &hwinfo, sizeof(hwinfo)); + sysbus_mmio_map(sysbus, 0, hwinfo.mmio_address); +} + +static void uefi_vars_x64_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = uefi_vars_x64_realize; +} + +/* x64: hardware discovery via etc/hardware-info fw_cfg */ +static const TypeInfo uefi_vars_x64_info = { + .name = TYPE_UEFI_VARS_X64, + .parent = TYPE_UEFI_VARS_SYSBUS, + .class_init = uefi_vars_x64_class_init, +}; +module_obj(TYPE_UEFI_VARS_X64); + static void uefi_vars_sysbus_register_types(void) { type_register_static(&uefi_vars_sysbus_info); + type_register_static(&uefi_vars_x64_info); } type_init(uefi_vars_sysbus_register_types) From 22ebb90e62e97a431ec50edf7ddb19412a934555 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:25 +0100 Subject: [PATCH 0429/1179] hw/uefi-vars-sysbus: allow for arm virt Allow the device being added to aarch64 virt VMs. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-22-kraxel@redhat.com> --- hw/arm/virt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index ee69081ef421..904c698b1406 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -82,6 +82,7 @@ #include "hw/mem/pc-dimm.h" #include "hw/mem/nvdimm.h" #include "hw/acpi/generic_event_device.h" +#include "hw/uefi/var-service-api.h" #include "hw/virtio/virtio-md-pci.h" #include "hw/virtio/virtio-iommu.h" #include "hw/char/pl011.h" @@ -3162,6 +3163,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_AMD_XGBE); machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE); machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_PLATFORM); + machine_class_allow_dynamic_sysbus_dev(mc, TYPE_UEFI_VARS_SYSBUS); #ifdef CONFIG_TPM machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS); #endif From 918b8a12247090a03ac8db61ac19a97170a553df Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:26 +0100 Subject: [PATCH 0430/1179] hw/uefi-vars-sysbus: allow for pc and q35 Allow the device being added to x86_64 pc and q35 VMs. Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-23-kraxel@redhat.com> --- hw/i386/pc_piix.c | 2 ++ hw/i386/pc_q35.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index 04d2957adcd7..6c91e2d29298 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -65,6 +65,7 @@ #include "system/numa.h" #include "hw/hyperv/vmbus-bridge.h" #include "hw/mem/nvdimm.h" +#include "hw/uefi/var-service-api.h" #include "hw/i386/acpi-build.h" #include "target/i386/cpu.h" @@ -468,6 +469,7 @@ static void pc_i440fx_machine_options(MachineClass *m) m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE); machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE); + machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64); object_class_property_add_enum(oc, "x-south-bridge", "PCSouthBridgeOption", &PCSouthBridgeOption_lookup, diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 77536dd697f5..fd96d0345c7d 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -58,6 +58,7 @@ #include "system/numa.h" #include "hw/hyperv/vmbus-bridge.h" #include "hw/mem/nvdimm.h" +#include "hw/uefi/var-service-api.h" #include "hw/i386/acpi-build.h" #include "target/i386/cpu.h" @@ -355,6 +356,7 @@ static void pc_q35_machine_options(MachineClass *m) machine_class_allow_dynamic_sysbus_dev(m, TYPE_INTEL_IOMMU_DEVICE); machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE); machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE); + machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64); compat_props_add(m->compat_props, pc_q35_compat_defaults, pc_q35_compat_defaults_len); } From 06fa8ec6f656af28624ab2da20541ce5d6e8c18e Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:27 +0100 Subject: [PATCH 0431/1179] hw/uefi: add MAINTAINERS entry Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-24-kraxel@redhat.com> --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2e7fc6fa912a..27cdfbebddef 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2820,6 +2820,12 @@ F: hw/misc/ivshmem-flat.c F: include/hw/misc/ivshmem-flat.h F: docs/system/devices/ivshmem-flat.rst +UEFI variable service +M: Gerd Hoffmann +S: Maintained +F: hw/uefi/ +F: include/hw/uefi/ + Subsystems ---------- Overall Audio backends From 2bc10b15deb4b29391628e10b18701bfbcf4be17 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 25 Feb 2025 17:30:28 +0100 Subject: [PATCH 0432/1179] docs: add uefi variable service documentation Signed-off-by: Gerd Hoffmann Message-ID: <20250225163031.1409078-25-kraxel@redhat.com> --- docs/devel/index-internals.rst | 1 + docs/devel/uefi-vars.rst | 68 ++++++++++++++++++++++++++++++++++ hw/uefi/LIMITATIONS.md | 7 ++++ 3 files changed, 76 insertions(+) create mode 100644 docs/devel/uefi-vars.rst create mode 100644 hw/uefi/LIMITATIONS.md diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index bca597c65895..7a0678cbdd3a 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst @@ -20,6 +20,7 @@ Details about QEMU's various subsystems including how to add features to them. s390-cpu-topology s390-dasd-ipl tracing + uefi-vars vfio-iommufd writing-monitor-commands virtio-backends diff --git a/docs/devel/uefi-vars.rst b/docs/devel/uefi-vars.rst new file mode 100644 index 000000000000..0151a26a0a6f --- /dev/null +++ b/docs/devel/uefi-vars.rst @@ -0,0 +1,68 @@ +============== +UEFI variables +============== + +Guest UEFI variable management +============================== + +The traditional approach for UEFI Variable storage in qemu guests is +to work as close as possible to physical hardware. That means +providing pflash as storage and leaving the management of variables +and flash to the guest. + +Secure boot support comes with the requirement that the UEFI variable +storage must be protected against direct access by the OS. All update +requests must pass the sanity checks. (Parts of) the firmware must +run with a higher privilege level than the OS so this can be enforced +by the firmware. On x86 this has been implemented using System +Management Mode (SMM) in qemu and kvm, which again is the same +approach taken by physical hardware. Only privileged code running in +SMM mode is allowed to access flash storage. + +Communication with the firmware code running in SMM mode works by +serializing the requests to a shared buffer, then trapping into SMM +mode via SMI. The SMM code processes the request, stores the reply in +the same buffer and returns. + +Host UEFI variable service +========================== + +Instead of running the privileged code inside the guest we can run it +on the host. The serialization protocol can be reused. The +communication with the host uses a virtual device, which essentially +configures the shared buffer location and size, and traps to the host +to process the requests. + +The ``uefi-vars`` device implements the UEFI virtual device. It comes +in ``uefi-vars-x86`` and ``uefi-vars-sysbus`` flavours. The device +reimplements the handlers needed, specifically +``EfiSmmVariableProtocol`` and ``VarCheckPolicyLibMmiHandler``. It +also consumes events (``EfiEndOfDxeEventGroup``, +``EfiEventReadyToBoot`` and ``EfiEventExitBootServices``). + +The advantage of the approach is that we do not need a special +privilege level for the firmware to protect itself, i.e. it does not +depend on SMM emulation on x64, which allows the removal of a bunch of +complex code for SMM emulation from the linux kernel +(CONFIG_KVM_SMM=n). It also allows support for secure boot on arm +without implementing secure world (el3) emulation in kvm. + +Of course there are also downsides. The added device increases the +attack surface of the host, and we are adding some code duplication +because we have to reimplement some edk2 functionality in qemu. + +usage on x86_64 +--------------- + +.. code:: + + qemu-system-x86_64 \ + -device uefi-vars-x86,jsonfile=/path/to/vars.json + +usage on aarch64 +---------------- + +.. code:: + + qemu-system-aarch64 -M virt \ + -device uefi-vars-sysbus,jsonfile=/path/to/vars.json diff --git a/hw/uefi/LIMITATIONS.md b/hw/uefi/LIMITATIONS.md new file mode 100644 index 000000000000..29308bd587aa --- /dev/null +++ b/hw/uefi/LIMITATIONS.md @@ -0,0 +1,7 @@ +known issues and limitations +---------------------------- + +* works only on little endian hosts + - accessing structs in guest ram is done without endian conversion. +* works only for 64-bit guests + - UINTN is mapped to uint64_t, for 32-bit guests that would be uint32_t From 996fa948f9cc87b97375e78077c7e36df100a6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 27 Dec 2024 21:12:07 +0100 Subject: [PATCH 0433/1179] hw/intc: Remove TCG dependency on ARM_GICV3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TYPE_ARM_GICV3 model doesn't have any particular dependency on TCG, remove it. Rename the Kconfig selector ARM_GICV3_TCG -> ARM_GICV3. Fixes: a8a5546798c ("hw/intc/arm_gicv3: Introduce CONFIG_ARM_GIC_TCG Kconfig selector") Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Phil Dennis-Jordan Tested-by: Phil Dennis-Jordan Message-Id: <20241227202435.48055-2-philmd@linaro.org> --- hw/intc/Kconfig | 6 +++--- hw/intc/meson.build | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index dd405bdb5d21..7547528f2c27 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -23,13 +23,13 @@ config APIC config ARM_GIC bool - select ARM_GICV3_TCG if TCG + select ARM_GICV3 if TCG select ARM_GIC_KVM if KVM select MSI_NONBROKEN -config ARM_GICV3_TCG +config ARM_GICV3 bool - depends on ARM_GIC && TCG + depends on ARM_GIC config ARM_GIC_KVM bool diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 510fdfb68865..602da304b02d 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -6,7 +6,7 @@ system_ss.add(when: 'CONFIG_ARM_GIC', if_true: files( 'arm_gicv3_common.c', 'arm_gicv3_its_common.c', )) -system_ss.add(when: 'CONFIG_ARM_GICV3_TCG', if_true: files( +system_ss.add(when: 'CONFIG_ARM_GICV3', if_true: files( 'arm_gicv3.c', 'arm_gicv3_dist.c', 'arm_gicv3_its.c', @@ -39,7 +39,7 @@ endif specific_ss.add(when: 'CONFIG_APIC', if_true: files('apic.c', 'apic_common.c')) specific_ss.add(when: 'CONFIG_ARM_GIC', if_true: files('arm_gicv3_cpuif_common.c')) -specific_ss.add(when: 'CONFIG_ARM_GICV3_TCG', if_true: files('arm_gicv3_cpuif.c')) +specific_ss.add(when: 'CONFIG_ARM_GICV3', if_true: files('arm_gicv3_cpuif.c')) specific_ss.add(when: 'CONFIG_ARM_GIC_KVM', if_true: files('arm_gic_kvm.c')) specific_ss.add(when: ['CONFIG_ARM_GIC_KVM', 'TARGET_AARCH64'], if_true: files('arm_gicv3_kvm.c', 'arm_gicv3_its_kvm.c')) specific_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_nvic.c')) From a89607c4d0e421fa600b84e446a50f0f5f078941 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 14 Jun 2023 22:40:28 +0000 Subject: [PATCH 0434/1179] hw/misc/pvpanic: Add MMIO interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to the ISA and PCI variants of pvpanic, let's add an MMIO platform device that we can use in embedded arm environments. Signed-off-by: Alexander Graf Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé Signed-off-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Tested-by: Akihiko Odaki Message-ID: <20241223221645.29911-8-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- hw/misc/Kconfig | 4 +++ hw/misc/meson.build | 1 + hw/misc/pvpanic-mmio.c | 60 +++++++++++++++++++++++++++++++++++++++ include/hw/misc/pvpanic.h | 1 + 4 files changed, 66 insertions(+) create mode 100644 hw/misc/pvpanic-mmio.c diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 82bd68b4bb82..ec0fa5aa9f8c 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -148,6 +148,10 @@ config PVPANIC_ISA depends on ISA_BUS select PVPANIC_COMMON +config PVPANIC_MMIO + bool + select PVPANIC_COMMON + config AUX bool select I2C diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 0b5187a2f746..6d47de482c58 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -126,6 +126,7 @@ system_ss.add(when: 'CONFIG_ARMSSE_MHU', if_true: files('armsse-mhu.c')) system_ss.add(when: 'CONFIG_PVPANIC_ISA', if_true: files('pvpanic-isa.c')) system_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c')) +system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( 'aspeed_hace.c', diff --git a/hw/misc/pvpanic-mmio.c b/hw/misc/pvpanic-mmio.c new file mode 100644 index 000000000000..70097cecc743 --- /dev/null +++ b/hw/misc/pvpanic-mmio.c @@ -0,0 +1,60 @@ +/* + * QEMU simulated pvpanic device (MMIO frontend) + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "hw/qdev-properties.h" +#include "hw/misc/pvpanic.h" +#include "hw/sysbus.h" +#include "standard-headers/misc/pvpanic.h" + +OBJECT_DECLARE_SIMPLE_TYPE(PVPanicMMIOState, PVPANIC_MMIO_DEVICE) + +#define PVPANIC_MMIO_SIZE 0x2 + +struct PVPanicMMIOState { + SysBusDevice parent_obj; + + PVPanicState pvpanic; +}; + +static void pvpanic_mmio_initfn(Object *obj) +{ + PVPanicMMIOState *s = PVPANIC_MMIO_DEVICE(obj); + + pvpanic_setup_io(&s->pvpanic, DEVICE(s), PVPANIC_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->pvpanic.mr); +} + +static const Property pvpanic_mmio_properties[] = { + DEFINE_PROP_UINT8("events", PVPanicMMIOState, pvpanic.events, + PVPANIC_PANICKED | PVPANIC_CRASH_LOADED), +}; + +static void pvpanic_mmio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, pvpanic_mmio_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo pvpanic_mmio_info = { + .name = TYPE_PVPANIC_MMIO_DEVICE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PVPanicMMIOState), + .instance_init = pvpanic_mmio_initfn, + .class_init = pvpanic_mmio_class_init, +}; + +static void pvpanic_register_types(void) +{ + type_register_static(&pvpanic_mmio_info); +} + +type_init(pvpanic_register_types) diff --git a/include/hw/misc/pvpanic.h b/include/hw/misc/pvpanic.h index 9a71a5ad0d7d..049a94c11254 100644 --- a/include/hw/misc/pvpanic.h +++ b/include/hw/misc/pvpanic.h @@ -26,6 +26,7 @@ #define TYPE_PVPANIC_ISA_DEVICE "pvpanic" #define TYPE_PVPANIC_PCI_DEVICE "pvpanic-pci" +#define TYPE_PVPANIC_MMIO_DEVICE "pvpanic-mmio" #define PVPANIC_IOPORT_PROP "ioport" From 11fa056e792a99c83de34af8d4266fef90e498cb Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 14 Jun 2023 22:56:23 +0000 Subject: [PATCH 0435/1179] hw: Add vmapple subdir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will introduce a number of devices that are specific to the vmapple target machine. To keep them all tidily together, let's put them into a single target directory. Signed-off-by: Alexander Graf Signed-off-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Tested-by: Akihiko Odaki Message-ID: <20241223221645.29911-7-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- MAINTAINERS | 7 +++++++ hw/Kconfig | 1 + hw/meson.build | 1 + hw/vmapple/Kconfig | 1 + hw/vmapple/meson.build | 1 + hw/vmapple/trace-events | 2 ++ hw/vmapple/trace.h | 2 ++ meson.build | 1 + 8 files changed, 16 insertions(+) create mode 100644 hw/vmapple/Kconfig create mode 100644 hw/vmapple/meson.build create mode 100644 hw/vmapple/trace-events create mode 100644 hw/vmapple/trace.h diff --git a/MAINTAINERS b/MAINTAINERS index 2e7fc6fa912a..0e1603609183 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2820,6 +2820,13 @@ F: hw/misc/ivshmem-flat.c F: include/hw/misc/ivshmem-flat.h F: docs/system/devices/ivshmem-flat.rst +VMapple +M: Alexander Graf +M: Phil Dennis-Jordan +S: Maintained +F: hw/vmapple/* +F: include/hw/vmapple/* + Subsystems ---------- Overall Audio backends diff --git a/hw/Kconfig b/hw/Kconfig index 1b4e9bb07f7d..2871784cfdc9 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -41,6 +41,7 @@ source ufs/Kconfig source usb/Kconfig source virtio/Kconfig source vfio/Kconfig +source vmapple/Kconfig source xen/Kconfig source watchdog/Kconfig diff --git a/hw/meson.build b/hw/meson.build index b827c82c5d7b..9c4f6d0d6367 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -39,6 +39,7 @@ subdir('ufs') subdir('usb') subdir('vfio') subdir('virtio') +subdir('vmapple') subdir('watchdog') subdir('xen') subdir('xenpv') diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig new file mode 100644 index 000000000000..315c06b689cf --- /dev/null +++ b/hw/vmapple/Kconfig @@ -0,0 +1 @@ +# SPDX-License-Identifier: GPL-2.0-or-later diff --git a/hw/vmapple/meson.build b/hw/vmapple/meson.build new file mode 100644 index 000000000000..315c06b689cf --- /dev/null +++ b/hw/vmapple/meson.build @@ -0,0 +1 @@ +# SPDX-License-Identifier: GPL-2.0-or-later diff --git a/hw/vmapple/trace-events b/hw/vmapple/trace-events new file mode 100644 index 000000000000..211257941212 --- /dev/null +++ b/hw/vmapple/trace-events @@ -0,0 +1,2 @@ +# See docs/devel/tracing.rst for syntax documentation. +# SPDX-License-Identifier: GPL-2.0-or-later diff --git a/hw/vmapple/trace.h b/hw/vmapple/trace.h new file mode 100644 index 000000000000..d099d5ecd9e2 --- /dev/null +++ b/hw/vmapple/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-hw_vmapple.h" diff --git a/meson.build b/meson.build index 0a2c61d2bfa0..d1b807aa53f9 100644 --- a/meson.build +++ b/meson.build @@ -3605,6 +3605,7 @@ if have_system 'hw/usb', 'hw/vfio', 'hw/virtio', + 'hw/vmapple', 'hw/watchdog', 'hw/xen', 'hw/gpio', From c960b389554bd04e645e321e1cee1d3b4590cc83 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 14 Jun 2023 22:56:25 +0000 Subject: [PATCH 0436/1179] hw/vmapple/aes: Introduce aes engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VMApple contains an "aes" engine device that it uses to encrypt and decrypt its nvram. It has trivial hard coded keys it uses for that purpose. Add device emulation for this device model. Signed-off-by: Alexander Graf Signed-off-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Tested-by: Akihiko Odaki Message-ID: <20241223221645.29911-10-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- hw/vmapple/Kconfig | 3 + hw/vmapple/aes.c | 581 +++++++++++++++++++++++++++++++++++ hw/vmapple/meson.build | 2 + hw/vmapple/trace-events | 14 + include/hw/vmapple/vmapple.h | 17 + include/qemu/cutils.h | 15 + util/hexdump.c | 18 ++ 7 files changed, 650 insertions(+) create mode 100644 hw/vmapple/aes.c create mode 100644 include/hw/vmapple/vmapple.h diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig index 315c06b689cf..b1944d73129f 100644 --- a/hw/vmapple/Kconfig +++ b/hw/vmapple/Kconfig @@ -1 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-or-later + +config VMAPPLE_AES + bool diff --git a/hw/vmapple/aes.c b/hw/vmapple/aes.c new file mode 100644 index 000000000000..3a7641ab4b00 --- /dev/null +++ b/hw/vmapple/aes.c @@ -0,0 +1,581 @@ +/* + * QEMU Apple AES device emulation + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "trace.h" +#include "crypto/hash.h" +#include "crypto/aes.h" +#include "crypto/cipher.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "hw/vmapple/vmapple.h" +#include "migration/vmstate.h" +#include "qemu/cutils.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "system/dma.h" + +OBJECT_DECLARE_SIMPLE_TYPE(AESState, APPLE_AES) + +#define MAX_FIFO_SIZE 9 + +#define CMD_KEY 0x1 +#define CMD_KEY_CONTEXT_SHIFT 27 +#define CMD_KEY_CONTEXT_MASK (0x1 << CMD_KEY_CONTEXT_SHIFT) +#define CMD_KEY_SELECT_MAX_IDX 0x7 +#define CMD_KEY_SELECT_SHIFT 24 +#define CMD_KEY_SELECT_MASK (CMD_KEY_SELECT_MAX_IDX << CMD_KEY_SELECT_SHIFT) +#define CMD_KEY_KEY_LEN_NUM 4u +#define CMD_KEY_KEY_LEN_SHIFT 22 +#define CMD_KEY_KEY_LEN_MASK ((CMD_KEY_KEY_LEN_NUM - 1u) << CMD_KEY_KEY_LEN_SHIFT) +#define CMD_KEY_ENCRYPT_SHIFT 20 +#define CMD_KEY_ENCRYPT_MASK (0x1 << CMD_KEY_ENCRYPT_SHIFT) +#define CMD_KEY_BLOCK_MODE_SHIFT 16 +#define CMD_KEY_BLOCK_MODE_MASK (0x3 << CMD_KEY_BLOCK_MODE_SHIFT) +#define CMD_IV 0x2 +#define CMD_IV_CONTEXT_SHIFT 26 +#define CMD_IV_CONTEXT_MASK (0x3 << CMD_KEY_CONTEXT_SHIFT) +#define CMD_DSB 0x3 +#define CMD_SKG 0x4 +#define CMD_DATA 0x5 +#define CMD_DATA_KEY_CTX_SHIFT 27 +#define CMD_DATA_KEY_CTX_MASK (0x1 << CMD_DATA_KEY_CTX_SHIFT) +#define CMD_DATA_IV_CTX_SHIFT 25 +#define CMD_DATA_IV_CTX_MASK (0x3 << CMD_DATA_IV_CTX_SHIFT) +#define CMD_DATA_LEN_MASK 0xffffff +#define CMD_STORE_IV 0x6 +#define CMD_STORE_IV_ADDR_MASK 0xffffff +#define CMD_WRITE_REG 0x7 +#define CMD_FLAG 0x8 +#define CMD_FLAG_STOP_MASK BIT(26) +#define CMD_FLAG_RAISE_IRQ_MASK BIT(27) +#define CMD_FLAG_INFO_MASK 0xff +#define CMD_MAX 0x10 + +#define CMD_SHIFT 28 + +#define REG_STATUS 0xc +#define REG_STATUS_DMA_READ_RUNNING BIT(0) +#define REG_STATUS_DMA_READ_PENDING BIT(1) +#define REG_STATUS_DMA_WRITE_RUNNING BIT(2) +#define REG_STATUS_DMA_WRITE_PENDING BIT(3) +#define REG_STATUS_BUSY BIT(4) +#define REG_STATUS_EXECUTING BIT(5) +#define REG_STATUS_READY BIT(6) +#define REG_STATUS_TEXT_DPA_SEEDED BIT(7) +#define REG_STATUS_UNWRAP_DPA_SEEDED BIT(8) + +#define REG_IRQ_STATUS 0x18 +#define REG_IRQ_STATUS_INVALID_CMD BIT(2) +#define REG_IRQ_STATUS_FLAG BIT(5) +#define REG_IRQ_ENABLE 0x1c +#define REG_WATERMARK 0x20 +#define REG_Q_STATUS 0x24 +#define REG_FLAG_INFO 0x30 +#define REG_FIFO 0x200 + +static const uint32_t key_lens[CMD_KEY_KEY_LEN_NUM] = { + [0] = 16, + [1] = 24, + [2] = 32, + [3] = 64, +}; + +typedef struct Key { + uint32_t key_len; + uint8_t key[32]; +} Key; + +typedef struct IV { + uint32_t iv[4]; +} IV; + +static Key builtin_keys[CMD_KEY_SELECT_MAX_IDX + 1] = { + [1] = { + .key_len = 32, + .key = { 0x1 }, + }, + [2] = { + .key_len = 32, + .key = { 0x2 }, + }, + [3] = { + .key_len = 32, + .key = { 0x3 }, + } +}; + +struct AESState { + SysBusDevice parent_obj; + + qemu_irq irq; + MemoryRegion iomem1; + MemoryRegion iomem2; + AddressSpace *as; + + uint32_t status; + uint32_t q_status; + uint32_t irq_status; + uint32_t irq_enable; + uint32_t watermark; + uint32_t flag_info; + uint32_t fifo[MAX_FIFO_SIZE]; + uint32_t fifo_idx; + Key key[2]; + IV iv[4]; + bool is_encrypt; + QCryptoCipherMode block_mode; +}; + +static void aes_update_irq(AESState *s) +{ + qemu_set_irq(s->irq, !!(s->irq_status & s->irq_enable)); +} + +static uint64_t aes1_read(void *opaque, hwaddr offset, unsigned size) +{ + AESState *s = opaque; + uint64_t res = 0; + + switch (offset) { + case REG_STATUS: + res = s->status; + break; + case REG_IRQ_STATUS: + res = s->irq_status; + break; + case REG_IRQ_ENABLE: + res = s->irq_enable; + break; + case REG_WATERMARK: + res = s->watermark; + break; + case REG_Q_STATUS: + res = s->q_status; + break; + case REG_FLAG_INFO: + res = s->flag_info; + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: Unknown AES MMIO offset %" PRIx64 "\n", + __func__, offset); + break; + } + + trace_aes_read(offset, res); + + return res; +} + +static void fifo_append(AESState *s, uint64_t val) +{ + if (s->fifo_idx == MAX_FIFO_SIZE) { + /* Exceeded the FIFO. Bail out */ + return; + } + + s->fifo[s->fifo_idx++] = val; +} + +static bool has_payload(AESState *s, uint32_t elems) +{ + return s->fifo_idx >= elems + 1; +} + +static bool cmd_key(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t key_select = (cmd & CMD_KEY_SELECT_MASK) >> CMD_KEY_SELECT_SHIFT; + uint32_t ctxt = (cmd & CMD_KEY_CONTEXT_MASK) >> CMD_KEY_CONTEXT_SHIFT; + uint32_t key_len; + + switch ((cmd & CMD_KEY_BLOCK_MODE_MASK) >> CMD_KEY_BLOCK_MODE_SHIFT) { + case 0: + s->block_mode = QCRYPTO_CIPHER_MODE_ECB; + break; + case 1: + s->block_mode = QCRYPTO_CIPHER_MODE_CBC; + break; + default: + return false; + } + + s->is_encrypt = cmd & CMD_KEY_ENCRYPT_MASK; + key_len = key_lens[(cmd & CMD_KEY_KEY_LEN_MASK) >> CMD_KEY_KEY_LEN_SHIFT]; + + if (key_select) { + trace_aes_cmd_key_select_builtin(ctxt, key_select, + s->is_encrypt ? "en" : "de", + QCryptoCipherMode_str(s->block_mode)); + s->key[ctxt] = builtin_keys[key_select]; + } else { + trace_aes_cmd_key_select_new(ctxt, key_len, + s->is_encrypt ? "en" : "de", + QCryptoCipherMode_str(s->block_mode)); + if (key_len > sizeof(s->key[ctxt].key)) { + return false; + } + if (!has_payload(s, key_len / sizeof(uint32_t))) { + /* wait for payload */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__); + return false; + } + memcpy(&s->key[ctxt].key, &s->fifo[1], key_len); + s->key[ctxt].key_len = key_len; + } + + return true; +} + +static bool cmd_iv(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t ctxt = (cmd & CMD_IV_CONTEXT_MASK) >> CMD_IV_CONTEXT_SHIFT; + + if (!has_payload(s, 4)) { + /* wait for payload */ + return false; + } + memcpy(&s->iv[ctxt].iv, &s->fifo[1], sizeof(s->iv[ctxt].iv)); + trace_aes_cmd_iv(ctxt, s->fifo[1], s->fifo[2], s->fifo[3], s->fifo[4]); + + return true; +} + +static void dump_data(const char *desc, const void *p, size_t len) +{ + static const size_t MAX_LEN = 0x1000; + char hex[MAX_LEN * 2 + 1] = ""; + + if (len > MAX_LEN) { + return; + } + + qemu_hexdump_to_buffer(hex, sizeof(hex), p, len); + trace_aes_dump_data(desc, hex); +} + +static bool cmd_data(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t ctxt_iv = 0; + uint32_t ctxt_key = (cmd & CMD_DATA_KEY_CTX_MASK) >> CMD_DATA_KEY_CTX_SHIFT; + uint32_t len = cmd & CMD_DATA_LEN_MASK; + uint64_t src_addr = s->fifo[2]; + uint64_t dst_addr = s->fifo[3]; + QCryptoCipherAlgo alg; + g_autoptr(QCryptoCipher) cipher = NULL; + g_autoptr(GByteArray) src = NULL; + g_autoptr(GByteArray) dst = NULL; + MemTxResult r; + + src_addr |= ((uint64_t)s->fifo[1] << 16) & 0xffff00000000ULL; + dst_addr |= ((uint64_t)s->fifo[1] << 32) & 0xffff00000000ULL; + + trace_aes_cmd_data(ctxt_key, ctxt_iv, src_addr, dst_addr, len); + + if (!has_payload(s, 3)) { + /* wait for payload */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__); + return false; + } + + if (ctxt_key >= ARRAY_SIZE(s->key) || + ctxt_iv >= ARRAY_SIZE(s->iv)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid key or iv\n", __func__); + return false; + } + + src = g_byte_array_sized_new(len); + g_byte_array_set_size(src, len); + dst = g_byte_array_sized_new(len); + g_byte_array_set_size(dst, len); + + r = dma_memory_read(s->as, src_addr, src->data, len, MEMTXATTRS_UNSPECIFIED); + if (r != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA read of %"PRIu32" bytes " + "from 0x%"PRIx64" failed. (r=%d)\n", + __func__, len, src_addr, r); + return false; + } + + dump_data("cmd_data(): src_data=", src->data, len); + + switch (s->key[ctxt_key].key_len) { + case 128 / 8: + alg = QCRYPTO_CIPHER_ALGO_AES_128; + break; + case 192 / 8: + alg = QCRYPTO_CIPHER_ALGO_AES_192; + break; + case 256 / 8: + alg = QCRYPTO_CIPHER_ALGO_AES_256; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid key length\n", __func__); + return false; + } + cipher = qcrypto_cipher_new(alg, s->block_mode, + s->key[ctxt_key].key, + s->key[ctxt_key].key_len, NULL); + if (!cipher) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to create cipher object\n", + __func__); + return false; + } + if (s->block_mode != QCRYPTO_CIPHER_MODE_ECB) { + if (qcrypto_cipher_setiv(cipher, (void *)s->iv[ctxt_iv].iv, + sizeof(s->iv[ctxt_iv].iv), NULL) != 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to set IV\n", __func__); + return false; + } + } + if (s->is_encrypt) { + if (qcrypto_cipher_encrypt(cipher, src->data, dst->data, len, NULL) != 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Encryption failed\n", __func__); + return false; + } + } else { + if (qcrypto_cipher_decrypt(cipher, src->data, dst->data, len, NULL) != 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Decryption failed\n", __func__); + return false; + } + } + + dump_data("cmd_data(): dst_data=", dst->data, len); + r = dma_memory_write(s->as, dst_addr, dst->data, len, MEMTXATTRS_UNSPECIFIED); + if (r != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA write of %"PRIu32" bytes " + "to 0x%"PRIx64" failed. (r=%d)\n", + __func__, len, src_addr, r); + return false; + } + + return true; +} + +static bool cmd_store_iv(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t ctxt = (cmd & CMD_IV_CONTEXT_MASK) >> CMD_IV_CONTEXT_SHIFT; + uint64_t addr = s->fifo[1]; + MemTxResult dma_result; + + if (!has_payload(s, 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__); + return false; + } + + if (ctxt >= ARRAY_SIZE(s->iv)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid context. ctxt = %u, allowed: 0..%zu\n", + __func__, ctxt, ARRAY_SIZE(s->iv) - 1); + return false; + } + + addr |= ((uint64_t)cmd << 32) & 0xff00000000ULL; + dma_result = dma_memory_write(&address_space_memory, addr, + &s->iv[ctxt].iv, sizeof(s->iv[ctxt].iv), + MEMTXATTRS_UNSPECIFIED); + + trace_aes_cmd_store_iv(ctxt, addr, s->iv[ctxt].iv[0], s->iv[ctxt].iv[1], + s->iv[ctxt].iv[2], s->iv[ctxt].iv[3]); + + return dma_result == MEMTX_OK; +} + +static bool cmd_flag(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t raise_irq = cmd & CMD_FLAG_RAISE_IRQ_MASK; + + /* We always process data when it's coming in, so fire an IRQ immediately */ + if (raise_irq) { + s->irq_status |= REG_IRQ_STATUS_FLAG; + } + + s->flag_info = cmd & CMD_FLAG_INFO_MASK; + + trace_aes_cmd_flag(!!raise_irq, s->flag_info); + + return true; +} + +static void fifo_process(AESState *s) +{ + uint32_t cmd = s->fifo[0] >> CMD_SHIFT; + bool success = false; + + if (!s->fifo_idx) { + return; + } + + switch (cmd) { + case CMD_KEY: + success = cmd_key(s); + break; + case CMD_IV: + success = cmd_iv(s); + break; + case CMD_DATA: + success = cmd_data(s); + break; + case CMD_STORE_IV: + success = cmd_store_iv(s); + break; + case CMD_FLAG: + success = cmd_flag(s); + break; + default: + s->irq_status |= REG_IRQ_STATUS_INVALID_CMD; + break; + } + + if (success) { + s->fifo_idx = 0; + } + + trace_aes_fifo_process(cmd, success); +} + +static void aes1_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) +{ + AESState *s = opaque; + + trace_aes_write(offset, val); + + switch (offset) { + case REG_IRQ_STATUS: + s->irq_status &= ~val; + break; + case REG_IRQ_ENABLE: + s->irq_enable = val; + break; + case REG_FIFO: + fifo_append(s, val); + fifo_process(s); + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: Unknown AES MMIO offset %"PRIx64", data %"PRIx64"\n", + __func__, offset, val); + return; + } + + aes_update_irq(s); +} + +static const MemoryRegionOps aes1_ops = { + .read = aes1_read, + .write = aes1_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static uint64_t aes2_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t res = 0; + + switch (offset) { + case 0: + res = 0; + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: Unknown AES MMIO 2 offset %"PRIx64"\n", + __func__, offset); + break; + } + + trace_aes_2_read(offset, res); + + return res; +} + +static void aes2_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) +{ + trace_aes_2_write(offset, val); + + switch (offset) { + default: + qemu_log_mask(LOG_UNIMP, + "%s: Unknown AES MMIO 2 offset %"PRIx64", data %"PRIx64"\n", + __func__, offset, val); + return; + } +} + +static const MemoryRegionOps aes2_ops = { + .read = aes2_read, + .write = aes2_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void aes_reset(Object *obj, ResetType type) +{ + AESState *s = APPLE_AES(obj); + + s->status = 0x3f80; + s->q_status = 2; + s->irq_status = 0; + s->irq_enable = 0; + s->watermark = 0; +} + +static void aes_init(Object *obj) +{ + AESState *s = APPLE_AES(obj); + + memory_region_init_io(&s->iomem1, obj, &aes1_ops, s, TYPE_APPLE_AES, 0x4000); + memory_region_init_io(&s->iomem2, obj, &aes2_ops, s, TYPE_APPLE_AES, 0x4000); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem1); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem2); + sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq); + s->as = &address_space_memory; +} + +static void aes_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.hold = aes_reset; +} + +static const TypeInfo aes_info = { + .name = TYPE_APPLE_AES, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AESState), + .class_init = aes_class_init, + .instance_init = aes_init, +}; + +static void aes_register_types(void) +{ + type_register_static(&aes_info); +} + +type_init(aes_register_types) diff --git a/hw/vmapple/meson.build b/hw/vmapple/meson.build index 315c06b689cf..a701d06a39de 100644 --- a/hw/vmapple/meson.build +++ b/hw/vmapple/meson.build @@ -1 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-or-later + +system_ss.add(when: 'CONFIG_VMAPPLE_AES', if_true: files('aes.c')) diff --git a/hw/vmapple/trace-events b/hw/vmapple/trace-events index 211257941212..188547a6ad83 100644 --- a/hw/vmapple/trace-events +++ b/hw/vmapple/trace-events @@ -1,2 +1,16 @@ # See docs/devel/tracing.rst for syntax documentation. # SPDX-License-Identifier: GPL-2.0-or-later + +# aes.c +aes_read(uint64_t offset, uint64_t res) "offset=0x%"PRIx64" res=0x%"PRIx64 +aes_cmd_key_select_builtin(uint32_t ctx, uint32_t key_id, const char *direction, const char *cipher) "[%d] Selecting builtin key %d to %scrypt with %s" +aes_cmd_key_select_new(uint32_t ctx, uint32_t key_len, const char *direction, const char *cipher) "[%d] Selecting new key size=%d to %scrypt with %s" +aes_cmd_iv(uint32_t ctx, uint32_t iv0, uint32_t iv1, uint32_t iv2, uint32_t iv3) "[%d] 0x%08x 0x%08x 0x%08x 0x%08x" +aes_cmd_data(uint32_t key, uint32_t iv, uint64_t src, uint64_t dst, uint32_t len) "[key=%d iv=%d] src=0x%"PRIx64" dst=0x%"PRIx64" len=0x%x" +aes_cmd_store_iv(uint32_t ctx, uint64_t addr, uint32_t iv0, uint32_t iv1, uint32_t iv2, uint32_t iv3) "[%d] addr=0x%"PRIx64"x -> 0x%08x 0x%08x 0x%08x 0x%08x" +aes_cmd_flag(uint32_t raise, uint32_t flag_info) "raise=%d flag_info=0x%x" +aes_fifo_process(uint32_t cmd, bool success) "cmd=%d success=%d" +aes_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64 +aes_2_read(uint64_t offset, uint64_t res) "offset=0x%"PRIx64" res=0x%"PRIx64 +aes_2_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64 +aes_dump_data(const char *desc, const char *hex) "%s%s" diff --git a/include/hw/vmapple/vmapple.h b/include/hw/vmapple/vmapple.h new file mode 100644 index 000000000000..6762b6c869f8 --- /dev/null +++ b/include/hw/vmapple/vmapple.h @@ -0,0 +1,17 @@ +/* + * Devices specific to the VMApple machine type + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_VMAPPLE_VMAPPLE_H +#define HW_VMAPPLE_VMAPPLE_H + +#define TYPE_APPLE_AES "apple-aes" + +#endif /* HW_VMAPPLE_VMAPPLE_H */ diff --git a/include/qemu/cutils.h b/include/qemu/cutils.h index 34a9b9b2204f..36c68ce86c52 100644 --- a/include/qemu/cutils.h +++ b/include/qemu/cutils.h @@ -302,4 +302,19 @@ GString *qemu_hexdump_line(GString *str, const void *buf, size_t len, void qemu_hexdump(FILE *fp, const char *prefix, const void *bufptr, size_t size); +/** + * qemu_hexdump_to_buffer: + * @buffer: output string buffer + * @buffer_size: amount of available space in buffer. Must be at least + * data_size*2+1. + * @data: input bytes + * @data_size: number of bytes in data + * + * Converts the @data_size bytes in @data into hex digit pairs, writing them to + * @buffer. Finally, a nul terminating character is written; @buffer therefore + * needs space for (data_size*2+1) chars. + */ +void qemu_hexdump_to_buffer(char *restrict buffer, size_t buffer_size, + const uint8_t *restrict data, size_t data_size); + #endif diff --git a/util/hexdump.c b/util/hexdump.c index ae0d4992dcf3..f29ffceb7466 100644 --- a/util/hexdump.c +++ b/util/hexdump.c @@ -15,6 +15,7 @@ #include "qemu/osdep.h" #include "qemu/cutils.h" +#include "qemu/host-utils.h" static inline char hexdump_nibble(unsigned x) { @@ -97,3 +98,20 @@ void qemu_hexdump(FILE *fp, const char *prefix, } } + +void qemu_hexdump_to_buffer(char *restrict buffer, size_t buffer_size, + const uint8_t *restrict data, size_t data_size) +{ + size_t i; + uint64_t required_buffer_size; + bool overflow = umul64_overflow(data_size, 2, &required_buffer_size); + overflow |= uadd64_overflow(required_buffer_size, 1, &required_buffer_size); + assert(!overflow && buffer_size >= required_buffer_size); + + for (i = 0; i < data_size; i++) { + uint8_t val = data[i]; + *(buffer++) = hexdump_nibble(val >> 4); + *(buffer++) = hexdump_nibble(val & 0xf); + } + *buffer = '\0'; +} From 0179bb3c48cfb915da64305b3cfbc110766d4078 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 14 Jun 2023 22:56:26 +0000 Subject: [PATCH 0437/1179] hw/vmapple/bdif: Introduce vmapple backdoor interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VMApple machine exposes AUX and ROOT block devices (as well as USB OTG emulation) via virtio-pci as well as a special, simple backdoor platform device. This patch implements this backdoor platform device to the best of my understanding. I left out any USB OTG parts; they're only needed for guest recovery and I don't understand the protocol yet. Signed-off-by: Alexander Graf Signed-off-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Tested-by: Akihiko Odaki Message-ID: <20241223221645.29911-11-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- hw/vmapple/Kconfig | 3 + hw/vmapple/bdif.c | 274 +++++++++++++++++++++++++++++++++++ hw/vmapple/meson.build | 1 + hw/vmapple/trace-events | 5 + include/hw/vmapple/vmapple.h | 2 + 5 files changed, 285 insertions(+) create mode 100644 hw/vmapple/bdif.c diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig index b1944d73129f..ff5f97c292e8 100644 --- a/hw/vmapple/Kconfig +++ b/hw/vmapple/Kconfig @@ -2,3 +2,6 @@ config VMAPPLE_AES bool + +config VMAPPLE_BDIF + bool diff --git a/hw/vmapple/bdif.c b/hw/vmapple/bdif.c new file mode 100644 index 000000000000..5827dd2aab8c --- /dev/null +++ b/hw/vmapple/bdif.c @@ -0,0 +1,274 @@ +/* + * VMApple Backdoor Interface + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "trace.h" +#include "hw/vmapple/vmapple.h" +#include "hw/sysbus.h" +#include "hw/block/block.h" +#include "qapi/error.h" +#include "system/block-backend.h" +#include "system/dma.h" + +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleBdifState, VMAPPLE_BDIF) + +struct VMAppleBdifState { + SysBusDevice parent_obj; + + BlockBackend *aux; + BlockBackend *root; + MemoryRegion mmio; +}; + +#define VMAPPLE_BDIF_SIZE 0x00200000 + +#define REG_DEVID_MASK 0xffff0000 +#define DEVID_ROOT 0x00000000 +#define DEVID_AUX 0x00010000 +#define DEVID_USB 0x00100000 + +#define REG_STATUS 0x0 +#define REG_STATUS_ACTIVE BIT(0) +#define REG_CFG 0x4 +#define REG_CFG_ACTIVE BIT(1) +#define REG_UNK1 0x8 +#define REG_BUSY 0x10 +#define REG_BUSY_READY BIT(0) +#define REG_UNK2 0x400 +#define REG_CMD 0x408 +#define REG_NEXT_DEVICE 0x420 +#define REG_UNK3 0x434 + +typedef struct VblkSector { + uint32_t pad; + uint32_t pad2; + uint32_t sector; + uint32_t pad3; +} VblkSector; + +typedef struct VblkReqCmd { + uint64_t addr; + uint32_t len; + uint32_t flags; +} VblkReqCmd; + +typedef struct VblkReq { + VblkReqCmd sector; + VblkReqCmd data; + VblkReqCmd retval; +} VblkReq; + +#define VBLK_DATA_FLAGS_READ 0x00030001 +#define VBLK_DATA_FLAGS_WRITE 0x00010001 + +#define VBLK_RET_SUCCESS 0 +#define VBLK_RET_FAILED 1 + +static uint64_t bdif_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t ret = -1; + uint64_t devid = offset & REG_DEVID_MASK; + + switch (offset & ~REG_DEVID_MASK) { + case REG_STATUS: + ret = REG_STATUS_ACTIVE; + break; + case REG_CFG: + ret = REG_CFG_ACTIVE; + break; + case REG_UNK1: + ret = 0x420; + break; + case REG_BUSY: + ret = REG_BUSY_READY; + break; + case REG_UNK2: + ret = 0x1; + break; + case REG_UNK3: + ret = 0x0; + break; + case REG_NEXT_DEVICE: + switch (devid) { + case DEVID_ROOT: + ret = 0x8000000; + break; + case DEVID_AUX: + ret = 0x10000; + break; + } + break; + } + + trace_bdif_read(offset, size, ret); + return ret; +} + +static void le2cpu_sector(VblkSector *sector) +{ + sector->sector = le32_to_cpu(sector->sector); +} + +static void le2cpu_reqcmd(VblkReqCmd *cmd) +{ + cmd->addr = le64_to_cpu(cmd->addr); + cmd->len = le32_to_cpu(cmd->len); + cmd->flags = le32_to_cpu(cmd->flags); +} + +static void le2cpu_req(VblkReq *req) +{ + le2cpu_reqcmd(&req->sector); + le2cpu_reqcmd(&req->data); + le2cpu_reqcmd(&req->retval); +} + +static void vblk_cmd(uint64_t devid, BlockBackend *blk, uint64_t gp_addr, + uint64_t static_off) +{ + VblkReq req; + VblkSector sector; + uint64_t off = 0; + g_autofree char *buf = NULL; + uint8_t ret = VBLK_RET_FAILED; + int r; + MemTxResult dma_result; + + dma_result = dma_memory_read(&address_space_memory, gp_addr, + &req, sizeof(req), MEMTXATTRS_UNSPECIFIED); + if (dma_result != MEMTX_OK) { + goto out; + } + + le2cpu_req(&req); + + if (req.sector.len != sizeof(sector)) { + goto out; + } + + /* Read the vblk command */ + dma_result = dma_memory_read(&address_space_memory, req.sector.addr, + §or, sizeof(sector), + MEMTXATTRS_UNSPECIFIED); + if (dma_result != MEMTX_OK) { + goto out; + } + le2cpu_sector(§or); + + off = sector.sector * 512ULL + static_off; + + /* Sanity check that we're not allocating bogus sizes */ + if (req.data.len > 128 * MiB) { + goto out; + } + + buf = g_malloc0(req.data.len); + switch (req.data.flags) { + case VBLK_DATA_FLAGS_READ: + r = blk_pread(blk, off, req.data.len, buf, 0); + trace_bdif_vblk_read(devid == DEVID_AUX ? "aux" : "root", + req.data.addr, off, req.data.len, r); + if (r < 0) { + goto out; + } + dma_result = dma_memory_write(&address_space_memory, req.data.addr, buf, + req.data.len, MEMTXATTRS_UNSPECIFIED); + if (dma_result == MEMTX_OK) { + ret = VBLK_RET_SUCCESS; + } + break; + case VBLK_DATA_FLAGS_WRITE: + /* Not needed, iBoot only reads */ + break; + default: + break; + } + +out: + dma_memory_write(&address_space_memory, req.retval.addr, &ret, 1, + MEMTXATTRS_UNSPECIFIED); +} + +static void bdif_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + VMAppleBdifState *s = opaque; + uint64_t devid = (offset & REG_DEVID_MASK); + + trace_bdif_write(offset, size, value); + + switch (offset & ~REG_DEVID_MASK) { + case REG_CMD: + switch (devid) { + case DEVID_ROOT: + vblk_cmd(devid, s->root, value, 0x0); + break; + case DEVID_AUX: + vblk_cmd(devid, s->aux, value, 0x0); + break; + } + break; + } +} + +static const MemoryRegionOps bdif_ops = { + .read = bdif_read, + .write = bdif_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + +static void bdif_init(Object *obj) +{ + VMAppleBdifState *s = VMAPPLE_BDIF(obj); + + memory_region_init_io(&s->mmio, obj, &bdif_ops, obj, + "VMApple Backdoor Interface", VMAPPLE_BDIF_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static const Property bdif_properties[] = { + DEFINE_PROP_DRIVE("aux", VMAppleBdifState, aux), + DEFINE_PROP_DRIVE("root", VMAppleBdifState, root), +}; + +static void bdif_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "VMApple Backdoor Interface"; + device_class_set_props(dc, bdif_properties); +} + +static const TypeInfo bdif_info = { + .name = TYPE_VMAPPLE_BDIF, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(VMAppleBdifState), + .instance_init = bdif_init, + .class_init = bdif_class_init, +}; + +static void bdif_register_types(void) +{ + type_register_static(&bdif_info); +} + +type_init(bdif_register_types) diff --git a/hw/vmapple/meson.build b/hw/vmapple/meson.build index a701d06a39de..e2aca6b7c2bf 100644 --- a/hw/vmapple/meson.build +++ b/hw/vmapple/meson.build @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-or-later system_ss.add(when: 'CONFIG_VMAPPLE_AES', if_true: files('aes.c')) +system_ss.add(when: 'CONFIG_VMAPPLE_BDIF', if_true: files('bdif.c')) diff --git a/hw/vmapple/trace-events b/hw/vmapple/trace-events index 188547a6ad83..93380ede1451 100644 --- a/hw/vmapple/trace-events +++ b/hw/vmapple/trace-events @@ -14,3 +14,8 @@ aes_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64 aes_2_read(uint64_t offset, uint64_t res) "offset=0x%"PRIx64" res=0x%"PRIx64 aes_2_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64 aes_dump_data(const char *desc, const char *hex) "%s%s" + +# bdif.c +bdif_read(uint64_t offset, uint32_t size, uint64_t value) "offset=0x%"PRIx64" size=0x%x value=0x%"PRIx64 +bdif_write(uint64_t offset, uint32_t size, uint64_t value) "offset=0x%"PRIx64" size=0x%x value=0x%"PRIx64 +bdif_vblk_read(const char *dev, uint64_t addr, uint64_t offset, uint32_t len, int r) "dev=%s addr=0x%"PRIx64" off=0x%"PRIx64" size=0x%x r=%d" diff --git a/include/hw/vmapple/vmapple.h b/include/hw/vmapple/vmapple.h index 6762b6c869f8..9090e9c5ac82 100644 --- a/include/hw/vmapple/vmapple.h +++ b/include/hw/vmapple/vmapple.h @@ -14,4 +14,6 @@ #define TYPE_APPLE_AES "apple-aes" +#define TYPE_VMAPPLE_BDIF "vmapple-bdif" + #endif /* HW_VMAPPLE_VMAPPLE_H */ From 33b54462067331c75fe180f809d50efe9659895a Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 14 Jun 2023 22:57:32 +0000 Subject: [PATCH 0438/1179] hw/vmapple/cfg: Introduce vmapple cfg region MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of device tree or other more standardized means, VMApple passes platform configuration to the first stage boot loader in a binary encoded format that resides at a dedicated RAM region in physical address space. This patch models this configuration space as a qdev device which we can then map at the fixed location in the address space. That way, we can influence and annotate all configuration fields easily. Signed-off-by: Alexander Graf Signed-off-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Tested-by: Akihiko Odaki Message-ID: <20241223221645.29911-12-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- hw/vmapple/Kconfig | 3 + hw/vmapple/cfg.c | 195 +++++++++++++++++++++++++++++++++++ hw/vmapple/meson.build | 1 + include/hw/vmapple/vmapple.h | 2 + 4 files changed, 201 insertions(+) create mode 100644 hw/vmapple/cfg.c diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig index ff5f97c292e8..f5898661a911 100644 --- a/hw/vmapple/Kconfig +++ b/hw/vmapple/Kconfig @@ -5,3 +5,6 @@ config VMAPPLE_AES config VMAPPLE_BDIF bool + +config VMAPPLE_CFG + bool diff --git a/hw/vmapple/cfg.c b/hw/vmapple/cfg.c new file mode 100644 index 000000000000..63414d801fe4 --- /dev/null +++ b/hw/vmapple/cfg.c @@ -0,0 +1,195 @@ +/* + * VMApple Configuration Region + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/vmapple/vmapple.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "net/net.h" + +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleCfgState, VMAPPLE_CFG) + +#define VMAPPLE_CFG_SIZE 0x00010000 + +typedef struct VMAppleCfg { + uint32_t version; /* 0x000 */ + uint32_t nr_cpus; /* 0x004 */ + uint32_t unk1; /* 0x008 */ + uint32_t unk2; /* 0x00c */ + uint32_t unk3; /* 0x010 */ + uint32_t unk4; /* 0x014 */ + uint64_t ecid; /* 0x018 */ + uint64_t ram_size; /* 0x020 */ + uint32_t run_installer1; /* 0x028 */ + uint32_t unk5; /* 0x02c */ + uint32_t unk6; /* 0x030 */ + uint32_t run_installer2; /* 0x034 */ + uint32_t rnd; /* 0x038 */ + uint32_t unk7; /* 0x03c */ + MACAddr mac_en0; /* 0x040 */ + uint8_t pad1[2]; + MACAddr mac_en1; /* 0x048 */ + uint8_t pad2[2]; + MACAddr mac_wifi0; /* 0x050 */ + uint8_t pad3[2]; + MACAddr mac_bt0; /* 0x058 */ + uint8_t pad4[2]; + uint8_t reserved[0xa0]; /* 0x060 */ + uint32_t cpu_ids[0x80]; /* 0x100 */ + uint8_t scratch[0x200]; /* 0x180 */ + char serial[32]; /* 0x380 */ + char unk8[32]; /* 0x3a0 */ + char model[32]; /* 0x3c0 */ + uint8_t unk9[32]; /* 0x3e0 */ + uint32_t unk10; /* 0x400 */ + char soc_name[32]; /* 0x404 */ +} VMAppleCfg; + +struct VMAppleCfgState { + SysBusDevice parent_obj; + VMAppleCfg cfg; + + MemoryRegion mem; + char *serial; + char *model; + char *soc_name; +}; + +static void vmapple_cfg_reset(Object *obj, ResetType type) +{ + VMAppleCfgState *s = VMAPPLE_CFG(obj); + VMAppleCfg *cfg; + + cfg = memory_region_get_ram_ptr(&s->mem); + memset(cfg, 0, VMAPPLE_CFG_SIZE); + *cfg = s->cfg; +} + +static bool set_fixlen_property_or_error(char *restrict dst, + const char *restrict src, + size_t dst_size, Error **errp, + const char *property_name) +{ + ERRP_GUARD(); + size_t len; + + len = g_strlcpy(dst, src, dst_size); + if (len < dst_size) { /* len does not count nul terminator */ + return true; + } + + error_setg(errp, "Provided value too long for property '%s'", property_name); + error_append_hint(errp, "length (%zu) exceeds maximum of %zu\n", + len, dst_size - 1); + return false; +} + +#define set_fixlen_property_or_return(dst_array, src, errp, property_name) \ + do { \ + if (!set_fixlen_property_or_error((dst_array), (src), \ + ARRAY_SIZE(dst_array), \ + (errp), (property_name))) { \ + return; \ + } \ + } while (0) + +static void vmapple_cfg_realize(DeviceState *dev, Error **errp) +{ + VMAppleCfgState *s = VMAPPLE_CFG(dev); + uint32_t i; + + if (!s->serial) { + s->serial = g_strdup("1234"); + } + if (!s->model) { + s->model = g_strdup("VM0001"); + } + if (!s->soc_name) { + s->soc_name = g_strdup("Apple M1 (Virtual)"); + } + + set_fixlen_property_or_return(s->cfg.serial, s->serial, errp, "serial"); + set_fixlen_property_or_return(s->cfg.model, s->model, errp, "model"); + set_fixlen_property_or_return(s->cfg.soc_name, s->soc_name, errp, "soc_name"); + set_fixlen_property_or_return(s->cfg.unk8, "D/A", errp, "unk8"); + s->cfg.version = 2; + s->cfg.unk1 = 1; + s->cfg.unk2 = 1; + s->cfg.unk3 = 0x20; + s->cfg.unk4 = 0; + s->cfg.unk5 = 1; + s->cfg.unk6 = 1; + s->cfg.unk7 = 0; + s->cfg.unk10 = 1; + + if (s->cfg.nr_cpus > ARRAY_SIZE(s->cfg.cpu_ids)) { + error_setg(errp, + "Failed to create %u CPUs, vmapple machine supports %zu max", + s->cfg.nr_cpus, ARRAY_SIZE(s->cfg.cpu_ids)); + return; + } + for (i = 0; i < s->cfg.nr_cpus; i++) { + s->cfg.cpu_ids[i] = i; + } +} + +static void vmapple_cfg_init(Object *obj) +{ + VMAppleCfgState *s = VMAPPLE_CFG(obj); + + memory_region_init_ram(&s->mem, obj, "VMApple Config", VMAPPLE_CFG_SIZE, + &error_fatal); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mem); +} + +static const Property vmapple_cfg_properties[] = { + DEFINE_PROP_UINT32("nr-cpus", VMAppleCfgState, cfg.nr_cpus, 1), + DEFINE_PROP_UINT64("ecid", VMAppleCfgState, cfg.ecid, 0), + DEFINE_PROP_UINT64("ram-size", VMAppleCfgState, cfg.ram_size, 0), + DEFINE_PROP_UINT32("run_installer1", VMAppleCfgState, cfg.run_installer1, 0), + DEFINE_PROP_UINT32("run_installer2", VMAppleCfgState, cfg.run_installer2, 0), + DEFINE_PROP_UINT32("rnd", VMAppleCfgState, cfg.rnd, 0), + DEFINE_PROP_MACADDR("mac-en0", VMAppleCfgState, cfg.mac_en0), + DEFINE_PROP_MACADDR("mac-en1", VMAppleCfgState, cfg.mac_en1), + DEFINE_PROP_MACADDR("mac-wifi0", VMAppleCfgState, cfg.mac_wifi0), + DEFINE_PROP_MACADDR("mac-bt0", VMAppleCfgState, cfg.mac_bt0), + DEFINE_PROP_STRING("serial", VMAppleCfgState, serial), + DEFINE_PROP_STRING("model", VMAppleCfgState, model), + DEFINE_PROP_STRING("soc_name", VMAppleCfgState, soc_name), +}; + +static void vmapple_cfg_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = vmapple_cfg_realize; + dc->desc = "VMApple Configuration Region"; + device_class_set_props(dc, vmapple_cfg_properties); + rc->phases.hold = vmapple_cfg_reset; +} + +static const TypeInfo vmapple_cfg_info = { + .name = TYPE_VMAPPLE_CFG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(VMAppleCfgState), + .instance_init = vmapple_cfg_init, + .class_init = vmapple_cfg_class_init, +}; + +static void vmapple_cfg_register_types(void) +{ + type_register_static(&vmapple_cfg_info); +} + +type_init(vmapple_cfg_register_types) diff --git a/hw/vmapple/meson.build b/hw/vmapple/meson.build index e2aca6b7c2bf..9e881c7b555f 100644 --- a/hw/vmapple/meson.build +++ b/hw/vmapple/meson.build @@ -2,3 +2,4 @@ system_ss.add(when: 'CONFIG_VMAPPLE_AES', if_true: files('aes.c')) system_ss.add(when: 'CONFIG_VMAPPLE_BDIF', if_true: files('bdif.c')) +system_ss.add(when: 'CONFIG_VMAPPLE_CFG', if_true: files('cfg.c')) diff --git a/include/hw/vmapple/vmapple.h b/include/hw/vmapple/vmapple.h index 9090e9c5ac82..3bba59f5ec77 100644 --- a/include/hw/vmapple/vmapple.h +++ b/include/hw/vmapple/vmapple.h @@ -16,4 +16,6 @@ #define TYPE_VMAPPLE_BDIF "vmapple-bdif" +#define TYPE_VMAPPLE_CFG "vmapple-cfg" + #endif /* HW_VMAPPLE_VMAPPLE_H */ From ee241d79bbf45fe7354dde51b0ca2574824205d4 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 14 Jun 2023 22:56:22 +0000 Subject: [PATCH 0439/1179] hw/vmapple/virtio-blk: Add support for apple virtio-blk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple has its own virtio-blk PCI device ID where it deviates from the official virtio-pci spec slightly: It puts a new "apple type" field at a static offset in config space and introduces a new barrier command. This patch first creates a mechanism for virtio-blk downstream classes to handle unknown commands. It then creates such a downstream class and a new vmapple-virtio-blk-pci class which support the additional apple type config identifier as well as the barrier command. The 'aux' or 'root' device type are selected using the 'variant' property. Signed-off-by: Alexander Graf Signed-off-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Tested-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Message-ID: <20241223221645.29911-13-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/virtio-blk.c | 17 ++- hw/core/qdev-properties-system.c | 9 ++ hw/vmapple/Kconfig | 3 + hw/vmapple/meson.build | 1 + hw/vmapple/virtio-blk.c | 204 ++++++++++++++++++++++++++++ include/hw/pci/pci_ids.h | 1 + include/hw/qdev-properties-system.h | 6 + include/hw/virtio/virtio-blk.h | 11 +- include/hw/vmapple/vmapple.h | 2 + qapi/virtio.json | 14 ++ 10 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 hw/vmapple/virtio-blk.c diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index a1829e3abdf7..5135b4d8f1e2 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -50,7 +50,7 @@ static void virtio_blk_init_request(VirtIOBlock *s, VirtQueue *vq, req->mr_next = NULL; } -static void virtio_blk_req_complete(VirtIOBlockReq *req, unsigned char status) +void virtio_blk_req_complete(VirtIOBlockReq *req, unsigned char status) { VirtIOBlock *s = req->dev; VirtIODevice *vdev = VIRTIO_DEVICE(s); @@ -961,8 +961,18 @@ static int virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb) break; } default: - virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); - g_free(req); + { + /* + * Give subclasses a chance to handle unknown requests. This way the + * class lookup is not in the hot path. + */ + VirtIOBlkClass *vbk = VIRTIO_BLK_GET_CLASS(s); + if (!vbk->handle_unknown_request || + !vbk->handle_unknown_request(req, mrb, type)) { + virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); + g_free(req); + } + } } return 0; } @@ -2029,6 +2039,7 @@ static const TypeInfo virtio_blk_info = { .instance_size = sizeof(VirtIOBlock), .instance_init = virtio_blk_instance_init, .class_init = virtio_blk_class_init, + .class_size = sizeof(VirtIOBlkClass), }; static void virtio_register_types(void) diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index 89f954f569ef..a91551a5ee89 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -1294,3 +1294,12 @@ const PropertyInfo qdev_prop_endian_mode = { .set = qdev_propinfo_set_enum, .set_default_value = qdev_propinfo_set_default_value_enum, }; + +const PropertyInfo qdev_prop_vmapple_virtio_blk_variant = { + .name = "VMAppleVirtioBlkVariant", + .description = "unspecified/root/aux", + .enum_table = &VMAppleVirtioBlkVariant_lookup, + .get = qdev_propinfo_get_enum, + .set = qdev_propinfo_set_enum, + .set_default_value = qdev_propinfo_set_default_value_enum, +}; diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig index f5898661a911..5586fd460b7b 100644 --- a/hw/vmapple/Kconfig +++ b/hw/vmapple/Kconfig @@ -8,3 +8,6 @@ config VMAPPLE_BDIF config VMAPPLE_CFG bool + +config VMAPPLE_VIRTIO_BLK + bool diff --git a/hw/vmapple/meson.build b/hw/vmapple/meson.build index 9e881c7b555f..3553ec615189 100644 --- a/hw/vmapple/meson.build +++ b/hw/vmapple/meson.build @@ -3,3 +3,4 @@ system_ss.add(when: 'CONFIG_VMAPPLE_AES', if_true: files('aes.c')) system_ss.add(when: 'CONFIG_VMAPPLE_BDIF', if_true: files('bdif.c')) system_ss.add(when: 'CONFIG_VMAPPLE_CFG', if_true: files('cfg.c')) +system_ss.add(when: 'CONFIG_VMAPPLE_VIRTIO_BLK', if_true: files('virtio-blk.c')) diff --git a/hw/vmapple/virtio-blk.c b/hw/vmapple/virtio-blk.c new file mode 100644 index 000000000000..aa3f18c47db8 --- /dev/null +++ b/hw/vmapple/virtio-blk.c @@ -0,0 +1,204 @@ +/* + * VMApple specific VirtIO Block implementation + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * VMApple uses almost standard VirtIO Block, but with a few key differences: + * + * - Different PCI device/vendor ID + * - An additional "type" identifier to differentiate AUX and Root volumes + * - An additional BARRIER command + */ + +#include "qemu/osdep.h" +#include "hw/vmapple/vmapple.h" +#include "hw/virtio/virtio-blk.h" +#include "hw/virtio/virtio-pci.h" +#include "qemu/bswap.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" + +#define TYPE_VMAPPLE_VIRTIO_BLK "vmapple-virtio-blk" +OBJECT_DECLARE_TYPE(VMAppleVirtIOBlk, VMAppleVirtIOBlkClass, VMAPPLE_VIRTIO_BLK) + +typedef struct VMAppleVirtIOBlkClass { + VirtIOBlkClass parent; + + void (*get_config)(VirtIODevice *vdev, uint8_t *config); +} VMAppleVirtIOBlkClass; + +typedef struct VMAppleVirtIOBlk { + VirtIOBlock parent_obj; + + uint32_t apple_type; +} VMAppleVirtIOBlk; + +/* + * vmapple-virtio-blk-pci: This extends VirtioPCIProxy. + */ +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleVirtIOBlkPCI, VMAPPLE_VIRTIO_BLK_PCI) + +#define VIRTIO_BLK_T_APPLE_BARRIER 0x10000 + +static bool vmapple_virtio_blk_handle_unknown_request(VirtIOBlockReq *req, + MultiReqBuffer *mrb, + uint32_t type) +{ + switch (type) { + case VIRTIO_BLK_T_APPLE_BARRIER: + qemu_log_mask(LOG_UNIMP, "%s: Barrier requests are currently no-ops\n", + __func__); + virtio_blk_req_complete(req, VIRTIO_BLK_S_OK); + g_free(req); + return true; + default: + return false; + } +} + +/* + * VMApple virtio-blk uses the same config format as normal virtio, with one + * exception: It adds an "apple type" specififer at the same location that + * the spec reserves for max_secure_erase_sectors. Let's hook into the + * get_config code path here, run it as usual and then patch in the apple type. + */ +static void vmapple_virtio_blk_get_config(VirtIODevice *vdev, uint8_t *config) +{ + VMAppleVirtIOBlk *dev = VMAPPLE_VIRTIO_BLK(vdev); + VMAppleVirtIOBlkClass *vvbk = VMAPPLE_VIRTIO_BLK_GET_CLASS(dev); + struct virtio_blk_config *blkcfg = (struct virtio_blk_config *)config; + + vvbk->get_config(vdev, config); + + g_assert(dev->parent_obj.config_size >= endof(struct virtio_blk_config, zoned)); + + /* Apple abuses the field for max_secure_erase_sectors as type id */ + stl_he_p(&blkcfg->max_secure_erase_sectors, dev->apple_type); +} + +static void vmapple_virtio_blk_class_init(ObjectClass *klass, void *data) +{ + VirtIOBlkClass *vbk = VIRTIO_BLK_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + VMAppleVirtIOBlkClass *vvbk = VMAPPLE_VIRTIO_BLK_CLASS(klass); + + vbk->handle_unknown_request = vmapple_virtio_blk_handle_unknown_request; + vvbk->get_config = vdc->get_config; + vdc->get_config = vmapple_virtio_blk_get_config; +} + +static const TypeInfo vmapple_virtio_blk_info = { + .name = TYPE_VMAPPLE_VIRTIO_BLK, + .parent = TYPE_VIRTIO_BLK, + .instance_size = sizeof(VMAppleVirtIOBlk), + .class_size = sizeof(VMAppleVirtIOBlkClass), + .class_init = vmapple_virtio_blk_class_init, +}; + +/* PCI Devices */ + +struct VMAppleVirtIOBlkPCI { + VirtIOPCIProxy parent_obj; + + VMAppleVirtIOBlk vdev; + VMAppleVirtioBlkVariant variant; +}; + +static const Property vmapple_virtio_blk_pci_properties[] = { + DEFINE_PROP_UINT32("class", VirtIOPCIProxy, class_code, 0), + DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, + VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true), + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, + DEV_NVECTORS_UNSPECIFIED), + DEFINE_PROP_VMAPPLE_VIRTIO_BLK_VARIANT("variant", VMAppleVirtIOBlkPCI, variant, + VM_APPLE_VIRTIO_BLK_VARIANT_UNSPECIFIED), +}; + +static void vmapple_virtio_blk_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) +{ + ERRP_GUARD(); + VMAppleVirtIOBlkPCI *dev = VMAPPLE_VIRTIO_BLK_PCI(vpci_dev); + DeviceState *vdev = DEVICE(&dev->vdev); + VirtIOBlkConf *conf = &dev->vdev.parent_obj.conf; + + if (dev->variant == VM_APPLE_VIRTIO_BLK_VARIANT_UNSPECIFIED) { + error_setg(errp, "vmapple virtio block device variant unspecified"); + error_append_hint(errp, + "Variant property must be set to 'aux' or 'root'.\n" + "Use a regular virtio-blk-pci device instead when " + "neither is applicaple.\n"); + return; + } + + if (conf->num_queues == VIRTIO_BLK_AUTO_NUM_QUEUES) { + conf->num_queues = virtio_pci_optimal_num_queues(0); + } + + if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) { + vpci_dev->nvectors = conf->num_queues + 1; + } + + /* + * We don't support zones, but we need the additional config space size. + * Let's just expose the feature so the rest of the virtio-blk logic + * allocates enough space for us. The guest will ignore zones anyway. + */ + virtio_add_feature(&dev->vdev.parent_obj.host_features, VIRTIO_BLK_F_ZONED); + /* Propagate the apple type down to the virtio-blk device */ + dev->vdev.apple_type = dev->variant; + /* and spawn the virtio-blk device */ + qdev_realize(vdev, BUS(&vpci_dev->bus), errp); + + /* + * The virtio-pci machinery adjusts its vendor/device ID based on whether + * we support modern or legacy virtio. Let's patch it back to the Apple + * identifiers here. + */ + pci_config_set_vendor_id(vpci_dev->pci_dev.config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(vpci_dev->pci_dev.config, + PCI_DEVICE_ID_APPLE_VIRTIO_BLK); +} + +static void vmapple_virtio_blk_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + device_class_set_props(dc, vmapple_virtio_blk_pci_properties); + k->realize = vmapple_virtio_blk_pci_realize; + pcidev_k->vendor_id = PCI_VENDOR_ID_APPLE; + pcidev_k->device_id = PCI_DEVICE_ID_APPLE_VIRTIO_BLK; + pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; + pcidev_k->class_id = PCI_CLASS_STORAGE_SCSI; +} + +static void vmapple_virtio_blk_pci_instance_init(Object *obj) +{ + VMAppleVirtIOBlkPCI *dev = VMAPPLE_VIRTIO_BLK_PCI(obj); + + virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), + TYPE_VMAPPLE_VIRTIO_BLK); +} + +static const VirtioPCIDeviceTypeInfo vmapple_virtio_blk_pci_info = { + .generic_name = TYPE_VMAPPLE_VIRTIO_BLK_PCI, + .instance_size = sizeof(VMAppleVirtIOBlkPCI), + .instance_init = vmapple_virtio_blk_pci_instance_init, + .class_init = vmapple_virtio_blk_pci_class_init, +}; + +static void vmapple_virtio_blk_register_types(void) +{ + type_register_static(&vmapple_virtio_blk_info); + virtio_pci_types_register(&vmapple_virtio_blk_pci_info); +} + +type_init(vmapple_virtio_blk_register_types) diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h index f1a53fea8d62..33e2898be957 100644 --- a/include/hw/pci/pci_ids.h +++ b/include/hw/pci/pci_ids.h @@ -191,6 +191,7 @@ #define PCI_DEVICE_ID_APPLE_UNI_N_AGP 0x0020 #define PCI_DEVICE_ID_APPLE_U3_AGP 0x004b #define PCI_DEVICE_ID_APPLE_UNI_N_GMAC 0x0021 +#define PCI_DEVICE_ID_APPLE_VIRTIO_BLK 0x1a00 #define PCI_VENDOR_ID_SUN 0x108e #define PCI_DEVICE_ID_SUN_EBUS 0x1000 diff --git a/include/hw/qdev-properties-system.h b/include/hw/qdev-properties-system.h index ead4dfc2f028..b921392c5256 100644 --- a/include/hw/qdev-properties-system.h +++ b/include/hw/qdev-properties-system.h @@ -31,6 +31,7 @@ extern const PropertyInfo qdev_prop_pcie_link_width; extern const PropertyInfo qdev_prop_cpus390entitlement; extern const PropertyInfo qdev_prop_iothread_vq_mapping_list; extern const PropertyInfo qdev_prop_endian_mode; +extern const PropertyInfo qdev_prop_vmapple_virtio_blk_variant; #define DEFINE_PROP_PCI_DEVFN(_n, _s, _f, _d) \ DEFINE_PROP_SIGNED(_n, _s, _f, _d, qdev_prop_pci_devfn, int32_t) @@ -104,4 +105,9 @@ extern const PropertyInfo qdev_prop_endian_mode; #define DEFINE_PROP_ENDIAN_NODEFAULT(_name, _state, _field) \ DEFINE_PROP_ENDIAN(_name, _state, _field, ENDIAN_MODE_UNSPECIFIED) +#define DEFINE_PROP_VMAPPLE_VIRTIO_BLK_VARIANT(_name, _state, _fld, _default) \ + DEFINE_PROP_UNSIGNED(_name, _state, _fld, _default, \ + qdev_prop_vmapple_virtio_blk_variant, \ + VMAppleVirtioBlkVariant) + #endif diff --git a/include/hw/virtio/virtio-blk.h b/include/hw/virtio/virtio-blk.h index 8a16218c4098..3d8dee7ec158 100644 --- a/include/hw/virtio/virtio-blk.h +++ b/include/hw/virtio/virtio-blk.h @@ -24,7 +24,7 @@ #include "qapi/qapi-types-virtio.h" #define TYPE_VIRTIO_BLK "virtio-blk-device" -OBJECT_DECLARE_SIMPLE_TYPE(VirtIOBlock, VIRTIO_BLK) +OBJECT_DECLARE_TYPE(VirtIOBlock, VirtIOBlkClass, VIRTIO_BLK) /* This is the last element of the write scatter-gather list */ struct virtio_blk_inhdr @@ -100,6 +100,15 @@ typedef struct MultiReqBuffer { bool is_write; } MultiReqBuffer; +typedef struct VirtIOBlkClass { + /*< private >*/ + VirtioDeviceClass parent; + /*< public >*/ + bool (*handle_unknown_request)(VirtIOBlockReq *req, MultiReqBuffer *mrb, + uint32_t type); +} VirtIOBlkClass; + void virtio_blk_handle_vq(VirtIOBlock *s, VirtQueue *vq); +void virtio_blk_req_complete(VirtIOBlockReq *req, unsigned char status); #endif diff --git a/include/hw/vmapple/vmapple.h b/include/hw/vmapple/vmapple.h index 3bba59f5ec77..9c1ad1bd8c3c 100644 --- a/include/hw/vmapple/vmapple.h +++ b/include/hw/vmapple/vmapple.h @@ -18,4 +18,6 @@ #define TYPE_VMAPPLE_CFG "vmapple-cfg" +#define TYPE_VMAPPLE_VIRTIO_BLK_PCI "vmapple-virtio-blk-pci" + #endif /* HW_VMAPPLE_VMAPPLE_H */ diff --git a/qapi/virtio.json b/qapi/virtio.json index 2529c2d8b204..d351d2166ef1 100644 --- a/qapi/virtio.json +++ b/qapi/virtio.json @@ -992,3 +992,17 @@ ## { 'enum': 'GranuleMode', 'data': [ '4k', '8k', '16k', '64k', 'host' ] } + +## +# @VMAppleVirtioBlkVariant: +# +# @unspecified: The default, not a valid setting. +# +# @root: Block device holding the root volume +# +# @aux: Block device holding auxiliary data required for boot +# +# Since: 9.2 +## +{ 'enum': 'VMAppleVirtioBlkVariant', + 'data': [ 'unspecified', 'root', 'aux' ] } From 9422a5acf2125e9a67a8a14538306f123bcc6098 Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Sun, 8 Dec 2024 20:16:44 +0100 Subject: [PATCH 0440/1179] hw/usb/hcd-xhci-pci: Adds property for disabling mapping in IRQ mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change addresses an edge case that trips up macOS guest drivers for PCI based XHCI controllers. The guest driver would attempt to schedule events to XHCI event rings 1 and 2 even when using PCI pin-based interrupts. Interrupts would therefore be dropped, and events only handled on timeout. So, in addition to disabling interrupter mapping if numintrs is 1, a callback is added to xhci to check whether interrupter mapping should be enabled. The PCI XHCI device type now provides an implementation of this callback if the new "conditional-intr-mapping" property is enabled. (default: disabled) When enabled, interrupter mapping is only enabled when MSI-X or MSI is active. This means that when using pin-based interrupts, events are only submitted to interrupter 0 regardless of selected target. This allows the macOS guest drivers to work with the device in those configurations. Signed-off-by: Phil Dennis-Jordan Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2705 Message-ID: <20241227121336.25838-6-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- hw/usb/hcd-xhci-pci.c | 24 ++++++++++++++++++++++++ hw/usb/hcd-xhci-pci.h | 1 + hw/usb/hcd-xhci.c | 3 ++- hw/usb/hcd-xhci.h | 5 +++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/hw/usb/hcd-xhci-pci.c b/hw/usb/hcd-xhci-pci.c index 49642aab58ec..d908eb787d34 100644 --- a/hw/usb/hcd-xhci-pci.c +++ b/hw/usb/hcd-xhci-pci.c @@ -82,6 +82,21 @@ static bool xhci_pci_intr_raise(XHCIState *xhci, int n, bool level) return false; } +static bool xhci_pci_intr_mapping_conditional(XHCIState *xhci) +{ + XHCIPciState *s = container_of(xhci, XHCIPciState, xhci); + PCIDevice *pci_dev = PCI_DEVICE(s); + + /* + * Implementation of the "conditional-intr-mapping" property, which only + * enables interrupter mapping if MSI or MSI-X is available and active. + * Forces all events onto interrupter/event ring 0 in pin-based IRQ mode. + * Provides compatibility with macOS guests on machine types where MSI(-X) + * is not available. + */ + return msix_enabled(pci_dev) || msi_enabled(pci_dev); +} + static void xhci_pci_reset(DeviceState *dev) { XHCIPciState *s = XHCI_PCI(dev); @@ -119,6 +134,9 @@ static void usb_xhci_pci_realize(struct PCIDevice *dev, Error **errp) object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL); s->xhci.intr_update = xhci_pci_intr_update; s->xhci.intr_raise = xhci_pci_intr_raise; + if (s->conditional_intr_mapping) { + s->xhci.intr_mapping_supported = xhci_pci_intr_mapping_conditional; + } if (!qdev_realize(DEVICE(&s->xhci), NULL, errp)) { return; } @@ -201,6 +219,8 @@ static void xhci_instance_init(Object *obj) static const Property xhci_pci_properties[] = { DEFINE_PROP_ON_OFF_AUTO("msi", XHCIPciState, msi, ON_OFF_AUTO_AUTO), DEFINE_PROP_ON_OFF_AUTO("msix", XHCIPciState, msix, ON_OFF_AUTO_AUTO), + DEFINE_PROP_BOOL("conditional-intr-mapping", XHCIPciState, + conditional_intr_mapping, false), }; static void xhci_class_init(ObjectClass *klass, void *data) @@ -215,6 +235,10 @@ static void xhci_class_init(ObjectClass *klass, void *data) k->exit = usb_xhci_pci_exit; k->class_id = PCI_CLASS_SERIAL_USB; device_class_set_props(dc, xhci_pci_properties); + object_class_property_set_description(klass, "conditional-intr-mapping", + "When true, disables interrupter mapping for pin-based IRQ mode. " + "Intended to be used with guest drivers with questionable behaviour, " + "such as macOS's."); } static const TypeInfo xhci_pci_info = { diff --git a/hw/usb/hcd-xhci-pci.h b/hw/usb/hcd-xhci-pci.h index 08f70ce97cc6..5b61ae845553 100644 --- a/hw/usb/hcd-xhci-pci.h +++ b/hw/usb/hcd-xhci-pci.h @@ -40,6 +40,7 @@ typedef struct XHCIPciState { XHCIState xhci; OnOffAuto msi; OnOffAuto msix; + bool conditional_intr_mapping; } XHCIPciState; #endif diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index 00d5bc377927..64c3a23b9b79 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -644,7 +644,8 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v) dma_addr_t erdp; unsigned int dp_idx; - if (xhci->numintrs == 1) { + if (xhci->numintrs == 1 || + (xhci->intr_mapping_supported && !xhci->intr_mapping_supported(xhci))) { v = 0; } diff --git a/hw/usb/hcd-xhci.h b/hw/usb/hcd-xhci.h index 9609b8351413..9c3974f1489c 100644 --- a/hw/usb/hcd-xhci.h +++ b/hw/usb/hcd-xhci.h @@ -193,6 +193,11 @@ typedef struct XHCIState { uint32_t max_pstreams_mask; void (*intr_update)(XHCIState *s, int n, bool enable); bool (*intr_raise)(XHCIState *s, int n, bool level); + /* + * Callback for special-casing interrupter mapping support. NULL for most + * implementations, for defaulting to enabled mapping unless numintrs == 1. + */ + bool (*intr_mapping_supported)(XHCIState *s); DeviceState *hostOpaque; /* Operational Registers */ From 59f4d65584bd3372070e2484876436c8d02505e4 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 14 Jun 2023 22:57:34 +0000 Subject: [PATCH 0441/1179] hw/vmapple/vmapple: Add vmapple machine type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple defines a new "vmapple" machine type as part of its proprietary macOS Virtualization.Framework vmm. This machine type is similar to the virt one, but with subtle differences in base devices, a few special vmapple device additions and a vastly different boot chain. This patch reimplements this machine type in QEMU. To use it, you have to have a readily installed version of macOS for VMApple, run on macOS with -accel hvf, pass the Virtualization.Framework boot rom (AVPBooter) in via -bios, pass the aux and root volume as pflash and pass aux and root volume as virtio drives. In addition, you also need to find the machine UUID and pass that as -M vmapple,uuid= parameter: $ qemu-system-aarch64 -accel hvf -M vmapple,uuid=0x1234 -m 4G \ -bios /System/Library/Frameworks/Virtualization.framework/Versions/A/Resources/AVPBooter.vmapple2.bin -drive file=aux,if=pflash,format=raw \ -drive file=root,if=pflash,format=raw \ -drive file=aux,if=none,id=aux,format=raw \ -device vmapple-virtio-blk-pci,variant=aux,drive=aux \ -drive file=root,if=none,id=root,format=raw \ -device vmapple-virtio-blk-pci,variant=root,drive=root With all these in place, you should be able to see macOS booting successfully. Known issues: - Currently only macOS 12 guests are supported. The boot process for 13+ will need further investigation and adjustment. Signed-off-by: Alexander Graf Co-authored-by: Phil Dennis-Jordan Signed-off-by: Phil Dennis-Jordan Reviewed-by: Akihiko Odaki Tested-by: Akihiko Odaki Message-ID: <20241223221645.29911-15-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- MAINTAINERS | 1 + contrib/vmapple/uuid.sh | 12 + docs/system/arm/vmapple.rst | 65 ++++ docs/system/target-arm.rst | 1 + hw/vmapple/Kconfig | 21 ++ hw/vmapple/meson.build | 1 + hw/vmapple/vmapple.c | 618 ++++++++++++++++++++++++++++++++++++ 7 files changed, 719 insertions(+) create mode 100755 contrib/vmapple/uuid.sh create mode 100644 docs/system/arm/vmapple.rst create mode 100644 hw/vmapple/vmapple.c diff --git a/MAINTAINERS b/MAINTAINERS index 0e1603609183..d1e69539de04 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2826,6 +2826,7 @@ M: Phil Dennis-Jordan S: Maintained F: hw/vmapple/* F: include/hw/vmapple/* +F: docs/system/arm/vmapple.rst Subsystems ---------- diff --git a/contrib/vmapple/uuid.sh b/contrib/vmapple/uuid.sh new file mode 100755 index 000000000000..f5637221d236 --- /dev/null +++ b/contrib/vmapple/uuid.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# +# Used for converting a guest provisioned using Virtualization.framework +# for use with the QEMU 'vmapple' aarch64 machine type. +# +# Extracts the Machine UUID from Virtualization.framework VM JSON file. +# (as produced by 'macosvm', passed as command line argument) +# +# SPDX-License-Identifier: GPL-2.0-or-later + +plutil -extract machineId raw "$1" | base64 -d | plutil -extract ECID raw - + diff --git a/docs/system/arm/vmapple.rst b/docs/system/arm/vmapple.rst new file mode 100644 index 000000000000..35c329ea5a81 --- /dev/null +++ b/docs/system/arm/vmapple.rst @@ -0,0 +1,65 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +VMApple machine emulation +======================================================================================== + +VMApple is the device model that the macOS built-in hypervisor called "Virtualization.framework" +exposes to Apple Silicon macOS guests. The "vmapple" machine model in QEMU implements the same +device model, but does not use any code from Virtualization.Framework. + +Prerequisites +------------- + +To run the vmapple machine model, you need to + + * Run on Apple Silicon + * Run on macOS 12.0 or above + * Have an already installed copy of a Virtualization.Framework macOS 12 virtual + machine. Note that newer versions than 12.x are currently NOT supported on + the guest side. I will assume that you installed it using the + `macosvm `__ CLI. + +First, we need to extract the UUID from the virtual machine that you installed. You can do this +by running the shell script in contrib/vmapple/uuid.sh on the macosvm.json file. + +.. code-block:: bash + :caption: uuid.sh script to extract the UUID from a macosvm.json file + + $ contrib/vmapple/uuid.sh "path/to/macosvm.json" + +Now we also need to trim the aux partition. It contains metadata that we can just discard: + +.. code-block:: bash + :caption: Command to trim the aux file + + $ dd if="aux.img" of="aux.img.trimmed" bs=$(( 0x4000 )) skip=1 + +How to run +---------- + +Then, we can launch QEMU with the Virtualization.Framework pre-boot environment and the readily +installed target disk images. I recommend to port forward the VM's ssh and vnc ports to the host +to get better interactive access into the target system: + +.. code-block:: bash + :caption: Example execution command line + + $ UUID="$(contrib/vmapple/uuid.sh 'macosvm.json')" + $ AVPBOOTER="/System/Library/Frameworks/Virtualization.framework/Resources/AVPBooter.vmapple2.bin" + $ AUX="aux.img.trimmed" + $ DISK="disk.img" + $ qemu-system-aarch64 \ + -serial mon:stdio \ + -m 4G \ + -accel hvf \ + -M vmapple,uuid="$UUID" \ + -bios "$AVPBOOTER" \ + -drive file="$AUX",if=pflash,format=raw \ + -drive file="$DISK",if=pflash,format=raw \ + -drive file="$AUX",if=none,id=aux,format=raw \ + -drive file="$DISK",if=none,id=root,format=raw \ + -device vmapple-virtio-blk-pci,variant=aux,drive=aux \ + -device vmapple-virtio-blk-pci,variant=root,drive=root \ + -netdev user,id=net0,ipv6=off,hostfwd=tcp::2222-:22,hostfwd=tcp::5901-:5900 \ + -device virtio-net-pci,netdev=net0 + diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index a43ec8f10e03..b96a05a92064 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -103,6 +103,7 @@ Board-specific documentation arm/stellaris arm/stm32 arm/virt + arm/vmapple arm/xenpvh arm/xlnx-versal-virt arm/xlnx-zynq diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig index 5586fd460b7b..2382b2976722 100644 --- a/hw/vmapple/Kconfig +++ b/hw/vmapple/Kconfig @@ -11,3 +11,24 @@ config VMAPPLE_CFG config VMAPPLE_VIRTIO_BLK bool + +config VMAPPLE + bool + depends on ARM + depends on HVF + default y if ARM + imply PCI_DEVICES + select ARM_GICV3 + select PLATFORM_BUS + select PCI_EXPRESS + select PCI_EXPRESS_GENERIC_BRIDGE + select PL011 # UART + select PL031 # RTC + select PL061 # GPIO + select GPIO_PWR + select PVPANIC_MMIO + select VMAPPLE_AES + select VMAPPLE_BDIF + select VMAPPLE_CFG + select MAC_PVG_MMIO + select VMAPPLE_VIRTIO_BLK diff --git a/hw/vmapple/meson.build b/hw/vmapple/meson.build index 3553ec615189..23bc4c999e38 100644 --- a/hw/vmapple/meson.build +++ b/hw/vmapple/meson.build @@ -4,3 +4,4 @@ system_ss.add(when: 'CONFIG_VMAPPLE_AES', if_true: files('aes.c')) system_ss.add(when: 'CONFIG_VMAPPLE_BDIF', if_true: files('bdif.c')) system_ss.add(when: 'CONFIG_VMAPPLE_CFG', if_true: files('cfg.c')) system_ss.add(when: 'CONFIG_VMAPPLE_VIRTIO_BLK', if_true: files('virtio-blk.c')) +specific_ss.add(when: 'CONFIG_VMAPPLE', if_true: files('vmapple.c')) diff --git a/hw/vmapple/vmapple.c b/hw/vmapple/vmapple.c new file mode 100644 index 000000000000..fa117bf15113 --- /dev/null +++ b/hw/vmapple/vmapple.c @@ -0,0 +1,618 @@ +/* + * VMApple machine emulation + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * VMApple is the device model that the macOS built-in hypervisor called + * "Virtualization.framework" exposes to Apple Silicon macOS guests. The + * machine model in this file implements the same device model in QEMU, but + * does not use any code from Virtualization.Framework. + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/datadir.h" +#include "qemu/error-report.h" +#include "qemu/guest-random.h" +#include "qemu/help-texts.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/option.h" +#include "qemu/units.h" +#include "monitor/qdev.h" +#include "hw/boards.h" +#include "hw/irq.h" +#include "hw/loader.h" +#include "hw/qdev-properties.h" +#include "hw/sysbus.h" +#include "hw/usb.h" +#include "hw/arm/boot.h" +#include "hw/arm/primecell.h" +#include "hw/char/pl011.h" +#include "hw/intc/arm_gic.h" +#include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/pvpanic.h" +#include "hw/pci-host/gpex.h" +#include "hw/usb/hcd-xhci-pci.h" +#include "hw/virtio/virtio-pci.h" +#include "hw/vmapple/vmapple.h" +#include "net/net.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qapi/qapi-visit-common.h" +#include "qobject/qlist.h" +#include "standard-headers/linux/input.h" +#include "system/hvf.h" +#include "system/reset.h" +#include "system/runstate.h" +#include "system/system.h" + +struct VMAppleMachineState { + MachineState parent; + + Notifier machine_done; + struct arm_boot_info bootinfo; + const MemMapEntry *memmap; + const int *irqmap; + DeviceState *gic; + DeviceState *cfg; + DeviceState *pvpanic; + Notifier powerdown_notifier; + PCIBus *bus; + MemoryRegion fw_mr; + MemoryRegion ecam_alias; + uint64_t uuid; +}; + +#define TYPE_VMAPPLE_MACHINE MACHINE_TYPE_NAME("vmapple") +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleMachineState, VMAPPLE_MACHINE) + +/* Number of external interrupt lines to configure the GIC with */ +#define NUM_IRQS 256 + +enum { + VMAPPLE_FIRMWARE, + VMAPPLE_CONFIG, + VMAPPLE_MEM, + VMAPPLE_GIC_DIST, + VMAPPLE_GIC_REDIST, + VMAPPLE_UART, + VMAPPLE_RTC, + VMAPPLE_PCIE, + VMAPPLE_PCIE_MMIO, + VMAPPLE_PCIE_ECAM, + VMAPPLE_GPIO, + VMAPPLE_PVPANIC, + VMAPPLE_APV_GFX, + VMAPPLE_APV_IOSFC, + VMAPPLE_AES_1, + VMAPPLE_AES_2, + VMAPPLE_BDOOR, + VMAPPLE_MEMMAP_LAST, +}; + +static const MemMapEntry memmap[] = { + [VMAPPLE_FIRMWARE] = { 0x00100000, 0x00100000 }, + [VMAPPLE_CONFIG] = { 0x00400000, 0x00010000 }, + + [VMAPPLE_GIC_DIST] = { 0x10000000, 0x00010000 }, + [VMAPPLE_GIC_REDIST] = { 0x10010000, 0x00400000 }, + + [VMAPPLE_UART] = { 0x20010000, 0x00010000 }, + [VMAPPLE_RTC] = { 0x20050000, 0x00001000 }, + [VMAPPLE_GPIO] = { 0x20060000, 0x00001000 }, + [VMAPPLE_PVPANIC] = { 0x20070000, 0x00000002 }, + [VMAPPLE_BDOOR] = { 0x30000000, 0x00200000 }, + [VMAPPLE_APV_GFX] = { 0x30200000, 0x00010000 }, + [VMAPPLE_APV_IOSFC] = { 0x30210000, 0x00010000 }, + [VMAPPLE_AES_1] = { 0x30220000, 0x00004000 }, + [VMAPPLE_AES_2] = { 0x30230000, 0x00004000 }, + [VMAPPLE_PCIE_ECAM] = { 0x40000000, 0x10000000 }, + [VMAPPLE_PCIE_MMIO] = { 0x50000000, 0x1fff0000 }, + + /* Actual RAM size depends on configuration */ + [VMAPPLE_MEM] = { 0x70000000ULL, GiB}, +}; + +static const int irqmap[] = { + [VMAPPLE_UART] = 1, + [VMAPPLE_RTC] = 2, + [VMAPPLE_GPIO] = 0x5, + [VMAPPLE_APV_IOSFC] = 0x10, + [VMAPPLE_APV_GFX] = 0x11, + [VMAPPLE_AES_1] = 0x12, + [VMAPPLE_PCIE] = 0x20, +}; + +#define GPEX_NUM_IRQS 16 + +static void create_bdif(VMAppleMachineState *vms, MemoryRegion *mem) +{ + DeviceState *bdif; + SysBusDevice *bdif_sb; + DriveInfo *di_aux = drive_get(IF_PFLASH, 0, 0); + DriveInfo *di_root = drive_get(IF_PFLASH, 0, 1); + + if (!di_aux) { + error_report("No AUX device. Please specify one as pflash drive."); + exit(1); + } + + if (!di_root) { + /* Fall back to the first IF_VIRTIO device as root device */ + di_root = drive_get(IF_VIRTIO, 0, 0); + } + + if (!di_root) { + error_report("No root device. Please specify one as virtio drive."); + exit(1); + } + + /* PV backdoor device */ + bdif = qdev_new(TYPE_VMAPPLE_BDIF); + bdif_sb = SYS_BUS_DEVICE(bdif); + sysbus_mmio_map(bdif_sb, 0, vms->memmap[VMAPPLE_BDOOR].base); + + qdev_prop_set_drive(DEVICE(bdif), "aux", blk_by_legacy_dinfo(di_aux)); + qdev_prop_set_drive(DEVICE(bdif), "root", blk_by_legacy_dinfo(di_root)); + + sysbus_realize_and_unref(bdif_sb, &error_fatal); +} + +static void create_pvpanic(VMAppleMachineState *vms, MemoryRegion *mem) +{ + SysBusDevice *pvpanic; + + vms->pvpanic = qdev_new(TYPE_PVPANIC_MMIO_DEVICE); + pvpanic = SYS_BUS_DEVICE(vms->pvpanic); + sysbus_mmio_map(pvpanic, 0, vms->memmap[VMAPPLE_PVPANIC].base); + + sysbus_realize_and_unref(pvpanic, &error_fatal); +} + +static bool create_cfg(VMAppleMachineState *vms, MemoryRegion *mem, + Error **errp) +{ + ERRP_GUARD(); + SysBusDevice *cfg; + MachineState *machine = MACHINE(vms); + uint32_t rnd = 1; + + vms->cfg = qdev_new(TYPE_VMAPPLE_CFG); + cfg = SYS_BUS_DEVICE(vms->cfg); + sysbus_mmio_map(cfg, 0, vms->memmap[VMAPPLE_CONFIG].base); + + qemu_guest_getrandom_nofail(&rnd, sizeof(rnd)); + + qdev_prop_set_uint32(vms->cfg, "nr-cpus", machine->smp.cpus); + qdev_prop_set_uint64(vms->cfg, "ecid", vms->uuid); + qdev_prop_set_uint64(vms->cfg, "ram-size", machine->ram_size); + qdev_prop_set_uint32(vms->cfg, "rnd", rnd); + + if (!sysbus_realize_and_unref(cfg, errp)) { + error_prepend(errp, "Error creating vmapple cfg device: "); + return false; + } + + return true; +} + +static void create_gfx(VMAppleMachineState *vms, MemoryRegion *mem) +{ + int irq_gfx = vms->irqmap[VMAPPLE_APV_GFX]; + int irq_iosfc = vms->irqmap[VMAPPLE_APV_IOSFC]; + SysBusDevice *gfx; + + gfx = SYS_BUS_DEVICE(qdev_new("apple-gfx-mmio")); + sysbus_mmio_map(gfx, 0, vms->memmap[VMAPPLE_APV_GFX].base); + sysbus_mmio_map(gfx, 1, vms->memmap[VMAPPLE_APV_IOSFC].base); + sysbus_connect_irq(gfx, 0, qdev_get_gpio_in(vms->gic, irq_gfx)); + sysbus_connect_irq(gfx, 1, qdev_get_gpio_in(vms->gic, irq_iosfc)); + sysbus_realize_and_unref(gfx, &error_fatal); +} + +static void create_aes(VMAppleMachineState *vms, MemoryRegion *mem) +{ + int irq = vms->irqmap[VMAPPLE_AES_1]; + SysBusDevice *aes; + + aes = SYS_BUS_DEVICE(qdev_new(TYPE_APPLE_AES)); + sysbus_mmio_map(aes, 0, vms->memmap[VMAPPLE_AES_1].base); + sysbus_mmio_map(aes, 1, vms->memmap[VMAPPLE_AES_2].base); + sysbus_connect_irq(aes, 0, qdev_get_gpio_in(vms->gic, irq)); + sysbus_realize_and_unref(aes, &error_fatal); +} + +static int arm_gic_ppi_index(int cpu_nr, int ppi_index) +{ + return NUM_IRQS + cpu_nr * GIC_INTERNAL + ppi_index; +} + +static void create_gic(VMAppleMachineState *vms, MemoryRegion *mem) +{ + MachineState *ms = MACHINE(vms); + /* We create a standalone GIC */ + SysBusDevice *gicbusdev; + QList *redist_region_count; + int i; + unsigned int smp_cpus = ms->smp.cpus; + + vms->gic = qdev_new(gicv3_class_name()); + qdev_prop_set_uint32(vms->gic, "revision", 3); + qdev_prop_set_uint32(vms->gic, "num-cpu", smp_cpus); + /* + * Note that the num-irq property counts both internal and external + * interrupts; there are always 32 of the former (mandated by GIC spec). + */ + qdev_prop_set_uint32(vms->gic, "num-irq", NUM_IRQS + 32); + + uint32_t redist0_capacity = + vms->memmap[VMAPPLE_GIC_REDIST].size / GICV3_REDIST_SIZE; + uint32_t redist0_count = MIN(smp_cpus, redist0_capacity); + + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, redist0_count); + qdev_prop_set_array(vms->gic, "redist-region-count", redist_region_count); + + gicbusdev = SYS_BUS_DEVICE(vms->gic); + sysbus_realize_and_unref(gicbusdev, &error_fatal); + sysbus_mmio_map(gicbusdev, 0, vms->memmap[VMAPPLE_GIC_DIST].base); + sysbus_mmio_map(gicbusdev, 1, vms->memmap[VMAPPLE_GIC_REDIST].base); + + /* + * Wire the outputs from each CPU's generic timer and the GICv3 + * maintenance interrupt signal to the appropriate GIC PPI inputs, + * and the GIC's IRQ/FIQ/VIRQ/VFIQ interrupt outputs to the CPU's inputs. + */ + for (i = 0; i < smp_cpus; i++) { + DeviceState *cpudev = DEVICE(qemu_get_cpu(i)); + + /* Map the virt timer to PPI 27 */ + qdev_connect_gpio_out(cpudev, GTIMER_VIRT, + qdev_get_gpio_in(vms->gic, + arm_gic_ppi_index(i, 27))); + + /* Map the GIC IRQ and FIQ lines to CPU */ + sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbusdev, i + smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + } +} + +static void create_uart(const VMAppleMachineState *vms, int uart, + MemoryRegion *mem, Chardev *chr) +{ + hwaddr base = vms->memmap[uart].base; + int irq = vms->irqmap[uart]; + DeviceState *dev = qdev_new(TYPE_PL011); + SysBusDevice *s = SYS_BUS_DEVICE(dev); + + qdev_prop_set_chr(dev, "chardev", chr); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + memory_region_add_subregion(mem, base, + sysbus_mmio_get_region(s, 0)); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); +} + +static void create_rtc(const VMAppleMachineState *vms) +{ + hwaddr base = vms->memmap[VMAPPLE_RTC].base; + int irq = vms->irqmap[VMAPPLE_RTC]; + + sysbus_create_simple("pl031", base, qdev_get_gpio_in(vms->gic, irq)); +} + +static DeviceState *gpio_key_dev; +static void vmapple_powerdown_req(Notifier *n, void *opaque) +{ + /* use gpio Pin 3 for power button event */ + qemu_set_irq(qdev_get_gpio_in(gpio_key_dev, 0), 1); +} + +static void create_gpio_devices(const VMAppleMachineState *vms, int gpio, + MemoryRegion *mem) +{ + DeviceState *pl061_dev; + hwaddr base = vms->memmap[gpio].base; + int irq = vms->irqmap[gpio]; + SysBusDevice *s; + + pl061_dev = qdev_new("pl061"); + /* Pull lines down to 0 if not driven by the PL061 */ + qdev_prop_set_uint32(pl061_dev, "pullups", 0); + qdev_prop_set_uint32(pl061_dev, "pulldowns", 0xff); + s = SYS_BUS_DEVICE(pl061_dev); + sysbus_realize_and_unref(s, &error_fatal); + memory_region_add_subregion(mem, base, sysbus_mmio_get_region(s, 0)); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); + gpio_key_dev = sysbus_create_simple("gpio-key", -1, + qdev_get_gpio_in(pl061_dev, 3)); +} + +static void vmapple_firmware_init(VMAppleMachineState *vms, + MemoryRegion *sysmem) +{ + hwaddr size = vms->memmap[VMAPPLE_FIRMWARE].size; + hwaddr base = vms->memmap[VMAPPLE_FIRMWARE].base; + const char *bios_name; + int image_size; + char *fname; + + bios_name = MACHINE(vms)->firmware; + if (!bios_name) { + error_report("No firmware specified"); + exit(1); + } + + fname = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (!fname) { + error_report("Could not find ROM image '%s'", bios_name); + exit(1); + } + + memory_region_init_ram(&vms->fw_mr, NULL, "firmware", size, &error_fatal); + image_size = load_image_mr(fname, &vms->fw_mr); + + g_free(fname); + if (image_size < 0) { + error_report("Could not load ROM image '%s'", bios_name); + exit(1); + } + + memory_region_add_subregion(get_system_memory(), base, &vms->fw_mr); +} + +static void create_pcie(VMAppleMachineState *vms) +{ + hwaddr base_mmio = vms->memmap[VMAPPLE_PCIE_MMIO].base; + hwaddr size_mmio = vms->memmap[VMAPPLE_PCIE_MMIO].size; + hwaddr base_ecam = vms->memmap[VMAPPLE_PCIE_ECAM].base; + hwaddr size_ecam = vms->memmap[VMAPPLE_PCIE_ECAM].size; + int irq = vms->irqmap[VMAPPLE_PCIE]; + MemoryRegion *mmio_alias; + MemoryRegion *mmio_reg; + MemoryRegion *ecam_reg; + DeviceState *dev; + int i; + PCIHostState *pci; + DeviceState *usb_controller; + USBBus *usb_bus; + + dev = qdev_new(TYPE_GPEX_HOST); + qdev_prop_set_uint32(dev, "num-irqs", GPEX_NUM_IRQS); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + /* Map only the first size_ecam bytes of ECAM space */ + ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0); + memory_region_init_alias(&vms->ecam_alias, OBJECT(dev), "pcie-ecam", + ecam_reg, 0, size_ecam); + memory_region_add_subregion(get_system_memory(), base_ecam, + &vms->ecam_alias); + + /* + * Map the MMIO window from [0x50000000-0x7fff0000] in PCI space into + * system address space at [0x50000000-0x7fff0000]. + */ + mmio_alias = g_new0(MemoryRegion, 1); + mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1); + memory_region_init_alias(mmio_alias, OBJECT(dev), "pcie-mmio", + mmio_reg, base_mmio, size_mmio); + memory_region_add_subregion(get_system_memory(), base_mmio, mmio_alias); + + for (i = 0; i < GPEX_NUM_IRQS; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, + qdev_get_gpio_in(vms->gic, irq + i)); + gpex_set_irq_num(GPEX_HOST(dev), i, irq + i); + } + + pci = PCI_HOST_BRIDGE(dev); + vms->bus = pci->bus; + g_assert(vms->bus); + + while ((dev = qemu_create_nic_device("virtio-net-pci", true, NULL))) { + qdev_realize_and_unref(dev, BUS(vms->bus), &error_fatal); + } + + if (defaults_enabled()) { + usb_controller = qdev_new(TYPE_QEMU_XHCI); + qdev_realize_and_unref(usb_controller, BUS(pci->bus), &error_fatal); + + usb_bus = USB_BUS(object_resolve_type_unambiguous(TYPE_USB_BUS, + &error_fatal)); + usb_create_simple(usb_bus, "usb-kbd"); + usb_create_simple(usb_bus, "usb-tablet"); + } +} + +static void vmapple_reset(void *opaque) +{ + VMAppleMachineState *vms = opaque; + hwaddr base = vms->memmap[VMAPPLE_FIRMWARE].base; + + cpu_set_pc(first_cpu, base); +} + +static void mach_vmapple_init(MachineState *machine) +{ + VMAppleMachineState *vms = VMAPPLE_MACHINE(machine); + MachineClass *mc = MACHINE_GET_CLASS(machine); + const CPUArchIdList *possible_cpus; + MemoryRegion *sysmem = get_system_memory(); + int n; + unsigned int smp_cpus = machine->smp.cpus; + unsigned int max_cpus = machine->smp.max_cpus; + + vms->memmap = memmap; + machine->usb = true; + + possible_cpus = mc->possible_cpu_arch_ids(machine); + assert(possible_cpus->len == max_cpus); + for (n = 0; n < possible_cpus->len; n++) { + Object *cpu; + CPUState *cs; + + if (n >= smp_cpus) { + break; + } + + cpu = object_new(possible_cpus->cpus[n].type); + object_property_set_int(cpu, "mp-affinity", + possible_cpus->cpus[n].arch_id, &error_fatal); + + cs = CPU(cpu); + cs->cpu_index = n; + + numa_cpu_pre_plug(&possible_cpus->cpus[cs->cpu_index], DEVICE(cpu), + &error_fatal); + + if (object_property_find(cpu, "has_el3")) { + object_property_set_bool(cpu, "has_el3", false, &error_fatal); + } + if (object_property_find(cpu, "has_el2")) { + object_property_set_bool(cpu, "has_el2", false, &error_fatal); + } + object_property_set_int(cpu, "psci-conduit", QEMU_PSCI_CONDUIT_HVC, + &error_fatal); + + /* Secondary CPUs start in PSCI powered-down state */ + if (n > 0) { + object_property_set_bool(cpu, "start-powered-off", true, + &error_fatal); + } + + object_property_set_link(cpu, "memory", OBJECT(sysmem), &error_abort); + qdev_realize(DEVICE(cpu), NULL, &error_fatal); + object_unref(cpu); + } + + memory_region_add_subregion(sysmem, vms->memmap[VMAPPLE_MEM].base, + machine->ram); + + create_gic(vms, sysmem); + create_bdif(vms, sysmem); + create_pvpanic(vms, sysmem); + create_aes(vms, sysmem); + create_gfx(vms, sysmem); + create_uart(vms, VMAPPLE_UART, sysmem, serial_hd(0)); + create_rtc(vms); + create_pcie(vms); + + create_gpio_devices(vms, VMAPPLE_GPIO, sysmem); + + vmapple_firmware_init(vms, sysmem); + create_cfg(vms, sysmem, &error_fatal); + + /* connect powerdown request */ + vms->powerdown_notifier.notify = vmapple_powerdown_req; + qemu_register_powerdown_notifier(&vms->powerdown_notifier); + + vms->bootinfo.ram_size = machine->ram_size; + vms->bootinfo.board_id = -1; + vms->bootinfo.loader_start = vms->memmap[VMAPPLE_MEM].base; + vms->bootinfo.skip_dtb_autoload = true; + vms->bootinfo.firmware_loaded = true; + arm_load_kernel(ARM_CPU(first_cpu), machine, &vms->bootinfo); + + qemu_register_reset(vmapple_reset, vms); +} + +static CpuInstanceProperties +vmapple_cpu_index_to_props(MachineState *ms, unsigned cpu_index) +{ + MachineClass *mc = MACHINE_GET_CLASS(ms); + const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(ms); + + assert(cpu_index < possible_cpus->len); + return possible_cpus->cpus[cpu_index].props; +} + + +static int64_t vmapple_get_default_cpu_node_id(const MachineState *ms, int idx) +{ + return idx % ms->numa_state->num_nodes; +} + +static const CPUArchIdList *vmapple_possible_cpu_arch_ids(MachineState *ms) +{ + int n; + unsigned int max_cpus = ms->smp.max_cpus; + + if (ms->possible_cpus) { + assert(ms->possible_cpus->len == max_cpus); + return ms->possible_cpus; + } + + ms->possible_cpus = g_malloc0(sizeof(CPUArchIdList) + + sizeof(CPUArchId) * max_cpus); + ms->possible_cpus->len = max_cpus; + for (n = 0; n < ms->possible_cpus->len; n++) { + ms->possible_cpus->cpus[n].type = ms->cpu_type; + ms->possible_cpus->cpus[n].arch_id = + arm_build_mp_affinity(n, GICV3_TARGETLIST_BITS); + ms->possible_cpus->cpus[n].props.has_thread_id = true; + ms->possible_cpus->cpus[n].props.thread_id = n; + } + return ms->possible_cpus; +} + +static GlobalProperty vmapple_compat_defaults[] = { + { TYPE_VIRTIO_PCI, "disable-legacy", "on" }, + /* + * macOS XHCI driver attempts to schedule events onto even rings 1 & 2 + * even when (as here) there is no MSI(-X) support. Disabling interrupter + * mapping in the XHCI controller works around the problem. + */ + { TYPE_XHCI_PCI, "conditional-intr-mapping", "on" }, +}; + +static void vmapple_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->init = mach_vmapple_init; + mc->max_cpus = 32; + mc->block_default_type = IF_VIRTIO; + mc->no_cdrom = 1; + mc->pci_allow_0_address = true; + mc->minimum_page_bits = 12; + mc->possible_cpu_arch_ids = vmapple_possible_cpu_arch_ids; + mc->cpu_index_to_instance_props = vmapple_cpu_index_to_props; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("host"); + mc->get_default_cpu_node_id = vmapple_get_default_cpu_node_id; + mc->default_ram_id = "mach-vmapple.ram"; + mc->desc = "Apple aarch64 Virtual Machine"; + + compat_props_add(mc->compat_props, vmapple_compat_defaults, + G_N_ELEMENTS(vmapple_compat_defaults)); +} + +static void vmapple_instance_init(Object *obj) +{ + VMAppleMachineState *vms = VMAPPLE_MACHINE(obj); + + vms->irqmap = irqmap; + + object_property_add_uint64_ptr(obj, "uuid", &vms->uuid, + OBJ_PROP_FLAG_READWRITE); + object_property_set_description(obj, "uuid", "Machine UUID (SDOM)"); +} + +static const TypeInfo vmapple_machine_info = { + .name = TYPE_VMAPPLE_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(VMAppleMachineState), + .class_init = vmapple_machine_class_init, + .instance_init = vmapple_instance_init, +}; + +static void machvmapple_machine_init(void) +{ + type_register_static(&vmapple_machine_info); +} +type_init(machvmapple_machine_init); + From 00ad70b7fb527a80b31c8b6b1a0e7a66b0d08498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 17:28:39 +0100 Subject: [PATCH 0442/1179] hw/ppc/spapr: Restrict part of PAGE_INIT hypercall to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restrict the tb_flush() call to TCG. Assert we are using KVM or TCG. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-Id: <20250127102620.39159-3-philmd@linaro.org> --- hw/ppc/spapr_hcall.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index f8ab76706306..f987ff323f87 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -299,8 +299,10 @@ static target_ulong h_page_init(PowerPCCPU *cpu, SpaprMachineState *spapr, if (flags & (H_ICACHE_SYNCHRONIZE | H_ICACHE_INVALIDATE)) { if (kvm_enabled()) { kvmppc_icbi_range(cpu, pdst, len); - } else { + } else if (tcg_enabled()) { tb_flush(CPU(cpu)); + } else { + g_assert_not_reached(); } } From 611f3bdb20f7828b0813aa90d47d1275ef18329b Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Fri, 14 Feb 2025 14:16:32 +1000 Subject: [PATCH 0443/1179] hw/acpi/ghes: Make ghes_record_cper_errors() static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit acpi_ghes_memory_errors() is the only caller, no need to expose the function. Besides, the last 'return' in this function isn't necessary and remove it. No functional changes intended. Signed-off-by: Gavin Shan Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250214041635.608012-2-gshan@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/acpi/ghes.c | 6 ++---- include/hw/acpi/ghes.h | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/hw/acpi/ghes.c b/hw/acpi/ghes.c index b709c177cdea..b85bb48195a0 100644 --- a/hw/acpi/ghes.c +++ b/hw/acpi/ghes.c @@ -390,8 +390,8 @@ static void get_hw_error_offsets(uint64_t ghes_addr, *read_ack_register_addr = ghes_addr + sizeof(uint64_t); } -void ghes_record_cper_errors(const void *cper, size_t len, - uint16_t source_id, Error **errp) +static void ghes_record_cper_errors(const void *cper, size_t len, + uint16_t source_id, Error **errp) { uint64_t cper_addr = 0, read_ack_register_addr = 0, read_ack_register; AcpiGedState *acpi_ged_state; @@ -440,8 +440,6 @@ void ghes_record_cper_errors(const void *cper, size_t len, /* Write the generic error data entry into guest memory */ cpu_physical_memory_write(cper_addr, cper, len); - - return; } int acpi_ghes_memory_errors(uint16_t source_id, uint64_t physical_address) diff --git a/include/hw/acpi/ghes.h b/include/hw/acpi/ghes.h index 39619a2457cb..578a582203ce 100644 --- a/include/hw/acpi/ghes.h +++ b/include/hw/acpi/ghes.h @@ -75,8 +75,6 @@ void acpi_build_hest(GArray *table_data, GArray *hardware_errors, void acpi_ghes_add_fw_cfg(AcpiGhesState *vms, FWCfgState *s, GArray *hardware_errors); int acpi_ghes_memory_errors(uint16_t source_id, uint64_t error_physical_addr); -void ghes_record_cper_errors(const void *cper, size_t len, - uint16_t source_id, Error **errp); /** * acpi_ghes_present: Report whether ACPI GHES table is present From 8c4648f5a20d88ca16ad6e12134c6c9bf01fac9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 18 Feb 2025 13:03:18 +0100 Subject: [PATCH 0444/1179] hw/arm: Do not expose the virt machine on Xen-only binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the Virt machine is useless under Xen, do not even try to build it there. A Xen-only binary now only offers the XenPVH machine: $ qemu-system-aarch64 -M help Supported machines are: none empty machine xenpvh Xen PVH ARM machine Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Edgar E. Iglesias Message-Id: <20250218162618.46167-3-philmd@linaro.org> --- hw/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index faa00d1db3b4..15200a2d7e72 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -2,6 +2,7 @@ config ARM_VIRT bool default y depends on ARM + depends on TCG || KVM || HVF imply PCI_DEVICES imply TEST_DEVICES imply VFIO_AMD_XGBE From 1ff51df2ef9cce9927ee54321886b435f1a293e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 18 Feb 2025 13:03:13 +0100 Subject: [PATCH 0445/1179] hw/xen: Link XenPVH with GPEX PCIe bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XenPVH requires the PCIe/GPEX device. Add it to Kconfig to avoid when configuring using --without-default-devices: /usr/bin/ld: libqemu-aarch64-softmmu.a.p/hw_xen_xen-pvh-common.c.o: in function `xenpvh_gpex_init': hw/xen/xen-pvh-common.c:174: undefined reference to `gpex_set_irq_num' /usr/bin/ld: libqemu-aarch64-softmmu.a.p/hw_xen_xen-hvm-common.c.o: in function `pci_dev_bus_num': include/hw/pci/pci.h:337: undefined reference to `pci_bus_num' /usr/bin/ld: include/hw/pci/pci.h:337: undefined reference to `pci_bus_num' /usr/bin/ld: include/hw/pci/pci.h:337: undefined reference to `pci_bus_num' /usr/bin/ld: include/hw/pci/pci.h:337: undefined reference to `pci_bus_num' /usr/bin/ld: include/hw/pci/pci.h:337: undefined reference to `pci_bus_num' /usr/bin/ld: libqemu-aarch64-softmmu.a.p/hw_xen_xen-hvm-common.c.o: in function `cpu_ioreq_config': hw/xen/xen-hvm-common.c:412: undefined reference to `pci_host_config_read_common' /usr/bin/ld: hw/xen/xen-hvm-common.c:428: undefined reference to `pci_host_config_read_common' /usr/bin/ld: hw/xen/xen-hvm-common.c:438: undefined reference to `pci_host_config_write_common' Fixes: f22e598a72c ("hw/xen: pvh-common: Add support for creating PCIe/GPEX") Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Edgar E. Iglesias Message-Id: <20250218162618.46167-2-philmd@linaro.org> --- accel/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/accel/Kconfig b/accel/Kconfig index 794e0d18d21d..4263cab72272 100644 --- a/accel/Kconfig +++ b/accel/Kconfig @@ -16,4 +16,5 @@ config KVM config XEN bool select FSDEV_9P if VIRTFS + select PCI_EXPRESS_GENERIC_BRIDGE select XEN_BUS From 4702dcd4ee2dbf2c383e53e6fcbc5075a85f0613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 18 Feb 2025 13:45:11 +0100 Subject: [PATCH 0446/1179] hw/xen/xen-pvh: Reduce included headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have "hw/xen/xen-pvh-common.h" include the bare minimal set of headers. Adapt sources to avoid errors when refactoring unrelated headers such: hw/i386/xen/xen-pvh.c: In function ‘xen_pvh_machine_class_init’: hw/i386/xen/xen-pvh.c:84:28: error: ‘TARGET_DEFAULT_CPU_TYPE’ undeclared (first use in this function) 84 | mc->default_cpu_type = TARGET_DEFAULT_CPU_TYPE; | ^~~~~~~~~~~~~~~~~~~~~~~ hw/xen/xen-pvh-common.c: In function ‘xen_pvh_init’: hw/xen/xen-pvh-common.c:217:43: error: ‘MiB’ undeclared (first use in this function) 217 | if (s->cfg.pci_ecam.size != 256 * MiB) { | ^~~ hw/xen/xen-hvm-common.c:18:6: error: no previous prototype for ‘xen_mr_is_memory’ [-Werror=missing-prototypes] 18 | bool xen_mr_is_memory(MemoryRegion *mr) | ^~~~~~~~~~~~~~~~ Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Edgar E. Iglesias Message-Id: <20250218162618.46167-5-philmd@linaro.org> --- hw/i386/xen/xen-pvh.c | 1 + hw/xen/xen-pvh-common.c | 5 ++--- include/hw/xen/xen-pvh-common.h | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hw/i386/xen/xen-pvh.c b/hw/i386/xen/xen-pvh.c index 33c102797639..f6356f2a7ed3 100644 --- a/hw/i386/xen/xen-pvh.c +++ b/hw/i386/xen/xen-pvh.c @@ -14,6 +14,7 @@ #include "hw/xen/arch_hvm.h" #include #include "hw/xen/xen-pvh-common.h" +#include "target/i386/cpu.h" #define TYPE_XEN_PVH_X86 MACHINE_TYPE_NAME("xenpvh") OBJECT_DECLARE_SIMPLE_TYPE(XenPVHx86State, XEN_PVH_X86) diff --git a/hw/xen/xen-pvh-common.c b/hw/xen/xen-pvh-common.c index 9c21fa858d34..d675f7a8aebc 100644 --- a/hw/xen/xen-pvh-common.c +++ b/hw/xen/xen-pvh-common.c @@ -8,14 +8,13 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" -#include "qapi/error.h" +#include "qemu/units.h" #include "qapi/visitor.h" #include "hw/boards.h" #include "hw/irq.h" -#include "hw/sysbus.h" -#include "system/system.h" #include "system/tpm.h" #include "system/tpm_backend.h" +#include "system/runstate.h" #include "hw/xen/xen-pvh-common.h" #include "trace.h" diff --git a/include/hw/xen/xen-pvh-common.h b/include/hw/xen/xen-pvh-common.h index 5cdd23c2f4d3..17c5a58a5a4e 100644 --- a/include/hw/xen/xen-pvh-common.h +++ b/include/hw/xen/xen-pvh-common.h @@ -9,11 +9,11 @@ #ifndef XEN_PVH_COMMON_H__ #define XEN_PVH_COMMON_H__ -#include -#include "hw/sysbus.h" -#include "hw/hw.h" -#include "hw/xen/xen-hvm-common.h" +#include "exec/memory.h" +#include "qom/object.h" +#include "hw/boards.h" #include "hw/pci-host/gpex.h" +#include "hw/xen/xen-hvm-common.h" #define TYPE_XEN_PVH_MACHINE MACHINE_TYPE_NAME("xen-pvh-base") OBJECT_DECLARE_TYPE(XenPVHMachineState, XenPVHMachineClass, From 65132d39ac31b492ec60047b0c82c1a9001fb967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 18 Feb 2025 13:44:57 +0100 Subject: [PATCH 0447/1179] hw/xen/xen-hvm: Reduce included headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have "hw/xen/xen-hvm-common.h" include the bare minimal set of headers. Adapt sources to avoid errors when refactoring unrelated headers such: include/hw/xen/xen-hvm-common.h:71:5: error: unknown type name ‘xenevtchn_handle’ 71 | xenevtchn_handle *xce_handle; | ^~~~~~~~~~~~~~~~ hw/xen/xen-hvm-common.c: In function ‘cpu_get_ioreq’: hw/xen/xen-hvm-common.c:227:13: error: implicit declaration of function ‘hw_error’ 227 | hw_error("Fatal error while trying to get io event!\n"); | ^~~~~~~~ | herror hw/xen/xen-hvm-common.c: In function ‘handle_ioreq’: hw/xen/xen-hvm-common.c:446:34: error: ‘target_ulong’ undeclared (first use in this function) 446 | (req->size < sizeof (target_ulong))) { | ^~~~~~~~~~~~ hw/i386/xen/xen-hvm.c: In function ‘xen_add_to_physmap’: hw/i386/xen/xen-hvm.c:298:22: error: implicit declaration of function ‘xen_replace_cache_entry’ 298 | uint8_t *p = xen_replace_cache_entry(phys_offset, start_addr, size); | ^~~~~~~~~~~~~~~~~~~~~~~ hw/i386/xen/xen-hvm.c:314:9: error: implicit declaration of function 'error_report' is invalid in C99 314 | error_report("relocate_memory %lu pages from GFN %"HWADDR_PRIx ^~~~~~~~~~~~ hw/i386/xen/xen-hvm.c: In function ‘xen_log_global_start’: hw/i386/xen/xen-hvm.c:465:9: error: implicit declaration of function ‘xen_enabled’ 465 | if (xen_enabled()) { | ^~~~~~~~~~~ hw/i386/xen/xen-hvm.c: In function ‘regs_to_cpu’: hw/i386/xen/xen-hvm.c:487:5: error: unknown type name ‘X86CPU’ 487 | X86CPU *cpu; | ^~~~~~ hw/i386/xen/xen-hvm.c:492:15: error: ‘R_EAX’ undeclared (first use in this function) 492 | env->regs[R_EAX] = req->data; | ^~~~~ | REG_RAX Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Anthony PERARD Message-Id: <20250218162618.46167-6-philmd@linaro.org> --- hw/arm/xen-stubs.c | 5 ++--- hw/i386/xen/xen-hvm.c | 6 ++++++ hw/xen/xen-hvm-common.c | 7 +++++++ include/hw/xen/xen-hvm-common.h | 14 +++----------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/hw/arm/xen-stubs.c b/hw/arm/xen-stubs.c index 34beb8b08cb0..5551584dc208 100644 --- a/hw/arm/xen-stubs.c +++ b/hw/arm/xen-stubs.c @@ -5,10 +5,9 @@ */ #include "qemu/osdep.h" -#include "qemu/error-report.h" #include "qapi/qapi-commands-migration.h" -#include "hw/boards.h" -#include "system/system.h" +#include "system/xen.h" +#include "hw/hw.h" #include "hw/xen/xen-hvm-common.h" #include "hw/xen/arch_hvm.h" diff --git a/hw/i386/xen/xen-hvm.c b/hw/i386/xen/xen-hvm.c index d3df488c4833..d4516acec699 100644 --- a/hw/i386/xen/xen-hvm.c +++ b/hw/i386/xen/xen-hvm.c @@ -10,10 +10,12 @@ #include "qemu/osdep.h" #include "qemu/units.h" +#include "qemu/error-report.h" #include "qapi/error.h" #include "qapi/qapi-commands-migration.h" #include "trace.h" +#include "hw/hw.h" #include "hw/i386/pc.h" #include "hw/irq.h" #include "hw/i386/apic-msidef.h" @@ -24,6 +26,10 @@ #include "hw/xen/arch_hvm.h" #include #include "exec/target_page.h" +#include "target/i386/cpu.h" +#include "system/runstate.h" +#include "system/xen-mapcache.h" +#include "system/xen.h" static MemoryRegion ram_640k, ram_lo, ram_hi; static MemoryRegion *framebuffer; diff --git a/hw/xen/xen-hvm-common.c b/hw/xen/xen-hvm-common.c index 7ffbbfea23b3..9a677e8ed749 100644 --- a/hw/xen/xen-hvm-common.c +++ b/hw/xen/xen-hvm-common.c @@ -1,14 +1,21 @@ #include "qemu/osdep.h" #include "qemu/units.h" +#include "qemu/error-report.h" #include "qapi/error.h" +#include "exec/target_long.h" #include "exec/target_page.h" #include "trace.h" +#include "hw/hw.h" #include "hw/pci/pci_host.h" #include "hw/xen/xen-hvm-common.h" #include "hw/xen/xen-bus.h" #include "hw/boards.h" #include "hw/xen/arch_hvm.h" +#include "system/runstate.h" +#include "system/system.h" +#include "system/xen.h" +#include "system/xen-mapcache.h" MemoryRegion xen_memory, xen_grants; diff --git a/include/hw/xen/xen-hvm-common.h b/include/hw/xen/xen-hvm-common.h index c1ea2c0d787b..19df5600a397 100644 --- a/include/hw/xen/xen-hvm-common.h +++ b/include/hw/xen/xen-hvm-common.h @@ -1,18 +1,10 @@ #ifndef HW_XEN_HVM_COMMON_H #define HW_XEN_HVM_COMMON_H -#include "qemu/units.h" - -#include "cpu.h" -#include "hw/pci/pci.h" -#include "hw/hw.h" +#include "qemu/queue.h" +#include "exec/hwaddr.h" #include "hw/xen/xen_native.h" -#include "hw/xen/xen-legacy-backend.h" -#include "system/runstate.h" -#include "system/system.h" -#include "system/xen.h" -#include "system/xen-mapcache.h" -#include "qemu/error-report.h" +#include "hw/xen/xen_backend_ops.h" #include extern MemoryRegion xen_memory; From cc2b1c5b0764a2f96106ed75c5bd141950148b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 18 Feb 2025 13:45:19 +0100 Subject: [PATCH 0448/1179] hw/xen/xen-bus: Reduce included headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have "hw/xen/xen-bus" include the bare minimal set of headers. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Anthony PERARD Message-Id: <20250218162618.46167-7-philmd@linaro.org> --- include/hw/xen/xen-bus.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/hw/xen/xen-bus.h b/include/hw/xen/xen-bus.h index 2adb2af83919..bdbf1ed6fd0c 100644 --- a/include/hw/xen/xen-bus.h +++ b/include/hw/xen/xen-bus.h @@ -8,9 +8,10 @@ #ifndef HW_XEN_BUS_H #define HW_XEN_BUS_H +#include "hw/qdev-core.h" #include "hw/xen/xen_backend_ops.h" -#include "hw/sysbus.h" #include "qemu/notify.h" +#include "qemu/queue.h" #include "qom/object.h" typedef struct XenEventChannel XenEventChannel; From d1bb9921bbbc65ecbb7c6193834e154af0cd0a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 18 Feb 2025 16:57:06 +0100 Subject: [PATCH 0449/1179] hw/xen/xen-legacy-backend: Remove unused 'net/net.h' header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Anthony PERARD Message-Id: <20250218162618.46167-8-philmd@linaro.org> --- include/hw/xen/xen-legacy-backend.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/hw/xen/xen-legacy-backend.h b/include/hw/xen/xen-legacy-backend.h index e198b120c5d2..2d0cbfecade8 100644 --- a/include/hw/xen/xen-legacy-backend.h +++ b/include/hw/xen/xen-legacy-backend.h @@ -3,7 +3,6 @@ #include "hw/xen/xen_backend_ops.h" #include "hw/xen/xen_pvdev.h" -#include "net/net.h" #include "qom/object.h" #define TYPE_XENSYSDEV "xen-sysdev" From 92988c45017ca9385f408eb109184d68d4fef8e7 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Tue, 18 Feb 2025 16:54:07 +0100 Subject: [PATCH 0450/1179] hw/net/fsl_etsec: Set eTSEC device description and category MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add description and set category for eTSEC device so it shows up better in -device help. Signed-off-by: BALATON Zoltan Reviewed-by: Bernhard Beschow Message-ID: <20250218155407.838774E600E@zero.eik.bme.hu> Signed-off-by: Philippe Mathieu-Daudé --- hw/net/fsl_etsec/etsec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/net/fsl_etsec/etsec.c b/hw/net/fsl_etsec/etsec.c index 3ce4fa2662d6..adde64489265 100644 --- a/hw/net/fsl_etsec/etsec.c +++ b/hw/net/fsl_etsec/etsec.c @@ -423,8 +423,10 @@ static void etsec_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = etsec_realize; + dc->desc = "Freescale Enhanced Three-Speed Ethernet Controller"; device_class_set_legacy_reset(dc, etsec_reset); device_class_set_props(dc, etsec_properties); + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); } static const TypeInfo etsec_types[] = { From abf2b6a028670bd2890bb3aee7e103fe53e4b0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 22 May 2023 11:05:49 +0200 Subject: [PATCH 0451/1179] hw/char/pl011: Warn when using disabled receiver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We shouldn't receive characters when the full UART or its receiver is disabled. However we don't want to break the possibly incomplete "my first bare metal assembly program"s, so we choose to simply display a warning when this occurs. Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Message-Id: <20250220092903.3726-2-philmd@linaro.org> --- hw/char/pl011.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index 06ce851044d9..12a2d4bc7bd7 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -85,6 +85,7 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr) #define CR_OUT1 (1 << 12) #define CR_RTS (1 << 11) #define CR_DTR (1 << 10) +#define CR_RXE (1 << 9) #define CR_TXE (1 << 8) #define CR_LBE (1 << 7) #define CR_UARTEN (1 << 0) @@ -487,6 +488,14 @@ static int pl011_can_receive(void *opaque) PL011State *s = (PL011State *)opaque; int r; + if (!(s->cr & CR_UARTEN)) { + qemu_log_mask(LOG_GUEST_ERROR, + "PL011 receiving data on disabled UART\n"); + } + if (!(s->cr & CR_RXE)) { + qemu_log_mask(LOG_GUEST_ERROR, + "PL011 receiving data on disabled RX UART\n"); + } r = s->read_count < pl011_get_fifo_depth(s); trace_pl011_can_receive(s->lcr, s->read_count, r); return r; From f33af61dbab3b6fe0923bd829461584eaa41039e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 15:35:53 +0100 Subject: [PATCH 0452/1179] hw/char/pl011: Simplify a bit pl011_can_receive() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce 'fifo_depth' and 'fifo_available' local variables to better express the 'r' variable use. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Reviewed-by: Richard Henderson Message-Id: <20250220092903.3726-3-philmd@linaro.org> --- hw/char/pl011.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index 12a2d4bc7bd7..5bb83c54216c 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -486,7 +486,9 @@ static void pl011_write(void *opaque, hwaddr offset, static int pl011_can_receive(void *opaque) { PL011State *s = (PL011State *)opaque; - int r; + unsigned fifo_depth = pl011_get_fifo_depth(s); + unsigned fifo_available = fifo_depth - s->read_count; + int r = fifo_available ? 1 : 0; if (!(s->cr & CR_UARTEN)) { qemu_log_mask(LOG_GUEST_ERROR, @@ -496,7 +498,6 @@ static int pl011_can_receive(void *opaque) qemu_log_mask(LOG_GUEST_ERROR, "PL011 receiving data on disabled RX UART\n"); } - r = s->read_count < pl011_get_fifo_depth(s); trace_pl011_can_receive(s->lcr, s->read_count, r); return r; } From 2c459f734ddda44f92231098e8cc9e3f36a8593c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 15:37:08 +0100 Subject: [PATCH 0453/1179] hw/char/pl011: Improve RX flow tracing events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Log FIFO use (availability and depth). Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Alex Bennée Reviewed-by: Luc Michel Reviewed-by: Richard Henderson Message-Id: <20250220092903.3726-4-philmd@linaro.org> --- hw/char/pl011.c | 10 ++++++---- hw/char/trace-events | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index 5bb83c54216c..f7485e7c5414 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -185,7 +185,7 @@ static void pl011_fifo_rx_put(void *opaque, uint32_t value) s->read_fifo[slot] = value; s->read_count++; s->flags &= ~PL011_FLAG_RXFE; - trace_pl011_fifo_rx_put(value, s->read_count); + trace_pl011_fifo_rx_put(value, s->read_count, pipe_depth); if (s->read_count == pipe_depth) { trace_pl011_fifo_rx_full(); s->flags |= PL011_FLAG_RXFF; @@ -248,12 +248,13 @@ static void pl011_write_txdata(PL011State *s, uint8_t data) static uint32_t pl011_read_rxdata(PL011State *s) { uint32_t c; + unsigned fifo_depth = pl011_get_fifo_depth(s); s->flags &= ~PL011_FLAG_RXFF; c = s->read_fifo[s->read_pos]; if (s->read_count > 0) { s->read_count--; - s->read_pos = (s->read_pos + 1) & (pl011_get_fifo_depth(s) - 1); + s->read_pos = (s->read_pos + 1) & (fifo_depth - 1); } if (s->read_count == 0) { s->flags |= PL011_FLAG_RXFE; @@ -261,7 +262,7 @@ static uint32_t pl011_read_rxdata(PL011State *s) if (s->read_count == s->read_trigger - 1) { s->int_level &= ~INT_RX; } - trace_pl011_read_fifo(s->read_count); + trace_pl011_read_fifo(s->read_count, fifo_depth); s->rsr = c >> 8; pl011_update(s); qemu_chr_fe_accept_input(&s->chr); @@ -498,12 +499,13 @@ static int pl011_can_receive(void *opaque) qemu_log_mask(LOG_GUEST_ERROR, "PL011 receiving data on disabled RX UART\n"); } - trace_pl011_can_receive(s->lcr, s->read_count, r); + trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available); return r; } static void pl011_receive(void *opaque, const uint8_t *buf, int size) { + trace_pl011_receive(size); /* * In loopback mode, the RX input signal is internally disconnected * from the entire receiving logics; thus, all inputs are ignored, diff --git a/hw/char/trace-events b/hw/char/trace-events index b2e3d25ae346..05a33036c120 100644 --- a/hw/char/trace-events +++ b/hw/char/trace-events @@ -60,12 +60,13 @@ imx_serial_put_data(const char *chrname, uint32_t value) "%s: 0x%" PRIx32 # pl011.c pl011_irq_state(int level) "irq state %d" pl011_read(uint32_t addr, uint32_t value, const char *regname) "addr 0x%03x value 0x%08x reg %s" -pl011_read_fifo(int read_count) "FIFO read, read_count now %d" +pl011_read_fifo(unsigned rx_fifo_used, size_t rx_fifo_depth) "RX FIFO read, used %u/%zu" pl011_write(uint32_t addr, uint32_t value, const char *regname) "addr 0x%03x value 0x%08x reg %s" -pl011_can_receive(uint32_t lcr, int read_count, int r) "LCR 0x%08x read_count %d returning %d" -pl011_fifo_rx_put(uint32_t c, int read_count) "new char 0x%02x read_count now %d" +pl011_can_receive(uint32_t lcr, unsigned rx_fifo_used, size_t rx_fifo_depth, unsigned rx_fifo_available) "LCR 0x%02x, RX FIFO used %u/%zu, can_receive %u chars" +pl011_fifo_rx_put(uint32_t c, unsigned read_count, size_t rx_fifo_depth) "RX FIFO push char [0x%02x] %d/%zu depth used" pl011_fifo_rx_full(void) "RX FIFO now full, RXFF set" pl011_baudrate_change(unsigned int baudrate, uint64_t clock, uint32_t ibrd, uint32_t fbrd) "new baudrate %u (clk: %" PRIu64 "hz, ibrd: %" PRIu32 ", fbrd: %" PRIu32 ")" +pl011_receive(int size) "recv %d chars" # cmsdk-apb-uart.c cmsdk_apb_uart_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB UART read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" From 3e0f118f8259dd5bcdf9caf3762f92718b97f47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 15:37:50 +0100 Subject: [PATCH 0454/1179] hw/char/pl011: Really use RX FIFO depth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While we model a 16-elements RX FIFO since the PL011 model was introduced in commit cdbdb648b7c ("ARM Versatile Platform Baseboard emulation"), we only read 1 char at a time! Have the IOCanReadHandler handler return how many elements are available, and use that in the IOReadHandler handler. Example of FIFO better used by enabling the pl011 tracing events and running the tests/functional/test_aarch64_virt.py tests: pl011_can_receive LCR 0x70, RX FIFO used 0/16, can_receive 16 chars pl011_receive recv 5 chars pl011_fifo_rx_put RX FIFO push char [0x72] 1/16 depth used pl011_irq_state irq state 1 pl011_fifo_rx_put RX FIFO push char [0x6f] 2/16 depth used pl011_fifo_rx_put RX FIFO push char [0x6f] 3/16 depth used pl011_fifo_rx_put RX FIFO push char [0x74] 4/16 depth used pl011_fifo_rx_put RX FIFO push char [0x0d] 5/16 depth used pl011_can_receive LCR 0x70, RX FIFO used 5/16, can_receive 11 chars pl011_can_receive LCR 0x70, RX FIFO used 5/16, can_receive 11 chars pl011_write addr 0x038 value 0x00000050 reg IMSC pl011_irq_state irq state 1 pl011_can_receive LCR 0x70, RX FIFO used 5/16, can_receive 11 chars pl011_read addr 0x03c value 0x00000030 reg RIS pl011_write addr 0x044 value 0x00000000 reg ICR pl011_irq_state irq state 1 pl011_read addr 0x018 value 0x00000080 reg FR pl011_read_fifo RX FIFO read, used 4/16 pl011_irq_state irq state 1 pl011_read addr 0x000 value 0x00000072 reg DR pl011_can_receive LCR 0x70, RX FIFO used 4/16, can_receive 12 chars pl011_read addr 0x018 value 0x00000080 reg FR pl011_read_fifo RX FIFO read, used 3/16 pl011_irq_state irq state 1 pl011_read addr 0x000 value 0x0000006f reg DR pl011_can_receive LCR 0x70, RX FIFO used 3/16, can_receive 13 chars pl011_read addr 0x018 value 0x00000080 reg FR pl011_read_fifo RX FIFO read, used 2/16 pl011_irq_state irq state 1 pl011_read addr 0x000 value 0x0000006f reg DR pl011_can_receive LCR 0x70, RX FIFO used 2/16, can_receive 14 chars pl011_read addr 0x018 value 0x00000080 reg FR pl011_read_fifo RX FIFO read, used 1/16 pl011_irq_state irq state 1 pl011_read addr 0x000 value 0x00000074 reg DR pl011_can_receive LCR 0x70, RX FIFO used 1/16, can_receive 15 chars pl011_read addr 0x018 value 0x00000080 reg FR pl011_read_fifo RX FIFO read, used 0/16 pl011_irq_state irq state 0 pl011_read addr 0x000 value 0x0000000d reg DR pl011_can_receive LCR 0x70, RX FIFO used 0/16, can_receive 16 chars pl011_read addr 0x018 value 0x00000090 reg FR pl011_read addr 0x03c value 0x00000020 reg RIS pl011_write addr 0x038 value 0x00000050 reg IMSC pl011_irq_state irq state 0 pl011_can_receive LCR 0x70, RX FIFO used 0/16, can_receive 16 chars pl011_can_receive LCR 0x70, RX FIFO used 0/16, can_receive 16 chars pl011_read addr 0x018 value 0x00000090 reg FR pl011_write addr 0x000 value 0x00000072 reg DR Inspired-by: Peter Maydell Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Reviewed-by: Richard Henderson Message-Id: <20250220092903.3726-5-philmd@linaro.org> --- hw/char/pl011.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index f7485e7c5414..23a9db8c57cb 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -489,7 +489,6 @@ static int pl011_can_receive(void *opaque) PL011State *s = (PL011State *)opaque; unsigned fifo_depth = pl011_get_fifo_depth(s); unsigned fifo_available = fifo_depth - s->read_count; - int r = fifo_available ? 1 : 0; if (!(s->cr & CR_UARTEN)) { qemu_log_mask(LOG_GUEST_ERROR, @@ -500,7 +499,8 @@ static int pl011_can_receive(void *opaque) "PL011 receiving data on disabled RX UART\n"); } trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available); - return r; + + return fifo_available; } static void pl011_receive(void *opaque, const uint8_t *buf, int size) @@ -515,7 +515,9 @@ static void pl011_receive(void *opaque, const uint8_t *buf, int size) return; } - pl011_fifo_rx_put(opaque, *buf); + for (int i = 0; i < size; i++) { + pl011_fifo_rx_put(opaque, buf[i]); + } } static void pl011_event(void *opaque, QEMUChrEvent event) From 2e6b2e08756e618e5d4316ff277e78213942a2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 16:23:13 +0100 Subject: [PATCH 0455/1179] hw/char/bcm2835_aux: Really use RX FIFO depth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While we model a 8-elements RX FIFO since the BCM2835 AUX model was introduced in commit 97398d900ca ("bcm2835_aux: add emulation of BCM2835 AUX block") we only read 1 char at a time! Have the IOCanReadHandler handler return how many elements are available, and use that in the IOReadHandler handler. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Reviewed-by: Richard Henderson Message-Id: <20250220092903.3726-6-philmd@linaro.org> --- hw/char/bcm2835_aux.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/char/bcm2835_aux.c b/hw/char/bcm2835_aux.c index 73ad59340673..c6e7eccf7dd7 100644 --- a/hw/char/bcm2835_aux.c +++ b/hw/char/bcm2835_aux.c @@ -221,7 +221,7 @@ static int bcm2835_aux_can_receive(void *opaque) { BCM2835AuxState *s = opaque; - return s->read_count < BCM2835_AUX_RX_FIFO_LEN; + return BCM2835_AUX_RX_FIFO_LEN - s->read_count; } static void bcm2835_aux_put_fifo(void *opaque, uint8_t value) @@ -243,7 +243,9 @@ static void bcm2835_aux_put_fifo(void *opaque, uint8_t value) static void bcm2835_aux_receive(void *opaque, const uint8_t *buf, int size) { - bcm2835_aux_put_fifo(opaque, *buf); + for (int i = 0; i < size; i++) { + bcm2835_aux_put_fifo(opaque, buf[i]); + } } static const MemoryRegionOps bcm2835_aux_ops = { From 91f8c04dd25f2eacbb9fd57db95b09313cdfd085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 16:23:30 +0100 Subject: [PATCH 0456/1179] hw/char/imx_serial: Really use RX FIFO depth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While we model a 32-elements RX FIFO since the IMX serial model was introduced in commit 988f2442971 ("hw/char/imx_serial: Implement receive FIFO and ageing timer") we only read 1 char at a time! Have the IOCanReadHandler handler return how many elements are available, and use that in the IOReadHandler handler. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Reviewed-by: Richard Henderson Tested-by: Bernhard Beschow Message-Id: <20250220092903.3726-7-philmd@linaro.org> --- hw/char/imx_serial.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hw/char/imx_serial.c b/hw/char/imx_serial.c index 38b4865157ed..6f14f8403a9a 100644 --- a/hw/char/imx_serial.c +++ b/hw/char/imx_serial.c @@ -386,7 +386,8 @@ static void imx_serial_write(void *opaque, hwaddr offset, static int imx_can_receive(void *opaque) { IMXSerialState *s = (IMXSerialState *)opaque; - return s->ucr2 & UCR2_RXEN && fifo32_num_used(&s->rx_fifo) < FIFO_SIZE; + + return s->ucr2 & UCR2_RXEN ? fifo32_num_free(&s->rx_fifo) : 0; } static void imx_put_data(void *opaque, uint32_t value) @@ -417,7 +418,10 @@ static void imx_receive(void *opaque, const uint8_t *buf, int size) IMXSerialState *s = (IMXSerialState *)opaque; s->usr2 |= USR2_WAKE; - imx_put_data(opaque, *buf); + + for (int i = 0; i < size; i++) { + imx_put_data(opaque, buf[i]); + } } static void imx_event(void *opaque, QEMUChrEvent event) From 3d978e7b9b25913620d57eb73256f395ab1c18a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 16:23:44 +0100 Subject: [PATCH 0457/1179] hw/char/mcf_uart: Use FIFO_DEPTH definition instead of magic values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defines FIFO_DEPTH and use it, fixing coding style. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250220092903.3726-8-philmd@linaro.org> --- hw/char/mcf_uart.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/hw/char/mcf_uart.c b/hw/char/mcf_uart.c index 980a12fcb7dd..95f269ee9b76 100644 --- a/hw/char/mcf_uart.c +++ b/hw/char/mcf_uart.c @@ -17,6 +17,8 @@ #include "chardev/char-fe.h" #include "qom/object.h" +#define FIFO_DEPTH 4 + struct mcf_uart_state { SysBusDevice parent_obj; @@ -27,7 +29,7 @@ struct mcf_uart_state { uint8_t imr; uint8_t bg1; uint8_t bg2; - uint8_t fifo[4]; + uint8_t fifo[FIFO_DEPTH]; uint8_t tb; int current_mr; int fifo_len; @@ -247,14 +249,16 @@ static void mcf_uart_reset(DeviceState *dev) static void mcf_uart_push_byte(mcf_uart_state *s, uint8_t data) { /* Break events overwrite the last byte if the fifo is full. */ - if (s->fifo_len == 4) + if (s->fifo_len == FIFO_DEPTH) { s->fifo_len--; + } s->fifo[s->fifo_len] = data; s->fifo_len++; s->sr |= MCF_UART_RxRDY; - if (s->fifo_len == 4) + if (s->fifo_len == FIFO_DEPTH) { s->sr |= MCF_UART_FFULL; + } mcf_uart_update(s); } From 3ca8af5445b493ae3bc1520c71de3fd7e4555af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 16:25:17 +0100 Subject: [PATCH 0458/1179] hw/char/mcf_uart: Really use RX FIFO depth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While we model a 4-elements RX FIFO since the MCF UART model was introduced in commit 20dcee94833 ("MCF5208 emulation"), we only read 1 char at a time! Have the IOCanReadHandler handler return how many elements are available, and use that in the IOReadHandler handler. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Tested-by: Thomas Huth Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250220092903.3726-9-philmd@linaro.org> --- hw/char/mcf_uart.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/char/mcf_uart.c b/hw/char/mcf_uart.c index 95f269ee9b76..529c26be93a3 100644 --- a/hw/char/mcf_uart.c +++ b/hw/char/mcf_uart.c @@ -281,14 +281,16 @@ static int mcf_uart_can_receive(void *opaque) { mcf_uart_state *s = (mcf_uart_state *)opaque; - return s->rx_enabled && (s->sr & MCF_UART_FFULL) == 0; + return s->rx_enabled ? FIFO_DEPTH - s->fifo_len : 0; } static void mcf_uart_receive(void *opaque, const uint8_t *buf, int size) { mcf_uart_state *s = (mcf_uart_state *)opaque; - mcf_uart_push_byte(s, buf[0]); + for (int i = 0; i < size; i++) { + mcf_uart_push_byte(s, buf[i]); + } } static const MemoryRegionOps mcf_uart_ops = { From 543671d9907f7604b83fa9e76cdb04fa9fab9cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 19 Feb 2025 16:44:42 +0100 Subject: [PATCH 0459/1179] hw/char/sh_serial: Return correct number of empty RX FIFO elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the IOCanReadHandler sh_serial_can_receive(), if the Serial Control Register 'Receive Enable' bit is set (bit 4), then we return a size of (1 << 4) which happens to be equal to 16, so effectively SH_RX_FIFO_LENGTH. The IOReadHandler, sh_serial_receive1() takes care to receive multiple chars, but if the FIFO is partly filled, we only process the number of free slots in the FIFO, discarding the other chars! Fix by returning how many elements the FIFO can queue in the IOCanReadHandler, so we don't have to process more than that in the IOReadHandler, thus not discarding anything. Remove the now unnecessary check on 's->rx_cnt < SH_RX_FIFO_LENGTH' in IOReadHandler, reducing the block indentation. Fixes: 63242a007a1 ("SH4: Serial controller improvement") Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Luc Michel Reviewed-by: Richard Henderson Message-Id: <20250220092903.3726-10-philmd@linaro.org> --- hw/char/sh_serial.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/hw/char/sh_serial.c b/hw/char/sh_serial.c index 247aeb071ac6..41c8175a638f 100644 --- a/hw/char/sh_serial.c +++ b/hw/char/sh_serial.c @@ -320,7 +320,7 @@ static uint64_t sh_serial_read(void *opaque, hwaddr offs, static int sh_serial_can_receive(SHSerialState *s) { - return s->scr & (1 << 4); + return s->scr & (1 << 4) ? SH_RX_FIFO_LENGTH - s->rx_head : 0; } static void sh_serial_receive_break(SHSerialState *s) @@ -353,22 +353,20 @@ static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size) if (s->feat & SH_SERIAL_FEAT_SCIF) { int i; for (i = 0; i < size; i++) { - if (s->rx_cnt < SH_RX_FIFO_LENGTH) { - s->rx_fifo[s->rx_head++] = buf[i]; - if (s->rx_head == SH_RX_FIFO_LENGTH) { - s->rx_head = 0; - } - s->rx_cnt++; - if (s->rx_cnt >= s->rtrg) { - s->flags |= SH_SERIAL_FLAG_RDF; - if (s->scr & (1 << 6) && s->rxi) { - timer_del(&s->fifo_timeout_timer); - qemu_set_irq(s->rxi, 1); - } - } else { - timer_mod(&s->fifo_timeout_timer, - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 15 * s->etu); + s->rx_fifo[s->rx_head++] = buf[i]; + if (s->rx_head == SH_RX_FIFO_LENGTH) { + s->rx_head = 0; + } + s->rx_cnt++; + if (s->rx_cnt >= s->rtrg) { + s->flags |= SH_SERIAL_FLAG_RDF; + if (s->scr & (1 << 6) && s->rxi) { + timer_del(&s->fifo_timeout_timer); + qemu_set_irq(s->rxi, 1); } + } else { + timer_mod(&s->fifo_timeout_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 15 * s->etu); } } } else { From 7fc96bc4fca0cd8f1733235e987fe8ccf4517203 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Mon, 3 Mar 2025 12:31:20 +1000 Subject: [PATCH 0460/1179] hw/char/sifive_uart: Free fifo on unrealize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We previously allocate the fifo on reset and never free it, which means we are leaking memory. Instead let's allocate on realize and free on unrealize. Signed-off-by: Alistair Francis Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Daniel Henrique Barboza Tested-by: Clément Chigot Message-ID: <20250303023120.157221-1-alistair.francis@wdc.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/char/sifive_uart.c | 44 +++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/hw/char/sifive_uart.c b/hw/char/sifive_uart.c index 4bc5767284b4..b45e6c098c44 100644 --- a/hw/char/sifive_uart.c +++ b/hw/char/sifive_uart.c @@ -251,6 +251,23 @@ static int sifive_uart_be_change(void *opaque) return 0; } +static void sifive_uart_reset_enter(Object *obj, ResetType type) +{ + SiFiveUARTState *s = SIFIVE_UART(obj); + + s->txfifo = 0; + s->ie = 0; + s->ip = 0; + s->txctrl = 0; + s->rxctrl = 0; + s->div = 0; + + s->rx_fifo_len = 0; + + memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE); + fifo8_reset(&s->tx_fifo); +} + static const Property sifive_uart_properties[] = { DEFINE_PROP_CHR("chardev", SiFiveUARTState, chr), }; @@ -270,30 +287,24 @@ static void sifive_uart_realize(DeviceState *dev, Error **errp) { SiFiveUARTState *s = SIFIVE_UART(dev); + fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE); + s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL, fifo_trigger_update, s); - qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx, - sifive_uart_event, sifive_uart_be_change, s, - NULL, true); + if (qemu_chr_fe_backend_connected(&s->chr)) { + qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx, + sifive_uart_event, sifive_uart_be_change, s, + NULL, true); + } } -static void sifive_uart_reset_enter(Object *obj, ResetType type) +static void sifive_uart_unrealize(DeviceState *dev) { - SiFiveUARTState *s = SIFIVE_UART(obj); - - s->txfifo = 0; - s->ie = 0; - s->ip = 0; - s->txctrl = 0; - s->rxctrl = 0; - s->div = 0; - - s->rx_fifo_len = 0; + SiFiveUARTState *s = SIFIVE_UART(dev); - memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE); - fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE); + fifo8_destroy(&s->tx_fifo); } static void sifive_uart_reset_hold(Object *obj, ResetType type) @@ -329,6 +340,7 @@ static void sifive_uart_class_init(ObjectClass *oc, void *data) ResettableClass *rc = RESETTABLE_CLASS(oc); dc->realize = sifive_uart_realize; + dc->unrealize = sifive_uart_unrealize; dc->vmsd = &vmstate_sifive_uart; rc->phases.enter = sifive_uart_reset_enter; rc->phases.hold = sifive_uart_reset_hold; From d0f439ddd37e1354daca3234976295189985cb04 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: Fri, 28 Feb 2025 21:57:08 +0200 Subject: [PATCH 0461/1179] iotest: Unbreak 302 with python 3.13 This test depends on TarFile.addfile() to add tar member header without writing the member data, which we write ourself using qemu-nbd. Python 3.13 changed the function in a backward incompatible way[1] to require a file object for tarinfo with non-zero size, breaking the test: -[{"name": "vm.ovf", "offset": 512, "size": 6}, {"name": "disk", "offset": 1536, "size": 393216}] +Traceback (most recent call last): + File "/home/stefanha/qemu/tests/qemu-iotests/302", line 118, in + tar.addfile(disk) + ~~~~~~~~~~~^^^^^^ + File "/usr/lib64/python3.13/tarfile.py", line 2262, in addfile + raise ValueError("fileobj not provided for non zero-size regular file") +ValueError: fileobj not provided for non zero-size regular file The new behavior makes sense for most users, but breaks our unusual usage. Fix the test to add the member header directly using public but undocumented attributes. This is more fragile but the test works again. This also fixes a bug in the previous code - when calling addfile() without a fileobject, tar.offset points to the start of the member data instead of the end. [1] https://github.com/python/cpython/pull/117988 Signed-off-by: Nir Soffer Message-ID: <20250228195708.48035-1-nirsof@gmail.com> Reviewed-by: Eric Blake Reviewed-by: Stefan Hajnoczi Signed-off-by: Eric Blake --- tests/qemu-iotests/302 | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/qemu-iotests/302 b/tests/qemu-iotests/302 index a6d79e727b55..e980ec513f26 100755 --- a/tests/qemu-iotests/302 +++ b/tests/qemu-iotests/302 @@ -115,13 +115,22 @@ with tarfile.open(tar_file, "w") as tar: disk = tarfile.TarInfo("disk") disk.size = actual_size - tar.addfile(disk) - # 6. Shrink the tar to the actual size, aligned to 512 bytes. + # Since python 3.13 we cannot use addfile() to create the member header. + # Add the tarinfo directly using public but undocumented attributes. - tar_size = offset + (disk.size + 511) & ~511 - tar.fileobj.seek(tar_size) - tar.fileobj.truncate(tar_size) + buf = disk.tobuf(tar.format, tar.encoding, tar.errors) + tar.fileobj.write(buf) + tar.members.append(disk) + + # Update the offset and position to the location of the next member. + + tar.offset = offset + (disk.size + 511) & ~511 + tar.fileobj.seek(tar.offset) + + # 6. Shrink the tar to the actual size. + + tar.fileobj.truncate(tar.offset) with tarfile.open(tar_file) as tar: members = [{"name": m.name, "size": m.size, "offset": m.offset_data} From e2668ba1ed44ad56f2f1653ff5f53b277d534fac Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 25 Feb 2025 08:06:50 +0100 Subject: [PATCH 0462/1179] iotests: Stop NBD server in test 162 before starting the next one Test 162 recently started failing for me for no obvious reasons (I did not spot any suspicious commits in this area), but looking in the 162.out.bad log file, there was a suspicious message at the end: qemu-nbd: Cannot lock pid file: Resource temporarily unavailable And indeed, the test starts the NBD server two times, without stopping the first server before running the second one, so the second one can indeed fail to lock the PID file. Thus let's make sure to stop the first server before the test continues with the second one. With this change, the test works fine for me again. Signed-off-by: Thomas Huth Message-ID: <20250225070650.387638-1-thuth@redhat.com> Reviewed-by: Eric Blake Signed-off-by: Eric Blake --- tests/qemu-iotests/162 | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/qemu-iotests/162 b/tests/qemu-iotests/162 index 94dae60d3041..956c2c5f339b 100755 --- a/tests/qemu-iotests/162 +++ b/tests/qemu-iotests/162 @@ -65,6 +65,7 @@ done $QEMU_IMG info "json:{'driver': 'nbd', 'host': 'localhost', 'port': $port}" \ | grep '^image' | sed -e "s/$port/PORT/" +_stop_nbd_server # This is a test for NBD's bdrv_refresh_filename() implementation: It expects # either host or path to be set, but it must not assume that they are set to From 57f3962bf17c088c3567d216e3eaa1b3131be5a4 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 19 Feb 2025 22:19:14 +0300 Subject: [PATCH 0463/1179] qapi: merge common parts of NbdServerOptions and nbd-server-start data Instead of comment "Keep this type consistent with the nbd-server-start arguments", we can simply merge these things. Note that each field of new base already has "since" tag, equal in both original copies. So "since" information is saved. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-ID: <20250219191914.440451-1-vsementsov@yandex-team.ru> Reviewed-by: Eric Blake Signed-off-by: Eric Blake --- blockdev-nbd.c | 4 +-- qapi/block-export.json | 72 ++++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/blockdev-nbd.c b/blockdev-nbd.c index 3f6f4ef92b44..1e3e634b87d1 100644 --- a/blockdev-nbd.c +++ b/blockdev-nbd.c @@ -219,12 +219,12 @@ void nbd_server_start_options(NbdServerOptions *arg, Error **errp) arg->tls_authz, arg->max_connections, errp); } -void qmp_nbd_server_start(SocketAddressLegacy *addr, - bool has_handshake_max_secs, +void qmp_nbd_server_start(bool has_handshake_max_secs, uint32_t handshake_max_secs, const char *tls_creds, const char *tls_authz, bool has_max_connections, uint32_t max_connections, + SocketAddressLegacy *addr, Error **errp) { SocketAddress *addr_flat = socket_address_flatten(addr); diff --git a/qapi/block-export.json b/qapi/block-export.json index 68dcec7edc50..c783e01a5328 100644 --- a/qapi/block-export.json +++ b/qapi/block-export.json @@ -9,17 +9,11 @@ { 'include': 'block-core.json' } ## -# @NbdServerOptions: -# -# Keep this type consistent with the nbd-server-start arguments. The -# only intended difference is using SocketAddress instead of -# SocketAddressLegacy. -# -# @addr: Address on which to listen. +# @NbdServerOptionsBase: # # @handshake-max-seconds: Time limit, in seconds, at which a client # that has not completed the negotiation handshake will be -# disconnected, 0 for no limit (since 10.0; default: 10). +# disconnected, or 0 for no limit (since 10.0; default: 10). # # @tls-creds: ID of the TLS credentials object (since 2.6). # @@ -32,47 +26,47 @@ # @max-connections: The maximum number of connections to allow at the # same time, 0 for unlimited. Setting this to 1 also stops the # server from advertising multiple client support (since 5.2; -# default: 100) -# -# Since: 4.2 +# default: 100). ## -{ 'struct': 'NbdServerOptions', - 'data': { 'addr': 'SocketAddress', - '*handshake-max-seconds': 'uint32', +{ 'struct': 'NbdServerOptionsBase', + 'data': { '*handshake-max-seconds': 'uint32', '*tls-creds': 'str', '*tls-authz': 'str', '*max-connections': 'uint32' } } ## -# @nbd-server-start: +# @NbdServerOptions: # -# Start an NBD server listening on the given host and port. Block -# devices can then be exported using @nbd-server-add. The NBD server -# will present them as named exports; for example, another QEMU -# instance could refer to them as "nbd:HOST:PORT:exportname=NAME". +# Keep this type consistent with the NbdServerOptionsLegacy type. The +# only intended difference is using SocketAddress instead of +# SocketAddressLegacy. +# +# @addr: Address on which to listen (since 4.2). +## +{ 'struct': 'NbdServerOptions', + 'base': 'NbdServerOptionsBase', + 'data': { 'addr': 'SocketAddress' } } + +## +# @NbdServerOptionsLegacy: # # Keep this type consistent with the NbdServerOptions type. The only # intended difference is using SocketAddressLegacy instead of # SocketAddress. # -# @addr: Address on which to listen. -# -# @handshake-max-seconds: Time limit, in seconds, at which a client -# that has not completed the negotiation handshake will be -# disconnected, or 0 for no limit (since 10.0; default: 10). -# -# @tls-creds: ID of the TLS credentials object (since 2.6). -# -# @tls-authz: ID of the QAuthZ authorization object used to validate -# the client's x509 distinguished name. This object is is only -# resolved at time of use, so can be deleted and recreated on the -# fly while the NBD server is active. If missing, it will default -# to denying access (since 4.0). +# @addr: Address on which to listen (since 1.3). +## +{ 'struct': 'NbdServerOptionsLegacy', + 'base': 'NbdServerOptionsBase', + 'data': { 'addr': 'SocketAddressLegacy' } } + +## +# @nbd-server-start: # -# @max-connections: The maximum number of connections to allow at the -# same time, 0 for unlimited. Setting this to 1 also stops the -# server from advertising multiple client support (since 5.2; -# default: 100). +# Start an NBD server listening on the given host and port. Block +# devices can then be exported using @nbd-server-add. The NBD server +# will present them as named exports; for example, another QEMU +# instance could refer to them as "nbd:HOST:PORT:exportname=NAME". # # Errors: # - if the server is already running @@ -80,11 +74,7 @@ # Since: 1.3 ## { 'command': 'nbd-server-start', - 'data': { 'addr': 'SocketAddressLegacy', - '*handshake-max-seconds': 'uint32', - '*tls-creds': 'str', - '*tls-authz': 'str', - '*max-connections': 'uint32' }, + 'data': 'NbdServerOptionsLegacy', 'allow-preconfig': true } ## From 70f98ae150ab05e4807625878d271049af23716b Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Sat, 22 Feb 2025 13:28:50 +0100 Subject: [PATCH 0464/1179] hw/misc/macio: Improve trace logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add macio_gpio_read trace event and use that in macio_gpio_read() instead of macio_gpio_write. Also change log message to match macio_timer_{read,write}. Signed-off-by: BALATON Zoltan Reviewed-by: Mark Cave-Ayland Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250222122850.9D8B84E603D@zero.eik.bme.hu> Signed-off-by: Philippe Mathieu-Daudé --- hw/misc/macio/gpio.c | 2 +- hw/misc/macio/trace-events | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hw/misc/macio/gpio.c b/hw/misc/macio/gpio.c index 7cad62819a06..4364afc84af8 100644 --- a/hw/misc/macio/gpio.c +++ b/hw/misc/macio/gpio.c @@ -135,7 +135,7 @@ static uint64_t macio_gpio_read(void *opaque, hwaddr addr, unsigned size) } } - trace_macio_gpio_write(addr, val); + trace_macio_gpio_read(addr, val); return val; } diff --git a/hw/misc/macio/trace-events b/hw/misc/macio/trace-events index ad4b9d1c08ea..055a407aebb6 100644 --- a/hw/misc/macio/trace-events +++ b/hw/misc/macio/trace-events @@ -18,7 +18,8 @@ macio_timer_read(uint64_t addr, unsigned len, uint32_t val) "read addr 0x%"PRIx6 macio_set_gpio(int gpio, bool state) "setting GPIO %d to %d" macio_gpio_irq_assert(int gpio) "asserting GPIO %d" macio_gpio_irq_deassert(int gpio) "deasserting GPIO %d" -macio_gpio_write(uint64_t addr, uint64_t val) "addr: 0x%"PRIx64" value: 0x%"PRIx64 +macio_gpio_write(uint64_t addr, uint64_t val) "addr 0x%"PRIx64" val 0x%"PRIx64 +macio_gpio_read(uint64_t addr, uint64_t val) "addr 0x%"PRIx64" val 0x%"PRIx64 # pmu.c pmu_adb_poll(int olen) "ADB autopoll, olen=%d" From d9bf3cec6752663946c886e28d9ef1fc9586edb3 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Mon, 24 Feb 2025 15:10:26 +0100 Subject: [PATCH 0465/1179] hw/misc/macio/gpio: Add constants for register bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add named constants for register bit values that should make it easier to understand what these mean. Signed-off-by: BALATON Zoltan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Mark Cave-Ayland Message-ID: <20250224141026.3B36C4E6010@zero.eik.bme.hu> Signed-off-by: Philippe Mathieu-Daudé --- hw/misc/macio/gpio.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/hw/misc/macio/gpio.c b/hw/misc/macio/gpio.c index 4364afc84af8..e87bfca1f5d3 100644 --- a/hw/misc/macio/gpio.c +++ b/hw/misc/macio/gpio.c @@ -34,6 +34,11 @@ #include "qemu/module.h" #include "trace.h" +enum MacioGPIORegisterBits { + OUT_DATA = 1, + IN_DATA = 2, + OUT_ENABLE = 4, +}; void macio_set_gpio(MacIOGPIOState *s, uint32_t gpio, bool state) { @@ -41,14 +46,14 @@ void macio_set_gpio(MacIOGPIOState *s, uint32_t gpio, bool state) trace_macio_set_gpio(gpio, state); - if (s->gpio_regs[gpio] & 4) { + if (s->gpio_regs[gpio] & OUT_ENABLE) { qemu_log_mask(LOG_GUEST_ERROR, "GPIO: Setting GPIO %d while it's an output\n", gpio); } - new_reg = s->gpio_regs[gpio] & ~2; + new_reg = s->gpio_regs[gpio] & ~IN_DATA; if (state) { - new_reg |= 2; + new_reg |= IN_DATA; } if (new_reg == s->gpio_regs[gpio]) { @@ -107,12 +112,12 @@ static void macio_gpio_write(void *opaque, hwaddr addr, uint64_t value, addr -= 8; if (addr < 36) { - value &= ~2; + value &= ~IN_DATA; - if (value & 4) { - ibit = (value & 1) << 1; + if (value & OUT_ENABLE) { + ibit = (value & OUT_DATA) << 1; } else { - ibit = s->gpio_regs[addr] & 2; + ibit = s->gpio_regs[addr] & IN_DATA; } s->gpio_regs[addr] = value | ibit; From 07b12aae50e4dc02ebce932dafd7dcae3335a915 Mon Sep 17 00:00:00 2001 From: Keoseong Park Date: Tue, 25 Feb 2025 15:41:46 +0900 Subject: [PATCH 0466/1179] hw/ufs: Add temperature event notification support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces temperature event notification support to the UFS emulation. It enables the emulated UFS device to generate temperature-related events, including high and low temperature notifications, in compliance with the UFS specification. With this feature, UFS drivers can now handle temperature exception events during testing and development within the emulated environment. This enhances validation and debugging capabilities for thermal event handling in UFS implementations. Signed-off-by: Keoseong Park Reviewed-by: Jeuk Kim Message-ID: <20250225064146epcms2p50889cb0066e2d4734f2386de325bcdf6@epcms2p5> Signed-off-by: Philippe Mathieu-Daudé --- hw/ufs/ufs.c | 78 ++++++++++++++++++++++++++++++++++++++++++++- hw/ufs/ufs.h | 2 ++ include/block/ufs.h | 13 +++++++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 1ccd6f88b69b..857de6e9c2ca 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -34,6 +34,11 @@ #define UFS_MAX_NUTMRS 8 #define UFS_MCQ_QCFGPTR 2 +/* Each value represents the temperature in celsius as (value - 80) */ +#define UFS_TEMPERATURE 120 +#define UFS_TOO_HIGH_TEMP_BOUNDARY 160 +#define UFS_TOO_LOW_TEMP_BOUNDARY 60 + static void ufs_exec_req(UfsRequest *req); static void ufs_clear_req(UfsRequest *req); @@ -838,6 +843,42 @@ static const MemoryRegionOps ufs_mmio_ops = { }, }; +static void ufs_update_ee_status(UfsHc *u) +{ + uint16_t ee_status = be16_to_cpu(u->attributes.exception_event_status); + uint8_t high_temp_thresh = u->attributes.device_too_high_temp_boundary; + uint8_t low_temp_thresh = u->attributes.device_too_low_temp_boundary; + + if (u->temperature >= high_temp_thresh) { + ee_status |= MASK_EE_TOO_HIGH_TEMP; + } else { + ee_status &= ~MASK_EE_TOO_HIGH_TEMP; + } + + if (u->temperature <= low_temp_thresh) { + ee_status |= MASK_EE_TOO_LOW_TEMP; + } else { + ee_status &= ~MASK_EE_TOO_LOW_TEMP; + } + + u->attributes.exception_event_status = cpu_to_be16(ee_status); +} + +static bool ufs_check_exception_event_alert(UfsHc *u, uint8_t trans_type) +{ + uint16_t ee_control = be16_to_cpu(u->attributes.exception_event_control); + uint16_t ee_status; + + if (trans_type != UFS_UPIU_TRANSACTION_RESPONSE) { + return false; + } + + ufs_update_ee_status(u); + + ee_status = be16_to_cpu(u->attributes.exception_event_status); + + return ee_control & ee_status; +} void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, uint8_t flags, uint8_t response, uint8_t scsi_status, @@ -848,6 +889,8 @@ void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, uint8_t flags, req->rsp_upiu.header.flags = flags; req->rsp_upiu.header.response = response; req->rsp_upiu.header.scsi_status = scsi_status; + req->rsp_upiu.header.device_inf = + ufs_check_exception_event_alert(req->hc, trans_type); req->rsp_upiu.header.data_segment_length = cpu_to_be16(data_segment_length); } @@ -1042,6 +1085,25 @@ static QueryRespCode ufs_exec_query_flag(UfsRequest *req, int op) return UFS_QUERY_RESULT_SUCCESS; } +static inline uint8_t ufs_read_device_temp(UfsHc *u) +{ + uint8_t feat_sup = u->device_desc.ufs_features_support; + bool high_temp_sup, low_temp_sup, high_temp_en, low_temp_en; + uint16_t ee_control = be16_to_cpu(u->attributes.exception_event_control); + + high_temp_sup = feat_sup & UFS_DEV_HIGH_TEMP_NOTIF; + low_temp_sup = feat_sup & UFS_DEV_LOW_TEMP_NOTIF; + high_temp_en = ee_control & MASK_EE_TOO_HIGH_TEMP; + low_temp_en = ee_control & MASK_EE_TOO_LOW_TEMP; + + if ((high_temp_sup && high_temp_en) || + (low_temp_sup && low_temp_en)) { + return u->temperature; + } + + return 0; +} + static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) { switch (idn) { @@ -1072,6 +1134,7 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) case UFS_QUERY_ATTR_IDN_EE_CONTROL: return be16_to_cpu(u->attributes.exception_event_control); case UFS_QUERY_ATTR_IDN_EE_STATUS: + ufs_update_ee_status(u); return be16_to_cpu(u->attributes.exception_event_status); case UFS_QUERY_ATTR_IDN_SECONDS_PASSED: return be32_to_cpu(u->attributes.seconds_passed); @@ -1086,7 +1149,8 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) case UFS_QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME: return u->attributes.ref_clk_gating_wait_time; case UFS_QUERY_ATTR_IDN_CASE_ROUGH_TEMP: - return u->attributes.device_case_rough_temperaure; + u->attributes.device_case_rough_temperature = ufs_read_device_temp(u); + return u->attributes.device_case_rough_temperature; case UFS_QUERY_ATTR_IDN_HIGH_TEMP_BOUND: return u->attributes.device_too_high_temp_boundary; case UFS_QUERY_ATTR_IDN_LOW_TEMP_BOUND: @@ -1677,8 +1741,12 @@ static void ufs_init_hc(UfsHc *u) u->device_desc.ud_0_base_offset = 0x16; u->device_desc.ud_config_p_length = 0x1A; u->device_desc.device_rtt_cap = 0x02; + u->device_desc.ufs_features_support = UFS_DEV_HIGH_TEMP_NOTIF | + UFS_DEV_LOW_TEMP_NOTIF; u->device_desc.queue_depth = u->params.nutrs; u->device_desc.product_revision_level = 0x04; + u->device_desc.extended_ufs_features_support = + cpu_to_be32(UFS_DEV_HIGH_TEMP_NOTIF | UFS_DEV_LOW_TEMP_NOTIF); memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor)); u->geometry_desc.length = sizeof(GeometryDescriptor); @@ -1702,9 +1770,17 @@ static void ufs_init_hc(UfsHc *u) /* configure descriptor is not supported */ u->attributes.config_descr_lock = 0x01; u->attributes.max_num_of_rtt = 0x02; + u->attributes.device_too_high_temp_boundary = UFS_TOO_HIGH_TEMP_BOUNDARY; + u->attributes.device_too_low_temp_boundary = UFS_TOO_LOW_TEMP_BOUNDARY; memset(&u->flags, 0, sizeof(u->flags)); u->flags.permanently_disable_fw_update = 1; + + /* + * The temperature value is fixed to UFS_TEMPERATURE and does not change + * dynamically + */ + u->temperature = UFS_TEMPERATURE; } static void ufs_realize(PCIDevice *pci_dev, Error **errp) diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 4bcc41f53a0d..3799d97f30d3 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -146,6 +146,8 @@ typedef struct UfsHc { /* MCQ properties */ UfsSq *sq[UFS_MAX_MCQ_QNUM]; UfsCq *cq[UFS_MAX_MCQ_QNUM]; + + uint8_t temperature; } UfsHc; static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid) diff --git a/include/block/ufs.h b/include/block/ufs.h index 57f5ea3500c3..a3ee62b027a5 100644 --- a/include/block/ufs.h +++ b/include/block/ufs.h @@ -461,7 +461,7 @@ typedef struct Attributes { uint8_t psa_state; uint32_t psa_data_size; uint8_t ref_clk_gating_wait_time; - uint8_t device_case_rough_temperaure; + uint8_t device_case_rough_temperature; uint8_t device_too_high_temp_boundary; uint8_t device_too_low_temp_boundary; uint8_t throttling_status; @@ -1073,6 +1073,11 @@ enum health_desc_param { UFS_HEALTH_DESC_PARAM_LIFE_TIME_EST_B = 0x4, }; +enum { + UFS_DEV_HIGH_TEMP_NOTIF = BIT(4), + UFS_DEV_LOW_TEMP_NOTIF = BIT(5), +}; + /* WriteBooster buffer mode */ enum { UFS_WB_BUF_MODE_LU_DEDICATED = 0x0, @@ -1091,6 +1096,12 @@ enum ufs_lu_wp_type { UFS_LU_PERM_WP = 0x02, }; +/* Exception event mask values */ +enum { + MASK_EE_TOO_HIGH_TEMP = BIT(3), + MASK_EE_TOO_LOW_TEMP = BIT(4), +}; + /* UTP QUERY Transaction Specific Fields OpCode */ enum query_opcode { UFS_UPIU_QUERY_OPCODE_NOP = 0x0, From 5f6cb3ca97c7ff4999855f561cc7b64e148cf497 Mon Sep 17 00:00:00 2001 From: Keoseong Park Date: Tue, 25 Feb 2025 15:42:43 +0900 Subject: [PATCH 0467/1179] tests/qtest/ufs-test: Add test code for the temperature feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds tests to verify the correctness of query attribute results related to the temperature feature. It ensures that querying temperature attributes returns expected values. Signed-off-by: Keoseong Park Acked-by: Fabiano Rosas Reviewed-by: Jeuk Kim Message-ID: <20250225064243epcms2p8b7b59e7bf381bd68d30a6f59b40dea9f@epcms2p8> Signed-off-by: Philippe Mathieu-Daudé --- tests/qtest/ufs-test.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index d5076bdeb549..4867ccf08a12 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -784,6 +784,30 @@ static void ufstest_query_attr_request(void *obj, void *data, g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_CASE_ROUGH_TEMP, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); + g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); + + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_HIGH_TEMP_BOUND, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); + g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(160)); + + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_LOW_TEMP_BOUND, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); + g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(60)); + /* Write Writable Attributes & Read Again */ ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, From 08c99626cb66dda00a66d4396f7e0ccf6dec75fd Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 27 Feb 2025 17:01:13 +0000 Subject: [PATCH 0468/1179] hw/arm/omap1: Convert raw printfs to qemu_log_mask() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit omap1.c is very old code, and it contains numerous calls direct to printf() for various error and information cases. In this commit, convert the printf() calls that are for either guest error or unimplemented functionality to qemu_log_mask() calls. This leaves the printf() calls that are informative or which are ifdeffed-out debug statements untouched. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250227170117.1726895-2-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/arm/omap1.c | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/hw/arm/omap1.c b/hw/arm/omap1.c index ca2eb0d15761..3c0ce5e09799 100644 --- a/hw/arm/omap1.c +++ b/hw/arm/omap1.c @@ -2559,8 +2559,9 @@ static void omap_rtc_interrupts_update(struct omap_rtc_s *s) static void omap_rtc_alarm_update(struct omap_rtc_s *s) { s->alarm_ti = mktimegm(&s->alarm_tm); - if (s->alarm_ti == -1) - printf("%s: conversion failed\n", __func__); + if (s->alarm_ti == -1) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: conversion failed\n", __func__); + } } static uint64_t omap_rtc_read(void *opaque, hwaddr addr, unsigned size) @@ -3024,8 +3025,9 @@ static void omap_mcbsp_source_tick(void *opaque) if (!s->rx_rate) return; - if (s->rx_req) - printf("%s: Rx FIFO overrun\n", __func__); + if (s->rx_req) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Rx FIFO overrun\n", __func__); + } s->rx_req = s->rx_rate << bps[(s->rcr[0] >> 5) & 7]; @@ -3070,8 +3072,9 @@ static void omap_mcbsp_sink_tick(void *opaque) if (!s->tx_rate) return; - if (s->tx_req) - printf("%s: Tx FIFO underrun\n", __func__); + if (s->tx_req) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tx FIFO underrun\n", __func__); + } s->tx_req = s->tx_rate << bps[(s->xcr[0] >> 5) & 7]; @@ -3173,7 +3176,7 @@ static uint64_t omap_mcbsp_read(void *opaque, hwaddr addr, /* Fall through. */ case 0x02: /* DRR1 */ if (s->rx_req < 2) { - printf("%s: Rx FIFO underrun\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Rx FIFO underrun\n", __func__); omap_mcbsp_rx_done(s); } else { s->tx_req -= 2; @@ -3278,8 +3281,9 @@ static void omap_mcbsp_writeh(void *opaque, hwaddr addr, } if (s->tx_req < 2) omap_mcbsp_tx_done(s); - } else - printf("%s: Tx FIFO overrun\n", __func__); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tx FIFO overrun\n", __func__); + } return; case 0x08: /* SPCR2 */ @@ -3293,8 +3297,11 @@ static void omap_mcbsp_writeh(void *opaque, hwaddr addr, case 0x0a: /* SPCR1 */ s->spcr[0] &= 0x0006; s->spcr[0] |= 0xf8f9 & value; - if (value & (1 << 15)) /* DLB */ - printf("%s: Digital Loopback mode enable attempt\n", __func__); + if (value & (1 << 15)) { /* DLB */ + qemu_log_mask(LOG_UNIMP, + "%s: Digital Loopback mode enable attempt\n", + __func__); + } if (~value & 1) { /* RRST */ s->spcr[0] &= ~6; s->rx_req = 0; @@ -3325,13 +3332,19 @@ static void omap_mcbsp_writeh(void *opaque, hwaddr addr, return; case 0x18: /* MCR2 */ s->mcr[1] = value & 0x03e3; - if (value & 3) /* XMCM */ - printf("%s: Tx channel selection mode enable attempt\n", __func__); + if (value & 3) { /* XMCM */ + qemu_log_mask(LOG_UNIMP, + "%s: Tx channel selection mode enable attempt\n", + __func__); + } return; case 0x1a: /* MCR1 */ s->mcr[0] = value & 0x03e1; - if (value & 1) /* RMCM */ - printf("%s: Rx channel selection mode enable attempt\n", __func__); + if (value & 1) { /* RMCM */ + qemu_log_mask(LOG_UNIMP, + "%s: Rx channel selection mode enable attempt\n", + __func__); + } return; case 0x1c: /* RCERA */ s->rcer[0] = value & 0xffff; @@ -3412,8 +3425,9 @@ static void omap_mcbsp_writew(void *opaque, hwaddr addr, } if (s->tx_req < 4) omap_mcbsp_tx_done(s); - } else - printf("%s: Tx FIFO overrun\n", __func__); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tx FIFO overrun\n", __func__); + } return; } From 4af3c6eca90f9e3809e9c8405727c6343c0b3819 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 27 Feb 2025 17:01:14 +0000 Subject: [PATCH 0469/1179] hw/arm/omap1: Drop ALMDEBUG ifdeffed out code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In omap1.c, there are some debug printfs in the omap_rtc_write() function that are guardad by ifdef ALMDEBUG. ALMDEBUG is never set, so this is all dead code. It's not worth the effort of converting all of these to tracepoints; a modern tracepoint approach would probably have a single tracepoint covering all the register writes anyway. Just delete the printf()s. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250227170117.1726895-3-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/arm/omap1.c | 51 -------------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/hw/arm/omap1.c b/hw/arm/omap1.c index 3c0ce5e09799..8f5bb81c96a8 100644 --- a/hw/arm/omap1.c +++ b/hw/arm/omap1.c @@ -2660,25 +2660,16 @@ static void omap_rtc_write(void *opaque, hwaddr addr, switch (offset) { case 0x00: /* SECONDS_REG */ -#ifdef ALMDEBUG - printf("RTC SEC_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_sec; s->ti += from_bcd(value); return; case 0x04: /* MINUTES_REG */ -#ifdef ALMDEBUG - printf("RTC MIN_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_min * 60; s->ti += from_bcd(value) * 60; return; case 0x08: /* HOURS_REG */ -#ifdef ALMDEBUG - printf("RTC HRS_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_hour * 3600; if (s->pm_am) { s->ti += (from_bcd(value & 0x3f) & 12) * 3600; @@ -2688,17 +2679,11 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x0c: /* DAYS_REG */ -#ifdef ALMDEBUG - printf("RTC DAY_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_mday * 86400; s->ti += from_bcd(value) * 86400; return; case 0x10: /* MONTHS_REG */ -#ifdef ALMDEBUG - printf("RTC MTH_REG <-- %02x\n", value); -#endif memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); new_tm.tm_mon = from_bcd(value); ti[0] = mktimegm(&s->current_tm); @@ -2715,9 +2700,6 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x14: /* YEARS_REG */ -#ifdef ALMDEBUG - printf("RTC YRS_REG <-- %02x\n", value); -#endif memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); new_tm.tm_year += from_bcd(value) - (new_tm.tm_year % 100); ti[0] = mktimegm(&s->current_tm); @@ -2737,25 +2719,16 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; /* Ignored */ case 0x20: /* ALARM_SECONDS_REG */ -#ifdef ALMDEBUG - printf("ALM SEC_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_sec = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x24: /* ALARM_MINUTES_REG */ -#ifdef ALMDEBUG - printf("ALM MIN_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_min = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x28: /* ALARM_HOURS_REG */ -#ifdef ALMDEBUG - printf("ALM HRS_REG <-- %02x\n", value); -#endif if (s->pm_am) s->alarm_tm.tm_hour = ((from_bcd(value & 0x3f)) % 12) + @@ -2766,33 +2739,21 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x2c: /* ALARM_DAYS_REG */ -#ifdef ALMDEBUG - printf("ALM DAY_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_mday = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x30: /* ALARM_MONTHS_REG */ -#ifdef ALMDEBUG - printf("ALM MON_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_mon = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x34: /* ALARM_YEARS_REG */ -#ifdef ALMDEBUG - printf("ALM YRS_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_year = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x40: /* RTC_CTRL_REG */ -#ifdef ALMDEBUG - printf("RTC CONTROL <-- %02x\n", value); -#endif s->pm_am = (value >> 3) & 1; s->auto_comp = (value >> 2) & 1; s->round = (value >> 1) & 1; @@ -2802,32 +2763,20 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x44: /* RTC_STATUS_REG */ -#ifdef ALMDEBUG - printf("RTC STATUSL <-- %02x\n", value); -#endif s->status &= ~((value & 0xc0) ^ 0x80); omap_rtc_interrupts_update(s); return; case 0x48: /* RTC_INTERRUPTS_REG */ -#ifdef ALMDEBUG - printf("RTC INTRS <-- %02x\n", value); -#endif s->interrupts = value; return; case 0x4c: /* RTC_COMP_LSB_REG */ -#ifdef ALMDEBUG - printf("RTC COMPLSB <-- %02x\n", value); -#endif s->comp_reg &= 0xff00; s->comp_reg |= 0x00ff & value; return; case 0x50: /* RTC_COMP_MSB_REG */ -#ifdef ALMDEBUG - printf("RTC COMPMSB <-- %02x\n", value); -#endif s->comp_reg &= 0x00ff; s->comp_reg |= 0xff00 & (value << 8); return; From 92bf1c72e0f539172bd054f494c659fc51d055e0 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 27 Feb 2025 17:01:15 +0000 Subject: [PATCH 0470/1179] hw/arm/omap1: Convert information printfs to tracepoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The omap1 code uses raw printf() statements to print information about some events; convert these to tracepoints. In particular, this will stop the functional test for the sx1 from printing the not-very-helpful note "omap_clkm_write: clocking scheme set to synchronous scalable" to the test's default.log. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250227170117.1726895-4-peter.maydell@linaro.org> [PMD: Include component name (pwl/pwt/lpg) in trace events] Signed-off-by: Philippe Mathieu-Daudé --- hw/arm/omap1.c | 26 ++++++++++++++------------ hw/arm/trace-events | 7 +++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/hw/arm/omap1.c b/hw/arm/omap1.c index 8f5bb81c96a8..3ee10b477704 100644 --- a/hw/arm/omap1.c +++ b/hw/arm/omap1.c @@ -42,6 +42,7 @@ #include "qemu/cutils.h" #include "qemu/bcd.h" #include "target/arm/cpu-qom.h" +#include "trace.h" static inline void omap_log_badwidth(const char *funcname, hwaddr addr, int sz) { @@ -1731,7 +1732,7 @@ static void omap_clkm_write(void *opaque, hwaddr addr, case 0x18: /* ARM_SYSST */ if ((s->clkm.clocking_scheme ^ (value >> 11)) & 7) { s->clkm.clocking_scheme = (value >> 11) & 7; - printf("%s: clocking scheme set to %s\n", __func__, + trace_omap1_pwl_clocking_scheme( clkschemename[s->clkm.clocking_scheme]); } s->clkm.cold_start &= value & 0x3f; @@ -2335,7 +2336,7 @@ static void omap_pwl_update(struct omap_pwl_s *s) if (output != s->output) { s->output = output; - printf("%s: Backlight now at %i/256\n", __func__, output); + trace_omap1_pwl_backlight(output); } } @@ -2470,8 +2471,8 @@ static void omap_pwt_write(void *opaque, hwaddr addr, break; case 0x04: /* VRC */ if ((value ^ s->vrc) & 1) { - if (value & 1) - printf("%s: %iHz buzz on\n", __func__, (int) + if (value & 1) { + trace_omap1_pwt_buzz( /* 1.5 MHz from a 12-MHz or 13-MHz PWT_CLK */ ((omap_clk_getrate(s->clk) >> 3) / /* Pre-multiplexer divider */ @@ -2487,8 +2488,9 @@ static void omap_pwt_write(void *opaque, hwaddr addr, /* 80/127 divider */ ((value & (1 << 5)) ? 80 : 127) / (107 * 55 * 63 * 127))); - else - printf("%s: silence!\n", __func__); + } else { + trace_omap1_pwt_silence(); + } } s->vrc = value & 0x7f; break; @@ -3494,7 +3496,7 @@ static void omap_lpg_tick(void *opaque) timer_mod(s->tm, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->on); s->cycle = !s->cycle; - printf("%s: LED is %s\n", __func__, s->cycle ? "on" : "off"); + trace_omap1_lpg_led(s->cycle ? "on" : "off"); } static void omap_lpg_update(struct omap_lpg_s *s) @@ -3514,11 +3516,11 @@ static void omap_lpg_update(struct omap_lpg_s *s) } timer_del(s->tm); - if (on == period && s->on < s->period) - printf("%s: LED is on\n", __func__); - else if (on == 0 && s->on) - printf("%s: LED is off\n", __func__); - else if (on && (on != s->on || period != s->period)) { + if (on == period && s->on < s->period) { + trace_omap1_lpg_led("on"); + } else if (on == 0 && s->on) { + trace_omap1_lpg_led("off"); + } else if (on && (on != s->on || period != s->period)) { s->cycle = 0; s->on = on; s->period = period; diff --git a/hw/arm/trace-events b/hw/arm/trace-events index 7790db780e00..f49cae1656e3 100644 --- a/hw/arm/trace-events +++ b/hw/arm/trace-events @@ -1,5 +1,12 @@ # See docs/devel/tracing.rst for syntax documentation. +# omap1.c +omap1_pwl_clocking_scheme(const char *scheme) "omap1 CLKM: clocking scheme set to %s" +omap1_pwl_backlight(int output) "omap1 PWL: backlight now at %d/256" +omap1_pwt_buzz(int freq) "omap1 PWT: %dHz buzz on" +omap1_pwt_silence(void) "omap1 PWT: buzzer silenced" +omap1_lpg_led(const char *onoff) "omap1 LPG: LED is %s" + # virt-acpi-build.c virt_acpi_setup(void) "No fw cfg or ACPI disabled. Bailing out." From a2e46dbe0a1e65ad7140120e3805cef9c99f5259 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 27 Feb 2025 17:01:16 +0000 Subject: [PATCH 0471/1179] hw/arm/omap_sx1: Remove ifdeffed out debug printf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove an ifdeffed out debug printf from the static_write() function in omap_sx1.c. In theory we could turn this into a tracepoint, but for code this old it doesn't seem worthwhile. We can add tracepoints if and when we have a reason to debug something. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-ID: <20250227170117.1726895-5-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/arm/omap_sx1.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hw/arm/omap_sx1.c b/hw/arm/omap_sx1.c index c6b0bed07960..24b404318328 100644 --- a/hw/arm/omap_sx1.c +++ b/hw/arm/omap_sx1.c @@ -76,10 +76,6 @@ static uint64_t static_read(void *opaque, hwaddr offset, static void static_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { -#ifdef SPY - printf("%s: value %" PRIx64 " %u bytes written at 0x%x\n", - __func__, value, size, (int)offset); -#endif } static const MemoryRegionOps static_ops = { From 5ae3ca2d170ea022e8b1bf63598569aad511368f Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 27 Feb 2025 17:01:17 +0000 Subject: [PATCH 0472/1179] hw/arm/versatilepb: Convert printfs to LOG_GUEST_ERROR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert some printf() calls for attempts to access nonexistent registers into LOG_GUEST_ERROR logging. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250227170117.1726895-6-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/arm/versatilepb.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c index 941616cd25bc..35766445fa47 100644 --- a/hw/arm/versatilepb.c +++ b/hw/arm/versatilepb.c @@ -27,6 +27,7 @@ #include "qom/object.h" #include "audio/audio.h" #include "target/arm/cpu-qom.h" +#include "qemu/log.h" #define VERSATILE_FLASH_ADDR 0x34000000 #define VERSATILE_FLASH_SIZE (64 * 1024 * 1024) @@ -110,7 +111,8 @@ static uint64_t vpb_sic_read(void *opaque, hwaddr offset, case 8: /* PICENABLE */ return s->pic_enable; default: - printf ("vpb_sic_read: Bad register offset 0x%x\n", (int)offset); + qemu_log_mask(LOG_GUEST_ERROR, + "vpb_sic_read: Bad register offset 0x%x\n", (int)offset); return 0; } } @@ -144,7 +146,8 @@ static void vpb_sic_write(void *opaque, hwaddr offset, vpb_sic_update_pic(s); break; default: - printf ("vpb_sic_write: Bad register offset 0x%x\n", (int)offset); + qemu_log_mask(LOG_GUEST_ERROR, + "vpb_sic_write: Bad register offset 0x%x\n", (int)offset); return; } vpb_sic_update(s); From f94a158c708666d97fe68bf39c8f8a32c0a5176d Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Sat, 1 Mar 2025 15:35:33 +0100 Subject: [PATCH 0473/1179] hw/nvram/eeprom_at24c: Use OBJECT_DECLARE_SIMPLE_TYPE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No need to open code it so use the simple object type declaration. Signed-off-by: BALATON Zoltan Reviewed-by: Philippe Mathieu-Daudé Message-ID: <08d9900af04789ede485942c8072eaa58bf52f80.1740839457.git.balaton@eik.bme.hu> Signed-off-by: Philippe Mathieu-Daudé --- hw/nvram/eeprom_at24c.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hw/nvram/eeprom_at24c.c b/hw/nvram/eeprom_at24c.c index a40cc5dd15ab..2ae03935d479 100644 --- a/hw/nvram/eeprom_at24c.c +++ b/hw/nvram/eeprom_at24c.c @@ -30,9 +30,7 @@ ## __VA_ARGS__) #define TYPE_AT24C_EE "at24c-eeprom" -typedef struct EEPROMState EEPROMState; -DECLARE_INSTANCE_CHECKER(EEPROMState, AT24C_EE, - TYPE_AT24C_EE) +OBJECT_DECLARE_SIMPLE_TYPE(EEPROMState, AT24C_EE) struct EEPROMState { I2CSlave parent_obj; From bf042a6a2ab9aceae951f6afc897986f7387a7e5 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Sat, 1 Mar 2025 15:35:34 +0100 Subject: [PATCH 0474/1179] hw/nvram/eeprom_at24c: Remove ERR macro that calls fprintf to stderr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the realize method error_setg can be used like other places there already do. The other usage can be replaced with error_report which is the preferred way instead of directly printing to stderr. Signed-off-by: BALATON Zoltan Reviewed-by: Philippe Mathieu-Daudé Message-ID: <637b92984795a385b648a84208f093947cc261e4.1740839457.git.balaton@eik.bme.hu> Signed-off-by: Philippe Mathieu-Daudé --- hw/nvram/eeprom_at24c.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/hw/nvram/eeprom_at24c.c b/hw/nvram/eeprom_at24c.c index 2ae03935d479..9f606842eb65 100644 --- a/hw/nvram/eeprom_at24c.c +++ b/hw/nvram/eeprom_at24c.c @@ -10,6 +10,7 @@ #include "qemu/osdep.h" #include "qapi/error.h" +#include "qemu/error-report.h" #include "qemu/module.h" #include "hw/i2c/i2c.h" #include "hw/nvram/eeprom_at24c.h" @@ -26,9 +27,6 @@ #define DPRINTK(FMT, ...) do {} while (0) #endif -#define ERR(FMT, ...) fprintf(stderr, TYPE_AT24C_EE " : " FMT, \ - ## __VA_ARGS__) - #define TYPE_AT24C_EE "at24c-eeprom" OBJECT_DECLARE_SIMPLE_TYPE(EEPROMState, AT24C_EE) @@ -75,8 +73,7 @@ int at24c_eeprom_event(I2CSlave *s, enum i2c_event event) if (ee->blk && ee->changed) { int ret = blk_pwrite(ee->blk, 0, ee->rsize, ee->mem, 0); if (ret < 0) { - ERR(TYPE_AT24C_EE - " : failed to write backing file\n"); + error_report("%s: failed to write backing file", __func__); } DPRINTK("Wrote to backing file\n"); } @@ -203,8 +200,9 @@ static void at24c_eeprom_realize(DeviceState *dev, Error **errp) int ret = blk_pread(ee->blk, 0, ee->rsize, ee->mem, 0); if (ret < 0) { - ERR(TYPE_AT24C_EE - " : Failed initial sync with backing file\n"); + error_setg(errp, "%s: Failed initial sync with backing file", + TYPE_AT24C_EE); + return; } DPRINTK("Reset read backing file\n"); } From 902cc3c59eb64bfff95a32de11151209779d118e Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Sat, 1 Mar 2025 15:35:35 +0100 Subject: [PATCH 0475/1179] hw/nvram/eeprom_at24c: Remove memset after g_malloc0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling memset to zero memory is not needed after g_malloc0 which already clears memory. These used to be in separate functions but after some patches the memset ended up after g_malloc0 and thus can be dropped. Fixes: 4f2c6448c3 (hw/nvram/eeprom_at24c: Make reset behavior more like hardware) Signed-off-by: BALATON Zoltan Reviewed-by: Philippe Mathieu-Daudé Message-ID: Signed-off-by: Philippe Mathieu-Daudé --- hw/nvram/eeprom_at24c.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/nvram/eeprom_at24c.c b/hw/nvram/eeprom_at24c.c index 9f606842eb65..78c81bea77f7 100644 --- a/hw/nvram/eeprom_at24c.c +++ b/hw/nvram/eeprom_at24c.c @@ -190,7 +190,6 @@ static void at24c_eeprom_realize(DeviceState *dev, Error **errp) } ee->mem = g_malloc0(ee->rsize); - memset(ee->mem, 0, ee->rsize); if (ee->init_rom) { memcpy(ee->mem, ee->init_rom, MIN(ee->init_rom_size, ee->rsize)); From 15571873d76b8fdd6cafb1d268415a1327ae3a6f Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Sat, 1 Mar 2025 15:35:36 +0100 Subject: [PATCH 0476/1179] hw/nvram/eeprom_at24c: Reorganise init to avoid overwriting values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The init_rom[] can write values to the beginning of the memory but these are overwritten by values from a backing file that covers the whole memory. init_rom[] is used only if there's no backing file (provides default content) but should not overwrite backing file content (especially leaving the file unchanged and only change it in memory). Do the init_rom[] handling only if it would not be overwritten. Signed-off-by: BALATON Zoltan Reviewed-by: Philippe Mathieu-Daudé Message-ID: Signed-off-by: Philippe Mathieu-Daudé --- hw/nvram/eeprom_at24c.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hw/nvram/eeprom_at24c.c b/hw/nvram/eeprom_at24c.c index 78c81bea77f7..ff7a21eee7f4 100644 --- a/hw/nvram/eeprom_at24c.c +++ b/hw/nvram/eeprom_at24c.c @@ -191,10 +191,6 @@ static void at24c_eeprom_realize(DeviceState *dev, Error **errp) ee->mem = g_malloc0(ee->rsize); - if (ee->init_rom) { - memcpy(ee->mem, ee->init_rom, MIN(ee->init_rom_size, ee->rsize)); - } - if (ee->blk) { int ret = blk_pread(ee->blk, 0, ee->rsize, ee->mem, 0); @@ -204,6 +200,8 @@ static void at24c_eeprom_realize(DeviceState *dev, Error **errp) return; } DPRINTK("Reset read backing file\n"); + } else if (ee->init_rom) { + memcpy(ee->mem, ee->init_rom, MIN(ee->init_rom_size, ee->rsize)); } /* From 9d71149a64f0ab051575a3f534e80918a3ca8610 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 20 Jan 2025 10:49:19 +0800 Subject: [PATCH 0477/1179] hw/intc/loongarch_ipi: Add basic hotplug framework LoongArch ipi can send interrupt to multiple CPUs, interrupt routing to CPU comes from destination physical cpu id. Here hotplug interface is added for IPI object, so that parent irq line can be connected, and routing table can be added for new created cpu. Here only basic hotplug framework is added, it is stub function. Signed-off-by: Bibo Mao --- hw/intc/loongarch_ipi.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/hw/intc/loongarch_ipi.c b/hw/intc/loongarch_ipi.c index 5376f1e08405..90bbb7ac6e32 100644 --- a/hw/intc/loongarch_ipi.c +++ b/hw/intc/loongarch_ipi.c @@ -6,6 +6,7 @@ */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "hw/boards.h" #include "qapi/error.h" #include "hw/intc/loongarch_ipi.h" @@ -76,9 +77,34 @@ static void loongarch_ipi_realize(DeviceState *dev, Error **errp) } } +static void loongarch_ipi_cpu_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + Object *obj = OBJECT(dev); + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } +} + +static void loongarch_ipi_cpu_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + Object *obj = OBJECT(dev); + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } +} + static void loongarch_ipi_class_init(ObjectClass *klass, void *data) { LoongsonIPICommonClass *licc = LOONGSON_IPI_COMMON_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); LoongarchIPIClass *lic = LOONGARCH_IPI_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); @@ -86,6 +112,8 @@ static void loongarch_ipi_class_init(ObjectClass *klass, void *data) &lic->parent_realize); licc->get_iocsr_as = get_iocsr_as; licc->cpu_by_arch_id = loongarch_cpu_by_arch_id; + hc->plug = loongarch_ipi_cpu_plug; + hc->unplug = loongarch_ipi_cpu_unplug; } static const TypeInfo loongarch_ipi_types[] = { @@ -95,6 +123,10 @@ static const TypeInfo loongarch_ipi_types[] = { .instance_size = sizeof(LoongarchIPIState), .class_size = sizeof(LoongarchIPIClass), .class_init = loongarch_ipi_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + }, } }; From 54492213e6a2be203afff03098a188a0a2413fc9 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 20 Jan 2025 12:02:30 +0800 Subject: [PATCH 0478/1179] hw/intc/loongarch_ipi: Implment cpu hotplug interface Add logic cpu allocation and cpu mapping with cpu hotplug interface. When cpu is added, connect ipi gpio irq to CPU IRQ_IPI irq pin. Signed-off-by: Bibo Mao --- hw/intc/loongarch_ipi.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/hw/intc/loongarch_ipi.c b/hw/intc/loongarch_ipi.c index 90bbb7ac6e32..b10641dd0334 100644 --- a/hw/intc/loongarch_ipi.c +++ b/hw/intc/loongarch_ipi.c @@ -49,6 +49,22 @@ static int loongarch_cpu_by_arch_id(LoongsonIPICommonState *lics, return MEMTX_ERROR; } +static IPICore *loongarch_ipi_get_cpu(LoongsonIPICommonState *lics, + DeviceState *dev) +{ + CPUClass *k = CPU_GET_CLASS(dev); + uint64_t arch_id = k->get_arch_id(CPU(dev)); + int i; + + for (i = 0; i < lics->num_cpu; i++) { + if (lics->cpu[i].arch_id == arch_id) { + return &lics->cpu[i]; + } + } + + return NULL; +} + static void loongarch_ipi_realize(DeviceState *dev, Error **errp) { LoongsonIPICommonState *lics = LOONGSON_IPI_COMMON(dev); @@ -80,25 +96,48 @@ static void loongarch_ipi_realize(DeviceState *dev, Error **errp) static void loongarch_ipi_cpu_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + LoongsonIPICommonState *lics = LOONGSON_IPI_COMMON(hotplug_dev); Object *obj = OBJECT(dev); + IPICore *core; + int index; if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { warn_report("LoongArch extioi: Invalid %s device type", object_get_typename(obj)); return; } + + core = loongarch_ipi_get_cpu(lics, dev); + if (!core) { + return; + } + + core->cpu = CPU(dev); + index = core - lics->cpu; + + /* connect ipi irq to cpu irq */ + qdev_connect_gpio_out(DEVICE(lics), index, qdev_get_gpio_in(dev, IRQ_IPI)); } static void loongarch_ipi_cpu_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + LoongsonIPICommonState *lics = LOONGSON_IPI_COMMON(hotplug_dev); Object *obj = OBJECT(dev); + IPICore *core; if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { warn_report("LoongArch extioi: Invalid %s device type", object_get_typename(obj)); return; } + + core = loongarch_ipi_get_cpu(lics, dev); + if (!core) { + return; + } + + core->cpu = NULL; } static void loongarch_ipi_class_init(ObjectClass *klass, void *data) From 50ebc3fc47f7e4133e0d5e2674a1db17ddd5e815 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 10 Feb 2025 10:19:49 +0800 Subject: [PATCH 0479/1179] hw/intc/loongarch_ipi: Notify ipi object when cpu is plugged Use hotplug_handler_plug() to nofity ipi object when cold-plug cpu is created, so that ipi can set and configure irq routing to new cpu. Signed-off-by: Bibo Mao --- hw/loongarch/virt.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 59533b058b88..0629347da129 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -317,6 +317,7 @@ static void virt_cpu_irq_init(LoongArchVirtMachineState *lvms) MachineClass *mc = MACHINE_GET_CLASS(ms); const CPUArchIdList *possible_cpus; CPUState *cs; + Error *err = NULL; /* cpu nodes */ possible_cpus = mc->possible_cpu_arch_ids(ms); @@ -326,9 +327,7 @@ static void virt_cpu_irq_init(LoongArchVirtMachineState *lvms) continue; } - /* connect ipi irq to cpu irq */ - qdev_connect_gpio_out(lvms->ipi, num, - qdev_get_gpio_in(DEVICE(cs), IRQ_IPI)); + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->ipi), DEVICE(cs), &err); /* * connect ext irq to the cpu irq From 8b4b668f6a3661885fcabcedcf812930d5577f7e Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Fri, 10 Jan 2025 16:29:47 +0800 Subject: [PATCH 0480/1179] hw/intc/loongarch_extioi: Move gpio irq initial to common code When cpu is added, it will connect gpio irq line to cpu irq. And cpu hot-add is put in common code, move gpio irq initial part into common code. Signed-off-by: Bibo Mao --- hw/intc/loongarch_extioi.c | 8 +------- hw/intc/loongarch_extioi_common.c | 6 +++++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/hw/intc/loongarch_extioi.c b/hw/intc/loongarch_extioi.c index f3055ec4d26d..a51a215e6ec2 100644 --- a/hw/intc/loongarch_extioi.c +++ b/hw/intc/loongarch_extioi.c @@ -343,7 +343,7 @@ static void loongarch_extioi_realize(DeviceState *dev, Error **errp) LoongArchExtIOIClass *lec = LOONGARCH_EXTIOI_GET_CLASS(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); Error *local_err = NULL; - int i, pin; + int i; lec->parent_realize(dev, &local_err); if (local_err) { @@ -368,12 +368,6 @@ static void loongarch_extioi_realize(DeviceState *dev, Error **errp) } else { s->status |= BIT(EXTIOI_ENABLE); } - - for (i = 0; i < s->num_cpu; i++) { - for (pin = 0; pin < LS3A_INTC_IP; pin++) { - qdev_init_gpio_out(dev, &s->cpu[i].parent_irq[pin], 1); - } - } } static void loongarch_extioi_unrealize(DeviceState *dev) diff --git a/hw/intc/loongarch_extioi_common.c b/hw/intc/loongarch_extioi_common.c index fd56253d10b9..e3a38b318a5a 100644 --- a/hw/intc/loongarch_extioi_common.c +++ b/hw/intc/loongarch_extioi_common.c @@ -16,7 +16,7 @@ static void loongarch_extioi_common_realize(DeviceState *dev, Error **errp) MachineState *machine = MACHINE(qdev_get_machine()); MachineClass *mc = MACHINE_GET_CLASS(machine); const CPUArchIdList *id_list; - int i; + int i, pin; assert(mc->possible_cpu_arch_ids); id_list = mc->possible_cpu_arch_ids(machine); @@ -30,6 +30,10 @@ static void loongarch_extioi_common_realize(DeviceState *dev, Error **errp) for (i = 0; i < s->num_cpu; i++) { s->cpu[i].arch_id = id_list->cpus[i].arch_id; s->cpu[i].cpu = CPU(id_list->cpus[i].cpu); + + for (pin = 0; pin < LS3A_INTC_IP; pin++) { + qdev_init_gpio_out(dev, &s->cpu[i].parent_irq[pin], 1); + } } } From e45c96b7d62513327324e801c325b5b6530f8e4a Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Fri, 10 Jan 2025 15:35:24 +0800 Subject: [PATCH 0481/1179] hw/intc/loongarch_extioi: Add basic hotplug framework LoongArch extioi interrupt controller routes peripheral interrupt to multiple CPUs, physical cpu id is used in interrupt routing table. Here hotplug interface is added for extioi object, so that parent irq line can be connected, and routing table can be added for new created cpu. Here only basic hotplug framework is added, it is stub function. Signed-off-by: Bibo Mao --- hw/intc/loongarch_extioi_common.c | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/hw/intc/loongarch_extioi_common.c b/hw/intc/loongarch_extioi_common.c index e3a38b318a5a..19e19a9f73a5 100644 --- a/hw/intc/loongarch_extioi_common.c +++ b/hw/intc/loongarch_extioi_common.c @@ -4,11 +4,37 @@ * Copyright (C) 2024 Loongson Technology Corporation Limited */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "qemu/module.h" #include "qapi/error.h" #include "hw/qdev-properties.h" #include "hw/intc/loongarch_extioi_common.h" #include "migration/vmstate.h" +#include "target/loongarch/cpu.h" + +static void loongarch_extioi_cpu_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + Object *obj = OBJECT(dev); + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } +} + +static void loongarch_extioi_cpu_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + Object *obj = OBJECT(dev); + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } +} static void loongarch_extioi_common_realize(DeviceState *dev, Error **errp) { @@ -107,11 +133,14 @@ static void loongarch_extioi_common_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); LoongArchExtIOICommonClass *lecc = LOONGARCH_EXTIOI_COMMON_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); device_class_set_parent_realize(dc, loongarch_extioi_common_realize, &lecc->parent_realize); device_class_set_props(dc, extioi_properties); dc->vmsd = &vmstate_loongarch_extioi; + hc->plug = loongarch_extioi_cpu_plug; + hc->unplug = loongarch_extioi_cpu_unplug; } static const TypeInfo loongarch_extioi_common_types[] = { @@ -121,6 +150,10 @@ static const TypeInfo loongarch_extioi_common_types[] = { .instance_size = sizeof(LoongArchExtIOICommonState), .class_size = sizeof(LoongArchExtIOICommonClass), .class_init = loongarch_extioi_common_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + }, .abstract = true, } }; From 8e63a7a7c2227ed014d55717c3a4de90bff426a8 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Fri, 10 Jan 2025 16:38:42 +0800 Subject: [PATCH 0482/1179] hw/intc/loongarch_extioi: Implment cpu hotplug interface When cpu is added, connect extioi gpio irq to CPU irq pin. Signed-off-by: Bibo Mao --- hw/intc/loongarch_extioi_common.c | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/hw/intc/loongarch_extioi_common.c b/hw/intc/loongarch_extioi_common.c index 19e19a9f73a5..ff3974f2a10d 100644 --- a/hw/intc/loongarch_extioi_common.c +++ b/hw/intc/loongarch_extioi_common.c @@ -12,28 +12,73 @@ #include "migration/vmstate.h" #include "target/loongarch/cpu.h" +static ExtIOICore *loongarch_extioi_get_cpu(LoongArchExtIOICommonState *s, + DeviceState *dev) +{ + CPUClass *k = CPU_GET_CLASS(dev); + uint64_t arch_id = k->get_arch_id(CPU(dev)); + int i; + + for (i = 0; i < s->num_cpu; i++) { + if (s->cpu[i].arch_id == arch_id) { + return &s->cpu[i]; + } + } + + return NULL; +} + static void loongarch_extioi_cpu_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(hotplug_dev); Object *obj = OBJECT(dev); + ExtIOICore *core; + int pin, index; if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { warn_report("LoongArch extioi: Invalid %s device type", object_get_typename(obj)); return; } + + core = loongarch_extioi_get_cpu(s, dev); + if (!core) { + return; + } + + core->cpu = CPU(dev); + index = core - s->cpu; + + /* + * connect extioi irq to the cpu irq + * cpu_pin[LS3A_INTC_IP + 2 : 2] <= intc_pin[LS3A_INTC_IP : 0] + */ + for (pin = 0; pin < LS3A_INTC_IP; pin++) { + qdev_connect_gpio_out(DEVICE(s), index * LS3A_INTC_IP + pin, + qdev_get_gpio_in(dev, pin + 2)); + } } static void loongarch_extioi_cpu_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(hotplug_dev); Object *obj = OBJECT(dev); + ExtIOICore *core; if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { warn_report("LoongArch extioi: Invalid %s device type", object_get_typename(obj)); return; } + + core = loongarch_extioi_get_cpu(s, dev); + if (!core) { + return; + } + + core->cpu = NULL; } static void loongarch_extioi_common_realize(DeviceState *dev, Error **errp) From 087a23a87c57725f8653aea9be70a2d55cf0309e Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 10 Feb 2025 10:45:13 +0800 Subject: [PATCH 0483/1179] hw/intc/loongarch_extioi: Use cpu plug notification Use hotplug_handler_plug() to nofity extioi object when cold-plug cpu is created, so that extioi can set and configure irq routing to new cpu. Signed-off-by: Bibo Mao --- hw/loongarch/virt.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 0629347da129..907f965c549c 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -312,7 +312,7 @@ static void virt_devices_init(DeviceState *pch_pic, static void virt_cpu_irq_init(LoongArchVirtMachineState *lvms) { - int num, pin; + int num; MachineState *ms = MACHINE(lvms); MachineClass *mc = MACHINE_GET_CLASS(ms); const CPUArchIdList *possible_cpus; @@ -328,15 +328,7 @@ static void virt_cpu_irq_init(LoongArchVirtMachineState *lvms) } hotplug_handler_plug(HOTPLUG_HANDLER(lvms->ipi), DEVICE(cs), &err); - - /* - * connect ext irq to the cpu irq - * cpu_pin[9:2] <= intc_pin[7:0] - */ - for (pin = 0; pin < LS3A_INTC_IP; pin++) { - qdev_connect_gpio_out(lvms->extioi, (num * LS3A_INTC_IP + pin), - qdev_get_gpio_in(DEVICE(cs), pin + 2)); - } + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->extioi), DEVICE(cs), &err); } } From 8ccf28c2f6cf54c82485de099b4566b260893445 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 26 Nov 2024 16:41:42 +0800 Subject: [PATCH 0484/1179] hw/loongarch/virt: Add CPU topology support Add topological relationships for Loongarch VCPU and initialize topology member variables. On LoongArch system there is socket/core/thread topo information, physical CPU id is calculated from CPU topo, every topo sub-field is aligned by power of 2. So it is different from logical cpu index. Co-developed-by: Xianglai Li Signed-off-by: Bibo Mao --- hw/loongarch/virt.c | 59 ++++++++++++++++++++++++++++++++++++------ target/loongarch/cpu.h | 6 +++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 907f965c549c..2bbbbbfbcf5e 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -774,6 +774,48 @@ static void virt_initfn(Object *obj) virt_flash_create(lvms); } +static void virt_get_topo_from_index(MachineState *ms, + LoongArchCPUTopo *topo, int index) +{ + topo->socket_id = index / (ms->smp.cores * ms->smp.threads); + topo->core_id = index / ms->smp.threads % ms->smp.cores; + topo->thread_id = index % ms->smp.threads; +} + +static unsigned int topo_align_up(unsigned int count) +{ + g_assert(count >= 1); + count -= 1; + return BIT(count ? 32 - clz32(count) : 0); +} + +/* + * LoongArch Reference Manual Vol1, Chapter 7.4.12 CPU Identity + * For CPU architecture, bit0 .. bit8 is valid for CPU id, max cpuid is 512 + * However for IPI/Eiointc interrupt controller, max supported cpu id for + * irq routingis 256 + * + * Here max cpu id is 256 for virt machine + */ +static int virt_get_arch_id_from_topo(MachineState *ms, LoongArchCPUTopo *topo) +{ + int arch_id, threads, cores, sockets; + + threads = topo_align_up(ms->smp.threads); + cores = topo_align_up(ms->smp.cores); + sockets = topo_align_up(ms->smp.sockets); + if ((threads * cores * sockets) > 256) { + error_report("Exceeding max cpuid 256 with sockets[%d] cores[%d]" + " threads[%d]", ms->smp.sockets, ms->smp.cores, + ms->smp.threads); + exit(1); + } + + arch_id = topo->thread_id + topo->core_id * threads; + arch_id += topo->socket_id * threads * cores; + return arch_id; +} + static bool memhp_type_supported(DeviceState *dev) { /* we only support pc dimm now */ @@ -873,8 +915,9 @@ static HotplugHandler *virt_get_hotplug_handler(MachineState *machine, static const CPUArchIdList *virt_possible_cpu_arch_ids(MachineState *ms) { - int n; + int n, arch_id; unsigned int max_cpus = ms->smp.max_cpus; + LoongArchCPUTopo topo; if (ms->possible_cpus) { assert(ms->possible_cpus->len == max_cpus); @@ -885,17 +928,17 @@ static const CPUArchIdList *virt_possible_cpu_arch_ids(MachineState *ms) sizeof(CPUArchId) * max_cpus); ms->possible_cpus->len = max_cpus; for (n = 0; n < ms->possible_cpus->len; n++) { + virt_get_topo_from_index(ms, &topo, n); + arch_id = virt_get_arch_id_from_topo(ms, &topo); ms->possible_cpus->cpus[n].type = ms->cpu_type; - ms->possible_cpus->cpus[n].arch_id = n; - + ms->possible_cpus->cpus[n].arch_id = arch_id; + ms->possible_cpus->cpus[n].vcpus_count = 1; ms->possible_cpus->cpus[n].props.has_socket_id = true; - ms->possible_cpus->cpus[n].props.socket_id = - n / (ms->smp.cores * ms->smp.threads); + ms->possible_cpus->cpus[n].props.socket_id = topo.socket_id; ms->possible_cpus->cpus[n].props.has_core_id = true; - ms->possible_cpus->cpus[n].props.core_id = - n / ms->smp.threads % ms->smp.cores; + ms->possible_cpus->cpus[n].props.core_id = topo.core_id; ms->possible_cpus->cpus[n].props.has_thread_id = true; - ms->possible_cpus->cpus[n].props.thread_id = n % ms->smp.threads; + ms->possible_cpus->cpus[n].props.thread_id = topo.thread_id; } return ms->possible_cpus; } diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 83183a33ab8f..9dc71fa7f1d1 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -393,6 +393,12 @@ typedef struct CPUArchState { #endif } CPULoongArchState; +typedef struct LoongArchCPUTopo { + int32_t socket_id; /* socket-id of this VCPU */ + int32_t core_id; /* core-id of this VCPU */ + int32_t thread_id; /* thread-id of this VCPU */ +} LoongArchCPUTopo; + /** * LoongArchCPU: * @env: #CPULoongArchState From d32fde20bd334a9fa4efab95583bc596d01a39ea Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Wed, 5 Mar 2025 09:27:59 +0800 Subject: [PATCH 0485/1179] hw/loongarch/virt: Add topo properties on CPU object Add some properties such as socket_id, core_id, thread_id and node_id on LoongArch CPU object. Co-developed-by: Xianglai Li Signed-off-by: Bibo Mao --- target/loongarch/cpu.c | 9 +++++++++ target/loongarch/cpu.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index 3788f895c157..df76ab66d5a8 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -14,6 +14,7 @@ #include "system/tcg.h" #include "system/kvm.h" #include "kvm/kvm_loongarch.h" +#include "hw/qdev-properties.h" #include "exec/exec-all.h" #include "exec/translation-block.h" #include "cpu.h" @@ -879,6 +880,13 @@ static int64_t loongarch_cpu_get_arch_id(CPUState *cs) } #endif +static const Property loongarch_cpu_properties[] = { + DEFINE_PROP_INT32("socket-id", LoongArchCPU, socket_id, 0), + DEFINE_PROP_INT32("core-id", LoongArchCPU, core_id, 0), + DEFINE_PROP_INT32("thread-id", LoongArchCPU, thread_id, 0), + DEFINE_PROP_INT32("node-id", LoongArchCPU, node_id, CPU_UNSET_NUMA_NODE_ID), +}; + static void loongarch_cpu_class_init(ObjectClass *c, void *data) { LoongArchCPUClass *lacc = LOONGARCH_CPU_CLASS(c); @@ -886,6 +894,7 @@ static void loongarch_cpu_class_init(ObjectClass *c, void *data) DeviceClass *dc = DEVICE_CLASS(c); ResettableClass *rc = RESETTABLE_CLASS(c); + device_class_set_props(dc, loongarch_cpu_properties); device_class_set_parent_realize(dc, loongarch_cpu_realizefn, &lacc->parent_realize); resettable_class_set_parent_phases(rc, NULL, loongarch_cpu_reset_hold, NULL, diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 9dc71fa7f1d1..677100bd4272 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -417,6 +417,10 @@ struct ArchCPU { OnOffAuto lasx; OnOffAuto kvm_pv_ipi; OnOffAuto kvm_steal_time; + int32_t socket_id; /* socket-id of this CPU */ + int32_t core_id; /* core-id of this CPU */ + int32_t thread_id; /* thread-id of this CPU */ + int32_t node_id; /* NUMA node of this CPU */ /* 'compatible' string for this CPU for Linux device trees */ const char *dtb_compatible; From 7bf633c53f66ea9b5666ed246de4122152910f54 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 10 Feb 2025 14:56:01 +0800 Subject: [PATCH 0486/1179] hw/loongarch/virt: Add basic cpu plug interface framework Add basic cpu hotplug interface framework, cpu hotplug interface is stub function and only framework is added here. Co-developed-by: Xianglai Li Signed-off-by: Bibo Mao --- hw/loongarch/virt.c | 29 +++++++++++++++++++++++++++++ target/loongarch/cpu.c | 13 +++++++++++++ target/loongarch/cpu.h | 1 + 3 files changed, 43 insertions(+) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 2bbbbbfbcf5e..2b4b60f718c8 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -816,6 +816,26 @@ static int virt_get_arch_id_from_topo(MachineState *ms, LoongArchCPUTopo *topo) return arch_id; } +static void virt_cpu_pre_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ +} + +static void virt_cpu_unplug_request(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ +} + +static void virt_cpu_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ +} + +static void virt_cpu_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ +} + static bool memhp_type_supported(DeviceState *dev) { /* we only support pc dimm now */ @@ -834,6 +854,8 @@ static void virt_device_pre_plug(HotplugHandler *hotplug_dev, { if (memhp_type_supported(dev)) { virt_mem_pre_plug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_pre_plug(hotplug_dev, dev, errp); } } @@ -852,6 +874,8 @@ static void virt_device_unplug_request(HotplugHandler *hotplug_dev, { if (memhp_type_supported(dev)) { virt_mem_unplug_request(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_unplug_request(hotplug_dev, dev, errp); } } @@ -870,6 +894,8 @@ static void virt_device_unplug(HotplugHandler *hotplug_dev, { if (memhp_type_supported(dev)) { virt_mem_unplug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_unplug(hotplug_dev, dev, errp); } } @@ -897,6 +923,8 @@ static void virt_device_plug_cb(HotplugHandler *hotplug_dev, } } else if (memhp_type_supported(dev)) { virt_mem_plug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_plug(hotplug_dev, dev, errp); } } @@ -906,6 +934,7 @@ static HotplugHandler *virt_get_hotplug_handler(MachineState *machine, MachineClass *mc = MACHINE_GET_CLASS(machine); if (device_is_dynamic_sysbus(mc, dev) || + object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU) || object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_IOMMU_PCI) || memhp_type_supported(dev)) { return HOTPLUG_HANDLER(machine); diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index df76ab66d5a8..8b99b8def4d0 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -647,6 +647,17 @@ static void loongarch_cpu_realizefn(DeviceState *dev, Error **errp) lacc->parent_realize(dev, errp); } +static void loongarch_cpu_unrealizefn(DeviceState *dev) +{ + LoongArchCPUClass *lacc = LOONGARCH_CPU_GET_CLASS(dev); + +#ifndef CONFIG_USER_ONLY + cpu_remove_sync(CPU(dev)); +#endif + + lacc->parent_unrealize(dev); +} + static bool loongarch_get_lsx(Object *obj, Error **errp) { return LOONGARCH_CPU(obj)->lsx != ON_OFF_AUTO_OFF; @@ -897,6 +908,8 @@ static void loongarch_cpu_class_init(ObjectClass *c, void *data) device_class_set_props(dc, loongarch_cpu_properties); device_class_set_parent_realize(dc, loongarch_cpu_realizefn, &lacc->parent_realize); + device_class_set_parent_unrealize(dc, loongarch_cpu_unrealizefn, + &lacc->parent_unrealize); resettable_class_set_parent_phases(rc, NULL, loongarch_cpu_reset_hold, NULL, &lacc->parent_phases); diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 677100bd4272..eae874c67bd6 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -439,6 +439,7 @@ struct LoongArchCPUClass { CPUClass parent_class; DeviceRealize parent_realize; + DeviceUnrealize parent_unrealize; ResettablePhases parent_phases; }; From 2cd6857f6f5b92ed6c0fa3404efaa843aaa6be0c Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Wed, 27 Nov 2024 09:58:17 +0800 Subject: [PATCH 0487/1179] hw/loongarch/virt: Implement cpu unplug interface Implement cpu unplug interfaces including virt_cpu_unplug_request() and virt_cpu_unplug(). Co-developed-by: Xianglai Li Signed-off-by: Bibo Mao --- hw/loongarch/virt.c | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 2b4b60f718c8..297f71dc0022 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -816,6 +816,19 @@ static int virt_get_arch_id_from_topo(MachineState *ms, LoongArchCPUTopo *topo) return arch_id; } +/* Find cpu slot in machine->possible_cpus by arch_id */ +static CPUArchId *virt_find_cpu_slot(MachineState *ms, int arch_id) +{ + int n; + for (n = 0; n < ms->possible_cpus->len; n++) { + if (ms->possible_cpus->cpus[n].arch_id == arch_id) { + return &ms->possible_cpus->cpus[n]; + } + } + + return NULL; +} + static void virt_cpu_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { @@ -824,11 +837,56 @@ static void virt_cpu_pre_plug(HotplugHandler *hotplug_dev, static void virt_cpu_unplug_request(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + Error *err = NULL; + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + CPUState *cs = CPU(dev); + + if (cs->cpu_index == 0) { + error_setg(&err, "hot-unplug of boot cpu(id%d=%d:%d:%d) not supported", + cs->cpu_index, cpu->socket_id, + cpu->core_id, cpu->thread_id); + error_propagate(errp, err); + return; + } + + hotplug_handler_unplug_request(HOTPLUG_HANDLER(lvms->acpi_ged), dev, &err); + if (err) { + error_propagate(errp, err); + } } static void virt_cpu_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + CPUArchId *cpu_slot; + Error *err = NULL; + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + + /* Notify ipi and extioi irqchip to remove interrupt routing to CPU */ + hotplug_handler_unplug(HOTPLUG_HANDLER(lvms->ipi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + + hotplug_handler_unplug(HOTPLUG_HANDLER(lvms->extioi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + + /* Notify acpi ged CPU removed */ + hotplug_handler_unplug(HOTPLUG_HANDLER(lvms->acpi_ged), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + + cpu_slot = virt_find_cpu_slot(MACHINE(lvms), cpu->phy_id); + cpu_slot->cpu = NULL; + return; } static void virt_cpu_plug(HotplugHandler *hotplug_dev, From ab9935d2991e620482b10a261e264f0e6e16ed61 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 10 Feb 2025 15:21:39 +0800 Subject: [PATCH 0488/1179] hw/loongarch/virt: Implement cpu plug interface Implement cpu plug interface, and cold-plug cpu uses plug interface when cpu object is created. Co-developed-by: Xianglai Li Signed-off-by: Bibo Mao --- hw/loongarch/virt.c | 88 ++++++++++++++++++++++++++++++++++++------ target/loongarch/cpu.c | 1 + 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 297f71dc0022..7788efbe4421 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -647,15 +647,13 @@ static void fw_cfg_add_memory(MachineState *ms) static void virt_init(MachineState *machine) { - LoongArchCPU *lacpu; const char *cpu_model = machine->cpu_type; MemoryRegion *address_space_mem = get_system_memory(); LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine); int i; hwaddr base, size, ram_size = machine->ram_size; - const CPUArchIdList *possible_cpus; MachineClass *mc = MACHINE_GET_CLASS(machine); - CPUState *cpu; + Object *cpuobj; if (!cpu_model) { cpu_model = LOONGARCH_CPU_TYPE_NAME("la464"); @@ -671,14 +669,15 @@ static void virt_init(MachineState *machine) memory_region_add_subregion(&lvms->system_iocsr, 0, &lvms->iocsr_mem); /* Init CPUs */ - possible_cpus = mc->possible_cpu_arch_ids(machine); - for (i = 0; i < possible_cpus->len; i++) { - cpu = cpu_create(machine->cpu_type); - cpu->cpu_index = i; - machine->possible_cpus->cpus[i].cpu = cpu; - lacpu = LOONGARCH_CPU(cpu); - lacpu->phy_id = machine->possible_cpus->cpus[i].arch_id; - lacpu->env.address_space_iocsr = &lvms->as_iocsr; + mc->possible_cpu_arch_ids(machine); + for (i = 0; i < machine->smp.cpus; i++) { + cpuobj = object_new(machine->cpu_type); + if (cpuobj == NULL) { + error_report("Fail to create object with type %s ", + machine->cpu_type); + exit(EXIT_FAILURE); + } + qdev_realize_and_unref(DEVICE(cpuobj), NULL, &error_fatal); } fw_cfg_add_memory(machine); @@ -829,9 +828,52 @@ static CPUArchId *virt_find_cpu_slot(MachineState *ms, int arch_id) return NULL; } +/* Find cpu slot for cold-plut CPU object where cpu is NULL */ +static CPUArchId *virt_find_empty_cpu_slot(MachineState *ms) +{ + int n; + for (n = 0; n < ms->possible_cpus->len; n++) { + if (ms->possible_cpus->cpus[n].cpu == NULL) { + return &ms->possible_cpus->cpus[n]; + } + } + + return NULL; +} + static void virt_cpu_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + MachineState *ms = MACHINE(OBJECT(hotplug_dev)); + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + CPUState *cs = CPU(dev); + CPUArchId *cpu_slot; + Error *err = NULL; + LoongArchCPUTopo topo; + + if (lvms->acpi_ged) { + error_setg(&err, "CPU hotplug not supported"); + goto out; + } else { + /* For cold-add cpu, find empty cpu slot */ + cpu_slot = virt_find_empty_cpu_slot(ms); + topo.socket_id = cpu_slot->props.socket_id; + topo.core_id = cpu_slot->props.core_id; + topo.thread_id = cpu_slot->props.thread_id; + object_property_set_int(OBJECT(dev), "socket-id", topo.socket_id, NULL); + object_property_set_int(OBJECT(dev), "core-id", topo.core_id, NULL); + object_property_set_int(OBJECT(dev), "thread-id", topo.thread_id, NULL); + } + + cpu->env.address_space_iocsr = &lvms->as_iocsr; + cpu->phy_id = cpu_slot->arch_id; + cs->cpu_index = cpu_slot - ms->possible_cpus->cpus; + numa_cpu_pre_plug(cpu_slot, dev, &err); +out: + if (err) { + error_propagate(errp, err); + } } static void virt_cpu_unplug_request(HotplugHandler *hotplug_dev, @@ -892,6 +934,30 @@ static void virt_cpu_unplug(HotplugHandler *hotplug_dev, static void virt_cpu_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + CPUArchId *cpu_slot; + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + Error *err = NULL; + + cpu_slot = virt_find_cpu_slot(MACHINE(lvms), cpu->phy_id); + cpu_slot->cpu = CPU(dev); + if (lvms->ipi) { + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->ipi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + } + + if (lvms->extioi) { + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->extioi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + } + + return; } static bool memhp_type_supported(DeviceState *dev) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index 8b99b8def4d0..b2961d860587 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -932,6 +932,7 @@ static void loongarch_cpu_class_init(ObjectClass *c, void *data) #ifdef CONFIG_TCG cc->tcg_ops = &loongarch_tcg_ops; #endif + dc->user_creatable = true; } static const gchar *loongarch32_gdb_arch_name(CPUState *cs) From 25cdac981fd012d9b96d8db9a809841c7e521154 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 10 Feb 2025 16:47:00 +0800 Subject: [PATCH 0489/1179] hw/loongarch/virt: Update the ACPI table for hotplug cpu On LoongArch virt machine, ACPI GED hardware is used for CPU hotplug handler, here CPU hotplug support feature is added based on GED handler, also CPU scan and reject method is added about CPU device in DSDT table. Co-developed-by: Xianglai Li Signed-off-by: Bibo Mao --- hw/loongarch/Kconfig | 1 + hw/loongarch/virt-acpi-build.c | 35 +++++++++++++++++++++++++++++++--- hw/loongarch/virt.c | 10 ++++++++++ include/hw/loongarch/virt.h | 1 + 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig index fe1c6feac138..bb2838b7b53a 100644 --- a/hw/loongarch/Kconfig +++ b/hw/loongarch/Kconfig @@ -17,6 +17,7 @@ config LOONGARCH_VIRT select LOONGARCH_EXTIOI select LS7A_RTC select SMBIOS + select ACPI_CPU_HOTPLUG select ACPI_PCI select ACPI_HW_REDUCED select FW_CFG_DMA diff --git a/hw/loongarch/virt-acpi-build.c b/hw/loongarch/virt-acpi-build.c index 9ca88d63aebd..fced6c445ac6 100644 --- a/hw/loongarch/virt-acpi-build.c +++ b/hw/loongarch/virt-acpi-build.c @@ -47,6 +47,22 @@ #define ACPI_BUILD_DPRINTF(fmt, ...) #endif +static void virt_madt_cpu_entry(int uid, + const CPUArchIdList *apic_ids, + GArray *entry, bool force_enabled) +{ + uint32_t flags, apic_id = apic_ids->cpus[uid].arch_id; + + flags = apic_ids->cpus[uid].cpu || force_enabled ? 1 /* Enabled */ : 0; + + /* Rev 1.0b, Table 5-13 Processor Local APIC Structure */ + build_append_int_noprefix(entry, 0, 1); /* Type */ + build_append_int_noprefix(entry, 8, 1); /* Length */ + build_append_int_noprefix(entry, uid, 1); /* ACPI Processor ID */ + build_append_int_noprefix(entry, apic_id, 1); /* APIC ID */ + build_append_int_noprefix(entry, flags, 4); /* Flags */ +} + /* build FADT */ static void init_common_fadt_data(AcpiFadtData *data) { @@ -112,7 +128,7 @@ build_madt(GArray *table_data, BIOSLinker *linker, MachineState *ms = MACHINE(lvms); MachineClass *mc = MACHINE_GET_CLASS(ms); const CPUArchIdList *arch_ids = mc->possible_cpu_arch_ids(ms); - int i, arch_id; + int i, arch_id, flags; AcpiTable table = { .sig = "APIC", .rev = 1, .oem_id = lvms->oem_id, .oem_table_id = lvms->oem_table_id }; @@ -125,13 +141,13 @@ build_madt(GArray *table_data, BIOSLinker *linker, for (i = 0; i < arch_ids->len; i++) { /* Processor Core Interrupt Controller Structure */ arch_id = arch_ids->cpus[i].arch_id; - + flags = arch_ids->cpus[i].cpu ? 1 : 0; build_append_int_noprefix(table_data, 17, 1); /* Type */ build_append_int_noprefix(table_data, 15, 1); /* Length */ build_append_int_noprefix(table_data, 1, 1); /* Version */ build_append_int_noprefix(table_data, i, 4); /* ACPI Processor ID */ build_append_int_noprefix(table_data, arch_id, 4); /* Core ID */ - build_append_int_noprefix(table_data, 1, 4); /* Flags */ + build_append_int_noprefix(table_data, flags, 4); /* Flags */ } /* Extend I/O Interrupt Controller Structure */ @@ -338,6 +354,7 @@ build_la_ged_aml(Aml *dsdt, MachineState *machine) { uint32_t event; LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine); + CPUHotplugFeatures opts; build_ged_aml(dsdt, "\\_SB."GED_DEVICE, HOTPLUG_HANDLER(lvms->acpi_ged), @@ -350,6 +367,18 @@ build_la_ged_aml(Aml *dsdt, MachineState *machine) AML_SYSTEM_MEMORY, VIRT_GED_MEM_ADDR); } + + if (event & ACPI_GED_CPU_HOTPLUG_EVT) { + opts.acpi_1_compatible = false; + opts.has_legacy_cphp = false; + opts.fw_unplugs_cpu = false; + opts.smi_path = NULL; + + build_cpus_aml(dsdt, machine, opts, virt_madt_cpu_entry, + VIRT_GED_CPUHP_ADDR, "\\_SB", + AML_GED_EVT_CPU_SCAN_METHOD, AML_SYSTEM_MEMORY); + } + acpi_dsdt_add_power_button(dsdt); } diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 7788efbe4421..8aba14556681 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -187,11 +187,17 @@ static DeviceState *create_acpi_ged(DeviceState *pch_pic, { DeviceState *dev; MachineState *ms = MACHINE(lvms); + MachineClass *mc = MACHINE_GET_CLASS(lvms); uint32_t event = ACPI_GED_PWR_DOWN_EVT; if (ms->ram_slots) { event |= ACPI_GED_MEM_HOTPLUG_EVT; } + + if (mc->has_hotpluggable_cpus) { + event |= ACPI_GED_CPU_HOTPLUG_EVT; + } + dev = qdev_new(TYPE_ACPI_GED); qdev_prop_set_uint32(dev, "ged-event", event); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); @@ -203,6 +209,10 @@ static DeviceState *create_acpi_ged(DeviceState *pch_pic, /* ged regs used for reset and power down */ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, VIRT_GED_REG_ADDR); + if (mc->has_hotpluggable_cpus) { + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 3, VIRT_GED_CPUHP_ADDR); + } + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, qdev_get_gpio_in(pch_pic, VIRT_SCI_IRQ - VIRT_GSI_BASE)); return dev; diff --git a/include/hw/loongarch/virt.h b/include/hw/loongarch/virt.h index 2e7cdfaef054..2b7d19953f43 100644 --- a/include/hw/loongarch/virt.h +++ b/include/hw/loongarch/virt.h @@ -30,6 +30,7 @@ #define VIRT_GED_EVT_ADDR 0x100e0000 #define VIRT_GED_MEM_ADDR (VIRT_GED_EVT_ADDR + ACPI_GED_EVT_SEL_LEN) #define VIRT_GED_REG_ADDR (VIRT_GED_MEM_ADDR + MEMORY_HOTPLUG_IO_LEN) +#define VIRT_GED_CPUHP_ADDR (VIRT_GED_REG_ADDR + ACPI_GED_REG_COUNT) #define COMMAND_LINE_SIZE 512 From a97cceb1d7ed05070dc52e7850112f906d7e3512 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Mon, 10 Feb 2025 17:03:29 +0800 Subject: [PATCH 0490/1179] hw/loongarch/virt: Enable cpu hotplug feature on virt machine On virt machine, enable CPU hotplug feature has_hotpluggable_cpus. For hot-added CPUs, there is socket-id/core-id/thread-id property set, arch_id can be caculated from these properties. So that cpu slot can be searched from its arch_id. Co-developed-by: Xianglai Li Signed-off-by: Bibo Mao --- hw/loongarch/virt.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 8aba14556681..a5840ff96857 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -861,10 +861,42 @@ static void virt_cpu_pre_plug(HotplugHandler *hotplug_dev, CPUArchId *cpu_slot; Error *err = NULL; LoongArchCPUTopo topo; + int arch_id; if (lvms->acpi_ged) { - error_setg(&err, "CPU hotplug not supported"); - goto out; + if ((cpu->thread_id < 0) || (cpu->thread_id >= ms->smp.threads)) { + error_setg(&err, + "Invalid thread-id %u specified, must be in range 1:%u", + cpu->thread_id, ms->smp.threads - 1); + goto out; + } + + if ((cpu->core_id < 0) || (cpu->core_id >= ms->smp.cores)) { + error_setg(&err, + "Invalid core-id %u specified, must be in range 1:%u", + cpu->core_id, ms->smp.cores - 1); + goto out; + } + + if ((cpu->socket_id < 0) || (cpu->socket_id >= ms->smp.sockets)) { + error_setg(&err, + "Invalid socket-id %u specified, must be in range 1:%u", + cpu->socket_id, ms->smp.sockets - 1); + goto out; + } + + topo.socket_id = cpu->socket_id; + topo.core_id = cpu->core_id; + topo.thread_id = cpu->thread_id; + arch_id = virt_get_arch_id_from_topo(ms, &topo); + cpu_slot = virt_find_cpu_slot(ms, arch_id); + if (CPU(cpu_slot->cpu)) { + error_setg(&err, + "cpu(id%d=%d:%d:%d) with arch-id %" PRIu64 " exists", + cs->cpu_index, cpu->socket_id, cpu->core_id, + cpu->thread_id, cpu_slot->arch_id); + goto out; + } } else { /* For cold-add cpu, find empty cpu slot */ cpu_slot = virt_find_empty_cpu_slot(ms); @@ -967,6 +999,13 @@ static void virt_cpu_plug(HotplugHandler *hotplug_dev, } } + if (lvms->acpi_ged) { + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->acpi_ged), dev, &err); + if (err) { + error_propagate(errp, err); + } + } + return; } @@ -1149,6 +1188,7 @@ static void virt_class_init(ObjectClass *oc, void *data) mc->numa_mem_supported = true; mc->auto_enable_numa_with_memhp = true; mc->auto_enable_numa_with_memdev = true; + mc->has_hotpluggable_cpus = true; mc->get_hotplug_handler = virt_get_hotplug_handler; mc->default_nic = "virtio-net-pci"; hc->plug = virt_device_plug_cb; From 0a629c827300d514cc1f61806414d214fcf75051 Mon Sep 17 00:00:00 2001 From: Xianglai Li Date: Mon, 3 Mar 2025 14:31:33 +0800 Subject: [PATCH 0491/1179] target/loongarch: Adjust the cpu reset action to a proper position The commit 5a99a10da6cf ("target/loongarch: fix vcpu reset command word issue") fixes the error in the cpu reset ioctl command word delivery process, so that the command word can be delivered correctly, and adds the judgment and processing of the error return value, which exposes another problem that under loongarch, the cpu reset action is earlier than the creation of vcpu. An error occurs when the cpu reset command is sent. Now adjust the order of cpu reset and vcpu create actions to fix this problem Signed-off-by: Xianglai Li Acked-by: Igor Mammedov Signed-off-by: Bibo Mao --- target/loongarch/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index b2961d860587..ac514a15fba8 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -641,8 +641,8 @@ static void loongarch_cpu_realizefn(DeviceState *dev, Error **errp) loongarch_cpu_register_gdb_regs_for_features(cs); - cpu_reset(cs); qemu_init_vcpu(cs); + cpu_reset(cs); lacc->parent_realize(dev, errp); } From e4d6c94e674a3162857dbd78c27b1d7a1b2c250f Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:07 +0100 Subject: [PATCH 0492/1179] ui/console-vc: introduce parsing of the 'ESC ( ' sequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change introduces parsing of the 'ESC ( ' sequence, which is supposed to change character set [1]. In the QEMU case, the introduced parsing logic does not actually change the character set, but simply parses the sequence and does not let output of a tool to be corrupted with leftovers: `top` sends 'ESC ( B', so if character sequence is not parsed correctly, chracter 'B' appears in the output: Btop - 11:08:42 up 5 min, 1 user, load average: 0BB Tasks:B 158 Btotal,B 1 Brunning,B 157 Bsleeping,B 0 BsBB %Cpu(s):B 0.0 Bus,B 0.0 Bsy,B 0.0 Bni,B 99.8 Bid,B 0.2 BB MiB Mem :B 7955.6 Btotal,B 7778.6 Bfree,B 79.6 BB MiB Swap:B 0.0 Btotal,B 0.0 Bfree,B 0.0 BB PID USER PR NI VIRT RES SHR S B B 735 root 20 0 9328 3540 3152 R B B 1 root 20 0 20084 10904 8404 S B B 2 root 20 0 0 0 0 S B [1] https://vt100.net/docs/vt100-ug/chapter3.html#SCS Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-2-r.peniaev@gmail.com> --- ui/console-vc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ui/console-vc.c b/ui/console-vc.c index fe20579832a5..90ff0ffda8c5 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -42,6 +42,8 @@ enum TTYState { TTY_STATE_NORM, TTY_STATE_ESC, TTY_STATE_CSI, + TTY_STATE_G0, + TTY_STATE_G1, }; typedef struct QemuTextConsole { @@ -694,6 +696,10 @@ static void vc_putchar(VCChardev *vc, int ch) vc->esc_params[i] = 0; vc->nb_esc_params = 0; vc->state = TTY_STATE_CSI; + } else if (ch == '(') { + vc->state = TTY_STATE_G0; + } else if (ch == ')') { + vc->state = TTY_STATE_G1; } else { vc->state = TTY_STATE_NORM; } @@ -844,6 +850,16 @@ static void vc_putchar(VCChardev *vc, int ch) } break; } + break; + case TTY_STATE_G0: /* set character sets */ + case TTY_STATE_G1: /* set character sets */ + switch (ch) { + case 'B': + /* Latin-1 map */ + break; + } + vc->state = TTY_STATE_NORM; + break; } } From 0a9f48e9ead2b067e8d7058e7bc7a1d68721882d Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:08 +0100 Subject: [PATCH 0493/1179] ui/console-vc: report to the application instead of screen rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Terminal Device Status Report (DSR) [1] should be sent to an application, not rendered to the screen. This patch fixes rendering of terminal report, which appear only on the graphical screen of the terminal (console "vc") and can be reproduced by the following command: echo -en '\e[6n'; IFS='[;' read -sdR _ row col; echo $row:$col Command requests cursor position and waits for terminal response, but instead, the response is rendered to the graphical screen and never sent to an application. Why bother? Busybox shell (ash) in Alpine distribution requests cursor position on each shell prompt (once is pressed), which makes a prompt on a graphical screen corrupted with repeating Cursor Position Report (CPR) [2]: [root@alpine ~]# \033[57;1R] Which is very annoying and incorrect. [1] https://vt100.net/docs/vt100-ug/chapter3.html#DSR [2] https://vt100.net/docs/vt100-ug/chapter3.html#CPR Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-3-r.peniaev@gmail.com> --- ui/console-vc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/console-vc.c b/ui/console-vc.c index 90ff0ffda8c5..d512f57e10a9 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -617,10 +617,9 @@ static void vc_put_one(VCChardev *vc, int ch) static void vc_respond_str(VCChardev *vc, const char *buf) { - while (*buf) { - vc_put_one(vc, *buf); - buf++; - } + QemuTextConsole *s = vc->console; + + qemu_chr_be_write(s->chr, (const uint8_t *)buf, strlen(buf)); } /* set cursor, checking bounds */ From 40339871da115b68e01f1da9ce2f8175e8f65d3c Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:09 +0100 Subject: [PATCH 0494/1179] ui/console-vc: report cursor position in the screen not in the scroll buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The format of the CSI cursor position report is `ESC[row;columnR`, where `row` is a row of a cursor in the screen, not in the scrollback buffer. What's the difference? Let's say the terminal screen has 24 lines, no matter how long the scrollback buffer may be, the last line is the 24th. For example the following command can be executed in xterm on the last screen line: $ echo -en '\e[6n'; IFS='[;' read -sdR _ row col; echo $row:$col 24:1 It shows the cursor position on the current screen and not relative to the backscroll buffer. Before this change the row number was always increasing for the QEMU VC and represents the cursor position relative to the backscroll buffer. Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-4-r.peniaev@gmail.com> --- ui/console-vc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/console-vc.c b/ui/console-vc.c index d512f57e10a9..87f57f1c52c6 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -827,8 +827,7 @@ static void vc_putchar(VCChardev *vc, int ch) case 6: /* report cursor position */ response = g_strdup_printf("\033[%d;%dR", - (s->y_base + s->y) % s->total_height + 1, - s->x + 1); + s->y + 1, s->x + 1); vc_respond_str(vc, response); break; } From 1a0fd7838a9dddf91241bc9faa471dc9dec04329 Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:10 +0100 Subject: [PATCH 0495/1179] ui/console-vc: add support for cursor DECSC and DECRC commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are aliases for save and restore cursor commands: * save cursor `ESC 7` (DEC Save Cursor [1], older VT100) `ESC [ s` (CSI Save Cursor, standard ANSI) * load cursor `ESC 8` (DEC Restore Cursor [2], older VT100) `ESC [ u` (CSI Restore Cursor, standard ANSI) This change introduces older DEC sequencies for compatibility with some scripts (for example [3]) and tools. This change also adds saving and restoring of character attributes, which is according to the VT spec [1][2] [1] https://vt100.net/docs/vt510-rm/DECSC.html [2] https://vt100.net/docs/vt510-rm/DECRC.html [3] https://wiki.archlinux.org/title/Working_with_the_serial_console#Resizing_a_terminal Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-5-r.peniaev@gmail.com> --- ui/console-vc.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/ui/console-vc.c b/ui/console-vc.c index 87f57f1c52c6..522adc2806c8 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -90,6 +90,7 @@ struct VCChardev { int esc_params[MAX_ESC_PARAMS]; int nb_esc_params; TextAttributes t_attrib; /* currently active text attributes */ + TextAttributes t_attrib_saved; int x_saved, y_saved; }; typedef struct VCChardev VCChardev; @@ -644,6 +645,31 @@ static void vc_set_cursor(VCChardev *vc, int x, int y) s->y = y; } +/** + * vc_save_cursor() - saves cursor position and character attributes. + */ +static void vc_save_cursor(VCChardev *vc) +{ + QemuTextConsole *s = vc->console; + + vc->x_saved = s->x; + vc->y_saved = s->y; + vc->t_attrib_saved = vc->t_attrib; +} + +/** + * vc_restore_cursor() - restores cursor position and character + * attributes from saved state. + */ +static void vc_restore_cursor(VCChardev *vc) +{ + QemuTextConsole *s = vc->console; + + s->x = vc->x_saved; + s->y = vc->y_saved; + vc->t_attrib = vc->t_attrib_saved; +} + static void vc_putchar(VCChardev *vc, int ch) { QemuTextConsole *s = vc->console; @@ -699,6 +725,12 @@ static void vc_putchar(VCChardev *vc, int ch) vc->state = TTY_STATE_G0; } else if (ch == ')') { vc->state = TTY_STATE_G1; + } else if (ch == '7') { + vc_save_cursor(vc); + vc->state = TTY_STATE_NORM; + } else if (ch == '8') { + vc_restore_cursor(vc); + vc->state = TTY_STATE_NORM; } else { vc->state = TTY_STATE_NORM; } @@ -833,14 +865,10 @@ static void vc_putchar(VCChardev *vc, int ch) } break; case 's': - /* save cursor position */ - vc->x_saved = s->x; - vc->y_saved = s->y; + vc_save_cursor(vc); break; case 'u': - /* restore cursor position */ - s->x = vc->x_saved; - s->y = vc->y_saved; + vc_restore_cursor(vc); break; default: trace_console_putchar_unhandled(ch); From a97ef3624437c5a5fbc8bd45e2a206d10ca840be Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 26 Feb 2025 08:59:11 +0100 Subject: [PATCH 0496/1179] ui/console-vc: implement DCH (delete) and ICH (insert) commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch implements DCH (delete character) and ICH (insert character) commands. DCH - Delete Character: "As characters are deleted, the remaining characters between the cursor and right margin move to the left. Character attributes move with the characters. The terminal adds blank spaces with no visual character attributes at the right margin. DCH has no effect outside the scrolling margins" [1]. ICH - Insert Character: "The ICH sequence inserts Pn blank characters with the normal character attribute. The cursor remains at the beginning of the blank characters. Text between the cursor and right margin moves to the right. Characters scrolled past the right margin are lost. ICH has no effect outside the scrolling margins" [2]. Without these commands console is barely usable. [1] https://vt100.net/docs/vt510-rm/DCH.html [1] https://vt100.net/docs/vt510-rm/ICH.html Signed-off-by: Roman Penyaev Cc: "Marc-André Lureau" Cc: qemu-devel@nongnu.org Reviewed-by: Marc-André Lureau Message-ID: <20250226075913.353676-6-r.peniaev@gmail.com> --- ui/console-vc.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/ui/console-vc.c b/ui/console-vc.c index 522adc2806c8..df1341513d53 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -645,6 +645,88 @@ static void vc_set_cursor(VCChardev *vc, int x, int y) s->y = y; } +/** + * vc_csi_P() - (DCH) deletes one or more characters from the cursor + * position to the right. As characters are deleted, the remaining + * characters between the cursor and right margin move to the + * left. Character attributes move with the characters. + */ +static void vc_csi_P(struct VCChardev *vc, unsigned int nr) +{ + QemuTextConsole *s = vc->console; + TextCell *c1, *c2; + unsigned int x1, x2, y; + unsigned int end, len; + + if (!nr) { + nr = 1; + } + if (nr > s->width - s->x) { + nr = s->width - s->x; + if (!nr) { + return; + } + } + + x1 = s->x; + x2 = s->x + nr; + len = s->width - x2; + if (len) { + y = (s->y_base + s->y) % s->total_height; + c1 = &s->cells[y * s->width + x1]; + c2 = &s->cells[y * s->width + x2]; + memmove(c1, c2, len * sizeof(*c1)); + for (end = x1 + len; x1 < end; x1++) { + vc_update_xy(vc, x1, s->y); + } + } + /* Clear the rest */ + for (; x1 < s->width; x1++) { + vc_clear_xy(vc, x1, s->y); + } +} + +/** + * vc_csi_at() - (ICH) inserts `nr` blank characters with the default + * character attribute. The cursor remains at the beginning of the + * blank characters. Text between the cursor and right margin moves to + * the right. Characters scrolled past the right margin are lost. + */ +static void vc_csi_at(struct VCChardev *vc, unsigned int nr) +{ + QemuTextConsole *s = vc->console; + TextCell *c1, *c2; + unsigned int x1, x2, y; + unsigned int end, len; + + if (!nr) { + nr = 1; + } + if (nr > s->width - s->x) { + nr = s->width - s->x; + if (!nr) { + return; + } + } + + x1 = s->x + nr; + x2 = s->x; + len = s->width - x1; + if (len) { + y = (s->y_base + s->y) % s->total_height; + c1 = &s->cells[y * s->width + x1]; + c2 = &s->cells[y * s->width + x2]; + memmove(c1, c2, len * sizeof(*c1)); + for (end = x1 + len; x1 < end; x1++) { + vc_update_xy(vc, x1, s->y); + } + } + /* Insert blanks */ + for (x1 = s->x; x1 < s->x + nr; x1++) { + vc_clear_xy(vc, x1, s->y); + } +} + /** * vc_save_cursor() - saves cursor position and character attributes. */ @@ -847,6 +929,9 @@ static void vc_putchar(VCChardev *vc, int ch) break; } break; + case 'P': + vc_csi_P(vc, vc->esc_params[0]); + break; case 'm': vc_handle_escape(vc); break; @@ -870,6 +955,9 @@ static void vc_putchar(VCChardev *vc, int ch) case 'u': vc_restore_cursor(vc); break; + case '@': + vc_csi_at(vc, vc->esc_params[0]); + break; default: trace_console_putchar_unhandled(ch); break; From 46f83c898a6658921fed57f98af6d505ab78a6e4 Mon Sep 17 00:00:00 2001 From: Haoqian He Date: Tue, 25 Feb 2025 18:45:26 +0800 Subject: [PATCH 0497/1179] chardev: use remoteAddr if the chardev is client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the chardev is client, the socket file path in localAddr may be NULL. This is because the socket path comes from getsockname(), according to man page, getsockname() returns the current address bound by the socket sockfd. If the chardev is client, it's socket is unbound sockfd. Therefore, when computing the client chardev socket file path, using remoteAddr is more appropriate. Signed-off-by: Haoqian He Acked-by: Marc-André Lureau Message-ID: <20250225104526.2924175-1-haoqian.he@smartx.com> --- chardev/char-socket.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 91496ceda90c..2f842f9f88b9 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -571,9 +571,13 @@ static char *qemu_chr_compute_filename(SocketChardev *s) switch (ss->ss_family) { case AF_UNIX: - return g_strdup_printf("unix:%s%s", - ((struct sockaddr_un *)(ss))->sun_path, - s->is_listen ? ",server=on" : ""); + if (s->is_listen) { + return g_strdup_printf("unix:%s,server=on", + ((struct sockaddr_un *)(ss))->sun_path); + } else { + return g_strdup_printf("unix:%s", + ((struct sockaddr_un *)(ps))->sun_path); + } case AF_INET6: left = "["; right = "]"; From 3e1683485656c095860a8dfbe39ab2d0664b84d9 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 27 Feb 2025 16:06:15 -0600 Subject: [PATCH 0498/1179] nbd: Defer trace init until after daemonization At least the simple trace backend works by spawning a helper thread, and setting up an atexit() handler that coordinates completion with the helper thread. But since atexit registrations survive fork() but helper threads do not, this means that qemu-nbd configured to use the simple trace will deadlock waiting for a thread that no longer exists when it has daemonized. Better is to follow the example of vl.c: don't call any setup functions that might spawn helper threads until we are in the final process that will be doing the work worth tracing. Tested by configuring with --enable-trace-backends=simple, then running qemu-nbd --fork --trace=nbd_\*,file=qemu-nbd.trace -f raw -r README.rst followed by `nbdinfo nbd://localhost`, and observing that the trace file is now created without hanging. Reported-by: Thomas Huth Signed-off-by: Eric Blake Message-ID: <20250227220625.870246-2-eblake@redhat.com> Reviewed-by: Thomas Huth --- qemu-nbd.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/qemu-nbd.c b/qemu-nbd.c index 05b61da51ea3..ed5895861bb0 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -852,10 +852,6 @@ int main(int argc, char **argv) export_name = ""; } - if (!trace_init_backends()) { - exit(1); - } - trace_init_file(); qemu_set_log(LOG_TRACE, &error_fatal); socket_activation = check_socket_activation(); @@ -1045,6 +1041,18 @@ int main(int argc, char **argv) #endif /* WIN32 */ } + /* + * trace_init must be done after daemonization. Why? Because at + * least the simple backend spins up a helper thread as well as an + * atexit() handler that waits on that thread, but the helper + * thread won't survive a fork, leading to deadlock in the child + * if we initialized pre-fork. + */ + if (!trace_init_backends()) { + exit(1); + } + trace_init_file(); + if (opts.device != NULL && sockpath == NULL) { sockpath = g_malloc(128); snprintf(sockpath, 128, SOCKET_PATH, basename(opts.device)); From 2ad638a3d160923ef3dbf87c73944e6e44bdc724 Mon Sep 17 00:00:00 2001 From: Denis Rastyogin Date: Tue, 4 Mar 2025 11:39:10 +0300 Subject: [PATCH 0499/1179] block/qed: fix use-after-free by nullifying timer pointer after free This error was discovered by fuzzing qemu-img. In the QED block driver, the need_check_timer timer is freed in bdrv_qed_detach_aio_context, but the pointer to the timer is not set to NULL. This can lead to a use-after-free scenario in bdrv_qed_drain_begin(). The need_check_timer pointer is set to NULL after freeing the timer. Which helps catch this condition when checking in bdrv_qed_drain_begin(). Closes: https://gitlab.com/qemu-project/qemu/-/issues/2852 Signed-off-by: Denis Rastyogin Message-ID: <20250304083927.37681-1-gerben@altlinux.org> Signed-off-by: Stefan Hajnoczi --- block/qed.c | 1 + 1 file changed, 1 insertion(+) diff --git a/block/qed.c b/block/qed.c index 382c9e533579..ac24449ffb3f 100644 --- a/block/qed.c +++ b/block/qed.c @@ -353,6 +353,7 @@ static void bdrv_qed_detach_aio_context(BlockDriverState *bs) qed_cancel_need_check_timer(s); timer_free(s->need_check_timer); + s->need_check_timer = NULL; } static void bdrv_qed_attach_aio_context(BlockDriverState *bs, From 4526687bf12624d957088cd40ee02540a5404546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Mon, 17 Feb 2025 18:34:55 +0100 Subject: [PATCH 0500/1179] vfio: Add property documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Investigate the git history to uncover when and why the VFIO properties were introduced and update the models. This is mostly targeting vfio-pci device, since vfio-platform, vfio-ap and vfio-ccw devices are simpler. Sort the properties based on the QEMU version in which they were introduced. Cc: Tony Krowiak Cc: Eric Farman Cc: Eric Auger Reviewed-by: Kirti Wankhede Reviewed-by: Anthony Krowiak Reviewed-by: Eric Farman # vfio-ccw Reviewed-by: Alex Williamson Reviewed-by: Eric Auger Link: https://lore.kernel.org/qemu-devel/20250217173455.449983-1-clg@redhat.com Signed-off-by: Cédric Le Goater --- hw/vfio/ap.c | 9 ++++ hw/vfio/ccw.c | 15 ++++++ hw/vfio/pci.c | 125 +++++++++++++++++++++++++++++++++++++++++++++ hw/vfio/platform.c | 24 +++++++++ 4 files changed, 173 insertions(+) diff --git a/hw/vfio/ap.c b/hw/vfio/ap.c index 30b08ad375d5..c7ab4ff57ada 100644 --- a/hw/vfio/ap.c +++ b/hw/vfio/ap.c @@ -257,6 +257,15 @@ static void vfio_ap_class_init(ObjectClass *klass, void *data) dc->hotpluggable = true; device_class_set_legacy_reset(dc, vfio_ap_reset); dc->bus_type = TYPE_AP_BUS; + + object_class_property_set_description(klass, /* 3.1 */ + "sysfsdev", + "Host sysfs path of assigned device"); +#ifdef CONFIG_IOMMUFD + object_class_property_set_description(klass, /* 9.0 */ + "iommufd", + "Set host IOMMUFD backend device"); +#endif } static const TypeInfo vfio_ap_info = { diff --git a/hw/vfio/ccw.c b/hw/vfio/ccw.c index 67bc137f9be6..ea766ae26c74 100644 --- a/hw/vfio/ccw.c +++ b/hw/vfio/ccw.c @@ -717,6 +717,21 @@ static void vfio_ccw_class_init(ObjectClass *klass, void *data) cdc->handle_halt = vfio_ccw_handle_halt; cdc->handle_clear = vfio_ccw_handle_clear; cdc->handle_store = vfio_ccw_handle_store; + + object_class_property_set_description(klass, /* 2.10 */ + "sysfsdev", + "Host sysfs path of assigned device"); + object_class_property_set_description(klass, /* 3.0 */ + "force-orb-pfch", + "Force unlimited prefetch"); +#ifdef CONFIG_IOMMUFD + object_class_property_set_description(klass, /* 9.0 */ + "iommufd", + "Set host IOMMUFD backend device"); +#endif + object_class_property_set_description(klass, /* 9.2 */ + "loadparm", + "Define which devices that can be used for booting"); } static const TypeInfo vfio_ccw_info = { diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 89d900e9cf0c..4f92b50b1330 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3433,6 +3433,122 @@ static void vfio_pci_dev_class_init(ObjectClass *klass, void *data) pdc->exit = vfio_exitfn; pdc->config_read = vfio_pci_read_config; pdc->config_write = vfio_pci_write_config; + + object_class_property_set_description(klass, /* 1.3 */ + "host", + "Host PCI address [domain:] of assigned device"); + object_class_property_set_description(klass, /* 1.3 */ + "x-intx-mmap-timeout-ms", + "When EOI is not provided by KVM/QEMU, wait time " + "(milliseconds) to re-enable device direct access " + "after INTx (DEBUG)"); + object_class_property_set_description(klass, /* 1.5 */ + "x-vga", + "Expose VGA address spaces for device"); + object_class_property_set_description(klass, /* 2.3 */ + "x-req", + "Disable device request notification support (DEBUG)"); + object_class_property_set_description(klass, /* 2.4 and 2.5 */ + "x-no-mmap", + "Disable MMAP for device. Allows to trace MMIO " + "accesses (DEBUG)"); + object_class_property_set_description(klass, /* 2.5 */ + "x-no-kvm-intx", + "Disable direct VFIO->KVM INTx injection. Allows to " + "trace INTx interrupts (DEBUG)"); + object_class_property_set_description(klass, /* 2.5 */ + "x-no-kvm-msi", + "Disable direct VFIO->KVM MSI injection. Allows to " + "trace MSI interrupts (DEBUG)"); + object_class_property_set_description(klass, /* 2.5 */ + "x-no-kvm-msix", + "Disable direct VFIO->KVM MSIx injection. Allows to " + "trace MSIx interrupts (DEBUG)"); + object_class_property_set_description(klass, /* 2.5 */ + "x-pci-vendor-id", + "Override PCI Vendor ID with provided value (DEBUG)"); + object_class_property_set_description(klass, /* 2.5 */ + "x-pci-device-id", + "Override PCI device ID with provided value (DEBUG)"); + object_class_property_set_description(klass, /* 2.5 */ + "x-pci-sub-vendor-id", + "Override PCI Subsystem Vendor ID with provided value " + "(DEBUG)"); + object_class_property_set_description(klass, /* 2.5 */ + "x-pci-sub-device-id", + "Override PCI Subsystem Device ID with provided value " + "(DEBUG)"); + object_class_property_set_description(klass, /* 2.6 */ + "sysfsdev", + "Host sysfs path of assigned device"); + object_class_property_set_description(klass, /* 2.7 */ + "x-igd-opregion", + "Expose host IGD OpRegion to guest"); + object_class_property_set_description(klass, /* 2.7 (See c4c45e943e51) */ + "x-igd-gms", + "Override IGD data stolen memory size (32MiB units)"); + object_class_property_set_description(klass, /* 2.11 */ + "x-nv-gpudirect-clique", + "Add NVIDIA GPUDirect capability indicating P2P DMA " + "clique for device [0-15]"); + object_class_property_set_description(klass, /* 2.12 */ + "x-no-geforce-quirks", + "Disable GeForce quirks (for NVIDIA Quadro/GRID/Tesla). " + "Improves performance"); + object_class_property_set_description(klass, /* 2.12 */ + "display", + "Enable display support for device, ex. vGPU"); + object_class_property_set_description(klass, /* 2.12 */ + "x-msix-relocation", + "Specify MSI-X MMIO relocation to the end of specified " + "existing BAR or new BAR to avoid virtualization overhead " + "due to adjacent device registers"); + object_class_property_set_description(klass, /* 3.0 */ + "x-no-kvm-ioeventfd", + "Disable registration of ioeventfds with KVM (DEBUG)"); + object_class_property_set_description(klass, /* 3.0 */ + "x-no-vfio-ioeventfd", + "Disable linking of KVM ioeventfds to VFIO ioeventfds " + "(DEBUG)"); + object_class_property_set_description(klass, /* 3.1 */ + "x-balloon-allowed", + "Override allowing ballooning with device (DEBUG, DANGER)"); + object_class_property_set_description(klass, /* 3.2 */ + "xres", + "Set X display resolution the vGPU should use"); + object_class_property_set_description(klass, /* 3.2 */ + "yres", + "Set Y display resolution the vGPU should use"); + object_class_property_set_description(klass, /* 5.2 */ + "x-pre-copy-dirty-page-tracking", + "Disable dirty pages tracking during iterative phase " + "(DEBUG)"); + object_class_property_set_description(klass, /* 5.2, 8.0 non-experimetal */ + "enable-migration", + "Enale device migration. Also requires a host VFIO PCI " + "variant or mdev driver with migration support enabled"); + object_class_property_set_description(klass, /* 8.1 */ + "vf-token", + "Specify UUID VF token. Required for VF when PF is owned " + "by another VFIO driver"); +#ifdef CONFIG_IOMMUFD + object_class_property_set_description(klass, /* 9.0 */ + "iommufd", + "Set host IOMMUFD backend device"); +#endif + object_class_property_set_description(klass, /* 9.1 */ + "x-device-dirty-page-tracking", + "Disable device dirty page tracking and use " + "container-based dirty page tracking (DEBUG)"); + object_class_property_set_description(klass, /* 9.1 */ + "migration-events", + "Emit VFIO migration QAPI event when a VFIO device " + "changes its migration state. For management applications"); + object_class_property_set_description(klass, /* 9.1 */ + "skip-vsc-check", + "Skip config space check for Vendor Specific Capability. " + "Setting to false will enforce strict checking of VSC content " + "(DEBUG)"); } static const TypeInfo vfio_pci_dev_info = { @@ -3461,6 +3577,15 @@ static void vfio_pci_nohotplug_dev_class_init(ObjectClass *klass, void *data) device_class_set_props(dc, vfio_pci_dev_nohotplug_properties); dc->hotpluggable = false; + + object_class_property_set_description(klass, /* 3.1 */ + "ramfb", + "Enable ramfb to provide pre-boot graphics for devices " + "enabling display option"); + object_class_property_set_description(klass, /* 8.2 */ + "x-ramfb-migrate", + "Override default migration support for ramfb support " + "(DEBUG)"); } static const TypeInfo vfio_pci_nohotplug_dev_info = { diff --git a/hw/vfio/platform.c b/hw/vfio/platform.c index f491f4dc9543..d9faaa73959a 100644 --- a/hw/vfio/platform.c +++ b/hw/vfio/platform.c @@ -672,6 +672,30 @@ static void vfio_platform_class_init(ObjectClass *klass, void *data) dc->desc = "VFIO-based platform device assignment"; sbc->connect_irq_notifier = vfio_start_irqfd_injection; set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + object_class_property_set_description(klass, /* 2.4 */ + "host", + "Host device name of assigned device"); + object_class_property_set_description(klass, /* 2.4 and 2.5 */ + "x-no-mmap", + "Disable MMAP for device. Allows to trace MMIO " + "accesses (DEBUG)"); + object_class_property_set_description(klass, /* 2.4 */ + "mmap-timeout-ms", + "When EOI is not provided by KVM/QEMU, wait time " + "(milliseconds) to re-enable device direct access " + "after level interrupt (DEBUG)"); + object_class_property_set_description(klass, /* 2.4 */ + "x-irqfd", + "Allow disabling irqfd support (DEBUG)"); + object_class_property_set_description(klass, /* 2.6 */ + "sysfsdev", + "Host sysfs path of assigned device"); +#ifdef CONFIG_IOMMUFD + object_class_property_set_description(klass, /* 9.0 */ + "iommufd", + "Set host IOMMUFD backend device"); +#endif } static const TypeInfo vfio_platform_dev_info = { From 3f8f6ef701ac6b3691b6003025005a970b2075de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Fri, 14 Feb 2025 17:19:36 +0100 Subject: [PATCH 0501/1179] vfio/ccw: Replace warn_once_pfch() with warn_report_once() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the common helper warn_report_once() instead of implementing its own. Cc: Eric Farman Reviewed-by: Eric Farman Link: https://lore.kernel.org/qemu-devel/20250214161936.1720039-1-clg@redhat.com Signed-off-by: Cédric Le Goater --- hw/vfio/ccw.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/hw/vfio/ccw.c b/hw/vfio/ccw.c index ea766ae26c74..e5e0d9e3e7ed 100644 --- a/hw/vfio/ccw.c +++ b/hw/vfio/ccw.c @@ -51,17 +51,8 @@ struct VFIOCCWDevice { EventNotifier crw_notifier; EventNotifier req_notifier; bool force_orb_pfch; - bool warned_orb_pfch; }; -static inline void warn_once_pfch(VFIOCCWDevice *vcdev, SubchDev *sch, - const char *msg) -{ - warn_report_once_cond(&vcdev->warned_orb_pfch, - "vfio-ccw (devno %x.%x.%04x): %s", - sch->cssid, sch->ssid, sch->devno, msg); -} - static void vfio_ccw_compute_needs_reset(VFIODevice *vdev) { vdev->needs_reset = false; @@ -83,7 +74,8 @@ static IOInstEnding vfio_ccw_handle_request(SubchDev *sch) if (!(sch->orb.ctrl0 & ORB_CTRL0_MASK_PFCH) && vcdev->force_orb_pfch) { sch->orb.ctrl0 |= ORB_CTRL0_MASK_PFCH; - warn_once_pfch(vcdev, sch, "PFCH flag forced"); + warn_report_once("vfio-ccw (devno %x.%x.%04x): PFCH flag forced", + sch->cssid, sch->ssid, sch->devno); } QEMU_BUILD_BUG_ON(sizeof(region->orb_area) != sizeof(ORB)); From 9461afd2008b0820fc45a6a7bc675df1b6791e4f Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Tue, 25 Feb 2025 14:52:25 -0700 Subject: [PATCH 0502/1179] hw/pci: Basic support for PCI power management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The memory and IO BARs for devices are only accessible in the D0 power state. In other power states the PCI spec defines that the device responds to TLPs and messages with an Unsupported Request response. To approximate this behavior, consider the BARs as unmapped when the device is not in the D0 power state. This makes the BARs inaccessible and has the additional bonus for vfio-pci that we don't attempt to DMA map BARs for devices in a non-D0 power state. To support this, an interface is added for devices to register the PM capability, which allows central tracking to enforce valid transitions and unmap BARs in non-D0 states. NB. We currently have device models (eepro100 and pcie_pci_bridge) that register a PM capability but do not set wmask to enable writes to the power state field. In order to maintain migration compatibility, this new helper does not manage the wmask to enable guest writes to initiate a power state change. The contents and write access of the PM capability are still managed by the caller. Cc: Michael S. Tsirkin Cc: Marcel Apfelbaum Signed-off-by: Alex Williamson Reviewed-by: Eric Auger Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/qemu-devel/20250225215237.3314011-2-alex.williamson@redhat.com Signed-off-by: Cédric Le Goater --- hw/pci/pci.c | 93 ++++++++++++++++++++++++++++++++++++- hw/pci/trace-events | 2 + include/hw/pci/pci.h | 3 ++ include/hw/pci/pci_device.h | 3 ++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/hw/pci/pci.c b/hw/pci/pci.c index 1d42847ef044..2ef7f6174914 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -435,6 +435,84 @@ static void pci_msi_trigger(PCIDevice *dev, MSIMessage msg) attrs, NULL); } +/* + * Register and track a PM capability. If wmask is also enabled for the power + * state field of the pmcsr register, guest writes may change the device PM + * state. BAR access is only enabled while the device is in the D0 state. + * Return the capability offset or negative error code. + */ +int pci_pm_init(PCIDevice *d, uint8_t offset, Error **errp) +{ + int cap = pci_add_capability(d, PCI_CAP_ID_PM, offset, PCI_PM_SIZEOF, errp); + + if (cap < 0) { + return cap; + } + + d->pm_cap = cap; + d->cap_present |= QEMU_PCI_CAP_PM; + + return cap; +} + +static uint8_t pci_pm_state(PCIDevice *d) +{ + uint16_t pmcsr; + + if (!(d->cap_present & QEMU_PCI_CAP_PM)) { + return 0; + } + + pmcsr = pci_get_word(d->config + d->pm_cap + PCI_PM_CTRL); + + return pmcsr & PCI_PM_CTRL_STATE_MASK; +} + +/* + * Update the PM capability state based on the new value stored in config + * space respective to the old, pre-write state provided. If the new value + * is rejected (unsupported or invalid transition) restore the old value. + * Return the resulting PM state. + */ +static uint8_t pci_pm_update(PCIDevice *d, uint32_t addr, int l, uint8_t old) +{ + uint16_t pmc; + uint8_t new; + + if (!(d->cap_present & QEMU_PCI_CAP_PM) || + !range_covers_byte(addr, l, d->pm_cap + PCI_PM_CTRL)) { + return old; + } + + new = pci_pm_state(d); + if (new == old) { + return old; + } + + pmc = pci_get_word(d->config + d->pm_cap + PCI_PM_PMC); + + /* + * Transitions to D1 & D2 are only allowed if supported. Devices may + * only transition to higher D-states or to D0. + */ + if ((!(pmc & PCI_PM_CAP_D1) && new == 1) || + (!(pmc & PCI_PM_CAP_D2) && new == 2) || + (old && new && new < old)) { + pci_word_test_and_clear_mask(d->config + d->pm_cap + PCI_PM_CTRL, + PCI_PM_CTRL_STATE_MASK); + pci_word_test_and_set_mask(d->config + d->pm_cap + PCI_PM_CTRL, + old); + trace_pci_pm_bad_transition(d->name, pci_dev_bus_num(d), + PCI_SLOT(d->devfn), PCI_FUNC(d->devfn), + old, new); + return old; + } + + trace_pci_pm_transition(d->name, pci_dev_bus_num(d), PCI_SLOT(d->devfn), + PCI_FUNC(d->devfn), old, new); + return new; +} + static void pci_reset_regions(PCIDevice *dev) { int r; @@ -474,6 +552,11 @@ static void pci_do_device_reset(PCIDevice *dev) pci_get_word(dev->wmask + PCI_INTERRUPT_LINE) | pci_get_word(dev->w1cmask + PCI_INTERRUPT_LINE)); dev->config[PCI_CACHE_LINE_SIZE] = 0x0; + /* Default PM state is D0 */ + if (dev->cap_present & QEMU_PCI_CAP_PM) { + pci_word_test_and_clear_mask(dev->config + dev->pm_cap + PCI_PM_CTRL, + PCI_PM_CTRL_STATE_MASK); + } pci_reset_regions(dev); pci_update_mappings(dev); @@ -1606,7 +1689,7 @@ static void pci_update_mappings(PCIDevice *d) continue; new_addr = pci_bar_address(d, i, r->type, r->size); - if (!d->enabled) { + if (!d->enabled || pci_pm_state(d)) { new_addr = PCI_BAR_UNMAPPED; } @@ -1672,6 +1755,7 @@ uint32_t pci_default_read_config(PCIDevice *d, void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val_in, int l) { + uint8_t new_pm_state, old_pm_state = pci_pm_state(d); int i, was_irq_disabled = pci_irq_disabled(d); uint32_t val = val_in; @@ -1684,11 +1768,16 @@ void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val_in, int d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask); d->config[addr + i] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */ } + + new_pm_state = pci_pm_update(d, addr, l, old_pm_state); + if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) || ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) || ranges_overlap(addr, l, PCI_ROM_ADDRESS1, 4) || - range_covers_byte(addr, l, PCI_COMMAND)) + range_covers_byte(addr, l, PCI_COMMAND) || + !!new_pm_state != !!old_pm_state) { pci_update_mappings(d); + } if (ranges_overlap(addr, l, PCI_COMMAND, 2)) { pci_update_irq_disabled(d, was_irq_disabled); diff --git a/hw/pci/trace-events b/hw/pci/trace-events index e98f575a9d19..6a9968962f4a 100644 --- a/hw/pci/trace-events +++ b/hw/pci/trace-events @@ -1,6 +1,8 @@ # See docs/devel/tracing.rst for syntax documentation. # pci.c +pci_pm_bad_transition(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, uint8_t old, uint8_t new) "%s %02x:%02x.%x REJECTED PM transition D%d->D%d" +pci_pm_transition(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, uint8_t old, uint8_t new) "%s %02x:%02x.%x PM transition D%d->D%d" pci_update_mappings_del(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, int bar, uint64_t addr, uint64_t size) "%s %02x:%02x.%x %d,0x%"PRIx64"+0x%"PRIx64 pci_update_mappings_add(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, int bar, uint64_t addr, uint64_t size) "%s %02x:%02x.%x %d,0x%"PRIx64"+0x%"PRIx64 pci_route_irq(int dev_irq, const char *dev_path, int parent_irq, const char *parent_path) "IRQ %d @%s -> IRQ %d @%s" diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h index 4002bbeebde0..c220cc844962 100644 --- a/include/hw/pci/pci.h +++ b/include/hw/pci/pci.h @@ -216,6 +216,8 @@ enum { QEMU_PCIE_ARI_NEXTFN_1 = (1 << QEMU_PCIE_ARI_NEXTFN_1_BITNR), #define QEMU_PCIE_EXT_TAG_BITNR 13 QEMU_PCIE_EXT_TAG = (1 << QEMU_PCIE_EXT_TAG_BITNR), +#define QEMU_PCI_CAP_PM_BITNR 14 + QEMU_PCI_CAP_PM = (1 << QEMU_PCI_CAP_PM_BITNR), }; typedef struct PCIINTxRoute { @@ -676,5 +678,6 @@ static inline void pci_irq_deassert(PCIDevice *pci_dev) MSIMessage pci_get_msi_message(PCIDevice *dev, int vector); void pci_set_enabled(PCIDevice *pci_dev, bool state); void pci_set_power(PCIDevice *pci_dev, bool state); +int pci_pm_init(PCIDevice *pci_dev, uint8_t offset, Error **errp); #endif diff --git a/include/hw/pci/pci_device.h b/include/hw/pci/pci_device.h index add208edfabd..345b12eaac1a 100644 --- a/include/hw/pci/pci_device.h +++ b/include/hw/pci/pci_device.h @@ -105,6 +105,9 @@ struct PCIDevice { /* Capability bits */ uint32_t cap_present; + /* Offset of PM capability in config space */ + uint8_t pm_cap; + /* Offset of MSI-X capability in config space */ uint8_t msix_cap; From 0681ec253141d838210b3c5e6bc0d2d71f2e111e Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Tue, 25 Feb 2025 14:52:26 -0700 Subject: [PATCH 0503/1179] pci: Use PCI PM capability initializer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch callers directly initializing the PCI PM capability with pci_add_capability() to use pci_pm_init(). Cc: Dmitry Fleytman Cc: Akihiko Odaki Cc: Jason Wang Cc: Stefan Weil Cc: Sriram Yagnaraman Cc: Keith Busch Cc: Klaus Jensen Cc: Jesper Devantier Cc: Michael S. Tsirkin Cc: Marcel Apfelbaum Cc: Cédric Le Goater Signed-off-by: Alex Williamson Reviewed-by: Eric Auger Reviewed-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/qemu-devel/20250225215237.3314011-3-alex.williamson@redhat.com Signed-off-by: Cédric Le Goater --- hw/net/e1000e.c | 3 +-- hw/net/eepro100.c | 4 +--- hw/net/igb.c | 3 +-- hw/nvme/ctrl.c | 3 +-- hw/pci-bridge/pcie_pci_bridge.c | 2 +- hw/vfio/pci.c | 7 ++++++- hw/virtio/virtio-pci.c | 3 +-- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/hw/net/e1000e.c b/hw/net/e1000e.c index f637853073e2..b72cbab7e889 100644 --- a/hw/net/e1000e.c +++ b/hw/net/e1000e.c @@ -372,8 +372,7 @@ static int e1000e_add_pm_capability(PCIDevice *pdev, uint8_t offset, uint16_t pmc) { Error *local_err = NULL; - int ret = pci_add_capability(pdev, PCI_CAP_ID_PM, offset, - PCI_PM_SIZEOF, &local_err); + int ret = pci_pm_init(pdev, offset, &local_err); if (local_err) { error_report_err(local_err); diff --git a/hw/net/eepro100.c b/hw/net/eepro100.c index 6d853229aec2..29a39865a608 100644 --- a/hw/net/eepro100.c +++ b/hw/net/eepro100.c @@ -551,9 +551,7 @@ static void e100_pci_reset(EEPRO100State *s, Error **errp) if (info->power_management) { /* Power Management Capabilities */ int cfg_offset = 0xdc; - int r = pci_add_capability(&s->dev, PCI_CAP_ID_PM, - cfg_offset, PCI_PM_SIZEOF, - errp); + int r = pci_pm_init(&s->dev, cfg_offset, errp); if (r < 0) { return; } diff --git a/hw/net/igb.c b/hw/net/igb.c index c965fc2fb68a..e318df40e013 100644 --- a/hw/net/igb.c +++ b/hw/net/igb.c @@ -356,8 +356,7 @@ static int igb_add_pm_capability(PCIDevice *pdev, uint8_t offset, uint16_t pmc) { Error *local_err = NULL; - int ret = pci_add_capability(pdev, PCI_CAP_ID_PM, offset, - PCI_PM_SIZEOF, &local_err); + int ret = pci_pm_init(pdev, offset, &local_err); if (local_err) { error_report_err(local_err); diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index e62c6a358828..518d02dc6670 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -8600,8 +8600,7 @@ static int nvme_add_pm_capability(PCIDevice *pci_dev, uint8_t offset) Error *err = NULL; int ret; - ret = pci_add_capability(pci_dev, PCI_CAP_ID_PM, offset, - PCI_PM_SIZEOF, &err); + ret = pci_pm_init(pci_dev, offset, &err); if (err) { error_report_err(err); return ret; diff --git a/hw/pci-bridge/pcie_pci_bridge.c b/hw/pci-bridge/pcie_pci_bridge.c index fd4514a595ce..9fa656b43b42 100644 --- a/hw/pci-bridge/pcie_pci_bridge.c +++ b/hw/pci-bridge/pcie_pci_bridge.c @@ -52,7 +52,7 @@ static void pcie_pci_bridge_realize(PCIDevice *d, Error **errp) goto cap_error; } - pos = pci_add_capability(d, PCI_CAP_ID_PM, 0, PCI_PM_SIZEOF, errp); + pos = pci_pm_init(d, 0, errp); if (pos < 0) { goto pm_error; } diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 4f92b50b1330..d33b795af079 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -2216,7 +2216,12 @@ static bool vfio_add_std_cap(VFIOPCIDevice *vdev, uint8_t pos, Error **errp) case PCI_CAP_ID_PM: vfio_check_pm_reset(vdev, pos); vdev->pm_cap = pos; - ret = pci_add_capability(pdev, cap_id, pos, size, errp) >= 0; + ret = pci_pm_init(pdev, pos, errp) >= 0; + /* + * PCI-core config space emulation needs write access to the power + * state enabled for tracking BAR mapping relative to PM state. + */ + pci_set_word(pdev->wmask + pos + PCI_PM_CTRL, PCI_PM_CTRL_STATE_MASK); break; case PCI_CAP_ID_AF: vfio_check_af_flr(vdev, pos); diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index c773a9130c7e..afe8b5551c5c 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -2204,8 +2204,7 @@ static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp) pos = pcie_endpoint_cap_init(pci_dev, 0); assert(pos > 0); - pos = pci_add_capability(pci_dev, PCI_CAP_ID_PM, 0, - PCI_PM_SIZEOF, errp); + pos = pci_pm_init(pci_dev, 0, errp); if (pos < 0) { return; } From 05c6a8eff6298675080aa2692ee05a310b3483b4 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Tue, 25 Feb 2025 14:52:27 -0700 Subject: [PATCH 0504/1179] vfio/pci: Delete local pm_cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is now redundant to PCIDevice.pm_cap. Cc: Cédric Le Goater Reviewed-by: Zhenzhong Duan Reviewed-by: Eric Auger Signed-off-by: Alex Williamson Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/qemu-devel/20250225215237.3314011-4-alex.williamson@redhat.com Signed-off-by: Cédric Le Goater --- hw/vfio/pci.c | 9 ++++----- hw/vfio/pci.h | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index d33b795af079..a8db19d8d2f9 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -2215,7 +2215,6 @@ static bool vfio_add_std_cap(VFIOPCIDevice *vdev, uint8_t pos, Error **errp) break; case PCI_CAP_ID_PM: vfio_check_pm_reset(vdev, pos); - vdev->pm_cap = pos; ret = pci_pm_init(pdev, pos, errp) >= 0; /* * PCI-core config space emulation needs write access to the power @@ -2412,17 +2411,17 @@ void vfio_pci_pre_reset(VFIOPCIDevice *vdev) vfio_disable_interrupts(vdev); /* Make sure the device is in D0 */ - if (vdev->pm_cap) { + if (pdev->pm_cap) { uint16_t pmcsr; uint8_t state; - pmcsr = vfio_pci_read_config(pdev, vdev->pm_cap + PCI_PM_CTRL, 2); + pmcsr = vfio_pci_read_config(pdev, pdev->pm_cap + PCI_PM_CTRL, 2); state = pmcsr & PCI_PM_CTRL_STATE_MASK; if (state) { pmcsr &= ~PCI_PM_CTRL_STATE_MASK; - vfio_pci_write_config(pdev, vdev->pm_cap + PCI_PM_CTRL, pmcsr, 2); + vfio_pci_write_config(pdev, pdev->pm_cap + PCI_PM_CTRL, pmcsr, 2); /* vfio handles the necessary delay here */ - pmcsr = vfio_pci_read_config(pdev, vdev->pm_cap + PCI_PM_CTRL, 2); + pmcsr = vfio_pci_read_config(pdev, pdev->pm_cap + PCI_PM_CTRL, 2); state = pmcsr & PCI_PM_CTRL_STATE_MASK; if (state) { error_report("vfio: Unable to power on device, stuck in D%d", diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index 43c166680abb..d638c781f6f1 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -160,7 +160,6 @@ struct VFIOPCIDevice { int32_t bootindex; uint32_t igd_gms; OffAutoPCIBAR msix_relo; - uint8_t pm_cap; uint8_t nv_gpudirect_clique; bool pci_aer; bool req_enabled; From 8b8d08cf293b930d0f55b2d5385d8dd27e0c6b41 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Tue, 25 Feb 2025 14:52:28 -0700 Subject: [PATCH 0505/1179] pcie, virtio: Remove redundant pm_cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pm_cap on the PCIExpressDevice object can be distilled down to the new instance on the PCIDevice object. Cc: Michael S. Tsirkin Cc: Marcel Apfelbaum Reviewed-by: Michael S. Tsirkin Reviewed-by: Zhenzhong Duan Reviewed-by: Eric Auger Signed-off-by: Alex Williamson Link: https://lore.kernel.org/qemu-devel/20250225215237.3314011-5-alex.williamson@redhat.com Signed-off-by: Cédric Le Goater --- hw/pci-bridge/pcie_pci_bridge.c | 1 - hw/virtio/virtio-pci.c | 8 +++----- include/hw/pci/pcie.h | 2 -- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/hw/pci-bridge/pcie_pci_bridge.c b/hw/pci-bridge/pcie_pci_bridge.c index 9fa656b43b42..2429503cfbbf 100644 --- a/hw/pci-bridge/pcie_pci_bridge.c +++ b/hw/pci-bridge/pcie_pci_bridge.c @@ -56,7 +56,6 @@ static void pcie_pci_bridge_realize(PCIDevice *d, Error **errp) if (pos < 0) { goto pm_error; } - d->exp.pm_cap = pos; pci_set_word(d->config + pos + PCI_PM_PMC, 0x3); pcie_cap_arifwd_init(d); diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index afe8b5551c5c..3ca3f849d391 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -2209,8 +2209,6 @@ static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp) return; } - pci_dev->exp.pm_cap = pos; - /* * Indicates that this function complies with revision 1.2 of the * PCI Power Management Interface Specification. @@ -2309,11 +2307,11 @@ static bool virtio_pci_no_soft_reset(PCIDevice *dev) { uint16_t pmcsr; - if (!pci_is_express(dev) || !dev->exp.pm_cap) { + if (!pci_is_express(dev) || !(dev->cap_present & QEMU_PCI_CAP_PM)) { return false; } - pmcsr = pci_get_word(dev->config + dev->exp.pm_cap + PCI_PM_CTRL); + pmcsr = pci_get_word(dev->config + dev->pm_cap + PCI_PM_CTRL); /* * When No_Soft_Reset bit is set and the device @@ -2342,7 +2340,7 @@ static void virtio_pci_bus_reset_hold(Object *obj, ResetType type) if (proxy->flags & VIRTIO_PCI_FLAG_INIT_PM) { pci_word_test_and_clear_mask( - dev->config + dev->exp.pm_cap + PCI_PM_CTRL, + dev->config + dev->pm_cap + PCI_PM_CTRL, PCI_PM_CTRL_STATE_MASK); } } diff --git a/include/hw/pci/pcie.h b/include/hw/pci/pcie.h index b8d59732bc63..70a5de09de39 100644 --- a/include/hw/pci/pcie.h +++ b/include/hw/pci/pcie.h @@ -58,8 +58,6 @@ typedef enum { struct PCIExpressDevice { /* Offset of express capability in config space */ uint8_t exp_cap; - /* Offset of Power Management capability in config space */ - uint8_t pm_cap; /* SLOT */ bool hpev_notified; /* Logical AND of conditions for hot plug event. From 518a69a598916749338de3852d41d961d4503115 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Tue, 25 Feb 2025 14:52:29 -0700 Subject: [PATCH 0506/1179] hw/vfio/pci: Re-order pre-reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want the device in the D0 power state going into reset, but the config write can enable the BARs in the address space, which are then removed from the address space once we clear the memory enable bit in the command register. Re-order to clear the command bit first, so the power state change doesn't enable the BARs. Cc: Cédric Le Goater Reviewed-by: Zhenzhong Duan Reviewed-by: Eric Auger Signed-off-by: Alex Williamson Reviewed-by: Michael S. Tsirkin Link: https://lore.kernel.org/qemu-devel/20250225215237.3314011-6-alex.williamson@redhat.com Signed-off-by: Cédric Le Goater --- hw/vfio/pci.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index a8db19d8d2f9..c1cee280ae4b 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -2410,6 +2410,15 @@ void vfio_pci_pre_reset(VFIOPCIDevice *vdev) vfio_disable_interrupts(vdev); + /* + * Stop any ongoing DMA by disconnecting I/O, MMIO, and bus master. + * Also put INTx Disable in known state. + */ + cmd = vfio_pci_read_config(pdev, PCI_COMMAND, 2); + cmd &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | + PCI_COMMAND_INTX_DISABLE); + vfio_pci_write_config(pdev, PCI_COMMAND, cmd, 2); + /* Make sure the device is in D0 */ if (pdev->pm_cap) { uint16_t pmcsr; @@ -2429,15 +2438,6 @@ void vfio_pci_pre_reset(VFIOPCIDevice *vdev) } } } - - /* - * Stop any ongoing DMA by disconnecting I/O, MMIO, and bus master. - * Also put INTx Disable in known state. - */ - cmd = vfio_pci_read_config(pdev, PCI_COMMAND, 2); - cmd &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | - PCI_COMMAND_INTX_DISABLE); - vfio_pci_write_config(pdev, PCI_COMMAND, cmd, 2); } void vfio_pci_post_reset(VFIOPCIDevice *vdev) From 515d80d66527b105493fad2df313693a72bf7560 Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 28 Feb 2025 00:27:41 +0800 Subject: [PATCH 0507/1179] MAINTAINERS: Add myself as vfio-igd maintainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As suggested by Cédric, I'm glad to be a maintainer of vfio-igd. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250227162741.9860-1-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- MAINTAINERS | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 692628cd7801..1a920e7dc4cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2186,10 +2186,17 @@ M: Cédric Le Goater S: Supported F: hw/vfio/* F: include/hw/vfio/ -F: docs/igd-assign.txt F: docs/devel/migration/vfio.rst F: qapi/vfio.json +vfio-igd +M: Alex Williamson +M: Cédric Le Goater +M: Tomita Moeko +S: Supported +F: hw/vfio/igd.c +F: docs/igd-assign.txt + vfio-ccw M: Eric Farman M: Matthew Rosato From 8d8a30d1ac7cc9b35833106d749e1b3e8675bc53 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Wed, 5 Mar 2025 13:42:25 +0100 Subject: [PATCH 0508/1179] vfio-platform: Deprecate all forms of vfio-platform devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As an outcome of KVM forum 2024 "vfio-platform: live and let die?" talk, let's deprecate vfio-platform devices. Signed-off-by: Eric Auger Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250305124225.952791-1-eric.auger@redhat.com [ clg: Fixed spelling in vfio-amd-xgbe section ] Signed-off-by: Cédric Le Goater --- docs/about/deprecated.rst | 25 +++++++++++++++++++++++++ hw/vfio/amd-xgbe.c | 2 ++ hw/vfio/calxeda-xgmac.c | 2 ++ hw/vfio/platform.c | 1 + 4 files changed, 30 insertions(+) diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index abadf8de2708..589951b13683 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -434,6 +434,31 @@ Stream ``reconnect`` (since 9.2) The ``reconnect`` option only allows specifiying second granularity timeouts, which is not enough for all types of use cases, use ``reconnect-ms`` instead. +VFIO device options +''''''''''''''''''' + +``-device vfio-calxeda-xgmac`` (since 10.0) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The vfio-calxeda-xgmac device allows to assign a host Calxeda Highbank +10Gb XGMAC Ethernet controller device ("calxeda,hb-xgmac" compatibility +string) to a guest. Calxeda HW has been ewasted now and there is no point +keeping that device. + +``-device vfio-amd-xgbe`` (since 10.0) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The vfio-amd-xgbe device allows to assign a host AMD 10GbE controller +to a guest ("amd,xgbe-seattle-v1a" compatibility string). AMD "Seattle" +is not supported anymore and there is no point keeping that device. + +``-device vfio-platform`` (since 10.0) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The vfio-platform device allows to assign a host platform device +to a guest in a generic manner. Integrating a new device into +the vfio-platform infrastructure requires some adaptation at +both kernel and qemu level. No such attempt has been done for years +and the conclusion is that vfio-platform has not got any traction. +PCIe passthrough shall be the mainline solution. + CPU device properties ''''''''''''''''''''' diff --git a/hw/vfio/amd-xgbe.c b/hw/vfio/amd-xgbe.c index aaa96903db0a..5927503b5c45 100644 --- a/hw/vfio/amd-xgbe.c +++ b/hw/vfio/amd-xgbe.c @@ -15,12 +15,14 @@ #include "hw/vfio/vfio-amd-xgbe.h" #include "migration/vmstate.h" #include "qemu/module.h" +#include "qemu/error-report.h" static void amd_xgbe_realize(DeviceState *dev, Error **errp) { VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev); VFIOAmdXgbeDeviceClass *k = VFIO_AMD_XGBE_DEVICE_GET_CLASS(dev); + warn_report("-device vfio-amd-xgbe is deprecated"); vdev->compat = g_strdup("amd,xgbe-seattle-v1a"); vdev->num_compat = 1; diff --git a/hw/vfio/calxeda-xgmac.c b/hw/vfio/calxeda-xgmac.c index b016d42b4961..a5ef262def95 100644 --- a/hw/vfio/calxeda-xgmac.c +++ b/hw/vfio/calxeda-xgmac.c @@ -15,12 +15,14 @@ #include "hw/vfio/vfio-calxeda-xgmac.h" #include "migration/vmstate.h" #include "qemu/module.h" +#include "qemu/error-report.h" static void calxeda_xgmac_realize(DeviceState *dev, Error **errp) { VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev); VFIOCalxedaXgmacDeviceClass *k = VFIO_CALXEDA_XGMAC_DEVICE_GET_CLASS(dev); + warn_report("-device vfio-calxeda-xgmac is deprecated"); vdev->compat = g_strdup("calxeda,hb-xgmac"); vdev->num_compat = 1; diff --git a/hw/vfio/platform.c b/hw/vfio/platform.c index d9faaa73959a..67bc57409c1f 100644 --- a/hw/vfio/platform.c +++ b/hw/vfio/platform.c @@ -575,6 +575,7 @@ static void vfio_platform_realize(DeviceState *dev, Error **errp) VFIODevice *vbasedev = &vdev->vbasedev; int i; + warn_report("-device vfio-platform is deprecated"); qemu_mutex_init(&vdev->intp_mutex); trace_vfio_platform_realize(vbasedev->sysfsdev ? From d3237d0d85e9fba79b37c776b0ddda611c338f7d Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:28 +0100 Subject: [PATCH 0509/1179] migration: Clarify that {load, save}_cleanup handlers can run without setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's possible for {load,save}_cleanup SaveVMHandlers to get called without the corresponding {load,save}_setup handler being called first. One such example is if {load,save}_setup handler of a proceeding device returns error. In this case the migration core cleanup code will call all corresponding cleanup handlers, even for these devices which haven't had its setup handler called. Since this behavior can generate some surprises let's clearly document it in these SaveVMHandlers description. Reviewed-by: Fabiano Rosas Reviewed-by: Cédric Le Goater Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/991636623fb780350f493b5f045cb17e13ce4c0f.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/migration/register.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/migration/register.h b/include/migration/register.h index f60e797894e5..0b0292738320 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -69,7 +69,9 @@ typedef struct SaveVMHandlers { /** * @save_cleanup * - * Uninitializes the data structures on the source + * Uninitializes the data structures on the source. + * Note that this handler can be called even if save_setup + * wasn't called earlier. * * @opaque: data pointer passed to register_savevm_live() */ @@ -244,6 +246,8 @@ typedef struct SaveVMHandlers { * @load_cleanup * * Uninitializes the data structures on the destination. + * Note that this handler can be called even if load_setup + * wasn't called earlier. * * @opaque: data pointer passed to register_savevm_live() * From 03c6468a136b3d79e7c6bb269ac7924fa8fef634 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:29 +0100 Subject: [PATCH 0510/1179] thread-pool: Remove thread_pool_submit() function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function name conflicts with one used by a future generic thread pool function and it was only used by one test anyway. Update the trace event name in thread_pool_submit_aio() accordingly. Acked-by: Fabiano Rosas Reviewed-by: Cédric Le Goater Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/6830f07777f939edaf0a2d301c39adcaaf3817f0.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/block/thread-pool.h | 3 +-- tests/unit/test-thread-pool.c | 6 +++--- util/thread-pool.c | 7 +------ util/trace-events | 2 +- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/include/block/thread-pool.h b/include/block/thread-pool.h index 948ff5f30c31..4f6694026123 100644 --- a/include/block/thread-pool.h +++ b/include/block/thread-pool.h @@ -30,13 +30,12 @@ ThreadPool *thread_pool_new(struct AioContext *ctx); void thread_pool_free(ThreadPool *pool); /* - * thread_pool_submit* API: submit I/O requests in the thread's + * thread_pool_submit_{aio,co} API: submit I/O requests in the thread's * current AioContext. */ BlockAIOCB *thread_pool_submit_aio(ThreadPoolFunc *func, void *arg, BlockCompletionFunc *cb, void *opaque); int coroutine_fn thread_pool_submit_co(ThreadPoolFunc *func, void *arg); -void thread_pool_submit(ThreadPoolFunc *func, void *arg); void thread_pool_update_params(ThreadPool *pool, struct AioContext *ctx); diff --git a/tests/unit/test-thread-pool.c b/tests/unit/test-thread-pool.c index 1483e53473db..33407b595d35 100644 --- a/tests/unit/test-thread-pool.c +++ b/tests/unit/test-thread-pool.c @@ -43,10 +43,10 @@ static void done_cb(void *opaque, int ret) active--; } -static void test_submit(void) +static void test_submit_no_complete(void) { WorkerTestData data = { .n = 0 }; - thread_pool_submit(worker_cb, &data); + thread_pool_submit_aio(worker_cb, &data, NULL, NULL); while (data.n == 0) { aio_poll(ctx, true); } @@ -236,7 +236,7 @@ int main(int argc, char **argv) ctx = qemu_get_current_aio_context(); g_test_init(&argc, &argv, NULL); - g_test_add_func("/thread-pool/submit", test_submit); + g_test_add_func("/thread-pool/submit-no-complete", test_submit_no_complete); g_test_add_func("/thread-pool/submit-aio", test_submit_aio); g_test_add_func("/thread-pool/submit-co", test_submit_co); g_test_add_func("/thread-pool/submit-many", test_submit_many); diff --git a/util/thread-pool.c b/util/thread-pool.c index 27eb777e855b..2f751d55b33f 100644 --- a/util/thread-pool.c +++ b/util/thread-pool.c @@ -256,7 +256,7 @@ BlockAIOCB *thread_pool_submit_aio(ThreadPoolFunc *func, void *arg, QLIST_INSERT_HEAD(&pool->head, req, all); - trace_thread_pool_submit(pool, req, arg); + trace_thread_pool_submit_aio(pool, req, arg); qemu_mutex_lock(&pool->lock); if (pool->idle_threads == 0 && pool->cur_threads < pool->max_threads) { @@ -290,11 +290,6 @@ int coroutine_fn thread_pool_submit_co(ThreadPoolFunc *func, void *arg) return tpc.ret; } -void thread_pool_submit(ThreadPoolFunc *func, void *arg) -{ - thread_pool_submit_aio(func, arg, NULL, NULL); -} - void thread_pool_update_params(ThreadPool *pool, AioContext *ctx) { qemu_mutex_lock(&pool->lock); diff --git a/util/trace-events b/util/trace-events index 49a4962e1886..5be12d7fab89 100644 --- a/util/trace-events +++ b/util/trace-events @@ -14,7 +14,7 @@ aio_co_schedule_bh_cb(void *ctx, void *co) "ctx %p co %p" reentrant_aio(void *ctx, const char *name) "ctx %p name %s" # thread-pool.c -thread_pool_submit(void *pool, void *req, void *opaque) "pool %p req %p opaque %p" +thread_pool_submit_aio(void *pool, void *req, void *opaque) "pool %p req %p opaque %p" thread_pool_complete(void *pool, void *req, void *opaque, int ret) "pool %p req %p opaque %p ret %d" thread_pool_cancel(void *req, void *opaque) "req %p opaque %p" From dc67daeed579ea52f045f78b88d9e5e712038ccf Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:30 +0100 Subject: [PATCH 0511/1179] thread-pool: Rename AIO pool functions to *_aio() and data types to *Aio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These names conflict with ones used by future generic thread pool equivalents. Generic names should belong to the generic pool type, not specific (AIO) type. Acked-by: Fabiano Rosas Reviewed-by: Cédric Le Goater Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/70f9e0fb4b01042258a1a57996c64d19779dc7f0.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/block/aio.h | 8 ++--- include/block/thread-pool.h | 8 ++--- util/async.c | 6 ++-- util/thread-pool.c | 58 ++++++++++++++++++------------------- util/trace-events | 4 +-- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/include/block/aio.h b/include/block/aio.h index 43883a8a33a8..b2ab3514de23 100644 --- a/include/block/aio.h +++ b/include/block/aio.h @@ -54,7 +54,7 @@ typedef void QEMUBHFunc(void *opaque); typedef bool AioPollFn(void *opaque); typedef void IOHandler(void *opaque); -struct ThreadPool; +struct ThreadPoolAio; struct LinuxAioState; typedef struct LuringState LuringState; @@ -207,7 +207,7 @@ struct AioContext { /* Thread pool for performing work and receiving completion callbacks. * Has its own locking. */ - struct ThreadPool *thread_pool; + struct ThreadPoolAio *thread_pool; #ifdef CONFIG_LINUX_AIO struct LinuxAioState *linux_aio; @@ -500,8 +500,8 @@ void aio_set_event_notifier_poll(AioContext *ctx, */ GSource *aio_get_g_source(AioContext *ctx); -/* Return the ThreadPool bound to this AioContext */ -struct ThreadPool *aio_get_thread_pool(AioContext *ctx); +/* Return the ThreadPoolAio bound to this AioContext */ +struct ThreadPoolAio *aio_get_thread_pool(AioContext *ctx); /* Setup the LinuxAioState bound to this AioContext */ struct LinuxAioState *aio_setup_linux_aio(AioContext *ctx, Error **errp); diff --git a/include/block/thread-pool.h b/include/block/thread-pool.h index 4f6694026123..6f27eb085b45 100644 --- a/include/block/thread-pool.h +++ b/include/block/thread-pool.h @@ -24,10 +24,10 @@ typedef int ThreadPoolFunc(void *opaque); -typedef struct ThreadPool ThreadPool; +typedef struct ThreadPoolAio ThreadPoolAio; -ThreadPool *thread_pool_new(struct AioContext *ctx); -void thread_pool_free(ThreadPool *pool); +ThreadPoolAio *thread_pool_new_aio(struct AioContext *ctx); +void thread_pool_free_aio(ThreadPoolAio *pool); /* * thread_pool_submit_{aio,co} API: submit I/O requests in the thread's @@ -36,7 +36,7 @@ void thread_pool_free(ThreadPool *pool); BlockAIOCB *thread_pool_submit_aio(ThreadPoolFunc *func, void *arg, BlockCompletionFunc *cb, void *opaque); int coroutine_fn thread_pool_submit_co(ThreadPoolFunc *func, void *arg); +void thread_pool_update_params(ThreadPoolAio *pool, struct AioContext *ctx); -void thread_pool_update_params(ThreadPool *pool, struct AioContext *ctx); #endif diff --git a/util/async.c b/util/async.c index 0fe29436090d..47e3d35a263f 100644 --- a/util/async.c +++ b/util/async.c @@ -369,7 +369,7 @@ aio_ctx_finalize(GSource *source) QEMUBH *bh; unsigned flags; - thread_pool_free(ctx->thread_pool); + thread_pool_free_aio(ctx->thread_pool); #ifdef CONFIG_LINUX_AIO if (ctx->linux_aio) { @@ -435,10 +435,10 @@ GSource *aio_get_g_source(AioContext *ctx) return &ctx->source; } -ThreadPool *aio_get_thread_pool(AioContext *ctx) +ThreadPoolAio *aio_get_thread_pool(AioContext *ctx) { if (!ctx->thread_pool) { - ctx->thread_pool = thread_pool_new(ctx); + ctx->thread_pool = thread_pool_new_aio(ctx); } return ctx->thread_pool; } diff --git a/util/thread-pool.c b/util/thread-pool.c index 2f751d55b33f..908194dc070f 100644 --- a/util/thread-pool.c +++ b/util/thread-pool.c @@ -23,9 +23,9 @@ #include "block/thread-pool.h" #include "qemu/main-loop.h" -static void do_spawn_thread(ThreadPool *pool); +static void do_spawn_thread(ThreadPoolAio *pool); -typedef struct ThreadPoolElement ThreadPoolElement; +typedef struct ThreadPoolElementAio ThreadPoolElementAio; enum ThreadState { THREAD_QUEUED, @@ -33,9 +33,9 @@ enum ThreadState { THREAD_DONE, }; -struct ThreadPoolElement { +struct ThreadPoolElementAio { BlockAIOCB common; - ThreadPool *pool; + ThreadPoolAio *pool; ThreadPoolFunc *func; void *arg; @@ -47,13 +47,13 @@ struct ThreadPoolElement { int ret; /* Access to this list is protected by lock. */ - QTAILQ_ENTRY(ThreadPoolElement) reqs; + QTAILQ_ENTRY(ThreadPoolElementAio) reqs; /* This list is only written by the thread pool's mother thread. */ - QLIST_ENTRY(ThreadPoolElement) all; + QLIST_ENTRY(ThreadPoolElementAio) all; }; -struct ThreadPool { +struct ThreadPoolAio { AioContext *ctx; QEMUBH *completion_bh; QemuMutex lock; @@ -62,10 +62,10 @@ struct ThreadPool { QEMUBH *new_thread_bh; /* The following variables are only accessed from one AioContext. */ - QLIST_HEAD(, ThreadPoolElement) head; + QLIST_HEAD(, ThreadPoolElementAio) head; /* The following variables are protected by lock. */ - QTAILQ_HEAD(, ThreadPoolElement) request_list; + QTAILQ_HEAD(, ThreadPoolElementAio) request_list; int cur_threads; int idle_threads; int new_threads; /* backlog of threads we need to create */ @@ -76,14 +76,14 @@ struct ThreadPool { static void *worker_thread(void *opaque) { - ThreadPool *pool = opaque; + ThreadPoolAio *pool = opaque; qemu_mutex_lock(&pool->lock); pool->pending_threads--; do_spawn_thread(pool); while (pool->cur_threads <= pool->max_threads) { - ThreadPoolElement *req; + ThreadPoolElementAio *req; int ret; if (QTAILQ_EMPTY(&pool->request_list)) { @@ -131,7 +131,7 @@ static void *worker_thread(void *opaque) return NULL; } -static void do_spawn_thread(ThreadPool *pool) +static void do_spawn_thread(ThreadPoolAio *pool) { QemuThread t; @@ -148,14 +148,14 @@ static void do_spawn_thread(ThreadPool *pool) static void spawn_thread_bh_fn(void *opaque) { - ThreadPool *pool = opaque; + ThreadPoolAio *pool = opaque; qemu_mutex_lock(&pool->lock); do_spawn_thread(pool); qemu_mutex_unlock(&pool->lock); } -static void spawn_thread(ThreadPool *pool) +static void spawn_thread(ThreadPoolAio *pool) { pool->cur_threads++; pool->new_threads++; @@ -173,8 +173,8 @@ static void spawn_thread(ThreadPool *pool) static void thread_pool_completion_bh(void *opaque) { - ThreadPool *pool = opaque; - ThreadPoolElement *elem, *next; + ThreadPoolAio *pool = opaque; + ThreadPoolElementAio *elem, *next; defer_call_begin(); /* cb() may use defer_call() to coalesce work */ @@ -184,8 +184,8 @@ static void thread_pool_completion_bh(void *opaque) continue; } - trace_thread_pool_complete(pool, elem, elem->common.opaque, - elem->ret); + trace_thread_pool_complete_aio(pool, elem, elem->common.opaque, + elem->ret); QLIST_REMOVE(elem, all); if (elem->common.cb) { @@ -217,10 +217,10 @@ static void thread_pool_completion_bh(void *opaque) static void thread_pool_cancel(BlockAIOCB *acb) { - ThreadPoolElement *elem = (ThreadPoolElement *)acb; - ThreadPool *pool = elem->pool; + ThreadPoolElementAio *elem = (ThreadPoolElementAio *)acb; + ThreadPoolAio *pool = elem->pool; - trace_thread_pool_cancel(elem, elem->common.opaque); + trace_thread_pool_cancel_aio(elem, elem->common.opaque); QEMU_LOCK_GUARD(&pool->lock); if (elem->state == THREAD_QUEUED) { @@ -234,16 +234,16 @@ static void thread_pool_cancel(BlockAIOCB *acb) } static const AIOCBInfo thread_pool_aiocb_info = { - .aiocb_size = sizeof(ThreadPoolElement), + .aiocb_size = sizeof(ThreadPoolElementAio), .cancel_async = thread_pool_cancel, }; BlockAIOCB *thread_pool_submit_aio(ThreadPoolFunc *func, void *arg, BlockCompletionFunc *cb, void *opaque) { - ThreadPoolElement *req; + ThreadPoolElementAio *req; AioContext *ctx = qemu_get_current_aio_context(); - ThreadPool *pool = aio_get_thread_pool(ctx); + ThreadPoolAio *pool = aio_get_thread_pool(ctx); /* Assert that the thread submitting work is the same running the pool */ assert(pool->ctx == qemu_get_current_aio_context()); @@ -290,7 +290,7 @@ int coroutine_fn thread_pool_submit_co(ThreadPoolFunc *func, void *arg) return tpc.ret; } -void thread_pool_update_params(ThreadPool *pool, AioContext *ctx) +void thread_pool_update_params(ThreadPoolAio *pool, AioContext *ctx) { qemu_mutex_lock(&pool->lock); @@ -317,7 +317,7 @@ void thread_pool_update_params(ThreadPool *pool, AioContext *ctx) qemu_mutex_unlock(&pool->lock); } -static void thread_pool_init_one(ThreadPool *pool, AioContext *ctx) +static void thread_pool_init_one(ThreadPoolAio *pool, AioContext *ctx) { if (!ctx) { ctx = qemu_get_aio_context(); @@ -337,14 +337,14 @@ static void thread_pool_init_one(ThreadPool *pool, AioContext *ctx) thread_pool_update_params(pool, ctx); } -ThreadPool *thread_pool_new(AioContext *ctx) +ThreadPoolAio *thread_pool_new_aio(AioContext *ctx) { - ThreadPool *pool = g_new(ThreadPool, 1); + ThreadPoolAio *pool = g_new(ThreadPoolAio, 1); thread_pool_init_one(pool, ctx); return pool; } -void thread_pool_free(ThreadPool *pool) +void thread_pool_free_aio(ThreadPoolAio *pool) { if (!pool) { return; diff --git a/util/trace-events b/util/trace-events index 5be12d7fab89..bd8f25fb5920 100644 --- a/util/trace-events +++ b/util/trace-events @@ -15,8 +15,8 @@ reentrant_aio(void *ctx, const char *name) "ctx %p name %s" # thread-pool.c thread_pool_submit_aio(void *pool, void *req, void *opaque) "pool %p req %p opaque %p" -thread_pool_complete(void *pool, void *req, void *opaque, int ret) "pool %p req %p opaque %p ret %d" -thread_pool_cancel(void *req, void *opaque) "req %p opaque %p" +thread_pool_complete_aio(void *pool, void *req, void *opaque, int ret) "pool %p req %p opaque %p ret %d" +thread_pool_cancel_aio(void *req, void *opaque) "req %p opaque %p" # buffer.c buffer_resize(const char *buf, size_t olen, size_t len) "%s: old %zd, new %zd" From b5aa74968b27f37523c180e9c42ca007dbc7758f Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:31 +0100 Subject: [PATCH 0512/1179] thread-pool: Implement generic (non-AIO) pool support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration code wants to manage device data sending threads in one place. QEMU has an existing thread pool implementation, however it is limited to queuing AIO operations only and essentially has a 1:1 mapping between the current AioContext and the AIO ThreadPool in use. Implement generic (non-AIO) ThreadPool by essentially wrapping Glib's GThreadPool. This brings a few new operations on a pool: * thread_pool_wait() operation waits until all the submitted work requests have finished. * thread_pool_set_max_threads() explicitly sets the maximum thread count in the pool. * thread_pool_adjust_max_threads_to_work() adjusts the maximum thread count in the pool to equal the number of still waiting in queue or unfinished work. Reviewed-by: Fabiano Rosas Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/b1efaebdbea7cb7068b8fb74148777012383e12b.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/block/thread-pool.h | 51 ++++++++++++++++ util/thread-pool.c | 119 ++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/include/block/thread-pool.h b/include/block/thread-pool.h index 6f27eb085b45..dd48cf07e85f 100644 --- a/include/block/thread-pool.h +++ b/include/block/thread-pool.h @@ -38,5 +38,56 @@ BlockAIOCB *thread_pool_submit_aio(ThreadPoolFunc *func, void *arg, int coroutine_fn thread_pool_submit_co(ThreadPoolFunc *func, void *arg); void thread_pool_update_params(ThreadPoolAio *pool, struct AioContext *ctx); +/* ------------------------------------------- */ +/* Generic thread pool types and methods below */ +typedef struct ThreadPool ThreadPool; + +/* Create a new thread pool. Never returns NULL. */ +ThreadPool *thread_pool_new(void); + +/* + * Free the thread pool. + * Waits for all the previously submitted work to complete before performing + * the actual freeing operation. + */ +void thread_pool_free(ThreadPool *pool); + +/* + * Submit a new work (task) for the pool. + * + * @opaque_destroy is an optional GDestroyNotify for the @opaque argument + * to the work function at @func. + */ +void thread_pool_submit(ThreadPool *pool, ThreadPoolFunc *func, + void *opaque, GDestroyNotify opaque_destroy); + +/* + * Submit a new work (task) for the pool, making sure it starts getting + * processed immediately, launching a new thread for it if necessary. + * + * @opaque_destroy is an optional GDestroyNotify for the @opaque argument + * to the work function at @func. + */ +void thread_pool_submit_immediate(ThreadPool *pool, ThreadPoolFunc *func, + void *opaque, GDestroyNotify opaque_destroy); + +/* + * Wait for all previously submitted work to complete before returning. + * + * Can be used as a barrier between two sets of tasks executed on a thread + * pool without destroying it or in a performance sensitive path where the + * caller just wants to wait for all tasks to complete while deferring the + * pool free operation for later, less performance sensitive time. + */ +void thread_pool_wait(ThreadPool *pool); + +/* Set the maximum number of threads in the pool. */ +bool thread_pool_set_max_threads(ThreadPool *pool, int max_threads); + +/* + * Adjust the maximum number of threads in the pool to give each task its + * own thread (exactly one thread per task). + */ +bool thread_pool_adjust_max_threads_to_work(ThreadPool *pool); #endif diff --git a/util/thread-pool.c b/util/thread-pool.c index 908194dc070f..d2ead6b72857 100644 --- a/util/thread-pool.c +++ b/util/thread-pool.c @@ -374,3 +374,122 @@ void thread_pool_free_aio(ThreadPoolAio *pool) qemu_mutex_destroy(&pool->lock); g_free(pool); } + +struct ThreadPool { + GThreadPool *t; + size_t cur_work; + QemuMutex cur_work_lock; + QemuCond all_finished_cond; +}; + +typedef struct { + ThreadPoolFunc *func; + void *opaque; + GDestroyNotify opaque_destroy; +} ThreadPoolElement; + +static void thread_pool_func(gpointer data, gpointer user_data) +{ + ThreadPool *pool = user_data; + g_autofree ThreadPoolElement *el = data; + + el->func(el->opaque); + + if (el->opaque_destroy) { + el->opaque_destroy(el->opaque); + } + + QEMU_LOCK_GUARD(&pool->cur_work_lock); + + assert(pool->cur_work > 0); + pool->cur_work--; + + if (pool->cur_work == 0) { + qemu_cond_signal(&pool->all_finished_cond); + } +} + +ThreadPool *thread_pool_new(void) +{ + ThreadPool *pool = g_new(ThreadPool, 1); + + pool->cur_work = 0; + qemu_mutex_init(&pool->cur_work_lock); + qemu_cond_init(&pool->all_finished_cond); + + pool->t = g_thread_pool_new(thread_pool_func, pool, 0, TRUE, NULL); + /* + * g_thread_pool_new() can only return errors if initial thread(s) + * creation fails but we ask for 0 initial threads above. + */ + assert(pool->t); + + return pool; +} + +void thread_pool_free(ThreadPool *pool) +{ + /* + * With _wait = TRUE this effectively waits for all + * previously submitted work to complete first. + */ + g_thread_pool_free(pool->t, FALSE, TRUE); + + qemu_cond_destroy(&pool->all_finished_cond); + qemu_mutex_destroy(&pool->cur_work_lock); + + g_free(pool); +} + +void thread_pool_submit(ThreadPool *pool, ThreadPoolFunc *func, + void *opaque, GDestroyNotify opaque_destroy) +{ + ThreadPoolElement *el = g_new(ThreadPoolElement, 1); + + el->func = func; + el->opaque = opaque; + el->opaque_destroy = opaque_destroy; + + WITH_QEMU_LOCK_GUARD(&pool->cur_work_lock) { + pool->cur_work++; + } + + /* + * Ignore the return value since this function can only return errors + * if creation of an additional thread fails but even in this case the + * provided work is still getting queued (just for the existing threads). + */ + g_thread_pool_push(pool->t, el, NULL); +} + +void thread_pool_submit_immediate(ThreadPool *pool, ThreadPoolFunc *func, + void *opaque, GDestroyNotify opaque_destroy) +{ + thread_pool_submit(pool, func, opaque, opaque_destroy); + thread_pool_adjust_max_threads_to_work(pool); +} + +void thread_pool_wait(ThreadPool *pool) +{ + QEMU_LOCK_GUARD(&pool->cur_work_lock); + + while (pool->cur_work > 0) { + qemu_cond_wait(&pool->all_finished_cond, + &pool->cur_work_lock); + } +} + +bool thread_pool_set_max_threads(ThreadPool *pool, + int max_threads) +{ + assert(max_threads > 0); + + return g_thread_pool_set_max_threads(pool->t, max_threads, NULL); +} + +bool thread_pool_adjust_max_threads_to_work(ThreadPool *pool) +{ + QEMU_LOCK_GUARD(&pool->cur_work_lock); + + return thread_pool_set_max_threads(pool, pool->cur_work); +} From 4e55cb3cdeb099cb65f75f5d3b061e3e1319cf3b Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:32 +0100 Subject: [PATCH 0513/1179] migration: Add MIG_CMD_SWITCHOVER_START and its load handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This QEMU_VM_COMMAND sub-command and its switchover_start SaveVMHandler is used to mark the switchover point in main migration stream. It can be used to inform the destination that all pre-switchover main migration stream data has been sent/received so it can start to process post-switchover data that it might have received via other migration channels like the multifd ones. Add also the relevant MigrationState bit stream compatibility property and its hw_compat entry. Reviewed-by: Fabiano Rosas Reviewed-by: Zhang Chen # for the COLO part Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/311be6da85fc7e49a7598684d80aa631778dcbce.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/core/machine.c | 1 + include/migration/client-options.h | 4 +++ include/migration/register.h | 12 +++++++++ migration/colo.c | 3 +++ migration/migration-hmp-cmds.c | 2 ++ migration/migration.c | 2 ++ migration/migration.h | 2 ++ migration/options.c | 9 +++++++ migration/savevm.c | 39 ++++++++++++++++++++++++++++++ migration/savevm.h | 1 + migration/trace-events | 1 + scripts/analyze-migration.py | 11 +++++++++ 12 files changed, 87 insertions(+) diff --git a/hw/core/machine.c b/hw/core/machine.c index b68b8b94a3c4..d1ddc3a3db59 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -44,6 +44,7 @@ GlobalProperty hw_compat_9_2[] = { { "virtio-balloon-pci-non-transitional", "vectors", "0" }, { "virtio-mem-pci", "vectors", "0" }, { "migration", "multifd-clean-tls-termination", "false" }, + { "migration", "send-switchover-start", "off"}, }; const size_t hw_compat_9_2_len = G_N_ELEMENTS(hw_compat_9_2); diff --git a/include/migration/client-options.h b/include/migration/client-options.h index 59f4b55cf4f7..289c9d776221 100644 --- a/include/migration/client-options.h +++ b/include/migration/client-options.h @@ -10,6 +10,10 @@ #ifndef QEMU_MIGRATION_CLIENT_OPTIONS_H #define QEMU_MIGRATION_CLIENT_OPTIONS_H + +/* properties */ +bool migrate_send_switchover_start(void); + /* capabilities */ bool migrate_background_snapshot(void); diff --git a/include/migration/register.h b/include/migration/register.h index 0b0292738320..ff0faf5f68c8 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -279,6 +279,18 @@ typedef struct SaveVMHandlers { * otherwise */ bool (*switchover_ack_needed)(void *opaque); + + /** + * @switchover_start + * + * Notifies that the switchover has started. Called only on + * the destination. + * + * @opaque: data pointer passed to register_savevm_live() + * + * Returns zero to indicate success and negative for error + */ + int (*switchover_start)(void *opaque); } SaveVMHandlers; /** diff --git a/migration/colo.c b/migration/colo.c index 9a8e5fbe9b94..c976b3ff344d 100644 --- a/migration/colo.c +++ b/migration/colo.c @@ -452,6 +452,9 @@ static int colo_do_checkpoint_transaction(MigrationState *s, bql_unlock(); goto out; } + + qemu_savevm_maybe_send_switchover_start(s->to_dst_file); + /* Note: device state is saved into buffer */ ret = qemu_save_device_state(fb); diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c index 3347e34c4891..49c26daed359 100644 --- a/migration/migration-hmp-cmds.c +++ b/migration/migration-hmp-cmds.c @@ -46,6 +46,8 @@ static void migration_global_dump(Monitor *mon) ms->send_configuration ? "on" : "off"); monitor_printf(mon, "send-section-footer: %s\n", ms->send_section_footer ? "on" : "off"); + monitor_printf(mon, "send-switchover-start: %s\n", + ms->send_switchover_start ? "on" : "off"); monitor_printf(mon, "clear-bitmap-shift: %u\n", ms->clear_bitmap_shift); } diff --git a/migration/migration.c b/migration/migration.c index c597aa707e57..9e9db26667f1 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -2891,6 +2891,8 @@ static bool migration_switchover_start(MigrationState *s, Error **errp) precopy_notify_complete(); + qemu_savevm_maybe_send_switchover_start(s->to_dst_file); + return true; } diff --git a/migration/migration.h b/migration/migration.h index 4639e2a7e42f..7b4278e2a32b 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -400,6 +400,8 @@ struct MigrationState { bool send_configuration; /* Whether we send section footer during migration */ bool send_section_footer; + /* Whether we send switchover start notification during migration */ + bool send_switchover_start; /* Needed by postcopy-pause state */ QemuSemaphore postcopy_pause_sem; diff --git a/migration/options.c b/migration/options.c index bb259d192a93..b0ac2ea4083f 100644 --- a/migration/options.c +++ b/migration/options.c @@ -93,6 +93,8 @@ const Property migration_properties[] = { send_configuration, true), DEFINE_PROP_BOOL("send-section-footer", MigrationState, send_section_footer, true), + DEFINE_PROP_BOOL("send-switchover-start", MigrationState, + send_switchover_start, true), DEFINE_PROP_BOOL("multifd-flush-after-each-section", MigrationState, multifd_flush_after_each_section, false), DEFINE_PROP_UINT8("x-clear-bitmap-shift", MigrationState, @@ -209,6 +211,13 @@ bool migrate_auto_converge(void) return s->capabilities[MIGRATION_CAPABILITY_AUTO_CONVERGE]; } +bool migrate_send_switchover_start(void) +{ + MigrationState *s = migrate_get_current(); + + return s->send_switchover_start; +} + bool migrate_background_snapshot(void) { MigrationState *s = migrate_get_current(); diff --git a/migration/savevm.c b/migration/savevm.c index 4046faf0091e..faebf47ef51f 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -90,6 +90,7 @@ enum qemu_vm_cmd { MIG_CMD_ENABLE_COLO, /* Enable COLO */ MIG_CMD_POSTCOPY_RESUME, /* resume postcopy on dest */ MIG_CMD_RECV_BITMAP, /* Request for recved bitmap on dst */ + MIG_CMD_SWITCHOVER_START, /* Switchover start notification */ MIG_CMD_MAX }; @@ -109,6 +110,7 @@ static struct mig_cmd_args { [MIG_CMD_POSTCOPY_RESUME] = { .len = 0, .name = "POSTCOPY_RESUME" }, [MIG_CMD_PACKAGED] = { .len = 4, .name = "PACKAGED" }, [MIG_CMD_RECV_BITMAP] = { .len = -1, .name = "RECV_BITMAP" }, + [MIG_CMD_SWITCHOVER_START] = { .len = 0, .name = "SWITCHOVER_START" }, [MIG_CMD_MAX] = { .len = -1, .name = "MAX" }, }; @@ -1201,6 +1203,19 @@ void qemu_savevm_send_recv_bitmap(QEMUFile *f, char *block_name) qemu_savevm_command_send(f, MIG_CMD_RECV_BITMAP, len + 1, (uint8_t *)buf); } +static void qemu_savevm_send_switchover_start(QEMUFile *f) +{ + trace_savevm_send_switchover_start(); + qemu_savevm_command_send(f, MIG_CMD_SWITCHOVER_START, 0, NULL); +} + +void qemu_savevm_maybe_send_switchover_start(QEMUFile *f) +{ + if (migrate_send_switchover_start()) { + qemu_savevm_send_switchover_start(f); + } +} + bool qemu_savevm_state_blocked(Error **errp) { SaveStateEntry *se; @@ -1687,6 +1702,7 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp) ret = qemu_file_get_error(f); if (ret == 0) { + qemu_savevm_maybe_send_switchover_start(f); qemu_savevm_state_complete_precopy(f, false); ret = qemu_file_get_error(f); } @@ -2383,6 +2399,26 @@ static int loadvm_process_enable_colo(MigrationIncomingState *mis) return ret; } +static int loadvm_postcopy_handle_switchover_start(void) +{ + SaveStateEntry *se; + + QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { + int ret; + + if (!se->ops || !se->ops->switchover_start) { + continue; + } + + ret = se->ops->switchover_start(se->opaque); + if (ret < 0) { + return ret; + } + } + + return 0; +} + /* * Process an incoming 'QEMU_VM_COMMAND' * 0 just a normal return @@ -2481,6 +2517,9 @@ static int loadvm_process_command(QEMUFile *f) case MIG_CMD_ENABLE_COLO: return loadvm_process_enable_colo(mis); + + case MIG_CMD_SWITCHOVER_START: + return loadvm_postcopy_handle_switchover_start(); } return 0; diff --git a/migration/savevm.h b/migration/savevm.h index 7957460062ca..58f871a7ed9c 100644 --- a/migration/savevm.h +++ b/migration/savevm.h @@ -53,6 +53,7 @@ void qemu_savevm_send_postcopy_listen(QEMUFile *f); void qemu_savevm_send_postcopy_run(QEMUFile *f); void qemu_savevm_send_postcopy_resume(QEMUFile *f); void qemu_savevm_send_recv_bitmap(QEMUFile *f, char *block_name); +void qemu_savevm_maybe_send_switchover_start(QEMUFile *f); void qemu_savevm_send_postcopy_ram_discard(QEMUFile *f, const char *name, uint16_t len, diff --git a/migration/trace-events b/migration/trace-events index 58c0f07f5b2d..c506e11a2e1d 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -39,6 +39,7 @@ savevm_send_postcopy_run(void) "" savevm_send_postcopy_resume(void) "" savevm_send_colo_enable(void) "" savevm_send_recv_bitmap(char *name) "%s" +savevm_send_switchover_start(void) "" savevm_state_setup(void) "" savevm_state_resume_prepare(void) "" savevm_state_header(void) "" diff --git a/scripts/analyze-migration.py b/scripts/analyze-migration.py index 8e1fbf4c9d9f..67631ac43e9f 100755 --- a/scripts/analyze-migration.py +++ b/scripts/analyze-migration.py @@ -620,7 +620,9 @@ class MigrationDump(object): QEMU_VM_SUBSECTION = 0x05 QEMU_VM_VMDESCRIPTION = 0x06 QEMU_VM_CONFIGURATION = 0x07 + QEMU_VM_COMMAND = 0x08 QEMU_VM_SECTION_FOOTER= 0x7e + QEMU_MIG_CMD_SWITCHOVER_START = 0x0b def __init__(self, filename): self.section_classes = { @@ -685,6 +687,15 @@ def read(self, desc_only = False, dump_memory = False, elif section_type == self.QEMU_VM_SECTION_PART or section_type == self.QEMU_VM_SECTION_END: section_id = file.read32() self.sections[section_id].read() + elif section_type == self.QEMU_VM_COMMAND: + command_type = file.read16() + command_data_len = file.read16() + if command_type != self.QEMU_MIG_CMD_SWITCHOVER_START: + raise Exception("Unknown QEMU_VM_COMMAND: %x" % + (command_type)) + if command_data_len != 0: + raise Exception("Invalid SWITCHOVER_START length: %x" % + (command_data_len)) elif section_type == self.QEMU_VM_SECTION_FOOTER: read_section_id = file.read32() if read_section_id != section_id: From a30363db082a6947794be6e085ad9437798ec211 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:33 +0100 Subject: [PATCH 0514/1179] migration: Add qemu_loadvm_load_state_buffer() and its handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qemu_loadvm_load_state_buffer() and its load_state_buffer SaveVMHandler allow providing device state buffer to explicitly specified device via its idstr and instance id. Reviewed-by: Fabiano Rosas Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/71ca753286b87831ced4afd422e2e2bed071af25.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/migration/register.h | 15 +++++++++++++++ migration/savevm.c | 23 +++++++++++++++++++++++ migration/savevm.h | 3 +++ 3 files changed, 41 insertions(+) diff --git a/include/migration/register.h b/include/migration/register.h index ff0faf5f68c8..58891aa54b76 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -229,6 +229,21 @@ typedef struct SaveVMHandlers { */ int (*load_state)(QEMUFile *f, void *opaque, int version_id); + /** + * @load_state_buffer (invoked outside the BQL) + * + * Load device state buffer provided to qemu_loadvm_load_state_buffer(). + * + * @opaque: data pointer passed to register_savevm_live() + * @buf: the data buffer to load + * @len: the data length in buffer + * @errp: pointer to Error*, to store an error if it happens. + * + * Returns true to indicate success and false for errors. + */ + bool (*load_state_buffer)(void *opaque, char *buf, size_t len, + Error **errp); + /** * @load_setup * diff --git a/migration/savevm.c b/migration/savevm.c index faebf47ef51f..7c1aa8ad7b9d 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -3060,6 +3060,29 @@ int qemu_loadvm_approve_switchover(void) return migrate_send_rp_switchover_ack(mis); } +bool qemu_loadvm_load_state_buffer(const char *idstr, uint32_t instance_id, + char *buf, size_t len, Error **errp) +{ + SaveStateEntry *se; + + se = find_se(idstr, instance_id); + if (!se) { + error_setg(errp, + "Unknown idstr %s or instance id %u for load state buffer", + idstr, instance_id); + return false; + } + + if (!se->ops || !se->ops->load_state_buffer) { + error_setg(errp, + "idstr %s / instance %u has no load state buffer operation", + idstr, instance_id); + return false; + } + + return se->ops->load_state_buffer(se->opaque, buf, len, errp); +} + bool save_snapshot(const char *name, bool overwrite, const char *vmstate, bool has_devices, strList *devices, Error **errp) { diff --git a/migration/savevm.h b/migration/savevm.h index 58f871a7ed9c..cb58434a9437 100644 --- a/migration/savevm.h +++ b/migration/savevm.h @@ -71,4 +71,7 @@ int qemu_loadvm_approve_switchover(void); int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, bool in_postcopy); +bool qemu_loadvm_load_state_buffer(const char *idstr, uint32_t instance_id, + char *buf, size_t len, Error **errp); + #endif From 6a76eb4872f632974307bf12cb7f2416a77ad4a8 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Wed, 5 Mar 2025 17:49:10 +0100 Subject: [PATCH 0515/1179] migration: Always take BQL for migration_incoming_state_destroy() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All callers to migration_incoming_state_destroy() other than postcopy_ram_listen_thread() do this call with BQL held. Since migration_incoming_state_destroy() ultimately calls "load_cleanup" SaveVMHandlers and it will soon call BQL-sensitive code it makes sense to always call that function under BQL rather than to have it deal with both cases (with BQL and without BQL). Add the necessary bql_lock() and bql_unlock() to postcopy_ram_listen_thread(). qemu_loadvm_state_main() in postcopy_ram_listen_thread() could call "load_state" SaveVMHandlers that are expecting BQL to be held. In principle, the only devices that should be arriving on migration channel serviced by postcopy_ram_listen_thread() are those that are postcopiable and whose load handlers are safe to be called without BQL being held. But nothing currently prevents the source from sending data for "unsafe" devices which would cause trouble there. Add a TODO comment there so it's clear that it would be good to improve handling of such (erroneous) case in the future. Acked-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/21bb5ca337b1d5a802e697f553f37faf296b5ff4.1741193259.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- migration/migration.c | 13 +++++++++++++ migration/savevm.c | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/migration/migration.c b/migration/migration.c index 9e9db26667f1..0bf70ea9717d 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -402,10 +402,23 @@ void migration_incoming_state_destroy(void) struct MigrationIncomingState *mis = migration_incoming_get_current(); multifd_recv_cleanup(); + /* * RAM state cleanup needs to happen after multifd cleanup, because * multifd threads can use some of its states (receivedmap). + * The VFIO load_cleanup() implementation is BQL-sensitive. It requires + * BQL must NOT be taken when recycling load threads, so that it won't + * block the load threads from making progress on address space + * modification operations. + * + * To make it work, we could try to not take BQL for all load_cleanup(), + * or conditionally unlock BQL only if bql_locked() in VFIO. + * + * Since most existing call sites take BQL for load_cleanup(), make + * it simple by taking BQL always as the rule, so that VFIO can unlock + * BQL and retake unconditionally. */ + assert(bql_locked()); qemu_loadvm_state_cleanup(); if (mis->to_src_file) { diff --git a/migration/savevm.c b/migration/savevm.c index 7c1aa8ad7b9d..3e86b572cfa8 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -1986,6 +1986,8 @@ static void *postcopy_ram_listen_thread(void *opaque) * in qemu_file, and thus we must be blocking now. */ qemu_file_set_blocking(f, true); + + /* TODO: sanity check that only postcopiable data will be loaded here */ load_res = qemu_loadvm_state_main(f, mis); /* @@ -2046,7 +2048,9 @@ static void *postcopy_ram_listen_thread(void *opaque) * (If something broke then qemu will have to exit anyway since it's * got a bad migration state). */ + bql_lock(); migration_incoming_state_destroy(); + bql_unlock(); rcu_unregister_thread(); mis->have_listen_thread = false; From 18eb55546a54e443d94a4c49286348176ad4b00a Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:35 +0100 Subject: [PATCH 0516/1179] error: define g_autoptr() cleanup function for the Error type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automatic memory management helps avoid memory safety issues. Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/a5843c5fa64d7e5239a4316092ec0ef0d10c2320.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/qapi/error.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/qapi/error.h b/include/qapi/error.h index f5fe2162623e..41e381638049 100644 --- a/include/qapi/error.h +++ b/include/qapi/error.h @@ -437,6 +437,8 @@ Error *error_copy(const Error *err); */ void error_free(Error *err); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(Error, error_free) + /* * Convenience function to assert that *@errp is set, then silently free it. */ From b1937fd1eb8360d0dc0abb0a8da221d8edce3733 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:36 +0100 Subject: [PATCH 0517/1179] migration: Add thread pool of optional load threads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some drivers might want to make use of auxiliary helper threads during VM state loading, for example to make sure that their blocking (sync) I/O operations don't block the rest of the migration process. Add a migration core managed thread pool to facilitate this use case. The migration core will wait for these threads to finish before (re)starting the VM at destination. Reviewed-by: Fabiano Rosas Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/b09fd70369b6159c75847e69f235cb908b02570c.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/migration/misc.h | 3 ++ include/qemu/typedefs.h | 2 + migration/migration.c | 2 +- migration/migration.h | 5 +++ migration/savevm.c | 95 +++++++++++++++++++++++++++++++++++++++- migration/savevm.h | 2 +- 6 files changed, 105 insertions(+), 4 deletions(-) diff --git a/include/migration/misc.h b/include/migration/misc.h index c660be80954a..4c171f4e897e 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -45,9 +45,12 @@ bool migrate_ram_is_ignored(RAMBlock *block); /* migration/block.c */ AnnounceParameters *migrate_announce_params(void); + /* migration/savevm.c */ void dump_vmstate_json_to_file(FILE *out_fp); +void qemu_loadvm_start_load_thread(MigrationLoadThread function, + void *opaque); /* migration/migration.c */ void migration_object_init(void); diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h index 3d84efcac47a..fd23ff7771b1 100644 --- a/include/qemu/typedefs.h +++ b/include/qemu/typedefs.h @@ -131,5 +131,7 @@ typedef struct IRQState *qemu_irq; * Function types */ typedef void (*qemu_irq_handler)(void *opaque, int n, int level); +typedef bool (*MigrationLoadThread)(void *opaque, bool *should_quit, + Error **errp); #endif /* QEMU_TYPEDEFS_H */ diff --git a/migration/migration.c b/migration/migration.c index 0bf70ea9717d..1833cfe3580c 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -419,7 +419,7 @@ void migration_incoming_state_destroy(void) * BQL and retake unconditionally. */ assert(bql_locked()); - qemu_loadvm_state_cleanup(); + qemu_loadvm_state_cleanup(mis); if (mis->to_src_file) { /* Tell source that we are done */ diff --git a/migration/migration.h b/migration/migration.h index 7b4278e2a32b..d53f7cad84d8 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -43,6 +43,7 @@ #define MIGRATION_THREAD_DST_PREEMPT "mig/dst/preempt" struct PostcopyBlocktimeContext; +typedef struct ThreadPool ThreadPool; #define MIGRATION_RESUME_ACK_VALUE (1) @@ -187,6 +188,10 @@ struct MigrationIncomingState { Coroutine *colo_incoming_co; QemuSemaphore colo_incoming_sem; + /* Optional load threads pool and its thread exit request flag */ + ThreadPool *load_threads; + bool load_threads_abort; + /* * PostcopyBlocktimeContext to keep information for postcopy * live migration, to calculate vCPU block time diff --git a/migration/savevm.c b/migration/savevm.c index 3e86b572cfa8..1abc365570e3 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -54,6 +54,7 @@ #include "qemu/job.h" #include "qemu/main-loop.h" #include "block/snapshot.h" +#include "block/thread-pool.h" #include "qemu/cutils.h" #include "io/channel-buffer.h" #include "io/channel-file.h" @@ -131,6 +132,35 @@ static struct mig_cmd_args { * generic extendable format with an exception for two old entities. */ +/***********************************************************/ +/* Optional load threads pool support */ + +static void qemu_loadvm_thread_pool_create(MigrationIncomingState *mis) +{ + assert(!mis->load_threads); + mis->load_threads = thread_pool_new(); + mis->load_threads_abort = false; +} + +static void qemu_loadvm_thread_pool_destroy(MigrationIncomingState *mis) +{ + qatomic_set(&mis->load_threads_abort, true); + + bql_unlock(); /* Load threads might be waiting for BQL */ + g_clear_pointer(&mis->load_threads, thread_pool_free); + bql_lock(); +} + +static bool qemu_loadvm_thread_pool_wait(MigrationState *s, + MigrationIncomingState *mis) +{ + bql_unlock(); /* Let load threads do work requiring BQL */ + thread_pool_wait(mis->load_threads); + bql_lock(); + + return !migrate_has_error(s); +} + /***********************************************************/ /* savevm/loadvm support */ @@ -2783,16 +2813,68 @@ static int qemu_loadvm_state_setup(QEMUFile *f, Error **errp) return 0; } -void qemu_loadvm_state_cleanup(void) +struct LoadThreadData { + MigrationLoadThread function; + void *opaque; +}; + +static int qemu_loadvm_load_thread(void *thread_opaque) +{ + struct LoadThreadData *data = thread_opaque; + MigrationIncomingState *mis = migration_incoming_get_current(); + g_autoptr(Error) local_err = NULL; + + if (!data->function(data->opaque, &mis->load_threads_abort, &local_err)) { + MigrationState *s = migrate_get_current(); + + /* + * Can't set load_threads_abort here since processing of main migration + * channel data could still be happening, resulting in launching of new + * load threads. + */ + + assert(local_err); + + /* + * In case of multiple load threads failing which thread error + * return we end setting is purely arbitrary. + */ + migrate_set_error(s, local_err); + } + + return 0; +} + +void qemu_loadvm_start_load_thread(MigrationLoadThread function, + void *opaque) +{ + MigrationIncomingState *mis = migration_incoming_get_current(); + struct LoadThreadData *data; + + /* We only set it from this thread so it's okay to read it directly */ + assert(!mis->load_threads_abort); + + data = g_new(struct LoadThreadData, 1); + data->function = function; + data->opaque = opaque; + + thread_pool_submit_immediate(mis->load_threads, qemu_loadvm_load_thread, + data, g_free); +} + +void qemu_loadvm_state_cleanup(MigrationIncomingState *mis) { SaveStateEntry *se; trace_loadvm_state_cleanup(); + QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { if (se->ops && se->ops->load_cleanup) { se->ops->load_cleanup(se->opaque); } } + + qemu_loadvm_thread_pool_destroy(mis); } /* Return true if we should continue the migration, or false. */ @@ -2943,6 +3025,7 @@ int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis) int qemu_loadvm_state(QEMUFile *f) { + MigrationState *s = migrate_get_current(); MigrationIncomingState *mis = migration_incoming_get_current(); Error *local_err = NULL; int ret; @@ -2952,6 +3035,8 @@ int qemu_loadvm_state(QEMUFile *f) return -EINVAL; } + qemu_loadvm_thread_pool_create(mis); + ret = qemu_loadvm_state_header(f); if (ret) { return ret; @@ -2983,12 +3068,18 @@ int qemu_loadvm_state(QEMUFile *f) /* When reaching here, it must be precopy */ if (ret == 0) { - if (migrate_has_error(migrate_get_current())) { + if (migrate_has_error(migrate_get_current()) || + !qemu_loadvm_thread_pool_wait(s, mis)) { ret = -EINVAL; } else { ret = qemu_file_get_error(f); } } + /* + * Set this flag unconditionally so we'll catch further attempts to + * start additional threads via an appropriate assert() + */ + qatomic_set(&mis->load_threads_abort, true); /* * Try to read in the VMDESC section as well, so that dumping tools that diff --git a/migration/savevm.h b/migration/savevm.h index cb58434a9437..138c39a7f9f9 100644 --- a/migration/savevm.h +++ b/migration/savevm.h @@ -64,7 +64,7 @@ void qemu_savevm_live_state(QEMUFile *f); int qemu_save_device_state(QEMUFile *f); int qemu_loadvm_state(QEMUFile *f); -void qemu_loadvm_state_cleanup(void); +void qemu_loadvm_state_cleanup(MigrationIncomingState *mis); int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis); int qemu_load_device_state(QEMUFile *f); int qemu_loadvm_approve_switchover(void); From 8050c435b70b6805f05441a8a7cc6a3d70c3ee71 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:37 +0100 Subject: [PATCH 0518/1179] migration/multifd: Split packet into header and RAM data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read packet header first so in the future we will be able to differentiate between a RAM multifd packet and a device state multifd packet. Since these two are of different size we can't read the packet body until we know which packet type it is. Reviewed-by: Fabiano Rosas Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/832ad055fe447561ac1ad565d61658660cb3f63f.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- migration/multifd.c | 55 ++++++++++++++++++++++++++++++++++++--------- migration/multifd.h | 5 +++++ 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/migration/multifd.c b/migration/multifd.c index 215ad0414a79..3b47e63c2c4a 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -209,10 +209,10 @@ void multifd_send_fill_packet(MultiFDSendParams *p) memset(packet, 0, p->packet_len); - packet->magic = cpu_to_be32(MULTIFD_MAGIC); - packet->version = cpu_to_be32(MULTIFD_VERSION); + packet->hdr.magic = cpu_to_be32(MULTIFD_MAGIC); + packet->hdr.version = cpu_to_be32(MULTIFD_VERSION); - packet->flags = cpu_to_be32(p->flags); + packet->hdr.flags = cpu_to_be32(p->flags); packet->next_packet_size = cpu_to_be32(p->next_packet_size); packet_num = qatomic_fetch_inc(&multifd_send_state->packet_num); @@ -228,12 +228,12 @@ void multifd_send_fill_packet(MultiFDSendParams *p) p->flags, p->next_packet_size); } -static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) +static int multifd_recv_unfill_packet_header(MultiFDRecvParams *p, + const MultiFDPacketHdr_t *hdr, + Error **errp) { - const MultiFDPacket_t *packet = p->packet; - uint32_t magic = be32_to_cpu(packet->magic); - uint32_t version = be32_to_cpu(packet->version); - int ret = 0; + uint32_t magic = be32_to_cpu(hdr->magic); + uint32_t version = be32_to_cpu(hdr->version); if (magic != MULTIFD_MAGIC) { error_setg(errp, "multifd: received packet magic %x, expected %x", @@ -247,7 +247,16 @@ static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) return -1; } - p->flags = be32_to_cpu(packet->flags); + p->flags = be32_to_cpu(hdr->flags); + + return 0; +} + +static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) +{ + const MultiFDPacket_t *packet = p->packet; + int ret = 0; + p->next_packet_size = be32_to_cpu(packet->next_packet_size); p->packet_num = be64_to_cpu(packet->packet_num); p->packets_recved++; @@ -1165,14 +1174,18 @@ static void *multifd_recv_thread(void *opaque) } while (true) { + MultiFDPacketHdr_t hdr; uint32_t flags = 0; bool has_data = false; + uint8_t *pkt_buf; + size_t pkt_len; + p->normal_num = 0; if (use_packets) { struct iovec iov = { - .iov_base = (void *)p->packet, - .iov_len = p->packet_len + .iov_base = (void *)&hdr, + .iov_len = sizeof(hdr) }; if (multifd_recv_should_exit()) { @@ -1191,6 +1204,26 @@ static void *multifd_recv_thread(void *opaque) break; } + ret = multifd_recv_unfill_packet_header(p, &hdr, &local_err); + if (ret) { + break; + } + + pkt_buf = (uint8_t *)p->packet + sizeof(hdr); + pkt_len = p->packet_len - sizeof(hdr); + + ret = qio_channel_read_all_eof(p->c, (char *)pkt_buf, pkt_len, + &local_err); + if (!ret) { + /* EOF */ + error_setg(&local_err, "multifd: unexpected EOF after packet header"); + break; + } + + if (ret == -1) { + break; + } + qemu_mutex_lock(&p->mutex); ret = multifd_recv_unfill_packet(p, &local_err); if (ret) { diff --git a/migration/multifd.h b/migration/multifd.h index cf408ff72140..f7156f66c0f6 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -69,6 +69,11 @@ typedef struct { uint32_t magic; uint32_t version; uint32_t flags; +} __attribute__((packed)) MultiFDPacketHdr_t; + +typedef struct { + MultiFDPacketHdr_t hdr; + /* maximum number of allocated pages */ uint32_t pages_alloc; /* non zero pages */ From f588f3c46ae278661cdad5f1198a455e3ec9f910 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:38 +0100 Subject: [PATCH 0519/1179] migration/multifd: Device state transfer support - receive side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a basic support for receiving device state via multifd channels - channels that are shared with RAM transfers. Depending whether MULTIFD_FLAG_DEVICE_STATE flag is present or not in the packet header either device state (MultiFDPacketDeviceState_t) or RAM data (existing MultiFDPacket_t) is read. The received device state data is provided to qemu_loadvm_load_state_buffer() function for processing in the device's load_state_buffer handler. Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/9b86f806c134e7815ecce0eee84f0e0e34aa0146.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- migration/multifd.c | 101 +++++++++++++++++++++++++++++++++++++++----- migration/multifd.h | 19 ++++++++- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/migration/multifd.c b/migration/multifd.c index 3b47e63c2c4a..01f427d8ed03 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -21,6 +21,7 @@ #include "file.h" #include "migration.h" #include "migration-stats.h" +#include "savevm.h" #include "socket.h" #include "tls.h" #include "qemu-file.h" @@ -252,14 +253,24 @@ static int multifd_recv_unfill_packet_header(MultiFDRecvParams *p, return 0; } -static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) +static int multifd_recv_unfill_packet_device_state(MultiFDRecvParams *p, + Error **errp) +{ + MultiFDPacketDeviceState_t *packet = p->packet_dev_state; + + packet->instance_id = be32_to_cpu(packet->instance_id); + p->next_packet_size = be32_to_cpu(packet->next_packet_size); + + return 0; +} + +static int multifd_recv_unfill_packet_ram(MultiFDRecvParams *p, Error **errp) { const MultiFDPacket_t *packet = p->packet; int ret = 0; p->next_packet_size = be32_to_cpu(packet->next_packet_size); p->packet_num = be64_to_cpu(packet->packet_num); - p->packets_recved++; /* Always unfill, old QEMUs (<9.0) send data along with SYNC */ ret = multifd_ram_unfill_packet(p, errp); @@ -270,6 +281,17 @@ static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) return ret; } +static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) +{ + p->packets_recved++; + + if (p->flags & MULTIFD_FLAG_DEVICE_STATE) { + return multifd_recv_unfill_packet_device_state(p, errp); + } + + return multifd_recv_unfill_packet_ram(p, errp); +} + static bool multifd_send_should_exit(void) { return qatomic_read(&multifd_send_state->exiting); @@ -1057,6 +1079,7 @@ static void multifd_recv_cleanup_channel(MultiFDRecvParams *p) p->packet_len = 0; g_free(p->packet); p->packet = NULL; + g_clear_pointer(&p->packet_dev_state, g_free); g_free(p->normal); p->normal = NULL; g_free(p->zero); @@ -1158,6 +1181,34 @@ void multifd_recv_sync_main(void) trace_multifd_recv_sync_main(multifd_recv_state->packet_num); } +static int multifd_device_state_recv(MultiFDRecvParams *p, Error **errp) +{ + g_autofree char *dev_state_buf = NULL; + int ret; + + dev_state_buf = g_malloc(p->next_packet_size); + + ret = qio_channel_read_all(p->c, dev_state_buf, p->next_packet_size, errp); + if (ret != 0) { + return ret; + } + + if (p->packet_dev_state->idstr[sizeof(p->packet_dev_state->idstr) - 1] + != 0) { + error_setg(errp, "unterminated multifd device state idstr"); + return -1; + } + + if (!qemu_loadvm_load_state_buffer(p->packet_dev_state->idstr, + p->packet_dev_state->instance_id, + dev_state_buf, p->next_packet_size, + errp)) { + ret = -1; + } + + return ret; +} + static void *multifd_recv_thread(void *opaque) { MigrationState *s = migrate_get_current(); @@ -1176,6 +1227,7 @@ static void *multifd_recv_thread(void *opaque) while (true) { MultiFDPacketHdr_t hdr; uint32_t flags = 0; + bool is_device_state = false; bool has_data = false; uint8_t *pkt_buf; size_t pkt_len; @@ -1209,8 +1261,14 @@ static void *multifd_recv_thread(void *opaque) break; } - pkt_buf = (uint8_t *)p->packet + sizeof(hdr); - pkt_len = p->packet_len - sizeof(hdr); + is_device_state = p->flags & MULTIFD_FLAG_DEVICE_STATE; + if (is_device_state) { + pkt_buf = (uint8_t *)p->packet_dev_state + sizeof(hdr); + pkt_len = sizeof(*p->packet_dev_state) - sizeof(hdr); + } else { + pkt_buf = (uint8_t *)p->packet + sizeof(hdr); + pkt_len = p->packet_len - sizeof(hdr); + } ret = qio_channel_read_all_eof(p->c, (char *)pkt_buf, pkt_len, &local_err); @@ -1235,12 +1293,17 @@ static void *multifd_recv_thread(void *opaque) /* recv methods don't know how to handle the SYNC flag */ p->flags &= ~MULTIFD_FLAG_SYNC; - /* - * Even if it's a SYNC packet, this needs to be set - * because older QEMUs (<9.0) still send data along with - * the SYNC packet. - */ - has_data = p->normal_num || p->zero_num; + if (is_device_state) { + has_data = p->next_packet_size > 0; + } else { + /* + * Even if it's a SYNC packet, this needs to be set + * because older QEMUs (<9.0) still send data along with + * the SYNC packet. + */ + has_data = p->normal_num || p->zero_num; + } + qemu_mutex_unlock(&p->mutex); } else { /* @@ -1269,14 +1332,29 @@ static void *multifd_recv_thread(void *opaque) } if (has_data) { - ret = multifd_recv_state->ops->recv(p, &local_err); + if (is_device_state) { + assert(use_packets); + ret = multifd_device_state_recv(p, &local_err); + } else { + ret = multifd_recv_state->ops->recv(p, &local_err); + } if (ret != 0) { break; } + } else if (is_device_state) { + error_setg(&local_err, + "multifd: received empty device state packet"); + break; } if (use_packets) { if (flags & MULTIFD_FLAG_SYNC) { + if (is_device_state) { + error_setg(&local_err, + "multifd: received SYNC device state packet"); + break; + } + qemu_sem_post(&multifd_recv_state->sem_sync); qemu_sem_wait(&p->sem_sync); } @@ -1345,6 +1423,7 @@ int multifd_recv_setup(Error **errp) p->packet_len = sizeof(MultiFDPacket_t) + sizeof(uint64_t) * page_count; p->packet = g_malloc0(p->packet_len); + p->packet_dev_state = g_malloc0(sizeof(*p->packet_dev_state)); } p->name = g_strdup_printf(MIGRATION_THREAD_DST_MULTIFD, i); p->normal = g_new0(ram_addr_t, page_count); diff --git a/migration/multifd.h b/migration/multifd.h index f7156f66c0f6..d682c5a9b743 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -62,6 +62,12 @@ MultiFDRecvData *multifd_get_recv_data(void); #define MULTIFD_FLAG_UADK (8 << 1) #define MULTIFD_FLAG_QATZIP (16 << 1) +/* + * If set it means that this packet contains device state + * (MultiFDPacketDeviceState_t), not RAM data (MultiFDPacket_t). + */ +#define MULTIFD_FLAG_DEVICE_STATE (32 << 1) + /* This value needs to be a multiple of qemu_target_page_size() */ #define MULTIFD_PACKET_SIZE (512 * 1024) @@ -94,6 +100,16 @@ typedef struct { uint64_t offset[]; } __attribute__((packed)) MultiFDPacket_t; +typedef struct { + MultiFDPacketHdr_t hdr; + + char idstr[256]; + uint32_t instance_id; + + /* size of the next packet that contains the actual data */ + uint32_t next_packet_size; +} __attribute__((packed)) MultiFDPacketDeviceState_t; + typedef struct { /* number of used pages */ uint32_t num; @@ -227,8 +243,9 @@ typedef struct { /* thread local variables. No locking required */ - /* pointer to the packet */ + /* pointers to the possible packet types */ MultiFDPacket_t *packet; + MultiFDPacketDeviceState_t *packet_dev_state; /* size of the next packet that contains pages */ uint32_t next_packet_size; /* packets received through this channel */ From d19cc4dca0b21af95fee36a2ddad34eb4bd6b67f Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:39 +0100 Subject: [PATCH 0520/1179] migration/multifd: Make multifd_send() thread safe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit multifd_send() function is currently not thread safe, make it thread safe by holding a lock during its execution. This way it will be possible to safely call it concurrently from multiple threads. Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/dd0f3bcc02ca96a7d523ca58ea69e495a33b453b.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- migration/multifd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/migration/multifd.c b/migration/multifd.c index 01f427d8ed03..add6f86175c2 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -50,6 +50,10 @@ typedef struct { struct { MultiFDSendParams *params; + + /* multifd_send() body is not thread safe, needs serialization */ + QemuMutex multifd_send_mutex; + /* * Global number of generated multifd packets. * @@ -339,6 +343,8 @@ bool multifd_send(MultiFDSendData **send_data) return false; } + QEMU_LOCK_GUARD(&multifd_send_state->multifd_send_mutex); + /* We wait here, until at least one channel is ready */ qemu_sem_wait(&multifd_send_state->channels_ready); @@ -507,6 +513,7 @@ static void multifd_send_cleanup_state(void) socket_cleanup_outgoing_migration(); qemu_sem_destroy(&multifd_send_state->channels_created); qemu_sem_destroy(&multifd_send_state->channels_ready); + qemu_mutex_destroy(&multifd_send_state->multifd_send_mutex); g_free(multifd_send_state->params); multifd_send_state->params = NULL; g_free(multifd_send_state); @@ -887,6 +894,7 @@ bool multifd_send_setup(void) thread_count = migrate_multifd_channels(); multifd_send_state = g_malloc0(sizeof(*multifd_send_state)); multifd_send_state->params = g_new0(MultiFDSendParams, thread_count); + qemu_mutex_init(&multifd_send_state->multifd_send_mutex); qemu_sem_init(&multifd_send_state->channels_created, 0); qemu_sem_init(&multifd_send_state->channels_ready, 0); qatomic_set(&multifd_send_state->exiting, 0); From 7ecfab1ddd3e6678c6a0b12d348d82cfaaa406ff Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:40 +0100 Subject: [PATCH 0521/1179] migration/multifd: Add an explicit MultiFDSendData destructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way if there are fields there that needs explicit disposal (like, for example, some attached buffers) they will be handled appropriately. Add a related assert to multifd_set_payload_type() in order to make sure that this function is only used to fill a previously empty MultiFDSendData with some payload, not the other way around. Reviewed-by: Fabiano Rosas Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/6755205f2b95abbed251f87061feee1c0e410836.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- migration/multifd-nocomp.c | 3 +-- migration/multifd.c | 31 ++++++++++++++++++++++++++++--- migration/multifd.h | 5 +++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/migration/multifd-nocomp.c b/migration/multifd-nocomp.c index 1325dba97cea..e46e79d8b272 100644 --- a/migration/multifd-nocomp.c +++ b/migration/multifd-nocomp.c @@ -42,8 +42,7 @@ void multifd_ram_save_setup(void) void multifd_ram_save_cleanup(void) { - g_free(multifd_ram_send); - multifd_ram_send = NULL; + g_clear_pointer(&multifd_ram_send, multifd_send_data_free); } static void multifd_set_file_bitmap(MultiFDSendParams *p) diff --git a/migration/multifd.c b/migration/multifd.c index add6f86175c2..c8508cadab2d 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -123,6 +123,32 @@ MultiFDSendData *multifd_send_data_alloc(void) return g_malloc0(size_minus_payload + max_payload_size); } +void multifd_send_data_clear(MultiFDSendData *data) +{ + if (multifd_payload_empty(data)) { + return; + } + + switch (data->type) { + default: + /* Nothing to do */ + break; + } + + data->type = MULTIFD_PAYLOAD_NONE; +} + +void multifd_send_data_free(MultiFDSendData *data) +{ + if (!data) { + return; + } + + multifd_send_data_clear(data); + + g_free(data); +} + static bool multifd_use_packets(void) { return !migrate_mapped_ram(); @@ -496,8 +522,7 @@ static bool multifd_send_cleanup_channel(MultiFDSendParams *p, Error **errp) qemu_sem_destroy(&p->sem_sync); g_free(p->name); p->name = NULL; - g_free(p->data); - p->data = NULL; + g_clear_pointer(&p->data, multifd_send_data_free); p->packet_len = 0; g_free(p->packet); p->packet = NULL; @@ -695,7 +720,7 @@ static void *multifd_send_thread(void *opaque) (uint64_t)p->next_packet_size + p->packet_len); p->next_packet_size = 0; - multifd_set_payload_type(p->data, MULTIFD_PAYLOAD_NONE); + multifd_send_data_clear(p->data); /* * Making sure p->data is published before saying "we're diff --git a/migration/multifd.h b/migration/multifd.h index d682c5a9b743..8d639eec69fe 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -149,6 +149,9 @@ static inline bool multifd_payload_empty(MultiFDSendData *data) static inline void multifd_set_payload_type(MultiFDSendData *data, MultiFDPayloadType type) { + assert(multifd_payload_empty(data)); + assert(type != MULTIFD_PAYLOAD_NONE); + data->type = type; } @@ -365,6 +368,8 @@ static inline void multifd_send_prepare_header(MultiFDSendParams *p) void multifd_channel_connect(MultiFDSendParams *p, QIOChannel *ioc); bool multifd_send(MultiFDSendData **send_data); MultiFDSendData *multifd_send_data_alloc(void); +void multifd_send_data_clear(MultiFDSendData *data); +void multifd_send_data_free(MultiFDSendData *data); static inline uint32_t multifd_ram_page_size(void) { From 0525b91a0b993f95d29b2ea84155e7e4366c120e Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:41 +0100 Subject: [PATCH 0522/1179] migration/multifd: Device state transfer support - send side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new function multifd_queue_device_state() is provided for device to queue its state for transmission via a multifd channel. Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/ebd55768d3e5fecb5eb3f197bad9c0c07e5bc084.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/migration/misc.h | 4 ++ migration/meson.build | 1 + migration/multifd-device-state.c | 118 +++++++++++++++++++++++++++++++ migration/multifd-nocomp.c | 14 +++- migration/multifd.c | 42 +++++++++-- migration/multifd.h | 34 ++++++--- 6 files changed, 197 insertions(+), 16 deletions(-) create mode 100644 migration/multifd-device-state.c diff --git a/include/migration/misc.h b/include/migration/misc.h index 4c171f4e897e..bd3b725fa0b7 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -118,4 +118,8 @@ bool migrate_is_uri(const char *uri); bool migrate_uri_parse(const char *uri, MigrationChannel **channel, Error **errp); +/* migration/multifd-device-state.c */ +bool multifd_queue_device_state(char *idstr, uint32_t instance_id, + char *data, size_t len); + #endif diff --git a/migration/meson.build b/migration/meson.build index d3bfe84d6204..9aa48b290e2a 100644 --- a/migration/meson.build +++ b/migration/meson.build @@ -25,6 +25,7 @@ system_ss.add(files( 'migration-hmp-cmds.c', 'migration.c', 'multifd.c', + 'multifd-device-state.c', 'multifd-nocomp.c', 'multifd-zlib.c', 'multifd-zero-page.c', diff --git a/migration/multifd-device-state.c b/migration/multifd-device-state.c new file mode 100644 index 000000000000..e383e75b1a02 --- /dev/null +++ b/migration/multifd-device-state.c @@ -0,0 +1,118 @@ +/* + * Multifd device state migration + * + * Copyright (C) 2024,2025 Oracle and/or its affiliates. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/lockable.h" +#include "migration/misc.h" +#include "multifd.h" + +static struct { + QemuMutex queue_job_mutex; + + MultiFDSendData *send_data; +} *multifd_send_device_state; + +size_t multifd_device_state_payload_size(void) +{ + return sizeof(MultiFDDeviceState_t); +} + +void multifd_device_state_send_setup(void) +{ + assert(!multifd_send_device_state); + multifd_send_device_state = g_malloc(sizeof(*multifd_send_device_state)); + + qemu_mutex_init(&multifd_send_device_state->queue_job_mutex); + + multifd_send_device_state->send_data = multifd_send_data_alloc(); +} + +void multifd_device_state_send_cleanup(void) +{ + g_clear_pointer(&multifd_send_device_state->send_data, + multifd_send_data_free); + + qemu_mutex_destroy(&multifd_send_device_state->queue_job_mutex); + + g_clear_pointer(&multifd_send_device_state, g_free); +} + +void multifd_send_data_clear_device_state(MultiFDDeviceState_t *device_state) +{ + g_clear_pointer(&device_state->idstr, g_free); + g_clear_pointer(&device_state->buf, g_free); +} + +static void multifd_device_state_fill_packet(MultiFDSendParams *p) +{ + MultiFDDeviceState_t *device_state = &p->data->u.device_state; + MultiFDPacketDeviceState_t *packet = p->packet_device_state; + + packet->hdr.flags = cpu_to_be32(p->flags); + strncpy(packet->idstr, device_state->idstr, sizeof(packet->idstr) - 1); + packet->idstr[sizeof(packet->idstr) - 1] = 0; + packet->instance_id = cpu_to_be32(device_state->instance_id); + packet->next_packet_size = cpu_to_be32(p->next_packet_size); +} + +static void multifd_prepare_header_device_state(MultiFDSendParams *p) +{ + p->iov[0].iov_len = sizeof(*p->packet_device_state); + p->iov[0].iov_base = p->packet_device_state; + p->iovs_num++; +} + +void multifd_device_state_send_prepare(MultiFDSendParams *p) +{ + MultiFDDeviceState_t *device_state = &p->data->u.device_state; + + assert(multifd_payload_device_state(p->data)); + + multifd_prepare_header_device_state(p); + + assert(!(p->flags & MULTIFD_FLAG_SYNC)); + + p->next_packet_size = device_state->buf_len; + if (p->next_packet_size > 0) { + p->iov[p->iovs_num].iov_base = device_state->buf; + p->iov[p->iovs_num].iov_len = p->next_packet_size; + p->iovs_num++; + } + + p->flags |= MULTIFD_FLAG_NOCOMP | MULTIFD_FLAG_DEVICE_STATE; + + multifd_device_state_fill_packet(p); +} + +bool multifd_queue_device_state(char *idstr, uint32_t instance_id, + char *data, size_t len) +{ + /* Device state submissions can come from multiple threads */ + QEMU_LOCK_GUARD(&multifd_send_device_state->queue_job_mutex); + MultiFDDeviceState_t *device_state; + + assert(multifd_payload_empty(multifd_send_device_state->send_data)); + + multifd_set_payload_type(multifd_send_device_state->send_data, + MULTIFD_PAYLOAD_DEVICE_STATE); + device_state = &multifd_send_device_state->send_data->u.device_state; + device_state->idstr = g_strdup(idstr); + device_state->instance_id = instance_id; + device_state->buf = g_memdup2(data, len); + device_state->buf_len = len; + + if (!multifd_send(&multifd_send_device_state->send_data)) { + multifd_send_data_clear(multifd_send_device_state->send_data); + return false; + } + + return true; +} diff --git a/migration/multifd-nocomp.c b/migration/multifd-nocomp.c index e46e79d8b272..c00804652383 100644 --- a/migration/multifd-nocomp.c +++ b/migration/multifd-nocomp.c @@ -14,6 +14,7 @@ #include "exec/ramblock.h" #include "exec/target_page.h" #include "file.h" +#include "migration-stats.h" #include "multifd.h" #include "options.h" #include "qapi/error.h" @@ -85,6 +86,13 @@ static void multifd_nocomp_send_cleanup(MultiFDSendParams *p, Error **errp) return; } +static void multifd_ram_prepare_header(MultiFDSendParams *p) +{ + p->iov[0].iov_len = p->packet_len; + p->iov[0].iov_base = p->packet; + p->iovs_num++; +} + static void multifd_send_prepare_iovs(MultiFDSendParams *p) { MultiFDPages_t *pages = &p->data->u.ram; @@ -118,7 +126,7 @@ static int multifd_nocomp_send_prepare(MultiFDSendParams *p, Error **errp) * Only !zerocopy needs the header in IOV; zerocopy will * send it separately. */ - multifd_send_prepare_header(p); + multifd_ram_prepare_header(p); } multifd_send_prepare_iovs(p); @@ -133,6 +141,8 @@ static int multifd_nocomp_send_prepare(MultiFDSendParams *p, Error **errp) if (ret != 0) { return -1; } + + stat64_add(&mig_stats.multifd_bytes, p->packet_len); } return 0; @@ -431,7 +441,7 @@ int multifd_ram_flush_and_sync(QEMUFile *f) bool multifd_send_prepare_common(MultiFDSendParams *p) { MultiFDPages_t *pages = &p->data->u.ram; - multifd_send_prepare_header(p); + multifd_ram_prepare_header(p); multifd_send_zero_page_detect(p); if (!pages->normal_num) { diff --git a/migration/multifd.c b/migration/multifd.c index c8508cadab2d..3625c9a37c0e 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -12,6 +12,7 @@ #include "qemu/osdep.h" #include "qemu/cutils.h" +#include "qemu/iov.h" #include "qemu/rcu.h" #include "exec/target_page.h" #include "system/system.h" @@ -19,6 +20,7 @@ #include "qemu/error-report.h" #include "qapi/error.h" #include "file.h" +#include "migration/misc.h" #include "migration.h" #include "migration-stats.h" #include "savevm.h" @@ -111,7 +113,9 @@ MultiFDSendData *multifd_send_data_alloc(void) * added to the union in the future are larger than * (MultiFDPages_t + flex array). */ - max_payload_size = MAX(multifd_ram_payload_size(), sizeof(MultiFDPayload)); + max_payload_size = MAX(multifd_ram_payload_size(), + multifd_device_state_payload_size()); + max_payload_size = MAX(max_payload_size, sizeof(MultiFDPayload)); /* * Account for any holes the compiler might insert. We can't pack @@ -130,6 +134,9 @@ void multifd_send_data_clear(MultiFDSendData *data) } switch (data->type) { + case MULTIFD_PAYLOAD_DEVICE_STATE: + multifd_send_data_clear_device_state(&data->u.device_state); + break; default: /* Nothing to do */ break; @@ -232,6 +239,7 @@ static int multifd_recv_initial_packet(QIOChannel *c, Error **errp) return msg.id; } +/* Fills a RAM multifd packet */ void multifd_send_fill_packet(MultiFDSendParams *p) { MultiFDPacket_t *packet = p->packet; @@ -524,6 +532,7 @@ static bool multifd_send_cleanup_channel(MultiFDSendParams *p, Error **errp) p->name = NULL; g_clear_pointer(&p->data, multifd_send_data_free); p->packet_len = 0; + g_clear_pointer(&p->packet_device_state, g_free); g_free(p->packet); p->packet = NULL; multifd_send_state->ops->send_cleanup(p, errp); @@ -536,6 +545,7 @@ static void multifd_send_cleanup_state(void) { file_cleanup_outgoing_migration(); socket_cleanup_outgoing_migration(); + multifd_device_state_send_cleanup(); qemu_sem_destroy(&multifd_send_state->channels_created); qemu_sem_destroy(&multifd_send_state->channels_ready); qemu_mutex_destroy(&multifd_send_state->multifd_send_mutex); @@ -694,16 +704,32 @@ static void *multifd_send_thread(void *opaque) * qatomic_store_release() in multifd_send(). */ if (qatomic_load_acquire(&p->pending_job)) { + bool is_device_state = multifd_payload_device_state(p->data); + size_t total_size; + p->flags = 0; p->iovs_num = 0; assert(!multifd_payload_empty(p->data)); - ret = multifd_send_state->ops->send_prepare(p, &local_err); - if (ret != 0) { - break; + if (is_device_state) { + multifd_device_state_send_prepare(p); + } else { + ret = multifd_send_state->ops->send_prepare(p, &local_err); + if (ret != 0) { + break; + } } + /* + * The packet header in the zerocopy RAM case is accounted for + * in multifd_nocomp_send_prepare() - where it is actually + * being sent. + */ + total_size = iov_size(p->iov, p->iovs_num); + if (migrate_mapped_ram()) { + assert(!is_device_state); + ret = file_write_ramblock_iov(p->c, p->iov, p->iovs_num, &p->data->u.ram, &local_err); } else { @@ -716,8 +742,7 @@ static void *multifd_send_thread(void *opaque) break; } - stat64_add(&mig_stats.multifd_bytes, - (uint64_t)p->next_packet_size + p->packet_len); + stat64_add(&mig_stats.multifd_bytes, total_size); p->next_packet_size = 0; multifd_send_data_clear(p->data); @@ -938,6 +963,9 @@ bool multifd_send_setup(void) p->packet_len = sizeof(MultiFDPacket_t) + sizeof(uint64_t) * page_count; p->packet = g_malloc0(p->packet_len); + p->packet_device_state = g_malloc0(sizeof(*p->packet_device_state)); + p->packet_device_state->hdr.magic = cpu_to_be32(MULTIFD_MAGIC); + p->packet_device_state->hdr.version = cpu_to_be32(MULTIFD_VERSION); } p->name = g_strdup_printf(MIGRATION_THREAD_SRC_MULTIFD, i); p->write_flags = 0; @@ -973,6 +1001,8 @@ bool multifd_send_setup(void) assert(p->iov); } + multifd_device_state_send_setup(); + return true; err: diff --git a/migration/multifd.h b/migration/multifd.h index 8d639eec69fe..aa679d8bbe83 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -127,13 +127,22 @@ struct MultiFDRecvData { off_t file_offset; }; +typedef struct { + char *idstr; + uint32_t instance_id; + char *buf; + size_t buf_len; +} MultiFDDeviceState_t; + typedef enum { MULTIFD_PAYLOAD_NONE, MULTIFD_PAYLOAD_RAM, + MULTIFD_PAYLOAD_DEVICE_STATE, } MultiFDPayloadType; typedef union MultiFDPayload { MultiFDPages_t ram; + MultiFDDeviceState_t device_state; } MultiFDPayload; struct MultiFDSendData { @@ -146,6 +155,11 @@ static inline bool multifd_payload_empty(MultiFDSendData *data) return data->type == MULTIFD_PAYLOAD_NONE; } +static inline bool multifd_payload_device_state(MultiFDSendData *data) +{ + return data->type == MULTIFD_PAYLOAD_DEVICE_STATE; +} + static inline void multifd_set_payload_type(MultiFDSendData *data, MultiFDPayloadType type) { @@ -198,8 +212,9 @@ typedef struct { /* thread local variables. No locking required */ - /* pointer to the packet */ + /* pointers to the possible packet types */ MultiFDPacket_t *packet; + MultiFDPacketDeviceState_t *packet_device_state; /* size of the next packet that contains pages */ uint32_t next_packet_size; /* packets sent through this channel */ @@ -358,13 +373,6 @@ bool multifd_send_prepare_common(MultiFDSendParams *p); void multifd_send_zero_page_detect(MultiFDSendParams *p); void multifd_recv_zero_page_process(MultiFDRecvParams *p); -static inline void multifd_send_prepare_header(MultiFDSendParams *p) -{ - p->iov[0].iov_len = p->packet_len; - p->iov[0].iov_base = p->packet; - p->iovs_num++; -} - void multifd_channel_connect(MultiFDSendParams *p, QIOChannel *ioc); bool multifd_send(MultiFDSendData **send_data); MultiFDSendData *multifd_send_data_alloc(void); @@ -389,4 +397,14 @@ bool multifd_ram_sync_per_section(void); size_t multifd_ram_payload_size(void); void multifd_ram_fill_packet(MultiFDSendParams *p); int multifd_ram_unfill_packet(MultiFDRecvParams *p, Error **errp); + +size_t multifd_device_state_payload_size(void); + +void multifd_send_data_clear_device_state(MultiFDDeviceState_t *device_state); + +void multifd_device_state_send_setup(void); +void multifd_device_state_send_cleanup(void); + +void multifd_device_state_send_prepare(MultiFDSendParams *p); + #endif From 99fab22350e4eed1d8a238ed97c77b1a5ee77dd4 Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Tue, 4 Mar 2025 23:03:42 +0100 Subject: [PATCH 0523/1179] migration/multifd: Make MultiFDSendData a struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The newly introduced device state buffer can be used for either storing VFIO's read() raw data, but already also possible to store generic device states. After noticing that device states may not easily provide a max buffer size (also the fact that RAM MultiFDPages_t after all also want to have flexibility on managing offset[] array), it may not be a good idea to stick with union on MultiFDSendData.. as it won't play well with such flexibility. Switch MultiFDSendData to a struct. It won't consume a lot more space in reality, after all the real buffers were already dynamically allocated, so it's so far only about the two structs (pages, device_state) that will be duplicated, but they're small. With this, we can remove the pretty hard to understand alloc size logic. Because now we can allocate offset[] together with the SendData, and properly free it when the SendData is freed. [MSS: Make sure to clear possible device state payload before freeing MultiFDSendData, remove placeholders for other patches not included] Signed-off-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Acked-by: Fabiano Rosas Link: https://lore.kernel.org/qemu-devel/7b02baba8e6ddb23ef7c349d312b9b631db09d7e.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- migration/multifd-device-state.c | 5 ----- migration/multifd-nocomp.c | 13 ++++++------- migration/multifd.c | 25 +++++++------------------ migration/multifd.h | 15 +++++++++------ 4 files changed, 22 insertions(+), 36 deletions(-) diff --git a/migration/multifd-device-state.c b/migration/multifd-device-state.c index e383e75b1a02..64d8ca180167 100644 --- a/migration/multifd-device-state.c +++ b/migration/multifd-device-state.c @@ -20,11 +20,6 @@ static struct { MultiFDSendData *send_data; } *multifd_send_device_state; -size_t multifd_device_state_payload_size(void) -{ - return sizeof(MultiFDDeviceState_t); -} - void multifd_device_state_send_setup(void) { assert(!multifd_send_device_state); diff --git a/migration/multifd-nocomp.c b/migration/multifd-nocomp.c index c00804652383..ffe75256c9fb 100644 --- a/migration/multifd-nocomp.c +++ b/migration/multifd-nocomp.c @@ -25,15 +25,14 @@ static MultiFDSendData *multifd_ram_send; -size_t multifd_ram_payload_size(void) +void multifd_ram_payload_alloc(MultiFDPages_t *pages) { - uint32_t n = multifd_ram_page_count(); + pages->offset = g_new0(ram_addr_t, multifd_ram_page_count()); +} - /* - * We keep an array of page offsets at the end of MultiFDPages_t, - * add space for it in the allocation. - */ - return sizeof(MultiFDPages_t) + n * sizeof(ram_addr_t); +void multifd_ram_payload_free(MultiFDPages_t *pages) +{ + g_clear_pointer(&pages->offset, g_free); } void multifd_ram_save_setup(void) diff --git a/migration/multifd.c b/migration/multifd.c index 3625c9a37c0e..dfb5189f0ea3 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -105,26 +105,12 @@ struct { MultiFDSendData *multifd_send_data_alloc(void) { - size_t max_payload_size, size_minus_payload; + MultiFDSendData *new = g_new0(MultiFDSendData, 1); - /* - * MultiFDPages_t has a flexible array at the end, account for it - * when allocating MultiFDSendData. Use max() in case other types - * added to the union in the future are larger than - * (MultiFDPages_t + flex array). - */ - max_payload_size = MAX(multifd_ram_payload_size(), - multifd_device_state_payload_size()); - max_payload_size = MAX(max_payload_size, sizeof(MultiFDPayload)); - - /* - * Account for any holes the compiler might insert. We can't pack - * the structure because that misaligns the members and triggers - * Waddress-of-packed-member. - */ - size_minus_payload = sizeof(MultiFDSendData) - sizeof(MultiFDPayload); + multifd_ram_payload_alloc(&new->u.ram); + /* Device state allocates its payload on-demand */ - return g_malloc0(size_minus_payload + max_payload_size); + return new; } void multifd_send_data_clear(MultiFDSendData *data) @@ -151,8 +137,11 @@ void multifd_send_data_free(MultiFDSendData *data) return; } + /* This also free's device state payload */ multifd_send_data_clear(data); + multifd_ram_payload_free(&data->u.ram); + g_free(data); } diff --git a/migration/multifd.h b/migration/multifd.h index aa679d8bbe83..2d337e7b3b52 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -115,9 +115,13 @@ typedef struct { uint32_t num; /* number of normal pages */ uint32_t normal_num; + /* + * Pointer to the ramblock. NOTE: it's caller's responsibility to make + * sure the pointer is always valid! + */ RAMBlock *block; - /* offset of each page */ - ram_addr_t offset[]; + /* offset array of each page, managed by multifd */ + ram_addr_t *offset; } MultiFDPages_t; struct MultiFDRecvData { @@ -140,7 +144,7 @@ typedef enum { MULTIFD_PAYLOAD_DEVICE_STATE, } MultiFDPayloadType; -typedef union MultiFDPayload { +typedef struct MultiFDPayload { MultiFDPages_t ram; MultiFDDeviceState_t device_state; } MultiFDPayload; @@ -394,12 +398,11 @@ void multifd_ram_save_cleanup(void); int multifd_ram_flush_and_sync(QEMUFile *f); bool multifd_ram_sync_per_round(void); bool multifd_ram_sync_per_section(void); -size_t multifd_ram_payload_size(void); +void multifd_ram_payload_alloc(MultiFDPages_t *pages); +void multifd_ram_payload_free(MultiFDPages_t *pages); void multifd_ram_fill_packet(MultiFDSendParams *p); int multifd_ram_unfill_packet(MultiFDRecvParams *p, Error **errp); -size_t multifd_device_state_payload_size(void); - void multifd_send_data_clear_device_state(MultiFDDeviceState_t *device_state); void multifd_device_state_send_setup(void); From a1131aa94256d1007b4267ff9e79c3b4ada6e2b9 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:43 +0100 Subject: [PATCH 0524/1179] migration/multifd: Add multifd_device_state_supported() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since device state transfer via multifd channels requires multifd channels with packets and is currently not compatible with multifd compression add an appropriate query function so device can learn whether it can actually make use of it. Reviewed-by: Fabiano Rosas Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/1ff0d98b85f470e5a33687406e877583b8fab74e.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/migration/misc.h | 1 + migration/multifd-device-state.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/migration/misc.h b/include/migration/misc.h index bd3b725fa0b7..273ebfca6256 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -121,5 +121,6 @@ bool migrate_uri_parse(const char *uri, MigrationChannel **channel, /* migration/multifd-device-state.c */ bool multifd_queue_device_state(char *idstr, uint32_t instance_id, char *data, size_t len); +bool multifd_device_state_supported(void); #endif diff --git a/migration/multifd-device-state.c b/migration/multifd-device-state.c index 64d8ca180167..3097ffa31025 100644 --- a/migration/multifd-device-state.c +++ b/migration/multifd-device-state.c @@ -13,6 +13,7 @@ #include "qemu/lockable.h" #include "migration/misc.h" #include "multifd.h" +#include "options.h" static struct { QemuMutex queue_job_mutex; @@ -111,3 +112,9 @@ bool multifd_queue_device_state(char *idstr, uint32_t instance_id, return true; } + +bool multifd_device_state_supported(void) +{ + return migrate_multifd() && !migrate_mapped_ram() && + migrate_multifd_compression() == MULTIFD_COMPRESSION_NONE; +} From 8305921a91a940023fe971c74eb1e06f2725ebab Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:44 +0100 Subject: [PATCH 0525/1179] migration: Add save_live_complete_precopy_thread handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This SaveVMHandler helps device provide its own asynchronous transmission of the remaining data at the end of a precopy phase via multifd channels, in parallel with the transfer done by save_live_complete_precopy handlers. These threads are launched only when multifd device state transfer is supported. Management of these threads in done in the multifd migration code, wrapping them in the generic thread pool. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Peter Xu Link: https://lore.kernel.org/qemu-devel/eac74a4ca7edd8968bbf72aa07b9041c76364a16.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- include/migration/misc.h | 17 ++++++ include/migration/register.h | 19 +++++++ include/qemu/typedefs.h | 3 ++ migration/multifd-device-state.c | 92 ++++++++++++++++++++++++++++++++ migration/savevm.c | 40 +++++++++++++- 5 files changed, 170 insertions(+), 1 deletion(-) diff --git a/include/migration/misc.h b/include/migration/misc.h index 273ebfca6256..8fd36eba1da7 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -119,8 +119,25 @@ bool migrate_uri_parse(const char *uri, MigrationChannel **channel, Error **errp); /* migration/multifd-device-state.c */ +typedef struct SaveLiveCompletePrecopyThreadData { + SaveLiveCompletePrecopyThreadHandler hdlr; + char *idstr; + uint32_t instance_id; + void *handler_opaque; +} SaveLiveCompletePrecopyThreadData; + bool multifd_queue_device_state(char *idstr, uint32_t instance_id, char *data, size_t len); bool multifd_device_state_supported(void); +void +multifd_spawn_device_state_save_thread(SaveLiveCompletePrecopyThreadHandler hdlr, + char *idstr, uint32_t instance_id, + void *opaque); + +bool multifd_device_state_save_thread_should_exit(void); + +void multifd_abort_device_state_save_threads(void); +bool multifd_join_device_state_save_threads(void); + #endif diff --git a/include/migration/register.h b/include/migration/register.h index 58891aa54b76..c041ce32f2fc 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -105,6 +105,25 @@ typedef struct SaveVMHandlers { */ int (*save_live_complete_precopy)(QEMUFile *f, void *opaque); + /** + * @save_live_complete_precopy_thread (invoked in a separate thread) + * + * Called at the end of a precopy phase from a separate worker thread + * in configurations where multifd device state transfer is supported + * in order to perform asynchronous transmission of the remaining data in + * parallel with @save_live_complete_precopy handlers. + * When postcopy is enabled, devices that support postcopy will skip this + * step. + * + * @d: a #SaveLiveCompletePrecopyThreadData containing parameters that the + * handler may need, including this device section idstr and instance_id, + * and opaque data pointer passed to register_savevm_live(). + * @errp: pointer to Error*, to store an error if it happens. + * + * Returns true to indicate success and false for errors. + */ + SaveLiveCompletePrecopyThreadHandler save_live_complete_precopy_thread; + /* This runs both outside and inside the BQL. */ /** diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h index fd23ff7771b1..42ed4e6be150 100644 --- a/include/qemu/typedefs.h +++ b/include/qemu/typedefs.h @@ -108,6 +108,7 @@ typedef struct QString QString; typedef struct RAMBlock RAMBlock; typedef struct Range Range; typedef struct ReservedRegion ReservedRegion; +typedef struct SaveLiveCompletePrecopyThreadData SaveLiveCompletePrecopyThreadData; typedef struct SHPCDevice SHPCDevice; typedef struct SSIBus SSIBus; typedef struct TCGCPUOps TCGCPUOps; @@ -133,5 +134,7 @@ typedef struct IRQState *qemu_irq; typedef void (*qemu_irq_handler)(void *opaque, int n, int level); typedef bool (*MigrationLoadThread)(void *opaque, bool *should_quit, Error **errp); +typedef bool (*SaveLiveCompletePrecopyThreadHandler)(SaveLiveCompletePrecopyThreadData *d, + Error **errp); #endif /* QEMU_TYPEDEFS_H */ diff --git a/migration/multifd-device-state.c b/migration/multifd-device-state.c index 3097ffa31025..94222d0eb0d8 100644 --- a/migration/multifd-device-state.c +++ b/migration/multifd-device-state.c @@ -10,7 +10,10 @@ */ #include "qemu/osdep.h" +#include "qapi/error.h" #include "qemu/lockable.h" +#include "block/thread-pool.h" +#include "migration.h" #include "migration/misc.h" #include "multifd.h" #include "options.h" @@ -19,6 +22,9 @@ static struct { QemuMutex queue_job_mutex; MultiFDSendData *send_data; + + ThreadPool *threads; + bool threads_abort; } *multifd_send_device_state; void multifd_device_state_send_setup(void) @@ -29,10 +35,14 @@ void multifd_device_state_send_setup(void) qemu_mutex_init(&multifd_send_device_state->queue_job_mutex); multifd_send_device_state->send_data = multifd_send_data_alloc(); + + multifd_send_device_state->threads = thread_pool_new(); + multifd_send_device_state->threads_abort = false; } void multifd_device_state_send_cleanup(void) { + g_clear_pointer(&multifd_send_device_state->threads, thread_pool_free); g_clear_pointer(&multifd_send_device_state->send_data, multifd_send_data_free); @@ -118,3 +128,85 @@ bool multifd_device_state_supported(void) return migrate_multifd() && !migrate_mapped_ram() && migrate_multifd_compression() == MULTIFD_COMPRESSION_NONE; } + +static void multifd_device_state_save_thread_data_free(void *opaque) +{ + SaveLiveCompletePrecopyThreadData *data = opaque; + + g_clear_pointer(&data->idstr, g_free); + g_free(data); +} + +static int multifd_device_state_save_thread(void *opaque) +{ + SaveLiveCompletePrecopyThreadData *data = opaque; + g_autoptr(Error) local_err = NULL; + + if (!data->hdlr(data, &local_err)) { + MigrationState *s = migrate_get_current(); + + /* + * Can't call abort_device_state_save_threads() here since new + * save threads could still be in process of being launched + * (if, for example, the very first save thread launched exited + * with an error very quickly). + */ + + assert(local_err); + + /* + * In case of multiple save threads failing which thread error + * return we end setting is purely arbitrary. + */ + migrate_set_error(s, local_err); + } + + return 0; +} + +bool multifd_device_state_save_thread_should_exit(void) +{ + return qatomic_read(&multifd_send_device_state->threads_abort); +} + +void +multifd_spawn_device_state_save_thread(SaveLiveCompletePrecopyThreadHandler hdlr, + char *idstr, uint32_t instance_id, + void *opaque) +{ + SaveLiveCompletePrecopyThreadData *data; + + assert(multifd_device_state_supported()); + assert(multifd_send_device_state); + + assert(!qatomic_read(&multifd_send_device_state->threads_abort)); + + data = g_new(SaveLiveCompletePrecopyThreadData, 1); + data->hdlr = hdlr; + data->idstr = g_strdup(idstr); + data->instance_id = instance_id; + data->handler_opaque = opaque; + + thread_pool_submit_immediate(multifd_send_device_state->threads, + multifd_device_state_save_thread, + data, + multifd_device_state_save_thread_data_free); +} + +void multifd_abort_device_state_save_threads(void) +{ + assert(multifd_device_state_supported()); + + qatomic_set(&multifd_send_device_state->threads_abort, true); +} + +bool multifd_join_device_state_save_threads(void) +{ + MigrationState *s = migrate_get_current(); + + assert(multifd_device_state_supported()); + + thread_pool_wait(multifd_send_device_state->threads); + + return !migrate_has_error(s); +} diff --git a/migration/savevm.c b/migration/savevm.c index 1abc365570e3..5c4fdfd95eeb 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -37,6 +37,7 @@ #include "migration/register.h" #include "migration/global_state.h" #include "migration/channel-block.h" +#include "multifd.h" #include "ram.h" #include "qemu-file.h" #include "savevm.h" @@ -1527,6 +1528,24 @@ int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy) int64_t start_ts_each, end_ts_each; SaveStateEntry *se; int ret; + bool multifd_device_state = multifd_device_state_supported(); + + if (multifd_device_state) { + QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { + SaveLiveCompletePrecopyThreadHandler hdlr; + + if (!se->ops || (in_postcopy && se->ops->has_postcopy && + se->ops->has_postcopy(se->opaque)) || + !se->ops->save_live_complete_precopy_thread) { + continue; + } + + hdlr = se->ops->save_live_complete_precopy_thread; + multifd_spawn_device_state_save_thread(hdlr, + se->idstr, se->instance_id, + se->opaque); + } + } QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { if (!se->ops || @@ -1552,16 +1571,35 @@ int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy) save_section_footer(f, se); if (ret < 0) { qemu_file_set_error(f, ret); - return -1; + goto ret_fail_abort_threads; } end_ts_each = qemu_clock_get_us(QEMU_CLOCK_REALTIME); trace_vmstate_downtime_save("iterable", se->idstr, se->instance_id, end_ts_each - start_ts_each); } + if (multifd_device_state) { + if (migrate_has_error(migrate_get_current())) { + multifd_abort_device_state_save_threads(); + } + + if (!multifd_join_device_state_save_threads()) { + qemu_file_set_error(f, -EINVAL); + return -1; + } + } + trace_vmstate_downtime_checkpoint("src-iterable-saved"); return 0; + +ret_fail_abort_threads: + if (multifd_device_state) { + multifd_abort_device_state_save_threads(); + multifd_join_device_state_save_threads(); + } + + return -1; } int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, From 5963c219a0e60d3f20c09ba2d34671d5e9623e70 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:45 +0100 Subject: [PATCH 0526/1179] vfio/migration: Add load_device_config_state_start trace event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And rename existing load_device_config_state trace event to load_device_config_state_end for consistency since it is triggered at the end of loading of the VFIO device config state. This way both the start and end points of particular device config loading operation (a long, BQL-serialized operation) are known. Reviewed-by: Cédric Le Goater Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/1b6c5a2097e64c272eb7e53f9e4cca4b79581b38.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration.c | 4 +++- hw/vfio/trace-events | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index adfa752db527..03890eaa48a9 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -285,6 +285,8 @@ static int vfio_load_device_config_state(QEMUFile *f, void *opaque) VFIODevice *vbasedev = opaque; uint64_t data; + trace_vfio_load_device_config_state_start(vbasedev->name); + if (vbasedev->ops && vbasedev->ops->vfio_load_config) { int ret; @@ -303,7 +305,7 @@ static int vfio_load_device_config_state(QEMUFile *f, void *opaque) return -EINVAL; } - trace_vfio_load_device_config_state(vbasedev->name); + trace_vfio_load_device_config_state_end(vbasedev->name); return qemu_file_get_error(f); } diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index c5385e1a4f98..a02c668f28a4 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -150,7 +150,8 @@ vfio_display_edid_write_error(void) "" # migration.c vfio_load_cleanup(const char *name) " (%s)" -vfio_load_device_config_state(const char *name) " (%s)" +vfio_load_device_config_state_start(const char *name) " (%s)" +vfio_load_device_config_state_end(const char *name) " (%s)" vfio_load_state(const char *name, uint64_t data) " (%s) data 0x%"PRIx64 vfio_load_state_device_data(const char *name, uint64_t data_size, int ret) " (%s) size %"PRIu64" ret %d" vfio_migration_realize(const char *name) " (%s)" From bd846c5d583a63eaaf836403515c4bf3748f3bb3 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:46 +0100 Subject: [PATCH 0527/1179] vfio/migration: Convert bytes_transferred counter to atomic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So it can be safety accessed from multiple threads. This variable type needs to be changed to unsigned long since 32-bit host platforms lack the necessary addition atomics on 64-bit variables. Using 32-bit counters on 32-bit host platforms should not be a problem in practice since they can't realistically address more memory anyway. Reviewed-by: Cédric Le Goater Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/dc391771d2d9ad0f311994f0cb9e666da564aeaf.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 03890eaa48a9..5532787be63b 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -55,7 +55,7 @@ */ #define VFIO_MIG_DEFAULT_DATA_BUFFER_SIZE (1 * MiB) -static int64_t bytes_transferred; +static unsigned long bytes_transferred; static const char *mig_state_to_str(enum vfio_device_mig_state state) { @@ -391,7 +391,7 @@ static ssize_t vfio_save_block(QEMUFile *f, VFIOMigration *migration) qemu_put_be64(f, VFIO_MIG_FLAG_DEV_DATA_STATE); qemu_put_be64(f, data_size); qemu_put_buffer(f, migration->data_buffer, data_size); - bytes_transferred += data_size; + qatomic_add(&bytes_transferred, data_size); trace_vfio_save_block(migration->vbasedev->name, data_size); @@ -1013,12 +1013,12 @@ static int vfio_block_migration(VFIODevice *vbasedev, Error *err, Error **errp) int64_t vfio_mig_bytes_transferred(void) { - return bytes_transferred; + return MIN(qatomic_read(&bytes_transferred), INT64_MAX); } void vfio_reset_bytes_transferred(void) { - bytes_transferred = 0; + qatomic_set(&bytes_transferred, 0); } /* From 47c7133629cc35b417b8f32512a4715ee53bfae3 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:47 +0100 Subject: [PATCH 0528/1179] vfio/migration: Add vfio_add_bytes_transferred() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way bytes_transferred can also be incremented in other translation units than migration.c. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/d1fbc27ac2417b49892f354ba20f6c6b3f7209f8.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration.c | 7 ++++++- include/hw/vfio/vfio-common.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 5532787be63b..51c056e152aa 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -391,7 +391,7 @@ static ssize_t vfio_save_block(QEMUFile *f, VFIOMigration *migration) qemu_put_be64(f, VFIO_MIG_FLAG_DEV_DATA_STATE); qemu_put_be64(f, data_size); qemu_put_buffer(f, migration->data_buffer, data_size); - qatomic_add(&bytes_transferred, data_size); + vfio_mig_add_bytes_transferred(data_size); trace_vfio_save_block(migration->vbasedev->name, data_size); @@ -1021,6 +1021,11 @@ void vfio_reset_bytes_transferred(void) qatomic_set(&bytes_transferred, 0); } +void vfio_mig_add_bytes_transferred(unsigned long val) +{ + qatomic_add(&bytes_transferred, val); +} + /* * Return true when either migration initialized or blocker registered. * Currently only return false when adding blocker fails which will diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h index ac35136a1105..5c84ebb00298 100644 --- a/include/hw/vfio/vfio-common.h +++ b/include/hw/vfio/vfio-common.h @@ -274,6 +274,7 @@ void vfio_unblock_multiple_devices_migration(void); bool vfio_viommu_preset(VFIODevice *vbasedev); int64_t vfio_mig_bytes_transferred(void); void vfio_reset_bytes_transferred(void); +void vfio_mig_add_bytes_transferred(unsigned long val); bool vfio_device_state_is_running(VFIODevice *vbasedev); bool vfio_device_state_is_precopy(VFIODevice *vbasedev); From eb6608619a16ab949f3cf1138638d17afa45fe4d Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:48 +0100 Subject: [PATCH 0529/1179] vfio/migration: Move migration channel flags to vfio-common.h header file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way they can also be referenced in other translation units than migration.c. Reviewed-by: Cédric Le Goater Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/26a940f6b22c1b685818251b7a3ddbbca601b1d6.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration.c | 17 ----------------- include/hw/vfio/vfio-common.h | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 51c056e152aa..a9b0970604aa 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -31,23 +31,6 @@ #include "trace.h" #include "hw/hw.h" -/* - * Flags to be used as unique delimiters for VFIO devices in the migration - * stream. These flags are composed as: - * 0xffffffff => MSB 32-bit all 1s - * 0xef10 => Magic ID, represents emulated (virtual) function IO - * 0x0000 => 16-bits reserved for flags - * - * The beginning of state information is marked by _DEV_CONFIG_STATE, - * _DEV_SETUP_STATE, or _DEV_DATA_STATE, respectively. The end of a - * certain state information is marked by _END_OF_STATE. - */ -#define VFIO_MIG_FLAG_END_OF_STATE (0xffffffffef100001ULL) -#define VFIO_MIG_FLAG_DEV_CONFIG_STATE (0xffffffffef100002ULL) -#define VFIO_MIG_FLAG_DEV_SETUP_STATE (0xffffffffef100003ULL) -#define VFIO_MIG_FLAG_DEV_DATA_STATE (0xffffffffef100004ULL) -#define VFIO_MIG_FLAG_DEV_INIT_DATA_SENT (0xffffffffef100005ULL) - /* * This is an arbitrary size based on migration of mlx5 devices, where typically * total device migration size is on the order of 100s of MB. Testing with diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h index 5c84ebb00298..bf5d52087129 100644 --- a/include/hw/vfio/vfio-common.h +++ b/include/hw/vfio/vfio-common.h @@ -36,6 +36,23 @@ #define VFIO_MSG_PREFIX "vfio %s: " +/* + * Flags to be used as unique delimiters for VFIO devices in the migration + * stream. These flags are composed as: + * 0xffffffff => MSB 32-bit all 1s + * 0xef10 => Magic ID, represents emulated (virtual) function IO + * 0x0000 => 16-bits reserved for flags + * + * The beginning of state information is marked by _DEV_CONFIG_STATE, + * _DEV_SETUP_STATE, or _DEV_DATA_STATE, respectively. The end of a + * certain state information is marked by _END_OF_STATE. + */ +#define VFIO_MIG_FLAG_END_OF_STATE (0xffffffffef100001ULL) +#define VFIO_MIG_FLAG_DEV_CONFIG_STATE (0xffffffffef100002ULL) +#define VFIO_MIG_FLAG_DEV_SETUP_STATE (0xffffffffef100003ULL) +#define VFIO_MIG_FLAG_DEV_DATA_STATE (0xffffffffef100004ULL) +#define VFIO_MIG_FLAG_DEV_INIT_DATA_SENT (0xffffffffef100005ULL) + enum { VFIO_DEVICE_TYPE_PCI = 0, VFIO_DEVICE_TYPE_PLATFORM = 1, From 961165122bc8a4cf58eaaab75ff09ba8fc950a88 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:49 +0100 Subject: [PATCH 0530/1179] vfio/migration: Multifd device state transfer support - basic types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add basic types and flags used by VFIO multifd device state transfer support. Since we'll be introducing a lot of multifd transfer specific code, add a new file migration-multifd.c to home it, wired into main VFIO migration code (migration.c) via migration-multifd.h header file. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/4eedd529e6617f80f3d6a66d7268a0db2bc173fa.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/meson.build | 1 + hw/vfio/migration-multifd.c | 33 +++++++++++++++++++++++++++++++++ hw/vfio/migration-multifd.h | 17 +++++++++++++++++ hw/vfio/migration.c | 1 + 4 files changed, 52 insertions(+) create mode 100644 hw/vfio/migration-multifd.c create mode 100644 hw/vfio/migration-multifd.h diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build index bba776f75cc7..260d65febd6b 100644 --- a/hw/vfio/meson.build +++ b/hw/vfio/meson.build @@ -5,6 +5,7 @@ vfio_ss.add(files( 'container-base.c', 'container.c', 'migration.c', + 'migration-multifd.c', 'cpr.c', )) vfio_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr.c')) diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c new file mode 100644 index 000000000000..fa594b33fdd1 --- /dev/null +++ b/hw/vfio/migration-multifd.c @@ -0,0 +1,33 @@ +/* + * Multifd VFIO migration + * + * Copyright (C) 2024,2025 Oracle and/or its affiliates. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/vfio/vfio-common.h" +#include "migration/misc.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/lockable.h" +#include "qemu/main-loop.h" +#include "qemu/thread.h" +#include "migration/qemu-file.h" +#include "migration-multifd.h" +#include "trace.h" + +#define VFIO_DEVICE_STATE_CONFIG_STATE (1) + +#define VFIO_DEVICE_STATE_PACKET_VER_CURRENT (0) + +typedef struct VFIODeviceStatePacket { + uint32_t version; + uint32_t idx; + uint32_t flags; + uint8_t data[0]; +} QEMU_PACKED VFIODeviceStatePacket; diff --git a/hw/vfio/migration-multifd.h b/hw/vfio/migration-multifd.h new file mode 100644 index 000000000000..5b221c6e16b0 --- /dev/null +++ b/hw/vfio/migration-multifd.h @@ -0,0 +1,17 @@ +/* + * Multifd VFIO migration + * + * Copyright (C) 2024,2025 Oracle and/or its affiliates. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_VFIO_MIGRATION_MULTIFD_H +#define HW_VFIO_MIGRATION_MULTIFD_H + +#include "hw/vfio/vfio-common.h" + +#endif diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index a9b0970604aa..dc1fe4e717a4 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -23,6 +23,7 @@ #include "migration/qemu-file.h" #include "migration/register.h" #include "migration/blocker.h" +#include "migration-multifd.h" #include "qapi/error.h" #include "qapi/qapi-events-vfio.h" #include "exec/ramlist.h" From 2efa35d34edfeca0d151de8283e52006e2c6cbd4 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:50 +0100 Subject: [PATCH 0531/1179] vfio/migration: Multifd device state transfer - add support checking function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add vfio_multifd_transfer_supported() function that tells whether the multifd device state transfer is supported. Reviewed-by: Cédric Le Goater Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/8ce50256f341b3d47342bb217cb5fbb2deb14639.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration-multifd.c | 6 ++++++ hw/vfio/migration-multifd.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index fa594b33fdd1..79fae0b6296f 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -31,3 +31,9 @@ typedef struct VFIODeviceStatePacket { uint32_t flags; uint8_t data[0]; } QEMU_PACKED VFIODeviceStatePacket; + +bool vfio_multifd_transfer_supported(void) +{ + return multifd_device_state_supported() && + migrate_send_switchover_start(); +} diff --git a/hw/vfio/migration-multifd.h b/hw/vfio/migration-multifd.h index 5b221c6e16b0..1b60d5f67a1c 100644 --- a/hw/vfio/migration-multifd.h +++ b/hw/vfio/migration-multifd.h @@ -14,4 +14,6 @@ #include "hw/vfio/vfio-common.h" +bool vfio_multifd_transfer_supported(void); + #endif From ff2fd1f7e23f528f03f41b5475afb173718fea07 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:51 +0100 Subject: [PATCH 0532/1179] vfio/migration: Multifd setup/cleanup functions and associated VFIOMultifd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add multifd setup/cleanup functions and an associated VFIOMultifd data structure that will contain most of the receive-side data together with its init/cleanup methods. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/c0520523053b1087787152ddf2163257d3030be0.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration-multifd.c | 44 +++++++++++++++++++++++++++++++++++ hw/vfio/migration-multifd.h | 4 ++++ include/hw/vfio/vfio-common.h | 3 +++ 3 files changed, 51 insertions(+) diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index 79fae0b6296f..091dc43210ad 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -32,8 +32,52 @@ typedef struct VFIODeviceStatePacket { uint8_t data[0]; } QEMU_PACKED VFIODeviceStatePacket; +typedef struct VFIOMultifd { +} VFIOMultifd; + +static VFIOMultifd *vfio_multifd_new(void) +{ + VFIOMultifd *multifd = g_new(VFIOMultifd, 1); + + return multifd; +} + +static void vfio_multifd_free(VFIOMultifd *multifd) +{ + g_free(multifd); +} + +void vfio_multifd_cleanup(VFIODevice *vbasedev) +{ + VFIOMigration *migration = vbasedev->migration; + + g_clear_pointer(&migration->multifd, vfio_multifd_free); +} + bool vfio_multifd_transfer_supported(void) { return multifd_device_state_supported() && migrate_send_switchover_start(); } + +bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev) +{ + return false; +} + +bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp) +{ + VFIOMigration *migration = vbasedev->migration; + + if (!vfio_multifd_transfer_enabled(vbasedev)) { + /* Nothing further to check or do */ + return true; + } + + if (alloc_multifd) { + assert(!migration->multifd); + migration->multifd = vfio_multifd_new(); + } + + return true; +} diff --git a/hw/vfio/migration-multifd.h b/hw/vfio/migration-multifd.h index 1b60d5f67a1c..2a7a76164f29 100644 --- a/hw/vfio/migration-multifd.h +++ b/hw/vfio/migration-multifd.h @@ -14,6 +14,10 @@ #include "hw/vfio/vfio-common.h" +bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp); +void vfio_multifd_cleanup(VFIODevice *vbasedev); + bool vfio_multifd_transfer_supported(void); +bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev); #endif diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h index bf5d52087129..40382390692d 100644 --- a/include/hw/vfio/vfio-common.h +++ b/include/hw/vfio/vfio-common.h @@ -78,6 +78,8 @@ typedef struct VFIORegion { uint8_t nr; /* cache the region number for debug */ } VFIORegion; +typedef struct VFIOMultifd VFIOMultifd; + typedef struct VFIOMigration { struct VFIODevice *vbasedev; VMChangeStateEntry *vm_state; @@ -89,6 +91,7 @@ typedef struct VFIOMigration { uint64_t mig_flags; uint64_t precopy_init_size; uint64_t precopy_dirty_size; + VFIOMultifd *multifd; bool initial_data_sent; bool event_save_iterate_started; From 6bcffb1cad5b2b45152c0faa1133c96d3f129914 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:52 +0100 Subject: [PATCH 0533/1179] vfio/migration: Setup and cleanup multifd transfer in these general methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire VFIO multifd transfer specific setup and cleanup functions into general VFIO load/save setup and cleanup methods. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/b1f864a65fafd4fdab1f89230df52e46ae41f2ac.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index dc1fe4e717a4..3c8286ae6230 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -453,6 +453,10 @@ static int vfio_save_setup(QEMUFile *f, void *opaque, Error **errp) uint64_t stop_copy_size = VFIO_MIG_DEFAULT_DATA_BUFFER_SIZE; int ret; + if (!vfio_multifd_setup(vbasedev, false, errp)) { + return -EINVAL; + } + qemu_put_be64(f, VFIO_MIG_FLAG_DEV_SETUP_STATE); vfio_query_stop_copy_size(vbasedev, &stop_copy_size); @@ -509,6 +513,9 @@ static void vfio_save_cleanup(void *opaque) Error *local_err = NULL; int ret; + /* Currently a NOP, done for symmetry with load_cleanup() */ + vfio_multifd_cleanup(vbasedev); + /* * Changing device state from STOP_COPY to STOP can take time. Do it here, * after migration has completed, so it won't increase downtime. @@ -674,15 +681,28 @@ static void vfio_save_state(QEMUFile *f, void *opaque) static int vfio_load_setup(QEMUFile *f, void *opaque, Error **errp) { VFIODevice *vbasedev = opaque; + VFIOMigration *migration = vbasedev->migration; + int ret; - return vfio_migration_set_state(vbasedev, VFIO_DEVICE_STATE_RESUMING, - vbasedev->migration->device_state, errp); + if (!vfio_multifd_setup(vbasedev, true, errp)) { + return -EINVAL; + } + + ret = vfio_migration_set_state(vbasedev, VFIO_DEVICE_STATE_RESUMING, + migration->device_state, errp); + if (ret) { + return ret; + } + + return 0; } static int vfio_load_cleanup(void *opaque) { VFIODevice *vbasedev = opaque; + vfio_multifd_cleanup(vbasedev); + vfio_migration_cleanup(vbasedev); trace_vfio_load_cleanup(vbasedev->name); From 3228d311ab1882f75b04d080d33a71fc7a0bcac5 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:53 +0100 Subject: [PATCH 0534/1179] vfio/migration: Multifd device state transfer support - received buffers queuing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The multifd received data needs to be reassembled since device state packets sent via different multifd channels can arrive out-of-order. Therefore, each VFIO device state packet carries a header indicating its position in the stream. The raw device state data is saved into a VFIOStateBuffer for later in-order loading into the device. The last such VFIO device state packet should have VFIO_DEVICE_STATE_CONFIG_STATE flag set and carry the device config state. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/e3bff515a8d61c582b94b409eb12a45b1a143a69.1741124640.git.maciej.szmigiero@oracle.com [ clg: - Reordered savevm_vfio_handlers - Added load_state_buffer documentation ] Signed-off-by: Cédric Le Goater --- docs/devel/migration/vfio.rst | 7 ++ hw/vfio/migration-multifd.c | 163 ++++++++++++++++++++++++++++++++++ hw/vfio/migration-multifd.h | 3 + hw/vfio/migration.c | 4 + hw/vfio/trace-events | 1 + 5 files changed, 178 insertions(+) diff --git a/docs/devel/migration/vfio.rst b/docs/devel/migration/vfio.rst index c49482eab66d..8b1f28890a0b 100644 --- a/docs/devel/migration/vfio.rst +++ b/docs/devel/migration/vfio.rst @@ -76,6 +76,10 @@ VFIO implements the device hooks for the iterative approach as follows: * A ``load_state`` function that loads the config section and the data sections that are generated by the save functions above. +* A ``load_state_buffer`` function that loads the device state and the device + config that arrived via multifd channels. + It's used only in the multifd mode. + * ``cleanup`` functions for both save and load that perform any migration related cleanup. @@ -194,6 +198,9 @@ Live migration resume path (RESTORE_VM, _ACTIVE, _STOP) | For each device, .load_state() is called for that device section data + transmitted via the main migration channel. + For data transmitted via multifd channels .load_state_buffer() is called + instead. (RESTORE_VM, _ACTIVE, _RESUMING) | At the end, .load_cleanup() is called for each device and vCPUs are started diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index 091dc43210ad..79df11b7baa9 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -32,18 +32,181 @@ typedef struct VFIODeviceStatePacket { uint8_t data[0]; } QEMU_PACKED VFIODeviceStatePacket; +/* type safety */ +typedef struct VFIOStateBuffers { + GArray *array; +} VFIOStateBuffers; + +typedef struct VFIOStateBuffer { + bool is_present; + char *data; + size_t len; +} VFIOStateBuffer; + typedef struct VFIOMultifd { + VFIOStateBuffers load_bufs; + QemuCond load_bufs_buffer_ready_cond; + QemuMutex load_bufs_mutex; /* Lock order: this lock -> BQL */ + uint32_t load_buf_idx; + uint32_t load_buf_idx_last; } VFIOMultifd; +static void vfio_state_buffer_clear(gpointer data) +{ + VFIOStateBuffer *lb = data; + + if (!lb->is_present) { + return; + } + + g_clear_pointer(&lb->data, g_free); + lb->is_present = false; +} + +static void vfio_state_buffers_init(VFIOStateBuffers *bufs) +{ + bufs->array = g_array_new(FALSE, TRUE, sizeof(VFIOStateBuffer)); + g_array_set_clear_func(bufs->array, vfio_state_buffer_clear); +} + +static void vfio_state_buffers_destroy(VFIOStateBuffers *bufs) +{ + g_clear_pointer(&bufs->array, g_array_unref); +} + +static void vfio_state_buffers_assert_init(VFIOStateBuffers *bufs) +{ + assert(bufs->array); +} + +static unsigned int vfio_state_buffers_size_get(VFIOStateBuffers *bufs) +{ + return bufs->array->len; +} + +static void vfio_state_buffers_size_set(VFIOStateBuffers *bufs, + unsigned int size) +{ + g_array_set_size(bufs->array, size); +} + +static VFIOStateBuffer *vfio_state_buffers_at(VFIOStateBuffers *bufs, + unsigned int idx) +{ + return &g_array_index(bufs->array, VFIOStateBuffer, idx); +} + +/* called with load_bufs_mutex locked */ +static bool vfio_load_state_buffer_insert(VFIODevice *vbasedev, + VFIODeviceStatePacket *packet, + size_t packet_total_size, + Error **errp) +{ + VFIOMigration *migration = vbasedev->migration; + VFIOMultifd *multifd = migration->multifd; + VFIOStateBuffer *lb; + + vfio_state_buffers_assert_init(&multifd->load_bufs); + if (packet->idx >= vfio_state_buffers_size_get(&multifd->load_bufs)) { + vfio_state_buffers_size_set(&multifd->load_bufs, packet->idx + 1); + } + + lb = vfio_state_buffers_at(&multifd->load_bufs, packet->idx); + if (lb->is_present) { + error_setg(errp, "%s: state buffer %" PRIu32 " already filled", + vbasedev->name, packet->idx); + return false; + } + + assert(packet->idx >= multifd->load_buf_idx); + + lb->data = g_memdup2(&packet->data, packet_total_size - sizeof(*packet)); + lb->len = packet_total_size - sizeof(*packet); + lb->is_present = true; + + return true; +} + +bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, + Error **errp) +{ + VFIODevice *vbasedev = opaque; + VFIOMigration *migration = vbasedev->migration; + VFIOMultifd *multifd = migration->multifd; + VFIODeviceStatePacket *packet = (VFIODeviceStatePacket *)data; + + if (!vfio_multifd_transfer_enabled(vbasedev)) { + error_setg(errp, + "%s: got device state packet but not doing multifd transfer", + vbasedev->name); + return false; + } + + assert(multifd); + + if (data_size < sizeof(*packet)) { + error_setg(errp, "%s: packet too short at %zu (min is %zu)", + vbasedev->name, data_size, sizeof(*packet)); + return false; + } + + if (packet->version != VFIO_DEVICE_STATE_PACKET_VER_CURRENT) { + error_setg(errp, "%s: packet has unknown version %" PRIu32, + vbasedev->name, packet->version); + return false; + } + + if (packet->idx == UINT32_MAX) { + error_setg(errp, "%s: packet index is invalid", vbasedev->name); + return false; + } + + trace_vfio_load_state_device_buffer_incoming(vbasedev->name, packet->idx); + + /* + * Holding BQL here would violate the lock order and can cause + * a deadlock once we attempt to lock load_bufs_mutex below. + */ + assert(!bql_locked()); + + WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) { + /* config state packet should be the last one in the stream */ + if (packet->flags & VFIO_DEVICE_STATE_CONFIG_STATE) { + multifd->load_buf_idx_last = packet->idx; + } + + if (!vfio_load_state_buffer_insert(vbasedev, packet, data_size, + errp)) { + return false; + } + + qemu_cond_signal(&multifd->load_bufs_buffer_ready_cond); + } + + return true; +} + static VFIOMultifd *vfio_multifd_new(void) { VFIOMultifd *multifd = g_new(VFIOMultifd, 1); + vfio_state_buffers_init(&multifd->load_bufs); + + qemu_mutex_init(&multifd->load_bufs_mutex); + + multifd->load_buf_idx = 0; + multifd->load_buf_idx_last = UINT32_MAX; + qemu_cond_init(&multifd->load_bufs_buffer_ready_cond); + return multifd; } static void vfio_multifd_free(VFIOMultifd *multifd) { + vfio_state_buffers_destroy(&multifd->load_bufs); + qemu_cond_destroy(&multifd->load_bufs_buffer_ready_cond); + qemu_mutex_destroy(&multifd->load_bufs_mutex); + g_free(multifd); } diff --git a/hw/vfio/migration-multifd.h b/hw/vfio/migration-multifd.h index 2a7a76164f29..8c6320fcb484 100644 --- a/hw/vfio/migration-multifd.h +++ b/hw/vfio/migration-multifd.h @@ -20,4 +20,7 @@ void vfio_multifd_cleanup(VFIODevice *vbasedev); bool vfio_multifd_transfer_supported(void); bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev); +bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, + Error **errp); + #endif diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 3c8286ae6230..2cdb92356e0a 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -802,6 +802,10 @@ static const SaveVMHandlers savevm_vfio_handlers = { .load_cleanup = vfio_load_cleanup, .load_state = vfio_load_state, .switchover_ack_needed = vfio_switchover_ack_needed, + /* + * Multifd support + */ + .load_state_buffer = vfio_multifd_load_state_buffer, }; /* ---------------------------------------------------------------------- */ diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index a02c668f28a4..404ea079b25c 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -154,6 +154,7 @@ vfio_load_device_config_state_start(const char *name) " (%s)" vfio_load_device_config_state_end(const char *name) " (%s)" vfio_load_state(const char *name, uint64_t data) " (%s) data 0x%"PRIx64 vfio_load_state_device_data(const char *name, uint64_t data_size, int ret) " (%s) size %"PRIu64" ret %d" +vfio_load_state_device_buffer_incoming(const char *name, uint32_t idx) " (%s) idx %"PRIu32 vfio_migration_realize(const char *name) " (%s)" vfio_migration_set_device_state(const char *name, const char *state) " (%s) state %s" vfio_migration_set_state(const char *name, const char *new_state, const char *recover_state) " (%s) new state %s, recover state %s" From c59748c1ff924963a67af9efd7e1a1ee6f82d6d6 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:54 +0100 Subject: [PATCH 0535/1179] vfio/migration: Multifd device state transfer support - load thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a thread which loads the VFIO device state buffers that were received via multifd. Each VFIO device that has multifd device state transfer enabled has one such thread, which is created using migration core API qemu_loadvm_start_load_thread(). Since it's important to finish loading device state transferred via the main migration channel (via save_live_iterate SaveVMHandler) before starting loading the data asynchronously transferred via multifd the thread doing the actual loading of the multifd transferred data is only started from switchover_start SaveVMHandler. switchover_start handler is called when MIG_CMD_SWITCHOVER_START sub-command of QEMU_VM_COMMAND is received via the main migration channel. This sub-command is only sent after all save_live_iterate data have already been posted so it is safe to commence loading of the multifd-transferred device state upon receiving it - loading of save_live_iterate data happens synchronously in the main migration thread (much like the processing of MIG_CMD_SWITCHOVER_START) so by the time MIG_CMD_SWITCHOVER_START is processed all the proceeding data must have already been loaded. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/9abe612d775aaf42e31646796acd2363c723a57a.1741124640.git.maciej.szmigiero@oracle.com [ clg: - Reordered savevm_vfio_handlers - Added switchover_start documentation ] Signed-off-by: Cédric Le Goater --- docs/devel/migration/vfio.rst | 4 + hw/vfio/migration-multifd.c | 226 ++++++++++++++++++++++++++++++++++ hw/vfio/migration-multifd.h | 2 + hw/vfio/migration.c | 12 ++ hw/vfio/trace-events | 7 ++ 5 files changed, 251 insertions(+) diff --git a/docs/devel/migration/vfio.rst b/docs/devel/migration/vfio.rst index 8b1f28890a0b..d6cf60890c43 100644 --- a/docs/devel/migration/vfio.rst +++ b/docs/devel/migration/vfio.rst @@ -67,6 +67,10 @@ VFIO implements the device hooks for the iterative approach as follows: * A ``switchover_ack_needed`` function that checks if the VFIO device uses "switchover-ack" migration capability when this capability is enabled. +* A ``switchover_start`` function that in the multifd mode starts a thread that + reassembles the multifd received data and loads it in-order into the device. + In the non-multifd mode this function is a NOP. + * A ``save_state`` function to save the device config space if it is present. * A ``save_live_complete_precopy`` function that sets the VFIO device in diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index 79df11b7baa9..2eef27604e4f 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -44,8 +44,12 @@ typedef struct VFIOStateBuffer { } VFIOStateBuffer; typedef struct VFIOMultifd { + bool load_bufs_thread_running; + bool load_bufs_thread_want_exit; + VFIOStateBuffers load_bufs; QemuCond load_bufs_buffer_ready_cond; + QemuCond load_bufs_thread_finished_cond; QemuMutex load_bufs_mutex; /* Lock order: this lock -> BQL */ uint32_t load_buf_idx; uint32_t load_buf_idx_last; @@ -186,6 +190,178 @@ bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, return true; } +static bool vfio_load_bufs_thread_load_config(VFIODevice *vbasedev, + Error **errp) +{ + error_setg(errp, "not yet there"); + return false; +} + +static VFIOStateBuffer *vfio_load_state_buffer_get(VFIOMultifd *multifd) +{ + VFIOStateBuffer *lb; + unsigned int bufs_len; + + bufs_len = vfio_state_buffers_size_get(&multifd->load_bufs); + if (multifd->load_buf_idx >= bufs_len) { + assert(multifd->load_buf_idx == bufs_len); + return NULL; + } + + lb = vfio_state_buffers_at(&multifd->load_bufs, + multifd->load_buf_idx); + if (!lb->is_present) { + return NULL; + } + + return lb; +} + +static bool vfio_load_state_buffer_write(VFIODevice *vbasedev, + VFIOStateBuffer *lb, + Error **errp) +{ + VFIOMigration *migration = vbasedev->migration; + VFIOMultifd *multifd = migration->multifd; + g_autofree char *buf = NULL; + char *buf_cur; + size_t buf_len; + + if (!lb->len) { + return true; + } + + trace_vfio_load_state_device_buffer_load_start(vbasedev->name, + multifd->load_buf_idx); + + /* lb might become re-allocated when we drop the lock */ + buf = g_steal_pointer(&lb->data); + buf_cur = buf; + buf_len = lb->len; + while (buf_len > 0) { + ssize_t wr_ret; + int errno_save; + + /* + * Loading data to the device takes a while, + * drop the lock during this process. + */ + qemu_mutex_unlock(&multifd->load_bufs_mutex); + wr_ret = write(migration->data_fd, buf_cur, buf_len); + errno_save = errno; + qemu_mutex_lock(&multifd->load_bufs_mutex); + + if (wr_ret < 0) { + error_setg(errp, + "%s: writing state buffer %" PRIu32 " failed: %d", + vbasedev->name, multifd->load_buf_idx, errno_save); + return false; + } + + assert(wr_ret <= buf_len); + buf_len -= wr_ret; + buf_cur += wr_ret; + } + + trace_vfio_load_state_device_buffer_load_end(vbasedev->name, + multifd->load_buf_idx); + + return true; +} + +static bool vfio_load_bufs_thread_want_exit(VFIOMultifd *multifd, + bool *should_quit) +{ + return multifd->load_bufs_thread_want_exit || qatomic_read(should_quit); +} + +/* + * This thread is spawned by vfio_multifd_switchover_start() which gets + * called upon encountering the switchover point marker in main migration + * stream. + * + * It exits after either: + * * completing loading the remaining device state and device config, OR: + * * encountering some error while doing the above, OR: + * * being forcefully aborted by the migration core by it setting should_quit + * or by vfio_load_cleanup_load_bufs_thread() setting + * multifd->load_bufs_thread_want_exit. + */ +static bool vfio_load_bufs_thread(void *opaque, bool *should_quit, Error **errp) +{ + VFIODevice *vbasedev = opaque; + VFIOMigration *migration = vbasedev->migration; + VFIOMultifd *multifd = migration->multifd; + bool ret = false; + + trace_vfio_load_bufs_thread_start(vbasedev->name); + + assert(multifd); + QEMU_LOCK_GUARD(&multifd->load_bufs_mutex); + + assert(multifd->load_bufs_thread_running); + + while (true) { + VFIOStateBuffer *lb; + + /* + * Always check cancellation first after the buffer_ready wait below in + * case that cond was signalled by vfio_load_cleanup_load_bufs_thread(). + */ + if (vfio_load_bufs_thread_want_exit(multifd, should_quit)) { + error_setg(errp, "operation cancelled"); + goto thread_exit; + } + + assert(multifd->load_buf_idx <= multifd->load_buf_idx_last); + + lb = vfio_load_state_buffer_get(multifd); + if (!lb) { + trace_vfio_load_state_device_buffer_starved(vbasedev->name, + multifd->load_buf_idx); + qemu_cond_wait(&multifd->load_bufs_buffer_ready_cond, + &multifd->load_bufs_mutex); + continue; + } + + if (multifd->load_buf_idx == multifd->load_buf_idx_last) { + break; + } + + if (multifd->load_buf_idx == 0) { + trace_vfio_load_state_device_buffer_start(vbasedev->name); + } + + if (!vfio_load_state_buffer_write(vbasedev, lb, errp)) { + goto thread_exit; + } + + if (multifd->load_buf_idx == multifd->load_buf_idx_last - 1) { + trace_vfio_load_state_device_buffer_end(vbasedev->name); + } + + multifd->load_buf_idx++; + } + + if (!vfio_load_bufs_thread_load_config(vbasedev, errp)) { + goto thread_exit; + } + + ret = true; + +thread_exit: + /* + * Notify possibly waiting vfio_load_cleanup_load_bufs_thread() that + * this thread is exiting. + */ + multifd->load_bufs_thread_running = false; + qemu_cond_signal(&multifd->load_bufs_thread_finished_cond); + + trace_vfio_load_bufs_thread_end(vbasedev->name); + + return ret; +} + static VFIOMultifd *vfio_multifd_new(void) { VFIOMultifd *multifd = g_new(VFIOMultifd, 1); @@ -198,11 +374,41 @@ static VFIOMultifd *vfio_multifd_new(void) multifd->load_buf_idx_last = UINT32_MAX; qemu_cond_init(&multifd->load_bufs_buffer_ready_cond); + multifd->load_bufs_thread_running = false; + multifd->load_bufs_thread_want_exit = false; + qemu_cond_init(&multifd->load_bufs_thread_finished_cond); + return multifd; } +/* + * Terminates vfio_load_bufs_thread by setting + * multifd->load_bufs_thread_want_exit and signalling all the conditions + * the thread could be blocked on. + * + * Waits for the thread to signal that it had finished. + */ +static void vfio_load_cleanup_load_bufs_thread(VFIOMultifd *multifd) +{ + /* The lock order is load_bufs_mutex -> BQL so unlock BQL here first */ + bql_unlock(); + WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) { + while (multifd->load_bufs_thread_running) { + multifd->load_bufs_thread_want_exit = true; + + qemu_cond_signal(&multifd->load_bufs_buffer_ready_cond); + qemu_cond_wait(&multifd->load_bufs_thread_finished_cond, + &multifd->load_bufs_mutex); + } + } + bql_lock(); +} + static void vfio_multifd_free(VFIOMultifd *multifd) { + vfio_load_cleanup_load_bufs_thread(multifd); + + qemu_cond_destroy(&multifd->load_bufs_thread_finished_cond); vfio_state_buffers_destroy(&multifd->load_bufs); qemu_cond_destroy(&multifd->load_bufs_buffer_ready_cond); qemu_mutex_destroy(&multifd->load_bufs_mutex); @@ -244,3 +450,23 @@ bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp) return true; } + +int vfio_multifd_switchover_start(VFIODevice *vbasedev) +{ + VFIOMigration *migration = vbasedev->migration; + VFIOMultifd *multifd = migration->multifd; + + assert(multifd); + + /* The lock order is load_bufs_mutex -> BQL so unlock BQL here first */ + bql_unlock(); + WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) { + assert(!multifd->load_bufs_thread_running); + multifd->load_bufs_thread_running = true; + } + bql_lock(); + + qemu_loadvm_start_load_thread(vfio_load_bufs_thread, vbasedev); + + return 0; +} diff --git a/hw/vfio/migration-multifd.h b/hw/vfio/migration-multifd.h index 8c6320fcb484..f0d28fcef2ea 100644 --- a/hw/vfio/migration-multifd.h +++ b/hw/vfio/migration-multifd.h @@ -23,4 +23,6 @@ bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev); bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, Error **errp); +int vfio_multifd_switchover_start(VFIODevice *vbasedev); + #endif diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 2cdb92356e0a..815ad8fc93c7 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -788,6 +788,17 @@ static bool vfio_switchover_ack_needed(void *opaque) return vfio_precopy_supported(vbasedev); } +static int vfio_switchover_start(void *opaque) +{ + VFIODevice *vbasedev = opaque; + + if (vfio_multifd_transfer_enabled(vbasedev)) { + return vfio_multifd_switchover_start(vbasedev); + } + + return 0; +} + static const SaveVMHandlers savevm_vfio_handlers = { .save_prepare = vfio_save_prepare, .save_setup = vfio_save_setup, @@ -806,6 +817,7 @@ static const SaveVMHandlers savevm_vfio_handlers = { * Multifd support */ .load_state_buffer = vfio_multifd_load_state_buffer, + .switchover_start = vfio_switchover_start, }; /* ---------------------------------------------------------------------- */ diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index 404ea079b25c..d6b7e34faa39 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -149,12 +149,19 @@ vfio_display_edid_update(uint32_t prefx, uint32_t prefy) "%ux%u" vfio_display_edid_write_error(void) "" # migration.c +vfio_load_bufs_thread_start(const char *name) " (%s)" +vfio_load_bufs_thread_end(const char *name) " (%s)" vfio_load_cleanup(const char *name) " (%s)" vfio_load_device_config_state_start(const char *name) " (%s)" vfio_load_device_config_state_end(const char *name) " (%s)" vfio_load_state(const char *name, uint64_t data) " (%s) data 0x%"PRIx64 vfio_load_state_device_data(const char *name, uint64_t data_size, int ret) " (%s) size %"PRIu64" ret %d" vfio_load_state_device_buffer_incoming(const char *name, uint32_t idx) " (%s) idx %"PRIu32 +vfio_load_state_device_buffer_start(const char *name) " (%s)" +vfio_load_state_device_buffer_starved(const char *name, uint32_t idx) " (%s) idx %"PRIu32 +vfio_load_state_device_buffer_load_start(const char *name, uint32_t idx) " (%s) idx %"PRIu32 +vfio_load_state_device_buffer_load_end(const char *name, uint32_t idx) " (%s) idx %"PRIu32 +vfio_load_state_device_buffer_end(const char *name) " (%s)" vfio_migration_realize(const char *name) " (%s)" vfio_migration_set_device_state(const char *name, const char *state) " (%s) state %s" vfio_migration_set_state(const char *name, const char *new_state, const char *recover_state) " (%s) new state %s, recover state %s" From fda70ed83d54abad6bf2d437a7c05204b0fad228 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:55 +0100 Subject: [PATCH 0536/1179] migration/qemu-file: Define g_autoptr() cleanup function for QEMUFile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automatic memory management helps avoid memory safety issues. Reviewed-by: Fabiano Rosas Reviewed-by: Peter Xu Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/2fd01d773a783d572dcf538a064a98cc09e75c12.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- migration/qemu-file.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migration/qemu-file.h b/migration/qemu-file.h index 3e47a20621a7..f5b9f430e04b 100644 --- a/migration/qemu-file.h +++ b/migration/qemu-file.h @@ -33,6 +33,8 @@ QEMUFile *qemu_file_new_input(QIOChannel *ioc); QEMUFile *qemu_file_new_output(QIOChannel *ioc); int qemu_fclose(QEMUFile *f); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(QEMUFile, qemu_fclose) + /* * qemu_file_transferred: * From b659c07c534490369ca0954f0116b05c4a063065 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:56 +0100 Subject: [PATCH 0537/1179] vfio/migration: Multifd device state transfer support - config loading support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Load device config received via multifd using the existing machinery behind vfio_load_device_config_state(). Also, make sure to process the relevant main migration channel flags. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/5dbd3f3703ec1097da2cf82a7262233452146fee.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration-multifd.c | 49 +++++++++++++++++++++++++++++++++-- hw/vfio/migration.c | 9 ++++++- include/hw/vfio/vfio-common.h | 2 ++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index 2eef27604e4f..1d81233c755f 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -17,6 +17,7 @@ #include "qemu/lockable.h" #include "qemu/main-loop.h" #include "qemu/thread.h" +#include "io/channel-buffer.h" #include "migration/qemu-file.h" #include "migration-multifd.h" #include "trace.h" @@ -193,8 +194,52 @@ bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, static bool vfio_load_bufs_thread_load_config(VFIODevice *vbasedev, Error **errp) { - error_setg(errp, "not yet there"); - return false; + VFIOMigration *migration = vbasedev->migration; + VFIOMultifd *multifd = migration->multifd; + VFIOStateBuffer *lb; + g_autoptr(QIOChannelBuffer) bioc = NULL; + g_autoptr(QEMUFile) f_out = NULL, f_in = NULL; + uint64_t mig_header; + int ret; + + assert(multifd->load_buf_idx == multifd->load_buf_idx_last); + lb = vfio_state_buffers_at(&multifd->load_bufs, multifd->load_buf_idx); + assert(lb->is_present); + + bioc = qio_channel_buffer_new(lb->len); + qio_channel_set_name(QIO_CHANNEL(bioc), "vfio-device-config-load"); + + f_out = qemu_file_new_output(QIO_CHANNEL(bioc)); + qemu_put_buffer(f_out, (uint8_t *)lb->data, lb->len); + + ret = qemu_fflush(f_out); + if (ret) { + error_setg(errp, "%s: load config state flush failed: %d", + vbasedev->name, ret); + return false; + } + + qio_channel_io_seek(QIO_CHANNEL(bioc), 0, 0, NULL); + f_in = qemu_file_new_input(QIO_CHANNEL(bioc)); + + mig_header = qemu_get_be64(f_in); + if (mig_header != VFIO_MIG_FLAG_DEV_CONFIG_STATE) { + error_setg(errp, "%s: expected FLAG_DEV_CONFIG_STATE but got %" PRIx64, + vbasedev->name, mig_header); + return false; + } + + bql_lock(); + ret = vfio_load_device_config_state(f_in, vbasedev); + bql_unlock(); + + if (ret < 0) { + error_setg(errp, "%s: vfio_load_device_config_state() failed: %d", + vbasedev->name, ret); + return false; + } + + return true; } static VFIOStateBuffer *vfio_load_state_buffer_get(VFIOMultifd *multifd) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 815ad8fc93c7..2ca3fa08d486 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -264,7 +264,7 @@ static int vfio_save_device_config_state(QEMUFile *f, void *opaque, return ret; } -static int vfio_load_device_config_state(QEMUFile *f, void *opaque) +int vfio_load_device_config_state(QEMUFile *f, void *opaque) { VFIODevice *vbasedev = opaque; uint64_t data; @@ -723,6 +723,13 @@ static int vfio_load_state(QEMUFile *f, void *opaque, int version_id) switch (data) { case VFIO_MIG_FLAG_DEV_CONFIG_STATE: { + if (vfio_multifd_transfer_enabled(vbasedev)) { + error_report("%s: got DEV_CONFIG_STATE in main migration " + "channel but doing multifd transfer", + vbasedev->name); + return -EINVAL; + } + return vfio_load_device_config_state(f, opaque); } case VFIO_MIG_FLAG_DEV_SETUP_STATE: diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h index 40382390692d..9d72ac1eae8a 100644 --- a/include/hw/vfio/vfio-common.h +++ b/include/hw/vfio/vfio-common.h @@ -298,6 +298,8 @@ void vfio_mig_add_bytes_transferred(unsigned long val); bool vfio_device_state_is_running(VFIODevice *vbasedev); bool vfio_device_state_is_precopy(VFIODevice *vbasedev); +int vfio_load_device_config_state(QEMUFile *f, void *opaque); + #ifdef CONFIG_LINUX int vfio_get_region_info(VFIODevice *vbasedev, int index, struct vfio_region_info **info); From 6d644baef20303fa4b2b342f556e26c2262b439f Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:57 +0100 Subject: [PATCH 0538/1179] vfio/migration: Multifd device state transfer support - send side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the multifd device state transfer via additional per-device thread inside save_live_complete_precopy_thread handler. Switch between doing the data transfer in the new handler and doing it in the old save_state handler depending if VFIO multifd transfer is enabled or not. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/4d727e2e0435e0022d50004e474077632830e08d.1741124640.git.maciej.szmigiero@oracle.com [ clg: - Reordered savevm_vfio_handlers - Updated save_live_complete_precopy* documentation ] Signed-off-by: Cédric Le Goater --- docs/devel/migration/vfio.rst | 19 ++++- hw/vfio/migration-multifd.c | 142 ++++++++++++++++++++++++++++++++++ hw/vfio/migration-multifd.h | 6 ++ hw/vfio/migration.c | 22 ++++-- hw/vfio/trace-events | 2 + include/hw/vfio/vfio-common.h | 6 ++ 6 files changed, 189 insertions(+), 8 deletions(-) diff --git a/docs/devel/migration/vfio.rst b/docs/devel/migration/vfio.rst index d6cf60890c43..a803a09bc156 100644 --- a/docs/devel/migration/vfio.rst +++ b/docs/devel/migration/vfio.rst @@ -71,11 +71,23 @@ VFIO implements the device hooks for the iterative approach as follows: reassembles the multifd received data and loads it in-order into the device. In the non-multifd mode this function is a NOP. -* A ``save_state`` function to save the device config space if it is present. +* A ``save_state`` function to save the device config space if it is present + in the non-multifd mode. + In the multifd mode it just emits either a dummy EOS marker. * A ``save_live_complete_precopy`` function that sets the VFIO device in _STOP_COPY state and iteratively copies the data for the VFIO device until the vendor driver indicates that no data remains. + In the multifd mode it just emits a dummy EOS marker. + +* A ``save_live_complete_precopy_thread`` function that in the multifd mode + provides thread handler performing multifd device state transfer. + It sets the VFIO device to _STOP_COPY state, iteratively reads the data + from the VFIO device and queues it for multifd transmission until the vendor + driver indicates that no data remains. + After that, it saves the device config space and queues it for multifd + transfer too. + In the non-multifd mode this thread is a NOP. * A ``load_state`` function that loads the config section and the data sections that are generated by the save functions above. @@ -184,8 +196,11 @@ Live migration save path Then the VFIO device is put in _STOP_COPY state (FINISH_MIGRATE, _ACTIVE, _STOP_COPY) .save_live_complete_precopy() is called for each active device - For the VFIO device, iterate in .save_live_complete_precopy() until + For the VFIO device: in the non-multifd mode iterate in + .save_live_complete_precopy() until pending data is 0 + In the multifd mode this iteration is done in + .save_live_complete_precopy_thread() instead. | (POSTMIGRATE, _COMPLETED, _STOP_COPY) Migraton thread schedules cleanup bottom half and exits diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index 1d81233c755f..bfb9a72fa450 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -496,6 +496,148 @@ bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp) return true; } +void vfio_multifd_emit_dummy_eos(VFIODevice *vbasedev, QEMUFile *f) +{ + assert(vfio_multifd_transfer_enabled(vbasedev)); + + /* + * Emit dummy NOP data on the main migration channel since the actual + * device state transfer is done via multifd channels. + */ + qemu_put_be64(f, VFIO_MIG_FLAG_END_OF_STATE); +} + +static bool +vfio_save_complete_precopy_thread_config_state(VFIODevice *vbasedev, + char *idstr, + uint32_t instance_id, + uint32_t idx, + Error **errp) +{ + g_autoptr(QIOChannelBuffer) bioc = NULL; + g_autoptr(QEMUFile) f = NULL; + int ret; + g_autofree VFIODeviceStatePacket *packet = NULL; + size_t packet_len; + + bioc = qio_channel_buffer_new(0); + qio_channel_set_name(QIO_CHANNEL(bioc), "vfio-device-config-save"); + + f = qemu_file_new_output(QIO_CHANNEL(bioc)); + + if (vfio_save_device_config_state(f, vbasedev, errp)) { + return false; + } + + ret = qemu_fflush(f); + if (ret) { + error_setg(errp, "%s: save config state flush failed: %d", + vbasedev->name, ret); + return false; + } + + packet_len = sizeof(*packet) + bioc->usage; + packet = g_malloc0(packet_len); + packet->version = VFIO_DEVICE_STATE_PACKET_VER_CURRENT; + packet->idx = idx; + packet->flags = VFIO_DEVICE_STATE_CONFIG_STATE; + memcpy(&packet->data, bioc->data, bioc->usage); + + if (!multifd_queue_device_state(idstr, instance_id, + (char *)packet, packet_len)) { + error_setg(errp, "%s: multifd config data queuing failed", + vbasedev->name); + return false; + } + + vfio_mig_add_bytes_transferred(packet_len); + + return true; +} + +/* + * This thread is spawned by the migration core directly via + * .save_live_complete_precopy_thread SaveVMHandler. + * + * It exits after either: + * * completing saving the remaining device state and device config, OR: + * * encountering some error while doing the above, OR: + * * being forcefully aborted by the migration core by + * multifd_device_state_save_thread_should_exit() returning true. + */ +bool +vfio_multifd_save_complete_precopy_thread(SaveLiveCompletePrecopyThreadData *d, + Error **errp) +{ + VFIODevice *vbasedev = d->handler_opaque; + VFIOMigration *migration = vbasedev->migration; + bool ret = false; + g_autofree VFIODeviceStatePacket *packet = NULL; + uint32_t idx; + + if (!vfio_multifd_transfer_enabled(vbasedev)) { + /* Nothing to do, vfio_save_complete_precopy() does the transfer. */ + return true; + } + + trace_vfio_save_complete_precopy_thread_start(vbasedev->name, + d->idstr, d->instance_id); + + /* We reach here with device state STOP or STOP_COPY only */ + if (vfio_migration_set_state(vbasedev, VFIO_DEVICE_STATE_STOP_COPY, + VFIO_DEVICE_STATE_STOP, errp)) { + goto thread_exit; + } + + packet = g_malloc0(sizeof(*packet) + migration->data_buffer_size); + packet->version = VFIO_DEVICE_STATE_PACKET_VER_CURRENT; + + for (idx = 0; ; idx++) { + ssize_t data_size; + size_t packet_size; + + if (multifd_device_state_save_thread_should_exit()) { + error_setg(errp, "operation cancelled"); + goto thread_exit; + } + + data_size = read(migration->data_fd, &packet->data, + migration->data_buffer_size); + if (data_size < 0) { + error_setg(errp, "%s: reading state buffer %" PRIu32 " failed: %d", + vbasedev->name, idx, errno); + goto thread_exit; + } else if (data_size == 0) { + break; + } + + packet->idx = idx; + packet_size = sizeof(*packet) + data_size; + + if (!multifd_queue_device_state(d->idstr, d->instance_id, + (char *)packet, packet_size)) { + error_setg(errp, "%s: multifd data queuing failed", vbasedev->name); + goto thread_exit; + } + + vfio_mig_add_bytes_transferred(packet_size); + } + + if (!vfio_save_complete_precopy_thread_config_state(vbasedev, + d->idstr, + d->instance_id, + idx, errp)) { + goto thread_exit; + } + + ret = true; + +thread_exit: + trace_vfio_save_complete_precopy_thread_end(vbasedev->name, ret); + + return ret; +} + int vfio_multifd_switchover_start(VFIODevice *vbasedev) { VFIOMigration *migration = vbasedev->migration; diff --git a/hw/vfio/migration-multifd.h b/hw/vfio/migration-multifd.h index f0d28fcef2ea..a664051eb8ae 100644 --- a/hw/vfio/migration-multifd.h +++ b/hw/vfio/migration-multifd.h @@ -23,6 +23,12 @@ bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev); bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, Error **errp); +void vfio_multifd_emit_dummy_eos(VFIODevice *vbasedev, QEMUFile *f); + +bool +vfio_multifd_save_complete_precopy_thread(SaveLiveCompletePrecopyThreadData *d, + Error **errp); + int vfio_multifd_switchover_start(VFIODevice *vbasedev); #endif diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 2ca3fa08d486..416643ddd69a 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -120,10 +120,10 @@ static void vfio_migration_set_device_state(VFIODevice *vbasedev, vfio_migration_send_event(vbasedev); } -static int vfio_migration_set_state(VFIODevice *vbasedev, - enum vfio_device_mig_state new_state, - enum vfio_device_mig_state recover_state, - Error **errp) +int vfio_migration_set_state(VFIODevice *vbasedev, + enum vfio_device_mig_state new_state, + enum vfio_device_mig_state recover_state, + Error **errp) { VFIOMigration *migration = vbasedev->migration; uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature) + @@ -238,8 +238,7 @@ static int vfio_load_buffer(QEMUFile *f, VFIODevice *vbasedev, return ret; } -static int vfio_save_device_config_state(QEMUFile *f, void *opaque, - Error **errp) +int vfio_save_device_config_state(QEMUFile *f, void *opaque, Error **errp) { VFIODevice *vbasedev = opaque; int ret; @@ -638,6 +637,11 @@ static int vfio_save_complete_precopy(QEMUFile *f, void *opaque) int ret; Error *local_err = NULL; + if (vfio_multifd_transfer_enabled(vbasedev)) { + vfio_multifd_emit_dummy_eos(vbasedev, f); + return 0; + } + trace_vfio_save_complete_precopy_start(vbasedev->name); /* We reach here with device state STOP or STOP_COPY only */ @@ -669,6 +673,11 @@ static void vfio_save_state(QEMUFile *f, void *opaque) Error *local_err = NULL; int ret; + if (vfio_multifd_transfer_enabled(vbasedev)) { + vfio_multifd_emit_dummy_eos(vbasedev, f); + return; + } + ret = vfio_save_device_config_state(f, opaque, &local_err); if (ret) { error_prepend(&local_err, @@ -825,6 +834,7 @@ static const SaveVMHandlers savevm_vfio_handlers = { */ .load_state_buffer = vfio_multifd_load_state_buffer, .switchover_start = vfio_switchover_start, + .save_live_complete_precopy_thread = vfio_multifd_save_complete_precopy_thread, }; /* ---------------------------------------------------------------------- */ diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index d6b7e34faa39..9347e3a5f660 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -171,6 +171,8 @@ vfio_save_block_precopy_empty_hit(const char *name) " (%s)" vfio_save_cleanup(const char *name) " (%s)" vfio_save_complete_precopy(const char *name, int ret) " (%s) ret %d" vfio_save_complete_precopy_start(const char *name) " (%s)" +vfio_save_complete_precopy_thread_start(const char *name, const char *idstr, uint32_t instance_id) " (%s) idstr %s instance %"PRIu32 +vfio_save_complete_precopy_thread_end(const char *name, int ret) " (%s) ret %d" vfio_save_device_config_state(const char *name) " (%s)" vfio_save_iterate(const char *name, uint64_t precopy_init_size, uint64_t precopy_dirty_size) " (%s) precopy initial size %"PRIu64" precopy dirty size %"PRIu64 vfio_save_iterate_start(const char *name) " (%s)" diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h index 9d72ac1eae8a..961931d9f457 100644 --- a/include/hw/vfio/vfio-common.h +++ b/include/hw/vfio/vfio-common.h @@ -298,6 +298,7 @@ void vfio_mig_add_bytes_transferred(unsigned long val); bool vfio_device_state_is_running(VFIODevice *vbasedev); bool vfio_device_state_is_precopy(VFIODevice *vbasedev); +int vfio_save_device_config_state(QEMUFile *f, void *opaque, Error **errp); int vfio_load_device_config_state(QEMUFile *f, void *opaque); #ifdef CONFIG_LINUX @@ -314,6 +315,11 @@ struct vfio_info_cap_header * vfio_get_device_info_cap(struct vfio_device_info *info, uint16_t id); struct vfio_info_cap_header * vfio_get_cap(void *ptr, uint32_t cap_offset, uint16_t id); + +int vfio_migration_set_state(VFIODevice *vbasedev, + enum vfio_device_mig_state new_state, + enum vfio_device_mig_state recover_state, + Error **errp); #endif bool vfio_migration_realize(VFIODevice *vbasedev, Error **errp); From 623af41dd331d1a57a41bc3374e3d134adb33f4c Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:58 +0100 Subject: [PATCH 0539/1179] vfio/migration: Add x-migration-multifd-transfer VFIO property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This property allows configuring whether to transfer the particular device state via multifd channels when live migrating that device. It defaults to AUTO, which means that VFIO device state transfer via multifd channels is attempted in configurations that otherwise support it. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/d6dbb326e3d53c7104d62c96c9e3dd64e1c7b940.1741124640.git.maciej.szmigiero@oracle.com [ clg: Added documentation ] Signed-off-by: Cédric Le Goater --- docs/devel/migration/vfio.rst | 15 +++++++++++++++ hw/vfio/migration-multifd.c | 18 +++++++++++++++++- hw/vfio/pci.c | 7 +++++++ include/hw/vfio/vfio-common.h | 2 ++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/devel/migration/vfio.rst b/docs/devel/migration/vfio.rst index a803a09bc156..673e354754c8 100644 --- a/docs/devel/migration/vfio.rst +++ b/docs/devel/migration/vfio.rst @@ -232,3 +232,18 @@ Postcopy ======== Postcopy migration is currently not supported for VFIO devices. + +Multifd +======= + +Starting from QEMU version 10.0 there's a possibility to transfer VFIO device +_STOP_COPY state via multifd channels. This helps reduce downtime - especially +with multiple VFIO devices or with devices having a large migration state. +As an additional benefit, setting the VFIO device to _STOP_COPY state and +saving its config space is also parallelized (run in a separate thread) in +such migration mode. + +The multifd VFIO device state transfer is controlled by +"x-migration-multifd-transfer" VFIO device property. This property defaults to +AUTO, which means that VFIO device state transfer via multifd channels is +attempted in configurations that otherwise support it. diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index bfb9a72fa450..aacddc503bb8 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -476,18 +476,34 @@ bool vfio_multifd_transfer_supported(void) bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev) { - return false; + VFIOMigration *migration = vbasedev->migration; + + return migration->multifd_transfer; } bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp) { VFIOMigration *migration = vbasedev->migration; + if (vbasedev->migration_multifd_transfer == ON_OFF_AUTO_AUTO) { + migration->multifd_transfer = vfio_multifd_transfer_supported(); + } else { + migration->multifd_transfer = + vbasedev->migration_multifd_transfer == ON_OFF_AUTO_ON; + } + if (!vfio_multifd_transfer_enabled(vbasedev)) { /* Nothing further to check or do */ return true; } + if (!vfio_multifd_transfer_supported()) { + error_setg(errp, + "%s: Multifd device transfer requested but unsupported in the current config", + vbasedev->name); + return false; + } + if (alloc_multifd) { assert(!migration->multifd); migration->multifd = vfio_multifd_new(); diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index c1cee280ae4b..1bbf15cea326 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3381,6 +3381,9 @@ static const Property vfio_pci_dev_properties[] = { VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT, false), DEFINE_PROP_ON_OFF_AUTO("enable-migration", VFIOPCIDevice, vbasedev.enable_migration, ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO("x-migration-multifd-transfer", VFIOPCIDevice, + vbasedev.migration_multifd_transfer, + ON_OFF_AUTO_AUTO), DEFINE_PROP_BOOL("migration-events", VFIOPCIDevice, vbasedev.migration_events, false), DEFINE_PROP_BOOL("x-no-mmap", VFIOPCIDevice, vbasedev.no_mmap, false), @@ -3553,6 +3556,10 @@ static void vfio_pci_dev_class_init(ObjectClass *klass, void *data) "Skip config space check for Vendor Specific Capability. " "Setting to false will enforce strict checking of VSC content " "(DEBUG)"); + object_class_property_set_description(klass, /* 10.0 */ + "x-migration-multifd-transfer", + "Transfer this device state via " + "multifd channels when live migrating it"); } static const TypeInfo vfio_pci_dev_info = { diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h index 961931d9f457..04b123a6c929 100644 --- a/include/hw/vfio/vfio-common.h +++ b/include/hw/vfio/vfio-common.h @@ -91,6 +91,7 @@ typedef struct VFIOMigration { uint64_t mig_flags; uint64_t precopy_init_size; uint64_t precopy_dirty_size; + bool multifd_transfer; VFIOMultifd *multifd; bool initial_data_sent; @@ -153,6 +154,7 @@ typedef struct VFIODevice { bool no_mmap; bool ram_block_discard_allowed; OnOffAuto enable_migration; + OnOffAuto migration_multifd_transfer; bool migration_events; VFIODeviceOps *ops; unsigned int num_irqs; From 4c765ceaace4e7828e2790d8f4829f69989888de Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:03:59 +0100 Subject: [PATCH 0540/1179] vfio/migration: Make x-migration-multifd-transfer VFIO property mutable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DEFINE_PROP_ON_OFF_AUTO() property isn't runtime-mutable so using it would mean that the source VM would need to decide upfront at startup time whether it wants to do a multifd device state transfer at some point. Source VM can run for a long time before being migrated so it is desirable to have a fallback mechanism to the old way of transferring VFIO device state if it turns to be necessary. This brings this property to the same mutability level as ordinary migration parameters, which too can be adjusted at the run time. Signed-off-by: Maciej S. Szmigiero Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/f2f2d66bda477da3e6cb8c0311006cff36e8651d.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration-multifd.c | 4 ++++ hw/vfio/pci.c | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index aacddc503bb8..233724710b37 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -485,6 +485,10 @@ bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp) { VFIOMigration *migration = vbasedev->migration; + /* + * Make a copy of this setting at the start in case it is changed + * mid-migration. + */ if (vbasedev->migration_multifd_transfer == ON_OFF_AUTO_AUTO) { migration->multifd_transfer = vfio_multifd_transfer_supported(); } else { diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 1bbf15cea326..fdbc15885d44 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3357,6 +3357,8 @@ static void vfio_instance_init(Object *obj) pci_dev->cap_present |= QEMU_PCI_CAP_EXPRESS; } +static PropertyInfo vfio_pci_migration_multifd_transfer_prop; + static const Property vfio_pci_dev_properties[] = { DEFINE_PROP_PCI_HOST_DEVADDR("host", VFIOPCIDevice, host), DEFINE_PROP_UUID_NODEFAULT("vf-token", VFIOPCIDevice, vf_token), @@ -3381,9 +3383,10 @@ static const Property vfio_pci_dev_properties[] = { VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT, false), DEFINE_PROP_ON_OFF_AUTO("enable-migration", VFIOPCIDevice, vbasedev.enable_migration, ON_OFF_AUTO_AUTO), - DEFINE_PROP_ON_OFF_AUTO("x-migration-multifd-transfer", VFIOPCIDevice, - vbasedev.migration_multifd_transfer, - ON_OFF_AUTO_AUTO), + DEFINE_PROP("x-migration-multifd-transfer", VFIOPCIDevice, + vbasedev.migration_multifd_transfer, + vfio_pci_migration_multifd_transfer_prop, OnOffAuto, + .set_default = true, .defval.i = ON_OFF_AUTO_AUTO), DEFINE_PROP_BOOL("migration-events", VFIOPCIDevice, vbasedev.migration_events, false), DEFINE_PROP_BOOL("x-no-mmap", VFIOPCIDevice, vbasedev.no_mmap, false), @@ -3608,6 +3611,17 @@ static const TypeInfo vfio_pci_nohotplug_dev_info = { static void register_vfio_pci_dev_type(void) { + /* + * Ordinary ON_OFF_AUTO property isn't runtime-mutable, but source VM can + * run for a long time before being migrated so it is desirable to have a + * fallback mechanism to the old way of transferring VFIO device state if + * it turns to be necessary. + * The following makes this type of property have the same mutability level + * as ordinary migration parameters. + */ + vfio_pci_migration_multifd_transfer_prop = qdev_prop_on_off_auto; + vfio_pci_migration_multifd_transfer_prop.realized_set_allowed = true; + type_register_static(&vfio_pci_dev_info); type_register_static(&vfio_pci_nohotplug_dev_info); } From 59a67e70950bcc2002d3a8d22a17743e0f70da96 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Tue, 4 Mar 2025 23:04:00 +0100 Subject: [PATCH 0541/1179] hw/core/machine: Add compat for x-migration-multifd-transfer VFIO property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a hw_compat entry for recently added x-migration-multifd-transfer VFIO property. Reviewed-by: Cédric Le Goater Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/92c354f0457c152d1f267cc258c6967fff551cb1.1741124640.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/core/machine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/core/machine.c b/hw/core/machine.c index d1ddc3a3db59..f52a4f2273b2 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -45,6 +45,7 @@ GlobalProperty hw_compat_9_2[] = { { "virtio-mem-pci", "vectors", "0" }, { "migration", "multifd-clean-tls-termination", "false" }, { "migration", "send-switchover-start", "off"}, + { "vfio-pci", "x-migration-multifd-transfer", "off" }, }; const size_t hw_compat_9_2_len = G_N_ELEMENTS(hw_compat_9_2); From 6c5a1467f8d0a9e840c8aa193bc110cc76ee80e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 28 Feb 2025 10:27:32 +0000 Subject: [PATCH 0542/1179] tests/functional: remove unused 'bin_prefix' variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was copied over from avocado but has not been used in the new functional tests. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Reviewed-by: Richard Henderson Message-ID: <20250228102738.3064045-2-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/testcase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 869f3949fe9f..9d5611c4d7fc 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -192,7 +192,7 @@ def assets_available(self): return False return True - def setUp(self, bin_prefix): + def setUp(self): self.assertIsNotNone(self.qemu_bin, 'QEMU_TEST_QEMU_BINARY must be set') self.arch = self.qemu_bin.split('-')[-1] self.socketdir = None @@ -254,7 +254,7 @@ def main(): class QemuUserTest(QemuBaseTest): def setUp(self): - super().setUp('qemu-') + super().setUp() self._ldpath = [] def add_ldpath(self, ldpath): @@ -277,7 +277,7 @@ class QemuSystemTest(QemuBaseTest): def setUp(self): self._vms = {} - super().setUp('qemu-system-') + super().setUp() console_log = logging.getLogger('console') console_log.setLevel(logging.DEBUG) From 8188356a260ca0201c42d128d8fa86f40160b513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 28 Feb 2025 10:27:33 +0000 Subject: [PATCH 0543/1179] tests/functional: set 'qemu_bin' as an object level field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'qemu_bin' field is currently set on the class, despite being accessed as if it were an object instance field with 'self.qemu_bin'. This is no obvious need to have it as a class field, so move it into the object instance. Reviewed-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Reviewed-by: Richard Henderson Message-ID: <20250228102738.3064045-3-berrange@redhat.com> Signed-off-by: Thomas Huth --- docs/devel/testing/functional.rst | 2 +- tests/functional/qemu_test/testcase.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/devel/testing/functional.rst b/docs/devel/testing/functional.rst index ecc738922b7c..bcb5509512e7 100644 --- a/docs/devel/testing/functional.rst +++ b/docs/devel/testing/functional.rst @@ -173,7 +173,7 @@ QEMU binary selection ^^^^^^^^^^^^^^^^^^^^^ The QEMU binary used for the ``self.vm`` QEMUMachine instance will -primarily depend on the value of the ``qemu_bin`` class attribute. +primarily depend on the value of the ``qemu_bin`` instance attribute. If it is not explicitly set by the test code, its default value will be the result the QEMU_TEST_QEMU_BINARY environment variable. diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 9d5611c4d7fc..058bf270ecaf 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -33,7 +33,6 @@ class QemuBaseTest(unittest.TestCase): - qemu_bin = os.getenv('QEMU_TEST_QEMU_BINARY') arch = None workdir = None @@ -193,6 +192,7 @@ def assets_available(self): return True def setUp(self): + self.qemu_bin = os.getenv('QEMU_TEST_QEMU_BINARY') self.assertIsNotNone(self.qemu_bin, 'QEMU_TEST_QEMU_BINARY must be set') self.arch = self.qemu_bin.split('-')[-1] self.socketdir = None From 188f71929520940048ec5ba85ea30588e0566e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 28 Feb 2025 10:27:35 +0000 Subject: [PATCH 0544/1179] tests/functional: reduce tuxrun maxmem to work on 32-bit hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maxmem=4G is too large to address on 32-bit hosts, so reduce it to 2G since the tuxrun tests don't actually need such an elevated memory limit. Signed-off-by: Daniel P. Berrangé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-ID: <20250228102738.3064045-5-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/test_ppc64_tuxrun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/test_ppc64_tuxrun.py b/tests/functional/test_ppc64_tuxrun.py index 05c6162b5e2d..e8f79c676e5e 100755 --- a/tests/functional/test_ppc64_tuxrun.py +++ b/tests/functional/test_ppc64_tuxrun.py @@ -64,7 +64,7 @@ def ppc64_common_tuxrun(self, kernel_asset, rootfs_asset, prefix): ',"index":1,"id":"pci.1"}') self.vm.add_args('-device', '{"driver":"spapr-vscsi","id":"scsi1"' ',"reg":12288}') - self.vm.add_args('-m', '2G,slots=32,maxmem=4G', + self.vm.add_args('-m', '1G,slots=32,maxmem=2G', '-object', 'memory-backend-ram,id=ram1,size=1G', '-device', 'pc-dimm,id=dimm1,memdev=ram1') From 87c8b4fc3c1c89ec52540bfb74f9b0518f247323 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:07:55 +0100 Subject: [PATCH 0545/1179] docs/about/build-platforms: Correct minimum supported Python version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: ca056f4499c2 (Python: Drop support for Python 3.7) Signed-off-by: Markus Armbruster Message-ID: <20250227080757.3978333-2-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- docs/about/build-platforms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about/build-platforms.rst b/docs/about/build-platforms.rst index 482b09819c15..1552b1a70441 100644 --- a/docs/about/build-platforms.rst +++ b/docs/about/build-platforms.rst @@ -101,7 +101,7 @@ Python runtime option of the ``configure`` script to point QEMU to a supported version of the Python runtime. - As of QEMU |version|, the minimum supported version of Python is 3.7. + As of QEMU |version|, the minimum supported version of Python is 3.8. Python build dependencies Some of QEMU's build dependencies are written in Python. Usually these From 5fbc8126acaf07a0294f8f94f4c244c3c5b62d5d Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:07:56 +0100 Subject: [PATCH 0546/1179] qapi: Eliminate OrderedDict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We use OrderedDict to ensure dictionary order is insertion order. Plain dict does that since Python 3.6, but it wasn't guaranteed until 3.7. Since we have 3.7 now, replace OrderedDict by dict. Signed-off-by: Markus Armbruster Message-ID: <20250227080757.3978333-3-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- scripts/qapi/parser.py | 5 ++--- scripts/qapi/schema.py | 11 +++++------ tests/qapi-schema/test-qapi.py | 11 +---------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index adc85b5b3946..64f0bb824aed 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -14,7 +14,6 @@ # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. -from collections import OrderedDict import os import re from typing import ( @@ -154,7 +153,7 @@ def _parse(self) -> None: "value of 'include' must be a string") incl_fname = os.path.join(os.path.dirname(self._fname), include) - self._add_expr(OrderedDict({'include': incl_fname}), info) + self._add_expr({'include': incl_fname}, info) exprs_include = self._include(include, info, incl_fname, self._included) if exprs_include: @@ -355,7 +354,7 @@ def accept(self, skip_comment: bool = True) -> None: raise QAPIParseError(self, "stray '%s'" % match.group(0)) def get_members(self) -> Dict[str, object]: - expr: Dict[str, object] = OrderedDict() + expr: Dict[str, object] = {} if self.tok == '}': self.accept() return expr diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 7f70969c0919..cbe3b5aa91ec 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -19,7 +19,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections import OrderedDict import os import re from typing import ( @@ -557,7 +556,7 @@ def check(self, schema: QAPISchema) -> None: super().check(schema) assert self._checked and not self._check_complete - seen = OrderedDict() + seen = {} if self._base_name: self.base = schema.resolve_type(self._base_name, self.info, "'base'") @@ -1141,10 +1140,10 @@ def __init__(self, fname: str): self.docs = parser.docs self._entity_list: List[QAPISchemaEntity] = [] self._entity_dict: Dict[str, QAPISchemaDefinition] = {} - self._module_dict: Dict[str, QAPISchemaModule] = OrderedDict() + self._module_dict: Dict[str, QAPISchemaModule] = {} # NB, values in the dict will identify the first encountered # usage of a named feature only - self._feature_dict: Dict[str, QAPISchemaFeature] = OrderedDict() + self._feature_dict: Dict[str, QAPISchemaFeature] = {} # All schemas get the names defined in the QapiSpecialFeature enum. # Rely on dict iteration order matching insertion order so that @@ -1454,7 +1453,7 @@ def _def_command(self, expr: QAPIExpression) -> None: ifcond = QAPISchemaIfCond(expr.get('if')) info = expr.info features = self._make_features(expr.get('features'), info) - if isinstance(data, OrderedDict): + if isinstance(data, dict): data = self._make_implicit_object_type( name, info, ifcond, 'arg', self._make_members(data, info)) @@ -1473,7 +1472,7 @@ def _def_event(self, expr: QAPIExpression) -> None: ifcond = QAPISchemaIfCond(expr.get('if')) info = expr.info features = self._make_features(expr.get('features'), info) - if isinstance(data, OrderedDict): + if isinstance(data, dict): data = self._make_implicit_object_type( name, info, ifcond, 'arg', self._make_members(data, info)) diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 7e3f9f4aa1fe..8fe951c88034 100755 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -96,17 +96,8 @@ def _print_variants(variants): @staticmethod def _print_if(ifcond, indent=4): - # TODO Drop this hack after replacing OrderedDict by plain - # dict (requires Python 3.7) - def _massage(subcond): - if isinstance(subcond, str): - return subcond - if isinstance(subcond, list): - return [_massage(val) for val in subcond] - return {key: _massage(val) for key, val in subcond.items()} - if ifcond.is_present(): - print('%sif %s' % (' ' * indent, _massage(ifcond.ifcond))) + print('%sif %s' % (' ' * indent, ifcond.ifcond)) @classmethod def _print_features(cls, features, indent=4): From e6985cc4407ddb14a27a282efc0b4c8d34534317 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:07:57 +0100 Subject: [PATCH 0547/1179] qapi/introspect: Use @dataclass to simplify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A TODO comment in class Annotated reminds us to simplify it once we can use @dataclass, new in Python 3.7. We have that now, so do it. There's a similar comment in scripts/qapi/source.py, but I can't figure out how to use @dataclass there. Left for another day. Signed-off-by: Markus Armbruster Message-ID: <20250227080757.3978333-4-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- scripts/qapi/introspect.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 42e5185c7c66..89ee5d5f1764 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -11,6 +11,7 @@ See the COPYING file in the top-level directory. """ +from dataclasses import dataclass from typing import ( Any, Dict, @@ -79,19 +80,16 @@ _ValueT = TypeVar('_ValueT', bound=_Value) +@dataclass class Annotated(Generic[_ValueT]): """ Annotated generally contains a SchemaInfo-like type (as a dict), But it also used to wrap comments/ifconds around scalar leaf values, for the benefit of features and enums. """ - # TODO: Remove after Python 3.7 adds @dataclass: - # pylint: disable=too-few-public-methods - def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond, - comment: Optional[str] = None): - self.value = value - self.comment: Optional[str] = comment - self.ifcond = ifcond + value: _ValueT + ifcond: QAPISchemaIfCond + comment: Optional[str] = None def _tree_to_qlit(obj: JSONValue, From 744bce1bf7fecf8b8a8de484328515800f9c9639 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:55:56 +0100 Subject: [PATCH 0548/1179] qdev: Delete unused qdev_prop_enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Markus Armbruster Message-ID: <20250227085601.4140852-2-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- hw/core/qdev-properties.c | 7 ------- include/hw/qdev-properties.h | 1 - 2 files changed, 8 deletions(-) diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index 0b52aad55516..2540bd8880b9 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -122,13 +122,6 @@ void qdev_propinfo_set_default_value_enum(ObjectProperty *op, qapi_enum_lookup(prop->info->enum_table, prop->defval.i)); } -const PropertyInfo qdev_prop_enum = { - .name = "enum", - .get = qdev_propinfo_get_enum, - .set = qdev_propinfo_set_enum, - .set_default_value = qdev_propinfo_set_default_value_enum, -}; - /* Bit */ static uint32_t qdev_get_prop_mask(const Property *prop) diff --git a/include/hw/qdev-properties.h b/include/hw/qdev-properties.h index bf27375a3ccd..ae6ec2b99053 100644 --- a/include/hw/qdev-properties.h +++ b/include/hw/qdev-properties.h @@ -49,7 +49,6 @@ struct PropertyInfo { extern const PropertyInfo qdev_prop_bit; extern const PropertyInfo qdev_prop_bit64; extern const PropertyInfo qdev_prop_bool; -extern const PropertyInfo qdev_prop_enum; extern const PropertyInfo qdev_prop_uint8; extern const PropertyInfo qdev_prop_uint16; extern const PropertyInfo qdev_prop_uint32; From e09daf1dff8f5117b3e482f1907e762470b5675c Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:55:57 +0100 Subject: [PATCH 0549/1179] qdev: Change qdev_prop_pci_devfn member @name from "int32" to "str" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Properties using qdev_prop_pci_devfn initially accepted a string of the form "DEV.FN" or "DEV" where DEV and FN are in hexadecimal. Member @name was "pci-devfn" initially. Commit b403298adb5 (qdev: make the non-legacy pci address property accept an integer) changed them to additionally accept integers: bits 3..7 are DEV, and bits 0..2 are FN. This is inaccessible externally in device_add so far. The commit also changed @name to "int32", and set member @legacy-name to "pci-devfn". Together, this kept QMP command device-list-properties unaffected: it used @name only when @legacy_name was null. Commit 07d09c58dbb (qmp: Print descriptions of object properties) quietly dumbed that down to use @name always, and the next commit 18b91a3e082q (qdev: Drop legacy_name from qdev properties) dropped member @legacy_name. This changed the value of @type reported by QMP command device-list-properties from "pci-devfn" to "int32". But "int32" is misleading: device_add actually wants QAPI type "str". So change @name to that. Signed-off-by: Markus Armbruster Message-ID: <20250227085601.4140852-3-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- hw/core/qdev-properties-system.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index a91551a5ee89..f2b6136d0a8b 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -882,7 +882,7 @@ static int print_pci_devfn(Object *obj, const Property *prop, char *dest, } const PropertyInfo qdev_prop_pci_devfn = { - .name = "int32", + .name = "str", .description = "Slot and optional function number, example: 06.0 or 06", .print = print_pci_devfn, .get = qdev_propinfo_get_int32, From c98dac169e645e7efe0b912a9acb3c5ec88b3bdf Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:55:58 +0100 Subject: [PATCH 0550/1179] qdev: Rename PropertyInfo member @name to @type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PropertyInfo member @name becomes ObjectProperty member @type, while Property member @name becomes ObjectProperty member @name. Rename the former. Signed-off-by: Markus Armbruster Message-ID: <20250227085601.4140852-4-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé [One missed instance of @type fixed] --- backends/tpm/tpm_util.c | 2 +- hw/block/xen-block.c | 2 +- hw/core/qdev-properties-system.c | 52 ++++++++++++++++---------------- hw/core/qdev-properties.c | 36 +++++++++++----------- hw/display/apple-gfx.m | 2 +- hw/misc/xlnx-versal-trng.c | 2 +- hw/nvme/nguid.c | 2 +- hw/nvram/xlnx-bbram.c | 2 +- hw/nvram/xlnx-efuse.c | 2 +- hw/pci/pci.c | 2 +- hw/s390x/ccw-device.c | 2 +- hw/s390x/css.c | 4 +-- hw/s390x/s390-pci-bus.c | 2 +- hw/vfio/pci-quirks.c | 2 +- include/hw/qdev-properties.h | 2 +- target/riscv/cpu.c | 28 ++++++++--------- target/sparc/cpu.c | 2 +- 17 files changed, 73 insertions(+), 73 deletions(-) diff --git a/backends/tpm/tpm_util.c b/backends/tpm/tpm_util.c index 0a428eaf756d..f07a2656ce2b 100644 --- a/backends/tpm/tpm_util.c +++ b/backends/tpm/tpm_util.c @@ -76,7 +76,7 @@ static void release_tpm(Object *obj, const char *name, void *opaque) } const PropertyInfo qdev_prop_tpm = { - .name = "str", + .type = "str", .description = "ID of a tpm to use as a backend", .get = get_tpm, .set = set_tpm, diff --git a/hw/block/xen-block.c b/hw/block/xen-block.c index 6c26052561a7..7c9d1b658c2f 100644 --- a/hw/block/xen-block.c +++ b/hw/block/xen-block.c @@ -661,7 +661,7 @@ static void xen_block_set_vdev(Object *obj, Visitor *v, const char *name, * https://xenbits.xen.org/docs/unstable/man/xen-vbd-interface.7.html */ static const PropertyInfo xen_block_prop_vdev = { - .name = "str", + .type = "str", .description = "Virtual Disk specifier: d*p*/xvd*/hd*/sd*", .get = xen_block_get_vdev, .set = xen_block_set_vdev, diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index f2b6136d0a8b..56fe5e25dbf5 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -235,7 +235,7 @@ static void release_drive(Object *obj, const char *name, void *opaque) } const PropertyInfo qdev_prop_drive = { - .name = "str", + .type = "str", .description = "Node name or ID of a block device to use as a backend", .realized_set_allowed = true, .get = get_drive, @@ -244,7 +244,7 @@ const PropertyInfo qdev_prop_drive = { }; const PropertyInfo qdev_prop_drive_iothread = { - .name = "str", + .type = "str", .description = "Node name or ID of a block device to use as a backend", .realized_set_allowed = true, .get = get_drive, @@ -312,7 +312,7 @@ static void release_chr(Object *obj, const char *name, void *opaque) } const PropertyInfo qdev_prop_chr = { - .name = "str", + .type = "str", .description = "ID of a chardev to use as a backend", .get = get_chr, .set = set_chr, @@ -386,7 +386,7 @@ static void set_mac(Object *obj, Visitor *v, const char *name, void *opaque, } const PropertyInfo qdev_prop_macaddr = { - .name = "str", + .type = "str", .description = "Ethernet 6-byte MAC Address, example: 52:54:00:12:34:56", .get = get_mac, .set = set_mac, @@ -474,7 +474,7 @@ static void set_netdev(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_netdev = { - .name = "str", + .type = "str", .description = "ID of a netdev to use as a backend", .get = get_netdev, .set = set_netdev, @@ -512,7 +512,7 @@ static void set_audiodev(Object *obj, Visitor *v, const char* name, } const PropertyInfo qdev_prop_audiodev = { - .name = "str", + .type = "str", .description = "ID of an audiodev to use as a backend", /* release done on shutdown */ .get = get_audiodev, @@ -602,7 +602,7 @@ static void qdev_propinfo_set_losttickpolicy(Object *obj, Visitor *v, QEMU_BUILD_BUG_ON(sizeof(LostTickPolicy) != sizeof(int)); const PropertyInfo qdev_prop_losttickpolicy = { - .name = "LostTickPolicy", + .type = "LostTickPolicy", .enum_table = &LostTickPolicy_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_losttickpolicy, @@ -628,7 +628,7 @@ static void set_blocksize(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_blocksize = { - .name = "size", + .type = "size", .description = "A power of two between " MIN_BLOCK_SIZE_STR " and " MAX_BLOCK_SIZE_STR, .get = qdev_propinfo_get_size32, @@ -641,7 +641,7 @@ const PropertyInfo qdev_prop_blocksize = { QEMU_BUILD_BUG_ON(sizeof(BlockdevOnError) != sizeof(int)); const PropertyInfo qdev_prop_blockdev_on_error = { - .name = "BlockdevOnError", + .type = "BlockdevOnError", .description = "Error handling policy, " "report/ignore/enospc/stop/auto", .enum_table = &BlockdevOnError_lookup, @@ -655,7 +655,7 @@ const PropertyInfo qdev_prop_blockdev_on_error = { QEMU_BUILD_BUG_ON(sizeof(BiosAtaTranslation) != sizeof(int)); const PropertyInfo qdev_prop_bios_chs_trans = { - .name = "BiosAtaTranslation", + .type = "BiosAtaTranslation", .description = "Logical CHS translation algorithm, " "auto/none/lba/large/rechs", .enum_table = &BiosAtaTranslation_lookup, @@ -667,7 +667,7 @@ const PropertyInfo qdev_prop_bios_chs_trans = { /* --- FDC default drive types */ const PropertyInfo qdev_prop_fdc_drive_type = { - .name = "FdcDriveType", + .type = "FdcDriveType", .description = "FDC drive type, " "144/288/120/none/auto", .enum_table = &FloppyDriveType_lookup, @@ -679,7 +679,7 @@ const PropertyInfo qdev_prop_fdc_drive_type = { /* --- MultiFDCompression --- */ const PropertyInfo qdev_prop_multifd_compression = { - .name = "MultiFDCompression", + .type = "MultiFDCompression", .description = "multifd_compression values, " "none/zlib/zstd/qpl/uadk/qatzip", .enum_table = &MultiFDCompression_lookup, @@ -693,7 +693,7 @@ const PropertyInfo qdev_prop_multifd_compression = { QEMU_BUILD_BUG_ON(sizeof(MigMode) != sizeof(int)); const PropertyInfo qdev_prop_mig_mode = { - .name = "MigMode", + .type = "MigMode", .description = "mig_mode values, " "normal,cpr-reboot", .enum_table = &MigMode_lookup, @@ -707,7 +707,7 @@ const PropertyInfo qdev_prop_mig_mode = { QEMU_BUILD_BUG_ON(sizeof(GranuleMode) != sizeof(int)); const PropertyInfo qdev_prop_granule_mode = { - .name = "GranuleMode", + .type = "GranuleMode", .description = "granule_mode values, " "4k, 8k, 16k, 64k, host", .enum_table = &GranuleMode_lookup, @@ -717,7 +717,7 @@ const PropertyInfo qdev_prop_granule_mode = { }; const PropertyInfo qdev_prop_zero_page_detection = { - .name = "ZeroPageDetection", + .type = "ZeroPageDetection", .description = "zero_page_detection values, " "none,legacy,multifd", .enum_table = &ZeroPageDetection_lookup, @@ -801,7 +801,7 @@ static void set_reserved_region(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_reserved_region = { - .name = "reserved_region", + .type = "reserved_region", .description = "Reserved Region, example: 0xFEE00000:0xFEEFFFFF:0", .get = get_reserved_region, .set = set_reserved_region, @@ -882,7 +882,7 @@ static int print_pci_devfn(Object *obj, const Property *prop, char *dest, } const PropertyInfo qdev_prop_pci_devfn = { - .name = "str", + .type = "str", .description = "Slot and optional function number, example: 06.0 or 06", .print = print_pci_devfn, .get = qdev_propinfo_get_int32, @@ -988,7 +988,7 @@ static void set_pci_host_devaddr(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_pci_host_devaddr = { - .name = "str", + .type = "str", .description = "Address (bus/device/function) of " "the host device, example: 04:10.0", .get = get_pci_host_devaddr, @@ -998,7 +998,7 @@ const PropertyInfo qdev_prop_pci_host_devaddr = { /* --- OffAutoPCIBAR off/auto/bar0/bar1/bar2/bar3/bar4/bar5 --- */ const PropertyInfo qdev_prop_off_auto_pcibar = { - .name = "OffAutoPCIBAR", + .type = "OffAutoPCIBAR", .description = "off/auto/bar0/bar1/bar2/bar3/bar4/bar5", .enum_table = &OffAutoPCIBAR_lookup, .get = qdev_propinfo_get_enum, @@ -1080,7 +1080,7 @@ static void set_prop_pcielinkspeed(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_pcie_link_speed = { - .name = "PCIELinkSpeed", + .type = "PCIELinkSpeed", .description = "2_5/5/8/16/32/64", .enum_table = &PCIELinkSpeed_lookup, .get = get_prop_pcielinkspeed, @@ -1168,7 +1168,7 @@ static void set_prop_pcielinkwidth(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_pcie_link_width = { - .name = "PCIELinkWidth", + .type = "PCIELinkWidth", .description = "1/2/4/8/12/16/32", .enum_table = &PCIELinkWidth_lookup, .get = get_prop_pcielinkwidth, @@ -1218,7 +1218,7 @@ static void set_default_uuid_auto(ObjectProperty *op, const Property *prop) } const PropertyInfo qdev_prop_uuid = { - .name = "str", + .type = "str", .description = "UUID (aka GUID) or \"" UUID_VALUE_AUTO "\" for random value (default)", .get = get_uuid, @@ -1231,7 +1231,7 @@ const PropertyInfo qdev_prop_uuid = { QEMU_BUILD_BUG_ON(sizeof(S390CpuEntitlement) != sizeof(int)); const PropertyInfo qdev_prop_cpus390entitlement = { - .name = "S390CpuEntitlement", + .type = "S390CpuEntitlement", .description = "low/medium (default)/high", .enum_table = &S390CpuEntitlement_lookup, .get = qdev_propinfo_get_enum, @@ -1276,7 +1276,7 @@ static void release_iothread_vq_mapping_list(Object *obj, } const PropertyInfo qdev_prop_iothread_vq_mapping_list = { - .name = "IOThreadVirtQueueMappingList", + .type = "IOThreadVirtQueueMappingList", .description = "IOThread virtqueue mapping list [{\"iothread\":\"\", " "\"vqs\":[1,2,3,...]},...]", .get = get_iothread_vq_mapping_list, @@ -1287,7 +1287,7 @@ const PropertyInfo qdev_prop_iothread_vq_mapping_list = { /* --- Endian modes */ const PropertyInfo qdev_prop_endian_mode = { - .name = "EndianMode", + .type = "EndianMode", .description = "Endian mode, big/little/unspecified", .enum_table = &EndianMode_lookup, .get = qdev_propinfo_get_enum, @@ -1296,7 +1296,7 @@ const PropertyInfo qdev_prop_endian_mode = { }; const PropertyInfo qdev_prop_vmapple_virtio_blk_variant = { - .name = "VMAppleVirtioBlkVariant", + .type = "VMAppleVirtioBlkVariant", .description = "unspecified/root/aux", .enum_table = &VMAppleVirtioBlkVariant_lookup, .get = qdev_propinfo_get_enum, diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index 2540bd8880b9..5a801057dbad 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -169,7 +169,7 @@ static void set_default_value_bool(ObjectProperty *op, const Property *prop) } const PropertyInfo qdev_prop_bit = { - .name = "bool", + .type = "bool", .description = "on/off", .get = prop_get_bit, .set = prop_set_bit, @@ -218,7 +218,7 @@ static void prop_set_bit64(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_bit64 = { - .name = "bool", + .type = "bool", .description = "on/off", .get = prop_get_bit64, .set = prop_set_bit64, @@ -246,7 +246,7 @@ static void set_bool(Object *obj, Visitor *v, const char *name, void *opaque, } const PropertyInfo qdev_prop_bool = { - .name = "bool", + .type = "bool", .get = get_bool, .set = set_bool, .set_default_value = set_default_value_bool, @@ -285,7 +285,7 @@ void qdev_propinfo_set_default_value_uint(ObjectProperty *op, } const PropertyInfo qdev_prop_uint8 = { - .name = "uint8", + .type = "uint8", .get = get_uint8, .set = set_uint8, .set_default_value = qdev_propinfo_set_default_value_uint, @@ -312,7 +312,7 @@ static void set_uint16(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_uint16 = { - .name = "uint16", + .type = "uint16", .get = get_uint16, .set = set_uint16, .set_default_value = qdev_propinfo_set_default_value_uint, @@ -357,14 +357,14 @@ static void set_int32(Object *obj, Visitor *v, const char *name, void *opaque, } const PropertyInfo qdev_prop_uint32 = { - .name = "uint32", + .type = "uint32", .get = get_uint32, .set = set_uint32, .set_default_value = qdev_propinfo_set_default_value_uint, }; const PropertyInfo qdev_prop_int32 = { - .name = "int32", + .type = "int32", .get = qdev_propinfo_get_int32, .set = set_int32, .set_default_value = qdev_propinfo_set_default_value_int, @@ -409,14 +409,14 @@ static void set_int64(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_uint64 = { - .name = "uint64", + .type = "uint64", .get = get_uint64, .set = set_uint64, .set_default_value = qdev_propinfo_set_default_value_uint, }; const PropertyInfo qdev_prop_int64 = { - .name = "int64", + .type = "int64", .get = get_int64, .set = set_int64, .set_default_value = qdev_propinfo_set_default_value_int, @@ -436,7 +436,7 @@ static void set_uint64_checkmask(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_uint64_checkmask = { - .name = "uint64", + .type = "uint64", .get = get_uint64, .set = set_uint64_checkmask, }; @@ -478,7 +478,7 @@ static void set_string(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_string = { - .name = "str", + .type = "str", .release = release_string, .get = get_string, .set = set_string, @@ -487,7 +487,7 @@ const PropertyInfo qdev_prop_string = { /* --- on/off/auto --- */ const PropertyInfo qdev_prop_on_off_auto = { - .name = "OnOffAuto", + .type = "OnOffAuto", .description = "on/off/auto", .enum_table = &OnOffAuto_lookup, .get = qdev_propinfo_get_enum, @@ -530,7 +530,7 @@ static void set_size32(Object *obj, Visitor *v, const char *name, void *opaque, } const PropertyInfo qdev_prop_size32 = { - .name = "size", + .type = "size", .get = qdev_propinfo_get_size32, .set = set_size32, .set_default_value = qdev_propinfo_set_default_value_uint, @@ -733,7 +733,7 @@ static void default_prop_array(ObjectProperty *op, const Property *prop) } const PropertyInfo qdev_prop_array = { - .name = "list", + .type = "list", .get = get_prop_array, .set = set_prop_array, .release = release_prop_array, @@ -937,7 +937,7 @@ static void set_size(Object *obj, Visitor *v, const char *name, void *opaque, } const PropertyInfo qdev_prop_size = { - .name = "size", + .type = "size", .get = get_size, .set = set_size, .set_default_value = qdev_propinfo_set_default_value_uint, @@ -955,7 +955,7 @@ static ObjectProperty *create_link_property(ObjectClass *oc, const char *name, } const PropertyInfo qdev_prop_link = { - .name = "link", + .type = "link", .create = create_link_property, }; @@ -966,7 +966,7 @@ void qdev_property_add_static(DeviceState *dev, const Property *prop) assert(!prop->info->create); - op = object_property_add(obj, prop->name, prop->info->name, + op = object_property_add(obj, prop->name, prop->info->type, field_prop_getter(prop->info), field_prop_setter(prop->info), prop->info->release, @@ -993,7 +993,7 @@ static void qdev_class_add_property(DeviceClass *klass, const char *name, op = prop->info->create(oc, name, prop); } else { op = object_class_property_add(oc, - name, prop->info->name, + name, prop->info->type, field_prop_getter(prop->info), field_prop_setter(prop->info), prop->info->release, diff --git a/hw/display/apple-gfx.m b/hw/display/apple-gfx.m index 1554f3b8016b..c4323574e121 100644 --- a/hw/display/apple-gfx.m +++ b/hw/display/apple-gfx.m @@ -871,7 +871,7 @@ static void apple_gfx_set_display_mode(Object *obj, Visitor *v, } const PropertyInfo qdev_prop_apple_gfx_display_mode = { - .name = "display_mode", + .type = "display_mode", .description = "Display mode in pixels and Hertz, as x@ " "Example: 3840x2160@60", diff --git a/hw/misc/xlnx-versal-trng.c b/hw/misc/xlnx-versal-trng.c index dbd9b58a4ece..9a44a90d1e5b 100644 --- a/hw/misc/xlnx-versal-trng.c +++ b/hw/misc/xlnx-versal-trng.c @@ -652,7 +652,7 @@ static void trng_prop_fault_event_set(Object *obj, Visitor *v, } static const PropertyInfo trng_prop_fault_events = { - .name = "uint32:bits", + .type = "uint32:bits", .description = "Set to trigger TRNG fault events", .set = trng_prop_fault_event_set, .realized_set_allowed = true, diff --git a/hw/nvme/nguid.c b/hw/nvme/nguid.c index be63cb75e168..4cd6fad6ac9b 100644 --- a/hw/nvme/nguid.c +++ b/hw/nvme/nguid.c @@ -179,7 +179,7 @@ static void set_nguid(Object *obj, Visitor *v, const char *name, void *opaque, } const PropertyInfo qdev_prop_nguid = { - .name = "str", + .type = "str", .description = "NGUID or \"" NGUID_VALUE_AUTO "\" for random value", .get = get_nguid, diff --git a/hw/nvram/xlnx-bbram.c b/hw/nvram/xlnx-bbram.c index 0e8552ce6561..14cc9073c782 100644 --- a/hw/nvram/xlnx-bbram.c +++ b/hw/nvram/xlnx-bbram.c @@ -502,7 +502,7 @@ static void bbram_prop_release_drive(Object *obj, const char *name, } static const PropertyInfo bbram_prop_drive = { - .name = "str", + .type = "str", .description = "Node name or ID of a block device to use as BBRAM backend", .realized_set_allowed = true, .get = bbram_prop_get_drive, diff --git a/hw/nvram/xlnx-efuse.c b/hw/nvram/xlnx-efuse.c index e2e8311a48cb..29e7dd539ec6 100644 --- a/hw/nvram/xlnx-efuse.c +++ b/hw/nvram/xlnx-efuse.c @@ -257,7 +257,7 @@ static void efuse_prop_release_drive(Object *obj, const char *name, } static const PropertyInfo efuse_prop_drive = { - .name = "str", + .type = "str", .description = "Node name or ID of a block device to use as eFUSE backend", .realized_set_allowed = true, .get = efuse_prop_get_drive, diff --git a/hw/pci/pci.c b/hw/pci/pci.c index 1d42847ef044..e3c286683070 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -77,7 +77,7 @@ static void prop_pci_busnr_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_pci_busnr = { - .name = "busnr", + .type = "busnr", .get = prop_pci_busnr_get, }; diff --git a/hw/s390x/ccw-device.c b/hw/s390x/ccw-device.c index 494faebb5a8a..5aa6ff8eac0b 100644 --- a/hw/s390x/ccw-device.c +++ b/hw/s390x/ccw-device.c @@ -74,7 +74,7 @@ static void ccw_device_set_loadparm(Object *obj, Visitor *v, } const PropertyInfo ccw_loadparm = { - .name = "ccw_loadparm", + .type = "ccw_loadparm", .description = "Up to 8 chars in set of [A-Za-z0-9. ] to pass" " to the guest loader/kernel", .get = ccw_device_get_loadparm, diff --git a/hw/s390x/css.c b/hw/s390x/css.c index 4e27b2961b8c..738800c98df5 100644 --- a/hw/s390x/css.c +++ b/hw/s390x/css.c @@ -2523,7 +2523,7 @@ static void set_css_devid(Object *obj, Visitor *v, const char *name, } const PropertyInfo css_devid_propinfo = { - .name = "str", + .type = "str", .description = "Identifier of an I/O device in the channel " "subsystem, example: fe.1.23ab", .get = get_css_devid, @@ -2531,7 +2531,7 @@ const PropertyInfo css_devid_propinfo = { }; const PropertyInfo css_devid_ro_propinfo = { - .name = "str", + .type = "str", .description = "Read-only identifier of an I/O device in the channel " "subsystem, example: fe.1.23ab", .get = get_css_devid, diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index 913d72cc7480..7f340965c0d9 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -1495,7 +1495,7 @@ static void s390_pci_set_fid(Object *obj, Visitor *v, const char *name, } static const PropertyInfo s390_pci_fid_propinfo = { - .name = "zpci_fid", + .type = "zpci_fid", .get = s390_pci_get_fid, .set = s390_pci_set_fid, }; diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c index fbe43b0a7903..ba97d597919a 100644 --- a/hw/vfio/pci-quirks.c +++ b/hw/vfio/pci-quirks.c @@ -1480,7 +1480,7 @@ static void set_nv_gpudirect_clique_id(Object *obj, Visitor *v, } const PropertyInfo qdev_prop_nv_gpudirect_clique = { - .name = "uint4", + .type = "uint4", .description = "NVIDIA GPUDirect Clique ID (0 - 15)", .get = get_nv_gpudirect_clique_id, .set = set_nv_gpudirect_clique_id, diff --git a/include/hw/qdev-properties.h b/include/hw/qdev-properties.h index ae6ec2b99053..15fcec5260c7 100644 --- a/include/hw/qdev-properties.h +++ b/include/hw/qdev-properties.h @@ -30,7 +30,7 @@ struct Property { }; struct PropertyInfo { - const char *name; + const char *type; const char *description; const QEnumLookup *enum_table; bool realized_set_allowed; /* allow setting property on realized device */ diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 47424fd5e2a0..1ac34e398d83 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -1816,7 +1816,7 @@ static void prop_pmu_num_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_pmu_num = { - .name = "pmu-num", + .type = "pmu-num", .get = prop_pmu_num_get, .set = prop_pmu_num_set, }; @@ -1857,7 +1857,7 @@ static void prop_pmu_mask_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_pmu_mask = { - .name = "pmu-mask", + .type = "pmu-mask", .get = prop_pmu_mask_get, .set = prop_pmu_mask_set, }; @@ -1888,7 +1888,7 @@ static void prop_mmu_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_mmu = { - .name = "mmu", + .type = "mmu", .get = prop_mmu_get, .set = prop_mmu_set, }; @@ -1919,7 +1919,7 @@ static void prop_pmp_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_pmp = { - .name = "pmp", + .type = "pmp", .get = prop_pmp_get, .set = prop_pmp_set, }; @@ -1993,7 +1993,7 @@ static void prop_priv_spec_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_priv_spec = { - .name = "priv_spec", + .type = "priv_spec", .get = prop_priv_spec_get, .set = prop_priv_spec_set, }; @@ -2024,7 +2024,7 @@ static void prop_vext_spec_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_vext_spec = { - .name = "vext_spec", + .type = "vext_spec", .get = prop_vext_spec_get, .set = prop_vext_spec_set, }; @@ -2065,7 +2065,7 @@ static void prop_vlen_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_vlen = { - .name = "vlen", + .type = "vlen", .get = prop_vlen_get, .set = prop_vlen_set, }; @@ -2105,7 +2105,7 @@ static void prop_elen_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_elen = { - .name = "elen", + .type = "elen", .get = prop_elen_get, .set = prop_elen_set, }; @@ -2140,7 +2140,7 @@ static void prop_cbom_blksize_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_cbom_blksize = { - .name = "cbom_blocksize", + .type = "cbom_blocksize", .get = prop_cbom_blksize_get, .set = prop_cbom_blksize_set, }; @@ -2175,7 +2175,7 @@ static void prop_cbop_blksize_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_cbop_blksize = { - .name = "cbop_blocksize", + .type = "cbop_blocksize", .get = prop_cbop_blksize_get, .set = prop_cbop_blksize_set, }; @@ -2210,7 +2210,7 @@ static void prop_cboz_blksize_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_cboz_blksize = { - .name = "cboz_blocksize", + .type = "cboz_blocksize", .get = prop_cboz_blksize_get, .set = prop_cboz_blksize_set, }; @@ -2245,7 +2245,7 @@ static void prop_mvendorid_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_mvendorid = { - .name = "mvendorid", + .type = "mvendorid", .get = prop_mvendorid_get, .set = prop_mvendorid_set, }; @@ -2280,7 +2280,7 @@ static void prop_mimpid_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_mimpid = { - .name = "mimpid", + .type = "mimpid", .get = prop_mimpid_get, .set = prop_mimpid_set, }; @@ -2336,7 +2336,7 @@ static void prop_marchid_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_marchid = { - .name = "marchid", + .type = "marchid", .get = prop_marchid_get, .set = prop_marchid_set, }; diff --git a/target/sparc/cpu.c b/target/sparc/cpu.c index fbd38ec334a9..c8ea35be7650 100644 --- a/target/sparc/cpu.c +++ b/target/sparc/cpu.c @@ -938,7 +938,7 @@ static void sparc_set_nwindows(Object *obj, Visitor *v, const char *name, } static const PropertyInfo qdev_prop_nwindows = { - .name = "int", + .type = "int", .get = sparc_get_nwindows, .set = sparc_set_nwindows, }; From ff30d3b1ac7777dd6048fb7932fedce51f3ea89d Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:55:59 +0100 Subject: [PATCH 0551/1179] qdev: Change values of PropertyInfo member @type to be QAPI types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PropertyInfo member @type is externally visible via QMP device-list-properties and qom-list-properies. Its meaning is not documented at its definition. It gets passed as @type argument to object_property_add() and object_class_property_add(). This argument's documentation isn't of much help, either: * @type: the type name of the property. This namespace is pretty loosely * defined. Sub namespaces are constructed by using a prefix and then * to angle brackets. For instance, the type 'virtio-net-pci' in the * 'link' namespace would be 'link'. The two QMP commands document it as # @type: the type of the property. This will typically come in one of # four forms: # # 1) A primitive type such as 'u8', 'u16', 'bool', 'str', or # 'double'. These types are mapped to the appropriate JSON # type. # # 2) A child type in the form 'child' where subtype is a # qdev device type name. Child properties create the # composition tree. # # 3) A link type in the form 'link' where subtype is a # qdev device type name. Link properties form the device model # graph. "Typically come in one of four forms" followed by three items inspires the level of trust that is appropriate here. Clean up a bunch of funnies: * qdev_prop_fdc_drive_type.type is "FdcDriveType". Its .enum_table refers to QAPI type "FloppyDriveType". So use that. * qdev_prop_reserved_region is "reserved_region". Its only user is an array property called "reserved-regions". Its .set() visits str. So change @type to "str". * trng_prop_fault_event_set.type is "uint32:bits". Its .set() visits uint32, so change @type to "uint32". If we believe mentioning it's actually bits is useful, the proper place would be .description. * ccw_loadparm.type is "ccw_loadparm". It's users are properties called "loadparm". Its .set() visits str. So change @type to "str". * qdev_prop_nv_gpudirect_clique.type is "uint4". Its set() visits uint8, so change @type to "uint8". If we believe mentioning the range is useful, the proper place would be .description. * s390_pci_fid_propinfo.type is "zpci_fid". Its .set() visits uint32. So change type to that, and move the "zpci_fid" to .description. This is admittedly a lousy description, but it's still an improvement; for instance, output of -device zpci,help changes from fid= to fid= - zpci_fid * Similarly for a raft of PropertyInfo in target/riscv/cpu.c. Signed-off-by: Markus Armbruster Message-ID: <20250227085601.4140852-5-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé [Commit message typo fixed] --- hw/core/qdev-properties-system.c | 4 +-- hw/misc/xlnx-versal-trng.c | 2 +- hw/s390x/ccw-device.c | 2 +- hw/s390x/s390-pci-bus.c | 3 ++- hw/vfio/pci-quirks.c | 2 +- target/riscv/cpu.c | 44 ++++++++++++++++++++++---------- 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index 56fe5e25dbf5..0ac1485d5469 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -667,7 +667,7 @@ const PropertyInfo qdev_prop_bios_chs_trans = { /* --- FDC default drive types */ const PropertyInfo qdev_prop_fdc_drive_type = { - .type = "FdcDriveType", + .type = "FloppyDriveType", .description = "FDC drive type, " "144/288/120/none/auto", .enum_table = &FloppyDriveType_lookup, @@ -801,7 +801,7 @@ static void set_reserved_region(Object *obj, Visitor *v, const char *name, } const PropertyInfo qdev_prop_reserved_region = { - .type = "reserved_region", + .type = "str", .description = "Reserved Region, example: 0xFEE00000:0xFEEFFFFF:0", .get = get_reserved_region, .set = set_reserved_region, diff --git a/hw/misc/xlnx-versal-trng.c b/hw/misc/xlnx-versal-trng.c index 9a44a90d1e5b..ba93f93cab94 100644 --- a/hw/misc/xlnx-versal-trng.c +++ b/hw/misc/xlnx-versal-trng.c @@ -652,7 +652,7 @@ static void trng_prop_fault_event_set(Object *obj, Visitor *v, } static const PropertyInfo trng_prop_fault_events = { - .type = "uint32:bits", + .type = "uint32", .description = "Set to trigger TRNG fault events", .set = trng_prop_fault_event_set, .realized_set_allowed = true, diff --git a/hw/s390x/ccw-device.c b/hw/s390x/ccw-device.c index 5aa6ff8eac0b..1d4b8ea35c5e 100644 --- a/hw/s390x/ccw-device.c +++ b/hw/s390x/ccw-device.c @@ -74,7 +74,7 @@ static void ccw_device_set_loadparm(Object *obj, Visitor *v, } const PropertyInfo ccw_loadparm = { - .type = "ccw_loadparm", + .type = "str", .description = "Up to 8 chars in set of [A-Za-z0-9. ] to pass" " to the guest loader/kernel", .get = ccw_device_get_loadparm, diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index 7f340965c0d9..04cdd4a11b59 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -1495,7 +1495,8 @@ static void s390_pci_set_fid(Object *obj, Visitor *v, const char *name, } static const PropertyInfo s390_pci_fid_propinfo = { - .type = "zpci_fid", + .type = "uint32", + .description = "zpci_fid", .get = s390_pci_get_fid, .set = s390_pci_set_fid, }; diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c index ba97d597919a..c53591fe2ba5 100644 --- a/hw/vfio/pci-quirks.c +++ b/hw/vfio/pci-quirks.c @@ -1480,7 +1480,7 @@ static void set_nv_gpudirect_clique_id(Object *obj, Visitor *v, } const PropertyInfo qdev_prop_nv_gpudirect_clique = { - .type = "uint4", + .type = "uint8", .description = "NVIDIA GPUDirect Clique ID (0 - 15)", .get = get_nv_gpudirect_clique_id, .set = set_nv_gpudirect_clique_id, diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 1ac34e398d83..045c9c78ee0a 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -1816,7 +1816,8 @@ static void prop_pmu_num_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_pmu_num = { - .type = "pmu-num", + .type = "int8", + .description = "pmu-num", .get = prop_pmu_num_get, .set = prop_pmu_num_set, }; @@ -1857,7 +1858,8 @@ static void prop_pmu_mask_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_pmu_mask = { - .type = "pmu-mask", + .type = "int8", + .description = "pmu-mask", .get = prop_pmu_mask_get, .set = prop_pmu_mask_set, }; @@ -1888,7 +1890,8 @@ static void prop_mmu_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_mmu = { - .type = "mmu", + .type = "bool", + .description = "mmu", .get = prop_mmu_get, .set = prop_mmu_set, }; @@ -1919,7 +1922,8 @@ static void prop_pmp_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_pmp = { - .type = "pmp", + .type = "bool", + .description = "pmp", .get = prop_pmp_get, .set = prop_pmp_set, }; @@ -1993,7 +1997,9 @@ static void prop_priv_spec_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_priv_spec = { - .type = "priv_spec", + .type = "str", + .description = "priv_spec", + /* FIXME enum? */ .get = prop_priv_spec_get, .set = prop_priv_spec_set, }; @@ -2024,7 +2030,9 @@ static void prop_vext_spec_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_vext_spec = { - .type = "vext_spec", + .type = "str", + .description = "vext_spec", + /* FIXME enum? */ .get = prop_vext_spec_get, .set = prop_vext_spec_set, }; @@ -2065,7 +2073,8 @@ static void prop_vlen_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_vlen = { - .type = "vlen", + .type = "uint16", + .description = "vlen", .get = prop_vlen_get, .set = prop_vlen_set, }; @@ -2105,7 +2114,8 @@ static void prop_elen_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_elen = { - .type = "elen", + .type = "uint16", + .description = "elen", .get = prop_elen_get, .set = prop_elen_set, }; @@ -2140,7 +2150,8 @@ static void prop_cbom_blksize_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_cbom_blksize = { - .type = "cbom_blocksize", + .type = "uint16", + .description = "cbom_blocksize", .get = prop_cbom_blksize_get, .set = prop_cbom_blksize_set, }; @@ -2175,7 +2186,8 @@ static void prop_cbop_blksize_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_cbop_blksize = { - .type = "cbop_blocksize", + .type = "uint16", + .description = "cbop_blocksize", .get = prop_cbop_blksize_get, .set = prop_cbop_blksize_set, }; @@ -2210,7 +2222,8 @@ static void prop_cboz_blksize_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_cboz_blksize = { - .type = "cboz_blocksize", + .type = "uint16", + .description = "cboz_blocksize", .get = prop_cboz_blksize_get, .set = prop_cboz_blksize_set, }; @@ -2245,7 +2258,8 @@ static void prop_mvendorid_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_mvendorid = { - .type = "mvendorid", + .type = "uint32", + .description = "mvendorid", .get = prop_mvendorid_get, .set = prop_mvendorid_set, }; @@ -2280,7 +2294,8 @@ static void prop_mimpid_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_mimpid = { - .type = "mimpid", + .type = "uint64", + .description = "mimpid", .get = prop_mimpid_get, .set = prop_mimpid_set, }; @@ -2336,7 +2351,8 @@ static void prop_marchid_get(Object *obj, Visitor *v, const char *name, } static const PropertyInfo prop_marchid = { - .type = "marchid", + .type = "uint64", + .description = "marchid", .get = prop_marchid_get, .set = prop_marchid_set, }; From 0b9d12b03cc178c479f94413d580e8b7b37f44f9 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:56:00 +0100 Subject: [PATCH 0552/1179] qdev: Improve PropertyInfo member @description for enum properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consistently use format "DESCRIPTION (VALUE/VALUE...)". Signed-off-by: Markus Armbruster Message-ID: <20250227085601.4140852-6-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- hw/core/qdev-properties-system.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index 0ac1485d5469..6d7dcf368d31 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -603,6 +603,7 @@ QEMU_BUILD_BUG_ON(sizeof(LostTickPolicy) != sizeof(int)); const PropertyInfo qdev_prop_losttickpolicy = { .type = "LostTickPolicy", + .description = "Policy for handling lost ticks (discard/delay/slew)", .enum_table = &LostTickPolicy_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_losttickpolicy, @@ -642,8 +643,7 @@ QEMU_BUILD_BUG_ON(sizeof(BlockdevOnError) != sizeof(int)); const PropertyInfo qdev_prop_blockdev_on_error = { .type = "BlockdevOnError", - .description = "Error handling policy, " - "report/ignore/enospc/stop/auto", + .description = "Error handling policy (report/ignore/enospc/stop/auto)", .enum_table = &BlockdevOnError_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, @@ -656,8 +656,8 @@ QEMU_BUILD_BUG_ON(sizeof(BiosAtaTranslation) != sizeof(int)); const PropertyInfo qdev_prop_bios_chs_trans = { .type = "BiosAtaTranslation", - .description = "Logical CHS translation algorithm, " - "auto/none/lba/large/rechs", + .description = "Logical CHS translation algorithm " + " (auto/none/lba/large/rechs)", .enum_table = &BiosAtaTranslation_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, @@ -668,8 +668,7 @@ const PropertyInfo qdev_prop_bios_chs_trans = { const PropertyInfo qdev_prop_fdc_drive_type = { .type = "FloppyDriveType", - .description = "FDC drive type, " - "144/288/120/none/auto", + .description = "Floppy drive type (144/288/120/none/auto)", .enum_table = &FloppyDriveType_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, @@ -680,8 +679,8 @@ const PropertyInfo qdev_prop_fdc_drive_type = { const PropertyInfo qdev_prop_multifd_compression = { .type = "MultiFDCompression", - .description = "multifd_compression values, " - "none/zlib/zstd/qpl/uadk/qatzip", + .description = "multifd_compression values" + " (none/zlib/zstd/qpl/uadk/qatzip)", .enum_table = &MultiFDCompression_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, @@ -694,8 +693,7 @@ QEMU_BUILD_BUG_ON(sizeof(MigMode) != sizeof(int)); const PropertyInfo qdev_prop_mig_mode = { .type = "MigMode", - .description = "mig_mode values, " - "normal,cpr-reboot", + .description = "Migration mode (normal/cpr-reboot)", .enum_table = &MigMode_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, @@ -708,8 +706,7 @@ QEMU_BUILD_BUG_ON(sizeof(GranuleMode) != sizeof(int)); const PropertyInfo qdev_prop_granule_mode = { .type = "GranuleMode", - .description = "granule_mode values, " - "4k, 8k, 16k, 64k, host", + .description = "Granule page size (4k/8k/16k/64k/host)", .enum_table = &GranuleMode_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, @@ -718,8 +715,7 @@ const PropertyInfo qdev_prop_granule_mode = { const PropertyInfo qdev_prop_zero_page_detection = { .type = "ZeroPageDetection", - .description = "zero_page_detection values, " - "none,legacy,multifd", + .description = "Zero page detection (none/legacy/multifd)", .enum_table = &ZeroPageDetection_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, @@ -1232,7 +1228,7 @@ QEMU_BUILD_BUG_ON(sizeof(S390CpuEntitlement) != sizeof(int)); const PropertyInfo qdev_prop_cpus390entitlement = { .type = "S390CpuEntitlement", - .description = "low/medium (default)/high", + .description = "auto/low/medium/high (default medium)", .enum_table = &S390CpuEntitlement_lookup, .get = qdev_propinfo_get_enum, .set = qdev_propinfo_set_enum, From 45e5b49360224019eddd307e34ea5625e4e730bf Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 27 Feb 2025 09:56:01 +0100 Subject: [PATCH 0553/1179] qdev: Improve a few more PropertyInfo @description members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Markus Armbruster Message-ID: <20250227085601.4140852-7-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- hw/block/xen-block.c | 2 +- hw/core/qdev-properties-system.c | 2 +- hw/core/qdev-properties.c | 1 + hw/s390x/ccw-device.c | 4 ++-- target/sparc/cpu.c | 1 + 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/hw/block/xen-block.c b/hw/block/xen-block.c index 7c9d1b658c2f..2098286b5f96 100644 --- a/hw/block/xen-block.c +++ b/hw/block/xen-block.c @@ -662,7 +662,7 @@ static void xen_block_set_vdev(Object *obj, Visitor *v, const char *name, */ static const PropertyInfo xen_block_prop_vdev = { .type = "str", - .description = "Virtual Disk specifier: d*p*/xvd*/hd*/sd*", + .description = "Virtual Disk specifier (d*p*/xvd*/hd*/sd*)", .get = xen_block_get_vdev, .set = xen_block_set_vdev, }; diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index 6d7dcf368d31..a7dde73c29bf 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -985,7 +985,7 @@ static void set_pci_host_devaddr(Object *obj, Visitor *v, const char *name, const PropertyInfo qdev_prop_pci_host_devaddr = { .type = "str", - .description = "Address (bus/device/function) of " + .description = "Address (bus:device.function) of " "the host device, example: 04:10.0", .get = get_pci_host_devaddr, .set = set_pci_host_devaddr, diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index 5a801057dbad..c04df3b337ae 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -247,6 +247,7 @@ static void set_bool(Object *obj, Visitor *v, const char *name, void *opaque, const PropertyInfo qdev_prop_bool = { .type = "bool", + .description = "on/off", .get = get_bool, .set = set_bool, .set_default_value = set_default_value_bool, diff --git a/hw/s390x/ccw-device.c b/hw/s390x/ccw-device.c index 1d4b8ea35c5e..1ea9934f6cee 100644 --- a/hw/s390x/ccw-device.c +++ b/hw/s390x/ccw-device.c @@ -75,8 +75,8 @@ static void ccw_device_set_loadparm(Object *obj, Visitor *v, const PropertyInfo ccw_loadparm = { .type = "str", - .description = "Up to 8 chars in set of [A-Za-z0-9. ] to pass" - " to the guest loader/kernel", + .description = "Up to 8 chars in set of [A-Za-z0-9. ] to select" + " a guest kernel", .get = ccw_device_get_loadparm, .set = ccw_device_set_loadparm, }; diff --git a/target/sparc/cpu.c b/target/sparc/cpu.c index c8ea35be7650..f0613f8a8e9a 100644 --- a/target/sparc/cpu.c +++ b/target/sparc/cpu.c @@ -939,6 +939,7 @@ static void sparc_set_nwindows(Object *obj, Visitor *v, const char *name, static const PropertyInfo qdev_prop_nwindows = { .type = "int", + .description = "Number of register windows", .get = sparc_get_nwindows, .set = sparc_set_nwindows, }; From 71ba2613ad470b6397868ae7333cd255e467be68 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Fri, 28 Feb 2025 14:43:35 +0100 Subject: [PATCH 0554/1179] docs/devel/qapi-code-gen: Discourage use of 'prefix' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QAPI's 'prefix' feature can make the connection between enumeration type and its constants less than obvious. It's best used with restraint. Commit 7bbadc60b5..64f5e9db77 eliminated most uses. Discourage new ones. Signed-off-by: Markus Armbruster Message-ID: <20250228134335.132278-1-armbru@redhat.com> Reviewed-by: Daniel P. Berrangé --- docs/devel/qapi-code-gen.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst index 9fa94251b072..f9cfe8721f7c 100644 --- a/docs/devel/qapi-code-gen.rst +++ b/docs/devel/qapi-code-gen.rst @@ -229,7 +229,8 @@ These are of the form PREFIX_NAME, where PREFIX is derived from the enumeration type's name, and NAME from the value's name. For the example above, the generator maps 'MyEnum' to MY_ENUM and 'value1' to VALUE1, resulting in the enumeration constant MY_ENUM_VALUE1. The -optional 'prefix' member overrides PREFIX. +optional 'prefix' member overrides PREFIX. This is rarely necessary, +and should be used with restraint. The generated C enumeration constants have values 0, 1, ..., N-1 (in QAPI schema order), where N is the number of values. There is an From 41494da7df8d28eb75eac4799f4b49061fbbf64c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 27 Feb 2025 13:34:30 +0100 Subject: [PATCH 0555/1179] chardev: express dependency on io/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chardev is using qio functions, so express that in the Meson internal dependency. (I found this when adding character devices bindings for Rust; they initially needed the io dependency added by hand). Reviewed-by: Daniel P. Berrangé Signed-off-by: Paolo Bonzini --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 8b9fda4d95e6..67ec2b78319b 100644 --- a/meson.build +++ b/meson.build @@ -4015,7 +4015,7 @@ libchardev = static_library('chardev', chardev_ss.sources() + genh, build_by_default: false) chardev = declare_dependency(objects: libchardev.extract_all_objects(recursive: false), - dependencies: chardev_ss.dependencies()) + dependencies: [chardev_ss.dependencies(), io]) hwcore_ss = hwcore_ss.apply({}) libhwcore = static_library('hwcore', sources: hwcore_ss.sources() + genh, From cff666a3aee566889fcc1ab7167ca0a727af7167 Mon Sep 17 00:00:00 2001 From: Nabih Estefan Date: Thu, 27 Feb 2025 18:04:54 +0000 Subject: [PATCH 0556/1179] scripts: dump stdin on meson-buildoptions error Dump sys.stdin when it errors on meson-buildoptions.py, letting us debug the build errors instead of just saying "Couldn't parse" Signed-off-by: Nabih Estefan Signed-off-by: Patrick Venture Link: https://lore.kernel.org/r/20250227180454.2006757-1-venture@google.com Signed-off-by: Paolo Bonzini --- scripts/meson-buildoptions.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/meson-buildoptions.py b/scripts/meson-buildoptions.py index 4814a8ff61f9..a3e22471b2fd 100644 --- a/scripts/meson-buildoptions.py +++ b/scripts/meson-buildoptions.py @@ -241,8 +241,14 @@ def print_parse(options): print(" esac") print("}") - -options = load_options(json.load(sys.stdin)) +json_data = sys.stdin.read() +try: + options = load_options(json.loads(json_data)) +except: + print("Failure in scripts/meson-buildoptions.py parsing stdin as json", + file=sys.stderr) + print(json_data, file=sys.stderr) + sys.exit(1) print("# This file is generated by meson-buildoptions.py, do not edit!") print_help(options) print_parse(options) From 0b9d05e3c98fe168f3502ccc422b9171467314fa Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 10:18:34 +0100 Subject: [PATCH 0557/1179] rust: cell: add wrapper for FFI types Inspired by the same-named type in Linux. This type provides the compiler with a correct view of what goes on with FFI types. In addition, it separates the glue code from the bindgen-generated code, allowing traits such as Send, Sync or Zeroable to be specified independently for C and Rust structs. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 36 +++++-- rust/qemu-api/src/cell.rs | 204 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 224 insertions(+), 16 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 5d8aa3a45bca..784c3e40bdcf 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -296,15 +296,33 @@ of ``&mut self``; access to internal fields must use *interior mutability* to go from a shared reference to a ``&mut``. Whenever C code provides you with an opaque ``void *``, avoid converting it -to a Rust mutable reference, and use a shared reference instead. Rust code -will then have to use QEMU's ``BqlRefCell`` and ``BqlCell`` type, which -enforce that locking rules for the "Big QEMU Lock" are respected. These cell -types are also known to the ``vmstate`` crate, which is able to "look inside" -them when building an in-memory representation of a ``struct``'s layout. -Note that the same is not true of a ``RefCell`` or ``Mutex``. - -In the future, similar cell types might also be provided for ``AioContext``-based -locking as well. +to a Rust mutable reference, and use a shared reference instead. The +``qemu_api::cell`` module provides wrappers that can be used to tell the +Rust compiler about interior mutability, and optionally to enforce locking +rules for the "Big QEMU Lock". In the future, similar cell types might +also be provided for ``AioContext``-based locking as well. + +In particular, device code will usually rely on the ``BqlRefCell`` and +``BqlCell`` type to ensure that data is accessed correctly under the +"Big QEMU Lock". These cell types are also known to the ``vmstate`` +crate, which is able to "look inside" them when building an in-memory +representation of a ``struct``'s layout. Note that the same is not true +of a ``RefCell`` or ``Mutex``. + +Bindings code instead will usually use the ``Opaque`` type, which hides +the contents of the underlying struct and can be easily converted to +a raw pointer, for use in calls to C functions. It can be used for +example as follows:: + + #[repr(transparent)] + #[derive(Debug)] + pub struct Object(Opaque); + +The bindings will then manually check for the big QEMU lock with +assertions, which allows the wrapper to be declared thread-safe:: + + unsafe impl Send for Object {} + unsafe impl Sync for Object {} Writing bindings to C code '''''''''''''''''''''''''' diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index eae4e2ce786b..2889abb868ee 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -27,7 +27,7 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! BQL-protected mutable containers. +//! QEMU-specific mutable containers //! //! Rust memory safety is based on this rule: Given an object `T`, it is only //! possible to have one of the following: @@ -43,8 +43,10 @@ //! usually have their pointer shared with the "outside world very early in //! their lifetime", for example when they create their //! [`MemoryRegion`s](crate::bindings::MemoryRegion). Therefore, individual -//! parts of a device must be made mutable in a controlled manner through the -//! use of cell types. +//! parts of a device must be made mutable in a controlled manner; this module +//! provides the tools to do so. +//! +//! ## Cell types //! //! [`BqlCell`] and [`BqlRefCell`] allow doing this via the Big QEMU Lock. //! While they are essentially the same single-threaded primitives that are @@ -71,7 +73,7 @@ //! QEMU device implementations is usually incorrect and can lead to //! thread-safety issues. //! -//! ## `BqlCell` +//! ### `BqlCell` //! //! [`BqlCell`] implements interior mutability by moving values in and out of //! the cell. That is, an `&mut T` to the inner value can never be obtained as @@ -91,7 +93,7 @@ //! - [`set`](BqlCell::set): this method replaces the interior value, //! dropping the replaced value. //! -//! ## `BqlRefCell` +//! ### `BqlRefCell` //! //! [`BqlRefCell`] uses Rust's lifetimes to implement "dynamic borrowing", a //! process whereby one can claim temporary, exclusive, mutable access to the @@ -111,13 +113,82 @@ //! Multiple immutable borrows are allowed via [`borrow`](BqlRefCell::borrow), //! or a single mutable borrow via [`borrow_mut`](BqlRefCell::borrow_mut). The //! thread will panic if these rules are violated or if the BQL is not held. +//! +//! ## Opaque wrappers +//! +//! The cell types from the previous section are useful at the boundaries +//! of code that requires interior mutability. When writing glue code that +//! interacts directly with C structs, however, it is useful to operate +//! at a lower level. +//! +//! C functions often violate Rust's fundamental assumptions about memory +//! safety by modifying memory even if it is shared. Furthermore, C structs +//! often start their life uninitialized and may be populated lazily. +//! +//! For this reason, this module provides the [`Opaque`] type to opt out +//! of Rust's usual guarantees about the wrapped type. Access to the wrapped +//! value is always through raw pointers, obtained via methods like +//! [`as_mut_ptr()`](Opaque::as_mut_ptr) and [`as_ptr()`](Opaque::as_ptr). These +//! pointers can then be passed to C functions or dereferenced; both actions +//! require `unsafe` blocks, making it clear where safety guarantees must be +//! manually verified. For example +//! +//! ```ignore +//! unsafe { +//! let state = Opaque::::uninit(); +//! qemu_struct_init(state.as_mut_ptr()); +//! } +//! ``` +//! +//! [`Opaque`] will usually be wrapped one level further, so that +//! bridge methods can be added to the wrapper: +//! +//! ```ignore +//! pub struct MyStruct(Opaque); +//! +//! impl MyStruct { +//! fn new() -> Pin> { +//! let result = Box::pin(unsafe { Opaque::uninit() }); +//! unsafe { qemu_struct_init(result.as_mut_ptr()) }; +//! result +//! } +//! } +//! ``` +//! +//! This pattern of wrapping bindgen-generated types in [`Opaque`] provides +//! several advantages: +//! +//! * The choice of traits to be implemented is not limited by the +//! bindgen-generated code. For example, [`Drop`] can be added without +//! disabling [`Copy`] on the underlying bindgen type +//! +//! * [`Send`] and [`Sync`] implementations can be controlled by the wrapper +//! type rather than being automatically derived from the C struct's layout +//! +//! * Methods can be implemented in a separate crate from the bindgen-generated +//! bindings +//! +//! * [`Debug`](std::fmt::Debug) and [`Display`](std::fmt::Display) +//! implementations can be customized to be more readable than the raw C +//! struct representation +//! +//! The [`Opaque`] type does not include BQL validation; it is possible to +//! assert in the code that the right lock is taken, to use it together +//! with a custom lock guard type, or to let C code take the lock, as +//! appropriate. It is also possible to use it with non-thread-safe +//! types, since by default (unlike [`BqlCell`] and [`BqlRefCell`] +//! it is neither `Sync` nor `Send`. +//! +//! While [`Opaque`] is necessary for C interop, it should be used sparingly +//! and only at FFI boundaries. For QEMU-specific types that need interior +//! mutability, prefer [`BqlCell`] or [`BqlRefCell`]. use std::{ cell::{Cell, UnsafeCell}, cmp::Ordering, fmt, - marker::PhantomData, - mem, + marker::{PhantomData, PhantomPinned}, + mem::{self, MaybeUninit}, ops::{Deref, DerefMut}, ptr::NonNull, }; @@ -840,3 +911,122 @@ impl fmt::Display for BqlRefMut<'_, T> { (**self).fmt(f) } } + +/// Stores an opaque value that is shared with C code. +/// +/// Often, C structs can changed when calling a C function even if they are +/// behind a shared Rust reference, or they can be initialized lazily and have +/// invalid bit patterns (e.g. `3` for a [`bool`]). This goes against Rust's +/// strict aliasing rules, which normally prevent mutation through shared +/// references. +/// +/// Wrapping the struct with `Opaque` ensures that the Rust compiler does not +/// assume the usual constraints that Rust structs require, and allows using +/// shared references on the Rust side. +/// +/// `Opaque` is `#[repr(transparent)]`, so that it matches the memory layout +/// of `T`. +#[repr(transparent)] +pub struct Opaque { + value: UnsafeCell>, + // PhantomPinned also allows multiple references to the `Opaque`, i.e. + // one `&mut Opaque` can coexist with a `&mut T` or any number of `&T`; + // see https://docs.rs/pinned-aliasable/latest/pinned_aliasable/. + _pin: PhantomPinned, +} + +impl Opaque { + /// Creates a new shared reference from a C pointer + /// + /// # Safety + /// + /// The pointer must be valid, though it need not point to a valid value. + pub unsafe fn from_raw<'a>(ptr: *mut T) -> &'a Self { + let ptr = NonNull::new(ptr).unwrap().cast::(); + // SAFETY: Self is a transparent wrapper over T + unsafe { ptr.as_ref() } + } + + /// Creates a new opaque object with uninitialized contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be initialized and pinned before + /// calling them. + #[allow(clippy::missing_const_for_fn)] + pub unsafe fn uninit() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::uninit()), + _pin: PhantomPinned, + } + } + + /// Creates a new opaque object with zeroed contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be pinned (and possibly initialized) + /// before calling them. + #[allow(clippy::missing_const_for_fn)] + pub unsafe fn zeroed() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::zeroed()), + _pin: PhantomPinned, + } + } + + /// Returns a raw mutable pointer to the opaque data. + pub const fn as_mut_ptr(&self) -> *mut T { + UnsafeCell::get(&self.value).cast() + } + + /// Returns a raw pointer to the opaque data. + pub const fn as_ptr(&self) -> *const T { + self.as_mut_ptr() as *const _ + } + + /// Returns a raw pointer to the opaque data that can be passed to a + /// C function as `void *`. + pub const fn as_void_ptr(&self) -> *mut std::ffi::c_void { + UnsafeCell::get(&self.value).cast() + } + + /// Converts a raw pointer to the wrapped type. + pub const fn raw_get(slot: *mut Self) -> *mut T { + // Compare with Linux's raw_get method, which goes through an UnsafeCell + // because it takes a *const Self instead. + slot.cast() + } +} + +impl fmt::Debug for Opaque { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut name: String = "Opaque<".to_string(); + name += std::any::type_name::(); + name += ">"; + f.debug_tuple(&name).field(&self.as_ptr()).finish() + } +} + +impl Opaque { + /// Creates a new opaque object with default contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be pinned before calling them. + pub unsafe fn new() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::new(T::default())), + _pin: PhantomPinned, + } + } +} From f07a5674cf97b8473e5d06d7b1df9b51e97d553f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 11:13:53 +0100 Subject: [PATCH 0558/1179] rust: qemu_api_macros: add Wrapper derive macro Add a derive macro that makes it easy to peel off all the layers of specialness (UnsafeCell, MaybeUninit, etc.) and just get a pointer to the wrapped type; and likewise add them back starting from a *mut. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 8 +-- rust/qemu-api-macros/src/lib.rs | 90 ++++++++++++++++++++++++++++++++- rust/qemu-api/meson.build | 7 +-- rust/qemu-api/src/cell.rs | 45 +++++++++++++++++ 4 files changed, 141 insertions(+), 9 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 784c3e40bdcf..88bdec1eb28f 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -315,11 +315,13 @@ a raw pointer, for use in calls to C functions. It can be used for example as follows:: #[repr(transparent)] - #[derive(Debug)] + #[derive(Debug, qemu_api_macros::Wrapper)] pub struct Object(Opaque); -The bindings will then manually check for the big QEMU lock with -assertions, which allows the wrapper to be declared thread-safe:: +where the special ``derive`` macro provides useful methods such as +``from_raw``, ``as_ptr`, ``as_mut_ptr`` and ``raw_get``. The bindings will +then manually check for the big QEMU lock with assertions, which allows +the wrapper to be declared thread-safe:: unsafe impl Send for Object {} unsafe impl Sync for Object {} diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 7ec218202f41..eda0d46d1225 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -6,7 +6,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, - DeriveInput, Field, Fields, Ident, Meta, Path, Token, Type, Variant, Visibility, + DeriveInput, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Type, Variant, Visibility, }; mod utils; @@ -33,6 +33,35 @@ fn get_fields<'a>( } } +fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, MacroError> { + if let Data::Struct(s) = &input.data { + let unnamed = match &s.fields { + Fields::Unnamed(FieldsUnnamed { + unnamed: ref fields, + .. + }) => fields, + _ => { + return Err(MacroError::Message( + format!("Tuple struct required for {}", msg), + s.fields.span(), + )) + } + }; + if unnamed.len() != 1 { + return Err(MacroError::Message( + format!("A single field is required for {}", msg), + s.fields.span(), + )); + } + Ok(&unnamed[0]) + } else { + Err(MacroError::Message( + format!("Struct required for {}", msg), + input.ident.span(), + )) + } +} + fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { let expected = parse_quote! { #[repr(C)] }; @@ -46,6 +75,19 @@ fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { } } +fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { + let expected = parse_quote! { #[repr(transparent)] }; + + if input.attrs.iter().any(|attr| attr == &expected) { + Ok(()) + } else { + Err(MacroError::Message( + format!("#[repr(transparent)] required for {}", msg), + input.ident.span(), + )) + } +} + fn derive_object_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(Object)]")?; @@ -72,6 +114,52 @@ pub fn derive_object(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } +fn derive_opaque_or_error(input: DeriveInput) -> Result { + is_transparent_repr(&input, "#[derive(Wrapper)]")?; + + let name = &input.ident; + let field = &get_unnamed_field(&input, "#[derive(Wrapper)]")?; + let typ = &field.ty; + + // TODO: how to add "::qemu_api"? For now, this is only used in the + // qemu_api crate so it's not a problem. + Ok(quote! { + unsafe impl crate::cell::Wrapper for #name { + type Wrapped = <#typ as crate::cell::Wrapper>::Wrapped; + } + impl #name { + pub unsafe fn from_raw<'a>(ptr: *mut ::Wrapped) -> &'a Self { + let ptr = ::std::ptr::NonNull::new(ptr).unwrap().cast::(); + unsafe { ptr.as_ref() } + } + + pub const fn as_mut_ptr(&self) -> *mut ::Wrapped { + self.0.as_mut_ptr() + } + + pub const fn as_ptr(&self) -> *const ::Wrapped { + self.0.as_ptr() + } + + pub const fn as_void_ptr(&self) -> *mut ::core::ffi::c_void { + self.0.as_void_ptr() + } + + pub const fn raw_get(slot: *mut Self) -> *mut ::Wrapped { + slot.cast() + } + } + }) +} + +#[proc_macro_derive(Wrapper)] +pub fn derive_opaque(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = derive_opaque_or_error(input).unwrap_or_else(Into::into); + + TokenStream::from(expanded) +} + #[rustfmt::skip::macros(quote)] fn derive_offsets_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(offsets)]")?; diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index bcf1cf780f38..6e52c4bad745 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -42,16 +42,13 @@ _qemu_api_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', rust_args: _qemu_api_cfg, - dependencies: libc_dep, + dependencies: [libc_dep, qemu_api_macros], ) rust.test('rust-qemu-api-tests', _qemu_api_rs, suite: ['unit', 'rust']) -qemu_api = declare_dependency( - link_with: _qemu_api_rs, - dependencies: qemu_api_macros, -) +qemu_api = declare_dependency(link_with: _qemu_api_rs) # Rust executables do not support objects, so add an intermediate step. rust_qemu_api_objs = static_library( diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index 2889abb868ee..448638e8967e 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -1030,3 +1030,48 @@ impl Opaque { } } } + +/// Annotates [`Self`] as a transparent wrapper for another type. +/// +/// Usually defined via the [`qemu_api_macros::Wrapper`] derive macro. +/// +/// # Examples +/// +/// ``` +/// # use std::mem::ManuallyDrop; +/// # use qemu_api::cell::Wrapper; +/// #[repr(transparent)] +/// pub struct Example { +/// inner: ManuallyDrop, +/// } +/// +/// unsafe impl Wrapper for Example { +/// type Wrapped = String; +/// } +/// ``` +/// +/// # Safety +/// +/// `Self` must be a `#[repr(transparent)]` wrapper for the `Wrapped` type, +/// whether directly or indirectly. +/// +/// # Methods +/// +/// By convention, types that implement Wrapper also implement the following +/// methods: +/// +/// ```ignore +/// pub const unsafe fn from_raw<'a>(value: *mut Self::Wrapped) -> &'a Self; +/// pub const unsafe fn as_mut_ptr(&self) -> *mut Self::Wrapped; +/// pub const unsafe fn as_ptr(&self) -> *const Self::Wrapped; +/// pub const unsafe fn raw_get(slot: *mut Self) -> *const Self::Wrapped; +/// ``` +/// +/// They are not defined here to allow them to be `const`. +pub unsafe trait Wrapper { + type Wrapped; +} + +unsafe impl Wrapper for Opaque { + type Wrapped = T; +} From d7f5ae8b30cc9652a4ddcfeb52076f5aef6d78b6 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 25 Feb 2025 10:28:56 +0100 Subject: [PATCH 0559/1179] rust: vmstate: add std::pin::Pin as transparent wrapper Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/vmstate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 24a4dc81e7fd..1e7ba531e2a1 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -330,6 +330,7 @@ macro_rules! impl_vmstate_transparent { impl_vmstate_transparent!(std::cell::Cell where T: VMState); impl_vmstate_transparent!(std::cell::UnsafeCell where T: VMState); +impl_vmstate_transparent!(std::pin::Pin where T: VMState); impl_vmstate_transparent!(crate::cell::BqlCell where T: VMState); impl_vmstate_transparent!(crate::cell::BqlRefCell where T: VMState); From e8dc87fef2677dc286b3fe72e04d1b763cf98fef Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 3 Mar 2025 16:27:08 +0100 Subject: [PATCH 0560/1179] rust: hpet: embed Timer without the Option and Box indirection This simplifies things for migration, since Option> does not implement VMState. This also shows a soundness issue because Timer::new() will leave a NULL timer list pointer, which can then be dereferenced by Timer::modify(). It will be fixed shortly. Signed-off-by: Paolo Bonzini --- rust/hw/timer/hpet/src/hpet.rs | 61 ++++++++++++++++------------------ 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index be27eb0eff40..02c81ae048fd 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -151,14 +151,14 @@ fn timer_handler(timer_cell: &BqlRefCell) { /// HPET Timer Abstraction #[repr(C)] -#[derive(Debug, Default, qemu_api_macros::offsets)] +#[derive(Debug, qemu_api_macros::offsets)] pub struct HPETTimer { /// timer N index within the timer block (`HPETState`) #[doc(alias = "tn")] index: usize, - qemu_timer: Option>, + qemu_timer: Timer, /// timer block abstraction containing this timer - state: Option>, + state: NonNull, // Memory-mapped, software visible timer registers /// Timer N Configuration and Capability Register @@ -181,32 +181,34 @@ pub struct HPETTimer { } impl HPETTimer { - fn init(&mut self, index: usize, state_ptr: *mut HPETState) -> &mut Self { - *self = HPETTimer::default(); - self.index = index; - self.state = NonNull::new(state_ptr); - self - } - - fn init_timer_with_state(&mut self) { - self.qemu_timer = Some(Box::new({ - let mut t = Timer::new(); - t.init_full( - None, - CLOCK_VIRTUAL, - Timer::NS, - 0, - timer_handler, - &self.get_state().timers[self.index], - ); - t - })); + fn init(&mut self, index: usize, state: &HPETState) { + *self = HPETTimer { + index, + qemu_timer: Timer::new(), + state: NonNull::new(state as *const _ as *mut _).unwrap(), + config: 0, + cmp: 0, + fsb: 0, + cmp64: 0, + period: 0, + wrap_flag: 0, + last: 0, + }; + + self.qemu_timer.init_full( + None, + CLOCK_VIRTUAL, + Timer::NS, + 0, + timer_handler, + &state.timers[self.index], + ) } fn get_state(&self) -> &HPETState { // SAFETY: // the pointer is convertible to a reference - unsafe { self.state.unwrap().as_ref() } + unsafe { self.state.as_ref() } } fn is_int_active(&self) -> bool { @@ -330,7 +332,7 @@ impl HPETTimer { } self.last = ns; - self.qemu_timer.as_ref().unwrap().modify(self.last); + self.qemu_timer.modify(self.last); } fn set_timer(&mut self) { @@ -353,7 +355,7 @@ impl HPETTimer { fn del_timer(&mut self) { // Just remove the timer from the timer_list without destroying // this timer instance. - self.qemu_timer.as_ref().unwrap().delete(); + self.qemu_timer.delete(); if self.is_int_active() { // For level-triggered interrupt, this leaves interrupt status @@ -581,13 +583,8 @@ impl HPETState { } fn init_timer(&self) { - let raw_ptr: *mut HPETState = self as *const HPETState as *mut HPETState; - for (index, timer) in self.timers.iter().enumerate() { - timer - .borrow_mut() - .init(index, raw_ptr) - .init_timer_with_state(); + timer.borrow_mut().init(index, self); } } From a32b239699377f09bba08b2e8ae0d167c1488b1f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:06:13 +0100 Subject: [PATCH 0561/1179] rust: timer: wrap QEMUTimer with Opaque<> and express pinning requirements Timers must be pinned in memory, because modify() stores a pointer to them in the TimerList. To express this requirement, change init_full() to take a pinned reference. Because the only way to obtain a Timer is through Timer::new(), which is unsafe, modify() can assume that the timer it got was later initialized; and because the initialization takes a Pin<&mut Timer> modify() can assume that the timer is pinned. In the future the pinning requirement will be expressed through the pin_init crate instead. Note that Timer is a bit different from other users of Opaque, in that it is created in Rust code rather than C code. This is why it has to use the unsafe constructors provided by Opaque; and in fact Timer::new() is also unsafe, because it leaves it to the caller to invoke init_full() before modify(). Without a call to init_full(), modify() will cause a NULL pointer dereference. An alternative could be to combine new() + init_full() by returning a pinned box; however, using a reference makes it easier to express the requirement that the opaque outlives the timer. Signed-off-by: Paolo Bonzini --- meson.build | 7 ----- rust/hw/timer/hpet/src/hpet.rs | 10 ++++++-- rust/qemu-api/src/timer.rs | 47 ++++++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/meson.build b/meson.build index 67ec2b78319b..6da4eb317c28 100644 --- a/meson.build +++ b/meson.build @@ -4100,13 +4100,6 @@ if have_rust foreach enum : c_bitfields bindgen_args += ['--bitfield-enum', enum] endforeach - c_nocopy = [ - 'QEMUTimer', - ] - # Used to customize Drop trait - foreach struct : c_nocopy - bindgen_args += ['--no-copy', struct] - endforeach # TODO: Remove this comment when the clang/libclang mismatch issue is solved. # diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index 02c81ae048fd..3d3d6ef8eeca 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -4,6 +4,7 @@ use std::{ ffi::CStr, + pin::Pin, ptr::{addr_of_mut, null_mut, NonNull}, slice::from_ref, }; @@ -184,7 +185,9 @@ impl HPETTimer { fn init(&mut self, index: usize, state: &HPETState) { *self = HPETTimer { index, - qemu_timer: Timer::new(), + // SAFETY: the HPETTimer will only be used after the timer + // is initialized below. + qemu_timer: unsafe { Timer::new() }, state: NonNull::new(state as *const _ as *mut _).unwrap(), config: 0, cmp: 0, @@ -195,7 +198,10 @@ impl HPETTimer { last: 0, }; - self.qemu_timer.init_full( + // SAFETY: HPETTimer is only used as part of HPETState, which is + // always pinned. + let qemu_timer = unsafe { Pin::new_unchecked(&mut self.qemu_timer) }; + qemu_timer.init_full( None, CLOCK_VIRTUAL, Timer::NS, diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs index a593538917ac..f0b04ef95d7e 100644 --- a/rust/qemu-api/src/timer.rs +++ b/rust/qemu-api/src/timer.rs @@ -2,31 +2,51 @@ // Author(s): Zhao Liu // SPDX-License-Identifier: GPL-2.0-or-later -use std::os::raw::{c_int, c_void}; +use std::{ + os::raw::{c_int, c_void}, + pin::Pin, +}; use crate::{ bindings::{self, qemu_clock_get_ns, timer_del, timer_init_full, timer_mod, QEMUClockType}, callbacks::FnCall, + cell::Opaque, }; -pub type Timer = bindings::QEMUTimer; -pub type TimerListGroup = bindings::QEMUTimerListGroup; +/// A safe wrapper around [`bindings::QEMUTimer`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct Timer(Opaque); + +unsafe impl Send for Timer {} +unsafe impl Sync for Timer {} + +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct TimerListGroup(Opaque); + +unsafe impl Send for TimerListGroup {} +unsafe impl Sync for TimerListGroup {} impl Timer { pub const MS: u32 = bindings::SCALE_MS; pub const US: u32 = bindings::SCALE_US; pub const NS: u32 = bindings::SCALE_NS; - pub fn new() -> Self { - Default::default() - } - - const fn as_mut_ptr(&self) -> *mut Self { - self as *const Timer as *mut _ + /// Create a `Timer` struct without initializing it. + /// + /// # Safety + /// + /// The timer must be initialized before it is armed with + /// [`modify`](Self::modify). + pub unsafe fn new() -> Self { + // SAFETY: requirements relayed to callers of Timer::new + Self(unsafe { Opaque::zeroed() }) } + /// Create a new timer with the given attributes. pub fn init_full<'timer, 'opaque: 'timer, T, F>( - &'timer mut self, + self: Pin<&'timer mut Self>, timer_list_group: Option<&TimerListGroup>, clk_type: ClockType, scale: u32, @@ -51,7 +71,7 @@ impl Timer { // SAFETY: the opaque outlives the timer unsafe { timer_init_full( - self, + self.as_mut_ptr(), if let Some(g) = timer_list_group { g as *const TimerListGroup as *mut _ } else { @@ -67,14 +87,19 @@ impl Timer { } pub fn modify(&self, expire_time: u64) { + // SAFETY: the only way to obtain a Timer safely is via methods that + // take a Pin<&mut Self>, therefore the timer is pinned unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) } } pub fn delete(&self) { + // SAFETY: the only way to obtain a Timer safely is via methods that + // take a Pin<&mut Self>, therefore the timer is pinned unsafe { timer_del(self.as_mut_ptr()) } } } +// FIXME: use something like PinnedDrop from the pinned_init crate impl Drop for Timer { fn drop(&mut self) { self.delete() From 9c9a6a889cb3589779b019a343892aa0e9bdb254 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:05:59 +0100 Subject: [PATCH 0562/1179] rust: irq: wrap IRQState with Opaque<> Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/irq.rs | 15 ++++++++++----- rust/qemu-api/src/sysbus.rs | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 34c19263c233..1222d4fde30c 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -8,10 +8,16 @@ use std::{ffi::CStr, marker::PhantomData, os::raw::c_int, ptr}; use crate::{ bindings::{self, qemu_set_irq}, + cell::Opaque, prelude::*, qom::ObjectClass, }; +/// An opaque wrapper around [`bindings::IRQState`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct IRQState(Opaque); + /// Interrupt sources are used by devices to pass changes to a value (typically /// a boolean). The interrupt sink is usually an interrupt controller or /// GPIO controller. @@ -21,8 +27,7 @@ use crate::{ /// method sends a `true` value to the sink. If the guest has to see a /// different polarity, that change is performed by the board between the /// device and the interrupt controller. -pub type IRQState = bindings::IRQState; - +/// /// Interrupts are implemented as a pointer to the interrupt "sink", which has /// type [`IRQState`]. A device exposes its source as a QOM link property using /// a function such as [`SysBusDeviceMethods::init_irq`], and @@ -40,7 +45,7 @@ pub struct InterruptSource where c_int: From, { - cell: BqlCell<*mut IRQState>, + cell: BqlCell<*mut bindings::IRQState>, _marker: PhantomData, } @@ -79,11 +84,11 @@ where } } - pub(crate) const fn as_ptr(&self) -> *mut *mut IRQState { + pub(crate) const fn as_ptr(&self) -> *mut *mut bindings::IRQState { self.cell.as_ptr() } - pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut IRQState { + pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut bindings::IRQState { assert!(!slice.is_empty()); slice[0].as_ptr() } diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 04821a2b9b38..48803a655f9a 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -79,6 +79,7 @@ where fn connect_irq(&self, id: u32, irq: &Owned) { assert!(bql_locked()); let id: i32 = id.try_into().unwrap(); + let irq: &IRQState = irq; unsafe { bindings::sysbus_connect_irq(self.as_mut_ptr(), id, irq.as_mut_ptr()); } From 7fb4a99df17c8ae5f5e00d643042b9d95477a426 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:09:53 +0100 Subject: [PATCH 0563/1179] rust: qom: wrap Object with Opaque<> Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 3 --- rust/qemu-api/src/memory.rs | 2 +- rust/qemu-api/src/qdev.rs | 6 +++--- rust/qemu-api/src/qom.rs | 35 ++++++++++++++++++++++------------- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index d2868639ff60..be6dd68c09c5 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -46,9 +46,6 @@ unsafe impl Sync for MemoryRegion {} unsafe impl Send for ObjectClass {} unsafe impl Sync for ObjectClass {} -unsafe impl Send for Object {} -unsafe impl Sync for Object {} - unsafe impl Send for SysBusDevice {} unsafe impl Sync for SysBusDevice {} diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs index 682951ab44e1..713c494ca2e9 100644 --- a/rust/qemu-api/src/memory.rs +++ b/rust/qemu-api/src/memory.rs @@ -157,7 +157,7 @@ impl MemoryRegion { let cstr = CString::new(name).unwrap(); memory_region_init_io( slot, - owner.cast::(), + owner.cast::(), ops, owner.cast::(), cstr.as_ptr(), diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index c136457090cf..1a4d1f387626 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -52,7 +52,7 @@ pub trait ResettablePhasesImpl { /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_resettable_enter_fn( - obj: *mut Object, + obj: *mut bindings::Object, typ: ResetType, ) { let state = NonNull::new(obj).unwrap().cast::(); @@ -65,7 +65,7 @@ unsafe extern "C" fn rust_resettable_enter_fn( /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_resettable_hold_fn( - obj: *mut Object, + obj: *mut bindings::Object, typ: ResetType, ) { let state = NonNull::new(obj).unwrap().cast::(); @@ -78,7 +78,7 @@ unsafe extern "C" fn rust_resettable_hold_fn( /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_resettable_exit_fn( - obj: *mut Object, + obj: *mut bindings::Object, typ: ResetType, ) { let state = NonNull::new(obj).unwrap().cast::(); diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 5488643a2fdf..2defbd235160 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -101,16 +101,24 @@ use std::{ ptr::NonNull, }; -pub use bindings::{Object, ObjectClass}; +pub use bindings::ObjectClass; use crate::{ bindings::{ self, object_class_dynamic_cast, object_dynamic_cast, object_get_class, object_get_typename, object_new, object_ref, object_unref, TypeInfo, }, - cell::bql_locked, + cell::{bql_locked, Opaque}, }; +/// A safe wrapper around [`bindings::Object`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct Object(Opaque); + +unsafe impl Send for Object {} +unsafe impl Sync for Object {} + /// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct /// or indirect parent of `Self`). /// @@ -199,7 +207,7 @@ impl fmt::Display for ParentField { } } -unsafe extern "C" fn rust_instance_init(obj: *mut Object) { +unsafe extern "C" fn rust_instance_init(obj: *mut bindings::Object) { let mut state = NonNull::new(obj).unwrap().cast::(); // SAFETY: obj is an instance of T, since rust_instance_init // is called from QOM core as the instance_init function @@ -209,7 +217,7 @@ unsafe extern "C" fn rust_instance_init(obj: *mut Object) { } } -unsafe extern "C" fn rust_instance_post_init(obj: *mut Object) { +unsafe extern "C" fn rust_instance_post_init(obj: *mut bindings::Object) { let state = NonNull::new(obj).unwrap().cast::(); // SAFETY: obj is an instance of T, since rust_instance_post_init // is called from QOM core as the instance_post_init function @@ -230,7 +238,7 @@ unsafe extern "C" fn rust_class_init( ::CLASS_INIT(unsafe { klass.as_mut() }) } -unsafe extern "C" fn drop_object(obj: *mut Object) { +unsafe extern "C" fn drop_object(obj: *mut bindings::Object) { // SAFETY: obj is an instance of T, since drop_object is called // from the QOM core function object_deinit() as the instance_finalize // function for class T. Note that while object_deinit() will drop the @@ -280,14 +288,14 @@ pub unsafe trait ObjectType: Sized { /// Return the receiver as an Object. This is always safe, even /// if this type represents an interface. fn as_object(&self) -> &Object { - unsafe { &*self.as_object_ptr() } + unsafe { &*self.as_ptr().cast() } } /// Return the receiver as a const raw pointer to Object. /// This is preferrable to `as_object_mut_ptr()` if a C /// function only needs a `const Object *`. - fn as_object_ptr(&self) -> *const Object { - self.as_ptr().cast() + fn as_object_ptr(&self) -> *const bindings::Object { + self.as_object().as_ptr() } /// Return the receiver as a mutable raw pointer to Object. @@ -297,8 +305,8 @@ pub unsafe trait ObjectType: Sized { /// This cast is always safe, but because the result is mutable /// and the incoming reference is not, this should only be used /// for calls to C functions, and only if needed. - unsafe fn as_object_mut_ptr(&self) -> *mut Object { - self.as_object_ptr() as *mut _ + unsafe fn as_object_mut_ptr(&self) -> *mut bindings::Object { + self.as_object().as_mut_ptr() } } @@ -621,7 +629,7 @@ pub trait ObjectImpl: ObjectType + IsA { /// We expect the FFI user of this function to pass a valid pointer that /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_unparent_fn(dev: *mut Object) { +unsafe extern "C" fn rust_unparent_fn(dev: *mut bindings::Object) { let state = NonNull::new(dev).unwrap().cast::(); T::UNPARENT.unwrap()(unsafe { state.as_ref() }); } @@ -796,8 +804,9 @@ pub trait ObjectClassMethods: IsA { // SAFETY: the object created by object_new is allocated on // the heap and has a reference count of 1 unsafe { - let obj = &*object_new(Self::TYPE_NAME.as_ptr()); - Owned::from_raw(obj.unsafe_cast::()) + let raw_obj = object_new(Self::TYPE_NAME.as_ptr()); + let obj = Object::from_raw(raw_obj).unsafe_cast::(); + Owned::from_raw(obj) } } } From fc22d650d54363b8f2bad56aea1dde773f600067 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 11:45:25 +0100 Subject: [PATCH 0564/1179] rust: qdev: wrap Clock and DeviceState with Opaque<> Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 6 ---- rust/qemu-api/src/qdev.rs | 68 ++++++++++++++++++++++++----------- rust/qemu-api/src/vmstate.rs | 2 +- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index be6dd68c09c5..6e70a75a0e6f 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -34,12 +34,6 @@ unsafe impl Sync for CharBackend {} unsafe impl Send for Chardev {} unsafe impl Sync for Chardev {} -unsafe impl Send for Clock {} -unsafe impl Sync for Clock {} - -unsafe impl Send for DeviceState {} -unsafe impl Sync for DeviceState {} - unsafe impl Send for MemoryRegion {} unsafe impl Sync for MemoryRegion {} diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 1a4d1f387626..1c4a67b57286 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -10,12 +10,12 @@ use std::{ ptr::NonNull, }; -pub use bindings::{Clock, ClockEvent, DeviceClass, DeviceState, Property, ResetType}; +pub use bindings::{ClockEvent, DeviceClass, Property, ResetType}; use crate::{ bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, Error, ResettableClass}, callbacks::FnCall, - cell::bql_locked, + cell::{bql_locked, Opaque}, chardev::Chardev, irq::InterruptSource, prelude::*, @@ -23,6 +23,22 @@ use crate::{ vmstate::VMStateDescription, }; +/// A safe wrapper around [`bindings::Clock`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct Clock(Opaque); + +unsafe impl Send for Clock {} +unsafe impl Sync for Clock {} + +/// A safe wrapper around [`bindings::DeviceState`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct DeviceState(Opaque); + +unsafe impl Send for DeviceState {} +unsafe impl Sync for DeviceState {} + /// Trait providing the contents of the `ResettablePhases` struct, /// which is part of the QOM `Resettable` interface. pub trait ResettablePhasesImpl { @@ -117,7 +133,10 @@ pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA { /// We expect the FFI user of this function to pass a valid pointer that /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_realize_fn(dev: *mut DeviceState, _errp: *mut *mut Error) { +unsafe extern "C" fn rust_realize_fn( + dev: *mut bindings::DeviceState, + _errp: *mut *mut Error, +) { let state = NonNull::new(dev).unwrap().cast::(); T::REALIZE.unwrap()(unsafe { state.as_ref() }); } @@ -251,7 +270,7 @@ where events: ClockEvent, ) -> Owned { fn do_init_clock_in( - dev: *mut DeviceState, + dev: &DeviceState, name: &str, cb: Option, events: ClockEvent, @@ -265,14 +284,15 @@ where unsafe { let cstr = CString::new(name).unwrap(); let clk = bindings::qdev_init_clock_in( - dev, + dev.as_mut_ptr(), cstr.as_ptr(), cb, - dev.cast::(), + dev.as_void_ptr(), events.0, ); - Owned::from(&*clk) + let clk: &Clock = Clock::from_raw(clk); + Owned::from(clk) } } @@ -289,7 +309,7 @@ where None }; - do_init_clock_in(self.as_mut_ptr(), name, cb, events) + do_init_clock_in(self.upcast(), name, cb, events) } /// Add an output clock named `name`. @@ -304,9 +324,10 @@ where fn init_clock_out(&self, name: &str) -> Owned { unsafe { let cstr = CString::new(name).unwrap(); - let clk = bindings::qdev_init_clock_out(self.as_mut_ptr(), cstr.as_ptr()); + let clk = bindings::qdev_init_clock_out(self.upcast().as_mut_ptr(), cstr.as_ptr()); - Owned::from(&*clk) + let clk: &Clock = Clock::from_raw(clk); + Owned::from(clk) } } @@ -314,7 +335,11 @@ where assert!(bql_locked()); let c_propname = CString::new(propname).unwrap(); unsafe { - bindings::qdev_prop_set_chr(self.as_mut_ptr(), c_propname.as_ptr(), chr.as_mut_ptr()); + bindings::qdev_prop_set_chr( + self.upcast().as_mut_ptr(), + c_propname.as_ptr(), + chr.as_mut_ptr(), + ); } } @@ -323,8 +348,17 @@ where num_lines: u32, _cb: F, ) { - let _: () = F::ASSERT_IS_SOME; + fn do_init_gpio_in( + dev: &DeviceState, + num_lines: u32, + gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int), + ) { + unsafe { + qdev_init_gpio_in(dev.as_mut_ptr(), Some(gpio_in_cb), num_lines as c_int); + } + } + let _: () = F::ASSERT_IS_SOME; unsafe extern "C" fn rust_irq_handler FnCall<(&'a T, u32, u32)>>( opaque: *mut c_void, line: c_int, @@ -337,19 +371,13 @@ where let gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int) = rust_irq_handler::; - unsafe { - qdev_init_gpio_in( - self.as_mut_ptr::(), - Some(gpio_in_cb), - num_lines as c_int, - ); - } + do_init_gpio_in(self.upcast(), num_lines, gpio_in_cb); } fn init_gpio_out(&self, pins: &[InterruptSource]) { unsafe { qdev_init_gpio_out( - self.as_mut_ptr::(), + self.upcast().as_mut_ptr(), InterruptSource::slice_as_ptr(pins), pins.len() as c_int, ); diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 1e7ba531e2a1..f0510ae769d1 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -470,7 +470,7 @@ macro_rules! vmstate_clock { $crate::assert_field_type!( $struct_name, $field_name, - $crate::qom::Owned<$crate::bindings::Clock> + $crate::qom::Owned<$crate::qdev::Clock> ); $crate::offset_of!($struct_name, $field_name) }, From 09fda8f5dc925ba059aca539163d16796af6a299 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 25 Feb 2025 10:06:20 +0100 Subject: [PATCH 0565/1179] rust: hpet: do not access fields of SysBusDevice Fields of SysBusDevice must only be accessed with the BQL taken. Add a wrapper that verifies that. Signed-off-by: Paolo Bonzini --- rust/hw/timer/hpet/src/hpet.rs | 4 +--- rust/qemu-api/src/sysbus.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index 3d3d6ef8eeca..d989360ede89 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -730,8 +730,6 @@ impl HPETState { } fn reset_hold(&self, _type: ResetType) { - let sbd = self.upcast::(); - for timer in self.timers.iter().take(self.num_timers.get()) { timer.borrow_mut().reset(); } @@ -744,7 +742,7 @@ impl HPETState { HPETFwConfig::update_hpet_cfg( self.hpet_id.get(), self.capability.get() as u32, - sbd.mmio[0].addr, + self.mmio_addr(0).unwrap(), ); // to document that the RTC lowers its output on reset as well diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 48803a655f9a..0790576d446a 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -64,6 +64,18 @@ where } } + // TODO: do we want a type like GuestAddress here? + fn mmio_addr(&self, id: u32) -> Option { + assert!(bql_locked()); + let sbd = self.upcast(); + let id: usize = id.try_into().unwrap(); + if sbd.mmio[id].memory.is_null() { + None + } else { + Some(sbd.mmio[id].addr) + } + } + // TODO: do we want a type like GuestAddress here? fn mmio_map(&self, id: u32, addr: u64) { assert!(bql_locked()); From f4751c7a42b194eb4166c7f3f294bf89c3e23cd9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 11:45:36 +0100 Subject: [PATCH 0566/1179] rust: sysbus: wrap SysBusDevice with Opaque<> Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 3 --- rust/qemu-api/src/sysbus.rs | 29 +++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index 6e70a75a0e6f..b791ca6d87fe 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -40,9 +40,6 @@ unsafe impl Sync for MemoryRegion {} unsafe impl Send for ObjectClass {} unsafe impl Sync for ObjectClass {} -unsafe impl Send for SysBusDevice {} -unsafe impl Sync for SysBusDevice {} - // SAFETY: this is a pure data struct unsafe impl Send for CoalescedMemoryRange {} unsafe impl Sync for CoalescedMemoryRange {} diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 0790576d446a..e92502a8fe65 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -6,11 +6,11 @@ use std::{ffi::CStr, ptr::addr_of_mut}; -pub use bindings::{SysBusDevice, SysBusDeviceClass}; +pub use bindings::SysBusDeviceClass; use crate::{ bindings, - cell::bql_locked, + cell::{bql_locked, Opaque}, irq::{IRQState, InterruptSource}, memory::MemoryRegion, prelude::*, @@ -18,6 +18,14 @@ use crate::{ qom::Owned, }; +/// A safe wrapper around [`bindings::SysBusDevice`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct SysBusDevice(Opaque); + +unsafe impl Send for SysBusDevice {} +unsafe impl Sync for SysBusDevice {} + unsafe impl ObjectType for SysBusDevice { type Class = SysBusDeviceClass; const TYPE_NAME: &'static CStr = @@ -49,7 +57,7 @@ where fn init_mmio(&self, iomem: &MemoryRegion) { assert!(bql_locked()); unsafe { - bindings::sysbus_init_mmio(self.as_mut_ptr(), iomem.as_mut_ptr()); + bindings::sysbus_init_mmio(self.upcast().as_mut_ptr(), iomem.as_mut_ptr()); } } @@ -60,14 +68,16 @@ where fn init_irq(&self, irq: &InterruptSource) { assert!(bql_locked()); unsafe { - bindings::sysbus_init_irq(self.as_mut_ptr(), irq.as_ptr()); + bindings::sysbus_init_irq(self.upcast().as_mut_ptr(), irq.as_ptr()); } } // TODO: do we want a type like GuestAddress here? fn mmio_addr(&self, id: u32) -> Option { assert!(bql_locked()); - let sbd = self.upcast(); + // SAFETY: the BQL ensures that no one else writes to sbd.mmio[], and + // the SysBusDevice must be initialized to get an IsA. + let sbd = unsafe { *self.upcast().as_ptr() }; let id: usize = id.try_into().unwrap(); if sbd.mmio[id].memory.is_null() { None @@ -81,7 +91,7 @@ where assert!(bql_locked()); let id: i32 = id.try_into().unwrap(); unsafe { - bindings::sysbus_mmio_map(self.as_mut_ptr(), id, addr); + bindings::sysbus_mmio_map(self.upcast().as_mut_ptr(), id, addr); } } @@ -93,7 +103,7 @@ where let id: i32 = id.try_into().unwrap(); let irq: &IRQState = irq; unsafe { - bindings::sysbus_connect_irq(self.as_mut_ptr(), id, irq.as_mut_ptr()); + bindings::sysbus_connect_irq(self.upcast().as_mut_ptr(), id, irq.as_mut_ptr()); } } @@ -101,7 +111,10 @@ where // TODO: return an Error assert!(bql_locked()); unsafe { - bindings::sysbus_realize(self.as_mut_ptr(), addr_of_mut!(bindings::error_fatal)); + bindings::sysbus_realize( + self.upcast().as_mut_ptr(), + addr_of_mut!(bindings::error_fatal), + ); } } } From af0868cba33aaf327a49d642b6b0ad3ae3f01240 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:34:47 +0100 Subject: [PATCH 0567/1179] rust: memory: wrap MemoryRegion with Opaque<> Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 3 --- rust/qemu-api/src/memory.rs | 35 +++++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index b791ca6d87fe..26cc8de0cf2a 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -34,9 +34,6 @@ unsafe impl Sync for CharBackend {} unsafe impl Send for Chardev {} unsafe impl Sync for Chardev {} -unsafe impl Send for MemoryRegion {} -unsafe impl Sync for MemoryRegion {} - unsafe impl Send for ObjectClass {} unsafe impl Sync for ObjectClass {} diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs index 713c494ca2e9..eff9f09fd7ff 100644 --- a/rust/qemu-api/src/memory.rs +++ b/rust/qemu-api/src/memory.rs @@ -6,9 +6,8 @@ use std::{ ffi::{CStr, CString}, - marker::{PhantomData, PhantomPinned}, + marker::PhantomData, os::raw::{c_uint, c_void}, - ptr::addr_of, }; pub use bindings::{hwaddr, MemTxAttrs}; @@ -16,6 +15,7 @@ pub use bindings::{hwaddr, MemTxAttrs}; use crate::{ bindings::{self, device_endian, memory_region_init_io}, callbacks::FnCall, + cell::Opaque, prelude::*, zeroable::Zeroable, }; @@ -132,13 +132,13 @@ impl Default for MemoryRegionOpsBuilder { } } -/// A safe wrapper around [`bindings::MemoryRegion`]. Compared to the -/// underlying C struct it is marked as pinned because the QOM tree -/// contains a pointer to it. -pub struct MemoryRegion { - inner: bindings::MemoryRegion, - _pin: PhantomPinned, -} +/// A safe wrapper around [`bindings::MemoryRegion`]. +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct MemoryRegion(Opaque); + +unsafe impl Send for MemoryRegion {} +unsafe impl Sync for MemoryRegion {} impl MemoryRegion { // inline to ensure that it is not included in tests, which only @@ -174,13 +174,20 @@ impl MemoryRegion { size: u64, ) { unsafe { - Self::do_init_io(&mut self.inner, owner.cast::(), &ops.0, name, size); + Self::do_init_io( + // self.0.as_mut_ptr() needed because Rust tries to call + // ObjectDeref::as_mut_ptr() on "&mut Self", instead of coercing + // to "&Self" and then calling MemoryRegion::as_mut_ptr(). + // Revisit if/when ObjectCastMut is not needed anymore; it is + // only used in a couple places for initialization. + self.0.as_mut_ptr(), + owner.cast::(), + &ops.0, + name, + size, + ); } } - - pub(crate) const fn as_mut_ptr(&self) -> *mut bindings::MemoryRegion { - addr_of!(self.inner) as *mut _ - } } unsafe impl ObjectType for MemoryRegion { From 48627510a7fed7a045358743e6b869a98931f85e Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:34:47 +0100 Subject: [PATCH 0568/1179] rust: chardev: wrap Chardev with Opaque<> Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 3 --- rust/qemu-api/src/chardev.rs | 8 ++++++-- rust/qemu-api/src/qdev.rs | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index 26cc8de0cf2a..c3f36108bd58 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -31,9 +31,6 @@ unsafe impl Sync for BusState {} unsafe impl Send for CharBackend {} unsafe impl Sync for CharBackend {} -unsafe impl Send for Chardev {} -unsafe impl Sync for Chardev {} - unsafe impl Send for ObjectClass {} unsafe impl Sync for ObjectClass {} diff --git a/rust/qemu-api/src/chardev.rs b/rust/qemu-api/src/chardev.rs index 74cfb634e5fe..a35b9217e904 100644 --- a/rust/qemu-api/src/chardev.rs +++ b/rust/qemu-api/src/chardev.rs @@ -6,9 +6,13 @@ use std::ffi::CStr; -use crate::{bindings, prelude::*}; +use crate::{bindings, cell::Opaque, prelude::*}; + +/// A safe wrapper around [`bindings::Chardev`]. +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct Chardev(Opaque); -pub type Chardev = bindings::Chardev; pub type ChardevClass = bindings::ChardevClass; unsafe impl ObjectType for Chardev { diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 1c4a67b57286..18b4a9ba687d 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -334,6 +334,7 @@ where fn prop_set_chr(&self, propname: &str, chr: &Owned) { assert!(bql_locked()); let c_propname = CString::new(propname).unwrap(); + let chr: &Chardev = chr; unsafe { bindings::qdev_prop_set_chr( self.upcast().as_mut_ptr(), From 2ad011d466697d69f7f9aa84662a6553049f6556 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:36:11 +0100 Subject: [PATCH 0569/1179] rust: bindings: remove more unnecessary Send/Sync impls Send and Sync are now implemented on the opaque wrappers. Remove them from the bindings module, unless the structs are pure data containers and/or have no C functions defined on them. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/bindings.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index c3f36108bd58..3c1d297581ea 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -25,15 +25,11 @@ include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); // SAFETY: these are implemented in C; the bindings need to assert that the // BQL is taken, either directly or via `BqlCell` and `BqlRefCell`. -unsafe impl Send for BusState {} -unsafe impl Sync for BusState {} - +// When bindings for character devices are introduced, this can be +// moved to the Opaque<> wrapper in src/chardev.rs. unsafe impl Send for CharBackend {} unsafe impl Sync for CharBackend {} -unsafe impl Send for ObjectClass {} -unsafe impl Sync for ObjectClass {} - // SAFETY: this is a pure data struct unsafe impl Send for CoalescedMemoryRange {} unsafe impl Sync for CoalescedMemoryRange {} From 2d0050cbe27fed5233561451e6de64af5ecb6571 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:41:14 +0100 Subject: [PATCH 0570/1179] rust: chardev: provide basic bindings to character devices Most of the character device API is pretty simple, with "0 or -errno" or "number of bytes or -errno" as the convention for return codes. Add safe wrappers for the API to the CharBackend bindgen-generated struct. The API is not complete, but it covers the parts that are used by the PL011 device, plus qemu_chr_fe_write which is needed to implement the standard library Write trait. Signed-off-by: Paolo Bonzini --- rust/qemu-api/meson.build | 17 ++- rust/qemu-api/src/chardev.rs | 242 +++++++++++++++++++++++++++++++++- rust/qemu-api/src/zeroable.rs | 1 + 3 files changed, 255 insertions(+), 5 deletions(-) diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 6e52c4bad745..a3f226ccc2a5 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -54,7 +54,19 @@ qemu_api = declare_dependency(link_with: _qemu_api_rs) rust_qemu_api_objs = static_library( 'rust_qemu_api_objs', objects: [libqom.extract_all_objects(recursive: false), - libhwcore.extract_all_objects(recursive: false)]) + libhwcore.extract_all_objects(recursive: false), + libchardev.extract_all_objects(recursive: false), + libcrypto.extract_all_objects(recursive: false), + libauthz.extract_all_objects(recursive: false), + libio.extract_all_objects(recursive: false)]) +rust_qemu_api_deps = declare_dependency( + dependencies: [ + qom_ss.dependencies(), + chardev_ss.dependencies(), + crypto_ss.dependencies(), + authz_ss.dependencies(), + io_ss.dependencies()], + link_whole: [rust_qemu_api_objs, libqemuutil]) test('rust-qemu-api-integration', executable( @@ -63,8 +75,7 @@ test('rust-qemu-api-integration', override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_args: ['--test'], install: false, - dependencies: [qemu_api, qemu_api_macros], - link_whole: [rust_qemu_api_objs, libqemuutil]), + dependencies: [qemu_api, qemu_api_macros, rust_qemu_api_deps]), args: [ '--test', '--test-threads', '1', '--format', 'pretty', diff --git a/rust/qemu-api/src/chardev.rs b/rust/qemu-api/src/chardev.rs index a35b9217e904..11e6c45afaf1 100644 --- a/rust/qemu-api/src/chardev.rs +++ b/rust/qemu-api/src/chardev.rs @@ -3,10 +3,28 @@ // SPDX-License-Identifier: GPL-2.0-or-later //! Bindings for character devices +//! +//! Character devices in QEMU can run under the big QEMU lock or in a separate +//! `GMainContext`. Here we only support the former, because the bindings +//! enforce that the BQL is taken whenever the functions in [`CharBackend`] are +//! called. -use std::ffi::CStr; +use std::{ + ffi::CStr, + fmt::{self, Debug}, + io::{self, ErrorKind, Write}, + marker::PhantomPinned, + os::raw::{c_int, c_void}, + ptr::addr_of_mut, + slice, +}; -use crate::{bindings, cell::Opaque, prelude::*}; +use crate::{ + bindings, + callbacks::FnCall, + cell::{BqlRefMut, Opaque}, + prelude::*, +}; /// A safe wrapper around [`bindings::Chardev`]. #[repr(transparent)] @@ -14,6 +32,226 @@ use crate::{bindings, cell::Opaque, prelude::*}; pub struct Chardev(Opaque); pub type ChardevClass = bindings::ChardevClass; +pub type Event = bindings::QEMUChrEvent; + +/// A safe wrapper around [`bindings::CharBackend`], denoting the character +/// back-end that is used for example by a device. Compared to the +/// underlying C struct it adds BQL protection, and is marked as pinned +/// because the QOM object ([`bindings::Chardev`]) contains a pointer to +/// the `CharBackend`. +pub struct CharBackend { + inner: BqlRefCell, + _pin: PhantomPinned, +} + +impl Write for BqlRefMut<'_, bindings::CharBackend> { + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + fn write(&mut self, buf: &[u8]) -> io::Result { + let chr: &mut bindings::CharBackend = self; + + let len = buf.len().try_into().unwrap(); + let r = unsafe { bindings::qemu_chr_fe_write(addr_of_mut!(*chr), buf.as_ptr(), len) }; + errno::into_io_result(r).map(|cnt| cnt as usize) + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let chr: &mut bindings::CharBackend = self; + + let len = buf.len().try_into().unwrap(); + let r = unsafe { bindings::qemu_chr_fe_write_all(addr_of_mut!(*chr), buf.as_ptr(), len) }; + errno::into_io_result(r).and_then(|cnt| { + if cnt as usize == buf.len() { + Ok(()) + } else { + Err(ErrorKind::WriteZero.into()) + } + }) + } +} + +impl Debug for CharBackend { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // SAFETY: accessed just to print the values + let chr = self.inner.as_ptr(); + Debug::fmt(unsafe { &*chr }, f) + } +} + +// FIXME: use something like PinnedDrop from the pinned_init crate +impl Drop for CharBackend { + fn drop(&mut self) { + self.disable_handlers(); + } +} + +impl CharBackend { + /// Enable the front-end's character device handlers, if there is an + /// associated `Chardev`. + pub fn enable_handlers< + 'chardev, + 'owner: 'chardev, + T, + CanReceiveFn: for<'a> FnCall<(&'a T,), u32>, + ReceiveFn: for<'a, 'b> FnCall<(&'a T, &'b [u8])>, + EventFn: for<'a> FnCall<(&'a T, Event)>, + >( + // When "self" is dropped, the handlers are automatically disabled. + // However, this is not necessarily true if the owner is dropped. + // So require the owner to outlive the character device. + &'chardev self, + owner: &'owner T, + _can_receive: CanReceiveFn, + _receive: ReceiveFn, + _event: EventFn, + ) { + unsafe extern "C" fn rust_can_receive_cb FnCall<(&'a T,), u32>>( + opaque: *mut c_void, + ) -> c_int { + // SAFETY: the values are safe according to the contract of + // enable_handlers() and qemu_chr_fe_set_handlers() + let owner: &T = unsafe { &*(opaque.cast::()) }; + let r = F::call((owner,)); + r.try_into().unwrap() + } + + unsafe extern "C" fn rust_receive_cb FnCall<(&'a T, &'b [u8])>>( + opaque: *mut c_void, + buf: *const u8, + size: c_int, + ) { + // SAFETY: the values are safe according to the contract of + // enable_handlers() and qemu_chr_fe_set_handlers() + let owner: &T = unsafe { &*(opaque.cast::()) }; + let buf = unsafe { slice::from_raw_parts(buf, size.try_into().unwrap()) }; + F::call((owner, buf)) + } + + unsafe extern "C" fn rust_event_cb FnCall<(&'a T, Event)>>( + opaque: *mut c_void, + event: Event, + ) { + // SAFETY: the values are safe according to the contract of + // enable_handlers() and qemu_chr_fe_set_handlers() + let owner: &T = unsafe { &*(opaque.cast::()) }; + F::call((owner, event)) + } + + let _: () = CanReceiveFn::ASSERT_IS_SOME; + let receive_cb: Option = + if ReceiveFn::is_some() { + Some(rust_receive_cb::) + } else { + None + }; + let event_cb: Option = if EventFn::is_some() { + Some(rust_event_cb::) + } else { + None + }; + + let mut chr = self.inner.borrow_mut(); + // SAFETY: the borrow promises that the BQL is taken + unsafe { + bindings::qemu_chr_fe_set_handlers( + addr_of_mut!(*chr), + Some(rust_can_receive_cb::), + receive_cb, + event_cb, + None, + (owner as *const T as *mut T).cast::(), + core::ptr::null_mut(), + true, + ); + } + } + + /// Disable the front-end's character device handlers. + pub fn disable_handlers(&self) { + let mut chr = self.inner.borrow_mut(); + // SAFETY: the borrow promises that the BQL is taken + unsafe { + bindings::qemu_chr_fe_set_handlers( + addr_of_mut!(*chr), + None, + None, + None, + None, + core::ptr::null_mut(), + core::ptr::null_mut(), + true, + ); + } + } + + /// Notify that the frontend is ready to receive data. + pub fn accept_input(&self) { + let mut chr = self.inner.borrow_mut(); + // SAFETY: the borrow promises that the BQL is taken + unsafe { bindings::qemu_chr_fe_accept_input(addr_of_mut!(*chr)) } + } + + /// Temporarily borrow the character device, allowing it to be used + /// as an implementor of `Write`. Note that it is not valid to drop + /// the big QEMU lock while the character device is borrowed, as + /// that might cause C code to write to the character device. + pub fn borrow_mut(&self) -> impl Write + '_ { + self.inner.borrow_mut() + } + + /// Send a continuous stream of zero bits on the line if `enabled` is + /// true, or a short stream if `enabled` is false. + pub fn send_break(&self, long: bool) -> io::Result<()> { + let mut chr = self.inner.borrow_mut(); + let mut duration: c_int = long.into(); + // SAFETY: the borrow promises that the BQL is taken + let r = unsafe { + bindings::qemu_chr_fe_ioctl( + addr_of_mut!(*chr), + bindings::CHR_IOCTL_SERIAL_SET_BREAK as i32, + addr_of_mut!(duration).cast::(), + ) + }; + + errno::into_io_result(r).map(|_| ()) + } + + /// Write data to a character backend from the front end. This function + /// will send data from the front end to the back end. Unlike + /// `write`, this function will block if the back end cannot + /// consume all of the data attempted to be written. + /// + /// Returns the number of bytes consumed (0 if no associated Chardev) or an + /// error. + pub fn write(&self, buf: &[u8]) -> io::Result { + let len = buf.len().try_into().unwrap(); + // SAFETY: qemu_chr_fe_write is thread-safe + let r = unsafe { bindings::qemu_chr_fe_write(self.inner.as_ptr(), buf.as_ptr(), len) }; + errno::into_io_result(r).map(|cnt| cnt as usize) + } + + /// Write data to a character backend from the front end. This function + /// will send data from the front end to the back end. Unlike + /// `write`, this function will block if the back end cannot + /// consume all of the data attempted to be written. + /// + /// Returns the number of bytes consumed (0 if no associated Chardev) or an + /// error. + pub fn write_all(&self, buf: &[u8]) -> io::Result<()> { + let len = buf.len().try_into().unwrap(); + // SAFETY: qemu_chr_fe_write_all is thread-safe + let r = unsafe { bindings::qemu_chr_fe_write_all(self.inner.as_ptr(), buf.as_ptr(), len) }; + errno::into_io_result(r).and_then(|cnt| { + if cnt as usize == buf.len() { + Ok(()) + } else { + Err(ErrorKind::WriteZero.into()) + } + }) + } +} unsafe impl ObjectType for Chardev { type Class = ChardevClass; diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index 47b6977828da..a3415a2ebccb 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -106,3 +106,4 @@ impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1); impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2); impl_zeroable!(crate::bindings::MemoryRegionOps); impl_zeroable!(crate::bindings::MemTxAttrs); +impl_zeroable!(crate::bindings::CharBackend); From 959fd759a2a55d90bf18f5b275cf6c7b11b27a79 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 27 Feb 2025 14:36:44 +0100 Subject: [PATCH 0571/1179] rust: pl011: move register definitions out of lib.rs Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 7 +- rust/hw/char/pl011/src/lib.rs | 509 +--------------------------- rust/hw/char/pl011/src/registers.rs | 506 +++++++++++++++++++++++++++ 3 files changed, 512 insertions(+), 510 deletions(-) create mode 100644 rust/hw/char/pl011/src/registers.rs diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index d0857b470c95..01540654cc90 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -26,10 +26,13 @@ use qemu_api::{ use crate::{ device_class, - registers::{self, Interrupt}, - RegisterOffset, + registers::{self, Interrupt, RegisterOffset}, }; +// TODO: You must disable the UART before any of the control registers are +// reprogrammed. When the UART is disabled in the middle of transmission or +// reception, it completes the current character before stopping + /// Integer Baud Rate Divider, `UARTIBRD` const IBRD_MASK: u32 = 0xffff; diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 1bf46c65af24..45c13ba899e8 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -18,516 +18,9 @@ use qemu_api::c_str; mod device; mod device_class; +mod registers; pub use device::pl011_create; pub const TYPE_PL011: &::std::ffi::CStr = c_str!("pl011"); pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary"); - -/// Offset of each register from the base memory address of the device. -/// -/// # Source -/// ARM DDI 0183G, Table 3-1 p.3-3 -#[doc(alias = "offset")] -#[allow(non_camel_case_types)] -#[repr(u64)] -#[derive(Debug, Eq, PartialEq, qemu_api_macros::TryInto)] -enum RegisterOffset { - /// Data Register - /// - /// A write to this register initiates the actual data transmission - #[doc(alias = "UARTDR")] - DR = 0x000, - /// Receive Status Register or Error Clear Register - #[doc(alias = "UARTRSR")] - #[doc(alias = "UARTECR")] - RSR = 0x004, - /// Flag Register - /// - /// A read of this register shows if transmission is complete - #[doc(alias = "UARTFR")] - FR = 0x018, - /// Fractional Baud Rate Register - /// - /// responsible for baud rate speed - #[doc(alias = "UARTFBRD")] - FBRD = 0x028, - /// `IrDA` Low-Power Counter Register - #[doc(alias = "UARTILPR")] - ILPR = 0x020, - /// Integer Baud Rate Register - /// - /// Responsible for baud rate speed - #[doc(alias = "UARTIBRD")] - IBRD = 0x024, - /// line control register (data frame format) - #[doc(alias = "UARTLCR_H")] - LCR_H = 0x02C, - /// Toggle UART, transmission or reception - #[doc(alias = "UARTCR")] - CR = 0x030, - /// Interrupt FIFO Level Select Register - #[doc(alias = "UARTIFLS")] - FLS = 0x034, - /// Interrupt Mask Set/Clear Register - #[doc(alias = "UARTIMSC")] - IMSC = 0x038, - /// Raw Interrupt Status Register - #[doc(alias = "UARTRIS")] - RIS = 0x03C, - /// Masked Interrupt Status Register - #[doc(alias = "UARTMIS")] - MIS = 0x040, - /// Interrupt Clear Register - #[doc(alias = "UARTICR")] - ICR = 0x044, - /// DMA control Register - #[doc(alias = "UARTDMACR")] - DMACR = 0x048, - ///// Reserved, offsets `0x04C` to `0x07C`. - //Reserved = 0x04C, -} - -mod registers { - //! Device registers exposed as typed structs which are backed by arbitrary - //! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc. - use bilge::prelude::*; - use qemu_api::impl_vmstate_bitsized; - - /// Receive Status Register / Data Register common error bits - /// - /// The `UARTRSR` register is updated only when a read occurs - /// from the `UARTDR` register with the same status information - /// that can also be obtained by reading the `UARTDR` register - #[bitsize(8)] - #[derive(Clone, Copy, Default, DebugBits, FromBits)] - pub struct Errors { - pub framing_error: bool, - pub parity_error: bool, - pub break_error: bool, - pub overrun_error: bool, - _reserved_unpredictable: u4, - } - - // TODO: FIFO Mode has different semantics - /// Data Register, `UARTDR` - /// - /// The `UARTDR` register is the data register. - /// - /// For words to be transmitted: - /// - /// - if the FIFOs are enabled, data written to this location is pushed onto - /// the transmit - /// FIFO - /// - if the FIFOs are not enabled, data is stored in the transmitter - /// holding register (the - /// bottom word of the transmit FIFO). - /// - /// The write operation initiates transmission from the UART. The data is - /// prefixed with a start bit, appended with the appropriate parity bit - /// (if parity is enabled), and a stop bit. The resultant word is then - /// transmitted. - /// - /// For received words: - /// - /// - if the FIFOs are enabled, the data byte and the 4-bit status (break, - /// frame, parity, - /// and overrun) is pushed onto the 12-bit wide receive FIFO - /// - if the FIFOs are not enabled, the data byte and status are stored in - /// the receiving - /// holding register (the bottom word of the receive FIFO). - /// - /// The received data byte is read by performing reads from the `UARTDR` - /// register along with the corresponding status information. The status - /// information can also be read by a read of the `UARTRSR/UARTECR` - /// register. - /// - /// # Note - /// - /// You must disable the UART before any of the control registers are - /// reprogrammed. When the UART is disabled in the middle of - /// transmission or reception, it completes the current character before - /// stopping. - /// - /// # Source - /// ARM DDI 0183G 3.3.1 Data Register, UARTDR - #[bitsize(32)] - #[derive(Clone, Copy, Default, DebugBits, FromBits)] - #[doc(alias = "UARTDR")] - pub struct Data { - pub data: u8, - pub errors: Errors, - _reserved: u16, - } - impl_vmstate_bitsized!(Data); - - impl Data { - // bilge is not very const-friendly, unfortunately - pub const BREAK: Self = Self { value: 1 << 10 }; - } - - // TODO: FIFO Mode has different semantics - /// Receive Status Register / Error Clear Register, `UARTRSR/UARTECR` - /// - /// The UARTRSR/UARTECR register is the receive status register/error clear - /// register. Receive status can also be read from the `UARTRSR` - /// register. If the status is read from this register, then the status - /// information for break, framing and parity corresponds to the - /// data character read from the [Data register](Data), `UARTDR` prior to - /// reading the UARTRSR register. The status information for overrun is - /// set immediately when an overrun condition occurs. - /// - /// - /// # Note - /// The received data character must be read first from the [Data - /// Register](Data), `UARTDR` before reading the error status associated - /// with that data character from the `UARTRSR` register. This read - /// sequence cannot be reversed, because the `UARTRSR` register is - /// updated only when a read occurs from the `UARTDR` register. However, - /// the status information can also be obtained by reading the `UARTDR` - /// register - /// - /// # Source - /// ARM DDI 0183G 3.3.2 Receive Status Register/Error Clear Register, - /// UARTRSR/UARTECR - #[bitsize(32)] - #[derive(Clone, Copy, DebugBits, FromBits)] - pub struct ReceiveStatusErrorClear { - pub errors: Errors, - _reserved_unpredictable: u24, - } - impl_vmstate_bitsized!(ReceiveStatusErrorClear); - - impl ReceiveStatusErrorClear { - pub fn set_from_data(&mut self, data: Data) { - self.set_errors(data.errors()); - } - - pub fn reset(&mut self) { - // All the bits are cleared to 0 on reset. - *self = Self::default(); - } - } - - impl Default for ReceiveStatusErrorClear { - fn default() -> Self { - 0.into() - } - } - - #[bitsize(32)] - #[derive(Clone, Copy, DebugBits, FromBits)] - /// Flag Register, `UARTFR` - #[doc(alias = "UARTFR")] - pub struct Flags { - /// CTS Clear to send. This bit is the complement of the UART clear to - /// send, `nUARTCTS`, modem status input. That is, the bit is 1 - /// when `nUARTCTS` is LOW. - pub clear_to_send: bool, - /// DSR Data set ready. This bit is the complement of the UART data set - /// ready, `nUARTDSR`, modem status input. That is, the bit is 1 when - /// `nUARTDSR` is LOW. - pub data_set_ready: bool, - /// DCD Data carrier detect. This bit is the complement of the UART data - /// carrier detect, `nUARTDCD`, modem status input. That is, the bit is - /// 1 when `nUARTDCD` is LOW. - pub data_carrier_detect: bool, - /// BUSY UART busy. If this bit is set to 1, the UART is busy - /// transmitting data. This bit remains set until the complete - /// byte, including all the stop bits, has been sent from the - /// shift register. This bit is set as soon as the transmit FIFO - /// becomes non-empty, regardless of whether the UART is enabled - /// or not. - pub busy: bool, - /// RXFE Receive FIFO empty. The meaning of this bit depends on the - /// state of the FEN bit in the UARTLCR_H register. If the FIFO - /// is disabled, this bit is set when the receive holding - /// register is empty. If the FIFO is enabled, the RXFE bit is - /// set when the receive FIFO is empty. - pub receive_fifo_empty: bool, - /// TXFF Transmit FIFO full. The meaning of this bit depends on the - /// state of the FEN bit in the UARTLCR_H register. If the FIFO - /// is disabled, this bit is set when the transmit holding - /// register is full. If the FIFO is enabled, the TXFF bit is - /// set when the transmit FIFO is full. - pub transmit_fifo_full: bool, - /// RXFF Receive FIFO full. The meaning of this bit depends on the state - /// of the FEN bit in the UARTLCR_H register. If the FIFO is - /// disabled, this bit is set when the receive holding register - /// is full. If the FIFO is enabled, the RXFF bit is set when - /// the receive FIFO is full. - pub receive_fifo_full: bool, - /// Transmit FIFO empty. The meaning of this bit depends on the state of - /// the FEN bit in the [Line Control register](LineControl), - /// `UARTLCR_H`. If the FIFO is disabled, this bit is set when the - /// transmit holding register is empty. If the FIFO is enabled, - /// the TXFE bit is set when the transmit FIFO is empty. This - /// bit does not indicate if there is data in the transmit shift - /// register. - pub transmit_fifo_empty: bool, - /// `RI`, is `true` when `nUARTRI` is `LOW`. - pub ring_indicator: bool, - _reserved_zero_no_modify: u23, - } - impl_vmstate_bitsized!(Flags); - - impl Flags { - pub fn reset(&mut self) { - *self = Self::default(); - } - } - - impl Default for Flags { - fn default() -> Self { - let mut ret: Self = 0.into(); - // After reset TXFF, RXFF, and BUSY are 0, and TXFE and RXFE are 1 - ret.set_receive_fifo_empty(true); - ret.set_transmit_fifo_empty(true); - ret - } - } - - #[bitsize(32)] - #[derive(Clone, Copy, DebugBits, FromBits)] - /// Line Control Register, `UARTLCR_H` - #[doc(alias = "UARTLCR_H")] - pub struct LineControl { - /// BRK Send break. - /// - /// If this bit is set to `1`, a low-level is continually output on the - /// `UARTTXD` output, after completing transmission of the - /// current character. For the proper execution of the break command, - /// the software must set this bit for at least two complete - /// frames. For normal use, this bit must be cleared to `0`. - pub send_break: bool, - /// 1 PEN Parity enable: - /// - /// - 0 = parity is disabled and no parity bit added to the data frame - /// - 1 = parity checking and generation is enabled. - /// - /// See Table 3-11 on page 3-14 for the parity truth table. - pub parity_enabled: bool, - /// EPS Even parity select. Controls the type of parity the UART uses - /// during transmission and reception: - /// - 0 = odd parity. The UART generates or checks for an odd number of - /// 1s in the data and parity bits. - /// - 1 = even parity. The UART generates or checks for an even number - /// of 1s in the data and parity bits. - /// This bit has no effect when the `PEN` bit disables parity checking - /// and generation. See Table 3-11 on page 3-14 for the parity - /// truth table. - pub parity: Parity, - /// 3 STP2 Two stop bits select. If this bit is set to 1, two stop bits - /// are transmitted at the end of the frame. The receive - /// logic does not check for two stop bits being received. - pub two_stops_bits: bool, - /// FEN Enable FIFOs: - /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become - /// 1-byte-deep holding registers 1 = transmit and receive FIFO - /// buffers are enabled (FIFO mode). - pub fifos_enabled: Mode, - /// WLEN Word length. These bits indicate the number of data bits - /// transmitted or received in a frame as follows: b11 = 8 bits - /// b10 = 7 bits - /// b01 = 6 bits - /// b00 = 5 bits. - pub word_length: WordLength, - /// 7 SPS Stick parity select. - /// 0 = stick parity is disabled - /// 1 = either: - /// • if the EPS bit is 0 then the parity bit is transmitted and checked - /// as a 1 • if the EPS bit is 1 then the parity bit is - /// transmitted and checked as a 0. This bit has no effect when - /// the PEN bit disables parity checking and generation. See Table 3-11 - /// on page 3-14 for the parity truth table. - pub sticky_parity: bool, - /// 31:8 - Reserved, do not modify, read as zero. - _reserved_zero_no_modify: u24, - } - impl_vmstate_bitsized!(LineControl); - - impl LineControl { - pub fn reset(&mut self) { - // All the bits are cleared to 0 when reset. - *self = 0.into(); - } - } - - impl Default for LineControl { - fn default() -> Self { - 0.into() - } - } - - #[bitsize(1)] - #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] - /// `EPS` "Even parity select", field of [Line Control - /// register](LineControl). - pub enum Parity { - /// - 0 = odd parity. The UART generates or checks for an odd number of - /// 1s in the data and parity bits. - Odd = 0, - /// - 1 = even parity. The UART generates or checks for an even number - /// of 1s in the data and parity bits. - Even = 1, - } - - #[bitsize(1)] - #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] - /// `FEN` "Enable FIFOs" or Device mode, field of [Line Control - /// register](LineControl). - pub enum Mode { - /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become - /// 1-byte-deep holding registers - Character = 0, - /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode). - FIFO = 1, - } - - #[bitsize(2)] - #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] - /// `WLEN` Word length, field of [Line Control register](LineControl). - /// - /// These bits indicate the number of data bits transmitted or received in a - /// frame as follows: - pub enum WordLength { - /// b11 = 8 bits - _8Bits = 0b11, - /// b10 = 7 bits - _7Bits = 0b10, - /// b01 = 6 bits - _6Bits = 0b01, - /// b00 = 5 bits. - _5Bits = 0b00, - } - - /// Control Register, `UARTCR` - /// - /// The `UARTCR` register is the control register. All the bits are cleared - /// to `0` on reset except for bits `9` and `8` that are set to `1`. - /// - /// # Source - /// ARM DDI 0183G, 3.3.8 Control Register, `UARTCR`, Table 3-12 - #[bitsize(32)] - #[doc(alias = "UARTCR")] - #[derive(Clone, Copy, DebugBits, FromBits)] - pub struct Control { - /// `UARTEN` UART enable: 0 = UART is disabled. If the UART is disabled - /// in the middle of transmission or reception, it completes the current - /// character before stopping. 1 = the UART is enabled. Data - /// transmission and reception occurs for either UART signals or SIR - /// signals depending on the setting of the SIREN bit. - pub enable_uart: bool, - /// `SIREN` `SIR` enable: 0 = IrDA SIR ENDEC is disabled. `nSIROUT` - /// remains LOW (no light pulse generated), and signal transitions on - /// SIRIN have no effect. 1 = IrDA SIR ENDEC is enabled. Data is - /// transmitted and received on nSIROUT and SIRIN. UARTTXD remains HIGH, - /// in the marking state. Signal transitions on UARTRXD or modem status - /// inputs have no effect. This bit has no effect if the UARTEN bit - /// disables the UART. - pub enable_sir: bool, - /// `SIRLP` SIR low-power IrDA mode. This bit selects the IrDA encoding - /// mode. If this bit is cleared to 0, low-level bits are transmitted as - /// an active high pulse with a width of 3/ 16th of the bit period. If - /// this bit is set to 1, low-level bits are transmitted with a pulse - /// width that is 3 times the period of the IrLPBaud16 input signal, - /// regardless of the selected bit rate. Setting this bit uses less - /// power, but might reduce transmission distances. - pub sir_lowpower_irda_mode: u1, - /// Reserved, do not modify, read as zero. - _reserved_zero_no_modify: u4, - /// `LBE` Loopback enable. If this bit is set to 1 and the SIREN bit is - /// set to 1 and the SIRTEST bit in the Test Control register, UARTTCR - /// on page 4-5 is set to 1, then the nSIROUT path is inverted, and fed - /// through to the SIRIN path. The SIRTEST bit in the test register must - /// be set to 1 to override the normal half-duplex SIR operation. This - /// must be the requirement for accessing the test registers during - /// normal operation, and SIRTEST must be cleared to 0 when loopback - /// testing is finished. This feature reduces the amount of external - /// coupling required during system test. If this bit is set to 1, and - /// the SIRTEST bit is set to 0, the UARTTXD path is fed through to the - /// UARTRXD path. In either SIR mode or UART mode, when this bit is set, - /// the modem outputs are also fed through to the modem inputs. This bit - /// is cleared to 0 on reset, to disable loopback. - pub enable_loopback: bool, - /// `TXE` Transmit enable. If this bit is set to 1, the transmit section - /// of the UART is enabled. Data transmission occurs for either UART - /// signals, or SIR signals depending on the setting of the SIREN bit. - /// When the UART is disabled in the middle of transmission, it - /// completes the current character before stopping. - pub enable_transmit: bool, - /// `RXE` Receive enable. If this bit is set to 1, the receive section - /// of the UART is enabled. Data reception occurs for either UART - /// signals or SIR signals depending on the setting of the SIREN bit. - /// When the UART is disabled in the middle of reception, it completes - /// the current character before stopping. - pub enable_receive: bool, - /// `DTR` Data transmit ready. This bit is the complement of the UART - /// data transmit ready, `nUARTDTR`, modem status output. That is, when - /// the bit is programmed to a 1 then `nUARTDTR` is LOW. - pub data_transmit_ready: bool, - /// `RTS` Request to send. This bit is the complement of the UART - /// request to send, `nUARTRTS`, modem status output. That is, when the - /// bit is programmed to a 1 then `nUARTRTS` is LOW. - pub request_to_send: bool, - /// `Out1` This bit is the complement of the UART Out1 (`nUARTOut1`) - /// modem status output. That is, when the bit is programmed to a 1 the - /// output is 0. For DTE this can be used as Data Carrier Detect (DCD). - pub out_1: bool, - /// `Out2` This bit is the complement of the UART Out2 (`nUARTOut2`) - /// modem status output. That is, when the bit is programmed to a 1, the - /// output is 0. For DTE this can be used as Ring Indicator (RI). - pub out_2: bool, - /// `RTSEn` RTS hardware flow control enable. If this bit is set to 1, - /// RTS hardware flow control is enabled. Data is only requested when - /// there is space in the receive FIFO for it to be received. - pub rts_hardware_flow_control_enable: bool, - /// `CTSEn` CTS hardware flow control enable. If this bit is set to 1, - /// CTS hardware flow control is enabled. Data is only transmitted when - /// the `nUARTCTS` signal is asserted. - pub cts_hardware_flow_control_enable: bool, - /// 31:16 - Reserved, do not modify, read as zero. - _reserved_zero_no_modify2: u16, - } - impl_vmstate_bitsized!(Control); - - impl Control { - pub fn reset(&mut self) { - *self = 0.into(); - self.set_enable_receive(true); - self.set_enable_transmit(true); - } - } - - impl Default for Control { - fn default() -> Self { - let mut ret: Self = 0.into(); - ret.reset(); - ret - } - } - - /// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC - pub struct Interrupt(pub u32); - - impl Interrupt { - pub const OE: Self = Self(1 << 10); - pub const BE: Self = Self(1 << 9); - pub const PE: Self = Self(1 << 8); - pub const FE: Self = Self(1 << 7); - pub const RT: Self = Self(1 << 6); - pub const TX: Self = Self(1 << 5); - pub const RX: Self = Self(1 << 4); - pub const DSR: Self = Self(1 << 3); - pub const DCD: Self = Self(1 << 2); - pub const CTS: Self = Self(1 << 1); - pub const RI: Self = Self(1 << 0); - - pub const E: Self = Self(Self::OE.0 | Self::BE.0 | Self::PE.0 | Self::FE.0); - pub const MS: Self = Self(Self::RI.0 | Self::DSR.0 | Self::DCD.0 | Self::CTS.0); - } -} - -// TODO: You must disable the UART before any of the control registers are -// reprogrammed. When the UART is disabled in the middle of transmission or -// reception, it completes the current character before stopping diff --git a/rust/hw/char/pl011/src/registers.rs b/rust/hw/char/pl011/src/registers.rs new file mode 100644 index 000000000000..cd92fa2c3007 --- /dev/null +++ b/rust/hw/char/pl011/src/registers.rs @@ -0,0 +1,506 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Device registers exposed as typed structs which are backed by arbitrary +//! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc. + +use bilge::prelude::*; +use qemu_api::impl_vmstate_bitsized; + +/// Offset of each register from the base memory address of the device. +/// +/// # Source +/// ARM DDI 0183G, Table 3-1 p.3-3 +#[doc(alias = "offset")] +#[allow(non_camel_case_types)] +#[repr(u64)] +#[derive(Debug, Eq, PartialEq, qemu_api_macros::TryInto)] +pub enum RegisterOffset { + /// Data Register + /// + /// A write to this register initiates the actual data transmission + #[doc(alias = "UARTDR")] + DR = 0x000, + /// Receive Status Register or Error Clear Register + #[doc(alias = "UARTRSR")] + #[doc(alias = "UARTECR")] + RSR = 0x004, + /// Flag Register + /// + /// A read of this register shows if transmission is complete + #[doc(alias = "UARTFR")] + FR = 0x018, + /// Fractional Baud Rate Register + /// + /// responsible for baud rate speed + #[doc(alias = "UARTFBRD")] + FBRD = 0x028, + /// `IrDA` Low-Power Counter Register + #[doc(alias = "UARTILPR")] + ILPR = 0x020, + /// Integer Baud Rate Register + /// + /// Responsible for baud rate speed + #[doc(alias = "UARTIBRD")] + IBRD = 0x024, + /// line control register (data frame format) + #[doc(alias = "UARTLCR_H")] + LCR_H = 0x02C, + /// Toggle UART, transmission or reception + #[doc(alias = "UARTCR")] + CR = 0x030, + /// Interrupt FIFO Level Select Register + #[doc(alias = "UARTIFLS")] + FLS = 0x034, + /// Interrupt Mask Set/Clear Register + #[doc(alias = "UARTIMSC")] + IMSC = 0x038, + /// Raw Interrupt Status Register + #[doc(alias = "UARTRIS")] + RIS = 0x03C, + /// Masked Interrupt Status Register + #[doc(alias = "UARTMIS")] + MIS = 0x040, + /// Interrupt Clear Register + #[doc(alias = "UARTICR")] + ICR = 0x044, + /// DMA control Register + #[doc(alias = "UARTDMACR")] + DMACR = 0x048, + ///// Reserved, offsets `0x04C` to `0x07C`. + //Reserved = 0x04C, +} + +/// Receive Status Register / Data Register common error bits +/// +/// The `UARTRSR` register is updated only when a read occurs +/// from the `UARTDR` register with the same status information +/// that can also be obtained by reading the `UARTDR` register +#[bitsize(8)] +#[derive(Clone, Copy, Default, DebugBits, FromBits)] +pub struct Errors { + pub framing_error: bool, + pub parity_error: bool, + pub break_error: bool, + pub overrun_error: bool, + _reserved_unpredictable: u4, +} + +// TODO: FIFO Mode has different semantics +/// Data Register, `UARTDR` +/// +/// The `UARTDR` register is the data register. +/// +/// For words to be transmitted: +/// +/// - if the FIFOs are enabled, data written to this location is pushed onto the +/// transmit +/// FIFO +/// - if the FIFOs are not enabled, data is stored in the transmitter holding +/// register (the +/// bottom word of the transmit FIFO). +/// +/// The write operation initiates transmission from the UART. The data is +/// prefixed with a start bit, appended with the appropriate parity bit +/// (if parity is enabled), and a stop bit. The resultant word is then +/// transmitted. +/// +/// For received words: +/// +/// - if the FIFOs are enabled, the data byte and the 4-bit status (break, +/// frame, parity, +/// and overrun) is pushed onto the 12-bit wide receive FIFO +/// - if the FIFOs are not enabled, the data byte and status are stored in the +/// receiving +/// holding register (the bottom word of the receive FIFO). +/// +/// The received data byte is read by performing reads from the `UARTDR` +/// register along with the corresponding status information. The status +/// information can also be read by a read of the `UARTRSR/UARTECR` +/// register. +/// +/// # Note +/// +/// You must disable the UART before any of the control registers are +/// reprogrammed. When the UART is disabled in the middle of +/// transmission or reception, it completes the current character before +/// stopping. +/// +/// # Source +/// ARM DDI 0183G 3.3.1 Data Register, UARTDR +#[bitsize(32)] +#[derive(Clone, Copy, Default, DebugBits, FromBits)] +#[doc(alias = "UARTDR")] +pub struct Data { + pub data: u8, + pub errors: Errors, + _reserved: u16, +} +impl_vmstate_bitsized!(Data); + +impl Data { + // bilge is not very const-friendly, unfortunately + pub const BREAK: Self = Self { value: 1 << 10 }; +} + +// TODO: FIFO Mode has different semantics +/// Receive Status Register / Error Clear Register, `UARTRSR/UARTECR` +/// +/// The UARTRSR/UARTECR register is the receive status register/error clear +/// register. Receive status can also be read from the `UARTRSR` +/// register. If the status is read from this register, then the status +/// information for break, framing and parity corresponds to the +/// data character read from the [Data register](Data), `UARTDR` prior to +/// reading the UARTRSR register. The status information for overrun is +/// set immediately when an overrun condition occurs. +/// +/// +/// # Note +/// The received data character must be read first from the [Data +/// Register](Data), `UARTDR` before reading the error status associated +/// with that data character from the `UARTRSR` register. This read +/// sequence cannot be reversed, because the `UARTRSR` register is +/// updated only when a read occurs from the `UARTDR` register. However, +/// the status information can also be obtained by reading the `UARTDR` +/// register +/// +/// # Source +/// ARM DDI 0183G 3.3.2 Receive Status Register/Error Clear Register, +/// UARTRSR/UARTECR +#[bitsize(32)] +#[derive(Clone, Copy, DebugBits, FromBits)] +pub struct ReceiveStatusErrorClear { + pub errors: Errors, + _reserved_unpredictable: u24, +} +impl_vmstate_bitsized!(ReceiveStatusErrorClear); + +impl ReceiveStatusErrorClear { + pub fn set_from_data(&mut self, data: Data) { + self.set_errors(data.errors()); + } + + pub fn reset(&mut self) { + // All the bits are cleared to 0 on reset. + *self = Self::default(); + } +} + +impl Default for ReceiveStatusErrorClear { + fn default() -> Self { + 0.into() + } +} + +#[bitsize(32)] +#[derive(Clone, Copy, DebugBits, FromBits)] +/// Flag Register, `UARTFR` +#[doc(alias = "UARTFR")] +pub struct Flags { + /// CTS Clear to send. This bit is the complement of the UART clear to + /// send, `nUARTCTS`, modem status input. That is, the bit is 1 + /// when `nUARTCTS` is LOW. + pub clear_to_send: bool, + /// DSR Data set ready. This bit is the complement of the UART data set + /// ready, `nUARTDSR`, modem status input. That is, the bit is 1 when + /// `nUARTDSR` is LOW. + pub data_set_ready: bool, + /// DCD Data carrier detect. This bit is the complement of the UART data + /// carrier detect, `nUARTDCD`, modem status input. That is, the bit is + /// 1 when `nUARTDCD` is LOW. + pub data_carrier_detect: bool, + /// BUSY UART busy. If this bit is set to 1, the UART is busy + /// transmitting data. This bit remains set until the complete + /// byte, including all the stop bits, has been sent from the + /// shift register. This bit is set as soon as the transmit FIFO + /// becomes non-empty, regardless of whether the UART is enabled + /// or not. + pub busy: bool, + /// RXFE Receive FIFO empty. The meaning of this bit depends on the + /// state of the FEN bit in the UARTLCR_H register. If the FIFO + /// is disabled, this bit is set when the receive holding + /// register is empty. If the FIFO is enabled, the RXFE bit is + /// set when the receive FIFO is empty. + pub receive_fifo_empty: bool, + /// TXFF Transmit FIFO full. The meaning of this bit depends on the + /// state of the FEN bit in the UARTLCR_H register. If the FIFO + /// is disabled, this bit is set when the transmit holding + /// register is full. If the FIFO is enabled, the TXFF bit is + /// set when the transmit FIFO is full. + pub transmit_fifo_full: bool, + /// RXFF Receive FIFO full. The meaning of this bit depends on the state + /// of the FEN bit in the UARTLCR_H register. If the FIFO is + /// disabled, this bit is set when the receive holding register + /// is full. If the FIFO is enabled, the RXFF bit is set when + /// the receive FIFO is full. + pub receive_fifo_full: bool, + /// Transmit FIFO empty. The meaning of this bit depends on the state of + /// the FEN bit in the [Line Control register](LineControl), + /// `UARTLCR_H`. If the FIFO is disabled, this bit is set when the + /// transmit holding register is empty. If the FIFO is enabled, + /// the TXFE bit is set when the transmit FIFO is empty. This + /// bit does not indicate if there is data in the transmit shift + /// register. + pub transmit_fifo_empty: bool, + /// `RI`, is `true` when `nUARTRI` is `LOW`. + pub ring_indicator: bool, + _reserved_zero_no_modify: u23, +} +impl_vmstate_bitsized!(Flags); + +impl Flags { + pub fn reset(&mut self) { + *self = Self::default(); + } +} + +impl Default for Flags { + fn default() -> Self { + let mut ret: Self = 0.into(); + // After reset TXFF, RXFF, and BUSY are 0, and TXFE and RXFE are 1 + ret.set_receive_fifo_empty(true); + ret.set_transmit_fifo_empty(true); + ret + } +} + +#[bitsize(32)] +#[derive(Clone, Copy, DebugBits, FromBits)] +/// Line Control Register, `UARTLCR_H` +#[doc(alias = "UARTLCR_H")] +pub struct LineControl { + /// BRK Send break. + /// + /// If this bit is set to `1`, a low-level is continually output on the + /// `UARTTXD` output, after completing transmission of the + /// current character. For the proper execution of the break command, + /// the software must set this bit for at least two complete + /// frames. For normal use, this bit must be cleared to `0`. + pub send_break: bool, + /// 1 PEN Parity enable: + /// + /// - 0 = parity is disabled and no parity bit added to the data frame + /// - 1 = parity checking and generation is enabled. + /// + /// See Table 3-11 on page 3-14 for the parity truth table. + pub parity_enabled: bool, + /// EPS Even parity select. Controls the type of parity the UART uses + /// during transmission and reception: + /// - 0 = odd parity. The UART generates or checks for an odd number of 1s + /// in the data and parity bits. + /// - 1 = even parity. The UART generates or checks for an even number of 1s + /// in the data and parity bits. + /// This bit has no effect when the `PEN` bit disables parity checking + /// and generation. See Table 3-11 on page 3-14 for the parity + /// truth table. + pub parity: Parity, + /// 3 STP2 Two stop bits select. If this bit is set to 1, two stop bits + /// are transmitted at the end of the frame. The receive + /// logic does not check for two stop bits being received. + pub two_stops_bits: bool, + /// FEN Enable FIFOs: + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become + /// 1-byte-deep holding registers 1 = transmit and receive FIFO + /// buffers are enabled (FIFO mode). + pub fifos_enabled: Mode, + /// WLEN Word length. These bits indicate the number of data bits + /// transmitted or received in a frame as follows: b11 = 8 bits + /// b10 = 7 bits + /// b01 = 6 bits + /// b00 = 5 bits. + pub word_length: WordLength, + /// 7 SPS Stick parity select. + /// 0 = stick parity is disabled + /// 1 = either: + /// • if the EPS bit is 0 then the parity bit is transmitted and checked + /// as a 1 • if the EPS bit is 1 then the parity bit is + /// transmitted and checked as a 0. This bit has no effect when + /// the PEN bit disables parity checking and generation. See Table 3-11 + /// on page 3-14 for the parity truth table. + pub sticky_parity: bool, + /// 31:8 - Reserved, do not modify, read as zero. + _reserved_zero_no_modify: u24, +} +impl_vmstate_bitsized!(LineControl); + +impl LineControl { + pub fn reset(&mut self) { + // All the bits are cleared to 0 when reset. + *self = 0.into(); + } +} + +impl Default for LineControl { + fn default() -> Self { + 0.into() + } +} + +#[bitsize(1)] +#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] +/// `EPS` "Even parity select", field of [Line Control +/// register](LineControl). +pub enum Parity { + /// - 0 = odd parity. The UART generates or checks for an odd number of 1s + /// in the data and parity bits. + Odd = 0, + /// - 1 = even parity. The UART generates or checks for an even number of 1s + /// in the data and parity bits. + Even = 1, +} + +#[bitsize(1)] +#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] +/// `FEN` "Enable FIFOs" or Device mode, field of [Line Control +/// register](LineControl). +pub enum Mode { + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become + /// 1-byte-deep holding registers + Character = 0, + /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode). + FIFO = 1, +} + +#[bitsize(2)] +#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] +/// `WLEN` Word length, field of [Line Control register](LineControl). +/// +/// These bits indicate the number of data bits transmitted or received in a +/// frame as follows: +pub enum WordLength { + /// b11 = 8 bits + _8Bits = 0b11, + /// b10 = 7 bits + _7Bits = 0b10, + /// b01 = 6 bits + _6Bits = 0b01, + /// b00 = 5 bits. + _5Bits = 0b00, +} + +/// Control Register, `UARTCR` +/// +/// The `UARTCR` register is the control register. All the bits are cleared +/// to `0` on reset except for bits `9` and `8` that are set to `1`. +/// +/// # Source +/// ARM DDI 0183G, 3.3.8 Control Register, `UARTCR`, Table 3-12 +#[bitsize(32)] +#[doc(alias = "UARTCR")] +#[derive(Clone, Copy, DebugBits, FromBits)] +pub struct Control { + /// `UARTEN` UART enable: 0 = UART is disabled. If the UART is disabled + /// in the middle of transmission or reception, it completes the current + /// character before stopping. 1 = the UART is enabled. Data + /// transmission and reception occurs for either UART signals or SIR + /// signals depending on the setting of the SIREN bit. + pub enable_uart: bool, + /// `SIREN` `SIR` enable: 0 = IrDA SIR ENDEC is disabled. `nSIROUT` + /// remains LOW (no light pulse generated), and signal transitions on + /// SIRIN have no effect. 1 = IrDA SIR ENDEC is enabled. Data is + /// transmitted and received on nSIROUT and SIRIN. UARTTXD remains HIGH, + /// in the marking state. Signal transitions on UARTRXD or modem status + /// inputs have no effect. This bit has no effect if the UARTEN bit + /// disables the UART. + pub enable_sir: bool, + /// `SIRLP` SIR low-power IrDA mode. This bit selects the IrDA encoding + /// mode. If this bit is cleared to 0, low-level bits are transmitted as + /// an active high pulse with a width of 3/ 16th of the bit period. If + /// this bit is set to 1, low-level bits are transmitted with a pulse + /// width that is 3 times the period of the IrLPBaud16 input signal, + /// regardless of the selected bit rate. Setting this bit uses less + /// power, but might reduce transmission distances. + pub sir_lowpower_irda_mode: u1, + /// Reserved, do not modify, read as zero. + _reserved_zero_no_modify: u4, + /// `LBE` Loopback enable. If this bit is set to 1 and the SIREN bit is + /// set to 1 and the SIRTEST bit in the Test Control register, UARTTCR + /// on page 4-5 is set to 1, then the nSIROUT path is inverted, and fed + /// through to the SIRIN path. The SIRTEST bit in the test register must + /// be set to 1 to override the normal half-duplex SIR operation. This + /// must be the requirement for accessing the test registers during + /// normal operation, and SIRTEST must be cleared to 0 when loopback + /// testing is finished. This feature reduces the amount of external + /// coupling required during system test. If this bit is set to 1, and + /// the SIRTEST bit is set to 0, the UARTTXD path is fed through to the + /// UARTRXD path. In either SIR mode or UART mode, when this bit is set, + /// the modem outputs are also fed through to the modem inputs. This bit + /// is cleared to 0 on reset, to disable loopback. + pub enable_loopback: bool, + /// `TXE` Transmit enable. If this bit is set to 1, the transmit section + /// of the UART is enabled. Data transmission occurs for either UART + /// signals, or SIR signals depending on the setting of the SIREN bit. + /// When the UART is disabled in the middle of transmission, it + /// completes the current character before stopping. + pub enable_transmit: bool, + /// `RXE` Receive enable. If this bit is set to 1, the receive section + /// of the UART is enabled. Data reception occurs for either UART + /// signals or SIR signals depending on the setting of the SIREN bit. + /// When the UART is disabled in the middle of reception, it completes + /// the current character before stopping. + pub enable_receive: bool, + /// `DTR` Data transmit ready. This bit is the complement of the UART + /// data transmit ready, `nUARTDTR`, modem status output. That is, when + /// the bit is programmed to a 1 then `nUARTDTR` is LOW. + pub data_transmit_ready: bool, + /// `RTS` Request to send. This bit is the complement of the UART + /// request to send, `nUARTRTS`, modem status output. That is, when the + /// bit is programmed to a 1 then `nUARTRTS` is LOW. + pub request_to_send: bool, + /// `Out1` This bit is the complement of the UART Out1 (`nUARTOut1`) + /// modem status output. That is, when the bit is programmed to a 1 the + /// output is 0. For DTE this can be used as Data Carrier Detect (DCD). + pub out_1: bool, + /// `Out2` This bit is the complement of the UART Out2 (`nUARTOut2`) + /// modem status output. That is, when the bit is programmed to a 1, the + /// output is 0. For DTE this can be used as Ring Indicator (RI). + pub out_2: bool, + /// `RTSEn` RTS hardware flow control enable. If this bit is set to 1, + /// RTS hardware flow control is enabled. Data is only requested when + /// there is space in the receive FIFO for it to be received. + pub rts_hardware_flow_control_enable: bool, + /// `CTSEn` CTS hardware flow control enable. If this bit is set to 1, + /// CTS hardware flow control is enabled. Data is only transmitted when + /// the `nUARTCTS` signal is asserted. + pub cts_hardware_flow_control_enable: bool, + /// 31:16 - Reserved, do not modify, read as zero. + _reserved_zero_no_modify2: u16, +} +impl_vmstate_bitsized!(Control); + +impl Control { + pub fn reset(&mut self) { + *self = 0.into(); + self.set_enable_receive(true); + self.set_enable_transmit(true); + } +} + +impl Default for Control { + fn default() -> Self { + let mut ret: Self = 0.into(); + ret.reset(); + ret + } +} + +/// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC +pub struct Interrupt(pub u32); + +impl Interrupt { + pub const OE: Self = Self(1 << 10); + pub const BE: Self = Self(1 << 9); + pub const PE: Self = Self(1 << 8); + pub const FE: Self = Self(1 << 7); + pub const RT: Self = Self(1 << 6); + pub const TX: Self = Self(1 << 5); + pub const RX: Self = Self(1 << 4); + pub const DSR: Self = Self(1 << 3); + pub const DCD: Self = Self(1 << 2); + pub const CTS: Self = Self(1 << 1); + pub const RI: Self = Self(1 << 0); + + pub const E: Self = Self(Self::OE.0 | Self::BE.0 | Self::PE.0 | Self::FE.0); + pub const MS: Self = Self(Self::RI.0 | Self::DSR.0 | Self::DCD.0 | Self::CTS.0); +} From 87f5c138363da28449835055299abbae57f39a19 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 11 Feb 2025 13:31:38 +0100 Subject: [PATCH 0572/1179] rust: pl011: clean up visibilities of callbacks Do not make callbacks unnecessarily "pub", they are only used through function pointers. Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 01540654cc90..4cdbbf4b73da 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -537,7 +537,7 @@ impl PL011State { } } - pub fn read(&self, offset: hwaddr, _size: u32) -> u64 { + fn read(&self, offset: hwaddr, _size: u32) -> u64 { match RegisterOffset::try_from(offset) { Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => { let device_id = self.get_class().device_id; @@ -560,7 +560,7 @@ impl PL011State { } } - pub fn write(&self, offset: hwaddr, value: u64, _size: u32) { + fn write(&self, offset: hwaddr, value: u64, _size: u32) { let mut update_irq = false; if let Ok(field) = RegisterOffset::try_from(offset) { // qemu_chr_fe_write_all() calls into the can_receive @@ -621,7 +621,7 @@ impl PL011State { } } - pub fn realize(&self) { + fn realize(&self) { // SAFETY: self.char_backend has the correct size and alignment for a // CharBackend object, and its callbacks are of the correct types. unsafe { @@ -638,11 +638,11 @@ impl PL011State { } } - pub fn reset_hold(&self, _type: ResetType) { + fn reset_hold(&self, _type: ResetType) { self.regs.borrow_mut().reset(); } - pub fn update(&self) { + fn update(&self) { let regs = self.regs.borrow(); let flags = regs.int_level & regs.int_enabled; for (irq, i) in self.interrupts.iter().zip(IRQMASK) { From 9b642097d6b793c161c3d1c540dd19a66e02100f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 11 Feb 2025 13:33:29 +0100 Subject: [PATCH 0573/1179] rust: pl011: switch to safe chardev operation Switch bindings::CharBackend with chardev::CharBackend. This removes occurrences of "unsafe" due to FFI and switches the wrappers for receive, can_receive and event callbacks to the common ones implemented by chardev::CharBackend. Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 119 +++++++------------------------ 1 file changed, 25 insertions(+), 94 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 4cdbbf4b73da..4e282bc9e9d1 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -2,18 +2,10 @@ // Author(s): Manos Pitsidianakis // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ - ffi::CStr, - os::raw::{c_int, c_void}, - ptr::{addr_of, addr_of_mut, NonNull}, -}; +use std::{ffi::CStr, ptr::addr_of_mut}; use qemu_api::{ - bindings::{ - qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, - qemu_chr_fe_write_all, CharBackend, QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, - }, - chardev::Chardev, + chardev::{CharBackend, Chardev, Event}, impl_vmstate_forward, irq::{IRQState, InterruptSource}, memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, @@ -235,7 +227,7 @@ impl PL011Registers { &mut self, offset: RegisterOffset, value: u32, - char_backend: *mut CharBackend, + char_backend: &CharBackend, ) -> bool { // eprintln!("write offset {offset} value {value}"); use RegisterOffset::*; @@ -269,17 +261,9 @@ impl PL011Registers { self.reset_tx_fifo(); } let update = (self.line_control.send_break() != new_val.send_break()) && { - let mut break_enable: c_int = new_val.send_break().into(); - // SAFETY: self.char_backend is a valid CharBackend instance after it's been - // initialized in realize(). - unsafe { - qemu_chr_fe_ioctl( - char_backend, - CHR_IOCTL_SERIAL_SET_BREAK as i32, - addr_of_mut!(break_enable).cast::(), - ); - } - self.loopback_break(break_enable > 0) + let break_enable = new_val.send_break(); + let _ = char_backend.send_break(break_enable); + self.loopback_break(break_enable) }; self.line_control = new_val; self.set_read_trigger(); @@ -551,9 +535,7 @@ impl PL011State { let (update_irq, result) = self.regs.borrow_mut().read(field); if update_irq { self.update(); - unsafe { - qemu_chr_fe_accept_input(addr_of!(self.char_backend) as *mut _); - } + self.char_backend.accept_input(); } result.into() } @@ -567,21 +549,16 @@ impl PL011State { // callback, so handle writes before entering PL011Registers. if field == RegisterOffset::DR { // ??? Check if transmitter is enabled. - let ch: u8 = value as u8; - // SAFETY: char_backend is a valid CharBackend instance after it's been - // initialized in realize(). + let ch: [u8; 1] = [value as u8]; // XXX this blocks entire thread. Rewrite to use // qemu_chr_fe_write and background I/O callbacks - unsafe { - qemu_chr_fe_write_all(addr_of!(self.char_backend) as *mut _, &ch, 1); - } + let _ = self.char_backend.write_all(&ch); } - update_irq = self.regs.borrow_mut().write( - field, - value as u32, - addr_of!(self.char_backend) as *mut _, - ); + update_irq = self + .regs + .borrow_mut() + .write(field, value as u32, &self.char_backend); } else { eprintln!("write bad offset {offset} value {value}"); } @@ -590,15 +567,18 @@ impl PL011State { } } - pub fn can_receive(&self) -> bool { - // trace_pl011_can_receive(s->lcr, s->read_count, r); + fn can_receive(&self) -> u32 { let regs = self.regs.borrow(); - regs.read_count < regs.fifo_depth() + // trace_pl011_can_receive(s->lcr, s->read_count, r); + u32::from(regs.read_count < regs.fifo_depth()) } - pub fn receive(&self, ch: u32) { + fn receive(&self, buf: &[u8]) { + if buf.is_empty() { + return; + } let mut regs = self.regs.borrow_mut(); - let update_irq = !regs.loopback_enabled() && regs.put_fifo(ch); + let update_irq = !regs.loopback_enabled() && regs.put_fifo(buf[0].into()); // Release the BqlRefCell before calling self.update() drop(regs); @@ -607,10 +587,10 @@ impl PL011State { } } - pub fn event(&self, event: QEMUChrEvent) { + fn event(&self, event: Event) { let mut update_irq = false; let mut regs = self.regs.borrow_mut(); - if event == QEMUChrEvent::CHR_EVENT_BREAK && !regs.loopback_enabled() { + if event == Event::CHR_EVENT_BREAK && !regs.loopback_enabled() { update_irq = regs.put_fifo(registers::Data::BREAK.into()); } // Release the BqlRefCell before calling self.update() @@ -622,20 +602,8 @@ impl PL011State { } fn realize(&self) { - // SAFETY: self.char_backend has the correct size and alignment for a - // CharBackend object, and its callbacks are of the correct types. - unsafe { - qemu_chr_fe_set_handlers( - addr_of!(self.char_backend) as *mut CharBackend, - Some(pl011_can_receive), - Some(pl011_receive), - Some(pl011_event), - None, - addr_of!(*self).cast::() as *mut c_void, - core::ptr::null_mut(), - true, - ); - } + self.char_backend + .enable_handlers(self, Self::can_receive, Self::receive, Self::event); } fn reset_hold(&self, _type: ResetType) { @@ -666,43 +634,6 @@ const IRQMASK: [u32; 6] = [ Interrupt::E.0, ]; -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer, that has -/// the same size as [`PL011State`]. We also expect the device is -/// readable/writeable from one thread at any time. -pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int { - let state = NonNull::new(opaque).unwrap().cast::(); - unsafe { state.as_ref().can_receive().into() } -} - -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer, that has -/// the same size as [`PL011State`]. We also expect the device is -/// readable/writeable from one thread at any time. -/// -/// The buffer and size arguments must also be valid. -pub unsafe extern "C" fn pl011_receive(opaque: *mut c_void, buf: *const u8, size: c_int) { - let state = NonNull::new(opaque).unwrap().cast::(); - unsafe { - if size > 0 { - debug_assert!(!buf.is_null()); - state.as_ref().receive(u32::from(buf.read_volatile())); - } - } -} - -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer, that has -/// the same size as [`PL011State`]. We also expect the device is -/// readable/writeable from one thread at any time. -pub unsafe extern "C" fn pl011_event(opaque: *mut c_void, event: QEMUChrEvent) { - let state = NonNull::new(opaque).unwrap().cast::(); - unsafe { state.as_ref().event(event) } -} - /// # Safety /// /// We expect the FFI user of this function to pass a valid pointer for `chr` From aa50bc4fb9d4fc1dc027c4d70babe0acb6c09971 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 18 Dec 2024 12:31:46 +0100 Subject: [PATCH 0574/1179] rust: pl011: pass around registers::Data The values stored in the Fifo are instances of the bitfield-struct registers::Data. Convert as soon as possible the value written into DR, and always refer to the bitfield struct; it's generally cleaner other than PL011State::receive having to do a double conversion u8=>u32=>registers::Data. Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 4e282bc9e9d1..af93ae8bebe1 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -234,7 +234,7 @@ impl PL011Registers { match offset { DR => { // interrupts always checked - let _ = self.loopback_tx(value); + let _ = self.loopback_tx(value.into()); self.int_level |= Interrupt::TX.0; return true; } @@ -301,7 +301,7 @@ impl PL011Registers { #[inline] #[must_use] - fn loopback_tx(&mut self, value: u32) -> bool { + fn loopback_tx(&mut self, value: registers::Data) -> bool { // Caveat: // // In real hardware, TX loopback happens at the serial-bit level @@ -370,7 +370,7 @@ impl PL011Registers { } fn loopback_break(&mut self, enable: bool) -> bool { - enable && self.loopback_tx(registers::Data::BREAK.into()) + enable && self.loopback_tx(registers::Data::BREAK) } fn set_read_trigger(&mut self) { @@ -429,11 +429,11 @@ impl PL011Registers { } #[must_use] - pub fn put_fifo(&mut self, value: u32) -> bool { + pub fn put_fifo(&mut self, value: registers::Data) -> bool { let depth = self.fifo_depth(); assert!(depth > 0); let slot = (self.read_pos + self.read_count) & (depth - 1); - self.read_fifo[slot] = registers::Data::from(value); + self.read_fifo[slot] = value; self.read_count += 1; self.flags.set_receive_fifo_empty(false); if self.read_count == depth { @@ -578,7 +578,8 @@ impl PL011State { return; } let mut regs = self.regs.borrow_mut(); - let update_irq = !regs.loopback_enabled() && regs.put_fifo(buf[0].into()); + let c: u32 = buf[0].into(); + let update_irq = !regs.loopback_enabled() && regs.put_fifo(c.into()); // Release the BqlRefCell before calling self.update() drop(regs); @@ -591,7 +592,7 @@ impl PL011State { let mut update_irq = false; let mut regs = self.regs.borrow_mut(); if event == Event::CHR_EVENT_BREAK && !regs.loopback_enabled() { - update_irq = regs.put_fifo(registers::Data::BREAK.into()); + update_irq = regs.put_fifo(registers::Data::BREAK); } // Release the BqlRefCell before calling self.update() drop(regs); From 519088b7cf6dbdef08d8753b57aa29162b83d1a1 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 27 Feb 2025 18:19:35 +0100 Subject: [PATCH 0575/1179] rust: hpet: decode HPET registers into enums Generalize timer_and_addr() to decode all registers into a single enum HPETRegister, and use the TryInto derive to separate valid and invalid values. The main advantage lies in checking that all registers are enumerated in the "match" statements. Signed-off-by: Paolo Bonzini --- rust/Cargo.toml | 2 + rust/hw/char/pl011/src/lib.rs | 2 - rust/hw/timer/hpet/src/hpet.rs | 206 +++++++++++++++++---------------- 3 files changed, 111 insertions(+), 99 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5041d6291fd1..ab1185a81430 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -37,6 +37,8 @@ result_unit_err = "allow" should_implement_trait = "deny" # can be for a reason, e.g. in callbacks unused_self = "allow" +# common in device crates +upper_case_acronyms = "allow" # default-allow lints as_ptr_cast_mut = "deny" diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 45c13ba899e8..dbae76991c9a 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -12,8 +12,6 @@ //! See [`PL011State`](crate::device::PL011State) for the device model type and //! the [`registers`] module for register types. -#![allow(clippy::upper_case_acronyms)] - use qemu_api::c_str; mod device; diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index d989360ede89..20e0afdfca8a 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -48,8 +48,6 @@ const RTC_ISA_IRQ: usize = 8; const HPET_CLK_PERIOD: u64 = 10; // 10 ns const FS_PER_NS: u64 = 1000000; // 1000000 femtoseconds == 1 ns -/// General Capabilities and ID Register -const HPET_CAP_REG: u64 = 0x000; /// Revision ID (bits 0:7). Revision 1 is implemented (refer to v1.0a spec). const HPET_CAP_REV_ID_VALUE: u64 = 0x1; const HPET_CAP_REV_ID_SHIFT: usize = 0; @@ -65,8 +63,6 @@ const HPET_CAP_VENDER_ID_SHIFT: usize = 16; /// Main Counter Tick Period (bits 32:63) const HPET_CAP_CNT_CLK_PERIOD_SHIFT: usize = 32; -/// General Configuration Register -const HPET_CFG_REG: u64 = 0x010; /// Overall Enable (bit 0) const HPET_CFG_ENABLE_SHIFT: usize = 0; /// Legacy Replacement Route (bit 1) @@ -74,14 +70,6 @@ const HPET_CFG_LEG_RT_SHIFT: usize = 1; /// Other bits are reserved. const HPET_CFG_WRITE_MASK: u64 = 0x003; -/// General Interrupt Status Register -const HPET_INT_STATUS_REG: u64 = 0x020; - -/// Main Counter Value Register -const HPET_COUNTER_REG: u64 = 0x0f0; - -/// Timer N Configuration and Capability Register (masked by 0x18) -const HPET_TN_CFG_REG: u64 = 0x000; /// bit 0, 7, and bits 16:31 are reserved. /// bit 4, 5, 15, and bits 32:64 are read-only. const HPET_TN_CFG_WRITE_MASK: u64 = 0x7f4e; @@ -109,11 +97,51 @@ const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15; /// Timer N Interrupt Routing Capability (bits 32:63) const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32; -/// Timer N Comparator Value Register (masked by 0x18) -const HPET_TN_CMP_REG: u64 = 0x008; +#[derive(qemu_api_macros::TryInto)] +#[repr(u64)] +#[allow(non_camel_case_types)] +/// Timer registers, masked by 0x18 +enum TimerRegister { + /// Timer N Configuration and Capability Register + CFG = 0, + /// Timer N Comparator Value Register + CMP = 8, + /// Timer N FSB Interrupt Route Register + ROUTE = 16, +} + +#[derive(qemu_api_macros::TryInto)] +#[repr(u64)] +#[allow(non_camel_case_types)] +/// Global registers +enum GlobalRegister { + /// General Capabilities and ID Register + CAP = 0, + /// General Configuration Register + CFG = 0x10, + /// General Interrupt Status Register + INT_STATUS = 0x20, + /// Main Counter Value Register + COUNTER = 0xF0, +} -/// Timer N FSB Interrupt Route Register (masked by 0x18) -const HPET_TN_FSB_ROUTE_REG: u64 = 0x010; +enum HPETRegister<'a> { + /// Global register in the range from `0` to `0xff` + Global(GlobalRegister), + + /// Register in the timer block `0x100`...`0x3ff` + Timer(&'a BqlRefCell, TimerRegister), + + /// Invalid address + #[allow(dead_code)] + Unknown(hwaddr), +} + +struct HPETAddrDecode<'a> { + shift: u32, + len: u32, + reg: HPETRegister<'a>, +} const fn hpet_next_wrap(cur_tick: u64) -> u64 { (cur_tick | 0xffffffff) + 1 @@ -471,33 +499,21 @@ impl HPETTimer { self.update_irq(true); } - const fn read(&self, addr: hwaddr, _size: u32) -> u64 { - let shift: u64 = (addr & 4) * 8; - - match addr & !4 { - HPET_TN_CFG_REG => self.config >> shift, // including interrupt capabilities - HPET_TN_CMP_REG => self.cmp >> shift, // comparator register - HPET_TN_FSB_ROUTE_REG => self.fsb >> shift, - _ => { - // TODO: Add trace point - trace_hpet_ram_read_invalid() - // Reserved. - 0 - } + const fn read(&self, reg: TimerRegister) -> u64 { + use TimerRegister::*; + match reg { + CFG => self.config, // including interrupt capabilities + CMP => self.cmp, // comparator register + ROUTE => self.fsb, } } - fn write(&mut self, addr: hwaddr, value: u64, size: u32) { - let shift = ((addr & 4) * 8) as u32; - let len = std::cmp::min(size * 8, 64 - shift); - - match addr & !4 { - HPET_TN_CFG_REG => self.set_tn_cfg_reg(shift, len, value), - HPET_TN_CMP_REG => self.set_tn_cmp_reg(shift, len, value), - HPET_TN_FSB_ROUTE_REG => self.set_tn_fsb_route_reg(shift, len, value), - _ => { - // TODO: Add trace point - trace_hpet_ram_write_invalid() - // Reserved. - } + fn write(&mut self, reg: TimerRegister, value: u64, shift: u32, len: u32) { + use TimerRegister::*; + match reg { + CFG => self.set_tn_cfg_reg(shift, len, value), + CMP => self.set_tn_cmp_reg(shift, len, value), + ROUTE => self.set_tn_fsb_route_reg(shift, len, value), } } } @@ -749,76 +765,72 @@ impl HPETState { self.rtc_irq_level.set(0); } - fn timer_and_addr(&self, addr: hwaddr) -> Option<(&BqlRefCell, hwaddr)> { - let timer_id: usize = ((addr - 0x100) / 0x20) as usize; + fn decode(&self, mut addr: hwaddr, size: u32) -> HPETAddrDecode { + let shift = ((addr & 4) * 8) as u32; + let len = std::cmp::min(size * 8, 64 - shift); - // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) - if timer_id > self.num_timers.get() { - // TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id) - None + addr &= !4; + let reg = if (0..=0xff).contains(&addr) { + GlobalRegister::try_from(addr).map(HPETRegister::Global) } else { - // Keep the complete address so that HPETTimer's read and write could - // detect the invalid access. - Some((&self.timers[timer_id], addr & 0x1F)) - } + let timer_id: usize = ((addr - 0x100) / 0x20) as usize; + if timer_id <= self.num_timers.get() { + // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) + TimerRegister::try_from(addr) + .map(|reg| HPETRegister::Timer(&self.timers[timer_id], reg)) + } else { + // TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id) + Err(addr) + } + }; + + // reg is now a Result + // convert the Err case into HPETRegister as well + let reg = reg.unwrap_or_else(HPETRegister::Unknown); + HPETAddrDecode { shift, len, reg } } fn read(&self, addr: hwaddr, size: u32) -> u64 { - let shift: u64 = (addr & 4) * 8; - - // address range of all TN regs // TODO: Add trace point - trace_hpet_ram_read(addr) - if (0x100..=0x3ff).contains(&addr) { - match self.timer_and_addr(addr) { - None => 0, // Reserved, - Some((timer, tn_addr)) => timer.borrow_mut().read(tn_addr, size), - } - } else { - match addr & !4 { - HPET_CAP_REG => self.capability.get() >> shift, /* including HPET_PERIOD 0x004 */ - // (CNT_CLK_PERIOD field) - HPET_CFG_REG => self.config.get() >> shift, - HPET_COUNTER_REG => { - let cur_tick: u64 = if self.is_hpet_enabled() { - self.get_ticks() - } else { - self.counter.get() - }; - - // TODO: Add trace point - trace_hpet_ram_read_reading_counter(addr & 4, - // cur_tick) - cur_tick >> shift - } - HPET_INT_STATUS_REG => self.int_status.get() >> shift, - _ => { - // TODO: Add trace point- trace_hpet_ram_read_invalid() - // Reserved. - 0 + let HPETAddrDecode { shift, reg, .. } = self.decode(addr, size); + + use GlobalRegister::*; + use HPETRegister::*; + (match reg { + Timer(timer, tn_reg) => timer.borrow_mut().read(tn_reg), + Global(CAP) => self.capability.get(), /* including HPET_PERIOD 0x004 */ + Global(CFG) => self.config.get(), + Global(INT_STATUS) => self.int_status.get(), + Global(COUNTER) => { + // TODO: Add trace point + // trace_hpet_ram_read_reading_counter(addr & 4, cur_tick) + if self.is_hpet_enabled() { + self.get_ticks() + } else { + self.counter.get() } } - } + Unknown(_) => { + // TODO: Add trace point- trace_hpet_ram_read_invalid() + 0 + } + }) >> shift } fn write(&self, addr: hwaddr, value: u64, size: u32) { - let shift = ((addr & 4) * 8) as u32; - let len = std::cmp::min(size * 8, 64 - shift); + let HPETAddrDecode { shift, len, reg } = self.decode(addr, size); // TODO: Add trace point - trace_hpet_ram_write(addr, value) - if (0x100..=0x3ff).contains(&addr) { - match self.timer_and_addr(addr) { - None => (), // Reserved. - Some((timer, tn_addr)) => timer.borrow_mut().write(tn_addr, value, size), - } - } else { - match addr & !0x4 { - HPET_CAP_REG => {} // General Capabilities and ID Register: Read Only - HPET_CFG_REG => self.set_cfg_reg(shift, len, value), - HPET_INT_STATUS_REG => self.set_int_status_reg(shift, len, value), - HPET_COUNTER_REG => self.set_counter_reg(shift, len, value), - _ => { - // TODO: Add trace point - trace_hpet_ram_write_invalid() - // Reserved. - } + use GlobalRegister::*; + use HPETRegister::*; + match reg { + Timer(timer, tn_reg) => timer.borrow_mut().write(tn_reg, value, shift, len), + Global(CAP) => {} // General Capabilities and ID Register: Read Only + Global(CFG) => self.set_cfg_reg(shift, len, value), + Global(INT_STATUS) => self.set_int_status_reg(shift, len, value), + Global(COUNTER) => self.set_counter_reg(shift, len, value), + Unknown(_) => { + // TODO: Add trace point - trace_hpet_ram_write_invalid() } } } From 5778ce99971f7e09952a1efbac91d0c97d7a0fee Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 14:10:38 +0100 Subject: [PATCH 0576/1179] rust: cell: add full example of declaring a SysBusDevice Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/cell.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index 448638e8967e..ab0785a26928 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -73,6 +73,34 @@ //! QEMU device implementations is usually incorrect and can lead to //! thread-safety issues. //! +//! ### Example +//! +//! ``` +//! # use qemu_api::prelude::*; +//! # use qemu_api::{c_str, cell::BqlRefCell, irq::InterruptSource, irq::IRQState}; +//! # use qemu_api::{sysbus::SysBusDevice, qom::Owned, qom::ParentField}; +//! # const N_GPIOS: usize = 8; +//! # struct PL061Registers { /* ... */ } +//! # unsafe impl ObjectType for PL061State { +//! # type Class = ::Class; +//! # const TYPE_NAME: &'static std::ffi::CStr = c_str!("pl061"); +//! # } +//! struct PL061State { +//! parent_obj: ParentField, +//! +//! // Configuration is read-only after initialization +//! pullups: u32, +//! pulldowns: u32, +//! +//! // Single values shared with C code use BqlCell, in this case via InterruptSource +//! out: [InterruptSource; N_GPIOS], +//! interrupt: InterruptSource, +//! +//! // Larger state accessed by device methods uses BqlRefCell or Mutex +//! registers: BqlRefCell, +//! } +//! ``` +//! //! ### `BqlCell` //! //! [`BqlCell`] implements interior mutability by moving values in and out of From 094cd35913bd66228a9a3239e66b1f6f5d667d4b Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 13 Dec 2024 17:54:33 +0100 Subject: [PATCH 0577/1179] rust: qom: remove operations on &mut The dubious casts of mutable references to objects are not used anymore: the wrappers for qdev_init_clock_in and for IRQ and MMIO initialization can be called directly on the subclasses, without casts, plus they take a shared reference so they can just use "upcast()" instead of "upcast_mut()". Remove them. Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/memory.rs | 5 --- rust/qemu-api/src/prelude.rs | 1 - rust/qemu-api/src/qom.rs | 83 ------------------------------------ rust/qemu-api/tests/tests.rs | 34 +-------------- 4 files changed, 2 insertions(+), 121 deletions(-) diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs index eff9f09fd7ff..fdb1ea11fcf9 100644 --- a/rust/qemu-api/src/memory.rs +++ b/rust/qemu-api/src/memory.rs @@ -175,11 +175,6 @@ impl MemoryRegion { ) { unsafe { Self::do_init_io( - // self.0.as_mut_ptr() needed because Rust tries to call - // ObjectDeref::as_mut_ptr() on "&mut Self", instead of coercing - // to "&Self" and then calling MemoryRegion::as_mut_ptr(). - // Revisit if/when ObjectCastMut is not needed anymore; it is - // only used in a couple places for initialization. self.0.as_mut_ptr(), owner.cast::(), &ops.0, diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 634acf37a850..43bfcd5fcab8 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -17,7 +17,6 @@ pub use crate::qom::InterfaceType; pub use crate::qom::IsA; pub use crate::qom::Object; pub use crate::qom::ObjectCast; -pub use crate::qom::ObjectCastMut; pub use crate::qom::ObjectDeref; pub use crate::qom::ObjectClassMethods; pub use crate::qom::ObjectMethods; diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 2defbd235160..34d7bc0ef96f 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -463,90 +463,7 @@ where impl ObjectDeref for &T {} impl ObjectCast for &T {} -/// Trait for mutable type casting operations in the QOM hierarchy. -/// -/// This trait provides the mutable counterparts to [`ObjectCast`]'s conversion -/// functions. Unlike `ObjectCast`, this trait returns `Result` for fallible -/// conversions to preserve the original smart pointer if the cast fails. This -/// is necessary because mutable references cannot be copied, so a failed cast -/// must return ownership of the original reference. For example: -/// -/// ```ignore -/// let mut dev = get_device(); -/// // If this fails, we need the original `dev` back to try something else -/// match dev.dynamic_cast_mut::() { -/// Ok(foodev) => /* use foodev */, -/// Err(dev) => /* still have ownership of dev */ -/// } -/// ``` -pub trait ObjectCastMut: Sized + ObjectDeref + DerefMut -where - Self::Target: ObjectType, -{ - /// Safely convert from a derived type to one of its parent types. - /// - /// This is always safe; the [`IsA`] trait provides static verification - /// that `Self` dereferences to `U` or a child of `U`. - fn upcast_mut<'a, U: ObjectType>(self) -> &'a mut U - where - Self::Target: IsA, - Self: 'a, - { - // SAFETY: soundness is declared via IsA, which is an unsafe trait - unsafe { self.unsafe_cast_mut::() } - } - - /// Attempt to convert to a derived type. - /// - /// Returns `Ok(..)` if the object is of type `U`, or `Err(self)` if the - /// object if the conversion failed. This is verified at runtime by - /// checking the object's type information. - fn downcast_mut<'a, U: IsA>(self) -> Result<&'a mut U, Self> - where - Self: 'a, - { - self.dynamic_cast_mut::() - } - - /// Attempt to convert between any two types in the QOM hierarchy. - /// - /// Returns `Ok(..)` if the object is of type `U`, or `Err(self)` if the - /// object if the conversion failed. This is verified at runtime by - /// checking the object's type information. - fn dynamic_cast_mut<'a, U: ObjectType>(self) -> Result<&'a mut U, Self> - where - Self: 'a, - { - unsafe { - // SAFETY: upcasting to Object is always valid, and the - // return type is either NULL or the argument itself - let result: *mut U = - object_dynamic_cast(self.as_object_mut_ptr(), U::TYPE_NAME.as_ptr()).cast(); - - result.as_mut().ok_or(self) - } - } - - /// Convert to any QOM type without verification. - /// - /// # Safety - /// - /// What safety? You need to know yourself that the cast is correct; only - /// use when performance is paramount. It is still better than a raw - /// pointer `cast()`, which does not even check that you remain in the - /// realm of QOM `ObjectType`s. - /// - /// `unsafe_cast::()` is always safe. - unsafe fn unsafe_cast_mut<'a, U: ObjectType>(self) -> &'a mut U - where - Self: 'a, - { - unsafe { &mut *self.as_mut_ptr::().cast::() } - } -} - impl ObjectDeref for &mut T {} -impl ObjectCastMut for &mut T {} /// Trait a type must implement to be registered with QEMU. pub trait ObjectImpl: ObjectType + IsA { diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index e3985782a388..269122e7ec19 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -2,13 +2,10 @@ // Author(s): Manos Pitsidianakis // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ - ffi::{c_void, CStr}, - ptr::{addr_of, addr_of_mut}, -}; +use std::{ffi::CStr, ptr::addr_of}; use qemu_api::{ - bindings::{module_call_init, module_init_type, object_new, object_unref, qdev_prop_bool}, + bindings::{module_call_init, module_init_type, qdev_prop_bool}, c_str, cell::{self, BqlCell}, declare_properties, define_property, @@ -182,30 +179,3 @@ fn test_cast() { assert_eq!(addr_of!(*sbd_ref), p_ptr.cast()); } } - -#[test] -#[allow(clippy::shadow_unrelated)] -/// Test casts on mutable references. -fn test_cast_mut() { - init_qom(); - let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; - - let p_ref: &mut DummyState = unsafe { &mut *p }; - let obj_ref: &mut Object = p_ref.upcast_mut(); - assert_eq!(addr_of_mut!(*obj_ref), p.cast()); - - let sbd_ref: Result<&mut SysBusDevice, &mut Object> = obj_ref.dynamic_cast_mut(); - let obj_ref = sbd_ref.unwrap_err(); - - let dev_ref: Result<&mut DeviceState, &mut Object> = obj_ref.downcast_mut(); - let dev_ref = dev_ref.unwrap(); - assert_eq!(addr_of_mut!(*dev_ref), p.cast()); - - // SAFETY: the cast is wrong, but the value is only used for comparison - unsafe { - let sbd_ref: &mut SysBusDevice = obj_ref.unsafe_cast_mut(); - assert_eq!(addr_of_mut!(*sbd_ref), p.cast()); - - object_unref(p_ref.as_object_mut_ptr().cast::()); - } -} From 82c4d8a3b4e390dbb2601e11e07d291e760def7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 16 Jan 2025 22:00:47 +0100 Subject: [PATCH 0578/1179] qemu/compiler: Absorb 'clang-tsa.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We already have "qemu/compiler.h" for compiler-specific arrangements, automatically included by "qemu/osdep.h" for each source file. No need to explicitly include a header for a Clang particularity. Suggested-by: Pierrick Bouvier Reviewed-by: Pierrick Bouvier Reviewed-by: Alex Bennée Reviewed-by: Kevin Wolf Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250117170201.91182-1-philmd@linaro.org> --- block/create.c | 1 - bsd-user/qemu.h | 1 - include/block/block_int-common.h | 1 - include/block/graph-lock.h | 2 - include/exec/page-protection.h | 2 - include/qemu/clang-tsa.h | 114 ------------------------------- include/qemu/compiler.h | 96 ++++++++++++++++++++++++++ include/qemu/thread.h | 1 - tests/unit/test-bdrv-drain.c | 1 - tests/unit/test-block-iothread.c | 1 - util/qemu-thread-posix.c | 1 - 11 files changed, 96 insertions(+), 125 deletions(-) delete mode 100644 include/qemu/clang-tsa.h diff --git a/block/create.c b/block/create.c index 72abafb4c124..6b23a2167535 100644 --- a/block/create.c +++ b/block/create.c @@ -24,7 +24,6 @@ #include "qemu/osdep.h" #include "block/block_int.h" -#include "qemu/clang-tsa.h" #include "qemu/job.h" #include "qemu/main-loop.h" #include "qapi/qapi-commands-block-core.h" diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h index 3eaa14f3f56d..4e97c7963181 100644 --- a/bsd-user/qemu.h +++ b/bsd-user/qemu.h @@ -40,7 +40,6 @@ extern char **environ; #include "target.h" #include "exec/gdbstub.h" #include "exec/page-protection.h" -#include "qemu/clang-tsa.h" #include "accel/tcg/vcpu-state.h" #include "qemu-os.h" diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h index bb91a0f62fa1..ebb4e56a503b 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -28,7 +28,6 @@ #include "block/block-common.h" #include "block/block-global-state.h" #include "block/snapshot.h" -#include "qemu/clang-tsa.h" #include "qemu/iov.h" #include "qemu/rcu.h" #include "qemu/stats64.h" diff --git a/include/block/graph-lock.h b/include/block/graph-lock.h index dc8d94918436..2c26c721081e 100644 --- a/include/block/graph-lock.h +++ b/include/block/graph-lock.h @@ -20,8 +20,6 @@ #ifndef GRAPH_LOCK_H #define GRAPH_LOCK_H -#include "qemu/clang-tsa.h" - /** * Graph Lock API * This API provides a rwlock used to protect block layer diff --git a/include/exec/page-protection.h b/include/exec/page-protection.h index bae3355f62c8..3e0a8a033311 100644 --- a/include/exec/page-protection.h +++ b/include/exec/page-protection.h @@ -40,8 +40,6 @@ #ifdef CONFIG_USER_ONLY -#include "qemu/clang-tsa.h" - void TSA_NO_TSA mmap_lock(void); void TSA_NO_TSA mmap_unlock(void); bool have_mmap_lock(void); diff --git a/include/qemu/clang-tsa.h b/include/qemu/clang-tsa.h deleted file mode 100644 index ba06fb8c9249..000000000000 --- a/include/qemu/clang-tsa.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef CLANG_TSA_H -#define CLANG_TSA_H - -/* - * Copyright 2018 Jarkko Hietaniemi - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* http://clang.llvm.org/docs/ThreadSafetyAnalysis.html - * - * TSA is available since clang 3.6-ish. - */ -#ifdef __clang__ -# define TSA(x) __attribute__((x)) -#else -# define TSA(x) /* No TSA, make TSA attributes no-ops. */ -#endif - -/* TSA_CAPABILITY() is used to annotate typedefs: - * - * typedef pthread_mutex_t TSA_CAPABILITY("mutex") tsa_mutex; - */ -#define TSA_CAPABILITY(x) TSA(capability(x)) - -/* TSA_GUARDED_BY() is used to annotate global variables, - * the data is guarded: - * - * Foo foo TSA_GUARDED_BY(mutex); - */ -#define TSA_GUARDED_BY(x) TSA(guarded_by(x)) - -/* TSA_PT_GUARDED_BY() is used to annotate global pointers, the data - * behind the pointer is guarded. - * - * Foo* ptr TSA_PT_GUARDED_BY(mutex); - */ -#define TSA_PT_GUARDED_BY(x) TSA(pt_guarded_by(x)) - -/* The TSA_REQUIRES() is used to annotate functions: the caller of the - * function MUST hold the resource, the function will NOT release it. - * - * More than one mutex may be specified, comma-separated. - * - * void Foo(void) TSA_REQUIRES(mutex); - */ -#define TSA_REQUIRES(...) TSA(requires_capability(__VA_ARGS__)) -#define TSA_REQUIRES_SHARED(...) TSA(requires_shared_capability(__VA_ARGS__)) - -/* TSA_EXCLUDES() is used to annotate functions: the caller of the - * function MUST NOT hold resource, the function first acquires the - * resource, and then releases it. - * - * More than one mutex may be specified, comma-separated. - * - * void Foo(void) TSA_EXCLUDES(mutex); - */ -#define TSA_EXCLUDES(...) TSA(locks_excluded(__VA_ARGS__)) - -/* TSA_ACQUIRE() is used to annotate functions: the caller of the - * function MUST NOT hold the resource, the function will acquire the - * resource, but NOT release it. - * - * More than one mutex may be specified, comma-separated. - * - * void Foo(void) TSA_ACQUIRE(mutex); - */ -#define TSA_ACQUIRE(...) TSA(acquire_capability(__VA_ARGS__)) -#define TSA_ACQUIRE_SHARED(...) TSA(acquire_shared_capability(__VA_ARGS__)) - -/* TSA_RELEASE() is used to annotate functions: the caller of the - * function MUST hold the resource, but the function will then release it. - * - * More than one mutex may be specified, comma-separated. - * - * void Foo(void) TSA_RELEASE(mutex); - */ -#define TSA_RELEASE(...) TSA(release_capability(__VA_ARGS__)) -#define TSA_RELEASE_SHARED(...) TSA(release_shared_capability(__VA_ARGS__)) - -/* TSA_NO_TSA is used to annotate functions. Use only when you need to. - * - * void Foo(void) TSA_NO_TSA; - */ -#define TSA_NO_TSA TSA(no_thread_safety_analysis) - -/* - * TSA_ASSERT() is used to annotate functions: This function will assert that - * the lock is held. When it returns, the caller of the function is assumed to - * already hold the resource. - * - * More than one mutex may be specified, comma-separated. - */ -#define TSA_ASSERT(...) TSA(assert_capability(__VA_ARGS__)) -#define TSA_ASSERT_SHARED(...) TSA(assert_shared_capability(__VA_ARGS__)) - -#endif /* #ifndef CLANG_TSA_H */ diff --git a/include/qemu/compiler.h b/include/qemu/compiler.h index d904408e5ed9..496dac5ac111 100644 --- a/include/qemu/compiler.h +++ b/include/qemu/compiler.h @@ -207,6 +207,102 @@ # define QEMU_USED #endif +/* + * http://clang.llvm.org/docs/ThreadSafetyAnalysis.html + * + * TSA is available since clang 3.6-ish. + */ +#ifdef __clang__ +# define TSA(x) __attribute__((x)) +#else +# define TSA(x) /* No TSA, make TSA attributes no-ops. */ +#endif + +/* + * TSA_CAPABILITY() is used to annotate typedefs: + * + * typedef pthread_mutex_t TSA_CAPABILITY("mutex") tsa_mutex; + */ +#define TSA_CAPABILITY(x) TSA(capability(x)) + +/* + * TSA_GUARDED_BY() is used to annotate global variables, + * the data is guarded: + * + * Foo foo TSA_GUARDED_BY(mutex); + */ +#define TSA_GUARDED_BY(x) TSA(guarded_by(x)) + +/* + * TSA_PT_GUARDED_BY() is used to annotate global pointers, the data + * behind the pointer is guarded. + * + * Foo* ptr TSA_PT_GUARDED_BY(mutex); + */ +#define TSA_PT_GUARDED_BY(x) TSA(pt_guarded_by(x)) + +/* + * The TSA_REQUIRES() is used to annotate functions: the caller of the + * function MUST hold the resource, the function will NOT release it. + * + * More than one mutex may be specified, comma-separated. + * + * void Foo(void) TSA_REQUIRES(mutex); + */ +#define TSA_REQUIRES(...) TSA(requires_capability(__VA_ARGS__)) +#define TSA_REQUIRES_SHARED(...) TSA(requires_shared_capability(__VA_ARGS__)) + +/* + * TSA_EXCLUDES() is used to annotate functions: the caller of the + * function MUST NOT hold resource, the function first acquires the + * resource, and then releases it. + * + * More than one mutex may be specified, comma-separated. + * + * void Foo(void) TSA_EXCLUDES(mutex); + */ +#define TSA_EXCLUDES(...) TSA(locks_excluded(__VA_ARGS__)) + +/* + * TSA_ACQUIRE() is used to annotate functions: the caller of the + * function MUST NOT hold the resource, the function will acquire the + * resource, but NOT release it. + * + * More than one mutex may be specified, comma-separated. + * + * void Foo(void) TSA_ACQUIRE(mutex); + */ +#define TSA_ACQUIRE(...) TSA(acquire_capability(__VA_ARGS__)) +#define TSA_ACQUIRE_SHARED(...) TSA(acquire_shared_capability(__VA_ARGS__)) + +/* + * TSA_RELEASE() is used to annotate functions: the caller of the + * function MUST hold the resource, but the function will then release it. + * + * More than one mutex may be specified, comma-separated. + * + * void Foo(void) TSA_RELEASE(mutex); + */ +#define TSA_RELEASE(...) TSA(release_capability(__VA_ARGS__)) +#define TSA_RELEASE_SHARED(...) TSA(release_shared_capability(__VA_ARGS__)) + +/* + * TSA_NO_TSA is used to annotate functions. Use only when you need to. + * + * void Foo(void) TSA_NO_TSA; + */ +#define TSA_NO_TSA TSA(no_thread_safety_analysis) + +/* + * TSA_ASSERT() is used to annotate functions: This function will assert that + * the lock is held. When it returns, the caller of the function is assumed to + * already hold the resource. + * + * More than one mutex may be specified, comma-separated. + */ +#define TSA_ASSERT(...) TSA(assert_capability(__VA_ARGS__)) +#define TSA_ASSERT_SHARED(...) TSA(assert_shared_capability(__VA_ARGS__)) + /* * Ugly CPP trick that is like "defined FOO", but also works in C * code. Useful to replace #ifdef with "if" statements; assumes diff --git a/include/qemu/thread.h b/include/qemu/thread.h index 7eba27a70493..6f800aad31a9 100644 --- a/include/qemu/thread.h +++ b/include/qemu/thread.h @@ -3,7 +3,6 @@ #include "qemu/processor.h" #include "qemu/atomic.h" -#include "qemu/clang-tsa.h" typedef struct QemuCond QemuCond; typedef struct QemuSemaphore QemuSemaphore; diff --git a/tests/unit/test-bdrv-drain.c b/tests/unit/test-bdrv-drain.c index 98ad89b390c1..7410e6f35288 100644 --- a/tests/unit/test-bdrv-drain.c +++ b/tests/unit/test-bdrv-drain.c @@ -28,7 +28,6 @@ #include "system/block-backend.h" #include "qapi/error.h" #include "qemu/main-loop.h" -#include "qemu/clang-tsa.h" #include "iothread.h" static QemuEvent done_event; diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c index 7324ea4a68d3..2b358eaaa820 100644 --- a/tests/unit/test-block-iothread.c +++ b/tests/unit/test-block-iothread.c @@ -29,7 +29,6 @@ #include "system/block-backend.h" #include "qapi/error.h" #include "qobject/qdict.h" -#include "qemu/clang-tsa.h" #include "qemu/main-loop.h" #include "iothread.h" diff --git a/util/qemu-thread-posix.c b/util/qemu-thread-posix.c index 6fff4162ac6b..b2e26e21205b 100644 --- a/util/qemu-thread-posix.c +++ b/util/qemu-thread-posix.c @@ -17,7 +17,6 @@ #include "qemu-thread-common.h" #include "qemu/tsan.h" #include "qemu/bitmap.h" -#include "qemu/clang-tsa.h" #ifdef CONFIG_PTHREAD_SET_NAME_NP #include From 46a2cfc448931aeeb86bd721fa64e48ec0594299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:12:05 +0100 Subject: [PATCH 0579/1179] gdbstub: Clarify no more than @gdb_num_core_regs can be accessed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both CPUClass::gdb_read_register() and CPUClass::gdb_write_register() handlers are called from common gdbstub code, and won't be called with register index over CPUClass::gdb_num_core_regs: int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) { CPUClass *cc = CPU_GET_CLASS(cpu); if (reg < cc->gdb_num_core_regs) { return cc->gdb_read_register(cpu, buf, reg); } ... } static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { CPUClass *cc = CPU_GET_CLASS(cpu); if (reg < cc->gdb_num_core_regs) { return cc->gdb_write_register(cpu, mem_buf, reg); } ... } Clarify that in CPUClass docstring, and remove unreachable code on the microblaze and openrisc implementations. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Alex Bennée Message-Id: <20250122093028.52416-3-philmd@linaro.org> --- include/hw/core/cpu.h | 2 ++ target/microblaze/gdbstub.c | 5 ----- target/openrisc/gdbstub.c | 5 ----- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index fb397cdfc53d..7b6b22c431b7 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -124,7 +124,9 @@ struct SysemuCPUOps; * @get_pc: Callback for getting the Program Counter register. * As above, with the semantics of the target architecture. * @gdb_read_register: Callback for letting GDB read a register. + * No more than @gdb_num_core_regs registers can be read. * @gdb_write_register: Callback for letting GDB write a register. + * No more than @gdb_num_core_regs registers can be written. * @gdb_adjust_breakpoint: Callback for adjusting the address of a * breakpoint. Used by AVR to handle a gdb mis-feature with * its Harvard architecture split code and data. diff --git a/target/microblaze/gdbstub.c b/target/microblaze/gdbstub.c index 09d74e164d0d..d493681d38d3 100644 --- a/target/microblaze/gdbstub.c +++ b/target/microblaze/gdbstub.c @@ -110,14 +110,9 @@ int mb_cpu_gdb_read_stack_protect(CPUState *cs, GByteArray *mem_buf, int n) int mb_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) { - CPUClass *cc = CPU_GET_CLASS(cs); CPUMBState *env = cpu_env(cs); uint32_t tmp; - if (n > cc->gdb_num_core_regs) { - return 0; - } - tmp = ldl_p(mem_buf); switch (n) { diff --git a/target/openrisc/gdbstub.c b/target/openrisc/gdbstub.c index c2a77d5d4d52..45bba80d8789 100644 --- a/target/openrisc/gdbstub.c +++ b/target/openrisc/gdbstub.c @@ -47,14 +47,9 @@ int openrisc_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) int openrisc_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) { - CPUClass *cc = CPU_GET_CLASS(cs); CPUOpenRISCState *env = cpu_env(cs); uint32_t tmp; - if (n > cc->gdb_num_core_regs) { - return 0; - } - tmp = ldl_p(mem_buf); if (n < 32) { From 270dbee10cd31b86f0a3a8a2691026bdb0b9a071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 14:09:19 +0100 Subject: [PATCH 0580/1179] gdbstub: Check for TCG before calling tb_flush() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the tcg_enabled() check so the compiler can elide the call when TCG isn't available, allowing to remove the tb_flush() stub. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-4-philmd@linaro.org> --- accel/stubs/tcg-stub.c | 4 ---- gdbstub/system.c | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/accel/stubs/tcg-stub.c b/accel/stubs/tcg-stub.c index 7f4208fddf25..b2b9881bdfbd 100644 --- a/accel/stubs/tcg-stub.c +++ b/accel/stubs/tcg-stub.c @@ -14,10 +14,6 @@ #include "exec/tb-flush.h" #include "exec/exec-all.h" -void tb_flush(CPUState *cpu) -{ -} - G_NORETURN void cpu_loop_exit(CPUState *cpu) { g_assert_not_reached(); diff --git a/gdbstub/system.c b/gdbstub/system.c index 8ce79fa88cf4..7f047a285c8c 100644 --- a/gdbstub/system.c +++ b/gdbstub/system.c @@ -22,6 +22,7 @@ #include "system/cpus.h" #include "system/runstate.h" #include "system/replay.h" +#include "system/tcg.h" #include "hw/core/cpu.h" #include "hw/cpu/cluster.h" #include "hw/boards.h" @@ -171,7 +172,9 @@ static void gdb_vm_state_change(void *opaque, bool running, RunState state) } else { trace_gdbstub_hit_break(); } - tb_flush(cpu); + if (tcg_enabled()) { + tb_flush(cpu); + } ret = GDB_SIGNAL_TRAP; break; case RUN_STATE_PAUSED: From 0e86d7a71e8b5af201f2066a56071d3e23f4693c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:38:45 +0100 Subject: [PATCH 0581/1179] cpus: Cache CPUClass early in instance_init() handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cache CPUClass as early as possible, when the instance is initialized. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-5-philmd@linaro.org> --- cpu-target.c | 3 --- hw/core/cpu-common.c | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index 667688332c92..89874496a41e 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -134,9 +134,6 @@ const VMStateDescription vmstate_cpu_common = { bool cpu_exec_realizefn(CPUState *cpu, Error **errp) { - /* cache the cpu class for the hotpath */ - cpu->cc = CPU_GET_CLASS(cpu); - if (!accel_cpu_common_realize(cpu, errp)) { return false; } diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index cb79566cc51b..ff605059c155 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -238,6 +238,9 @@ static void cpu_common_initfn(Object *obj) { CPUState *cpu = CPU(obj); + /* cache the cpu class for the hotpath */ + cpu->cc = CPU_GET_CLASS(cpu); + gdb_init_cpu(cpu); cpu->cpu_index = UNASSIGNED_CPU_INDEX; cpu->cluster_index = UNASSIGNED_CLUSTER_INDEX; From 6042c47cddae04d0c1f0c750968f66a553611f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 16 Jan 2025 18:45:41 +0100 Subject: [PATCH 0582/1179] cpus: Keep default fields initialization in cpu_common_initfn() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cpu_common_initfn() is our target agnostic initializer, while cpu_exec_initfn() is the target specific one. The %as and %num_ases fields are not target specific, so initialize them in the common helper. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-6-philmd@linaro.org> --- cpu-target.c | 3 --- hw/core/cpu-common.c | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index 89874496a41e..75501a909dfb 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -234,9 +234,6 @@ void cpu_class_init_props(DeviceClass *dc) void cpu_exec_initfn(CPUState *cpu) { - cpu->as = NULL; - cpu->num_ases = 0; - #ifndef CONFIG_USER_ONLY cpu->memory = get_system_memory(); object_ref(OBJECT(cpu->memory)); diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index ff605059c155..71425cb7422c 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -244,6 +244,8 @@ static void cpu_common_initfn(Object *obj) gdb_init_cpu(cpu); cpu->cpu_index = UNASSIGNED_CPU_INDEX; cpu->cluster_index = UNASSIGNED_CLUSTER_INDEX; + cpu->as = NULL; + cpu->num_ases = 0; /* user-mode doesn't have configurable SMP topology */ /* the default value is changed by qemu_init_vcpu() for system-mode */ cpu->nr_threads = 1; From e92a883ffee93a8b811992b92b8a31ed54400c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 28 Jan 2020 18:28:59 +0100 Subject: [PATCH 0583/1179] accel/accel: Make TYPE_ACCEL abstract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no generic acceleration, we have to use specific implementations. Make the base class abstract. Fixes: b14a0b7469fa ("accel: Use QOM classes for accel types") Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Cornelia Huck Message-Id: <20200129212345.20547-3-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- accel/accel-target.c | 1 + 1 file changed, 1 insertion(+) diff --git a/accel/accel-target.c b/accel/accel-target.c index 08626c00c2d4..3236d6335b1b 100644 --- a/accel/accel-target.c +++ b/accel/accel-target.c @@ -38,6 +38,7 @@ static const TypeInfo accel_type = { .parent = TYPE_OBJECT, .class_size = sizeof(AccelClass), .instance_size = sizeof(AccelState), + .abstract = true, }; /* Lookup AccelClass from opt_name. Returns NULL if not found */ From de5a43192b55b5744ed7ec0e263d4d208c834617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 29 Nov 2023 16:55:37 +0100 Subject: [PATCH 0584/1179] accel/tcg: Remove pointless initialization of cflags_next_tb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cflags_next_tb is always re-initialized in the CPU Reset() handler in cpu_common_reset_hold(), no need to initialize it in cpu_common_initfn(). Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20240427155714.53669-13-philmd@linaro.org> --- hw/core/cpu-common.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 71425cb7422c..d5cd227fe6d5 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -249,7 +249,6 @@ static void cpu_common_initfn(Object *obj) /* user-mode doesn't have configurable SMP topology */ /* the default value is changed by qemu_init_vcpu() for system-mode */ cpu->nr_threads = 1; - cpu->cflags_next_tb = -1; /* allocate storage for thread info, initialise condition variables */ cpu->thread = g_new0(QemuThread, 1); From b28378850444547f8a8d64fe8d81e46d1e0bfeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 16:19:49 +0100 Subject: [PATCH 0585/1179] accel/tcg: Build tcg_flags helpers as common code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While cpu-exec.c is build for each target,tcg_flags helpers aren't target specific. Move them to cpu-exec-common.c to build them once. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-8-philmd@linaro.org> --- accel/tcg/cpu-exec-common.c | 33 +++++++++++++++++++++++++++++++++ accel/tcg/cpu-exec.c | 32 -------------------------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/accel/tcg/cpu-exec-common.c b/accel/tcg/cpu-exec-common.c index 6ecfc4e7c21e..100746d555ae 100644 --- a/accel/tcg/cpu-exec-common.c +++ b/accel/tcg/cpu-exec-common.c @@ -18,6 +18,7 @@ */ #include "qemu/osdep.h" +#include "exec/log.h" #include "system/cpus.h" #include "system/tcg.h" #include "qemu/plugin.h" @@ -25,6 +26,38 @@ bool tcg_allowed; +bool tcg_cflags_has(CPUState *cpu, uint32_t flags) +{ + return cpu->tcg_cflags & flags; +} + +void tcg_cflags_set(CPUState *cpu, uint32_t flags) +{ + cpu->tcg_cflags |= flags; +} + +uint32_t curr_cflags(CPUState *cpu) +{ + uint32_t cflags = cpu->tcg_cflags; + + /* + * Record gdb single-step. We should be exiting the TB by raising + * EXCP_DEBUG, but to simplify other tests, disable chaining too. + * + * For singlestep and -d nochain, suppress goto_tb so that + * we can log -d cpu,exec after every TB. + */ + if (unlikely(cpu->singlestep_enabled)) { + cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR | CF_SINGLE_STEP | 1; + } else if (qatomic_read(&one_insn_per_tb)) { + cflags |= CF_NO_GOTO_TB | 1; + } else if (qemu_loglevel_mask(CPU_LOG_TB_NOCHAIN)) { + cflags |= CF_NO_GOTO_TB; + } + + return cflags; +} + /* exit the current TB, but without causing any exception to be raised */ void cpu_loop_exit_noexc(CPUState *cpu) { diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index 8b773d88478b..be2ba199d3d7 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -148,38 +148,6 @@ static void init_delay_params(SyncClocks *sc, const CPUState *cpu) } #endif /* CONFIG USER ONLY */ -bool tcg_cflags_has(CPUState *cpu, uint32_t flags) -{ - return cpu->tcg_cflags & flags; -} - -void tcg_cflags_set(CPUState *cpu, uint32_t flags) -{ - cpu->tcg_cflags |= flags; -} - -uint32_t curr_cflags(CPUState *cpu) -{ - uint32_t cflags = cpu->tcg_cflags; - - /* - * Record gdb single-step. We should be exiting the TB by raising - * EXCP_DEBUG, but to simplify other tests, disable chaining too. - * - * For singlestep and -d nochain, suppress goto_tb so that - * we can log -d cpu,exec after every TB. - */ - if (unlikely(cpu->singlestep_enabled)) { - cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR | CF_SINGLE_STEP | 1; - } else if (qatomic_read(&one_insn_per_tb)) { - cflags |= CF_NO_GOTO_TB | 1; - } else if (qemu_loglevel_mask(CPU_LOG_TB_NOCHAIN)) { - cflags |= CF_NO_GOTO_TB; - } - - return cflags; -} - struct tb_desc { vaddr pc; uint64_t cs_base; From cbaae5338b62a6e005e9a97f419b968e04a7004f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 18:39:31 +0100 Subject: [PATCH 0586/1179] accel/tcg: Restrict tlb_init() / destroy() to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU TLB related methods to accel/tcg/ scope, in "internal-common.h". Suggested-by: Richard Henderson Reviewed-by: Pierrick Bouvier Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-9-philmd@linaro.org> --- accel/tcg/internal-common.h | 11 +++++++++++ accel/tcg/user-exec-stub.c | 11 +++++++++++ include/exec/exec-all.h | 16 ---------------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/accel/tcg/internal-common.h b/accel/tcg/internal-common.h index c8d714256cb9..d31867218395 100644 --- a/accel/tcg/internal-common.h +++ b/accel/tcg/internal-common.h @@ -53,6 +53,17 @@ TranslationBlock *tb_link_page(TranslationBlock *tb); void cpu_restore_state_from_tb(CPUState *cpu, TranslationBlock *tb, uintptr_t host_pc); +/** + * tlb_init - initialize a CPU's TLB + * @cpu: CPU whose TLB should be initialized + */ +void tlb_init(CPUState *cpu); +/** + * tlb_destroy - destroy a CPU's TLB + * @cpu: CPU whose TLB should be destroyed + */ +void tlb_destroy(CPUState *cpu); + bool tcg_exec_realizefn(CPUState *cpu, Error **errp); void tcg_exec_unrealizefn(CPUState *cpu); diff --git a/accel/tcg/user-exec-stub.c b/accel/tcg/user-exec-stub.c index 4fbe2dbdc883..1d52f48226ab 100644 --- a/accel/tcg/user-exec-stub.c +++ b/accel/tcg/user-exec-stub.c @@ -1,6 +1,7 @@ #include "qemu/osdep.h" #include "hw/core/cpu.h" #include "exec/replay-core.h" +#include "internal-common.h" void cpu_resume(CPUState *cpu) { @@ -18,6 +19,16 @@ void cpu_exec_reset_hold(CPUState *cpu) { } +/* User mode emulation does not support softmmu yet. */ + +void tlb_init(CPUState *cpu) +{ +} + +void tlb_destroy(CPUState *cpu) +{ +} + /* User mode emulation does not support record/replay yet. */ bool replay_exception(void) diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index d9045c9ac4c7..8eb0df48f945 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -29,16 +29,6 @@ #if !defined(CONFIG_USER_ONLY) && defined(CONFIG_TCG) /* cputlb.c */ -/** - * tlb_init - initialize a CPU's TLB - * @cpu: CPU whose TLB should be initialized - */ -void tlb_init(CPUState *cpu); -/** - * tlb_destroy - destroy a CPU's TLB - * @cpu: CPU whose TLB should be destroyed - */ -void tlb_destroy(CPUState *cpu); /** * tlb_flush_page: * @cpu: CPU whose TLB should be flushed @@ -223,12 +213,6 @@ void tlb_set_page(CPUState *cpu, vaddr addr, hwaddr paddr, int prot, int mmu_idx, vaddr size); #else -static inline void tlb_init(CPUState *cpu) -{ -} -static inline void tlb_destroy(CPUState *cpu) -{ -} static inline void tlb_flush_page(CPUState *cpu, vaddr addr) { } From fb26a3fd0e7b80c2b5bf6b90a36d5214153d0c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 13:57:20 +0100 Subject: [PATCH 0587/1179] accel/tcg: Restrict 'icount_align_option' global to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 740b1759734 ("cpu-timers, icount: new modules") we don't need to expose icount_align_option to all the system code, we can restrict it to TCG. Since it is used as a boolean, declare it as 'bool' type. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-10-philmd@linaro.org> --- accel/tcg/icount-common.c | 2 ++ accel/tcg/internal-common.h | 2 ++ include/system/cpus.h | 2 -- system/globals.c | 1 - 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/accel/tcg/icount-common.c b/accel/tcg/icount-common.c index b178dccec450..402d3e3f4e88 100644 --- a/accel/tcg/icount-common.c +++ b/accel/tcg/icount-common.c @@ -48,6 +48,8 @@ static bool icount_sleep = true; /* Arbitrarily pick 1MIPS as the minimum allowable speed. */ #define MAX_ICOUNT_SHIFT 10 +bool icount_align_option; + /* Do not count executed instructions */ ICountMode use_icount = ICOUNT_DISABLED; diff --git a/accel/tcg/internal-common.h b/accel/tcg/internal-common.h index d31867218395..7ef620d96318 100644 --- a/accel/tcg/internal-common.h +++ b/accel/tcg/internal-common.h @@ -17,6 +17,8 @@ extern int64_t max_advance; extern bool one_insn_per_tb; +extern bool icount_align_option; + /* * Return true if CS is not running in parallel with other cpus, either * because there are no other cpus or we are within an exclusive context. diff --git a/include/system/cpus.h b/include/system/cpus.h index 3d8fd368f326..1cffeaaf5c48 100644 --- a/include/system/cpus.h +++ b/include/system/cpus.h @@ -38,8 +38,6 @@ void resume_all_vcpus(void); void pause_all_vcpus(void); void cpu_stop_current(void); -extern int icount_align_option; - /* Unblock cpu */ void qemu_cpu_kick_self(void); diff --git a/system/globals.c b/system/globals.c index 316623bd20af..9640c9511e9a 100644 --- a/system/globals.c +++ b/system/globals.c @@ -58,7 +58,6 @@ unsigned int nb_prom_envs; const char *prom_envs[MAX_PROM_ENVS]; uint8_t *boot_splash_filedata; int only_migratable; /* turn it off unless user states otherwise */ -int icount_align_option; /* The bytes in qemu_uuid are in the order specified by RFC4122, _not_ in the * little-endian "wire format" described in the SMBIOS 2.6 specification. From 1501743654692ae6acf98ed8ec162b256eb54a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 24 Jan 2025 00:03:40 +0100 Subject: [PATCH 0588/1179] accel/tcg: Rename 'hw/core/tcg-cpu-ops.h' -> 'accel/tcg/cpu-ops.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TCGCPUOps structure makes more sense in the accelerator context rather than hardware emulation. Move it under the accel/tcg/ scope. Mechanical change doing: $ sed -i -e 's,hw/core/tcg-cpu-ops.h,accel/tcg/cpu-ops.h,g' \ $(git grep -l hw/core/tcg-cpu-ops.h) Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-11-philmd@linaro.org> --- MAINTAINERS | 2 +- accel/tcg/cpu-exec.c | 2 +- accel/tcg/cputlb.c | 2 +- accel/tcg/translate-all.c | 2 +- accel/tcg/user-exec.c | 2 +- accel/tcg/watchpoint.c | 2 +- bsd-user/signal.c | 2 +- hw/mips/jazz.c | 2 +- include/{hw/core/tcg-cpu-ops.h => accel/tcg/cpu-ops.h} | 0 linux-user/signal.c | 2 +- system/physmem.c | 2 +- target/alpha/cpu.c | 2 +- target/arm/cpu.c | 2 +- target/arm/tcg/cpu-v7m.c | 2 +- target/arm/tcg/cpu32.c | 2 +- target/arm/tcg/mte_helper.c | 2 +- target/arm/tcg/sve_helper.c | 2 +- target/avr/cpu.c | 2 +- target/avr/helper.c | 2 +- target/hexagon/cpu.c | 2 +- target/hppa/cpu.c | 2 +- target/i386/tcg/tcg-cpu.c | 2 +- target/loongarch/cpu.c | 2 +- target/m68k/cpu.c | 2 +- target/microblaze/cpu.c | 2 +- target/mips/cpu.c | 2 +- target/openrisc/cpu.c | 2 +- target/ppc/cpu_init.c | 2 +- target/riscv/cpu_helper.c | 2 +- target/riscv/tcg/tcg-cpu.c | 2 +- target/rx/cpu.c | 2 +- target/s390x/cpu.c | 2 +- target/s390x/tcg/mem_helper.c | 2 +- target/sh4/cpu.c | 2 +- target/sparc/cpu.c | 2 +- target/tricore/cpu.c | 2 +- target/xtensa/cpu.c | 2 +- 37 files changed, 36 insertions(+), 36 deletions(-) rename include/{hw/core/tcg-cpu-ops.h => accel/tcg/cpu-ops.h} (100%) diff --git a/MAINTAINERS b/MAINTAINERS index 692628cd7801..2d9ba810851c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -175,7 +175,7 @@ F: include/exec/helper-info.c.inc F: include/exec/page-protection.h F: include/system/cpus.h F: include/system/tcg.h -F: include/hw/core/tcg-cpu-ops.h +F: include/accel/tcg/cpu-ops.h F: host/include/*/host/cpuinfo.h F: util/cpuinfo-*.c F: include/tcg/ diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index be2ba199d3d7..3a3c45f52ed8 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -22,7 +22,7 @@ #include "qapi/error.h" #include "qapi/type-helpers.h" #include "hw/core/cpu.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "trace.h" #include "disas/disas.h" #include "exec/cpu-common.h" diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index ad158050a138..c8761683a0a6 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -19,7 +19,7 @@ #include "qemu/osdep.h" #include "qemu/main-loop.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/memory.h" diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index d4189c73860d..786e2f6f1a7d 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -58,7 +58,7 @@ #include "system/cpu-timers.h" #include "system/tcg.h" #include "qapi/error.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "tb-jmp-cache.h" #include "tb-hash.h" #include "tb-context.h" diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index 0561c4f6dc75..c4454100ad77 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -17,7 +17,7 @@ * License along with this library; if not, see . */ #include "qemu/osdep.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "disas/disas.h" #include "exec/exec-all.h" #include "tcg/tcg.h" diff --git a/accel/tcg/watchpoint.c b/accel/tcg/watchpoint.c index af57d182d5bf..40112b2b2e70 100644 --- a/accel/tcg/watchpoint.c +++ b/accel/tcg/watchpoint.c @@ -26,7 +26,7 @@ #include "tb-internal.h" #include "system/tcg.h" #include "system/replay.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "hw/core/cpu.h" #include "internal-common.h" diff --git a/bsd-user/signal.c b/bsd-user/signal.c index ff2ccbbf605a..ab1d9ddd50f6 100644 --- a/bsd-user/signal.c +++ b/bsd-user/signal.c @@ -29,7 +29,7 @@ #include "gdbstub/user.h" #include "signal-common.h" #include "trace.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "host-signal.h" /* target_siginfo_t must fit in gdbstub's siginfo save area. */ diff --git a/hw/mips/jazz.c b/hw/mips/jazz.c index c89610639a9e..1700c3765ded 100644 --- a/hw/mips/jazz.c +++ b/hw/mips/jazz.c @@ -50,7 +50,7 @@ #include "qemu/error-report.h" #include "qemu/help_option.h" #ifdef CONFIG_TCG -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #endif /* CONFIG_TCG */ #include "cpu.h" diff --git a/include/hw/core/tcg-cpu-ops.h b/include/accel/tcg/cpu-ops.h similarity index 100% rename from include/hw/core/tcg-cpu-ops.h rename to include/accel/tcg/cpu-ops.h diff --git a/linux-user/signal.c b/linux-user/signal.c index 81a98c6d02f5..4799b79dedee 100644 --- a/linux-user/signal.c +++ b/linux-user/signal.c @@ -21,7 +21,7 @@ #include "qemu/cutils.h" #include "gdbstub/user.h" #include "exec/page-protection.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include #include diff --git a/system/physmem.c b/system/physmem.c index eff8b55c2dda..8c1736f84ee7 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -28,7 +28,7 @@ #include "qemu/lockable.h" #ifdef CONFIG_TCG -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #endif /* CONFIG_TCG */ #include "exec/exec-all.h" diff --git a/target/alpha/cpu.c b/target/alpha/cpu.c index f5dd74498761..57e41fcd784a 100644 --- a/target/alpha/cpu.c +++ b/target/alpha/cpu.c @@ -227,7 +227,7 @@ static const struct SysemuCPUOps alpha_sysemu_ops = { }; #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps alpha_tcg_ops = { .initialize = alpha_translate_init, diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 656070afb597..ac1ceec2110a 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -29,7 +29,7 @@ #include "cpu.h" #ifdef CONFIG_TCG #include "exec/translation-block.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #endif /* CONFIG_TCG */ #include "internals.h" #include "cpu-features.h" diff --git a/target/arm/tcg/cpu-v7m.c b/target/arm/tcg/cpu-v7m.c index 03acdf83e006..29a41fde694b 100644 --- a/target/arm/tcg/cpu-v7m.c +++ b/target/arm/tcg/cpu-v7m.c @@ -10,7 +10,7 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "internals.h" #if !defined(CONFIG_USER_ONLY) diff --git a/target/arm/tcg/cpu32.c b/target/arm/tcg/cpu32.c index 0f1c5bc87e88..2c45b7eddda7 100644 --- a/target/arm/tcg/cpu32.c +++ b/target/arm/tcg/cpu32.c @@ -10,7 +10,7 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "internals.h" #include "target/arm/idau.h" #if !defined(CONFIG_USER_ONLY) diff --git a/target/arm/tcg/mte_helper.c b/target/arm/tcg/mte_helper.c index f72ce2ae0d4f..5d6d8a17ae89 100644 --- a/target/arm/tcg/mte_helper.c +++ b/target/arm/tcg/mte_helper.c @@ -31,7 +31,7 @@ #endif #include "exec/cpu_ldst.h" #include "exec/helper-proto.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "qapi/error.h" #include "qemu/guest-random.h" #include "mte_helper.h" diff --git a/target/arm/tcg/sve_helper.c b/target/arm/tcg/sve_helper.c index c206ca65ceba..d786b4b11189 100644 --- a/target/arm/tcg/sve_helper.c +++ b/target/arm/tcg/sve_helper.c @@ -28,7 +28,7 @@ #include "tcg/tcg.h" #include "vec_internal.h" #include "sve_ldst_internal.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #ifdef CONFIG_USER_ONLY #include "user/page-protection.h" #endif diff --git a/target/avr/cpu.c b/target/avr/cpu.c index 8a126ff32228..5a0e21465e5e 100644 --- a/target/avr/cpu.c +++ b/target/avr/cpu.c @@ -203,7 +203,7 @@ static const struct SysemuCPUOps avr_sysemu_ops = { .get_phys_page_debug = avr_cpu_get_phys_page_debug, }; -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps avr_tcg_ops = { .initialize = avr_cpu_tcg_init, diff --git a/target/avr/helper.c b/target/avr/helper.c index 345708a1b39f..9ea6870e44dc 100644 --- a/target/avr/helper.c +++ b/target/avr/helper.c @@ -22,7 +22,7 @@ #include "qemu/log.h" #include "qemu/error-report.h" #include "cpu.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/cpu_ldst.h" diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 0b7fc98f6ce1..238e63bcea42 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -321,7 +321,7 @@ static void hexagon_cpu_init(Object *obj) { } -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps hexagon_tcg_ops = { .initialize = hexagon_translate_init, diff --git a/target/hppa/cpu.c b/target/hppa/cpu.c index 5655677431c6..4bb5cff624e9 100644 --- a/target/hppa/cpu.c +++ b/target/hppa/cpu.c @@ -245,7 +245,7 @@ static const struct SysemuCPUOps hppa_sysemu_ops = { }; #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps hppa_tcg_ops = { .initialize = hppa_translate_init, diff --git a/target/i386/tcg/tcg-cpu.c b/target/i386/tcg/tcg-cpu.c index 14ee038079a7..f09ee813ac99 100644 --- a/target/i386/tcg/tcg-cpu.c +++ b/target/i386/tcg/tcg-cpu.c @@ -105,7 +105,7 @@ static bool x86_debug_check_breakpoint(CPUState *cs) } #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps x86_tcg_ops = { .initialize = tcg_x86_init, diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index ac514a15fba8..b4b82425b18c 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -857,7 +857,7 @@ static void loongarch_cpu_dump_state(CPUState *cs, FILE *f, int flags) } #ifdef CONFIG_TCG -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps loongarch_tcg_ops = { .initialize = loongarch_translate_init, diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index 2617d8f6ede1..eedda07c2ab4 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -582,7 +582,7 @@ static const struct SysemuCPUOps m68k_sysemu_ops = { }; #endif /* !CONFIG_USER_ONLY */ -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps m68k_tcg_ops = { .initialize = m68k_tcg_init, diff --git a/target/microblaze/cpu.c b/target/microblaze/cpu.c index f114789abd82..13d194cef880 100644 --- a/target/microblaze/cpu.c +++ b/target/microblaze/cpu.c @@ -419,7 +419,7 @@ static const struct SysemuCPUOps mb_sysemu_ops = { }; #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps mb_tcg_ops = { .initialize = mb_tcg_init, diff --git a/target/mips/cpu.c b/target/mips/cpu.c index 47cd7cfdcef5..0b267d2e507e 100644 --- a/target/mips/cpu.c +++ b/target/mips/cpu.c @@ -544,7 +544,7 @@ static const Property mips_cpu_properties[] = { }; #ifdef CONFIG_TCG -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps mips_tcg_ops = { .initialize = mips_tcg_init, .translate_code = mips_translate_code, diff --git a/target/openrisc/cpu.c b/target/openrisc/cpu.c index b7bab0d7abf8..0669ba2fd108 100644 --- a/target/openrisc/cpu.c +++ b/target/openrisc/cpu.c @@ -232,7 +232,7 @@ static const struct SysemuCPUOps openrisc_sysemu_ops = { }; #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps openrisc_tcg_ops = { .initialize = openrisc_translate_init, diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index 062a6e85fbae..425049ab0936 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -7430,7 +7430,7 @@ static const struct SysemuCPUOps ppc_sysemu_ops = { #endif #ifdef CONFIG_TCG -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps ppc_tcg_ops = { .initialize = ppc_translate_init, diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 3f5fd861a80b..34092f372df6 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -27,7 +27,7 @@ #include "exec/page-protection.h" #include "instmap.h" #include "tcg/tcg-op.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "trace.h" #include "semihosting/common-semi.h" #include "system/cpu-timers.h" diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index f1d971eec1f3..70f4c7984aaa 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -31,7 +31,7 @@ #include "qemu/error-report.h" #include "qemu/log.h" #include "hw/core/accel-cpu.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "tcg/tcg.h" #ifndef CONFIG_USER_ONLY #include "hw/boards.h" diff --git a/target/rx/cpu.c b/target/rx/cpu.c index 37a6fdd569b3..7d5fcbf76ac4 100644 --- a/target/rx/cpu.c +++ b/target/rx/cpu.c @@ -200,7 +200,7 @@ static const struct SysemuCPUOps rx_sysemu_ops = { }; #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps rx_tcg_ops = { .initialize = rx_translate_init, diff --git a/target/s390x/cpu.c b/target/s390x/cpu.c index 97d41c23de7c..3bea014f9ee9 100644 --- a/target/s390x/cpu.c +++ b/target/s390x/cpu.c @@ -322,7 +322,7 @@ static const Property s390x_cpu_properties[] = { #endif #ifdef CONFIG_TCG -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" void cpu_get_tb_cpu_state(CPUS390XState *env, vaddr *pc, uint64_t *cs_base, uint32_t *pflags) diff --git a/target/s390x/tcg/mem_helper.c b/target/s390x/tcg/mem_helper.c index c6ab2901e5ad..ea9fa64d6b44 100644 --- a/target/s390x/tcg/mem_helper.c +++ b/target/s390x/tcg/mem_helper.c @@ -28,7 +28,7 @@ #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/cpu_ldst.h" -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" #include "qemu/int128.h" #include "qemu/atomic128.h" diff --git a/target/sh4/cpu.c b/target/sh4/cpu.c index ccfe222bdf31..22cdf9b4e122 100644 --- a/target/sh4/cpu.c +++ b/target/sh4/cpu.c @@ -256,7 +256,7 @@ static const struct SysemuCPUOps sh4_sysemu_ops = { }; #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps superh_tcg_ops = { .initialize = sh4_translate_init, diff --git a/target/sparc/cpu.c b/target/sparc/cpu.c index fbd38ec334a9..e3b46137178d 100644 --- a/target/sparc/cpu.c +++ b/target/sparc/cpu.c @@ -992,7 +992,7 @@ static const struct SysemuCPUOps sparc_sysemu_ops = { #endif #ifdef CONFIG_TCG -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps sparc_tcg_ops = { .initialize = sparc_tcg_init, diff --git a/target/tricore/cpu.c b/target/tricore/cpu.c index 95202fadbfdf..eb794674c8da 100644 --- a/target/tricore/cpu.c +++ b/target/tricore/cpu.c @@ -168,7 +168,7 @@ static const struct SysemuCPUOps tricore_sysemu_ops = { .get_phys_page_debug = tricore_cpu_get_phys_page_debug, }; -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps tricore_tcg_ops = { .initialize = tricore_tcg_init, diff --git a/target/xtensa/cpu.c b/target/xtensa/cpu.c index 4eb699d1f456..efbfe73fcfbc 100644 --- a/target/xtensa/cpu.c +++ b/target/xtensa/cpu.c @@ -228,7 +228,7 @@ static const struct SysemuCPUOps xtensa_sysemu_ops = { }; #endif -#include "hw/core/tcg-cpu-ops.h" +#include "accel/tcg/cpu-ops.h" static const TCGCPUOps xtensa_tcg_ops = { .initialize = xtensa_translate_init, From b12a0f856691264bc1a8f0ed1e5e62649cea7fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 11:11:24 +0100 Subject: [PATCH 0589/1179] accel: Rename 'hw/core/accel-cpu.h' -> 'accel/accel-cpu-target.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AccelCPUClass is for accelerator to initialize target specific features of a vCPU. Not really related to hardware emulation, rename "hw/core/accel-cpu.h" as "accel/accel-cpu-target.h" (using the explicit -target suffix). More importantly, target specific header often access the target specific definitions which are in each target/FOO/cpu.h header, usually included generically as "cpu.h" relative to target/FOO/. However, there is already a "cpu.h" in hw/core/ which takes precedence. This change allows "accel-cpu-target.h" to include a target "cpu.h". Mechanical change doing: $ git mv include/hw/core/accel-cpu.h \ include/accel/accel-cpu-target.h $ sed -i -e 's,hw/core/accel-cpu.h,accel/accel-cpu-target.h,' \ $(git grep -l hw/core/accel-cpu.h) and renaming header guard 'ACCEL_CPU_TARGET_H'. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-12-philmd@linaro.org> --- MAINTAINERS | 2 +- accel/accel-target.c | 2 +- cpu-target.c | 2 +- include/{hw/core/accel-cpu.h => accel/accel-cpu-target.h} | 4 ++-- target/i386/hvf/hvf-cpu.c | 2 +- target/i386/kvm/kvm-cpu.c | 2 +- target/i386/tcg/tcg-cpu.c | 2 +- target/ppc/kvm.c | 2 +- target/riscv/kvm/kvm-cpu.c | 2 +- target/riscv/tcg/tcg-cpu.c | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) rename include/{hw/core/accel-cpu.h => accel/accel-cpu-target.h} (95%) diff --git a/MAINTAINERS b/MAINTAINERS index 2d9ba810851c..a0e462d03ca8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -499,7 +499,7 @@ R: Paolo Bonzini S: Maintained F: include/qemu/accel.h F: include/system/accel-*.h -F: include/hw/core/accel-cpu.h +F: include/accel/accel-cpu-target.h F: accel/accel-*.c F: accel/Makefile.objs F: accel/stubs/Makefile.objs diff --git a/accel/accel-target.c b/accel/accel-target.c index 3236d6335b1b..835872746264 100644 --- a/accel/accel-target.c +++ b/accel/accel-target.c @@ -27,7 +27,7 @@ #include "qemu/accel.h" #include "cpu.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" #ifndef CONFIG_USER_ONLY #include "accel-system.h" diff --git a/cpu-target.c b/cpu-target.c index 75501a909dfb..f97f3a14751d 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -44,7 +44,7 @@ #include "exec/tb-flush.h" #include "exec/translation-block.h" #include "exec/log.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" #include "trace/trace-root.h" #include "qemu/accel.h" diff --git a/include/hw/core/accel-cpu.h b/include/accel/accel-cpu-target.h similarity index 95% rename from include/hw/core/accel-cpu.h rename to include/accel/accel-cpu-target.h index 24dad45ab9eb..0a8e518600d1 100644 --- a/include/hw/core/accel-cpu.h +++ b/include/accel/accel-cpu-target.h @@ -8,8 +8,8 @@ * See the COPYING file in the top-level directory. */ -#ifndef ACCEL_CPU_H -#define ACCEL_CPU_H +#ifndef ACCEL_CPU_TARGET_H +#define ACCEL_CPU_TARGET_H /* * This header is used to define new accelerator-specific target-specific diff --git a/target/i386/hvf/hvf-cpu.c b/target/i386/hvf/hvf-cpu.c index 560b5a059402..b5f4c80028fc 100644 --- a/target/i386/hvf/hvf-cpu.c +++ b/target/i386/hvf/hvf-cpu.c @@ -14,7 +14,7 @@ #include "system/system.h" #include "hw/boards.h" #include "system/hvf.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" #include "hvf-i386.h" static void hvf_cpu_max_instance_init(X86CPU *cpu) diff --git a/target/i386/kvm/kvm-cpu.c b/target/i386/kvm/kvm-cpu.c index 1bda403f88b4..6269fa80452c 100644 --- a/target/i386/kvm/kvm-cpu.c +++ b/target/i386/kvm/kvm-cpu.c @@ -15,7 +15,7 @@ #include "hw/boards.h" #include "kvm_i386.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" static void kvm_set_guest_phys_bits(CPUState *cs) { diff --git a/target/i386/tcg/tcg-cpu.c b/target/i386/tcg/tcg-cpu.c index f09ee813ac99..b8aff825eec7 100644 --- a/target/i386/tcg/tcg-cpu.c +++ b/target/i386/tcg/tcg-cpu.c @@ -21,7 +21,7 @@ #include "cpu.h" #include "helper-tcg.h" #include "qemu/accel.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" #include "exec/translation-block.h" #include "tcg-cpu.h" diff --git a/target/ppc/kvm.c b/target/ppc/kvm.c index 966c2c657234..216638dee409 100644 --- a/target/ppc/kvm.c +++ b/target/ppc/kvm.c @@ -49,7 +49,7 @@ #include "elf.h" #include "system/kvm_int.h" #include "system/kvm.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" #include CONFIG_DEVICES diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index 7f3b59cb72c9..4ffeeaa1c953 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -32,7 +32,7 @@ #include "system/kvm_int.h" #include "cpu.h" #include "trace.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" #include "hw/pci/pci.h" #include "exec/memattrs.h" #include "exec/address-spaces.h" diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index 70f4c7984aaa..5aef9eef3666 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -30,7 +30,7 @@ #include "qemu/accel.h" #include "qemu/error-report.h" #include "qemu/log.h" -#include "hw/core/accel-cpu.h" +#include "accel/accel-cpu-target.h" #include "accel/tcg/cpu-ops.h" #include "tcg/tcg.h" #ifndef CONFIG_USER_ONLY From 0f66536a012b2d1b02818bbb2d24485205fc2f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 13:39:05 +0100 Subject: [PATCH 0590/1179] accel: Forward-declare AccelOpsClass in 'qemu/typedefs.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The heavily imported "system/cpus.h" header includes "accel-ops.h" to get AccelOpsClass type declaration. Reduce headers pressure by forward declaring it in "qemu/typedefs.h", where we already declare the AccelCPUState type. Reduce "system/cpus.h" inclusions by only including "system/accel-ops.h" when necessary. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-14-philmd@linaro.org> --- accel/accel-system.c | 1 + accel/hvf/hvf-accel-ops.c | 1 + accel/kvm/kvm-accel-ops.c | 1 + accel/qtest/qtest.c | 1 + accel/tcg/cpu-exec-common.c | 1 - accel/tcg/cpu-exec.c | 1 - accel/tcg/monitor.c | 1 - accel/tcg/tcg-accel-ops.c | 1 + accel/tcg/translate-all.c | 1 - accel/xen/xen-all.c | 1 + cpu-common.c | 1 - cpu-target.c | 1 + gdbstub/system.c | 1 + include/qemu/typedefs.h | 1 + include/system/accel-ops.h | 1 - include/system/cpus.h | 2 -- system/cpus.c | 1 + target/i386/nvmm/nvmm-accel-ops.c | 1 + target/i386/whpx/whpx-accel-ops.c | 1 + 19 files changed, 12 insertions(+), 8 deletions(-) diff --git a/accel/accel-system.c b/accel/accel-system.c index a7596aef59d1..5df49fbe831d 100644 --- a/accel/accel-system.c +++ b/accel/accel-system.c @@ -26,6 +26,7 @@ #include "qemu/osdep.h" #include "qemu/accel.h" #include "hw/boards.h" +#include "system/accel-ops.h" #include "system/cpus.h" #include "qemu/error-report.h" #include "accel-system.h" diff --git a/accel/hvf/hvf-accel-ops.c b/accel/hvf/hvf-accel-ops.c index 945ba720513a..12fc30c27611 100644 --- a/accel/hvf/hvf-accel-ops.c +++ b/accel/hvf/hvf-accel-ops.c @@ -54,6 +54,7 @@ #include "exec/exec-all.h" #include "gdbstub/enums.h" #include "hw/boards.h" +#include "system/accel-ops.h" #include "system/cpus.h" #include "system/hvf.h" #include "system/hvf_int.h" diff --git a/accel/kvm/kvm-accel-ops.c b/accel/kvm/kvm-accel-ops.c index a81e8f3b03bb..54ea60909e2f 100644 --- a/accel/kvm/kvm-accel-ops.c +++ b/accel/kvm/kvm-accel-ops.c @@ -16,6 +16,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" +#include "system/accel-ops.h" #include "system/kvm.h" #include "system/kvm_int.h" #include "system/runstate.h" diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index ad7e3441a5a8..7fae80f6a1be 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -18,6 +18,7 @@ #include "qemu/option.h" #include "qemu/config-file.h" #include "qemu/accel.h" +#include "system/accel-ops.h" #include "system/qtest.h" #include "system/cpus.h" #include "qemu/guest-random.h" diff --git a/accel/tcg/cpu-exec-common.c b/accel/tcg/cpu-exec-common.c index 100746d555ae..c5c513f1e4a0 100644 --- a/accel/tcg/cpu-exec-common.c +++ b/accel/tcg/cpu-exec-common.c @@ -19,7 +19,6 @@ #include "qemu/osdep.h" #include "exec/log.h" -#include "system/cpus.h" #include "system/tcg.h" #include "qemu/plugin.h" #include "internal-common.h" diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index 3a3c45f52ed8..ef3d967e3af0 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -33,7 +33,6 @@ #include "qemu/rcu.h" #include "exec/log.h" #include "qemu/main-loop.h" -#include "system/cpus.h" #include "exec/cpu-all.h" #include "system/cpu-timers.h" #include "exec/replay-core.h" diff --git a/accel/tcg/monitor.c b/accel/tcg/monitor.c index ae1dbeb79f84..eeb38a4d9ce6 100644 --- a/accel/tcg/monitor.c +++ b/accel/tcg/monitor.c @@ -13,7 +13,6 @@ #include "qapi/type-helpers.h" #include "qapi/qapi-commands-machine.h" #include "monitor/monitor.h" -#include "system/cpus.h" #include "system/cpu-timers.h" #include "system/tcg.h" #include "tcg/tcg.h" diff --git a/accel/tcg/tcg-accel-ops.c b/accel/tcg/tcg-accel-ops.c index 6e3f1fa92b24..132c5d146138 100644 --- a/accel/tcg/tcg-accel-ops.c +++ b/accel/tcg/tcg-accel-ops.c @@ -26,6 +26,7 @@ */ #include "qemu/osdep.h" +#include "system/accel-ops.h" #include "system/tcg.h" #include "system/replay.h" #include "system/cpu-timers.h" diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index 786e2f6f1a7d..0914d6e98b20 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -54,7 +54,6 @@ #include "qemu/cacheinfo.h" #include "qemu/timer.h" #include "exec/log.h" -#include "system/cpus.h" #include "system/cpu-timers.h" #include "system/tcg.h" #include "qapi/error.h" diff --git a/accel/xen/xen-all.c b/accel/xen/xen-all.c index 852e9fbe5feb..7aa28b9ab939 100644 --- a/accel/xen/xen-all.c +++ b/accel/xen/xen-all.c @@ -18,6 +18,7 @@ #include "hw/xen/xen_igd.h" #include "chardev/char.h" #include "qemu/accel.h" +#include "system/accel-ops.h" #include "system/cpus.h" #include "system/xen.h" #include "system/runstate.h" diff --git a/cpu-common.c b/cpu-common.c index 4248b2d727ed..f5dcc2d136bc 100644 --- a/cpu-common.c +++ b/cpu-common.c @@ -21,7 +21,6 @@ #include "qemu/main-loop.h" #include "exec/cpu-common.h" #include "hw/core/cpu.h" -#include "system/cpus.h" #include "qemu/lockable.h" #include "trace/trace-root.h" diff --git a/cpu-target.c b/cpu-target.c index f97f3a14751d..20933bde7d40 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -35,6 +35,7 @@ #include "exec/address-spaces.h" #include "exec/memory.h" #endif +#include "system/accel-ops.h" #include "system/cpus.h" #include "system/tcg.h" #include "exec/tswap.h" diff --git a/gdbstub/system.c b/gdbstub/system.c index 7f047a285c8c..416c1dbe1e92 100644 --- a/gdbstub/system.c +++ b/gdbstub/system.c @@ -19,6 +19,7 @@ #include "gdbstub/commands.h" #include "exec/hwaddr.h" #include "exec/tb-flush.h" +#include "system/accel-ops.h" #include "system/cpus.h" #include "system/runstate.h" #include "system/replay.h" diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h index 3d84efcac47a..465cc5017730 100644 --- a/include/qemu/typedefs.h +++ b/include/qemu/typedefs.h @@ -22,6 +22,7 @@ * Please keep this list in case-insensitive alphabetical order. */ typedef struct AccelCPUState AccelCPUState; +typedef struct AccelOpsClass AccelOpsClass; typedef struct AccelState AccelState; typedef struct AddressSpace AddressSpace; typedef struct AioContext AioContext; diff --git a/include/system/accel-ops.h b/include/system/accel-ops.h index 137fb96d4441..4c99d25aeffe 100644 --- a/include/system/accel-ops.h +++ b/include/system/accel-ops.h @@ -17,7 +17,6 @@ #define TYPE_ACCEL_OPS "accel" ACCEL_OPS_SUFFIX #define ACCEL_OPS_NAME(name) (name "-" TYPE_ACCEL_OPS) -typedef struct AccelOpsClass AccelOpsClass; DECLARE_CLASS_CHECKERS(AccelOpsClass, ACCEL_OPS, TYPE_ACCEL_OPS) /** diff --git a/include/system/cpus.h b/include/system/cpus.h index 1cffeaaf5c48..3226c765d010 100644 --- a/include/system/cpus.h +++ b/include/system/cpus.h @@ -1,8 +1,6 @@ #ifndef QEMU_CPUS_H #define QEMU_CPUS_H -#include "system/accel-ops.h" - /* register accel-specific operations */ void cpus_register_accel(const AccelOpsClass *i); diff --git a/system/cpus.c b/system/cpus.c index 37e5892c2404..2cc5f887ab5d 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -31,6 +31,7 @@ #include "qapi/qapi-events-run-state.h" #include "qapi/qmp/qerror.h" #include "exec/gdbstub.h" +#include "system/accel-ops.h" #include "system/hw_accel.h" #include "exec/cpu-common.h" #include "qemu/thread.h" diff --git a/target/i386/nvmm/nvmm-accel-ops.c b/target/i386/nvmm/nvmm-accel-ops.c index e7b56662feec..4e4e63de78e9 100644 --- a/target/i386/nvmm/nvmm-accel-ops.c +++ b/target/i386/nvmm/nvmm-accel-ops.c @@ -10,6 +10,7 @@ #include "qemu/osdep.h" #include "system/kvm_int.h" #include "qemu/main-loop.h" +#include "system/accel-ops.h" #include "system/cpus.h" #include "qemu/guest-random.h" diff --git a/target/i386/whpx/whpx-accel-ops.c b/target/i386/whpx/whpx-accel-ops.c index ab2e014c9eaa..81fdd06e487a 100644 --- a/target/i386/whpx/whpx-accel-ops.c +++ b/target/i386/whpx/whpx-accel-ops.c @@ -11,6 +11,7 @@ #include "qemu/osdep.h" #include "system/kvm_int.h" #include "qemu/main-loop.h" +#include "system/accel-ops.h" #include "system/cpus.h" #include "qemu/guest-random.h" From 217e72024c12c95e7b5f74fde74206951e706b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 10:37:05 +0100 Subject: [PATCH 0591/1179] accel/accel-cpu-target.h: Include missing 'cpu.h' header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CPU_RESOLVING_TYPE is declared per target in "cpu.h". Include it (along with "qom/object.h") to avoid when moving code around: include/accel/accel-cpu-target.h:26:50: error: expected ')' 26 | DECLARE_CLASS_CHECKERS(AccelCPUClass, ACCEL_CPU, TYPE_ACCEL_CPU) | ^ include/accel/accel-cpu-target.h:23:33: note: expanded from macro 'TYPE_ACCEL_CPU' 23 | #define TYPE_ACCEL_CPU "accel-" CPU_RESOLVING_TYPE | ^ include/accel/accel-cpu-target.h:26:1: note: to match this '(' 26 | DECLARE_CLASS_CHECKERS(AccelCPUClass, ACCEL_CPU, TYPE_ACCEL_CPU) | ^ include/qom/object.h:196:14: note: expanded from macro 'DECLARE_CLASS_CHECKERS' 196 | { return OBJECT_GET_CLASS(ClassType, obj, TYPENAME); } \ | ^ include/qom/object.h:558:5: note: expanded from macro 'OBJECT_GET_CLASS' 558 | OBJECT_CLASS_CHECK(class, object_get_class(OBJECT(obj)), name) | ^ include/qom/object.h:544:74: note: expanded from macro 'OBJECT_CLASS_CHECK' 544 | ((class_type *)object_class_dynamic_cast_assert(OBJECT_CLASS(class), (name), \ | ^ Signed-off-by: Philippe Mathieu-Daudé Acked-by: Richard Henderson Message-Id: <20250123234415.59850-13-philmd@linaro.org> --- include/accel/accel-cpu-target.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/accel/accel-cpu-target.h b/include/accel/accel-cpu-target.h index 0a8e518600d1..37dde7fae3e0 100644 --- a/include/accel/accel-cpu-target.h +++ b/include/accel/accel-cpu-target.h @@ -20,6 +20,9 @@ * subclasses in target/, or the accel implementation itself in accel/ */ +#include "qom/object.h" +#include "cpu.h" + #define TYPE_ACCEL_CPU "accel-" CPU_RESOLVING_TYPE #define ACCEL_CPU_NAME(name) (name "-" TYPE_ACCEL_CPU) typedef struct AccelCPUClass AccelCPUClass; From a523b62c8573c09d96472e06486bb58740945215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 17 Feb 2025 12:21:42 +0100 Subject: [PATCH 0592/1179] accel/tcg: Include missing bswap headers in user-exec.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 35c653c4029 ("tcg: Add 128-bit guest memory primitives") introduced the use of bswap128() which is declared in "qemu/int128.h", commit de95016dfbf ("accel/tcg: Implement helper_{ld,st}*_mmu for user-only") introduced the other bswap*() uses, which are declared in "qemu/bswap.h". Include the missing headers. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250217130610.18313-3-philmd@linaro.org> --- accel/tcg/user-exec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index c4454100ad77..9d53c9440ea8 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -30,6 +30,8 @@ #include "exec/page-protection.h" #include "exec/helper-proto.h" #include "qemu/atomic128.h" +#include "qemu/bswap.h" +#include "qemu/int128.h" #include "trace.h" #include "tcg/tcg-ldst.h" #include "internal-common.h" From 964a4f2c2972ec9c7574b87541d6070ef5d22f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 17 Feb 2025 11:53:47 +0100 Subject: [PATCH 0593/1179] accel/tcg: Take mmap lock in the whole cpu_memory_rw_debug() function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify user implementation of cpu_memory_rw_debug() by taking the mmap lock globally. See commit 87ab2704296 ("linux-user: Allow gdbstub to ignore page protection") for why this lock is necessary. Suggested-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250217130610.18313-4-philmd@linaro.org> --- cpu-target.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index 20933bde7d40..b5230ce1837d 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -380,6 +380,8 @@ int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, int ret = -1; int fd = -1; + mmap_lock(); + while (len > 0) { page = addr & TARGET_PAGE_MASK; l = (page + TARGET_PAGE_SIZE) - addr; @@ -414,11 +416,9 @@ int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, * be under mmap_lock() in order to prevent the creation of * another TranslationBlock in between. */ - mmap_lock(); tb_invalidate_phys_range(addr, addr + l - 1); written = pwrite(fd, buf, l, (off_t)(uintptr_t)g2h_untagged(addr)); - mmap_unlock(); if (written != l) { goto out_close; } @@ -454,6 +454,8 @@ int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, close(fd); } out: + mmap_unlock(); + return ret; } #endif From eacd1f8445fd033c3ce927e543be2818d0564130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 17 Feb 2025 12:01:09 +0100 Subject: [PATCH 0594/1179] accel/tcg: Avoid using lock_user() in cpu_memory_rw_debug() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We checked the page flags with page_get_flags(), so locking the page is superfluous. Remove the lock_user() calls and directly use g2h() in place. Suggested-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250217130610.18313-5-philmd@linaro.org> --- cpu-target.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index b5230ce1837d..3892ce122296 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -374,7 +374,6 @@ int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, { int flags; vaddr l, page; - void * p; uint8_t *buf = ptr; ssize_t written; int ret = -1; @@ -393,13 +392,7 @@ int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, } if (is_write) { if (flags & PAGE_WRITE) { - /* XXX: this code should not depend on lock_user */ - p = lock_user(VERIFY_WRITE, addr, l, 0); - if (!p) { - goto out_close; - } - memcpy(p, buf, l); - unlock_user(p, addr, l); + memcpy(g2h(cpu, addr), buf, l); } else { /* Bypass the host page protection using ptrace. */ if (fd == -1) { @@ -424,13 +417,7 @@ int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, } } } else if (flags & PAGE_READ) { - /* XXX: this code should not depend on lock_user */ - p = lock_user(VERIFY_READ, addr, l, 1); - if (!p) { - goto out_close; - } - memcpy(buf, p, l); - unlock_user(p, addr, 0); + memcpy(buf, g2h(cpu, addr), l); } else { /* Bypass the host page protection using ptrace. */ if (fd == -1) { From 585d4b122914b7be170eef147a0269cc233f0adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 17 Feb 2025 12:13:16 +0100 Subject: [PATCH 0595/1179] accel/tcg: Move cpu_memory_rw_debug() user implementation to user-exec.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cpu_memory_rw_debug() system implementation is defined in system/physmem.c. Move the user one to accel/tcg/user-exec.c to simplify cpu-target.c maintenance. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250217130610.18313-6-philmd@linaro.org> --- accel/tcg/user-exec.c | 80 ++++++++++++++++++++++++++++++++++++++ cpu-target.c | 90 +------------------------------------------ 2 files changed, 82 insertions(+), 88 deletions(-) diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index 9d53c9440ea8..2322181b1516 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -19,6 +19,7 @@ #include "qemu/osdep.h" #include "accel/tcg/cpu-ops.h" #include "disas/disas.h" +#include "exec/vaddr.h" #include "exec/exec-all.h" #include "tcg/tcg.h" #include "qemu/bitops.h" @@ -971,6 +972,85 @@ static void *cpu_mmu_lookup(CPUState *cpu, vaddr addr, return ret; } +/* physical memory access (slow version, mainly for debug) */ +int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, + void *ptr, size_t len, bool is_write) +{ + int flags; + vaddr l, page; + uint8_t *buf = ptr; + ssize_t written; + int ret = -1; + int fd = -1; + + mmap_lock(); + + while (len > 0) { + page = addr & TARGET_PAGE_MASK; + l = (page + TARGET_PAGE_SIZE) - addr; + if (l > len) { + l = len; + } + flags = page_get_flags(page); + if (!(flags & PAGE_VALID)) { + goto out_close; + } + if (is_write) { + if (flags & PAGE_WRITE) { + memcpy(g2h(cpu, addr), buf, l); + } else { + /* Bypass the host page protection using ptrace. */ + if (fd == -1) { + fd = open("/proc/self/mem", O_WRONLY); + if (fd == -1) { + goto out; + } + } + /* + * If there is a TranslationBlock and we weren't bypassing the + * host page protection, the memcpy() above would SEGV, + * ultimately leading to page_unprotect(). So invalidate the + * translations manually. Both invalidation and pwrite() must + * be under mmap_lock() in order to prevent the creation of + * another TranslationBlock in between. + */ + tb_invalidate_phys_range(addr, addr + l - 1); + written = pwrite(fd, buf, l, + (off_t)(uintptr_t)g2h_untagged(addr)); + if (written != l) { + goto out_close; + } + } + } else if (flags & PAGE_READ) { + memcpy(buf, g2h(cpu, addr), l); + } else { + /* Bypass the host page protection using ptrace. */ + if (fd == -1) { + fd = open("/proc/self/mem", O_RDONLY); + if (fd == -1) { + goto out; + } + } + if (pread(fd, buf, l, + (off_t)(uintptr_t)g2h_untagged(addr)) != l) { + goto out_close; + } + } + len -= l; + buf += l; + addr += l; + } + ret = 0; +out_close: + if (fd != -1) { + close(fd); + } +out: + mmap_unlock(); + + return ret; +} + #include "ldst_atomicity.c.inc" static uint8_t do_ld1_mmu(CPUState *cpu, vaddr addr, MemOpIdx oi, diff --git a/cpu-target.c b/cpu-target.c index 3892ce122296..83688f1d5082 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -19,18 +19,12 @@ #include "qemu/osdep.h" #include "qapi/error.h" - -#include "exec/target_page.h" -#include "exec/page-protection.h" #include "hw/qdev-core.h" #include "hw/qdev-properties.h" #include "qemu/error-report.h" #include "qemu/qemu-print.h" #include "migration/vmstate.h" -#ifdef CONFIG_USER_ONLY -#include "qemu.h" -#include "user/page-protection.h" -#else +#ifndef CONFIG_USER_ONLY #include "hw/core/sysemu-cpu-ops.h" #include "exec/address-spaces.h" #include "exec/memory.h" @@ -43,11 +37,11 @@ #include "exec/cpu-common.h" #include "exec/exec-all.h" #include "exec/tb-flush.h" -#include "exec/translation-block.h" #include "exec/log.h" #include "accel/accel-cpu-target.h" #include "trace/trace-root.h" #include "qemu/accel.h" +#include "hw/core/cpu.h" #ifndef CONFIG_USER_ONLY static int cpu_common_post_load(void *opaque, int version_id) @@ -367,86 +361,6 @@ void cpu_abort(CPUState *cpu, const char *fmt, ...) abort(); } -/* physical memory access (slow version, mainly for debug) */ -#if defined(CONFIG_USER_ONLY) -int cpu_memory_rw_debug(CPUState *cpu, vaddr addr, - void *ptr, size_t len, bool is_write) -{ - int flags; - vaddr l, page; - uint8_t *buf = ptr; - ssize_t written; - int ret = -1; - int fd = -1; - - mmap_lock(); - - while (len > 0) { - page = addr & TARGET_PAGE_MASK; - l = (page + TARGET_PAGE_SIZE) - addr; - if (l > len) - l = len; - flags = page_get_flags(page); - if (!(flags & PAGE_VALID)) { - goto out_close; - } - if (is_write) { - if (flags & PAGE_WRITE) { - memcpy(g2h(cpu, addr), buf, l); - } else { - /* Bypass the host page protection using ptrace. */ - if (fd == -1) { - fd = open("/proc/self/mem", O_WRONLY); - if (fd == -1) { - goto out; - } - } - /* - * If there is a TranslationBlock and we weren't bypassing the - * host page protection, the memcpy() above would SEGV, - * ultimately leading to page_unprotect(). So invalidate the - * translations manually. Both invalidation and pwrite() must - * be under mmap_lock() in order to prevent the creation of - * another TranslationBlock in between. - */ - tb_invalidate_phys_range(addr, addr + l - 1); - written = pwrite(fd, buf, l, - (off_t)(uintptr_t)g2h_untagged(addr)); - if (written != l) { - goto out_close; - } - } - } else if (flags & PAGE_READ) { - memcpy(buf, g2h(cpu, addr), l); - } else { - /* Bypass the host page protection using ptrace. */ - if (fd == -1) { - fd = open("/proc/self/mem", O_RDONLY); - if (fd == -1) { - goto out; - } - } - if (pread(fd, buf, l, - (off_t)(uintptr_t)g2h_untagged(addr)) != l) { - goto out_close; - } - } - len -= l; - buf += l; - addr += l; - } - ret = 0; -out_close: - if (fd != -1) { - close(fd); - } -out: - mmap_unlock(); - - return ret; -} -#endif - bool target_words_bigendian(void) { return TARGET_BIG_ENDIAN; From 6eeff37b43d496deadd4cb4697f9f3f5b50d0926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 13:51:14 +0100 Subject: [PATCH 0596/1179] accel/kvm: Remove unused 'system/cpus.h' header in kvm-cpus.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Missed in commit b86f59c7155 ("accel: replace struct CpusAccel with AccelOpsClass") which removed the single CpusAccel use. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-7-philmd@linaro.org> --- accel/kvm/kvm-cpus.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/accel/kvm/kvm-cpus.h b/accel/kvm/kvm-cpus.h index b5435286e420..688511151c8d 100644 --- a/accel/kvm/kvm-cpus.h +++ b/accel/kvm/kvm-cpus.h @@ -10,8 +10,6 @@ #ifndef KVM_CPUS_H #define KVM_CPUS_H -#include "system/cpus.h" - int kvm_init_vcpu(CPUState *cpu, Error **errp); int kvm_cpu_exec(CPUState *cpu); void kvm_destroy_vcpu(CPUState *cpu); From c90476325cf669d9bba15f4fd8d8637926f272a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 19:55:50 +0100 Subject: [PATCH 0597/1179] cpus: Fix style in cpu-target.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix style on code we are going to modify. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-16-philmd@linaro.org> --- cpu-target.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index 83688f1d5082..b925b9391e81 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -48,12 +48,15 @@ static int cpu_common_post_load(void *opaque, int version_id) { CPUState *cpu = opaque; - /* 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the - version_id is increased. */ + /* + * 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the + * version_id is increased. + */ cpu->interrupt_request &= ~0x01; tlb_flush(cpu); - /* loadvm has just updated the content of RAM, bypassing the + /* + * loadvm has just updated the content of RAM, bypassing the * usual mechanisms that ensure we flush TBs for writes to * memory we've translated code from. So we must flush all TBs, * which will now be stale. From 530c7139f64aa0e45b61ce9abecb7df8c55b3f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 23:30:04 +0100 Subject: [PATCH 0598/1179] cpus: Restrict cpu_common_post_load() code to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CPU_INTERRUPT_EXIT was removed in commit 3098dba01c7 ("Use a dedicated function to request exit from execution loop"), tlb_flush() and tb_flush() are related to TCG accelerator. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-17-philmd@linaro.org> --- cpu-target.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index b925b9391e81..48446c902127 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -46,22 +46,25 @@ #ifndef CONFIG_USER_ONLY static int cpu_common_post_load(void *opaque, int version_id) { - CPUState *cpu = opaque; - - /* - * 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the - * version_id is increased. - */ - cpu->interrupt_request &= ~0x01; - tlb_flush(cpu); - - /* - * loadvm has just updated the content of RAM, bypassing the - * usual mechanisms that ensure we flush TBs for writes to - * memory we've translated code from. So we must flush all TBs, - * which will now be stale. - */ - tb_flush(cpu); + if (tcg_enabled()) { + CPUState *cpu = opaque; + + /* + * 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the + * version_id is increased. + */ + cpu->interrupt_request &= ~0x01; + + tlb_flush(cpu); + + /* + * loadvm has just updated the content of RAM, bypassing the + * usual mechanisms that ensure we flush TBs for writes to + * memory we've translated code from. So we must flush all TBs, + * which will now be stale. + */ + tb_flush(cpu); + } return 0; } From e3a575f5609569400da628d384b32f5e3cf58745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 10:14:17 +0100 Subject: [PATCH 0599/1179] cpus: Have cpu_class_init_props() per user / system emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than maintaining a mix of system / user code for CPU class properties, move system properties to cpu-system.c and user ones to the new cpu-user.c unit. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-18-philmd@linaro.org> --- MAINTAINERS | 1 + cpu-target.c | 58 -------------------------------------------- hw/core/cpu-system.c | 40 ++++++++++++++++++++++++++++++ hw/core/cpu-user.c | 27 +++++++++++++++++++++ hw/core/meson.build | 5 +++- 5 files changed, 72 insertions(+), 59 deletions(-) create mode 100644 hw/core/cpu-user.c diff --git a/MAINTAINERS b/MAINTAINERS index a0e462d03ca8..1d1fadc3bc92 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3791,6 +3791,7 @@ Overall usermode emulation M: Riku Voipio S: Maintained F: accel/tcg/user-exec*.c +F: hw/core/cpu-user.c F: include/user/ F: common-user/ diff --git a/cpu-target.c b/cpu-target.c index 48446c902127..f4c834fd26a1 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -19,15 +19,12 @@ #include "qemu/osdep.h" #include "qapi/error.h" -#include "hw/qdev-core.h" -#include "hw/qdev-properties.h" #include "qemu/error-report.h" #include "qemu/qemu-print.h" #include "migration/vmstate.h" #ifndef CONFIG_USER_ONLY #include "hw/core/sysemu-cpu-ops.h" #include "exec/address-spaces.h" -#include "exec/memory.h" #endif #include "system/accel-ops.h" #include "system/cpus.h" @@ -178,61 +175,6 @@ void cpu_exec_unrealizefn(CPUState *cpu) accel_cpu_common_unrealize(cpu); } -/* - * This can't go in hw/core/cpu.c because that file is compiled only - * once for both user-mode and system builds. - */ -static const Property cpu_common_props[] = { -#ifdef CONFIG_USER_ONLY - /* - * Create a property for the user-only object, so users can - * adjust prctl(PR_SET_UNALIGN) from the command-line. - * Has no effect if the target does not support the feature. - */ - DEFINE_PROP_BOOL("prctl-unalign-sigbus", CPUState, - prctl_unalign_sigbus, false), -#else - /* - * Create a memory property for system CPU object, so users can - * wire up its memory. The default if no link is set up is to use - * the system address space. - */ - DEFINE_PROP_LINK("memory", CPUState, memory, TYPE_MEMORY_REGION, - MemoryRegion *), -#endif -}; - -#ifndef CONFIG_USER_ONLY -static bool cpu_get_start_powered_off(Object *obj, Error **errp) -{ - CPUState *cpu = CPU(obj); - return cpu->start_powered_off; -} - -static void cpu_set_start_powered_off(Object *obj, bool value, Error **errp) -{ - CPUState *cpu = CPU(obj); - cpu->start_powered_off = value; -} -#endif - -void cpu_class_init_props(DeviceClass *dc) -{ -#ifndef CONFIG_USER_ONLY - ObjectClass *oc = OBJECT_CLASS(dc); - - /* - * We can't use DEFINE_PROP_BOOL in the Property array for this - * property, because we want this to be settable after realize. - */ - object_class_property_add_bool(oc, "start-powered-off", - cpu_get_start_powered_off, - cpu_set_start_powered_off); -#endif - - device_class_set_props(dc, cpu_common_props); -} - void cpu_exec_initfn(CPUState *cpu) { #ifndef CONFIG_USER_ONLY diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index 6e307c89597f..1310b4203fdb 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -20,7 +20,10 @@ #include "qemu/osdep.h" #include "qapi/error.h" +#include "exec/memory.h" #include "exec/tswap.h" +#include "hw/qdev-core.h" +#include "hw/qdev-properties.h" #include "hw/core/sysemu-cpu-ops.h" bool cpu_paging_enabled(const CPUState *cpu) @@ -147,3 +150,40 @@ GuestPanicInformation *cpu_get_crash_info(CPUState *cpu) } return res; } + +static const Property cpu_system_props[] = { + /* + * Create a memory property for system CPU object, so users can + * wire up its memory. The default if no link is set up is to use + * the system address space. + */ + DEFINE_PROP_LINK("memory", CPUState, memory, TYPE_MEMORY_REGION, + MemoryRegion *), +}; + +static bool cpu_get_start_powered_off(Object *obj, Error **errp) +{ + CPUState *cpu = CPU(obj); + return cpu->start_powered_off; +} + +static void cpu_set_start_powered_off(Object *obj, bool value, Error **errp) +{ + CPUState *cpu = CPU(obj); + cpu->start_powered_off = value; +} + +void cpu_class_init_props(DeviceClass *dc) +{ + ObjectClass *oc = OBJECT_CLASS(dc); + + /* + * We can't use DEFINE_PROP_BOOL in the Property array for this + * property, because we want this to be settable after realize. + */ + object_class_property_add_bool(oc, "start-powered-off", + cpu_get_start_powered_off, + cpu_set_start_powered_off); + + device_class_set_props(dc, cpu_system_props); +} diff --git a/hw/core/cpu-user.c b/hw/core/cpu-user.c new file mode 100644 index 000000000000..e5ccf6bf13a3 --- /dev/null +++ b/hw/core/cpu-user.c @@ -0,0 +1,27 @@ +/* + * QEMU CPU model (user specific) + * + * Copyright (c) Linaro, Ltd. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/qdev-core.h" +#include "hw/qdev-properties.h" +#include "hw/core/cpu.h" + +static const Property cpu_user_props[] = { + /* + * Create a property for the user-only object, so users can + * adjust prctl(PR_SET_UNALIGN) from the command-line. + * Has no effect if the target does not support the feature. + */ + DEFINE_PROP_BOOL("prctl-unalign-sigbus", CPUState, + prctl_unalign_sigbus, false), +}; + +void cpu_class_init_props(DeviceClass *dc) +{ + device_class_set_props(dc, cpu_user_props); +} diff --git a/hw/core/meson.build b/hw/core/meson.build index 65a1698ed1fb..b5a545a0edd6 100644 --- a/hw/core/meson.build +++ b/hw/core/meson.build @@ -46,4 +46,7 @@ system_ss.add(files( 'vm-change-state-handler.c', 'clock-vmstate.c', )) -user_ss.add(files('qdev-user.c')) +user_ss.add(files( + 'cpu-user.c', + 'qdev-user.c', +)) From a86cf967a1afe8ccbf58d34983816bc2985d65d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 10:19:44 +0100 Subject: [PATCH 0600/1179] cpus: Have cpu_exec_initfn() per user / system emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slighly simplify cpu-target.c again by extracting cpu_exec_initfn() to cpu-{system,user}.c, adding an empty stub for user emulation. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-19-philmd@linaro.org> --- cpu-target.c | 9 --------- hw/core/cpu-system.c | 7 +++++++ hw/core/cpu-user.c | 5 +++++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index f4c834fd26a1..5aa6c4b0c63d 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -24,7 +24,6 @@ #include "migration/vmstate.h" #ifndef CONFIG_USER_ONLY #include "hw/core/sysemu-cpu-ops.h" -#include "exec/address-spaces.h" #endif #include "system/accel-ops.h" #include "system/cpus.h" @@ -175,14 +174,6 @@ void cpu_exec_unrealizefn(CPUState *cpu) accel_cpu_common_unrealize(cpu); } -void cpu_exec_initfn(CPUState *cpu) -{ -#ifndef CONFIG_USER_ONLY - cpu->memory = get_system_memory(); - object_ref(OBJECT(cpu->memory)); -#endif -} - char *cpu_model_from_type(const char *typename) { const char *suffix = "-" CPU_RESOLVING_TYPE; diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index 1310b4203fdb..e511507e13b6 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "qapi/error.h" +#include "exec/address-spaces.h" #include "exec/memory.h" #include "exec/tswap.h" #include "hw/qdev-core.h" @@ -187,3 +188,9 @@ void cpu_class_init_props(DeviceClass *dc) device_class_set_props(dc, cpu_system_props); } + +void cpu_exec_initfn(CPUState *cpu) +{ + cpu->memory = get_system_memory(); + object_ref(OBJECT(cpu->memory)); +} diff --git a/hw/core/cpu-user.c b/hw/core/cpu-user.c index e5ccf6bf13a3..cdd8de2fefa6 100644 --- a/hw/core/cpu-user.c +++ b/hw/core/cpu-user.c @@ -25,3 +25,8 @@ void cpu_class_init_props(DeviceClass *dc) { device_class_set_props(dc, cpu_user_props); } + +void cpu_exec_initfn(CPUState *cpu) +{ + /* nothing to do */ +} From f821d894de2025611f2b19598fc4191ac4167ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:52:57 +0100 Subject: [PATCH 0601/1179] cpus: Restrict cpu_get_memory_mapping() to system emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250121142341.17001-5-philmd@linaro.org> --- include/hw/core/cpu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index 7b6b22c431b7..9dd6ac7c7639 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -616,6 +616,8 @@ extern bool mttcg_enabled; */ bool cpu_paging_enabled(const CPUState *cpu); +#if !defined(CONFIG_USER_ONLY) + /** * cpu_get_memory_mapping: * @cpu: The CPU whose memory mappings are to be obtained. @@ -627,8 +629,6 @@ bool cpu_paging_enabled(const CPUState *cpu); bool cpu_get_memory_mapping(CPUState *cpu, MemoryMappingList *list, Error **errp); -#if !defined(CONFIG_USER_ONLY) - /** * cpu_write_elf64_note: * @f: pointer to a function that writes memory to a file From 2beb871dc20b57fc4b1ec2285dfc2145baf9df80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:12:21 +0100 Subject: [PATCH 0602/1179] hw/core/generic-loader: Do not open-code cpu_set_pc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Directly call cpu_set_pc() instead of open-coding it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250122093028.52416-2-philmd@linaro.org> --- hw/core/generic-loader.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hw/core/generic-loader.c b/hw/core/generic-loader.c index d9f5c2e83257..d3a426a1a26c 100644 --- a/hw/core/generic-loader.c +++ b/hw/core/generic-loader.c @@ -47,11 +47,8 @@ static void generic_loader_reset(void *opaque) GenericLoaderState *s = GENERIC_LOADER(opaque); if (s->set_pc) { - CPUClass *cc = CPU_GET_CLASS(s->cpu); cpu_reset(s->cpu); - if (cc) { - cc->set_pc(s->cpu, s->addr); - } + cpu_set_pc(s->cpu, s->addr); } if (s->data_len) { From 607854ae7c55bb00fbe58e80aa661887cd3eb256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 24 Sep 2024 23:17:05 +0200 Subject: [PATCH 0603/1179] target/microblaze: Explode MO_TExx -> MO_TE | MO_xx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the implicit MO_TE definition in order to replace it by runtime variable in the next commit. Mechanical change using: $ for n in UW UL UQ UO SW SL SQ; do \ sed -i -e "s/MO_TE$n/MO_TE | MO_$n/" \ $(git grep -l MO_TE$n target/microblaze); \ done Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Alistair Francis Message-Id: <20241105130431.22564-14-philmd@linaro.org> --- target/microblaze/translate.c | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/target/microblaze/translate.c b/target/microblaze/translate.c index 24005f05b21f..86efabb83b5a 100644 --- a/target/microblaze/translate.c +++ b/target/microblaze/translate.c @@ -780,13 +780,13 @@ static bool trans_lbui(DisasContext *dc, arg_typeb *arg) static bool trans_lhu(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TEUW, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); } static bool trans_lhur(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TEUW, dc->mem_index, true); + return do_load(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, true); } static bool trans_lhuea(DisasContext *dc, arg_typea *arg) @@ -798,26 +798,26 @@ static bool trans_lhuea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TEUW, MMU_NOMMU_IDX, false); + return do_load(dc, arg->rd, addr, MO_TE | MO_UW, MMU_NOMMU_IDX, false); #endif } static bool trans_lhui(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_load(dc, arg->rd, addr, MO_TEUW, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); } static bool trans_lw(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TEUL, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); } static bool trans_lwr(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TEUL, dc->mem_index, true); + return do_load(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, true); } static bool trans_lwea(DisasContext *dc, arg_typea *arg) @@ -829,14 +829,14 @@ static bool trans_lwea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TEUL, MMU_NOMMU_IDX, false); + return do_load(dc, arg->rd, addr, MO_TE | MO_UL, MMU_NOMMU_IDX, false); #endif } static bool trans_lwi(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_load(dc, arg->rd, addr, MO_TEUL, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); } static bool trans_lwx(DisasContext *dc, arg_typea *arg) @@ -846,7 +846,7 @@ static bool trans_lwx(DisasContext *dc, arg_typea *arg) /* lwx does not throw unaligned access errors, so force alignment */ tcg_gen_andi_tl(addr, addr, ~3); - tcg_gen_qemu_ld_i32(cpu_res_val, addr, dc->mem_index, MO_TEUL); + tcg_gen_qemu_ld_i32(cpu_res_val, addr, dc->mem_index, MO_TE | MO_UL); tcg_gen_mov_tl(cpu_res_addr, addr); if (arg->rd) { @@ -930,13 +930,13 @@ static bool trans_sbi(DisasContext *dc, arg_typeb *arg) static bool trans_sh(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TEUW, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); } static bool trans_shr(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TEUW, dc->mem_index, true); + return do_store(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, true); } static bool trans_shea(DisasContext *dc, arg_typea *arg) @@ -948,26 +948,26 @@ static bool trans_shea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TEUW, MMU_NOMMU_IDX, false); + return do_store(dc, arg->rd, addr, MO_TE | MO_UW, MMU_NOMMU_IDX, false); #endif } static bool trans_shi(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_store(dc, arg->rd, addr, MO_TEUW, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); } static bool trans_sw(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TEUL, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); } static bool trans_swr(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TEUL, dc->mem_index, true); + return do_store(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, true); } static bool trans_swea(DisasContext *dc, arg_typea *arg) @@ -979,14 +979,14 @@ static bool trans_swea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TEUL, MMU_NOMMU_IDX, false); + return do_store(dc, arg->rd, addr, MO_TE | MO_UL, MMU_NOMMU_IDX, false); #endif } static bool trans_swi(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_store(dc, arg->rd, addr, MO_TEUL, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); } static bool trans_swx(DisasContext *dc, arg_typea *arg) @@ -1015,7 +1015,7 @@ static bool trans_swx(DisasContext *dc, arg_typea *arg) tcg_gen_atomic_cmpxchg_i32(tval, cpu_res_addr, cpu_res_val, reg_for_write(dc, arg->rd), - dc->mem_index, MO_TEUL); + dc->mem_index, MO_TE | MO_UL); tcg_gen_brcond_i32(TCG_COND_NE, cpu_res_val, tval, swx_fail); From 401bd7d340a4558995e8f61bd2050174e043ef20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 24 Sep 2024 23:42:38 +0200 Subject: [PATCH 0604/1179] target/microblaze: Set MO_TE once in do_load() / do_store() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All callers of do_load() / do_store() set MO_TE flag. Set it once in the callees. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20241105130431.22564-15-philmd@linaro.org> --- target/microblaze/translate.c | 36 +++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/target/microblaze/translate.c b/target/microblaze/translate.c index 86efabb83b5a..0d51b2c468c2 100644 --- a/target/microblaze/translate.c +++ b/target/microblaze/translate.c @@ -713,6 +713,8 @@ static bool do_load(DisasContext *dc, int rd, TCGv addr, MemOp mop, { MemOp size = mop & MO_SIZE; + mop |= MO_TE; + /* * When doing reverse accesses we need to do two things. * @@ -780,13 +782,13 @@ static bool trans_lbui(DisasContext *dc, arg_typeb *arg) static bool trans_lhu(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_UW, dc->mem_index, false); } static bool trans_lhur(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, true); + return do_load(dc, arg->rd, addr, MO_UW, dc->mem_index, true); } static bool trans_lhuea(DisasContext *dc, arg_typea *arg) @@ -798,26 +800,26 @@ static bool trans_lhuea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TE | MO_UW, MMU_NOMMU_IDX, false); + return do_load(dc, arg->rd, addr, MO_UW, MMU_NOMMU_IDX, false); #endif } static bool trans_lhui(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_load(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_UW, dc->mem_index, false); } static bool trans_lw(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_UL, dc->mem_index, false); } static bool trans_lwr(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, true); + return do_load(dc, arg->rd, addr, MO_UL, dc->mem_index, true); } static bool trans_lwea(DisasContext *dc, arg_typea *arg) @@ -829,14 +831,14 @@ static bool trans_lwea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_load(dc, arg->rd, addr, MO_TE | MO_UL, MMU_NOMMU_IDX, false); + return do_load(dc, arg->rd, addr, MO_UL, MMU_NOMMU_IDX, false); #endif } static bool trans_lwi(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_load(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); + return do_load(dc, arg->rd, addr, MO_UL, dc->mem_index, false); } static bool trans_lwx(DisasContext *dc, arg_typea *arg) @@ -863,6 +865,8 @@ static bool do_store(DisasContext *dc, int rd, TCGv addr, MemOp mop, { MemOp size = mop & MO_SIZE; + mop |= MO_TE; + /* * When doing reverse accesses we need to do two things. * @@ -930,13 +934,13 @@ static bool trans_sbi(DisasContext *dc, arg_typeb *arg) static bool trans_sh(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_UW, dc->mem_index, false); } static bool trans_shr(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, true); + return do_store(dc, arg->rd, addr, MO_UW, dc->mem_index, true); } static bool trans_shea(DisasContext *dc, arg_typea *arg) @@ -948,26 +952,26 @@ static bool trans_shea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TE | MO_UW, MMU_NOMMU_IDX, false); + return do_store(dc, arg->rd, addr, MO_UW, MMU_NOMMU_IDX, false); #endif } static bool trans_shi(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_store(dc, arg->rd, addr, MO_TE | MO_UW, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_UW, dc->mem_index, false); } static bool trans_sw(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_UL, dc->mem_index, false); } static bool trans_swr(DisasContext *dc, arg_typea *arg) { TCGv addr = compute_ldst_addr_typea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, true); + return do_store(dc, arg->rd, addr, MO_UL, dc->mem_index, true); } static bool trans_swea(DisasContext *dc, arg_typea *arg) @@ -979,14 +983,14 @@ static bool trans_swea(DisasContext *dc, arg_typea *arg) return true; #else TCGv addr = compute_ldst_addr_ea(dc, arg->ra, arg->rb); - return do_store(dc, arg->rd, addr, MO_TE | MO_UL, MMU_NOMMU_IDX, false); + return do_store(dc, arg->rd, addr, MO_UL, MMU_NOMMU_IDX, false); #endif } static bool trans_swi(DisasContext *dc, arg_typeb *arg) { TCGv addr = compute_ldst_addr_typeb(dc, arg->ra, arg->imm); - return do_store(dc, arg->rd, addr, MO_TE | MO_UL, dc->mem_index, false); + return do_store(dc, arg->rd, addr, MO_UL, dc->mem_index, false); } static bool trans_swx(DisasContext *dc, arg_typea *arg) From 2c9e8ddd769959e899206b4cdea466ba5845e0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 24 Sep 2024 23:45:35 +0200 Subject: [PATCH 0605/1179] target/microblaze: Introduce mo_endian() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mo_endian() returns the target endianness, currently static. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20241105130431.22564-16-philmd@linaro.org> --- target/microblaze/translate.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/target/microblaze/translate.c b/target/microblaze/translate.c index 0d51b2c468c2..b5389d65b2ea 100644 --- a/target/microblaze/translate.c +++ b/target/microblaze/translate.c @@ -708,12 +708,17 @@ static void record_unaligned_ess(DisasContext *dc, int rd, } #endif +static inline MemOp mo_endian(DisasContext *dc) +{ + return MO_TE; +} + static bool do_load(DisasContext *dc, int rd, TCGv addr, MemOp mop, int mem_index, bool rev) { MemOp size = mop & MO_SIZE; - mop |= MO_TE; + mop |= mo_endian(dc); /* * When doing reverse accesses we need to do two things. @@ -848,7 +853,8 @@ static bool trans_lwx(DisasContext *dc, arg_typea *arg) /* lwx does not throw unaligned access errors, so force alignment */ tcg_gen_andi_tl(addr, addr, ~3); - tcg_gen_qemu_ld_i32(cpu_res_val, addr, dc->mem_index, MO_TE | MO_UL); + tcg_gen_qemu_ld_i32(cpu_res_val, addr, dc->mem_index, + mo_endian(dc) | MO_UL); tcg_gen_mov_tl(cpu_res_addr, addr); if (arg->rd) { @@ -865,7 +871,7 @@ static bool do_store(DisasContext *dc, int rd, TCGv addr, MemOp mop, { MemOp size = mop & MO_SIZE; - mop |= MO_TE; + mop |= mo_endian(dc); /* * When doing reverse accesses we need to do two things. @@ -1019,7 +1025,7 @@ static bool trans_swx(DisasContext *dc, arg_typea *arg) tcg_gen_atomic_cmpxchg_i32(tval, cpu_res_addr, cpu_res_val, reg_for_write(dc, arg->rd), - dc->mem_index, MO_TE | MO_UL); + dc->mem_index, mo_endian(dc) | MO_UL); tcg_gen_brcond_i32(TCG_COND_NE, cpu_res_val, tval, swx_fail); From 415aae543edad19eda8f66955dde386c7fd7c680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 24 Sep 2024 23:45:54 +0200 Subject: [PATCH 0606/1179] target/microblaze: Consider endianness while translating code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consider the CPU ENDI bit, swap instructions when the CPU endianness doesn't match the binary one. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20241105130431.22564-17-philmd@linaro.org> --- target/microblaze/cpu.h | 7 +++++++ target/microblaze/translate.c | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/target/microblaze/cpu.h b/target/microblaze/cpu.h index f6879eee352e..e44ddd530789 100644 --- a/target/microblaze/cpu.h +++ b/target/microblaze/cpu.h @@ -414,6 +414,13 @@ void mb_translate_code(CPUState *cs, TranslationBlock *tb, /* Ensure there is no overlap between the two masks. */ QEMU_BUILD_BUG_ON(MSR_TB_MASK & IFLAGS_TB_MASK); +static inline bool mb_cpu_is_big_endian(CPUState *cs) +{ + MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs); + + return !cpu->cfg.endi; +} + static inline void cpu_get_tb_cpu_state(CPUMBState *env, vaddr *pc, uint64_t *cs_base, uint32_t *flags) { diff --git a/target/microblaze/translate.c b/target/microblaze/translate.c index b5389d65b2ea..b54e5ac4b2f7 100644 --- a/target/microblaze/translate.c +++ b/target/microblaze/translate.c @@ -710,7 +710,7 @@ static void record_unaligned_ess(DisasContext *dc, int rd, static inline MemOp mo_endian(DisasContext *dc) { - return MO_TE; + return dc->cfg->endi ? MO_LE : MO_BE; } static bool do_load(DisasContext *dc, int rd, TCGv addr, MemOp mop, @@ -1647,7 +1647,8 @@ static void mb_tr_translate_insn(DisasContextBase *dcb, CPUState *cs) dc->tb_flags_to_set = 0; - ir = translator_ldl(cpu_env(cs), &dc->base, dc->base.pc_next); + ir = translator_ldl_swap(cpu_env(cs), &dc->base, dc->base.pc_next, + mb_cpu_is_big_endian(cs) != TARGET_BIG_ENDIAN); if (!decode(dc, ir)) { trap_illegal(dc, true); } From 40b839cb840ce032c8f048325b486d3284f1b68f Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Mon, 9 Dec 2024 21:36:26 +0100 Subject: [PATCH 0607/1179] target/i386/hvf: Variable type fixup in decoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit decode_bytes reads 1, 2, 4, or 8 bytes at a time. The destination variable should therefore be a uint64_t, not a target_ulong. Signed-off-by: Phil Dennis-Jordan Fixes: ff2de1668c9 ("i386: hvf: remove addr_t") Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20241209203629.74436-9-phil@philjordan.eu> Signed-off-by: Philippe Mathieu-Daudé --- target/i386/hvf/x86_decode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/i386/hvf/x86_decode.c b/target/i386/hvf/x86_decode.c index d6d5894e54b4..5fea2dd3cc03 100644 --- a/target/i386/hvf/x86_decode.c +++ b/target/i386/hvf/x86_decode.c @@ -61,8 +61,8 @@ uint64_t sign(uint64_t val, int size) static inline uint64_t decode_bytes(CPUX86State *env, struct x86_decode *decode, int size) { - target_ulong val = 0; - + uint64_t val = 0; + switch (size) { case 1: case 2: From befd818b58c8522f573b8831df820f121bfe642a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 15 Jan 2025 00:07:23 +0100 Subject: [PATCH 0608/1179] target/openrisc: Call cpu_openrisc_clock_init() in cpu_realize() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenRISC timer is architecturally tied to the CPU. It doesn't belong to the machine init() code to instanciate it: move its creation when a vCPU is realized (after being created). Reported-by: Paolo Bonzini Signed-off-by: Philippe Mathieu-Daudé Acked-by: Richard Henderson Message-Id: <20250114231304.77150-1-philmd@linaro.org> --- hw/openrisc/openrisc_sim.c | 2 -- hw/openrisc/virt.c | 2 -- target/openrisc/cpu.c | 4 ++++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/openrisc/openrisc_sim.c b/hw/openrisc/openrisc_sim.c index d9e0744922a8..83d7c2a8afc6 100644 --- a/hw/openrisc/openrisc_sim.c +++ b/hw/openrisc/openrisc_sim.c @@ -306,8 +306,6 @@ static void openrisc_sim_init(MachineState *machine) exit(1); } - cpu_openrisc_clock_init(cpus[n]); - qemu_register_reset(main_cpu_reset, cpus[n]); } diff --git a/hw/openrisc/virt.c b/hw/openrisc/virt.c index 9afe407b00a7..3055306783e1 100644 --- a/hw/openrisc/virt.c +++ b/hw/openrisc/virt.c @@ -487,8 +487,6 @@ static void openrisc_virt_init(MachineState *machine) exit(1); } - cpu_openrisc_clock_init(cpus[n]); - qemu_register_reset(main_cpu_reset, cpus[n]); } diff --git a/target/openrisc/cpu.c b/target/openrisc/cpu.c index 0669ba2fd108..785b065b513d 100644 --- a/target/openrisc/cpu.c +++ b/target/openrisc/cpu.c @@ -165,6 +165,10 @@ static void openrisc_cpu_realizefn(DeviceState *dev, Error **errp) qemu_init_vcpu(cs); cpu_reset(cs); +#ifndef CONFIG_USER_ONLY + cpu_openrisc_clock_init(OPENRISC_CPU(dev)); +#endif + occ->parent_realize(dev, errp); } From a770b10bafc3e67b16c258167e15f228298c2d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 14:33:33 +0100 Subject: [PATCH 0609/1179] target/hexagon: Ensure not being build on system emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently only user emulation is supported. Assert no target code is built for system emulation. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Brian Cain Message-Id: <20250121142341.17001-2-philmd@linaro.org> --- target/hexagon/cpu.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 79e60d4bfa1b..f78c8f9c2a00 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -26,6 +26,10 @@ #include "mmvec/mmvec.h" #include "hw/registerfields.h" +#ifndef CONFIG_USER_ONLY +#error "Hexagon does not support system emulation" +#endif + #define NUM_PREGS 4 #define TOTAL_PER_THREAD_REGS 64 From edee3da2e62ffa741083e1caa78203f91a7f1a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:16:26 +0100 Subject: [PATCH 0610/1179] target/rx: Ensure not being build on user emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently only system emulation is supported. Assert no target code is built for user emulation. Remove #ifdef'ry since more work is required before being able to emulate a user process. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250121142341.17001-3-philmd@linaro.org> --- target/rx/cpu.c | 6 ------ target/rx/cpu.h | 6 ++++-- target/rx/helper.c | 4 ---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/target/rx/cpu.c b/target/rx/cpu.c index 7d5fcbf76ac4..17ede51cd11c 100644 --- a/target/rx/cpu.c +++ b/target/rx/cpu.c @@ -192,13 +192,11 @@ static void rx_cpu_init(Object *obj) qdev_init_gpio_in(DEVICE(cpu), rx_cpu_set_irq, 2); } -#ifndef CONFIG_USER_ONLY #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps rx_sysemu_ops = { .get_phys_page_debug = rx_cpu_get_phys_page_debug, }; -#endif #include "accel/tcg/cpu-ops.h" @@ -209,11 +207,9 @@ static const TCGCPUOps rx_tcg_ops = { .restore_state_to_opc = rx_restore_state_to_opc, .tlb_fill = rx_cpu_tlb_fill, -#ifndef CONFIG_USER_ONLY .cpu_exec_interrupt = rx_cpu_exec_interrupt, .cpu_exec_halt = rx_cpu_has_work, .do_interrupt = rx_cpu_do_interrupt, -#endif /* !CONFIG_USER_ONLY */ }; static void rx_cpu_class_init(ObjectClass *klass, void *data) @@ -235,9 +231,7 @@ static void rx_cpu_class_init(ObjectClass *klass, void *data) cc->set_pc = rx_cpu_set_pc; cc->get_pc = rx_cpu_get_pc; -#ifndef CONFIG_USER_ONLY cc->sysemu_ops = &rx_sysemu_ops; -#endif cc->gdb_read_register = rx_cpu_gdb_read_register; cc->gdb_write_register = rx_cpu_gdb_write_register; cc->disas_set_info = rx_cpu_disas_set_info; diff --git a/target/rx/cpu.h b/target/rx/cpu.h index 5ba1874bd761..349d61c4e402 100644 --- a/target/rx/cpu.h +++ b/target/rx/cpu.h @@ -26,6 +26,10 @@ #include "exec/cpu-defs.h" #include "qemu/cpu-float.h" +#ifdef CONFIG_USER_ONLY +#error "RX does not support user mode emulation" +#endif + /* PSW define */ REG32(PSW, 0) FIELD(PSW, C, 0, 1) @@ -129,11 +133,9 @@ struct RXCPUClass { #define CPU_RESOLVING_TYPE TYPE_RX_CPU const char *rx_crname(uint8_t cr); -#ifndef CONFIG_USER_ONLY void rx_cpu_do_interrupt(CPUState *cpu); bool rx_cpu_exec_interrupt(CPUState *cpu, int int_req); hwaddr rx_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr); -#endif /* !CONFIG_USER_ONLY */ void rx_cpu_dump_state(CPUState *cpu, FILE *f, int flags); int rx_cpu_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); int rx_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg); diff --git a/target/rx/helper.c b/target/rx/helper.c index 80912e8dcb40..7f28e7298919 100644 --- a/target/rx/helper.c +++ b/target/rx/helper.c @@ -40,8 +40,6 @@ void rx_cpu_unpack_psw(CPURXState *env, uint32_t psw, int rte) env->psw_c = FIELD_EX32(psw, PSW, C); } -#ifndef CONFIG_USER_ONLY - #define INT_FLAGS (CPU_INTERRUPT_HARD | CPU_INTERRUPT_FIR) void rx_cpu_do_interrupt(CPUState *cs) { @@ -146,5 +144,3 @@ hwaddr rx_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) { return addr; } - -#endif /* !CONFIG_USER_ONLY */ From ff3779a543954f7c3e7f3a604eefcc7c15726940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:16:31 +0100 Subject: [PATCH 0611/1179] target/tricore: Ensure not being build on user emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently only system emulation is supported. Assert no target code is built for user emulation. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250121142341.17001-4-philmd@linaro.org> --- target/tricore/cpu.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/target/tricore/cpu.h b/target/tricore/cpu.h index 8e431d792223..cf9dbc6df8ee 100644 --- a/target/tricore/cpu.h +++ b/target/tricore/cpu.h @@ -26,6 +26,10 @@ #include "qemu/cpu-float.h" #include "tricore-defs.h" +#ifdef CONFIG_USER_ONLY +#error "TriCore does not support user mode emulation" +#endif + typedef struct CPUArchState { /* GPR Register */ uint32_t gpr_a[16]; From 96adf9b404e51b9acdf9592595ad935905de1f4e Mon Sep 17 00:00:00 2001 From: Denis Rastyogin Date: Fri, 24 Jan 2025 15:26:32 +0300 Subject: [PATCH 0612/1179] target/mips: Fix possible MSA int overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix possible overflow in 1 << (DF_BITS(df) - 2) when DF_BITS(df) is 64 by using a 64-bit integer for the shift operation. Found by Linux Verification Center (linuxtesting.org) with SVACE. Reported-by: Dmitriy Fedin Signed-off-by: Denis Rastyogin Reviewed-by: Peter Maydell Message-ID: <20250124122707.54264-1-gerben@altlinux.org> Signed-off-by: Philippe Mathieu-Daudé --- target/mips/tcg/msa_helper.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/target/mips/tcg/msa_helper.c b/target/mips/tcg/msa_helper.c index ec38d9fde5ed..74fb80cc256b 100644 --- a/target/mips/tcg/msa_helper.c +++ b/target/mips/tcg/msa_helper.c @@ -5577,7 +5577,7 @@ static inline int64_t msa_mulr_q_df(uint32_t df, int64_t arg1, int64_t arg2) { int64_t q_min = DF_MIN_INT(df); int64_t q_max = DF_MAX_INT(df); - int64_t r_bit = 1 << (DF_BITS(df) - 2); + int64_t r_bit = 1LL << (DF_BITS(df) - 2); if (arg1 == q_min && arg2 == q_min) { return q_max; @@ -5685,7 +5685,7 @@ static inline int64_t msa_maddr_q_df(uint32_t df, int64_t dest, int64_t arg1, int64_t q_max = DF_MAX_INT(df); int64_t q_min = DF_MIN_INT(df); - int64_t r_bit = 1 << (DF_BITS(df) - 2); + int64_t r_bit = 1LL << (DF_BITS(df) - 2); q_prod = arg1 * arg2; q_ret = ((dest << (DF_BITS(df) - 1)) + q_prod + r_bit) >> (DF_BITS(df) - 1); @@ -5700,7 +5700,7 @@ static inline int64_t msa_msubr_q_df(uint32_t df, int64_t dest, int64_t arg1, int64_t q_max = DF_MAX_INT(df); int64_t q_min = DF_MIN_INT(df); - int64_t r_bit = 1 << (DF_BITS(df) - 2); + int64_t r_bit = 1LL << (DF_BITS(df) - 2); q_prod = arg1 * arg2; q_ret = ((dest << (DF_BITS(df) - 1)) - q_prod + r_bit) >> (DF_BITS(df) - 1); From b8b37affc74e24f1c11f7dfbba416965b805e123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:36:56 +0100 Subject: [PATCH 0613/1179] target: Set disassemble_info::endian value for little-endian targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field for little-endian targets. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-2-philmd@linaro.org> --- target/alpha/cpu.c | 1 + target/avr/cpu.c | 1 + target/hexagon/cpu.c | 1 + target/i386/cpu.c | 1 + target/loongarch/cpu.c | 1 + target/rx/cpu.c | 1 + 6 files changed, 6 insertions(+) diff --git a/target/alpha/cpu.c b/target/alpha/cpu.c index 57e41fcd784a..2eabd7724df3 100644 --- a/target/alpha/cpu.c +++ b/target/alpha/cpu.c @@ -85,6 +85,7 @@ static int alpha_cpu_mmu_index(CPUState *cs, bool ifetch) static void alpha_cpu_disas_set_info(CPUState *cpu, disassemble_info *info) { + info->endian = BFD_ENDIAN_LITTLE; info->mach = bfd_mach_alpha_ev6; info->print_insn = print_insn_alpha; } diff --git a/target/avr/cpu.c b/target/avr/cpu.c index 5a0e21465e5e..2871d30540a4 100644 --- a/target/avr/cpu.c +++ b/target/avr/cpu.c @@ -102,6 +102,7 @@ static void avr_cpu_reset_hold(Object *obj, ResetType type) static void avr_cpu_disas_set_info(CPUState *cpu, disassemble_info *info) { + info->endian = BFD_ENDIAN_LITTLE; info->mach = bfd_arch_avr; info->print_insn = avr_print_insn; } diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 238e63bcea42..a9beb9a17572 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -293,6 +293,7 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) static void hexagon_cpu_disas_set_info(CPUState *s, disassemble_info *info) { info->print_insn = print_insn_hexagon; + info->endian = BFD_ENDIAN_LITTLE; } static void hexagon_cpu_realize(DeviceState *dev, Error **errp) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 0cd9b70938d7..ab328485acce 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8691,6 +8691,7 @@ static void x86_disas_set_info(CPUState *cs, disassemble_info *info) X86CPU *cpu = X86_CPU(cs); CPUX86State *env = &cpu->env; + info->endian = BFD_ENDIAN_LITTLE; info->mach = (env->hflags & HF_CS64_MASK ? bfd_mach_x86_64 : env->hflags & HF_CS32_MASK ? bfd_mach_i386_i386 : bfd_mach_i386_i8086); diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index b4b82425b18c..d2e739a029f6 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -624,6 +624,7 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type) static void loongarch_cpu_disas_set_info(CPUState *s, disassemble_info *info) { + info->endian = BFD_ENDIAN_LITTLE; info->print_insn = print_insn_loongarch; } diff --git a/target/rx/cpu.c b/target/rx/cpu.c index 17ede51cd11c..1c40c8977e76 100644 --- a/target/rx/cpu.c +++ b/target/rx/cpu.c @@ -168,6 +168,7 @@ static void rx_cpu_set_irq(void *opaque, int no, int request) static void rx_cpu_disas_set_info(CPUState *cpu, disassemble_info *info) { + info->endian = BFD_ENDIAN_LITTLE; info->mach = bfd_mach_rx; info->print_insn = print_insn_rx; } From 2136f7f1f95b26129922d05d233b2056b8cbff5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:37:18 +0100 Subject: [PATCH 0614/1179] target: Set disassemble_info::endian value for big-endian targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field for big-endian targets. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-3-philmd@linaro.org> --- target/hppa/cpu.c | 1 + target/m68k/cpu.c | 1 + target/openrisc/cpu.c | 1 + target/s390x/cpu.c | 1 + target/sparc/cpu.c | 1 + 5 files changed, 5 insertions(+) diff --git a/target/hppa/cpu.c b/target/hppa/cpu.c index 4bb5cff624e9..d15f8c9c2172 100644 --- a/target/hppa/cpu.c +++ b/target/hppa/cpu.c @@ -150,6 +150,7 @@ static int hppa_cpu_mmu_index(CPUState *cs, bool ifetch) static void hppa_cpu_disas_set_info(CPUState *cs, disassemble_info *info) { info->mach = bfd_mach_hppa20; + info->endian = BFD_ENDIAN_BIG; info->print_insn = print_insn_hppa; } diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index eedda07c2ab4..df8b9c53fca0 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -157,6 +157,7 @@ static void m68k_cpu_reset_hold(Object *obj, ResetType type) static void m68k_cpu_disas_set_info(CPUState *s, disassemble_info *info) { info->print_insn = print_insn_m68k; + info->endian = BFD_ENDIAN_BIG; info->mach = 0; } diff --git a/target/openrisc/cpu.c b/target/openrisc/cpu.c index 785b065b513d..e8c357ae8369 100644 --- a/target/openrisc/cpu.c +++ b/target/openrisc/cpu.c @@ -83,6 +83,7 @@ static int openrisc_cpu_mmu_index(CPUState *cs, bool ifetch) static void openrisc_disas_set_info(CPUState *cpu, disassemble_info *info) { + info->endian = BFD_ENDIAN_BIG; info->print_insn = print_insn_or1k; } diff --git a/target/s390x/cpu.c b/target/s390x/cpu.c index 3bea014f9ee9..972d265478d7 100644 --- a/target/s390x/cpu.c +++ b/target/s390x/cpu.c @@ -243,6 +243,7 @@ static void s390_cpu_disas_set_info(CPUState *cpu, disassemble_info *info) { info->mach = bfd_mach_s390_64; info->cap_arch = CS_ARCH_SYSZ; + info->endian = BFD_ENDIAN_BIG; info->cap_insn_unit = 2; info->cap_insn_split = 6; } diff --git a/target/sparc/cpu.c b/target/sparc/cpu.c index e3b46137178d..9fd222e4c827 100644 --- a/target/sparc/cpu.c +++ b/target/sparc/cpu.c @@ -106,6 +106,7 @@ static bool sparc_cpu_exec_interrupt(CPUState *cs, int interrupt_request) static void cpu_sparc_disas_set_info(CPUState *cpu, disassemble_info *info) { info->print_insn = print_insn_sparc; + info->endian = BFD_ENDIAN_BIG; #ifdef TARGET_SPARC64 info->mach = bfd_mach_sparc_v9b; #endif From 4b7d6557efaa3c0e291ab02319364f139ac400d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:30:35 +0100 Subject: [PATCH 0615/1179] target/arm: Set disassemble_info::endian value in disas_set_info() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-4-philmd@linaro.org> --- target/arm/cpu.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index ac1ceec2110a..948defa3f5d4 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -1171,7 +1171,7 @@ static void arm_disas_set_info(CPUState *cpu, disassemble_info *info) { ARMCPU *ac = ARM_CPU(cpu); CPUARMState *env = &ac->env; - bool sctlr_b; + bool sctlr_b = arm_sctlr_b(env); if (is_a64(env)) { info->cap_arch = CS_ARCH_ARM64; @@ -1198,13 +1198,9 @@ static void arm_disas_set_info(CPUState *cpu, disassemble_info *info) info->cap_mode = cap_mode; } - sctlr_b = arm_sctlr_b(env); + info->endian = BFD_ENDIAN_LITTLE; if (bswap_code(sctlr_b)) { -#if TARGET_BIG_ENDIAN - info->endian = BFD_ENDIAN_LITTLE; -#else - info->endian = BFD_ENDIAN_BIG; -#endif + info->endian = TARGET_BIG_ENDIAN ? BFD_ENDIAN_LITTLE : BFD_ENDIAN_BIG; } info->flags &= ~INSN_ARM_BE32; #ifndef CONFIG_USER_ONLY From 840e0862c52899258ae6743de2fc3826f9a4e4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:31:15 +0100 Subject: [PATCH 0616/1179] target/microblaze: Set disassemble_info::endian value in disas_set_info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-5-philmd@linaro.org> --- target/microblaze/cpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/target/microblaze/cpu.c b/target/microblaze/cpu.c index 13d194cef880..d5ee1244cad7 100644 --- a/target/microblaze/cpu.c +++ b/target/microblaze/cpu.c @@ -224,6 +224,8 @@ static void mb_disas_set_info(CPUState *cpu, disassemble_info *info) { info->mach = bfd_arch_microblaze; info->print_insn = print_insn_microblaze; + info->endian = TARGET_BIG_ENDIAN ? BFD_ENDIAN_BIG + : BFD_ENDIAN_LITTLE; } static void mb_cpu_realizefn(DeviceState *dev, Error **errp) From 7bb1a717cb2fd11962d1ad72933e6235c2d638c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:31:20 +0100 Subject: [PATCH 0617/1179] target/mips: Set disassemble_info::endian value in disas_set_info() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-6-philmd@linaro.org> --- target/mips/cpu.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/target/mips/cpu.c b/target/mips/cpu.c index 0b267d2e507e..e76298699ab3 100644 --- a/target/mips/cpu.c +++ b/target/mips/cpu.c @@ -428,13 +428,13 @@ static void mips_cpu_reset_hold(Object *obj, ResetType type) static void mips_cpu_disas_set_info(CPUState *s, disassemble_info *info) { if (!(cpu_env(s)->insn_flags & ISA_NANOMIPS32)) { -#if TARGET_BIG_ENDIAN - info->print_insn = print_insn_big_mips; -#else - info->print_insn = print_insn_little_mips; -#endif + info->endian = TARGET_BIG_ENDIAN ? BFD_ENDIAN_BIG + : BFD_ENDIAN_LITTLE; + info->print_insn = TARGET_BIG_ENDIAN ? print_insn_big_mips + : print_insn_little_mips; } else { info->print_insn = print_insn_nanomips; + info->endian = BFD_ENDIAN_LITTLE; } } From 724bac41906752aafd432714d13fc78da2265f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:31:38 +0100 Subject: [PATCH 0618/1179] target/ppc: Set disassemble_info::endian value in disas_set_info() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback always set\ the disassemble_info::endian field. Reviewed-by: Thomas Huth Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-7-philmd@linaro.org> --- target/ppc/cpu_init.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index 425049ab0936..b9772c53ecce 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -7401,6 +7401,8 @@ static void ppc_disas_set_info(CPUState *cs, disassemble_info *info) if ((env->hflags >> MSR_LE) & 1) { info->endian = BFD_ENDIAN_LITTLE; + } else { + info->endian = BFD_ENDIAN_BIG; } info->mach = env->bfd_mach; if (!env->bfd_mach) { From 0a8bfcbe7ca32f160c47faa9d611173b0697a698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:31:44 +0100 Subject: [PATCH 0619/1179] target/riscv: Set disassemble_info::endian value in disas_set_info() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-8-philmd@linaro.org> --- target/riscv/cpu.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 47424fd5e2a0..6da391738f36 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -1152,6 +1152,15 @@ static void riscv_cpu_disas_set_info(CPUState *s, disassemble_info *info) CPURISCVState *env = &cpu->env; info->target_info = &cpu->cfg; + /* + * A couple of bits in MSTATUS set the endianness: + * - MSTATUS_UBE (User-mode), + * - MSTATUS_SBE (Supervisor-mode), + * - MSTATUS_MBE (Machine-mode) + * but we don't implement that yet. + */ + info->endian = BFD_ENDIAN_LITTLE; + switch (env->xl) { case MXL_RV32: info->print_insn = print_insn_riscv32; From 35e9b36d6e724160d8f33ab9b61dd8b660e4df1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:32:03 +0100 Subject: [PATCH 0620/1179] target/sh4: Set disassemble_info::endian value in disas_set_info() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-9-philmd@linaro.org> --- target/sh4/cpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/target/sh4/cpu.c b/target/sh4/cpu.c index 22cdf9b4e122..c2aaa40a037f 100644 --- a/target/sh4/cpu.c +++ b/target/sh4/cpu.c @@ -143,6 +143,8 @@ static void superh_cpu_reset_hold(Object *obj, ResetType type) static void superh_cpu_disas_set_info(CPUState *cpu, disassemble_info *info) { + info->endian = TARGET_BIG_ENDIAN ? BFD_ENDIAN_BIG + : BFD_ENDIAN_LITTLE; info->mach = bfd_mach_sh4; info->print_insn = print_insn_sh; } From 059eb605fd67f15c0ca7f07c7a02c319035b095b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:35:24 +0100 Subject: [PATCH 0621/1179] target/xtensa: Set disassemble_info::endian value in disas_set_info() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Have the CPUClass::disas_set_info() callback set the disassemble_info::endian field. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Message-Id: <20250210212931.62401-10-philmd@linaro.org> --- target/xtensa/cpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/target/xtensa/cpu.c b/target/xtensa/cpu.c index efbfe73fcfbc..f9e298ace458 100644 --- a/target/xtensa/cpu.c +++ b/target/xtensa/cpu.c @@ -159,6 +159,8 @@ static void xtensa_cpu_disas_set_info(CPUState *cs, disassemble_info *info) info->private_data = cpu->env.config->isa; info->print_insn = print_insn_xtensa; + info->endian = TARGET_BIG_ENDIAN ? BFD_ENDIAN_BIG + : BFD_ENDIAN_LITTLE; } static void xtensa_cpu_realizefn(DeviceState *dev, Error **errp) From ae24e13a9f46128fb815957dc234020351797c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 14:34:19 +0100 Subject: [PATCH 0622/1179] disas: Remove target_words_bigendian() call in initialize_debug_target() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All CPUClass implementating disas_set_info() must set the disassemble_info::endian value. Ensure that by setting %endian to BFD_ENDIAN_UNKNOWN before calling the CPUClass::disas_set_info() handler, then asserting %endian is not BFD_ENDIAN_UNKNOWN after the call. This allows removing the target_words_bigendian() call in disas/. Suggested-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Thomas Huth Message-Id: <20250210212931.62401-11-philmd@linaro.org> --- disas/disas-common.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/disas/disas-common.c b/disas/disas-common.c index de61f6d8a123..ae3f9e46ea1e 100644 --- a/disas/disas-common.c +++ b/disas/disas-common.c @@ -7,7 +7,6 @@ #include "disas/disas.h" #include "disas/capstone.h" #include "hw/core/cpu.h" -#include "exec/tswap.h" #include "disas-internal.h" @@ -61,15 +60,12 @@ void disas_initialize_debug_target(CPUDebug *s, CPUState *cpu) s->cpu = cpu; s->info.print_address_func = print_address; - if (target_words_bigendian()) { - s->info.endian = BFD_ENDIAN_BIG; - } else { - s->info.endian = BFD_ENDIAN_LITTLE; - } + s->info.endian = BFD_ENDIAN_UNKNOWN; CPUClass *cc = CPU_GET_CLASS(cpu); if (cc->disas_set_info) { cc->disas_set_info(cpu, &s->info); + g_assert(s->info.endian != BFD_ENDIAN_UNKNOWN); } } From 0048035a870312f2fd0f3dd28115398d26e419bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 9 Feb 2025 22:11:32 +0100 Subject: [PATCH 0623/1179] target/i386: Constify X86CPUModel uses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210133134.90879-2-philmd@linaro.org> --- target/i386/cpu.c | 8 ++++---- target/i386/cpu.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index ab328485acce..b3e1c2bca499 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6563,7 +6563,7 @@ void x86_cpu_apply_props(X86CPU *cpu, PropValue *props) * Only for builtin_x86_defs models initialized with x86_register_cpudef_types. */ -static void x86_cpu_apply_version_props(X86CPU *cpu, X86CPUModel *model) +static void x86_cpu_apply_version_props(X86CPU *cpu, const X86CPUModel *model) { const X86CPUVersionDefinition *vdef; X86CPUVersion version = x86_cpu_model_resolve_version(model); @@ -6592,7 +6592,7 @@ static void x86_cpu_apply_version_props(X86CPU *cpu, X86CPUModel *model) } static const CPUCaches *x86_cpu_get_versioned_cache_info(X86CPU *cpu, - X86CPUModel *model) + const X86CPUModel *model) { const X86CPUVersionDefinition *vdef; X86CPUVersion version = x86_cpu_model_resolve_version(model); @@ -6620,7 +6620,7 @@ static const CPUCaches *x86_cpu_get_versioned_cache_info(X86CPU *cpu, * Load data from X86CPUDefinition into a X86CPU object. * Only for builtin_x86_defs models initialized with x86_register_cpudef_types. */ -static void x86_cpu_load_model(X86CPU *cpu, X86CPUModel *model) +static void x86_cpu_load_model(X86CPU *cpu, const X86CPUModel *model) { const X86CPUDefinition *def = model->cpudef; CPUX86State *env = &cpu->env; @@ -6690,7 +6690,7 @@ static const gchar *x86_gdb_arch_name(CPUState *cs) static void x86_cpu_cpudef_class_init(ObjectClass *oc, void *data) { - X86CPUModel *model = data; + const X86CPUModel *model = data; X86CPUClass *xcc = X86_CPU_CLASS(oc); CPUClass *cc = CPU_CLASS(oc); diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 10ce019e3f82..7882b63b9b61 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2327,7 +2327,7 @@ struct X86CPUClass { * CPU definition, automatically loaded by instance_init if not NULL. * Should be eventually replaced by subclass-specific property defaults. */ - X86CPUModel *model; + const X86CPUModel *model; bool host_cpuid_required; int ordering; From 1e6fbd637bbbfdd8ff13ed665b5294fab4771862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 9 Feb 2025 22:11:38 +0100 Subject: [PATCH 0624/1179] target/sparc: Constify SPARCCPUClass::cpu_def MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210133134.90879-3-philmd@linaro.org> --- target/sparc/cpu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/sparc/cpu.h b/target/sparc/cpu.h index dda811503b56..462bcb6c0e6e 100644 --- a/target/sparc/cpu.h +++ b/target/sparc/cpu.h @@ -574,7 +574,7 @@ struct SPARCCPUClass { DeviceRealize parent_realize; ResettablePhases parent_phases; - sparc_def_t *cpu_def; + const sparc_def_t *cpu_def; }; #ifndef CONFIG_USER_ONLY From 05769aae6288a69ba04b0162ed0a15b08b2b7878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 9 Feb 2025 22:11:46 +0100 Subject: [PATCH 0625/1179] target/xtensa: Finalize config in xtensa_register_core() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make XtensaConfigList::config not const. Only modify XtensaConfig within xtensa_register_core(), when the class is registered, not when it is initialized. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Acked-by: Max Filippov Message-Id: <20250210133134.90879-4-philmd@linaro.org> --- target/xtensa/cpu.h | 2 +- target/xtensa/helper.c | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/target/xtensa/cpu.h b/target/xtensa/cpu.h index 0e6302c5bd36..8d70bfc0cd42 100644 --- a/target/xtensa/cpu.h +++ b/target/xtensa/cpu.h @@ -490,7 +490,7 @@ typedef struct XtensaConfig { } XtensaConfig; typedef struct XtensaConfigList { - const XtensaConfig *config; + XtensaConfig *config; struct XtensaConfigList *next; } XtensaConfigList; diff --git a/target/xtensa/helper.c b/target/xtensa/helper.c index 2978c471c1fc..f64699b116df 100644 --- a/target/xtensa/helper.c +++ b/target/xtensa/helper.c @@ -173,9 +173,8 @@ static void xtensa_core_class_init(ObjectClass *oc, void *data) { CPUClass *cc = CPU_CLASS(oc); XtensaCPUClass *xcc = XTENSA_CPU_CLASS(oc); - XtensaConfig *config = data; + const XtensaConfig *config = data; - xtensa_finalize_config(config); xcc->config = config; /* @@ -195,6 +194,8 @@ void xtensa_register_core(XtensaConfigList *node) .class_data = (void *)node->config, }; + xtensa_finalize_config(node->config); + node->next = xtensa_cores; xtensa_cores = node; type.name = g_strdup_printf(XTENSA_CPU_TYPE_NAME("%s"), node->config->name); From 3bbcc0f732a173f164628243c6345b659c08900d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 10 Feb 2025 10:11:16 +0100 Subject: [PATCH 0626/1179] target/riscv: Declare RISCVCPUClass::misa_mxl_max as RISCVMXL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210133134.90879-5-philmd@linaro.org> --- target/riscv/cpu.c | 2 +- target/riscv/cpu.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 6da391738f36..d4f01965dfad 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -3056,7 +3056,7 @@ static void riscv_cpu_class_init(ObjectClass *c, void *data) { RISCVCPUClass *mcc = RISCV_CPU_CLASS(c); - mcc->misa_mxl_max = (uint32_t)(uintptr_t)data; + mcc->misa_mxl_max = (RISCVMXL)(uintptr_t)data; riscv_cpu_validate_misa_mxl(mcc); } diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 616c3bdc1c24..7de19b418369 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -539,7 +539,7 @@ struct RISCVCPUClass { DeviceRealize parent_realize; ResettablePhases parent_phases; - uint32_t misa_mxl_max; /* max mxl for this cpu */ + RISCVMXL misa_mxl_max; /* max mxl for this cpu */ }; static inline int riscv_has_ext(CPURISCVState *env, target_ulong ext) From 2101c85aeab4236c7b569bcf4ccaaecf318c231d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 10 Feb 2025 10:11:52 +0100 Subject: [PATCH 0627/1179] target/riscv: Convert misa_mxl_max using GLib macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use GLib conversion macros to pass misa_mxl_max as riscv_cpu_class_init() class data. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250210133134.90879-6-philmd@linaro.org> --- target/riscv/cpu.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index d4f01965dfad..6db2252aac71 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -3056,7 +3056,7 @@ static void riscv_cpu_class_init(ObjectClass *c, void *data) { RISCVCPUClass *mcc = RISCV_CPU_CLASS(c); - mcc->misa_mxl_max = (RISCVMXL)(uintptr_t)data; + mcc->misa_mxl_max = (RISCVMXL)GPOINTER_TO_UINT(data); riscv_cpu_validate_misa_mxl(mcc); } @@ -3158,7 +3158,7 @@ void riscv_isa_write_fdt(RISCVCPU *cpu, void *fdt, char *nodename) .parent = TYPE_RISCV_DYNAMIC_CPU, \ .instance_init = (initfn), \ .class_init = riscv_cpu_class_init, \ - .class_data = (void *)(misa_mxl_max) \ + .class_data = GUINT_TO_POINTER(misa_mxl_max) \ } #define DEFINE_VENDOR_CPU(type_name, misa_mxl_max, initfn) \ @@ -3167,7 +3167,7 @@ void riscv_isa_write_fdt(RISCVCPU *cpu, void *fdt, char *nodename) .parent = TYPE_RISCV_VENDOR_CPU, \ .instance_init = (initfn), \ .class_init = riscv_cpu_class_init, \ - .class_data = (void *)(misa_mxl_max) \ + .class_data = GUINT_TO_POINTER(misa_mxl_max) \ } #define DEFINE_BARE_CPU(type_name, misa_mxl_max, initfn) \ @@ -3176,7 +3176,7 @@ void riscv_isa_write_fdt(RISCVCPU *cpu, void *fdt, char *nodename) .parent = TYPE_RISCV_BARE_CPU, \ .instance_init = (initfn), \ .class_init = riscv_cpu_class_init, \ - .class_data = (void *)(misa_mxl_max) \ + .class_data = GUINT_TO_POINTER(misa_mxl_max) \ } #define DEFINE_PROFILE_CPU(type_name, misa_mxl_max, initfn) \ @@ -3185,7 +3185,7 @@ void riscv_isa_write_fdt(RISCVCPU *cpu, void *fdt, char *nodename) .parent = TYPE_RISCV_BARE_CPU, \ .instance_init = (initfn), \ .class_init = riscv_cpu_class_init, \ - .class_data = (void *)(misa_mxl_max) \ + .class_data = GUINT_TO_POINTER(misa_mxl_max) \ } static const TypeInfo riscv_cpu_type_infos[] = { From 35487a6dc0e53b998217b0963d10c18e84c5bb28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Feb 2025 17:15:26 +0100 Subject: [PATCH 0628/1179] target/alpha: Do not mix exception flags and FPCR bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_float_exception_flags() returns exception flags, which are distinct from the FPCR bits used as error code. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250211162604.83446-1-philmd@linaro.org> --- target/alpha/fpu_helper.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/target/alpha/fpu_helper.c b/target/alpha/fpu_helper.c index f810a9b6a47a..6aefb9b851ab 100644 --- a/target/alpha/fpu_helper.c +++ b/target/alpha/fpu_helper.c @@ -455,29 +455,28 @@ static uint64_t do_cvttq(CPUAlphaState *env, uint64_t a, int roundmode) { float64 fa; int64_t ret; - uint32_t exc; + uint32_t exc = 0; + int flags; fa = t_to_float64(a); ret = float64_to_int64_modulo(fa, roundmode, &FP_STATUS); - exc = get_float_exception_flags(&FP_STATUS); - if (unlikely(exc)) { + flags = get_float_exception_flags(&FP_STATUS); + if (unlikely(flags)) { set_float_exception_flags(0, &FP_STATUS); /* We need to massage the resulting exceptions. */ - if (exc & float_flag_invalid_cvti) { + if (flags & float_flag_invalid_cvti) { /* Overflow, either normal or infinity. */ if (float64_is_infinity(fa)) { exc = FPCR_INV; } else { exc = FPCR_IOV | FPCR_INE; } - } else if (exc & float_flag_invalid) { + } else if (flags & float_flag_invalid) { exc = FPCR_INV; - } else if (exc & float_flag_inexact) { + } else if (flags & float_flag_inexact) { exc = FPCR_INE; - } else { - exc = 0; } } env->error_code = exc; From df9ae6aa84b92bb73c84194dc60f938e2495594c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 12 Feb 2025 09:50:11 +0100 Subject: [PATCH 0629/1179] target/i386: Mark WHPX APIC region as little-endian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This device is only used by the x86 targets, which are only built as little-endian. Therefore the DEVICE_NATIVE_ENDIAN definition expand to DEVICE_LITTLE_ENDIAN (besides, the DEVICE_BIG_ENDIAN case isn't tested). Simplify directly using DEVICE_LITTLE_ENDIAN. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250212113938.38692-6-philmd@linaro.org> --- target/i386/whpx/whpx-apic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/i386/whpx/whpx-apic.c b/target/i386/whpx/whpx-apic.c index 4245ab68a273..630a9616d71d 100644 --- a/target/i386/whpx/whpx-apic.c +++ b/target/i386/whpx/whpx-apic.c @@ -231,7 +231,7 @@ static void whpx_apic_mem_write(void *opaque, hwaddr addr, static const MemoryRegionOps whpx_apic_io_ops = { .read = whpx_apic_mem_read, .write = whpx_apic_mem_write, - .endianness = DEVICE_NATIVE_ENDIAN, + .endianness = DEVICE_LITTLE_ENDIAN, }; static void whpx_apic_reset(APICCommonState *s) From f2d4df439e0b2c2c3cebf792a7966466c9d97b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Mar 2025 22:26:08 +0100 Subject: [PATCH 0630/1179] system: Open-code qemu_init_arch_modules() using target_name() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mostly revert commit c80cafa0c73 ("system: Add qemu_init_arch_modules") but using target_name() instead of the target specific 'TARGET_NAME' definition. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250305005225.95051-3-philmd@linaro.org> --- include/system/arch_init.h | 2 -- system/arch_init.c | 9 --------- system/vl.c | 7 ++++++- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/include/system/arch_init.h b/include/system/arch_init.h index 5b1c1026f3af..d8b77440487d 100644 --- a/include/system/arch_init.h +++ b/include/system/arch_init.h @@ -27,6 +27,4 @@ enum { extern const uint32_t arch_type; -void qemu_init_arch_modules(void); - #endif diff --git a/system/arch_init.c b/system/arch_init.c index d2c32f848872..b1baed18a300 100644 --- a/system/arch_init.c +++ b/system/arch_init.c @@ -22,7 +22,6 @@ * THE SOFTWARE. */ #include "qemu/osdep.h" -#include "qemu/module.h" #include "system/arch_init.h" #ifdef TARGET_SPARC @@ -40,11 +39,3 @@ int graphic_depth = 32; #endif const uint32_t arch_type = QEMU_ARCH; - -void qemu_init_arch_modules(void) -{ -#ifdef CONFIG_MODULES - module_init_info(qemu_modinfo); - module_allow_arch(TARGET_NAME); -#endif -} diff --git a/system/vl.c b/system/vl.c index 8f776684ec87..04f78466c412 100644 --- a/system/vl.c +++ b/system/vl.c @@ -26,6 +26,7 @@ #include "qemu/help-texts.h" #include "qemu/datadir.h" #include "qemu/units.h" +#include "qemu/module.h" #include "exec/cpu-common.h" #include "exec/page-vary.h" #include "hw/qdev-properties.h" @@ -78,6 +79,7 @@ #include "hw/block/block.h" #include "hw/i386/x86.h" #include "hw/i386/pc.h" +#include "hw/core/cpu.h" #include "migration/cpr.h" #include "migration/misc.h" #include "migration/snapshot.h" @@ -2885,7 +2887,10 @@ void qemu_init(int argc, char **argv) os_setup_limits(); - qemu_init_arch_modules(); +#ifdef CONFIG_MODULES + module_init_info(qemu_modinfo); + module_allow_arch(target_name()); +#endif qemu_init_subsystems(); From 92941c94e7f4858fdd61b4c1b85f6d1c6f164359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 5 Mar 2025 12:33:14 +0100 Subject: [PATCH 0631/1179] include: Poison TARGET_PHYS_ADDR_SPACE_BITS definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensure common code never use this target specific definition. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250305153929.43687-4-philmd@linaro.org> --- include/exec/poison.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/exec/poison.h b/include/exec/poison.h index f4283f693af2..d6d4832854e0 100644 --- a/include/exec/poison.h +++ b/include/exec/poison.h @@ -48,6 +48,7 @@ #pragma GCC poison TARGET_PAGE_MASK #pragma GCC poison TARGET_PAGE_BITS #pragma GCC poison TARGET_PAGE_ALIGN +#pragma GCC poison TARGET_PHYS_ADDR_SPACE_BITS #pragma GCC poison CPU_INTERRUPT_HARD #pragma GCC poison CPU_INTERRUPT_EXITTB From 089fa3d7302b38285ae146de8bbe5cf6ecc04f34 Mon Sep 17 00:00:00 2001 From: Song Gao Date: Wed, 5 Mar 2025 14:33:10 +0800 Subject: [PATCH 0632/1179] target/loongarch: fix 'make check-functional' failed some tlb instructions get the tlb_ps from tlb->misc but the value may has been initialized to 0,just check the tlb_ps skip the function and write a log. Signed-off-by: Song Gao Reviewed-by: Bibo Mao Message-Id: <20250305063311.830674-2-gaosong@loongson.cn> --- target/loongarch/tcg/tlb_helper.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/target/loongarch/tcg/tlb_helper.c b/target/loongarch/tcg/tlb_helper.c index a323606e5a9e..27c729b5b5bc 100644 --- a/target/loongarch/tcg/tlb_helper.c +++ b/target/loongarch/tcg/tlb_helper.c @@ -123,7 +123,11 @@ static void invalidate_tlb_entry(CPULoongArchState *env, int index) uint8_t tlb_v0 = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, V); uint8_t tlb_v1 = FIELD_EX64(tlb->tlb_entry1, TLBENTRY, V); uint64_t tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); + uint8_t tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); + if (!tlb_e) { + return; + } if (index >= LOONGARCH_STLB) { tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); } else { @@ -427,7 +431,11 @@ void helper_invtlb_page_asid(CPULoongArchState *env, target_ulong info, uint16_t tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); uint64_t vpn, tlb_vppn; uint8_t tlb_ps, compare_shift; + uint8_t tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); + if (!tlb_e) { + continue; + } if (i >= LOONGARCH_STLB) { tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); } else { @@ -456,7 +464,11 @@ void helper_invtlb_page_asid_or_g(CPULoongArchState *env, uint16_t tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); uint64_t vpn, tlb_vppn; uint8_t tlb_ps, compare_shift; + uint8_t tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); + if (!tlb_e) { + continue; + } if (i >= LOONGARCH_STLB) { tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); } else { From d882c284a3d4472d827e49a7357198b611900b08 Mon Sep 17 00:00:00 2001 From: Song Gao Date: Wed, 5 Mar 2025 14:33:11 +0800 Subject: [PATCH 0633/1179] target/loongarch: check tlb_ps For LoongArch th min tlb_ps is 12(4KB), for TLB code, the tlb_ps may be 0,this may case UndefinedBehavior Add a check-tlb_ps fuction to check tlb_ps, to make sure the tlb_ps is avalablie. we check tlb_ps when get the tlb_ps from tlb->misc or CSR bits. 1. cpu reset set CSR_PWCL.PTBASE and CSR_STLBPS.PS bits a default value from CSR_PRCFG2; 2. tlb instructions. some tlb instructions get the tlb_ps from tlb->misc but the value may has been initialized to 0. we need just check the tlb_ps skip the function and write a guest log. 3. csrwr instructions. to make sure CSR_PWCL.PTBASE and CSR_STLBPS.PS bits are avalable, cheke theses bits and set a default value from CSR_PRCFG2. Signed-off-by: Song Gao Reviewed-by: Bibo Mao Message-Id: <20250305063311.830674-3-gaosong@loongson.cn> --- target/loongarch/cpu.c | 11 +++++--- target/loongarch/helper.h | 1 + target/loongarch/internals.h | 2 ++ target/loongarch/tcg/csr_helper.c | 26 ++++++++++++++++--- .../tcg/insn_trans/trans_privileged.c.inc | 1 + target/loongarch/tcg/tlb_helper.c | 23 ++++++++++++++-- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index ac514a15fba8..0486853048e6 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -544,6 +544,7 @@ static void loongarch_max_initfn(Object *obj) static void loongarch_cpu_reset_hold(Object *obj, ResetType type) { + uint8_t tlb_ps; CPUState *cs = CPU(obj); LoongArchCPUClass *lacc = LOONGARCH_CPU_GET_CLASS(obj); CPULoongArchState *env = cpu_env(cs); @@ -592,13 +593,17 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type) */ env->CSR_PGDH = 0; env->CSR_PGDL = 0; - env->CSR_PWCL = 0; env->CSR_PWCH = 0; - env->CSR_STLBPS = 0; env->CSR_EENTRY = 0; env->CSR_TLBRENTRY = 0; env->CSR_MERRENTRY = 0; - + /* set CSR_PWCL.PTBASE and CSR_STLBPS.PS bits from CSR_PRCFG2 */ + if (env->CSR_PRCFG2 == 0) { + env->CSR_PRCFG2 = 0x3fffff000; + } + tlb_ps = ctz32(env->CSR_PRCFG2); + env->CSR_STLBPS = FIELD_DP64(env->CSR_STLBPS, CSR_STLBPS, PS, tlb_ps); + env->CSR_PWCL = FIELD_DP64(env->CSR_PWCL, CSR_PWCL, PTBASE, tlb_ps); for (n = 0; n < 4; n++) { env->CSR_DMW[n] = FIELD_DP64(env->CSR_DMW[n], CSR_DMW, PLV0, 0); env->CSR_DMW[n] = FIELD_DP64(env->CSR_DMW[n], CSR_DMW, PLV1, 0); diff --git a/target/loongarch/helper.h b/target/loongarch/helper.h index 943517b5f22c..1d5cb0198c97 100644 --- a/target/loongarch/helper.h +++ b/target/loongarch/helper.h @@ -100,6 +100,7 @@ DEF_HELPER_1(rdtime_d, i64, env) DEF_HELPER_1(csrrd_pgd, i64, env) DEF_HELPER_1(csrrd_cpuid, i64, env) DEF_HELPER_1(csrrd_tval, i64, env) +DEF_HELPER_2(csrwr_stlbps, i64, env, tl) DEF_HELPER_2(csrwr_estat, i64, env, tl) DEF_HELPER_2(csrwr_asid, i64, env, tl) DEF_HELPER_2(csrwr_tcfg, i64, env, tl) diff --git a/target/loongarch/internals.h b/target/loongarch/internals.h index 7b254c5f49cf..1cd959a76677 100644 --- a/target/loongarch/internals.h +++ b/target/loongarch/internals.h @@ -43,6 +43,8 @@ enum { TLBRET_PE = 7, }; +bool check_ps(CPULoongArchState *ent, int ps); + extern const VMStateDescription vmstate_loongarch_cpu; void loongarch_cpu_set_irq(void *opaque, int irq, int level); diff --git a/target/loongarch/tcg/csr_helper.c b/target/loongarch/tcg/csr_helper.c index 6c95be991083..289d89266edf 100644 --- a/target/loongarch/tcg/csr_helper.c +++ b/target/loongarch/tcg/csr_helper.c @@ -17,6 +17,22 @@ #include "hw/irq.h" #include "cpu-csr.h" +target_ulong helper_csrwr_stlbps(CPULoongArchState *env, target_ulong val) +{ + int64_t old_v = env->CSR_STLBPS; + + /* + * The real hardware only supports the min tlb_ps is 12 + * tlb_ps=0 may cause undefined-behavior. + */ + uint8_t tlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); + if (!check_ps(env, tlb_ps)) { + qemu_log_mask(LOG_GUEST_ERROR, + "Attempted set ps %d\n", tlb_ps); + } + return old_v; +} + target_ulong helper_csrrd_pgd(CPULoongArchState *env) { int64_t v; @@ -99,7 +115,7 @@ target_ulong helper_csrwr_ticlr(CPULoongArchState *env, target_ulong val) target_ulong helper_csrwr_pwcl(CPULoongArchState *env, target_ulong val) { - int shift; + int shift, ptbase; int64_t old_v = env->CSR_PWCL; /* @@ -107,12 +123,16 @@ target_ulong helper_csrwr_pwcl(CPULoongArchState *env, target_ulong val) * treated as illegal. */ shift = FIELD_EX64(val, CSR_PWCL, PTEWIDTH); + ptbase = FIELD_EX64(val, CSR_PWCL, PTBASE); if (shift) { qemu_log_mask(LOG_GUEST_ERROR, "Attempted set pte width with %d bit\n", 64 << shift); val = FIELD_DP64(val, CSR_PWCL, PTEWIDTH, 0); } - - env->CSR_PWCL = val; + if (!check_ps(env, ptbase)) { + qemu_log_mask(LOG_GUEST_ERROR, + "Attrmpted set ptbase 2^%d\n", ptbase); + } + env->CSR_PWCL =val; return old_v; } diff --git a/target/loongarch/tcg/insn_trans/trans_privileged.c.inc b/target/loongarch/tcg/insn_trans/trans_privileged.c.inc index 3afa23af7930..ecbfe23b6362 100644 --- a/target/loongarch/tcg/insn_trans/trans_privileged.c.inc +++ b/target/loongarch/tcg/insn_trans/trans_privileged.c.inc @@ -74,6 +74,7 @@ static bool set_csr_trans_func(unsigned int csr_num, GenCSRRead readfn, void loongarch_csr_translate_init(void) { + SET_CSR_FUNC(STLBPS, NULL, gen_helper_csrwr_stlbps); SET_CSR_FUNC(ESTAT, NULL, gen_helper_csrwr_estat); SET_CSR_FUNC(ASID, NULL, gen_helper_csrwr_asid); SET_CSR_FUNC(PGD, gen_helper_csrrd_pgd, NULL); diff --git a/target/loongarch/tcg/tlb_helper.c b/target/loongarch/tcg/tlb_helper.c index 27c729b5b5bc..5a426691bc73 100644 --- a/target/loongarch/tcg/tlb_helper.c +++ b/target/loongarch/tcg/tlb_helper.c @@ -18,6 +18,14 @@ #include "exec/log.h" #include "cpu-csr.h" +bool check_ps(CPULoongArchState *env, int tlb_ps) +{ + if (tlb_ps > 64) { + return false; + } + return BIT_ULL(tlb_ps) & (env->CSR_PRCFG2); +} + void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base, uint64_t *dir_width, target_ulong level) { @@ -191,8 +199,10 @@ static void fill_tlb_entry(CPULoongArchState *env, int index) lo1 = env->CSR_TLBELO1; } - if (csr_ps == 0) { - qemu_log_mask(CPU_LOG_MMU, "page size is 0\n"); + /*check csr_ps */ + if (!check_ps(env, csr_ps)) { + qemu_log_mask(LOG_GUEST_ERROR, "csr_ps %d is illegal\n", csr_ps); + return; } /* Only MTLB has the ps fields */ @@ -302,7 +312,16 @@ void helper_tlbfill(CPULoongArchState *env) pagesize = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, PS); } + if (!check_ps(env, pagesize)) { + qemu_log_mask(LOG_GUEST_ERROR, "pagesize %d is illegal\n", pagesize); + return; + } + stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); + if (!check_ps(env, stlb_ps)) { + qemu_log_mask(LOG_GUEST_ERROR, "stlb_ps %d is illegal\n", stlb_ps); + return; + } if (pagesize == stlb_ps) { /* Only write into STLB bits [47:13] */ From 42ea7f782a32df4ac58e7d9d73e736def3057ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 28 Feb 2025 10:27:36 +0000 Subject: [PATCH 0634/1179] tests/functional: skip memaddr tests on 32-bit builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the QEMU binary was built for a 32-bit ELF target we cannot run the memory address space tests as they all require ability to address more RAM that can be represented on 32-bit. We can't use a decorator to skip the tests as we need setUp() to run to pick the QEMU binary, thus we must call a method at the start of each test to check and skip it. The functional result is effectively the same as using a decorator, just less pretty. This code will go away when 32-bit hosts are full dropped from QEMU. The code allows any non-ELF target since all macOS versions supported at 64-bit only and we already dropped support for 32-bit Windows. Signed-off-by: Daniel P. Berrangé Message-ID: <20250228102738.3064045-6-berrange@redhat.com> [thuth: Add missing byteorder='little' to from_bytes()] Signed-off-by: Thomas Huth --- tests/functional/test_mem_addr_space.py | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/functional/test_mem_addr_space.py b/tests/functional/test_mem_addr_space.py index bb0cf062ca7f..2d9d31efb597 100755 --- a/tests/functional/test_mem_addr_space.py +++ b/tests/functional/test_mem_addr_space.py @@ -20,6 +20,25 @@ class MemAddrCheck(QemuSystemTest): # this reason. DELAY_Q35_BOOT_SEQUENCE = 1 + # This helper can go away when the 32-bit host deprecation + # turns into full & final removal of support. + def ensure_64bit_binary(self): + with open(self.qemu_bin, "rb") as fh: + ident = fh.read(4) + + # "\x7fELF" + if ident != bytes([0x7f, 0x45, 0x4C, 0x46]): + # Non-ELF file implies macOS or Windows which + # we already assume to be 64-bit only + return + + # bits == 1 -> 32-bit; bits == 2 -> 64-bit + bits = int.from_bytes(fh.read(1), byteorder='little') + if bits != 2: + # 32-bit ELF builds won't be able to address sufficient + # RAM to run the tests + self.skipTest("64-bit build host is required") + # first, lets test some 32-bit processors. # for all 32-bit cases, pci64_hole_size is 0. def test_phybits_low_pse36(self): @@ -38,6 +57,7 @@ def test_phybits_low_pse36(self): If maxmem is set to 59.5G with all other QEMU parameters identical, QEMU should start fine. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-machine', 'q35', '-m', '512,slots=1,maxmem=59.6G', '-cpu', 'pentium,pse36=on', '-display', 'none', @@ -55,6 +75,7 @@ def test_phybits_low_pae(self): access up to a maximum of 64GiB of memory. Rest is the same as the case with pse36 above. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-machine', 'q35', '-m', '512,slots=1,maxmem=59.6G', '-cpu', 'pentium,pae=on', '-display', 'none', @@ -71,6 +92,7 @@ def test_phybits_ok_pentium_pse36(self): Setting maxmem to 59.5G and making sure that QEMU can start with the same options as the failing case above with pse36 cpu feature. """ + self.ensure_64bit_binary() self.vm.add_args('-machine', 'q35', '-m', '512,slots=1,maxmem=59.5G', '-cpu', 'pentium,pse36=on', '-display', 'none', @@ -88,6 +110,7 @@ def test_phybits_ok_pentium_pae(self): Setting maxmem to 59.5G and making sure that QEMU can start fine with the same options as the case above. """ + self.ensure_64bit_binary() self.vm.add_args('-machine', 'q35', '-m', '512,slots=1,maxmem=59.5G', '-cpu', 'pentium,pae=on', '-display', 'none', @@ -104,6 +127,7 @@ def test_phybits_ok_pentium2(self): Pentium2 has 36 bits of addressing, so its same as pentium with pse36 ON. """ + self.ensure_64bit_binary() self.vm.add_args('-machine', 'q35', '-m', '512,slots=1,maxmem=59.5G', '-cpu', 'pentium2', '-display', 'none', @@ -123,6 +147,7 @@ def test_phybits_low_nonpse36(self): message because the region for memory hotplug is always placed above 4 GiB due to the PCI hole and simplicity. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-machine', 'q35', '-m', '512,slots=1,maxmem=4G', '-cpu', 'pentium', '-display', 'none', @@ -150,6 +175,7 @@ def test_phybits_low_tcg_q35_70_amd(self): which is equal to 987.5 GiB. Setting the value to 988 GiB should make QEMU fail with the error message. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-machine', 'pc-q35-7.0', '-m', '512,slots=1,maxmem=988G', '-display', 'none', @@ -170,6 +196,7 @@ def test_phybits_low_tcg_q35_71_amd(self): Make sure QEMU fails when maxmem size is 976 GiB (12 GiB less than 988 GiB). """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-machine', 'pc-q35-7.1', '-m', '512,slots=1,maxmem=976G', '-display', 'none', @@ -186,6 +213,7 @@ def test_phybits_ok_tcg_q35_70_amd(self): Same as q35-7.0 AMD case except that here we check that QEMU can successfully start when maxmem is < 988G. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-machine', 'pc-q35-7.0', '-m', '512,slots=1,maxmem=987.5G', '-display', 'none', @@ -202,6 +230,7 @@ def test_phybits_ok_tcg_q35_71_amd(self): Same as q35-7.1 AMD case except that here we check that QEMU can successfully start when maxmem is < 976G. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-machine', 'pc-q35-7.1', '-m', '512,slots=1,maxmem=975.5G', '-display', 'none', @@ -219,6 +248,7 @@ def test_phybits_ok_tcg_q35_71_intel(self): Intel cpu instead. QEMU should start fine in this case as "above_4G" memory starts at 4G. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-cpu', 'Skylake-Server', '-machine', 'pc-q35-7.1', '-m', '512,slots=1,maxmem=976G', @@ -243,6 +273,7 @@ def test_phybits_low_tcg_q35_71_amd_41bits(self): memory for the VM (1024 - 32 - 1 + 0.5). With 992 GiB, QEMU should fail to start. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-cpu', 'EPYC-v4,phys-bits=41', '-machine', 'pc-q35-7.1', '-m', '512,slots=1,maxmem=992G', @@ -261,6 +292,7 @@ def test_phybits_ok_tcg_q35_71_amd_41bits(self): Same as above but by setting maxram between 976 GiB and 992 Gib, QEMU should start fine. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-cpu', 'EPYC-v4,phys-bits=41', '-machine', 'pc-q35-7.1', '-m', '512,slots=1,maxmem=990G', @@ -281,6 +313,7 @@ def test_phybits_low_tcg_q35_intel_cxl(self): So maxmem here should be at most 986 GiB considering all memory boundary alignment constraints with 40 bits (1 TiB) of processor physical bits. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-cpu', 'Skylake-Server,phys-bits=40', '-machine', 'q35,cxl=on', '-m', '512,slots=1,maxmem=987G', @@ -299,6 +332,7 @@ def test_phybits_ok_tcg_q35_intel_cxl(self): with the exact same parameters as above, QEMU should start fine even with cxl enabled. """ + self.ensure_64bit_binary() self.vm.add_args('-S', '-cpu', 'Skylake-Server,phys-bits=40', '-machine', 'q35,cxl=on', '-m', '512,slots=1,maxmem=987G', From 5ad2c8f357a76bbc502452c60076a4b36708f46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 28 Feb 2025 10:27:37 +0000 Subject: [PATCH 0635/1179] tests/functional: drop unused 'get_tag' method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel P. Berrangé Reviewed-by: Thomas Huth Message-ID: <20250228102738.3064045-7-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/tuxruntest.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/functional/qemu_test/tuxruntest.py b/tests/functional/qemu_test/tuxruntest.py index 41a4945a14f5..ad74156f9c5e 100644 --- a/tests/functional/qemu_test/tuxruntest.py +++ b/tests/functional/qemu_test/tuxruntest.py @@ -24,17 +24,6 @@ class TuxRunBaselineTest(QemuSystemTest): # Tests are ~10-40s, allow for --debug/--enable-gcov overhead timeout = 100 - def get_tag(self, tagname, default=None): - """ - Get the metadata tag or return the default. - """ - utag = self._get_unique_tag_val(tagname) - print(f"{tagname}/{default} -> {utag}") - if utag: - return utag - - return default - def setUp(self): super().setUp() From 981395889201f556c37e18c7a896d2555ffa4373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 28 Feb 2025 10:27:38 +0000 Subject: [PATCH 0636/1179] tests/functional: stop output from zstd command when uncompressing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zstd command will print incremental decompression progress to stderr when running. Fortunately it is not on stdout as that would confuse the TAP parsing, but we should still not have this printed. By switching from 'check_call' to 'run' with the check=True and capture_output=True we'll get the desired silence on success, and on failure the raised exception will automatically include stdout/stderr data for diagnosis purposes. Signed-off-by: Daniel P. Berrangé Reviewed-by: Thomas Huth Message-ID: <20250228102738.3064045-8-berrange@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/uncompress.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/qemu_test/uncompress.py b/tests/functional/qemu_test/uncompress.py index 76dcf2238561..ce79da1b6860 100644 --- a/tests/functional/qemu_test/uncompress.py +++ b/tests/functional/qemu_test/uncompress.py @@ -13,7 +13,7 @@ import stat import shutil from urllib.parse import urlparse -from subprocess import check_call, CalledProcessError +from subprocess import run, CalledProcessError, DEVNULL from .asset import Asset @@ -46,8 +46,8 @@ def zstd_uncompress(zstd_path, output_path): return try: - check_call(['zstd', "-f", "-d", zstd_path, - "-o", output_path]) + run(['zstd', "-f", "-d", zstd_path, + "-o", output_path], capture_output=True, check=True) except CalledProcessError as e: os.remove(output_path) raise Exception( From 2c92ecb678cddf4bf3ced98f94acd2f3691c21bc Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 27 Feb 2025 11:39:10 +0100 Subject: [PATCH 0637/1179] tests/functional: Move the code for testing HTTP downloads to a common function We are going to use this code in other tests, too, so let's move it to the qemu_test module to be able to re-use it more easily. Message-ID: <20250227103915.19795-2-thuth@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/linuxkernel.py | 26 ++++++++++++++++++++++- tests/functional/test_intel_iommu.py | 22 +------------------ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/tests/functional/qemu_test/linuxkernel.py b/tests/functional/qemu_test/linuxkernel.py index 2c9598102d04..2aca0ee3cd03 100644 --- a/tests/functional/qemu_test/linuxkernel.py +++ b/tests/functional/qemu_test/linuxkernel.py @@ -3,8 +3,12 @@ # This work is licensed under the terms of the GNU GPL, version 2 or # later. See the COPYING file in the top-level directory. +import hashlib +import urllib.request + +from .cmd import wait_for_console_pattern, exec_command_and_wait_for_pattern from .testcase import QemuSystemTest -from .cmd import wait_for_console_pattern +from .utils import get_usernet_hostfwd_port class LinuxKernelTest(QemuSystemTest): @@ -26,3 +30,23 @@ def launch_kernel(self, kernel, initrd=None, dtb=None, console_index=0, self.vm.launch() if wait_for: self.wait_for_console_pattern(wait_for) + + def check_http_download(self, filename, hashsum, guestport=8080, + pythoncmd='python3 -m http.server'): + exec_command_and_wait_for_pattern(self, + f'{pythoncmd} {guestport} & sleep 1', + f'Serving HTTP on 0.0.0.0 port {guestport}') + hl = hashlib.sha256() + hostport = get_usernet_hostfwd_port(self.vm) + url = f'http://localhost:{hostport}{filename}' + self.log.info(f'Downloading {url} ...') + with urllib.request.urlopen(url) as response: + while True: + chunk = response.read(1 << 20) + if not chunk: + break + hl.update(chunk) + + digest = hl.hexdigest() + self.log.info(f'sha256sum of download is {digest}.') + self.assertEqual(digest, hashsum) diff --git a/tests/functional/test_intel_iommu.py b/tests/functional/test_intel_iommu.py index a9e8f82ab597..62268d6f2788 100755 --- a/tests/functional/test_intel_iommu.py +++ b/tests/functional/test_intel_iommu.py @@ -10,11 +10,7 @@ # This work is licensed under the terms of the GNU GPL, version 2 or # later. See the COPYING file in the top-level directory. -import hashlib -import urllib.request - from qemu_test import LinuxKernelTest, Asset, exec_command_and_wait_for_pattern -from qemu_test.utils import get_usernet_hostfwd_port class IntelIOMMU(LinuxKernelTest): @@ -125,23 +121,7 @@ def run_and_check(self): # Check virtio-net via HTTP: exec_command_and_wait_for_pattern(self, 'dhclient eth0', prompt) - exec_command_and_wait_for_pattern(self, - f'python3 -m http.server {self.GUEST_PORT} & sleep 1', - f'Serving HTTP on 0.0.0.0 port {self.GUEST_PORT}') - hl = hashlib.sha256() - hostport = get_usernet_hostfwd_port(self.vm) - url = f'http://localhost:{hostport}{filename}' - self.log.info(f'Downloading {url} ...') - with urllib.request.urlopen(url) as response: - while True: - chunk = response.read(1 << 20) - if not chunk: - break - hl.update(chunk) - - digest = hl.hexdigest() - self.log.info(f'sha256sum of download is {digest}.') - self.assertEqual(digest, hashsum) + self.check_http_download(filename, hashsum, self.GUEST_PORT) def test_intel_iommu(self): self.common_vm_setup() From 7b7f98efa7628a58fae594d3aa51b3c8a10293b3 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 27 Feb 2025 11:39:11 +0100 Subject: [PATCH 0638/1179] tests/functional/test_mips_malta: Add a network test via the pcnet NIC The kernel has a driver for the pcnet NIC included, and the initrd has a "tftp" command, so we can test a simple network transfer here, too. Message-ID: <20250227103915.19795-3-thuth@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/test_mips_malta.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/functional/test_mips_malta.py b/tests/functional/test_mips_malta.py index eaf372255ba3..9697c7d63f83 100755 --- a/tests/functional/test_mips_malta.py +++ b/tests/functional/test_mips_malta.py @@ -45,12 +45,15 @@ def test_mips_malta(self): 'dcfe3a7fe3200da3a00d176b95caaa086495eb158f2bff64afc67d7e1eb2cddc') def test_mips_malta_cpio(self): + self.require_netdev('user') + self.set_machine('malta') + self.require_device('pcnet') + kernel_path = self.archive_extract( self.ASSET_KERNEL_4_5_0, member='boot/vmlinux-4.5.0-2-4kc-malta') initrd_path = self.uncompress(self.ASSET_INITRD) - self.set_machine('malta') self.vm.set_console() kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0 console=tty ' @@ -58,6 +61,8 @@ def test_mips_malta_cpio(self): self.vm.add_args('-kernel', kernel_path, '-initrd', initrd_path, '-append', kernel_command_line, + '-netdev', 'user,id=n1,tftp=' + self.scratch_file('boot'), + '-device', 'pcnet,netdev=n1', '-no-reboot') self.vm.launch() self.wait_for_console_pattern('Boot successful.') @@ -66,6 +71,19 @@ def test_mips_malta_cpio(self): 'BogoMIPS') exec_command_and_wait_for_pattern(self, 'uname -a', 'Debian') + + exec_command_and_wait_for_pattern(self, 'ip link set eth0 up', + 'eth0: link up') + exec_command_and_wait_for_pattern(self, + 'ip addr add 10.0.2.15 dev eth0', + '#') + exec_command_and_wait_for_pattern(self, 'route add default eth0', '#') + exec_command_and_wait_for_pattern(self, + 'tftp -g -r vmlinux-4.5.0-2-4kc-malta 10.0.2.2', '#') + exec_command_and_wait_for_pattern(self, + 'md5sum vmlinux-4.5.0-2-4kc-malta', + 'a98218a7efbdefb2dfdf9ecd08c98318') + exec_command_and_wait_for_pattern(self, 'reboot', 'reboot: Restarting system') # Wait for VM to shut down gracefully From a31001b1c807cc59b2a9aa99845783cb40a27d0c Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 5 Mar 2025 08:43:53 +0100 Subject: [PATCH 0639/1179] tests/functional: Increase the timeout of the mips64el_replay test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We run the gitlab-CI with the untrusted tests enabled, and the test_replay_mips64el_malta_5KEc_cpio subtest is rather slow, so this already hit the standard 90 seconds timeout in the CI. Increase the timeout for more headroom. Reported-by: Stefan Hajnoczi Message-ID: <20250305074353.52552-1-thuth@redhat.com> Reviewed-by: Daniel P. Berrangé Reviewed-by: Stefan Hajnoczi Signed-off-by: Thomas Huth --- tests/functional/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 3fd2652c0782..97c3f4ad4e98 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -36,6 +36,7 @@ test_timeouts = { 'intel_iommu': 300, 'mips_malta' : 120, 'mipsel_replay' : 480, + 'mips64el_replay' : 180, 'netdev_ethtool' : 180, 'ppc_40p' : 240, 'ppc64_hv' : 1000, From 842721581fdbed45fa4738d02df8d28b1eaf28dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Tue, 4 Mar 2025 18:33:40 +0000 Subject: [PATCH 0640/1179] tests/functional: fix race in virtio balloon test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are two race conditions in the recently added virtio balloon test * The /dev/vda device node is not ready * The virtio-balloon driver has not issued the first stats refresh To fix the former, monitor dmesg for a line about 'vda'. To fix the latter, retry the stats query until seeing fresh data. Adding 'quiet' to the kernel command line reduces serial output which otherwise slows boot, making it less likely to hit the former race too. Signed-off-by: Daniel P. Berrangé Message-ID: <20250304183340.3749797-1-berrange@redhat.com> Reviewed-by: Thomas Huth Reviewed-by: David Hildenbrand [thuth: Break long line to avoid checkpatch error] Signed-off-by: Thomas Huth --- tests/functional/test_virtio_balloon.py | 26 ++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/functional/test_virtio_balloon.py b/tests/functional/test_virtio_balloon.py index 67b48e1b4e8e..082bf08c4e87 100755 --- a/tests/functional/test_virtio_balloon.py +++ b/tests/functional/test_virtio_balloon.py @@ -32,7 +32,7 @@ class VirtioBalloonx86(QemuSystemTest): 'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0') DEFAULT_KERNEL_PARAMS = ('root=/dev/vda1 console=ttyS0 net.ifnames=0 ' - 'rd.rescue') + 'rd.rescue quiet') def wait_for_console_pattern(self, success_message, vm=None): wait_for_console_pattern( @@ -47,6 +47,11 @@ def mount_root(self): prompt = '# ' self.wait_for_console_pattern(prompt) + # Synchronize on virtio-block driver creating the root device + exec_command_and_wait_for_pattern(self, + "while ! (dmesg -c | grep vda:) ; do sleep 1 ; done", + "vda1") + exec_command_and_wait_for_pattern(self, 'mount /dev/vda1 /sysroot', prompt) exec_command_and_wait_for_pattern(self, 'chroot /sysroot', @@ -65,10 +70,21 @@ def assert_initial_stats(self): assert val == UNSET_STATS_VALUE def assert_running_stats(self, then): - ret = self.vm.qmp('qom-get', - {'path': '/machine/peripheral/balloon', - 'property': 'guest-stats'})['return'] - when = ret.get('last-update') + # We told the QEMU to refresh stats every 100ms, but + # there can be a delay between virtio-ballon driver + # being modprobed and seeing the first stats refresh + # Retry a few times for robustness under heavy load + retries = 10 + when = 0 + while when == 0 and retries: + ret = self.vm.qmp('qom-get', + {'path': '/machine/peripheral/balloon', + 'property': 'guest-stats'})['return'] + when = ret.get('last-update') + if when == 0: + retries = retries - 1 + time.sleep(0.5) + now = time.time() assert when > then and when < now From 4dc11ee468df3ffdaa312a16b4ded3378133bb39 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 7 Mar 2025 07:25:37 +0100 Subject: [PATCH 0641/1179] tests/functional/test_virtio_balloon: Only use KVM for running this test The virtio_balloon test is currently hanging for unknown reasons when being run on the shared gitlab CI runners (which don't provide KVM, thus it's running in TCG mode there). All other functional tests that use the same asset (the Fedora 31 kernel) have already been marked to work only with KVM in the past, so those other tests are skipped on the shared gitlab CI runners. As long as the problem isn't fully understood and fixed, let's do the same with the virtio_balloon test to avoid that the CI is failing here. Message-ID: <20250307063904.1081961-1-thuth@redhat.com> Reviewed-by: David Hildenbrand Signed-off-by: Thomas Huth --- tests/functional/test_virtio_balloon.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/functional/test_virtio_balloon.py b/tests/functional/test_virtio_balloon.py index 082bf08c4e87..5877b6c408ca 100755 --- a/tests/functional/test_virtio_balloon.py +++ b/tests/functional/test_virtio_balloon.py @@ -110,6 +110,7 @@ def assert_running_stats(self, then): def test_virtio_balloon_stats(self): self.set_machine('q35') + self.require_accelerator("kvm") kernel_path = self.ASSET_KERNEL.fetch() initrd_path = self.ASSET_INITRD.fetch() diskimage_path = self.ASSET_DISKIMAGE.fetch() @@ -122,7 +123,7 @@ def test_virtio_balloon_stats(self): # reset, we can reliably catch the clean stats again in BIOS # phase before the guest OS launches self.vm.add_args("-boot", "menu=on") - self.vm.add_args("-machine", "q35,accel=kvm:tcg") + self.vm.add_args("-accel", "kvm") self.vm.add_args("-device", "virtio-balloon,id=balloon") self.vm.add_args('-drive', f'file={diskimage_path},if=none,id=drv0,snapshot=on') From e7f091d0c1750a57fd7ab39db50d1aae1c7647b0 Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Thu, 6 Mar 2025 11:37:06 +0530 Subject: [PATCH 0642/1179] doc: add missing 'Asset' type in function test doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems 'Asset' got missed in the documentation by mistake. Also fix the one spellcheck issue pointed by spellcheck Signed-off-by: Aditya Gupta Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250306060706.1982992-1-adityag@linux.ibm.com> Signed-off-by: Thomas Huth --- docs/devel/testing/functional.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/devel/testing/functional.rst b/docs/devel/testing/functional.rst index bcb5509512e7..a9fa45eac1f6 100644 --- a/docs/devel/testing/functional.rst +++ b/docs/devel/testing/functional.rst @@ -251,7 +251,7 @@ Many functional tests download assets (e.g. Linux kernels, initrds, firmware images, etc.) from the internet to be able to run tests with them. This imposes additional challenges to the test framework. -First there is the the problem that some people might not have an +First there is the problem that some people might not have an unconstrained internet connection, so such tests should not be run by default when running ``make check``. To accomplish this situation, the tests that download files should only be added to the "thorough" @@ -274,7 +274,9 @@ the tests are run. This pre-caching is done with the qemu_test.Asset class. To use it in your test, declare an asset in your test class with its URL and SHA256 checksum like this:: - ASSET_somename = ( + from qemu_test import Asset + + ASSET_somename = Asset( ('https://www.qemu.org/assets/images/qemu_head_200.png'), '34b74cad46ea28a2966c1d04e102510daf1fd73e6582b6b74523940d5da029dd') From 9cbff6f29ee099e7cb331802a1bf9b179c4c3934 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 6 Mar 2025 11:51:23 +0100 Subject: [PATCH 0643/1179] MAINTAINERS: Add docs/devel/testing/functional.rst to the functional section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an entry for docs/devel/testing/functional.rst to get notified on patches that change this file. Message-ID: <20250306105124.702131-1-thuth@redhat.com> Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Thomas Huth --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 692628cd7801..51f424ee8489 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4228,6 +4228,7 @@ Functional testing framework M: Thomas Huth R: Philippe Mathieu-Daudé R: Daniel P. Berrange +F: docs/devel/testing/functional.rst F: tests/functional/qemu_test/ Windows Hosted Continuous Integration From dfcee1ea4c52ac60e0a06221eafb7b6253eb10c3 Mon Sep 17 00:00:00 2001 From: Matthew Rosato Date: Wed, 26 Feb 2025 16:00:12 -0500 Subject: [PATCH 0644/1179] s390x/pci: add support for guests that request direct mapping When receiving a guest mpcifc(4) or mpcifc(6) instruction without the T bit set, treat this as a request to perform direct mapping instead of address translation. In order to facilitate this, pin the entirety of guest memory into the host iommu. Pinning for the direct mapping case is handled via vfio and its memory listener. Additionally, ram discard settings are inherited from vfio: coordinated discards (e.g. virtio-mem) are allowed while uncoordinated discards (e.g. virtio-balloon) are disabled. Subsequent guest DMA operations are all expected to be of the format guest_phys+sdma, allowing them to be used as lookup into the host iommu table. Signed-off-by: Matthew Rosato Reviewed-by: David Hildenbrand Message-ID: <20250226210013.238349-2-mjrosato@linux.ibm.com> Signed-off-by: Thomas Huth --- hw/s390x/s390-pci-bus.c | 39 +++++++++++++++++++++++++++++++-- hw/s390x/s390-pci-inst.c | 13 +++++++++-- hw/s390x/s390-pci-vfio.c | 23 +++++++++++++++---- hw/s390x/s390-virtio-ccw.c | 5 +++++ include/hw/s390x/s390-pci-bus.h | 3 +++ 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index 913d72cc7480..9d7b0f754073 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -18,6 +18,8 @@ #include "hw/s390x/s390-pci-inst.h" #include "hw/s390x/s390-pci-kvm.h" #include "hw/s390x/s390-pci-vfio.h" +#include "hw/s390x/s390-virtio-ccw.h" +#include "hw/boards.h" #include "hw/pci/pci_bus.h" #include "hw/qdev-properties.h" #include "hw/pci/pci_bridge.h" @@ -724,12 +726,42 @@ void s390_pci_iommu_enable(S390PCIIOMMU *iommu) g_free(name); } +void s390_pci_iommu_direct_map_enable(S390PCIIOMMU *iommu) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + S390CcwMachineState *s390ms = S390_CCW_MACHINE(ms); + + /* + * For direct-mapping we must map the entire guest address space. Rather + * than using an iommu, create a memory region alias that maps GPA X to + * IOVA X + SDMA. VFIO will handle pinning via its memory listener. + */ + g_autofree char *name = g_strdup_printf("iommu-dm-s390-%04x", + iommu->pbdev->uid); + + iommu->dm_mr = g_malloc0(sizeof(*iommu->dm_mr)); + memory_region_init_alias(iommu->dm_mr, OBJECT(&iommu->mr), name, + get_system_memory(), 0, + s390_get_memory_limit(s390ms)); + iommu->enabled = true; + memory_region_add_subregion(&iommu->mr, iommu->pbdev->zpci_fn.sdma, + iommu->dm_mr); +} + void s390_pci_iommu_disable(S390PCIIOMMU *iommu) { iommu->enabled = false; g_hash_table_remove_all(iommu->iotlb); - memory_region_del_subregion(&iommu->mr, MEMORY_REGION(&iommu->iommu_mr)); - object_unparent(OBJECT(&iommu->iommu_mr)); + if (iommu->dm_mr) { + memory_region_del_subregion(&iommu->mr, iommu->dm_mr); + object_unparent(OBJECT(iommu->dm_mr)); + g_free(iommu->dm_mr); + iommu->dm_mr = NULL; + } else { + memory_region_del_subregion(&iommu->mr, + MEMORY_REGION(&iommu->iommu_mr)); + object_unparent(OBJECT(&iommu->iommu_mr)); + } } static void s390_pci_iommu_free(S390pciState *s, PCIBus *bus, int32_t devfn) @@ -1145,6 +1177,7 @@ static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev, /* Always intercept emulated devices */ pbdev->interp = false; pbdev->forwarding_assist = false; + pbdev->rtr_avail = false; } if (s390_pci_msix_init(pbdev) && !pbdev->interp) { @@ -1510,6 +1543,8 @@ static const Property s390_pci_device_properties[] = { DEFINE_PROP_BOOL("interpret", S390PCIBusDevice, interp, true), DEFINE_PROP_BOOL("forwarding-assist", S390PCIBusDevice, forwarding_assist, true), + DEFINE_PROP_BOOL("relaxed-translation", S390PCIBusDevice, rtr_avail, + true), }; static const VMStateDescription s390_pci_device_vmstate = { diff --git a/hw/s390x/s390-pci-inst.c b/hw/s390x/s390-pci-inst.c index e386d75d58c3..8cdeb6cb7f71 100644 --- a/hw/s390x/s390-pci-inst.c +++ b/hw/s390x/s390-pci-inst.c @@ -16,6 +16,7 @@ #include "exec/memory.h" #include "qemu/error-report.h" #include "system/hw_accel.h" +#include "hw/boards.h" #include "hw/pci/pci_device.h" #include "hw/s390x/s390-pci-inst.h" #include "hw/s390x/s390-pci-bus.h" @@ -1008,17 +1009,25 @@ static int reg_ioat(CPUS390XState *env, S390PCIBusDevice *pbdev, ZpciFib fib, } /* currently we only support designation type 1 with translation */ - if (!(dt == ZPCI_IOTA_RTTO && t)) { + if (t && dt != ZPCI_IOTA_RTTO) { error_report("unsupported ioat dt %d t %d", dt, t); s390_program_interrupt(env, PGM_OPERAND, ra); return -EINVAL; + } else if (!t && !pbdev->rtr_avail) { + error_report("relaxed translation not allowed"); + s390_program_interrupt(env, PGM_OPERAND, ra); + return -EINVAL; } iommu->pba = pba; iommu->pal = pal; iommu->g_iota = g_iota; - s390_pci_iommu_enable(iommu); + if (t) { + s390_pci_iommu_enable(iommu); + } else { + s390_pci_iommu_direct_map_enable(iommu); + } return 0; } diff --git a/hw/s390x/s390-pci-vfio.c b/hw/s390x/s390-pci-vfio.c index 7dbbc76823a5..443e22291272 100644 --- a/hw/s390x/s390-pci-vfio.c +++ b/hw/s390x/s390-pci-vfio.c @@ -131,13 +131,28 @@ static void s390_pci_read_base(S390PCIBusDevice *pbdev, /* Store function type separately for type-specific behavior */ pbdev->pft = cap->pft; + /* + * If the device is a passthrough ISM device, disallow relaxed + * translation. + */ + if (pbdev->pft == ZPCI_PFT_ISM) { + pbdev->rtr_avail = false; + } + /* * If appropriate, reduce the size of the supported DMA aperture reported - * to the guest based upon the vfio DMA limit. + * to the guest based upon the vfio DMA limit. This is applicable for + * devices that are guaranteed to not use relaxed translation. If the + * device is capable of relaxed translation then we must advertise the + * full aperture. In this case, if translation is used then we will + * rely on the vfio DMA limit counting and use RPCIT CC1 / status 16 + * to request that the guest free DMA mappings as necessary. */ - vfio_size = pbdev->iommu->max_dma_limit << TARGET_PAGE_BITS; - if (vfio_size > 0 && vfio_size < cap->end_dma - cap->start_dma + 1) { - pbdev->zpci_fn.edma = cap->start_dma + vfio_size - 1; + if (!pbdev->rtr_avail) { + vfio_size = pbdev->iommu->max_dma_limit << TARGET_PAGE_BITS; + if (vfio_size > 0 && vfio_size < cap->end_dma - cap->start_dma + 1) { + pbdev->zpci_fn.edma = cap->start_dma + vfio_size - 1; + } } } diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index 51ae0c133d8e..a9b3db19f63d 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c @@ -936,8 +936,13 @@ static void ccw_machine_9_2_instance_options(MachineState *machine) static void ccw_machine_9_2_class_options(MachineClass *mc) { + static GlobalProperty compat[] = { + { TYPE_S390_PCI_DEVICE, "relaxed-translation", "off", }, + }; + ccw_machine_10_0_class_options(mc); compat_props_add(mc->compat_props, hw_compat_9_2, hw_compat_9_2_len); + compat_props_add(mc->compat_props, compat, G_N_ELEMENTS(compat)); } DEFINE_CCW_MACHINE(9, 2); diff --git a/include/hw/s390x/s390-pci-bus.h b/include/hw/s390x/s390-pci-bus.h index 2c43ea123f0c..04944d4fed75 100644 --- a/include/hw/s390x/s390-pci-bus.h +++ b/include/hw/s390x/s390-pci-bus.h @@ -277,6 +277,7 @@ struct S390PCIIOMMU { AddressSpace as; MemoryRegion mr; IOMMUMemoryRegion iommu_mr; + MemoryRegion *dm_mr; bool enabled; uint64_t g_iota; uint64_t pba; @@ -362,6 +363,7 @@ struct S390PCIBusDevice { bool interp; bool forwarding_assist; bool aif; + bool rtr_avail; QTAILQ_ENTRY(S390PCIBusDevice) link; }; @@ -389,6 +391,7 @@ int pci_chsc_sei_nt2_have_event(void); void s390_pci_sclp_configure(SCCB *sccb); void s390_pci_sclp_deconfigure(SCCB *sccb); void s390_pci_iommu_enable(S390PCIIOMMU *iommu); +void s390_pci_iommu_direct_map_enable(S390PCIIOMMU *iommu); void s390_pci_iommu_disable(S390PCIIOMMU *iommu); void s390_pci_generate_error_event(uint16_t pec, uint32_t fh, uint32_t fid, uint64_t faddr, uint32_t e); From d9b5dfc7122559e5b5959ecf534788b90c3dd102 Mon Sep 17 00:00:00 2001 From: Matthew Rosato Date: Wed, 26 Feb 2025 16:00:13 -0500 Subject: [PATCH 0645/1179] s390x/pci: indicate QEMU supports relaxed translation for passthrough Specifying this bit in the guest CLP response indicates that the guest can optionally choose to skip translation and instead use identity-mapped operations. Tested-by: Niklas Schnelle Reviewed-by: Niklas Schnelle Signed-off-by: Matthew Rosato Message-ID: <20250226210013.238349-3-mjrosato@linux.ibm.com> Signed-off-by: Thomas Huth --- hw/s390x/s390-pci-vfio.c | 5 ++++- include/hw/s390x/s390-pci-clp.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hw/s390x/s390-pci-vfio.c b/hw/s390x/s390-pci-vfio.c index 443e22291272..6236ac7f1e68 100644 --- a/hw/s390x/s390-pci-vfio.c +++ b/hw/s390x/s390-pci-vfio.c @@ -238,8 +238,11 @@ static void s390_pci_read_group(S390PCIBusDevice *pbdev, pbdev->pci_group = s390_group_create(pbdev->zpci_fn.pfgid, start_gid); resgrp = &pbdev->pci_group->zpci_group; + if (pbdev->rtr_avail) { + resgrp->fr |= CLP_RSP_QPCIG_MASK_RTR; + } if (cap->flags & VFIO_DEVICE_INFO_ZPCI_FLAG_REFRESH) { - resgrp->fr = 1; + resgrp->fr |= CLP_RSP_QPCIG_MASK_REFRESH; } resgrp->dasm = cap->dasm; resgrp->msia = cap->msi_addr; diff --git a/include/hw/s390x/s390-pci-clp.h b/include/hw/s390x/s390-pci-clp.h index 03b7f9ba5f74..6a635d693baf 100644 --- a/include/hw/s390x/s390-pci-clp.h +++ b/include/hw/s390x/s390-pci-clp.h @@ -158,6 +158,7 @@ typedef struct ClpRspQueryPciGrp { #define CLP_RSP_QPCIG_MASK_NOI 0xfff uint16_t i; uint8_t version; +#define CLP_RSP_QPCIG_MASK_RTR 0x20 #define CLP_RSP_QPCIG_MASK_FRAME 0x2 #define CLP_RSP_QPCIG_MASK_REFRESH 0x1 uint8_t fr; From a674db604db3fc4ef5267243dc991852f1f1bebc Mon Sep 17 00:00:00 2001 From: JianChunfu Date: Fri, 7 Mar 2025 10:08:18 +0000 Subject: [PATCH 0646/1179] hw/arm/smmu-common: Remove the repeated ttb field SMMUTransCfg->ttb is never used in QEMU, TT base address can be accessed by SMMUTransCfg->tt[i]->ttb. Signed-off-by: JianChunfu Reviewed-by: Eric Auger Message-id: 20250221031034.69822-1-jansef.jian@hj-micro.com Signed-off-by: Peter Maydell --- include/hw/arm/smmu-common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/hw/arm/smmu-common.h b/include/hw/arm/smmu-common.h index d1a4a64551d3..e5ad55bbae71 100644 --- a/include/hw/arm/smmu-common.h +++ b/include/hw/arm/smmu-common.h @@ -110,7 +110,6 @@ typedef struct SMMUTransCfg { /* Used by stage-1 only. */ bool aa64; /* arch64 or aarch32 translation table */ bool record_faults; /* record fault events */ - uint64_t ttb; /* TT base address */ uint8_t oas; /* output address width */ uint8_t tbi; /* Top Byte Ignore */ int asid; From 3b2e22c0bbe2ce07123d93961d52f17644562cd7 Mon Sep 17 00:00:00 2001 From: Patrick Venture Date: Fri, 7 Mar 2025 10:08:19 +0000 Subject: [PATCH 0647/1179] hw/gpio: npcm7xx: fixup out-of-bounds access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reg isn't validated to be a possible register before it's dereferenced for one case. The mmio space registered for the gpio device is 4KiB but there aren't that many registers in the struct. Cc: qemu-stable@nongnu.org Fixes: 526dbbe0874 ("hw/gpio: Add GPIO model for Nuvoton NPCM7xx") Signed-off-by: Patrick Venture Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250226024603.493148-1-venture@google.com Signed-off-by: Peter Maydell --- hw/gpio/npcm7xx_gpio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/gpio/npcm7xx_gpio.c b/hw/gpio/npcm7xx_gpio.c index 23e67424c9f4..2916056fae6d 100644 --- a/hw/gpio/npcm7xx_gpio.c +++ b/hw/gpio/npcm7xx_gpio.c @@ -220,8 +220,6 @@ static void npcm7xx_gpio_regs_write(void *opaque, hwaddr addr, uint64_t v, return; } - diff = s->regs[reg] ^ value; - switch (reg) { case NPCM7XX_GPIO_TLOCK1: case NPCM7XX_GPIO_TLOCK2: @@ -242,6 +240,7 @@ static void npcm7xx_gpio_regs_write(void *opaque, hwaddr addr, uint64_t v, case NPCM7XX_GPIO_PU: case NPCM7XX_GPIO_PD: case NPCM7XX_GPIO_IEM: + diff = s->regs[reg] ^ value; s->regs[reg] = value; npcm7xx_gpio_update_pins(s, diff); break; From 5f6b9b0564b69bd9548860419a70e79579d64aeb Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 7 Mar 2025 10:08:19 +0000 Subject: [PATCH 0648/1179] tests/functional/test_arm_sx1: Check whether the serial console is working MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel that is used in the sx1 test prints the usual Linux log onto the serial console, but this test currently ignores it. To make sure that the serial device is working properly, let's check for some strings in the output here. While we're at it, also add the test to the corresponding section in the MAINTAINERS file. Signed-off-by: Thomas Huth Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250226104833.1176253-1-thuth@redhat.com Signed-off-by: Peter Maydell --- MAINTAINERS | 1 + tests/functional/test_arm_sx1.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 5df6020ed545..699cf59e0b87 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2010,6 +2010,7 @@ S: Maintained F: hw/*/omap* F: include/hw/arm/omap.h F: docs/system/arm/sx1.rst +F: tests/functional/test_arm_sx1.py IPack M: Alberto Garcia diff --git a/tests/functional/test_arm_sx1.py b/tests/functional/test_arm_sx1.py index 4dd1e1859fa5..25800b388c99 100755 --- a/tests/functional/test_arm_sx1.py +++ b/tests/functional/test_arm_sx1.py @@ -43,7 +43,8 @@ def test_arm_sx1_initrd(self): self.vm.add_args('-append', f'kunit.enable=0 rdinit=/sbin/init {self.CONSOLE_ARGS}') self.vm.add_args('-no-reboot') self.launch_kernel(zimage_path, - initrd=initrd_path) + initrd=initrd_path, + wait_for='Boot successful') self.vm.wait(timeout=120) def test_arm_sx1_sd(self): @@ -54,7 +55,7 @@ def test_arm_sx1_sd(self): self.vm.add_args('-no-reboot') self.vm.add_args('-snapshot') self.vm.add_args('-drive', f'format=raw,if=sd,file={sd_fs_path}') - self.launch_kernel(zimage_path) + self.launch_kernel(zimage_path, wait_for='Boot successful') self.vm.wait(timeout=120) def test_arm_sx1_flash(self): @@ -65,7 +66,7 @@ def test_arm_sx1_flash(self): self.vm.add_args('-no-reboot') self.vm.add_args('-snapshot') self.vm.add_args('-drive', f'format=raw,if=pflash,file={flash_path}') - self.launch_kernel(zimage_path) + self.launch_kernel(zimage_path, wait_for='Boot successful') self.vm.wait(timeout=120) if __name__ == '__main__': From db6c2192839ee0282d38f6f6666a87e0629fcd13 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:19 +0000 Subject: [PATCH 0649/1179] target/arm: Apply correct timer offset when calculating deadlines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we are calculating timer deadlines, the correct definition of whether or not to apply an offset to the physical count is described in the Arm ARM DDI4087 rev L.a section D12.2.4.1. This is different from when the offset should be applied for a direct read of the counter sysreg. We got this right for the EL1 physical timer and for the EL1 virtual timer, but got all the rest wrong: they should be using a zero offset always. Factor the offset calculation out into a function that has a comment documenting exactly which offset it is calculating and which gets the HYP, SEC, and HYPVIRT cases right. Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Message-id: 20250204125009.2281315-2-peter.maydell@linaro.org --- target/arm/helper.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index 71dead7241b4..7f341d753cdc 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2460,6 +2460,32 @@ static uint64_t gt_phys_cnt_offset(CPUARMState *env) return gt_phys_raw_cnt_offset(env); } +static uint64_t gt_indirect_access_timer_offset(CPUARMState *env, int timeridx) +{ + /* + * Return the timer offset to use for indirect accesses to the timer. + * This is the Offset value as defined in D12.2.4.1 "Operation of the + * CompareValue views of the timers". + * + * The condition here is not always the same as the condition for + * whether to apply an offset register when doing a direct read of + * the counter sysreg; those conditions are described in the + * access pseudocode for each counter register. + */ + switch (timeridx) { + case GTIMER_PHYS: + return gt_phys_raw_cnt_offset(env); + case GTIMER_VIRT: + return env->cp15.cntvoff_el2; + case GTIMER_HYP: + case GTIMER_SEC: + case GTIMER_HYPVIRT: + return 0; + default: + g_assert_not_reached(); + } +} + static void gt_recalc_timer(ARMCPU *cpu, int timeridx) { ARMGenericTimer *gt = &cpu->env.cp15.c14_timer[timeridx]; @@ -2469,8 +2495,7 @@ static void gt_recalc_timer(ARMCPU *cpu, int timeridx) * Timer enabled: calculate and set current ISTATUS, irq, and * reset timer to when ISTATUS next has to change */ - uint64_t offset = timeridx == GTIMER_VIRT ? - cpu->env.cp15.cntvoff_el2 : gt_phys_raw_cnt_offset(&cpu->env); + uint64_t offset = gt_indirect_access_timer_offset(&cpu->env, timeridx); uint64_t count = gt_get_countervalue(&cpu->env); /* Note that this must be unsigned 64 bit arithmetic: */ int istatus = count - offset >= gt->cval; From 5709038aa8b4d58b8c201ed53c327074173a35c6 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:20 +0000 Subject: [PATCH 0650/1179] target/arm: Don't apply CNTVOFF_EL2 for EL2_VIRT timer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CNTVOFF_EL2 offset register should only be applied for accessses to CNTVCT_EL0 and for the EL1 virtual timer (CNTV_*). We were incorrectly applying it for the EL2 virtual timer (CNTHV_*). Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Message-id: 20250204125009.2281315-3-peter.maydell@linaro.org --- target/arm/helper.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index 7f341d753cdc..5729b313f84b 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2604,7 +2604,6 @@ static uint64_t gt_tval_read(CPUARMState *env, const ARMCPRegInfo *ri, switch (timeridx) { case GTIMER_VIRT: - case GTIMER_HYPVIRT: offset = gt_virt_cnt_offset(env); break; case GTIMER_PHYS: @@ -2624,7 +2623,6 @@ static void gt_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, switch (timeridx) { case GTIMER_VIRT: - case GTIMER_HYPVIRT: offset = gt_virt_cnt_offset(env); break; case GTIMER_PHYS: From bdd641541fbef0a27bf9f60e7eba6f8a31d4706c Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:20 +0000 Subject: [PATCH 0651/1179] target/arm: Make CNTPS_* UNDEF from Secure EL1 when Secure EL2 is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we added Secure EL2 support, we missed that this needs an update to the access code for the EL3 physical timer registers. These are supposed to UNDEF from Secure EL1 when Secure EL2 is enabled. (Note for stable backporting: for backports to branches where CP_ACCESS_UNDEFINED is not defined, the old name to use instead is CP_ACCESS_TRAP_UNCATEGORIZED.) Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Message-id: 20250204125009.2281315-4-peter.maydell@linaro.org --- target/arm/helper.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/target/arm/helper.c b/target/arm/helper.c index 5729b313f84b..5b6de446ace3 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2387,6 +2387,9 @@ static CPAccessResult gt_stimer_access(CPUARMState *env, if (!arm_is_secure(env)) { return CP_ACCESS_UNDEFINED; } + if (arm_is_el2_enabled(env)) { + return CP_ACCESS_UNDEFINED; + } if (!(env->cp15.scr_el3 & SCR_ST)) { return CP_ACCESS_TRAP_EL3; } From 4aecd4b442d7abb4355896d878ffc9b028625b01 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:20 +0000 Subject: [PATCH 0652/1179] target/arm: Always apply CNTVOFF_EL2 for CNTV_TVAL_EL02 accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we handle CNTV_TVAL_EL02 by calling gt_tval_read() for the EL1 virt timer. This is almost correct, but the underlying CNTV_TVAL_EL0 register behaves slightly differently. CNTV_TVAL_EL02 always applies the CNTVOFF_EL2 offset; CNTV_TVAL_EL0 doesn't do so if we're at EL2 and HCR_EL2.E2H is 1. We were getting this wrong, because we ended up in gt_virt_cnt_offset() and did the E2H check. Factor out the tval read/write calculation from the selection of the offset, so that we can special case gt_virt_tval_read() and gt_virt_tval_write() to unconditionally pass CNTVOFF_EL2. Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Message-id: 20250204125009.2281315-5-peter.maydell@linaro.org --- target/arm/helper.c | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index 5b6de446ace3..acf77793c79e 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2600,6 +2600,12 @@ static void gt_cval_write(CPUARMState *env, const ARMCPRegInfo *ri, gt_recalc_timer(env_archcpu(env), timeridx); } +static uint64_t do_tval_read(CPUARMState *env, int timeridx, uint64_t offset) +{ + return (uint32_t)(env->cp15.c14_timer[timeridx].cval - + (gt_get_countervalue(env) - offset)); +} + static uint64_t gt_tval_read(CPUARMState *env, const ARMCPRegInfo *ri, int timeridx) { @@ -2614,8 +2620,16 @@ static uint64_t gt_tval_read(CPUARMState *env, const ARMCPRegInfo *ri, break; } - return (uint32_t)(env->cp15.c14_timer[timeridx].cval - - (gt_get_countervalue(env) - offset)); + return do_tval_read(env, timeridx, offset); +} + +static void do_tval_write(CPUARMState *env, int timeridx, uint64_t value, + uint64_t offset) +{ + trace_arm_gt_tval_write(timeridx, value); + env->cp15.c14_timer[timeridx].cval = gt_get_countervalue(env) - offset + + sextract64(value, 0, 32); + gt_recalc_timer(env_archcpu(env), timeridx); } static void gt_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -2632,11 +2646,7 @@ static void gt_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, offset = gt_phys_cnt_offset(env); break; } - - trace_arm_gt_tval_write(timeridx, value); - env->cp15.c14_timer[timeridx].cval = gt_get_countervalue(env) - offset + - sextract64(value, 0, 32); - gt_recalc_timer(env_archcpu(env), timeridx); + do_tval_write(env, timeridx, value, offset); } static void gt_ctl_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -2768,13 +2778,21 @@ static void gt_virt_cval_write(CPUARMState *env, const ARMCPRegInfo *ri, static uint64_t gt_virt_tval_read(CPUARMState *env, const ARMCPRegInfo *ri) { - return gt_tval_read(env, ri, GTIMER_VIRT); + /* + * This is CNTV_TVAL_EL02; unlike the underlying CNTV_TVAL_EL0 + * we always apply CNTVOFF_EL2. Special case that here rather + * than going into the generic gt_tval_read() and then having + * to re-detect that it's this register. + * Note that the accessfn/perms mean we know we're at EL2 or EL3 here. + */ + return do_tval_read(env, GTIMER_VIRT, env->cp15.cntvoff_el2); } static void gt_virt_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { - gt_tval_write(env, ri, GTIMER_VIRT, value); + /* Similarly for writes to CNTV_TVAL_EL02 */ + do_tval_write(env, GTIMER_VIRT, value, env->cp15.cntvoff_el2); } static void gt_virt_ctl_write(CPUARMState *env, const ARMCPRegInfo *ri, From 02c648a0a103a1a7b2c077ec5a81da9907f45544 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:21 +0000 Subject: [PATCH 0653/1179] target/arm: Refactor handling of timer offset for direct register accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When reading or writing the timer registers, sometimes we need to apply one of the timer offsets. Specifically, this happens for direct reads of the counter registers CNTPCT_EL0 and CNTVCT_EL0 (and their self-synchronized variants CNTVCTSS_EL0 and CNTPCTSS_EL0). It also applies for direct reads and writes of the CNT*_TVAL_EL* registers that provide the 32-bit downcounting view of each timer. We currently do this with duplicated code in gt_tval_read() and gt_tval_write() and a special-case in gt_virt_cnt_read() and gt_cnt_read(). Refactor this so that we handle it all in a single function gt_direct_access_timer_offset(), to parallel how we handle the offset for indirect accesses. The call in the WFIT helper previously to gt_virt_cnt_offset() is now to gt_direct_access_timer_offset(); this is the correct behaviour, but it's not immediately obvious that it shouldn't be considered an indirect access, so we add an explanatory comment. This commit should make no behavioural changes. (Cc to stable because the following bugfix commit will depend on this one.) Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Alex Bennée Message-id: 20250204125009.2281315-6-peter.maydell@linaro.org --- target/arm/helper.c | 103 +++++++++++++++++++------------------ target/arm/internals.h | 5 +- target/arm/tcg/op_helper.c | 8 ++- 3 files changed, 62 insertions(+), 54 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index acf77793c79e..54147d97611c 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2455,14 +2455,6 @@ static uint64_t gt_phys_raw_cnt_offset(CPUARMState *env) return 0; } -static uint64_t gt_phys_cnt_offset(CPUARMState *env) -{ - if (arm_current_el(env) >= 2) { - return 0; - } - return gt_phys_raw_cnt_offset(env); -} - static uint64_t gt_indirect_access_timer_offset(CPUARMState *env, int timeridx) { /* @@ -2489,6 +2481,52 @@ static uint64_t gt_indirect_access_timer_offset(CPUARMState *env, int timeridx) } } +uint64_t gt_direct_access_timer_offset(CPUARMState *env, int timeridx) +{ + /* + * Return the timer offset to use for direct accesses to the + * counter registers CNTPCT and CNTVCT, and for direct accesses + * to the CNT*_TVAL registers. + * + * This isn't exactly the same as the indirect-access offset, + * because here we also care about what EL the register access + * is being made from. + * + * This corresponds to the access pseudocode for the registers. + */ + uint64_t hcr; + + switch (timeridx) { + case GTIMER_PHYS: + if (arm_current_el(env) >= 2) { + return 0; + } + return gt_phys_raw_cnt_offset(env); + case GTIMER_VIRT: + switch (arm_current_el(env)) { + case 2: + hcr = arm_hcr_el2_eff(env); + if (hcr & HCR_E2H) { + return 0; + } + break; + case 0: + hcr = arm_hcr_el2_eff(env); + if ((hcr & (HCR_E2H | HCR_TGE)) == (HCR_E2H | HCR_TGE)) { + return 0; + } + break; + } + return env->cp15.cntvoff_el2; + case GTIMER_HYP: + case GTIMER_SEC: + case GTIMER_HYPVIRT: + return 0; + default: + g_assert_not_reached(); + } +} + static void gt_recalc_timer(ARMCPU *cpu, int timeridx) { ARMGenericTimer *gt = &cpu->env.cp15.c14_timer[timeridx]; @@ -2561,34 +2599,14 @@ static void gt_timer_reset(CPUARMState *env, const ARMCPRegInfo *ri, static uint64_t gt_cnt_read(CPUARMState *env, const ARMCPRegInfo *ri) { - return gt_get_countervalue(env) - gt_phys_cnt_offset(env); -} - -uint64_t gt_virt_cnt_offset(CPUARMState *env) -{ - uint64_t hcr; - - switch (arm_current_el(env)) { - case 2: - hcr = arm_hcr_el2_eff(env); - if (hcr & HCR_E2H) { - return 0; - } - break; - case 0: - hcr = arm_hcr_el2_eff(env); - if ((hcr & (HCR_E2H | HCR_TGE)) == (HCR_E2H | HCR_TGE)) { - return 0; - } - break; - } - - return env->cp15.cntvoff_el2; + uint64_t offset = gt_direct_access_timer_offset(env, GTIMER_PHYS); + return gt_get_countervalue(env) - offset; } static uint64_t gt_virt_cnt_read(CPUARMState *env, const ARMCPRegInfo *ri) { - return gt_get_countervalue(env) - gt_virt_cnt_offset(env); + uint64_t offset = gt_direct_access_timer_offset(env, GTIMER_VIRT); + return gt_get_countervalue(env) - offset; } static void gt_cval_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -2609,16 +2627,7 @@ static uint64_t do_tval_read(CPUARMState *env, int timeridx, uint64_t offset) static uint64_t gt_tval_read(CPUARMState *env, const ARMCPRegInfo *ri, int timeridx) { - uint64_t offset = 0; - - switch (timeridx) { - case GTIMER_VIRT: - offset = gt_virt_cnt_offset(env); - break; - case GTIMER_PHYS: - offset = gt_phys_cnt_offset(env); - break; - } + uint64_t offset = gt_direct_access_timer_offset(env, timeridx); return do_tval_read(env, timeridx, offset); } @@ -2636,16 +2645,8 @@ static void gt_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, int timeridx, uint64_t value) { - uint64_t offset = 0; + uint64_t offset = gt_direct_access_timer_offset(env, timeridx); - switch (timeridx) { - case GTIMER_VIRT: - offset = gt_virt_cnt_offset(env); - break; - case GTIMER_PHYS: - offset = gt_phys_cnt_offset(env); - break; - } do_tval_write(env, timeridx, value, offset); } diff --git a/target/arm/internals.h b/target/arm/internals.h index a6ff228f9fd2..bb9623891923 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -1819,9 +1819,10 @@ int delete_hw_watchpoint(target_ulong addr, target_ulong len, int type); uint64_t gt_get_countervalue(CPUARMState *env); /* * Return the currently applicable offset between the system counter - * and CNTVCT_EL0 (this will be either 0 or the value of CNTVOFF_EL2). + * and the counter for the specified timer, as used for direct register + * accesses. */ -uint64_t gt_virt_cnt_offset(CPUARMState *env); +uint64_t gt_direct_access_timer_offset(CPUARMState *env, int timeridx); /* * Return mask of ARMMMUIdxBit values corresponding to an "invalidate diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index 02c375d196da..30786fd1ff4e 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -427,7 +427,13 @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) int target_el = check_wfx_trap(env, false, &excp); /* The WFIT should time out when CNTVCT_EL0 >= the specified value. */ uint64_t cntval = gt_get_countervalue(env); - uint64_t offset = gt_virt_cnt_offset(env); + /* + * We want the value that we would get if we read CNTVCT_EL0 from + * the current exception level, so the direct_access offset, not + * the indirect_access one. Compare the pseudocode LocalTimeoutEvent(), + * which calls VirtualCounterTimer(). + */ + uint64_t offset = gt_direct_access_timer_offset(env, GTIMER_VIRT); uint64_t cntvct = cntval - offset; uint64_t nexttick; From f9f99d7ca522339c1de2292f132bb8ddc3471c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Fri, 7 Mar 2025 10:08:21 +0000 Subject: [PATCH 0654/1179] target/arm: Implement SEL2 physical and virtual timers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When FEAT_SEL2 was implemented the SEL2 timers were missed. This shows up when building the latest Hafnium with SPMC_AT_EL=2. The actual implementation utilises the same logic as the rest of the timers so all we need to do is: - define the timers and their access functions - conditionally add the correct system registers - create a new accessfn as the rules are subtly different to the existing secure timer Fixes: e9152ee91c (target/arm: add ARMv8.4-SEL2 system registers) Signed-off-by: Alex Bennée Signed-off-by: Peter Maydell Reviewed-by: Peter Maydell Message-id: 20250204125009.2281315-7-peter.maydell@linaro.org Cc: qemu-stable@nongnu.org Cc: Andrei Homescu Cc: Arve Hjønnevåg Cc: Rémi Denis-Courmont [PMM: CP_ACCESS_TRAP_UNCATEGORIZED -> CP_ACCESS_UNDEFINED; offset logic now in gt_{indirect,direct}_access_timer_offset() ] Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- include/hw/arm/bsa.h | 2 + target/arm/cpu.c | 4 ++ target/arm/cpu.h | 2 + target/arm/gtimer.h | 4 +- target/arm/helper.c | 163 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 1 deletion(-) diff --git a/include/hw/arm/bsa.h b/include/hw/arm/bsa.h index 8eaab603c03a..13ed2d2ac191 100644 --- a/include/hw/arm/bsa.h +++ b/include/hw/arm/bsa.h @@ -22,6 +22,8 @@ #define QEMU_ARM_BSA_H /* These are architectural INTID values */ +#define ARCH_TIMER_S_EL2_VIRT_IRQ 19 +#define ARCH_TIMER_S_EL2_IRQ 20 #define VIRTUAL_PMU_IRQ 23 #define ARCH_GIC_MAINT_IRQ 25 #define ARCH_TIMER_NS_EL2_IRQ 26 diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 948defa3f5d4..cacbbc615a21 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -2069,6 +2069,10 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) arm_gt_stimer_cb, cpu); cpu->gt_timer[GTIMER_HYPVIRT] = timer_new(QEMU_CLOCK_VIRTUAL, scale, arm_gt_hvtimer_cb, cpu); + cpu->gt_timer[GTIMER_S_EL2_PHYS] = timer_new(QEMU_CLOCK_VIRTUAL, scale, + arm_gt_sel2timer_cb, cpu); + cpu->gt_timer[GTIMER_S_EL2_VIRT] = timer_new(QEMU_CLOCK_VIRTUAL, scale, + arm_gt_sel2vtimer_cb, cpu); } #endif diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 215845c7e256..8f52380c88c3 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -1171,6 +1171,8 @@ void arm_gt_vtimer_cb(void *opaque); void arm_gt_htimer_cb(void *opaque); void arm_gt_stimer_cb(void *opaque); void arm_gt_hvtimer_cb(void *opaque); +void arm_gt_sel2timer_cb(void *opaque); +void arm_gt_sel2vtimer_cb(void *opaque); unsigned int gt_cntfrq_period_ns(ARMCPU *cpu); void gt_rme_post_el_change(ARMCPU *cpu, void *opaque); diff --git a/target/arm/gtimer.h b/target/arm/gtimer.h index b992941bef11..0e89b8e58d02 100644 --- a/target/arm/gtimer.h +++ b/target/arm/gtimer.h @@ -15,7 +15,9 @@ enum { GTIMER_HYP = 2, GTIMER_SEC = 3, GTIMER_HYPVIRT = 4, -#define NUM_GTIMERS 5 + GTIMER_S_EL2_PHYS = 5, /* CNTHPS_* ; only if FEAT_SEL2 */ + GTIMER_S_EL2_VIRT = 6, /* CNTHVS_* ; only if FEAT_SEL2 */ +#define NUM_GTIMERS 7 }; #endif diff --git a/target/arm/helper.c b/target/arm/helper.c index 54147d97611c..407efe9427cd 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2404,6 +2404,45 @@ static CPAccessResult gt_stimer_access(CPUARMState *env, } } +static CPAccessResult gt_sel2timer_access(CPUARMState *env, + const ARMCPRegInfo *ri, + bool isread) +{ + /* + * The AArch64 register view of the secure EL2 timers are mostly + * accessible from EL3 and EL2 although can also be trapped to EL2 + * from EL1 depending on nested virt config. + */ + switch (arm_current_el(env)) { + case 0: /* UNDEFINED */ + return CP_ACCESS_UNDEFINED; + case 1: + if (!arm_is_secure(env)) { + /* UNDEFINED */ + return CP_ACCESS_UNDEFINED; + } else if (arm_hcr_el2_eff(env) & HCR_NV) { + /* Aarch64.SystemAccessTrap(EL2, 0x18) */ + return CP_ACCESS_TRAP_EL2; + } + /* UNDEFINED */ + return CP_ACCESS_UNDEFINED; + case 2: + if (!arm_is_secure(env)) { + /* UNDEFINED */ + return CP_ACCESS_UNDEFINED; + } + return CP_ACCESS_OK; + case 3: + if (env->cp15.scr_el3 & SCR_EEL2) { + return CP_ACCESS_OK; + } else { + return CP_ACCESS_UNDEFINED; + } + default: + g_assert_not_reached(); + } +} + uint64_t gt_get_countervalue(CPUARMState *env) { ARMCPU *cpu = env_archcpu(env); @@ -2475,6 +2514,8 @@ static uint64_t gt_indirect_access_timer_offset(CPUARMState *env, int timeridx) case GTIMER_HYP: case GTIMER_SEC: case GTIMER_HYPVIRT: + case GTIMER_S_EL2_PHYS: + case GTIMER_S_EL2_VIRT: return 0; default: g_assert_not_reached(); @@ -2521,6 +2562,8 @@ uint64_t gt_direct_access_timer_offset(CPUARMState *env, int timeridx) case GTIMER_HYP: case GTIMER_SEC: case GTIMER_HYPVIRT: + case GTIMER_S_EL2_PHYS: + case GTIMER_S_EL2_VIRT: return 0; default: g_assert_not_reached(); @@ -2953,6 +2996,62 @@ static void gt_sec_ctl_write(CPUARMState *env, const ARMCPRegInfo *ri, gt_ctl_write(env, ri, GTIMER_SEC, value); } +static void gt_sec_pel2_timer_reset(CPUARMState *env, const ARMCPRegInfo *ri) +{ + gt_timer_reset(env, ri, GTIMER_S_EL2_PHYS); +} + +static void gt_sec_pel2_cval_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + gt_cval_write(env, ri, GTIMER_S_EL2_PHYS, value); +} + +static uint64_t gt_sec_pel2_tval_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + return gt_tval_read(env, ri, GTIMER_S_EL2_PHYS); +} + +static void gt_sec_pel2_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + gt_tval_write(env, ri, GTIMER_S_EL2_PHYS, value); +} + +static void gt_sec_pel2_ctl_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + gt_ctl_write(env, ri, GTIMER_S_EL2_PHYS, value); +} + +static void gt_sec_vel2_timer_reset(CPUARMState *env, const ARMCPRegInfo *ri) +{ + gt_timer_reset(env, ri, GTIMER_S_EL2_VIRT); +} + +static void gt_sec_vel2_cval_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + gt_cval_write(env, ri, GTIMER_S_EL2_VIRT, value); +} + +static uint64_t gt_sec_vel2_tval_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + return gt_tval_read(env, ri, GTIMER_S_EL2_VIRT); +} + +static void gt_sec_vel2_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + gt_tval_write(env, ri, GTIMER_S_EL2_VIRT, value); +} + +static void gt_sec_vel2_ctl_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + gt_ctl_write(env, ri, GTIMER_S_EL2_VIRT, value); +} + static void gt_hv_timer_reset(CPUARMState *env, const ARMCPRegInfo *ri) { gt_timer_reset(env, ri, GTIMER_HYPVIRT); @@ -3009,6 +3108,20 @@ void arm_gt_stimer_cb(void *opaque) gt_recalc_timer(cpu, GTIMER_SEC); } +void arm_gt_sel2timer_cb(void *opaque) +{ + ARMCPU *cpu = opaque; + + gt_recalc_timer(cpu, GTIMER_S_EL2_PHYS); +} + +void arm_gt_sel2vtimer_cb(void *opaque) +{ + ARMCPU *cpu = opaque; + + gt_recalc_timer(cpu, GTIMER_S_EL2_VIRT); +} + void arm_gt_hvtimer_cb(void *opaque) { ARMCPU *cpu = opaque; @@ -5733,6 +5846,56 @@ static const ARMCPRegInfo el2_sec_cp_reginfo[] = { .access = PL2_RW, .accessfn = sel2_access, .nv2_redirect_offset = 0x48, .fieldoffset = offsetof(CPUARMState, cp15.vstcr_el2) }, +#ifndef CONFIG_USER_ONLY + /* Secure EL2 Physical Timer */ + { .name = "CNTHPS_TVAL_EL2", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 4, .crn = 14, .crm = 5, .opc2 = 0, + .type = ARM_CP_NO_RAW | ARM_CP_IO, .access = PL2_RW, + .accessfn = gt_sel2timer_access, + .readfn = gt_sec_pel2_tval_read, + .writefn = gt_sec_pel2_tval_write, + .resetfn = gt_sec_pel2_timer_reset, + }, + { .name = "CNTHPS_CTL_EL2", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 4, .crn = 14, .crm = 5, .opc2 = 1, + .type = ARM_CP_IO, .access = PL2_RW, + .accessfn = gt_sel2timer_access, + .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_S_EL2_PHYS].ctl), + .resetvalue = 0, + .writefn = gt_sec_pel2_ctl_write, .raw_writefn = raw_write, + }, + { .name = "CNTHPS_CVAL_EL2", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 4, .crn = 14, .crm = 5, .opc2 = 2, + .type = ARM_CP_IO, .access = PL2_RW, + .accessfn = gt_sel2timer_access, + .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_S_EL2_PHYS].cval), + .writefn = gt_sec_pel2_cval_write, .raw_writefn = raw_write, + }, + /* Secure EL2 Virtual Timer */ + { .name = "CNTHVS_TVAL_EL2", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 4, .crn = 14, .crm = 4, .opc2 = 0, + .type = ARM_CP_NO_RAW | ARM_CP_IO, .access = PL2_RW, + .accessfn = gt_sel2timer_access, + .readfn = gt_sec_vel2_tval_read, + .writefn = gt_sec_vel2_tval_write, + .resetfn = gt_sec_vel2_timer_reset, + }, + { .name = "CNTHVS_CTL_EL2", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 4, .crn = 14, .crm = 4, .opc2 = 1, + .type = ARM_CP_IO, .access = PL2_RW, + .accessfn = gt_sel2timer_access, + .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_S_EL2_VIRT].ctl), + .resetvalue = 0, + .writefn = gt_sec_vel2_ctl_write, .raw_writefn = raw_write, + }, + { .name = "CNTHVS_CVAL_EL2", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 4, .crn = 14, .crm = 4, .opc2 = 2, + .type = ARM_CP_IO, .access = PL2_RW, + .accessfn = gt_sel2timer_access, + .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_S_EL2_VIRT].cval), + .writefn = gt_sec_vel2_cval_write, .raw_writefn = raw_write, + }, +#endif }; static CPAccessResult nsacr_access(CPUARMState *env, const ARMCPRegInfo *ri, From 47e2c5510f8c13310bfe738ebaa913bb52feca2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Fri, 7 Mar 2025 10:08:21 +0000 Subject: [PATCH 0655/1179] target/arm: Document the architectural names of our GTIMERs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we are about to add more physical and virtual timers let's make it clear what each timer does. Signed-off-by: Alex Bennée Signed-off-by: Peter Maydell Reviewed-by: Peter Maydell Message-id: 20250204125009.2281315-8-peter.maydell@linaro.org [PMM: Add timer register name prefix to each comment] Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- target/arm/gtimer.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/target/arm/gtimer.h b/target/arm/gtimer.h index 0e89b8e58d02..d49c63cbf87d 100644 --- a/target/arm/gtimer.h +++ b/target/arm/gtimer.h @@ -10,11 +10,11 @@ #define TARGET_ARM_GTIMER_H enum { - GTIMER_PHYS = 0, - GTIMER_VIRT = 1, - GTIMER_HYP = 2, - GTIMER_SEC = 3, - GTIMER_HYPVIRT = 4, + GTIMER_PHYS = 0, /* CNTP_* ; EL1 physical timer */ + GTIMER_VIRT = 1, /* CNTV_* ; EL1 virtual timer */ + GTIMER_HYP = 2, /* CNTHP_* ; EL2 physical timer */ + GTIMER_SEC = 3, /* CNTPS_* ; EL3 physical timer */ + GTIMER_HYPVIRT = 4, /* CNTHV_* ; EL2 virtual timer ; only if FEAT_VHE */ GTIMER_S_EL2_PHYS = 5, /* CNTHPS_* ; only if FEAT_SEL2 */ GTIMER_S_EL2_VIRT = 6, /* CNTHVS_* ; only if FEAT_SEL2 */ #define NUM_GTIMERS 7 From 5dcaea8bcd82972add29eef350547f922fb4caa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Fri, 7 Mar 2025 10:08:21 +0000 Subject: [PATCH 0656/1179] hw/arm: enable secure EL2 timers for virt machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alex Bennée Signed-off-by: Peter Maydell Reviewed-by: Peter Maydell Message-id: 20250204125009.2281315-9-peter.maydell@linaro.org Cc: qemu-stable@nongnu.org Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/arm/virt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 904c698b1406..a96452f17a48 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -882,6 +882,8 @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, [GTIMER_HYPVIRT] = ARCH_TIMER_NS_EL2_VIRT_IRQ, + [GTIMER_S_EL2_PHYS] = ARCH_TIMER_S_EL2_IRQ, + [GTIMER_S_EL2_VIRT] = ARCH_TIMER_S_EL2_VIRT_IRQ, }; for (unsigned irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) { From 9a9d9e82093efa22e3e2bdaac0f24c823f8786f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Fri, 7 Mar 2025 10:08:21 +0000 Subject: [PATCH 0657/1179] hw/arm: enable secure EL2 timers for sbsa machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell Message-id: 20250204125009.2281315-10-peter.maydell@linaro.org Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell --- hw/arm/sbsa-ref.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c index e720de306419..aa09d7a09173 100644 --- a/hw/arm/sbsa-ref.c +++ b/hw/arm/sbsa-ref.c @@ -484,6 +484,8 @@ static void create_gic(SBSAMachineState *sms, MemoryRegion *mem) [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, [GTIMER_HYPVIRT] = ARCH_TIMER_NS_EL2_VIRT_IRQ, + [GTIMER_S_EL2_PHYS] = ARCH_TIMER_S_EL2_IRQ, + [GTIMER_S_EL2_VIRT] = ARCH_TIMER_S_EL2_VIRT_IRQ, }; for (irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) { From cde3247651dc998da5dc1005148302a90d72f21f Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:21 +0000 Subject: [PATCH 0658/1179] target/arm: Correct LDRD atomicity and fault behaviour Our LDRD implementation is wrong in two respects: * if the address is 4-aligned and the load crosses a page boundary and the second load faults and the first load was to the base register (as in cases like "ldrd r2, r3, [r2]", then we must not update the base register before taking the fault * if the address is 8-aligned the access must be a 64-bit single-copy atomic access, not two 32-bit accesses Rewrite the handling of the loads in LDRD to use a single tcg_gen_qemu_ld_i64() and split the result into the destination registers. This allows us to get the atomicity requirements right, and also implicitly means that we won't update the base register too early for the page-crossing case. Note that because we no longer increment 'addr' by 4 in the course of performing the LDRD we must change the adjustment value we pass to op_addr_ri_post() and op_addr_rr_post(): it no longer needs to subtract 4 to get the correct value to use if doing base register writeback. STRD has the same problem with not getting the atomicity right; we will deal with that in the following commit. Cc: qemu-stable@nongnu.org Reported-by: Stu Grossman Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250227142746.1698904-2-peter.maydell@linaro.org --- target/arm/tcg/translate.c | 70 +++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c index d8225b77c8cc..93772da39a43 100644 --- a/target/arm/tcg/translate.c +++ b/target/arm/tcg/translate.c @@ -5003,10 +5003,49 @@ static bool op_store_rr(DisasContext *s, arg_ldst_rr *a, return true; } -static bool trans_LDRD_rr(DisasContext *s, arg_ldst_rr *a) +static void do_ldrd_load(DisasContext *s, TCGv_i32 addr, int rt, int rt2) { + /* + * LDRD is required to be an atomic 64-bit access if the + * address is 8-aligned, two atomic 32-bit accesses if + * it's only 4-aligned, and to give an alignment fault + * if it's not 4-aligned. This is MO_ALIGN_4 | MO_ATOM_SUBALIGN. + * Rt is always the word from the lower address, and Rt2 the + * data from the higher address, regardless of endianness. + * So (like gen_load_exclusive) we avoid gen_aa32_ld_i64() + * so we don't get its SCTLR_B check, and instead do a 64-bit access + * using MO_BE if appropriate and then split the two halves. + * + * For M-profile, and for A-profile before LPAE, the 64-bit + * atomicity is not required. We could model that using + * the looser MO_ATOM_IFALIGN_PAIR, but providing a higher + * level of atomicity than required is harmless (we would not + * currently generate better code for IFALIGN_PAIR here). + * + * This also gives us the correct behaviour of not updating + * rt if the load of rt2 faults; this is required for cases + * like "ldrd r2, r3, [r2]" where rt is also the base register. + */ int mem_idx = get_mem_index(s); - TCGv_i32 addr, tmp; + MemOp opc = MO_64 | MO_ALIGN_4 | MO_ATOM_SUBALIGN | s->be_data; + TCGv taddr = gen_aa32_addr(s, addr, opc); + TCGv_i64 t64 = tcg_temp_new_i64(); + TCGv_i32 tmp = tcg_temp_new_i32(); + TCGv_i32 tmp2 = tcg_temp_new_i32(); + + tcg_gen_qemu_ld_i64(t64, taddr, mem_idx, opc); + if (s->be_data == MO_BE) { + tcg_gen_extr_i64_i32(tmp2, tmp, t64); + } else { + tcg_gen_extr_i64_i32(tmp, tmp2, t64); + } + store_reg(s, rt, tmp); + store_reg(s, rt2, tmp2); +} + +static bool trans_LDRD_rr(DisasContext *s, arg_ldst_rr *a) +{ + TCGv_i32 addr; if (!ENABLE_ARCH_5TE) { return false; @@ -5017,18 +5056,10 @@ static bool trans_LDRD_rr(DisasContext *s, arg_ldst_rr *a) } addr = op_addr_rr_pre(s, a); - tmp = tcg_temp_new_i32(); - gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); - store_reg(s, a->rt, tmp); - - tcg_gen_addi_i32(addr, addr, 4); - - tmp = tcg_temp_new_i32(); - gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); - store_reg(s, a->rt + 1, tmp); + do_ldrd_load(s, addr, a->rt, a->rt + 1); /* LDRD w/ base writeback is undefined if the registers overlap. */ - op_addr_rr_post(s, a, addr, -4); + op_addr_rr_post(s, a, addr, 0); return true; } @@ -5152,23 +5183,14 @@ static bool op_store_ri(DisasContext *s, arg_ldst_ri *a, static bool op_ldrd_ri(DisasContext *s, arg_ldst_ri *a, int rt2) { - int mem_idx = get_mem_index(s); - TCGv_i32 addr, tmp; + TCGv_i32 addr; addr = op_addr_ri_pre(s, a); - tmp = tcg_temp_new_i32(); - gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); - store_reg(s, a->rt, tmp); - - tcg_gen_addi_i32(addr, addr, 4); - - tmp = tcg_temp_new_i32(); - gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); - store_reg(s, rt2, tmp); + do_ldrd_load(s, addr, a->rt, rt2); /* LDRD w/ base writeback is undefined if the registers overlap. */ - op_addr_ri_post(s, a, addr, -4); + op_addr_ri_post(s, a, addr, 0); return true; } From ee786ca115045a2b7e86ac3073b0761cb99e0d49 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:21 +0000 Subject: [PATCH 0659/1179] target/arm: Correct STRD atomicity Our STRD implementation doesn't correctly implement the requirement: * if the address is 8-aligned the access must be a 64-bit single-copy atomic access, not two 32-bit accesses Rewrite the handling of STRD to use a single tcg_gen_qemu_st_i64() of a value produced by concatenating the two 32 bit source registers. This allows us to get the atomicity right. As with the LDRD change, now that we don't update 'addr' in the course of performing the store we need to adjust the offset we pass to op_addr_ri_post() and op_addr_rr_post(). Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250227142746.1698904-3-peter.maydell@linaro.org --- target/arm/tcg/translate.c | 59 +++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c index 93772da39a43..404a254678aa 100644 --- a/target/arm/tcg/translate.c +++ b/target/arm/tcg/translate.c @@ -5063,10 +5063,42 @@ static bool trans_LDRD_rr(DisasContext *s, arg_ldst_rr *a) return true; } -static bool trans_STRD_rr(DisasContext *s, arg_ldst_rr *a) +static void do_strd_store(DisasContext *s, TCGv_i32 addr, int rt, int rt2) { + /* + * STRD is required to be an atomic 64-bit access if the + * address is 8-aligned, two atomic 32-bit accesses if + * it's only 4-aligned, and to give an alignment fault + * if it's not 4-aligned. + * Rt is always the word from the lower address, and Rt2 the + * data from the higher address, regardless of endianness. + * So (like gen_store_exclusive) we avoid gen_aa32_ld_i64() + * so we don't get its SCTLR_B check, and instead do a 64-bit access + * using MO_BE if appropriate, using a value constructed + * by putting the two halves together in the right order. + * + * As with LDRD, the 64-bit atomicity is not required for + * M-profile, or for A-profile before LPAE, and we provide + * the higher guarantee always for simplicity. + */ int mem_idx = get_mem_index(s); - TCGv_i32 addr, tmp; + MemOp opc = MO_64 | MO_ALIGN_4 | MO_ATOM_SUBALIGN | s->be_data; + TCGv taddr = gen_aa32_addr(s, addr, opc); + TCGv_i32 t1 = load_reg(s, rt); + TCGv_i32 t2 = load_reg(s, rt2); + TCGv_i64 t64 = tcg_temp_new_i64(); + + if (s->be_data == MO_BE) { + tcg_gen_concat_i32_i64(t64, t2, t1); + } else { + tcg_gen_concat_i32_i64(t64, t1, t2); + } + tcg_gen_qemu_st_i64(t64, taddr, mem_idx, opc); +} + +static bool trans_STRD_rr(DisasContext *s, arg_ldst_rr *a) +{ + TCGv_i32 addr; if (!ENABLE_ARCH_5TE) { return false; @@ -5077,15 +5109,9 @@ static bool trans_STRD_rr(DisasContext *s, arg_ldst_rr *a) } addr = op_addr_rr_pre(s, a); - tmp = load_reg(s, a->rt); - gen_aa32_st_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); + do_strd_store(s, addr, a->rt, a->rt + 1); - tcg_gen_addi_i32(addr, addr, 4); - - tmp = load_reg(s, a->rt + 1); - gen_aa32_st_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); - - op_addr_rr_post(s, a, addr, -4); + op_addr_rr_post(s, a, addr, 0); return true; } @@ -5213,20 +5239,13 @@ static bool trans_LDRD_ri_t32(DisasContext *s, arg_ldst_ri2 *a) static bool op_strd_ri(DisasContext *s, arg_ldst_ri *a, int rt2) { - int mem_idx = get_mem_index(s); - TCGv_i32 addr, tmp; + TCGv_i32 addr; addr = op_addr_ri_pre(s, a); - tmp = load_reg(s, a->rt); - gen_aa32_st_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); - - tcg_gen_addi_i32(addr, addr, 4); - - tmp = load_reg(s, rt2); - gen_aa32_st_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN); + do_strd_store(s, addr, a->rt, rt2); - op_addr_ri_post(s, a, addr, -4); + op_addr_ri_post(s, a, addr, 0); return true; } From 5be4419c573e78c21be953a4c31947f3087931a5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 10:08:22 +0000 Subject: [PATCH 0660/1179] target/arm: Drop unused address_offset from op_addr_{rr, ri}_post() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All the callers of op_addr_rr_post() and op_addr_ri_post() now pass in zero for the address_offset, so we can remove that argument. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250227142746.1698904-4-peter.maydell@linaro.org --- target/arm/tcg/translate.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c index 404a254678aa..d2800181388e 100644 --- a/target/arm/tcg/translate.c +++ b/target/arm/tcg/translate.c @@ -4941,7 +4941,7 @@ static TCGv_i32 op_addr_rr_pre(DisasContext *s, arg_ldst_rr *a) } static void op_addr_rr_post(DisasContext *s, arg_ldst_rr *a, - TCGv_i32 addr, int address_offset) + TCGv_i32 addr) { if (!a->p) { TCGv_i32 ofs = load_reg(s, a->rm); @@ -4954,7 +4954,6 @@ static void op_addr_rr_post(DisasContext *s, arg_ldst_rr *a, } else if (!a->w) { return; } - tcg_gen_addi_i32(addr, addr, address_offset); store_reg(s, a->rn, addr); } @@ -4974,7 +4973,7 @@ static bool op_load_rr(DisasContext *s, arg_ldst_rr *a, * Perform base writeback before the loaded value to * ensure correct behavior with overlapping index registers. */ - op_addr_rr_post(s, a, addr, 0); + op_addr_rr_post(s, a, addr); store_reg_from_load(s, a->rt, tmp); return true; } @@ -4999,7 +4998,7 @@ static bool op_store_rr(DisasContext *s, arg_ldst_rr *a, gen_aa32_st_i32(s, tmp, addr, mem_idx, mop); disas_set_da_iss(s, mop, issinfo); - op_addr_rr_post(s, a, addr, 0); + op_addr_rr_post(s, a, addr); return true; } @@ -5059,7 +5058,7 @@ static bool trans_LDRD_rr(DisasContext *s, arg_ldst_rr *a) do_ldrd_load(s, addr, a->rt, a->rt + 1); /* LDRD w/ base writeback is undefined if the registers overlap. */ - op_addr_rr_post(s, a, addr, 0); + op_addr_rr_post(s, a, addr); return true; } @@ -5111,7 +5110,7 @@ static bool trans_STRD_rr(DisasContext *s, arg_ldst_rr *a) do_strd_store(s, addr, a->rt, a->rt + 1); - op_addr_rr_post(s, a, addr, 0); + op_addr_rr_post(s, a, addr); return true; } @@ -5147,13 +5146,14 @@ static TCGv_i32 op_addr_ri_pre(DisasContext *s, arg_ldst_ri *a) } static void op_addr_ri_post(DisasContext *s, arg_ldst_ri *a, - TCGv_i32 addr, int address_offset) + TCGv_i32 addr) { + int address_offset = 0; if (!a->p) { if (a->u) { - address_offset += a->imm; + address_offset = a->imm; } else { - address_offset -= a->imm; + address_offset = -a->imm; } } else if (!a->w) { return; @@ -5178,7 +5178,7 @@ static bool op_load_ri(DisasContext *s, arg_ldst_ri *a, * Perform base writeback before the loaded value to * ensure correct behavior with overlapping index registers. */ - op_addr_ri_post(s, a, addr, 0); + op_addr_ri_post(s, a, addr); store_reg_from_load(s, a->rt, tmp); return true; } @@ -5203,7 +5203,7 @@ static bool op_store_ri(DisasContext *s, arg_ldst_ri *a, gen_aa32_st_i32(s, tmp, addr, mem_idx, mop); disas_set_da_iss(s, mop, issinfo); - op_addr_ri_post(s, a, addr, 0); + op_addr_ri_post(s, a, addr); return true; } @@ -5216,7 +5216,7 @@ static bool op_ldrd_ri(DisasContext *s, arg_ldst_ri *a, int rt2) do_ldrd_load(s, addr, a->rt, rt2); /* LDRD w/ base writeback is undefined if the registers overlap. */ - op_addr_ri_post(s, a, addr, 0); + op_addr_ri_post(s, a, addr); return true; } @@ -5245,7 +5245,7 @@ static bool op_strd_ri(DisasContext *s, arg_ldst_ri *a, int rt2) do_strd_store(s, addr, a->rt, rt2); - op_addr_ri_post(s, a, addr, 0); + op_addr_ri_post(s, a, addr); return true; } From cc503abf4ba30ed34bbf18b3fd8eaa8046fae48b Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 28 Feb 2025 16:24:24 +0000 Subject: [PATCH 0661/1179] target/arm: Make dummy debug registers RAZ, not NOP In debug_helper.c we provide a few dummy versions of debug registers: * DBGVCR (AArch32 only): enable bits for vector-catch debug events * MDCCINT_EL1: interrupt enable bits for the DCC debug communications channel * DBGVCR32_EL2: the AArch64 accessor for the state in DBGVCR We implemented these only to stop Linux crashing on startup, but we chose to implement them as ARM_CP_NOP. This worked for Linux where it only cares about trying to write to these registers, but is very confusing behaviour for anything that wants to read the registers (perhaps for context state switches), because the destination register will be left with whatever random value it happened to have before the read. Model these registers instead as RAZ. Fixes: 5e8b12ffbb8c68 ("target-arm: Implement minimal DBGVCR, OSDLR_EL1, MDCCSR_EL0") Fixes: 5dbdc4342f479d ("target-arm: Implement dummy MDCCINT_EL1") Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2708 Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250228162424.1917269-1-peter.maydell@linaro.org --- target/arm/debug_helper.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c index 36bffde74e94..a9a619ba6b1b 100644 --- a/target/arm/debug_helper.c +++ b/target/arm/debug_helper.c @@ -1037,7 +1037,7 @@ static const ARMCPRegInfo debug_cp_reginfo[] = { { .name = "DBGVCR", .cp = 14, .opc1 = 0, .crn = 0, .crm = 7, .opc2 = 0, .access = PL1_RW, .accessfn = access_tda, - .type = ARM_CP_NOP }, + .type = ARM_CP_CONST, .resetvalue = 0 }, /* * Dummy MDCCINT_EL1, since we don't implement the Debug Communications * Channel but Linux may try to access this register. The 32-bit @@ -1046,7 +1046,7 @@ static const ARMCPRegInfo debug_cp_reginfo[] = { { .name = "MDCCINT_EL1", .state = ARM_CP_STATE_BOTH, .cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = 2, .opc2 = 0, .access = PL1_RW, .accessfn = access_tdcc, - .type = ARM_CP_NOP }, + .type = ARM_CP_CONST, .resetvalue = 0 }, /* * Dummy DBGCLAIM registers. * "The architecture does not define any functionality for the CLAIM tag bits.", @@ -1075,7 +1075,8 @@ static const ARMCPRegInfo debug_aa32_el1_reginfo[] = { { .name = "DBGVCR32_EL2", .state = ARM_CP_STATE_AA64, .opc0 = 2, .opc1 = 4, .crn = 0, .crm = 7, .opc2 = 0, .access = PL2_RW, .accessfn = access_dbgvcr32, - .type = ARM_CP_NOP | ARM_CP_EL3_NO_EL2_KEEP }, + .type = ARM_CP_CONST | ARM_CP_EL3_NO_EL2_KEEP, + .resetvalue = 0 }, }; static const ARMCPRegInfo debug_lpae_cp_reginfo[] = { From 02ae315467cee589d02dfb89e13a2a6a8de09fc5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 10 Feb 2025 13:58:04 +0000 Subject: [PATCH 0662/1179] util/qemu-timer.c: Don't warp timer from timerlist_rearm() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we call icount_start_warp_timer() from timerlist_rearm(). This produces incorrect behaviour, because timerlist_rearm() is called, for instance, when a timer callback modifies its timer. We cannot decide here to warp the timer forwards to the next timer deadline merely because all_cpu_threads_idle() is true, because the timer callback we were called from (or some other callback later in the list of callbacks being invoked) may be about to raise a CPU interrupt and move a CPU from idle to ready. The only valid place to choose to warp the timer forward is from the main loop, when we know we have no outstanding IO or timer callbacks that might be about to wake up a CPU. For Arm guests, this bug was mostly latent until the refactoring commit f6fc36deef6abc ("target/arm/helper: Implement CNTHCTL_EL2.CNT[VP]MASK"), which exposed it because it refactored a timer callback so that it happened to call timer_mod() first and raise the interrupt second, when it had previously raised the interrupt first and called timer_mod() afterwards. This call seems to have originally derived from the pre-record-and-replay icount code, which (as of e.g. commit db1a49726c3c in 2010) in this location did a call to qemu_notify_event(), necessary to get the icount code in the vCPU round-robin thread to stop and recalculate the icount deadline when a timer was reprogrammed from the IO thread. In current QEMU, everything is done on the vCPU thread when we are in icount mode, so there's no need to try to notify another thread here. I suspect that the other reason why this call was doing icount timer warping is that it pre-dates commit efab87cf79077a from 2015, which added a call to icount_start_warp_timer() to main_loop_wait(). Once the call in timerlist_rearm() has been removed, if the timer callbacks don't cause any CPU to be woken up then we will end up calling icount_start_warp_timer() from main_loop_wait() when the rr main loop code calls rr_wait_io_event(). Remove the incorrect call from timerlist_rearm(). Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2703 Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Reviewed-by: Alex Bennée Tested-by: Alex Bennée Message-id: 20250210135804.3526943-1-peter.maydell@linaro.org --- util/qemu-timer.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/util/qemu-timer.c b/util/qemu-timer.c index 3243d2c515c1..788466fe22f4 100644 --- a/util/qemu-timer.c +++ b/util/qemu-timer.c @@ -409,10 +409,6 @@ static bool timer_mod_ns_locked(QEMUTimerList *timer_list, static void timerlist_rearm(QEMUTimerList *timer_list) { - /* Interrupt execution to force deadline recalculation. */ - if (icount_enabled() && timer_list->clock->type == QEMU_CLOCK_VIRTUAL) { - icount_start_warp_timer(); - } timerlist_notify(timer_list); } From 84e5ce68c0c2d52d97a66ff2f53697f9ced190a8 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 28 Feb 2025 10:32:22 +0000 Subject: [PATCH 0663/1179] include/exec/memop.h: Expand comment for MO_ATOM_SUBALIGN Expand the example in the comment documenting MO_ATOM_SUBALIGN, to be clearer about the atomicity guarantees it represents. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-id: 20250228103222.1838913-1-peter.maydell@linaro.org --- include/exec/memop.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/exec/memop.h b/include/exec/memop.h index acdb40a9b3b3..407a47d82c7e 100644 --- a/include/exec/memop.h +++ b/include/exec/memop.h @@ -91,8 +91,12 @@ typedef enum MemOp { * Depending on alignment, one or both will be single-copy atomic. * This is the atomicity e.g. of Arm FEAT_LSE2 LDP. * MO_ATOM_SUBALIGN: the operation is single-copy atomic by parts - * by the alignment. E.g. if the address is 0 mod 4, then each - * 4-byte subobject is single-copy atomic. + * by the alignment. E.g. if an 8-byte value is accessed at an + * address which is 0 mod 8, then the whole 8-byte access is + * single-copy atomic; otherwise, if it is accessed at 0 mod 4 + * then each 4-byte subobject is single-copy atomic; otherwise + * if it is accessed at 0 mod 2 then the four 2-byte subobjects + * are single-copy atomic. * This is the atomicity e.g. of IBM Power. * MO_ATOM_NONE: the operation has no atomicity requirements. * From 8881b691d2d3613e9d7ff596a46a451b393377a5 Mon Sep 17 00:00:00 2001 From: JianChunfu Date: Fri, 28 Feb 2025 11:14:38 +0800 Subject: [PATCH 0664/1179] hw/arm/smmu: Introduce smmu_configs_inv_sid_range() helper Use a similar terminology smmu_hash_remove_by_sid_range() as the one being used for other hash table matching functions since smmuv3_invalidate_ste() name is not self explanatory, and introduce a helper that invokes the g_hash_table_foreach_remove. No functional change intended. Signed-off-by: JianChunfu Reviewed-by: Eric Auger Message-id: 20250228031438.3916-1-jansef.jian@hj-micro.com Signed-off-by: Peter Maydell --- hw/arm/smmu-common.c | 21 +++++++++++++++++++++ hw/arm/smmu-internal.h | 5 ----- hw/arm/smmuv3.c | 19 ++----------------- hw/arm/trace-events | 3 ++- include/hw/arm/smmu-common.h | 6 ++++++ 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/hw/arm/smmu-common.c b/hw/arm/smmu-common.c index 8c1b407b824c..6e720e1b9a0b 100644 --- a/hw/arm/smmu-common.c +++ b/hw/arm/smmu-common.c @@ -225,6 +225,27 @@ static gboolean smmu_hash_remove_by_vmid_ipa(gpointer key, gpointer value, ((entry->iova & ~info->mask) == info->iova); } +static gboolean +smmu_hash_remove_by_sid_range(gpointer key, gpointer value, gpointer user_data) +{ + SMMUDevice *sdev = (SMMUDevice *)key; + uint32_t sid = smmu_get_sid(sdev); + SMMUSIDRange *sid_range = (SMMUSIDRange *)user_data; + + if (sid < sid_range->start || sid > sid_range->end) { + return false; + } + trace_smmu_config_cache_inv(sid); + return true; +} + +void smmu_configs_inv_sid_range(SMMUState *s, SMMUSIDRange sid_range) +{ + trace_smmu_configs_inv_sid_range(sid_range.start, sid_range.end); + g_hash_table_foreach_remove(s->configs, smmu_hash_remove_by_sid_range, + &sid_range); +} + void smmu_iotlb_inv_iova(SMMUState *s, int asid, int vmid, dma_addr_t iova, uint8_t tg, uint64_t num_pages, uint8_t ttl) { diff --git a/hw/arm/smmu-internal.h b/hw/arm/smmu-internal.h index 843bebb185d7..d143d296f343 100644 --- a/hw/arm/smmu-internal.h +++ b/hw/arm/smmu-internal.h @@ -141,9 +141,4 @@ typedef struct SMMUIOTLBPageInvInfo { uint64_t mask; } SMMUIOTLBPageInvInfo; -typedef struct SMMUSIDRange { - uint32_t start; - uint32_t end; -} SMMUSIDRange; - #endif diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c index b40acbe02455..1a96287ba9d6 100644 --- a/hw/arm/smmuv3.c +++ b/hw/arm/smmuv3.c @@ -903,7 +903,7 @@ static void smmuv3_flush_config(SMMUDevice *sdev) SMMUv3State *s = sdev->smmu; SMMUState *bc = &s->smmu_state; - trace_smmuv3_config_cache_inv(smmu_get_sid(sdev)); + trace_smmu_config_cache_inv(smmu_get_sid(sdev)); g_hash_table_remove(bc->configs, sdev); } @@ -1277,20 +1277,6 @@ static void smmuv3_range_inval(SMMUState *s, Cmd *cmd, SMMUStage stage) } } -static gboolean -smmuv3_invalidate_ste(gpointer key, gpointer value, gpointer user_data) -{ - SMMUDevice *sdev = (SMMUDevice *)key; - uint32_t sid = smmu_get_sid(sdev); - SMMUSIDRange *sid_range = (SMMUSIDRange *)user_data; - - if (sid < sid_range->start || sid > sid_range->end) { - return false; - } - trace_smmuv3_config_cache_inv(sid); - return true; -} - static int smmuv3_cmdq_consume(SMMUv3State *s) { SMMUState *bs = ARM_SMMU(s); @@ -1373,8 +1359,7 @@ static int smmuv3_cmdq_consume(SMMUv3State *s) sid_range.end = sid_range.start + mask; trace_smmuv3_cmdq_cfgi_ste_range(sid_range.start, sid_range.end); - g_hash_table_foreach_remove(bs->configs, smmuv3_invalidate_ste, - &sid_range); + smmu_configs_inv_sid_range(bs, sid_range); break; } case SMMU_CMD_CFGI_CD: diff --git a/hw/arm/trace-events b/hw/arm/trace-events index f49cae1656e3..f3386bd7ae14 100644 --- a/hw/arm/trace-events +++ b/hw/arm/trace-events @@ -22,6 +22,8 @@ smmu_iotlb_inv_asid_vmid(int asid, int vmid) "IOTLB invalidate asid=%d vmid=%d" smmu_iotlb_inv_vmid(int vmid) "IOTLB invalidate vmid=%d" smmu_iotlb_inv_vmid_s1(int vmid) "IOTLB invalidate vmid=%d" smmu_iotlb_inv_iova(int asid, uint64_t addr) "IOTLB invalidate asid=%d addr=0x%"PRIx64 +smmu_configs_inv_sid_range(uint32_t start, uint32_t end) "Config cache INV SID range from 0x%x to 0x%x" +smmu_config_cache_inv(uint32_t sid) "Config cache INV for sid=0x%x" smmu_inv_notifiers_mr(const char *name) "iommu mr=%s" smmu_iotlb_lookup_hit(int asid, int vmid, uint64_t addr, uint32_t hit, uint32_t miss, uint32_t p) "IOTLB cache HIT asid=%d vmid=%d addr=0x%"PRIx64" hit=%d miss=%d hit rate=%d" smmu_iotlb_lookup_miss(int asid, int vmid, uint64_t addr, uint32_t hit, uint32_t miss, uint32_t p) "IOTLB cache MISS asid=%d vmid=%d addr=0x%"PRIx64" hit=%d miss=%d hit rate=%d" @@ -59,7 +61,6 @@ smmuv3_cmdq_tlbi_nh(int vmid) "vmid=%d" smmuv3_cmdq_tlbi_nsnh(void) "" smmuv3_cmdq_tlbi_nh_asid(int asid) "asid=%d" smmuv3_cmdq_tlbi_s12_vmid(int vmid) "vmid=%d" -smmuv3_config_cache_inv(uint32_t sid) "Config cache INV for sid=0x%x" smmuv3_notify_flag_add(const char *iommu) "ADD SMMUNotifier node for iommu mr=%s" smmuv3_notify_flag_del(const char *iommu) "DEL SMMUNotifier node for iommu mr=%s" smmuv3_inv_notifiers_iova(const char *name, int asid, int vmid, uint64_t iova, uint8_t tg, uint64_t num_pages, int stage) "iommu mr=%s asid=%d vmid=%d iova=0x%"PRIx64" tg=%d num_pages=0x%"PRIx64" stage=%d" diff --git a/include/hw/arm/smmu-common.h b/include/hw/arm/smmu-common.h index e5ad55bbae71..e5e2d09294d7 100644 --- a/include/hw/arm/smmu-common.h +++ b/include/hw/arm/smmu-common.h @@ -142,6 +142,11 @@ typedef struct SMMUIOTLBKey { uint8_t level; } SMMUIOTLBKey; +typedef struct SMMUSIDRange { + uint32_t start; + uint32_t end; +} SMMUSIDRange; + struct SMMUState { /* */ SysBusDevice dev; @@ -219,6 +224,7 @@ void smmu_iotlb_inv_iova(SMMUState *s, int asid, int vmid, dma_addr_t iova, uint8_t tg, uint64_t num_pages, uint8_t ttl); void smmu_iotlb_inv_ipa(SMMUState *s, int vmid, dma_addr_t ipa, uint8_t tg, uint64_t num_pages, uint8_t ttl); +void smmu_configs_inv_sid_range(SMMUState *s, SMMUSIDRange sid_range); /* Unmap the range of all the notifiers registered to any IOMMU mr */ void smmu_inv_notifiers_all(SMMUState *s); From 7610317f45159b620475aafc7a94c93b66eda7e2 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Tue, 18 Feb 2025 13:21:00 -0800 Subject: [PATCH 0665/1179] target/rx: Set exception vector base to 0xffffff80 The documentation says the vector is at 0xffffff80, instead of the previous value of 0xffffffc0. That value must have been a bug because the standard vector values (20, 21, 23, 25, 30) were all past the end of the array. Signed-off-by: Keith Packard Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- target/rx/helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/rx/helper.c b/target/rx/helper.c index 7f28e7298919..e8aabf40ffb4 100644 --- a/target/rx/helper.c +++ b/target/rx/helper.c @@ -88,7 +88,7 @@ void rx_cpu_do_interrupt(CPUState *cs) cpu_stl_data(env, env->isp, env->pc); if (vec < 0x100) { - env->pc = cpu_ldl_data(env, 0xffffffc0 + vec * 4); + env->pc = cpu_ldl_data(env, 0xffffff80 + vec * 4); } else { env->pc = cpu_ldl_data(env, env->intb + (vec & 0xff) * 4); } From 0ce0739d46983e5e88fa9c149cb305689c9d8c6f Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Tue, 18 Feb 2025 13:21:01 -0800 Subject: [PATCH 0666/1179] target/rx: Remove TCG_CALL_NO_WG from helpers which write env Functions which modify TCG globals must not be marked TCG_CALL_NO_WG, as that tells the optimizer that TCG global values already loaded in machine registers are still valid, and so any changes which these helpers make to the CPU state may be ignored. The target/rx code chooses to put (among other things) all the PSW bits and also ACC into globals, so the NO_WG flag on various functions that touch the PSW or ACC is incorrect and must be removed. This includes all the floating point helper functions, because update_fpsw() will update PSW Z and S. Signed-off-by: Keith Packard [PMM: Clarified commit message] Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- target/rx/helper.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/target/rx/helper.h b/target/rx/helper.h index ebb47394744e..8cc38b0cb717 100644 --- a/target/rx/helper.h +++ b/target/rx/helper.h @@ -4,27 +4,27 @@ DEF_HELPER_1(raise_privilege_violation, noreturn, env) DEF_HELPER_1(wait, noreturn, env) DEF_HELPER_2(rxint, noreturn, env, i32) DEF_HELPER_1(rxbrk, noreturn, env) -DEF_HELPER_FLAGS_3(fadd, TCG_CALL_NO_WG, f32, env, f32, f32) -DEF_HELPER_FLAGS_3(fsub, TCG_CALL_NO_WG, f32, env, f32, f32) -DEF_HELPER_FLAGS_3(fmul, TCG_CALL_NO_WG, f32, env, f32, f32) -DEF_HELPER_FLAGS_3(fdiv, TCG_CALL_NO_WG, f32, env, f32, f32) -DEF_HELPER_FLAGS_3(fcmp, TCG_CALL_NO_WG, void, env, f32, f32) -DEF_HELPER_FLAGS_2(ftoi, TCG_CALL_NO_WG, i32, env, f32) -DEF_HELPER_FLAGS_2(round, TCG_CALL_NO_WG, i32, env, f32) -DEF_HELPER_FLAGS_2(itof, TCG_CALL_NO_WG, f32, env, i32) +DEF_HELPER_3(fadd, f32, env, f32, f32) +DEF_HELPER_3(fsub, f32, env, f32, f32) +DEF_HELPER_3(fmul, f32, env, f32, f32) +DEF_HELPER_3(fdiv, f32, env, f32, f32) +DEF_HELPER_3(fcmp, void, env, f32, f32) +DEF_HELPER_2(ftoi, i32, env, f32) +DEF_HELPER_2(round, i32, env, f32) +DEF_HELPER_2(itof, f32, env, i32) DEF_HELPER_2(set_fpsw, void, env, i32) -DEF_HELPER_FLAGS_2(racw, TCG_CALL_NO_WG, void, env, i32) -DEF_HELPER_FLAGS_2(set_psw_rte, TCG_CALL_NO_WG, void, env, i32) -DEF_HELPER_FLAGS_2(set_psw, TCG_CALL_NO_WG, void, env, i32) +DEF_HELPER_2(racw, void, env, i32) +DEF_HELPER_2(set_psw_rte, void, env, i32) +DEF_HELPER_2(set_psw, void, env, i32) DEF_HELPER_1(pack_psw, i32, env) -DEF_HELPER_FLAGS_3(div, TCG_CALL_NO_WG, i32, env, i32, i32) -DEF_HELPER_FLAGS_3(divu, TCG_CALL_NO_WG, i32, env, i32, i32) -DEF_HELPER_FLAGS_1(scmpu, TCG_CALL_NO_WG, void, env) +DEF_HELPER_3(div, i32, env, i32, i32) +DEF_HELPER_3(divu, i32, env, i32, i32) +DEF_HELPER_1(scmpu, void, env) DEF_HELPER_1(smovu, void, env) DEF_HELPER_1(smovf, void, env) DEF_HELPER_1(smovb, void, env) DEF_HELPER_2(sstr, void, env, i32) -DEF_HELPER_FLAGS_2(swhile, TCG_CALL_NO_WG, void, env, i32) -DEF_HELPER_FLAGS_2(suntil, TCG_CALL_NO_WG, void, env, i32) -DEF_HELPER_FLAGS_2(rmpa, TCG_CALL_NO_WG, void, env, i32) +DEF_HELPER_2(swhile, void, env, i32) +DEF_HELPER_2(suntil, void, env, i32) +DEF_HELPER_2(rmpa, void, env, i32) DEF_HELPER_1(satr, void, env) From 563b1a35ed1f1151505d4fe5f723827d1b3fd4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Thu, 6 Mar 2025 16:16:31 +0000 Subject: [PATCH 0667/1179] meson.build: default to -gsplit-dwarf for debug info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This option is supported by both gcc (since 4.7) and clang (since 7.0). Not only does this make the linkers job easier by reducing the amount of ELF it needs to parse it also reduces the total build size quite considerably. In my case a default build went from 5.8G to 3.9G (vs 1.9G for --disable-debug-info). The --disable-split-debug option allows distros to keep all the info together for ease of packaging. Signed-off-by: Alex Bennée Reviewed-by: Daniel P. Berrangé Link: https://lore.kernel.org/r/20250306161631.2477685-1-alex.bennee@linaro.org Signed-off-by: Paolo Bonzini --- meson.build | 6 ++++++ meson_options.txt | 2 ++ scripts/meson-buildoptions.sh | 2 ++ 3 files changed, 10 insertions(+) diff --git a/meson.build b/meson.build index 6da4eb317c28..4899d896de0b 100644 --- a/meson.build +++ b/meson.build @@ -601,6 +601,10 @@ if get_option('tsan') qemu_ldflags = ['-fsanitize=thread'] + qemu_ldflags endif +if get_option('debug') and get_option('split_debug') + qemu_cflags += '-gsplit-dwarf' +endif + # Detect support for PT_GNU_RELRO + DT_BIND_NOW. # The combination is known as "full relro", because .got.plt is read-only too. qemu_ldflags += cc.get_supported_link_arguments('-Wl,-z,relro', '-Wl,-z,now') @@ -4583,6 +4587,8 @@ if have_rust summary_info += {'bindgen': bindgen.full_path()} summary_info += {'bindgen version': bindgen.version()} endif +# option_cflags is purely for the summary display, meson will pass +# -g/-O options directly option_cflags = (get_option('debug') ? ['-g'] : []) if get_option('optimization') != 'plain' option_cflags += ['-O' + get_option('optimization')] diff --git a/meson_options.txt b/meson_options.txt index 59d973bca00f..3432123fee2e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -362,6 +362,8 @@ option('debug_mutex', type: 'boolean', value: false, description: 'mutex debugging support') option('debug_stack_usage', type: 'boolean', value: false, description: 'measure coroutine stack usage') +option('split_debug', type: 'boolean', value: true, + description: 'split debug info from object files') option('qom_cast_debug', type: 'boolean', value: true, description: 'cast debugging support') option('slirp_smbd', type : 'feature', value : 'auto', diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index 3e8e00852b2c..aca6e6883024 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -504,6 +504,8 @@ _meson_option_parse() { --disable-strict-rust-lints) printf "%s" -Dstrict_rust_lints=false ;; --enable-strip) printf "%s" -Dstrip=true ;; --disable-strip) printf "%s" -Dstrip=false ;; + --enable-split-debug) printf "%s" -Dsplit_debug=true ;; + --disable-split-debug) printf "%s" -Dsplit_debug=false ;; --sysconfdir=*) quote_sh "-Dsysconfdir=$2" ;; --enable-tcg) printf "%s" -Dtcg=enabled ;; --disable-tcg) printf "%s" -Dtcg=disabled ;; From 44ed2fd1ea0e5c62dca2f26bee871652a93c6837 Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 30 Jul 2024 23:54:57 +0200 Subject: [PATCH 0668/1179] linux-user/main: Allow setting tb-size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While qemu-system can set tb-size using -accel tcg,tb-size=n, there is no similar knob for qemu-user. Add one in a way similar to how one-insn-per-tb is already handled. Signed-off-by: Ilya Leoshkevich Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-ID: <20240730215532.1442-1-iii@linux.ibm.com> --- linux-user/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/linux-user/main.c b/linux-user/main.c index 5c74c52cc52a..e2ec5970bed2 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -72,6 +72,7 @@ char *exec_path; char real_exec_path[PATH_MAX]; static bool opt_one_insn_per_tb; +static unsigned long opt_tb_size; static const char *argv0; static const char *gdbstub; static envlist_t *envlist; @@ -425,6 +426,13 @@ static void handle_arg_one_insn_per_tb(const char *arg) opt_one_insn_per_tb = true; } +static void handle_arg_tb_size(const char *arg) +{ + if (qemu_strtoul(arg, NULL, 0, &opt_tb_size)) { + usage(EXIT_FAILURE); + } +} + static void handle_arg_strace(const char *arg) { enable_strace = true; @@ -517,6 +525,8 @@ static const struct qemu_argument arg_table[] = { {"one-insn-per-tb", "QEMU_ONE_INSN_PER_TB", false, handle_arg_one_insn_per_tb, "", "run with one guest instruction per emulated TB"}, + {"tb-size", "QEMU_TB_SIZE", true, handle_arg_tb_size, + "size", "TCG translation block cache size"}, {"strace", "QEMU_STRACE", false, handle_arg_strace, "", "log system calls"}, {"seed", "QEMU_RAND_SEED", true, handle_arg_seed, @@ -808,6 +818,8 @@ int main(int argc, char **argv, char **envp) accel_init_interfaces(ac); object_property_set_bool(OBJECT(accel), "one-insn-per-tb", opt_one_insn_per_tb, &error_abort); + object_property_set_int(OBJECT(accel), "tb-size", + opt_tb_size, &error_abort); ac->init_machine(NULL); } From 3504f104ea97ffaa89f509db8059ec1047bd62ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 5 Mar 2025 20:18:59 +0100 Subject: [PATCH 0669/1179] accel/tcg: Restrict CPU_TLB_DYN_*_BITS definitions to accel/tcg/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CPU_TLB_DYN_*_BITS definitions are only used by accel/tcg/cputlb.c and accel/tcg/translate-all.c. Move them to accel/tcg/tb-internal.h. Signed-off-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson Reviewed-by: Richard Henderson Message-ID: <20250305191859.71608-1-philmd@linaro.org> --- accel/tcg/tb-internal.h | 27 +++++++++++++++++++++++++++ include/exec/cpu-defs.h | 26 -------------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/accel/tcg/tb-internal.h b/accel/tcg/tb-internal.h index 90be61f296a1..abd423fcf588 100644 --- a/accel/tcg/tb-internal.h +++ b/accel/tcg/tb-internal.h @@ -13,6 +13,33 @@ #include "exec/exec-all.h" #include "exec/translation-block.h" +#ifdef CONFIG_SOFTMMU + +#define CPU_TLB_DYN_MIN_BITS 6 +#define CPU_TLB_DYN_DEFAULT_BITS 8 + +# if HOST_LONG_BITS == 32 +/* Make sure we do not require a double-word shift for the TLB load */ +# define CPU_TLB_DYN_MAX_BITS (32 - TARGET_PAGE_BITS) +# else /* HOST_LONG_BITS == 64 */ +/* + * Assuming TARGET_PAGE_BITS==12, with 2**22 entries we can cover 2**(22+12) == + * 2**34 == 16G of address space. This is roughly what one would expect a + * TLB to cover in a modern (as of 2018) x86_64 CPU. For instance, Intel + * Skylake's Level-2 STLB has 16 1G entries. + * Also, make sure we do not size the TLB past the guest's address space. + */ +# ifdef TARGET_PAGE_BITS_VARY +# define CPU_TLB_DYN_MAX_BITS \ + MIN(22, TARGET_VIRT_ADDR_SPACE_BITS - TARGET_PAGE_BITS) +# else +# define CPU_TLB_DYN_MAX_BITS \ + MIN_CONST(22, TARGET_VIRT_ADDR_SPACE_BITS - TARGET_PAGE_BITS) +# endif +# endif + +#endif /* CONFIG_SOFTMMU */ + #ifdef CONFIG_USER_ONLY #include "user/page-protection.h" /* diff --git a/include/exec/cpu-defs.h b/include/exec/cpu-defs.h index ae18398fa996..9f955f53fde2 100644 --- a/include/exec/cpu-defs.h +++ b/include/exec/cpu-defs.h @@ -46,30 +46,4 @@ #include "exec/target_long.h" -#if defined(CONFIG_SOFTMMU) && defined(CONFIG_TCG) -#define CPU_TLB_DYN_MIN_BITS 6 -#define CPU_TLB_DYN_DEFAULT_BITS 8 - -# if HOST_LONG_BITS == 32 -/* Make sure we do not require a double-word shift for the TLB load */ -# define CPU_TLB_DYN_MAX_BITS (32 - TARGET_PAGE_BITS) -# else /* HOST_LONG_BITS == 64 */ -/* - * Assuming TARGET_PAGE_BITS==12, with 2**22 entries we can cover 2**(22+12) == - * 2**34 == 16G of address space. This is roughly what one would expect a - * TLB to cover in a modern (as of 2018) x86_64 CPU. For instance, Intel - * Skylake's Level-2 STLB has 16 1G entries. - * Also, make sure we do not size the TLB past the guest's address space. - */ -# ifdef TARGET_PAGE_BITS_VARY -# define CPU_TLB_DYN_MAX_BITS \ - MIN(22, TARGET_VIRT_ADDR_SPACE_BITS - TARGET_PAGE_BITS) -# else -# define CPU_TLB_DYN_MAX_BITS \ - MIN_CONST(22, TARGET_VIRT_ADDR_SPACE_BITS - TARGET_PAGE_BITS) -# endif -# endif - -#endif /* CONFIG_SOFTMMU && CONFIG_TCG */ - #endif From 58d00538ceeef9900802bcd4b7ad613ca78c8583 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 6 Mar 2025 13:32:36 -0800 Subject: [PATCH 0670/1179] include/exec: Move TARGET_PAGE_{SIZE,MASK,BITS} to target_page.h Re-use the TARGET_PAGE_BITS_VARY mechanism to define TARGET_PAGE_SIZE and friends when not compiling per-target. Inline qemu_target_page_{size,mask,bits} as they are now trivial. Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- include/exec/cpu-all.h | 21 +-------------- include/exec/poison.h | 4 --- include/exec/target_page.h | 54 +++++++++++++++++++++++++++++++++++--- page-target.c | 18 ------------- page-vary-target.c | 2 -- 5 files changed, 51 insertions(+), 48 deletions(-) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index 09f537d06faf..8f7aebb0881e 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -105,26 +105,7 @@ static inline void stl_phys_notdirty(AddressSpace *as, hwaddr addr, uint32_t val /* page related stuff */ #include "exec/cpu-defs.h" -#ifdef TARGET_PAGE_BITS_VARY -# include "exec/page-vary.h" -extern const TargetPageBits target_page; -# ifdef CONFIG_DEBUG_TCG -# define TARGET_PAGE_BITS ({ assert(target_page.decided); \ - target_page.bits; }) -# define TARGET_PAGE_MASK ({ assert(target_page.decided); \ - (target_long)target_page.mask; }) -# else -# define TARGET_PAGE_BITS target_page.bits -# define TARGET_PAGE_MASK ((target_long)target_page.mask) -# endif -# define TARGET_PAGE_SIZE (-(int)TARGET_PAGE_MASK) -#else -# define TARGET_PAGE_BITS_MIN TARGET_PAGE_BITS -# define TARGET_PAGE_SIZE (1 << TARGET_PAGE_BITS) -# define TARGET_PAGE_MASK ((target_long)-1 << TARGET_PAGE_BITS) -#endif - -#define TARGET_PAGE_ALIGN(addr) ROUND_UP((addr), TARGET_PAGE_SIZE) +#include "exec/target_page.h" CPUArchState *cpu_copy(CPUArchState *env); diff --git a/include/exec/poison.h b/include/exec/poison.h index d6d4832854e0..35721366d7ba 100644 --- a/include/exec/poison.h +++ b/include/exec/poison.h @@ -44,10 +44,6 @@ #pragma GCC poison TARGET_FMT_ld #pragma GCC poison TARGET_FMT_lu -#pragma GCC poison TARGET_PAGE_SIZE -#pragma GCC poison TARGET_PAGE_MASK -#pragma GCC poison TARGET_PAGE_BITS -#pragma GCC poison TARGET_PAGE_ALIGN #pragma GCC poison TARGET_PHYS_ADDR_SPACE_BITS #pragma GCC poison CPU_INTERRUPT_HARD diff --git a/include/exec/target_page.h b/include/exec/target_page.h index 98ffbb5c2396..8e89e5cbe6f9 100644 --- a/include/exec/target_page.h +++ b/include/exec/target_page.h @@ -14,10 +14,56 @@ #ifndef EXEC_TARGET_PAGE_H #define EXEC_TARGET_PAGE_H -size_t qemu_target_page_size(void); -int qemu_target_page_mask(void); -int qemu_target_page_bits(void); -int qemu_target_page_bits_min(void); +/* + * If compiling per-target, get the real values. + * For generic code, reuse the mechanism for variable page size. + */ +#ifdef COMPILING_PER_TARGET +#include "cpu-param.h" +#include "exec/target_long.h" +#define TARGET_PAGE_TYPE target_long +#else +#define TARGET_PAGE_BITS_VARY +#define TARGET_PAGE_TYPE int +#endif + +#ifdef TARGET_PAGE_BITS_VARY +# include "exec/page-vary.h" +extern const TargetPageBits target_page; +# ifdef CONFIG_DEBUG_TCG +# define TARGET_PAGE_BITS ({ assert(target_page.decided); \ + target_page.bits; }) +# define TARGET_PAGE_MASK ({ assert(target_page.decided); \ + (TARGET_PAGE_TYPE)target_page.mask; }) +# else +# define TARGET_PAGE_BITS target_page.bits +# define TARGET_PAGE_MASK ((TARGET_PAGE_TYPE)target_page.mask) +# endif +# define TARGET_PAGE_SIZE (-(int)TARGET_PAGE_MASK) +#else +# define TARGET_PAGE_BITS_MIN TARGET_PAGE_BITS +# define TARGET_PAGE_SIZE (1 << TARGET_PAGE_BITS) +# define TARGET_PAGE_MASK ((TARGET_PAGE_TYPE)-1 << TARGET_PAGE_BITS) +#endif +#define TARGET_PAGE_ALIGN(addr) ROUND_UP((addr), TARGET_PAGE_SIZE) + +static inline size_t qemu_target_page_size(void) +{ + return TARGET_PAGE_SIZE; +} + +static inline int qemu_target_page_mask(void) +{ + return TARGET_PAGE_MASK; +} + +static inline int qemu_target_page_bits(void) +{ + return TARGET_PAGE_BITS; +} + +int qemu_target_page_bits_min(void); size_t qemu_target_pages_to_MiB(size_t pages); + #endif diff --git a/page-target.c b/page-target.c index 82211c85937a..321e43d06f48 100644 --- a/page-target.c +++ b/page-target.c @@ -8,24 +8,6 @@ #include "qemu/osdep.h" #include "exec/target_page.h" -#include "exec/cpu-defs.h" -#include "cpu.h" -#include "exec/cpu-all.h" - -size_t qemu_target_page_size(void) -{ - return TARGET_PAGE_SIZE; -} - -int qemu_target_page_mask(void) -{ - return TARGET_PAGE_MASK; -} - -int qemu_target_page_bits(void) -{ - return TARGET_PAGE_BITS; -} int qemu_target_page_bits_min(void) { diff --git a/page-vary-target.c b/page-vary-target.c index 343b4adb95a5..3f81144cda8a 100644 --- a/page-vary-target.c +++ b/page-vary-target.c @@ -35,7 +35,5 @@ bool set_preferred_target_page_bits(int bits) void finalize_target_page_bits(void) { -#ifdef TARGET_PAGE_BITS_VARY finalize_target_page_bits_common(TARGET_PAGE_BITS_MIN); -#endif } From b9e3bf884aaa2d243fa3554c4dafea293187aa02 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 6 Mar 2025 18:09:00 -0800 Subject: [PATCH 0671/1179] include/exec: Split out exec/cpu-interrupt.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of these bits are actually common to all cpus; while the reset have common reservations for target-specific usage. While generic code cannot know what the target-specific usage is, common code can know what to do with the bits, e.g. single-step. Tested-by: Philippe Mathieu-Daudé Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- include/exec/cpu-all.h | 53 +-------------------------- include/exec/cpu-interrupt.h | 70 ++++++++++++++++++++++++++++++++++++ include/exec/poison.h | 13 ------- 3 files changed, 71 insertions(+), 65 deletions(-) create mode 100644 include/exec/cpu-interrupt.h diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index 8f7aebb0881e..9e6724097ce6 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -21,6 +21,7 @@ #include "exec/page-protection.h" #include "exec/cpu-common.h" +#include "exec/cpu-interrupt.h" #include "exec/memory.h" #include "exec/tswap.h" #include "hw/core/cpu.h" @@ -109,58 +110,6 @@ static inline void stl_phys_notdirty(AddressSpace *as, hwaddr addr, uint32_t val CPUArchState *cpu_copy(CPUArchState *env); -/* Flags for use in ENV->INTERRUPT_PENDING. - - The numbers assigned here are non-sequential in order to preserve - binary compatibility with the vmstate dump. Bit 0 (0x0001) was - previously used for CPU_INTERRUPT_EXIT, and is cleared when loading - the vmstate dump. */ - -/* External hardware interrupt pending. This is typically used for - interrupts from devices. */ -#define CPU_INTERRUPT_HARD 0x0002 - -/* Exit the current TB. This is typically used when some system-level device - makes some change to the memory mapping. E.g. the a20 line change. */ -#define CPU_INTERRUPT_EXITTB 0x0004 - -/* Halt the CPU. */ -#define CPU_INTERRUPT_HALT 0x0020 - -/* Debug event pending. */ -#define CPU_INTERRUPT_DEBUG 0x0080 - -/* Reset signal. */ -#define CPU_INTERRUPT_RESET 0x0400 - -/* Several target-specific external hardware interrupts. Each target/cpu.h - should define proper names based on these defines. */ -#define CPU_INTERRUPT_TGT_EXT_0 0x0008 -#define CPU_INTERRUPT_TGT_EXT_1 0x0010 -#define CPU_INTERRUPT_TGT_EXT_2 0x0040 -#define CPU_INTERRUPT_TGT_EXT_3 0x0200 -#define CPU_INTERRUPT_TGT_EXT_4 0x1000 - -/* Several target-specific internal interrupts. These differ from the - preceding target-specific interrupts in that they are intended to - originate from within the cpu itself, typically in response to some - instruction being executed. These, therefore, are not masked while - single-stepping within the debugger. */ -#define CPU_INTERRUPT_TGT_INT_0 0x0100 -#define CPU_INTERRUPT_TGT_INT_1 0x0800 -#define CPU_INTERRUPT_TGT_INT_2 0x2000 - -/* First unused bit: 0x4000. */ - -/* The set of all bits that should be masked when single-stepping. */ -#define CPU_INTERRUPT_SSTEP_MASK \ - (CPU_INTERRUPT_HARD \ - | CPU_INTERRUPT_TGT_EXT_0 \ - | CPU_INTERRUPT_TGT_EXT_1 \ - | CPU_INTERRUPT_TGT_EXT_2 \ - | CPU_INTERRUPT_TGT_EXT_3 \ - | CPU_INTERRUPT_TGT_EXT_4) - #include "cpu.h" #ifdef CONFIG_USER_ONLY diff --git a/include/exec/cpu-interrupt.h b/include/exec/cpu-interrupt.h new file mode 100644 index 000000000000..40715193ca30 --- /dev/null +++ b/include/exec/cpu-interrupt.h @@ -0,0 +1,70 @@ +/* + * Flags for use with cpu_interrupt() + * + * Copyright (c) 2003 Fabrice Bellard + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef CPU_INTERRUPT_H +#define CPU_INTERRUPT_H + +/* + * The numbers assigned here are non-sequential in order to preserve binary + * compatibility with the vmstate dump. Bit 0 (0x0001) was previously used + * for CPU_INTERRUPT_EXIT, and is cleared when loading the vmstate dump. + */ + +/* + * External hardware interrupt pending. + * This is typically used for interrupts from devices. + */ +#define CPU_INTERRUPT_HARD 0x0002 + +/* + * Exit the current TB. This is typically used when some system-level device + * makes some change to the memory mapping. E.g. the a20 line change. + */ +#define CPU_INTERRUPT_EXITTB 0x0004 + +/* Halt the CPU. */ +#define CPU_INTERRUPT_HALT 0x0020 + +/* Debug event pending. */ +#define CPU_INTERRUPT_DEBUG 0x0080 + +/* Reset signal. */ +#define CPU_INTERRUPT_RESET 0x0400 + +/* + * Several target-specific external hardware interrupts. Each target/cpu.h + * should define proper names based on these defines. + */ +#define CPU_INTERRUPT_TGT_EXT_0 0x0008 +#define CPU_INTERRUPT_TGT_EXT_1 0x0010 +#define CPU_INTERRUPT_TGT_EXT_2 0x0040 +#define CPU_INTERRUPT_TGT_EXT_3 0x0200 +#define CPU_INTERRUPT_TGT_EXT_4 0x1000 + +/* + * Several target-specific internal interrupts. These differ from the + * preceding target-specific interrupts in that they are intended to + * originate from within the cpu itself, typically in response to some + * instruction being executed. These, therefore, are not masked while + * single-stepping within the debugger. + */ +#define CPU_INTERRUPT_TGT_INT_0 0x0100 +#define CPU_INTERRUPT_TGT_INT_1 0x0800 +#define CPU_INTERRUPT_TGT_INT_2 0x2000 + +/* First unused bit: 0x4000. */ + +/* The set of all bits that should be masked when single-stepping. */ +#define CPU_INTERRUPT_SSTEP_MASK \ + (CPU_INTERRUPT_HARD \ + | CPU_INTERRUPT_TGT_EXT_0 \ + | CPU_INTERRUPT_TGT_EXT_1 \ + | CPU_INTERRUPT_TGT_EXT_2 \ + | CPU_INTERRUPT_TGT_EXT_3 \ + | CPU_INTERRUPT_TGT_EXT_4) + +#endif /* CPU_INTERRUPT_H */ diff --git a/include/exec/poison.h b/include/exec/poison.h index 35721366d7ba..8ed04b31083e 100644 --- a/include/exec/poison.h +++ b/include/exec/poison.h @@ -46,19 +46,6 @@ #pragma GCC poison TARGET_PHYS_ADDR_SPACE_BITS -#pragma GCC poison CPU_INTERRUPT_HARD -#pragma GCC poison CPU_INTERRUPT_EXITTB -#pragma GCC poison CPU_INTERRUPT_HALT -#pragma GCC poison CPU_INTERRUPT_DEBUG -#pragma GCC poison CPU_INTERRUPT_TGT_EXT_0 -#pragma GCC poison CPU_INTERRUPT_TGT_EXT_1 -#pragma GCC poison CPU_INTERRUPT_TGT_EXT_2 -#pragma GCC poison CPU_INTERRUPT_TGT_EXT_3 -#pragma GCC poison CPU_INTERRUPT_TGT_EXT_4 -#pragma GCC poison CPU_INTERRUPT_TGT_INT_0 -#pragma GCC poison CPU_INTERRUPT_TGT_INT_1 -#pragma GCC poison CPU_INTERRUPT_TGT_INT_2 - #pragma GCC poison CONFIG_ALPHA_DIS #pragma GCC poison CONFIG_HPPA_DIS #pragma GCC poison CONFIG_I386_DIS From 5469933810b00589d5db36408dd4e0236eec95ac Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 6 Mar 2025 18:58:53 -0800 Subject: [PATCH 0672/1179] accel/tcg: Compile watchpoint.c once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move tb_check_watchpoint declaration from tb-internal.h, which is still target-specific, to internal-common.h, which isn't. Otherwise, all that is required to build watchpoint.c once is to include the new exec/cpu-interrupt.h instead of exec/exec-all.h. Tested-by: Philippe Mathieu-Daudé Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- accel/tcg/internal-common.h | 2 ++ accel/tcg/meson.build | 2 +- accel/tcg/tb-internal.h | 2 -- accel/tcg/watchpoint.c | 5 ++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/accel/tcg/internal-common.h b/accel/tcg/internal-common.h index 7ef620d96318..9b6ab3a8cc0b 100644 --- a/accel/tcg/internal-common.h +++ b/accel/tcg/internal-common.h @@ -72,4 +72,6 @@ void tcg_exec_unrealizefn(CPUState *cpu); /* current cflags for hashing/comparison */ uint32_t curr_cflags(CPUState *cpu); +void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr); + #endif diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 69f4808ac491..979ce90eb0a0 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -20,7 +20,6 @@ specific_ss.add_all(when: 'CONFIG_TCG', if_true: tcg_specific_ss) specific_ss.add(when: ['CONFIG_SYSTEM_ONLY', 'CONFIG_TCG'], if_true: files( 'cputlb.c', - 'watchpoint.c', 'tcg-accel-ops.c', 'tcg-accel-ops-mttcg.c', 'tcg-accel-ops-icount.c', @@ -30,4 +29,5 @@ specific_ss.add(when: ['CONFIG_SYSTEM_ONLY', 'CONFIG_TCG'], if_true: files( system_ss.add(when: ['CONFIG_TCG'], if_true: files( 'icount-common.c', 'monitor.c', + 'watchpoint.c', )) diff --git a/accel/tcg/tb-internal.h b/accel/tcg/tb-internal.h index abd423fcf588..62a59a5307ec 100644 --- a/accel/tcg/tb-internal.h +++ b/accel/tcg/tb-internal.h @@ -75,6 +75,4 @@ void tb_invalidate_phys_range_fast(ram_addr_t ram_addr, bool tb_invalidate_phys_page_unwind(tb_page_addr_t addr, uintptr_t pc); -void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr); - #endif diff --git a/accel/tcg/watchpoint.c b/accel/tcg/watchpoint.c index 40112b2b2e70..ba8c9859cf40 100644 --- a/accel/tcg/watchpoint.c +++ b/accel/tcg/watchpoint.c @@ -19,11 +19,10 @@ #include "qemu/osdep.h" #include "qemu/main-loop.h" -#include "qemu/error-report.h" -#include "exec/exec-all.h" +#include "exec/breakpoint.h" +#include "exec/cpu-interrupt.h" #include "exec/page-protection.h" #include "exec/translation-block.h" -#include "tb-internal.h" #include "system/tcg.h" #include "system/replay.h" #include "accel/tcg/cpu-ops.h" From e7d269adb260417149e4bb85cc1882fbb11074fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 14 Nov 2024 02:12:58 +0100 Subject: [PATCH 0673/1179] exec: Declare tlb_reset_dirty*() in 'exec/cputlb.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU TLB related methods to "exec/cputlb.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Message-ID: <20241114011310.3615-14-philmd@linaro.org> Signed-off-by: Richard Henderson --- include/exec/cputlb.h | 7 +++++++ include/exec/exec-all.h | 3 --- include/exec/ram_addr.h | 1 + system/physmem.c | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/exec/cputlb.h b/include/exec/cputlb.h index ef18642a3294..6cac7d530fce 100644 --- a/include/exec/cputlb.h +++ b/include/exec/cputlb.h @@ -32,4 +32,11 @@ void tlb_unprotect_code(ram_addr_t ram_addr); #endif /* CONFIG_TCG */ +#ifndef CONFIG_USER_ONLY + +void tlb_reset_dirty(CPUState *cpu, ram_addr_t start1, ram_addr_t length); +void tlb_reset_dirty_range_all(ram_addr_t start, ram_addr_t length); + +#endif + #endif diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index 8eb0df48f945..f24256fb5e7f 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -486,9 +486,6 @@ static inline tb_page_addr_t get_page_addr_code(CPUArchState *env, #if !defined(CONFIG_USER_ONLY) -void tlb_reset_dirty(CPUState *cpu, ram_addr_t start1, ram_addr_t length); -void tlb_reset_dirty_range_all(ram_addr_t start, ram_addr_t length); - MemoryRegionSection * address_space_translate_for_iotlb(CPUState *cpu, int asidx, hwaddr addr, hwaddr *xlat, hwaddr *plen, diff --git a/include/exec/ram_addr.h b/include/exec/ram_addr.h index 94bb3ccbe42b..3d8df4edf15c 100644 --- a/include/exec/ram_addr.h +++ b/include/exec/ram_addr.h @@ -23,6 +23,7 @@ #include "cpu.h" #include "system/xen.h" #include "system/tcg.h" +#include "exec/cputlb.h" #include "exec/ramlist.h" #include "exec/ramblock.h" #include "exec/exec-all.h" diff --git a/system/physmem.c b/system/physmem.c index 8c1736f84ee7..a6af555f4b7a 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -32,6 +32,7 @@ #endif /* CONFIG_TCG */ #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "exec/target_page.h" #include "exec/translation-block.h" From a9f5ab9279bba8832712197dcf6053941d3d5d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 14 Nov 2024 02:13:00 +0100 Subject: [PATCH 0674/1179] exec: Declare tlb_set_page_full() in 'exec/cputlb.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU TLB related methods to "exec/cputlb.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson Message-ID: <20241114011310.3615-16-philmd@linaro.org> --- include/exec/cputlb.h | 23 +++++++++++++++++++++++ include/exec/exec-all.h | 22 ---------------------- target/sparc/mmu_helper.c | 2 +- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/include/exec/cputlb.h b/include/exec/cputlb.h index 6cac7d530fce..733ef012d1ab 100644 --- a/include/exec/cputlb.h +++ b/include/exec/cputlb.h @@ -21,6 +21,7 @@ #define CPUTLB_H #include "exec/cpu-common.h" +#include "exec/vaddr.h" #ifdef CONFIG_TCG @@ -39,4 +40,26 @@ void tlb_reset_dirty_range_all(ram_addr_t start, ram_addr_t length); #endif +/** + * tlb_set_page_full: + * @cpu: CPU context + * @mmu_idx: mmu index of the tlb to modify + * @addr: virtual address of the entry to add + * @full: the details of the tlb entry + * + * Add an entry to @cpu tlb index @mmu_idx. All of the fields of + * @full must be filled, except for xlat_section, and constitute + * the complete description of the translated page. + * + * This is generally called by the target tlb_fill function after + * having performed a successful page table walk to find the physical + * address and attributes for the translation. + * + * At most one entry for a given virtual address is permitted. Only a + * single TARGET_PAGE_SIZE region is mapped; @full->lg_page_size is only + * used by tlb_flush_page. + */ +void tlb_set_page_full(CPUState *cpu, int mmu_idx, vaddr addr, + CPUTLBEntryFull *full); + #endif diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index f24256fb5e7f..f43c67366ba7 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -156,28 +156,6 @@ void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *cpu, uint16_t idxmap, unsigned bits); -/** - * tlb_set_page_full: - * @cpu: CPU context - * @mmu_idx: mmu index of the tlb to modify - * @addr: virtual address of the entry to add - * @full: the details of the tlb entry - * - * Add an entry to @cpu tlb index @mmu_idx. All of the fields of - * @full must be filled, except for xlat_section, and constitute - * the complete description of the translated page. - * - * This is generally called by the target tlb_fill function after - * having performed a successful page table walk to find the physical - * address and attributes for the translation. - * - * At most one entry for a given virtual address is permitted. Only a - * single TARGET_PAGE_SIZE region is mapped; @full->lg_page_size is only - * used by tlb_flush_page. - */ -void tlb_set_page_full(CPUState *cpu, int mmu_idx, vaddr addr, - CPUTLBEntryFull *full); - /** * tlb_set_page_with_attrs: * @cpu: CPU to add this TLB entry for diff --git a/target/sparc/mmu_helper.c b/target/sparc/mmu_helper.c index 9ff06026b8c1..7548d01777c8 100644 --- a/target/sparc/mmu_helper.c +++ b/target/sparc/mmu_helper.c @@ -20,7 +20,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "qemu/qemu-print.h" #include "trace.h" From 2809e2d6c4570ee9c04c3f846893c8cc2b966bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 14 Nov 2024 02:13:01 +0100 Subject: [PATCH 0675/1179] exec: Declare tlb_set_page_with_attrs() in 'exec/cputlb.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU TLB related methods to "exec/cputlb.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson Message-ID: <20241114011310.3615-17-philmd@linaro.org> --- include/exec/cputlb.h | 28 ++++++++++++++++++++++++++++ include/exec/exec-all.h | 25 ------------------------- target/i386/tcg/system/excp_helper.c | 2 +- target/microblaze/helper.c | 2 +- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/include/exec/cputlb.h b/include/exec/cputlb.h index 733ef012d1ab..56dd05a1484f 100644 --- a/include/exec/cputlb.h +++ b/include/exec/cputlb.h @@ -21,6 +21,8 @@ #define CPUTLB_H #include "exec/cpu-common.h" +#include "exec/hwaddr.h" +#include "exec/memattrs.h" #include "exec/vaddr.h" #ifdef CONFIG_TCG @@ -62,4 +64,30 @@ void tlb_reset_dirty_range_all(ram_addr_t start, ram_addr_t length); void tlb_set_page_full(CPUState *cpu, int mmu_idx, vaddr addr, CPUTLBEntryFull *full); +/** + * tlb_set_page_with_attrs: + * @cpu: CPU to add this TLB entry for + * @addr: virtual address of page to add entry for + * @paddr: physical address of the page + * @attrs: memory transaction attributes + * @prot: access permissions (PAGE_READ/PAGE_WRITE/PAGE_EXEC bits) + * @mmu_idx: MMU index to insert TLB entry for + * @size: size of the page in bytes + * + * Add an entry to this CPU's TLB (a mapping from virtual address + * @addr to physical address @paddr) with the specified memory + * transaction attributes. This is generally called by the target CPU + * specific code after it has been called through the tlb_fill() + * entry point and performed a successful page table walk to find + * the physical address and attributes for the virtual address + * which provoked the TLB miss. + * + * At most one entry for a given virtual address is permitted. Only a + * single TARGET_PAGE_SIZE region is mapped; the supplied @size is only + * used by tlb_flush_page. + */ +void tlb_set_page_with_attrs(CPUState *cpu, vaddr addr, + hwaddr paddr, MemTxAttrs attrs, + int prot, int mmu_idx, vaddr size); + #endif diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index f43c67366ba7..62d6300752e9 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -156,31 +156,6 @@ void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *cpu, uint16_t idxmap, unsigned bits); -/** - * tlb_set_page_with_attrs: - * @cpu: CPU to add this TLB entry for - * @addr: virtual address of page to add entry for - * @paddr: physical address of the page - * @attrs: memory transaction attributes - * @prot: access permissions (PAGE_READ/PAGE_WRITE/PAGE_EXEC bits) - * @mmu_idx: MMU index to insert TLB entry for - * @size: size of the page in bytes - * - * Add an entry to this CPU's TLB (a mapping from virtual address - * @addr to physical address @paddr) with the specified memory - * transaction attributes. This is generally called by the target CPU - * specific code after it has been called through the tlb_fill() - * entry point and performed a successful page table walk to find - * the physical address and attributes for the virtual address - * which provoked the TLB miss. - * - * At most one entry for a given virtual address is permitted. Only a - * single TARGET_PAGE_SIZE region is mapped; the supplied @size is only - * used by tlb_flush_page. - */ -void tlb_set_page_with_attrs(CPUState *cpu, vaddr addr, - hwaddr paddr, MemTxAttrs attrs, - int prot, int mmu_idx, vaddr size); /* tlb_set_page: * * This function is equivalent to calling tlb_set_page_with_attrs() diff --git a/target/i386/tcg/system/excp_helper.c b/target/i386/tcg/system/excp_helper.c index 864e3140e3de..6876329de21e 100644 --- a/target/i386/tcg/system/excp_helper.c +++ b/target/i386/tcg/system/excp_helper.c @@ -20,7 +20,7 @@ #include "qemu/osdep.h" #include "cpu.h" #include "exec/cpu_ldst.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "tcg/helper-tcg.h" diff --git a/target/microblaze/helper.c b/target/microblaze/helper.c index 5d3259ce316d..27fc929bee40 100644 --- a/target/microblaze/helper.c +++ b/target/microblaze/helper.c @@ -20,7 +20,7 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "qemu/host-utils.h" #include "exec/log.h" From eb9b25c6565d8c49a0db40f65a8a1f7932e81ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 14 Nov 2024 02:13:02 +0100 Subject: [PATCH 0676/1179] exec: Declare tlb_set_page() in 'exec/cputlb.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU TLB related methods to "exec/cputlb.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson Message-ID: <20241114011310.3615-18-philmd@linaro.org> --- include/exec/cputlb.h | 11 +++++++++++ include/exec/exec-all.h | 9 --------- target/alpha/helper.c | 2 +- target/avr/helper.c | 2 +- target/loongarch/tcg/tlb_helper.c | 1 + target/m68k/helper.c | 1 + target/mips/tcg/system/tlb_helper.c | 1 + target/openrisc/mmu.c | 2 +- target/ppc/mmu_helper.c | 1 + target/riscv/cpu_helper.c | 1 + target/rx/cpu.c | 2 +- target/s390x/tcg/excp_helper.c | 1 + target/sh4/helper.c | 1 + target/tricore/helper.c | 2 +- target/xtensa/helper.c | 2 +- 15 files changed, 24 insertions(+), 15 deletions(-) diff --git a/include/exec/cputlb.h b/include/exec/cputlb.h index 56dd05a1484f..cdfaf1740371 100644 --- a/include/exec/cputlb.h +++ b/include/exec/cputlb.h @@ -90,4 +90,15 @@ void tlb_set_page_with_attrs(CPUState *cpu, vaddr addr, hwaddr paddr, MemTxAttrs attrs, int prot, int mmu_idx, vaddr size); +/** + * tlb_set_page: + * + * This function is equivalent to calling tlb_set_page_with_attrs() + * with an @attrs argument of MEMTXATTRS_UNSPECIFIED. It's provided + * as a convenience for CPUs which don't use memory transaction attributes. + */ +void tlb_set_page(CPUState *cpu, vaddr addr, + hwaddr paddr, int prot, + int mmu_idx, vaddr size); + #endif diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index 62d6300752e9..a3aa8448d0af 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -156,15 +156,6 @@ void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *cpu, uint16_t idxmap, unsigned bits); -/* tlb_set_page: - * - * This function is equivalent to calling tlb_set_page_with_attrs() - * with an @attrs argument of MEMTXATTRS_UNSPECIFIED. It's provided - * as a convenience for CPUs which don't use memory transaction attributes. - */ -void tlb_set_page(CPUState *cpu, vaddr addr, - hwaddr paddr, int prot, - int mmu_idx, vaddr size); #else static inline void tlb_flush_page(CPUState *cpu, vaddr addr) { diff --git a/target/alpha/helper.c b/target/alpha/helper.c index 2f1000c99fa9..57cefcba1448 100644 --- a/target/alpha/helper.c +++ b/target/alpha/helper.c @@ -20,7 +20,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "fpu/softfloat-types.h" #include "exec/helper-proto.h" diff --git a/target/avr/helper.c b/target/avr/helper.c index 9ea6870e44dc..3412312ad5e9 100644 --- a/target/avr/helper.c +++ b/target/avr/helper.c @@ -23,7 +23,7 @@ #include "qemu/error-report.h" #include "cpu.h" #include "accel/tcg/cpu-ops.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "exec/cpu_ldst.h" #include "exec/address-spaces.h" diff --git a/target/loongarch/tcg/tlb_helper.c b/target/loongarch/tcg/tlb_helper.c index a323606e5a9e..f6b63c7224e4 100644 --- a/target/loongarch/tcg/tlb_helper.c +++ b/target/loongarch/tcg/tlb_helper.c @@ -12,6 +12,7 @@ #include "cpu.h" #include "internals.h" #include "exec/helper-proto.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/cpu_ldst.h" diff --git a/target/m68k/helper.c b/target/m68k/helper.c index beefeb7069c9..0bf574830f9e 100644 --- a/target/m68k/helper.c +++ b/target/m68k/helper.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/gdbstub.h" diff --git a/target/mips/tcg/system/tlb_helper.c b/target/mips/tcg/system/tlb_helper.c index e98bb9595178..ca4d6b27bc94 100644 --- a/target/mips/tcg/system/tlb_helper.c +++ b/target/mips/tcg/system/tlb_helper.c @@ -21,6 +21,7 @@ #include "cpu.h" #include "internal.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/cpu_ldst.h" diff --git a/target/openrisc/mmu.c b/target/openrisc/mmu.c index c632d5230b29..47ac783c5251 100644 --- a/target/openrisc/mmu.c +++ b/target/openrisc/mmu.c @@ -21,7 +21,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "gdbstub/helpers.h" #include "qemu/host-utils.h" diff --git a/target/ppc/mmu_helper.c b/target/ppc/mmu_helper.c index a802bc9c62b0..ad9ba8294cc6 100644 --- a/target/ppc/mmu_helper.c +++ b/target/ppc/mmu_helper.c @@ -24,6 +24,7 @@ #include "kvm_ppc.h" #include "mmu-hash64.h" #include "mmu-hash32.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/log.h" diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 34092f372df6..6c4391d96b84 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -23,6 +23,7 @@ #include "cpu.h" #include "internals.h" #include "pmu.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "instmap.h" diff --git a/target/rx/cpu.c b/target/rx/cpu.c index 1c40c8977e76..f01e069a907b 100644 --- a/target/rx/cpu.c +++ b/target/rx/cpu.c @@ -21,7 +21,7 @@ #include "qapi/error.h" #include "cpu.h" #include "migration/vmstate.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "exec/translation-block.h" #include "hw/loader.h" diff --git a/target/s390x/tcg/excp_helper.c b/target/s390x/tcg/excp_helper.c index 4c0b692c9e85..f969850f8716 100644 --- a/target/s390x/tcg/excp_helper.c +++ b/target/s390x/tcg/excp_helper.c @@ -22,6 +22,7 @@ #include "qemu/log.h" #include "cpu.h" #include "exec/helper-proto.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "s390x-internal.h" #include "tcg_s390x.h" diff --git a/target/sh4/helper.c b/target/sh4/helper.c index b8774e046e47..7567e6c8b669 100644 --- a/target/sh4/helper.c +++ b/target/sh4/helper.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/page-protection.h" #include "exec/log.h" diff --git a/target/tricore/helper.c b/target/tricore/helper.c index 9898752eb00e..a64412e6bd89 100644 --- a/target/tricore/helper.c +++ b/target/tricore/helper.c @@ -19,7 +19,7 @@ #include "qemu/log.h" #include "hw/registerfields.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "fpu/softfloat-helpers.h" #include "qemu/qemu-print.h" diff --git a/target/xtensa/helper.c b/target/xtensa/helper.c index f64699b116df..4824b97e371e 100644 --- a/target/xtensa/helper.c +++ b/target/xtensa/helper.c @@ -28,7 +28,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "gdbstub/helpers.h" #include "exec/helper-proto.h" #include "qemu/error-report.h" From bcde46f57dccb3a5d7d669cabef7da0b506c319b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 14 Nov 2024 02:13:04 +0100 Subject: [PATCH 0677/1179] exec: Declare tlb_hit*() in 'exec/cputlb.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU TLB related methods to "exec/cputlb.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson Message-ID: <20241114011310.3615-20-philmd@linaro.org> --- accel/tcg/cputlb.c | 23 +++++++++++++++++++++++ include/exec/cpu-all.h | 23 ----------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index c8761683a0a6..fb22048876e3 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -1201,6 +1201,29 @@ void tlb_set_page(CPUState *cpu, vaddr addr, prot, mmu_idx, size); } +/** + * tlb_hit_page: return true if page aligned @addr is a hit against the + * TLB entry @tlb_addr + * + * @addr: virtual address to test (must be page aligned) + * @tlb_addr: TLB entry address (a CPUTLBEntry addr_read/write/code value) + */ +static inline bool tlb_hit_page(uint64_t tlb_addr, vaddr addr) +{ + return addr == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK)); +} + +/** + * tlb_hit: return true if @addr is a hit against the TLB entry @tlb_addr + * + * @addr: virtual address to test (need not be page aligned) + * @tlb_addr: TLB entry address (a CPUTLBEntry addr_read/write/code value) + */ +static inline bool tlb_hit(uint64_t tlb_addr, vaddr addr) +{ + return tlb_hit_page(tlb_addr, addr & TARGET_PAGE_MASK); +} + /* * Note: tlb_fill_align() can trigger a resize of the TLB. * This means that all of the caller's prior references to the TLB table diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index 9e6724097ce6..8cd6c00cf892 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -179,29 +179,6 @@ static inline int cpu_mmu_index(CPUState *cs, bool ifetch) /* The two sets of flags must not overlap. */ QEMU_BUILD_BUG_ON(TLB_FLAGS_MASK & TLB_SLOW_FLAGS_MASK); -/** - * tlb_hit_page: return true if page aligned @addr is a hit against the - * TLB entry @tlb_addr - * - * @addr: virtual address to test (must be page aligned) - * @tlb_addr: TLB entry address (a CPUTLBEntry addr_read/write/code value) - */ -static inline bool tlb_hit_page(uint64_t tlb_addr, vaddr addr) -{ - return addr == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK)); -} - -/** - * tlb_hit: return true if @addr is a hit against the TLB entry @tlb_addr - * - * @addr: virtual address to test (need not be page aligned) - * @tlb_addr: TLB entry address (a CPUTLBEntry addr_read/write/code value) - */ -static inline bool tlb_hit(uint64_t tlb_addr, vaddr addr) -{ - return tlb_hit_page(tlb_addr, addr & TARGET_PAGE_MASK); -} - #endif /* !CONFIG_USER_ONLY */ /* Validate correct placement of CPUArchState. */ From 6ff5da16000f908140723e164d33a0b51a6c4162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 14 Nov 2024 02:13:03 +0100 Subject: [PATCH 0678/1179] exec: Declare tlb_flush*() in 'exec/cputlb.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU TLB related methods to "exec/cputlb.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Pierrick Bouvier Message-ID: <20241114011310.3615-19-philmd@linaro.org> Signed-off-by: Richard Henderson --- accel/tcg/tcg-accel-ops.c | 2 +- cpu-target.c | 1 + hw/intc/armv7m_nvic.c | 2 +- hw/ppc/spapr_nested.c | 1 + hw/sh4/sh7750.c | 1 + include/exec/cputlb.h | 200 +++++++++++++++++++++++++-- include/exec/exec-all.h | 184 ------------------------ system/watchpoint.c | 3 +- target/alpha/sys_helper.c | 2 +- target/arm/helper.c | 1 + target/arm/tcg/tlb-insns.c | 2 +- target/hppa/mem_helper.c | 1 + target/i386/helper.c | 2 +- target/i386/machine.c | 2 +- target/i386/tcg/fpu_helper.c | 2 +- target/i386/tcg/misc_helper.c | 2 +- target/i386/tcg/system/misc_helper.c | 2 +- target/i386/tcg/system/svm_helper.c | 2 +- target/loongarch/tcg/csr_helper.c | 2 +- target/microblaze/mmu.c | 2 +- target/mips/system/cp0.c | 2 +- target/mips/tcg/system/cp0_helper.c | 2 +- target/openrisc/sys_helper.c | 1 + target/ppc/helper_regs.c | 2 +- target/ppc/misc_helper.c | 1 + target/riscv/csr.c | 1 + target/riscv/op_helper.c | 1 + target/riscv/pmp.c | 2 +- target/s390x/gdbstub.c | 2 +- target/s390x/sigp.c | 1 + target/s390x/tcg/mem_helper.c | 1 + target/s390x/tcg/misc_helper.c | 1 + target/sparc/ldst_helper.c | 1 + target/xtensa/mmu_helper.c | 1 + 34 files changed, 224 insertions(+), 211 deletions(-) diff --git a/accel/tcg/tcg-accel-ops.c b/accel/tcg/tcg-accel-ops.c index 132c5d146138..53e580d128bf 100644 --- a/accel/tcg/tcg-accel-ops.c +++ b/accel/tcg/tcg-accel-ops.c @@ -33,7 +33,7 @@ #include "qemu/main-loop.h" #include "qemu/guest-random.h" #include "qemu/timer.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/hwaddr.h" #include "exec/tb-flush.h" #include "exec/translation-block.h" diff --git a/cpu-target.c b/cpu-target.c index 5aa6c4b0c63d..b6e66d5ac023 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -31,6 +31,7 @@ #include "exec/tswap.h" #include "exec/replay-core.h" #include "exec/cpu-common.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/tb-flush.h" #include "exec/log.h" diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c index 5fd076098243..7212c87c68ec 100644 --- a/hw/intc/armv7m_nvic.c +++ b/hw/intc/armv7m_nvic.c @@ -22,7 +22,7 @@ #include "system/runstate.h" #include "target/arm/cpu.h" #include "target/arm/cpu-features.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/memop.h" #include "qemu/log.h" #include "qemu/module.h" diff --git a/hw/ppc/spapr_nested.c b/hw/ppc/spapr_nested.c index 7def8eb73b83..23958c6383a3 100644 --- a/hw/ppc/spapr_nested.c +++ b/hw/ppc/spapr_nested.c @@ -1,6 +1,7 @@ #include "qemu/osdep.h" #include "qemu/cutils.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "helper_regs.h" #include "hw/ppc/ppc.h" #include "hw/ppc/spapr.h" diff --git a/hw/sh4/sh7750.c b/hw/sh4/sh7750.c index 8892eaddcbc6..6faf0e3ca8bb 100644 --- a/hw/sh4/sh7750.c +++ b/hw/sh4/sh7750.c @@ -36,6 +36,7 @@ #include "hw/sh4/sh_intc.h" #include "hw/timer/tmu012.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "trace.h" typedef struct SH7750State { diff --git a/include/exec/cputlb.h b/include/exec/cputlb.h index cdfaf1740371..8125f6809c6f 100644 --- a/include/exec/cputlb.h +++ b/include/exec/cputlb.h @@ -25,21 +25,14 @@ #include "exec/memattrs.h" #include "exec/vaddr.h" -#ifdef CONFIG_TCG - -#if !defined(CONFIG_USER_ONLY) -/* cputlb.c */ +#if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY) void tlb_protect_code(ram_addr_t ram_addr); void tlb_unprotect_code(ram_addr_t ram_addr); #endif -#endif /* CONFIG_TCG */ - #ifndef CONFIG_USER_ONLY - void tlb_reset_dirty(CPUState *cpu, ram_addr_t start1, ram_addr_t length); void tlb_reset_dirty_range_all(ram_addr_t start, ram_addr_t length); - #endif /** @@ -101,4 +94,193 @@ void tlb_set_page(CPUState *cpu, vaddr addr, hwaddr paddr, int prot, int mmu_idx, vaddr size); -#endif +#if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY) +/** + * tlb_flush_page: + * @cpu: CPU whose TLB should be flushed + * @addr: virtual address of page to be flushed + * + * Flush one page from the TLB of the specified CPU, for all + * MMU indexes. + */ +void tlb_flush_page(CPUState *cpu, vaddr addr); + +/** + * tlb_flush_page_all_cpus_synced: + * @cpu: src CPU of the flush + * @addr: virtual address of page to be flushed + * + * Flush one page from the TLB of all CPUs, for all + * MMU indexes. + * + * When this function returns, no CPUs will subsequently perform + * translations using the flushed TLBs. + */ +void tlb_flush_page_all_cpus_synced(CPUState *src, vaddr addr); + +/** + * tlb_flush: + * @cpu: CPU whose TLB should be flushed + * + * Flush the entire TLB for the specified CPU. Most CPU architectures + * allow the implementation to drop entries from the TLB at any time + * so this is generally safe. If more selective flushing is required + * use one of the other functions for efficiency. + */ +void tlb_flush(CPUState *cpu); + +/** + * tlb_flush_all_cpus_synced: + * @cpu: src CPU of the flush + * + * Flush the entire TLB for all CPUs, for all MMU indexes. + * + * When this function returns, no CPUs will subsequently perform + * translations using the flushed TLBs. + */ +void tlb_flush_all_cpus_synced(CPUState *src_cpu); + +/** + * tlb_flush_page_by_mmuidx: + * @cpu: CPU whose TLB should be flushed + * @addr: virtual address of page to be flushed + * @idxmap: bitmap of MMU indexes to flush + * + * Flush one page from the TLB of the specified CPU, for the specified + * MMU indexes. + */ +void tlb_flush_page_by_mmuidx(CPUState *cpu, vaddr addr, + uint16_t idxmap); + +/** + * tlb_flush_page_by_mmuidx_all_cpus_synced: + * @cpu: Originating CPU of the flush + * @addr: virtual address of page to be flushed + * @idxmap: bitmap of MMU indexes to flush + * + * Flush one page from the TLB of all CPUs, for the specified + * MMU indexes. + * + * When this function returns, no CPUs will subsequently perform + * translations using the flushed TLBs. + */ +void tlb_flush_page_by_mmuidx_all_cpus_synced(CPUState *cpu, vaddr addr, + uint16_t idxmap); + +/** + * tlb_flush_by_mmuidx: + * @cpu: CPU whose TLB should be flushed + * @wait: If true ensure synchronisation by exiting the cpu_loop + * @idxmap: bitmap of MMU indexes to flush + * + * Flush all entries from the TLB of the specified CPU, for the specified + * MMU indexes. + */ +void tlb_flush_by_mmuidx(CPUState *cpu, uint16_t idxmap); + +/** + * tlb_flush_by_mmuidx_all_cpus_synced: + * @cpu: Originating CPU of the flush + * @idxmap: bitmap of MMU indexes to flush + * + * Flush all entries from the TLB of all CPUs, for the specified + * MMU indexes. + * + * When this function returns, no CPUs will subsequently perform + * translations using the flushed TLBs. + */ +void tlb_flush_by_mmuidx_all_cpus_synced(CPUState *cpu, uint16_t idxmap); + +/** + * tlb_flush_page_bits_by_mmuidx + * @cpu: CPU whose TLB should be flushed + * @addr: virtual address of page to be flushed + * @idxmap: bitmap of mmu indexes to flush + * @bits: number of significant bits in address + * + * Similar to tlb_flush_page_mask, but with a bitmap of indexes. + */ +void tlb_flush_page_bits_by_mmuidx(CPUState *cpu, vaddr addr, + uint16_t idxmap, unsigned bits); + +/* Similarly, with broadcast and syncing. */ +void tlb_flush_page_bits_by_mmuidx_all_cpus_synced(CPUState *cpu, vaddr addr, + uint16_t idxmap, + unsigned bits); + +/** + * tlb_flush_range_by_mmuidx + * @cpu: CPU whose TLB should be flushed + * @addr: virtual address of the start of the range to be flushed + * @len: length of range to be flushed + * @idxmap: bitmap of mmu indexes to flush + * @bits: number of significant bits in address + * + * For each mmuidx in @idxmap, flush all pages within [@addr,@addr+@len), + * comparing only the low @bits worth of each virtual page. + */ +void tlb_flush_range_by_mmuidx(CPUState *cpu, vaddr addr, + vaddr len, uint16_t idxmap, + unsigned bits); + +/* Similarly, with broadcast and syncing. */ +void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *cpu, + vaddr addr, + vaddr len, + uint16_t idxmap, + unsigned bits); +#else +static inline void tlb_flush_page(CPUState *cpu, vaddr addr) +{ +} +static inline void tlb_flush_page_all_cpus_synced(CPUState *src, vaddr addr) +{ +} +static inline void tlb_flush(CPUState *cpu) +{ +} +static inline void tlb_flush_all_cpus_synced(CPUState *src_cpu) +{ +} +static inline void tlb_flush_page_by_mmuidx(CPUState *cpu, + vaddr addr, uint16_t idxmap) +{ +} + +static inline void tlb_flush_by_mmuidx(CPUState *cpu, uint16_t idxmap) +{ +} +static inline void tlb_flush_page_by_mmuidx_all_cpus_synced(CPUState *cpu, + vaddr addr, + uint16_t idxmap) +{ +} +static inline void tlb_flush_by_mmuidx_all_cpus_synced(CPUState *cpu, + uint16_t idxmap) +{ +} +static inline void tlb_flush_page_bits_by_mmuidx(CPUState *cpu, + vaddr addr, + uint16_t idxmap, + unsigned bits) +{ +} +static inline void +tlb_flush_page_bits_by_mmuidx_all_cpus_synced(CPUState *cpu, vaddr addr, + uint16_t idxmap, unsigned bits) +{ +} +static inline void tlb_flush_range_by_mmuidx(CPUState *cpu, vaddr addr, + vaddr len, uint16_t idxmap, + unsigned bits) +{ +} +static inline void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *cpu, + vaddr addr, + vaddr len, + uint16_t idxmap, + unsigned bits) +{ +} +#endif /* CONFIG_TCG && !CONFIG_USER_ONLY */ +#endif /* CPUTLB_H */ diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index a3aa8448d0af..a758b7a8438a 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -27,190 +27,6 @@ #include "exec/mmu-access-type.h" #include "exec/translation-block.h" -#if !defined(CONFIG_USER_ONLY) && defined(CONFIG_TCG) -/* cputlb.c */ -/** - * tlb_flush_page: - * @cpu: CPU whose TLB should be flushed - * @addr: virtual address of page to be flushed - * - * Flush one page from the TLB of the specified CPU, for all - * MMU indexes. - */ -void tlb_flush_page(CPUState *cpu, vaddr addr); -/** - * tlb_flush_page_all_cpus_synced: - * @cpu: src CPU of the flush - * @addr: virtual address of page to be flushed - * - * Flush one page from the TLB of all CPUs, for all - * MMU indexes. - * - * When this function returns, no CPUs will subsequently perform - * translations using the flushed TLBs. - */ -void tlb_flush_page_all_cpus_synced(CPUState *src, vaddr addr); -/** - * tlb_flush: - * @cpu: CPU whose TLB should be flushed - * - * Flush the entire TLB for the specified CPU. Most CPU architectures - * allow the implementation to drop entries from the TLB at any time - * so this is generally safe. If more selective flushing is required - * use one of the other functions for efficiency. - */ -void tlb_flush(CPUState *cpu); -/** - * tlb_flush_all_cpus_synced: - * @cpu: src CPU of the flush - * - * Flush the entire TLB for all CPUs, for all MMU indexes. - * - * When this function returns, no CPUs will subsequently perform - * translations using the flushed TLBs. - */ -void tlb_flush_all_cpus_synced(CPUState *src_cpu); -/** - * tlb_flush_page_by_mmuidx: - * @cpu: CPU whose TLB should be flushed - * @addr: virtual address of page to be flushed - * @idxmap: bitmap of MMU indexes to flush - * - * Flush one page from the TLB of the specified CPU, for the specified - * MMU indexes. - */ -void tlb_flush_page_by_mmuidx(CPUState *cpu, vaddr addr, - uint16_t idxmap); -/** - * tlb_flush_page_by_mmuidx_all_cpus_synced: - * @cpu: Originating CPU of the flush - * @addr: virtual address of page to be flushed - * @idxmap: bitmap of MMU indexes to flush - * - * Flush one page from the TLB of all CPUs, for the specified - * MMU indexes. - * - * When this function returns, no CPUs will subsequently perform - * translations using the flushed TLBs. - */ -void tlb_flush_page_by_mmuidx_all_cpus_synced(CPUState *cpu, vaddr addr, - uint16_t idxmap); -/** - * tlb_flush_by_mmuidx: - * @cpu: CPU whose TLB should be flushed - * @wait: If true ensure synchronisation by exiting the cpu_loop - * @idxmap: bitmap of MMU indexes to flush - * - * Flush all entries from the TLB of the specified CPU, for the specified - * MMU indexes. - */ -void tlb_flush_by_mmuidx(CPUState *cpu, uint16_t idxmap); -/** - * tlb_flush_by_mmuidx_all_cpus_synced: - * @cpu: Originating CPU of the flush - * @idxmap: bitmap of MMU indexes to flush - * - * Flush all entries from the TLB of all CPUs, for the specified - * MMU indexes. - * - * When this function returns, no CPUs will subsequently perform - * translations using the flushed TLBs. - */ -void tlb_flush_by_mmuidx_all_cpus_synced(CPUState *cpu, uint16_t idxmap); - -/** - * tlb_flush_page_bits_by_mmuidx - * @cpu: CPU whose TLB should be flushed - * @addr: virtual address of page to be flushed - * @idxmap: bitmap of mmu indexes to flush - * @bits: number of significant bits in address - * - * Similar to tlb_flush_page_mask, but with a bitmap of indexes. - */ -void tlb_flush_page_bits_by_mmuidx(CPUState *cpu, vaddr addr, - uint16_t idxmap, unsigned bits); - -/* Similarly, with broadcast and syncing. */ -void tlb_flush_page_bits_by_mmuidx_all_cpus_synced - (CPUState *cpu, vaddr addr, uint16_t idxmap, unsigned bits); - -/** - * tlb_flush_range_by_mmuidx - * @cpu: CPU whose TLB should be flushed - * @addr: virtual address of the start of the range to be flushed - * @len: length of range to be flushed - * @idxmap: bitmap of mmu indexes to flush - * @bits: number of significant bits in address - * - * For each mmuidx in @idxmap, flush all pages within [@addr,@addr+@len), - * comparing only the low @bits worth of each virtual page. - */ -void tlb_flush_range_by_mmuidx(CPUState *cpu, vaddr addr, - vaddr len, uint16_t idxmap, - unsigned bits); - -/* Similarly, with broadcast and syncing. */ -void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *cpu, - vaddr addr, - vaddr len, - uint16_t idxmap, - unsigned bits); - -#else -static inline void tlb_flush_page(CPUState *cpu, vaddr addr) -{ -} -static inline void tlb_flush_page_all_cpus_synced(CPUState *src, vaddr addr) -{ -} -static inline void tlb_flush(CPUState *cpu) -{ -} -static inline void tlb_flush_all_cpus_synced(CPUState *src_cpu) -{ -} -static inline void tlb_flush_page_by_mmuidx(CPUState *cpu, - vaddr addr, uint16_t idxmap) -{ -} - -static inline void tlb_flush_by_mmuidx(CPUState *cpu, uint16_t idxmap) -{ -} -static inline void tlb_flush_page_by_mmuidx_all_cpus_synced(CPUState *cpu, - vaddr addr, - uint16_t idxmap) -{ -} -static inline void tlb_flush_by_mmuidx_all_cpus_synced(CPUState *cpu, - uint16_t idxmap) -{ -} -static inline void tlb_flush_page_bits_by_mmuidx(CPUState *cpu, - vaddr addr, - uint16_t idxmap, - unsigned bits) -{ -} -static inline void -tlb_flush_page_bits_by_mmuidx_all_cpus_synced(CPUState *cpu, vaddr addr, - uint16_t idxmap, unsigned bits) -{ -} -static inline void tlb_flush_range_by_mmuidx(CPUState *cpu, vaddr addr, - vaddr len, uint16_t idxmap, - unsigned bits) -{ -} -static inline void tlb_flush_range_by_mmuidx_all_cpus_synced(CPUState *cpu, - vaddr addr, - vaddr len, - uint16_t idxmap, - unsigned bits) -{ -} -#endif - #if defined(CONFIG_TCG) /** diff --git a/system/watchpoint.c b/system/watchpoint.c index 2aa2a9ea63f5..08dbd8483d15 100644 --- a/system/watchpoint.c +++ b/system/watchpoint.c @@ -19,7 +19,8 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" +#include "exec/target_page.h" #include "hw/core/cpu.h" /* Add a watchpoint. */ diff --git a/target/alpha/sys_helper.c b/target/alpha/sys_helper.c index 54ee93f34ccc..51e32544287a 100644 --- a/target/alpha/sys_helper.c +++ b/target/alpha/sys_helper.c @@ -19,7 +19,7 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/tb-flush.h" #include "exec/helper-proto.h" #include "system/runstate.h" diff --git a/target/arm/helper.c b/target/arm/helper.c index 71dead7241b4..e786c8df5f49 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -18,6 +18,7 @@ #include "qemu/timer.h" #include "qemu/bitops.h" #include "qemu/qemu-print.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/translation-block.h" #include "hw/irq.h" diff --git a/target/arm/tcg/tlb-insns.c b/target/arm/tcg/tlb-insns.c index fadc61a76e96..630a481f0f81 100644 --- a/target/arm/tcg/tlb-insns.c +++ b/target/arm/tcg/tlb-insns.c @@ -7,7 +7,7 @@ */ #include "qemu/osdep.h" #include "qemu/log.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "cpu.h" #include "internals.h" #include "cpu-features.h" diff --git a/target/hppa/mem_helper.c b/target/hppa/mem_helper.c index 304f0b61e256..fb1d93ef1f19 100644 --- a/target/hppa/mem_helper.c +++ b/target/hppa/mem_helper.c @@ -21,6 +21,7 @@ #include "qemu/log.h" #include "cpu.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "exec/helper-proto.h" #include "hw/core/cpu.h" diff --git a/target/i386/helper.c b/target/i386/helper.c index 3bc15fba6ee8..c07b1b16ea17 100644 --- a/target/i386/helper.c +++ b/target/i386/helper.c @@ -20,7 +20,7 @@ #include "qemu/osdep.h" #include "qapi/qapi-events-run-state.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/translation-block.h" #include "system/runstate.h" #ifndef CONFIG_USER_ONLY diff --git a/target/i386/machine.c b/target/i386/machine.c index d9d4f25d1a2e..70f632a36fdd 100644 --- a/target/i386/machine.c +++ b/target/i386/machine.c @@ -1,6 +1,6 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "hw/isa/isa.h" #include "migration/cpu.h" #include "kvm/hyperv.h" diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index 4858ae9a5fb2..c1184ca2198f 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -21,7 +21,7 @@ #include #include "cpu.h" #include "tcg-cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/cpu_ldst.h" #include "exec/helper-proto.h" #include "fpu/softfloat.h" diff --git a/target/i386/tcg/misc_helper.c b/target/i386/tcg/misc_helper.c index ed4cda8001e5..2b5f092a23f0 100644 --- a/target/i386/tcg/misc_helper.c +++ b/target/i386/tcg/misc_helper.c @@ -21,7 +21,7 @@ #include "qemu/log.h" #include "cpu.h" #include "exec/helper-proto.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "helper-tcg.h" /* diff --git a/target/i386/tcg/system/misc_helper.c b/target/i386/tcg/system/misc_helper.c index c9c4d42f8449..ce18c75b9f3b 100644 --- a/target/i386/tcg/system/misc_helper.c +++ b/target/i386/tcg/system/misc_helper.c @@ -23,7 +23,7 @@ #include "exec/helper-proto.h" #include "exec/cpu_ldst.h" #include "exec/address-spaces.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "tcg/helper-tcg.h" #include "hw/i386/apic.h" diff --git a/target/i386/tcg/system/svm_helper.c b/target/i386/tcg/system/svm_helper.c index 5f95b5227b76..f9982b72d17e 100644 --- a/target/i386/tcg/system/svm_helper.c +++ b/target/i386/tcg/system/svm_helper.c @@ -21,7 +21,7 @@ #include "qemu/log.h" #include "cpu.h" #include "exec/helper-proto.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/cpu_ldst.h" #include "tcg/helper-tcg.h" diff --git a/target/loongarch/tcg/csr_helper.c b/target/loongarch/tcg/csr_helper.c index 6c95be991083..84f7ff25f68a 100644 --- a/target/loongarch/tcg/csr_helper.c +++ b/target/loongarch/tcg/csr_helper.c @@ -12,7 +12,7 @@ #include "internals.h" #include "qemu/host-utils.h" #include "exec/helper-proto.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/cpu_ldst.h" #include "hw/irq.h" #include "cpu-csr.h" diff --git a/target/microblaze/mmu.c b/target/microblaze/mmu.c index 2423ac6172d3..f8587d5ac4da 100644 --- a/target/microblaze/mmu.c +++ b/target/microblaze/mmu.c @@ -21,7 +21,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" static unsigned int tlb_decode_size(unsigned int f) diff --git a/target/mips/system/cp0.c b/target/mips/system/cp0.c index bae37f515bf8..ff7d3db00c73 100644 --- a/target/mips/system/cp0.c +++ b/target/mips/system/cp0.c @@ -21,7 +21,7 @@ #include "qemu/osdep.h" #include "cpu.h" #include "internal.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" /* Called for updates to CP0_Status. */ void sync_c0_status(CPUMIPSState *env, CPUMIPSState *cpu, int tc) diff --git a/target/mips/tcg/system/cp0_helper.c b/target/mips/tcg/system/cp0_helper.c index 79a5c833ceec..01a07a169f6d 100644 --- a/target/mips/tcg/system/cp0_helper.c +++ b/target/mips/tcg/system/cp0_helper.c @@ -27,7 +27,7 @@ #include "internal.h" #include "qemu/host-utils.h" #include "exec/helper-proto.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" /* SMP helpers. */ diff --git a/target/openrisc/sys_helper.c b/target/openrisc/sys_helper.c index 77567afba47f..21bc137cccaf 100644 --- a/target/openrisc/sys_helper.c +++ b/target/openrisc/sys_helper.c @@ -21,6 +21,7 @@ #include "qemu/osdep.h" #include "cpu.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/helper-proto.h" #include "exception.h" #ifndef CONFIG_USER_ONLY diff --git a/target/ppc/helper_regs.c b/target/ppc/helper_regs.c index 3ad4273c1649..f211bc983044 100644 --- a/target/ppc/helper_regs.c +++ b/target/ppc/helper_regs.c @@ -20,7 +20,7 @@ #include "qemu/osdep.h" #include "cpu.h" #include "qemu/main-loop.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "system/kvm.h" #include "system/tcg.h" #include "helper_regs.h" diff --git a/target/ppc/misc_helper.c b/target/ppc/misc_helper.c index f0ca80153b2b..e379da6010bb 100644 --- a/target/ppc/misc_helper.c +++ b/target/ppc/misc_helper.c @@ -21,6 +21,7 @@ #include "qemu/log.h" #include "cpu.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/helper-proto.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" diff --git a/target/riscv/csr.c b/target/riscv/csr.c index 0ebcca459781..49566d3c0828 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -25,6 +25,7 @@ #include "pmu.h" #include "time_helper.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/tb-flush.h" #include "system/cpu-timers.h" #include "qemu/guest-random.h" diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c index f156bfab12da..0d4220ba93b7 100644 --- a/target/riscv/op_helper.c +++ b/target/riscv/op_helper.c @@ -22,6 +22,7 @@ #include "cpu.h" #include "internals.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/cpu_ldst.h" #include "exec/helper-proto.h" #include "trace.h" diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c index 85ab270dad47..b0841d44f4c2 100644 --- a/target/riscv/pmp.c +++ b/target/riscv/pmp.c @@ -24,7 +24,7 @@ #include "qapi/error.h" #include "cpu.h" #include "trace.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" static bool pmp_write_cfg(CPURISCVState *env, uint32_t addr_index, diff --git a/target/s390x/gdbstub.c b/target/s390x/gdbstub.c index 6879430adc2a..6bca376f2b6f 100644 --- a/target/s390x/gdbstub.c +++ b/target/s390x/gdbstub.c @@ -21,7 +21,7 @@ #include "qemu/osdep.h" #include "cpu.h" #include "s390x-internal.h" -#include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/gdbstub.h" #include "gdbstub/helpers.h" #include "qemu/bitops.h" diff --git a/target/s390x/sigp.c b/target/s390x/sigp.c index cf53b2329185..6a4d9c50813d 100644 --- a/target/s390x/sigp.c +++ b/target/s390x/sigp.c @@ -15,6 +15,7 @@ #include "system/hw_accel.h" #include "system/runstate.h" #include "exec/address-spaces.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "system/tcg.h" #include "trace.h" diff --git a/target/s390x/tcg/mem_helper.c b/target/s390x/tcg/mem_helper.c index ea9fa64d6b44..8187b917ba12 100644 --- a/target/s390x/tcg/mem_helper.c +++ b/target/s390x/tcg/mem_helper.c @@ -26,6 +26,7 @@ #include "exec/helper-proto.h" #include "exec/cpu-common.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "exec/cpu_ldst.h" #include "accel/tcg/cpu-ops.h" diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c index 0245451472e7..31266aeda479 100644 --- a/target/s390x/tcg/misc_helper.c +++ b/target/s390x/tcg/misc_helper.c @@ -27,6 +27,7 @@ #include "exec/helper-proto.h" #include "qemu/timer.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/cpu_ldst.h" #include "qapi/error.h" #include "tcg_s390x.h" diff --git a/target/sparc/ldst_helper.c b/target/sparc/ldst_helper.c index 4c54e4565535..b559afc9a944 100644 --- a/target/sparc/ldst_helper.c +++ b/target/sparc/ldst_helper.c @@ -24,6 +24,7 @@ #include "tcg/tcg.h" #include "exec/helper-proto.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/page-protection.h" #include "exec/cpu_ldst.h" #ifdef CONFIG_USER_ONLY diff --git a/target/xtensa/mmu_helper.c b/target/xtensa/mmu_helper.c index 29b84d5dbf66..63be741a42ae 100644 --- a/target/xtensa/mmu_helper.c +++ b/target/xtensa/mmu_helper.c @@ -32,6 +32,7 @@ #include "cpu.h" #include "exec/helper-proto.h" #include "qemu/host-utils.h" +#include "exec/cputlb.h" #include "exec/exec-all.h" #include "exec/page-protection.h" From 5516b44bea9ce7fcc5d8eddd061ffc8dacc31b51 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 6 Mar 2025 21:49:50 -0800 Subject: [PATCH 0679/1179] system: Build watchpoint.c once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that watchpoint.c uses cputlb.h instead of exec-all.h, it can be built once. Tested-by: Philippe Mathieu-Daudé Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- system/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/meson.build b/system/meson.build index 4952f4b2c7d4..c83d80fa2485 100644 --- a/system/meson.build +++ b/system/meson.build @@ -3,7 +3,6 @@ specific_ss.add(when: 'CONFIG_SYSTEM_ONLY', if_true: [files( 'ioport.c', 'memory.c', 'physmem.c', - 'watchpoint.c', )]) system_ss.add(files( @@ -24,6 +23,7 @@ system_ss.add(files( 'runstate.c', 'tpm-hmp-cmds.c', 'vl.c', + 'watchpoint.c', ), sdl, libpmem, libdaxctl) if have_tpm From ab6d72979ace98257141ba36da1c5297b71ddf61 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 6 Mar 2025 21:50:29 -0800 Subject: [PATCH 0680/1179] accel/tcg: Build tcg-accel-ops.c once Now that tcg-accel-ops.c uses cputlb.h instead of exec-all.h, it can be built once. Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- accel/tcg/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 979ce90eb0a0..70ada21f4276 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -20,7 +20,6 @@ specific_ss.add_all(when: 'CONFIG_TCG', if_true: tcg_specific_ss) specific_ss.add(when: ['CONFIG_SYSTEM_ONLY', 'CONFIG_TCG'], if_true: files( 'cputlb.c', - 'tcg-accel-ops.c', 'tcg-accel-ops-mttcg.c', 'tcg-accel-ops-icount.c', 'tcg-accel-ops-rr.c', @@ -29,5 +28,6 @@ specific_ss.add(when: ['CONFIG_SYSTEM_ONLY', 'CONFIG_TCG'], if_true: files( system_ss.add(when: ['CONFIG_TCG'], if_true: files( 'icount-common.c', 'monitor.c', + 'tcg-accel-ops.c', 'watchpoint.c', )) From 29172ec53658a035742d2d94fb0d9a3d169e90a1 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 7 Mar 2025 07:51:15 -0800 Subject: [PATCH 0681/1179] accel/tcg: Build tcg-accel-ops-icount.c once All that is required is to avoid including exec-all.h. Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- accel/tcg/meson.build | 2 +- accel/tcg/tcg-accel-ops-icount.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 70ada21f4276..891b724eb625 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -21,7 +21,6 @@ specific_ss.add_all(when: 'CONFIG_TCG', if_true: tcg_specific_ss) specific_ss.add(when: ['CONFIG_SYSTEM_ONLY', 'CONFIG_TCG'], if_true: files( 'cputlb.c', 'tcg-accel-ops-mttcg.c', - 'tcg-accel-ops-icount.c', 'tcg-accel-ops-rr.c', )) @@ -29,5 +28,6 @@ system_ss.add(when: ['CONFIG_TCG'], if_true: files( 'icount-common.c', 'monitor.c', 'tcg-accel-ops.c', + 'tcg-accel-ops-icount.c', 'watchpoint.c', )) diff --git a/accel/tcg/tcg-accel-ops-icount.c b/accel/tcg/tcg-accel-ops-icount.c index d6b472a0b0e9..27cf1044c7f1 100644 --- a/accel/tcg/tcg-accel-ops-icount.c +++ b/accel/tcg/tcg-accel-ops-icount.c @@ -28,7 +28,7 @@ #include "system/cpu-timers.h" #include "qemu/main-loop.h" #include "qemu/guest-random.h" -#include "exec/exec-all.h" +#include "hw/core/cpu.h" #include "tcg-accel-ops.h" #include "tcg-accel-ops-icount.h" From 3b9aec101b38a824441b65a6f93e3c16e04c0914 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 7 Mar 2025 08:49:06 -0800 Subject: [PATCH 0682/1179] accel/tcg: Build tcg-accel-ops-rr.c once All that is required is to use cpu-common.h instead of exec-all.h. Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- accel/tcg/meson.build | 2 +- accel/tcg/tcg-accel-ops-rr.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 891b724eb625..87c1394b626a 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -21,7 +21,6 @@ specific_ss.add_all(when: 'CONFIG_TCG', if_true: tcg_specific_ss) specific_ss.add(when: ['CONFIG_SYSTEM_ONLY', 'CONFIG_TCG'], if_true: files( 'cputlb.c', 'tcg-accel-ops-mttcg.c', - 'tcg-accel-ops-rr.c', )) system_ss.add(when: ['CONFIG_TCG'], if_true: files( @@ -29,5 +28,6 @@ system_ss.add(when: ['CONFIG_TCG'], if_true: files( 'monitor.c', 'tcg-accel-ops.c', 'tcg-accel-ops-icount.c', + 'tcg-accel-ops-rr.c', 'watchpoint.c', )) diff --git a/accel/tcg/tcg-accel-ops-rr.c b/accel/tcg/tcg-accel-ops-rr.c index 028b385af9aa..f62cf24e1d4d 100644 --- a/accel/tcg/tcg-accel-ops-rr.c +++ b/accel/tcg/tcg-accel-ops-rr.c @@ -31,7 +31,7 @@ #include "qemu/main-loop.h" #include "qemu/notify.h" #include "qemu/guest-random.h" -#include "exec/exec-all.h" +#include "exec/cpu-common.h" #include "tcg/startup.h" #include "tcg-accel-ops.h" #include "tcg-accel-ops-rr.h" From 3fd60df2a6e59133f58074f5d6e0e57f99c769cd Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 7 Mar 2025 08:52:20 -0800 Subject: [PATCH 0683/1179] accel/tcg: Build tcg-accel-ops-mttcg.c once All that is required is to avoid including exec-all.h. Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- accel/tcg/meson.build | 2 +- accel/tcg/tcg-accel-ops-mttcg.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 87c1394b626a..81fb25da5cca 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -20,7 +20,6 @@ specific_ss.add_all(when: 'CONFIG_TCG', if_true: tcg_specific_ss) specific_ss.add(when: ['CONFIG_SYSTEM_ONLY', 'CONFIG_TCG'], if_true: files( 'cputlb.c', - 'tcg-accel-ops-mttcg.c', )) system_ss.add(when: ['CONFIG_TCG'], if_true: files( @@ -28,6 +27,7 @@ system_ss.add(when: ['CONFIG_TCG'], if_true: files( 'monitor.c', 'tcg-accel-ops.c', 'tcg-accel-ops-icount.c', + 'tcg-accel-ops-mttcg.c', 'tcg-accel-ops-rr.c', 'watchpoint.c', )) diff --git a/accel/tcg/tcg-accel-ops-mttcg.c b/accel/tcg/tcg-accel-ops-mttcg.c index ba7cf6819d66..bdcc385ae971 100644 --- a/accel/tcg/tcg-accel-ops-mttcg.c +++ b/accel/tcg/tcg-accel-ops-mttcg.c @@ -30,7 +30,6 @@ #include "qemu/main-loop.h" #include "qemu/notify.h" #include "qemu/guest-random.h" -#include "exec/exec-all.h" #include "hw/boards.h" #include "tcg/startup.h" #include "tcg-accel-ops.h" From bf4a155b7a847c9290b1b5ab4208ca361f5a469c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 8 Mar 2025 08:23:46 +0100 Subject: [PATCH 0684/1179] accel/tcg: Restrict GETPC_ADJ to 'tb-internal.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GETPC_ADJ is only used within accel/tcg/, no need to expose it to all the code base. Signed-off-by: Philippe Mathieu-Daudé Message-ID: <20250308072348.65723-2-philmd@linaro.org> Signed-off-by: Richard Henderson --- accel/tcg/tb-internal.h | 11 +++++++++++ include/exec/exec-all.h | 9 --------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/accel/tcg/tb-internal.h b/accel/tcg/tb-internal.h index 62a59a5307ec..68aa8d17f413 100644 --- a/accel/tcg/tb-internal.h +++ b/accel/tcg/tb-internal.h @@ -13,6 +13,17 @@ #include "exec/exec-all.h" #include "exec/translation-block.h" +/* + * The true return address will often point to a host insn that is part of + * the next translated guest insn. Adjust the address backward to point to + * the middle of the call insn. Subtracting one would do the job except for + * several compressed mode architectures (arm, mips) which set the low bit + * to indicate the compressed mode; subtracting two works around that. It + * is also the case that there are no host isas that contain a call insn + * smaller than 4 bytes, so we don't worry about special-casing this. + */ +#define GETPC_ADJ 2 + #ifdef CONFIG_SOFTMMU #define CPU_TLB_DYN_MIN_BITS 6 diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index a758b7a8438a..2ac98e56c415 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -186,15 +186,6 @@ extern __thread uintptr_t tci_tb_ptr; ((uintptr_t)__builtin_extract_return_addr(__builtin_return_address(0))) #endif -/* The true return address will often point to a host insn that is part of - the next translated guest insn. Adjust the address backward to point to - the middle of the call insn. Subtracting one would do the job except for - several compressed mode architectures (arm, mips) which set the low bit - to indicate the compressed mode; subtracting two works around that. It - is also the case that there are no host isas that contain a call insn - smaller than 4 bytes, so we don't worry about special-casing this. */ -#define GETPC_ADJ 2 - #if !defined(CONFIG_USER_ONLY) /** From b73f58496dc7e14b56521c9eeec7aa1225861fc2 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sat, 8 Mar 2025 08:23:47 +0100 Subject: [PATCH 0685/1179] accel/tcg: Split out getpc.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split out GETPC to a target-independent header. Signed-off-by: Richard Henderson Reviewed-by: Pierrick Bouvier Signed-off-by: Philippe Mathieu-Daudé Message-ID: <20250308072348.65723-3-philmd@linaro.org> Signed-off-by: Richard Henderson --- include/accel/tcg/getpc.h | 24 ++++++++++++++++++++++++ include/exec/exec-all.h | 10 +--------- 2 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 include/accel/tcg/getpc.h diff --git a/include/accel/tcg/getpc.h b/include/accel/tcg/getpc.h new file mode 100644 index 000000000000..8a97ce34e76f --- /dev/null +++ b/include/accel/tcg/getpc.h @@ -0,0 +1,24 @@ +/* + * Get host pc for helper unwinding. + * + * Copyright (c) 2003 Fabrice Bellard + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef ACCEL_TCG_GETPC_H +#define ACCEL_TCG_GETPC_H + +#ifndef CONFIG_TCG +#error Can only include this header with TCG +#endif + +/* GETPC is the true target of the return instruction that we'll execute. */ +#ifdef CONFIG_TCG_INTERPRETER +extern __thread uintptr_t tci_tb_ptr; +# define GETPC() tci_tb_ptr +#else +# define GETPC() \ + ((uintptr_t)__builtin_extract_return_addr(__builtin_return_address(0))) +#endif + +#endif /* ACCEL_TCG_GETPC_H */ diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index 2ac98e56c415..dd5c40f22331 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -28,6 +28,7 @@ #include "exec/translation-block.h" #if defined(CONFIG_TCG) +#include "accel/tcg/getpc.h" /** * probe_access: @@ -177,15 +178,6 @@ void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr); void tb_invalidate_phys_range(tb_page_addr_t start, tb_page_addr_t last); void tb_set_jmp_target(TranslationBlock *tb, int n, uintptr_t addr); -/* GETPC is the true target of the return instruction that we'll execute. */ -#if defined(CONFIG_TCG_INTERPRETER) -extern __thread uintptr_t tci_tb_ptr; -# define GETPC() tci_tb_ptr -#else -# define GETPC() \ - ((uintptr_t)__builtin_extract_return_addr(__builtin_return_address(0))) -#endif - #if !defined(CONFIG_USER_ONLY) /** From 15606965400b8f3038d6e85cfe5956d5a6ac33a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 12 Dec 2024 15:10:16 +0100 Subject: [PATCH 0686/1179] qemu/atomic: Rename atomic128-cas.h headers using .h.inc suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 139c1837db ("meson: rename included C source files to .c.inc"), QEMU standard procedure for included C files is to use *.c.inc. Besides, since commit 6a0057aa22 ("docs/devel: make a statement about includes") this is documented in the Coding Style: If you do use template header files they should be named with the ``.c.inc`` or ``.h.inc`` suffix to make it clear they are being included for expansion. Therefore rename 'atomic128-cas.h' as 'atomic128-cas.h.inc'. Signed-off-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson Message-ID: <20241212141018.59428-2-philmd@linaro.org> --- host/include/aarch64/host/atomic128-cas.h | 2 +- .../generic/host/{atomic128-cas.h => atomic128-cas.h.inc} | 0 include/qemu/atomic128.h | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename host/include/generic/host/{atomic128-cas.h => atomic128-cas.h.inc} (100%) diff --git a/host/include/aarch64/host/atomic128-cas.h b/host/include/aarch64/host/atomic128-cas.h index 58630107bcce..991da4ef5433 100644 --- a/host/include/aarch64/host/atomic128-cas.h +++ b/host/include/aarch64/host/atomic128-cas.h @@ -13,7 +13,7 @@ /* Through gcc 10, aarch64 has no support for 128-bit atomics. */ #if defined(CONFIG_ATOMIC128) || defined(CONFIG_CMPXCHG128) -#include "host/include/generic/host/atomic128-cas.h" +#include "host/include/generic/host/atomic128-cas.h.inc" #else static inline Int128 atomic16_cmpxchg(Int128 *ptr, Int128 cmp, Int128 new) { diff --git a/host/include/generic/host/atomic128-cas.h b/host/include/generic/host/atomic128-cas.h.inc similarity index 100% rename from host/include/generic/host/atomic128-cas.h rename to host/include/generic/host/atomic128-cas.h.inc diff --git a/include/qemu/atomic128.h b/include/qemu/atomic128.h index 88af6d4ea3fa..03c27022f0cf 100644 --- a/include/qemu/atomic128.h +++ b/include/qemu/atomic128.h @@ -58,7 +58,7 @@ * Therefore, special case each platform. */ -#include "host/atomic128-cas.h" +#include "host/atomic128-cas.h.inc" #include "host/atomic128-ldst.h" #endif /* QEMU_ATOMIC128_H */ From 883cc6c5789b4210284241e7a2742c67f98e8843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 12 Dec 2024 15:10:17 +0100 Subject: [PATCH 0687/1179] qemu/atomic: Rename atomic128-ldst.h headers using .h.inc suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 139c1837db ("meson: rename included C source files to .c.inc"), QEMU standard procedure for included C files is to use *.c.inc. Besides, since commit 6a0057aa22 ("docs/devel: make a statement about includes") this is documented in the Coding Style: If you do use template header files they should be named with the ``.c.inc`` or ``.h.inc`` suffix to make it clear they are being included for expansion. Therefore rename 'atomic128-ldst.h' as 'atomic128-ldst.h.inc'. Signed-off-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson Message-ID: <20241212141018.59428-3-philmd@linaro.org> --- .../aarch64/host/{atomic128-ldst.h => atomic128-ldst.h.inc} | 0 .../generic/host/{atomic128-ldst.h => atomic128-ldst.h.inc} | 0 .../loongarch64/host/{atomic128-ldst.h => atomic128-ldst.h.inc} | 0 .../x86_64/host/{atomic128-ldst.h => atomic128-ldst.h.inc} | 2 +- host/include/x86_64/host/load-extract-al16-al8.h.inc | 2 +- include/qemu/atomic128.h | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename host/include/aarch64/host/{atomic128-ldst.h => atomic128-ldst.h.inc} (100%) rename host/include/generic/host/{atomic128-ldst.h => atomic128-ldst.h.inc} (100%) rename host/include/loongarch64/host/{atomic128-ldst.h => atomic128-ldst.h.inc} (100%) rename host/include/x86_64/host/{atomic128-ldst.h => atomic128-ldst.h.inc} (96%) diff --git a/host/include/aarch64/host/atomic128-ldst.h b/host/include/aarch64/host/atomic128-ldst.h.inc similarity index 100% rename from host/include/aarch64/host/atomic128-ldst.h rename to host/include/aarch64/host/atomic128-ldst.h.inc diff --git a/host/include/generic/host/atomic128-ldst.h b/host/include/generic/host/atomic128-ldst.h.inc similarity index 100% rename from host/include/generic/host/atomic128-ldst.h rename to host/include/generic/host/atomic128-ldst.h.inc diff --git a/host/include/loongarch64/host/atomic128-ldst.h b/host/include/loongarch64/host/atomic128-ldst.h.inc similarity index 100% rename from host/include/loongarch64/host/atomic128-ldst.h rename to host/include/loongarch64/host/atomic128-ldst.h.inc diff --git a/host/include/x86_64/host/atomic128-ldst.h b/host/include/x86_64/host/atomic128-ldst.h.inc similarity index 96% rename from host/include/x86_64/host/atomic128-ldst.h rename to host/include/x86_64/host/atomic128-ldst.h.inc index 8d6f909d3c9c..4c698e3246f4 100644 --- a/host/include/x86_64/host/atomic128-ldst.h +++ b/host/include/x86_64/host/atomic128-ldst.h.inc @@ -69,7 +69,7 @@ static inline void atomic16_set(Int128 *ptr, Int128 val) } #else /* Provide QEMU_ERROR stubs. */ -#include "host/include/generic/host/atomic128-ldst.h" +#include "host/include/generic/host/atomic128-ldst.h.inc" #endif #endif /* X86_64_ATOMIC128_LDST_H */ diff --git a/host/include/x86_64/host/load-extract-al16-al8.h.inc b/host/include/x86_64/host/load-extract-al16-al8.h.inc index baa506b7b5b1..b837c3786843 100644 --- a/host/include/x86_64/host/load-extract-al16-al8.h.inc +++ b/host/include/x86_64/host/load-extract-al16-al8.h.inc @@ -9,7 +9,7 @@ #define X86_64_LOAD_EXTRACT_AL16_AL8_H #ifdef CONFIG_INT128_TYPE -#include "host/atomic128-ldst.h" +#include "host/atomic128-ldst.h.inc" /** * load_atom_extract_al16_or_al8: diff --git a/include/qemu/atomic128.h b/include/qemu/atomic128.h index 03c27022f0cf..448fb6447997 100644 --- a/include/qemu/atomic128.h +++ b/include/qemu/atomic128.h @@ -59,6 +59,6 @@ */ #include "host/atomic128-cas.h.inc" -#include "host/atomic128-ldst.h" +#include "host/atomic128-ldst.h.inc" #endif /* QEMU_ATOMIC128_H */ From 67ba7439481d61bc8482ff2ea436f74a575e371a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 12 Dec 2024 15:10:18 +0100 Subject: [PATCH 0688/1179] qemu/atomic128: Include missing 'qemu/atomic.h' header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qatomic_cmpxchg__nocheck() is declared in "qemu/atomic.h". Include it in order to avoid when refactoring unrelated headers: In file included from ../../accel/tcg/tcg-runtime-gvec.c:22: In file included from include/exec/helper-proto-common.h:10: In file included from include/qemu/atomic128.h:61: host/include/generic/host/atomic128-cas.h.inc:23:11: error: call to undeclared function 'qatomic_cmpxchg__nocheck'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 23 | r.i = qatomic_cmpxchg__nocheck(ptr_align, c.i, n.i); | ^ 1 error generated. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Signed-off-by: Richard Henderson Message-ID: <20241212141018.59428-4-philmd@linaro.org> --- include/qemu/atomic128.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/qemu/atomic128.h b/include/qemu/atomic128.h index 448fb6447997..31e5c48d8fa3 100644 --- a/include/qemu/atomic128.h +++ b/include/qemu/atomic128.h @@ -13,6 +13,7 @@ #ifndef QEMU_ATOMIC128_H #define QEMU_ATOMIC128_H +#include "qemu/atomic.h" #include "qemu/int128.h" /* From 6e9f90021178cbf08f841206942946c67b515317 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 7 Mar 2025 10:31:49 -0800 Subject: [PATCH 0689/1179] accel/tcg: Build tcg-runtime.c once Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- accel/tcg/meson.build | 2 +- accel/tcg/tcg-runtime.c | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 81fb25da5cca..411fe28deac9 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -1,5 +1,6 @@ common_ss.add(when: 'CONFIG_TCG', if_true: files( 'cpu-exec-common.c', + 'tcg-runtime.c', )) tcg_specific_ss = ss.source_set() tcg_specific_ss.add(files( @@ -7,7 +8,6 @@ tcg_specific_ss.add(files( 'cpu-exec.c', 'tb-maint.c', 'tcg-runtime-gvec.c', - 'tcg-runtime.c', 'translate-all.c', 'translator.c', )) diff --git a/accel/tcg/tcg-runtime.c b/accel/tcg/tcg-runtime.c index 9fa539ad3d77..fa7ed9739c7b 100644 --- a/accel/tcg/tcg-runtime.c +++ b/accel/tcg/tcg-runtime.c @@ -23,13 +23,9 @@ */ #include "qemu/osdep.h" #include "qemu/host-utils.h" -#include "cpu.h" +#include "exec/cpu-common.h" #include "exec/helper-proto-common.h" -#include "exec/cpu_ldst.h" -#include "exec/exec-all.h" -#include "disas/disas.h" -#include "exec/log.h" -#include "tcg/tcg.h" +#include "accel/tcg/getpc.h" #define HELPER_H "accel/tcg/tcg-runtime.h" #include "exec/helper-info.c.inc" From 9e2080766f037857fc366012aaefd6fead0a75f9 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 7 Mar 2025 10:34:13 -0800 Subject: [PATCH 0690/1179] accel/tcg: Build tcg-runtime-gvec.c once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested-by: Philippe Mathieu-Daudé Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Signed-off-by: Richard Henderson --- accel/tcg/meson.build | 2 +- accel/tcg/tcg-runtime-gvec.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 411fe28deac9..38ff227eb039 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -1,13 +1,13 @@ common_ss.add(when: 'CONFIG_TCG', if_true: files( 'cpu-exec-common.c', 'tcg-runtime.c', + 'tcg-runtime-gvec.c', )) tcg_specific_ss = ss.source_set() tcg_specific_ss.add(files( 'tcg-all.c', 'cpu-exec.c', 'tb-maint.c', - 'tcg-runtime-gvec.c', 'translate-all.c', 'translator.c', )) diff --git a/accel/tcg/tcg-runtime-gvec.c b/accel/tcg/tcg-runtime-gvec.c index afca89baa1cb..ff927c5dd8db 100644 --- a/accel/tcg/tcg-runtime-gvec.c +++ b/accel/tcg/tcg-runtime-gvec.c @@ -19,7 +19,6 @@ #include "qemu/osdep.h" #include "qemu/host-utils.h" -#include "cpu.h" #include "exec/helper-proto-common.h" #include "tcg/tcg-gvec-desc.h" From 816945364f698ae750aa665fce3d121c98e37a6f Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 19:00:51 +0000 Subject: [PATCH 0691/1179] rust: pl011: Allow NULL chardev argument to pl011_create() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's valid for the caller to pass a NULL chardev to pl011_create(); this means "don't set the chardev property on the device", which in turn means "act like there's no chardev". All the chardev frontend APIs (in C, at least) accept a NULL pointer to mean "do nothing". This fixes some failures in 'make check-functional' when Rust support is enabled. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Link: https://lore.kernel.org/r/20250307190051.3274226-1-peter.maydell@linaro.org Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index af93ae8bebe1..f137b49feafc 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -648,10 +648,12 @@ pub unsafe extern "C" fn pl011_create( // SAFETY: The callers promise that they have owned references. // They do not gift them to pl011_create, so use `Owned::from`. let irq = unsafe { Owned::::from(&*irq) }; - let chr = unsafe { Owned::::from(&*chr) }; let dev = PL011State::new(); - dev.prop_set_chr("chardev", &chr); + if !chr.is_null() { + let chr = unsafe { Owned::::from(&*chr) }; + dev.prop_set_chr("chardev", &chr); + } dev.sysbus_realize(); dev.mmio_map(0, addr); dev.connect_irq(0, &irq); From ae139d6e9248526dcfe5d522061910509809a778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Wed, 29 Jan 2025 08:18:16 +0100 Subject: [PATCH 0692/1179] tests/functional: Introduce a new test routine for OpenBMC images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OpenBMC images currently used by QEMU to test the Aspeed machines are rather old. To prepare an update to the latest builds, we need to adjust the console patterns. Introduce a new routine to preserve the current tests. Reviewed-by: Thomas Huth Link: https://lore.kernel.org/qemu-devel/20250129071820.1258133-2-clg@redhat.com Signed-off-by: Cédric Le Goater --- tests/functional/aspeed.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/functional/aspeed.py b/tests/functional/aspeed.py index b52358bb8c61..ea75939e05f9 100644 --- a/tests/functional/aspeed.py +++ b/tests/functional/aspeed.py @@ -23,6 +23,24 @@ def do_test_arm_aspeed(self, machine, image): self.wait_for_console_pattern("ftgmac100 1e660000.ethernet eth0: irq ") self.wait_for_console_pattern("systemd[1]: Set hostname to") + def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', + cpu_id='0x0', soc='AST2500 rev A1'): + hostname = machine.removesuffix('-bmc') + + self.set_machine(machine) + self.vm.set_console() + self.vm.add_args('-drive', f'file={image},if=mtd,format=raw', + '-snapshot') + self.vm.launch() + + self.wait_for_console_pattern(f'U-Boot {uboot}') + self.wait_for_console_pattern('## Loading kernel from FIT Image') + self.wait_for_console_pattern('Starting kernel ...') + self.wait_for_console_pattern(f'Booting Linux on physical CPU {cpu_id}') + self.wait_for_console_pattern(f'ASPEED {soc}') + self.wait_for_console_pattern('/init as init process') + self.wait_for_console_pattern(f'systemd[1]: Hostname set to <{hostname}>.') + def do_test_arm_aspeed_buildroot_start(self, image, cpu_id, pattern='Aspeed EVB'): self.require_netdev('user') self.vm.set_console() From 6664b3e255ed89b93e4311361ae62d05313f2ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Wed, 29 Jan 2025 08:18:17 +0100 Subject: [PATCH 0693/1179] tests/functional: Update OpenBMC image of palmetto machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the new do_test_arm_aspeed_openbmc() routine to run the latest OpenBMC firmware build of the palmetto BMC. Reviewed-by: Thomas Huth Link: https://lore.kernel.org/qemu-devel/20250129071820.1258133-3-clg@redhat.com Signed-off-by: Cédric Le Goater --- tests/functional/test_arm_aspeed_palmetto.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/functional/test_arm_aspeed_palmetto.py b/tests/functional/test_arm_aspeed_palmetto.py index 6588c02aad79..35d832bc98ec 100755 --- a/tests/functional/test_arm_aspeed_palmetto.py +++ b/tests/functional/test_arm_aspeed_palmetto.py @@ -7,18 +7,19 @@ from qemu_test import Asset from aspeed import AspeedTest + class PalmettoMachine(AspeedTest): ASSET_PALMETTO_FLASH = Asset( - ('https://github.com/openbmc/openbmc/releases/download/2.9.0/' - 'obmc-phosphor-image-palmetto.static.mtd'), - '3e13bbbc28e424865dc42f35ad672b10f2e82cdb11846bb28fa625b48beafd0d'); + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/palmetto-bmc/openbmc-20250128071432/obmc-phosphor-image-palmetto-20250128071432.static.mtd', + 'bce7c392eec75c707a91cfc8fad7ca9a69d7e4f10df936930d65c1cb9897ac81'); - def test_arm_ast2400_palmetto_openbmc_v2_9_0(self): + def test_arm_ast2400_palmetto_openbmc(self): image_path = self.ASSET_PALMETTO_FLASH.fetch() - self.do_test_arm_aspeed('palmetto-bmc', image_path) - + self.do_test_arm_aspeed_openbmc('palmetto-bmc', image=image_path, + uboot='2019.04', cpu_id='0x0', + soc='AST2400 rev A1'); if __name__ == '__main__': AspeedTest.main() From 01050d97e1f342590f5f2db4a6d2b557a8244f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Wed, 29 Jan 2025 08:18:18 +0100 Subject: [PATCH 0694/1179] tests/functional: Update OpenBMC image of romulus machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the new do_test_arm_aspeed_openbmc() routine to run the latest OpenBMC firmware build of the romulus BMC. Remove the older routine which is now unused. Reviewed-by: Thomas Huth Link: https://lore.kernel.org/qemu-devel/20250129071820.1258133-4-clg@redhat.com Signed-off-by: Cédric Le Goater --- tests/functional/aspeed.py | 16 ---------------- tests/functional/test_arm_aspeed_romulus.py | 13 +++++++------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/tests/functional/aspeed.py b/tests/functional/aspeed.py index ea75939e05f9..77dc8930fa41 100644 --- a/tests/functional/aspeed.py +++ b/tests/functional/aspeed.py @@ -7,22 +7,6 @@ class AspeedTest(LinuxKernelTest): - def do_test_arm_aspeed(self, machine, image): - self.set_machine(machine) - self.vm.set_console() - self.vm.add_args('-drive', 'file=' + image + ',if=mtd,format=raw', - '-net', 'nic', '-snapshot') - self.vm.launch() - - self.wait_for_console_pattern("U-Boot 2016.07") - self.wait_for_console_pattern("## Loading kernel from FIT Image at 20080000") - self.wait_for_console_pattern("Starting kernel ...") - self.wait_for_console_pattern("Booting Linux on physical CPU 0x0") - self.wait_for_console_pattern( - "aspeed-smc 1e620000.spi: read control register: 203b0641") - self.wait_for_console_pattern("ftgmac100 1e660000.ethernet eth0: irq ") - self.wait_for_console_pattern("systemd[1]: Set hostname to") - def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', cpu_id='0x0', soc='AST2500 rev A1'): hostname = machine.removesuffix('-bmc') diff --git a/tests/functional/test_arm_aspeed_romulus.py b/tests/functional/test_arm_aspeed_romulus.py index 747b616201ce..b97ed951b1f3 100755 --- a/tests/functional/test_arm_aspeed_romulus.py +++ b/tests/functional/test_arm_aspeed_romulus.py @@ -7,18 +7,19 @@ from qemu_test import Asset from aspeed import AspeedTest + class RomulusMachine(AspeedTest): ASSET_ROMULUS_FLASH = Asset( - ('https://github.com/openbmc/openbmc/releases/download/2.9.0/' - 'obmc-phosphor-image-romulus.static.mtd'), - '820341076803f1955bc31e647a512c79f9add4f5233d0697678bab4604c7bb25') + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/romulus-bmc/openbmc-20250128071340/obmc-phosphor-image-romulus-20250128071340.static.mtd', + '6d031376440c82ed9d087d25e9fa76aea75b42f80daa252ec402c0bc3cf6cf5b'); - def test_arm_ast2500_romulus_openbmc_v2_9_0(self): + def test_arm_ast2500_romulus_openbmc(self): image_path = self.ASSET_ROMULUS_FLASH.fetch() - self.do_test_arm_aspeed('romulus-bmc', image_path) - + self.do_test_arm_aspeed_openbmc('romulus-bmc', image=image_path, + uboot='2019.04', cpu_id='0x0', + soc='AST2500 rev A1'); if __name__ == '__main__': AspeedTest.main() From 3058b634f2785454504328e1710b7cab66a5acf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Wed, 29 Jan 2025 08:18:19 +0100 Subject: [PATCH 0695/1179] tests/functional: Introduce a witherspoon machine test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use do_test_arm_aspeed_openbmc() routine to run the latest OpenBMC firmware build of the witherspoon BMC. Reviewed-by: Thomas Huth Link: https://lore.kernel.org/qemu-devel/20250129071820.1258133-5-clg@redhat.com Signed-off-by: Cédric Le Goater --- tests/functional/meson.build | 2 ++ .../functional/test_arm_aspeed_witherspoon.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/functional/test_arm_aspeed_witherspoon.py diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 97c3f4ad4e98..0573f0091dc7 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -22,6 +22,7 @@ test_timeouts = { 'acpi_bits' : 420, 'arm_aspeed_palmetto' : 120, 'arm_aspeed_romulus' : 120, + 'arm_aspeed_witherspoon' : 120, 'arm_aspeed_ast2500' : 720, 'arm_aspeed_ast2600' : 1200, 'arm_aspeed_rainier' : 480, @@ -104,6 +105,7 @@ tests_arm_system_thorough = [ 'arm_aspeed_ast1030', 'arm_aspeed_palmetto', 'arm_aspeed_romulus', + 'arm_aspeed_witherspoon', 'arm_aspeed_ast2500', 'arm_aspeed_ast2600', 'arm_aspeed_rainier', diff --git a/tests/functional/test_arm_aspeed_witherspoon.py b/tests/functional/test_arm_aspeed_witherspoon.py new file mode 100644 index 000000000000..ea1ce89b05cc --- /dev/null +++ b/tests/functional/test_arm_aspeed_witherspoon.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# +# Functional test that boots the ASPEED machines +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from aspeed import AspeedTest + + +class WitherspoonMachine(AspeedTest): + + ASSET_WITHERSPOON_FLASH = Asset( + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/witherspoon-bmc/openbmc-20240618035022/obmc-phosphor-image-witherspoon-20240618035022.ubi.mtd', + '937d9ed449ea6c6cbed983519088a42d0cafe276bcfe4fce07772ca6673f9213'); + + def test_arm_ast2500_witherspoon_openbmc(self): + image_path = self.ASSET_WITHERSPOON_FLASH.fetch() + + self.do_test_arm_aspeed_openbmc('witherspoon-bmc', image=image_path, + uboot='2016.07', cpu_id='0x0', + soc='AST2500 rev A1'); + +if __name__ == '__main__': + AspeedTest.main() From b91a1d31106f042df294410214656608e5b2fe2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Wed, 29 Jan 2025 08:18:20 +0100 Subject: [PATCH 0696/1179] tests/functional: Introduce a bletchley machine test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use do_test_arm_aspeed_openbmc() to run the latest OpenBMC firmware build of the bletchley BMC. Reviewed-by: Patrick Williams Reviewed-by: Thomas Huth Link: https://lore.kernel.org/qemu-devel/20250129071820.1258133-6-clg@redhat.com Signed-off-by: Cédric Le Goater --- tests/functional/meson.build | 2 ++ tests/functional/test_arm_aspeed_bletchley.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/functional/test_arm_aspeed_bletchley.py diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 0573f0091dc7..5dc66c03fcc3 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -25,6 +25,7 @@ test_timeouts = { 'arm_aspeed_witherspoon' : 120, 'arm_aspeed_ast2500' : 720, 'arm_aspeed_ast2600' : 1200, + 'arm_aspeed_bletchley' : 120, 'arm_aspeed_rainier' : 480, 'arm_bpim2u' : 500, 'arm_collie' : 180, @@ -108,6 +109,7 @@ tests_arm_system_thorough = [ 'arm_aspeed_witherspoon', 'arm_aspeed_ast2500', 'arm_aspeed_ast2600', + 'arm_aspeed_bletchley', 'arm_aspeed_rainier', 'arm_bpim2u', 'arm_canona1100', diff --git a/tests/functional/test_arm_aspeed_bletchley.py b/tests/functional/test_arm_aspeed_bletchley.py new file mode 100644 index 000000000000..0da856c5ed4d --- /dev/null +++ b/tests/functional/test_arm_aspeed_bletchley.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# +# Functional test that boots the ASPEED machines +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from aspeed import AspeedTest + + +class BletchleyMachine(AspeedTest): + + ASSET_BLETCHLEY_FLASH = Asset( + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/bletchley-bmc/openbmc-20250128071329/obmc-phosphor-image-bletchley-20250128071329.static.mtd.xz', + 'db21d04d47d7bb2a276f59d308614b4dfb70b9c7c81facbbca40a3977a2d8844'); + + def test_arm_ast2600_bletchley_openbmc(self): + image_path = self.uncompress(self.ASSET_BLETCHLEY_FLASH) + + self.do_test_arm_aspeed_openbmc('bletchley-bmc', image=image_path, + uboot='2019.04', cpu_id='0xf00', + soc='AST2600 rev A3'); + +if __name__ == '__main__': + AspeedTest.main() From 136367e567771b7aef49e734817667950413deba Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 4 Feb 2025 14:09:55 +0800 Subject: [PATCH 0697/1179] aspeed/soc: Support Non-maskable Interrupt for AST2700 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QEMU supports GICv3 Non-maskable Interrupt, adds to support Non-maskable Interrupt for AST2700. Reference: https://github.com/qemu/qemu/commit/b36a32ead Signed-off-by: Jamin Lin Suggested-by: Philippe Mathieu-Daudé Reviewed-by: Philippe Mathieu-Daudé Link: https://lore.kernel.org/qemu-devel/20250204060955.3546022-1-jamin_lin@aspeedtech.com --- hw/arm/aspeed_ast27x0.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 2d0c99f1591c..3e373f966b5f 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -470,6 +470,10 @@ static bool aspeed_soc_ast2700_gic_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); sysbus_connect_irq(gicbusdev, i + 3 * sc->num_cpus, qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + sysbus_connect_irq(gicbusdev, i + 4 * sc->num_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_NMI)); + sysbus_connect_irq(gicbusdev, i + 5 * sc->num_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VINMI)); } return true; From a5b9621024f25b374c270e8f7216e35f911720e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Tue, 18 Feb 2025 08:35:34 +0100 Subject: [PATCH 0698/1179] aspeed: Remove duplicate typename in AspeedSoCClass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SoC type name is stored under AspeedSoCClass which is redundant. Use object_get_typename() instead where needed. Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/qemu-devel/20250218073534.585066-1-clg@redhat.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast10x0.c | 3 +-- hw/arm/aspeed_ast2400.c | 4 +--- hw/arm/aspeed_ast2600.c | 3 +-- hw/arm/aspeed_ast27x0.c | 3 +-- include/hw/arm/aspeed_soc.h | 1 - 5 files changed, 4 insertions(+), 10 deletions(-) diff --git a/hw/arm/aspeed_ast10x0.c b/hw/arm/aspeed_ast10x0.c index e76c7100a1d2..ec329f4991c9 100644 --- a/hw/arm/aspeed_ast10x0.c +++ b/hw/arm/aspeed_ast10x0.c @@ -116,7 +116,7 @@ static void aspeed_soc_ast1030_init(Object *obj) char typename[64]; int i; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -428,7 +428,6 @@ static void aspeed_soc_ast1030_class_init(ObjectClass *klass, void *data) dc->user_creatable = false; dc->realize = aspeed_soc_ast1030_realize; - sc->name = "ast1030-a1"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST1030_A1_SILICON_REV; sc->sram_size = 0xc0000; diff --git a/hw/arm/aspeed_ast2400.c b/hw/arm/aspeed_ast2400.c index 8784b6e79305..0158f6e9c24e 100644 --- a/hw/arm/aspeed_ast2400.c +++ b/hw/arm/aspeed_ast2400.c @@ -151,7 +151,7 @@ static void aspeed_ast2400_soc_init(Object *obj) char socname[8]; char typename[64]; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -515,7 +515,6 @@ static void aspeed_soc_ast2400_class_init(ObjectClass *oc, void *data) /* Reason: Uses serial_hds and nd_table in realize() directly */ dc->user_creatable = false; - sc->name = "ast2400-a1"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2400_A1_SILICON_REV; sc->sram_size = 0x8000; @@ -544,7 +543,6 @@ static void aspeed_soc_ast2500_class_init(ObjectClass *oc, void *data) /* Reason: Uses serial_hds and nd_table in realize() directly */ dc->user_creatable = false; - sc->name = "ast2500-a1"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2500_A1_SILICON_REV; sc->sram_size = 0x9000; diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c index 07210483bb29..1f994ba26c65 100644 --- a/hw/arm/aspeed_ast2600.c +++ b/hw/arm/aspeed_ast2600.c @@ -157,7 +157,7 @@ static void aspeed_soc_ast2600_init(Object *obj) char socname[8]; char typename[64]; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -666,7 +666,6 @@ static void aspeed_soc_ast2600_class_init(ObjectClass *oc, void *data) /* Reason: The Aspeed SoC can only be instantiated from a board */ dc->user_creatable = false; - sc->name = "ast2600-a3"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2600_A3_SILICON_REV; sc->sram_size = 0x16400; diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 3e373f966b5f..6506bdfdff68 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -316,7 +316,7 @@ static void aspeed_soc_ast2700_init(Object *obj) char socname[8]; char typename[64]; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -757,7 +757,6 @@ static void aspeed_soc_ast2700_class_init(ObjectClass *oc, void *data) dc->user_creatable = false; dc->realize = aspeed_soc_ast2700_realize; - sc->name = "ast2700-a0"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2700_A0_SILICON_REV; sc->sram_size = 0x20000; diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index 689f52dae8fb..4a8881ca8b57 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -148,7 +148,6 @@ OBJECT_DECLARE_SIMPLE_TYPE(Aspeed10x0SoCState, ASPEED10X0_SOC) struct AspeedSoCClass { DeviceClass parent_class; - const char *name; /** valid_cpu_types: NULL terminated array of a single CPU type. */ const char * const *valid_cpu_types; uint32_t silicon_rev; From cde8182b8772fc35dc44cd688166c59e7b9c9530 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 25 Feb 2025 15:56:18 +0800 Subject: [PATCH 0699/1179] hw/misc/aspeed_hace: Fix coding style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix coding style issues from checkpatch.pl. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250225075622.305515-2-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/misc/aspeed_hace.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index e3f7df2e862a..18b85081c7c5 100644 --- a/hw/misc/aspeed_hace.c +++ b/hw/misc/aspeed_hace.c @@ -75,9 +75,12 @@ static const struct { { HASH_ALGO_SHA1, QCRYPTO_HASH_ALGO_SHA1 }, { HASH_ALGO_SHA224, QCRYPTO_HASH_ALGO_SHA224 }, { HASH_ALGO_SHA256, QCRYPTO_HASH_ALGO_SHA256 }, - { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA512, QCRYPTO_HASH_ALGO_SHA512 }, - { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA384, QCRYPTO_HASH_ALGO_SHA384 }, - { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA256, QCRYPTO_HASH_ALGO_SHA256 }, + { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA512, + QCRYPTO_HASH_ALGO_SHA512 }, + { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA384, + QCRYPTO_HASH_ALGO_SHA384 }, + { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA256, + QCRYPTO_HASH_ALGO_SHA256 }, }; static int hash_algo_lookup(uint32_t reg) @@ -201,7 +204,8 @@ static void do_hash_operation(AspeedHACEState *s, int algo, bool sg_mode, haddr = address_space_map(&s->dram_as, addr, &plen, false, MEMTXATTRS_UNSPECIFIED); if (haddr == NULL) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto failed\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: qcrypto failed\n", __func__); return; } iov[i].iov_base = haddr; From 393c908afb39df5ca44b67edf3acb16bb8835cf7 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 25 Feb 2025 15:56:19 +0800 Subject: [PATCH 0700/1179] hw/misc/aspeed_hace: Add AST2700 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a new ast2700 class to support AST2700. Signed-off-by: Jamin Lin Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/qemu-devel/20250225075622.305515-3-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/misc/aspeed_hace.c | 20 ++++++++++++++++++++ include/hw/misc/aspeed_hace.h | 1 + 2 files changed, 21 insertions(+) diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index 18b85081c7c5..86422cb3be70 100644 --- a/hw/misc/aspeed_hace.c +++ b/hw/misc/aspeed_hace.c @@ -552,12 +552,32 @@ static const TypeInfo aspeed_ast1030_hace_info = { .class_init = aspeed_ast1030_hace_class_init, }; +static void aspeed_ast2700_hace_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + AspeedHACEClass *ahc = ASPEED_HACE_CLASS(klass); + + dc->desc = "AST2700 Hash and Crypto Engine"; + + ahc->src_mask = 0x7FFFFFFF; + ahc->dest_mask = 0x7FFFFFF8; + ahc->key_mask = 0x7FFFFFF8; + ahc->hash_mask = 0x00147FFF; +} + +static const TypeInfo aspeed_ast2700_hace_info = { + .name = TYPE_ASPEED_AST2700_HACE, + .parent = TYPE_ASPEED_HACE, + .class_init = aspeed_ast2700_hace_class_init, +}; + static void aspeed_hace_register_types(void) { type_register_static(&aspeed_ast2400_hace_info); type_register_static(&aspeed_ast2500_hace_info); type_register_static(&aspeed_ast2600_hace_info); type_register_static(&aspeed_ast1030_hace_info); + type_register_static(&aspeed_ast2700_hace_info); type_register_static(&aspeed_hace_info); } diff --git a/include/hw/misc/aspeed_hace.h b/include/hw/misc/aspeed_hace.h index 4af99191955a..d13fd3da078c 100644 --- a/include/hw/misc/aspeed_hace.h +++ b/include/hw/misc/aspeed_hace.h @@ -18,6 +18,7 @@ #define TYPE_ASPEED_AST2500_HACE TYPE_ASPEED_HACE "-ast2500" #define TYPE_ASPEED_AST2600_HACE TYPE_ASPEED_HACE "-ast2600" #define TYPE_ASPEED_AST1030_HACE TYPE_ASPEED_HACE "-ast1030" +#define TYPE_ASPEED_AST2700_HACE TYPE_ASPEED_HACE "-ast2700" OBJECT_DECLARE_TYPE(AspeedHACEState, AspeedHACEClass, ASPEED_HACE) From 7b5d6b47a6c99e3e3c843000c821e6549c8feb01 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 25 Feb 2025 15:56:20 +0800 Subject: [PATCH 0701/1179] hw/arm/aspeed_ast27x0: Add HACE support for AST2700 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HACE controller between AST2600 and AST2700 are almost identical. The HACE controller registers base address starts at 0x1207_0000 and its alarm interrupt is connected to GICINT4. Signed-off-by: Jamin Lin Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/qemu-devel/20250225075622.305515-4-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 6506bdfdff68..56e43d45ad22 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -67,6 +67,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = { [ASPEED_DEV_RTC] = 0x12C0F000, [ASPEED_DEV_SDHCI] = 0x14080000, [ASPEED_DEV_TIMER1] = 0x12C10000, + [ASPEED_DEV_HACE] = 0x12070000, }; #define AST2700_MAX_IRQ 256 @@ -401,6 +402,9 @@ static void aspeed_soc_ast2700_init(Object *obj) snprintf(typename, sizeof(typename), "aspeed.timer-%s", socname); object_initialize_child(obj, "timerctrl", &s->timerctrl, typename); + + snprintf(typename, sizeof(typename), "aspeed.hace-%s", socname); + object_initialize_child(obj, "hace", &s->hace, typename); } /* @@ -737,6 +741,17 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->timerctrl), i, irq); } + /* HACE */ + object_property_set_link(OBJECT(&s->hace), "dram", OBJECT(s->dram_mr), + &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->hace), errp)) { + return; + } + aspeed_mmio_map(s, SYS_BUS_DEVICE(&s->hace), 0, + sc->memmap[ASPEED_DEV_HACE]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->hace), 0, + aspeed_soc_get_irq(s, ASPEED_DEV_HACE)); + create_unimplemented_device("ast2700.dpmcu", 0x11000000, 0x40000); create_unimplemented_device("ast2700.iomem0", 0x12000000, 0x01000000); create_unimplemented_device("ast2700.iomem1", 0x14000000, 0x01000000); From 8e002a693198865632f6bed072c5473a6bb9cf45 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 25 Feb 2025 15:56:21 +0800 Subject: [PATCH 0702/1179] hw/misc/aspeed_hace: Fix boot issue in the Crypto Manager Self Test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, it does not support the CRYPT command. Instead, it only sends an interrupt to notify the firmware that the crypt command has completed. It is a temporary workaround to resolve the boot issue in the Crypto Manager Self Test. Introduce a new "use_crypt_workaround" class attribute and set it to true in the AST2700 HACE model to enable this workaround by default for AST2700. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250225075622.305515-5-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/misc/aspeed_hace.c | 23 +++++++++++++++++++++++ include/hw/misc/aspeed_hace.h | 1 + 2 files changed, 24 insertions(+) diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index 86422cb3be70..32a5dbded3c6 100644 --- a/hw/misc/aspeed_hace.c +++ b/hw/misc/aspeed_hace.c @@ -59,6 +59,7 @@ /* Other cmd bits */ #define HASH_IRQ_EN BIT(9) #define HASH_SG_EN BIT(18) +#define CRYPT_IRQ_EN BIT(12) /* Scatter-gather data list */ #define SG_LIST_LEN_SIZE 4 #define SG_LIST_LEN_MASK 0x0FFFFFFF @@ -343,6 +344,15 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data, qemu_irq_lower(s->irq); } } + if (ahc->raise_crypt_interrupt_workaround) { + if (data & CRYPT_IRQ) { + data &= ~CRYPT_IRQ; + + if (s->regs[addr] & CRYPT_IRQ) { + qemu_irq_lower(s->irq); + } + } + } break; case R_HASH_SRC: data &= ahc->src_mask; @@ -388,6 +398,12 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data, case R_CRYPT_CMD: qemu_log_mask(LOG_UNIMP, "%s: Crypt commands not implemented\n", __func__); + if (ahc->raise_crypt_interrupt_workaround) { + s->regs[R_STATUS] |= CRYPT_IRQ; + if (data & CRYPT_IRQ_EN) { + qemu_irq_raise(s->irq); + } + } break; default: break; @@ -563,6 +579,13 @@ static void aspeed_ast2700_hace_class_init(ObjectClass *klass, void *data) ahc->dest_mask = 0x7FFFFFF8; ahc->key_mask = 0x7FFFFFF8; ahc->hash_mask = 0x00147FFF; + + /* + * Currently, it does not support the CRYPT command. Instead, it only + * sends an interrupt to notify the firmware that the crypt command + * has completed. It is a temporary workaround. + */ + ahc->raise_crypt_interrupt_workaround = true; } static const TypeInfo aspeed_ast2700_hace_info = { diff --git a/include/hw/misc/aspeed_hace.h b/include/hw/misc/aspeed_hace.h index d13fd3da078c..5d4aa19cfecb 100644 --- a/include/hw/misc/aspeed_hace.h +++ b/include/hw/misc/aspeed_hace.h @@ -50,6 +50,7 @@ struct AspeedHACEClass { uint32_t dest_mask; uint32_t key_mask; uint32_t hash_mask; + bool raise_crypt_interrupt_workaround; }; #endif /* ASPEED_HACE_H */ From 2d082fea485ee455a70ed3e963cdf9a70f34858a Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 4 Mar 2025 14:47:03 +0800 Subject: [PATCH 0703/1179] hw/misc/aspeed_scu: Skipping dram_init in u-boot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting BIT6 in VGA0 SCRATCH register will indicate that the ddr traning is done, therefore skipping the u-boot-spl dram_init() process. Signed-off-by: Jamin Lin Signed-off-by: Troy Lee Reviewed-by: Cédric Le Goater Tested-by: Nabih Estefan Link: https://lore.kernel.org/qemu-devel/20250304064710.2128993-2-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/misc/aspeed_scu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index bac1441b061a..50f74fbabd6e 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -157,6 +157,7 @@ #define AST2700_SCU_FREQ_CNTR TO_REG(0x3b0) #define AST2700_SCU_CPU_SCRATCH_0 TO_REG(0x780) #define AST2700_SCU_CPU_SCRATCH_1 TO_REG(0x784) +#define AST2700_SCU_VGA_SCRATCH_0 TO_REG(0x900) #define AST2700_SCUIO_CLK_STOP_CTL_1 TO_REG(0x240) #define AST2700_SCUIO_CLK_STOP_CLR_1 TO_REG(0x244) @@ -930,6 +931,7 @@ static const uint32_t ast2700_a0_resets[ASPEED_AST2700_SCU_NR_REGS] = { [AST2700_SCU_FREQ_CNTR] = 0x000375eb, [AST2700_SCU_CPU_SCRATCH_0] = 0x00000000, [AST2700_SCU_CPU_SCRATCH_1] = 0x00000004, + [AST2700_SCU_VGA_SCRATCH_0] = 0x00000040, }; static void aspeed_ast2700_scu_reset(DeviceState *dev) From 801e0dad6ad4c4e078f907d825113251e374f6b2 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 4 Mar 2025 14:47:04 +0800 Subject: [PATCH 0704/1179] hw/misc/aspeed_scu: Fix the revision ID cannot be set in the SOC layer for AST2700 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the design of the AST2600, it has a Silicon Revision ID Register, specifically SCU004 and SCU014, to set the Revision ID for the AST2600. For the AST2600 A3, SCU004 is set to 0x05030303 and SCU014 is set to 0x05030303. In the "aspeed_ast2600_scu_reset" function, the hardcoded value "AST2600_A3_SILICON_REV" is set in SCU004, and "s->silicon_rev" is set in SCU014. The value of "s->silicon_rev" is set by the SOC layer via the "silicon-rev" property. However, the design of the AST2700 is different. There are two SCU controllers: SCU0 (CPU Die) and SCU1 (IO Die). In the AST2700, the firmware reads the SCU Silicon Revision ID register (SCU0_000) and the SCUIO Silicon Revision ID register (SCU1_000), combining them into a single 64-bit value. The upper 32 bits represent the SCUIO, while the lower 32 bits correspond to the SCU. For example, the AST2700-A1 revision is represented as 0x0601010306010103. SCUIO_000 occupies bits [63:32] with a value of 0x06010103 and SCU_000 occupies bits [31:0] with a value of 0x06010103. Reference: https://github.com/AspeedTech-BMC/u-boot/blob/aspeed-master-v2023.10/arch/arm/mach-aspeed/ast2700/cpu-info.c Signed-off-by: Jamin Lin Tested-by: Nabih Estefan Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250304064710.2128993-3-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/misc/aspeed_scu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index 50f74fbabd6e..545d00474968 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -910,7 +910,6 @@ static const MemoryRegionOps aspeed_ast2700_scu_ops = { }; static const uint32_t ast2700_a0_resets[ASPEED_AST2700_SCU_NR_REGS] = { - [AST2700_SILICON_REV] = AST2700_A0_SILICON_REV, [AST2700_HW_STRAP1] = 0x00000800, [AST2700_HW_STRAP1_CLR] = 0xFFF0FFF0, [AST2700_HW_STRAP1_LOCK] = 0x00000FFF, @@ -940,6 +939,7 @@ static void aspeed_ast2700_scu_reset(DeviceState *dev) AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev); memcpy(s->regs, asc->resets, asc->nr_regs * 4); + s->regs[AST2700_SILICON_REV] = s->silicon_rev; } static void aspeed_2700_scu_class_init(ObjectClass *klass, void *data) @@ -1032,7 +1032,6 @@ static const MemoryRegionOps aspeed_ast2700_scuio_ops = { }; static const uint32_t ast2700_a0_resets_io[ASPEED_AST2700_SCU_NR_REGS] = { - [AST2700_SILICON_REV] = 0x06000003, [AST2700_HW_STRAP1] = 0x00000504, [AST2700_HW_STRAP1_CLR] = 0xFFF0FFF0, [AST2700_HW_STRAP1_LOCK] = 0x00000FFF, From 172329c6b281e11feb3d6e60df6754008e23a089 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 4 Mar 2025 14:47:05 +0800 Subject: [PATCH 0705/1179] hw/arm/aspeed Update HW Strap Default Values for AST2700 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate HW Strap Registers for SCU and SCUIO. AST2700_EVB_HW_STRAP1 is used for the SCU (CPU Die) hw-strap1. AST2700_EVB_HW_STRAP2 is used for the SCUIO (IO Die) hw-strap1. Additionally, both default values are updated based on the dump from the EVB. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Tested-by: Nabih Estefan Link: https://lore.kernel.org/qemu-devel/20250304064710.2128993-4-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index 98bf071139b9..c6c18596d62e 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -181,8 +181,10 @@ struct AspeedMachineState { #ifdef TARGET_AARCH64 /* AST2700 evb hardware value */ -#define AST2700_EVB_HW_STRAP1 0x000000C0 -#define AST2700_EVB_HW_STRAP2 0x00000003 +/* SCU HW Strap1 */ +#define AST2700_EVB_HW_STRAP1 0x00000800 +/* SCUIO HW Strap1 */ +#define AST2700_EVB_HW_STRAP2 0x00000700 #endif /* Rainier hardware value: (QEMU prototype) */ From 720e850f83ef6497d9992c43716fb5d7cc9a2ad2 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 4 Mar 2025 14:47:06 +0800 Subject: [PATCH 0706/1179] hw/misc/aspeed_scu: Fix the hw-strap1 cannot be set in the SOC layer for AST2700 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is one hw_strap1 register in the SCU (CPU DIE) and another hw_strap1 register in the SCUIO (IO DIE). In the "ast2700_a0_resets" function, the hardcoded value "0x00000800" is set in SCU hw-strap1 (CPU DIE), and in "ast2700_a0_resets_io" the hardcoded value "0x00000504" is set in SCUIO hw-strap1 (IO DIE). Both values cannot be set via the SOC layer. The value of "s->hw_strap1" is set by the SOC layer via the "hw-strap1" property. Update the "aspeed_ast2700_scu_reset" function to set the value of "s->hw_strap1" in both the SCU and SCUIO hw-strap1 registers. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Tested-by: Nabih Estefan Link: https://lore.kernel.org/qemu-devel/20250304064710.2128993-5-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/misc/aspeed_scu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index 545d00474968..0581c744f112 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -910,7 +910,6 @@ static const MemoryRegionOps aspeed_ast2700_scu_ops = { }; static const uint32_t ast2700_a0_resets[ASPEED_AST2700_SCU_NR_REGS] = { - [AST2700_HW_STRAP1] = 0x00000800, [AST2700_HW_STRAP1_CLR] = 0xFFF0FFF0, [AST2700_HW_STRAP1_LOCK] = 0x00000FFF, [AST2700_HW_STRAP1_SEC1] = 0x000000FF, @@ -940,6 +939,7 @@ static void aspeed_ast2700_scu_reset(DeviceState *dev) memcpy(s->regs, asc->resets, asc->nr_regs * 4); s->regs[AST2700_SILICON_REV] = s->silicon_rev; + s->regs[AST2700_HW_STRAP1] = s->hw_strap1; } static void aspeed_2700_scu_class_init(ObjectClass *klass, void *data) @@ -1032,7 +1032,6 @@ static const MemoryRegionOps aspeed_ast2700_scuio_ops = { }; static const uint32_t ast2700_a0_resets_io[ASPEED_AST2700_SCU_NR_REGS] = { - [AST2700_HW_STRAP1] = 0x00000504, [AST2700_HW_STRAP1_CLR] = 0xFFF0FFF0, [AST2700_HW_STRAP1_LOCK] = 0x00000FFF, [AST2700_HW_STRAP1_SEC1] = 0x000000FF, From b741ab395b398058198ba3f055d9d8c4d631122f Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 4 Mar 2025 14:47:07 +0800 Subject: [PATCH 0707/1179] hw/arm/aspeed_ast27x0.c Separate HW Strap Registers for SCU and SCUIO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is one hw-strap1 register in the SCU (CPU DIE) and another hw-strap1 register in the SCUIO (IO DIE). The values of these two registers should not be the same. To reuse the current design of hw-strap, hw-strap1 is assigned to the SCU and sets the value in the SCU hw-strap1 register, while hw-strap2 is assigned to the SCUIO and sets the value in the SCUIO hw-strap1 register. Signed-off-by: Jamin Lin Tested-by: Nabih Estefan Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250304064710.2128993-6-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 56e43d45ad22..4cee6ddc0b57 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -333,14 +333,21 @@ static void aspeed_soc_ast2700_init(Object *obj) sc->silicon_rev); object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu), "hw-strap1"); - object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scu), - "hw-strap2"); object_property_add_alias(obj, "hw-prot-key", OBJECT(&s->scu), "hw-prot-key"); object_initialize_child(obj, "scuio", &s->scuio, TYPE_ASPEED_2700_SCUIO); qdev_prop_set_uint32(DEVICE(&s->scuio), "silicon-rev", sc->silicon_rev); + /* + * There is one hw-strap1 register in the SCU (CPU DIE) and another + * hw-strap1 register in the SCUIO (IO DIE). To reuse the current design + * of hw-strap, hw-strap1 is assigned to the SCU and sets the value in the + * SCU hw-strap1 register, while hw-strap2 is assigned to the SCUIO and + * sets the value in the SCUIO hw-strap1 register. + */ + object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scuio), + "hw-strap1"); snprintf(typename, sizeof(typename), "aspeed.fmc-%s", socname); object_initialize_child(obj, "fmc", &s->fmc, typename); From 8dd163f915cf26277fa175476c0af6898b8fd864 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Tue, 4 Mar 2025 14:47:08 +0800 Subject: [PATCH 0708/1179] hw/arm/aspeed_ast27x0.c Fix boot issue for AST2700 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, ASPEED_DEV_SPI_BOOT is set to "0x400000000", which is the DRAM start address, and the QEMU loader is used to load the U-Boot binary into this address. However, if users want to install FMC flash contents as a boot ROM, the DRAM address 0x400000000 would be overwritten with Boot ROM data. This causes the AST2700 to fail to boot because the U-Boot data becomes incorrect. To fix this, change the ASPEED_DEV_SPI_BOOT address to "0x100000000", which is the FMC0 memory-mapped start address in the AST2700. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Tested-by: Nabih Estefan Link: https://lore.kernel.org/qemu-devel/20250304064710.2128993-7-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 4cee6ddc0b57..f14d2ea1753d 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -24,7 +24,7 @@ #include "qemu/log.h" static const hwaddr aspeed_soc_ast2700_memmap[] = { - [ASPEED_DEV_SPI_BOOT] = 0x400000000, + [ASPEED_DEV_SPI_BOOT] = 0x100000000, [ASPEED_DEV_SRAM] = 0x10000000, [ASPEED_DEV_SDMC] = 0x12C00000, [ASPEED_DEV_SCU] = 0x12C02000, From c5728c3488b936e1cef5d4d83d26df853f8fac6b Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:10 +0800 Subject: [PATCH 0709/1179] hw/intc/aspeed: Support setting different memory size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the AST2700 datasheet, the INTC(CPU DIE) controller has 16KB (0x4000) of register space, and the INTCIO (I/O DIE) controller has 1KB (0x400) of register space. Introduced a new class attribute "mem_size" to set different memory sizes for the INTC models in AST2700. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-2-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 9 ++++++++- include/hw/intc/aspeed_intc.h | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 126b711b9430..033b574c1e24 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -302,10 +302,16 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); int i; + memory_region_init(&s->iomem_container, OBJECT(s), + TYPE_ASPEED_INTC ".container", aic->mem_size); + + sysbus_init_mmio(sbd, &s->iomem_container); + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_intc_ops, s, TYPE_ASPEED_INTC ".regs", ASPEED_INTC_NR_REGS << 2); - sysbus_init_mmio(sbd, &s->iomem); + memory_region_add_subregion(&s->iomem_container, 0x0, &s->iomem); + qdev_init_gpio_in(dev, aspeed_intc_set_irq, aic->num_ints); for (i = 0; i < aic->num_ints; i++) { @@ -344,6 +350,7 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) dc->desc = "ASPEED 2700 INTC Controller"; aic->num_lines = 32; aic->num_ints = 9; + aic->mem_size = 0x4000; } static const TypeInfo aspeed_2700_intc_info = { diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 18cb43476cd7..03324f05ab58 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -25,6 +25,8 @@ struct AspeedINTCState { /*< public >*/ MemoryRegion iomem; + MemoryRegion iomem_container; + uint32_t regs[ASPEED_INTC_NR_REGS]; OrIRQState orgates[ASPEED_INTC_NR_INTS]; qemu_irq output_pins[ASPEED_INTC_NR_INTS]; @@ -39,6 +41,7 @@ struct AspeedINTCClass { uint32_t num_lines; uint32_t num_ints; + uint64_t mem_size; }; #endif /* ASPEED_INTC_H */ From 0cffaace0565b68a354c67f147bf8f0f438726e1 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:11 +0800 Subject: [PATCH 0710/1179] hw/intc/aspeed: Rename status_addr and addr to status_reg and reg for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the variables "status_addr" to "status_reg" and "addr" to "reg" because they are used as register index. This change makes the code more appropriate and improves readability. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-3-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 033b574c1e24..465f41e4fd35 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -60,7 +60,7 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) { AspeedINTCState *s = (AspeedINTCState *)opaque; AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); - uint32_t status_addr = GICINT_STATUS_BASE + ((0x100 * irq) >> 2); + uint32_t status_reg = GICINT_STATUS_BASE + ((0x100 * irq) >> 2); uint32_t select = 0; uint32_t enable; int i; @@ -92,7 +92,7 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) trace_aspeed_intc_select(select); - if (s->mask[irq] || s->regs[status_addr]) { + if (s->mask[irq] || s->regs[status_reg]) { /* * a. mask is not 0 means in ISR mode * sources interrupt routine are executing. @@ -108,8 +108,8 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) * notify firmware which source interrupt are coming * by setting status register */ - s->regs[status_addr] = select; - trace_aspeed_intc_trigger_irq(irq, s->regs[status_addr]); + s->regs[status_reg] = select; + trace_aspeed_intc_trigger_irq(irq, s->regs[status_reg]); aspeed_intc_update(s, irq, 1); } } @@ -117,17 +117,17 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) static uint64_t aspeed_intc_read(void *opaque, hwaddr offset, unsigned int size) { AspeedINTCState *s = ASPEED_INTC(opaque); - uint32_t addr = offset >> 2; + uint32_t reg = offset >> 2; uint32_t value = 0; - if (addr >= ASPEED_INTC_NR_REGS) { + if (reg >= ASPEED_INTC_NR_REGS) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", __func__, offset); return 0; } - value = s->regs[addr]; + value = s->regs[reg]; trace_aspeed_intc_read(offset, size, value); return value; @@ -138,12 +138,12 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, { AspeedINTCState *s = ASPEED_INTC(opaque); AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); - uint32_t addr = offset >> 2; + uint32_t reg = offset >> 2; uint32_t old_enable; uint32_t change; uint32_t irq; - if (addr >= ASPEED_INTC_NR_REGS) { + if (reg >= ASPEED_INTC_NR_REGS) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", __func__, offset); @@ -152,7 +152,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, trace_aspeed_intc_write(offset, size, data); - switch (addr) { + switch (reg) { case R_GICINT128_EN: case R_GICINT129_EN: case R_GICINT130_EN: @@ -177,7 +177,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, /* disable all source interrupt */ if (!data && !s->enable[irq]) { - s->regs[addr] = data; + s->regs[reg] = data; return; } @@ -187,12 +187,12 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, /* enable new source interrupt */ if (old_enable != s->enable[irq]) { trace_aspeed_intc_enable(s->enable[irq]); - s->regs[addr] = data; + s->regs[reg] = data; return; } /* mask and unmask source interrupt */ - change = s->regs[addr] ^ data; + change = s->regs[reg] ^ data; if (change & data) { s->mask[irq] &= ~change; trace_aspeed_intc_unmask(change, s->mask[irq]); @@ -200,7 +200,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, s->mask[irq] |= change; trace_aspeed_intc_mask(change, s->mask[irq]); } - s->regs[addr] = data; + s->regs[reg] = data; break; case R_GICINT128_STATUS: case R_GICINT129_STATUS: @@ -220,7 +220,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, } /* clear status */ - s->regs[addr] &= ~data; + s->regs[reg] &= ~data; /* * These status registers are used for notify sources ISR are executed. @@ -233,7 +233,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, } /* All source ISR execution are done */ - if (!s->regs[addr]) { + if (!s->regs[reg]) { trace_aspeed_intc_all_isr_done(irq); if (s->pending[irq]) { /* @@ -241,9 +241,9 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, * notify firmware which source interrupt are pending * by setting status register */ - s->regs[addr] = s->pending[irq]; + s->regs[reg] = s->pending[irq]; s->pending[irq] = 0; - trace_aspeed_intc_trigger_irq(irq, s->regs[addr]); + trace_aspeed_intc_trigger_irq(irq, s->regs[reg]); aspeed_intc_update(s, irq, 1); } else { /* clear irq */ @@ -253,7 +253,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, } break; default: - s->regs[addr] = data; + s->regs[reg] = data; break; } From 563afea0aebd15eac74b89467204f4b76b2ee6fa Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:12 +0800 Subject: [PATCH 0711/1179] hw/intc/aspeed: Introduce dynamic allocation for regs array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the size of the "regs" array is 0x2000, which is too large. To save code size and avoid mapping large unused gaps, will update it to only map the useful set of registers. This update will support multiple sub-regions with different sizes. To address the redundant size issue, replace the static "regs" array with a dynamically allocated "regs" memory. Introduce a new "aspeed_intc_unrealize" function to free the allocated "regs" memory. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-4-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 12 +++++++++++- include/hw/intc/aspeed_intc.h | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 465f41e4fd35..558901570f82 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -289,7 +289,7 @@ static void aspeed_intc_reset(DeviceState *dev) { AspeedINTCState *s = ASPEED_INTC(dev); - memset(s->regs, 0, sizeof(s->regs)); + memset(s->regs, 0, ASPEED_INTC_NR_REGS << 2); memset(s->enable, 0, sizeof(s->enable)); memset(s->mask, 0, sizeof(s->mask)); memset(s->pending, 0, sizeof(s->pending)); @@ -307,6 +307,7 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) sysbus_init_mmio(sbd, &s->iomem_container); + s->regs = g_new(uint32_t, ASPEED_INTC_NR_REGS); memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_intc_ops, s, TYPE_ASPEED_INTC ".regs", ASPEED_INTC_NR_REGS << 2); @@ -322,12 +323,21 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) } } +static void aspeed_intc_unrealize(DeviceState *dev) +{ + AspeedINTCState *s = ASPEED_INTC(dev); + + g_free(s->regs); + s->regs = NULL; +} + static void aspeed_intc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->desc = "ASPEED INTC Controller"; dc->realize = aspeed_intc_realize; + dc->unrealize = aspeed_intc_unrealize; device_class_set_legacy_reset(dc, aspeed_intc_reset); dc->vmsd = NULL; } diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 03324f05ab58..47ea0520b5d8 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -27,7 +27,7 @@ struct AspeedINTCState { MemoryRegion iomem; MemoryRegion iomem_container; - uint32_t regs[ASPEED_INTC_NR_REGS]; + uint32_t *regs; OrIRQState orgates[ASPEED_INTC_NR_INTS]; qemu_irq output_pins[ASPEED_INTC_NR_INTS]; From b008465d655ff3ff314fe1ef81031293b582ebaf Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:13 +0800 Subject: [PATCH 0712/1179] hw/intc/aspeed: Support setting different register size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the size of the regs array is 0x2000, which is too large. So far, it only use GICINT128 - GICINT134, and the offsets from 0 to 0x1000 are unused. To save code size, introduce a new class attribute "reg_size" to set the different register sizes for the INTC models in AST2700 and add a regs sub-region in the memory container. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-5-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 22 +++++----------------- include/hw/intc/aspeed_intc.h | 2 +- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 558901570f82..134922e46fdc 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -120,13 +120,6 @@ static uint64_t aspeed_intc_read(void *opaque, hwaddr offset, unsigned int size) uint32_t reg = offset >> 2; uint32_t value = 0; - if (reg >= ASPEED_INTC_NR_REGS) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", - __func__, offset); - return 0; - } - value = s->regs[reg]; trace_aspeed_intc_read(offset, size, value); @@ -143,13 +136,6 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, uint32_t change; uint32_t irq; - if (reg >= ASPEED_INTC_NR_REGS) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", - __func__, offset); - return; - } - trace_aspeed_intc_write(offset, size, data); switch (reg) { @@ -288,8 +274,9 @@ static void aspeed_intc_instance_init(Object *obj) static void aspeed_intc_reset(DeviceState *dev) { AspeedINTCState *s = ASPEED_INTC(dev); + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); - memset(s->regs, 0, ASPEED_INTC_NR_REGS << 2); + memset(s->regs, 0, aic->nr_regs << 2); memset(s->enable, 0, sizeof(s->enable)); memset(s->mask, 0, sizeof(s->mask)); memset(s->pending, 0, sizeof(s->pending)); @@ -307,9 +294,9 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) sysbus_init_mmio(sbd, &s->iomem_container); - s->regs = g_new(uint32_t, ASPEED_INTC_NR_REGS); + s->regs = g_new(uint32_t, aic->nr_regs); memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_intc_ops, s, - TYPE_ASPEED_INTC ".regs", ASPEED_INTC_NR_REGS << 2); + TYPE_ASPEED_INTC ".regs", aic->nr_regs << 2); memory_region_add_subregion(&s->iomem_container, 0x0, &s->iomem); @@ -361,6 +348,7 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) aic->num_lines = 32; aic->num_ints = 9; aic->mem_size = 0x4000; + aic->nr_regs = 0x2000 >> 2; } static const TypeInfo aspeed_2700_intc_info = { diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 47ea0520b5d8..ec4936b3f486 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -16,7 +16,6 @@ #define TYPE_ASPEED_2700_INTC TYPE_ASPEED_INTC "-ast2700" OBJECT_DECLARE_TYPE(AspeedINTCState, AspeedINTCClass, ASPEED_INTC) -#define ASPEED_INTC_NR_REGS (0x2000 >> 2) #define ASPEED_INTC_NR_INTS 9 struct AspeedINTCState { @@ -42,6 +41,7 @@ struct AspeedINTCClass { uint32_t num_lines; uint32_t num_ints; uint64_t mem_size; + uint64_t nr_regs; }; #endif /* ASPEED_INTC_H */ From 7ffee511fcf1487e016ae1d11c5e191557a8b804 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:14 +0800 Subject: [PATCH 0713/1179] hw/intc/aspeed: Reduce regs array size by adding a register sub-region MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the size of the "regs" array is 0x2000, which is too large. So far, it only uses "GICINT128 to `GICINT134", and the offsets from 0 to 0x1000 are unused. To save code size and avoid mapping large unused gaps, update to only map the useful set of registers: INTC register [0x1000 – 0x1804] Update "reg_size" to 0x808. Introduce a new class attribute "reg_offset" to set the start offset of a "INTC" sub-region. Set the "reg_offset" to 0x1000 for INTC registers. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-6-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 50 ++++++++++++++++++++--------------- include/hw/intc/aspeed_intc.h | 1 + 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 134922e46fdc..d684b4bb4fec 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -14,25 +14,31 @@ #include "hw/registerfields.h" #include "qapi/error.h" -/* INTC Registers */ -REG32(GICINT128_EN, 0x1000) -REG32(GICINT128_STATUS, 0x1004) -REG32(GICINT129_EN, 0x1100) -REG32(GICINT129_STATUS, 0x1104) -REG32(GICINT130_EN, 0x1200) -REG32(GICINT130_STATUS, 0x1204) -REG32(GICINT131_EN, 0x1300) -REG32(GICINT131_STATUS, 0x1304) -REG32(GICINT132_EN, 0x1400) -REG32(GICINT132_STATUS, 0x1404) -REG32(GICINT133_EN, 0x1500) -REG32(GICINT133_STATUS, 0x1504) -REG32(GICINT134_EN, 0x1600) -REG32(GICINT134_STATUS, 0x1604) -REG32(GICINT135_EN, 0x1700) -REG32(GICINT135_STATUS, 0x1704) -REG32(GICINT136_EN, 0x1800) -REG32(GICINT136_STATUS, 0x1804) +/* + * INTC Registers + * + * values below are offset by - 0x1000 from datasheet + * because its memory region is start at 0x1000 + * + */ +REG32(GICINT128_EN, 0x000) +REG32(GICINT128_STATUS, 0x004) +REG32(GICINT129_EN, 0x100) +REG32(GICINT129_STATUS, 0x104) +REG32(GICINT130_EN, 0x200) +REG32(GICINT130_STATUS, 0x204) +REG32(GICINT131_EN, 0x300) +REG32(GICINT131_STATUS, 0x304) +REG32(GICINT132_EN, 0x400) +REG32(GICINT132_STATUS, 0x404) +REG32(GICINT133_EN, 0x500) +REG32(GICINT133_STATUS, 0x504) +REG32(GICINT134_EN, 0x600) +REG32(GICINT134_STATUS, 0x604) +REG32(GICINT135_EN, 0x700) +REG32(GICINT135_STATUS, 0x704) +REG32(GICINT136_EN, 0x800) +REG32(GICINT136_STATUS, 0x804) #define GICINT_STATUS_BASE R_GICINT128_STATUS @@ -298,7 +304,8 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_intc_ops, s, TYPE_ASPEED_INTC ".regs", aic->nr_regs << 2); - memory_region_add_subregion(&s->iomem_container, 0x0, &s->iomem); + memory_region_add_subregion(&s->iomem_container, aic->reg_offset, + &s->iomem); qdev_init_gpio_in(dev, aspeed_intc_set_irq, aic->num_ints); @@ -348,7 +355,8 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) aic->num_lines = 32; aic->num_ints = 9; aic->mem_size = 0x4000; - aic->nr_regs = 0x2000 >> 2; + aic->nr_regs = 0x808 >> 2; + aic->reg_offset = 0x1000; } static const TypeInfo aspeed_2700_intc_info = { diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index ec4936b3f486..208e764c4ade 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -42,6 +42,7 @@ struct AspeedINTCClass { uint32_t num_ints; uint64_t mem_size; uint64_t nr_regs; + uint64_t reg_offset; }; #endif /* ASPEED_INTC_H */ From 3d6e15eafb3a3977f6659211e08d112807f20626 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:15 +0800 Subject: [PATCH 0714/1179] hw/intc/aspeed: Introduce helper functions for enable and status registers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The behavior of the enable and status registers is almost identical between INTC(CPU Die) and INTCIO(IO Die). To reduce duplicated code, adds "aspeed_intc_enable_handler" functions to handle enable register write behavior and "aspeed_intc_status_handler" functions to handle status register write behavior. No functional change. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-7-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 191 ++++++++++++++++++++++++------------------ 1 file changed, 108 insertions(+), 83 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index d684b4bb4fec..b58a7ee7126e 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -120,6 +120,112 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) } } +static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, + uint64_t data) +{ + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + uint32_t reg = offset >> 2; + uint32_t old_enable; + uint32_t change; + uint32_t irq; + + irq = (offset & 0x0f00) >> 8; + + if (irq >= aic->num_ints) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", + __func__, irq); + return; + } + + /* + * The enable registers are used to enable source interrupts. + * They also handle masking and unmasking of source interrupts + * during the execution of the source ISR. + */ + + /* disable all source interrupt */ + if (!data && !s->enable[irq]) { + s->regs[reg] = data; + return; + } + + old_enable = s->enable[irq]; + s->enable[irq] |= data; + + /* enable new source interrupt */ + if (old_enable != s->enable[irq]) { + trace_aspeed_intc_enable(s->enable[irq]); + s->regs[reg] = data; + return; + } + + /* mask and unmask source interrupt */ + change = s->regs[reg] ^ data; + if (change & data) { + s->mask[irq] &= ~change; + trace_aspeed_intc_unmask(change, s->mask[irq]); + } else { + s->mask[irq] |= change; + trace_aspeed_intc_mask(change, s->mask[irq]); + } + + s->regs[reg] = data; +} + +static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, + uint64_t data) +{ + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + uint32_t reg = offset >> 2; + uint32_t irq; + + if (!data) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid data 0\n", __func__); + return; + } + + irq = (offset & 0x0f00) >> 8; + + if (irq >= aic->num_ints) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", + __func__, irq); + return; + } + + /* clear status */ + s->regs[reg] &= ~data; + + /* + * These status registers are used for notify sources ISR are executed. + * If one source ISR is executed, it will clear one bit. + * If it clear all bits, it means to initialize this register status + * rather than sources ISR are executed. + */ + if (data == 0xffffffff) { + return; + } + + /* All source ISR execution are done */ + if (!s->regs[reg]) { + trace_aspeed_intc_all_isr_done(irq); + if (s->pending[irq]) { + /* + * handle pending source interrupt + * notify firmware which source interrupt are pending + * by setting status register + */ + s->regs[reg] = s->pending[irq]; + s->pending[irq] = 0; + trace_aspeed_intc_trigger_irq(irq, s->regs[reg]); + aspeed_intc_update(s, irq, 1); + } else { + /* clear irq */ + trace_aspeed_intc_clear_irq(irq, 0); + aspeed_intc_update(s, irq, 0); + } + } +} + static uint64_t aspeed_intc_read(void *opaque, hwaddr offset, unsigned int size) { AspeedINTCState *s = ASPEED_INTC(opaque); @@ -136,11 +242,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, unsigned size) { AspeedINTCState *s = ASPEED_INTC(opaque); - AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); uint32_t reg = offset >> 2; - uint32_t old_enable; - uint32_t change; - uint32_t irq; trace_aspeed_intc_write(offset, size, data); @@ -154,45 +256,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, case R_GICINT134_EN: case R_GICINT135_EN: case R_GICINT136_EN: - irq = (offset & 0x0f00) >> 8; - - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", - __func__, irq); - return; - } - - /* - * These registers are used for enable sources interrupt and - * mask and unmask source interrupt while executing source ISR. - */ - - /* disable all source interrupt */ - if (!data && !s->enable[irq]) { - s->regs[reg] = data; - return; - } - - old_enable = s->enable[irq]; - s->enable[irq] |= data; - - /* enable new source interrupt */ - if (old_enable != s->enable[irq]) { - trace_aspeed_intc_enable(s->enable[irq]); - s->regs[reg] = data; - return; - } - - /* mask and unmask source interrupt */ - change = s->regs[reg] ^ data; - if (change & data) { - s->mask[irq] &= ~change; - trace_aspeed_intc_unmask(change, s->mask[irq]); - } else { - s->mask[irq] |= change; - trace_aspeed_intc_mask(change, s->mask[irq]); - } - s->regs[reg] = data; + aspeed_intc_enable_handler(s, offset, data); break; case R_GICINT128_STATUS: case R_GICINT129_STATUS: @@ -203,46 +267,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, case R_GICINT134_STATUS: case R_GICINT135_STATUS: case R_GICINT136_STATUS: - irq = (offset & 0x0f00) >> 8; - - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", - __func__, irq); - return; - } - - /* clear status */ - s->regs[reg] &= ~data; - - /* - * These status registers are used for notify sources ISR are executed. - * If one source ISR is executed, it will clear one bit. - * If it clear all bits, it means to initialize this register status - * rather than sources ISR are executed. - */ - if (data == 0xffffffff) { - return; - } - - /* All source ISR execution are done */ - if (!s->regs[reg]) { - trace_aspeed_intc_all_isr_done(irq); - if (s->pending[irq]) { - /* - * handle pending source interrupt - * notify firmware which source interrupt are pending - * by setting status register - */ - s->regs[reg] = s->pending[irq]; - s->pending[irq] = 0; - trace_aspeed_intc_trigger_irq(irq, s->regs[reg]); - aspeed_intc_update(s, irq, 1); - } else { - /* clear irq */ - trace_aspeed_intc_clear_irq(irq, 0); - aspeed_intc_update(s, irq, 0); - } - } + aspeed_intc_status_handler(s, offset, data); break; default: s->regs[reg] = data; From 49da40cf5ffca090f6918ba882db8fb9536792a7 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:16 +0800 Subject: [PATCH 0715/1179] hw/intc/aspeed: Add object type name to trace events for better debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, these trace events only refer to INTC. To simplify the INTC model, both INTC(CPU Die) and INTCIO(IO Die) will share the same helper functions. However, it is difficult to recognize whether these trace events are comes from INTC or INTCIO. To make these trace events more readable, adds object type name to the INTC trace events. Update trace events to include the "name" field for better identification. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-8-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 32 +++++++++++++++++++------------- hw/intc/trace-events | 24 ++++++++++++------------ 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index b58a7ee7126e..d06e697ecc2f 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -45,6 +45,7 @@ REG32(GICINT136_STATUS, 0x804) static void aspeed_intc_update(AspeedINTCState *s, int irq, int level) { AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const char *name = object_get_typename(OBJECT(s)); if (irq >= aic->num_ints) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", @@ -52,7 +53,7 @@ static void aspeed_intc_update(AspeedINTCState *s, int irq, int level) return; } - trace_aspeed_intc_update_irq(irq, level); + trace_aspeed_intc_update_irq(name, irq, level); qemu_set_irq(s->output_pins[irq], level); } @@ -66,6 +67,7 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) { AspeedINTCState *s = (AspeedINTCState *)opaque; AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const char *name = object_get_typename(OBJECT(s)); uint32_t status_reg = GICINT_STATUS_BASE + ((0x100 * irq) >> 2); uint32_t select = 0; uint32_t enable; @@ -77,7 +79,7 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) return; } - trace_aspeed_intc_set_irq(irq, level); + trace_aspeed_intc_set_irq(name, irq, level); enable = s->enable[irq]; if (!level) { @@ -96,7 +98,7 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) return; } - trace_aspeed_intc_select(select); + trace_aspeed_intc_select(name, select); if (s->mask[irq] || s->regs[status_reg]) { /* @@ -108,14 +110,14 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) * save source interrupt to pending variable. */ s->pending[irq] |= select; - trace_aspeed_intc_pending_irq(irq, s->pending[irq]); + trace_aspeed_intc_pending_irq(name, irq, s->pending[irq]); } else { /* * notify firmware which source interrupt are coming * by setting status register */ s->regs[status_reg] = select; - trace_aspeed_intc_trigger_irq(irq, s->regs[status_reg]); + trace_aspeed_intc_trigger_irq(name, irq, s->regs[status_reg]); aspeed_intc_update(s, irq, 1); } } @@ -124,6 +126,7 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, uint64_t data) { AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const char *name = object_get_typename(OBJECT(s)); uint32_t reg = offset >> 2; uint32_t old_enable; uint32_t change; @@ -154,7 +157,7 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, /* enable new source interrupt */ if (old_enable != s->enable[irq]) { - trace_aspeed_intc_enable(s->enable[irq]); + trace_aspeed_intc_enable(name, s->enable[irq]); s->regs[reg] = data; return; } @@ -163,10 +166,10 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, change = s->regs[reg] ^ data; if (change & data) { s->mask[irq] &= ~change; - trace_aspeed_intc_unmask(change, s->mask[irq]); + trace_aspeed_intc_unmask(name, change, s->mask[irq]); } else { s->mask[irq] |= change; - trace_aspeed_intc_mask(change, s->mask[irq]); + trace_aspeed_intc_mask(name, change, s->mask[irq]); } s->regs[reg] = data; @@ -176,6 +179,7 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, uint64_t data) { AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const char *name = object_get_typename(OBJECT(s)); uint32_t reg = offset >> 2; uint32_t irq; @@ -207,7 +211,7 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, /* All source ISR execution are done */ if (!s->regs[reg]) { - trace_aspeed_intc_all_isr_done(irq); + trace_aspeed_intc_all_isr_done(name, irq); if (s->pending[irq]) { /* * handle pending source interrupt @@ -216,11 +220,11 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, */ s->regs[reg] = s->pending[irq]; s->pending[irq] = 0; - trace_aspeed_intc_trigger_irq(irq, s->regs[reg]); + trace_aspeed_intc_trigger_irq(name, irq, s->regs[reg]); aspeed_intc_update(s, irq, 1); } else { /* clear irq */ - trace_aspeed_intc_clear_irq(irq, 0); + trace_aspeed_intc_clear_irq(name, irq, 0); aspeed_intc_update(s, irq, 0); } } @@ -229,11 +233,12 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, static uint64_t aspeed_intc_read(void *opaque, hwaddr offset, unsigned int size) { AspeedINTCState *s = ASPEED_INTC(opaque); + const char *name = object_get_typename(OBJECT(s)); uint32_t reg = offset >> 2; uint32_t value = 0; value = s->regs[reg]; - trace_aspeed_intc_read(offset, size, value); + trace_aspeed_intc_read(name, offset, size, value); return value; } @@ -242,9 +247,10 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, unsigned size) { AspeedINTCState *s = ASPEED_INTC(opaque); + const char *name = object_get_typename(OBJECT(s)); uint32_t reg = offset >> 2; - trace_aspeed_intc_write(offset, size, data); + trace_aspeed_intc_write(name, offset, size, data); switch (reg) { case R_GICINT128_EN: diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 3dcf14719833..e9ca34755e09 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -80,18 +80,18 @@ aspeed_vic_update_irq(int flags) "Raising IRQ: %d" aspeed_vic_read(uint64_t offset, unsigned size, uint32_t value) "From 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_vic_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 # aspeed_intc.c -aspeed_intc_read(uint64_t offset, unsigned size, uint32_t value) "From 0x%" PRIx64 " of size %u: 0x%" PRIx32 -aspeed_intc_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 -aspeed_intc_set_irq(int irq, int level) "Set IRQ %d: %d" -aspeed_intc_clear_irq(int irq, int level) "Clear IRQ %d: %d" -aspeed_intc_update_irq(int irq, int level) "Update IRQ: %d: %d" -aspeed_intc_pending_irq(int irq, uint32_t value) "Pending IRQ: %d: 0x%x" -aspeed_intc_trigger_irq(int irq, uint32_t value) "Trigger IRQ: %d: 0x%x" -aspeed_intc_all_isr_done(int irq) "All source ISR execution are done: %d" -aspeed_intc_enable(uint32_t value) "Enable: 0x%x" -aspeed_intc_select(uint32_t value) "Select: 0x%x" -aspeed_intc_mask(uint32_t change, uint32_t value) "Mask: 0x%x: 0x%x" -aspeed_intc_unmask(uint32_t change, uint32_t value) "UnMask: 0x%x: 0x%x" +aspeed_intc_read(const char *s, uint64_t offset, unsigned size, uint32_t value) "%s: From 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_intc_write(const char *s, uint64_t offset, unsigned size, uint32_t data) "%s: To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_intc_set_irq(const char *s, int irq, int level) "%s: Set IRQ %d: %d" +aspeed_intc_clear_irq(const char *s, int irq, int level) "%s: Clear IRQ %d: %d" +aspeed_intc_update_irq(const char *s, int irq, int level) "%s: Update IRQ: %d: %d" +aspeed_intc_pending_irq(const char *s, int irq, uint32_t value) "%s: Pending IRQ: %d: 0x%x" +aspeed_intc_trigger_irq(const char *s, int irq, uint32_t value) "%s: Trigger IRQ: %d: 0x%x" +aspeed_intc_all_isr_done(const char *s, int irq) "%s: All source ISR execution are done: %d" +aspeed_intc_enable(const char *s, uint32_t value) "%s: Enable: 0x%x" +aspeed_intc_select(const char *s, uint32_t value) "%s: Select: 0x%x" +aspeed_intc_mask(const char *s, uint32_t change, uint32_t value) "%s: Mask: 0x%x: 0x%x" +aspeed_intc_unmask(const char *s, uint32_t change, uint32_t value) "%s: UnMask: 0x%x: 0x%x" # arm_gic.c gic_enable_irq(int irq) "irq %d enabled" From de4e979973ce5c09d0b9a6d8a7aa17ab77c869c8 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:17 +0800 Subject: [PATCH 0716/1179] hw/arm/aspeed: Rename IRQ table and machine name for AST2700 A0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, AST2700 SoC only supports A0. To support AST2700 A1, rename its IRQ table and machine name. To follow the machine deprecation rule, the initial machine "ast2700-evb" is aliased to "ast2700a0-evb." In the future, we will alias "ast2700-evb" to new SoCs, such as "ast2700a1-evb." Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-9-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed.c | 9 +++++---- hw/arm/aspeed_ast27x0.c | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index c6c18596d62e..18f7c450dadd 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -1673,12 +1673,13 @@ static void ast2700_evb_i2c_init(AspeedMachineState *bmc) TYPE_TMP105, 0x4d); } -static void aspeed_machine_ast2700_evb_class_init(ObjectClass *oc, void *data) +static void aspeed_machine_ast2700a0_evb_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc); - mc->desc = "Aspeed AST2700 EVB (Cortex-A35)"; + mc->alias = "ast2700-evb"; + mc->desc = "Aspeed AST2700 A0 EVB (Cortex-A35)"; amc->soc_name = "ast2700-a0"; amc->hw_strap1 = AST2700_EVB_HW_STRAP1; amc->hw_strap2 = AST2700_EVB_HW_STRAP2; @@ -1817,9 +1818,9 @@ static const TypeInfo aspeed_machine_types[] = { .class_init = aspeed_minibmc_machine_ast1030_evb_class_init, #ifdef TARGET_AARCH64 }, { - .name = MACHINE_TYPE_NAME("ast2700-evb"), + .name = MACHINE_TYPE_NAME("ast2700a0-evb"), .parent = TYPE_ASPEED_MACHINE, - .class_init = aspeed_machine_ast2700_evb_class_init, + .class_init = aspeed_machine_ast2700a0_evb_class_init, #endif }, { .name = TYPE_ASPEED_MACHINE, diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index f14d2ea1753d..44ddfdd2c6c9 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -73,7 +73,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = { #define AST2700_MAX_IRQ 256 /* Shared Peripheral Interrupt values below are offset by -32 from datasheet */ -static const int aspeed_soc_ast2700_irqmap[] = { +static const int aspeed_soc_ast2700a0_irqmap[] = { [ASPEED_DEV_UART0] = 132, [ASPEED_DEV_UART1] = 132, [ASPEED_DEV_UART2] = 132, @@ -766,7 +766,7 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) create_unimplemented_device("ast2700.io", 0x0, 0x4000000); } -static void aspeed_soc_ast2700_class_init(ObjectClass *oc, void *data) +static void aspeed_soc_ast2700a0_class_init(ObjectClass *oc, void *data) { static const char * const valid_cpu_types[] = { ARM_CPU_TYPE_NAME("cortex-a35"), @@ -788,7 +788,7 @@ static void aspeed_soc_ast2700_class_init(ObjectClass *oc, void *data) sc->uarts_num = 13; sc->num_cpus = 4; sc->uarts_base = ASPEED_DEV_UART0; - sc->irqmap = aspeed_soc_ast2700_irqmap; + sc->irqmap = aspeed_soc_ast2700a0_irqmap; sc->memmap = aspeed_soc_ast2700_memmap; sc->get_irq = aspeed_soc_ast2700_get_irq; } @@ -803,7 +803,7 @@ static const TypeInfo aspeed_soc_ast27x0_types[] = { .name = "ast2700-a0", .parent = TYPE_ASPEED27X0_SOC, .instance_init = aspeed_soc_ast2700_init, - .class_init = aspeed_soc_ast2700_class_init, + .class_init = aspeed_soc_ast2700a0_class_init, }, }; From 617cacefb7f7062f026994a256f8798d767cff73 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:18 +0800 Subject: [PATCH 0717/1179] hw/arm/aspeed_ast27x0: Sort the IRQ table by IRQ number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To improve readability, sort the IRQ table by IRQ number. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-10-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 50 ++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 44ddfdd2c6c9..642a8f55212f 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -74,27 +74,13 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = { /* Shared Peripheral Interrupt values below are offset by -32 from datasheet */ static const int aspeed_soc_ast2700a0_irqmap[] = { - [ASPEED_DEV_UART0] = 132, - [ASPEED_DEV_UART1] = 132, - [ASPEED_DEV_UART2] = 132, - [ASPEED_DEV_UART3] = 132, - [ASPEED_DEV_UART4] = 8, - [ASPEED_DEV_UART5] = 132, - [ASPEED_DEV_UART6] = 132, - [ASPEED_DEV_UART7] = 132, - [ASPEED_DEV_UART8] = 132, - [ASPEED_DEV_UART9] = 132, - [ASPEED_DEV_UART10] = 132, - [ASPEED_DEV_UART11] = 132, - [ASPEED_DEV_UART12] = 132, - [ASPEED_DEV_FMC] = 131, [ASPEED_DEV_SDMC] = 0, - [ASPEED_DEV_SCU] = 12, - [ASPEED_DEV_ADC] = 130, + [ASPEED_DEV_HACE] = 4, [ASPEED_DEV_XDMA] = 5, - [ASPEED_DEV_EMMC] = 15, - [ASPEED_DEV_GPIO] = 130, + [ASPEED_DEV_UART4] = 8, + [ASPEED_DEV_SCU] = 12, [ASPEED_DEV_RTC] = 13, + [ASPEED_DEV_EMMC] = 15, [ASPEED_DEV_TIMER1] = 16, [ASPEED_DEV_TIMER2] = 17, [ASPEED_DEV_TIMER3] = 18, @@ -103,19 +89,33 @@ static const int aspeed_soc_ast2700a0_irqmap[] = { [ASPEED_DEV_TIMER6] = 21, [ASPEED_DEV_TIMER7] = 22, [ASPEED_DEV_TIMER8] = 23, - [ASPEED_DEV_WDT] = 131, - [ASPEED_DEV_PWM] = 131, + [ASPEED_DEV_DP] = 28, [ASPEED_DEV_LPC] = 128, [ASPEED_DEV_IBT] = 128, + [ASPEED_DEV_KCS] = 128, + [ASPEED_DEV_ADC] = 130, + [ASPEED_DEV_GPIO] = 130, [ASPEED_DEV_I2C] = 130, - [ASPEED_DEV_PECI] = 133, + [ASPEED_DEV_FMC] = 131, + [ASPEED_DEV_WDT] = 131, + [ASPEED_DEV_PWM] = 131, + [ASPEED_DEV_I3C] = 131, + [ASPEED_DEV_UART0] = 132, + [ASPEED_DEV_UART1] = 132, + [ASPEED_DEV_UART2] = 132, + [ASPEED_DEV_UART3] = 132, + [ASPEED_DEV_UART5] = 132, + [ASPEED_DEV_UART6] = 132, + [ASPEED_DEV_UART7] = 132, + [ASPEED_DEV_UART8] = 132, + [ASPEED_DEV_UART9] = 132, + [ASPEED_DEV_UART10] = 132, + [ASPEED_DEV_UART11] = 132, + [ASPEED_DEV_UART12] = 132, [ASPEED_DEV_ETH1] = 132, [ASPEED_DEV_ETH2] = 132, [ASPEED_DEV_ETH3] = 132, - [ASPEED_DEV_HACE] = 4, - [ASPEED_DEV_KCS] = 128, - [ASPEED_DEV_DP] = 28, - [ASPEED_DEV_I3C] = 131, + [ASPEED_DEV_PECI] = 133, [ASPEED_DEV_SDHCI] = 133, }; From 28194d5d15b92f0b3f6628236f93001c3fdd0d39 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:19 +0800 Subject: [PATCH 0718/1179] hw/intc/aspeed: Support different memory region ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous implementation set the "aspeed_intc_ops" struct, containing read and write callbacks, to be used when I/O is performed on the INTC region. Both "aspeed_intc_read" and "aspeed_intc_write" callback functions were used for INTC (CPU Die). To support the INTCIO (IO Die) model, introduces a new "reg_ops" class attribute. This allows setting different memory region operations to support different INTC models. Will introduce "aspeed_intcio_read" and "aspeed_intcio_write" callback functions are used for INTCIO. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-11-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 5 ++++- include/hw/intc/aspeed_intc.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index d06e697ecc2f..d8ee6e1c0406 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -332,7 +332,7 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) sysbus_init_mmio(sbd, &s->iomem_container); s->regs = g_new(uint32_t, aic->nr_regs); - memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_intc_ops, s, + memory_region_init_io(&s->iomem, OBJECT(s), aic->reg_ops, s, TYPE_ASPEED_INTC ".regs", aic->nr_regs << 2); memory_region_add_subregion(&s->iomem_container, aic->reg_offset, @@ -359,12 +359,15 @@ static void aspeed_intc_unrealize(DeviceState *dev) static void aspeed_intc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + AspeedINTCClass *aic = ASPEED_INTC_CLASS(klass); dc->desc = "ASPEED INTC Controller"; dc->realize = aspeed_intc_realize; dc->unrealize = aspeed_intc_unrealize; device_class_set_legacy_reset(dc, aspeed_intc_reset); dc->vmsd = NULL; + + aic->reg_ops = &aspeed_intc_ops; } static const TypeInfo aspeed_intc_info = { diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 208e764c4ade..3433277d8715 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -43,6 +43,7 @@ struct AspeedINTCClass { uint64_t mem_size; uint64_t nr_regs; uint64_t reg_offset; + const MemoryRegionOps *reg_ops; }; #endif /* ASPEED_INTC_H */ From 63f3618f9be0f28ff36cd4b5685877715b97e669 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:20 +0800 Subject: [PATCH 0719/1179] hw/intc/aspeed: Rename num_ints to num_inpins for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To support AST2700 A1, some registers of the INTC(CPU Die) support one input pin to multiple output pins. Renamed "num_ints" to "num_inpins" in the INTC controller code for better clarity and consistency in naming conventions. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-12-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 2 +- hw/intc/aspeed_intc.c | 31 +++++++++++++++++-------------- include/hw/intc/aspeed_intc.h | 11 ++++++----- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 642a8f55212f..1a3eb02af3bf 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -535,7 +535,7 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) sc->memmap[ASPEED_DEV_INTC]); /* GICINT orgates -> INTC -> GIC */ - for (i = 0; i < ic->num_ints; i++) { + for (i = 0; i < ic->num_inpins; i++) { qdev_connect_gpio_out(DEVICE(&a->intc.orgates[i]), 0, qdev_get_gpio_in(DEVICE(&a->intc), i)); sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc), i, diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index d8ee6e1c0406..217fda6fe001 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -47,8 +47,9 @@ static void aspeed_intc_update(AspeedINTCState *s, int irq, int level) AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", + if (irq >= aic->num_inpins) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid input pin index: %d\n", __func__, irq); return; } @@ -60,7 +61,7 @@ static void aspeed_intc_update(AspeedINTCState *s, int irq, int level) /* * The address of GICINT128 to GICINT136 are from 0x1000 to 0x1804. * Utilize "address & 0x0f00" to get the irq and irq output pin index - * The value of irq should be 0 to num_ints. + * The value of irq should be 0 to num_inpins. * The irq 0 indicates GICINT128, irq 1 indicates GICINT129 and so on. */ static void aspeed_intc_set_irq(void *opaque, int irq, int level) @@ -73,8 +74,8 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) uint32_t enable; int i; - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", + if (irq >= aic->num_inpins) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid input pin index: %d\n", __func__, irq); return; } @@ -134,8 +135,9 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, irq = (offset & 0x0f00) >> 8; - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", + if (irq >= aic->num_inpins) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid input pin index: %d\n", __func__, irq); return; } @@ -190,8 +192,9 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, irq = (offset & 0x0f00) >> 8; - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", + if (irq >= aic->num_inpins) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid input pin index: %d\n", __func__, irq); return; } @@ -299,8 +302,8 @@ static void aspeed_intc_instance_init(Object *obj) AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); int i; - assert(aic->num_ints <= ASPEED_INTC_NR_INTS); - for (i = 0; i < aic->num_ints; i++) { + assert(aic->num_inpins <= ASPEED_INTC_MAX_INPINS); + for (i = 0; i < aic->num_inpins; i++) { object_initialize_child(obj, "intc-orgates[*]", &s->orgates[i], TYPE_OR_IRQ); object_property_set_int(OBJECT(&s->orgates[i]), "num-lines", @@ -338,9 +341,9 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(&s->iomem_container, aic->reg_offset, &s->iomem); - qdev_init_gpio_in(dev, aspeed_intc_set_irq, aic->num_ints); + qdev_init_gpio_in(dev, aspeed_intc_set_irq, aic->num_inpins); - for (i = 0; i < aic->num_ints; i++) { + for (i = 0; i < aic->num_inpins; i++) { if (!qdev_realize(DEVICE(&s->orgates[i]), NULL, errp)) { return; } @@ -387,7 +390,7 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) dc->desc = "ASPEED 2700 INTC Controller"; aic->num_lines = 32; - aic->num_ints = 9; + aic->num_inpins = 9; aic->mem_size = 0x4000; aic->nr_regs = 0x808 >> 2; aic->reg_offset = 0x1000; diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 3433277d8715..58be5b3e1323 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -17,6 +17,7 @@ OBJECT_DECLARE_TYPE(AspeedINTCState, AspeedINTCClass, ASPEED_INTC) #define ASPEED_INTC_NR_INTS 9 +#define ASPEED_INTC_MAX_INPINS 9 struct AspeedINTCState { /*< private >*/ @@ -27,19 +28,19 @@ struct AspeedINTCState { MemoryRegion iomem_container; uint32_t *regs; - OrIRQState orgates[ASPEED_INTC_NR_INTS]; + OrIRQState orgates[ASPEED_INTC_MAX_INPINS]; qemu_irq output_pins[ASPEED_INTC_NR_INTS]; - uint32_t enable[ASPEED_INTC_NR_INTS]; - uint32_t mask[ASPEED_INTC_NR_INTS]; - uint32_t pending[ASPEED_INTC_NR_INTS]; + uint32_t enable[ASPEED_INTC_MAX_INPINS]; + uint32_t mask[ASPEED_INTC_MAX_INPINS]; + uint32_t pending[ASPEED_INTC_MAX_INPINS]; }; struct AspeedINTCClass { SysBusDeviceClass parent_class; uint32_t num_lines; - uint32_t num_ints; + uint32_t num_inpins; uint64_t mem_size; uint64_t nr_regs; uint64_t reg_offset; From 35c909cd80d4095690bb1c98c263b01d9617de65 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:21 +0800 Subject: [PATCH 0720/1179] hw/intc/aspeed: Add support for multiple output pins in INTC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support for multiple output pins in the INTC controller to accommodate the AST2700 A1. Introduced "num_outpins" to represent the number of output pins. Updated the IRQ handling logic to initialize and connect output pins separately from input pins. Modified the "aspeed_soc_ast2700_realize" function to connect source orgates to INTC and INTC to GIC128 - GIC136. Updated the "aspeed_intc_realize" function to initialize output pins. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-13-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 6 +++++- hw/intc/aspeed_intc.c | 4 ++++ include/hw/intc/aspeed_intc.h | 5 +++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 1a3eb02af3bf..3cb95210a06f 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -534,10 +534,14 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc), 0, sc->memmap[ASPEED_DEV_INTC]); - /* GICINT orgates -> INTC -> GIC */ + /* source orgates -> INTC */ for (i = 0; i < ic->num_inpins; i++) { qdev_connect_gpio_out(DEVICE(&a->intc.orgates[i]), 0, qdev_get_gpio_in(DEVICE(&a->intc), i)); + } + + /* INTC -> GIC128 - GIC136 */ + for (i = 0; i < ic->num_outpins; i++) { sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc), i, qdev_get_gpio_in(DEVICE(&a->gic), aspeed_soc_ast2700_gic_intcmap[i].irq)); diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 217fda6fe001..6f37afc17e25 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -347,6 +347,9 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) if (!qdev_realize(DEVICE(&s->orgates[i]), NULL, errp)) { return; } + } + + for (i = 0; i < aic->num_outpins; i++) { sysbus_init_irq(sbd, &s->output_pins[i]); } } @@ -391,6 +394,7 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) dc->desc = "ASPEED 2700 INTC Controller"; aic->num_lines = 32; aic->num_inpins = 9; + aic->num_outpins = 9; aic->mem_size = 0x4000; aic->nr_regs = 0x808 >> 2; aic->reg_offset = 0x1000; diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 58be5b3e1323..2a22e3084615 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -16,8 +16,8 @@ #define TYPE_ASPEED_2700_INTC TYPE_ASPEED_INTC "-ast2700" OBJECT_DECLARE_TYPE(AspeedINTCState, AspeedINTCClass, ASPEED_INTC) -#define ASPEED_INTC_NR_INTS 9 #define ASPEED_INTC_MAX_INPINS 9 +#define ASPEED_INTC_MAX_OUTPINS 9 struct AspeedINTCState { /*< private >*/ @@ -29,7 +29,7 @@ struct AspeedINTCState { uint32_t *regs; OrIRQState orgates[ASPEED_INTC_MAX_INPINS]; - qemu_irq output_pins[ASPEED_INTC_NR_INTS]; + qemu_irq output_pins[ASPEED_INTC_MAX_OUTPINS]; uint32_t enable[ASPEED_INTC_MAX_INPINS]; uint32_t mask[ASPEED_INTC_MAX_INPINS]; @@ -41,6 +41,7 @@ struct AspeedINTCClass { uint32_t num_lines; uint32_t num_inpins; + uint32_t num_outpins; uint64_t mem_size; uint64_t nr_regs; uint64_t reg_offset; From c6c5e63d46add459732d8d8d3b84bd5d26dff0ad Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:22 +0800 Subject: [PATCH 0721/1179] hw/intc/aspeed: Refactor INTC to support separate input and output pin indices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactors the INTC to distinguish between input and output pin indices, improving interrupt handling clarity and accuracy. Updated the functions to handle both input and output pin indices. Added detailed logging for input and output pin indices in trace events. These changes ensure that the INTC controller can handle multiple input and output pins, improving support for the AST2700 A1. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-14-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 97 +++++++++++++++++++++++++++---------------- hw/intc/trace-events | 12 +++--- 2 files changed, 67 insertions(+), 42 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 6f37afc17e25..1cbee0e17a5f 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -42,20 +42,32 @@ REG32(GICINT136_STATUS, 0x804) #define GICINT_STATUS_BASE R_GICINT128_STATUS -static void aspeed_intc_update(AspeedINTCState *s, int irq, int level) +/* + * Update the state of an interrupt controller pin by setting + * the specified output pin to the given level. + * The input pin index should be between 0 and the number of input pins. + * The output pin index should be between 0 and the number of output pins. + */ +static void aspeed_intc_update(AspeedINTCState *s, int inpin_idx, + int outpin_idx, int level) { AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); - if (irq >= aic->num_inpins) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid input pin index: %d\n", - __func__, irq); + if (inpin_idx >= aic->num_inpins) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid input pin index: %d\n", + __func__, inpin_idx); return; } - trace_aspeed_intc_update_irq(name, irq, level); - qemu_set_irq(s->output_pins[irq], level); + if (outpin_idx >= aic->num_outpins) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid output pin index: %d\n", + __func__, outpin_idx); + return; + } + + trace_aspeed_intc_update_irq(name, inpin_idx, outpin_idx, level); + qemu_set_irq(s->output_pins[outpin_idx], level); } /* @@ -72,23 +84,28 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) uint32_t status_reg = GICINT_STATUS_BASE + ((0x100 * irq) >> 2); uint32_t select = 0; uint32_t enable; + int outpin_idx; + int inpin_idx; int i; + outpin_idx = irq; + inpin_idx = irq; + if (irq >= aic->num_inpins) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid input pin index: %d\n", __func__, irq); return; } - trace_aspeed_intc_set_irq(name, irq, level); - enable = s->enable[irq]; + trace_aspeed_intc_set_irq(name, inpin_idx, level); + enable = s->enable[inpin_idx]; if (!level) { return; } for (i = 0; i < aic->num_lines; i++) { - if (s->orgates[irq].levels[i]) { + if (s->orgates[inpin_idx].levels[i]) { if (enable & BIT(i)) { select |= BIT(i); } @@ -101,7 +118,7 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) trace_aspeed_intc_select(name, select); - if (s->mask[irq] || s->regs[status_reg]) { + if (s->mask[inpin_idx] || s->regs[status_reg]) { /* * a. mask is not 0 means in ISR mode * sources interrupt routine are executing. @@ -110,16 +127,17 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) * * save source interrupt to pending variable. */ - s->pending[irq] |= select; - trace_aspeed_intc_pending_irq(name, irq, s->pending[irq]); + s->pending[inpin_idx] |= select; + trace_aspeed_intc_pending_irq(name, inpin_idx, s->pending[inpin_idx]); } else { /* * notify firmware which source interrupt are coming * by setting status register */ s->regs[status_reg] = select; - trace_aspeed_intc_trigger_irq(name, irq, s->regs[status_reg]); - aspeed_intc_update(s, irq, 1); + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx, + s->regs[status_reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx, 1); } } @@ -131,14 +149,16 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, uint32_t reg = offset >> 2; uint32_t old_enable; uint32_t change; + int inpin_idx; uint32_t irq; irq = (offset & 0x0f00) >> 8; + inpin_idx = irq; - if (irq >= aic->num_inpins) { + if (inpin_idx >= aic->num_inpins) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid input pin index: %d\n", - __func__, irq); + __func__, inpin_idx); return; } @@ -149,17 +169,17 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, */ /* disable all source interrupt */ - if (!data && !s->enable[irq]) { + if (!data && !s->enable[inpin_idx]) { s->regs[reg] = data; return; } - old_enable = s->enable[irq]; - s->enable[irq] |= data; + old_enable = s->enable[inpin_idx]; + s->enable[inpin_idx] |= data; /* enable new source interrupt */ - if (old_enable != s->enable[irq]) { - trace_aspeed_intc_enable(name, s->enable[irq]); + if (old_enable != s->enable[inpin_idx]) { + trace_aspeed_intc_enable(name, s->enable[inpin_idx]); s->regs[reg] = data; return; } @@ -167,11 +187,11 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, /* mask and unmask source interrupt */ change = s->regs[reg] ^ data; if (change & data) { - s->mask[irq] &= ~change; - trace_aspeed_intc_unmask(name, change, s->mask[irq]); + s->mask[inpin_idx] &= ~change; + trace_aspeed_intc_unmask(name, change, s->mask[inpin_idx]); } else { - s->mask[irq] |= change; - trace_aspeed_intc_mask(name, change, s->mask[irq]); + s->mask[inpin_idx] |= change; + trace_aspeed_intc_mask(name, change, s->mask[inpin_idx]); } s->regs[reg] = data; @@ -183,6 +203,8 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); uint32_t reg = offset >> 2; + int outpin_idx; + int inpin_idx; uint32_t irq; if (!data) { @@ -191,11 +213,13 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, } irq = (offset & 0x0f00) >> 8; + outpin_idx = irq; + inpin_idx = irq; - if (irq >= aic->num_inpins) { + if (inpin_idx >= aic->num_inpins) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid input pin index: %d\n", - __func__, irq); + __func__, inpin_idx); return; } @@ -214,21 +238,22 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, /* All source ISR execution are done */ if (!s->regs[reg]) { - trace_aspeed_intc_all_isr_done(name, irq); - if (s->pending[irq]) { + trace_aspeed_intc_all_isr_done(name, inpin_idx); + if (s->pending[inpin_idx]) { /* * handle pending source interrupt * notify firmware which source interrupt are pending * by setting status register */ - s->regs[reg] = s->pending[irq]; - s->pending[irq] = 0; - trace_aspeed_intc_trigger_irq(name, irq, s->regs[reg]); - aspeed_intc_update(s, irq, 1); + s->regs[reg] = s->pending[inpin_idx]; + s->pending[inpin_idx] = 0; + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx, + s->regs[reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx, 1); } else { /* clear irq */ - trace_aspeed_intc_clear_irq(name, irq, 0); - aspeed_intc_update(s, irq, 0); + trace_aspeed_intc_clear_irq(name, inpin_idx, outpin_idx, 0); + aspeed_intc_update(s, inpin_idx, outpin_idx, 0); } } } diff --git a/hw/intc/trace-events b/hw/intc/trace-events index e9ca34755e09..e97eea820b82 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -82,12 +82,12 @@ aspeed_vic_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 # aspeed_intc.c aspeed_intc_read(const char *s, uint64_t offset, unsigned size, uint32_t value) "%s: From 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_intc_write(const char *s, uint64_t offset, unsigned size, uint32_t data) "%s: To 0x%" PRIx64 " of size %u: 0x%" PRIx32 -aspeed_intc_set_irq(const char *s, int irq, int level) "%s: Set IRQ %d: %d" -aspeed_intc_clear_irq(const char *s, int irq, int level) "%s: Clear IRQ %d: %d" -aspeed_intc_update_irq(const char *s, int irq, int level) "%s: Update IRQ: %d: %d" -aspeed_intc_pending_irq(const char *s, int irq, uint32_t value) "%s: Pending IRQ: %d: 0x%x" -aspeed_intc_trigger_irq(const char *s, int irq, uint32_t value) "%s: Trigger IRQ: %d: 0x%x" -aspeed_intc_all_isr_done(const char *s, int irq) "%s: All source ISR execution are done: %d" +aspeed_intc_set_irq(const char *s, int inpin_idx, int level) "%s: Set IRQ %d: %d" +aspeed_intc_clear_irq(const char *s, int inpin_idx, int outpin_idx, int level) "%s: Clear IRQ %d-%d: %d" +aspeed_intc_update_irq(const char *s, int inpin_idx, int outpin_idx, int level) "%s: Update IRQ: %d-%d: %d" +aspeed_intc_pending_irq(const char *s, int inpin_idx, uint32_t value) "%s: Pending IRQ: %d: 0x%x" +aspeed_intc_trigger_irq(const char *s, int inpin_idx, int outpin_idx, uint32_t value) "%s: Trigger IRQ: %d-%d: 0x%x" +aspeed_intc_all_isr_done(const char *s, int inpin_idx) "%s: All source ISR execution are done: %d" aspeed_intc_enable(const char *s, uint32_t value) "%s: Enable: 0x%x" aspeed_intc_select(const char *s, uint32_t value) "%s: Select: 0x%x" aspeed_intc_mask(const char *s, uint32_t change, uint32_t value) "%s: Mask: 0x%x: 0x%x" From ab24c6a2df8e6c8055b6f1dfe80697320b327c50 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:23 +0800 Subject: [PATCH 0722/1179] hw/intc/aspeed: Introduce AspeedINTCIRQ structure to save the irq index and register address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The INTC controller supports GICINT128 to GICINT136, mapping 1:1 to input and output IRQs 0 to 8. Previously, the formula "address & 0x0f00" was used to derive the IRQ index numbers. However, the INTC controller also supports GICINT192_201, mapping 1 input IRQ pin to 10 output IRQ pins. The pin numbers for input and output are different. It is difficult to use a formula to determine the index number of INTC model supported input and output IRQs. To simplify and improve readability, introduces the AspeedINTCIRQ structure to save the input/output IRQ index and its enable/status register address. Introduce the "aspeed_2700_intc_irqs" table to store IRQ information for INTC. Introduce the "aspeed_intc_get_irq" function to retrieve the input/output IRQ pin index from the provided status/enable register address. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-15-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 87 +++++++++++++++++++---------------- include/hw/intc/aspeed_intc.h | 10 ++++ 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 1cbee0e17a5f..be24516ec970 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -40,7 +40,23 @@ REG32(GICINT135_STATUS, 0x704) REG32(GICINT136_EN, 0x800) REG32(GICINT136_STATUS, 0x804) -#define GICINT_STATUS_BASE R_GICINT128_STATUS +static const AspeedINTCIRQ *aspeed_intc_get_irq(AspeedINTCClass *aic, + uint32_t reg) +{ + int i; + + for (i = 0; i < aic->irq_table_count; i++) { + if (aic->irq_table[i].enable_reg == reg || + aic->irq_table[i].status_reg == reg) { + return &aic->irq_table[i]; + } + } + + /* + * Invalid reg. + */ + g_assert_not_reached(); +} /* * Update the state of an interrupt controller pin by setting @@ -54,17 +70,7 @@ static void aspeed_intc_update(AspeedINTCState *s, int inpin_idx, AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); - if (inpin_idx >= aic->num_inpins) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid input pin index: %d\n", - __func__, inpin_idx); - return; - } - - if (outpin_idx >= aic->num_outpins) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid output pin index: %d\n", - __func__, outpin_idx); - return; - } + assert((outpin_idx < aic->num_outpins) && (inpin_idx < aic->num_inpins)); trace_aspeed_intc_update_irq(name, inpin_idx, outpin_idx, level); qemu_set_irq(s->output_pins[outpin_idx], level); @@ -81,21 +87,20 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) AspeedINTCState *s = (AspeedINTCState *)opaque; AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); - uint32_t status_reg = GICINT_STATUS_BASE + ((0x100 * irq) >> 2); + const AspeedINTCIRQ *intc_irq; + uint32_t status_reg; uint32_t select = 0; uint32_t enable; int outpin_idx; int inpin_idx; int i; - outpin_idx = irq; - inpin_idx = irq; + assert(irq < aic->num_inpins); - if (irq >= aic->num_inpins) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid input pin index: %d\n", - __func__, irq); - return; - } + intc_irq = &aic->irq_table[irq]; + status_reg = intc_irq->status_reg; + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; trace_aspeed_intc_set_irq(name, inpin_idx, level); enable = s->enable[inpin_idx]; @@ -146,21 +151,16 @@ static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, { AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); + const AspeedINTCIRQ *intc_irq; uint32_t reg = offset >> 2; uint32_t old_enable; uint32_t change; int inpin_idx; - uint32_t irq; - irq = (offset & 0x0f00) >> 8; - inpin_idx = irq; + intc_irq = aspeed_intc_get_irq(aic, reg); + inpin_idx = intc_irq->inpin_idx; - if (inpin_idx >= aic->num_inpins) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid input pin index: %d\n", - __func__, inpin_idx); - return; - } + assert(inpin_idx < aic->num_inpins); /* * The enable registers are used to enable source interrupts. @@ -202,26 +202,21 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, { AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); + const AspeedINTCIRQ *intc_irq; uint32_t reg = offset >> 2; int outpin_idx; int inpin_idx; - uint32_t irq; if (!data) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid data 0\n", __func__); return; } - irq = (offset & 0x0f00) >> 8; - outpin_idx = irq; - inpin_idx = irq; + intc_irq = aspeed_intc_get_irq(aic, reg); + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; - if (inpin_idx >= aic->num_inpins) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Invalid input pin index: %d\n", - __func__, inpin_idx); - return; - } + assert(inpin_idx < aic->num_inpins); /* clear status */ s->regs[reg] &= ~data; @@ -411,6 +406,18 @@ static const TypeInfo aspeed_intc_info = { .abstract = true, }; +static AspeedINTCIRQ aspeed_2700_intc_irqs[ASPEED_INTC_MAX_INPINS] = { + {0, 0, 1, R_GICINT128_EN, R_GICINT128_STATUS}, + {1, 1, 1, R_GICINT129_EN, R_GICINT129_STATUS}, + {2, 2, 1, R_GICINT130_EN, R_GICINT130_STATUS}, + {3, 3, 1, R_GICINT131_EN, R_GICINT131_STATUS}, + {4, 4, 1, R_GICINT132_EN, R_GICINT132_STATUS}, + {5, 5, 1, R_GICINT133_EN, R_GICINT133_STATUS}, + {6, 6, 1, R_GICINT134_EN, R_GICINT134_STATUS}, + {7, 7, 1, R_GICINT135_EN, R_GICINT135_STATUS}, + {8, 8, 1, R_GICINT136_EN, R_GICINT136_STATUS}, +}; + static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -423,6 +430,8 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) aic->mem_size = 0x4000; aic->nr_regs = 0x808 >> 2; aic->reg_offset = 0x1000; + aic->irq_table = aspeed_2700_intc_irqs; + aic->irq_table_count = ARRAY_SIZE(aspeed_2700_intc_irqs); } static const TypeInfo aspeed_2700_intc_info = { diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 2a22e3084615..e6c3a27264b3 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -19,6 +19,14 @@ OBJECT_DECLARE_TYPE(AspeedINTCState, AspeedINTCClass, ASPEED_INTC) #define ASPEED_INTC_MAX_INPINS 9 #define ASPEED_INTC_MAX_OUTPINS 9 +typedef struct AspeedINTCIRQ { + int inpin_idx; + int outpin_idx; + int num_outpins; + uint32_t enable_reg; + uint32_t status_reg; +} AspeedINTCIRQ; + struct AspeedINTCState { /*< private >*/ SysBusDevice parent_obj; @@ -46,6 +54,8 @@ struct AspeedINTCClass { uint64_t nr_regs; uint64_t reg_offset; const MemoryRegionOps *reg_ops; + const AspeedINTCIRQ *irq_table; + int irq_table_count; }; #endif /* ASPEED_INTC_H */ From 5824e8bf6beb226aa5dee94d4e92b671e9b082f1 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:24 +0800 Subject: [PATCH 0723/1179] hw/intc/aspeed: Introduce IRQ handler function to reduce code duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The behavior of the INTC set IRQ is almost identical between INTC and INTCIO. To reduce duplicated code, introduce the "aspeed_intc_set_irq_handler" function to handle both INTC and INTCIO IRQ behavior. No functional change. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-16-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 70 ++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index be24516ec970..3aa97add8bd0 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -76,11 +76,45 @@ static void aspeed_intc_update(AspeedINTCState *s, int inpin_idx, qemu_set_irq(s->output_pins[outpin_idx], level); } +static void aspeed_intc_set_irq_handler(AspeedINTCState *s, + const AspeedINTCIRQ *intc_irq, + uint32_t select) +{ + const char *name = object_get_typename(OBJECT(s)); + uint32_t status_reg; + int outpin_idx; + int inpin_idx; + + status_reg = intc_irq->status_reg; + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; + + if (s->mask[inpin_idx] || s->regs[status_reg]) { + /* + * a. mask is not 0 means in ISR mode + * sources interrupt routine are executing. + * b. status register value is not 0 means previous + * source interrupt does not be executed, yet. + * + * save source interrupt to pending variable. + */ + s->pending[inpin_idx] |= select; + trace_aspeed_intc_pending_irq(name, inpin_idx, s->pending[inpin_idx]); + } else { + /* + * notify firmware which source interrupt are coming + * by setting status register + */ + s->regs[status_reg] = select; + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx, + s->regs[status_reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx, 1); + } +} + /* - * The address of GICINT128 to GICINT136 are from 0x1000 to 0x1804. - * Utilize "address & 0x0f00" to get the irq and irq output pin index - * The value of irq should be 0 to num_inpins. - * The irq 0 indicates GICINT128, irq 1 indicates GICINT129 and so on. + * GICINT128 to GICINT136 map 1:1 to input and output IRQs 0 to 8. + * The value of input IRQ should be between 0 and the number of inputs. */ static void aspeed_intc_set_irq(void *opaque, int irq, int level) { @@ -88,20 +122,15 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); const char *name = object_get_typename(OBJECT(s)); const AspeedINTCIRQ *intc_irq; - uint32_t status_reg; uint32_t select = 0; uint32_t enable; - int outpin_idx; int inpin_idx; int i; assert(irq < aic->num_inpins); intc_irq = &aic->irq_table[irq]; - status_reg = intc_irq->status_reg; - outpin_idx = intc_irq->outpin_idx; inpin_idx = intc_irq->inpin_idx; - trace_aspeed_intc_set_irq(name, inpin_idx, level); enable = s->enable[inpin_idx]; @@ -122,28 +151,7 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) } trace_aspeed_intc_select(name, select); - - if (s->mask[inpin_idx] || s->regs[status_reg]) { - /* - * a. mask is not 0 means in ISR mode - * sources interrupt routine are executing. - * b. status register value is not 0 means previous - * source interrupt does not be executed, yet. - * - * save source interrupt to pending variable. - */ - s->pending[inpin_idx] |= select; - trace_aspeed_intc_pending_irq(name, inpin_idx, s->pending[inpin_idx]); - } else { - /* - * notify firmware which source interrupt are coming - * by setting status register - */ - s->regs[status_reg] = select; - trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx, - s->regs[status_reg]); - aspeed_intc_update(s, inpin_idx, outpin_idx, 1); - } + aspeed_intc_set_irq_handler(s, intc_irq, select); } static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, From 9178ff91f3105d25fef8e595014fbdfba7d9e278 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:25 +0800 Subject: [PATCH 0724/1179] hw/intc/aspeed: Add Support for Multi-Output IRQ Handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This update introduces support for handling multi-output IRQs in the AST2700 interrupt controller (INTC), specifically for GICINT192_201. GICINT192_201 maps 1:10 to input IRQ 0 and output IRQs 0 to 9. Each status bit corresponds to a specific IRQ. Implemented "aspeed_intc_set_irq_handler_multi_outpins" to handle IRQs with multiple output pins. Introduced "aspeed_intc_status_handler_multi_outpins" for managing status registers associated with multi-output IRQs. Added new IRQ definitions for GICINT192_201 in INTC. Adjusted the IRQ array to accommodate 10 input pins and 19 output pins, aligning with the new GICINT192_201 mappings. |------------------------------| | INTC | |inpin[0:0]--------->outpin[0] | |inpin[0:1]--------->outpin[1] | |inpin[0:2]--------->outpin[2] | |inpin[0:3]--------->outpin[3] | orgates[0]-------> |inpin[0:4]--------->outpin[4] | |inpin[0:5]--------->outpin[5] | |inpin[0:6]--------->outpin[6] | |inpin[0:7]--------->outpin[7] | |inpin[0:8]--------->outpin[8] | |inpin[0:9]--------->outpin[9] | | | orgates[1]------> |inpin[1]----------->outpin[10]| orgates[2]------> |inpin[2]----------->outpin[11]| orgates[3]------> |inpin[3]----------->outpin[12]| orgates[4]------> |inpin[4]----------->outpin[13]| orgates[5]------> |inpin[5]----------->outpin[14]| orgates[6]------> |inpin[6]----------->outpin[15]| orgates[7]------> |inpin[7]----------->outpin[16]| orgates[8]------> |inpin[8]----------->outpin[17]| orgates[9]------> |inpin[9]----------->outpin[18]| |------------------------------| Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-17-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 149 ++++++++++++++++++++++++++++++---- hw/intc/trace-events | 1 + include/hw/intc/aspeed_intc.h | 4 +- 3 files changed, 137 insertions(+), 17 deletions(-) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 3aa97add8bd0..f2ca9237ea9a 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -39,6 +39,8 @@ REG32(GICINT135_EN, 0x700) REG32(GICINT135_STATUS, 0x704) REG32(GICINT136_EN, 0x800) REG32(GICINT136_STATUS, 0x804) +REG32(GICINT192_201_EN, 0xB00) +REG32(GICINT192_201_STATUS, 0xB04) static const AspeedINTCIRQ *aspeed_intc_get_irq(AspeedINTCClass *aic, uint32_t reg) @@ -112,9 +114,55 @@ static void aspeed_intc_set_irq_handler(AspeedINTCState *s, } } +static void aspeed_intc_set_irq_handler_multi_outpins(AspeedINTCState *s, + const AspeedINTCIRQ *intc_irq, uint32_t select) +{ + const char *name = object_get_typename(OBJECT(s)); + uint32_t status_reg; + int num_outpins; + int outpin_idx; + int inpin_idx; + int i; + + num_outpins = intc_irq->num_outpins; + status_reg = intc_irq->status_reg; + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; + + for (i = 0; i < num_outpins; i++) { + if (select & BIT(i)) { + if (s->mask[inpin_idx] & BIT(i) || + s->regs[status_reg] & BIT(i)) { + /* + * a. mask bit is not 0 means in ISR mode sources interrupt + * routine are executing. + * b. status bit is not 0 means previous source interrupt + * does not be executed, yet. + * + * save source interrupt to pending bit. + */ + s->pending[inpin_idx] |= BIT(i); + trace_aspeed_intc_pending_irq(name, inpin_idx, + s->pending[inpin_idx]); + } else { + /* + * notify firmware which source interrupt are coming + * by setting status bit + */ + s->regs[status_reg] |= BIT(i); + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx + i, + s->regs[status_reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx + i, 1); + } + } + } +} + /* - * GICINT128 to GICINT136 map 1:1 to input and output IRQs 0 to 8. - * The value of input IRQ should be between 0 and the number of inputs. + * GICINT192_201 maps 1:10 to input IRQ 0 and output IRQs 0 to 9. + * GICINT128 to GICINT136 map 1:1 to input IRQs 1 to 9 and output + * IRQs 10 to 18. The value of input IRQ should be between 0 and + * the number of input pins. */ static void aspeed_intc_set_irq(void *opaque, int irq, int level) { @@ -124,12 +172,14 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) const AspeedINTCIRQ *intc_irq; uint32_t select = 0; uint32_t enable; + int num_outpins; int inpin_idx; int i; assert(irq < aic->num_inpins); intc_irq = &aic->irq_table[irq]; + num_outpins = intc_irq->num_outpins; inpin_idx = intc_irq->inpin_idx; trace_aspeed_intc_set_irq(name, inpin_idx, level); enable = s->enable[inpin_idx]; @@ -151,7 +201,11 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) } trace_aspeed_intc_select(name, select); - aspeed_intc_set_irq_handler(s, intc_irq, select); + if (num_outpins > 1) { + aspeed_intc_set_irq_handler_multi_outpins(s, intc_irq, select); + } else { + aspeed_intc_set_irq_handler(s, intc_irq, select); + } } static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, @@ -261,6 +315,66 @@ static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, } } +static void aspeed_intc_status_handler_multi_outpins(AspeedINTCState *s, + hwaddr offset, uint64_t data) +{ + const char *name = object_get_typename(OBJECT(s)); + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const AspeedINTCIRQ *intc_irq; + uint32_t reg = offset >> 2; + int num_outpins; + int outpin_idx; + int inpin_idx; + int i; + + if (!data) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid data 0\n", __func__); + return; + } + + intc_irq = aspeed_intc_get_irq(aic, reg); + num_outpins = intc_irq->num_outpins; + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; + assert(inpin_idx < aic->num_inpins); + + /* clear status */ + s->regs[reg] &= ~data; + + /* + * The status registers are used for notify sources ISR are executed. + * If one source ISR is executed, it will clear one bit. + * If it clear all bits, it means to initialize this register status + * rather than sources ISR are executed. + */ + if (data == 0xffffffff) { + return; + } + + for (i = 0; i < num_outpins; i++) { + /* All source ISR executions are done from a specific bit */ + if (data & BIT(i)) { + trace_aspeed_intc_all_isr_done_bit(name, inpin_idx, i); + if (s->pending[inpin_idx] & BIT(i)) { + /* + * Handle pending source interrupt. + * Notify firmware which source interrupt is pending + * by setting the status bit. + */ + s->regs[reg] |= BIT(i); + s->pending[inpin_idx] &= ~BIT(i); + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx + i, + s->regs[reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx + i, 1); + } else { + /* clear irq for the specific bit */ + trace_aspeed_intc_clear_irq(name, inpin_idx, outpin_idx + i, 0); + aspeed_intc_update(s, inpin_idx, outpin_idx + i, 0); + } + } + } +} + static uint64_t aspeed_intc_read(void *opaque, hwaddr offset, unsigned int size) { AspeedINTCState *s = ASPEED_INTC(opaque); @@ -293,6 +407,7 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, case R_GICINT134_EN: case R_GICINT135_EN: case R_GICINT136_EN: + case R_GICINT192_201_EN: aspeed_intc_enable_handler(s, offset, data); break; case R_GICINT128_STATUS: @@ -306,6 +421,9 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, case R_GICINT136_STATUS: aspeed_intc_status_handler(s, offset, data); break; + case R_GICINT192_201_STATUS: + aspeed_intc_status_handler_multi_outpins(s, offset, data); + break; default: s->regs[reg] = data; break; @@ -415,15 +533,16 @@ static const TypeInfo aspeed_intc_info = { }; static AspeedINTCIRQ aspeed_2700_intc_irqs[ASPEED_INTC_MAX_INPINS] = { - {0, 0, 1, R_GICINT128_EN, R_GICINT128_STATUS}, - {1, 1, 1, R_GICINT129_EN, R_GICINT129_STATUS}, - {2, 2, 1, R_GICINT130_EN, R_GICINT130_STATUS}, - {3, 3, 1, R_GICINT131_EN, R_GICINT131_STATUS}, - {4, 4, 1, R_GICINT132_EN, R_GICINT132_STATUS}, - {5, 5, 1, R_GICINT133_EN, R_GICINT133_STATUS}, - {6, 6, 1, R_GICINT134_EN, R_GICINT134_STATUS}, - {7, 7, 1, R_GICINT135_EN, R_GICINT135_STATUS}, - {8, 8, 1, R_GICINT136_EN, R_GICINT136_STATUS}, + {0, 0, 10, R_GICINT192_201_EN, R_GICINT192_201_STATUS}, + {1, 10, 1, R_GICINT128_EN, R_GICINT128_STATUS}, + {2, 11, 1, R_GICINT129_EN, R_GICINT129_STATUS}, + {3, 12, 1, R_GICINT130_EN, R_GICINT130_STATUS}, + {4, 13, 1, R_GICINT131_EN, R_GICINT131_STATUS}, + {5, 14, 1, R_GICINT132_EN, R_GICINT132_STATUS}, + {6, 15, 1, R_GICINT133_EN, R_GICINT133_STATUS}, + {7, 16, 1, R_GICINT134_EN, R_GICINT134_STATUS}, + {8, 17, 1, R_GICINT135_EN, R_GICINT135_STATUS}, + {9, 18, 1, R_GICINT136_EN, R_GICINT136_STATUS}, }; static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) @@ -433,10 +552,10 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) dc->desc = "ASPEED 2700 INTC Controller"; aic->num_lines = 32; - aic->num_inpins = 9; - aic->num_outpins = 9; + aic->num_inpins = 10; + aic->num_outpins = 19; aic->mem_size = 0x4000; - aic->nr_regs = 0x808 >> 2; + aic->nr_regs = 0xB08 >> 2; aic->reg_offset = 0x1000; aic->irq_table = aspeed_2700_intc_irqs; aic->irq_table_count = ARRAY_SIZE(aspeed_2700_intc_irqs); diff --git a/hw/intc/trace-events b/hw/intc/trace-events index e97eea820b82..913197a181f7 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -92,6 +92,7 @@ aspeed_intc_enable(const char *s, uint32_t value) "%s: Enable: 0x%x" aspeed_intc_select(const char *s, uint32_t value) "%s: Select: 0x%x" aspeed_intc_mask(const char *s, uint32_t change, uint32_t value) "%s: Mask: 0x%x: 0x%x" aspeed_intc_unmask(const char *s, uint32_t change, uint32_t value) "%s: UnMask: 0x%x: 0x%x" +aspeed_intc_all_isr_done_bit(const char *s, int inpin_idx, int bit) "%s: All source ISR execution are done from specific bit: %d-%d" # arm_gic.c gic_enable_irq(int irq) "irq %d enabled" diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index e6c3a27264b3..85df8c6be94f 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -16,8 +16,8 @@ #define TYPE_ASPEED_2700_INTC TYPE_ASPEED_INTC "-ast2700" OBJECT_DECLARE_TYPE(AspeedINTCState, AspeedINTCClass, ASPEED_INTC) -#define ASPEED_INTC_MAX_INPINS 9 -#define ASPEED_INTC_MAX_OUTPINS 9 +#define ASPEED_INTC_MAX_INPINS 10 +#define ASPEED_INTC_MAX_OUTPINS 19 typedef struct AspeedINTCIRQ { int inpin_idx; From 38ba38d87df3421ee0b28f9dfaf393456861a8e0 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:26 +0800 Subject: [PATCH 0725/1179] hw/intc/aspeed: Add Support for AST2700 INTCIO Controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a new ast2700 INTCIO class to support AST2700 INTCIO. Added new register definitions for INTCIO, including enable and status registers for IRQs GICINT192 through GICINT197. Created a dedicated IRQ array for INTCIO, supporting six input pins and six output pins, aligning with the newly defined registers. Implemented "aspeed_intcio_read" and "aspeed_intcio_write" to handle INTCIO-specific register access. To GICINT196 | ETH1 |-----------| |--------------------------| -------->|0 | | INTCIO | ETH2 | 4| orgates[0]------>|inpin[0]-------->outpin[0]| -------->|1 5| orgates[1]------>|inpin[1]-------->outpin[1]| ETH3 | 6| orgates[2]------>|inpin[2]-------->outpin[2]| -------->|2 19| orgates[3]------>|inpin[3]-------->outpin[3]| UART0 | 20|-->orgates[4]------>|inpin[4]-------->outpin[4]| -------->|7 21| orgates[5]------>|inpin[5]-------->outpin[5]| UART1 | 22| |--------------------------| -------->|8 23| UART2 | 24| -------->|9 25| UART3 | 26| ---------|10 27| UART5 | 28| -------->|11 29| UART6 | | -------->|12 30| UART7 | 31| -------->|13 | UART8 | OR[0:31] | -------->|14 | UART9 | | -------->|15 | UART10 | | -------->|16 | UART11 | | -------->|17 | UART12 | | -------->|18 | |-----------| Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-18-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/intc/aspeed_intc.c | 112 ++++++++++++++++++++++++++++++++++ include/hw/intc/aspeed_intc.h | 1 + 2 files changed, 113 insertions(+) diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index f2ca9237ea9a..3fd417084f5b 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -42,6 +42,26 @@ REG32(GICINT136_STATUS, 0x804) REG32(GICINT192_201_EN, 0xB00) REG32(GICINT192_201_STATUS, 0xB04) +/* + * INTCIO Registers + * + * values below are offset by - 0x100 from datasheet + * because its memory region is start at 0x100 + * + */ +REG32(GICINT192_EN, 0x00) +REG32(GICINT192_STATUS, 0x04) +REG32(GICINT193_EN, 0x10) +REG32(GICINT193_STATUS, 0x14) +REG32(GICINT194_EN, 0x20) +REG32(GICINT194_STATUS, 0x24) +REG32(GICINT195_EN, 0x30) +REG32(GICINT195_STATUS, 0x34) +REG32(GICINT196_EN, 0x40) +REG32(GICINT196_STATUS, 0x44) +REG32(GICINT197_EN, 0x50) +REG32(GICINT197_STATUS, 0x54) + static const AspeedINTCIRQ *aspeed_intc_get_irq(AspeedINTCClass *aic, uint32_t reg) { @@ -432,6 +452,55 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, return; } +static uint64_t aspeed_intcio_read(void *opaque, hwaddr offset, + unsigned int size) +{ + AspeedINTCState *s = ASPEED_INTC(opaque); + const char *name = object_get_typename(OBJECT(s)); + uint32_t reg = offset >> 2; + uint32_t value = 0; + + value = s->regs[reg]; + trace_aspeed_intc_read(name, offset, size, value); + + return value; +} + +static void aspeed_intcio_write(void *opaque, hwaddr offset, uint64_t data, + unsigned size) +{ + AspeedINTCState *s = ASPEED_INTC(opaque); + const char *name = object_get_typename(OBJECT(s)); + uint32_t reg = offset >> 2; + + trace_aspeed_intc_write(name, offset, size, data); + + switch (reg) { + case R_GICINT192_EN: + case R_GICINT193_EN: + case R_GICINT194_EN: + case R_GICINT195_EN: + case R_GICINT196_EN: + case R_GICINT197_EN: + aspeed_intc_enable_handler(s, offset, data); + break; + case R_GICINT192_STATUS: + case R_GICINT193_STATUS: + case R_GICINT194_STATUS: + case R_GICINT195_STATUS: + case R_GICINT196_STATUS: + case R_GICINT197_STATUS: + aspeed_intc_status_handler(s, offset, data); + break; + default: + s->regs[reg] = data; + break; + } + + return; +} + + static const MemoryRegionOps aspeed_intc_ops = { .read = aspeed_intc_read, .write = aspeed_intc_write, @@ -442,6 +511,16 @@ static const MemoryRegionOps aspeed_intc_ops = { } }; +static const MemoryRegionOps aspeed_intcio_ops = { + .read = aspeed_intcio_read, + .write = aspeed_intcio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + static void aspeed_intc_instance_init(Object *obj) { AspeedINTCState *s = ASPEED_INTC(obj); @@ -567,10 +646,43 @@ static const TypeInfo aspeed_2700_intc_info = { .class_init = aspeed_2700_intc_class_init, }; +static AspeedINTCIRQ aspeed_2700_intcio_irqs[ASPEED_INTC_MAX_INPINS] = { + {0, 0, 1, R_GICINT192_EN, R_GICINT192_STATUS}, + {1, 1, 1, R_GICINT193_EN, R_GICINT193_STATUS}, + {2, 2, 1, R_GICINT194_EN, R_GICINT194_STATUS}, + {3, 3, 1, R_GICINT195_EN, R_GICINT195_STATUS}, + {4, 4, 1, R_GICINT196_EN, R_GICINT196_STATUS}, + {5, 5, 1, R_GICINT197_EN, R_GICINT197_STATUS}, +}; + +static void aspeed_2700_intcio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + AspeedINTCClass *aic = ASPEED_INTC_CLASS(klass); + + dc->desc = "ASPEED 2700 INTC IO Controller"; + aic->num_lines = 32; + aic->num_inpins = 6; + aic->num_outpins = 6; + aic->mem_size = 0x400; + aic->nr_regs = 0x58 >> 2; + aic->reg_offset = 0x100; + aic->reg_ops = &aspeed_intcio_ops; + aic->irq_table = aspeed_2700_intcio_irqs; + aic->irq_table_count = ARRAY_SIZE(aspeed_2700_intcio_irqs); +} + +static const TypeInfo aspeed_2700_intcio_info = { + .name = TYPE_ASPEED_2700_INTCIO, + .parent = TYPE_ASPEED_INTC, + .class_init = aspeed_2700_intcio_class_init, +}; + static void aspeed_intc_register_types(void) { type_register_static(&aspeed_intc_info); type_register_static(&aspeed_2700_intc_info); + type_register_static(&aspeed_2700_intcio_info); } type_init(aspeed_intc_register_types); diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 85df8c6be94f..3727ba24be72 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -14,6 +14,7 @@ #define TYPE_ASPEED_INTC "aspeed.intc" #define TYPE_ASPEED_2700_INTC TYPE_ASPEED_INTC "-ast2700" +#define TYPE_ASPEED_2700_INTCIO TYPE_ASPEED_INTC "io-ast2700" OBJECT_DECLARE_TYPE(AspeedINTCState, AspeedINTCClass, ASPEED_INTC) #define ASPEED_INTC_MAX_INPINS 10 From d3b38cbbed845a650819af3ec59d5e463b3fe47a Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:27 +0800 Subject: [PATCH 0726/1179] hw/misc/aspeed_scu: Add Support for AST2700/AST2750 A1 Silicon Revisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added new definitions for AST2700_A1_SILICON_REV and AST2750_A1_SILICON_REV to identify the A1 silicon revisions. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-19-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/misc/aspeed_scu.c | 2 ++ include/hw/misc/aspeed_scu.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index 0581c744f112..76cfd9167161 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -560,6 +560,8 @@ static uint32_t aspeed_silicon_revs[] = { AST2700_A0_SILICON_REV, AST2720_A0_SILICON_REV, AST2750_A0_SILICON_REV, + AST2700_A1_SILICON_REV, + AST2750_A1_SILICON_REV, }; bool is_supported_silicon_rev(uint32_t silicon_rev) diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h index 356be95e4585..684b48b7222a 100644 --- a/include/hw/misc/aspeed_scu.h +++ b/include/hw/misc/aspeed_scu.h @@ -54,6 +54,8 @@ struct AspeedSCUState { #define AST2700_A0_SILICON_REV 0x06000103U #define AST2720_A0_SILICON_REV 0x06000203U #define AST2750_A0_SILICON_REV 0x06000003U +#define AST2700_A1_SILICON_REV 0x06010103U +#define AST2750_A1_SILICON_REV 0x06010003U #define ASPEED_IS_AST2500(si_rev) ((((si_rev) >> 24) & 0xff) == 0x04) From d2c8093567b3681e4439702f129e89156f59afb5 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:28 +0800 Subject: [PATCH 0727/1179] hw/arm/aspeed_ast27x0.c Support AST2700 A1 GIC Interrupt Mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, these IRQ tables support from GIC 128 - 136 for AST2700 A0. These IRQ tables can be reused for AST2700 A1 from GIC 192 - 197. Updates the interrupt mapping to include support for AST2700 A1 by extending the existing mappings to the new GIC range. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-20-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 77 ++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 3cb95210a06f..9e5f4a208453 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -120,21 +120,27 @@ static const int aspeed_soc_ast2700a0_irqmap[] = { }; /* GICINT 128 */ -static const int aspeed_soc_ast2700_gic128_intcmap[] = { +/* GICINT 192 */ +static const int ast2700_gic128_gic192_intcmap[] = { [ASPEED_DEV_LPC] = 0, [ASPEED_DEV_IBT] = 2, [ASPEED_DEV_KCS] = 4, }; +/* GICINT 129 */ +/* GICINT 193 */ + /* GICINT 130 */ -static const int aspeed_soc_ast2700_gic130_intcmap[] = { +/* GICINT 194 */ +static const int ast2700_gic130_gic194_intcmap[] = { [ASPEED_DEV_I2C] = 0, [ASPEED_DEV_ADC] = 16, [ASPEED_DEV_GPIO] = 18, }; /* GICINT 131 */ -static const int aspeed_soc_ast2700_gic131_intcmap[] = { +/* GICINT 195 */ +static const int ast2700_gic131_gic195_intcmap[] = { [ASPEED_DEV_I3C] = 0, [ASPEED_DEV_WDT] = 16, [ASPEED_DEV_FMC] = 25, @@ -142,7 +148,8 @@ static const int aspeed_soc_ast2700_gic131_intcmap[] = { }; /* GICINT 132 */ -static const int aspeed_soc_ast2700_gic132_intcmap[] = { +/* GICINT 196 */ +static const int ast2700_gic132_gic196_intcmap[] = { [ASPEED_DEV_ETH1] = 0, [ASPEED_DEV_ETH2] = 1, [ASPEED_DEV_ETH3] = 2, @@ -161,24 +168,26 @@ static const int aspeed_soc_ast2700_gic132_intcmap[] = { }; /* GICINT 133 */ -static const int aspeed_soc_ast2700_gic133_intcmap[] = { +/* GICINT 197 */ +static const int ast2700_gic133_gic197_intcmap[] = { [ASPEED_DEV_SDHCI] = 1, [ASPEED_DEV_PECI] = 4, }; /* GICINT 128 ~ 136 */ +/* GICINT 192 ~ 201 */ struct gic_intc_irq_info { int irq; const int *ptr; }; -static const struct gic_intc_irq_info aspeed_soc_ast2700_gic_intcmap[] = { - {128, aspeed_soc_ast2700_gic128_intcmap}, +static const struct gic_intc_irq_info ast2700_gic_intcmap[] = { + {128, ast2700_gic128_gic192_intcmap}, {129, NULL}, - {130, aspeed_soc_ast2700_gic130_intcmap}, - {131, aspeed_soc_ast2700_gic131_intcmap}, - {132, aspeed_soc_ast2700_gic132_intcmap}, - {133, aspeed_soc_ast2700_gic133_intcmap}, + {130, ast2700_gic130_gic194_intcmap}, + {131, ast2700_gic131_gic195_intcmap}, + {132, ast2700_gic132_gic196_intcmap}, + {133, ast2700_gic133_gic197_intcmap}, {134, NULL}, {135, NULL}, {136, NULL}, @@ -190,11 +199,11 @@ static qemu_irq aspeed_soc_ast2700_get_irq(AspeedSoCState *s, int dev) AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); int i; - for (i = 0; i < ARRAY_SIZE(aspeed_soc_ast2700_gic_intcmap); i++) { - if (sc->irqmap[dev] == aspeed_soc_ast2700_gic_intcmap[i].irq) { - assert(aspeed_soc_ast2700_gic_intcmap[i].ptr); + for (i = 0; i < ARRAY_SIZE(ast2700_gic_intcmap); i++) { + if (sc->irqmap[dev] == ast2700_gic_intcmap[i].irq) { + assert(ast2700_gic_intcmap[i].ptr); return qdev_get_gpio_in(DEVICE(&a->intc.orgates[i]), - aspeed_soc_ast2700_gic_intcmap[i].ptr[dev]); + ast2700_gic_intcmap[i].ptr[dev]); } } @@ -208,16 +217,17 @@ static qemu_irq aspeed_soc_ast2700_get_irq_index(AspeedSoCState *s, int dev, AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); int i; - for (i = 0; i < ARRAY_SIZE(aspeed_soc_ast2700_gic_intcmap); i++) { - if (sc->irqmap[dev] == aspeed_soc_ast2700_gic_intcmap[i].irq) { - assert(aspeed_soc_ast2700_gic_intcmap[i].ptr); + for (i = 0; i < ARRAY_SIZE(ast2700_gic_intcmap); i++) { + if (sc->irqmap[dev] == ast2700_gic_intcmap[i].irq) { + assert(ast2700_gic_intcmap[i].ptr); return qdev_get_gpio_in(DEVICE(&a->intc.orgates[i]), - aspeed_soc_ast2700_gic_intcmap[i].ptr[dev] + index); + ast2700_gic_intcmap[i].ptr[dev] + index); } } /* - * Invalid orgate index, device irq should be 128 to 136. + * Invalid OR gate index, device IRQ should be between 128 to 136 + * and 192 to 201. */ g_assert_not_reached(); } @@ -534,17 +544,18 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc), 0, sc->memmap[ASPEED_DEV_INTC]); - /* source orgates -> INTC */ + /* irq sources -> orgates -> INTC */ for (i = 0; i < ic->num_inpins; i++) { qdev_connect_gpio_out(DEVICE(&a->intc.orgates[i]), 0, - qdev_get_gpio_in(DEVICE(&a->intc), i)); + qdev_get_gpio_in(DEVICE(&a->intc), i)); } + /* INTC -> GIC192 - GIC201 */ /* INTC -> GIC128 - GIC136 */ for (i = 0; i < ic->num_outpins; i++) { sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc), i, qdev_get_gpio_in(DEVICE(&a->gic), - aspeed_soc_ast2700_gic_intcmap[i].irq)); + ast2700_gic_intcmap[i].irq)); } /* SRAM */ @@ -695,10 +706,22 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) for (i = 0; i < ASPEED_I2C_GET_CLASS(&s->i2c)->num_busses; i++) { /* * The AST2700 I2C controller has one source INTC per bus. - * I2C buses interrupt are connected to GICINT130_INTC - * from bit 0 to bit 15. - * I2C bus 0 is connected to GICINT130_INTC at bit 0. - * I2C bus 15 is connected to GICINT130_INTC at bit 15. + * + * For AST2700 A0: + * I2C bus interrupts are connected to the OR gate from bit 0 to bit + * 15, and the OR gate output pin is connected to the input pin of + * GICINT130 of INTC (CPU Die). Then, the output pin is connected to + * the GIC. + * + * For AST2700 A1: + * I2C bus interrupts are connected to the OR gate from bit 0 to bit + * 15, and the OR gate output pin is connected to the input pin of + * GICINT194 of INTCIO (IO Die). Then, the output pin is connected + * to the INTC (CPU Die) input pin, and its output pin is connected + * to the GIC. + * + * I2C bus 0 is connected to the OR gate at bit 0. + * I2C bus 15 is connected to the OR gate at bit 15. */ irq = aspeed_soc_ast2700_get_irq_index(s, ASPEED_DEV_I2C, i); sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c.busses[i]), 0, irq); From cd99eda62a5129305d186468f8056618d0b3bd87 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:29 +0800 Subject: [PATCH 0728/1179] hw/arm/aspeed_ast27x0: Define an Array of AspeedINTCState with Two Instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated Aspeed27x0SoCState to include an intc[2] array instead of a single AspeedINTCState instance. Modified aspeed_soc_ast2700_get_irq and aspeed_soc_ast2700_get_irq_index to correctly reference the corresponding interrupt controller instance and OR gate index. Currently, only GIC 192 to 201 are supported, and their source interrupts are from INTCIO and connected to INTC at input pin 0 and output pins 0 to 9 for GIC 192-201. To support both AST2700 A1 and A0, INTC input pins 1 to 9 and output pins 10 to 18 remain to support GIC 128-136, which source interrupts from INTC. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-21-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 58 +++++++++++++++++++++++++------------ include/hw/arm/aspeed_soc.h | 2 +- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 9e5f4a208453..6cffa5b9a05e 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -178,32 +178,48 @@ static const int ast2700_gic133_gic197_intcmap[] = { /* GICINT 192 ~ 201 */ struct gic_intc_irq_info { int irq; + int intc_idx; + int orgate_idx; const int *ptr; }; static const struct gic_intc_irq_info ast2700_gic_intcmap[] = { - {128, ast2700_gic128_gic192_intcmap}, - {129, NULL}, - {130, ast2700_gic130_gic194_intcmap}, - {131, ast2700_gic131_gic195_intcmap}, - {132, ast2700_gic132_gic196_intcmap}, - {133, ast2700_gic133_gic197_intcmap}, - {134, NULL}, - {135, NULL}, - {136, NULL}, + {192, 1, 0, ast2700_gic128_gic192_intcmap}, + {193, 1, 1, NULL}, + {194, 1, 2, ast2700_gic130_gic194_intcmap}, + {195, 1, 3, ast2700_gic131_gic195_intcmap}, + {196, 1, 4, ast2700_gic132_gic196_intcmap}, + {197, 1, 5, ast2700_gic133_gic197_intcmap}, + {198, 1, 6, NULL}, + {199, 1, 7, NULL}, + {200, 1, 8, NULL}, + {201, 1, 9, NULL}, + {128, 0, 1, ast2700_gic128_gic192_intcmap}, + {129, 0, 2, NULL}, + {130, 0, 3, ast2700_gic130_gic194_intcmap}, + {131, 0, 4, ast2700_gic131_gic195_intcmap}, + {132, 0, 5, ast2700_gic132_gic196_intcmap}, + {133, 0, 6, ast2700_gic133_gic197_intcmap}, + {134, 0, 7, NULL}, + {135, 0, 8, NULL}, + {136, 0, 9, NULL}, }; static qemu_irq aspeed_soc_ast2700_get_irq(AspeedSoCState *s, int dev) { Aspeed27x0SoCState *a = ASPEED27X0_SOC(s); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); + int or_idx; + int idx; int i; for (i = 0; i < ARRAY_SIZE(ast2700_gic_intcmap); i++) { if (sc->irqmap[dev] == ast2700_gic_intcmap[i].irq) { assert(ast2700_gic_intcmap[i].ptr); - return qdev_get_gpio_in(DEVICE(&a->intc.orgates[i]), - ast2700_gic_intcmap[i].ptr[dev]); + or_idx = ast2700_gic_intcmap[i].orgate_idx; + idx = ast2700_gic_intcmap[i].intc_idx; + return qdev_get_gpio_in(DEVICE(&a->intc[idx].orgates[or_idx]), + ast2700_gic_intcmap[i].ptr[dev]); } } @@ -215,12 +231,16 @@ static qemu_irq aspeed_soc_ast2700_get_irq_index(AspeedSoCState *s, int dev, { Aspeed27x0SoCState *a = ASPEED27X0_SOC(s); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); + int or_idx; + int idx; int i; for (i = 0; i < ARRAY_SIZE(ast2700_gic_intcmap); i++) { if (sc->irqmap[dev] == ast2700_gic_intcmap[i].irq) { assert(ast2700_gic_intcmap[i].ptr); - return qdev_get_gpio_in(DEVICE(&a->intc.orgates[i]), + or_idx = ast2700_gic_intcmap[i].orgate_idx; + idx = ast2700_gic_intcmap[i].intc_idx; + return qdev_get_gpio_in(DEVICE(&a->intc[idx].orgates[or_idx]), ast2700_gic_intcmap[i].ptr[dev] + index); } } @@ -390,7 +410,7 @@ static void aspeed_soc_ast2700_init(Object *obj) object_initialize_child(obj, "sli", &s->sli, TYPE_ASPEED_2700_SLI); object_initialize_child(obj, "sliio", &s->sliio, TYPE_ASPEED_2700_SLIIO); - object_initialize_child(obj, "intc", &a->intc, TYPE_ASPEED_2700_INTC); + object_initialize_child(obj, "intc", &a->intc[0], TYPE_ASPEED_2700_INTC); snprintf(typename, sizeof(typename), "aspeed.adc-%s", socname); object_initialize_child(obj, "adc", &s->adc, typename); @@ -506,7 +526,7 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) Aspeed27x0SoCState *a = ASPEED27X0_SOC(dev); AspeedSoCState *s = ASPEED_SOC(dev); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); - AspeedINTCClass *ic = ASPEED_INTC_GET_CLASS(&a->intc); + AspeedINTCClass *ic = ASPEED_INTC_GET_CLASS(&a->intc[0]); g_autofree char *sram_name = NULL; qemu_irq irq; @@ -537,23 +557,23 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) } /* INTC */ - if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc), errp)) { + if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) { return; } - aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc), 0, + aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc[0]), 0, sc->memmap[ASPEED_DEV_INTC]); /* irq sources -> orgates -> INTC */ for (i = 0; i < ic->num_inpins; i++) { - qdev_connect_gpio_out(DEVICE(&a->intc.orgates[i]), 0, - qdev_get_gpio_in(DEVICE(&a->intc), i)); + qdev_connect_gpio_out(DEVICE(&a->intc[0].orgates[i]), 0, + qdev_get_gpio_in(DEVICE(&a->intc[0]), i)); } /* INTC -> GIC192 - GIC201 */ /* INTC -> GIC128 - GIC136 */ for (i = 0; i < ic->num_outpins; i++) { - sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc), i, + sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc[0]), i, qdev_get_gpio_in(DEVICE(&a->gic), ast2700_gic_intcmap[i].irq)); } diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index 4a8881ca8b57..066d2fcc204b 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -128,7 +128,7 @@ struct Aspeed27x0SoCState { AspeedSoCState parent; ARMCPU cpu[ASPEED_CPUS_NUM]; - AspeedINTCState intc; + AspeedINTCState intc[2]; GICv3State gic; MemoryRegion dram_empty; }; From 8107448de709a64362c56687764fbd41587c9de9 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:30 +0800 Subject: [PATCH 0729/1179] hw/arm/aspeed_ast27x0: Support two levels of INTC controllers for AST2700 A1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The design of INTC controllers has significantly changed in AST2700 A1. There are a total of 480 interrupt sources in AST2700 A1. For interrupt numbers from 0 to 127, they can route directly to PSP, SSP, and TSP. Due to the limitation of interrupt numbers of processors, the interrupts are merged every 32 sources for interrupt numbers greater than 127. There are two levels of interrupt controllers, INTC(CPUD Die) and INTCIO (IO Die). The interrupt sources of INTC are the interrupt numbers from INTC_0 to INTC_127 and interrupts from INTCIO. The interrupt sources of INTCIO are the interrupt numbers greater than INTC_127. INTC_IO controls the interrupts INTC_128 to INTC_319 only. Currently, only GIC 192 to 201 are supported, and their source interrupts are from INTCIO and connected to INTC at input pin 0 and output pins 0 to 9 for GIC 192-201. The design of the orgates for GICINT 196 is as follows: It has interrupt sources ranging from 0 to 31, with its output pin connected to INTCIO "T0 GICINT_196". The output pin is then connected to INTC "GIC_192_201" at bit 4, and its bit 4 output should be connected to GIC 196. The design of INTC GIC_192_201 have 10 output pins, mapped as following: Bit 0 -> GIC 192 Bit 1 -> GIC 193 Bit 2 -> GIC 194 Bit 3 -> GIC 195 Bit 4 -> GIC 196 To support both AST2700 A1 and A0, INTC input pins 1 to 9 and output pins 10 to 18 remain to support GIC 128-136, which source interrupts from INTC. These will be removed if we decide not to support AST2700 A0 in the future. |-------------------------------------------------------------------------------------------------------| | AST2700 A1 Design | | To GICINT196 | | | | ETH1 |-----------| |--------------------------| |--------------| | | -------->|0 | | INTCIO | | orgates[0] | | | ETH2 | 4| orgates[0]------>|inpin[0]-------->outpin[0]|------->| 0 | | | -------->|1 5| orgates[1]------>|inpin[1]-------->outpin[1]|------->| 1 | | | ETH3 | 6| orgates[2]------>|inpin[2]-------->outpin[2]|------->| 2 | | | -------->|2 19| orgates[3]------>|inpin[3]-------->outpin[3]|------->| 3 OR[0:9] |-----| | | UART0 | 20|-->orgates[4]------>|inpin[4]-------->outpin[4]|------->| 4 | | | | -------->|7 21| orgates[5]------>|inpin[5]-------->outpin[5]|------->| 5 | | | | UART1 | 22| orgates[6]------>|inpin[6]-------->outpin[6]|------->| 6 | | | | -------->|8 23| orgates[7]------>|inpin[7]-------->outpin[7]|------->| 7 | | | | UART2 | 24| orgates[8]------>|inpin[8]-------->outpin[8]|------->| 8 | | | | -------->|9 25| orgates[9]------>|inpin[9]-------->outpin[9]|------->| 9 | | | | UART3 | 26| |--------------------------| |--------------| | | | ---------|10 27| | | | UART5 | 28| | | | -------->|11 29| | | | UART6 | | | | | -------->|12 30| |-----------------------------------------------------------------------| | | UART7 | 31| | | | -------->|13 | | | | UART8 | OR[0:31] | | |------------------------------| |----------| | | -------->|14 | | | INTC | | GIC | | | UART9 | | | |inpin[0:0]--------->outpin[0] |---------->|192 | | | -------->|15 | | |inpin[0:1]--------->outpin[1] |---------->|193 | | | UART10 | | | |inpin[0:2]--------->outpin[2] |---------->|194 | | | -------->|16 | | |inpin[0:3]--------->outpin[3] |---------->|195 | | | UART11 | | |--------------> |inpin[0:4]--------->outpin[4] |---------->|196 | | | -------->|17 | |inpin[0:5]--------->outpin[5] |---------->|197 | | | UART12 | | |inpin[0:6]--------->outpin[6] |---------->|198 | | | -------->|18 | |inpin[0:7]--------->outpin[7] |---------->|199 | | | |-----------| |inpin[0:8]--------->outpin[8] |---------->|200 | | | |inpin[0:9]--------->outpin[9] |---------->|201 | | |-------------------------------------------------------------------------------------------------------| |-------------------------------------------------------------------------------------------------------| | ETH1 |-----------| orgates[1]------->|inpin[1]----------->outpin[10]|---------->|128 | | | -------->|0 | orgates[2]------->|inpin[2]----------->outpin[11]|---------->|129 | | | ETH2 | 4| orgates[3]------->|inpin[3]----------->outpin[12]|---------->|130 | | | -------->|1 5| orgates[4]------->|inpin[4]----------->outpin[13]|---------->|131 | | | ETH3 | 6|---->orgates[5]------->|inpin[5]----------->outpin[14]|---------->|132 | | | -------->|2 19| orgates[6]------->|inpin[6]----------->outpin[15]|---------->|133 | | | UART0 | 20| orgates[7]------->|inpin[7]----------->outpin[16]|---------->|134 | | | -------->|7 21| orgates[8]------->|inpin[8]----------->outpin[17]|---------->|135 | | | UART1 | 22| orgates[9]------->|inpin[9]----------->outpin[18]|---------->|136 | | | -------->|8 23| |------------------------------| |----------| | | UART2 | 24| | | -------->|9 25| AST2700 A0 Design | | UART3 | 26| | | -------->|10 27| | | UART5 | 28| | | -------->|11 29| GICINT132 | | UART6 | | | | -------->|12 30| | | UART7 | 31| | | -------->|13 | | | UART8 | OR[0:31] | | | -------->|14 | | | UART9 | | | | -------->|15 | | | UART10 | | | | -------->|16 | | | UART11 | | | | -------->|17 | | | UART12 | | | | -------->|18 | | | |-----------| | | | |-------------------------------------------------------------------------------------------------------| Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-22-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 24 ++++++++++++++++++++++++ include/hw/arm/aspeed_soc.h | 1 + 2 files changed, 25 insertions(+) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 6cffa5b9a05e..8ed57d23ef1c 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -57,6 +57,7 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = { [ASPEED_DEV_ETH3] = 0x14070000, [ASPEED_DEV_EMMC] = 0x12090000, [ASPEED_DEV_INTC] = 0x12100000, + [ASPEED_DEV_INTCIO] = 0x14C18000, [ASPEED_DEV_SLI] = 0x12C17000, [ASPEED_DEV_SLIIO] = 0x14C1E000, [ASPEED_GIC_DIST] = 0x12200000, @@ -411,6 +412,8 @@ static void aspeed_soc_ast2700_init(Object *obj) object_initialize_child(obj, "sli", &s->sli, TYPE_ASPEED_2700_SLI); object_initialize_child(obj, "sliio", &s->sliio, TYPE_ASPEED_2700_SLIIO); object_initialize_child(obj, "intc", &a->intc[0], TYPE_ASPEED_2700_INTC); + object_initialize_child(obj, "intcio", &a->intc[1], + TYPE_ASPEED_2700_INTCIO); snprintf(typename, sizeof(typename), "aspeed.adc-%s", socname); object_initialize_child(obj, "adc", &s->adc, typename); @@ -527,6 +530,7 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) AspeedSoCState *s = ASPEED_SOC(dev); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); AspeedINTCClass *ic = ASPEED_INTC_GET_CLASS(&a->intc[0]); + AspeedINTCClass *icio = ASPEED_INTC_GET_CLASS(&a->intc[1]); g_autofree char *sram_name = NULL; qemu_irq irq; @@ -564,6 +568,14 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc[0]), 0, sc->memmap[ASPEED_DEV_INTC]); + /* INTCIO */ + if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[1]), errp)) { + return; + } + + aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc[1]), 0, + sc->memmap[ASPEED_DEV_INTCIO]); + /* irq sources -> orgates -> INTC */ for (i = 0; i < ic->num_inpins; i++) { qdev_connect_gpio_out(DEVICE(&a->intc[0].orgates[i]), 0, @@ -578,6 +590,18 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) ast2700_gic_intcmap[i].irq)); } + /* irq source -> orgates -> INTCIO */ + for (i = 0; i < icio->num_inpins; i++) { + qdev_connect_gpio_out(DEVICE(&a->intc[1].orgates[i]), 0, + qdev_get_gpio_in(DEVICE(&a->intc[1]), i)); + } + + /* INTCIO -> INTC */ + for (i = 0; i < icio->num_outpins; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc[1]), i, + qdev_get_gpio_in(DEVICE(&a->intc[0].orgates[0]), i)); + } + /* SRAM */ sram_name = g_strdup_printf("aspeed.sram.%d", CPU(&a->cpu[0])->cpu_index); if (!memory_region_init_ram(&s->sram, OBJECT(s), sram_name, sc->sram_size, diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index 066d2fcc204b..f899356ed94c 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -194,6 +194,7 @@ enum { ASPEED_DEV_EHCI2, ASPEED_DEV_VIC, ASPEED_DEV_INTC, + ASPEED_DEV_INTCIO, ASPEED_DEV_SDMC, ASPEED_DEV_SCU, ASPEED_DEV_ADC, From 6de4aa8dc54451e5902658648fd3d268284c45e9 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:31 +0800 Subject: [PATCH 0730/1179] hw/arm/aspeed_ast27x0: Add SoC Support for AST2700 A1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The memory map for AST2700 A1 remains compatible with AST2700 A0. However, the IRQ mapping has been updated for AST2700 A1, with GIC interrupts now ranging from 192 to 201. Add a new IRQ map table for AST2700 A1. Add "aspeed_soc_ast2700a1_class_init" to initialize the AST2700 A1 SoC. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-23-jamin_lin@aspeedtech.com [ clg: Removed sc->name ] Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 8ed57d23ef1c..682ab9bf8a38 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -120,6 +120,52 @@ static const int aspeed_soc_ast2700a0_irqmap[] = { [ASPEED_DEV_SDHCI] = 133, }; +static const int aspeed_soc_ast2700a1_irqmap[] = { + [ASPEED_DEV_SDMC] = 0, + [ASPEED_DEV_HACE] = 4, + [ASPEED_DEV_XDMA] = 5, + [ASPEED_DEV_UART4] = 8, + [ASPEED_DEV_SCU] = 12, + [ASPEED_DEV_RTC] = 13, + [ASPEED_DEV_EMMC] = 15, + [ASPEED_DEV_TIMER1] = 16, + [ASPEED_DEV_TIMER2] = 17, + [ASPEED_DEV_TIMER3] = 18, + [ASPEED_DEV_TIMER4] = 19, + [ASPEED_DEV_TIMER5] = 20, + [ASPEED_DEV_TIMER6] = 21, + [ASPEED_DEV_TIMER7] = 22, + [ASPEED_DEV_TIMER8] = 23, + [ASPEED_DEV_DP] = 28, + [ASPEED_DEV_LPC] = 192, + [ASPEED_DEV_IBT] = 192, + [ASPEED_DEV_KCS] = 192, + [ASPEED_DEV_I2C] = 194, + [ASPEED_DEV_ADC] = 194, + [ASPEED_DEV_GPIO] = 194, + [ASPEED_DEV_FMC] = 195, + [ASPEED_DEV_WDT] = 195, + [ASPEED_DEV_PWM] = 195, + [ASPEED_DEV_I3C] = 195, + [ASPEED_DEV_UART0] = 196, + [ASPEED_DEV_UART1] = 196, + [ASPEED_DEV_UART2] = 196, + [ASPEED_DEV_UART3] = 196, + [ASPEED_DEV_UART5] = 196, + [ASPEED_DEV_UART6] = 196, + [ASPEED_DEV_UART7] = 196, + [ASPEED_DEV_UART8] = 196, + [ASPEED_DEV_UART9] = 196, + [ASPEED_DEV_UART10] = 196, + [ASPEED_DEV_UART11] = 196, + [ASPEED_DEV_UART12] = 196, + [ASPEED_DEV_ETH1] = 196, + [ASPEED_DEV_ETH2] = 196, + [ASPEED_DEV_ETH3] = 196, + [ASPEED_DEV_PECI] = 197, + [ASPEED_DEV_SDHCI] = 197, +}; + /* GICINT 128 */ /* GICINT 192 */ static const int ast2700_gic128_gic192_intcmap[] = { @@ -864,6 +910,33 @@ static void aspeed_soc_ast2700a0_class_init(ObjectClass *oc, void *data) sc->get_irq = aspeed_soc_ast2700_get_irq; } +static void aspeed_soc_ast2700a1_class_init(ObjectClass *oc, void *data) +{ + static const char * const valid_cpu_types[] = { + ARM_CPU_TYPE_NAME("cortex-a35"), + NULL + }; + DeviceClass *dc = DEVICE_CLASS(oc); + AspeedSoCClass *sc = ASPEED_SOC_CLASS(oc); + + /* Reason: The Aspeed SoC can only be instantiated from a board */ + dc->user_creatable = false; + dc->realize = aspeed_soc_ast2700_realize; + + sc->valid_cpu_types = valid_cpu_types; + sc->silicon_rev = AST2700_A1_SILICON_REV; + sc->sram_size = 0x20000; + sc->spis_num = 3; + sc->wdts_num = 8; + sc->macs_num = 3; + sc->uarts_num = 13; + sc->num_cpus = 4; + sc->uarts_base = ASPEED_DEV_UART0; + sc->irqmap = aspeed_soc_ast2700a1_irqmap; + sc->memmap = aspeed_soc_ast2700_memmap; + sc->get_irq = aspeed_soc_ast2700_get_irq; +} + static const TypeInfo aspeed_soc_ast27x0_types[] = { { .name = TYPE_ASPEED27X0_SOC, @@ -876,6 +949,12 @@ static const TypeInfo aspeed_soc_ast27x0_types[] = { .instance_init = aspeed_soc_ast2700_init, .class_init = aspeed_soc_ast2700a0_class_init, }, + { + .name = "ast2700-a1", + .parent = TYPE_ASPEED27X0_SOC, + .instance_init = aspeed_soc_ast2700_init, + .class_init = aspeed_soc_ast2700a1_class_init, + }, }; DEFINE_TYPES(aspeed_soc_ast27x0_types) From 498c519eb7761b09adf2d2e863cf4a70b186005f Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:32 +0800 Subject: [PATCH 0731/1179] hw/arm/aspeed: Add Machine Support for AST2700 A1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce "aspeed_machine_ast2700a1_evb_class_init" to initialize the AST2700 A1 EVB. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-24-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index 18f7c450dadd..82f42582fa3c 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -1693,6 +1693,26 @@ static void aspeed_machine_ast2700a0_evb_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); } + +static void aspeed_machine_ast2700a1_evb_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc); + + mc->desc = "Aspeed AST2700 A1 EVB (Cortex-A35)"; + amc->soc_name = "ast2700-a1"; + amc->hw_strap1 = AST2700_EVB_HW_STRAP1; + amc->hw_strap2 = AST2700_EVB_HW_STRAP2; + amc->fmc_model = "w25q01jvq"; + amc->spi_model = "w25q512jv"; + amc->num_cs = 2; + amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON; + amc->uart_default = ASPEED_DEV_UART12; + amc->i2c_init = ast2700_evb_i2c_init; + mc->auto_create_sdcard = true; + mc->default_ram_size = 1 * GiB; + aspeed_machine_class_init_cpus_defaults(mc); +} #endif static void aspeed_machine_qcom_dc_scm_v1_class_init(ObjectClass *oc, @@ -1821,6 +1841,10 @@ static const TypeInfo aspeed_machine_types[] = { .name = MACHINE_TYPE_NAME("ast2700a0-evb"), .parent = TYPE_ASPEED_MACHINE, .class_init = aspeed_machine_ast2700a0_evb_class_init, + }, { + .name = MACHINE_TYPE_NAME("ast2700a1-evb"), + .parent = TYPE_ASPEED_MACHINE, + .class_init = aspeed_machine_ast2700a1_evb_class_init, #endif }, { .name = TYPE_ASPEED_MACHINE, From ecc1a4e966b5d4ce1732ee223eaf40ef37e9a30b Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:33 +0800 Subject: [PATCH 0732/1179] hw/arm/aspeed_ast27x0: Sort the memmap table by mapping address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To improve readability, sort the memmap table by mapping address Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-25-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- hw/arm/aspeed_ast27x0.c | 54 ++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 682ab9bf8a38..dce7255a2c0d 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -24,16 +24,40 @@ #include "qemu/log.h" static const hwaddr aspeed_soc_ast2700_memmap[] = { - [ASPEED_DEV_SPI_BOOT] = 0x100000000, [ASPEED_DEV_SRAM] = 0x10000000, + [ASPEED_DEV_HACE] = 0x12070000, + [ASPEED_DEV_EMMC] = 0x12090000, + [ASPEED_DEV_INTC] = 0x12100000, + [ASPEED_GIC_DIST] = 0x12200000, + [ASPEED_GIC_REDIST] = 0x12280000, [ASPEED_DEV_SDMC] = 0x12C00000, [ASPEED_DEV_SCU] = 0x12C02000, + [ASPEED_DEV_RTC] = 0x12C0F000, + [ASPEED_DEV_TIMER1] = 0x12C10000, + [ASPEED_DEV_SLI] = 0x12C17000, + [ASPEED_DEV_UART4] = 0X12C1A000, + [ASPEED_DEV_FMC] = 0x14000000, + [ASPEED_DEV_SPI0] = 0x14010000, + [ASPEED_DEV_SPI1] = 0x14020000, + [ASPEED_DEV_SPI2] = 0x14030000, + [ASPEED_DEV_MII1] = 0x14040000, + [ASPEED_DEV_MII2] = 0x14040008, + [ASPEED_DEV_MII3] = 0x14040010, + [ASPEED_DEV_ETH1] = 0x14050000, + [ASPEED_DEV_ETH2] = 0x14060000, + [ASPEED_DEV_ETH3] = 0x14070000, + [ASPEED_DEV_SDHCI] = 0x14080000, + [ASPEED_DEV_ADC] = 0x14C00000, [ASPEED_DEV_SCUIO] = 0x14C02000, + [ASPEED_DEV_GPIO] = 0x14C0B000, + [ASPEED_DEV_I2C] = 0x14C0F000, + [ASPEED_DEV_INTCIO] = 0x14C18000, + [ASPEED_DEV_SLIIO] = 0x14C1E000, + [ASPEED_DEV_VUART] = 0X14C30000, [ASPEED_DEV_UART0] = 0X14C33000, [ASPEED_DEV_UART1] = 0X14C33100, [ASPEED_DEV_UART2] = 0X14C33200, [ASPEED_DEV_UART3] = 0X14C33300, - [ASPEED_DEV_UART4] = 0X12C1A000, [ASPEED_DEV_UART5] = 0X14C33400, [ASPEED_DEV_UART6] = 0X14C33500, [ASPEED_DEV_UART7] = 0X14C33600, @@ -43,32 +67,8 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = { [ASPEED_DEV_UART11] = 0X14C33A00, [ASPEED_DEV_UART12] = 0X14C33B00, [ASPEED_DEV_WDT] = 0x14C37000, - [ASPEED_DEV_VUART] = 0X14C30000, - [ASPEED_DEV_FMC] = 0x14000000, - [ASPEED_DEV_SPI0] = 0x14010000, - [ASPEED_DEV_SPI1] = 0x14020000, - [ASPEED_DEV_SPI2] = 0x14030000, + [ASPEED_DEV_SPI_BOOT] = 0x100000000, [ASPEED_DEV_SDRAM] = 0x400000000, - [ASPEED_DEV_MII1] = 0x14040000, - [ASPEED_DEV_MII2] = 0x14040008, - [ASPEED_DEV_MII3] = 0x14040010, - [ASPEED_DEV_ETH1] = 0x14050000, - [ASPEED_DEV_ETH2] = 0x14060000, - [ASPEED_DEV_ETH3] = 0x14070000, - [ASPEED_DEV_EMMC] = 0x12090000, - [ASPEED_DEV_INTC] = 0x12100000, - [ASPEED_DEV_INTCIO] = 0x14C18000, - [ASPEED_DEV_SLI] = 0x12C17000, - [ASPEED_DEV_SLIIO] = 0x14C1E000, - [ASPEED_GIC_DIST] = 0x12200000, - [ASPEED_GIC_REDIST] = 0x12280000, - [ASPEED_DEV_ADC] = 0x14C00000, - [ASPEED_DEV_I2C] = 0x14C0F000, - [ASPEED_DEV_GPIO] = 0x14C0B000, - [ASPEED_DEV_RTC] = 0x12C0F000, - [ASPEED_DEV_SDHCI] = 0x14080000, - [ASPEED_DEV_TIMER1] = 0x12C10000, - [ASPEED_DEV_HACE] = 0x12070000, }; #define AST2700_MAX_IRQ 256 From 827eecb0e8d3cda71c5529909345acda266b2e6d Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:34 +0800 Subject: [PATCH 0733/1179] tests/functional/aspeed: Introduce start_ast2700_test API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a new method "start_ast2700_test" to the "AST2x00MachineSDK" class and this method centralizes the logic for starting the AST2700 test, making it reusable for different test cases. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-26-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- tests/functional/test_aarch64_aspeed.py | 29 +++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/functional/test_aarch64_aspeed.py b/tests/functional/test_aarch64_aspeed.py index 9595498ace79..e1ad7fd4708c 100755 --- a/tests/functional/test_aarch64_aspeed.py +++ b/tests/functional/test_aarch64_aspeed.py @@ -31,33 +31,29 @@ def do_test_aarch64_aspeed_sdk_start(self, image): 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.03/ast2700-default-obmc.tar.gz', '91225f50d255e2905ba8d8e0c80b71b9d157c3609770c7a740cd786370d85a77') - def test_aarch64_ast2700_evb_sdk_v09_03(self): - self.set_machine('ast2700-evb') - - self.archive_extract(self.ASSET_SDK_V903_AST2700) - + def start_ast2700_test(self, name): num_cpu = 4 - uboot_size = os.path.getsize(self.scratch_file('ast2700-default', + uboot_size = os.path.getsize(self.scratch_file(name, 'u-boot-nodtb.bin')) uboot_dtb_load_addr = hex(0x400000000 + uboot_size) load_images_list = [ { 'addr': '0x400000000', - 'file': self.scratch_file('ast2700-default', + 'file': self.scratch_file(name, 'u-boot-nodtb.bin') }, { 'addr': str(uboot_dtb_load_addr), - 'file': self.scratch_file('ast2700-default', 'u-boot.dtb') + 'file': self.scratch_file(name, 'u-boot.dtb') }, { 'addr': '0x430000000', - 'file': self.scratch_file('ast2700-default', 'bl31.bin') + 'file': self.scratch_file(name, 'bl31.bin') }, { 'addr': '0x430080000', - 'file': self.scratch_file('ast2700-default', 'optee', + 'file': self.scratch_file(name, 'optee', 'tee-raw.bin') } ] @@ -76,13 +72,12 @@ def test_aarch64_ast2700_evb_sdk_v09_03(self): self.vm.add_args('-device', 'tmp105,bus=aspeed.i2c.bus.1,address=0x4d,id=tmp-test') self.do_test_aarch64_aspeed_sdk_start( - self.scratch_file('ast2700-default', 'image-bmc')) + self.scratch_file(name, 'image-bmc')) - wait_for_console_pattern(self, 'ast2700-default login:') + wait_for_console_pattern(self, f'{name} login:') exec_command_and_wait_for_pattern(self, 'root', 'Password:') - exec_command_and_wait_for_pattern(self, - '0penBmc', 'root@ast2700-default:~#') + exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#') exec_command_and_wait_for_pattern(self, 'echo lm75 0x4d > /sys/class/i2c-dev/i2c-1/device/new_device ', @@ -94,6 +89,12 @@ def test_aarch64_ast2700_evb_sdk_v09_03(self): exec_command_and_wait_for_pattern(self, 'cat /sys/class/hwmon/hwmon20/temp1_input', '18000') + def test_aarch64_ast2700_evb_sdk_v09_03(self): + self.set_machine('ast2700-evb') + + self.archive_extract(self.ASSET_SDK_V903_AST2700) + self.start_ast2700_test('ast2700-default') + if __name__ == '__main__': QemuSystemTest.main() From 8241d6f8d5b7a423dd4289b8140934d96819d019 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:35 +0800 Subject: [PATCH 0734/1179] tests/functional/aspeed: Update temperature hwmon path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modified the temperature hwmon path to use a wildcard to handle different SDK versions: "cat /sys/bus/i2c/devices/1-004d/hwmon/hwmon*/temp1_input". Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-27-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- tests/functional/test_aarch64_aspeed.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/test_aarch64_aspeed.py b/tests/functional/test_aarch64_aspeed.py index e1ad7fd4708c..07b0c7c1fd27 100755 --- a/tests/functional/test_aarch64_aspeed.py +++ b/tests/functional/test_aarch64_aspeed.py @@ -83,11 +83,11 @@ def start_ast2700_test(self, name): 'echo lm75 0x4d > /sys/class/i2c-dev/i2c-1/device/new_device ', 'i2c i2c-1: new_device: Instantiated device lm75 at 0x4d'); exec_command_and_wait_for_pattern(self, - 'cat /sys/class/hwmon/hwmon20/temp1_input', '0') + 'cat /sys/bus/i2c/devices/1-004d/hwmon/hwmon*/temp1_input', '0') self.vm.cmd('qom-set', path='/machine/peripheral/tmp-test', property='temperature', value=18000) exec_command_and_wait_for_pattern(self, - 'cat /sys/class/hwmon/hwmon20/temp1_input', '18000') + 'cat /sys/bus/i2c/devices/1-004d/hwmon/hwmon*/temp1_input', '18000') def test_aarch64_ast2700_evb_sdk_v09_03(self): self.set_machine('ast2700-evb') From c77ec95a4b51ed36156c94e06fac056d3dd620b2 Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:36 +0800 Subject: [PATCH 0735/1179] tests/functional/aspeed: Update test ASPEED SDK v09.05 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In ASPEED SDK v09.05, the naming convention for pre-built images has been updated. The pre-built image for AST2700 A0 has been renamed to ast2700-a0-default, while ast2700-default is now used for AST2700 A1. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-28-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- tests/functional/test_aarch64_aspeed.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional/test_aarch64_aspeed.py b/tests/functional/test_aarch64_aspeed.py index 07b0c7c1fd27..8df6a97a2852 100755 --- a/tests/functional/test_aarch64_aspeed.py +++ b/tests/functional/test_aarch64_aspeed.py @@ -27,9 +27,9 @@ def do_test_aarch64_aspeed_sdk_start(self, image): wait_for_console_pattern(self, '## Loading kernel from FIT Image') wait_for_console_pattern(self, 'Starting kernel ...') - ASSET_SDK_V903_AST2700 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.03/ast2700-default-obmc.tar.gz', - '91225f50d255e2905ba8d8e0c80b71b9d157c3609770c7a740cd786370d85a77') + ASSET_SDK_V905_AST2700 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.05/ast2700-a0-default-obmc.tar.gz', + 'cfbbd1cce72f2a3b73b9080c41eecdadebb7077fba4f7806d72ac99f3e84b74a') def start_ast2700_test(self, name): num_cpu = 4 @@ -89,11 +89,11 @@ def start_ast2700_test(self, name): exec_command_and_wait_for_pattern(self, 'cat /sys/bus/i2c/devices/1-004d/hwmon/hwmon*/temp1_input', '18000') - def test_aarch64_ast2700_evb_sdk_v09_03(self): + def test_aarch64_ast2700_evb_sdk_v09_05(self): self.set_machine('ast2700-evb') - self.archive_extract(self.ASSET_SDK_V903_AST2700) - self.start_ast2700_test('ast2700-default') + self.archive_extract(self.ASSET_SDK_V905_AST2700) + self.start_ast2700_test('ast2700-a0-default') if __name__ == '__main__': From 2cab06e930cb724bd8ea7ce7ff0e1f362b41344d Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:37 +0800 Subject: [PATCH 0736/1179] tests/functional/aspeed: Add test case for AST2700 A1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-29-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- tests/functional/test_aarch64_aspeed.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/functional/test_aarch64_aspeed.py b/tests/functional/test_aarch64_aspeed.py index 8df6a97a2852..c25c96627823 100755 --- a/tests/functional/test_aarch64_aspeed.py +++ b/tests/functional/test_aarch64_aspeed.py @@ -31,6 +31,10 @@ def do_test_aarch64_aspeed_sdk_start(self, image): 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.05/ast2700-a0-default-obmc.tar.gz', 'cfbbd1cce72f2a3b73b9080c41eecdadebb7077fba4f7806d72ac99f3e84b74a') + ASSET_SDK_V905_AST2700A1 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.05/ast2700-default-obmc.tar.gz', + 'c1f4496aec06743c812a6e9a1a18d032f34d62f3ddb6956e924fef62aa2046a5') + def start_ast2700_test(self, name): num_cpu = 4 uboot_size = os.path.getsize(self.scratch_file(name, @@ -95,6 +99,12 @@ def test_aarch64_ast2700_evb_sdk_v09_05(self): self.archive_extract(self.ASSET_SDK_V905_AST2700) self.start_ast2700_test('ast2700-a0-default') + def test_aarch64_ast2700a1_evb_sdk_v09_05(self): + self.set_machine('ast2700a1-evb') + + self.archive_extract(self.ASSET_SDK_V905_AST2700A1) + self.start_ast2700_test('ast2700-default') + if __name__ == '__main__': QemuSystemTest.main() From 5ab179db11ca297c9e89a6d57f954d31965cbd7b Mon Sep 17 00:00:00 2001 From: Jamin Lin Date: Fri, 7 Mar 2025 11:59:38 +0800 Subject: [PATCH 0737/1179] docs/specs: Add aspeed-intc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add AST2700 INTC design guidance and its block diagram. Signed-off-by: Jamin Lin Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250307035945.3698802-30-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater --- docs/specs/aspeed-intc.rst | 136 +++++++++++++++++++++++++++++++++++++ docs/specs/index.rst | 1 + 2 files changed, 137 insertions(+) create mode 100644 docs/specs/aspeed-intc.rst diff --git a/docs/specs/aspeed-intc.rst b/docs/specs/aspeed-intc.rst new file mode 100644 index 000000000000..9cefd7f37f85 --- /dev/null +++ b/docs/specs/aspeed-intc.rst @@ -0,0 +1,136 @@ +=========================== +ASPEED Interrupt Controller +=========================== + +AST2700 +------- +There are a total of 480 interrupt sources in AST2700. Due to the limitation of +interrupt numbers of processors, the interrupts are merged every 32 sources for +interrupt numbers greater than 127. + +There are two levels of interrupt controllers, INTC (CPU Die) and INTCIO +(I/O Die). + +Interrupt Mapping +----------------- +- INTC: Handles interrupt sources 0 - 127 and integrates signals from INTCIO. +- INTCIO: Handles interrupt sources 128 - 319 independently. + +QEMU Support +------------ +Currently, only GIC 192 to 201 are supported, and their source interrupts are +from INTCIO and connected to INTC at input pin 0 and output pins 0 to 9 for +GIC 192-201. + +Design for GICINT 196 +--------------------- +The orgate has interrupt sources ranging from 0 to 31, with its output pin +connected to INTCIO "T0 GICINT_196". The output pin is then connected to INTC +"GIC_192_201" at bit 4, and its bit 4 output pin is connected to GIC 196. + +INTC GIC_192_201 Output Pin Mapping +----------------------------------- +The design of INTC GIC_192_201 have 10 output pins, mapped as following: + +==== ==== +Bit GIC +==== ==== +0 192 +1 193 +2 194 +3 195 +4 196 +5 197 +6 198 +7 199 +8 200 +9 201 +==== ==== + +AST2700 A0 +---------- +It has only one INTC controller, and currently, only GIC 128-136 is supported. +To support both AST2700 A1 and AST2700 A0, there are 10 OR gates in the INTC, +with gates 1 to 9 supporting GIC 128-136. + +Design for GICINT 132 +--------------------- +The orgate has interrupt sources ranging from 0 to 31, with its output pin +connected to INTC. The output pin is then connected to GIC 132. + +Block Diagram of GICINT 196 for AST2700 A1 and GICINT 132 for AST2700 A0 +------------------------------------------------------------------------ + +.. code-block:: + + |-------------------------------------------------------------------------------------------------------| + | AST2700 A1 Design | + | To GICINT196 | + | | + | ETH1 |-----------| |--------------------------| |--------------| | + | -------->|0 | | INTCIO | | orgates[0] | | + | ETH2 | 4| orgates[0]------>|inpin[0]-------->outpin[0]|------->| 0 | | + | -------->|1 5| orgates[1]------>|inpin[1]-------->outpin[1]|------->| 1 | | + | ETH3 | 6| orgates[2]------>|inpin[2]-------->outpin[2]|------->| 2 | | + | -------->|2 19| orgates[3]------>|inpin[3]-------->outpin[3]|------->| 3 OR[0:9] |-----| | + | UART0 | 20|-->orgates[4]------>|inpin[4]-------->outpin[4]|------->| 4 | | | + | -------->|7 21| orgates[5]------>|inpin[5]-------->outpin[5]|------->| 5 | | | + | UART1 | 22| orgates[6]------>|inpin[6]-------->outpin[6]|------->| 6 | | | + | -------->|8 23| orgates[7]------>|inpin[7]-------->outpin[7]|------->| 7 | | | + | UART2 | 24| orgates[8]------>|inpin[8]-------->outpin[8]|------->| 8 | | | + | -------->|9 25| orgates[9]------>|inpin[9]-------->outpin[9]|------->| 9 | | | + | UART3 | 26| |--------------------------| |--------------| | | + | ---------|10 27| | | + | UART5 | 28| | | + | -------->|11 29| | | + | UART6 | | | | + | -------->|12 30| |-----------------------------------------------------------------------| | + | UART7 | 31| | | + | -------->|13 | | | + | UART8 | OR[0:31] | | |------------------------------| |----------| | + | -------->|14 | | | INTC | | GIC | | + | UART9 | | | |inpin[0:0]--------->outpin[0] |---------->|192 | | + | -------->|15 | | |inpin[0:1]--------->outpin[1] |---------->|193 | | + | UART10 | | | |inpin[0:2]--------->outpin[2] |---------->|194 | | + | -------->|16 | | |inpin[0:3]--------->outpin[3] |---------->|195 | | + | UART11 | | |--------------> |inpin[0:4]--------->outpin[4] |---------->|196 | | + | -------->|17 | |inpin[0:5]--------->outpin[5] |---------->|197 | | + | UART12 | | |inpin[0:6]--------->outpin[6] |---------->|198 | | + | -------->|18 | |inpin[0:7]--------->outpin[7] |---------->|199 | | + | |-----------| |inpin[0:8]--------->outpin[8] |---------->|200 | | + | |inpin[0:9]--------->outpin[9] |---------->|201 | | + |-------------------------------------------------------------------------------------------------------| + |-------------------------------------------------------------------------------------------------------| + | ETH1 |-----------| orgates[1]------->|inpin[1]----------->outpin[10]|---------->|128 | | + | -------->|0 | orgates[2]------->|inpin[2]----------->outpin[11]|---------->|129 | | + | ETH2 | 4| orgates[3]------->|inpin[3]----------->outpin[12]|---------->|130 | | + | -------->|1 5| orgates[4]------->|inpin[4]----------->outpin[13]|---------->|131 | | + | ETH3 | 6|---->orgates[5]------->|inpin[5]----------->outpin[14]|---------->|132 | | + | -------->|2 19| orgates[6]------->|inpin[6]----------->outpin[15]|---------->|133 | | + | UART0 | 20| orgates[7]------->|inpin[7]----------->outpin[16]|---------->|134 | | + | -------->|7 21| orgates[8]------->|inpin[8]----------->outpin[17]|---------->|135 | | + | UART1 | 22| orgates[9]------->|inpin[9]----------->outpin[18]|---------->|136 | | + | -------->|8 23| |------------------------------| |----------| | + | UART2 | 24| | + | -------->|9 25| AST2700 A0 Design | + | UART3 | 26| | + | -------->|10 27| | + | UART5 | 28| | + | -------->|11 29| GICINT132 | + | UART6 | | | + | -------->|12 30| | + | UART7 | 31| | + | -------->|13 | | + | UART8 | OR[0:31] | | + | -------->|14 | | + | UART9 | | | + | -------->|15 | | + | UART10 | | | + | -------->|16 | | + | UART11 | | | + | -------->|17 | | + | UART12 | | | + | -------->|18 | | + | |-----------| | + | | + |-------------------------------------------------------------------------------------------------------| diff --git a/docs/specs/index.rst b/docs/specs/index.rst index d7675cebc2d9..f19d73c9f6e1 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -38,3 +38,4 @@ guest hardware that is specific to QEMU. rocker riscv-iommu riscv-aia + aspeed-intc From c9ce8a1ffdedbc55d107c4b5629eca5d1e219165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 2 Jan 2025 18:55:03 +0100 Subject: [PATCH 0738/1179] linux-user: Only include 'exec/tb-flush.h' header when necessary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Very few source files require to access "exec/tb-flush.h" declarations, and except a pair, they all include it explicitly. No need to overload the generic "user-internals.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Ilya Leoshkevich Reviewed-by: Pierrick Bouvier Message-Id: <20250102182521.65428-2-philmd@linaro.org> --- linux-user/mmap.c | 1 + linux-user/syscall.c | 1 + linux-user/user-internals.h | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/linux-user/mmap.c b/linux-user/mmap.c index 6828b17a63fa..d1f36e6f16ba 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -21,6 +21,7 @@ #include "trace.h" #include "exec/log.h" #include "exec/page-protection.h" +#include "exec/tb-flush.h" #include "exec/translation-block.h" #include "qemu.h" #include "user/page-protection.h" diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 02ea4221c96d..b32de763f7e7 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -26,6 +26,7 @@ #include "tcg/startup.h" #include "target_mman.h" #include "exec/page-protection.h" +#include "exec/tb-flush.h" #include "exec/translation-block.h" #include #include diff --git a/linux-user/user-internals.h b/linux-user/user-internals.h index b9b05c1d11f2..4aa253b56636 100644 --- a/linux-user/user-internals.h +++ b/linux-user/user-internals.h @@ -20,7 +20,6 @@ #include "user/thunk.h" #include "exec/exec-all.h" -#include "exec/tb-flush.h" #include "qemu/log.h" extern char *exec_path; From 019b4e84eda27a006f94ed0faa024babd0a97e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 7 Mar 2025 14:02:21 +0100 Subject: [PATCH 0739/1179] bsd-user: Always use mmap_find_vma_aligned() in target_mmap() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Massage target_mmap(): calculate alignment once, then unconditionally call mmap_find_vma_aligned(). Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Warner Losh Message-Id: <20250308122842.76377-2-philmd@linaro.org> --- bsd-user/mmap.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c index 346f2cefd32e..dfa6e728ab56 100644 --- a/bsd-user/mmap.c +++ b/bsd-user/mmap.c @@ -489,13 +489,12 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, * before we truncate the length for mapping files below. */ if (!(flags & MAP_FIXED)) { + abi_ulong alignment; + host_len = len + offset - host_offset; host_len = HOST_PAGE_ALIGN(host_len); - if ((flags & MAP_ALIGNMENT_MASK) != 0) - start = mmap_find_vma_aligned(real_start, host_len, - (flags & MAP_ALIGNMENT_MASK) >> MAP_ALIGNMENT_SHIFT); - else - start = mmap_find_vma(real_start, host_len); + alignment = (flags & MAP_ALIGNMENT_MASK) >> MAP_ALIGNMENT_SHIFT; + start = mmap_find_vma_aligned(real_start, host_len, alignment); if (start == (abi_ulong)-1) { errno = ENOMEM; goto fail; From 84d66261bef3cdfea3bd1fb052a7ba38abb34b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 7 Mar 2025 13:47:21 +0100 Subject: [PATCH 0740/1179] bsd-user: Propagate alignment argument to mmap_find_vma() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Propagate the alignment to mmap_find_vma(), effectively embedding mmap_find_vma_aligned() within mmap_find_vma(). Add a comment in do_bsd_shmat() to clarify alignment above page size is not required. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Warner Losh Message-Id: <20250308122842.76377-3-philmd@linaro.org> --- bsd-user/bsd-mem.h | 4 +++- bsd-user/mmap.c | 10 ++-------- bsd-user/qemu.h | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/bsd-user/bsd-mem.h b/bsd-user/bsd-mem.h index f5ec0de24ca6..90ca0e33775a 100644 --- a/bsd-user/bsd-mem.h +++ b/bsd-user/bsd-mem.h @@ -370,9 +370,11 @@ static inline abi_long do_bsd_shmat(int shmid, abi_ulong shmaddr, int shmflg) if (shmaddr) { host_raddr = shmat(shmid, (void *)g2h_untagged(shmaddr), shmflg); } else { + abi_ulong alignment; abi_ulong mmap_start; - mmap_start = mmap_find_vma(0, shm_info.shm_segsz); + alignment = 0; /* alignment above page size not required */ + mmap_start = mmap_find_vma(0, shm_info.shm_segsz, alignment); if (mmap_start == -1) { return -TARGET_ENOMEM; diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c index dfa6e728ab56..3f0df79c375a 100644 --- a/bsd-user/mmap.c +++ b/bsd-user/mmap.c @@ -275,8 +275,7 @@ static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size, * It must be called with mmap_lock() held. * Return -1 if error. */ -static abi_ulong mmap_find_vma_aligned(abi_ulong start, abi_ulong size, - abi_ulong alignment) +abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size, abi_ulong alignment) { void *ptr, *prev; abi_ulong addr; @@ -395,11 +394,6 @@ static abi_ulong mmap_find_vma_aligned(abi_ulong start, abi_ulong size, } } -abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size) -{ - return mmap_find_vma_aligned(start, size, 0); -} - /* NOTE: all the constants are the HOST ones */ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, int flags, int fd, off_t offset) @@ -494,7 +488,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, host_len = len + offset - host_offset; host_len = HOST_PAGE_ALIGN(host_len); alignment = (flags & MAP_ALIGNMENT_MASK) >> MAP_ALIGNMENT_SHIFT; - start = mmap_find_vma_aligned(real_start, host_len, alignment); + start = mmap_find_vma(real_start, host_len, alignment); if (start == (abi_ulong)-1) { errno = ENOMEM; goto fail; diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h index 4e97c7963181..0b3bd65b1800 100644 --- a/bsd-user/qemu.h +++ b/bsd-user/qemu.h @@ -242,7 +242,7 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, abi_ulong new_addr); int target_msync(abi_ulong start, abi_ulong len, int flags); extern abi_ulong mmap_next_start; -abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size); +abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size, abi_ulong alignment); void mmap_reserve(abi_ulong start, abi_ulong size); void TSA_NO_TSA mmap_fork_start(void); void TSA_NO_TSA mmap_fork_end(int child); From 1405d7e60d8c98a28b29885f70da4f2e4407fbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 16 Jan 2025 17:33:13 +0100 Subject: [PATCH 0741/1179] user: Extract common MMAP API to 'user/mmap.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep common MMAP-related declarations in a single place. Note, this disable ThreadSafetyAnalysis on Linux for: - mmap_fork_start() - mmap_fork_end(). Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Warner Losh Reviewed-by: Richard Henderson Message-Id: <20250308122842.76377-4-philmd@linaro.org> --- bsd-user/qemu.h | 12 +----------- include/user/mmap.h | 32 ++++++++++++++++++++++++++++++++ linux-user/user-mmap.h | 19 ++----------------- 3 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 include/user/mmap.h diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h index 0b3bd65b1800..c1c508281a82 100644 --- a/bsd-user/qemu.h +++ b/bsd-user/qemu.h @@ -32,6 +32,7 @@ extern char **environ; #include "user/thunk.h" +#include "user/mmap.h" #include "target_arch.h" #include "syscall_defs.h" #include "target_syscall.h" @@ -233,19 +234,8 @@ void print_taken_signal(int target_signum, const target_siginfo_t *tinfo); extern int do_strace; /* mmap.c */ -int target_mprotect(abi_ulong start, abi_ulong len, int prot); -abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, - int flags, int fd, off_t offset); -int target_munmap(abi_ulong start, abi_ulong len); -abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, - abi_ulong new_size, unsigned long flags, - abi_ulong new_addr); int target_msync(abi_ulong start, abi_ulong len, int flags); -extern abi_ulong mmap_next_start; -abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size, abi_ulong alignment); void mmap_reserve(abi_ulong start, abi_ulong size); -void TSA_NO_TSA mmap_fork_start(void); -void TSA_NO_TSA mmap_fork_end(int child); /* main.c */ extern char qemu_proc_pathname[]; diff --git a/include/user/mmap.h b/include/user/mmap.h new file mode 100644 index 000000000000..4d5e9aac70ab --- /dev/null +++ b/include/user/mmap.h @@ -0,0 +1,32 @@ +/* + * MMAP declarations for QEMU user emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef USER_MMAP_H +#define USER_MMAP_H + +#include "user/abitypes.h" + +/* + * mmap_next_start: The base address for the next mmap without hint, + * increased after each successful map, starting at task_unmapped_base. + * This is an optimization within QEMU and not part of ADDR_COMPAT_LAYOUT. + */ +extern abi_ulong mmap_next_start; + +int target_mprotect(abi_ulong start, abi_ulong len, int prot); + +abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, + int flags, int fd, off_t offset); +int target_munmap(abi_ulong start, abi_ulong len); +abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, + abi_ulong new_size, unsigned long flags, + abi_ulong new_addr); + +abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size, abi_ulong alignment); + +void TSA_NO_TSA mmap_fork_start(void); +void TSA_NO_TSA mmap_fork_end(int child); + +#endif diff --git a/linux-user/user-mmap.h b/linux-user/user-mmap.h index b94bcdcf83c5..dfc4477a720d 100644 --- a/linux-user/user-mmap.h +++ b/linux-user/user-mmap.h @@ -18,6 +18,8 @@ #ifndef LINUX_USER_USER_MMAP_H #define LINUX_USER_USER_MMAP_H +#include "user/mmap.h" + /* * Guest parameters for the ADDR_COMPAT_LAYOUT personality * (at present this is the only layout supported by QEMU). @@ -39,24 +41,7 @@ extern abi_ulong task_unmapped_base; extern abi_ulong elf_et_dyn_base; -/* - * mmap_next_start: The base address for the next mmap without hint, - * increased after each successful map, starting at task_unmapped_base. - * This is an optimization within QEMU and not part of ADDR_COMPAT_LAYOUT. - */ -extern abi_ulong mmap_next_start; - -int target_mprotect(abi_ulong start, abi_ulong len, int prot); -abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, - int flags, int fd, off_t offset); -int target_munmap(abi_ulong start, abi_ulong len); -abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, - abi_ulong new_size, unsigned long flags, - abi_ulong new_addr); abi_long target_madvise(abi_ulong start, abi_ulong len_in, int advice); -abi_ulong mmap_find_vma(abi_ulong, abi_ulong, abi_ulong); -void mmap_fork_start(void); -void mmap_fork_end(int child); abi_ulong target_shmat(CPUArchState *cpu_env, int shmid, abi_ulong shmaddr, int shmflg); From ca05578fc80f4253ed19f4c4128a4cbd5b83f0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 19:56:20 +0100 Subject: [PATCH 0742/1179] cpus: Register VMState per user / system emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify cpu-target.c by extracting mixed vmstate code into the cpu_vmstate_register() / cpu_vmstate_unregister() helpers, implemented in cpu-user.c and cpu-system.c. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-20-philmd@linaro.org> --- cpu-target.c | 121 +----------------------------------------- hw/core/cpu-system.c | 115 +++++++++++++++++++++++++++++++++++++++ hw/core/cpu-user.c | 12 +++++ include/hw/core/cpu.h | 2 + 4 files changed, 131 insertions(+), 119 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index b6e66d5ac023..bc9c537c5758 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -21,115 +21,17 @@ #include "qapi/error.h" #include "qemu/error-report.h" #include "qemu/qemu-print.h" -#include "migration/vmstate.h" -#ifndef CONFIG_USER_ONLY -#include "hw/core/sysemu-cpu-ops.h" -#endif #include "system/accel-ops.h" #include "system/cpus.h" -#include "system/tcg.h" #include "exec/tswap.h" #include "exec/replay-core.h" #include "exec/cpu-common.h" -#include "exec/cputlb.h" -#include "exec/exec-all.h" -#include "exec/tb-flush.h" #include "exec/log.h" #include "accel/accel-cpu-target.h" #include "trace/trace-root.h" #include "qemu/accel.h" #include "hw/core/cpu.h" -#ifndef CONFIG_USER_ONLY -static int cpu_common_post_load(void *opaque, int version_id) -{ - if (tcg_enabled()) { - CPUState *cpu = opaque; - - /* - * 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the - * version_id is increased. - */ - cpu->interrupt_request &= ~0x01; - - tlb_flush(cpu); - - /* - * loadvm has just updated the content of RAM, bypassing the - * usual mechanisms that ensure we flush TBs for writes to - * memory we've translated code from. So we must flush all TBs, - * which will now be stale. - */ - tb_flush(cpu); - } - - return 0; -} - -static int cpu_common_pre_load(void *opaque) -{ - CPUState *cpu = opaque; - - cpu->exception_index = -1; - - return 0; -} - -static bool cpu_common_exception_index_needed(void *opaque) -{ - CPUState *cpu = opaque; - - return tcg_enabled() && cpu->exception_index != -1; -} - -static const VMStateDescription vmstate_cpu_common_exception_index = { - .name = "cpu_common/exception_index", - .version_id = 1, - .minimum_version_id = 1, - .needed = cpu_common_exception_index_needed, - .fields = (const VMStateField[]) { - VMSTATE_INT32(exception_index, CPUState), - VMSTATE_END_OF_LIST() - } -}; - -static bool cpu_common_crash_occurred_needed(void *opaque) -{ - CPUState *cpu = opaque; - - return cpu->crash_occurred; -} - -static const VMStateDescription vmstate_cpu_common_crash_occurred = { - .name = "cpu_common/crash_occurred", - .version_id = 1, - .minimum_version_id = 1, - .needed = cpu_common_crash_occurred_needed, - .fields = (const VMStateField[]) { - VMSTATE_BOOL(crash_occurred, CPUState), - VMSTATE_END_OF_LIST() - } -}; - -const VMStateDescription vmstate_cpu_common = { - .name = "cpu_common", - .version_id = 1, - .minimum_version_id = 1, - .pre_load = cpu_common_pre_load, - .post_load = cpu_common_post_load, - .fields = (const VMStateField[]) { - VMSTATE_UINT32(halted, CPUState), - VMSTATE_UINT32(interrupt_request, CPUState), - VMSTATE_END_OF_LIST() - }, - .subsections = (const VMStateDescription * const []) { - &vmstate_cpu_common_exception_index, - &vmstate_cpu_common_crash_occurred, - NULL - } -}; -#endif - bool cpu_exec_realizefn(CPUState *cpu, Error **errp) { if (!accel_cpu_common_realize(cpu, errp)) { @@ -139,33 +41,14 @@ bool cpu_exec_realizefn(CPUState *cpu, Error **errp) /* Wait until cpu initialization complete before exposing cpu. */ cpu_list_add(cpu); -#ifdef CONFIG_USER_ONLY - assert(qdev_get_vmsd(DEVICE(cpu)) == NULL || - qdev_get_vmsd(DEVICE(cpu))->unmigratable); -#else - if (qdev_get_vmsd(DEVICE(cpu)) == NULL) { - vmstate_register(NULL, cpu->cpu_index, &vmstate_cpu_common, cpu); - } - if (cpu->cc->sysemu_ops->legacy_vmsd != NULL) { - vmstate_register(NULL, cpu->cpu_index, cpu->cc->sysemu_ops->legacy_vmsd, cpu); - } -#endif /* CONFIG_USER_ONLY */ + cpu_vmstate_register(cpu); return true; } void cpu_exec_unrealizefn(CPUState *cpu) { -#ifndef CONFIG_USER_ONLY - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->sysemu_ops->legacy_vmsd != NULL) { - vmstate_unregister(NULL, cc->sysemu_ops->legacy_vmsd, cpu); - } - if (qdev_get_vmsd(DEVICE(cpu)) == NULL) { - vmstate_unregister(NULL, &vmstate_cpu_common, cpu); - } -#endif + cpu_vmstate_unregister(cpu); cpu_list_remove(cpu); /* diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index e511507e13b6..6c89d76e498b 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -21,11 +21,15 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "exec/address-spaces.h" +#include "exec/cputlb.h" #include "exec/memory.h" +#include "exec/tb-flush.h" #include "exec/tswap.h" #include "hw/qdev-core.h" #include "hw/qdev-properties.h" #include "hw/core/sysemu-cpu-ops.h" +#include "migration/vmstate.h" +#include "system/tcg.h" bool cpu_paging_enabled(const CPUState *cpu) { @@ -194,3 +198,114 @@ void cpu_exec_initfn(CPUState *cpu) cpu->memory = get_system_memory(); object_ref(OBJECT(cpu->memory)); } + +static int cpu_common_post_load(void *opaque, int version_id) +{ + if (tcg_enabled()) { + CPUState *cpu = opaque; + + /* + * 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the + * version_id is increased. + */ + cpu->interrupt_request &= ~0x01; + + tlb_flush(cpu); + + /* + * loadvm has just updated the content of RAM, bypassing the + * usual mechanisms that ensure we flush TBs for writes to + * memory we've translated code from. So we must flush all TBs, + * which will now be stale. + */ + tb_flush(cpu); + } + + return 0; +} + +static int cpu_common_pre_load(void *opaque) +{ + CPUState *cpu = opaque; + + cpu->exception_index = -1; + + return 0; +} + +static bool cpu_common_exception_index_needed(void *opaque) +{ + CPUState *cpu = opaque; + + return tcg_enabled() && cpu->exception_index != -1; +} + +static const VMStateDescription vmstate_cpu_common_exception_index = { + .name = "cpu_common/exception_index", + .version_id = 1, + .minimum_version_id = 1, + .needed = cpu_common_exception_index_needed, + .fields = (const VMStateField[]) { + VMSTATE_INT32(exception_index, CPUState), + VMSTATE_END_OF_LIST() + } +}; + +static bool cpu_common_crash_occurred_needed(void *opaque) +{ + CPUState *cpu = opaque; + + return cpu->crash_occurred; +} + +static const VMStateDescription vmstate_cpu_common_crash_occurred = { + .name = "cpu_common/crash_occurred", + .version_id = 1, + .minimum_version_id = 1, + .needed = cpu_common_crash_occurred_needed, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(crash_occurred, CPUState), + VMSTATE_END_OF_LIST() + } +}; + +const VMStateDescription vmstate_cpu_common = { + .name = "cpu_common", + .version_id = 1, + .minimum_version_id = 1, + .pre_load = cpu_common_pre_load, + .post_load = cpu_common_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(halted, CPUState), + VMSTATE_UINT32(interrupt_request, CPUState), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription * const []) { + &vmstate_cpu_common_exception_index, + &vmstate_cpu_common_crash_occurred, + NULL + } +}; + +void cpu_vmstate_register(CPUState *cpu) +{ + if (qdev_get_vmsd(DEVICE(cpu)) == NULL) { + vmstate_register(NULL, cpu->cpu_index, &vmstate_cpu_common, cpu); + } + if (cpu->cc->sysemu_ops->legacy_vmsd != NULL) { + vmstate_register(NULL, cpu->cpu_index, + cpu->cc->sysemu_ops->legacy_vmsd, cpu); + } +} + +void cpu_vmstate_unregister(CPUState *cpu) +{ + CPUClass *cc = CPU_GET_CLASS(cpu); + + if (cc->sysemu_ops->legacy_vmsd != NULL) { + vmstate_unregister(NULL, cc->sysemu_ops->legacy_vmsd, cpu); + } + if (qdev_get_vmsd(DEVICE(cpu)) == NULL) { + vmstate_unregister(NULL, &vmstate_cpu_common, cpu); + } +} diff --git a/hw/core/cpu-user.c b/hw/core/cpu-user.c index cdd8de2fefa6..1892acdee0f5 100644 --- a/hw/core/cpu-user.c +++ b/hw/core/cpu-user.c @@ -10,6 +10,7 @@ #include "hw/qdev-core.h" #include "hw/qdev-properties.h" #include "hw/core/cpu.h" +#include "migration/vmstate.h" static const Property cpu_user_props[] = { /* @@ -30,3 +31,14 @@ void cpu_exec_initfn(CPUState *cpu) { /* nothing to do */ } + +void cpu_vmstate_register(CPUState *cpu) +{ + assert(qdev_get_vmsd(DEVICE(cpu)) == NULL || + qdev_get_vmsd(DEVICE(cpu))->unmigratable); +} + +void cpu_vmstate_unregister(CPUState *cpu) +{ + /* nothing to do */ +} diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index 9dd6ac7c7639..bc0c94683448 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -1165,6 +1165,8 @@ G_NORETURN void cpu_abort(CPUState *cpu, const char *fmt, ...) /* $(top_srcdir)/cpu.c */ void cpu_class_init_props(DeviceClass *dc); void cpu_exec_initfn(CPUState *cpu); +void cpu_vmstate_register(CPUState *cpu); +void cpu_vmstate_unregister(CPUState *cpu); bool cpu_exec_realizefn(CPUState *cpu, Error **errp); void cpu_exec_unrealizefn(CPUState *cpu); void cpu_exec_reset_hold(CPUState *cpu); From 43610f3184f846da948e8ab9dbc0c5de1e9bde79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 23 Jan 2025 13:46:03 +0100 Subject: [PATCH 0743/1179] cpus: Build cpu_exec_[un]realizefn() methods once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that cpu_exec_realizefn() and cpu_exec_unrealizefn() methods don't use any target specific definition anymore, we can move them to cpu-common.c to be able to build them once. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250123234415.59850-21-philmd@linaro.org> --- cpu-target.c | 29 ----------------------------- hw/core/cpu-common.c | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/cpu-target.c b/cpu-target.c index bc9c537c5758..cae77374b384 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -25,38 +25,9 @@ #include "system/cpus.h" #include "exec/tswap.h" #include "exec/replay-core.h" -#include "exec/cpu-common.h" #include "exec/log.h" #include "accel/accel-cpu-target.h" #include "trace/trace-root.h" -#include "qemu/accel.h" -#include "hw/core/cpu.h" - -bool cpu_exec_realizefn(CPUState *cpu, Error **errp) -{ - if (!accel_cpu_common_realize(cpu, errp)) { - return false; - } - - /* Wait until cpu initialization complete before exposing cpu. */ - cpu_list_add(cpu); - - cpu_vmstate_register(cpu); - - return true; -} - -void cpu_exec_unrealizefn(CPUState *cpu) -{ - cpu_vmstate_unregister(cpu); - - cpu_list_remove(cpu); - /* - * Now that the vCPU has been removed from the RCU list, we can call - * accel_cpu_common_unrealize, which may free fields using call_rcu. - */ - accel_cpu_common_unrealize(cpu); -} char *cpu_model_from_type(const char *typename) { diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index d5cd227fe6d5..5671d8d4f54d 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -193,6 +193,20 @@ static void cpu_common_parse_features(const char *typename, char *features, } } +bool cpu_exec_realizefn(CPUState *cpu, Error **errp) +{ + if (!accel_cpu_common_realize(cpu, errp)) { + return false; + } + + /* Wait until cpu initialization complete before exposing cpu. */ + cpu_list_add(cpu); + + cpu_vmstate_register(cpu); + + return true; +} + static void cpu_common_realizefn(DeviceState *dev, Error **errp) { CPUState *cpu = CPU(dev); @@ -234,6 +248,18 @@ static void cpu_common_unrealizefn(DeviceState *dev) cpu_exec_unrealizefn(cpu); } +void cpu_exec_unrealizefn(CPUState *cpu) +{ + cpu_vmstate_unregister(cpu); + + cpu_list_remove(cpu); + /* + * Now that the vCPU has been removed from the RCU list, we can call + * accel_cpu_common_unrealize, which may free fields using call_rcu. + */ + accel_cpu_common_unrealize(cpu); +} + static void cpu_common_initfn(Object *obj) { CPUState *cpu = CPU(obj); From 30e76638eb35ffe88e95cca2b5af952c14dc336d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:12:35 +0100 Subject: [PATCH 0744/1179] cpus: Prefer cached CpuClass over CPU_GET_CLASS() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CpuState caches its CPUClass since commit 6fbdff87062 ("cpu: cache CPUClass in CPUState for hot code paths"), use it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250122093028.52416-5-philmd@linaro.org> --- cpu-common.c | 10 +++---- hw/core/cpu-common.c | 13 +++------ hw/core/cpu-system.c | 61 ++++++++++++++++--------------------------- include/hw/core/cpu.h | 10 +++---- 4 files changed, 33 insertions(+), 61 deletions(-) diff --git a/cpu-common.c b/cpu-common.c index f5dcc2d136bc..ef5757d23bf6 100644 --- a/cpu-common.c +++ b/cpu-common.c @@ -388,11 +388,10 @@ void process_queued_cpu_work(CPUState *cpu) int cpu_breakpoint_insert(CPUState *cpu, vaddr pc, int flags, CPUBreakpoint **breakpoint) { - CPUClass *cc = CPU_GET_CLASS(cpu); CPUBreakpoint *bp; - if (cc->gdb_adjust_breakpoint) { - pc = cc->gdb_adjust_breakpoint(cpu, pc); + if (cpu->cc->gdb_adjust_breakpoint) { + pc = cpu->cc->gdb_adjust_breakpoint(cpu, pc); } bp = g_malloc(sizeof(*bp)); @@ -418,11 +417,10 @@ int cpu_breakpoint_insert(CPUState *cpu, vaddr pc, int flags, /* Remove a specific breakpoint. */ int cpu_breakpoint_remove(CPUState *cpu, vaddr pc, int flags) { - CPUClass *cc = CPU_GET_CLASS(cpu); CPUBreakpoint *bp; - if (cc->gdb_adjust_breakpoint) { - pc = cc->gdb_adjust_breakpoint(cpu, pc); + if (cpu->cc->gdb_adjust_breakpoint) { + pc = cpu->cc->gdb_adjust_breakpoint(cpu, pc); } QTAILQ_FOREACH(bp, &cpu->breakpoints, entry) { diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index 5671d8d4f54d..ba0f02e49da7 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -40,9 +40,7 @@ CPUState *cpu_by_arch_id(int64_t id) CPUState *cpu; CPU_FOREACH(cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->get_arch_id(cpu) == id) { + if (cpu->cc->get_arch_id(cpu) == id) { return cpu; } } @@ -101,11 +99,9 @@ static int cpu_common_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg) void cpu_dump_state(CPUState *cpu, FILE *f, int flags) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->dump_state) { + if (cpu->cc->dump_state) { cpu_synchronize_state(cpu); - cc->dump_state(cpu, f, flags); + cpu->cc->dump_state(cpu, f, flags); } } @@ -119,11 +115,10 @@ void cpu_reset(CPUState *cpu) static void cpu_common_reset_hold(Object *obj, ResetType type) { CPUState *cpu = CPU(obj); - CPUClass *cc = CPU_GET_CLASS(cpu); if (qemu_loglevel_mask(CPU_LOG_RESET)) { qemu_log("CPU Reset (CPU %d)\n", cpu->cpu_index); - log_cpu_state(cpu, cc->reset_dump_flags); + log_cpu_state(cpu, cpu->cc->reset_dump_flags); } cpu->interrupt_request = 0; diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index 6c89d76e498b..e29664d39bbe 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -33,10 +33,8 @@ bool cpu_paging_enabled(const CPUState *cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->sysemu_ops->get_paging_enabled) { - return cc->sysemu_ops->get_paging_enabled(cpu); + if (cpu->cc->sysemu_ops->get_paging_enabled) { + return cpu->cc->sysemu_ops->get_paging_enabled(cpu); } return false; @@ -45,10 +43,8 @@ bool cpu_paging_enabled(const CPUState *cpu) bool cpu_get_memory_mapping(CPUState *cpu, MemoryMappingList *list, Error **errp) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->sysemu_ops->get_memory_mapping) { - return cc->sysemu_ops->get_memory_mapping(cpu, list, errp); + if (cpu->cc->sysemu_ops->get_memory_mapping) { + return cpu->cc->sysemu_ops->get_memory_mapping(cpu, list, errp); } error_setg(errp, "Obtaining memory mappings is unsupported on this CPU."); @@ -58,15 +54,15 @@ bool cpu_get_memory_mapping(CPUState *cpu, MemoryMappingList *list, hwaddr cpu_get_phys_page_attrs_debug(CPUState *cpu, vaddr addr, MemTxAttrs *attrs) { - CPUClass *cc = CPU_GET_CLASS(cpu); hwaddr paddr; - if (cc->sysemu_ops->get_phys_page_attrs_debug) { - paddr = cc->sysemu_ops->get_phys_page_attrs_debug(cpu, addr, attrs); + if (cpu->cc->sysemu_ops->get_phys_page_attrs_debug) { + paddr = cpu->cc->sysemu_ops->get_phys_page_attrs_debug(cpu, addr, + attrs); } else { /* Fallback for CPUs which don't implement the _attrs_ hook */ *attrs = MEMTXATTRS_UNSPECIFIED; - paddr = cc->sysemu_ops->get_phys_page_debug(cpu, addr); + paddr = cpu->cc->sysemu_ops->get_phys_page_debug(cpu, addr); } /* Indicate that this is a debug access. */ attrs->debug = 1; @@ -94,64 +90,53 @@ int cpu_asidx_from_attrs(CPUState *cpu, MemTxAttrs attrs) int cpu_write_elf32_qemunote(WriteCoreDumpFunction f, CPUState *cpu, void *opaque) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (!cc->sysemu_ops->write_elf32_qemunote) { + if (!cpu->cc->sysemu_ops->write_elf32_qemunote) { return 0; } - return (*cc->sysemu_ops->write_elf32_qemunote)(f, cpu, opaque); + return (*cpu->cc->sysemu_ops->write_elf32_qemunote)(f, cpu, opaque); } int cpu_write_elf32_note(WriteCoreDumpFunction f, CPUState *cpu, int cpuid, void *opaque) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (!cc->sysemu_ops->write_elf32_note) { + if (!cpu->cc->sysemu_ops->write_elf32_note) { return -1; } - return (*cc->sysemu_ops->write_elf32_note)(f, cpu, cpuid, opaque); + return (*cpu->cc->sysemu_ops->write_elf32_note)(f, cpu, cpuid, opaque); } int cpu_write_elf64_qemunote(WriteCoreDumpFunction f, CPUState *cpu, void *opaque) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (!cc->sysemu_ops->write_elf64_qemunote) { + if (!cpu->cc->sysemu_ops->write_elf64_qemunote) { return 0; } - return (*cc->sysemu_ops->write_elf64_qemunote)(f, cpu, opaque); + return (*cpu->cc->sysemu_ops->write_elf64_qemunote)(f, cpu, opaque); } int cpu_write_elf64_note(WriteCoreDumpFunction f, CPUState *cpu, int cpuid, void *opaque) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (!cc->sysemu_ops->write_elf64_note) { + if (!cpu->cc->sysemu_ops->write_elf64_note) { return -1; } - return (*cc->sysemu_ops->write_elf64_note)(f, cpu, cpuid, opaque); + return (*cpu->cc->sysemu_ops->write_elf64_note)(f, cpu, cpuid, opaque); } bool cpu_virtio_is_big_endian(CPUState *cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->sysemu_ops->virtio_is_big_endian) { - return cc->sysemu_ops->virtio_is_big_endian(cpu); + if (cpu->cc->sysemu_ops->virtio_is_big_endian) { + return cpu->cc->sysemu_ops->virtio_is_big_endian(cpu); } return target_words_bigendian(); } GuestPanicInformation *cpu_get_crash_info(CPUState *cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); GuestPanicInformation *res = NULL; - if (cc->sysemu_ops->get_crash_info) { - res = cc->sysemu_ops->get_crash_info(cpu); + if (cpu->cc->sysemu_ops->get_crash_info) { + res = cpu->cc->sysemu_ops->get_crash_info(cpu); } return res; } @@ -300,10 +285,8 @@ void cpu_vmstate_register(CPUState *cpu) void cpu_vmstate_unregister(CPUState *cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->sysemu_ops->legacy_vmsd != NULL) { - vmstate_unregister(NULL, cc->sysemu_ops->legacy_vmsd, cpu); + if (cpu->cc->sysemu_ops->legacy_vmsd != NULL) { + vmstate_unregister(NULL, cpu->cc->sysemu_ops->legacy_vmsd, cpu); } if (qdev_get_vmsd(DEVICE(cpu)) == NULL) { vmstate_unregister(NULL, &vmstate_cpu_common, cpu); diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index bc0c94683448..c6df426c9476 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -826,10 +826,8 @@ const char *parse_cpu_option(const char *cpu_option); */ static inline bool cpu_has_work(CPUState *cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - g_assert(cc->has_work); - return cc->has_work(cpu); + g_assert(cpu->cc->has_work); + return cpu->cc->has_work(cpu); } /** @@ -968,9 +966,7 @@ void cpu_interrupt(CPUState *cpu, int mask); */ static inline void cpu_set_pc(CPUState *cpu, vaddr addr) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - cc->set_pc(cpu, addr); + cpu->cc->set_pc(cpu, addr); } /** From e27fa95fb9d1dd4668e8b1411b47df14253e47fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:11:09 +0100 Subject: [PATCH 0745/1179] accel: Prefer cached CpuClass over CPU_GET_CLASS() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CpuState caches its CPUClass since commit 6fbdff87062 ("cpu: cache CPUClass in CPUState for hot code paths"), use it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250122093028.52416-6-philmd@linaro.org> --- accel/accel-target.c | 12 +++++------- accel/tcg/tcg-accel-ops.c | 3 +-- accel/tcg/translate-all.c | 2 +- accel/tcg/watchpoint.c | 9 ++++----- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/accel/accel-target.c b/accel/accel-target.c index 835872746264..33a539b4cbbf 100644 --- a/accel/accel-target.c +++ b/accel/accel-target.c @@ -113,22 +113,20 @@ void accel_init_interfaces(AccelClass *ac) void accel_cpu_instance_init(CPUState *cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); - - if (cc->accel_cpu && cc->accel_cpu->cpu_instance_init) { - cc->accel_cpu->cpu_instance_init(cpu); + if (cpu->cc->accel_cpu && cpu->cc->accel_cpu->cpu_instance_init) { + cpu->cc->accel_cpu->cpu_instance_init(cpu); } } bool accel_cpu_common_realize(CPUState *cpu, Error **errp) { - CPUClass *cc = CPU_GET_CLASS(cpu); AccelState *accel = current_accel(); AccelClass *acc = ACCEL_GET_CLASS(accel); /* target specific realization */ - if (cc->accel_cpu && cc->accel_cpu->cpu_target_realize - && !cc->accel_cpu->cpu_target_realize(cpu, errp)) { + if (cpu->cc->accel_cpu + && cpu->cc->accel_cpu->cpu_target_realize + && !cpu->cc->accel_cpu->cpu_target_realize(cpu, errp)) { return false; } diff --git a/accel/tcg/tcg-accel-ops.c b/accel/tcg/tcg-accel-ops.c index 53e580d128bf..d9b662efe3ba 100644 --- a/accel/tcg/tcg-accel-ops.c +++ b/accel/tcg/tcg-accel-ops.c @@ -121,10 +121,9 @@ static inline int xlat_gdb_type(CPUState *cpu, int gdbtype) [GDB_WATCHPOINT_ACCESS] = BP_GDB | BP_MEM_ACCESS, }; - CPUClass *cc = CPU_GET_CLASS(cpu); int cputype = xlat[gdbtype]; - if (cc->gdb_stop_before_watchpoint) { + if (cpu->cc->gdb_stop_before_watchpoint) { cputype |= BP_STOP_BEFORE_ACCESS; } return cputype; diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index 0914d6e98b20..82bc16bd5353 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -630,7 +630,7 @@ void cpu_io_recompile(CPUState *cpu, uintptr_t retaddr) * to account for the re-execution of the branch. */ n = 1; - cc = CPU_GET_CLASS(cpu); + cc = cpu->cc; if (cc->tcg_ops->io_recompile_replay_branch && cc->tcg_ops->io_recompile_replay_branch(cpu, tb)) { cpu->neg.icount_decr.u16.low++; diff --git a/accel/tcg/watchpoint.c b/accel/tcg/watchpoint.c index ba8c9859cf40..65b21884cec9 100644 --- a/accel/tcg/watchpoint.c +++ b/accel/tcg/watchpoint.c @@ -68,7 +68,6 @@ int cpu_watchpoint_address_matches(CPUState *cpu, vaddr addr, vaddr len) void cpu_check_watchpoint(CPUState *cpu, vaddr addr, vaddr len, MemTxAttrs attrs, int flags, uintptr_t ra) { - CPUClass *cc = CPU_GET_CLASS(cpu); CPUWatchpoint *wp; assert(tcg_enabled()); @@ -84,9 +83,9 @@ void cpu_check_watchpoint(CPUState *cpu, vaddr addr, vaddr len, return; } - if (cc->tcg_ops->adjust_watchpoint_address) { + if (cpu->cc->tcg_ops->adjust_watchpoint_address) { /* this is currently used only by ARM BE32 */ - addr = cc->tcg_ops->adjust_watchpoint_address(cpu, addr, len); + addr = cpu->cc->tcg_ops->adjust_watchpoint_address(cpu, addr, len); } assert((flags & ~BP_MEM_ACCESS) == 0); @@ -118,8 +117,8 @@ void cpu_check_watchpoint(CPUState *cpu, vaddr addr, vaddr len, wp->hitattrs = attrs; if (wp->flags & BP_CPU - && cc->tcg_ops->debug_check_watchpoint - && !cc->tcg_ops->debug_check_watchpoint(cpu, wp)) { + && cpu->cc->tcg_ops->debug_check_watchpoint + && !cpu->cc->tcg_ops->debug_check_watchpoint(cpu, wp)) { wp->flags &= ~BP_WATCHPOINT_HIT; continue; } From 18b3abb7224f84364bee200a10413d6be2a1c4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:11:17 +0100 Subject: [PATCH 0746/1179] user: Prefer cached CpuClass over CPU_GET_CLASS() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CpuState caches its CPUClass since commit 6fbdff87062 ("cpu: cache CPUClass in CPUState for hot code paths"), use it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250122093028.52416-7-philmd@linaro.org> --- bsd-user/signal.c | 4 ++-- linux-user/alpha/target_proc.h | 2 +- linux-user/signal.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bsd-user/signal.c b/bsd-user/signal.c index ab1d9ddd50f6..a8cfcca130ec 100644 --- a/bsd-user/signal.c +++ b/bsd-user/signal.c @@ -1034,7 +1034,7 @@ void process_pending_signals(CPUArchState *env) void cpu_loop_exit_sigsegv(CPUState *cpu, target_ulong addr, MMUAccessType access_type, bool maperr, uintptr_t ra) { - const TCGCPUOps *tcg_ops = CPU_GET_CLASS(cpu)->tcg_ops; + const TCGCPUOps *tcg_ops = cpu->cc->tcg_ops; if (tcg_ops->record_sigsegv) { tcg_ops->record_sigsegv(cpu, addr, access_type, maperr, ra); @@ -1050,7 +1050,7 @@ void cpu_loop_exit_sigsegv(CPUState *cpu, target_ulong addr, void cpu_loop_exit_sigbus(CPUState *cpu, target_ulong addr, MMUAccessType access_type, uintptr_t ra) { - const TCGCPUOps *tcg_ops = CPU_GET_CLASS(cpu)->tcg_ops; + const TCGCPUOps *tcg_ops = cpu->cc->tcg_ops; if (tcg_ops->record_sigbus) { tcg_ops->record_sigbus(cpu, addr, access_type, ra); diff --git a/linux-user/alpha/target_proc.h b/linux-user/alpha/target_proc.h index dac37dffc9db..da437ee0e566 100644 --- a/linux-user/alpha/target_proc.h +++ b/linux-user/alpha/target_proc.h @@ -15,7 +15,7 @@ static int open_cpuinfo(CPUArchState *cpu_env, int fd) const char *p, *q; int t; - p = object_class_get_name(OBJECT_CLASS(CPU_GET_CLASS(env_cpu(cpu_env)))); + p = object_class_get_name(OBJECT_CLASS(env_cpu(cpu_env)->cc)); q = strchr(p, '-'); t = q - p; assert(t < sizeof(model)); diff --git a/linux-user/signal.c b/linux-user/signal.c index 4799b79dedee..4dafc2c3a298 100644 --- a/linux-user/signal.c +++ b/linux-user/signal.c @@ -753,7 +753,7 @@ void force_sigsegv(int oldsig) void cpu_loop_exit_sigsegv(CPUState *cpu, target_ulong addr, MMUAccessType access_type, bool maperr, uintptr_t ra) { - const TCGCPUOps *tcg_ops = CPU_GET_CLASS(cpu)->tcg_ops; + const TCGCPUOps *tcg_ops = cpu->cc->tcg_ops; if (tcg_ops->record_sigsegv) { tcg_ops->record_sigsegv(cpu, addr, access_type, maperr, ra); @@ -769,7 +769,7 @@ void cpu_loop_exit_sigsegv(CPUState *cpu, target_ulong addr, void cpu_loop_exit_sigbus(CPUState *cpu, target_ulong addr, MMUAccessType access_type, uintptr_t ra) { - const TCGCPUOps *tcg_ops = CPU_GET_CLASS(cpu)->tcg_ops; + const TCGCPUOps *tcg_ops = cpu->cc->tcg_ops; if (tcg_ops->record_sigbus) { tcg_ops->record_sigbus(cpu, addr, access_type, ra); From 4c5c410ceb4e039a49a120a436cc6183831a778b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:11:25 +0100 Subject: [PATCH 0747/1179] disas: Prefer cached CpuClass over CPU_GET_CLASS() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CpuState caches its CPUClass since commit 6fbdff87062 ("cpu: cache CPUClass in CPUState for hot code paths"), use it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250122093028.52416-8-philmd@linaro.org> --- disas/disas-common.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/disas/disas-common.c b/disas/disas-common.c index ae3f9e46ea1e..21c2f03430b8 100644 --- a/disas/disas-common.c +++ b/disas/disas-common.c @@ -62,9 +62,8 @@ void disas_initialize_debug_target(CPUDebug *s, CPUState *cpu) s->info.print_address_func = print_address; s->info.endian = BFD_ENDIAN_UNKNOWN; - CPUClass *cc = CPU_GET_CLASS(cpu); - if (cc->disas_set_info) { - cc->disas_set_info(cpu, &s->info); + if (cpu->cc->disas_set_info) { + cpu->cc->disas_set_info(cpu, &s->info); g_assert(s->info.endian != BFD_ENDIAN_UNKNOWN); } } From 0368d8d189af0444cd818f4f695beb1d94706f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:11:35 +0100 Subject: [PATCH 0748/1179] gdbstub: Prefer cached CpuClass over CPU_GET_CLASS() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CpuState caches its CPUClass since commit 6fbdff87062 ("cpu: cache CPUClass in CPUState for hot code paths"), use it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Acked-by: Alex Bennée Message-Id: <20250122093028.52416-9-philmd@linaro.org> --- gdbstub/gdbstub.c | 26 +++++++++----------------- gdbstub/system.c | 7 ++----- gdbstub/user-target.c | 6 ++---- gdbstub/user.c | 7 ++----- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index e366df12d4ae..282e13e163f5 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -354,7 +354,6 @@ static const char *get_feature_xml(const char *p, const char **newp, GDBProcess *process) { CPUState *cpu = gdb_get_first_cpu_in_process(process); - CPUClass *cc = CPU_GET_CLASS(cpu); GDBRegisterState *r; size_t len; @@ -377,11 +376,11 @@ static const char *get_feature_xml(const char *p, const char **newp, "" "")); - if (cc->gdb_arch_name) { + if (cpu->cc->gdb_arch_name) { g_ptr_array_add( xml, g_markup_printf_escaped("%s", - cc->gdb_arch_name(cpu))); + cpu->cc->gdb_arch_name(cpu))); } for (guint i = 0; i < cpu->gdb_regs->len; i++) { r = &g_array_index(cpu->gdb_regs, GDBRegisterState, i); @@ -520,11 +519,10 @@ GArray *gdb_get_register_list(CPUState *cpu) int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) { - CPUClass *cc = CPU_GET_CLASS(cpu); GDBRegisterState *r; - if (reg < cc->gdb_num_core_regs) { - return cc->gdb_read_register(cpu, buf, reg); + if (reg < cpu->cc->gdb_num_core_regs) { + return cpu->cc->gdb_read_register(cpu, buf, reg); } for (guint i = 0; i < cpu->gdb_regs->len; i++) { @@ -538,11 +536,10 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg) { - CPUClass *cc = CPU_GET_CLASS(cpu); GDBRegisterState *r; - if (reg < cc->gdb_num_core_regs) { - return cc->gdb_write_register(cpu, mem_buf, reg); + if (reg < cpu->cc->gdb_num_core_regs) { + return cpu->cc->gdb_write_register(cpu, mem_buf, reg); } for (guint i = 0; i < cpu->gdb_regs->len; i++) { @@ -570,7 +567,7 @@ static void gdb_register_feature(CPUState *cpu, int base_reg, void gdb_init_cpu(CPUState *cpu) { - CPUClass *cc = CPU_GET_CLASS(cpu); + CPUClass *cc = cpu->cc; const GDBFeature *feature; cpu->gdb_regs = g_array_new(false, false, sizeof(GDBRegisterState)); @@ -1646,11 +1643,8 @@ void gdb_extend_qsupported_features(char *qflags) static void handle_query_supported(GArray *params, void *user_ctx) { - CPUClass *cc; - g_string_printf(gdbserver_state.str_buf, "PacketSize=%x", MAX_PACKET_LENGTH); - cc = CPU_GET_CLASS(first_cpu); - if (cc->gdb_core_xml_file) { + if (first_cpu->cc->gdb_core_xml_file) { g_string_append(gdbserver_state.str_buf, ";qXfer:features:read+"); } @@ -1697,7 +1691,6 @@ static void handle_query_supported(GArray *params, void *user_ctx) static void handle_query_xfer_features(GArray *params, void *user_ctx) { GDBProcess *process; - CPUClass *cc; unsigned long len, total_len, addr; const char *xml; const char *p; @@ -1708,8 +1701,7 @@ static void handle_query_xfer_features(GArray *params, void *user_ctx) } process = gdb_get_cpu_process(gdbserver_state.g_cpu); - cc = CPU_GET_CLASS(gdbserver_state.g_cpu); - if (!cc->gdb_core_xml_file) { + if (!gdbserver_state.g_cpu->cc->gdb_core_xml_file) { gdb_put_packet(""); return; } diff --git a/gdbstub/system.c b/gdbstub/system.c index 416c1dbe1e92..dd22ff0fb3a0 100644 --- a/gdbstub/system.c +++ b/gdbstub/system.c @@ -456,8 +456,6 @@ static int phy_memory_mode; int gdb_target_memory_rw_debug(CPUState *cpu, hwaddr addr, uint8_t *buf, int len, bool is_write) { - CPUClass *cc; - if (phy_memory_mode) { if (is_write) { cpu_physical_memory_write(addr, buf, len); @@ -467,9 +465,8 @@ int gdb_target_memory_rw_debug(CPUState *cpu, hwaddr addr, return 0; } - cc = CPU_GET_CLASS(cpu); - if (cc->memory_rw_debug) { - return cc->memory_rw_debug(cpu, addr, buf, len, is_write); + if (cpu->cc->memory_rw_debug) { + return cpu->cc->memory_rw_debug(cpu, addr, buf, len, is_write); } return cpu_memory_rw_debug(cpu, addr, buf, len, is_write); diff --git a/gdbstub/user-target.c b/gdbstub/user-target.c index 4bfcf78aaab0..43231e695e84 100644 --- a/gdbstub/user-target.c +++ b/gdbstub/user-target.c @@ -233,10 +233,8 @@ void gdb_handle_query_offsets(GArray *params, void *user_ctx) static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr, uint8_t *buf, int len, bool is_write) { - CPUClass *cc; - cc = CPU_GET_CLASS(cpu); - if (cc->memory_rw_debug) { - return cc->memory_rw_debug(cpu, addr, buf, len, is_write); + if (cpu->cc->memory_rw_debug) { + return cpu->cc->memory_rw_debug(cpu, addr, buf, len, is_write); } return cpu_memory_rw_debug(cpu, addr, buf, len, is_write); } diff --git a/gdbstub/user.c b/gdbstub/user.c index 3730f32c415f..67403e5a252a 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -743,11 +743,8 @@ int gdb_continue_partial(char *newstates) int gdb_target_memory_rw_debug(CPUState *cpu, hwaddr addr, uint8_t *buf, int len, bool is_write) { - CPUClass *cc; - - cc = CPU_GET_CLASS(cpu); - if (cc->memory_rw_debug) { - return cc->memory_rw_debug(cpu, addr, buf, len, is_write); + if (cpu->cc->memory_rw_debug) { + return cpu->cc->memory_rw_debug(cpu, addr, buf, len, is_write); } return cpu_memory_rw_debug(cpu, addr, buf, len, is_write); } From bd7d74283464491b461bb1136e69963962fd05aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:11:43 +0100 Subject: [PATCH 0749/1179] hw/acpi: Prefer cached CpuClass over CPU_GET_CLASS() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CpuState caches its CPUClass since commit 6fbdff87062 ("cpu: cache CPUClass in CPUState for hot code paths"), use it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250122093028.52416-10-philmd@linaro.org> --- hw/acpi/cpu.c | 4 ++-- hw/acpi/cpu_hotplug.c | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hw/acpi/cpu.c b/hw/acpi/cpu.c index f70a2c045e1b..6f1ae79edbf3 100644 --- a/hw/acpi/cpu.c +++ b/hw/acpi/cpu.c @@ -235,8 +235,8 @@ void cpu_hotplug_hw_init(MemoryRegion *as, Object *owner, static AcpiCpuStatus *get_cpu_status(CPUHotplugState *cpu_st, DeviceState *dev) { - CPUClass *k = CPU_GET_CLASS(dev); - uint64_t cpu_arch_id = k->get_arch_id(CPU(dev)); + CPUState *cpu = CPU(dev); + uint64_t cpu_arch_id = cpu->cc->get_arch_id(cpu); int i; for (i = 0; i < cpu_st->dev_count; i++) { diff --git a/hw/acpi/cpu_hotplug.c b/hw/acpi/cpu_hotplug.c index 83b8bc5deb87..aa0e1e3efa54 100644 --- a/hw/acpi/cpu_hotplug.c +++ b/hw/acpi/cpu_hotplug.c @@ -62,10 +62,9 @@ static const MemoryRegionOps AcpiCpuHotplug_ops = { static void acpi_set_cpu_present_bit(AcpiCpuHotplug *g, CPUState *cpu, bool *swtchd_to_modern) { - CPUClass *k = CPU_GET_CLASS(cpu); int64_t cpu_id; - cpu_id = k->get_arch_id(cpu); + cpu_id = cpu->cc->get_arch_id(cpu); if ((cpu_id / 8) >= ACPI_GPE_PROC_LEN) { object_property_set_bool(g->device, "cpu-hotplug-legacy", false, &error_abort); From 0ebdf989c32031019aa0974dbb6b840fca52991e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:11:55 +0100 Subject: [PATCH 0750/1179] target/arm: Prefer cached CpuClass over CPU_GET_CLASS() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CpuState caches its CPUClass since commit 6fbdff87062 ("cpu: cache CPUClass in CPUState for hot code paths"), use it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250122093028.52416-11-philmd@linaro.org> --- target/arm/cpu.c | 3 +-- target/arm/tcg/cpu-v7m.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index cacbbc615a21..d7e61d08bbb4 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -830,7 +830,6 @@ static inline bool arm_excp_unmasked(CPUState *cs, unsigned int excp_idx, static bool arm_cpu_exec_interrupt(CPUState *cs, int interrupt_request) { - CPUClass *cc = CPU_GET_CLASS(cs); CPUARMState *env = cpu_env(cs); uint32_t cur_el = arm_current_el(env); bool secure = arm_is_secure(env); @@ -930,7 +929,7 @@ static bool arm_cpu_exec_interrupt(CPUState *cs, int interrupt_request) found: cs->exception_index = excp_idx; env->exception.target_el = target_el; - cc->tcg_ops->do_interrupt(cs); + cs->cc->tcg_ops->do_interrupt(cs); return true; } diff --git a/target/arm/tcg/cpu-v7m.c b/target/arm/tcg/cpu-v7m.c index 29a41fde694b..c4dd30927268 100644 --- a/target/arm/tcg/cpu-v7m.c +++ b/target/arm/tcg/cpu-v7m.c @@ -19,7 +19,6 @@ static bool arm_v7m_cpu_exec_interrupt(CPUState *cs, int interrupt_request) { - CPUClass *cc = CPU_GET_CLASS(cs); ARMCPU *cpu = ARM_CPU(cs); CPUARMState *env = &cpu->env; bool ret = false; @@ -35,7 +34,7 @@ static bool arm_v7m_cpu_exec_interrupt(CPUState *cs, int interrupt_request) if (interrupt_request & CPU_INTERRUPT_HARD && (armv7m_nvic_can_take_pending_exception(env->nvic))) { cs->exception_index = EXCP_IRQ; - cc->tcg_ops->do_interrupt(cs); + cs->cc->tcg_ops->do_interrupt(cs); ret = true; } return ret; From c0ee4dd1552b73bdde90875ce62e036a3ca8a007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:53:40 +0100 Subject: [PATCH 0751/1179] cpus: Restrict cpu_has_work() to system emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method is not used on user emulation, because there is always work to do there. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-2-philmd@linaro.org> --- include/hw/core/cpu.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index c6df426c9476..2d4ebb79905d 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -750,6 +750,20 @@ int cpu_asidx_from_attrs(CPUState *cpu, MemTxAttrs attrs); */ bool cpu_virtio_is_big_endian(CPUState *cpu); +/** + * cpu_has_work: + * @cpu: The vCPU to check. + * + * Checks whether the CPU has work to do. + * + * Returns: %true if the CPU has work, %false otherwise. + */ +static inline bool cpu_has_work(CPUState *cpu) +{ + g_assert(cpu->cc->has_work); + return cpu->cc->has_work(cpu); +} + #endif /* CONFIG_USER_ONLY */ /** @@ -816,20 +830,6 @@ CPUState *cpu_create(const char *typename); */ const char *parse_cpu_option(const char *cpu_option); -/** - * cpu_has_work: - * @cpu: The vCPU to check. - * - * Checks whether the CPU has work to do. - * - * Returns: %true if the CPU has work, %false otherwise. - */ -static inline bool cpu_has_work(CPUState *cpu) -{ - g_assert(cpu->cc->has_work); - return cpu->cc->has_work(cpu); -} - /** * qemu_cpu_is_self: * @cpu: The vCPU to check against. From 8f8dbe04bdafdbe265e9ae25737bb18daacc6ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:56:10 +0100 Subject: [PATCH 0752/1179] cpus: Un-inline cpu_has_work() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to expand cpu_has_work(), un-inline it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-3-philmd@linaro.org> --- hw/core/cpu-system.c | 6 ++++++ include/hw/core/cpu.h | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index e29664d39bbe..c10e3c9ba644 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -31,6 +31,12 @@ #include "migration/vmstate.h" #include "system/tcg.h" +bool cpu_has_work(CPUState *cpu) +{ + g_assert(cpu->cc->has_work); + return cpu->cc->has_work(cpu); +} + bool cpu_paging_enabled(const CPUState *cpu) { if (cpu->cc->sysemu_ops->get_paging_enabled) { diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index 2d4ebb79905d..a54dd2cf699f 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -758,11 +758,7 @@ bool cpu_virtio_is_big_endian(CPUState *cpu); * * Returns: %true if the CPU has work, %false otherwise. */ -static inline bool cpu_has_work(CPUState *cpu) -{ - g_assert(cpu->cc->has_work); - return cpu->cc->has_work(cpu); -} +bool cpu_has_work(CPUState *cpu); #endif /* CONFIG_USER_ONLY */ From 72eacd623170dd680557ece6957575c30774cdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:57:16 +0100 Subject: [PATCH 0753/1179] cpus: Introduce SysemuCPUOps::has_work() handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SysemuCPUOps::has_work() is similar to CPUClass::has_work(), but only exposed on system emulation. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-4-philmd@linaro.org> --- hw/core/cpu-system.c | 4 ++++ include/accel/tcg/cpu-ops.h | 2 +- include/hw/core/sysemu-cpu-ops.h | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index c10e3c9ba644..601335fd764b 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -33,6 +33,10 @@ bool cpu_has_work(CPUState *cpu) { + if (cpu->cc->sysemu_ops->has_work) { + return cpu->cc->sysemu_ops->has_work(cpu); + } + g_assert(cpu->cc->has_work); return cpu->cc->has_work(cpu); } diff --git a/include/accel/tcg/cpu-ops.h b/include/accel/tcg/cpu-ops.h index 2e3f1690f127..f60e5303f214 100644 --- a/include/accel/tcg/cpu-ops.h +++ b/include/accel/tcg/cpu-ops.h @@ -141,7 +141,7 @@ struct TCGCPUOps { * * This method must be provided. If the target does not need to * do anything special for halt, the same function used for its - * CPUClass::has_work method can be used here, as they have the + * SysemuCPUOps::has_work method can be used here, as they have the * same function signature. */ bool (*cpu_exec_halt)(CPUState *cpu); diff --git a/include/hw/core/sysemu-cpu-ops.h b/include/hw/core/sysemu-cpu-ops.h index 0df5b058f500..dee8a62ca989 100644 --- a/include/hw/core/sysemu-cpu-ops.h +++ b/include/hw/core/sysemu-cpu-ops.h @@ -16,6 +16,10 @@ * struct SysemuCPUOps: System operations specific to a CPU class */ typedef struct SysemuCPUOps { + /** + * @has_work: Callback for checking if there is work to do. + */ + bool (*has_work)(CPUState *cpu); /** * @get_memory_mapping: Callback for obtaining the memory mappings. */ From 35e0769d3f341f9a3fc1de104ac57a3d1080d3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:58:10 +0100 Subject: [PATCH 0754/1179] target/alpha: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-5-philmd@linaro.org> --- target/alpha/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/alpha/cpu.c b/target/alpha/cpu.c index 2eabd7724df3..584c2aa76bd9 100644 --- a/target/alpha/cpu.c +++ b/target/alpha/cpu.c @@ -63,6 +63,7 @@ static void alpha_restore_state_to_opc(CPUState *cs, } } +#ifndef CONFIG_USER_ONLY static bool alpha_cpu_has_work(CPUState *cs) { /* Here we are checking to see if the CPU should wake up from HALT. @@ -77,6 +78,7 @@ static bool alpha_cpu_has_work(CPUState *cs) | CPU_INTERRUPT_SMP | CPU_INTERRUPT_MCHK); } +#endif /* !CONFIG_USER_ONLY */ static int alpha_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -224,6 +226,7 @@ static void alpha_cpu_initfn(Object *obj) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps alpha_sysemu_ops = { + .has_work = alpha_cpu_has_work, .get_phys_page_debug = alpha_cpu_get_phys_page_debug, }; #endif @@ -259,7 +262,6 @@ static void alpha_cpu_class_init(ObjectClass *oc, void *data) &acc->parent_realize); cc->class_by_name = alpha_cpu_class_by_name; - cc->has_work = alpha_cpu_has_work; cc->mmu_index = alpha_cpu_mmu_index; cc->dump_state = alpha_cpu_dump_state; cc->set_pc = alpha_cpu_set_pc; From f5e67b6ddad2262fd692caeb6090fa138241306f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:59:17 +0100 Subject: [PATCH 0755/1179] target/arm: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-6-philmd@linaro.org> --- target/arm/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index d7e61d08bbb4..01786ac7879c 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -123,6 +123,7 @@ void arm_restore_state_to_opc(CPUState *cs, } #endif /* CONFIG_TCG */ +#ifndef CONFIG_USER_ONLY /* * With SCTLR_ELx.NMI == 0, IRQ with Superpriority is masked identically with * IRQ without Superpriority. Moreover, if the GIC is configured so that @@ -141,6 +142,7 @@ static bool arm_cpu_has_work(CPUState *cs) | CPU_INTERRUPT_VFIQ | CPU_INTERRUPT_VIRQ | CPU_INTERRUPT_VSERR | CPU_INTERRUPT_EXITTB); } +#endif /* !CONFIG_USER_ONLY */ static int arm_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -2655,6 +2657,7 @@ static const gchar *arm_gdb_arch_name(CPUState *cs) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps arm_sysemu_ops = { + .has_work = arm_cpu_has_work, .get_phys_page_attrs_debug = arm_cpu_get_phys_page_attrs_debug, .asidx_from_attrs = arm_asidx_from_attrs, .write_elf32_note = arm_cpu_write_elf32_note, @@ -2705,7 +2708,6 @@ static void arm_cpu_class_init(ObjectClass *oc, void *data) &acc->parent_phases); cc->class_by_name = arm_cpu_class_by_name; - cc->has_work = arm_cpu_has_work; cc->mmu_index = arm_cpu_mmu_index; cc->dump_state = arm_cpu_dump_state; cc->set_pc = arm_cpu_set_pc; From 6c8d41eab6d1774105562329eea3a731dd267c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:59:48 +0100 Subject: [PATCH 0756/1179] target/avr: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-7-philmd@linaro.org> --- target/avr/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/avr/cpu.c b/target/avr/cpu.c index 2871d30540a4..834c7082aa71 100644 --- a/target/avr/cpu.c +++ b/target/avr/cpu.c @@ -201,6 +201,7 @@ static void avr_cpu_dump_state(CPUState *cs, FILE *f, int flags) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps avr_sysemu_ops = { + .has_work = avr_cpu_has_work, .get_phys_page_debug = avr_cpu_get_phys_page_debug, }; @@ -233,7 +234,6 @@ static void avr_cpu_class_init(ObjectClass *oc, void *data) cc->class_by_name = avr_cpu_class_by_name; - cc->has_work = avr_cpu_has_work; cc->mmu_index = avr_cpu_mmu_index; cc->dump_state = avr_cpu_dump_state; cc->set_pc = avr_cpu_set_pc; From 7ec1c634215527782ff508dd88f5bb4e324a3d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 12:59:57 +0100 Subject: [PATCH 0757/1179] target/hexagon: Remove CPUClass:has_work() handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove as unreachable code. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Reviewed-by: Brian Cain Message-Id: <20250125170125.32855-8-philmd@linaro.org> --- target/hexagon/cpu.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index a9beb9a17572..766b6786511c 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -262,11 +262,6 @@ static void hexagon_cpu_synchronize_from_tb(CPUState *cs, cpu_env(cs)->gpr[HEX_REG_PC] = tb->pc; } -static bool hexagon_cpu_has_work(CPUState *cs) -{ - return true; -} - static void hexagon_restore_state_to_opc(CPUState *cs, const TranslationBlock *tb, const uint64_t *data) @@ -346,7 +341,6 @@ static void hexagon_cpu_class_init(ObjectClass *c, void *data) &mcc->parent_phases); cc->class_by_name = hexagon_cpu_class_by_name; - cc->has_work = hexagon_cpu_has_work; cc->dump_state = hexagon_dump_state; cc->set_pc = hexagon_cpu_set_pc; cc->get_pc = hexagon_cpu_get_pc; From 91231d99acec93dc62a524868199801395c9f69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:00:16 +0100 Subject: [PATCH 0758/1179] target/hppa: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-9-philmd@linaro.org> --- target/hppa/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/hppa/cpu.c b/target/hppa/cpu.c index d15f8c9c2172..2a85495d02f9 100644 --- a/target/hppa/cpu.c +++ b/target/hppa/cpu.c @@ -131,10 +131,12 @@ static void hppa_restore_state_to_opc(CPUState *cs, env->psw_n = 0; } +#ifndef CONFIG_USER_ONLY static bool hppa_cpu_has_work(CPUState *cs) { return cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_NMI); } +#endif /* !CONFIG_USER_ONLY */ static int hppa_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -242,6 +244,7 @@ static ObjectClass *hppa_cpu_class_by_name(const char *cpu_model) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps hppa_sysemu_ops = { + .has_work = hppa_cpu_has_work, .get_phys_page_debug = hppa_cpu_get_phys_page_debug, }; #endif @@ -278,7 +281,6 @@ static void hppa_cpu_class_init(ObjectClass *oc, void *data) &acc->parent_phases); cc->class_by_name = hppa_cpu_class_by_name; - cc->has_work = hppa_cpu_has_work; cc->mmu_index = hppa_cpu_mmu_index; cc->dump_state = hppa_cpu_dump_state; cc->set_pc = hppa_cpu_set_pc; From f0bef005712d55463904760533a12480880d9a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:02:29 +0100 Subject: [PATCH 0759/1179] target/i386: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move has_work() from CPUClass to SysemuCPUOps, restrict x86_cpu_pending_interrupt() to system. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-10-philmd@linaro.org> --- target/i386/cpu.c | 8 +++----- target/i386/cpu.h | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index b3e1c2bca499..1b64ceaaba46 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -8604,16 +8604,15 @@ static vaddr x86_cpu_get_pc(CPUState *cs) return cpu->env.eip + cpu->env.segs[R_CS].base; } +#if !defined(CONFIG_USER_ONLY) int x86_cpu_pending_interrupt(CPUState *cs, int interrupt_request) { X86CPU *cpu = X86_CPU(cs); CPUX86State *env = &cpu->env; -#if !defined(CONFIG_USER_ONLY) if (interrupt_request & CPU_INTERRUPT_POLL) { return CPU_INTERRUPT_POLL; } -#endif if (interrupt_request & CPU_INTERRUPT_SIPI) { return CPU_INTERRUPT_SIPI; } @@ -8634,14 +8633,12 @@ int x86_cpu_pending_interrupt(CPUState *cs, int interrupt_request) (env->eflags & IF_MASK && !(env->hflags & HF_INHIBIT_IRQ_MASK))))) { return CPU_INTERRUPT_HARD; -#if !defined(CONFIG_USER_ONLY) } else if (env->hflags2 & HF2_VGIF_MASK) { if((interrupt_request & CPU_INTERRUPT_VIRQ) && (env->eflags & IF_MASK) && !(env->hflags & HF_INHIBIT_IRQ_MASK)) { return CPU_INTERRUPT_VIRQ; } -#endif } } @@ -8652,6 +8649,7 @@ static bool x86_cpu_has_work(CPUState *cs) { return x86_cpu_pending_interrupt(cs, cs->interrupt_request) != 0; } +#endif /* !CONFIG_USER_ONLY */ int x86_mmu_index_pl(CPUX86State *env, unsigned pl) { @@ -8893,6 +8891,7 @@ static const Property x86_cpu_properties[] = { #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps i386_sysemu_ops = { + .has_work = x86_cpu_has_work, .get_memory_mapping = x86_cpu_get_memory_mapping, .get_paging_enabled = x86_cpu_get_paging_enabled, .get_phys_page_attrs_debug = x86_cpu_get_phys_page_attrs_debug, @@ -8926,7 +8925,6 @@ static void x86_cpu_common_class_init(ObjectClass *oc, void *data) cc->class_by_name = x86_cpu_class_by_name; cc->parse_features = x86_cpu_parse_featurestr; - cc->has_work = x86_cpu_has_work; cc->mmu_index = x86_cpu_mmu_index; cc->dump_state = x86_cpu_dump_state; cc->set_pc = x86_cpu_set_pc; diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 7882b63b9b61..76f24446a55d 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2349,8 +2349,6 @@ struct X86CPUClass { extern const VMStateDescription vmstate_x86_cpu; #endif -int x86_cpu_pending_interrupt(CPUState *cs, int interrupt_request); - int x86_cpu_write_elf64_note(WriteCoreDumpFunction f, CPUState *cpu, int cpuid, DumpState *s); int x86_cpu_write_elf32_note(WriteCoreDumpFunction f, CPUState *cpu, @@ -2373,6 +2371,8 @@ void x86_cpu_list(void); int cpu_x86_support_mca_broadcast(CPUX86State *env); #ifndef CONFIG_USER_ONLY +int x86_cpu_pending_interrupt(CPUState *cs, int interrupt_request); + hwaddr x86_cpu_get_phys_page_attrs_debug(CPUState *cpu, vaddr addr, MemTxAttrs *attrs); int cpu_get_pic_interrupt(CPUX86State *s); From 87969d6681ac2837cd6c8ce1724a305011f770d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:03:30 +0100 Subject: [PATCH 0760/1179] target/loongarch: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-11-philmd@linaro.org> --- target/loongarch/cpu.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index 49f603149dc0..ea1665e27053 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -350,11 +350,9 @@ static void loongarch_restore_state_to_opc(CPUState *cs, } #endif /* CONFIG_TCG */ +#ifndef CONFIG_USER_ONLY static bool loongarch_cpu_has_work(CPUState *cs) { -#ifdef CONFIG_USER_ONLY - return true; -#else bool has_work = false; if ((cs->interrupt_request & CPU_INTERRUPT_HARD) && @@ -363,8 +361,8 @@ static bool loongarch_cpu_has_work(CPUState *cs) } return has_work; -#endif } +#endif /* !CONFIG_USER_ONLY */ static int loongarch_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -885,6 +883,7 @@ static const TCGCPUOps loongarch_tcg_ops = { #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps loongarch_sysemu_ops = { + .has_work = loongarch_cpu_has_work, .write_elf64_note = loongarch_cpu_write_elf64_note, .get_phys_page_debug = loongarch_cpu_get_phys_page_debug, }; @@ -920,7 +919,6 @@ static void loongarch_cpu_class_init(ObjectClass *c, void *data) &lacc->parent_phases); cc->class_by_name = loongarch_cpu_class_by_name; - cc->has_work = loongarch_cpu_has_work; cc->mmu_index = loongarch_cpu_mmu_index; cc->dump_state = loongarch_cpu_dump_state; cc->set_pc = loongarch_cpu_set_pc; From 4a119cfc6cd7affc07d4b76c1340cf96b6ff0268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:03:41 +0100 Subject: [PATCH 0761/1179] target/m68k: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-12-philmd@linaro.org> --- target/m68k/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index df8b9c53fca0..0065e1c1ca52 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -51,10 +51,12 @@ static void m68k_restore_state_to_opc(CPUState *cs, } } +#ifndef CONFIG_USER_ONLY static bool m68k_cpu_has_work(CPUState *cs) { return cs->interrupt_request & CPU_INTERRUPT_HARD; } +#endif /* !CONFIG_USER_ONLY */ static int m68k_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -579,6 +581,7 @@ static const VMStateDescription vmstate_m68k_cpu = { #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps m68k_sysemu_ops = { + .has_work = m68k_cpu_has_work, .get_phys_page_debug = m68k_cpu_get_phys_page_debug, }; #endif /* !CONFIG_USER_ONLY */ @@ -612,7 +615,6 @@ static void m68k_cpu_class_init(ObjectClass *c, void *data) &mcc->parent_phases); cc->class_by_name = m68k_cpu_class_by_name; - cc->has_work = m68k_cpu_has_work; cc->mmu_index = m68k_cpu_mmu_index; cc->dump_state = m68k_cpu_dump_state; cc->set_pc = m68k_cpu_set_pc; From 55f29126b6f980f759e0bdab2e83361b98232305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:03:51 +0100 Subject: [PATCH 0762/1179] target/microblaze: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-13-philmd@linaro.org> --- target/microblaze/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/microblaze/cpu.c b/target/microblaze/cpu.c index d5ee1244cad7..f3bebea856ee 100644 --- a/target/microblaze/cpu.c +++ b/target/microblaze/cpu.c @@ -115,10 +115,12 @@ static void mb_restore_state_to_opc(CPUState *cs, cpu->env.iflags = data[1]; } +#ifndef CONFIG_USER_ONLY static bool mb_cpu_has_work(CPUState *cs) { return cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_NMI); } +#endif /* !CONFIG_USER_ONLY */ static int mb_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -417,6 +419,7 @@ static ObjectClass *mb_cpu_class_by_name(const char *cpu_model) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps mb_sysemu_ops = { + .has_work = mb_cpu_has_work, .get_phys_page_attrs_debug = mb_cpu_get_phys_page_attrs_debug, }; #endif @@ -452,7 +455,6 @@ static void mb_cpu_class_init(ObjectClass *oc, void *data) &mcc->parent_phases); cc->class_by_name = mb_cpu_class_by_name; - cc->has_work = mb_cpu_has_work; cc->mmu_index = mb_cpu_mmu_index; cc->dump_state = mb_cpu_dump_state; cc->set_pc = mb_cpu_set_pc; From 85edafe385f538ce3a84ed478faea12e28ab1720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:04:01 +0100 Subject: [PATCH 0763/1179] target/mips: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move has_work() from CPUClass to SysemuCPUOps and cpu_mips_hw_interrupts_enabled() to system. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-14-philmd@linaro.org> --- target/mips/cpu.c | 4 +++- target/mips/internal.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/target/mips/cpu.c b/target/mips/cpu.c index e76298699ab3..b207106dd793 100644 --- a/target/mips/cpu.c +++ b/target/mips/cpu.c @@ -132,6 +132,7 @@ static vaddr mips_cpu_get_pc(CPUState *cs) return cpu->env.active_tc.PC; } +#if !defined(CONFIG_USER_ONLY) static bool mips_cpu_has_work(CPUState *cs) { CPUMIPSState *env = cpu_env(cs); @@ -177,6 +178,7 @@ static bool mips_cpu_has_work(CPUState *cs) } return has_work; } +#endif /* !CONFIG_USER_ONLY */ static int mips_cpu_mmu_index(CPUState *cs, bool ifunc) { @@ -534,6 +536,7 @@ static ObjectClass *mips_cpu_class_by_name(const char *cpu_model) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps mips_sysemu_ops = { + .has_work = mips_cpu_has_work, .get_phys_page_debug = mips_cpu_get_phys_page_debug, .legacy_vmsd = &vmstate_mips_cpu, }; @@ -577,7 +580,6 @@ static void mips_cpu_class_init(ObjectClass *c, void *data) &mcc->parent_phases); cc->class_by_name = mips_cpu_class_by_name; - cc->has_work = mips_cpu_has_work; cc->mmu_index = mips_cpu_mmu_index; cc->dump_state = mips_cpu_dump_state; cc->set_pc = mips_cpu_set_pc; diff --git a/target/mips/internal.h b/target/mips/internal.h index 91c786cff8ac..28eb28936ba0 100644 --- a/target/mips/internal.h +++ b/target/mips/internal.h @@ -162,8 +162,6 @@ void cpu_mips_store_cause(CPUMIPSState *env, target_ulong val); extern const VMStateDescription vmstate_mips_cpu; -#endif /* !CONFIG_USER_ONLY */ - static inline bool cpu_mips_hw_interrupts_enabled(CPUMIPSState *env) { return (env->CP0_Status & (1 << CP0St_IE)) && @@ -206,6 +204,8 @@ static inline bool cpu_mips_hw_interrupts_pending(CPUMIPSState *env) return r; } +#endif /* !CONFIG_USER_ONLY */ + void msa_reset(CPUMIPSState *env); /* cp0_timer.c */ From 6a2b2943145c1dcb44091afa993de6d01824190b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:04:11 +0100 Subject: [PATCH 0764/1179] target/openrisc: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-15-philmd@linaro.org> --- target/openrisc/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/openrisc/cpu.c b/target/openrisc/cpu.c index e8c357ae8369..e8abf1f8b5cd 100644 --- a/target/openrisc/cpu.c +++ b/target/openrisc/cpu.c @@ -63,11 +63,13 @@ static void openrisc_restore_state_to_opc(CPUState *cs, } } +#ifndef CONFIG_USER_ONLY static bool openrisc_cpu_has_work(CPUState *cs) { return cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_TIMER); } +#endif /* !CONFIG_USER_ONLY */ static int openrisc_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -233,6 +235,7 @@ static void openrisc_any_initfn(Object *obj) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps openrisc_sysemu_ops = { + .has_work = openrisc_cpu_has_work, .get_phys_page_debug = openrisc_cpu_get_phys_page_debug, }; #endif @@ -266,7 +269,6 @@ static void openrisc_cpu_class_init(ObjectClass *oc, void *data) &occ->parent_phases); cc->class_by_name = openrisc_cpu_class_by_name; - cc->has_work = openrisc_cpu_has_work; cc->mmu_index = openrisc_cpu_mmu_index; cc->dump_state = openrisc_cpu_dump_state; cc->set_pc = openrisc_cpu_set_pc; From 71e950afe2b9cc43d1c4186c40c1aa0dced1077d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:04:18 +0100 Subject: [PATCH 0765/1179] target/ppc: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-16-philmd@linaro.org> --- target/ppc/cpu_init.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index b9772c53ecce..1780cabfc6a5 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -7177,10 +7177,12 @@ static void ppc_restore_state_to_opc(CPUState *cs, } #endif /* CONFIG_TCG */ +#ifndef CONFIG_USER_ONLY static bool ppc_cpu_has_work(CPUState *cs) { return cs->interrupt_request & CPU_INTERRUPT_HARD; } +#endif /* !CONFIG_USER_ONLY */ static int ppc_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -7423,6 +7425,7 @@ static void ppc_disas_set_info(CPUState *cs, disassemble_info *info) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps ppc_sysemu_ops = { + .has_work = ppc_cpu_has_work, .get_phys_page_debug = ppc_cpu_get_phys_page_debug, .write_elf32_note = ppc32_cpu_write_elf32_note, .write_elf64_note = ppc64_cpu_write_elf64_note, @@ -7474,7 +7477,6 @@ static void ppc_cpu_class_init(ObjectClass *oc, void *data) &pcc->parent_phases); cc->class_by_name = ppc_cpu_class_by_name; - cc->has_work = ppc_cpu_has_work; cc->mmu_index = ppc_cpu_mmu_index; cc->dump_state = ppc_cpu_dump_state; cc->set_pc = ppc_cpu_set_pc; From 3810e17173c3c1848f44c4fccf0160477a399dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:06:54 +0100 Subject: [PATCH 0766/1179] target/riscv: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-17-philmd@linaro.org> --- target/riscv/cpu.c | 8 +++----- target/riscv/internals.h | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 1c000c30f8df..09ded6829a2c 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -1006,9 +1006,9 @@ static vaddr riscv_cpu_get_pc(CPUState *cs) return env->pc; } +#ifndef CONFIG_USER_ONLY bool riscv_cpu_has_work(CPUState *cs) { -#ifndef CONFIG_USER_ONLY RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; /* @@ -1018,10 +1018,8 @@ bool riscv_cpu_has_work(CPUState *cs) return riscv_cpu_all_pending(env) != 0 || riscv_cpu_sirq_pending(env) != RISCV_EXCP_NONE || riscv_cpu_vsirq_pending(env) != RISCV_EXCP_NONE; -#else - return true; -#endif } +#endif /* !CONFIG_USER_ONLY */ static int riscv_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -3029,6 +3027,7 @@ static int64_t riscv_get_arch_id(CPUState *cs) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps riscv_sysemu_ops = { + .has_work = riscv_cpu_has_work, .get_phys_page_debug = riscv_cpu_get_phys_page_debug, .write_elf64_note = riscv_cpu_write_elf64_note, .write_elf32_note = riscv_cpu_write_elf32_note, @@ -3050,7 +3049,6 @@ static void riscv_cpu_common_class_init(ObjectClass *c, void *data) &mcc->parent_phases); cc->class_by_name = riscv_cpu_class_by_name; - cc->has_work = riscv_cpu_has_work; cc->mmu_index = riscv_cpu_mmu_index; cc->dump_state = riscv_cpu_dump_state; cc->set_pc = riscv_cpu_set_pc; diff --git a/target/riscv/internals.h b/target/riscv/internals.h index 67291933f848..213aff31d85d 100644 --- a/target/riscv/internals.h +++ b/target/riscv/internals.h @@ -142,8 +142,10 @@ static inline float16 check_nanbox_h(CPURISCVState *env, uint64_t f) } } -/* Our implementation of CPUClass::has_work */ +#ifndef CONFIG_USER_ONLY +/* Our implementation of SysemuCPUOps::has_work */ bool riscv_cpu_has_work(CPUState *cs); +#endif /* Zjpm addr masking routine */ static inline target_ulong adjust_addr_body(CPURISCVState *env, From 52df41e3536865ee3bea1bd22426b3cb84186165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:07:26 +0100 Subject: [PATCH 0767/1179] target/rx: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-18-philmd@linaro.org> --- target/rx/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/rx/cpu.c b/target/rx/cpu.c index f01e069a907b..0ba0d55ab5bd 100644 --- a/target/rx/cpu.c +++ b/target/rx/cpu.c @@ -196,6 +196,7 @@ static void rx_cpu_init(Object *obj) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps rx_sysemu_ops = { + .has_work = rx_cpu_has_work, .get_phys_page_debug = rx_cpu_get_phys_page_debug, }; @@ -226,7 +227,6 @@ static void rx_cpu_class_init(ObjectClass *klass, void *data) &rcc->parent_phases); cc->class_by_name = rx_cpu_class_by_name; - cc->has_work = rx_cpu_has_work; cc->mmu_index = riscv_cpu_mmu_index; cc->dump_state = rx_cpu_dump_state; cc->set_pc = rx_cpu_set_pc; From 0df9781074cab07dcb526b6e83bd0570eab3afe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:08:53 +0100 Subject: [PATCH 0768/1179] target/s390x: Restrict I/O handler installers to system emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-19-philmd@linaro.org> --- target/s390x/s390x-internal.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h index a750e7a343a4..6e2c98de97a9 100644 --- a/target/s390x/s390x-internal.h +++ b/target/s390x/s390x-internal.h @@ -356,6 +356,7 @@ void cpu_inject_stop(S390CPU *cpu); /* ioinst.c */ +#ifndef CONFIG_USER_ONLY void ioinst_handle_xsch(S390CPU *cpu, uint64_t reg1, uintptr_t ra); void ioinst_handle_csch(S390CPU *cpu, uint64_t reg1, uintptr_t ra); void ioinst_handle_hsch(S390CPU *cpu, uint64_t reg1, uintptr_t ra); @@ -373,6 +374,7 @@ void ioinst_handle_schm(S390CPU *cpu, uint64_t reg1, uint64_t reg2, void ioinst_handle_rsch(S390CPU *cpu, uint64_t reg1, uintptr_t ra); void ioinst_handle_rchp(S390CPU *cpu, uint64_t reg1, uintptr_t ra); void ioinst_handle_sal(S390CPU *cpu, uint64_t reg1, uintptr_t ra); +#endif /* CONFIG_USER_ONLY */ /* mem_helper.c */ From f54c047e866802aa9c82f226dbb6b2542c4ff245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:07:44 +0100 Subject: [PATCH 0769/1179] target/s390x: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move has_work() from CPUClass to SysemuCPUOps, move s390_cpu_has_work() to cpu-system.c so it is only build for system emulation binaries, restrict functions not used anymore on user emulation in interrupt.c. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-20-philmd@linaro.org> --- target/s390x/cpu-system.c | 18 ++++++++++++++++++ target/s390x/cpu.c | 18 ------------------ target/s390x/interrupt.c | 8 ++------ target/s390x/s390x-internal.h | 3 +++ 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/target/s390x/cpu-system.c b/target/s390x/cpu-system.c index e9f8e7cc72f5..9b380e343c25 100644 --- a/target/s390x/cpu-system.c +++ b/target/s390x/cpu-system.c @@ -39,6 +39,23 @@ #include "system/tcg.h" #include "hw/core/sysemu-cpu-ops.h" +bool s390_cpu_has_work(CPUState *cs) +{ + S390CPU *cpu = S390_CPU(cs); + + /* STOPPED cpus can never wake up */ + if (s390_cpu_get_state(cpu) != S390_CPU_STATE_LOAD && + s390_cpu_get_state(cpu) != S390_CPU_STATE_OPERATING) { + return false; + } + + if (!(cs->interrupt_request & CPU_INTERRUPT_HARD)) { + return false; + } + + return s390_cpu_has_int(cpu); +} + /* S390CPUClass::load_normal() */ static void s390_cpu_load_normal(CPUState *s) { @@ -158,6 +175,7 @@ void s390_cpu_finalize(Object *obj) } static const struct SysemuCPUOps s390_sysemu_ops = { + .has_work = s390_cpu_has_work, .get_phys_page_debug = s390_cpu_get_phys_page_debug, .get_crash_info = s390_cpu_get_crash_info, .write_elf64_note = s390_cpu_write_elf64_note, diff --git a/target/s390x/cpu.c b/target/s390x/cpu.c index 972d265478d7..d73142600bff 100644 --- a/target/s390x/cpu.c +++ b/target/s390x/cpu.c @@ -126,23 +126,6 @@ static vaddr s390_cpu_get_pc(CPUState *cs) return cpu->env.psw.addr; } -static bool s390_cpu_has_work(CPUState *cs) -{ - S390CPU *cpu = S390_CPU(cs); - - /* STOPPED cpus can never wake up */ - if (s390_cpu_get_state(cpu) != S390_CPU_STATE_LOAD && - s390_cpu_get_state(cpu) != S390_CPU_STATE_OPERATING) { - return false; - } - - if (!(cs->interrupt_request & CPU_INTERRUPT_HARD)) { - return false; - } - - return s390_cpu_has_int(cpu); -} - static int s390x_cpu_mmu_index(CPUState *cs, bool ifetch) { return s390x_env_mmu_index(cpu_env(cs), ifetch); @@ -395,7 +378,6 @@ static void s390_cpu_class_init(ObjectClass *oc, void *data) &scc->parent_phases); cc->class_by_name = s390_cpu_class_by_name, - cc->has_work = s390_cpu_has_work; cc->mmu_index = s390x_cpu_mmu_index; cc->dump_state = s390_cpu_dump_state; cc->query_cpu_fast = s390_query_cpu_fast; diff --git a/target/s390x/interrupt.c b/target/s390x/interrupt.c index d68d8955b1a5..4ae6e2ddeaad 100644 --- a/target/s390x/interrupt.c +++ b/target/s390x/interrupt.c @@ -30,6 +30,7 @@ void trigger_pgm_exception(CPUS390XState *env, uint32_t code) /* env->int_pgm_ilen is already set, or will be set during unwinding */ } +#if !defined(CONFIG_USER_ONLY) void s390_program_interrupt(CPUS390XState *env, uint32_t code, uintptr_t ra) { if (kvm_enabled()) { @@ -41,7 +42,6 @@ void s390_program_interrupt(CPUS390XState *env, uint32_t code, uintptr_t ra) } } -#if !defined(CONFIG_USER_ONLY) void cpu_inject_clock_comparator(S390CPU *cpu) { CPUS390XState *env = &cpu->env; @@ -225,11 +225,9 @@ bool s390_cpu_has_stop_int(S390CPU *cpu) return env->pending_int & INTERRUPT_STOP; } -#endif bool s390_cpu_has_int(S390CPU *cpu) { -#ifndef CONFIG_USER_ONLY if (!tcg_enabled()) { return false; } @@ -238,7 +236,5 @@ bool s390_cpu_has_int(S390CPU *cpu) s390_cpu_has_io_int(cpu) || s390_cpu_has_restart_int(cpu) || s390_cpu_has_stop_int(cpu); -#else - return false; -#endif } +#endif /* !CONFIG_USER_ONLY */ diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h index 6e2c98de97a9..a4ba6227ab4a 100644 --- a/target/s390x/s390x-internal.h +++ b/target/s390x/s390x-internal.h @@ -245,6 +245,7 @@ bool s390_cpu_system_realize(DeviceState *dev, Error **errp); void s390_cpu_finalize(Object *obj); void s390_cpu_system_class_init(CPUClass *cc); void s390_cpu_machine_reset_cb(void *opaque); +bool s390_cpu_has_work(CPUState *cs); #else static inline unsigned int s390_cpu_halt(S390CPU *cpu) @@ -341,6 +342,7 @@ void cpu_unmap_lowcore(LowCore *lowcore); /* interrupt.c */ void trigger_pgm_exception(CPUS390XState *env, uint32_t code); +#ifndef CONFIG_USER_ONLY void cpu_inject_clock_comparator(S390CPU *cpu); void cpu_inject_cpu_timer(S390CPU *cpu); void cpu_inject_emergency_signal(S390CPU *cpu, uint16_t src_cpu_addr); @@ -353,6 +355,7 @@ bool s390_cpu_has_restart_int(S390CPU *cpu); bool s390_cpu_has_stop_int(S390CPU *cpu); void cpu_inject_restart(S390CPU *cpu); void cpu_inject_stop(S390CPU *cpu); +#endif /* CONFIG_USER_ONLY */ /* ioinst.c */ From 644a8119bd54b075029acf7fad7740afb4fe9e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:09:23 +0100 Subject: [PATCH 0770/1179] target/sh4: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-21-philmd@linaro.org> --- target/sh4/cpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/sh4/cpu.c b/target/sh4/cpu.c index c2aaa40a037f..ce84bdf539a6 100644 --- a/target/sh4/cpu.c +++ b/target/sh4/cpu.c @@ -82,12 +82,12 @@ static bool superh_io_recompile_replay_branch(CPUState *cs, } return false; } -#endif static bool superh_cpu_has_work(CPUState *cs) { return cs->interrupt_request & CPU_INTERRUPT_HARD; } +#endif /* !CONFIG_USER_ONLY */ static int sh4_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -254,6 +254,7 @@ static const VMStateDescription vmstate_sh_cpu = { #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps sh4_sysemu_ops = { + .has_work = superh_cpu_has_work, .get_phys_page_debug = superh_cpu_get_phys_page_debug, }; #endif @@ -290,7 +291,6 @@ static void superh_cpu_class_init(ObjectClass *oc, void *data) &scc->parent_phases); cc->class_by_name = superh_cpu_class_by_name; - cc->has_work = superh_cpu_has_work; cc->mmu_index = sh4_cpu_mmu_index; cc->dump_state = superh_cpu_dump_state; cc->set_pc = superh_cpu_set_pc; From 82f0f44d62b3faacc447a68d05f3b58d3567ec5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:09:35 +0100 Subject: [PATCH 0771/1179] target/sparc: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-22-philmd@linaro.org> --- target/sparc/cpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/sparc/cpu.c b/target/sparc/cpu.c index e27b1fa2949e..571612011730 100644 --- a/target/sparc/cpu.c +++ b/target/sparc/cpu.c @@ -777,11 +777,13 @@ static void sparc_restore_state_to_opc(CPUState *cs, } } +#ifndef CONFIG_USER_ONLY static bool sparc_cpu_has_work(CPUState *cs) { return (cs->interrupt_request & CPU_INTERRUPT_HARD) && cpu_interrupts_enabled(cpu_env(cs)); } +#endif /* !CONFIG_USER_ONLY */ static int sparc_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -988,6 +990,7 @@ static const Property sparc_cpu_properties[] = { #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps sparc_sysemu_ops = { + .has_work = sparc_cpu_has_work, .get_phys_page_debug = sparc_cpu_get_phys_page_debug, .legacy_vmsd = &vmstate_sparc_cpu, }; @@ -1029,7 +1032,6 @@ static void sparc_cpu_class_init(ObjectClass *oc, void *data) cc->class_by_name = sparc_cpu_class_by_name; cc->parse_features = sparc_cpu_parse_features; - cc->has_work = sparc_cpu_has_work; cc->mmu_index = sparc_cpu_mmu_index; cc->dump_state = sparc_cpu_dump_state; #if !defined(TARGET_SPARC64) && !defined(CONFIG_USER_ONLY) From d55ea95f256f1300edea837136f2a3841d20c588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:10:07 +0100 Subject: [PATCH 0772/1179] target/tricore: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-23-philmd@linaro.org> --- target/tricore/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/tricore/cpu.c b/target/tricore/cpu.c index eb794674c8da..16acc4ecb92e 100644 --- a/target/tricore/cpu.c +++ b/target/tricore/cpu.c @@ -165,6 +165,7 @@ static bool tricore_cpu_exec_interrupt(CPUState *cs, int interrupt_request) #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps tricore_sysemu_ops = { + .has_work = tricore_cpu_has_work, .get_phys_page_debug = tricore_cpu_get_phys_page_debug, }; @@ -193,7 +194,6 @@ static void tricore_cpu_class_init(ObjectClass *c, void *data) resettable_class_set_parent_phases(rc, NULL, tricore_cpu_reset_hold, NULL, &mcc->parent_phases); cc->class_by_name = tricore_cpu_class_by_name; - cc->has_work = tricore_cpu_has_work; cc->mmu_index = tricore_cpu_mmu_index; cc->gdb_read_register = tricore_cpu_gdb_read_register; From f37799c6c1d2e8fb43f5e1f100f26c5401d9b3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 21 Jan 2025 13:10:30 +0100 Subject: [PATCH 0773/1179] target/xtensa: Move has_work() from CPUClass to SysemuCPUOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move has_work() from CPUClass to SysemuCPUOps, simplifying xtensa_cpu_has_work() by directly using CPU env. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250125170125.32855-24-philmd@linaro.org> --- target/xtensa/cpu.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/target/xtensa/cpu.c b/target/xtensa/cpu.c index f9e298ace458..7663b62d01eb 100644 --- a/target/xtensa/cpu.c +++ b/target/xtensa/cpu.c @@ -63,16 +63,14 @@ static void xtensa_restore_state_to_opc(CPUState *cs, cpu->env.pc = data[0]; } +#ifndef CONFIG_USER_ONLY static bool xtensa_cpu_has_work(CPUState *cs) { -#ifndef CONFIG_USER_ONLY - XtensaCPU *cpu = XTENSA_CPU(cs); + CPUXtensaState *env = cpu_env(cs); - return !cpu->env.runstall && cpu->env.pending_irq_level; -#else - return true; -#endif + return !env->runstall && env->pending_irq_level; } +#endif /* !CONFIG_USER_ONLY */ static int xtensa_cpu_mmu_index(CPUState *cs, bool ifetch) { @@ -226,6 +224,7 @@ static const VMStateDescription vmstate_xtensa_cpu = { #include "hw/core/sysemu-cpu-ops.h" static const struct SysemuCPUOps xtensa_sysemu_ops = { + .has_work = xtensa_cpu_has_work, .get_phys_page_debug = xtensa_cpu_get_phys_page_debug, }; #endif @@ -263,7 +262,6 @@ static void xtensa_cpu_class_init(ObjectClass *oc, void *data) &xcc->parent_phases); cc->class_by_name = xtensa_cpu_class_by_name; - cc->has_work = xtensa_cpu_has_work; cc->mmu_index = xtensa_cpu_mmu_index; cc->dump_state = xtensa_cpu_dump_state; cc->set_pc = xtensa_cpu_set_pc; From d0a4ccae953b7482a682b9b9f8619804059ecc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 26 Jan 2025 08:17:59 +0100 Subject: [PATCH 0774/1179] cpus: Remove CPUClass::has_work() handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All handlers have been converted to SysemuCPUOps::has_work(). Remove CPUClass::has_work along with cpu_common_has_work() and simplify cpu_has_work(), making SysemuCPUOps::has_work handler mandatory. Note, since cpu-common.c is in meson's common_ss[] source set, we must define cpu_exec_class_post_init() in cpu-target.c (which is in the specific_ss[] source set) to have CONFIG_USER_ONLY defined. Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20250125170125.32855-25-philmd@linaro.org> --- hw/core/cpu-common.c | 8 ++------ hw/core/cpu-system.c | 13 +++++++------ hw/core/cpu-user.c | 5 +++++ include/hw/core/cpu.h | 3 +-- include/hw/core/sysemu-cpu-ops.h | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index ba0f02e49da7..9064dd24f827 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -134,11 +134,6 @@ static void cpu_common_reset_hold(Object *obj, ResetType type) cpu_exec_reset_hold(cpu); } -static bool cpu_common_has_work(CPUState *cs) -{ - return false; -} - ObjectClass *cpu_class_by_name(const char *typename, const char *cpu_model) { ObjectClass *oc; @@ -259,6 +254,8 @@ static void cpu_common_initfn(Object *obj) { CPUState *cpu = CPU(obj); + cpu_exec_class_post_init(CPU_GET_CLASS(obj)); + /* cache the cpu class for the hotpath */ cpu->cc = CPU_GET_CLASS(cpu); @@ -331,7 +328,6 @@ static void cpu_common_class_init(ObjectClass *klass, void *data) k->parse_features = cpu_common_parse_features; k->get_arch_id = cpu_common_get_arch_id; - k->has_work = cpu_common_has_work; k->gdb_read_register = cpu_common_gdb_read_register; k->gdb_write_register = cpu_common_gdb_write_register; set_bit(DEVICE_CATEGORY_CPU, dc->categories); diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index 601335fd764b..aed5076ec78b 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -33,12 +33,7 @@ bool cpu_has_work(CPUState *cpu) { - if (cpu->cc->sysemu_ops->has_work) { - return cpu->cc->sysemu_ops->has_work(cpu); - } - - g_assert(cpu->cc->has_work); - return cpu->cc->has_work(cpu); + return cpu->cc->sysemu_ops->has_work(cpu); } bool cpu_paging_enabled(const CPUState *cpu) @@ -188,6 +183,12 @@ void cpu_class_init_props(DeviceClass *dc) device_class_set_props(dc, cpu_system_props); } +void cpu_exec_class_post_init(CPUClass *cc) +{ + /* Check mandatory SysemuCPUOps handlers */ + g_assert(cc->sysemu_ops->has_work); +} + void cpu_exec_initfn(CPUState *cpu) { cpu->memory = get_system_memory(); diff --git a/hw/core/cpu-user.c b/hw/core/cpu-user.c index 1892acdee0f5..7176791851b4 100644 --- a/hw/core/cpu-user.c +++ b/hw/core/cpu-user.c @@ -27,6 +27,11 @@ void cpu_class_init_props(DeviceClass *dc) device_class_set_props(dc, cpu_user_props); } +void cpu_exec_class_post_init(CPUClass *cc) +{ + /* nothing to do */ +} + void cpu_exec_initfn(CPUState *cpu) { /* nothing to do */ diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index a54dd2cf699f..5d11d26556a5 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -104,7 +104,6 @@ struct SysemuCPUOps; * instantiatable CPU type. * @parse_features: Callback to parse command line arguments. * @reset_dump_flags: #CPUDumpFlags to use for reset logging. - * @has_work: Callback for checking if there is work to do. * @mmu_index: Callback for choosing softmmu mmu index; * may be used internally by memory_rw_debug without TCG. * @memory_rw_debug: Callback for GDB memory access. @@ -153,7 +152,6 @@ struct CPUClass { ObjectClass *(*class_by_name)(const char *cpu_model); void (*parse_features)(const char *typename, char *str, Error **errp); - bool (*has_work)(CPUState *cpu); int (*mmu_index)(CPUState *cpu, bool ifetch); int (*memory_rw_debug)(CPUState *cpu, vaddr addr, uint8_t *buf, int len, bool is_write); @@ -1156,6 +1154,7 @@ G_NORETURN void cpu_abort(CPUState *cpu, const char *fmt, ...) /* $(top_srcdir)/cpu.c */ void cpu_class_init_props(DeviceClass *dc); +void cpu_exec_class_post_init(CPUClass *cc); void cpu_exec_initfn(CPUState *cpu); void cpu_vmstate_register(CPUState *cpu); void cpu_vmstate_unregister(CPUState *cpu); diff --git a/include/hw/core/sysemu-cpu-ops.h b/include/hw/core/sysemu-cpu-ops.h index dee8a62ca989..877892373f95 100644 --- a/include/hw/core/sysemu-cpu-ops.h +++ b/include/hw/core/sysemu-cpu-ops.h @@ -19,7 +19,7 @@ typedef struct SysemuCPUOps { /** * @has_work: Callback for checking if there is work to do. */ - bool (*has_work)(CPUState *cpu); + bool (*has_work)(CPUState *cpu); /* MANDATORY NON-NULL */ /** * @get_memory_mapping: Callback for obtaining the memory mappings. */ From 8ff6ff09b9890ba390395d7510eca1025f7284df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 18 Feb 2025 09:18:21 +0100 Subject: [PATCH 0775/1179] MAINTAINERS: Consolidate core exec/vCPU handling section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some common cpu/exec files are listed under the 'TCG CPUs' section. Move them to the generic 'Overall Guest CPU Cores' one where they belong. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20250308134938.77267-1-philmd@linaro.org> --- MAINTAINERS | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 7ac04f352016..618d75f087ea 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -152,10 +152,7 @@ Overall TCG CPUs M: Richard Henderson R: Paolo Bonzini S: Maintained -F: system/cpus.c F: system/watchpoint.c -F: cpu-common.c -F: cpu-target.c F: page-vary-target.c F: page-vary-common.c F: accel/tcg/ @@ -165,15 +162,11 @@ F: util/cacheflush.c F: scripts/decodetree.py F: docs/devel/decodetree.rst F: docs/devel/tcg* -F: include/exec/cpu*.h -F: include/exec/exec-all.h F: include/exec/tb-flush.h -F: include/exec/target_long.h F: include/exec/helper*.h F: include/exec/helper*.h.inc F: include/exec/helper-info.c.inc F: include/exec/page-protection.h -F: include/system/cpus.h F: include/system/tcg.h F: include/accel/tcg/cpu-ops.h F: host/include/*/host/cpuinfo.h @@ -497,12 +490,19 @@ Overall M: Richard Henderson R: Paolo Bonzini S: Maintained +F: include/exec/cpu*.h +F: include/exec/exec-all.h +F: include/exec/target_long.h F: include/qemu/accel.h F: include/system/accel-*.h +F: include/system/cpus.h F: include/accel/accel-cpu-target.h F: accel/accel-*.c F: accel/Makefile.objs F: accel/stubs/Makefile.objs +F: cpu-common.c +F: cpu-target.c +F: system/cpus.c Apple Silicon HVF CPUs M: Alexander Graf From db0d4017f9b9e87f962b35dd19a4912bbfcd3cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Mon, 6 Jan 2025 10:57:34 -0500 Subject: [PATCH 0776/1179] net: parameterize the removing client from nc list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change is used in later commits so we can avoid the removal of the netclient if it is delayed. No functional change intended. Reviewed-by: Si-Wei Liu Acked-by: Jason Wang Signed-off-by: Eugenio Pérez Signed-off-by: Jason Wang --- net/net.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/net/net.c b/net/net.c index a3996d5c62cc..4eb78a129912 100644 --- a/net/net.c +++ b/net/net.c @@ -381,9 +381,12 @@ NetClientState *qemu_get_peer(NetClientState *nc, int queue_index) return ncs->peer; } -static void qemu_cleanup_net_client(NetClientState *nc) +static void qemu_cleanup_net_client(NetClientState *nc, + bool remove_from_net_clients) { - QTAILQ_REMOVE(&net_clients, nc, next); + if (remove_from_net_clients) { + QTAILQ_REMOVE(&net_clients, nc, next); + } if (nc->info->cleanup) { nc->info->cleanup(nc); @@ -442,14 +445,14 @@ void qemu_del_net_client(NetClientState *nc) } for (i = 0; i < queues; i++) { - qemu_cleanup_net_client(ncs[i]); + qemu_cleanup_net_client(ncs[i], true); } return; } for (i = 0; i < queues; i++) { - qemu_cleanup_net_client(ncs[i]); + qemu_cleanup_net_client(ncs[i], true); qemu_free_net_client(ncs[i]); } } @@ -474,7 +477,7 @@ void qemu_del_nic(NICState *nic) for (i = queues - 1; i >= 0; i--) { NetClientState *nc = qemu_get_subqueue(nic, i); - qemu_cleanup_net_client(nc); + qemu_cleanup_net_client(nc, true); qemu_free_net_client(nc); } From e7891c575fb294618b172119a91c892b8f4384a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Mon, 6 Jan 2025 10:57:35 -0500 Subject: [PATCH 0777/1179] net: move backend cleanup to NIC cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit a0d7215e33 ("vhost-vdpa: do not cleanup the vdpa/vhost-net structures if peer nic is present") effectively delayed the backend cleanup, allowing the frontend or the guest to access it resources as long as the frontend is still visible to the guest. However it does not clean up the resources until the qemu process is over. This causes an effective leak if the device is deleted with device_del, as there is no way to close the vdpa device. This makes impossible to re-add that device to this or other QEMU instances until the first instance of QEMU is finished. Move the cleanup from qemu_cleanup to the NIC deletion and to net_cleanup. Fixes: a0d7215e33 ("vhost-vdpa: do not cleanup the vdpa/vhost-net structures if peer nic is present") Reported-by: Lei Yang Signed-off-by: Eugenio Pérez Signed-off-by: Jonah Palmer Signed-off-by: Jason Wang --- net/net.c | 33 +++++++++++++++++++++++++++------ net/vhost-vdpa.c | 8 -------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/net/net.c b/net/net.c index 4eb78a129912..39d6f28158a3 100644 --- a/net/net.c +++ b/net/net.c @@ -428,7 +428,13 @@ void qemu_del_net_client(NetClientState *nc) object_unparent(OBJECT(nf)); } - /* If there is a peer NIC, delete and cleanup client, but do not free. */ + /* + * If there is a peer NIC, transfer ownership to it. Delete the client + * from net_client list but do not cleanup nor free. This way NIC can + * still access to members of the backend. + * + * The cleanup and free will be done when the NIC is free. + */ if (nc->peer && nc->peer->info->type == NET_CLIENT_DRIVER_NIC) { NICState *nic = qemu_get_nic(nc->peer); if (nic->peer_deleted) { @@ -438,16 +444,13 @@ void qemu_del_net_client(NetClientState *nc) for (i = 0; i < queues; i++) { ncs[i]->peer->link_down = true; + QTAILQ_REMOVE(&net_clients, ncs[i], next); } if (nc->peer->info->link_status_changed) { nc->peer->info->link_status_changed(nc->peer); } - for (i = 0; i < queues; i++) { - qemu_cleanup_net_client(ncs[i], true); - } - return; } @@ -465,8 +468,12 @@ void qemu_del_nic(NICState *nic) for (i = 0; i < queues; i++) { NetClientState *nc = qemu_get_subqueue(nic, i); - /* If this is a peer NIC and peer has already been deleted, free it now. */ + /* + * If this is a peer NIC and peer has already been deleted, clean it up + * and free it now. + */ if (nic->peer_deleted) { + qemu_cleanup_net_client(nc->peer, false); qemu_free_net_client(nc->peer); } else if (nc->peer) { /* if there are RX packets pending, complete them */ @@ -1681,6 +1688,9 @@ void net_cleanup(void) * of the latest NET_CLIENT_DRIVER_NIC, and operate on *p as we walk * the list. * + * However, the NIC may have peers that trust to be clean beyond this + * point. For example, if they have been removed with device_del. + * * The 'nc' variable isn't part of the list traversal; it's purely * for convenience as too much '(*p)->' has a tendency to make the * readers' eyes bleed. @@ -1688,6 +1698,17 @@ void net_cleanup(void) while (*p) { nc = *p; if (nc->info->type == NET_CLIENT_DRIVER_NIC) { + NICState *nic = qemu_get_nic(nc); + + if (nic->peer_deleted) { + int queues = MAX(nic->conf->peers.queues, 1); + + for (int i = 0; i < queues; i++) { + nc = qemu_get_subqueue(nic, i); + qemu_cleanup_net_client(nc->peer, false); + } + } + /* Skip NET_CLIENT_DRIVER_NIC entries */ p = &QTAILQ_NEXT(nc, next); } else { diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index bd0186687878..f7a54f46aa72 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -224,14 +224,6 @@ static void vhost_vdpa_cleanup(NetClientState *nc) { VhostVDPAState *s = DO_UPCAST(VhostVDPAState, nc, nc); - /* - * If a peer NIC is attached, do not cleanup anything. - * Cleanup will happen as a part of qemu_cleanup() -> net_cleanup() - * when the guest is shutting down. - */ - if (nc->peer && nc->peer->info->type == NET_CLIENT_DRIVER_NIC) { - return; - } munmap(s->cvq_cmd_out_buffer, vhost_vdpa_net_cvq_cmd_page_len()); munmap(s->status, vhost_vdpa_net_cvq_cmd_page_len()); if (s->vhost_net) { From 9dc64bd5a4bebdc820e7e8484cb30e02befdc774 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 28 Apr 2024 20:11:22 +0900 Subject: [PATCH 0778/1179] util/iov: Do not assert offset is in iov iov_from_buf(), iov_to_buf(), iov_memset(), and iov_copy() asserts that the given offset fits in the iov while tolerating the specified number of bytes to operate with to be greater than the size of iov. This is inconsistent so remove the assertions. Asserting the offset fits in the iov makes sense if it is expected that there are other operations that process the content before the offset and the content is processed in order. Under this expectation, the offset should point to the end of bytes that are previously processed and fit in the iov. However, this expectation depends on the details of the caller, and did not hold true at least one case and required code to check iov_size(), which is added with commit 83ddb3dbba2e ("hw/net/net_tx_pkt: Fix overrun in update_sctp_checksum()"). Adding such a check is inefficient and error-prone. These functions already tolerate the specified number of bytes to operate with to be greater than the size of iov to avoid such checks so remove the assertions to tolerate invalid offset as well. They return the number of bytes they operated with so their callers can still check the returned value to ensure there are sufficient space at the given offset. Signed-off-by: Akihiko Odaki Signed-off-by: Jason Wang --- include/qemu/iov.h | 5 +++-- util/iov.c | 5 ----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/include/qemu/iov.h b/include/qemu/iov.h index 44f9db5cee97..9535673c13d2 100644 --- a/include/qemu/iov.h +++ b/include/qemu/iov.h @@ -31,7 +31,7 @@ size_t iov_size(const struct iovec *iov, const unsigned int iov_cnt); * only part of data will be copied, up to the end of the iovec. * Number of bytes actually copied will be returned, which is * min(bytes, iov_size(iov)-offset) - * `Offset' must point to the inside of iovec. + * Returns 0 when `offset' points to the outside of iovec. */ size_t iov_from_buf_full(const struct iovec *iov, unsigned int iov_cnt, size_t offset, const void *buf, size_t bytes); @@ -67,11 +67,12 @@ iov_to_buf(const struct iovec *iov, const unsigned int iov_cnt, /** * Set data bytes pointed out by iovec `iov' of size `iov_cnt' elements, * starting at byte offset `start', to value `fillc', repeating it - * `bytes' number of times. `Offset' must point to the inside of iovec. + * `bytes' number of times. * If `bytes' is large enough, only last bytes portion of iovec, * up to the end of it, will be filled with the specified value. * Function return actual number of bytes processed, which is * min(size, iov_size(iov) - offset). + * Returns 0 when `offset' points to the outside of iovec. */ size_t iov_memset(const struct iovec *iov, const unsigned int iov_cnt, size_t offset, int fillc, size_t bytes); diff --git a/util/iov.c b/util/iov.c index 7777116123d0..f8536f0474a6 100644 --- a/util/iov.c +++ b/util/iov.c @@ -37,7 +37,6 @@ size_t iov_from_buf_full(const struct iovec *iov, unsigned int iov_cnt, offset -= iov[i].iov_len; } } - assert(offset == 0); return done; } @@ -56,7 +55,6 @@ size_t iov_to_buf_full(const struct iovec *iov, const unsigned int iov_cnt, offset -= iov[i].iov_len; } } - assert(offset == 0); return done; } @@ -75,7 +73,6 @@ size_t iov_memset(const struct iovec *iov, const unsigned int iov_cnt, offset -= iov[i].iov_len; } } - assert(offset == 0); return done; } @@ -277,7 +274,6 @@ unsigned iov_copy(struct iovec *dst_iov, unsigned int dst_iov_cnt, bytes -= len; offset = 0; } - assert(offset == 0); return j; } @@ -348,7 +344,6 @@ size_t qemu_iovec_concat_iov(QEMUIOVector *dst, soffset -= src_iov[i].iov_len; } } - assert(soffset == 0); /* offset beyond end of src */ return done; } From 2938c36937559554a2408b008470c9a76cb9271a Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 28 Apr 2024 20:11:23 +0900 Subject: [PATCH 0779/1179] Revert "hw/net/net_tx_pkt: Fix overrun in update_sctp_checksum()" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 83ddb3dbba2ee0f1767442ae6ee665058aeb1093. The added check is no longer necessary due to a change of iov_from_buf(). Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Akihiko Odaki Signed-off-by: Jason Wang --- hw/net/net_tx_pkt.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hw/net/net_tx_pkt.c b/hw/net/net_tx_pkt.c index 1f79b82b77f4..903238dca24d 100644 --- a/hw/net/net_tx_pkt.c +++ b/hw/net/net_tx_pkt.c @@ -141,10 +141,6 @@ bool net_tx_pkt_update_sctp_checksum(struct NetTxPkt *pkt) uint32_t csum = 0; struct iovec *pl_start_frag = pkt->vec + NET_TX_PKT_PL_START_FRAG; - if (iov_size(pl_start_frag, pkt->payload_frags) < 8 + sizeof(csum)) { - return false; - } - if (iov_from_buf(pl_start_frag, pkt->payload_frags, 8, &csum, sizeof(csum)) < sizeof(csum)) { return false; } From ac2ff9b840ce82cc7d5fd9ce4fd3019a434d7dc9 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 8 Oct 2024 15:52:06 +0900 Subject: [PATCH 0780/1179] tap-linux: Open ipvtap and macvtap ipvtap and macvtap create a file for each interface unlike tuntap, which creates one file shared by all interfaces. Try to open a file dedicated to the interface first for ipvtap and macvtap. Signed-off-by: Akihiko Odaki Signed-off-by: Jason Wang --- net/tap-linux.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/net/tap-linux.c b/net/tap-linux.c index 1226d5fda2d9..22ec2f45d2b7 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -45,10 +45,21 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int len = sizeof(struct virtio_net_hdr); unsigned int features; - fd = RETRY_ON_EINTR(open(PATH_NET_TUN, O_RDWR)); + + ret = if_nametoindex(ifname); + if (ret) { + g_autofree char *file = g_strdup_printf("/dev/tap%d", ret); + fd = open(file, O_RDWR); + } else { + fd = -1; + } + if (fd < 0) { - error_setg_errno(errp, errno, "could not open %s", PATH_NET_TUN); - return -1; + fd = RETRY_ON_EINTR(open(PATH_NET_TUN, O_RDWR)); + if (fd < 0) { + error_setg_errno(errp, errno, "could not open %s", PATH_NET_TUN); + return -1; + } } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; From 9a72282560fd478e69326617cbe891461375c93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:08 +0000 Subject: [PATCH 0781/1179] tests/functional: move aarch64 GPU test into own file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I want to expand the number of tests to cover a wide range of configurations. That starts with splitting off from the normal virt test from which it doesn't really share much code. We can also reduce the timeout of the original virt test now it is now longer burdened with testing the GPU. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-2-alex.bennee@linaro.org> --- MAINTAINERS | 1 + tests/functional/meson.build | 4 +- tests/functional/test_aarch64_virt.py | 71 ----------------- tests/functional/test_aarch64_virt_gpu.py | 94 +++++++++++++++++++++++ 4 files changed, 98 insertions(+), 72 deletions(-) create mode 100755 tests/functional/test_aarch64_virt_gpu.py diff --git a/MAINTAINERS b/MAINTAINERS index 618d75f087ea..758cc461beac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2631,6 +2631,7 @@ F: hw/display/virtio-gpu* F: hw/display/virtio-vga.* F: include/hw/virtio/virtio-gpu.h F: docs/system/devices/virtio-gpu.rst +F: tests/functional/test_aarch64_virt_gpu.py vhost-user-blk M: Raphael Norwitz diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 5dc66c03fcc3..7fcc4731d5ba 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -18,7 +18,8 @@ test_timeouts = { 'aarch64_sbsaref_alpine' : 1200, 'aarch64_sbsaref_freebsd' : 720, 'aarch64_tuxrun' : 240, - 'aarch64_virt' : 720, + 'aarch64_virt' : 360, + 'aarch64_virt_gpu' : 480, 'acpi_bits' : 420, 'arm_aspeed_palmetto' : 120, 'arm_aspeed_romulus' : 120, @@ -84,6 +85,7 @@ tests_aarch64_system_thorough = [ 'aarch64_tcg_plugins', 'aarch64_tuxrun', 'aarch64_virt', + 'aarch64_virt_gpu', 'aarch64_xen', 'aarch64_xlnx_versal', 'multiprocess', diff --git a/tests/functional/test_aarch64_virt.py b/tests/functional/test_aarch64_virt.py index 95f5ce8b4c02..884aad7af61f 100755 --- a/tests/functional/test_aarch64_virt.py +++ b/tests/functional/test_aarch64_virt.py @@ -134,77 +134,6 @@ def test_aarch64_virt_gicv2(self): self.common_aarch64_virt("virt,gic-version=2") - ASSET_VIRT_GPU_KERNEL = Asset( - 'https://fileserver.linaro.org/s/ce5jXBFinPxtEdx/' - 'download?path=%2F&files=' - 'Image', - '89e5099d26166204cc5ca4bb6d1a11b92c217e1f82ec67e3ba363d09157462f6') - - ASSET_VIRT_GPU_ROOTFS = Asset( - 'https://fileserver.linaro.org/s/ce5jXBFinPxtEdx/' - 'download?path=%2F&files=' - 'rootfs.ext4.zstd', - '792da7573f5dc2913ddb7c638151d4a6b2d028a4cb2afb38add513c1924bdad4') - - @skipIfMissingCommands('zstd') - def test_aarch64_virt_with_gpu(self): - # This tests boots with a buildroot test image that contains - # vkmark and other GPU exercising tools. We run a headless - # weston that nevertheless still exercises the virtio-gpu - # backend. - - self.set_machine('virt') - self.require_accelerator("tcg") - - kernel_path = self.ASSET_VIRT_GPU_KERNEL.fetch() - image_path = self.uncompress(self.ASSET_VIRT_GPU_ROOTFS, format="zstd") - - self.vm.set_console() - kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + - 'console=ttyAMA0 root=/dev/vda') - - self.vm.add_args("-accel", "tcg") - self.vm.add_args("-cpu", "neoverse-v1,pauth-impdef=on") - self.vm.add_args("-machine", "virt,gic-version=max", - '-kernel', kernel_path, - '-append', kernel_command_line) - self.vm.add_args("-smp", "2", "-m", "2048") - self.vm.add_args("-device", - "virtio-gpu-gl-pci,hostmem=4G,blob=on,venus=on") - self.vm.add_args("-display", "egl-headless") - self.vm.add_args("-display", "dbus,gl=on") - self.vm.add_args("-device", "virtio-blk-device,drive=hd0") - self.vm.add_args("-blockdev", - "driver=raw,file.driver=file," - "node-name=hd0,read-only=on," - f"file.filename={image_path}") - self.vm.add_args("-snapshot") - - try: - self.vm.launch() - except VMLaunchFailure as excp: - if "old virglrenderer, blob resources unsupported" in excp.output: - self.skipTest("No blob support for virtio-gpu") - elif "old virglrenderer, venus unsupported" in excp.output: - self.skipTest("No venus support for virtio-gpu") - elif "egl: no drm render node available" in excp.output: - self.skipTest("Can't access host DRM render node") - elif "'type' does not accept value 'egl-headless'" in excp.output: - self.skipTest("egl-headless support is not available") - else: - self.log.info(f"unhandled launch failure: {excp.output}") - raise excp - - self.wait_for_console_pattern('buildroot login:') - exec_command(self, 'root') - exec_command(self, 'export XDG_RUNTIME_DIR=/tmp') - exec_command_and_wait_for_pattern(self, - "weston -B headless " - "--renderer gl " - "--shell kiosk " - "-- vkmark -b:duration=1.0", - "vkmark Score") - if __name__ == '__main__': QemuSystemTest.main() diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py new file mode 100755 index 000000000000..32af941cd559 --- /dev/null +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# +# Functional tests for the various graphics modes we can support. +# +# Copyright (c) 2024, 2025 Linaro Ltd. +# +# Author: +# Alex Bennée +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu.machine.machine import VMLaunchFailure + +from qemu_test import Asset +from qemu_test import exec_command, exec_command_and_wait_for_pattern +from qemu_test import skipIfMissingCommands + +from qemu_test.linuxkernel import LinuxKernelTest + +class Aarch64VirtGPUMachine(LinuxKernelTest): + + ASSET_VIRT_GPU_KERNEL = Asset( + 'https://fileserver.linaro.org/s/ce5jXBFinPxtEdx/' + 'download?path=%2F&files=' + 'Image', + '89e5099d26166204cc5ca4bb6d1a11b92c217e1f82ec67e3ba363d09157462f6') + + ASSET_VIRT_GPU_ROOTFS = Asset( + 'https://fileserver.linaro.org/s/ce5jXBFinPxtEdx/' + 'download?path=%2F&files=' + 'rootfs.ext4.zstd', + '792da7573f5dc2913ddb7c638151d4a6b2d028a4cb2afb38add513c1924bdad4') + + @skipIfMissingCommands('zstd') + def test_aarch64_virt_with_vulkan_gpu(self): + # This tests boots with a buildroot test image that contains + # vkmark and other GPU exercising tools. We run a headless + # weston that nevertheless still exercises the virtio-gpu + # backend. + + self.set_machine('virt') + self.require_accelerator("tcg") + + kernel_path = self.ASSET_VIRT_GPU_KERNEL.fetch() + image_path = self.uncompress(self.ASSET_VIRT_GPU_ROOTFS, format="zstd") + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0 root=/dev/vda') + + self.vm.add_args("-accel", "tcg") + self.vm.add_args("-cpu", "neoverse-v1,pauth-impdef=on") + self.vm.add_args("-machine", "virt,gic-version=max", + '-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.add_args("-smp", "2", "-m", "2048") + self.vm.add_args("-device", + "virtio-gpu-gl-pci,hostmem=4G,blob=on,venus=on") + self.vm.add_args("-display", "egl-headless") + self.vm.add_args("-display", "dbus,gl=on") + self.vm.add_args("-device", "virtio-blk-device,drive=hd0") + self.vm.add_args("-blockdev", + "driver=raw,file.driver=file," + "node-name=hd0,read-only=on," + f"file.filename={image_path}") + self.vm.add_args("-snapshot") + + try: + self.vm.launch() + except VMLaunchFailure as excp: + if "old virglrenderer, blob resources unsupported" in excp.output: + self.skipTest("No blob support for virtio-gpu") + elif "old virglrenderer, venus unsupported" in excp.output: + self.skipTest("No venus support for virtio-gpu") + elif "egl: no drm render node available" in excp.output: + self.skipTest("Can't access host DRM render node") + elif "'type' does not accept value 'egl-headless'" in excp.output: + self.skipTest("egl-headless support is not available") + else: + self.log.info(f"unhandled launch failure: {excp.output}") + raise excp + + self.wait_for_console_pattern('buildroot login:') + exec_command(self, 'root') + exec_command(self, 'export XDG_RUNTIME_DIR=/tmp') + exec_command_and_wait_for_pattern(self, + "weston -B headless " + "--renderer gl " + "--shell kiosk " + "-- vkmark -b:duration=1.0", + "vkmark Score") + +if __name__ == '__main__': + LinuxKernelTest.main() From 868c7703e052a14f9c612c38f7171581cb626f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:09 +0000 Subject: [PATCH 0782/1179] tests/functional: factor out common code in gpu test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for handling more tests split out the common machine setup details from the test specific stuff and add a helper for launching the weston test. Instead of searching for "vkmark score" we set a custom PS1 and wait for a successful completion. This ensures we capture the score in the console log which otherwise wouldn't log anything. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-3-alex.bennee@linaro.org> --- tests/functional/test_aarch64_virt_gpu.py | 38 +++++++++++++---------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py index 32af941cd559..b4679c046051 100755 --- a/tests/functional/test_aarch64_virt_gpu.py +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -12,7 +12,7 @@ from qemu.machine.machine import VMLaunchFailure from qemu_test import Asset -from qemu_test import exec_command, exec_command_and_wait_for_pattern +from qemu_test import exec_command_and_wait_for_pattern as ec_and_wait from qemu_test import skipIfMissingCommands from qemu_test.linuxkernel import LinuxKernelTest @@ -31,12 +31,7 @@ class Aarch64VirtGPUMachine(LinuxKernelTest): 'rootfs.ext4.zstd', '792da7573f5dc2913ddb7c638151d4a6b2d028a4cb2afb38add513c1924bdad4') - @skipIfMissingCommands('zstd') - def test_aarch64_virt_with_vulkan_gpu(self): - # This tests boots with a buildroot test image that contains - # vkmark and other GPU exercising tools. We run a headless - # weston that nevertheless still exercises the virtio-gpu - # backend. + def _launch_virt_gpu(self, gpu_device): self.set_machine('virt') self.require_accelerator("tcg") @@ -54,10 +49,10 @@ def test_aarch64_virt_with_vulkan_gpu(self): '-kernel', kernel_path, '-append', kernel_command_line) self.vm.add_args("-smp", "2", "-m", "2048") - self.vm.add_args("-device", - "virtio-gpu-gl-pci,hostmem=4G,blob=on,venus=on") + self.vm.add_args("-device", gpu_device) self.vm.add_args("-display", "egl-headless") self.vm.add_args("-display", "dbus,gl=on") + self.vm.add_args("-device", "virtio-blk-device,drive=hd0") self.vm.add_args("-blockdev", "driver=raw,file.driver=file," @@ -81,14 +76,23 @@ def test_aarch64_virt_with_vulkan_gpu(self): raise excp self.wait_for_console_pattern('buildroot login:') - exec_command(self, 'root') - exec_command(self, 'export XDG_RUNTIME_DIR=/tmp') - exec_command_and_wait_for_pattern(self, - "weston -B headless " - "--renderer gl " - "--shell kiosk " - "-- vkmark -b:duration=1.0", - "vkmark Score") + ec_and_wait(self, 'root', '#') + + def _run_virt_weston_test(self, cmd): + + # make it easier to detect successful return to shell + PS1 = 'RES=[$?] # ' + OK_CMD = 'RES=[0] # ' + + ec_and_wait(self, 'export XDG_RUNTIME_DIR=/tmp', '#') + ec_and_wait(self, f"export PS1='{PS1}'", OK_CMD) + full_cmd = f"weston -B headless --renderer gl --shell kiosk -- {cmd}" + ec_and_wait(self, full_cmd, OK_CMD) + + @skipIfMissingCommands('zstd') + def test_aarch64_virt_with_vulkan_gpu(self): + self._launch_virt_gpu("virtio-gpu-gl-pci,hostmem=4G,blob=on,venus=on") + self._run_virt_weston_test("vkmark -b:duration=1.0") if __name__ == '__main__': LinuxKernelTest.main() From 94634fbc66869b31491e95c26eab731d67c13e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:10 +0000 Subject: [PATCH 0783/1179] tests/functional: ensure we have a GPU device for tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's possible to build QEMU without support for the GL enabled GPU devices and we can catch that earlier with an explicit check. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-4-alex.bennee@linaro.org> --- tests/functional/test_aarch64_virt_gpu.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py index b4679c046051..9a1ee2befc95 100755 --- a/tests/functional/test_aarch64_virt_gpu.py +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -91,6 +91,9 @@ def _run_virt_weston_test(self, cmd): @skipIfMissingCommands('zstd') def test_aarch64_virt_with_vulkan_gpu(self): + + self.require_device('virtio-gpu-gl-pci') + self._launch_virt_gpu("virtio-gpu-gl-pci,hostmem=4G,blob=on,venus=on") self._run_virt_weston_test("vkmark -b:duration=1.0") From 4e4e6986b62d0bb29282e4aeb09ca5b959b0afe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:11 +0000 Subject: [PATCH 0784/1179] tests/functional: bail early if vkmark hangs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The message: MESA-VIRTIO: debug: stuck in fence wait with iter at %d Seems to occur more often on debug builds. Rather than waiting for our long timeout to hit we might as well bail as soon as we see the message. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-5-alex.bennee@linaro.org> --- tests/functional/test_aarch64_virt_gpu.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py index 9a1ee2befc95..eea1e8c9739b 100755 --- a/tests/functional/test_aarch64_virt_gpu.py +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -78,7 +78,7 @@ def _launch_virt_gpu(self, gpu_device): self.wait_for_console_pattern('buildroot login:') ec_and_wait(self, 'root', '#') - def _run_virt_weston_test(self, cmd): + def _run_virt_weston_test(self, cmd, fail = None): # make it easier to detect successful return to shell PS1 = 'RES=[$?] # ' @@ -87,7 +87,7 @@ def _run_virt_weston_test(self, cmd): ec_and_wait(self, 'export XDG_RUNTIME_DIR=/tmp', '#') ec_and_wait(self, f"export PS1='{PS1}'", OK_CMD) full_cmd = f"weston -B headless --renderer gl --shell kiosk -- {cmd}" - ec_and_wait(self, full_cmd, OK_CMD) + ec_and_wait(self, full_cmd, OK_CMD, fail) @skipIfMissingCommands('zstd') def test_aarch64_virt_with_vulkan_gpu(self): @@ -95,7 +95,9 @@ def test_aarch64_virt_with_vulkan_gpu(self): self.require_device('virtio-gpu-gl-pci') self._launch_virt_gpu("virtio-gpu-gl-pci,hostmem=4G,blob=on,venus=on") - self._run_virt_weston_test("vkmark -b:duration=1.0") + self._run_virt_weston_test("vkmark -b:duration=1.0", + "debug: stuck in fence wait with iter at") + if __name__ == '__main__': LinuxKernelTest.main() From 9f7e493d117c852be4af529c1670c293eab063b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:12 +0000 Subject: [PATCH 0785/1179] tests/functional: skip vulkan tests with nVidia MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While running the new GPU tests it was noted that the proprietary nVidia driver barfed when run under the sanitiser: 2025-02-20 11:13:08,226: [11:13:07.782] Output 'headless' attempts EOTF mode SDR and colorimetry mode default. 2025-02-20 11:13:08,227: [11:13:07.784] Output 'headless' using color profile: stock sRGB color profile and that's the last thing it outputs. The sanitizer reports that when the framework sends the SIGTERM because of the timeout we get a write to a NULL pointer (but interesting not this time in an atexit callback): UndefinedBehaviorSanitizer:DEADLYSIGNAL ==471863==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7a18ceaafe80 bp 0x000000000000 sp 0x7ffe8e3ff6d0 T471863) ==471863==The signal is caused by a WRITE memory access. ==471863==Hint: address points to the zero page. #0 0x7a18ceaafe80 (/lib/x86_64-linux-gnu/libnvidia-eglcore.so.535.183.01+0x16afe80) (BuildId: 24b0d0b90369112e3de888a93eb8d7e00304a6db) #1 0x7a18ce9e72c0 (/lib/x86_64-linux-gnu/libnvidia-eglcore.so.535.183.01+0x15e72c0) (BuildId: 24b0d0b90369112e3de888a93eb8d7e00304a6db) #2 0x7a18ce9f11bb (/lib/x86_64-linux-gnu/libnvidia-eglcore.so.535.183.01+0x15f11bb) (BuildId: 24b0d0b90369112e3de888a93eb8d7e00304a6db) #3 0x7a18ce6dc9d1 (/lib/x86_64-linux-gnu/libnvidia-eglcore.so.535.183.01+0x12dc9d1) (BuildId: 24b0d0b90369112e3de888a93eb8d7e00304a6db) #4 0x7a18e7d15326 in vrend_renderer_create_fence /usr/src/virglrenderer-1.0.0-1ubuntu2/obj-x86_64-linux-gnu/../src/vrend_renderer.c:10883:26 #5 0x55bfb6621871 in virtio_gpu_virgl_process_cmd The #dri-devel channel confirmed: stsquad: nv driver is known to not work with venus, don't use it for testing So lets skip running the test to avoid known failures. As we now use vulkaninfo to probe we also need to handle the case where there is no Vulkan driver configured for the hardware. Reviewed-by: Thomas Huth Reported-by: Peter Maydell Cc: Dmitry Osipenko [AJB: also skip if vulkaninfo can't find environment] Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-6-alex.bennee@linaro.org> --- tests/functional/test_aarch64_virt_gpu.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py index eea1e8c9739b..8e6f08154440 100755 --- a/tests/functional/test_aarch64_virt_gpu.py +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -17,6 +17,9 @@ from qemu_test.linuxkernel import LinuxKernelTest +from re import search +from subprocess import check_output, CalledProcessError + class Aarch64VirtGPUMachine(LinuxKernelTest): ASSET_VIRT_GPU_KERNEL = Asset( @@ -72,7 +75,7 @@ def _launch_virt_gpu(self, gpu_device): elif "'type' does not accept value 'egl-headless'" in excp.output: self.skipTest("egl-headless support is not available") else: - self.log.info(f"unhandled launch failure: {excp.output}") + self.log.info("unhandled launch failure: %s", excp.output) raise excp self.wait_for_console_pattern('buildroot login:') @@ -94,6 +97,15 @@ def test_aarch64_virt_with_vulkan_gpu(self): self.require_device('virtio-gpu-gl-pci') + try: + vk_info = check_output(["vulkaninfo", "--summary"], + encoding="utf-8") + except CalledProcessError as excp: + self.skipTest(f"Miss-configured host Vulkan: {excp.output}") + + if search(r"driverID\s+=\s+DRIVER_ID_NVIDIA_PROPRIETARY", vk_info): + self.skipTest("Test skipped on NVIDIA proprietary driver") + self._launch_virt_gpu("virtio-gpu-gl-pci,hostmem=4G,blob=on,venus=on") self._run_virt_weston_test("vkmark -b:duration=1.0", "debug: stuck in fence wait with iter at") From 8233f4f26d831c72970d00164f8f58a63c73ecfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:13 +0000 Subject: [PATCH 0786/1179] tests/functional: expand tests to cover virgl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two more test modes using glmark2-wayland to exercise the OpenGL pass-through modes with virgl. Virgl can run with or without the hostmem blob support. To avoid repeating ourselves too much we make the initial pass a simple --validate pass. We might want to eventually add more directed tests and individual features later on but the glmark/vkmark tests are a good general smoke test for accelerated 3D. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-7-alex.bennee@linaro.org> --- tests/functional/test_aarch64_virt_gpu.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py index 8e6f08154440..56a3ed319305 100755 --- a/tests/functional/test_aarch64_virt_gpu.py +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -92,6 +92,28 @@ def _run_virt_weston_test(self, cmd, fail = None): full_cmd = f"weston -B headless --renderer gl --shell kiosk -- {cmd}" ec_and_wait(self, full_cmd, OK_CMD, fail) + @skipIfMissingCommands('zstd') + def test_aarch64_virt_with_virgl_gpu(self): + + self.require_device('virtio-gpu-gl-pci') + + self._launch_virt_gpu("virtio-gpu-gl-pci") + + # subset of the glmark tests + tests = " ".join([f"-b {test}" for test in + ["build", "texture", "shading", + "bump", "desktop", "buffer"]]) + + self._run_virt_weston_test("glmark2-wayland --validate " + tests) + + @skipIfMissingCommands('zstd') + def test_aarch64_virt_with_virgl_blobs_gpu(self): + + self.require_device('virtio-gpu-gl-pci') + + self._launch_virt_gpu("virtio-gpu-gl-pci,hostmem=4G,blob=on") + self._run_virt_weston_test("glmark2-wayland -b:duration=1.0") + @skipIfMissingCommands('zstd') def test_aarch64_virt_with_vulkan_gpu(self): From d69178d3705994c1e4f7f919dfb6dd31b8b11d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:14 +0000 Subject: [PATCH 0787/1179] tests/functional: update the aarch64_virg_gpu images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update to the most recent aarch64_virt_gpu image. The principle differences are: - target a v8.0 baseline CPU - latest vkmark (2025.1) - actually uses the rootfs (previously was initrd) - rootfs includes more testing tools for interactive use See README.md in https://fileserver.linaro.org/s/ce5jXBFinPxtEdx for details about the image creation and the buildroot config. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-8-alex.bennee@linaro.org> --- tests/functional/test_aarch64_virt_gpu.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py index 56a3ed319305..f19a47f8b639 100755 --- a/tests/functional/test_aarch64_virt_gpu.py +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -25,14 +25,14 @@ class Aarch64VirtGPUMachine(LinuxKernelTest): ASSET_VIRT_GPU_KERNEL = Asset( 'https://fileserver.linaro.org/s/ce5jXBFinPxtEdx/' 'download?path=%2F&files=' - 'Image', - '89e5099d26166204cc5ca4bb6d1a11b92c217e1f82ec67e3ba363d09157462f6') + 'Image.6.12.16.aarch64', + '7888c51c55d37e86bbbdeb5acea9f08c34e6b0f03c1f5b2463285f6a6f6eec8b') ASSET_VIRT_GPU_ROOTFS = Asset( 'https://fileserver.linaro.org/s/ce5jXBFinPxtEdx/' 'download?path=%2F&files=' - 'rootfs.ext4.zstd', - '792da7573f5dc2913ddb7c638151d4a6b2d028a4cb2afb38add513c1924bdad4') + 'rootfs.aarch64.ext2.zstd', + 'd45118c899420b7e673f1539a37a35480134b3e36e3a59e2cb69b1781cbb14ef') def _launch_virt_gpu(self, gpu_device): @@ -47,7 +47,7 @@ def _launch_virt_gpu(self, gpu_device): 'console=ttyAMA0 root=/dev/vda') self.vm.add_args("-accel", "tcg") - self.vm.add_args("-cpu", "neoverse-v1,pauth-impdef=on") + self.vm.add_args("-cpu", "cortex-a72") self.vm.add_args("-machine", "virt,gic-version=max", '-kernel', kernel_path, '-append', kernel_command_line) From 892b06c40e46656b07579b9f4fb7a8f2652cacf2 Mon Sep 17 00:00:00 2001 From: Pierrick Bouvier Date: Tue, 4 Mar 2025 22:24:15 +0000 Subject: [PATCH 0788/1179] plugins: add explicit dependency in functional tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ./tests/functional/test_aarch64_tcg_plugins.py needs to have plugin libinsn built. However, it's not listed as a dependency, so meson can't know it needs to be built. Thus, we keep track of all plugins, and add them as an explicit dependency. Fixes: 4c134d07b9e ("tests: add a new set of tests to exercise plugins") Signed-off-by: Pierrick Bouvier Tested-by: Philippe Mathieu-Daudé Signed-off-by: Philippe Mathieu-Daudé Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-9-alex.bennee@linaro.org> --- contrib/plugins/meson.build | 2 ++ meson.build | 1 + tests/functional/meson.build | 2 +- tests/tcg/plugins/meson.build | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index 484b9a808c83..fa8a426c8b59 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -26,3 +26,5 @@ if t.length() > 0 else run_target('contrib-plugins', command: find_program('true')) endif + +plugin_modules += t diff --git a/meson.build b/meson.build index 4899d896de0b..9d9c11731f30 100644 --- a/meson.build +++ b/meson.build @@ -3668,6 +3668,7 @@ qtest_module_ss = ss.source_set() modules = {} target_modules = {} +plugin_modules = [] hw_arch = {} target_arch = {} target_system_arch = {} diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 7fcc4731d5ba..e78560a9011d 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -385,7 +385,7 @@ foreach speed : ['quick', 'thorough'] # 'run_target' logic below & in Makefile.include test('func-' + testname, python, - depends: [test_deps, test_emulator, emulator_modules], + depends: [test_deps, test_emulator, emulator_modules, plugin_modules], env: test_env, args: [testpath], protocol: 'tap', diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build index 87a17d67bd45..c8cb0626a6da 100644 --- a/tests/tcg/plugins/meson.build +++ b/tests/tcg/plugins/meson.build @@ -19,3 +19,5 @@ if t.length() > 0 else run_target('test-plugins', command: find_program('true')) endif + +plugin_modules += t From 3a7b9054b8d761e61628e67e90b118d05b4a4697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Mar 2025 22:24:16 +0000 Subject: [PATCH 0789/1179] tests/functional: Introduce the dso_suffix() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a helper to get the default shared library suffix used on the host. Suggested-by: Pierrick Bouvier Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Thomas Huth Message-Id: <20250220080215.49165-3-philmd@linaro.org> [AJB: dropped whitespace cmd.py damage] Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-10-alex.bennee@linaro.org> --- tests/functional/qemu_test/__init__.py | 2 +- tests/functional/qemu_test/config.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/functional/qemu_test/__init__.py b/tests/functional/qemu_test/__init__.py index 5c972843a6d0..45f7befa374a 100644 --- a/tests/functional/qemu_test/__init__.py +++ b/tests/functional/qemu_test/__init__.py @@ -7,7 +7,7 @@ from .asset import Asset -from .config import BUILD_DIR +from .config import BUILD_DIR, dso_suffix from .cmd import is_readable_executable_file, \ interrupt_interactive_console_until_pattern, wait_for_console_pattern, \ exec_command, exec_command_and_wait_for_pattern, get_qemu_img, which diff --git a/tests/functional/qemu_test/config.py b/tests/functional/qemu_test/config.py index edd75b7fd06b..6d4c9c3ce1db 100644 --- a/tests/functional/qemu_test/config.py +++ b/tests/functional/qemu_test/config.py @@ -13,6 +13,7 @@ import os from pathlib import Path +import platform def _source_dir(): @@ -34,3 +35,14 @@ def _build_dir(): raise Exception("Cannot identify build dir, set QEMU_BUILD_ROOT") BUILD_DIR = _build_dir() + +def dso_suffix(): + '''Return the dynamic libraries suffix for the current platform''' + + if platform.system() == "Darwin": + return "dylib" + + if platform.system() == "Windows": + return "dll" + + return "so" From ed557cc544b2162eec8ec977af65cdd09277d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Mar 2025 22:24:17 +0000 Subject: [PATCH 0790/1179] tests/functional: Allow running TCG plugins tests on non-Linux/BSD hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not all platforms use the '.so' suffix for shared libraries, which is how plugins are built. Use the recently introduced dso_suffix() helper to get the proper host suffix. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2804 Suggested-by: Pierrick Bouvier Suggested-by: Daniel P. Berrangé Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Thomas Huth Message-Id: <20250220080215.49165-4-philmd@linaro.org> [AJB: moved plugin_file into testcase.py] Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-11-alex.bennee@linaro.org> --- tests/functional/qemu_test/testcase.py | 12 +++++++++++- tests/functional/test_aarch64_tcg_plugins.py | 5 +++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index 058bf270ecaf..50d232a7c637 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -27,7 +27,7 @@ from .archive import archive_extract from .asset import Asset -from .config import BUILD_DIR +from .config import BUILD_DIR, dso_suffix from .uncompress import uncompress @@ -183,6 +183,16 @@ def scratch_file(self, *args): def log_file(self, *args): return str(Path(self.outputdir, *args)) + ''' + @params plugin name + + Return the full path to the plugin taking into account any host OS + specific suffixes. + ''' + def plugin_file(self, plugin_name): + sfx = dso_suffix() + return os.path.join('tests', 'tcg', 'plugins', f'{plugin_name}.{sfx}') + def assets_available(self): for name, asset in vars(self.__class__).items(): if name.startswith("ASSET_") and type(asset) == Asset: diff --git a/tests/functional/test_aarch64_tcg_plugins.py b/tests/functional/test_aarch64_tcg_plugins.py index 7e8beacc833c..4ea71f5f882e 100755 --- a/tests/functional/test_aarch64_tcg_plugins.py +++ b/tests/functional/test_aarch64_tcg_plugins.py @@ -13,6 +13,7 @@ import tempfile import mmap +import os import re from qemu.machine.machine import VMLaunchFailure @@ -74,7 +75,7 @@ def test_aarch64_virt_insn(self): suffix=".log") self.run_vm(kernel_path, kernel_command_line, - "tests/tcg/plugins/libinsn.so", plugin_log.name, + self.plugin_file('libinsn'), plugin_log.name, console_pattern) with plugin_log as lf, \ @@ -100,7 +101,7 @@ def test_aarch64_virt_insn_icount(self): suffix=".log") self.run_vm(kernel_path, kernel_command_line, - "tests/tcg/plugins/libinsn.so", plugin_log.name, + self.plugin_file('libinsn'), plugin_log.name, console_pattern, args=('-icount', 'shift=1')) From 4748be5e9df56e13045c0f76fe9f60fa7655fed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:18 +0000 Subject: [PATCH 0791/1179] libvirt-ci: bump to latest for vulkan-tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The alpine baseline has also been updated in the meantime so we need to address that while we are at it. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-12-alex.bennee@linaro.org> --- .gitlab-ci.d/cirrus/freebsd-14.vars | 2 +- .gitlab-ci.d/cirrus/macos-14.vars | 2 +- scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml | 1 + scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml | 1 + tests/docker/dockerfiles/alpine.docker | 5 +++-- tests/docker/dockerfiles/centos9.docker | 1 + tests/docker/dockerfiles/debian-amd64-cross.docker | 3 ++- tests/docker/dockerfiles/debian-arm64-cross.docker | 3 ++- tests/docker/dockerfiles/debian-armhf-cross.docker | 3 ++- tests/docker/dockerfiles/debian-i686-cross.docker | 3 ++- tests/docker/dockerfiles/debian-mips64el-cross.docker | 3 ++- tests/docker/dockerfiles/debian-mipsel-cross.docker | 3 ++- tests/docker/dockerfiles/debian-ppc64el-cross.docker | 3 ++- tests/docker/dockerfiles/debian-s390x-cross.docker | 3 ++- tests/docker/dockerfiles/debian.docker | 3 ++- tests/docker/dockerfiles/fedora-rust-nightly.docker | 1 + tests/docker/dockerfiles/fedora-win64-cross.docker | 1 + tests/docker/dockerfiles/fedora.docker | 1 + tests/docker/dockerfiles/opensuse-leap.docker | 1 + tests/docker/dockerfiles/ubuntu2204.docker | 1 + tests/lcitool/libvirt-ci | 2 +- tests/lcitool/projects/qemu.yml | 1 + tests/lcitool/refresh | 2 +- tests/vm/generated/freebsd.json | 1 + 24 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.d/cirrus/freebsd-14.vars b/.gitlab-ci.d/cirrus/freebsd-14.vars index 0997c47af586..19ca0d36638c 100644 --- a/.gitlab-ci.d/cirrus/freebsd-14.vars +++ b/.gitlab-ci.d/cirrus/freebsd-14.vars @@ -11,6 +11,6 @@ MAKE='/usr/local/bin/gmake' NINJA='/usr/local/bin/ninja' PACKAGING_COMMAND='pkg' PIP3='/usr/local/bin/pip' -PKGS='alsa-lib bash bison bzip2 ca_root_nss capstone4 ccache4 cmocka ctags curl cyrus-sasl dbus diffutils dtc flex fusefs-libs3 gettext git glib gmake gnutls gsed gtk-vnc gtk3 json-c libepoxy libffi libgcrypt libjpeg-turbo libnfs libslirp libspice-server libssh libtasn1 llvm lzo2 meson mtools ncurses nettle ninja opencv pixman pkgconf png py311-numpy py311-pillow py311-pip py311-pyyaml py311-sphinx py311-sphinx_rtd_theme py311-tomli python3 rpm2cpio rust rust-bindgen-cli sdl2 sdl2_image snappy sndio socat spice-protocol tesseract usbredir virglrenderer vte3 xorriso zstd' +PKGS='alsa-lib bash bison bzip2 ca_root_nss capstone4 ccache4 cmocka ctags curl cyrus-sasl dbus diffutils dtc flex fusefs-libs3 gettext git glib gmake gnutls gsed gtk-vnc gtk3 json-c libepoxy libffi libgcrypt libjpeg-turbo libnfs libslirp libspice-server libssh libtasn1 llvm lzo2 meson mtools ncurses nettle ninja opencv pixman pkgconf png py311-numpy py311-pillow py311-pip py311-pyyaml py311-sphinx py311-sphinx_rtd_theme py311-tomli python3 rpm2cpio rust rust-bindgen-cli sdl2 sdl2_image snappy sndio socat spice-protocol tesseract usbredir virglrenderer vte3 vulkan-tools xorriso zstd' PYPI_PKGS='' PYTHON='/usr/local/bin/python3' diff --git a/.gitlab-ci.d/cirrus/macos-14.vars b/.gitlab-ci.d/cirrus/macos-14.vars index 25dff322e6ac..b039465f56f2 100644 --- a/.gitlab-ci.d/cirrus/macos-14.vars +++ b/.gitlab-ci.d/cirrus/macos-14.vars @@ -11,6 +11,6 @@ MAKE='/opt/homebrew/bin/gmake' NINJA='/opt/homebrew/bin/ninja' PACKAGING_COMMAND='brew' PIP3='/opt/homebrew/bin/pip3' -PKGS='bash bc bindgen bison bzip2 capstone ccache cmocka ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libcbor libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python3 rpm2cpio rust sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 xorriso zlib zstd' +PKGS='bash bc bindgen bison bzip2 capstone ccache cmocka ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libcbor libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python3 rpm2cpio rust sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 vulkan-tools xorriso zlib zstd' PYPI_PKGS='PyYAML numpy pillow sphinx sphinx-rtd-theme tomli' PYTHON='/opt/homebrew/bin/python3' diff --git a/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml b/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml index 288156d1e4b6..dbcd2e076dec 100644 --- a/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml +++ b/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml @@ -123,6 +123,7 @@ packages: - tar - tesseract-ocr - tesseract-ocr-eng + - vulkan-tools - xorriso - zlib1g-dev - zstd diff --git a/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml b/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml index d497139ef39b..4b8ee3d885d5 100644 --- a/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml +++ b/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml @@ -121,6 +121,7 @@ packages: - tar - tesseract-ocr - tesseract-ocr-eng + - vulkan-tools - xorriso - zlib1g-dev - zstd diff --git a/tests/docker/dockerfiles/alpine.docker b/tests/docker/dockerfiles/alpine.docker index f87c40fbfe34..bf3bd5a30dd9 100644 --- a/tests/docker/dockerfiles/alpine.docker +++ b/tests/docker/dockerfiles/alpine.docker @@ -1,10 +1,10 @@ # THIS FILE WAS AUTO-GENERATED # -# $ lcitool dockerfile --layers all alpine-319 qemu +# $ lcitool dockerfile --layers all alpine-321 qemu # # https://gitlab.com/libvirt/libvirt-ci -FROM docker.io/library/alpine:3.19 +FROM docker.io/library/alpine:3.21 RUN apk update && \ apk upgrade && \ @@ -111,6 +111,7 @@ RUN apk update && \ vde2-dev \ virglrenderer-dev \ vte3-dev \ + vulkan-tools \ which \ xen-dev \ xorriso \ diff --git a/tests/docker/dockerfiles/centos9.docker b/tests/docker/dockerfiles/centos9.docker index a9681c8a96f4..a942835a1d28 100644 --- a/tests/docker/dockerfiles/centos9.docker +++ b/tests/docker/dockerfiles/centos9.docker @@ -115,6 +115,7 @@ RUN dnf distro-sync -y && \ usbredir-devel \ util-linux \ vte291-devel \ + vulkan-tools \ which \ xorriso \ zlib-devel \ diff --git a/tests/docker/dockerfiles/debian-amd64-cross.docker b/tests/docker/dockerfiles/debian-amd64-cross.docker index 644fd3734d5b..05355854285e 100644 --- a/tests/docker/dockerfiles/debian-amd64-cross.docker +++ b/tests/docker/dockerfiles/debian-amd64-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -135,7 +136,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev:amd64 \ libspice-protocol-dev:amd64 \ libspice-server-dev:amd64 \ - libssh-gcrypt-dev:amd64 \ + libssh-dev:amd64 \ libsystemd-dev:amd64 \ libtasn1-6-dev:amd64 \ libubsan1:amd64 \ diff --git a/tests/docker/dockerfiles/debian-arm64-cross.docker b/tests/docker/dockerfiles/debian-arm64-cross.docker index 060da53796e0..6b1e4fc82797 100644 --- a/tests/docker/dockerfiles/debian-arm64-cross.docker +++ b/tests/docker/dockerfiles/debian-arm64-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -134,7 +135,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev:arm64 \ libspice-protocol-dev:arm64 \ libspice-server-dev:arm64 \ - libssh-gcrypt-dev:arm64 \ + libssh-dev:arm64 \ libsystemd-dev:arm64 \ libtasn1-6-dev:arm64 \ libubsan1:arm64 \ diff --git a/tests/docker/dockerfiles/debian-armhf-cross.docker b/tests/docker/dockerfiles/debian-armhf-cross.docker index a481fc96959e..cf0fe63af91c 100644 --- a/tests/docker/dockerfiles/debian-armhf-cross.docker +++ b/tests/docker/dockerfiles/debian-armhf-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -134,7 +135,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev:armhf \ libspice-protocol-dev:armhf \ libspice-server-dev:armhf \ - libssh-gcrypt-dev:armhf \ + libssh-dev:armhf \ libsystemd-dev:armhf \ libtasn1-6-dev:armhf \ libubsan1:armhf \ diff --git a/tests/docker/dockerfiles/debian-i686-cross.docker b/tests/docker/dockerfiles/debian-i686-cross.docker index 61bc361e85a4..1c84dfb9456d 100644 --- a/tests/docker/dockerfiles/debian-i686-cross.docker +++ b/tests/docker/dockerfiles/debian-i686-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -134,7 +135,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev:i386 \ libspice-protocol-dev:i386 \ libspice-server-dev:i386 \ - libssh-gcrypt-dev:i386 \ + libssh-dev:i386 \ libsystemd-dev:i386 \ libtasn1-6-dev:i386 \ libubsan1:i386 \ diff --git a/tests/docker/dockerfiles/debian-mips64el-cross.docker b/tests/docker/dockerfiles/debian-mips64el-cross.docker index 9f6c4763c508..257204eae483 100644 --- a/tests/docker/dockerfiles/debian-mips64el-cross.docker +++ b/tests/docker/dockerfiles/debian-mips64el-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -133,7 +134,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev:mips64el \ libspice-protocol-dev:mips64el \ libspice-server-dev:mips64el \ - libssh-gcrypt-dev:mips64el \ + libssh-dev:mips64el \ libsystemd-dev:mips64el \ libtasn1-6-dev:mips64el \ libudev-dev:mips64el \ diff --git a/tests/docker/dockerfiles/debian-mipsel-cross.docker b/tests/docker/dockerfiles/debian-mipsel-cross.docker index 2e979111e012..395c84d65bce 100644 --- a/tests/docker/dockerfiles/debian-mipsel-cross.docker +++ b/tests/docker/dockerfiles/debian-mipsel-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -133,7 +134,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev:mipsel \ libspice-protocol-dev:mipsel \ libspice-server-dev:mipsel \ - libssh-gcrypt-dev:mipsel \ + libssh-dev:mipsel \ libsystemd-dev:mipsel \ libtasn1-6-dev:mipsel \ libudev-dev:mipsel \ diff --git a/tests/docker/dockerfiles/debian-ppc64el-cross.docker b/tests/docker/dockerfiles/debian-ppc64el-cross.docker index 8ee450dba09d..1ae227ccded7 100644 --- a/tests/docker/dockerfiles/debian-ppc64el-cross.docker +++ b/tests/docker/dockerfiles/debian-ppc64el-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -134,7 +135,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev:ppc64el \ libspice-protocol-dev:ppc64el \ libspice-server-dev:ppc64el \ - libssh-gcrypt-dev:ppc64el \ + libssh-dev:ppc64el \ libsystemd-dev:ppc64el \ libtasn1-6-dev:ppc64el \ libubsan1:ppc64el \ diff --git a/tests/docker/dockerfiles/debian-s390x-cross.docker b/tests/docker/dockerfiles/debian-s390x-cross.docker index f451a07c4c68..afa81a57ba8d 100644 --- a/tests/docker/dockerfiles/debian-s390x-cross.docker +++ b/tests/docker/dockerfiles/debian-s390x-cross.docker @@ -58,6 +58,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zstd && \ eatmydata apt-get autoremove -y && \ @@ -133,7 +134,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsnappy-dev:s390x \ libsndio-dev:s390x \ libspice-protocol-dev:s390x \ - libssh-gcrypt-dev:s390x \ + libssh-dev:s390x \ libsystemd-dev:s390x \ libtasn1-6-dev:s390x \ libubsan1:s390x \ diff --git a/tests/docker/dockerfiles/debian.docker b/tests/docker/dockerfiles/debian.docker index 505330a9e22e..5b3bac43ccbe 100644 --- a/tests/docker/dockerfiles/debian.docker +++ b/tests/docker/dockerfiles/debian.docker @@ -87,7 +87,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libsndio-dev \ libspice-protocol-dev \ libspice-server-dev \ - libssh-gcrypt-dev \ + libssh-dev \ libsystemd-dev \ libtasn1-6-dev \ libubsan1 \ @@ -131,6 +131,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zlib1g-dev \ zstd && \ diff --git a/tests/docker/dockerfiles/fedora-rust-nightly.docker b/tests/docker/dockerfiles/fedora-rust-nightly.docker index a8e4fb279a76..fe4a6ed48d68 100644 --- a/tests/docker/dockerfiles/fedora-rust-nightly.docker +++ b/tests/docker/dockerfiles/fedora-rust-nightly.docker @@ -132,6 +132,7 @@ exec "$@"\n' > /usr/bin/nosync && \ util-linux \ virglrenderer-devel \ vte291-devel \ + vulkan-tools \ which \ xen-devel \ xorriso \ diff --git a/tests/docker/dockerfiles/fedora-win64-cross.docker b/tests/docker/dockerfiles/fedora-win64-cross.docker index 7dc3eb03f537..a95034440270 100644 --- a/tests/docker/dockerfiles/fedora-win64-cross.docker +++ b/tests/docker/dockerfiles/fedora-win64-cross.docker @@ -61,6 +61,7 @@ exec "$@"\n' > /usr/bin/nosync && \ tesseract \ tesseract-langpack-eng \ util-linux \ + vulkan-tools \ which \ xorriso \ zstd && \ diff --git a/tests/docker/dockerfiles/fedora.docker b/tests/docker/dockerfiles/fedora.docker index b64399af662c..014e3ccf17dd 100644 --- a/tests/docker/dockerfiles/fedora.docker +++ b/tests/docker/dockerfiles/fedora.docker @@ -132,6 +132,7 @@ exec "$@"\n' > /usr/bin/nosync && \ util-linux \ virglrenderer-devel \ vte291-devel \ + vulkan-tools \ which \ xen-devel \ xorriso \ diff --git a/tests/docker/dockerfiles/opensuse-leap.docker b/tests/docker/dockerfiles/opensuse-leap.docker index 4d5fb3e3a149..e90225dc2351 100644 --- a/tests/docker/dockerfiles/opensuse-leap.docker +++ b/tests/docker/dockerfiles/opensuse-leap.docker @@ -115,6 +115,7 @@ RUN zypper update -y && \ util-linux \ virglrenderer-devel \ vte-devel \ + vulkan-tools \ which \ xen-devel \ xorriso \ diff --git a/tests/docker/dockerfiles/ubuntu2204.docker b/tests/docker/dockerfiles/ubuntu2204.docker index e1b70b536dee..88ce4ef9a9d3 100644 --- a/tests/docker/dockerfiles/ubuntu2204.docker +++ b/tests/docker/dockerfiles/ubuntu2204.docker @@ -130,6 +130,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ tar \ tesseract-ocr \ tesseract-ocr-eng \ + vulkan-tools \ xorriso \ zlib1g-dev \ zstd && \ diff --git a/tests/lcitool/libvirt-ci b/tests/lcitool/libvirt-ci index b6a65806bc9b..18c4bfe02c46 160000 --- a/tests/lcitool/libvirt-ci +++ b/tests/lcitool/libvirt-ci @@ -1 +1 @@ -Subproject commit b6a65806bc9b2b56985f5e97c936b77c7e7a99fc +Subproject commit 18c4bfe02c467e5639bf9a687139735ccd7a3fff diff --git a/tests/lcitool/projects/qemu.yml b/tests/lcitool/projects/qemu.yml index 80bcac090276..c07242f27287 100644 --- a/tests/lcitool/projects/qemu.yml +++ b/tests/lcitool/projects/qemu.yml @@ -122,6 +122,7 @@ packages: - usbredir - virglrenderer - vte + - vulkan-tools - which - xen - xorriso diff --git a/tests/lcitool/refresh b/tests/lcitool/refresh index 53f8d2585f82..aa551aca9be2 100755 --- a/tests/lcitool/refresh +++ b/tests/lcitool/refresh @@ -163,7 +163,7 @@ try: # # Standard native builds # - generate_dockerfile("alpine", "alpine-319") + generate_dockerfile("alpine", "alpine-321") generate_dockerfile("centos9", "centos-stream-9") generate_dockerfile("debian", "debian-12", trailer="".join(debian12_extras)) diff --git a/tests/vm/generated/freebsd.json b/tests/vm/generated/freebsd.json index 81fc38d798a5..c03e1cd58637 100644 --- a/tests/vm/generated/freebsd.json +++ b/tests/vm/generated/freebsd.json @@ -73,6 +73,7 @@ "usbredir", "virglrenderer", "vte3", + "vulkan-tools", "xorriso", "zstd" ], From 830bbfb965274c2ac577420d28c7adecf8a39fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:19 +0000 Subject: [PATCH 0792/1179] tests/vm: bump timeout for shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On my fairly beefy machine the timeout was triggering leaving a corrupted disk image due to power being pulled before the disk had synced. Triple the timeout to avoid this. Reviewed-by: Thomas Huth Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-13-alex.bennee@linaro.org> --- tests/vm/basevm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/vm/basevm.py b/tests/vm/basevm.py index 6d41ac757434..9e879e966a36 100644 --- a/tests/vm/basevm.py +++ b/tests/vm/basevm.py @@ -83,7 +83,7 @@ class BaseVM(object): # command to halt the guest, can be overridden by subclasses poweroff = "poweroff" # Time to wait for shutdown to finish. - shutdown_timeout_default = 30 + shutdown_timeout_default = 90 # enable IPv6 networking ipv6 = True # This is the timeout on the wait for console bytes. From 33629b82aeadb070a803e5887d3a5de59b1c95c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:20 +0000 Subject: [PATCH 0793/1179] tests/tcg: mark test-vma as a linux-only test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The main multiarch tests should compile for any POSIX system, however test-vma's usage of MAP_NORESERVE makes it a linux-only test. Simply moving the source file is enough for the build logic to skip on BSD's. Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-14-alex.bennee@linaro.org> --- tests/tcg/multiarch/{ => linux}/test-vma.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/tcg/multiarch/{ => linux}/test-vma.c (100%) diff --git a/tests/tcg/multiarch/test-vma.c b/tests/tcg/multiarch/linux/test-vma.c similarity index 100% rename from tests/tcg/multiarch/test-vma.c rename to tests/tcg/multiarch/linux/test-vma.c From 0cee7cfc2495c2c7d108c38a9b07162af78553b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:21 +0000 Subject: [PATCH 0794/1179] tests/tcg: add message to _Static_assert in test-avx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for enabling clang and avoiding: error: '_Static_assert' with no message is a C2x extension [-Werror,-Wc2x-extensions] let us just add the message to silence the warning. Reviewed-by: Thomas Huth Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-15-alex.bennee@linaro.org> --- tests/tcg/i386/test-avx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tcg/i386/test-avx.c b/tests/tcg/i386/test-avx.c index 230e6d84b84a..80fe363cfc29 100644 --- a/tests/tcg/i386/test-avx.c +++ b/tests/tcg/i386/test-avx.c @@ -244,7 +244,7 @@ v4di indexd = {0x00000002ffffffcdull, 0xfffffff500000010ull, 0x0000003afffffff0ull, 0x000000000000000eull}; v4di gather_mem[0x20]; -_Static_assert(sizeof(gather_mem) == 1024); +_Static_assert(sizeof(gather_mem) == 1024, "gather_mem not expected size"); void init_f16reg(v4di *r) { From 77f005f954ea3cd0a161618a9f6c3f9d874c5b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:22 +0000 Subject: [PATCH 0795/1179] tests/tcg: fix constraints in test-i386-adcox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clang complains: clang -O2 -m64 -mcx16 /home/alex/lsrc/qemu.git/tests/tcg/i386/test-i386-adcox.c -o test-i386-adcox -static /home/alex/lsrc/qemu.git/tests/tcg/i386/test-i386-adcox.c:32:26: error: invalid input constraint '0' in asm : "r" ((REG)-1), "0" (flags), "1" (out_adcx), "2" (out_adox)); ^ /home/alex/lsrc/qemu.git/tests/tcg/i386/test-i386-adcox.c:57:26: error: invalid input constraint '0' in asm : "r" ((REG)-1), "0" (flags), "1" (out_adcx), "2" (out_adox)); ^ 2 errors generated. Pointing out a numbered input constraint can't point to a read/write output [1]. Convert to a read-only input constraint to allow this. [1] https://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20101101/036036.html Suggested-by: Daniel P. Berrangé Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-16-alex.bennee@linaro.org> --- tests/tcg/i386/test-i386-adcox.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tcg/i386/test-i386-adcox.c b/tests/tcg/i386/test-i386-adcox.c index 16169efff823..a717064adb0c 100644 --- a/tests/tcg/i386/test-i386-adcox.c +++ b/tests/tcg/i386/test-i386-adcox.c @@ -29,7 +29,7 @@ void test_adox_adcx(uint32_t in_c, uint32_t in_o, REG adcx_operand, REG adox_ope "adcx %3, %1;" "pushf; pop %0" : "+r" (flags), "+r" (out_adcx), "+r" (out_adox) - : "r" ((REG)-1), "0" (flags), "1" (out_adcx), "2" (out_adox)); + : "r" ((REG) - 1), "0" (flags), "1" (out_adcx), "2" (out_adox)); assert(out_adcx == in_c + adcx_operand - 1); assert(out_adox == in_o + adox_operand - 1); @@ -53,8 +53,8 @@ void test_adcx_adox(uint32_t in_c, uint32_t in_o, REG adcx_operand, REG adox_ope "adcx %3, %1;" "adox %3, %2;" "pushf; pop %0" - : "+r" (flags), "+r" (out_adcx), "+r" (out_adox) - : "r" ((REG)-1), "0" (flags), "1" (out_adcx), "2" (out_adox)); + : "+r"(flags), "+r"(out_adcx), "+r"(out_adox) + : "r" ((REG)-1)); assert(out_adcx == in_c + adcx_operand - 1); assert(out_adox == in_o + adox_operand - 1); From 874c712dc148142dfdaf2589d66a17391fe331f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:23 +0000 Subject: [PATCH 0796/1179] tests/tcg: enable -fwrapv for test-i386-bmi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We allow things like: tests/tcg/i386/test-i386-bmi2.c:124:35: warning: shifting a negative signed value is undefined [-Wshift-negative-value] assert(result == (mask & ~(-1 << 30))); in the main code, so allow it for the test. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-17-alex.bennee@linaro.org> --- tests/tcg/i386/Makefile.target | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tcg/i386/Makefile.target b/tests/tcg/i386/Makefile.target index bbe2c44b2a79..f1df40411b09 100644 --- a/tests/tcg/i386/Makefile.target +++ b/tests/tcg/i386/Makefile.target @@ -22,7 +22,7 @@ run-test-i386-sse-exceptions: QEMU_OPTS += -cpu max test-i386-pcmpistri: CFLAGS += -msse4.2 run-test-i386-pcmpistri: QEMU_OPTS += -cpu max -test-i386-bmi2: CFLAGS=-O2 +test-i386-bmi2: CFLAGS=-O2 -fwrapv run-test-i386-bmi2: QEMU_OPTS += -cpu max test-i386-adcox: CFLAGS=-O2 From c05aec9d4a9cc0ab6615efad209acff6c750c40a Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Tue, 4 Mar 2025 22:24:24 +0000 Subject: [PATCH 0797/1179] tests/tcg: Suppress compiler false-positive warning on sha1.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC versions at least 12 through 15 incorrectly report a warning about code in sha1.c: tests/tcg/multiarch/sha1.c:161:13: warning: ‘SHA1Transform’ reading 64 bytes from a region of size 0 [-Wstringop-overread] 161 | SHA1Transform(context->state, &data[i]); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is a piece of stock library code for doing SHA1 which we've simply copied, rather than writing ourselves. The bug has been reported to upstream GCC (about a different library's use of this code): https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106709 For our test case, since this isn't our original code and there isn't actually a bug in it, suppress the incorrect warning rather than trying to modify the code to work around the compiler issue. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2328 Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-Id: <20250227141343.1675415-1-peter.maydell@linaro.org> [AJB: -Wno-unknown-warning-option for clang's sake] Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-18-alex.bennee@linaro.org> --- tests/tcg/aarch64/Makefile.target | 3 ++- tests/tcg/arm/Makefile.target | 3 ++- tests/tcg/hexagon/Makefile.target | 2 +- tests/tcg/multiarch/Makefile.target | 8 ++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile.target index 9efe2f81adf5..16ddcf4f8831 100644 --- a/tests/tcg/aarch64/Makefile.target +++ b/tests/tcg/aarch64/Makefile.target @@ -83,7 +83,8 @@ test-aes: CFLAGS += -O -march=armv8-a+aes test-aes: test-aes-main.c.inc # Vector SHA1 -sha1-vector: CFLAGS=-O3 +# Work around compiler false-positive warning, as we do for the 'sha1' test +sha1-vector: CFLAGS=-O3 -Wno-stringop-overread sha1-vector: sha1.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) run-sha1-vector: sha1-vector run-sha1 diff --git a/tests/tcg/arm/Makefile.target b/tests/tcg/arm/Makefile.target index 99a953b66711..6189d7a0e243 100644 --- a/tests/tcg/arm/Makefile.target +++ b/tests/tcg/arm/Makefile.target @@ -61,7 +61,8 @@ endif ARM_TESTS += commpage # Vector SHA1 -sha1-vector: CFLAGS=-O3 +# Work around compiler false-positive warning, as we do for the 'sha1' test +sha1-vector: CFLAGS=-O3 -Wno-stringop-overread sha1-vector: sha1.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) run-sha1-vector: sha1-vector run-sha1 diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target index e5182c01d8a0..4dfc39bc98ee 100644 --- a/tests/tcg/hexagon/Makefile.target +++ b/tests/tcg/hexagon/Makefile.target @@ -18,7 +18,7 @@ # Hexagon doesn't support gdb, so skip the EXTRA_RUNS EXTRA_RUNS = -CFLAGS += -Wno-incompatible-pointer-types -Wno-undefined-internal +CFLAGS += -Wno-incompatible-pointer-types -Wno-undefined-internal -Wno-unknown-warning-option CFLAGS += -fno-unroll-loops -fno-stack-protector HEX_SRC=$(SRC_PATH)/tests/tcg/hexagon diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target index 688a6be203ca..c769a7d69d9e 100644 --- a/tests/tcg/multiarch/Makefile.target +++ b/tests/tcg/multiarch/Makefile.target @@ -45,6 +45,14 @@ vma-pthread: LDFLAGS+=-pthread sigreturn-sigmask: CFLAGS+=-pthread sigreturn-sigmask: LDFLAGS+=-pthread +# GCC versions 12/13/14/15 at least incorrectly complain about +# "'SHA1Transform' reading 64 bytes from a region of size 0"; see the gcc bug +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106709 +# Since this is just a standard piece of library code we've borrowed for a +# TCG test case, suppress the warning rather than trying to modify the +# code to work around the compiler. +sha1: CFLAGS+=-Wno-stringop-overread + # The vma-pthread seems very sensitive on gitlab and we currently # don't know if its exposing a real bug or the test is flaky. ifneq ($(GITLAB_CI),) From 3df360cb03d56cecdc23abc5855ffceedfcbfa0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:25 +0000 Subject: [PATCH 0798/1179] gitlab: add a new build_unit job to track build size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to reduce the total number of build units in the system to get on our way to a single binary. It will help to have some numbers so lets add a job to gitlab to track our progress. Cc: Pierrick Bouvier Cc: Philippe Mathieu-Daudé Cc: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-19-alex.bennee@linaro.org> --- .gitlab-ci.d/check-units.py | 66 ++++++++++++++++++++++++++++++++++ .gitlab-ci.d/static_checks.yml | 22 ++++++++++++ 2 files changed, 88 insertions(+) create mode 100755 .gitlab-ci.d/check-units.py diff --git a/.gitlab-ci.d/check-units.py b/.gitlab-ci.d/check-units.py new file mode 100755 index 000000000000..268a4118d599 --- /dev/null +++ b/.gitlab-ci.d/check-units.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# +# check-units.py: check the number of compilation units and identify +# those that are rebuilt multiple times +# +# Copyright (C) 2025 Linaro Ltd. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from os import access, R_OK, path +from sys import argv, exit +import json +from collections import Counter + + +def extract_build_units(cc_path): + """ + Extract the build units and their counds from compile_commands.json file. + + Returns: + Hash table of ["unit"] = count + """ + + j = json.load(open(cc_path, 'r')) + files = [f['file'] for f in j] + build_units = Counter(files) + + return build_units + + +def analyse_units(build_units): + """ + Analyse the build units and report stats and the top 10 rebuilds + """ + + print(f"Total source files: {len(build_units.keys())}") + print(f"Total build units: {sum(units.values())}") + + # Create a sorted list by number of rebuilds + sorted_build_units = sorted(build_units.items(), + key=lambda item: item[1], + reverse=True) + + print("Most rebuilt units:") + for unit, count in sorted_build_units[:20]: + print(f" {unit} built {count} times") + + print("Least rebuilt units:") + for unit, count in sorted_build_units[-10:]: + print(f" {unit} built {count} times") + + +if __name__ == "__main__": + if len(argv) != 2: + script_name = path.basename(argv[0]) + print(f"Usage: {script_name} ") + exit(1) + + cc_path = argv[1] + if path.isfile(cc_path) and access(cc_path, R_OK): + units = extract_build_units(cc_path) + analyse_units(units) + exit(0) + else: + print(f"{cc_path} doesn't exist or isn't readable") + exit(1) diff --git a/.gitlab-ci.d/static_checks.yml b/.gitlab-ci.d/static_checks.yml index c0ba4533826e..c3ed6de453d0 100644 --- a/.gitlab-ci.d/static_checks.yml +++ b/.gitlab-ci.d/static_checks.yml @@ -70,3 +70,25 @@ check-rust-tools-nightly: expire_in: 2 days paths: - rust/target/doc + +check-build-units: + extends: .base_job_template + stage: build + image: $CI_REGISTRY_IMAGE/qemu/debian:$QEMU_CI_CONTAINER_TAG + needs: + job: amd64-debian-container + before_script: + - source scripts/ci/gitlab-ci-section + - section_start setup "Install Tools" + - apt install --assume-yes --no-install-recommends jq + - section_end setup + script: + - mkdir build + - cd build + - section_start configure "Running configure" + - ../configure + - cd .. + - section_end configure + - section_start analyse "Analyse" + - .gitlab-ci.d/check-units.py build/compile_commands.json + - section_end analyse From 2a8e8544b285ace2a6f21b72e18d823738fc8167 Mon Sep 17 00:00:00 2001 From: Pierrick Bouvier Date: Tue, 4 Mar 2025 22:24:26 +0000 Subject: [PATCH 0799/1179] tests/functional: add boot error detection for RME tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was identified that those tests randomly fail with a synchronous exception at boot (reported by EDK2). While we solve this problem, report failure immediately so tests don't timeout in CI. Signed-off-by: Pierrick Bouvier Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20250303185745.2504842-1-pierrick.bouvier@linaro.org> Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-20-alex.bennee@linaro.org> --- tests/functional/test_aarch64_rme_sbsaref.py | 3 ++- tests/functional/test_aarch64_rme_virt.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/functional/test_aarch64_rme_sbsaref.py b/tests/functional/test_aarch64_rme_sbsaref.py index 93bb52833865..ddcc9493a6cf 100755 --- a/tests/functional/test_aarch64_rme_sbsaref.py +++ b/tests/functional/test_aarch64_rme_sbsaref.py @@ -60,7 +60,8 @@ def test_aarch64_rme_sbsaref(self): self.vm.launch() # Wait for host VM boot to complete. - wait_for_console_pattern(self, 'Welcome to Buildroot') + wait_for_console_pattern(self, 'Welcome to Buildroot', + failure_message='Synchronous Exception at') exec_command_and_wait_for_pattern(self, 'root', '#') test_realms_guest(self) diff --git a/tests/functional/test_aarch64_rme_virt.py b/tests/functional/test_aarch64_rme_virt.py index 42b9229b4cb0..38e01721a4e1 100755 --- a/tests/functional/test_aarch64_rme_virt.py +++ b/tests/functional/test_aarch64_rme_virt.py @@ -89,7 +89,8 @@ def test_aarch64_rme_virt(self): self.vm.launch() # Wait for host VM boot to complete. - wait_for_console_pattern(self, 'Welcome to Buildroot') + wait_for_console_pattern(self, 'Welcome to Buildroot', + failure_message='Synchronous Exception at') exec_command_and_wait_for_pattern(self, 'root', '#') test_realms_guest(self) From 9d06b0ccb1089f8713c4244109958bed8374ada4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:27 +0000 Subject: [PATCH 0800/1179] plugins/api: use qemu_target_page_mask() to get value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requiring TARGET_PAGE_MASK to be defined gets in the way of building this unit once. qemu_target_page_mask() will tell us what it is. Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-21-alex.bennee@linaro.org> --- plugins/api.c | 3 ++- tests/tcg/hexagon/Makefile.target | 2 +- tests/tcg/multiarch/Makefile.target | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/api.c b/plugins/api.c index cf8cdf076a0f..fa4d4952779a 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -43,6 +43,7 @@ #include "tcg/tcg.h" #include "exec/exec-all.h" #include "exec/gdbstub.h" +#include "exec/target_page.h" #include "exec/translation-block.h" #include "exec/translator.h" #include "disas/disas.h" @@ -287,7 +288,7 @@ uint64_t qemu_plugin_insn_vaddr(const struct qemu_plugin_insn *insn) void *qemu_plugin_insn_haddr(const struct qemu_plugin_insn *insn) { const DisasContextBase *db = tcg_ctx->plugin_db; - vaddr page0_last = db->pc_first | ~TARGET_PAGE_MASK; + vaddr page0_last = db->pc_first | ~qemu_target_page_mask(); if (db->fake_insn) { return NULL; diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target index 4dfc39bc98ee..e5182c01d8a0 100644 --- a/tests/tcg/hexagon/Makefile.target +++ b/tests/tcg/hexagon/Makefile.target @@ -18,7 +18,7 @@ # Hexagon doesn't support gdb, so skip the EXTRA_RUNS EXTRA_RUNS = -CFLAGS += -Wno-incompatible-pointer-types -Wno-undefined-internal -Wno-unknown-warning-option +CFLAGS += -Wno-incompatible-pointer-types -Wno-undefined-internal CFLAGS += -fno-unroll-loops -fno-stack-protector HEX_SRC=$(SRC_PATH)/tests/tcg/hexagon diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target index c769a7d69d9e..45c9cfe18c5b 100644 --- a/tests/tcg/multiarch/Makefile.target +++ b/tests/tcg/multiarch/Makefile.target @@ -51,7 +51,7 @@ sigreturn-sigmask: LDFLAGS+=-pthread # Since this is just a standard piece of library code we've borrowed for a # TCG test case, suppress the warning rather than trying to modify the # code to work around the compiler. -sha1: CFLAGS+=-Wno-stringop-overread +sha1: CFLAGS+=-Wno-stringop-overread -Wno-unknown-warning-option # The vma-pthread seems very sensitive on gitlab and we currently # don't know if its exposing a real bug or the test is flaky. From 1d9a9743589e3622a93a46b593820ffb22cbda1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:28 +0000 Subject: [PATCH 0801/1179] plugins/loader: populate target_name with target_name() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have a function we can call for this, lets not rely on macros that stop us building once. Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-22-alex.bennee@linaro.org> --- plugins/loader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/loader.c b/plugins/loader.c index 99686b546666..827473c8b646 100644 --- a/plugins/loader.c +++ b/plugins/loader.c @@ -297,7 +297,7 @@ int qemu_plugin_load_list(QemuPluginList *head, Error **errp) struct qemu_plugin_desc *desc, *next; g_autofree qemu_info_t *info = g_new0(qemu_info_t, 1); - info->target_name = TARGET_NAME; + info->target_name = target_name(); info->version.min = QEMU_PLUGIN_MIN_VERSION; info->version.cur = QEMU_PLUGIN_VERSION; #ifndef CONFIG_USER_ONLY From 4752ed52727acf7f0e798de75ec024fc92e92af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:29 +0000 Subject: [PATCH 0802/1179] include/qemu: plugin-memory.h doesn't need cpu-defs.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hwaddr is a fixed size on all builds. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-23-alex.bennee@linaro.org> --- include/qemu/plugin-memory.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/qemu/plugin-memory.h b/include/qemu/plugin-memory.h index 71c1123308e0..6065ec7aaf76 100644 --- a/include/qemu/plugin-memory.h +++ b/include/qemu/plugin-memory.h @@ -9,7 +9,6 @@ #ifndef PLUGIN_MEMORY_H #define PLUGIN_MEMORY_H -#include "exec/cpu-defs.h" #include "exec/hwaddr.h" struct qemu_plugin_hwaddr { From f85a28dedfde2d530b3fb6cf3dc12e4b67e02909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:30 +0000 Subject: [PATCH 0803/1179] plugins/api: clean-up the includes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to re-factoring and clean-up work (especially to exec-all) we no longer need such broad headers for the api. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-24-alex.bennee@linaro.org> --- plugins/api.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/api.c b/plugins/api.c index fa4d4952779a..c3ba1e98e8b2 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -39,9 +39,7 @@ #include "qemu/main-loop.h" #include "qemu/plugin.h" #include "qemu/log.h" -#include "qemu/timer.h" #include "tcg/tcg.h" -#include "exec/exec-all.h" #include "exec/gdbstub.h" #include "exec/target_page.h" #include "exec/translation-block.h" @@ -51,7 +49,6 @@ #ifndef CONFIG_USER_ONLY #include "qapi/error.h" #include "migration/blocker.h" -#include "exec/ram_addr.h" #include "qemu/plugin-memory.h" #include "hw/boards.h" #else From 5dd09b8157a132468547ad8a392ec9f8d836c169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:31 +0000 Subject: [PATCH 0804/1179] plugins/plugin.h: include queue.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Headers should bring in what they need so don't rely on getting queue.h by side effects. This will help with clean-ups in the following patches. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-25-alex.bennee@linaro.org> --- plugins/plugin.h | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/plugin.h b/plugins/plugin.h index 30e2299a54db..9ed20b5c413c 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -13,6 +13,7 @@ #define PLUGIN_H #include +#include "qemu/queue.h" #include "qemu/qht.h" #define QEMU_PLUGIN_MIN_VERSION 2 From 8c15f6e435a15e90456d26ff26199861369c9d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:32 +0000 Subject: [PATCH 0805/1179] plugins/loader: compile loader only once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is very little in loader that is different between builds save for a tiny user/system mode difference in the plugin_info structure. Create two new files, user and system to hold mode specific helpers and move loader into common_ss. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-26-alex.bennee@linaro.org> --- plugins/loader.c | 13 ++----------- plugins/meson.build | 7 ++++++- plugins/plugin.h | 6 ++++++ plugins/system.c | 24 ++++++++++++++++++++++++ plugins/user.c | 19 +++++++++++++++++++ 5 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 plugins/system.c create mode 100644 plugins/user.c diff --git a/plugins/loader.c b/plugins/loader.c index 827473c8b646..7523d554f034 100644 --- a/plugins/loader.c +++ b/plugins/loader.c @@ -31,9 +31,6 @@ #include "qemu/memalign.h" #include "hw/core/cpu.h" #include "exec/tb-flush.h" -#ifndef CONFIG_USER_ONLY -#include "hw/boards.h" -#endif #include "plugin.h" @@ -300,14 +297,8 @@ int qemu_plugin_load_list(QemuPluginList *head, Error **errp) info->target_name = target_name(); info->version.min = QEMU_PLUGIN_MIN_VERSION; info->version.cur = QEMU_PLUGIN_VERSION; -#ifndef CONFIG_USER_ONLY - MachineState *ms = MACHINE(qdev_get_machine()); - info->system_emulation = true; - info->system.smp_vcpus = ms->smp.cpus; - info->system.max_vcpus = ms->smp.max_cpus; -#else - info->system_emulation = false; -#endif + + qemu_plugin_fillin_mode_info(info); QTAILQ_FOREACH_SAFE(desc, head, entry, next) { int err; diff --git a/plugins/meson.build b/plugins/meson.build index d60be2a4d6d0..f7820806d307 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -57,8 +57,13 @@ if host_os == 'windows' command: dlltool_cmd ) endif + +user_ss.add(files('user.c')) +system_ss.add(files('system.c')) + +common_ss.add(files('loader.c')) + specific_ss.add(files( - 'loader.c', 'core.c', 'api.c', )) diff --git a/plugins/plugin.h b/plugins/plugin.h index 9ed20b5c413c..6fbc443b96c9 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -119,4 +119,10 @@ struct qemu_plugin_scoreboard *plugin_scoreboard_new(size_t element_size); void plugin_scoreboard_free(struct qemu_plugin_scoreboard *score); +/** + * qemu_plugin_fillin_mode_info() - populate mode specific info + * info: pointer to qemu_info_t structure + */ +void qemu_plugin_fillin_mode_info(qemu_info_t *info); + #endif /* PLUGIN_H */ diff --git a/plugins/system.c b/plugins/system.c new file mode 100644 index 000000000000..b3ecc33ba520 --- /dev/null +++ b/plugins/system.c @@ -0,0 +1,24 @@ +/* + * QEMU Plugin system-emulation helpers + * + * Helpers that are specific to system emulation. + * + * Copyright (C) 2017, Emilio G. Cota + * Copyright (C) 2019-2025, Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/plugin.h" +#include "hw/boards.h" + +#include "plugin.h" + +void qemu_plugin_fillin_mode_info(qemu_info_t *info) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + info->system_emulation = true; + info->system.smp_vcpus = ms->smp.cpus; + info->system.max_vcpus = ms->smp.max_cpus; +} diff --git a/plugins/user.c b/plugins/user.c new file mode 100644 index 000000000000..250d5425020c --- /dev/null +++ b/plugins/user.c @@ -0,0 +1,19 @@ +/* + * QEMU Plugin user-mode helpers + * + * Helpers that are specific to user-mode. + * + * Copyright (C) 2017, Emilio G. Cota + * Copyright (C) 2019-2025, Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/plugin.h" +#include "plugin.h" + +void qemu_plugin_fillin_mode_info(qemu_info_t *info) +{ + info->system_emulation = false; +} From 903e870f2453731f9b44ce9734cfcb5509304677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:33 +0000 Subject: [PATCH 0806/1179] plugins/api: split out binary path/start/end/entry code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To move the main api.c to a single build compilation object we need to start splitting out user and system specific code. As we need to grob around host headers we move these particular helpers into the *-user mode directories. The binary/start/end/entry helpers are all NOPs for system mode. While using the plugin-api.c.inc trick means we build for both linux-user and bsd-user the BSD user-mode command line is still missing -plugin. This can be enabled once we have reliable check-tcg tests working for the BSDs. Reviewed-by: Richard Henderson Reviewed-by: Warner Losh Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-27-alex.bennee@linaro.org> --- bsd-user/meson.build | 1 + bsd-user/plugin-api.c | 15 +++++++++++++ common-user/plugin-api.c.inc | 43 ++++++++++++++++++++++++++++++++++++ linux-user/meson.build | 1 + linux-user/plugin-api.c | 15 +++++++++++++ plugins/api-system.c | 39 ++++++++++++++++++++++++++++++++ plugins/api.c | 43 ------------------------------------ plugins/meson.build | 2 +- 8 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 bsd-user/plugin-api.c create mode 100644 common-user/plugin-api.c.inc create mode 100644 linux-user/plugin-api.c create mode 100644 plugins/api-system.c diff --git a/bsd-user/meson.build b/bsd-user/meson.build index 39bad0ae33e7..37b7cd6de87c 100644 --- a/bsd-user/meson.build +++ b/bsd-user/meson.build @@ -13,6 +13,7 @@ bsd_user_ss.add(files( 'elfload.c', 'main.c', 'mmap.c', + 'plugin-api.c', 'signal.c', 'strace.c', 'uaccess.c', diff --git a/bsd-user/plugin-api.c b/bsd-user/plugin-api.c new file mode 100644 index 000000000000..6ccef7eaa04d --- /dev/null +++ b/bsd-user/plugin-api.c @@ -0,0 +1,15 @@ +/* + * QEMU Plugin API - bsd-user-mode only implementations + * + * Common user-mode only APIs are in plugins/api-user. These helpers + * are only specific to bsd-user. + * + * Copyright (C) 2017, Emilio G. Cota + * Copyright (C) 2019-2025, Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu.h" +#include "common-user/plugin-api.c.inc" diff --git a/common-user/plugin-api.c.inc b/common-user/plugin-api.c.inc new file mode 100644 index 000000000000..5b8a1396b606 --- /dev/null +++ b/common-user/plugin-api.c.inc @@ -0,0 +1,43 @@ +/* + * QEMU Plugin API - *-user-mode only implementations + * + * Common user-mode only APIs are in plugins/api-user. These helpers + * are only specific to the *-user frontends. + * + * Copyright (C) 2017, Emilio G. Cota + * Copyright (C) 2019-2025, Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/main-loop.h" +#include "qemu/plugin.h" +#include "qemu.h" + +/* + * Binary path, start and end locations. Host specific due to TaskState. + */ +const char *qemu_plugin_path_to_binary(void) +{ + TaskState *ts = get_task_state(current_cpu); + return g_strdup(ts->bprm->filename); +} + +uint64_t qemu_plugin_start_code(void) +{ + TaskState *ts = get_task_state(current_cpu); + return ts->info->start_code; +} + +uint64_t qemu_plugin_end_code(void) +{ + TaskState *ts = get_task_state(current_cpu); + return ts->info->end_code; +} + +uint64_t qemu_plugin_entry_code(void) +{ + TaskState *ts = get_task_state(current_cpu); + return ts->info->entry; +} diff --git a/linux-user/meson.build b/linux-user/meson.build index f75b4fe0e3ec..f47a213ca3dd 100644 --- a/linux-user/meson.build +++ b/linux-user/meson.build @@ -27,6 +27,7 @@ linux_user_ss.add(libdw) linux_user_ss.add(when: 'TARGET_HAS_BFLT', if_true: files('flatload.c')) linux_user_ss.add(when: 'TARGET_I386', if_true: files('vm86.c')) linux_user_ss.add(when: 'CONFIG_ARM_COMPATIBLE_SEMIHOSTING', if_true: files('semihost.c')) +linux_user_ss.add(when: 'CONFIG_TCG_PLUGINS', if_true: files('plugin-api.c')) syscall_nr_generators = {} diff --git a/linux-user/plugin-api.c b/linux-user/plugin-api.c new file mode 100644 index 000000000000..66755df5263b --- /dev/null +++ b/linux-user/plugin-api.c @@ -0,0 +1,15 @@ +/* + * QEMU Plugin API - linux-user-mode only implementations + * + * Common user-mode only APIs are in plugins/api-user. These helpers + * are only specific to linux-user. + * + * Copyright (C) 2017, Emilio G. Cota + * Copyright (C) 2019-2025, Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu.h" +#include "common-user/plugin-api.c.inc" diff --git a/plugins/api-system.c b/plugins/api-system.c new file mode 100644 index 000000000000..cb0dd8f73079 --- /dev/null +++ b/plugins/api-system.c @@ -0,0 +1,39 @@ +/* + * QEMU Plugin API - System specific implementations + * + * This provides the APIs that have a specific system implementation + * or are only relevant to system-mode. + * + * Copyright (C) 2017, Emilio G. Cota + * Copyright (C) 2019-2025, Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/main-loop.h" +#include "qemu/plugin.h" + +/* + * In system mode we cannot trace the binary being executed so the + * helpers all return NULL/0. + */ +const char *qemu_plugin_path_to_binary(void) +{ + return NULL; +} + +uint64_t qemu_plugin_start_code(void) +{ + return 0; +} + +uint64_t qemu_plugin_end_code(void) +{ + return 0; +} + +uint64_t qemu_plugin_entry_code(void) +{ + return 0; +} diff --git a/plugins/api.c b/plugins/api.c index c3ba1e98e8b2..ffccd71e4bf0 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -471,49 +471,6 @@ bool qemu_plugin_bool_parse(const char *name, const char *value, bool *ret) return name && value && qapi_bool_parse(name, value, ret, NULL); } -/* - * Binary path, start and end locations - */ -const char *qemu_plugin_path_to_binary(void) -{ - char *path = NULL; -#ifdef CONFIG_USER_ONLY - TaskState *ts = get_task_state(current_cpu); - path = g_strdup(ts->bprm->filename); -#endif - return path; -} - -uint64_t qemu_plugin_start_code(void) -{ - uint64_t start = 0; -#ifdef CONFIG_USER_ONLY - TaskState *ts = get_task_state(current_cpu); - start = ts->info->start_code; -#endif - return start; -} - -uint64_t qemu_plugin_end_code(void) -{ - uint64_t end = 0; -#ifdef CONFIG_USER_ONLY - TaskState *ts = get_task_state(current_cpu); - end = ts->info->end_code; -#endif - return end; -} - -uint64_t qemu_plugin_entry_code(void) -{ - uint64_t entry = 0; -#ifdef CONFIG_USER_ONLY - TaskState *ts = get_task_state(current_cpu); - entry = ts->info->entry; -#endif - return entry; -} - /* * Create register handles. * diff --git a/plugins/meson.build b/plugins/meson.build index f7820806d307..9c9bc9e5bbf9 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -59,7 +59,7 @@ if host_os == 'windows' endif user_ss.add(files('user.c')) -system_ss.add(files('system.c')) +system_ss.add(files('system.c', 'api-system.c')) common_ss.add(files('loader.c')) From 455a2d265cf6c3947243d772b9e5d1b8dd13a9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:34 +0000 Subject: [PATCH 0807/1179] plugins/api: split out the vaddr/hwaddr helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These only work for system-mode and are NOPs for user-mode. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-28-alex.bennee@linaro.org> --- plugins/api-system.c | 58 ++++++++++++++++++++++++++++++++++++ plugins/api-user.c | 40 +++++++++++++++++++++++++ plugins/api.c | 70 -------------------------------------------- plugins/meson.build | 2 +- 4 files changed, 99 insertions(+), 71 deletions(-) create mode 100644 plugins/api-user.c diff --git a/plugins/api-system.c b/plugins/api-system.c index cb0dd8f73079..38560de342f7 100644 --- a/plugins/api-system.c +++ b/plugins/api-system.c @@ -12,6 +12,10 @@ #include "qemu/osdep.h" #include "qemu/main-loop.h" +#include "qapi/error.h" +#include "migration/blocker.h" +#include "hw/boards.h" +#include "qemu/plugin-memory.h" #include "qemu/plugin.h" /* @@ -37,3 +41,57 @@ uint64_t qemu_plugin_entry_code(void) { return 0; } + +/* + * Virtual Memory queries + */ + +static __thread struct qemu_plugin_hwaddr hwaddr_info; + +struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info, + uint64_t vaddr) +{ + CPUState *cpu = current_cpu; + unsigned int mmu_idx = get_mmuidx(info); + enum qemu_plugin_mem_rw rw = get_plugin_meminfo_rw(info); + hwaddr_info.is_store = (rw & QEMU_PLUGIN_MEM_W) != 0; + + assert(mmu_idx < NB_MMU_MODES); + + if (!tlb_plugin_lookup(cpu, vaddr, mmu_idx, + hwaddr_info.is_store, &hwaddr_info)) { + error_report("invalid use of qemu_plugin_get_hwaddr"); + return NULL; + } + + return &hwaddr_info; +} + +bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr) +{ + return haddr->is_io; +} + +uint64_t qemu_plugin_hwaddr_phys_addr(const struct qemu_plugin_hwaddr *haddr) +{ + if (haddr) { + return haddr->phys_addr; + } + return 0; +} + +const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h) +{ + if (h && h->is_io) { + MemoryRegion *mr = h->mr; + if (!mr->name) { + unsigned maddr = (uintptr_t)mr; + g_autofree char *temp = g_strdup_printf("anon%08x", maddr); + return g_intern_string(temp); + } else { + return g_intern_string(mr->name); + } + } else { + return g_intern_static_string("RAM"); + } +} diff --git a/plugins/api-user.c b/plugins/api-user.c new file mode 100644 index 000000000000..867b42033942 --- /dev/null +++ b/plugins/api-user.c @@ -0,0 +1,40 @@ +/* + * QEMU Plugin API - user-mode only implementations + * + * This provides the APIs that have a user-mode specific + * implementations or are only relevant to user-mode. + * + * Copyright (C) 2017, Emilio G. Cota + * Copyright (C) 2019-2025, Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/plugin.h" + +/* + * Virtual Memory queries - these are all NOPs for user-mode which + * only ever has visibility of virtual addresses. + */ + +struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info, + uint64_t vaddr) +{ + return NULL; +} + +bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr) +{ + return false; +} + +uint64_t qemu_plugin_hwaddr_phys_addr(const struct qemu_plugin_hwaddr *haddr) +{ + return 0; +} + +const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h) +{ + return g_intern_static_string("Invalid"); +} diff --git a/plugins/api.c b/plugins/api.c index ffccd71e4bf0..82241699a58f 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -383,76 +383,6 @@ qemu_plugin_mem_value qemu_plugin_mem_get_value(qemu_plugin_meminfo_t info) return value; } -/* - * Virtual Memory queries - */ - -#ifdef CONFIG_SOFTMMU -static __thread struct qemu_plugin_hwaddr hwaddr_info; -#endif - -struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info, - uint64_t vaddr) -{ -#ifdef CONFIG_SOFTMMU - CPUState *cpu = current_cpu; - unsigned int mmu_idx = get_mmuidx(info); - enum qemu_plugin_mem_rw rw = get_plugin_meminfo_rw(info); - hwaddr_info.is_store = (rw & QEMU_PLUGIN_MEM_W) != 0; - - assert(mmu_idx < NB_MMU_MODES); - - if (!tlb_plugin_lookup(cpu, vaddr, mmu_idx, - hwaddr_info.is_store, &hwaddr_info)) { - error_report("invalid use of qemu_plugin_get_hwaddr"); - return NULL; - } - - return &hwaddr_info; -#else - return NULL; -#endif -} - -bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr) -{ -#ifdef CONFIG_SOFTMMU - return haddr->is_io; -#else - return false; -#endif -} - -uint64_t qemu_plugin_hwaddr_phys_addr(const struct qemu_plugin_hwaddr *haddr) -{ -#ifdef CONFIG_SOFTMMU - if (haddr) { - return haddr->phys_addr; - } -#endif - return 0; -} - -const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h) -{ -#ifdef CONFIG_SOFTMMU - if (h && h->is_io) { - MemoryRegion *mr = h->mr; - if (!mr->name) { - unsigned maddr = (uintptr_t)mr; - g_autofree char *temp = g_strdup_printf("anon%08x", maddr); - return g_intern_string(temp); - } else { - return g_intern_string(mr->name); - } - } else { - return g_intern_static_string("RAM"); - } -#else - return g_intern_static_string("Invalid"); -#endif -} - int qemu_plugin_num_vcpus(void) { return plugin_num_vcpus(); diff --git a/plugins/meson.build b/plugins/meson.build index 9c9bc9e5bbf9..942b59e90412 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -58,7 +58,7 @@ if host_os == 'windows' ) endif -user_ss.add(files('user.c')) +user_ss.add(files('user.c', 'api-user.c')) system_ss.add(files('system.c', 'api-system.c')) common_ss.add(files('loader.c')) From 1d3e745f7ada921d544f29fbf84c6f0f63025e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:35 +0000 Subject: [PATCH 0808/1179] plugins/api: split out time control helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are only usable in system mode where we control the timer. For user-mode make them NOPs. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-29-alex.bennee@linaro.org> --- plugins/api-system.c | 34 ++++++++++++++++++++++++++++++++++ plugins/api-user.c | 17 +++++++++++++++++ plugins/api.c | 41 ----------------------------------------- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/plugins/api-system.c b/plugins/api-system.c index 38560de342f7..cc190b167eac 100644 --- a/plugins/api-system.c +++ b/plugins/api-system.c @@ -95,3 +95,37 @@ const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h) return g_intern_static_string("RAM"); } } + +/* + * Time control + */ +static bool has_control; +static Error *migration_blocker; + +const void *qemu_plugin_request_time_control(void) +{ + if (!has_control) { + has_control = true; + error_setg(&migration_blocker, + "TCG plugin time control does not support migration"); + migrate_add_blocker(&migration_blocker, NULL); + return &has_control; + } + return NULL; +} + +static void advance_virtual_time__async(CPUState *cpu, run_on_cpu_data data) +{ + int64_t new_time = data.host_ulong; + qemu_clock_advance_virtual_time(new_time); +} + +void qemu_plugin_update_ns(const void *handle, int64_t new_time) +{ + if (handle == &has_control) { + /* Need to execute out of cpu_exec, so bql can be locked. */ + async_run_on_cpu(current_cpu, + advance_virtual_time__async, + RUN_ON_CPU_HOST_ULONG(new_time)); + } +} diff --git a/plugins/api-user.c b/plugins/api-user.c index 867b42033942..28704a89e899 100644 --- a/plugins/api-user.c +++ b/plugins/api-user.c @@ -12,6 +12,7 @@ #include "qemu/osdep.h" #include "qemu/plugin.h" +#include "exec/log.h" /* * Virtual Memory queries - these are all NOPs for user-mode which @@ -38,3 +39,19 @@ const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h) { return g_intern_static_string("Invalid"); } + +/* + * Time control - for user mode the only real time is wall clock time + * so realistically all you can do in user mode is slow down execution + * which doesn't require the ability to mess with the clock. + */ + +const void *qemu_plugin_request_time_control(void) +{ + return NULL; +} + +void qemu_plugin_update_ns(const void *handle, int64_t new_time) +{ + qemu_log_mask(LOG_UNIMP, "user-mode can't control time"); +} diff --git a/plugins/api.c b/plugins/api.c index 82241699a58f..832bf6ee5e6f 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -526,44 +526,3 @@ uint64_t qemu_plugin_u64_sum(qemu_plugin_u64 entry) return total; } -/* - * Time control - */ -static bool has_control; -#ifdef CONFIG_SOFTMMU -static Error *migration_blocker; -#endif - -const void *qemu_plugin_request_time_control(void) -{ - if (!has_control) { - has_control = true; -#ifdef CONFIG_SOFTMMU - error_setg(&migration_blocker, - "TCG plugin time control does not support migration"); - migrate_add_blocker(&migration_blocker, NULL); -#endif - return &has_control; - } - return NULL; -} - -#ifdef CONFIG_SOFTMMU -static void advance_virtual_time__async(CPUState *cpu, run_on_cpu_data data) -{ - int64_t new_time = data.host_ulong; - qemu_clock_advance_virtual_time(new_time); -} -#endif - -void qemu_plugin_update_ns(const void *handle, int64_t new_time) -{ -#ifdef CONFIG_SOFTMMU - if (handle == &has_control) { - /* Need to execute out of cpu_exec, so bql can be locked. */ - async_run_on_cpu(current_cpu, - advance_virtual_time__async, - RUN_ON_CPU_HOST_ULONG(new_time)); - } -#endif -} From 40988ed9dfca934791bd2aeb1cc2d11e3a239265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:36 +0000 Subject: [PATCH 0809/1179] plugins/api: build only once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now all the softmmu/user-mode stuff has been split out we can build this compilation unit only once. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-30-alex.bennee@linaro.org> --- plugins/api.c | 11 ----------- plugins/meson.build | 3 +-- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/plugins/api.c b/plugins/api.c index 832bf6ee5e6f..604ce06802a5 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -46,17 +46,6 @@ #include "exec/translator.h" #include "disas/disas.h" #include "plugin.h" -#ifndef CONFIG_USER_ONLY -#include "qapi/error.h" -#include "migration/blocker.h" -#include "qemu/plugin-memory.h" -#include "hw/boards.h" -#else -#include "qemu.h" -#ifdef CONFIG_LINUX -#include "loader.h" -#endif -#endif /* Uninstall and Reset handlers */ diff --git a/plugins/meson.build b/plugins/meson.build index 942b59e90412..d27220d5ff31 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -61,9 +61,8 @@ endif user_ss.add(files('user.c', 'api-user.c')) system_ss.add(files('system.c', 'api-system.c')) -common_ss.add(files('loader.c')) +common_ss.add(files('loader.c', 'api.c')) specific_ss.add(files( 'core.c', - 'api.c', )) From 606ad7fe17a0baee43a29d71469fee246a09a7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:37 +0000 Subject: [PATCH 0810/1179] plugins/core: make a single build unit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trim through the includes and remove everything not needed for the core. Only include tcg-op-common.h to remove the need to TARGET_LONG_BITS and move the build unit into the common set. Reviewed-by: Richard Henderson Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-31-alex.bennee@linaro.org> --- plugins/core.c | 10 +--------- plugins/meson.build | 5 +---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/plugins/core.c b/plugins/core.c index bb105e8e6880..eb9281fe5476 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -12,22 +12,14 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" -#include "qemu/error-report.h" -#include "qemu/config-file.h" -#include "qapi/error.h" #include "qemu/lockable.h" #include "qemu/option.h" #include "qemu/plugin.h" #include "qemu/queue.h" #include "qemu/rcu_queue.h" -#include "qemu/xxhash.h" #include "qemu/rcu.h" -#include "hw/core/cpu.h" - -#include "exec/exec-all.h" #include "exec/tb-flush.h" -#include "tcg/tcg.h" -#include "tcg/tcg-op.h" +#include "tcg/tcg-op-common.h" #include "plugin.h" struct qemu_plugin_cb { diff --git a/plugins/meson.build b/plugins/meson.build index d27220d5ff31..3be8245a698d 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -61,8 +61,5 @@ endif user_ss.add(files('user.c', 'api-user.c')) system_ss.add(files('system.c', 'api-system.c')) -common_ss.add(files('loader.c', 'api.c')) +common_ss.add(files('loader.c', 'api.c', 'core.c')) -specific_ss.add(files( - 'core.c', -)) From 0d3dea7d7a49c22897e7435e8e09d9f587bc56c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Tue, 4 Mar 2025 22:24:38 +0000 Subject: [PATCH 0811/1179] MAINTAINERS: remove widely sanctioned entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following organisations appear on the US sanctions list: Yadro: https://sanctionssearch.ofac.treas.gov/Details.aspx?id=41125 ISPRAS: https://sanctionssearch.ofac.treas.gov/Details.aspx?id=50890 As a result maintainers interacting with such entities would face legal risk in a number of jurisdictions. To reduce the risk of inadvertent non-compliance remove entries from these organisations from the MAINTAINERS file. Mark the pcf8574 system as orphaned until someone volunteers to step up as a maintainer. Add myself as a second reviewer to record/replay so I can help with what odd fixes I can. Reviewed-by: Markus Armbruster Reviewed-by: Daniel P. Berrangé Acked-by: Paolo Bonzini Signed-off-by: Alex Bennée Message-Id: <20250304222439.2035603-32-alex.bennee@linaro.org> --- MAINTAINERS | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 758cc461beac..0e5db7a57449 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2547,8 +2547,7 @@ F: hw/i2c/i2c_mux_pca954x.c F: include/hw/i2c/i2c_mux_pca954x.h pcf8574 -M: Dmitrii Sharikhin -S: Maintained +S: Orphaned F: hw/gpio/pcf8574.c F: include/gpio/pcf8574.h @@ -3660,10 +3659,10 @@ F: net/filter-mirror.c F: tests/qtest/test-filter* Record/replay -M: Pavel Dovgalyuk R: Paolo Bonzini +R: Alex Bennée W: https://wiki.qemu.org/Features/record-replay -S: Supported +S: Odd Fixes F: replay/* F: block/blkreplay.c F: net/filter-replay.c From cfcacbab38e43200264c06135946b1c5096c393a Mon Sep 17 00:00:00 2001 From: Jiqian Chen Date: Wed, 6 Nov 2024 14:14:18 +0800 Subject: [PATCH 0812/1179] xen/passthrough: use gsi to map pirq when dom0 is PVH In PVH dom0, when passthrough a device to domU, QEMU code xen_pt_realize->xc_physdev_map_pirq wants to use gsi, but in current codes the gsi number is got from file /sys/bus/pci/devices//irq, that is wrong, because irq is not equal with gsi, they are in different spaces, so pirq mapping fails. To solve above problem, use new interface of Xen, xc_pcidev_get_gsi to get gsi and use xc_physdev_map_pirq_gsi to map pirq when dom0 is PVH. Signed-off-by: Jiqian Chen Signed-off-by: Huang Rui Signed-off-by: Jiqian Chen Acked-by: Anthony PERARD Reviewed-by: Stewart Hildebrand Message-Id: <20241106061418.3655304-1-Jiqian.Chen@amd.com> Signed-off-by: Anthony PERARD --- hw/xen/xen_pt.c | 60 ++++++++++++++++++++++++++++++++++++++++++++ include/hw/pci/pci.h | 4 +++ 2 files changed, 64 insertions(+) diff --git a/hw/xen/xen_pt.c b/hw/xen/xen_pt.c index e2bd4c7d4116..9487f68f2e98 100644 --- a/hw/xen/xen_pt.c +++ b/hw/xen/xen_pt.c @@ -766,6 +766,57 @@ static void xen_pt_destroy(PCIDevice *d) { } /* init */ +#if CONFIG_XEN_CTRL_INTERFACE_VERSION >= 42000 +static bool xen_pt_need_gsi(void) +{ + FILE *fp; + int len; + /* + * The max length of guest_type is "PVH"+'\n'+'\0', it is 5, + * so here set the length of type to be twice. + */ + char type[10]; + const char *guest_type = "/sys/hypervisor/guest_type"; + + fp = fopen(guest_type, "r"); + if (!fp) { + error_report("Cannot open %s: %s", guest_type, strerror(errno)); + return false; + } + + if (fgets(type, sizeof(type), fp)) { + len = strlen(type); + if (len) { + type[len - 1] = '\0'; + if (!strcmp(type, "PVH")) { + fclose(fp); + return true; + } + } + } + + fclose(fp); + return false; +} + +static int xen_pt_map_pirq_for_gsi(PCIDevice *d, int *pirq) +{ + int gsi; + XenPCIPassthroughState *s = XEN_PT_DEVICE(d); + + gsi = xc_pcidev_get_gsi(xen_xc, + PCI_SBDF(s->real_device.domain, + s->real_device.bus, + s->real_device.dev, + s->real_device.func)); + if (gsi >= 0) { + return xc_physdev_map_pirq_gsi(xen_xc, xen_domid, gsi, pirq); + } + + return gsi; +} +#endif + static void xen_pt_realize(PCIDevice *d, Error **errp) { ERRP_GUARD(); @@ -847,7 +898,16 @@ static void xen_pt_realize(PCIDevice *d, Error **errp) goto out; } +#if CONFIG_XEN_CTRL_INTERFACE_VERSION >= 42000 + if (xen_pt_need_gsi()) { + rc = xen_pt_map_pirq_for_gsi(d, &pirq); + } else { + rc = xc_physdev_map_pirq(xen_xc, xen_domid, machine_irq, &pirq); + } +#else rc = xc_physdev_map_pirq(xen_xc, xen_domid, machine_irq, &pirq); +#endif + if (rc < 0) { XEN_PT_ERR(d, "Mapping machine irq %u to pirq %i failed, (err: %d)\n", machine_irq, pirq, errno); diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h index c220cc844962..822fbacdf017 100644 --- a/include/hw/pci/pci.h +++ b/include/hw/pci/pci.h @@ -23,6 +23,10 @@ extern bool pci_available; #define PCI_SLOT_MAX 32 #define PCI_FUNC_MAX 8 +#define PCI_SBDF(seg, bus, dev, func) \ + ((((uint32_t)(seg)) << 16) | \ + (PCI_BUILD_BDF(bus, PCI_DEVFN(dev, func)))) + /* Class, Vendor and Device IDs from Linux's pci_ids.h */ #include "hw/pci/pci_ids.h" From 4173b3d83752d9547c188db0d99ade08a1adb1fc Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Fri, 7 Feb 2025 14:37:24 +0000 Subject: [PATCH 0813/1179] hw/xen: Add "mode" parameter to xen-block devices Block devices don't work in PV Grub (0.9x) if there is no mode specified. It complains: "Error ENOENT when reading the mode" Signed-off-by: David Woodhouse Message-Id: <20250207143724.30792-2-dwmw2@infradead.org> Signed-off-by: Anthony PERARD --- hw/block/xen-block.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/block/xen-block.c b/hw/block/xen-block.c index 2098286b5f96..ec04102b669e 100644 --- a/hw/block/xen-block.c +++ b/hw/block/xen-block.c @@ -408,6 +408,8 @@ static void xen_block_realize(XenDevice *xendev, Error **errp) } xen_device_backend_printf(xendev, "info", "%u", blockdev->info); + xen_device_backend_printf(xendev, "mode", + (blockdev->info & VDISK_READONLY) ? "r" : "w"); xen_device_frontend_printf(xendev, "virtual-device", "%lu", vdev->number); From 68adcc784bad13421ac7211c316a751fb99fcb94 Mon Sep 17 00:00:00 2001 From: Stefano Stabellini Date: Thu, 6 Feb 2025 20:49:15 +0100 Subject: [PATCH 0814/1179] xen: No need to flush the mapcache for grants On IOREQ_TYPE_INVALIDATE we need to invalidate the mapcache for regular mappings. Since recently we started reusing the mapcache also to keep track of grants mappings. However, there is no need to remove grant mappings on IOREQ_TYPE_INVALIDATE requests, we shouldn't do that. So remove the function call. Fixes: 9ecdd4bf08 (xen: mapcache: Add support for grant mappings) Cc: qemu-stable@nongnu.org Reported-by: Olaf Hering Reviewed-by: Edgar E. Iglesias Signed-off-by: Stefano Stabellini Signed-off-by: Edgar E. Iglesias Reviewed-by: Anthony PERARD Message-Id: <20250206194915.3357743-2-edgar.iglesias@gmail.com> Signed-off-by: Anthony PERARD --- hw/xen/xen-mapcache.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/xen/xen-mapcache.c b/hw/xen/xen-mapcache.c index 00bfbcc6fb04..698b5c53ed58 100644 --- a/hw/xen/xen-mapcache.c +++ b/hw/xen/xen-mapcache.c @@ -700,7 +700,6 @@ void xen_invalidate_map_cache(void) bdrv_drain_all(); xen_invalidate_map_cache_single(mapcache); - xen_invalidate_map_cache_single(mapcache_grants); } static uint8_t *xen_replace_cache_entry_unlocked(MapCache *mc, From d657a14de5d597bbfe7b54e4c4f0646f440e98ad Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Thu, 20 Feb 2025 08:24:59 -0500 Subject: [PATCH 0815/1179] migration: Fix UAF for incoming migration on MigrationState On the incoming migration side, QEMU uses a coroutine to load all the VM states. Inside, it may reference MigrationState on global states like migration capabilities, parameters, error state, shared mutexes and more. However there's nothing yet to make sure MigrationState won't get destroyed (e.g. after migration_shutdown()). Meanwhile there's also no API available to remove the incoming coroutine in migration_shutdown(), avoiding it to access the freed elements. There's a bug report showing this can happen and crash dest QEMU when migration is cancelled on source. When it happens, the dest main thread is trying to cleanup everything: #0 qemu_aio_coroutine_enter #1 aio_dispatch_handler #2 aio_poll #3 monitor_cleanup #4 qemu_cleanup #5 qemu_default_main Then it found the migration incoming coroutine, schedule it (even after migration_shutdown()), causing crash: #0 __pthread_kill_implementation #1 __pthread_kill_internal #2 __GI_raise #3 __GI_abort #4 __assert_fail_base #5 __assert_fail #6 qemu_mutex_lock_impl #7 qemu_lockable_mutex_lock #8 qemu_lockable_lock #9 qemu_lockable_auto_lock #10 migrate_set_error #11 process_incoming_migration_co #12 coroutine_trampoline To fix it, take a refcount after an incoming setup is properly done when qmp_migrate_incoming() succeeded the 1st time. As it's during a QMP handler which needs BQL, it means the main loop is still alive (without going into cleanups, which also needs BQL). Releasing the refcount now only until the incoming migration coroutine finished or failed. Hence the refcount is valid for both (1) setup phase of incoming ports, mostly IO watches (e.g. qio_channel_add_watch_full()), and (2) the incoming coroutine itself (process_incoming_migration_co()). Note that we can't unref in migration_incoming_state_destroy(), because both qmp_xen_load_devices_state() and load_snapshot() will use it without an incoming migration. Those hold BQL so they're not prone to this issue. PS: I suspect nobody uses Xen's command at all, as it didn't register yank, hence AFAIU the command should crash on master when trying to unregister yank in migration_incoming_state_destroy().. but that's another story. Also note that in some incoming failure cases we may not always unref the MigrationState refcount, which is a trade-off to keep things simple. We could make it accurate, but it can be an overkill. Some examples: - Unlike most of the rest protocols, socket_start_incoming_migration() may create net listener after incoming port setup sucessfully. It means we can't unref in migration_channel_process_incoming() as a generic path because socket protocol might keep using MigrationState. - For either socket or file, multiple IO watches might be created, it means logically each IO watch needs to take one refcount for MigrationState so as to be 100% accurate on ownership of refcount taken. In general, we at least need per-protocol handling to make it accurate, which can be an overkill if we know incoming failed after all. Add a short comment to explain that when taking the refcount in qmp_migrate_incoming(). Bugzilla: https://issues.redhat.com/browse/RHEL-69775 Tested-by: Yan Fu Signed-off-by: Peter Xu Reviewed-by: Fabiano Rosas Message-ID: <20250220132459.512610-1-peterx@redhat.com> Signed-off-by: Fabiano Rosas --- migration/migration.c | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/migration/migration.c b/migration/migration.c index 1833cfe3580c..d46e776e244f 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -116,6 +116,27 @@ static void migration_downtime_start(MigrationState *s) s->downtime_start = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); } +/* + * This is unfortunate: incoming migration actually needs the outgoing + * migration state (MigrationState) to be there too, e.g. to query + * capabilities, parameters, using locks, setup errors, etc. + * + * NOTE: when calling this, making sure current_migration exists and not + * been freed yet! Otherwise trying to access the refcount is already + * an use-after-free itself.. + * + * TODO: Move shared part of incoming / outgoing out into separate object. + * Then this is not needed. + */ +static void migrate_incoming_ref_outgoing_state(void) +{ + object_ref(migrate_get_current()); +} +static void migrate_incoming_unref_outgoing_state(void) +{ + object_unref(migrate_get_current()); +} + static void migration_downtime_end(MigrationState *s) { int64_t now = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); @@ -863,7 +884,7 @@ process_incoming_migration_co(void *opaque) * postcopy thread. */ trace_process_incoming_migration_co_postcopy_end_main(); - return; + goto out; } /* Else if something went wrong then just fall out of the normal exit */ } @@ -879,7 +900,8 @@ process_incoming_migration_co(void *opaque) } migration_bh_schedule(process_incoming_migration_bh, mis); - return; + goto out; + fail: migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE, MIGRATION_STATUS_FAILED); @@ -896,6 +918,9 @@ process_incoming_migration_co(void *opaque) exit(EXIT_FAILURE); } +out: + /* Pairs with the refcount taken in qmp_migrate_incoming() */ + migrate_incoming_unref_outgoing_state(); } /** @@ -1901,6 +1926,17 @@ void qmp_migrate_incoming(const char *uri, bool has_channels, return; } + /* + * Making sure MigrationState is available until incoming migration + * completes. + * + * NOTE: QEMU _might_ leak this refcount in some failure paths, but + * that's OK. This is the minimum change we need to at least making + * sure success case is clean on the refcount. We can try harder to + * make it accurate for any kind of failures, but it might be an + * overkill and doesn't bring us much benefit. + */ + migrate_incoming_ref_outgoing_state(); once = false; } From 094a3dbc55df1bbd2169eaf784cb75b594a72941 Mon Sep 17 00:00:00 2001 From: Steve Sistare Date: Thu, 27 Feb 2025 06:48:01 -0800 Subject: [PATCH 0816/1179] migration: ram block cpr blockers Unlike cpr-reboot mode, cpr-transfer mode cannot save volatile ram blocks in the migration stream file and recreate them later, because the physical memory for the blocks is pinned and registered for vfio. Add a blocker for volatile ram blocks. Also add a blocker for RAM_GUEST_MEMFD. Preserving guest_memfd may be sufficient for CPR, but it has not been tested yet. Signed-off-by: Steve Sistare Reviewed-by: Fabiano Rosas Reviewed-by: Peter Xu Reviewed-by: David Hildenbrand Message-ID: <1740667681-257312-1-git-send-email-steven.sistare@oracle.com> Signed-off-by: Fabiano Rosas --- include/exec/memory.h | 3 ++ include/exec/ramblock.h | 1 + migration/savevm.c | 2 ++ system/physmem.c | 66 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/include/exec/memory.h b/include/exec/memory.h index 78c4e0aec8d1..d09af58c971b 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -3203,6 +3203,9 @@ bool ram_block_discard_is_disabled(void); */ bool ram_block_discard_is_required(void); +void ram_block_add_cpr_blocker(RAMBlock *rb, Error **errp); +void ram_block_del_cpr_blocker(RAMBlock *rb); + #endif #endif diff --git a/include/exec/ramblock.h b/include/exec/ramblock.h index 0babd105c020..64484cd8217a 100644 --- a/include/exec/ramblock.h +++ b/include/exec/ramblock.h @@ -39,6 +39,7 @@ struct RAMBlock { /* RCU-enabled, writes protected by the ramlist lock */ QLIST_ENTRY(RAMBlock) next; QLIST_HEAD(, RAMBlockNotifier) ramblock_notifiers; + Error *cpr_blocker; int fd; uint64_t fd_offset; int guest_memfd; diff --git a/migration/savevm.c b/migration/savevm.c index 5c4fdfd95eeb..ce158c351245 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -3514,12 +3514,14 @@ void vmstate_register_ram(MemoryRegion *mr, DeviceState *dev) qemu_ram_set_idstr(mr->ram_block, memory_region_name(mr), dev); qemu_ram_set_migratable(mr->ram_block); + ram_block_add_cpr_blocker(mr->ram_block, &error_fatal); } void vmstate_unregister_ram(MemoryRegion *mr, DeviceState *dev) { qemu_ram_unset_idstr(mr->ram_block); qemu_ram_unset_migratable(mr->ram_block); + ram_block_del_cpr_blocker(mr->ram_block); } void vmstate_register_ram_global(MemoryRegion *mr) diff --git a/system/physmem.c b/system/physmem.c index a6af555f4b7a..e97de3ef65cf 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -71,7 +71,10 @@ #include "qemu/pmem.h" +#include "qapi/qapi-types-migration.h" +#include "migration/blocker.h" #include "migration/cpr.h" +#include "migration/options.h" #include "migration/vmstate.h" #include "qemu/range.h" @@ -1904,6 +1907,14 @@ static void ram_block_add(RAMBlock *new_block, Error **errp) qemu_mutex_unlock_ramlist(); goto out_free; } + + error_setg(&new_block->cpr_blocker, + "Memory region %s uses guest_memfd, " + "which is not supported with CPR.", + memory_region_name(new_block->mr)); + migrate_add_blocker_modes(&new_block->cpr_blocker, errp, + MIG_MODE_CPR_TRANSFER, + -1); } ram_size = (new_block->offset + new_block->max_length) >> TARGET_PAGE_BITS; @@ -4095,3 +4106,58 @@ bool ram_block_discard_is_required(void) return qatomic_read(&ram_block_discard_required_cnt) || qatomic_read(&ram_block_coordinated_discard_required_cnt); } + +/* + * Return true if ram is compatible with CPR. Do not exclude rom, + * because the rom file could change in new QEMU. + */ +static bool ram_is_cpr_compatible(RAMBlock *rb) +{ + MemoryRegion *mr = rb->mr; + + if (!mr || !memory_region_is_ram(mr)) { + return true; + } + + /* Ram device is remapped in new QEMU */ + if (memory_region_is_ram_device(mr)) { + return true; + } + + /* + * A file descriptor is passed to new QEMU and remapped, or its backing + * file is reopened and mapped. It must be shared to avoid COW. + */ + if (rb->fd >= 0 && qemu_ram_is_shared(rb)) { + return true; + } + + return false; +} + +/* + * Add a blocker for each volatile ram block. This function should only be + * called after we know that the block is migratable. Non-migratable blocks + * are either re-created in new QEMU, or are handled specially, or are covered + * by a device-level CPR blocker. + */ +void ram_block_add_cpr_blocker(RAMBlock *rb, Error **errp) +{ + assert(qemu_ram_is_migratable(rb)); + + if (ram_is_cpr_compatible(rb)) { + return; + } + + error_setg(&rb->cpr_blocker, + "Memory region %s is not compatible with CPR. share=on is " + "required for memory-backend objects, and aux-ram-share=on is " + "required.", memory_region_name(rb->mr)); + migrate_add_blocker_modes(&rb->cpr_blocker, errp, MIG_MODE_CPR_TRANSFER, + -1); +} + +void ram_block_del_cpr_blocker(RAMBlock *rb) +{ + migrate_del_blocker(&rb->cpr_blocker); +} From baa41af1c083446971feac39b0da845e547ca068 Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Wed, 5 Mar 2025 14:28:20 +0800 Subject: [PATCH 0817/1179] migration: Prioritize RDMA in ram_save_target_page() Address an error in RDMA-based migration by ensuring RDMA is prioritized when saving pages in `ram_save_target_page()`. Previously, the RDMA protocol's page-saving step was placed after other protocols due to a refactoring in commit bc38dc2f5f3. This led to migration failures characterized by unknown control messages and state loading errors destination: (qemu) qemu-system-x86_64: Unknown control message QEMU FILE qemu-system-x86_64: error while loading state section id 1(ram) qemu-system-x86_64: load of migration failed: Operation not permitted source: (qemu) qemu-system-x86_64: RDMA is in an error state waiting migration to abort! qemu-system-x86_64: failed to save SaveStateEntry with id(name): 1(ram): -1 qemu-system-x86_64: rdma migration: recv polling control error! qemu-system-x86_64: warning: Early error. Sending error. qemu-system-x86_64: warning: rdma migration: send polling control error RDMA migration implemented its own protocol/method to send pages to destination side, hand over to RDMA first to prevent pages being saved by other protocol. Fixes: bc38dc2f5f3 ("migration: refactor ram_save_target_page functions") Reviewed-by: Peter Xu Signed-off-by: Li Zhijian Message-ID: <20250305062825.772629-2-lizhijian@fujitsu.com> Signed-off-by: Fabiano Rosas --- migration/ram.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/migration/ram.c b/migration/ram.c index 589b6505eb22..424df6d9f133 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -1964,6 +1964,11 @@ static int ram_save_target_page(RAMState *rs, PageSearchStatus *pss) ram_addr_t offset = ((ram_addr_t)pss->page) << TARGET_PAGE_BITS; int res; + /* Hand over to RDMA first */ + if (control_save_page(pss, offset, &res)) { + return res; + } + if (!migrate_multifd() || migrate_zero_page_detection() == ZERO_PAGE_DETECTION_LEGACY) { if (save_zero_page(rs, pss, offset)) { @@ -1976,10 +1981,6 @@ static int ram_save_target_page(RAMState *rs, PageSearchStatus *pss) return ram_save_multifd_page(block, offset); } - if (control_save_page(pss, offset, &res)) { - return res; - } - return ram_save_page(rs, pss); } From a506a1b16702aae69a43e782f225bdc0ec6545fc Mon Sep 17 00:00:00 2001 From: Pierrick Bouvier Date: Tue, 4 Mar 2025 21:07:16 -0800 Subject: [PATCH 0818/1179] trace/control-target: cleanup headers and make compilation unit common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pierrick Bouvier Reviewed-by: Alex Bennée Tested-by: Alex Bennée Reviewed-by: Richard Henderson Message-ID: <20250305050716.3460989-1-pierrick.bouvier@linaro.org> Signed-off-by: Stefan Hajnoczi --- trace/control-target.c | 2 -- trace/meson.build | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/trace/control-target.c b/trace/control-target.c index d58e84f6dd8e..57ceac210842 100644 --- a/trace/control-target.c +++ b/trace/control-target.c @@ -8,8 +8,6 @@ */ #include "qemu/osdep.h" -#include "qemu/lockable.h" -#include "cpu.h" #include "trace/control.h" diff --git a/trace/meson.build b/trace/meson.build index c3412dc0ba5a..3df454935550 100644 --- a/trace/meson.build +++ b/trace/meson.build @@ -1,6 +1,4 @@ -system_ss.add(files('trace-hmp-cmds.c')) - -specific_ss.add(files('control-target.c')) +system_ss.add(files('control-target.c', 'trace-hmp-cmds.c')) trace_events_files = [] foreach item : [ '.' ] + trace_events_subdirs + qapi_trace_events From ecf92e3618ab6e6cd7ae151f9c12b9e2a6ead198 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:01 -0400 Subject: [PATCH 0819/1179] docs/sphinx: create QAPI domain extension stub A Sphinx domain is a collection of directive and role extensions meant to facilitate the documentation of a specific language. For instance, Sphinx ships with "python" and "cpp" domains. This patch introduces a stub for the "qapi" language domain. Please see https://www.sphinx-doc.org/en/master/usage/domains/index.html for more information. This stub doesn't really do anything yet, we'll get to it brick-by-brick in the forthcoming commits to keep the series breezy and the git history informative. Signed-off-by: John Snow Message-ID: <20250311034303.75779-4-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/conf.py | 9 +++++- docs/sphinx/qapi_domain.py | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 docs/sphinx/qapi_domain.py diff --git a/docs/conf.py b/docs/conf.py index 31bb9a37893f..49d9de894c0c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,14 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc'] +extensions = [ + 'depfile', + 'hxtool', + 'kerneldoc', + 'qapi_domain', + 'qapidoc', + 'qmp_lexer', +] if sphinx.version_info[:3] > (4, 0, 0): tags.add('sphinx4') diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py new file mode 100644 index 000000000000..a1983d94440a --- /dev/null +++ b/docs/sphinx/qapi_domain.py @@ -0,0 +1,56 @@ +""" +QAPI domain extension. +""" + +from __future__ import annotations + +from typing import ( + TYPE_CHECKING, + AbstractSet, + Any, + Dict, + Tuple, +) + +from sphinx.domains import Domain, ObjType +from sphinx.util import logging + + +if TYPE_CHECKING: + from sphinx.application import Sphinx + +logger = logging.getLogger(__name__) + + +class QAPIDomain(Domain): + """QAPI language domain.""" + + name = "qapi" + label = "QAPI" + + object_types: Dict[str, ObjType] = {} + directives = {} + roles = {} + initial_data: Dict[str, Dict[str, Tuple[Any]]] = {} + indices = [] + + def merge_domaindata( + self, docnames: AbstractSet[str], otherdata: Dict[str, Any] + ) -> None: + pass + + def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any: + # pylint: disable=unused-argument + return [] + + +def setup(app: Sphinx) -> Dict[str, Any]: + app.setup_extension("sphinx.directives") + app.add_domain(QAPIDomain) + + return { + "version": "1.0", + "env_version": 1, + "parallel_read_safe": True, + "parallel_write_safe": True, + } From abf6bedc38aea51ecce988a499a0b277bbd3c267 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:02 -0400 Subject: [PATCH 0820/1179] docs/sphinx: add compat.py module and nested_parse helper Create a compat module that handles sphinx cross-version compatibility issues. For the inaugural function, add a nested_parse_with_titles() helper that handles differences in line number tracking for nested directive body parsing. Spoilers: there are more cross-version hacks to come throughout the series. Signed-off-by: John Snow Message-ID: <20250311034303.75779-5-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/compat.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/sphinx/compat.py diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py new file mode 100644 index 000000000000..39b859a25e3c --- /dev/null +++ b/docs/sphinx/compat.py @@ -0,0 +1,35 @@ +""" +Sphinx cross-version compatibility goop +""" + +from docutils.nodes import Element + +from sphinx.util import nodes +from sphinx.util.docutils import SphinxDirective, switch_source_input + + +def nested_parse_with_titles( + directive: SphinxDirective, content_node: Element +) -> None: + """ + This helper preserves error parsing context across sphinx versions. + """ + + # necessary so that the child nodes get the right source/line set + content_node.document = directive.state.document + + try: + # Modern sphinx (6.2.0+) supports proper offsetting for + # nested parse error context management + nodes.nested_parse_with_titles( + directive.state, + directive.content, + content_node, + content_offset=directive.content_offset, + ) + except TypeError: + # No content_offset argument. Fall back to SSI method. + with switch_source_input(directive.state, directive.content): + nodes.nested_parse_with_titles( + directive.state, directive.content, content_node + ) From 36ceafad9e4e61138b08dc371c42248dc5289a57 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:03 -0400 Subject: [PATCH 0821/1179] docs/qapi-domain: add QAPI domain object registry This is the first step towards QAPI domain cross-references and a QAPI reference index. This patch just creates the object registry, and updates the merge_domaindata stub method now that we have actual data we may need to merge. Note that how to handle merge conflict resolution is unhandled, as the Sphinx python domain itself does not handle it either. I do not know how to intentionally trigger it, so I've left an assertion instead if it should ever come up ... Signed-off-by: John Snow Message-ID: <20250311034303.75779-6-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 77 +++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index a1983d94440a..f3ece42bc2a3 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -9,10 +9,12 @@ AbstractSet, Any, Dict, + NamedTuple, Tuple, ) from sphinx.domains import Domain, ObjType +from sphinx.locale import __ from sphinx.util import logging @@ -22,22 +24,93 @@ logger = logging.getLogger(__name__) +class ObjectEntry(NamedTuple): + docname: str + node_id: str + objtype: str + aliased: bool + + class QAPIDomain(Domain): """QAPI language domain.""" name = "qapi" label = "QAPI" + # This table associates cross-reference object types (key) with an + # ObjType instance, which defines the valid cross-reference roles + # for each object type. + + # Actual table entries for module, command, event, etc will come in + # forthcoming commits. object_types: Dict[str, ObjType] = {} + directives = {} roles = {} - initial_data: Dict[str, Dict[str, Tuple[Any]]] = {} + + # Moved into the data property at runtime; + # this is the internal index of reference-able objects. + initial_data: Dict[str, Dict[str, Tuple[Any]]] = { + "objects": {}, # fullname -> ObjectEntry + } + indices = [] + @property + def objects(self) -> Dict[str, ObjectEntry]: + ret = self.data.setdefault("objects", {}) + return ret # type: ignore[no-any-return] + + def note_object( + self, + name: str, + objtype: str, + node_id: str, + aliased: bool = False, + location: Any = None, + ) -> None: + """Note a QAPI object for cross reference.""" + if name in self.objects: + other = self.objects[name] + if other.aliased and aliased is False: + # The original definition found. Override it! + pass + elif other.aliased is False and aliased: + # The original definition is already registered. + return + else: + # duplicated + logger.warning( + __( + "duplicate object description of %s, " + "other instance in %s, use :no-index: for one of them" + ), + name, + other.docname, + location=location, + ) + self.objects[name] = ObjectEntry( + self.env.docname, node_id, objtype, aliased + ) + + def clear_doc(self, docname: str) -> None: + for fullname, obj in list(self.objects.items()): + if obj.docname == docname: + del self.objects[fullname] + def merge_domaindata( self, docnames: AbstractSet[str], otherdata: Dict[str, Any] ) -> None: - pass + for fullname, obj in otherdata["objects"].items(): + if obj.docname in docnames: + # Sphinx's own python domain doesn't appear to bother to + # check for collisions. Assert they don't happen and + # we'll fix it if/when the case arises. + assert fullname not in self.objects, ( + "bug - collision on merge?" + f" {fullname=} {obj=} {self.objects[fullname]=}" + ) + self.objects[fullname] = obj def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any: # pylint: disable=unused-argument From e93d29d27e93a25e0bd59d44299fc15486c62246 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:04 -0400 Subject: [PATCH 0822/1179] docs/qapi-domain: add QAPI index Use the QAPI object registry to generate a special index just for QAPI definitions. The index can show entries both by definition type and all together, alphabetically. The index can be linked from anywhere in the QEMU manual by using the reference `qapi-index`. Signed-off-by: John Snow Message-ID: <20250311034303.75779-7-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 73 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index f3ece42bc2a3..3e7718d32d1b 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -9,12 +9,20 @@ AbstractSet, Any, Dict, + Iterable, + List, NamedTuple, + Optional, Tuple, ) -from sphinx.domains import Domain, ObjType -from sphinx.locale import __ +from sphinx.domains import ( + Domain, + Index, + IndexEntry, + ObjType, +) +from sphinx.locale import _, __ from sphinx.util import logging @@ -31,6 +39,62 @@ class ObjectEntry(NamedTuple): aliased: bool +class QAPIIndex(Index): + """ + Index subclass to provide the QAPI definition index. + """ + + # pylint: disable=too-few-public-methods + + name = "index" + localname = _("QAPI Index") + shortname = _("QAPI Index") + + def generate( + self, + docnames: Optional[Iterable[str]] = None, + ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: + assert isinstance(self.domain, QAPIDomain) + content: Dict[str, List[IndexEntry]] = {} + collapse = False + + # list of all object (name, ObjectEntry) pairs, sorted by name + # (ignoring the module) + objects = sorted( + self.domain.objects.items(), + key=lambda x: x[0].split(".")[-1].lower(), + ) + + for objname, obj in objects: + if docnames and obj.docname not in docnames: + continue + + # Strip the module name out: + objname = objname.split(".")[-1] + + # Add an alphabetical entry: + entries = content.setdefault(objname[0].upper(), []) + entries.append( + IndexEntry( + objname, 0, obj.docname, obj.node_id, obj.objtype, "", "" + ) + ) + + # Add a categorical entry: + category = obj.objtype.title() + "s" + entries = content.setdefault(category, []) + entries.append( + IndexEntry(objname, 0, obj.docname, obj.node_id, "", "", "") + ) + + # alphabetically sort categories; type names first, ABC entries last. + sorted_content = sorted( + content.items(), + key=lambda x: (len(x[0]) == 1, x[0]), + ) + return sorted_content, collapse + + class QAPIDomain(Domain): """QAPI language domain.""" @@ -54,7 +118,10 @@ class QAPIDomain(Domain): "objects": {}, # fullname -> ObjectEntry } - indices = [] + # Index pages to generate; each entry is an Index class. + indices = [ + QAPIIndex, + ] @property def objects(self) -> Dict[str, ObjectEntry]: From dca2f3c47137b2bc276f443b3c269215a1ef9166 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:05 -0400 Subject: [PATCH 0823/1179] docs/qapi-domain: add resolve_any_xref() Add the ability to resolve cross-references using the `any` cross-reference syntax. Adding QAPI-specific cross-reference roles will be added in a forthcoming commit, and will share the same find_obj() helper. (There's less code needed for the generic cross-reference resolver, so it comes first in this series.) Once again, this code is based very heavily on sphinx.domains.python. Signed-off-by: John Snow Message-ID: <20250311034303.75779-8-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 96 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 3e7718d32d1b..f05c2cadf065 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -16,6 +16,9 @@ Tuple, ) +from docutils import nodes + +from sphinx.addnodes import pending_xref from sphinx.domains import ( Domain, Index, @@ -24,10 +27,15 @@ ) from sphinx.locale import _, __ from sphinx.util import logging +from sphinx.util.nodes import make_refnode if TYPE_CHECKING: + from docutils.nodes import Element + from sphinx.application import Sphinx + from sphinx.builders import Builder + from sphinx.environment import BuildEnvironment logger = logging.getLogger(__name__) @@ -179,9 +187,91 @@ def merge_domaindata( ) self.objects[fullname] = obj - def resolve_any_xref(self, *args: Any, **kwargs: Any) -> Any: - # pylint: disable=unused-argument - return [] + def find_obj( + self, modname: str, name: str, typ: Optional[str] + ) -> list[tuple[str, ObjectEntry]]: + """ + Find a QAPI object for "name", perhaps using the given module. + + Returns a list of (name, object entry) tuples. + + :param modname: The current module context (if any!) + under which we are searching. + :param name: The name of the x-ref to resolve; + may or may not include a leading module. + :param type: The role name of the x-ref we're resolving, if provided. + (This is absent for "any" lookups.) + """ + if not name: + return [] + + names: list[str] = [] + matches: list[tuple[str, ObjectEntry]] = [] + + fullname = name + if "." in fullname: + # We're searching for a fully qualified reference; + # ignore the contextual module. + pass + elif modname: + # We're searching for something from somewhere; + # try searching the current module first. + # e.g. :qapi:cmd:`query-block` or `query-block` is being searched. + fullname = f"{modname}.{name}" + + if typ is None: + # type isn't specified, this is a generic xref. + # search *all* qapi-specific object types. + objtypes: List[str] = list(self.object_types) + else: + # type is specified and will be a role (e.g. obj, mod, cmd) + # convert this to eligible object types (e.g. command, module) + # using the QAPIDomain.object_types table. + objtypes = self.objtypes_for_role(typ, []) + + if name in self.objects and self.objects[name].objtype in objtypes: + names = [name] + elif ( + fullname in self.objects + and self.objects[fullname].objtype in objtypes + ): + names = [fullname] + else: + # exact match wasn't found; e.g. we are searching for + # `query-block` from a different (or no) module. + searchname = "." + name + names = [ + oname + for oname in self.objects + if oname.endswith(searchname) + and self.objects[oname].objtype in objtypes + ] + + matches = [(oname, self.objects[oname]) for oname in names] + if len(matches) > 1: + matches = [m for m in matches if not m[1].aliased] + return matches + + def resolve_any_xref( + self, + env: BuildEnvironment, + fromdocname: str, + builder: Builder, + target: str, + node: pending_xref, + contnode: Element, + ) -> List[Tuple[str, nodes.reference]]: + results: List[Tuple[str, nodes.reference]] = [] + matches = self.find_obj(node.get("qapi:module"), target, None) + for name, obj in matches: + rolename = self.role_for_objtype(obj.objtype) + assert rolename is not None + role = f"qapi:{rolename}" + refnode = make_refnode( + builder, fromdocname, obj.docname, obj.node_id, contnode, name + ) + results.append((role, refnode)) + return results def setup(app: Sphinx) -> Dict[str, Any]: From 760b37e1df0cd4129127e1e381fb25b98ceecc79 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:06 -0400 Subject: [PATCH 0824/1179] docs/qapi-domain: add QAPI xref roles Add domain-specific cross-reference syntax. As of this commit, that means new :qapi:any:`block-core` referencing syntax. The :any: role will find anything registered to the QAPI domain, including modules, commands, events, etc. Creating the cross-references is powered by the QAPIXRefRole class; resolving them is handled by QAPIDomain.resolve_xref(). QAPIXrefRole is based heavily on Sphinx's own PyXrefRole, with modifications necessary for QAPI features. Signed-off-by: John Snow Message-ID: <20250311034303.75779-9-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 88 +++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index f05c2cadf065..49d42c0921cb 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -26,6 +26,7 @@ ObjType, ) from sphinx.locale import _, __ +from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util.nodes import make_refnode @@ -47,6 +48,54 @@ class ObjectEntry(NamedTuple): aliased: bool +class QAPIXRefRole(XRefRole): + + def process_link( + self, + env: BuildEnvironment, + refnode: Element, + has_explicit_title: bool, + title: str, + target: str, + ) -> tuple[str, str]: + refnode["qapi:module"] = env.ref_context.get("qapi:module") + + # Cross-references that begin with a tilde adjust the title to + # only show the reference without a leading module, even if one + # was provided. This is a Sphinx-standard syntax; give it + # priority over QAPI-specific type markup below. + hide_module = False + if target.startswith("~"): + hide_module = True + target = target[1:] + + # Type names that end with "?" are considered optional + # arguments and should be documented as such, but it's not + # part of the xref itself. + if target.endswith("?"): + refnode["qapi:optional"] = True + target = target[:-1] + + # Type names wrapped in brackets denote lists. strip the + # brackets and remember to add them back later. + if target.startswith("[") and target.endswith("]"): + refnode["qapi:array"] = True + target = target[1:-1] + + if has_explicit_title: + # Don't mess with the title at all if it was explicitly set. + # Explicit title syntax for references is e.g. + # :qapi:type:`target ` + # and this explicit title overrides everything else here. + return title, target + + title = target + if hide_module: + title = target.split(".")[-1] + + return title, target + + class QAPIIndex(Index): """ Index subclass to provide the QAPI definition index. @@ -118,7 +167,13 @@ class QAPIDomain(Domain): object_types: Dict[str, ObjType] = {} directives = {} - roles = {} + + # These are all cross-reference roles; e.g. + # :qapi:cmd:`query-block`. The keys correlate to the names used in + # the object_types table values above. + roles = { + "any": QAPIXRefRole(), # reference *any* type of QAPI object. + } # Moved into the data property at runtime; # this is the internal index of reference-able objects. @@ -252,6 +307,37 @@ def find_obj( matches = [m for m in matches if not m[1].aliased] return matches + def resolve_xref( + self, + env: BuildEnvironment, + fromdocname: str, + builder: Builder, + typ: str, + target: str, + node: pending_xref, + contnode: Element, + ) -> nodes.reference | None: + modname = node.get("qapi:module") + matches = self.find_obj(modname, target, typ) + + if not matches: + return None + + if len(matches) > 1: + logger.warning( + __("more than one target found for cross-reference %r: %s"), + target, + ", ".join(match[0] for match in matches), + type="ref", + subtype="qapi", + location=node, + ) + + name, obj = matches[0] + return make_refnode( + builder, fromdocname, obj.docname, obj.node_id, contnode, name + ) + def resolve_any_xref( self, env: BuildEnvironment, From 6d64a27cd367d9d8639b560dc4ed13cf05b5f43b Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:07 -0400 Subject: [PATCH 0825/1179] docs/qapi-domain: add compatibility node classes Sphinx prior to v4.0 uses different classes for rendering elements of documentation objects; add some compatibility classes to use the right node classes conditionally. Signed-off-by: John Snow Message-ID: <20250311034303.75779-10-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/compat.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py index 39b859a25e3c..6bc698c5adae 100644 --- a/docs/sphinx/compat.py +++ b/docs/sphinx/compat.py @@ -2,12 +2,27 @@ Sphinx cross-version compatibility goop """ -from docutils.nodes import Element +from typing import Callable +from docutils.nodes import Element, Node, Text + +import sphinx +from sphinx import addnodes from sphinx.util import nodes from sphinx.util.docutils import SphinxDirective, switch_source_input +SpaceNode: Callable[[str], Node] +KeywordNode: Callable[[str, str], Node] + +if sphinx.version_info[:3] >= (4, 0, 0): + SpaceNode = addnodes.desc_sig_space + KeywordNode = addnodes.desc_sig_keyword +else: + SpaceNode = Text + KeywordNode = addnodes.desc_annotation + + def nested_parse_with_titles( directive: SphinxDirective, content_node: Element ) -> None: From 1ea664862ad097f25d722c1c733d16c5b971e99b Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:08 -0400 Subject: [PATCH 0826/1179] docs/qapi-domain: Add QAPIDescription abstract class This class is a generic, top-level directive for documenting some kind of QAPI thingamajig that we expect to go into the Index. This class doesn't do much by itself, and it isn't yet associated with any particular directive. handle_signature(), _object_hierarchy_parts() and _toc_entry_name() are defined in the base class. get_index_text() and add_target_and_index() are new methods defined here; they are based heavily on the layout and format of the Python domain's general object class. Signed-off-by: John Snow Message-ID: <20250311034303.75779-11-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 101 ++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 49d42c0921cb..0ee36b464483 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -14,11 +14,13 @@ NamedTuple, Optional, Tuple, + cast, ) from docutils import nodes -from sphinx.addnodes import pending_xref +from sphinx.addnodes import desc_signature, pending_xref +from sphinx.directives import ObjectDescription from sphinx.domains import ( Domain, Index, @@ -28,7 +30,7 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging -from sphinx.util.nodes import make_refnode +from sphinx.util.nodes import make_id, make_refnode if TYPE_CHECKING: @@ -96,6 +98,101 @@ def process_link( return title, target +Signature = str + + +class QAPIDescription(ObjectDescription[Signature]): + """ + Generic QAPI description. + + This is meant to be an abstract class, not instantiated + directly. This class handles the abstract details of indexing, the + TOC, and reference targets for QAPI descriptions. + """ + + def handle_signature(self, sig: str, signode: desc_signature) -> Signature: + # Do nothing. The return value here is the "name" of the entity + # being documented; for QAPI, this is the same as the + # "signature", which is just a name. + + # Normally this method must also populate signode with nodes to + # render the signature; here we do nothing instead - the + # subclasses will handle this. + return sig + + def get_index_text(self, name: Signature) -> Tuple[str, str]: + """Return the text for the index entry of the object.""" + + # NB: this is used for the global index, not the QAPI index. + return ("single", f"{name} (QMP {self.objtype})") + + def add_target_and_index( + self, name: Signature, sig: str, signode: desc_signature + ) -> None: + # name is the return value of handle_signature. + # sig is the original, raw text argument to handle_signature. + # For QAPI, these are identical, currently. + + assert self.objtype + + # If we're documenting a module, don't include the module as + # part of the FQN. + modname = "" + if self.objtype != "module": + modname = self.options.get( + "module", self.env.ref_context.get("qapi:module") + ) + fullname = (modname + "." if modname else "") + name + + node_id = make_id( + self.env, self.state.document, self.objtype, fullname + ) + signode["ids"].append(node_id) + + self.state.document.note_explicit_target(signode) + domain = cast(QAPIDomain, self.env.get_domain("qapi")) + domain.note_object(fullname, self.objtype, node_id, location=signode) + + if "no-index-entry" not in self.options: + arity, indextext = self.get_index_text(name) + assert self.indexnode is not None + if indextext: + self.indexnode["entries"].append( + (arity, indextext, node_id, "", None) + ) + + def _object_hierarchy_parts( + self, sig_node: desc_signature + ) -> Tuple[str, ...]: + if "fullname" not in sig_node: + return () + modname = sig_node.get("module") + fullname = sig_node["fullname"] + + if modname: + return (modname, *fullname.split(".")) + + return tuple(fullname.split(".")) + + def _toc_entry_name(self, sig_node: desc_signature) -> str: + # This controls the name in the TOC and on the sidebar. + + # This is the return type of _object_hierarchy_parts(). + toc_parts = cast(Tuple[str, ...], sig_node.get("_toc_parts", ())) + if not toc_parts: + return "" + + config = self.env.app.config + *parents, name = toc_parts + if config.toc_object_entries_show_parents == "domain": + return sig_node.get("fullname", name) + if config.toc_object_entries_show_parents == "hide": + return name + if config.toc_object_entries_show_parents == "all": + return ".".join(parents + [name]) + return "" + + class QAPIIndex(Index): """ Index subclass to provide the QAPI definition index. From 7320feeb9663c2fba1f70ed263f4c18080c75e3e Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:09 -0400 Subject: [PATCH 0827/1179] docs/qapi-domain: add qapi:module directive This adds the qapi:module directive, which just notes the current module being documented and performs a nested parse of the content block, if present. This code is based pretty heavily on Sphinx's PyModule directive, but with unnecessary features excised. For example: .. qapi:module:: block-core Hello, and welcome to block-core! ================================= lorem ipsum, dolor sit amet ... Signed-off-by: John Snow Message-ID: <20250311034303.75779-12-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 71 ++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 0ee36b464483..e623d1f86782 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -19,6 +19,7 @@ from docutils import nodes +from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref from sphinx.directives import ObjectDescription from sphinx.domains import ( @@ -34,7 +35,7 @@ if TYPE_CHECKING: - from docutils.nodes import Element + from docutils.nodes import Element, Node from sphinx.application import Sphinx from sphinx.builders import Builder @@ -193,6 +194,60 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: return "" +class QAPIModule(QAPIDescription): + """ + Directive to mark description of a new module. + + This directive doesn't generate any special formatting, and is just + a pass-through for the content body. Named section titles are + allowed in the content body. + + Use this directive to create entries for the QAPI module in the + global index and the QAPI index; as well as to associate subsequent + definitions with the module they are defined in for purposes of + search and QAPI index organization. + + :arg: The name of the module. + :opt no-index: Don't add cross-reference targets or index entries. + :opt no-typesetting: Don't render the content body (but preserve any + cross-reference target IDs in the squelched output.) + + Example:: + + .. qapi:module:: block-core + :no-index: + :no-typesetting: + + Lorem ipsum, dolor sit amet ... + """ + + def run(self) -> List[Node]: + modname = self.arguments[0].strip() + self.env.ref_context["qapi:module"] = modname + ret = super().run() + + # ObjectDescription always creates a visible signature bar. We + # want module items to be "invisible", however. + + # Extract the content body of the directive: + assert isinstance(ret[-1], addnodes.desc) + desc_node = ret.pop(-1) + assert isinstance(desc_node.children[1], addnodes.desc_content) + ret.extend(desc_node.children[1].children) + + # Re-home node_ids so anchor refs still work: + node_ids: List[str] + if node_ids := [ + node_id + for el in desc_node.children[0].traverse(nodes.Element) + for node_id in cast(List[str], el.get("ids", ())) + ]: + target_node = nodes.target(ids=node_ids) + ret.insert(1, target_node) + + return ret + + class QAPIIndex(Index): """ Index subclass to provide the QAPI definition index. @@ -258,17 +313,21 @@ class QAPIDomain(Domain): # This table associates cross-reference object types (key) with an # ObjType instance, which defines the valid cross-reference roles # for each object type. + object_types: Dict[str, ObjType] = { + "module": ObjType(_("module"), "mod", "any"), + } - # Actual table entries for module, command, event, etc will come in - # forthcoming commits. - object_types: Dict[str, ObjType] = {} - - directives = {} + # Each of these provides a rST directive, + # e.g. .. qapi:module:: block-core + directives = { + "module": QAPIModule, + } # These are all cross-reference roles; e.g. # :qapi:cmd:`query-block`. The keys correlate to the names used in # the object_types table values above. roles = { + "mod": QAPIXRefRole(), "any": QAPIXRefRole(), # reference *any* type of QAPI object. } From 8799c3641a869bb04387ccc4922a66bbd5fcd67e Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:10 -0400 Subject: [PATCH 0828/1179] docs/qapi-domain: add QAPIObject class This patch adds another abstract class that describes "a QAPI thingie". The main difference here is that this class will be generating visible documentation, unlike the QAPIDescription class. Signed-off-by: John Snow Message-ID: <20250311034303.75779-13-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index e623d1f86782..3109c0cb90a0 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -18,7 +18,9 @@ ) from docutils import nodes +from docutils.parsers.rst import directives +from compat import KeywordNode, SpaceNode from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref from sphinx.directives import ObjectDescription @@ -40,6 +42,7 @@ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.environment import BuildEnvironment + from sphinx.util.typing import OptionSpec logger = logging.getLogger(__name__) @@ -99,6 +102,8 @@ def process_link( return title, target +# Alias for the return of handle_signature(), which is used in several places. +# (In the Python domain, this is Tuple[str, str] instead.) Signature = str @@ -194,6 +199,65 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: return "" +class QAPIObject(QAPIDescription): + """ + Description of a generic QAPI object. + + It's not used directly, but is instead subclassed by specific directives. + """ + + # Inherit some standard options from Sphinx's ObjectDescription + option_spec: OptionSpec = ( # type:ignore[misc] + ObjectDescription.option_spec.copy() + ) + option_spec.update( + { + # Borrowed from the Python domain: + "module": directives.unchanged, # Override contextual module name + } + ) + + def get_signature_prefix(self) -> List[nodes.Node]: + """Return a prefix to put before the object name in the signature.""" + assert self.objtype + return [ + KeywordNode("", self.objtype.title()), + SpaceNode(" "), + ] + + def get_signature_suffix(self) -> List[nodes.Node]: + """Return a suffix to put after the object name in the signature.""" + return [] + + def handle_signature(self, sig: str, signode: desc_signature) -> Signature: + """ + Transform a QAPI definition name into RST nodes. + + This method was originally intended for handling function + signatures. In the QAPI domain, however, we only pass the + definition name as the directive argument and handle everything + else in the content body with field lists. + + As such, the only argument here is "sig", which is just the QAPI + definition name. + """ + modname = self.options.get( + "module", self.env.ref_context.get("qapi:module") + ) + + signode["fullname"] = sig + signode["module"] = modname + sig_prefix = self.get_signature_prefix() + if sig_prefix: + signode += addnodes.desc_annotation( + str(sig_prefix), "", *sig_prefix + ) + signode += addnodes.desc_name(sig, sig) + signode += self.get_signature_suffix() + + return sig + + class QAPIModule(QAPIDescription): """ Directive to mark description of a new module. From 758bbdcd1212bb0b7369fea249114ad02d4b7fd0 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:11 -0400 Subject: [PATCH 0829/1179] docs/qapi-domain: add qapi:command directive This commit adds a stubbed version of QAPICommand that utilizes the QAPIObject class, the qapi:command directive, the :qapi:cmd: cross-reference role, and the "command" object type in the QAPI object registry. They don't do anything *particularly* interesting yet, but that will come in forthcoming commits. Signed-off-by: John Snow Message-ID: <20250311034303.75779-14-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 3109c0cb90a0..547040f75a7a 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -258,6 +258,12 @@ def handle_signature(self, sig: str, signode: desc_signature) -> Signature: return sig +class QAPICommand(QAPIObject): + """Description of a QAPI Command.""" + + # Nothing unique for now! Changed in later commits O:-) + + class QAPIModule(QAPIDescription): """ Directive to mark description of a new module. @@ -379,12 +385,14 @@ class QAPIDomain(Domain): # for each object type. object_types: Dict[str, ObjType] = { "module": ObjType(_("module"), "mod", "any"), + "command": ObjType(_("command"), "cmd", "any"), } # Each of these provides a rST directive, # e.g. .. qapi:module:: block-core directives = { "module": QAPIModule, + "command": QAPICommand, } # These are all cross-reference roles; e.g. @@ -392,6 +400,7 @@ class QAPIDomain(Domain): # the object_types table values above. roles = { "mod": QAPIXRefRole(), + "cmd": QAPIXRefRole(), "any": QAPIXRefRole(), # reference *any* type of QAPI object. } From 700d51a45c614068393f9754d3ef366e2c05a884 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:12 -0400 Subject: [PATCH 0830/1179] docs/qapi-domain: add :since: directive option Add a little special markup for registering "Since:" information. Adding it as an option instead of generic content lets us hoist the information into the Signature bar, optionally put it in the index, etc. Signed-off-by: John Snow Message-ID: <20250311034303.75779-15-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 547040f75a7a..222b420d2a78 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -214,6 +214,8 @@ class QAPIObject(QAPIDescription): { # Borrowed from the Python domain: "module": directives.unchanged, # Override contextual module name + # These are QAPI originals: + "since": directives.unchanged, } ) @@ -227,7 +229,17 @@ def get_signature_prefix(self) -> List[nodes.Node]: def get_signature_suffix(self) -> List[nodes.Node]: """Return a suffix to put after the object name in the signature.""" - return [] + ret: List[nodes.Node] = [] + + if "since" in self.options: + ret += [ + SpaceNode(" "), + addnodes.desc_sig_element( + "", f"(Since: {self.options['since']})" + ), + ] + + return ret def handle_signature(self, sig: str, signode: desc_signature) -> Signature: """ From 618379701b4127877d858aa6a792c8a329ec48bc Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:13 -0400 Subject: [PATCH 0831/1179] docs/qapi-domain: add "Arguments:" field lists This adds special rendering for Sphinx's typed info field lists. This patch does not add any QAPI-aware markup, rendering, or cross-referencing for the type names, yet. That feature requires a subclass to TypedField which will happen in its own commit quite a bit later in this series; after all the basic fields and objects have been established first. The syntax for this field is: :arg type name: description description cont'd You can omit the type or the description. You should not omit the name; if you do so, it degenerates into a "normal field list" entry, and probably isn't what you want. Signed-off-by: John Snow Message-ID: <20250311034303.75779-16-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 222b420d2a78..b4289db6d81f 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -33,6 +33,7 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging +from sphinx.util.docfields import TypedField from sphinx.util.nodes import make_id, make_refnode @@ -273,7 +274,18 @@ def handle_signature(self, sig: str, signode: desc_signature) -> Signature: class QAPICommand(QAPIObject): """Description of a QAPI Command.""" - # Nothing unique for now! Changed in later commits O:-) + doc_field_types = QAPIObject.doc_field_types.copy() + doc_field_types.extend( + [ + # :arg TypeName ArgName: descr + TypedField( + "argument", + label=_("Arguments"), + names=("arg",), + can_collapse=False, + ), + ] + ) class QAPIModule(QAPIDescription): From 3d9a23f92f35afd16dfa5aaf52ede850de54560c Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:14 -0400 Subject: [PATCH 0832/1179] docs/qapi-domain: add "Features:" field lists Add support for Features field lists. There is no QAPI-specific functionality here, but this could be changed if desired (if we wanted the feature names to link somewhere, for instance.) This feature list doesn't have any restrictions, so it can be used to document object-wide features or per-member features as deemed appropriate. It's essentially free-form text. The syntax for this field is: :feat name: description description cont'd Signed-off-by: John Snow Message-ID: <20250311034303.75779-17-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index b4289db6d81f..8ec4482b2915 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -33,7 +33,7 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging -from sphinx.util.docfields import TypedField +from sphinx.util.docfields import GroupedField, TypedField from sphinx.util.nodes import make_id, make_refnode @@ -220,6 +220,16 @@ class QAPIObject(QAPIDescription): } ) + doc_field_types = [ + # :feat name: descr + GroupedField( + "feature", + label=_("Features"), + names=("feat",), + can_collapse=False, + ), + ] + def get_signature_prefix(self) -> List[nodes.Node]: """Return a prefix to put before the object name in the signature.""" assert self.objtype From 9605c2047766367160304645b2db5464275d83d3 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:15 -0400 Subject: [PATCH 0833/1179] docs/qapi-domain: add "Errors:" field lists ``:error: descr`` can now be used to document error conditions. The format of the description is not defined here; so the ability to name specific types is left to the document writer. Signed-off-by: John Snow Message-ID: <20250311034303.75779-18-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 8ec4482b2915..753500907833 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -33,7 +33,7 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging -from sphinx.util.docfields import GroupedField, TypedField +from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.nodes import make_id, make_refnode @@ -294,6 +294,13 @@ class QAPICommand(QAPIObject): names=("arg",), can_collapse=False, ), + # :error: descr + Field( + "error", + label=_("Errors"), + names=("error", "errors"), + has_arg=False, + ), ] ) From 8b77f8d5730003d868b5748af5438c43d17f8c3a Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:16 -0400 Subject: [PATCH 0834/1179] docs/qapi-domain: add "Return:" field lists Add "Return:" field list syntax to QAPI Commands. Like "Arguments:" and "Errors:", the type name isn't currently processed for cross-referencing, but this will be addressed in a forthcoming commit. The syntax of the new field is: :return TypeName: description description cont'd This patch adds "Return" as a GroupedField, which means that multiple return values can be annotated - this is only done because Sphinx does not support mandatory type arguments to Ungrouped fields. Because we want to cross-reference this type information later, we want to make the type argument mandatory. As a result, you can technically add multiple :return: fields, though I'm not aware of any circumstance in which you'd need or want to. Recommendation: "Don't do that, then." The forthcoming QAPIDoc transmogrifier does not, in fact, ever "do that". Signed-off-by: John Snow Message-ID: <20250311034303.75779-19-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 753500907833..45e69689d1ee 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -301,6 +301,13 @@ class QAPICommand(QAPIObject): names=("error", "errors"), has_arg=False, ), + # :return TypeName: descr + GroupedField( + "returnvalue", + label=_("Return"), + names=("return",), + can_collapse=True, + ), ] ) From 902c9b0e34049283628c4bd00a6841e13b754fec Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:17 -0400 Subject: [PATCH 0835/1179] docs/qapi-domain: add qapi:enum directive Add the .. qapi:enum:: directive, object, and :qapi:enum:`name` cross-reference role. Add the :value name: field list for documenting Enum values. Of note, also introduce a new "type" role that is intended to be used by other QAPI object directives to cross-reference arbitrary QAPI type names, but will exclude commands, events, and modules from consideration. Signed-off-by: John Snow Message-ID: <20250311034303.75779-20-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 45e69689d1ee..e399474dc5b4 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -312,6 +312,23 @@ class QAPICommand(QAPIObject): ) +class QAPIEnum(QAPIObject): + """Description of a QAPI Enum.""" + + doc_field_types = QAPIObject.doc_field_types.copy() + doc_field_types.extend( + [ + # :value name: descr + GroupedField( + "value", + label=_("Values"), + names=("value",), + can_collapse=False, + ) + ] + ) + + class QAPIModule(QAPIDescription): """ Directive to mark description of a new module. @@ -431,9 +448,14 @@ class QAPIDomain(Domain): # This table associates cross-reference object types (key) with an # ObjType instance, which defines the valid cross-reference roles # for each object type. + # + # e.g., the :qapi:type: cross-reference role can refer to enum, + # struct, union, or alternate objects; but :qapi:obj: can refer to + # anything. Each object also gets its own targeted cross-reference role. object_types: Dict[str, ObjType] = { "module": ObjType(_("module"), "mod", "any"), "command": ObjType(_("command"), "cmd", "any"), + "enum": ObjType(_("enum"), "enum", "type", "any"), } # Each of these provides a rST directive, @@ -441,6 +463,7 @@ class QAPIDomain(Domain): directives = { "module": QAPIModule, "command": QAPICommand, + "enum": QAPIEnum, } # These are all cross-reference roles; e.g. @@ -449,6 +472,9 @@ class QAPIDomain(Domain): roles = { "mod": QAPIXRefRole(), "cmd": QAPIXRefRole(), + "enum": QAPIXRefRole(), + # reference any data type (excludes modules, commands, events) + "type": QAPIXRefRole(), "any": QAPIXRefRole(), # reference *any* type of QAPI object. } From bac3f1313c2d1dca49bd5499965d8cee0d7bb98f Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:18 -0400 Subject: [PATCH 0836/1179] docs/qapi-domain: add qapi:alternate directive Add the .. qapi:alternate:: directive, object, and qapi:alt:`name` cross-reference role. Add the "Alternatives:" field list for describing alternate choices. Like other field lists that reference QAPI types, a forthcoming commit will add cross-referencing support to this field. Signed-off-by: John Snow Message-ID: <20250311034303.75779-21-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index e399474dc5b4..506ed92700d0 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -329,6 +329,23 @@ class QAPIEnum(QAPIObject): ) +class QAPIAlternate(QAPIObject): + """Description of a QAPI Alternate.""" + + doc_field_types = QAPIObject.doc_field_types.copy() + doc_field_types.extend( + [ + # :alt type name: descr + TypedField( + "alternative", + label=_("Alternatives"), + names=("alt",), + can_collapse=False, + ), + ] + ) + + class QAPIModule(QAPIDescription): """ Directive to mark description of a new module. @@ -456,6 +473,7 @@ class QAPIDomain(Domain): "module": ObjType(_("module"), "mod", "any"), "command": ObjType(_("command"), "cmd", "any"), "enum": ObjType(_("enum"), "enum", "type", "any"), + "alternate": ObjType(_("alternate"), "alt", "type", "any"), } # Each of these provides a rST directive, @@ -464,6 +482,7 @@ class QAPIDomain(Domain): "module": QAPIModule, "command": QAPICommand, "enum": QAPIEnum, + "alternate": QAPIAlternate, } # These are all cross-reference roles; e.g. @@ -473,6 +492,7 @@ class QAPIDomain(Domain): "mod": QAPIXRefRole(), "cmd": QAPIXRefRole(), "enum": QAPIXRefRole(), + "alt": QAPIXRefRole(), # reference any data type (excludes modules, commands, events) "type": QAPIXRefRole(), "any": QAPIXRefRole(), # reference *any* type of QAPI object. From 6d5f6f69ca90557ec8a317a737e7b1e4ad1fff0d Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:19 -0400 Subject: [PATCH 0837/1179] docs/qapi-domain: add qapi:event directive Adds the .. qapi:event:: directive, object, and :qapi:event:`name` cross-referencing role. Adds the :memb type name: field list syntax for documenting event data members. As this syntax and phrasing will be shared with Structs and Unions as well, add the field list definition to a shared abstract class. As per usual, QAPI cross-referencing for types in the member field list will be added in a forthcoming commit. Signed-off-by: John Snow Message-ID: <20250311034303.75779-22-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 506ed92700d0..3ffb3eb72d1f 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -346,6 +346,27 @@ class QAPIAlternate(QAPIObject): ) +class QAPIObjectWithMembers(QAPIObject): + """Base class for Events/Structs/Unions""" + + doc_field_types = QAPIObject.doc_field_types.copy() + doc_field_types.extend( + [ + # :member type name: descr + TypedField( + "member", + label=_("Members"), + names=("memb",), + can_collapse=False, + ), + ] + ) + + +class QAPIEvent(QAPIObjectWithMembers): + """Description of a QAPI Event.""" + + class QAPIModule(QAPIDescription): """ Directive to mark description of a new module. @@ -472,6 +493,7 @@ class QAPIDomain(Domain): object_types: Dict[str, ObjType] = { "module": ObjType(_("module"), "mod", "any"), "command": ObjType(_("command"), "cmd", "any"), + "event": ObjType(_("event"), "event", "any"), "enum": ObjType(_("enum"), "enum", "type", "any"), "alternate": ObjType(_("alternate"), "alt", "type", "any"), } @@ -481,6 +503,7 @@ class QAPIDomain(Domain): directives = { "module": QAPIModule, "command": QAPICommand, + "event": QAPIEvent, "enum": QAPIEnum, "alternate": QAPIAlternate, } @@ -491,6 +514,7 @@ class QAPIDomain(Domain): roles = { "mod": QAPIXRefRole(), "cmd": QAPIXRefRole(), + "event": QAPIXRefRole(), "enum": QAPIXRefRole(), "alt": QAPIXRefRole(), # reference any data type (excludes modules, commands, events) From 3fe3349d232cdd8ffd3eef31f8c5ba8e7e08094a Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:20 -0400 Subject: [PATCH 0838/1179] docs/qapi-domain: add qapi:object directive Adds the .. qapi:object:: directive, object, and :qapi:obj:`name` cross-referencing role. This directive is meant to document both structs and unions. As per usual, QAPI cross-referencing for types in the member field list will be added in a forthcoming commit. Signed-off-by: John Snow Message-ID: <20250311034303.75779-23-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 3ffb3eb72d1f..b11300bc85de 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -367,6 +367,10 @@ class QAPIEvent(QAPIObjectWithMembers): """Description of a QAPI Event.""" +class QAPIJSONObject(QAPIObjectWithMembers): + """Description of a QAPI Object: structs and unions.""" + + class QAPIModule(QAPIDescription): """ Directive to mark description of a new module. @@ -495,6 +499,7 @@ class QAPIDomain(Domain): "command": ObjType(_("command"), "cmd", "any"), "event": ObjType(_("event"), "event", "any"), "enum": ObjType(_("enum"), "enum", "type", "any"), + "object": ObjType(_("object"), "obj", "type", "any"), "alternate": ObjType(_("alternate"), "alt", "type", "any"), } @@ -505,6 +510,7 @@ class QAPIDomain(Domain): "command": QAPICommand, "event": QAPIEvent, "enum": QAPIEnum, + "object": QAPIJSONObject, "alternate": QAPIAlternate, } @@ -516,6 +522,7 @@ class QAPIDomain(Domain): "cmd": QAPIXRefRole(), "event": QAPIXRefRole(), "enum": QAPIXRefRole(), + "obj": QAPIXRefRole(), # specifically structs and unions. "alt": QAPIXRefRole(), # reference any data type (excludes modules, commands, events) "type": QAPIXRefRole(), From 1a0c090a5bb3f7bd526224cd166703d6c80ab1ee Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:21 -0400 Subject: [PATCH 0839/1179] docs/qapi-domain: add :deprecated: directive option Although "deprecated" is a feature (and *will* appear in the features list), add a special :deprecated: option to generate an eye-catch that makes this information very hard to miss. The forthcoming Transmogrifier in qapidoc.py will add this option whenever it detects that the features list attached to a definition contains the "deprecated" entry. P.S., I outsourced the CSS ;) Signed-off-by: Harmonie Snow Signed-off-by: John Snow Message-ID: <20250311034303.75779-24-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx-static/theme_overrides.css | 25 +++++++++++++++++++++++++ docs/sphinx/qapi_domain.py | 26 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css index 965ecac54fdd..3765cab1b204 100644 --- a/docs/sphinx-static/theme_overrides.css +++ b/docs/sphinx-static/theme_overrides.css @@ -208,3 +208,28 @@ div[class^="highlight"] pre { color: inherit; } } + +/* QAPI domain theming */ + +.qapi-infopips { + margin-bottom: 1em; +} + +.qapi-infopip { + display: inline-block; + padding: 0em 0.5em 0em 0.5em; + margin: 0.25em; +} + +.qapi-deprecated { + background-color: #fffef5; + border: solid #fff176 6px; + font-weight: bold; + padding: 8px; + border-radius: 15px; + margin: 5px; +} + +.qapi-deprecated::before { + content: '⚠️ '; +} diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index b11300bc85de..b672ae6c504f 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -217,6 +217,7 @@ class QAPIObject(QAPIDescription): "module": directives.unchanged, # Override contextual module name # These are QAPI originals: "since": directives.unchanged, + "deprecated": directives.flag, } ) @@ -280,6 +281,31 @@ def handle_signature(self, sig: str, signode: desc_signature) -> Signature: return sig + def _add_infopips(self, contentnode: addnodes.desc_content) -> None: + # Add various eye-catches and things that go below the signature + # bar, but precede the user-defined content. + infopips = nodes.container() + infopips.attributes["classes"].append("qapi-infopips") + + def _add_pip(source: str, content: str, classname: str) -> None: + node = nodes.container(source) + node.append(nodes.Text(content)) + node.attributes["classes"].extend(["qapi-infopip", classname]) + infopips.append(node) + + if "deprecated" in self.options: + _add_pip( + ":deprecated:", + f"This {self.objtype} is deprecated.", + "qapi-deprecated", + ) + + if infopips.children: + contentnode.insert(0, infopips) + + def transform_content(self, content_node: addnodes.desc_content) -> None: + self._add_infopips(content_node) + class QAPICommand(QAPIObject): """Description of a QAPI Command.""" From d25808c2bc7921e5cd245111212ad7e3b6da3849 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:22 -0400 Subject: [PATCH 0840/1179] docs/qapi-domain: add :unstable: directive option Although "unstable" is a feature (and *will* appear in the features list), add a special :unstable: option to generate an eye-catch that makes this information very hard to miss. The forthcoming Transmogrifier in qapidoc.py will add this option whenever it detects that the features list attached to a definition contains the "unstable" entry. Signed-off-by: Harmonie Snow Signed-off-by: John Snow Message-ID: <20250311034303.75779-25-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx-static/theme_overrides.css | 6 +++++- docs/sphinx/qapi_domain.py | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css index 3765cab1b204..5f58f1d52461 100644 --- a/docs/sphinx-static/theme_overrides.css +++ b/docs/sphinx-static/theme_overrides.css @@ -221,7 +221,7 @@ div[class^="highlight"] pre { margin: 0.25em; } -.qapi-deprecated { +.qapi-deprecated,.qapi-unstable { background-color: #fffef5; border: solid #fff176 6px; font-weight: bold; @@ -230,6 +230,10 @@ div[class^="highlight"] pre { margin: 5px; } +.qapi-unstable::before { + content: '🚧 '; +} + .qapi-deprecated::before { content: '⚠️ '; } diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index b672ae6c504f..00fd11ebf791 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -218,6 +218,7 @@ class QAPIObject(QAPIDescription): # These are QAPI originals: "since": directives.unchanged, "deprecated": directives.flag, + "unstable": directives.flag, } ) @@ -300,6 +301,13 @@ def _add_pip(source: str, content: str, classname: str) -> None: "qapi-deprecated", ) + if "unstable" in self.options: + _add_pip( + ":unstable:", + f"This {self.objtype} is unstable/experimental.", + "qapi-unstable", + ) + if infopips.children: contentnode.insert(0, infopips) From 6a41330206e0df32b93c371b551f89d393eda2c3 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:23 -0400 Subject: [PATCH 0841/1179] docs/qapi-domain: add :ifcond: directive option Add a special :ifcond: option that allows us to annotate the definition-level conditionals. The syntax of the argument is currently undefined, but it's possible we can apply better formatting in the future. Currently, we just display the ifcond string as preformatted text. Signed-off-by: Harmonie Snow Signed-off-by: John Snow Message-ID: <20250311034303.75779-26-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx-static/theme_overrides.css | 13 +++++++++++++ docs/sphinx/qapi_domain.py | 23 +++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css index 5f58f1d52461..3fd326613d97 100644 --- a/docs/sphinx-static/theme_overrides.css +++ b/docs/sphinx-static/theme_overrides.css @@ -237,3 +237,16 @@ div[class^="highlight"] pre { .qapi-deprecated::before { content: '⚠️ '; } + +.qapi-ifcond::before { + /* gaze ye into the crystal ball to determine feature availability */ + content: '🔮 '; +} + +.qapi-ifcond { + background-color: #f9f5ff; + border: solid #dac2ff 6px; + padding: 8px; + border-radius: 15px; + margin: 5px; +} diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 00fd11ebf791..4531b5d85747 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -14,6 +14,7 @@ NamedTuple, Optional, Tuple, + Union, cast, ) @@ -217,6 +218,7 @@ class QAPIObject(QAPIDescription): "module": directives.unchanged, # Override contextual module name # These are QAPI originals: "since": directives.unchanged, + "ifcond": directives.unchanged, "deprecated": directives.flag, "unstable": directives.flag, } @@ -288,9 +290,14 @@ def _add_infopips(self, contentnode: addnodes.desc_content) -> None: infopips = nodes.container() infopips.attributes["classes"].append("qapi-infopips") - def _add_pip(source: str, content: str, classname: str) -> None: + def _add_pip( + source: str, content: Union[str, List[nodes.Node]], classname: str + ) -> None: node = nodes.container(source) - node.append(nodes.Text(content)) + if isinstance(content, str): + node.append(nodes.Text(content)) + else: + node.extend(content) node.attributes["classes"].extend(["qapi-infopip", classname]) infopips.append(node) @@ -308,6 +315,18 @@ def _add_pip(source: str, content: str, classname: str) -> None: "qapi-unstable", ) + if self.options.get("ifcond", ""): + ifcond = self.options["ifcond"] + _add_pip( + f":ifcond: {ifcond}", + [ + nodes.emphasis("", "Availability"), + nodes.Text(": "), + nodes.literal(ifcond, ifcond), + ], + "qapi-ifcond", + ) + if infopips.children: contentnode.insert(0, infopips) From ef137a224192cf145c8ae207fe96fd77b63596a9 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:24 -0400 Subject: [PATCH 0842/1179] docs/qapi-domain: add warnings for malformed field lists Normally, Sphinx will silently fall back to its standard field list processing if it doesn't match one of your defined fields. A lot of the time, that's not what we want - we want to be warned if we goof something up. For instance, the canonical argument field list form is: :arg type name: descr This form is captured by Sphinx and transformed so that the field label will become "Arguments:". It's possible to omit the type name and descr and still have it be processed correctly. However, if you omit the type name, Sphinx no longer recognizes it: :arg: this is not recognized. This will turn into an arbitrary field list entry whose label is "Arg:", and it otherwise silently fails. You may also see failures for doing things like using :values: instead of :value:, or :errors: instead of :error:, and so on. It's also case sensitive, and easy to trip up. Add a validator that guarantees all field list entries that are the direct child of an ObjectDescription use only recognized forms of field lists, and emit a warning (treated as error by default in most build configurations) whenever we detect one that is goofed up. However, there's still benefit to allowing arbitrary fields -- they are after all not a Sphinx invention, but perfectly normal docutils syntax. Create an allow list for known spellings we don't mind letting through, but warn against anything else. Signed-off-by: John Snow Message-ID: <20250311034303.75779-27-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/conf.py | 9 +++++ docs/sphinx/qapi_domain.py | 74 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 49d9de894c0c..a3f9fa63d946 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -153,6 +153,15 @@ with open(os.path.join(qemu_docdir, 'defs.rst.inc')) as f: rst_epilog += f.read() + +# Normally, the QAPI domain is picky about what field lists you use to +# describe a QAPI entity. If you'd like to use arbitrary additional +# fields in source documentation, add them here. +qapi_allowed_fields = { + "see also", +} + + # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 4531b5d85747..9fe006eef3e2 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -49,6 +49,19 @@ logger = logging.getLogger(__name__) +def _unpack_field( + field: nodes.Node, +) -> Tuple[nodes.field_name, nodes.field_body]: + """ + docutils helper: unpack a field node in a type-safe manner. + """ + assert isinstance(field, nodes.field) + assert len(field.children) == 2 + assert isinstance(field.children[0], nodes.field_name) + assert isinstance(field.children[1], nodes.field_body) + return (field.children[0], field.children[1]) + + class ObjectEntry(NamedTuple): docname: str node_id: str @@ -330,9 +343,64 @@ def _add_pip( if infopips.children: contentnode.insert(0, infopips) + def _validate_field(self, field: nodes.field) -> None: + """Validate field lists in this QAPI Object Description.""" + name, _ = _unpack_field(field) + allowed_fields = set(self.env.app.config.qapi_allowed_fields) + + field_label = name.astext() + if field_label in allowed_fields: + # Explicitly allowed field list name, OK. + return + + try: + # split into field type and argument (if provided) + # e.g. `:arg type name: descr` is + # field_type = "arg", field_arg = "type name". + field_type, field_arg = field_label.split(None, 1) + except ValueError: + # No arguments provided + field_type = field_label + field_arg = "" + + typemap = self.get_field_type_map() + if field_type in typemap: + # This is a special docfield, yet-to-be-processed. Catch + # correct names, but incorrect arguments. This mismatch WILL + # cause Sphinx to render this field incorrectly (without a + # warning), which is never what we want. + typedesc = typemap[field_type][0] + if typedesc.has_arg != bool(field_arg): + msg = f"docfield field list type {field_type!r} " + if typedesc.has_arg: + msg += "requires an argument." + else: + msg += "takes no arguments." + logger.warning(msg, location=field) + else: + # This is unrecognized entirely. It's valid rST to use + # arbitrary fields, but let's ensure the documentation + # writer has done this intentionally. + valid = ", ".join(sorted(set(typemap) | allowed_fields)) + msg = ( + f"Unrecognized field list name {field_label!r}.\n" + f"Valid fields for qapi:{self.objtype} are: {valid}\n" + "\n" + "If this usage is intentional, please add it to " + "'qapi_allowed_fields' in docs/conf.py." + ) + logger.warning(msg, location=field) + def transform_content(self, content_node: addnodes.desc_content) -> None: self._add_infopips(content_node) + # Validate field lists. + for child in content_node: + if isinstance(child, nodes.field_list): + for field in child.children: + assert isinstance(field, nodes.field) + self._validate_field(field) + class QAPICommand(QAPIObject): """Description of a QAPI Command.""" @@ -769,6 +837,12 @@ def resolve_any_xref( def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension("sphinx.directives") + app.add_config_value( + "qapi_allowed_fields", + set(), + "env", # Setting impacts parsing phase + types=set, + ) app.add_domain(QAPIDomain) return { From 03947c80ce81702ea0ba85933bd380ec978c7635 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:25 -0400 Subject: [PATCH 0843/1179] docs/qapi-domain: add type cross-refs to field lists This commit, finally, adds cross-referencing support to various field lists; modeled tightly after Sphinx's own Python domain code. Cross-referencing support is added to type names provided to :arg:, :memb:, :returns: and :choice:. :feat:, :error: and :value:, which do not take type names, do not support this syntax. The general syntax is simple: :arg TypeName ArgName: Lorem Ipsum ... The domain will transform TypeName into :qapi:type:`TypeName` in this basic case, and also apply the ``literal`` decoration to indicate that this is a type cross-reference. For optional arguments, the special "?" suffix is used. Because "*" has special meaning in rST that would cause parsing errors, we elect to use "?" instead. The special syntax processing strips this character from the end of any type name argument and will append ", optional" to the rendered output, applying the cross-reference only to the actual type name. The intent here is that the actual syntax in doc-blocks need not change; but e.g. qapidoc.py will need to process and transform "@arg foo lorem ipsum" into ":arg type? foo: lorem ipsum" based on the schema information. Therefore, nobody should ever actually witness this intermediate syntax unless they are writing manual documentation or the doc transmogrifier breaks. For array arguments, type names can similarly be surrounded by "[]", which are stripped off and then re-appended outside of the cross-reference. Signed-off-by: John Snow Message-ID: <20250311034303.75779-28-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 9fe006eef3e2..06fe78ce0bc3 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -2,6 +2,9 @@ QAPI domain extension. """ +# The best laid plans of mice and men, ... +# pylint: disable=too-many-lines + from __future__ import annotations from typing import ( @@ -116,6 +119,28 @@ def process_link( return title, target + def result_nodes( + self, + document: nodes.document, + env: BuildEnvironment, + node: Element, + is_ref: bool, + ) -> Tuple[List[nodes.Node], List[nodes.system_message]]: + + # node here is the pending_xref node (or whatever nodeclass was + # configured at XRefRole class instantiation time). + results: List[nodes.Node] = [node] + + if node.get("qapi:array"): + results.insert(0, nodes.literal("[", "[")) + results.append(nodes.literal("]", "]")) + + if node.get("qapi:optional"): + results.append(nodes.Text(", ")) + results.append(nodes.emphasis("?", "optional")) + + return results, [] + # Alias for the return of handle_signature(), which is used in several places. # (In the Python domain, this is Tuple[str, str] instead.) @@ -413,6 +438,7 @@ class QAPICommand(QAPIObject): "argument", label=_("Arguments"), names=("arg",), + typerolename="type", can_collapse=False, ), # :error: descr @@ -426,6 +452,7 @@ class QAPICommand(QAPIObject): GroupedField( "returnvalue", label=_("Return"), + rolename="type", names=("return",), can_collapse=True, ), @@ -461,6 +488,7 @@ class QAPIAlternate(QAPIObject): "alternative", label=_("Alternatives"), names=("alt",), + typerolename="type", can_collapse=False, ), ] @@ -478,6 +506,7 @@ class QAPIObjectWithMembers(QAPIObject): "member", label=_("Members"), names=("memb",), + typerolename="type", can_collapse=False, ), ] From 282c8d256efba2c348ddafaa6fcfa7cca371c309 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:26 -0400 Subject: [PATCH 0844/1179] docs/qapi-domain: add CSS styling Improve the general look and feel of generated QAPI docs. Attempt to limit line lengths to offer a more comfortable measure on maximized windows, and improve some margin and spacing for field lists. Signed-off-by: Harmonie Snow Signed-off-by: John Snow Message-ID: <20250311034303.75779-29-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx-static/theme_overrides.css | 56 +++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/docs/sphinx-static/theme_overrides.css b/docs/sphinx-static/theme_overrides.css index 3fd326613d97..b225bf706f50 100644 --- a/docs/sphinx-static/theme_overrides.css +++ b/docs/sphinx-static/theme_overrides.css @@ -18,8 +18,8 @@ h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend { .rst-content dl:not(.docutils) dt { border-top: none; - border-left: solid 3px #ccc; - background-color: #f0f0f0; + border-left: solid 5px #bcc6d2; + background-color: #eaedf1; color: black; } @@ -211,6 +211,18 @@ div[class^="highlight"] pre { /* QAPI domain theming */ +/* most content in a QAPI object definition should not eclipse about + 80ch, but nested field lists are explicitly exempt due to their + two-column nature */ +.qapi dd *:not(dl) { + max-width: 80ch; +} + +/* but the content column itself should still be less than ~80ch. */ +.qapi .field-list dd { + max-width: 80ch; +} + .qapi-infopips { margin-bottom: 1em; } @@ -250,3 +262,43 @@ div[class^="highlight"] pre { border-radius: 15px; margin: 5px; } + +/* code blocks */ +.qapi div[class^="highlight"] { + width: fit-content; + background-color: #fffafd; + border: 2px solid #ffe1f3; +} + +/* note, warning, etc. */ +.qapi .admonition { + width: fit-content; +} + +/* pad the top of the field-list so the text doesn't start directly at + the top border; primarily for the field list labels, but adjust the + field bodies as well for parity. */ +dl.field-list > dt:first-of-type, dl.field-list > dd:first-of-type { + padding-top: 0.3em; +} + +dl.field-list > dt:last-of-type, dl.field-list > dd:last-of-type { + padding-bottom: 0.3em; +} + +/* pad the field list labels so they don't crash into the border */ +dl.field-list > dt { + padding-left: 0.5em; + padding-right: 0.5em; +} + +/* Add a little padding between field list sections */ +dl.field-list > dd:not(:last-child) { + padding-bottom: 1em; +} + +/* Sphinx 3.x: unresolved xrefs */ +.rst-content *:not(a) > code.xref { + font-weight: 400; + color: #333333; +} From a1fe2cd443e6ae9d5211053d2ac0ca83264dc950 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:27 -0400 Subject: [PATCH 0845/1179] docs/qapi-domain: add XREF compatibility goop for Sphinx < 4.1 Sphinx < 4.1 handles cross-references ... differently. Factor out and isolate the compatibility goop we need to make cross references work properly in old versions of Sphinx. Yes, it's ugly. Yes, it works. No, I don't want to talk about it. Understand that this patch exists because of the overflowing love in my heart. Signed-off-by: John Snow Message-ID: <20250311034303.75779-30-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/compat.py | 136 +++++++++++++++++++++++++++++++++++-- docs/sphinx/qapi_domain.py | 23 ++++--- 2 files changed, 144 insertions(+), 15 deletions(-) diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py index 6bc698c5adae..f068d70388d0 100644 --- a/docs/sphinx/compat.py +++ b/docs/sphinx/compat.py @@ -2,14 +2,31 @@ Sphinx cross-version compatibility goop """ -from typing import Callable +import re +from typing import ( + Any, + Callable, + Optional, + Type, +) +from docutils import nodes from docutils.nodes import Element, Node, Text import sphinx -from sphinx import addnodes -from sphinx.util import nodes -from sphinx.util.docutils import SphinxDirective, switch_source_input +from sphinx import addnodes, util +from sphinx.environment import BuildEnvironment +from sphinx.roles import XRefRole +from sphinx.util import docfields +from sphinx.util.docutils import ( + ReferenceRole, + SphinxDirective, + switch_source_input, +) +from sphinx.util.typing import TextlikeNode + + +MAKE_XREF_WORKAROUND = sphinx.version_info[:3] < (4, 1, 0) SpaceNode: Callable[[str], Node] @@ -36,7 +53,7 @@ def nested_parse_with_titles( try: # Modern sphinx (6.2.0+) supports proper offsetting for # nested parse error context management - nodes.nested_parse_with_titles( + util.nodes.nested_parse_with_titles( directive.state, directive.content, content_node, @@ -45,6 +62,113 @@ def nested_parse_with_titles( except TypeError: # No content_offset argument. Fall back to SSI method. with switch_source_input(directive.state, directive.content): - nodes.nested_parse_with_titles( + util.nodes.nested_parse_with_titles( directive.state, directive.content, content_node ) + + +# ########################################### +# xref compatibility hacks for Sphinx < 4.1 # +# ########################################### + +# When we require >= Sphinx 4.1, the following function and the +# subsequent 3 compatibility classes can be removed. Anywhere in +# qapi_domain that uses one of these Compat* types can be switched to +# using the garden-variety lib-provided classes with no trickery. + + +def _compat_make_xref( # pylint: disable=unused-argument + self: sphinx.util.docfields.Field, + rolename: str, + domain: str, + target: str, + innernode: Type[TextlikeNode] = addnodes.literal_emphasis, + contnode: Optional[Node] = None, + env: Optional[BuildEnvironment] = None, + inliner: Any = None, + location: Any = None, +) -> Node: + """ + Compatibility workaround for Sphinx versions prior to 4.1.0. + + Older sphinx versions do not use the domain's XRefRole for parsing + and formatting cross-references, so we need to perform this magick + ourselves to avoid needing to write the parser/formatter in two + separate places. + + This workaround isn't brick-for-brick compatible with modern Sphinx + versions, because we do not have access to the parent directive's + state during this parsing like we do in more modern versions. + + It's no worse than what pre-Sphinx 4.1.0 does, so... oh well! + """ + + # Yes, this function is gross. Pre-4.1 support is a miracle. + # pylint: disable=too-many-locals + + assert env + # Note: Sphinx's own code ignores the type warning here, too. + if not rolename: + return contnode or innernode(target, target) # type: ignore[call-arg] + + # Get the role instance, but don't *execute it* - we lack the + # correct state to do so. Instead, we'll just use its public + # methods to do our reference formatting, and emulate the rest. + role = env.get_domain(domain).roles[rolename] + assert isinstance(role, XRefRole) + + # XRefRole features not supported by this compatibility shim; + # these were not supported in Sphinx 3.x either, so nothing of + # value is really lost. + assert not target.startswith("!") + assert not re.match(ReferenceRole.explicit_title_re, target) + assert not role.lowercase + assert not role.fix_parens + + # Code below based mostly on sphinx.roles.XRefRole; run() and + # create_xref_node() + options = { + "refdoc": env.docname, + "refdomain": domain, + "reftype": rolename, + "refexplicit": False, + "refwarn": role.warn_dangling, + } + refnode = role.nodeclass(target, **options) + title, target = role.process_link(env, refnode, False, target, target) + refnode["reftarget"] = target + classes = ["xref", domain, f"{domain}-{rolename}"] + refnode += role.innernodeclass(target, title, classes=classes) + + # This is the very gross part of the hack. Normally, + # result_nodes takes a document object to which we would pass + # self.inliner.document. Prior to Sphinx 4.1, we don't *have* an + # inliner to pass, so we have nothing to pass here. However, the + # actual implementation of role.result_nodes in this case + # doesn't actually use that argument, so this winds up being + # ... fine. Rest easy at night knowing this code only runs under + # old versions of Sphinx, so at least it won't change in the + # future on us and lead to surprising new failures. + # Gross, I know. + result_nodes, _messages = role.result_nodes( + None, # type: ignore + env, + refnode, + is_ref=True, + ) + return nodes.inline(target, "", *result_nodes) + + +class CompatField(docfields.Field): + if MAKE_XREF_WORKAROUND: + make_xref = _compat_make_xref + + +class CompatGroupedField(docfields.GroupedField): + if MAKE_XREF_WORKAROUND: + make_xref = _compat_make_xref + + +class CompatTypedField(docfields.TypedField): + if MAKE_XREF_WORKAROUND: + make_xref = _compat_make_xref diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 06fe78ce0bc3..3b1490e29a1c 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -24,7 +24,13 @@ from docutils import nodes from docutils.parsers.rst import directives -from compat import KeywordNode, SpaceNode +from compat import ( + CompatField, + CompatGroupedField, + CompatTypedField, + KeywordNode, + SpaceNode, +) from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref from sphinx.directives import ObjectDescription @@ -37,7 +43,6 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging -from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.nodes import make_id, make_refnode @@ -264,7 +269,7 @@ class QAPIObject(QAPIDescription): doc_field_types = [ # :feat name: descr - GroupedField( + CompatGroupedField( "feature", label=_("Features"), names=("feat",), @@ -434,7 +439,7 @@ class QAPICommand(QAPIObject): doc_field_types.extend( [ # :arg TypeName ArgName: descr - TypedField( + CompatTypedField( "argument", label=_("Arguments"), names=("arg",), @@ -442,14 +447,14 @@ class QAPICommand(QAPIObject): can_collapse=False, ), # :error: descr - Field( + CompatField( "error", label=_("Errors"), names=("error", "errors"), has_arg=False, ), # :return TypeName: descr - GroupedField( + CompatGroupedField( "returnvalue", label=_("Return"), rolename="type", @@ -467,7 +472,7 @@ class QAPIEnum(QAPIObject): doc_field_types.extend( [ # :value name: descr - GroupedField( + CompatGroupedField( "value", label=_("Values"), names=("value",), @@ -484,7 +489,7 @@ class QAPIAlternate(QAPIObject): doc_field_types.extend( [ # :alt type name: descr - TypedField( + CompatTypedField( "alternative", label=_("Alternatives"), names=("alt",), @@ -502,7 +507,7 @@ class QAPIObjectWithMembers(QAPIObject): doc_field_types.extend( [ # :member type name: descr - TypedField( + CompatTypedField( "member", label=_("Members"), names=("memb",), From d48a8f8de3f2c5609ecd362f1d1c5c1ba60161fc Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:28 -0400 Subject: [PATCH 0846/1179] docs/qapi-domain: warn when QAPI domain xrefs fail to resolve This patch adds a warning (which is a build failure under our current build settings) whenever a QAPI cross-reference fails to resolve. This applies to any cross-references of the form :qapi:{role}:`foo`, which covers all of the automatically generated references by the qapi domain, and any such references that are manually written into the documentation rst files. Cross-references of the form `foo` do not use this system, but are already configured to issue a warning (Again, a build failure) if the cross-reference isn't found anywhere. Adds warnings that look like the following: docs/qapi/index.rst:48: WARNING: qapi:type reference target not found: 'footype' [ref.qapi] docs/qapi/index.rst:50: WARNING: qapi:mod reference target not found: 'foomod' [ref.qapi] Signed-off-by: John Snow Message-ID: <20250311034303.75779-31-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 3b1490e29a1c..b23db1eba267 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -830,6 +830,29 @@ def resolve_xref( matches = self.find_obj(modname, target, typ) if not matches: + # Normally, we could pass warn_dangling=True to QAPIXRefRole(), + # but that will trigger on references to these built-in types, + # which we'd like to ignore instead. + + # Take care of that warning here instead, so long as the + # reference isn't to one of our built-in core types. + if target not in ( + "string", + "number", + "int", + "boolean", + "null", + "value", + "q_empty", + ): + logger.warning( + __("qapi:%s reference target not found: %r"), + typ, + target, + type="ref", + subtype="qapi", + location=node, + ) return None if len(matches) > 1: From 707f2bbb7899297884095a76a1237c8dbfce09fd Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:29 -0400 Subject: [PATCH 0847/1179] docs/qapi-domain: Fix error context reporting in Sphinx 5.x and 6.x Sphinx 5.3.0 to Sphinx 6.2.0 has a bug where nested content in an ObjectDescription content block has its error position reported incorrectly due to an oversight when they added nested section support to this directive. (This bug is present in Sphinx's own Python and C domains; test it yourself by creating a py:func directive and creating a syntax error in the directive's content block. The reporting will be incorrect.) To avoid overriding and re-implementing the entirety of the run() method, a workaround is employed where we parse the content block ourselves in before_content(), then null the content block to make Sphinx's own parsing a no-op. Then, in transform_content (which occurs after Sphinx's nested parse), we simply swap our own parsed content tree back in for Sphinx's. It appears a little tricky, but it's the nicest solution I can find. Signed-off-by: John Snow Message-ID: <20250311034303.75779-32-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/compat.py | 56 ++++++++++++++++++++++++++++++++++++++ docs/sphinx/qapi_domain.py | 15 ++++++---- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/compat.py b/docs/sphinx/compat.py index f068d70388d0..9cf7fe006e44 100644 --- a/docs/sphinx/compat.py +++ b/docs/sphinx/compat.py @@ -4,6 +4,7 @@ import re from typing import ( + TYPE_CHECKING, Any, Callable, Optional, @@ -12,9 +13,11 @@ from docutils import nodes from docutils.nodes import Element, Node, Text +from docutils.statemachine import StringList import sphinx from sphinx import addnodes, util +from sphinx.directives import ObjectDescription from sphinx.environment import BuildEnvironment from sphinx.roles import XRefRole from sphinx.util import docfields @@ -172,3 +175,56 @@ class CompatGroupedField(docfields.GroupedField): class CompatTypedField(docfields.TypedField): if MAKE_XREF_WORKAROUND: make_xref = _compat_make_xref + + +# ################################################################ +# Nested parsing error location fix for Sphinx 5.3.0 < x < 6.2.0 # +# ################################################################ + +# When we require Sphinx 4.x, the TYPE_CHECKING hack where we avoid +# subscripting ObjectDescription at runtime can be removed in favor of +# just always subscripting the class. + +# When we require Sphinx > 6.2.0, the rest of this compatibility hack +# can be dropped and QAPIObject can just inherit directly from +# ObjectDescription[Signature]. + +SOURCE_LOCATION_FIX = (5, 3, 0) <= sphinx.version_info[:3] < (6, 2, 0) + +Signature = str + + +if TYPE_CHECKING: + _BaseClass = ObjectDescription[Signature] +else: + _BaseClass = ObjectDescription + + +class ParserFix(_BaseClass): + + _temp_content: StringList + _temp_offset: int + _temp_node: Optional[addnodes.desc_content] + + def before_content(self) -> None: + # Work around a sphinx bug and parse the content ourselves. + self._temp_content = self.content + self._temp_offset = self.content_offset + self._temp_node = None + + if SOURCE_LOCATION_FIX: + self._temp_node = addnodes.desc_content() + self.state.nested_parse( + self.content, self.content_offset, self._temp_node + ) + # Sphinx will try to parse the content block itself, + # Give it nothingness to parse instead. + self.content = StringList() + self.content_offset = 0 + + def transform_content(self, content_node: addnodes.desc_content) -> None: + # Sphinx workaround: Inject our parsed content and restore state. + if self._temp_node: + content_node += self._temp_node.children + self.content = self._temp_content + self.content_offset = self._temp_offset diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index b23db1eba267..ca3f3a7e2d55 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -29,6 +29,8 @@ CompatGroupedField, CompatTypedField, KeywordNode, + ParserFix, + Signature, SpaceNode, ) from sphinx import addnodes @@ -147,12 +149,7 @@ def result_nodes( return results, [] -# Alias for the return of handle_signature(), which is used in several places. -# (In the Python domain, this is Tuple[str, str] instead.) -Signature = str - - -class QAPIDescription(ObjectDescription[Signature]): +class QAPIDescription(ParserFix): """ Generic QAPI description. @@ -422,6 +419,10 @@ def _validate_field(self, field: nodes.field) -> None: logger.warning(msg, location=field) def transform_content(self, content_node: addnodes.desc_content) -> None: + # This hook runs after before_content and the nested parse, but + # before the DocFieldTransformer is executed. + super().transform_content(content_node) + self._add_infopips(content_node) # Validate field lists. @@ -519,10 +520,12 @@ class QAPIObjectWithMembers(QAPIObject): class QAPIEvent(QAPIObjectWithMembers): + # pylint: disable=too-many-ancestors """Description of a QAPI Event.""" class QAPIJSONObject(QAPIObjectWithMembers): + # pylint: disable=too-many-ancestors """Description of a QAPI Object: structs and unions.""" From faeacf858bd9529cab10a13ff9d2137c8f2ae17c Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:30 -0400 Subject: [PATCH 0848/1179] qapi/parser: adjust info location for doc body section Instead of using the info object for the doc block as a whole (which always points to the very first line of the block), update the info pointer for each call to ensure_untagged_section when the existing section is otherwise empty. This way, Sphinx error information will match precisely to where the text actually starts. For example, this patch will move the info pointer for the "Hello!" untagged section ... > ## <-- from here ... > # Hello! <-- ... to here. > ## This doesn't seem to improve error reporting now. It will with the forthcoming QAPI doc transmogrifier. If I stick bad rST into qapi/block-core.json like this: > ## > # @SnapshotInfo: > # > +# rST syntax error: *ahh! > +# > # @id: unique shapshot id > # > # @name: user chosen name The existing code's error message will point to the beginning of the doc comment, which is less than helpful. The transmogrifier's message will point to the erroneous line, but to accomplish this, it needs this patch. Signed-off-by: John Snow Message-ID: <20250311034303.75779-33-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/parser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 64f0bb824aed..97def9f0e4fb 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -686,7 +686,11 @@ def end(self) -> None: def ensure_untagged_section(self, info: QAPISourceInfo) -> None: if self.all_sections and not self.all_sections[-1].tag: # extend current section - self.all_sections[-1].text += '\n' + section = self.all_sections[-1] + if not section.text: + # Section is empty so far; update info to start *here*. + section.info = info + section.text += '\n' return # start new section section = self.Section(info) From 323c668934a650673548088c6718c633b57b6ce5 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:31 -0400 Subject: [PATCH 0849/1179] qapi: clean up encoding of section kinds We have several kinds of sections, and to tell them apart, we use Section attribute @tag and also the section object's Python type: type @tag untagged Section None @foo: ArgSection 'foo' Returns: Section 'Returns' Errors: Section 'Errors' Since: Section 'Since' TODO: Section 'TODO' Note: * @foo can be a member or a feature description, depending on context. * tag == 'Since' can be a Since: section or a member or feature description. If it's a Section, it's the former, and if it's an ArgSection, it's the latter. Clean this up as follows. Move the member or feature name to new ArgSection attribute @name, and replace @tag by enum @kind like this: type kind name untagged Section PLAIN @foo: ArgSection MEMBER 'foo' if member or argument ArgSection FEATURE 'foo' if feature Returns: Section RETURNS Errors: Section ERRORS Since: Section SINCE TODO: Section TODO The qapi-schema tests are updated to account for the new section names; "TODO" becomes "Todo" and `None` becomes "Plain" there. Signed-off-by: John Snow Message-ID: <20250311034303.75779-34-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 7 +-- scripts/qapi/parser.py | 97 ++++++++++++++++++++++++---------- tests/qapi-schema/doc-good.out | 10 ++-- tests/qapi-schema/test-qapi.py | 2 +- 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 61997fd21afe..d622398f1daa 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -35,6 +35,7 @@ from docutils.statemachine import ViewList from qapi.error import QAPIError, QAPISemError from qapi.gen import QAPISchemaVisitor +from qapi.parser import QAPIDoc from qapi.schema import QAPISchema from sphinx import addnodes @@ -258,11 +259,11 @@ def _nodes_for_sections(self, doc): """Return list of doctree nodes for additional sections""" nodelist = [] for section in doc.sections: - if section.tag and section.tag == 'TODO': + if section.kind == QAPIDoc.Kind.TODO: # Hide TODO: sections continue - if not section.tag: + if section.kind == QAPIDoc.Kind.PLAIN: # Sphinx cannot handle sectionless titles; # Instead, just append the results to the prior section. container = nodes.container() @@ -270,7 +271,7 @@ def _nodes_for_sections(self, doc): nodelist += container.children continue - snode = self._make_section(section.tag) + snode = self._make_section(section.kind.name.title()) self._parse_text_into_node(dedent(section.text), snode) nodelist.append(snode) return nodelist diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 97def9f0e4fb..94d5322f8af3 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -14,6 +14,7 @@ # This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. +import enum import os import re from typing import ( @@ -574,7 +575,10 @@ def get_doc(self) -> 'QAPIDoc': ) raise QAPIParseError(self, emsg) - doc.new_tagged_section(self.info, match.group(1)) + doc.new_tagged_section( + self.info, + QAPIDoc.Kind.from_string(match.group(1)) + ) text = line[match.end():] if text: doc.append_line(text) @@ -585,7 +589,7 @@ def get_doc(self) -> 'QAPIDoc': self, "unexpected '=' markup in definition documentation") else: - # tag-less paragraph + # plain paragraph doc.ensure_untagged_section(self.info) doc.append_line(line) line = self.get_doc_paragraph(doc) @@ -634,14 +638,33 @@ class QAPIDoc: Free-form documentation blocks consist only of a body section. """ + class Kind(enum.Enum): + PLAIN = 0 + MEMBER = 1 + FEATURE = 2 + RETURNS = 3 + ERRORS = 4 + SINCE = 5 + TODO = 6 + + @staticmethod + def from_string(kind: str) -> 'QAPIDoc.Kind': + return QAPIDoc.Kind[kind.upper()] + + def __str__(self) -> str: + return self.name.title() + class Section: # pylint: disable=too-few-public-methods - def __init__(self, info: QAPISourceInfo, - tag: Optional[str] = None): + def __init__( + self, + info: QAPISourceInfo, + kind: 'QAPIDoc.Kind', + ): # section source info, i.e. where it begins self.info = info - # section tag, if any ('Returns', '@name', ...) - self.tag = tag + # section kind + self.kind = kind # section text without tag self.text = '' @@ -649,8 +672,14 @@ def append_line(self, line: str) -> None: self.text += line + '\n' class ArgSection(Section): - def __init__(self, info: QAPISourceInfo, tag: str): - super().__init__(info, tag) + def __init__( + self, + info: QAPISourceInfo, + kind: 'QAPIDoc.Kind', + name: str + ): + super().__init__(info, kind) + self.name = name self.member: Optional['QAPISchemaMember'] = None def connect(self, member: 'QAPISchemaMember') -> None: @@ -662,7 +691,9 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None): # definition doc's symbol, None for free-form doc self.symbol: Optional[str] = symbol # the sections in textual order - self.all_sections: List[QAPIDoc.Section] = [QAPIDoc.Section(info)] + self.all_sections: List[QAPIDoc.Section] = [ + QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN) + ] # the body section self.body: Optional[QAPIDoc.Section] = self.all_sections[0] # dicts mapping parameter/feature names to their description @@ -679,12 +710,14 @@ def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None): def end(self) -> None: for section in self.all_sections: section.text = section.text.strip('\n') - if section.tag is not None and section.text == '': + if section.kind != QAPIDoc.Kind.PLAIN and section.text == '': raise QAPISemError( - section.info, "text required after '%s:'" % section.tag) + section.info, "text required after '%s:'" % section.kind) def ensure_untagged_section(self, info: QAPISourceInfo) -> None: - if self.all_sections and not self.all_sections[-1].tag: + kind = QAPIDoc.Kind.PLAIN + + if self.all_sections and self.all_sections[-1].kind == kind: # extend current section section = self.all_sections[-1] if not section.text: @@ -692,46 +725,56 @@ def ensure_untagged_section(self, info: QAPISourceInfo) -> None: section.info = info section.text += '\n' return + # start new section - section = self.Section(info) + section = self.Section(info, kind) self.sections.append(section) self.all_sections.append(section) - def new_tagged_section(self, info: QAPISourceInfo, tag: str) -> None: - section = self.Section(info, tag) - if tag == 'Returns': + def new_tagged_section( + self, + info: QAPISourceInfo, + kind: 'QAPIDoc.Kind', + ) -> None: + section = self.Section(info, kind) + if kind == QAPIDoc.Kind.RETURNS: if self.returns: raise QAPISemError( - info, "duplicated '%s' section" % tag) + info, "duplicated '%s' section" % kind) self.returns = section - elif tag == 'Errors': + elif kind == QAPIDoc.Kind.ERRORS: if self.errors: raise QAPISemError( - info, "duplicated '%s' section" % tag) + info, "duplicated '%s' section" % kind) self.errors = section - elif tag == 'Since': + elif kind == QAPIDoc.Kind.SINCE: if self.since: raise QAPISemError( - info, "duplicated '%s' section" % tag) + info, "duplicated '%s' section" % kind) self.since = section self.sections.append(section) self.all_sections.append(section) - def _new_description(self, info: QAPISourceInfo, name: str, - desc: Dict[str, ArgSection]) -> None: + def _new_description( + self, + info: QAPISourceInfo, + name: str, + kind: 'QAPIDoc.Kind', + desc: Dict[str, ArgSection] + ) -> None: if not name: raise QAPISemError(info, "invalid parameter name") if name in desc: raise QAPISemError(info, "'%s' parameter name duplicated" % name) - section = self.ArgSection(info, '@' + name) + section = self.ArgSection(info, kind, name) self.all_sections.append(section) desc[name] = section def new_argument(self, info: QAPISourceInfo, name: str) -> None: - self._new_description(info, name, self.args) + self._new_description(info, name, QAPIDoc.Kind.MEMBER, self.args) def new_feature(self, info: QAPISourceInfo, name: str) -> None: - self._new_description(info, name, self.features) + self._new_description(info, name, QAPIDoc.Kind.FEATURE, self.features) def append_line(self, line: str) -> None: self.all_sections[-1].append_line(line) @@ -744,7 +787,7 @@ def connect_member(self, member: 'QAPISchemaMember') -> None: "%s '%s' lacks documentation" % (member.role, member.name)) self.args[member.name] = QAPIDoc.ArgSection( - self.info, '@' + member.name) + self.info, QAPIDoc.Kind.MEMBER, member.name) self.args[member.name].connect(member) def connect_feature(self, feature: 'QAPISchemaFeature') -> None: diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index 0a9da3efdeb9..5773f1dd6d62 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -113,7 +113,7 @@ The _one_ {and only}, description on the same line Also _one_ {and only} feature=enum-member-feat a member feature - section=None + section=Plain @two is undocumented doc symbol=Base body= @@ -171,15 +171,15 @@ description starts on the same line a feature feature=cmd-feat2 another feature - section=None + section=Plain .. note:: @arg3 is undocumented section=Returns @Object section=Errors some - section=TODO + section=Todo frobnicate - section=None + section=Plain .. admonition:: Notes - Lorem ipsum dolor sit amet @@ -212,7 +212,7 @@ If you're bored enough to read this, go see a video of boxed cats a feature feature=cmd-feat2 another feature - section=None + section=Plain .. qmp-example:: -> "this example" diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 8fe951c88034..4be930228cc3 100755 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -122,7 +122,7 @@ def test_frontend(fname): for feat, section in doc.features.items(): print(' feature=%s\n%s' % (feat, section.text)) for section in doc.sections: - print(' section=%s\n%s' % (section.tag, section.text)) + print(' section=%s\n%s' % (section.kind, section.text)) def open_test_result(dir_name, file_name, update): From d7ca9a3a4c55cba910a3556544e90aadd92c4efe Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:32 -0400 Subject: [PATCH 0850/1179] qapi/schema: add __repr__ to QAPIDoc.Section Makes debugging far more pleasant when you can just print(section) and get something reasonable to display. Signed-off-by: John Snow Message-ID: <20250311034303.75779-35-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 94d5322f8af3..11c11bb09e5e 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -668,6 +668,9 @@ def __init__( # section text without tag self.text = '' + def __repr__(self) -> str: + return f"" + def append_line(self, line: str) -> None: self.text += line + '\n' From 7d125c339d32cbef65404b621d3ca02cc67f3090 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:33 -0400 Subject: [PATCH 0851/1179] docs/qapidoc: add transmogrifier stub This commit adds a stubbed option to the qapi-doc directive that opts-in to the new rST generator; the implementation of which will follow in subsequent commits. Once all QAPI documents have been converted, this option and the old qapidoc implementation can be dropped. Note that moving code outside of the try...except block has no impact because the code moved outside of that block does not ever raise a QAPIError. Signed-off-by: John Snow Message-ID: <20250311034303.75779-36-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index d622398f1daa..dc72f3fd3f30 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -452,9 +452,9 @@ def _parse_text_into_node(self, doctext, node): rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line) self._sphinx_directive.do_parse(rstlist, node) - def get_document_nodes(self): - """Return the list of docutils nodes which make up the document""" - return self._top_node.children + def get_document_node(self): + """Return the root docutils node which makes up the document""" + return self._top_node # Turn the black formatter on for the rest of the file. @@ -503,7 +503,10 @@ class QAPIDocDirective(NestedDirective): required_argument = 1 optional_arguments = 1 - option_spec = {"qapifile": directives.unchanged_required} + option_spec = { + "qapifile": directives.unchanged_required, + "transmogrify": directives.flag, + } has_content = False def new_serialno(self): @@ -511,10 +514,24 @@ def new_serialno(self): env = self.state.document.settings.env return "qapidoc-%d" % env.new_serialno("qapidoc") + def transmogrify(self, schema) -> nodes.Element: + raise NotImplementedError + + def legacy(self, schema) -> nodes.Element: + vis = QAPISchemaGenRSTVisitor(self) + vis.visit_begin(schema) + for doc in schema.docs: + if doc.symbol: + vis.symbol(doc, schema.lookup_entity(doc.symbol)) + else: + vis.freeform(doc) + return vis.get_document_node() + def run(self): env = self.state.document.settings.env qapifile = env.config.qapidoc_srctree + "/" + self.arguments[0] qapidir = os.path.dirname(qapifile) + transmogrify = "transmogrify" in self.options try: schema = QAPISchema(qapifile) @@ -522,20 +539,18 @@ def run(self): # First tell Sphinx about all the schema files that the # output documentation depends on (including 'qapifile' itself) schema.visit(QAPISchemaGenDepVisitor(env, qapidir)) - - vis = QAPISchemaGenRSTVisitor(self) - vis.visit_begin(schema) - for doc in schema.docs: - if doc.symbol: - vis.symbol(doc, schema.lookup_entity(doc.symbol)) - else: - vis.freeform(doc) - return vis.get_document_nodes() except QAPIError as err: # Launder QAPI parse errors into Sphinx extension errors # so they are displayed nicely to the user raise ExtensionError(str(err)) from err + if transmogrify: + contentnode = self.transmogrify(schema) + else: + contentnode = self.legacy(schema) + + return contentnode.children + class QMPExample(CodeBlock, NestedDirective): """ From 7640410d5c2f7a37c8b6409f2d288807c99afd1e Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:34 -0400 Subject: [PATCH 0852/1179] docs/qapidoc: split old implementation into qapidoc_legacy.py This is being done primarily to be able to type check and delint the new implementation without needing to worry about fixing up the old implementation. I'm adding the new implementation into the existing file instead of into a new file so that when the dust settles, qapidoc.py will contain the full history of development on this generative module. This patch *should* be pure motion, give or take the import statements. Signed-off-by: John Snow Message-ID: <20250311034303.75779-37-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 420 +------------------------------- docs/sphinx/qapidoc_legacy.py | 439 ++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+), 418 deletions(-) create mode 100644 docs/sphinx/qapidoc_legacy.py diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index dc72f3fd3f30..f4abf42e7bf6 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -25,19 +25,16 @@ """ import os -import re import sys -import textwrap from typing import List from docutils import nodes from docutils.parsers.rst import Directive, directives -from docutils.statemachine import ViewList -from qapi.error import QAPIError, QAPISemError +from qapi.error import QAPIError from qapi.gen import QAPISchemaVisitor -from qapi.parser import QAPIDoc from qapi.schema import QAPISchema +from qapidoc_legacy import QAPISchemaGenRSTVisitor from sphinx import addnodes from sphinx.directives.code import CodeBlock from sphinx.errors import ExtensionError @@ -48,419 +45,6 @@ __version__ = "1.0" -def dedent(text: str) -> str: - # Adjust indentation to make description text parse as paragraph. - - lines = text.splitlines(True) - if re.match(r"\s+", lines[0]): - # First line is indented; description started on the line after - # the name. dedent the whole block. - return textwrap.dedent(text) - - # Descr started on same line. Dedent line 2+. - return lines[0] + textwrap.dedent("".join(lines[1:])) - - -# Disable black auto-formatter until re-enabled: -# fmt: off - - -class QAPISchemaGenRSTVisitor(QAPISchemaVisitor): - """A QAPI schema visitor which generates docutils/Sphinx nodes - - This class builds up a tree of docutils/Sphinx nodes corresponding - to documentation for the various QAPI objects. To use it, first - create a QAPISchemaGenRSTVisitor object, and call its - visit_begin() method. Then you can call one of the two methods - 'freeform' (to add documentation for a freeform documentation - chunk) or 'symbol' (to add documentation for a QAPI symbol). These - will cause the visitor to build up the tree of document - nodes. Once you've added all the documentation via 'freeform' and - 'symbol' method calls, you can call 'get_document_nodes' to get - the final list of document nodes (in a form suitable for returning - from a Sphinx directive's 'run' method). - """ - def __init__(self, sphinx_directive): - self._cur_doc = None - self._sphinx_directive = sphinx_directive - self._top_node = nodes.section() - self._active_headings = [self._top_node] - - def _make_dlitem(self, term, defn): - """Return a dlitem node with the specified term and definition. - - term should be a list of Text and literal nodes. - defn should be one of: - - a string, which will be handed to _parse_text_into_node - - a list of Text and literal nodes, which will be put into - a paragraph node - """ - dlitem = nodes.definition_list_item() - dlterm = nodes.term('', '', *term) - dlitem += dlterm - if defn: - dldef = nodes.definition() - if isinstance(defn, list): - dldef += nodes.paragraph('', '', *defn) - else: - self._parse_text_into_node(defn, dldef) - dlitem += dldef - return dlitem - - def _make_section(self, title): - """Return a section node with optional title""" - section = nodes.section(ids=[self._sphinx_directive.new_serialno()]) - if title: - section += nodes.title(title, title) - return section - - def _nodes_for_ifcond(self, ifcond, with_if=True): - """Return list of Text, literal nodes for the ifcond - - Return a list which gives text like ' (If: condition)'. - If with_if is False, we don't return the "(If: " and ")". - """ - - doc = ifcond.docgen() - if not doc: - return [] - doc = nodes.literal('', doc) - if not with_if: - return [doc] - - nodelist = [nodes.Text(' ('), nodes.strong('', 'If: ')] - nodelist.append(doc) - nodelist.append(nodes.Text(')')) - return nodelist - - def _nodes_for_one_member(self, member): - """Return list of Text, literal nodes for this member - - Return a list of doctree nodes which give text like - 'name: type (optional) (If: ...)' suitable for use as the - 'term' part of a definition list item. - """ - term = [nodes.literal('', member.name)] - if member.type.doc_type(): - term.append(nodes.Text(': ')) - term.append(nodes.literal('', member.type.doc_type())) - if member.optional: - term.append(nodes.Text(' (optional)')) - if member.ifcond.is_present(): - term.extend(self._nodes_for_ifcond(member.ifcond)) - return term - - def _nodes_for_variant_when(self, branches, variant): - """Return list of Text, literal nodes for variant 'when' clause - - Return a list of doctree nodes which give text like - 'when tagname is variant (If: ...)' suitable for use in - the 'branches' part of a definition list. - """ - term = [nodes.Text(' when '), - nodes.literal('', branches.tag_member.name), - nodes.Text(' is '), - nodes.literal('', '"%s"' % variant.name)] - if variant.ifcond.is_present(): - term.extend(self._nodes_for_ifcond(variant.ifcond)) - return term - - def _nodes_for_members(self, doc, what, base=None, branches=None): - """Return list of doctree nodes for the table of members""" - dlnode = nodes.definition_list() - for section in doc.args.values(): - term = self._nodes_for_one_member(section.member) - # TODO drop fallbacks when undocumented members are outlawed - if section.text: - defn = dedent(section.text) - else: - defn = [nodes.Text('Not documented')] - - dlnode += self._make_dlitem(term, defn) - - if base: - dlnode += self._make_dlitem([nodes.Text('The members of '), - nodes.literal('', base.doc_type())], - None) - - if branches: - for v in branches.variants: - if v.type.name == 'q_empty': - continue - assert not v.type.is_implicit() - term = [nodes.Text('The members of '), - nodes.literal('', v.type.doc_type())] - term.extend(self._nodes_for_variant_when(branches, v)) - dlnode += self._make_dlitem(term, None) - - if not dlnode.children: - return [] - - section = self._make_section(what) - section += dlnode - return [section] - - def _nodes_for_enum_values(self, doc): - """Return list of doctree nodes for the table of enum values""" - seen_item = False - dlnode = nodes.definition_list() - for section in doc.args.values(): - termtext = [nodes.literal('', section.member.name)] - if section.member.ifcond.is_present(): - termtext.extend(self._nodes_for_ifcond(section.member.ifcond)) - # TODO drop fallbacks when undocumented members are outlawed - if section.text: - defn = dedent(section.text) - else: - defn = [nodes.Text('Not documented')] - - dlnode += self._make_dlitem(termtext, defn) - seen_item = True - - if not seen_item: - return [] - - section = self._make_section('Values') - section += dlnode - return [section] - - def _nodes_for_arguments(self, doc, arg_type): - """Return list of doctree nodes for the arguments section""" - if arg_type and not arg_type.is_implicit(): - assert not doc.args - section = self._make_section('Arguments') - dlnode = nodes.definition_list() - dlnode += self._make_dlitem( - [nodes.Text('The members of '), - nodes.literal('', arg_type.name)], - None) - section += dlnode - return [section] - - return self._nodes_for_members(doc, 'Arguments') - - def _nodes_for_features(self, doc): - """Return list of doctree nodes for the table of features""" - seen_item = False - dlnode = nodes.definition_list() - for section in doc.features.values(): - dlnode += self._make_dlitem( - [nodes.literal('', section.member.name)], dedent(section.text)) - seen_item = True - - if not seen_item: - return [] - - section = self._make_section('Features') - section += dlnode - return [section] - - def _nodes_for_sections(self, doc): - """Return list of doctree nodes for additional sections""" - nodelist = [] - for section in doc.sections: - if section.kind == QAPIDoc.Kind.TODO: - # Hide TODO: sections - continue - - if section.kind == QAPIDoc.Kind.PLAIN: - # Sphinx cannot handle sectionless titles; - # Instead, just append the results to the prior section. - container = nodes.container() - self._parse_text_into_node(section.text, container) - nodelist += container.children - continue - - snode = self._make_section(section.kind.name.title()) - self._parse_text_into_node(dedent(section.text), snode) - nodelist.append(snode) - return nodelist - - def _nodes_for_if_section(self, ifcond): - """Return list of doctree nodes for the "If" section""" - nodelist = [] - if ifcond.is_present(): - snode = self._make_section('If') - snode += nodes.paragraph( - '', '', *self._nodes_for_ifcond(ifcond, with_if=False) - ) - nodelist.append(snode) - return nodelist - - def _add_doc(self, typ, sections): - """Add documentation for a command/object/enum... - - We assume we're documenting the thing defined in self._cur_doc. - typ is the type of thing being added ("Command", "Object", etc) - - sections is a list of nodes for sections to add to the definition. - """ - - doc = self._cur_doc - snode = nodes.section(ids=[self._sphinx_directive.new_serialno()]) - snode += nodes.title('', '', *[nodes.literal(doc.symbol, doc.symbol), - nodes.Text(' (' + typ + ')')]) - self._parse_text_into_node(doc.body.text, snode) - for s in sections: - if s is not None: - snode += s - self._add_node_to_current_heading(snode) - - def visit_enum_type(self, name, info, ifcond, features, members, prefix): - doc = self._cur_doc - self._add_doc('Enum', - self._nodes_for_enum_values(doc) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_object_type(self, name, info, ifcond, features, - base, members, branches): - doc = self._cur_doc - if base and base.is_implicit(): - base = None - self._add_doc('Object', - self._nodes_for_members(doc, 'Members', base, branches) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_alternate_type(self, name, info, ifcond, features, - alternatives): - doc = self._cur_doc - self._add_doc('Alternate', - self._nodes_for_members(doc, 'Members') - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_command(self, name, info, ifcond, features, arg_type, - ret_type, gen, success_response, boxed, allow_oob, - allow_preconfig, coroutine): - doc = self._cur_doc - self._add_doc('Command', - self._nodes_for_arguments(doc, arg_type) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_event(self, name, info, ifcond, features, arg_type, boxed): - doc = self._cur_doc - self._add_doc('Event', - self._nodes_for_arguments(doc, arg_type) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def symbol(self, doc, entity): - """Add documentation for one symbol to the document tree - - This is the main entry point which causes us to add documentation - nodes for a symbol (which could be a 'command', 'object', 'event', - etc). We do this by calling 'visit' on the schema entity, which - will then call back into one of our visit_* methods, depending - on what kind of thing this symbol is. - """ - self._cur_doc = doc - entity.visit(self) - self._cur_doc = None - - def _start_new_heading(self, heading, level): - """Start a new heading at the specified heading level - - Create a new section whose title is 'heading' and which is placed - in the docutils node tree as a child of the most recent level-1 - heading. Subsequent document sections (commands, freeform doc chunks, - etc) will be placed as children of this new heading section. - """ - if len(self._active_headings) < level: - raise QAPISemError(self._cur_doc.info, - 'Level %d subheading found outside a ' - 'level %d heading' - % (level, level - 1)) - snode = self._make_section(heading) - self._active_headings[level - 1] += snode - self._active_headings = self._active_headings[:level] - self._active_headings.append(snode) - return snode - - def _add_node_to_current_heading(self, node): - """Add the node to whatever the current active heading is""" - self._active_headings[-1] += node - - def freeform(self, doc): - """Add a piece of 'freeform' documentation to the document tree - - A 'freeform' document chunk doesn't relate to any particular - symbol (for instance, it could be an introduction). - - If the freeform document starts with a line of the form - '= Heading text', this is a section or subsection heading, with - the heading level indicated by the number of '=' signs. - """ - - # QAPIDoc documentation says free-form documentation blocks - # must have only a body section, nothing else. - assert not doc.sections - assert not doc.args - assert not doc.features - self._cur_doc = doc - - text = doc.body.text - if re.match(r'=+ ', text): - # Section/subsection heading (if present, will always be - # the first line of the block) - (heading, _, text) = text.partition('\n') - (leader, _, heading) = heading.partition(' ') - node = self._start_new_heading(heading, len(leader)) - if text == '': - return - else: - node = nodes.container() - - self._parse_text_into_node(text, node) - self._cur_doc = None - - def _parse_text_into_node(self, doctext, node): - """Parse a chunk of QAPI-doc-format text into the node - - The doc comment can contain most inline rST markup, including - bulleted and enumerated lists. - As an extra permitted piece of markup, @var will be turned - into ``var``. - """ - - # Handle the "@var means ``var`` case - doctext = re.sub(r'@([\w-]+)', r'``\1``', doctext) - - rstlist = ViewList() - for line in doctext.splitlines(): - # The reported line number will always be that of the start line - # of the doc comment, rather than the actual location of the error. - # Being more precise would require overhaul of the QAPIDoc class - # to track lines more exactly within all the sub-parts of the doc - # comment, as well as counting lines here. - rstlist.append(line, self._cur_doc.info.fname, - self._cur_doc.info.line) - # Append a blank line -- in some cases rST syntax errors get - # attributed to the line after one with actual text, and if there - # isn't anything in the ViewList corresponding to that then Sphinx - # 1.6's AutodocReporter will then misidentify the source/line location - # in the error message (usually attributing it to the top-level - # .rst file rather than the offending .json file). The extra blank - # line won't affect the rendered output. - rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line) - self._sphinx_directive.do_parse(rstlist, node) - - def get_document_node(self): - """Return the root docutils node which makes up the document""" - return self._top_node - - -# Turn the black formatter on for the rest of the file. -# fmt: on - - class QAPISchemaGenDepVisitor(QAPISchemaVisitor): """A QAPI schema visitor which adds Sphinx dependencies each module diff --git a/docs/sphinx/qapidoc_legacy.py b/docs/sphinx/qapidoc_legacy.py new file mode 100644 index 000000000000..679f38356b16 --- /dev/null +++ b/docs/sphinx/qapidoc_legacy.py @@ -0,0 +1,439 @@ +# coding=utf-8 +# +# QEMU qapidoc QAPI file parsing extension +# +# Copyright (c) 2020 Linaro +# +# This work is licensed under the terms of the GNU GPLv2 or later. +# See the COPYING file in the top-level directory. + +""" +qapidoc is a Sphinx extension that implements the qapi-doc directive + +The purpose of this extension is to read the documentation comments +in QAPI schema files, and insert them all into the current document. + +It implements one new rST directive, "qapi-doc::". +Each qapi-doc:: directive takes one argument, which is the +pathname of the schema file to process, relative to the source tree. + +The docs/conf.py file must set the qapidoc_srctree config value to +the root of the QEMU source tree. + +The Sphinx documentation on writing extensions is at: +https://www.sphinx-doc.org/en/master/development/index.html +""" + +import re +import textwrap + +from docutils import nodes +from docutils.statemachine import ViewList +from qapi.error import QAPISemError +from qapi.gen import QAPISchemaVisitor +from qapi.parser import QAPIDoc + + +def dedent(text: str) -> str: + # Adjust indentation to make description text parse as paragraph. + + lines = text.splitlines(True) + if re.match(r"\s+", lines[0]): + # First line is indented; description started on the line after + # the name. dedent the whole block. + return textwrap.dedent(text) + + # Descr started on same line. Dedent line 2+. + return lines[0] + textwrap.dedent("".join(lines[1:])) + + +class QAPISchemaGenRSTVisitor(QAPISchemaVisitor): + """A QAPI schema visitor which generates docutils/Sphinx nodes + + This class builds up a tree of docutils/Sphinx nodes corresponding + to documentation for the various QAPI objects. To use it, first + create a QAPISchemaGenRSTVisitor object, and call its + visit_begin() method. Then you can call one of the two methods + 'freeform' (to add documentation for a freeform documentation + chunk) or 'symbol' (to add documentation for a QAPI symbol). These + will cause the visitor to build up the tree of document + nodes. Once you've added all the documentation via 'freeform' and + 'symbol' method calls, you can call 'get_document_nodes' to get + the final list of document nodes (in a form suitable for returning + from a Sphinx directive's 'run' method). + """ + def __init__(self, sphinx_directive): + self._cur_doc = None + self._sphinx_directive = sphinx_directive + self._top_node = nodes.section() + self._active_headings = [self._top_node] + + def _make_dlitem(self, term, defn): + """Return a dlitem node with the specified term and definition. + + term should be a list of Text and literal nodes. + defn should be one of: + - a string, which will be handed to _parse_text_into_node + - a list of Text and literal nodes, which will be put into + a paragraph node + """ + dlitem = nodes.definition_list_item() + dlterm = nodes.term('', '', *term) + dlitem += dlterm + if defn: + dldef = nodes.definition() + if isinstance(defn, list): + dldef += nodes.paragraph('', '', *defn) + else: + self._parse_text_into_node(defn, dldef) + dlitem += dldef + return dlitem + + def _make_section(self, title): + """Return a section node with optional title""" + section = nodes.section(ids=[self._sphinx_directive.new_serialno()]) + if title: + section += nodes.title(title, title) + return section + + def _nodes_for_ifcond(self, ifcond, with_if=True): + """Return list of Text, literal nodes for the ifcond + + Return a list which gives text like ' (If: condition)'. + If with_if is False, we don't return the "(If: " and ")". + """ + + doc = ifcond.docgen() + if not doc: + return [] + doc = nodes.literal('', doc) + if not with_if: + return [doc] + + nodelist = [nodes.Text(' ('), nodes.strong('', 'If: ')] + nodelist.append(doc) + nodelist.append(nodes.Text(')')) + return nodelist + + def _nodes_for_one_member(self, member): + """Return list of Text, literal nodes for this member + + Return a list of doctree nodes which give text like + 'name: type (optional) (If: ...)' suitable for use as the + 'term' part of a definition list item. + """ + term = [nodes.literal('', member.name)] + if member.type.doc_type(): + term.append(nodes.Text(': ')) + term.append(nodes.literal('', member.type.doc_type())) + if member.optional: + term.append(nodes.Text(' (optional)')) + if member.ifcond.is_present(): + term.extend(self._nodes_for_ifcond(member.ifcond)) + return term + + def _nodes_for_variant_when(self, branches, variant): + """Return list of Text, literal nodes for variant 'when' clause + + Return a list of doctree nodes which give text like + 'when tagname is variant (If: ...)' suitable for use in + the 'branches' part of a definition list. + """ + term = [nodes.Text(' when '), + nodes.literal('', branches.tag_member.name), + nodes.Text(' is '), + nodes.literal('', '"%s"' % variant.name)] + if variant.ifcond.is_present(): + term.extend(self._nodes_for_ifcond(variant.ifcond)) + return term + + def _nodes_for_members(self, doc, what, base=None, branches=None): + """Return list of doctree nodes for the table of members""" + dlnode = nodes.definition_list() + for section in doc.args.values(): + term = self._nodes_for_one_member(section.member) + # TODO drop fallbacks when undocumented members are outlawed + if section.text: + defn = dedent(section.text) + else: + defn = [nodes.Text('Not documented')] + + dlnode += self._make_dlitem(term, defn) + + if base: + dlnode += self._make_dlitem([nodes.Text('The members of '), + nodes.literal('', base.doc_type())], + None) + + if branches: + for v in branches.variants: + if v.type.name == 'q_empty': + continue + assert not v.type.is_implicit() + term = [nodes.Text('The members of '), + nodes.literal('', v.type.doc_type())] + term.extend(self._nodes_for_variant_when(branches, v)) + dlnode += self._make_dlitem(term, None) + + if not dlnode.children: + return [] + + section = self._make_section(what) + section += dlnode + return [section] + + def _nodes_for_enum_values(self, doc): + """Return list of doctree nodes for the table of enum values""" + seen_item = False + dlnode = nodes.definition_list() + for section in doc.args.values(): + termtext = [nodes.literal('', section.member.name)] + if section.member.ifcond.is_present(): + termtext.extend(self._nodes_for_ifcond(section.member.ifcond)) + # TODO drop fallbacks when undocumented members are outlawed + if section.text: + defn = dedent(section.text) + else: + defn = [nodes.Text('Not documented')] + + dlnode += self._make_dlitem(termtext, defn) + seen_item = True + + if not seen_item: + return [] + + section = self._make_section('Values') + section += dlnode + return [section] + + def _nodes_for_arguments(self, doc, arg_type): + """Return list of doctree nodes for the arguments section""" + if arg_type and not arg_type.is_implicit(): + assert not doc.args + section = self._make_section('Arguments') + dlnode = nodes.definition_list() + dlnode += self._make_dlitem( + [nodes.Text('The members of '), + nodes.literal('', arg_type.name)], + None) + section += dlnode + return [section] + + return self._nodes_for_members(doc, 'Arguments') + + def _nodes_for_features(self, doc): + """Return list of doctree nodes for the table of features""" + seen_item = False + dlnode = nodes.definition_list() + for section in doc.features.values(): + dlnode += self._make_dlitem( + [nodes.literal('', section.member.name)], dedent(section.text)) + seen_item = True + + if not seen_item: + return [] + + section = self._make_section('Features') + section += dlnode + return [section] + + def _nodes_for_sections(self, doc): + """Return list of doctree nodes for additional sections""" + nodelist = [] + for section in doc.sections: + if section.kind == QAPIDoc.Kind.TODO: + # Hide TODO: sections + continue + + if section.kind == QAPIDoc.Kind.PLAIN: + # Sphinx cannot handle sectionless titles; + # Instead, just append the results to the prior section. + container = nodes.container() + self._parse_text_into_node(section.text, container) + nodelist += container.children + continue + + snode = self._make_section(section.kind.name.title()) + self._parse_text_into_node(dedent(section.text), snode) + nodelist.append(snode) + return nodelist + + def _nodes_for_if_section(self, ifcond): + """Return list of doctree nodes for the "If" section""" + nodelist = [] + if ifcond.is_present(): + snode = self._make_section('If') + snode += nodes.paragraph( + '', '', *self._nodes_for_ifcond(ifcond, with_if=False) + ) + nodelist.append(snode) + return nodelist + + def _add_doc(self, typ, sections): + """Add documentation for a command/object/enum... + + We assume we're documenting the thing defined in self._cur_doc. + typ is the type of thing being added ("Command", "Object", etc) + + sections is a list of nodes for sections to add to the definition. + """ + + doc = self._cur_doc + snode = nodes.section(ids=[self._sphinx_directive.new_serialno()]) + snode += nodes.title('', '', *[nodes.literal(doc.symbol, doc.symbol), + nodes.Text(' (' + typ + ')')]) + self._parse_text_into_node(doc.body.text, snode) + for s in sections: + if s is not None: + snode += s + self._add_node_to_current_heading(snode) + + def visit_enum_type(self, name, info, ifcond, features, members, prefix): + doc = self._cur_doc + self._add_doc('Enum', + self._nodes_for_enum_values(doc) + + self._nodes_for_features(doc) + + self._nodes_for_sections(doc) + + self._nodes_for_if_section(ifcond)) + + def visit_object_type(self, name, info, ifcond, features, + base, members, branches): + doc = self._cur_doc + if base and base.is_implicit(): + base = None + self._add_doc('Object', + self._nodes_for_members(doc, 'Members', base, branches) + + self._nodes_for_features(doc) + + self._nodes_for_sections(doc) + + self._nodes_for_if_section(ifcond)) + + def visit_alternate_type(self, name, info, ifcond, features, + alternatives): + doc = self._cur_doc + self._add_doc('Alternate', + self._nodes_for_members(doc, 'Members') + + self._nodes_for_features(doc) + + self._nodes_for_sections(doc) + + self._nodes_for_if_section(ifcond)) + + def visit_command(self, name, info, ifcond, features, arg_type, + ret_type, gen, success_response, boxed, allow_oob, + allow_preconfig, coroutine): + doc = self._cur_doc + self._add_doc('Command', + self._nodes_for_arguments(doc, arg_type) + + self._nodes_for_features(doc) + + self._nodes_for_sections(doc) + + self._nodes_for_if_section(ifcond)) + + def visit_event(self, name, info, ifcond, features, arg_type, boxed): + doc = self._cur_doc + self._add_doc('Event', + self._nodes_for_arguments(doc, arg_type) + + self._nodes_for_features(doc) + + self._nodes_for_sections(doc) + + self._nodes_for_if_section(ifcond)) + + def symbol(self, doc, entity): + """Add documentation for one symbol to the document tree + + This is the main entry point which causes us to add documentation + nodes for a symbol (which could be a 'command', 'object', 'event', + etc). We do this by calling 'visit' on the schema entity, which + will then call back into one of our visit_* methods, depending + on what kind of thing this symbol is. + """ + self._cur_doc = doc + entity.visit(self) + self._cur_doc = None + + def _start_new_heading(self, heading, level): + """Start a new heading at the specified heading level + + Create a new section whose title is 'heading' and which is placed + in the docutils node tree as a child of the most recent level-1 + heading. Subsequent document sections (commands, freeform doc chunks, + etc) will be placed as children of this new heading section. + """ + if len(self._active_headings) < level: + raise QAPISemError(self._cur_doc.info, + 'Level %d subheading found outside a ' + 'level %d heading' + % (level, level - 1)) + snode = self._make_section(heading) + self._active_headings[level - 1] += snode + self._active_headings = self._active_headings[:level] + self._active_headings.append(snode) + return snode + + def _add_node_to_current_heading(self, node): + """Add the node to whatever the current active heading is""" + self._active_headings[-1] += node + + def freeform(self, doc): + """Add a piece of 'freeform' documentation to the document tree + + A 'freeform' document chunk doesn't relate to any particular + symbol (for instance, it could be an introduction). + + If the freeform document starts with a line of the form + '= Heading text', this is a section or subsection heading, with + the heading level indicated by the number of '=' signs. + """ + + # QAPIDoc documentation says free-form documentation blocks + # must have only a body section, nothing else. + assert not doc.sections + assert not doc.args + assert not doc.features + self._cur_doc = doc + + text = doc.body.text + if re.match(r'=+ ', text): + # Section/subsection heading (if present, will always be + # the first line of the block) + (heading, _, text) = text.partition('\n') + (leader, _, heading) = heading.partition(' ') + node = self._start_new_heading(heading, len(leader)) + if text == '': + return + else: + node = nodes.container() + + self._parse_text_into_node(text, node) + self._cur_doc = None + + def _parse_text_into_node(self, doctext, node): + """Parse a chunk of QAPI-doc-format text into the node + + The doc comment can contain most inline rST markup, including + bulleted and enumerated lists. + As an extra permitted piece of markup, @var will be turned + into ``var``. + """ + + # Handle the "@var means ``var`` case + doctext = re.sub(r'@([\w-]+)', r'``\1``', doctext) + + rstlist = ViewList() + for line in doctext.splitlines(): + # The reported line number will always be that of the start line + # of the doc comment, rather than the actual location of the error. + # Being more precise would require overhaul of the QAPIDoc class + # to track lines more exactly within all the sub-parts of the doc + # comment, as well as counting lines here. + rstlist.append(line, self._cur_doc.info.fname, + self._cur_doc.info.line) + # Append a blank line -- in some cases rST syntax errors get + # attributed to the line after one with actual text, and if there + # isn't anything in the ViewList corresponding to that then Sphinx + # 1.6's AutodocReporter will then misidentify the source/line location + # in the error message (usually attributing it to the top-level + # .rst file rather than the offending .json file). The extra blank + # line won't affect the rendered output. + rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line) + self._sphinx_directive.do_parse(rstlist, node) + + def get_document_node(self): + """Return the root docutils node which makes up the document""" + return self._top_node From 45d483a851051bcea3a32b1ce7367180a8cf7bae Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:35 -0400 Subject: [PATCH 0853/1179] docs/qapidoc: Fix static typing on qapidoc.py Now that the legacy code is factored out, fix up the typing on the remaining code in qapidoc.py. Add a type ignore to qapi_legacy.py to prevent the errors there from bleeding out into qapidoc.py. Signed-off-by: John Snow Message-ID: <20250311034303.75779-38-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 40 ++++++++++++++++++++++------------- docs/sphinx/qapidoc_legacy.py | 1 + 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index f4abf42e7bf6..5246832b68cd 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -24,17 +24,18 @@ https://www.sphinx-doc.org/en/master/development/index.html """ +from __future__ import annotations + import os import sys -from typing import List +from typing import TYPE_CHECKING from docutils import nodes from docutils.parsers.rst import Directive, directives from qapi.error import QAPIError -from qapi.gen import QAPISchemaVisitor -from qapi.schema import QAPISchema +from qapi.schema import QAPISchema, QAPISchemaVisitor -from qapidoc_legacy import QAPISchemaGenRSTVisitor +from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore from sphinx import addnodes from sphinx.directives.code import CodeBlock from sphinx.errors import ExtensionError @@ -42,6 +43,15 @@ from sphinx.util.nodes import nested_parse_with_titles +if TYPE_CHECKING: + from typing import Any, List, Sequence + + from docutils.statemachine import StringList + + from sphinx.application import Sphinx + from sphinx.util.typing import ExtensionMetadata + + __version__ = "1.0" @@ -53,11 +63,11 @@ class QAPISchemaGenDepVisitor(QAPISchemaVisitor): schema file associated with each module in the QAPI input. """ - def __init__(self, env, qapidir): + def __init__(self, env: Any, qapidir: str) -> None: self._env = env self._qapidir = qapidir - def visit_module(self, name): + def visit_module(self, name: str) -> None: if name != "./builtin": qapifile = self._qapidir + "/" + name self._env.note_dependency(os.path.abspath(qapifile)) @@ -65,10 +75,10 @@ def visit_module(self, name): class NestedDirective(Directive): - def run(self): + def run(self) -> Sequence[nodes.Node]: raise NotImplementedError - def do_parse(self, rstlist, node): + def do_parse(self, rstlist: StringList, node: nodes.Node) -> None: """ Parse rST source lines and add them to the specified node @@ -93,15 +103,15 @@ class QAPIDocDirective(NestedDirective): } has_content = False - def new_serialno(self): + def new_serialno(self) -> str: """Return a unique new ID string suitable for use as a node's ID""" env = self.state.document.settings.env return "qapidoc-%d" % env.new_serialno("qapidoc") - def transmogrify(self, schema) -> nodes.Element: + def transmogrify(self, schema: QAPISchema) -> nodes.Element: raise NotImplementedError - def legacy(self, schema) -> nodes.Element: + def legacy(self, schema: QAPISchema) -> nodes.Element: vis = QAPISchemaGenRSTVisitor(self) vis.visit_begin(schema) for doc in schema.docs: @@ -109,9 +119,9 @@ def legacy(self, schema) -> nodes.Element: vis.symbol(doc, schema.lookup_entity(doc.symbol)) else: vis.freeform(doc) - return vis.get_document_node() + return vis.get_document_node() # type: ignore - def run(self): + def run(self) -> Sequence[nodes.Node]: env = self.state.document.settings.env qapifile = env.config.qapidoc_srctree + "/" + self.arguments[0] qapidir = os.path.dirname(qapifile) @@ -185,7 +195,7 @@ def _highlightlang(self) -> addnodes.highlightlang: ) return node - def admonition_wrap(self, *content) -> List[nodes.Node]: + def admonition_wrap(self, *content: nodes.Node) -> List[nodes.Node]: title = "Example:" if "title" in self.options: title = f"{title} {self.options['title']}" @@ -231,7 +241,7 @@ def run(self) -> List[nodes.Node]: return self.admonition_wrap(*content_nodes) -def setup(app): +def setup(app: Sphinx) -> ExtensionMetadata: """Register qapi-doc directive with Sphinx""" app.add_config_value("qapidoc_srctree", None, "env") app.add_directive("qapi-doc", QAPIDocDirective) diff --git a/docs/sphinx/qapidoc_legacy.py b/docs/sphinx/qapidoc_legacy.py index 679f38356b16..13520f4c26b3 100644 --- a/docs/sphinx/qapidoc_legacy.py +++ b/docs/sphinx/qapidoc_legacy.py @@ -1,4 +1,5 @@ # coding=utf-8 +# type: ignore # # QEMU qapidoc QAPI file parsing extension # From bd7c1496aa695f7b7a560a2caf29f4b84f8752af Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:37 -0400 Subject: [PATCH 0854/1179] docs/qapidoc: add transmogrifier class stub Add the beginnings of the Transmogrifier class by adding the rST conversion helpers that will be used to build the virtual rST document. This version of the class does not actually "do anything" yet; each individual feature is added one-at-a-time in the forthcoming commits. Signed-off-by: John Snow Message-ID: <20250311034303.75779-40-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 73 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 5246832b68cd..c243bb6faaae 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -26,14 +26,17 @@ from __future__ import annotations +from contextlib import contextmanager import os import sys from typing import TYPE_CHECKING from docutils import nodes from docutils.parsers.rst import Directive, directives +from docutils.statemachine import StringList from qapi.error import QAPIError from qapi.schema import QAPISchema, QAPISchemaVisitor +from qapi.source import QAPISourceInfo from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore from sphinx import addnodes @@ -44,9 +47,12 @@ if TYPE_CHECKING: - from typing import Any, List, Sequence - - from docutils.statemachine import StringList + from typing import ( + Any, + Generator, + List, + Sequence, + ) from sphinx.application import Sphinx from sphinx.util.typing import ExtensionMetadata @@ -55,6 +61,67 @@ __version__ = "1.0" +class Transmogrifier: + def __init__(self) -> None: + self._result = StringList() + self.indent = 0 + + # General-purpose rST generation functions + + def get_indent(self) -> str: + return " " * self.indent + + @contextmanager + def indented(self) -> Generator[None]: + self.indent += 1 + try: + yield + finally: + self.indent -= 1 + + def add_line_raw(self, line: str, source: str, *lineno: int) -> None: + """Append one line of generated reST to the output.""" + + # NB: Sphinx uses zero-indexed lines; subtract one. + lineno = tuple((n - 1 for n in lineno)) + + if line.strip(): + # not a blank line + self._result.append( + self.get_indent() + line.rstrip("\n"), source, *lineno + ) + else: + self._result.append("", source, *lineno) + + def add_line(self, content: str, info: QAPISourceInfo) -> None: + # NB: We *require* an info object; this works out OK because we + # don't document built-in objects that don't have + # one. Everything else should. + self.add_line_raw(content, info.fname, info.line) + + def add_lines( + self, + content: str, + info: QAPISourceInfo, + ) -> None: + lines = content.splitlines(True) + for i, line in enumerate(lines): + self.add_line_raw(line, info.fname, info.line + i) + + def ensure_blank_line(self) -> None: + # Empty document -- no blank line required. + if not self._result: + return + + # Last line isn't blank, add one. + if self._result[-1].strip(): # pylint: disable=no-member + fname, line = self._result.info(-1) + assert isinstance(line, int) + # New blank line is credited to one-after the current last line. + # +2: correct for zero/one index, then increment by one. + self.add_line_raw("", fname, line + 2) + + class QAPISchemaGenDepVisitor(QAPISchemaVisitor): """A QAPI schema visitor which adds Sphinx dependencies each module From 5edd7411c4f25a43400c5b8d6e5647603942f36b Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:38 -0400 Subject: [PATCH 0855/1179] docs/qapidoc: add visit_module() method This method annotates the start of a new module, crediting the source location to the first line of the module file. Signed-off-by: John Snow Message-ID: <20250311034303.75779-41-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index c243bb6faaae..6de8c9005430 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -28,6 +28,7 @@ from contextlib import contextmanager import os +from pathlib import Path import sys from typing import TYPE_CHECKING @@ -121,6 +122,14 @@ def ensure_blank_line(self) -> None: # +2: correct for zero/one index, then increment by one. self.add_line_raw("", fname, line + 2) + # Transmogrification core methods + + def visit_module(self, path: str) -> None: + name = Path(path).stem + # module directives are credited to the first line of a module file. + self.add_line_raw(f".. qapi:module:: {name}", path, 1) + self.ensure_blank_line() + class QAPISchemaGenDepVisitor(QAPISchemaVisitor): """A QAPI schema visitor which adds Sphinx dependencies each module From 36e4182f4086edf3e7bbc5202bd692678d454793 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:39 -0400 Subject: [PATCH 0856/1179] qapi/source: allow multi-line QAPISourceInfo advancing This is for the sake of the new rST generator (the "transmogrifier") so we can advance multiple lines on occasion while keeping the generated<-->source mappings accurate. next_line now simply takes an optional n parameter which chooses the number of lines to advance. The next patch will use this when converting section syntax in free-form documentation to more traditional rST section header syntax, which does not always line up 1:1 for line counts. For example: ``` ## # = Section <-- Info is pointing here, "L1" # # Lorem Ipsum ## ``` would be transformed to rST as: ``` ======= <-- L1 Section <-- L1 ======= <-- L1 <-- L2 Lorem Ipsum <-- L3 ``` After consuming the single "Section" line from the source, we want to advance the source pointer to the next non-empty line which requires jumping by more than one line. Signed-off-by: John Snow Reviewed-by: Markus Armbruster Message-ID: <20250311034303.75779-42-jsnow@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/qapi/source.py b/scripts/qapi/source.py index 7b379fdc9257..ffdc3f482acd 100644 --- a/scripts/qapi/source.py +++ b/scripts/qapi/source.py @@ -47,9 +47,9 @@ def set_defn(self, meta: str, name: str) -> None: self.defn_meta = meta self.defn_name = name - def next_line(self: T) -> T: + def next_line(self: T, n: int = 1) -> T: info = copy.copy(self) - info.line += 1 + info.line += n return info def loc(self) -> str: From f0b2fe99f63023e25b827449d54ae3623339217e Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:40 -0400 Subject: [PATCH 0857/1179] docs/qapidoc: add visit_freeform() method Add the transmogrifier implementation for converting freeform doc blocks to rST. Signed-off-by: John Snow Message-ID: <20250311034303.75779-43-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 6de8c9005430..ddad6041455b 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -29,6 +29,7 @@ from contextlib import contextmanager import os from pathlib import Path +import re import sys from typing import TYPE_CHECKING @@ -55,6 +56,8 @@ Sequence, ) + from qapi.parser import QAPIDoc + from sphinx.application import Sphinx from sphinx.util.typing import ExtensionMetadata @@ -130,6 +133,47 @@ def visit_module(self, path: str) -> None: self.add_line_raw(f".. qapi:module:: {name}", path, 1) self.ensure_blank_line() + def visit_freeform(self, doc: QAPIDoc) -> None: + # TODO: Once the old qapidoc transformer is deprecated, freeform + # sections can be updated to pure rST, and this transformed removed. + # + # For now, translate our micro-format into rST. Code adapted + # from Peter Maydell's freeform(). + + assert len(doc.all_sections) == 1, doc.all_sections + body = doc.all_sections[0] + text = body.text + info = doc.info + + if re.match(r"=+ ", text): + # Section/subsection heading (if present, will always be the + # first line of the block) + (heading, _, text) = text.partition("\n") + (leader, _, heading) = heading.partition(" ") + # Implicit +1 for heading in the containing .rst doc + level = len(leader) + 1 + + # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections + markers = ' #*=_^"' + overline = level <= 2 + marker = markers[level] + + self.ensure_blank_line() + # This credits all 2 or 3 lines to the single source line. + if overline: + self.add_line(marker * len(heading), info) + self.add_line(heading, info) + self.add_line(marker * len(heading), info) + self.ensure_blank_line() + + # Eat blank line(s) and advance info + trimmed = text.lstrip("\n") + text = trimmed + info = info.next_line(len(text) - len(trimmed) + 1) + + self.add_lines(text, info) + self.ensure_blank_line() + class QAPISchemaGenDepVisitor(QAPISchemaVisitor): """A QAPI schema visitor which adds Sphinx dependencies each module From 803df114fd68b5e7a3d6c60b162c0013cf6966e6 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:41 -0400 Subject: [PATCH 0858/1179] docs/qapidoc: add preamble() method This method adds the options/preamble to each definition block. Notably, :since: and :ifcond: are added, as are any "special features" such as :deprecated: and :unstable:. If conditionals, if attached to special features, are currently unhandled in this patch and will be addressed at a future date. We currently do not have any if conditionals attached to special features. Signed-off-by: John Snow Message-ID: <20250311034303.75779-44-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index ddad6041455b..f56aa6d1fd75 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -37,7 +37,12 @@ from docutils.parsers.rst import Directive, directives from docutils.statemachine import StringList from qapi.error import QAPIError -from qapi.schema import QAPISchema, QAPISchemaVisitor +from qapi.parser import QAPIDoc +from qapi.schema import ( + QAPISchema, + QAPISchemaDefinition, + QAPISchemaVisitor, +) from qapi.source import QAPISourceInfo from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore @@ -56,8 +61,6 @@ Sequence, ) - from qapi.parser import QAPIDoc - from sphinx.application import Sphinx from sphinx.util.typing import ExtensionMetadata @@ -125,6 +128,38 @@ def ensure_blank_line(self) -> None: # +2: correct for zero/one index, then increment by one. self.add_line_raw("", fname, line + 2) + # Transmogrification helpers + + def preamble(self, ent: QAPISchemaDefinition) -> None: + """ + Generate option lines for QAPI entity directives. + """ + if ent.doc and ent.doc.since: + assert ent.doc.since.kind == QAPIDoc.Kind.SINCE + # Generated from the entity's docblock; info location is exact. + self.add_line(f":since: {ent.doc.since.text}", ent.doc.since.info) + + if ent.ifcond.is_present(): + doc = ent.ifcond.docgen() + assert ent.info + # Generated from entity definition; info location is approximate. + self.add_line(f":ifcond: {doc}", ent.info) + + # Hoist special features such as :deprecated: and :unstable: + # into the options block for the entity. If, in the future, new + # special features are added, qapi-domain will chirp about + # unrecognized options and fail until they are handled in + # qapi-domain. + for feat in ent.features: + if feat.is_special(): + # FIXME: handle ifcond if present. How to display that + # information is TBD. + # Generated from entity def; info location is approximate. + assert feat.info + self.add_line(f":{feat.name}:", feat.info) + + self.ensure_blank_line() + # Transmogrification core methods def visit_module(self, path: str) -> None: From 56e1adf293037ec3324a7f20d54d50cd671dc281 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:42 -0400 Subject: [PATCH 0859/1179] docs/qapidoc: add visit_paragraph() method This transforms "formerly known as untagged sections" into our pure intermediate rST format. These sections are already pure rST, so this method doesn't do a whole lot except ensure appropriate newlines. Signed-off-by: John Snow Message-ID: <20250311034303.75779-45-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index f56aa6d1fd75..a9f98d465713 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -130,6 +130,15 @@ def ensure_blank_line(self) -> None: # Transmogrification helpers + def visit_paragraph(self, section: QAPIDoc.Section) -> None: + # Squelch empty paragraphs. + if not section.text: + return + + self.ensure_blank_line() + self.add_lines(section.text, section.info) + self.ensure_blank_line() + def preamble(self, ent: QAPISchemaDefinition) -> None: """ Generate option lines for QAPI entity directives. From e9fbf1a0c6c2df9c53bb577b4245cf48914687fe Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:43 -0400 Subject: [PATCH 0860/1179] docs/qapidoc: add visit_errors() method Notably, this method does not currently address the formatting issues present with the "errors" section in QAPIDoc and just vomits the text verbatim into the rST doc, with somewhat inconsistent results. To be addressed in a future patch. Signed-off-by: John Snow Message-ID: <20250311034303.75779-46-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index a9f98d465713..c17cb9f9b16f 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -139,6 +139,12 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None: self.add_lines(section.text, section.info) self.ensure_blank_line() + def visit_errors(self, section: QAPIDoc.Section) -> None: + # FIXME: the formatting for errors may be inconsistent and may + # or may not require different newline placement to ensure + # proper rendering as a nested list. + self.add_lines(f":error:\n{section.text}", section.info) + def preamble(self, ent: QAPISchemaDefinition) -> None: """ Generate option lines for QAPI entity directives. From 3a396a865be6a55322baa854c470c43c0a9f64e6 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:44 -0400 Subject: [PATCH 0861/1179] docs/qapidoc: add format_type() method This method is responsible for generating a type name for a given member with the correct annotations for the QAPI domain. Features and enums do not *have* types, so they return None. Everything else returns the type name with a "?" suffix if that type is optional, and ensconced in [brackets] if it's an array type. Signed-off-by: John Snow Message-ID: <20250311034303.75779-47-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index c17cb9f9b16f..5144bb965af6 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -40,7 +40,13 @@ from qapi.parser import QAPIDoc from qapi.schema import ( QAPISchema, + QAPISchemaArrayType, QAPISchemaDefinition, + QAPISchemaEnumMember, + QAPISchemaFeature, + QAPISchemaMember, + QAPISchemaObjectTypeMember, + QAPISchemaType, QAPISchemaVisitor, ) from qapi.source import QAPISourceInfo @@ -58,7 +64,9 @@ Any, Generator, List, + Optional, Sequence, + Union, ) from sphinx.application import Sphinx @@ -128,6 +136,30 @@ def ensure_blank_line(self) -> None: # +2: correct for zero/one index, then increment by one. self.add_line_raw("", fname, line + 2) + def format_type( + self, ent: Union[QAPISchemaDefinition | QAPISchemaMember] + ) -> Optional[str]: + if isinstance(ent, (QAPISchemaEnumMember, QAPISchemaFeature)): + return None + + qapi_type = ent + optional = False + if isinstance(ent, QAPISchemaObjectTypeMember): + qapi_type = ent.type + optional = ent.optional + + if isinstance(qapi_type, QAPISchemaArrayType): + ret = f"[{qapi_type.element_type.doc_type()}]" + else: + assert isinstance(qapi_type, QAPISchemaType) + tmp = qapi_type.doc_type() + assert tmp + ret = tmp + if optional: + ret += "?" + + return ret + # Transmogrification helpers def visit_paragraph(self, section: QAPIDoc.Section) -> None: From 604df9bb001fc66f77d349ce63e870af84b42d96 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:45 -0400 Subject: [PATCH 0862/1179] docs/qapidoc: add add_field() and generate_field() helper methods These are simple rST generation methods that assist in getting the types and formatting correct for a field list entry. add_field() is a more raw, direct call while generate_field() is intended to be used for generating the correct field from a member object. Signed-off-by: John Snow Message-ID: <20250311034303.75779-48-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 5144bb965af6..2f85fe0bc3e9 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -136,6 +136,20 @@ def ensure_blank_line(self) -> None: # +2: correct for zero/one index, then increment by one. self.add_line_raw("", fname, line + 2) + def add_field( + self, + kind: str, + name: str, + body: str, + info: QAPISourceInfo, + typ: Optional[str] = None, + ) -> None: + if typ: + text = f":{kind} {typ} {name}: {body}" + else: + text = f":{kind} {name}: {body}" + self.add_lines(text, info) + def format_type( self, ent: Union[QAPISchemaDefinition | QAPISchemaMember] ) -> Optional[str]: @@ -160,6 +174,16 @@ def format_type( return ret + def generate_field( + self, + kind: str, + member: QAPISchemaMember, + body: str, + info: QAPISourceInfo, + ) -> None: + typ = self.format_type(member) + self.add_field(kind, member.name, body, info, typ) + # Transmogrification helpers def visit_paragraph(self, section: QAPIDoc.Section) -> None: From 6c43b008c4b6338f49b6dffb82437285bf98b97a Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:46 -0400 Subject: [PATCH 0863/1179] docs/qapidoc: add visit_feature() method This adds a simple ":feat name: lorem ipsum ..." line to the generated rST document, so at the moment it's only for "top level" features. Features not attached directly to a QAPI definition are not currently handled! This is a small regression over the prior documentation generator that will be addressed in a future patch. Signed-off-by: John Snow Message-ID: <20250311034303.75779-49-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 2f85fe0bc3e9..208d7ca14463 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -195,6 +195,15 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None: self.add_lines(section.text, section.info) self.ensure_blank_line() + def visit_feature(self, section: QAPIDoc.ArgSection) -> None: + # FIXME - ifcond for features is not handled at all yet! + # Proposal: decorate the right-hand column with some graphical + # element to indicate conditional availability? + assert section.text # Guaranteed by parser.py + assert section.member + + self.generate_field("feat", section.member, section.text, section.info) + def visit_errors(self, section: QAPIDoc.Section) -> None: # FIXME: the formatting for errors may be inconsistent and may # or may not require different newline placement to ensure From 38a349ff5b9ae583fe8a66e3e507ea9954b1aeb1 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:47 -0400 Subject: [PATCH 0864/1179] docs/qapidoc: prepare to record entity being transmogrified Prepare to keep a record of which entity we're working on documenting for the purposes of being able to change certain generative features conditionally and create stronger assertions. If you find yourself asking: "Wait, but where does the current entity actually get recorded?!", you're right! That part comes with the visit_entity() implementation, which gets added later. This patch is front-loaded for the sake of type checking in the forthcoming commits before visit_entity() is ready to be added. Signed-off-by: John Snow Message-ID: <20250311034303.75779-50-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 208d7ca14463..47c2eeef8718 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -78,9 +78,15 @@ class Transmogrifier: def __init__(self) -> None: + self._curr_ent: Optional[QAPISchemaDefinition] = None self._result = StringList() self.indent = 0 + @property + def entity(self) -> QAPISchemaDefinition: + assert self._curr_ent is not None + return self._curr_ent + # General-purpose rST generation functions def get_indent(self) -> str: From 52c806cad08bfed53bf11c1a49bb203cc53deea0 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:48 -0400 Subject: [PATCH 0865/1179] docs/qapidoc: add visit_returns() method Generates :return: fields for explicit returns statements. Note that this does not presently handle undocumented returns, which is handled in a later commit. Signed-off-by: John Snow Message-ID: <20250311034303.75779-51-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 47c2eeef8718..eb8841099c70 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -41,6 +41,7 @@ from qapi.schema import ( QAPISchema, QAPISchemaArrayType, + QAPISchemaCommand, QAPISchemaDefinition, QAPISchemaEnumMember, QAPISchemaFeature, @@ -210,6 +211,20 @@ def visit_feature(self, section: QAPIDoc.ArgSection) -> None: self.generate_field("feat", section.member, section.text, section.info) + def visit_returns(self, section: QAPIDoc.Section) -> None: + assert isinstance(self.entity, QAPISchemaCommand) + rtype = self.entity.ret_type + # q_empty can produce None, but we won't be documenting anything + # without an explicit return statement in the doc block, and we + # should not have any such explicit statements when there is no + # return value. + assert rtype + + typ = self.format_type(rtype) + assert typ + assert section.text + self.add_field("return", typ, section.text, section.info) + def visit_errors(self, section: QAPIDoc.Section) -> None: # FIXME: the formatting for errors may be inconsistent and may # or may not require different newline placement to ensure From dbf51d15fdbb5410e21540d47c16f505413ce1eb Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:49 -0400 Subject: [PATCH 0866/1179] docs/qapidoc: add visit_member() method This method is used for generating the "members" of a wide variety of things, including structs, unions, enums, alternates, etc. The field name it uses to do so is dependent on the type of entity the "member" belongs to. Currently, IF conditionals for individual members are not handled or rendered, a small regression from the prior documentation generator. This will be fixed in a future patch. Signed-off-by: John Snow Message-ID: <20250311034303.75779-52-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index eb8841099c70..a8e19487d0a4 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -78,6 +78,16 @@ class Transmogrifier: + # Field names used for different entity types: + field_types = { + "enum": "value", + "struct": "memb", + "union": "memb", + "event": "memb", + "command": "arg", + "alternate": "alt", + } + def __init__(self) -> None: self._curr_ent: Optional[QAPISchemaDefinition] = None self._result = StringList() @@ -88,6 +98,10 @@ def entity(self) -> QAPISchemaDefinition: assert self._curr_ent is not None return self._curr_ent + @property + def member_field_type(self) -> str: + return self.field_types[self.entity.meta] + # General-purpose rST generation functions def get_indent(self) -> str: @@ -202,6 +216,19 @@ def visit_paragraph(self, section: QAPIDoc.Section) -> None: self.add_lines(section.text, section.info) self.ensure_blank_line() + def visit_member(self, section: QAPIDoc.ArgSection) -> None: + # FIXME: ifcond for members + # TODO: features for members (documented at entity-level, + # but sometimes defined per-member. Should we add such + # information to member descriptions when we can?) + assert section.text and section.member + self.generate_field( + self.member_field_type, + section.member, + section.text, + section.info, + ) + def visit_feature(self, section: QAPIDoc.ArgSection) -> None: # FIXME - ifcond for features is not handled at all yet! # Proposal: decorate the right-hand column with some graphical From 8cb0a41490364400bc6b1c361b8e1abf9c21bd76 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:50 -0400 Subject: [PATCH 0867/1179] docs/qapidoc: add visit_sections() method Implement the actual main dispatch method that processes and handles the list of doc sections for a given QAPI entity. Process doc sections in strict source order. This is good; reordering doc text is undesirable. Improvement over the old doc generator, which can reorder doc comments that don't adhere to (largely unspoken) conventions. Signed-off-by: John Snow Message-ID: <20250311034303.75779-53-jsnow@redhat.com> Acked-by: Markus Armbruster [Commit message extended] Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index a8e19487d0a4..83022b15ca25 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -288,6 +288,31 @@ def preamble(self, ent: QAPISchemaDefinition) -> None: self.ensure_blank_line() + def visit_sections(self, ent: QAPISchemaDefinition) -> None: + sections = ent.doc.all_sections if ent.doc else [] + + # Add sections in source order: + for section in sections: + if section.kind == QAPIDoc.Kind.PLAIN: + self.visit_paragraph(section) + elif section.kind == QAPIDoc.Kind.MEMBER: + assert isinstance(section, QAPIDoc.ArgSection) + self.visit_member(section) + elif section.kind == QAPIDoc.Kind.FEATURE: + assert isinstance(section, QAPIDoc.ArgSection) + self.visit_feature(section) + elif section.kind in (QAPIDoc.Kind.SINCE, QAPIDoc.Kind.TODO): + # Since is handled in preamble, TODO is skipped intentionally. + pass + elif section.kind == QAPIDoc.Kind.RETURNS: + self.visit_returns(section) + elif section.kind == QAPIDoc.Kind.ERRORS: + self.visit_errors(section) + else: + assert False + + self.ensure_blank_line() + # Transmogrification core methods def visit_module(self, path: str) -> None: From c05de7235a24dd1719ee6132fc45802b15ce49df Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:51 -0400 Subject: [PATCH 0868/1179] docs/qapidoc: add visit_entity() Finally, the core entry method for a qapi entity. Signed-off-by: John Snow Message-ID: <20250311034303.75779-54-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 83022b15ca25..aaf5b6e22bcd 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -78,6 +78,8 @@ class Transmogrifier: + # pylint: disable=too-many-public-methods + # Field names used for different entity types: field_types = { "enum": "value", @@ -362,6 +364,25 @@ def visit_freeform(self, doc: QAPIDoc) -> None: self.add_lines(text, info) self.ensure_blank_line() + def visit_entity(self, ent: QAPISchemaDefinition) -> None: + assert ent.info + + try: + self._curr_ent = ent + + # Squish structs and unions together into an "object" directive. + meta = ent.meta + if meta in ("struct", "union"): + meta = "object" + + # This line gets credited to the start of the /definition/. + self.add_line(f".. qapi:{meta}:: {ent.name}", ent.info) + with self.indented(): + self.preamble(ent) + self.visit_sections(ent) + finally: + self._curr_ent = None + class QAPISchemaGenDepVisitor(QAPISchemaVisitor): """A QAPI schema visitor which adds Sphinx dependencies each module From 5c1636f7cc4bfbe5737e3acca009a97dfa188879 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:52 -0400 Subject: [PATCH 0869/1179] docs/qapidoc: implement transmogrify() method This is the true top-level processor for the new transmogrifier; responsible both for generating the intermediate rST and then running the nested parse on that generated document to produce the final docutils tree that is then - very finally - postprocessed by sphinx for final rendering to HTML &c. Signed-off-by: John Snow Message-ID: <20250311034303.75779-55-jsnow@redhat.com> Acked-by: Markus Armbruster [Use the opportunity to move the __version__ assignment to where PEP 8 wants it] Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 49 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index aaf5b6e22bcd..a0016f88532e 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -2,6 +2,7 @@ # # QEMU qapidoc QAPI file parsing extension # +# Copyright (c) 2024-2025 Red Hat # Copyright (c) 2020 Linaro # # This work is licensed under the terms of the GNU GPLv2 or later. @@ -26,6 +27,8 @@ from __future__ import annotations +__version__ = "2.0" + from contextlib import contextmanager import os from pathlib import Path @@ -56,6 +59,7 @@ from sphinx import addnodes from sphinx.directives.code import CodeBlock from sphinx.errors import ExtensionError +from sphinx.util import logging from sphinx.util.docutils import switch_source_input from sphinx.util.nodes import nested_parse_with_titles @@ -74,7 +78,7 @@ from sphinx.util.typing import ExtensionMetadata -__version__ = "1.0" +logger = logging.getLogger(__name__) class Transmogrifier: @@ -95,6 +99,10 @@ def __init__(self) -> None: self._result = StringList() self.indent = 0 + @property + def result(self) -> StringList: + return self._result + @property def entity(self) -> QAPISchemaDefinition: assert self._curr_ent is not None @@ -438,7 +446,43 @@ def new_serialno(self) -> str: return "qapidoc-%d" % env.new_serialno("qapidoc") def transmogrify(self, schema: QAPISchema) -> nodes.Element: - raise NotImplementedError + logger.info("Transmogrifying QAPI to rST ...") + vis = Transmogrifier() + modules = set() + + for doc in schema.docs: + module_source = doc.info.fname + if module_source not in modules: + vis.visit_module(module_source) + modules.add(module_source) + + if doc.symbol: + ent = schema.lookup_entity(doc.symbol) + assert isinstance(ent, QAPISchemaDefinition) + vis.visit_entity(ent) + else: + vis.visit_freeform(doc) + + logger.info("Transmogrification complete.") + + contentnode = nodes.section() + content = vis.result + titles_allowed = True + + logger.info("Transmogrifier running nested parse ...") + with switch_source_input(self.state, content): + if titles_allowed: + node: nodes.Element = nodes.section() + node.document = self.state.document + nested_parse_with_titles(self.state, content, contentnode) + else: + node = nodes.paragraph() + node.document = self.state.document + self.state.nested_parse(content, 0, contentnode) + logger.info("Transmogrifier's nested parse completed.") + sys.stdout.flush() + + return contentnode def legacy(self, schema: QAPISchema) -> nodes.Element: vis = QAPISchemaGenRSTVisitor(self) @@ -572,6 +616,7 @@ def run(self) -> List[nodes.Node]: def setup(app: Sphinx) -> ExtensionMetadata: """Register qapi-doc directive with Sphinx""" + app.setup_extension("qapi_domain") app.add_config_value("qapidoc_srctree", None, "env") app.add_directive("qapi-doc", QAPIDocDirective) app.add_directive("qmp-example", QMPExample) From c9b6f988037b3d6f042871b149c7fd307bed8475 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:53 -0400 Subject: [PATCH 0870/1179] docs/qapidoc: process @foo into ``foo`` Add support for the special QAPI doc syntax to process @references as ``preformatted text``. At the moment, there are no actual cross-references for individual members, so there is nothing to link against. For now, process it identically to how we did in the old qapidoc system. Signed-off-by: John Snow Message-ID: <20250311034303.75779-56-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index a0016f88532e..ebdf709594e3 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -303,6 +303,9 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None: # Add sections in source order: for section in sections: + # @var is translated to ``var``: + section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text) + if section.kind == QAPIDoc.Kind.PLAIN: self.visit_paragraph(section) elif section.kind == QAPIDoc.Kind.MEMBER: From 7f6f24aaf55b567df2729d283cf8eac57c399493 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:54 -0400 Subject: [PATCH 0871/1179] docs/qapidoc: add intermediate output debugger Add debugging output for the qapidoc transmogrifier - setting DEBUG=1 will produce .ir files (one for each qapidoc directive) that write the generated rst file to disk to allow for easy debugging and verification of the generated document. Signed-off-by: John Snow Message-ID: <20250311034303.75779-57-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index ebdf709594e3..8ddebf73f28d 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -37,7 +37,7 @@ from typing import TYPE_CHECKING from docutils import nodes -from docutils.parsers.rst import Directive, directives +from docutils.parsers.rst import directives from docutils.statemachine import StringList from qapi.error import QAPIError from qapi.parser import QAPIDoc @@ -60,7 +60,7 @@ from sphinx.directives.code import CodeBlock from sphinx.errors import ExtensionError from sphinx.util import logging -from sphinx.util.docutils import switch_source_input +from sphinx.util.docutils import SphinxDirective, switch_source_input from sphinx.util.nodes import nested_parse_with_titles @@ -414,7 +414,7 @@ def visit_module(self, name: str) -> None: super().visit_module(name) -class NestedDirective(Directive): +class NestedDirective(SphinxDirective): def run(self) -> Sequence[nodes.Node]: raise NotImplementedError @@ -483,10 +483,43 @@ def transmogrify(self, schema: QAPISchema) -> nodes.Element: node.document = self.state.document self.state.nested_parse(content, 0, contentnode) logger.info("Transmogrifier's nested parse completed.") - sys.stdout.flush() + if self.env.app.verbosity >= 2 or os.environ.get("DEBUG"): + argname = "_".join(Path(self.arguments[0]).parts) + name = Path(argname).stem + ".ir" + self.write_intermediate(content, name) + + sys.stdout.flush() return contentnode + def write_intermediate(self, content: StringList, filename: str) -> None: + logger.info( + "writing intermediate rST for '%s' to '%s'", + self.arguments[0], + filename, + ) + + srctree = Path(self.env.app.config.qapidoc_srctree).resolve() + outlines = [] + lcol_width = 0 + + for i, line in enumerate(content): + src, lineno = content.info(i) + srcpath = Path(src).resolve() + srcpath = srcpath.relative_to(srctree) + + lcol = f"{srcpath}:{lineno:04d}" + lcol_width = max(lcol_width, len(lcol)) + outlines.append((lcol, line)) + + with open(filename, "w", encoding="UTF-8") as outfile: + for lcol, rcol in outlines: + outfile.write(lcol.rjust(lcol_width)) + outfile.write(" |") + if rcol: + outfile.write(f" {rcol}") + outfile.write("\n") + def legacy(self, schema: QAPISchema) -> nodes.Element: vis = QAPISchemaGenRSTVisitor(self) vis.visit_begin(schema) From 1884492e64da659323a8da4de98b344bc689f62a Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:55 -0400 Subject: [PATCH 0872/1179] docs/qapidoc: Add "the members of" pointers Add "the members of ..." pointers to Members and Arguments lists where appropriate, with clickable cross-references - so it's a slight improvement over the old system :) This patch is meant to be a temporary solution until we can review and merge the inliner. The implementation of this patch is a little bit of a hack: Sphinx is not designed to allow you to mix fields of different "type"; i.e. mixing member descriptions and free-form text under the same heading. To accomplish this with a minimum of hackery, we technically document a "dummy field" and then just strip off the documentation for that dummy field in a post-processing step. We use the "q_dummy" variable for this purpose, then strip it back out before final processing. If this processing step should fail, you'll see warnings for a bad cross-reference. (So if you don't see any, it must be working!) Signed-off-by: John Snow Message-ID: <20250311034303.75779-58-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 22 +++++++++++++-- docs/sphinx/qapidoc.py | 58 +++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index ca3f3a7e2d55..7ff618d8cdae 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -433,6 +433,24 @@ def transform_content(self, content_node: addnodes.desc_content) -> None: self._validate_field(field) +class SpecialTypedField(CompatTypedField): + def make_field(self, *args: Any, **kwargs: Any) -> nodes.field: + ret = super().make_field(*args, **kwargs) + + # Look for the characteristic " -- " text node that Sphinx + # inserts for each TypedField entry ... + for node in ret.traverse(lambda n: str(n) == " -- "): + par = node.parent + if par.children[0].astext() != "q_dummy": + continue + + # If the first node's text is q_dummy, this is a dummy + # field we want to strip down to just its contents. + del par.children[:-1] + + return ret + + class QAPICommand(QAPIObject): """Description of a QAPI Command.""" @@ -440,7 +458,7 @@ class QAPICommand(QAPIObject): doc_field_types.extend( [ # :arg TypeName ArgName: descr - CompatTypedField( + SpecialTypedField( "argument", label=_("Arguments"), names=("arg",), @@ -508,7 +526,7 @@ class QAPIObjectWithMembers(QAPIObject): doc_field_types.extend( [ # :member type name: descr - CompatTypedField( + SpecialTypedField( "member", label=_("Members"), names=("memb",), diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 8ddebf73f28d..a2d6f648a2e2 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -47,8 +47,10 @@ QAPISchemaCommand, QAPISchemaDefinition, QAPISchemaEnumMember, + QAPISchemaEvent, QAPISchemaFeature, QAPISchemaMember, + QAPISchemaObjectType, QAPISchemaObjectTypeMember, QAPISchemaType, QAPISchemaVisitor, @@ -298,11 +300,61 @@ def preamble(self, ent: QAPISchemaDefinition) -> None: self.ensure_blank_line() + def _insert_member_pointer(self, ent: QAPISchemaDefinition) -> None: + + def _get_target( + ent: QAPISchemaDefinition, + ) -> Optional[QAPISchemaDefinition]: + if isinstance(ent, (QAPISchemaCommand, QAPISchemaEvent)): + return ent.arg_type + if isinstance(ent, QAPISchemaObjectType): + return ent.base + return None + + target = _get_target(ent) + if target is not None and not target.is_implicit(): + assert ent.info + self.add_field( + self.member_field_type, + "q_dummy", + f"The members of :qapi:type:`{target.name}`.", + ent.info, + "q_dummy", + ) + + if isinstance(ent, QAPISchemaObjectType) and ent.branches is not None: + for variant in ent.branches.variants: + if variant.type.name == "q_empty": + continue + assert ent.info + self.add_field( + self.member_field_type, + "q_dummy", + f" When ``{ent.branches.tag_member.name}`` is " + f"``{variant.name}``: " + f"The members of :qapi:type:`{variant.type.name}`.", + ent.info, + "q_dummy", + ) + def visit_sections(self, ent: QAPISchemaDefinition) -> None: sections = ent.doc.all_sections if ent.doc else [] + # Determine the index location at which we should generate + # documentation for "The members of ..." pointers. This should + # go at the end of the members section(s) if any. Note that + # index 0 is assumed to be a plain intro section, even if it is + # empty; and that a members section if present will always + # immediately follow the opening PLAIN section. + gen_index = 1 + if len(sections) > 1: + while sections[gen_index].kind == QAPIDoc.Kind.MEMBER: + gen_index += 1 + if gen_index >= len(sections): + break + # Add sections in source order: - for section in sections: + for i, section in enumerate(sections): # @var is translated to ``var``: section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text) @@ -324,6 +376,10 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None: else: assert False + # Generate "The members of ..." entries if necessary: + if i == gen_index - 1: + self._insert_member_pointer(ent) + self.ensure_blank_line() # Transmogrification core methods From 565274da10fe46bf8c30a8175e39753e4fedb60e Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:56 -0400 Subject: [PATCH 0873/1179] docs/qapidoc: generate entries for undocumented members Presently, we never have any empty text entries for members. The next patch will explicitly generate such sections, so enable support for it in advance. The parser will generate placeholder sections to indicate undocumented members, but it's the qapidoc generator that's responsible for deciding what to do with that stub section. Signed-off-by: John Snow Message-ID: <20250311034303.75779-59-jsnow@redhat.com> Acked-by: Markus Armbruster [Tweak the stub section text] Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index a2d6f648a2e2..432fef04b057 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -233,11 +233,12 @@ def visit_member(self, section: QAPIDoc.ArgSection) -> None: # TODO: features for members (documented at entity-level, # but sometimes defined per-member. Should we add such # information to member descriptions when we can?) - assert section.text and section.member + assert section.member self.generate_field( self.member_field_type, section.member, - section.text, + # TODO drop fallbacks when undocumented members are outlawed + section.text if section.text else "Not documented", section.info, ) From 4d7d30b405a16d85ede6184595e8001fe2c412c4 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:57 -0400 Subject: [PATCH 0874/1179] qapi/parser: add undocumented stub members to all_sections Parser and doc generator cooperate on generating stub documentation for undocumented members. The parser makes up an ArgSection with an empty description, and the doc generator makes up a description. Right now, the made-up ArgSections go into doc.args. However, the new doc generator uses .all_sections, not .args. So put them into .all_sections, too. Insert them right after existing 'member' sections. If there are none, insert directly after the leading section. Doesn't affect the old generator, because that one doesn't use .all_sections. Signed-off-by: John Snow Message-ID: <20250311034303.75779-60-jsnow@redhat.com> Reviewed-by: Markus Armbruster [Commit message rewritten] Signed-off-by: Markus Armbruster --- scripts/qapi/parser.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 11c11bb09e5e..949d9e8bff72 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -789,8 +789,23 @@ def connect_member(self, member: 'QAPISchemaMember') -> None: raise QAPISemError(member.info, "%s '%s' lacks documentation" % (member.role, member.name)) - self.args[member.name] = QAPIDoc.ArgSection( + # Insert stub documentation section for missing member docs. + # TODO: drop when undocumented members are outlawed + + section = QAPIDoc.ArgSection( self.info, QAPIDoc.Kind.MEMBER, member.name) + self.args[member.name] = section + + # Determine where to insert stub doc - it should go at the + # end of the members section(s), if any. Note that index 0 + # is assumed to be an untagged intro section, even if it is + # empty. + index = 1 + if len(self.all_sections) > 1: + while self.all_sections[index].kind == QAPIDoc.Kind.MEMBER: + index += 1 + self.all_sections.insert(index, section) + self.args[member.name].connect(member) def connect_feature(self, feature: 'QAPISchemaFeature') -> None: From 30ab96cf84dae15c727408b7c7361edd0d9d5b1e Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:58 -0400 Subject: [PATCH 0875/1179] docs: disambiguate cross-references The next patch will engage the qapidoc transmogrifier, which creates a lot of cross-reference targets. Some of the existing targets ("migration", "qom", "replay") will become ambiguous as a result. Nail them down more explicitly to prevent ambiguous cross-reference warnings. Signed-off-by: John Snow Message-ID: <20250311034303.75779-61-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/devel/codebase.rst | 6 +++--- docs/glossary.rst | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/devel/codebase.rst b/docs/devel/codebase.rst index 4039875ee042..1b09953197b2 100644 --- a/docs/devel/codebase.rst +++ b/docs/devel/codebase.rst @@ -23,7 +23,7 @@ Some of the main QEMU subsystems are: - `Devices` & Board models - `Documentation ` - `GDB support` -- `Migration` +- :ref:`Migration` - `Monitor` - :ref:`QOM (QEMU Object Model)` - `System mode` @@ -112,7 +112,7 @@ yet, so sometimes the source code is all you have. * `libdecnumber `_: Import of gcc library, used to implement decimal number arithmetic. * `migration `__: - `Migration framework `. + :ref:`Migration framework `. * `monitor `_: `Monitor ` implementation (HMP & QMP). * `nbd `_: @@ -193,7 +193,7 @@ yet, so sometimes the source code is all you have. - `lcitool `_: Generate dockerfiles for CI containers. - `migration `_: - Test scripts and data for `Migration framework `. + Test scripts and data for :ref:`Migration framework `. - `multiboot `_: Test multiboot functionality for x86_64/i386. - `qapi-schema `_: diff --git a/docs/glossary.rst b/docs/glossary.rst index 693d9855dd13..4fa044bfb6ee 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -120,7 +120,7 @@ Migration --------- QEMU can save and restore the execution of a virtual machine between different -host systems. This is provided by the `Migration framework`. +host systems. This is provided by the :ref:`Migration framework`. NBD --- @@ -212,14 +212,14 @@ machine emulator and virtualizer. QOM --- -`QEMU Object Model ` is an object oriented API used to define various -devices and hardware in the QEMU codebase. +:ref:`QEMU Object Model ` is an object oriented API used to define +various devices and hardware in the QEMU codebase. Record/replay ------------- -`Record/replay ` is a feature of QEMU allowing to have a deterministic -and reproducible execution of a virtual machine. +:ref:`Record/replay ` is a feature of QEMU allowing to have a +deterministic and reproducible execution of a virtual machine. Rust ---- From a377f39f38fefe7dd6835728fea96a21c658d804 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:42:59 -0400 Subject: [PATCH 0876/1179] docs: enable qapidoc transmogrifier for QEMU QMP Reference We are not enabling the transmogrifier for QSD or QGA yet because we don't (yet) have a way to create separate indices, and all of the definitions will bleed together, which isn't so nice. For now, QMP is better than nothing at all! Signed-off-by: John Snow Message-ID: <20250311034303.75779-62-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/interop/qemu-qmp-ref.rst | 1 + qapi/qapi-schema.json | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/interop/qemu-qmp-ref.rst b/docs/interop/qemu-qmp-ref.rst index f94614a0b2f7..e95eeac45e20 100644 --- a/docs/interop/qemu-qmp-ref.rst +++ b/docs/interop/qemu-qmp-ref.rst @@ -7,3 +7,4 @@ QEMU QMP Reference Manual :depth: 3 .. qapi-doc:: qapi/qapi-schema.json + :transmogrify: diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index 2877aff73d0c..4475e81cc3eb 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -5,6 +5,8 @@ # # This document describes all commands currently supported by QMP. # +# For locating a particular item, please see the `qapi-index`. +# # Most of the time their usage is exactly the same as in the user # Monitor, this means that any other document which also describe # commands (the manpage, QEMU's manual, etc) can and should be From 42b633dbd1b5d3f7ce5c9b8e4417267399f641e1 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:43:00 -0400 Subject: [PATCH 0877/1179] docs: add qapi-domain syntax documentation Who documents the documentation? Me, I guess. Signed-off-by: John Snow Message-ID: <20250311034303.75779-63-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/devel/index-build.rst | 1 + docs/devel/qapi-domain.rst | 670 +++++++++++++++++++++++++++++++++++++ 2 files changed, 671 insertions(+) create mode 100644 docs/devel/qapi-domain.rst diff --git a/docs/devel/index-build.rst b/docs/devel/index-build.rst index 0745c81a264b..3f3cb21b9b42 100644 --- a/docs/devel/index-build.rst +++ b/docs/devel/index-build.rst @@ -12,4 +12,5 @@ some of the basics if you are adding new files and targets to the build. kconfig docs qapi-code-gen + qapi-domain control-flow-integrity diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst new file mode 100644 index 000000000000..1475870ca6c2 --- /dev/null +++ b/docs/devel/qapi-domain.rst @@ -0,0 +1,670 @@ +====================== +The Sphinx QAPI Domain +====================== + +An extension to the `rST syntax +`_ +in Sphinx is provided by the QAPI Domain, located in +``docs/sphinx/qapi_domain.py``. This extension is analogous to the +`Python Domain +`_ +included with Sphinx, but provides special directives and roles +speciically for annotating and documenting QAPI definitions +specifically. + +A `Domain +`_ +provides a set of special rST directives and cross-referencing roles to +Sphinx for understanding rST markup written to document a specific +language. By itself, this QAPI extension is only sufficient to parse rST +markup written by hand; the `autodoc +`_ +functionality is provided elsewhere, in ``docs/sphinx/qapidoc.py``, by +the "Transmogrifier". + +It is not expected that any developer nor documentation writer would +never need to write *nor* read these special rST forms. However, in the +event that something needs to be debugged, knowing the syntax of the +domain is quite handy. This reference may also be useful as a guide for +understanding the QAPI Domain extension code itself. Although most of +these forms will not be needed for documentation writing purposes, +understanding the cross-referencing syntax *will* be helpful when +writing rST documentation elsewhere, or for enriching the body of +QAPIDoc blocks themselves. + + +Concepts +======== + +The QAPI Domain itself provides no mechanisms for reading the QAPI +Schema or generating documentation from code that exists. It is merely +the rST syntax used to describe things. For instance, the Sphinx Python +domain adds syntax like ``:py:func:`` for describing Python functions in +documentation, but it's the autodoc module that is responsible for +reading python code and generating such syntax. QAPI is analagous here: +qapidoc.py is responsible for reading the QAPI Schema and generating rST +syntax, and qapi_domain.py is responsible for translating that special +syntax and providing APIs for Sphinx internals. + +In other words: + +qapi_domain.py adds syntax like ``.. qapi:command::`` to Sphinx, and +qapidoc.py transforms the documentation in ``qapi/*.json`` into rST +using directives defined by the domain. + +Or even shorter: + +``:py:`` is to ``:qapi:`` as *autodoc* is to *qapidoc*. + + +Info Field Lists +================ + +`Field lists +`_ +are a standard syntax in reStructuredText. Sphinx `extends that syntax +`_ +to give certain field list entries special meaning and parsing to, for +example, add cross-references. The QAPI Domain takes advantage of this +field list extension to document things like Arguments, Members, Values, +and so on. + +The special parsing and handling of info field lists in Sphinx is provided by +three main classes; Field, GroupedField, and TypedField. The behavior +and formatting for each configured field list entry in the domain +changes depending on which class is used. + +Field: + * Creates an ungrouped field: i.e., each entry will create its own + section and they will not be combined. + * May *optionally* support an argument. + * May apply cross-reference roles to *either* the argument *or* the + content body, both, or neither. + +This is used primarily for entries which are not expected to be +repeated, i.e., items that may only show up at most once. The QAPI +domain uses this class for "Errors" section. + +GroupedField: + * Creates a grouped field: i.e. multiple adjacent entries will be + merged into one section, and the content will form a bulleted list. + * *Must* take an argument. + * May optionally apply a cross-reference role to the argument, but not + the body. + * Can be configured to remove the bulleted list if there is only a + single entry. + * All items will be generated with the form: "argument -- body" + +This is used for entries which are expected to be repeated, but aren't +expected to have two arguments, i.e. types without names, or names +without types. The QAPI domain uses this class for features, returns, +and enum values. + +TypedField: + * Creates a grouped, typed field. Multiple adjacent entres will be + merged into one section, and the content will form a bulleted list. + * *Must* take at least one argument, but supports up to two - + nominally, a name and a type. + * May optionally apply a cross-reference role to the type or the name + argument, but not the body. + * Can be configured to remove the bulleted list if there is only a + single entry. + * All items will be generated with the form "name (type) -- body" + +This is used for entries that are expected to be repeated and will have +a name, a type, and a description. The QAPI domain uses this class for +arguments, alternatives, and members. Wherever type names are referenced +below, They must be a valid, documented type that will be +cross-referenced in the HTML output; or one of the built-in JSON types +(string, number, int, boolean, null, value, q_empty). + + +``:feat:`` +---------- + +Document a feature attached to a QAPI definition. + +:availability: This field list is available in the body of Command, + Event, Enum, Object and Alternate directives. +:syntax: ``:feat name: Lorem ipsum, dolor sit amet...`` +:type: `sphinx.util.docfields.GroupedField + `_ + +Example:: + + .. qapi:object:: BlockdevOptionsVirtioBlkVhostVdpa + :since: 7.2 + :ifcond: CONFIG_BLKIO + + Driver specific block device options for the virtio-blk-vhost-vdpa + backend. + + :memb string path: path to the vhost-vdpa character device. + :feat fdset: Member ``path`` supports the special "/dev/fdset/N" path + (since 8.1) + + +``:arg:`` +--------- + +Document an argument to a QAPI command. + +:availability: This field list is only available in the body of the + Command directive. +:syntax: ``:arg type name: description`` +:type: `sphinx.util.docfields.TypedField + `_ + + +Example:: + + .. qapi:command:: job-pause + :since: 3.0 + + Pause an active job. + + This command returns immediately after marking the active job for + pausing. Pausing an already paused job is an error. + + The job will pause as soon as possible, which means transitioning + into the PAUSED state if it was RUNNING, or into STANDBY if it was + READY. The corresponding JOB_STATUS_CHANGE event will be emitted. + + Cancelling a paused job automatically resumes it. + + :arg string id: The job identifier. + + +``:error:`` +----------- + +Document the error condition(s) of a QAPI command. + +:availability: This field list is only available in the body of the + Command directive. +:syntax: ``:error: Lorem ipsum dolor sit amet ...`` +:type: `sphinx.util.docfields.Field + `_ + +The format of the :errors: field list description is free-form rST. The +alternative spelling ":errors:" is also permitted, but strictly +analogous. + +Example:: + + .. qapi:command:: block-job-set-speed + :since: 1.1 + + Set maximum speed for a background block operation. + + This command can only be issued when there is an active block job. + + Throttling can be disabled by setting the speed to 0. + + :arg string device: The job identifier. This used to be a device + name (hence the name of the parameter), but since QEMU 2.7 it + can have other values. + :arg int speed: the maximum speed, in bytes per second, or 0 for + unlimited. Defaults to 0. + :error: + - If no background operation is active on this device, + DeviceNotActive + + +``:return:`` +------------- + +Document the return type(s) and value(s) of a QAPI command. + +:availability: This field list is only available in the body of the + Command directive. +:syntax: ``:return type: Lorem ipsum dolor sit amet ...`` +:type: `sphinx.util.docfields.GroupedField + `_ + + +Example:: + + .. qapi:command:: query-replay + :since: 5.2 + + Retrieve the record/replay information. It includes current + instruction count which may be used for ``replay-break`` and + ``replay-seek`` commands. + + :return ReplayInfo: record/replay information. + + .. qmp-example:: + + -> { "execute": "query-replay" } + <- { "return": { + "mode": "play", "filename": "log.rr", "icount": 220414 } + } + + +``:value:`` +----------- + +Document a possible value for a QAPI enum. + +:availability: This field list is only available in the body of the Enum + directive. +:syntax: ``:value name: Lorem ipsum, dolor sit amet ...`` +:type: `sphinx.util.docfields.GroupedField + `_ + +Example:: + + .. qapi:enum:: QapiErrorClass + :since: 1.2 + + QEMU error classes + + :value GenericError: this is used for errors that don't require a specific + error class. This should be the default case for most errors + :value CommandNotFound: the requested command has not been found + :value DeviceNotActive: a device has failed to be become active + :value DeviceNotFound: the requested device has not been found + :value KVMMissingCap: the requested operation can't be fulfilled because a + required KVM capability is missing + + +``:alt:`` +------------ + +Document a possible branch for a QAPI alternate. + +:availability: This field list is only available in the body of the + Alternate directive. +:syntax: ``:alt type name: Lorem ipsum, dolor sit amet ...`` +:type: `sphinx.util.docfields.TypedField + `_ + +As a limitation of Sphinx, we must document the "name" of the branch in +addition to the type, even though this information is not visible on the +wire in the QMP protocol format. This limitation *may* be lifted at a +future date. + +Example:: + + .. qapi:alternate:: StrOrNull + :since: 2.10 + + This is a string value or the explicit lack of a string (null + pointer in C). Intended for cases when 'optional absent' already + has a different meaning. + + :alt string s: the string value + :alt null n: no string value + + +``:memb:`` +---------- + +Document a member of an Event or Object. + +:availability: This field list is available in the body of Event or + Object directives. +:syntax: ``:memb type name: Lorem ipsum, dolor sit amet ...`` +:type: `sphinx.util.docfields.TypedField + `_ + +This is fundamentally the same as ``:arg:`` and ``:alt:``, but uses the +"Members" phrasing for Events and Objects (Structs and Unions). + +Example:: + + .. qapi:event:: JOB_STATUS_CHANGE + :since: 3.0 + + Emitted when a job transitions to a different status. + + :memb string id: The job identifier + :memb JobStatus status: The new job status + + +Arbitrary field lists +--------------------- + +Other field list names, while valid rST syntax, are prohibited inside of +QAPI directives to help prevent accidental misspellings of info field +list names. If you want to add a new arbitrary "non-value-added" field +list to QAPI documentation, you must add the field name to the allow +list in ``docs/conf.py`` + +For example:: + + qapi_allowed_fields = { + "see also", + } + +Will allow you to add arbitrary field lists in QAPI directives:: + + .. qapi:command:: x-fake-command + + :see also: Lorem ipsum, dolor sit amet ... + + +Cross-references +================ + +Cross-reference `roles +`_ +in the QAPI domain are modeled closely after the `Python +cross-referencing syntax +`_. + +QAPI definitions can be referenced using the standard `any +`_ +role cross-reference syntax, such as with ```query-blockstats```. In +the event that disambiguation is needed, cross-references can also be +written using a number of explicit cross-reference roles: + +* ``:qapi:mod:`block-core``` -- Reference a QAPI module. The link will + take you to the beginning of that section in the documentation. +* ``:qapi:cmd:`query-block``` -- Reference a QAPI command. +* ``:qapi:event:`JOB_STATUS_CHANGE``` -- Reference a QAPI event. +* ``:qapi:enum:`QapiErrorClass``` -- Reference a QAPI enum. +* ``:qapi:obj:`BlockdevOptionsVirtioBlkVhostVdpa`` -- Reference a QAPI + object (struct or union) +* ``:qapi:alt:`StrOrNull``` -- Reference a QAPI alternate. +* ``:qapi:type:`BlockDirtyInfo``` -- Reference *any* QAPI type; this + excludes modules, commands, and events. +* ``:qapi:any:`block-job-set-speed``` -- Reference absolutely any QAPI entity. + +Type arguments in info field lists are converted into references as if +you had used the ``:qapi:type:`` role. All of the special syntax below +applies to both info field lists and standalone explicit +cross-references. + + +Type decorations +---------------- + +Type names in references can be surrounded by brackets, like +``[typename]``, to indicate an array of that type. The cross-reference +will apply only to the type name between the brackets. For example; +``:qapi:type:`[Qcow2BitmapInfoFlags]``` renders to: +:qapi:type:`[Qcow2BitmapInfoFlags]` + +To indicate an optional argument/member in a field list, the type name +can be suffixed with ``?``. The cross-reference will be transformed to +"type, Optional" with the link applying only to the type name. For +example; ``:qapi:type:`BitmapSyncMode?``` renders to: +:qapi:type:`BitmapSyncMode?` + + +Namespaces +---------- + +Mimicking the `Python domain target specification syntax +`_, +QAPI allows you to specify the fully qualified path for a data +type. QAPI enforces globally unique names, so it's unlikely you'll need +this specific feature, but it may be extended in the near future to +allow referencing identically named commands and data types from +different utilities; i.e. QEMU Storage Daemon vs QMP. + +* A module can be explicitly provided; + ``:qapi:type:`block-core.BitmapSyncMode``` will render to: + :qapi:type:`block-core.BitmapSyncMode` +* If you don't want to display the "fully qualified" name, it can be + prefixed with a tilde; ``:qapi:type:`~block-core.BitmapSyncMode``` + will render to: :qapi:type:`~block-core.BitmapSyncMode` + + +Custom link text +---------------- + +The name of a cross-reference link can be explicitly overridden like +`most stock Sphinx references +`_ +using the ``custom text `` syntax. + +For example, ``:qapi:cmd:`Merge dirty bitmaps +``` will render as: :qapi:cmd:`Merge dirty +bitmaps ` + + +Directives +========== + +The QAPI domain adds a number of custom directives for documenting +various QAPI/QMP entities. The syntax is plain rST, and follows this +general format:: + + .. qapi:directive:: argument + :option: + :another-option: with an argument + + Content body, arbitrary rST is allowed here. + + +Sphinx standard options +----------------------- + +All QAPI directives inherit a number of `standard options +`_ +from Sphinx's ObjectDescription class. + +The dashed spellings of the below options were added in Sphinx 7.2, the +undashed spellings are currently retained as aliases, but will be +removed in a future version. + +* ``:no-index:`` and ``:noindex:`` -- Do not add this item into the + Index, and do not make it available for cross-referencing. +* ``no-index-entry:`` and ``:noindexentry:`` -- Do not add this item + into the Index, but allow it to be cross-referenced. +* ``no-contents-entry`` and ``:nocontentsentry:`` -- Exclude this item + from the Table of Contents. +* ``no-typesetting`` -- Create TOC, Index and cross-referencing + entities, but don't actually display the content. + + +QAPI standard options +--------------------- + +All QAPI directives -- *except* for module -- support these common options. + +* ``:module: modname`` -- Borrowed from the Python domain, this option allows + you to override the module association of a given definition. +* ``:since: x.y`` -- Allows the documenting of "Since" information, which is + displayed in the signature bar. +* ``:ifcond: CONDITION`` -- Allows the documenting of conditional availability + information, which is displayed in an eyecatch just below the + signature bar. +* ``:deprecated:`` -- Adds an eyecatch just below the signature bar that + advertises that this definition is deprecated and should be avoided. +* ``:unstable:`` -- Adds an eyecatch just below the signature bar that + advertises that this definition is unstable and should not be used in + production code. + + +qapi:module +----------- + +The ``qapi:module`` directive marks the start of a QAPI module. It may have +a content body, but it can be omitted. All subsequent QAPI directives +are associated with the most recent module; this effects their "fully +qualified" name, but has no other effect. + +Example:: + + .. qapi:module:: block-core + + Welcome to the block-core module! + +Will be rendered as: + +.. qapi:module:: block-core + :noindex: + + Welcome to the block-core module! + + +qapi:command +------------ + +This directive documents a QMP command. It may use any of the standard +Sphinx or QAPI options, and the documentation body may contain +``:arg:``, ``:feat:``, ``:error:``, or ``:return:`` info field list +entries. + +Example:: + + .. qapi:command:: x-fake-command + :since: 42.0 + :unstable: + + This command is fake, so it can't hurt you! + + :arg int foo: Your favorite number. + :arg string? bar: Your favorite season. + :return [string]: A lovely computer-written poem for you. + + +Will be rendered as: + + .. qapi:command:: x-fake-command + :noindex: + :since: 42.0 + :unstable: + + This command is fake, so it can't hurt you! + + :arg int foo: Your favorite number. + :arg string? bar: Your favorite season. + :return [string]: A lovely computer-written poem for you. + + +qapi:event +---------- + +This directive documents a QMP event. It may use any of the standard +Sphinx or QAPI options, and the documentation body may contain +``:memb:`` or ``:feat:`` info field list entries. + +Example:: + + .. qapi:event:: COMPUTER_IS_RUINED + :since: 0.1 + :deprecated: + + This event is emitted when your computer is *extremely* ruined. + + :memb string reason: Diagnostics as to what caused your computer to + be ruined. + :feat sadness: When present, the diagnostic message will also + explain how sad the computer is as a result of your wrongdoings. + +Will be rendered as: + +.. qapi:event:: COMPUTER_IS_RUINED + :noindex: + :since: 0.1 + :deprecated: + + This event is emitted when your computer is *extremely* ruined. + + :memb string reason: Diagnostics as to what caused your computer to + be ruined. + :feat sadness: When present, the diagnostic message will also explain + how sad the computer is as a result of your wrongdoings. + + +qapi:enum +--------- + +This directive documents a QAPI enum. It may use any of the standard +Sphinx or QAPI options, and the documentation body may contain +``:value:`` or ``:feat:`` info field list entries. + +Example:: + + .. qapi:enum:: Mood + :ifcond: LIB_PERSONALITY + + This enum represents your virtual machine's current mood! + + :value Happy: Your VM is content and well-fed. + :value Hungry: Your VM needs food. + :value Melancholic: Your VM is experiencing existential angst. + :value Petulant: Your VM is throwing a temper tantrum. + +Will be rendered as: + +.. qapi:enum:: Mood + :noindex: + :ifcond: LIB_PERSONALITY + + This enum represents your virtual machine's current mood! + + :value Happy: Your VM is content and well-fed. + :value Hungry: Your VM needs food. + :value Melancholic: Your VM is experiencing existential angst. + :value Petulant: Your VM is throwing a temper tantrum. + + +qapi:object +----------- + +This directive documents a QAPI structure or union and represents a QMP +object. It may use any of the standard Sphinx or QAPI options, and the +documentation body may contain ``:memb:`` or ``:feat:`` info field list +entries. + +Example:: + + .. qapi:object:: BigBlobOfStuff + + This object has a bunch of disparate and unrelated things in it. + + :memb int Birthday: Your birthday, represented in seconds since the + UNIX epoch. + :memb [string] Fav-Foods: A list of your favorite foods. + :memb boolean? Bizarre-Docs: True if the documentation reference + should be strange. + +Will be rendered as: + +.. qapi:object:: BigBlobOfStuff + :noindex: + + This object has a bunch of disparate and unrelated things in it. + + :memb int Birthday: Your birthday, represented in seconds since the + UNIX epoch. + :memb [string] Fav-Foods: A list of your favorite foods. + :memb boolean? Bizarre-Docs: True if the documentation reference + should be strange. + + +qapi:alternate +-------------- + +This directive documents a QAPI alternate. It may use any of the +standard Sphinx or QAPI options, and the documentation body may contain +``:alt:`` or ``:feat:`` info field list entries. + +Example:: + + .. qapi:alternate:: ErrorCode + + This alternate represents an Error Code from the VM. + + :alt int ec: An error code, like the type you're used to. + :alt string em: An expletive-laced error message, if your + computer is feeling particularly cranky and tired of your + antics. + +Will be rendered as: + +.. qapi:alternate:: ErrorCode + :noindex: + + This alternate represents an Error Code from the VM. + + :alt int ec: An error code, like the type you're used to. + :alt string em: An expletive-laced error message, if your + computer is feeling particularly cranky and tired of your + antics. From e95ffabbde1e1ea76ffc48bd04a0138b12b08a6e Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 10 Mar 2025 23:43:01 -0400 Subject: [PATCH 0878/1179] MAINTAINERS: Add jsnow as maintainer for Sphinx documentation Since I've just about rewritten the entirety of the QAPI documentation system, it's probably fair that I be the contact point for if it goes awry. Signed-off-by: John Snow Message-ID: <20250311034303.75779-64-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 0e5db7a57449..55af2f8d175f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4325,6 +4325,7 @@ S: Orphan F: po/*.po Sphinx documentation configuration and build machinery +M: John Snow M: Peter Maydell S: Maintained F: docs/conf.py From 93db9c84fc40b82d6bc3a944cb8eb8443980824c Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 11 Mar 2025 07:53:52 +0100 Subject: [PATCH 0879/1179] scripts/qapi/backend: Clean up create_backend()'s failure mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit create_backend()'s caller catches QAPIError, and returns non-zero exit code on catch. The caller's caller passes the exit code to sys.exit(). create_backend() doesn't care: it reports errors to stderr and sys.exit()s. Change it to raise QAPIError instead. Signed-off-by: Markus Armbruster Message-ID: <20250311065352.992307-1-armbru@redhat.com> Reviewed-by: Philippe Mathieu-Daudé --- scripts/qapi/main.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py index 5b4679abcf11..0e2a6ae3f070 100644 --- a/scripts/qapi/main.py +++ b/scripts/qapi/main.py @@ -31,34 +31,28 @@ def create_backend(path: str) -> QAPIBackend: module_path, dot, class_name = path.rpartition('.') if not dot: - print("argument of -B must be of the form MODULE.CLASS", - file=sys.stderr) - sys.exit(1) + raise QAPIError("argument of -B must be of the form MODULE.CLASS") try: mod = import_module(module_path) except Exception as ex: - print(f"unable to import '{module_path}': {ex}", file=sys.stderr) - sys.exit(1) + raise QAPIError(f"unable to import '{module_path}': {ex}") from ex try: klass = getattr(mod, class_name) - except AttributeError: - print(f"module '{module_path}' has no class '{class_name}'", - file=sys.stderr) - sys.exit(1) + except AttributeError as ex: + raise QAPIError( + f"module '{module_path}' has no class '{class_name}'") from ex try: backend = klass() except Exception as ex: - print(f"backend '{path}' cannot be instantiated: {ex}", - file=sys.stderr) - sys.exit(1) + raise QAPIError( + f"backend '{path}' cannot be instantiated: {ex}") from ex if not isinstance(backend, QAPIBackend): - print(f"backend '{path}' must be an instance of QAPIBackend", - file=sys.stderr) - sys.exit(1) + raise QAPIError( + f"backend '{path}' must be an instance of QAPIBackend") return backend From e5029e28f0f5ef7e7d5b927aa61a32232be99438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Tue, 4 Feb 2025 09:06:47 +0100 Subject: [PATCH 0880/1179] ppc/ppc405: Remove tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we are about to remove all support for PPC 405, start by removing the tests referring to the ref405ep machine. Link: https://lore.kernel.org/qemu-devel/20250110141800.1587589-2-clg@redhat.com Signed-off-by: Cédric Le Goater Reviewed-by: Nicholas Piggin Message-ID: <20250204080649.836155-2-clg@redhat.com> Signed-off-by: Nicholas Piggin --- tests/functional/meson.build | 1 - tests/functional/test_ppc_405.py | 37 -------------------------------- tests/qtest/m48t59-test.c | 5 ----- tests/qtest/meson.build | 1 - 4 files changed, 44 deletions(-) delete mode 100755 tests/functional/test_ppc_405.py diff --git a/tests/functional/meson.build b/tests/functional/meson.build index e78560a9011d..74f8414a0c74 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -207,7 +207,6 @@ tests_ppc_system_quick = [ ] tests_ppc_system_thorough = [ - 'ppc_405', 'ppc_40p', 'ppc_amiga', 'ppc_bamboo', diff --git a/tests/functional/test_ppc_405.py b/tests/functional/test_ppc_405.py deleted file mode 100755 index 9851c03ee998..000000000000 --- a/tests/functional/test_ppc_405.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# -# Test that the U-Boot firmware boots on ppc 405 machines and check the console -# -# Copyright (c) 2021 Red Hat, Inc. -# -# This work is licensed under the terms of the GNU GPL, version 2 or -# later. See the COPYING file in the top-level directory. - -from qemu_test import QemuSystemTest, Asset -from qemu_test import wait_for_console_pattern -from qemu_test import exec_command_and_wait_for_pattern - -class Ppc405Machine(QemuSystemTest): - - timeout = 90 - - ASSET_UBOOT = Asset( - ('https://gitlab.com/huth/u-boot/-/raw/taihu-2021-10-09/' - 'u-boot-taihu.bin'), - 'a076bb6cdeaafa406330e51e074b66d8878d9036d67d4caa0137be03ee4c112c') - - def do_test_ppc405(self): - file_path = self.ASSET_UBOOT.fetch() - self.vm.set_console(console_index=1) - self.vm.add_args('-bios', file_path) - self.vm.launch() - wait_for_console_pattern(self, 'AMCC PPC405EP Evaluation Board') - exec_command_and_wait_for_pattern(self, 'reset', 'AMCC PowerPC 405EP') - - def test_ppc_ref405ep(self): - self.require_accelerator("tcg") - self.set_machine('ref405ep') - self.do_test_ppc405() - -if __name__ == '__main__': - QemuSystemTest.main() diff --git a/tests/qtest/m48t59-test.c b/tests/qtest/m48t59-test.c index 605797ab785d..1e39a0e8f07a 100644 --- a/tests/qtest/m48t59-test.c +++ b/tests/qtest/m48t59-test.c @@ -247,11 +247,6 @@ static void base_setup(void) base_year = 1968; base_machine = "SS-5"; use_mmio = true; - } else if (g_str_equal(arch, "ppc") || g_str_equal(arch, "ppc64")) { - base = 0xF0000000; - base_year = 1968; - base_machine = "ref405ep"; - use_mmio = true; } else { g_assert_not_reached(); } diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 8a6243382a18..b23fe67db7ac 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -171,7 +171,6 @@ qtests_mips64el = qtests_mips qtests_ppc = \ qtests_filter + \ (config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \ - (config_all_devices.has_key('CONFIG_M48T59') ? ['m48t59-test'] : []) + \ (config_all_accel.has_key('CONFIG_TCG') ? ['prom-env-test'] : []) + \ (config_all_accel.has_key('CONFIG_TCG') ? ['boot-serial-test'] : []) + \ ['boot-order-test'] From e7dba30e827d0ab59b23de444a4d7f7412430223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Tue, 4 Feb 2025 09:06:48 +0100 Subject: [PATCH 0881/1179] ppc/ppc405: Remove boards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ref405ep machine is the only PPC 405 machine. Drop all support by removing the SoC and associated devices as-well as the machine. Link: https://lore.kernel.org/qemu-devel/20250110141800.1587589-3-clg@redhat.com Signed-off-by: Cédric Le Goater Reviewed-by: Nicholas Piggin Message-ID: <20250204080649.836155-3-clg@redhat.com> Signed-off-by: Nicholas Piggin --- MAINTAINERS | 6 - docs/about/deprecated.rst | 8 - docs/about/removed-features.rst | 7 + docs/system/ppc/embedded.rst | 1 - hw/ppc/Kconfig | 9 - hw/ppc/meson.build | 3 - hw/ppc/ppc405.h | 186 ----- hw/ppc/ppc405_boards.c | 520 ------------- hw/ppc/ppc405_uc.c | 1216 ------------------------------- 9 files changed, 7 insertions(+), 1949 deletions(-) delete mode 100644 hw/ppc/ppc405.h delete mode 100644 hw/ppc/ppc405_boards.c delete mode 100644 hw/ppc/ppc405_uc.c diff --git a/MAINTAINERS b/MAINTAINERS index 0e5db7a57449..e2f538fc1625 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1407,12 +1407,6 @@ F: hw/openrisc/openrisc_sim.c PowerPC Machines ---------------- -405 (ref405ep) -L: qemu-ppc@nongnu.org -S: Orphan -F: hw/ppc/ppc405* -F: tests/functional/test_ppc_405.py - Bamboo L: qemu-ppc@nongnu.org S: Orphan diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 589951b13683..3d39d2a9da52 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -277,14 +277,6 @@ deprecated; use the new name ``dtb-randomness`` instead. The new name better reflects the way this property affects all random data within the device tree blob, not just the ``kaslr-seed`` node. -PPC 405 ``ref405ep`` machine (since 9.1) -'''''''''''''''''''''''''''''''''''''''' - -The ``ref405ep`` machine and PPC 405 CPU have no known users, firmware -images are not available, OpenWRT dropped support in 2019, U-Boot in -2017, Linux also is dropping support in 2024. It is time to let go of -this ancient hardware and focus on newer CPUs and platforms. - Big-Endian variants of MicroBlaze ``petalogix-ml605`` and ``xlnx-zynqmp-pmu`` machines (since 9.2) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' diff --git a/docs/about/removed-features.rst b/docs/about/removed-features.rst index 156c0c253c82..2527a91795ad 100644 --- a/docs/about/removed-features.rst +++ b/docs/about/removed-features.rst @@ -1064,6 +1064,13 @@ for all machine types using the PXA2xx and OMAP2 SoCs. We are also dropping the ``cheetah`` OMAP1 board, because we don't have any test images for it and don't know of anybody who does. +ppc ``ref405ep`` machine (removed in 10.0) +'''''''''''''''''''''''''''''''''''''''''' + +This machine was removed because PPC 405 CPU have no known users, +firmware images are not available, OpenWRT dropped support in 2019, +U-Boot in 2017, and Linux in 2024. + linux-user mode CPUs -------------------- diff --git a/docs/system/ppc/embedded.rst b/docs/system/ppc/embedded.rst index af3b3d9fa460..5cb7d98b450d 100644 --- a/docs/system/ppc/embedded.rst +++ b/docs/system/ppc/embedded.rst @@ -4,6 +4,5 @@ Embedded family boards - ``bamboo`` bamboo - ``mpc8544ds`` mpc8544ds - ``ppce500`` generic paravirt e500 platform -- ``ref405ep`` ref405ep - ``sam460ex`` aCube Sam460ex - ``virtex-ml507`` Xilinx Virtex ML507 reference design diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig index b44d91bebb2a..ced6bbc7404e 100644 --- a/hw/ppc/Kconfig +++ b/hw/ppc/Kconfig @@ -44,15 +44,6 @@ config POWERNV select SSI_M25P80 select PNV_SPI -config PPC405 - bool - default y - depends on PPC - select M48T59 - select PFLASH_CFI02 - select PPC4XX - select SERIAL_MM - config PPC440 bool default y diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build index 7cd91898699e..9893f8adebb0 100644 --- a/hw/ppc/meson.build +++ b/hw/ppc/meson.build @@ -57,9 +57,6 @@ ppc_ss.add(when: 'CONFIG_POWERNV', if_true: files( 'pnv_n1_chiplet.c', )) # PowerPC 4xx boards -ppc_ss.add(when: 'CONFIG_PPC405', if_true: files( - 'ppc405_boards.c', - 'ppc405_uc.c')) ppc_ss.add(when: 'CONFIG_PPC440', if_true: files( 'ppc440_bamboo.c', 'ppc440_uc.c')) diff --git a/hw/ppc/ppc405.h b/hw/ppc/ppc405.h deleted file mode 100644 index 9a4312691e15..000000000000 --- a/hw/ppc/ppc405.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * QEMU PowerPC 405 shared definitions - * - * Copyright (c) 2007 Jocelyn Mayer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef PPC405_H -#define PPC405_H - -#include "qom/object.h" -#include "hw/ppc/ppc4xx.h" -#include "hw/intc/ppc-uic.h" -#include "hw/i2c/ppc4xx_i2c.h" - -/* PLB to OPB bridge */ -#define TYPE_PPC405_POB "ppc405-pob" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405PobState, PPC405_POB); -struct Ppc405PobState { - Ppc4xxDcrDeviceState parent_obj; - - uint32_t bear; - uint32_t besr0; - uint32_t besr1; -}; - -/* OPB arbitrer */ -#define TYPE_PPC405_OPBA "ppc405-opba" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405OpbaState, PPC405_OPBA); -struct Ppc405OpbaState { - SysBusDevice parent_obj; - - MemoryRegion io; - uint8_t cr; - uint8_t pr; -}; - -/* DMA controller */ -#define TYPE_PPC405_DMA "ppc405-dma" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405DmaState, PPC405_DMA); -struct Ppc405DmaState { - Ppc4xxDcrDeviceState parent_obj; - - qemu_irq irqs[4]; - uint32_t cr[4]; - uint32_t ct[4]; - uint32_t da[4]; - uint32_t sa[4]; - uint32_t sg[4]; - uint32_t sr; - uint32_t sgc; - uint32_t slp; - uint32_t pol; -}; - -/* GPIO */ -#define TYPE_PPC405_GPIO "ppc405-gpio" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405GpioState, PPC405_GPIO); -struct Ppc405GpioState { - SysBusDevice parent_obj; - - MemoryRegion io; - uint32_t or; - uint32_t tcr; - uint32_t osrh; - uint32_t osrl; - uint32_t tsrh; - uint32_t tsrl; - uint32_t odr; - uint32_t ir; - uint32_t rr1; - uint32_t isr1h; - uint32_t isr1l; -}; - -/* On Chip Memory */ -#define TYPE_PPC405_OCM "ppc405-ocm" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405OcmState, PPC405_OCM); -struct Ppc405OcmState { - Ppc4xxDcrDeviceState parent_obj; - - MemoryRegion ram; - MemoryRegion isarc_ram; - MemoryRegion dsarc_ram; - uint32_t isarc; - uint32_t isacntl; - uint32_t dsarc; - uint32_t dsacntl; -}; - -/* General purpose timers */ -#define TYPE_PPC405_GPT "ppc405-gpt" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405GptState, PPC405_GPT); -struct Ppc405GptState { - SysBusDevice parent_obj; - - MemoryRegion iomem; - - int64_t tb_offset; - uint32_t tb_freq; - QEMUTimer *timer; - qemu_irq irqs[5]; - uint32_t oe; - uint32_t ol; - uint32_t im; - uint32_t is; - uint32_t ie; - uint32_t comp[5]; - uint32_t mask[5]; -}; - -#define TYPE_PPC405_CPC "ppc405-cpc" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405CpcState, PPC405_CPC); - -enum { - PPC405EP_CPU_CLK = 0, - PPC405EP_PLB_CLK = 1, - PPC405EP_OPB_CLK = 2, - PPC405EP_EBC_CLK = 3, - PPC405EP_MAL_CLK = 4, - PPC405EP_PCI_CLK = 5, - PPC405EP_UART0_CLK = 6, - PPC405EP_UART1_CLK = 7, - PPC405EP_CLK_NB = 8, -}; - -struct Ppc405CpcState { - Ppc4xxDcrDeviceState parent_obj; - - uint32_t sysclk; - clk_setup_t clk_setup[PPC405EP_CLK_NB]; - uint32_t boot; - uint32_t epctl; - uint32_t pllmr[2]; - uint32_t ucr; - uint32_t srr; - uint32_t jtagid; - uint32_t pci; - /* Clock and power management */ - uint32_t er; - uint32_t fr; - uint32_t sr; -}; - -#define TYPE_PPC405_SOC "ppc405-soc" -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405SoCState, PPC405_SOC); - -struct Ppc405SoCState { - /* Private */ - DeviceState parent_obj; - - /* Public */ - PowerPCCPU cpu; - PPCUIC uic; - Ppc405CpcState cpc; - Ppc405GptState gpt; - Ppc405OcmState ocm; - Ppc405GpioState gpio; - Ppc405DmaState dma; - PPC4xxI2CState i2c; - Ppc4xxEbcState ebc; - Ppc405OpbaState opba; - Ppc405PobState pob; - Ppc4xxPlbState plb; - Ppc4xxMalState mal; - Ppc4xxSdramDdrState sdram; -}; - -#endif /* PPC405_H */ diff --git a/hw/ppc/ppc405_boards.c b/hw/ppc/ppc405_boards.c deleted file mode 100644 index 969cac345ac8..000000000000 --- a/hw/ppc/ppc405_boards.c +++ /dev/null @@ -1,520 +0,0 @@ -/* - * QEMU PowerPC 405 evaluation boards emulation - * - * Copyright (c) 2007 Jocelyn Mayer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "qemu/osdep.h" -#include "qemu/units.h" -#include "qapi/error.h" -#include "qemu/datadir.h" -#include "cpu.h" -#include "hw/ppc/ppc.h" -#include "hw/qdev-properties.h" -#include "hw/sysbus.h" -#include "ppc405.h" -#include "hw/rtc/m48t59.h" -#include "hw/block/flash.h" -#include "system/qtest.h" -#include "system/reset.h" -#include "system/block-backend.h" -#include "hw/boards.h" -#include "qemu/error-report.h" -#include "hw/loader.h" -#include "qemu/cutils.h" -#include "elf.h" - -#define BIOS_FILENAME "ppc405_rom.bin" -#define BIOS_SIZE (2 * MiB) - -#define KERNEL_LOAD_ADDR 0x01000000 -#define INITRD_LOAD_ADDR 0x01800000 - -#define PPC405EP_SDRAM_BASE 0x00000000 -#define PPC405EP_SRAM_BASE 0xFFF00000 -#define PPC405EP_SRAM_SIZE (512 * KiB) - -#define USE_FLASH_BIOS - -#define TYPE_PPC405_MACHINE MACHINE_TYPE_NAME("ppc405") -OBJECT_DECLARE_SIMPLE_TYPE(Ppc405MachineState, PPC405_MACHINE); - -struct Ppc405MachineState { - /* Private */ - MachineState parent_obj; - /* Public */ - - Ppc405SoCState soc; -}; - -/* CPU reset handler when booting directly from a loaded kernel */ -static struct boot_info { - uint32_t entry; - uint32_t bdloc; - uint32_t initrd_base; - uint32_t initrd_size; - uint32_t cmdline_base; - uint32_t cmdline_size; -} boot_info; - -static void main_cpu_reset(void *opaque) -{ - PowerPCCPU *cpu = opaque; - CPUPPCState *env = &cpu->env; - struct boot_info *bi = env->load_info; - - cpu_reset(CPU(cpu)); - - /* stack: top of sram */ - env->gpr[1] = PPC405EP_SRAM_BASE + PPC405EP_SRAM_SIZE - 8; - - /* Tune our boot state */ - env->gpr[3] = bi->bdloc; - env->gpr[4] = bi->initrd_base; - env->gpr[5] = bi->initrd_base + bi->initrd_size; - env->gpr[6] = bi->cmdline_base; - env->gpr[7] = bi->cmdline_size; - - env->nip = bi->entry; -} - -/* Bootinfo as set-up by u-boot */ -typedef struct { - uint32_t bi_memstart; - uint32_t bi_memsize; - uint32_t bi_flashstart; - uint32_t bi_flashsize; - uint32_t bi_flashoffset; /* 0x10 */ - uint32_t bi_sramstart; - uint32_t bi_sramsize; - uint32_t bi_bootflags; - uint32_t bi_ipaddr; /* 0x20 */ - uint8_t bi_enetaddr[6]; - uint16_t bi_ethspeed; - uint32_t bi_intfreq; - uint32_t bi_busfreq; /* 0x30 */ - uint32_t bi_baudrate; - uint8_t bi_s_version[4]; - uint8_t bi_r_version[32]; - uint32_t bi_procfreq; - uint32_t bi_plb_busfreq; - uint32_t bi_pci_busfreq; - uint8_t bi_pci_enetaddr[6]; - uint8_t bi_pci_enetaddr2[6]; /* PPC405EP specific */ - uint32_t bi_opbfreq; - uint32_t bi_iic_fast[2]; -} ppc4xx_bd_info_t; - -static void ppc405_set_default_bootinfo(ppc4xx_bd_info_t *bd, - ram_addr_t ram_size) -{ - memset(bd, 0, sizeof(*bd)); - - bd->bi_memstart = PPC405EP_SDRAM_BASE; - bd->bi_memsize = ram_size; - bd->bi_sramstart = PPC405EP_SRAM_BASE; - bd->bi_sramsize = PPC405EP_SRAM_SIZE; - bd->bi_bootflags = 0; - bd->bi_intfreq = 133333333; - bd->bi_busfreq = 33333333; - bd->bi_baudrate = 115200; - bd->bi_s_version[0] = 'Q'; - bd->bi_s_version[1] = 'M'; - bd->bi_s_version[2] = 'U'; - bd->bi_s_version[3] = '\0'; - bd->bi_r_version[0] = 'Q'; - bd->bi_r_version[1] = 'E'; - bd->bi_r_version[2] = 'M'; - bd->bi_r_version[3] = 'U'; - bd->bi_r_version[4] = '\0'; - bd->bi_procfreq = 133333333; - bd->bi_plb_busfreq = 33333333; - bd->bi_pci_busfreq = 33333333; - bd->bi_opbfreq = 33333333; -} - -static ram_addr_t __ppc405_set_bootinfo(CPUPPCState *env, ppc4xx_bd_info_t *bd) -{ - CPUState *cs = env_cpu(env); - ram_addr_t bdloc; - int i, n; - - /* We put the bd structure at the top of memory */ - if (bd->bi_memsize >= 0x01000000UL) { - bdloc = 0x01000000UL - sizeof(ppc4xx_bd_info_t); - } else { - bdloc = bd->bi_memsize - sizeof(ppc4xx_bd_info_t); - } - stl_be_phys(cs->as, bdloc + 0x00, bd->bi_memstart); - stl_be_phys(cs->as, bdloc + 0x04, bd->bi_memsize); - stl_be_phys(cs->as, bdloc + 0x08, bd->bi_flashstart); - stl_be_phys(cs->as, bdloc + 0x0C, bd->bi_flashsize); - stl_be_phys(cs->as, bdloc + 0x10, bd->bi_flashoffset); - stl_be_phys(cs->as, bdloc + 0x14, bd->bi_sramstart); - stl_be_phys(cs->as, bdloc + 0x18, bd->bi_sramsize); - stl_be_phys(cs->as, bdloc + 0x1C, bd->bi_bootflags); - stl_be_phys(cs->as, bdloc + 0x20, bd->bi_ipaddr); - for (i = 0; i < 6; i++) { - stb_phys(cs->as, bdloc + 0x24 + i, bd->bi_enetaddr[i]); - } - stw_be_phys(cs->as, bdloc + 0x2A, bd->bi_ethspeed); - stl_be_phys(cs->as, bdloc + 0x2C, bd->bi_intfreq); - stl_be_phys(cs->as, bdloc + 0x30, bd->bi_busfreq); - stl_be_phys(cs->as, bdloc + 0x34, bd->bi_baudrate); - for (i = 0; i < 4; i++) { - stb_phys(cs->as, bdloc + 0x38 + i, bd->bi_s_version[i]); - } - for (i = 0; i < 32; i++) { - stb_phys(cs->as, bdloc + 0x3C + i, bd->bi_r_version[i]); - } - stl_be_phys(cs->as, bdloc + 0x5C, bd->bi_procfreq); - stl_be_phys(cs->as, bdloc + 0x60, bd->bi_plb_busfreq); - stl_be_phys(cs->as, bdloc + 0x64, bd->bi_pci_busfreq); - for (i = 0; i < 6; i++) { - stb_phys(cs->as, bdloc + 0x68 + i, bd->bi_pci_enetaddr[i]); - } - n = 0x70; /* includes 2 bytes hole */ - for (i = 0; i < 6; i++) { - stb_phys(cs->as, bdloc + n++, bd->bi_pci_enetaddr2[i]); - } - stl_be_phys(cs->as, bdloc + n, bd->bi_opbfreq); - n += 4; - for (i = 0; i < 2; i++) { - stl_be_phys(cs->as, bdloc + n, bd->bi_iic_fast[i]); - n += 4; - } - - return bdloc; -} - -static ram_addr_t ppc405_set_bootinfo(CPUPPCState *env, ram_addr_t ram_size) -{ - ppc4xx_bd_info_t bd; - - memset(&bd, 0, sizeof(bd)); - - ppc405_set_default_bootinfo(&bd, ram_size); - - return __ppc405_set_bootinfo(env, &bd); -} - -static void boot_from_kernel(MachineState *machine, PowerPCCPU *cpu) -{ - CPUPPCState *env = &cpu->env; - hwaddr boot_entry; - hwaddr kernel_base; - int kernel_size; - hwaddr initrd_base; - int initrd_size; - ram_addr_t bdloc; - int len; - - bdloc = ppc405_set_bootinfo(env, machine->ram_size); - boot_info.bdloc = bdloc; - - kernel_size = load_elf(machine->kernel_filename, NULL, NULL, NULL, - &boot_entry, &kernel_base, NULL, NULL, - ELFDATA2MSB, PPC_ELF_MACHINE, 0, 0); - if (kernel_size < 0) { - error_report("Could not load kernel '%s' : %s", - machine->kernel_filename, load_elf_strerror(kernel_size)); - exit(1); - } - boot_info.entry = boot_entry; - - /* load initrd */ - if (machine->initrd_filename) { - initrd_base = INITRD_LOAD_ADDR; - initrd_size = load_image_targphys(machine->initrd_filename, initrd_base, - machine->ram_size - initrd_base); - if (initrd_size < 0) { - error_report("could not load initial ram disk '%s'", - machine->initrd_filename); - exit(1); - } - - boot_info.initrd_base = initrd_base; - boot_info.initrd_size = initrd_size; - } - - if (machine->kernel_cmdline) { - len = strlen(machine->kernel_cmdline); - bdloc -= ((len + 255) & ~255); - cpu_physical_memory_write(bdloc, machine->kernel_cmdline, len + 1); - boot_info.cmdline_base = bdloc; - boot_info.cmdline_size = bdloc + len; - } - - /* Install our custom reset handler to start from Linux */ - qemu_register_reset(main_cpu_reset, cpu); - env->load_info = &boot_info; -} - -static void ppc405_init(MachineState *machine) -{ - Ppc405MachineState *ppc405 = PPC405_MACHINE(machine); - const char *kernel_filename = machine->kernel_filename; - MemoryRegion *sysmem = get_system_memory(); - - object_initialize_child(OBJECT(machine), "soc", &ppc405->soc, - TYPE_PPC405_SOC); - object_property_set_link(OBJECT(&ppc405->soc), "dram", - OBJECT(machine->ram), &error_abort); - object_property_set_uint(OBJECT(&ppc405->soc), "sys-clk", 33333333, - &error_abort); - qdev_realize(DEVICE(&ppc405->soc), NULL, &error_fatal); - - /* allocate and load BIOS */ - if (machine->firmware) { - MemoryRegion *bios = g_new(MemoryRegion, 1); - g_autofree char *filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, - machine->firmware); - long bios_size; - - memory_region_init_rom(bios, NULL, "ef405ep.bios", BIOS_SIZE, - &error_fatal); - - if (!filename) { - error_report("Could not find firmware '%s'", machine->firmware); - exit(1); - } - - bios_size = load_image_size(filename, - memory_region_get_ram_ptr(bios), - BIOS_SIZE); - if (bios_size < 0) { - error_report("Could not load PowerPC BIOS '%s'", machine->firmware); - exit(1); - } - - bios_size = (bios_size + 0xfff) & ~0xfff; - memory_region_add_subregion(sysmem, (uint32_t)(-bios_size), bios); - } - - /* Load kernel and initrd using U-Boot images */ - if (kernel_filename && machine->firmware) { - target_ulong kernel_base, initrd_base; - long kernel_size, initrd_size; - - kernel_base = KERNEL_LOAD_ADDR; - kernel_size = load_image_targphys(kernel_filename, kernel_base, - machine->ram_size - kernel_base); - if (kernel_size < 0) { - error_report("could not load kernel '%s'", kernel_filename); - exit(1); - } - - /* load initrd */ - if (machine->initrd_filename) { - initrd_base = INITRD_LOAD_ADDR; - initrd_size = load_image_targphys(machine->initrd_filename, - initrd_base, - machine->ram_size - initrd_base); - if (initrd_size < 0) { - error_report("could not load initial ram disk '%s'", - machine->initrd_filename); - exit(1); - } - } - - /* Load ELF kernel and rootfs.cpio */ - } else if (kernel_filename && !machine->firmware) { - ppc4xx_sdram_ddr_enable(&ppc405->soc.sdram); - boot_from_kernel(machine, &ppc405->soc.cpu); - } -} - -static void ppc405_machine_class_init(ObjectClass *oc, void *data) -{ - MachineClass *mc = MACHINE_CLASS(oc); - - mc->desc = "PPC405 generic machine"; - mc->init = ppc405_init; - mc->default_ram_size = 128 * MiB; - mc->default_ram_id = "ppc405.ram"; - mc->deprecation_reason = "machine is old and unmaintained"; -} - -static const TypeInfo ppc405_machine_type = { - .name = TYPE_PPC405_MACHINE, - .parent = TYPE_MACHINE, - .instance_size = sizeof(Ppc405MachineState), - .class_init = ppc405_machine_class_init, - .abstract = true, -}; - -/*****************************************************************************/ -/* PPC405EP reference board (IBM) */ -/* - * Standalone board with: - * - PowerPC 405EP CPU - * - SDRAM (0x00000000) - * - Flash (0xFFF80000) - * - SRAM (0xFFF00000) - * - NVRAM (0xF0000000) - * - FPGA (0xF0300000) - */ - -#define PPC405EP_NVRAM_BASE 0xF0000000 -#define PPC405EP_FPGA_BASE 0xF0300000 -#define PPC405EP_FLASH_BASE 0xFFF80000 - -#define TYPE_REF405EP_FPGA "ref405ep-fpga" -OBJECT_DECLARE_SIMPLE_TYPE(Ref405epFpgaState, REF405EP_FPGA); -struct Ref405epFpgaState { - SysBusDevice parent_obj; - - MemoryRegion iomem; - - uint8_t reg0; - uint8_t reg1; -}; - -static uint64_t ref405ep_fpga_readb(void *opaque, hwaddr addr, unsigned size) -{ - Ref405epFpgaState *fpga = opaque; - uint32_t ret; - - switch (addr) { - case 0x0: - ret = fpga->reg0; - break; - case 0x1: - ret = fpga->reg1; - break; - default: - ret = 0; - break; - } - - return ret; -} - -static void ref405ep_fpga_writeb(void *opaque, hwaddr addr, uint64_t value, - unsigned size) -{ - Ref405epFpgaState *fpga = opaque; - - switch (addr) { - case 0x0: - /* Read only */ - break; - case 0x1: - fpga->reg1 = value; - break; - default: - break; - } -} - -static const MemoryRegionOps ref405ep_fpga_ops = { - .read = ref405ep_fpga_readb, - .write = ref405ep_fpga_writeb, - .impl.min_access_size = 1, - .impl.max_access_size = 1, - .valid.min_access_size = 1, - .valid.max_access_size = 4, - .endianness = DEVICE_BIG_ENDIAN, -}; - -static void ref405ep_fpga_reset(DeviceState *dev) -{ - Ref405epFpgaState *fpga = REF405EP_FPGA(dev); - - fpga->reg0 = 0x00; - fpga->reg1 = 0x0F; -} - -static void ref405ep_fpga_realize(DeviceState *dev, Error **errp) -{ - Ref405epFpgaState *s = REF405EP_FPGA(dev); - - memory_region_init_io(&s->iomem, OBJECT(s), &ref405ep_fpga_ops, s, - "fpga", 0x00000100); - sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); -} - -static void ref405ep_fpga_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ref405ep_fpga_realize; - device_class_set_legacy_reset(dc, ref405ep_fpga_reset); - /* Reason: only works as part of a ppc405 board */ - dc->user_creatable = false; -} - -static const TypeInfo ref405ep_fpga_type = { - .name = TYPE_REF405EP_FPGA, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(Ref405epFpgaState), - .class_init = ref405ep_fpga_class_init, -}; - -static void ref405ep_init(MachineState *machine) -{ - DeviceState *dev; - SysBusDevice *s; - MemoryRegion *sram = g_new(MemoryRegion, 1); - - ppc405_init(machine); - - /* allocate SRAM */ - memory_region_init_ram(sram, NULL, "ref405ep.sram", PPC405EP_SRAM_SIZE, - &error_fatal); - memory_region_add_subregion(get_system_memory(), PPC405EP_SRAM_BASE, sram); - - /* Register FPGA */ - dev = qdev_new(TYPE_REF405EP_FPGA); - object_property_add_child(OBJECT(machine), "fpga", OBJECT(dev)); - sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, PPC405EP_FPGA_BASE); - - /* Register NVRAM */ - dev = qdev_new("sysbus-m48t08"); - qdev_prop_set_int32(dev, "base-year", 1968); - s = SYS_BUS_DEVICE(dev); - sysbus_realize_and_unref(s, &error_fatal); - sysbus_mmio_map(s, 0, PPC405EP_NVRAM_BASE); -} - -static void ref405ep_class_init(ObjectClass *oc, void *data) -{ - MachineClass *mc = MACHINE_CLASS(oc); - - mc->desc = "ref405ep"; - mc->init = ref405ep_init; -} - -static const TypeInfo ref405ep_type = { - .name = MACHINE_TYPE_NAME("ref405ep"), - .parent = TYPE_PPC405_MACHINE, - .class_init = ref405ep_class_init, -}; - -static void ppc405_machine_init(void) -{ - type_register_static(&ppc405_machine_type); - type_register_static(&ref405ep_type); - type_register_static(&ref405ep_fpga_type); -} - -type_init(ppc405_machine_init) diff --git a/hw/ppc/ppc405_uc.c b/hw/ppc/ppc405_uc.c deleted file mode 100644 index 8250824a1a24..000000000000 --- a/hw/ppc/ppc405_uc.c +++ /dev/null @@ -1,1216 +0,0 @@ -/* - * QEMU PowerPC 405 embedded processors emulation - * - * Copyright (c) 2007 Jocelyn Mayer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "qemu/osdep.h" -#include "qemu/units.h" -#include "qapi/error.h" -#include "qemu/log.h" -#include "cpu.h" -#include "hw/ppc/ppc.h" -#include "hw/i2c/ppc4xx_i2c.h" -#include "hw/irq.h" -#include "hw/qdev-properties.h" -#include "ppc405.h" -#include "hw/char/serial-mm.h" -#include "qemu/timer.h" -#include "system/reset.h" -#include "system/system.h" -#include "exec/address-spaces.h" -#include "hw/intc/ppc-uic.h" -#include "trace.h" - -/*****************************************************************************/ -/* Shared peripherals */ - -/*****************************************************************************/ -/* PLB to OPB bridge */ -enum { - POB0_BESR0 = 0x0A0, - POB0_BESR1 = 0x0A2, - POB0_BEAR = 0x0A4, -}; - -static uint32_t dcr_read_pob(void *opaque, int dcrn) -{ - Ppc405PobState *pob = opaque; - uint32_t ret; - - switch (dcrn) { - case POB0_BEAR: - ret = pob->bear; - break; - case POB0_BESR0: - ret = pob->besr0; - break; - case POB0_BESR1: - ret = pob->besr1; - break; - default: - /* Avoid gcc warning */ - ret = 0; - break; - } - - return ret; -} - -static void dcr_write_pob(void *opaque, int dcrn, uint32_t val) -{ - Ppc405PobState *pob = opaque; - - switch (dcrn) { - case POB0_BEAR: - /* Read only */ - break; - case POB0_BESR0: - /* Write-clear */ - pob->besr0 &= ~val; - break; - case POB0_BESR1: - /* Write-clear */ - pob->besr1 &= ~val; - break; - } -} - -static void ppc405_pob_reset(DeviceState *dev) -{ - Ppc405PobState *pob = PPC405_POB(dev); - - /* No error */ - pob->bear = 0x00000000; - pob->besr0 = 0x0000000; - pob->besr1 = 0x0000000; -} - -static void ppc405_pob_realize(DeviceState *dev, Error **errp) -{ - Ppc405PobState *pob = PPC405_POB(dev); - Ppc4xxDcrDeviceState *dcr = PPC4xx_DCR_DEVICE(dev); - - ppc4xx_dcr_register(dcr, POB0_BEAR, pob, &dcr_read_pob, &dcr_write_pob); - ppc4xx_dcr_register(dcr, POB0_BESR0, pob, &dcr_read_pob, &dcr_write_pob); - ppc4xx_dcr_register(dcr, POB0_BESR1, pob, &dcr_read_pob, &dcr_write_pob); -} - -static void ppc405_pob_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_pob_realize; - device_class_set_legacy_reset(dc, ppc405_pob_reset); - /* Reason: only works as function of a ppc4xx SoC */ - dc->user_creatable = false; -} - -/*****************************************************************************/ -/* OPB arbitrer */ -static uint64_t opba_readb(void *opaque, hwaddr addr, unsigned size) -{ - Ppc405OpbaState *opba = opaque; - uint32_t ret; - - switch (addr) { - case 0x00: - ret = opba->cr; - break; - case 0x01: - ret = opba->pr; - break; - default: - ret = 0x00; - break; - } - - trace_opba_readb(addr, ret); - return ret; -} - -static void opba_writeb(void *opaque, hwaddr addr, uint64_t value, - unsigned size) -{ - Ppc405OpbaState *opba = opaque; - - trace_opba_writeb(addr, value); - - switch (addr) { - case 0x00: - opba->cr = value & 0xF8; - break; - case 0x01: - opba->pr = value & 0xFF; - break; - default: - break; - } -} -static const MemoryRegionOps opba_ops = { - .read = opba_readb, - .write = opba_writeb, - .impl.min_access_size = 1, - .impl.max_access_size = 1, - .valid.min_access_size = 1, - .valid.max_access_size = 4, - .endianness = DEVICE_BIG_ENDIAN, -}; - -static void ppc405_opba_reset(DeviceState *dev) -{ - Ppc405OpbaState *opba = PPC405_OPBA(dev); - - opba->cr = 0x00; /* No dynamic priorities - park disabled */ - opba->pr = 0x11; -} - -static void ppc405_opba_realize(DeviceState *dev, Error **errp) -{ - Ppc405OpbaState *s = PPC405_OPBA(dev); - - memory_region_init_io(&s->io, OBJECT(s), &opba_ops, s, "opba", 2); - sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->io); -} - -static void ppc405_opba_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_opba_realize; - device_class_set_legacy_reset(dc, ppc405_opba_reset); - /* Reason: only works as function of a ppc4xx SoC */ - dc->user_creatable = false; -} - -/*****************************************************************************/ -/* Code decompression controller */ -/* XXX: TODO */ - -/*****************************************************************************/ -/* DMA controller */ -enum { - DMA0_CR0 = 0x100, - DMA0_CT0 = 0x101, - DMA0_DA0 = 0x102, - DMA0_SA0 = 0x103, - DMA0_SG0 = 0x104, - DMA0_CR1 = 0x108, - DMA0_CT1 = 0x109, - DMA0_DA1 = 0x10A, - DMA0_SA1 = 0x10B, - DMA0_SG1 = 0x10C, - DMA0_CR2 = 0x110, - DMA0_CT2 = 0x111, - DMA0_DA2 = 0x112, - DMA0_SA2 = 0x113, - DMA0_SG2 = 0x114, - DMA0_CR3 = 0x118, - DMA0_CT3 = 0x119, - DMA0_DA3 = 0x11A, - DMA0_SA3 = 0x11B, - DMA0_SG3 = 0x11C, - DMA0_SR = 0x120, - DMA0_SGC = 0x123, - DMA0_SLP = 0x125, - DMA0_POL = 0x126, -}; - -static uint32_t dcr_read_dma(void *opaque, int dcrn) -{ - return 0; -} - -static void dcr_write_dma(void *opaque, int dcrn, uint32_t val) -{ -} - -static void ppc405_dma_reset(DeviceState *dev) -{ - Ppc405DmaState *dma = PPC405_DMA(dev); - int i; - - for (i = 0; i < 4; i++) { - dma->cr[i] = 0x00000000; - dma->ct[i] = 0x00000000; - dma->da[i] = 0x00000000; - dma->sa[i] = 0x00000000; - dma->sg[i] = 0x00000000; - } - dma->sr = 0x00000000; - dma->sgc = 0x00000000; - dma->slp = 0x7C000000; - dma->pol = 0x00000000; -} - -static void ppc405_dma_realize(DeviceState *dev, Error **errp) -{ - Ppc405DmaState *dma = PPC405_DMA(dev); - Ppc4xxDcrDeviceState *dcr = PPC4xx_DCR_DEVICE(dev); - int i; - - for (i = 0; i < ARRAY_SIZE(dma->irqs); i++) { - sysbus_init_irq(SYS_BUS_DEVICE(dma), &dma->irqs[i]); - } - - ppc4xx_dcr_register(dcr, DMA0_CR0, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_CT0, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_DA0, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SA0, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SG0, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_CR1, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_CT1, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_DA1, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SA1, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SG1, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_CR2, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_CT2, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_DA2, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SA2, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SG2, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_CR3, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_CT3, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_DA3, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SA3, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SG3, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SR, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SGC, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_SLP, dma, &dcr_read_dma, &dcr_write_dma); - ppc4xx_dcr_register(dcr, DMA0_POL, dma, &dcr_read_dma, &dcr_write_dma); -} - -static void ppc405_dma_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_dma_realize; - device_class_set_legacy_reset(dc, ppc405_dma_reset); - /* Reason: only works as function of a ppc4xx SoC */ - dc->user_creatable = false; -} - -/*****************************************************************************/ -/* GPIO */ -static uint64_t ppc405_gpio_read(void *opaque, hwaddr addr, unsigned size) -{ - trace_ppc405_gpio_read(addr, size); - return 0; -} - -static void ppc405_gpio_write(void *opaque, hwaddr addr, uint64_t value, - unsigned size) -{ - trace_ppc405_gpio_write(addr, size, value); -} - -static const MemoryRegionOps ppc405_gpio_ops = { - .read = ppc405_gpio_read, - .write = ppc405_gpio_write, - .endianness = DEVICE_NATIVE_ENDIAN, -}; - -static void ppc405_gpio_realize(DeviceState *dev, Error **errp) -{ - Ppc405GpioState *s = PPC405_GPIO(dev); - - memory_region_init_io(&s->io, OBJECT(s), &ppc405_gpio_ops, s, "gpio", - 0x38); - sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->io); -} - -static void ppc405_gpio_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_gpio_realize; - /* Reason: only works as function of a ppc4xx SoC */ - dc->user_creatable = false; -} - -/*****************************************************************************/ -/* On Chip Memory */ -enum { - OCM0_ISARC = 0x018, - OCM0_ISACNTL = 0x019, - OCM0_DSARC = 0x01A, - OCM0_DSACNTL = 0x01B, -}; - -static void ocm_update_mappings(Ppc405OcmState *ocm, - uint32_t isarc, uint32_t isacntl, - uint32_t dsarc, uint32_t dsacntl) -{ - trace_ocm_update_mappings(isarc, isacntl, dsarc, dsacntl, ocm->isarc, - ocm->isacntl, ocm->dsarc, ocm->dsacntl); - - if (ocm->isarc != isarc || - (ocm->isacntl & 0x80000000) != (isacntl & 0x80000000)) { - if (ocm->isacntl & 0x80000000) { - /* Unmap previously assigned memory region */ - trace_ocm_unmap("ISA", ocm->isarc); - memory_region_del_subregion(get_system_memory(), &ocm->isarc_ram); - } - if (isacntl & 0x80000000) { - /* Map new instruction memory region */ - trace_ocm_map("ISA", isarc); - memory_region_add_subregion(get_system_memory(), isarc, - &ocm->isarc_ram); - } - } - if (ocm->dsarc != dsarc || - (ocm->dsacntl & 0x80000000) != (dsacntl & 0x80000000)) { - if (ocm->dsacntl & 0x80000000) { - /* Beware not to unmap the region we just mapped */ - if (!(isacntl & 0x80000000) || ocm->dsarc != isarc) { - /* Unmap previously assigned memory region */ - trace_ocm_unmap("DSA", ocm->dsarc); - memory_region_del_subregion(get_system_memory(), - &ocm->dsarc_ram); - } - } - if (dsacntl & 0x80000000) { - /* Beware not to remap the region we just mapped */ - if (!(isacntl & 0x80000000) || dsarc != isarc) { - /* Map new data memory region */ - trace_ocm_map("DSA", dsarc); - memory_region_add_subregion(get_system_memory(), dsarc, - &ocm->dsarc_ram); - } - } - } -} - -static uint32_t dcr_read_ocm(void *opaque, int dcrn) -{ - Ppc405OcmState *ocm = opaque; - uint32_t ret; - - switch (dcrn) { - case OCM0_ISARC: - ret = ocm->isarc; - break; - case OCM0_ISACNTL: - ret = ocm->isacntl; - break; - case OCM0_DSARC: - ret = ocm->dsarc; - break; - case OCM0_DSACNTL: - ret = ocm->dsacntl; - break; - default: - ret = 0; - break; - } - - return ret; -} - -static void dcr_write_ocm(void *opaque, int dcrn, uint32_t val) -{ - Ppc405OcmState *ocm = opaque; - uint32_t isarc, dsarc, isacntl, dsacntl; - - isarc = ocm->isarc; - dsarc = ocm->dsarc; - isacntl = ocm->isacntl; - dsacntl = ocm->dsacntl; - switch (dcrn) { - case OCM0_ISARC: - isarc = val & 0xFC000000; - break; - case OCM0_ISACNTL: - isacntl = val & 0xC0000000; - break; - case OCM0_DSARC: - isarc = val & 0xFC000000; - break; - case OCM0_DSACNTL: - isacntl = val & 0xC0000000; - break; - } - ocm_update_mappings(ocm, isarc, isacntl, dsarc, dsacntl); - ocm->isarc = isarc; - ocm->dsarc = dsarc; - ocm->isacntl = isacntl; - ocm->dsacntl = dsacntl; -} - -static void ppc405_ocm_reset(DeviceState *dev) -{ - Ppc405OcmState *ocm = PPC405_OCM(dev); - uint32_t isarc, dsarc, isacntl, dsacntl; - - isarc = 0x00000000; - isacntl = 0x00000000; - dsarc = 0x00000000; - dsacntl = 0x00000000; - ocm_update_mappings(ocm, isarc, isacntl, dsarc, dsacntl); - ocm->isarc = isarc; - ocm->dsarc = dsarc; - ocm->isacntl = isacntl; - ocm->dsacntl = dsacntl; -} - -static void ppc405_ocm_realize(DeviceState *dev, Error **errp) -{ - Ppc405OcmState *ocm = PPC405_OCM(dev); - Ppc4xxDcrDeviceState *dcr = PPC4xx_DCR_DEVICE(dev); - - /* XXX: Size is 4096 or 0x04000000 */ - memory_region_init_ram(&ocm->isarc_ram, OBJECT(ocm), "ppc405.ocm", 4 * KiB, - &error_fatal); - memory_region_init_alias(&ocm->dsarc_ram, OBJECT(ocm), "ppc405.dsarc", - &ocm->isarc_ram, 0, 4 * KiB); - - ppc4xx_dcr_register(dcr, OCM0_ISARC, ocm, &dcr_read_ocm, &dcr_write_ocm); - ppc4xx_dcr_register(dcr, OCM0_ISACNTL, ocm, &dcr_read_ocm, &dcr_write_ocm); - ppc4xx_dcr_register(dcr, OCM0_DSARC, ocm, &dcr_read_ocm, &dcr_write_ocm); - ppc4xx_dcr_register(dcr, OCM0_DSACNTL, ocm, &dcr_read_ocm, &dcr_write_ocm); -} - -static void ppc405_ocm_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_ocm_realize; - device_class_set_legacy_reset(dc, ppc405_ocm_reset); - /* Reason: only works as function of a ppc4xx SoC */ - dc->user_creatable = false; -} - -/*****************************************************************************/ -/* General purpose timers */ -static int ppc4xx_gpt_compare(Ppc405GptState *gpt, int n) -{ - /* XXX: TODO */ - return 0; -} - -static void ppc4xx_gpt_set_output(Ppc405GptState *gpt, int n, int level) -{ - /* XXX: TODO */ -} - -static void ppc4xx_gpt_set_outputs(Ppc405GptState *gpt) -{ - uint32_t mask; - int i; - - mask = 0x80000000; - for (i = 0; i < 5; i++) { - if (gpt->oe & mask) { - /* Output is enabled */ - if (ppc4xx_gpt_compare(gpt, i)) { - /* Comparison is OK */ - ppc4xx_gpt_set_output(gpt, i, gpt->ol & mask); - } else { - /* Comparison is KO */ - ppc4xx_gpt_set_output(gpt, i, gpt->ol & mask ? 0 : 1); - } - } - mask = mask >> 1; - } -} - -static void ppc4xx_gpt_set_irqs(Ppc405GptState *gpt) -{ - uint32_t mask; - int i; - - mask = 0x00008000; - for (i = 0; i < 5; i++) { - if (gpt->is & gpt->im & mask) { - qemu_irq_raise(gpt->irqs[i]); - } else { - qemu_irq_lower(gpt->irqs[i]); - } - mask = mask >> 1; - } -} - -static void ppc4xx_gpt_compute_timer(Ppc405GptState *gpt) -{ - /* XXX: TODO */ -} - -static uint64_t ppc4xx_gpt_read(void *opaque, hwaddr addr, unsigned size) -{ - Ppc405GptState *gpt = opaque; - uint32_t ret; - int idx; - - trace_ppc4xx_gpt_read(addr, size); - - switch (addr) { - case 0x00: - /* Time base counter */ - ret = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + gpt->tb_offset, - gpt->tb_freq, NANOSECONDS_PER_SECOND); - break; - case 0x10: - /* Output enable */ - ret = gpt->oe; - break; - case 0x14: - /* Output level */ - ret = gpt->ol; - break; - case 0x18: - /* Interrupt mask */ - ret = gpt->im; - break; - case 0x1C: - case 0x20: - /* Interrupt status */ - ret = gpt->is; - break; - case 0x24: - /* Interrupt enable */ - ret = gpt->ie; - break; - case 0x80 ... 0x90: - /* Compare timer */ - idx = (addr - 0x80) >> 2; - ret = gpt->comp[idx]; - break; - case 0xC0 ... 0xD0: - /* Compare mask */ - idx = (addr - 0xC0) >> 2; - ret = gpt->mask[idx]; - break; - default: - ret = -1; - break; - } - - return ret; -} - -static void ppc4xx_gpt_write(void *opaque, hwaddr addr, uint64_t value, - unsigned size) -{ - Ppc405GptState *gpt = opaque; - int idx; - - trace_ppc4xx_gpt_write(addr, size, value); - - switch (addr) { - case 0x00: - /* Time base counter */ - gpt->tb_offset = muldiv64(value, NANOSECONDS_PER_SECOND, gpt->tb_freq) - - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - ppc4xx_gpt_compute_timer(gpt); - break; - case 0x10: - /* Output enable */ - gpt->oe = value & 0xF8000000; - ppc4xx_gpt_set_outputs(gpt); - break; - case 0x14: - /* Output level */ - gpt->ol = value & 0xF8000000; - ppc4xx_gpt_set_outputs(gpt); - break; - case 0x18: - /* Interrupt mask */ - gpt->im = value & 0x0000F800; - break; - case 0x1C: - /* Interrupt status set */ - gpt->is |= value & 0x0000F800; - ppc4xx_gpt_set_irqs(gpt); - break; - case 0x20: - /* Interrupt status clear */ - gpt->is &= ~(value & 0x0000F800); - ppc4xx_gpt_set_irqs(gpt); - break; - case 0x24: - /* Interrupt enable */ - gpt->ie = value & 0x0000F800; - ppc4xx_gpt_set_irqs(gpt); - break; - case 0x80 ... 0x90: - /* Compare timer */ - idx = (addr - 0x80) >> 2; - gpt->comp[idx] = value & 0xF8000000; - ppc4xx_gpt_compute_timer(gpt); - break; - case 0xC0 ... 0xD0: - /* Compare mask */ - idx = (addr - 0xC0) >> 2; - gpt->mask[idx] = value & 0xF8000000; - ppc4xx_gpt_compute_timer(gpt); - break; - } -} - -static const MemoryRegionOps gpt_ops = { - .read = ppc4xx_gpt_read, - .write = ppc4xx_gpt_write, - .valid.min_access_size = 4, - .valid.max_access_size = 4, - .endianness = DEVICE_NATIVE_ENDIAN, -}; - -static void ppc4xx_gpt_cb(void *opaque) -{ - Ppc405GptState *gpt = opaque; - - ppc4xx_gpt_set_irqs(gpt); - ppc4xx_gpt_set_outputs(gpt); - ppc4xx_gpt_compute_timer(gpt); -} - -static void ppc405_gpt_reset(DeviceState *dev) -{ - Ppc405GptState *gpt = PPC405_GPT(dev); - int i; - - timer_del(gpt->timer); - gpt->oe = 0x00000000; - gpt->ol = 0x00000000; - gpt->im = 0x00000000; - gpt->is = 0x00000000; - gpt->ie = 0x00000000; - for (i = 0; i < 5; i++) { - gpt->comp[i] = 0x00000000; - gpt->mask[i] = 0x00000000; - } -} - -static void ppc405_gpt_realize(DeviceState *dev, Error **errp) -{ - Ppc405GptState *s = PPC405_GPT(dev); - SysBusDevice *sbd = SYS_BUS_DEVICE(dev); - int i; - - s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &ppc4xx_gpt_cb, s); - memory_region_init_io(&s->iomem, OBJECT(s), &gpt_ops, s, "gpt", 0xd4); - sysbus_init_mmio(sbd, &s->iomem); - - for (i = 0; i < ARRAY_SIZE(s->irqs); i++) { - sysbus_init_irq(sbd, &s->irqs[i]); - } -} - -static void ppc405_gpt_finalize(Object *obj) -{ - /* timer will be NULL if the GPT wasn't realized */ - if (PPC405_GPT(obj)->timer) { - timer_del(PPC405_GPT(obj)->timer); - } -} - -static void ppc405_gpt_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_gpt_realize; - device_class_set_legacy_reset(dc, ppc405_gpt_reset); - /* Reason: only works as function of a ppc4xx SoC */ - dc->user_creatable = false; -} - -/*****************************************************************************/ -/* PowerPC 405EP */ -/* CPU control */ -enum { - PPC405EP_CPC0_PLLMR0 = 0x0F0, - PPC405EP_CPC0_BOOT = 0x0F1, - PPC405EP_CPC0_EPCTL = 0x0F3, - PPC405EP_CPC0_PLLMR1 = 0x0F4, - PPC405EP_CPC0_UCR = 0x0F5, - PPC405EP_CPC0_SRR = 0x0F6, - PPC405EP_CPC0_JTAGID = 0x0F7, - PPC405EP_CPC0_PCI = 0x0F9, -#if 0 - PPC405EP_CPC0_ER = xxx, - PPC405EP_CPC0_FR = xxx, - PPC405EP_CPC0_SR = xxx, -#endif -}; - -static void ppc405ep_compute_clocks(Ppc405CpcState *cpc) -{ - uint32_t CPU_clk, PLB_clk, OPB_clk, EBC_clk, MAL_clk, PCI_clk; - uint32_t UART0_clk, UART1_clk; - uint64_t VCO_out, PLL_out; - int M, D; - - VCO_out = 0; - if ((cpc->pllmr[1] & 0x80000000) && !(cpc->pllmr[1] & 0x40000000)) { - M = (((cpc->pllmr[1] >> 20) - 1) & 0xF) + 1; /* FBMUL */ - trace_ppc405ep_clocks_compute("FBMUL", (cpc->pllmr[1] >> 20) & 0xF, M); - D = 8 - ((cpc->pllmr[1] >> 16) & 0x7); /* FWDA */ - trace_ppc405ep_clocks_compute("FWDA", (cpc->pllmr[1] >> 16) & 0x7, D); - VCO_out = (uint64_t)cpc->sysclk * M * D; - if (VCO_out < 500000000UL || VCO_out > 1000000000UL) { - /* Error - unlock the PLL */ - qemu_log_mask(LOG_GUEST_ERROR, "VCO out of range %" PRIu64 "\n", - VCO_out); -#if 0 - cpc->pllmr[1] &= ~0x80000000; - goto pll_bypass; -#endif - } - PLL_out = VCO_out / D; - /* Pretend the PLL is locked */ - cpc->boot |= 0x00000001; - } else { -#if 0 - pll_bypass: -#endif - PLL_out = cpc->sysclk; - if (cpc->pllmr[1] & 0x40000000) { - /* Pretend the PLL is not locked */ - cpc->boot &= ~0x00000001; - } - } - /* Now, compute all other clocks */ - D = ((cpc->pllmr[0] >> 20) & 0x3) + 1; /* CCDV */ - trace_ppc405ep_clocks_compute("CCDV", (cpc->pllmr[0] >> 20) & 0x3, D); - CPU_clk = PLL_out / D; - D = ((cpc->pllmr[0] >> 16) & 0x3) + 1; /* CBDV */ - trace_ppc405ep_clocks_compute("CBDV", (cpc->pllmr[0] >> 16) & 0x3, D); - PLB_clk = CPU_clk / D; - D = ((cpc->pllmr[0] >> 12) & 0x3) + 1; /* OPDV */ - trace_ppc405ep_clocks_compute("OPDV", (cpc->pllmr[0] >> 12) & 0x3, D); - OPB_clk = PLB_clk / D; - D = ((cpc->pllmr[0] >> 8) & 0x3) + 2; /* EPDV */ - trace_ppc405ep_clocks_compute("EPDV", (cpc->pllmr[0] >> 8) & 0x3, D); - EBC_clk = PLB_clk / D; - D = ((cpc->pllmr[0] >> 4) & 0x3) + 1; /* MPDV */ - trace_ppc405ep_clocks_compute("MPDV", (cpc->pllmr[0] >> 4) & 0x3, D); - MAL_clk = PLB_clk / D; - D = (cpc->pllmr[0] & 0x3) + 1; /* PPDV */ - trace_ppc405ep_clocks_compute("PPDV", cpc->pllmr[0] & 0x3, D); - PCI_clk = PLB_clk / D; - D = ((cpc->ucr - 1) & 0x7F) + 1; /* U0DIV */ - trace_ppc405ep_clocks_compute("U0DIV", cpc->ucr & 0x7F, D); - UART0_clk = PLL_out / D; - D = (((cpc->ucr >> 8) - 1) & 0x7F) + 1; /* U1DIV */ - trace_ppc405ep_clocks_compute("U1DIV", (cpc->ucr >> 8) & 0x7F, D); - UART1_clk = PLL_out / D; - - if (trace_event_get_state_backends(TRACE_PPC405EP_CLOCKS_SETUP)) { - g_autofree char *trace = g_strdup_printf( - "Setup PPC405EP clocks - sysclk %" PRIu32 " VCO %" PRIu64 - " PLL out %" PRIu64 " Hz\n" - "CPU %" PRIu32 " PLB %" PRIu32 " OPB %" PRIu32 " EBC %" PRIu32 - " MAL %" PRIu32 " PCI %" PRIu32 " UART0 %" PRIu32 - " UART1 %" PRIu32 "\n", - cpc->sysclk, VCO_out, PLL_out, - CPU_clk, PLB_clk, OPB_clk, EBC_clk, MAL_clk, PCI_clk, - UART0_clk, UART1_clk); - trace_ppc405ep_clocks_setup(trace); - } - - /* Setup CPU clocks */ - clk_setup(&cpc->clk_setup[PPC405EP_CPU_CLK], CPU_clk); - /* Setup PLB clock */ - clk_setup(&cpc->clk_setup[PPC405EP_PLB_CLK], PLB_clk); - /* Setup OPB clock */ - clk_setup(&cpc->clk_setup[PPC405EP_OPB_CLK], OPB_clk); - /* Setup external clock */ - clk_setup(&cpc->clk_setup[PPC405EP_EBC_CLK], EBC_clk); - /* Setup MAL clock */ - clk_setup(&cpc->clk_setup[PPC405EP_MAL_CLK], MAL_clk); - /* Setup PCI clock */ - clk_setup(&cpc->clk_setup[PPC405EP_PCI_CLK], PCI_clk); - /* Setup UART0 clock */ - clk_setup(&cpc->clk_setup[PPC405EP_UART0_CLK], UART0_clk); - /* Setup UART1 clock */ - clk_setup(&cpc->clk_setup[PPC405EP_UART1_CLK], UART1_clk); -} - -static uint32_t dcr_read_epcpc(void *opaque, int dcrn) -{ - Ppc405CpcState *cpc = opaque; - uint32_t ret; - - switch (dcrn) { - case PPC405EP_CPC0_BOOT: - ret = cpc->boot; - break; - case PPC405EP_CPC0_EPCTL: - ret = cpc->epctl; - break; - case PPC405EP_CPC0_PLLMR0: - ret = cpc->pllmr[0]; - break; - case PPC405EP_CPC0_PLLMR1: - ret = cpc->pllmr[1]; - break; - case PPC405EP_CPC0_UCR: - ret = cpc->ucr; - break; - case PPC405EP_CPC0_SRR: - ret = cpc->srr; - break; - case PPC405EP_CPC0_JTAGID: - ret = cpc->jtagid; - break; - case PPC405EP_CPC0_PCI: - ret = cpc->pci; - break; - default: - /* Avoid gcc warning */ - ret = 0; - break; - } - - return ret; -} - -static void dcr_write_epcpc(void *opaque, int dcrn, uint32_t val) -{ - Ppc405CpcState *cpc = opaque; - - switch (dcrn) { - case PPC405EP_CPC0_BOOT: - /* Read-only register */ - break; - case PPC405EP_CPC0_EPCTL: - /* Don't care for now */ - cpc->epctl = val & 0xC00000F3; - break; - case PPC405EP_CPC0_PLLMR0: - cpc->pllmr[0] = val & 0x00633333; - ppc405ep_compute_clocks(cpc); - break; - case PPC405EP_CPC0_PLLMR1: - cpc->pllmr[1] = val & 0xC0F73FFF; - ppc405ep_compute_clocks(cpc); - break; - case PPC405EP_CPC0_UCR: - /* UART control - don't care for now */ - cpc->ucr = val & 0x003F7F7F; - break; - case PPC405EP_CPC0_SRR: - cpc->srr = val; - break; - case PPC405EP_CPC0_JTAGID: - /* Read-only */ - break; - case PPC405EP_CPC0_PCI: - cpc->pci = val; - break; - } -} - -static void ppc405_cpc_reset(DeviceState *dev) -{ - Ppc405CpcState *cpc = PPC405_CPC(dev); - - cpc->boot = 0x00000010; /* Boot from PCI - IIC EEPROM disabled */ - cpc->epctl = 0x00000000; - cpc->pllmr[0] = 0x00021002; - cpc->pllmr[1] = 0x80a552be; - cpc->ucr = 0x00004646; - cpc->srr = 0x00040000; - cpc->pci = 0x00000000; - cpc->er = 0x00000000; - cpc->fr = 0x00000000; - cpc->sr = 0x00000000; - cpc->jtagid = 0x20267049; - ppc405ep_compute_clocks(cpc); -} - -/* XXX: sysclk should be between 25 and 100 MHz */ -static void ppc405_cpc_realize(DeviceState *dev, Error **errp) -{ - Ppc405CpcState *cpc = PPC405_CPC(dev); - Ppc4xxDcrDeviceState *dcr = PPC4xx_DCR_DEVICE(dev); - - assert(dcr->cpu); - cpc->clk_setup[PPC405EP_CPU_CLK].cb = - ppc_40x_timers_init(&dcr->cpu->env, cpc->sysclk, PPC_INTERRUPT_PIT); - cpc->clk_setup[PPC405EP_CPU_CLK].opaque = &dcr->cpu->env; - - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_BOOT, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_EPCTL, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_PLLMR0, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_PLLMR1, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_UCR, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_SRR, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_JTAGID, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); - ppc4xx_dcr_register(dcr, PPC405EP_CPC0_PCI, cpc, - &dcr_read_epcpc, &dcr_write_epcpc); -} - -static const Property ppc405_cpc_properties[] = { - DEFINE_PROP_UINT32("sys-clk", Ppc405CpcState, sysclk, 0), -}; - -static void ppc405_cpc_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_cpc_realize; - device_class_set_legacy_reset(dc, ppc405_cpc_reset); - /* Reason: only works as function of a ppc4xx SoC */ - dc->user_creatable = false; - device_class_set_props(dc, ppc405_cpc_properties); -} - -/* PPC405_SOC */ - -static void ppc405_soc_instance_init(Object *obj) -{ - Ppc405SoCState *s = PPC405_SOC(obj); - - object_initialize_child(obj, "cpu", &s->cpu, - POWERPC_CPU_TYPE_NAME("405ep")); - - object_initialize_child(obj, "uic", &s->uic, TYPE_PPC_UIC); - - object_initialize_child(obj, "cpc", &s->cpc, TYPE_PPC405_CPC); - object_property_add_alias(obj, "sys-clk", OBJECT(&s->cpc), "sys-clk"); - - object_initialize_child(obj, "gpt", &s->gpt, TYPE_PPC405_GPT); - - object_initialize_child(obj, "ocm", &s->ocm, TYPE_PPC405_OCM); - - object_initialize_child(obj, "gpio", &s->gpio, TYPE_PPC405_GPIO); - - object_initialize_child(obj, "dma", &s->dma, TYPE_PPC405_DMA); - - object_initialize_child(obj, "i2c", &s->i2c, TYPE_PPC4xx_I2C); - - object_initialize_child(obj, "ebc", &s->ebc, TYPE_PPC4xx_EBC); - - object_initialize_child(obj, "opba", &s->opba, TYPE_PPC405_OPBA); - - object_initialize_child(obj, "pob", &s->pob, TYPE_PPC405_POB); - - object_initialize_child(obj, "plb", &s->plb, TYPE_PPC4xx_PLB); - - object_initialize_child(obj, "mal", &s->mal, TYPE_PPC4xx_MAL); - - object_initialize_child(obj, "sdram", &s->sdram, TYPE_PPC4xx_SDRAM_DDR); - object_property_add_alias(obj, "dram", OBJECT(&s->sdram), "dram"); -} - -static void ppc405_reset(void *opaque) -{ - cpu_reset(CPU(opaque)); -} - -static void ppc405_soc_realize(DeviceState *dev, Error **errp) -{ - Ppc405SoCState *s = PPC405_SOC(dev); - CPUPPCState *env; - SysBusDevice *sbd; - int i; - - /* init CPUs */ - if (!qdev_realize(DEVICE(&s->cpu), NULL, errp)) { - return; - } - qemu_register_reset(ppc405_reset, &s->cpu); - - env = &s->cpu.env; - - ppc_dcr_init(env, NULL, NULL); - - /* CPU control */ - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->cpc), &s->cpu, errp)) { - return; - } - - /* PLB arbitrer */ - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->plb), &s->cpu, errp)) { - return; - } - - /* PLB to OPB bridge */ - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->pob), &s->cpu, errp)) { - return; - } - - /* OBP arbitrer */ - sbd = SYS_BUS_DEVICE(&s->opba); - if (!sysbus_realize(sbd, errp)) { - return; - } - sysbus_mmio_map(sbd, 0, 0xef600600); - - /* Universal interrupt controller */ - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->uic), &s->cpu, errp)) { - return; - } - sbd = SYS_BUS_DEVICE(&s->uic); - sysbus_connect_irq(sbd, PPCUIC_OUTPUT_INT, - qdev_get_gpio_in(DEVICE(&s->cpu), PPC40x_INPUT_INT)); - sysbus_connect_irq(sbd, PPCUIC_OUTPUT_CINT, - qdev_get_gpio_in(DEVICE(&s->cpu), PPC40x_INPUT_CINT)); - - /* SDRAM controller */ - /* - * We use the 440 DDR SDRAM controller which has more regs and features - * but it's compatible enough for now - */ - object_property_set_int(OBJECT(&s->sdram), "nbanks", 2, &error_abort); - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->sdram), &s->cpu, errp)) { - return; - } - /* XXX 405EP has no ECC interrupt */ - sysbus_connect_irq(SYS_BUS_DEVICE(&s->sdram), 0, - qdev_get_gpio_in(DEVICE(&s->uic), 17)); - - /* External bus controller */ - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->ebc), &s->cpu, errp)) { - return; - } - - /* DMA controller */ - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->dma), &s->cpu, errp)) { - return; - } - sbd = SYS_BUS_DEVICE(&s->dma); - for (i = 0; i < ARRAY_SIZE(s->dma.irqs); i++) { - sysbus_connect_irq(sbd, i, qdev_get_gpio_in(DEVICE(&s->uic), 5 + i)); - } - - /* I2C controller */ - sbd = SYS_BUS_DEVICE(&s->i2c); - if (!sysbus_realize(sbd, errp)) { - return; - } - sysbus_mmio_map(sbd, 0, 0xef600500); - sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(DEVICE(&s->uic), 2)); - - /* GPIO */ - sbd = SYS_BUS_DEVICE(&s->gpio); - if (!sysbus_realize(sbd, errp)) { - return; - } - sysbus_mmio_map(sbd, 0, 0xef600700); - - /* Serial ports */ - if (serial_hd(0) != NULL) { - serial_mm_init(get_system_memory(), 0xef600300, 0, - qdev_get_gpio_in(DEVICE(&s->uic), 0), - PPC_SERIAL_MM_BAUDBASE, serial_hd(0), - DEVICE_BIG_ENDIAN); - } - if (serial_hd(1) != NULL) { - serial_mm_init(get_system_memory(), 0xef600400, 0, - qdev_get_gpio_in(DEVICE(&s->uic), 1), - PPC_SERIAL_MM_BAUDBASE, serial_hd(1), - DEVICE_BIG_ENDIAN); - } - - /* OCM */ - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->ocm), &s->cpu, errp)) { - return; - } - - /* GPT */ - sbd = SYS_BUS_DEVICE(&s->gpt); - if (!sysbus_realize(sbd, errp)) { - return; - } - sysbus_mmio_map(sbd, 0, 0xef600000); - for (i = 0; i < ARRAY_SIZE(s->gpt.irqs); i++) { - sysbus_connect_irq(sbd, i, qdev_get_gpio_in(DEVICE(&s->uic), 19 + i)); - } - - /* MAL */ - object_property_set_int(OBJECT(&s->mal), "txc-num", 4, &error_abort); - object_property_set_int(OBJECT(&s->mal), "rxc-num", 2, &error_abort); - if (!ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(&s->mal), &s->cpu, errp)) { - return; - } - sbd = SYS_BUS_DEVICE(&s->mal); - for (i = 0; i < ARRAY_SIZE(s->mal.irqs); i++) { - sysbus_connect_irq(sbd, i, qdev_get_gpio_in(DEVICE(&s->uic), 11 + i)); - } - - /* Ethernet */ - /* Uses UIC IRQs 9, 15, 17 */ -} - -static void ppc405_soc_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - - dc->realize = ppc405_soc_realize; - /* Reason: only works as part of a ppc405 board/machine */ - dc->user_creatable = false; -} - -static const TypeInfo ppc405_types[] = { - { - .name = TYPE_PPC405_POB, - .parent = TYPE_PPC4xx_DCR_DEVICE, - .instance_size = sizeof(Ppc405PobState), - .class_init = ppc405_pob_class_init, - }, { - .name = TYPE_PPC405_OPBA, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(Ppc405OpbaState), - .class_init = ppc405_opba_class_init, - }, { - .name = TYPE_PPC405_DMA, - .parent = TYPE_PPC4xx_DCR_DEVICE, - .instance_size = sizeof(Ppc405DmaState), - .class_init = ppc405_dma_class_init, - }, { - .name = TYPE_PPC405_GPIO, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(Ppc405GpioState), - .class_init = ppc405_gpio_class_init, - }, { - .name = TYPE_PPC405_OCM, - .parent = TYPE_PPC4xx_DCR_DEVICE, - .instance_size = sizeof(Ppc405OcmState), - .class_init = ppc405_ocm_class_init, - }, { - .name = TYPE_PPC405_GPT, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(Ppc405GptState), - .instance_finalize = ppc405_gpt_finalize, - .class_init = ppc405_gpt_class_init, - }, { - .name = TYPE_PPC405_CPC, - .parent = TYPE_PPC4xx_DCR_DEVICE, - .instance_size = sizeof(Ppc405CpcState), - .class_init = ppc405_cpc_class_init, - }, { - .name = TYPE_PPC405_SOC, - .parent = TYPE_DEVICE, - .instance_size = sizeof(Ppc405SoCState), - .instance_init = ppc405_soc_instance_init, - .class_init = ppc405_soc_class_init, - } -}; - -DEFINE_TYPES(ppc405_types) From 52f0b59ec6b780f2a3e162d5862b90b406fa4697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Tue, 4 Feb 2025 09:06:49 +0100 Subject: [PATCH 0882/1179] hw/ppc: Deprecate 405 CPUs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ref405ep machine is scheduled for removal in QEMU 10.0. Keep the 405 CPU implementation for a while because it is theoretically possible to model the power management (OCC) co-processor found on the IBM POWER [8-11] processors. Signed-off-by: Cédric Le Goater Reviewed-by: Nicholas Piggin Message-ID: <20250204080649.836155-4-clg@redhat.com> Signed-off-by: Nicholas Piggin --- docs/about/deprecated.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 3d39d2a9da52..e2b4f077d453 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -266,6 +266,15 @@ in the QEMU object model anymore. ``Sun-UltraSparc-IIIi+`` and but for consistency these will get removed in a future release, too. Use ``Sun-UltraSparc-IIIi-plus`` and ``Sun-UltraSparc-IV-plus`` instead. +PPC 405 CPUs (since 10.0) +''''''''''''''''''''''''' + +The PPC 405 CPU has no known users and the ``ref405ep`` machine was +removed in QEMU 10.0. Since the IBM POWER [8-11] processors uses an +embedded 405 for power management (OCC) and other internal tasks, it +is theoretically possible to use QEMU to model them. Let's keep the +CPU implementation for a while before removing all support. + System emulator machines ------------------------ From 9cbbe72924b4be1870563b5101548ee2241b279d Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Tue, 11 Mar 2025 14:54:49 +1000 Subject: [PATCH 0883/1179] ppc/pnv: Update skiboot to 7.1-106 This skiboot firmware importantly contains updates for HOMER/OCC bugs. These subsystems have bitrotted in QEMU and skiboot and this update allows new QEMU models to be exercised. Power11 support is also added. This model is not yet merged in QEMU, but firmware support will make development and testing simpler. Signed-off-by: Nicholas Piggin --- pc-bios/skiboot.lid | Bin 2527328 -> 2592960 bytes roms/skiboot | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pc-bios/skiboot.lid b/pc-bios/skiboot.lid index 906bd5127175d4f9fa8a1e160f7a648902bdedd4..ffc77ee11126c9b70e6c5691f0604ca8579bd0b7 100644 GIT binary patch literal 2592960 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 s`crIu#n=}Y6h4!fr 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�e!^<$&4!lc$ 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�Xd+|INPtwPRyypU8iQ=#QAZ{qK*t0IM*`R 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 z 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

7>kS+gkM|zkN)d{@}G?P@bcQw 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

lsTqqGzT04C!K#bmZ%o2h%5Gt-fRHdrP2qdUyw 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

itqY6peN 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 zM1urKfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c 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

}BX7Y`5Vm3OxJF06g*& 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�T*nH4IH{?3-`nu_AKDlg9i6+P;t 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

=6 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

Q}%=UVt0lP z>$3f17LhJE@6gqNx0$zbJvm+?@FHE5*$+Qg^$YY$zrF+T>n()LhEpwz!Pk{JY$}sI;YI9f9NW+9Q2TK z+1x2Rufo`K?A}JFgKoR{#XbOaq@h4;|G>vhkfZQEY2XrP#SOiE%dERL>c8@S3*s8j zu~(_n<3{(rIal{ZzstqOf%brx;kuJ*3BDyh1mDzcPqwKQS-f0g_#^UIh#ZbRO!lt} z^2v9T#GU33zkWpUp!KhlQw0Ul(!zp4(DB>p!Z#ZM&l|wQWcpMm_FO+@!Iy5qSty}5 z@Yu)W1|fCj3dyfSx~~W$|2I4xRT2&A+d-P=@w{moT;bxMb@qAPde2|CV!uz%6CL zx%k>|KQIT!kWFZFwgwbz z&mTHDG@uH0P3HQp>l=G|y2EE~3vBD@L5z?;9bOJU(YkekvUTgmS154`!E^gx?` zeA_3Bd)i;dIhxMm`o<4^D6s8AA7c5Q)&I~y`)beC(+$kelp477`0PH89@4SGoOt_# zSYe4Dwy*g;IWYI$sXX_z-R+NgE=F{8&{aM6zk{#PFA7^6WM}-o3cpJzGo=T>=LfKd zgtae)_CAi2e#wE2tj=DomgBn+-_{h)2*T%I3f$87Uom@ZEPuLL%CZh* z$BvGRsprUDh~+i%+7#$j{L5JX#@|<30A7 zm(F|*y3KcC2gG_k`b)L!XhxM|{Oef{au5s@!rx(E{-U1u>Dz*F+B&@Rw zRpFUe@ZF4jti-`0iG3)=^9nq(aK9Ay^Wnf6U)5%h8}<&IV*t*8kLCQ$wr1na89#WV zJ^rbMs`M6|=f(J!z5a??9zP{A@a2#%7b4cZ0{oh?>?Ft7ratl9X~2{&wa7PlPEq&^ z#+bxkmd`kL+E2K3-$i1lgnosxW1t<$V+Nl}`JT)(OD8g~q&Q_b|Zy5$<6&&M`)u z8wcOKM0BC$c)o=S05PjW(H28k8p=N4AeXq83V0Fk&I_4>hN6tv#AI9?u_$i@%!mH{r&Kw`@5;8B=ercOT(9qwl6_@CkaD+BiDXK z$9UPt#9v##1iDfobUV_fhT(~hM~;7NtVCirZb7}Q?rLxu{n5q3@v-NT0|M^`cVKuG z`5{U*4k|h>fQKj3|5leWxCjG=$7bUK<2y&(GdA5lpO9II|4YaNv-A0}9jOP8m(s_A zw&u`AO{-}O_D6SHkXedx_WNA=hlrV{ZPIQ#hwd(Wv!`tG%I*h`e=Pmr_LPR3xO?yG z=xfgUo)2#Jg?Mw=-w}JTSX~O7>2c)tHLb^S4(}YCJB~J0d|uWsFZ(a4sA2( z^L5}MXYQjs!ud$Ac!A%#j|SlCHd#l<(Z+lA_0)YQkrNI%vNZkB_WRPFUmyP%�yu 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

!JR%dh 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 GyXF5Q`0cg; diff --git a/roms/SLOF b/roms/SLOF index 3a259df2449f..ee03aec2c106 160000 --- a/roms/SLOF +++ b/roms/SLOF @@ -1 +1 @@ -Subproject commit 3a259df2449fc4a4e43ab5f33f0b2c66484b4bc3 +Subproject commit ee03aec2c106a699aaddd2d3dd52cbd7b7e8d544 From 6b56bb6dbce5cfa185c34c0519ab8015f30699f7 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 16 Nov 2024 20:19:19 +1000 Subject: [PATCH 0885/1179] ppc/pnv/phb4: Add pervasive chiplet support to PHB4/5 Each non-core chiplet on a chip has a "pervasive chiplet" unit and its xscom register set. This adds support for PHB4/5. skiboot reads the CPLT_CONF1 register in __phb4/5_get_max_link_width(), which shows up as unimplemented xscom reads. Set a value in PCI CONF1 register's link-width field to demonstrate skiboot doing something interesting with it. In the bigger picture, it might be better to model the pervasive chiplet type as parent that each non-core chiplet model derives from. For now this is enough to get the PHB registers implemented and working for skiboot, and provides a second example (after the N1 chiplet) that will help if the design is reworked as such. Signed-off-by: Nicholas Piggin --- hw/pci-host/pnv_phb4_pec.c | 55 +++++++++++++++++++++++++++++++++- hw/ppc/pnv.c | 8 +++++ include/hw/pci-host/pnv_phb4.h | 5 ++++ include/hw/ppc/pnv_xscom.h | 4 +++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/hw/pci-host/pnv_phb4_pec.c b/hw/pci-host/pnv_phb4_pec.c index a156839caf5c..cb8a7e3afa77 100644 --- a/hw/pci-host/pnv_phb4_pec.c +++ b/hw/pci-host/pnv_phb4_pec.c @@ -197,6 +197,9 @@ static PnvPHB *pnv_pec_default_phb_realize(PnvPhb4PecState *pec, return phb; } +#define XPEC_P9_PCI_LANE_CFG PPC_BITMASK(10, 11) +#define XPEC_P10_PCI_LANE_CFG PPC_BITMASK(0, 1) + static void pnv_pec_realize(DeviceState *dev, Error **errp) { PnvPhb4PecState *pec = PNV_PHB4_PEC(dev); @@ -211,6 +214,43 @@ static void pnv_pec_realize(DeviceState *dev, Error **errp) pec->num_phbs = pecc->num_phbs[pec->index]; + /* Pervasive chiplet */ + object_initialize_child(OBJECT(pec), "nest-pervasive-common", + &pec->nest_pervasive, + TYPE_PNV_NEST_CHIPLET_PERVASIVE); + if (!qdev_realize(DEVICE(&pec->nest_pervasive), NULL, errp)) { + return; + } + + /* Set up pervasive chiplet registers */ + /* + * Most registers are not set up, this just sets the PCI CONF1 link-width + * field because skiboot probes it. + */ + if (pecc->version == PNV_PHB4_VERSION) { + /* + * On P9, PEC2 has configurable 1/2/3-furcation). + * Make it trifurcated (x8, x4, x4) to match pnv_pec_num_phbs. + */ + if (pec->index == 2) { + pec->nest_pervasive.control_regs.cplt_cfg1 = + SETFIELD(XPEC_P9_PCI_LANE_CFG, + pec->nest_pervasive.control_regs.cplt_cfg1, + 0b10); + } + } else if (pecc->version == PNV_PHB5_VERSION) { + /* + * On P10, both PECs are configurable 1/2/3-furcation). + * Both are trifurcated to match pnv_phb5_pec_num_stacks. + */ + pec->nest_pervasive.control_regs.cplt_cfg1 = + SETFIELD(XPEC_P10_PCI_LANE_CFG, + pec->nest_pervasive.control_regs.cplt_cfg1, + 0b10); + } else { + g_assert_not_reached(); + } + /* Create PHBs if running with defaults */ if (defaults_enabled()) { g_assert(pec->num_phbs <= MAX_PHBS_PER_PEC); @@ -290,9 +330,16 @@ static const Property pnv_pec_properties[] = { PnvChip *), }; +#define XPEC_PCI_CPLT_OFFSET 0x1000000ULL + +static uint32_t pnv_pec_xscom_cplt_base(PnvPhb4PecState *pec) +{ + return PNV9_XSCOM_PEC_NEST_CPLT_BASE + XPEC_PCI_CPLT_OFFSET * pec->index; +} + static uint32_t pnv_pec_xscom_pci_base(PnvPhb4PecState *pec) { - return PNV9_XSCOM_PEC_PCI_BASE + 0x1000000 * pec->index; + return PNV9_XSCOM_PEC_PCI_BASE + XPEC_PCI_CPLT_OFFSET * pec->index; } static uint32_t pnv_pec_xscom_nest_base(PnvPhb4PecState *pec) @@ -321,6 +368,7 @@ static void pnv_pec_class_init(ObjectClass *klass, void *data) device_class_set_props(dc, pnv_pec_properties); dc->user_creatable = false; + pecc->xscom_cplt_base = pnv_pec_xscom_cplt_base; pecc->xscom_nest_base = pnv_pec_xscom_nest_base; pecc->xscom_pci_base = pnv_pec_xscom_pci_base; pecc->xscom_nest_size = PNV9_XSCOM_PEC_NEST_SIZE; @@ -349,6 +397,10 @@ static const TypeInfo pnv_pec_type_info = { /* * POWER10 definitions */ +static uint32_t pnv_phb5_pec_xscom_cplt_base(PnvPhb4PecState *pec) +{ + return PNV10_XSCOM_PEC_NEST_CPLT_BASE + XPEC_PCI_CPLT_OFFSET * pec->index; +} static uint32_t pnv_phb5_pec_xscom_pci_base(PnvPhb4PecState *pec) { @@ -373,6 +425,7 @@ static void pnv_phb5_pec_class_init(ObjectClass *klass, void *data) static const char compat[] = "ibm,power10-pbcq"; static const char stk_compat[] = "ibm,power10-phb-stack"; + pecc->xscom_cplt_base = pnv_phb5_pec_xscom_cplt_base; pecc->xscom_nest_base = pnv_phb5_pec_xscom_nest_base; pecc->xscom_pci_base = pnv_phb5_pec_xscom_pci_base; pecc->xscom_nest_size = PNV10_XSCOM_PEC_NEST_SIZE; diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 87607508c768..4407b3a1a2b5 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -1753,6 +1753,7 @@ static void pnv_chip_power9_pec_realize(PnvChip *chip, Error **errp) for (i = 0; i < chip->num_pecs; i++) { PnvPhb4PecState *pec = &chip9->pecs[i]; PnvPhb4PecClass *pecc = PNV_PHB4_PEC_GET_CLASS(pec); + uint32_t pec_cplt_base; uint32_t pec_nest_base; uint32_t pec_pci_base; @@ -1765,9 +1766,12 @@ static void pnv_chip_power9_pec_realize(PnvChip *chip, Error **errp) return; } + pec_cplt_base = pecc->xscom_cplt_base(pec); pec_nest_base = pecc->xscom_nest_base(pec); pec_pci_base = pecc->xscom_pci_base(pec); + pnv_xscom_add_subregion(chip, pec_cplt_base, + &pec->nest_pervasive.xscom_ctrl_regs_mr); pnv_xscom_add_subregion(chip, pec_nest_base, &pec->nest_regs_mr); pnv_xscom_add_subregion(chip, pec_pci_base, &pec->pci_regs_mr); } @@ -2027,6 +2031,7 @@ static void pnv_chip_power10_phb_realize(PnvChip *chip, Error **errp) for (i = 0; i < chip->num_pecs; i++) { PnvPhb4PecState *pec = &chip10->pecs[i]; PnvPhb4PecClass *pecc = PNV_PHB4_PEC_GET_CLASS(pec); + uint32_t pec_cplt_base; uint32_t pec_nest_base; uint32_t pec_pci_base; @@ -2039,9 +2044,12 @@ static void pnv_chip_power10_phb_realize(PnvChip *chip, Error **errp) return; } + pec_cplt_base = pecc->xscom_cplt_base(pec); pec_nest_base = pecc->xscom_nest_base(pec); pec_pci_base = pecc->xscom_pci_base(pec); + pnv_xscom_add_subregion(chip, pec_cplt_base, + &pec->nest_pervasive.xscom_ctrl_regs_mr); pnv_xscom_add_subregion(chip, pec_nest_base, &pec->nest_regs_mr); pnv_xscom_add_subregion(chip, pec_pci_base, &pec->pci_regs_mr); } diff --git a/include/hw/pci-host/pnv_phb4.h b/include/hw/pci-host/pnv_phb4.h index 8abee78e4d45..8a80c0c667a8 100644 --- a/include/hw/pci-host/pnv_phb4.h +++ b/include/hw/pci-host/pnv_phb4.h @@ -13,6 +13,7 @@ #include "hw/pci-host/pnv_phb.h" #include "hw/pci/pci_bus.h" #include "hw/ppc/pnv.h" +#include "hw/ppc/pnv_nest_pervasive.h" #include "hw/ppc/xive.h" #include "qom/object.h" @@ -174,6 +175,9 @@ struct PnvPhb4PecState { uint32_t index; uint32_t chip_id; + /* Pervasive chiplet control */ + PnvNestChipletPervasive nest_pervasive; + /* Nest registers, excuding per-stack */ #define PHB4_PEC_NEST_REGS_COUNT 0xf uint64_t nest_regs[PHB4_PEC_NEST_REGS_COUNT]; @@ -196,6 +200,7 @@ struct PnvPhb4PecState { struct PnvPhb4PecClass { DeviceClass parent_class; + uint32_t (*xscom_cplt_base)(PnvPhb4PecState *pec); uint32_t (*xscom_nest_base)(PnvPhb4PecState *pec); uint32_t xscom_nest_size; uint32_t (*xscom_pci_base)(PnvPhb4PecState *pec); diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h index 648388a59951..a927aea1c09c 100644 --- a/include/hw/ppc/pnv_xscom.h +++ b/include/hw/ppc/pnv_xscom.h @@ -126,6 +126,8 @@ struct PnvXScomInterfaceClass { #define PNV9_XSCOM_PEC_PCI_BASE 0xd010800 #define PNV9_XSCOM_PEC_PCI_SIZE 0x200 +#define PNV9_XSCOM_PEC_NEST_CPLT_BASE 0x0d000000 + /* XSCOM PCI "pass-through" window to PHB SCOM */ #define PNV9_XSCOM_PEC_PCI_STK0 0x100 #define PNV9_XSCOM_PEC_PCI_STK1 0x140 @@ -197,6 +199,8 @@ struct PnvXScomInterfaceClass { #define PNV10_XSCOM_PEC_NEST_BASE 0x3011800 /* index goes downwards ... */ #define PNV10_XSCOM_PEC_NEST_SIZE 0x100 +#define PNV10_XSCOM_PEC_NEST_CPLT_BASE 0x08000000 + #define PNV10_XSCOM_PEC_PCI_BASE 0x8010800 /* index goes upwards ... */ #define PNV10_XSCOM_PEC_PCI_SIZE 0x200 From f24ff35af9b242163ac0d209a70240f13fd9f163 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Mon, 9 Dec 2024 23:16:35 +1000 Subject: [PATCH 0886/1179] ppc/pnv/homer: Fix OCC registers The HOMER OCC registers seem to have bitrotted and fail for various reasons on powernv8, 9, and 10. The major problems are that POWER8 has the wrong version value and its pstate ordering is incorrect. POWER9/10 have not set the OCC state to active. Non-zero chips are also set to OCC slaves for POWER9/10. Unfortunately skiboot has also bitrotted and requires fixes that are not yet in the bios files to run. With a patched skiboot, before this change, powernv9/10 report: [ 0.262050394,3] OCC: Chip: 0: OCC not active [ 0.262128603,3] OCC: Initialization on all chips did not complete(timed out) powernv8 reports: [ 0.173572100,3] OCC: Unknown OCC-OPAL interface version. [ 0.173812059,3] OCC: Initialization on all chips did not complete(timed out) After this patch, all report: [ 0.176815668,5] OCC: All Chip Rdy after 0 ms Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_homer.c | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/hw/ppc/pnv_homer.c b/hw/ppc/pnv_homer.c index a1d83c8149d3..acd2f7b3a685 100644 --- a/hw/ppc/pnv_homer.c +++ b/hw/ppc/pnv_homer.c @@ -70,21 +70,24 @@ static uint64_t pnv_power8_homer_read(void *opaque, hwaddr addr, PnvHomer *homer = PNV_HOMER(opaque); switch (addr) { - case PNV8_OCC_PSTATE_VERSION: - case PNV8_OCC_PSTATE_MIN: - case PNV8_OCC_PSTATE_ID_ZERO: - return 0; case PNV8_OCC_PSTATE_VALID: + return 1; case PNV8_OCC_PSTATE_THROTTLE: + return 0; + case PNV8_OCC_PSTATE_VERSION: + return 0x02; + case PNV8_OCC_PSTATE_MIN: + return -2; case PNV8_OCC_PSTATE_NOM: case PNV8_OCC_PSTATE_TURBO: - case PNV8_OCC_PSTATE_ID_ONE: + return -1; + case PNV8_OCC_PSTATE_ULTRA_TURBO: + return 0; + case PNV8_OCC_PSTATE_ID_ZERO: + return 0; case PNV8_OCC_VDD_VOLTAGE_IDENTIFIER: case PNV8_OCC_VCS_VOLTAGE_IDENTIFIER: return 1; - case PNV8_OCC_PSTATE_ULTRA_TURBO: - case PNV8_OCC_PSTATE_ID_TWO: - return 2; case PNV8_OCC_PSTATE_DATA: return 0x1000000000000000; /* P8 frequency for 0, 1, and 2 pstates */ @@ -92,6 +95,10 @@ static uint64_t pnv_power8_homer_read(void *opaque, hwaddr addr, case PNV8_OCC_PSTATE_ONE_FREQUENCY: case PNV8_OCC_PSTATE_TWO_FREQUENCY: return 3000; + case PNV8_OCC_PSTATE_ID_ONE: + return -1; + case PNV8_OCC_PSTATE_ID_TWO: + return -2; } /* pstate table core max array */ if (core_max_array(homer, addr)) { @@ -192,11 +199,12 @@ static const TypeInfo pnv_homer_power8_type_info = { /* P9 Pstate table */ +#define PNV9_OCC_PSTATE_VALID 0xe2000 #define PNV9_OCC_PSTATE_ID_ZERO 0xe2018 #define PNV9_OCC_PSTATE_ID_ONE 0xe2020 #define PNV9_OCC_PSTATE_ID_TWO 0xe2028 #define PNV9_OCC_PSTATE_DATA 0xe2000 -#define PNV9_OCC_PSTATE_DATA_AREA 0xe2008 +#define PNV9_OCC_PSTATE_MINOR_VERSION 0xe2008 #define PNV9_OCC_PSTATE_MIN 0xe2003 #define PNV9_OCC_PSTATE_NOM 0xe2004 #define PNV9_OCC_PSTATE_TURBO 0xe2005 @@ -211,7 +219,7 @@ static const TypeInfo pnv_homer_power8_type_info = { #define PNV9_OCC_PSTATE_TWO_FREQUENCY 0xe202c #define PNV9_OCC_ROLE_MASTER_OR_SLAVE 0xe2002 #define PNV9_CORE_MAX_BASE 0xe2819 - +#define PNV9_DYNAMIC_DATA_STATE 0xe2b80 static uint64_t pnv_power9_homer_read(void *opaque, hwaddr addr, unsigned size) @@ -219,11 +227,17 @@ static uint64_t pnv_power9_homer_read(void *opaque, hwaddr addr, PnvHomer *homer = PNV_HOMER(opaque); switch (addr) { + case PNV9_OCC_PSTATE_VALID: + return 1; case PNV9_OCC_MAX_PSTATE_ULTRA_TURBO: case PNV9_OCC_PSTATE_ID_ZERO: return 0; - case PNV9_OCC_PSTATE_DATA: case PNV9_OCC_ROLE_MASTER_OR_SLAVE: + if (homer->chip->chip_id == 0) { + return 0x1; /* master */ + } else { + return 0x0; /* slave */ + } case PNV9_OCC_PSTATE_NOM: case PNV9_OCC_PSTATE_TURBO: case PNV9_OCC_PSTATE_ID_ONE: @@ -241,10 +255,13 @@ static uint64_t pnv_power9_homer_read(void *opaque, hwaddr addr, return 3000; case PNV9_OCC_PSTATE_MAJOR_VERSION: return 0x90; + case PNV9_OCC_PSTATE_MINOR_VERSION: + return 0x01; case PNV9_CHIP_HOMER_BASE: - case PNV9_OCC_PSTATE_DATA_AREA: case PNV9_CHIP_HOMER_IMAGE_POINTER: return 0x1000000000000000; + case PNV9_DYNAMIC_DATA_STATE: + return 0x03; /* active */ } /* pstate table core max array */ if (core_max_array(homer, addr)) { From 634cf61e463a8020cb8eb835fae4e2939f387975 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Tue, 10 Dec 2024 12:08:35 +1000 Subject: [PATCH 0887/1179] ppc/pnv/homer: Make dummy reads return 0 HOMER memory implements some dummy registers that return a nonsense value to satisfy skiboot accesses caused by "SLW" init and register save/restore programming that has never worked under QEMU: [ 0.265000943,3] SLW: Failed to set HRMOR for CPU 0,RC=0x1 [ 0.265356988,3] Disabling deep stop states To simplify a later change to implement HOMER as a RAM area, make these return zero, which has the same result. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_homer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/ppc/pnv_homer.c b/hw/ppc/pnv_homer.c index acd2f7b3a685..75b0ee79647a 100644 --- a/hw/ppc/pnv_homer.c +++ b/hw/ppc/pnv_homer.c @@ -89,7 +89,7 @@ static uint64_t pnv_power8_homer_read(void *opaque, hwaddr addr, case PNV8_OCC_VCS_VOLTAGE_IDENTIFIER: return 1; case PNV8_OCC_PSTATE_DATA: - return 0x1000000000000000; + return 0; /* P8 frequency for 0, 1, and 2 pstates */ case PNV8_OCC_PSTATE_ZERO_FREQUENCY: case PNV8_OCC_PSTATE_ONE_FREQUENCY: @@ -259,7 +259,7 @@ static uint64_t pnv_power9_homer_read(void *opaque, hwaddr addr, return 0x01; case PNV9_CHIP_HOMER_BASE: case PNV9_CHIP_HOMER_IMAGE_POINTER: - return 0x1000000000000000; + return 0; case PNV9_DYNAMIC_DATA_STATE: return 0x03; /* active */ } From 29c041ca7f8d6910c894788482efff892789dcd2 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Mon, 9 Dec 2024 19:55:58 +1000 Subject: [PATCH 0888/1179] ppc/pnv/occ: Fix common area sensor offsets The commit to fix the OCC common area sensor mappings didn't update the register offsets to match. Before this change, skiboot reports: [ 0.347100086,3] OCC: Chip 0 sensor data invalid Afterward, there is no error and the sensor_groups directory appears under /sys/firmware/opal/. The SLW_IMAGE_BASE address looks like a workaround to intercept firmware memory accesses, but that does not seem to be required now (and would have been broken by the OCC common area region mapping change anyway). So it can be removed. Fixes: 3a1b70b66b5cb4 ("ppc/pnv: Fix OCC common area region mapping") Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_occ.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/hw/ppc/pnv_occ.c b/hw/ppc/pnv_occ.c index 48123ceae176..c6681a035a7d 100644 --- a/hw/ppc/pnv_occ.c +++ b/hw/ppc/pnv_occ.c @@ -32,22 +32,21 @@ #define OCB_OCI_OCCMISC_OR 0x4022 /* OCC sensors */ -#define OCC_SENSOR_DATA_BLOCK_OFFSET 0x580000 -#define OCC_SENSOR_DATA_VALID 0x580001 -#define OCC_SENSOR_DATA_VERSION 0x580002 -#define OCC_SENSOR_DATA_READING_VERSION 0x580004 -#define OCC_SENSOR_DATA_NR_SENSORS 0x580008 -#define OCC_SENSOR_DATA_NAMES_OFFSET 0x580010 -#define OCC_SENSOR_DATA_READING_PING_OFFSET 0x580014 -#define OCC_SENSOR_DATA_READING_PONG_OFFSET 0x58000c -#define OCC_SENSOR_DATA_NAME_LENGTH 0x58000d -#define OCC_SENSOR_NAME_STRUCTURE_TYPE 0x580023 -#define OCC_SENSOR_LOC_CORE 0x580022 -#define OCC_SENSOR_LOC_GPU 0x580020 -#define OCC_SENSOR_TYPE_POWER 0x580003 -#define OCC_SENSOR_NAME 0x580005 -#define HWMON_SENSORS_MASK 0x58001e -#define SLW_IMAGE_BASE 0x0 +#define OCC_SENSOR_DATA_BLOCK_OFFSET 0x0000 +#define OCC_SENSOR_DATA_VALID 0x0001 +#define OCC_SENSOR_DATA_VERSION 0x0002 +#define OCC_SENSOR_DATA_READING_VERSION 0x0004 +#define OCC_SENSOR_DATA_NR_SENSORS 0x0008 +#define OCC_SENSOR_DATA_NAMES_OFFSET 0x0010 +#define OCC_SENSOR_DATA_READING_PING_OFFSET 0x0014 +#define OCC_SENSOR_DATA_READING_PONG_OFFSET 0x000c +#define OCC_SENSOR_DATA_NAME_LENGTH 0x000d +#define OCC_SENSOR_NAME_STRUCTURE_TYPE 0x0023 +#define OCC_SENSOR_LOC_CORE 0x0022 +#define OCC_SENSOR_LOC_GPU 0x0020 +#define OCC_SENSOR_TYPE_POWER 0x0003 +#define OCC_SENSOR_NAME 0x0005 +#define HWMON_SENSORS_MASK 0x001e static void pnv_occ_set_misc(PnvOCC *occ, uint64_t val) { @@ -129,8 +128,6 @@ static uint64_t pnv_occ_common_area_read(void *opaque, hwaddr addr, case HWMON_SENSORS_MASK: case OCC_SENSOR_LOC_GPU: return 0x8e00; - case SLW_IMAGE_BASE: - return 0x1000000000000000; } return 0; } From 2935a3fb03e0e835ab13dfa67ea6ad475edb5665 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Mon, 9 Dec 2024 18:47:20 +1000 Subject: [PATCH 0889/1179] ppc/pnv/homer: class-based base and size Put HOMER memory region base and size into the class, to allow more code-reuse between different machines in later changes. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_homer.c | 46 +++++++++++++++++++++++++++----------- include/hw/ppc/pnv.h | 6 ++--- include/hw/ppc/pnv_homer.h | 7 +++++- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/hw/ppc/pnv_homer.c b/hw/ppc/pnv_homer.c index 75b0ee79647a..67a1fd77ba83 100644 --- a/hw/ppc/pnv_homer.c +++ b/hw/ppc/pnv_homer.c @@ -138,16 +138,16 @@ static uint64_t pnv_homer_power8_pba_read(void *opaque, hwaddr addr, unsigned size) { PnvHomer *homer = PNV_HOMER(opaque); - PnvChip *chip = homer->chip; + PnvHomerClass *hmrc = PNV_HOMER_GET_CLASS(homer); uint32_t reg = addr >> 3; uint64_t val = 0; switch (reg) { case PBA_BAR0: - val = PNV_HOMER_BASE(chip); + val = homer->base; break; case PBA_BARMASK0: /* P8 homer region mask */ - val = (PNV_HOMER_SIZE - 1) & 0x300000; + val = (hmrc->size - 1) & 0x300000; break; case PBA_BAR3: /* P8 occ common area */ val = PNV_OCC_COMMON_AREA_BASE; @@ -179,13 +179,19 @@ static const MemoryRegionOps pnv_homer_power8_pba_ops = { .endianness = DEVICE_BIG_ENDIAN, }; +static hwaddr pnv_homer_power8_get_base(PnvChip *chip) +{ + return PNV_HOMER_BASE(chip); +} + static void pnv_homer_power8_class_init(ObjectClass *klass, void *data) { PnvHomerClass *homer = PNV_HOMER_CLASS(klass); + homer->get_base = pnv_homer_power8_get_base; + homer->size = PNV_HOMER_SIZE; homer->pba_size = PNV_XSCOM_PBA_SIZE; homer->pba_ops = &pnv_homer_power8_pba_ops; - homer->homer_size = PNV_HOMER_SIZE; homer->homer_ops = &pnv_power8_homer_ops; homer->core_max_base = PNV8_CORE_MAX_BASE; } @@ -291,16 +297,16 @@ static uint64_t pnv_homer_power9_pba_read(void *opaque, hwaddr addr, unsigned size) { PnvHomer *homer = PNV_HOMER(opaque); - PnvChip *chip = homer->chip; + PnvHomerClass *hmrc = PNV_HOMER_GET_CLASS(homer); uint32_t reg = addr >> 3; uint64_t val = 0; switch (reg) { case PBA_BAR0: - val = PNV9_HOMER_BASE(chip); + val = homer->base; break; case PBA_BARMASK0: /* P9 homer region mask */ - val = (PNV9_HOMER_SIZE - 1) & 0x300000; + val = (hmrc->size - 1) & 0x300000; break; case PBA_BAR2: /* P9 occ common area */ val = PNV9_OCC_COMMON_AREA_BASE; @@ -332,13 +338,19 @@ static const MemoryRegionOps pnv_homer_power9_pba_ops = { .endianness = DEVICE_BIG_ENDIAN, }; +static hwaddr pnv_homer_power9_get_base(PnvChip *chip) +{ + return PNV9_HOMER_BASE(chip); +} + static void pnv_homer_power9_class_init(ObjectClass *klass, void *data) { PnvHomerClass *homer = PNV_HOMER_CLASS(klass); + homer->get_base = pnv_homer_power9_get_base; + homer->size = PNV_HOMER_SIZE; homer->pba_size = PNV9_XSCOM_PBA_SIZE; homer->pba_ops = &pnv_homer_power9_pba_ops; - homer->homer_size = PNV9_HOMER_SIZE; homer->homer_ops = &pnv_power9_homer_ops; homer->core_max_base = PNV9_CORE_MAX_BASE; } @@ -354,16 +366,16 @@ static uint64_t pnv_homer_power10_pba_read(void *opaque, hwaddr addr, unsigned size) { PnvHomer *homer = PNV_HOMER(opaque); - PnvChip *chip = homer->chip; + PnvHomerClass *hmrc = PNV_HOMER_GET_CLASS(homer); uint32_t reg = addr >> 3; uint64_t val = 0; switch (reg) { case PBA_BAR0: - val = PNV10_HOMER_BASE(chip); + val = homer->base; break; case PBA_BARMASK0: /* P10 homer region mask */ - val = (PNV10_HOMER_SIZE - 1) & 0x300000; + val = (hmrc->size - 1) & 0x300000; break; case PBA_BAR2: /* P10 occ common area */ val = PNV10_OCC_COMMON_AREA_BASE; @@ -395,13 +407,19 @@ static const MemoryRegionOps pnv_homer_power10_pba_ops = { .endianness = DEVICE_BIG_ENDIAN, }; +static hwaddr pnv_homer_power10_get_base(PnvChip *chip) +{ + return PNV10_HOMER_BASE(chip); +} + static void pnv_homer_power10_class_init(ObjectClass *klass, void *data) { PnvHomerClass *homer = PNV_HOMER_CLASS(klass); + homer->get_base = pnv_homer_power10_get_base; + homer->size = PNV_HOMER_SIZE; homer->pba_size = PNV10_XSCOM_PBA_SIZE; homer->pba_ops = &pnv_homer_power10_pba_ops; - homer->homer_size = PNV10_HOMER_SIZE; homer->homer_ops = &pnv_power9_homer_ops; /* TODO */ homer->core_max_base = PNV9_CORE_MAX_BASE; } @@ -424,9 +442,11 @@ static void pnv_homer_realize(DeviceState *dev, Error **errp) homer, "xscom-pba", hmrc->pba_size); /* homer region */ + homer->base = hmrc->get_base(homer->chip); + memory_region_init_io(&homer->regs, OBJECT(dev), hmrc->homer_ops, homer, "homer-main-memory", - hmrc->homer_size); + hmrc->size); } static const Property pnv_homer_properties[] = { diff --git a/include/hw/ppc/pnv.h b/include/hw/ppc/pnv.h index fcb6699150c8..d8fca079f2fe 100644 --- a/include/hw/ppc/pnv.h +++ b/include/hw/ppc/pnv.h @@ -205,9 +205,8 @@ void pnv_bmc_set_pnor(IPMIBmc *bmc, PnvPnor *pnor); #define PNV9_OCC_SENSOR_BASE(chip) (PNV9_OCC_COMMON_AREA_BASE + \ PNV_OCC_SENSOR_DATA_BLOCK_BASE((chip)->chip_id)) -#define PNV9_HOMER_SIZE 0x0000000000400000ull #define PNV9_HOMER_BASE(chip) \ - (0x203ffd800000ull + ((uint64_t)(chip)->chip_id) * PNV9_HOMER_SIZE) + (0x203ffd800000ull + ((uint64_t)(chip)->chip_id) * PNV_HOMER_SIZE) /* * POWER10 MMIO base addresses - 16TB stride per chip @@ -250,8 +249,7 @@ void pnv_bmc_set_pnor(IPMIBmc *bmc, PnvPnor *pnor); #define PNV10_OCC_SENSOR_BASE(chip) (PNV10_OCC_COMMON_AREA_BASE + \ PNV_OCC_SENSOR_DATA_BLOCK_BASE((chip)->chip_id)) -#define PNV10_HOMER_SIZE 0x0000000000400000ull #define PNV10_HOMER_BASE(chip) \ - (0x300ffd800000ll + ((uint64_t)(chip)->chip_id) * PNV10_HOMER_SIZE) + (0x300ffd800000ll + ((uint64_t)(chip)->chip_id) * PNV_HOMER_SIZE) #endif /* PPC_PNV_H */ diff --git a/include/hw/ppc/pnv_homer.h b/include/hw/ppc/pnv_homer.h index b1c5d498dc55..5ffc0c97afed 100644 --- a/include/hw/ppc/pnv_homer.h +++ b/include/hw/ppc/pnv_homer.h @@ -42,15 +42,20 @@ struct PnvHomer { PnvChip *chip; MemoryRegion pba_regs; MemoryRegion regs; + hwaddr base; }; struct PnvHomerClass { DeviceClass parent_class; + /* Get base address of HOMER memory */ + hwaddr (*get_base)(PnvChip *chip); + /* Size of HOMER memory */ + int size; + int pba_size; const MemoryRegionOps *pba_ops; - int homer_size; const MemoryRegionOps *homer_ops; hwaddr core_max_base; From 84c085342f1e98bd2b277654209b6ff5ce5d2f0e Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Mon, 9 Dec 2024 20:36:55 +1000 Subject: [PATCH 0890/1179] ppc/pnv/occ: Better document OCCMISC bits Use defines for the OCCMISC register bits, and add a comment about the IRQ request bit, which QEMU may not model quite correctly. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_occ.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/hw/ppc/pnv_occ.c b/hw/ppc/pnv_occ.c index c6681a035a7d..5424d87ee97a 100644 --- a/hw/ppc/pnv_occ.c +++ b/hw/ppc/pnv_occ.c @@ -30,6 +30,7 @@ #define OCB_OCI_OCCMISC 0x4020 #define OCB_OCI_OCCMISC_AND 0x4021 #define OCB_OCI_OCCMISC_OR 0x4022 +#define OCCMISC_PSI_IRQ PPC_BIT(0) /* OCC sensors */ #define OCC_SENSOR_DATA_BLOCK_OFFSET 0x0000 @@ -50,13 +51,16 @@ static void pnv_occ_set_misc(PnvOCC *occ, uint64_t val) { - bool irq_state; - - val &= 0xffff000000000000ull; + val &= PPC_BITMASK(0, 18); /* Mask out unimplemented bits */ occ->occmisc = val; - irq_state = !!(val >> 63); - qemu_set_irq(occ->psi_irq, irq_state); + + /* + * OCCMISC IRQ bit triggers the interrupt on a 0->1 edge, but not clear + * how that is handled in PSI so it is level-triggered here, which is not + * really correct (but skiboot is okay with it). + */ + qemu_set_irq(occ->psi_irq, !!(val & OCCMISC_PSI_IRQ)); } static uint64_t pnv_occ_power8_xscom_read(void *opaque, hwaddr addr, From 70bc5c2498f464b63515984f1996031010476c25 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sun, 17 Nov 2024 00:29:13 +1000 Subject: [PATCH 0891/1179] ppc/pnv: Make HOMER memory a RAM region The HOMER is a region of memory used by host and firmware and microconrollers. It has very little logic by itself, just some BAR registers. Users of this memory should operate on it rather than have HOMER implement them with MMIO registers, which is not the right model. This change switches the implementation of HOMER from MMIO to RAM, and moves the OCC register implementation to in-memory structure accesses performed by the OCC model. This has the downside that access to unimplemented regions of HOMER are no longer flagged. Perhaps that could be done by adding a memory region for HOMER, and ram subregions under that for each implemented part. But for now this takes the simpler approach. Note: This brings some data structure definitions from skiboot, which does not match QEMU coding style but is not changed to make comparisons and updates simpler. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv.c | 89 ++++---- hw/ppc/pnv_homer.c | 203 +---------------- hw/ppc/pnv_occ.c | 434 ++++++++++++++++++++++++++++++++++++- include/hw/ppc/pnv_homer.h | 5 +- include/hw/ppc/pnv_occ.h | 6 + roms/SLOF | 2 +- 6 files changed, 495 insertions(+), 244 deletions(-) diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 4407b3a1a2b5..8c0a2d057311 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -1555,7 +1555,21 @@ static void pnv_chip_power8_realize(DeviceState *dev, Error **errp) return; } + /* HOMER (must be created before OCC) */ + object_property_set_link(OBJECT(&chip8->homer), "chip", OBJECT(chip), + &error_abort); + if (!qdev_realize(DEVICE(&chip8->homer), NULL, errp)) { + return; + } + /* Homer Xscom region */ + pnv_xscom_add_subregion(chip, PNV_XSCOM_PBA_BASE, &chip8->homer.pba_regs); + /* Homer RAM region */ + memory_region_add_subregion(get_system_memory(), chip8->homer.base, + &chip8->homer.mem); + /* Create the simplified OCC model */ + object_property_set_link(OBJECT(&chip8->occ), "homer", + OBJECT(&chip8->homer), &error_abort); if (!qdev_realize(DEVICE(&chip8->occ), NULL, errp)) { return; } @@ -1567,19 +1581,6 @@ static void pnv_chip_power8_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(get_system_memory(), PNV_OCC_SENSOR_BASE(chip), &chip8->occ.sram_regs); - /* HOMER */ - object_property_set_link(OBJECT(&chip8->homer), "chip", OBJECT(chip), - &error_abort); - if (!qdev_realize(DEVICE(&chip8->homer), NULL, errp)) { - return; - } - /* Homer Xscom region */ - pnv_xscom_add_subregion(chip, PNV_XSCOM_PBA_BASE, &chip8->homer.pba_regs); - - /* Homer mmio region */ - memory_region_add_subregion(get_system_memory(), PNV_HOMER_BASE(chip), - &chip8->homer.regs); - /* PHB controllers */ for (i = 0; i < chip8->num_phbs; i++) { PnvPHB *phb = chip8->phbs[i]; @@ -1863,18 +1864,6 @@ static void pnv_chip_power9_realize(DeviceState *dev, Error **errp) pnv_xscom_add_subregion(chip, PNV9_XSCOM_CHIPTOD_BASE, &chip9->chiptod.xscom_regs); - /* Create the simplified OCC model */ - if (!qdev_realize(DEVICE(&chip9->occ), NULL, errp)) { - return; - } - pnv_xscom_add_subregion(chip, PNV9_XSCOM_OCC_BASE, &chip9->occ.xscom_regs); - qdev_connect_gpio_out(DEVICE(&chip9->occ), 0, qdev_get_gpio_in( - DEVICE(psi9), PSIHB9_IRQ_OCC)); - - /* OCC SRAM model */ - memory_region_add_subregion(get_system_memory(), PNV9_OCC_SENSOR_BASE(chip), - &chip9->occ.sram_regs); - /* SBE */ if (!qdev_realize(DEVICE(&chip9->sbe), NULL, errp)) { return; @@ -1886,7 +1875,7 @@ static void pnv_chip_power9_realize(DeviceState *dev, Error **errp) qdev_connect_gpio_out(DEVICE(&chip9->sbe), 0, qdev_get_gpio_in( DEVICE(psi9), PSIHB9_IRQ_PSU)); - /* HOMER */ + /* HOMER (must be created before OCC) */ object_property_set_link(OBJECT(&chip9->homer), "chip", OBJECT(chip), &error_abort); if (!qdev_realize(DEVICE(&chip9->homer), NULL, errp)) { @@ -1894,10 +1883,23 @@ static void pnv_chip_power9_realize(DeviceState *dev, Error **errp) } /* Homer Xscom region */ pnv_xscom_add_subregion(chip, PNV9_XSCOM_PBA_BASE, &chip9->homer.pba_regs); + /* Homer RAM region */ + memory_region_add_subregion(get_system_memory(), chip9->homer.base, + &chip9->homer.mem); - /* Homer mmio region */ - memory_region_add_subregion(get_system_memory(), PNV9_HOMER_BASE(chip), - &chip9->homer.regs); + /* Create the simplified OCC model */ + object_property_set_link(OBJECT(&chip9->occ), "homer", + OBJECT(&chip9->homer), &error_abort); + if (!qdev_realize(DEVICE(&chip9->occ), NULL, errp)) { + return; + } + pnv_xscom_add_subregion(chip, PNV9_XSCOM_OCC_BASE, &chip9->occ.xscom_regs); + qdev_connect_gpio_out(DEVICE(&chip9->occ), 0, qdev_get_gpio_in( + DEVICE(psi9), PSIHB9_IRQ_OCC)); + + /* OCC SRAM model */ + memory_region_add_subregion(get_system_memory(), PNV9_OCC_SENSOR_BASE(chip), + &chip9->occ.sram_regs); /* PEC PHBs */ pnv_chip_power9_pec_realize(chip, &local_err); @@ -2144,7 +2146,22 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp) pnv_xscom_add_subregion(chip, PNV10_XSCOM_CHIPTOD_BASE, &chip10->chiptod.xscom_regs); + /* HOMER (must be created before OCC) */ + object_property_set_link(OBJECT(&chip10->homer), "chip", OBJECT(chip), + &error_abort); + if (!qdev_realize(DEVICE(&chip10->homer), NULL, errp)) { + return; + } + /* Homer Xscom region */ + pnv_xscom_add_subregion(chip, PNV10_XSCOM_PBA_BASE, + &chip10->homer.pba_regs); + /* Homer RAM region */ + memory_region_add_subregion(get_system_memory(), chip10->homer.base, + &chip10->homer.mem); + /* Create the simplified OCC model */ + object_property_set_link(OBJECT(&chip10->occ), "homer", + OBJECT(&chip10->homer), &error_abort); if (!qdev_realize(DEVICE(&chip10->occ), NULL, errp)) { return; } @@ -2169,20 +2186,6 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp) qdev_connect_gpio_out(DEVICE(&chip10->sbe), 0, qdev_get_gpio_in( DEVICE(&chip10->psi), PSIHB9_IRQ_PSU)); - /* HOMER */ - object_property_set_link(OBJECT(&chip10->homer), "chip", OBJECT(chip), - &error_abort); - if (!qdev_realize(DEVICE(&chip10->homer), NULL, errp)) { - return; - } - /* Homer Xscom region */ - pnv_xscom_add_subregion(chip, PNV10_XSCOM_PBA_BASE, - &chip10->homer.pba_regs); - - /* Homer mmio region */ - memory_region_add_subregion(get_system_memory(), PNV10_HOMER_BASE(chip), - &chip10->homer.regs); - /* N1 chiplet */ if (!qdev_realize(DEVICE(&chip10->n1_chiplet), NULL, errp)) { return; diff --git a/hw/ppc/pnv_homer.c b/hw/ppc/pnv_homer.c index 67a1fd77ba83..18a53a80c183 100644 --- a/hw/ppc/pnv_homer.c +++ b/hw/ppc/pnv_homer.c @@ -29,101 +29,6 @@ #include "hw/ppc/pnv_homer.h" #include "hw/ppc/pnv_xscom.h" - -static bool core_max_array(PnvHomer *homer, hwaddr addr) -{ - int i; - PnvHomerClass *hmrc = PNV_HOMER_GET_CLASS(homer); - - for (i = 0; i <= homer->chip->nr_cores; i++) { - if (addr == (hmrc->core_max_base + i)) { - return true; - } - } - return false; -} - -/* P8 Pstate table */ - -#define PNV8_OCC_PSTATE_VERSION 0x1f8001 -#define PNV8_OCC_PSTATE_MIN 0x1f8003 -#define PNV8_OCC_PSTATE_VALID 0x1f8000 -#define PNV8_OCC_PSTATE_THROTTLE 0x1f8002 -#define PNV8_OCC_PSTATE_NOM 0x1f8004 -#define PNV8_OCC_PSTATE_TURBO 0x1f8005 -#define PNV8_OCC_PSTATE_ULTRA_TURBO 0x1f8006 -#define PNV8_OCC_PSTATE_DATA 0x1f8008 -#define PNV8_OCC_PSTATE_ID_ZERO 0x1f8010 -#define PNV8_OCC_PSTATE_ID_ONE 0x1f8018 -#define PNV8_OCC_PSTATE_ID_TWO 0x1f8020 -#define PNV8_OCC_VDD_VOLTAGE_IDENTIFIER 0x1f8012 -#define PNV8_OCC_VCS_VOLTAGE_IDENTIFIER 0x1f8013 -#define PNV8_OCC_PSTATE_ZERO_FREQUENCY 0x1f8014 -#define PNV8_OCC_PSTATE_ONE_FREQUENCY 0x1f801c -#define PNV8_OCC_PSTATE_TWO_FREQUENCY 0x1f8024 -#define PNV8_CORE_MAX_BASE 0x1f8810 - - -static uint64_t pnv_power8_homer_read(void *opaque, hwaddr addr, - unsigned size) -{ - PnvHomer *homer = PNV_HOMER(opaque); - - switch (addr) { - case PNV8_OCC_PSTATE_VALID: - return 1; - case PNV8_OCC_PSTATE_THROTTLE: - return 0; - case PNV8_OCC_PSTATE_VERSION: - return 0x02; - case PNV8_OCC_PSTATE_MIN: - return -2; - case PNV8_OCC_PSTATE_NOM: - case PNV8_OCC_PSTATE_TURBO: - return -1; - case PNV8_OCC_PSTATE_ULTRA_TURBO: - return 0; - case PNV8_OCC_PSTATE_ID_ZERO: - return 0; - case PNV8_OCC_VDD_VOLTAGE_IDENTIFIER: - case PNV8_OCC_VCS_VOLTAGE_IDENTIFIER: - return 1; - case PNV8_OCC_PSTATE_DATA: - return 0; - /* P8 frequency for 0, 1, and 2 pstates */ - case PNV8_OCC_PSTATE_ZERO_FREQUENCY: - case PNV8_OCC_PSTATE_ONE_FREQUENCY: - case PNV8_OCC_PSTATE_TWO_FREQUENCY: - return 3000; - case PNV8_OCC_PSTATE_ID_ONE: - return -1; - case PNV8_OCC_PSTATE_ID_TWO: - return -2; - } - /* pstate table core max array */ - if (core_max_array(homer, addr)) { - return 1; - } - return 0; -} - -static void pnv_power8_homer_write(void *opaque, hwaddr addr, - uint64_t val, unsigned size) -{ - /* callback function defined to homer write */ - return; -} - -static const MemoryRegionOps pnv_power8_homer_ops = { - .read = pnv_power8_homer_read, - .write = pnv_power8_homer_write, - .valid.min_access_size = 1, - .valid.max_access_size = 8, - .impl.min_access_size = 1, - .impl.max_access_size = 8, - .endianness = DEVICE_BIG_ENDIAN, -}; - /* P8 PBA BARs */ #define PBA_BAR0 0x00 #define PBA_BAR1 0x01 @@ -192,8 +97,6 @@ static void pnv_homer_power8_class_init(ObjectClass *klass, void *data) homer->size = PNV_HOMER_SIZE; homer->pba_size = PNV_XSCOM_PBA_SIZE; homer->pba_ops = &pnv_homer_power8_pba_ops; - homer->homer_ops = &pnv_power8_homer_ops; - homer->core_max_base = PNV8_CORE_MAX_BASE; } static const TypeInfo pnv_homer_power8_type_info = { @@ -203,96 +106,6 @@ static const TypeInfo pnv_homer_power8_type_info = { .class_init = pnv_homer_power8_class_init, }; -/* P9 Pstate table */ - -#define PNV9_OCC_PSTATE_VALID 0xe2000 -#define PNV9_OCC_PSTATE_ID_ZERO 0xe2018 -#define PNV9_OCC_PSTATE_ID_ONE 0xe2020 -#define PNV9_OCC_PSTATE_ID_TWO 0xe2028 -#define PNV9_OCC_PSTATE_DATA 0xe2000 -#define PNV9_OCC_PSTATE_MINOR_VERSION 0xe2008 -#define PNV9_OCC_PSTATE_MIN 0xe2003 -#define PNV9_OCC_PSTATE_NOM 0xe2004 -#define PNV9_OCC_PSTATE_TURBO 0xe2005 -#define PNV9_OCC_PSTATE_ULTRA_TURBO 0xe2818 -#define PNV9_OCC_MAX_PSTATE_ULTRA_TURBO 0xe2006 -#define PNV9_OCC_PSTATE_MAJOR_VERSION 0xe2001 -#define PNV9_OCC_OPAL_RUNTIME_DATA 0xe2b85 -#define PNV9_CHIP_HOMER_IMAGE_POINTER 0x200008 -#define PNV9_CHIP_HOMER_BASE 0x0 -#define PNV9_OCC_PSTATE_ZERO_FREQUENCY 0xe201c -#define PNV9_OCC_PSTATE_ONE_FREQUENCY 0xe2024 -#define PNV9_OCC_PSTATE_TWO_FREQUENCY 0xe202c -#define PNV9_OCC_ROLE_MASTER_OR_SLAVE 0xe2002 -#define PNV9_CORE_MAX_BASE 0xe2819 -#define PNV9_DYNAMIC_DATA_STATE 0xe2b80 - -static uint64_t pnv_power9_homer_read(void *opaque, hwaddr addr, - unsigned size) -{ - PnvHomer *homer = PNV_HOMER(opaque); - - switch (addr) { - case PNV9_OCC_PSTATE_VALID: - return 1; - case PNV9_OCC_MAX_PSTATE_ULTRA_TURBO: - case PNV9_OCC_PSTATE_ID_ZERO: - return 0; - case PNV9_OCC_ROLE_MASTER_OR_SLAVE: - if (homer->chip->chip_id == 0) { - return 0x1; /* master */ - } else { - return 0x0; /* slave */ - } - case PNV9_OCC_PSTATE_NOM: - case PNV9_OCC_PSTATE_TURBO: - case PNV9_OCC_PSTATE_ID_ONE: - case PNV9_OCC_PSTATE_ULTRA_TURBO: - case PNV9_OCC_OPAL_RUNTIME_DATA: - return 1; - case PNV9_OCC_PSTATE_MIN: - case PNV9_OCC_PSTATE_ID_TWO: - return 2; - - /* 3000 khz frequency for 0, 1, and 2 pstates */ - case PNV9_OCC_PSTATE_ZERO_FREQUENCY: - case PNV9_OCC_PSTATE_ONE_FREQUENCY: - case PNV9_OCC_PSTATE_TWO_FREQUENCY: - return 3000; - case PNV9_OCC_PSTATE_MAJOR_VERSION: - return 0x90; - case PNV9_OCC_PSTATE_MINOR_VERSION: - return 0x01; - case PNV9_CHIP_HOMER_BASE: - case PNV9_CHIP_HOMER_IMAGE_POINTER: - return 0; - case PNV9_DYNAMIC_DATA_STATE: - return 0x03; /* active */ - } - /* pstate table core max array */ - if (core_max_array(homer, addr)) { - return 1; - } - return 0; -} - -static void pnv_power9_homer_write(void *opaque, hwaddr addr, - uint64_t val, unsigned size) -{ - /* callback function defined to homer write */ - return; -} - -static const MemoryRegionOps pnv_power9_homer_ops = { - .read = pnv_power9_homer_read, - .write = pnv_power9_homer_write, - .valid.min_access_size = 1, - .valid.max_access_size = 8, - .impl.min_access_size = 1, - .impl.max_access_size = 8, - .endianness = DEVICE_BIG_ENDIAN, -}; - static uint64_t pnv_homer_power9_pba_read(void *opaque, hwaddr addr, unsigned size) { @@ -351,8 +164,6 @@ static void pnv_homer_power9_class_init(ObjectClass *klass, void *data) homer->size = PNV_HOMER_SIZE; homer->pba_size = PNV9_XSCOM_PBA_SIZE; homer->pba_ops = &pnv_homer_power9_pba_ops; - homer->homer_ops = &pnv_power9_homer_ops; - homer->core_max_base = PNV9_CORE_MAX_BASE; } static const TypeInfo pnv_homer_power9_type_info = { @@ -420,8 +231,6 @@ static void pnv_homer_power10_class_init(ObjectClass *klass, void *data) homer->size = PNV_HOMER_SIZE; homer->pba_size = PNV10_XSCOM_PBA_SIZE; homer->pba_ops = &pnv_homer_power10_pba_ops; - homer->homer_ops = &pnv_power9_homer_ops; /* TODO */ - homer->core_max_base = PNV9_CORE_MAX_BASE; } static const TypeInfo pnv_homer_power10_type_info = { @@ -435,18 +244,22 @@ static void pnv_homer_realize(DeviceState *dev, Error **errp) { PnvHomer *homer = PNV_HOMER(dev); PnvHomerClass *hmrc = PNV_HOMER_GET_CLASS(homer); + char homer_str[32]; assert(homer->chip); pnv_xscom_region_init(&homer->pba_regs, OBJECT(dev), hmrc->pba_ops, homer, "xscom-pba", hmrc->pba_size); - /* homer region */ + /* Homer RAM region */ homer->base = hmrc->get_base(homer->chip); - memory_region_init_io(&homer->regs, OBJECT(dev), - hmrc->homer_ops, homer, "homer-main-memory", - hmrc->size); + snprintf(homer_str, sizeof(homer_str), "homer-chip%d-memory", + homer->chip->chip_id); + if (!memory_region_init_ram(&homer->mem, OBJECT(homer), + homer_str, hmrc->size, errp)) { + return; + } } static const Property pnv_homer_properties[] = { diff --git a/hw/ppc/pnv_occ.c b/hw/ppc/pnv_occ.c index 5424d87ee97a..22b07a415ac0 100644 --- a/hw/ppc/pnv_occ.c +++ b/hw/ppc/pnv_occ.c @@ -24,9 +24,13 @@ #include "hw/irq.h" #include "hw/qdev-properties.h" #include "hw/ppc/pnv.h" +#include "hw/ppc/pnv_chip.h" #include "hw/ppc/pnv_xscom.h" #include "hw/ppc/pnv_occ.h" +#define P8_HOMER_OPAL_DATA_OFFSET 0x1F8000 +#define P9_HOMER_OPAL_DATA_OFFSET 0x0E2000 + #define OCB_OCI_OCCMISC 0x4020 #define OCB_OCI_OCCMISC_AND 0x4021 #define OCB_OCI_OCCMISC_OR 0x4022 @@ -166,7 +170,11 @@ const MemoryRegionOps pnv_occ_sram_ops = { static void pnv_occ_power8_class_init(ObjectClass *klass, void *data) { PnvOCCClass *poc = PNV_OCC_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + dc->desc = "PowerNV OCC Controller (POWER8)"; + poc->opal_shared_memory_offset = P8_HOMER_OPAL_DATA_OFFSET; + poc->opal_shared_memory_version = 0x02; poc->xscom_size = PNV_XSCOM_OCC_SIZE; poc->xscom_ops = &pnv_occ_power8_xscom_ops; } @@ -239,8 +247,11 @@ static void pnv_occ_power9_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->desc = "PowerNV OCC Controller (POWER9)"; + poc->opal_shared_memory_offset = P9_HOMER_OPAL_DATA_OFFSET; + poc->opal_shared_memory_version = 0x90; poc->xscom_size = PNV9_XSCOM_OCC_SIZE; poc->xscom_ops = &pnv_occ_power9_xscom_ops; + assert(!dc->user_creatable); } static const TypeInfo pnv_occ_power9_type_info = { @@ -263,10 +274,19 @@ static const TypeInfo pnv_occ_power10_type_info = { .class_init = pnv_occ_power10_class_init, }; +static bool occ_init_homer_memory(PnvOCC *occ, Error **errp); + static void pnv_occ_realize(DeviceState *dev, Error **errp) { PnvOCC *occ = PNV_OCC(dev); PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); + PnvHomer *homer = occ->homer; + + assert(homer); + + if (!occ_init_homer_memory(occ, errp)) { + return; + } occ->occmisc = 0; @@ -282,12 +302,16 @@ static void pnv_occ_realize(DeviceState *dev, Error **errp) qdev_init_gpio_out(dev, &occ->psi_irq, 1); } +static const Property pnv_occ_properties[] = { + DEFINE_PROP_LINK("homer", PnvOCC, homer, TYPE_PNV_HOMER, PnvHomer *), +}; + static void pnv_occ_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = pnv_occ_realize; - dc->desc = "PowerNV OCC Controller"; + device_class_set_props(dc, pnv_occ_properties); dc->user_creatable = false; } @@ -309,3 +333,411 @@ static void pnv_occ_register_types(void) } type_init(pnv_occ_register_types); + +/* From skiboot/hw/occ.c with tab to space conversion */ +/* OCC Communication Area for PStates */ + +#define OPAL_DYNAMIC_DATA_OFFSET 0x0B80 +/* relative to HOMER_OPAL_DATA_OFFSET */ + +#define MAX_PSTATES 256 +#define MAX_P8_CORES 12 +#define MAX_P9_CORES 24 +#define MAX_P10_CORES 32 + +#define MAX_OPAL_CMD_DATA_LENGTH 4090 +#define MAX_OCC_RSP_DATA_LENGTH 8698 + +#define P8_PIR_CORE_MASK 0xFFF8 +#define P9_PIR_QUAD_MASK 0xFFF0 +#define P10_PIR_CHIP_MASK 0x0000 +#define FREQ_MAX_IN_DOMAIN 0 +#define FREQ_MOST_RECENTLY_SET 1 + +#define u8 uint8_t +#define s8 int8_t +#define u16 uint16_t +#define s16 int16_t +#define u32 uint32_t +#define s32 int32_t +#define u64 uint64_t +#define s64 int64_t +#define __be16 uint16_t +#define __be32 uint32_t +#define __packed QEMU_PACKED + +/** + * OCC-OPAL Shared Memory Region + * + * Reference document : + * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf + * + * Supported layout versions: + * - 0x01, 0x02 : P8 + * https://github.com/open-power/occ/blob/master_p8/src/occ/proc/proc_pstate.h + * + * - 0x90 : P9 + * https://github.com/open-power/occ/blob/master/src/occ_405/proc/proc_pstate.h + * In 0x90 the data is separated into :- + * -- Static Data (struct occ_pstate_table): Data is written once by OCC + * -- Dynamic Data (struct occ_dynamic_data): Data is updated at runtime + * + * struct occ_pstate_table - Pstate table layout + * @valid: Indicates if data is valid + * @version: Layout version [Major/Minor] + * @v2.throttle: Reason for limiting the max pstate + * @v9.occ_role: OCC role (Master/Slave) + * @v#.pstate_min: Minimum pstate ever allowed + * @v#.pstate_nom: Nominal pstate + * @v#.pstate_turbo: Maximum turbo pstate + * @v#.pstate_ultra_turbo: Maximum ultra turbo pstate and the maximum + * pstate ever allowed + * @v#.pstates: Pstate-id and frequency list from Pmax to Pmin + * @v#.pstates.id: Pstate-id + * @v#.pstates.flags: Pstate-flag(reserved) + * @v2.pstates.vdd: Voltage Identifier + * @v2.pstates.vcs: Voltage Identifier + * @v#.pstates.freq_khz: Frequency in KHz + * @v#.core_max[1..N]: Max pstate with N active cores + * @spare/reserved/pad: Unused data + */ +struct occ_pstate_table { + u8 valid; + u8 version; + union __packed { + struct __packed { /* Version 0x01 and 0x02 */ + u8 throttle; + s8 pstate_min; + s8 pstate_nom; + s8 pstate_turbo; + s8 pstate_ultra_turbo; + u8 spare; + u64 reserved; + struct __packed { + s8 id; + u8 flags; + u8 vdd; + u8 vcs; + __be32 freq_khz; + } pstates[MAX_PSTATES]; + s8 core_max[MAX_P8_CORES]; + u8 pad[100]; + } v2; + struct __packed { /* Version 0x90 */ + u8 occ_role; + u8 pstate_min; + u8 pstate_nom; + u8 pstate_turbo; + u8 pstate_ultra_turbo; + u8 spare; + u64 reserved1; + u64 reserved2; + struct __packed { + u8 id; + u8 flags; + u16 reserved; + __be32 freq_khz; + } pstates[MAX_PSTATES]; + u8 core_max[MAX_P9_CORES]; + u8 pad[56]; + } v9; + struct __packed { /* Version 0xA0 */ + u8 occ_role; + u8 pstate_min; + u8 pstate_fixed_freq; + u8 pstate_base; + u8 pstate_ultra_turbo; + u8 pstate_fmax; + u8 minor; + u8 pstate_bottom_throttle; + u8 spare; + u8 spare1; + u32 reserved_32; + u64 reserved_64; + struct __packed { + u8 id; + u8 valid; + u16 reserved; + __be32 freq_khz; + } pstates[MAX_PSTATES]; + u8 core_max[MAX_P10_CORES]; + u8 pad[48]; + } v10; + }; +} __packed; + +/** + * OPAL-OCC Command Response Interface + * + * OPAL-OCC Command Buffer + * + * --------------------------------------------------------------------- + * | OPAL | Cmd | OPAL | | Cmd Data | Cmd Data | OPAL | + * | Cmd | Request | OCC | Reserved | Length | Length | Cmd | + * | Flags | ID | Cmd | | (MSB) | (LSB) | Data... | + * --------------------------------------------------------------------- + * | ….OPAL Command Data up to max of Cmd Data Length 4090 bytes | + * | | + * --------------------------------------------------------------------- + * + * OPAL Command Flag + * + * ----------------------------------------------------------------- + * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | + * | (msb) | | | | | | | (lsb) | + * ----------------------------------------------------------------- + * |Cmd | | | | | | | | + * |Ready | | | | | | | | + * ----------------------------------------------------------------- + * + * struct opal_command_buffer - Defines the layout of OPAL command buffer + * @flag: Provides general status of the command + * @request_id: Token to identify request + * @cmd: Command sent + * @data_size: Command data length + * @data: Command specific data + * @spare: Unused byte + */ +struct opal_command_buffer { + u8 flag; + u8 request_id; + u8 cmd; + u8 spare; + __be16 data_size; + u8 data[MAX_OPAL_CMD_DATA_LENGTH]; +} __packed; + +/** + * OPAL-OCC Response Buffer + * + * --------------------------------------------------------------------- + * | OCC | Cmd | OPAL | Response | Rsp Data | Rsp Data | OPAL | + * | Rsp | Request | OCC | Status | Length | Length | Rsp | + * | Flags | ID | Cmd | | (MSB) | (LSB) | Data... | + * --------------------------------------------------------------------- + * | ….OPAL Response Data up to max of Rsp Data Length 8698 bytes | + * | | + * --------------------------------------------------------------------- + * + * OCC Response Flag + * + * ----------------------------------------------------------------- + * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | + * | (msb) | | | | | | | (lsb) | + * ----------------------------------------------------------------- + * | | | | | | |OCC in | Rsp | + * | | | | | | |progress|Ready | + * ----------------------------------------------------------------- + * + * struct occ_response_buffer - Defines the layout of OCC response buffer + * @flag: Provides general status of the response + * @request_id: Token to identify request + * @cmd: Command requested + * @status: Indicates success/failure status of + * the command + * @data_size: Response data length + * @data: Response specific data + */ +struct occ_response_buffer { + u8 flag; + u8 request_id; + u8 cmd; + u8 status; + __be16 data_size; + u8 data[MAX_OCC_RSP_DATA_LENGTH]; +} __packed; + +/** + * OCC-OPAL Shared Memory Interface Dynamic Data Vx90 + * + * struct occ_dynamic_data - Contains runtime attributes + * @occ_state: Current state of OCC + * @major_version: Major version number + * @minor_version: Minor version number (backwards compatible) + * Version 1 indicates GPU presence populated + * @gpus_present: Bitmask of GPUs present (on systems where GPU + * presence is detected through APSS) + * @cpu_throttle: Reason for limiting the max pstate + * @mem_throttle: Reason for throttling memory + * @quick_pwr_drop: Indicates if QPD is asserted + * @pwr_shifting_ratio: Indicates the current percentage of power to + * take away from the CPU vs GPU when shifting + * power to maintain a power cap. Value of 100 + * means take all power from CPU. + * @pwr_cap_type: Indicates type of power cap in effect + * @hard_min_pwr_cap: Hard minimum system power cap in Watts. + * Guaranteed unless hardware failure + * @max_pwr_cap: Maximum allowed system power cap in Watts + * @cur_pwr_cap: Current system power cap + * @soft_min_pwr_cap: Soft powercap minimum. OCC may or may not be + * able to maintain this + * @spare/reserved: Unused data + * @cmd: Opal Command Buffer + * @rsp: OCC Response Buffer + */ +struct occ_dynamic_data { + u8 occ_state; + u8 major_version; + u8 minor_version; + u8 gpus_present; + union __packed { + struct __packed { /* Version 0x90 */ + u8 spare1; + } v9; + struct __packed { /* Version 0xA0 */ + u8 wof_enabled; + } v10; + }; + u8 cpu_throttle; + u8 mem_throttle; + u8 quick_pwr_drop; + u8 pwr_shifting_ratio; + u8 pwr_cap_type; + __be16 hard_min_pwr_cap; + __be16 max_pwr_cap; + __be16 cur_pwr_cap; + __be16 soft_min_pwr_cap; + u8 pad[110]; + struct opal_command_buffer cmd; + struct occ_response_buffer rsp; +} __packed; + +enum occ_response_status { + OCC_RSP_SUCCESS = 0x00, + OCC_RSP_INVALID_COMMAND = 0x11, + OCC_RSP_INVALID_CMD_DATA_LENGTH = 0x12, + OCC_RSP_INVALID_DATA = 0x13, + OCC_RSP_INTERNAL_ERROR = 0x15, +}; + +#define OCC_ROLE_SLAVE 0x00 +#define OCC_ROLE_MASTER 0x01 + +#define OCC_FLAG_RSP_READY 0x01 +#define OCC_FLAG_CMD_IN_PROGRESS 0x02 +#define OPAL_FLAG_CMD_READY 0x80 + +#define PCAP_MAX_POWER_W 100 +#define PCAP_SOFT_MIN_POWER_W 20 +#define PCAP_HARD_MIN_POWER_W 10 + +static bool occ_write_static_data(PnvOCC *occ, + struct occ_pstate_table *static_data, + Error **errp) +{ + PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); + PnvHomer *homer = occ->homer; + hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; + MemTxResult ret; + + ret = address_space_write(&address_space_memory, static_addr, + MEMTXATTRS_UNSPECIFIED, static_data, + sizeof(*static_data)); + if (ret != MEMTX_OK) { + error_setg(errp, "OCC: cannot write OCC-OPAL static data"); + return false; + } + + return true; +} + +static bool occ_write_dynamic_data(PnvOCC *occ, + struct occ_dynamic_data *dynamic_data, + Error **errp) +{ + PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); + PnvHomer *homer = occ->homer; + hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; + hwaddr dynamic_addr = static_addr + OPAL_DYNAMIC_DATA_OFFSET; + MemTxResult ret; + + ret = address_space_write(&address_space_memory, dynamic_addr, + MEMTXATTRS_UNSPECIFIED, dynamic_data, + sizeof(*dynamic_data)); + if (ret != MEMTX_OK) { + error_setg(errp, "OCC: cannot write OCC-OPAL dynamic data"); + return false; + } + + return true; +} + +static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) +{ + PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); + PnvHomer *homer = occ->homer; + PnvChip *chip = homer->chip; + struct occ_pstate_table static_data; + struct occ_dynamic_data dynamic_data; + int i; + + memset(&static_data, 0, sizeof(static_data)); + static_data.valid = 1; + static_data.version = poc->opal_shared_memory_version; + switch (poc->opal_shared_memory_version) { + case 0x02: + static_data.v2.throttle = 0; + static_data.v2.pstate_min = -2; + static_data.v2.pstate_nom = -1; + static_data.v2.pstate_turbo = -1; + static_data.v2.pstate_ultra_turbo = 0; + static_data.v2.pstates[0].id = 0; + static_data.v2.pstates[1].freq_khz = cpu_to_be32(3000); + static_data.v2.pstates[1].id = -1; + static_data.v2.pstates[1].freq_khz = cpu_to_be32(3000); + static_data.v2.pstates[2].id = -2; + static_data.v2.pstates[2].freq_khz = cpu_to_be32(3000); + for (i = 0; i < chip->nr_cores; i++) { + static_data.v2.core_max[i] = 1; + } + break; + case 0x90: + if (chip->chip_id == 0) { + static_data.v9.occ_role = OCC_ROLE_MASTER; + } else { + static_data.v9.occ_role = OCC_ROLE_SLAVE; + } + static_data.v9.pstate_min = 2; + static_data.v9.pstate_nom = 1; + static_data.v9.pstate_turbo = 1; + static_data.v9.pstate_ultra_turbo = 0; + static_data.v9.pstates[0].id = 0; + static_data.v9.pstates[0].freq_khz = cpu_to_be32(3000); + static_data.v9.pstates[1].id = 1; + static_data.v9.pstates[1].freq_khz = cpu_to_be32(3000); + static_data.v9.pstates[2].id = 2; + static_data.v9.pstates[2].freq_khz = cpu_to_be32(3000); + for (i = 0; i < chip->nr_cores; i++) { + static_data.v9.core_max[i] = 1; + } + break; + default: + g_assert_not_reached(); + } + if (!occ_write_static_data(occ, &static_data, errp)) { + return false; + } + + memset(&dynamic_data, 0, sizeof(dynamic_data)); + dynamic_data.occ_state = 0x3; /* active */ + dynamic_data.major_version = 0x0; + dynamic_data.hard_min_pwr_cap = cpu_to_be16(PCAP_HARD_MIN_POWER_W); + dynamic_data.max_pwr_cap = cpu_to_be16(PCAP_MAX_POWER_W); + dynamic_data.cur_pwr_cap = cpu_to_be16(PCAP_MAX_POWER_W); + dynamic_data.soft_min_pwr_cap = cpu_to_be16(PCAP_SOFT_MIN_POWER_W); + switch (poc->opal_shared_memory_version) { + case 0x90: + dynamic_data.minor_version = 0x1; + break; + case 0x02: + dynamic_data.minor_version = 0x0; + break; + default: + g_assert_not_reached(); + } + if (!occ_write_dynamic_data(occ, &dynamic_data, errp)) { + return false; + } + + return true; +} diff --git a/include/hw/ppc/pnv_homer.h b/include/hw/ppc/pnv_homer.h index 5ffc0c97afed..a6f2710fa16b 100644 --- a/include/hw/ppc/pnv_homer.h +++ b/include/hw/ppc/pnv_homer.h @@ -41,7 +41,7 @@ struct PnvHomer { PnvChip *chip; MemoryRegion pba_regs; - MemoryRegion regs; + MemoryRegion mem; hwaddr base; }; @@ -56,9 +56,6 @@ struct PnvHomerClass { int pba_size; const MemoryRegionOps *pba_ops; - const MemoryRegionOps *homer_ops; - - hwaddr core_max_base; }; #endif /* PPC_PNV_HOMER_H */ diff --git a/include/hw/ppc/pnv_occ.h b/include/hw/ppc/pnv_occ.h index df321244e3b1..f99486098085 100644 --- a/include/hw/ppc/pnv_occ.h +++ b/include/hw/ppc/pnv_occ.h @@ -46,6 +46,9 @@ struct PnvOCC { qemu_irq psi_irq; + /* OCCs operate on regions of HOMER memory */ + PnvHomer *homer; + MemoryRegion xscom_regs; MemoryRegion sram_regs; }; @@ -53,6 +56,9 @@ struct PnvOCC { struct PnvOCCClass { DeviceClass parent_class; + hwaddr opal_shared_memory_offset; /* offset in HOMER */ + uint8_t opal_shared_memory_version; + int xscom_size; const MemoryRegionOps *xscom_ops; }; diff --git a/roms/SLOF b/roms/SLOF index ee03aec2c106..3a259df2449f 160000 --- a/roms/SLOF +++ b/roms/SLOF @@ -1 +1 @@ -Subproject commit ee03aec2c106a699aaddd2d3dd52cbd7b7e8d544 +Subproject commit 3a259df2449fc4a4e43ab5f33f0b2c66484b4bc3 From fedbab2c512e8685c2ae87a05f8862e5e83f81ed Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Tue, 10 Dec 2024 00:47:48 +1000 Subject: [PATCH 0892/1179] ppc/pnv/occ: Update pstate frequency tables OCC pstate frequencies are in kHz, so the OCC data was 3-4MHz. Upgrade to GHz. Make each pstate have a different frequency. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_occ.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/ppc/pnv_occ.c b/hw/ppc/pnv_occ.c index 22b07a415ac0..19ccfe1bbfc1 100644 --- a/hw/ppc/pnv_occ.c +++ b/hw/ppc/pnv_occ.c @@ -682,11 +682,11 @@ static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) static_data.v2.pstate_turbo = -1; static_data.v2.pstate_ultra_turbo = 0; static_data.v2.pstates[0].id = 0; - static_data.v2.pstates[1].freq_khz = cpu_to_be32(3000); + static_data.v2.pstates[1].freq_khz = cpu_to_be32(4000000); static_data.v2.pstates[1].id = -1; - static_data.v2.pstates[1].freq_khz = cpu_to_be32(3000); + static_data.v2.pstates[1].freq_khz = cpu_to_be32(3000000); static_data.v2.pstates[2].id = -2; - static_data.v2.pstates[2].freq_khz = cpu_to_be32(3000); + static_data.v2.pstates[2].freq_khz = cpu_to_be32(2000000); for (i = 0; i < chip->nr_cores; i++) { static_data.v2.core_max[i] = 1; } @@ -702,11 +702,11 @@ static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) static_data.v9.pstate_turbo = 1; static_data.v9.pstate_ultra_turbo = 0; static_data.v9.pstates[0].id = 0; - static_data.v9.pstates[0].freq_khz = cpu_to_be32(3000); + static_data.v9.pstates[0].freq_khz = cpu_to_be32(4000000); static_data.v9.pstates[1].id = 1; - static_data.v9.pstates[1].freq_khz = cpu_to_be32(3000); + static_data.v9.pstates[1].freq_khz = cpu_to_be32(3000000); static_data.v9.pstates[2].id = 2; - static_data.v9.pstates[2].freq_khz = cpu_to_be32(3000); + static_data.v9.pstates[2].freq_khz = cpu_to_be32(2000000); for (i = 0; i < chip->nr_cores; i++) { static_data.v9.core_max[i] = 1; } From 028b1803fbe79aaf598b3254338d7010960d1b44 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Mon, 9 Dec 2024 23:00:39 +1000 Subject: [PATCH 0893/1179] ppc/pnv/occ: Add POWER10 OCC-OPAL data format Add POWER10 OCC-OPAL data format. POWER10 changes major version and adds a few fields. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_occ.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/hw/ppc/pnv_occ.c b/hw/ppc/pnv_occ.c index 19ccfe1bbfc1..34decb1700b6 100644 --- a/hw/ppc/pnv_occ.c +++ b/hw/ppc/pnv_occ.c @@ -263,14 +263,20 @@ static const TypeInfo pnv_occ_power9_type_info = { static void pnv_occ_power10_class_init(ObjectClass *klass, void *data) { + PnvOCCClass *poc = PNV_OCC_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); dc->desc = "PowerNV OCC Controller (POWER10)"; + poc->opal_shared_memory_offset = P9_HOMER_OPAL_DATA_OFFSET; + poc->opal_shared_memory_version = 0xA0; + poc->xscom_size = PNV9_XSCOM_OCC_SIZE; + poc->xscom_ops = &pnv_occ_power9_xscom_ops; + assert(!dc->user_creatable); } static const TypeInfo pnv_occ_power10_type_info = { .name = TYPE_PNV10_OCC, - .parent = TYPE_PNV9_OCC, + .parent = TYPE_PNV_OCC, .class_init = pnv_occ_power10_class_init, }; @@ -711,6 +717,37 @@ static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) static_data.v9.core_max[i] = 1; } break; + case 0xA0: + if (chip->chip_id == 0) { + static_data.v10.occ_role = OCC_ROLE_MASTER; + } else { + static_data.v10.occ_role = OCC_ROLE_SLAVE; + } + static_data.v10.pstate_min = 4; + static_data.v10.pstate_fixed_freq = 3; + static_data.v10.pstate_base = 2; + static_data.v10.pstate_ultra_turbo = 0; + static_data.v10.pstate_fmax = 1; + static_data.v10.minor = 0x01; + static_data.v10.pstates[0].valid = 1; + static_data.v10.pstates[0].id = 0; + static_data.v10.pstates[0].freq_khz = cpu_to_be32(4200000); + static_data.v10.pstates[1].valid = 1; + static_data.v10.pstates[1].id = 1; + static_data.v10.pstates[1].freq_khz = cpu_to_be32(4000000); + static_data.v10.pstates[2].valid = 1; + static_data.v10.pstates[2].id = 2; + static_data.v10.pstates[2].freq_khz = cpu_to_be32(3800000); + static_data.v10.pstates[3].valid = 1; + static_data.v10.pstates[3].id = 3; + static_data.v10.pstates[3].freq_khz = cpu_to_be32(3000000); + static_data.v10.pstates[4].valid = 1; + static_data.v10.pstates[4].id = 4; + static_data.v10.pstates[4].freq_khz = cpu_to_be32(2000000); + for (i = 0; i < chip->nr_cores; i++) { + static_data.v10.core_max[i] = 1; + } + break; default: g_assert_not_reached(); } @@ -726,6 +763,10 @@ static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) dynamic_data.cur_pwr_cap = cpu_to_be16(PCAP_MAX_POWER_W); dynamic_data.soft_min_pwr_cap = cpu_to_be16(PCAP_SOFT_MIN_POWER_W); switch (poc->opal_shared_memory_version) { + case 0xA0: + dynamic_data.minor_version = 0x1; + dynamic_data.v10.wof_enabled = 0x1; + break; case 0x90: dynamic_data.minor_version = 0x1; break; From a1750b2cba9c70191221f206bfbed277251dd644 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Mon, 9 Dec 2024 20:00:45 +1000 Subject: [PATCH 0894/1179] ppc/pnv/occ: Implement a basic dynamic OCC model The OCC is an On Chip Controller that handles various thermal and power management. It is a PPC405 microcontroller that runs its own firmware which is out of scope of the powernv machine model. Some dynamic behaviour and interfaces that are important for host CPU testing can be implemented with a much simpler state machine. This change adds a 100ms timer that ticks through a simple state machine that looks for "OCC command requests" coming from host firmware, and responds to them. For now the powercap command is implemented because that is used by OPAL and exported to Linux and is easy to test. $ F=/sys/firmware/opal/powercap/system-powercap/powercap-current $ cat $F 100 $ echo 50 | sudo tee $F 50 $ cat $F 50 Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_occ.c | 146 +++++++++++++++++++++++++++++++++++++++ include/hw/ppc/pnv_occ.h | 3 + 2 files changed, 149 insertions(+) diff --git a/hw/ppc/pnv_occ.c b/hw/ppc/pnv_occ.c index 34decb1700b6..d9ce35a4d658 100644 --- a/hw/ppc/pnv_occ.c +++ b/hw/ppc/pnv_occ.c @@ -35,6 +35,7 @@ #define OCB_OCI_OCCMISC_AND 0x4021 #define OCB_OCI_OCCMISC_OR 0x4022 #define OCCMISC_PSI_IRQ PPC_BIT(0) +#define OCCMISC_IRQ_SHMEM PPC_BIT(3) /* OCC sensors */ #define OCC_SENSOR_DATA_BLOCK_OFFSET 0x0000 @@ -67,6 +68,11 @@ static void pnv_occ_set_misc(PnvOCC *occ, uint64_t val) qemu_set_irq(occ->psi_irq, !!(val & OCCMISC_PSI_IRQ)); } +static void pnv_occ_raise_msg_irq(PnvOCC *occ) +{ + pnv_occ_set_misc(occ, occ->occmisc | OCCMISC_PSI_IRQ | OCCMISC_IRQ_SHMEM); +} + static uint64_t pnv_occ_power8_xscom_read(void *opaque, hwaddr addr, unsigned size) { @@ -281,6 +287,20 @@ static const TypeInfo pnv_occ_power10_type_info = { }; static bool occ_init_homer_memory(PnvOCC *occ, Error **errp); +static bool occ_model_tick(PnvOCC *occ); + +/* Relatively arbitrary */ +#define OCC_POLL_MS 100 + +static void occ_state_machine_timer(void *opaque) +{ + PnvOCC *occ = opaque; + uint64_t next = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + OCC_POLL_MS; + + if (occ_model_tick(occ)) { + timer_mod(&occ->state_machine_timer, next); + } +} static void pnv_occ_realize(DeviceState *dev, Error **errp) { @@ -306,6 +326,10 @@ static void pnv_occ_realize(DeviceState *dev, Error **errp) PNV_OCC_SENSOR_DATA_BLOCK_SIZE); qdev_init_gpio_out(dev, &occ->psi_irq, 1); + + timer_init_ms(&occ->state_machine_timer, QEMU_CLOCK_VIRTUAL, + occ_state_machine_timer, occ); + timer_mod(&occ->state_machine_timer, OCC_POLL_MS); } static const Property pnv_occ_properties[] = { @@ -647,6 +671,27 @@ static bool occ_write_static_data(PnvOCC *occ, return true; } +static bool occ_read_dynamic_data(PnvOCC *occ, + struct occ_dynamic_data *dynamic_data, + Error **errp) +{ + PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); + PnvHomer *homer = occ->homer; + hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; + hwaddr dynamic_addr = static_addr + OPAL_DYNAMIC_DATA_OFFSET; + MemTxResult ret; + + ret = address_space_read(&address_space_memory, dynamic_addr, + MEMTXATTRS_UNSPECIFIED, dynamic_data, + sizeof(*dynamic_data)); + if (ret != MEMTX_OK) { + error_setg(errp, "OCC: cannot read OCC-OPAL dynamic data"); + return false; + } + + return true; +} + static bool occ_write_dynamic_data(PnvOCC *occ, struct occ_dynamic_data *dynamic_data, Error **errp) @@ -668,6 +713,107 @@ static bool occ_write_dynamic_data(PnvOCC *occ, return true; } +static bool occ_opal_send_response(PnvOCC *occ, + struct occ_dynamic_data *dynamic_data, + enum occ_response_status status, + uint8_t *data, uint16_t datalen) +{ + struct opal_command_buffer *cmd = &dynamic_data->cmd; + struct occ_response_buffer *rsp = &dynamic_data->rsp; + + rsp->request_id = cmd->request_id; + rsp->cmd = cmd->cmd; + rsp->status = status; + rsp->data_size = cpu_to_be16(datalen); + if (datalen) { + memcpy(rsp->data, data, datalen); + } + if (!occ_write_dynamic_data(occ, dynamic_data, NULL)) { + return false; + } + /* Would be a memory barrier here */ + rsp->flag = OCC_FLAG_RSP_READY; + cmd->flag = 0; + if (!occ_write_dynamic_data(occ, dynamic_data, NULL)) { + return false; + } + + pnv_occ_raise_msg_irq(occ); + + return true; +} + +/* Returns error status */ +static bool occ_opal_process_command(PnvOCC *occ, + struct occ_dynamic_data *dynamic_data) +{ + struct opal_command_buffer *cmd = &dynamic_data->cmd; + struct occ_response_buffer *rsp = &dynamic_data->rsp; + + if (rsp->flag == 0) { + /* Spend one "tick" in the in-progress state */ + rsp->flag = OCC_FLAG_CMD_IN_PROGRESS; + return occ_write_dynamic_data(occ, dynamic_data, NULL); + } else if (rsp->flag != OCC_FLAG_CMD_IN_PROGRESS) { + return occ_opal_send_response(occ, dynamic_data, + OCC_RSP_INTERNAL_ERROR, + NULL, 0); + } + + switch (cmd->cmd) { + case 0xD1: { /* SET_POWER_CAP */ + uint16_t data; + if (be16_to_cpu(cmd->data_size) != 2) { + return occ_opal_send_response(occ, dynamic_data, + OCC_RSP_INVALID_CMD_DATA_LENGTH, + (uint8_t *)&dynamic_data->cur_pwr_cap, + 2); + } + data = be16_to_cpu(*(uint16_t *)cmd->data); + if (data == 0) { /* clear power cap */ + dynamic_data->pwr_cap_type = 0x00; /* none */ + data = PCAP_MAX_POWER_W; + } else { + dynamic_data->pwr_cap_type = 0x02; /* user set in-band */ + if (data < PCAP_HARD_MIN_POWER_W) { + data = PCAP_HARD_MIN_POWER_W; + } else if (data > PCAP_MAX_POWER_W) { + data = PCAP_MAX_POWER_W; + } + } + dynamic_data->cur_pwr_cap = cpu_to_be16(data); + return occ_opal_send_response(occ, dynamic_data, + OCC_RSP_SUCCESS, + (uint8_t *)&dynamic_data->cur_pwr_cap, 2); + } + + default: + return occ_opal_send_response(occ, dynamic_data, + OCC_RSP_INVALID_COMMAND, + NULL, 0); + } + g_assert_not_reached(); +} + +static bool occ_model_tick(PnvOCC *occ) +{ + struct occ_dynamic_data dynamic_data; + + if (!occ_read_dynamic_data(occ, &dynamic_data, NULL)) { + /* Can't move OCC state field to safe because we can't map it! */ + qemu_log("OCC: failed to read HOMER data, shutting down OCC\n"); + return false; + } + if (dynamic_data.cmd.flag == OPAL_FLAG_CMD_READY) { + if (!occ_opal_process_command(occ, &dynamic_data)) { + qemu_log("OCC: failed to write HOMER data, shutting down OCC\n"); + return false; + } + } + + return true; +} + static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) { PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); diff --git a/include/hw/ppc/pnv_occ.h b/include/hw/ppc/pnv_occ.h index f99486098085..3ec42de0ff16 100644 --- a/include/hw/ppc/pnv_occ.h +++ b/include/hw/ppc/pnv_occ.h @@ -41,6 +41,9 @@ DECLARE_INSTANCE_CHECKER(PnvOCC, PNV10_OCC, TYPE_PNV10_OCC) struct PnvOCC { DeviceState xd; + /* OCC dynamic model is driven by this timer. */ + QEMUTimer state_machine_timer; + /* OCC Misc interrupt */ uint64_t occmisc; From d3ce7dc9e282e1785c7a6da73b3ebb1da5c34971 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Wed, 18 Dec 2024 23:28:47 +1000 Subject: [PATCH 0895/1179] target/ppc: Add Power9/10 power management SPRs Linux power management code accesses these registers for pstate management. Wire up a very simple implementation. Signed-off-by: Nicholas Piggin --- After OCC fixes in QEMU pnv model and skiboot (since they have suffered some bitrot), Linux will start performing PM SPR accesses. This is a very simple implementation that makes it a bit happier. Thanks, Nick --- target/ppc/cpu.h | 2 ++ target/ppc/cpu_init.c | 11 +++++++++ target/ppc/helper.h | 2 ++ target/ppc/misc_helper.c | 53 ++++++++++++++++++++++++++++++++++++++++ target/ppc/spr_common.h | 2 ++ target/ppc/translate.c | 16 ++++++++++++ 6 files changed, 86 insertions(+) diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 0b8b4c051724..682583d1d10f 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -2091,6 +2091,7 @@ void ppc_compat_add_property(Object *obj, const char *name, #define SPR_VTB (0x351) #define SPR_LDBAR (0x352) #define SPR_MMCRC (0x353) +#define SPR_PMSR (0x355) #define SPR_PSSCR (0x357) #define SPR_440_INV0 (0x370) #define SPR_440_INV1 (0x371) @@ -2098,6 +2099,7 @@ void ppc_compat_add_property(Object *obj, const char *name, #define SPR_440_INV2 (0x372) #define SPR_TRIG2 (0x372) #define SPR_440_INV3 (0x373) +#define SPR_PMCR (0x374) #define SPR_440_ITV0 (0x374) #define SPR_440_ITV1 (0x375) #define SPR_440_ITV2 (0x376) diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index 1780cabfc6a5..54035c7bbbd2 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -6451,6 +6451,17 @@ static void register_power9_common_sprs(CPUPPCState *env) spr_read_generic, spr_write_generic, KVM_REG_PPC_PSSCR, 0); + spr_register_hv(env, SPR_PMSR, "PMSR", + SPR_NOACCESS, SPR_NOACCESS, + SPR_NOACCESS, SPR_NOACCESS, + &spr_read_pmsr, SPR_NOACCESS, + 0); + spr_register_hv(env, SPR_PMCR, "PMCR", + SPR_NOACCESS, SPR_NOACCESS, + SPR_NOACCESS, SPR_NOACCESS, + &spr_read_generic, &spr_write_pmcr, + PPC_BIT(63)); /* Version 1 (POWER9/10) */ + } static void init_proc_POWER9(CPUPPCState *env) diff --git a/target/ppc/helper.h b/target/ppc/helper.h index 5a77e761bd3f..11b914e640b9 100644 --- a/target/ppc/helper.h +++ b/target/ppc/helper.h @@ -733,6 +733,8 @@ DEF_HELPER_2(store_tfmr, void, env, tl) DEF_HELPER_FLAGS_2(store_sprc, TCG_CALL_NO_RWG, void, env, tl) DEF_HELPER_FLAGS_1(load_sprd, TCG_CALL_NO_RWG_SE, tl, env) DEF_HELPER_FLAGS_2(store_sprd, TCG_CALL_NO_RWG, void, env, tl) +DEF_HELPER_FLAGS_1(load_pmsr, TCG_CALL_NO_RWG_SE, tl, env) +DEF_HELPER_FLAGS_2(store_pmcr, TCG_CALL_NO_RWG, void, env, tl) #endif DEF_HELPER_2(store_sdr1, void, env, tl) DEF_HELPER_2(store_pidr, void, env, tl) diff --git a/target/ppc/misc_helper.c b/target/ppc/misc_helper.c index e379da6010bb..190e9091fc23 100644 --- a/target/ppc/misc_helper.c +++ b/target/ppc/misc_helper.c @@ -377,6 +377,59 @@ void helper_store_sprd(CPUPPCState *env, target_ulong val) break; } } + +target_ulong helper_load_pmsr(CPUPPCState *env) +{ + target_ulong lowerps = extract64(env->spr[SPR_PMCR], PPC_BIT_NR(15), 8); + target_ulong val = 0; + + val |= PPC_BIT(63); /* verion 0x1 (POWER9/10) */ + /* Pmin = 0 */ + /* XXX: POWER9 should be 3 */ + val |= 4ULL << PPC_BIT_NR(31); /* Pmax */ + val |= lowerps << PPC_BIT_NR(15); /* Local actual Pstate */ + val |= lowerps << PPC_BIT_NR(7); /* Global actual Pstate */ + + return val; +} + +static void ppc_set_pmcr(PowerPCCPU *cpu, target_ulong val) +{ + cpu->env.spr[SPR_PMCR] = val; +} + +void helper_store_pmcr(CPUPPCState *env, target_ulong val) +{ + PowerPCCPU *cpu = env_archcpu(env); + CPUState *cs = env_cpu(env); + CPUState *ccs; + + /* Leave version field unchanged (0x1) */ + val &= ~PPC_BITMASK(60, 63); + val |= PPC_BIT(63); + + val &= ~PPC_BITMASK(0, 7); /* UpperPS ignored */ + if (val & PPC_BITMASK(16, 59)) { + qemu_log_mask(LOG_GUEST_ERROR, "Non-zero PMCR reserved bits " + TARGET_FMT_lx"\n", val); + val &= ~PPC_BITMASK(16, 59); + } + + /* DPDES behaves as 1-thread in LPAR-per-thread mode */ + if (ppc_cpu_lpar_single_threaded(cs)) { + ppc_set_pmcr(cpu, val); + return; + } + + /* Does iothread need to be locked for walking CPU list? */ + bql_lock(); + THREAD_SIBLING_FOREACH(cs, ccs) { + PowerPCCPU *ccpu = POWERPC_CPU(ccs); + ppc_set_pmcr(ccpu, val); + } + bql_unlock(); +} + #endif /* defined(TARGET_PPC64) */ void helper_store_pidr(CPUPPCState *env, target_ulong val) diff --git a/target/ppc/spr_common.h b/target/ppc/spr_common.h index 01aff449bccc..8e3117b463bc 100644 --- a/target/ppc/spr_common.h +++ b/target/ppc/spr_common.h @@ -204,6 +204,8 @@ void spr_write_hmer(DisasContext *ctx, int sprn, int gprn); void spr_read_tfmr(DisasContext *ctx, int gprn, int sprn); void spr_write_tfmr(DisasContext *ctx, int sprn, int gprn); void spr_write_lpcr(DisasContext *ctx, int sprn, int gprn); +void spr_read_pmsr(DisasContext *ctx, int gprn, int sprn); +void spr_write_pmcr(DisasContext *ctx, int sprn, int gprn); void spr_read_dexcr_ureg(DisasContext *ctx, int gprn, int sprn); void spr_read_ppr32(DisasContext *ctx, int sprn, int gprn); void spr_write_ppr32(DisasContext *ctx, int sprn, int gprn); diff --git a/target/ppc/translate.c b/target/ppc/translate.c index 80638ab53597..b0cc8bf2839c 100644 --- a/target/ppc/translate.c +++ b/target/ppc/translate.c @@ -1326,6 +1326,22 @@ void spr_write_lpcr(DisasContext *ctx, int sprn, int gprn) translator_io_start(&ctx->base); gen_helper_store_lpcr(tcg_env, cpu_gpr[gprn]); } + +void spr_read_pmsr(DisasContext *ctx, int gprn, int sprn) +{ + translator_io_start(&ctx->base); + gen_helper_load_pmsr(cpu_gpr[gprn], tcg_env); +} + +void spr_write_pmcr(DisasContext *ctx, int sprn, int gprn) +{ + if (!gen_serialize_core_lpar(ctx)) { + return; + } + translator_io_start(&ctx->base); + gen_helper_store_pmcr(tcg_env, cpu_gpr[gprn]); +} + #endif /* !defined(CONFIG_USER_ONLY) */ void spr_read_tar(DisasContext *ctx, int gprn, int sprn) From a2dea722cd0df663696fc8bb136b1fd29da18742 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 1 Mar 2025 03:16:29 +1000 Subject: [PATCH 0896/1179] ppc/pnv: Support LPC host controller irqs other than serirqs The LPC model has only supported serirqs (ISA device IRQs), however there are internal sources that can raise other interrupts. Update the device to handle these interrupt sources. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_lpc.c | 64 +++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/hw/ppc/pnv_lpc.c b/hw/ppc/pnv_lpc.c index 0480a60f3f79..d0fccc165d9f 100644 --- a/hw/ppc/pnv_lpc.c +++ b/hw/ppc/pnv_lpc.c @@ -456,46 +456,18 @@ static void pnv_lpc_eval_irqs(PnvLpcController *lpc) { uint32_t active_irqs = 0; - if (lpc->lpc_hc_irqstat & PPC_BITMASK32(16, 31)) { - qemu_log_mask(LOG_UNIMP, "LPC HC Unimplemented irqs in IRQSTAT: " - "0x%08"PRIx32"\n", lpc->lpc_hc_irqstat); - } - - if (lpc->lpc_hc_irqser_ctrl & LPC_HC_IRQSER_EN) { - active_irqs = lpc->lpc_hc_irqstat & lpc->lpc_hc_irqmask; + active_irqs = lpc->lpc_hc_irqstat & lpc->lpc_hc_irqmask; + if (!(lpc->lpc_hc_irqser_ctrl & LPC_HC_IRQSER_EN)) { + active_irqs &= ~LPC_HC_IRQ_SERIRQ_ALL; } /* Reflect the interrupt */ - if (!lpc->psi_has_serirq) { - /* - * POWER8 ORs all irqs together (also with LPCHC internal interrupt - * sources) and outputs a single line that raises the PSI LPCHC irq - * which then latches an OPB IRQ status register that sends the irq - * to PSI. - * - * We don't honor the polarity register, it's pointless and unused - * anyway - */ - if (active_irqs) { - lpc->opb_irq_input |= OPB_MASTER_IRQ_LPC; - } else { - lpc->opb_irq_input &= ~OPB_MASTER_IRQ_LPC; - } - - /* Update OPB internal latch */ - lpc->opb_irq_stat |= lpc->opb_irq_input & lpc->opb_irq_mask; - - qemu_set_irq(lpc->psi_irq_lpchc, lpc->opb_irq_stat != 0); - } else { + if (lpc->psi_has_serirq) { /* - * POWER9 and POWER10 have routing fields in OPB master registers that + * POWER9 and later have routing fields in OPB master registers that * send LPC irqs to 4 output lines that raise the PSI SERIRQ irqs. * These don't appear to get latched into an OPB register like the * LPCHC irqs. - * - * POWER9 LPC controller internal irqs still go via the OPB - * and LPCHC PSI irqs like P8, but we have no such internal sources - * modelled yet. */ bool serirq_out[4] = { false, false, false, false }; int irq; @@ -510,7 +482,33 @@ static void pnv_lpc_eval_irqs(PnvLpcController *lpc) qemu_set_irq(lpc->psi_irq_serirq[1], serirq_out[1]); qemu_set_irq(lpc->psi_irq_serirq[2], serirq_out[2]); qemu_set_irq(lpc->psi_irq_serirq[3], serirq_out[3]); + + /* + * POWER9 and later LPC controller internal irqs still go via the OPB + * and LPCHC PSI irqs like P8, so take the SERIRQs out and continue. + */ + active_irqs &= ~LPC_HC_IRQ_SERIRQ_ALL; + } + + /* + * POWER8 ORs all irqs together (also with LPCHC internal interrupt + * sources) and outputs a single line that raises the PSI LPCHC irq + * which then latches an OPB IRQ status register that sends the irq + * to PSI. + * + * We don't honor the polarity register, it's pointless and unused + * anyway + */ + if (active_irqs) { + lpc->opb_irq_input |= OPB_MASTER_IRQ_LPC; + } else { + lpc->opb_irq_input &= ~OPB_MASTER_IRQ_LPC; } + + /* Update OPB internal latch */ + lpc->opb_irq_stat |= lpc->opb_irq_input & lpc->opb_irq_mask; + + qemu_set_irq(lpc->psi_irq_lpchc, lpc->opb_irq_stat != 0); } static uint64_t lpc_hc_read(void *opaque, hwaddr addr, unsigned size) From f27f31b552df88641fe711846c94f0c3b86d2907 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 1 Mar 2025 03:06:43 +1000 Subject: [PATCH 0897/1179] ppc/pnv: raise no-response errors if an LPC transaction fails If nothing responds to an LPC access, the LPC host controller should set an IRQSTAT error. Model this behaviour. skiboot uses this error to "probe" LPC accesses, among other things to determine if a SuperIO chip is present. After this change it recognizes there is no SuperIO present and does not keep trying to access it. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_lpc.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hw/ppc/pnv_lpc.c b/hw/ppc/pnv_lpc.c index d0fccc165d9f..0e02ce6e9407 100644 --- a/hw/ppc/pnv_lpc.c +++ b/hw/ppc/pnv_lpc.c @@ -353,6 +353,8 @@ static const MemoryRegionOps pnv_lpc_xscom_ops = { .endianness = DEVICE_BIG_ENDIAN, }; +static void pnv_lpc_opb_noresponse(PnvLpcController *lpc); + static uint64_t pnv_lpc_mmio_read(void *opaque, hwaddr addr, unsigned size) { PnvLpcController *lpc = PNV_LPC(opaque); @@ -376,6 +378,7 @@ static uint64_t pnv_lpc_mmio_read(void *opaque, hwaddr addr, unsigned size) } if (result != MEMTX_OK) { + pnv_lpc_opb_noresponse(lpc); qemu_log_mask(LOG_GUEST_ERROR, "OPB read failed at @0x%" HWADDR_PRIx "\n", addr); } @@ -406,6 +409,7 @@ static void pnv_lpc_mmio_write(void *opaque, hwaddr addr, } if (result != MEMTX_OK) { + pnv_lpc_opb_noresponse(lpc); qemu_log_mask(LOG_GUEST_ERROR, "OPB write failed at @0x%" HWADDR_PRIx "\n", addr); } @@ -511,6 +515,12 @@ static void pnv_lpc_eval_irqs(PnvLpcController *lpc) qemu_set_irq(lpc->psi_irq_lpchc, lpc->opb_irq_stat != 0); } +static void pnv_lpc_opb_noresponse(PnvLpcController *lpc) +{ + lpc->lpc_hc_irqstat |= LPC_HC_IRQ_SYNC_NORESP_ERR; + pnv_lpc_eval_irqs(lpc); +} + static uint64_t lpc_hc_read(void *opaque, hwaddr addr, unsigned size) { PnvLpcController *lpc = opaque; From b9ece4a70c12d749af4b5213f11961aac47ea890 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 1 Mar 2025 00:36:37 +1000 Subject: [PATCH 0898/1179] ppc/pnv: Implement LPC FW address space IDSEL LPC FW address space is a 256MB (28-bit) region to one of 16-devices that are selected with the IDSEL register. Implement this by making the ISA FW address space 4GB, and move the 256MB OPB alias within that space according to IDSEL. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_lpc.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/hw/ppc/pnv_lpc.c b/hw/ppc/pnv_lpc.c index 0e02ce6e9407..d812dc826810 100644 --- a/hw/ppc/pnv_lpc.c +++ b/hw/ppc/pnv_lpc.c @@ -85,7 +85,7 @@ enum { #define ISA_IO_SIZE 0x00010000 #define ISA_MEM_SIZE 0x10000000 -#define ISA_FW_SIZE 0x10000000 +#define ISA_FW_SIZE 0x100000000 #define LPC_IO_OPB_ADDR 0xd0010000 #define LPC_IO_OPB_SIZE 0x00010000 #define LPC_MEM_OPB_ADDR 0xe0000000 @@ -561,10 +561,13 @@ static void lpc_hc_write(void *opaque, hwaddr addr, uint64_t val, switch (addr) { case LPC_HC_FW_SEG_IDSEL: - /* XXX Actually figure out how that works as this impact - * memory regions/aliases + /* + * ISA FW "devices" are modeled as 16x256MB windows into a + * 4GB LPC FW address space. */ + val &= 0xf; /* Selects device 0-15 */ lpc->lpc_hc_fw_seg_idsel = val; + memory_region_set_alias_offset(&lpc->opb_isa_fw, val * LPC_FW_OPB_SIZE); break; case LPC_HC_FW_RD_ACC_SIZE: lpc->lpc_hc_fw_rd_acc_size = val; @@ -798,9 +801,9 @@ static void pnv_lpc_realize(DeviceState *dev, Error **errp) memory_region_init(&lpc->opb_mr, OBJECT(dev), "lpc-opb", 0x100000000ull); address_space_init(&lpc->opb_as, &lpc->opb_mr, "lpc-opb"); - /* Create ISA IO and Mem space regions which are the root of - * the ISA bus (ie, ISA address spaces). We don't create a - * separate one for FW which we alias to memory. + /* + * Create ISA IO, Mem, and FW space regions which are the root of + * the ISA bus (ie, ISA address spaces). */ memory_region_init(&lpc->isa_io, OBJECT(dev), "isa-io", ISA_IO_SIZE); memory_region_init(&lpc->isa_mem, OBJECT(dev), "isa-mem", ISA_MEM_SIZE); From b899de9a3d959652bf61d671ffa2c2ba23259b15 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Mon, 3 Mar 2025 10:27:01 +1000 Subject: [PATCH 0899/1179] ppc/pnv: Move PNOR to offset 0 in the ISA FW space skiboot has a bug that does not handle ISA FW access correctly for IDSEL devices > 0, and the current PNOR default address and size puts 64MB in device 0 and 64MB in device 1, which causes skiboot to hit this bug and breaks PNOR accesses. Move the PNOR address down to 0 for now, so a 256MB PNOR can be accessed via device 0. Signed-off-by: Nicholas Piggin --- include/hw/ppc/pnv_pnor.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/hw/ppc/pnv_pnor.h b/include/hw/ppc/pnv_pnor.h index 2e37ac88bf15..19c2d642e828 100644 --- a/include/hw/ppc/pnv_pnor.h +++ b/include/hw/ppc/pnv_pnor.h @@ -13,9 +13,11 @@ #include "hw/sysbus.h" /* - * PNOR offset on the LPC FW address space + * PNOR offset on the LPC FW address space. For now this should be 0 because + * skiboot 7.1 has a bug where IDSEL > 0 (LPC FW address > 256MB) access is + * not performed correctly. */ -#define PNOR_SPI_OFFSET 0x0c000000UL +#define PNOR_SPI_OFFSET 0x00000000UL #define TYPE_PNV_PNOR "pnv-pnor" OBJECT_DECLARE_SIMPLE_TYPE(PnvPnor, PNV_PNOR) From 4c84a0a4a6e5c4a374493d99e7eb702def7e7eae Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 1 Mar 2025 01:07:57 +1000 Subject: [PATCH 0900/1179] ppc/pnv: Add a PNOR address and size sanity checks The BMC HIOMAP PNOR access protocol has certain limits on PNOR addresses and sizes. Add some sanity checks for these so we don't get strange behaviour. Signed-off-by: Nicholas Piggin --- hw/ppc/pnv_bmc.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/hw/ppc/pnv_bmc.c b/hw/ppc/pnv_bmc.c index 0c1274df21a9..811ba3d7a499 100644 --- a/hw/ppc/pnv_bmc.c +++ b/hw/ppc/pnv_bmc.c @@ -251,10 +251,38 @@ static const IPMINetfn hiomap_netfn = { void pnv_bmc_set_pnor(IPMIBmc *bmc, PnvPnor *pnor) { + uint32_t pnor_size = pnor->size; + uint32_t pnor_addr = PNOR_SPI_OFFSET; + if (!pnv_bmc_is_simulator(bmc)) { return; } + /* + * The HIOMAP protocol uses block units and 16-bit addressing. + * Prevent overflow or misalign. + */ + if (pnor_addr >= 1U << (BLOCK_SHIFT + 16)) { + warn_report("PNOR address is larger than 2^%d, disabling PNOR", + BLOCK_SHIFT + 16); + return; + } + if (pnor_addr & ((1U << BLOCK_SHIFT) - 1)) { + warn_report("PNOR address is not aligned to 2^%d, disabling PNOR", + BLOCK_SHIFT); + return; + } + if (pnor_size > 1U << (BLOCK_SHIFT + 16)) { + warn_report("PNOR size is larger than 2^%d, disabling PNOR", + BLOCK_SHIFT + 16); + return; + } + if (pnor_size & ((1U << BLOCK_SHIFT) - 1)) { + warn_report("PNOR size is not aligned to 2^%d, disabling PNOR", + BLOCK_SHIFT); + return; + } + object_ref(OBJECT(pnor)); object_property_add_const_link(OBJECT(bmc), "pnor", OBJECT(pnor)); From 80f9321308720fa30651e7803347268a7c12c63a Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Sat, 1 Mar 2025 01:07:57 +1000 Subject: [PATCH 0901/1179] ppc/pnv: Add a default formatted PNOR image The default PNOR image is erased and not recognised by skiboot, so NVRAM gets disabled. This change adds a tiny pnor file that is a proper FFS image with a formatted NVRAM partition. This is recognised by skiboot and will persist across machine reboots. Signed-off-by: Nicholas Piggin --- MAINTAINERS | 1 + docs/system/ppc/powernv.rst | 7 +++++++ hw/ppc/pnv.c | 16 +++++++++++++++- pc-bios/README | 13 +++++++++++++ pc-bios/meson.build | 1 + pc-bios/pnv-pnor.bin | Bin 0 -> 139264 bytes 6 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 pc-bios/pnv-pnor.bin diff --git a/MAINTAINERS b/MAINTAINERS index e2f538fc1625..cdb3041bb23f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1537,6 +1537,7 @@ F: include/hw/ppc/pnv* F: include/hw/pci-host/pnv* F: include/hw/ssi/pnv_spi* F: pc-bios/skiboot.lid +F: pc-bios/pnv-pnor.bin F: tests/qtest/pnv* F: tests/functional/test_ppc64_powernv.py diff --git a/docs/system/ppc/powernv.rst b/docs/system/ppc/powernv.rst index de7a807ac762..f3ec2cc69c0d 100644 --- a/docs/system/ppc/powernv.rst +++ b/docs/system/ppc/powernv.rst @@ -195,6 +195,13 @@ Use a MTD drive to add a PNOR to the machine, and get a NVRAM : -drive file=./witherspoon.pnor,format=raw,if=mtd +If no mtd drive is provided, the powernv platform will create a default +PNOR device using a tiny formatted PNOR in pc-bios/pnv-pnor.bin opened +read-only (PNOR changes will be persistent across reboots but not across +invocations of QEMU). If no defaults are used, an erased 128MB PNOR is +provided (which skiboot will probably not recognize since it is not +formatted). + Maintainer contact information ------------------------------ diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 8c0a2d057311..6fec455ff95c 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -64,6 +64,8 @@ #define FW_LOAD_ADDR 0x0 #define FW_MAX_SIZE (16 * MiB) +#define PNOR_FILE_NAME "pnv-pnor.bin" + #define KERNEL_LOAD_ADDR 0x20000000 #define KERNEL_MAX_SIZE (128 * MiB) #define INITRD_LOAD_ADDR 0x28000000 @@ -941,7 +943,7 @@ static void pnv_init(MachineState *machine) uint64_t chip_ram_start = 0; int i; char *chip_typename; - DriveInfo *pnor = drive_get(IF_MTD, 0, 0); + DriveInfo *pnor; DeviceState *dev; if (kvm_enabled()) { @@ -971,6 +973,18 @@ static void pnv_init(MachineState *machine) * Create our simple PNOR device */ dev = qdev_new(TYPE_PNV_PNOR); + pnor = drive_get(IF_MTD, 0, 0); + if (!pnor && defaults_enabled()) { + fw_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, PNOR_FILE_NAME); + if (!fw_filename) { + warn_report("Could not find PNOR '%s'", PNOR_FILE_NAME); + } else { + QemuOpts *opts; + opts = drive_add(IF_MTD, -1, fw_filename, "format=raw,readonly=on"); + pnor = drive_new(opts, IF_MTD, &error_fatal); + g_free(fw_filename); + } + } if (pnor) { qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(pnor)); } diff --git a/pc-bios/README b/pc-bios/README index a08e034fc32b..f0f13e15f2c3 100644 --- a/pc-bios/README +++ b/pc-bios/README @@ -43,6 +43,19 @@ run an hypervisor OS or simply a host OS on the "baremetal" platform, also known as the PowerNV (Non-Virtualized) platform. +- pnv-pnor.bin is a non-volatile RAM image used by PowerNV, which stores + NVRAM BIOS settings among other things. This image was created with the + following command (the ffspart tool can be found in the skiboot source tree): + + ffspart -s 0x1000 -c 34 -i pnv-pnor.in -p pnv-pnor.bin + + Where pnv-pnor.in contains the two lines (no leading whitespace): + + NVRAM,0x01000,0x00020000,,,/dev/zero + VERSION,0x21000,0x00001000,,,/dev/zero + + skiboot is then booted once to format the NVRAM partition. + - QemuMacDrivers (https://github.com/ozbenh/QemuMacDrivers) is a project to provide virtualised drivers for PPC MacOS guests. diff --git a/pc-bios/meson.build b/pc-bios/meson.build index 51e95cc90316..34d6616c32b4 100644 --- a/pc-bios/meson.build +++ b/pc-bios/meson.build @@ -70,6 +70,7 @@ blobs = [ 's390-ccw.img', 'slof.bin', 'skiboot.lid', + 'pnv-pnor.bin', 'palcode-clipper', 'u-boot.e500', 'u-boot-sam460-20100605.bin', diff --git a/pc-bios/pnv-pnor.bin b/pc-bios/pnv-pnor.bin new file mode 100644 index 0000000000000000000000000000000000000000..3e6f70014408e76d5aeca758c31113f3eee2da84 GIT binary patch literal 139264 zcmeI#F-pWh6adg4Z3XuTYGvUiB3M|su2Hsktrl6cs9>qBjb1>#fLMAT?;zU=IwKJx z*a&F???dwcWXPYH*UhM`jv}IHo|}}HBL*qOMt-$pRBkWk$LE*rZ%ti%rbu<}lm5^7 zyGJwKO}c-2yd93Ka_@J$yyjZ7{!*&*I3iaa$H()_!+57U+}$6xJFlm~&-t6P=jrax z|F(F)%jXmX2oNAZfB*pk1PBlyK;XXu2d_m;C$p`K)9IwH|GL_@uexdi1PBlyK!5-N z0t5&UAV8px0`rwYoYb>feb&d_+cN~(+J_HC5AV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U jAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly&@X{6e@Sm% literal 0 HcmV?d00001 From 19db3b5a247c57a40d7e8a545a8dee9faf4db150 Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:51:21 +1000 Subject: [PATCH 0902/1179] ppc/xive2: Update NVP save/restore for group attributes If the 'H' attribute is set on the NVP structure, the hardware automatically saves and restores some attributes from the TIMA in the NVP structure. The group-specific attributes LSMFB, LGS and T have an extra flag to individually control what is saved/restored. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/xive2.c | 23 ++++++++++++++++++----- include/hw/ppc/xive2_regs.h | 10 +++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index fc5aed331580..cd075e4db955 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -1,10 +1,9 @@ /* * QEMU PowerPC XIVE2 interrupt controller model (POWER10) * - * Copyright (c) 2019-2022, IBM Corporation.. + * Copyright (c) 2019-2024, IBM Corporation.. * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" @@ -313,7 +312,19 @@ static void xive2_tctx_save_ctx(Xive2Router *xrtr, XiveTCTX *tctx, nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, regs[TM_IPB]); nvp.w2 = xive_set_field32(NVP2_W2_CPPR, nvp.w2, regs[TM_CPPR]); - nvp.w2 = xive_set_field32(NVP2_W2_LSMFB, nvp.w2, regs[TM_LSMFB]); + if (nvp.w0 & NVP2_W0_L) { + /* + * Typically not used. If LSMFB is restored with 0, it will + * force a backlog rescan + */ + nvp.w2 = xive_set_field32(NVP2_W2_LSMFB, nvp.w2, regs[TM_LSMFB]); + } + if (nvp.w0 & NVP2_W0_G) { + nvp.w2 = xive_set_field32(NVP2_W2_LGS, nvp.w2, regs[TM_LGS]); + } + if (nvp.w0 & NVP2_W0_T) { + nvp.w2 = xive_set_field32(NVP2_W2_T, nvp.w2, regs[TM_T]); + } xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2); nvp.w1 = xive_set_field32(NVP2_W1_CO, nvp.w1, 0); @@ -527,7 +538,9 @@ static uint8_t xive2_tctx_restore_os_ctx(Xive2Router *xrtr, XiveTCTX *tctx, xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, nvp, 2); tctx->regs[TM_QW1_OS + TM_CPPR] = cppr; - /* we don't model LSMFB */ + tctx->regs[TM_QW1_OS + TM_LSMFB] = xive_get_field32(NVP2_W2_LSMFB, nvp->w2); + tctx->regs[TM_QW1_OS + TM_LGS] = xive_get_field32(NVP2_W2_LGS, nvp->w2); + tctx->regs[TM_QW1_OS + TM_T] = xive_get_field32(NVP2_W2_T, nvp->w2); nvp->w1 = xive_set_field32(NVP2_W1_CO, nvp->w1, 1); nvp->w1 = xive_set_field32(NVP2_W1_CO_THRID_VALID, nvp->w1, 1); diff --git a/include/hw/ppc/xive2_regs.h b/include/hw/ppc/xive2_regs.h index 1d00c8df6429..e88d6eab1e56 100644 --- a/include/hw/ppc/xive2_regs.h +++ b/include/hw/ppc/xive2_regs.h @@ -1,10 +1,9 @@ /* * QEMU PowerPC XIVE2 internal structure definitions (POWER10) * - * Copyright (c) 2019-2022, IBM Corporation. + * Copyright (c) 2019-2024, IBM Corporation. * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef PPC_XIVE2_REGS_H @@ -152,6 +151,9 @@ typedef struct Xive2Nvp { uint32_t w0; #define NVP2_W0_VALID PPC_BIT32(0) #define NVP2_W0_HW PPC_BIT32(7) +#define NVP2_W0_L PPC_BIT32(8) +#define NVP2_W0_G PPC_BIT32(9) +#define NVP2_W0_T PPC_BIT32(10) #define NVP2_W0_ESC_END PPC_BIT32(25) /* 'N' bit 0:ESB 1:END */ #define NVP2_W0_PGOFIRST PPC_BITMASK32(26, 31) uint32_t w1; @@ -163,6 +165,8 @@ typedef struct Xive2Nvp { #define NVP2_W2_CPPR PPC_BITMASK32(0, 7) #define NVP2_W2_IPB PPC_BITMASK32(8, 15) #define NVP2_W2_LSMFB PPC_BITMASK32(16, 23) +#define NVP2_W2_T PPC_BIT32(27) +#define NVP2_W2_LGS PPC_BITMASK32(28, 31) uint32_t w3; uint32_t w4; #define NVP2_W4_ESC_ESB_BLOCK PPC_BITMASK32(0, 3) /* N:0 */ From a45580ad03f034c84689c6bee5f875432dbd73ba Mon Sep 17 00:00:00 2001 From: Michael Kowal Date: Tue, 11 Mar 2025 11:51:21 +1000 Subject: [PATCH 0903/1179] ppc/xive: Rename ipb_to_pipr() to xive_ipb_to_pipr() Rename to follow the convention of the other function names. Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/xive.c | 22 ++++++---------------- include/hw/ppc/xive.h | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/hw/intc/xive.c b/hw/intc/xive.c index 139cfdf9bfaf..f603d9f1a546 100644 --- a/hw/intc/xive.c +++ b/hw/intc/xive.c @@ -3,8 +3,7 @@ * * Copyright (c) 2017-2018, IBM Corporation. * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" @@ -27,15 +26,6 @@ * XIVE Thread Interrupt Management context */ -/* - * Convert an Interrupt Pending Buffer (IPB) register to a Pending - * Interrupt Priority Register (PIPR), which contains the priority of - * the most favored pending notification. - */ -static uint8_t ipb_to_pipr(uint8_t ibp) -{ - return ibp ? clz32((uint32_t)ibp << 24) : 0xff; -} static uint8_t exception_mask(uint8_t ring) { @@ -159,7 +149,7 @@ static void xive_tctx_set_cppr(XiveTCTX *tctx, uint8_t ring, uint8_t cppr) * Recompute the PIPR based on local pending interrupts. The PHYS * ring must take the minimum of both the PHYS and POOL PIPR values. */ - pipr_min = ipb_to_pipr(regs[TM_IPB]); + pipr_min = xive_ipb_to_pipr(regs[TM_IPB]); ring_min = ring; /* PHYS updates also depend on POOL values */ @@ -169,7 +159,7 @@ static void xive_tctx_set_cppr(XiveTCTX *tctx, uint8_t ring, uint8_t cppr) /* POOL values only matter if POOL ctx is valid */ if (pool_regs[TM_WORD2] & 0x80) { - uint8_t pool_pipr = ipb_to_pipr(pool_regs[TM_IPB]); + uint8_t pool_pipr = xive_ipb_to_pipr(pool_regs[TM_IPB]); /* * Determine highest priority interrupt and @@ -193,7 +183,7 @@ void xive_tctx_ipb_update(XiveTCTX *tctx, uint8_t ring, uint8_t ipb) uint8_t *regs = &tctx->regs[ring]; regs[TM_IPB] |= ipb; - regs[TM_PIPR] = ipb_to_pipr(regs[TM_IPB]); + regs[TM_PIPR] = xive_ipb_to_pipr(regs[TM_IPB]); xive_tctx_notify(tctx, ring); } @@ -841,9 +831,9 @@ void xive_tctx_reset(XiveTCTX *tctx) * CPPR is first set. */ tctx->regs[TM_QW1_OS + TM_PIPR] = - ipb_to_pipr(tctx->regs[TM_QW1_OS + TM_IPB]); + xive_ipb_to_pipr(tctx->regs[TM_QW1_OS + TM_IPB]); tctx->regs[TM_QW3_HV_PHYS + TM_PIPR] = - ipb_to_pipr(tctx->regs[TM_QW3_HV_PHYS + TM_IPB]); + xive_ipb_to_pipr(tctx->regs[TM_QW3_HV_PHYS + TM_IPB]); } static void xive_tctx_realize(DeviceState *dev, Error **errp) diff --git a/include/hw/ppc/xive.h b/include/hw/ppc/xive.h index ea5d03a34635..75276b9ba637 100644 --- a/include/hw/ppc/xive.h +++ b/include/hw/ppc/xive.h @@ -130,11 +130,9 @@ * TCTX Thread interrupt Context * * - * Copyright (c) 2017-2018, IBM Corporation. - * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * Copyright (c) 2017-2024, IBM Corporation. * + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef PPC_XIVE_H @@ -510,6 +508,16 @@ static inline uint8_t xive_priority_to_ipb(uint8_t priority) 0 : 1 << (XIVE_PRIORITY_MAX - priority); } +/* + * Convert an Interrupt Pending Buffer (IPB) register to a Pending + * Interrupt Priority Register (PIPR), which contains the priority of + * the most favored pending notification. + */ +static inline uint8_t xive_ipb_to_pipr(uint8_t ibp) +{ + return ibp ? clz32((uint32_t)ibp << 24) : 0xff; +} + /* * XIVE Thread Interrupt Management Aera (TIMA) * From 9d2b6058c5b0601779a65e7db4176073940a713d Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:51:21 +1000 Subject: [PATCH 0904/1179] ppc/xive2: Add grouping level to notification The NSR has a (so far unused) grouping level field. When a interrupt is presented, that field tells the hypervisor or OS if the interrupt is for an individual VP or for a VP-group/crowd. This patch reworks the presentation API to allow to set/unset the level when raising/accepting an interrupt. It also renames xive_tctx_ipb_update() to xive_tctx_pipr_update() as the IPB is only used for VP-specific target, whereas the PIPR always needs to be updated. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/trace-events | 2 +- hw/intc/xive.c | 88 ++++++++++++++++++++++---------------- hw/intc/xive2.c | 19 ++++---- include/hw/ppc/xive.h | 9 +++- include/hw/ppc/xive_regs.h | 25 ++++++++--- 5 files changed, 90 insertions(+), 53 deletions(-) diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 913197a181f7..e9fe1978f9a2 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -283,7 +283,7 @@ xive_router_end_notify(uint8_t end_blk, uint32_t end_idx, uint32_t end_data) "EN xive_router_end_escalate(uint8_t end_blk, uint32_t end_idx, uint8_t esc_blk, uint32_t esc_idx, uint32_t end_data) "END 0x%02x/0x%04x -> escalate END 0x%02x/0x%04x data 0x%08x" xive_tctx_tm_write(uint32_t index, uint64_t offset, unsigned int size, uint64_t value) "target=%d @0x%"PRIx64" sz=%d val=0x%" PRIx64 xive_tctx_tm_read(uint32_t index, uint64_t offset, unsigned int size, uint64_t value) "target=%d @0x%"PRIx64" sz=%d val=0x%" PRIx64 -xive_presenter_notify(uint8_t nvt_blk, uint32_t nvt_idx, uint8_t ring) "found NVT 0x%x/0x%x ring=0x%x" +xive_presenter_notify(uint8_t nvt_blk, uint32_t nvt_idx, uint8_t ring, uint8_t group_level) "found NVT 0x%x/0x%x ring=0x%x group_level=%d" xive_end_source_read(uint8_t end_blk, uint32_t end_idx, uint64_t addr) "END 0x%x/0x%x @0x%"PRIx64 # pnv_xive.c diff --git a/hw/intc/xive.c b/hw/intc/xive.c index f603d9f1a546..1e362f987ac5 100644 --- a/hw/intc/xive.c +++ b/hw/intc/xive.c @@ -26,19 +26,6 @@ * XIVE Thread Interrupt Management context */ - -static uint8_t exception_mask(uint8_t ring) -{ - switch (ring) { - case TM_QW1_OS: - return TM_QW1_NSR_EO; - case TM_QW3_HV_PHYS: - return TM_QW3_NSR_HE; - default: - g_assert_not_reached(); - } -} - static qemu_irq xive_tctx_output(XiveTCTX *tctx, uint8_t ring) { switch (ring) { @@ -58,11 +45,10 @@ static uint64_t xive_tctx_accept(XiveTCTX *tctx, uint8_t ring) { uint8_t *regs = &tctx->regs[ring]; uint8_t nsr = regs[TM_NSR]; - uint8_t mask = exception_mask(ring); qemu_irq_lower(xive_tctx_output(tctx, ring)); - if (regs[TM_NSR] & mask) { + if (regs[TM_NSR] != 0) { uint8_t cppr = regs[TM_PIPR]; uint8_t alt_ring; uint8_t *alt_regs; @@ -77,11 +63,18 @@ static uint64_t xive_tctx_accept(XiveTCTX *tctx, uint8_t ring) regs[TM_CPPR] = cppr; - /* Reset the pending buffer bit */ - alt_regs[TM_IPB] &= ~xive_priority_to_ipb(cppr); + /* + * If the interrupt was for a specific VP, reset the pending + * buffer bit, otherwise clear the logical server indicator + */ + if (regs[TM_NSR] & TM_NSR_GRP_LVL) { + regs[TM_NSR] &= ~TM_NSR_GRP_LVL; + } else { + alt_regs[TM_IPB] &= ~xive_priority_to_ipb(cppr); + } - /* Drop Exception bit */ - regs[TM_NSR] &= ~mask; + /* Drop the exception bit and any group/crowd */ + regs[TM_NSR] = 0; trace_xive_tctx_accept(tctx->cs->cpu_index, alt_ring, alt_regs[TM_IPB], regs[TM_PIPR], @@ -91,7 +84,7 @@ static uint64_t xive_tctx_accept(XiveTCTX *tctx, uint8_t ring) return ((uint64_t)nsr << 8) | regs[TM_CPPR]; } -static void xive_tctx_notify(XiveTCTX *tctx, uint8_t ring) +void xive_tctx_notify(XiveTCTX *tctx, uint8_t ring, uint8_t group_level) { /* HV_POOL ring uses HV_PHYS NSR, CPPR and PIPR registers */ uint8_t alt_ring = (ring == TM_QW2_HV_POOL) ? TM_QW3_HV_PHYS : ring; @@ -101,13 +94,13 @@ static void xive_tctx_notify(XiveTCTX *tctx, uint8_t ring) if (alt_regs[TM_PIPR] < alt_regs[TM_CPPR]) { switch (ring) { case TM_QW1_OS: - regs[TM_NSR] |= TM_QW1_NSR_EO; + regs[TM_NSR] = TM_QW1_NSR_EO | (group_level & 0x3F); break; case TM_QW2_HV_POOL: - alt_regs[TM_NSR] = (TM_QW3_NSR_HE_POOL << 6); + alt_regs[TM_NSR] = (TM_QW3_NSR_HE_POOL << 6) | (group_level & 0x3F); break; case TM_QW3_HV_PHYS: - regs[TM_NSR] |= (TM_QW3_NSR_HE_PHYS << 6); + regs[TM_NSR] = (TM_QW3_NSR_HE_PHYS << 6) | (group_level & 0x3F); break; default: g_assert_not_reached(); @@ -175,17 +168,27 @@ static void xive_tctx_set_cppr(XiveTCTX *tctx, uint8_t ring, uint8_t cppr) regs[TM_PIPR] = pipr_min; /* CPPR has changed, check if we need to raise a pending exception */ - xive_tctx_notify(tctx, ring_min); + xive_tctx_notify(tctx, ring_min, 0); } -void xive_tctx_ipb_update(XiveTCTX *tctx, uint8_t ring, uint8_t ipb) -{ +void xive_tctx_pipr_update(XiveTCTX *tctx, uint8_t ring, uint8_t priority, + uint8_t group_level) + { + /* HV_POOL ring uses HV_PHYS NSR, CPPR and PIPR registers */ + uint8_t alt_ring = (ring == TM_QW2_HV_POOL) ? TM_QW3_HV_PHYS : ring; + uint8_t *alt_regs = &tctx->regs[alt_ring]; uint8_t *regs = &tctx->regs[ring]; - regs[TM_IPB] |= ipb; - regs[TM_PIPR] = xive_ipb_to_pipr(regs[TM_IPB]); - xive_tctx_notify(tctx, ring); -} + if (group_level == 0) { + /* VP-specific */ + regs[TM_IPB] |= xive_priority_to_ipb(priority); + alt_regs[TM_PIPR] = xive_ipb_to_pipr(regs[TM_IPB]); + } else { + /* VP-group */ + alt_regs[TM_PIPR] = xive_priority_to_pipr(priority); + } + xive_tctx_notify(tctx, ring, group_level); + } /* * XIVE Thread Interrupt Management Area (TIMA) @@ -401,13 +404,13 @@ static void xive_tm_set_os_lgs(XivePresenter *xptr, XiveTCTX *tctx, } /* - * Adjust the IPB to allow a CPU to process event queues of other + * Adjust the PIPR to allow a CPU to process event queues of other * priorities during one physical interrupt cycle. */ static void xive_tm_set_os_pending(XivePresenter *xptr, XiveTCTX *tctx, hwaddr offset, uint64_t value, unsigned size) { - xive_tctx_ipb_update(tctx, TM_QW1_OS, xive_priority_to_ipb(value & 0xff)); + xive_tctx_pipr_update(tctx, TM_QW1_OS, value & 0xff, 0); } static void xive_os_cam_decode(uint32_t cam, uint8_t *nvt_blk, @@ -485,16 +488,20 @@ static void xive_tctx_need_resend(XiveRouter *xrtr, XiveTCTX *tctx, /* Reset the NVT value */ nvt.w4 = xive_set_field32(NVT_W4_IPB, nvt.w4, 0); xive_router_write_nvt(xrtr, nvt_blk, nvt_idx, &nvt, 4); + + uint8_t *regs = &tctx->regs[TM_QW1_OS]; + regs[TM_IPB] |= ipb; } + /* - * Always call xive_tctx_ipb_update(). Even if there were no + * Always call xive_tctx_pipr_update(). Even if there were no * escalation triggered, there could be a pending interrupt which * was saved when the context was pulled and that we need to take * into account by recalculating the PIPR (which is not * saved/restored). * It will also raise the External interrupt signal if needed. */ - xive_tctx_ipb_update(tctx, TM_QW1_OS, ipb); + xive_tctx_pipr_update(tctx, TM_QW1_OS, 0xFF, 0); /* fxb */ } /* @@ -1648,6 +1655,12 @@ static uint32_t xive_tctx_hw_cam_line(XivePresenter *xptr, XiveTCTX *tctx) return xive_nvt_cam_line(blk, 1 << 7 | (pir & 0x7f)); } +static uint8_t xive_get_group_level(uint32_t nvp_index) +{ + /* FIXME add crowd encoding */ + return ctz32(~nvp_index) + 1; +} + /* * The thread context register words are in big-endian format. */ @@ -1733,6 +1746,7 @@ bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, { XiveFabricClass *xfc = XIVE_FABRIC_GET_CLASS(xfb); XiveTCTXMatch match = { .tctx = NULL, .ring = 0 }; + uint8_t group_level; int count; /* @@ -1746,9 +1760,9 @@ bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, /* handle CPU exception delivery */ if (count) { - trace_xive_presenter_notify(nvt_blk, nvt_idx, match.ring); - xive_tctx_ipb_update(match.tctx, match.ring, - xive_priority_to_ipb(priority)); + group_level = cam_ignore ? xive_get_group_level(nvt_idx) : 0; + trace_xive_presenter_notify(nvt_blk, nvt_idx, match.ring, group_level); + xive_tctx_pipr_update(match.tctx, match.ring, priority, group_level); } return !!count; diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index cd075e4db955..62c3f05af926 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -563,8 +563,11 @@ static void xive2_tctx_need_resend(Xive2Router *xrtr, XiveTCTX *tctx, uint8_t nvp_blk, uint32_t nvp_idx, bool do_restore) { - Xive2Nvp nvp; uint8_t ipb; + uint8_t backlog_level; + uint8_t backlog_prio; + uint8_t *regs = &tctx->regs[TM_QW1_OS]; + Xive2Nvp nvp; /* * Grab the associated thread interrupt context registers in the @@ -593,15 +596,15 @@ static void xive2_tctx_need_resend(Xive2Router *xrtr, XiveTCTX *tctx, nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, 0); xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2); } + regs[TM_IPB] |= ipb; + backlog_prio = xive_ipb_to_pipr(ipb); + backlog_level = 0; + /* - * Always call xive_tctx_ipb_update(). Even if there were no - * escalation triggered, there could be a pending interrupt which - * was saved when the context was pulled and that we need to take - * into account by recalculating the PIPR (which is not - * saved/restored). - * It will also raise the External interrupt signal if needed. + * Compute the PIPR based on the restored state. + * It will raise the External interrupt signal if needed. */ - xive_tctx_ipb_update(tctx, TM_QW1_OS, ipb); + xive_tctx_pipr_update(tctx, TM_QW1_OS, backlog_prio, backlog_level); } /* diff --git a/include/hw/ppc/xive.h b/include/hw/ppc/xive.h index 75276b9ba637..805b1d7b80e1 100644 --- a/include/hw/ppc/xive.h +++ b/include/hw/ppc/xive.h @@ -508,6 +508,11 @@ static inline uint8_t xive_priority_to_ipb(uint8_t priority) 0 : 1 << (XIVE_PRIORITY_MAX - priority); } +static inline uint8_t xive_priority_to_pipr(uint8_t priority) +{ + return priority > XIVE_PRIORITY_MAX ? 0xFF : priority; +} + /* * Convert an Interrupt Pending Buffer (IPB) register to a Pending * Interrupt Priority Register (PIPR), which contains the priority of @@ -540,8 +545,10 @@ void xive_tctx_pic_print_info(XiveTCTX *tctx, GString *buf); Object *xive_tctx_create(Object *cpu, XivePresenter *xptr, Error **errp); void xive_tctx_reset(XiveTCTX *tctx); void xive_tctx_destroy(XiveTCTX *tctx); -void xive_tctx_ipb_update(XiveTCTX *tctx, uint8_t ring, uint8_t ipb); +void xive_tctx_pipr_update(XiveTCTX *tctx, uint8_t ring, uint8_t priority, + uint8_t group_level); void xive_tctx_reset_signal(XiveTCTX *tctx, uint8_t ring); +void xive_tctx_notify(XiveTCTX *tctx, uint8_t ring, uint8_t group_level); /* * KVM XIVE device helpers diff --git a/include/hw/ppc/xive_regs.h b/include/hw/ppc/xive_regs.h index 326327fc79f9..54bc6c53b46b 100644 --- a/include/hw/ppc/xive_regs.h +++ b/include/hw/ppc/xive_regs.h @@ -7,10 +7,9 @@ * access to the different fields. * * - * Copyright (c) 2016-2018, IBM Corporation. + * Copyright (c) 2016-2024, IBM Corporation. * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef PPC_XIVE_REGS_H @@ -146,7 +145,14 @@ #define TM_SPC_PULL_PHYS_CTX_OL 0xc38 /* Pull phys ctx to odd cache line */ /* XXX more... */ -/* NSR fields for the various QW ack types */ +/* + * NSR fields for the various QW ack types + * + * P10 has an extra bit in QW3 for the group level instead of the + * reserved 'i' bit. Since it is not used and we don't support group + * interrupts on P9, we use the P10 definition for the group level so + * that we can have common macros for the NSR + */ #define TM_QW0_NSR_EB PPC_BIT8(0) #define TM_QW1_NSR_EO PPC_BIT8(0) #define TM_QW3_NSR_HE PPC_BITMASK8(0, 1) @@ -154,8 +160,15 @@ #define TM_QW3_NSR_HE_POOL 1 #define TM_QW3_NSR_HE_PHYS 2 #define TM_QW3_NSR_HE_LSI 3 -#define TM_QW3_NSR_I PPC_BIT8(2) -#define TM_QW3_NSR_GRP_LVL PPC_BIT8(3, 7) +#define TM_NSR_GRP_LVL PPC_BITMASK8(2, 7) +/* + * On P10, the format of the 6-bit group level is: 2 bits for the + * crowd size and 4 bits for the group size. Since group/crowd size is + * always a power of 2, we encode the log. For example, group_level=4 + * means crowd size = 0 and group size = 16 (2^4) + * Same encoding is used in the NVP and NVGC structures for + * PGoFirst and PGoNext fields + */ /* * EAS (Event Assignment Structure) From 9cb7f6ebed60739ff6e5f09e8ce85c9602d33f2a Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:51:21 +1000 Subject: [PATCH 0905/1179] ppc/xive2: Support group-matching when looking for target If an END has the 'i' bit set (ignore), then it targets a group of VPs. The size of the group depends on the VP index of the target (first 0 found when looking at the least significant bits of the index) so a mask is applied on the VP index of a running thread to know if we have a match. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/pnv_xive2.c | 38 +++++++++++++++--------- hw/intc/xive.c | 56 +++++++++++++++++++++++++----------- hw/intc/xive2.c | 65 ++++++++++++++++++++++++++++++------------ include/hw/ppc/xive.h | 5 +++- include/hw/ppc/xive2.h | 7 ++--- 5 files changed, 118 insertions(+), 53 deletions(-) diff --git a/hw/intc/pnv_xive2.c b/hw/intc/pnv_xive2.c index 9ed759417e00..8429ccbd51c4 100644 --- a/hw/intc/pnv_xive2.c +++ b/hw/intc/pnv_xive2.c @@ -1,10 +1,9 @@ /* * QEMU PowerPC XIVE2 interrupt controller model (POWER10) * - * Copyright (c) 2019-2022, IBM Corporation. + * Copyright (c) 2019-2024, IBM Corporation. * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" @@ -660,21 +659,34 @@ static int pnv_xive2_match_nvt(XivePresenter *xptr, uint8_t format, logic_serv); } - /* - * Save the context and follow on to catch duplicates, - * that we don't support yet. - */ if (ring != -1) { - if (match->tctx) { + /* + * For VP-specific match, finding more than one is a + * problem. For group notification, it's possible. + */ + if (!cam_ignore && match->tctx) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: already found a " "thread context NVT %x/%x\n", nvt_blk, nvt_idx); - return false; + /* Should set a FIR if we ever model it */ + return -1; + } + /* + * For a group notification, we need to know if the + * match is precluded first by checking the current + * thread priority. If the interrupt can be delivered, + * we always notify the first match (for now). + */ + if (cam_ignore && + xive2_tm_irq_precluded(tctx, ring, priority)) { + match->precluded = true; + } else { + if (!match->tctx) { + match->ring = ring; + match->tctx = tctx; + } + count++; } - - match->ring = ring; - match->tctx = tctx; - count++; } } } diff --git a/hw/intc/xive.c b/hw/intc/xive.c index 1e362f987ac5..3e4c932f1908 100644 --- a/hw/intc/xive.c +++ b/hw/intc/xive.c @@ -1655,6 +1655,16 @@ static uint32_t xive_tctx_hw_cam_line(XivePresenter *xptr, XiveTCTX *tctx) return xive_nvt_cam_line(blk, 1 << 7 | (pir & 0x7f)); } +uint32_t xive_get_vpgroup_size(uint32_t nvp_index) +{ + /* + * Group size is a power of 2. The position of the first 0 + * (starting with the least significant bits) in the NVP index + * gives the size of the group. + */ + return 1 << (ctz32(~nvp_index) + 1); +} + static uint8_t xive_get_group_level(uint32_t nvp_index) { /* FIXME add crowd encoding */ @@ -1727,30 +1737,39 @@ int xive_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, /* * This is our simple Xive Presenter Engine model. It is merged in the * Router as it does not require an extra object. - * - * It receives notification requests sent by the IVRE to find one - * matching NVT (or more) dispatched on the processor threads. In case - * of a single NVT notification, the process is abbreviated and the - * thread is signaled if a match is found. In case of a logical server - * notification (bits ignored at the end of the NVT identifier), the - * IVPE and IVRE select a winning thread using different filters. This - * involves 2 or 3 exchanges on the PowerBus that the model does not - * support. - * - * The parameters represent what is sent on the PowerBus */ bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, bool cam_ignore, uint8_t priority, - uint32_t logic_serv) + uint32_t logic_serv, bool *precluded) { XiveFabricClass *xfc = XIVE_FABRIC_GET_CLASS(xfb); - XiveTCTXMatch match = { .tctx = NULL, .ring = 0 }; + XiveTCTXMatch match = { .tctx = NULL, .ring = 0, .precluded = false }; uint8_t group_level; int count; /* - * Ask the machine to scan the interrupt controllers for a match + * Ask the machine to scan the interrupt controllers for a match. + * + * For VP-specific notification, we expect at most one match and + * one call to the presenters is all we need (abbreviated notify + * sequence documented by the architecture). + * + * For VP-group notification, match_nvt() is the equivalent of the + * "histogram" and "poll" commands sent to the power bus to the + * presenters. 'count' could be more than one, but we always + * select the first match for now. 'precluded' tells if (at least) + * one thread matches but can't take the interrupt now because + * it's running at a more favored priority. We return the + * information to the router so that it can take appropriate + * actions (backlog, escalation, broadcast, etc...) + * + * If we were to implement a better way of dispatching the + * interrupt in case of multiple matches (instead of the first + * match), we would need a heuristic to elect a thread (for + * example, the hardware keeps track of an 'age' in the TIMA) and + * a new command to the presenters (the equivalent of the "assign" + * power bus command in the documented full notify sequence. */ count = xfc->match_nvt(xfb, format, nvt_blk, nvt_idx, cam_ignore, priority, logic_serv, &match); @@ -1763,6 +1782,8 @@ bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, group_level = cam_ignore ? xive_get_group_level(nvt_idx) : 0; trace_xive_presenter_notify(nvt_blk, nvt_idx, match.ring, group_level); xive_tctx_pipr_update(match.tctx, match.ring, priority, group_level); + } else { + *precluded = match.precluded; } return !!count; @@ -1802,7 +1823,7 @@ void xive_router_end_notify(XiveRouter *xrtr, XiveEAS *eas) uint8_t nvt_blk; uint32_t nvt_idx; XiveNVT nvt; - bool found; + bool found, precluded; uint8_t end_blk = xive_get_field64(EAS_END_BLOCK, eas->w); uint32_t end_idx = xive_get_field64(EAS_END_INDEX, eas->w); @@ -1885,8 +1906,9 @@ void xive_router_end_notify(XiveRouter *xrtr, XiveEAS *eas) found = xive_presenter_notify(xrtr->xfb, format, nvt_blk, nvt_idx, xive_get_field32(END_W7_F0_IGNORE, end.w7), priority, - xive_get_field32(END_W7_F1_LOG_SERVER_ID, end.w7)); - + xive_get_field32(END_W7_F1_LOG_SERVER_ID, end.w7), + &precluded); + /* we don't support VP-group notification on P9, so precluded is not used */ /* TODO: Auto EOI. */ if (found) { diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index 62c3f05af926..41dbbdbb68cf 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -739,6 +739,12 @@ int xive2_router_write_nvgc(Xive2Router *xrtr, bool crowd, return xrc->write_nvgc(xrtr, crowd, nvgc_blk, nvgc_idx, nvgc); } +static bool xive2_vp_match_mask(uint32_t cam1, uint32_t cam2, + uint32_t vp_mask) +{ + return (cam1 & vp_mask) == (cam2 & vp_mask); +} + /* * The thread context register words are in big-endian format. */ @@ -753,44 +759,50 @@ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, uint32_t qw1w2 = xive_tctx_word2(&tctx->regs[TM_QW1_OS]); uint32_t qw0w2 = xive_tctx_word2(&tctx->regs[TM_QW0_USER]); - /* - * TODO (PowerNV): ignore mode. The low order bits of the NVT - * identifier are ignored in the "CAM" match. - */ + uint32_t vp_mask = 0xFFFFFFFF; if (format == 0) { - if (cam_ignore == true) { - /* - * F=0 & i=1: Logical server notification (bits ignored at - * the end of the NVT identifier) - */ - qemu_log_mask(LOG_UNIMP, "XIVE: no support for LS NVT %x/%x\n", - nvt_blk, nvt_idx); - return -1; + /* + * i=0: Specific NVT notification + * i=1: VP-group notification (bits ignored at the end of the + * NVT identifier) + */ + if (cam_ignore) { + vp_mask = ~(xive_get_vpgroup_size(nvt_idx) - 1); } - /* F=0 & i=0: Specific NVT notification */ + /* For VP-group notifications, threads with LGS=0 are excluded */ /* PHYS ring */ if ((be32_to_cpu(qw3w2) & TM2_QW3W2_VT) && - cam == xive2_tctx_hw_cam_line(xptr, tctx)) { + !(cam_ignore && tctx->regs[TM_QW3_HV_PHYS + TM_LGS] == 0) && + xive2_vp_match_mask(cam, + xive2_tctx_hw_cam_line(xptr, tctx), + vp_mask)) { return TM_QW3_HV_PHYS; } /* HV POOL ring */ if ((be32_to_cpu(qw2w2) & TM2_QW2W2_VP) && - cam == xive_get_field32(TM2_QW2W2_POOL_CAM, qw2w2)) { + !(cam_ignore && tctx->regs[TM_QW2_HV_POOL + TM_LGS] == 0) && + xive2_vp_match_mask(cam, + xive_get_field32(TM2_QW2W2_POOL_CAM, qw2w2), + vp_mask)) { return TM_QW2_HV_POOL; } /* OS ring */ if ((be32_to_cpu(qw1w2) & TM2_QW1W2_VO) && - cam == xive_get_field32(TM2_QW1W2_OS_CAM, qw1w2)) { + !(cam_ignore && tctx->regs[TM_QW1_OS + TM_LGS] == 0) && + xive2_vp_match_mask(cam, + xive_get_field32(TM2_QW1W2_OS_CAM, qw1w2), + vp_mask)) { return TM_QW1_OS; } } else { /* F=1 : User level Event-Based Branch (EBB) notification */ + /* FIXME: what if cam_ignore and LGS = 0 ? */ /* USER ring */ if ((be32_to_cpu(qw1w2) & TM2_QW1W2_VO) && (cam == xive_get_field32(TM2_QW1W2_OS_CAM, qw1w2)) && @@ -802,6 +814,22 @@ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, return -1; } +bool xive2_tm_irq_precluded(XiveTCTX *tctx, int ring, uint8_t priority) +{ + uint8_t *regs = &tctx->regs[ring]; + + /* + * The xive2_presenter_tctx_match() above tells if there's a match + * but for VP-group notification, we still need to look at the + * priority to know if the thread can take the interrupt now or if + * it is precluded. + */ + if (priority < regs[TM_CPPR]) { + return false; + } + return true; +} + static void xive2_router_realize(DeviceState *dev, Error **errp) { Xive2Router *xrtr = XIVE2_ROUTER(dev); @@ -841,7 +869,7 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, Xive2End end; uint8_t priority; uint8_t format; - bool found; + bool found, precluded; Xive2Nvp nvp; uint8_t nvp_blk; uint32_t nvp_idx; @@ -922,7 +950,8 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, found = xive_presenter_notify(xrtr->xfb, format, nvp_blk, nvp_idx, xive2_end_is_ignore(&end), priority, - xive_get_field32(END2_W7_F1_LOG_SERVER_ID, end.w7)); + xive_get_field32(END2_W7_F1_LOG_SERVER_ID, end.w7), + &precluded); /* TODO: Auto EOI. */ diff --git a/include/hw/ppc/xive.h b/include/hw/ppc/xive.h index 805b1d7b80e1..e78adaffa595 100644 --- a/include/hw/ppc/xive.h +++ b/include/hw/ppc/xive.h @@ -422,6 +422,7 @@ void xive_router_end_notify(XiveRouter *xrtr, XiveEAS *eas); typedef struct XiveTCTXMatch { XiveTCTX *tctx; uint8_t ring; + bool precluded; } XiveTCTXMatch; #define TYPE_XIVE_PRESENTER "xive-presenter" @@ -450,7 +451,9 @@ int xive_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, bool cam_ignore, uint8_t priority, - uint32_t logic_serv); + uint32_t logic_serv, bool *precluded); + +uint32_t xive_get_vpgroup_size(uint32_t nvp_index); /* * XIVE Fabric (Interface between Interrupt Controller and Machine) diff --git a/include/hw/ppc/xive2.h b/include/hw/ppc/xive2.h index 5bccf41159cb..65154f78d81f 100644 --- a/include/hw/ppc/xive2.h +++ b/include/hw/ppc/xive2.h @@ -1,11 +1,9 @@ /* * QEMU PowerPC XIVE2 interrupt controller model (POWER10) * - * Copyright (c) 2019-2022, IBM Corporation. - * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * Copyright (c) 2019-2024, IBM Corporation. * + * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef PPC_XIVE2_H @@ -121,6 +119,7 @@ uint64_t xive2_tm_pull_os_ctx(XivePresenter *xptr, XiveTCTX *tctx, hwaddr offset, unsigned size); void xive2_tm_pull_os_ctx_ol(XivePresenter *xptr, XiveTCTX *tctx, hwaddr offset, uint64_t value, unsigned size); +bool xive2_tm_irq_precluded(XiveTCTX *tctx, int ring, uint8_t priority); void xive2_tm_set_hv_target(XivePresenter *xptr, XiveTCTX *tctx, hwaddr offset, uint64_t value, unsigned size); void xive2_tm_pull_phys_ctx_ol(XivePresenter *xptr, XiveTCTX *tctx, From 58fa4433e02a394872ff83c28edea174c43e6afb Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:51:21 +1000 Subject: [PATCH 0906/1179] ppc/xive2: Add undelivered group interrupt to backlog When a group interrupt cannot be delivered, we need to: - increment the backlog counter for the group in the NVG table (if the END is configured to keep a backlog). - start a broadcast operation to set the LSMFB field on matching CPUs which can't take the interrupt now because they're running at too high a priority. [npiggin: squash in fixes from milesg] [milesg: only load the NVP if the END is !ignore] [milesg: always broadcast backlog, not only when there are precluded VPs] Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/pnv_xive2.c | 42 +++++++++++++ hw/intc/xive2.c | 136 ++++++++++++++++++++++++++++++++--------- hw/ppc/pnv.c | 22 ++++++- include/hw/ppc/xive.h | 5 ++ include/hw/ppc/xive2.h | 1 + 5 files changed, 175 insertions(+), 31 deletions(-) diff --git a/hw/intc/pnv_xive2.c b/hw/intc/pnv_xive2.c index 8429ccbd51c4..e7a7d1b50fab 100644 --- a/hw/intc/pnv_xive2.c +++ b/hw/intc/pnv_xive2.c @@ -705,6 +705,47 @@ static uint32_t pnv_xive2_presenter_get_config(XivePresenter *xptr) return cfg; } +static int pnv_xive2_broadcast(XivePresenter *xptr, + uint8_t nvt_blk, uint32_t nvt_idx, + uint8_t priority) +{ + PnvXive2 *xive = PNV_XIVE2(xptr); + PnvChip *chip = xive->chip; + int i, j; + bool gen1_tima_os = + xive->cq_regs[CQ_XIVE_CFG >> 3] & CQ_XIVE_CFG_GEN1_TIMA_OS; + + for (i = 0; i < chip->nr_cores; i++) { + PnvCore *pc = chip->cores[i]; + CPUCore *cc = CPU_CORE(pc); + + for (j = 0; j < cc->nr_threads; j++) { + PowerPCCPU *cpu = pc->threads[j]; + XiveTCTX *tctx; + int ring; + + if (!pnv_xive2_is_cpu_enabled(xive, cpu)) { + continue; + } + + tctx = XIVE_TCTX(pnv_cpu_state(cpu)->intc); + + if (gen1_tima_os) { + ring = xive_presenter_tctx_match(xptr, tctx, 0, nvt_blk, + nvt_idx, true, 0); + } else { + ring = xive2_presenter_tctx_match(xptr, tctx, 0, nvt_blk, + nvt_idx, true, 0); + } + + if (ring != -1) { + xive2_tm_set_lsmfb(tctx, ring, priority); + } + } + } + return 0; +} + static uint8_t pnv_xive2_get_block_id(Xive2Router *xrtr) { return pnv_xive2_block_id(PNV_XIVE2(xrtr)); @@ -2444,6 +2485,7 @@ static void pnv_xive2_class_init(ObjectClass *klass, void *data) xpc->match_nvt = pnv_xive2_match_nvt; xpc->get_config = pnv_xive2_presenter_get_config; + xpc->broadcast = pnv_xive2_broadcast; }; static const TypeInfo pnv_xive2_info = { diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index 41dbbdbb68cf..44d891f1f60f 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -53,7 +53,8 @@ static uint32_t xive2_nvgc_get_backlog(Xive2Nvgc *nvgc, uint8_t priority) /* * The per-priority backlog counters are 24-bit and the structure - * is stored in big endian + * is stored in big endian. NVGC is 32-bytes long, so 24-bytes from + * w2, which fits 8 priorities * 24-bits per priority. */ ptr = (uint8_t *)&nvgc->w2 + priority * 3; for (i = 0; i < 3; i++, ptr++) { @@ -62,6 +63,30 @@ static uint32_t xive2_nvgc_get_backlog(Xive2Nvgc *nvgc, uint8_t priority) return val; } +static void xive2_nvgc_set_backlog(Xive2Nvgc *nvgc, uint8_t priority, + uint32_t val) +{ + uint8_t *ptr, i; + uint32_t shift; + + if (priority > 7) { + return; + } + + if (val > 0xFFFFFF) { + val = 0xFFFFFF; + } + /* + * The per-priority backlog counters are 24-bit and the structure + * is stored in big endian + */ + ptr = (uint8_t *)&nvgc->w2 + priority * 3; + for (i = 0; i < 3; i++, ptr++) { + shift = 8 * (2 - i); + *ptr = (val >> shift) & 0xFF; + } +} + void xive2_eas_pic_print_info(Xive2Eas *eas, uint32_t lisn, GString *buf) { if (!xive2_eas_is_valid(eas)) { @@ -830,6 +855,19 @@ bool xive2_tm_irq_precluded(XiveTCTX *tctx, int ring, uint8_t priority) return true; } +void xive2_tm_set_lsmfb(XiveTCTX *tctx, int ring, uint8_t priority) +{ + uint8_t *regs = &tctx->regs[ring]; + + /* + * Called by the router during a VP-group notification when the + * thread matches but can't take the interrupt because it's + * already running at a more favored priority. It then stores the + * new interrupt priority in the LSMFB field. + */ + regs[TM_LSMFB] = priority; +} + static void xive2_router_realize(DeviceState *dev, Error **errp) { Xive2Router *xrtr = XIVE2_ROUTER(dev); @@ -870,7 +908,6 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, uint8_t priority; uint8_t format; bool found, precluded; - Xive2Nvp nvp; uint8_t nvp_blk; uint32_t nvp_idx; @@ -934,19 +971,6 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, nvp_blk = xive_get_field32(END2_W6_VP_BLOCK, end.w6); nvp_idx = xive_get_field32(END2_W6_VP_OFFSET, end.w6); - /* NVP cache lookup */ - if (xive2_router_get_nvp(xrtr, nvp_blk, nvp_idx, &nvp)) { - qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no NVP %x/%x\n", - nvp_blk, nvp_idx); - return; - } - - if (!xive2_nvp_is_valid(&nvp)) { - qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVP %x/%x is invalid\n", - nvp_blk, nvp_idx); - return; - } - found = xive_presenter_notify(xrtr->xfb, format, nvp_blk, nvp_idx, xive2_end_is_ignore(&end), priority, @@ -962,10 +986,9 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, /* * If no matching NVP is dispatched on a HW thread : * - specific VP: update the NVP structure if backlog is activated - * - logical server : forward request to IVPE (not supported) + * - VP-group: update the backlog counter for that priority in the NVG */ if (xive2_end_is_backlog(&end)) { - uint8_t ipb; if (format == 1) { qemu_log_mask(LOG_GUEST_ERROR, @@ -974,19 +997,72 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, return; } - /* - * Record the IPB in the associated NVP structure for later - * use. The presenter will resend the interrupt when the vCPU - * is dispatched again on a HW thread. - */ - ipb = xive_get_field32(NVP2_W2_IPB, nvp.w2) | - xive_priority_to_ipb(priority); - nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, ipb); - xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2); - - /* - * On HW, follows a "Broadcast Backlog" to IVPEs - */ + if (!xive2_end_is_ignore(&end)) { + uint8_t ipb; + Xive2Nvp nvp; + + /* NVP cache lookup */ + if (xive2_router_get_nvp(xrtr, nvp_blk, nvp_idx, &nvp)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no NVP %x/%x\n", + nvp_blk, nvp_idx); + return; + } + + if (!xive2_nvp_is_valid(&nvp)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVP %x/%x is invalid\n", + nvp_blk, nvp_idx); + return; + } + + /* + * Record the IPB in the associated NVP structure for later + * use. The presenter will resend the interrupt when the vCPU + * is dispatched again on a HW thread. + */ + ipb = xive_get_field32(NVP2_W2_IPB, nvp.w2) | + xive_priority_to_ipb(priority); + nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, ipb); + xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2); + } else { + Xive2Nvgc nvg; + uint32_t backlog; + + /* For groups, the per-priority backlog counters are in the NVG */ + if (xive2_router_get_nvgc(xrtr, false, nvp_blk, nvp_idx, &nvg)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no NVG %x/%x\n", + nvp_blk, nvp_idx); + return; + } + + if (!xive2_nvgc_is_valid(&nvg)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVG %x/%x is invalid\n", + nvp_blk, nvp_idx); + return; + } + + /* + * Increment the backlog counter for that priority. + * We only call broadcast the first time the counter is + * incremented. broadcast will set the LSMFB field of the TIMA of + * relevant threads so that they know an interrupt is pending. + */ + backlog = xive2_nvgc_get_backlog(&nvg, priority) + 1; + xive2_nvgc_set_backlog(&nvg, priority, backlog); + xive2_router_write_nvgc(xrtr, false, nvp_blk, nvp_idx, &nvg); + + if (backlog == 1) { + XiveFabricClass *xfc = XIVE_FABRIC_GET_CLASS(xrtr->xfb); + xfc->broadcast(xrtr->xfb, nvp_blk, nvp_idx, priority); + + if (!xive2_end_is_precluded_escalation(&end)) { + /* + * The interrupt will be picked up when the + * matching thread lowers its priority level + */ + return; + } + } + } } do_escalation: diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 6fec455ff95c..af836c1388eb 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -1,7 +1,9 @@ /* * QEMU PowerPC PowerNV machine model * - * Copyright (c) 2016, IBM Corporation. + * Copyright (c) 2016-2024, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -2662,6 +2664,23 @@ static int pnv10_xive_match_nvt(XiveFabric *xfb, uint8_t format, return total_count; } +static int pnv10_xive_broadcast(XiveFabric *xfb, + uint8_t nvt_blk, uint32_t nvt_idx, + uint8_t priority) +{ + PnvMachineState *pnv = PNV_MACHINE(xfb); + int i; + + for (i = 0; i < pnv->num_chips; i++) { + Pnv10Chip *chip10 = PNV10_CHIP(pnv->chips[i]); + XivePresenter *xptr = XIVE_PRESENTER(&chip10->xive); + XivePresenterClass *xpc = XIVE_PRESENTER_GET_CLASS(xptr); + + xpc->broadcast(xptr, nvt_blk, nvt_idx, priority); + } + return 0; +} + static bool pnv_machine_get_big_core(Object *obj, Error **errp) { PnvMachineState *pnv = PNV_MACHINE(obj); @@ -2795,6 +2814,7 @@ static void pnv_machine_p10_common_class_init(ObjectClass *oc, void *data) pmc->dt_power_mgt = pnv_dt_power_mgt; xfc->match_nvt = pnv10_xive_match_nvt; + xfc->broadcast = pnv10_xive_broadcast; machine_class_allow_dynamic_sysbus_dev(mc, TYPE_PNV_PHB); } diff --git a/include/hw/ppc/xive.h b/include/hw/ppc/xive.h index e78adaffa595..6a410c6f1a54 100644 --- a/include/hw/ppc/xive.h +++ b/include/hw/ppc/xive.h @@ -442,6 +442,9 @@ struct XivePresenterClass { uint32_t logic_serv, XiveTCTXMatch *match); bool (*in_kernel)(const XivePresenter *xptr); uint32_t (*get_config)(XivePresenter *xptr); + int (*broadcast)(XivePresenter *xptr, + uint8_t nvt_blk, uint32_t nvt_idx, + uint8_t priority); }; int xive_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, @@ -472,6 +475,8 @@ struct XiveFabricClass { uint8_t nvt_blk, uint32_t nvt_idx, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match); + int (*broadcast)(XiveFabric *xfb, uint8_t nvt_blk, uint32_t nvt_idx, + uint8_t priority); }; /* diff --git a/include/hw/ppc/xive2.h b/include/hw/ppc/xive2.h index 65154f78d81f..ebf301bb5b9a 100644 --- a/include/hw/ppc/xive2.h +++ b/include/hw/ppc/xive2.h @@ -120,6 +120,7 @@ uint64_t xive2_tm_pull_os_ctx(XivePresenter *xptr, XiveTCTX *tctx, void xive2_tm_pull_os_ctx_ol(XivePresenter *xptr, XiveTCTX *tctx, hwaddr offset, uint64_t value, unsigned size); bool xive2_tm_irq_precluded(XiveTCTX *tctx, int ring, uint8_t priority); +void xive2_tm_set_lsmfb(XiveTCTX *tctx, int ring, uint8_t priority); void xive2_tm_set_hv_target(XivePresenter *xptr, XiveTCTX *tctx, hwaddr offset, uint64_t value, unsigned size); void xive2_tm_pull_phys_ctx_ol(XivePresenter *xptr, XiveTCTX *tctx, From 071456d0c7d4783e3a2053e67cdf58d5f0610fdf Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:51:22 +1000 Subject: [PATCH 0907/1179] ppc/xive2: Process group backlog when pushing an OS context When pushing an OS context, we were already checking if there was a pending interrupt in the IPB and sending a notification if needed. We also need to check if there is a pending group interrupt stored in the NVG table. To avoid useless backlog scans, we only scan if the NVP belongs to a group. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/xive2.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index 44d891f1f60f..2fa7b906698a 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -279,6 +279,85 @@ static void xive2_end_enqueue(Xive2End *end, uint32_t data) end->w1 = xive_set_field32(END2_W1_PAGE_OFF, end->w1, qindex); } +/* + * Scan the group chain and return the highest priority and group + * level of pending group interrupts. + */ +static uint8_t xive2_presenter_backlog_scan(XivePresenter *xptr, + uint8_t nvp_blk, uint32_t nvp_idx, + uint8_t first_group, + uint8_t *out_level) +{ + Xive2Router *xrtr = XIVE2_ROUTER(xptr); + uint32_t nvgc_idx, mask; + uint32_t current_level, count; + uint8_t prio; + Xive2Nvgc nvgc; + + for (prio = 0; prio <= XIVE_PRIORITY_MAX; prio++) { + current_level = first_group & 0xF; + + while (current_level) { + mask = (1 << current_level) - 1; + nvgc_idx = nvp_idx & ~mask; + nvgc_idx |= mask >> 1; + qemu_log("fxb %s checking backlog for prio %d group idx %x\n", + __func__, prio, nvgc_idx); + + if (xive2_router_get_nvgc(xrtr, false, nvp_blk, nvgc_idx, &nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVG %x/%x\n", + nvp_blk, nvgc_idx); + return 0xFF; + } + if (!xive2_nvgc_is_valid(&nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", + nvp_blk, nvgc_idx); + return 0xFF; + } + + count = xive2_nvgc_get_backlog(&nvgc, prio); + if (count) { + *out_level = current_level; + return prio; + } + current_level = xive_get_field32(NVGC2_W0_PGONEXT, nvgc.w0) & 0xF; + } + } + return 0xFF; +} + +static void xive2_presenter_backlog_decr(XivePresenter *xptr, + uint8_t nvp_blk, uint32_t nvp_idx, + uint8_t group_prio, + uint8_t group_level) +{ + Xive2Router *xrtr = XIVE2_ROUTER(xptr); + uint32_t nvgc_idx, mask, count; + Xive2Nvgc nvgc; + + group_level &= 0xF; + mask = (1 << group_level) - 1; + nvgc_idx = nvp_idx & ~mask; + nvgc_idx |= mask >> 1; + + if (xive2_router_get_nvgc(xrtr, false, nvp_blk, nvgc_idx, &nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVG %x/%x\n", + nvp_blk, nvgc_idx); + return; + } + if (!xive2_nvgc_is_valid(&nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", + nvp_blk, nvgc_idx); + return; + } + count = xive2_nvgc_get_backlog(&nvgc, group_prio); + if (!count) { + return; + } + xive2_nvgc_set_backlog(&nvgc, group_prio, count - 1); + xive2_router_write_nvgc(xrtr, false, nvp_blk, nvgc_idx, &nvgc); +} + /* * XIVE Thread Interrupt Management Area (TIMA) - Gen2 mode * @@ -588,9 +667,13 @@ static void xive2_tctx_need_resend(Xive2Router *xrtr, XiveTCTX *tctx, uint8_t nvp_blk, uint32_t nvp_idx, bool do_restore) { + XivePresenter *xptr = XIVE_PRESENTER(xrtr); uint8_t ipb; uint8_t backlog_level; + uint8_t group_level; + uint8_t first_group; uint8_t backlog_prio; + uint8_t group_prio; uint8_t *regs = &tctx->regs[TM_QW1_OS]; Xive2Nvp nvp; @@ -625,6 +708,20 @@ static void xive2_tctx_need_resend(Xive2Router *xrtr, XiveTCTX *tctx, backlog_prio = xive_ipb_to_pipr(ipb); backlog_level = 0; + first_group = xive_get_field32(NVP2_W0_PGOFIRST, nvp.w0); + if (first_group && regs[TM_LSMFB] < backlog_prio) { + group_prio = xive2_presenter_backlog_scan(xptr, nvp_blk, nvp_idx, + first_group, &group_level); + regs[TM_LSMFB] = group_prio; + if (regs[TM_LGS] && group_prio < backlog_prio) { + /* VP can take a group interrupt */ + xive2_presenter_backlog_decr(xptr, nvp_blk, nvp_idx, + group_prio, group_level); + backlog_prio = group_prio; + backlog_level = group_level; + } + } + /* * Compute the PIPR based on the restored state. * It will raise the External interrupt signal if needed. From 26c55b99418173107897b28ffdb8171e913339e9 Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:51:22 +1000 Subject: [PATCH 0908/1179] ppc/xive2: Process group backlog when updating the CPPR When the hypervisor or OS pushes a new value to the CPPR, if the LSMFB value is lower than the new CPPR value, there could be a pending group interrupt in the backlog, so it needs to be scanned. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/xive.c | 4 +- hw/intc/xive2.c | 173 ++++++++++++++++++++++++++++++++++++++++- include/hw/ppc/xive2.h | 4 + 3 files changed, 177 insertions(+), 4 deletions(-) diff --git a/hw/intc/xive.c b/hw/intc/xive.c index 3e4c932f1908..535e59646fcc 100644 --- a/hw/intc/xive.c +++ b/hw/intc/xive.c @@ -589,7 +589,7 @@ static const XiveTmOp xive2_tm_operations[] = { * MMIOs below 2K : raw values and special operations without side * effects */ - { XIVE_TM_OS_PAGE, TM_QW1_OS + TM_CPPR, 1, xive_tm_set_os_cppr, + { XIVE_TM_OS_PAGE, TM_QW1_OS + TM_CPPR, 1, xive2_tm_set_os_cppr, NULL }, { XIVE_TM_HV_PAGE, TM_QW1_OS + TM_WORD2, 4, xive2_tm_push_os_ctx, NULL }, @@ -597,7 +597,7 @@ static const XiveTmOp xive2_tm_operations[] = { NULL }, { XIVE_TM_OS_PAGE, TM_QW1_OS + TM_LGS, 1, xive_tm_set_os_lgs, NULL }, - { XIVE_TM_HV_PAGE, TM_QW3_HV_PHYS + TM_CPPR, 1, xive_tm_set_hv_cppr, + { XIVE_TM_HV_PAGE, TM_QW3_HV_PHYS + TM_CPPR, 1, xive2_tm_set_hv_cppr, NULL }, { XIVE_TM_HV_PAGE, TM_QW3_HV_PHYS + TM_WORD2, 1, xive_tm_vt_push, NULL }, diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index 2fa7b906698a..017c0f8bb4b9 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -17,6 +17,7 @@ #include "hw/ppc/xive.h" #include "hw/ppc/xive2.h" #include "hw/ppc/xive2_regs.h" +#include "trace.h" uint32_t xive2_router_get_config(Xive2Router *xrtr) { @@ -768,6 +769,172 @@ void xive2_tm_push_os_ctx(XivePresenter *xptr, XiveTCTX *tctx, } } +static int xive2_tctx_get_nvp_indexes(XiveTCTX *tctx, uint8_t ring, + uint32_t *nvp_blk, uint32_t *nvp_idx) +{ + uint32_t w2, cam; + + w2 = xive_tctx_word2(&tctx->regs[ring]); + switch (ring) { + case TM_QW1_OS: + if (!(be32_to_cpu(w2) & TM2_QW1W2_VO)) { + return -1; + } + cam = xive_get_field32(TM2_QW1W2_OS_CAM, w2); + break; + case TM_QW2_HV_POOL: + if (!(be32_to_cpu(w2) & TM2_QW2W2_VP)) { + return -1; + } + cam = xive_get_field32(TM2_QW2W2_POOL_CAM, w2); + break; + case TM_QW3_HV_PHYS: + if (!(be32_to_cpu(w2) & TM2_QW3W2_VT)) { + return -1; + } + cam = xive2_tctx_hw_cam_line(tctx->xptr, tctx); + break; + default: + return -1; + } + *nvp_blk = xive2_nvp_blk(cam); + *nvp_idx = xive2_nvp_idx(cam); + return 0; +} + +static void xive2_tctx_set_cppr(XiveTCTX *tctx, uint8_t ring, uint8_t cppr) +{ + uint8_t *regs = &tctx->regs[ring]; + Xive2Router *xrtr = XIVE2_ROUTER(tctx->xptr); + uint8_t old_cppr, backlog_prio, first_group, group_level = 0; + uint8_t pipr_min, lsmfb_min, ring_min; + bool group_enabled; + uint32_t nvp_blk, nvp_idx; + Xive2Nvp nvp; + int rc; + + trace_xive_tctx_set_cppr(tctx->cs->cpu_index, ring, + regs[TM_IPB], regs[TM_PIPR], + cppr, regs[TM_NSR]); + + if (cppr > XIVE_PRIORITY_MAX) { + cppr = 0xff; + } + + old_cppr = regs[TM_CPPR]; + regs[TM_CPPR] = cppr; + + /* + * Recompute the PIPR based on local pending interrupts. It will + * be adjusted below if needed in case of pending group interrupts. + */ + pipr_min = xive_ipb_to_pipr(regs[TM_IPB]); + group_enabled = !!regs[TM_LGS]; + lsmfb_min = (group_enabled) ? regs[TM_LSMFB] : 0xff; + ring_min = ring; + + /* PHYS updates also depend on POOL values */ + if (ring == TM_QW3_HV_PHYS) { + uint8_t *pregs = &tctx->regs[TM_QW2_HV_POOL]; + + /* POOL values only matter if POOL ctx is valid */ + if (pregs[TM_WORD2] & 0x80) { + + uint8_t pool_pipr = xive_ipb_to_pipr(pregs[TM_IPB]); + uint8_t pool_lsmfb = pregs[TM_LSMFB]; + + /* + * Determine highest priority interrupt and + * remember which ring has it. + */ + if (pool_pipr < pipr_min) { + pipr_min = pool_pipr; + if (pool_pipr < lsmfb_min) { + ring_min = TM_QW2_HV_POOL; + } + } + + /* Values needed for group priority calculation */ + if (pregs[TM_LGS] && (pool_lsmfb < lsmfb_min)) { + group_enabled = true; + lsmfb_min = pool_lsmfb; + if (lsmfb_min < pipr_min) { + ring_min = TM_QW2_HV_POOL; + } + } + } + } + regs[TM_PIPR] = pipr_min; + + rc = xive2_tctx_get_nvp_indexes(tctx, ring_min, &nvp_blk, &nvp_idx); + if (rc) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: set CPPR on invalid context\n"); + return; + } + + if (cppr < old_cppr) { + /* + * FIXME: check if there's a group interrupt being presented + * and if the new cppr prevents it. If so, then the group + * interrupt needs to be re-added to the backlog and + * re-triggered (see re-trigger END info in the NVGC + * structure) + */ + } + + if (group_enabled && + lsmfb_min < cppr && + lsmfb_min < regs[TM_PIPR]) { + /* + * Thread has seen a group interrupt with a higher priority + * than the new cppr or pending local interrupt. Check the + * backlog + */ + if (xive2_router_get_nvp(xrtr, nvp_blk, nvp_idx, &nvp)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVP %x/%x\n", + nvp_blk, nvp_idx); + return; + } + + if (!xive2_nvp_is_valid(&nvp)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid NVP %x/%x\n", + nvp_blk, nvp_idx); + return; + } + + first_group = xive_get_field32(NVP2_W0_PGOFIRST, nvp.w0); + if (!first_group) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid NVP %x/%x\n", + nvp_blk, nvp_idx); + return; + } + + backlog_prio = xive2_presenter_backlog_scan(tctx->xptr, + nvp_blk, nvp_idx, + first_group, &group_level); + tctx->regs[ring_min + TM_LSMFB] = backlog_prio; + if (backlog_prio != 0xFF) { + xive2_presenter_backlog_decr(tctx->xptr, nvp_blk, nvp_idx, + backlog_prio, group_level); + regs[TM_PIPR] = backlog_prio; + } + } + /* CPPR has changed, check if we need to raise a pending exception */ + xive_tctx_notify(tctx, ring_min, group_level); +} + +void xive2_tm_set_hv_cppr(XivePresenter *xptr, XiveTCTX *tctx, + hwaddr offset, uint64_t value, unsigned size) +{ + xive2_tctx_set_cppr(tctx, TM_QW3_HV_PHYS, value & 0xff); +} + +void xive2_tm_set_os_cppr(XivePresenter *xptr, XiveTCTX *tctx, + hwaddr offset, uint64_t value, unsigned size) +{ + xive2_tctx_set_cppr(tctx, TM_QW1_OS, value & 0xff); +} + static void xive2_tctx_set_target(XiveTCTX *tctx, uint8_t ring, uint8_t target) { uint8_t *regs = &tctx->regs[ring]; @@ -938,7 +1105,9 @@ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, bool xive2_tm_irq_precluded(XiveTCTX *tctx, int ring, uint8_t priority) { - uint8_t *regs = &tctx->regs[ring]; + /* HV_POOL ring uses HV_PHYS NSR, CPPR and PIPR registers */ + uint8_t alt_ring = (ring == TM_QW2_HV_POOL) ? TM_QW3_HV_PHYS : ring; + uint8_t *alt_regs = &tctx->regs[alt_ring]; /* * The xive2_presenter_tctx_match() above tells if there's a match @@ -946,7 +1115,7 @@ bool xive2_tm_irq_precluded(XiveTCTX *tctx, int ring, uint8_t priority) * priority to know if the thread can take the interrupt now or if * it is precluded. */ - if (priority < regs[TM_CPPR]) { + if (priority < alt_regs[TM_CPPR]) { return false; } return true; diff --git a/include/hw/ppc/xive2.h b/include/hw/ppc/xive2.h index ebf301bb5b9a..fc7422fea75b 100644 --- a/include/hw/ppc/xive2.h +++ b/include/hw/ppc/xive2.h @@ -113,6 +113,10 @@ typedef struct Xive2EndSource { * XIVE2 Thread Interrupt Management Area (POWER10) */ +void xive2_tm_set_hv_cppr(XivePresenter *xptr, XiveTCTX *tctx, + hwaddr offset, uint64_t value, unsigned size); +void xive2_tm_set_os_cppr(XivePresenter *xptr, XiveTCTX *tctx, + hwaddr offset, uint64_t value, unsigned size); void xive2_tm_push_os_ctx(XivePresenter *xptr, XiveTCTX *tctx, hwaddr offset, uint64_t value, unsigned size); uint64_t xive2_tm_pull_os_ctx(XivePresenter *xptr, XiveTCTX *tctx, From c2b7fade9f9bca1cc0ca91ab614ea5cee3629e2e Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:51:22 +1000 Subject: [PATCH 0909/1179] qtest/xive: Add group-interrupt test Add XIVE2 tests for group interrupts and group interrupts that have been backlogged. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- tests/qtest/pnv-xive2-test.c | 160 +++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/tests/qtest/pnv-xive2-test.c b/tests/qtest/pnv-xive2-test.c index dd19e888614e..a4d06550ee6a 100644 --- a/tests/qtest/pnv-xive2-test.c +++ b/tests/qtest/pnv-xive2-test.c @@ -2,6 +2,8 @@ * QTest testcase for PowerNV 10 interrupt controller (xive2) * - Test irq to hardware thread * - Test 'Pull Thread Context to Odd Thread Reporting Line' + * - Test irq to hardware group + * - Test irq to hardware group going through backlog * * Copyright (c) 2024, IBM Corporation. * @@ -315,6 +317,158 @@ static void test_pull_thread_ctx_to_odd_thread_cl(QTestState *qts) word2 = get_tima32(qts, target_pir, TM_QW3_HV_PHYS + TM_WORD2); g_assert_cmphex(xive_get_field32(TM_QW3W2_VT, word2), ==, 0); } + +static void test_hw_group_irq(QTestState *qts) +{ + uint32_t irq = 100; + uint32_t irq_data = 0xdeadbeef; + uint32_t end_index = 23; + uint32_t chosen_one; + uint32_t target_nvp = 0x81; /* group size = 4 */ + uint8_t priority = 6; + uint32_t reg32; + uint16_t reg16; + uint8_t pq, nsr, cppr; + + printf("# ============================================================\n"); + printf("# Testing irq %d to hardware group of size 4\n", irq); + + /* irq config */ + set_eas(qts, irq, end_index, irq_data); + set_end(qts, end_index, target_nvp, priority, true /* group */); + + /* enable and trigger irq */ + get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_SET_PQ_00); + set_esb(qts, irq, XIVE_TRIGGER_PAGE, 0, 0); + + /* check irq is raised on cpu */ + pq = get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_GET); + g_assert_cmpuint(pq, ==, XIVE_ESB_PENDING); + + /* find the targeted vCPU */ + for (chosen_one = 0; chosen_one < SMT; chosen_one++) { + reg32 = get_tima32(qts, chosen_one, TM_QW3_HV_PHYS + TM_WORD0); + nsr = reg32 >> 24; + if (nsr == 0x82) { + break; + } + } + g_assert_cmphex(chosen_one, <, SMT); + cppr = (reg32 >> 16) & 0xFF; + g_assert_cmphex(nsr, ==, 0x82); + g_assert_cmphex(cppr, ==, 0xFF); + + /* ack the irq */ + reg16 = get_tima16(qts, chosen_one, TM_SPC_ACK_HV_REG); + nsr = reg16 >> 8; + cppr = reg16 & 0xFF; + g_assert_cmphex(nsr, ==, 0x82); + g_assert_cmphex(cppr, ==, priority); + + /* check irq data is what was configured */ + reg32 = qtest_readl(qts, xive_get_queue_addr(end_index)); + g_assert_cmphex((reg32 & 0x7fffffff), ==, (irq_data & 0x7fffffff)); + + /* End Of Interrupt */ + set_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_STORE_EOI, 0); + pq = get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_GET); + g_assert_cmpuint(pq, ==, XIVE_ESB_RESET); + + /* reset CPPR */ + set_tima8(qts, chosen_one, TM_QW3_HV_PHYS + TM_CPPR, 0xFF); + reg32 = get_tima32(qts, chosen_one, TM_QW3_HV_PHYS + TM_WORD0); + nsr = reg32 >> 24; + cppr = (reg32 >> 16) & 0xFF; + g_assert_cmphex(nsr, ==, 0x00); + g_assert_cmphex(cppr, ==, 0xFF); +} + +static void test_hw_group_irq_backlog(QTestState *qts) +{ + uint32_t irq = 31; + uint32_t irq_data = 0x01234567; + uint32_t end_index = 129; + uint32_t target_nvp = 0x81; /* group size = 4 */ + uint32_t chosen_one = 3; + uint8_t blocking_priority, priority = 3; + uint32_t reg32; + uint16_t reg16; + uint8_t pq, nsr, cppr, lsmfb, i; + + printf("# ============================================================\n"); + printf("# Testing irq %d to hardware group of size 4 going through " \ + "backlog\n", + irq); + + /* + * set current priority of all threads in the group to something + * higher than what we're about to trigger + */ + blocking_priority = priority - 1; + for (i = 0; i < SMT; i++) { + set_tima8(qts, i, TM_QW3_HV_PHYS + TM_CPPR, blocking_priority); + } + + /* irq config */ + set_eas(qts, irq, end_index, irq_data); + set_end(qts, end_index, target_nvp, priority, true /* group */); + + /* enable and trigger irq */ + get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_SET_PQ_00); + set_esb(qts, irq, XIVE_TRIGGER_PAGE, 0, 0); + + /* check irq is raised on cpu */ + pq = get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_GET); + g_assert_cmpuint(pq, ==, XIVE_ESB_PENDING); + + /* check no interrupt is pending on the 2 possible targets */ + for (i = 0; i < SMT; i++) { + reg32 = get_tima32(qts, i, TM_QW3_HV_PHYS + TM_WORD0); + nsr = reg32 >> 24; + cppr = (reg32 >> 16) & 0xFF; + lsmfb = reg32 & 0xFF; + g_assert_cmphex(nsr, ==, 0x0); + g_assert_cmphex(cppr, ==, blocking_priority); + g_assert_cmphex(lsmfb, ==, priority); + } + + /* lower priority of one thread */ + set_tima8(qts, chosen_one, TM_QW3_HV_PHYS + TM_CPPR, priority + 1); + + /* check backlogged interrupt is presented */ + reg32 = get_tima32(qts, chosen_one, TM_QW3_HV_PHYS + TM_WORD0); + nsr = reg32 >> 24; + cppr = (reg32 >> 16) & 0xFF; + g_assert_cmphex(nsr, ==, 0x82); + g_assert_cmphex(cppr, ==, priority + 1); + + /* ack the irq */ + reg16 = get_tima16(qts, chosen_one, TM_SPC_ACK_HV_REG); + nsr = reg16 >> 8; + cppr = reg16 & 0xFF; + g_assert_cmphex(nsr, ==, 0x82); + g_assert_cmphex(cppr, ==, priority); + + /* check irq data is what was configured */ + reg32 = qtest_readl(qts, xive_get_queue_addr(end_index)); + g_assert_cmphex((reg32 & 0x7fffffff), ==, (irq_data & 0x7fffffff)); + + /* End Of Interrupt */ + set_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_STORE_EOI, 0); + pq = get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_GET); + g_assert_cmpuint(pq, ==, XIVE_ESB_RESET); + + /* reset CPPR */ + set_tima8(qts, chosen_one, TM_QW3_HV_PHYS + TM_CPPR, 0xFF); + reg32 = get_tima32(qts, chosen_one, TM_QW3_HV_PHYS + TM_WORD0); + nsr = reg32 >> 24; + cppr = (reg32 >> 16) & 0xFF; + lsmfb = reg32 & 0xFF; + g_assert_cmphex(nsr, ==, 0x00); + g_assert_cmphex(cppr, ==, 0xFF); + g_assert_cmphex(lsmfb, ==, 0xFF); +} + static void test_xive(void) { QTestState *qts; @@ -330,6 +484,12 @@ static void test_xive(void) /* omit reset_state here and use settings from test_hw_irq */ test_pull_thread_ctx_to_odd_thread_cl(qts); + reset_state(qts); + test_hw_group_irq(qts); + + reset_state(qts); + test_hw_group_irq_backlog(qts); + reset_state(qts); test_flush_sync_inject(qts); From 96a2132ce95dab3e61002412839a118aecca0be0 Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:53:45 +1000 Subject: [PATCH 0910/1179] ppc/xive2: Add support for MMIO operations on the NVPG/NVC BAR Add support for the NVPG and NVC BARs. Access to the BAR pages will cause backlog counter operations to either increment or decriment the counter. Also added qtests for the same. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/pnv_xive2.c | 80 +++++++++++++--- hw/intc/trace-events | 4 + hw/intc/xive2.c | 87 ++++++++++++++++++ include/hw/ppc/xive2.h | 9 ++ include/hw/ppc/xive2_regs.h | 3 + tests/qtest/meson.build | 3 +- tests/qtest/pnv-xive2-common.h | 1 + tests/qtest/pnv-xive2-nvpg_bar.c | 153 +++++++++++++++++++++++++++++++ tests/qtest/pnv-xive2-test.c | 3 + 9 files changed, 328 insertions(+), 15 deletions(-) create mode 100644 tests/qtest/pnv-xive2-nvpg_bar.c diff --git a/hw/intc/pnv_xive2.c b/hw/intc/pnv_xive2.c index e7a7d1b50fab..c55d596e488d 100644 --- a/hw/intc/pnv_xive2.c +++ b/hw/intc/pnv_xive2.c @@ -2202,21 +2202,40 @@ static const MemoryRegionOps pnv_xive2_tm_ops = { }, }; -static uint64_t pnv_xive2_nvc_read(void *opaque, hwaddr offset, +static uint64_t pnv_xive2_nvc_read(void *opaque, hwaddr addr, unsigned size) { PnvXive2 *xive = PNV_XIVE2(opaque); + XivePresenter *xptr = XIVE_PRESENTER(xive); + uint32_t page = addr >> xive->nvpg_shift; + uint16_t op = addr & 0xFFF; + uint8_t blk = pnv_xive2_block_id(xive); - xive2_error(xive, "NVC: invalid read @%"HWADDR_PRIx, offset); - return -1; + if (size != 2) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvc load size %d\n", + size); + return -1; + } + + return xive2_presenter_nvgc_backlog_op(xptr, true, blk, page, op, 1); } -static void pnv_xive2_nvc_write(void *opaque, hwaddr offset, +static void pnv_xive2_nvc_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { PnvXive2 *xive = PNV_XIVE2(opaque); + XivePresenter *xptr = XIVE_PRESENTER(xive); + uint32_t page = addr >> xive->nvc_shift; + uint16_t op = addr & 0xFFF; + uint8_t blk = pnv_xive2_block_id(xive); - xive2_error(xive, "NVC: invalid write @%"HWADDR_PRIx, offset); + if (size != 1) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvc write size %d\n", + size); + return; + } + + (void)xive2_presenter_nvgc_backlog_op(xptr, true, blk, page, op, val); } static const MemoryRegionOps pnv_xive2_nvc_ops = { @@ -2224,30 +2243,63 @@ static const MemoryRegionOps pnv_xive2_nvc_ops = { .write = pnv_xive2_nvc_write, .endianness = DEVICE_BIG_ENDIAN, .valid = { - .min_access_size = 8, + .min_access_size = 1, .max_access_size = 8, }, .impl = { - .min_access_size = 8, + .min_access_size = 1, .max_access_size = 8, }, }; -static uint64_t pnv_xive2_nvpg_read(void *opaque, hwaddr offset, +static uint64_t pnv_xive2_nvpg_read(void *opaque, hwaddr addr, unsigned size) { PnvXive2 *xive = PNV_XIVE2(opaque); + XivePresenter *xptr = XIVE_PRESENTER(xive); + uint32_t page = addr >> xive->nvpg_shift; + uint16_t op = addr & 0xFFF; + uint32_t index = page >> 1; + uint8_t blk = pnv_xive2_block_id(xive); - xive2_error(xive, "NVPG: invalid read @%"HWADDR_PRIx, offset); - return -1; + if (size != 2) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvpg load size %d\n", + size); + return -1; + } + + if (page % 2) { + /* odd page - NVG */ + return xive2_presenter_nvgc_backlog_op(xptr, false, blk, index, op, 1); + } else { + /* even page - NVP */ + return xive2_presenter_nvp_backlog_op(xptr, blk, index, op); + } } -static void pnv_xive2_nvpg_write(void *opaque, hwaddr offset, +static void pnv_xive2_nvpg_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { PnvXive2 *xive = PNV_XIVE2(opaque); + XivePresenter *xptr = XIVE_PRESENTER(xive); + uint32_t page = addr >> xive->nvpg_shift; + uint16_t op = addr & 0xFFF; + uint32_t index = page >> 1; + uint8_t blk = pnv_xive2_block_id(xive); - xive2_error(xive, "NVPG: invalid write @%"HWADDR_PRIx, offset); + if (size != 1) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid nvpg write size %d\n", + size); + return; + } + + if (page % 2) { + /* odd page - NVG */ + (void)xive2_presenter_nvgc_backlog_op(xptr, false, blk, index, op, val); + } else { + /* even page - NVP */ + (void)xive2_presenter_nvp_backlog_op(xptr, blk, index, op); + } } static const MemoryRegionOps pnv_xive2_nvpg_ops = { @@ -2255,11 +2307,11 @@ static const MemoryRegionOps pnv_xive2_nvpg_ops = { .write = pnv_xive2_nvpg_write, .endianness = DEVICE_BIG_ENDIAN, .valid = { - .min_access_size = 8, + .min_access_size = 1, .max_access_size = 8, }, .impl = { - .min_access_size = 8, + .min_access_size = 1, .max_access_size = 8, }, }; diff --git a/hw/intc/trace-events b/hw/intc/trace-events index e9fe1978f9a2..0ba9a02e7397 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -286,6 +286,10 @@ xive_tctx_tm_read(uint32_t index, uint64_t offset, unsigned int size, uint64_t v xive_presenter_notify(uint8_t nvt_blk, uint32_t nvt_idx, uint8_t ring, uint8_t group_level) "found NVT 0x%x/0x%x ring=0x%x group_level=%d" xive_end_source_read(uint8_t end_blk, uint32_t end_idx, uint64_t addr) "END 0x%x/0x%x @0x%"PRIx64 +# xive2.c +xive_nvp_backlog_op(uint8_t blk, uint32_t idx, uint8_t op, uint8_t priority, uint8_t rc) "NVP 0x%x/0x%x operation=%d priority=%d rc=%d" +xive_nvgc_backlog_op(bool c, uint8_t blk, uint32_t idx, uint8_t op, uint8_t priority, uint32_t rc) "NVGC crowd=%d 0x%x/0x%x operation=%d priority=%d rc=%d" + # pnv_xive.c pnv_xive_ic_hw_trigger(uint64_t addr, uint64_t val) "@0x%"PRIx64" val=0x%"PRIx64 diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index 017c0f8bb4b9..34628f292229 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -88,6 +88,93 @@ static void xive2_nvgc_set_backlog(Xive2Nvgc *nvgc, uint8_t priority, } } +uint64_t xive2_presenter_nvgc_backlog_op(XivePresenter *xptr, + bool crowd, + uint8_t blk, uint32_t idx, + uint16_t offset, uint16_t val) +{ + Xive2Router *xrtr = XIVE2_ROUTER(xptr); + uint8_t priority = GETFIELD(NVx_BACKLOG_PRIO, offset); + uint8_t op = GETFIELD(NVx_BACKLOG_OP, offset); + Xive2Nvgc nvgc; + uint32_t count, old_count; + + if (xive2_router_get_nvgc(xrtr, crowd, blk, idx, &nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No %s %x/%x\n", + crowd ? "NVC" : "NVG", blk, idx); + return -1; + } + if (!xive2_nvgc_is_valid(&nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", blk, idx); + return -1; + } + + old_count = xive2_nvgc_get_backlog(&nvgc, priority); + count = old_count; + /* + * op: + * 0b00 => increment + * 0b01 => decrement + * 0b1- => read + */ + if (op == 0b00 || op == 0b01) { + if (op == 0b00) { + count += val; + } else { + if (count > val) { + count -= val; + } else { + count = 0; + } + } + xive2_nvgc_set_backlog(&nvgc, priority, count); + xive2_router_write_nvgc(xrtr, crowd, blk, idx, &nvgc); + } + trace_xive_nvgc_backlog_op(crowd, blk, idx, op, priority, old_count); + return old_count; +} + +uint64_t xive2_presenter_nvp_backlog_op(XivePresenter *xptr, + uint8_t blk, uint32_t idx, + uint16_t offset) +{ + Xive2Router *xrtr = XIVE2_ROUTER(xptr); + uint8_t priority = GETFIELD(NVx_BACKLOG_PRIO, offset); + uint8_t op = GETFIELD(NVx_BACKLOG_OP, offset); + Xive2Nvp nvp; + uint8_t ipb, old_ipb, rc; + + if (xive2_router_get_nvp(xrtr, blk, idx, &nvp)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVP %x/%x\n", blk, idx); + return -1; + } + if (!xive2_nvp_is_valid(&nvp)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVP %x/%x\n", blk, idx); + return -1; + } + + old_ipb = xive_get_field32(NVP2_W2_IPB, nvp.w2); + ipb = old_ipb; + /* + * op: + * 0b00 => set priority bit + * 0b01 => reset priority bit + * 0b1- => read + */ + if (op == 0b00 || op == 0b01) { + if (op == 0b00) { + ipb |= xive_priority_to_ipb(priority); + } else { + ipb &= ~xive_priority_to_ipb(priority); + } + nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, ipb); + xive2_router_write_nvp(xrtr, blk, idx, &nvp, 2); + } + rc = !!(old_ipb & xive_priority_to_ipb(priority)); + trace_xive_nvp_backlog_op(blk, idx, op, priority, rc); + return rc; +} + void xive2_eas_pic_print_info(Xive2Eas *eas, uint32_t lisn, GString *buf) { if (!xive2_eas_is_valid(eas)) { diff --git a/include/hw/ppc/xive2.h b/include/hw/ppc/xive2.h index fc7422fea75b..c07e23e1d36c 100644 --- a/include/hw/ppc/xive2.h +++ b/include/hw/ppc/xive2.h @@ -90,6 +90,15 @@ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, uint8_t nvt_blk, uint32_t nvt_idx, bool cam_ignore, uint32_t logic_serv); +uint64_t xive2_presenter_nvp_backlog_op(XivePresenter *xptr, + uint8_t blk, uint32_t idx, + uint16_t offset); + +uint64_t xive2_presenter_nvgc_backlog_op(XivePresenter *xptr, + bool crowd, + uint8_t blk, uint32_t idx, + uint16_t offset, uint16_t val); + /* * XIVE2 END ESBs (POWER10) */ diff --git a/include/hw/ppc/xive2_regs.h b/include/hw/ppc/xive2_regs.h index e88d6eab1e56..9bcf7a8a6f29 100644 --- a/include/hw/ppc/xive2_regs.h +++ b/include/hw/ppc/xive2_regs.h @@ -233,4 +233,7 @@ typedef struct Xive2Nvgc { void xive2_nvgc_pic_print_info(Xive2Nvgc *nvgc, uint32_t nvgc_idx, GString *buf); +#define NVx_BACKLOG_OP PPC_BITMASK(52, 53) +#define NVx_BACKLOG_PRIO PPC_BITMASK(57, 59) + #endif /* PPC_XIVE2_REGS_H */ diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index b23fe67db7ac..3ecb23e610db 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -368,7 +368,8 @@ qtests = { 'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'], 'migration-test': migration_files + migration_tls_files, 'pxe-test': files('boot-sector.c'), - 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c'), + 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c', + 'pnv-xive2-nvpg_bar.c'), 'qos-test': [chardev, io, qos_test_ss.apply({}).sources()], 'tpm-crb-swtpm-test': [io, tpmemu_files], 'tpm-crb-test': [io, tpmemu_files], diff --git a/tests/qtest/pnv-xive2-common.h b/tests/qtest/pnv-xive2-common.h index 9ae34771aa41..2077c05ebc7a 100644 --- a/tests/qtest/pnv-xive2-common.h +++ b/tests/qtest/pnv-xive2-common.h @@ -107,5 +107,6 @@ extern void set_end(QTestState *qts, uint32_t index, uint32_t nvp_index, void test_flush_sync_inject(QTestState *qts); +void test_nvpg_bar(QTestState *qts); #endif /* TEST_PNV_XIVE2_COMMON_H */ diff --git a/tests/qtest/pnv-xive2-nvpg_bar.c b/tests/qtest/pnv-xive2-nvpg_bar.c new file mode 100644 index 000000000000..028512bddc83 --- /dev/null +++ b/tests/qtest/pnv-xive2-nvpg_bar.c @@ -0,0 +1,153 @@ +/* + * QTest testcase for PowerNV 10 interrupt controller (xive2) + * - Test NVPG BAR MMIO operations + * + * Copyright (c) 2024, IBM Corporation. + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * later. See the COPYING file in the top-level directory. + */ +#include "qemu/osdep.h" +#include "libqtest.h" + +#include "pnv-xive2-common.h" + +#define NVPG_BACKLOG_OP_SHIFT 10 +#define NVPG_BACKLOG_PRIO_SHIFT 4 + +#define XIVE_PRIORITY_MAX 7 + +enum NVx { + NVP, + NVG, + NVC +}; + +typedef enum { + INCR_STORE = 0b100, + INCR_LOAD = 0b000, + DECR_STORE = 0b101, + DECR_LOAD = 0b001, + READ_x = 0b010, + READ_y = 0b011, +} backlog_op; + +static uint32_t nvpg_backlog_op(QTestState *qts, backlog_op op, + enum NVx type, uint64_t index, + uint8_t priority, uint8_t delta) +{ + uint64_t addr, offset; + uint32_t count = 0; + + switch (type) { + case NVP: + addr = XIVE_NVPG_ADDR + (index << (XIVE_PAGE_SHIFT + 1)); + break; + case NVG: + addr = XIVE_NVPG_ADDR + (index << (XIVE_PAGE_SHIFT + 1)) + + (1 << XIVE_PAGE_SHIFT); + break; + case NVC: + addr = XIVE_NVC_ADDR + (index << XIVE_PAGE_SHIFT); + break; + default: + g_assert_not_reached(); + } + + offset = (op & 0b11) << NVPG_BACKLOG_OP_SHIFT; + offset |= priority << NVPG_BACKLOG_PRIO_SHIFT; + if (op >> 2) { + qtest_writeb(qts, addr + offset, delta); + } else { + count = qtest_readw(qts, addr + offset); + } + return count; +} + +void test_nvpg_bar(QTestState *qts) +{ + uint32_t nvp_target = 0x11; + uint32_t group_target = 0x17; /* size 16 */ + uint32_t vp_irq = 33, group_irq = 47; + uint32_t vp_end = 3, group_end = 97; + uint32_t vp_irq_data = 0x33333333; + uint32_t group_irq_data = 0x66666666; + uint8_t vp_priority = 0, group_priority = 5; + uint32_t vp_count[XIVE_PRIORITY_MAX + 1] = { 0 }; + uint32_t group_count[XIVE_PRIORITY_MAX + 1] = { 0 }; + uint32_t count, delta; + uint8_t i; + + printf("# ============================================================\n"); + printf("# Testing NVPG BAR operations\n"); + + set_nvg(qts, group_target, 0); + set_nvp(qts, nvp_target, 0x04); + set_nvp(qts, group_target, 0x04); + + /* + * Setup: trigger a VP-specific interrupt and a group interrupt + * so that the backlog counters are initialized to something else + * than 0 for at least one priority level + */ + set_eas(qts, vp_irq, vp_end, vp_irq_data); + set_end(qts, vp_end, nvp_target, vp_priority, false /* group */); + + set_eas(qts, group_irq, group_end, group_irq_data); + set_end(qts, group_end, group_target, group_priority, true /* group */); + + get_esb(qts, vp_irq, XIVE_EOI_PAGE, XIVE_ESB_SET_PQ_00); + set_esb(qts, vp_irq, XIVE_TRIGGER_PAGE, 0, 0); + vp_count[vp_priority]++; + + get_esb(qts, group_irq, XIVE_EOI_PAGE, XIVE_ESB_SET_PQ_00); + set_esb(qts, group_irq, XIVE_TRIGGER_PAGE, 0, 0); + group_count[group_priority]++; + + /* check the initial counters */ + for (i = 0; i <= XIVE_PRIORITY_MAX; i++) { + count = nvpg_backlog_op(qts, READ_x, NVP, nvp_target, i, 0); + g_assert_cmpuint(count, ==, vp_count[i]); + + count = nvpg_backlog_op(qts, READ_y, NVG, group_target, i, 0); + g_assert_cmpuint(count, ==, group_count[i]); + } + + /* do a few ops on the VP. Counter can only be 0 and 1 */ + vp_priority = 2; + delta = 7; + nvpg_backlog_op(qts, INCR_STORE, NVP, nvp_target, vp_priority, delta); + vp_count[vp_priority] = 1; + count = nvpg_backlog_op(qts, INCR_LOAD, NVP, nvp_target, vp_priority, 0); + g_assert_cmpuint(count, ==, vp_count[vp_priority]); + count = nvpg_backlog_op(qts, READ_y, NVP, nvp_target, vp_priority, 0); + g_assert_cmpuint(count, ==, vp_count[vp_priority]); + + count = nvpg_backlog_op(qts, DECR_LOAD, NVP, nvp_target, vp_priority, 0); + g_assert_cmpuint(count, ==, vp_count[vp_priority]); + vp_count[vp_priority] = 0; + nvpg_backlog_op(qts, DECR_STORE, NVP, nvp_target, vp_priority, delta); + count = nvpg_backlog_op(qts, READ_x, NVP, nvp_target, vp_priority, 0); + g_assert_cmpuint(count, ==, vp_count[vp_priority]); + + /* do a few ops on the group */ + group_priority = 2; + delta = 9; + /* can't go negative */ + nvpg_backlog_op(qts, DECR_STORE, NVG, group_target, group_priority, delta); + count = nvpg_backlog_op(qts, READ_y, NVG, group_target, group_priority, 0); + g_assert_cmpuint(count, ==, 0); + nvpg_backlog_op(qts, INCR_STORE, NVG, group_target, group_priority, delta); + group_count[group_priority] += delta; + count = nvpg_backlog_op(qts, INCR_LOAD, NVG, group_target, + group_priority, delta); + g_assert_cmpuint(count, ==, group_count[group_priority]); + group_count[group_priority]++; + + count = nvpg_backlog_op(qts, DECR_LOAD, NVG, group_target, + group_priority, delta); + g_assert_cmpuint(count, ==, group_count[group_priority]); + group_count[group_priority]--; + count = nvpg_backlog_op(qts, READ_x, NVG, group_target, group_priority, 0); + g_assert_cmpuint(count, ==, group_count[group_priority]); +} diff --git a/tests/qtest/pnv-xive2-test.c b/tests/qtest/pnv-xive2-test.c index a4d06550ee6a..a0e9f19313ca 100644 --- a/tests/qtest/pnv-xive2-test.c +++ b/tests/qtest/pnv-xive2-test.c @@ -493,6 +493,9 @@ static void test_xive(void) reset_state(qts); test_flush_sync_inject(qts); + reset_state(qts); + test_nvpg_bar(qts); + qtest_quit(qts); } From 1a3cc1209b4ca541e3b8f4fd360704e071e1e7bc Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:53:52 +1000 Subject: [PATCH 0911/1179] ppc/xive2: Support crowd-matching when looking for target XIVE crowd sizes are encoded into a 2-bit field as follows: 0: 0b00 2: 0b01 4: 0b10 16: 0b11 A crowd size of 8 is not supported. If an END is defined with the 'crowd' bit set, then a target can be running on different blocks. It means that some bits from the block VP are masked when looking for a match. It is similar to groups, but on the block instead of the VP index. Most of the changes are due to passing the extra argument 'crowd' all the way to the function checking for matches. Signed-off-by: Frederic Barrat Signed-off-by: Glenn Miles Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/pnv_xive.c | 10 +++--- hw/intc/pnv_xive2.c | 12 +++---- hw/intc/spapr_xive.c | 8 ++--- hw/intc/xive.c | 45 +++++++++++++++++++++---- hw/intc/xive2.c | 76 +++++++++++++++++++++++++++++++++--------- hw/ppc/pnv.c | 15 +++++---- hw/ppc/spapr.c | 7 ++-- include/hw/ppc/xive.h | 10 +++--- include/hw/ppc/xive2.h | 3 +- 9 files changed, 134 insertions(+), 52 deletions(-) diff --git a/hw/intc/pnv_xive.c b/hw/intc/pnv_xive.c index b755ddf0ffab..ccbe95a58ea0 100644 --- a/hw/intc/pnv_xive.c +++ b/hw/intc/pnv_xive.c @@ -1,10 +1,9 @@ /* * QEMU PowerPC XIVE interrupt controller model * - * Copyright (c) 2017-2019, IBM Corporation. + * Copyright (c) 2017-2024, IBM Corporation. * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" @@ -473,7 +472,7 @@ static bool pnv_xive_is_cpu_enabled(PnvXive *xive, PowerPCCPU *cpu) static int pnv_xive_match_nvt(XivePresenter *xptr, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match) { PnvXive *xive = PNV_XIVE(xptr); @@ -500,7 +499,8 @@ static int pnv_xive_match_nvt(XivePresenter *xptr, uint8_t format, * Check the thread context CAM lines and record matches. */ ring = xive_presenter_tctx_match(xptr, tctx, format, nvt_blk, - nvt_idx, cam_ignore, logic_serv); + nvt_idx, cam_ignore, + logic_serv); /* * Save the context and follow on to catch duplicates, that we * don't support yet. diff --git a/hw/intc/pnv_xive2.c b/hw/intc/pnv_xive2.c index c55d596e488d..0b81dad6ba59 100644 --- a/hw/intc/pnv_xive2.c +++ b/hw/intc/pnv_xive2.c @@ -624,7 +624,7 @@ static bool pnv_xive2_is_cpu_enabled(PnvXive2 *xive, PowerPCCPU *cpu) static int pnv_xive2_match_nvt(XivePresenter *xptr, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match) { PnvXive2 *xive = PNV_XIVE2(xptr); @@ -655,8 +655,8 @@ static int pnv_xive2_match_nvt(XivePresenter *xptr, uint8_t format, logic_serv); } else { ring = xive2_presenter_tctx_match(xptr, tctx, format, nvt_blk, - nvt_idx, cam_ignore, - logic_serv); + nvt_idx, crowd, cam_ignore, + logic_serv); } if (ring != -1) { @@ -707,7 +707,7 @@ static uint32_t pnv_xive2_presenter_get_config(XivePresenter *xptr) static int pnv_xive2_broadcast(XivePresenter *xptr, uint8_t nvt_blk, uint32_t nvt_idx, - uint8_t priority) + bool crowd, bool ignore, uint8_t priority) { PnvXive2 *xive = PNV_XIVE2(xptr); PnvChip *chip = xive->chip; @@ -732,10 +732,10 @@ static int pnv_xive2_broadcast(XivePresenter *xptr, if (gen1_tima_os) { ring = xive_presenter_tctx_match(xptr, tctx, 0, nvt_blk, - nvt_idx, true, 0); + nvt_idx, ignore, 0); } else { ring = xive2_presenter_tctx_match(xptr, tctx, 0, nvt_blk, - nvt_idx, true, 0); + nvt_idx, crowd, ignore, 0); } if (ring != -1) { diff --git a/hw/intc/spapr_xive.c b/hw/intc/spapr_xive.c index a764c0bb575f..ce734b03ab53 100644 --- a/hw/intc/spapr_xive.c +++ b/hw/intc/spapr_xive.c @@ -1,10 +1,9 @@ /* * QEMU PowerPC sPAPR XIVE interrupt controller model * - * Copyright (c) 2017-2018, IBM Corporation. + * Copyright (c) 2017-2024, IBM Corporation. * - * This code is licensed under the GPL version 2 or later. See the - * COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" @@ -431,7 +430,8 @@ static int spapr_xive_write_nvt(XiveRouter *xrtr, uint8_t nvt_blk, static int spapr_xive_match_nvt(XivePresenter *xptr, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, + uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match) { CPUState *cs; diff --git a/hw/intc/xive.c b/hw/intc/xive.c index 535e59646fcc..c77df2c1f8cc 100644 --- a/hw/intc/xive.c +++ b/hw/intc/xive.c @@ -1665,10 +1665,42 @@ uint32_t xive_get_vpgroup_size(uint32_t nvp_index) return 1 << (ctz32(~nvp_index) + 1); } -static uint8_t xive_get_group_level(uint32_t nvp_index) +static uint8_t xive_get_group_level(bool crowd, bool ignore, + uint32_t nvp_blk, uint32_t nvp_index) { - /* FIXME add crowd encoding */ - return ctz32(~nvp_index) + 1; + uint8_t level; + + if (!ignore) { + g_assert(!crowd); + return 0; + } + + level = (ctz32(~nvp_index) + 1) & 0b1111; + if (crowd) { + uint32_t blk; + + /* crowd level is bit position of first 0 from the right in nvp_blk */ + blk = ctz32(~nvp_blk) + 1; + + /* + * Supported crowd sizes are 2^1, 2^2, and 2^4. 2^3 is not supported. + * HW will encode level 4 as the value 3. See xive2_pgofnext(). + */ + switch (level) { + case 1: + case 2: + break; + case 4: + blk = 3; + break; + default: + g_assert_not_reached(); + } + + /* Crowd level bits reside in upper 2 bits of the 6 bit group level */ + level |= blk << 4; + } + return level; } /* @@ -1740,7 +1772,7 @@ int xive_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, */ bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, bool *precluded) { XiveFabricClass *xfc = XIVE_FABRIC_GET_CLASS(xfb); @@ -1771,7 +1803,7 @@ bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, * a new command to the presenters (the equivalent of the "assign" * power bus command in the documented full notify sequence. */ - count = xfc->match_nvt(xfb, format, nvt_blk, nvt_idx, cam_ignore, + count = xfc->match_nvt(xfb, format, nvt_blk, nvt_idx, crowd, cam_ignore, priority, logic_serv, &match); if (count < 0) { return false; @@ -1779,7 +1811,7 @@ bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, /* handle CPU exception delivery */ if (count) { - group_level = cam_ignore ? xive_get_group_level(nvt_idx) : 0; + group_level = xive_get_group_level(crowd, cam_ignore, nvt_blk, nvt_idx); trace_xive_presenter_notify(nvt_blk, nvt_idx, match.ring, group_level); xive_tctx_pipr_update(match.tctx, match.ring, priority, group_level); } else { @@ -1904,6 +1936,7 @@ void xive_router_end_notify(XiveRouter *xrtr, XiveEAS *eas) } found = xive_presenter_notify(xrtr->xfb, format, nvt_blk, nvt_idx, + false /* crowd */, xive_get_field32(END_W7_F0_IGNORE, end.w7), priority, xive_get_field32(END_W7_F1_LOG_SERVER_ID, end.w7), diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index 34628f292229..5fa8e1b3fbcb 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -1121,13 +1121,40 @@ static bool xive2_vp_match_mask(uint32_t cam1, uint32_t cam2, return (cam1 & vp_mask) == (cam2 & vp_mask); } +static uint8_t xive2_get_vp_block_mask(uint32_t nvt_blk, bool crowd) +{ + uint8_t size, block_mask = 0b1111; + + /* 3 supported crowd sizes: 2, 4, 16 */ + if (crowd) { + size = xive_get_vpgroup_size(nvt_blk); + if (size == 8) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid crowd size of 8n"); + return block_mask; + } + block_mask &= ~(size - 1); + } + return block_mask; +} + +static uint32_t xive2_get_vp_index_mask(uint32_t nvt_index, bool cam_ignore) +{ + uint32_t index_mask = 0xFFFFFF; /* 24 bits */ + + if (cam_ignore) { + index_mask &= ~(xive_get_vpgroup_size(nvt_index) - 1); + } + return index_mask; +} + /* * The thread context register words are in big-endian format. */ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint32_t logic_serv) + bool crowd, bool cam_ignore, + uint32_t logic_serv) { uint32_t cam = xive2_nvp_cam_line(nvt_blk, nvt_idx); uint32_t qw3w2 = xive_tctx_word2(&tctx->regs[TM_QW3_HV_PHYS]); @@ -1135,7 +1162,8 @@ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, uint32_t qw1w2 = xive_tctx_word2(&tctx->regs[TM_QW1_OS]); uint32_t qw0w2 = xive_tctx_word2(&tctx->regs[TM_QW0_USER]); - uint32_t vp_mask = 0xFFFFFFFF; + uint32_t index_mask, vp_mask; + uint8_t block_mask; if (format == 0) { /* @@ -1143,9 +1171,9 @@ int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, * i=1: VP-group notification (bits ignored at the end of the * NVT identifier) */ - if (cam_ignore) { - vp_mask = ~(xive_get_vpgroup_size(nvt_idx) - 1); - } + block_mask = xive2_get_vp_block_mask(nvt_blk, crowd); + index_mask = xive2_get_vp_index_mask(nvt_idx, cam_ignore); + vp_mask = xive2_nvp_cam_line(block_mask, index_mask); /* For VP-group notifications, threads with LGS=0 are excluded */ @@ -1277,6 +1305,12 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, return; } + if (xive2_end_is_crowd(&end) & !xive2_end_is_ignore(&end)) { + qemu_log_mask(LOG_GUEST_ERROR, + "XIVE: invalid END, 'crowd' bit requires 'ignore' bit\n"); + return; + } + if (xive2_end_is_enqueue(&end)) { xive2_end_enqueue(&end, end_data); /* Enqueuing event data modifies the EQ toggle and index */ @@ -1325,7 +1359,7 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, nvp_idx = xive_get_field32(END2_W6_VP_OFFSET, end.w6); found = xive_presenter_notify(xrtr->xfb, format, nvp_blk, nvp_idx, - xive2_end_is_ignore(&end), + xive2_end_is_crowd(&end), xive2_end_is_ignore(&end), priority, xive_get_field32(END2_W7_F1_LOG_SERVER_ID, end.w7), &precluded); @@ -1377,17 +1411,24 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, ipb); xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2); } else { - Xive2Nvgc nvg; + Xive2Nvgc nvgc; uint32_t backlog; + bool crowd; - /* For groups, the per-priority backlog counters are in the NVG */ - if (xive2_router_get_nvgc(xrtr, false, nvp_blk, nvp_idx, &nvg)) { - qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no NVG %x/%x\n", - nvp_blk, nvp_idx); + crowd = xive2_end_is_crowd(&end); + + /* + * For groups and crowds, the per-priority backlog + * counters are stored in the NVG/NVC structures + */ + if (xive2_router_get_nvgc(xrtr, crowd, + nvp_blk, nvp_idx, &nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no %s %x/%x\n", + crowd ? "NVC" : "NVG", nvp_blk, nvp_idx); return; } - if (!xive2_nvgc_is_valid(&nvg)) { + if (!xive2_nvgc_is_valid(&nvgc)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVG %x/%x is invalid\n", nvp_blk, nvp_idx); return; @@ -1399,13 +1440,16 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, * incremented. broadcast will set the LSMFB field of the TIMA of * relevant threads so that they know an interrupt is pending. */ - backlog = xive2_nvgc_get_backlog(&nvg, priority) + 1; - xive2_nvgc_set_backlog(&nvg, priority, backlog); - xive2_router_write_nvgc(xrtr, false, nvp_blk, nvp_idx, &nvg); + backlog = xive2_nvgc_get_backlog(&nvgc, priority) + 1; + xive2_nvgc_set_backlog(&nvgc, priority, backlog); + xive2_router_write_nvgc(xrtr, crowd, nvp_blk, nvp_idx, &nvgc); if (backlog == 1) { XiveFabricClass *xfc = XIVE_FABRIC_GET_CLASS(xrtr->xfb); - xfc->broadcast(xrtr->xfb, nvp_blk, nvp_idx, priority); + xfc->broadcast(xrtr->xfb, nvp_blk, nvp_idx, + xive2_end_is_crowd(&end), + xive2_end_is_ignore(&end), + priority); if (!xive2_end_is_precluded_escalation(&end)) { /* diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index af836c1388eb..d60574d60199 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -2608,7 +2608,7 @@ static void pnv_pic_print_info(InterruptStatsProvider *obj, GString *buf) static int pnv_match_nvt(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match) { @@ -2622,8 +2622,8 @@ static int pnv_match_nvt(XiveFabric *xfb, uint8_t format, XivePresenterClass *xpc = XIVE_PRESENTER_GET_CLASS(xptr); int count; - count = xpc->match_nvt(xptr, format, nvt_blk, nvt_idx, cam_ignore, - priority, logic_serv, match); + count = xpc->match_nvt(xptr, format, nvt_blk, nvt_idx, crowd, + cam_ignore, priority, logic_serv, match); if (count < 0) { return count; @@ -2637,7 +2637,7 @@ static int pnv_match_nvt(XiveFabric *xfb, uint8_t format, static int pnv10_xive_match_nvt(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match) { @@ -2651,8 +2651,8 @@ static int pnv10_xive_match_nvt(XiveFabric *xfb, uint8_t format, XivePresenterClass *xpc = XIVE_PRESENTER_GET_CLASS(xptr); int count; - count = xpc->match_nvt(xptr, format, nvt_blk, nvt_idx, cam_ignore, - priority, logic_serv, match); + count = xpc->match_nvt(xptr, format, nvt_blk, nvt_idx, crowd, + cam_ignore, priority, logic_serv, match); if (count < 0) { return count; @@ -2666,6 +2666,7 @@ static int pnv10_xive_match_nvt(XiveFabric *xfb, uint8_t format, static int pnv10_xive_broadcast(XiveFabric *xfb, uint8_t nvt_blk, uint32_t nvt_idx, + bool crowd, bool cam_ignore, uint8_t priority) { PnvMachineState *pnv = PNV_MACHINE(xfb); @@ -2676,7 +2677,7 @@ static int pnv10_xive_broadcast(XiveFabric *xfb, XivePresenter *xptr = XIVE_PRESENTER(&chip10->xive); XivePresenterClass *xpc = XIVE_PRESENTER_GET_CLASS(xptr); - xpc->broadcast(xptr, nvt_blk, nvt_idx, priority); + xpc->broadcast(xptr, nvt_blk, nvt_idx, crowd, cam_ignore, priority); } return 0; } diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index c15340a58d81..c7cf04e06386 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -4,6 +4,9 @@ * Copyright (c) 2004-2007 Fabrice Bellard * Copyright (c) 2007 Jocelyn Mayer * Copyright (c) 2010 David Gibson, IBM Corporation. + * Copyright (c) 2010-2024, IBM Corporation.. + * + * SPDX-License-Identifier: GPL-2.0-or-later * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -4436,7 +4439,7 @@ static void spapr_pic_print_info(InterruptStatsProvider *obj, GString *buf) */ static int spapr_match_nvt(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match) { SpaprMachineState *spapr = SPAPR_MACHINE(xfb); @@ -4444,7 +4447,7 @@ static int spapr_match_nvt(XiveFabric *xfb, uint8_t format, XivePresenterClass *xpc = XIVE_PRESENTER_GET_CLASS(xptr); int count; - count = xpc->match_nvt(xptr, format, nvt_blk, nvt_idx, cam_ignore, + count = xpc->match_nvt(xptr, format, nvt_blk, nvt_idx, crowd, cam_ignore, priority, logic_serv, match); if (count < 0) { return count; diff --git a/include/hw/ppc/xive.h b/include/hw/ppc/xive.h index 6a410c6f1a54..538f43868172 100644 --- a/include/hw/ppc/xive.h +++ b/include/hw/ppc/xive.h @@ -438,13 +438,13 @@ struct XivePresenterClass { InterfaceClass parent; int (*match_nvt)(XivePresenter *xptr, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match); bool (*in_kernel)(const XivePresenter *xptr); uint32_t (*get_config)(XivePresenter *xptr); int (*broadcast)(XivePresenter *xptr, uint8_t nvt_blk, uint32_t nvt_idx, - uint8_t priority); + bool crowd, bool cam_ignore, uint8_t priority); }; int xive_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, @@ -453,7 +453,7 @@ int xive_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, bool cam_ignore, uint32_t logic_serv); bool xive_presenter_notify(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, bool *precluded); uint32_t xive_get_vpgroup_size(uint32_t nvp_index); @@ -473,10 +473,10 @@ struct XiveFabricClass { InterfaceClass parent; int (*match_nvt)(XiveFabric *xfb, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint8_t priority, + bool crowd, bool cam_ignore, uint8_t priority, uint32_t logic_serv, XiveTCTXMatch *match); int (*broadcast)(XiveFabric *xfb, uint8_t nvt_blk, uint32_t nvt_idx, - uint8_t priority); + bool crowd, bool cam_ignore, uint8_t priority); }; /* diff --git a/include/hw/ppc/xive2.h b/include/hw/ppc/xive2.h index c07e23e1d36c..8cdf8191742e 100644 --- a/include/hw/ppc/xive2.h +++ b/include/hw/ppc/xive2.h @@ -88,7 +88,8 @@ void xive2_router_notify(XiveNotifier *xn, uint32_t lisn, bool pq_checked); int xive2_presenter_tctx_match(XivePresenter *xptr, XiveTCTX *tctx, uint8_t format, uint8_t nvt_blk, uint32_t nvt_idx, - bool cam_ignore, uint32_t logic_serv); + bool crowd, bool cam_ignore, + uint32_t logic_serv); uint64_t xive2_presenter_nvp_backlog_op(XivePresenter *xptr, uint8_t blk, uint32_t idx, From 7988ac082603164937becea2910ea4fec8e9d3de Mon Sep 17 00:00:00 2001 From: Glenn Miles Date: Tue, 11 Mar 2025 12:26:33 +1000 Subject: [PATCH 0912/1179] pnv/xive2: Rename nvp_ to nvx_ if they can refer to NVP or NVGC The blk/index in some paths may refer to an NVP or an NVGC. When it is not known ahead of time, use the nvx_ prefix to prevent confusion. [npiggin: split out of larger fix patch and reworded] Signed-off-by: Glenn Miles Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/xive2.c | 56 ++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index 5fa8e1b3fbcb..e925307d0f2e 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -226,8 +226,8 @@ void xive2_end_pic_print_info(Xive2End *end, uint32_t end_idx, GString *buf) uint32_t qsize = xive_get_field32(END2_W3_QSIZE, end->w3); uint32_t qentries = 1 << (qsize + 10); - uint32_t nvp_blk = xive_get_field32(END2_W6_VP_BLOCK, end->w6); - uint32_t nvp_idx = xive_get_field32(END2_W6_VP_OFFSET, end->w6); + uint32_t nvx_blk = xive_get_field32(END2_W6_VP_BLOCK, end->w6); + uint32_t nvx_idx = xive_get_field32(END2_W6_VP_OFFSET, end->w6); uint8_t priority = xive_get_field32(END2_W7_F0_PRIORITY, end->w7); uint8_t pq; @@ -256,7 +256,7 @@ void xive2_end_pic_print_info(Xive2End *end, uint32_t end_idx, GString *buf) xive2_end_is_firmware2(end) ? 'F' : '-', xive2_end_is_ignore(end) ? 'i' : '-', xive2_end_is_crowd(end) ? 'c' : '-', - priority, nvp_blk, nvp_idx); + priority, nvx_blk, nvx_idx); if (qaddr_base) { g_string_append_printf(buf, " eq:@%08"PRIx64"% 6d/%5d ^%d", @@ -372,7 +372,7 @@ static void xive2_end_enqueue(Xive2End *end, uint32_t data) * level of pending group interrupts. */ static uint8_t xive2_presenter_backlog_scan(XivePresenter *xptr, - uint8_t nvp_blk, uint32_t nvp_idx, + uint8_t nvx_blk, uint32_t nvx_idx, uint8_t first_group, uint8_t *out_level) { @@ -387,19 +387,19 @@ static uint8_t xive2_presenter_backlog_scan(XivePresenter *xptr, while (current_level) { mask = (1 << current_level) - 1; - nvgc_idx = nvp_idx & ~mask; + nvgc_idx = nvx_idx & ~mask; nvgc_idx |= mask >> 1; qemu_log("fxb %s checking backlog for prio %d group idx %x\n", __func__, prio, nvgc_idx); - if (xive2_router_get_nvgc(xrtr, false, nvp_blk, nvgc_idx, &nvgc)) { + if (xive2_router_get_nvgc(xrtr, false, nvx_blk, nvgc_idx, &nvgc)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVG %x/%x\n", - nvp_blk, nvgc_idx); + nvx_blk, nvgc_idx); return 0xFF; } if (!xive2_nvgc_is_valid(&nvgc)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", - nvp_blk, nvgc_idx); + nvx_blk, nvgc_idx); return 0xFF; } @@ -415,7 +415,7 @@ static uint8_t xive2_presenter_backlog_scan(XivePresenter *xptr, } static void xive2_presenter_backlog_decr(XivePresenter *xptr, - uint8_t nvp_blk, uint32_t nvp_idx, + uint8_t nvx_blk, uint32_t nvx_idx, uint8_t group_prio, uint8_t group_level) { @@ -425,17 +425,17 @@ static void xive2_presenter_backlog_decr(XivePresenter *xptr, group_level &= 0xF; mask = (1 << group_level) - 1; - nvgc_idx = nvp_idx & ~mask; + nvgc_idx = nvx_idx & ~mask; nvgc_idx |= mask >> 1; - if (xive2_router_get_nvgc(xrtr, false, nvp_blk, nvgc_idx, &nvgc)) { + if (xive2_router_get_nvgc(xrtr, false, nvx_blk, nvgc_idx, &nvgc)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVG %x/%x\n", - nvp_blk, nvgc_idx); + nvx_blk, nvgc_idx); return; } if (!xive2_nvgc_is_valid(&nvgc)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", - nvp_blk, nvgc_idx); + nvx_blk, nvgc_idx); return; } count = xive2_nvgc_get_backlog(&nvgc, group_prio); @@ -443,7 +443,7 @@ static void xive2_presenter_backlog_decr(XivePresenter *xptr, return; } xive2_nvgc_set_backlog(&nvgc, group_prio, count - 1); - xive2_router_write_nvgc(xrtr, false, nvp_blk, nvgc_idx, &nvgc); + xive2_router_write_nvgc(xrtr, false, nvx_blk, nvgc_idx, &nvgc); } /* @@ -1289,8 +1289,8 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, uint8_t priority; uint8_t format; bool found, precluded; - uint8_t nvp_blk; - uint32_t nvp_idx; + uint8_t nvx_blk; + uint32_t nvx_idx; /* END cache lookup */ if (xive2_router_get_end(xrtr, end_blk, end_idx, &end)) { @@ -1355,10 +1355,10 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, /* * Follows IVPE notification */ - nvp_blk = xive_get_field32(END2_W6_VP_BLOCK, end.w6); - nvp_idx = xive_get_field32(END2_W6_VP_OFFSET, end.w6); + nvx_blk = xive_get_field32(END2_W6_VP_BLOCK, end.w6); + nvx_idx = xive_get_field32(END2_W6_VP_OFFSET, end.w6); - found = xive_presenter_notify(xrtr->xfb, format, nvp_blk, nvp_idx, + found = xive_presenter_notify(xrtr->xfb, format, nvx_blk, nvx_idx, xive2_end_is_crowd(&end), xive2_end_is_ignore(&end), priority, xive_get_field32(END2_W7_F1_LOG_SERVER_ID, end.w7), @@ -1389,15 +1389,15 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, Xive2Nvp nvp; /* NVP cache lookup */ - if (xive2_router_get_nvp(xrtr, nvp_blk, nvp_idx, &nvp)) { + if (xive2_router_get_nvp(xrtr, nvx_blk, nvx_idx, &nvp)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no NVP %x/%x\n", - nvp_blk, nvp_idx); + nvx_blk, nvx_idx); return; } if (!xive2_nvp_is_valid(&nvp)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVP %x/%x is invalid\n", - nvp_blk, nvp_idx); + nvx_blk, nvx_idx); return; } @@ -1409,7 +1409,7 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, ipb = xive_get_field32(NVP2_W2_IPB, nvp.w2) | xive_priority_to_ipb(priority); nvp.w2 = xive_set_field32(NVP2_W2_IPB, nvp.w2, ipb); - xive2_router_write_nvp(xrtr, nvp_blk, nvp_idx, &nvp, 2); + xive2_router_write_nvp(xrtr, nvx_blk, nvx_idx, &nvp, 2); } else { Xive2Nvgc nvgc; uint32_t backlog; @@ -1422,15 +1422,15 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, * counters are stored in the NVG/NVC structures */ if (xive2_router_get_nvgc(xrtr, crowd, - nvp_blk, nvp_idx, &nvgc)) { + nvx_blk, nvx_idx, &nvgc)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: no %s %x/%x\n", - crowd ? "NVC" : "NVG", nvp_blk, nvp_idx); + crowd ? "NVC" : "NVG", nvx_blk, nvx_idx); return; } if (!xive2_nvgc_is_valid(&nvgc)) { qemu_log_mask(LOG_GUEST_ERROR, "XIVE: NVG %x/%x is invalid\n", - nvp_blk, nvp_idx); + nvx_blk, nvx_idx); return; } @@ -1442,11 +1442,11 @@ static void xive2_router_end_notify(Xive2Router *xrtr, uint8_t end_blk, */ backlog = xive2_nvgc_get_backlog(&nvgc, priority) + 1; xive2_nvgc_set_backlog(&nvgc, priority, backlog); - xive2_router_write_nvgc(xrtr, crowd, nvp_blk, nvp_idx, &nvgc); + xive2_router_write_nvgc(xrtr, crowd, nvx_blk, nvx_idx, &nvgc); if (backlog == 1) { XiveFabricClass *xfc = XIVE_FABRIC_GET_CLASS(xrtr->xfb); - xfc->broadcast(xrtr->xfb, nvp_blk, nvp_idx, + xfc->broadcast(xrtr->xfb, nvx_blk, nvx_idx, xive2_end_is_crowd(&end), xive2_end_is_ignore(&end), priority); From 0b266ae15e8c00bd8e72a55bc0bfc1a89c20bc34 Mon Sep 17 00:00:00 2001 From: Frederic Barrat Date: Tue, 11 Mar 2025 11:53:53 +1000 Subject: [PATCH 0913/1179] ppc/xive2: Check crowd backlog when scanning group backlog When processing a backlog scan for group interrupts, also take into account crowd interrupts. Signed-off-by: Frederic Barrat Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- hw/intc/xive2.c | 82 +++++++++++++++++++++++++------------ include/hw/ppc/xive2_regs.h | 4 ++ 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/hw/intc/xive2.c b/hw/intc/xive2.c index e925307d0f2e..f8ef61548782 100644 --- a/hw/intc/xive2.c +++ b/hw/intc/xive2.c @@ -367,6 +367,35 @@ static void xive2_end_enqueue(Xive2End *end, uint32_t data) end->w1 = xive_set_field32(END2_W1_PAGE_OFF, end->w1, qindex); } +static void xive2_pgofnext(uint8_t *nvgc_blk, uint32_t *nvgc_idx, + uint8_t next_level) +{ + uint32_t mask, next_idx; + uint8_t next_blk; + + /* + * Adjust the block and index of a VP for the next group/crowd + * size (PGofFirst/PGofNext field in the NVP and NVGC structures). + * + * The 6-bit group level is split into a 2-bit crowd and 4-bit + * group levels. Encoding is similar. However, we don't support + * crowd size of 8. So a crowd level of 0b11 is bumped to a crowd + * size of 16. + */ + next_blk = NVx_CROWD_LVL(next_level); + if (next_blk == 3) { + next_blk = 4; + } + mask = (1 << next_blk) - 1; + *nvgc_blk &= ~mask; + *nvgc_blk |= mask >> 1; + + next_idx = NVx_GROUP_LVL(next_level); + mask = (1 << next_idx) - 1; + *nvgc_idx &= ~mask; + *nvgc_idx |= mask >> 1; +} + /* * Scan the group chain and return the highest priority and group * level of pending group interrupts. @@ -377,29 +406,28 @@ static uint8_t xive2_presenter_backlog_scan(XivePresenter *xptr, uint8_t *out_level) { Xive2Router *xrtr = XIVE2_ROUTER(xptr); - uint32_t nvgc_idx, mask; + uint32_t nvgc_idx; uint32_t current_level, count; - uint8_t prio; + uint8_t nvgc_blk, prio; Xive2Nvgc nvgc; for (prio = 0; prio <= XIVE_PRIORITY_MAX; prio++) { - current_level = first_group & 0xF; + current_level = first_group & 0x3F; + nvgc_blk = nvx_blk; + nvgc_idx = nvx_idx; while (current_level) { - mask = (1 << current_level) - 1; - nvgc_idx = nvx_idx & ~mask; - nvgc_idx |= mask >> 1; - qemu_log("fxb %s checking backlog for prio %d group idx %x\n", - __func__, prio, nvgc_idx); - - if (xive2_router_get_nvgc(xrtr, false, nvx_blk, nvgc_idx, &nvgc)) { - qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVG %x/%x\n", - nvx_blk, nvgc_idx); + xive2_pgofnext(&nvgc_blk, &nvgc_idx, current_level); + + if (xive2_router_get_nvgc(xrtr, NVx_CROWD_LVL(current_level), + nvgc_blk, nvgc_idx, &nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVGC %x/%x\n", + nvgc_blk, nvgc_idx); return 0xFF; } if (!xive2_nvgc_is_valid(&nvgc)) { - qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", - nvx_blk, nvgc_idx); + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVGC %x/%x\n", + nvgc_blk, nvgc_idx); return 0xFF; } @@ -408,7 +436,7 @@ static uint8_t xive2_presenter_backlog_scan(XivePresenter *xptr, *out_level = current_level; return prio; } - current_level = xive_get_field32(NVGC2_W0_PGONEXT, nvgc.w0) & 0xF; + current_level = xive_get_field32(NVGC2_W0_PGONEXT, nvgc.w0) & 0x3F; } } return 0xFF; @@ -420,22 +448,23 @@ static void xive2_presenter_backlog_decr(XivePresenter *xptr, uint8_t group_level) { Xive2Router *xrtr = XIVE2_ROUTER(xptr); - uint32_t nvgc_idx, mask, count; + uint32_t nvgc_idx, count; + uint8_t nvgc_blk; Xive2Nvgc nvgc; - group_level &= 0xF; - mask = (1 << group_level) - 1; - nvgc_idx = nvx_idx & ~mask; - nvgc_idx |= mask >> 1; + nvgc_blk = nvx_blk; + nvgc_idx = nvx_idx; + xive2_pgofnext(&nvgc_blk, &nvgc_idx, group_level); - if (xive2_router_get_nvgc(xrtr, false, nvx_blk, nvgc_idx, &nvgc)) { - qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVG %x/%x\n", - nvx_blk, nvgc_idx); + if (xive2_router_get_nvgc(xrtr, NVx_CROWD_LVL(group_level), + nvgc_blk, nvgc_idx, &nvgc)) { + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: No NVGC %x/%x\n", + nvgc_blk, nvgc_idx); return; } if (!xive2_nvgc_is_valid(&nvgc)) { - qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVG %x/%x\n", - nvx_blk, nvgc_idx); + qemu_log_mask(LOG_GUEST_ERROR, "XIVE: Invalid NVGC %x/%x\n", + nvgc_blk, nvgc_idx); return; } count = xive2_nvgc_get_backlog(&nvgc, group_prio); @@ -443,7 +472,8 @@ static void xive2_presenter_backlog_decr(XivePresenter *xptr, return; } xive2_nvgc_set_backlog(&nvgc, group_prio, count - 1); - xive2_router_write_nvgc(xrtr, false, nvx_blk, nvgc_idx, &nvgc); + xive2_router_write_nvgc(xrtr, NVx_CROWD_LVL(group_level), + nvgc_blk, nvgc_idx, &nvgc); } /* diff --git a/include/hw/ppc/xive2_regs.h b/include/hw/ppc/xive2_regs.h index 9bcf7a8a6f29..b11395c56350 100644 --- a/include/hw/ppc/xive2_regs.h +++ b/include/hw/ppc/xive2_regs.h @@ -236,4 +236,8 @@ void xive2_nvgc_pic_print_info(Xive2Nvgc *nvgc, uint32_t nvgc_idx, #define NVx_BACKLOG_OP PPC_BITMASK(52, 53) #define NVx_BACKLOG_PRIO PPC_BITMASK(57, 59) +/* split the 6-bit crowd/group level */ +#define NVx_CROWD_LVL(level) ((level >> 4) & 0b11) +#define NVx_GROUP_LVL(level) (level & 0b1111) + #endif /* PPC_XIVE2_REGS_H */ From c4b50387b41fa86217743fe5b182eb5730e1a9c8 Mon Sep 17 00:00:00 2001 From: Glenn Miles Date: Tue, 11 Mar 2025 13:36:14 +1000 Subject: [PATCH 0914/1179] qtest/xive: Change printf to g_test_message Change all printf() in pnv-xive2-* qtests to g_test_message() [npiggin: split from pool qtest] Signed-off-by: Glenn Miles Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- tests/qtest/pnv-xive2-flush-sync.c | 6 +++--- tests/qtest/pnv-xive2-nvpg_bar.c | 7 +++---- tests/qtest/pnv-xive2-test.c | 22 ++++++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/qtest/pnv-xive2-flush-sync.c b/tests/qtest/pnv-xive2-flush-sync.c index 3b32446adba2..142826bad0f4 100644 --- a/tests/qtest/pnv-xive2-flush-sync.c +++ b/tests/qtest/pnv-xive2-flush-sync.c @@ -178,14 +178,14 @@ void test_flush_sync_inject(QTestState *qts) int test_nr; uint8_t byte; - printf("# ============================================================\n"); - printf("# Starting cache flush/queue sync injection tests...\n"); + g_test_message("========================================================="); + g_test_message("Starting cache flush/queue sync injection tests..."); for (test_nr = 0; test_nr < sizeof(xive_inject_tests); test_nr++) { int op_type = xive_inject_tests[test_nr]; - printf("# Running test %d\n", test_nr); + g_test_message("Running test %d", test_nr); /* start with status byte set to 0 */ clr_sync(qts, src_pir, ic_topo_id, op_type); diff --git a/tests/qtest/pnv-xive2-nvpg_bar.c b/tests/qtest/pnv-xive2-nvpg_bar.c index 028512bddc83..6ac8d36c821e 100644 --- a/tests/qtest/pnv-xive2-nvpg_bar.c +++ b/tests/qtest/pnv-xive2-nvpg_bar.c @@ -4,8 +4,7 @@ * * Copyright (c) 2024, IBM Corporation. * - * This work is licensed under the terms of the GNU GPL, version 2 or - * later. See the COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "libqtest.h" @@ -78,8 +77,8 @@ void test_nvpg_bar(QTestState *qts) uint32_t count, delta; uint8_t i; - printf("# ============================================================\n"); - printf("# Testing NVPG BAR operations\n"); + g_test_message("========================================================="); + g_test_message("Testing NVPG BAR operations"); set_nvg(qts, group_target, 0); set_nvp(qts, nvp_target, 0x04); diff --git a/tests/qtest/pnv-xive2-test.c b/tests/qtest/pnv-xive2-test.c index a0e9f19313ca..7e7b1e79c03a 100644 --- a/tests/qtest/pnv-xive2-test.c +++ b/tests/qtest/pnv-xive2-test.c @@ -4,6 +4,7 @@ * - Test 'Pull Thread Context to Odd Thread Reporting Line' * - Test irq to hardware group * - Test irq to hardware group going through backlog + * - Test irq to pool thread * * Copyright (c) 2024, IBM Corporation. * @@ -220,8 +221,8 @@ static void test_hw_irq(QTestState *qts) uint16_t reg16; uint8_t pq, nsr, cppr; - printf("# ============================================================\n"); - printf("# Testing irq %d to hardware thread %d\n", irq, target_pir); + g_test_message("========================================================="); + g_test_message("Testing irq %d to hardware thread %d", irq, target_pir); /* irq config */ set_eas(qts, irq, end_index, irq_data); @@ -278,8 +279,9 @@ static void test_pull_thread_ctx_to_odd_thread_cl(QTestState *qts) uint32_t cl_word; uint32_t word2; - printf("# ============================================================\n"); - printf("# Testing 'Pull Thread Context to Odd Thread Reporting Line'\n"); + g_test_message("========================================================="); + g_test_message("Testing 'Pull Thread Context to Odd Thread Reporting " \ + "Line'"); /* clear odd cache line prior to pull operation */ memset(cl_pair, 0, sizeof(cl_pair)); @@ -330,8 +332,8 @@ static void test_hw_group_irq(QTestState *qts) uint16_t reg16; uint8_t pq, nsr, cppr; - printf("# ============================================================\n"); - printf("# Testing irq %d to hardware group of size 4\n", irq); + g_test_message("========================================================="); + g_test_message("Testing irq %d to hardware group of size 4", irq); /* irq config */ set_eas(qts, irq, end_index, irq_data); @@ -395,10 +397,10 @@ static void test_hw_group_irq_backlog(QTestState *qts) uint16_t reg16; uint8_t pq, nsr, cppr, lsmfb, i; - printf("# ============================================================\n"); - printf("# Testing irq %d to hardware group of size 4 going through " \ - "backlog\n", - irq); + g_test_message("========================================================="); + g_test_message("Testing irq %d to hardware group of size 4 going " \ + "through backlog", + irq); /* * set current priority of all threads in the group to something From ffc2cabeb536bc595543845c5fe6147d4f9718d3 Mon Sep 17 00:00:00 2001 From: Glenn Miles Date: Tue, 11 Mar 2025 11:55:00 +1000 Subject: [PATCH 0915/1179] qtest/xive: Add test of pool interrupts Added new test for pool interrupts. Removed all printfs from pnv-xive2-* qtests. Signed-off-by: Glenn Miles Signed-off-by: Michael Kowal Reviewed-by: Nicholas Piggin Signed-off-by: Nicholas Piggin --- tests/qtest/pnv-xive2-test.c | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/qtest/pnv-xive2-test.c b/tests/qtest/pnv-xive2-test.c index 7e7b1e79c03a..5313d4ef18b7 100644 --- a/tests/qtest/pnv-xive2-test.c +++ b/tests/qtest/pnv-xive2-test.c @@ -267,6 +267,79 @@ static void test_hw_irq(QTestState *qts) g_assert_cmphex(cppr, ==, 0xFF); } +static void test_pool_irq(QTestState *qts) +{ + uint32_t irq = 2; + uint32_t irq_data = 0x600d0d06; + uint32_t end_index = 5; + uint32_t target_pir = 1; + uint32_t target_nvp = 0x100 + target_pir; + uint8_t priority = 5; + uint32_t reg32; + uint16_t reg16; + uint8_t pq, nsr, cppr, ipb; + + g_test_message("========================================================="); + g_test_message("Testing irq %d to pool thread %d", irq, target_pir); + + /* irq config */ + set_eas(qts, irq, end_index, irq_data); + set_end(qts, end_index, target_nvp, priority, false /* group */); + + /* enable and trigger irq */ + get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_SET_PQ_00); + set_esb(qts, irq, XIVE_TRIGGER_PAGE, 0, 0); + + /* check irq is raised on cpu */ + pq = get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_GET); + g_assert_cmpuint(pq, ==, XIVE_ESB_PENDING); + + /* check TIMA values in the PHYS ring (shared by POOL ring) */ + reg32 = get_tima32(qts, target_pir, TM_QW3_HV_PHYS + TM_WORD0); + nsr = reg32 >> 24; + cppr = (reg32 >> 16) & 0xFF; + g_assert_cmphex(nsr, ==, 0x40); + g_assert_cmphex(cppr, ==, 0xFF); + + /* check TIMA values in the POOL ring */ + reg32 = get_tima32(qts, target_pir, TM_QW2_HV_POOL + TM_WORD0); + nsr = reg32 >> 24; + cppr = (reg32 >> 16) & 0xFF; + ipb = (reg32 >> 8) & 0xFF; + g_assert_cmphex(nsr, ==, 0); + g_assert_cmphex(cppr, ==, 0); + g_assert_cmphex(ipb, ==, 0x80 >> priority); + + /* ack the irq */ + reg16 = get_tima16(qts, target_pir, TM_SPC_ACK_HV_REG); + nsr = reg16 >> 8; + cppr = reg16 & 0xFF; + g_assert_cmphex(nsr, ==, 0x40); + g_assert_cmphex(cppr, ==, priority); + + /* check irq data is what was configured */ + reg32 = qtest_readl(qts, xive_get_queue_addr(end_index)); + g_assert_cmphex((reg32 & 0x7fffffff), ==, (irq_data & 0x7fffffff)); + + /* check IPB is cleared in the POOL ring */ + reg32 = get_tima32(qts, target_pir, TM_QW2_HV_POOL + TM_WORD0); + ipb = (reg32 >> 8) & 0xFF; + g_assert_cmphex(ipb, ==, 0); + + /* End Of Interrupt */ + set_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_STORE_EOI, 0); + pq = get_esb(qts, irq, XIVE_EOI_PAGE, XIVE_ESB_GET); + g_assert_cmpuint(pq, ==, XIVE_ESB_RESET); + + /* reset CPPR */ + set_tima8(qts, target_pir, TM_QW3_HV_PHYS + TM_CPPR, 0xFF); + reg32 = get_tima32(qts, target_pir, TM_QW3_HV_PHYS + TM_WORD0); + nsr = reg32 >> 24; + cppr = (reg32 >> 16) & 0xFF; + g_assert_cmphex(nsr, ==, 0x00); + g_assert_cmphex(cppr, ==, 0xFF); +} + #define XIVE_ODD_CL 0x80 static void test_pull_thread_ctx_to_odd_thread_cl(QTestState *qts) { @@ -486,6 +559,9 @@ static void test_xive(void) /* omit reset_state here and use settings from test_hw_irq */ test_pull_thread_ctx_to_odd_thread_cl(qts); + reset_state(qts); + test_pool_irq(qts); + reset_state(qts); test_hw_group_irq(qts); From 17befecda85d585c5b0186af3c4e74fb8b82cbce Mon Sep 17 00:00:00 2001 From: Chalapathi V Date: Mon, 3 Mar 2025 08:13:25 -0600 Subject: [PATCH 0916/1179] hw/ssi/pnv_spi: Replace PnvXferBuffer with Fifo8 structure In PnvXferBuffer dynamically allocating and freeing is a process overhead. Hence used an existing Fifo8 buffer with capacity of 16 bytes. Signed-off-by: Chalapathi V Message-ID: <20250303141328.23991-2-chalapathi.v@linux.ibm.com> Signed-off-by: Nicholas Piggin --- hw/ssi/pnv_spi.c | 264 ++++++++++++++++----------------------- include/hw/ssi/pnv_spi.h | 3 + 2 files changed, 108 insertions(+), 159 deletions(-) diff --git a/hw/ssi/pnv_spi.c b/hw/ssi/pnv_spi.c index 15e25bd1be35..388b42515763 100644 --- a/hw/ssi/pnv_spi.c +++ b/hw/ssi/pnv_spi.c @@ -19,6 +19,7 @@ #define PNV_SPI_OPCODE_LO_NIBBLE(x) (x & 0x0F) #define PNV_SPI_MASKED_OPCODE(x) (x & 0xF0) +#define PNV_SPI_FIFO_SIZE 16 /* * Macro from include/hw/ppc/fdt.h @@ -35,48 +36,14 @@ } \ } while (0) -/* PnvXferBuffer */ -typedef struct PnvXferBuffer { - - uint32_t len; - uint8_t *data; - -} PnvXferBuffer; - -/* pnv_spi_xfer_buffer_methods */ -static PnvXferBuffer *pnv_spi_xfer_buffer_new(void) -{ - PnvXferBuffer *payload = g_malloc0(sizeof(*payload)); - - return payload; -} - -static void pnv_spi_xfer_buffer_free(PnvXferBuffer *payload) -{ - g_free(payload->data); - g_free(payload); -} - -static uint8_t *pnv_spi_xfer_buffer_write_ptr(PnvXferBuffer *payload, - uint32_t offset, uint32_t length) -{ - if (payload->len < (offset + length)) { - payload->len = offset + length; - payload->data = g_realloc(payload->data, payload->len); - } - return &payload->data[offset]; -} - static bool does_rdr_match(PnvSpi *s) { /* * According to spec, the mask bits that are 0 are compared and the * bits that are 1 are ignored. */ - uint16_t rdr_match_mask = GETFIELD(SPI_MM_RDR_MATCH_MASK, - s->regs[SPI_MM_REG]); - uint16_t rdr_match_val = GETFIELD(SPI_MM_RDR_MATCH_VAL, - s->regs[SPI_MM_REG]); + uint16_t rdr_match_mask = GETFIELD(SPI_MM_RDR_MATCH_MASK, s->regs[SPI_MM_REG]); + uint16_t rdr_match_val = GETFIELD(SPI_MM_RDR_MATCH_VAL, s->regs[SPI_MM_REG]); if ((~rdr_match_mask & rdr_match_val) == ((~rdr_match_mask) & GETFIELD(PPC_BITMASK(48, 63), s->regs[SPI_RCV_DATA_REG]))) { @@ -107,8 +74,8 @@ static uint8_t get_from_offset(PnvSpi *s, uint8_t offset) return byte; } -static uint8_t read_from_frame(PnvSpi *s, uint8_t *read_buf, uint8_t nr_bytes, - uint8_t ecc_count, uint8_t shift_in_count) +static uint8_t read_from_frame(PnvSpi *s, uint8_t nr_bytes, uint8_t ecc_count, + uint8_t shift_in_count) { uint8_t byte; int count = 0; @@ -118,20 +85,24 @@ static uint8_t read_from_frame(PnvSpi *s, uint8_t *read_buf, uint8_t nr_bytes, if ((ecc_count != 0) && (shift_in_count == (PNV_SPI_REG_SIZE + ecc_count))) { shift_in_count = 0; - } else { - byte = read_buf[count]; + } else if (!fifo8_is_empty(&s->rx_fifo)) { + byte = fifo8_pop(&s->rx_fifo); trace_pnv_spi_shift_rx(byte, count); s->regs[SPI_RCV_DATA_REG] = (s->regs[SPI_RCV_DATA_REG] << 8) | byte; + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: Reading empty RX_FIFO\n"); } count++; } /* end of while */ return shift_in_count; } -static void spi_response(PnvSpi *s, int bits, PnvXferBuffer *rsp_payload) +static void spi_response(PnvSpi *s) { uint8_t ecc_count; uint8_t shift_in_count; + uint32_t rx_len; + int i; /* * Processing here must handle: @@ -144,13 +115,14 @@ static void spi_response(PnvSpi *s, int bits, PnvXferBuffer *rsp_payload) * First check that the response payload is the exact same * number of bytes as the request payload was */ - if (rsp_payload->len != (s->N1_bytes + s->N2_bytes)) { + rx_len = fifo8_num_used(&s->rx_fifo); + if (rx_len != (s->N1_bytes + s->N2_bytes)) { qemu_log_mask(LOG_GUEST_ERROR, "Invalid response payload size in " "bytes, expected %d, got %d\n", - (s->N1_bytes + s->N2_bytes), rsp_payload->len); + (s->N1_bytes + s->N2_bytes), rx_len); } else { uint8_t ecc_control; - trace_pnv_spi_rx_received(rsp_payload->len); + trace_pnv_spi_rx_received(rx_len); trace_pnv_spi_log_Ncounts(s->N1_bits, s->N1_bytes, s->N1_tx, s->N1_rx, s->N2_bits, s->N2_bytes, s->N2_tx, s->N2_rx); /* @@ -175,15 +147,23 @@ static void spi_response(PnvSpi *s, int bits, PnvXferBuffer *rsp_payload) /* Handle the N1 portion of the frame first */ if (s->N1_rx != 0) { trace_pnv_spi_rx_read_N1frame(); - shift_in_count = read_from_frame(s, &rsp_payload->data[0], - s->N1_bytes, ecc_count, shift_in_count); + shift_in_count = read_from_frame(s, s->N1_bytes, ecc_count, shift_in_count); } /* Handle the N2 portion of the frame */ if (s->N2_rx != 0) { + /* pop out N1_bytes from rx_fifo if not already */ + if (s->N1_rx == 0) { + for (i = 0; i < s->N1_bytes; i++) { + if (!fifo8_is_empty(&s->rx_fifo)) { + fifo8_pop(&s->rx_fifo); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: Reading empty" + " RX_FIFO\n"); + } + } + } trace_pnv_spi_rx_read_N2frame(); - shift_in_count = read_from_frame(s, - &rsp_payload->data[s->N1_bytes], s->N2_bytes, - ecc_count, shift_in_count); + shift_in_count = read_from_frame(s, s->N2_bytes, ecc_count, shift_in_count); } if ((s->N1_rx + s->N2_rx) > 0) { /* @@ -210,36 +190,41 @@ static void spi_response(PnvSpi *s, int bits, PnvXferBuffer *rsp_payload) } /* end of else */ } /* end of spi_response() */ -static void transfer(PnvSpi *s, PnvXferBuffer *payload) +static void transfer(PnvSpi *s) { - uint32_t tx; - uint32_t rx; - PnvXferBuffer *rsp_payload = NULL; + uint32_t tx, rx, payload_len; + uint8_t rx_byte; - rsp_payload = pnv_spi_xfer_buffer_new(); - if (!rsp_payload) { - return; - } - for (int offset = 0; offset < payload->len; offset += s->transfer_len) { + payload_len = fifo8_num_used(&s->tx_fifo); + for (int offset = 0; offset < payload_len; offset += s->transfer_len) { tx = 0; for (int i = 0; i < s->transfer_len; i++) { - if ((offset + i) >= payload->len) { + if ((offset + i) >= payload_len) { tx <<= 8; + } else if (!fifo8_is_empty(&s->tx_fifo)) { + tx = (tx << 8) | fifo8_pop(&s->tx_fifo); } else { - tx = (tx << 8) | payload->data[offset + i]; + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: TX_FIFO underflow\n"); } } rx = ssi_transfer(s->ssi_bus, tx); for (int i = 0; i < s->transfer_len; i++) { - if ((offset + i) >= payload->len) { + if ((offset + i) >= payload_len) { + break; + } + rx_byte = (rx >> (8 * (s->transfer_len - 1) - i * 8)) & 0xFF; + if (!fifo8_is_full(&s->rx_fifo)) { + fifo8_push(&s->rx_fifo, rx_byte); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: RX_FIFO is full\n"); break; } - *(pnv_spi_xfer_buffer_write_ptr(rsp_payload, rsp_payload->len, 1)) = - (rx >> (8 * (s->transfer_len - 1) - i * 8)) & 0xFF; } } - spi_response(s, s->N1_bits, rsp_payload); - pnv_spi_xfer_buffer_free(rsp_payload); + spi_response(s); + /* Reset fifo for next frame */ + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_fifo); } static inline uint8_t get_seq_index(PnvSpi *s) @@ -310,13 +295,11 @@ static void calculate_N1(PnvSpi *s, uint8_t opcode) * If Forced Implicit mode and count control doesn't * indicate transmit then reset the tx count to 0 */ - if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B2, - s->regs[SPI_CTR_CFG_REG]) == 0) { + if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B2, s->regs[SPI_CTR_CFG_REG]) == 0) { s->N1_tx = 0; } /* If rx count control for N1 is set, load the rx value */ - if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B3, - s->regs[SPI_CTR_CFG_REG]) == 1) { + if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B3, s->regs[SPI_CTR_CFG_REG]) == 1) { s->N1_rx = s->N1_bytes; } } @@ -328,8 +311,7 @@ static void calculate_N1(PnvSpi *s, uint8_t opcode) * cap the size at a max of 64 bits or 72 bits and set the sequencer FSM * error bit. */ - uint8_t ecc_control = GETFIELD(SPI_CLK_CFG_ECC_CTRL, - s->regs[SPI_CLK_CFG_REG]); + uint8_t ecc_control = GETFIELD(SPI_CLK_CFG_ECC_CTRL, s->regs[SPI_CLK_CFG_REG]); if (ecc_control == 0 || ecc_control == 2) { if (s->N1_bytes > (PNV_SPI_REG_SIZE + 1)) { qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size when " @@ -340,8 +322,7 @@ static void calculate_N1(PnvSpi *s, uint8_t opcode) } } else if (s->N1_bytes > PNV_SPI_REG_SIZE) { qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size, " - "bytes = 0x%x, bits = 0x%x\n", - s->N1_bytes, s->N1_bits); + "bytes = 0x%x, bits = 0x%x\n", s->N1_bytes, s->N1_bits); s->N1_bytes = PNV_SPI_REG_SIZE; s->N1_bits = s->N1_bytes * 8; } @@ -350,19 +331,10 @@ static void calculate_N1(PnvSpi *s, uint8_t opcode) /* * Shift_N1 operation handler method */ -static bool operation_shiftn1(PnvSpi *s, uint8_t opcode, - PnvXferBuffer **payload, bool send_n1_alone) +static bool operation_shiftn1(PnvSpi *s, uint8_t opcode, bool send_n1_alone) { uint8_t n1_count; bool stop = false; - - /* - * If there isn't a current payload left over from a stopped sequence - * create a new one. - */ - if (*payload == NULL) { - *payload = pnv_spi_xfer_buffer_new(); - } /* * Use a combination of N1 counters to build the N1 portion of the * transmit payload. @@ -413,9 +385,13 @@ static bool operation_shiftn1(PnvSpi *s, uint8_t opcode, */ uint8_t n1_byte = 0x00; n1_byte = get_from_offset(s, n1_count); - trace_pnv_spi_tx_append("n1_byte", n1_byte, n1_count); - *(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) = - n1_byte; + if (!fifo8_is_full(&s->tx_fifo)) { + trace_pnv_spi_tx_append("n1_byte", n1_byte, n1_count); + fifo8_push(&s->tx_fifo, n1_byte); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: TX_FIFO is full\n"); + break; + } } else { /* * We hit a shift_n1 opcode TX but the TDR is empty, tell the @@ -436,16 +412,17 @@ static bool operation_shiftn1(PnvSpi *s, uint8_t opcode, * - we are receiving and the RDR is empty so we allow the operation * to proceed. */ - if ((s->N1_rx != 0) && (GETFIELD(SPI_STS_RDR_FULL, - s->status) == 1)) { + if ((s->N1_rx != 0) && (GETFIELD(SPI_STS_RDR_FULL, s->status) == 1)) { trace_pnv_spi_sequencer_stop_requested("shift N1" "set for receive but RDR is full"); stop = true; break; - } else { + } else if (!fifo8_is_full(&s->tx_fifo)) { trace_pnv_spi_tx_append_FF("n1_byte"); - *(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) - = 0xff; + fifo8_push(&s->tx_fifo, 0xff); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: TX_FIFO is full\n"); + break; } } n1_count++; @@ -486,15 +463,13 @@ static bool operation_shiftn1(PnvSpi *s, uint8_t opcode, */ if (send_n1_alone && !stop) { /* We have a TX and a full TDR or an RX and an empty RDR */ - trace_pnv_spi_tx_request("Shifting N1 frame", (*payload)->len); - transfer(s, *payload); + trace_pnv_spi_tx_request("Shifting N1 frame", fifo8_num_used(&s->tx_fifo)); + transfer(s); /* The N1 frame shift is complete so reset the N1 counters */ s->N2_bits = 0; s->N2_bytes = 0; s->N2_tx = 0; s->N2_rx = 0; - pnv_spi_xfer_buffer_free(*payload); - *payload = NULL; } return stop; } /* end of operation_shiftn1() */ @@ -552,13 +527,11 @@ static void calculate_N2(PnvSpi *s, uint8_t opcode) * If Forced Implicit mode and count control doesn't * indicate a receive then reset the rx count to 0 */ - if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B3, - s->regs[SPI_CTR_CFG_REG]) == 0) { + if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B3, s->regs[SPI_CTR_CFG_REG]) == 0) { s->N2_rx = 0; } /* If tx count control for N2 is set, load the tx value */ - if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B2, - s->regs[SPI_CTR_CFG_REG]) == 1) { + if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B2, s->regs[SPI_CTR_CFG_REG]) == 1) { s->N2_tx = s->N2_bytes; } } @@ -571,8 +544,7 @@ static void calculate_N2(PnvSpi *s, uint8_t opcode) * cap the size at a max of 64 bits or 72 bits and set the sequencer FSM * error bit. */ - uint8_t ecc_control = GETFIELD(SPI_CLK_CFG_ECC_CTRL, - s->regs[SPI_CLK_CFG_REG]); + uint8_t ecc_control = GETFIELD(SPI_CLK_CFG_ECC_CTRL, s->regs[SPI_CLK_CFG_REG]); if (ecc_control == 0 || ecc_control == 2) { if (s->N2_bytes > (PNV_SPI_REG_SIZE + 1)) { /* Unsupported N2 shift size when ECC enabled */ @@ -590,19 +562,10 @@ static void calculate_N2(PnvSpi *s, uint8_t opcode) * Shift_N2 operation handler method */ -static bool operation_shiftn2(PnvSpi *s, uint8_t opcode, - PnvXferBuffer **payload) +static bool operation_shiftn2(PnvSpi *s, uint8_t opcode) { uint8_t n2_count; bool stop = false; - - /* - * If there isn't a current payload left over from a stopped sequence - * create a new one. - */ - if (*payload == NULL) { - *payload = pnv_spi_xfer_buffer_new(); - } /* * Use a combination of N2 counters to build the N2 portion of the * transmit payload. @@ -629,44 +592,47 @@ static bool operation_shiftn2(PnvSpi *s, uint8_t opcode, * code continue will end up building the payload twice in the same * buffer since RDR full causes a sequence stop and restart. */ - if ((s->N2_rx != 0) && - (GETFIELD(SPI_STS_RDR_FULL, s->status) == 1)) { + if ((s->N2_rx != 0) && (GETFIELD(SPI_STS_RDR_FULL, s->status) == 1)) { trace_pnv_spi_sequencer_stop_requested("shift N2 set" "for receive but RDR is full"); stop = true; break; } - if ((s->N2_tx != 0) && ((s->N1_tx + n2_count) < - PNV_SPI_REG_SIZE)) { + if ((s->N2_tx != 0) && ((s->N1_tx + n2_count) < PNV_SPI_REG_SIZE)) { /* Always append data for the N2 segment if it is set for TX */ uint8_t n2_byte = 0x00; n2_byte = get_from_offset(s, (s->N1_tx + n2_count)); - trace_pnv_spi_tx_append("n2_byte", n2_byte, (s->N1_tx + n2_count)); - *(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) - = n2_byte; - } else { + if (!fifo8_is_full(&s->tx_fifo)) { + trace_pnv_spi_tx_append("n2_byte", n2_byte, (s->N1_tx + n2_count)); + fifo8_push(&s->tx_fifo, n2_byte); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: TX_FIFO is full\n"); + break; + } + } else if (!fifo8_is_full(&s->tx_fifo)) { /* * Regardless of whether or not N2 is set for TX or RX, we need * the number of bytes in the payload to match the overall length * of the operation. */ trace_pnv_spi_tx_append_FF("n2_byte"); - *(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) - = 0xff; + fifo8_push(&s->tx_fifo, 0xff); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: TX_FIFO is full\n"); + break; } n2_count++; } /* end of while */ if (!stop) { /* We have a TX and a full TDR or an RX and an empty RDR */ - trace_pnv_spi_tx_request("Shifting N2 frame", (*payload)->len); - transfer(s, *payload); + trace_pnv_spi_tx_request("Shifting N2 frame", fifo8_num_used(&s->tx_fifo)); + transfer(s); /* * If we are doing an N2 TX and the TDR is full we need to clear the * TDR_full status. Do this here instead of up in the loop above so we * don't log the message in every loop iteration. */ - if ((s->N2_tx != 0) && - (GETFIELD(SPI_STS_TDR_FULL, s->status) == 1)) { + if ((s->N2_tx != 0) && (GETFIELD(SPI_STS_TDR_FULL, s->status) == 1)) { s->status = SETFIELD(SPI_STS_TDR_FULL, s->status, 0); } /* @@ -682,8 +648,6 @@ static bool operation_shiftn2(PnvSpi *s, uint8_t opcode, s->N1_bytes = 0; s->N1_tx = 0; s->N1_rx = 0; - pnv_spi_xfer_buffer_free(*payload); - *payload = NULL; } return stop; } /* end of operation_shiftn2()*/ @@ -701,19 +665,6 @@ static void operation_sequencer(PnvSpi *s) uint8_t opcode = 0; uint8_t masked_opcode = 0; - /* - * PnvXferBuffer for containing the payload of the SPI frame. - * This is a static because there are cases where a sequence has to stop - * and wait for the target application to unload the RDR. If this occurs - * during a sequence where N1 is not sent alone and instead combined with - * N2 since the N1 tx length + the N2 tx length is less than the size of - * the TDR. - */ - static PnvXferBuffer *payload; - - if (payload == NULL) { - payload = pnv_spi_xfer_buffer_new(); - } /* * Clear the sequencer FSM error bit - general_SPI_status[3] * before starting a sequence. @@ -775,10 +726,8 @@ static void operation_sequencer(PnvSpi *s) s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_DONE); } else if (s->responder_select != 1) { qemu_log_mask(LOG_GUEST_ERROR, "Slave selection other than 1 " - "not supported, select = 0x%x\n", - s->responder_select); - trace_pnv_spi_sequencer_stop_requested("invalid " - "responder select"); + "not supported, select = 0x%x\n", s->responder_select); + trace_pnv_spi_sequencer_stop_requested("invalid responder select"); s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_IDLE); stop = true; } else { @@ -840,9 +789,8 @@ static void operation_sequencer(PnvSpi *s) == SEQ_OP_SHIFT_N2) { send_n1_alone = false; } - s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, - FSM_SHIFT_N1); - stop = operation_shiftn1(s, opcode, &payload, send_n1_alone); + s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_SHIFT_N1); + stop = operation_shiftn1(s, opcode, send_n1_alone); if (stop) { /* * The operation code says to stop, this can occur if: @@ -858,7 +806,7 @@ static void operation_sequencer(PnvSpi *s) if (GETFIELD(SPI_STS_TDR_UNDERRUN, s->status)) { s->shift_n1_done = true; s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, - FSM_SHIFT_N2); + FSM_SHIFT_N2); s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, (get_seq_index(s) + 1)); } else { @@ -866,8 +814,7 @@ static void operation_sequencer(PnvSpi *s) * This is case (1) or (2) so the sequencer needs to * wait and NOT go to the next sequence yet. */ - s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, - FSM_WAIT); + s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_WAIT); } } else { /* Ok to move on to the next index */ @@ -890,21 +837,18 @@ static void operation_sequencer(PnvSpi *s) * error bit 3 (general_SPI_status[3]) in status reg. */ s->status = SETFIELD(SPI_STS_GEN_STATUS_B3, s->status, 1); - trace_pnv_spi_sequencer_stop_requested("shift_n2 " - "w/no shift_n1 done"); + trace_pnv_spi_sequencer_stop_requested("shift_n2 w/no shift_n1 done"); stop = true; } else { /* Ok to do a Shift_N2 */ - s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, - FSM_SHIFT_N2); - stop = operation_shiftn2(s, opcode, &payload); + s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_SHIFT_N2); + stop = operation_shiftn2(s, opcode); /* * If the operation code says to stop set the shifter state to * wait and stop */ if (stop) { - s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, - FSM_WAIT); + s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_WAIT); } else { /* Ok to move on to the next index */ next_sequencer_fsm(s); @@ -988,8 +932,7 @@ static void operation_sequencer(PnvSpi *s) case SEQ_OP_BRANCH_IFNEQ_INC_2: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_INC_2", get_seq_index(s)); - uint8_t condition2 = GETFIELD(SPI_CTR_CFG_CMP2, - s->regs[SPI_CTR_CFG_REG]); + uint8_t condition2 = GETFIELD(SPI_CTR_CFG_CMP2, s->regs[SPI_CTR_CFG_REG]); /* * The spec says the loop should execute count compare + 1 times. * However we learned from engineering that we really only loop @@ -1209,6 +1152,9 @@ static void pnv_spi_realize(DeviceState *dev, Error **errp) s->cs_line = g_new0(qemu_irq, 1); qdev_init_gpio_out_named(DEVICE(s), s->cs_line, "cs", 1); + fifo8_create(&s->tx_fifo, PNV_SPI_FIFO_SIZE); + fifo8_create(&s->rx_fifo, PNV_SPI_FIFO_SIZE); + /* spi scoms */ pnv_xscom_region_init(&s->xscom_spic_regs, OBJECT(s), &pnv_spi_xscom_ops, s, "xscom-spi", PNV10_XSCOM_PIB_SPIC_SIZE); diff --git a/include/hw/ssi/pnv_spi.h b/include/hw/ssi/pnv_spi.h index 8815f67d4539..9878d9a25f6e 100644 --- a/include/hw/ssi/pnv_spi.h +++ b/include/hw/ssi/pnv_spi.h @@ -23,6 +23,7 @@ #include "hw/ssi/ssi.h" #include "hw/sysbus.h" +#include "qemu/fifo8.h" #define TYPE_PNV_SPI "pnv-spi" OBJECT_DECLARE_SIMPLE_TYPE(PnvSpi, PNV_SPI) @@ -37,6 +38,8 @@ typedef struct PnvSpi { SSIBus *ssi_bus; qemu_irq *cs_line; MemoryRegion xscom_spic_regs; + Fifo8 tx_fifo; + Fifo8 rx_fifo; /* SPI object number */ uint32_t spic_num; uint8_t transfer_len; From f1f756f305981c24a37a0be3113c2326a28594a5 Mon Sep 17 00:00:00 2001 From: Chalapathi V Date: Mon, 3 Mar 2025 08:13:26 -0600 Subject: [PATCH 0917/1179] hw/ssi/pnv_spi: Use local var seq_index instead of get_seq_index(). Use a local variable seq_index instead of repeatedly calling get_seq_index() method and open-code next_sequencer_fsm(). Signed-off-by: Chalapathi V Reviewed-by: Nicholas Piggin Message-ID: <20250303141328.23991-3-chalapathi.v@linux.ibm.com> Signed-off-by: Nicholas Piggin --- hw/ssi/pnv_spi.c | 97 ++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/hw/ssi/pnv_spi.c b/hw/ssi/pnv_spi.c index 388b42515763..de33542c357f 100644 --- a/hw/ssi/pnv_spi.c +++ b/hw/ssi/pnv_spi.c @@ -227,18 +227,6 @@ static void transfer(PnvSpi *s) fifo8_reset(&s->rx_fifo); } -static inline uint8_t get_seq_index(PnvSpi *s) -{ - return GETFIELD(SPI_STS_SEQ_INDEX, s->status); -} - -static inline void next_sequencer_fsm(PnvSpi *s) -{ - uint8_t seq_index = get_seq_index(s); - s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, (seq_index + 1)); - s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_INDEX_INCREMENT); -} - /* * Calculate the N1 counters based on passed in opcode and * internal register values. @@ -664,6 +652,7 @@ static void operation_sequencer(PnvSpi *s) bool stop = false; /* Flag to stop the sequencer */ uint8_t opcode = 0; uint8_t masked_opcode = 0; + uint8_t seq_index; /* * Clear the sequencer FSM error bit - general_SPI_status[3] @@ -677,12 +666,17 @@ static void operation_sequencer(PnvSpi *s) if (GETFIELD(SPI_STS_SEQ_FSM, s->status) == SEQ_STATE_IDLE) { s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, 0); } + /* + * SPI_STS_SEQ_INDEX of status register is kept in seq_index variable and + * updated back to status register at the end of operation_sequencer(). + */ + seq_index = GETFIELD(SPI_STS_SEQ_INDEX, s->status); /* * There are only 8 possible operation IDs to iterate through though * some operations may cause more than one frame to be sequenced. */ - while (get_seq_index(s) < NUM_SEQ_OPS) { - opcode = s->seq_op[get_seq_index(s)]; + while (seq_index < NUM_SEQ_OPS) { + opcode = s->seq_op[seq_index]; /* Set sequencer state to decode */ s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_DECODE); /* @@ -699,7 +693,7 @@ static void operation_sequencer(PnvSpi *s) case SEQ_OP_STOP: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); /* A stop operation in any position stops the sequencer */ - trace_pnv_spi_sequencer_op("STOP", get_seq_index(s)); + trace_pnv_spi_sequencer_op("STOP", seq_index); stop = true; s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_IDLE); @@ -710,7 +704,7 @@ static void operation_sequencer(PnvSpi *s) case SEQ_OP_SELECT_SLAVE: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); - trace_pnv_spi_sequencer_op("SELECT_SLAVE", get_seq_index(s)); + trace_pnv_spi_sequencer_op("SELECT_SLAVE", seq_index); /* * This device currently only supports a single responder * connection at position 0. De-selecting a responder is fine @@ -721,8 +715,7 @@ static void operation_sequencer(PnvSpi *s) if (s->responder_select == 0) { trace_pnv_spi_shifter_done(); qemu_set_irq(s->cs_line[0], 1); - s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, - (get_seq_index(s) + 1)); + seq_index++; s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_DONE); } else if (s->responder_select != 1) { qemu_log_mask(LOG_GUEST_ERROR, "Slave selection other than 1 " @@ -747,13 +740,15 @@ static void operation_sequencer(PnvSpi *s) * applies once a valid responder select has occurred. */ s->shift_n1_done = false; - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, + SEQ_STATE_INDEX_INCREMENT); } break; case SEQ_OP_SHIFT_N1: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); - trace_pnv_spi_sequencer_op("SHIFT_N1", get_seq_index(s)); + trace_pnv_spi_sequencer_op("SHIFT_N1", seq_index); /* * Only allow a shift_n1 when the state is not IDLE or DONE. * In either of those two cases the sequencer is not in a proper @@ -785,8 +780,9 @@ static void operation_sequencer(PnvSpi *s) * transmission to the responder without requiring a refill of * the TDR between the two operations. */ - if (PNV_SPI_MASKED_OPCODE(s->seq_op[get_seq_index(s) + 1]) - == SEQ_OP_SHIFT_N2) { + if ((seq_index != 7) && + PNV_SPI_MASKED_OPCODE(s->seq_op[(seq_index + 1)]) == + SEQ_OP_SHIFT_N2) { send_n1_alone = false; } s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_SHIFT_N1); @@ -806,9 +802,8 @@ static void operation_sequencer(PnvSpi *s) if (GETFIELD(SPI_STS_TDR_UNDERRUN, s->status)) { s->shift_n1_done = true; s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, - FSM_SHIFT_N2); - s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, - (get_seq_index(s) + 1)); + FSM_SHIFT_N2); + seq_index++; } else { /* * This is case (1) or (2) so the sequencer needs to @@ -819,14 +814,16 @@ static void operation_sequencer(PnvSpi *s) } else { /* Ok to move on to the next index */ s->shift_n1_done = true; - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, + SEQ_STATE_INDEX_INCREMENT); } } break; case SEQ_OP_SHIFT_N2: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); - trace_pnv_spi_sequencer_op("SHIFT_N2", get_seq_index(s)); + trace_pnv_spi_sequencer_op("SHIFT_N2", seq_index); if (!s->shift_n1_done) { qemu_log_mask(LOG_GUEST_ERROR, "Shift_N2 is not allowed if a " "Shift_N1 is not done, shifter state = 0x%llx", @@ -851,14 +848,16 @@ static void operation_sequencer(PnvSpi *s) s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_WAIT); } else { /* Ok to move on to the next index */ - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, + SEQ_STATE_INDEX_INCREMENT); } } break; case SEQ_OP_BRANCH_IFNEQ_RDR: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); - trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_RDR", get_seq_index(s)); + trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_RDR", seq_index); /* * The memory mapping register RDR match value is compared against * the 16 rightmost bytes of the RDR (potentially with masking). @@ -874,15 +873,16 @@ static void operation_sequencer(PnvSpi *s) if (rdr_matched) { trace_pnv_spi_RDR_match("success"); /* A match occurred, increment the sequencer index. */ - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, + SEQ_STATE_INDEX_INCREMENT); } else { trace_pnv_spi_RDR_match("failed"); /* * Branch the sequencer to the index coded into the op * code. */ - s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, - PNV_SPI_OPCODE_LO_NIBBLE(opcode)); + seq_index = PNV_SPI_OPCODE_LO_NIBBLE(opcode); } /* * Regardless of where the branch ended up we want the @@ -901,12 +901,13 @@ static void operation_sequencer(PnvSpi *s) case SEQ_OP_TRANSFER_TDR: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); qemu_log_mask(LOG_GUEST_ERROR, "Transfer TDR is not supported\n"); - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_INDEX_INCREMENT); break; case SEQ_OP_BRANCH_IFNEQ_INC_1: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); - trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_INC_1", get_seq_index(s)); + trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_INC_1", seq_index); /* * The spec says the loop should execute count compare + 1 times. * However we learned from engineering that we really only loop @@ -920,19 +921,21 @@ static void operation_sequencer(PnvSpi *s) * mask off all but the first three bits so we don't try to * access beyond the sequencer_operation_reg boundary. */ - s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, - PNV_SPI_OPCODE_LO_NIBBLE(opcode)); + seq_index = PNV_SPI_OPCODE_LO_NIBBLE(opcode); s->loop_counter_1++; } else { /* Continue to next index if loop counter is reached */ - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, + SEQ_STATE_INDEX_INCREMENT); } break; case SEQ_OP_BRANCH_IFNEQ_INC_2: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); - trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_INC_2", get_seq_index(s)); - uint8_t condition2 = GETFIELD(SPI_CTR_CFG_CMP2, s->regs[SPI_CTR_CFG_REG]); + trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_INC_2", seq_index); + uint8_t condition2 = GETFIELD(SPI_CTR_CFG_CMP2, + s->regs[SPI_CTR_CFG_REG]); /* * The spec says the loop should execute count compare + 1 times. * However we learned from engineering that we really only loop @@ -945,19 +948,21 @@ static void operation_sequencer(PnvSpi *s) * mask off all but the first three bits so we don't try to * access beyond the sequencer_operation_reg boundary. */ - s->status = SETFIELD(SPI_STS_SEQ_INDEX, - s->status, PNV_SPI_OPCODE_LO_NIBBLE(opcode)); + seq_index = PNV_SPI_OPCODE_LO_NIBBLE(opcode); s->loop_counter_2++; } else { /* Continue to next index if loop counter is reached */ - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, + SEQ_STATE_INDEX_INCREMENT); } break; default: s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE); /* Ignore unsupported operations. */ - next_sequencer_fsm(s); + seq_index++; + s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_INDEX_INCREMENT); break; } /* end of switch */ /* @@ -965,10 +970,10 @@ static void operation_sequencer(PnvSpi *s) * we need to go ahead and end things as if there was a STOP at the * end. */ - if (get_seq_index(s) == NUM_SEQ_OPS) { + if (seq_index == NUM_SEQ_OPS) { /* All 8 opcodes completed, sequencer idling */ s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_IDLE); - s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, 0); + seq_index = 0; s->loop_counter_1 = 0; s->loop_counter_2 = 0; s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_IDLE); @@ -979,6 +984,8 @@ static void operation_sequencer(PnvSpi *s) break; } } /* end of while */ + /* Update sequencer index field in status.*/ + s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, seq_index); return; } /* end of operation_sequencer() */ From 7192d7b7fea15f2226a896f02b360bf7cfce1ab1 Mon Sep 17 00:00:00 2001 From: Chalapathi V Date: Mon, 3 Mar 2025 08:13:27 -0600 Subject: [PATCH 0918/1179] hw/ssi/pnv_spi: Make bus names distinct for each controllers of a socket Create a spi buses with distinct names on each socket so that responders are attached to correct SPI controllers. Change the bus name to chipX.spi. where X = 0.. QOM tree on a 2 socket machine: (qemu) info qom-tree /machine (powernv10-machine) /chip[0] (power10_v2.0-pnv-chip) /pib_spic[0] (pnv-spi) /chip0.spi.0 (SSI) /xscom-spi[0] (memory-region) /chip[1] (power10_v2.0-pnv-chip) /pib_spic[0] (pnv-spi) /chip1.spi.0 (SSI) /xscom-spi[0] (memory-region) Signed-off-by: Chalapathi V Message-ID: <20250303141328.23991-4-chalapathi.v@linux.ibm.com> Signed-off-by: Nicholas Piggin --- hw/ppc/pnv.c | 2 ++ hw/ssi/pnv_spi.c | 5 +++-- include/hw/ssi/pnv_spi.h | 3 ++- tests/qtest/pnv-spi-seeprom-test.c | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index d60574d60199..59365370c378 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -2252,6 +2252,8 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp) /* pib_spic[2] connected to 25csm04 which implements 1 byte transfer */ object_property_set_int(OBJECT(&chip10->pib_spic[i]), "transfer_len", (i == 2) ? 1 : 4, &error_fatal); + object_property_set_int(OBJECT(&chip10->pib_spic[i]), "chip-id", + chip->chip_id, &error_fatal); if (!sysbus_realize(SYS_BUS_DEVICE(OBJECT (&chip10->pib_spic[i])), errp)) { return; diff --git a/hw/ssi/pnv_spi.c b/hw/ssi/pnv_spi.c index de33542c357f..83221607c982 100644 --- a/hw/ssi/pnv_spi.c +++ b/hw/ssi/pnv_spi.c @@ -1147,14 +1147,15 @@ static const MemoryRegionOps pnv_spi_xscom_ops = { static const Property pnv_spi_properties[] = { DEFINE_PROP_UINT32("spic_num", PnvSpi, spic_num, 0), + DEFINE_PROP_UINT32("chip-id", PnvSpi, chip_id, 0), DEFINE_PROP_UINT8("transfer_len", PnvSpi, transfer_len, 4), }; static void pnv_spi_realize(DeviceState *dev, Error **errp) { PnvSpi *s = PNV_SPI(dev); - g_autofree char *name = g_strdup_printf(TYPE_PNV_SPI_BUS ".%d", - s->spic_num); + g_autofree char *name = g_strdup_printf("chip%d." TYPE_PNV_SPI_BUS ".%d", + s->chip_id, s->spic_num); s->ssi_bus = ssi_create_bus(dev, name); s->cs_line = g_new0(qemu_irq, 1); qdev_init_gpio_out_named(DEVICE(s), s->cs_line, "cs", 1); diff --git a/include/hw/ssi/pnv_spi.h b/include/hw/ssi/pnv_spi.h index 9878d9a25f6e..6adb72dbb204 100644 --- a/include/hw/ssi/pnv_spi.h +++ b/include/hw/ssi/pnv_spi.h @@ -31,7 +31,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(PnvSpi, PNV_SPI) #define PNV_SPI_REG_SIZE 8 #define PNV_SPI_REGS 7 -#define TYPE_PNV_SPI_BUS "pnv-spi-bus" +#define TYPE_PNV_SPI_BUS "spi" typedef struct PnvSpi { SysBusDevice parent_obj; @@ -42,6 +42,7 @@ typedef struct PnvSpi { Fifo8 rx_fifo; /* SPI object number */ uint32_t spic_num; + uint32_t chip_id; uint8_t transfer_len; uint8_t responder_select; /* To verify if shift_n1 happens prior to shift_n2 */ diff --git a/tests/qtest/pnv-spi-seeprom-test.c b/tests/qtest/pnv-spi-seeprom-test.c index 57f20af76e1f..600493c425da 100644 --- a/tests/qtest/pnv-spi-seeprom-test.c +++ b/tests/qtest/pnv-spi-seeprom-test.c @@ -92,7 +92,7 @@ static void test_spi_seeprom(const void *data) qts = qtest_initf("-machine powernv10 -smp 2,cores=2," "threads=1 -accel tcg,thread=single -nographic " "-blockdev node-name=pib_spic2,driver=file," - "filename=%s -device 25csm04,bus=pnv-spi-bus.2,cs=0," + "filename=%s -device 25csm04,bus=chip0.spi.2,cs=0," "drive=pib_spic2", tmp_path); spi_seeprom_transaction(qts, chip); qtest_quit(qts); From a613b9d321f5536943e29aa5847553df46dc2340 Mon Sep 17 00:00:00 2001 From: Chalapathi V Date: Mon, 3 Mar 2025 08:13:28 -0600 Subject: [PATCH 0919/1179] hw/ssi/pnv_spi: Put a limit to RDR match failures There is a possibility that SPI controller can get into loop due to indefinite RDR match failures. Hence put a limit to failures and stop the sequencer. Signed-off-by: Chalapathi V Reviewed-by: Nicholas Piggin Message-ID: <20250303141328.23991-5-chalapathi.v@linux.ibm.com> Signed-off-by: Nicholas Piggin --- hw/ssi/pnv_spi.c | 10 ++++++++++ include/hw/ssi/pnv_spi.h | 1 + 2 files changed, 11 insertions(+) diff --git a/hw/ssi/pnv_spi.c b/hw/ssi/pnv_spi.c index 83221607c982..126070393eec 100644 --- a/hw/ssi/pnv_spi.c +++ b/hw/ssi/pnv_spi.c @@ -20,6 +20,7 @@ #define PNV_SPI_OPCODE_LO_NIBBLE(x) (x & 0x0F) #define PNV_SPI_MASKED_OPCODE(x) (x & 0xF0) #define PNV_SPI_FIFO_SIZE 16 +#define RDR_MATCH_FAILURE_LIMIT 16 /* * Macro from include/hw/ppc/fdt.h @@ -872,18 +873,27 @@ static void operation_sequencer(PnvSpi *s) rdr_matched = does_rdr_match(s); if (rdr_matched) { trace_pnv_spi_RDR_match("success"); + s->fail_count = 0; /* A match occurred, increment the sequencer index. */ seq_index++; s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_INDEX_INCREMENT); } else { trace_pnv_spi_RDR_match("failed"); + s->fail_count++; /* * Branch the sequencer to the index coded into the op * code. */ seq_index = PNV_SPI_OPCODE_LO_NIBBLE(opcode); } + if (s->fail_count >= RDR_MATCH_FAILURE_LIMIT) { + qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi: RDR match failure" + " limit crossed %d times hence requesting " + "sequencer to stop.\n", + RDR_MATCH_FAILURE_LIMIT); + stop = true; + } /* * Regardless of where the branch ended up we want the * sequencer to continue shifting so we have to clear diff --git a/include/hw/ssi/pnv_spi.h b/include/hw/ssi/pnv_spi.h index 6adb72dbb204..c591a0663da6 100644 --- a/include/hw/ssi/pnv_spi.h +++ b/include/hw/ssi/pnv_spi.h @@ -40,6 +40,7 @@ typedef struct PnvSpi { MemoryRegion xscom_spic_regs; Fifo8 tx_fifo; Fifo8 rx_fifo; + uint8_t fail_count; /* RDR Match failure counter */ /* SPI object number */ uint32_t spic_num; uint32_t chip_id; From 3e84d0381578467931d1145a32a55b7e1c3b2b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:05 +0100 Subject: [PATCH 0920/1179] hw/ppc/spapr: Restrict CONFER hypercall to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KVM handles H_CONFER and does not pass it along to QEMU, so only vhyp (as used by TCG spapr) needs to handle it. [npiggin: Add changelog] Signed-off-by: Philippe Mathieu-Daudé Message-ID: <20250127102620.39159-2-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr_hcall.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index f987ff323f87..4f1933b8da68 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -580,6 +580,8 @@ static target_ulong h_confer(PowerPCCPU *cpu, SpaprMachineState *spapr, CPUState *cs = CPU(cpu); SpaprCpuState *spapr_cpu; + assert(tcg_enabled()); /* KVM will have handled this */ + /* * -1 means confer to all other CPUs without dispatch counter check, * otherwise it's a targeted confer. From ffb6440cc5e755d3cdf0cf317880576517cb0de7 Mon Sep 17 00:00:00 2001 From: dan tan Date: Thu, 16 Jan 2025 09:42:26 -0600 Subject: [PATCH 0921/1179] ppc/pnv: Add new PowerPC Special Purpose Registers (RWMR) Register RWMR - Region Weighted Mode Register for privileged access in Power9 and Power10 It controls what the SPURR register produces. Specs: - Power10: https://files.openpower.foundation/s/EgCy7C43p2NSRfR TCG does not model SMT priority, timing, resource controls and status so this register has no effect for now. [npiggin: adjust changelog] Signed-off-by: dan tan Message-ID: <20250116154226.13376-1-dantan@linux.vnet.ibm.com> Signed-off-by: Nicholas Piggin --- target/ppc/cpu.h | 1 + target/ppc/cpu_init.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 682583d1d10f..25b1e6d6b01c 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -2102,6 +2102,7 @@ void ppc_compat_add_property(Object *obj, const char *name, #define SPR_PMCR (0x374) #define SPR_440_ITV0 (0x374) #define SPR_440_ITV1 (0x375) +#define SPR_RWMR (0x375) #define SPR_440_ITV2 (0x376) #define SPR_440_ITV3 (0x377) #define SPR_440_CCR1 (0x378) diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index 54035c7bbbd2..8d73e11540b0 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -5773,6 +5773,11 @@ static void register_power9_book4_sprs(CPUPPCState *env) SPR_NOACCESS, SPR_NOACCESS, &spr_read_generic, &spr_write_generic, KVM_REG_PPC_WORT, 0); + spr_register_hv(env, SPR_RWMR, "RWMR", + SPR_NOACCESS, SPR_NOACCESS, + &spr_read_generic, &spr_write_generic, + &spr_read_generic, &spr_write_generic, + 0x00000000); #endif } From 215b2ee8f1546ed96a2b3b0f871e18c045317c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:07 +0100 Subject: [PATCH 0922/1179] target/ppc: Make ppc_ldl_code() declaration public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We are going to move code calling ppc_ldl_code() out of excp_helper.c where it is defined. Expose its declaration for few commits, until eventually making it static again once everything is moved. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-4-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 2 +- target/ppc/internal.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index fde9912230e9..7ed4bbec035a 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -144,7 +144,7 @@ static inline bool insn_need_byteswap(CPUArchState *env) return !!(env->msr & ((target_ulong)1 << MSR_LE)); } -static uint32_t ppc_ldl_code(CPUArchState *env, target_ulong addr) +uint32_t ppc_ldl_code(CPUArchState *env, target_ulong addr) { uint32_t insn = cpu_ldl_code(env, addr); diff --git a/target/ppc/internal.h b/target/ppc/internal.h index 20fb2ec593c5..46db6adfcf66 100644 --- a/target/ppc/internal.h +++ b/target/ppc/internal.h @@ -268,6 +268,8 @@ static inline void pte_invalidate(target_ulong *pte0) #define PTE_PTEM_MASK 0x7FFFFFBF #define PTE_CHECK_MASK (TARGET_PAGE_MASK | 0x7B) +uint32_t ppc_ldl_code(CPUArchState *env, target_ulong addr); + #ifdef CONFIG_USER_ONLY void ppc_cpu_record_sigsegv(CPUState *cs, vaddr addr, MMUAccessType access_type, From 0fc76338fe4f587cfe1f582ef0594b32690b7c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:08 +0100 Subject: [PATCH 0923/1179] target/ppc: Move TCG specific exception handlers to tcg-excp_helper.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the TCGCPUOps handlers to a new unit: tcg-excp_helper.c, only built when TCG is selected. See in target/ppc/cpu_init.c: #ifdef CONFIG_TCG static const TCGCPUOps ppc_tcg_ops = { ... .do_unaligned_access = ppc_cpu_do_unaligned_access, .do_transaction_failed = ppc_cpu_do_transaction_failed, .debug_excp_handler = ppc_cpu_debug_excp_handler, .debug_check_breakpoint = ppc_cpu_debug_check_breakpoint, .debug_check_watchpoint = ppc_cpu_debug_check_watchpoint, }; #endif /* CONFIG_TCG */ Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-5-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 173 ------------------------------ target/ppc/meson.build | 1 + target/ppc/tcg-excp_helper.c | 202 +++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 173 deletions(-) create mode 100644 target/ppc/tcg-excp_helper.c diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 7ed4bbec035a..b05eb7f5aec1 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -3144,178 +3144,5 @@ void helper_book3s_trace(CPUPPCState *env, target_ulong prev_ip) raise_exception_err(env, POWERPC_EXCP_TRACE, error_code); } -void ppc_cpu_do_unaligned_access(CPUState *cs, vaddr vaddr, - MMUAccessType access_type, - int mmu_idx, uintptr_t retaddr) -{ - CPUPPCState *env = cpu_env(cs); - uint32_t insn; - - /* Restore state and reload the insn we executed, for filling in DSISR. */ - cpu_restore_state(cs, retaddr); - insn = ppc_ldl_code(env, env->nip); - - switch (env->mmu_model) { - case POWERPC_MMU_SOFT_4xx: - env->spr[SPR_40x_DEAR] = vaddr; - break; - case POWERPC_MMU_BOOKE: - case POWERPC_MMU_BOOKE206: - env->spr[SPR_BOOKE_DEAR] = vaddr; - break; - default: - env->spr[SPR_DAR] = vaddr; - break; - } - - cs->exception_index = POWERPC_EXCP_ALIGN; - env->error_code = insn & 0x03FF0000; - cpu_loop_exit(cs); -} - -void ppc_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr, - vaddr vaddr, unsigned size, - MMUAccessType access_type, - int mmu_idx, MemTxAttrs attrs, - MemTxResult response, uintptr_t retaddr) -{ - CPUPPCState *env = cpu_env(cs); - - switch (env->excp_model) { -#if defined(TARGET_PPC64) - case POWERPC_EXCP_POWER8: - case POWERPC_EXCP_POWER9: - case POWERPC_EXCP_POWER10: - case POWERPC_EXCP_POWER11: - /* - * Machine check codes can be found in processor User Manual or - * Linux or skiboot source. - */ - if (access_type == MMU_DATA_LOAD) { - env->spr[SPR_DAR] = vaddr; - env->spr[SPR_DSISR] = PPC_BIT(57); - env->error_code = PPC_BIT(42); - - } else if (access_type == MMU_DATA_STORE) { - /* - * MCE for stores in POWER is asynchronous so hardware does - * not set DAR, but QEMU can do better. - */ - env->spr[SPR_DAR] = vaddr; - env->error_code = PPC_BIT(36) | PPC_BIT(43) | PPC_BIT(45); - env->error_code |= PPC_BIT(42); - - } else { /* Fetch */ - /* - * is_prefix_insn_excp() tests !PPC_BIT(42) to avoid fetching - * the instruction, so that must always be clear for fetches. - */ - env->error_code = PPC_BIT(36) | PPC_BIT(44) | PPC_BIT(45); - } - break; -#endif - default: - /* - * TODO: Check behaviour for other CPUs, for now do nothing. - * Could add a basic MCE even if real hardware ignores. - */ - return; - } - - cs->exception_index = POWERPC_EXCP_MCHECK; - cpu_loop_exit_restore(cs, retaddr); -} - -void ppc_cpu_debug_excp_handler(CPUState *cs) -{ -#if defined(TARGET_PPC64) - CPUPPCState *env = cpu_env(cs); - - if (env->insns_flags2 & PPC2_ISA207S) { - if (cs->watchpoint_hit) { - if (cs->watchpoint_hit->flags & BP_CPU) { - env->spr[SPR_DAR] = cs->watchpoint_hit->hitaddr; - env->spr[SPR_DSISR] = PPC_BIT(41); - cs->watchpoint_hit = NULL; - raise_exception(env, POWERPC_EXCP_DSI); - } - cs->watchpoint_hit = NULL; - } else if (cpu_breakpoint_test(cs, env->nip, BP_CPU)) { - raise_exception_err(env, POWERPC_EXCP_TRACE, - PPC_BIT(33) | PPC_BIT(43)); - } - } -#endif -} - -bool ppc_cpu_debug_check_breakpoint(CPUState *cs) -{ -#if defined(TARGET_PPC64) - CPUPPCState *env = cpu_env(cs); - - if (env->insns_flags2 & PPC2_ISA207S) { - target_ulong priv; - - priv = env->spr[SPR_CIABR] & PPC_BITMASK(62, 63); - switch (priv) { - case 0x1: /* problem */ - return env->msr & ((target_ulong)1 << MSR_PR); - case 0x2: /* supervisor */ - return (!(env->msr & ((target_ulong)1 << MSR_PR)) && - !(env->msr & ((target_ulong)1 << MSR_HV))); - case 0x3: /* hypervisor */ - return (!(env->msr & ((target_ulong)1 << MSR_PR)) && - (env->msr & ((target_ulong)1 << MSR_HV))); - default: - g_assert_not_reached(); - } - } -#endif - - return false; -} - -bool ppc_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) -{ -#if defined(TARGET_PPC64) - CPUPPCState *env = cpu_env(cs); - - if (env->insns_flags2 & PPC2_ISA207S) { - if (wp == env->dawr0_watchpoint) { - uint32_t dawrx = env->spr[SPR_DAWRX0]; - bool wt = extract32(dawrx, PPC_BIT_NR(59), 1); - bool wti = extract32(dawrx, PPC_BIT_NR(60), 1); - bool hv = extract32(dawrx, PPC_BIT_NR(61), 1); - bool sv = extract32(dawrx, PPC_BIT_NR(62), 1); - bool pr = extract32(dawrx, PPC_BIT_NR(62), 1); - - if ((env->msr & ((target_ulong)1 << MSR_PR)) && !pr) { - return false; - } else if ((env->msr & ((target_ulong)1 << MSR_HV)) && !hv) { - return false; - } else if (!sv) { - return false; - } - - if (!wti) { - if (env->msr & ((target_ulong)1 << MSR_DR)) { - if (!wt) { - return false; - } - } else { - if (wt) { - return false; - } - } - } - - return true; - } - } -#endif - - return false; -} - #endif /* !CONFIG_USER_ONLY */ #endif /* CONFIG_TCG */ diff --git a/target/ppc/meson.build b/target/ppc/meson.build index db3b7a0c33b0..8eed1fa40ca9 100644 --- a/target/ppc/meson.build +++ b/target/ppc/meson.build @@ -14,6 +14,7 @@ ppc_ss.add(when: 'CONFIG_TCG', if_true: files( 'int_helper.c', 'mem_helper.c', 'misc_helper.c', + 'tcg-excp_helper.c', 'timebase_helper.c', 'translate.c', 'power8-pmu.c', diff --git a/target/ppc/tcg-excp_helper.c b/target/ppc/tcg-excp_helper.c new file mode 100644 index 000000000000..3402dbe05eec --- /dev/null +++ b/target/ppc/tcg-excp_helper.c @@ -0,0 +1,202 @@ +/* + * PowerPC exception emulation helpers for QEMU (TCG specific) + * + * Copyright (c) 2003-2007 Jocelyn Mayer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +#include "qemu/osdep.h" +#include "exec/cpu_ldst.h" + +#include "hw/ppc/ppc.h" +#include "internal.h" +#include "cpu.h" +#include "trace.h" + +#ifndef CONFIG_USER_ONLY + +void ppc_cpu_do_unaligned_access(CPUState *cs, vaddr vaddr, + MMUAccessType access_type, + int mmu_idx, uintptr_t retaddr) +{ + CPUPPCState *env = cpu_env(cs); + uint32_t insn; + + /* Restore state and reload the insn we executed, for filling in DSISR. */ + cpu_restore_state(cs, retaddr); + insn = ppc_ldl_code(env, env->nip); + + switch (env->mmu_model) { + case POWERPC_MMU_SOFT_4xx: + env->spr[SPR_40x_DEAR] = vaddr; + break; + case POWERPC_MMU_BOOKE: + case POWERPC_MMU_BOOKE206: + env->spr[SPR_BOOKE_DEAR] = vaddr; + break; + default: + env->spr[SPR_DAR] = vaddr; + break; + } + + cs->exception_index = POWERPC_EXCP_ALIGN; + env->error_code = insn & 0x03FF0000; + cpu_loop_exit(cs); +} + +void ppc_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr, + vaddr vaddr, unsigned size, + MMUAccessType access_type, + int mmu_idx, MemTxAttrs attrs, + MemTxResult response, uintptr_t retaddr) +{ + CPUPPCState *env = cpu_env(cs); + + switch (env->excp_model) { +#if defined(TARGET_PPC64) + case POWERPC_EXCP_POWER8: + case POWERPC_EXCP_POWER9: + case POWERPC_EXCP_POWER10: + case POWERPC_EXCP_POWER11: + /* + * Machine check codes can be found in processor User Manual or + * Linux or skiboot source. + */ + if (access_type == MMU_DATA_LOAD) { + env->spr[SPR_DAR] = vaddr; + env->spr[SPR_DSISR] = PPC_BIT(57); + env->error_code = PPC_BIT(42); + + } else if (access_type == MMU_DATA_STORE) { + /* + * MCE for stores in POWER is asynchronous so hardware does + * not set DAR, but QEMU can do better. + */ + env->spr[SPR_DAR] = vaddr; + env->error_code = PPC_BIT(36) | PPC_BIT(43) | PPC_BIT(45); + env->error_code |= PPC_BIT(42); + + } else { /* Fetch */ + /* + * is_prefix_insn_excp() tests !PPC_BIT(42) to avoid fetching + * the instruction, so that must always be clear for fetches. + */ + env->error_code = PPC_BIT(36) | PPC_BIT(44) | PPC_BIT(45); + } + break; +#endif + default: + /* + * TODO: Check behaviour for other CPUs, for now do nothing. + * Could add a basic MCE even if real hardware ignores. + */ + return; + } + + cs->exception_index = POWERPC_EXCP_MCHECK; + cpu_loop_exit_restore(cs, retaddr); +} + +void ppc_cpu_debug_excp_handler(CPUState *cs) +{ +#if defined(TARGET_PPC64) + CPUPPCState *env = cpu_env(cs); + + if (env->insns_flags2 & PPC2_ISA207S) { + if (cs->watchpoint_hit) { + if (cs->watchpoint_hit->flags & BP_CPU) { + env->spr[SPR_DAR] = cs->watchpoint_hit->hitaddr; + env->spr[SPR_DSISR] = PPC_BIT(41); + cs->watchpoint_hit = NULL; + raise_exception(env, POWERPC_EXCP_DSI); + } + cs->watchpoint_hit = NULL; + } else if (cpu_breakpoint_test(cs, env->nip, BP_CPU)) { + raise_exception_err(env, POWERPC_EXCP_TRACE, + PPC_BIT(33) | PPC_BIT(43)); + } + } +#endif +} + +bool ppc_cpu_debug_check_breakpoint(CPUState *cs) +{ +#if defined(TARGET_PPC64) + CPUPPCState *env = cpu_env(cs); + + if (env->insns_flags2 & PPC2_ISA207S) { + target_ulong priv; + + priv = env->spr[SPR_CIABR] & PPC_BITMASK(62, 63); + switch (priv) { + case 0x1: /* problem */ + return env->msr & ((target_ulong)1 << MSR_PR); + case 0x2: /* supervisor */ + return (!(env->msr & ((target_ulong)1 << MSR_PR)) && + !(env->msr & ((target_ulong)1 << MSR_HV))); + case 0x3: /* hypervisor */ + return (!(env->msr & ((target_ulong)1 << MSR_PR)) && + (env->msr & ((target_ulong)1 << MSR_HV))); + default: + g_assert_not_reached(); + } + } +#endif + + return false; +} + +bool ppc_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) +{ +#if defined(TARGET_PPC64) + CPUPPCState *env = cpu_env(cs); + + if (env->insns_flags2 & PPC2_ISA207S) { + if (wp == env->dawr0_watchpoint) { + uint32_t dawrx = env->spr[SPR_DAWRX0]; + bool wt = extract32(dawrx, PPC_BIT_NR(59), 1); + bool wti = extract32(dawrx, PPC_BIT_NR(60), 1); + bool hv = extract32(dawrx, PPC_BIT_NR(61), 1); + bool sv = extract32(dawrx, PPC_BIT_NR(62), 1); + bool pr = extract32(dawrx, PPC_BIT_NR(62), 1); + + if ((env->msr & ((target_ulong)1 << MSR_PR)) && !pr) { + return false; + } else if ((env->msr & ((target_ulong)1 << MSR_HV)) && !hv) { + return false; + } else if (!sv) { + return false; + } + + if (!wti) { + if (env->msr & ((target_ulong)1 << MSR_DR)) { + if (!wt) { + return false; + } + } else { + if (wt) { + return false; + } + } + } + + return true; + } + } +#endif + + return false; +} + +#endif /* !CONFIG_USER_ONLY */ From 720c2f2d53d45bae18cbc9eaa39b56c2f00fefb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:09 +0100 Subject: [PATCH 0924/1179] target/ppc: Move ppc_ldl_code() to tcg-excp_helper.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-6-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 21 --------------------- target/ppc/tcg-excp_helper.c | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index b05eb7f5aec1..8956466db1de 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -136,27 +136,6 @@ static void dump_hcall(CPUPPCState *env) env->nip); } -#ifdef CONFIG_TCG -/* Return true iff byteswap is needed to load instruction */ -static inline bool insn_need_byteswap(CPUArchState *env) -{ - /* SYSTEM builds TARGET_BIG_ENDIAN. Need to swap when MSR[LE] is set */ - return !!(env->msr & ((target_ulong)1 << MSR_LE)); -} - -uint32_t ppc_ldl_code(CPUArchState *env, target_ulong addr) -{ - uint32_t insn = cpu_ldl_code(env, addr); - - if (insn_need_byteswap(env)) { - insn = bswap32(insn); - } - - return insn; -} - -#endif - static void ppc_excp_debug_sw_tlb(CPUPPCState *env, int excp) { const char *es; diff --git a/target/ppc/tcg-excp_helper.c b/target/ppc/tcg-excp_helper.c index 3402dbe05eec..6950b78774d1 100644 --- a/target/ppc/tcg-excp_helper.c +++ b/target/ppc/tcg-excp_helper.c @@ -199,4 +199,22 @@ bool ppc_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) return false; } +/* Return true iff byteswap is needed to load instruction */ +static inline bool insn_need_byteswap(CPUArchState *env) +{ + /* SYSTEM builds TARGET_BIG_ENDIAN. Need to swap when MSR[LE] is set */ + return !!(env->msr & ((target_ulong)1 << MSR_LE)); +} + +uint32_t ppc_ldl_code(CPUArchState *env, target_ulong addr) +{ + uint32_t insn = cpu_ldl_code(env, addr); + + if (insn_need_byteswap(env)) { + insn = bswap32(insn); + } + + return insn; +} + #endif /* !CONFIG_USER_ONLY */ From 30de74bda77fd5eb7c7dc9c50ed4ea39bbc367b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:10 +0100 Subject: [PATCH 0925/1179] target/ppc: Ensure powerpc_mcheck_checkstop() is only called under TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Philippe Mathieu-Daudé Message-ID: <20250127102620.39159-7-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 8956466db1de..b08cd53688c0 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -19,6 +19,7 @@ #include "qemu/osdep.h" #include "qemu/main-loop.h" #include "qemu/log.h" +#include "system/tcg.h" #include "system/system.h" #include "system/runstate.h" #include "cpu.h" @@ -30,7 +31,6 @@ #include "trace.h" #ifdef CONFIG_TCG -#include "system/tcg.h" #include "exec/helper-proto.h" #include "exec/cpu_ldst.h" #endif @@ -443,13 +443,11 @@ void helper_attn(CPUPPCState *env) static void powerpc_mcheck_checkstop(CPUPPCState *env) { /* KVM guests always have MSR[ME] enabled */ -#ifdef CONFIG_TCG if (FIELD_EX64(env->msr, MSR, ME)) { return; } - + assert(tcg_enabled()); powerpc_checkstop(env, "machine check with MSR[ME]=0"); -#endif } static void powerpc_excp_40x(PowerPCCPU *cpu, int excp) From 2f96c00b61a48803f3963214eac0ae04a7b7ee82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:11 +0100 Subject: [PATCH 0926/1179] target/ppc: Restrict powerpc_checkstop() to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose powerpc_checkstop() prototype, and move it to tcg-excp_helper.c, only built when TCG is available. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-8-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 26 -------------------------- target/ppc/internal.h | 4 +++- target/ppc/tcg-excp_helper.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index b08cd53688c0..236e5078f569 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -400,32 +400,6 @@ static void powerpc_set_excp_state(PowerPCCPU *cpu, target_ulong vector, } #ifdef CONFIG_TCG -/* - * This stops the machine and logs CPU state without killing QEMU (like - * cpu_abort()) because it is often a guest error as opposed to a QEMU error, - * so the machine can still be debugged. - */ -static G_NORETURN void powerpc_checkstop(CPUPPCState *env, const char *reason) -{ - CPUState *cs = env_cpu(env); - FILE *f; - - f = qemu_log_trylock(); - if (f) { - fprintf(f, "Entering checkstop state: %s\n", reason); - cpu_dump_state(cs, f, CPU_DUMP_FPU | CPU_DUMP_CCOP); - qemu_log_unlock(f); - } - - /* - * This stops the machine and logs CPU state without killing QEMU - * (like cpu_abort()) so the machine can still be debugged (because - * it is often a guest error). - */ - qemu_system_guest_panicked(NULL); - cpu_loop_exit_noexc(cs); -} - #if defined(TARGET_PPC64) && !defined(CONFIG_USER_ONLY) void helper_attn(CPUPPCState *env) { diff --git a/target/ppc/internal.h b/target/ppc/internal.h index 46db6adfcf66..62186bc1e614 100644 --- a/target/ppc/internal.h +++ b/target/ppc/internal.h @@ -289,7 +289,9 @@ void ppc_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr, void ppc_cpu_debug_excp_handler(CPUState *cs); bool ppc_cpu_debug_check_breakpoint(CPUState *cs); bool ppc_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp); -#endif + +G_NORETURN void powerpc_checkstop(CPUPPCState *env, const char *reason); +#endif /* !CONFIG_USER_ONLY */ FIELD(GER_MSK, XMSK, 0, 4) FIELD(GER_MSK, YMSK, 4, 4) diff --git a/target/ppc/tcg-excp_helper.c b/target/ppc/tcg-excp_helper.c index 6950b78774d1..93c2d6b5a036 100644 --- a/target/ppc/tcg-excp_helper.c +++ b/target/ppc/tcg-excp_helper.c @@ -17,7 +17,9 @@ * License along with this library; if not, see . */ #include "qemu/osdep.h" +#include "qemu/log.h" #include "exec/cpu_ldst.h" +#include "system/runstate.h" #include "hw/ppc/ppc.h" #include "internal.h" @@ -199,6 +201,32 @@ bool ppc_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) return false; } +/* + * This stops the machine and logs CPU state without killing QEMU (like + * cpu_abort()) because it is often a guest error as opposed to a QEMU error, + * so the machine can still be debugged. + */ +G_NORETURN void powerpc_checkstop(CPUPPCState *env, const char *reason) +{ + CPUState *cs = env_cpu(env); + FILE *f; + + f = qemu_log_trylock(); + if (f) { + fprintf(f, "Entering checkstop state: %s\n", reason); + cpu_dump_state(cs, f, CPU_DUMP_FPU | CPU_DUMP_CCOP); + qemu_log_unlock(f); + } + + /* + * This stops the machine and logs CPU state without killing QEMU + * (like cpu_abort()) so the machine can still be debugged (because + * it is often a guest error). + */ + qemu_system_guest_panicked(NULL); + cpu_loop_exit_noexc(cs); +} + /* Return true iff byteswap is needed to load instruction */ static inline bool insn_need_byteswap(CPUArchState *env) { From 94a37684a59e320a32ba948e9f8d75810c6dcdea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:12 +0100 Subject: [PATCH 0927/1179] target/ppc: Remove raise_exception_ra() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduced in commit db789c6cd33 ("ppc: Provide basic raise_exception_* functions"), raise_exception_ra() has never been used. Remove as dead code. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-9-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/cpu.h | 2 -- target/ppc/excp_helper.c | 6 ------ 2 files changed, 8 deletions(-) diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 25b1e6d6b01c..505b5897142a 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -2756,8 +2756,6 @@ static inline void cpu_get_tb_cpu_state(CPUPPCState *env, vaddr *pc, #endif G_NORETURN void raise_exception(CPUPPCState *env, uint32_t exception); -G_NORETURN void raise_exception_ra(CPUPPCState *env, uint32_t exception, - uintptr_t raddr); G_NORETURN void raise_exception_err(CPUPPCState *env, uint32_t exception, uint32_t error_code); G_NORETURN void raise_exception_err_ra(CPUPPCState *env, uint32_t exception, diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 236e5078f569..9e1a2ecc36f8 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -2528,12 +2528,6 @@ void raise_exception(CPUPPCState *env, uint32_t exception) raise_exception_err_ra(env, exception, 0, 0); } -void raise_exception_ra(CPUPPCState *env, uint32_t exception, - uintptr_t raddr) -{ - raise_exception_err_ra(env, exception, 0, raddr); -} - #ifdef CONFIG_TCG void helper_raise_exception_err(CPUPPCState *env, uint32_t exception, uint32_t error_code) From 1d0b82f86db20f875fe44c0df1ed246bf7fb5fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:13 +0100 Subject: [PATCH 0928/1179] target/ppc: Restrict exception helpers to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move exception helpers to tcg-excp_helper.c so they are only built when TCG is selected. Preprocessor guards are added for some helpers unused when CONFIG_USER_ONLY. [npiggin: mention USER_ONLY change] Signed-off-by: Philippe Mathieu-Daudé Message-ID: <20250127102620.39159-10-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 34 -------------------------------- target/ppc/tcg-excp_helper.c | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 9e1a2ecc36f8..6a12402b23af 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -2504,41 +2504,7 @@ bool ppc_cpu_exec_interrupt(CPUState *cs, int interrupt_request) #endif /* !CONFIG_USER_ONLY */ -/*****************************************************************************/ -/* Exceptions processing helpers */ - -void raise_exception_err_ra(CPUPPCState *env, uint32_t exception, - uint32_t error_code, uintptr_t raddr) -{ - CPUState *cs = env_cpu(env); - - cs->exception_index = exception; - env->error_code = error_code; - cpu_loop_exit_restore(cs, raddr); -} - -void raise_exception_err(CPUPPCState *env, uint32_t exception, - uint32_t error_code) -{ - raise_exception_err_ra(env, exception, error_code, 0); -} - -void raise_exception(CPUPPCState *env, uint32_t exception) -{ - raise_exception_err_ra(env, exception, 0, 0); -} - #ifdef CONFIG_TCG -void helper_raise_exception_err(CPUPPCState *env, uint32_t exception, - uint32_t error_code) -{ - raise_exception_err_ra(env, exception, error_code, 0); -} - -void helper_raise_exception(CPUPPCState *env, uint32_t exception) -{ - raise_exception_err_ra(env, exception, 0, 0); -} #ifndef CONFIG_USER_ONLY void helper_store_msr(CPUPPCState *env, target_ulong val) diff --git a/target/ppc/tcg-excp_helper.c b/target/ppc/tcg-excp_helper.c index 93c2d6b5a036..268a1614597a 100644 --- a/target/ppc/tcg-excp_helper.c +++ b/target/ppc/tcg-excp_helper.c @@ -19,15 +19,53 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "exec/cpu_ldst.h" +#include "exec/exec-all.h" +#include "exec/helper-proto.h" #include "system/runstate.h" +#include "helper_regs.h" #include "hw/ppc/ppc.h" #include "internal.h" #include "cpu.h" #include "trace.h" +/*****************************************************************************/ +/* Exceptions processing helpers */ + +void raise_exception_err_ra(CPUPPCState *env, uint32_t exception, + uint32_t error_code, uintptr_t raddr) +{ + CPUState *cs = env_cpu(env); + + cs->exception_index = exception; + env->error_code = error_code; + cpu_loop_exit_restore(cs, raddr); +} + +void helper_raise_exception_err(CPUPPCState *env, uint32_t exception, + uint32_t error_code) +{ + raise_exception_err_ra(env, exception, error_code, 0); +} + +void helper_raise_exception(CPUPPCState *env, uint32_t exception) +{ + raise_exception_err_ra(env, exception, 0, 0); +} + #ifndef CONFIG_USER_ONLY +void raise_exception_err(CPUPPCState *env, uint32_t exception, + uint32_t error_code) +{ + raise_exception_err_ra(env, exception, error_code, 0); +} + +void raise_exception(CPUPPCState *env, uint32_t exception) +{ + raise_exception_err_ra(env, exception, 0, 0); +} + void ppc_cpu_do_unaligned_access(CPUState *cs, vaddr vaddr, MMUAccessType access_type, int mmu_idx, uintptr_t retaddr) From ad8ad893a3f563b7ed8df6665d48f47c6337c7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:15 +0100 Subject: [PATCH 0929/1179] target/ppc: Restrict various common helpers to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move helpers common to system/user emulation to tcg-excp_helper.c. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-12-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 141 ---------------------------------- target/ppc/tcg-excp_helper.c | 143 +++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 141 deletions(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 6a12402b23af..511e27f7262a 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -2711,148 +2711,7 @@ void helper_rfmci(CPUPPCState *env) /* FIXME: choose CSRR1 or MCSRR1 based on cpu type */ do_rfi(env, env->spr[SPR_BOOKE_MCSRR0], env->spr[SPR_BOOKE_MCSRR1]); } -#endif /* !CONFIG_USER_ONLY */ - -void helper_TW(CPUPPCState *env, target_ulong arg1, target_ulong arg2, - uint32_t flags) -{ - if (!likely(!(((int32_t)arg1 < (int32_t)arg2 && (flags & 0x10)) || - ((int32_t)arg1 > (int32_t)arg2 && (flags & 0x08)) || - ((int32_t)arg1 == (int32_t)arg2 && (flags & 0x04)) || - ((uint32_t)arg1 < (uint32_t)arg2 && (flags & 0x02)) || - ((uint32_t)arg1 > (uint32_t)arg2 && (flags & 0x01))))) { - raise_exception_err_ra(env, POWERPC_EXCP_PROGRAM, - POWERPC_EXCP_TRAP, GETPC()); - } -} - -#ifdef TARGET_PPC64 -void helper_TD(CPUPPCState *env, target_ulong arg1, target_ulong arg2, - uint32_t flags) -{ - if (!likely(!(((int64_t)arg1 < (int64_t)arg2 && (flags & 0x10)) || - ((int64_t)arg1 > (int64_t)arg2 && (flags & 0x08)) || - ((int64_t)arg1 == (int64_t)arg2 && (flags & 0x04)) || - ((uint64_t)arg1 < (uint64_t)arg2 && (flags & 0x02)) || - ((uint64_t)arg1 > (uint64_t)arg2 && (flags & 0x01))))) { - raise_exception_err_ra(env, POWERPC_EXCP_PROGRAM, - POWERPC_EXCP_TRAP, GETPC()); - } -} -#endif /* TARGET_PPC64 */ - -static uint32_t helper_SIMON_LIKE_32_64(uint32_t x, uint64_t key, uint32_t lane) -{ - const uint16_t c = 0xfffc; - const uint64_t z0 = 0xfa2561cdf44ac398ULL; - uint16_t z = 0, temp; - uint16_t k[32], eff_k[32], xleft[33], xright[33], fxleft[32]; - - for (int i = 3; i >= 0; i--) { - k[i] = key & 0xffff; - key >>= 16; - } - xleft[0] = x & 0xffff; - xright[0] = (x >> 16) & 0xffff; - - for (int i = 0; i < 28; i++) { - z = (z0 >> (63 - i)) & 1; - temp = ror16(k[i + 3], 3) ^ k[i + 1]; - k[i + 4] = c ^ z ^ k[i] ^ temp ^ ror16(temp, 1); - } - - for (int i = 0; i < 8; i++) { - eff_k[4 * i + 0] = k[4 * i + ((0 + lane) % 4)]; - eff_k[4 * i + 1] = k[4 * i + ((1 + lane) % 4)]; - eff_k[4 * i + 2] = k[4 * i + ((2 + lane) % 4)]; - eff_k[4 * i + 3] = k[4 * i + ((3 + lane) % 4)]; - } - - for (int i = 0; i < 32; i++) { - fxleft[i] = (rol16(xleft[i], 1) & - rol16(xleft[i], 8)) ^ rol16(xleft[i], 2); - xleft[i + 1] = xright[i] ^ fxleft[i] ^ eff_k[i]; - xright[i + 1] = xleft[i]; - } - - return (((uint32_t)xright[32]) << 16) | xleft[32]; -} -static uint64_t hash_digest(uint64_t ra, uint64_t rb, uint64_t key) -{ - uint64_t stage0_h = 0ULL, stage0_l = 0ULL; - uint64_t stage1_h, stage1_l; - - for (int i = 0; i < 4; i++) { - stage0_h |= ror64(rb & 0xff, 8 * (2 * i + 1)); - stage0_h |= ((ra >> 32) & 0xff) << (8 * 2 * i); - stage0_l |= ror64((rb >> 32) & 0xff, 8 * (2 * i + 1)); - stage0_l |= (ra & 0xff) << (8 * 2 * i); - rb >>= 8; - ra >>= 8; - } - - stage1_h = (uint64_t)helper_SIMON_LIKE_32_64(stage0_h >> 32, key, 0) << 32; - stage1_h |= helper_SIMON_LIKE_32_64(stage0_h, key, 1); - stage1_l = (uint64_t)helper_SIMON_LIKE_32_64(stage0_l >> 32, key, 2) << 32; - stage1_l |= helper_SIMON_LIKE_32_64(stage0_l, key, 3); - - return stage1_h ^ stage1_l; -} - -static void do_hash(CPUPPCState *env, target_ulong ea, target_ulong ra, - target_ulong rb, uint64_t key, bool store) -{ - uint64_t calculated_hash = hash_digest(ra, rb, key), loaded_hash; - - if (store) { - cpu_stq_data_ra(env, ea, calculated_hash, GETPC()); - } else { - loaded_hash = cpu_ldq_data_ra(env, ea, GETPC()); - if (loaded_hash != calculated_hash) { - raise_exception_err_ra(env, POWERPC_EXCP_PROGRAM, - POWERPC_EXCP_TRAP, GETPC()); - } - } -} - -#include "qemu/guest-random.h" - -#ifdef TARGET_PPC64 -#define HELPER_HASH(op, key, store, dexcr_aspect) \ -void helper_##op(CPUPPCState *env, target_ulong ea, target_ulong ra, \ - target_ulong rb) \ -{ \ - if (env->msr & R_MSR_PR_MASK) { \ - if (!(env->spr[SPR_DEXCR] & R_DEXCR_PRO_##dexcr_aspect##_MASK || \ - env->spr[SPR_HDEXCR] & R_HDEXCR_ENF_##dexcr_aspect##_MASK)) \ - return; \ - } else if (!(env->msr & R_MSR_HV_MASK)) { \ - if (!(env->spr[SPR_DEXCR] & R_DEXCR_PNH_##dexcr_aspect##_MASK || \ - env->spr[SPR_HDEXCR] & R_HDEXCR_ENF_##dexcr_aspect##_MASK)) \ - return; \ - } else if (!(env->msr & R_MSR_S_MASK)) { \ - if (!(env->spr[SPR_HDEXCR] & R_HDEXCR_HNU_##dexcr_aspect##_MASK)) \ - return; \ - } \ - \ - do_hash(env, ea, ra, rb, key, store); \ -} -#else -#define HELPER_HASH(op, key, store, dexcr_aspect) \ -void helper_##op(CPUPPCState *env, target_ulong ea, target_ulong ra, \ - target_ulong rb) \ -{ \ - do_hash(env, ea, ra, rb, key, store); \ -} -#endif /* TARGET_PPC64 */ - -HELPER_HASH(HASHST, env->spr[SPR_HASHKEYR], true, NPHIE) -HELPER_HASH(HASHCHK, env->spr[SPR_HASHKEYR], false, NPHIE) -HELPER_HASH(HASHSTP, env->spr[SPR_HASHPKEYR], true, PHIE) -HELPER_HASH(HASHCHKP, env->spr[SPR_HASHPKEYR], false, PHIE) - -#ifndef CONFIG_USER_ONLY /* Embedded.Processor Control */ static int dbell2irq(target_ulong rb) { diff --git a/target/ppc/tcg-excp_helper.c b/target/ppc/tcg-excp_helper.c index 268a1614597a..2459d2d095af 100644 --- a/target/ppc/tcg-excp_helper.c +++ b/target/ppc/tcg-excp_helper.c @@ -66,6 +66,149 @@ void raise_exception(CPUPPCState *env, uint32_t exception) raise_exception_err_ra(env, exception, 0, 0); } +#endif /* !CONFIG_USER_ONLY */ + +void helper_TW(CPUPPCState *env, target_ulong arg1, target_ulong arg2, + uint32_t flags) +{ + if (!likely(!(((int32_t)arg1 < (int32_t)arg2 && (flags & 0x10)) || + ((int32_t)arg1 > (int32_t)arg2 && (flags & 0x08)) || + ((int32_t)arg1 == (int32_t)arg2 && (flags & 0x04)) || + ((uint32_t)arg1 < (uint32_t)arg2 && (flags & 0x02)) || + ((uint32_t)arg1 > (uint32_t)arg2 && (flags & 0x01))))) { + raise_exception_err_ra(env, POWERPC_EXCP_PROGRAM, + POWERPC_EXCP_TRAP, GETPC()); + } +} + +#ifdef TARGET_PPC64 +void helper_TD(CPUPPCState *env, target_ulong arg1, target_ulong arg2, + uint32_t flags) +{ + if (!likely(!(((int64_t)arg1 < (int64_t)arg2 && (flags & 0x10)) || + ((int64_t)arg1 > (int64_t)arg2 && (flags & 0x08)) || + ((int64_t)arg1 == (int64_t)arg2 && (flags & 0x04)) || + ((uint64_t)arg1 < (uint64_t)arg2 && (flags & 0x02)) || + ((uint64_t)arg1 > (uint64_t)arg2 && (flags & 0x01))))) { + raise_exception_err_ra(env, POWERPC_EXCP_PROGRAM, + POWERPC_EXCP_TRAP, GETPC()); + } +} +#endif /* TARGET_PPC64 */ + +static uint32_t helper_SIMON_LIKE_32_64(uint32_t x, uint64_t key, uint32_t lane) +{ + const uint16_t c = 0xfffc; + const uint64_t z0 = 0xfa2561cdf44ac398ULL; + uint16_t z = 0, temp; + uint16_t k[32], eff_k[32], xleft[33], xright[33], fxleft[32]; + + for (int i = 3; i >= 0; i--) { + k[i] = key & 0xffff; + key >>= 16; + } + xleft[0] = x & 0xffff; + xright[0] = (x >> 16) & 0xffff; + + for (int i = 0; i < 28; i++) { + z = (z0 >> (63 - i)) & 1; + temp = ror16(k[i + 3], 3) ^ k[i + 1]; + k[i + 4] = c ^ z ^ k[i] ^ temp ^ ror16(temp, 1); + } + + for (int i = 0; i < 8; i++) { + eff_k[4 * i + 0] = k[4 * i + ((0 + lane) % 4)]; + eff_k[4 * i + 1] = k[4 * i + ((1 + lane) % 4)]; + eff_k[4 * i + 2] = k[4 * i + ((2 + lane) % 4)]; + eff_k[4 * i + 3] = k[4 * i + ((3 + lane) % 4)]; + } + + for (int i = 0; i < 32; i++) { + fxleft[i] = (rol16(xleft[i], 1) & + rol16(xleft[i], 8)) ^ rol16(xleft[i], 2); + xleft[i + 1] = xright[i] ^ fxleft[i] ^ eff_k[i]; + xright[i + 1] = xleft[i]; + } + + return (((uint32_t)xright[32]) << 16) | xleft[32]; +} + +static uint64_t hash_digest(uint64_t ra, uint64_t rb, uint64_t key) +{ + uint64_t stage0_h = 0ULL, stage0_l = 0ULL; + uint64_t stage1_h, stage1_l; + + for (int i = 0; i < 4; i++) { + stage0_h |= ror64(rb & 0xff, 8 * (2 * i + 1)); + stage0_h |= ((ra >> 32) & 0xff) << (8 * 2 * i); + stage0_l |= ror64((rb >> 32) & 0xff, 8 * (2 * i + 1)); + stage0_l |= (ra & 0xff) << (8 * 2 * i); + rb >>= 8; + ra >>= 8; + } + + stage1_h = (uint64_t)helper_SIMON_LIKE_32_64(stage0_h >> 32, key, 0) << 32; + stage1_h |= helper_SIMON_LIKE_32_64(stage0_h, key, 1); + stage1_l = (uint64_t)helper_SIMON_LIKE_32_64(stage0_l >> 32, key, 2) << 32; + stage1_l |= helper_SIMON_LIKE_32_64(stage0_l, key, 3); + + return stage1_h ^ stage1_l; +} + +static void do_hash(CPUPPCState *env, target_ulong ea, target_ulong ra, + target_ulong rb, uint64_t key, bool store) +{ + uint64_t calculated_hash = hash_digest(ra, rb, key), loaded_hash; + + if (store) { + cpu_stq_data_ra(env, ea, calculated_hash, GETPC()); + } else { + loaded_hash = cpu_ldq_data_ra(env, ea, GETPC()); + if (loaded_hash != calculated_hash) { + raise_exception_err_ra(env, POWERPC_EXCP_PROGRAM, + POWERPC_EXCP_TRAP, GETPC()); + } + } +} + +#include "qemu/guest-random.h" + +#ifdef TARGET_PPC64 +#define HELPER_HASH(op, key, store, dexcr_aspect) \ +void helper_##op(CPUPPCState *env, target_ulong ea, target_ulong ra, \ + target_ulong rb) \ +{ \ + if (env->msr & R_MSR_PR_MASK) { \ + if (!(env->spr[SPR_DEXCR] & R_DEXCR_PRO_##dexcr_aspect##_MASK || \ + env->spr[SPR_HDEXCR] & R_HDEXCR_ENF_##dexcr_aspect##_MASK)) \ + return; \ + } else if (!(env->msr & R_MSR_HV_MASK)) { \ + if (!(env->spr[SPR_DEXCR] & R_DEXCR_PNH_##dexcr_aspect##_MASK || \ + env->spr[SPR_HDEXCR] & R_HDEXCR_ENF_##dexcr_aspect##_MASK)) \ + return; \ + } else if (!(env->msr & R_MSR_S_MASK)) { \ + if (!(env->spr[SPR_HDEXCR] & R_HDEXCR_HNU_##dexcr_aspect##_MASK)) \ + return; \ + } \ + \ + do_hash(env, ea, ra, rb, key, store); \ +} +#else +#define HELPER_HASH(op, key, store, dexcr_aspect) \ +void helper_##op(CPUPPCState *env, target_ulong ea, target_ulong ra, \ + target_ulong rb) \ +{ \ + do_hash(env, ea, ra, rb, key, store); \ +} +#endif /* TARGET_PPC64 */ + +HELPER_HASH(HASHST, env->spr[SPR_HASHKEYR], true, NPHIE) +HELPER_HASH(HASHCHK, env->spr[SPR_HASHKEYR], false, NPHIE) +HELPER_HASH(HASHSTP, env->spr[SPR_HASHPKEYR], true, PHIE) +HELPER_HASH(HASHCHKP, env->spr[SPR_HASHPKEYR], false, PHIE) + +#ifndef CONFIG_USER_ONLY + void ppc_cpu_do_unaligned_access(CPUState *cs, vaddr vaddr, MMUAccessType access_type, int mmu_idx, uintptr_t retaddr) From b8d6a858fef723616f5a0e244ef0802700ab88d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:16 +0100 Subject: [PATCH 0930/1179] target/ppc: Fix style in excp_helper.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix style in do_rfi() before moving the code around. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-13-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 511e27f7262a..659852543f3d 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -2558,8 +2558,9 @@ static void do_rfi(CPUPPCState *env, target_ulong nip, target_ulong msr) msr &= ~(1ULL << MSR_POW); /* MSR:TGPR cannot be set by any form of rfi */ - if (env->flags & POWERPC_FLAG_TGPR) + if (env->flags & POWERPC_FLAG_TGPR) { msr &= ~(1ULL << MSR_TGPR); + } #ifdef TARGET_PPC64 /* Switching to 32-bit ? Crop the nip */ From 92c787de34d6103613fa7453765603c94a0494e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:17 +0100 Subject: [PATCH 0931/1179] target/ppc: Make powerpc_excp() prototype public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to move TCG specific code dependent on powerpc_excp() in the next commit, expose its prototype in "internal.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-14-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/excp_helper.c | 2 +- target/ppc/internal.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 659852543f3d..9ba533569837 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -1571,7 +1571,7 @@ static inline void powerpc_excp_books(PowerPCCPU *cpu, int excp) } #endif /* TARGET_PPC64 */ -static void powerpc_excp(PowerPCCPU *cpu, int excp) +void powerpc_excp(PowerPCCPU *cpu, int excp) { CPUPPCState *env = &cpu->env; diff --git a/target/ppc/internal.h b/target/ppc/internal.h index 62186bc1e614..9012d3809cb7 100644 --- a/target/ppc/internal.h +++ b/target/ppc/internal.h @@ -291,6 +291,8 @@ bool ppc_cpu_debug_check_breakpoint(CPUState *cs); bool ppc_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp); G_NORETURN void powerpc_checkstop(CPUPPCState *env, const char *reason); +void powerpc_excp(PowerPCCPU *cpu, int excp); + #endif /* !CONFIG_USER_ONLY */ FIELD(GER_MSK, XMSK, 0, 4) From c2c687013dcc998b02570e02bff373738a636a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Jan 2025 11:26:18 +0100 Subject: [PATCH 0932/1179] target/ppc: Restrict ATTN / SCV / PMINSN helpers to TCG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move helper_attn(), helper_scv() and helper_pminsn() to tcg-excp_helper.c. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20250127102620.39159-15-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- target/ppc/cpu.h | 3 - target/ppc/excp_helper.c | 434 ----------------------------------- target/ppc/tcg-excp_helper.c | 423 +++++++++++++++++++++++++++++++++- 3 files changed, 421 insertions(+), 439 deletions(-) diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 505b5897142a..8d43983fe1ac 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -2755,9 +2755,6 @@ static inline void cpu_get_tb_cpu_state(CPUPPCState *env, vaddr *pc, } #endif -G_NORETURN void raise_exception(CPUPPCState *env, uint32_t exception); -G_NORETURN void raise_exception_err(CPUPPCState *env, uint32_t exception, - uint32_t error_code); G_NORETURN void raise_exception_err_ra(CPUPPCState *env, uint32_t exception, uint32_t error_code, uintptr_t raddr); diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c index 9ba533569837..44e19aacd8d8 100644 --- a/target/ppc/excp_helper.c +++ b/target/ppc/excp_helper.c @@ -30,11 +30,6 @@ #include "trace.h" -#ifdef CONFIG_TCG -#include "exec/helper-proto.h" -#include "exec/cpu_ldst.h" -#endif - /*****************************************************************************/ /* Exception processing */ #ifndef CONFIG_USER_ONLY @@ -399,21 +394,6 @@ static void powerpc_set_excp_state(PowerPCCPU *cpu, target_ulong vector, env->reserve_addr = -1; } -#ifdef CONFIG_TCG -#if defined(TARGET_PPC64) && !defined(CONFIG_USER_ONLY) -void helper_attn(CPUPPCState *env) -{ - /* POWER attn is unprivileged when enabled by HID, otherwise illegal */ - if ((*env->check_attn)(env)) { - powerpc_checkstop(env, "host executed attn"); - } else { - raise_exception_err(env, POWERPC_EXCP_HV_EMU, - POWERPC_EXCP_INVAL | POWERPC_EXCP_INVAL_INVAL); - } -} -#endif -#endif /* CONFIG_TCG */ - static void powerpc_mcheck_checkstop(CPUPPCState *env) { /* KVM guests always have MSR[ME] enabled */ @@ -2503,417 +2483,3 @@ bool ppc_cpu_exec_interrupt(CPUState *cs, int interrupt_request) } #endif /* !CONFIG_USER_ONLY */ - -#ifdef CONFIG_TCG - -#ifndef CONFIG_USER_ONLY -void helper_store_msr(CPUPPCState *env, target_ulong val) -{ - uint32_t excp = hreg_store_msr(env, val, 0); - - if (excp != 0) { - cpu_interrupt_exittb(env_cpu(env)); - raise_exception(env, excp); - } -} - -void helper_ppc_maybe_interrupt(CPUPPCState *env) -{ - ppc_maybe_interrupt(env); -} - -#ifdef TARGET_PPC64 -void helper_scv(CPUPPCState *env, uint32_t lev) -{ - if (env->spr[SPR_FSCR] & (1ull << FSCR_SCV)) { - raise_exception_err(env, POWERPC_EXCP_SYSCALL_VECTORED, lev); - } else { - raise_exception_err(env, POWERPC_EXCP_FU, FSCR_IC_SCV); - } -} - -void helper_pminsn(CPUPPCState *env, uint32_t insn) -{ - CPUState *cs = env_cpu(env); - - cs->halted = 1; - - /* Condition for waking up at 0x100 */ - env->resume_as_sreset = (insn != PPC_PM_STOP) || - (env->spr[SPR_PSSCR] & PSSCR_EC); - - /* HDECR is not to wake from PM state, it may have already fired */ - if (env->resume_as_sreset) { - PowerPCCPU *cpu = env_archcpu(env); - ppc_set_irq(cpu, PPC_INTERRUPT_HDECR, 0); - } - - ppc_maybe_interrupt(env); -} -#endif /* TARGET_PPC64 */ - -static void do_rfi(CPUPPCState *env, target_ulong nip, target_ulong msr) -{ - /* MSR:POW cannot be set by any form of rfi */ - msr &= ~(1ULL << MSR_POW); - - /* MSR:TGPR cannot be set by any form of rfi */ - if (env->flags & POWERPC_FLAG_TGPR) { - msr &= ~(1ULL << MSR_TGPR); - } - -#ifdef TARGET_PPC64 - /* Switching to 32-bit ? Crop the nip */ - if (!msr_is_64bit(env, msr)) { - nip = (uint32_t)nip; - } -#else - nip = (uint32_t)nip; -#endif - /* XXX: beware: this is false if VLE is supported */ - env->nip = nip & ~((target_ulong)0x00000003); - hreg_store_msr(env, msr, 1); - trace_ppc_excp_rfi(env->nip, env->msr); - /* - * No need to raise an exception here, as rfi is always the last - * insn of a TB - */ - cpu_interrupt_exittb(env_cpu(env)); - /* Reset the reservation */ - env->reserve_addr = -1; - - /* Context synchronizing: check if TCG TLB needs flush */ - check_tlb_flush(env, false); -} - -void helper_rfi(CPUPPCState *env) -{ - do_rfi(env, env->spr[SPR_SRR0], env->spr[SPR_SRR1] & 0xfffffffful); -} - -#ifdef TARGET_PPC64 -void helper_rfid(CPUPPCState *env) -{ - /* - * The architecture defines a number of rules for which bits can - * change but in practice, we handle this in hreg_store_msr() - * which will be called by do_rfi(), so there is no need to filter - * here - */ - do_rfi(env, env->spr[SPR_SRR0], env->spr[SPR_SRR1]); -} - -void helper_rfscv(CPUPPCState *env) -{ - do_rfi(env, env->lr, env->ctr); -} - -void helper_hrfid(CPUPPCState *env) -{ - do_rfi(env, env->spr[SPR_HSRR0], env->spr[SPR_HSRR1]); -} - -void helper_rfebb(CPUPPCState *env, target_ulong s) -{ - target_ulong msr = env->msr; - - /* - * Handling of BESCR bits 32:33 according to PowerISA v3.1: - * - * "If BESCR 32:33 != 0b00 the instruction is treated as if - * the instruction form were invalid." - */ - if (env->spr[SPR_BESCR] & BESCR_INVALID) { - raise_exception_err(env, POWERPC_EXCP_PROGRAM, - POWERPC_EXCP_INVAL | POWERPC_EXCP_INVAL_INVAL); - } - - env->nip = env->spr[SPR_EBBRR]; - - /* Switching to 32-bit ? Crop the nip */ - if (!msr_is_64bit(env, msr)) { - env->nip = (uint32_t)env->spr[SPR_EBBRR]; - } - - if (s) { - env->spr[SPR_BESCR] |= BESCR_GE; - } else { - env->spr[SPR_BESCR] &= ~BESCR_GE; - } -} - -/* - * Triggers or queues an 'ebb_excp' EBB exception. All checks - * but FSCR, HFSCR and msr_pr must be done beforehand. - * - * PowerISA v3.1 isn't clear about whether an EBB should be - * postponed or cancelled if the EBB facility is unavailable. - * Our assumption here is that the EBB is cancelled if both - * FSCR and HFSCR EBB facilities aren't available. - */ -static void do_ebb(CPUPPCState *env, int ebb_excp) -{ - PowerPCCPU *cpu = env_archcpu(env); - - /* - * FSCR_EBB and FSCR_IC_EBB are the same bits used with - * HFSCR. - */ - helper_fscr_facility_check(env, FSCR_EBB, 0, FSCR_IC_EBB); - helper_hfscr_facility_check(env, FSCR_EBB, "EBB", FSCR_IC_EBB); - - if (ebb_excp == POWERPC_EXCP_PERFM_EBB) { - env->spr[SPR_BESCR] |= BESCR_PMEO; - } else if (ebb_excp == POWERPC_EXCP_EXTERNAL_EBB) { - env->spr[SPR_BESCR] |= BESCR_EEO; - } - - if (FIELD_EX64(env->msr, MSR, PR)) { - powerpc_excp(cpu, ebb_excp); - } else { - ppc_set_irq(cpu, PPC_INTERRUPT_EBB, 1); - } -} - -void raise_ebb_perfm_exception(CPUPPCState *env) -{ - bool perfm_ebb_enabled = env->spr[SPR_POWER_MMCR0] & MMCR0_EBE && - env->spr[SPR_BESCR] & BESCR_PME && - env->spr[SPR_BESCR] & BESCR_GE; - - if (!perfm_ebb_enabled) { - return; - } - - do_ebb(env, POWERPC_EXCP_PERFM_EBB); -} -#endif /* TARGET_PPC64 */ - -/*****************************************************************************/ -/* Embedded PowerPC specific helpers */ -void helper_40x_rfci(CPUPPCState *env) -{ - do_rfi(env, env->spr[SPR_40x_SRR2], env->spr[SPR_40x_SRR3]); -} - -void helper_rfci(CPUPPCState *env) -{ - do_rfi(env, env->spr[SPR_BOOKE_CSRR0], env->spr[SPR_BOOKE_CSRR1]); -} - -void helper_rfdi(CPUPPCState *env) -{ - /* FIXME: choose CSRR1 or DSRR1 based on cpu type */ - do_rfi(env, env->spr[SPR_BOOKE_DSRR0], env->spr[SPR_BOOKE_DSRR1]); -} - -void helper_rfmci(CPUPPCState *env) -{ - /* FIXME: choose CSRR1 or MCSRR1 based on cpu type */ - do_rfi(env, env->spr[SPR_BOOKE_MCSRR0], env->spr[SPR_BOOKE_MCSRR1]); -} - -/* Embedded.Processor Control */ -static int dbell2irq(target_ulong rb) -{ - int msg = rb & DBELL_TYPE_MASK; - int irq = -1; - - switch (msg) { - case DBELL_TYPE_DBELL: - irq = PPC_INTERRUPT_DOORBELL; - break; - case DBELL_TYPE_DBELL_CRIT: - irq = PPC_INTERRUPT_CDOORBELL; - break; - case DBELL_TYPE_G_DBELL: - case DBELL_TYPE_G_DBELL_CRIT: - case DBELL_TYPE_G_DBELL_MC: - /* XXX implement */ - default: - break; - } - - return irq; -} - -void helper_msgclr(CPUPPCState *env, target_ulong rb) -{ - int irq = dbell2irq(rb); - - if (irq < 0) { - return; - } - - ppc_set_irq(env_archcpu(env), irq, 0); -} - -void helper_msgsnd(target_ulong rb) -{ - int irq = dbell2irq(rb); - int pir = rb & DBELL_PIRTAG_MASK; - CPUState *cs; - - if (irq < 0) { - return; - } - - bql_lock(); - CPU_FOREACH(cs) { - PowerPCCPU *cpu = POWERPC_CPU(cs); - CPUPPCState *cenv = &cpu->env; - - if ((rb & DBELL_BRDCAST_MASK) || (cenv->spr[SPR_BOOKE_PIR] == pir)) { - ppc_set_irq(cpu, irq, 1); - } - } - bql_unlock(); -} - -/* Server Processor Control */ - -static bool dbell_type_server(target_ulong rb) -{ - /* - * A Directed Hypervisor Doorbell message is sent only if the - * message type is 5. All other types are reserved and the - * instruction is a no-op - */ - return (rb & DBELL_TYPE_MASK) == DBELL_TYPE_DBELL_SERVER; -} - -static inline bool dbell_bcast_core(target_ulong rb) -{ - return (rb & DBELL_BRDCAST_MASK) == DBELL_BRDCAST_CORE; -} - -static inline bool dbell_bcast_subproc(target_ulong rb) -{ - return (rb & DBELL_BRDCAST_MASK) == DBELL_BRDCAST_SUBPROC; -} - -/* - * Send an interrupt to a thread in the same core as env). - */ -static void msgsnd_core_tir(CPUPPCState *env, uint32_t target_tir, int irq) -{ - PowerPCCPU *cpu = env_archcpu(env); - CPUState *cs = env_cpu(env); - - if (ppc_cpu_lpar_single_threaded(cs)) { - if (target_tir == 0) { - ppc_set_irq(cpu, irq, 1); - } - } else { - CPUState *ccs; - - /* Does iothread need to be locked for walking CPU list? */ - bql_lock(); - THREAD_SIBLING_FOREACH(cs, ccs) { - PowerPCCPU *ccpu = POWERPC_CPU(ccs); - if (target_tir == ppc_cpu_tir(ccpu)) { - ppc_set_irq(ccpu, irq, 1); - break; - } - } - bql_unlock(); - } -} - -void helper_book3s_msgclr(CPUPPCState *env, target_ulong rb) -{ - if (!dbell_type_server(rb)) { - return; - } - - ppc_set_irq(env_archcpu(env), PPC_INTERRUPT_HDOORBELL, 0); -} - -void helper_book3s_msgsnd(CPUPPCState *env, target_ulong rb) -{ - int pir = rb & DBELL_PROCIDTAG_MASK; - bool brdcast = false; - CPUState *cs, *ccs; - PowerPCCPU *cpu; - - if (!dbell_type_server(rb)) { - return; - } - - /* POWER8 msgsnd is like msgsndp (targets a thread within core) */ - if (!(env->insns_flags2 & PPC2_ISA300)) { - msgsnd_core_tir(env, rb & PPC_BITMASK(57, 63), PPC_INTERRUPT_HDOORBELL); - return; - } - - /* POWER9 and later msgsnd is a global (targets any thread) */ - cpu = ppc_get_vcpu_by_pir(pir); - if (!cpu) { - return; - } - cs = CPU(cpu); - - if (dbell_bcast_core(rb) || (dbell_bcast_subproc(rb) && - (env->flags & POWERPC_FLAG_SMT_1LPAR))) { - brdcast = true; - } - - if (ppc_cpu_core_single_threaded(cs) || !brdcast) { - ppc_set_irq(cpu, PPC_INTERRUPT_HDOORBELL, 1); - return; - } - - /* - * Why is bql needed for walking CPU list? Answer seems to be because ppc - * irq handling needs it, but ppc_set_irq takes the lock itself if needed, - * so could this be removed? - */ - bql_lock(); - THREAD_SIBLING_FOREACH(cs, ccs) { - ppc_set_irq(POWERPC_CPU(ccs), PPC_INTERRUPT_HDOORBELL, 1); - } - bql_unlock(); -} - -#ifdef TARGET_PPC64 -void helper_book3s_msgclrp(CPUPPCState *env, target_ulong rb) -{ - helper_hfscr_facility_check(env, HFSCR_MSGP, "msgclrp", HFSCR_IC_MSGP); - - if (!dbell_type_server(rb)) { - return; - } - - ppc_set_irq(env_archcpu(env), PPC_INTERRUPT_DOORBELL, 0); -} - -/* - * sends a message to another thread on the same - * multi-threaded processor - */ -void helper_book3s_msgsndp(CPUPPCState *env, target_ulong rb) -{ - helper_hfscr_facility_check(env, HFSCR_MSGP, "msgsndp", HFSCR_IC_MSGP); - - if (!dbell_type_server(rb)) { - return; - } - - msgsnd_core_tir(env, rb & PPC_BITMASK(57, 63), PPC_INTERRUPT_DOORBELL); -} -#endif /* TARGET_PPC64 */ - -/* Single-step tracing */ -void helper_book3s_trace(CPUPPCState *env, target_ulong prev_ip) -{ - uint32_t error_code = 0; - if (env->insns_flags2 & PPC2_ISA207S) { - /* Load/store reporting, SRR1[35, 36] and SDAR, are not implemented. */ - env->spr[SPR_POWER_SIAR] = prev_ip; - error_code = PPC_BIT(33); - } - raise_exception_err(env, POWERPC_EXCP_TRACE, error_code); -} - -#endif /* !CONFIG_USER_ONLY */ -#endif /* CONFIG_TCG */ diff --git a/target/ppc/tcg-excp_helper.c b/target/ppc/tcg-excp_helper.c index 2459d2d095af..4b859a8ffaa4 100644 --- a/target/ppc/tcg-excp_helper.c +++ b/target/ppc/tcg-excp_helper.c @@ -17,6 +17,7 @@ * License along with this library; if not, see . */ #include "qemu/osdep.h" +#include "qemu/main-loop.h" #include "qemu/log.h" #include "exec/cpu_ldst.h" #include "exec/exec-all.h" @@ -55,13 +56,13 @@ void helper_raise_exception(CPUPPCState *env, uint32_t exception) #ifndef CONFIG_USER_ONLY -void raise_exception_err(CPUPPCState *env, uint32_t exception, +static G_NORETURN void raise_exception_err(CPUPPCState *env, uint32_t exception, uint32_t error_code) { raise_exception_err_ra(env, exception, error_code, 0); } -void raise_exception(CPUPPCState *env, uint32_t exception) +static G_NORETURN void raise_exception(CPUPPCState *env, uint32_t exception) { raise_exception_err_ra(env, exception, 0, 0); } @@ -426,4 +427,422 @@ uint32_t ppc_ldl_code(CPUArchState *env, target_ulong addr) return insn; } +#if defined(TARGET_PPC64) +void helper_attn(CPUPPCState *env) +{ + /* POWER attn is unprivileged when enabled by HID, otherwise illegal */ + if ((*env->check_attn)(env)) { + powerpc_checkstop(env, "host executed attn"); + } else { + raise_exception_err(env, POWERPC_EXCP_HV_EMU, + POWERPC_EXCP_INVAL | POWERPC_EXCP_INVAL_INVAL); + } +} + +void helper_scv(CPUPPCState *env, uint32_t lev) +{ + if (env->spr[SPR_FSCR] & (1ull << FSCR_SCV)) { + raise_exception_err(env, POWERPC_EXCP_SYSCALL_VECTORED, lev); + } else { + raise_exception_err(env, POWERPC_EXCP_FU, FSCR_IC_SCV); + } +} + +void helper_pminsn(CPUPPCState *env, uint32_t insn) +{ + CPUState *cs = env_cpu(env); + + cs->halted = 1; + + /* Condition for waking up at 0x100 */ + env->resume_as_sreset = (insn != PPC_PM_STOP) || + (env->spr[SPR_PSSCR] & PSSCR_EC); + + /* HDECR is not to wake from PM state, it may have already fired */ + if (env->resume_as_sreset) { + PowerPCCPU *cpu = env_archcpu(env); + ppc_set_irq(cpu, PPC_INTERRUPT_HDECR, 0); + } + + ppc_maybe_interrupt(env); +} + +#endif /* TARGET_PPC64 */ +void helper_store_msr(CPUPPCState *env, target_ulong val) +{ + uint32_t excp = hreg_store_msr(env, val, 0); + + if (excp != 0) { + cpu_interrupt_exittb(env_cpu(env)); + raise_exception(env, excp); + } +} + +void helper_ppc_maybe_interrupt(CPUPPCState *env) +{ + ppc_maybe_interrupt(env); +} + +static void do_rfi(CPUPPCState *env, target_ulong nip, target_ulong msr) +{ + /* MSR:POW cannot be set by any form of rfi */ + msr &= ~(1ULL << MSR_POW); + + /* MSR:TGPR cannot be set by any form of rfi */ + if (env->flags & POWERPC_FLAG_TGPR) { + msr &= ~(1ULL << MSR_TGPR); + } + +#ifdef TARGET_PPC64 + /* Switching to 32-bit ? Crop the nip */ + if (!msr_is_64bit(env, msr)) { + nip = (uint32_t)nip; + } +#else + nip = (uint32_t)nip; +#endif + /* XXX: beware: this is false if VLE is supported */ + env->nip = nip & ~((target_ulong)0x00000003); + hreg_store_msr(env, msr, 1); + trace_ppc_excp_rfi(env->nip, env->msr); + /* + * No need to raise an exception here, as rfi is always the last + * insn of a TB + */ + cpu_interrupt_exittb(env_cpu(env)); + /* Reset the reservation */ + env->reserve_addr = -1; + + /* Context synchronizing: check if TCG TLB needs flush */ + check_tlb_flush(env, false); +} + +void helper_rfi(CPUPPCState *env) +{ + do_rfi(env, env->spr[SPR_SRR0], env->spr[SPR_SRR1] & 0xfffffffful); +} + +#ifdef TARGET_PPC64 +void helper_rfid(CPUPPCState *env) +{ + /* + * The architecture defines a number of rules for which bits can + * change but in practice, we handle this in hreg_store_msr() + * which will be called by do_rfi(), so there is no need to filter + * here + */ + do_rfi(env, env->spr[SPR_SRR0], env->spr[SPR_SRR1]); +} + +void helper_rfscv(CPUPPCState *env) +{ + do_rfi(env, env->lr, env->ctr); +} + +void helper_hrfid(CPUPPCState *env) +{ + do_rfi(env, env->spr[SPR_HSRR0], env->spr[SPR_HSRR1]); +} + +void helper_rfebb(CPUPPCState *env, target_ulong s) +{ + target_ulong msr = env->msr; + + /* + * Handling of BESCR bits 32:33 according to PowerISA v3.1: + * + * "If BESCR 32:33 != 0b00 the instruction is treated as if + * the instruction form were invalid." + */ + if (env->spr[SPR_BESCR] & BESCR_INVALID) { + raise_exception_err(env, POWERPC_EXCP_PROGRAM, + POWERPC_EXCP_INVAL | POWERPC_EXCP_INVAL_INVAL); + } + + env->nip = env->spr[SPR_EBBRR]; + + /* Switching to 32-bit ? Crop the nip */ + if (!msr_is_64bit(env, msr)) { + env->nip = (uint32_t)env->spr[SPR_EBBRR]; + } + + if (s) { + env->spr[SPR_BESCR] |= BESCR_GE; + } else { + env->spr[SPR_BESCR] &= ~BESCR_GE; + } +} + +/* + * Triggers or queues an 'ebb_excp' EBB exception. All checks + * but FSCR, HFSCR and msr_pr must be done beforehand. + * + * PowerISA v3.1 isn't clear about whether an EBB should be + * postponed or cancelled if the EBB facility is unavailable. + * Our assumption here is that the EBB is cancelled if both + * FSCR and HFSCR EBB facilities aren't available. + */ +static void do_ebb(CPUPPCState *env, int ebb_excp) +{ + PowerPCCPU *cpu = env_archcpu(env); + + /* + * FSCR_EBB and FSCR_IC_EBB are the same bits used with + * HFSCR. + */ + helper_fscr_facility_check(env, FSCR_EBB, 0, FSCR_IC_EBB); + helper_hfscr_facility_check(env, FSCR_EBB, "EBB", FSCR_IC_EBB); + + if (ebb_excp == POWERPC_EXCP_PERFM_EBB) { + env->spr[SPR_BESCR] |= BESCR_PMEO; + } else if (ebb_excp == POWERPC_EXCP_EXTERNAL_EBB) { + env->spr[SPR_BESCR] |= BESCR_EEO; + } + + if (FIELD_EX64(env->msr, MSR, PR)) { + powerpc_excp(cpu, ebb_excp); + } else { + ppc_set_irq(cpu, PPC_INTERRUPT_EBB, 1); + } +} + +void raise_ebb_perfm_exception(CPUPPCState *env) +{ + bool perfm_ebb_enabled = env->spr[SPR_POWER_MMCR0] & MMCR0_EBE && + env->spr[SPR_BESCR] & BESCR_PME && + env->spr[SPR_BESCR] & BESCR_GE; + + if (!perfm_ebb_enabled) { + return; + } + + do_ebb(env, POWERPC_EXCP_PERFM_EBB); +} +#endif /* TARGET_PPC64 */ + +/*****************************************************************************/ +/* Embedded PowerPC specific helpers */ +void helper_40x_rfci(CPUPPCState *env) +{ + do_rfi(env, env->spr[SPR_40x_SRR2], env->spr[SPR_40x_SRR3]); +} + +void helper_rfci(CPUPPCState *env) +{ + do_rfi(env, env->spr[SPR_BOOKE_CSRR0], env->spr[SPR_BOOKE_CSRR1]); +} + +void helper_rfdi(CPUPPCState *env) +{ + /* FIXME: choose CSRR1 or DSRR1 based on cpu type */ + do_rfi(env, env->spr[SPR_BOOKE_DSRR0], env->spr[SPR_BOOKE_DSRR1]); +} + +void helper_rfmci(CPUPPCState *env) +{ + /* FIXME: choose CSRR1 or MCSRR1 based on cpu type */ + do_rfi(env, env->spr[SPR_BOOKE_MCSRR0], env->spr[SPR_BOOKE_MCSRR1]); +} + +/* Embedded.Processor Control */ +static int dbell2irq(target_ulong rb) +{ + int msg = rb & DBELL_TYPE_MASK; + int irq = -1; + + switch (msg) { + case DBELL_TYPE_DBELL: + irq = PPC_INTERRUPT_DOORBELL; + break; + case DBELL_TYPE_DBELL_CRIT: + irq = PPC_INTERRUPT_CDOORBELL; + break; + case DBELL_TYPE_G_DBELL: + case DBELL_TYPE_G_DBELL_CRIT: + case DBELL_TYPE_G_DBELL_MC: + /* XXX implement */ + default: + break; + } + + return irq; +} + +void helper_msgclr(CPUPPCState *env, target_ulong rb) +{ + int irq = dbell2irq(rb); + + if (irq < 0) { + return; + } + + ppc_set_irq(env_archcpu(env), irq, 0); +} + +void helper_msgsnd(target_ulong rb) +{ + int irq = dbell2irq(rb); + int pir = rb & DBELL_PIRTAG_MASK; + CPUState *cs; + + if (irq < 0) { + return; + } + + bql_lock(); + CPU_FOREACH(cs) { + PowerPCCPU *cpu = POWERPC_CPU(cs); + CPUPPCState *cenv = &cpu->env; + + if ((rb & DBELL_BRDCAST_MASK) || (cenv->spr[SPR_BOOKE_PIR] == pir)) { + ppc_set_irq(cpu, irq, 1); + } + } + bql_unlock(); +} + +/* Server Processor Control */ + +static bool dbell_type_server(target_ulong rb) +{ + /* + * A Directed Hypervisor Doorbell message is sent only if the + * message type is 5. All other types are reserved and the + * instruction is a no-op + */ + return (rb & DBELL_TYPE_MASK) == DBELL_TYPE_DBELL_SERVER; +} + +static inline bool dbell_bcast_core(target_ulong rb) +{ + return (rb & DBELL_BRDCAST_MASK) == DBELL_BRDCAST_CORE; +} + +static inline bool dbell_bcast_subproc(target_ulong rb) +{ + return (rb & DBELL_BRDCAST_MASK) == DBELL_BRDCAST_SUBPROC; +} + +/* + * Send an interrupt to a thread in the same core as env). + */ +static void msgsnd_core_tir(CPUPPCState *env, uint32_t target_tir, int irq) +{ + PowerPCCPU *cpu = env_archcpu(env); + CPUState *cs = env_cpu(env); + + if (ppc_cpu_lpar_single_threaded(cs)) { + if (target_tir == 0) { + ppc_set_irq(cpu, irq, 1); + } + } else { + CPUState *ccs; + + /* Does iothread need to be locked for walking CPU list? */ + bql_lock(); + THREAD_SIBLING_FOREACH(cs, ccs) { + PowerPCCPU *ccpu = POWERPC_CPU(ccs); + if (target_tir == ppc_cpu_tir(ccpu)) { + ppc_set_irq(ccpu, irq, 1); + break; + } + } + bql_unlock(); + } +} + +void helper_book3s_msgclr(CPUPPCState *env, target_ulong rb) +{ + if (!dbell_type_server(rb)) { + return; + } + + ppc_set_irq(env_archcpu(env), PPC_INTERRUPT_HDOORBELL, 0); +} + +void helper_book3s_msgsnd(CPUPPCState *env, target_ulong rb) +{ + int pir = rb & DBELL_PROCIDTAG_MASK; + bool brdcast = false; + CPUState *cs, *ccs; + PowerPCCPU *cpu; + + if (!dbell_type_server(rb)) { + return; + } + + /* POWER8 msgsnd is like msgsndp (targets a thread within core) */ + if (!(env->insns_flags2 & PPC2_ISA300)) { + msgsnd_core_tir(env, rb & PPC_BITMASK(57, 63), PPC_INTERRUPT_HDOORBELL); + return; + } + + /* POWER9 and later msgsnd is a global (targets any thread) */ + cpu = ppc_get_vcpu_by_pir(pir); + if (!cpu) { + return; + } + cs = CPU(cpu); + + if (dbell_bcast_core(rb) || (dbell_bcast_subproc(rb) && + (env->flags & POWERPC_FLAG_SMT_1LPAR))) { + brdcast = true; + } + + if (ppc_cpu_core_single_threaded(cs) || !brdcast) { + ppc_set_irq(cpu, PPC_INTERRUPT_HDOORBELL, 1); + return; + } + + /* + * Why is bql needed for walking CPU list? Answer seems to be because ppc + * irq handling needs it, but ppc_set_irq takes the lock itself if needed, + * so could this be removed? + */ + bql_lock(); + THREAD_SIBLING_FOREACH(cs, ccs) { + ppc_set_irq(POWERPC_CPU(ccs), PPC_INTERRUPT_HDOORBELL, 1); + } + bql_unlock(); +} + +#ifdef TARGET_PPC64 +void helper_book3s_msgclrp(CPUPPCState *env, target_ulong rb) +{ + helper_hfscr_facility_check(env, HFSCR_MSGP, "msgclrp", HFSCR_IC_MSGP); + + if (!dbell_type_server(rb)) { + return; + } + + ppc_set_irq(env_archcpu(env), PPC_INTERRUPT_DOORBELL, 0); +} + +/* + * sends a message to another thread on the same + * multi-threaded processor + */ +void helper_book3s_msgsndp(CPUPPCState *env, target_ulong rb) +{ + helper_hfscr_facility_check(env, HFSCR_MSGP, "msgsndp", HFSCR_IC_MSGP); + + if (!dbell_type_server(rb)) { + return; + } + + msgsnd_core_tir(env, rb & PPC_BITMASK(57, 63), PPC_INTERRUPT_DOORBELL); +} +#endif /* TARGET_PPC64 */ + +/* Single-step tracing */ +void helper_book3s_trace(CPUPPCState *env, target_ulong prev_ip) +{ + uint32_t error_code = 0; + if (env->insns_flags2 & PPC2_ISA207S) { + /* Load/store reporting, SRR1[35, 36] and SDAR, are not implemented. */ + env->spr[SPR_POWER_SIAR] = prev_ip; + error_code = PPC_BIT(33); + } + raise_exception_err(env, POWERPC_EXCP_TRACE, error_code); +} #endif /* !CONFIG_USER_ONLY */ From c894bdf78b32ed825903f4f05c765eecf2e4f1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 20 Dec 2024 22:30:58 +0100 Subject: [PATCH 0933/1179] hw/ppc/spapr: Convert HPTE() macro as hpte_get_ptr() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert HPTE() macro as hpte_get_ptr() method. Reviewed-by: Nicholas Piggin Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20241220213103.6314-2-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index c7cf04e06386..0cae4853db8a 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1402,7 +1402,13 @@ static bool spapr_get_pate(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu, } } -#define HPTE(_table, _i) (void *)(((uint64_t *)(_table)) + ((_i) * 2)) +static uint64_t *hpte_get_ptr(SpaprMachineState *s, unsigned index) +{ + uint64_t *table = s->htab; + + return &table[2 * index]; +} + #define HPTE_VALID(_hpte) (tswap64(*((uint64_t *)(_hpte))) & HPTE64_V_VALID) #define HPTE_DIRTY(_hpte) (tswap64(*((uint64_t *)(_hpte))) & HPTE64_V_HPTE_DIRTY) #define CLEAN_HPTE(_hpte) ((*(uint64_t *)(_hpte)) &= tswap64(~HPTE64_V_HPTE_DIRTY)) @@ -1617,7 +1623,7 @@ int spapr_reallocate_hpt(SpaprMachineState *spapr, int shift, Error **errp) spapr->htab_shift = shift; for (i = 0; i < size / HASH_PTE_SIZE_64; i++) { - DIRTY_HPTE(HPTE(spapr->htab, i)); + DIRTY_HPTE(hpte_get_ptr(spapr, i)); } } /* We're setting up a hash table, so that means we're not radix */ @@ -2174,7 +2180,7 @@ static void htab_save_chunk(QEMUFile *f, SpaprMachineState *spapr, qemu_put_be32(f, chunkstart); qemu_put_be16(f, n_valid); qemu_put_be16(f, n_invalid); - qemu_put_buffer(f, HPTE(spapr->htab, chunkstart), + qemu_put_buffer(f, (void *)hpte_get_ptr(spapr, chunkstart), HASH_PTE_SIZE_64 * n_valid); } @@ -2200,16 +2206,16 @@ static void htab_save_first_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume invalid HPTEs */ while ((index < htabslots) - && !HPTE_VALID(HPTE(spapr->htab, index))) { - CLEAN_HPTE(HPTE(spapr->htab, index)); + && !HPTE_VALID(hpte_get_ptr(spapr, index))) { + CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; } /* Consume valid HPTEs */ chunkstart = index; while ((index < htabslots) && (index - chunkstart < USHRT_MAX) - && HPTE_VALID(HPTE(spapr->htab, index))) { - CLEAN_HPTE(HPTE(spapr->htab, index)); + && HPTE_VALID(hpte_get_ptr(spapr, index))) { + CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; } @@ -2249,7 +2255,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume non-dirty HPTEs */ while ((index < htabslots) - && !HPTE_DIRTY(HPTE(spapr->htab, index))) { + && !HPTE_DIRTY(hpte_get_ptr(spapr, index))) { index++; examined++; } @@ -2257,9 +2263,9 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, chunkstart = index; /* Consume valid dirty HPTEs */ while ((index < htabslots) && (index - chunkstart < USHRT_MAX) - && HPTE_DIRTY(HPTE(spapr->htab, index)) - && HPTE_VALID(HPTE(spapr->htab, index))) { - CLEAN_HPTE(HPTE(spapr->htab, index)); + && HPTE_DIRTY(hpte_get_ptr(spapr, index)) + && HPTE_VALID(hpte_get_ptr(spapr, index))) { + CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; examined++; } @@ -2267,9 +2273,9 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, invalidstart = index; /* Consume invalid dirty HPTEs */ while ((index < htabslots) && (index - invalidstart < USHRT_MAX) - && HPTE_DIRTY(HPTE(spapr->htab, index)) - && !HPTE_VALID(HPTE(spapr->htab, index))) { - CLEAN_HPTE(HPTE(spapr->htab, index)); + && HPTE_DIRTY(hpte_get_ptr(spapr, index)) + && !HPTE_VALID(hpte_get_ptr(spapr, index))) { + CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; examined++; } @@ -2451,11 +2457,11 @@ static int htab_load(QEMUFile *f, void *opaque, int version_id) if (spapr->htab) { if (n_valid) { - qemu_get_buffer(f, HPTE(spapr->htab, index), + qemu_get_buffer(f, (void *)hpte_get_ptr(spapr, index), HASH_PTE_SIZE_64 * n_valid); } if (n_invalid) { - memset(HPTE(spapr->htab, index + n_valid), 0, + memset(hpte_get_ptr(spapr, index + n_valid), 0, HASH_PTE_SIZE_64 * n_invalid); } } else { From c5411a065370ae2b056107ae68727c5cbb998233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 20 Dec 2024 22:30:59 +0100 Subject: [PATCH 0934/1179] hw/ppc/spapr: Convert HPTE_VALID() macro as hpte_is_valid() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert HPTE_VALID() macro as hpte_is_valid() method. sPAPR data structures including the hash page table are big-endian regardless of current CPU endian mode, so use the big-endian LD/ST API to access the hash PTEs. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Nicholas Piggin Reviewed-by: Harsh Prateek Bora Message-ID: <20241220213103.6314-3-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 0cae4853db8a..daf997cea1fc 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1409,7 +1409,11 @@ static uint64_t *hpte_get_ptr(SpaprMachineState *s, unsigned index) return &table[2 * index]; } -#define HPTE_VALID(_hpte) (tswap64(*((uint64_t *)(_hpte))) & HPTE64_V_VALID) +static bool hpte_is_valid(SpaprMachineState *s, unsigned index) +{ + return ldq_be_p(hpte_get_ptr(s, index)) & HPTE64_V_VALID; +} + #define HPTE_DIRTY(_hpte) (tswap64(*((uint64_t *)(_hpte))) & HPTE64_V_HPTE_DIRTY) #define CLEAN_HPTE(_hpte) ((*(uint64_t *)(_hpte)) &= tswap64(~HPTE64_V_HPTE_DIRTY)) #define DIRTY_HPTE(_hpte) ((*(uint64_t *)(_hpte)) |= tswap64(HPTE64_V_HPTE_DIRTY)) @@ -2206,7 +2210,7 @@ static void htab_save_first_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume invalid HPTEs */ while ((index < htabslots) - && !HPTE_VALID(hpte_get_ptr(spapr, index))) { + && !hpte_is_valid(spapr, index)) { CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; } @@ -2214,7 +2218,7 @@ static void htab_save_first_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume valid HPTEs */ chunkstart = index; while ((index < htabslots) && (index - chunkstart < USHRT_MAX) - && HPTE_VALID(hpte_get_ptr(spapr, index))) { + && hpte_is_valid(spapr, index)) { CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; } @@ -2264,7 +2268,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume valid dirty HPTEs */ while ((index < htabslots) && (index - chunkstart < USHRT_MAX) && HPTE_DIRTY(hpte_get_ptr(spapr, index)) - && HPTE_VALID(hpte_get_ptr(spapr, index))) { + && hpte_is_valid(spapr, index)) { CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; examined++; @@ -2274,7 +2278,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume invalid dirty HPTEs */ while ((index < htabslots) && (index - invalidstart < USHRT_MAX) && HPTE_DIRTY(hpte_get_ptr(spapr, index)) - && !HPTE_VALID(hpte_get_ptr(spapr, index))) { + && !hpte_is_valid(spapr, index)) { CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; examined++; From 9087929887fa51cbd11418e40d6f9fee09e63169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 20 Dec 2024 22:31:00 +0100 Subject: [PATCH 0935/1179] hw/ppc/spapr: Convert HPTE_DIRTY() macro as hpte_is_dirty() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert HPTE_DIRTY() macro as hpte_is_dirty() method. sPAPR data structures including the hash page table are big-endian regardless of current CPU endian mode, so use the big-endian LD/ST API to access the hash PTEs. Reviewed-by: Nicholas Piggin Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20241220213103.6314-4-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index daf997cea1fc..dd81398445b8 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1414,7 +1414,11 @@ static bool hpte_is_valid(SpaprMachineState *s, unsigned index) return ldq_be_p(hpte_get_ptr(s, index)) & HPTE64_V_VALID; } -#define HPTE_DIRTY(_hpte) (tswap64(*((uint64_t *)(_hpte))) & HPTE64_V_HPTE_DIRTY) +static bool hpte_is_dirty(SpaprMachineState *s, unsigned index) +{ + return ldq_be_p(hpte_get_ptr(s, index)) & HPTE64_V_HPTE_DIRTY; +} + #define CLEAN_HPTE(_hpte) ((*(uint64_t *)(_hpte)) &= tswap64(~HPTE64_V_HPTE_DIRTY)) #define DIRTY_HPTE(_hpte) ((*(uint64_t *)(_hpte)) |= tswap64(HPTE64_V_HPTE_DIRTY)) @@ -2259,7 +2263,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume non-dirty HPTEs */ while ((index < htabslots) - && !HPTE_DIRTY(hpte_get_ptr(spapr, index))) { + && !hpte_is_dirty(spapr, index)) { index++; examined++; } @@ -2267,7 +2271,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, chunkstart = index; /* Consume valid dirty HPTEs */ while ((index < htabslots) && (index - chunkstart < USHRT_MAX) - && HPTE_DIRTY(hpte_get_ptr(spapr, index)) + && hpte_is_dirty(spapr, index) && hpte_is_valid(spapr, index)) { CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; @@ -2277,7 +2281,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, invalidstart = index; /* Consume invalid dirty HPTEs */ while ((index < htabslots) && (index - invalidstart < USHRT_MAX) - && HPTE_DIRTY(hpte_get_ptr(spapr, index)) + && hpte_is_dirty(spapr, index) && !hpte_is_valid(spapr, index)) { CLEAN_HPTE(hpte_get_ptr(spapr, index)); index++; From 735f9c878a3e97b1257f2345579734ea2877c46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 20 Dec 2024 22:31:01 +0100 Subject: [PATCH 0936/1179] hw/ppc/spapr: Convert CLEAN_HPTE() macro as hpte_set_clean() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert CLEAN_HPTE() macro as hpte_set_clean() method. sPAPR data structures including the hash page table are big-endian regardless of current CPU endian mode, so use the big-endian LD/ST API to access the hash PTEs. Reviewed-by: Nicholas Piggin Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20241220213103.6314-5-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index dd81398445b8..3568a97045bc 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1419,7 +1419,12 @@ static bool hpte_is_dirty(SpaprMachineState *s, unsigned index) return ldq_be_p(hpte_get_ptr(s, index)) & HPTE64_V_HPTE_DIRTY; } -#define CLEAN_HPTE(_hpte) ((*(uint64_t *)(_hpte)) &= tswap64(~HPTE64_V_HPTE_DIRTY)) +static void hpte_set_clean(SpaprMachineState *s, unsigned index) +{ + stq_be_p(hpte_get_ptr(s, index), + ldq_be_p(hpte_get_ptr(s, index)) & ~HPTE64_V_HPTE_DIRTY); +} + #define DIRTY_HPTE(_hpte) ((*(uint64_t *)(_hpte)) |= tswap64(HPTE64_V_HPTE_DIRTY)) /* @@ -2215,7 +2220,7 @@ static void htab_save_first_pass(QEMUFile *f, SpaprMachineState *spapr, /* Consume invalid HPTEs */ while ((index < htabslots) && !hpte_is_valid(spapr, index)) { - CLEAN_HPTE(hpte_get_ptr(spapr, index)); + hpte_set_clean(spapr, index); index++; } @@ -2223,7 +2228,7 @@ static void htab_save_first_pass(QEMUFile *f, SpaprMachineState *spapr, chunkstart = index; while ((index < htabslots) && (index - chunkstart < USHRT_MAX) && hpte_is_valid(spapr, index)) { - CLEAN_HPTE(hpte_get_ptr(spapr, index)); + hpte_set_clean(spapr, index); index++; } @@ -2273,7 +2278,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, while ((index < htabslots) && (index - chunkstart < USHRT_MAX) && hpte_is_dirty(spapr, index) && hpte_is_valid(spapr, index)) { - CLEAN_HPTE(hpte_get_ptr(spapr, index)); + hpte_set_clean(spapr, index); index++; examined++; } @@ -2283,7 +2288,7 @@ static int htab_save_later_pass(QEMUFile *f, SpaprMachineState *spapr, while ((index < htabslots) && (index - invalidstart < USHRT_MAX) && hpte_is_dirty(spapr, index) && !hpte_is_valid(spapr, index)) { - CLEAN_HPTE(hpte_get_ptr(spapr, index)); + hpte_set_clean(spapr, index); index++; examined++; } From c2ac9f4c297c53aa96d2b500e8db03e57f97d97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 20 Dec 2024 22:31:02 +0100 Subject: [PATCH 0937/1179] hw/ppc/spapr: Convert DIRTY_HPTE() macro as hpte_set_dirty() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert DIRTY_HPTE() macro as hpte_set_dirty() method. sPAPR data structures including the hash page table are big-endian regardless of current CPU endian mode, so use the big-endian LD/ST API to access the hash PTEs. Reviewed-by: Nicholas Piggin Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Harsh Prateek Bora Message-ID: <20241220213103.6314-6-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 3568a97045bc..0acf3c53dc8c 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1425,7 +1425,11 @@ static void hpte_set_clean(SpaprMachineState *s, unsigned index) ldq_be_p(hpte_get_ptr(s, index)) & ~HPTE64_V_HPTE_DIRTY); } -#define DIRTY_HPTE(_hpte) ((*(uint64_t *)(_hpte)) |= tswap64(HPTE64_V_HPTE_DIRTY)) +static void hpte_set_dirty(SpaprMachineState *s, unsigned index) +{ + stq_be_p(hpte_get_ptr(s, index), + ldq_be_p(hpte_get_ptr(s, index)) | HPTE64_V_HPTE_DIRTY); +} /* * Get the fd to access the kernel htab, re-opening it if necessary @@ -1636,7 +1640,7 @@ int spapr_reallocate_hpt(SpaprMachineState *spapr, int shift, Error **errp) spapr->htab_shift = shift; for (i = 0; i < size / HASH_PTE_SIZE_64; i++) { - DIRTY_HPTE(hpte_get_ptr(spapr, i)); + hpte_set_dirty(spapr, i); } } /* We're setting up a hash table, so that means we're not radix */ From 0829b6f0a8c5829b8ed8dc77e1570a8a97e0484d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 20 Dec 2024 22:31:03 +0100 Subject: [PATCH 0938/1179] hw/ppc/epapr: Do not swap ePAPR magic value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ePAPR magic value in $r6 doesn't need to be byte swapped. See ePAPR-v1.1.pdf chapter 5.4.1 "Boot CPU Initial Register State" and the following mailing-list threads: https://lore.kernel.org/qemu-devel/CAFEAcA_NR4XW5DNL4nq7vnH4XRH5UWbhQCxuLyKqYk6_FCBrAA@mail.gmail.com/ https://lore.kernel.org/qemu-devel/D6F93NM6OW2L.2FDO88L38PABR@gmail.com/ Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Nicholas Piggin Tested-by: BALATON Zoltan Reviewed-by: Harsh Prateek Bora Message-ID: <20241220213103.6314-7-philmd@linaro.org> Signed-off-by: Nicholas Piggin --- hw/ppc/sam460ex.c | 2 +- hw/ppc/virtex_ml507.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/ppc/sam460ex.c b/hw/ppc/sam460ex.c index 3ecae6a95048..7dc3b309c89f 100644 --- a/hw/ppc/sam460ex.c +++ b/hw/ppc/sam460ex.c @@ -234,7 +234,7 @@ static void main_cpu_reset(void *opaque) /* Create a mapping for the kernel. */ booke_set_tlb(&env->tlb.tlbe[0], 0, 0, 1 << 31); - env->gpr[6] = tswap32(EPAPR_MAGIC); + env->gpr[6] = EPAPR_MAGIC; env->gpr[7] = (16 * MiB) - 8; /* bi->ima_size; */ } else { diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c index a01354d991d7..17115be74d52 100644 --- a/hw/ppc/virtex_ml507.c +++ b/hw/ppc/virtex_ml507.c @@ -119,7 +119,7 @@ static void main_cpu_reset(void *opaque) /* Create a mapping spanning the 32bit addr space. */ booke_set_tlb(&env->tlb.tlbe[0], 0, 0, 1U << 31); booke_set_tlb(&env->tlb.tlbe[1], 0x80000000, 0x80000000, 1U << 31); - env->gpr[6] = tswap32(EPAPR_MAGIC); + env->gpr[6] = EPAPR_MAGIC; env->gpr[7] = bi->ima_size; } From 7ea6e125297050a239b4028cae5ea3ffbc374861 Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Fri, 17 Jan 2025 04:06:51 +0000 Subject: [PATCH 0939/1179] ppc: Enable 2nd DAWR support on Power10 PowerNV machine Extend the existing watchpoint facility from TCG DAWR0 emulation to DAWR1 on POWER10. Reviewed-by: Nicholas Piggin Reviewed-by: Harsh Prateek Bora Signed-off-by: Shivaprasad G Bhat Message-ID: <173708680684.1678.13237334676438770057.stgit@linux.ibm.com> Signed-off-by: Nicholas Piggin --- target/ppc/cpu.c | 45 +++++++++++++++++++-------- target/ppc/cpu.h | 6 ++-- target/ppc/cpu_init.c | 15 +++++++++ target/ppc/helper.h | 2 ++ target/ppc/machine.c | 3 +- target/ppc/misc_helper.c | 10 ++++++ target/ppc/spr_common.h | 2 ++ target/ppc/tcg-excp_helper.c | 59 +++++++++++++++++++----------------- target/ppc/translate.c | 12 ++++++++ 9 files changed, 110 insertions(+), 44 deletions(-) diff --git a/target/ppc/cpu.c b/target/ppc/cpu.c index d148cd76b479..bfcc695de768 100644 --- a/target/ppc/cpu.c +++ b/target/ppc/cpu.c @@ -130,11 +130,13 @@ void ppc_store_ciabr(CPUPPCState *env, target_ulong val) ppc_update_ciabr(env); } -void ppc_update_daw0(CPUPPCState *env) +void ppc_update_daw(CPUPPCState *env, int rid) { CPUState *cs = env_cpu(env); - target_ulong deaw = env->spr[SPR_DAWR0] & PPC_BITMASK(0, 60); - uint32_t dawrx = env->spr[SPR_DAWRX0]; + int spr_dawr = rid ? SPR_DAWR1 : SPR_DAWR0; + int spr_dawrx = rid ? SPR_DAWRX1 : SPR_DAWRX0; + target_ulong deaw = env->spr[spr_dawr] & PPC_BITMASK(0, 60); + uint32_t dawrx = env->spr[spr_dawrx]; int mrd = extract32(dawrx, PPC_BIT_NR(48), 54 - 48); bool dw = extract32(dawrx, PPC_BIT_NR(57), 1); bool dr = extract32(dawrx, PPC_BIT_NR(58), 1); @@ -144,9 +146,9 @@ void ppc_update_daw0(CPUPPCState *env) vaddr len; int flags; - if (env->dawr0_watchpoint) { - cpu_watchpoint_remove_by_ref(cs, env->dawr0_watchpoint); - env->dawr0_watchpoint = NULL; + if (env->dawr_watchpoint[rid]) { + cpu_watchpoint_remove_by_ref(cs, env->dawr_watchpoint[rid]); + env->dawr_watchpoint[rid] = NULL; } if (!dr && !dw) { @@ -166,28 +168,45 @@ void ppc_update_daw0(CPUPPCState *env) flags |= BP_MEM_WRITE; } - cpu_watchpoint_insert(cs, deaw, len, flags, &env->dawr0_watchpoint); + cpu_watchpoint_insert(cs, deaw, len, flags, &env->dawr_watchpoint[rid]); } void ppc_store_dawr0(CPUPPCState *env, target_ulong val) { env->spr[SPR_DAWR0] = val; - ppc_update_daw0(env); + ppc_update_daw(env, 0); } -void ppc_store_dawrx0(CPUPPCState *env, uint32_t val) +static void ppc_store_dawrx(CPUPPCState *env, uint32_t val, int rid) { int hrammc = extract32(val, PPC_BIT_NR(56), 1); if (hrammc) { /* This might be done with a second watchpoint at the xor of DEAW[0] */ - qemu_log_mask(LOG_UNIMP, "%s: DAWRX0[HRAMMC] is unimplemented\n", - __func__); + qemu_log_mask(LOG_UNIMP, "%s: DAWRX%d[HRAMMC] is unimplemented\n", + __func__, rid); } - env->spr[SPR_DAWRX0] = val; - ppc_update_daw0(env); + env->spr[rid ? SPR_DAWRX1 : SPR_DAWRX0] = val; + ppc_update_daw(env, rid); +} + +void ppc_store_dawrx0(CPUPPCState *env, uint32_t val) +{ + ppc_store_dawrx(env, val, 0); +} + +void ppc_store_dawr1(CPUPPCState *env, target_ulong val) +{ + env->spr[SPR_DAWR1] = val; + ppc_update_daw(env, 1); +} + +void ppc_store_dawrx1(CPUPPCState *env, uint32_t val) +{ + ppc_store_dawrx(env, val, 1); } + #endif #endif diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 8d43983fe1ac..efab54a06830 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -1260,7 +1260,7 @@ struct CPUArchState { #if defined(TARGET_PPC64) ppc_slb_t slb[MAX_SLB_ENTRIES]; /* PowerPC 64 SLB area */ struct CPUBreakpoint *ciabr_breakpoint; - struct CPUWatchpoint *dawr0_watchpoint; + struct CPUWatchpoint *dawr_watchpoint[2]; #endif target_ulong sr[32]; /* segment registers */ uint32_t nb_BATs; /* number of BATs */ @@ -1589,9 +1589,11 @@ void ppc_store_sdr1(CPUPPCState *env, target_ulong value); void ppc_store_lpcr(PowerPCCPU *cpu, target_ulong val); void ppc_update_ciabr(CPUPPCState *env); void ppc_store_ciabr(CPUPPCState *env, target_ulong value); -void ppc_update_daw0(CPUPPCState *env); +void ppc_update_daw(CPUPPCState *env, int rid); void ppc_store_dawr0(CPUPPCState *env, target_ulong value); void ppc_store_dawrx0(CPUPPCState *env, uint32_t value); +void ppc_store_dawr1(CPUPPCState *env, target_ulong value); +void ppc_store_dawrx1(CPUPPCState *env, uint32_t value); #endif /* !defined(CONFIG_USER_ONLY) */ void ppc_store_msr(CPUPPCState *env, target_ulong value); diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index 8d73e11540b0..9dc5ace8289a 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -5172,6 +5172,20 @@ static void register_book3s_207_dbg_sprs(CPUPPCState *env) KVM_REG_PPC_CIABR, 0x00000000); } +static void register_book3s_310_dbg_sprs(CPUPPCState *env) +{ + spr_register_kvm_hv(env, SPR_DAWR1, "DAWR1", + SPR_NOACCESS, SPR_NOACCESS, + SPR_NOACCESS, SPR_NOACCESS, + &spr_read_generic, &spr_write_dawr1, + KVM_REG_PPC_DAWR1, 0x00000000); + spr_register_kvm_hv(env, SPR_DAWRX1, "DAWRX1", + SPR_NOACCESS, SPR_NOACCESS, + SPR_NOACCESS, SPR_NOACCESS, + &spr_read_generic, &spr_write_dawrx1, + KVM_REG_PPC_DAWRX1, 0x00000000); +} + static void register_970_dbg_sprs(CPUPPCState *env) { /* Breakpoints */ @@ -6584,6 +6598,7 @@ static void init_proc_POWER10(CPUPPCState *env) { register_power9_common_sprs(env); register_HEIR64_spr(env); + register_book3s_310_dbg_sprs(env); register_power10_hash_sprs(env); register_power10_dexcr_sprs(env); register_power10_pmu_sup_sprs(env); diff --git a/target/ppc/helper.h b/target/ppc/helper.h index 11b914e640b9..ca414f2f43d3 100644 --- a/target/ppc/helper.h +++ b/target/ppc/helper.h @@ -28,6 +28,8 @@ DEF_HELPER_2(store_pcr, void, env, tl) DEF_HELPER_2(store_ciabr, void, env, tl) DEF_HELPER_2(store_dawr0, void, env, tl) DEF_HELPER_2(store_dawrx0, void, env, tl) +DEF_HELPER_2(store_dawr1, void, env, tl) +DEF_HELPER_2(store_dawrx1, void, env, tl) DEF_HELPER_2(store_mmcr0, void, env, tl) DEF_HELPER_2(store_mmcr1, void, env, tl) DEF_HELPER_2(store_mmcrA, void, env, tl) diff --git a/target/ppc/machine.c b/target/ppc/machine.c index 0bd7ae6c0ca3..98df5b4a3a2a 100644 --- a/target/ppc/machine.c +++ b/target/ppc/machine.c @@ -264,7 +264,8 @@ static int cpu_post_load(void *opaque, int version_id) /* Re-set breaks based on regs */ #if defined(TARGET_PPC64) ppc_update_ciabr(env); - ppc_update_daw0(env); + ppc_update_daw(env, 0); + ppc_update_daw(env, 1); #endif /* * TCG needs to re-start the decrementer timer and/or raise the diff --git a/target/ppc/misc_helper.c b/target/ppc/misc_helper.c index 190e9091fc23..2d9512c116b3 100644 --- a/target/ppc/misc_helper.c +++ b/target/ppc/misc_helper.c @@ -234,6 +234,16 @@ void helper_store_dawrx0(CPUPPCState *env, target_ulong value) ppc_store_dawrx0(env, value); } +void helper_store_dawr1(CPUPPCState *env, target_ulong value) +{ + ppc_store_dawr1(env, value); +} + +void helper_store_dawrx1(CPUPPCState *env, target_ulong value) +{ + ppc_store_dawrx1(env, value); +} + /* * DPDES register is shared. Each bit reflects the state of the * doorbell interrupt of a thread of the same core. diff --git a/target/ppc/spr_common.h b/target/ppc/spr_common.h index 8e3117b463bc..84c910c440f7 100644 --- a/target/ppc/spr_common.h +++ b/target/ppc/spr_common.h @@ -165,6 +165,8 @@ void spr_write_cfar(DisasContext *ctx, int sprn, int gprn); void spr_write_ciabr(DisasContext *ctx, int sprn, int gprn); void spr_write_dawr0(DisasContext *ctx, int sprn, int gprn); void spr_write_dawrx0(DisasContext *ctx, int sprn, int gprn); +void spr_write_dawr1(DisasContext *ctx, int sprn, int gprn); +void spr_write_dawrx1(DisasContext *ctx, int sprn, int gprn); void spr_write_ureg(DisasContext *ctx, int sprn, int gprn); void spr_read_purr(DisasContext *ctx, int gprn, int sprn); void spr_write_purr(DisasContext *ctx, int sprn, int gprn); diff --git a/target/ppc/tcg-excp_helper.c b/target/ppc/tcg-excp_helper.c index 4b859a8ffaa4..5a189dc3d709 100644 --- a/target/ppc/tcg-excp_helper.c +++ b/target/ppc/tcg-excp_helper.c @@ -345,39 +345,42 @@ bool ppc_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) { #if defined(TARGET_PPC64) CPUPPCState *env = cpu_env(cs); + bool wt, wti, hv, sv, pr; + uint32_t dawrx; + + if ((env->insns_flags2 & PPC2_ISA207S) && + (wp == env->dawr_watchpoint[0])) { + dawrx = env->spr[SPR_DAWRX0]; + } else if ((env->insns_flags2 & PPC2_ISA310) && + (wp == env->dawr_watchpoint[1])) { + dawrx = env->spr[SPR_DAWRX1]; + } else { + return false; + } - if (env->insns_flags2 & PPC2_ISA207S) { - if (wp == env->dawr0_watchpoint) { - uint32_t dawrx = env->spr[SPR_DAWRX0]; - bool wt = extract32(dawrx, PPC_BIT_NR(59), 1); - bool wti = extract32(dawrx, PPC_BIT_NR(60), 1); - bool hv = extract32(dawrx, PPC_BIT_NR(61), 1); - bool sv = extract32(dawrx, PPC_BIT_NR(62), 1); - bool pr = extract32(dawrx, PPC_BIT_NR(62), 1); - - if ((env->msr & ((target_ulong)1 << MSR_PR)) && !pr) { - return false; - } else if ((env->msr & ((target_ulong)1 << MSR_HV)) && !hv) { - return false; - } else if (!sv) { - return false; - } + wt = extract32(dawrx, PPC_BIT_NR(59), 1); + wti = extract32(dawrx, PPC_BIT_NR(60), 1); + hv = extract32(dawrx, PPC_BIT_NR(61), 1); + sv = extract32(dawrx, PPC_BIT_NR(62), 1); + pr = extract32(dawrx, PPC_BIT_NR(62), 1); - if (!wti) { - if (env->msr & ((target_ulong)1 << MSR_DR)) { - if (!wt) { - return false; - } - } else { - if (wt) { - return false; - } - } - } + if ((env->msr & ((target_ulong)1 << MSR_PR)) && !pr) { + return false; + } else if ((env->msr & ((target_ulong)1 << MSR_HV)) && !hv) { + return false; + } else if (!sv) { + return false; + } - return true; + if (!wti) { + if (env->msr & ((target_ulong)1 << MSR_DR)) { + return wt; + } else { + return !wt; } } + + return true; #endif return false; diff --git a/target/ppc/translate.c b/target/ppc/translate.c index b0cc8bf2839c..a52cbc869ae0 100644 --- a/target/ppc/translate.c +++ b/target/ppc/translate.c @@ -637,6 +637,18 @@ void spr_write_dawrx0(DisasContext *ctx, int sprn, int gprn) translator_io_start(&ctx->base); gen_helper_store_dawrx0(tcg_env, cpu_gpr[gprn]); } + +void spr_write_dawr1(DisasContext *ctx, int sprn, int gprn) +{ + translator_io_start(&ctx->base); + gen_helper_store_dawr1(tcg_env, cpu_gpr[gprn]); +} + +void spr_write_dawrx1(DisasContext *ctx, int sprn, int gprn) +{ + translator_io_start(&ctx->base); + gen_helper_store_dawrx1(tcg_env, cpu_gpr[gprn]); +} #endif /* defined(TARGET_PPC64) && !defined(CONFIG_USER_ONLY) */ /* CTR */ From 5f361ea187ba5f876757d4c90952ff23810f24f5 Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Fri, 17 Jan 2025 04:07:01 +0000 Subject: [PATCH 0940/1179] ppc: spapr: Enable 2nd DAWR on Power10 pSeries machine As per the PAPR, bit 0 of byte 64 in pa-features property indicates availability of 2nd DAWR registers. i.e. If this bit is set, 2nd DAWR is present, otherwise not. Use KVM_CAP_PPC_DAWR1 capability to find whether kvm supports 2nd DAWR or not. If it's supported, allow user to set the pa-feature bit in guest DT using cap-dawr1 machine capability. Reviewed-by: Nicholas Piggin Reviewed-by: Harsh Prateek Bora Signed-off-by: Ravi Bangoria Signed-off-by: Shivaprasad G Bhat Message-ID: <173708681866.1678.11128625982438367069.stgit@linux.ibm.com> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr.c | 7 ++++++- hw/ppc/spapr_caps.c | 43 ++++++++++++++++++++++++++++++++++++++++++ hw/ppc/spapr_hcall.c | 27 +++++++++++++++++--------- include/hw/ppc/spapr.h | 6 +++++- target/ppc/kvm.c | 12 ++++++++++++ target/ppc/kvm_ppc.h | 12 ++++++++++++ 6 files changed, 96 insertions(+), 11 deletions(-) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 0acf3c53dc8c..fcd2ca515c1a 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -246,7 +246,7 @@ static void spapr_dt_pa_features(SpaprMachineState *spapr, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 48 - 53 */ /* 54: DecFP, 56: DecI, 58: SHA */ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, /* 54 - 59 */ - /* 60: NM atomic, 62: RNG */ + /* 60: NM atomic, 62: RNG, 64: DAWR1 (ISA 3.1) */ 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, /* 60 - 65 */ /* 68: DEXCR[SBHE|IBRTPDUS|SRAPD|NPHIE|PHIE] */ 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, /* 66 - 71 */ @@ -295,6 +295,9 @@ static void spapr_dt_pa_features(SpaprMachineState *spapr, * in pa-features. So hide it from them. */ pa_features[40 + 2] &= ~0x80; /* Radix MMU */ } + if (spapr_get_cap(spapr, SPAPR_CAP_DAWR1)) { + pa_features[66] |= 0x80; + } _FDT((fdt_setprop(fdt, offset, "ibm,pa-features", pa_features, pa_size))); } @@ -2163,6 +2166,7 @@ static const VMStateDescription vmstate_spapr = { &vmstate_spapr_cap_rpt_invalidate, &vmstate_spapr_cap_ail_mode_3, &vmstate_spapr_cap_nested_papr, + &vmstate_spapr_cap_dawr1, NULL } }; @@ -4680,6 +4684,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data) smc->default_caps.caps[SPAPR_CAP_CCF_ASSIST] = SPAPR_CAP_ON; smc->default_caps.caps[SPAPR_CAP_FWNMI] = SPAPR_CAP_ON; smc->default_caps.caps[SPAPR_CAP_RPT_INVALIDATE] = SPAPR_CAP_OFF; + smc->default_caps.caps[SPAPR_CAP_DAWR1] = SPAPR_CAP_ON; /* * This cap specifies whether the AIL 3 mode for diff --git a/hw/ppc/spapr_caps.c b/hw/ppc/spapr_caps.c index 904bff87ce12..9f4fd0cb5e0b 100644 --- a/hw/ppc/spapr_caps.c +++ b/hw/ppc/spapr_caps.c @@ -696,6 +696,34 @@ static void cap_ail_mode_3_apply(SpaprMachineState *spapr, } } +static void cap_dawr1_apply(SpaprMachineState *spapr, uint8_t val, + Error **errp) +{ + ERRP_GUARD(); + + if (!val) { + return; /* Disable by default */ + } + + if (!ppc_type_check_compat(MACHINE(spapr)->cpu_type, + CPU_POWERPC_LOGICAL_3_10, 0, + spapr->max_compat_pvr)) { + error_setg(errp, "DAWR1 supported only on POWER10 and later CPUs"); + error_append_hint(errp, "Try appending -machine cap-dawr1=off\n"); + return; + } + + if (kvm_enabled()) { + if (!kvmppc_has_cap_dawr1()) { + error_setg(errp, "DAWR1 not supported by KVM."); + error_append_hint(errp, "Try appending -machine cap-dawr1=off"); + } else if (kvmppc_set_cap_dawr1(val) < 0) { + error_setg(errp, "Error enabling cap-dawr1 with KVM."); + error_append_hint(errp, "Try appending -machine cap-dawr1=off"); + } + } +} + SpaprCapabilityInfo capability_table[SPAPR_CAP_NUM] = { [SPAPR_CAP_HTM] = { .name = "htm", @@ -831,6 +859,15 @@ SpaprCapabilityInfo capability_table[SPAPR_CAP_NUM] = { .type = "bool", .apply = cap_ail_mode_3_apply, }, + [SPAPR_CAP_DAWR1] = { + .name = "dawr1", + .description = "Allow 2nd Data Address Watchpoint Register (DAWR1)", + .index = SPAPR_CAP_DAWR1, + .get = spapr_cap_get_bool, + .set = spapr_cap_set_bool, + .type = "bool", + .apply = cap_dawr1_apply, + }, }; static SpaprCapabilities default_caps_with_cpu(SpaprMachineState *spapr, @@ -841,6 +878,11 @@ static SpaprCapabilities default_caps_with_cpu(SpaprMachineState *spapr, caps = smc->default_caps; + if (!ppc_type_check_compat(cputype, CPU_POWERPC_LOGICAL_3_10, + 0, spapr->max_compat_pvr)) { + caps.caps[SPAPR_CAP_DAWR1] = SPAPR_CAP_OFF; + } + if (!ppc_type_check_compat(cputype, CPU_POWERPC_LOGICAL_3_00, 0, spapr->max_compat_pvr)) { caps.caps[SPAPR_CAP_LARGE_DECREMENTER] = SPAPR_CAP_OFF; @@ -975,6 +1017,7 @@ SPAPR_CAP_MIG_STATE(ccf_assist, SPAPR_CAP_CCF_ASSIST); SPAPR_CAP_MIG_STATE(fwnmi, SPAPR_CAP_FWNMI); SPAPR_CAP_MIG_STATE(rpt_invalidate, SPAPR_CAP_RPT_INVALIDATE); SPAPR_CAP_MIG_STATE(ail_mode_3, SPAPR_CAP_AIL_MODE_3); +SPAPR_CAP_MIG_STATE(dawr1, SPAPR_CAP_DAWR1); void spapr_caps_init(SpaprMachineState *spapr) { diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index 4f1933b8da68..406aea4ecbeb 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -822,11 +822,12 @@ static target_ulong h_set_mode_resource_set_ciabr(PowerPCCPU *cpu, return H_SUCCESS; } -static target_ulong h_set_mode_resource_set_dawr0(PowerPCCPU *cpu, - SpaprMachineState *spapr, - target_ulong mflags, - target_ulong value1, - target_ulong value2) +static target_ulong h_set_mode_resource_set_dawr(PowerPCCPU *cpu, + SpaprMachineState *spapr, + target_ulong mflags, + target_ulong resource, + target_ulong value1, + target_ulong value2) { CPUPPCState *env = &cpu->env; @@ -839,8 +840,15 @@ static target_ulong h_set_mode_resource_set_dawr0(PowerPCCPU *cpu, return H_P4; } - ppc_store_dawr0(env, value1); - ppc_store_dawrx0(env, value2); + if (resource == H_SET_MODE_RESOURCE_SET_DAWR0) { + ppc_store_dawr0(env, value1); + ppc_store_dawrx0(env, value2); + } else if (resource == H_SET_MODE_RESOURCE_SET_DAWR1) { + ppc_store_dawr1(env, value1); + ppc_store_dawrx1(env, value2); + } else { + g_assert_not_reached(); + } return H_SUCCESS; } @@ -919,8 +927,9 @@ static target_ulong h_set_mode(PowerPCCPU *cpu, SpaprMachineState *spapr, args[3]); break; case H_SET_MODE_RESOURCE_SET_DAWR0: - ret = h_set_mode_resource_set_dawr0(cpu, spapr, args[0], args[2], - args[3]); + case H_SET_MODE_RESOURCE_SET_DAWR1: + ret = h_set_mode_resource_set_dawr(cpu, spapr, args[0], args[1], + args[2], args[3]); break; case H_SET_MODE_RESOURCE_LE: ret = h_set_mode_resource_le(cpu, spapr, args[0], args[2], args[3]); diff --git a/include/hw/ppc/spapr.h b/include/hw/ppc/spapr.h index a6c0547e313d..d227f0b94b93 100644 --- a/include/hw/ppc/spapr.h +++ b/include/hw/ppc/spapr.h @@ -83,8 +83,10 @@ typedef enum { #define SPAPR_CAP_AIL_MODE_3 0x0C /* Nested PAPR */ #define SPAPR_CAP_NESTED_PAPR 0x0D +/* DAWR1 */ +#define SPAPR_CAP_DAWR1 0x0E /* Num Caps */ -#define SPAPR_CAP_NUM (SPAPR_CAP_NESTED_PAPR + 1) +#define SPAPR_CAP_NUM (SPAPR_CAP_DAWR1 + 1) /* * Capability Values @@ -406,6 +408,7 @@ struct SpaprMachineState { #define H_SET_MODE_RESOURCE_SET_DAWR0 2 #define H_SET_MODE_RESOURCE_ADDR_TRANS_MODE 3 #define H_SET_MODE_RESOURCE_LE 4 +#define H_SET_MODE_RESOURCE_SET_DAWR1 5 /* Flags for H_SET_MODE_RESOURCE_LE */ #define H_SET_MODE_ENDIAN_BIG 0 @@ -1003,6 +1006,7 @@ extern const VMStateDescription vmstate_spapr_cap_fwnmi; extern const VMStateDescription vmstate_spapr_cap_rpt_invalidate; extern const VMStateDescription vmstate_spapr_cap_ail_mode_3; extern const VMStateDescription vmstate_spapr_wdt; +extern const VMStateDescription vmstate_spapr_cap_dawr1; static inline uint8_t spapr_get_cap(SpaprMachineState *spapr, int cap) { diff --git a/target/ppc/kvm.c b/target/ppc/kvm.c index 216638dee409..992356cb7593 100644 --- a/target/ppc/kvm.c +++ b/target/ppc/kvm.c @@ -92,6 +92,7 @@ static int cap_large_decr; static int cap_fwnmi; static int cap_rpt_invalidate; static int cap_ail_mode_3; +static int cap_dawr1; #ifdef CONFIG_PSERIES static int cap_papr; @@ -152,6 +153,7 @@ int kvm_arch_init(MachineState *ms, KVMState *s) cap_ppc_nested_kvm_hv = kvm_vm_check_extension(s, KVM_CAP_PPC_NESTED_HV); cap_large_decr = kvmppc_get_dec_bits(); cap_fwnmi = kvm_vm_check_extension(s, KVM_CAP_PPC_FWNMI); + cap_dawr1 = kvm_vm_check_extension(s, KVM_CAP_PPC_DAWR1); /* * Note: setting it to false because there is not such capability * in KVM at this moment. @@ -2114,6 +2116,16 @@ int kvmppc_set_fwnmi(PowerPCCPU *cpu) return kvm_vcpu_enable_cap(cs, KVM_CAP_PPC_FWNMI, 0); } +bool kvmppc_has_cap_dawr1(void) +{ + return !!cap_dawr1; +} + +int kvmppc_set_cap_dawr1(int enable) +{ + return kvm_vm_enable_cap(kvm_state, KVM_CAP_PPC_DAWR1, 0, enable); +} + int kvmppc_smt_threads(void) { return cap_ppc_smt ? cap_ppc_smt : 1; diff --git a/target/ppc/kvm_ppc.h b/target/ppc/kvm_ppc.h index 1d8cb76a6bee..a8768c1dfd8c 100644 --- a/target/ppc/kvm_ppc.h +++ b/target/ppc/kvm_ppc.h @@ -68,6 +68,8 @@ bool kvmppc_has_cap_htm(void); bool kvmppc_has_cap_mmu_radix(void); bool kvmppc_has_cap_mmu_hash_v3(void); bool kvmppc_has_cap_xive(void); +bool kvmppc_has_cap_dawr1(void); +int kvmppc_set_cap_dawr1(int enable); int kvmppc_get_cap_safe_cache(void); int kvmppc_get_cap_safe_bounds_check(void); int kvmppc_get_cap_safe_indirect_branch(void); @@ -377,6 +379,16 @@ static inline bool kvmppc_has_cap_xive(void) return false; } +static inline bool kvmppc_has_cap_dawr1(void) +{ + return false; +} + +static inline int kvmppc_set_cap_dawr1(int enable) +{ + abort(); +} + static inline int kvmppc_get_cap_safe_cache(void) { return 0; From 5f7d861e65d90e0446b8f22a0bc859a5d8058ea6 Mon Sep 17 00:00:00 2001 From: Vaibhav Jain Date: Fri, 21 Feb 2025 21:24:48 +0530 Subject: [PATCH 0941/1179] spapr: nested: Add support for reporting Hostwide state counter Add support for reporting Hostwide state counters for nested KVM pseries guests running with 'cap-nested-papr' on Qemu-TCG acting as L0-hypervisor. The Hostwide state counters are statistics about state that L0-hypervisor maintains for the L2-guests and represent the state of all L2-guests, not just a specific one. These stats counters are exposed to L1-Hypervisor by the L0-Hypervisor via a new bit-flag named 'getHostWideState' for the H_GUEST_GET_STATE hcall which is documented at [1]. Once this flag is set the hcall should populate the Guest-State-Elements in the requested GSB with the stat counter values. Currently following five counters are supported: * l0_guest_heap_size_inuse * l0_guest_heap_size_max * l0_guest_pagetable_size_inuse * l0_guest_pagetable_size_max * l0_guest_pagetable_reclaimed At the moment '0' is being reported for all these counters as these counters doesn't align with how L0-Qemu manages Guest memory. The patch implements support for these counters by adding new members to the 'struct SpaprMachineStateNested'. These new members are then plugged into the existing 'guest_state_element_types[]' with the help of a new macro 'GSBE_NESTED_MACHINE_DW' together with a new helper 'get_machine_ptr()'. guest_state_request_check() is updated to ensure correctness of the requested GSB and finally h_guest_getset_state() is updated to handle the newly introduced flag 'GUEST_STATE_REQUEST_HOST_WIDE'. This patch is tested with the proposed linux-kernel implementation to expose these stat-counter as perf-events at [2]. [1] https://lore.kernel.org/all/20241222140247.174998-2-vaibhav@linux.ibm.com [2] https://lore.kernel.org/all/20241222140247.174998-1-vaibhav@linux.ibm.com Signed-off-by: Vaibhav Jain Reviewed-by: Harsh Prateek Bora Message-ID: <20250221155449.530645-1-vaibhav@linux.ibm.com> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr_nested.c | 119 ++++++++++++++++++++++++---------- include/hw/ppc/spapr_nested.h | 67 +++++++++++++++++-- 2 files changed, 147 insertions(+), 39 deletions(-) diff --git a/hw/ppc/spapr_nested.c b/hw/ppc/spapr_nested.c index 23958c6383a3..201f62920332 100644 --- a/hw/ppc/spapr_nested.c +++ b/hw/ppc/spapr_nested.c @@ -65,10 +65,9 @@ static SpaprMachineStateNestedGuest *spapr_get_nested_guest(SpaprMachineState *spapr, target_ulong guestid) { - SpaprMachineStateNestedGuest *guest; - - guest = g_hash_table_lookup(spapr->nested.guests, GINT_TO_POINTER(guestid)); - return guest; + return spapr->nested.guests ? + g_hash_table_lookup(spapr->nested.guests, + GINT_TO_POINTER(guestid)) : NULL; } bool spapr_get_pate_nested_papr(SpaprMachineState *spapr, PowerPCCPU *cpu, @@ -594,26 +593,37 @@ static bool spapr_nested_vcpu_check(SpaprMachineStateNestedGuest *guest, return false; } -static void *get_vcpu_state_ptr(SpaprMachineStateNestedGuest *guest, - target_ulong vcpuid) +static void *get_vcpu_state_ptr(SpaprMachineState *spapr, + SpaprMachineStateNestedGuest *guest, + target_ulong vcpuid) { assert(spapr_nested_vcpu_check(guest, vcpuid, false)); return &guest->vcpus[vcpuid].state; } -static void *get_vcpu_ptr(SpaprMachineStateNestedGuest *guest, - target_ulong vcpuid) +static void *get_vcpu_ptr(SpaprMachineState *spapr, + SpaprMachineStateNestedGuest *guest, + target_ulong vcpuid) { assert(spapr_nested_vcpu_check(guest, vcpuid, false)); return &guest->vcpus[vcpuid]; } -static void *get_guest_ptr(SpaprMachineStateNestedGuest *guest, +static void *get_guest_ptr(SpaprMachineState *spapr, + SpaprMachineStateNestedGuest *guest, target_ulong vcpuid) { return guest; /* for GSBE_NESTED */ } +static void *get_machine_ptr(SpaprMachineState *spapr, + SpaprMachineStateNestedGuest *guest, + target_ulong vcpuid) +{ + /* ignore guest and vcpuid for this */ + return &spapr->nested; +} + /* * set=1 means the L1 is trying to set some state * set=0 means the L1 is trying to get some state @@ -1013,7 +1023,15 @@ struct guest_state_element_type guest_state_element_types[] = { GSBE_NESTED_VCPU(GSB_VCPU_OUT_BUFFER, 0x10, runbufout, copy_state_runbuf), GSBE_NESTED_VCPU(GSB_VCPU_OUT_BUF_MIN_SZ, 0x8, runbufout, out_buf_min_size), GSBE_NESTED_VCPU(GSB_VCPU_HDEC_EXPIRY_TB, 0x8, hdecr_expiry_tb, - copy_state_hdecr) + copy_state_hdecr), + GSBE_NESTED_MACHINE_DW(GSB_L0_GUEST_HEAP_INUSE, l0_guest_heap_inuse), + GSBE_NESTED_MACHINE_DW(GSB_L0_GUEST_HEAP_MAX, l0_guest_heap_max), + GSBE_NESTED_MACHINE_DW(GSB_L0_GUEST_PGTABLE_SIZE_INUSE, + l0_guest_pgtable_size_inuse), + GSBE_NESTED_MACHINE_DW(GSB_L0_GUEST_PGTABLE_SIZE_MAX, + l0_guest_pgtable_size_max), + GSBE_NESTED_MACHINE_DW(GSB_L0_GUEST_PGTABLE_RECLAIMED, + l0_guest_pgtable_reclaimed), }; void spapr_nested_gsb_init(void) @@ -1031,8 +1049,13 @@ void spapr_nested_gsb_init(void) else if (type->id >= GSB_VCPU_IN_BUFFER) /* 0x0c00 - 0xf000 Thread + RW */ type->flags = 0; + else if (type->id >= GSB_L0_GUEST_HEAP_INUSE) + + /*0x0800 - 0x0804 Hostwide Counters + RO */ + type->flags = GUEST_STATE_ELEMENT_TYPE_FLAG_HOST_WIDE | + GUEST_STATE_ELEMENT_TYPE_FLAG_READ_ONLY; else if (type->id >= GSB_VCPU_LPVR) - /* 0x0003 - 0x0bff Guest + RW */ + /* 0x0003 - 0x07ff Guest + RW */ type->flags = GUEST_STATE_ELEMENT_TYPE_FLAG_GUEST_WIDE; else if (type->id >= GSB_HV_VCPU_STATE_SIZE) /* 0x0001 - 0x0002 Guest + RO */ @@ -1139,18 +1162,26 @@ static bool guest_state_request_check(struct guest_state_request *gsr) return false; } - if (type->flags & GUEST_STATE_ELEMENT_TYPE_FLAG_GUEST_WIDE) { + if (type->flags & GUEST_STATE_ELEMENT_TYPE_FLAG_HOST_WIDE) { + /* Hostwide elements cant be clubbed with other types */ + if (!(gsr->flags & GUEST_STATE_REQUEST_HOST_WIDE)) { + qemu_log_mask(LOG_GUEST_ERROR, "trying to get/set a host wide " + "Element ID:%04x.\n", id); + return false; + } + } else if (type->flags & GUEST_STATE_ELEMENT_TYPE_FLAG_GUEST_WIDE) { /* guest wide element type */ if (!(gsr->flags & GUEST_STATE_REQUEST_GUEST_WIDE)) { - qemu_log_mask(LOG_GUEST_ERROR, "trying to set a guest wide " + qemu_log_mask(LOG_GUEST_ERROR, "trying to get/set a guest wide " "Element ID:%04x.\n", id); return false; } } else { /* thread wide element type */ - if (gsr->flags & GUEST_STATE_REQUEST_GUEST_WIDE) { - qemu_log_mask(LOG_GUEST_ERROR, "trying to set a thread wide " - "Element ID:%04x.\n", id); + if (gsr->flags & (GUEST_STATE_REQUEST_GUEST_WIDE | + GUEST_STATE_REQUEST_HOST_WIDE)) { + qemu_log_mask(LOG_GUEST_ERROR, "trying to get/set a thread wide" + " Element ID:%04x.\n", id); return false; } } @@ -1419,7 +1450,8 @@ static target_ulong h_guest_create_vcpu(PowerPCCPU *cpu, return H_SUCCESS; } -static target_ulong getset_state(SpaprMachineStateNestedGuest *guest, +static target_ulong getset_state(SpaprMachineState *spapr, + SpaprMachineStateNestedGuest *guest, uint64_t vcpuid, struct guest_state_request *gsr) { @@ -1452,7 +1484,7 @@ static target_ulong getset_state(SpaprMachineStateNestedGuest *guest, /* Get pointer to guest data to get/set */ if (type->location && type->copy) { - ptr = type->location(guest, vcpuid); + ptr = type->location(spapr, guest, vcpuid); assert(ptr); if (!~(type->mask) && is_gsr_invalid(gsr, element, type)) { return H_INVALID_ELEMENT_VALUE; @@ -1469,6 +1501,7 @@ static target_ulong getset_state(SpaprMachineStateNestedGuest *guest, } static target_ulong map_and_getset_state(PowerPCCPU *cpu, + SpaprMachineState *spapr, SpaprMachineStateNestedGuest *guest, uint64_t vcpuid, struct guest_state_request *gsr) @@ -1492,7 +1525,7 @@ static target_ulong map_and_getset_state(PowerPCCPU *cpu, goto out1; } - rc = getset_state(guest, vcpuid, gsr); + rc = getset_state(spapr, guest, vcpuid, gsr); out1: address_space_unmap(CPU(cpu)->as, gsr->gsb, len, is_write, len); @@ -1510,27 +1543,46 @@ static target_ulong h_guest_getset_state(PowerPCCPU *cpu, target_ulong buf = args[3]; target_ulong buflen = args[4]; struct guest_state_request gsr; - SpaprMachineStateNestedGuest *guest; + SpaprMachineStateNestedGuest *guest = NULL; - guest = spapr_get_nested_guest(spapr, lpid); - if (!guest) { - return H_P2; - } gsr.buf = buf; assert(buflen <= GSB_MAX_BUF_SIZE); gsr.len = buflen; gsr.flags = 0; - if (flags & H_GUEST_GETSET_STATE_FLAG_GUEST_WIDE) { + + /* Works for both get/set state */ + if ((flags & H_GUEST_GET_STATE_FLAGS_GUEST_WIDE) || + (flags & H_GUEST_SET_STATE_FLAGS_GUEST_WIDE)) { gsr.flags |= GUEST_STATE_REQUEST_GUEST_WIDE; } - if (flags & ~H_GUEST_GETSET_STATE_FLAG_GUEST_WIDE) { - return H_PARAMETER; /* flag not supported yet */ - } if (set) { + if (flags & ~H_GUEST_SET_STATE_FLAGS_MASK) { + return H_PARAMETER; + } gsr.flags |= GUEST_STATE_REQUEST_SET; + } else { + /* + * No reserved fields to be set in flags nor both + * GUEST/HOST wide bits + */ + if ((flags & ~H_GUEST_GET_STATE_FLAGS_MASK) || + (flags == H_GUEST_GET_STATE_FLAGS_MASK)) { + return H_PARAMETER; + } + + if (flags & H_GUEST_GET_STATE_FLAGS_HOST_WIDE) { + gsr.flags |= GUEST_STATE_REQUEST_HOST_WIDE; + } + } + + if (!(gsr.flags & GUEST_STATE_REQUEST_HOST_WIDE)) { + guest = spapr_get_nested_guest(spapr, lpid); + if (!guest) { + return H_P2; + } } - return map_and_getset_state(cpu, guest, vcpuid, &gsr); + return map_and_getset_state(cpu, spapr, guest, vcpuid, &gsr); } static target_ulong h_guest_set_state(PowerPCCPU *cpu, @@ -1641,7 +1693,8 @@ static int get_exit_ids(uint64_t srr0, uint16_t ids[16]) return nr; } -static void exit_process_output_buffer(PowerPCCPU *cpu, +static void exit_process_output_buffer(SpaprMachineState *spapr, + PowerPCCPU *cpu, SpaprMachineStateNestedGuest *guest, target_ulong vcpuid, target_ulong *r3) @@ -1679,7 +1732,7 @@ static void exit_process_output_buffer(PowerPCCPU *cpu, gsr.gsb = gsb; gsr.len = VCPU_OUT_BUF_MIN_SZ; gsr.flags = 0; /* get + never guest wide */ - getset_state(guest, vcpuid, &gsr); + getset_state(spapr, guest, vcpuid, &gsr); address_space_unmap(CPU(cpu)->as, gsb, len, true, len); return; @@ -1705,7 +1758,7 @@ void spapr_exit_nested_papr(SpaprMachineState *spapr, PowerPCCPU *cpu, int excp) exit_nested_store_l2(cpu, excp, vcpu); /* do the output buffer for run_vcpu*/ - exit_process_output_buffer(cpu, guest, vcpuid, &r3_return); + exit_process_output_buffer(spapr, cpu, guest, vcpuid, &r3_return); assert(env->spr[SPR_LPIDR] != 0); nested_load_state(cpu, spapr_cpu->nested_host_state); @@ -1820,7 +1873,7 @@ static target_ulong h_guest_run_vcpu(PowerPCCPU *cpu, gsr.buf = vcpu->runbufin.addr; gsr.len = vcpu->runbufin.size; gsr.flags = GUEST_STATE_REQUEST_SET; /* Thread wide + writing */ - rc = map_and_getset_state(cpu, guest, vcpuid, &gsr); + rc = map_and_getset_state(cpu, spapr, guest, vcpuid, &gsr); if (rc == H_SUCCESS) { nested_papr_run_vcpu(cpu, lpid, vcpu); } else { diff --git a/include/hw/ppc/spapr_nested.h b/include/hw/ppc/spapr_nested.h index e4202204846c..f7be0d5a9557 100644 --- a/include/hw/ppc/spapr_nested.h +++ b/include/hw/ppc/spapr_nested.h @@ -11,7 +11,13 @@ #define GSB_TB_OFFSET 0x0004 /* Timebase Offset */ #define GSB_PART_SCOPED_PAGETBL 0x0005 /* Partition Scoped Page Table */ #define GSB_PROCESS_TBL 0x0006 /* Process Table */ - /* RESERVED 0x0007 - 0x0BFF */ + /* RESERVED 0x0007 - 0x07FF */ +#define GSB_L0_GUEST_HEAP_INUSE 0x0800 /* Guest Management Heap Size */ +#define GSB_L0_GUEST_HEAP_MAX 0x0801 /* Guest Management Heap Max Size */ +#define GSB_L0_GUEST_PGTABLE_SIZE_INUSE 0x0802 /* Guest Pagetable Size */ +#define GSB_L0_GUEST_PGTABLE_SIZE_MAX 0x0803 /* Guest Pagetable Max Size */ +#define GSB_L0_GUEST_PGTABLE_RECLAIMED 0x0804 /* Pagetable Reclaim in bytes */ + /* RESERVED 0x0805 - 0xBFF */ #define GSB_VCPU_IN_BUFFER 0x0C00 /* Run VCPU Input Buffer */ #define GSB_VCPU_OUT_BUFFER 0x0C01 /* Run VCPU Out Buffer */ #define GSB_VCPU_VPA 0x0C02 /* HRA to Guest VCPU VPA */ @@ -196,6 +202,38 @@ typedef struct SpaprMachineStateNested { #define NESTED_API_PAPR 2 bool capabilities_set; uint32_t pvr_base; + + /** + * l0_guest_heap_inuse: The currently used bytes in the Hypervisor's Guest + * Management Space associated with the Host Partition. + **/ + uint64_t l0_guest_heap_inuse; + + /** + * host_heap_max: The maximum bytes available in the Hypervisor's Guest + * Management Space associated with the Host Partition. + **/ + uint64_t l0_guest_heap_max; + + /** + * host_pagetable: The currently used bytes in the Hypervisor's Guest + * Page Table Management Space associated with the Host Partition. + **/ + uint64_t l0_guest_pgtable_size_inuse; + + /** + * host_pagetable_max: The maximum bytes available in the Hypervisor's Guest + * Page Table Management Space associated with the Host Partition. + **/ + uint64_t l0_guest_pgtable_size_max; + + /** + * host_pagetable_reclaim: The amount of space in bytes that has been + * reclaimed due to overcommit in the Hypervisor's Guest Page Table + * Management Space associated with the Host Partition. + **/ + uint64_t l0_guest_pgtable_reclaimed; + GHashTable *guests; } SpaprMachineStateNested; @@ -229,9 +267,15 @@ typedef struct SpaprMachineStateNestedGuest { #define HVMASK_HDEXCR 0x00000000FFFFFFFF #define HVMASK_TB_OFFSET 0x000000FFFFFFFFFF #define GSB_MAX_BUF_SIZE (1024 * 1024) -#define H_GUEST_GETSET_STATE_FLAG_GUEST_WIDE 0x8000000000000000 -#define GUEST_STATE_REQUEST_GUEST_WIDE 0x1 -#define GUEST_STATE_REQUEST_SET 0x2 +#define H_GUEST_GET_STATE_FLAGS_MASK 0xC000000000000000ULL +#define H_GUEST_SET_STATE_FLAGS_MASK 0x8000000000000000ULL +#define H_GUEST_SET_STATE_FLAGS_GUEST_WIDE 0x8000000000000000ULL +#define H_GUEST_GET_STATE_FLAGS_GUEST_WIDE 0x8000000000000000ULL +#define H_GUEST_GET_STATE_FLAGS_HOST_WIDE 0x4000000000000000ULL + +#define GUEST_STATE_REQUEST_GUEST_WIDE 0x1 +#define GUEST_STATE_REQUEST_HOST_WIDE 0x2 +#define GUEST_STATE_REQUEST_SET 0x4 /* * As per ISA v3.1B, following bits are reserved: @@ -251,6 +295,15 @@ typedef struct SpaprMachineStateNestedGuest { .copy = (c) \ } +#define GSBE_NESTED_MACHINE_DW(i, f) { \ + .id = (i), \ + .size = 8, \ + .location = get_machine_ptr, \ + .offset = offsetof(struct SpaprMachineStateNested, f), \ + .copy = copy_state_8to8, \ + .mask = HVMASK_DEFAULT \ +} + #define GSBE_NESTED(i, sz, f, c) { \ .id = (i), \ .size = (sz), \ @@ -509,9 +562,11 @@ struct guest_state_element_type { uint16_t id; int size; #define GUEST_STATE_ELEMENT_TYPE_FLAG_GUEST_WIDE 0x1 -#define GUEST_STATE_ELEMENT_TYPE_FLAG_READ_ONLY 0x2 +#define GUEST_STATE_ELEMENT_TYPE_FLAG_HOST_WIDE 0x2 +#define GUEST_STATE_ELEMENT_TYPE_FLAG_READ_ONLY 0x4 uint16_t flags; - void *(*location)(SpaprMachineStateNestedGuest *, target_ulong); + void *(*location)(struct SpaprMachineState *, SpaprMachineStateNestedGuest *, + target_ulong); size_t offset; void (*copy)(void *, void *, bool); uint64_t mask; From e8291ec16da80566c121c68d9112be458954d90b Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Thu, 19 Dec 2024 13:40:31 +1000 Subject: [PATCH 0942/1179] target/ppc: fix timebase register reset state (H)DEC and PURR get reset before icount does, which causes them to be skewed and not match the init state. This can cause replay to not match the recorded trace exactly. For DEC and HDEC this is usually not noticable since they tend to get programmed before affecting the target machine. PURR has been observed to cause replay bugs when running Linux. Fix this by resetting using a time of 0. Message-ID: <20241219034035.1826173-2-npiggin@gmail.com> Signed-off-by: Nicholas Piggin --- hw/ppc/ppc.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/hw/ppc/ppc.c b/hw/ppc/ppc.c index 90e3db5cfeba..3a80931538f1 100644 --- a/hw/ppc/ppc.c +++ b/hw/ppc/ppc.c @@ -1123,16 +1123,21 @@ void cpu_ppc_tb_reset(CPUPPCState *env) timer_del(tb_env->hdecr_timer); ppc_set_irq(cpu, PPC_INTERRUPT_HDECR, 0); tb_env->hdecr_next = 0; + _cpu_ppc_store_hdecr(cpu, 0, 0, 0, 64); } /* * There is a bug in Linux 2.4 kernels: * if a decrementer exception is pending when it enables msr_ee at startup, * it's not ready to handle it... + * + * On machine reset, this is called before icount is reset, so for + * icount-mode, setting TB registers using now == qemu_clock_get_ns() + * results in them being garbage after icount is reset. Use an + * explicit now == 0 to get a consistent reset state. */ - cpu_ppc_store_decr(env, -1); - cpu_ppc_store_hdecr(env, -1); - cpu_ppc_store_purr(env, 0x0000000000000000ULL); + _cpu_ppc_store_decr(cpu, 0, 0, -1, 64); + _cpu_ppc_store_purr(env, 0, 0); } void cpu_ppc_tb_free(CPUPPCState *env) From d8a624515a9773cb9d7afd47926bd64ae8fa0fc6 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Thu, 19 Dec 2024 13:40:35 +1000 Subject: [PATCH 0943/1179] target/ppc: Wire up BookE ATB registers for e500 family From the Freescale PowerPC Architecture Primer: Alternate time base APU. This APU, implemented on the e500v2, defines a 64-bit time base counter that differs from the PowerPC defined time base in that it is not writable and counts at a different, and typically much higher, frequency. The alternate time base always counts up, wrapping when the 64-bit count overflows. This implementation of ATB uses the same frequency as the TB. The existing spr_read_atbu/l functions are unused without this patch to wire them into the SPR. RTEMS uses this SPR on the e6500, though this hasn't been tested. Message-ID: <20241219034035.1826173-6-npiggin@gmail.com> Signed-off-by: Nicholas Piggin --- target/ppc/cpu_init.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index 9dc5ace8289a..8b590e7f17c1 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -922,6 +922,18 @@ static void register_BookE206_sprs(CPUPPCState *env, uint32_t mas_mask, #endif } +static void register_atb_sprs(CPUPPCState *env) +{ + spr_register(env, SPR_ATBL, "ATBL", + &spr_read_atbl, SPR_NOACCESS, + &spr_read_atbl, SPR_NOACCESS, + 0x00000000); + spr_register(env, SPR_ATBU, "ATBU", + &spr_read_atbu, SPR_NOACCESS, + &spr_read_atbu, SPR_NOACCESS, + 0x00000000); +} + /* SPR specific to PowerPC 440 implementation */ static void register_440_sprs(CPUPPCState *env) { @@ -2911,6 +2923,11 @@ static void init_proc_e500(CPUPPCState *env, int version) register_BookE206_sprs(env, 0x000000DF, tlbncfg, mmucfg); register_usprgh_sprs(env); + if (version != fsl_e500v1) { + /* e500v1 has no support for alternate timebase */ + register_atb_sprs(env); + } + spr_register(env, SPR_HID0, "HID0", SPR_NOACCESS, SPR_NOACCESS, &spr_read_generic, &spr_write_generic, From b4aa82dc3a698abdcdef342fc1f4620f888c3cf7 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Thu, 19 Dec 2024 13:40:34 +1000 Subject: [PATCH 0944/1179] target/ppc: Avoid warning message for zero process table entries A translation that encounters a process table entry that is zero is something that Linux does to cause certain kernel NULL pointer dereferences to fault. It is not itself a programming error, so avoid the guest error log. Message-ID: <20241219034035.1826173-5-npiggin@gmail.com> Signed-off-by: Nicholas Piggin --- target/ppc/mmu-radix64.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/target/ppc/mmu-radix64.c b/target/ppc/mmu-radix64.c index 1d3d9e1be773..461eda4a3dce 100644 --- a/target/ppc/mmu-radix64.c +++ b/target/ppc/mmu-radix64.c @@ -571,6 +571,20 @@ static int ppc_radix64_process_scoped_xlate(PowerPCCPU *cpu, prtbe0 = ldq_phys(cs->as, h_raddr); } + /* + * Some Linux uses a zero process table entry in PID!=0 for kernel context + * without userspace in order to fault on NULL dereference, because using + * PIDR=0 for the kernel causes the Q0 page table to be used to translate + * Q3 as well. Check for that case here to avoid the invalid configuration + * message. + */ + if (unlikely(!prtbe0)) { + if (guest_visible) { + ppc_radix64_raise_si(cpu, access_type, eaddr, DSISR_R_BADCONFIG); + } + return 1; + } + /* Walk Radix Tree from Process Table Entry to Convert EA to RA */ *g_page_size = PRTBE_R_GET_RTS(prtbe0); base_addr = prtbe0 & PRTBE_R_RPDB; From d91b101da1075f57dda0f30f6802129328716da1 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Thu, 19 Dec 2024 13:40:33 +1000 Subject: [PATCH 0945/1179] spapr: Generate random HASHPKEYR for spapr machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hypervisor is expected to create a value for the HASHPKEY SPR for each partition. Currently it uses zero for all partitions, use a random number instead, which in theory might make kernel ROP protection more secure. Signed-of-by: Nicholas Piggin Reviewed-by: Harsh Prateek Bora Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20241219034035.1826173-4-npiggin@gmail.com> Signed-off-by: Nicholas Piggin --- hw/ppc/spapr.c | 3 +++ hw/ppc/spapr_cpu_core.c | 2 ++ include/hw/ppc/spapr.h | 1 + 3 files changed, 6 insertions(+) diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index fcd2ca515c1a..a415e51d077a 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -2917,6 +2917,9 @@ static void spapr_machine_init(MachineState *machine) spapr_ovec_set(spapr->ov5, OV5_XIVE_EXPLOIT); } + qemu_guest_getrandom_nofail(&spapr->hashpkey_val, + sizeof(spapr->hashpkey_val)); + /* init CPUs */ spapr_init_cpus(spapr); diff --git a/hw/ppc/spapr_cpu_core.c b/hw/ppc/spapr_cpu_core.c index 9e0e0648a723..0671d9e44b48 100644 --- a/hw/ppc/spapr_cpu_core.c +++ b/hw/ppc/spapr_cpu_core.c @@ -273,6 +273,8 @@ static bool spapr_realize_vcpu(PowerPCCPU *cpu, SpaprMachineState *spapr, env->spr_cb[SPR_PIR].default_value = cs->cpu_index; env->spr_cb[SPR_TIR].default_value = thread_index; + env->spr_cb[SPR_HASHPKEYR].default_value = spapr->hashpkey_val; + cpu_ppc_set_1lpar(cpu); /* Set time-base frequency to 512 MHz. vhyp must be set first. */ diff --git a/include/hw/ppc/spapr.h b/include/hw/ppc/spapr.h index d227f0b94b93..39bd5bd5ed31 100644 --- a/include/hw/ppc/spapr.h +++ b/include/hw/ppc/spapr.h @@ -203,6 +203,7 @@ struct SpaprMachineState { uint32_t fdt_initial_size; void *fdt_blob; uint8_t fdt_rng_seed[32]; + uint64_t hashpkey_val; long kernel_size; bool kernel_le; uint64_t kernel_addr; From 222d37d38999546d8407139410ee45df98fac805 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Thu, 27 Feb 2025 17:39:15 +0100 Subject: [PATCH 0946/1179] ppc/amigaone: Simplify replacement dummy_fw There's no need to do shift in a loop, doing it in one instruction works just as well, only the result is used. Signed-off-by: BALATON Zoltan Reviewed-by: Nicholas Piggin Message-ID: <446bf740cbb99422be2cc5a31e51a1034eddded7.1740673173.git.balaton@eik.bme.hu> Signed-off-by: Nicholas Piggin --- hw/ppc/amigaone.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index b02792221cc5..4290d5861396 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -41,10 +41,7 @@ /* AmigaOS calls this routine from ROM, use this if no firmware loaded */ static const char dummy_fw[] = { - 0x38, 0x00, 0x00, 0x08, /* li r0,8 */ - 0x7c, 0x09, 0x03, 0xa6, /* mtctr r0 */ - 0x54, 0x63, 0xf8, 0x7e, /* srwi r3,r3,1 */ - 0x42, 0x00, 0xff, 0xfc, /* bdnz 0x8 */ + 0x54, 0x63, 0xc2, 0x3e, /* srwi r3,r3,8 */ 0x7c, 0x63, 0x18, 0xf8, /* not r3,r3 */ 0x4e, 0x80, 0x00, 0x20, /* blr */ }; From addff513a10ea7cf3bab27b65ec492e7619eadc5 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Thu, 27 Feb 2025 17:39:16 +0100 Subject: [PATCH 0947/1179] ppc/amigaone: Implement NVRAM emulation The board has a battery backed NVRAM where U-Boot environment is stored which is also accessed by AmigaOS and e.g. C:NVGetVar command crashes without it having at least a valid checksum. [npiggin: 32-bit compile fix] Signed-off-by: BALATON Zoltan Reviewed-by: Nicholas Piggin Message-ID: <7e4c0107ef6bdc2b20fb1e780a188275c7dc1e49.1740673173.git.balaton@eik.bme.hu> Signed-off-by: Nicholas Piggin --- hw/ppc/amigaone.c | 113 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index 4290d5861396..feb2cf452cd1 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -21,10 +21,13 @@ #include "hw/ide/pci.h" #include "hw/i2c/smbus_eeprom.h" #include "hw/ppc/ppc.h" +#include "system/block-backend.h" #include "system/qtest.h" #include "system/reset.h" #include "kvm_ppc.h" +#include /* for crc32 */ + #define BUS_FREQ_HZ 100000000 /* @@ -46,6 +49,100 @@ static const char dummy_fw[] = { 0x4e, 0x80, 0x00, 0x20, /* blr */ }; +#define NVRAM_ADDR 0xfd0e0000 +#define NVRAM_SIZE (4 * KiB) + +#define TYPE_A1_NVRAM "a1-nvram" +OBJECT_DECLARE_SIMPLE_TYPE(A1NVRAMState, A1_NVRAM) + +struct A1NVRAMState { + SysBusDevice parent_obj; + + MemoryRegion mr; + BlockBackend *blk; +}; + +static uint64_t nvram_read(void *opaque, hwaddr addr, unsigned int size) +{ + /* read callback not used because of romd mode */ + g_assert_not_reached(); +} + +static void nvram_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + A1NVRAMState *s = opaque; + uint8_t *p = memory_region_get_ram_ptr(&s->mr); + + p[addr] = val; + if (s->blk) { + blk_pwrite(s->blk, addr, 1, &val, 0); + } +} + +static const MemoryRegionOps nvram_ops = { + .read = nvram_read, + .write = nvram_write, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void nvram_realize(DeviceState *dev, Error **errp) +{ + A1NVRAMState *s = A1_NVRAM(dev); + void *p; + uint32_t *c; + + memory_region_init_rom_device(&s->mr, NULL, &nvram_ops, s, "nvram", + NVRAM_SIZE, &error_fatal); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr); + c = p = memory_region_get_ram_ptr(&s->mr); + if (s->blk) { + if (blk_getlength(s->blk) != NVRAM_SIZE) { + error_setg(errp, "NVRAM backing file size must be %" PRId64 "bytes", + NVRAM_SIZE); + return; + } + blk_set_perm(s->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, + BLK_PERM_ALL, &error_fatal); + if (blk_pread(s->blk, 0, NVRAM_SIZE, p, 0) < 0) { + error_setg(errp, "Cannot read NVRAM contents from backing file"); + return; + } + } + if (*c == 0) { + *c = cpu_to_be32(crc32(0, p + 4, NVRAM_SIZE - 4)); + if (s->blk) { + blk_pwrite(s->blk, 0, 4, p, 0); + } + } +} + +static const Property nvram_properties[] = { + DEFINE_PROP_DRIVE("drive", A1NVRAMState, blk), +}; + +static void nvram_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = nvram_realize; + device_class_set_props(dc, nvram_properties); +} + +static const TypeInfo nvram_types[] = { + { + .name = TYPE_A1_NVRAM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(A1NVRAMState), + .class_init = nvram_class_init, + }, +}; +DEFINE_TYPES(nvram_types) + static void amigaone_cpu_reset(void *opaque) { PowerPCCPU *cpu = opaque; @@ -72,7 +169,7 @@ static void amigaone_init(MachineState *machine) DeviceState *dev; I2CBus *i2c_bus; uint8_t *spd_data; - int i; + DriveInfo *di; /* init CPU */ cpu = POWERPC_CPU(cpu_create(machine->cpu_type)); @@ -97,6 +194,16 @@ static void amigaone_init(MachineState *machine) memory_region_add_subregion(get_system_memory(), 0x40000000, mr); } + /* nvram */ + dev = qdev_new(TYPE_A1_NVRAM); + di = drive_get(IF_MTD, 0, 0); + if (di) { + qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(di)); + } + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + memory_region_add_subregion(get_system_memory(), NVRAM_ADDR, + sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0)); + /* allocate and load firmware */ rom = g_new(MemoryRegion, 1); memory_region_init_rom(rom, NULL, "rom", PROM_SIZE, &error_fatal); @@ -136,7 +243,7 @@ static void amigaone_init(MachineState *machine) pci_mem = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1); mr = g_new(MemoryRegion, 1); memory_region_init_alias(mr, OBJECT(dev), "pci-mem-low", pci_mem, - 0, 0x1000000); + 0, 0xe0000); memory_region_add_subregion(get_system_memory(), 0xfd000000, mr); mr = g_new(MemoryRegion, 1); memory_region_init_alias(mr, OBJECT(dev), "pci-mem-high", pci_mem, @@ -153,7 +260,7 @@ static void amigaone_init(MachineState *machine) qdev_connect_gpio_out_named(DEVICE(via), "intr", 0, qdev_get_gpio_in(DEVICE(cpu), PPC6xx_INPUT_INT)); - for (i = 0; i < PCI_NUM_PINS; i++) { + for (int i = 0; i < PCI_NUM_PINS; i++) { qdev_connect_gpio_out(dev, i, qdev_get_gpio_in_named(DEVICE(via), "pirq", i)); } From c2bed9957a7c30cec8dacc9581700088434889b6 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Thu, 27 Feb 2025 17:39:17 +0100 Subject: [PATCH 0948/1179] ppc/amigaone: Add default environment Initialise empty NVRAM with default values. This also enables IDE UDMA mode in AmigaOS that is faster but has to be enabled in environment due to problems with real hardware but that does not affect emulation so we can use faster defaults here. Signed-off-by: BALATON Zoltan Reviewed-by: Nicholas Piggin Message-ID: <4d63f88191612329e0ca8102c7c0d4fc626dc372.1740673173.git.balaton@eik.bme.hu> Signed-off-by: Nicholas Piggin --- hw/ppc/amigaone.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index feb2cf452cd1..1c6f2a944de2 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -52,6 +52,28 @@ static const char dummy_fw[] = { #define NVRAM_ADDR 0xfd0e0000 #define NVRAM_SIZE (4 * KiB) +static char default_env[] = + "baudrate=115200\0" + "stdout=vga\0" + "stdin=ps2kbd\0" + "bootcmd=boota; menu; run menuboot_cmd\0" + "boot1=ide\0" + "boot2=cdrom\0" + "boota_timeout=3\0" + "ide_doreset=on\0" + "pci_irqa=9\0" + "pci_irqa_select=level\0" + "pci_irqb=10\0" + "pci_irqb_select=level\0" + "pci_irqc=11\0" + "pci_irqc_select=level\0" + "pci_irqd=7\0" + "pci_irqd_select=level\0" + "a1ide_irq=1111\0" + "a1ide_xfer=FFFF\0"; +#define CRC32_DEFAULT_ENV 0xb5548481 +#define CRC32_ALL_ZEROS 0x603b0489 + #define TYPE_A1_NVRAM "a1-nvram" OBJECT_DECLARE_SIMPLE_TYPE(A1NVRAMState, A1_NVRAM) @@ -94,7 +116,7 @@ static void nvram_realize(DeviceState *dev, Error **errp) { A1NVRAMState *s = A1_NVRAM(dev); void *p; - uint32_t *c; + uint32_t crc, *c; memory_region_init_rom_device(&s->mr, NULL, &nvram_ops, s, "nvram", NVRAM_SIZE, &error_fatal); @@ -113,12 +135,25 @@ static void nvram_realize(DeviceState *dev, Error **errp) return; } } + crc = crc32(0, p + 4, NVRAM_SIZE - 4); + if (crc == CRC32_ALL_ZEROS) { /* If env is uninitialized set default */ + *c = cpu_to_be32(CRC32_DEFAULT_ENV); + /* Also copies terminating \0 as env is terminated by \0\0 */ + memcpy(p + 4, default_env, sizeof(default_env)); + if (s->blk) { + blk_pwrite(s->blk, 0, sizeof(crc) + sizeof(default_env), p, 0); + } + return; + } if (*c == 0) { *c = cpu_to_be32(crc32(0, p + 4, NVRAM_SIZE - 4)); if (s->blk) { blk_pwrite(s->blk, 0, 4, p, 0); } } + if (be32_to_cpu(*c) != crc) { + warn_report("NVRAM checksum mismatch"); + } } static const Property nvram_properties[] = { From 34f053d86b2835c038d75e3d6ac967d73594e157 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Thu, 27 Feb 2025 17:39:18 +0100 Subject: [PATCH 0949/1179] ppc/amigaone: Add kernel and initrd support Add support for -kernel, -initrd and -append command line options. Signed-off-by: BALATON Zoltan Reviewed-by: Nicholas Piggin Message-ID: <489b1be5d95d5153e924c95b0691b8b53f9ffb9e.1740673173.git.balaton@eik.bme.hu> Signed-off-by: Nicholas Piggin --- hw/ppc/amigaone.c | 113 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index 1c6f2a944de2..359f5fa125bb 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -25,11 +25,14 @@ #include "system/qtest.h" #include "system/reset.h" #include "kvm_ppc.h" +#include "elf.h" #include /* for crc32 */ #define BUS_FREQ_HZ 100000000 +#define INITRD_MIN_ADDR 0x600000 + /* * Firmware binary available at * https://www.hyperion-entertainment.com/index.php/downloads?view=files&parent=28 @@ -178,12 +181,68 @@ static const TypeInfo nvram_types[] = { }; DEFINE_TYPES(nvram_types) +struct boot_info { + hwaddr entry; + hwaddr stack; + hwaddr bd_info; + hwaddr initrd_start; + hwaddr initrd_end; + hwaddr cmdline_start; + hwaddr cmdline_end; +}; + +/* Board info struct from U-Boot */ +struct bd_info { + uint32_t bi_memstart; + uint32_t bi_memsize; + uint32_t bi_flashstart; + uint32_t bi_flashsize; + uint32_t bi_flashoffset; + uint32_t bi_sramstart; + uint32_t bi_sramsize; + uint32_t bi_bootflags; + uint32_t bi_ip_addr; + uint8_t bi_enetaddr[6]; + uint16_t bi_ethspeed; + uint32_t bi_intfreq; + uint32_t bi_busfreq; + uint32_t bi_baudrate; +} QEMU_PACKED; + +static void create_bd_info(hwaddr addr, ram_addr_t ram_size) +{ + struct bd_info *bd = g_new0(struct bd_info, 1); + + bd->bi_memsize = cpu_to_be32(ram_size); + bd->bi_flashstart = cpu_to_be32(PROM_ADDR); + bd->bi_flashsize = cpu_to_be32(1); /* match what U-Boot detects */ + bd->bi_bootflags = cpu_to_be32(1); + bd->bi_intfreq = cpu_to_be32(11.5 * BUS_FREQ_HZ); + bd->bi_busfreq = cpu_to_be32(BUS_FREQ_HZ); + bd->bi_baudrate = cpu_to_be32(115200); + + cpu_physical_memory_write(addr, bd, sizeof(*bd)); +} + static void amigaone_cpu_reset(void *opaque) { PowerPCCPU *cpu = opaque; + CPUPPCState *env = &cpu->env; cpu_reset(CPU(cpu)); - cpu_ppc_tb_reset(&cpu->env); + if (env->load_info) { + struct boot_info *bi = env->load_info; + + env->gpr[1] = bi->stack; + env->gpr[2] = 1024; + env->gpr[3] = bi->bd_info; + env->gpr[4] = bi->initrd_start; + env->gpr[5] = bi->initrd_end; + env->gpr[6] = bi->cmdline_start; + env->gpr[7] = bi->cmdline_end; + env->nip = bi->entry; + } + cpu_ppc_tb_reset(env); } static void fix_spd_data(uint8_t *spd) @@ -205,6 +264,8 @@ static void amigaone_init(MachineState *machine) I2CBus *i2c_bus; uint8_t *spd_data; DriveInfo *di; + hwaddr loadaddr; + struct boot_info *bi = NULL; /* init CPU */ cpu = POWERPC_CPU(cpu_create(machine->cpu_type)); @@ -301,6 +362,56 @@ static void amigaone_init(MachineState *machine) } pci_ide_create_devs(PCI_DEVICE(object_resolve_path_component(via, "ide"))); pci_vga_init(pci_bus); + + if (!machine->kernel_filename) { + return; + } + + /* handle -kernel, -initrd, -append options and emulate U-Boot */ + bi = g_new0(struct boot_info, 1); + cpu->env.load_info = bi; + + loadaddr = MIN(machine->ram_size, 256 * MiB); + bi->bd_info = loadaddr - 8 * MiB; + create_bd_info(bi->bd_info, machine->ram_size); + bi->stack = bi->bd_info - 64 * KiB - 8; + + if (machine->kernel_cmdline && machine->kernel_cmdline[0]) { + size_t len = strlen(machine->kernel_cmdline); + + loadaddr = bi->bd_info + 1 * MiB; + cpu_physical_memory_write(loadaddr, machine->kernel_cmdline, len + 1); + bi->cmdline_start = loadaddr; + bi->cmdline_end = loadaddr + len + 1; /* including terminating '\0' */ + } + + sz = load_elf(machine->kernel_filename, NULL, NULL, NULL, + &bi->entry, &loadaddr, NULL, NULL, + ELFDATA2MSB, PPC_ELF_MACHINE, 0, 0); + if (sz <= 0) { + sz = load_uimage(machine->kernel_filename, &bi->entry, &loadaddr, + NULL, NULL, NULL); + } + if (sz <= 0) { + error_report("Could not load kernel '%s'", + machine->kernel_filename); + exit(1); + } + loadaddr += sz; + + if (machine->initrd_filename) { + loadaddr = ROUND_UP(loadaddr + 4 * MiB, 4 * KiB); + loadaddr = MAX(loadaddr, INITRD_MIN_ADDR); + sz = load_image_targphys(machine->initrd_filename, loadaddr, + bi->bd_info - loadaddr); + if (sz <= 0) { + error_report("Could not load initrd '%s'", + machine->initrd_filename); + exit(1); + } + bi->initrd_start = loadaddr; + bi->initrd_end = loadaddr + sz; + } } static void amigaone_machine_init(MachineClass *mc) From e6521e41bada87e32ffe58f2b2e6cb69be799136 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Thu, 27 Feb 2025 17:39:19 +0100 Subject: [PATCH 0950/1179] ppc/amigaone: Add #defines for memory map constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggested-by: Nicholas Piggin Signed-off-by: BALATON Zoltan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Nicholas Piggin Message-ID: <3b8e54ad9220d57e7b0a33f3570e880f26677ce8.1740673173.git.balaton@eik.bme.hu> Signed-off-by: Nicholas Piggin --- hw/ppc/amigaone.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/hw/ppc/amigaone.c b/hw/ppc/amigaone.c index 359f5fa125bb..483512125fcf 100644 --- a/hw/ppc/amigaone.c +++ b/hw/ppc/amigaone.c @@ -32,6 +32,14 @@ #define BUS_FREQ_HZ 100000000 #define INITRD_MIN_ADDR 0x600000 +#define INIT_RAM_ADDR 0x40000000 + +#define PCI_HIGH_ADDR 0x80000000 +#define PCI_HIGH_SIZE 0x7d000000 +#define PCI_LOW_ADDR 0xfd000000 +#define PCI_LOW_SIZE 0xe0000 + +#define ARTICIA_ADDR 0xfe000000 /* * Firmware binary available at @@ -287,7 +295,7 @@ static void amigaone_init(MachineState *machine) /* Firmware uses this area for startup */ mr = g_new(MemoryRegion, 1); memory_region_init_ram(mr, NULL, "init-cache", 32 * KiB, &error_fatal); - memory_region_add_subregion(get_system_memory(), 0x40000000, mr); + memory_region_add_subregion(get_system_memory(), INIT_RAM_ADDR, mr); } /* nvram */ @@ -322,7 +330,7 @@ static void amigaone_init(MachineState *machine) } /* Articia S */ - dev = sysbus_create_simple(TYPE_ARTICIA, 0xfe000000, NULL); + dev = sysbus_create_simple(TYPE_ARTICIA, ARTICIA_ADDR, NULL); i2c_bus = I2C_BUS(qdev_get_child_bus(dev, "smbus")); if (machine->ram_size > 512 * MiB) { @@ -339,12 +347,12 @@ static void amigaone_init(MachineState *machine) pci_mem = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1); mr = g_new(MemoryRegion, 1); memory_region_init_alias(mr, OBJECT(dev), "pci-mem-low", pci_mem, - 0, 0xe0000); - memory_region_add_subregion(get_system_memory(), 0xfd000000, mr); + 0, PCI_LOW_SIZE); + memory_region_add_subregion(get_system_memory(), PCI_LOW_ADDR, mr); mr = g_new(MemoryRegion, 1); memory_region_init_alias(mr, OBJECT(dev), "pci-mem-high", pci_mem, - 0x80000000, 0x7d000000); - memory_region_add_subregion(get_system_memory(), 0x80000000, mr); + PCI_HIGH_ADDR, PCI_HIGH_SIZE); + memory_region_add_subregion(get_system_memory(), PCI_HIGH_ADDR, mr); pci_bus = PCI_BUS(qdev_get_child_bus(dev, "pci.0")); /* VIA VT82c686B South Bridge (multifunction PCI device) */ From 0f17ae24b53eaab4bbe9cfab267c536e2f7fdbd7 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Tue, 4 Mar 2025 21:59:26 +0100 Subject: [PATCH 0951/1179] docs/system/ppc/amigang.rst: Update for NVRAM emulation Add NVRAM and hint on how to make it persistent. Also update Linux boot section which should now boot automatically with the new NVRAM defaults so manual settings in menu may not be needed normally. Signed-off-by: BALATON Zoltan Reviewed-by: Nicholas Piggin Message-ID: <20250304205926.87E364E6010@zero.eik.bme.hu> Signed-off-by: Nicholas Piggin --- docs/system/ppc/amigang.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/system/ppc/amigang.rst b/docs/system/ppc/amigang.rst index e2c9cb74b7f0..21bb14ed0991 100644 --- a/docs/system/ppc/amigang.rst +++ b/docs/system/ppc/amigang.rst @@ -21,6 +21,7 @@ Emulated devices * VIA VT82C686B south bridge * PCI VGA compatible card (guests may need other card instead) * PS/2 keyboard and mouse + * 4 KiB NVRAM (use ``-drive if=mtd,format=raw,file=nvram.bin`` to keep contents persistent) Firmware -------- @@ -54,14 +55,14 @@ To boot the system run: -cdrom "A1 Linux Net Installer.iso" \ -device ati-vga,model=rv100,romfile=VGABIOS-lgpl-latest.bin -From the firmware menu that appears select ``Boot sequence`` → -``Amiga Multiboot Options`` and set ``Boot device 1`` to -``Onboard VIA IDE CDROM``. Then hit escape until the main screen appears again, -hit escape once more and from the exit menu that appears select either -``Save settings and exit`` or ``Use settings for this session only``. It may -take a long time loading the kernel into memory but eventually it boots and the -installer becomes visible. The ``ati-vga`` RV100 emulation is not -complete yet so only frame buffer works, DRM and 3D is not available. +If a firmware menu appears, select ``Boot sequence`` → ``Amiga Multiboot Options`` +and set ``Boot device 1`` to ``Onboard VIA IDE CDROM``. Then hit escape until +the main screen appears again, hit escape once more and from the exit menu that +appears select either ``Save settings and exit`` or ``Use settings for this +session only``. It may take a long time loading the kernel into memory but +eventually it boots and the installer becomes visible. The ``ati-vga`` RV100 +emulation is not complete yet so only frame buffer works, DRM and 3D is not +available. Genesi/bPlan Pegasos II (``pegasos2``) ====================================== From 000a41b69c3c34ae132ebc737dbe56f08fab50a9 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Thu, 6 Feb 2025 17:53:31 +0100 Subject: [PATCH 0952/1179] block: Remove unused blk_op_is_blocked() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit fc4e394b28 removed the last caller of blk_op_is_blocked(). Remove the now unused function. Signed-off-by: Kevin Wolf Message-ID: <20250206165331.379033-1-kwolf@redhat.com> Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- block/block-backend.c | 12 ------------ include/system/block-backend-global-state.h | 1 - 2 files changed, 13 deletions(-) diff --git a/block/block-backend.c b/block/block-backend.c index 9288f7e1c69e..a402db13f291 100644 --- a/block/block-backend.c +++ b/block/block-backend.c @@ -2357,18 +2357,6 @@ void *blk_blockalign(BlockBackend *blk, size_t size) return qemu_blockalign(blk ? blk_bs(blk) : NULL, size); } -bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp) -{ - BlockDriverState *bs = blk_bs(blk); - GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); - - if (!bs) { - return false; - } - - return bdrv_op_is_blocked(bs, op, errp); -} /** * Return BB's current AioContext. Note that this context may change diff --git a/include/system/block-backend-global-state.h b/include/system/block-backend-global-state.h index 9cc9b008ec7d..35b5e8380b93 100644 --- a/include/system/block-backend-global-state.h +++ b/include/system/block-backend-global-state.h @@ -86,7 +86,6 @@ bool blk_supports_write_perm(BlockBackend *blk); bool blk_is_sg(BlockBackend *blk); void blk_set_enable_write_cache(BlockBackend *blk, bool wce); int blk_get_flags(BlockBackend *blk); -bool blk_op_is_blocked(BlockBackend *blk, BlockOpType op, Error **errp); int blk_set_aio_context(BlockBackend *blk, AioContext *new_context, Error **errp); void blk_add_aio_context_notifier(BlockBackend *blk, From b75c5f9879166b86ed7c48b772fdcd0693e8a9a3 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Mon, 10 Mar 2025 11:48:58 +0100 Subject: [PATCH 0953/1179] block: Zero block driver state before reopening Block drivers assume in their .bdrv_open() implementation that their state in bs->opaque has been zeroed; it is initially allocated with g_malloc0() in bdrv_open_driver(). bdrv_snapshot_goto() needs to make sure that it is zeroed again before calling drv->bdrv_open() to avoid that block drivers use stale values. One symptom of this bug is VMDK running into a double free when the user tries to apply an internal snapshot like 'qemu-img snapshot -a test test.vmdk'. This should be a graceful error because VMDK doesn't support internal snapshots. ==25507== Invalid free() / delete / delete[] / realloc() ==25507== at 0x484B347: realloc (vg_replace_malloc.c:1801) ==25507== by 0x54B592A: g_realloc (gmem.c:171) ==25507== by 0x1B221D: vmdk_add_extent (../block/vmdk.c:570) ==25507== by 0x1B1084: vmdk_open_sparse (../block/vmdk.c:1059) ==25507== by 0x1AF3D8: vmdk_open (../block/vmdk.c:1371) ==25507== by 0x1A2AE0: bdrv_snapshot_goto (../block/snapshot.c:299) ==25507== by 0x205C77: img_snapshot (../qemu-img.c:3500) ==25507== by 0x58FA087: (below main) (libc_start_call_main.h:58) ==25507== Address 0x832f3e0 is 0 bytes inside a block of size 272 free'd ==25507== at 0x4846B83: free (vg_replace_malloc.c:989) ==25507== by 0x54AEAC4: g_free (gmem.c:208) ==25507== by 0x1AF629: vmdk_close (../block/vmdk.c:2889) ==25507== by 0x1A2A9C: bdrv_snapshot_goto (../block/snapshot.c:290) ==25507== by 0x205C77: img_snapshot (../qemu-img.c:3500) ==25507== by 0x58FA087: (below main) (libc_start_call_main.h:58) This error was discovered by fuzzing qemu-img. Cc: qemu-stable@nongnu.org Closes: https://gitlab.com/qemu-project/qemu/-/issues/2853 Closes: https://gitlab.com/qemu-project/qemu/-/issues/2851 Reported-by: Denis Rastyogin Signed-off-by: Kevin Wolf Message-ID: <20250310104858.28221-1-kwolf@redhat.com> Signed-off-by: Kevin Wolf --- block/snapshot.c | 1 + 1 file changed, 1 insertion(+) diff --git a/block/snapshot.c b/block/snapshot.c index 9c44780e96db..22567f1fb983 100644 --- a/block/snapshot.c +++ b/block/snapshot.c @@ -296,6 +296,7 @@ int bdrv_snapshot_goto(BlockDriverState *bs, bdrv_graph_wrunlock(); ret = bdrv_snapshot_goto(fallback_bs, snapshot_id, errp); + memset(bs->opaque, 0, drv->instance_size); open_ret = drv->bdrv_open(bs, options, bs->open_flags, &local_err); qobject_unref(options); if (open_ret < 0) { From 5aed8b0f0be25d2554f8bd76211e43b51e58f736 Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:21 +0800 Subject: [PATCH 0954/1179] vfio/igd: Remove GTT write quirk in IO BAR 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IO BAR4 of IGD devices contains a pair of 32-bit address/data registers, MMIO_Index (0x0) and MMIO_Data (0x4), which provide access to the MMIO BAR0 (GTTMMADR) from IO space. These registers are probably only used by the VBIOS, and are not documented by intel. The observed layout of MMIO_Index register is: 31 2 1 0 +-------------------------------------------------------------------+ | Offset | Rsvd | Sel | +-------------------------------------------------------------------+ - Offset: Byte offset in specified region, 4-byte aligned. - Sel: Region selector 0: MMIO register region (first half of MMIO BAR0) 1: GTT region (second half of MMIO BAR0). Pre Gen11 only. Currently, QEMU implements a quirk that adjusts the guest Data Stolen Memory (DSM) region address to be (addr - host BDSM + guest BDSM) when programming GTT entries via IO BAR4, assuming guest still programs GTT with host DSM address, which is not the case. Guest's BDSM register is emulated and initialized to 0 at startup by QEMU, then SeaBIOS programs its value[1]. As result, the address programmed to GTT entries by VBIOS running in guest are valid GPA, and this unnecessary adjustment brings inconsistency. [1] https://gitlab.com/qemu-project/seabios/-/blob/1.12-stable/src/fw/pciinit.c#L319-332 Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-2-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 191 +------------------------------------------------- 1 file changed, 1 insertion(+), 190 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index b1a237edd660..ca3a32f4f2b8 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -106,12 +106,6 @@ static int igd_gen(VFIOPCIDevice *vdev) return -1; } -typedef struct VFIOIGDQuirk { - struct VFIOPCIDevice *vdev; - uint32_t index; - uint64_t bdsm; -} VFIOIGDQuirk; - #define IGD_GMCH 0x50 /* Graphics Control Register */ #define IGD_BDSM 0x5c /* Base Data of Stolen Memory */ #define IGD_BDSM_GEN11 0xc0 /* Base Data of Stolen Memory of gen 11 and later */ @@ -300,129 +294,6 @@ static int vfio_pci_igd_lpc_init(VFIOPCIDevice *vdev, return ret; } -/* - * IGD Gen8 and newer support up to 8MB for the GTT and use a 64bit PTE - * entry, older IGDs use 2MB and 32bit. Each PTE maps a 4k page. Therefore - * we either have 2M/4k * 4 = 2k or 8M/4k * 8 = 16k as the maximum iobar index - * for programming the GTT. - * - * See linux:include/drm/i915_drm.h for shift and mask values. - */ -static int vfio_igd_gtt_max(VFIOPCIDevice *vdev) -{ - uint32_t gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, sizeof(gmch)); - int gen = igd_gen(vdev); - uint64_t ggms_size = igd_gtt_memory_size(gen, gmch); - - return (ggms_size / (4 * KiB)) * (gen < 8 ? 4 : 8); -} - -/* - * The IGD ROM will make use of stolen memory (GGMS) for support of VESA modes. - * Somehow the host stolen memory range is used for this, but how the ROM gets - * it is a mystery, perhaps it's hardcoded into the ROM. Thankfully though, it - * reprograms the GTT through the IOBAR where we can trap it and transpose the - * programming to the VM allocated buffer. That buffer gets reserved by the VM - * firmware via the fw_cfg entry added below. Here we're just monitoring the - * IOBAR address and data registers to detect a write sequence targeting the - * GTTADR. This code is developed by observed behavior and doesn't have a - * direct spec reference, unfortunately. - */ -static uint64_t vfio_igd_quirk_data_read(void *opaque, - hwaddr addr, unsigned size) -{ - VFIOIGDQuirk *igd = opaque; - VFIOPCIDevice *vdev = igd->vdev; - - igd->index = ~0; - - return vfio_region_read(&vdev->bars[4].region, addr + 4, size); -} - -static void vfio_igd_quirk_data_write(void *opaque, hwaddr addr, - uint64_t data, unsigned size) -{ - VFIOIGDQuirk *igd = opaque; - VFIOPCIDevice *vdev = igd->vdev; - uint64_t val = data; - int gen = igd_gen(vdev); - - /* - * Programming the GGMS starts at index 0x1 and uses every 4th index (ie. - * 0x1, 0x5, 0x9, 0xd,...). For pre-Gen8 each 4-byte write is a whole PTE - * entry, with 0th bit enable set. For Gen8 and up, PTEs are 64bit, so - * entries 0x5 & 0xd are the high dword, in our case zero. Each PTE points - * to a 4k page, which we translate to a page from the VM allocated region, - * pointed to by the BDSM register. If this is not set, we fail. - * - * We trap writes to the full configured GTT size, but we typically only - * see the vBIOS writing up to (nearly) the 1MB barrier. In fact it often - * seems to miss the last entry for an even 1MB GTT. Doing a gratuitous - * write of that last entry does work, but is hopefully unnecessary since - * we clear the previous GTT on initialization. - */ - if ((igd->index % 4 == 1) && igd->index < vfio_igd_gtt_max(vdev)) { - if (gen < 8 || (igd->index % 8 == 1)) { - uint64_t base; - - if (gen < 11) { - base = pci_get_long(vdev->pdev.config + IGD_BDSM); - } else { - base = pci_get_quad(vdev->pdev.config + IGD_BDSM_GEN11); - } - if (!base) { - hw_error("vfio-igd: Guest attempted to program IGD GTT before " - "BIOS reserved stolen memory. Unsupported BIOS?"); - } - - val = data - igd->bdsm + base; - } else { - val = 0; /* upper 32bits of pte, we only enable below 4G PTEs */ - } - - trace_vfio_pci_igd_bar4_write(vdev->vbasedev.name, - igd->index, data, val); - } - - vfio_region_write(&vdev->bars[4].region, addr + 4, val, size); - - igd->index = ~0; -} - -static const MemoryRegionOps vfio_igd_data_quirk = { - .read = vfio_igd_quirk_data_read, - .write = vfio_igd_quirk_data_write, - .endianness = DEVICE_LITTLE_ENDIAN, -}; - -static uint64_t vfio_igd_quirk_index_read(void *opaque, - hwaddr addr, unsigned size) -{ - VFIOIGDQuirk *igd = opaque; - VFIOPCIDevice *vdev = igd->vdev; - - igd->index = ~0; - - return vfio_region_read(&vdev->bars[4].region, addr, size); -} - -static void vfio_igd_quirk_index_write(void *opaque, hwaddr addr, - uint64_t data, unsigned size) -{ - VFIOIGDQuirk *igd = opaque; - VFIOPCIDevice *vdev = igd->vdev; - - igd->index = data; - - vfio_region_write(&vdev->bars[4].region, addr, data, size); -} - -static const MemoryRegionOps vfio_igd_index_quirk = { - .read = vfio_igd_quirk_index_read, - .write = vfio_igd_quirk_index_write, - .endianness = DEVICE_LITTLE_ENDIAN, -}; - #define IGD_GGC_MMIO_OFFSET 0x108040 #define IGD_BDSM_MMIO_OFFSET 0x1080C0 @@ -494,14 +365,11 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) g_autofree struct vfio_region_info *opregion = NULL; g_autofree struct vfio_region_info *host = NULL; g_autofree struct vfio_region_info *lpc = NULL; - VFIOQuirk *quirk; - VFIOIGDQuirk *igd; PCIDevice *lpc_bridge; - int i, ret, gen; + int ret, gen; uint64_t ggms_size, gms_size; uint64_t *bdsm_size; uint32_t gmch; - uint16_t cmd_orig, cmd; Error *err = NULL; /* @@ -634,32 +502,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) return; } - /* Setup our quirk to munge GTT addresses to the VM allocated buffer */ - quirk = vfio_quirk_alloc(2); - igd = quirk->data = g_malloc0(sizeof(*igd)); - igd->vdev = vdev; - igd->index = ~0; - if (gen < 11) { - igd->bdsm = vfio_pci_read_config(&vdev->pdev, IGD_BDSM, 4); - } else { - igd->bdsm = vfio_pci_read_config(&vdev->pdev, IGD_BDSM_GEN11, 4); - igd->bdsm |= - (uint64_t)vfio_pci_read_config(&vdev->pdev, IGD_BDSM_GEN11 + 4, 4) << 32; - } - igd->bdsm &= ~((1 * MiB) - 1); /* 1MB aligned */ - - memory_region_init_io(&quirk->mem[0], OBJECT(vdev), &vfio_igd_index_quirk, - igd, "vfio-igd-index-quirk", 4); - memory_region_add_subregion_overlap(vdev->bars[nr].region.mem, - 0, &quirk->mem[0], 1); - - memory_region_init_io(&quirk->mem[1], OBJECT(vdev), &vfio_igd_data_quirk, - igd, "vfio-igd-data-quirk", 4); - memory_region_add_subregion_overlap(vdev->bars[nr].region.mem, - 4, &quirk->mem[1], 1); - - QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, quirk, next); - /* * Allow user to override dsm size using x-igd-gms option, in multiples of * 32MiB. This option should only be used when the desired size cannot be @@ -717,37 +559,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) pci_set_quad(vdev->emulated_config_bits + IGD_BDSM_GEN11, ~0); } - /* - * This IOBAR gives us access to GTTADR, which allows us to write to - * the GTT itself. So let's go ahead and write zero to all the GTT - * entries to avoid spurious DMA faults. Be sure I/O access is enabled - * before talking to the device. - */ - if (pread(vdev->vbasedev.fd, &cmd_orig, sizeof(cmd_orig), - vdev->config_offset + PCI_COMMAND) != sizeof(cmd_orig)) { - error_report("IGD device %s - failed to read PCI command register", - vdev->vbasedev.name); - } - - cmd = cmd_orig | PCI_COMMAND_IO; - - if (pwrite(vdev->vbasedev.fd, &cmd, sizeof(cmd), - vdev->config_offset + PCI_COMMAND) != sizeof(cmd)) { - error_report("IGD device %s - failed to write PCI command register", - vdev->vbasedev.name); - } - - for (i = 1; i < vfio_igd_gtt_max(vdev); i += 4) { - vfio_region_write(&vdev->bars[4].region, 0, i, 4); - vfio_region_write(&vdev->bars[4].region, 4, 0, 4); - } - - if (pwrite(vdev->vbasedev.fd, &cmd_orig, sizeof(cmd_orig), - vdev->config_offset + PCI_COMMAND) != sizeof(cmd_orig)) { - error_report("IGD device %s - failed to restore PCI command register", - vdev->vbasedev.name); - } - trace_vfio_pci_igd_bdsm_enabled(vdev->vbasedev.name, (ggms_size + gms_size) / MiB); } From 5faec6a5e2202d3460b6b0550ec4ad897be3c76f Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:22 +0800 Subject: [PATCH 0955/1179] vfio/igd: Do not include GTT stolen size in etc/igd-bdsm-size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Though GTT Stolen Memory (GSM) is right below Data Stolen Memory (DSM) in host address space, direct access to GSM is prohibited, and it is not mapped to guest address space. Both host and guest accesses GSM indirectly through the second half of MMIO BAR0 (GTTMMADR). Guest firmware only need to reserve a memory region for DSM and program the BDSM register with the base address of that region, that's actually what both SeaBIOS[1] and IgdAssignmentDxe does now. [1] https://gitlab.com/qemu-project/seabios/-/blob/1.12-stable/src/fw/pciinit.c#L319-332 Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-3-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index ca3a32f4f2b8..dda4c7bb5df8 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -112,28 +112,8 @@ static int igd_gen(VFIOPCIDevice *vdev) #define IGD_GMCH_GEN6_GMS_SHIFT 3 /* SNB_GMCH in i915 */ #define IGD_GMCH_GEN6_GMS_MASK 0x1f -#define IGD_GMCH_GEN6_GGMS_SHIFT 8 -#define IGD_GMCH_GEN6_GGMS_MASK 0x3 #define IGD_GMCH_GEN8_GMS_SHIFT 8 /* BDW_GMCH in i915 */ #define IGD_GMCH_GEN8_GMS_MASK 0xff -#define IGD_GMCH_GEN8_GGMS_SHIFT 6 -#define IGD_GMCH_GEN8_GGMS_MASK 0x3 - -static uint64_t igd_gtt_memory_size(int gen, uint16_t gmch) -{ - uint64_t ggms; - - if (gen < 8) { - ggms = (gmch >> IGD_GMCH_GEN6_GGMS_SHIFT) & IGD_GMCH_GEN6_GGMS_MASK; - } else { - ggms = (gmch >> IGD_GMCH_GEN8_GGMS_SHIFT) & IGD_GMCH_GEN8_GGMS_MASK; - if (ggms != 0) { - ggms = 1ULL << ggms; - } - } - - return ggms * MiB; -} static uint64_t igd_stolen_memory_size(int gen, uint32_t gmch) { @@ -367,7 +347,7 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) g_autofree struct vfio_region_info *lpc = NULL; PCIDevice *lpc_bridge; int ret, gen; - uint64_t ggms_size, gms_size; + uint64_t gms_size; uint64_t *bdsm_size; uint32_t gmch; Error *err = NULL; @@ -527,7 +507,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) } } - ggms_size = igd_gtt_memory_size(gen, gmch); gms_size = igd_stolen_memory_size(gen, gmch); /* @@ -539,7 +518,7 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) * config offset 0x5C. */ bdsm_size = g_malloc(sizeof(*bdsm_size)); - *bdsm_size = cpu_to_le64(ggms_size + gms_size); + *bdsm_size = cpu_to_le64(gms_size); fw_cfg_add_file(fw_cfg_find(), "etc/igd-bdsm-size", bdsm_size, sizeof(*bdsm_size)); @@ -559,6 +538,5 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) pci_set_quad(vdev->emulated_config_bits + IGD_BDSM_GEN11, ~0); } - trace_vfio_pci_igd_bdsm_enabled(vdev->vbasedev.name, - (ggms_size + gms_size) / MiB); + trace_vfio_pci_igd_bdsm_enabled(vdev->vbasedev.name, (gms_size / MiB)); } From ae9d9ec4a643aae8704b4252b402488bde7b4be4 Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:23 +0800 Subject: [PATCH 0956/1179] vfio/igd: Consolidate OpRegion initialization into a single function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both x-igd-opregion option and legacy mode require identical steps to set up OpRegion for IGD devices. Consolidate these steps into a single vfio_pci_igd_setup_opregion function. The function call in pci.c is wrapped with ifdef temporarily to prevent build error for non-x86 archs, it will be removed after we decouple it from legacy mode. Additionally, move vfio_pci_igd_opregion_init to igd.c to prevent it from being compiled in non-x86 builds. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-4-tomitamoeko@gmail.com [ clg: Fixed spelling in vfio_pci_igd_setup_opregion() ] Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 101 +++++++++++++++++++++++++++++++++++-------- hw/vfio/pci-quirks.c | 50 --------------------- hw/vfio/pci.c | 22 ++-------- hw/vfio/pci.h | 4 +- 4 files changed, 88 insertions(+), 89 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index dda4c7bb5df8..113ad56ad420 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -106,6 +106,7 @@ static int igd_gen(VFIOPCIDevice *vdev) return -1; } +#define IGD_ASLS 0xfc /* ASL Storage Register */ #define IGD_GMCH 0x50 /* Graphics Control Register */ #define IGD_BDSM 0x5c /* Base Data of Stolen Memory */ #define IGD_BDSM_GEN11 0xc0 /* Base Data of Stolen Memory of gen 11 and later */ @@ -138,6 +139,82 @@ static uint64_t igd_stolen_memory_size(int gen, uint32_t gmch) return 0; } +/* + * The OpRegion includes the Video BIOS Table, which seems important for + * telling the driver what sort of outputs it has. Without this, the device + * may work in the guest, but we may not get output. This also requires BIOS + * support to reserve and populate a section of guest memory sufficient for + * the table and to write the base address of that memory to the ASLS register + * of the IGD device. + */ +static bool vfio_pci_igd_opregion_init(VFIOPCIDevice *vdev, + struct vfio_region_info *info, + Error **errp) +{ + int ret; + + vdev->igd_opregion = g_malloc0(info->size); + ret = pread(vdev->vbasedev.fd, vdev->igd_opregion, + info->size, info->offset); + if (ret != info->size) { + error_setg(errp, "failed to read IGD OpRegion"); + g_free(vdev->igd_opregion); + vdev->igd_opregion = NULL; + return false; + } + + /* + * Provide fw_cfg with a copy of the OpRegion which the VM firmware is to + * allocate 32bit reserved memory for, copy these contents into, and write + * the reserved memory base address to the device ASLS register at 0xFC. + * Alignment of this reserved region seems flexible, but using a 4k page + * alignment seems to work well. This interface assumes a single IGD + * device, which may be at VM address 00:02.0 in legacy mode or another + * address in UPT mode. + * + * NB, there may be future use cases discovered where the VM should have + * direct interaction with the host OpRegion, in which case the write to + * the ASLS register would trigger MemoryRegion setup to enable that. + */ + fw_cfg_add_file(fw_cfg_find(), "etc/igd-opregion", + vdev->igd_opregion, info->size); + + trace_vfio_pci_igd_opregion_enabled(vdev->vbasedev.name); + + pci_set_long(vdev->pdev.config + IGD_ASLS, 0); + pci_set_long(vdev->pdev.wmask + IGD_ASLS, ~0); + pci_set_long(vdev->emulated_config_bits + IGD_ASLS, ~0); + + return true; +} + +bool vfio_pci_igd_setup_opregion(VFIOPCIDevice *vdev, Error **errp) +{ + g_autofree struct vfio_region_info *opregion = NULL; + int ret; + + /* Hotplugging is not supported for opregion access */ + if (vdev->pdev.qdev.hotplugged) { + error_setg(errp, "IGD OpRegion is not supported on hotplugged device"); + return false; + } + + ret = vfio_get_dev_region_info(&vdev->vbasedev, + VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, + VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, &opregion); + if (ret) { + error_setg_errno(errp, -ret, + "Device does not supports IGD OpRegion feature"); + return false; + } + + if (!vfio_pci_igd_opregion_init(vdev, opregion, errp)) { + return false; + } + + return true; +} + /* * The rather short list of registers that we copy from the host devices. * The LPC/ISA bridge values are definitely needed to support the vBIOS, the @@ -342,7 +419,6 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr) void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) { g_autofree struct vfio_region_info *rom = NULL; - g_autofree struct vfio_region_info *opregion = NULL; g_autofree struct vfio_region_info *host = NULL; g_autofree struct vfio_region_info *lpc = NULL; PCIDevice *lpc_bridge; @@ -418,15 +494,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) * Check whether we have all the vfio device specific regions to * support legacy mode (added in Linux v4.6). If not, bail. */ - ret = vfio_get_dev_region_info(&vdev->vbasedev, - VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, - VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, &opregion); - if (ret) { - error_report("IGD device %s does not support OpRegion access," - "legacy mode disabled", vdev->vbasedev.name); - return; - } - ret = vfio_get_dev_region_info(&vdev->vbasedev, VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG, &host); @@ -459,6 +526,13 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) return; } + /* Setup OpRegion access */ + if (!vfio_pci_igd_setup_opregion(vdev, &err)) { + error_append_hint(&err, "IGD legacy mode disabled\n"); + error_report_err(err); + return; + } + /* Create our LPC/ISA bridge */ ret = vfio_pci_igd_lpc_init(vdev, lpc); if (ret) { @@ -475,13 +549,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) return; } - /* Setup OpRegion access */ - if (!vfio_pci_igd_opregion_init(vdev, opregion, &err)) { - error_append_hint(&err, "IGD legacy mode disabled\n"); - error_reportf_err(err, VFIO_MSG_PREFIX, vdev->vbasedev.name); - return; - } - /* * Allow user to override dsm size using x-igd-gms option, in multiples of * 32MiB. This option should only be used when the desired size cannot be diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c index c53591fe2ba5..37966e17f017 100644 --- a/hw/vfio/pci-quirks.c +++ b/hw/vfio/pci-quirks.c @@ -1114,56 +1114,6 @@ static void vfio_probe_rtl8168_bar2_quirk(VFIOPCIDevice *vdev, int nr) trace_vfio_quirk_rtl8168_probe(vdev->vbasedev.name); } -#define IGD_ASLS 0xfc /* ASL Storage Register */ - -/* - * The OpRegion includes the Video BIOS Table, which seems important for - * telling the driver what sort of outputs it has. Without this, the device - * may work in the guest, but we may not get output. This also requires BIOS - * support to reserve and populate a section of guest memory sufficient for - * the table and to write the base address of that memory to the ASLS register - * of the IGD device. - */ -bool vfio_pci_igd_opregion_init(VFIOPCIDevice *vdev, - struct vfio_region_info *info, Error **errp) -{ - int ret; - - vdev->igd_opregion = g_malloc0(info->size); - ret = pread(vdev->vbasedev.fd, vdev->igd_opregion, - info->size, info->offset); - if (ret != info->size) { - error_setg(errp, "failed to read IGD OpRegion"); - g_free(vdev->igd_opregion); - vdev->igd_opregion = NULL; - return false; - } - - /* - * Provide fw_cfg with a copy of the OpRegion which the VM firmware is to - * allocate 32bit reserved memory for, copy these contents into, and write - * the reserved memory base address to the device ASLS register at 0xFC. - * Alignment of this reserved region seems flexible, but using a 4k page - * alignment seems to work well. This interface assumes a single IGD - * device, which may be at VM address 00:02.0 in legacy mode or another - * address in UPT mode. - * - * NB, there may be future use cases discovered where the VM should have - * direct interaction with the host OpRegion, in which case the write to - * the ASLS register would trigger MemoryRegion setup to enable that. - */ - fw_cfg_add_file(fw_cfg_find(), "etc/igd-opregion", - vdev->igd_opregion, info->size); - - trace_vfio_pci_igd_opregion_enabled(vdev->vbasedev.name); - - pci_set_long(vdev->pdev.config + IGD_ASLS, 0); - pci_set_long(vdev->pdev.wmask + IGD_ASLS, ~0); - pci_set_long(vdev->emulated_config_bits + IGD_ASLS, ~0); - - return true; -} - /* * Common quirk probe entry points. */ diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index fdbc15885d44..419dc2c4c85f 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3136,30 +3136,14 @@ static void vfio_realize(PCIDevice *pdev, Error **errp) vfio_bar_quirk_setup(vdev, i); } +#ifdef CONFIG_VFIO_IGD if (!vdev->igd_opregion && vdev->features & VFIO_FEATURE_ENABLE_IGD_OPREGION) { - g_autofree struct vfio_region_info *opregion = NULL; - - if (vdev->pdev.qdev.hotplugged) { - error_setg(errp, - "cannot support IGD OpRegion feature on hotplugged " - "device"); - goto out_unset_idev; - } - - ret = vfio_get_dev_region_info(vbasedev, - VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, - VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, &opregion); - if (ret) { - error_setg_errno(errp, -ret, - "does not support requested IGD OpRegion feature"); - goto out_unset_idev; - } - - if (!vfio_pci_igd_opregion_init(vdev, opregion, errp)) { + if (!vfio_pci_igd_setup_opregion(vdev, errp)) { goto out_unset_idev; } } +#endif /* QEMU emulates all of MSI & MSIX */ if (pdev->cap_present & QEMU_PCI_CAP_MSIX) { diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index d638c781f6f1..f660b0d80fcf 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -227,9 +227,7 @@ int vfio_pci_get_pci_hot_reset_info(VFIOPCIDevice *vdev, bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp); -bool vfio_pci_igd_opregion_init(VFIOPCIDevice *vdev, - struct vfio_region_info *info, - Error **errp); +bool vfio_pci_igd_setup_opregion(VFIOPCIDevice *vdev, Error **errp); void vfio_display_reset(VFIOPCIDevice *vdev); bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp); From eabaa7b468eac63cb9acf47717f030c679532e6c Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:24 +0800 Subject: [PATCH 0957/1179] vfio/igd: Move LPC bridge initialization to a separate function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new option will soon be introduced to decouple the LPC bridge/Host bridge ID quirk from legacy mode. To prepare for this, move the LPC bridge initialization into a separate function. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-5-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 122 +++++++++++++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index 113ad56ad420..7feed7dfa920 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -351,6 +351,72 @@ static int vfio_pci_igd_lpc_init(VFIOPCIDevice *vdev, return ret; } +static bool vfio_pci_igd_setup_lpc_bridge(VFIOPCIDevice *vdev, Error **errp) +{ + g_autofree struct vfio_region_info *host = NULL; + g_autofree struct vfio_region_info *lpc = NULL; + PCIDevice *lpc_bridge; + int ret; + + /* + * Copying IDs or creating new devices are not supported on hotplug + */ + if (vdev->pdev.qdev.hotplugged) { + error_setg(errp, "IGD LPC is not supported on hotplugged device"); + return false; + } + + /* + * We need to create an LPC/ISA bridge at PCI bus address 00:1f.0 that we + * can stuff host values into, so if there's already one there and it's not + * one we can hack on, this quirk is no-go. Sorry Q35. + */ + lpc_bridge = pci_find_device(pci_device_root_bus(&vdev->pdev), + 0, PCI_DEVFN(0x1f, 0)); + if (lpc_bridge && !object_dynamic_cast(OBJECT(lpc_bridge), + "vfio-pci-igd-lpc-bridge")) { + error_setg(errp, + "Cannot create LPC bridge due to existing device at 1f.0"); + return false; + } + + /* + * Check whether we have all the vfio device specific regions to + * support LPC quirk (added in Linux v4.6). + */ + ret = vfio_get_dev_region_info(&vdev->vbasedev, + VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, + VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG, &lpc); + if (ret) { + error_setg(errp, "IGD LPC bridge access is not supported by kernel"); + return false; + } + + ret = vfio_get_dev_region_info(&vdev->vbasedev, + VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, + VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG, &host); + if (ret) { + error_setg(errp, "IGD host bridge access is not supported by kernel"); + return false; + } + + /* Create/modify LPC bridge */ + ret = vfio_pci_igd_lpc_init(vdev, lpc); + if (ret) { + error_setg(errp, "Failed to create/modify LPC bridge for IGD"); + return false; + } + + /* Stuff some host values into the VM PCI host bridge */ + ret = vfio_pci_igd_host_init(vdev, host); + if (ret) { + error_setg(errp, "Failed to modify host bridge for IGD"); + return false; + } + + return true; +} + #define IGD_GGC_MMIO_OFFSET 0x108040 #define IGD_BDSM_MMIO_OFFSET 0x1080C0 @@ -419,9 +485,6 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr) void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) { g_autofree struct vfio_region_info *rom = NULL; - g_autofree struct vfio_region_info *host = NULL; - g_autofree struct vfio_region_info *lpc = NULL; - PCIDevice *lpc_bridge; int ret, gen; uint64_t gms_size; uint64_t *bdsm_size; @@ -440,20 +503,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) return; } - /* - * We need to create an LPC/ISA bridge at PCI bus address 00:1f.0 that we - * can stuff host values into, so if there's already one there and it's not - * one we can hack on, legacy mode is no-go. Sorry Q35. - */ - lpc_bridge = pci_find_device(pci_device_root_bus(&vdev->pdev), - 0, PCI_DEVFN(0x1f, 0)); - if (lpc_bridge && !object_dynamic_cast(OBJECT(lpc_bridge), - "vfio-pci-igd-lpc-bridge")) { - error_report("IGD device %s cannot support legacy mode due to existing " - "devices at address 1f.0", vdev->vbasedev.name); - return; - } - /* * IGD is not a standard, they like to change their specs often. We * only attempt to support back to SandBridge and we hope that newer @@ -490,28 +539,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) return; } - /* - * Check whether we have all the vfio device specific regions to - * support legacy mode (added in Linux v4.6). If not, bail. - */ - ret = vfio_get_dev_region_info(&vdev->vbasedev, - VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, - VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG, &host); - if (ret) { - error_report("IGD device %s does not support host bridge access," - "legacy mode disabled", vdev->vbasedev.name); - return; - } - - ret = vfio_get_dev_region_info(&vdev->vbasedev, - VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL, - VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG, &lpc); - if (ret) { - error_report("IGD device %s does not support LPC bridge access," - "legacy mode disabled", vdev->vbasedev.name); - return; - } - gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, 4); /* @@ -533,19 +560,10 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) return; } - /* Create our LPC/ISA bridge */ - ret = vfio_pci_igd_lpc_init(vdev, lpc); - if (ret) { - error_report("IGD device %s failed to create LPC bridge, " - "legacy mode disabled", vdev->vbasedev.name); - return; - } - - /* Stuff some host values into the VM PCI host bridge */ - ret = vfio_pci_igd_host_init(vdev, host); - if (ret) { - error_report("IGD device %s failed to modify host bridge, " - "legacy mode disabled", vdev->vbasedev.name); + /* Setup LPC bridge / Host bridge PCI IDs */ + if (!vfio_pci_igd_setup_lpc_bridge(vdev, &err)) { + error_append_hint(&err, "IGD legacy mode disabled\n"); + error_report_err(err); return; } From b22ab580d284558e298228e40f11be057cd3576c Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:25 +0800 Subject: [PATCH 0958/1179] vfio/pci: Add placeholder for device-specific config space quirks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IGD devices require device-specific quirk to be applied to their PCI config space. Currently, it is put in the BAR4 quirk that does nothing to BAR4 itself. Add a placeholder for PCI config space quirks to hold that quirk later. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-6-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- hw/vfio/pci-quirks.c | 5 +++++ hw/vfio/pci.c | 4 ++++ hw/vfio/pci.h | 1 + 3 files changed, 10 insertions(+) diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c index 37966e17f017..78aef7d60eff 100644 --- a/hw/vfio/pci-quirks.c +++ b/hw/vfio/pci-quirks.c @@ -1117,6 +1117,11 @@ static void vfio_probe_rtl8168_bar2_quirk(VFIOPCIDevice *vdev, int nr) /* * Common quirk probe entry points. */ +bool vfio_config_quirk_setup(VFIOPCIDevice *vdev, Error **errp) +{ + return true; +} + void vfio_vga_quirk_setup(VFIOPCIDevice *vdev) { vfio_vga_probe_ati_3c3_quirk(vdev); diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 419dc2c4c85f..ff1e720dbadc 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3128,6 +3128,10 @@ static void vfio_realize(PCIDevice *pdev, Error **errp) goto out_unset_idev; } + if (!vfio_config_quirk_setup(vdev, errp)) { + goto out_unset_idev; + } + if (vdev->vga) { vfio_vga_quirk_setup(vdev); } diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index f660b0d80fcf..d125e738655a 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -204,6 +204,7 @@ uint64_t vfio_vga_read(void *opaque, hwaddr addr, unsigned size); void vfio_vga_write(void *opaque, hwaddr addr, uint64_t data, unsigned size); bool vfio_opt_rom_in_denylist(VFIOPCIDevice *vdev); +bool vfio_config_quirk_setup(VFIOPCIDevice *vdev, Error **errp); void vfio_vga_quirk_setup(VFIOPCIDevice *vdev); void vfio_vga_quirk_exit(VFIOPCIDevice *vdev); void vfio_vga_quirk_finalize(VFIOPCIDevice *vdev); From 9267f96ad6f26100e17a34f169a79d1ad9c1c7aa Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:26 +0800 Subject: [PATCH 0959/1179] vfio/igd: Refactor vfio_probe_igd_bar4_quirk into pci config quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The actual IO BAR4 write quirk in vfio_probe_igd_bar4_quirk was removed in previous change, leaving the function not matching its name, so move it into the newly introduced vfio_config_quirk_setup. There is no functional change in this commit. For now, to align with current legacy mode behavior, it returns and proceeds on error. Later it will fail on error after decoupling the quirks from legacy mode. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-7-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 21 ++++++++++++--------- hw/vfio/pci-quirks.c | 6 +++++- hw/vfio/pci.h | 2 +- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index 7feed7dfa920..65a3dbb5afd0 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -482,7 +482,8 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr) QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, bdsm_quirk, next); } -void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) +bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, + Error **errp G_GNUC_UNUSED) { g_autofree struct vfio_region_info *rom = NULL; int ret, gen; @@ -497,10 +498,10 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) * PCI bus address. */ if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) || - !vfio_is_vga(vdev) || nr != 4 || + !vfio_is_vga(vdev) || &vdev->pdev != pci_find_device(pci_device_root_bus(&vdev->pdev), 0, PCI_DEVFN(0x2, 0))) { - return; + return true; } /* @@ -512,7 +513,7 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) if (gen == -1) { error_report("IGD device %s is unsupported in legacy mode, " "try SandyBridge or newer", vdev->vbasedev.name); - return; + return true; } /* @@ -525,7 +526,7 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) if ((ret || !rom->size) && !vdev->pdev.romfile) { error_report("IGD device %s has no ROM, legacy mode disabled", vdev->vbasedev.name); - return; + return true; } /* @@ -536,7 +537,7 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) error_report("IGD device %s hotplugged, ROM disabled, " "legacy mode disabled", vdev->vbasedev.name); vdev->rom_read_failed = true; - return; + return true; } gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, 4); @@ -550,21 +551,21 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) error_reportf_err(err, VFIO_MSG_PREFIX, vdev->vbasedev.name); error_report("IGD device %s failed to enable VGA access, " "legacy mode disabled", vdev->vbasedev.name); - return; + return true; } /* Setup OpRegion access */ if (!vfio_pci_igd_setup_opregion(vdev, &err)) { error_append_hint(&err, "IGD legacy mode disabled\n"); error_report_err(err); - return; + return true; } /* Setup LPC bridge / Host bridge PCI IDs */ if (!vfio_pci_igd_setup_lpc_bridge(vdev, &err)) { error_append_hint(&err, "IGD legacy mode disabled\n"); error_report_err(err); - return; + return true; } /* @@ -624,4 +625,6 @@ void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr) } trace_vfio_pci_igd_bdsm_enabled(vdev->vbasedev.name, (gms_size / MiB)); + + return true; } diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c index 78aef7d60eff..f998761abca2 100644 --- a/hw/vfio/pci-quirks.c +++ b/hw/vfio/pci-quirks.c @@ -1119,6 +1119,11 @@ static void vfio_probe_rtl8168_bar2_quirk(VFIOPCIDevice *vdev, int nr) */ bool vfio_config_quirk_setup(VFIOPCIDevice *vdev, Error **errp) { +#ifdef CONFIG_VFIO_IGD + if (!vfio_probe_igd_config_quirk(vdev, errp)) { + return false; + } +#endif return true; } @@ -1170,7 +1175,6 @@ void vfio_bar_quirk_setup(VFIOPCIDevice *vdev, int nr) vfio_probe_rtl8168_bar2_quirk(vdev, nr); #ifdef CONFIG_VFIO_IGD vfio_probe_igd_bar0_quirk(vdev, nr); - vfio_probe_igd_bar4_quirk(vdev, nr); #endif } diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index d125e738655a..fc7ead7727b3 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -216,7 +216,7 @@ bool vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp); void vfio_quirk_reset(VFIOPCIDevice *vdev); VFIOQuirk *vfio_quirk_alloc(int nr_mem); void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr); -void vfio_probe_igd_bar4_quirk(VFIOPCIDevice *vdev, int nr); +bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp); extern const PropertyInfo qdev_prop_nv_gpudirect_clique; From 2fedccf03c889562855bfbe0628ac577938c50a2 Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:27 +0800 Subject: [PATCH 0960/1179] vfio/igd: Decouple common quirks from legacy mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far, IGD-specific quirks all require enabling legacy mode, which is toggled by assigning IGD to 00:02.0. However, some quirks, like the BDSM and GGC register quirks, should be applied to all supported IGD devices. A new config option, x-igd-legacy-mode=[on|off|auto], is introduced to control the legacy mode only quirks. The default value is "auto", which keeps current behavior that enables legacy mode implicitly and continues on error when all following conditions are met. * Machine type is i440fx * IGD device is at guest BDF 00:02.0 If any one of the conditions above is not met, the default behavior is equivalent to "off", QEMU will fail immediately if any error occurs. Users can also use "on" to force enabling legacy mode. It checks if all the conditions above are met and set up legacy mode. QEMU will also fail immediately on error in this case. Additionally, the hotplug check in legacy mode is removed as hotplugging IGD device is never supported, and it will be checked when enabling the OpRegion quirk. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-8-tomitamoeko@gmail.com [ clg: - Changed warn_report() by info_report() in vfio_probe_igd_config_quirk() as suggested by Alex W. - Fixed spelling in vfio_probe_igd_config_quirk () ] Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 127 +++++++++++++++++++++++++++++--------------------- hw/vfio/pci.c | 2 + hw/vfio/pci.h | 1 + 3 files changed, 77 insertions(+), 53 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index 65a3dbb5afd0..ee36875310b4 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -15,6 +15,7 @@ #include "qemu/error-report.h" #include "qapi/error.h" #include "qapi/qmp/qerror.h" +#include "hw/boards.h" #include "hw/hw.h" #include "hw/nvram/fw_cfg.h" #include "pci.h" @@ -432,9 +433,7 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr) * bus address. */ if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) || - !vfio_is_vga(vdev) || nr != 0 || - &vdev->pdev != pci_find_device(pci_device_root_bus(&vdev->pdev), - 0, PCI_DEVFN(0x2, 0))) { + !vfio_is_vga(vdev) || nr != 0) { return; } @@ -482,14 +481,13 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr) QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, bdsm_quirk, next); } -bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, - Error **errp G_GNUC_UNUSED) +bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) { - g_autofree struct vfio_region_info *rom = NULL; int ret, gen; uint64_t gms_size; uint64_t *bdsm_size; uint32_t gmch; + bool legacy_mode_enabled = false; Error *err = NULL; /* @@ -498,9 +496,7 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, * PCI bus address. */ if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) || - !vfio_is_vga(vdev) || - &vdev->pdev != pci_find_device(pci_device_root_bus(&vdev->pdev), - 0, PCI_DEVFN(0x2, 0))) { + !vfio_is_vga(vdev)) { return true; } @@ -516,56 +512,67 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, return true; } - /* - * Most of what we're doing here is to enable the ROM to run, so if - * there's no ROM, there's no point in setting up this quirk. - * NB. We only seem to get BIOS ROMs, so a UEFI VM would need CSM support. - */ - ret = vfio_get_region_info(&vdev->vbasedev, - VFIO_PCI_ROM_REGION_INDEX, &rom); - if ((ret || !rom->size) && !vdev->pdev.romfile) { - error_report("IGD device %s has no ROM, legacy mode disabled", - vdev->vbasedev.name); - return true; - } - - /* - * Ignore the hotplug corner case, mark the ROM failed, we can't - * create the devices we need for legacy mode in the hotplug scenario. - */ - if (vdev->pdev.qdev.hotplugged) { - error_report("IGD device %s hotplugged, ROM disabled, " - "legacy mode disabled", vdev->vbasedev.name); - vdev->rom_read_failed = true; - return true; - } - gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, 4); /* - * If IGD VGA Disable is clear (expected) and VGA is not already enabled, - * try to enable it. Probably shouldn't be using legacy mode without VGA, - * but also no point in us enabling VGA if disabled in hardware. + * For backward compatibility, enable legacy mode when + * - Machine type is i440fx (pc_piix) + * - IGD device is at guest BDF 00:02.0 + * - Not manually disabled by x-igd-legacy-mode=off */ - if (!(gmch & 0x2) && !vdev->vga && !vfio_populate_vga(vdev, &err)) { - error_reportf_err(err, VFIO_MSG_PREFIX, vdev->vbasedev.name); - error_report("IGD device %s failed to enable VGA access, " - "legacy mode disabled", vdev->vbasedev.name); - return true; - } + if ((vdev->igd_legacy_mode != ON_OFF_AUTO_OFF) && + !strcmp(MACHINE_GET_CLASS(qdev_get_machine())->family, "pc_piix") && + (&vdev->pdev == pci_find_device(pci_device_root_bus(&vdev->pdev), + 0, PCI_DEVFN(0x2, 0)))) { + /* + * IGD legacy mode requires: + * - VBIOS in ROM BAR or file + * - VGA IO/MMIO ranges are claimed by IGD + * - OpRegion + * - Same LPC bridge and Host bridge VID/DID/SVID/SSID as host + */ + g_autofree struct vfio_region_info *rom = NULL; + + legacy_mode_enabled = true; + info_report("IGD legacy mode enabled, " + "use x-igd-legacy-mode=off to disable it if unwanted."); + + /* + * Most of what we're doing here is to enable the ROM to run, so if + * there's no ROM, there's no point in setting up this quirk. + * NB. We only seem to get BIOS ROMs, so UEFI VM would need CSM support. + */ + ret = vfio_get_region_info(&vdev->vbasedev, + VFIO_PCI_ROM_REGION_INDEX, &rom); + if ((ret || !rom->size) && !vdev->pdev.romfile) { + error_setg(&err, "Device has no ROM"); + goto error; + } - /* Setup OpRegion access */ - if (!vfio_pci_igd_setup_opregion(vdev, &err)) { - error_append_hint(&err, "IGD legacy mode disabled\n"); - error_report_err(err); - return true; - } + /* + * If IGD VGA Disable is clear (expected) and VGA is not already + * enabled, try to enable it. Probably shouldn't be using legacy mode + * without VGA, but also no point in us enabling VGA if disabled in + * hardware. + */ + if (!(gmch & 0x2) && !vdev->vga && !vfio_populate_vga(vdev, &err)) { + error_setg(&err, "Unable to enable VGA access"); + goto error; + } - /* Setup LPC bridge / Host bridge PCI IDs */ - if (!vfio_pci_igd_setup_lpc_bridge(vdev, &err)) { - error_append_hint(&err, "IGD legacy mode disabled\n"); - error_report_err(err); - return true; + /* Setup OpRegion access */ + if (!vfio_pci_igd_setup_opregion(vdev, &err)) { + goto error; + } + + /* Setup LPC bridge / Host bridge PCI IDs */ + if (!vfio_pci_igd_setup_lpc_bridge(vdev, &err)) { + goto error; + } + } else if (vdev->igd_legacy_mode == ON_OFF_AUTO_ON) { + error_setg(&err, + "Machine is not i440fx or assigned BDF is not 00:02.0"); + goto error; } /* @@ -627,4 +634,18 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, trace_vfio_pci_igd_bdsm_enabled(vdev->vbasedev.name, (gms_size / MiB)); return true; + +error: + /* + * When legacy mode is implicity enabled, continue on error, + * to keep compatibility + */ + if (legacy_mode_enabled && (vdev->igd_legacy_mode == ON_OFF_AUTO_AUTO)) { + error_report_err(err); + error_report("IGD legacy mode disabled"); + return true; + } + + error_propagate(errp, err); + return false; } diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index ff1e720dbadc..444a33d94b7e 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3369,6 +3369,8 @@ static const Property vfio_pci_dev_properties[] = { VFIO_FEATURE_ENABLE_REQ_BIT, true), DEFINE_PROP_BIT("x-igd-opregion", VFIOPCIDevice, features, VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT, false), + DEFINE_PROP_ON_OFF_AUTO("x-igd-legacy-mode", VFIOPCIDevice, + igd_legacy_mode, ON_OFF_AUTO_AUTO), DEFINE_PROP_ON_OFF_AUTO("enable-migration", VFIOPCIDevice, vbasedev.enable_migration, ON_OFF_AUTO_AUTO), DEFINE_PROP("x-migration-multifd-transfer", VFIOPCIDevice, diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index fc7ead7727b3..3e66b19d8f24 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -158,6 +158,7 @@ struct VFIOPCIDevice { uint32_t display_xres; uint32_t display_yres; int32_t bootindex; + OnOffAuto igd_legacy_mode; uint32_t igd_gms; OffAutoPCIBAR msix_relo; uint8_t nv_gpudirect_clique; From 434ac62ef2443f25956dafcfcbf29663fd0cd3e1 Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:28 +0800 Subject: [PATCH 0961/1179] vfio/igd: Handle x-igd-opregion option in config quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both enable OpRegion option (x-igd-opregion) and legacy mode require setting up OpRegion copy for IGD devices. As the config quirk no longer depends on legacy mode, we can now handle x-igd-opregion option there instead of in vfio_realize. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-9-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 14 +++++++++----- hw/vfio/pci.c | 9 --------- hw/vfio/pci.h | 2 -- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index ee36875310b4..12e07517b4a0 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -189,7 +189,7 @@ static bool vfio_pci_igd_opregion_init(VFIOPCIDevice *vdev, return true; } -bool vfio_pci_igd_setup_opregion(VFIOPCIDevice *vdev, Error **errp) +static bool vfio_pci_igd_setup_opregion(VFIOPCIDevice *vdev, Error **errp) { g_autofree struct vfio_region_info *opregion = NULL; int ret; @@ -560,10 +560,8 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) goto error; } - /* Setup OpRegion access */ - if (!vfio_pci_igd_setup_opregion(vdev, &err)) { - goto error; - } + /* Enable OpRegion quirk */ + vdev->features |= VFIO_FEATURE_ENABLE_IGD_OPREGION; /* Setup LPC bridge / Host bridge PCI IDs */ if (!vfio_pci_igd_setup_lpc_bridge(vdev, &err)) { @@ -575,6 +573,12 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) goto error; } + /* Setup OpRegion access */ + if ((vdev->features & VFIO_FEATURE_ENABLE_IGD_OPREGION) && + !vfio_pci_igd_setup_opregion(vdev, errp)) { + goto error; + } + /* * Allow user to override dsm size using x-igd-gms option, in multiples of * 32MiB. This option should only be used when the desired size cannot be diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 444a33d94b7e..e2897bdcd58d 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3140,15 +3140,6 @@ static void vfio_realize(PCIDevice *pdev, Error **errp) vfio_bar_quirk_setup(vdev, i); } -#ifdef CONFIG_VFIO_IGD - if (!vdev->igd_opregion && - vdev->features & VFIO_FEATURE_ENABLE_IGD_OPREGION) { - if (!vfio_pci_igd_setup_opregion(vdev, errp)) { - goto out_unset_idev; - } - } -#endif - /* QEMU emulates all of MSI & MSIX */ if (pdev->cap_present & QEMU_PCI_CAP_MSIX) { memset(vdev->emulated_config_bits + pdev->msix_cap, 0xff, diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index 3e66b19d8f24..816bdbf844dc 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -229,8 +229,6 @@ int vfio_pci_get_pci_hot_reset_info(VFIOPCIDevice *vdev, bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp); -bool vfio_pci_igd_setup_opregion(VFIOPCIDevice *vdev, Error **errp); - void vfio_display_reset(VFIOPCIDevice *vdev); bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp); void vfio_display_finalize(VFIOPCIDevice *vdev); From 674f61117d3652c969bd9d04201615bb69614fa2 Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:29 +0800 Subject: [PATCH 0962/1179] vfio/igd: Introduce x-igd-lpc option for LPC bridge ID quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LPC bridge/Host bridge IDs quirk is also not dependent on legacy mode. Recent Windows driver no longer depends on these IDs, as well as Linux i915 driver, while UEFI GOP seems still needs them. Make it an option to allow users enabling and disabling it as needed. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-10-tomitamoeko@gmail.com [ clg: - Fixed spelling in vfio_probe_igd_config_quirk() ] Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 14 ++++++++------ hw/vfio/pci.c | 2 ++ hw/vfio/pci.h | 3 +++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index 12e07517b4a0..8a88dbab13ed 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -560,13 +560,9 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) goto error; } - /* Enable OpRegion quirk */ + /* Enable OpRegion and LPC bridge quirk */ vdev->features |= VFIO_FEATURE_ENABLE_IGD_OPREGION; - - /* Setup LPC bridge / Host bridge PCI IDs */ - if (!vfio_pci_igd_setup_lpc_bridge(vdev, &err)) { - goto error; - } + vdev->features |= VFIO_FEATURE_ENABLE_IGD_LPC; } else if (vdev->igd_legacy_mode == ON_OFF_AUTO_ON) { error_setg(&err, "Machine is not i440fx or assigned BDF is not 00:02.0"); @@ -579,6 +575,12 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) goto error; } + /* Setup LPC bridge / Host bridge PCI IDs */ + if ((vdev->features & VFIO_FEATURE_ENABLE_IGD_LPC) && + !vfio_pci_igd_setup_lpc_bridge(vdev, errp)) { + goto error; + } + /* * Allow user to override dsm size using x-igd-gms option, in multiples of * 32MiB. This option should only be used when the desired size cannot be diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index e2897bdcd58d..3cb7806f2f21 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3360,6 +3360,8 @@ static const Property vfio_pci_dev_properties[] = { VFIO_FEATURE_ENABLE_REQ_BIT, true), DEFINE_PROP_BIT("x-igd-opregion", VFIOPCIDevice, features, VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT, false), + DEFINE_PROP_BIT("x-igd-lpc", VFIOPCIDevice, features, + VFIO_FEATURE_ENABLE_IGD_LPC_BIT, false), DEFINE_PROP_ON_OFF_AUTO("x-igd-legacy-mode", VFIOPCIDevice, igd_legacy_mode, ON_OFF_AUTO_AUTO), DEFINE_PROP_ON_OFF_AUTO("enable-migration", VFIOPCIDevice, diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index 816bdbf844dc..d94ecaba689c 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -154,6 +154,9 @@ struct VFIOPCIDevice { #define VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT 2 #define VFIO_FEATURE_ENABLE_IGD_OPREGION \ (1 << VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT) +#define VFIO_FEATURE_ENABLE_IGD_LPC_BIT 3 +#define VFIO_FEATURE_ENABLE_IGD_LPC \ + (1 << VFIO_FEATURE_ENABLE_IGD_LPC_BIT) OnOffAuto display; uint32_t display_xres; uint32_t display_yres; From e46b7af508c27a4e830818ac05fa3e2e4c33b416 Mon Sep 17 00:00:00 2001 From: Tomita Moeko Date: Fri, 7 Mar 2025 02:01:30 +0800 Subject: [PATCH 0963/1179] vfio/igd: Fix broken KVMGT OpRegion support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The KVMGT/GVT-g vGPU also exposes OpRegion. But unlike IGD passthrough, it only needs the OpRegion quirk. A previous change moved x-igd-opregion handling to config quirk breaks KVMGT functionality as it brings extra checks and applied other quirks. Here we check if the device is mdev (KVMGT) or not (passthrough), and then applies corresponding quirks. As before, users must manually specify x-igd-opregion=on to enable it on KVMGT devices. In the future, we may check the VID/DID and enable OpRegion automatically. Signed-off-by: Tomita Moeko Reviewed-by: Alex Williamson Tested-by: Alex Williamson Reviewed-by: Corvin Köhne Link: https://lore.kernel.org/qemu-devel/20250306180131.32970-11-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater --- hw/vfio/igd.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c index 8a88dbab13ed..265fffc2aa52 100644 --- a/hw/vfio/igd.c +++ b/hw/vfio/igd.c @@ -481,7 +481,7 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr) QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, bdsm_quirk, next); } -bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) +static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) { int ret, gen; uint64_t gms_size; @@ -655,3 +655,28 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) error_propagate(errp, err); return false; } + +/* + * KVMGT/GVT-g vGPU exposes an emulated OpRegion. So far, users have to specify + * x-igd-opregion=on to enable the access. + * TODO: Check VID/DID and enable opregion access automatically + */ +static bool vfio_pci_kvmgt_config_quirk(VFIOPCIDevice *vdev, Error **errp) +{ + if ((vdev->features & VFIO_FEATURE_ENABLE_IGD_OPREGION) && + !vfio_pci_igd_setup_opregion(vdev, errp)) { + return false; + } + + return true; +} + +bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp) +{ + /* KVMGT/GVT-g vGPU is exposed as mdev */ + if (vdev->vbasedev.mdev) { + return vfio_pci_kvmgt_config_quirk(vdev, errp); + } + + return vfio_pci_igd_config_quirk(vdev, errp); +} From cbb2e105268d0c5b22ba6fc936af1ae6bae3f959 Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Mon, 10 Mar 2025 13:53:10 +0100 Subject: [PATCH 0964/1179] vfio/migration: Use BE byte order for device state wire packets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire data commonly use BE byte order (including in the existing migration protocol), use it also for for VFIO device state packets. This will allow VFIO multifd device state transfer between hosts with different endianness. Although currently there is no such use case, it's good to have it now for completeness. Reviewed-by: Avihai Horon Signed-off-by: Maciej S. Szmigiero Link: https://lore.kernel.org/qemu-devel/dcfc04cc1a50655650dbac8398e2742ada84ee39.1741611079.git.maciej.szmigiero@oracle.com Signed-off-by: Cédric Le Goater --- hw/vfio/migration-multifd.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index 233724710b37..378f6f3bf01f 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -13,6 +13,7 @@ #include "hw/vfio/vfio-common.h" #include "migration/misc.h" #include "qapi/error.h" +#include "qemu/bswap.h" #include "qemu/error-report.h" #include "qemu/lockable.h" #include "qemu/main-loop.h" @@ -155,12 +156,16 @@ bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, return false; } + packet->version = be32_to_cpu(packet->version); if (packet->version != VFIO_DEVICE_STATE_PACKET_VER_CURRENT) { error_setg(errp, "%s: packet has unknown version %" PRIu32, vbasedev->name, packet->version); return false; } + packet->idx = be32_to_cpu(packet->idx); + packet->flags = be32_to_cpu(packet->flags); + if (packet->idx == UINT32_MAX) { error_setg(errp, "%s: packet index is invalid", vbasedev->name); return false; @@ -558,9 +563,9 @@ vfio_save_complete_precopy_thread_config_state(VFIODevice *vbasedev, packet_len = sizeof(*packet) + bioc->usage; packet = g_malloc0(packet_len); - packet->version = VFIO_DEVICE_STATE_PACKET_VER_CURRENT; - packet->idx = idx; - packet->flags = VFIO_DEVICE_STATE_CONFIG_STATE; + packet->version = cpu_to_be32(VFIO_DEVICE_STATE_PACKET_VER_CURRENT); + packet->idx = cpu_to_be32(idx); + packet->flags = cpu_to_be32(VFIO_DEVICE_STATE_CONFIG_STATE); memcpy(&packet->data, bioc->data, bioc->usage); if (!multifd_queue_device_state(idstr, instance_id, @@ -610,7 +615,7 @@ vfio_multifd_save_complete_precopy_thread(SaveLiveCompletePrecopyThreadData *d, } packet = g_malloc0(sizeof(*packet) + migration->data_buffer_size); - packet->version = VFIO_DEVICE_STATE_PACKET_VER_CURRENT; + packet->version = cpu_to_be32(VFIO_DEVICE_STATE_PACKET_VER_CURRENT); for (idx = 0; ; idx++) { ssize_t data_size; @@ -631,7 +636,7 @@ vfio_multifd_save_complete_precopy_thread(SaveLiveCompletePrecopyThreadData *d, break; } - packet->idx = idx; + packet->idx = cpu_to_be32(idx); packet_size = sizeof(*packet) + data_size; if (!multifd_queue_device_state(d->idstr, d->instance_id, From c6cd30feadb6907cf69d35babec74274e690164f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:36 +0100 Subject: [PATCH 0965/1179] system: Declare qemu_[min/max]rampagesize() in 'system/hostmem.h' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both qemu_minrampagesize() and qemu_maxrampagesize() are related to host memory backends, having the following call stack: qemu_minrampagesize() -> find_min_backend_pagesize() -> object_dynamic_cast(obj, TYPE_MEMORY_BACKEND) qemu_maxrampagesize() -> find_max_backend_pagesize() -> object_dynamic_cast(obj, TYPE_MEMORY_BACKEND) Having TYPE_MEMORY_BACKEND defined in "system/hostmem.h": include/system/hostmem.h:23:#define TYPE_MEMORY_BACKEND "memory-backend" Move their prototype declaration to "system/hostmem.h". Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Eric Auger Message-Id: <20250308230917.18907-7-philmd@linaro.org> Acked-by: David Hildenbrand Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-2-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/ppc/spapr_caps.c | 1 + hw/s390x/s390-virtio-ccw.c | 1 + hw/vfio/spapr.c | 1 + include/exec/ram_addr.h | 3 --- include/system/hostmem.h | 3 +++ 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hw/ppc/spapr_caps.c b/hw/ppc/spapr_caps.c index 904bff87ce12..9e53d0c1fd1c 100644 --- a/hw/ppc/spapr_caps.c +++ b/hw/ppc/spapr_caps.c @@ -34,6 +34,7 @@ #include "kvm_ppc.h" #include "migration/vmstate.h" #include "system/tcg.h" +#include "system/hostmem.h" #include "hw/ppc/spapr.h" diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index a9b3db19f63d..75b32182eb01 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c @@ -41,6 +41,7 @@ #include "hw/s390x/tod.h" #include "system/system.h" #include "system/cpus.h" +#include "system/hostmem.h" #include "target/s390x/kvm/pv.h" #include "migration/blocker.h" #include "qapi/visitor.h" diff --git a/hw/vfio/spapr.c b/hw/vfio/spapr.c index ad4c499eafea..237f96dd3fa2 100644 --- a/hw/vfio/spapr.c +++ b/hw/vfio/spapr.c @@ -15,6 +15,7 @@ #include #endif #include "system/kvm.h" +#include "system/hostmem.h" #include "exec/address-spaces.h" #include "hw/vfio/vfio-common.h" diff --git a/include/exec/ram_addr.h b/include/exec/ram_addr.h index 3d8df4edf15c..e4c28fbec9b4 100644 --- a/include/exec/ram_addr.h +++ b/include/exec/ram_addr.h @@ -102,9 +102,6 @@ static inline unsigned long int ramblock_recv_bitmap_offset(void *host_addr, bool ramblock_is_pmem(RAMBlock *rb); -long qemu_minrampagesize(void); -long qemu_maxrampagesize(void); - /** * qemu_ram_alloc_from_file, * qemu_ram_alloc_from_fd: Allocate a ram block from the specified backing diff --git a/include/system/hostmem.h b/include/system/hostmem.h index 5c21ca55c017..62642e602ca9 100644 --- a/include/system/hostmem.h +++ b/include/system/hostmem.h @@ -93,4 +93,7 @@ bool host_memory_backend_is_mapped(HostMemoryBackend *backend); size_t host_memory_backend_pagesize(HostMemoryBackend *memdev); char *host_memory_backend_get_name(HostMemoryBackend *backend); +long qemu_minrampagesize(void); +long qemu_maxrampagesize(void); + #endif From a19ce97ed16c2892c6340fb46e693aa934ab7ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:37 +0100 Subject: [PATCH 0966/1179] hw/vfio/spapr: Do not include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit is already included by "system/kvm.h" in the next line. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Richard Henderson Reviewed-by: Cédric Le Goater Reviewed-by: Eric Auger Message-Id: <20250307180337.14811-3-philmd@linaro.org> Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-3-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/vfio/spapr.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/hw/vfio/spapr.c b/hw/vfio/spapr.c index 237f96dd3fa2..1a5d1611f2cd 100644 --- a/hw/vfio/spapr.c +++ b/hw/vfio/spapr.c @@ -11,9 +11,6 @@ #include "qemu/osdep.h" #include #include -#ifdef CONFIG_KVM -#include -#endif #include "system/kvm.h" #include "system/hostmem.h" #include "exec/address-spaces.h" From 514c29678715c6b27e09fef8e3594dc1dba6820b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:38 +0100 Subject: [PATCH 0967/1179] hw/vfio/common: Include missing 'system/tcg.h' header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always include necessary headers explicitly, to avoid when refactoring unrelated ones: hw/vfio/common.c:1176:45: error: implicit declaration of function ‘tcg_enabled’; 1176 | tcg_enabled() ? DIRTY_CLIENTS_ALL : | ^~~~~~~~~~~ Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Richard Henderson Reviewed-by: Cédric Le Goater Reviewed-by: Eric Auger Message-Id: <20250307180337.14811-2-philmd@linaro.org> Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-4-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/vfio/common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/vfio/common.c b/hw/vfio/common.c index 7a4010ef4ee6..b1596b6bf647 100644 --- a/hw/vfio/common.c +++ b/hw/vfio/common.c @@ -42,6 +42,7 @@ #include "migration/misc.h" #include "migration/blocker.h" #include "migration/qemu-file.h" +#include "system/tcg.h" #include "system/tpm.h" VFIODeviceList vfio_device_list = From 80ce7bb5cf0157fa835ecaabeaed90fc282830c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:39 +0100 Subject: [PATCH 0968/1179] hw/vfio/common: Get target page size using runtime helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prefer runtime helpers to get target page size. Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20250305153929.43687-3-philmd@linaro.org> Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-5-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/vfio/common.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hw/vfio/common.c b/hw/vfio/common.c index b1596b6bf647..1a0d9290f88c 100644 --- a/hw/vfio/common.c +++ b/hw/vfio/common.c @@ -30,6 +30,7 @@ #include "exec/address-spaces.h" #include "exec/memory.h" #include "exec/ram_addr.h" +#include "exec/target_page.h" #include "hw/hw.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" @@ -393,13 +394,14 @@ static void vfio_register_ram_discard_listener(VFIOContainerBase *bcontainer, MemoryRegionSection *section) { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr); + int target_page_size = qemu_target_page_size(); VFIORamDiscardListener *vrdl; /* Ignore some corner cases not relevant in practice. */ - g_assert(QEMU_IS_ALIGNED(section->offset_within_region, TARGET_PAGE_SIZE)); + g_assert(QEMU_IS_ALIGNED(section->offset_within_region, target_page_size)); g_assert(QEMU_IS_ALIGNED(section->offset_within_address_space, - TARGET_PAGE_SIZE)); - g_assert(QEMU_IS_ALIGNED(int128_get64(section->size), TARGET_PAGE_SIZE)); + target_page_size)); + g_assert(QEMU_IS_ALIGNED(int128_get64(section->size), target_page_size)); vrdl = g_new0(VFIORamDiscardListener, 1); vrdl->bcontainer = bcontainer; From 5731baee6c3ca1fbd23ce9dfe1a825aafd473e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:40 +0100 Subject: [PATCH 0969/1179] hw/vfio: Compile some common objects once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some files don't rely on any target-specific knowledge and can be compiled once: - helpers.c - container-base.c - migration.c (removing unnecessary "exec/ram_addr.h") - migration-multifd.c - cpr.c Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Richard Henderson Reviewed-by: Cédric Le Goater Reviewed-by: Eric Auger Message-Id: <20250308230917.18907-4-philmd@linaro.org> Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-6-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/vfio/meson.build | 13 ++++++++----- hw/vfio/migration.c | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build index 260d65febd6b..8e376cfcbf89 100644 --- a/hw/vfio/meson.build +++ b/hw/vfio/meson.build @@ -1,12 +1,7 @@ vfio_ss = ss.source_set() vfio_ss.add(files( - 'helpers.c', 'common.c', - 'container-base.c', 'container.c', - 'migration.c', - 'migration-multifd.c', - 'cpr.c', )) vfio_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr.c')) vfio_ss.add(when: 'CONFIG_IOMMUFD', if_true: files( @@ -25,3 +20,11 @@ vfio_ss.add(when: 'CONFIG_VFIO_AP', if_true: files('ap.c')) vfio_ss.add(when: 'CONFIG_VFIO_IGD', if_true: files('igd.c')) specific_ss.add_all(when: 'CONFIG_VFIO', if_true: vfio_ss) + +system_ss.add(when: 'CONFIG_VFIO', if_true: files( + 'helpers.c', + 'container-base.c', + 'migration.c', + 'migration-multifd.c', + 'cpr.c', +)) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index 416643ddd69a..fbff46cfc35e 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -27,7 +27,6 @@ #include "qapi/error.h" #include "qapi/qapi-events-vfio.h" #include "exec/ramlist.h" -#include "exec/ram_addr.h" #include "pci.h" #include "trace.h" #include "hw/hw.h" From 761d63ccaf6768dc528f2e0e1a422caa12fbe216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:41 +0100 Subject: [PATCH 0970/1179] hw/vfio: Compile more objects once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These files depend on the VFIO symbol in their Kconfig definition. They don't rely on target specific definitions, move them to system_ss[] to build them once. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Richard Henderson Reviewed-by: Cédric Le Goater Reviewed-by: Eric Auger Message-Id: <20250308230917.18907-5-philmd@linaro.org> Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-7-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/vfio/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build index 8e376cfcbf89..784eae4b5599 100644 --- a/hw/vfio/meson.build +++ b/hw/vfio/meson.build @@ -14,13 +14,13 @@ vfio_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files( )) vfio_ss.add(when: 'CONFIG_VFIO_CCW', if_true: files('ccw.c')) vfio_ss.add(when: 'CONFIG_VFIO_PLATFORM', if_true: files('platform.c')) -vfio_ss.add(when: 'CONFIG_VFIO_XGMAC', if_true: files('calxeda-xgmac.c')) -vfio_ss.add(when: 'CONFIG_VFIO_AMD_XGBE', if_true: files('amd-xgbe.c')) vfio_ss.add(when: 'CONFIG_VFIO_AP', if_true: files('ap.c')) vfio_ss.add(when: 'CONFIG_VFIO_IGD', if_true: files('igd.c')) specific_ss.add_all(when: 'CONFIG_VFIO', if_true: vfio_ss) +system_ss.add(when: 'CONFIG_VFIO_XGMAC', if_true: files('calxeda-xgmac.c')) +system_ss.add(when: 'CONFIG_VFIO_AMD_XGBE', if_true: files('amd-xgbe.c')) system_ss.add(when: 'CONFIG_VFIO', if_true: files( 'helpers.c', 'container-base.c', From d5c0be1a94fb3733ee04b8d5cde6641ad54ee2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:42 +0100 Subject: [PATCH 0971/1179] hw/vfio: Compile iommufd.c once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing unused "exec/ram_addr.h" header allow to compile iommufd.c once for all targets. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Richard Henderson Reviewed-by: Cédric Le Goater Reviewed-by: Eric Auger Message-Id: <20250308230917.18907-6-philmd@linaro.org> Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-8-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/vfio/iommufd.c | 1 - hw/vfio/meson.build | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hw/vfio/iommufd.c b/hw/vfio/iommufd.c index df61edffc084..42c8412bbf50 100644 --- a/hw/vfio/iommufd.c +++ b/hw/vfio/iommufd.c @@ -25,7 +25,6 @@ #include "qemu/cutils.h" #include "qemu/chardev_open.h" #include "pci.h" -#include "exec/ram_addr.h" static int iommufd_cdev_map(const VFIOContainerBase *bcontainer, hwaddr iova, ram_addr_t size, void *vaddr, bool readonly) diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build index 784eae4b5599..5c9ec7e8971b 100644 --- a/hw/vfio/meson.build +++ b/hw/vfio/meson.build @@ -4,9 +4,6 @@ vfio_ss.add(files( 'container.c', )) vfio_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr.c')) -vfio_ss.add(when: 'CONFIG_IOMMUFD', if_true: files( - 'iommufd.c', -)) vfio_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files( 'display.c', 'pci-quirks.c', @@ -28,3 +25,6 @@ system_ss.add(when: 'CONFIG_VFIO', if_true: files( 'migration-multifd.c', 'cpr.c', )) +system_ss.add(when: ['CONFIG_VFIO', 'CONFIG_IOMMUFD'], if_true: files( + 'iommufd.c', +)) From 28ea52dd639b75e212ad32cc704a1dd212286914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 11 Mar 2025 09:57:43 +0100 Subject: [PATCH 0972/1179] hw/vfio: Compile display.c once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit display.c doesn't rely on target specific definitions, move it to system_ss[] to build it once. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Richard Henderson Reviewed-by: Cédric Le Goater Reviewed-by: Eric Auger Message-Id: <20250308230917.18907-8-philmd@linaro.org> Link: https://lore.kernel.org/qemu-devel/20250311085743.21724-9-philmd@linaro.org Signed-off-by: Cédric Le Goater --- hw/vfio/meson.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build index 5c9ec7e8971b..a8939c838657 100644 --- a/hw/vfio/meson.build +++ b/hw/vfio/meson.build @@ -5,7 +5,6 @@ vfio_ss.add(files( )) vfio_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr.c')) vfio_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files( - 'display.c', 'pci-quirks.c', 'pci.c', )) @@ -28,3 +27,6 @@ system_ss.add(when: 'CONFIG_VFIO', if_true: files( system_ss.add(when: ['CONFIG_VFIO', 'CONFIG_IOMMUFD'], if_true: files( 'iommufd.c', )) +system_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files( + 'display.c', +)) From 63316f973ad9f1465b1cb820c5be954260663c8a Mon Sep 17 00:00:00 2001 From: Vasilis Liaskovitis Date: Tue, 11 Mar 2025 00:58:33 +0100 Subject: [PATCH 0973/1179] vfio/pci-quirks: Exclude non-ioport BAR from ATI quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ATI BAR4 quirk is targeting an ioport BAR. Older devices may have a BAR4 which is not an ioport, causing a segfault here. Test the BAR type to skip these devices. Similar to "8f419c5b: vfio/pci-quirks: Exclude non-ioport BAR from NVIDIA quirk" Untested, as I don't have the card to test. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2856 Signed-off-by: Vasilis Liaskovitis Reviewed-by: Alex Williamson Link: https://lore.kernel.org/qemu-devel/20250310235833.41026-1-vliaskovitis@suse.com Signed-off-by: Cédric Le Goater --- hw/vfio/pci-quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c index f998761abca2..3f002252acfb 100644 --- a/hw/vfio/pci-quirks.c +++ b/hw/vfio/pci-quirks.c @@ -403,7 +403,7 @@ static void vfio_probe_ati_bar4_quirk(VFIOPCIDevice *vdev, int nr) /* This windows doesn't seem to be used except by legacy VGA code */ if (!vfio_pci_is(vdev, PCI_VENDOR_ID_ATI, PCI_ANY_ID) || - !vdev->vga || nr != 4) { + !vdev->vga || nr != 4 || !vdev->bars[4].ioport) { return; } From 4d9607481560e6c8e1508a0aafe94f86a0503c8c Mon Sep 17 00:00:00 2001 From: Joao Martins Date: Tue, 11 Mar 2025 17:48:07 +0000 Subject: [PATCH 0974/1179] vfio/pci: Drop debug commentary from x-device-dirty-page-tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The intent behind the x-device-dirty-page-tracking option is twofold: 1) development/testing in the presence of VFs with VF dirty page tracking 2) deliberately choosing platform dirty tracker over the VF one. Item 2) scenario is useful when VF dirty tracker is not as fast as IOMMU, or there's some limitations around it (e.g. number of them is limited; aggregated address space under tracking is limited), efficiency/scalability (e.g. 1 pagetable in IOMMU dirty tracker to scan vs N VFs) or just troubleshooting. Given item 2 it is not restricted to debugging, hence drop the debug parenthesis from the option description. Signed-off-by: Joao Martins Reviewed-by: Cédric Le Goater Link: https://lore.kernel.org/qemu-devel/20250311174807.79825-1-joao.m.martins@oracle.com [ clg: Fixed subject spelling ] Signed-off-by: Cédric Le Goater --- hw/vfio/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 3cb7806f2f21..7f1532fbed9a 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3532,7 +3532,7 @@ static void vfio_pci_dev_class_init(ObjectClass *klass, void *data) object_class_property_set_description(klass, /* 9.1 */ "x-device-dirty-page-tracking", "Disable device dirty page tracking and use " - "container-based dirty page tracking (DEBUG)"); + "container-based dirty page tracking"); object_class_property_set_description(klass, /* 9.1 */ "migration-events", "Emit VFIO migration QAPI event when a VFIO device " From d060b2789f71e8cd1d07c4374e0c96c299423952 Mon Sep 17 00:00:00 2001 From: BALATON Zoltan Date: Mon, 10 Feb 2025 17:03:29 +0100 Subject: [PATCH 0975/1179] hw/sd/sdhci: Set reset value of interrupt registers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The interrupt enable registers are not reset to 0 on Freescale eSDHC but some bits are enabled on reset. At least some U-Boot versions seem to expect this and not initialise these registers before expecting interrupts. Use existing vendor property for Freescale eSDHC and set the reset value of the interrupt registers to match Freescale documentation. Signed-off-by: BALATON Zoltan Message-ID: <20250210160329.DDA7F4E600E@zero.eik.bme.hu> Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Philippe Mathieu-Daudé --- hw/ppc/e500.c | 1 + hw/sd/sdhci.c | 4 ++++ include/hw/sd/sdhci.h | 1 + 3 files changed, 6 insertions(+) diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c index fe8b9f79621b..69269aa24c4f 100644 --- a/hw/ppc/e500.c +++ b/hw/ppc/e500.c @@ -1043,6 +1043,7 @@ void ppce500_init(MachineState *machine) dev = qdev_new(TYPE_SYSBUS_SDHCI); qdev_prop_set_uint8(dev, "sd-spec-version", 2); qdev_prop_set_uint8(dev, "endianness", DEVICE_BIG_ENDIAN); + qdev_prop_set_uint8(dev, "vendor", SDHCI_VENDOR_FSL); s = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(s, &error_fatal); sysbus_connect_irq(s, 0, qdev_get_gpio_in(mpicdev, MPC85XX_ESDHC_IRQ)); diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 1f45a77566c0..fe87e18d5d2c 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -307,6 +307,10 @@ static void sdhci_reset(SDHCIState *s) s->data_count = 0; s->stopped_state = sdhc_not_stopped; s->pending_insert_state = false; + if (s->vendor == SDHCI_VENDOR_FSL) { + s->norintstsen = 0x013f; + s->errintstsen = 0x117f; + } } static void sdhci_poweron_reset(DeviceState *dev) diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index 38c08e285980..f722d8eb1cce 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -110,6 +110,7 @@ typedef struct SDHCIState SDHCIState; #define SDHCI_VENDOR_NONE 0 #define SDHCI_VENDOR_IMX 1 +#define SDHCI_VENDOR_FSL 2 /* * Controller does not provide transfer-complete interrupt when not From 822405b1fea4f0f404df418bd70da0588b9207ce Mon Sep 17 00:00:00 2001 From: Bernhard Beschow Date: Sun, 23 Feb 2025 12:47:08 +0100 Subject: [PATCH 0976/1179] hw/rtc: Add Ricoh RS5C372 RTC emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The implementation just allows Linux to determine date and time. Signed-off-by: Bernhard Beschow Reviewed-by: Philippe Mathieu-Daudé Acked-by: Fabiano Rosas Message-ID: <20250223114708.1780-19-shentey@gmail.com> Signed-off-by: Philippe Mathieu-Daudé --- MAINTAINERS | 2 + hw/rtc/Kconfig | 5 + hw/rtc/meson.build | 1 + hw/rtc/rs5c372.c | 236 +++++++++++++++++++++++++++++++++++++ hw/rtc/trace-events | 4 + tests/qtest/meson.build | 1 + tests/qtest/rs5c372-test.c | 43 +++++++ 7 files changed, 292 insertions(+) create mode 100644 hw/rtc/rs5c372.c create mode 100644 tests/qtest/rs5c372-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 0e5db7a57449..e34de420f086 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -827,10 +827,12 @@ F: hw/arm/imx8mp-evk.c F: hw/arm/fsl-imx8mp.c F: hw/misc/imx8mp_*.c F: hw/pci-host/fsl_imx8m_phy.c +F: hw/rtc/rs5c372.c F: include/hw/arm/fsl-imx8mp.h F: include/hw/misc/imx8mp_*.h F: include/hw/pci-host/fsl_imx8m_phy.h F: docs/system/arm/imx8mp-evk.rst +F: tests/qtest/rs5c372-test.c MPS2 / MPS3 M: Peter Maydell diff --git a/hw/rtc/Kconfig b/hw/rtc/Kconfig index 2fe04ec1d049..315b0e4eccdd 100644 --- a/hw/rtc/Kconfig +++ b/hw/rtc/Kconfig @@ -26,3 +26,8 @@ config GOLDFISH_RTC config LS7A_RTC bool + +config RS5C372_RTC + bool + depends on I2C + default y if I2C_DEVICES diff --git a/hw/rtc/meson.build b/hw/rtc/meson.build index 8ecc2d792c1f..6c87864dc07c 100644 --- a/hw/rtc/meson.build +++ b/hw/rtc/meson.build @@ -13,3 +13,4 @@ system_ss.add(when: 'CONFIG_GOLDFISH_RTC', if_true: files('goldfish_rtc.c')) system_ss.add(when: 'CONFIG_LS7A_RTC', if_true: files('ls7a_rtc.c')) system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-rtc.c')) system_ss.add(when: 'CONFIG_MC146818RTC', if_true: files('mc146818rtc.c')) +system_ss.add(when: 'CONFIG_RS5C372_RTC', if_true: files('rs5c372.c')) diff --git a/hw/rtc/rs5c372.c b/hw/rtc/rs5c372.c new file mode 100644 index 000000000000..5542f74085a2 --- /dev/null +++ b/hw/rtc/rs5c372.c @@ -0,0 +1,236 @@ +/* + * Ricoh RS5C372, R222x I2C RTC + * + * Copyright (c) 2025 Bernhard Beschow + * + * Based on hw/rtc/ds1338.c + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/i2c/i2c.h" +#include "hw/qdev-properties.h" +#include "hw/resettable.h" +#include "migration/vmstate.h" +#include "qemu/bcd.h" +#include "qom/object.h" +#include "system/rtc.h" +#include "trace.h" + +#define NVRAM_SIZE 0x10 + +/* Flags definitions */ +#define SECONDS_CH 0x80 +#define HOURS_PM 0x20 +#define CTRL2_24 0x20 + +#define TYPE_RS5C372 "rs5c372" +OBJECT_DECLARE_SIMPLE_TYPE(RS5C372State, RS5C372) + +struct RS5C372State { + I2CSlave parent_obj; + + int64_t offset; + uint8_t wday_offset; + uint8_t nvram[NVRAM_SIZE]; + uint8_t ptr; + uint8_t tx_format; + bool addr_byte; +}; + +static void capture_current_time(RS5C372State *s) +{ + /* + * Capture the current time into the secondary registers which will be + * actually read by the data transfer operation. + */ + struct tm now; + qemu_get_timedate(&now, s->offset); + s->nvram[0] = to_bcd(now.tm_sec); + s->nvram[1] = to_bcd(now.tm_min); + if (s->nvram[0xf] & CTRL2_24) { + s->nvram[2] = to_bcd(now.tm_hour); + } else { + int tmp = now.tm_hour; + if (tmp % 12 == 0) { + tmp += 12; + } + if (tmp <= 12) { + s->nvram[2] = to_bcd(tmp); + } else { + s->nvram[2] = HOURS_PM | to_bcd(tmp - 12); + } + } + s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1; + s->nvram[4] = to_bcd(now.tm_mday); + s->nvram[5] = to_bcd(now.tm_mon + 1); + s->nvram[6] = to_bcd(now.tm_year - 100); +} + +static void inc_regptr(RS5C372State *s) +{ + s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1); +} + +static int rs5c372_event(I2CSlave *i2c, enum i2c_event event) +{ + RS5C372State *s = RS5C372(i2c); + + switch (event) { + case I2C_START_RECV: + /* + * In h/w, capture happens on any START condition, not just a + * START_RECV, but there is no need to actually capture on + * START_SEND, because the guest can't get at that data + * without going through a START_RECV which would overwrite it. + */ + capture_current_time(s); + s->ptr = 0xf; + break; + case I2C_START_SEND: + s->addr_byte = true; + break; + default: + break; + } + + return 0; +} + +static uint8_t rs5c372_recv(I2CSlave *i2c) +{ + RS5C372State *s = RS5C372(i2c); + uint8_t res; + + res = s->nvram[s->ptr]; + + trace_rs5c372_recv(s->ptr, res); + + inc_regptr(s); + return res; +} + +static int rs5c372_send(I2CSlave *i2c, uint8_t data) +{ + RS5C372State *s = RS5C372(i2c); + + if (s->addr_byte) { + s->ptr = data >> 4; + s->tx_format = data & 0xf; + s->addr_byte = false; + return 0; + } + + trace_rs5c372_send(s->ptr, data); + + if (s->ptr < 7) { + /* Time register. */ + struct tm now; + qemu_get_timedate(&now, s->offset); + switch (s->ptr) { + case 0: + now.tm_sec = from_bcd(data & 0x7f); + break; + case 1: + now.tm_min = from_bcd(data & 0x7f); + break; + case 2: + if (s->nvram[0xf] & CTRL2_24) { + now.tm_hour = from_bcd(data & 0x3f); + } else { + int tmp = from_bcd(data & (HOURS_PM - 1)); + if (data & HOURS_PM) { + tmp += 12; + } + if (tmp % 12 == 0) { + tmp -= 12; + } + now.tm_hour = tmp; + } + break; + case 3: + { + /* + * The day field is supposed to contain a value in the range + * 1-7. Otherwise behavior is undefined. + */ + int user_wday = (data & 7) - 1; + s->wday_offset = (user_wday - now.tm_wday + 7) % 7; + } + break; + case 4: + now.tm_mday = from_bcd(data & 0x3f); + break; + case 5: + now.tm_mon = from_bcd(data & 0x1f) - 1; + break; + case 6: + now.tm_year = from_bcd(data) + 100; + break; + } + s->offset = qemu_timedate_diff(&now); + } else { + s->nvram[s->ptr] = data; + } + inc_regptr(s); + return 0; +} + +static void rs5c372_reset_hold(Object *obj, ResetType type) +{ + RS5C372State *s = RS5C372(obj); + + /* The clock is running and synchronized with the host */ + s->offset = 0; + s->wday_offset = 0; + memset(s->nvram, 0, NVRAM_SIZE); + s->ptr = 0; + s->addr_byte = false; +} + +static const VMStateDescription rs5c372_vmstate = { + .name = "rs5c372", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_I2C_SLAVE(parent_obj, RS5C372State), + VMSTATE_INT64(offset, RS5C372State), + VMSTATE_UINT8_V(wday_offset, RS5C372State, 2), + VMSTATE_UINT8_ARRAY(nvram, RS5C372State, NVRAM_SIZE), + VMSTATE_UINT8(ptr, RS5C372State), + VMSTATE_UINT8(tx_format, RS5C372State), + VMSTATE_BOOL(addr_byte, RS5C372State), + VMSTATE_END_OF_LIST() + } +}; + +static void rs5c372_init(Object *obj) +{ + qdev_prop_set_uint8(DEVICE(obj), "address", 0x32); +} + +static void rs5c372_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + k->event = rs5c372_event; + k->recv = rs5c372_recv; + k->send = rs5c372_send; + dc->vmsd = &rs5c372_vmstate; + rc->phases.hold = rs5c372_reset_hold; +} + +static const TypeInfo rs5c372_types[] = { + { + .name = TYPE_RS5C372, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(RS5C372State), + .instance_init = rs5c372_init, + .class_init = rs5c372_class_init, + }, +}; + +DEFINE_TYPES(rs5c372_types) diff --git a/hw/rtc/trace-events b/hw/rtc/trace-events index 8012afe1021d..b9f2852d35fc 100644 --- a/hw/rtc/trace-events +++ b/hw/rtc/trace-events @@ -35,3 +35,7 @@ m48txx_nvram_mem_write(uint32_t addr, uint32_t value) "mem write addr:0x%04x val # goldfish_rtc.c goldfish_rtc_read(uint64_t addr, uint64_t value) "addr 0x%02" PRIx64 " value 0x%08" PRIx64 goldfish_rtc_write(uint64_t addr, uint64_t value) "addr 0x%02" PRIx64 " value 0x%08" PRIx64 + +# rs5c372.c +rs5c372_recv(uint32_t addr, uint8_t value) "[0x%" PRIx32 "] -> 0x%02" PRIx8 +rs5c372_send(uint32_t addr, uint8_t value) "[0x%" PRIx32 "] <- 0x%02" PRIx8 diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 8a6243382a18..9e5380ba7a2d 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -298,6 +298,7 @@ qos_test_ss.add( 'pca9552-test.c', 'pci-test.c', 'pcnet-test.c', + 'rs5c372-test.c', 'sdhci-test.c', 'spapr-phb-test.c', 'tmp105-test.c', diff --git a/tests/qtest/rs5c372-test.c b/tests/qtest/rs5c372-test.c new file mode 100644 index 000000000000..0f6a9b68b9f2 --- /dev/null +++ b/tests/qtest/rs5c372-test.c @@ -0,0 +1,43 @@ +/* + * QTest testcase for the RS5C372 RTC + * + * Copyright (c) 2025 Bernhard Beschow + * + * Based on ds1338-test.c + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bcd.h" +#include "libqos/i2c.h" + +#define RS5C372_ADDR 0x32 + +static void rs5c372_read_date(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = obj; + + uint8_t resp[0x10]; + time_t now = time(NULL); + struct tm *utc = gmtime(&now); + + i2c_read_block(i2cdev, 0, resp, sizeof(resp)); + + /* check retrieved time against local time */ + g_assert_cmpuint(from_bcd(resp[5]), == , utc->tm_mday); + g_assert_cmpuint(from_bcd(resp[6]), == , 1 + utc->tm_mon); + g_assert_cmpuint(2000 + from_bcd(resp[7]), == , 1900 + utc->tm_year); +} + +static void rs5c372_register_nodes(void) +{ + QOSGraphEdgeOptions opts = { }; + add_qi2c_address(&opts, &(QI2CAddress) { RS5C372_ADDR }); + + qos_node_create_driver("rs5c372", i2c_device_create); + qos_node_consumes("rs5c372", "i2c-bus", &opts); + qos_add_test("read_date", "rs5c372", rs5c372_read_date, NULL); +} + +libqos_init(rs5c372_register_nodes); From 2fa3a5b9469615d06091cf473d172794148e1248 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 28 Feb 2025 17:47:59 +0000 Subject: [PATCH 0977/1179] hw/net/smc91c111: Sanitize packet numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The smc91c111 uses packet numbers as an index into its internal s->data[][] array. Valid packet numbers are between 0 and 3, but the code does not generally check this, and there are various places where the guest can hand us an arbitrary packet number and cause an out-of-bounds access to the data array. Add validation of packet numbers. The datasheet is not very helpful about how guest errors like this should be handled: it says nothing on the subject, and none of the documented error conditions are relevant. We choose to log the situation with LOG_GUEST_ERROR and silently ignore the attempted operation. In the places where we are about to access the data[][] array using a packet number and we know the number is valid because we got it from somewhere that has already validated, we add an assert() to document that belief. Cc: qemu-stable@nongnu.org Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250228174802.1945417-2-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/net/smc91c111.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c index 0e13dfa18b28..2295c6acf257 100644 --- a/hw/net/smc91c111.c +++ b/hw/net/smc91c111.c @@ -118,6 +118,11 @@ static const VMStateDescription vmstate_smc91c111 = { #define RS_TOOSHORT 0x0400 #define RS_MULTICAST 0x0001 +static inline bool packetnum_valid(int packet_num) +{ + return packet_num >= 0 && packet_num < NUM_PACKETS; +} + /* Update interrupt status. */ static void smc91c111_update(smc91c111_state *s) { @@ -218,6 +223,17 @@ static void smc91c111_pop_tx_fifo_done(smc91c111_state *s) /* Release the memory allocated to a packet. */ static void smc91c111_release_packet(smc91c111_state *s, int packet) { + if (!packetnum_valid(packet)) { + /* + * Data sheet doesn't document behaviour in this guest error + * case, and there is no error status register to report it. + * Log and ignore the attempt. + */ + qemu_log_mask(LOG_GUEST_ERROR, + "smc91c111: attempt to release invalid packet %d\n", + packet); + return; + } s->allocated &= ~(1 << packet); if (s->tx_alloc == 0x80) smc91c111_tx_alloc(s); @@ -239,6 +255,8 @@ static void smc91c111_do_tx(smc91c111_state *s) return; for (i = 0; i < s->tx_fifo_len; i++) { packetnum = s->tx_fifo[i]; + /* queue_tx checked the packet number was valid */ + assert(packetnum_valid(packetnum)); p = &s->data[packetnum][0]; /* Set status word. */ *(p++) = 0x01; @@ -287,6 +305,17 @@ static void smc91c111_do_tx(smc91c111_state *s) /* Add a packet to the TX FIFO. */ static void smc91c111_queue_tx(smc91c111_state *s, int packet) { + if (!packetnum_valid(packet)) { + /* + * Datasheet doesn't document behaviour in this error case, and + * there's no error status register we could report it in. + * Log and ignore. + */ + qemu_log_mask(LOG_GUEST_ERROR, + "smc91c111: attempt to queue invalid packet %d\n", + packet); + return; + } if (s->tx_fifo_len == NUM_PACKETS) return; s->tx_fifo[s->tx_fifo_len++] = packet; @@ -457,6 +486,13 @@ static void smc91c111_writeb(void *opaque, hwaddr offset, n = s->rx_fifo[0]; else n = s->packet_num; + if (!packetnum_valid(n)) { + /* Datasheet doesn't document what to do here */ + qemu_log_mask(LOG_GUEST_ERROR, + "smc91c111: attempt to write data to invalid packet %d\n", + n); + return; + } p = s->ptr & 0x07ff; if (s->ptr & 0x4000) { s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x7ff); @@ -605,6 +641,13 @@ static uint32_t smc91c111_readb(void *opaque, hwaddr offset) n = s->rx_fifo[0]; else n = s->packet_num; + if (!packetnum_valid(n)) { + /* Datasheet doesn't document what to do here */ + qemu_log_mask(LOG_GUEST_ERROR, + "smc91c111: attempt to read data from invalid packet %d\n", + n); + return 0; + } p = s->ptr & 0x07ff; if (s->ptr & 0x4000) { s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x07ff); @@ -713,6 +756,8 @@ static ssize_t smc91c111_receive(NetClientState *nc, const uint8_t *buf, size_t return -1; s->rx_fifo[s->rx_fifo_len++] = packetnum; + /* allocate_packet() will not hand us back an invalid packet number */ + assert(packetnum_valid(packetnum)); p = &s->data[packetnum][0]; /* ??? Multicast packets? */ status = 0; From aad6f264add3f2be72acb660816588fe09110069 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 28 Feb 2025 17:48:00 +0000 Subject: [PATCH 0978/1179] hw/net/smc91c111: Sanitize packet length on tx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the smc91c111 transmits a packet, it must read a control byte which is at the end of the data area and CRC. However, we don't sanitize the length field in the packet buffer, so if the guest sets the length field to something large we will try to read past the end of the packet data buffer when we access the control byte. As usual, the datasheet says nothing about the behaviour of the hardware if the guest misprograms it in this way. It says only that the maximum valid length is 2048 bytes. We choose to log the guest error and silently drop the packet. This requires us to factor out the "mark the tx packet as complete" logic, so we can call it for this "drop packet" case as well as at the end of the loop when we send a valid packet. Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2742 Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250228174802.1945417-3-peter.maydell@linaro.org> [PMD: Update smc91c111_do_tx() as len > MAX_PACKET_SIZE] Signed-off-by: Philippe Mathieu-Daudé --- hw/net/smc91c111.c | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c index 2295c6acf257..72ce5d8f4de7 100644 --- a/hw/net/smc91c111.c +++ b/hw/net/smc91c111.c @@ -22,6 +22,13 @@ /* Number of 2k memory pages available. */ #define NUM_PACKETS 4 +/* + * Maximum size of a data frame, including the leading status word + * and byte count fields and the trailing CRC, last data byte + * and control byte (per figure 8-1 in the Microchip Technology + * LAN91C111 datasheet). + */ +#define MAX_PACKET_SIZE 2048 #define TYPE_SMC91C111 "smc91c111" OBJECT_DECLARE_SIMPLE_TYPE(smc91c111_state, SMC91C111) @@ -240,6 +247,16 @@ static void smc91c111_release_packet(smc91c111_state *s, int packet) smc91c111_flush_queued_packets(s); } +static void smc91c111_complete_tx_packet(smc91c111_state *s, int packetnum) +{ + if (s->ctr & CTR_AUTO_RELEASE) { + /* Race? */ + smc91c111_release_packet(s, packetnum); + } else if (s->tx_fifo_done_len < NUM_PACKETS) { + s->tx_fifo_done[s->tx_fifo_done_len++] = packetnum; + } +} + /* Flush the TX FIFO. */ static void smc91c111_do_tx(smc91c111_state *s) { @@ -263,6 +280,17 @@ static void smc91c111_do_tx(smc91c111_state *s) *(p++) = 0x40; len = *(p++); len |= ((int)*(p++)) << 8; + if (len > MAX_PACKET_SIZE) { + /* + * Datasheet doesn't say what to do here, and there is no + * relevant tx error condition listed. Log, and drop the packet. + */ + qemu_log_mask(LOG_GUEST_ERROR, + "smc91c111: tx packet with bad length %d, dropping\n", + len); + smc91c111_complete_tx_packet(s, packetnum); + continue; + } len -= 6; control = p[len + 1]; if (control & 0x20) @@ -291,11 +319,7 @@ static void smc91c111_do_tx(smc91c111_state *s) } } #endif - if (s->ctr & CTR_AUTO_RELEASE) - /* Race? */ - smc91c111_release_packet(s, packetnum); - else if (s->tx_fifo_done_len < NUM_PACKETS) - s->tx_fifo_done[s->tx_fifo_done_len++] = packetnum; + smc91c111_complete_tx_packet(s, packetnum); qemu_send_packet(qemu_get_queue(s->nic), p, len); } s->tx_fifo_len = 0; From e21fe8fb15e70dd8110fd5530521a2e41dc2c201 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 28 Feb 2025 17:48:01 +0000 Subject: [PATCH 0979/1179] hw/net/smc91c111: Use MAX_PACKET_SIZE instead of magic numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now we have a constant for the maximum packet size, we can use it to replace various hardcoded 2048 values. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250228174802.1945417-4-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/net/smc91c111.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c index 72ce5d8f4de7..b05970d5e1c1 100644 --- a/hw/net/smc91c111.c +++ b/hw/net/smc91c111.c @@ -58,7 +58,7 @@ struct smc91c111_state { int tx_fifo_done_len; int tx_fifo_done[NUM_PACKETS]; /* Packet buffer memory. */ - uint8_t data[NUM_PACKETS][2048]; + uint8_t data[NUM_PACKETS][MAX_PACKET_SIZE]; uint8_t int_level; uint8_t int_mask; MemoryRegion mmio; @@ -86,7 +86,8 @@ static const VMStateDescription vmstate_smc91c111 = { VMSTATE_INT32_ARRAY(rx_fifo, smc91c111_state, NUM_PACKETS), VMSTATE_INT32(tx_fifo_done_len, smc91c111_state), VMSTATE_INT32_ARRAY(tx_fifo_done, smc91c111_state, NUM_PACKETS), - VMSTATE_BUFFER_UNSAFE(data, smc91c111_state, 0, NUM_PACKETS * 2048), + VMSTATE_BUFFER_UNSAFE(data, smc91c111_state, 0, + NUM_PACKETS * MAX_PACKET_SIZE), VMSTATE_UINT8(int_level, smc91c111_state), VMSTATE_UINT8(int_mask, smc91c111_state), VMSTATE_END_OF_LIST() @@ -773,8 +774,9 @@ static ssize_t smc91c111_receive(NetClientState *nc, const uint8_t *buf, size_t if (crc) packetsize += 4; /* TODO: Flag overrun and receive errors. */ - if (packetsize > 2048) + if (packetsize > MAX_PACKET_SIZE) { return -1; + } packetnum = smc91c111_allocate_packet(s); if (packetnum == 0x80) return -1; From 700d3d6dd41de3bd3f1153e3cfe00b93f99b1441 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 28 Feb 2025 19:16:51 +0000 Subject: [PATCH 0980/1179] hw/net/smc91c111: Don't allow data register access to overrun buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For accesses to the 91c111 data register, the address within the packet's data frame is determined by a combination of the pointer register and the offset used to access the data register, so that you can access data at effectively wider than byte width. The pointer register's pointer field is 11 bits wide, which is exactly the size to index a 2048-byte data frame. We weren't quite getting the logic right for ensuring that we end up with a pointer value to use in the s->data[][] array that isn't out of bounds: * we correctly mask when getting the initial pointer value * for the "autoincrement the pointer register" case, we correctly mask after adding 1 so that the pointer register wraps back around at the 2048 byte mark * but for the non-autoincrement case where we have to add the low 2 bits of the data register offset, we don't account for the possibility that the pointer register is 0x7ff and the addition should wrap Fix this bug by factoring out the "get the p value to use as an array index" into a function, making it use FIELD macro names rather than hard-coded constants, and having a utility function that does "add a value and wrap it" that we can use both for the "autoincrement" and "add the offset bits" codepaths. Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2758 Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250228191652.1957208-1-peter.maydell@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- hw/net/smc91c111.c | 65 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c index b05970d5e1c1..9ce42b56155b 100644 --- a/hw/net/smc91c111.c +++ b/hw/net/smc91c111.c @@ -13,6 +13,7 @@ #include "net/net.h" #include "hw/irq.h" #include "hw/net/smc91c111.h" +#include "hw/registerfields.h" #include "hw/qdev-properties.h" #include "qapi/error.h" #include "qemu/log.h" @@ -126,6 +127,13 @@ static const VMStateDescription vmstate_smc91c111 = { #define RS_TOOSHORT 0x0400 #define RS_MULTICAST 0x0001 +FIELD(PTR, PTR, 0, 11) +FIELD(PTR, NOT_EMPTY, 11, 1) +FIELD(PTR, RESERVED, 12, 1) +FIELD(PTR, READ, 13, 1) +FIELD(PTR, AUTOINCR, 14, 1) +FIELD(PTR, RCV, 15, 1) + static inline bool packetnum_valid(int packet_num) { return packet_num >= 0 && packet_num < NUM_PACKETS; @@ -372,6 +380,49 @@ static void smc91c111_reset(DeviceState *dev) #define SET_LOW(name, val) s->name = (s->name & 0xff00) | val #define SET_HIGH(name, val) s->name = (s->name & 0xff) | (val << 8) +/* + * The pointer register's pointer is an 11 bit value (so it exactly + * indexes a 2048-byte data frame). Add the specified offset to it, + * wrapping around at the 2048 byte mark, and return the resulting + * wrapped value. There are flag bits in the top part of the register, + * but we can ignore them here as the mask will mask them out. + */ +static int ptr_reg_add(smc91c111_state *s, int offset) +{ + return (s->ptr + offset) & R_PTR_PTR_MASK; +} + +/* + * For an access to the Data Register at @offset, return the + * required offset into the packet's data frame. This will + * perform the pointer register autoincrement if required, and + * guarantees to return an in-bounds offset. + */ +static int data_reg_ptr(smc91c111_state *s, int offset) +{ + int p; + + if (s->ptr & R_PTR_AUTOINCR_MASK) { + /* + * Autoincrement: use the current pointer value, and + * increment the pointer register's pointer field. + */ + p = FIELD_EX32(s->ptr, PTR, PTR); + s->ptr = FIELD_DP32(s->ptr, PTR, PTR, ptr_reg_add(s, 1)); + } else { + /* + * No autoincrement: register offset determines which + * byte we're addressing. Setting the pointer to the top + * of the data buffer and then using the pointer wrapping + * to read the bottom byte of the buffer is not something + * sensible guest software will do, but the datasheet + * doesn't say what the behaviour is, so we don't forbid it. + */ + p = ptr_reg_add(s, offset & 3); + } + return p; +} + static void smc91c111_writeb(void *opaque, hwaddr offset, uint32_t value) { @@ -518,12 +569,7 @@ static void smc91c111_writeb(void *opaque, hwaddr offset, n); return; } - p = s->ptr & 0x07ff; - if (s->ptr & 0x4000) { - s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x7ff); - } else { - p += (offset & 3); - } + p = data_reg_ptr(s, offset); s->data[n][p] = value; } return; @@ -673,12 +719,7 @@ static uint32_t smc91c111_readb(void *opaque, hwaddr offset) n); return 0; } - p = s->ptr & 0x07ff; - if (s->ptr & 0x4000) { - s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x07ff); - } else { - p += (offset & 3); - } + p = data_reg_ptr(s, offset); return s->data[n][p]; } case 12: /* Interrupt status. */ From 3a11b653a63fee0e43f4ab84b93f068b961d8fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 5 Mar 2025 12:36:32 +0100 Subject: [PATCH 0981/1179] hw/xen/hvm: Fix Aarch64 typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no TARGET_ARM_64 definition. Luckily enough, when TARGET_AARCH64 is defined, TARGET_ARM also is. Fixes: 733766cd373 ("hw/arm: introduce xenpvh machine") Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Pierrick Bouvier Reviewed-by: Richard Henderson Message-Id: <20250305153929.43687-2-philmd@linaro.org> --- include/hw/xen/arch_hvm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/hw/xen/arch_hvm.h b/include/hw/xen/arch_hvm.h index c7c515220d25..df39c819c8f1 100644 --- a/include/hw/xen/arch_hvm.h +++ b/include/hw/xen/arch_hvm.h @@ -1,5 +1,5 @@ #if defined(TARGET_I386) || defined(TARGET_X86_64) #include "hw/i386/xen_arch_hvm.h" -#elif defined(TARGET_ARM) || defined(TARGET_ARM_64) +#elif defined(TARGET_ARM) || defined(TARGET_AARCH64) #include "hw/arm/xen_arch_hvm.h" #endif From 5dc4337f7908606865f48e476e36482579e1183f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Mar 2025 23:44:42 +0100 Subject: [PATCH 0982/1179] system: Extract target-specific globals to their own compilation unit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We shouldn't use target specific globals for machine properties. These ones could be desugarized, as explained in [*]. While certainly doable, not trivial nor my priority for now. Just move them to a different file to clarify they are *globals*, like the generic globals residing in system/globals.c. Since arch_init.c was introduced using the MIT license (see commit ad96090a01d), retain the same license for the new globals-target.c file. [*] https://lore.kernel.org/qemu-devel/e514d6db-781d-4afe-b057-9046c70044dc@redhat.com/ Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Alex Bennée Message-Id: <20250305005225.95051-2-philmd@linaro.org> --- system/arch_init.c | 14 -------------- system/globals-target.c | 24 ++++++++++++++++++++++++ system/meson.build | 1 + 3 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 system/globals-target.c diff --git a/system/arch_init.c b/system/arch_init.c index b1baed18a300..b9147af93cb8 100644 --- a/system/arch_init.c +++ b/system/arch_init.c @@ -24,18 +24,4 @@ #include "qemu/osdep.h" #include "system/arch_init.h" -#ifdef TARGET_SPARC -int graphic_width = 1024; -int graphic_height = 768; -int graphic_depth = 8; -#elif defined(TARGET_M68K) -int graphic_width = 800; -int graphic_height = 600; -int graphic_depth = 8; -#else -int graphic_width = 800; -int graphic_height = 600; -int graphic_depth = 32; -#endif - const uint32_t arch_type = QEMU_ARCH; diff --git a/system/globals-target.c b/system/globals-target.c new file mode 100644 index 000000000000..989720591e72 --- /dev/null +++ b/system/globals-target.c @@ -0,0 +1,24 @@ +/* + * Global variables that should not exist (target specific) + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * SPDX-License-Identifier: MIT + */ + +#include "qemu/osdep.h" +#include "system/system.h" + +#ifdef TARGET_SPARC +int graphic_width = 1024; +int graphic_height = 768; +int graphic_depth = 8; +#elif defined(TARGET_M68K) +int graphic_width = 800; +int graphic_height = 600; +int graphic_depth = 8; +#else +int graphic_width = 800; +int graphic_height = 600; +int graphic_depth = 32; +#endif diff --git a/system/meson.build b/system/meson.build index c83d80fa2485..eec07a945130 100644 --- a/system/meson.build +++ b/system/meson.build @@ -1,6 +1,7 @@ specific_ss.add(when: 'CONFIG_SYSTEM_ONLY', if_true: [files( 'arch_init.c', 'ioport.c', + 'globals-target.c', 'memory.c', 'physmem.c', )]) From 44ac8eaff00735655401e7d899282875da568fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Tue, 4 Mar 2025 23:59:27 +0100 Subject: [PATCH 0983/1179] system: Replace arch_type global by qemu_arch_available() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qemu_arch_available() is a bit simpler to understand while reviewing than the undocumented arch_type variable. Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20250305005225.95051-5-philmd@linaro.org> --- hw/scsi/scsi-disk.c | 2 +- include/system/arch_init.h | 2 +- system/arch_init.c | 5 ++++- system/qdev-monitor.c | 4 ++-- system/vl.c | 6 +++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index e7f738b4841c..7c87b20e6944 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -3165,7 +3165,7 @@ static void scsi_property_add_specifics(DeviceClass *dc) ObjectClass *oc = OBJECT_CLASS(dc); /* The loadparm property is only supported on s390x */ - if (arch_type & QEMU_ARCH_S390X) { + if (qemu_arch_available(QEMU_ARCH_S390X)) { object_class_property_add_str(oc, "loadparm", scsi_property_get_loadparm, scsi_property_set_loadparm); diff --git a/include/system/arch_init.h b/include/system/arch_init.h index d8b77440487d..51e24c3091e2 100644 --- a/include/system/arch_init.h +++ b/include/system/arch_init.h @@ -25,6 +25,6 @@ enum { QEMU_ARCH_LOONGARCH = (1 << 23), }; -extern const uint32_t arch_type; +bool qemu_arch_available(unsigned qemu_arch_mask); #endif diff --git a/system/arch_init.c b/system/arch_init.c index b9147af93cb8..e85736884c97 100644 --- a/system/arch_init.c +++ b/system/arch_init.c @@ -24,4 +24,7 @@ #include "qemu/osdep.h" #include "system/arch_init.h" -const uint32_t arch_type = QEMU_ARCH; +bool qemu_arch_available(unsigned qemu_arch_mask) +{ + return qemu_arch_mask & QEMU_ARCH; +} diff --git a/system/qdev-monitor.c b/system/qdev-monitor.c index 856c9e8c32ea..5588ed2047d9 100644 --- a/system/qdev-monitor.c +++ b/system/qdev-monitor.c @@ -132,7 +132,7 @@ static const char *qdev_class_get_alias(DeviceClass *dc) for (i = 0; qdev_alias_table[i].typename; i++) { if (qdev_alias_table[i].arch_mask && - !(qdev_alias_table[i].arch_mask & arch_type)) { + !qemu_arch_available(qdev_alias_table[i].arch_mask)) { continue; } @@ -218,7 +218,7 @@ static const char *find_typename_by_alias(const char *alias) for (i = 0; qdev_alias_table[i].alias; i++) { if (qdev_alias_table[i].arch_mask && - !(qdev_alias_table[i].arch_mask & arch_type)) { + !qemu_arch_available(qdev_alias_table[i].arch_mask)) { continue; } diff --git a/system/vl.c b/system/vl.c index 04f78466c412..ec93988a03a4 100644 --- a/system/vl.c +++ b/system/vl.c @@ -878,11 +878,11 @@ static void help(int exitcode) g_get_prgname()); #define DEF(option, opt_arg, opt_enum, opt_help, arch_mask) \ - if ((arch_mask) & arch_type) \ + if (qemu_arch_available(arch_mask)) \ fputs(opt_help, stdout); #define ARCHHEADING(text, arch_mask) \ - if ((arch_mask) & arch_type) \ + if (qemu_arch_available(arch_mask)) \ puts(stringify(text)); #define DEFHEADING(text) ARCHHEADING(text, QEMU_ARCH_ALL) @@ -2929,7 +2929,7 @@ void qemu_init(int argc, char **argv) const QEMUOption *popt; popt = lookup_opt(argc, argv, &optarg, &optind); - if (!(popt->arch_mask & arch_type)) { + if (!qemu_arch_available(popt->arch_mask)) { error_report("Option not supported for this target"); exit(1); } From e6ffea40e221d26a213a59cba2598e15f01facf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 7 Mar 2025 16:01:31 +0100 Subject: [PATCH 0984/1179] hw/acpi: Introduce acpi_builtin() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit acpi_builtin() can be used to check at runtime whether the ACPI subsystem is built in a qemu-system binary. Reviewed-by: Ani Sinha Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20250307223949.54040-3-philmd@linaro.org> --- hw/acpi/acpi-stub.c | 5 +++++ hw/acpi/core.c | 5 +++++ include/hw/acpi/acpi.h | 3 +++ 3 files changed, 13 insertions(+) diff --git a/hw/acpi/acpi-stub.c b/hw/acpi/acpi-stub.c index e268ce9b1a9c..790bf509e5d8 100644 --- a/hw/acpi/acpi-stub.c +++ b/hw/acpi/acpi-stub.c @@ -25,3 +25,8 @@ void acpi_table_add(const QemuOpts *opts, Error **errp) { g_assert_not_reached(); } + +bool acpi_builtin(void) +{ + return false; +} diff --git a/hw/acpi/core.c b/hw/acpi/core.c index 870391ed7c8a..58f8964e1309 100644 --- a/hw/acpi/core.c +++ b/hw/acpi/core.c @@ -78,6 +78,11 @@ static void acpi_register_config(void) opts_init(acpi_register_config); +bool acpi_builtin(void) +{ + return true; +} + static int acpi_checksum(const uint8_t *data, int len) { int sum, i; diff --git a/include/hw/acpi/acpi.h b/include/hw/acpi/acpi.h index e0e51e85b418..d1a4fa2af848 100644 --- a/include/hw/acpi/acpi.h +++ b/include/hw/acpi/acpi.h @@ -150,6 +150,9 @@ struct ACPIREGS { Notifier wakeup; }; +/* Return whether ACPI subsystem is built in */ +bool acpi_builtin(void); + /* PM_TMR */ void acpi_pm_tmr_update(ACPIREGS *ar, bool enable); void acpi_pm_tmr_calc_overflow_time(ACPIREGS *ar); From bb99b92a6b1828fe7e37497ad1aac157cdcb36b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 7 Mar 2025 16:01:59 +0100 Subject: [PATCH 0985/1179] hw/i386/fw_cfg: Check ACPI availability with acpi_builtin() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define acpi_tables / acpi_tables_len stubs, then replace the compile-time CONFIG_ACPI check in fw_cfg.c by a runtime one. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Ani Sinha Message-Id: <20250307223949.54040-4-philmd@linaro.org> --- hw/acpi/acpi-stub.c | 3 +++ hw/i386/fw_cfg.c | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hw/acpi/acpi-stub.c b/hw/acpi/acpi-stub.c index 790bf509e5d8..fd0b62fad9e4 100644 --- a/hw/acpi/acpi-stub.c +++ b/hw/acpi/acpi-stub.c @@ -21,6 +21,9 @@ #include "qemu/osdep.h" #include "hw/acpi/acpi.h" +char unsigned *acpi_tables; +size_t acpi_tables_len; + void acpi_table_add(const QemuOpts *opts, Error **errp) { g_assert_not_reached(); diff --git a/hw/i386/fw_cfg.c b/hw/i386/fw_cfg.c index d08aefa02915..a7f1b60b98c0 100644 --- a/hw/i386/fw_cfg.c +++ b/hw/i386/fw_cfg.c @@ -145,10 +145,10 @@ FWCfgState *fw_cfg_arch_create(MachineState *ms, */ fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, apic_id_limit); fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, ms->ram_size); -#ifdef CONFIG_ACPI - fw_cfg_add_bytes(fw_cfg, FW_CFG_ACPI_TABLES, - acpi_tables, acpi_tables_len); -#endif + if (acpi_builtin()) { + fw_cfg_add_bytes(fw_cfg, FW_CFG_ACPI_TABLES, + acpi_tables, acpi_tables_len); + } fw_cfg_add_i32(fw_cfg, FW_CFG_IRQ0_OVERRIDE, 1); fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, &hpet_fw_cfg, sizeof(hpet_fw_cfg)); From f410d702e4b4925e178cf1effa314e01323c488b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Fri, 7 Mar 2025 16:02:23 +0100 Subject: [PATCH 0986/1179] hw/virtio/virtio-mem: Remove CONFIG_DEVICES include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than checking ACPI availability at compile time by checking the CONFIG_ACPI definition from CONFIG_DEVICES, check at runtime via acpi_builtin(). Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: David Hildenbrand Reviewed-by: Pierrick Bouvier Message-Id: <20250307223949.54040-5-philmd@linaro.org> --- hw/virtio/virtio-mem.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index 7b140add765c..5f57eccbb66e 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -28,7 +28,7 @@ #include "migration/misc.h" #include "hw/boards.h" #include "hw/qdev-properties.h" -#include CONFIG_DEVICES +#include "hw/acpi/acpi.h" #include "trace.h" static const VMStateDescription vmstate_virtio_mem_device_early; @@ -883,10 +883,8 @@ static uint64_t virtio_mem_get_features(VirtIODevice *vdev, uint64_t features, MachineState *ms = MACHINE(qdev_get_machine()); VirtIOMEM *vmem = VIRTIO_MEM(vdev); - if (ms->numa_state) { -#if defined(CONFIG_ACPI) + if (ms->numa_state && acpi_builtin()) { virtio_add_feature(&features, VIRTIO_MEM_F_ACPI_PXM); -#endif } assert(vmem->unplugged_inaccessible != ON_OFF_AUTO_AUTO); if (vmem->unplugged_inaccessible == ON_OFF_AUTO_ON) { From 003d35ad6c612d13ebf0a78f828b0c3ee4f44e3d Mon Sep 17 00:00:00 2001 From: Pierrick Bouvier Date: Fri, 7 Mar 2025 13:56:20 -0800 Subject: [PATCH 0987/1179] hw/hyperv/hyperv-proto: Move SYNDBG definitions from target/i386 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows SYNDBG definitions to be available for common compilation units. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Pierrick Bouvier Message-ID: <20250307215623.524987-5-pierrick.bouvier@linaro.org> Signed-off-by: Philippe Mathieu-Daudé --- include/hw/hyperv/hyperv-proto.h | 12 ++++++++++++ target/i386/kvm/hyperv-proto.h | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/hw/hyperv/hyperv-proto.h b/include/hw/hyperv/hyperv-proto.h index 4a2297307b00..fffc5ce342f2 100644 --- a/include/hw/hyperv/hyperv-proto.h +++ b/include/hw/hyperv/hyperv-proto.h @@ -61,6 +61,18 @@ #define HV_MESSAGE_X64_APIC_EOI 0x80010004 #define HV_MESSAGE_X64_LEGACY_FP_ERROR 0x80010005 +/* + * Hyper-V Synthetic debug options MSR + */ +#define HV_X64_MSR_SYNDBG_CONTROL 0x400000F1 +#define HV_X64_MSR_SYNDBG_STATUS 0x400000F2 +#define HV_X64_MSR_SYNDBG_SEND_BUFFER 0x400000F3 +#define HV_X64_MSR_SYNDBG_RECV_BUFFER 0x400000F4 +#define HV_X64_MSR_SYNDBG_PENDING_BUFFER 0x400000F5 +#define HV_X64_MSR_SYNDBG_OPTIONS 0x400000FF + +#define HV_X64_SYNDBG_OPTION_USE_HCALLS BIT(2) + /* * Message flags */ diff --git a/target/i386/kvm/hyperv-proto.h b/target/i386/kvm/hyperv-proto.h index 464fbf09e35a..a9f056f2f3ee 100644 --- a/target/i386/kvm/hyperv-proto.h +++ b/target/i386/kvm/hyperv-proto.h @@ -151,18 +151,6 @@ #define HV_X64_MSR_STIMER3_CONFIG 0x400000B6 #define HV_X64_MSR_STIMER3_COUNT 0x400000B7 -/* - * Hyper-V Synthetic debug options MSR - */ -#define HV_X64_MSR_SYNDBG_CONTROL 0x400000F1 -#define HV_X64_MSR_SYNDBG_STATUS 0x400000F2 -#define HV_X64_MSR_SYNDBG_SEND_BUFFER 0x400000F3 -#define HV_X64_MSR_SYNDBG_RECV_BUFFER 0x400000F4 -#define HV_X64_MSR_SYNDBG_PENDING_BUFFER 0x400000F5 -#define HV_X64_MSR_SYNDBG_OPTIONS 0x400000FF - -#define HV_X64_SYNDBG_OPTION_USE_HCALLS BIT(2) - /* * Guest crash notification MSRs */ From 7f2a5272ff3893ef412c093aae66b7ed34ab3dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 8 Mar 2025 16:12:13 +0100 Subject: [PATCH 0988/1179] hw/sd/sdhci: Remove need for SDHCI_VENDOR_IMX definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All instances of TYPE_IMX_USDHC set vendor=SDHCI_VENDOR_IMX. No need to special-case it. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: BALATON Zoltan Reviewed-by: Bernhard Beschow Message-Id: <20250308213640.13138-3-philmd@linaro.org> --- hw/arm/fsl-imx25.c | 2 -- hw/arm/fsl-imx6.c | 2 -- hw/arm/fsl-imx6ul.c | 2 -- hw/arm/fsl-imx7.c | 2 -- hw/arm/fsl-imx8mp.c | 2 -- hw/sd/sdhci.c | 14 ++++---------- include/hw/sd/sdhci.h | 1 - 7 files changed, 4 insertions(+), 21 deletions(-) diff --git a/hw/arm/fsl-imx25.c b/hw/arm/fsl-imx25.c index 5359a6d8d3b3..02214ca1a1cc 100644 --- a/hw/arm/fsl-imx25.c +++ b/hw/arm/fsl-imx25.c @@ -243,8 +243,6 @@ static void fsl_imx25_realize(DeviceState *dev, Error **errp) &error_abort); object_property_set_uint(OBJECT(&s->esdhc[i]), "capareg", IMX25_ESDHC_CAPABILITIES, &error_abort); - object_property_set_uint(OBJECT(&s->esdhc[i]), "vendor", - SDHCI_VENDOR_IMX, &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->esdhc[i]), errp)) { return; } diff --git a/hw/arm/fsl-imx6.c b/hw/arm/fsl-imx6.c index dc86338b3a5e..a114dc0d63d4 100644 --- a/hw/arm/fsl-imx6.c +++ b/hw/arm/fsl-imx6.c @@ -327,8 +327,6 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) &error_abort); object_property_set_uint(OBJECT(&s->esdhc[i]), "capareg", IMX6_ESDHC_CAPABILITIES, &error_abort); - object_property_set_uint(OBJECT(&s->esdhc[i]), "vendor", - SDHCI_VENDOR_IMX, &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->esdhc[i]), errp)) { return; } diff --git a/hw/arm/fsl-imx6ul.c b/hw/arm/fsl-imx6ul.c index 34c4aa15cd06..ce8d3ef535f1 100644 --- a/hw/arm/fsl-imx6ul.c +++ b/hw/arm/fsl-imx6ul.c @@ -531,8 +531,6 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_USDHC2_IRQ, }; - object_property_set_uint(OBJECT(&s->usdhc[i]), "vendor", - SDHCI_VENDOR_IMX, &error_abort); sysbus_realize(SYS_BUS_DEVICE(&s->usdhc[i]), &error_abort); sysbus_mmio_map(SYS_BUS_DEVICE(&s->usdhc[i]), 0, diff --git a/hw/arm/fsl-imx7.c b/hw/arm/fsl-imx7.c index 3374018cde07..ed1f10bca26b 100644 --- a/hw/arm/fsl-imx7.c +++ b/hw/arm/fsl-imx7.c @@ -471,8 +471,6 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) FSL_IMX7_USDHC3_IRQ, }; - object_property_set_uint(OBJECT(&s->usdhc[i]), "vendor", - SDHCI_VENDOR_IMX, &error_abort); sysbus_realize(SYS_BUS_DEVICE(&s->usdhc[i]), &error_abort); sysbus_mmio_map(SYS_BUS_DEVICE(&s->usdhc[i]), 0, diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c index 1ea98e146353..c3f6da63220f 100644 --- a/hw/arm/fsl-imx8mp.c +++ b/hw/arm/fsl-imx8mp.c @@ -524,8 +524,6 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error **errp) { fsl_imx8mp_memmap[FSL_IMX8MP_USDHC3].addr, FSL_IMX8MP_USDHC3_IRQ }, }; - object_property_set_uint(OBJECT(&s->usdhc[i]), "vendor", - SDHCI_VENDOR_IMX, &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->usdhc[i]), errp)) { return; } diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index fe87e18d5d2c..69baf73ae9b1 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -1735,16 +1735,10 @@ usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) case USDHC_VENDOR_SPEC: s->vendor_spec = value; - switch (s->vendor) { - case SDHCI_VENDOR_IMX: - if (value & USDHC_IMX_FRC_SDCLK_ON) { - s->prnsts &= ~SDHC_IMX_CLOCK_GATE_OFF; - } else { - s->prnsts |= SDHC_IMX_CLOCK_GATE_OFF; - } - break; - default: - break; + if (value & USDHC_IMX_FRC_SDCLK_ON) { + s->prnsts &= ~SDHC_IMX_CLOCK_GATE_OFF; + } else { + s->prnsts |= SDHC_IMX_CLOCK_GATE_OFF; } break; diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index f722d8eb1cce..51fb30ea528b 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -109,7 +109,6 @@ struct SDHCIState { typedef struct SDHCIState SDHCIState; #define SDHCI_VENDOR_NONE 0 -#define SDHCI_VENDOR_IMX 1 #define SDHCI_VENDOR_FSL 2 /* From 48170c2d865a5937092b1384421b01cd38113042 Mon Sep 17 00:00:00 2001 From: Greg Kurz Date: Thu, 6 Mar 2025 18:41:13 +0100 Subject: [PATCH 0989/1179] docs: Rename default-configs to configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was missed at the time. Fixes: 812b31d3f91 ("configs: rename default-configs to configs and reorganise") Signed-off-by: Greg Kurz Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250306174113.427116-1-groug@kaod.org> Signed-off-by: Thomas Huth --- docs/devel/build-system.rst | 10 +++++----- docs/devel/kconfig.rst | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/devel/build-system.rst b/docs/devel/build-system.rst index d42045a23250..a759982f45c4 100644 --- a/docs/devel/build-system.rst +++ b/docs/devel/build-system.rst @@ -260,7 +260,7 @@ Target-dependent emulator sourcesets: Each emulator also includes sources for files in the ``hw/`` and ``target/`` subdirectories. The subdirectory used for each emulator comes from the target's definition of ``TARGET_BASE_ARCH`` or (if missing) - ``TARGET_ARCH``, as found in ``default-configs/targets/*.mak``. + ``TARGET_ARCH``, as found in ``configs/targets/*.mak``. Each subdirectory in ``hw/`` adds one sourceset to the ``hw_arch`` dictionary, for example:: @@ -317,8 +317,8 @@ Utility sourcesets: The following files concur in the definition of which files are linked into each emulator: -``default-configs/devices/*.mak`` - The files under ``default-configs/devices/`` control the boards and devices +``configs/devices/*.mak`` + The files under ``configs/devices/`` control the boards and devices that are built into each QEMU system emulation targets. They merely contain a list of config variable definitions such as:: @@ -327,11 +327,11 @@ into each emulator: CONFIG_XLNX_VERSAL=y ``*/Kconfig`` - These files are processed together with ``default-configs/devices/*.mak`` and + These files are processed together with ``configs/devices/*.mak`` and describe the dependencies between various features, subsystems and device models. They are described in :ref:`kconfig` -``default-configs/targets/*.mak`` +``configs/targets/*.mak`` These files mostly define symbols that appear in the ``*-config-target.h`` file for each emulator\ [#cfgtarget]_. However, the ``TARGET_ARCH`` and ``TARGET_BASE_ARCH`` will also be used to select the ``hw/`` and diff --git a/docs/devel/kconfig.rst b/docs/devel/kconfig.rst index 52d4b905f675..493b76c4fbf7 100644 --- a/docs/devel/kconfig.rst +++ b/docs/devel/kconfig.rst @@ -38,7 +38,7 @@ originated in the Linux kernel, though it was heavily simplified and the handling of dependencies is stricter in QEMU. Unlike Linux, there is no user interface to edit the configuration, which -is instead specified in per-target files under the ``default-configs/`` +is instead specified in per-target files under the ``configs/`` directory of the QEMU source tree. This is because, unlike Linux, configuration and dependencies can be treated as a black box when building QEMU; the default configuration that QEMU ships with should be okay in @@ -103,7 +103,7 @@ directives can be included: **default value**: ``default [if ]`` Default values are assigned to the config symbol if no other value was - set by the user via ``default-configs/*.mak`` files, and only if + set by the user via ``configs/*.mak`` files, and only if ``select`` or ``depends on`` directives do not force the value to true or false respectively. ```` can be ``y`` or ``n``; it cannot be an arbitrary Boolean expression. However, a condition for applying @@ -119,7 +119,7 @@ directives can be included: This is similar to ``select`` as it applies a lower limit of ``y`` to another symbol. However, the lower limit is only a default and the "implied" symbol's value may still be set to ``n`` from a - ``default-configs/*.mak`` files. The following two examples are + ``configs/*.mak`` files. The following two examples are equivalent:: config FOO @@ -146,7 +146,7 @@ declares its dependencies in different ways: bool Subsystems always default to false (they have no ``default`` directive) - and are never visible in ``default-configs/*.mak`` files. It's + and are never visible in ``configs/*.mak`` files. It's up to other symbols to ``select`` whatever subsystems they require. They sometimes have ``select`` directives to bring in other required @@ -238,7 +238,7 @@ declares its dependencies in different ways: include libraries (such as ``FDT``) or ``TARGET_BIG_ENDIAN`` (possibly negated). - Boards are listed for convenience in the ``default-configs/*.mak`` + Boards are listed for convenience in the ``configs/*.mak`` for the target they apply to. **internal elements** @@ -251,18 +251,18 @@ declares its dependencies in different ways: Internal elements group code that is useful in several boards or devices. They are usually enabled with ``select`` and in turn select - other elements; they are never visible in ``default-configs/*.mak`` + other elements; they are never visible in ``configs/*.mak`` files, and often not even in the Makefile. Writing and modifying default configurations -------------------------------------------- In addition to the Kconfig files under hw/, each target also includes -a file called ``default-configs/TARGETNAME-softmmu.mak``. These files +a file called ``configs/TARGETNAME-softmmu.mak``. These files initialize some Kconfig variables to non-default values and provide the starting point to turn on devices and subsystems. -A file in ``default-configs/`` looks like the following example:: +A file in ``configs/`` looks like the following example:: # Default configuration for alpha-softmmu From 533b33d04bff604863deff3f7d41396d110b57e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Sat, 8 Mar 2025 08:13:28 +0100 Subject: [PATCH 0990/1179] tests/functional: Require 'user' netdev for ppc64 e500 test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When commit 72cdd672e18c extended the ppc64 e500 test to add network support, it forgot to require the 'user' netdev backend. Fix that. Fixes: 72cdd672e18c ("tests/functional: Replace the ppc64 e500 advent calendar test") Signed-off-by: Cédric Le Goater Reviewed-by: Thomas Huth Acked-by: Bernhard Beschow Reviewed-by: Philippe Mathieu-Daudé Message-ID: <20250308071328.193694-1-clg@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/test_ppc64_e500.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/test_ppc64_e500.py b/tests/functional/test_ppc64_e500.py index 9ce7ae6c4798..f5fcad9f6b66 100755 --- a/tests/functional/test_ppc64_e500.py +++ b/tests/functional/test_ppc64_e500.py @@ -20,6 +20,7 @@ class E500Test(LinuxKernelTest): def test_ppc64_e500_buildroot(self): self.set_machine('ppce500') + self.require_netdev('user') self.cpu = 'e5500' uimage_path = self.ASSET_BR2_E5500_UIMAGE.fetch() From 8c63f9aa3f885f06edac3d1e4f21bde939f8c517 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 10 Mar 2025 10:28:30 +0000 Subject: [PATCH 0991/1179] tests/functional: Bump up arm_replay timeout On my machine the arm_replay test takes over 2 minutes to run in a config with Rust enabled and debug enabled: $ time (cd build/rust ; PYTHONPATH=../../python:../../tests/functional QEMU_TEST_QEMU_BINARY=./qemu-system-arm ./pyvenv/bin/python3 ../../tests/functional/test_arm_replay.py) TAP version 13 ok 1 test_arm_replay.ArmReplay.test_cubieboard ok 2 test_arm_replay.ArmReplay.test_vexpressa9 ok 3 test_arm_replay.ArmReplay.test_virt 1..3 real 2m16.564s user 2m13.461s sys 0m3.523s Bump up the timeout to 4 minutes. Signed-off-by: Peter Maydell Reviewed-by: Thomas Huth Message-ID: <20250310102830.3752440-1-peter.maydell@linaro.org> Signed-off-by: Thomas Huth --- tests/functional/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index e78560a9011d..e181ed1ea579 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -34,6 +34,7 @@ test_timeouts = { 'arm_orangepi' : 540, 'arm_quanta_gsj' : 240, 'arm_raspi2' : 120, + 'arm_replay' : 240, 'arm_tuxrun' : 240, 'arm_sx1' : 360, 'intel_iommu': 300, From 15ef93dd48c2635d0e9f9e9a4f6dd92a40e23bff Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 11 Mar 2025 17:08:47 +0100 Subject: [PATCH 0992/1179] docs/system: Fix the information on how to run certain functional tests The tests have been converted to the functional framework, so we should not talk about Avocado here anymore. Fixes: f7d6b772200 ("tests/functional: Convert BananaPi tests to the functional framework") Fixes: 380f7268b7b ("tests/functional: Convert the OrangePi tests to the functional framework") Fixes: 4c0a2df81c9 ("tests/functional: Convert some tests that download files via fetch_asset()") Message-ID: <20250311160847.388670-1-thuth@redhat.com> Signed-off-by: Thomas Huth --- docs/system/arm/bananapi_m2u.rst | 5 ++--- docs/system/arm/orangepi.rst | 6 +++--- docs/system/devices/igb.rst | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/system/arm/bananapi_m2u.rst b/docs/system/arm/bananapi_m2u.rst index 587b48865534..d30db8d04c36 100644 --- a/docs/system/arm/bananapi_m2u.rst +++ b/docs/system/arm/bananapi_m2u.rst @@ -135,6 +135,5 @@ provide the following command: .. code-block:: bash $ cd qemu-build-dir - $ AVOCADO_ALLOW_LARGE_STORAGE=yes tests/venv/bin/avocado \ - --verbose --show=app,console run -t machine:bpim2u \ - ../tests/avocado/boot_linux_console.py + $ QEMU_TEST_ALLOW_LARGE_STORAGE=1 \ + meson test --suite thorough func-arm-arm_bpim2u diff --git a/docs/system/arm/orangepi.rst b/docs/system/arm/orangepi.rst index db87e81fec40..8b9448ca7b0a 100644 --- a/docs/system/arm/orangepi.rst +++ b/docs/system/arm/orangepi.rst @@ -257,9 +257,9 @@ Orange Pi PC integration tests The Orange Pi PC machine has several integration tests included. To run the whole set of tests, build QEMU from source and simply -provide the following command: +provide the following command from the build directory: .. code-block:: bash - $ AVOCADO_ALLOW_LARGE_STORAGE=yes avocado --show=app,console run \ - -t machine:orangepi-pc tests/avocado/boot_linux_console.py + $ QEMU_TEST_ALLOW_LARGE_STORAGE=1 \ + meson test --suite thorough func-arm-arm_orangepi diff --git a/docs/system/devices/igb.rst b/docs/system/devices/igb.rst index 04e79dfe5497..9145af5c757c 100644 --- a/docs/system/devices/igb.rst +++ b/docs/system/devices/igb.rst @@ -57,11 +57,11 @@ directory: meson test qtest-x86_64/qos-test ethtool can test register accesses, interrupts, etc. It is automated as an -Avocado test and can be ran with the following command: +functional test and can be ran with the following command: .. code:: shell - make check-avocado AVOCADO_TESTS=tests/avocado/netdev-ethtool.py + meson test --suite thorough func-x86_64-netdev_ethtool References ========== From a5e8299d1a119b9d757ae28a57612f633894d2f6 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Wed, 12 Mar 2025 23:00:00 +1000 Subject: [PATCH 0993/1179] tests/functional/asset: Fail assert fetch when retries are exceeded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the fetch code does not fail gracefully when retry limit is exceeded, it just falls through the loop with no file, which ends up hitting other errors. Add a check for non-existing file, which indicates the retry limit was exceeded. Reviewed-by: Daniel P. Berrangé Signed-off-by: Nicholas Piggin Message-ID: <20250312130002.945508-2-npiggin@gmail.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/asset.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/functional/qemu_test/asset.py b/tests/functional/qemu_test/asset.py index f0730695f09c..27dd839e705f 100644 --- a/tests/functional/qemu_test/asset.py +++ b/tests/functional/qemu_test/asset.py @@ -138,6 +138,9 @@ def fetch(self): tmp_cache_file.unlink() raise + if not os.path.exists(tmp_cache_file): + raise Exception("Retries exceeded downloading %s", self.url) + try: # Set these just for informational purposes os.setxattr(str(tmp_cache_file), "user.qemu-asset-url", From 7524e1b33679dc1356e8bb4efdd18e83fc50f5cc Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Wed, 12 Mar 2025 23:00:01 +1000 Subject: [PATCH 0994/1179] tests/functional/asset: Verify downloaded size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the server provides a Content-Length header, use that to verify the size of the downloaded file. This catches cases where the connection terminates early, and gives the opportunity to retry. Without this, the checksum will likely mismatch and fail without retry. Reviewed-by: Daniel P. Berrangé Signed-off-by: Nicholas Piggin Message-ID: <20250312130002.945508-3-npiggin@gmail.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/asset.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/functional/qemu_test/asset.py b/tests/functional/qemu_test/asset.py index 27dd839e705f..6bbfb9e1cade 100644 --- a/tests/functional/qemu_test/asset.py +++ b/tests/functional/qemu_test/asset.py @@ -121,6 +121,20 @@ def fetch(self): with tmp_cache_file.open("xb") as dst: with urllib.request.urlopen(self.url) as resp: copyfileobj(resp, dst) + length_hdr = resp.getheader("Content-Length") + + # Verify downloaded file size against length metadata, if + # available. + if length_hdr is not None: + length = int(length_hdr) + fsize = tmp_cache_file.stat().st_size + if fsize != length: + self.log.error("Unable to download %s: " + "connection closed before " + "transfer complete (%d/%d)", + self.url, fsize, length) + tmp_cache_file.unlink() + continue break except FileExistsError: self.log.debug("%s already exists, " From 28adad0a4d9f1f64f9f04748c6348a64ba7ad990 Mon Sep 17 00:00:00 2001 From: Nicholas Piggin Date: Wed, 12 Mar 2025 23:00:02 +1000 Subject: [PATCH 0995/1179] tests/functional/asset: Add AssetError exception class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assets are uniquely identified by human-readable-ish url, so make an AssetError exception class that prints url with error message. A property 'transient' is used to capture whether the client may retry or try again later, or if it is a serious and likely permanent error. This is used to retain the existing behaviour of treating HTTP errors other than 404 as 'transient' and not causing precache step to fail. Additionally, partial-downloads and stale asset caches that fail to resolve after the retry limit are now treated as transient and do not cause precache step to fail. For background: The NetBSD archive is, at the time of writing, failing with short transfer. Retrying the fetch at that position (as wget does) results in a "503 backend unavailable" error. We would like to get that error code directly, but I have not found a way to do that with urllib, so treating the short-copy as a transient failure covers that case (and seems like a reasonable way to handle it in general). Reviewed-by: Thomas Huth Reviewed-by: Daniel P. Berrangé Signed-off-by: Nicholas Piggin Message-ID: <20250312130002.945508-4-npiggin@gmail.com> Signed-off-by: Thomas Huth --- tests/functional/qemu_test/asset.py | 43 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/tests/functional/qemu_test/asset.py b/tests/functional/qemu_test/asset.py index 6bbfb9e1cade..704b84d0ea6e 100644 --- a/tests/functional/qemu_test/asset.py +++ b/tests/functional/qemu_test/asset.py @@ -17,6 +17,14 @@ from shutil import copyfileobj from urllib.error import HTTPError +class AssetError(Exception): + def __init__(self, asset, msg, transient=False): + self.url = asset.url + self.msg = msg + self.transient = transient + + def __str__(self): + return "%s: %s" % (self.url, self.msg) # Instances of this class must be declared as class level variables # starting with a name "ASSET_". This enables the pre-caching logic @@ -51,7 +59,7 @@ def _check(self, cache_file): elif len(self.hash) == 128: hl = hashlib.sha512() else: - raise Exception("unknown hash type") + raise AssetError(self, "unknown hash type") # Calculate the hash of the file: with open(cache_file, 'rb') as file: @@ -111,7 +119,8 @@ def fetch(self): return str(self.cache_file) if not self.fetchable(): - raise Exception("Asset cache is invalid and downloads disabled") + raise AssetError(self, + "Asset cache is invalid and downloads disabled") self.log.info("Downloading %s to %s...", self.url, self.cache_file) tmp_cache_file = self.cache_file.with_suffix(".download") @@ -147,13 +156,23 @@ def fetch(self): tmp_cache_file) tmp_cache_file.unlink() continue + except HTTPError as e: + tmp_cache_file.unlink() + self.log.error("Unable to download %s: HTTP error %d", + self.url, e.code) + # Treat 404 as fatal, since it is highly likely to + # indicate a broken test rather than a transient + # server or networking problem + if e.code == 404: + raise AssetError(self, "Unable to download: " + "HTTP error %d" % e.code) + continue except Exception as e: - self.log.error("Unable to download %s: %s", self.url, e) tmp_cache_file.unlink() - raise + raise AssetError(self, "Unable to download: " % e) if not os.path.exists(tmp_cache_file): - raise Exception("Retries exceeded downloading %s", self.url) + raise AssetError(self, "Download retries exceeded", transient=True) try: # Set these just for informational purposes @@ -167,8 +186,7 @@ def fetch(self): if not self._check(tmp_cache_file): tmp_cache_file.unlink() - raise Exception("Hash of %s does not match %s" % - (self.url, self.hash)) + raise AssetError(self, "Hash does not match %s" % self.hash) tmp_cache_file.replace(self.cache_file) # Remove write perms to stop tests accidentally modifying them os.chmod(self.cache_file, stat.S_IRUSR | stat.S_IRGRP) @@ -190,15 +208,10 @@ def precache_test(test): log.info("Attempting to cache '%s'" % asset) try: asset.fetch() - except HTTPError as e: - # Treat 404 as fatal, since it is highly likely to - # indicate a broken test rather than a transient - # server or networking problem - if e.code == 404: + except AssetError as e: + if not e.transient: raise - - log.debug(f"HTTP error {e.code} from {asset.url} " + - "skipping asset precache") + log.error("%s: skipping asset precache" % e) log.removeHandler(handler) From b3c03666fb10b4900e5bbff0a2b403731730e637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Wed, 12 Mar 2025 19:03:13 +0000 Subject: [PATCH 0996/1179] tests/functional: skip vulkan test if missing vulkaninfo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I could have sworn I had this is a previous iteration of the patches but I guess it got lost in a re-base. As we are going to call vulkaninfo to probe for "bad" drivers we need to skip if the binary isn't available. Fixes: 9f7e493d11 (tests/functional: skip vulkan tests with nVidia) Signed-off-by: Alex Bennée Message-ID: <20250312190314.1632357-1-alex.bennee@linaro.org> Signed-off-by: Thomas Huth --- tests/functional/test_aarch64_virt_gpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/test_aarch64_virt_gpu.py b/tests/functional/test_aarch64_virt_gpu.py index f19a47f8b639..314d994a7aad 100755 --- a/tests/functional/test_aarch64_virt_gpu.py +++ b/tests/functional/test_aarch64_virt_gpu.py @@ -115,6 +115,7 @@ def test_aarch64_virt_with_virgl_blobs_gpu(self): self._run_virt_weston_test("glmark2-wayland -b:duration=1.0") @skipIfMissingCommands('zstd') + @skipIfMissingCommands('vulkaninfo') def test_aarch64_virt_with_vulkan_gpu(self): self.require_device('virtio-gpu-gl-pci') From 984a32f17e8dab0dc3d2328c46cb3e0c0a472a73 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 7 Mar 2025 23:16:30 +0100 Subject: [PATCH 0997/1179] file-posix: Support FUA writes Until now, FUA was always emulated with a separate flush after the write for file-posix. The overhead of processing a second request can reduce performance significantly for a guest disk that has disabled the write cache, especially if the host disk is already write through, too, and the flush isn't actually doing anything. Advertise support for REQ_FUA in write requests and implement it for Linux AIO and io_uring using the RWF_DSYNC flag for write requests. The thread pool still performs a separate fdatasync() call. This can be improved later by using the pwritev2() syscall if available. As an example, this is how fio numbers can be improved in some scenarios with this patch (all using virtio-blk with cache=directsync on an nvme block device for the VM, fio with ioengine=libaio,direct=1,sync=1): | old | with FUA support ------------------------------+---------------+------------------- bs=4k, iodepth=1, numjobs=1 | 45.6k iops | 56.1k iops bs=4k, iodepth=1, numjobs=16 | 183.3k iops | 236.0k iops bs=4k, iodepth=16, numjobs=1 | 258.4k iops | 311.1k iops However, not all scenarios are clear wins. On another slower disk I saw little to no improvment. In fact, in two corner case scenarios, I even observed a regression, which I however consider acceptable: 1. On slow host disks in a write through cache mode, when the guest is using virtio-blk in a separate iothread so that polling can be enabled, and each completion is quickly followed up with a new request (so that polling gets it), it can happen that enabling FUA makes things slower - the additional very fast no-op flush we used to have gave the adaptive polling algorithm a success so that it kept polling. Without it, we only have the slow write request, which disables polling. This is a problem in the polling algorithm that will be fixed later in this series. 2. With a high queue depth, it can be beneficial to have flush requests for another reason: The optimisation in bdrv_co_flush() that flushes only once per write generation acts as a synchronisation mechanism that lets all requests complete at the same time. This can result in better batching and if the disk is very fast (I only saw this with a null_blk backend), this can make up for the overhead of the flush and improve throughput. In theory, we could optionally introduce a similar artificial latency in the normal completion path to achieve the same kind of completion batching. This is not implemented in this series. Compatibility is not a concern for the kernel side of io_uring, it has supported RWF_DSYNC from the start. However, io_uring_prep_writev2() is not available before liburing 2.2. Linux AIO started supporting it in Linux 4.13 and libaio 0.3.111. The kernel is not a problem for any supported build platform, so it's not necessary to add runtime checks. However, openSUSE is still stuck with an older libaio version that would break the build. We must detect the presence of the writev2 functions in the user space libraries at build time to avoid build failures. Signed-off-by: Kevin Wolf Message-ID: <20250307221634.71951-2-kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- block/file-posix.c | 29 +++++++++++++++++++++-------- block/io_uring.c | 25 ++++++++++++++++++++++--- block/linux-aio.c | 25 ++++++++++++++++++++++--- include/block/raw-aio.h | 19 +++++++++++++++++-- meson.build | 8 ++++++++ 5 files changed, 90 insertions(+), 16 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c index 44e16dda876b..56d1972d156e 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -194,6 +194,7 @@ static int fd_open(BlockDriverState *bs) } static int64_t raw_getlength(BlockDriverState *bs); +static int coroutine_fn raw_co_flush_to_disk(BlockDriverState *bs); typedef struct RawPosixAIOData { BlockDriverState *bs; @@ -804,6 +805,13 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, #endif s->needs_alignment = raw_needs_alignment(bs); + bs->supported_write_flags = BDRV_REQ_FUA; + if (s->use_linux_aio && !laio_has_fua()) { + bs->supported_write_flags &= ~BDRV_REQ_FUA; + } else if (s->use_linux_io_uring && !luring_has_fua()) { + bs->supported_write_flags &= ~BDRV_REQ_FUA; + } + bs->supported_zero_flags = BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK; if (S_ISREG(st.st_mode)) { /* When extending regular files, we get zeros from the OS */ @@ -2477,7 +2485,8 @@ static inline bool raw_check_linux_aio(BDRVRawState *s) #endif static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr, - uint64_t bytes, QEMUIOVector *qiov, int type) + uint64_t bytes, QEMUIOVector *qiov, int type, + int flags) { BDRVRawState *s = bs->opaque; RawPosixAIOData acb; @@ -2508,13 +2517,13 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr, #ifdef CONFIG_LINUX_IO_URING } else if (raw_check_linux_io_uring(s)) { assert(qiov->size == bytes); - ret = luring_co_submit(bs, s->fd, offset, qiov, type); + ret = luring_co_submit(bs, s->fd, offset, qiov, type, flags); goto out; #endif #ifdef CONFIG_LINUX_AIO } else if (raw_check_linux_aio(s)) { assert(qiov->size == bytes); - ret = laio_co_submit(s->fd, offset, qiov, type, + ret = laio_co_submit(s->fd, offset, qiov, type, flags, s->aio_max_batch); goto out; #endif @@ -2534,6 +2543,10 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr, assert(qiov->size == bytes); ret = raw_thread_pool_submit(handle_aiocb_rw, &acb); + if (ret == 0 && (flags & BDRV_REQ_FUA)) { + /* TODO Use pwritev2() instead if it's available */ + ret = raw_co_flush_to_disk(bs); + } goto out; /* Avoid the compiler err of unused label */ out: @@ -2571,14 +2584,14 @@ static int coroutine_fn raw_co_preadv(BlockDriverState *bs, int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags) { - return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_READ); + return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_READ, flags); } static int coroutine_fn raw_co_pwritev(BlockDriverState *bs, int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags) { - return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_WRITE); + return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_WRITE, flags); } static int coroutine_fn raw_co_flush_to_disk(BlockDriverState *bs) @@ -2600,12 +2613,12 @@ static int coroutine_fn raw_co_flush_to_disk(BlockDriverState *bs) #ifdef CONFIG_LINUX_IO_URING if (raw_check_linux_io_uring(s)) { - return luring_co_submit(bs, s->fd, 0, NULL, QEMU_AIO_FLUSH); + return luring_co_submit(bs, s->fd, 0, NULL, QEMU_AIO_FLUSH, 0); } #endif #ifdef CONFIG_LINUX_AIO if (s->has_laio_fdsync && raw_check_linux_aio(s)) { - return laio_co_submit(s->fd, 0, NULL, QEMU_AIO_FLUSH, 0); + return laio_co_submit(s->fd, 0, NULL, QEMU_AIO_FLUSH, 0, 0); } #endif return raw_thread_pool_submit(handle_aiocb_flush, &acb); @@ -3540,7 +3553,7 @@ static int coroutine_fn raw_co_zone_append(BlockDriverState *bs, } trace_zbd_zone_append(bs, *offset >> BDRV_SECTOR_BITS); - return raw_co_prw(bs, offset, len, qiov, QEMU_AIO_ZONE_APPEND); + return raw_co_prw(bs, offset, len, qiov, QEMU_AIO_ZONE_APPEND, 0); } #endif diff --git a/block/io_uring.c b/block/io_uring.c index f52b66b3407f..dd4f304910b6 100644 --- a/block/io_uring.c +++ b/block/io_uring.c @@ -335,15 +335,24 @@ static void luring_deferred_fn(void *opaque) * */ static int luring_do_submit(int fd, LuringAIOCB *luringcb, LuringState *s, - uint64_t offset, int type) + uint64_t offset, int type, BdrvRequestFlags flags) { int ret; struct io_uring_sqe *sqes = &luringcb->sqeq; switch (type) { case QEMU_AIO_WRITE: +#ifdef HAVE_IO_URING_PREP_WRITEV2 + { + int luring_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0; + io_uring_prep_writev2(sqes, fd, luringcb->qiov->iov, + luringcb->qiov->niov, offset, luring_flags); + } +#else + assert(flags == 0); io_uring_prep_writev(sqes, fd, luringcb->qiov->iov, luringcb->qiov->niov, offset); +#endif break; case QEMU_AIO_ZONE_APPEND: io_uring_prep_writev(sqes, fd, luringcb->qiov->iov, @@ -380,7 +389,8 @@ static int luring_do_submit(int fd, LuringAIOCB *luringcb, LuringState *s, } int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset, - QEMUIOVector *qiov, int type) + QEMUIOVector *qiov, int type, + BdrvRequestFlags flags) { int ret; AioContext *ctx = qemu_get_current_aio_context(); @@ -393,7 +403,7 @@ int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset, }; trace_luring_co_submit(bs, s, &luringcb, fd, offset, qiov ? qiov->size : 0, type); - ret = luring_do_submit(fd, &luringcb, s, offset, type); + ret = luring_do_submit(fd, &luringcb, s, offset, type, flags); if (ret < 0) { return ret; @@ -448,3 +458,12 @@ void luring_cleanup(LuringState *s) trace_luring_cleanup_state(s); g_free(s); } + +bool luring_has_fua(void) +{ +#ifdef HAVE_IO_URING_PREP_WRITEV2 + return true; +#else + return false; +#endif +} diff --git a/block/linux-aio.c b/block/linux-aio.c index 194c8f434f15..407369f5c95c 100644 --- a/block/linux-aio.c +++ b/block/linux-aio.c @@ -368,7 +368,8 @@ static void laio_deferred_fn(void *opaque) } static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset, - int type, uint64_t dev_max_batch) + int type, BdrvRequestFlags flags, + uint64_t dev_max_batch) { LinuxAioState *s = laiocb->ctx; struct iocb *iocbs = &laiocb->iocb; @@ -376,7 +377,15 @@ static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset, switch (type) { case QEMU_AIO_WRITE: +#ifdef HAVE_IO_PREP_PWRITEV2 + { + int laio_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0; + io_prep_pwritev2(iocbs, fd, qiov->iov, qiov->niov, offset, laio_flags); + } +#else + assert(flags == 0); io_prep_pwritev(iocbs, fd, qiov->iov, qiov->niov, offset); +#endif break; case QEMU_AIO_ZONE_APPEND: io_prep_pwritev(iocbs, fd, qiov->iov, qiov->niov, offset); @@ -409,7 +418,8 @@ static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset, } int coroutine_fn laio_co_submit(int fd, uint64_t offset, QEMUIOVector *qiov, - int type, uint64_t dev_max_batch) + int type, BdrvRequestFlags flags, + uint64_t dev_max_batch) { int ret; AioContext *ctx = qemu_get_current_aio_context(); @@ -422,7 +432,7 @@ int coroutine_fn laio_co_submit(int fd, uint64_t offset, QEMUIOVector *qiov, .qiov = qiov, }; - ret = laio_do_submit(fd, &laiocb, offset, type, dev_max_batch); + ret = laio_do_submit(fd, &laiocb, offset, type, flags, dev_max_batch); if (ret < 0) { return ret; } @@ -505,3 +515,12 @@ bool laio_has_fdsync(int fd) io_destroy(ctx); return (ret == -EINVAL) ? false : true; } + +bool laio_has_fua(void) +{ +#ifdef HAVE_IO_PREP_PWRITEV2 + return true; +#else + return false; +#endif +} diff --git a/include/block/raw-aio.h b/include/block/raw-aio.h index 626706827f54..6570244496a5 100644 --- a/include/block/raw-aio.h +++ b/include/block/raw-aio.h @@ -17,6 +17,7 @@ #define QEMU_RAW_AIO_H #include "block/aio.h" +#include "block/block-common.h" #include "qemu/iov.h" /* AIO request types */ @@ -58,11 +59,18 @@ void laio_cleanup(LinuxAioState *s); /* laio_co_submit: submit I/O requests in the thread's current AioContext. */ int coroutine_fn laio_co_submit(int fd, uint64_t offset, QEMUIOVector *qiov, - int type, uint64_t dev_max_batch); + int type, BdrvRequestFlags flags, + uint64_t dev_max_batch); bool laio_has_fdsync(int); +bool laio_has_fua(void); void laio_detach_aio_context(LinuxAioState *s, AioContext *old_context); void laio_attach_aio_context(LinuxAioState *s, AioContext *new_context); +#else +static inline bool laio_has_fua(void) +{ + return false; +} #endif /* io_uring.c - Linux io_uring implementation */ #ifdef CONFIG_LINUX_IO_URING @@ -71,9 +79,16 @@ void luring_cleanup(LuringState *s); /* luring_co_submit: submit I/O requests in the thread's current AioContext. */ int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset, - QEMUIOVector *qiov, int type); + QEMUIOVector *qiov, int type, + BdrvRequestFlags flags); void luring_detach_aio_context(LuringState *s, AioContext *old_context); void luring_attach_aio_context(LuringState *s, AioContext *new_context); +bool luring_has_fua(void); +#else +static inline bool luring_has_fua(void) +{ + return false; +} #endif #ifdef _WIN32 diff --git a/meson.build b/meson.build index 9d9c11731f30..2f43fd81bf4e 100644 --- a/meson.build +++ b/meson.build @@ -2727,6 +2727,14 @@ config_host_data.set('HAVE_OPTRESET', cc.has_header_symbol('getopt.h', 'optreset')) config_host_data.set('HAVE_IPPROTO_MPTCP', cc.has_header_symbol('netinet/in.h', 'IPPROTO_MPTCP')) +if libaio.found() + config_host_data.set('HAVE_IO_PREP_PWRITEV2', + cc.has_header_symbol('libaio.h', 'io_prep_pwritev2')) +endif +if linux_io_uring.found() + config_host_data.set('HAVE_IO_URING_PREP_WRITEV2', + cc.has_header_symbol('liburing.h', 'io_uring_prep_writev2')) +endif # has_member config_host_data.set('HAVE_SIGEV_NOTIFY_THREAD_ID', From 2f3b6e61f692bade441230dd25c1c0f101bd2eef Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 7 Mar 2025 23:16:31 +0100 Subject: [PATCH 0998/1179] block/io: Ignore FUA with cache.no-flush=on For block drivers that don't advertise FUA support, we already call bdrv_co_flush(), which considers BDRV_O_NO_FLUSH. However, drivers that do support FUA still see the FUA flag with BDRV_O_NO_FLUSH and get the associated performance penalty that cache.no-flush=on was supposed to avoid. Clear FUA for write requests if BDRV_O_NO_FLUSH is set. Signed-off-by: Kevin Wolf Message-ID: <20250307221634.71951-3-kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- block/io.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/block/io.c b/block/io.c index d369b994dff7..1ba8d1aeea14 100644 --- a/block/io.c +++ b/block/io.c @@ -1058,6 +1058,10 @@ bdrv_driver_pwritev(BlockDriverState *bs, int64_t offset, int64_t bytes, return -ENOMEDIUM; } + if (bs->open_flags & BDRV_O_NO_FLUSH) { + flags &= ~BDRV_REQ_FUA; + } + if ((flags & BDRV_REQ_FUA) && (~bs->supported_write_flags & BDRV_REQ_FUA)) { flags &= ~BDRV_REQ_FUA; From 518db1013cb0384dc19134585f227dbb7bf65e39 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 7 Mar 2025 23:16:32 +0100 Subject: [PATCH 0999/1179] aio: Create AioPolledEvent As a preparation for having multiple adaptive polling states per AioContext, move the 'ns' field into a separate struct. Signed-off-by: Kevin Wolf Message-ID: <20250307221634.71951-4-kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- include/block/aio.h | 6 +++++- util/aio-posix.c | 31 ++++++++++++++++--------------- util/async.c | 3 ++- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/include/block/aio.h b/include/block/aio.h index b2ab3514de23..c9fcfe7ccfd3 100644 --- a/include/block/aio.h +++ b/include/block/aio.h @@ -123,6 +123,10 @@ struct BHListSlice { typedef QSLIST_HEAD(, AioHandler) AioHandlerSList; +typedef struct AioPolledEvent { + int64_t ns; /* current polling time in nanoseconds */ +} AioPolledEvent; + struct AioContext { GSource source; @@ -229,7 +233,7 @@ struct AioContext { int poll_disable_cnt; /* Polling mode parameters */ - int64_t poll_ns; /* current polling time in nanoseconds */ + AioPolledEvent poll; int64_t poll_max_ns; /* maximum polling time in nanoseconds */ int64_t poll_grow; /* polling time growth factor */ int64_t poll_shrink; /* polling time shrink factor */ diff --git a/util/aio-posix.c b/util/aio-posix.c index 06bf9f456cf9..95bddb9e4ba9 100644 --- a/util/aio-posix.c +++ b/util/aio-posix.c @@ -585,7 +585,7 @@ static bool try_poll_mode(AioContext *ctx, AioHandlerList *ready_list, return false; } - max_ns = qemu_soonest_timeout(*timeout, ctx->poll_ns); + max_ns = qemu_soonest_timeout(*timeout, ctx->poll.ns); if (max_ns && !ctx->fdmon_ops->need_wait(ctx)) { /* * Enable poll mode. It pairs with the poll_set_started() in @@ -683,40 +683,40 @@ bool aio_poll(AioContext *ctx, bool blocking) if (ctx->poll_max_ns) { int64_t block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start; - if (block_ns <= ctx->poll_ns) { + if (block_ns <= ctx->poll.ns) { /* This is the sweet spot, no adjustment needed */ } else if (block_ns > ctx->poll_max_ns) { /* We'd have to poll for too long, poll less */ - int64_t old = ctx->poll_ns; + int64_t old = ctx->poll.ns; if (ctx->poll_shrink) { - ctx->poll_ns /= ctx->poll_shrink; + ctx->poll.ns /= ctx->poll_shrink; } else { - ctx->poll_ns = 0; + ctx->poll.ns = 0; } - trace_poll_shrink(ctx, old, ctx->poll_ns); - } else if (ctx->poll_ns < ctx->poll_max_ns && + trace_poll_shrink(ctx, old, ctx->poll.ns); + } else if (ctx->poll.ns < ctx->poll_max_ns && block_ns < ctx->poll_max_ns) { /* There is room to grow, poll longer */ - int64_t old = ctx->poll_ns; + int64_t old = ctx->poll.ns; int64_t grow = ctx->poll_grow; if (grow == 0) { grow = 2; } - if (ctx->poll_ns) { - ctx->poll_ns *= grow; + if (ctx->poll.ns) { + ctx->poll.ns *= grow; } else { - ctx->poll_ns = 4000; /* start polling at 4 microseconds */ + ctx->poll.ns = 4000; /* start polling at 4 microseconds */ } - if (ctx->poll_ns > ctx->poll_max_ns) { - ctx->poll_ns = ctx->poll_max_ns; + if (ctx->poll.ns > ctx->poll_max_ns) { + ctx->poll.ns = ctx->poll_max_ns; } - trace_poll_grow(ctx, old, ctx->poll_ns); + trace_poll_grow(ctx, old, ctx->poll.ns); } } @@ -770,8 +770,9 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns, /* No thread synchronization here, it doesn't matter if an incorrect value * is used once. */ + ctx->poll.ns = 0; + ctx->poll_max_ns = max_ns; - ctx->poll_ns = 0; ctx->poll_grow = grow; ctx->poll_shrink = shrink; diff --git a/util/async.c b/util/async.c index 47e3d35a263f..fc8a78aa79d5 100644 --- a/util/async.c +++ b/util/async.c @@ -609,7 +609,8 @@ AioContext *aio_context_new(Error **errp) qemu_rec_mutex_init(&ctx->lock); timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx); - ctx->poll_ns = 0; + ctx->poll.ns = 0; + ctx->poll_max_ns = 0; ctx->poll_grow = 0; ctx->poll_shrink = 0; From cf2e226fc654072acc185c5d7fb1ff77774f4563 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 7 Mar 2025 23:16:33 +0100 Subject: [PATCH 1000/1179] aio-posix: Factor out adjust_polling_time() Signed-off-by: Kevin Wolf Message-ID: <20250307221634.71951-5-kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- util/aio-posix.c | 77 ++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/util/aio-posix.c b/util/aio-posix.c index 95bddb9e4ba9..259827c7addc 100644 --- a/util/aio-posix.c +++ b/util/aio-posix.c @@ -600,6 +600,46 @@ static bool try_poll_mode(AioContext *ctx, AioHandlerList *ready_list, return false; } +static void adjust_polling_time(AioContext *ctx, AioPolledEvent *poll, + int64_t block_ns) +{ + if (block_ns <= poll->ns) { + /* This is the sweet spot, no adjustment needed */ + } else if (block_ns > ctx->poll_max_ns) { + /* We'd have to poll for too long, poll less */ + int64_t old = poll->ns; + + if (ctx->poll_shrink) { + poll->ns /= ctx->poll_shrink; + } else { + poll->ns = 0; + } + + trace_poll_shrink(ctx, old, poll->ns); + } else if (poll->ns < ctx->poll_max_ns && + block_ns < ctx->poll_max_ns) { + /* There is room to grow, poll longer */ + int64_t old = poll->ns; + int64_t grow = ctx->poll_grow; + + if (grow == 0) { + grow = 2; + } + + if (poll->ns) { + poll->ns *= grow; + } else { + poll->ns = 4000; /* start polling at 4 microseconds */ + } + + if (poll->ns > ctx->poll_max_ns) { + poll->ns = ctx->poll_max_ns; + } + + trace_poll_grow(ctx, old, poll->ns); + } +} + bool aio_poll(AioContext *ctx, bool blocking) { AioHandlerList ready_list = QLIST_HEAD_INITIALIZER(ready_list); @@ -682,42 +722,7 @@ bool aio_poll(AioContext *ctx, bool blocking) /* Adjust polling time */ if (ctx->poll_max_ns) { int64_t block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start; - - if (block_ns <= ctx->poll.ns) { - /* This is the sweet spot, no adjustment needed */ - } else if (block_ns > ctx->poll_max_ns) { - /* We'd have to poll for too long, poll less */ - int64_t old = ctx->poll.ns; - - if (ctx->poll_shrink) { - ctx->poll.ns /= ctx->poll_shrink; - } else { - ctx->poll.ns = 0; - } - - trace_poll_shrink(ctx, old, ctx->poll.ns); - } else if (ctx->poll.ns < ctx->poll_max_ns && - block_ns < ctx->poll_max_ns) { - /* There is room to grow, poll longer */ - int64_t old = ctx->poll.ns; - int64_t grow = ctx->poll_grow; - - if (grow == 0) { - grow = 2; - } - - if (ctx->poll.ns) { - ctx->poll.ns *= grow; - } else { - ctx->poll.ns = 4000; /* start polling at 4 microseconds */ - } - - if (ctx->poll.ns > ctx->poll_max_ns) { - ctx->poll.ns = ctx->poll_max_ns; - } - - trace_poll_grow(ctx, old, ctx->poll.ns); - } + adjust_polling_time(ctx, &ctx->poll, block_ns); } progress |= aio_bh_poll(ctx); From ee416407b3c0f45253779e98404acb41231a9279 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 7 Mar 2025 23:16:34 +0100 Subject: [PATCH 1001/1179] aio-posix: Separate AioPolledEvent per AioHandler Adaptive polling has a big problem: It doesn't consider that an event loop can wait for many different events that may have very different typical latencies. For example, think of a guest that tends to send a new I/O request soon after the previous I/O request completes, but the storage on the host is rather slow. In this case, getting the new request from guest quickly means that polling is enabled, but the next thing is performing the I/O request on the backend, which is slow and disables polling again for the next guest request. This means that in such a scenario, polling could help for every other event, but is only ever enabled when it can't succeed. In order to fix this, keep a separate AioPolledEvent for each AioHandler. We will then know that the backend file descriptor always has a high latency and isn't worth polling for, but we also know that the guest is always fast and we should poll for it. This solves at least half of the problem, we can now keep polling for those cases where it makes sense and get the improved performance from it. Since the event loop doesn't know which event will be next, we still do some unnecessary polling while we're waiting for the slow disk. I made some attempts to be more clever than just randomly growing and shrinking the polling time, and even to let callers be explicit about when they expect a new event, but so far this hasn't resulted in improved performance or even caused performance regressions. For now, let's just fix the part that is easy enough to fix, we can revisit the rest later. Signed-off-by: Kevin Wolf Message-ID: <20250307221634.71951-6-kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- include/block/aio.h | 1 - util/aio-posix.c | 26 ++++++++++++++++++++++---- util/aio-posix.h | 1 + util/async.c | 2 -- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/block/aio.h b/include/block/aio.h index c9fcfe7ccfd3..99ff48420b1f 100644 --- a/include/block/aio.h +++ b/include/block/aio.h @@ -233,7 +233,6 @@ struct AioContext { int poll_disable_cnt; /* Polling mode parameters */ - AioPolledEvent poll; int64_t poll_max_ns; /* maximum polling time in nanoseconds */ int64_t poll_grow; /* polling time growth factor */ int64_t poll_shrink; /* polling time shrink factor */ diff --git a/util/aio-posix.c b/util/aio-posix.c index 259827c7addc..80785c29d2c0 100644 --- a/util/aio-posix.c +++ b/util/aio-posix.c @@ -579,13 +579,19 @@ static bool run_poll_handlers(AioContext *ctx, AioHandlerList *ready_list, static bool try_poll_mode(AioContext *ctx, AioHandlerList *ready_list, int64_t *timeout) { + AioHandler *node; int64_t max_ns; if (QLIST_EMPTY_RCU(&ctx->poll_aio_handlers)) { return false; } - max_ns = qemu_soonest_timeout(*timeout, ctx->poll.ns); + max_ns = 0; + QLIST_FOREACH(node, &ctx->poll_aio_handlers, node_poll) { + max_ns = MAX(max_ns, node->poll.ns); + } + max_ns = qemu_soonest_timeout(*timeout, max_ns); + if (max_ns && !ctx->fdmon_ops->need_wait(ctx)) { /* * Enable poll mode. It pairs with the poll_set_started() in @@ -721,8 +727,14 @@ bool aio_poll(AioContext *ctx, bool blocking) /* Adjust polling time */ if (ctx->poll_max_ns) { + AioHandler *node; int64_t block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start; - adjust_polling_time(ctx, &ctx->poll, block_ns); + + QLIST_FOREACH(node, &ctx->poll_aio_handlers, node_poll) { + if (QLIST_IS_INSERTED(node, node_ready)) { + adjust_polling_time(ctx, &node->poll, block_ns); + } + } } progress |= aio_bh_poll(ctx); @@ -772,11 +784,17 @@ void aio_context_use_g_source(AioContext *ctx) void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns, int64_t grow, int64_t shrink, Error **errp) { + AioHandler *node; + + qemu_lockcnt_inc(&ctx->list_lock); + QLIST_FOREACH(node, &ctx->aio_handlers, node) { + node->poll.ns = 0; + } + qemu_lockcnt_dec(&ctx->list_lock); + /* No thread synchronization here, it doesn't matter if an incorrect value * is used once. */ - ctx->poll.ns = 0; - ctx->poll_max_ns = max_ns; ctx->poll_grow = grow; ctx->poll_shrink = shrink; diff --git a/util/aio-posix.h b/util/aio-posix.h index 4264c518be01..82a0201ea48b 100644 --- a/util/aio-posix.h +++ b/util/aio-posix.h @@ -38,6 +38,7 @@ struct AioHandler { #endif int64_t poll_idle_timeout; /* when to stop userspace polling */ bool poll_ready; /* has polling detected an event? */ + AioPolledEvent poll; }; /* Add a handler to a ready list */ diff --git a/util/async.c b/util/async.c index fc8a78aa79d5..863416dee929 100644 --- a/util/async.c +++ b/util/async.c @@ -609,8 +609,6 @@ AioContext *aio_context_new(Error **errp) qemu_rec_mutex_init(&ctx->lock); timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx); - ctx->poll.ns = 0; - ctx->poll_max_ns = 0; ctx->poll_grow = 0; ctx->poll_shrink = 0; From f76d3bee754a2f8d73373d5959dc983169a93eee Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Tue, 11 Mar 2025 15:19:12 +0100 Subject: [PATCH 1002/1179] aio-posix: Adjust polling time also for new handlers aio_dispatch_handler() adds handlers to ctx->poll_aio_handlers if polling should be enabled. If we call adjust_polling_time() for all polling handlers before this, new polling handlers are still left at poll->ns = 0 and polling is only actually enabled after the next event. Move the adjust_polling_time() call after aio_dispatch_handler(). This fixes test-nested-aio-poll, which expects that polling becomes effective the first time around. Signed-off-by: Kevin Wolf Message-ID: <20250311141912.135657-1-kwolf@redhat.com> Signed-off-by: Kevin Wolf --- util/aio-posix.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/util/aio-posix.c b/util/aio-posix.c index 80785c29d2c0..2e0a5dadc4d7 100644 --- a/util/aio-posix.c +++ b/util/aio-posix.c @@ -28,6 +28,9 @@ /* Stop userspace polling on a handler if it isn't active for some time */ #define POLL_IDLE_INTERVAL_NS (7 * NANOSECONDS_PER_SECOND) +static void adjust_polling_time(AioContext *ctx, AioPolledEvent *poll, + int64_t block_ns); + bool aio_poll_disabled(AioContext *ctx) { return qatomic_read(&ctx->poll_disable_cnt); @@ -392,7 +395,8 @@ static bool aio_dispatch_handler(AioContext *ctx, AioHandler *node) * scanning all handlers with aio_dispatch_handlers(). */ static bool aio_dispatch_ready_handlers(AioContext *ctx, - AioHandlerList *ready_list) + AioHandlerList *ready_list, + int64_t block_ns) { bool progress = false; AioHandler *node; @@ -400,6 +404,14 @@ static bool aio_dispatch_ready_handlers(AioContext *ctx, while ((node = QLIST_FIRST(ready_list))) { QLIST_REMOVE(node, node_ready); progress = aio_dispatch_handler(ctx, node) || progress; + + /* + * Adjust polling time only after aio_dispatch_handler(), which can + * add the handler to ctx->poll_aio_handlers. + */ + if (ctx->poll_max_ns && QLIST_IS_INSERTED(node, node_poll)) { + adjust_polling_time(ctx, &node->poll, block_ns); + } } return progress; @@ -653,6 +665,7 @@ bool aio_poll(AioContext *ctx, bool blocking) bool use_notify_me; int64_t timeout; int64_t start = 0; + int64_t block_ns = 0; /* * There cannot be two concurrent aio_poll calls for the same AioContext (or @@ -725,20 +738,13 @@ bool aio_poll(AioContext *ctx, bool blocking) aio_notify_accept(ctx); - /* Adjust polling time */ + /* Calculate blocked time for adaptive polling */ if (ctx->poll_max_ns) { - AioHandler *node; - int64_t block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start; - - QLIST_FOREACH(node, &ctx->poll_aio_handlers, node_poll) { - if (QLIST_IS_INSERTED(node, node_ready)) { - adjust_polling_time(ctx, &node->poll, block_ns); - } - } + block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start; } progress |= aio_bh_poll(ctx); - progress |= aio_dispatch_ready_handlers(ctx, &ready_list); + progress |= aio_dispatch_ready_handlers(ctx, &ready_list, block_ns); aio_free_deleted_handlers(ctx); From 71e1369bad01d441113ede02334175647275652d Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 24 Feb 2025 22:40:58 +0100 Subject: [PATCH 1003/1179] iotests: Limit qsd-migrate to working formats qsd-migrate is currently only working for raw, qcow2 and qed. Other formats are failing, e.g. because they don't support migration. Thus let's limit this test to the three usable formats now. Suggested-by: Kevin Wolf Signed-off-by: Thomas Huth Message-ID: <20250224214058.205889-1-thuth@redhat.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- tests/qemu-iotests/tests/qsd-migrate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/qemu-iotests/tests/qsd-migrate b/tests/qemu-iotests/tests/qsd-migrate index de17562cb06a..a4c6592420c4 100755 --- a/tests/qemu-iotests/tests/qsd-migrate +++ b/tests/qemu-iotests/tests/qsd-migrate @@ -22,7 +22,7 @@ import iotests from iotests import filter_qemu_io, filter_qtest -iotests.script_initialize(supported_fmts=['generic'], +iotests.script_initialize(supported_fmts=['qcow2', 'qed', 'raw'], supported_protocols=['file'], supported_platforms=['linux']) From b2e3659d0d769c84f5b15239a93a722c8012bffa Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:04 +0800 Subject: [PATCH 1004/1179] scsi-disk: drop unused SCSIDiskState->bh field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 71544d30a6f8 ("scsi: push request restart to SCSIDevice") removed the only user of SCSIDiskState->bh. Signed-off-by: Stefan Hajnoczi Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-2-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/scsi-disk.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index e7f738b4841c..caf6c1437f0d 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -106,7 +106,6 @@ struct SCSIDiskState { uint64_t max_unmap_size; uint64_t max_io_size; uint32_t quirks; - QEMUBH *bh; char *version; char *serial; char *vendor; From a89c3c9b2cc4107658c7260ecf329d869888fd51 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:05 +0800 Subject: [PATCH 1005/1179] dma: use current AioContext for dma_blk_io() In the past a single AioContext was used for block I/O and it was fetched using blk_get_aio_context(). Nowadays the block layer supports running I/O from any AioContext and multiple AioContexts at the same time. Remove the dma_blk_io() AioContext argument and use the current AioContext instead. This makes calling the function easier and enables multiple IOThreads to use dma_blk_io() concurrently for the same block device. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-3-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/ide/core.c | 3 +-- hw/ide/macio.c | 3 +-- hw/scsi/scsi-disk.c | 6 ++---- include/system/dma.h | 3 +-- system/dma-helpers.c | 8 ++++---- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/hw/ide/core.c b/hw/ide/core.c index f9baba59e936..b14983ec54fa 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -968,8 +968,7 @@ static void ide_dma_cb(void *opaque, int ret) BDRV_SECTOR_SIZE, ide_dma_cb, s); break; case IDE_DMA_TRIM: - s->bus->dma->aiocb = dma_blk_io(blk_get_aio_context(s->blk), - &s->sg, offset, BDRV_SECTOR_SIZE, + s->bus->dma->aiocb = dma_blk_io(&s->sg, offset, BDRV_SECTOR_SIZE, ide_issue_trim, s, ide_dma_cb, s, DMA_DIRECTION_TO_DEVICE); break; diff --git a/hw/ide/macio.c b/hw/ide/macio.c index 5fe764b49b58..c8e8e44cc9a0 100644 --- a/hw/ide/macio.c +++ b/hw/ide/macio.c @@ -187,8 +187,7 @@ static void pmac_ide_transfer_cb(void *opaque, int ret) pmac_ide_transfer_cb, io); break; case IDE_DMA_TRIM: - s->bus->dma->aiocb = dma_blk_io(blk_get_aio_context(s->blk), &s->sg, - offset, 0x1, ide_issue_trim, s, + s->bus->dma->aiocb = dma_blk_io(&s->sg, offset, 0x1, ide_issue_trim, s, pmac_ide_transfer_cb, io, DMA_DIRECTION_TO_DEVICE); break; diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index caf6c1437f0d..f049a20275d1 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -487,8 +487,7 @@ static void scsi_do_read(SCSIDiskReq *r, int ret) if (r->req.sg) { dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_READ); r->req.residual -= r->req.sg->size; - r->req.aiocb = dma_blk_io(blk_get_aio_context(s->qdev.conf.blk), - r->req.sg, r->sector << BDRV_SECTOR_BITS, + r->req.aiocb = dma_blk_io(r->req.sg, r->sector << BDRV_SECTOR_BITS, BDRV_SECTOR_SIZE, sdc->dma_readv, r, scsi_dma_complete, r, DMA_DIRECTION_FROM_DEVICE); @@ -650,8 +649,7 @@ static void scsi_write_data(SCSIRequest *req) if (r->req.sg) { dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_WRITE); r->req.residual -= r->req.sg->size; - r->req.aiocb = dma_blk_io(blk_get_aio_context(s->qdev.conf.blk), - r->req.sg, r->sector << BDRV_SECTOR_BITS, + r->req.aiocb = dma_blk_io(r->req.sg, r->sector << BDRV_SECTOR_BITS, BDRV_SECTOR_SIZE, sdc->dma_writev, r, scsi_dma_complete, r, DMA_DIRECTION_TO_DEVICE); diff --git a/include/system/dma.h b/include/system/dma.h index 5a49a306284d..e142f7efa68b 100644 --- a/include/system/dma.h +++ b/include/system/dma.h @@ -290,8 +290,7 @@ typedef BlockAIOCB *DMAIOFunc(int64_t offset, QEMUIOVector *iov, BlockCompletionFunc *cb, void *cb_opaque, void *opaque); -BlockAIOCB *dma_blk_io(AioContext *ctx, - QEMUSGList *sg, uint64_t offset, uint32_t align, +BlockAIOCB *dma_blk_io(QEMUSGList *sg, uint64_t offset, uint32_t align, DMAIOFunc *io_func, void *io_func_opaque, BlockCompletionFunc *cb, void *opaque, DMADirection dir); BlockAIOCB *dma_blk_read(BlockBackend *blk, diff --git a/system/dma-helpers.c b/system/dma-helpers.c index f6403242f5fe..6bad75876f11 100644 --- a/system/dma-helpers.c +++ b/system/dma-helpers.c @@ -211,7 +211,7 @@ static const AIOCBInfo dma_aiocb_info = { .cancel_async = dma_aio_cancel, }; -BlockAIOCB *dma_blk_io(AioContext *ctx, +BlockAIOCB *dma_blk_io( QEMUSGList *sg, uint64_t offset, uint32_t align, DMAIOFunc *io_func, void *io_func_opaque, BlockCompletionFunc *cb, @@ -223,7 +223,7 @@ BlockAIOCB *dma_blk_io(AioContext *ctx, dbs->acb = NULL; dbs->sg = sg; - dbs->ctx = ctx; + dbs->ctx = qemu_get_current_aio_context(); dbs->offset = offset; dbs->align = align; dbs->sg_cur_index = 0; @@ -251,7 +251,7 @@ BlockAIOCB *dma_blk_read(BlockBackend *blk, QEMUSGList *sg, uint64_t offset, uint32_t align, void (*cb)(void *opaque, int ret), void *opaque) { - return dma_blk_io(blk_get_aio_context(blk), sg, offset, align, + return dma_blk_io(sg, offset, align, dma_blk_read_io_func, blk, cb, opaque, DMA_DIRECTION_FROM_DEVICE); } @@ -269,7 +269,7 @@ BlockAIOCB *dma_blk_write(BlockBackend *blk, QEMUSGList *sg, uint64_t offset, uint32_t align, void (*cb)(void *opaque, int ret), void *opaque) { - return dma_blk_io(blk_get_aio_context(blk), sg, offset, align, + return dma_blk_io(sg, offset, align, dma_blk_write_io_func, blk, cb, opaque, DMA_DIRECTION_TO_DEVICE); } From 7eecba37788f48d34c015954f1207cc7b52728f5 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:06 +0800 Subject: [PATCH 1006/1179] scsi: track per-SCSIRequest AioContext Until now, a SCSIDevice's I/O requests have run in a single AioContext. In order to support multiple IOThreads it will be necessary to move to the concept of a per-SCSIRequest AioContext. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-4-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/scsi-bus.c | 1 + hw/scsi/scsi-disk.c | 17 ++++++----------- include/hw/scsi/scsi.h | 1 + 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c index 7d4546800fa6..846bbbf0ecc0 100644 --- a/hw/scsi/scsi-bus.c +++ b/hw/scsi/scsi-bus.c @@ -868,6 +868,7 @@ SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun, } } + req->ctx = qemu_get_current_aio_context(); req->cmd = cmd; req->residual = req->cmd.xfer; diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index f049a20275d1..7cf8c31b9890 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -328,9 +328,8 @@ static void scsi_aio_complete(void *opaque, int ret) SCSIDiskReq *r = (SCSIDiskReq *)opaque; SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); - /* The request must only run in the BlockBackend's AioContext */ - assert(blk_get_aio_context(s->qdev.conf.blk) == - qemu_get_current_aio_context()); + /* The request must run in its AioContext */ + assert(r->req.ctx == qemu_get_current_aio_context()); assert(r->req.aiocb != NULL); r->req.aiocb = NULL; @@ -430,12 +429,10 @@ static void scsi_dma_complete(void *opaque, int ret) static void scsi_read_complete_noio(SCSIDiskReq *r, int ret) { - SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); uint32_t n; - /* The request must only run in the BlockBackend's AioContext */ - assert(blk_get_aio_context(s->qdev.conf.blk) == - qemu_get_current_aio_context()); + /* The request must run in its AioContext */ + assert(r->req.ctx == qemu_get_current_aio_context()); assert(r->req.aiocb == NULL); if (scsi_disk_req_check_error(r, ret, ret > 0)) { @@ -562,12 +559,10 @@ static void scsi_read_data(SCSIRequest *req) static void scsi_write_complete_noio(SCSIDiskReq *r, int ret) { - SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); uint32_t n; - /* The request must only run in the BlockBackend's AioContext */ - assert(blk_get_aio_context(s->qdev.conf.blk) == - qemu_get_current_aio_context()); + /* The request must run in its AioContext */ + assert(r->req.ctx == qemu_get_current_aio_context()); assert (r->req.aiocb == NULL); if (scsi_disk_req_check_error(r, ret, ret > 0)) { diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h index c3d5e17e3852..ffc48203f94a 100644 --- a/include/hw/scsi/scsi.h +++ b/include/hw/scsi/scsi.h @@ -24,6 +24,7 @@ struct SCSIRequest { SCSIBus *bus; SCSIDevice *dev; const SCSIReqOps *ops; + AioContext *ctx; uint32_t refcount; uint32_t tag; uint32_t lun; From 1cf18cc9bf5e9f88ad92f89886652e0361e2f41f Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:07 +0800 Subject: [PATCH 1007/1179] scsi: introduce requests_lock SCSIDevice keeps track of in-flight requests for device reset and Task Management Functions (TMFs). The request list requires protection so that multi-threaded SCSI emulation can be implemented in commits that follow. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-5-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/scsi-bus.c | 120 +++++++++++++++++++++++++++++------------ include/hw/scsi/scsi.h | 7 ++- 2 files changed, 88 insertions(+), 39 deletions(-) diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c index 846bbbf0ecc0..ece1107ee871 100644 --- a/hw/scsi/scsi-bus.c +++ b/hw/scsi/scsi-bus.c @@ -100,8 +100,15 @@ static void scsi_device_for_each_req_sync(SCSIDevice *s, assert(!runstate_is_running()); assert(qemu_in_main_thread()); - QTAILQ_FOREACH_SAFE(req, &s->requests, next, next_req) { - fn(req, opaque); + /* + * Locking is not necessary because the guest is stopped and no other + * threads can be accessing the requests list, but take the lock for + * consistency. + */ + WITH_QEMU_LOCK_GUARD(&s->requests_lock) { + QTAILQ_FOREACH_SAFE(req, &s->requests, next, next_req) { + fn(req, opaque); + } } } @@ -115,21 +122,29 @@ static void scsi_device_for_each_req_async_bh(void *opaque) { g_autofree SCSIDeviceForEachReqAsyncData *data = opaque; SCSIDevice *s = data->s; - AioContext *ctx; - SCSIRequest *req; - SCSIRequest *next; + g_autoptr(GList) reqs = NULL; /* - * The BB cannot have changed contexts between this BH being scheduled and - * now: BBs' AioContexts, when they have a node attached, can only be - * changed via bdrv_try_change_aio_context(), in a drained section. While - * we have the in-flight counter incremented, that drain must block. + * Build a list of requests in this AioContext so fn() can be invoked later + * outside requests_lock. */ - ctx = blk_get_aio_context(s->conf.blk); - assert(ctx == qemu_get_current_aio_context()); + WITH_QEMU_LOCK_GUARD(&s->requests_lock) { + AioContext *ctx = qemu_get_current_aio_context(); + SCSIRequest *req; + SCSIRequest *next; + + QTAILQ_FOREACH_SAFE(req, &s->requests, next, next) { + if (req->ctx == ctx) { + scsi_req_ref(req); /* dropped after calling fn() */ + reqs = g_list_prepend(reqs, req); + } + } + } - QTAILQ_FOREACH_SAFE(req, &s->requests, next, next) { - data->fn(req, data->fn_opaque); + /* Call fn() on each request */ + for (GList *elem = g_list_first(reqs); elem; elem = g_list_next(elem)) { + data->fn(elem->data, data->fn_opaque); + scsi_req_unref(elem->data); } /* Drop the reference taken by scsi_device_for_each_req_async() */ @@ -139,9 +154,35 @@ static void scsi_device_for_each_req_async_bh(void *opaque) blk_dec_in_flight(s->conf.blk); } +static void scsi_device_for_each_req_async_do_ctx(gpointer key, gpointer value, + gpointer user_data) +{ + AioContext *ctx = key; + SCSIDeviceForEachReqAsyncData *params = user_data; + SCSIDeviceForEachReqAsyncData *data; + + data = g_new(SCSIDeviceForEachReqAsyncData, 1); + data->s = params->s; + data->fn = params->fn; + data->fn_opaque = params->fn_opaque; + + /* + * Hold a reference to the SCSIDevice until + * scsi_device_for_each_req_async_bh() finishes. + */ + object_ref(OBJECT(data->s)); + + /* Paired with scsi_device_for_each_req_async_bh() */ + blk_inc_in_flight(data->s->conf.blk); + + aio_bh_schedule_oneshot(ctx, scsi_device_for_each_req_async_bh, data); +} + /* * Schedule @fn() to be invoked for each enqueued request in device @s. @fn() - * runs in the AioContext that is executing the request. + * must be thread-safe because it runs concurrently in each AioContext that is + * executing a request. + * * Keeps the BlockBackend's in-flight counter incremented until everything is * done, so draining it will settle all scheduled @fn() calls. */ @@ -151,24 +192,26 @@ static void scsi_device_for_each_req_async(SCSIDevice *s, { assert(qemu_in_main_thread()); - SCSIDeviceForEachReqAsyncData *data = - g_new(SCSIDeviceForEachReqAsyncData, 1); - - data->s = s; - data->fn = fn; - data->fn_opaque = opaque; - - /* - * Hold a reference to the SCSIDevice until - * scsi_device_for_each_req_async_bh() finishes. - */ - object_ref(OBJECT(s)); + /* The set of AioContexts where the requests are being processed */ + g_autoptr(GHashTable) aio_contexts = g_hash_table_new(NULL, NULL); + WITH_QEMU_LOCK_GUARD(&s->requests_lock) { + SCSIRequest *req; + QTAILQ_FOREACH(req, &s->requests, next) { + g_hash_table_add(aio_contexts, req->ctx); + } + } - /* Paired with blk_dec_in_flight() in scsi_device_for_each_req_async_bh() */ - blk_inc_in_flight(s->conf.blk); - aio_bh_schedule_oneshot(blk_get_aio_context(s->conf.blk), - scsi_device_for_each_req_async_bh, - data); + /* Schedule a BH for each AioContext */ + SCSIDeviceForEachReqAsyncData params = { + .s = s, + .fn = fn, + .fn_opaque = opaque, + }; + g_hash_table_foreach( + aio_contexts, + scsi_device_for_each_req_async_do_ctx, + ¶ms + ); } static void scsi_device_realize(SCSIDevice *s, Error **errp) @@ -349,6 +392,7 @@ static void scsi_qdev_realize(DeviceState *qdev, Error **errp) dev->lun = lun; } + qemu_mutex_init(&dev->requests_lock); QTAILQ_INIT(&dev->requests); scsi_device_realize(dev, &local_err); if (local_err) { @@ -369,6 +413,8 @@ static void scsi_qdev_unrealize(DeviceState *qdev) scsi_device_purge_requests(dev, SENSE_CODE(NO_SENSE)); + qemu_mutex_destroy(&dev->requests_lock); + scsi_device_unrealize(dev); blockdev_mark_auto_del(dev->conf.blk); @@ -965,7 +1011,10 @@ static void scsi_req_enqueue_internal(SCSIRequest *req) req->sg = NULL; } req->enqueued = true; - QTAILQ_INSERT_TAIL(&req->dev->requests, req, next); + + WITH_QEMU_LOCK_GUARD(&req->dev->requests_lock) { + QTAILQ_INSERT_TAIL(&req->dev->requests, req, next); + } } int32_t scsi_req_enqueue(SCSIRequest *req) @@ -985,7 +1034,9 @@ static void scsi_req_dequeue(SCSIRequest *req) trace_scsi_req_dequeue(req->dev->id, req->lun, req->tag); req->retry = false; if (req->enqueued) { - QTAILQ_REMOVE(&req->dev->requests, req, next); + WITH_QEMU_LOCK_GUARD(&req->dev->requests_lock) { + QTAILQ_REMOVE(&req->dev->requests, req, next); + } req->enqueued = false; scsi_req_unref(req); } @@ -1962,8 +2013,7 @@ static void scsi_device_class_init(ObjectClass *klass, void *data) static void scsi_dev_instance_init(Object *obj) { - DeviceState *dev = DEVICE(obj); - SCSIDevice *s = SCSI_DEVICE(dev); + SCSIDevice *s = SCSI_DEVICE(obj); device_add_bootindex_property(obj, &s->conf.bootindex, "bootindex", NULL, diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h index ffc48203f94a..90ee192b4d40 100644 --- a/include/hw/scsi/scsi.h +++ b/include/hw/scsi/scsi.h @@ -49,6 +49,8 @@ struct SCSIRequest { bool dma_started; BlockAIOCB *aiocb; QEMUSGList *sg; + + /* Protected by SCSIDevice->requests_lock */ QTAILQ_ENTRY(SCSIRequest) next; }; @@ -77,10 +79,7 @@ struct SCSIDevice uint8_t sense[SCSI_SENSE_BUF_SIZE]; uint32_t sense_len; - /* - * The requests list is only accessed from the AioContext that executes - * requests or from the main loop when IOThread processing is stopped. - */ + QemuMutex requests_lock; /* protects the requests list */ QTAILQ_HEAD(, SCSIRequest) requests; uint32_t channel; From b348ca2e043c0f7c9ecc1bbbd7dd87db47887e9f Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:08 +0800 Subject: [PATCH 1008/1179] virtio-scsi: introduce event and ctrl virtqueue locks Virtqueues are not thread-safe. Until now this was not a major issue since all virtqueue processing happened in the same thread. The ctrl queue's Task Management Function (TMF) requests sometimes need the main loop, so a BH was used to schedule the virtqueue completion back in the thread that has virtqueue access. When IOThread Virtqueue Mapping is introduced in later commits, event and ctrl virtqueue accesses from other threads will become necessary. Introduce an optional per-virtqueue lock so the event and ctrl virtqueues can be protected in the commits that follow. The addition of the ctrl virtqueue lock makes virtio_scsi_complete_req_from_main_loop() and its BH unnecessary. Instead, take the ctrl virtqueue lock from the main loop thread. The cmd virtqueue does not have a lock because the entirety of SCSI command processing happens in one thread. Only one thread accesses the cmd virtqueue and a lock is unnecessary. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-6-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/virtio-scsi.c | 84 ++++++++++++++++++--------------- include/hw/virtio/virtio-scsi.h | 3 ++ 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index 7d094e188103..073ccd3d5b47 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -102,13 +102,18 @@ static void virtio_scsi_free_req(VirtIOSCSIReq *req) g_free(req); } -static void virtio_scsi_complete_req(VirtIOSCSIReq *req) +static void virtio_scsi_complete_req(VirtIOSCSIReq *req, QemuMutex *vq_lock) { VirtIOSCSI *s = req->dev; VirtQueue *vq = req->vq; VirtIODevice *vdev = VIRTIO_DEVICE(s); qemu_iovec_from_buf(&req->resp_iov, 0, &req->resp, req->resp_size); + + if (vq_lock) { + qemu_mutex_lock(vq_lock); + } + virtqueue_push(vq, &req->elem, req->qsgl.size + req->resp_iov.size); if (s->dataplane_started && !s->dataplane_fenced) { virtio_notify_irqfd(vdev, vq); @@ -116,6 +121,10 @@ static void virtio_scsi_complete_req(VirtIOSCSIReq *req) virtio_notify(vdev, vq); } + if (vq_lock) { + qemu_mutex_unlock(vq_lock); + } + if (req->sreq) { req->sreq->hba_private = NULL; scsi_req_unref(req->sreq); @@ -123,34 +132,20 @@ static void virtio_scsi_complete_req(VirtIOSCSIReq *req) virtio_scsi_free_req(req); } -static void virtio_scsi_complete_req_bh(void *opaque) +static void virtio_scsi_bad_req(VirtIOSCSIReq *req, QemuMutex *vq_lock) { - VirtIOSCSIReq *req = opaque; + virtio_error(VIRTIO_DEVICE(req->dev), "wrong size for virtio-scsi headers"); - virtio_scsi_complete_req(req); -} + if (vq_lock) { + qemu_mutex_lock(vq_lock); + } -/* - * Called from virtio_scsi_do_one_tmf_bh() in main loop thread. The main loop - * thread cannot touch the virtqueue since that could race with an IOThread. - */ -static void virtio_scsi_complete_req_from_main_loop(VirtIOSCSIReq *req) -{ - VirtIOSCSI *s = req->dev; + virtqueue_detach_element(req->vq, &req->elem, 0); - if (!s->ctx || s->ctx == qemu_get_aio_context()) { - /* No need to schedule a BH when there is no IOThread */ - virtio_scsi_complete_req(req); - } else { - /* Run request completion in the IOThread */ - aio_wait_bh_oneshot(s->ctx, virtio_scsi_complete_req_bh, req); + if (vq_lock) { + qemu_mutex_unlock(vq_lock); } -} -static void virtio_scsi_bad_req(VirtIOSCSIReq *req) -{ - virtio_error(VIRTIO_DEVICE(req->dev), "wrong size for virtio-scsi headers"); - virtqueue_detach_element(req->vq, &req->elem, 0); virtio_scsi_free_req(req); } @@ -235,12 +230,21 @@ static int virtio_scsi_parse_req(VirtIOSCSIReq *req, return 0; } -static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq) +static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq, QemuMutex *vq_lock) { VirtIOSCSICommon *vs = (VirtIOSCSICommon *)s; VirtIOSCSIReq *req; + if (vq_lock) { + qemu_mutex_lock(vq_lock); + } + req = virtqueue_pop(vq, sizeof(VirtIOSCSIReq) + vs->cdb_size); + + if (vq_lock) { + qemu_mutex_unlock(vq_lock); + } + if (!req) { return NULL; } @@ -305,7 +309,7 @@ static void virtio_scsi_cancel_notify(Notifier *notifier, void *data) trace_virtio_scsi_tmf_resp(virtio_scsi_get_lun(req->req.tmf.lun), req->req.tmf.tag, req->resp.tmf.response); - virtio_scsi_complete_req(req); + virtio_scsi_complete_req(req, &req->dev->ctrl_lock); } g_free(n); } @@ -361,7 +365,7 @@ static void virtio_scsi_do_one_tmf_bh(VirtIOSCSIReq *req) out: object_unref(OBJECT(d)); - virtio_scsi_complete_req_from_main_loop(req); + virtio_scsi_complete_req(req, &s->ctrl_lock); } /* Some TMFs must be processed from the main loop thread */ @@ -408,7 +412,7 @@ static void virtio_scsi_reset_tmf_bh(VirtIOSCSI *s) /* SAM-6 6.3.2 Hard reset */ req->resp.tmf.response = VIRTIO_SCSI_S_TARGET_FAILURE; - virtio_scsi_complete_req(req); + virtio_scsi_complete_req(req, &req->dev->ctrl_lock); } } @@ -562,7 +566,7 @@ static void virtio_scsi_handle_ctrl_req(VirtIOSCSI *s, VirtIOSCSIReq *req) if (iov_to_buf(req->elem.out_sg, req->elem.out_num, 0, &type, sizeof(type)) < sizeof(type)) { - virtio_scsi_bad_req(req); + virtio_scsi_bad_req(req, &s->ctrl_lock); return; } @@ -570,7 +574,7 @@ static void virtio_scsi_handle_ctrl_req(VirtIOSCSI *s, VirtIOSCSIReq *req) if (type == VIRTIO_SCSI_T_TMF) { if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICtrlTMFReq), sizeof(VirtIOSCSICtrlTMFResp)) < 0) { - virtio_scsi_bad_req(req); + virtio_scsi_bad_req(req, &s->ctrl_lock); return; } else { r = virtio_scsi_do_tmf(s, req); @@ -580,7 +584,7 @@ static void virtio_scsi_handle_ctrl_req(VirtIOSCSI *s, VirtIOSCSIReq *req) type == VIRTIO_SCSI_T_AN_SUBSCRIBE) { if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICtrlANReq), sizeof(VirtIOSCSICtrlANResp)) < 0) { - virtio_scsi_bad_req(req); + virtio_scsi_bad_req(req, &s->ctrl_lock); return; } else { req->req.an.event_requested = @@ -600,7 +604,7 @@ static void virtio_scsi_handle_ctrl_req(VirtIOSCSI *s, VirtIOSCSIReq *req) type == VIRTIO_SCSI_T_AN_SUBSCRIBE) trace_virtio_scsi_an_resp(virtio_scsi_get_lun(req->req.an.lun), req->resp.an.response); - virtio_scsi_complete_req(req); + virtio_scsi_complete_req(req, &s->ctrl_lock); } else { assert(r == -EINPROGRESS); } @@ -610,7 +614,7 @@ static void virtio_scsi_handle_ctrl_vq(VirtIOSCSI *s, VirtQueue *vq) { VirtIOSCSIReq *req; - while ((req = virtio_scsi_pop_req(s, vq))) { + while ((req = virtio_scsi_pop_req(s, vq, &s->ctrl_lock))) { virtio_scsi_handle_ctrl_req(s, req); } } @@ -654,7 +658,7 @@ static void virtio_scsi_complete_cmd_req(VirtIOSCSIReq *req) * in virtio_scsi_command_complete. */ req->resp_size = sizeof(VirtIOSCSICmdResp); - virtio_scsi_complete_req(req); + virtio_scsi_complete_req(req, NULL); } static void virtio_scsi_command_failed(SCSIRequest *r) @@ -788,7 +792,7 @@ static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req) virtio_scsi_fail_cmd_req(req); return -ENOTSUP; } else { - virtio_scsi_bad_req(req); + virtio_scsi_bad_req(req, NULL); return -EINVAL; } } @@ -843,7 +847,7 @@ static void virtio_scsi_handle_cmd_vq(VirtIOSCSI *s, VirtQueue *vq) virtio_queue_set_notification(vq, 0); } - while ((req = virtio_scsi_pop_req(s, vq))) { + while ((req = virtio_scsi_pop_req(s, vq, NULL))) { ret = virtio_scsi_handle_cmd_req_prepare(s, req); if (!ret) { QTAILQ_INSERT_TAIL(&reqs, req, next); @@ -973,7 +977,7 @@ static void virtio_scsi_push_event(VirtIOSCSI *s, return; } - req = virtio_scsi_pop_req(s, vs->event_vq); + req = virtio_scsi_pop_req(s, vs->event_vq, &s->event_lock); if (!req) { s->events_dropped = true; return; @@ -985,7 +989,7 @@ static void virtio_scsi_push_event(VirtIOSCSI *s, } if (virtio_scsi_parse_req(req, 0, sizeof(VirtIOSCSIEvent))) { - virtio_scsi_bad_req(req); + virtio_scsi_bad_req(req, &s->event_lock); return; } @@ -1005,7 +1009,7 @@ static void virtio_scsi_push_event(VirtIOSCSI *s, } trace_virtio_scsi_event(virtio_scsi_get_lun(evt->lun), event, reason); - virtio_scsi_complete_req(req); + virtio_scsi_complete_req(req, &s->event_lock); } static void virtio_scsi_handle_event_vq(VirtIOSCSI *s, VirtQueue *vq) @@ -1236,6 +1240,8 @@ static void virtio_scsi_device_realize(DeviceState *dev, Error **errp) Error *err = NULL; QTAILQ_INIT(&s->tmf_bh_list); + qemu_mutex_init(&s->ctrl_lock); + qemu_mutex_init(&s->event_lock); qemu_mutex_init(&s->tmf_bh_lock); virtio_scsi_common_realize(dev, @@ -1280,6 +1286,8 @@ static void virtio_scsi_device_unrealize(DeviceState *dev) qbus_set_hotplug_handler(BUS(&s->bus), NULL); virtio_scsi_common_unrealize(dev); qemu_mutex_destroy(&s->tmf_bh_lock); + qemu_mutex_destroy(&s->event_lock); + qemu_mutex_destroy(&s->ctrl_lock); } static const Property virtio_scsi_properties[] = { diff --git a/include/hw/virtio/virtio-scsi.h b/include/hw/virtio/virtio-scsi.h index be230cd4bfcd..4ee98ebf6327 100644 --- a/include/hw/virtio/virtio-scsi.h +++ b/include/hw/virtio/virtio-scsi.h @@ -84,6 +84,9 @@ struct VirtIOSCSI { int resetting; /* written from main loop thread, read from any thread */ bool events_dropped; + QemuMutex ctrl_lock; /* protects ctrl_vq */ + QemuMutex event_lock; /* protects event_vq */ + /* * TMFs deferred to main loop BH. These fields are protected by * tmf_bh_lock. From 7d8ab5b2f77d84a21dbeb5b254e26320e1943af4 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:09 +0800 Subject: [PATCH 1009/1179] virtio-scsi: protect events_dropped field The block layer can invoke the resize callback from any AioContext that is processing requests. The virtqueue is already protected but the events_dropped field also needs to be protected against races. Cover it using the event virtqueue lock because it is closely associated with accesses to the virtqueue. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-7-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/virtio-scsi.c | 29 ++++++++++++++++++++--------- include/hw/virtio/virtio-scsi.h | 3 ++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index 073ccd3d5b47..2d796a861b49 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -948,7 +948,10 @@ static void virtio_scsi_reset(VirtIODevice *vdev) vs->sense_size = VIRTIO_SCSI_SENSE_DEFAULT_SIZE; vs->cdb_size = VIRTIO_SCSI_CDB_DEFAULT_SIZE; - s->events_dropped = false; + + WITH_QEMU_LOCK_GUARD(&s->event_lock) { + s->events_dropped = false; + } } typedef struct { @@ -978,14 +981,16 @@ static void virtio_scsi_push_event(VirtIOSCSI *s, } req = virtio_scsi_pop_req(s, vs->event_vq, &s->event_lock); - if (!req) { - s->events_dropped = true; - return; - } + WITH_QEMU_LOCK_GUARD(&s->event_lock) { + if (!req) { + s->events_dropped = true; + return; + } - if (s->events_dropped) { - event |= VIRTIO_SCSI_T_EVENTS_MISSED; - s->events_dropped = false; + if (s->events_dropped) { + event |= VIRTIO_SCSI_T_EVENTS_MISSED; + s->events_dropped = false; + } } if (virtio_scsi_parse_req(req, 0, sizeof(VirtIOSCSIEvent))) { @@ -1014,7 +1019,13 @@ static void virtio_scsi_push_event(VirtIOSCSI *s, static void virtio_scsi_handle_event_vq(VirtIOSCSI *s, VirtQueue *vq) { - if (s->events_dropped) { + bool events_dropped; + + WITH_QEMU_LOCK_GUARD(&s->event_lock) { + events_dropped = s->events_dropped; + } + + if (events_dropped) { VirtIOSCSIEventInfo info = { .event = VIRTIO_SCSI_T_NO_EVENT, }; diff --git a/include/hw/virtio/virtio-scsi.h b/include/hw/virtio/virtio-scsi.h index 4ee98ebf6327..7b7e3ced7af6 100644 --- a/include/hw/virtio/virtio-scsi.h +++ b/include/hw/virtio/virtio-scsi.h @@ -82,10 +82,11 @@ struct VirtIOSCSI { SCSIBus bus; int resetting; /* written from main loop thread, read from any thread */ + + QemuMutex event_lock; /* protects event_vq and events_dropped */ bool events_dropped; QemuMutex ctrl_lock; /* protects ctrl_vq */ - QemuMutex event_lock; /* protects event_vq */ /* * TMFs deferred to main loop BH. These fields are protected by From da6eebb33b08131d2dc7c2594f0998012fe69e2f Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:10 +0800 Subject: [PATCH 1010/1179] virtio-scsi: perform TMFs in appropriate AioContexts With IOThread Virtqueue Mapping there will be multiple AioContexts processing SCSI requests. scsi_req_cancel() and other SCSI request operations must be performed from the AioContext where the request is running. Introduce a virtio_scsi_defer_tmf_to_aio_context() function and the necessary VirtIOSCSIReq->remaining refcount infrastructure to move the TMF code into the AioContext where the request is running. For the time being there is still just one AioContext: the main loop or the IOThread. When the iothread-vq-mapping parameter is added in a later patch this will be changed to per-virtqueue AioContexts. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-8-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/virtio-scsi.c | 270 ++++++++++++++++++++++++++++++++---------- 1 file changed, 206 insertions(+), 64 deletions(-) diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index 2d796a861b49..2045d272893b 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -47,7 +47,7 @@ typedef struct VirtIOSCSIReq { /* Used for two-stage request submission and TMFs deferred to BH */ QTAILQ_ENTRY(VirtIOSCSIReq) next; - /* Used for cancellation of request during TMFs */ + /* Used for cancellation of request during TMFs. Atomic. */ int remaining; SCSIRequest *sreq; @@ -298,19 +298,23 @@ typedef struct { VirtIOSCSIReq *tmf_req; } VirtIOSCSICancelNotifier; +static void virtio_scsi_tmf_dec_remaining(VirtIOSCSIReq *tmf) +{ + if (qatomic_fetch_dec(&tmf->remaining) == 1) { + trace_virtio_scsi_tmf_resp(virtio_scsi_get_lun(tmf->req.tmf.lun), + tmf->req.tmf.tag, tmf->resp.tmf.response); + + virtio_scsi_complete_req(tmf, &tmf->dev->ctrl_lock); + } +} + static void virtio_scsi_cancel_notify(Notifier *notifier, void *data) { VirtIOSCSICancelNotifier *n = container_of(notifier, VirtIOSCSICancelNotifier, notifier); - if (--n->tmf_req->remaining == 0) { - VirtIOSCSIReq *req = n->tmf_req; - - trace_virtio_scsi_tmf_resp(virtio_scsi_get_lun(req->req.tmf.lun), - req->req.tmf.tag, req->resp.tmf.response); - virtio_scsi_complete_req(req, &req->dev->ctrl_lock); - } + virtio_scsi_tmf_dec_remaining(n->tmf_req); g_free(n); } @@ -416,7 +420,7 @@ static void virtio_scsi_reset_tmf_bh(VirtIOSCSI *s) } } -static void virtio_scsi_defer_tmf_to_bh(VirtIOSCSIReq *req) +static void virtio_scsi_defer_tmf_to_main_loop(VirtIOSCSIReq *req) { VirtIOSCSI *s = req->dev; @@ -430,6 +434,137 @@ static void virtio_scsi_defer_tmf_to_bh(VirtIOSCSIReq *req) } } +static void virtio_scsi_tmf_cancel_req(VirtIOSCSIReq *tmf, SCSIRequest *r) +{ + VirtIOSCSICancelNotifier *notifier; + + assert(r->ctx == qemu_get_current_aio_context()); + + /* Decremented in virtio_scsi_cancel_notify() */ + qatomic_inc(&tmf->remaining); + + notifier = g_new(VirtIOSCSICancelNotifier, 1); + notifier->notifier.notify = virtio_scsi_cancel_notify; + notifier->tmf_req = tmf; + scsi_req_cancel_async(r, ¬ifier->notifier); +} + +/* Execute a TMF on the requests in the current AioContext */ +static void virtio_scsi_do_tmf_aio_context(void *opaque) +{ + AioContext *ctx = qemu_get_current_aio_context(); + VirtIOSCSIReq *tmf = opaque; + VirtIOSCSI *s = tmf->dev; + SCSIDevice *d = virtio_scsi_device_get(s, tmf->req.tmf.lun); + SCSIRequest *r; + bool match_tag; + + if (!d) { + tmf->resp.tmf.response = VIRTIO_SCSI_S_BAD_TARGET; + virtio_scsi_tmf_dec_remaining(tmf); + return; + } + + /* + * This function could handle other subtypes that need to be processed in + * the request's AioContext in the future, but for now only request + * cancelation subtypes are performed here. + */ + switch (tmf->req.tmf.subtype) { + case VIRTIO_SCSI_T_TMF_ABORT_TASK: + match_tag = true; + break; + case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET: + case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: + match_tag = false; + break; + default: + g_assert_not_reached(); + } + + WITH_QEMU_LOCK_GUARD(&d->requests_lock) { + QTAILQ_FOREACH(r, &d->requests, next) { + VirtIOSCSIReq *cmd_req = r->hba_private; + assert(cmd_req); /* request has hba_private while enqueued */ + + if (r->ctx != ctx) { + continue; + } + if (match_tag && cmd_req->req.cmd.tag != tmf->req.tmf.tag) { + continue; + } + virtio_scsi_tmf_cancel_req(tmf, r); + } + } + + /* Incremented by virtio_scsi_do_tmf() */ + virtio_scsi_tmf_dec_remaining(tmf); + + object_unref(d); +} + +static void dummy_bh(void *opaque) +{ + /* Do nothing */ +} + +/* + * Wait for pending virtio_scsi_defer_tmf_to_aio_context() BHs. + */ +static void virtio_scsi_flush_defer_tmf_to_aio_context(VirtIOSCSI *s) +{ + GLOBAL_STATE_CODE(); + + assert(!s->dataplane_started); + + if (s->ctx) { + /* Our BH only runs after previously scheduled BHs */ + aio_wait_bh_oneshot(s->ctx, dummy_bh, NULL); + } +} + +/* + * Run the TMF in a specific AioContext, handling only requests in that + * AioContext. This is necessary because requests can run in different + * AioContext and it is only possible to cancel them from the AioContext where + * they are running. + */ +static void virtio_scsi_defer_tmf_to_aio_context(VirtIOSCSIReq *tmf, + AioContext *ctx) +{ + /* Decremented in virtio_scsi_do_tmf_aio_context() */ + qatomic_inc(&tmf->remaining); + + /* See virtio_scsi_flush_defer_tmf_to_aio_context() cleanup during reset */ + aio_bh_schedule_oneshot(ctx, virtio_scsi_do_tmf_aio_context, tmf); +} + +/* + * Returns the AioContext for a given TMF's tag field or NULL. Note that the + * request identified by the tag may have completed by the time you can execute + * a BH in the AioContext, so don't assume the request still exists in your BH. + */ +static AioContext *find_aio_context_for_tmf_tag(SCSIDevice *d, + VirtIOSCSIReq *tmf) +{ + WITH_QEMU_LOCK_GUARD(&d->requests_lock) { + SCSIRequest *r; + SCSIRequest *next; + + QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) { + VirtIOSCSIReq *cmd_req = r->hba_private; + + /* hba_private is non-NULL while the request is enqueued */ + assert(cmd_req); + + if (cmd_req->req.cmd.tag == tmf->req.tmf.tag) { + return r->ctx; + } + } + } + return NULL; +} + /* Return 0 if the request is ready to be completed and return to guest; * -EINPROGRESS if the request is submitted and will be completed later, in the * case of async cancellation. */ @@ -437,6 +572,7 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) { SCSIDevice *d = virtio_scsi_device_get(s, req->req.tmf.lun); SCSIRequest *r, *next; + AioContext *ctx; int ret = 0; virtio_scsi_ctx_check(s, d); @@ -454,52 +590,72 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) req->req.tmf.tag, req->req.tmf.subtype); switch (req->req.tmf.subtype) { - case VIRTIO_SCSI_T_TMF_ABORT_TASK: - case VIRTIO_SCSI_T_TMF_QUERY_TASK: + case VIRTIO_SCSI_T_TMF_ABORT_TASK: { if (!d) { goto fail; } if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) { goto incorrect_lun; } - QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) { - VirtIOSCSIReq *cmd_req = r->hba_private; - if (cmd_req && cmd_req->req.cmd.tag == req->req.tmf.tag) { - break; - } + + ctx = find_aio_context_for_tmf_tag(d, req); + if (ctx) { + virtio_scsi_defer_tmf_to_aio_context(req, ctx); + ret = -EINPROGRESS; } - if (r) { - /* - * Assert that the request has not been completed yet, we - * check for it in the loop above. - */ - assert(r->hba_private); - if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK) { - /* "If the specified command is present in the task set, then - * return a service response set to FUNCTION SUCCEEDED". - */ - req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED; - } else { - VirtIOSCSICancelNotifier *notifier; - - req->remaining = 1; - notifier = g_new(VirtIOSCSICancelNotifier, 1); - notifier->tmf_req = req; - notifier->notifier.notify = virtio_scsi_cancel_notify; - scsi_req_cancel_async(r, ¬ifier->notifier); - ret = -EINPROGRESS; + break; + } + + case VIRTIO_SCSI_T_TMF_QUERY_TASK: + if (!d) { + goto fail; + } + if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) { + goto incorrect_lun; + } + + WITH_QEMU_LOCK_GUARD(&d->requests_lock) { + QTAILQ_FOREACH(r, &d->requests, next) { + VirtIOSCSIReq *cmd_req = r->hba_private; + assert(cmd_req); /* request has hba_private while enqueued */ + + if (cmd_req->req.cmd.tag == req->req.tmf.tag) { + /* + * "If the specified command is present in the task set, + * then return a service response set to FUNCTION + * SUCCEEDED". + */ + req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED; + } } } break; case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET: case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET: - virtio_scsi_defer_tmf_to_bh(req); + virtio_scsi_defer_tmf_to_main_loop(req); ret = -EINPROGRESS; break; case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET: - case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: + case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: { + if (!d) { + goto fail; + } + if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) { + goto incorrect_lun; + } + + qatomic_inc(&req->remaining); + + ctx = s->ctx ?: qemu_get_aio_context(); + virtio_scsi_defer_tmf_to_aio_context(req, ctx); + + virtio_scsi_tmf_dec_remaining(req); + ret = -EINPROGRESS; + break; + } + case VIRTIO_SCSI_T_TMF_QUERY_TASK_SET: if (!d) { goto fail; @@ -508,34 +664,19 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) goto incorrect_lun; } - /* Add 1 to "remaining" until virtio_scsi_do_tmf returns. - * This way, if the bus starts calling back to the notifiers - * even before we finish the loop, virtio_scsi_cancel_notify - * will not complete the TMF too early. - */ - req->remaining = 1; - QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) { - if (r->hba_private) { - if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK_SET) { - /* "If there is any command present in the task set, then - * return a service response set to FUNCTION SUCCEEDED". - */ - req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED; - break; - } else { - VirtIOSCSICancelNotifier *notifier; - - req->remaining++; - notifier = g_new(VirtIOSCSICancelNotifier, 1); - notifier->notifier.notify = virtio_scsi_cancel_notify; - notifier->tmf_req = req; - scsi_req_cancel_async(r, ¬ifier->notifier); - } + WITH_QEMU_LOCK_GUARD(&d->requests_lock) { + QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) { + /* Request has hba_private while enqueued */ + assert(r->hba_private); + + /* + * "If there is any command present in the task set, then + * return a service response set to FUNCTION SUCCEEDED". + */ + req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED; + break; } } - if (--req->remaining > 0) { - ret = -EINPROGRESS; - } break; case VIRTIO_SCSI_T_TMF_CLEAR_ACA: @@ -941,6 +1082,7 @@ static void virtio_scsi_reset(VirtIODevice *vdev) assert(!s->dataplane_started); virtio_scsi_reset_tmf_bh(s); + virtio_scsi_flush_defer_tmf_to_aio_context(s); qatomic_inc(&s->resetting); bus_cold_reset(BUS(&s->bus)); From 366b5811d6170f4ed74329a60dc77b1633e13798 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:11 +0800 Subject: [PATCH 1011/1179] virtio-blk: extract cleanup_iothread_vq_mapping() function This is the cleanup function that must be called after apply_iothread_vq_mapping() succeeds. virtio-scsi will need this function too, so extract it. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-9-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/block/virtio-blk.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index 5135b4d8f1e2..21b1b768ed6c 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -1495,6 +1495,9 @@ validate_iothread_vq_mapping_list(IOThreadVirtQueueMappingList *list, * Fill in the AioContext for each virtqueue in the @vq_aio_context array given * the iothread-vq-mapping parameter in @iothread_vq_mapping_list. * + * cleanup_iothread_vq_mapping() must be called to free IOThread object + * references after this function returns success. + * * Returns: %true on success, %false on failure. **/ static bool apply_iothread_vq_mapping( @@ -1545,6 +1548,23 @@ static bool apply_iothread_vq_mapping( return true; } +/** + * cleanup_iothread_vq_mapping: + * @list: The mapping of virtqueues to IOThreads. + * + * Release IOThread object references that were acquired by + * apply_iothread_vq_mapping(). + */ +static void cleanup_iothread_vq_mapping(IOThreadVirtQueueMappingList *list) +{ + IOThreadVirtQueueMappingList *node; + + for (node = list; node; node = node->next) { + IOThread *iothread = iothread_by_id(node->value->iothread); + object_unref(OBJECT(iothread)); + } +} + /* Context: BQL held */ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp) { @@ -1611,12 +1631,7 @@ static void virtio_blk_vq_aio_context_cleanup(VirtIOBlock *s) assert(!s->ioeventfd_started); if (conf->iothread_vq_mapping_list) { - IOThreadVirtQueueMappingList *node; - - for (node = conf->iothread_vq_mapping_list; node; node = node->next) { - IOThread *iothread = iothread_by_id(node->value->iothread); - object_unref(OBJECT(iothread)); - } + cleanup_iothread_vq_mapping(conf->iothread_vq_mapping_list); } if (conf->iothread) { From 2fa67a7b1d7957dc0cc482136ba58c460463ecb6 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:12 +0800 Subject: [PATCH 1012/1179] virtio-blk: tidy up iothread_vq_mapping functions Use noun_verb() function naming instead of verb_noun() because the former is the most common naming style for APIs. The next commit will move these functions into a header file so that virtio-scsi can call them. Shorten iothread_vq_mapping_apply()'s iothread_vq_mapping_list argument to just "list" like in the other functions. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-10-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/block/virtio-blk.c | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index 21b1b768ed6c..6bf7b5052079 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -1424,8 +1424,8 @@ static const BlockDevOps virtio_block_ops = { }; static bool -validate_iothread_vq_mapping_list(IOThreadVirtQueueMappingList *list, - uint16_t num_queues, Error **errp) +iothread_vq_mapping_validate(IOThreadVirtQueueMappingList *list, uint16_t + num_queues, Error **errp) { g_autofree unsigned long *vqs = bitmap_new(num_queues); g_autoptr(GHashTable) iothreads = @@ -1486,22 +1486,22 @@ validate_iothread_vq_mapping_list(IOThreadVirtQueueMappingList *list, } /** - * apply_iothread_vq_mapping: - * @iothread_vq_mapping_list: The mapping of virtqueues to IOThreads. + * iothread_vq_mapping_apply: + * @list: The mapping of virtqueues to IOThreads. * @vq_aio_context: The array of AioContext pointers to fill in. * @num_queues: The length of @vq_aio_context. * @errp: If an error occurs, a pointer to the area to store the error. * * Fill in the AioContext for each virtqueue in the @vq_aio_context array given - * the iothread-vq-mapping parameter in @iothread_vq_mapping_list. + * the iothread-vq-mapping parameter in @list. * - * cleanup_iothread_vq_mapping() must be called to free IOThread object + * iothread_vq_mapping_cleanup() must be called to free IOThread object * references after this function returns success. * * Returns: %true on success, %false on failure. **/ -static bool apply_iothread_vq_mapping( - IOThreadVirtQueueMappingList *iothread_vq_mapping_list, +static bool iothread_vq_mapping_apply( + IOThreadVirtQueueMappingList *list, AioContext **vq_aio_context, uint16_t num_queues, Error **errp) @@ -1510,16 +1510,15 @@ static bool apply_iothread_vq_mapping( size_t num_iothreads = 0; size_t cur_iothread = 0; - if (!validate_iothread_vq_mapping_list(iothread_vq_mapping_list, - num_queues, errp)) { + if (!iothread_vq_mapping_validate(list, num_queues, errp)) { return false; } - for (node = iothread_vq_mapping_list; node; node = node->next) { + for (node = list; node; node = node->next) { num_iothreads++; } - for (node = iothread_vq_mapping_list; node; node = node->next) { + for (node = list; node; node = node->next) { IOThread *iothread = iothread_by_id(node->value->iothread); AioContext *ctx = iothread_get_aio_context(iothread); @@ -1549,13 +1548,13 @@ static bool apply_iothread_vq_mapping( } /** - * cleanup_iothread_vq_mapping: + * iothread_vq_mapping_cleanup: * @list: The mapping of virtqueues to IOThreads. * * Release IOThread object references that were acquired by - * apply_iothread_vq_mapping(). + * iothread_vq_mapping_apply(). */ -static void cleanup_iothread_vq_mapping(IOThreadVirtQueueMappingList *list) +static void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list) { IOThreadVirtQueueMappingList *node; @@ -1597,7 +1596,7 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp) s->vq_aio_context = g_new(AioContext *, conf->num_queues); if (conf->iothread_vq_mapping_list) { - if (!apply_iothread_vq_mapping(conf->iothread_vq_mapping_list, + if (!iothread_vq_mapping_apply(conf->iothread_vq_mapping_list, s->vq_aio_context, conf->num_queues, errp)) { @@ -1631,7 +1630,7 @@ static void virtio_blk_vq_aio_context_cleanup(VirtIOBlock *s) assert(!s->ioeventfd_started); if (conf->iothread_vq_mapping_list) { - cleanup_iothread_vq_mapping(conf->iothread_vq_mapping_list); + iothread_vq_mapping_cleanup(conf->iothread_vq_mapping_list); } if (conf->iothread) { From b50629c335804e193b51936867d6cb7ea3735d72 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:13 +0800 Subject: [PATCH 1013/1179] virtio: extract iothread-vq-mapping.h API The code that builds an array of AioContext pointers indexed by the virtqueue is not specific to virtio-blk. virtio-scsi will need to do the same thing, so extract the functions. Signed-off-by: Stefan Hajnoczi Reviewed-by: Kevin Wolf Message-ID: <20250311132616.1049687-11-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/block/virtio-blk.c | 142 +----------------------- hw/virtio/iothread-vq-mapping.c | 131 ++++++++++++++++++++++ hw/virtio/meson.build | 1 + include/hw/virtio/iothread-vq-mapping.h | 45 ++++++++ 4 files changed, 178 insertions(+), 141 deletions(-) create mode 100644 hw/virtio/iothread-vq-mapping.c create mode 100644 include/hw/virtio/iothread-vq-mapping.h diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index 6bf7b5052079..5077793e5e33 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -33,6 +33,7 @@ #endif #include "hw/virtio/virtio-bus.h" #include "migration/qemu-file-types.h" +#include "hw/virtio/iothread-vq-mapping.h" #include "hw/virtio/virtio-access.h" #include "hw/virtio/virtio-blk-common.h" #include "qemu/coroutine.h" @@ -1423,147 +1424,6 @@ static const BlockDevOps virtio_block_ops = { .drained_end = virtio_blk_drained_end, }; -static bool -iothread_vq_mapping_validate(IOThreadVirtQueueMappingList *list, uint16_t - num_queues, Error **errp) -{ - g_autofree unsigned long *vqs = bitmap_new(num_queues); - g_autoptr(GHashTable) iothreads = - g_hash_table_new(g_str_hash, g_str_equal); - - for (IOThreadVirtQueueMappingList *node = list; node; node = node->next) { - const char *name = node->value->iothread; - uint16List *vq; - - if (!iothread_by_id(name)) { - error_setg(errp, "IOThread \"%s\" object does not exist", name); - return false; - } - - if (!g_hash_table_add(iothreads, (gpointer)name)) { - error_setg(errp, - "duplicate IOThread name \"%s\" in iothread-vq-mapping", - name); - return false; - } - - if (node != list) { - if (!!node->value->vqs != !!list->value->vqs) { - error_setg(errp, "either all items in iothread-vq-mapping " - "must have vqs or none of them must have it"); - return false; - } - } - - for (vq = node->value->vqs; vq; vq = vq->next) { - if (vq->value >= num_queues) { - error_setg(errp, "vq index %u for IOThread \"%s\" must be " - "less than num_queues %u in iothread-vq-mapping", - vq->value, name, num_queues); - return false; - } - - if (test_and_set_bit(vq->value, vqs)) { - error_setg(errp, "cannot assign vq %u to IOThread \"%s\" " - "because it is already assigned", vq->value, name); - return false; - } - } - } - - if (list->value->vqs) { - for (uint16_t i = 0; i < num_queues; i++) { - if (!test_bit(i, vqs)) { - error_setg(errp, - "missing vq %u IOThread assignment in iothread-vq-mapping", - i); - return false; - } - } - } - - return true; -} - -/** - * iothread_vq_mapping_apply: - * @list: The mapping of virtqueues to IOThreads. - * @vq_aio_context: The array of AioContext pointers to fill in. - * @num_queues: The length of @vq_aio_context. - * @errp: If an error occurs, a pointer to the area to store the error. - * - * Fill in the AioContext for each virtqueue in the @vq_aio_context array given - * the iothread-vq-mapping parameter in @list. - * - * iothread_vq_mapping_cleanup() must be called to free IOThread object - * references after this function returns success. - * - * Returns: %true on success, %false on failure. - **/ -static bool iothread_vq_mapping_apply( - IOThreadVirtQueueMappingList *list, - AioContext **vq_aio_context, - uint16_t num_queues, - Error **errp) -{ - IOThreadVirtQueueMappingList *node; - size_t num_iothreads = 0; - size_t cur_iothread = 0; - - if (!iothread_vq_mapping_validate(list, num_queues, errp)) { - return false; - } - - for (node = list; node; node = node->next) { - num_iothreads++; - } - - for (node = list; node; node = node->next) { - IOThread *iothread = iothread_by_id(node->value->iothread); - AioContext *ctx = iothread_get_aio_context(iothread); - - /* Released in virtio_blk_vq_aio_context_cleanup() */ - object_ref(OBJECT(iothread)); - - if (node->value->vqs) { - uint16List *vq; - - /* Explicit vq:IOThread assignment */ - for (vq = node->value->vqs; vq; vq = vq->next) { - assert(vq->value < num_queues); - vq_aio_context[vq->value] = ctx; - } - } else { - /* Round-robin vq:IOThread assignment */ - for (unsigned i = cur_iothread; i < num_queues; - i += num_iothreads) { - vq_aio_context[i] = ctx; - } - } - - cur_iothread++; - } - - return true; -} - -/** - * iothread_vq_mapping_cleanup: - * @list: The mapping of virtqueues to IOThreads. - * - * Release IOThread object references that were acquired by - * iothread_vq_mapping_apply(). - */ -static void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list) -{ - IOThreadVirtQueueMappingList *node; - - for (node = list; node; node = node->next) { - IOThread *iothread = iothread_by_id(node->value->iothread); - object_unref(OBJECT(iothread)); - } -} - /* Context: BQL held */ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp) { diff --git a/hw/virtio/iothread-vq-mapping.c b/hw/virtio/iothread-vq-mapping.c new file mode 100644 index 000000000000..15909eb9332a --- /dev/null +++ b/hw/virtio/iothread-vq-mapping.c @@ -0,0 +1,131 @@ +/* + * IOThread Virtqueue Mapping + * + * Copyright Red Hat, Inc + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include "qemu/osdep.h" +#include "system/iothread.h" +#include "hw/virtio/iothread-vq-mapping.h" + +static bool +iothread_vq_mapping_validate(IOThreadVirtQueueMappingList *list, uint16_t + num_queues, Error **errp) +{ + g_autofree unsigned long *vqs = bitmap_new(num_queues); + g_autoptr(GHashTable) iothreads = + g_hash_table_new(g_str_hash, g_str_equal); + + for (IOThreadVirtQueueMappingList *node = list; node; node = node->next) { + const char *name = node->value->iothread; + uint16List *vq; + + if (!iothread_by_id(name)) { + error_setg(errp, "IOThread \"%s\" object does not exist", name); + return false; + } + + if (!g_hash_table_add(iothreads, (gpointer)name)) { + error_setg(errp, + "duplicate IOThread name \"%s\" in iothread-vq-mapping", + name); + return false; + } + + if (node != list) { + if (!!node->value->vqs != !!list->value->vqs) { + error_setg(errp, "either all items in iothread-vq-mapping " + "must have vqs or none of them must have it"); + return false; + } + } + + for (vq = node->value->vqs; vq; vq = vq->next) { + if (vq->value >= num_queues) { + error_setg(errp, "vq index %u for IOThread \"%s\" must be " + "less than num_queues %u in iothread-vq-mapping", + vq->value, name, num_queues); + return false; + } + + if (test_and_set_bit(vq->value, vqs)) { + error_setg(errp, "cannot assign vq %u to IOThread \"%s\" " + "because it is already assigned", vq->value, name); + return false; + } + } + } + + if (list->value->vqs) { + for (uint16_t i = 0; i < num_queues; i++) { + if (!test_bit(i, vqs)) { + error_setg(errp, + "missing vq %u IOThread assignment in iothread-vq-mapping", + i); + return false; + } + } + } + + return true; +} + +bool iothread_vq_mapping_apply( + IOThreadVirtQueueMappingList *list, + AioContext **vq_aio_context, + uint16_t num_queues, + Error **errp) +{ + IOThreadVirtQueueMappingList *node; + size_t num_iothreads = 0; + size_t cur_iothread = 0; + + if (!iothread_vq_mapping_validate(list, num_queues, errp)) { + return false; + } + + for (node = list; node; node = node->next) { + num_iothreads++; + } + + for (node = list; node; node = node->next) { + IOThread *iothread = iothread_by_id(node->value->iothread); + AioContext *ctx = iothread_get_aio_context(iothread); + + /* Released in virtio_blk_vq_aio_context_cleanup() */ + object_ref(OBJECT(iothread)); + + if (node->value->vqs) { + uint16List *vq; + + /* Explicit vq:IOThread assignment */ + for (vq = node->value->vqs; vq; vq = vq->next) { + assert(vq->value < num_queues); + vq_aio_context[vq->value] = ctx; + } + } else { + /* Round-robin vq:IOThread assignment */ + for (unsigned i = cur_iothread; i < num_queues; + i += num_iothreads) { + vq_aio_context[i] = ctx; + } + } + + cur_iothread++; + } + + return true; +} + +void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list) +{ + IOThreadVirtQueueMappingList *node; + + for (node = list; node; node = node->next) { + IOThread *iothread = iothread_by_id(node->value->iothread); + object_unref(OBJECT(iothread)); + } +} + diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index a5f9f7999dde..19b04c4d9ccb 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -1,5 +1,6 @@ system_virtio_ss = ss.source_set() system_virtio_ss.add(files('virtio-bus.c')) +system_virtio_ss.add(files('iothread-vq-mapping.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_PCI', if_true: files('virtio-pci.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_MMIO', if_true: files('virtio-mmio.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('virtio-crypto.c')) diff --git a/include/hw/virtio/iothread-vq-mapping.h b/include/hw/virtio/iothread-vq-mapping.h new file mode 100644 index 000000000000..57335c3703cc --- /dev/null +++ b/include/hw/virtio/iothread-vq-mapping.h @@ -0,0 +1,45 @@ +/* + * IOThread Virtqueue Mapping + * + * Copyright Red Hat, Inc + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#ifndef HW_VIRTIO_IOTHREAD_VQ_MAPPING_H +#define HW_VIRTIO_IOTHREAD_VQ_MAPPING_H + +#include "qapi/error.h" +#include "qapi/qapi-types-virtio.h" + +/** + * iothread_vq_mapping_apply: + * @list: The mapping of virtqueues to IOThreads. + * @vq_aio_context: The array of AioContext pointers to fill in. + * @num_queues: The length of @vq_aio_context. + * @errp: If an error occurs, a pointer to the area to store the error. + * + * Fill in the AioContext for each virtqueue in the @vq_aio_context array given + * the iothread-vq-mapping parameter in @list. + * + * iothread_vq_mapping_cleanup() must be called to free IOThread object + * references after this function returns success. + * + * Returns: %true on success, %false on failure. + **/ +bool iothread_vq_mapping_apply( + IOThreadVirtQueueMappingList *list, + AioContext **vq_aio_context, + uint16_t num_queues, + Error **errp); + +/** + * iothread_vq_mapping_cleanup: + * @list: The mapping of virtqueues to IOThreads. + * + * Release IOThread object references that were acquired by + * iothread_vq_mapping_apply(). + */ +void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list); + +#endif /* HW_VIRTIO_IOTHREAD_VQ_MAPPING_H */ From 2e8e18c2e46307a355e547129b5a7a7000a0cf0d Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:14 +0800 Subject: [PATCH 1014/1179] virtio-scsi: add iothread-vq-mapping parameter Allow virtio-scsi virtqueues to be assigned to different IOThreads. This makes it possible to take advantage of host multi-queue block layer scalability by assigning virtqueues that have affinity with vCPUs to different IOThreads that have affinity with host CPUs. The same feature was introduced for virtio-blk in the past: https://developers.redhat.com/articles/2024/09/05/scaling-virtio-blk-disk-io-iothread-virtqueue-mapping Here are fio randread 4k iodepth=64 results from a 4 vCPU guest with an Intel P4800X SSD: iothreads IOPS ------------------------------ 1 189576 2 312698 4 346744 Signed-off-by: Stefan Hajnoczi Message-ID: <20250311132616.1049687-12-stefanha@redhat.com> Tested-by: Peter Krempa [kwolf: Updated 051 output, virtio-scsi can now use any iothread] Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- hw/scsi/virtio-scsi-dataplane.c | 90 ++++++++++++++++++++++++--------- hw/scsi/virtio-scsi.c | 63 ++++++++++++++--------- include/hw/virtio/virtio-scsi.h | 5 +- tests/qemu-iotests/051.pc.out | 2 +- 4 files changed, 108 insertions(+), 52 deletions(-) diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c index f49ab98eccfd..6bb368c8a5d6 100644 --- a/hw/scsi/virtio-scsi-dataplane.c +++ b/hw/scsi/virtio-scsi-dataplane.c @@ -18,6 +18,7 @@ #include "system/block-backend.h" #include "hw/scsi/scsi.h" #include "scsi/constants.h" +#include "hw/virtio/iothread-vq-mapping.h" #include "hw/virtio/virtio-bus.h" /* Context: BQL held */ @@ -27,8 +28,16 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp) VirtIODevice *vdev = VIRTIO_DEVICE(s); BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); + uint16_t num_vqs = vs->conf.num_queues + VIRTIO_SCSI_VQ_NUM_FIXED; - if (vs->conf.iothread) { + if (vs->conf.iothread && vs->conf.iothread_vq_mapping_list) { + error_setg(errp, + "iothread and iothread-vq-mapping properties cannot be set " + "at the same time"); + return; + } + + if (vs->conf.iothread || vs->conf.iothread_vq_mapping_list) { if (!k->set_guest_notifiers || !k->ioeventfd_assign) { error_setg(errp, "device is incompatible with iothread " @@ -39,13 +48,48 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp) error_setg(errp, "ioeventfd is required for iothread"); return; } - s->ctx = iothread_get_aio_context(vs->conf.iothread); - } else { - if (!virtio_device_ioeventfd_enabled(vdev)) { + } + + s->vq_aio_context = g_new(AioContext *, num_vqs); + + if (vs->conf.iothread_vq_mapping_list) { + if (!iothread_vq_mapping_apply(vs->conf.iothread_vq_mapping_list, + s->vq_aio_context, num_vqs, errp)) { + g_free(s->vq_aio_context); + s->vq_aio_context = NULL; return; } - s->ctx = qemu_get_aio_context(); + } else if (vs->conf.iothread) { + AioContext *ctx = iothread_get_aio_context(vs->conf.iothread); + for (uint16_t i = 0; i < num_vqs; i++) { + s->vq_aio_context[i] = ctx; + } + + /* Released in virtio_scsi_dataplane_cleanup() */ + object_ref(OBJECT(vs->conf.iothread)); + } else { + AioContext *ctx = qemu_get_aio_context(); + for (unsigned i = 0; i < num_vqs; i++) { + s->vq_aio_context[i] = ctx; + } + } +} + +/* Context: BQL held */ +void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s) +{ + VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s); + + if (vs->conf.iothread_vq_mapping_list) { + iothread_vq_mapping_cleanup(vs->conf.iothread_vq_mapping_list); } + + if (vs->conf.iothread) { + object_unref(OBJECT(vs->conf.iothread)); + } + + g_free(s->vq_aio_context); + s->vq_aio_context = NULL; } static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n) @@ -66,31 +110,20 @@ static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n) } /* Context: BH in IOThread */ -static void virtio_scsi_dataplane_stop_bh(void *opaque) +static void virtio_scsi_dataplane_stop_vq_bh(void *opaque) { - VirtIOSCSI *s = opaque; - VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s); + AioContext *ctx = qemu_get_current_aio_context(); + VirtQueue *vq = opaque; EventNotifier *host_notifier; - int i; - virtio_queue_aio_detach_host_notifier(vs->ctrl_vq, s->ctx); - host_notifier = virtio_queue_get_host_notifier(vs->ctrl_vq); + virtio_queue_aio_detach_host_notifier(vq, ctx); + host_notifier = virtio_queue_get_host_notifier(vq); /* * Test and clear notifier after disabling event, in case poll callback * didn't have time to run. */ virtio_queue_host_notifier_read(host_notifier); - - virtio_queue_aio_detach_host_notifier(vs->event_vq, s->ctx); - host_notifier = virtio_queue_get_host_notifier(vs->event_vq); - virtio_queue_host_notifier_read(host_notifier); - - for (i = 0; i < vs->conf.num_queues; i++) { - virtio_queue_aio_detach_host_notifier(vs->cmd_vqs[i], s->ctx); - host_notifier = virtio_queue_get_host_notifier(vs->cmd_vqs[i]); - virtio_queue_host_notifier_read(host_notifier); - } } /* Context: BQL held */ @@ -154,11 +187,14 @@ int virtio_scsi_dataplane_start(VirtIODevice *vdev) smp_wmb(); /* paired with aio_notify_accept() */ if (s->bus.drain_count == 0) { - virtio_queue_aio_attach_host_notifier(vs->ctrl_vq, s->ctx); - virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq, s->ctx); + virtio_queue_aio_attach_host_notifier(vs->ctrl_vq, + s->vq_aio_context[0]); + virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq, + s->vq_aio_context[1]); for (i = 0; i < vs->conf.num_queues; i++) { - virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], s->ctx); + AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i]; + virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], ctx); } } return 0; @@ -207,7 +243,11 @@ void virtio_scsi_dataplane_stop(VirtIODevice *vdev) s->dataplane_stopping = true; if (s->bus.drain_count == 0) { - aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s); + for (i = 0; i < vs->conf.num_queues + VIRTIO_SCSI_VQ_NUM_FIXED; i++) { + VirtQueue *vq = virtio_get_queue(&vs->parent_obj, i); + AioContext *ctx = s->vq_aio_context[i]; + aio_wait_bh_oneshot(ctx, virtio_scsi_dataplane_stop_vq_bh, vq); + } } blk_drain_all(); /* ensure there are no in-flight requests */ diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index 2045d272893b..e7d97036083d 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -27,6 +27,7 @@ #include "hw/qdev-properties.h" #include "hw/scsi/scsi.h" #include "scsi/constants.h" +#include "hw/virtio/iothread-vq-mapping.h" #include "hw/virtio/virtio-bus.h" #include "hw/virtio/virtio-access.h" #include "trace.h" @@ -318,13 +319,6 @@ static void virtio_scsi_cancel_notify(Notifier *notifier, void *data) g_free(n); } -static inline void virtio_scsi_ctx_check(VirtIOSCSI *s, SCSIDevice *d) -{ - if (s->dataplane_started && d && blk_is_available(d->conf.blk)) { - assert(blk_get_aio_context(d->conf.blk) == s->ctx); - } -} - static void virtio_scsi_do_one_tmf_bh(VirtIOSCSIReq *req) { VirtIOSCSI *s = req->dev; @@ -517,9 +511,11 @@ static void virtio_scsi_flush_defer_tmf_to_aio_context(VirtIOSCSI *s) assert(!s->dataplane_started); - if (s->ctx) { + for (uint32_t i = 0; i < s->parent_obj.conf.num_queues; i++) { + AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i]; + /* Our BH only runs after previously scheduled BHs */ - aio_wait_bh_oneshot(s->ctx, dummy_bh, NULL); + aio_wait_bh_oneshot(ctx, dummy_bh, NULL); } } @@ -575,7 +571,6 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) AioContext *ctx; int ret = 0; - virtio_scsi_ctx_check(s, d); /* Here VIRTIO_SCSI_S_OK means "FUNCTION COMPLETE". */ req->resp.tmf.response = VIRTIO_SCSI_S_OK; @@ -639,6 +634,8 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET: case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: { + g_autoptr(GHashTable) aio_contexts = g_hash_table_new(NULL, NULL); + if (!d) { goto fail; } @@ -648,8 +645,15 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) qatomic_inc(&req->remaining); - ctx = s->ctx ?: qemu_get_aio_context(); - virtio_scsi_defer_tmf_to_aio_context(req, ctx); + for (uint32_t i = 0; i < s->parent_obj.conf.num_queues; i++) { + ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i]; + + if (!g_hash_table_add(aio_contexts, ctx)) { + continue; /* skip previously added AioContext */ + } + + virtio_scsi_defer_tmf_to_aio_context(req, ctx); + } virtio_scsi_tmf_dec_remaining(req); ret = -EINPROGRESS; @@ -770,9 +774,12 @@ static void virtio_scsi_handle_ctrl_vq(VirtIOSCSI *s, VirtQueue *vq) */ static bool virtio_scsi_defer_to_dataplane(VirtIOSCSI *s) { - if (!s->ctx || s->dataplane_started) { + if (s->dataplane_started) { return false; } + if (s->vq_aio_context[0] == qemu_get_aio_context()) { + return false; /* not using IOThreads */ + } virtio_device_start_ioeventfd(&s->parent_obj.parent_obj); return !s->dataplane_fenced; @@ -946,7 +953,6 @@ static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req) virtio_scsi_complete_cmd_req(req); return -ENOENT; } - virtio_scsi_ctx_check(s, d); req->sreq = scsi_req_new(d, req->req.cmd.tag, virtio_scsi_get_lun(req->req.cmd.lun), req->req.cmd.cdb, vs->cdb_size, req); @@ -1218,14 +1224,16 @@ static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, { VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev); VirtIOSCSI *s = VIRTIO_SCSI(vdev); + AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED]; SCSIDevice *sd = SCSI_DEVICE(dev); - int ret; - if (s->ctx && !s->dataplane_fenced) { - ret = blk_set_aio_context(sd->conf.blk, s->ctx, errp); - if (ret < 0) { - return; - } + if (ctx != qemu_get_aio_context() && !s->dataplane_fenced) { + /* + * Try to make the BlockBackend's AioContext match ours. Ignore failure + * because I/O will still work although block jobs and other users + * might be slower when multiple AioContexts use a BlockBackend. + */ + blk_set_aio_context(sd->conf.blk, ctx, NULL); } if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_HOTPLUG)) { @@ -1260,7 +1268,7 @@ static void virtio_scsi_hotunplug(HotplugHandler *hotplug_dev, DeviceState *dev, qdev_simple_device_unplug_cb(hotplug_dev, dev, errp); - if (s->ctx) { + if (s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED] != qemu_get_aio_context()) { /* If other users keep the BlockBackend in the iothread, that's ok */ blk_set_aio_context(sd->conf.blk, qemu_get_aio_context(), NULL); } @@ -1294,7 +1302,7 @@ static void virtio_scsi_drained_begin(SCSIBus *bus) for (uint32_t i = 0; i < total_queues; i++) { VirtQueue *vq = virtio_get_queue(vdev, i); - virtio_queue_aio_detach_host_notifier(vq, s->ctx); + virtio_queue_aio_detach_host_notifier(vq, s->vq_aio_context[i]); } } @@ -1320,10 +1328,12 @@ static void virtio_scsi_drained_end(SCSIBus *bus) for (uint32_t i = 0; i < total_queues; i++) { VirtQueue *vq = virtio_get_queue(vdev, i); + AioContext *ctx = s->vq_aio_context[i]; + if (vq == vs->event_vq) { - virtio_queue_aio_attach_host_notifier_no_poll(vq, s->ctx); + virtio_queue_aio_attach_host_notifier_no_poll(vq, ctx); } else { - virtio_queue_aio_attach_host_notifier(vq, s->ctx); + virtio_queue_aio_attach_host_notifier(vq, ctx); } } } @@ -1430,12 +1440,13 @@ void virtio_scsi_common_unrealize(DeviceState *dev) virtio_cleanup(vdev); } +/* main loop */ static void virtio_scsi_device_unrealize(DeviceState *dev) { VirtIOSCSI *s = VIRTIO_SCSI(dev); virtio_scsi_reset_tmf_bh(s); - + virtio_scsi_dataplane_cleanup(s); qbus_set_hotplug_handler(BUS(&s->bus), NULL); virtio_scsi_common_unrealize(dev); qemu_mutex_destroy(&s->tmf_bh_lock); @@ -1460,6 +1471,8 @@ static const Property virtio_scsi_properties[] = { VIRTIO_SCSI_F_CHANGE, true), DEFINE_PROP_LINK("iothread", VirtIOSCSI, parent_obj.conf.iothread, TYPE_IOTHREAD, IOThread *), + DEFINE_PROP_IOTHREAD_VQ_MAPPING_LIST("iothread-vq-mapping", VirtIOSCSI, + parent_obj.conf.iothread_vq_mapping_list), }; static const VMStateDescription vmstate_virtio_scsi = { diff --git a/include/hw/virtio/virtio-scsi.h b/include/hw/virtio/virtio-scsi.h index 7b7e3ced7af6..086201efa290 100644 --- a/include/hw/virtio/virtio-scsi.h +++ b/include/hw/virtio/virtio-scsi.h @@ -22,6 +22,7 @@ #include "hw/virtio/virtio.h" #include "hw/scsi/scsi.h" #include "chardev/char-fe.h" +#include "qapi/qapi-types-virtio.h" #include "system/iothread.h" #define TYPE_VIRTIO_SCSI_COMMON "virtio-scsi-common" @@ -60,6 +61,7 @@ struct VirtIOSCSIConf { CharBackend chardev; uint32_t boot_tpgt; IOThread *iothread; + IOThreadVirtQueueMappingList *iothread_vq_mapping_list; }; struct VirtIOSCSI; @@ -97,7 +99,7 @@ struct VirtIOSCSI { QTAILQ_HEAD(, VirtIOSCSIReq) tmf_bh_list; /* Fields for dataplane below */ - AioContext *ctx; /* one iothread per virtio-scsi-pci for now */ + AioContext **vq_aio_context; /* per-virtqueue AioContext pointer */ bool dataplane_started; bool dataplane_starting; @@ -115,6 +117,7 @@ void virtio_scsi_common_realize(DeviceState *dev, void virtio_scsi_common_unrealize(DeviceState *dev); void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp); +void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s); int virtio_scsi_dataplane_start(VirtIODevice *s); void virtio_scsi_dataplane_stop(VirtIODevice *s); diff --git a/tests/qemu-iotests/051.pc.out b/tests/qemu-iotests/051.pc.out index 7e10c5fa1b0c..f19b532eb9b6 100644 --- a/tests/qemu-iotests/051.pc.out +++ b/tests/qemu-iotests/051.pc.out @@ -181,7 +181,7 @@ QEMU X.Y.Z monitor - type 'help' for more information Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id=thread0 -device virtio-scsi,iothread=thread0,id=virtio-scsi0 -device scsi-hd,bus=virtio-scsi0.0,drive=disk,share-rw=on -device virtio-scsi,id=virtio-scsi1 -device scsi-hd,bus=virtio-scsi1.0,drive=disk,share-rw=on QEMU X.Y.Z monitor - type 'help' for more information -(qemu) QEMU_PROG: -device scsi-hd,bus=virtio-scsi1.0,drive=disk,share-rw=on: Cannot change iothread of active block backend +(qemu) quit Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id=thread0 -device virtio-scsi,iothread=thread0,id=virtio-scsi0 -device scsi-hd,bus=virtio-scsi0.0,drive=disk,share-rw=on -device virtio-blk-pci,drive=disk,iothread=thread0,share-rw=on QEMU X.Y.Z monitor - type 'help' for more information From bcede51d2d1ae03f99ccb2569e52b5062033d40d Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:15 +0800 Subject: [PATCH 1015/1179] virtio-scsi: handle ctrl virtqueue in main loop Previously the ctrl virtqueue was handled in the AioContext where SCSI requests are processed. When IOThread Virtqueue Mapping was added things become more complicated because SCSI requests could run in other AioContexts. Simplify by handling the ctrl virtqueue in the main loop where reset operations can be performed. Note that BHs are still used canceling SCSI requests in their AioContexts but at least the mean loop activity doesn't need BHs anymore. Signed-off-by: Stefan Hajnoczi Message-ID: <20250311132616.1049687-13-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/virtio-scsi-dataplane.c | 6 ++ hw/scsi/virtio-scsi.c | 144 ++++++-------------------------- include/hw/virtio/virtio-scsi.h | 8 -- 3 files changed, 33 insertions(+), 125 deletions(-) diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c index 6bb368c8a5d6..2d37fa6712a4 100644 --- a/hw/scsi/virtio-scsi-dataplane.c +++ b/hw/scsi/virtio-scsi-dataplane.c @@ -73,6 +73,12 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp) s->vq_aio_context[i] = ctx; } } + + /* + * Always handle the ctrl virtqueue in the main loop thread where device + * resets can be performed. + */ + s->vq_aio_context[0] = qemu_get_aio_context(); } /* Context: BQL held */ diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index e7d97036083d..f5a3aa2366f7 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -319,115 +319,6 @@ static void virtio_scsi_cancel_notify(Notifier *notifier, void *data) g_free(n); } -static void virtio_scsi_do_one_tmf_bh(VirtIOSCSIReq *req) -{ - VirtIOSCSI *s = req->dev; - SCSIDevice *d = virtio_scsi_device_get(s, req->req.tmf.lun); - BusChild *kid; - int target; - - switch (req->req.tmf.subtype) { - case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET: - if (!d) { - req->resp.tmf.response = VIRTIO_SCSI_S_BAD_TARGET; - goto out; - } - if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) { - req->resp.tmf.response = VIRTIO_SCSI_S_INCORRECT_LUN; - goto out; - } - qatomic_inc(&s->resetting); - device_cold_reset(&d->qdev); - qatomic_dec(&s->resetting); - break; - - case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET: - target = req->req.tmf.lun[1]; - qatomic_inc(&s->resetting); - - rcu_read_lock(); - QTAILQ_FOREACH_RCU(kid, &s->bus.qbus.children, sibling) { - SCSIDevice *d1 = SCSI_DEVICE(kid->child); - if (d1->channel == 0 && d1->id == target) { - device_cold_reset(&d1->qdev); - } - } - rcu_read_unlock(); - - qatomic_dec(&s->resetting); - break; - - default: - g_assert_not_reached(); - } - -out: - object_unref(OBJECT(d)); - virtio_scsi_complete_req(req, &s->ctrl_lock); -} - -/* Some TMFs must be processed from the main loop thread */ -static void virtio_scsi_do_tmf_bh(void *opaque) -{ - VirtIOSCSI *s = opaque; - QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs); - VirtIOSCSIReq *req; - VirtIOSCSIReq *tmp; - - GLOBAL_STATE_CODE(); - - WITH_QEMU_LOCK_GUARD(&s->tmf_bh_lock) { - QTAILQ_FOREACH_SAFE(req, &s->tmf_bh_list, next, tmp) { - QTAILQ_REMOVE(&s->tmf_bh_list, req, next); - QTAILQ_INSERT_TAIL(&reqs, req, next); - } - - qemu_bh_delete(s->tmf_bh); - s->tmf_bh = NULL; - } - - QTAILQ_FOREACH_SAFE(req, &reqs, next, tmp) { - QTAILQ_REMOVE(&reqs, req, next); - virtio_scsi_do_one_tmf_bh(req); - } -} - -static void virtio_scsi_reset_tmf_bh(VirtIOSCSI *s) -{ - VirtIOSCSIReq *req; - VirtIOSCSIReq *tmp; - - GLOBAL_STATE_CODE(); - - /* Called after ioeventfd has been stopped, so tmf_bh_lock is not needed */ - if (s->tmf_bh) { - qemu_bh_delete(s->tmf_bh); - s->tmf_bh = NULL; - } - - QTAILQ_FOREACH_SAFE(req, &s->tmf_bh_list, next, tmp) { - QTAILQ_REMOVE(&s->tmf_bh_list, req, next); - - /* SAM-6 6.3.2 Hard reset */ - req->resp.tmf.response = VIRTIO_SCSI_S_TARGET_FAILURE; - virtio_scsi_complete_req(req, &req->dev->ctrl_lock); - } -} - -static void virtio_scsi_defer_tmf_to_main_loop(VirtIOSCSIReq *req) -{ - VirtIOSCSI *s = req->dev; - - WITH_QEMU_LOCK_GUARD(&s->tmf_bh_lock) { - QTAILQ_INSERT_TAIL(&s->tmf_bh_list, req, next); - - if (!s->tmf_bh) { - s->tmf_bh = qemu_bh_new(virtio_scsi_do_tmf_bh, s); - qemu_bh_schedule(s->tmf_bh); - } - } -} - static void virtio_scsi_tmf_cancel_req(VirtIOSCSIReq *tmf, SCSIRequest *r) { VirtIOSCSICancelNotifier *notifier; @@ -627,11 +518,35 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) break; case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET: - case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET: - virtio_scsi_defer_tmf_to_main_loop(req); - ret = -EINPROGRESS; + if (!d) { + goto fail; + } + if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) { + goto incorrect_lun; + } + qatomic_inc(&s->resetting); + device_cold_reset(&d->qdev); + qatomic_dec(&s->resetting); break; + case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET: { + BusChild *kid; + int target = req->req.tmf.lun[1]; + qatomic_inc(&s->resetting); + + rcu_read_lock(); + QTAILQ_FOREACH_RCU(kid, &s->bus.qbus.children, sibling) { + SCSIDevice *d1 = SCSI_DEVICE(kid->child); + if (d1->channel == 0 && d1->id == target) { + device_cold_reset(&d1->qdev); + } + } + rcu_read_unlock(); + + qatomic_dec(&s->resetting); + break; + } + case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET: case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: { g_autoptr(GHashTable) aio_contexts = g_hash_table_new(NULL, NULL); @@ -1087,7 +1002,6 @@ static void virtio_scsi_reset(VirtIODevice *vdev) assert(!s->dataplane_started); - virtio_scsi_reset_tmf_bh(s); virtio_scsi_flush_defer_tmf_to_aio_context(s); qatomic_inc(&s->resetting); @@ -1402,10 +1316,8 @@ static void virtio_scsi_device_realize(DeviceState *dev, Error **errp) VirtIOSCSI *s = VIRTIO_SCSI(dev); Error *err = NULL; - QTAILQ_INIT(&s->tmf_bh_list); qemu_mutex_init(&s->ctrl_lock); qemu_mutex_init(&s->event_lock); - qemu_mutex_init(&s->tmf_bh_lock); virtio_scsi_common_realize(dev, virtio_scsi_handle_ctrl, @@ -1445,11 +1357,9 @@ static void virtio_scsi_device_unrealize(DeviceState *dev) { VirtIOSCSI *s = VIRTIO_SCSI(dev); - virtio_scsi_reset_tmf_bh(s); virtio_scsi_dataplane_cleanup(s); qbus_set_hotplug_handler(BUS(&s->bus), NULL); virtio_scsi_common_unrealize(dev); - qemu_mutex_destroy(&s->tmf_bh_lock); qemu_mutex_destroy(&s->event_lock); qemu_mutex_destroy(&s->ctrl_lock); } diff --git a/include/hw/virtio/virtio-scsi.h b/include/hw/virtio/virtio-scsi.h index 086201efa290..31e852ed6c44 100644 --- a/include/hw/virtio/virtio-scsi.h +++ b/include/hw/virtio/virtio-scsi.h @@ -90,14 +90,6 @@ struct VirtIOSCSI { QemuMutex ctrl_lock; /* protects ctrl_vq */ - /* - * TMFs deferred to main loop BH. These fields are protected by - * tmf_bh_lock. - */ - QemuMutex tmf_bh_lock; - QEMUBH *tmf_bh; - QTAILQ_HEAD(, VirtIOSCSIReq) tmf_bh_list; - /* Fields for dataplane below */ AioContext **vq_aio_context; /* per-virtqueue AioContext pointer */ From 40aa38a651a8d4ca99c70e591176a97abcae5295 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 11 Mar 2025 21:26:16 +0800 Subject: [PATCH 1016/1179] virtio-scsi: only expose cmd vqs via iothread-vq-mapping Peter Krempa and Kevin Wolf observed that iothread-vq-mapping is confusing to use because the control and event virtqueues have a fixed location before the command virtqueues but need to be treated differently. Only expose the command virtqueues via iothread-vq-mapping so that the command-line parameter is intuitive: it controls where SCSI requests are processed. The control virtqueue needs to be hardcoded to the main loop thread for technical reasons anyway. Kevin also pointed out that it's better to place the event virtqueue in the main loop thread since its no poll behavior would prevent polling if assigned to an IOThread. This change is its own commit to avoid squashing the previous commit. Suggested-by: Kevin Wolf Suggested-by: Peter Krempa Signed-off-by: Stefan Hajnoczi Message-ID: <20250311132616.1049687-14-stefanha@redhat.com> Tested-by: Peter Krempa Signed-off-by: Kevin Wolf --- hw/scsi/virtio-scsi-dataplane.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c index 2d37fa6712a4..95f13fb7c285 100644 --- a/hw/scsi/virtio-scsi-dataplane.c +++ b/hw/scsi/virtio-scsi-dataplane.c @@ -28,7 +28,6 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp) VirtIODevice *vdev = VIRTIO_DEVICE(s); BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); - uint16_t num_vqs = vs->conf.num_queues + VIRTIO_SCSI_VQ_NUM_FIXED; if (vs->conf.iothread && vs->conf.iothread_vq_mapping_list) { error_setg(errp, @@ -50,35 +49,43 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp) } } - s->vq_aio_context = g_new(AioContext *, num_vqs); + s->vq_aio_context = g_new(AioContext *, vs->conf.num_queues + + VIRTIO_SCSI_VQ_NUM_FIXED); + + /* + * Handle the ctrl virtqueue in the main loop thread where device resets + * can be performed. + */ + s->vq_aio_context[0] = qemu_get_aio_context(); + + /* + * Handle the event virtqueue in the main loop thread where its no_poll + * behavior won't stop IOThread polling. + */ + s->vq_aio_context[1] = qemu_get_aio_context(); if (vs->conf.iothread_vq_mapping_list) { if (!iothread_vq_mapping_apply(vs->conf.iothread_vq_mapping_list, - s->vq_aio_context, num_vqs, errp)) { + &s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED], + vs->conf.num_queues, errp)) { g_free(s->vq_aio_context); s->vq_aio_context = NULL; return; } } else if (vs->conf.iothread) { AioContext *ctx = iothread_get_aio_context(vs->conf.iothread); - for (uint16_t i = 0; i < num_vqs; i++) { - s->vq_aio_context[i] = ctx; + for (uint16_t i = 0; i < vs->conf.num_queues; i++) { + s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx; } /* Released in virtio_scsi_dataplane_cleanup() */ object_ref(OBJECT(vs->conf.iothread)); } else { AioContext *ctx = qemu_get_aio_context(); - for (unsigned i = 0; i < num_vqs; i++) { - s->vq_aio_context[i] = ctx; + for (unsigned i = 0; i < vs->conf.num_queues; i++) { + s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx; } } - - /* - * Always handle the ctrl virtqueue in the main loop thread where device - * resets can be performed. - */ - s->vq_aio_context[0] = qemu_get_aio_context(); } /* Context: BQL held */ From df957115c46845e2c0ccc29ac0a75eb9700a9a0d Mon Sep 17 00:00:00 2001 From: Alberto Garcia Date: Tue, 30 Jul 2024 16:15:52 +0200 Subject: [PATCH 1017/1179] scripts/qcow2-to-stdout.py: Add script to write qcow2 images to stdout This tool converts a disk image to qcow2, writing the result directly to stdout. This can be used for example to send the generated file over the network. This is equivalent to using qemu-img to convert a file to qcow2 and then writing the result to stdout, with the difference that this tool does not need to create this temporary qcow2 file and therefore does not need any additional disk space. Implementing this directly in qemu-img is not really an option because it expects the output file to be seekable and it is also meant to be a generic tool that supports all combinations of file formats and image options. Instead, this tool can only produce qcow2 files with the basic options, without compression, encryption or other features. The input file is read twice. The first pass is used to determine which clusters contain non-zero data and that information is used to create the qcow2 header, refcount table and blocks, and L1 and L2 tables. After all that metadata is created then the second pass is used to write the guest data. By default qcow2-to-stdout.py expects the input to be a raw file, but if qemu-storage-daemon is available then it can also be used to read images in other formats. Alternatively the user can also run qemu-nbd or qemu-storage-daemon manually instead. Signed-off-by: Alberto Garcia Signed-off-by: Madeeha Javed Message-ID: <20240730141552.60404-1-berto@igalia.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- scripts/qcow2-to-stdout.py | 449 +++++++++++++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100755 scripts/qcow2-to-stdout.py diff --git a/scripts/qcow2-to-stdout.py b/scripts/qcow2-to-stdout.py new file mode 100755 index 000000000000..06b7c13ccbb5 --- /dev/null +++ b/scripts/qcow2-to-stdout.py @@ -0,0 +1,449 @@ +#!/usr/bin/env python3 + +# This tool reads a disk image in any format and converts it to qcow2, +# writing the result directly to stdout. +# +# Copyright (C) 2024 Igalia, S.L. +# +# Authors: Alberto Garcia +# Madeeha Javed +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# qcow2 files produced by this script are always arranged like this: +# +# - qcow2 header +# - refcount table +# - refcount blocks +# - L1 table +# - L2 tables +# - Data clusters +# +# A note about variable names: in qcow2 there is one refcount table +# and one (active) L1 table, although each can occupy several +# clusters. For the sake of simplicity the code sometimes talks about +# refcount tables and L1 tables when referring to those clusters. + +import argparse +import errno +import math +import os +import signal +import struct +import subprocess +import sys +import tempfile +import time +from contextlib import contextmanager + +QCOW2_DEFAULT_CLUSTER_SIZE = 65536 +QCOW2_DEFAULT_REFCOUNT_BITS = 16 +QCOW2_FEATURE_NAME_TABLE = 0x6803F857 +QCOW2_DATA_FILE_NAME_STRING = 0x44415441 +QCOW2_V3_HEADER_LENGTH = 112 # Header length in QEMU 9.0. Must be a multiple of 8 +QCOW2_INCOMPAT_DATA_FILE_BIT = 2 +QCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT = 1 +QCOW_OFLAG_COPIED = 1 << 63 +QEMU_STORAGE_DAEMON = "qemu-storage-daemon" + + +def bitmap_set(bitmap, idx): + bitmap[idx // 8] |= 1 << (idx % 8) + + +def bitmap_is_set(bitmap, idx): + return (bitmap[idx // 8] & (1 << (idx % 8))) != 0 + + +def bitmap_iterator(bitmap, length): + for idx in range(length): + if bitmap_is_set(bitmap, idx): + yield idx + + +def align_up(num, d): + return d * math.ceil(num / d) + + +# Holes in the input file contain only zeroes so we can skip them and +# save time. This function returns the indexes of the clusters that +# are known to contain data. Those are the ones that we need to read. +def clusters_with_data(fd, cluster_size): + data_to = 0 + while True: + try: + data_from = os.lseek(fd, data_to, os.SEEK_DATA) + data_to = align_up(os.lseek(fd, data_from, os.SEEK_HOLE), cluster_size) + for idx in range(data_from // cluster_size, data_to // cluster_size): + yield idx + except OSError as err: + if err.errno == errno.ENXIO: # End of file reached + break + raise err + + +# write_qcow2_content() expects a raw input file. If we have a different +# format we can use qemu-storage-daemon to make it appear as raw. +@contextmanager +def get_input_as_raw_file(input_file, input_format): + if input_format == "raw": + yield input_file + return + try: + temp_dir = tempfile.mkdtemp() + pid_file = os.path.join(temp_dir, "pid") + raw_file = os.path.join(temp_dir, "raw") + open(raw_file, "wb").close() + ret = subprocess.run( + [ + QEMU_STORAGE_DAEMON, + "--daemonize", + "--pidfile", pid_file, + "--blockdev", f"driver=file,node-name=file0,driver=file,filename={input_file},read-only=on", + "--blockdev", f"driver={input_format},node-name=disk0,file=file0,read-only=on", + "--export", f"type=fuse,id=export0,node-name=disk0,mountpoint={raw_file},writable=off", + ], + capture_output=True, + ) + if ret.returncode != 0: + sys.exit("[Error] Could not start the qemu-storage-daemon:\n" + + ret.stderr.decode().rstrip('\n')) + yield raw_file + finally: + # Kill the storage daemon on exit + # and remove all temporary files + if os.path.exists(pid_file): + with open(pid_file, "r") as f: + pid = int(f.readline()) + os.kill(pid, signal.SIGTERM) + while os.path.exists(pid_file): + time.sleep(0.1) + os.unlink(raw_file) + os.rmdir(temp_dir) + + +def write_features(cluster, offset, data_file_name): + if data_file_name is not None: + encoded_name = data_file_name.encode("utf-8") + padded_name_len = align_up(len(encoded_name), 8) + struct.pack_into(f">II{padded_name_len}s", cluster, offset, + QCOW2_DATA_FILE_NAME_STRING, + len(encoded_name), + encoded_name) + offset += 8 + padded_name_len + + qcow2_features = [ + # Incompatible + (0, 0, "dirty bit"), + (0, 1, "corrupt bit"), + (0, 2, "external data file"), + (0, 3, "compression type"), + (0, 4, "extended L2 entries"), + # Compatible + (1, 0, "lazy refcounts"), + # Autoclear + (2, 0, "bitmaps"), + (2, 1, "raw external data"), + ] + struct.pack_into(">I", cluster, offset, QCOW2_FEATURE_NAME_TABLE) + struct.pack_into(">I", cluster, offset + 4, len(qcow2_features) * 48) + offset += 8 + for feature_type, feature_bit, feature_name in qcow2_features: + struct.pack_into(">BB46s", cluster, offset, + feature_type, feature_bit, feature_name.encode("ascii")) + offset += 48 + + +def write_qcow2_content(input_file, cluster_size, refcount_bits, data_file_name, data_file_raw): + # Some basic values + l1_entries_per_table = cluster_size // 8 + l2_entries_per_table = cluster_size // 8 + refcounts_per_table = cluster_size // 8 + refcounts_per_block = cluster_size * 8 // refcount_bits + + # Virtual disk size, number of data clusters and L1 entries + disk_size = align_up(os.path.getsize(input_file), 512) + total_data_clusters = math.ceil(disk_size / cluster_size) + l1_entries = math.ceil(total_data_clusters / l2_entries_per_table) + allocated_l1_tables = math.ceil(l1_entries / l1_entries_per_table) + + # Max L1 table size is 32 MB (QCOW_MAX_L1_SIZE in block/qcow2.h) + if (l1_entries * 8) > (32 * 1024 * 1024): + sys.exit("[Error] The image size is too large. Try using a larger cluster size.") + + # Two bitmaps indicating which L1 and L2 entries are set + l1_bitmap = bytearray(allocated_l1_tables * l1_entries_per_table // 8) + l2_bitmap = bytearray(l1_entries * l2_entries_per_table // 8) + allocated_l2_tables = 0 + allocated_data_clusters = 0 + + if data_file_raw: + # If data_file_raw is set then all clusters are allocated and + # we don't need to read the input file at all. + allocated_l2_tables = l1_entries + for idx in range(l1_entries): + bitmap_set(l1_bitmap, idx) + for idx in range(total_data_clusters): + bitmap_set(l2_bitmap, idx) + else: + # Open the input file for reading + fd = os.open(input_file, os.O_RDONLY) + zero_cluster = bytes(cluster_size) + # Read all the clusters that contain data + for idx in clusters_with_data(fd, cluster_size): + cluster = os.pread(fd, cluster_size, cluster_size * idx) + # If the last cluster is smaller than cluster_size pad it with zeroes + if len(cluster) < cluster_size: + cluster += bytes(cluster_size - len(cluster)) + # If a cluster has non-zero data then it must be allocated + # in the output file and its L2 entry must be set + if cluster != zero_cluster: + bitmap_set(l2_bitmap, idx) + allocated_data_clusters += 1 + # Allocated data clusters also need their corresponding L1 entry and L2 table + l1_idx = math.floor(idx / l2_entries_per_table) + if not bitmap_is_set(l1_bitmap, l1_idx): + bitmap_set(l1_bitmap, l1_idx) + allocated_l2_tables += 1 + + # Total amount of allocated clusters excluding the refcount blocks and table + total_allocated_clusters = 1 + allocated_l1_tables + allocated_l2_tables + if data_file_name is None: + total_allocated_clusters += allocated_data_clusters + + # Clusters allocated for the refcount blocks and table + allocated_refcount_blocks = math.ceil(total_allocated_clusters / refcounts_per_block) + allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table) + + # Now we have a problem because allocated_refcount_blocks and allocated_refcount_tables... + # (a) increase total_allocated_clusters, and + # (b) need to be recalculated when total_allocated_clusters is increased + # So we need to repeat the calculation as long as the numbers change + while True: + new_total_allocated_clusters = total_allocated_clusters + allocated_refcount_tables + allocated_refcount_blocks + new_allocated_refcount_blocks = math.ceil(new_total_allocated_clusters / refcounts_per_block) + if new_allocated_refcount_blocks > allocated_refcount_blocks: + allocated_refcount_blocks = new_allocated_refcount_blocks + allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table) + else: + break + + # Now that we have the final numbers we can update total_allocated_clusters + total_allocated_clusters += allocated_refcount_tables + allocated_refcount_blocks + + # At this point we have the exact number of clusters that the output + # image is going to use so we can calculate all the offsets. + current_cluster_idx = 1 + + refcount_table_offset = current_cluster_idx * cluster_size + current_cluster_idx += allocated_refcount_tables + + refcount_block_offset = current_cluster_idx * cluster_size + current_cluster_idx += allocated_refcount_blocks + + l1_table_offset = current_cluster_idx * cluster_size + current_cluster_idx += allocated_l1_tables + + l2_table_offset = current_cluster_idx * cluster_size + current_cluster_idx += allocated_l2_tables + + data_clusters_offset = current_cluster_idx * cluster_size + + # Calculate some values used in the qcow2 header + if allocated_l1_tables == 0: + l1_table_offset = 0 + + hdr_cluster_bits = int(math.log2(cluster_size)) + hdr_refcount_bits = int(math.log2(refcount_bits)) + hdr_length = QCOW2_V3_HEADER_LENGTH + hdr_incompat_features = 0 + if data_file_name is not None: + hdr_incompat_features |= 1 << QCOW2_INCOMPAT_DATA_FILE_BIT + hdr_autoclear_features = 0 + if data_file_raw: + hdr_autoclear_features |= 1 << QCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT + + ### Write qcow2 header + cluster = bytearray(cluster_size) + struct.pack_into(">4sIQIIQIIQQIIQQQQII", cluster, 0, + b"QFI\xfb", # QCOW magic string + 3, # version + 0, # backing file offset + 0, # backing file sizes + hdr_cluster_bits, + disk_size, + 0, # encryption method + l1_entries, + l1_table_offset, + refcount_table_offset, + allocated_refcount_tables, + 0, # number of snapshots + 0, # snapshot table offset + hdr_incompat_features, + 0, # compatible features + hdr_autoclear_features, + hdr_refcount_bits, + hdr_length, + ) + + write_features(cluster, hdr_length, data_file_name) + + sys.stdout.buffer.write(cluster) + + ### Write refcount table + cur_offset = refcount_block_offset + remaining_refcount_table_entries = allocated_refcount_blocks # Each entry is a pointer to a refcount block + while remaining_refcount_table_entries > 0: + cluster = bytearray(cluster_size) + to_write = min(remaining_refcount_table_entries, refcounts_per_table) + remaining_refcount_table_entries -= to_write + for idx in range(to_write): + struct.pack_into(">Q", cluster, idx * 8, cur_offset) + cur_offset += cluster_size + sys.stdout.buffer.write(cluster) + + ### Write refcount blocks + remaining_refcount_block_entries = total_allocated_clusters # One entry for each allocated cluster + for tbl in range(allocated_refcount_blocks): + cluster = bytearray(cluster_size) + to_write = min(remaining_refcount_block_entries, refcounts_per_block) + remaining_refcount_block_entries -= to_write + # All refcount entries contain the number 1. The only difference + # is their bit width, defined when the image is created. + for idx in range(to_write): + if refcount_bits == 64: + struct.pack_into(">Q", cluster, idx * 8, 1) + elif refcount_bits == 32: + struct.pack_into(">L", cluster, idx * 4, 1) + elif refcount_bits == 16: + struct.pack_into(">H", cluster, idx * 2, 1) + elif refcount_bits == 8: + cluster[idx] = 1 + elif refcount_bits == 4: + cluster[idx // 2] |= 1 << ((idx % 2) * 4) + elif refcount_bits == 2: + cluster[idx // 4] |= 1 << ((idx % 4) * 2) + elif refcount_bits == 1: + cluster[idx // 8] |= 1 << (idx % 8) + sys.stdout.buffer.write(cluster) + + ### Write L1 table + cur_offset = l2_table_offset + for tbl in range(allocated_l1_tables): + cluster = bytearray(cluster_size) + for idx in range(l1_entries_per_table): + l1_idx = tbl * l1_entries_per_table + idx + if bitmap_is_set(l1_bitmap, l1_idx): + struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED) + cur_offset += cluster_size + sys.stdout.buffer.write(cluster) + + ### Write L2 tables + cur_offset = data_clusters_offset + for tbl in range(l1_entries): + # Skip the empty L2 tables. We can identify them because + # there is no L1 entry pointing at them. + if bitmap_is_set(l1_bitmap, tbl): + cluster = bytearray(cluster_size) + for idx in range(l2_entries_per_table): + l2_idx = tbl * l2_entries_per_table + idx + if bitmap_is_set(l2_bitmap, l2_idx): + if data_file_name is None: + struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED) + cur_offset += cluster_size + else: + struct.pack_into(">Q", cluster, idx * 8, (l2_idx * cluster_size) | QCOW_OFLAG_COPIED) + sys.stdout.buffer.write(cluster) + + ### Write data clusters + if data_file_name is None: + for idx in bitmap_iterator(l2_bitmap, total_data_clusters): + cluster = os.pread(fd, cluster_size, cluster_size * idx) + # If the last cluster is smaller than cluster_size pad it with zeroes + if len(cluster) < cluster_size: + cluster += bytes(cluster_size - len(cluster)) + sys.stdout.buffer.write(cluster) + + if not data_file_raw: + os.close(fd) + + +def main(): + # Command-line arguments + parser = argparse.ArgumentParser( + description="This program converts a QEMU disk image to qcow2 " + "and writes it to the standard output" + ) + parser.add_argument("input_file", help="name of the input file") + parser.add_argument( + "-f", + dest="input_format", + metavar="input_format", + help="format of the input file (default: raw)", + default="raw", + ) + parser.add_argument( + "-c", + dest="cluster_size", + metavar="cluster_size", + help=f"qcow2 cluster size (default: {QCOW2_DEFAULT_CLUSTER_SIZE})", + default=QCOW2_DEFAULT_CLUSTER_SIZE, + type=int, + choices=[1 << x for x in range(9, 22)], + ) + parser.add_argument( + "-r", + dest="refcount_bits", + metavar="refcount_bits", + help=f"width of the reference count entries (default: {QCOW2_DEFAULT_REFCOUNT_BITS})", + default=QCOW2_DEFAULT_REFCOUNT_BITS, + type=int, + choices=[1 << x for x in range(7)], + ) + parser.add_argument( + "-d", + dest="data_file", + help="create an image with input_file as an external data file", + action="store_true", + ) + parser.add_argument( + "-R", + dest="data_file_raw", + help="enable data_file_raw on the generated image (implies -d)", + action="store_true", + ) + args = parser.parse_args() + + if args.data_file_raw: + args.data_file = True + + if not os.path.isfile(args.input_file): + sys.exit(f"[Error] {args.input_file} does not exist or is not a regular file.") + + if args.data_file and args.input_format != "raw": + sys.exit("[Error] External data files can only be used with raw input images") + + # A 512 byte header is too small for the data file name extension + if args.data_file and args.cluster_size == 512: + sys.exit("[Error] External data files require a larger cluster size") + + if sys.stdout.isatty(): + sys.exit("[Error] Refusing to write to a tty. Try redirecting stdout.") + + if args.data_file: + data_file_name = args.input_file + else: + data_file_name = None + + with get_input_as_raw_file(args.input_file, args.input_format) as raw_file: + write_qcow2_content( + raw_file, + args.cluster_size, + args.refcount_bits, + data_file_name, + args.data_file_raw, + ) + + +if __name__ == "__main__": + main() From cbb698a2ba568f9746dee00ed59c442787103674 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 11 Mar 2025 14:17:15 +0100 Subject: [PATCH 1018/1179] qapi/block-core: Improve x-blockdev-change documentation The description of feature @unstable is three paragraphs. The second and third became part of the description by accident in commit 9fb49daabfb (qapi: Mark unstable QMP parts with feature 'unstable'). The second paragraph describes a defect in terms of the implementation. Fine, but doesn't belong into user-facing documentation. Turn it into a TODO section. Rewrite everything else for clarity and completeness. Signed-off-by: Markus Armbruster Message-ID: <20250311131715.1296101-1-armbru@redhat.com> Acked-by: Alberto Garcia --- qapi/block-core.json | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/qapi/block-core.json b/qapi/block-core.json index ee6eccc68c81..b1937780e198 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -5913,35 +5913,31 @@ ## # @x-blockdev-change: # -# Dynamically reconfigure the block driver state graph. It can be -# used to add, remove, insert or replace a graph node. Currently only -# the Quorum driver implements this feature to add or remove its -# child. This is useful to fix a broken quorum child. +# Dynamically reconfigure the block driver state graph. # -# If @node is specified, it will be inserted under @parent. @child -# may not be specified in this case. If both @parent and @child are -# specified but @node is not, @child will be detached from @parent. +# Currently only supports adding and deleting quorum children. A +# child will be added at the end of the list of children. Its +# contents *must* be consistent with the other childrens' contents. +# Deleting a child that is not last in the list of children is +# problematic, because it "renumbers" the children following it. # # @parent: the id or name of the parent node. # -# @child: the name of a child under the given parent node. +# @child: the name of a child to be deleted. Mutually exclusive with +# @node. # -# @node: the name of the node that will be added. +# @node: the name of the node to be added. Mutually exclusive with +# @child. # # Features: # -# @unstable: This command is experimental, and its API is not stable. -# It does not support all kinds of operations, all kinds of -# children, nor all block drivers. +# @unstable: This command is experimental. # -# FIXME Removing children from a quorum node means introducing +# TODO: Removing children from a quorum node means introducing # gaps in the child indices. This cannot be represented in the # 'children' list of BlockdevOptionsQuorum, as returned by # .bdrv_refresh_filename(). # -# Warning: The data in a new quorum child MUST be consistent with -# that of the rest of the array. -# # Since: 2.7 # # .. qmp-example:: From 8fad36626009ef51b52f4cdf08e29cc66284e41d Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:02 -0400 Subject: [PATCH 1019/1179] docs/qapi_domain: isolate TYPE_CHECKING imports When using the annotations feature, type hints do not need to be imported at runtime, only at type check time. Move type-check-only imports into a conditional to reduce the number of imports needed at runtime. Signed-off-by: John Snow Message-ID: <20250313044312.189276-2-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 7ff618d8cdae..d52e7df7bc72 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -9,15 +9,9 @@ from typing import ( TYPE_CHECKING, - AbstractSet, - Any, - Dict, - Iterable, List, NamedTuple, - Optional, Tuple, - Union, cast, ) @@ -34,7 +28,6 @@ SpaceNode, ) from sphinx import addnodes -from sphinx.addnodes import desc_signature, pending_xref from sphinx.directives import ObjectDescription from sphinx.domains import ( Domain, @@ -49,13 +42,24 @@ if TYPE_CHECKING: + from typing import ( + AbstractSet, + Any, + Dict, + Iterable, + Optional, + Union, + ) + from docutils.nodes import Element, Node + from sphinx.addnodes import desc_signature, pending_xref from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.environment import BuildEnvironment from sphinx.util.typing import OptionSpec + logger = logging.getLogger(__name__) From e36afc7bcc193da144f5d45c7c37eb62835b3ab1 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:03 -0400 Subject: [PATCH 1020/1179] docs/qapi-domain: always store fully qualified name in signode Currently, only the definition name is stored in the tree metadata; but the node property is confusingly called "fullname". Rectify this by always storing the FQN in the tree metadata. ... While we're here, re-organize the code in preparation for namespace support to make it a bit easier to add additional components of the FQN. With this change, there is now extremely little code left that's taken directly from the Python domain :) Signed-off-by: John Snow Message-ID: <20250313044312.189276-3-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 64 +++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index d52e7df7bc72..6b23fc73ef24 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -178,6 +178,25 @@ def get_index_text(self, name: Signature) -> Tuple[str, str]: # NB: this is used for the global index, not the QAPI index. return ("single", f"{name} (QMP {self.objtype})") + def _get_context(self) -> str: + modname = self.options.get( + "module", self.env.ref_context.get("qapi:module", "") + ) + assert isinstance(modname, str) + return modname + + def _get_fqn(self, name: Signature) -> str: + modname = self._get_context() + + # If we're documenting a module, don't include the module as + # part of the FQN; we ARE the module! + if self.objtype == "module": + modname = "" + + if modname: + name = f"{modname}.{name}" + return name + def add_target_and_index( self, name: Signature, sig: str, signode: desc_signature ) -> None: @@ -187,14 +206,8 @@ def add_target_and_index( assert self.objtype - # If we're documenting a module, don't include the module as - # part of the FQN. - modname = "" - if self.objtype != "module": - modname = self.options.get( - "module", self.env.ref_context.get("qapi:module") - ) - fullname = (modname + "." if modname else "") + name + if not (fullname := signode.get("fullname", "")): + fullname = self._get_fqn(name) node_id = make_id( self.env, self.state.document, self.objtype, fullname @@ -213,18 +226,21 @@ def add_target_and_index( (arity, indextext, node_id, "", None) ) + @staticmethod + def split_fqn(name: str) -> Tuple[str, str]: + if "." in name: + module, name = name.split(".") + else: + module = "" + + return (module, name) + def _object_hierarchy_parts( self, sig_node: desc_signature ) -> Tuple[str, ...]: if "fullname" not in sig_node: return () - modname = sig_node.get("module") - fullname = sig_node["fullname"] - - if modname: - return (modname, *fullname.split(".")) - - return tuple(fullname.split(".")) + return self.split_fqn(sig_node["fullname"]) def _toc_entry_name(self, sig_node: desc_signature) -> str: # This controls the name in the TOC and on the sidebar. @@ -235,13 +251,19 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: return "" config = self.env.app.config - *parents, name = toc_parts + modname, name = toc_parts + if config.toc_object_entries_show_parents == "domain": - return sig_node.get("fullname", name) + ret = name + if modname and modname != self.env.ref_context.get( + "qapi:module", "" + ): + ret = f"{modname}.{name}" + return ret if config.toc_object_entries_show_parents == "hide": return name if config.toc_object_entries_show_parents == "all": - return ".".join(parents + [name]) + return sig_node.get("fullname", name) return "" @@ -312,11 +334,9 @@ def handle_signature(self, sig: str, signode: desc_signature) -> Signature: As such, the only argument here is "sig", which is just the QAPI definition name. """ - modname = self.options.get( - "module", self.env.ref_context.get("qapi:module") - ) + modname = self._get_context() - signode["fullname"] = sig + signode["fullname"] = self._get_fqn(sig) signode["module"] = modname sig_prefix = self.get_signature_prefix() if sig_prefix: From 74d40b011c27dc343ad56022a322d212135c96ed Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:04 -0400 Subject: [PATCH 1021/1179] docs/qapi_domain: add namespace support to FQN This patch adds a namespace component to the "Fully Qualified Name", in the form of "domain:module.name". As there are no namespace directives or options yet, this component will simply be empty as of this patch. Signed-off-by: John Snow Message-ID: <20250313044312.189276-4-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapi_domain.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 6b23fc73ef24..48a082d489ae 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -178,15 +178,18 @@ def get_index_text(self, name: Signature) -> Tuple[str, str]: # NB: this is used for the global index, not the QAPI index. return ("single", f"{name} (QMP {self.objtype})") - def _get_context(self) -> str: + def _get_context(self) -> Tuple[str, str]: + namespace = self.options.get( + "namespace", self.env.ref_context.get("qapi:namespace", "") + ) modname = self.options.get( "module", self.env.ref_context.get("qapi:module", "") ) - assert isinstance(modname, str) - return modname + + return namespace, modname def _get_fqn(self, name: Signature) -> str: - modname = self._get_context() + namespace, modname = self._get_context() # If we're documenting a module, don't include the module as # part of the FQN; we ARE the module! @@ -195,6 +198,8 @@ def _get_fqn(self, name: Signature) -> str: if modname: name = f"{modname}.{name}" + if namespace: + name = f"{namespace}:{name}" return name def add_target_and_index( @@ -227,13 +232,18 @@ def add_target_and_index( ) @staticmethod - def split_fqn(name: str) -> Tuple[str, str]: + def split_fqn(name: str) -> Tuple[str, str, str]: + if ":" in name: + ns, name = name.split(":") + else: + ns = "" + if "." in name: module, name = name.split(".") else: module = "" - return (module, name) + return (ns, module, name) def _object_hierarchy_parts( self, sig_node: desc_signature @@ -251,7 +261,7 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: return "" config = self.env.app.config - modname, name = toc_parts + namespace, modname, name = toc_parts if config.toc_object_entries_show_parents == "domain": ret = name @@ -259,6 +269,10 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: "qapi:module", "" ): ret = f"{modname}.{name}" + if namespace and namespace != self.env.ref_context.get( + "qapi:namespace", "" + ): + ret = f"{namespace}:{ret}" return ret if config.toc_object_entries_show_parents == "hide": return name @@ -334,10 +348,15 @@ def handle_signature(self, sig: str, signode: desc_signature) -> Signature: As such, the only argument here is "sig", which is just the QAPI definition name. """ - modname = self._get_context() + # No module or domain info allowed in the signature! + assert ":" not in sig + assert "." not in sig + namespace, modname = self._get_context() signode["fullname"] = self._get_fqn(sig) + signode["namespace"] = namespace signode["module"] = modname + sig_prefix = self.get_signature_prefix() if sig_prefix: signode += addnodes.desc_annotation( From 9ca404f0043d63043bfed3af8da3adedc062cb13 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:05 -0400 Subject: [PATCH 1022/1179] docs/qapi-domain: add :namespace: override option Akin to the :module: override option, the :namespace: options allows you to forcibly override the contextual namespace associatied with a definition. We don't necessarily actually need this, but I felt compelled to stick close to how the Python domain works that offers context overrides. As of this commit, it is possible to add e.g. ":namespace: QMP" to any QAPI directive to forcibly associate that definition with a given namespace. Signed-off-by: John Snow Message-ID: <20250313044312.189276-5-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/devel/qapi-domain.rst | 2 ++ docs/sphinx/qapi_domain.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst index 1475870ca6c2..51b283277e14 100644 --- a/docs/devel/qapi-domain.rst +++ b/docs/devel/qapi-domain.rst @@ -466,6 +466,8 @@ QAPI standard options All QAPI directives -- *except* for module -- support these common options. +* ``:namespace: name`` -- This option allows you to override the + namespace association of a given definition. * ``:module: modname`` -- Borrowed from the Python domain, this option allows you to override the module association of a given definition. * ``:since: x.y`` -- Allows the documenting of "Since" information, which is diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 48a082d489ae..6485c4320638 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -294,8 +294,9 @@ class QAPIObject(QAPIDescription): ) option_spec.update( { - # Borrowed from the Python domain: - "module": directives.unchanged, # Override contextual module name + # Context overrides: + "namespace": directives.unchanged, + "module": directives.unchanged, # These are QAPI originals: "since": directives.unchanged, "ifcond": directives.unchanged, From 7c7247b252dd8b3911b96451c0eaaebbc6ac0af0 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:06 -0400 Subject: [PATCH 1023/1179] docs/qapi-domain: add qapi:namespace directive Add a new directive that marks the beginning of a QAPI "namespace", for example; "QMP", "QGA" or "QSD". This directive will associate all subsequent QAPI directives in a document with the specified namespace. This does not change the visual display of any of the definitions or index entries, but does change the "Fully Qualified Name" inside the QAPI domain's object table. This allows for two different "namespaces" to define entities with otherwise identical names -- which will come in handy for documenting both QEMU QMP and the QEMU Storage Daemon. Signed-off-by: John Snow Message-ID: <20250313044312.189276-6-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/devel/qapi-domain.rst | 20 +++++++++++++++++++- docs/sphinx/qapi_domain.py | 13 +++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst index 51b283277e14..73e13ab45ca7 100644 --- a/docs/devel/qapi-domain.rst +++ b/docs/devel/qapi-domain.rst @@ -464,7 +464,8 @@ removed in a future version. QAPI standard options --------------------- -All QAPI directives -- *except* for module -- support these common options. +All QAPI directives -- *except* for namespace and module -- support +these common options. * ``:namespace: name`` -- This option allows you to override the namespace association of a given definition. @@ -482,6 +483,23 @@ All QAPI directives -- *except* for module -- support these common options. production code. +qapi:namespace +-------------- + +The ``qapi:namespace`` directive marks the start of a QAPI namespace. It +does not take a content body, nor any options. All subsequent QAPI +directives are associated with the most recent namespace. This affects +the definition's "fully qualified name", allowing two different +namespaces to create an otherwise identically named definition. + +Example:: + + .. qapi:namespace:: QMP + + +This directive has no visible effect. + + qapi:module ----------- diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index 6485c4320638..a204af9b062f 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -38,6 +38,7 @@ from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging +from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_id, make_refnode @@ -645,6 +646,17 @@ def run(self) -> List[Node]: return ret +class QAPINamespace(SphinxDirective): + has_content = False + required_arguments = 1 + + def run(self) -> List[Node]: + namespace = self.arguments[0].strip() + self.env.ref_context["qapi:namespace"] = namespace + + return [] + + class QAPIIndex(Index): """ Index subclass to provide the QAPI definition index. @@ -726,6 +738,7 @@ class QAPIDomain(Domain): # Each of these provides a rST directive, # e.g. .. qapi:module:: block-core directives = { + "namespace": QAPINamespace, "module": QAPIModule, "command": QAPICommand, "event": QAPIEvent, From b1df602ebbd06d56311a77d195284216263b13f8 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:07 -0400 Subject: [PATCH 1024/1179] docs/qapidoc: add :namespace: option to qapi-doc directive Add a :namespace: option to the qapi-doc directive, which inserts a qapi:namespace directive into the start of the generated document. This, in turn, associates all auto-generated definitions by this directive with the specified namespace. The source info for these generated lines are credited to the start of the qapi-doc directive, which isn't precisely correct, but I wasn't sure how to get it more accurate without some re-parsing shenanigans. Signed-off-by: John Snow Message-ID: <20250313044312.189276-7-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/sphinx/qapidoc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 432fef04b057..661b2c4ed0e1 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -451,6 +451,12 @@ def visit_entity(self, ent: QAPISchemaDefinition) -> None: finally: self._curr_ent = None + def set_namespace(self, namespace: str, source: str, lineno: int) -> None: + self.add_line_raw( + f".. qapi:namespace:: {namespace}", source, lineno + 1 + ) + self.ensure_blank_line() + class QAPISchemaGenDepVisitor(QAPISchemaVisitor): """A QAPI schema visitor which adds Sphinx dependencies each module @@ -496,6 +502,7 @@ class QAPIDocDirective(NestedDirective): optional_arguments = 1 option_spec = { "qapifile": directives.unchanged_required, + "namespace": directives.unchanged, "transmogrify": directives.flag, } has_content = False @@ -510,6 +517,11 @@ def transmogrify(self, schema: QAPISchema) -> nodes.Element: vis = Transmogrifier() modules = set() + if "namespace" in self.options: + vis.set_namespace( + self.options["namespace"], *self.get_source_info() + ) + for doc in schema.docs: module_source = doc.info.fname if module_source not in modules: From 7127e14f15fc52b436eb63e482a9f117bd4346d2 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:08 -0400 Subject: [PATCH 1025/1179] docs/qapi_domain: add namespace support to cross-references This patch does three things: 1. Record the current namespace context in pending_xrefs so it can be used for link resolution later, 2. Pass that recorded namespace context to find_obj() when resolving a reference, and 3. Wildly and completely rewrite find_obj(). cross-reference support is expanded to tolerate the presence or absence of either namespace or module, and to cope with the presence or absence of contextual information for either. References now work like this: 1. If the explicit reference target is recorded in the domain's object registry, we link to that target and stop looking. We do this lookup regardless of how fully qualified the target is, which allows direct references to modules (which don't have a module component to their names) or direct references to definitions that may or may not belong to a namespace or module. 2. If contextual information is available from qapi:namespace or qapi:module directives, try using those components to find a direct match to the implied target name. 3. If both prior lookups fail, generate a series of regular expressions looking for wildcard matches in order from most to least specific. Any explicitly provided components (namespace, module) *must* match exactly, but both contextual and entirely omitted components are allowed to differ from the search result. Note that if more than one result is found, Sphinx will emit a warning (a build error for QEMU) and list all of the candidate references. The practical upshot is that in the large majority of cases, namespace and module information is not required when creating simple `references` to definitions from within the same context -- even when identical definitions exist in other contexts. Even when using simple `references` from elsewhere in the QEMU documentation manual, explicit namespace info is not required if there is only one definition by that name. Disambiguation *will* be required from outside of the QAPI documentation when referencing e.g. block-core definitions, which are shared between QEMU QMP and the QEMU Storage Daemon. In that case, there are two options: A: References can be made partially or fully explicit, e.g. `QMP:block-dirty-bitmap-add` will link to the QEMU version of the definition, while `QSD:block-dirty-bitmap-add` would link to the QSD version. B: If all of the references in a document are intended to go to the same place, you can insert a "qapi:namespace:: QMP" directive to influence the fuzzy-searching for later references. Signed-off-by: John Snow Message-ID: <20250313044312.189276-8-jsnow@redhat.com> Acked-by: Markus Armbruster [Commit message typo fixed] Signed-off-by: Markus Armbruster --- docs/devel/qapi-domain.rst | 34 ++++++++-- docs/sphinx/qapi_domain.py | 127 ++++++++++++++++++++++++------------- 2 files changed, 114 insertions(+), 47 deletions(-) diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst index 73e13ab45ca7..4705ba255e06 100644 --- a/docs/devel/qapi-domain.rst +++ b/docs/devel/qapi-domain.rst @@ -400,11 +400,10 @@ Namespaces Mimicking the `Python domain target specification syntax `_, QAPI allows you to specify the fully qualified path for a data -type. QAPI enforces globally unique names, so it's unlikely you'll need -this specific feature, but it may be extended in the near future to -allow referencing identically named commands and data types from -different utilities; i.e. QEMU Storage Daemon vs QMP. +type. +* A namespace can be explicitly provided; + e.g. ``:qapi:type:`QMP:BitmapSyncMode`` * A module can be explicitly provided; ``:qapi:type:`block-core.BitmapSyncMode``` will render to: :qapi:type:`block-core.BitmapSyncMode` @@ -413,6 +412,28 @@ different utilities; i.e. QEMU Storage Daemon vs QMP. will render to: :qapi:type:`~block-core.BitmapSyncMode` +Target resolution +----------------- + +Any cross-reference to a QAPI type, whether using the ```any``` style of +reference or the more explicit ```:qapi:any:`target``` syntax, allows +for the presence or absence of either the namespace or module +information. + +When absent, their value will be inferred from context by the presence +of any ``qapi:namespace`` or ``qapi:module`` directives preceding the +cross-reference. + +If no results are found when using the inferred values, other +namespaces/modules will be searched as a last resort; but any explicitly +provided values must always match in order to succeed. + +This allows for efficient cross-referencing with a minimum of syntax in +the large majority of cases, but additional context or namespace markup +may be required outside of the QAPI reference documents when linking to +items that share a name across multiple documented QAPI schema. + + Custom link text ---------------- @@ -492,6 +513,11 @@ directives are associated with the most recent namespace. This affects the definition's "fully qualified name", allowing two different namespaces to create an otherwise identically named definition. +This directive also influences how reference resolution works for any +references that do not explicity specify a namespace, so this directive +can be used to nudge references into preferring targets from within that +namespace. + Example:: .. qapi:namespace:: QMP diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index a204af9b062f..a8a85a2de366 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -7,6 +7,7 @@ from __future__ import annotations +import re from typing import ( TYPE_CHECKING, List, @@ -94,6 +95,7 @@ def process_link( title: str, target: str, ) -> tuple[str, str]: + refnode["qapi:namespace"] = env.ref_context.get("qapi:namespace") refnode["qapi:module"] = env.ref_context.get("qapi:module") # Cross-references that begin with a tilde adjust the title to @@ -830,40 +832,44 @@ def merge_domaindata( self.objects[fullname] = obj def find_obj( - self, modname: str, name: str, typ: Optional[str] - ) -> list[tuple[str, ObjectEntry]]: + self, namespace: str, modname: str, name: str, typ: Optional[str] + ) -> List[Tuple[str, ObjectEntry]]: """ - Find a QAPI object for "name", perhaps using the given module. + Find a QAPI object for "name", maybe using contextual information. Returns a list of (name, object entry) tuples. - :param modname: The current module context (if any!) - under which we are searching. - :param name: The name of the x-ref to resolve; - may or may not include a leading module. - :param type: The role name of the x-ref we're resolving, if provided. - (This is absent for "any" lookups.) + :param namespace: The current namespace context (if any!) under + which we are searching. + :param modname: The current module context (if any!) under + which we are searching. + :param name: The name of the x-ref to resolve; may or may not + include leading context. + :param type: The role name of the x-ref we're resolving, if + provided. This is absent for "any" role lookups. """ if not name: return [] - names: list[str] = [] - matches: list[tuple[str, ObjectEntry]] = [] + # ## + # what to search for + # ## - fullname = name - if "." in fullname: - # We're searching for a fully qualified reference; - # ignore the contextual module. - pass - elif modname: - # We're searching for something from somewhere; - # try searching the current module first. - # e.g. :qapi:cmd:`query-block` or `query-block` is being searched. - fullname = f"{modname}.{name}" + parts = list(QAPIDescription.split_fqn(name)) + explicit = tuple(bool(x) for x in parts) + + # Fill in the blanks where possible: + if namespace and not parts[0]: + parts[0] = namespace + if modname and not parts[1]: + parts[1] = modname + + implicit_fqn = "" + if all(parts): + implicit_fqn = f"{parts[0]}:{parts[1]}.{parts[2]}" if typ is None: - # type isn't specified, this is a generic xref. - # search *all* qapi-specific object types. + # :any: lookup, search everything: objtypes: List[str] = list(self.object_types) else: # type is specified and will be a role (e.g. obj, mod, cmd) @@ -871,25 +877,57 @@ def find_obj( # using the QAPIDomain.object_types table. objtypes = self.objtypes_for_role(typ, []) - if name in self.objects and self.objects[name].objtype in objtypes: - names = [name] - elif ( - fullname in self.objects - and self.objects[fullname].objtype in objtypes - ): - names = [fullname] - else: - # exact match wasn't found; e.g. we are searching for - # `query-block` from a different (or no) module. - searchname = "." + name - names = [ - oname - for oname in self.objects - if oname.endswith(searchname) - and self.objects[oname].objtype in objtypes - ] + # ## + # search! + # ## + + def _search(needle: str) -> List[str]: + if ( + needle + and needle in self.objects + and self.objects[needle].objtype in objtypes + ): + return [needle] + return [] - matches = [(oname, self.objects[oname]) for oname in names] + if found := _search(name): + # Exact match! + pass + elif found := _search(implicit_fqn): + # Exact match using contextual information to fill in the gaps. + pass + else: + # No exact hits, perform applicable fuzzy searches. + searches = [] + + esc = tuple(re.escape(s) for s in parts) + + # Try searching for ns:*.name or ns:name + if explicit[0] and not explicit[1]: + searches.append(f"^{esc[0]}:([^\\.]+\\.)?{esc[2]}$") + # Try searching for *:module.name or module.name + if explicit[1] and not explicit[0]: + searches.append(f"(^|:){esc[1]}\\.{esc[2]}$") + # Try searching for context-ns:*.name or context-ns:name + if parts[0] and not (explicit[0] or explicit[1]): + searches.append(f"^{esc[0]}:([^\\.]+\\.)?{esc[2]}$") + # Try searching for *:context-mod.name or context-mod.name + if parts[1] and not (explicit[0] or explicit[1]): + searches.append(f"(^|:){esc[1]}\\.{esc[2]}$") + # Try searching for *:name, *.name, or name + if not (explicit[0] or explicit[1]): + searches.append(f"(^|:|\\.){esc[2]}$") + + for search in searches: + if found := [ + oname + for oname in self.objects + if re.search(search, oname) + and self.objects[oname].objtype in objtypes + ]: + break + + matches = [(oname, self.objects[oname]) for oname in found] if len(matches) > 1: matches = [m for m in matches if not m[1].aliased] return matches @@ -904,8 +942,9 @@ def resolve_xref( node: pending_xref, contnode: Element, ) -> nodes.reference | None: + namespace = node.get("qapi:namespace") modname = node.get("qapi:module") - matches = self.find_obj(modname, target, typ) + matches = self.find_obj(namespace, modname, target, typ) if not matches: # Normally, we could pass warn_dangling=True to QAPIXRefRole(), @@ -958,7 +997,9 @@ def resolve_any_xref( contnode: Element, ) -> List[Tuple[str, nodes.reference]]: results: List[Tuple[str, nodes.reference]] = [] - matches = self.find_obj(node.get("qapi:module"), target, None) + matches = self.find_obj( + node.get("qapi:namespace"), node.get("qapi:module"), target, None + ) for name, obj in matches: rolename = self.role_for_objtype(obj.objtype) assert rolename is not None From 25d44f57e17b088fdc4e38042e04c4e9da2c1088 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:09 -0400 Subject: [PATCH 1026/1179] docs/qapi-domain: add namespaced index support Generate an index-per-namespace for the QAPI domain. Due to a limitation with Sphinx's architecture, these indices must be defined during setup time and cannot be dynamically created on-demand when a namespace directive is encountered. Owing to that limitation, add a configuration value to conf.py that specifies which QAPI namespaces we'll generate indices for. Indices will be named after their namespace, e.g. the "QMP" namespace will generate to "qapi-qmp-index.html" and can be referenced using `qapi-qmp-index`. Signed-off-by: John Snow Message-ID: <20250313044312.189276-9-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/conf.py | 3 +++ docs/sphinx/qapi_domain.py | 51 +++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a3f9fa63d946..175491148c32 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -161,6 +161,9 @@ "see also", } +# Due to a limitation in Sphinx, we need to know which indices to +# generate in advance. Adding a namespace here allows that generation. +qapi_namespaces = set() # -- Options for HTML output ---------------------------------------------- diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index a8a85a2de366..c94af5719ca0 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -8,11 +8,13 @@ from __future__ import annotations import re +import types from typing import ( TYPE_CHECKING, List, NamedTuple, Tuple, + Type, cast, ) @@ -669,6 +671,7 @@ class QAPIIndex(Index): name = "index" localname = _("QAPI Index") shortname = _("QAPI Index") + namespace = "" def generate( self, @@ -678,25 +681,20 @@ def generate( content: Dict[str, List[IndexEntry]] = {} collapse = False - # list of all object (name, ObjectEntry) pairs, sorted by name - # (ignoring the module) - objects = sorted( - self.domain.objects.items(), - key=lambda x: x[0].split(".")[-1].lower(), - ) - - for objname, obj in objects: + for objname, obj in self.domain.objects.items(): if docnames and obj.docname not in docnames: continue - # Strip the module name out: - objname = objname.split(".")[-1] + ns, _mod, name = QAPIDescription.split_fqn(objname) + + if self.namespace != ns: + continue # Add an alphabetical entry: - entries = content.setdefault(objname[0].upper(), []) + entries = content.setdefault(name[0].upper(), []) entries.append( IndexEntry( - objname, 0, obj.docname, obj.node_id, obj.objtype, "", "" + name, 0, obj.docname, obj.node_id, obj.objtype, "", "" ) ) @@ -704,10 +702,14 @@ def generate( category = obj.objtype.title() + "s" entries = content.setdefault(category, []) entries.append( - IndexEntry(objname, 0, obj.docname, obj.node_id, "", "", "") + IndexEntry(name, 0, obj.docname, obj.node_id, "", "", "") ) - # alphabetically sort categories; type names first, ABC entries last. + # Sort entries within each category alphabetically + for category in content: + content[category] = sorted(content[category]) + + # Sort the categories themselves; type names first, ABC entries last. sorted_content = sorted( content.items(), key=lambda x: (len(x[0]) == 1, x[0]), @@ -780,6 +782,21 @@ def objects(self) -> Dict[str, ObjectEntry]: ret = self.data.setdefault("objects", {}) return ret # type: ignore[no-any-return] + def setup(self) -> None: + namespaces = set(self.env.app.config.qapi_namespaces) + for namespace in namespaces: + new_index: Type[QAPIIndex] = types.new_class( + f"{namespace}Index", bases=(QAPIIndex,) + ) + new_index.name = f"{namespace.lower()}-index" + new_index.localname = _(f"{namespace} Index") + new_index.shortname = _(f"{namespace} Index") + new_index.namespace = namespace + + self.indices.append(new_index) + + super().setup() + def note_object( self, name: str, @@ -1019,6 +1036,12 @@ def setup(app: Sphinx) -> Dict[str, Any]: "env", # Setting impacts parsing phase types=set, ) + app.add_config_value( + "qapi_namespaces", + set(), + "env", + types=set, + ) app.add_domain(QAPIDomain) return { From 602c90beaedd9abbbf1535c8630293267e6b29c0 Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:10 -0400 Subject: [PATCH 1027/1179] docs: add QAPI namespace "QMP" to qemu-qmp-ref This also creates the qapi-qmp-index.html index and cross-reference target. Signed-off-by: John Snow Message-ID: <20250313044312.189276-10-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/conf.py | 4 +++- docs/interop/qemu-qmp-ref.rst | 1 + qapi/qapi-schema.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 175491148c32..9a86e84a8049 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -163,7 +163,9 @@ # Due to a limitation in Sphinx, we need to know which indices to # generate in advance. Adding a namespace here allows that generation. -qapi_namespaces = set() +qapi_namespaces = { + "QMP", +} # -- Options for HTML output ---------------------------------------------- diff --git a/docs/interop/qemu-qmp-ref.rst b/docs/interop/qemu-qmp-ref.rst index e95eeac45e20..ef8792b53f55 100644 --- a/docs/interop/qemu-qmp-ref.rst +++ b/docs/interop/qemu-qmp-ref.rst @@ -8,3 +8,4 @@ QEMU QMP Reference Manual .. qapi-doc:: qapi/qapi-schema.json :transmogrify: + :namespace: QMP diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index 4475e81cc3eb..c41c01eb2ab9 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -5,7 +5,7 @@ # # This document describes all commands currently supported by QMP. # -# For locating a particular item, please see the `qapi-index`. +# For locating a particular item, please see the `qapi-qmp-index`. # # Most of the time their usage is exactly the same as in the user # Monitor, this means that any other document which also describe From d85f7efe1f16c51b9c016ebc79f7c4081486642e Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:11 -0400 Subject: [PATCH 1028/1179] docs: disambiguate references in qapi-domain.rst Before we enable the QGA and QSD namespaces, we need to disambiguate some of the references that would become ambiguous as a result! Signed-off-by: John Snow Message-ID: <20250313044312.189276-11-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/devel/qapi-domain.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst index 4705ba255e06..a748529f5156 100644 --- a/docs/devel/qapi-domain.rst +++ b/docs/devel/qapi-domain.rst @@ -385,13 +385,13 @@ Type names in references can be surrounded by brackets, like ``[typename]``, to indicate an array of that type. The cross-reference will apply only to the type name between the brackets. For example; ``:qapi:type:`[Qcow2BitmapInfoFlags]``` renders to: -:qapi:type:`[Qcow2BitmapInfoFlags]` +:qapi:type:`[QMP:Qcow2BitmapInfoFlags]` To indicate an optional argument/member in a field list, the type name can be suffixed with ``?``. The cross-reference will be transformed to "type, Optional" with the link applying only to the type name. For example; ``:qapi:type:`BitmapSyncMode?``` renders to: -:qapi:type:`BitmapSyncMode?` +:qapi:type:`QMP:BitmapSyncMode?` Namespaces @@ -405,11 +405,11 @@ type. * A namespace can be explicitly provided; e.g. ``:qapi:type:`QMP:BitmapSyncMode`` * A module can be explicitly provided; - ``:qapi:type:`block-core.BitmapSyncMode``` will render to: - :qapi:type:`block-core.BitmapSyncMode` + ``:qapi:type:`QMP:block-core.BitmapSyncMode``` will render to: + :qapi:type:`QMP:block-core.BitmapSyncMode` * If you don't want to display the "fully qualified" name, it can be - prefixed with a tilde; ``:qapi:type:`~block-core.BitmapSyncMode``` - will render to: :qapi:type:`~block-core.BitmapSyncMode` + prefixed with a tilde; ``:qapi:type:`~QMP:block-core.BitmapSyncMode``` + will render to: :qapi:type:`~QMP:block-core.BitmapSyncMode` Target resolution @@ -444,7 +444,7 @@ using the ``custom text `` syntax. For example, ``:qapi:cmd:`Merge dirty bitmaps ``` will render as: :qapi:cmd:`Merge dirty -bitmaps ` +bitmaps ` Directives From a6af54434400099b8afd59ba036cf9a662006d1e Mon Sep 17 00:00:00 2001 From: John Snow Date: Thu, 13 Mar 2025 00:43:12 -0400 Subject: [PATCH 1029/1179] docs: enable transmogrifier for QSD and QGA This also creates the `qapi-qsd-index` and `qapi-qga-index` QMP indices. Signed-off-by: John Snow Message-ID: <20250313044312.189276-12-jsnow@redhat.com> Acked-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/conf.py | 2 ++ docs/interop/qemu-ga-ref.rst | 2 ++ docs/interop/qemu-storage-daemon-qmp-ref.rst | 2 ++ qga/qapi-schema.json | 3 +++ storage-daemon/qapi/qapi-schema.json | 8 ++++++++ 5 files changed, 17 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 9a86e84a8049..7b5712e122fc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -164,7 +164,9 @@ # Due to a limitation in Sphinx, we need to know which indices to # generate in advance. Adding a namespace here allows that generation. qapi_namespaces = { + "QGA", "QMP", + "QSD", } # -- Options for HTML output ---------------------------------------------- diff --git a/docs/interop/qemu-ga-ref.rst b/docs/interop/qemu-ga-ref.rst index 032d49245525..19b5c7a54985 100644 --- a/docs/interop/qemu-ga-ref.rst +++ b/docs/interop/qemu-ga-ref.rst @@ -5,3 +5,5 @@ QEMU Guest Agent Protocol Reference :depth: 3 .. qapi-doc:: qga/qapi-schema.json + :transmogrify: + :namespace: QGA diff --git a/docs/interop/qemu-storage-daemon-qmp-ref.rst b/docs/interop/qemu-storage-daemon-qmp-ref.rst index 9fed68152f5a..d0228d63b8d9 100644 --- a/docs/interop/qemu-storage-daemon-qmp-ref.rst +++ b/docs/interop/qemu-storage-daemon-qmp-ref.rst @@ -5,3 +5,5 @@ QEMU Storage Daemon QMP Reference Manual :depth: 3 .. qapi-doc:: storage-daemon/qapi/qapi-schema.json + :transmogrify: + :namespace: QSD diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 995594aaf438..35ec0e7db31d 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -3,6 +3,9 @@ ## # = QEMU guest agent protocol commands and structs +# +# For a concise listing of all commands, events, and types in the QEMU +# guest agent, please consult the `qapi-qga-index`. ## { 'pragma': { 'doc-required': true } } diff --git a/storage-daemon/qapi/qapi-schema.json b/storage-daemon/qapi/qapi-schema.json index f10c94949068..2a562ee32e52 100644 --- a/storage-daemon/qapi/qapi-schema.json +++ b/storage-daemon/qapi/qapi-schema.json @@ -13,6 +13,14 @@ # the array type in the main schema, even if it is unused outside of the # storage daemon. +## +# = QEMU storage daemon protocol commands and structs +# +# For a concise listing of all commands, events, and types in the QEMU +# storage daemon, please consult the `qapi-qsd-index`. +## + + { 'include': '../../qapi/pragma.json' } # Documentation generated with qapi-gen.py is in source order, with From eae0c3b659fbad5168c9bb9784b49d255185e35c Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:05 +0000 Subject: [PATCH 1030/1179] target/arm: Move A32_BANKED_REG_{GET,SET} macros to cpregs.h The A32_BANKED_REG_{GET,SET} macros are only used inside target/arm; move their definitions to cpregs.h. There's no need to have them defined in all the code that includes cpu.h. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- target/arm/cpregs.h | 28 ++++++++++++++++++++++++++++ target/arm/cpu.h | 27 --------------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/target/arm/cpregs.h b/target/arm/cpregs.h index 52377c6eb50f..2183de8eda69 100644 --- a/target/arm/cpregs.h +++ b/target/arm/cpregs.h @@ -1157,4 +1157,32 @@ static inline bool arm_cpreg_traps_in_nv(const ARMCPRegInfo *ri) return ri->opc1 == 4 || ri->opc1 == 5; } +/* Macros for accessing a specified CP register bank */ +#define A32_BANKED_REG_GET(_env, _regname, _secure) \ + ((_secure) ? (_env)->cp15._regname##_s : (_env)->cp15._regname##_ns) + +#define A32_BANKED_REG_SET(_env, _regname, _secure, _val) \ + do { \ + if (_secure) { \ + (_env)->cp15._regname##_s = (_val); \ + } else { \ + (_env)->cp15._regname##_ns = (_val); \ + } \ + } while (0) + +/* + * Macros for automatically accessing a specific CP register bank depending on + * the current secure state of the system. These macros are not intended for + * supporting instruction translation reads/writes as these are dependent + * solely on the SCR.NS bit and not the mode. + */ +#define A32_BANKED_CURRENT_REG_GET(_env, _regname) \ + A32_BANKED_REG_GET((_env), _regname, \ + (arm_is_secure(_env) && !arm_el_is_aa64((_env), 3))) + +#define A32_BANKED_CURRENT_REG_SET(_env, _regname, _val) \ + A32_BANKED_REG_SET((_env), _regname, \ + (arm_is_secure(_env) && !arm_el_is_aa64((_env), 3)), \ + (_val)) + #endif /* TARGET_ARM_CPREGS_H */ diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 8f52380c88c3..15d3a79b0af7 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -2684,33 +2684,6 @@ static inline bool access_secure_reg(CPUARMState *env) return ret; } -/* Macros for accessing a specified CP register bank */ -#define A32_BANKED_REG_GET(_env, _regname, _secure) \ - ((_secure) ? (_env)->cp15._regname##_s : (_env)->cp15._regname##_ns) - -#define A32_BANKED_REG_SET(_env, _regname, _secure, _val) \ - do { \ - if (_secure) { \ - (_env)->cp15._regname##_s = (_val); \ - } else { \ - (_env)->cp15._regname##_ns = (_val); \ - } \ - } while (0) - -/* Macros for automatically accessing a specific CP register bank depending on - * the current secure state of the system. These macros are not intended for - * supporting instruction translation reads/writes as these are dependent - * solely on the SCR.NS bit and not the mode. - */ -#define A32_BANKED_CURRENT_REG_GET(_env, _regname) \ - A32_BANKED_REG_GET((_env), _regname, \ - (arm_is_secure(_env) && !arm_el_is_aa64((_env), 3))) - -#define A32_BANKED_CURRENT_REG_SET(_env, _regname, _val) \ - A32_BANKED_REG_SET((_env), _regname, \ - (arm_is_secure(_env) && !arm_el_is_aa64((_env), 3)), \ - (_val)) - uint32_t arm_phys_excp_target_el(CPUState *cs, uint32_t excp_idx, uint32_t cur_el, bool secure); From 23560ada94bd22cb9e8d27b7e9389f6369f6d74d Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:05 +0000 Subject: [PATCH 1031/1179] target/arm: Un-inline access_secure_reg() We would like to move arm_el_is_aa64() to internals.h; however, it is used by access_secure_reg(). Make that function not be inline, so that it can stay in cpu.h. access_secure_reg() is used only in two places: * in hflags.c * in the user-mode arm emulators, to decide whether to store the TLS value in the secure or non-secure banked field The second of these is not on a super-hot path that would care about the inlining (and incidentally will always use the NS banked field because our user-mode CPUs never set ARM_FEATURE_EL3); put the definition of access_secure_reg() in hflags.c, near its only use inside target/arm. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- target/arm/cpu.h | 12 +++--------- target/arm/tcg/hflags.c | 9 +++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 15d3a79b0af7..12d2706f2b5e 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -2668,21 +2668,15 @@ static inline bool arm_el_is_aa64(CPUARMState *env, int el) return aa64; } -/* Function for determining whether guest cp register reads and writes should +/* + * Function for determining whether guest cp register reads and writes should * access the secure or non-secure bank of a cp register. When EL3 is * operating in AArch32 state, the NS-bit determines whether the secure * instance of a cp register should be used. When EL3 is AArch64 (or if * it doesn't exist at all) then there is no register banking, and all * accesses are to the non-secure version. */ -static inline bool access_secure_reg(CPUARMState *env) -{ - bool ret = (arm_feature(env, ARM_FEATURE_EL3) && - !arm_el_is_aa64(env, 3) && - !(env->cp15.scr_el3 & SCR_NS)); - - return ret; -} +bool access_secure_reg(CPUARMState *env); uint32_t arm_phys_excp_target_el(CPUState *cs, uint32_t excp_idx, uint32_t cur_el, bool secure); diff --git a/target/arm/tcg/hflags.c b/target/arm/tcg/hflags.c index 9e6a1869f94e..8d79b8b7ae1f 100644 --- a/target/arm/tcg/hflags.c +++ b/target/arm/tcg/hflags.c @@ -63,6 +63,15 @@ static bool aprofile_require_alignment(CPUARMState *env, int el, uint64_t sctlr) #endif } +bool access_secure_reg(CPUARMState *env) +{ + bool ret = (arm_feature(env, ARM_FEATURE_EL3) && + !arm_el_is_aa64(env, 3) && + !(env->cp15.scr_el3 & SCR_NS)); + + return ret; +} + static CPUARMTBFlags rebuild_hflags_common(CPUARMState *env, int fp_el, ARMMMUIdx mmu_idx, CPUARMTBFlags flags) From fe0f88ab87632075ae9404685672a8f172a3ae6f Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:05 +0000 Subject: [PATCH 1032/1179] linux-user/aarch64: Remove unused get/put_user macros At the top of linux-user/aarch64/cpu_loop.c we define a set of macros for reading and writing data and code words, but we never use these macros. Delete them. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- linux-user/aarch64/cpu_loop.c | 48 ----------------------------------- 1 file changed, 48 deletions(-) diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c index c5d8a483a3fb..fea43cefa6bc 100644 --- a/linux-user/aarch64/cpu_loop.c +++ b/linux-user/aarch64/cpu_loop.c @@ -27,54 +27,6 @@ #include "target/arm/syndrome.h" #include "target/arm/cpu-features.h" -#define get_user_code_u32(x, gaddr, env) \ - ({ abi_long __r = get_user_u32((x), (gaddr)); \ - if (!__r && bswap_code(arm_sctlr_b(env))) { \ - (x) = bswap32(x); \ - } \ - __r; \ - }) - -#define get_user_code_u16(x, gaddr, env) \ - ({ abi_long __r = get_user_u16((x), (gaddr)); \ - if (!__r && bswap_code(arm_sctlr_b(env))) { \ - (x) = bswap16(x); \ - } \ - __r; \ - }) - -#define get_user_data_u32(x, gaddr, env) \ - ({ abi_long __r = get_user_u32((x), (gaddr)); \ - if (!__r && arm_cpu_bswap_data(env)) { \ - (x) = bswap32(x); \ - } \ - __r; \ - }) - -#define get_user_data_u16(x, gaddr, env) \ - ({ abi_long __r = get_user_u16((x), (gaddr)); \ - if (!__r && arm_cpu_bswap_data(env)) { \ - (x) = bswap16(x); \ - } \ - __r; \ - }) - -#define put_user_data_u32(x, gaddr, env) \ - ({ typeof(x) __x = (x); \ - if (arm_cpu_bswap_data(env)) { \ - __x = bswap32(__x); \ - } \ - put_user_u32(__x, (gaddr)); \ - }) - -#define put_user_data_u16(x, gaddr, env) \ - ({ typeof(x) __x = (x); \ - if (arm_cpu_bswap_data(env)) { \ - __x = bswap16(__x); \ - } \ - put_user_u16(__x, (gaddr)); \ - }) - /* AArch64 main loop */ void cpu_loop(CPUARMState *env) { From 63d8b11d0aeb84ba53510cdf66612940a372451f Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:06 +0000 Subject: [PATCH 1033/1179] linux-user/arm: Remove unused get_put_user macros In linux-user/arm/cpu_loop.c we define a full set of get/put macros for both code and data (since the endianness handling is different between the two). However the only one we actually use is get_user_code_u32(). Remove the rest. We leave a comment noting how data-side accesses should be handled for big-endian, because that's a subtle point and we just removed the macros that were effectively documenting it. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- linux-user/arm/cpu_loop.c | 43 ++++----------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c index 10d8561f9b97..7416e3216ea1 100644 --- a/linux-user/arm/cpu_loop.c +++ b/linux-user/arm/cpu_loop.c @@ -36,45 +36,10 @@ __r; \ }) -#define get_user_code_u16(x, gaddr, env) \ - ({ abi_long __r = get_user_u16((x), (gaddr)); \ - if (!__r && bswap_code(arm_sctlr_b(env))) { \ - (x) = bswap16(x); \ - } \ - __r; \ - }) - -#define get_user_data_u32(x, gaddr, env) \ - ({ abi_long __r = get_user_u32((x), (gaddr)); \ - if (!__r && arm_cpu_bswap_data(env)) { \ - (x) = bswap32(x); \ - } \ - __r; \ - }) - -#define get_user_data_u16(x, gaddr, env) \ - ({ abi_long __r = get_user_u16((x), (gaddr)); \ - if (!__r && arm_cpu_bswap_data(env)) { \ - (x) = bswap16(x); \ - } \ - __r; \ - }) - -#define put_user_data_u32(x, gaddr, env) \ - ({ typeof(x) __x = (x); \ - if (arm_cpu_bswap_data(env)) { \ - __x = bswap32(__x); \ - } \ - put_user_u32(__x, (gaddr)); \ - }) - -#define put_user_data_u16(x, gaddr, env) \ - ({ typeof(x) __x = (x); \ - if (arm_cpu_bswap_data(env)) { \ - __x = bswap16(__x); \ - } \ - put_user_u16(__x, (gaddr)); \ - }) +/* + * Note that if we need to do data accesses here, they should do a + * bswap if arm_cpu_bswap_data() returns true. + */ /* * Similar to code in accel/tcg/user-exec.c, but outside the execution loop. From fefc1220ad68d816627aebc393cbf2cb34ff6924 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:06 +0000 Subject: [PATCH 1034/1179] target/arm: Move arm_cpu_data_is_big_endian() etc to internals.h The arm_cpu_data_is_big_endian() and related functions are now used only in target/arm; they can be moved to internals.h. The motivation here is that we would like to move arm_current_el() to internals.h. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- target/arm/cpu.h | 48 ------------------------------------------ target/arm/internals.h | 48 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 12d2706f2b5e..8a59f705167e 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -3032,47 +3032,6 @@ static inline bool arm_sctlr_b(CPUARMState *env) uint64_t arm_sctlr(CPUARMState *env, int el); -static inline bool arm_cpu_data_is_big_endian_a32(CPUARMState *env, - bool sctlr_b) -{ -#ifdef CONFIG_USER_ONLY - /* - * In system mode, BE32 is modelled in line with the - * architecture (as word-invariant big-endianness), where loads - * and stores are done little endian but from addresses which - * are adjusted by XORing with the appropriate constant. So the - * endianness to use for the raw data access is not affected by - * SCTLR.B. - * In user mode, however, we model BE32 as byte-invariant - * big-endianness (because user-only code cannot tell the - * difference), and so we need to use a data access endianness - * that depends on SCTLR.B. - */ - if (sctlr_b) { - return true; - } -#endif - /* In 32bit endianness is determined by looking at CPSR's E bit */ - return env->uncached_cpsr & CPSR_E; -} - -static inline bool arm_cpu_data_is_big_endian_a64(int el, uint64_t sctlr) -{ - return sctlr & (el ? SCTLR_EE : SCTLR_E0E); -} - -/* Return true if the processor is in big-endian mode. */ -static inline bool arm_cpu_data_is_big_endian(CPUARMState *env) -{ - if (!is_a64(env)) { - return arm_cpu_data_is_big_endian_a32(env, arm_sctlr_b(env)); - } else { - int cur_el = arm_current_el(env); - uint64_t sctlr = arm_sctlr(env, cur_el); - return arm_cpu_data_is_big_endian_a64(cur_el, sctlr); - } -} - #include "exec/cpu-all.h" /* @@ -3258,13 +3217,6 @@ static inline bool bswap_code(bool sctlr_b) #endif } -#ifdef CONFIG_USER_ONLY -static inline bool arm_cpu_bswap_data(CPUARMState *env) -{ - return TARGET_BIG_ENDIAN ^ arm_cpu_data_is_big_endian(env); -} -#endif - void cpu_get_tb_cpu_state(CPUARMState *env, vaddr *pc, uint64_t *cs_base, uint32_t *flags); diff --git a/target/arm/internals.h b/target/arm/internals.h index bb9623891923..c2c59e60309c 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -392,6 +392,54 @@ static inline FloatRoundMode arm_rmode_to_sf(ARMFPRounding rmode) return arm_rmode_to_sf_map[rmode]; } +static inline bool arm_cpu_data_is_big_endian_a32(CPUARMState *env, + bool sctlr_b) +{ +#ifdef CONFIG_USER_ONLY + /* + * In system mode, BE32 is modelled in line with the + * architecture (as word-invariant big-endianness), where loads + * and stores are done little endian but from addresses which + * are adjusted by XORing with the appropriate constant. So the + * endianness to use for the raw data access is not affected by + * SCTLR.B. + * In user mode, however, we model BE32 as byte-invariant + * big-endianness (because user-only code cannot tell the + * difference), and so we need to use a data access endianness + * that depends on SCTLR.B. + */ + if (sctlr_b) { + return true; + } +#endif + /* In 32bit endianness is determined by looking at CPSR's E bit */ + return env->uncached_cpsr & CPSR_E; +} + +static inline bool arm_cpu_data_is_big_endian_a64(int el, uint64_t sctlr) +{ + return sctlr & (el ? SCTLR_EE : SCTLR_E0E); +} + +/* Return true if the processor is in big-endian mode. */ +static inline bool arm_cpu_data_is_big_endian(CPUARMState *env) +{ + if (!is_a64(env)) { + return arm_cpu_data_is_big_endian_a32(env, arm_sctlr_b(env)); + } else { + int cur_el = arm_current_el(env); + uint64_t sctlr = arm_sctlr(env, cur_el); + return arm_cpu_data_is_big_endian_a64(cur_el, sctlr); + } +} + +#ifdef CONFIG_USER_ONLY +static inline bool arm_cpu_bswap_data(CPUARMState *env) +{ + return TARGET_BIG_ENDIAN ^ arm_cpu_data_is_big_endian(env); +} +#endif + static inline void aarch64_save_sp(CPUARMState *env, int el) { if (env->pstate & PSTATE_SP) { From 2beb051191b526608e0f269559962f4d2f618850 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:07 +0000 Subject: [PATCH 1035/1179] target/arm: Move arm_current_el() and arm_el_is_aa64() to internals.h The functions arm_current_el() and arm_el_is_aa64() are used only in target/arm and in hw/intc/arm_gicv3_cpuif.c. They're functions that query internal state of the CPU. Move them out of cpu.h and into internals.h. This means we need to include internals.h in arm_gicv3_cpuif.c, but this is justifiable because that file is implementing the GICv3 CPU interface, which really is part of the CPU proper; we just ended up implementing it in code in hw/intc/ for historical reasons. The motivation for this move is that we'd like to change arm_el_is_aa64() to add a condition that uses cpu_isar_feature(); but we don't want to include cpu-features.h in cpu.h. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- hw/intc/arm_gicv3_cpuif.c | 1 + target/arm/arch_dump.c | 1 + target/arm/cpu.h | 66 -------------------------------------- target/arm/internals.h | 67 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 66 deletions(-) diff --git a/hw/intc/arm_gicv3_cpuif.c b/hw/intc/arm_gicv3_cpuif.c index 7f1d071c198b..de37465bc879 100644 --- a/hw/intc/arm_gicv3_cpuif.c +++ b/hw/intc/arm_gicv3_cpuif.c @@ -22,6 +22,7 @@ #include "cpu.h" #include "target/arm/cpregs.h" #include "target/arm/cpu-features.h" +#include "target/arm/internals.h" #include "system/tcg.h" #include "system/qtest.h" diff --git a/target/arm/arch_dump.c b/target/arm/arch_dump.c index 5c943dc27b5e..c40df4e7fd74 100644 --- a/target/arm/arch_dump.c +++ b/target/arm/arch_dump.c @@ -23,6 +23,7 @@ #include "elf.h" #include "system/dump.h" #include "cpu-features.h" +#include "internals.h" /* struct user_pt_regs from arch/arm64/include/uapi/asm/ptrace.h */ struct aarch64_user_regs { diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 8a59f705167e..a8177c6c2e89 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -2635,39 +2635,6 @@ uint64_t arm_hcr_el2_eff_secstate(CPUARMState *env, ARMSecuritySpace space); uint64_t arm_hcr_el2_eff(CPUARMState *env); uint64_t arm_hcrx_el2_eff(CPUARMState *env); -/* Return true if the specified exception level is running in AArch64 state. */ -static inline bool arm_el_is_aa64(CPUARMState *env, int el) -{ - /* This isn't valid for EL0 (if we're in EL0, is_a64() is what you want, - * and if we're not in EL0 then the state of EL0 isn't well defined.) - */ - assert(el >= 1 && el <= 3); - bool aa64 = arm_feature(env, ARM_FEATURE_AARCH64); - - /* The highest exception level is always at the maximum supported - * register width, and then lower levels have a register width controlled - * by bits in the SCR or HCR registers. - */ - if (el == 3) { - return aa64; - } - - if (arm_feature(env, ARM_FEATURE_EL3) && - ((env->cp15.scr_el3 & SCR_NS) || !(env->cp15.scr_el3 & SCR_EEL2))) { - aa64 = aa64 && (env->cp15.scr_el3 & SCR_RW); - } - - if (el == 2) { - return aa64; - } - - if (arm_is_el2_enabled(env)) { - aa64 = aa64 && (env->cp15.hcr_el2 & HCR_RW); - } - - return aa64; -} - /* * Function for determining whether guest cp register reads and writes should * access the secure or non-secure bank of a cp register. When EL3 is @@ -2699,39 +2666,6 @@ static inline bool arm_v7m_is_handler_mode(CPUARMState *env) return env->v7m.exception != 0; } -/* Return the current Exception Level (as per ARMv8; note that this differs - * from the ARMv7 Privilege Level). - */ -static inline int arm_current_el(CPUARMState *env) -{ - if (arm_feature(env, ARM_FEATURE_M)) { - return arm_v7m_is_handler_mode(env) || - !(env->v7m.control[env->v7m.secure] & 1); - } - - if (is_a64(env)) { - return extract32(env->pstate, 2, 2); - } - - switch (env->uncached_cpsr & 0x1f) { - case ARM_CPU_MODE_USR: - return 0; - case ARM_CPU_MODE_HYP: - return 2; - case ARM_CPU_MODE_MON: - return 3; - default: - if (arm_is_secure(env) && !arm_el_is_aa64(env, 3)) { - /* If EL3 is 32-bit then all secure privileged modes run in - * EL3 - */ - return 3; - } - - return 1; - } -} - /** * write_list_to_cpustate * @cpu: ARMCPU diff --git a/target/arm/internals.h b/target/arm/internals.h index c2c59e60309c..d161a3e396bc 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -392,6 +392,73 @@ static inline FloatRoundMode arm_rmode_to_sf(ARMFPRounding rmode) return arm_rmode_to_sf_map[rmode]; } +/* Return true if the specified exception level is running in AArch64 state. */ +static inline bool arm_el_is_aa64(CPUARMState *env, int el) +{ + /* + * This isn't valid for EL0 (if we're in EL0, is_a64() is what you want, + * and if we're not in EL0 then the state of EL0 isn't well defined.) + */ + assert(el >= 1 && el <= 3); + bool aa64 = arm_feature(env, ARM_FEATURE_AARCH64); + + /* + * The highest exception level is always at the maximum supported + * register width, and then lower levels have a register width controlled + * by bits in the SCR or HCR registers. + */ + if (el == 3) { + return aa64; + } + + if (arm_feature(env, ARM_FEATURE_EL3) && + ((env->cp15.scr_el3 & SCR_NS) || !(env->cp15.scr_el3 & SCR_EEL2))) { + aa64 = aa64 && (env->cp15.scr_el3 & SCR_RW); + } + + if (el == 2) { + return aa64; + } + + if (arm_is_el2_enabled(env)) { + aa64 = aa64 && (env->cp15.hcr_el2 & HCR_RW); + } + + return aa64; +} + +/* + * Return the current Exception Level (as per ARMv8; note that this differs + * from the ARMv7 Privilege Level). + */ +static inline int arm_current_el(CPUARMState *env) +{ + if (arm_feature(env, ARM_FEATURE_M)) { + return arm_v7m_is_handler_mode(env) || + !(env->v7m.control[env->v7m.secure] & 1); + } + + if (is_a64(env)) { + return extract32(env->pstate, 2, 2); + } + + switch (env->uncached_cpsr & 0x1f) { + case ARM_CPU_MODE_USR: + return 0; + case ARM_CPU_MODE_HYP: + return 2; + case ARM_CPU_MODE_MON: + return 3; + default: + if (arm_is_secure(env) && !arm_el_is_aa64(env, 3)) { + /* If EL3 is 32-bit then all secure privileged modes run in EL3 */ + return 3; + } + + return 1; + } +} + static inline bool arm_cpu_data_is_big_endian_a32(CPUARMState *env, bool sctlr_b) { From 5d71c6820f3b91763b5807311969cc0362d457d9 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:07 +0000 Subject: [PATCH 1036/1179] target/arm: SCR_EL3.RW should be treated as 1 if EL2 doesn't support AArch32 The definition of SCR_EL3.RW says that its effective value is 1 if: - EL2 is implemented and does not support AArch32, and SCR_EL3.NS is 1 - the effective value of SCR_EL3.{EEL2,NS} is {1,0} (i.e. we are Secure and Secure EL2 is disabled) We implement the second of these in arm_el_is_aa64(), but forgot the first. Provide a new function arm_scr_rw_eff() to return the effective value of SCR_EL3.RW, and use it in arm_el_is_aa64() and the other places that currently look directly at the bit value. (scr_write() enforces that the RW bit is RAO/WI if neither EL1 nor EL2 have AArch32 support, but if EL1 does but EL2 does not then the bit must still be writeable.) This will mean that if code at EL3 attempts to perform an exception return to AArch32 EL2 when EL2 is AArch64-only we will correctly handle this as an illegal exception return: it will be caught by the "return to an EL which is configured for a different register width" check in HELPER(exception_return). We do already have some CPU types which don't implement AArch32 above EL0, so this is technically a bug; it doesn't seem worth backporting to stable because no sensible guest code will be deliberately attempting to set the RW bit to a value corresponding to an unimplemented execution state and then checking that we did the right thing. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- target/arm/helper.c | 4 ++-- target/arm/internals.h | 26 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index f0ead22937bf..3df7d5347cb8 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -9818,7 +9818,7 @@ uint32_t arm_phys_excp_target_el(CPUState *cs, uint32_t excp_idx, uint64_t hcr_el2; if (arm_feature(env, ARM_FEATURE_EL3)) { - rw = ((env->cp15.scr_el3 & SCR_RW) == SCR_RW); + rw = arm_scr_rw_eff(env); } else { /* * Either EL2 is the highest EL (and so the EL2 register width @@ -10627,7 +10627,7 @@ static void arm_cpu_do_interrupt_aarch64(CPUState *cs) switch (new_el) { case 3: - is_aa64 = (env->cp15.scr_el3 & SCR_RW) != 0; + is_aa64 = arm_scr_rw_eff(env); break; case 2: hcr = arm_hcr_el2_eff(env); diff --git a/target/arm/internals.h b/target/arm/internals.h index d161a3e396bc..28585c07555b 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -392,6 +392,27 @@ static inline FloatRoundMode arm_rmode_to_sf(ARMFPRounding rmode) return arm_rmode_to_sf_map[rmode]; } +/* Return the effective value of SCR_EL3.RW */ +static inline bool arm_scr_rw_eff(CPUARMState *env) +{ + /* + * SCR_EL3.RW has an effective value of 1 if: + * - we are NS and EL2 is implemented but doesn't support AArch32 + * - we are S and EL2 is enabled (in which case it must be AArch64) + */ + ARMCPU *cpu = env_archcpu(env); + + if (env->cp15.scr_el3 & SCR_RW) { + return true; + } + if (env->cp15.scr_el3 & SCR_NS) { + return arm_feature(env, ARM_FEATURE_EL2) && + !cpu_isar_feature(aa64_aa32_el2, cpu); + } else { + return env->cp15.scr_el3 & SCR_EEL2; + } +} + /* Return true if the specified exception level is running in AArch64 state. */ static inline bool arm_el_is_aa64(CPUARMState *env, int el) { @@ -411,9 +432,8 @@ static inline bool arm_el_is_aa64(CPUARMState *env, int el) return aa64; } - if (arm_feature(env, ARM_FEATURE_EL3) && - ((env->cp15.scr_el3 & SCR_NS) || !(env->cp15.scr_el3 & SCR_EEL2))) { - aa64 = aa64 && (env->cp15.scr_el3 & SCR_RW); + if (arm_feature(env, ARM_FEATURE_EL3)) { + aa64 = aa64 && arm_scr_rw_eff(env); } if (el == 2) { From 1632a2017f682a9dc6ce51756b9765af07977873 Mon Sep 17 00:00:00 2001 From: Steve Sistare Date: Fri, 7 Mar 2025 12:55:51 -0800 Subject: [PATCH 1037/1179] migration: cpr_is_incoming Define the cpr_is_incoming helper, to be used in several cpr fix patches. Signed-off-by: Steve Sistare Reviewed-by: Peter Xu Reviewed-by: Fabiano Rosas Message-ID: <1741380954-341079-2-git-send-email-steven.sistare@oracle.com> Signed-off-by: Fabiano Rosas --- include/migration/cpr.h | 1 + migration/cpr.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/migration/cpr.h b/include/migration/cpr.h index 3a6deb793330..7561fc75adc5 100644 --- a/include/migration/cpr.h +++ b/include/migration/cpr.h @@ -21,6 +21,7 @@ int cpr_find_fd(const char *name, int id); MigMode cpr_get_incoming_mode(void); void cpr_set_incoming_mode(MigMode mode); +bool cpr_is_incoming(void); int cpr_state_save(MigrationChannel *channel, Error **errp); int cpr_state_load(MigrationChannel *channel, Error **errp); diff --git a/migration/cpr.c b/migration/cpr.c index 180faab247f4..42c46563e522 100644 --- a/migration/cpr.c +++ b/migration/cpr.c @@ -128,6 +128,11 @@ void cpr_set_incoming_mode(MigMode mode) incoming_mode = mode; } +bool cpr_is_incoming(void) +{ + return incoming_mode != MIG_MODE_NONE; +} + int cpr_state_save(MigrationChannel *channel, Error **errp) { int ret; From e56ba1878fefe7babff76ff399311ae5e399c5c5 Mon Sep 17 00:00:00 2001 From: Steve Sistare Date: Fri, 7 Mar 2025 12:55:52 -0800 Subject: [PATCH 1038/1179] pflash: fix cpr During normal migration, new QEMU creates and initializes memory regions, then loads the preserved contents of the region from vmstate. During CPR, memory regions are preserved in place, then the realize method initializes the regions contents, losing the old contents. To fix, skip the re-init during CPR. Signed-off-by: Steve Sistare Reviewed-by: Fabiano Rosas Message-ID: <1741380954-341079-3-git-send-email-steven.sistare@oracle.com> Signed-off-by: Fabiano Rosas --- hw/block/block.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hw/block/block.c b/hw/block/block.c index 1d405e02bf87..2e10611d95a6 100644 --- a/hw/block/block.c +++ b/hw/block/block.c @@ -12,6 +12,7 @@ #include "system/blockdev.h" #include "system/block-backend.h" #include "hw/block/block.h" +#include "migration/cpr.h" #include "qapi/error.h" #include "qapi/qapi-types-block.h" @@ -66,6 +67,10 @@ bool blk_check_size_and_read_all(BlockBackend *blk, DeviceState *dev, int ret; g_autofree char *dev_id = NULL; + if (cpr_is_incoming()) { + return true; + } + blk_len = blk_getlength(blk); if (blk_len < 0) { error_setg_errno(errp, -blk_len, From b42f28111e081ab1fd370e92ee78a461027590f0 Mon Sep 17 00:00:00 2001 From: Steve Sistare Date: Fri, 7 Mar 2025 12:55:53 -0800 Subject: [PATCH 1039/1179] hw/loader: fix roms during cpr During normal migration, new QEMU creates and initializes memory regions, then loads the preserved contents of the region from vmstate. During CPR, memory regions are preserved in place, then the realize method initializes the regions contents, losing the old contents. To fix, skip the re-init during CPR. Signed-off-by: Steve Sistare Reviewed-by: Fabiano Rosas Message-ID: <1741380954-341079-4-git-send-email-steven.sistare@oracle.com> Signed-off-by: Fabiano Rosas --- hw/core/loader.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hw/core/loader.c b/hw/core/loader.c index 332b879a0bf0..ce6ff1b52e3b 100644 --- a/hw/core/loader.c +++ b/hw/core/loader.c @@ -51,6 +51,7 @@ #include "trace.h" #include "hw/hw.h" #include "disas/disas.h" +#include "migration/cpr.h" #include "migration/vmstate.h" #include "monitor/monitor.h" #include "system/reset.h" @@ -1029,7 +1030,9 @@ static void *rom_set_mr(Rom *rom, Object *owner, const char *name, bool ro) vmstate_register_ram_global(rom->mr); data = memory_region_get_ram_ptr(rom->mr); - memcpy(data, rom->data, rom->datasize); + if (!cpr_is_incoming()) { + memcpy(data, rom->data, rom->datasize); + } return data; } From 8ffe0623a1f40803feb4280fce13549baa4b0b47 Mon Sep 17 00:00:00 2001 From: Steve Sistare Date: Fri, 7 Mar 2025 12:55:54 -0800 Subject: [PATCH 1040/1179] hw/qxl: fix cpr During normal migration, new QEMU creates and initializes memory regions, then loads the preserved contents of the region from vmstate. During CPR, memory regions are preserved in place, then the realize method initializes the regions contents, losing the old contents. To fix, skip writes to the qxl memory regions during CPR load. Reported-by: andrey.drobyshev@virtuozzo.com Tested-by: andrey.drobyshev@virtuozzo.com Signed-off-by: Steve Sistare Reviewed-by: Fabiano Rosas Message-ID: <1741380954-341079-5-git-send-email-steven.sistare@oracle.com> Signed-off-by: Fabiano Rosas --- hw/display/qxl.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/hw/display/qxl.c b/hw/display/qxl.c index 2efdc77e6133..da14da520910 100644 --- a/hw/display/qxl.c +++ b/hw/display/qxl.c @@ -30,6 +30,7 @@ #include "qemu/module.h" #include "hw/qdev-properties.h" #include "system/runstate.h" +#include "migration/cpr.h" #include "migration/vmstate.h" #include "trace.h" @@ -333,6 +334,10 @@ static void init_qxl_rom(PCIQXLDevice *d) uint32_t fb; int i, n; + if (cpr_is_incoming()) { + goto skip_init; + } + memset(rom, 0, d->rom_size); rom->magic = cpu_to_le32(QXL_ROM_MAGIC); @@ -390,6 +395,7 @@ static void init_qxl_rom(PCIQXLDevice *d) sizeof(rom->client_monitors_config)); } +skip_init: d->shadow_rom = *rom; d->rom = rom; d->modes = modes; @@ -403,6 +409,9 @@ static void init_qxl_ram(PCIQXLDevice *d) buf = d->vga.vram_ptr; d->ram = (QXLRam *)(buf + le32_to_cpu(d->shadow_rom.ram_header_offset)); + if (cpr_is_incoming()) { + return; + } d->ram->magic = cpu_to_le32(QXL_RAM_MAGIC); d->ram->int_pending = cpu_to_le32(0); d->ram->int_mask = cpu_to_le32(0); @@ -539,6 +548,10 @@ static void interface_set_compression_level(QXLInstance *sin, int level) trace_qxl_interface_set_compression_level(qxl->id, level); qxl->shadow_rom.compression_level = cpu_to_le32(level); + if (cpr_is_incoming()) { + assert(qxl->rom->compression_level == cpu_to_le32(level)); + return; + } qxl->rom->compression_level = cpu_to_le32(level); qxl_rom_set_dirty(qxl); } @@ -997,7 +1010,8 @@ static void interface_set_client_capabilities(QXLInstance *sin, } if (runstate_check(RUN_STATE_INMIGRATE) || - runstate_check(RUN_STATE_POSTMIGRATE)) { + runstate_check(RUN_STATE_POSTMIGRATE) || + cpr_is_incoming()) { return; } @@ -1200,6 +1214,10 @@ static void qxl_reset_state(PCIQXLDevice *d) { QXLRom *rom = d->rom; + if (cpr_is_incoming()) { + return; + } + qxl_check_state(d); d->shadow_rom.update_id = cpu_to_le32(0); *rom = d->shadow_rom; @@ -1370,8 +1388,11 @@ static int qxl_add_memslot(PCIQXLDevice *d, uint32_t slot_id, uint64_t delta, memslot.virt_start = virt_start + (guest_start - pci_start); memslot.virt_end = virt_start + (guest_end - pci_start); memslot.addr_delta = memslot.virt_start - delta; - memslot.generation = d->rom->slot_generation = 0; - qxl_rom_set_dirty(d); + if (!cpr_is_incoming()) { + d->rom->slot_generation = 0; + qxl_rom_set_dirty(d); + } + memslot.generation = d->rom->slot_generation; qemu_spice_add_memslot(&d->ssd, &memslot, async); d->guest_slots[slot_id].mr = mr; From 39ec3fc030166c594a64d1d197e29fa9d100d4c5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:07 +0000 Subject: [PATCH 1041/1179] target/arm: HCR_EL2.RW should be RAO/WI if EL1 doesn't support AArch32 When EL1 doesn't support AArch32, the HCR_EL2.RW bit is supposed to be RAO/WI. Enforce the RAO/WI behaviour. Note that we handle "reset value should honour RES1 bits" in the same way that SCR_EL3 does, via a reset function. We do already have some CPU types which don't implement AArch32 above EL0, so this is technically a bug; it doesn't seem worth backporting to stable because no sensible guest code will be deliberately attempting to set the RW bit to a value corresponding to an unimplemented execution state and then checking that we did the right thing. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- target/arm/helper.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/target/arm/helper.c b/target/arm/helper.c index 3df7d5347cb8..bb445e30cd15 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -5326,6 +5326,11 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask) /* Clear RES0 bits. */ value &= valid_mask; + /* RW is RAO/WI if EL1 is AArch64 only */ + if (!cpu_isar_feature(aa64_aa32_el1, cpu)) { + value |= HCR_RW; + } + /* * These bits change the MMU setup: * HCR_VM enables stage 2 translation @@ -5383,6 +5388,12 @@ static void hcr_writelow(CPUARMState *env, const ARMCPRegInfo *ri, do_hcr_write(env, value, MAKE_64BIT_MASK(32, 32)); } +static void hcr_reset(CPUARMState *env, const ARMCPRegInfo *ri) +{ + /* hcr_write will set the RES1 bits on an AArch64-only CPU */ + hcr_write(env, ri, 0); +} + /* * Return the effective value of HCR_EL2, at the given security state. * Bits that are not included here: @@ -5618,6 +5629,7 @@ static const ARMCPRegInfo el2_cp_reginfo[] = { .opc0 = 3, .opc1 = 4, .crn = 1, .crm = 1, .opc2 = 0, .access = PL2_RW, .fieldoffset = offsetof(CPUARMState, cp15.hcr_el2), .nv2_redirect_offset = 0x78, + .resetfn = hcr_reset, .writefn = hcr_write, .raw_writefn = raw_write }, { .name = "HCR", .state = ARM_CP_STATE_AA32, .type = ARM_CP_ALIAS | ARM_CP_IO, From 44ab8c248dee2d899dfe858ce1962fedcd3398a1 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:08 +0000 Subject: [PATCH 1042/1179] target/arm: Add cpu local variable to exception_return helper We already call env_archcpu() multiple times within the exception_return helper function, and we're about to want to add another use of the ARMCPU pointer. Add a local variable cpu so we can call env_archcpu() just once. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- target/arm/tcg/helper-a64.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/target/arm/tcg/helper-a64.c b/target/arm/tcg/helper-a64.c index 32f0647ca4fb..e2bdf07833d7 100644 --- a/target/arm/tcg/helper-a64.c +++ b/target/arm/tcg/helper-a64.c @@ -631,6 +631,7 @@ static void cpsr_write_from_spsr_elx(CPUARMState *env, void HELPER(exception_return)(CPUARMState *env, uint64_t new_pc) { + ARMCPU *cpu = env_archcpu(env); int cur_el = arm_current_el(env); unsigned int spsr_idx = aarch64_banked_spsr_index(cur_el); uint32_t spsr = env->banked_spsr[spsr_idx]; @@ -682,7 +683,7 @@ void HELPER(exception_return)(CPUARMState *env, uint64_t new_pc) } bql_lock(); - arm_call_pre_el_change_hook(env_archcpu(env)); + arm_call_pre_el_change_hook(cpu); bql_unlock(); if (!return_to_aa64) { @@ -710,7 +711,7 @@ void HELPER(exception_return)(CPUARMState *env, uint64_t new_pc) int tbii; env->aarch64 = true; - spsr &= aarch64_pstate_valid_mask(&env_archcpu(env)->isar); + spsr &= aarch64_pstate_valid_mask(&cpu->isar); pstate_write(env, spsr); if (!arm_singlestep_active(env)) { env->pstate &= ~PSTATE_SS; @@ -749,7 +750,7 @@ void HELPER(exception_return)(CPUARMState *env, uint64_t new_pc) aarch64_sve_change_el(env, cur_el, new_el, return_to_aa64); bql_lock(); - arm_call_el_change_hook(env_archcpu(env)); + arm_call_el_change_hook(cpu); bql_unlock(); return; From 097d68ac2fd9bd40d0b6a3b3992c86a1f79d7187 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Wed, 12 Mar 2025 13:25:08 +0000 Subject: [PATCH 1043/1179] target/arm: Forbid return to AArch32 when CPU is AArch64-only In the Arm ARM, rule R_TYTWB states that returning to AArch32 is an illegal exception return if: * AArch32 is not supported at any exception level * the target EL is configured for AArch64 via SCR_EL3.RW or HCR_EL2.RW or via CPU state at reset We check the second of these, but not the first (which can only be relevant for the case of a return to EL0, because if AArch32 is not supported at one of the higher ELs then the RW bits will have an effective value of 1 and the the "configured for AArch64" condition will hold also). Add the missing condition. Although this is technically a bug (because we have one AArch64-only CPU: a64fx) it isn't worth backporting to stable because no sensible guest code will deliberately try to return to a nonexistent execution state to check that it gets an illegal exception return. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson --- target/arm/tcg/helper-a64.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/target/arm/tcg/helper-a64.c b/target/arm/tcg/helper-a64.c index e2bdf07833d7..9244848efed0 100644 --- a/target/arm/tcg/helper-a64.c +++ b/target/arm/tcg/helper-a64.c @@ -678,6 +678,11 @@ void HELPER(exception_return)(CPUARMState *env, uint64_t new_pc) goto illegal_return; } + if (!return_to_aa64 && !cpu_isar_feature(aa64_aa32, cpu)) { + /* Return to AArch32 when CPU is AArch64-only */ + goto illegal_return; + } + if (new_el == 1 && (arm_hcr_el2_eff(env) & HCR_TGE)) { goto illegal_return; } From adb478a584dcf9c112fe8a6f9a7369162d3239fb Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 7 Mar 2025 15:28:38 +0000 Subject: [PATCH 1044/1179] MAINTAINERS: Fix status for Arm boards I "maintain" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm down as the only listed maintainer for quite a lot of Arm SoC and board types. In some cases this is only as the "maintainer of last resort" and I'm not in practice doing anything beyond patch review and the odd bit of tidyup. Move these entries in MAINTAINERS from "Maintained" to "Odd Fixes", to better represent reality. Entries for other boards and SoCs where I do more actively care (or where there is a listed co-maintainer) remain as they are. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250307152838.3226398-1-peter.maydell@linaro.org --- MAINTAINERS | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 31b395fdfad7..8f470a1c9b7c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -786,7 +786,7 @@ F: docs/system/arm/kzm.rst Integrator CP M: Peter Maydell L: qemu-arm@nongnu.org -S: Maintained +S: Odd Fixes F: hw/arm/integratorcp.c F: hw/misc/arm_integrator_debug.c F: include/hw/misc/arm_integrator_debug.h @@ -867,7 +867,7 @@ F: docs/system/arm/mps2.rst Musca M: Peter Maydell L: qemu-arm@nongnu.org -S: Maintained +S: Odd Fixes F: hw/arm/musca.c F: docs/system/arm/musca.rst @@ -915,7 +915,7 @@ F: tests/functional/test_aarch64_raspi4.py Real View M: Peter Maydell L: qemu-arm@nongnu.org -S: Maintained +S: Odd Fixes F: hw/arm/realview* F: hw/cpu/realview_mpcore.c F: hw/intc/realview_gic.c @@ -965,7 +965,7 @@ F: tests/functional/test_arm_collie.py Stellaris M: Peter Maydell L: qemu-arm@nongnu.org -S: Maintained +S: Odd Fixes F: hw/*/stellaris* F: hw/display/ssd03* F: include/hw/input/gamepad.h @@ -995,7 +995,7 @@ F: docs/system/arm/stm32.rst Versatile Express M: Peter Maydell L: qemu-arm@nongnu.org -S: Maintained +S: Odd Fixes F: hw/arm/vexpress.c F: hw/display/sii9022.c F: docs/system/arm/vexpress.rst @@ -1004,7 +1004,7 @@ F: tests/functional/test_arm_vexpress.py Versatile PB M: Peter Maydell L: qemu-arm@nongnu.org -S: Maintained +S: Odd Fixes F: hw/*/versatile* F: hw/i2c/arm_sbcon_i2c.c F: include/hw/i2c/arm_sbcon_i2c.h @@ -2003,7 +2003,7 @@ F: include/hw/hyperv/vmbus*.h OMAP M: Peter Maydell L: qemu-arm@nongnu.org -S: Maintained +S: Odd Fixes F: hw/*/omap* F: include/hw/arm/omap.h F: docs/system/arm/sx1.rst From 9223d688111904f57e5dcbdb80b71ff73a68f8ca Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 10 Mar 2025 10:28:30 +0000 Subject: [PATCH 1045/1179] tests/functional: Bump up arm_replay timeout On my machine the arm_replay test takes over 2 minutes to run in a config with Rust enabled and debug enabled: $ time (cd build/rust ; PYTHONPATH=../../python:../../tests/functional QEMU_TEST_QEMU_BINARY=./qemu-system-arm ./pyvenv/bin/python3 ../../tests/functional/test_arm_replay.py) TAP version 13 ok 1 test_arm_replay.ArmReplay.test_cubieboard ok 2 test_arm_replay.ArmReplay.test_vexpressa9 ok 3 test_arm_replay.ArmReplay.test_virt 1..3 real 2m16.564s user 2m13.461s sys 0m3.523s Bump up the timeout to 4 minutes. Signed-off-by: Peter Maydell Reviewed-by: Thomas Huth Message-id: 20250310102830.3752440-1-peter.maydell@linaro.org From 5b14454d37854f5c4227d642133a477a07e49759 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 11 Mar 2025 16:37:17 +0100 Subject: [PATCH 1046/1179] Revert "hw/char/pl011: Warn when using disabled receiver" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guest does not control whether characters are sent on the UART. Sending them before the guest happens to boot will now result in a "guest error" log entry that is only because of timing, even if the guest _would_ later setup the receiver correctly. This reverts the bulk of commit abf2b6a028670bd2890bb3aee7e103fe53e4b0df, and instead adds a comment about why we don't check the enable bits. Cc: Philippe Mathieu-Daudé Cc: Peter Maydell Signed-off-by: Paolo Bonzini Message-id: 20250311153717.206129-1-pbonzini@redhat.com [PMM: expanded comment] Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/char/pl011.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index 23a9db8c57cb..0e9ec1301d3f 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -490,16 +490,17 @@ static int pl011_can_receive(void *opaque) unsigned fifo_depth = pl011_get_fifo_depth(s); unsigned fifo_available = fifo_depth - s->read_count; - if (!(s->cr & CR_UARTEN)) { - qemu_log_mask(LOG_GUEST_ERROR, - "PL011 receiving data on disabled UART\n"); - } - if (!(s->cr & CR_RXE)) { - qemu_log_mask(LOG_GUEST_ERROR, - "PL011 receiving data on disabled RX UART\n"); - } - trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available); + /* + * In theory we should check the UART and RX enable bits here and + * return 0 if they are not set (so the guest can't receive data + * until you have enabled the UART). In practice we suspect there + * is at least some guest code out there which has been tested only + * on QEMU and which never bothers to enable the UART because we + * historically never enforced that. So we effectively keep the + * UART continuously enabled regardless of the enable bits. + */ + trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available); return fifo_available; } From e6c38d2ab55d66c74ceade5699e22cabe9058d22 Mon Sep 17 00:00:00 2001 From: Joe Komlodi Date: Mon, 10 Mar 2025 20:36:22 +0000 Subject: [PATCH 1047/1179] util/cacheflush: Make first DSB unconditional on aarch64 On ARM hosts with CTR_EL0.DIC and CTR_EL0.IDC set, this would only cause an ISB to be executed during cache maintenance, which could lead to QEMU executing TBs containing garbage instructions. This seems to be because the ISB finishes executing instructions and flushes the pipeline, but the ISB doesn't guarantee that writes from the executed instructions are committed. If a small enough TB is created, it's possible that the writes setting up the TB aren't committed by the time the TB is executed. This function is intended to be a port of the gcc implementation (https://github.com/gcc-mirror/gcc/blob/85b46d0795ac76bc192cb8f88b646a647acf98c1/libgcc/config/aarch64/sync-cache.c#L67) which makes the first DSB unconditional, so we can fix the synchronization issue by doing that as well. Cc: qemu-stable@nongnu.org Fixes: 664a79735e4deb1 ("util: Specialize flush_idcache_range for aarch64") Signed-off-by: Joe Komlodi Message-id: 20250310203622.1827940-2-komlodi@google.com Reviewed-by: Peter Maydell Reviewed-by: Richard Henderson Signed-off-by: Peter Maydell --- util/cacheflush.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/cacheflush.c b/util/cacheflush.c index a08906155a9b..1d12899a3922 100644 --- a/util/cacheflush.c +++ b/util/cacheflush.c @@ -279,9 +279,11 @@ void flush_idcache_range(uintptr_t rx, uintptr_t rw, size_t len) for (p = rw & -dcache_lsize; p < rw + len; p += dcache_lsize) { asm volatile("dc\tcvau, %0" : : "r" (p) : "memory"); } - asm volatile("dsb\tish" : : : "memory"); } + /* DSB unconditionally to ensure any outstanding writes are committed. */ + asm volatile("dsb\tish" : : : "memory"); + /* * If CTR_EL0.DIC is enabled, Instruction cache cleaning to the Point * of Unification is not required for instruction to data coherence. From 298a04998fa4a6dc977abe9234d98dfcdab98423 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 7 Mar 2025 11:04:14 -0800 Subject: [PATCH 1048/1179] target/arm: Make DisasContext.{fp, sve}_access_checked tristate The check for fp_excp_el in assert_fp_access_checked is incorrect. For SME, with StreamingMode enabled, the access is really against the streaming mode vectors, and access to the normal fp registers is allowed to be disabled. C.f. sme_enabled_check. Convert sve_access_checked to match, even though we don't currently check the exception state. Cc: qemu-stable@nongnu.org Fixes: 3d74825f4d6 ("target/arm: Add SME enablement checks") Signed-off-by: Richard Henderson Message-id: 20250307190415.982049-2-richard.henderson@linaro.org Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- target/arm/tcg/translate-a64.c | 17 +++++++++-------- target/arm/tcg/translate-a64.h | 2 +- target/arm/tcg/translate.h | 10 +++++++--- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c index 8bef391bb039..48e0ac75b119 100644 --- a/target/arm/tcg/translate-a64.c +++ b/target/arm/tcg/translate-a64.c @@ -1381,14 +1381,14 @@ static bool fp_access_check_only(DisasContext *s) { if (s->fp_excp_el) { assert(!s->fp_access_checked); - s->fp_access_checked = true; + s->fp_access_checked = -1; gen_exception_insn_el(s, 0, EXCP_UDEF, syn_fp_access_trap(1, 0xe, false, 0), s->fp_excp_el); return false; } - s->fp_access_checked = true; + s->fp_access_checked = 1; return true; } @@ -1465,13 +1465,13 @@ bool sve_access_check(DisasContext *s) syn_sve_access_trap(), s->sve_excp_el); goto fail_exit; } - s->sve_access_checked = true; + s->sve_access_checked = 1; return fp_access_check(s); fail_exit: /* Assert that we only raise one exception per instruction. */ assert(!s->sve_access_checked); - s->sve_access_checked = true; + s->sve_access_checked = -1; return false; } @@ -1500,8 +1500,9 @@ bool sme_enabled_check(DisasContext *s) * sme_excp_el by itself for cpregs access checks. */ if (!s->fp_excp_el || s->sme_excp_el < s->fp_excp_el) { - s->fp_access_checked = true; - return sme_access_check(s); + bool ret = sme_access_check(s); + s->fp_access_checked = (ret ? 1 : -1); + return ret; } return fp_access_check_only(s); } @@ -10257,8 +10258,8 @@ static void aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) s->insn = insn; s->base.pc_next = pc + 4; - s->fp_access_checked = false; - s->sve_access_checked = false; + s->fp_access_checked = 0; + s->sve_access_checked = 0; if (s->pstate_il) { /* diff --git a/target/arm/tcg/translate-a64.h b/target/arm/tcg/translate-a64.h index 7d3b59ccd961..b2420f59ebe7 100644 --- a/target/arm/tcg/translate-a64.h +++ b/target/arm/tcg/translate-a64.h @@ -65,7 +65,7 @@ TCGv_i64 gen_mte_checkN(DisasContext *s, TCGv_i64 addr, bool is_write, static inline void assert_fp_access_checked(DisasContext *s) { #ifdef CONFIG_DEBUG_TCG - if (unlikely(!s->fp_access_checked || s->fp_excp_el)) { + if (unlikely(s->fp_access_checked <= 0)) { fprintf(stderr, "target-arm: FP access check missing for " "instruction 0x%08x\n", s->insn); abort(); diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h index f8dc2f0d4bb9..53e485d28ac4 100644 --- a/target/arm/tcg/translate.h +++ b/target/arm/tcg/translate.h @@ -92,15 +92,19 @@ typedef struct DisasContext { bool aarch64; bool thumb; bool lse2; - /* Because unallocated encodings generate different exception syndrome + /* + * Because unallocated encodings generate different exception syndrome * information from traps due to FP being disabled, we can't do a single * "is fp access disabled" check at a high level in the decode tree. * To help in catching bugs where the access check was forgotten in some * code path, we set this flag when the access check is done, and assert * that it is set at the point where we actually touch the FP regs. + * 0: not checked, + * 1: checked, access ok + * -1: checked, access denied */ - bool fp_access_checked; - bool sve_access_checked; + int8_t fp_access_checked; + int8_t sve_access_checked; /* ARMv8 single-step state (this is distinct from the QEMU gdbstub * single-step support). */ From cc7abc35dfa790ba6c20473c03745428c1c626b6 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 7 Mar 2025 11:04:15 -0800 Subject: [PATCH 1049/1179] target/arm: Simplify pstate_sm check in sve_access_check In StreamingMode, fp_access_checked is handled already. We cannot fall through to fp_access_check lest we fall foul of the double-check assertion. Cc: qemu-stable@nongnu.org Fixes: 285b1d5fcef ("target/arm: Handle SME in sve_access_check") Signed-off-by: Richard Henderson Message-id: 20250307190415.982049-3-richard.henderson@linaro.org Reviewed-by: Peter Maydell [PMM: move declaration of 'ret' to top of block] Signed-off-by: Peter Maydell --- target/arm/tcg/translate-a64.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c index 48e0ac75b119..39014325df18 100644 --- a/target/arm/tcg/translate-a64.c +++ b/target/arm/tcg/translate-a64.c @@ -1456,23 +1456,23 @@ static int fp_access_check_vector_hsd(DisasContext *s, bool is_q, MemOp esz) bool sve_access_check(DisasContext *s) { if (s->pstate_sm || !dc_isar_feature(aa64_sve, s)) { + bool ret; + assert(dc_isar_feature(aa64_sme, s)); - if (!sme_sm_enabled_check(s)) { - goto fail_exit; - } - } else if (s->sve_excp_el) { + ret = sme_sm_enabled_check(s); + s->sve_access_checked = (ret ? 1 : -1); + return ret; + } + if (s->sve_excp_el) { + /* Assert that we only raise one exception per instruction. */ + assert(!s->sve_access_checked); gen_exception_insn_el(s, 0, EXCP_UDEF, syn_sve_access_trap(), s->sve_excp_el); - goto fail_exit; + s->sve_access_checked = -1; + return false; } s->sve_access_checked = 1; return fp_access_check(s); - - fail_exit: - /* Assert that we only raise one exception per instruction. */ - assert(!s->sve_access_checked); - s->sve_access_checked = -1; - return false; } /* From a019e15edfd62beae1e2f6adc0fa7415ba20b14c Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 10 Mar 2025 10:29:50 +0000 Subject: [PATCH 1050/1179] meson.build: Set RUST_BACKTRACE for all tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to capture potential Rust backtraces on panics in our test logs, which isn't Rust's default behaviour. Set RUST_BACKTRACE=1 in the add_test_setup environments, so that all our tests get run with this environment variable set. This makes the setting of that variable in the gitlab CI template redundant, so we can remove it. Signed-off-by: Peter Maydell Reviewed-by: Daniel P. Berrangé Reviewed-by: Philippe Mathieu-Daudé Message-id: 20250310102950.3752908-1-peter.maydell@linaro.org --- .gitlab-ci.d/buildtest-template.yml | 1 - meson.build | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.d/buildtest-template.yml b/.gitlab-ci.d/buildtest-template.yml index 4cc192393194..39da7698b095 100644 --- a/.gitlab-ci.d/buildtest-template.yml +++ b/.gitlab-ci.d/buildtest-template.yml @@ -63,7 +63,6 @@ stage: test image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG script: - - export RUST_BACKTRACE=1 - source scripts/ci/gitlab-ci-section - section_start buildenv "Setting up to run tests" - scripts/git-submodule.sh update roms/SLOF diff --git a/meson.build b/meson.build index 2f43fd81bf4e..7f75256acf96 100644 --- a/meson.build +++ b/meson.build @@ -5,9 +5,12 @@ project('qemu', ['c'], meson_version: '>=1.5.0', meson.add_devenv({ 'MESON_BUILD_ROOT' : meson.project_build_root() }) -add_test_setup('quick', exclude_suites: ['slow', 'thorough'], is_default: true) -add_test_setup('slow', exclude_suites: ['thorough'], env: ['G_TEST_SLOW=1', 'SPEED=slow']) -add_test_setup('thorough', env: ['G_TEST_SLOW=1', 'SPEED=thorough']) +add_test_setup('quick', exclude_suites: ['slow', 'thorough'], is_default: true, + env: ['RUST_BACKTRACE=1']) +add_test_setup('slow', exclude_suites: ['thorough'], + env: ['G_TEST_SLOW=1', 'SPEED=slow', 'RUST_BACKTRACE=1']) +add_test_setup('thorough', + env: ['G_TEST_SLOW=1', 'SPEED=thorough', 'RUST_BACKTRACE=1']) meson.add_postconf_script(find_program('scripts/symlink-install-tree.py')) From b027f55a994af885a7a498a40373a2dcc2d8b15e Mon Sep 17 00:00:00 2001 From: Konstantin Shkolnyy Date: Fri, 21 Feb 2025 13:07:33 -0600 Subject: [PATCH 1051/1179] vdpa: Allow vDPA to work on big-endian machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add .set_vnet_le() function that always returns success, assuming that vDPA h/w always implements LE data format. Otherwise, QEMU disables vDPA and outputs the message: "backend does not support LE vnet headers; falling back on userspace virtio" Reviewed-by: Michael S. Tsirkin Acked-by: Eugenio Pérez Signed-off-by: Konstantin Shkolnyy Signed-off-by: Jason Wang --- net/vhost-vdpa.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index f7a54f46aa72..7ca8b46eee77 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -262,6 +262,18 @@ static bool vhost_vdpa_has_ufo(NetClientState *nc) } +/* + * FIXME: vhost_vdpa doesn't have an API to "set h/w endianness". But it's + * reasonable to assume that h/w is LE by default, because LE is what + * virtio 1.0 and later ask for. So, this function just says "yes, the h/w is + * LE". Otherwise, on a BE machine, higher-level code would mistakely think + * the h/w is BE and can't support VDPA for a virtio 1.0 client. + */ +static int vhost_vdpa_set_vnet_le(NetClientState *nc, bool enable) +{ + return 0; +} + static bool vhost_vdpa_check_peer_type(NetClientState *nc, ObjectClass *oc, Error **errp) { @@ -429,6 +441,7 @@ static NetClientInfo net_vhost_vdpa_info = { .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, .has_ufo = vhost_vdpa_has_ufo, + .set_vnet_le = vhost_vdpa_set_vnet_le, .check_peer_type = vhost_vdpa_check_peer_type, .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, }; From d18591157e5adf0e4491eed9b2c99828ba52bd80 Mon Sep 17 00:00:00 2001 From: Tigran Sogomonian Date: Fri, 27 Dec 2024 13:46:18 +0300 Subject: [PATCH 1052/1179] hw/misc: use extract64 instead of 1 << i MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1 << i is casted to uint64_t while bitwise and with val. So this value may become 0xffffffff80000000 but only 31th "start" bit is required. Use the bitfield extract() API instead. Found by Linux Verification Center (linuxtesting.org) with SVACE. Signed-off-by: Tigran Sogomonian Reviewed-by: Alex Bennée Link: https://lore.kernel.org/r/20241227104618.2526-1-tsogomonian@astralinux.ru Signed-off-by: Paolo Bonzini --- hw/misc/mps2-fpgaio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/misc/mps2-fpgaio.c b/hw/misc/mps2-fpgaio.c index d07568248d6e..04a3da5db051 100644 --- a/hw/misc/mps2-fpgaio.c +++ b/hw/misc/mps2-fpgaio.c @@ -198,7 +198,7 @@ static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value, s->led0 = value & MAKE_64BIT_MASK(0, s->num_leds); for (i = 0; i < s->num_leds; i++) { - led_set_state(s->led[i], value & (1 << i)); + led_set_state(s->led[i], extract64(value, i, 1)); } } break; From f35432a4f699d8e450f65e44ddcd5911f2d8c146 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 17 Mar 2025 08:53:45 +0100 Subject: [PATCH 1053/1179] Revert "meson.build: default to -gsplit-dwarf for debug info" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 563b1a35ed1f1151505d4fe5f723827d1b3fd4bc. Split debug info support is broken when cross compiling (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99973). People that would like to use it can add it via --extra-cflags. Reported-by: Konstantin Kostiuk Reviewed-by: Daniel P. Berrangé Signed-off-by: Paolo Bonzini --- meson.build | 6 ------ meson_options.txt | 2 -- scripts/meson-buildoptions.sh | 2 -- 3 files changed, 10 deletions(-) diff --git a/meson.build b/meson.build index 7f75256acf96..41f68d38069b 100644 --- a/meson.build +++ b/meson.build @@ -604,10 +604,6 @@ if get_option('tsan') qemu_ldflags = ['-fsanitize=thread'] + qemu_ldflags endif -if get_option('debug') and get_option('split_debug') - qemu_cflags += '-gsplit-dwarf' -endif - # Detect support for PT_GNU_RELRO + DT_BIND_NOW. # The combination is known as "full relro", because .got.plt is read-only too. qemu_ldflags += cc.get_supported_link_arguments('-Wl,-z,relro', '-Wl,-z,now') @@ -4599,8 +4595,6 @@ if have_rust summary_info += {'bindgen': bindgen.full_path()} summary_info += {'bindgen version': bindgen.version()} endif -# option_cflags is purely for the summary display, meson will pass -# -g/-O options directly option_cflags = (get_option('debug') ? ['-g'] : []) if get_option('optimization') != 'plain' option_cflags += ['-O' + get_option('optimization')] diff --git a/meson_options.txt b/meson_options.txt index 3432123fee2e..59d973bca00f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -362,8 +362,6 @@ option('debug_mutex', type: 'boolean', value: false, description: 'mutex debugging support') option('debug_stack_usage', type: 'boolean', value: false, description: 'measure coroutine stack usage') -option('split_debug', type: 'boolean', value: true, - description: 'split debug info from object files') option('qom_cast_debug', type: 'boolean', value: true, description: 'cast debugging support') option('slirp_smbd', type : 'feature', value : 'auto', diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index aca6e6883024..3e8e00852b2c 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -504,8 +504,6 @@ _meson_option_parse() { --disable-strict-rust-lints) printf "%s" -Dstrict_rust_lints=false ;; --enable-strip) printf "%s" -Dstrip=true ;; --disable-strip) printf "%s" -Dstrip=false ;; - --enable-split-debug) printf "%s" -Dsplit_debug=true ;; - --disable-split-debug) printf "%s" -Dsplit_debug=false ;; --sysconfdir=*) quote_sh "-Dsysconfdir=$2" ;; --enable-tcg) printf "%s" -Dtcg=enabled ;; --disable-tcg) printf "%s" -Dtcg=disabled ;; From 1dae461a913f9da88df05de6e2020d3134356f2e Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Tue, 18 Mar 2025 10:18:14 -0400 Subject: [PATCH 1054/1179] Update version for v10.0.0-rc0 release Signed-off-by: Stefan Hajnoczi --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a318b6e8435d..b77037335074 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.2.50 +9.2.90 From 20452003936d3c2c19dd1a3909d5aeedaa4f659f Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 30 Apr 2024 08:13:36 -0700 Subject: [PATCH 1055/1179] docs: Add hexagon sysemu docs Signed-off-by: Brian Cain --- MAINTAINERS | 2 + docs/devel/hexagon-sys.rst | 106 +++++++++++++++++++++++++++++++++ docs/devel/index-internals.rst | 1 + docs/system/hexagon/cdsp.rst | 10 ++++ docs/system/target-hexagon.rst | 100 +++++++++++++++++++++++++++++++ docs/system/targets.rst | 1 + 6 files changed, 220 insertions(+) create mode 100644 docs/devel/hexagon-sys.rst create mode 100644 docs/system/hexagon/cdsp.rst create mode 100644 docs/system/target-hexagon.rst diff --git a/MAINTAINERS b/MAINTAINERS index 8f470a1c9b7c..b2319ebbd6a9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -233,6 +233,8 @@ F: disas/hexagon.c F: configs/targets/hexagon-linux-user/default.mak F: docker/dockerfiles/debian-hexagon-cross.docker F: gdb-xml/hexagon*.xml +F: docs/system/target-hexagon.rst +F: docs/devel/hexagon-sys.rst T: git https://github.com/quic/qemu.git hex-next Hexagon idef-parser diff --git a/docs/devel/hexagon-sys.rst b/docs/devel/hexagon-sys.rst new file mode 100644 index 000000000000..3972261a2bbe --- /dev/null +++ b/docs/devel/hexagon-sys.rst @@ -0,0 +1,106 @@ +.. _Hexagon-System-arch: + +Hexagon System Architecture +=========================== + +The hexagon architecture has some unique elements which are described here. + +Interrupts +---------- +When interrupts arrive at a Hexagon DSP core, they are priority-steered to +be handled by an eligible hardware thread with the lowest priority. + +Memory +------ +Each hardware thread has an ``SSR.ASID`` field that contains its Address +Space Identifier. This value is catenated with a 32-bit virtual address - +the MMU can then resolve this extended virtual address to a physical address. + +TLBs +---- +The format of a TLB entry is shown below. + +.. note:: + The Small Core DSPs have a different TLB format which is not yet + supported. + +.. admonition:: Diagram + + .. code:: text + + 6 5 4 3 + 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |v|g|x|A|A| | | + |a|l|P|1|0| ASID | Virtual Page | + |l|b| | | | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 3 2 1 0 + 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | | | | | | | + |x|w|r|u|Cacheab| Physical Page |S| + | | | | | | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +* ASID: the address-space identifier +* A1, A0: the behavior of these cache line attributes are not modeled by QEMU. +* xP: the extra-physical bit is the most significant physical address bit. +* S: the S bit and the LSBs of the physical page indicate the page size +* val: this is the 'valid' bit, when set it indicates that page matching + should consider this entry. + +.. list-table:: Page sizes + :widths: 25 25 50 + :header-rows: 1 + + * - S-bit + - Phys page LSBs + - Page size + * - 1 + - N/A + - 4kb + * - 0 + - 0b1 + - 16kb + * - 0 + - 0b10 + - 64kb + * - 0 + - 0b100 + - 256kb + * - 0 + - 0b1000 + - 1MB + * - 0 + - 0b10000 + - 4MB + * - 0 + - 0b100000 + - 16MB + +* glb: if the global bit is set, the ASID is not considered when matching + TLBs. +* Cacheab: the cacheability attributes of TLBs are not modeled, these bits + are ignored. +* RWX: read-, write-, execute-, enable bits. Indicates if user programs + are permitted to read/write/execute the given page. +* U: indicates if user programs can access this page. + +Scheduler +--------- +The Hexagon system architecture has a feature to assist the guest OS +task scheduler. The guest OS can enable this feature by setting +``SCHEDCFG.EN``. The ``BESTWAIT`` register is programmed by the guest OS +to indicate the priority of the highest priority task waiting to run on a +hardware thread. The reschedule interrupt is triggered when any hardware +thread's priority in ``STID.PRIO`` is worse than the ``BESTWAIT``. When +it is triggered, the ``BESTWAIT.PRIO`` value is reset to 0x1ff. + +HVX Coprocessor +--------------- +The Supervisor Status Register field ``SSR.XA`` binds a DSP hardware thread +to one of the eight possible HVX contexts. The guest OS is responsible for +managing this resource. diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index 7a0678cbdd3a..0471db80645f 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst @@ -14,6 +14,7 @@ Details about QEMU's various subsystems including how to add features to them. block-coroutine-wrapper clocks ebpf_rss + hexagon-sys migration/index multi-process reset diff --git a/docs/system/hexagon/cdsp.rst b/docs/system/hexagon/cdsp.rst new file mode 100644 index 000000000000..f755fbe0a5ba --- /dev/null +++ b/docs/system/hexagon/cdsp.rst @@ -0,0 +1,10 @@ +Compute DSP +=========== + +A Hexagon CDSP is designed as a computation offload device for an SoC. The +``V66G_1024`` machine contains: + +* L2VIC interrupt controller +* QTimer timer device + +This machine will support any Hexagon CPU, but will default to ``v66``. diff --git a/docs/system/target-hexagon.rst b/docs/system/target-hexagon.rst new file mode 100644 index 000000000000..e12a93d15d4f --- /dev/null +++ b/docs/system/target-hexagon.rst @@ -0,0 +1,100 @@ +.. _Hexagon-System-emulator: + +Hexagon System emulator +----------------------- + +Use the ``qemu-system-hexagon`` executable to simulate a 32-bit Hexagon +machine. + +Hexagon Machines +================ + +Hexagon DSPs are suited to various functions and generally appear in a +"DSP subsystem" of a larger system-on-chip (SoC). + +Hexagon DSPs are often included in a subsystem that looks like the diagram +below. Instructions are loaded into DDR before the DSP is brought out of +reset and the first instructions are fetched from DDR via the EVB/reset vector. + +In a real system, a TBU/SMMU would normally arbitrate AXI accesses but +we don't have a need to model that for QEMU. + +Hexagon DSP cores use simultaneous multithreading (SMT) with as many as 8 +hardware threads. + +.. admonition:: Diagram + + .. code:: text + + AHB (local) bus AXI (global) bus + │ │ + │ │ + ┌─────────┐ │ ┌─────────────────┐ │ + │ L2VIC ├──┤ │ │ │ + │ ├──┼───────► ├───────┤ + └─────▲───┘ │ │ Hexagon DSP │ │ + │ │ │ │ │ ┌─────┐ + │ │ │ N threads │ │ │ DDR │ + │ ├───────┤ │ │ │ │ + ┌────┴──┐ │ │ │ ├────────┤ │ + │QTimer ├───┤ │ │ │ │ │ + │ │ │ │ │ │ │ │ + └───────┘ │ │ ┌─────────┐ │ │ │ │ + │ │ ┌─────────┐│ │ │ │ │ + ┌───────┐ │ │ │ HVX xM ││ │ │ │ │ + │QDSP6SS├───┤ │ │ │┘ │ │ │ │ + └───────┘ │ │ └─────────┘ │ │ └─────┘ + │ │ │ │ + ┌───────┐ │ └─────────────────┘ │ + │ CSR ├───┤ + └───────┘ │ ┌──────┐ ┌───────────┐ + │ │ TCM │ │ VTCM │ + │ │ │ │ + └──────┘ │ │ + │ │ + │ │ + │ │ + └───────────┘ + +Components +---------- +Other than l2vic and HVX, the components below are not implemented in QEMU. + +* L2VIC: the L2 vectored interrupt controller. Supports 1024 input + interrupts, edge- or level-triggered. The core ISA has system registers + ``VID``, ``VID1`` which read through to the L2VIC device. +* QTimer: ARMSSE-based programmable timer device. Its interrupts are + wired to the L2VIC. System registers ``TIMER``, ``UTIMER`` read + through to the QTimer device. +* QDSP6SS: DSP subsystem features, accessible to the entire SoC, including + DSP NMI, watchdog, reset, etc. +* CSR: Configuration/Status Registers. +* TCM: DSP-exclusive tightly-coupled memory. This memory can be used for + DSPs when isolated from DDR and in some bootstrapping modes. +* VTCM: DSP-exclusive vector tightly-coupled memory. This memory is accessed + by some HVX instructions. +* HVX: the vector coprocessor supports 64 and 128-byte vector registers. + 64-byte mode is not implemented in QEMU. + + +Bootstrapping +------------- +Hexagon systems do not generally have access to a block device. So, for +QEMU the typical use case involves loading a binary or ELF file into memory +and executing from the indicated start address:: + + $ qemu-system-hexagon -kernel ./prog -append 'arg1 arg2' + +Semihosting +----------- +Hexagon supports a semihosting interface similar to other architectures'. +The ``trap0`` instruction can activate these semihosting calls so that the +guest software can access the host console and filesystem. Semihosting +is not yet implemented in QEMU hexagon. + + +Hexagon Features +================ +.. toctree:: + hexagon/cdsp + diff --git a/docs/system/targets.rst b/docs/system/targets.rst index 224fadae71c4..e6dcdb9d4161 100644 --- a/docs/system/targets.rst +++ b/docs/system/targets.rst @@ -29,3 +29,4 @@ Contents: target-sparc64 target-i386 target-xtensa + target-hexagon From 241cc69e1c03af581e7281fe4a2d08a32a9c05d3 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 25 Oct 2024 23:20:05 -0500 Subject: [PATCH 1056/1179] docs/system: Add hexagon CPU emulation Signed-off-by: Brian Cain --- docs/system/hexagon/emulation.rst | 16 ++++++++++++++++ docs/system/target-hexagon.rst | 1 + 2 files changed, 17 insertions(+) create mode 100644 docs/system/hexagon/emulation.rst diff --git a/docs/system/hexagon/emulation.rst b/docs/system/hexagon/emulation.rst new file mode 100644 index 000000000000..03a6092a1281 --- /dev/null +++ b/docs/system/hexagon/emulation.rst @@ -0,0 +1,16 @@ +.. _Hexagon Emulation: + +Hexagon CPU architecture support +================================ + +QEMU's TCG emulation includes support for v65, v66, v67, v68, v69, v71, v73. +It also has support for the following architecture extensions: + +- HVX (Hexagon Vector eXtensions) + +For information on the specifics of the HVX extension, please refer +to the `Qualcomm Hexagon V69 HVX Programmer's Reference Manual +`_. + +.. code-block:: bash + diff --git a/docs/system/target-hexagon.rst b/docs/system/target-hexagon.rst index e12a93d15d4f..b2ffee91eb02 100644 --- a/docs/system/target-hexagon.rst +++ b/docs/system/target-hexagon.rst @@ -96,5 +96,6 @@ is not yet implemented in QEMU hexagon. Hexagon Features ================ .. toctree:: + hexagon/emulation hexagon/cdsp From b7231a59edeb0520fb5b5f7a22f70dd1e28e2f24 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 8 Aug 2024 11:17:57 -0700 Subject: [PATCH 1057/1179] target/hexagon: Fix badva reference, delete CAUSE The BADVA reg is referred to with the wrong identifier. The CAUSE reg field of SSR is not yet modeled, we will dump the SSR in a subsequent commit. Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 766b6786511c..62f1fe15b805 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -216,8 +216,7 @@ static void hexagon_dump(CPUHexagonState *env, FILE *f, int flags) qemu_fprintf(f, " cs0 = 0x00000000\n"); qemu_fprintf(f, " cs1 = 0x00000000\n"); #else - print_reg(f, env, HEX_REG_CAUSE); - print_reg(f, env, HEX_REG_BADVA); + print_reg(f, env, HEX_SREG_BADVA); print_reg(f, env, HEX_REG_CS0); print_reg(f, env, HEX_REG_CS1); #endif From 19dd0d3233f623d98142ef84be9c647268f3f905 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 17 May 2024 19:50:15 -0700 Subject: [PATCH 1058/1179] target/hexagon: Add missing A_CALL attr, hintjumpr to multi_cof Signed-off-by: Brian Cain --- target/hexagon/hex_common.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index 758e5fd12dfe..e60e8efabc93 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -247,7 +247,11 @@ def need_next_PC(tag): def need_pkt_has_multi_cof(tag): - return "A_COF" in attribdict[tag] + if "A_JUMP" in attribdict[tag] or "A_CALL" in attribdict[tag]: + if tag == "J4_hintjumpr": + return False + return True + return False def need_pkt_need_commit(tag): From e904d8eb7e315f97e8c22ecee4c9f586e3b203c7 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 17 May 2024 17:59:23 -0700 Subject: [PATCH 1059/1179] target/hexagon: Handle system/guest registers in gen_analyze_funcs.py and hex_common.py Signed-off-by: Brian Cain --- target/hexagon/gen_analyze_funcs.py | 21 +++- target/hexagon/hex_common.py | 159 ++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 3 deletions(-) diff --git a/target/hexagon/gen_analyze_funcs.py b/target/hexagon/gen_analyze_funcs.py index 3ac7cc2cfe57..dfdf5f3b87ba 100755 --- a/target/hexagon/gen_analyze_funcs.py +++ b/target/hexagon/gen_analyze_funcs.py @@ -22,6 +22,8 @@ import string import hex_common +def has_analyze_func(reg, mode): + return callable(getattr(reg, f"analyze_{mode}", None)) ## ## Generate the code to analyze the instruction @@ -42,6 +44,14 @@ def gen_analyze_func(f, tag, regs, imms): f.write(f"static void analyze_{tag}(DisasContext *ctx)\n") f.write("{\n") + if hex_common.tag_ignore(tag): + f.write("}\n\n") + return + + if ("A_PRIV" in hex_common.attribdict[tag] or + "A_GUEST" in hex_common.attribdict[tag]): + f.write("#ifndef CONFIG_USER_ONLY\n") + f.write(" Insn *insn G_GNUC_UNUSED = ctx->insn;\n") if (hex_common.is_hvx_insn(tag)): if hex_common.has_hvx_helper(tag): @@ -58,22 +68,27 @@ def gen_analyze_func(f, tag, regs, imms): for regno, register in enumerate(regs): reg_type, reg_id = register reg = hex_common.get_register(tag, reg_type, reg_id) - reg.decl_reg_num(f, regno) + if has_analyze_func(reg, "read") or has_analyze_func(reg, "write"): + reg.decl_reg_num(f, regno) ## Analyze the register reads for regno, register in enumerate(regs): reg_type, reg_id = register reg = hex_common.get_register(tag, reg_type, reg_id) - if reg.is_read(): + if reg.is_read() and has_analyze_func(reg, "read"): reg.analyze_read(f, regno) ## Analyze the register writes for regno, register in enumerate(regs): reg_type, reg_id = register reg = hex_common.get_register(tag, reg_type, reg_id) - if reg.is_written(): + if reg.is_written() and has_analyze_func(reg, "write"): reg.analyze_write(f, tag, regno) + if ("A_PRIV" in hex_common.attribdict[tag] or + "A_GUEST" in hex_common.attribdict[tag]): + f.write("#endif /* !CONFIG_USER_ONLY */\n") + f.write("}\n\n") diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index e60e8efabc93..77aef2f070ba 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -33,6 +33,41 @@ overrides = {} # tags with helper overrides idef_parser_enabled = {} # tags enabled for idef-parser + +def is_sysemu_tag(tag): + return "A_PRIV" in attribdict[tag] or "A_GUEST" in attribdict[tag] + + +def tag_ignore(tag): + tag_skips = ( + "Y6_diag", + "Y6_diag0", + "Y6_diag1", + ) + attr_skips = ( + "A_FAKEINSN", + "A_MAPPING", + ) + return tag in tag_skips or \ + any(attr in attribdict[tag] for attr in attr_skips) + + +def get_sys_tags(): + return sorted( + tag for tag in frozenset(tags) if is_sysemu_tag(tag) + ) + + +def get_user_tags(): + return sorted( + tag for tag in frozenset(tags) if not is_sysemu_tag(tag) + ) + + +def get_all_tags(): + return get_user_tags() + get_sys_tags() + + # We should do this as a hash for performance, # but to keep order let's keep it as a list. def uniquify(seq): @@ -370,12 +405,16 @@ def helper_proto_type(self): return "s32" def helper_arg_type(self): return "int32_t" + def is_pair(self): + return False class Pair(Scalar): def helper_proto_type(self): return "s64" def helper_arg_type(self): return "int64_t" + def is_pair(self): + return True class Hvx: def is_scalar_reg(self): @@ -1013,6 +1052,116 @@ def analyze_write(self, f, tag, regno): ctx_log_qreg_write(ctx, {self.reg_num}, insn_has_hvx_helper); """)) +class GuestRegister(Register): + def gen_check_impl(self, f, regno): + if self.is_written(): + f.write(code_fmt(f"""\ + if (!greg_writable(insn->regno[{regno}], + {str(self.is_pair()).lower()})) {{ + return; + }} + """)) + else: + f.write(code_fmt(f"""\ + check_greg_impl(insn->regno[{regno}], {str(self.is_pair()).lower()}); + """)) + +class GuestDest(GuestRegister, Single, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + self.gen_check_impl(f, regno) + f.write(code_fmt(f"""\ + TCGv {self.reg_tcg()} = tcg_temp_new(); + """)) + def log_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_greg_write(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_greg_write(ctx, {self.reg_num}); + """)) + +class GuestSource(GuestRegister, Single, OldSource): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno); + self.gen_check_impl(f, regno) + f.write(code_fmt(f"""\ + TCGv {self.reg_tcg()} = tcg_temp_new(); + gen_read_greg({self.reg_tcg()}, {self.reg_num}); + """)) + +class GuestPairDest(GuestRegister, Pair, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + self.gen_check_impl(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + """)) + def log_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_greg_write_pair(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_greg_write_pair(ctx, {self.reg_num}); + """)) + +class GuestPairSource(GuestRegister, Pair, OldSource): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + self.gen_check_impl(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + gen_read_greg_pair({self.reg_tcg()}, {self.reg_num}); + """)) + +class SystemDest(Register, Single, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv {self.reg_tcg()} = tcg_temp_new(); + """)) + def log_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_sreg_write(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_sreg_write(ctx, {self.reg_num}); + """)) + +class SystemSource(Register, Single, OldSource): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno); + f.write(code_fmt(f"""\ + TCGv {self.reg_tcg()} = tcg_temp_new(); + gen_read_sreg({self.reg_tcg()}, {self.reg_num}); + """)) + +class SystemPairDest(Register, Pair, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + """)) + def log_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_sreg_write_pair(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_sreg_write_pair(ctx, {self.reg_num}); + """)) + +class SystemPairSource(Register, Pair, OldSource): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + gen_read_sreg_pair({self.reg_tcg()}, {self.reg_num}); + """)) + def init_registers(): regs = { GprDest("R", "d"), @@ -1059,6 +1208,16 @@ def init_registers(): QRegSource("Q", "u"), QRegSource("Q", "v"), QRegReadWrite("Q", "x"), + + # system regs + GuestDest("G", "d"), + GuestSource("G", "s"), + GuestPairDest("G", "dd"), + GuestPairSource("G", "ss"), + SystemDest("S", "d"), + SystemSource("S", "s"), + SystemPairDest("S", "dd"), + SystemPairSource("S", "ss"), } for reg in regs: registers[f"{reg.regtype}{reg.regid}"] = reg From 8466d39b93d63404474dee5fc470beccfd2a42cf Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 16:15:39 -0500 Subject: [PATCH 1060/1179] target/hexagon: Make gen_exception_end_tb non-static Signed-off-by: Brian Cain --- target/hexagon/translate.c | 9 ++++----- target/hexagon/translate.h | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index fe7858703c8c..2e9a934fc6c1 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -185,13 +185,12 @@ static void gen_end_tb(DisasContext *ctx) ctx->base.is_jmp = DISAS_NORETURN; } -static void gen_exception_end_tb(DisasContext *ctx, int excp) +void hex_gen_exception_end_tb(DisasContext *ctx, int excp) { gen_exec_counters(ctx); tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->next_PC); gen_exception_raw(excp); ctx->base.is_jmp = DISAS_NORETURN; - } static int read_packet_words(CPUHexagonState *env, DisasContext *ctx, @@ -558,7 +557,7 @@ static void gen_insn(DisasContext *ctx) ctx->insn->generate(ctx); mark_store_width(ctx); } else { - gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_OPCODE); + hex_gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_OPCODE); } } @@ -912,7 +911,7 @@ static void decode_and_translate_packet(CPUHexagonState *env, DisasContext *ctx) nwords = read_packet_words(env, ctx, words); if (!nwords) { - gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_PACKET); + hex_gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_PACKET); return; } @@ -927,7 +926,7 @@ static void decode_and_translate_packet(CPUHexagonState *env, DisasContext *ctx) gen_commit_packet(ctx); ctx->base.pc_next += pkt.encod_pkt_size_in_bytes; } else { - gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_PACKET); + hex_gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_PACKET); } } diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index d251e2233fda..2bd125297a82 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h @@ -281,6 +281,8 @@ extern TCGv hex_vstore_addr[VSTORES_MAX]; extern TCGv hex_vstore_size[VSTORES_MAX]; extern TCGv hex_vstore_pending[VSTORES_MAX]; +void hex_gen_exception_end_tb(DisasContext *ctx, int excp); + void process_store(DisasContext *ctx, int slot_num); FIELD(PROBE_PKT_SCALAR_STORE_S0, MMU_IDX, 0, 2) From 3f0468f672c356699a82ea55a514a7f6bcc7eca5 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 17 May 2024 18:56:07 -0700 Subject: [PATCH 1061/1179] target/hexagon: Switch to tag_ignore(), generate via get_{user,sys}_tags() Signed-off-by: Brian Cain --- target/hexagon/gen_helper_funcs.py | 21 +++++++++------------ target/hexagon/gen_helper_protos.py | 23 ++++++++++++----------- target/hexagon/gen_idef_parser_funcs.py | 2 ++ target/hexagon/gen_op_attribs.py | 2 +- target/hexagon/gen_opcodes_def.py | 5 ++++- target/hexagon/gen_tcg_func_table.py | 14 ++------------ 6 files changed, 30 insertions(+), 37 deletions(-) diff --git a/target/hexagon/gen_helper_funcs.py b/target/hexagon/gen_helper_funcs.py index c1f806ac4b25..dd8ab6059855 100755 --- a/target/hexagon/gen_helper_funcs.py +++ b/target/hexagon/gen_helper_funcs.py @@ -109,26 +109,23 @@ def main(): tagimms = hex_common.get_tagimms() with open(args.out, "w") as f: - for tag in hex_common.tags: - ## Skip the priv instructions - if "A_PRIV" in hex_common.attribdict[tag]: + for tag in hex_common.get_user_tags(): + if hex_common.tag_ignore(tag): continue - ## Skip the guest instructions - if "A_GUEST" in hex_common.attribdict[tag]: - continue - ## Skip the diag instructions - if tag == "Y6_diag": - continue - if tag == "Y6_diag0": + if hex_common.skip_qemu_helper(tag): continue - if tag == "Y6_diag1": + if hex_common.is_idef_parser_enabled(tag): continue + gen_helper_function(f, tag, tagregs, tagimms) + + f.write("#if !defined(CONFIG_USER_ONLY)\n") + for tag in hex_common.get_sys_tags(): if hex_common.skip_qemu_helper(tag): continue if hex_common.is_idef_parser_enabled(tag): continue - gen_helper_function(f, tag, tagregs, tagimms) + f.write("#endif\n") if __name__ == "__main__": diff --git a/target/hexagon/gen_helper_protos.py b/target/hexagon/gen_helper_protos.py index 77f8e0a6a322..59c8bdd05c0f 100755 --- a/target/hexagon/gen_helper_protos.py +++ b/target/hexagon/gen_helper_protos.py @@ -59,27 +59,28 @@ def main(): tagimms = hex_common.get_tagimms() with open(args.out, "w") as f: - for tag in hex_common.tags: - ## Skip the priv instructions - if "A_PRIV" in hex_common.attribdict[tag]: + for tag in hex_common.get_user_tags(): + if hex_common.tag_ignore(tag): continue - ## Skip the guest instructions - if "A_GUEST" in hex_common.attribdict[tag]: - continue - ## Skip the diag instructions - if tag == "Y6_diag": - continue - if tag == "Y6_diag0": + + if hex_common.skip_qemu_helper(tag): continue - if tag == "Y6_diag1": + if hex_common.is_idef_parser_enabled(tag): continue + gen_helper_prototype(f, tag, tagregs, tagimms) + + f.write("#if !defined(CONFIG_USER_ONLY)\n") + for tag in hex_common.get_sys_tags(): + if hex_common.tag_ignore(tag): + continue if hex_common.skip_qemu_helper(tag): continue if hex_common.is_idef_parser_enabled(tag): continue gen_helper_prototype(f, tag, tagregs, tagimms) + f.write("#endif\n") if __name__ == "__main__": diff --git a/target/hexagon/gen_idef_parser_funcs.py b/target/hexagon/gen_idef_parser_funcs.py index 2f6e826f76d6..32bce9b00286 100644 --- a/target/hexagon/gen_idef_parser_funcs.py +++ b/target/hexagon/gen_idef_parser_funcs.py @@ -60,6 +60,8 @@ def main(): f.write('#include "macros.h.inc"\n\n') for tag in hex_common.tags: + if hex_common.tag_ignore(tag): + continue ## Skip the priv instructions if "A_PRIV" in hex_common.attribdict[tag]: continue diff --git a/target/hexagon/gen_op_attribs.py b/target/hexagon/gen_op_attribs.py index bbbb02df3a23..94dd1f876b21 100755 --- a/target/hexagon/gen_op_attribs.py +++ b/target/hexagon/gen_op_attribs.py @@ -38,7 +38,7 @@ def main(): ## Generate all the attributes associated with each instruction ## with open(args.out, "w") as f: - for tag in hex_common.tags: + for tag in hex_common.get_all_tags(): f.write( f"OP_ATTRIB({tag},ATTRIBS(" f'{",".join(sorted(hex_common.attribdict[tag]))}))\n' diff --git a/target/hexagon/gen_opcodes_def.py b/target/hexagon/gen_opcodes_def.py index 94a19ff412e2..17ba3f9db95e 100755 --- a/target/hexagon/gen_opcodes_def.py +++ b/target/hexagon/gen_opcodes_def.py @@ -37,7 +37,10 @@ def main(): ## Generate a list of all the opcodes ## with open(args.out, "w") as f: - for tag in hex_common.tags: + for tag in hex_common.get_user_tags(): + f.write(f"OPCODE({tag}),\n") + + for tag in hex_common.get_sys_tags(): f.write(f"OPCODE({tag}),\n") diff --git a/target/hexagon/gen_tcg_func_table.py b/target/hexagon/gen_tcg_func_table.py index 299a39b1aa02..70c8db5c44c8 100755 --- a/target/hexagon/gen_tcg_func_table.py +++ b/target/hexagon/gen_tcg_func_table.py @@ -41,19 +41,9 @@ def main(): f.write("#define HEXAGON_FUNC_TABLE_H\n\n") f.write("const SemanticInsn opcode_genptr[XX_LAST_OPCODE] = {\n") + for tag in hex_common.tags: - ## Skip the priv instructions - if "A_PRIV" in hex_common.attribdict[tag]: - continue - ## Skip the guest instructions - if "A_GUEST" in hex_common.attribdict[tag]: - continue - ## Skip the diag instructions - if tag == "Y6_diag": - continue - if tag == "Y6_diag0": - continue - if tag == "Y6_diag1": + if hex_common.tag_ignore(tag): continue f.write(f" [{tag}] = generate_{tag},\n") From 7a771c2d4edff053339a0d8fb0ccde4794c64763 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 17 May 2024 19:07:25 -0700 Subject: [PATCH 1062/1179] target/hexagon: Add privilege check, use tag_ignore() Signed-off-by: Brian Cain --- target/hexagon/cpu_bits.h | 2 ++ target/hexagon/gen_tcg_funcs.py | 32 +++++++++++++++++++------------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h index ff596e2a94c9..6582bb4f16fc 100644 --- a/target/hexagon/cpu_bits.h +++ b/target/hexagon/cpu_bits.h @@ -37,6 +37,8 @@ enum hex_cause { HEX_CAUSE_PC_NOT_ALIGNED = 0x01e, HEX_CAUSE_PRIV_NO_UREAD = 0x024, HEX_CAUSE_PRIV_NO_UWRITE = 0x025, + HEX_CAUSE_PRIV_USER_NO_GINSN = 0x01a, + HEX_CAUSE_PRIV_USER_NO_SINSN = 0x01b, }; #define PACKET_WORDS_MAX 4 diff --git a/target/hexagon/gen_tcg_funcs.py b/target/hexagon/gen_tcg_funcs.py index c2ba91ddc044..65bfa046b867 100755 --- a/target/hexagon/gen_tcg_funcs.py +++ b/target/hexagon/gen_tcg_funcs.py @@ -21,7 +21,7 @@ import re import string import hex_common - +from textwrap import dedent ## ## Generate the TCG code to call the helper @@ -50,6 +50,18 @@ def gen_tcg_func(f, tag, regs, imms): f.write(" Insn *insn G_GNUC_UNUSED = ctx->insn;\n") + if "A_PRIV" in hex_common.attribdict[tag]: + f.write(dedent("""\ +#ifdef CONFIG_USER_ONLY + hex_gen_exception_end_tb(ctx, HEX_CAUSE_PRIV_USER_NO_SINSN); +#else +""")) + if "A_GUEST" in hex_common.attribdict[tag]: + f.write(dedent("""\ +#ifdef CONFIG_USER_ONLY + hex_gen_exception_end_tb(ctx, HEX_CAUSE_PRIV_USER_NO_GINSN); +#else +""")) if hex_common.need_ea(tag): f.write(" TCGv EA G_GNUC_UNUSED = tcg_temp_new();\n") @@ -97,6 +109,11 @@ def gen_tcg_func(f, tag, regs, imms): if reg.is_written(): reg.log_write(f, tag) + if ( + "A_PRIV" in hex_common.attribdict[tag] + or "A_GUEST" in hex_common.attribdict[tag] + ): + f.write("#endif /* CONFIG_USER_ONLY */\n") f.write("}\n\n") @@ -121,18 +138,7 @@ def main(): f.write('#include "idef-generated-emitter.h.inc"\n\n') for tag in hex_common.tags: - ## Skip the priv instructions - if "A_PRIV" in hex_common.attribdict[tag]: - continue - ## Skip the guest instructions - if "A_GUEST" in hex_common.attribdict[tag]: - continue - ## Skip the diag instructions - if tag == "Y6_diag": - continue - if tag == "Y6_diag0": - continue - if tag == "Y6_diag1": + if hex_common.tag_ignore(tag): continue gen_def_tcg_func(f, tag, tagregs, tagimms) From da8f7b245a1fc1543dbe0d02a2d264a6a4f9467a Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sun, 19 May 2024 21:52:51 -0500 Subject: [PATCH 1063/1179] target/hexagon: Add memory order definition Signed-off-by: Brian Cain --- target/hexagon/cpu-param.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h index 45ee7b46409c..ccaf6a9d28d6 100644 --- a/target/hexagon/cpu-param.h +++ b/target/hexagon/cpu-param.h @@ -23,4 +23,9 @@ #define TARGET_PHYS_ADDR_SPACE_BITS 36 #define TARGET_VIRT_ADDR_SPACE_BITS 32 +/* + * Hexagon processors have a strong memory model. + */ +#define TCG_GUEST_DEFAULT_MO (TCG_MO_ALL) + #endif From 0510bd9a63dcd43a561ba6c319b3f6c79b1acc33 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sun, 19 May 2024 21:54:00 -0500 Subject: [PATCH 1064/1179] target/hexagon: Add a placeholder fp exception Signed-off-by: Brian Cain --- target/hexagon/arch.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/target/hexagon/arch.c b/target/hexagon/arch.c index d053d6848715..87c2f6a53f6c 100644 --- a/target/hexagon/arch.c +++ b/target/hexagon/arch.c @@ -208,6 +208,11 @@ void arch_fpop_start(CPUHexagonState *env) * model it in qemu user mode. */ #define RAISE_FP_EXCEPTION do {} while (0) +#else + /* + * To be implemented. + */ +#define RAISE_FP_EXCEPTION do { g_assert_not_reached(); } while (0) #endif #define SOFTFLOAT_TEST_FLAG(FLAG, MYF, MYE) \ From bd14b243d255f7c66b58b35b5158549186bb9829 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 14:27:37 -0500 Subject: [PATCH 1065/1179] target/hexagon: Add guest, system reg number defs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These registers are defined in the Qualcomm Hexagon V71 Programmer's Reference Manual - https://docs.qualcomm.com/bundle/publicresource/80-N2040-51_REV_AB_Hexagon_V71_ProgrammerS_Reference_Manual.pdf Refer to §11.9.1 SYSTEM GUEST, §11.9.2 SYSTEM MONITOR. Signed-off-by: Brian Cain --- target/hexagon/cpu.h | 5 ++ target/hexagon/hex_regs.h | 115 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index f78c8f9c2a00..5e15a8560a3b 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -20,6 +20,11 @@ #include "fpu/softfloat-types.h" +#define NUM_GREGS 32 +#define GREG_WRITES_MAX 32 +#define NUM_SREGS 64 +#define SREG_WRITES_MAX 64 + #include "cpu-qom.h" #include "exec/cpu-defs.h" #include "hex_regs.h" diff --git a/target/hexagon/hex_regs.h b/target/hexagon/hex_regs.h index bddfc28021c6..ea8c62eba9ce 100644 --- a/target/hexagon/hex_regs.h +++ b/target/hexagon/hex_regs.h @@ -81,4 +81,119 @@ enum { HEX_REG_UTIMERHI = 63, }; +#ifndef CONFIG_USER_ONLY + +#define HEX_GREG_VALUES \ + DECL_HEX_GREG(G0, 0) \ + DECL_HEX_GREG(GELR, 0) \ + DECL_HEX_GREG(G1, 1) \ + DECL_HEX_GREG(GSR, 1) \ + DECL_HEX_GREG(G2, 2) \ + DECL_HEX_GREG(GOSP, 2) \ + DECL_HEX_GREG(G3, 3) \ + DECL_HEX_GREG(GBADVA, 3) \ + DECL_HEX_GREG(GCYCLE_1T, 10) \ + DECL_HEX_GREG(GCYCLE_2T, 11) \ + DECL_HEX_GREG(GCYCLE_3T, 12) \ + DECL_HEX_GREG(GCYCLE_4T, 13) \ + DECL_HEX_GREG(GCYCLE_5T, 14) \ + DECL_HEX_GREG(GCYCLE_6T, 15) \ + DECL_HEX_GREG(GPMUCNT4, 16) \ + DECL_HEX_GREG(GPMUCNT5, 17) \ + DECL_HEX_GREG(GPMUCNT6, 18) \ + DECL_HEX_GREG(GPMUCNT7, 19) \ + DECL_HEX_GREG(GPCYCLELO, 24) \ + DECL_HEX_GREG(GPCYCLEHI, 25) \ + DECL_HEX_GREG(GPMUCNT0, 26) \ + DECL_HEX_GREG(GPMUCNT1, 27) \ + DECL_HEX_GREG(GPMUCNT2, 28) \ + DECL_HEX_GREG(GPMUCNT3, 29) \ + DECL_HEX_GREG_DONE + +#define DECL_HEX_GREG_DONE +#define DECL_HEX_GREG(name, val) HEX_GREG_ ##name = val, +enum hex_greg { + HEX_GREG_VALUES +}; +#undef DECL_HEX_GREG +#undef DECL_HEX_GREG_DONE + +#define DECL_HEX_GREG_DONE 0 +#define DECL_HEX_GREG(_, val) (1 << val) | +static inline bool greg_implemented(enum hex_greg greg) +{ +#if NUM_GREGS > 32 +#error "NUM_GREGS too large for greg_implemented(): update `impl_bitmap`" +#endif + static int32_t impl_bitmap = HEX_GREG_VALUES; + return impl_bitmap & (1 << greg); +} +#undef DECL_HEX_GREG +#undef DECL_HEX_GREG_DONE + +#endif /* CONFIG_USER_ONLY */ + +enum { + HEX_SREG_SGP0 = 0, + HEX_SREG_SGP1 = 1, + HEX_SREG_STID = 2, + HEX_SREG_ELR = 3, + HEX_SREG_BADVA0 = 4, + HEX_SREG_BADVA1 = 5, + HEX_SREG_SSR = 6, + HEX_SREG_CCR = 7, + HEX_SREG_HTID = 8, + HEX_SREG_BADVA = 9, + HEX_SREG_IMASK = 10, + HEX_SREG_GEVB = 11, + HEX_SREG_GLB_START = 16, + HEX_SREG_EVB = 16, + HEX_SREG_MODECTL = 17, + HEX_SREG_SYSCFG = 18, + HEX_SREG_IPENDAD = 20, + HEX_SREG_VID = 21, + HEX_SREG_VID1 = 22, + HEX_SREG_BESTWAIT = 23, + HEX_SREG_IEL = 24, + HEX_SREG_SCHEDCFG = 25, + HEX_SREG_IAHL = 26, + HEX_SREG_CFGBASE = 27, + HEX_SREG_DIAG = 28, + HEX_SREG_REV = 29, + HEX_SREG_PCYCLELO = 30, + HEX_SREG_PCYCLEHI = 31, + HEX_SREG_ISDBST = 32, + HEX_SREG_ISDBCFG0 = 33, + HEX_SREG_ISDBCFG1 = 34, + HEX_SREG_LIVELOCK = 35, + HEX_SREG_BRKPTPC0 = 36, + HEX_SREG_BRKPTCFG0 = 37, + HEX_SREG_BRKPTPC1 = 38, + HEX_SREG_BRKPTCFG1 = 39, + HEX_SREG_ISDBMBXIN = 40, + HEX_SREG_ISDBMBXOUT = 41, + HEX_SREG_ISDBEN = 42, + HEX_SREG_ISDBGPR = 43, + HEX_SREG_PMUCNT4 = 44, + HEX_SREG_PMUCNT5 = 45, + HEX_SREG_PMUCNT6 = 46, + HEX_SREG_PMUCNT7 = 47, + HEX_SREG_PMUCNT0 = 48, + HEX_SREG_PMUCNT1 = 49, + HEX_SREG_PMUCNT2 = 50, + HEX_SREG_PMUCNT3 = 51, + HEX_SREG_PMUEVTCFG = 52, + HEX_SREG_PMUSTID0 = 53, + HEX_SREG_PMUEVTCFG1 = 54, + HEX_SREG_PMUSTID1 = 55, + HEX_SREG_TIMERLO = 56, + HEX_SREG_TIMERHI = 57, + HEX_SREG_PMUCFG = 58, + HEX_SREG_S59 = 59, + HEX_SREG_S60 = 60, + HEX_SREG_S61 = 61, + HEX_SREG_S62 = 62, + HEX_SREG_S63 = 63, +}; + #endif From d347106e9b662990687c24824499e491ac0d8b25 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 28 May 2024 22:09:54 -0500 Subject: [PATCH 1066/1179] target/hexagon: Add guest, system reg number state Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 17 +++++++++++++++++ target/hexagon/cpu.h | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 62f1fe15b805..3cdd18c476a2 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -282,6 +282,14 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) set_float_detect_tininess(float_tininess_before_rounding, &env->fp_status); /* Default NaN value: sign bit set, all frac bits set */ set_float_default_nan_pattern(0b11111111, &env->fp_status); + +#ifndef CONFIG_USER_ONLY + if (cs->cpu_index == 0) { + memset(env->g_sreg, 0, sizeof(target_ulong) * NUM_SREGS); + } + memset(env->t_sreg, 0, sizeof(target_ulong) * NUM_SREGS); + memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS); +#endif } static void hexagon_cpu_disas_set_info(CPUState *s, disassemble_info *info) @@ -308,6 +316,15 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) qemu_init_vcpu(cs); cpu_reset(cs); +#ifndef CONFIG_USER_ONLY + if (cs->cpu_index == 0) { + env->g_sreg = g_new0(target_ulong, NUM_SREGS); + } else { + CPUState *cpu0 = qemu_get_cpu(0); + CPUHexagonState *env0 = cpu_env(cpu0); + env->g_sreg = env0->g_sreg; + } +#endif mcc->parent_realize(dev, errp); } diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 5e15a8560a3b..5dde4f8e880c 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -86,6 +86,14 @@ typedef struct CPUArchState { target_ulong stack_start; uint8_t slot_cancelled; + +#ifndef CONFIG_USER_ONLY + /* Some system registers are per thread and some are global. */ + target_ulong t_sreg[NUM_SREGS]; + target_ulong *g_sreg; + + target_ulong greg[NUM_GREGS]; +#endif target_ulong new_value_usr; MemLog mem_log_stores[STORES_MAX]; From 2d206661eba49271fda8d3ca7ee12918594b3c08 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 17:28:56 -0500 Subject: [PATCH 1067/1179] target/hexagon: Add TCG values for sreg, greg Signed-off-by: Brian Cain --- target/hexagon/translate.c | 7 +++++++ target/hexagon/translate.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 2e9a934fc6c1..71c137be308f 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -61,6 +61,13 @@ TCGv hex_vstore_addr[VSTORES_MAX]; TCGv hex_vstore_size[VSTORES_MAX]; TCGv hex_vstore_pending[VSTORES_MAX]; +#ifndef CONFIG_USER_ONLY +TCGv hex_greg[NUM_GREGS]; +TCGv hex_t_sreg[NUM_SREGS]; +TCGv_ptr hex_g_sreg_ptr; +TCGv hex_g_sreg[NUM_SREGS]; +#endif + static const char * const hexagon_prednames[] = { "p0", "p1", "p2", "p3" }; diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index 2bd125297a82..f611c854dcbd 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h @@ -280,6 +280,13 @@ extern TCGv_i64 hex_llsc_val_i64; extern TCGv hex_vstore_addr[VSTORES_MAX]; extern TCGv hex_vstore_size[VSTORES_MAX]; extern TCGv hex_vstore_pending[VSTORES_MAX]; +#ifndef CONFIG_USER_ONLY +extern TCGv hex_greg[NUM_GREGS]; +extern TCGv hex_t_sreg[NUM_SREGS]; +extern TCGv_ptr hex_g_sreg_ptr; +extern TCGv hex_g_sreg[NUM_SREGS]; +#endif + void hex_gen_exception_end_tb(DisasContext *ctx, int excp); From 3531e81d1abd14d4dce3b218cdc57b10321e3154 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 16:27:30 -0500 Subject: [PATCH 1068/1179] target/hexagon: Add guest/sys reg writes to DisasContext Signed-off-by: Brian Cain --- target/hexagon/translate.h | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index f611c854dcbd..0eaa3db03e81 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h @@ -39,6 +39,14 @@ typedef struct DisasContext { int reg_log_idx; DECLARE_BITMAP(regs_written, TOTAL_PER_THREAD_REGS); DECLARE_BITMAP(predicated_regs, TOTAL_PER_THREAD_REGS); +#ifndef CONFIG_USER_ONLY + int greg_log[GREG_WRITES_MAX]; + int greg_log_idx; + int sreg_log[SREG_WRITES_MAX]; + int sreg_log_idx; + TCGv t_sreg_new_value[NUM_SREGS]; + TCGv greg_new_value[NUM_GREGS]; +#endif int preg_log[PRED_WRITES_MAX]; int preg_log_idx; DECLARE_BITMAP(pregs_written, NUM_PREGS); @@ -79,6 +87,34 @@ typedef struct DisasContext { bool is_gather_store_insn(DisasContext *ctx); +#ifndef CONFIG_USER_ONLY +static inline void ctx_log_greg_write(DisasContext *ctx, int rnum) +{ + if (rnum <= HEX_GREG_G3) { + ctx->greg_log[ctx->greg_log_idx] = rnum; + ctx->greg_log_idx++; + } +} + +static inline void ctx_log_greg_write_pair(DisasContext *ctx, int rnum) +{ + ctx_log_greg_write(ctx, rnum); + ctx_log_greg_write(ctx, rnum + 1); +} + +static inline void ctx_log_sreg_write(DisasContext *ctx, int rnum) +{ + ctx->sreg_log[ctx->sreg_log_idx] = rnum; + ctx->sreg_log_idx++; +} + +static inline void ctx_log_sreg_write_pair(DisasContext *ctx, int rnum) +{ + ctx_log_sreg_write(ctx, rnum); + ctx_log_sreg_write(ctx, rnum + 1); +} +#endif + static inline void ctx_log_pred_write(DisasContext *ctx, int pnum) { if (!test_bit(pnum, ctx->pregs_written)) { From 9381990c6782d69e6e25dc9eef5c3ac1670e1bcb Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 17:04:25 -0500 Subject: [PATCH 1069/1179] target/hexagon: Add imported macro, attr defs for sysemu Signed-off-by: Brian Cain --- target/hexagon/attribs_def.h.inc | 414 +++++++++++++++++++-- target/hexagon/imported/macros.def | 558 +++++++++++++++++++++++++++++ 2 files changed, 942 insertions(+), 30 deletions(-) mode change 100755 => 100644 target/hexagon/imported/macros.def diff --git a/target/hexagon/attribs_def.h.inc b/target/hexagon/attribs_def.h.inc index 9e3a05f88281..e6523a739b10 100644 --- a/target/hexagon/attribs_def.h.inc +++ b/target/hexagon/attribs_def.h.inc @@ -19,20 +19,41 @@ DEF_ATTRIB(AA_DUMMY, "Dummy Zeroth Attribute", "", "") /* Misc */ +DEF_ATTRIB(FAKEINSN, "Not a real instruction", "", "") +DEF_ATTRIB(MAPPING, "Not real -- asm mapped", "", "") +DEF_ATTRIB(CONDMAPPING, "Not real -- mapped based on values", "", "") DEF_ATTRIB(EXTENSION, "Extension instruction", "", "") +DEF_ATTRIB(SHARED_EXTENSION, "Shared extension instruction", "", "") +DEF_ATTRIB(CABAC, + "Cabac Instruction. Used in conjuction with QDSP6_CABAC_PRESENT", "", + "") +DEF_ATTRIB(EXPERIMENTAL, "This may not work correctly not supported by RTL.", + "", "") DEF_ATTRIB(PRIV, "Not available in user or guest mode", "", "") DEF_ATTRIB(GUEST, "Not available in user mode", "", "") DEF_ATTRIB(FPOP, "Floating Point Operation", "", "") +DEF_ATTRIB(FPDOUBLE, "Double-precision Floating Point Operation", "", "") +DEF_ATTRIB(FPSINGLE, "Single-precision Floating Point Operation", "", "") +DEF_ATTRIB(SFMAKE, "Single Float Make", "", "") +DEF_ATTRIB(DFMAKE, "Single Float Make", "", "") + +DEF_ATTRIB(NO_TIMING_LOG, "Does not get logged to the timing model", "", "") DEF_ATTRIB(EXTENDABLE, "Immediate may be extended", "", "") +DEF_ATTRIB(EXT_UPPER_IMMED, "Extend upper case immediate", "", "") +DEF_ATTRIB(EXT_LOWER_IMMED, "Extend lower case immediate", "", "") +DEF_ATTRIB(MUST_EXTEND, "Immediate must be extended", "", "") +DEF_ATTRIB(NA_NT, "Non-Allocating Non-Temporal instruction", "", "") +DEF_ATTRIB(INVPRED, "The predicate is inverted for true/false sense", "", "") DEF_ATTRIB(ARCHV2, "V2 architecture", "", "") DEF_ATTRIB(ARCHV3, "V3 architecture", "", "") DEF_ATTRIB(ARCHV4, "V4 architecture", "", "") DEF_ATTRIB(ARCHV5, "V5 architecture", "", "") +DEF_ATTRIB(PACKED, "Packable instruction", "", "") DEF_ATTRIB(SUBINSN, "sub-instruction", "", "") /* Load and Store attributes */ @@ -46,21 +67,48 @@ DEF_ATTRIB(MEMSIZE_4B, "Memory width is 4 bytes", "", "") DEF_ATTRIB(MEMSIZE_8B, "Memory width is 8 bytes", "", "") DEF_ATTRIB(SCALAR_LOAD, "Load is scalar", "", "") DEF_ATTRIB(SCALAR_STORE, "Store is scalar", "", "") -DEF_ATTRIB(REGWRSIZE_1B, "Memory width is 1 byte", "", "") -DEF_ATTRIB(REGWRSIZE_2B, "Memory width is 2 bytes", "", "") -DEF_ATTRIB(REGWRSIZE_4B, "Memory width is 4 bytes", "", "") -DEF_ATTRIB(REGWRSIZE_8B, "Memory width is 8 bytes", "", "") +DEF_ATTRIB(REGWRSIZE_1B, "ETM Memory width is 1 byte", "", "") +DEF_ATTRIB(REGWRSIZE_2B, "ETM Memory width is 2 bytes", "", "") +DEF_ATTRIB(REGWRSIZE_4B, "ETM Memory width is 4 bytes", "", "") +DEF_ATTRIB(REGWRSIZE_8B, "ETM Memory width is 8 bytes", "", "") DEF_ATTRIB(MEMLIKE, "Memory-like instruction", "", "") DEF_ATTRIB(MEMLIKE_PACKET_RULES, "follows Memory-like packet rules", "", "") +DEF_ATTRIB(CACHEOP, "Cache operation", "", "") +DEF_ATTRIB(COPBYADDRESS, "Cache operation by address", "", "") +DEF_ATTRIB(COPBYIDX, "Cache operation by index", "", "") DEF_ATTRIB(RELEASE, "Releases a lock", "", "") DEF_ATTRIB(ACQUIRE, "Acquires a lock", "", "") +DEF_ATTRIB(LLSC, "load-locked/store-conditional instruction", "", "") DEF_ATTRIB(RLS_INNER, "Store release inner visibility", "", "") +DEF_ATTRIB(RLS_OUTER, "Store release outer visibility", "", "") DEF_ATTRIB(RLS_ALL_THREAD, "Store release among all threads", "", "") DEF_ATTRIB(RLS_SAME_THREAD, "Store release with the same thread", "", "") +/* Load and Store Addressing Mode Attributes */ +DEF_ATTRIB(EA_REG_ONLY, "EA = input register only", "", "") +DEF_ATTRIB(EA_IMM_ONLY, "EA = immediate only", "", "") +DEF_ATTRIB(EA_REG_PLUS_IMM, "EA = register plus immediate", "", "") +DEF_ATTRIB(EA_REG_PLUS_REGSCALED, "EA = register plus scaled register", "", "") +DEF_ATTRIB(EA_IMM_PLUS_REGSCALED, "EA = immediate plus scaled register", "", "") +DEF_ATTRIB(EA_BREV_REG, "EA = bit-reversed input register", "", "") +DEF_ATTRIB(EA_GP_IMM, "EA = GP plus immediate (unless extended)", "", "") +DEF_ATTRIB(EA_PAGECROSS, "EA calculation can have a Page Cross Stall", "", "") + +DEF_ATTRIB(PM_ANY, "Post Modify", "", "") +DEF_ATTRIB(PM_I, "Post Modify by Immediate", "", "") +DEF_ATTRIB(PM_M, "Post Modify by M register", "", "") +DEF_ATTRIB(PM_CIRI, "Post Modify with Circular Addressing by immediate", "", "") +DEF_ATTRIB(PM_CIRR, "Post Modify with Circular Addressing by I field", "", "") + +DEF_ATTRIB(VMEM, "VMEM-type", "", "") +DEF_ATTRIB(VBUF, "Touches the VBUF", "", "") +DEF_ATTRIB(VDBG, "Vector debugging instruction", "", "") + /* V6 Vector attributes */ DEF_ATTRIB(CVI, "Executes on the HVX extension", "", "") +DEF_ATTRIB(NT_VMEM, "Non-temporal memory access", "", "") +DEF_ATTRIB(VMEMU, "Unaligned memory access", "", "") DEF_ATTRIB(CVI_NEW, "New value memory instruction executes on HVX", "", "") DEF_ATTRIB(CVI_VM, "Memory instruction executes on HVX", "", "") @@ -69,109 +117,415 @@ DEF_ATTRIB(CVI_VP_VS, "Double vector permute/shft insn executes on HVX", "", "") DEF_ATTRIB(CVI_VX, "Multiply instruction executes on HVX", "", "") DEF_ATTRIB(CVI_VX_DV, "Double vector multiply insn executes on HVX", "", "") DEF_ATTRIB(CVI_VS, "Shift instruction executes on HVX", "", "") -DEF_ATTRIB(CVI_VS_3SRC, "This shift needs to borrow a source register", "", "") +DEF_ATTRIB( + CVI_VS_3SRC, + "This shift instruction needs to borrow a source register from the VP slot", + "", "") DEF_ATTRIB(CVI_VS_VX, "Permute/shift and multiply insn executes on HVX", "", "") DEF_ATTRIB(CVI_VA, "ALU instruction executes on HVX", "", "") +DEF_ATTRIB(CVI_VA_2SRC, + "This alu instruction executes on multimedia vector engine and " + "requires two vectro sources", + "", "") DEF_ATTRIB(CVI_VA_DV, "Double vector alu instruction executes on HVX", "", "") DEF_ATTRIB(CVI_4SLOT, "Consumes all the vector execution resources", "", "") DEF_ATTRIB(CVI_TMP, "Transient Memory Load not written to register", "", "") DEF_ATTRIB(CVI_REMAP, "Register Renaming not written to register file", "", "") +DEF_ATTRIB(CVI_TMP_SRC, "Transient reassign", "", "") +DEF_ATTRIB(CVI_EXTRACT, "HVX Extract Instruction that goes through L2", "", "") +DEF_ATTRIB(CVI_EARLY, "HVX instructions that require early sources", "", "") +DEF_ATTRIB(CVI_LATE, "HVX insn that always require late sources", "", "") +DEF_ATTRIB(CVI_VV_LATE, "HVX insn that always require late Vv source", "", "") +DEF_ATTRIB(CVI_REQUIRES_TMPLOAD, ".tmp load must be included in packet", "", "") +DEF_ATTRIB(CVI_PUMP_2X, "Goes through the pipeline twice", "", "") +DEF_ATTRIB(CVI_PUMP_4X, "Goes through the pipeline four times", "", "") DEF_ATTRIB(CVI_GATHER, "CVI Gather operation", "", "") DEF_ATTRIB(CVI_SCATTER, "CVI Scatter operation", "", "") DEF_ATTRIB(CVI_SCATTER_RELEASE, "CVI Store Release for scatter", "", "") +DEF_ATTRIB(CVI_GATHER_RELEASE, "CVI Store Release for gather", "", "") DEF_ATTRIB(CVI_TMP_DST, "CVI instruction that doesn't write a register", "", "") +DEF_ATTRIB(CVI_SCATTER_WORD_ACC, "CVI Scatter Word Accum (second pass)", "", "") +DEF_ATTRIB(CVI_SCATTER_ACC, "CVI Scatter Accumulate", "", "") +DEF_ATTRIB(CVI_VX_VSRC0_IS_DST, + "For the assembler to handle the special case of non-linear " + "instructions with Vxx specified both as src and dst in syntax ", + "", "") + +DEF_ATTRIB(CVI_VX_ACC_FWD, "VX Accumulator Forwarding", "", "") + +DEF_ATTRIB(CVI_VX_NO_TMP_LD, + "VX Accumulator renaming not allowed from tmp load instruction", "", + "") + +DEF_ATTRIB(RESTRICT_CVI_NOVP, + "Instructions with this attribute are assigned to the original " + "shift unit and can not be assigned to the shift/permute unit", + "", "") + +DEF_ATTRIB(CVI_GATHER_ADDR_2B, "CVI Scatter/Gather address is halfword", "", "") +DEF_ATTRIB(CVI_GATHER_ADDR_4B, "CVI Scatter/Gather address is word", "", "") + +DEF_ATTRIB(VFETCH, "memory fetch op to L2 for a single vector", "", "") + DEF_ATTRIB(CVI_SLOT23, "Can execute in slot 2 or slot 3 (HVX)", "", "") -DEF_ATTRIB(VTCM_ALLBANK_ACCESS, "Allocates in all VTCM schedulers.", "", "") +DEF_ATTRIB(HVX_FLT, "This a floating point HVX instruction.", "", "") + +DEF_ATTRIB( + VTCM_ALLBANK_ACCESS, + "This instruction allocates in all VTCM schedulers due to a region access.", + "", "") +DEF_ATTRIB(XUMINOR, "XU minor SMTable instruction", "", "") + +DEF_ATTRIB(SYNC_MARKER, "This instruction needs a sync marker.", "", "") + /* Change-of-flow attributes */ DEF_ATTRIB(JUMP, "Jump-type instruction", "", "") +DEF_ATTRIB(DIRECT, "Uses an PC-relative immediate field", "", "") DEF_ATTRIB(INDIRECT, "Absolute register jump", "", "") +DEF_ATTRIB(CJUMP, "Conditional jump", "", "") DEF_ATTRIB(CALL, "Function call instruction", "", "") +DEF_ATTRIB(RET, "Function return instruction", "", "") +DEF_ATTRIB(PERM, "Permute instruction", "", "") DEF_ATTRIB(COF, "Change-of-flow instruction", "", "") DEF_ATTRIB(HINTED_COF, "This instruction is a hinted change-of-flow", "", "") DEF_ATTRIB(CONDEXEC, "May be cancelled by a predicate", "", "") +DEF_ATTRIB(DOTOLD, "Uses a predicate generated in a previous packet", "", "") +DEF_ATTRIB(DOTNEW, "Uses a predicate generated in the same packet", "", "") DEF_ATTRIB(DOTNEWVALUE, "Uses a register value generated in this pkt", "", "") DEF_ATTRIB(NEWCMPJUMP, "Compound compare and jump", "", "") DEF_ATTRIB(NVSTORE, "New-value store", "", "") DEF_ATTRIB(MEMOP, "memop", "", "") -DEF_ATTRIB(ROPS_2, "Compound instruction worth 2 RISC-ops", "", "") -DEF_ATTRIB(ROPS_3, "Compound instruction worth 3 RISC-ops", "", "") +DEF_ATTRIB(ROPS_2, "Compound instruction worth 2 wimpy RISC-ops", "", "") +DEF_ATTRIB(ROPS_3, "Compound instruction worth 3 wimpy RISC-ops", "", "") /* access to implicit registers */ DEF_ATTRIB(IMPLICIT_WRITES_LR, "Writes the link register", "", "UREG.LR") +DEF_ATTRIB(IMPLICIT_READS_LR, "Reads the link register", "UREG.LR", "") +DEF_ATTRIB(IMPLICIT_READS_LC0, "Reads loop count for loop 0", "UREG.LC0", "") +DEF_ATTRIB(IMPLICIT_READS_LC1, "Reads loop count for loop 1", "UREG.LC1", "") +DEF_ATTRIB(IMPLICIT_READS_SA0, "Reads start address for loop 0", "UREG.SA0", "") +DEF_ATTRIB(IMPLICIT_READS_SA1, "Reads start address for loop 1", "UREG.SA1", "") +DEF_ATTRIB(IMPLICIT_WRITES_PC, "Writes the program counter", "", "UREG.PC") +DEF_ATTRIB(IMPLICIT_READS_PC, "Reads the program counter", "UREG.PC", "") DEF_ATTRIB(IMPLICIT_WRITES_SP, "Writes the stack pointer", "", "UREG.SP") +DEF_ATTRIB(IMPLICIT_READS_SP, "Reads the stack pointer", "UREG.SP", "") DEF_ATTRIB(IMPLICIT_WRITES_FP, "Writes the frame pointer", "", "UREG.FP") +DEF_ATTRIB(IMPLICIT_READS_FP, "Reads the frame pointer", "UREG.FP", "") +DEF_ATTRIB(IMPLICIT_WRITES_GP, "Writes the GP register", "", "UREG.GP") +DEF_ATTRIB(IMPLICIT_READS_GP, "Reads the GP register", "UREG.GP", "") DEF_ATTRIB(IMPLICIT_WRITES_LC0, "Writes loop count for loop 0", "", "UREG.LC0") DEF_ATTRIB(IMPLICIT_WRITES_LC1, "Writes loop count for loop 1", "", "UREG.LC1") DEF_ATTRIB(IMPLICIT_WRITES_SA0, "Writes start addr for loop 0", "", "UREG.SA0") DEF_ATTRIB(IMPLICIT_WRITES_SA1, "Writes start addr for loop 1", "", "UREG.SA1") +DEF_ATTRIB(IMPLICIT_WRITES_R00, "Writes Register 0", "", "UREG.R00") DEF_ATTRIB(IMPLICIT_WRITES_P0, "Writes Predicate 0", "", "UREG.P0") DEF_ATTRIB(IMPLICIT_WRITES_P1, "Writes Predicate 1", "", "UREG.P1") DEF_ATTRIB(IMPLICIT_WRITES_P2, "Writes Predicate 1", "", "UREG.P2") DEF_ATTRIB(IMPLICIT_WRITES_P3, "May write Predicate 3", "", "UREG.P3") -DEF_ATTRIB(IMPLICIT_READS_PC, "Reads the PC register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P0, "Reads the P0 register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P1, "Reads the P1 register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P2, "Reads the P2 register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P3, "Reads the P3 register", "", "") +DEF_ATTRIB(IMPLICIT_READS_R00, "Reads Register 0", "UREG.R00", "") +DEF_ATTRIB(IMPLICIT_READS_P0, "Reads Predicate 0", "UREG.P0", "") +DEF_ATTRIB(IMPLICIT_READS_P1, "Reads Predicate 1", "UREG.P1", "") +DEF_ATTRIB(IMPLICIT_READS_P3, "Reads Predicate 3", "UREG.P3", "") +DEF_ATTRIB(IMPLICIT_READS_Q3, "Reads Vector Predicate 3", "UREG.Q3", "") +DEF_ATTRIB(IMPLICIT_READS_CS, "Reads the CS/M register", "UREG.CS", "") +DEF_ATTRIB(IMPLICIT_READS_FRAMEKEY, "Reads FRAMEKEY", "UREG.FRAMEKEY", "") +DEF_ATTRIB(IMPLICIT_READS_FRAMELIMIT, "Reads FRAMELIMIT", "UREG.FRAMELIMIT", "") +DEF_ATTRIB(IMPLICIT_READS_ELR, "Reads the ELR register", "MREG.ELR", "") +DEF_ATTRIB(IMPLICIT_READS_SGP0, "Reads the SGP0 register", "MREG.SGP0", "") +DEF_ATTRIB(IMPLICIT_READS_SGP1, "Reads the SGP1 register", "MREG.SGP1", "") +DEF_ATTRIB(IMPLICIT_WRITES_SGP0, "Reads the SGP0 register", "", "MREG.SGP0") +DEF_ATTRIB(IMPLICIT_WRITES_SGP1, "Reads the SGP1 register", "", "MREG.SGP1") +DEF_ATTRIB(IMPLICIT_WRITES_STID_PRIO_ANYTHREAD, "Reads", "", "MREG.STID.PRIO") +DEF_ATTRIB(IMPLICIT_WRITES_SRBIT, "Writes the OVF bit", "", "UREG.SR.OVF") +DEF_ATTRIB(IMPLICIT_WRITES_FPFLAGS, "May write FP flags", "", "UREG.SR.FPFLAGS") +DEF_ATTRIB(IMPLICIT_WRITES_LPCFG, "Writes the loop config", "", "UREG.SR.LPCFG") +DEF_ATTRIB(IMPLICIT_WRITES_CVBITS, "Writes the CV flags", "", "UREG.SR.CV") +DEF_ATTRIB(IMPLICIT_READS_FPRND, "May read FP rnd mode", "UREG.SR.FPRND", "") +DEF_ATTRIB(IMPLICIT_READS_SSR, "May read SSR values", "MREG.SSR", "") +DEF_ATTRIB(IMPLICIT_READS_CCR, "May read CCR values", "MREG.CCR", "") +DEF_ATTRIB(IMPLICIT_WRITES_CCR, "May write CCR values", "", "MREG.CCR") +DEF_ATTRIB(IMPLICIT_WRITES_SSR, "May write SSR values", "", "MREG.SSR") +DEF_ATTRIB(IMPLICIT_READS_GELR, "May read GELR values", "GREG.GELR", "") +DEF_ATTRIB(IMPLICIT_READS_GEVB, "May read GEVB values", "MREG.GEVB", "") +DEF_ATTRIB(IMPLICIT_READS_GSR, "May read GSR values", "GREG.GSR", "") +DEF_ATTRIB(IMPLICIT_READS_GOSP, "May read GOSP values", "GREG.GOSP", "") +DEF_ATTRIB(IMPLICIT_WRITES_GELR, "May write GELR values", "", "GREG.GELR") +DEF_ATTRIB(IMPLICIT_WRITES_GSR, "May write GSR values", "", "GREG.GSR") +DEF_ATTRIB(IMPLICIT_WRITES_GOSP, "May write GOSP values", "", "GREG.GOSP") +DEF_ATTRIB(IMPLICIT_READS_IPENDAD_IPEND, "May read", "MREG.IPENDAD.IPEND", "") +DEF_ATTRIB(IMPLICIT_WRITES_IPENDAD_IPEND, "May write", "", "MREG.IPENDAD.IPEND") +DEF_ATTRIB(IMPLICIT_READS_IPENDAD_IAD, "May read", "MREG.IPENDAD.IAD", "") +DEF_ATTRIB(IMPLICIT_WRITES_IPENDAD_IAD, "May write", "", "MREG.IPENDAD.IAD") +DEF_ATTRIB(IMPLICIT_WRITES_IMASK_ANYTHREAD, "May write", "", "MREG.IMASK") +DEF_ATTRIB(IMPLICIT_READS_IMASK_ANYTHREAD, "May read", "MREG.IMASK", "") +DEF_ATTRIB(IMPLICIT_READS_SYSCFG_K0LOCK, "May read", "MREG.SYSCFG.K0LOCK", "") +DEF_ATTRIB(IMPLICIT_WRITES_SYSCFG_K0LOCK, "May write", "", "MREG.SYSCFG.K0LOCK") +DEF_ATTRIB(IMPLICIT_READS_SYSCFG_TLBLOCK, "May read", "MREG.SYSCFG.TLBLOCK", "") +DEF_ATTRIB(IMPLICIT_WRITES_SYSCFG_TLBLOCK, "May wr", "", "MREG.SYSCFG.TLBLOCK") +DEF_ATTRIB(IMPLICIT_WRITES_SYSCFG_GCA, "May write", "", "MREG.SYSCFG.GCA") +DEF_ATTRIB(IMPLICIT_READS_SYSCFG_GCA, "May read", "MREG.SYSCFG.GCA", "") DEF_ATTRIB(IMPLICIT_WRITES_USR, "May write USR", "", "") -DEF_ATTRIB(IMPLICIT_READS_SP, "Reads the SP register", "", "") + +/* Other things the instruction does */ +DEF_ATTRIB(ACC, "Has a multiply", "", "") +DEF_ATTRIB(MPY, "Has a multiply", "", "") +DEF_ATTRIB(SATURATE, "Does signed saturation", "", "") +DEF_ATTRIB(USATURATE, "Does unsigned saturation", "", "") +DEF_ATTRIB(CIRCADDR, "Uses circular addressing mode", "", "") +DEF_ATTRIB(BREVADDR, "Uses bit reverse addressing mode", "", "") +DEF_ATTRIB(BIDIRSHIFTL, "Uses a bidirectional shift left", "", "") +DEF_ATTRIB(BIDIRSHIFTR, "Uses a bidirectional shift right", "", "") +DEF_ATTRIB(BRANCHADDER, "Contains a PC-plus-immediate operation.", "", "") +DEF_ATTRIB(CRSLOT23, "Can execute in slot 2 or slot 3 (CR)", "", "") DEF_ATTRIB(COMMUTES, "The operation is communitive", "", "") DEF_ATTRIB(DEALLOCRET, "dealloc_return", "", "") DEF_ATTRIB(DEALLOCFRAME, "deallocframe", "", "") -DEF_ATTRIB(CRSLOT23, "Can execute in slot 2 or slot 3 (CR)", "", "") +/* Instruction Types */ + +DEF_ATTRIB(IT_ALU, "ALU type", "", "") +DEF_ATTRIB(IT_ALU_ADDSUB, "ALU add or subtract type", "", "") +DEF_ATTRIB(IT_ALU_MINMAX, "ALU MIN or MAX type", "", "") +DEF_ATTRIB(IT_ALU_MOVE, "ALU data movement type", "", "") +DEF_ATTRIB(IT_ALU_LOGICAL, "ALU logical operation type", "", "") +DEF_ATTRIB(IT_ALU_SHIFT, "ALU shift operation type", "", "") +DEF_ATTRIB(IT_ALU_SHIFT_AND_OP, "ALU shift and additional op type", "", "") +DEF_ATTRIB(IT_ALU_CMP, "ALU compare operation type", "", "") + +DEF_ATTRIB(IT_LOAD, "Loads from memory", "", "") +DEF_ATTRIB(IT_STORE, "Stores to memory", "", "") + +DEF_ATTRIB(IT_MPY, "Multiply type", "", "") +DEF_ATTRIB(IT_MPY_32, "32-bit Multiply type", "", "") + +DEF_ATTRIB(IT_COF, "Change-of-flow type", "", "") +DEF_ATTRIB(IT_HWLOOP, "Sets up hardware loop registers", "", "") + +DEF_ATTRIB(IT_MISC, "misc instruction type", "", "") + DEF_ATTRIB(IT_NOP, "nop instruction", "", "") DEF_ATTRIB(IT_EXTENDER, "constant extender instruction", "", "") +/* Exceptions the instruction can generate */ + +DEF_ATTRIB(EXCEPTION_TLB, "Can generate a TLB Miss Exception", "", "") +DEF_ATTRIB(EXCEPTION_ACCESS, "Can generate Access Violation Exception", "", "") +DEF_ATTRIB(EXCEPTION_SWI, "Software Interrupt (trap) exception", "", "") + + +/* Documentation Notes */ +DEF_ATTRIB(NOTE_ARCHV2, "Only available in the V2 architecture", "", "") + +DEF_ATTRIB(NOTE_PACKET_PC, "The PC is the addr of the start of the pkt", "", "") + +DEF_ATTRIB(NOTE_PACKET_NPC, "Next PC is the address following pkt", "", "") + +DEF_ATTRIB(NOTE_CONDITIONAL, "can be conditionally executed", "", "") + +DEF_ATTRIB(NOTE_NEWVAL_SLOT0, "New-value oprnd must execute on slot 0", "", "") + +DEF_ATTRIB(NOTE_RELATIVE_ADDRESS, "A PC-relative address is formed", "", "") + +DEF_ATTRIB(NOTE_LA_RESTRICT, "Cannot be in the last pkt of a HW loop", "", "") + +DEF_ATTRIB(NOTE_OOBVSHIFT, "Possible shift overflow", "", "") +DEF_ATTRIB(NOTE_BIDIRSHIFT, "Bidirectional shift", "", "") + +DEF_ATTRIB(NOTE_CVFLAGS, "Sets the Carry and Overflow flags in USR.", "", "") +DEF_ATTRIB(NOTE_SR_OVF_WHEN_SATURATING, "Might set OVF bit", "", "") +DEF_ATTRIB(NOTE_STNT, + "Non Temporal Data. The :nt appendix is a hint to the " + "microarchitecture indicating that the life of the cache line is " + "short. This information is used throughout the cache hierarchy to " + "make replacement and allocation decisions.", + "", "") +DEF_ATTRIB(NOTE_PRIV, "Monitor-level feature", "", "") +DEF_ATTRIB(NOTE_GUEST, "Guest-level feature", "", "") +DEF_ATTRIB(NOTE_NOPACKET, "solo instruction", "", "") +DEF_ATTRIB(NOTE_AXOK, "May only be grouped with ALU32 or non-FP XTYPE.", "", "") +DEF_ATTRIB(NOTE_NOSLOT1, "Packet with this insn must have slot 1 empty", "", "") +DEF_ATTRIB(NOTE_SLOT1_AOK, "Packet must have slot 1 empty or ALU32", "", "") +DEF_ATTRIB(NOTE_NOSLOT01, "Packet must have both slot 1 and 2 empty", "", "") +DEF_ATTRIB(NOTE_NEEDS_MEMLD, "Must be grouped with a memory load", "", "") +DEF_ATTRIB(NOTE_LATEPRED, "The predicate can not be used as a .new", "", "") +DEF_ATTRIB(NOTE_COMPAT_ACCURACY, "In the future accuracy may increase", "", "") +DEF_ATTRIB(NOTE_NVSLOT0, "Can execute only in slot 0 (ST)", "", "") +DEF_ATTRIB(NOTE_DEPRECATED, "Will be deprecated in a future version.", "", "") +DEF_ATTRIB(NOTE_NONAPALIV1, "may not work correctly in Napali V1.", "", "") +DEF_ATTRIB(NOTE_NOLAHAINAV1, "This may not work correctly in Lahaina V1.", "", + "") +DEF_ATTRIB(NOTE_BADTAG_UNDEF, "Undefined if a tag is non-present", "", "") +DEF_ATTRIB(NOTE_NOSLOT2_MPY, "Packet cannot have a slot 2 multiply", "", "") +DEF_ATTRIB(NOTE_HVX_ONLY, "Only available on a core with HVX.", "", "") + +DEF_ATTRIB(NOTE_NOCOF_RESTRICT, "Cannot be grouped with any COF", "", "") +DEF_ATTRIB(NOTE_BRANCHADDER_MAX1, "One PC-plus-offset calculation", "", "") + +DEF_ATTRIB(NOTE_CRSLOT23, "Execute on either slot2 or slot3 (CR)", "", "") +DEF_ATTRIB(NOTE_EXTENSION_AUDIO, "Hexagon audio extensions", "", "") +DEF_ATTRIB(NOTE_FETCHNT, + "Non Temporal Data Cache Prefetch. The :nt appendix is a hint to " + "the microarchitecture indicating that the life of the cache line " + "fetched is short. This information is used throughout the cache " + "hierarchy to make replacement and allocation decisions.", + "", "") +DEF_ATTRIB(NOTE_VECX_V67, "This instruction is only available on V67", "", "") + +DEF_ATTRIB(NOTE_NOVP, + "This instruction cannot be paired with a HVX permute instruction", + "", "") +DEF_ATTRIB(NOTE_VA_UNARY, + "If a packet contains this instruction and a HVX ALU op then the " + "ALU OP must be unary.", + "", "") + + +/* V6 MMVector Notes for Documentation */ +DEF_ATTRIB(NOTE_ANY_RESOURCE, "Can use any HVX resource.", "", "") +DEF_ATTRIB(NOTE_ANY2_RESOURCE, "Uses any pair of the HVX resources", "", "") +DEF_ATTRIB(NOTE_PERMUTE_RESOURCE, "Uses the HVX permute resource.", "", "") +DEF_ATTRIB(NOTE_SHIFT_RESOURCE, "Uses the HVX shift resource.", "", "") +DEF_ATTRIB(NOTE_MPY_RESOURCE, "Uses a HVX multiply resource.", "", "") +DEF_ATTRIB(NOTE_MPYDV_RESOURCE, "Uses both HVX multiply resources.", "", "") +DEF_ATTRIB(NOTE_NT_VMEM, "Non-temporal hint to the micro-architecture", "", "") +DEF_ATTRIB(NOTE_ALL_RESOURCE, "Uses all HVX resources.", "", "") +DEF_ATTRIB(NOTE_VMEM, "Immediates are in multiples of vector length.", "", "") +DEF_ATTRIB(NOTE_ANY_VS_VX_RESOURCE, "Consumes two resources", "", "") + +DEF_ATTRIB(NOTE_RT8, "Input scalar register Rt is limited to R0-R7", "", "") + +DEF_ATTRIB(NOTE_MX, "This is in-memory matrix multiply instruction.", "", "") +DEF_ATTRIB(NOTE_VX_ACC_FWD, + "The accumulator (Vxx) source of this instruction must be generate " + "in the previous packet to avoid a stall. The accumulator cannot " + "come from a .tmp operation.", + "", "") +DEF_ATTRIB(NOTE_TMP_NO_VX, + "The tmp load instruction destination register cannot be an " + "accumulator register.", + "", "") + +DEF_ATTRIB( + NOTE_NO_ECC, + "ECC is not supported for scatter and gather instructions. Enabling ECC " + "with unprotected access instructions result in undetermined behavior.", + "", "") + +/* FP8 instructions */ +DEF_ATTRIB(HVX_FP8, "HVX FP8 extension instruction", "", "") +DEF_ATTRIB(HVX_IEEE_FP_OUT_8, "HVX IEEE FP extension instruction: 8-bit output", + "", "") + /* Restrictions to make note of */ +DEF_ATTRIB(RESTRICT_LOOP_LA, "Cannot be in the last packet of a loop", "", "") +DEF_ATTRIB(RESTRICT_NEEDS_MEMLD, "Must be grouped with a load", "", "") DEF_ATTRIB(RESTRICT_COF_MAX1, "One change-of-flow per packet", "", "") DEF_ATTRIB(RESTRICT_NOPACKET, "Not allowed in a packet", "", "") +DEF_ATTRIB(RESTRICT_NOSRMOVE, "Do not write SR in the same packet", "", "") DEF_ATTRIB(RESTRICT_SLOT0ONLY, "Must execute on slot0", "", "") DEF_ATTRIB(RESTRICT_SLOT1ONLY, "Must execute on slot1", "", "") DEF_ATTRIB(RESTRICT_SLOT2ONLY, "Must execute on slot2", "", "") DEF_ATTRIB(RESTRICT_SLOT3ONLY, "Must execute on slot3", "", "") +DEF_ATTRIB(RESTRICT_NOSLOT2_MPY, "A packet cannot have a slot 2 mpy", "", "") DEF_ATTRIB(RESTRICT_NOSLOT1, "No slot 1 instruction in parallel", "", "") +DEF_ATTRIB(RESTRICT_SLOT1_AOK, "Slot 1 insn must be empty or A-type", "", "") +DEF_ATTRIB(RESTRICT_NOSLOT01, "No slot 0 or 1 instructions in parallel", "", "") +DEF_ATTRIB(RESTRICT_NOSLOT1_STORE, "Packet must not have slot 1 store", "", "") +DEF_ATTRIB(RESTRICT_NOSLOT0_LOAD, "Packet must not have a slot 1 load", "", "") +DEF_ATTRIB(RESTRICT_NOCOF, "Cannot be grouped with any COF", "", "") +DEF_ATTRIB(RESTRICT_BRANCHADDER_MAX1, "One PC-plus-offset calculation", "", "") DEF_ATTRIB(RESTRICT_PREFERSLOT0, "Try to encode into slot 0", "", "") +DEF_ATTRIB(RESTRICT_SINGLE_MEM_FIRST, "Single memory op must be last", "", "") DEF_ATTRIB(RESTRICT_PACKET_AXOK, "May exist with A-type or X-type", "", "") +DEF_ATTRIB(RESTRICT_PACKET_SOMEREGS_OK, "Relaxed grouping rules", "", "") +DEF_ATTRIB(RESTRICT_LATEPRED, "Predicate can not be used as a .new.", "", "") + +DEF_ATTRIB(PAIR_1OF2, "For assembler", "", "") +DEF_ATTRIB(PAIR_2OF2, "For assembler", "", "") +DEF_ATTRIB(NOTE_MX_PAIR, + "Weights and Activations need to be paired in a packet.", "", "") +DEF_ATTRIB(NOTE_RESTRICT_CVI_NOVP, + "This instruction cannot use the permute/shift resource", "", "") + +/* Performance based preferences */ +DEF_ATTRIB(PREFER_SLOT3, "Complex XU prefering slot3", "", "") + +DEF_ATTRIB(RELAX_COF_1ST, "COF can be fisrt in assembly order", "", "") +DEF_ATTRIB(RELAX_COF_2ND, "COF can be second in assembly order", "", "") DEF_ATTRIB(ICOP, "Instruction cache op", "", "") +DEF_ATTRIB(INTRINSIC_RETURNS_UNSIGNED, "Intrinsic returns an unsigned", "", "") + +DEF_ATTRIB(PRED_BIT_1, "The branch uses bit 1 as the prediction bit", "", "") +DEF_ATTRIB(PRED_BIT_4, "The branch uses bit 4 as the prediction bit", "", "") +DEF_ATTRIB(PRED_BIT_8, "The branch uses bit 8 as the prediction bit", "", "") +DEF_ATTRIB(PRED_BIT_12, "The branch uses bit 12 as the prediction bit", "", "") +DEF_ATTRIB(PRED_BIT_13, "The branch uses bit 13 as the prediction bit", "", "") +DEF_ATTRIB(PRED_BIT_7, "The branch uses bit 7 as the prediction bit", "", "") +DEF_ATTRIB(HWLOOP0_SETUP, "Sets up HW loop0", "", "") +DEF_ATTRIB(HWLOOP1_SETUP, "Sets up HW loop1", "", "") DEF_ATTRIB(HWLOOP0_END, "Ends HW loop0", "", "") DEF_ATTRIB(HWLOOP1_END, "Ends HW loop1", "", "") DEF_ATTRIB(RET_TYPE, "return type", "", "") +DEF_ATTRIB(HINTJR, "hintjr type", "", "") DEF_ATTRIB(DCZEROA, "dczeroa type", "", "") +DEF_ATTRIB(ICTAGOP, "ictag op type", "", "") DEF_ATTRIB(ICFLUSHOP, "icflush op type", "", "") DEF_ATTRIB(DCFLUSHOP, "dcflush op type", "", "") +DEF_ATTRIB(DCTAGOP, "dctag op type", "", "") DEF_ATTRIB(L2FLUSHOP, "l2flush op type", "", "") +DEF_ATTRIB(L2TAGOP, "l2tag op type", "", "") DEF_ATTRIB(DCFETCH, "dcfetch type", "", "") +DEF_ATTRIB(BIMODAL_BRANCH, "Updates the bimodal branch predictor", "", "") +DEF_ATTRIB(VECINSN, "Long Vector Instruction", "", "") +DEF_ATTRIB(MEMSIZE_32B, "Memory width is 32 bytes", "", "") +DEF_ATTRIB(FOUR_PHASE, "Four Phase Instruction", "", "") DEF_ATTRIB(L2FETCH, "Instruction is l2fetch type", "", "") +DEF_ATTRIB(PREDUSE_BSB, "Instructions need back-skip-back scheduling", "", "") DEF_ATTRIB(ICINVA, "icinva", "", "") DEF_ATTRIB(DCCLEANINVA, "dccleaninva", "", "") +DEF_ATTRIB(EXTENSION_AUDIO, "audio extension", "", "") + +DEF_ATTRIB(MEMCPY, "memcpy or dma-type instruction", "", "") DEF_ATTRIB(NO_INTRINSIC, "Don't generate an intrisic", "", "") -/* Documentation Notes */ -DEF_ATTRIB(NOTE_CONDITIONAL, "can be conditionally executed", "", "") -DEF_ATTRIB(NOTE_NEWVAL_SLOT0, "New-value oprnd must execute on slot 0", "", "") -DEF_ATTRIB(NOTE_PRIV, "Monitor-level feature", "", "") -DEF_ATTRIB(NOTE_NOPACKET, "solo instruction", "", "") -DEF_ATTRIB(NOTE_AXOK, "May only be grouped with ALU32 or non-FP XTYPE.", "", "") -DEF_ATTRIB(NOTE_LATEPRED, "The predicate can not be used as a .new", "", "") -DEF_ATTRIB(NOTE_NVSLOT0, "Can execute only in slot 0 (ST)", "", "") -DEF_ATTRIB(NOTE_NOVP, "Cannot be paired with a HVX permute instruction", "", "") -DEF_ATTRIB(NOTE_VA_UNARY, "Combined with HVX ALU op (must be unary)", "", "") +DEF_ATTRIB(NO_XML, "Don't generate a XML docs for this instruction", "", "") -/* V6 MMVector Notes for Documentation */ -DEF_ATTRIB(NOTE_SHIFT_RESOURCE, "Uses the HVX shift resource.", "", "") -/* Restrictions to make note of */ -DEF_ATTRIB(RESTRICT_NOSLOT1_STORE, "Packet must not have slot 1 store", "", "") -DEF_ATTRIB(RESTRICT_LATEPRED, "Predicate can not be used as a .new.", "", "") +DEF_ATTRIB(DMA, "User-DMA instruction", "", "") +DEF_ATTRIB(VERIF_DMASTEP, + "Hiphop needs to step dma prior to executing this packet", "", "") +DEF_ATTRIB(VERIF_DMATICK, + "DMA gets a tick in verif mode for this instruction after a commit", + "", "") + +DEF_ATTRIB(HVX_IEEE_FP, "HVX IEEE FP extension instruction", "", "") +DEF_ATTRIB(NOTE_HVX_IEEE_FP, + "Only supported on the HVX cores with the IEEE FP extension", "", "") + +DEF_ATTRIB(HVX_IEEE_FP_DV_ONE, + "HVX IEEE FP extension instruction - dual pipes: P2 and P3 - output " + "only on P2", + "", "") +DEF_ATTRIB(HVX_IEEE_FP_ACC, "HVX IEEE FP accumulate instruction", "", "") +DEF_ATTRIB(HVX_IEEE_BF, + "HVX IEEE BF extension instruction: 16-bit bfloat input", "", "") +DEF_ATTRIB(HVX_IEEE_FP_OUT_BF, + "HVX IEEE FP extension instruction: 16-bit bfloat output", "", "") +DEF_ATTRIB(HVX_IEEE_FP_OUT_16, + "HVX IEEE FP extension instruction: 16-bit output", "", "") +DEF_ATTRIB(HVX_IEEE_FP_OUT_32, + "HVX IEEE FP extension instruction: 32-bit output", "", "") +DEF_ATTRIB(HVX_IEEE_FP_BINARY_LATE, + "HVX IEEE FP extension instruction: Both inputs can arrive late", "", + "") /* Keep this as the last attribute: */ DEF_ATTRIB(ZZ_LASTATTRIB, "Last attribute in the file", "", "") diff --git a/target/hexagon/imported/macros.def b/target/hexagon/imported/macros.def old mode 100755 new mode 100644 index 4bbcfdd5e194..f24f89f36126 --- a/target/hexagon/imported/macros.def +++ b/target/hexagon/imported/macros.def @@ -353,6 +353,12 @@ DEF_MACRO( () ) +DEF_MACRO( + fREAD_SSR, /* read SSR register */ + (READ_RREG(REG_SSR)), /* behavior */ + () +) + DEF_MACRO( fWRITE_LR, /* write lr */ WRITE_RREG(REG_LR,A), /* behavior */ @@ -371,12 +377,36 @@ DEF_MACRO( (A_IMPLICIT_WRITES_SP) ) +DEF_MACRO( + fWRITE_GOSP, /* write gosp */ + WRITE_RREG(REG_GOSP,A), /* behavior */ + (A_IMPLICIT_WRITES_GOSP) +) + DEF_MACRO( fREAD_SP, /* read stack pointer */ (READ_RREG(REG_SP)), /* behavior */ () ) +DEF_MACRO( + fREAD_GOSP, /* read guest other stack pointer */ + (READ_RREG(REG_GOSP)), /* behavior */ + () +) + +DEF_MACRO( + fREAD_GELR, /* read guest other stack pointer */ + (READ_RREG(REG_GELR)), /* behavior */ + () +) + +DEF_MACRO( + fREAD_GEVB, /* read guest other stack pointer */ + (READ_RREG(REG_GEVB)), /* behavior */ + () +) + DEF_MACRO( fREAD_CSREG, /* read CS register */ (READ_RREG(REG_CSA+N)), /* behavior */ @@ -570,6 +600,11 @@ DEF_MACRO( WRITE_PREG(3,VAL), /* behavior */ (A_IMPLICIT_WRITES_P3) ) +DEF_MACRO( + fWRITE_P3_LATE, /* write Predicate 0 */ + {WRITE_PREG(3,VAL); fHIDE(MARK_LATE_PRED_WRITE(3))} , /* behavior */ + (A_IMPLICIT_WRITES_P3,A_RESTRICT_LATEPRED) +) DEF_MACRO( fPART1, /* write Predicate 0 */ @@ -660,6 +695,7 @@ DEF_MACRO( ((size8s_t)((size2s_t)(A))), /* optional attributes */ ) + DEF_MACRO( fCAST2_8u, /* macro name */ ((size8u_t)((size2u_t)(A))), @@ -1532,18 +1568,209 @@ DEF_MACRO(fECHO, /* OS interface and stop/wait */ /********************************************/ +DEF_MACRO(RUNNABLE_THREADS_MAX, + (thread->processor_ptr->runnable_threads_max), + () +) + +DEF_MACRO(THREAD_IS_ON, + ((PROC->arch_proc_options->thread_enable_mask>>TNUM) & 0x1), + () +) + +DEF_MACRO(THREAD_EN_MASK, + ((PROC->arch_proc_options->thread_enable_mask)), + () +) + + + +DEF_MACRO(READ_IMASK, + (((TH) >= (thread->processor_ptr->runnable_threads_max)) ? 0 : (thread->processor_ptr->thread[TH]->Regs[REG_IMASK])), + () +) +DEF_MACRO(WRITE_IMASK, + if ((TH) < (thread->processor_ptr->runnable_threads_max)) { thread->processor_ptr->thread[TH]->Regs[REG_IMASK]=(VAL & reg_mutability[REG_IMASK] ); }, + (A_IMPLICIT_WRITES_IMASK_ANYTHREAD) +) + + +DEF_MACRO(WRITE_PRIO, + { + if ((TH) < (thread->processor_ptr->runnable_threads_max)) { + size4u_t tid_reg = thread->processor_ptr->thread[TH]->Regs[REG_TID]; + fINSERT_BITS(tid_reg, reg_field_info[STID_PRIO].width, reg_field_info[STID_PRIO].offset, VAL); + LOG_OTHER_THREAD_REG_WRITE(thread,REG_TID,tid_reg,TH); + } + }, + (A_IMPLICIT_WRITES_STID_PRIO_ANYTHREAD) +) + + +DEF_MACRO(DO_IASSIGNW, + { + int i; + int intbitpos = ((REG>>16)&0xF); + for (i=0;iprocessor_ptr->arch_proc_options->thread_enable_mask>>i) & 0x1)) { + fINSERT_BITS(thread->processor_ptr->thread[i]->Regs[REG_IMASK],1, intbitpos, (REG>>i) & 1); + } + } + }, + (A_IMPLICIT_WRITES_IMASK_ANYTHREAD) +) + + + + +DEF_MACRO(fDO_NMI, + { + int i; + for (i=0;iprocessor_ptr->arch_proc_options->thread_enable_mask>>i) & 0x1) ) { + if (SREG & (1<processor_ptr->thread[i]); + } + } + } + }, +) + +DEF_MACRO(fDO_TRACE, + { + fHIDE(HEX_CALLBACK(thread->processor_ptr->options->trace_callback, + thread->system_ptr,thread->processor_ptr, + thread->threadId,SREG);) + }, +) + +DEF_MACRO(DO_IASSIGNR, + { + int i; + int result=0; + int intbitpos = ((SREG>>16)&0xF); + for (i=0;iprocessor_ptr->arch_proc_options->thread_enable_mask>>i) & 0x1)) { + result |= (((thread->processor_ptr->thread[i]->Regs[REG_IMASK]>>intbitpos)&1)<processor_ptr->options->swi_callback, + thread->system_ptr,thread->processor_ptr, + thread->threadId,REG)); + LOG_GLOBAL_REG_WRITE(REG_IPEND,(GLOBAL_REG_READ(REG_IPEND) | (REG & GLOBAL_REG_READ(REG_IEL)))); + }, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(DO_CSWI, + LOG_GLOBAL_REG_WRITE(REG_IPEND,GLOBAL_REG_READ(REG_IPEND) & ~((REG) & GLOBAL_REG_READ(REG_IEL)));, + () +) + +DEF_MACRO(DO_CIAD, + sys_ciad(thread,VAL); LOG_GLOBAL_REG_WRITE(REG_IAD,GLOBAL_REG_READ(REG_IAD) & ~(VAL));, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(DO_SIAD, + sys_siad(thread,VAL); LOG_GLOBAL_REG_WRITE(REG_IAD,GLOBAL_REG_READ(REG_IAD) | (VAL));, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(fBREAK, + {isdb_brkpt_insn(thread->processor_ptr,thread->threadId);}, + () +) + DEF_MACRO(fPAUSE, {sys_pause(thread, insn->slot, IMM);}, () ) + DEF_MACRO(fTRAP, warn("Trap NPC=%x ",fREAD_NPC()); warn("Trap exception, PCYCLE=%lld TYPE=%d NPC=%x IMM=0x%x",thread->processor_ptr->pstats[pcycles],TRAPTYPE,fREAD_NPC(),IMM); register_trap_exception(thread,fREAD_NPC(),TRAPTYPE,IMM);, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(fINTERNAL_CLEAR_SAMEPAGE, + /* force re-xlate at next fetch, refresh of in_user_mode, etc */ + /* Permissions change too... */ + sys_utlb_invalidate(thread->processor_ptr,thread), + /* NOTHING */ +) + +DEF_MACRO(fCLEAR_RTE_EX, + { + fLOG_REG_FIELD(SSR,SSR_EX,0); + fINTERNAL_CLEAR_SAMEPAGE(); + }, + () +) + +DEF_MACRO(fTLB_LOCK_AVAILABLE, + (fREAD_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_TLBLOCK) == 0), () ) +DEF_MACRO(fK0_LOCK_AVAILABLE, + (fREAD_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_K0LOCK) == 0), + () +) + +DEF_MACRO(fSET_TLB_LOCK, + { + if (fTLB_LOCK_AVAILABLE()) { + fLOG_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_TLBLOCK,1); + } else { + sys_waiting_for_tlb_lock(thread); + } + }, + () +) + +DEF_MACRO(fSET_K0_LOCK, + { + if (fK0_LOCK_AVAILABLE() && sys_k0lock_queue_ready(thread)) { + warn("k0lock: T%d: PC=0x%x: PCycle=%lld",thread->threadId,thread->Regs[REG_PC],thread->processor_ptr->pstats[pcycles]); + fLOG_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_K0LOCK,1); + } else { + warn("k0lock_waiting: T%d: PC=0x%x: PCycle=%lld",thread->threadId,thread->Regs[REG_PC],thread->processor_ptr->pstats[pcycles]); + sys_waiting_for_k0_lock(thread); + } + }, + () +) + +DEF_MACRO(fCLEAR_TLB_LOCK, + { + int i; + fLOG_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_TLBLOCK,0); + for (i = 0; i < RUNNABLE_THREADS_MAX; i++) { + if(( (thread->processor_ptr->arch_proc_options->thread_enable_mask>>i) & 0x1)) { + thread->processor_ptr->thread[i]->cu_tlb_lock_waiting = 0; + } + } + }, + () +) + +DEF_MACRO(fCLEAR_K0_LOCK, + do { + warn("k0unlock: T%d: PC=0x%x: Pcycle=%lld",thread->threadId,thread->Regs[REG_PC], thread->processor_ptr->pstats[pcycles]); + sys_initiate_clear_k0_lock(thread); + } while (0), + () +) + DEF_MACRO(fALIGN_REG_FIELD_VALUE, ((VAL)<processor_ptr->global_regs[REG_##REG], + reg_field_info[FIELD].width, + reg_field_info[FIELD].offset,VAL), +) + +DEF_MACRO(fLOG_GLOBAL_REG_FIELD, + LOG_MASKED_GLOBAL_REG_WRITE(REG_##REG, + fALIGN_REG_FIELD_VALUE(FIELD,VAL), + fGET_REG_FIELD_MASK(FIELD)), + () +) + DEF_MACRO(fREAD_REG_FIELD, fEXTRACTU_BITS(thread->Regs[REG_##REG], reg_field_info[FIELD].width, @@ -1561,6 +1808,13 @@ DEF_MACRO(fREAD_REG_FIELD, /* ATTRIBS */ ) +DEF_MACRO(fREAD_GLOBAL_REG_FIELD, + fEXTRACTU_BITS(thread->processor_ptr->global_regs[REG_##REG], + reg_field_info[FIELD].width, + reg_field_info[FIELD].offset), + /* ATTRIBS */ +) + DEF_MACRO(fGET_FIELD, fEXTRACTU_BITS(VAL, reg_field_info[FIELD].width, @@ -1576,6 +1830,185 @@ DEF_MACRO(fSET_FIELD, /* ATTRIBS */ ) +DEF_MACRO(fSET_RUN_MODE_NOW, + {thread->processor_ptr->global_regs[REG_MODECTL] |= (1<last_commit_cycle = thread->processor_ptr->pcycle_counter; + sys_recalc_num_running_threads(thread->processor_ptr);}, +) + +DEF_MACRO(fIN_DEBUG_MODE, + (thread->debug_mode || (fREAD_GLOBAL_REG_FIELD(ISDBST,ISDBST_DEBUGMODE) & 1<debug_mode), + () +) + + +DEF_MACRO(fIN_DEBUG_MODE_WARN, + { + if (fREAD_GLOBAL_REG_FIELD(ISDBST,ISDBST_DEBUGMODE) & 1<processor_ptr); + } while (0), + /* NOTHING */ +) + +DEF_MACRO(fGET_RUN_MODE, + ((thread->processor_ptr->global_regs[REG_MODECTL]>>TNUM)&0x1), +) + +DEF_MACRO(fSET_WAIT_MODE, + {fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_W, + fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_W) | 1<<(TNUM))}, + /* NOTHING */ +) + +DEF_MACRO(fCLEAR_WAIT_MODE, + {thread->processor_ptr->global_regs[REG_MODECTL] &= ~(1<<(TNUM+16)); + thread->last_commit_cycle = thread->processor_ptr->pcycle_counter; + sys_recalc_num_running_threads(thread->processor_ptr);}, +) + +DEF_MACRO(fGET_WAIT_MODE, + ((thread->processor_ptr->global_regs[REG_MODECTL]>>(TNUM+16))&0x1), +) + + +DEF_MACRO(fRESET_THREAD, + register_reset_interrupt(T,NUM), +) + +DEF_MACRO(fREAD_CURRENT_EVB, + (GLOBAL_REG_READ(REG_EVB)), + /* nothing */ +) + +DEF_MACRO(fREAD_ELR, + READ_RREG(REG_ELR), + () +) + +DEF_MACRO(fPOW2_HELP_ROUNDUP, + ((VAL) | ((VAL) >> 1) | ((VAL) >> 2) | ((VAL) >> 4) | ((VAL) >> 8) | ((VAL) >> 16)), + () +) + +DEF_MACRO(fPOW2_ROUNDUP, + fPOW2_HELP_ROUNDUP((VAL)-1)+1, + () +) + +DEF_MACRO(fTLB_IDXMASK, + ((INDEX) & (fPOW2_ROUNDUP(fCAST4u(thread->processor_ptr->arch_proc_options->jtlb_size)) - 1)), + () +) + +DEF_MACRO(fTLB_NONPOW2WRAP, + (((INDEX) >= thread->processor_ptr->arch_proc_options->jtlb_size) ? ((INDEX) - thread->processor_ptr->arch_proc_options->jtlb_size) : (INDEX)), + /* ATTRIBS */ +) + +DEF_MACRO(fTLBW, + do {size4u_t __myidx = fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX)); + TLB_REG_WRITE(__myidx,VALUE); + fHIDE(HEX_CALLBACK(thread->processor_ptr->options->tlbw_callback,thread->system_ptr,thread->processor_ptr,thread->threadId,__myidx);) + fHIDE(sys_tlb_write(thread,__myidx,VALUE);)} while (0), + /* ATTRIBS */ +) + +DEF_MACRO(fTLB_ENTRY_OVERLAP, + fHIDE( (sys_check_overlap(thread,VALUE)!=-2) ), + /* ATTRIBS */ +) + +DEF_MACRO(fTLB_ENTRY_OVERLAP_IDX, + fHIDE(sys_check_overlap(thread,VALUE)), + /* ATTRIBS */ +) + + +DEF_MACRO(fTLBR, + TLB_REG_READ(fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX))), + /* ATTRIBS */ +) + +DEF_MACRO(fTLBP, + tlb_lookup(thread,((TLBHI)>>12),((TLBHI)<<12),1), + /* attribs */ +) + + + +DEF_MACRO(READ_SGP0, + READ_RREG(REG_SGP), + () +) + +DEF_MACRO(READ_SGP1, + READ_RREG(REG_SGP+1), + () +) + +DEF_MACRO(READ_SGP10, + READ_RREG_PAIR(REG_SGP), + () +) + +DEF_MACRO(READ_UGP, + READ_RREG(REG_UGP), +) + +DEF_MACRO(WRITE_SGP0, + WRITE_RREG(REG_SGP,VAL), + (A_IMPLICIT_WRITES_SGP0) +) + +DEF_MACRO(WRITE_SGP1, + WRITE_RREG(REG_SGP+1,VAL), + (A_IMPLICIT_WRITES_SGP1) +) + +DEF_MACRO(WRITE_SGP10, + WRITE_RREG_PAIR(REG_SGP,VAL), + (A_IMPLICIT_WRITES_SGP0,A_IMPLICIT_WRITES_SGP1) +) + +DEF_MACRO(WRITE_UGP, + WRITE_RREG(REG_UGP,VAL), +) + +DEF_MACRO(fSTART, + fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_E, fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_E) | (((REG & ((1<processor_ptr))), + () +) + +DEF_MACRO(fRESUME, + fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_W, + fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_W) & (~(REG))), + () +) + +DEF_MACRO(fGET_TNUM, + thread->threadId, + () +) + /********************************************/ /* Cache Management */ /********************************************/ @@ -1602,6 +2035,11 @@ DEF_MACRO(fISYNC, ) +DEF_MACRO(fICFETCH, + , + () +) + DEF_MACRO(fDCFETCH, sys_dcfetch(thread, (REG), insn->slot), (A_MEMLIKE) @@ -1615,6 +2053,34 @@ DEF_MACRO(fICINVA, (A_ICINVA) ) +DEF_MACRO(fDCTAGR, + ({DST=sys_dctagr(thread, INDEX, insn->slot,DSTREGNO);})/* FIXME */, + () +) + +DEF_MACRO(fDCTAGW, + (sys_dctagw(thread, INDEX, PART2, insn->slot)), + () +) +DEF_MACRO(fICTAGR, + ({DST=sys_ictagr(thread, INDEX, insn->slot,REGNO);}), + () +) + +DEF_MACRO(fICDATAR, + ({DST=sys_icdatar(thread, INDEX, insn->slot);}), + () +) + +DEF_MACRO(fICTAGW, + (sys_ictagw(thread, INDEX, PART2, insn->slot)), + () +) +DEF_MACRO(fICDATAW, + ({ fHIDE(); }), + () +) + DEF_MACRO(fL2FETCH, sys_l2fetch(thread, ADDR,HEIGHT,WIDTH,STRIDE,FLAGS, insn->slot), (A_MEMLIKE,A_L2FETCH) @@ -1635,6 +2101,12 @@ DEF_MACRO(fDCZEROA, (A_MEMLIKE) ) +DEF_MACRO(fDCINVA, + sys_dcinva(thread, (REG)), + (A_MEMLIKE) +) + + DEF_MACRO(fCHECKFORPRIV, {sys_check_privs(thread); if (EXCEPTION_DETECTED) return; }, () @@ -1645,6 +2117,16 @@ DEF_MACRO(fCHECKFORGUEST, () ) +DEF_MACRO(fTAKEN_INTERRUPT_EDGECLEAR, + { proc->global_regs[REG_IPEND] &= ~(INT_NUMTOMASK(intnum) & proc->global_regs[REG_IEL]); }, + () +) + +DEF_MACRO(fSET_IAD, + { sys_siad(thread,INT_NUMTOMASK(intnum)); thread->processor_ptr->global_regs[REG_IAD] |= INT_NUMTOMASK(intnum); }, + () +) + DEF_MACRO(fBRANCH_SPECULATE_STALL, { sys_speculate_branch_stall(thread, insn->slot, JUMP_COND(JUMP_PRED_SET), @@ -1664,3 +2146,79 @@ DEF_MACRO(IV1DEAD, , () ) + +DEF_MACRO(fIN_MONITOR_MODE, + sys_in_monitor_mode(thread), + () +) + +DEF_MACRO(fIN_USER_MODE, + sys_in_user_mode(thread), + () +) + +DEF_MACRO(fIN_GUEST_MODE, + sys_in_guest_mode(thread), + () +) + +DEF_MACRO(fGRE_ENABLED, + fREAD_REG_FIELD(CCR,CCR_GRE), + () +) + +DEF_MACRO(fGTE_ENABLED, + fREAD_REG_FIELD(CCR,CCR_GRE), + () +) + +DEF_MACRO(fTRAP1_VIRTINSN, + ((fIN_GUEST_MODE()) + && (fGRE_ENABLED()) + && ( ((IMM) == 1) + || ((IMM) == 3) + || ((IMM) == 4) + || ((IMM) == 6))), + () +) + +DEF_MACRO(fVIRTINSN_RTE, + do { + thread->trap1_info = TRAP1_VIRTINSN_RTE; + fLOG_REG_FIELD(SSR,SSR_SS,fREAD_REG_FIELD(GSR,GSR_SS)); + fLOG_REG_FIELD(CCR,CCR_GIE,fREAD_REG_FIELD(GSR,GSR_IE)); + fLOG_REG_FIELD(SSR,SSR_GM,!fREAD_REG_FIELD(GSR,GSR_UM)); + fBRANCH((fREAD_GELR() & -4),COF_TYPE_RTE); + fINTERNAL_CLEAR_SAMEPAGE(); + } while (0), + (A_IMPLICIT_WRITES_CCR,A_IMPLICIT_WRITES_SSR) +) + +DEF_MACRO(fVIRTINSN_SETIE, + do { + fLOG_REG_FIELD(CCR,CCR_GIE,(REG) & 1); + REG = fREAD_REG_FIELD(CCR,CCR_GIE); + thread->trap1_info = TRAP1_VIRTINSN_SETIE; + } while (0), + (A_IMPLICIT_WRITES_CCR) +) + +DEF_MACRO(fVIRTINSN_GETIE, + { + thread->trap1_info = TRAP1_VIRTINSN_GETIE; + REG = fREAD_REG_FIELD(CCR,CCR_GIE); + }, + () +) + +DEF_MACRO(fVIRTINSN_SPSWAP, + do { + if (fREAD_REG_FIELD(GSR,GSR_UM)) { + size4u_t TEMP = REG; + REG = fREAD_GOSP(); + fWRITE_GOSP(TEMP); + thread->trap1_info = TRAP1_VIRTINSN_SPSWAP; + } + } while (0), + (A_IMPLICIT_WRITES_GOSP) +) From 9e9dc6faf6e845e835c6645b2d670b893d362522 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sun, 8 Sep 2024 18:52:49 -0700 Subject: [PATCH 1070/1179] target/hexagon: Define DCache states Signed-off-by: Brian Cain --- target/hexagon/cpu_bits.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h index 6582bb4f16fc..5d26815eb9bc 100644 --- a/target/hexagon/cpu_bits.h +++ b/target/hexagon/cpu_bits.h @@ -41,6 +41,13 @@ enum hex_cause { HEX_CAUSE_PRIV_USER_NO_SINSN = 0x01b, }; +enum data_cache_state { + HEX_DC_STATE_INVALID = 0x0, + HEX_DC_STATE_VALID = 0x1, + HEX_DC_STATE_RESERVED = 0x2, + HEX_DC_STATE_UNUSED_WT = 0x3, +}; + #define PACKET_WORDS_MAX 4 static inline uint32_t parse_bits(uint32_t encoding) From d4fc5ac615f7ac1b2bb8a97b950b7756cec98012 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 17:04:51 -0500 Subject: [PATCH 1071/1179] target/hexagon: Add new macro definitions for sysemu Also: add nop TCG overrides for break,unpause,fetchbo,dczeroa break: this hardware breakpoint instruction is used with the in-silicon debugger feature, this is not modeled. unpause: this instruction is used to resume hardware threads that are stalled by pause instructions. pause is modeled as a nop, or in RR mode as an EXCP_YIELD. This instruction is safe to ignore. Since cache/prefetch functions are not modeled, dczero and fetchbo are safe to ignore. Signed-off-by: Brian Cain --- target/hexagon/gen_tcg.h | 9 ++ target/hexagon/macros.h | 28 ++++- target/hexagon/op_helper.c | 1 + target/hexagon/sys_macros.h | 238 ++++++++++++++++++++++++++++++++++++ 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 target/hexagon/sys_macros.h diff --git a/target/hexagon/gen_tcg.h b/target/hexagon/gen_tcg.h index 8a3b801287c7..71f8a0e2d084 100644 --- a/target/hexagon/gen_tcg.h +++ b/target/hexagon/gen_tcg.h @@ -488,6 +488,7 @@ /* dczeroa clears the 32 byte cache line at the address given */ #define fGEN_TCG_Y2_dczeroa(SHORTCODE) SHORTCODE +#define fGEN_TCG_Y2_dczeroa_nt(SHORTCODE) SHORTCODE /* In linux-user mode, these are not modelled, suppress compiler warning */ #define fGEN_TCG_Y2_dcinva(SHORTCODE) \ @@ -1133,6 +1134,9 @@ RdV, tcg_constant_tl(0)); \ } while (0) +#define fGEN_TCG_Y2_break(SHORTCODE) +#define fGEN_TCG_J2_unpause(SHORTCODE) + #define fGEN_TCG_J2_pause(SHORTCODE) \ do { \ uiV = uiV; \ @@ -1342,6 +1346,11 @@ RsV = RsV; \ uiV = uiV; \ } while (0) +#define fGEN_TCG_Y2_dcfetchbo_nt(SHORTCODE) \ + do { \ + RsV = RsV; \ + uiV = uiV; \ + } while (0) #define fGEN_TCG_L2_loadw_aq(SHORTCODE) SHORTCODE #define fGEN_TCG_L4_loadd_aq(SHORTCODE) SHORTCODE diff --git a/target/hexagon/macros.h b/target/hexagon/macros.h index ee3d4c88e7bd..6e4a3a16970c 100644 --- a/target/hexagon/macros.h +++ b/target/hexagon/macros.h @@ -537,9 +537,6 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #ifdef CONFIG_USER_ONLY #define fFRAMECHECK(ADDR, EA) do { } while (0) /* Not modelled in linux-user */ -#else -/* System mode not implemented yet */ -#define fFRAMECHECK(ADDR, EA) g_assert_not_reached(); #endif #ifdef QEMU_GENERATE @@ -630,8 +627,18 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #define fCONSTLL(A) A##LL #define fECHO(A) (A) -#define fTRAP(TRAPTYPE, IMM) helper_raise_exception(env, HEX_EXCP_TRAP0) +#ifdef CONFIG_USER_ONLY +#define fTRAP(TRAPTYPE, IMM) \ + do { \ + hexagon_raise_exception_err(env, HEX_EVENT_TRAP0, PC); \ + } while (0) +#endif + +#define fDO_TRACE(SREG) +#define fBREAK() +#define fUNPAUSE() #define fPAUSE(IMM) +#define fDCFETCH(REG) #define fALIGN_REG_FIELD_VALUE(FIELD, VAL) \ ((VAL) << reg_field_info[FIELD].offset) @@ -648,10 +655,23 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) ctx->dczero_addr = tcg_temp_new(); \ tcg_gen_mov_tl(ctx->dczero_addr, (REG)); \ } while (0) +#else +#define fDCZEROA(REG) ((void) REG) #endif #define fBRANCH_SPECULATE_STALL(DOTNEWVAL, JUMP_COND, SPEC_DIR, HINTBITNUM, \ STRBITNUM) /* Nothing */ +#ifdef CONFIG_USER_ONLY +/* + * This macro can only be true in guest mode. + * In user mode, the 4 VIRTINSN's can't be reached + */ +#define fTRAP1_VIRTINSN(IMM) (false) +#define fVIRTINSN_SPSWAP(IMM, REG) g_assert_not_reached() +#define fVIRTINSN_GETIE(IMM, REG) g_assert_not_reached() +#define fVIRTINSN_SETIE(IMM, REG) g_assert_not_reached() +#define fVIRTINSN_RTE(IMM, REG) g_assert_not_reached() +#endif #endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 6da8db8ea5c5..4feec232983a 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -24,6 +24,7 @@ #include "cpu.h" #include "internal.h" #include "macros.h" +#include "sys_macros.h" #include "arch.h" #include "hex_arch_types.h" #include "fma_emu.h" diff --git a/target/hexagon/sys_macros.h b/target/hexagon/sys_macros.h new file mode 100644 index 000000000000..3c4c3c7aa5ec --- /dev/null +++ b/target/hexagon/sys_macros.h @@ -0,0 +1,238 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_SYS_MACROS_H +#define HEXAGON_SYS_MACROS_H + +/* + * Macro definitions for Hexagon system mode + */ + +#ifndef CONFIG_USER_ONLY + +#define READ_SREG(NUM) arch_get_system_reg(env, NUM) +#define READ_SGP0() arch_get_system_reg(env, HEX_SREG_SGP0) +#define READ_SGP1() arch_get_system_reg(env, HEX_SREG_SGP1) +#define READ_SGP10() ((uint64_t)arch_get_system_reg(env, HEX_SREG_SGP0) | \ + ((uint64_t)arch_get_system_reg(env, HEX_SREG_SGP1) << 32)) + +#define WRITE_SREG(NUM, VAL) log_sreg_write(env, NUM, VAL, slot) +#define WRITE_SGP0(VAL) log_sreg_write(env, HEX_SREG_SGP0, VAL, slot) +#define WRITE_SGP1(VAL) log_sreg_write(env, HEX_SREG_SGP1, VAL, slot) +#define WRITE_SGP10(VAL) \ + do { \ + log_sreg_write(env, HEX_SREG_SGP0, (VAL) & 0xFFFFFFFF, slot); \ + log_sreg_write(env, HEX_SREG_SGP1, (VAL) >> 32, slot); \ + } while (0) + +#ifdef QEMU_GENERATE +#define GET_SSR_FIELD(RES, FIELD) \ + GET_FIELD(RES, FIELD, hex_t_sreg[HEX_SREG_SSR]) +#else + +#define GET_SSR_FIELD(FIELD, REGIN) \ + (uint32_t)GET_FIELD(FIELD, REGIN) +#define GET_SYSCFG_FIELD(FIELD, REGIN) \ + (uint32_t)GET_FIELD(FIELD, REGIN) +#define SET_SYSTEM_FIELD(ENV, REG, FIELD, VAL) \ + do { \ + uint32_t regval = arch_get_system_reg(ENV, REG); \ + fINSERT_BITS(regval, reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset, (VAL)); \ + arch_set_system_reg(ENV, REG, regval); \ + } while (0) +#define SET_SSR_FIELD(ENV, FIELD, VAL) \ + SET_SYSTEM_FIELD(ENV, HEX_SREG_SSR, FIELD, VAL) +#define SET_SYSCFG_FIELD(ENV, FIELD, VAL) \ + SET_SYSTEM_FIELD(ENV, HEX_SREG_SYSCFG, FIELD, VAL) + +#define CCR_FIELD_SET(ENV, FIELD) \ + (!!GET_FIELD(FIELD, arch_get_system_reg(ENV, HEX_SREG_CCR))) + +/* + * Direct-to-guest is not implemented yet, continuing would cause unexpected + * behavior, so we abort. + */ +#define ASSERT_DIRECT_TO_GUEST_UNSET(ENV, EXCP) \ + do { \ + switch (EXCP) { \ + case HEX_EVENT_TRAP0: \ + g_assert(!CCR_FIELD_SET(ENV, CCR_GTE)); \ + break; \ + case HEX_EVENT_IMPRECISE: \ + case HEX_EVENT_PRECISE: \ + case HEX_EVENT_FPTRAP: \ + g_assert(!CCR_FIELD_SET(ENV, CCR_GEE)); \ + break; \ + default: \ + if ((EXCP) >= HEX_EVENT_INT0) { \ + g_assert(!CCR_FIELD_SET(ENV, CCR_GIE)); \ + } \ + break; \ + } \ + } while (0) +#endif + +#define fREAD_ELR() (READ_SREG(HEX_SREG_ELR)) + +#define fLOAD_PHYS(NUM, SIZE, SIGN, SRC1, SRC2, DST) { \ + const uintptr_t rs = ((unsigned long)(unsigned)(SRC1)) & 0x7ff; \ + const uintptr_t rt = ((unsigned long)(unsigned)(SRC2)) << 11; \ + const uintptr_t addr = rs + rt; \ + cpu_physical_memory_read(addr, &DST, sizeof(uint32_t)); \ +} + +#define fPOW2_HELP_ROUNDUP(VAL) \ + ((VAL) | \ + ((VAL) >> 1) | \ + ((VAL) >> 2) | \ + ((VAL) >> 4) | \ + ((VAL) >> 8) | \ + ((VAL) >> 16)) +#define fPOW2_ROUNDUP(VAL) (fPOW2_HELP_ROUNDUP((VAL) - 1) + 1) + +#define fFRAMECHECK(ADDR, EA) g_assert_not_reached(); + +#define fTRAP(TRAPTYPE, IMM) \ + register_trap_exception(env, TRAPTYPE, IMM, PC) + +#define fVIRTINSN_SPSWAP(IMM, REG) +#define fVIRTINSN_GETIE(IMM, REG) { REG = 0xdeafbeef; } +#define fVIRTINSN_SETIE(IMM, REG) +#define fVIRTINSN_RTE(IMM, REG) +#define fGRE_ENABLED() GET_FIELD(CCR_GRE, READ_SREG(HEX_SREG_CCR)) +#define fTRAP1_VIRTINSN(IMM) \ + (fGRE_ENABLED() && \ + (((IMM) == 1) || ((IMM) == 3) || ((IMM) == 4) || ((IMM) == 6))) + +/* Not modeled in qemu */ + +#define MARK_LATE_PRED_WRITE(RNUM) +#define fICINVIDX(REG) +#define fICKILL() +#define fDCKILL() +#define fL2KILL() +#define fL2UNLOCK() +#define fL2CLEAN() +#define fL2CLEANINV() +#define fL2CLEANPA(REG) +#define fL2CLEANINVPA(REG) +#define fL2CLEANINVIDX(REG) +#define fL2CLEANIDX(REG) +#define fL2INVIDX(REG) +#define fL2TAGR(INDEX, DST, DSTREG) +#define fL2UNLOCKA(VA) ((void) VA) +#define fL2TAGW(INDEX, PART2) +#define fDCCLEANIDX(REG) +#define fDCCLEANINVIDX(REG) + +/* Always succeed: */ +#define fL2LOCKA(EA, PDV, PDN) ((void) EA, PDV = 0xFF) +#define fCLEAR_RTE_EX() \ + do { \ + uint32_t tmp = 0; \ + tmp = arch_get_system_reg(env, HEX_SREG_SSR); \ + fINSERT_BITS(tmp, reg_field_info[SSR_EX].width, \ + reg_field_info[SSR_EX].offset, 0); \ + log_sreg_write(env, HEX_SREG_SSR, tmp, slot); \ + } while (0) + +#define fDCINVIDX(REG) +#define fDCINVA(REG) do { REG = REG; } while (0) /* Nothing to do in qemu */ + +#define fSET_TLB_LOCK() g_assert_not_reached() +#define fCLEAR_TLB_LOCK() g_assert_not_reached() + +#define fSET_K0_LOCK() g_assert_not_reached() +#define fCLEAR_K0_LOCK() g_assert_not_reached() + +#define fTLB_IDXMASK(INDEX) \ + ((INDEX) & (fPOW2_ROUNDUP(fCAST4u(env_archcpu(env)->num_tlbs)) - 1)) + +#define fTLB_NONPOW2WRAP(INDEX) \ + (((INDEX) >= env_archcpu(env)->num_tlbs) ? \ + ((INDEX) - env_archcpu(env)->num_tlbs) : \ + (INDEX)) + + +#define fTLBW(INDEX, VALUE) \ + hex_tlbw(env, (INDEX), (VALUE)) +#define fTLBW_EXTENDED(INDEX, VALUE) \ + hex_tlbw(env, (INDEX), (VALUE)) +#define fTLB_ENTRY_OVERLAP(VALUE) \ + (hex_tlb_check_overlap(env, VALUE, -1) != -2) +#define fTLB_ENTRY_OVERLAP_IDX(VALUE) \ + hex_tlb_check_overlap(env, VALUE, -1) +#define fTLBR(INDEX) \ + (env->hex_tlb->entries[fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX))]) +#define fTLBR_EXTENDED(INDEX) \ + (env->hex_tlb->entries[fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX))]) +#define fTLBP(TLBHI) \ + hex_tlb_lookup(env, ((TLBHI) >> 12), ((TLBHI) << 12)) +#define iic_flush_cache(p) + +#define fIN_DEBUG_MODE(TNUM) \ + ((GET_FIELD(ISDBST_DEBUGMODE, arch_get_system_reg(env, HEX_SREG_ISDBST)) \ + & (0x1 << (TNUM))) != 0) + +#define fIN_DEBUG_MODE_NO_ISDB(TNUM) false +#define fIN_DEBUG_MODE_WARN(TNUM) false + +#ifdef QEMU_GENERATE + +/* + * Read tags back as zero for now: + * + * tag value in RD[31:10] for 32k, RD[31:9] for 16k + */ +#define fICTAGR(RS, RD, RD2) \ + do { \ + RD = ctx->zero; \ + } while (0) +#define fICTAGW(RS, RD) +#define fICDATAR(RS, RD) \ + do { \ + RD = ctx->zero; \ + } while (0) +#define fICDATAW(RS, RD) + +#define fDCTAGW(RS, RT) +/* tag: RD[23:0], state: RD[30:29] */ +#define fDCTAGR(INDEX, DST, DST_REG_NUM) \ + do { \ + DST = ctx->zero; \ + } while (0) +#else + +/* + * Read tags back as zero for now: + * + * tag value in RD[31:10] for 32k, RD[31:9] for 16k + */ +#define fICTAGR(RS, RD, RD2) \ + do { \ + RD = 0x00; \ + } while (0) +#define fICTAGW(RS, RD) +#define fICDATAR(RS, RD) \ + do { \ + RD = 0x00; \ + } while (0) +#define fICDATAW(RS, RD) + +#define fDCTAGW(RS, RT) +/* tag: RD[23:0], state: RD[30:29] */ +#define fDCTAGR(INDEX, DST, DST_REG_NUM) \ + do { \ + DST = HEX_DC_STATE_INVALID | 0x00; \ + } while (0) +#endif + +#endif + +#define NUM_TLB_REGS(x) (env_archcpu(env)->num_tlbs) + +#endif From cd1e7c9cee93fae22e15a5355395fcfe03efab2f Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 17:05:26 -0500 Subject: [PATCH 1072/1179] target/hexagon: Add handlers for guest/sysreg r/w This commit provides handlers to generate TCG for guest and system register reads and writes. They will be leveraged by a future commit. Signed-off-by: Brian Cain --- target/hexagon/genptr.c | 159 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c index 2c5e15cfcf6f..488d0b4b978b 100644 --- a/target/hexagon/genptr.c +++ b/target/hexagon/genptr.c @@ -23,6 +23,7 @@ #include "exec/helper-gen.h" #include "insn.h" #include "opcodes.h" +#include "sys_macros.h" #include "translate.h" #define QEMU_GENERATE /* Used internally by macros.h */ #include "macros.h" @@ -128,6 +129,164 @@ TCGv get_result_pred(DisasContext *ctx, int pnum) } } +#ifndef CONFIG_USER_ONLY +G_GNUC_UNUSED +static bool greg_writable(int rnum, bool pair) +{ + if (pair) { + if (rnum < HEX_GREG_G3) { + return true; + } + qemu_log_mask(LOG_UNIMP, + "Warning: ignoring write to guest register pair G%d:%d\n", + rnum + 1, rnum); + } else { + if (rnum <= HEX_GREG_G3) { + return true; + } + qemu_log_mask(LOG_UNIMP, + "Warning: ignoring write to guest register G%d\n", rnum); + } + return false; +} + +G_GNUC_UNUSED +static void check_greg_impl(int rnum, bool pair) +{ + if (pair && (!greg_implemented(rnum) || !greg_implemented(rnum + 1))) { + qemu_log_mask(LOG_UNIMP, + "Warning: guest register pair G%d:%d is unimplemented or " + "reserved. Read will yield 0.\n", + rnum + 1, rnum); + } else if (!pair && !greg_implemented(rnum)) { + qemu_log_mask(LOG_UNIMP, + "Warning: guest register G%d is unimplemented or reserved." + " Read will yield 0.\n", rnum); + } +} + +G_GNUC_UNUSED +static inline void gen_log_greg_write(DisasContext *ctx, int rnum, TCGv val) +{ + tcg_gen_mov_tl(ctx->greg_new_value[rnum], val); +} + +G_GNUC_UNUSED +static void gen_log_greg_write_pair(DisasContext *ctx, int rnum, TCGv_i64 val) +{ + TCGv val32 = tcg_temp_new(); + + /* Low word */ + tcg_gen_extrl_i64_i32(val32, val); + gen_log_greg_write(ctx, rnum, val32); + + /* High word */ + tcg_gen_extrh_i64_i32(val32, val); + gen_log_greg_write(ctx, rnum + 1, val32); +} + +static const target_ulong sreg_immut_masks[NUM_SREGS] = { + [HEX_SREG_STID] = 0xff00ff00, + [HEX_SREG_ELR] = 0x00000003, + [HEX_SREG_SSR] = 0x00008000, + [HEX_SREG_CCR] = 0x10e0ff24, + [HEX_SREG_HTID] = IMMUTABLE, + [HEX_SREG_IMASK] = 0xffff0000, + [HEX_SREG_GEVB] = 0x000000ff, + [HEX_SREG_EVB] = 0x000000ff, + [HEX_SREG_MODECTL] = IMMUTABLE, + [HEX_SREG_SYSCFG] = 0x80001c00, + [HEX_SREG_IPENDAD] = IMMUTABLE, + [HEX_SREG_VID] = 0xfc00fc00, + [HEX_SREG_VID1] = 0xfc00fc00, + [HEX_SREG_BESTWAIT] = 0xfffffe00, + [HEX_SREG_SCHEDCFG] = 0xfffffef0, + [HEX_SREG_CFGBASE] = IMMUTABLE, + [HEX_SREG_REV] = IMMUTABLE, + [HEX_SREG_ISDBST] = IMMUTABLE, + [HEX_SREG_ISDBCFG0] = 0xe0000000, + [HEX_SREG_BRKPTPC0] = 0x00000003, + [HEX_SREG_BRKPTCFG0] = 0xfc007000, + [HEX_SREG_BRKPTPC1] = 0x00000003, + [HEX_SREG_BRKPTCFG1] = 0xfc007000, + [HEX_SREG_ISDBMBXIN] = IMMUTABLE, + [HEX_SREG_ISDBEN] = 0xfffffffe, + [HEX_SREG_TIMERLO] = IMMUTABLE, + [HEX_SREG_TIMERHI] = IMMUTABLE, +}; + +G_GNUC_UNUSED +static void gen_log_sreg_write(DisasContext *ctx, int rnum, TCGv val) +{ + const target_ulong reg_mask = sreg_immut_masks[rnum]; + + if (reg_mask != IMMUTABLE) { + if (rnum < HEX_SREG_GLB_START) { + gen_masked_reg_write(val, hex_t_sreg[rnum], reg_mask); + tcg_gen_mov_tl(ctx->t_sreg_new_value[rnum], val); + } else { + gen_masked_reg_write(val, hex_g_sreg[rnum], reg_mask); + gen_helper_sreg_write(tcg_env, tcg_constant_i32(rnum), val); + } + } +} + +G_GNUC_UNUSED +static void gen_log_sreg_write_pair(DisasContext *ctx, int rnum, TCGv_i64 val) +{ + TCGv val32 = tcg_temp_new(); + + /* Low word */ + tcg_gen_extrl_i64_i32(val32, val); + gen_log_sreg_write(ctx, rnum, val32); + + /* High word */ + tcg_gen_extrh_i64_i32(val32, val); + gen_log_sreg_write(ctx, rnum + 1, val32); +} + +G_GNUC_UNUSED +static void gen_read_sreg(TCGv dst, int reg_num) +{ + if (reg_num >= HEX_SREG_GLB_START || reg_num == HEX_SREG_BADVA) { + gen_helper_sreg_read(dst, tcg_env, tcg_constant_i32(reg_num)); + } else { + tcg_gen_mov_tl(dst, hex_t_sreg[reg_num]); + } +} + +G_GNUC_UNUSED +static void gen_read_sreg_pair(TCGv_i64 dst, int reg_num) +{ + if (reg_num < HEX_SREG_GLB_START) { + if (reg_num + 1 == HEX_SREG_BADVA) { + TCGv badva = tcg_temp_new(); + gen_helper_sreg_read(badva, tcg_env, + tcg_constant_tl(HEX_SREG_BADVA)); + tcg_gen_concat_i32_i64(dst, hex_t_sreg[reg_num], badva); + } else { + tcg_gen_concat_i32_i64(dst, hex_t_sreg[reg_num], + hex_t_sreg[reg_num + 1]); + } + } else { + gen_helper_sreg_read_pair(dst, tcg_env, tcg_constant_tl(reg_num)); + } +} + +G_GNUC_UNUSED +static void gen_read_greg(TCGv dst, int reg_num) +{ + gen_helper_greg_read(dst, tcg_env, tcg_constant_tl(reg_num)); +} + +G_GNUC_UNUSED +static void gen_read_greg_pair(TCGv_i64 dst, int reg_num) +{ + gen_helper_greg_read_pair(dst, tcg_env, tcg_constant_tl(reg_num)); +} +#endif + + void gen_log_pred_write(DisasContext *ctx, int pnum, TCGv val) { TCGv pred = get_result_pred(ctx, pnum); From a8a9e5fcde1d0d0e1aee6781a20b88d901416054 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 20 May 2024 17:54:50 -0500 Subject: [PATCH 1073/1179] target/hexagon: Add placeholder greg/sreg r/w helpers Signed-off-by: Brian Cain --- target/hexagon/helper.h | 9 +++++++++ target/hexagon/op_helper.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index f8baa599c88c..fddbd99a197d 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -107,3 +107,12 @@ DEF_HELPER_4(probe_noshuf_load, void, env, i32, int, int) DEF_HELPER_2(probe_pkt_scalar_store_s0, void, env, int) DEF_HELPER_2(probe_hvx_stores, void, env, int) DEF_HELPER_2(probe_pkt_scalar_hvx_stores, void, env, int) + +#if !defined(CONFIG_USER_ONLY) +DEF_HELPER_2(sreg_read, i32, env, i32) +DEF_HELPER_2(sreg_read_pair, i64, env, i32) +DEF_HELPER_2(greg_read, i32, env, i32) +DEF_HELPER_2(greg_read_pair, i64, env, i32) +DEF_HELPER_3(sreg_write, void, env, i32, i32) +DEF_HELPER_3(sreg_write_pair, void, env, i32, i64) +#endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 4feec232983a..ccd806836cf7 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1315,6 +1315,40 @@ void HELPER(vwhist128qm)(CPUHexagonState *env, int32_t uiV) } } +#ifndef CONFIG_USER_ONLY +void HELPER(sreg_write)(CPUHexagonState *env, uint32_t reg, uint32_t val) +{ + g_assert_not_reached(); +} + +void HELPER(sreg_write_pair)(CPUHexagonState *env, uint32_t reg, uint64_t val) + +{ + g_assert_not_reached(); +} + +uint32_t HELPER(sreg_read)(CPUHexagonState *env, uint32_t reg) +{ + g_assert_not_reached(); +} + +uint64_t HELPER(sreg_read_pair)(CPUHexagonState *env, uint32_t reg) +{ + g_assert_not_reached(); +} + +uint32_t HELPER(greg_read)(CPUHexagonState *env, uint32_t reg) +{ + g_assert_not_reached(); +} + +uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg) +{ + g_assert_not_reached(); +} +#endif + + /* These macros can be referenced in the generated helper functions */ #define warn(...) /* Nothing */ #define fatal(...) g_assert_not_reached(); From 80be3b3fc2cb8c7f6c2f7a0c5f4c2e3537bda39d Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sun, 8 Sep 2024 12:15:28 -0700 Subject: [PATCH 1074/1179] target/hexagon: Add vmstate representation Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 3 +++ target/hexagon/internal.h | 4 ++++ target/hexagon/machine.c | 25 +++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 target/hexagon/machine.c diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 3cdd18c476a2..6875ef0d8ef5 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -365,6 +365,9 @@ static void hexagon_cpu_class_init(ObjectClass *c, void *data) cc->gdb_stop_before_watchpoint = true; cc->gdb_core_xml_file = "hexagon-core.xml"; cc->disas_set_info = hexagon_cpu_disas_set_info; +#ifndef CONFIG_USER_ONLY + dc->vmsd = &vmstate_hexagon_cpu; +#endif cc->tcg_ops = &hexagon_tcg_ops; } diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h index 32e96f00d97a..96581413165c 100644 --- a/target/hexagon/internal.h +++ b/target/hexagon/internal.h @@ -31,4 +31,8 @@ void hexagon_debug(CPUHexagonState *env); extern const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS]; +#ifndef CONFIG_USER_ONLY +extern const VMStateDescription vmstate_hexagon_cpu; +#endif + #endif diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c new file mode 100644 index 000000000000..d9d71edf7718 --- /dev/null +++ b/target/hexagon/machine.c @@ -0,0 +1,25 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "migration/cpu.h" +#include "cpu.h" + + +const VMStateDescription vmstate_hexagon_cpu = { + .name = "cpu", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_CPU(), + VMSTATE_UINTTL_ARRAY(env.gpr, HexagonCPU, TOTAL_PER_THREAD_REGS), + VMSTATE_UINTTL_ARRAY(env.pred, HexagonCPU, NUM_PREGS), + VMSTATE_UINTTL_ARRAY(env.t_sreg, HexagonCPU, NUM_SREGS), + VMSTATE_UINTTL_ARRAY(env.greg, HexagonCPU, NUM_GREGS), + VMSTATE_END_OF_LIST() + }, +}; + From 5130dc1cddb69162c8c2d45c0a55af086fbc0218 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sun, 26 May 2024 19:11:55 -0500 Subject: [PATCH 1075/1179] target/hexagon: Make A_PRIV, "J2_trap*" insts need_env() Signed-off-by: Brian Cain --- target/hexagon/hex_common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index 77aef2f070ba..c39384ec9451 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -250,7 +250,9 @@ def need_env(tag): "A_LOAD" in attribdict[tag] or "A_CVI_GATHER" in attribdict[tag] or "A_CVI_SCATTER" in attribdict[tag] or - "A_IMPLICIT_WRITES_USR" in attribdict[tag]) + "A_IMPLICIT_WRITES_USR" in attribdict[tag] or + "A_PRIV" in attribdict[tag] or + "J2_trap" in tag) def need_slot(tag): From f1c9234bf168ff3aeb47d8dc8455b9fc3bd608d1 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 23 May 2024 21:42:44 -0500 Subject: [PATCH 1076/1179] target/hexagon: Define register fields for system regs Define the register fields for ssr, schedcfg, stid, bestwait, ccr, modectl, imask, ipendad. Define the fields for TLB entries. Signed-off-by: Brian Cain --- target/hexagon/reg_fields_def.h.inc | 96 +++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/target/hexagon/reg_fields_def.h.inc b/target/hexagon/reg_fields_def.h.inc index f2a58d486c55..156a3514e77d 100644 --- a/target/hexagon/reg_fields_def.h.inc +++ b/target/hexagon/reg_fields_def.h.inc @@ -39,3 +39,99 @@ DEF_REG_FIELD(USR_FPDBZE, 26, 1) DEF_REG_FIELD(USR_FPOVFE, 27, 1) DEF_REG_FIELD(USR_FPUNFE, 28, 1) DEF_REG_FIELD(USR_FPINPE, 29, 1) + +DEF_REG_FIELD(IPENDAD_IAD, 16, 16) +DEF_REG_FIELD(IPENDAD_IPEND, 0, 16) + +DEF_REG_FIELD(SCHEDCFG_EN, 8, 1) +DEF_REG_FIELD(SCHEDCFG_INTNO, 0, 4) +DEF_REG_FIELD(BESTWAIT_PRIO, 0, 10) + + +/* PTE (aka TLB entry) fields */ +DEF_REG_FIELD(PTE_PPD, 0, 24) +DEF_REG_FIELD(PTE_C, 24, 4) +DEF_REG_FIELD(PTE_U, 28, 1) +DEF_REG_FIELD(PTE_R, 29, 1) +DEF_REG_FIELD(PTE_W, 30, 1) +DEF_REG_FIELD(PTE_X, 31, 1) +DEF_REG_FIELD(PTE_VPN, 32, 20) +DEF_REG_FIELD(PTE_ASID, 52, 7) +DEF_REG_FIELD(PTE_ATR0, 59, 1) +DEF_REG_FIELD(PTE_ATR1, 60, 1) +DEF_REG_FIELD(PTE_PA35, 61, 1) +DEF_REG_FIELD(PTE_G, 62, 1) +DEF_REG_FIELD(PTE_V, 63, 1) + +/* SYSCFG fields */ +DEF_REG_FIELD(SYSCFG_MMUEN, 0, 1) +DEF_REG_FIELD(SYSCFG_ICEN, 1, 1) +DEF_REG_FIELD(SYSCFG_DCEN, 2, 1) +DEF_REG_FIELD(SYSCFG_ISDBTRUSTED, 3, 1) +DEF_REG_FIELD(SYSCFG_GIE, 4, 1) +DEF_REG_FIELD(SYSCFG_ISDBREADY, 5, 1) +DEF_REG_FIELD(SYSCFG_PCYCLEEN, 6, 1) +DEF_REG_FIELD(SYSCFG_V2X, 7, 1) +DEF_REG_FIELD(SYSCFG_IGNOREDABORT, 8, 1) +DEF_REG_FIELD(SYSCFG_PM, 9, 1) +DEF_REG_FIELD(SYSCFG_TLBLOCK, 11, 1) +DEF_REG_FIELD(SYSCFG_K0LOCK, 12, 1) +DEF_REG_FIELD(SYSCFG_BQ, 13, 1) +DEF_REG_FIELD(SYSCFG_PRIO, 14, 1) +DEF_REG_FIELD(SYSCFG_DMT, 15, 1) +DEF_REG_FIELD(SYSCFG_L2CFG, 16, 3) +DEF_REG_FIELD(SYSCFG_ITCM, 19, 1) +DEF_REG_FIELD(SYSCFG_L2NWA, 21, 1) +DEF_REG_FIELD(SYSCFG_L2NRA, 22, 1) +DEF_REG_FIELD(SYSCFG_L2WB, 23, 1) +DEF_REG_FIELD(SYSCFG_L2P, 24, 1) +DEF_REG_FIELD(SYSCFG_SLVCTL0, 25, 2) +DEF_REG_FIELD(SYSCFG_SLVCTL1, 27, 2) +DEF_REG_FIELD(SYSCFG_L2PARTSIZE, 29, 2) +DEF_REG_FIELD(SYSCFG_L2GCA, 31, 1) + +/* SSR fields */ +DEF_REG_FIELD(SSR_CAUSE, 0, 8) +DEF_REG_FIELD(SSR_ASID, 8, 7) +DEF_REG_FIELD(SSR_UM, 16, 1) +DEF_REG_FIELD(SSR_EX, 17, 1) +DEF_REG_FIELD(SSR_IE, 18, 1) +DEF_REG_FIELD(SSR_GM, 19, 1) +DEF_REG_FIELD(SSR_V0, 20, 1) +DEF_REG_FIELD(SSR_V1, 21, 1) +DEF_REG_FIELD(SSR_BVS, 22, 1) +DEF_REG_FIELD(SSR_CE, 23, 1) +DEF_REG_FIELD(SSR_PE, 24, 1) +DEF_REG_FIELD(SSR_BP, 25, 1) +DEF_REG_FIELD(SSR_XE2, 26, 1) +DEF_REG_FIELD(SSR_XA, 27, 3) +DEF_REG_FIELD(SSR_SS, 30, 1) +DEF_REG_FIELD(SSR_XE, 31, 1) + +/* misc registers */ +DEF_REG_FIELD(IMASK_MASK, 0, 16) + +DEF_REG_FIELD(STID_PRIO, 16, 8) +DEF_REG_FIELD(STID_STID, 0, 8) + +/* MODECTL fields */ +DEF_REG_FIELD(MODECTL_E, 0, 8) +DEF_REG_FIELD(MODECTL_W, 16, 8) + +DEF_REG_FIELD(CCR_L1ICP, 0, 2) +DEF_REG_FIELD(CCR_L1DCP, 3, 2) +DEF_REG_FIELD(CCR_L2CP, 6, 2) + +DEF_REG_FIELD(CCR_HFI, 16, 1) +DEF_REG_FIELD(CCR_HFD, 17, 1) +DEF_REG_FIELD(CCR_HFIL2, 18, 1) +DEF_REG_FIELD(CCR_HFDL2, 19, 1) +DEF_REG_FIELD(CCR_SFD, 20, 1) + +DEF_REG_FIELD(CCR_GIE, 24, 1) +DEF_REG_FIELD(CCR_GTE, 25, 1) +DEF_REG_FIELD(CCR_GEE, 26, 1) +DEF_REG_FIELD(CCR_GRE, 27, 1) +DEF_REG_FIELD(CCR_VV1, 29, 1) +DEF_REG_FIELD(CCR_VV2, 30, 1) +DEF_REG_FIELD(CCR_VV3, 31, 1) From 27524d8d0e9e0f383dbc021d789fb2633bd1b6c6 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 18:34:38 -0700 Subject: [PATCH 1077/1179] target/hexagon: Implement do_raise_exception() Signed-off-by: Brian Cain --- target/hexagon/internal.h | 5 +++++ target/hexagon/op_helper.c | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h index 96581413165c..7cf7bcaa6cd8 100644 --- a/target/hexagon/internal.h +++ b/target/hexagon/internal.h @@ -31,6 +31,11 @@ void hexagon_debug(CPUHexagonState *env); extern const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS]; +void G_NORETURN do_raise_exception(CPUHexagonState *env, + uint32_t exception, + target_ulong PC, + uintptr_t retaddr); + #ifndef CONFIG_USER_ONLY extern const VMStateDescription vmstate_hexagon_cpu; #endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index ccd806836cf7..1aa5b32b1f73 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -37,6 +37,26 @@ #define SF_MANTBITS 23 /* Exceptions processing helpers */ +G_NORETURN +void do_raise_exception(CPUHexagonState *env, uint32_t exception, + target_ulong PC, uintptr_t retaddr) +{ + CPUState *cs = env_cpu(env); +#ifdef CONFIG_USER_ONLY + qemu_log_mask(CPU_LOG_INT, "%s: 0x%08x\n", __func__, exception); +#else + qemu_log_mask(CPU_LOG_INT, "%s: 0x%08x, @ %08" PRIx32 "\n", + __func__, exception, PC); + + ASSERT_DIRECT_TO_GUEST_UNSET(env, exception); +#endif + + env->gpr[HEX_REG_PC] = PC; + cs->exception_index = exception; + cpu_loop_exit_restore(cs, retaddr); + cs->halted = false; +} + G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t exception, uintptr_t pc) From def1e39ba2d629aafe2e07363d25c62855e34014 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 29 May 2024 13:06:36 -0500 Subject: [PATCH 1078/1179] target/hexagon: Add system reg insns Signed-off-by: Brian Cain --- target/hexagon/hex_common.py | 15 +- target/hexagon/imported/encode_pp.def | 213 +++++++++++++++------ target/hexagon/imported/system.idef | 262 +++++++++++++++++++++++--- target/hexagon/macros.h | 2 + 4 files changed, 410 insertions(+), 82 deletions(-) diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index c39384ec9451..3b74143ff74f 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -1235,11 +1235,18 @@ def init_registers(): for reg in new_regs: new_registers[f"{reg.regtype}{reg.regid}"] = reg +def is_new_reg(tag, regid): + if regid[0] in "NO": + return True + return regid[0] == "P" and \ + f"{regid}N" in semdict[tag] and \ + f"{regid}V" not in semdict[tag] + def get_register(tag, regtype, regid): - if f"{regtype}{regid}V" in semdict[tag]: - return registers[f"{regtype}{regid}"] - else: - return new_registers[f"{regtype}{regid}"] + regid = f"{regtype}{regid}" + is_new = is_new_reg(tag, regid) + reg = new_registers[regid] if is_new else registers[regid] + return reg def helper_ret_type(tag, regs): ## If there is a scalar result, it is the return type diff --git a/target/hexagon/imported/encode_pp.def b/target/hexagon/imported/encode_pp.def index 0cd30a5e8575..37faf62b1b7e 100644 --- a/target/hexagon/imported/encode_pp.def +++ b/target/hexagon/imported/encode_pp.def @@ -1,5 +1,5 @@ /* - * Copyright(c) 2019-2023 Qualcomm Innovation Center, Inc. All Rights Reserved. + * Copyright(c) 2019-2020 Qualcomm Innovation Center, Inc. All Rights Reserved. * * 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 @@ -16,6 +16,7 @@ */ /* + * encode32.def * Encodings for 32 bit instructions * */ @@ -341,6 +342,8 @@ DEF_ENC32(L4_pload##TAG##tnew_abs,ICLASS_LD" 1 11 "OPC" iiiii PP110tti 1--ddd DEF_ENC32(L4_pload##TAG##fnew_abs,ICLASS_LD" 1 11 "OPC" iiiii PP111tti 1--ddddd") + + /* 0 000 misc: dealloc,loadw_locked,dcfetch */ STD_LD_ENC(bzw4,"0 101") STD_LD_ENC(bzw2,"0 011") @@ -375,6 +378,7 @@ DEF_ANTICLASS32(ICLASS_LD" 1110 000----- PP------ --------",LD_ADDR_POST_REG) DEF_ENC32(L2_deallocframe, ICLASS_LD" 000 0 000 sssss PP0----- ---ddddd") DEF_ENC32(L4_return, ICLASS_LD" 011 0 000 sssss PP0000-- ---ddddd") + DEF_ENC32(L4_return_t, ICLASS_LD" 011 0 000 sssss PP0100vv ---ddddd") DEF_ENC32(L4_return_f, ICLASS_LD" 011 0 000 sssss PP1100vv ---ddddd") DEF_ENC32(L4_return_tnew_pt, ICLASS_LD" 011 0 000 sssss PP0110vv ---ddddd") @@ -382,15 +386,18 @@ DEF_ENC32(L4_return_fnew_pt, ICLASS_LD" 011 0 000 sssss PP1110vv ---ddddd") DEF_ENC32(L4_return_tnew_pnt, ICLASS_LD" 011 0 000 sssss PP0010vv ---ddddd") DEF_ENC32(L4_return_fnew_pnt, ICLASS_LD" 011 0 000 sssss PP1010vv ---ddddd") -DEF_ENC32(L2_loadw_locked,ICLASS_LD" 001 0 000 sssss PP000--- 000ddddd") - +/** Load Acquire Store Release Encoding **/ +DEF_ENC32(L2_loadw_locked, ICLASS_LD" 001 0 000 sssss PP000--- 000ddddd") +DEF_ENC32(L4_loadd_locked, ICLASS_LD" 001 0 000 sssss PP010--- 000ddddd") DEF_ENC32(L2_loadw_aq, ICLASS_LD" 001 0 000 sssss PP001--- 000ddddd") DEF_ENC32(L4_loadd_aq, ICLASS_LD" 001 0 000 sssss PP011--- 000ddddd") -DEF_ENC32(R6_release_at_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --0011dd") -DEF_ENC32(R6_release_st_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --1011dd") + +DEF_ENC32(S2_storew_locked, ICLASS_ST" 000 01 01sssss PP-ttttt ----00dd") +DEF_ENC32(S4_stored_locked, ICLASS_ST" 000 01 11sssss PP0ttttt ----00dd") + DEF_ENC32(S2_storew_rl_at_vi, ICLASS_ST" 000 01 01sssss PP-ttttt --0010dd") DEF_ENC32(S2_storew_rl_st_vi, ICLASS_ST" 000 01 01sssss PP-ttttt --1010dd") @@ -398,13 +405,11 @@ DEF_ENC32(S2_storew_rl_st_vi, ICLASS_ST" 000 01 01sssss PP-ttttt --1010dd") DEF_ENC32(S4_stored_rl_at_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --0010dd") DEF_ENC32(S4_stored_rl_st_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --1010dd") -DEF_ENC32(L4_loadd_locked,ICLASS_LD" 001 0 000 sssss PP010--- 000ddddd") -DEF_EXT_SPACE(EXTRACTW, ICLASS_LD" 001 0 000 iiiii PP0iiiii -01iiiii") -DEF_ENC32(Y2_dcfetchbo, ICLASS_LD" 010 0 000 sssss PP0--iii iiiiiiii") - - - +DEF_ENC32(R6_release_at_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --0011dd") +DEF_ENC32(R6_release_st_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --1011dd") +DEF_EXT_SPACE(EXTRACTW, ICLASS_LD" 001 0 000 iiiii PP0iiiii 001iiiii") +DEF_ENC32(Y2_dcfetchbo, ICLASS_LD" 010 0 000 sssss PP0--iii iiiiiiii") @@ -488,13 +493,17 @@ STD_PST_ENC(rinew, "1 101","10ttt") /* x bus/cache */ /* x store/cache */ DEF_ENC32(S2_allocframe, ICLASS_ST" 000 01 00xxxxx PP000iii iiiiiiii") -DEF_ENC32(S2_storew_locked,ICLASS_ST" 000 01 01sssss PP-ttttt ----00dd") -DEF_ENC32(S4_stored_locked,ICLASS_ST" 000 01 11sssss PP0ttttt ----00dd") +DEF_ENC32(Y5_l2locka, ICLASS_ST" 000 01 11sssss PP1----- ------dd") DEF_ENC32(Y2_dczeroa, ICLASS_ST" 000 01 10sssss PP0----- --------") -DEF_ENC32(Y2_barrier, ICLASS_ST" 100 00 00----- PP------ 000-----") +DEF_ENC32(Y2_barrier, ICLASS_ST" 100 00 00----- PP------ 000-----") DEF_ENC32(Y2_syncht, ICLASS_ST" 100 00 10----- PP------ --------") +DEF_ENC32(Y2_l2kill, ICLASS_ST" 100 00 01----- PP-000-- --------") +DEF_ENC32(Y5_l2gunlock, ICLASS_ST" 100 00 01----- PP-010-- --------") +DEF_ENC32(Y5_l2gclean, ICLASS_ST" 100 00 01----- PP-100-- --------") +DEF_ENC32(Y5_l2gcleaninv, ICLASS_ST" 100 00 01----- PP-110-- --------") +DEF_ENC32(Y2_l2cleaninvidx,ICLASS_ST" 100 00 11sssss PP------ --------") @@ -502,9 +511,34 @@ DEF_ENC32(Y2_dccleana, ICLASS_ST" 000 00 00sssss PP------ --------") DEF_ENC32(Y2_dcinva, ICLASS_ST" 000 00 01sssss PP------ --------") DEF_ENC32(Y2_dccleaninva, ICLASS_ST" 000 00 10sssss PP------ --------") -DEF_ENC32(Y4_l2fetch, ICLASS_ST" 011 00 00sssss PP-ttttt 000-----") +/* Super */ +DEF_ENC32(Y2_dckill, ICLASS_ST" 001 00 00----- PP------ --------") +DEF_ENC32(Y2_dccleanidx, ICLASS_ST" 001 00 01sssss PP------ --------") +DEF_ENC32(Y2_dcinvidx, ICLASS_ST" 001 00 10sssss PP------ --------") +DEF_ENC32(Y2_dccleaninvidx,ICLASS_ST" 001 00 11sssss PP------ --------") + +DEF_ENC32(Y2_dctagw ,ICLASS_ST" 010 00 00sssss PP-ttttt --------") +DEF_ENC32(Y2_dctagr ,ICLASS_ST" 010 00 01sssss PP------ ---ddddd") + +DEF_ENC32(Y4_l2tagw ,ICLASS_ST" 010 00 10sssss PP0ttttt --------") +DEF_ENC32(Y4_l2tagr ,ICLASS_ST" 010 00 11sssss PP------ ---ddddd") + +DEF_ENC32(Y4_l2fetch, ICLASS_ST" 011 00 00sssss PP-ttttt 000-----") +DEF_ENC32(Y5_l2cleanidx, ICLASS_ST" 011 00 01sssss PP------ --------") +DEF_ENC32(Y5_l2invidx, ICLASS_ST" 011 00 10sssss PP------ --------") +DEF_ENC32(Y5_l2unlocka, ICLASS_ST" 011 00 11sssss PP------ --------") DEF_ENC32(Y5_l2fetch, ICLASS_ST" 011 01 00sssss PP-ttttt --------") +DEF_ENC32(Y6_l2gcleanpa, ICLASS_ST" 011 01 01----- PP-ttttt --------") +DEF_ENC32(Y6_l2gcleaninvpa,ICLASS_ST" 011 01 10----- PP-ttttt --------") + + + + + + + + /*******************************/ /* */ /* */ @@ -547,13 +581,23 @@ DEF_ENC32(J2_jumprfnewpt, ICLASS_J" 0011 011sssss PP-11-uu --------") DEF_FIELDROW_DESC32(ICLASS_J" 0100 -------- PP------ --------","[#4] (#u8) ") DEF_ENC32(J2_trap0, ICLASS_J" 0100 00------ PP-iiiii ---iii--") -DEF_ENC32(J2_pause, ICLASS_J" 0100 01------ PP-iiiii ---iii--") +DEF_ENC32(J2_trap1, ICLASS_J" 0100 10-xxxxx PP-iiiii ---iii--") +DEF_ENC32(J2_pause, ICLASS_J" 0100 01----ii PP-iiiii ---iii--") + +DEF_FIELDROW_DESC32(ICLASS_J" 0101 -------- PP------ --------","[#5] Rd=(Rs) ") +DEF_ENC32(Y2_icdatar, ICLASS_J" 0101 101sssss PP------ ---ddddd") +DEF_ENC32(Y2_ictagr, ICLASS_J" 0101 111sssss PP------ ---ddddd") +DEF_ENC32(Y2_ictagw, ICLASS_J" 0101 110sssss PP0ttttt --------") +DEF_ENC32(Y2_icdataw, ICLASS_J" 0101 110sssss PP1ttttt --------") DEF_FIELDROW_DESC32(ICLASS_J" 0110 -------- PP------ --------","[#6] icop(Rs) ") DEF_ENC32(Y2_icinva, ICLASS_J" 0110 110sssss PP000--- --------") +DEF_ENC32(Y2_icinvidx, ICLASS_J" 0110 110sssss PP001--- --------") +DEF_ENC32(Y2_ickill, ICLASS_J" 0110 110----- PP010--- --------") DEF_FIELDROW_DESC32(ICLASS_J" 0111 -------- PP------ --------","[#7] () ") DEF_ENC32(Y2_isync, ICLASS_J" 0111 11000000 PP0---00 00000010") +DEF_ENC32(J2_rte, ICLASS_J" 0111 111----- PP00---- 000-----") /* JUMP */ DEF_FIELDROW_DESC32(ICLASS_J" 100- -------- PP------ --------","[#8,9] PC=(#r22)") @@ -591,7 +635,6 @@ DEF_ENC32(J2_callf, ICLASS_J" 1101 ii1iiiii PPi-0-uu iiiiiii-") /*******************************/ -/* EJP: this has to match what we have in htmldocs.py... so I will call it CJ, we can change it */ DEF_CLASS32(ICLASS_CJ" 0--- -------- PP------ --------",CJ) DEF_FIELDROW_DESC32(ICLASS_CJ" 00-- -------- -------- --------","[#0-3] pd=cmp.xx(R,#u5) ; if ([!]p0.new) jump:[h] #s9:2 ") @@ -738,12 +781,19 @@ DEF_ENC32(J2_jumprltezpt,ICLASS_CR" 0001 11isssss PPi1iiii iiiiiii-") DEF_FIELDROW_DESC32( ICLASS_CR" 0010 -------- PP------ --------","[#2] Cd=Rs ") DEF_ENC32(A2_tfrrcr, ICLASS_CR" 0010 001sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgrcr, ICLASS_CR" 0010 000sssss PP------ ---ddddd") +DEF_ENC32(Y4_trace, ICLASS_CR" 0010 010sssss PP------ 000-----") +DEF_ENC32(Y6_diag, ICLASS_CR" 0010 010sssss PP------ 001-----") +DEF_ENC32(Y6_diag0, ICLASS_CR" 0010 010sssss PP-ttttt 010-----") +DEF_ENC32(Y6_diag1, ICLASS_CR" 0010 010sssss PP-ttttt 011-----") DEF_FIELDROW_DESC32( ICLASS_CR" 0011 -------- PP------ --------","[#3] Cdd=Rss ") DEF_ENC32(A4_tfrpcp, ICLASS_CR" 0011 001sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgpcp, ICLASS_CR" 0011 000sssss PP------ ---ddddd") DEF_FIELDROW_DESC32( ICLASS_CR" 1000 -------- PP------ --------","[#8] Rdd=Css ") DEF_ENC32(A4_tfrcpp, ICLASS_CR" 1000 000sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgcpp, ICLASS_CR" 1000 001sssss PP------ ---ddddd") DEF_FIELDROW_DESC32( ICLASS_CR" 1001 -------- PP------ --------","[#9] (#r8,#U10)") DEF_ENC32(J2_ploop1si, ICLASS_CR" 1001 101IIIII PP-iiiii IIIii-II") @@ -754,6 +804,7 @@ DEF_ENC32(J2_loop1i, ICLASS_CR" 1001 001IIIII PP-iiiii IIIii-II") DEF_FIELDROW_DESC32( ICLASS_CR" 1010 -------- PP------ --------","[#10] Rd=Cs ") DEF_ENC32(A2_tfrcrr, ICLASS_CR" 1010 000sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgcrr, ICLASS_CR" 1010 001sssss PP------ ---ddddd") DEF_ENC32(C4_addipc, ICLASS_CR" 1010 01001001 PP-iiiii i--ddddd") @@ -776,8 +827,66 @@ DEF_ENC32(C4_and_orn, ICLASS_CR" 1011 1011--ss PP0---tt uu----dd") DEF_ENC32(C4_or_andn, ICLASS_CR" 1011 1101--ss PP0---tt uu----dd") DEF_ENC32(C4_or_orn, ICLASS_CR" 1011 1111--ss PP0---tt uu----dd") -DEF_ENC32(C4_fastcorner9, ICLASS_CR"1011 0000--ss PP1---tt 1--1--dd") -DEF_ENC32(C4_fastcorner9_not, ICLASS_CR"1011 0001--ss PP1---tt 1--1--dd") +DEF_ENC32(C4_fastcorner9, ICLASS_CR"1011 0000--ss PP1---tt 1--1--dd") +DEF_ENC32(C4_fastcorner9_not, ICLASS_CR"1011 0001--ss PP1---tt 1--1--dd") + + + +/* Supervisor CR ops */ +/* Interrupts */ +DEF_FIELDROW_DESC32( ICLASS_CR" 0100 -------- PP------ --------","[#4] (Rs,Pt)") +DEF_ENC32(Y2_swi, ICLASS_CR" 0100 000sssss PP------ 000-----") +DEF_ENC32(Y2_cswi, ICLASS_CR" 0100 000sssss PP------ 001-----") +DEF_ENC32(Y2_iassignw, ICLASS_CR" 0100 000sssss PP------ 010-----") +DEF_ENC32(Y2_ciad, ICLASS_CR" 0100 000sssss PP------ 011-----") +DEF_ENC32(Y2_setimask, ICLASS_CR" 0100 100sssss PP----tt 000-----") +DEF_ENC32(Y2_setprio, ICLASS_CR" 0100 100sssss PP----tt 001-----") +DEF_ENC32(Y4_siad, ICLASS_CR" 0100 100sssss PP------ 011-----") + +DEF_ENC32(Y2_wait, ICLASS_CR" 0100 010sssss PP------ 000-----") +DEF_ENC32(Y2_resume, ICLASS_CR" 0100 010sssss PP------ 001-----") +DEF_ENC32(Y2_stop, ICLASS_CR" 0100 011sssss PP------ 000-----") +DEF_ENC32(Y2_start, ICLASS_CR" 0100 011sssss PP------ 001-----") +DEF_ENC32(Y4_nmi, ICLASS_CR" 0100 011sssss PP------ 010-----") + +DEF_FIELDROW_DESC32( ICLASS_CR" 0101 -------- PP------ --------","[#5] Rx ") +DEF_ENC32(Y2_crswap0, ICLASS_CR" 0101 000xxxxx PP------ --------") +DEF_ENC32(Y4_crswap1, ICLASS_CR" 0101 001xxxxx PP------ --------") + +DEF_FIELDROW_DESC32( ICLASS_CR" 0110 -------- PP------ --------","[#6] Rd=(Rs)") +DEF_ENC32(Y2_getimask, ICLASS_CR" 0110 000sssss PP------ ---ddddd") +DEF_ENC32(Y2_iassignr, ICLASS_CR" 0110 011sssss PP------ ---ddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 0111 -------- PP------ --------","[#7] cr=Rs ") +DEF_ENC32(Y2_tfrsrcr, ICLASS_CR" 0111 00-sssss PP------ -ddddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1100 -------- PP------ --------","[#12] ") +DEF_ENC32(Y2_break, ICLASS_CR" 1100 001----- PP------ 000-----") +DEF_ENC32(Y2_tlblock, ICLASS_CR" 1100 001----- PP------ 001-----") +DEF_ENC32(Y2_tlbunlock,ICLASS_CR" 1100 001----- PP------ 010-----") +DEF_ENC32(Y2_k0lock, ICLASS_CR" 1100 001----- PP------ 011-----") +DEF_ENC32(Y2_k0unlock, ICLASS_CR" 1100 001----- PP------ 100-----") +DEF_ENC32(Y2_tlbp, ICLASS_CR" 1100 100sssss PP------ ---ddddd") +DEF_ENC32(Y5_tlboc, ICLASS_CR" 1100 111sssss PP------ ---ddddd") +DEF_ENC32(Y5_tlbasidi, ICLASS_CR" 1100 101sssss PP------ --------") +DEF_ENC32(Y2_tlbr, ICLASS_CR" 1100 010sssss PP------ ---ddddd") +DEF_ENC32(Y2_tlbw, ICLASS_CR" 1100 000sssss PP0ttttt --------") +DEF_ENC32(Y5_ctlbw, ICLASS_CR" 1100 110sssss PP0ttttt ---ddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1101 -------- PP------ --------","[#13] Rxx ") +DEF_ENC32(Y4_crswap10, ICLASS_CR" 1101 10-xxxxx PP------ ---00000") +DEF_ENC32(Y4_tfrspcp, ICLASS_CR" 1101 00-sssss PP------ -ddddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1110 -------- PP------ --------","[#14] Rd=cr ") +DEF_ENC32(Y2_tfrscrr, ICLASS_CR" 1110 1sssssss PP------ ---ddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1111 -------- PP------ --------","[#15] Rdd=Sss ") +DEF_ENC32(Y4_tfrscpp, ICLASS_CR" 1111 0sssssss PP------ ---ddddd") + + + + + @@ -956,9 +1065,9 @@ MPY_ENC(F2_dfmin, "1000","ddddd","0","0","1","1","11") MPY_ENC(F2_dfmax, "1000","ddddd","0","1","0","0","11") MPY_ENC(F2_dfmpyll, "1000","ddddd","0","1","0","1","11") -MPY_ENC(M7_dcmpyrw, "1000","ddddd","0","0","0","1","10") +MPY_ENC(M7_dcmpyrw, "1000","ddddd","0","0","0","1","10") MPY_ENC(M7_dcmpyrwc, "1000","ddddd","0","0","1","1","10") -MPY_ENC(M7_dcmpyiw, "1000","ddddd","0","1","1","0","10") +MPY_ENC(M7_dcmpyiw, "1000","ddddd","0","1","1","0","10") MPY_ENC(M7_dcmpyiwc, "1000","ddddd","0","1","1","1","10") @@ -967,14 +1076,14 @@ DEF_FIELDROW_DESC32(ICLASS_M" 1001 -------- PP------ --------","[#9] Rd=(Rss,Rtt MPY_ENC(M2_vdmpyrs_s0, "1001","ddddd","0","0","0","0","00") MPY_ENC(M2_vdmpyrs_s1, "1001","ddddd","0","0","0","1","00") -MPY_ENC(M7_wcmpyrw, "1001","ddddd","0","0","1","0","00") +MPY_ENC(M7_wcmpyrw, "1001","ddddd","0","0","1","0","00") MPY_ENC(M7_wcmpyrw_rnd, "1001","ddddd","0","0","1","1","00") -MPY_ENC(M7_wcmpyiw, "1001","ddddd","0","1","0","0","00") +MPY_ENC(M7_wcmpyiw, "1001","ddddd","0","1","0","0","00") MPY_ENC(M7_wcmpyiw_rnd, "1001","ddddd","0","1","0","1","00") -MPY_ENC(M7_wcmpyrwc, "1001","ddddd","0","1","1","0","00") +MPY_ENC(M7_wcmpyrwc, "1001","ddddd","0","1","1","0","00") MPY_ENC(M7_wcmpyrwc_rnd, "1001","ddddd","0","1","1","1","00") -MPY_ENC(M7_wcmpyiwc, "1001","ddddd","1","0","0","0","00") +MPY_ENC(M7_wcmpyiwc, "1001","ddddd","1","0","0","0","00") MPY_ENC(M7_wcmpyiwc_rnd, "1001","ddddd","1","0","0","1","00") @@ -1030,10 +1139,10 @@ MPY_ENC(F2_dfmpylh, "1010","xxxxx","0","0","0","0","11") MPY_ENC(F2_dfmpyhh, "1010","xxxxx","0","0","0","1","11") -MPY_ENC(M7_dcmpyrw_acc, "1010","xxxxx","0","0","0","1","10") -MPY_ENC(M7_dcmpyrwc_acc, "1010","xxxxx","0","0","1","1","10") -MPY_ENC(M7_dcmpyiw_acc, "1010","xxxxx","0","1","1","0","10") -MPY_ENC(M7_dcmpyiwc_acc, "1010","xxxxx","1","0","1","0","10") +MPY_ENC(M7_dcmpyrw_acc, "1010","xxxxx","0","0","0","1","10") +MPY_ENC(M7_dcmpyrwc_acc, "1010","xxxxx","0","0","1","1","10") +MPY_ENC(M7_dcmpyiw_acc, "1010","xxxxx","0","1","1","0","10") +MPY_ENC(M7_dcmpyiwc_acc, "1010","xxxxx","1","0","1","0","10") @@ -1063,7 +1172,6 @@ SP_MPY(M2_mpy_sat_rnd, "1100","ddddd","1","1","0") SP_MPY(M2_mpyu, "1100","ddddd","0","0","1") DEF_FIELDROW_DESC32(ICLASS_M" 1101 -------- PP------ --------","[#13] Rd=(Rs,Rt)") -/* EJP: same as mpyi MPY_ENC(M2_mpyui, "1101","ddddd","0","0","1","0","00") */ MPY_ENC(M2_mpyi, "1101","ddddd","0","0","0","0","00") MPY_ENC(M2_mpy_up, "1101","ddddd","0","0","0","0","01") MPY_ENC(M2_mpyu_up, "1101","ddddd","0","0","1","0","01") @@ -1266,7 +1374,6 @@ DEF_ENC32(C2_cmovenewif,ICLASS_ALU2op" 1110 1uu0iiii PP1iiiii iiiddddd") DEF_ENC32(C2_cmoveit, ICLASS_ALU2op" 1110 0uu0iiii PP0iiiii iiiddddd") DEF_ENC32(C2_cmoveif, ICLASS_ALU2op" 1110 1uu0iiii PP0iiiii iiiddddd") - DEF_FIELDROW_DESC32( ICLASS_ALU2op" 1111 -------- PP------ --------","[#15] nop") DEF_ENC32(A2_nop, ICLASS_ALU2op" 1111 -------- PP------ --------") @@ -1408,9 +1515,6 @@ DEF_FIELDROW_DESC32(ICLASS_ALU3op" 1110 -------- PP------ --------","[#14] Rese - - - /*******************************/ /* */ /* */ @@ -1508,7 +1612,6 @@ SH_RRI6_ENC(S6_rol_i_##TAGEND,MAJ4,MIN3,SMOD1 "11",DSTCHARS) DEF_FIELDROW_DESC32(ICLASS_S2op" 0000 -------- PP------ --------","[#0] Rdd=(Rss,#u6)") -/* EJP: there is actually quite a bit of space here, look at the reserved bits */ I6SHIFTTYPES(p, "0000","000","0","ddddd") I5SHIFTTYPES_NOROL(vw, "0000","010","0","ddddd") I4SHIFTTYPES(vh, "0000","100","0","ddddd") @@ -1620,8 +1723,8 @@ SH2_RR_ENC(A2_roundsat, "1000","110","-","001","ddddd") SH_RRI5_ENC(S2_asr_i_svw_trun, "1000","110", "010","ddddd") SH_RRI5_ENC(A4_bitspliti, "1000","110", "100","ddddd") -SH_RRI5_ENC(A7_clip, "1000","110", "101","ddddd") -SH_RRI5_ENC(A7_vclip, "1000","110", "110","ddddd") +SH_RRI5_ENC(A7_clip, "1000","110", "101","ddddd") +SH_RRI5_ENC(A7_vclip, "1000","110", "110","ddddd") SH2_RR_ENC(S4_clbpnorm, "1000","011","-","000","ddddd") @@ -1743,10 +1846,11 @@ SH_RRR_ENC(S2_shuffob, "0001","00-","-","10-","ddddd") SH_RRR_ENC(S2_shuffeh, "0001","00-","-","11-","ddddd") SH_RRR_ENC(S2_shuffoh, "0001","10-","-","000","ddddd") +// 001 SH_RRR_ENC(S2_vtrunewh, "0001","10-","-","010","ddddd") -SH_RRR_ENC(S6_vtrunehb_ppp, "0001","10-","-","011","ddddd") +SH_RRR_ENC(S6_vtrunehb_ppp, "0001","10-","-","011","ddddd") SH_RRR_ENC(S2_vtrunowh, "0001","10-","-","100","ddddd") -SH_RRR_ENC(S6_vtrunohb_ppp, "0001","10-","-","101","ddddd") +SH_RRR_ENC(S6_vtrunohb_ppp, "0001","10-","-","101","ddddd") SH_RRR_ENC(S2_lfsp, "0001","10-","-","110","ddddd") SH_RRR_ENC(S4_vxaddsubw, "0001","01-","-","000","ddddd") @@ -1780,8 +1884,6 @@ SH_RRR_ENC(S4_vrcrotate, "0011","11-","i","11i","ddddd") DEF_FIELDROW_DESC32(ICLASS_S3op" 0100 -------- PP------ --------","[#4] Rd=(Rs,Rt,#u3)") DEF_ENC32(S2_addasl_rrri, ICLASS_S3op" 0100 000 sssss PP0ttttt iiiddddd") - - DEF_FIELDROW_DESC32(ICLASS_S3op" 0101 -------- PP------ --------","[#5] Rd=(Rss,Rt)") SH_RRR_ENC(S2_asr_r_svw_trun, "0101","---","-","010","ddddd") SH_RRR_ENC(M4_cmpyi_wh, "0101","---","-","100","ddddd") @@ -1841,6 +1943,7 @@ DEF_FIELDROW_DESC32(ICLASS_S3op" 1010 -------- PP------ --------","[#10] Rxx=(Rs SH_RRR_ENC(S2_insertp_rp, "1010","0--","0","---","xxxxx") SH_RRR_ENC(M4_xor_xacc, "1010","10-","0","000","xxxxx") + DEF_FIELDROW_DESC32(ICLASS_S3op" 1011 -------- PP------ --------","[#11] Rxx=(Rss,Rt)") RSHIFTTYPES(p_or, "1011","000","-","-","xxxxx") RSHIFTTYPES(p_and, "1011","010","-","-","xxxxx") @@ -1848,19 +1951,19 @@ RSHIFTTYPES(p_nac, "1011","100","-","-","xxxxx") RSHIFTTYPES(p_acc, "1011","110","-","-","xxxxx") RSHIFTTYPES(p_xor, "1011","011","-","-","xxxxx") -SH_RRR_ENCX(A4_vrmaxh, "1011","001","0","001","uuuuu") -SH_RRR_ENCX(A4_vrmaxuh, "1011","001","1","001","uuuuu") -SH_RRR_ENCX(A4_vrmaxw, "1011","001","0","010","uuuuu") -SH_RRR_ENCX(A4_vrmaxuw, "1011","001","1","010","uuuuu") +SH_RRR_ENCX(A4_vrmaxh, "1011","001","0","001","uuuuu") +SH_RRR_ENCX(A4_vrmaxuh, "1011","001","1","001","uuuuu") +SH_RRR_ENCX(A4_vrmaxw, "1011","001","0","010","uuuuu") +SH_RRR_ENCX(A4_vrmaxuw, "1011","001","1","010","uuuuu") -SH_RRR_ENCX(A4_vrminh, "1011","001","0","101","uuuuu") -SH_RRR_ENCX(A4_vrminuh, "1011","001","1","101","uuuuu") -SH_RRR_ENCX(A4_vrminw, "1011","001","0","110","uuuuu") -SH_RRR_ENCX(A4_vrminuw, "1011","001","1","110","uuuuu") +SH_RRR_ENCX(A4_vrminh, "1011","001","0","101","uuuuu") +SH_RRR_ENCX(A4_vrminuh, "1011","001","1","101","uuuuu") +SH_RRR_ENCX(A4_vrminw, "1011","001","0","110","uuuuu") +SH_RRR_ENCX(A4_vrminuw, "1011","001","1","110","uuuuu") -SH_RRR_ENC(S2_vrcnegh, "1011","001","1","111","xxxxx") +SH_RRR_ENC(S2_vrcnegh, "1011","001","1","111","xxxxx") -SH_RRR_ENC(S4_vrcrotate_acc, "1011","101","i","--i","xxxxx") +SH_RRR_ENC(S4_vrcrotate_acc, "1011","101","i","--i","xxxxx") DEF_FIELDROW_DESC32(ICLASS_S3op" 1100 -------- PP------ --------","[#12] Rx=(Rs,Rt)") @@ -1874,11 +1977,6 @@ DEF_FIELDROW_DESC32(ICLASS_S3op" 1101 -------- PP------ --------","[#13] Reserve DEF_FIELDROW_DESC32(ICLASS_S3op" 1110 -------- PP------ --------","[#14] Reserved") -DEF_FIELDROW_DESC32(ICLASS_S3op" 1111 -------- PP------ --------","[#14] User Instruction") - - - - @@ -2129,3 +2227,6 @@ OP_OPI_RI(lsr,"1") DEF_FIELDROW_DESC32(ICLASS_ALU64" 1111 -------- PP------ --------","[#15] Rd=(Rs,Ru,#u6:2)") DEF_ENC32(M4_mpyri_addr_u2, ICLASS_ALU64" 1111 0ii sssss PPiddddd iiiuuuuu") DEF_ENC32(M4_mpyri_addr, ICLASS_ALU64" 1111 1ii sssss PPiddddd iiiuuuuu") + + + diff --git a/target/hexagon/imported/system.idef b/target/hexagon/imported/system.idef index 7c6568e75e42..fd7ef18b3e34 100644 --- a/target/hexagon/imported/system.idef +++ b/target/hexagon/imported/system.idef @@ -25,44 +25,262 @@ /* User->OS interface */ /********************************************/ -Q6INSN(J2_trap0,"trap0(#u8)",ATTRIBS(A_COF), +Q6INSN(J2_trap0,"trap0(#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Trap to Operating System", - fTRAP(0,uiV); + fTRAP(0,uiV); ) -Q6INSN(J2_pause,"pause(#u8)",ATTRIBS(A_COF), +Q6INSN(J2_trap1,"trap1(Rx32,#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"Trap to Operating System", + /* + * Note: if RxV is not written, we get the same as the input. + * Since trap1 is SOLO, this means the register will effectively not be updated + */ + if (!fTRAP1_VIRTINSN(uiV)) { + fTRAP(1,uiV); + } else if (uiV == 1) { + fVIRTINSN_RTE(uiV,RxV); + } else if (uiV == 3) { + fVIRTINSN_SETIE(uiV,RxV); + } else if (uiV == 4) { + fVIRTINSN_GETIE(uiV,RxV); + } else if (uiV == 6) { + fVIRTINSN_SPSWAP(uiV,RxV); + }) + +Q6INSN(J2_pause,"pause(#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Enter low-power state for #u8 cycles",{fPAUSE(uiV);}) -Q6INSN(Y2_icinva,"icinva(Rs32)",ATTRIBS(A_ICOP,A_ICFLUSHOP),"Instruction Cache Invalidate Address",{fEA_REG(RsV); fICINVA(EA);}) +Q6INSN(J2_rte, "rte", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NO_TIMING_LOG), +"Return from Exception", +{ +fHIDE(if((thread->timing_on) && (thread->status & EXEC_STATUS_REPLAY)) { return; }) +fHIDE(CALLBACK(thread->processor_ptr->options->rte_callback, + thread->system_ptr,thread->processor_ptr, + thread->threadId,0);) +fCLEAR_RTE_EX(); +fBRANCH(fREAD_ELR(),COF_TYPE_RTE);}) + + +/********************************************/ +/* Interrupt Management */ +/********************************************/ + +Q6INSN(Y2_swi,"swi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Software Interrupt",{DO_SWI(RsV);}) +Q6INSN(Y2_cswi,"cswi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Cancel Software Interrupt",{DO_CSWI(RsV);}) +Q6INSN(Y2_ciad,"ciad(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Re-enable interrupt in IAD",{DO_CIAD(RsV);}) +Q6INSN(Y4_siad,"siad(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Disable interrupt in IAD",{DO_SIAD(RsV);}) +Q6INSN(Y2_iassignr,"Rd32=iassignr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Read interrupt to thread assignments",{DO_IASSIGNR(RsV,RdV);}) +Q6INSN(Y2_iassignw,"iassignw(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Write interrupt to thread assignments",{DO_IASSIGNW(RsV);}) + + +Q6INSN(Y2_getimask,"Rd32=getimask(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Read imask register of another thread", +{RdV = READ_IMASK(RsV & thread->processor_ptr->thread_system_mask); }) + +Q6INSN(Y2_setimask,"setimask(Pt4,Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Change imask register of another thread", +{fPREDUSE_TIMING();WRITE_IMASK(PtV & thread->processor_ptr->thread_system_mask,RsV); }) + + + +/********************************************/ +/* TLB management */ +/********************************************/ + +Q6INSN(Y2_tlbw,"tlbw(Rss32,Rt32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"Write TLB entry", {fTLBW(RtV,RssV);}) + +Q6INSN(Y5_ctlbw,"Rd32=ctlbw(Rss32,Rt32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"Conditional Write TLB entry", +{ + if (fTLB_ENTRY_OVERLAP( (1LL<<63) | RssV )) { + RdV=fTLB_ENTRY_OVERLAP_IDX( (1LL<<63) | RssV); + } else { + fTLBW(RtV,RssV); + RdV=0x80000000; + } +}) + +Q6INSN(Y5_tlboc,"Rd32=tlboc(Rss32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"TLB overlap check", +{ + if (fTLB_ENTRY_OVERLAP( (1LL<<63) | RssV )) { + RdV=fTLB_ENTRY_OVERLAP_IDX( (1LL<<63) | RssV); + } else { + RdV=0x80000000; + } +}) + +Q6INSN(Y2_tlbr,"Rdd32=tlbr(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Read TLB entry", +{RddV = fTLBR(RsV);}) + +Q6INSN(Y2_tlbp,"Rd32=tlbp(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Probe TLB", {RdV=fTLBP(RsV);}) + +Q6INSN(Y5_tlbasidi,"tlbinvasid(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Invalidate ASID", +{ + fHIDE(int i;) + fHIDE(unsigned int NUM_TLB_ENTRIES = NUM_TLB_REGS(thread->processor_ptr);) + for (i = 0; i < NUM_TLB_ENTRIES; i++) { + if ((fGET_FIELD(fTLBR(i),PTE_G) == 0) && + (fGET_FIELD(fTLBR(i),PTE_ASID) == fEXTRACTU_RANGE(RsV,26,20))) { + fTLBW(i,fTLBR(i) & ~(1ULL << 63)); + } + } +}) + +Q6INSN(Y2_tlblock,"tlblock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG), "Lock TLB", +{fSET_TLB_LOCK();}) + +Q6INSN(Y2_tlbunlock,"tlbunlock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Unlock TLB", +{fCLEAR_TLB_LOCK();}) + +Q6INSN(Y2_k0lock,"k0lock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG), "Lock K0", +{fSET_K0_LOCK();}) + +Q6INSN(Y2_k0unlock,"k0unlock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Unlock K0", +{fCLEAR_K0_LOCK();}) + +/********************************************/ +/* Supervisor Reg Management */ +/********************************************/ + +Q6INSN(Y2_crswap0,"crswap(Rx32,sgp0)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general pointer 0 with GPR", +{fHIDE(size4s_t tmp;) tmp = RxV; RxV = READ_SGP0(); WRITE_SGP0(tmp);}) +Q6INSN(Y4_crswap1,"crswap(Rx32,sgp1)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general pointer 1 with GPR", +{fHIDE(size4s_t tmp;) tmp = RxV; RxV = READ_SGP1(); WRITE_SGP1(tmp);}) + +Q6INSN(Y4_crswap10,"crswap(Rxx32,sgp1:0)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general purpose 0/1 with GPR Pair", +{fHIDE(size8s_t tmp;) tmp = RxxV; RxxV=READ_SGP10(); WRITE_SGP10(tmp);}) + +Q6INSN(Y2_tfrscrr,"Rd32=Ss128",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer Supervisor Reg to GPR", {RdV=SsV;}) +Q6INSN(Y2_tfrsrcr,"Sd128=Rs32",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer GPR to Supervisor Reg", {SdV=RsV;}) +Q6INSN(Y4_tfrscpp,"Rdd32=Sss128",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer Supervisor Reg to GPR", {RddV=SssV;}) +Q6INSN(Y4_tfrspcp,"Sdd128=Rss32",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer GPR to Supervisor Reg", {SddV=RssV;}) + +Q6INSN(G4_tfrgcrr,"Rd32=Gs32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer Guest Reg to GPR", {RdV=GsV;}) +Q6INSN(G4_tfrgrcr,"Gd32=Rs32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer GPR to Guest Reg", {GdV=RsV;}) +Q6INSN(G4_tfrgcpp,"Rdd32=Gss32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer Guest Reg to GPR", {RddV=GssV;}) +Q6INSN(G4_tfrgpcp,"Gdd32=Rss32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer GPR to Guest Reg", {GddV=RssV;}) -Q6INSN(Y2_isync,"isync",ATTRIBS(),"Memory Synchronization",{fISYNC();}) -Q6INSN(Y2_barrier,"barrier",ATTRIBS(A_RESTRICT_SLOT0ONLY),"Memory Barrier",{fBARRIER();}) -Q6INSN(Y2_syncht,"syncht",ATTRIBS(A_RESTRICT_SLOT0ONLY),"Memory Synchronization",{fSYNCH();}) -Q6INSN(Y2_dcfetchbo,"dcfetch(Rs32+#u11:3)",ATTRIBS(A_RESTRICT_PREFERSLOT0,A_DCFETCH),"Data Cache Prefetch",{fEA_RI(RsV,uiV); fDCFETCH(EA);}) +Q6INSN(Y2_setprio,"setprio(Pt4,Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Change TID Prio of another thread", +{fPREDUSE_TIMING();WRITE_PRIO(PtV & thread->processor_ptr->thread_system_mask,RsV); }) -Q6INSN(Y2_dczeroa,"dczeroa(Rs32)",ATTRIBS(A_STORE,A_RESTRICT_SLOT0ONLY,A_DCZEROA),"Zero an aligned 32-byte cacheline",{fEA_REG(RsV); fDCZEROA(EA);}) -Q6INSN(Y2_dccleana,"dccleana(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Clean Address",{fEA_REG(RsV); fDCCLEANA(EA);}) -Q6INSN(Y2_dccleaninva,"dccleaninva(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Clean and Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) -Q6INSN(Y2_dcinva,"dcinva(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) -Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY),"L2 Cache Prefetch", +/********************************************/ +/* Power Management / Thread on/off */ +/********************************************/ +Q6INSN(Y6_diag,"diag(Rs32)",ATTRIBS(),"Send value to Diag trace module",{ +}) +Q6INSN(Y6_diag0,"diag0(Rss32,Rtt32)",ATTRIBS(),"Send values of two register to DIAG Trace. Set X=0",{ +}) +Q6INSN(Y6_diag1,"diag1(Rss32,Rtt32)",ATTRIBS(),"Send values of two register to DIAG Trace. Set X=1",{ +}) + + +Q6INSN(Y4_trace,"trace(Rs32)",ATTRIBS(A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Send value to ETM trace",{ + fDO_TRACE(RsV); +}) + +Q6INSN(Y2_stop,"stop(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Stop thread(s)",{ + fHIDE(RsV=RsV;) + if (!fIN_DEBUG_MODE_NO_ISDB(fGET_TNUM())) fCLEAR_RUN_MODE(fGET_TNUM()); +}) + +Q6INSN(Y4_nmi,"nmi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG),"Raise NMI on thread(s)",{ + fDO_NMI(RsV); +}) + +Q6INSN(Y2_start,"start(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Start thread(s)",fSTART(RsV);) + +Q6INSN(Y2_wait,"wait(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG),"Make thread(s) wait",{ + fHIDE(RsV=RsV;) + if (!fIN_DEBUG_MODE(fGET_TNUM())) fSET_WAIT_MODE(fGET_TNUM()); + fIN_DEBUG_MODE_WARN(fGET_TNUM()); +}) + +Q6INSN(Y2_resume,"resume(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Make thread(s) stop waiting",fRESUME(RsV);) + +Q6INSN(Y2_break,"brkpt",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Breakpoint",{fBREAK();}) + + +/********************************************/ +/* Cache Management */ +/********************************************/ + +Q6INSN(Y2_ictagr,"Rd32=ictagr(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Tag Read",{fICTAGR(RsV,RdV,RdN);}) +Q6INSN(Y2_ictagw,"ictagw(Rs32,Rt32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Tag Write",{fICTAGW(RsV,RtV);}) +Q6INSN(Y2_icdataw,"icdataw(Rs32,Rt32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Data Write",{fICDATAW(RsV,RtV);}) +Q6INSN(Y2_icdatar,"Rd32=icdatar(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Data Read",{fICDATAR(RsV, RdV);}) +Q6INSN(Y2_icinva,"icinva(Rs32)",ATTRIBS(A_ICOP,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYADDRESS,A_ICFLUSHOP),"Instruction Cache Invalidate Address",{fEA_REG(RsV); fICINVA(EA);}) +Q6INSN(Y2_icinvidx,"icinvidx(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICFLUSHOP),"Instruction Cache Invalidate Index",{fICINVIDX(RsV);}) +Q6INSN(Y2_ickill,"ickill",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_ICFLUSHOP),"Instruction Cache Invalidate",{fICKILL();}) + +Q6INSN(Y2_isync,"isync",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Memory Synchronization",{fISYNC();}) +Q6INSN(Y2_barrier,"barrier",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK),"Memory Barrier",{fBARRIER();}) +Q6INSN(Y2_syncht,"syncht",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET),"Memory Synchronization",{fSYNCH();}) + + +Q6INSN(Y2_dcfetchbo,"dcfetch(Rs32+#u11:3)",ATTRIBS(A_RESTRICT_PREFERSLOT0,A_DCFETCH,A_RESTRICT_NOSLOT1_STORE),"Data Cache Prefetch",{fEA_RI(RsV,uiV); fDCFETCH(EA);}) +Q6INSN(Y2_dckill,"dckill",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_DCFLUSHOP),"Data Cache Invalidate",{fDCKILL();}) + + +Q6INSN(Y2_dczeroa,"dczeroa(Rs32)",ATTRIBS(A_STORE,A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCZEROA),"Zero an aligned 32-byte cacheline",{fEA_REG(RsV); fDCZEROA(EA);}) +Q6INSN(Y2_dccleana,"dccleana(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Clean Address",{fEA_REG(RsV); fDCCLEANA(EA);}) +Q6INSN(Y2_dccleanidx,"dccleanidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Clean Index",{fDCCLEANIDX(RsV);}) +Q6INSN(Y2_dccleaninva,"dccleaninva(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Clean and Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) +Q6INSN(Y2_dccleaninvidx,"dccleaninvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Clean and Invalidate Index",{fDCCLEANINVIDX(RsV);}) +Q6INSN(Y2_dcinva,"dcinva(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) +Q6INSN(Y2_dcinvidx,"dcinvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Invalidate Index",{fDCINVIDX(RsV);}) +Q6INSN(Y2_dctagr,"Rd32=dctagr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCTAGOP),"Data Cache Tag Read",{fDCTAGR(RsV,RdV,RdN);}) +Q6INSN(Y2_dctagw,"dctagw(Rs32,Rt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_DCTAGOP),"Data Cache Tag Write",{fDCTAGW(RsV,RtV);}) + + +Q6INSN(Y2_l2kill,"l2kill",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Cache Invalidate",{fL2KILL();}) +Q6INSN(Y4_l2tagw,"l2tagw(Rs32,Rt32)",ATTRIBS(A_PRIV,A_NOTE_BADTAG_UNDEF,A_NOTE_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_L2TAGOP),"L2 Cache Tag Write",{fL2TAGW(RsV,RtV);}) +Q6INSN(Y4_l2tagr,"Rd32=l2tagr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_BADTAG_UNDEF,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2TAGOP),"L2 Cache Tag Read",{fL2TAGR(RsV,RdV,RdN);}) + +Q6INSN(Y2_l2cleaninvidx,"l2cleaninvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Clean and Invalidate Index",{fL2CLEANINVIDX(RsV); }) +Q6INSN(Y5_l2cleanidx,"l2cleanidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Clean by Index",{fL2CLEANIDX(RsV); }) +Q6INSN(Y5_l2invidx,"l2invidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Invalidate by Index",{fL2INVIDX(RsV); }) + + + +Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK),"L2 Cache Prefetch", { fL2FETCH(RsV, - (RtV&0xff), /*height*/ - ((RtV>>8)&0xff), /*width*/ - ((RtV>>16)&0xffff), /*stride*/ - 0); /*extra attrib flags*/ + (RtV&0xff), /*height*/ + ((RtV>>8)&0xff), /*width*/ + ((RtV>>16)&0xffff), /*stride*/ + 0); /*extra attrib flags*/ }) -Q6INSN(Y5_l2fetch,"l2fetch(Rs32,Rtt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY),"L2 Cache Prefetch", +Q6INSN(Y5_l2fetch,"l2fetch(Rs32,Rtt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK),"L2 Cache Prefetch", { fL2FETCH(RsV, - fGETUHALF(0,RttV), /*height*/ - fGETUHALF(1,RttV), /*width*/ - fGETUHALF(2,RttV), /*stride*/ - fGETUHALF(3,RttV)); /*flags*/ + fGETUHALF(0,RttV), /*height*/ + fGETUHALF(1,RttV), /*width*/ + fGETUHALF(2,RttV), /*stride*/ + fGETUHALF(3,RttV)); /*flags*/ }) + +Q6INSN(Y5_l2locka,"Pd4=l2locka(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_CACHEOP,A_COPBYADDRESS,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_LATEPRED,A_NOTE_LATEPRED), +"Lock L2 cache line by address", { fEA_REG(RsV); fL2LOCKA(EA,PdV,PdN); fHIDE(MARK_LATE_PRED_WRITE(PdN)) }) + + +Q6INSN(Y5_l2unlocka,"l2unlocka(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_CACHEOP,A_COPBYADDRESS,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK), "UnLock L2 cache line by address", { fEA_REG(RsV); fL2UNLOCKA(EA); }) + + + +Q6INSN(Y5_l2gunlock,"l2gunlock",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Unlock",{fL2UNLOCK();}) + +Q6INSN(Y5_l2gclean,"l2gclean",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean",{fL2CLEAN();}) + +Q6INSN(Y5_l2gcleaninv,"l2gcleaninv",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean and Invalidate",{fL2CLEANINV();}) + +Q6INSN(Y6_l2gcleanpa,"l2gclean(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean by PA Range",{fL2CLEANPA(RttV);}) + +Q6INSN(Y6_l2gcleaninvpa,"l2gcleaninv(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean and Invalidate by PA Range",{fL2CLEANINVPA(RttV);}) + diff --git a/target/hexagon/macros.h b/target/hexagon/macros.h index 6e4a3a16970c..b0e9610d98d5 100644 --- a/target/hexagon/macros.h +++ b/target/hexagon/macros.h @@ -675,3 +675,5 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #endif #endif + +#define fPREDUSE_TIMING() From b5ff4a1987e6ffa54e3b686eaa175507d5463034 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 8 Jul 2024 13:28:08 -0700 Subject: [PATCH 1079/1179] target/hexagon: Add sysemu TCG overrides Define TCG overrides for setprio(), crswap(,sgp{0,1,1:0}). Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 37 ++++++++++++++++++++++++++++++++ target/hexagon/cpu_helper.h | 32 ++++++++++++++++++++++++++++ target/hexagon/gen_tcg_sys.h | 41 ++++++++++++++++++++++++++++++++++++ target/hexagon/genptr.c | 4 ++++ target/hexagon/helper.h | 1 + target/hexagon/hex_common.py | 2 ++ target/hexagon/meson.build | 14 ++++++------ target/hexagon/op_helper.c | 7 ++++++ 8 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 target/hexagon/cpu_helper.c create mode 100644 target/hexagon/cpu_helper.h create mode 100644 target/hexagon/gen_tcg_sys.h diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c new file mode 100644 index 000000000000..ef39ebf8daef --- /dev/null +++ b/target/hexagon/cpu_helper.c @@ -0,0 +1,37 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "cpu_helper.h" +#include "system/cpus.h" +#ifdef CONFIG_USER_ONLY +#include "qemu.h" +#include "exec/helper-proto.h" +#else +#include "hw/boards.h" +#include "hw/hexagon/hexagon.h" +#endif +#include "exec/exec-all.h" +#include "exec/cputlb.h" +#include "exec/cpu_ldst.h" +#include "qemu/log.h" +#include "tcg/tcg-op.h" +#include "internal.h" +#include "macros.h" +#include "sys_macros.h" +#include "arch.h" + + +#ifndef CONFIG_USER_ONLY + +uint32_t arch_get_system_reg(CPUHexagonState *env, uint32_t reg) +{ + g_assert_not_reached(); +} + + +#endif diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h new file mode 100644 index 000000000000..194bcbf45137 --- /dev/null +++ b/target/hexagon/cpu_helper.h @@ -0,0 +1,32 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_CPU_HELPER_H +#define HEXAGON_CPU_HELPER_H + +static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, + uint32_t val) +{ + g_assert(reg < TOTAL_PER_THREAD_REGS); + g_assert_not_reached(); +} + +static inline uint32_t arch_get_thread_reg(CPUHexagonState *env, uint32_t reg) +{ + g_assert(reg < TOTAL_PER_THREAD_REGS); + g_assert_not_reached(); +} + +static inline void arch_set_system_reg(CPUHexagonState *env, uint32_t reg, + uint32_t val) +{ + g_assert_not_reached(); +} + +uint32_t arch_get_system_reg(CPUHexagonState *env, uint32_t reg); + +#endif + diff --git a/target/hexagon/gen_tcg_sys.h b/target/hexagon/gen_tcg_sys.h new file mode 100644 index 000000000000..362703ab45e8 --- /dev/null +++ b/target/hexagon/gen_tcg_sys.h @@ -0,0 +1,41 @@ +/* + * Copyright(c) 2022-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_GEN_TCG_SYS_H +#define HEXAGON_GEN_TCG_SYS_H + +#define fGEN_TCG_Y2_setprio(SHORTCODE) \ + gen_helper_setprio(tcg_env, PtV, RsV) + +#define fGEN_TCG_Y2_crswap0(SHORTCODE) \ + do { \ + TCGv tmp = tcg_temp_new(); \ + tcg_gen_mov_tl(tmp, RxV); \ + tcg_gen_mov_tl(RxV, hex_t_sreg[HEX_SREG_SGP0]); \ + tcg_gen_mov_tl(ctx->t_sreg_new_value[HEX_SREG_SGP0], tmp); \ + } while (0) + +#define fGEN_TCG_Y4_crswap1(SHORTCODE) \ + do { \ + TCGv tmp = tcg_temp_new(); \ + tcg_gen_mov_tl(tmp, RxV); \ + tcg_gen_mov_tl(RxV, hex_t_sreg[HEX_SREG_SGP1]); \ + tcg_gen_mov_tl(ctx->t_sreg_new_value[HEX_SREG_SGP1], tmp); \ + } while (0) + +#define fGEN_TCG_Y4_crswap10(SHORTCODE) \ + do { \ + g_assert_not_reached(); \ + TCGv_i64 tmp = tcg_temp_new_i64(); \ + tcg_gen_mov_i64(tmp, RxxV); \ + tcg_gen_concat_i32_i64(RxxV, \ + hex_t_sreg[HEX_SREG_SGP0], \ + hex_t_sreg[HEX_SREG_SGP1]); \ + tcg_gen_extrl_i64_i32(ctx->t_sreg_new_value[HEX_SREG_SGP0], tmp); \ + tcg_gen_extrh_i64_i32(ctx->t_sreg_new_value[HEX_SREG_SGP1], tmp); \ + } while (0) + +#endif diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c index 488d0b4b978b..5554c9515c4d 100644 --- a/target/hexagon/genptr.c +++ b/target/hexagon/genptr.c @@ -31,6 +31,10 @@ #undef QEMU_GENERATE #include "gen_tcg.h" #include "gen_tcg_hvx.h" +#ifndef CONFIG_USER_ONLY +#include "gen_tcg_sys.h" +#endif + #include "genptr.h" TCGv gen_read_reg(TCGv result, int num) diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index fddbd99a197d..146f4f02e415 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -115,4 +115,5 @@ DEF_HELPER_2(greg_read, i32, env, i32) DEF_HELPER_2(greg_read_pair, i64, env, i32) DEF_HELPER_3(sreg_write, void, env, i32, i32) DEF_HELPER_3(sreg_write_pair, void, env, i32, i64) +DEF_HELPER_3(setprio, void, env, i32, i32) #endif diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index 3b74143ff74f..4f40a1b3b24c 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -1359,6 +1359,7 @@ def parse_common_args(desc): parser.add_argument("semantics", help="semantics file") parser.add_argument("overrides", help="overrides file") parser.add_argument("overrides_vec", help="vector overrides file") + parser.add_argument("overrides_sys", help="system overrides file") parser.add_argument("out", help="output file") parser.add_argument("--idef-parser", help="file of instructions translated by idef-parser") @@ -1366,6 +1367,7 @@ def parse_common_args(desc): read_semantics_file(args.semantics) read_overrides_file(args.overrides) read_overrides_file(args.overrides_vec) + read_overrides_file(args.overrides_sys) if args.idef_parser: read_idef_parser_enabled_file(args.idef_parser) calculate_attribs() diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build index bb4ebaae816e..3ec53010fa02 100644 --- a/target/hexagon/meson.build +++ b/target/hexagon/meson.build @@ -20,6 +20,7 @@ hexagon_ss = ss.source_set() hex_common_py = 'hex_common.py' gen_tcg_h = meson.current_source_dir() / 'gen_tcg.h' gen_tcg_hvx_h = meson.current_source_dir() / 'gen_tcg_hvx.h' +gen_tcg_sys_h = meson.current_source_dir() / 'gen_tcg_sys.h' idef_parser_dir = meson.current_source_dir() / 'idef-parser' # @@ -249,6 +250,7 @@ hexagon_ss.add(files( 'cpu.c', 'translate.c', 'op_helper.c', + 'cpu_helper.c', 'gdbstub.c', 'genptr.c', 'reg_fields.c', @@ -346,12 +348,12 @@ if idef_parser_enabled and 'hexagon-linux-user' in target_dirs # Setup input and dependencies for the next step, this depends on whether or # not idef-parser is enabled helper_dep = [semantics_generated, idef_generated_tcg_c, idef_generated_tcg] - helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, '--idef-parser', idef_generated_list] + helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h, '--idef-parser', idef_generated_list] else # Setup input and dependencies for the next step, this depends on whether or # not idef-parser is enabled helper_dep = [semantics_generated] - helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h] + helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h] endif # @@ -365,7 +367,7 @@ helper_protos_generated = custom_target( 'helper_protos_generated.h.inc', output: 'helper_protos_generated.h.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_helper_protos.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(helper_protos_generated) @@ -374,7 +376,7 @@ helper_funcs_generated = custom_target( 'helper_funcs_generated.c.inc', output: 'helper_funcs_generated.c.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_helper_funcs.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(helper_funcs_generated) @@ -383,7 +385,7 @@ tcg_funcs_generated = custom_target( 'tcg_funcs_generated.c.inc', output: 'tcg_funcs_generated.c.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_tcg_funcs.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(tcg_funcs_generated) @@ -392,7 +394,7 @@ analyze_funcs_generated = custom_target( 'analyze_funcs_generated.c.inc', output: 'analyze_funcs_generated.c.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_analyze_funcs.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(analyze_funcs_generated) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 1aa5b32b1f73..865e8ebb3cae 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -31,6 +31,7 @@ #include "mmvec/mmvec.h" #include "mmvec/macros.h" #include "op_helper.h" +#include "cpu_helper.h" #include "translate.h" #define SF_BIAS 127 @@ -1366,6 +1367,12 @@ uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg) { g_assert_not_reached(); } + +void HELPER(setprio)(CPUHexagonState *env, uint32_t thread, uint32_t prio) +{ + g_assert_not_reached(); +} + #endif From ebf9a5edc6a01c2bc659f578830d954426d018d9 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 12 Sep 2024 07:05:56 -0700 Subject: [PATCH 1080/1179] target/hexagon: Add implicit attributes to sysemu macros Signed-off-by: Brian Cain --- target/hexagon/hex_common.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index 4f40a1b3b24c..e2842fff3fcd 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -128,8 +128,13 @@ def calculate_attribs(): add_qemu_macro_attrib("fTRAP", "A_IMPLICIT_READS_PC") add_qemu_macro_attrib("fSET_OVERFLOW", "A_IMPLICIT_WRITES_USR") add_qemu_macro_attrib("fSET_LPCFG", "A_IMPLICIT_WRITES_USR") + add_qemu_macro_attrib("fLOAD_LOCKED", "A_LLSC") + add_qemu_macro_attrib("fSTORE_LOCKED", "A_LLSC") + add_qemu_macro_attrib("fCLEAR_RTE_EX", "A_IMPLICIT_WRITES_SSR") add_qemu_macro_attrib("fLOAD", "A_SCALAR_LOAD") add_qemu_macro_attrib("fSTORE", "A_SCALAR_STORE") + add_qemu_macro_attrib("fSET_K0_LOCK", "A_IMPLICIT_READS_PC") + add_qemu_macro_attrib("fSET_TLB_LOCK", "A_IMPLICIT_READS_PC") add_qemu_macro_attrib('fLSBNEW0', 'A_IMPLICIT_READS_P0') add_qemu_macro_attrib('fLSBNEW0NOT', 'A_IMPLICIT_READS_P0') add_qemu_macro_attrib('fREAD_P0', 'A_IMPLICIT_READS_P0') From e289130de4b1846be497459f592ce894c7991db4 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 24 Jul 2024 20:04:46 -0700 Subject: [PATCH 1081/1179] target/hexagon: Add TCG overrides for int handler insts Define TCG overrides for {c,}swi {c,s}iad, iassign{r,w}, {s,g}etimask instructions. Signed-off-by: Brian Cain --- target/hexagon/gen_tcg_sys.h | 25 ++++++++++++++++++++++ target/hexagon/helper.h | 8 ++++++++ target/hexagon/op_helper.c | 40 ++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/target/hexagon/gen_tcg_sys.h b/target/hexagon/gen_tcg_sys.h index 362703ab45e8..642ca3d3ff64 100644 --- a/target/hexagon/gen_tcg_sys.h +++ b/target/hexagon/gen_tcg_sys.h @@ -7,6 +7,31 @@ #ifndef HEXAGON_GEN_TCG_SYS_H #define HEXAGON_GEN_TCG_SYS_H +/* System mode instructions */ +#define fGEN_TCG_Y2_swi(SHORTCODE) \ + gen_helper_swi(tcg_env, RsV) + +#define fGEN_TCG_Y2_cswi(SHORTCODE) \ + gen_helper_cswi(tcg_env, RsV) + +#define fGEN_TCG_Y2_ciad(SHORTCODE) \ + gen_helper_ciad(tcg_env, RsV) + +#define fGEN_TCG_Y4_siad(SHORTCODE) \ + gen_helper_siad(tcg_env, RsV) + +#define fGEN_TCG_Y2_iassignw(SHORTCODE) \ + gen_helper_iassignw(tcg_env, RsV) + +#define fGEN_TCG_Y2_iassignr(SHORTCODE) \ + gen_helper_iassignr(RdV, tcg_env, RsV) + +#define fGEN_TCG_Y2_getimask(SHORTCODE) \ + gen_helper_getimask(RdV, tcg_env, RsV) + +#define fGEN_TCG_Y2_setimask(SHORTCODE) \ + gen_helper_setimask(tcg_env, PtV, RsV) + #define fGEN_TCG_Y2_setprio(SHORTCODE) \ gen_helper_setprio(tcg_env, PtV, RsV) diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index 146f4f02e415..2fe4440ddc6e 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -109,6 +109,14 @@ DEF_HELPER_2(probe_hvx_stores, void, env, int) DEF_HELPER_2(probe_pkt_scalar_hvx_stores, void, env, int) #if !defined(CONFIG_USER_ONLY) +DEF_HELPER_2(swi, void, env, i32) +DEF_HELPER_2(cswi, void, env, i32) +DEF_HELPER_2(ciad, void, env, i32) +DEF_HELPER_2(siad, void, env, i32) +DEF_HELPER_2(iassignw, void, env, i32) +DEF_HELPER_2(iassignr, i32, env, i32) +DEF_HELPER_2(getimask, i32, env, i32) +DEF_HELPER_3(setimask, void, env, i32, i32) DEF_HELPER_2(sreg_read, i32, env, i32) DEF_HELPER_2(sreg_read_pair, i64, env, i32) DEF_HELPER_2(greg_read, i32, env, i32) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 865e8ebb3cae..575f3fb1635f 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1337,6 +1337,46 @@ void HELPER(vwhist128qm)(CPUHexagonState *env, int32_t uiV) } #ifndef CONFIG_USER_ONLY +void HELPER(ciad)(CPUHexagonState *env, uint32_t mask) +{ + g_assert_not_reached(); +} + +void HELPER(siad)(CPUHexagonState *env, uint32_t mask) +{ + g_assert_not_reached(); +} + +void HELPER(swi)(CPUHexagonState *env, uint32_t mask) +{ + g_assert_not_reached(); +} + +void HELPER(cswi)(CPUHexagonState *env, uint32_t mask) +{ + g_assert_not_reached(); +} + +void HELPER(iassignw)(CPUHexagonState *env, uint32_t src) +{ + g_assert_not_reached(); +} + +uint32_t HELPER(iassignr)(CPUHexagonState *env, uint32_t src) +{ + g_assert_not_reached(); +} + +uint32_t HELPER(getimask)(CPUHexagonState *env, uint32_t tid) +{ + g_assert_not_reached(); +} + +void HELPER(setimask)(CPUHexagonState *env, uint32_t pred, uint32_t imask) +{ + g_assert_not_reached(); +} + void HELPER(sreg_write)(CPUHexagonState *env, uint32_t reg, uint32_t val) { g_assert_not_reached(); From 9f4f09cb5f0d6b502ccf86c276c0532293722116 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 24 Jul 2024 20:27:34 -0700 Subject: [PATCH 1082/1179] target/hexagon: Add TCG overrides for thread ctl Define TCG overrides for start, stop, wait, resume instructions. Signed-off-by: Brian Cain --- target/hexagon/gen_tcg_sys.h | 18 ++++++++++++++++++ target/hexagon/helper.h | 4 ++++ target/hexagon/op_helper.c | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/target/hexagon/gen_tcg_sys.h b/target/hexagon/gen_tcg_sys.h index 642ca3d3ff64..942d07b401ee 100644 --- a/target/hexagon/gen_tcg_sys.h +++ b/target/hexagon/gen_tcg_sys.h @@ -63,4 +63,22 @@ tcg_gen_extrh_i64_i32(ctx->t_sreg_new_value[HEX_SREG_SGP1], tmp); \ } while (0) +#define fGEN_TCG_Y2_wait(SHORTCODE) \ + do { \ + RsV = RsV; \ + gen_helper_wait(tcg_env, tcg_constant_tl(ctx->pkt->pc)); \ + } while (0) + +#define fGEN_TCG_Y2_resume(SHORTCODE) \ + gen_helper_resume(tcg_env, RsV) + +#define fGEN_TCG_Y2_start(SHORTCODE) \ + gen_helper_start(tcg_env, RsV) + +#define fGEN_TCG_Y2_stop(SHORTCODE) \ + do { \ + RsV = RsV; \ + gen_helper_stop(tcg_env); \ + } while (0) + #endif diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index 2fe4440ddc6e..ada520bd52ae 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -124,4 +124,8 @@ DEF_HELPER_2(greg_read_pair, i64, env, i32) DEF_HELPER_3(sreg_write, void, env, i32, i32) DEF_HELPER_3(sreg_write_pair, void, env, i32, i64) DEF_HELPER_3(setprio, void, env, i32, i32) +DEF_HELPER_2(start, void, env, i32) +DEF_HELPER_1(stop, void, env) +DEF_HELPER_2(wait, void, env, i32) +DEF_HELPER_2(resume, void, env, i32) #endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 575f3fb1635f..09a52843298a 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1367,6 +1367,26 @@ uint32_t HELPER(iassignr)(CPUHexagonState *env, uint32_t src) g_assert_not_reached(); } +void HELPER(start)(CPUHexagonState *env, uint32_t imask) +{ + g_assert_not_reached(); +} + +void HELPER(stop)(CPUHexagonState *env) +{ + g_assert_not_reached(); +} + +void HELPER(wait)(CPUHexagonState *env, target_ulong PC) +{ + g_assert_not_reached(); +} + +void HELPER(resume)(CPUHexagonState *env, uint32_t mask) +{ + g_assert_not_reached(); +} + uint32_t HELPER(getimask)(CPUHexagonState *env, uint32_t tid) { g_assert_not_reached(); From d3ba15217a3f3f19aaace94dc8c028dd38eb3c95 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 24 Jul 2024 20:34:02 -0700 Subject: [PATCH 1083/1179] target/hexagon: Add TCG overrides for rte, nmi Signed-off-by: Brian Cain --- target/hexagon/gen_tcg_sys.h | 19 +++++++++++++++++++ target/hexagon/helper.h | 1 + target/hexagon/op_helper.c | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/target/hexagon/gen_tcg_sys.h b/target/hexagon/gen_tcg_sys.h index 942d07b401ee..6d73a18db455 100644 --- a/target/hexagon/gen_tcg_sys.h +++ b/target/hexagon/gen_tcg_sys.h @@ -81,4 +81,23 @@ gen_helper_stop(tcg_env); \ } while (0) +/* + * rte (return from exception) + * Clear the EX bit in SSR + * Jump to ELR + */ +#define fGEN_TCG_J2_rte(SHORTCODE) \ + do { \ + TCGv new_ssr = tcg_temp_new(); \ + tcg_gen_deposit_tl(new_ssr, hex_t_sreg[HEX_SREG_SSR], \ + tcg_constant_tl(0), \ + reg_field_info[SSR_EX].offset, \ + reg_field_info[SSR_EX].width); \ + gen_log_sreg_write(ctx, HEX_SREG_SSR, new_ssr); \ + gen_jumpr(ctx, hex_t_sreg[HEX_SREG_ELR]); \ + } while (0) + +#define fGEN_TCG_Y4_nmi(SHORTCODE) \ + gen_helper_nmi(tcg_env, RsV) + #endif diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index ada520bd52ae..730eaf8b9a0f 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -128,4 +128,5 @@ DEF_HELPER_2(start, void, env, i32) DEF_HELPER_1(stop, void, env) DEF_HELPER_2(wait, void, env, i32) DEF_HELPER_2(resume, void, env, i32) +DEF_HELPER_2(nmi, void, env, i32) #endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 09a52843298a..139a0b5ab27c 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1433,6 +1433,10 @@ void HELPER(setprio)(CPUHexagonState *env, uint32_t thread, uint32_t prio) g_assert_not_reached(); } +void HELPER(nmi)(CPUHexagonState *env, uint32_t thread_mask) +{ + g_assert_not_reached(); +} #endif From 37cdda1bb673fac40a95ecf1ce6208ebad8fc056 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 25 Jul 2024 17:17:14 -0700 Subject: [PATCH 1084/1179] target/hexagon: Add sreg_{read,write} helpers Co-authored-by: Sid Manning Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 1 + target/hexagon/cpu_helper.c | 37 ++++++++++++ target/hexagon/cpu_helper.h | 8 +++ target/hexagon/op_helper.c | 114 ++++++++++++++++++++++++++++++++++-- 4 files changed, 156 insertions(+), 4 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 6875ef0d8ef5..95bd3d2a51c1 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -317,6 +317,7 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) qemu_init_vcpu(cs); cpu_reset(cs); #ifndef CONFIG_USER_ONLY + CPUHexagonState *env = cpu_env(cs); if (cs->cpu_index == 0) { env->g_sreg = g_new0(target_ulong, NUM_SREGS); } else { diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index ef39ebf8daef..46aa172fab31 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -28,10 +28,47 @@ #ifndef CONFIG_USER_ONLY +uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index) +{ + g_assert_not_reached(); +} + uint32_t arch_get_system_reg(CPUHexagonState *env, uint32_t reg) { g_assert_not_reached(); } +uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env) +{ + g_assert_not_reached(); +} + +uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env) +{ + g_assert_not_reached(); +} + +uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env) +{ + g_assert_not_reached(); +} + +void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, + uint32_t cycles_hi) +{ + g_assert_not_reached(); +} + +void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, + uint32_t cycles_lo) +{ + g_assert_not_reached(); +} + +void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t cycles) +{ + g_assert_not_reached(); +} + #endif diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index 194bcbf45137..5f5f15149ab7 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -7,6 +7,14 @@ #ifndef HEXAGON_CPU_HELPER_H #define HEXAGON_CPU_HELPER_H +uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index); +uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env); +uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env); +uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env); +void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t); +void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t); +void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t); + static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) { diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 139a0b5ab27c..76b2475d880a 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" +#include "qemu/main-loop.h" #include "exec/exec-all.h" #include "exec/cpu_ldst.h" #include "exec/helper-proto.h" @@ -1397,25 +1398,130 @@ void HELPER(setimask)(CPUHexagonState *env, uint32_t pred, uint32_t imask) g_assert_not_reached(); } -void HELPER(sreg_write)(CPUHexagonState *env, uint32_t reg, uint32_t val) +static bool handle_pmu_sreg_write(CPUHexagonState *env, uint32_t reg, + uint32_t val) +{ + if (reg == HEX_SREG_PMUSTID0 || reg == HEX_SREG_PMUSTID1 + || reg == HEX_SREG_PMUCFG || reg == HEX_SREG_PMUEVTCFG + || reg == HEX_SREG_PMUEVTCFG1 + || (reg >= HEX_SREG_PMUCNT4 && reg <= HEX_SREG_PMUCNT3)) { + qemu_log_mask(LOG_UNIMP, "PMU registers not yet implemented"); + return true; + } + return false; +} + +static void modify_syscfg(CPUHexagonState *env, uint32_t val) { g_assert_not_reached(); } -void HELPER(sreg_write_pair)(CPUHexagonState *env, uint32_t reg, uint64_t val) +static void hexagon_set_vid(CPUHexagonState *env, uint32_t offset, int val) +{ + g_assert_not_reached(); +} +static uint32_t hexagon_find_last_irq(CPUHexagonState *env, uint32_t vid) { g_assert_not_reached(); } +static void hexagon_read_timer(CPUHexagonState *env, uint32_t *low, + uint32_t *high) +{ + qemu_log_mask(LOG_UNIMP, "reading timer_hi/lo not yet supported\n"); +} + +static inline QEMU_ALWAYS_INLINE void sreg_write(CPUHexagonState *env, + uint32_t reg, uint32_t val) + +{ + g_assert(bql_locked()); + if ((reg == HEX_SREG_VID) || (reg == HEX_SREG_VID1)) { + hexagon_set_vid(env, (reg == HEX_SREG_VID) ? L2VIC_VID_0 : L2VIC_VID_1, + val); + arch_set_system_reg(env, reg, val); + } else if (reg == HEX_SREG_SYSCFG) { + modify_syscfg(env, val); + } else if (reg == HEX_SREG_IMASK) { + val = GET_FIELD(IMASK_MASK, val); + arch_set_system_reg(env, reg, val); + } else if (reg == HEX_SREG_PCYCLELO) { + hexagon_set_sys_pcycle_count_low(env, val); + } else if (reg == HEX_SREG_PCYCLEHI) { + hexagon_set_sys_pcycle_count_high(env, val); + } else if (!handle_pmu_sreg_write(env, reg, val)) { + if (reg >= HEX_SREG_GLB_START) { + arch_set_system_reg(env, reg, val); + } else { + arch_set_system_reg(env, reg, val); + } + } +} + +void HELPER(sreg_write)(CPUHexagonState *env, uint32_t reg, uint32_t val) +{ + BQL_LOCK_GUARD(); + sreg_write(env, reg, val); +} + +void HELPER(sreg_write_pair)(CPUHexagonState *env, uint32_t reg, uint64_t val) +{ + BQL_LOCK_GUARD(); + sreg_write(env, reg, val & 0xFFFFFFFF); + sreg_write(env, reg + 1, val >> 32); +} + +static inline QEMU_ALWAYS_INLINE uint32_t sreg_read(CPUHexagonState *env, + uint32_t reg) +{ + g_assert(bql_locked()); + if (reg == HEX_SREG_PMUSTID0 || reg == HEX_SREG_PMUSTID1 + || reg == HEX_SREG_PMUCFG || reg == HEX_SREG_PMUEVTCFG + || reg == HEX_SREG_PMUEVTCFG1 + || (reg >= HEX_SREG_PMUCNT4 && reg <= HEX_SREG_PMUCNT3)) { + qemu_log_mask(LOG_UNIMP, "PMU registers not yet implemented"); + return 0; + } + if ((reg == HEX_SREG_VID) || (reg == HEX_SREG_VID1)) { + const uint32_t vid = hexagon_find_last_irq(env, reg); + arch_set_system_reg(env, reg, vid); + } else if ((reg == HEX_SREG_TIMERLO) || (reg == HEX_SREG_TIMERHI)) { + uint32_t low = 0; + uint32_t high = 0; + hexagon_read_timer(env, &low, &high); + arch_set_system_reg(env, HEX_SREG_TIMERLO, low); + arch_set_system_reg(env, HEX_SREG_TIMERHI, high); + } else if (reg == HEX_SREG_BADVA) { + target_ulong ssr = arch_get_system_reg(env, HEX_SREG_SSR); + if (GET_SSR_FIELD(SSR_BVS, ssr)) { + return arch_get_system_reg(env, HEX_SREG_BADVA1); + } + return arch_get_system_reg(env, HEX_SREG_BADVA0); + } + return arch_get_system_reg(env, reg); +} + uint32_t HELPER(sreg_read)(CPUHexagonState *env, uint32_t reg) { - g_assert_not_reached(); + BQL_LOCK_GUARD(); + return sreg_read(env, reg); } uint64_t HELPER(sreg_read_pair)(CPUHexagonState *env, uint32_t reg) { - g_assert_not_reached(); + BQL_LOCK_GUARD(); + if (reg == HEX_SREG_TIMERLO) { + uint32_t low = 0; + uint32_t high = 0; + hexagon_read_timer(env, &low, &high); + arch_set_system_reg(env, HEX_SREG_TIMERLO, low); + arch_set_system_reg(env, HEX_SREG_TIMERHI, high); + } else if (reg == HEX_SREG_PCYCLELO) { + return hexagon_get_sys_pcycle_count(env); + } + return (uint64_t)sreg_read(env, reg) | + (((uint64_t)sreg_read(env, reg + 1)) << 32); } uint32_t HELPER(greg_read)(CPUHexagonState *env, uint32_t reg) From 17e8de02e303d1d33f4e3bc37e65de4344b1631e Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 9 Aug 2024 16:28:08 -0700 Subject: [PATCH 1085/1179] target/hexagon: Initialize htid, modectl regs Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 95bd3d2a51c1..f65848ada0fc 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -26,6 +26,7 @@ #include "fpu/softfloat-helpers.h" #include "tcg/tcg.h" #include "exec/gdbstub.h" +#include "cpu_helper.h" static void hexagon_v66_cpu_init(Object *obj) { } static void hexagon_v67_cpu_init(Object *obj) { } @@ -284,11 +285,18 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) set_float_default_nan_pattern(0b11111111, &env->fp_status); #ifndef CONFIG_USER_ONLY + HexagonCPU *cpu = HEXAGON_CPU(cs); + if (cs->cpu_index == 0) { memset(env->g_sreg, 0, sizeof(target_ulong) * NUM_SREGS); } memset(env->t_sreg, 0, sizeof(target_ulong) * NUM_SREGS); memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS); + + if (cs->cpu_index == 0) { + arch_set_system_reg(env, HEX_SREG_MODECTL, 0x1); + } + arch_set_system_reg(env, HEX_SREG_HTID, cs->cpu_index); #endif } From ffa17bf4dc2608f160163ff38f062a8bc6753e3a Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 9 Aug 2024 19:24:45 -0700 Subject: [PATCH 1086/1179] target/hexagon: Add locks, id, next_PC to state Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 6 ++++++ target/hexagon/cpu.h | 37 +++++++++++++++++++++++++++++++++++-- target/hexagon/machine.c | 4 ++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index f65848ada0fc..322e1c8cd1d8 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -297,6 +297,12 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) arch_set_system_reg(env, HEX_SREG_MODECTL, 0x1); } arch_set_system_reg(env, HEX_SREG_HTID, cs->cpu_index); + memset(env->t_sreg, 0, sizeof(target_ulong) * NUM_SREGS); + memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS); + env->threadId = cs->cpu_index; + env->tlb_lock_state = HEX_LOCK_UNLOCKED; + env->k0_lock_state = HEX_LOCK_UNLOCKED; + env->next_PC = 0; #endif } diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 5dde4f8e880c..aeb17d80ae54 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -44,9 +44,36 @@ #define PRED_WRITES_MAX 5 /* 4 insns + endloop */ #define VSTORES_MAX 2 -#define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU +#ifndef CONFIG_USER_ONLY +#define CPU_INTERRUPT_SWI CPU_INTERRUPT_TGT_INT_0 +#define CPU_INTERRUPT_K0_UNLOCK CPU_INTERRUPT_TGT_INT_1 +#define CPU_INTERRUPT_TLB_UNLOCK CPU_INTERRUPT_TGT_INT_2 + +#define HEX_CPU_MODE_USER 1 +#define HEX_CPU_MODE_GUEST 2 +#define HEX_CPU_MODE_MONITOR 3 + +#define HEX_EXE_MODE_OFF 1 +#define HEX_EXE_MODE_RUN 2 +#define HEX_EXE_MODE_WAIT 3 +#define HEX_EXE_MODE_DEBUG 4 +#endif -#define MMU_USER_IDX 0 +#define MMU_USER_IDX 0 +#ifndef CONFIG_USER_ONLY +#define MMU_GUEST_IDX 1 +#define MMU_KERNEL_IDX 2 + +typedef enum { + HEX_LOCK_UNLOCKED = 0, + HEX_LOCK_WAITING = 1, + HEX_LOCK_OWNER = 2, + HEX_LOCK_QUEUED = 3 +} hex_lock_state_t; +#endif + + +#define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU typedef struct { target_ulong va; @@ -93,6 +120,12 @@ typedef struct CPUArchState { target_ulong *g_sreg; target_ulong greg[NUM_GREGS]; + + /* This alias of CPUState.cpu_index is used by imported sources: */ + target_ulong threadId; + hex_lock_state_t tlb_lock_state; + hex_lock_state_t k0_lock_state; + target_ulong next_PC; #endif target_ulong new_value_usr; diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c index d9d71edf7718..dc900422f4c6 100644 --- a/target/hexagon/machine.c +++ b/target/hexagon/machine.c @@ -19,6 +19,10 @@ const VMStateDescription vmstate_hexagon_cpu = { VMSTATE_UINTTL_ARRAY(env.pred, HexagonCPU, NUM_PREGS), VMSTATE_UINTTL_ARRAY(env.t_sreg, HexagonCPU, NUM_SREGS), VMSTATE_UINTTL_ARRAY(env.greg, HexagonCPU, NUM_GREGS), + VMSTATE_UINTTL(env.next_PC, HexagonCPU), + VMSTATE_UINTTL(env.tlb_lock_state, HexagonCPU), + VMSTATE_UINTTL(env.k0_lock_state, HexagonCPU), + VMSTATE_UINTTL(env.threadId, HexagonCPU), VMSTATE_END_OF_LIST() }, }; From 931df7e2660890d50963e42b0d09e4964cb8f5f9 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 9 Aug 2024 19:29:54 -0700 Subject: [PATCH 1087/1179] target/hexagon: Add a TLB count property Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 4 ++++ target/hexagon/cpu.h | 1 + target/hexagon/max.h | 26 ++++++++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 target/hexagon/max.h diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 322e1c8cd1d8..48ead09baca9 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -27,6 +27,7 @@ #include "tcg/tcg.h" #include "exec/gdbstub.h" #include "cpu_helper.h" +#include "max.h" static void hexagon_v66_cpu_init(Object *obj) { } static void hexagon_v67_cpu_init(Object *obj) { } @@ -51,6 +52,9 @@ static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model) } static const Property hexagon_cpu_properties[] = { +#if !defined(CONFIG_USER_ONLY) + DEFINE_PROP_UINT32("jtlb-entries", HexagonCPU, num_tlbs, MAX_TLB_ENTRIES), +#endif DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, qdev_prop_uint32, target_ulong), diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index aeb17d80ae54..0113152e881b 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -172,6 +172,7 @@ struct ArchCPU { bool lldb_compat; target_ulong lldb_stack_adjust; bool short_circuit; + uint32_t num_tlbs; }; #include "cpu_bits.h" diff --git a/target/hexagon/max.h b/target/hexagon/max.h new file mode 100644 index 000000000000..0f595bcb736d --- /dev/null +++ b/target/hexagon/max.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_MAX_H +#define HEXAGON_MAX_H + +#define MAX_EXT_CONTEXTS 8 +#define MAX_L2_INTERLEAVES 2 +#define MAX_VFIFO_COUNT 4 + +#define SLOTS_MAX 4 + +#define REG_WRITES_MAX 32 +#define PRED_WRITES_MAX 5 +#define STORES_MAX 2 +#define LOADS_MAX 2 +#define MAX_PRED 4 + +#define PACKET_BYTES_MAX 16 +#define MAX_TLB_ENTRIES 1024 +#define DTLB_ENTRIES 16 +#define ITLB_ENTRIES 16 + +#endif /* HEXAGON_MAX_H */ From c5fd25dd489b50afc62b3c0de5297b772cd604db Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 16 Aug 2024 09:03:28 -0700 Subject: [PATCH 1088/1179] target/hexagon: Add {TLB,k0}lock, cause code, wait_next_pc {TLB,k0}lock counts are used to represent the TLB, k0 locks among hardware threads. wait_next_pc represents the program counter to set when resuming from a wait-for-interrupts state. cause_code contains the precise exception cause.This will be used by subsequent commits. Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 4 ++++ target/hexagon/cpu.h | 4 ++++ target/hexagon/machine.c | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 48ead09baca9..c94d4fd4f5f1 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -306,7 +306,11 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) env->threadId = cs->cpu_index; env->tlb_lock_state = HEX_LOCK_UNLOCKED; env->k0_lock_state = HEX_LOCK_UNLOCKED; + env->tlb_lock_count = 0; + env->k0_lock_count = 0; env->next_PC = 0; + env->wait_next_pc = 0; + env->cause_code = -1; #endif } diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 0113152e881b..0dd26cfbb184 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -107,6 +107,7 @@ typedef struct { typedef struct CPUArchState { target_ulong gpr[TOTAL_PER_THREAD_REGS]; target_ulong pred[NUM_PREGS]; + target_ulong cause_code; /* For comparing with LLDB on target - see adjust_stack_ptrs function */ target_ulong last_pc_dumped; @@ -120,11 +121,14 @@ typedef struct CPUArchState { target_ulong *g_sreg; target_ulong greg[NUM_GREGS]; + target_ulong wait_next_pc; /* This alias of CPUState.cpu_index is used by imported sources: */ target_ulong threadId; hex_lock_state_t tlb_lock_state; hex_lock_state_t k0_lock_state; + target_ulong tlb_lock_count; + target_ulong k0_lock_count; target_ulong next_PC; #endif target_ulong new_value_usr; diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c index dc900422f4c6..9fdafb4573dd 100644 --- a/target/hexagon/machine.c +++ b/target/hexagon/machine.c @@ -22,7 +22,11 @@ const VMStateDescription vmstate_hexagon_cpu = { VMSTATE_UINTTL(env.next_PC, HexagonCPU), VMSTATE_UINTTL(env.tlb_lock_state, HexagonCPU), VMSTATE_UINTTL(env.k0_lock_state, HexagonCPU), + VMSTATE_UINTTL(env.tlb_lock_count, HexagonCPU), + VMSTATE_UINTTL(env.k0_lock_count, HexagonCPU), VMSTATE_UINTTL(env.threadId, HexagonCPU), + VMSTATE_UINTTL(env.cause_code, HexagonCPU), + VMSTATE_UINTTL(env.wait_next_pc, HexagonCPU), VMSTATE_END_OF_LIST() }, }; From 73708691c1925ee1c9140d287fc22321bc045969 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 16 Aug 2024 09:03:59 -0700 Subject: [PATCH 1089/1179] target/hexagon: Add stubs for modify_ssr/get_exe_mode Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 8 ++++++++ target/hexagon/cpu_helper.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index 46aa172fab31..3297882a6aa5 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -70,5 +70,13 @@ void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t cycles) g_assert_not_reached(); } +void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old) +{ + g_assert_not_reached(); +} +int get_exe_mode(CPUHexagonState *env) +{ + g_assert_not_reached(); +} #endif diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index 5f5f15149ab7..e0c0c037a6ea 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -14,6 +14,8 @@ uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env); void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t); void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t); void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t); +void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old); +int get_exe_mode(CPUHexagonState *env); static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) From 49c7e0512574c588788e535ff4bc51d356c93bbd Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 26 Aug 2024 20:44:28 -0700 Subject: [PATCH 1090/1179] target/hexagon: Add gdb support for sys regs Co-authored-by: Matheus Tavares Bernardino Signed-off-by: Brian Cain --- gdb-xml/hexagon-sys.xml | 116 +++++++++++++++++++++++++++++++++++++ target/hexagon/cpu.c | 17 ++++++ target/hexagon/cpu.h | 6 ++ target/hexagon/gdbstub.c | 45 ++++++++++++++ target/hexagon/internal.h | 4 ++ target/hexagon/op_helper.c | 16 +++++ 6 files changed, 204 insertions(+) create mode 100644 gdb-xml/hexagon-sys.xml diff --git a/gdb-xml/hexagon-sys.xml b/gdb-xml/hexagon-sys.xml new file mode 100644 index 000000000000..1d9c21172253 --- /dev/null +++ b/gdb-xml/hexagon-sys.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index c94d4fd4f5f1..7e06033eac54 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -29,6 +29,10 @@ #include "cpu_helper.h" #include "max.h" +#ifndef CONFIG_USER_ONLY +#include "sys_macros.h" +#endif + static void hexagon_v66_cpu_init(Object *obj) { } static void hexagon_v67_cpu_init(Object *obj) { } static void hexagon_v68_cpu_init(Object *obj) { } @@ -336,6 +340,12 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) hexagon_hvx_gdb_write_register, gdb_find_static_feature("hexagon-hvx.xml"), 0); +#ifndef CONFIG_USER_ONLY + gdb_register_coprocessor(cs, hexagon_sys_gdb_read_register, + hexagon_sys_gdb_write_register, + gdb_find_static_feature("hexagon-sys.xml"), 0); +#endif + qemu_init_vcpu(cs); cpu_reset(cs); #ifndef CONFIG_USER_ONLY @@ -394,6 +404,13 @@ static void hexagon_cpu_class_init(ObjectClass *c, void *data) cc->tcg_ops = &hexagon_tcg_ops; } +#ifndef CONFIG_USER_ONLY +uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg) +{ + g_assert_not_reached(); +} +#endif + #define DEFINE_CPU(type_name, initfn) \ { \ .name = type_name, \ diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 0dd26cfbb184..84de8226f66a 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -187,6 +187,12 @@ G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t exception, uintptr_t pc); +#ifndef CONFIG_USER_ONLY +uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg); +uint32_t hexagon_sreg_read(CPUHexagonState *env, uint32_t reg); +void hexagon_gdb_sreg_write(CPUHexagonState *env, uint32_t reg, uint32_t val); +#endif + static inline void cpu_get_tb_cpu_state(CPUHexagonState *env, vaddr *pc, uint64_t *cs_base, uint32_t *flags) { diff --git a/target/hexagon/gdbstub.c b/target/hexagon/gdbstub.c index 12d6b3bbcbb1..8476199b753e 100644 --- a/target/hexagon/gdbstub.c +++ b/target/hexagon/gdbstub.c @@ -76,6 +76,51 @@ int hexagon_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) g_assert_not_reached(); } +#ifndef CONFIG_USER_ONLY +int hexagon_sys_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) +{ + CPUHexagonState *env = cpu_env(cs); + + if (n < NUM_SREGS) { + return gdb_get_regl(mem_buf, hexagon_sreg_read(env, n)); + } + n -= NUM_SREGS; + + if (n < NUM_GREGS) { + return gdb_get_regl(mem_buf, hexagon_greg_read(env, n)); + } + n -= NUM_GREGS; + + n -= TOTAL_PER_THREAD_REGS; + + if (n < NUM_PREGS) { + env->pred[n] = ldtul_p(mem_buf) & 0xff; + return sizeof(uint8_t); + } + + n -= NUM_PREGS; + + g_assert_not_reached(); +} + +int hexagon_sys_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) +{ + CPUHexagonState *env = cpu_env(cs); + + if (n < NUM_SREGS) { + hexagon_gdb_sreg_write(env, n, ldtul_p(mem_buf)); + return sizeof(target_ulong); + } + n -= NUM_SREGS; + + if (n < NUM_GREGS) { + return env->greg[n] = ldtul_p(mem_buf); + } + n -= NUM_GREGS; + + g_assert_not_reached(); +} +#endif static int gdb_get_vreg(CPUHexagonState *env, GByteArray *mem_buf, int n) { int total = 0; diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h index 7cf7bcaa6cd8..c24c36092161 100644 --- a/target/hexagon/internal.h +++ b/target/hexagon/internal.h @@ -22,6 +22,10 @@ int hexagon_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); int hexagon_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg); +#ifndef CONFIG_USER_ONLY +int hexagon_sys_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n); +int hexagon_sys_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n); +#endif int hexagon_hvx_gdb_read_register(CPUState *env, GByteArray *mem_buf, int n); int hexagon_hvx_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n); diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 76b2475d880a..fd9caafefc49 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1465,6 +1465,17 @@ void HELPER(sreg_write)(CPUHexagonState *env, uint32_t reg, uint32_t val) sreg_write(env, reg, val); } +void hexagon_gdb_sreg_write(CPUHexagonState *env, uint32_t reg, uint32_t val) +{ + BQL_LOCK_GUARD(); + sreg_write(env, reg, val); + /* + * The above is needed to run special logic for regs like syscfg, but it + * won't set read-only bits. This will: + */ + arch_set_system_reg(env, reg, val); +} + void HELPER(sreg_write_pair)(CPUHexagonState *env, uint32_t reg, uint64_t val) { BQL_LOCK_GUARD(); @@ -1508,6 +1519,11 @@ uint32_t HELPER(sreg_read)(CPUHexagonState *env, uint32_t reg) return sreg_read(env, reg); } +uint32_t hexagon_sreg_read(CPUHexagonState *env, uint32_t reg) +{ + return sreg_read(env, reg); +} + uint64_t HELPER(sreg_read_pair)(CPUHexagonState *env, uint32_t reg) { BQL_LOCK_GUARD(); From 93c44349e37ddb0246ef0a9de708f35decba0898 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 26 Aug 2024 13:44:55 -0700 Subject: [PATCH 1091/1179] target/hexagon: Add initial MMU model Co-authored-by: Taylor Simpson Co-authored-by: Michael Lambert Co-authored-by: Sid Manning Co-authored-by: Matheus Tavares Bernardino Signed-off-by: Brian Cain --- target/hexagon/cpu-param.h | 4 + target/hexagon/cpu.c | 27 +- target/hexagon/cpu.h | 13 + target/hexagon/hex_mmu.c | 529 +++++++++++++++++++++++++++++++++++++ target/hexagon/hex_mmu.h | 30 +++ target/hexagon/internal.h | 3 + target/hexagon/machine.c | 30 +++ target/hexagon/meson.build | 3 +- target/hexagon/translate.c | 2 +- 9 files changed, 638 insertions(+), 3 deletions(-) create mode 100644 target/hexagon/hex_mmu.c create mode 100644 target/hexagon/hex_mmu.h diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h index ccaf6a9d28d6..d414ca89d690 100644 --- a/target/hexagon/cpu-param.h +++ b/target/hexagon/cpu-param.h @@ -18,7 +18,11 @@ #ifndef HEXAGON_CPU_PARAM_H #define HEXAGON_CPU_PARAM_H +#ifdef CONFIG_USER_ONLY #define TARGET_PAGE_BITS 16 /* 64K pages */ +#else +#define TARGET_PAGE_BITS 12 /* 4K pages */ +#endif #define TARGET_PHYS_ADDR_SPACE_BITS 36 #define TARGET_VIRT_ADDR_SPACE_BITS 32 diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 7e06033eac54..25d8e8f387e6 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -28,6 +28,7 @@ #include "exec/gdbstub.h" #include "cpu_helper.h" #include "max.h" +#include "hex_mmu.h" #ifndef CONFIG_USER_ONLY #include "sys_macros.h" @@ -277,6 +278,18 @@ static void hexagon_restore_state_to_opc(CPUState *cs, cpu_env(cs)->gpr[HEX_REG_PC] = data[0]; } + +#ifndef CONFIG_USER_ONLY +static void mmu_reset(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + if (cs->cpu_index == 0) { + memset(env->hex_tlb, 0, sizeof(*env->hex_tlb)); + } +} +#endif + + static void hexagon_cpu_reset_hold(Object *obj, ResetType type) { CPUState *cs = CPU(obj); @@ -304,6 +317,7 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) if (cs->cpu_index == 0) { arch_set_system_reg(env, HEX_SREG_MODECTL, 0x1); } + mmu_reset(env); arch_set_system_reg(env, HEX_SREG_HTID, cs->cpu_index); memset(env->t_sreg, 0, sizeof(target_ulong) * NUM_SREGS); memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS); @@ -336,6 +350,14 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) return; } +#ifndef CONFIG_USER_ONLY + HexagonCPU *cpu = HEXAGON_CPU(cs); + if (cpu->num_tlbs > MAX_TLB_ENTRIES) { + error_setg(errp, "Number of TLBs selected is invalid"); + return; + } +#endif + gdb_register_coprocessor(cs, hexagon_hvx_gdb_read_register, hexagon_hvx_gdb_write_register, gdb_find_static_feature("hexagon-hvx.xml"), 0); @@ -347,9 +369,12 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) #endif qemu_init_vcpu(cs); - cpu_reset(cs); #ifndef CONFIG_USER_ONLY CPUHexagonState *env = cpu_env(cs); + hex_mmu_realize(env); +#endif + cpu_reset(cs); +#ifndef CONFIG_USER_ONLY if (cs->cpu_index == 0) { env->g_sreg = g_new0(target_ulong, NUM_SREGS); } else { diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 84de8226f66a..0811ca00a8ae 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -35,6 +35,8 @@ #error "Hexagon does not support system emulation" #endif +typedef struct CPUHexagonTLBContext CPUHexagonTLBContext; + #define NUM_PREGS 4 #define TOTAL_PER_THREAD_REGS 64 @@ -130,6 +132,7 @@ typedef struct CPUArchState { target_ulong tlb_lock_count; target_ulong k0_lock_count; target_ulong next_PC; + CPUHexagonTLBContext *hex_tlb; #endif target_ulong new_value_usr; @@ -176,12 +179,15 @@ struct ArchCPU { bool lldb_compat; target_ulong lldb_stack_adjust; bool short_circuit; +#ifndef CONFIG_USER_ONLY uint32_t num_tlbs; +#endif }; #include "cpu_bits.h" FIELD(TB_FLAGS, IS_TIGHT_LOOP, 0, 1) +FIELD(TB_FLAGS, MMU_INDEX, 1, 3) G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t exception, @@ -193,6 +199,7 @@ uint32_t hexagon_sreg_read(CPUHexagonState *env, uint32_t reg); void hexagon_gdb_sreg_write(CPUHexagonState *env, uint32_t reg, uint32_t val); #endif +#include "exec/cpu-all.h" static inline void cpu_get_tb_cpu_state(CPUHexagonState *env, vaddr *pc, uint64_t *cs_base, uint32_t *flags) { @@ -206,6 +213,12 @@ static inline void cpu_get_tb_cpu_state(CPUHexagonState *env, vaddr *pc, if (*pc & PCALIGN_MASK) { hexagon_raise_exception_err(env, HEX_CAUSE_PC_NOT_ALIGNED, 0); } +#ifndef CONFIG_USER_ONLY + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, + cpu_mmu_index(env_cpu(env), false)); +#else + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, MMU_USER_IDX); +#endif } typedef HexagonCPU ArchCPU; diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c new file mode 100644 index 000000000000..94330ac194d8 --- /dev/null +++ b/target/hexagon/hex_mmu.c @@ -0,0 +1,529 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/main-loop.h" +#include "qemu/qemu-print.h" +#include "cpu.h" +#include "system/cpus.h" +#include "internal.h" +#include "exec/exec-all.h" +#include "exec/cputlb.h" +#include "hex_mmu.h" +#include "macros.h" +#include "sys_macros.h" +#include "reg_fields.h" + +#define GET_TLB_FIELD(ENTRY, FIELD) \ + ((uint64_t)fEXTRACTU_BITS(ENTRY, reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset)) + +/* PPD (physical page descriptor) */ +static inline uint64_t GET_PPD(uint64_t entry) +{ + return GET_TLB_FIELD(entry, PTE_PPD) | + (GET_TLB_FIELD(entry, PTE_PA35) << reg_field_info[PTE_PPD].width); +} + +#define NO_ASID (1 << 8) + +typedef enum { + PGSIZE_4K, + PGSIZE_16K, + PGSIZE_64K, + PGSIZE_256K, + PGSIZE_1M, + PGSIZE_4M, + PGSIZE_16M, + PGSIZE_64M, + PGSIZE_256M, + PGSIZE_1G, + NUM_PGSIZE_TYPES +} tlb_pgsize_t; + +static const char *pgsize_str[NUM_PGSIZE_TYPES] = { + "4K", + "16K", + "64K", + "256K", + "1M", + "4M", + "16M", + "64M", + "256M", + "1G", +}; + +#define INVALID_MASK 0xffffffffLL + +static const uint64_t encmask_2_mask[] = { + 0x0fffLL, /* 4k, 0000 */ + 0x3fffLL, /* 16k, 0001 */ + 0xffffLL, /* 64k, 0010 */ + 0x3ffffLL, /* 256k, 0011 */ + 0xfffffLL, /* 1m, 0100 */ + 0x3fffffLL, /* 4m, 0101 */ + 0xffffffLL, /* 16m, 0110 */ + 0x3ffffffLL, /* 64m, 0111 */ + 0xfffffffLL, /* 256m, 1000 */ + 0x3fffffffLL, /* 1g, 1001 */ + INVALID_MASK, /* RSVD, 0111 */ +}; + +/* + * @return the page size type from @a entry. + */ +static inline tlb_pgsize_t hex_tlb_pgsize_type(uint64_t entry) +{ + if (entry == 0) { + qemu_log_mask(CPU_LOG_MMU, "%s: Supplied TLB entry was 0!\n", __func__); + return 0; + } + tlb_pgsize_t size = ctz64(entry); + g_assert(size < NUM_PGSIZE_TYPES); + return size; +} + +/* + * @return the page size of @a entry, in bytes. + */ +static inline uint64_t hex_tlb_page_size_bytes(uint64_t entry) +{ + return 1ull << (TARGET_PAGE_BITS + 2 * hex_tlb_pgsize_type(entry)); +} + +static inline uint64_t hex_tlb_phys_page_num(uint64_t entry) +{ + uint32_t ppd = GET_PPD(entry); + return ppd >> 1; +} + +static inline uint64_t hex_tlb_phys_addr(uint64_t entry) +{ + uint64_t pagemask = encmask_2_mask[hex_tlb_pgsize_type(entry)]; + uint64_t pagenum = hex_tlb_phys_page_num(entry); + uint64_t PA = (pagenum << TARGET_PAGE_BITS) & (~pagemask); + return PA; +} + +static inline uint64_t hex_tlb_virt_addr(uint64_t entry) +{ + return (uint64_t)GET_TLB_FIELD(entry, PTE_VPN) << TARGET_PAGE_BITS; +} + +static bool hex_dump_mmu_entry(FILE *f, uint64_t entry) +{ + if (GET_TLB_FIELD(entry, PTE_V)) { + fprintf(f, "0x%016" PRIx64 ": ", entry); + uint64_t PA = hex_tlb_phys_addr(entry); + uint64_t VA = hex_tlb_virt_addr(entry); + fprintf(f, "V:%" PRId64 " G:%" PRId64 " A1:%" PRId64 " A0:%" PRId64, + GET_TLB_FIELD(entry, PTE_V), GET_TLB_FIELD(entry, PTE_G), + GET_TLB_FIELD(entry, PTE_ATR1), GET_TLB_FIELD(entry, PTE_ATR0)); + fprintf(f, " ASID:0x%02" PRIx64 " VA:0x%08" PRIx64, + GET_TLB_FIELD(entry, PTE_ASID), VA); + fprintf(f, + " X:%" PRId64 " W:%" PRId64 " R:%" PRId64 " U:%" PRId64 + " C:%" PRId64, + GET_TLB_FIELD(entry, PTE_X), GET_TLB_FIELD(entry, PTE_W), + GET_TLB_FIELD(entry, PTE_R), GET_TLB_FIELD(entry, PTE_U), + GET_TLB_FIELD(entry, PTE_C)); + fprintf(f, " PA:0x%09" PRIx64 " SZ:%s (0x%" PRIx64 ")", PA, + pgsize_str[hex_tlb_pgsize_type(entry)], + hex_tlb_page_size_bytes(entry)); + fprintf(f, "\n"); + return true; + } + + /* Not valid */ + return false; +} + +void dump_mmu(CPUHexagonState *env) +{ + int i; + + HexagonCPU *cpu = env_archcpu(env); + for (i = 0; i < cpu->num_tlbs; i++) { + uint64_t entry = env->hex_tlb->entries[i]; + if (GET_TLB_FIELD(entry, PTE_V)) { + qemu_printf("0x%016" PRIx64 ": ", entry); + uint64_t PA = hex_tlb_phys_addr(entry); + uint64_t VA = hex_tlb_virt_addr(entry); + qemu_printf( + "V:%" PRId64 " G:%" PRId64 " A1:%" PRId64 " A0:%" PRId64, + GET_TLB_FIELD(entry, PTE_V), GET_TLB_FIELD(entry, PTE_G), + GET_TLB_FIELD(entry, PTE_ATR1), GET_TLB_FIELD(entry, PTE_ATR0)); + qemu_printf(" ASID:0x%02" PRIx64 " VA:0x%08" PRIx64, + GET_TLB_FIELD(entry, PTE_ASID), VA); + qemu_printf( + " X:%" PRId64 " W:%" PRId64 " R:%" PRId64 " U:%" PRId64 + " C:%" PRId64, + GET_TLB_FIELD(entry, PTE_X), GET_TLB_FIELD(entry, PTE_W), + GET_TLB_FIELD(entry, PTE_R), GET_TLB_FIELD(entry, PTE_U), + GET_TLB_FIELD(entry, PTE_C)); + qemu_printf(" PA:0x%09" PRIx64 " SZ:%s (0x%" PRIx64 ")", PA, + pgsize_str[hex_tlb_pgsize_type(entry)], + hex_tlb_page_size_bytes(entry)); + qemu_printf("\n"); + } + } +} + +static inline void hex_log_tlbw(uint32_t index, uint64_t entry) +{ + if (qemu_loglevel_mask(CPU_LOG_MMU)) { + if (qemu_log_enabled()) { + FILE *logfile = qemu_log_trylock(); + if (logfile) { + fprintf(logfile, "tlbw[%03d]: ", index); + if (!hex_dump_mmu_entry(logfile, entry)) { + fprintf(logfile, "invalid\n"); + } + qemu_log_unlock(logfile); + } + } + } +} + +void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value) +{ + uint32_t myidx = fTLB_NONPOW2WRAP(fTLB_IDXMASK(index)); + bool old_entry_valid = GET_TLB_FIELD(env->hex_tlb->entries[myidx], PTE_V); + if (old_entry_valid && hexagon_cpu_mmu_enabled(env)) { + CPUState *cs = env_cpu(env); + + tlb_flush(cs); + } + env->hex_tlb->entries[myidx] = (value); + hex_log_tlbw(myidx, value); +} + +void hex_mmu_realize(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + if (cs->cpu_index == 0) { + env->hex_tlb = g_malloc0(sizeof(CPUHexagonTLBContext)); + } else { + CPUState *cpu0_s = NULL; + CPUHexagonState *env0 = NULL; + CPU_FOREACH(cpu0_s) { + assert(cpu0_s->cpu_index == 0); + env0 = &(HEXAGON_CPU(cpu0_s)->env); + break; + } + env->hex_tlb = env0->hex_tlb; + } +} + +void hex_mmu_on(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned on!\n"); + tlb_flush(cs); +} + +void hex_mmu_off(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned off!\n"); + tlb_flush(cs); +} + +void hex_mmu_mode_change(CPUHexagonState *env) +{ + qemu_log_mask(CPU_LOG_MMU, "Hexagon mode change!\n"); + CPUState *cs = env_cpu(env); + tlb_flush(cs); +} + +static inline bool hex_tlb_entry_match_noperm(uint64_t entry, uint32_t asid, + uint64_t VA) +{ + if (GET_TLB_FIELD(entry, PTE_V)) { + if (GET_TLB_FIELD(entry, PTE_G)) { + /* Global entry - ingnore ASID */ + } else if (asid != NO_ASID) { + uint32_t tlb_asid = GET_TLB_FIELD(entry, PTE_ASID); + if (tlb_asid != asid) { + return false; + } + } + + uint64_t page_size = hex_tlb_page_size_bytes(entry); + uint64_t page_start = + ROUND_DOWN(hex_tlb_virt_addr(entry), page_size); + if (page_start <= VA && VA < page_start + page_size) { + return true; + } + } + return false; +} + +static inline void hex_tlb_entry_get_perm(CPUHexagonState *env, uint64_t entry, + MMUAccessType access_type, + int mmu_idx, int *prot, + int32_t *excp) +{ + g_assert_not_reached(); +} + +static inline bool hex_tlb_entry_match(CPUHexagonState *env, uint64_t entry, + uint8_t asid, target_ulong VA, + MMUAccessType access_type, hwaddr *PA, + int *prot, int *size, int32_t *excp, + int mmu_idx) +{ + if (hex_tlb_entry_match_noperm(entry, asid, VA)) { + hex_tlb_entry_get_perm(env, entry, access_type, mmu_idx, prot, excp); + *PA = hex_tlb_phys_addr(entry); + *size = hex_tlb_page_size_bytes(entry); + return true; + } + return false; +} + +bool hex_tlb_find_match(CPUHexagonState *env, target_ulong VA, + MMUAccessType access_type, hwaddr *PA, int *prot, + int *size, int32_t *excp, int mmu_idx) +{ + *PA = 0; + *prot = 0; + *size = 0; + *excp = 0; + uint32_t ssr = arch_get_system_reg(env, HEX_SREG_SSR); + uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr); + int i; + HexagonCPU *cpu = env_archcpu(env); + for (i = 0; i < cpu->num_tlbs; i++) { + uint64_t entry = env->hex_tlb->entries[i]; + if (hex_tlb_entry_match(env, entry, asid, VA, access_type, PA, prot, + size, excp, mmu_idx)) { + return true; + } + } + return false; +} + +static uint32_t hex_tlb_lookup_by_asid(CPUHexagonState *env, uint32_t asid, + uint32_t VA) +{ + g_assert_not_reached(); +} + +/* Called from tlbp instruction */ +uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA) +{ + return hex_tlb_lookup_by_asid(env, GET_SSR_FIELD(SSR_ASID, ssr), VA); +} + +static bool hex_tlb_is_match(CPUHexagonState *env, + uint64_t entry1, uint64_t entry2, + bool consider_gbit) +{ + bool valid1 = GET_TLB_FIELD(entry1, PTE_V); + bool valid2 = GET_TLB_FIELD(entry2, PTE_V); + uint64_t size1 = hex_tlb_page_size_bytes(entry1); + uint64_t vaddr1 = ROUND_DOWN(hex_tlb_virt_addr(entry1), size1); + uint64_t size2 = hex_tlb_page_size_bytes(entry2); + uint64_t vaddr2 = ROUND_DOWN(hex_tlb_virt_addr(entry2), size2); + int asid1 = GET_TLB_FIELD(entry1, PTE_ASID); + int asid2 = GET_TLB_FIELD(entry2, PTE_ASID); + bool gbit1 = GET_TLB_FIELD(entry1, PTE_G); + bool gbit2 = GET_TLB_FIELD(entry2, PTE_G); + + if (!valid1 || !valid2) { + return false; + } + + if (((vaddr1 <= vaddr2) && (vaddr2 < (vaddr1 + size1))) || + ((vaddr2 <= vaddr1) && (vaddr1 < (vaddr2 + size2)))) { + if (asid1 == asid2) { + return true; + } + if ((consider_gbit && gbit1) || gbit2) { + return true; + } + } + return false; +} + +/* + * Return codes: + * 0 or positive index of match + * -1 multiple matches + * -2 no match + */ +int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry, uint64_t index) +{ + int matches = 0; + int last_match = 0; + int i; + + HexagonCPU *cpu = env_archcpu(env); + for (i = 0; i < cpu->num_tlbs; i++) { + if (hex_tlb_is_match(env, entry, env->hex_tlb->entries[i], false)) { + matches++; + last_match = i; + } + } + + if (matches == 1) { + return last_match; + } + if (matches == 0) { + return -2; + } + return -1; +} + +static inline void print_thread(const char *str, CPUState *cs) +{ + g_assert(bql_locked()); + CPUHexagonState *thread = cpu_env(cs); + bool is_stopped = cpu_is_stopped(cs); + int exe_mode = get_exe_mode(thread); + hex_lock_state_t lock_state = thread->tlb_lock_state; + qemu_log_mask(CPU_LOG_MMU, + "%s: threadId = %d: %s, exe_mode = %s, tlb_lock_state = %s\n", + str, + thread->threadId, + is_stopped ? "stopped" : "running", + exe_mode == HEX_EXE_MODE_OFF ? "off" : + exe_mode == HEX_EXE_MODE_RUN ? "run" : + exe_mode == HEX_EXE_MODE_WAIT ? "wait" : + exe_mode == HEX_EXE_MODE_DEBUG ? "debug" : + "unknown", + lock_state == HEX_LOCK_UNLOCKED ? "unlocked" : + lock_state == HEX_LOCK_WAITING ? "waiting" : + lock_state == HEX_LOCK_OWNER ? "owner" : + "unknown"); +} + +static inline void print_thread_states(const char *str) +{ + CPUState *cs; + CPU_FOREACH(cs) { + print_thread(str, cs); + } +} + +void hex_tlb_lock(CPUHexagonState *env) +{ + qemu_log_mask(CPU_LOG_MMU, "hex_tlb_lock: %d\n", env->threadId); + BQL_LOCK_GUARD(); + g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1)); + + uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg); + if (tlb_lock) { + if (env->tlb_lock_state == HEX_LOCK_QUEUED) { + env->next_PC += 4; + env->tlb_lock_count++; + env->tlb_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1); + return; + } + if (env->tlb_lock_state == HEX_LOCK_OWNER) { + qemu_log_mask(CPU_LOG_MMU | LOG_GUEST_ERROR, + "Double tlblock at PC: 0x%x, thread may hang\n", + env->next_PC); + env->next_PC += 4; + CPUState *cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + return; + } + env->tlb_lock_state = HEX_LOCK_WAITING; + CPUState *cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + } else { + env->next_PC += 4; + env->tlb_lock_count++; + env->tlb_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1); + } + + if (qemu_loglevel_mask(CPU_LOG_MMU)) { + qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_lock:\n"); + print_thread_states("\tThread"); + } +} + +void hex_tlb_unlock(CPUHexagonState *env) +{ + BQL_LOCK_GUARD(); + g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1)); + + /* Nothing to do if the TLB isn't locked by this thread */ + uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg); + if ((tlb_lock == 0) || + (env->tlb_lock_state != HEX_LOCK_OWNER)) { + qemu_log_mask(LOG_GUEST_ERROR, + "thread %d attempted to tlbunlock without having the " + "lock, tlb_lock state = %d\n", + env->threadId, env->tlb_lock_state); + g_assert(env->tlb_lock_state != HEX_LOCK_WAITING); + return; + } + + env->tlb_lock_count--; + env->tlb_lock_state = HEX_LOCK_UNLOCKED; + SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 0); + + /* Look for a thread to unlock */ + unsigned int this_threadId = env->threadId; + CPUHexagonState *unlock_thread = NULL; + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *thread = cpu_env(cs); + + /* + * The hardware implements round-robin fairness, so we look for threads + * starting at env->threadId + 1 and incrementing modulo the number of + * threads. + * + * To implement this, we check if thread is a earlier in the modulo + * sequence than unlock_thread. + * if unlock thread is higher than this thread + * thread must be between this thread and unlock_thread + * else + * thread higher than this thread is ahead of unlock_thread + * thread must be lower then unlock thread + */ + if (thread->tlb_lock_state == HEX_LOCK_WAITING) { + if (!unlock_thread) { + unlock_thread = thread; + } else if (unlock_thread->threadId > this_threadId) { + if (this_threadId < thread->threadId && + thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } else { + if (thread->threadId > this_threadId) { + unlock_thread = thread; + } + if (thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } + } + } + if (unlock_thread) { + cs = env_cpu(unlock_thread); + print_thread("\tWaiting thread found", cs); + unlock_thread->tlb_lock_state = HEX_LOCK_QUEUED; + SET_SYSCFG_FIELD(unlock_thread, SYSCFG_TLBLOCK, 1); + cpu_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK); + } + + if (qemu_loglevel_mask(CPU_LOG_MMU)) { + qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_unlock:\n"); + print_thread_states("\tThread"); + } + +} + diff --git a/target/hexagon/hex_mmu.h b/target/hexagon/hex_mmu.h new file mode 100644 index 000000000000..fae8aefcac1d --- /dev/null +++ b/target/hexagon/hex_mmu.h @@ -0,0 +1,30 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_MMU_H +#define HEXAGON_MMU_H + +#include "max.h" + +struct CPUHexagonTLBContext { + uint64_t entries[MAX_TLB_ENTRIES]; +}; + +extern void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value); +extern uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA); +extern void hex_mmu_realize(CPUHexagonState *env); +extern void hex_mmu_on(CPUHexagonState *env); +extern void hex_mmu_off(CPUHexagonState *env); +extern void hex_mmu_mode_change(CPUHexagonState *env); +extern bool hex_tlb_find_match(CPUHexagonState *env, target_ulong VA, + MMUAccessType access_type, hwaddr *PA, int *prot, + int *size, int32_t *excp, int mmu_idx); +extern int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry, + uint64_t index); +extern void hex_tlb_lock(CPUHexagonState *env); +extern void hex_tlb_unlock(CPUHexagonState *env); +void dump_mmu(CPUHexagonState *env); +#endif diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h index c24c36092161..120cfde7b958 100644 --- a/target/hexagon/internal.h +++ b/target/hexagon/internal.h @@ -40,6 +40,9 @@ void G_NORETURN do_raise_exception(CPUHexagonState *env, target_ulong PC, uintptr_t retaddr); +#define hexagon_cpu_mmu_enabled(env) \ + GET_SYSCFG_FIELD(SYSCFG_MMUEN, arch_get_system_reg(env, HEX_SREG_SYSCFG)) + #ifndef CONFIG_USER_ONLY extern const VMStateDescription vmstate_hexagon_cpu; #endif diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c index 9fdafb4573dd..fcdbacf9fd56 100644 --- a/target/hexagon/machine.c +++ b/target/hexagon/machine.c @@ -7,6 +7,33 @@ #include "qemu/osdep.h" #include "migration/cpu.h" #include "cpu.h" +#include "hex_mmu.h" + +static int get_hex_tlb_ptr(QEMUFile *f, void *pv, size_t size, + const VMStateField *field) +{ + CPUHexagonTLBContext *tlb = pv; + for (int i = 0; i < ARRAY_SIZE(tlb->entries); i++) { + tlb->entries[i] = qemu_get_be64(f); + } + return 0; +} + +static int put_hex_tlb_ptr(QEMUFile *f, void *pv, size_t size, + const VMStateField *field, JSONWriter *vmdesc) +{ + CPUHexagonTLBContext *tlb = pv; + for (int i = 0; i < ARRAY_SIZE(tlb->entries); i++) { + qemu_put_be64(f, tlb->entries[i]); + } + return 0; +} + +const VMStateInfo vmstate_info_hex_tlb_ptr = { + .name = "hex_tlb_pointer", + .get = get_hex_tlb_ptr, + .put = put_hex_tlb_ptr, +}; const VMStateDescription vmstate_hexagon_cpu = { @@ -27,6 +54,9 @@ const VMStateDescription vmstate_hexagon_cpu = { VMSTATE_UINTTL(env.threadId, HexagonCPU), VMSTATE_UINTTL(env.cause_code, HexagonCPU), VMSTATE_UINTTL(env.wait_next_pc, HexagonCPU), + VMSTATE_POINTER(env.hex_tlb, HexagonCPU, 0, + vmstate_info_hex_tlb_ptr, CPUHexagonTLBContext *), + VMSTATE_END_OF_LIST() }, }; diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build index 3ec53010fa02..aa729a3683f1 100644 --- a/target/hexagon/meson.build +++ b/target/hexagon/meson.build @@ -273,7 +273,8 @@ hexagon_ss.add(files( # idef-generated-enabled-instructions # idef_parser_enabled = get_option('hexagon_idef_parser') -if idef_parser_enabled and 'hexagon-linux-user' in target_dirs +if idef_parser_enabled and ('hexagon-linux-user' in target_dirs or + 'hexagon-softmmu' in target_dirs) idef_parser_input_generated = custom_target( 'idef_parser_input.h.inc', output: 'idef_parser_input.h.inc', diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 71c137be308f..9119e42ff77f 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -944,7 +944,7 @@ static void hexagon_tr_init_disas_context(DisasContextBase *dcbase, HexagonCPU *hex_cpu = env_archcpu(cpu_env(cs)); uint32_t hex_flags = dcbase->tb->flags; - ctx->mem_idx = MMU_USER_IDX; + ctx->mem_idx = FIELD_EX32(hex_flags, TB_FLAGS, MMU_INDEX); ctx->num_packets = 0; ctx->num_insns = 0; ctx->num_hvx_insns = 0; From 81ff99434b5c7c7862606467f72de10b31db8a90 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 14:23:29 -0700 Subject: [PATCH 1092/1179] target/hexagon: Add IRQ events Signed-off-by: Brian Cain --- target/hexagon/cpu_bits.h | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h index 5d26815eb9bc..b559a7ba8809 100644 --- a/target/hexagon/cpu_bits.h +++ b/target/hexagon/cpu_bits.h @@ -26,6 +26,28 @@ enum hex_event { HEX_EVENT_NONE = -1, HEX_EVENT_TRAP0 = 0x008, + HEX_EVENT_FETCH_NO_UPAGE = 0x012, + HEX_EVENT_INVALID_PACKET = 0x015, + HEX_EVENT_INVALID_OPCODE = 0x015, + HEX_EVENT_PC_NOT_ALIGNED = 0x01e, + HEX_EVENT_PRIV_NO_UREAD = 0x024, + HEX_EVENT_PRIV_NO_UWRITE = 0x025, + HEX_EVENT_INT0 = 0x10, + HEX_EVENT_INT1 = 0x11, + HEX_EVENT_INT2 = 0x12, + HEX_EVENT_INT3 = 0x13, + HEX_EVENT_INT4 = 0x14, + HEX_EVENT_INT5 = 0x15, + HEX_EVENT_INT6 = 0x16, + HEX_EVENT_INT7 = 0x17, + HEX_EVENT_INT8 = 0x18, + HEX_EVENT_INT9 = 0x19, + HEX_EVENT_INTA = 0x1a, + HEX_EVENT_INTB = 0x1b, + HEX_EVENT_INTC = 0x1c, + HEX_EVENT_INTD = 0x1d, + HEX_EVENT_INTE = 0x1e, + HEX_EVENT_INTF = 0x1f, }; enum hex_cause { @@ -39,6 +61,18 @@ enum hex_cause { HEX_CAUSE_PRIV_NO_UWRITE = 0x025, HEX_CAUSE_PRIV_USER_NO_GINSN = 0x01a, HEX_CAUSE_PRIV_USER_NO_SINSN = 0x01b, + HEX_CAUSE_INT0 = 0x0c0, + HEX_CAUSE_INT1 = 0x0c1, + HEX_CAUSE_INT2 = 0x0c2, + HEX_CAUSE_INT3 = 0x0c3, + HEX_CAUSE_INT4 = 0x0c4, + HEX_CAUSE_INT5 = 0x0c5, + HEX_CAUSE_INT6 = 0x0c6, + HEX_CAUSE_INT7 = 0x0c7, + HEX_CAUSE_VIC0 = 0x0c2, + HEX_CAUSE_VIC1 = 0x0c3, + HEX_CAUSE_VIC2 = 0x0c4, + HEX_CAUSE_VIC3 = 0x0c5, }; enum data_cache_state { From 19eef6b920d9150b8d46813f859518753570dfb6 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 14:23:48 -0700 Subject: [PATCH 1093/1179] target/hexagon: Add clear_wait_mode() definition Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 10 ++++++++++ target/hexagon/cpu_helper.h | 1 + 2 files changed, 11 insertions(+) diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index 3297882a6aa5..471dc440a4ee 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -75,6 +75,16 @@ void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old) g_assert_not_reached(); } +void clear_wait_mode(CPUHexagonState *env) +{ + g_assert(bql_locked()); + + const uint32_t modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + uint32_t thread_wait_mask = GET_FIELD(MODECTL_W, modectl); + thread_wait_mask &= ~(0x1 << env->threadId); + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_W, thread_wait_mask); +} + int get_exe_mode(CPUHexagonState *env) { g_assert_not_reached(); diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index e0c0c037a6ea..6f0c6697ad13 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -16,6 +16,7 @@ void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t); void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t); void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old); int get_exe_mode(CPUHexagonState *env); +void clear_wait_mode(CPUHexagonState *env); static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) From 31d286a5ef82646c280f758d0e7bffbbbe53cf01 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 14:24:13 -0700 Subject: [PATCH 1094/1179] target/hexagon: Define f{S,G}ET_FIELD macros Signed-off-by: Brian Cain --- target/hexagon/macros.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/target/hexagon/macros.h b/target/hexagon/macros.h index b0e9610d98d5..afbbe8e26524 100644 --- a/target/hexagon/macros.h +++ b/target/hexagon/macros.h @@ -649,6 +649,16 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) reg_field_info[FIELD].width, \ reg_field_info[FIELD].offset) +#define fGET_FIELD(VAL, FIELD) \ + fEXTRACTU_BITS(VAL, \ + reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset) +#define fSET_FIELD(VAL, FIELD, NEWVAL) \ + fINSERT_BITS(VAL, \ + reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset, \ + (NEWVAL)) + #ifdef QEMU_GENERATE #define fDCZEROA(REG) \ do { \ From 253e5a99a97dcc23a5c23f9a9349bd2f5e793224 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 14:25:26 -0700 Subject: [PATCH 1095/1179] target/hexagon: Add hex_interrupts support Co-authored-by: Taylor Simpson Co-authored-by: Sid Manning Co-authored-by: Michael Lambert Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 2 + target/hexagon/cpu.h | 1 + target/hexagon/hex_interrupts.c | 324 ++++++++++++++++++++++++++++++++ target/hexagon/hex_interrupts.h | 15 ++ 4 files changed, 342 insertions(+) create mode 100644 target/hexagon/hex_interrupts.c create mode 100644 target/hexagon/hex_interrupts.h diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 25d8e8f387e6..0f1be6656c13 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -59,6 +59,8 @@ static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model) static const Property hexagon_cpu_properties[] = { #if !defined(CONFIG_USER_ONLY) DEFINE_PROP_UINT32("jtlb-entries", HexagonCPU, num_tlbs, MAX_TLB_ENTRIES), + DEFINE_PROP_UINT32("l2vic-base-addr", HexagonCPU, l2vic_base_addr, + 0xffffffffULL), #endif DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 0811ca00a8ae..9a93b5333629 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -181,6 +181,7 @@ struct ArchCPU { bool short_circuit; #ifndef CONFIG_USER_ONLY uint32_t num_tlbs; + uint32_t l2vic_base_addr; #endif }; diff --git a/target/hexagon/hex_interrupts.c b/target/hexagon/hex_interrupts.c new file mode 100644 index 000000000000..fd00bcfb9a57 --- /dev/null +++ b/target/hexagon/hex_interrupts.c @@ -0,0 +1,324 @@ +/* + * Copyright(c) 2022-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "cpu.h" +#include "hex_interrupts.h" +#include "macros.h" +#include "sys_macros.h" +#include "system/cpus.h" + +static bool hex_is_qualified_for_int(CPUHexagonState *env, int int_num); + +static bool get_syscfg_gie(CPUHexagonState *env) +{ + target_ulong syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + return GET_SYSCFG_FIELD(SYSCFG_GIE, syscfg); +} + +static bool get_ssr_ex(CPUHexagonState *env) +{ + target_ulong ssr = arch_get_system_reg(env, HEX_SREG_SSR); + return GET_SSR_FIELD(SSR_EX, ssr); +} + +static bool get_ssr_ie(CPUHexagonState *env) +{ + target_ulong ssr = arch_get_system_reg(env, HEX_SREG_SSR); + return GET_SSR_FIELD(SSR_IE, ssr); +} + +/* Do these together so we only have to call hexagon_modify_ssr once */ +static void set_ssr_ex_cause(CPUHexagonState *env, int ex, uint32_t cause) +{ + target_ulong old = arch_get_system_reg(env, HEX_SREG_SSR); + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_EX, ex); + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_CAUSE, cause); + target_ulong new = arch_get_system_reg(env, HEX_SREG_SSR); + hexagon_modify_ssr(env, new, old); +} + +static bool get_iad_bit(CPUHexagonState *env, int int_num) +{ + target_ulong ipendad = arch_get_system_reg(env, HEX_SREG_IPENDAD); + target_ulong iad = GET_FIELD(IPENDAD_IAD, ipendad); + return extract32(iad, int_num, 1); +} + +static void set_iad_bit(CPUHexagonState *env, int int_num, int val) +{ + target_ulong ipendad = arch_get_system_reg(env, HEX_SREG_IPENDAD); + target_ulong iad = GET_FIELD(IPENDAD_IAD, ipendad); + iad = deposit32(iad, int_num, 1, val); + fSET_FIELD(ipendad, IPENDAD_IAD, iad); + arch_set_system_reg(env, HEX_SREG_IPENDAD, ipendad); +} + +static uint32_t get_ipend(CPUHexagonState *env) +{ + target_ulong ipendad = arch_get_system_reg(env, HEX_SREG_IPENDAD); + return GET_FIELD(IPENDAD_IPEND, ipendad); +} + +static inline bool get_ipend_bit(CPUHexagonState *env, int int_num) +{ + target_ulong ipendad = arch_get_system_reg(env, HEX_SREG_IPENDAD); + target_ulong ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + return extract32(ipend, int_num, 1); +} + +static void clear_ipend(CPUHexagonState *env, uint32_t mask) +{ + target_ulong ipendad = arch_get_system_reg(env, HEX_SREG_IPENDAD); + target_ulong ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + ipend &= ~mask; + fSET_FIELD(ipendad, IPENDAD_IPEND, ipend); + arch_set_system_reg(env, HEX_SREG_IPENDAD, ipendad); +} + +static void set_ipend(CPUHexagonState *env, uint32_t mask) +{ + target_ulong ipendad = arch_get_system_reg(env, HEX_SREG_IPENDAD); + target_ulong ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + ipend |= mask; + fSET_FIELD(ipendad, IPENDAD_IPEND, ipend); + arch_set_system_reg(env, HEX_SREG_IPENDAD, ipendad); +} + +static void set_ipend_bit(CPUHexagonState *env, int int_num, int val) +{ + target_ulong ipendad = arch_get_system_reg(env, HEX_SREG_IPENDAD); + target_ulong ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + ipend = deposit32(ipend, int_num, 1, val); + fSET_FIELD(ipendad, IPENDAD_IPEND, ipend); + arch_set_system_reg(env, HEX_SREG_IPENDAD, ipendad); +} + +static bool get_imask_bit(CPUHexagonState *env, int int_num) +{ + target_ulong imask = arch_get_system_reg(env, HEX_SREG_IMASK); + return extract32(imask, int_num, 1); +} + +static uint32_t get_prio(CPUHexagonState *env) +{ + target_ulong stid = arch_get_system_reg(env, HEX_SREG_STID); + return extract32(stid, reg_field_info[STID_PRIO].offset, + reg_field_info[STID_PRIO].width); +} + +static void set_elr(CPUHexagonState *env, target_ulong val) +{ + arch_set_system_reg(env, HEX_SREG_ELR, val); +} + +static bool get_schedcfgen(CPUHexagonState *env) +{ + target_ulong schedcfg = arch_get_system_reg(env, HEX_SREG_SCHEDCFG); + return extract32(schedcfg, reg_field_info[SCHEDCFG_EN].offset, + reg_field_info[SCHEDCFG_EN].width); +} + +static bool is_lowest_prio(CPUHexagonState *env, int int_num) +{ + uint32_t my_prio = get_prio(env); + CPUState *cs; + + CPU_FOREACH(cs) { + CPUHexagonState *hex_env = cpu_env(cs); + if (!hex_is_qualified_for_int(hex_env, int_num)) { + continue; + } + + /* Note that lower values indicate *higher* priority */ + if (my_prio < get_prio(hex_env)) { + return false; + } + } + return true; +} + +static bool hex_is_qualified_for_int(CPUHexagonState *env, int int_num) +{ + bool syscfg_gie = get_syscfg_gie(env); + bool iad = get_iad_bit(env, int_num); + bool ssr_ie = get_ssr_ie(env); + bool ssr_ex = get_ssr_ex(env); + bool imask = get_imask_bit(env, int_num); + + return syscfg_gie && !iad && ssr_ie && !ssr_ex && !imask; +} + +static void clear_pending_locks(CPUHexagonState *env) +{ + g_assert(bql_locked()); + if (env->k0_lock_state == HEX_LOCK_WAITING) { + env->k0_lock_state = HEX_LOCK_UNLOCKED; + } + if (env->tlb_lock_state == HEX_LOCK_WAITING) { + env->tlb_lock_state = HEX_LOCK_UNLOCKED; + } +} + +static bool should_not_exec(CPUHexagonState *env) +{ + return (get_exe_mode(env) == HEX_EXE_MODE_WAIT); +} + +static void restore_state(CPUHexagonState *env, bool int_accepted) +{ + CPUState *cs = env_cpu(env); + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI); + if (!int_accepted && should_not_exec(env)) { + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + } +} + +static void hex_accept_int(CPUHexagonState *env, int int_num) +{ + CPUState *cs = env_cpu(env); + target_ulong evb = arch_get_system_reg(env, HEX_SREG_EVB); + const int exe_mode = get_exe_mode(env); + const bool in_wait_mode = exe_mode == HEX_EXE_MODE_WAIT; + + set_ipend_bit(env, int_num, 0); + set_iad_bit(env, int_num, 1); + set_ssr_ex_cause(env, 1, HEX_CAUSE_INT0 | int_num); + cs->exception_index = HEX_EVENT_INT0 + int_num; + env->cause_code = HEX_EVENT_INT0 + int_num; + clear_pending_locks(env); + if (in_wait_mode) { + qemu_log_mask(CPU_LOG_INT, + "%s: thread %d resuming, exiting WAIT mode\n", + __func__, env->threadId); + set_elr(env, env->wait_next_pc); + clear_wait_mode(env); + cs->halted = false; + } else if (env->k0_lock_state == HEX_LOCK_WAITING) { + g_assert_not_reached(); + } else { + set_elr(env, env->gpr[HEX_REG_PC]); + } + env->gpr[HEX_REG_PC] = evb | (cs->exception_index << 2); + if (get_ipend(env) == 0) { + restore_state(env, true); + } +} + + +bool hex_check_interrupts(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + bool int_handled = false; + bool ssr_ex = get_ssr_ex(env); + int max_ints = 32; + bool schedcfgen; + + /* Early exit if nothing pending */ + if (get_ipend(env) == 0) { + restore_state(env, false); + return false; + } + + BQL_LOCK_GUARD(); + /* Only check priorities when schedcfgen is set */ + schedcfgen = get_schedcfgen(env); + for (int i = 0; i < max_ints; i++) { + if (!get_iad_bit(env, i) && get_ipend_bit(env, i)) { + qemu_log_mask(CPU_LOG_INT, + "%s: thread[%d] pc = 0x%x found int %d\n", __func__, + env->threadId, env->gpr[HEX_REG_PC], i); + if (hex_is_qualified_for_int(env, i) && + (!schedcfgen || is_lowest_prio(env, i))) { + qemu_log_mask(CPU_LOG_INT, "%s: thread[%d] int %d handled_\n", + __func__, env->threadId, i); + hex_accept_int(env, i); + int_handled = true; + break; + } + bool syscfg_gie = get_syscfg_gie(env); + bool iad = get_iad_bit(env, i); + bool ssr_ie = get_ssr_ie(env); + bool imask = get_imask_bit(env, i); + + qemu_log_mask(CPU_LOG_INT, + "%s: thread[%d] int %d not handled, qualified: %d, " + "schedcfg_en: %d, low prio %d\n", + __func__, env->threadId, i, + hex_is_qualified_for_int(env, i), schedcfgen, + is_lowest_prio(env, i)); + + qemu_log_mask(CPU_LOG_INT, + "%s: thread[%d] int %d not handled, GIE %d, iad %d, " + "SSR:IE %d, SSR:EX: %d, imask bit %d\n", + __func__, env->threadId, i, syscfg_gie, iad, ssr_ie, + ssr_ex, imask); + } + } + + /* + * If we didn't handle the interrupt and it wasn't + * because we were in EX state, then we won't be able + * to execute the interrupt on this CPU unless something + * changes in the CPU state. Clear the interrupt_request bits + * while preserving the IPEND bits, and we can re-assert the + * interrupt_request bit(s) when we execute one of those instructions. + */ + if (!int_handled && !ssr_ex) { + restore_state(env, int_handled); + } else if (int_handled) { + assert(!cs->halted); + } + + return int_handled; +} + +void hex_clear_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type) +{ + if (mask == 0) { + return; + } + + /* + * Notify all CPUs that the interrupt has happened + */ + BQL_LOCK_GUARD(); + clear_ipend(env, mask); + hex_interrupt_update(env); +} + +void hex_raise_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type) +{ + g_assert(bql_locked()); + if (mask == 0) { + return; + } + + /* + * Notify all CPUs that the interrupt has happened + */ + set_ipend(env, mask); + hex_interrupt_update(env); +} + +void hex_interrupt_update(CPUHexagonState *env) +{ + CPUState *cs; + + g_assert(bql_locked()); + if (get_ipend(env) != 0) { + CPU_FOREACH(cs) { + CPUHexagonState *hex_env = cpu_env(cs); + const int exe_mode = get_exe_mode(hex_env); + if (exe_mode != HEX_EXE_MODE_OFF) { + cs->interrupt_request |= CPU_INTERRUPT_SWI; + cpu_resume(cs); + } + } + } +} diff --git a/target/hexagon/hex_interrupts.h b/target/hexagon/hex_interrupts.h new file mode 100644 index 000000000000..17a243946ce2 --- /dev/null +++ b/target/hexagon/hex_interrupts.h @@ -0,0 +1,15 @@ +/* + * Copyright(c) 2022-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEX_INTERRUPTS_H +#define HEX_INTERRUPTS_H + +bool hex_check_interrupts(CPUHexagonState *env); +void hex_clear_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type); +void hex_raise_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type); +void hex_interrupt_update(CPUHexagonState *env); + +#endif From 15c46e70db9daaa46054e14b8a731bcc8314c348 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 19:16:02 -0700 Subject: [PATCH 1096/1179] target/hexagon: Implement ciad helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ciad is the clear interrupt auto disable instruction. This instruction is defined in the Qualcomm Hexagon V71 Programmer's Reference Manual - https://docs.qualcomm.com/bundle/publicresource/80-N2040-51_REV_AB_Hexagon_V71_ProgrammerS_Reference_Manual.pdf See §11.9.2 SYSTEM MONITOR. Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 39 ++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index fd9caafefc49..b28a18adf618 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -34,6 +34,11 @@ #include "op_helper.h" #include "cpu_helper.h" #include "translate.h" +#ifndef CONFIG_USER_ONLY +#include "hex_mmu.h" +#include "hw/intc/l2vic.h" +#include "hex_interrupts.h" +#endif #define SF_BIAS 127 #define SF_MANTBITS 23 @@ -1338,9 +1343,36 @@ void HELPER(vwhist128qm)(CPUHexagonState *env, int32_t uiV) } #ifndef CONFIG_USER_ONLY +static void hexagon_set_vid(CPUHexagonState *env, uint32_t offset, int val) +{ + g_assert((offset == L2VIC_VID_0) || (offset == L2VIC_VID_1)); + CPUState *cs = env_cpu(env); + HexagonCPU *cpu = HEXAGON_CPU(cs); + const hwaddr pend_mem = cpu->l2vic_base_addr + offset; + cpu_physical_memory_write(pend_mem, &val, sizeof(val)); +} + +static void hexagon_clear_last_irq(CPUHexagonState *env, uint32_t offset) +{ + /* + * currently only l2vic is the only attached it uses vid0, remove + * the assert below if anther is added + */ + hexagon_set_vid(env, offset, L2VIC_CIAD_INSTRUCTION); +} + void HELPER(ciad)(CPUHexagonState *env, uint32_t mask) { - g_assert_not_reached(); + uint32_t ipendad; + uint32_t iad; + + BQL_LOCK_GUARD(); + ipendad = READ_SREG(HEX_SREG_IPENDAD); + iad = fGET_FIELD(ipendad, IPENDAD_IAD); + fSET_FIELD(ipendad, IPENDAD_IAD, iad & ~(mask)); + arch_set_system_reg(env, HEX_SREG_IPENDAD, ipendad); + hexagon_clear_last_irq(env, L2VIC_VID_0); + hex_interrupt_update(env); } void HELPER(siad)(CPUHexagonState *env, uint32_t mask) @@ -1416,11 +1448,6 @@ static void modify_syscfg(CPUHexagonState *env, uint32_t val) g_assert_not_reached(); } -static void hexagon_set_vid(CPUHexagonState *env, uint32_t offset, int val) -{ - g_assert_not_reached(); -} - static uint32_t hexagon_find_last_irq(CPUHexagonState *env, uint32_t vid) { g_assert_not_reached(); From b0c8a1e2674dde8ab41351c465e587da6dc0f095 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 19:19:43 -0700 Subject: [PATCH 1097/1179] target/hexagon: Implement {c,}swi helpers {c,}swi are the "software interrupt"/"Cancel pending interrupts" instructions. Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index b28a18adf618..fed5cc2715cb 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1382,12 +1382,14 @@ void HELPER(siad)(CPUHexagonState *env, uint32_t mask) void HELPER(swi)(CPUHexagonState *env, uint32_t mask) { - g_assert_not_reached(); + BQL_LOCK_GUARD(); + hex_raise_interrupts(env, mask, CPU_INTERRUPT_SWI); } void HELPER(cswi)(CPUHexagonState *env, uint32_t mask) { - g_assert_not_reached(); + BQL_LOCK_GUARD(); + hex_clear_interrupts(env, mask, CPU_INTERRUPT_SWI); } void HELPER(iassignw)(CPUHexagonState *env, uint32_t src) From bf9c3e94ff659261a937af7896386ce966e8c01c Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 19:22:54 -0700 Subject: [PATCH 1098/1179] target/hexagon: Implement iassign{r,w} helpers iassign{r,w} are the "Interrupt to thread assignment {read,write}" instructions. Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 48 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index fed5cc2715cb..ded6c80d6237 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1394,12 +1394,56 @@ void HELPER(cswi)(CPUHexagonState *env, uint32_t mask) void HELPER(iassignw)(CPUHexagonState *env, uint32_t src) { - g_assert_not_reached(); + uint32_t modectl; + uint32_t thread_enabled_mask; + CPUState *cpu; + + BQL_LOCK_GUARD(); + modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + + CPU_FOREACH(cpu) { + CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env); + uint32_t thread_id_mask = 0x1 << thread_env->threadId; + if (thread_enabled_mask & thread_id_mask) { + uint32_t imask = arch_get_system_reg(thread_env, HEX_SREG_IMASK); + uint32_t intbitpos = (src >> 16) & 0xF; + uint32_t val = (src >> thread_env->threadId) & 0x1; + imask = deposit32(imask, intbitpos, 1, val); + arch_set_system_reg(thread_env, HEX_SREG_IMASK, imask); + + qemu_log_mask(CPU_LOG_INT, "%s: thread " TARGET_FMT_ld + ", new imask 0x%" PRIx32 "\n", __func__, + thread_env->threadId, imask); + } + } + hex_interrupt_update(env); } uint32_t HELPER(iassignr)(CPUHexagonState *env, uint32_t src) { - g_assert_not_reached(); + uint32_t modectl; + uint32_t thread_enabled_mask; + uint32_t intbitpos; + uint32_t dest_reg; + CPUState *cpu; + + BQL_LOCK_GUARD(); + modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + /* src fields are in same position as modectl, but mean different things */ + intbitpos = GET_FIELD(MODECTL_W, src); + dest_reg = 0; + CPU_FOREACH(cpu) { + CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env); + uint32_t thread_id_mask = 0x1 << thread_env->threadId; + if (thread_enabled_mask & thread_id_mask) { + uint32_t imask = arch_get_system_reg(thread_env, HEX_SREG_IMASK); + dest_reg |= ((imask >> intbitpos) & 0x1) << thread_env->threadId; + } + } + + return dest_reg; } void HELPER(start)(CPUHexagonState *env, uint32_t imask) From dc6c61d3e3cd1729403ba39b2eeb2de46cc7daa9 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 20:39:01 -0700 Subject: [PATCH 1099/1179] target/hexagon: Implement start/stop helpers Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 14 +++++- target/hexagon/cpu.h | 3 ++ target/hexagon/cpu_bits.h | 1 + target/hexagon/cpu_helper.c | 94 +++++++++++++++++++++++++++++++++++++ target/hexagon/cpu_helper.h | 3 ++ target/hexagon/op_helper.c | 4 +- 6 files changed, 116 insertions(+), 3 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 0f1be6656c13..6c2939be023b 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -32,6 +32,7 @@ #ifndef CONFIG_USER_ONLY #include "sys_macros.h" +#include "qemu/main-loop.h" #endif static void hexagon_v66_cpu_init(Object *obj) { } @@ -61,6 +62,7 @@ static const Property hexagon_cpu_properties[] = { DEFINE_PROP_UINT32("jtlb-entries", HexagonCPU, num_tlbs, MAX_TLB_ENTRIES), DEFINE_PROP_UINT32("l2vic-base-addr", HexagonCPU, l2vic_base_addr, 0xffffffffULL), + DEFINE_PROP_UINT32("hvx-contexts", HexagonCPU, hvx_contexts, 0), #endif DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, @@ -289,8 +291,17 @@ static void mmu_reset(CPUHexagonState *env) memset(env->hex_tlb, 0, sizeof(*env->hex_tlb)); } } -#endif +void hexagon_cpu_soft_reset(CPUHexagonState *env) +{ + BQL_LOCK_GUARD(); + arch_set_system_reg(env, HEX_SREG_SSR, 0); + hexagon_ssr_set_cause(env, HEX_CAUSE_RESET); + + target_ulong evb = arch_get_system_reg(env, HEX_SREG_EVB); + arch_set_thread_reg(env, HEX_REG_PC, evb); +} +#endif static void hexagon_cpu_reset_hold(Object *obj, ResetType type) { @@ -321,6 +332,7 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) } mmu_reset(env); arch_set_system_reg(env, HEX_SREG_HTID, cs->cpu_index); + hexagon_cpu_soft_reset(env); memset(env->t_sreg, 0, sizeof(target_ulong) * NUM_SREGS); memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS); env->threadId = cs->cpu_index; diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 9a93b5333629..0f30b2791f2b 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -45,6 +45,7 @@ typedef struct CPUHexagonTLBContext CPUHexagonTLBContext; #define REG_WRITES_MAX 32 #define PRED_WRITES_MAX 5 /* 4 insns + endloop */ #define VSTORES_MAX 2 +#define VECTOR_UNIT_MAX 8 #ifndef CONFIG_USER_ONLY #define CPU_INTERRUPT_SWI CPU_INTERRUPT_TGT_INT_0 @@ -182,6 +183,7 @@ struct ArchCPU { #ifndef CONFIG_USER_ONLY uint32_t num_tlbs; uint32_t l2vic_base_addr; + uint32_t hvx_contexts; #endif }; @@ -198,6 +200,7 @@ G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg); uint32_t hexagon_sreg_read(CPUHexagonState *env, uint32_t reg); void hexagon_gdb_sreg_write(CPUHexagonState *env, uint32_t reg, uint32_t val); +void hexagon_cpu_soft_reset(CPUHexagonState *env); #endif #include "exec/cpu-all.h" diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h index b559a7ba8809..610094a759c5 100644 --- a/target/hexagon/cpu_bits.h +++ b/target/hexagon/cpu_bits.h @@ -52,6 +52,7 @@ enum hex_event { enum hex_cause { HEX_CAUSE_NONE = -1, + HEX_CAUSE_RESET = 0x000, HEX_CAUSE_TRAP0 = 0x172, HEX_CAUSE_FETCH_NO_UPAGE = 0x012, HEX_CAUSE_INVALID_PACKET = 0x015, diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index 471dc440a4ee..a958b2592c14 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -85,8 +85,102 @@ void clear_wait_mode(CPUHexagonState *env) SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_W, thread_wait_mask); } +void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause) +{ + g_assert(bql_locked()); + + const uint32_t old = arch_get_system_reg(env, HEX_SREG_SSR); + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_EX, 1); + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_CAUSE, cause); + const uint32_t new = arch_get_system_reg(env, HEX_SREG_SSR); + + hexagon_modify_ssr(env, new, old); +} + + int get_exe_mode(CPUHexagonState *env) { g_assert_not_reached(); } + +static void set_enable_mask(CPUHexagonState *env) +{ + g_assert(bql_locked()); + + const uint32_t modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + uint32_t thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + thread_enabled_mask |= 0x1 << env->threadId; + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_E, thread_enabled_mask); +} + +static uint32_t clear_enable_mask(CPUHexagonState *env) +{ + g_assert(bql_locked()); + + const uint32_t modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + uint32_t thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + thread_enabled_mask &= ~(0x1 << env->threadId); + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_E, thread_enabled_mask); + return thread_enabled_mask; +} +static void do_start_thread(CPUState *cs, run_on_cpu_data tbd) +{ + BQL_LOCK_GUARD(); + + CPUHexagonState *env = cpu_env(cs); + + hexagon_cpu_soft_reset(env); + + set_enable_mask(env); + + cs->halted = 0; + cs->exception_index = HEX_EVENT_NONE; + cpu_resume(cs); +} + +void hexagon_start_threads(CPUHexagonState *current_env, uint32_t mask) +{ + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *env = cpu_env(cs); + if (!(mask & (0x1 << env->threadId))) { + continue; + } + + if (current_env->threadId != env->threadId) { + async_safe_run_on_cpu(cs, do_start_thread, RUN_ON_CPU_NULL); + } + } +} + +/* + * When we have all threads stopped, the return + * value to the shell is register 2 from thread 0. + */ +static target_ulong get_thread0_r2(void) +{ + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *thread = cpu_env(cs); + if (thread->threadId == 0) { + return thread->gpr[2]; + } + } + g_assert_not_reached(); +} + +void hexagon_stop_thread(CPUHexagonState *env) + +{ + BQL_LOCK_GUARD(); + + uint32_t thread_enabled_mask = clear_enable_mask(env); + CPUState *cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + if (!thread_enabled_mask) { + /* All threads are stopped, exit */ + exit(get_thread0_r2()); + } +} + #endif diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index 6f0c6697ad13..95a0cc078868 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -17,6 +17,9 @@ void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t); void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old); int get_exe_mode(CPUHexagonState *env); void clear_wait_mode(CPUHexagonState *env); +void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause); +void hexagon_start_threads(CPUHexagonState *env, uint32_t mask); +void hexagon_stop_thread(CPUHexagonState *env); static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index ded6c80d6237..9f79b1a20c6c 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1448,12 +1448,12 @@ uint32_t HELPER(iassignr)(CPUHexagonState *env, uint32_t src) void HELPER(start)(CPUHexagonState *env, uint32_t imask) { - g_assert_not_reached(); + hexagon_start_threads(env, imask); } void HELPER(stop)(CPUHexagonState *env) { - g_assert_not_reached(); + hexagon_stop_thread(env); } void HELPER(wait)(CPUHexagonState *env, target_ulong PC) From 93c6f07856bc489434a754319fd1a484dc973010 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 21:09:08 -0700 Subject: [PATCH 1100/1179] target/hexagon: Implement modify SSR The per-vCPU System Status Register controls many modal behaviors of the system architecture. When the SSR is updated, we trigger the necessary effects for interrupts, privilege/MMU, and HVX context mapping. Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 100 +++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index a958b2592c14..c4a3ba8ce0d2 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -14,6 +14,8 @@ #else #include "hw/boards.h" #include "hw/hexagon/hexagon.h" +#include "hex_interrupts.h" +#include "hex_mmu.h" #endif #include "exec/exec-all.h" #include "exec/cputlb.h" @@ -70,9 +72,105 @@ void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t cycles) g_assert_not_reached(); } +static MMVector VRegs[VECTOR_UNIT_MAX][NUM_VREGS]; +static MMQReg QRegs[VECTOR_UNIT_MAX][NUM_QREGS]; + +/* + * EXT_CONTEXTS + * SSR.XA 2 4 6 8 + * 000 HVX Context 0 HVX Context 0 HVX Context 0 HVX Context 0 + * 001 HVX Context 1 HVX Context 1 HVX Context 1 HVX Context 1 + * 010 HVX Context 0 HVX Context 2 HVX Context 2 HVX Context 2 + * 011 HVX Context 1 HVX Context 3 HVX Context 3 HVX Context 3 + * 100 HVX Context 0 HVX Context 0 HVX Context 4 HVX Context 4 + * 101 HVX Context 1 HVX Context 1 HVX Context 5 HVX Context 5 + * 110 HVX Context 0 HVX Context 2 HVX Context 2 HVX Context 6 + * 111 HVX Context 1 HVX Context 3 HVX Context 3 HVX Context 7 + */ +static int parse_context_idx(CPUHexagonState *env, uint8_t XA) +{ + int ret; + HexagonCPU *cpu = env_archcpu(env); + if (cpu->hvx_contexts == 6 && XA >= 6) { + ret = XA - 6 + 2; + } else { + ret = XA % cpu->hvx_contexts; + } + g_assert(ret >= 0 && ret < VECTOR_UNIT_MAX); + return ret; +} + +static void check_overcommitted_hvx(CPUHexagonState *env, uint32_t ssr) +{ + if (!GET_FIELD(SSR_XE, ssr)) { + return; + } + + uint8_t XA = GET_SSR_FIELD(SSR_XA, ssr); + + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *env_ = cpu_env(cs); + if (env_ == env) { + continue; + } + /* Check if another thread has the XE bit set and same XA */ + uint32_t ssr_ = arch_get_system_reg(env_, HEX_SREG_SSR); + if (GET_SSR_FIELD(SSR_XE2, ssr_) && GET_FIELD(SSR_XA, ssr_) == XA) { + qemu_log_mask(LOG_GUEST_ERROR, + "setting SSR.XA '%d' on thread %d but thread" + " %d has same extension active\n", XA, env->threadId, + env_->threadId); + } + } +} + void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old) { - g_assert_not_reached(); + g_assert(bql_locked()); + + bool old_EX = GET_SSR_FIELD(SSR_EX, old); + bool old_UM = GET_SSR_FIELD(SSR_UM, old); + bool old_GM = GET_SSR_FIELD(SSR_GM, old); + bool old_IE = GET_SSR_FIELD(SSR_IE, old); + uint8_t old_XA = GET_SSR_FIELD(SSR_XA, old); + bool new_EX = GET_SSR_FIELD(SSR_EX, new); + bool new_UM = GET_SSR_FIELD(SSR_UM, new); + bool new_GM = GET_SSR_FIELD(SSR_GM, new); + bool new_IE = GET_SSR_FIELD(SSR_IE, new); + uint8_t new_XA = GET_SSR_FIELD(SSR_XA, new); + + if ((old_EX != new_EX) || + (old_UM != new_UM) || + (old_GM != new_GM)) { + hex_mmu_mode_change(env); + } + + uint8_t old_asid = GET_SSR_FIELD(SSR_ASID, old); + uint8_t new_asid = GET_SSR_FIELD(SSR_ASID, new); + if (new_asid != old_asid) { + CPUState *cs = env_cpu(env); + tlb_flush(cs); + } + + if (old_XA != new_XA) { + int old_unit = parse_context_idx(env, old_XA); + int new_unit = parse_context_idx(env, new_XA); + + /* Ownership exchange */ + memcpy(VRegs[old_unit], env->VRegs, sizeof(env->VRegs)); + memcpy(QRegs[old_unit], env->QRegs, sizeof(env->QRegs)); + memcpy(env->VRegs, VRegs[new_unit], sizeof(env->VRegs)); + memcpy(env->QRegs, QRegs[new_unit], sizeof(env->QRegs)); + + check_overcommitted_hvx(env, new); + } + + /* See if the interrupts have been enabled or we have exited EX mode */ + if ((new_IE && !old_IE) || + (!new_EX && old_EX)) { + hex_interrupt_update(env); + } } void clear_wait_mode(CPUHexagonState *env) From 0686121298718a74c94656f5fed0927588a54cfa Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 21:20:41 -0700 Subject: [PATCH 1101/1179] target/hexagon: Implement {g,s}etimask helpers Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 9f79b1a20c6c..83088cfaa3fb 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1468,12 +1468,39 @@ void HELPER(resume)(CPUHexagonState *env, uint32_t mask) uint32_t HELPER(getimask)(CPUHexagonState *env, uint32_t tid) { - g_assert_not_reached(); + CPUState *cs; + CPU_FOREACH(cs) { + HexagonCPU *found_cpu = HEXAGON_CPU(cs); + CPUHexagonState *found_env = &found_cpu->env; + if (found_env->threadId == tid) { + target_ulong imask = arch_get_system_reg(found_env, HEX_SREG_IMASK); + qemu_log_mask(CPU_LOG_INT, "%s: tid %d imask = 0x%x\n", + __func__, env->threadId, + (unsigned)GET_FIELD(IMASK_MASK, imask)); + return GET_FIELD(IMASK_MASK, imask); + } + } + return 0; } void HELPER(setimask)(CPUHexagonState *env, uint32_t pred, uint32_t imask) { - g_assert_not_reached(); + CPUState *cs; + + BQL_LOCK_GUARD(); + CPU_FOREACH(cs) { + HexagonCPU *found_cpu = HEXAGON_CPU(cs); + CPUHexagonState *found_env = &found_cpu->env; + + if (pred == found_env->threadId) { + SET_SYSTEM_FIELD(found_env, HEX_SREG_IMASK, IMASK_MASK, imask); + qemu_log_mask(CPU_LOG_INT, "%s: tid %d imask 0x%x\n", + __func__, found_env->threadId, imask); + hex_interrupt_update(env); + return; + } + } + hex_interrupt_update(env); } static bool handle_pmu_sreg_write(CPUHexagonState *env, uint32_t reg, From ab2200068a6368b3dedf31a5b64511e80e4827cf Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 21:25:51 -0700 Subject: [PATCH 1102/1179] target/hexagon: Implement wait helper Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 40 +++++++++++++++++++++++++++++++++++++ target/hexagon/cpu_helper.h | 1 + target/hexagon/op_helper.c | 6 +++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index c4a3ba8ce0d2..a80ace5bc3b0 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -72,6 +72,46 @@ void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t cycles) g_assert_not_reached(); } +static void set_wait_mode(CPUHexagonState *env) +{ + g_assert(bql_locked()); + + const uint32_t modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + uint32_t thread_wait_mask = GET_FIELD(MODECTL_W, modectl); + thread_wait_mask |= 0x1 << env->threadId; + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_W, thread_wait_mask); +} + +void hexagon_wait_thread(CPUHexagonState *env, target_ulong PC) +{ + g_assert(bql_locked()); + + if (qemu_loglevel_mask(LOG_GUEST_ERROR) && + (env->k0_lock_state != HEX_LOCK_UNLOCKED || + env->tlb_lock_state != HEX_LOCK_UNLOCKED)) { + qemu_log("WARNING: executing wait() with acquired lock" + "may lead to deadlock\n"); + } + g_assert(get_exe_mode(env) != HEX_EXE_MODE_WAIT); + + CPUState *cs = env_cpu(env); + /* + * The addtion of cpu_has_work is borrowed from arm's wfi helper + * and is critical for our stability + */ + if ((cs->exception_index != HEX_EVENT_NONE) || + (cpu_has_work(cs))) { + qemu_log_mask(CPU_LOG_INT, + "%s: thread %d skipping WAIT mode, have some work\n", + __func__, env->threadId); + return; + } + set_wait_mode(env); + env->wait_next_pc = PC + 4; + + cpu_interrupt(cs, CPU_INTERRUPT_HALT); +} + static MMVector VRegs[VECTOR_UNIT_MAX][NUM_VREGS]; static MMQReg QRegs[VECTOR_UNIT_MAX][NUM_QREGS]; diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index 95a0cc078868..e8d89d852679 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -20,6 +20,7 @@ void clear_wait_mode(CPUHexagonState *env); void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause); void hexagon_start_threads(CPUHexagonState *env, uint32_t mask); void hexagon_stop_thread(CPUHexagonState *env); +void hexagon_wait_thread(CPUHexagonState *env, target_ulong PC); static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 83088cfaa3fb..03bed11f6e83 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1458,7 +1458,11 @@ void HELPER(stop)(CPUHexagonState *env) void HELPER(wait)(CPUHexagonState *env, target_ulong PC) { - g_assert_not_reached(); + BQL_LOCK_GUARD(); + + if (!fIN_DEBUG_MODE(env->threadId)) { + hexagon_wait_thread(env, PC); + } } void HELPER(resume)(CPUHexagonState *env, uint32_t mask) From 5b213b2119dc037ddb4287ff5021afb1e869b16c Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 21:35:29 -0700 Subject: [PATCH 1103/1179] target/hexagon: Implement get_exe_mode() Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 24 ++++++++++++++++++++++++ target/hexagon/reg_fields_def.h.inc | 11 +++++++++++ 2 files changed, 35 insertions(+) diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index a80ace5bc3b0..e4905612777a 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -238,6 +238,30 @@ void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause) int get_exe_mode(CPUHexagonState *env) { + g_assert(bql_locked()); + + target_ulong modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + uint32_t thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + bool E_bit = thread_enabled_mask & (0x1 << env->threadId); + uint32_t thread_wait_mask = GET_FIELD(MODECTL_W, modectl); + bool W_bit = thread_wait_mask & (0x1 << env->threadId); + target_ulong isdbst = arch_get_system_reg(env, HEX_SREG_ISDBST); + uint32_t debugmode = GET_FIELD(ISDBST_DEBUGMODE, isdbst); + bool D_bit = debugmode & (0x1 << env->threadId); + + /* Figure 4-2 */ + if (!D_bit && !W_bit && !E_bit) { + return HEX_EXE_MODE_OFF; + } + if (!D_bit && !W_bit && E_bit) { + return HEX_EXE_MODE_RUN; + } + if (!D_bit && W_bit && E_bit) { + return HEX_EXE_MODE_WAIT; + } + if (D_bit && !W_bit && E_bit) { + return HEX_EXE_MODE_DEBUG; + } g_assert_not_reached(); } diff --git a/target/hexagon/reg_fields_def.h.inc b/target/hexagon/reg_fields_def.h.inc index 156a3514e77d..50b8c26f8bfa 100644 --- a/target/hexagon/reg_fields_def.h.inc +++ b/target/hexagon/reg_fields_def.h.inc @@ -135,3 +135,14 @@ DEF_REG_FIELD(CCR_GRE, 27, 1) DEF_REG_FIELD(CCR_VV1, 29, 1) DEF_REG_FIELD(CCR_VV2, 30, 1) DEF_REG_FIELD(CCR_VV3, 31, 1) + +/* ISDB ST fields */ +DEF_REG_FIELD(ISDBST_WAITRUN, 24, 8) +DEF_REG_FIELD(ISDBST_ONOFF, 16, 8) +DEF_REG_FIELD(ISDBST_DEBUGMODE, 8, 8) +DEF_REG_FIELD(ISDBST_STUFFSTATUS, 5, 1) +DEF_REG_FIELD(ISDBST_CMDSTATUS, 4, 1) +DEF_REG_FIELD(ISDBST_PROCMODE, 3, 1) +DEF_REG_FIELD(ISDBST_MBXINSTATUS, 2, 1) +DEF_REG_FIELD(ISDBST_MBXOUTSTATUS, 1, 1) +DEF_REG_FIELD(ISDBST_READY, 0, 1) From 2c8b39f9a0e2b4996f3b9f164481ed42f664ad17 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 21:36:56 -0700 Subject: [PATCH 1104/1179] target/hexagon: Implement arch_get_system_reg() Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index e4905612777a..c53ddd618e64 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -37,7 +37,14 @@ uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index) uint32_t arch_get_system_reg(CPUHexagonState *env, uint32_t reg) { - g_assert_not_reached(); + if (reg == HEX_SREG_PCYCLELO) { + return hexagon_get_sys_pcycle_count_low(env); + } else if (reg == HEX_SREG_PCYCLEHI) { + return hexagon_get_sys_pcycle_count_high(env); + } + + g_assert(reg < NUM_SREGS); + return reg < HEX_SREG_GLB_START ? env->t_sreg[reg] : env->g_sreg[reg]; } uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env) From 9badeafba6412454e61640603153c9a77d87261d Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 27 Aug 2024 21:50:21 -0700 Subject: [PATCH 1105/1179] target/hexagon: Implement arch_{s,g}et_{thread,system}_reg() Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index e8d89d852679..1cdf4f8dd0ed 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -26,20 +26,27 @@ static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) { g_assert(reg < TOTAL_PER_THREAD_REGS); - g_assert_not_reached(); + env->gpr[reg] = val; } static inline uint32_t arch_get_thread_reg(CPUHexagonState *env, uint32_t reg) { g_assert(reg < TOTAL_PER_THREAD_REGS); - g_assert_not_reached(); + return env->gpr[reg]; } +#ifndef CONFIG_USER_ONLY static inline void arch_set_system_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) { - g_assert_not_reached(); + g_assert(reg < NUM_SREGS); + if (reg < HEX_SREG_GLB_START) { + env->t_sreg[reg] = val; + } else { + env->g_sreg[reg] = val; + } } +#endif uint32_t arch_get_system_reg(CPUHexagonState *env, uint32_t reg); From 1d8abce868fa6ef8cab76f152c812f300141d2b3 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 28 Aug 2024 17:56:18 -0700 Subject: [PATCH 1106/1179] target/hexagon: Add representation to count cycles The PCYCLE register can be enabled to indicate accumulated clock cycles. Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 3 +++ target/hexagon/cpu.h | 3 ++- target/hexagon/machine.c | 25 ++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 6c2939be023b..5316800a6e49 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -329,6 +329,7 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) if (cs->cpu_index == 0) { arch_set_system_reg(env, HEX_SREG_MODECTL, 0x1); + *(env->g_pcycle_base) = 0; } mmu_reset(env); arch_set_system_reg(env, HEX_SREG_HTID, cs->cpu_index); @@ -391,10 +392,12 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) #ifndef CONFIG_USER_ONLY if (cs->cpu_index == 0) { env->g_sreg = g_new0(target_ulong, NUM_SREGS); + env->g_pcycle_base = g_malloc0(sizeof(*env->g_pcycle_base)); } else { CPUState *cpu0 = qemu_get_cpu(0); CPUHexagonState *env0 = cpu_env(cpu0); env->g_sreg = env0->g_sreg; + env->g_pcycle_base = env0->g_pcycle_base; } #endif diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 0f30b2791f2b..857fb5b6e37a 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -117,7 +117,8 @@ typedef struct CPUArchState { target_ulong stack_start; uint8_t slot_cancelled; - + uint64_t t_cycle_count; + uint64_t *g_pcycle_base; #ifndef CONFIG_USER_ONLY /* Some system registers are per thread and some are global. */ target_ulong t_sreg[NUM_SREGS]; diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c index fcdbacf9fd56..4baa22d51f8e 100644 --- a/target/hexagon/machine.c +++ b/target/hexagon/machine.c @@ -9,6 +9,27 @@ #include "cpu.h" #include "hex_mmu.h" +static int get_u64_ptr(QEMUFile *f, void *pv, size_t size, + const VMStateField *field) +{ + uint64_t *p = pv; + *p = qemu_get_be64(f); + return 0; +} + +static int put_u64_ptr(QEMUFile *f, void *pv, size_t size, + const VMStateField *field, JSONWriter *vmdesc) +{ + qemu_put_be64(f, *((uint64_t *)pv)); + return 0; +} + +const VMStateInfo vmstate_info_uint64_ptr = { + .name = "uint64_t_pointer", + .get = get_u64_ptr, + .put = put_u64_ptr, +}; + static int get_hex_tlb_ptr(QEMUFile *f, void *pv, size_t size, const VMStateField *field) { @@ -35,7 +56,6 @@ const VMStateInfo vmstate_info_hex_tlb_ptr = { .put = put_hex_tlb_ptr, }; - const VMStateDescription vmstate_hexagon_cpu = { .name = "cpu", .version_id = 0, @@ -56,6 +76,9 @@ const VMStateDescription vmstate_hexagon_cpu = { VMSTATE_UINTTL(env.wait_next_pc, HexagonCPU), VMSTATE_POINTER(env.hex_tlb, HexagonCPU, 0, vmstate_info_hex_tlb_ptr, CPUHexagonTLBContext *), + VMSTATE_UINT64(env.t_cycle_count, HexagonCPU), + VMSTATE_POINTER(env.g_pcycle_base, HexagonCPU, 0, + vmstate_info_uint64_ptr, uint64_t *), VMSTATE_END_OF_LIST() }, From c981d0f047d9d988c1c96a30f2ca33fc1c0fe378 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 28 Aug 2024 17:58:06 -0700 Subject: [PATCH 1107/1179] target/hexagon: Add implementation of cycle counters Co-authored-by: Sid Manning Signed-off-by: Brian Cain --- target/hexagon/cpu.h | 25 ++++++++++++++++++++++--- target/hexagon/cpu_helper.c | 12 +++++++++--- target/hexagon/translate.c | 27 +++++++++++++++++++++++++++ target/hexagon/translate.h | 2 ++ 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 857fb5b6e37a..3d42e5fc12bd 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -27,6 +27,7 @@ #include "cpu-qom.h" #include "exec/cpu-defs.h" +#include "exec/cpu-common.h" #include "hex_regs.h" #include "mmvec/mmvec.h" #include "hw/registerfields.h" @@ -35,7 +36,10 @@ #error "Hexagon does not support system emulation" #endif +#ifndef CONFIG_USER_ONLY +#include "reg_fields.h" typedef struct CPUHexagonTLBContext CPUHexagonTLBContext; +#endif #define NUM_PREGS 4 #define TOTAL_PER_THREAD_REGS 64 @@ -192,6 +196,7 @@ struct ArchCPU { FIELD(TB_FLAGS, IS_TIGHT_LOOP, 0, 1) FIELD(TB_FLAGS, MMU_INDEX, 1, 3) +FIELD(TB_FLAGS, PCYCLE_ENABLED, 4, 1) G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t exception, @@ -205,6 +210,11 @@ void hexagon_cpu_soft_reset(CPUHexagonState *env); #endif #include "exec/cpu-all.h" + +#ifndef CONFIG_USER_ONLY +#include "cpu_helper.h" +#endif + static inline void cpu_get_tb_cpu_state(CPUHexagonState *env, vaddr *pc, uint64_t *cs_base, uint32_t *flags) { @@ -214,16 +224,27 @@ static inline void cpu_get_tb_cpu_state(CPUHexagonState *env, vaddr *pc, if (*pc == env->gpr[HEX_REG_SA0]) { hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP, 1); } - *flags = hex_flags; if (*pc & PCALIGN_MASK) { hexagon_raise_exception_err(env, HEX_CAUSE_PC_NOT_ALIGNED, 0); } #ifndef CONFIG_USER_ONLY + target_ulong syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + + bool pcycle_enabled = extract32(syscfg, + reg_field_info[SYSCFG_PCYCLEEN].offset, + reg_field_info[SYSCFG_PCYCLEEN].width); + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, cpu_mmu_index(env_cpu(env), false)); + + if (pcycle_enabled) { + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, PCYCLE_ENABLED, 1); + } #else + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, PCYCLE_ENABLED, true); hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, MMU_USER_IDX); #endif + *flags = hex_flags; } typedef HexagonCPU ArchCPU; @@ -232,6 +253,4 @@ void hexagon_translate_init(void); void hexagon_translate_code(CPUState *cs, TranslationBlock *tb, int *max_insns, vaddr pc, void *host_pc); -#include "exec/cpu-all.h" - #endif /* HEXAGON_CPU_H */ diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index c53ddd618e64..89ceb92b4a8a 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -49,17 +49,23 @@ uint32_t arch_get_system_reg(CPUHexagonState *env, uint32_t reg) uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env) { - g_assert_not_reached(); + uint64_t cycles = 0; + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *env_ = cpu_env(cs); + cycles += env_->t_cycle_count; + } + return *(env->g_pcycle_base) + cycles; } uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env) { - g_assert_not_reached(); + return hexagon_get_sys_pcycle_count(env) >> 32; } uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env) { - g_assert_not_reached(); + return extract64(hexagon_get_sys_pcycle_count(env), 0, 32); } void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 9119e42ff77f..060df6e5eb62 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -57,6 +57,7 @@ TCGv_i64 hex_store_val64[STORES_MAX]; TCGv hex_llsc_addr; TCGv hex_llsc_val; TCGv_i64 hex_llsc_val_i64; +TCGv_i64 hex_cycle_count; TCGv hex_vstore_addr[VSTORES_MAX]; TCGv hex_vstore_size[VSTORES_MAX]; TCGv hex_vstore_pending[VSTORES_MAX]; @@ -125,6 +126,22 @@ static void gen_exception_raw(int excp) gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp)); } +#ifndef CONFIG_USER_ONLY +static inline void gen_precise_exception(int excp, target_ulong PC) +{ + tcg_gen_movi_tl(hex_cause_code, excp); + gen_exception(HEX_EVENT_PRECISE, PC); +} + +static inline void gen_pcycle_counters(DisasContext *ctx) +{ + if (ctx->pcycle_enabled) { + tcg_gen_addi_i64(hex_cycle_count, hex_cycle_count, ctx->num_cycles); + ctx->num_cycles = 0; + } +} +#endif + static void gen_exec_counters(DisasContext *ctx) { tcg_gen_addi_tl(hex_gpr[HEX_REG_QEMU_PKT_CNT], @@ -133,6 +150,10 @@ static void gen_exec_counters(DisasContext *ctx) hex_gpr[HEX_REG_QEMU_INSN_CNT], ctx->num_insns); tcg_gen_addi_tl(hex_gpr[HEX_REG_QEMU_HVX_CNT], hex_gpr[HEX_REG_QEMU_HVX_CNT], ctx->num_hvx_insns); + +#ifndef CONFIG_USER_ONLY + gen_pcycle_counters(ctx); +#endif } static bool use_goto_tb(DisasContext *ctx, target_ulong dest) @@ -785,6 +806,7 @@ static void gen_commit_hvx(DisasContext *ctx) } } +static const int PCYCLES_PER_PACKET = 3; static void update_exec_counters(DisasContext *ctx) { Packet *pkt = ctx->pkt; @@ -804,6 +826,7 @@ static void update_exec_counters(DisasContext *ctx) } ctx->num_packets++; + ctx->num_cycles += PCYCLES_PER_PACKET; ctx->num_insns += num_real_insns; ctx->num_hvx_insns += num_hvx_insns; } @@ -946,11 +969,13 @@ static void hexagon_tr_init_disas_context(DisasContextBase *dcbase, ctx->mem_idx = FIELD_EX32(hex_flags, TB_FLAGS, MMU_INDEX); ctx->num_packets = 0; + ctx->num_cycles = 0; ctx->num_insns = 0; ctx->num_hvx_insns = 0; ctx->branch_cond = TCG_COND_NEVER; ctx->is_tight_loop = FIELD_EX32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP); ctx->short_circuit = hex_cpu->short_circuit; + ctx->pcycle_enabled = FIELD_EX32(hex_flags, TB_FLAGS, PCYCLE_ENABLED); } static void hexagon_tr_tb_start(DisasContextBase *db, CPUState *cpu) @@ -1077,6 +1102,8 @@ void hexagon_translate_init(void) offsetof(CPUHexagonState, llsc_val), "llsc_val"); hex_llsc_val_i64 = tcg_global_mem_new_i64(tcg_env, offsetof(CPUHexagonState, llsc_val_i64), "llsc_val_i64"); + hex_cycle_count = tcg_global_mem_new_i64(tcg_env, + offsetof(CPUHexagonState, t_cycle_count), "t_cycle_count"); for (i = 0; i < STORES_MAX; i++) { snprintf(store_addr_names[i], NAME_LEN, "store_addr_%d", i); hex_store_addr[i] = tcg_global_mem_new(tcg_env, diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index 0eaa3db03e81..9bc4b3ce8b33 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h @@ -83,6 +83,8 @@ typedef struct DisasContext { TCGv new_pred_value[NUM_PREGS]; TCGv branch_taken; TCGv dczero_addr; + bool pcycle_enabled; + uint32_t num_cycles; } DisasContext; bool is_gather_store_insn(DisasContext *ctx); From 31f146c41aafc1e61d03d4c33b19232df0f1baa3 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 28 Aug 2024 17:58:36 -0700 Subject: [PATCH 1108/1179] target/hexagon: Implement modify_syscfg() Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 51 +++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 03bed11f6e83..42805d0f1d03 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1522,7 +1522,56 @@ static bool handle_pmu_sreg_write(CPUHexagonState *env, uint32_t reg, static void modify_syscfg(CPUHexagonState *env, uint32_t val) { - g_assert_not_reached(); + g_assert(bql_locked()); + + uint32_t old; + uint32_t syscfg_read_only_mask = 0x80001c00; + uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + + /* clear read-only bits if they are set in the new value. */ + val &= ~syscfg_read_only_mask; + /* if read-only are currently set in syscfg keep them set. */ + val |= (syscfg & syscfg_read_only_mask); + + uint32_t tmp = val; + old = arch_get_system_reg(env, HEX_SREG_SYSCFG); + arch_set_system_reg(env, HEX_SREG_SYSCFG, tmp); + + /* Check for change in MMU enable */ + target_ulong old_mmu_enable = GET_SYSCFG_FIELD(SYSCFG_MMUEN, old); + uint8_t old_en = GET_SYSCFG_FIELD(SYSCFG_PCYCLEEN, old); + uint8_t old_gie = GET_SYSCFG_FIELD(SYSCFG_GIE, old); + target_ulong new_mmu_enable = + GET_SYSCFG_FIELD(SYSCFG_MMUEN, val); + if (new_mmu_enable && !old_mmu_enable) { + hex_mmu_on(env); + } else if (!new_mmu_enable && old_mmu_enable) { + hex_mmu_off(env); + } + + /* Changing pcycle enable from 0 to 1 resets the counters */ + uint8_t new_en = GET_SYSCFG_FIELD(SYSCFG_PCYCLEEN, val); + CPUState *cs; + if (old_en == 0 && new_en == 1) { + CPU_FOREACH(cs) { + CPUHexagonState *_env = cpu_env(cs); + _env->t_cycle_count = 0; + } + } + + /* See if global interrupts are turned on */ + uint8_t new_gie = GET_SYSCFG_FIELD(SYSCFG_GIE, val); + if (!old_gie && new_gie) { + qemu_log_mask(CPU_LOG_INT, "%s: global interrupts enabled\n", __func__); + hex_interrupt_update(env); + } + + if (qemu_loglevel_mask(LOG_UNIMP)) { + int new_v2x = GET_SYSCFG_FIELD(SYSCFG_V2X, val); + if (!new_v2x) { + qemu_log("HVX: 64 byte vector length is unsupported\n"); + } + } } static uint32_t hexagon_find_last_irq(CPUHexagonState *env, uint32_t vid) From 1428a5a5b3d8e65120f9b827f27e2d6f4d5e99b9 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 10:21:50 -0700 Subject: [PATCH 1109/1179] target/hexagon: Add system event, cause codes Signed-off-by: Brian Cain --- target/hexagon/cpu.h | 10 ++++++- target/hexagon/cpu_bits.h | 55 ++++++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 3d42e5fc12bd..e1717dd303bc 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -71,6 +71,15 @@ typedef struct CPUHexagonTLBContext CPUHexagonTLBContext; #define MMU_GUEST_IDX 1 #define MMU_KERNEL_IDX 2 +#define HEXAGON_CPU_IRQ_0 0 +#define HEXAGON_CPU_IRQ_1 1 +#define HEXAGON_CPU_IRQ_2 2 +#define HEXAGON_CPU_IRQ_3 3 +#define HEXAGON_CPU_IRQ_4 4 +#define HEXAGON_CPU_IRQ_5 5 +#define HEXAGON_CPU_IRQ_6 6 +#define HEXAGON_CPU_IRQ_7 7 + typedef enum { HEX_LOCK_UNLOCKED = 0, HEX_LOCK_WAITING = 1, @@ -79,7 +88,6 @@ typedef enum { } hex_lock_state_t; #endif - #define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU typedef struct { diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h index 610094a759c5..c7cc426ec888 100644 --- a/target/hexagon/cpu_bits.h +++ b/target/hexagon/cpu_bits.h @@ -24,14 +24,16 @@ #define PCALIGN_MASK (PCALIGN - 1) enum hex_event { - HEX_EVENT_NONE = -1, - HEX_EVENT_TRAP0 = 0x008, - HEX_EVENT_FETCH_NO_UPAGE = 0x012, - HEX_EVENT_INVALID_PACKET = 0x015, - HEX_EVENT_INVALID_OPCODE = 0x015, - HEX_EVENT_PC_NOT_ALIGNED = 0x01e, - HEX_EVENT_PRIV_NO_UREAD = 0x024, - HEX_EVENT_PRIV_NO_UWRITE = 0x025, + HEX_EVENT_NONE = -1, + HEX_EVENT_RESET = 0x0, + HEX_EVENT_IMPRECISE = 0x1, + HEX_EVENT_PRECISE = 0x2, + HEX_EVENT_TLB_MISS_X = 0x4, + HEX_EVENT_TLB_MISS_RW = 0x6, + HEX_EVENT_TRAP0 = 0x8, + HEX_EVENT_TRAP1 = 0x9, + HEX_EVENT_FPTRAP = 0xb, + HEX_EVENT_DEBUG = 0xc, HEX_EVENT_INT0 = 0x10, HEX_EVENT_INT1 = 0x11, HEX_EVENT_INT2 = 0x12, @@ -53,15 +55,38 @@ enum hex_event { enum hex_cause { HEX_CAUSE_NONE = -1, HEX_CAUSE_RESET = 0x000, - HEX_CAUSE_TRAP0 = 0x172, - HEX_CAUSE_FETCH_NO_UPAGE = 0x012, - HEX_CAUSE_INVALID_PACKET = 0x015, - HEX_CAUSE_INVALID_OPCODE = 0x015, - HEX_CAUSE_PC_NOT_ALIGNED = 0x01e, - HEX_CAUSE_PRIV_NO_UREAD = 0x024, - HEX_CAUSE_PRIV_NO_UWRITE = 0x025, + HEX_CAUSE_BIU_PRECISE = 0x001, + HEX_CAUSE_UNSUPORTED_HVX_64B = 0x002, /* QEMU-specific */ + HEX_CAUSE_DOUBLE_EXCEPT = 0x003, + HEX_CAUSE_TRAP0 = 0x008, + HEX_CAUSE_TRAP1 = 0x009, + HEX_CAUSE_FETCH_NO_XPAGE = 0x011, + HEX_CAUSE_FETCH_NO_UPAGE = 0x012, + HEX_CAUSE_INVALID_PACKET = 0x015, + HEX_CAUSE_INVALID_OPCODE = 0x015, + HEX_CAUSE_NO_COPROC_ENABLE = 0x016, + HEX_CAUSE_NO_COPROC2_ENABLE = 0x018, HEX_CAUSE_PRIV_USER_NO_GINSN = 0x01a, HEX_CAUSE_PRIV_USER_NO_SINSN = 0x01b, + HEX_CAUSE_REG_WRITE_CONFLICT = 0x01d, + HEX_CAUSE_PC_NOT_ALIGNED = 0x01e, + HEX_CAUSE_MISALIGNED_LOAD = 0x020, + HEX_CAUSE_MISALIGNED_STORE = 0x021, + HEX_CAUSE_PRIV_NO_READ = 0x022, + HEX_CAUSE_PRIV_NO_WRITE = 0x023, + HEX_CAUSE_PRIV_NO_UREAD = 0x024, + HEX_CAUSE_PRIV_NO_UWRITE = 0x025, + HEX_CAUSE_COPROC_LDST = 0x026, + HEX_CAUSE_STACK_LIMIT = 0x027, + HEX_CAUSE_VWCTRL_WINDOW_MISS = 0x029, + HEX_CAUSE_IMPRECISE_NMI = 0x043, + HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH = 0x044, + HEX_CAUSE_TLBMISSX_CAUSE_NORMAL = 0x060, + HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE = 0x061, + HEX_CAUSE_TLBMISSRW_CAUSE_READ = 0x070, + HEX_CAUSE_TLBMISSRW_CAUSE_WRITE = 0x071, + HEX_CAUSE_DEBUG_SINGLESTEP = 0x80, + HEX_CAUSE_FPTRAP_CAUSE_BADFLOAT = 0x0bf, HEX_CAUSE_INT0 = 0x0c0, HEX_CAUSE_INT1 = 0x0c1, HEX_CAUSE_INT2 = 0x0c2, From 3a4cbdd524ad4f1c513891fd842ae24eba611edb Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 10:22:14 -0700 Subject: [PATCH 1110/1179] target/hexagon: Implement hex_tlb_entry_get_perm() Signed-off-by: Brian Cain --- target/hexagon/hex_mmu.c | 54 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c index 94330ac194d8..582bf42efc4f 100644 --- a/target/hexagon/hex_mmu.c +++ b/target/hexagon/hex_mmu.c @@ -268,7 +268,59 @@ static inline void hex_tlb_entry_get_perm(CPUHexagonState *env, uint64_t entry, int mmu_idx, int *prot, int32_t *excp) { - g_assert_not_reached(); + bool perm_x = GET_TLB_FIELD(entry, PTE_X); + bool perm_w = GET_TLB_FIELD(entry, PTE_W); + bool perm_r = GET_TLB_FIELD(entry, PTE_R); + bool perm_u = GET_TLB_FIELD(entry, PTE_U); + bool user_idx = mmu_idx == MMU_USER_IDX; + + if (mmu_idx == MMU_KERNEL_IDX) { + *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return; + } + + *prot = PAGE_VALID; + switch (access_type) { + case MMU_INST_FETCH: + if (user_idx && !perm_u) { + *excp = HEX_EVENT_PRECISE; + env->cause_code = HEX_CAUSE_FETCH_NO_UPAGE; + } else if (!perm_x) { + *excp = HEX_EVENT_PRECISE; + env->cause_code = HEX_CAUSE_FETCH_NO_XPAGE; + } + break; + case MMU_DATA_LOAD: + if (user_idx && !perm_u) { + *excp = HEX_EVENT_PRECISE; + env->cause_code = HEX_CAUSE_PRIV_NO_UREAD; + } else if (!perm_r) { + *excp = HEX_EVENT_PRECISE; + env->cause_code = HEX_CAUSE_PRIV_NO_READ; + } + break; + case MMU_DATA_STORE: + if (user_idx && !perm_u) { + *excp = HEX_EVENT_PRECISE; + env->cause_code = HEX_CAUSE_PRIV_NO_UWRITE; + } else if (!perm_w) { + *excp = HEX_EVENT_PRECISE; + env->cause_code = HEX_CAUSE_PRIV_NO_WRITE; + } + break; + } + + if (!user_idx || perm_u) { + if (perm_x) { + *prot |= PAGE_EXEC; + } + if (perm_r) { + *prot |= PAGE_READ; + } + if (perm_w) { + *prot |= PAGE_WRITE; + } + } } static inline bool hex_tlb_entry_match(CPUHexagonState *env, uint64_t entry, From 0108f4d8f54b4e1a1f69c57eeb74105d6605a33b Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 14:33:40 -0700 Subject: [PATCH 1111/1179] target/hexagon: Implement hex_tlb_lookup_by_asid() Signed-off-by: Brian Cain --- target/hexagon/hex_mmu.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c index 582bf42efc4f..5ba0c3e1e8df 100644 --- a/target/hexagon/hex_mmu.c +++ b/target/hexagon/hex_mmu.c @@ -363,7 +363,31 @@ bool hex_tlb_find_match(CPUHexagonState *env, target_ulong VA, static uint32_t hex_tlb_lookup_by_asid(CPUHexagonState *env, uint32_t asid, uint32_t VA) { - g_assert_not_reached(); + uint32_t not_found = 0x80000000; + uint32_t idx = not_found; + int i; + + HexagonCPU *cpu = env_archcpu(env); + for (i = 0; i < cpu->num_tlbs; i++) { + uint64_t entry = env->hex_tlb->entries[i]; + if (hex_tlb_entry_match_noperm(entry, asid, VA)) { + if (idx != not_found) { + env->cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH; + break; + } + idx = i; + } + } + + if (idx == not_found) { + qemu_log_mask(CPU_LOG_MMU, "%s: 0x%x, 0x%08x => NOT FOUND\n", + __func__, asid, VA); + } else { + qemu_log_mask(CPU_LOG_MMU, "%s: 0x%x, 0x%08x => %d\n", + __func__, asid, VA, idx); + } + + return idx; } /* Called from tlbp instruction */ From 4a3837bcb15a6fe14db55303781fe6917ba56686 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 14:38:50 -0700 Subject: [PATCH 1112/1179] target/hexagon: Implement software interrupt Co-authored-by: Mike Lambert Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 2 + target/hexagon/cpu.h | 1 - target/hexagon/hexswi.c | 258 +++++++++++++++++++++++++++++++++++++ target/hexagon/hexswi.h | 17 +++ target/hexagon/op_helper.c | 1 + 5 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 target/hexagon/hexswi.c create mode 100644 target/hexagon/hexswi.h diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 5316800a6e49..ca9c12c3da5c 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -33,6 +33,8 @@ #ifndef CONFIG_USER_ONLY #include "sys_macros.h" #include "qemu/main-loop.h" +#include "hex_interrupts.h" +#include "hexswi.h" #endif static void hexagon_v66_cpu_init(Object *obj) { } diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index e1717dd303bc..4da1dcd7cb2f 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -260,5 +260,4 @@ typedef HexagonCPU ArchCPU; void hexagon_translate_init(void); void hexagon_translate_code(CPUState *cs, TranslationBlock *tb, int *max_insns, vaddr pc, void *host_pc); - #endif /* HEXAGON_CPU_H */ diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c new file mode 100644 index 000000000000..5fcf9b2be933 --- /dev/null +++ b/target/hexagon/hexswi.c @@ -0,0 +1,258 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#ifdef CONFIG_USER_ONLY +#include "exec/helper-proto.h" +#include "qemu.h" +#endif +#include "exec/cpu_ldst.h" +#include "exec/exec-all.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "arch.h" +#include "internal.h" +#include "macros.h" +#include "sys_macros.h" +#include "tcg/tcg-op.h" +#ifndef CONFIG_USER_ONLY +#include "hex_mmu.h" +#include "hexswi.h" +#endif + +#ifndef CONFIG_USER_ONLY + + +static void set_addresses(CPUHexagonState *env, target_ulong pc_offset, + target_ulong exception_index) + +{ + arch_set_system_reg(env, HEX_SREG_ELR, + arch_get_thread_reg(env, HEX_REG_PC) + pc_offset); + arch_set_thread_reg(env, HEX_REG_PC, + arch_get_system_reg(env, HEX_SREG_EVB) | + (exception_index << 2)); +} + +static const char *event_name[] = { + [HEX_EVENT_RESET] = "HEX_EVENT_RESET", + [HEX_EVENT_IMPRECISE] = "HEX_EVENT_IMPRECISE", + [HEX_EVENT_TLB_MISS_X] = "HEX_EVENT_TLB_MISS_X", + [HEX_EVENT_TLB_MISS_RW] = "HEX_EVENT_TLB_MISS_RW", + [HEX_EVENT_TRAP0] = "HEX_EVENT_TRAP0", + [HEX_EVENT_TRAP1] = "HEX_EVENT_TRAP1", + [HEX_EVENT_FPTRAP] = "HEX_EVENT_FPTRAP", + [HEX_EVENT_DEBUG] = "HEX_EVENT_DEBUG", + [HEX_EVENT_INT0] = "HEX_EVENT_INT0", + [HEX_EVENT_INT1] = "HEX_EVENT_INT1", + [HEX_EVENT_INT2] = "HEX_EVENT_INT2", + [HEX_EVENT_INT3] = "HEX_EVENT_INT3", + [HEX_EVENT_INT4] = "HEX_EVENT_INT4", + [HEX_EVENT_INT5] = "HEX_EVENT_INT5", + [HEX_EVENT_INT6] = "HEX_EVENT_INT6", + [HEX_EVENT_INT7] = "HEX_EVENT_INT7", + [HEX_EVENT_INT8] = "HEX_EVENT_INT8", + [HEX_EVENT_INT9] = "HEX_EVENT_INT9", + [HEX_EVENT_INTA] = "HEX_EVENT_INTA", + [HEX_EVENT_INTB] = "HEX_EVENT_INTB", + [HEX_EVENT_INTC] = "HEX_EVENT_INTC", + [HEX_EVENT_INTD] = "HEX_EVENT_INTD", + [HEX_EVENT_INTE] = "HEX_EVENT_INTE", + [HEX_EVENT_INTF] = "HEX_EVENT_INTF" +}; + +void hexagon_cpu_do_interrupt(CPUState *cs) + +{ + CPUHexagonState *env = cpu_env(cs); + BQL_LOCK_GUARD(); + + qemu_log_mask(CPU_LOG_INT, "\t%s: event 0x%x:%s, cause 0x%x(%d)\n", + __func__, cs->exception_index, + event_name[cs->exception_index], env->cause_code, + env->cause_code); + + env->llsc_addr = ~0; + + uint32_t ssr = arch_get_system_reg(env, HEX_SREG_SSR); + if (GET_SSR_FIELD(SSR_EX, ssr) == 1) { + arch_set_system_reg(env, HEX_SREG_DIAG, env->cause_code); + env->cause_code = HEX_CAUSE_DOUBLE_EXCEPT; + cs->exception_index = HEX_EVENT_PRECISE; + } + + switch (cs->exception_index) { + case HEX_EVENT_TRAP0: + if (env->cause_code == 0) { + qemu_log_mask(LOG_UNIMP, + "trap0 is unhandled, no semihosting available\n"); + } + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 4, cs->exception_index); + break; + + case HEX_EVENT_TRAP1: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 4, cs->exception_index); + break; + + case HEX_EVENT_TLB_MISS_X: + switch (env->cause_code) { + case HEX_CAUSE_TLBMISSX_CAUSE_NORMAL: + case HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE: + qemu_log_mask(CPU_LOG_MMU, + "TLB miss EX exception (0x%x) caught: " + "Cause code (0x%x) " + "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32 + ", BADVA = 0x%" PRIx32 "\n", + cs->exception_index, env->cause_code, env->threadId, + arch_get_thread_reg(env, HEX_REG_PC), + arch_get_system_reg(env, HEX_SREG_BADVA)); + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + default: + cpu_abort(cs, + "1:Hexagon exception %d/0x%x: " + "Unknown cause code %d/0x%x\n", + cs->exception_index, cs->exception_index, env->cause_code, + env->cause_code); + break; + } + break; + + case HEX_EVENT_TLB_MISS_RW: + switch (env->cause_code) { + case HEX_CAUSE_TLBMISSRW_CAUSE_READ: + case HEX_CAUSE_TLBMISSRW_CAUSE_WRITE: + qemu_log_mask(CPU_LOG_MMU, + "TLB miss RW exception (0x%x) caught: " + "Cause code (0x%x) " + "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32 + ", BADVA = 0x%" PRIx32 "\n", + cs->exception_index, env->cause_code, env->threadId, + env->gpr[HEX_REG_PC], + arch_get_system_reg(env, HEX_SREG_BADVA)); + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + /* env->sreg[HEX_SREG_BADVA] is set when the exception is raised */ + break; + + default: + cpu_abort(cs, + "2:Hexagon exception %d/0x%x: " + "Unknown cause code %d/0x%x\n", + cs->exception_index, cs->exception_index, env->cause_code, + env->cause_code); + break; + } + break; + + case HEX_EVENT_FPTRAP: + hexagon_ssr_set_cause(env, env->cause_code); + arch_set_thread_reg(env, HEX_REG_PC, + arch_get_system_reg(env, HEX_SREG_EVB) | + (cs->exception_index << 2)); + break; + + case HEX_EVENT_DEBUG: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + qemu_log_mask(LOG_UNIMP, "single-step exception is not handled\n"); + break; + + case HEX_EVENT_PRECISE: + switch (env->cause_code) { + case HEX_CAUSE_FETCH_NO_XPAGE: + case HEX_CAUSE_FETCH_NO_UPAGE: + case HEX_CAUSE_PRIV_NO_READ: + case HEX_CAUSE_PRIV_NO_UREAD: + case HEX_CAUSE_PRIV_NO_WRITE: + case HEX_CAUSE_PRIV_NO_UWRITE: + case HEX_CAUSE_MISALIGNED_LOAD: + case HEX_CAUSE_MISALIGNED_STORE: + case HEX_CAUSE_PC_NOT_ALIGNED: + qemu_log_mask(CPU_LOG_MMU, + "MMU permission exception (0x%x) caught: " + "Cause code (0x%x) " + "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32 + ", BADVA = 0x%" PRIx32 "\n", + cs->exception_index, env->cause_code, env->threadId, + env->gpr[HEX_REG_PC], + arch_get_system_reg(env, HEX_SREG_BADVA)); + + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + /* env->sreg[HEX_SREG_BADVA] is set when the exception is raised */ + break; + + case HEX_CAUSE_DOUBLE_EXCEPT: + case HEX_CAUSE_PRIV_USER_NO_SINSN: + case HEX_CAUSE_PRIV_USER_NO_GINSN: + case HEX_CAUSE_INVALID_OPCODE: + case HEX_CAUSE_NO_COPROC_ENABLE: + case HEX_CAUSE_NO_COPROC2_ENABLE: + case HEX_CAUSE_UNSUPORTED_HVX_64B: + case HEX_CAUSE_REG_WRITE_CONFLICT: + case HEX_CAUSE_VWCTRL_WINDOW_MISS: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + case HEX_CAUSE_COPROC_LDST: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + case HEX_CAUSE_STACK_LIMIT: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + default: + cpu_abort(cs, + "3:Hexagon exception %d/0x%x: " + "Unknown cause code %d/0x%x\n", + cs->exception_index, cs->exception_index, env->cause_code, + env->cause_code); + break; + } + break; + + case HEX_EVENT_IMPRECISE: + qemu_log_mask(LOG_UNIMP, + "Imprecise exception: this case is not yet handled"); + break; + + default: + qemu_log_mask(LOG_UNIMP, + "Hexagon Unsupported exception 0x%x/0x%x\n", + cs->exception_index, env->cause_code); + break; + } + + cs->exception_index = HEX_EVENT_NONE; +} + +void register_trap_exception(CPUHexagonState *env, int traptype, int imm, + target_ulong PC) +{ + CPUState *cs = env_cpu(env); + + cs->exception_index = (traptype == 0) ? HEX_EVENT_TRAP0 : HEX_EVENT_TRAP1; + ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index); + + env->cause_code = imm; + env->gpr[HEX_REG_PC] = PC; + cpu_loop_exit(cs); +} +#endif diff --git a/target/hexagon/hexswi.h b/target/hexagon/hexswi.h new file mode 100644 index 000000000000..5d232cb06cb0 --- /dev/null +++ b/target/hexagon/hexswi.h @@ -0,0 +1,17 @@ +/* + * Copyright(c) 2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXSWI_H +#define HEXSWI_H + + +#include "cpu.h" + +void hexagon_cpu_do_interrupt(CPUState *cpu); +void register_trap_exception(CPUHexagonState *env, int type, int imm, + target_ulong PC); + +#endif /* HEXSWI_H */ diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 42805d0f1d03..687e7f45c27a 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -38,6 +38,7 @@ #include "hex_mmu.h" #include "hw/intc/l2vic.h" #include "hex_interrupts.h" +#include "hexswi.h" #endif #define SF_BIAS 127 From 2b8d255a3c1c535604cc3a829b3d6ee4acf6adb8 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 17:59:42 -0700 Subject: [PATCH 1113/1179] target/hexagon: Implement exec_interrupt, set_irq Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 78 ++++++++++++++++++++++++++++++++++++++++++++ target/hexagon/cpu.h | 5 +++ 2 files changed, 83 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index ca9c12c3da5c..e9b11ecb9d05 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -31,6 +31,7 @@ #include "hex_mmu.h" #ifndef CONFIG_USER_ONLY +#include "macros.h" #include "sys_macros.h" #include "qemu/main-loop.h" #include "hex_interrupts.h" @@ -277,6 +278,30 @@ static void hexagon_cpu_synchronize_from_tb(CPUState *cs, cpu_env(cs)->gpr[HEX_REG_PC] = tb->pc; } +#ifndef CONFIG_USER_ONLY +bool hexagon_thread_is_enabled(CPUHexagonState *env) +{ + target_ulong modectl = arch_get_system_reg(env, HEX_SREG_MODECTL); + uint32_t thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + bool E_bit = thread_enabled_mask & (0x1 << env->threadId); + + return E_bit; +} +#endif + +static bool hexagon_cpu_has_work(CPUState *cs) +{ +#ifndef CONFIG_USER_ONLY + CPUHexagonState *env = cpu_env(cs); + + return hexagon_thread_is_enabled(env) && + (cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI + | CPU_INTERRUPT_K0_UNLOCK | CPU_INTERRUPT_TLB_UNLOCK)); +#else + return true; +#endif +} + static void hexagon_restore_state_to_opc(CPUState *cs, const TranslationBlock *tb, const uint64_t *data) @@ -406,19 +431,72 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) mcc->parent_realize(dev, errp); } +#if !defined(CONFIG_USER_ONLY) +static void hexagon_cpu_set_irq(void *opaque, int irq, int level) +{ + HexagonCPU *cpu = HEXAGON_CPU(opaque); + CPUState *cs = CPU(cpu); + CPUHexagonState *env = cpu_env(cs); + + switch (irq) { + case HEXAGON_CPU_IRQ_0 ... HEXAGON_CPU_IRQ_7: + qemu_log_mask(CPU_LOG_INT, "%s: irq %d, level %d\n", + __func__, irq, level); + if (level) { + hex_raise_interrupts(env, 1 << irq, CPU_INTERRUPT_HARD); + } + break; + default: + g_assert_not_reached(); + } +} +#endif + + static void hexagon_cpu_init(Object *obj) { +#if !defined(CONFIG_USER_ONLY) + HexagonCPU *cpu = HEXAGON_CPU(obj); + qdev_init_gpio_in(DEVICE(cpu), hexagon_cpu_set_irq, 8); +#endif } #include "accel/tcg/cpu-ops.h" +#ifndef CONFIG_USER_ONLY + +static bool hexagon_cpu_exec_interrupt(CPUState *cs, int interrupt_request) +{ + CPUHexagonState *env = cpu_env(cs); + if (interrupt_request & CPU_INTERRUPT_TLB_UNLOCK) { + cs->halted = false; + cpu_reset_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK); + return true; + } + if (interrupt_request & CPU_INTERRUPT_K0_UNLOCK) { + cs->halted = false; + cpu_reset_interrupt(cs, CPU_INTERRUPT_K0_UNLOCK); + return true; + } + if (interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI)) { + return hex_check_interrupts(env); + } + return false; +} + +#endif + static const TCGCPUOps hexagon_tcg_ops = { .initialize = hexagon_translate_init, .translate_code = hexagon_translate_code, .synchronize_from_tb = hexagon_cpu_synchronize_from_tb, .restore_state_to_opc = hexagon_restore_state_to_opc, +#if !defined(CONFIG_USER_ONLY) + .cpu_exec_interrupt = hexagon_cpu_exec_interrupt, +#endif /* !CONFIG_USER_ONLY */ }; + static void hexagon_cpu_class_init(ObjectClass *c, void *data) { HexagonCPUClass *mcc = HEXAGON_CPU_CLASS(c); diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 4da1dcd7cb2f..545fad0e295e 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -211,6 +211,11 @@ G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uintptr_t pc); #ifndef CONFIG_USER_ONLY +/* + * @return true if the @a thread_env hardware thread is + * not stopped. + */ +bool hexagon_thread_is_enabled(CPUHexagonState *thread_env); uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg); uint32_t hexagon_sreg_read(CPUHexagonState *env, uint32_t reg); void hexagon_gdb_sreg_write(CPUHexagonState *env, uint32_t reg, uint32_t val); From 89b73a4cc7015272d6a5f9c553dc8ddab8c40da5 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 18:36:52 -0700 Subject: [PATCH 1114/1179] target/hexagon: Implement hexagon_tlb_fill() Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 133 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index e9b11ecb9d05..a2cb8198651d 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -463,7 +463,137 @@ static void hexagon_cpu_init(Object *obj) #include "accel/tcg/cpu-ops.h" -#ifndef CONFIG_USER_ONLY +#if !defined(CONFIG_USER_ONLY) +static bool get_physical_address(CPUHexagonState *env, hwaddr *phys, int *prot, + int *size, int32_t *excp, target_ulong address, + MMUAccessType access_type, int mmu_idx) + +{ + if (hexagon_cpu_mmu_enabled(env)) { + return hex_tlb_find_match(env, address, access_type, phys, prot, size, + excp, mmu_idx); + } else { + *phys = address & 0xFFFFFFFF; + *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC; + *size = TARGET_PAGE_SIZE; + return true; + } +} + +/* qemu seems to only want to know about TARGET_PAGE_SIZE pages */ +static void find_qemu_subpage(vaddr *addr, hwaddr *phys, int page_size) +{ + vaddr page_start = *addr & ~((vaddr)(page_size - 1)); + vaddr offset = ((*addr - page_start) / TARGET_PAGE_SIZE) * TARGET_PAGE_SIZE; + *addr = page_start + offset; + *phys += offset; +} + + +#define INVALID_BADVA 0xbadabada + +static void set_badva_regs(CPUHexagonState *env, target_ulong VA, int slot, + MMUAccessType access_type) +{ + arch_set_system_reg(env, HEX_SREG_BADVA, VA); + + if (access_type == MMU_INST_FETCH || slot == 0) { + arch_set_system_reg(env, HEX_SREG_BADVA0, VA); + arch_set_system_reg(env, HEX_SREG_BADVA1, INVALID_BADVA); + SET_SSR_FIELD(env, SSR_V0, 1); + SET_SSR_FIELD(env, SSR_V1, 0); + SET_SSR_FIELD(env, SSR_BVS, 0); + } else if (slot == 1) { + arch_set_system_reg(env, HEX_SREG_BADVA0, INVALID_BADVA); + arch_set_system_reg(env, HEX_SREG_BADVA1, VA); + SET_SSR_FIELD(env, SSR_V0, 0); + SET_SSR_FIELD(env, SSR_V1, 1); + SET_SSR_FIELD(env, SSR_BVS, 1); + } else { + g_assert_not_reached(); + } +} + +static void raise_tlbmiss_exception(CPUState *cs, target_ulong VA, int slot, + MMUAccessType access_type) +{ + CPUHexagonState *env = cpu_env(cs); + + set_badva_regs(env, VA, slot, access_type); + + switch (access_type) { + case MMU_INST_FETCH: + cs->exception_index = HEX_EVENT_TLB_MISS_X; + if ((VA & ~TARGET_PAGE_MASK) == 0) { + env->cause_code = HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE; + } else { + env->cause_code = HEX_CAUSE_TLBMISSX_CAUSE_NORMAL; + } + break; + case MMU_DATA_LOAD: + cs->exception_index = HEX_EVENT_TLB_MISS_RW; + env->cause_code = HEX_CAUSE_TLBMISSRW_CAUSE_READ; + break; + case MMU_DATA_STORE: + cs->exception_index = HEX_EVENT_TLB_MISS_RW; + env->cause_code = HEX_CAUSE_TLBMISSRW_CAUSE_WRITE; + break; + } +} + +static void raise_perm_exception(CPUState *cs, target_ulong VA, int slot, + MMUAccessType access_type, int32_t excp) +{ + CPUHexagonState *env = cpu_env(cs); + + set_badva_regs(env, VA, slot, access_type); + cs->exception_index = excp; +} + +static const char *access_type_names[] = { "MMU_DATA_LOAD ", "MMU_DATA_STORE", + "MMU_INST_FETCH" }; + +static const char *mmu_idx_names[] = { "MMU_USER_IDX", "MMU_GUEST_IDX", + "MMU_KERNEL_IDX" }; + +static bool hexagon_tlb_fill(CPUState *cs, vaddr address, int size, + MMUAccessType access_type, int mmu_idx, bool probe, + uintptr_t retaddr) +{ + CPUHexagonState *env = cpu_env(cs); + static int slot = 0 /* This is always zero for now */; + hwaddr phys; + int prot = 0; + int page_size = 0; + int32_t excp = 0; + bool ret = 0; + + qemu_log_mask( + CPU_LOG_MMU, + "%s: tid = 0x%x, pc = 0x%08" PRIx32 ", vaddr = 0x%08" VADDR_PRIx + ", size = %d, %s,\tprobe = %d, %s\n", + __func__, env->threadId, env->gpr[HEX_REG_PC], address, size, + access_type_names[access_type], probe, mmu_idx_names[mmu_idx]); + ret = get_physical_address(env, &phys, &prot, &page_size, &excp, address, + access_type, mmu_idx); + if (ret) { + if (!excp) { + find_qemu_subpage(&address, &phys, page_size); + tlb_set_page(cs, address, phys, prot, mmu_idx, TARGET_PAGE_SIZE); + return ret; + } else { + raise_perm_exception(cs, address, slot, access_type, excp); + do_raise_exception(env, cs->exception_index, env->gpr[HEX_REG_PC], + retaddr); + } + } + if (probe) { + return false; + } + raise_tlbmiss_exception(cs, address, slot, access_type); + do_raise_exception(env, cs->exception_index, env->gpr[HEX_REG_PC], retaddr); +} + static bool hexagon_cpu_exec_interrupt(CPUState *cs, int interrupt_request) { @@ -493,6 +623,7 @@ static const TCGCPUOps hexagon_tcg_ops = { .restore_state_to_opc = hexagon_restore_state_to_opc, #if !defined(CONFIG_USER_ONLY) .cpu_exec_interrupt = hexagon_cpu_exec_interrupt, + .tlb_fill = hexagon_tlb_fill, #endif /* !CONFIG_USER_ONLY */ }; From 93cc29e60428dba8bea43b29f38fa5948c2e544c Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 18:42:58 -0700 Subject: [PATCH 1115/1179] target/hexagon: Implement siad inst siad is the 'Set interrupt auto disable' instruction. Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 687e7f45c27a..118f112487a1 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1378,7 +1378,15 @@ void HELPER(ciad)(CPUHexagonState *env, uint32_t mask) void HELPER(siad)(CPUHexagonState *env, uint32_t mask) { - g_assert_not_reached(); + uint32_t ipendad; + uint32_t iad; + + BQL_LOCK_GUARD(); + ipendad = READ_SREG(HEX_SREG_IPENDAD); + iad = fGET_FIELD(ipendad, IPENDAD_IAD); + fSET_FIELD(ipendad, IPENDAD_IAD, iad | mask); + arch_set_system_reg(env, HEX_SREG_IPENDAD, ipendad); + hex_interrupt_update(env); } void HELPER(swi)(CPUHexagonState *env, uint32_t mask) From d88274b35b46f6b278732356653974bb69f231bd Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 18:51:44 -0700 Subject: [PATCH 1116/1179] target/hexagon: Implement hexagon_resume_threads() Signed-off-by: Brian Cain --- target/hexagon/cpu.h | 1 + target/hexagon/cpu_helper.c | 37 +++++++++++++++++++++++++++++++++++++ target/hexagon/cpu_helper.h | 1 + target/hexagon/op_helper.c | 3 ++- 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 545fad0e295e..02f148054cd1 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -49,6 +49,7 @@ typedef struct CPUHexagonTLBContext CPUHexagonTLBContext; #define REG_WRITES_MAX 32 #define PRED_WRITES_MAX 5 /* 4 insns + endloop */ #define VSTORES_MAX 2 +#define THREADS_MAX 8 #define VECTOR_UNIT_MAX 8 #ifndef CONFIG_USER_ONLY diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index 89ceb92b4a8a..c8ed9b3cc5db 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -125,6 +125,43 @@ void hexagon_wait_thread(CPUHexagonState *env, target_ulong PC) cpu_interrupt(cs, CPU_INTERRUPT_HALT); } +static void hexagon_resume_thread(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + clear_wait_mode(env); + /* + * The wait instruction keeps the PC pointing to itself + * so that it has an opportunity to check for interrupts. + * + * When we come out of wait mode, adjust the PC to the + * next executable instruction. + */ + env->gpr[HEX_REG_PC] = env->wait_next_pc; + cs = env_cpu(env); + ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index); + cs->halted = false; + cs->exception_index = HEX_EVENT_NONE; + qemu_cpu_kick(cs); +} + +void hexagon_resume_threads(CPUHexagonState *current_env, uint32_t mask) +{ + CPUState *cs; + CPUHexagonState *env; + + g_assert(bql_locked()); + CPU_FOREACH(cs) { + env = cpu_env(cs); + g_assert(env->threadId < THREADS_MAX); + if ((mask & (0x1 << env->threadId))) { + if (get_exe_mode(env) == HEX_EXE_MODE_WAIT) { + hexagon_resume_thread(env); + } + } + } +} + + static MMVector VRegs[VECTOR_UNIT_MAX][NUM_VREGS]; static MMQReg QRegs[VECTOR_UNIT_MAX][NUM_QREGS]; diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index 1cdf4f8dd0ed..0723485e79b0 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -21,6 +21,7 @@ void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause); void hexagon_start_threads(CPUHexagonState *env, uint32_t mask); void hexagon_stop_thread(CPUHexagonState *env); void hexagon_wait_thread(CPUHexagonState *env, target_ulong PC); +void hexagon_resume_threads(CPUHexagonState *env, uint32_t mask); static inline void arch_set_thread_reg(CPUHexagonState *env, uint32_t reg, uint32_t val) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 118f112487a1..0dce133d3a6e 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1476,7 +1476,8 @@ void HELPER(wait)(CPUHexagonState *env, target_ulong PC) void HELPER(resume)(CPUHexagonState *env, uint32_t mask) { - g_assert_not_reached(); + BQL_LOCK_GUARD(); + hexagon_resume_threads(env, mask); } uint32_t HELPER(getimask)(CPUHexagonState *env, uint32_t tid) From de58b1fa3f60bc1253d5677482abda33e2dc4cc1 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 18:54:12 -0700 Subject: [PATCH 1117/1179] target/hexagon: Implement setprio, resched MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardware-assisted scheduler helps manage tasks on the run queue and interrupt steering. This instruction is defined in the Qualcomm Hexagon V71 Programmer's Reference Manual - https://docs.qualcomm.com/bundle/publicresource/80-N2040-51_REV_AB_Hexagon_V71_ProgrammerS_Reference_Manual.pdf See §11.9.2 SYSTEM MONITOR. Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 65 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 0dce133d3a6e..d0dc4afac7e9 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1465,6 +1465,57 @@ void HELPER(stop)(CPUHexagonState *env) hexagon_stop_thread(env); } +static inline QEMU_ALWAYS_INLINE void resched(CPUHexagonState *env) +{ + uint32_t schedcfg; + uint32_t schedcfg_en; + int int_number; + CPUState *cs; + uint32_t lowest_th_prio = 0; /* 0 is highest prio */ + uint32_t bestwait_reg; + uint32_t best_prio; + + BQL_LOCK_GUARD(); + qemu_log_mask(CPU_LOG_INT, "%s: check resched\n", __func__); + schedcfg = arch_get_system_reg(env, HEX_SREG_SCHEDCFG); + schedcfg_en = GET_FIELD(SCHEDCFG_EN, schedcfg); + int_number = GET_FIELD(SCHEDCFG_INTNO, schedcfg); + + if (!schedcfg_en) { + return; + } + + CPU_FOREACH(cs) { + HexagonCPU *thread = HEXAGON_CPU(cs); + CPUHexagonState *thread_env = &(thread->env); + uint32_t th_prio = GET_FIELD( + STID_PRIO, arch_get_system_reg(thread_env, HEX_SREG_STID)); + if (!hexagon_thread_is_enabled(thread_env)) { + continue; + } + + lowest_th_prio = (lowest_th_prio > th_prio) + ? lowest_th_prio + : th_prio; + } + + bestwait_reg = arch_get_system_reg(env, HEX_SREG_BESTWAIT); + best_prio = GET_FIELD(BESTWAIT_PRIO, bestwait_reg); + + /* + * If the lowest priority thread is lower priority than the + * value in the BESTWAIT register, we must raise the reschedule + * interrupt on the lowest priority thread. + */ + if (lowest_th_prio > best_prio) { + qemu_log_mask(CPU_LOG_INT, + "%s: raising resched int %d, cur PC 0x" TARGET_FMT_lx "\n", + __func__, int_number, arch_get_thread_reg(env, HEX_REG_PC)); + SET_SYSTEM_FIELD(env, HEX_SREG_BESTWAIT, BESTWAIT_PRIO, 0x1ff); + hex_raise_interrupts(env, 1 << int_number, CPU_INTERRUPT_SWI); + } +} + void HELPER(wait)(CPUHexagonState *env, target_ulong PC) { BQL_LOCK_GUARD(); @@ -1715,6 +1766,20 @@ uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg) void HELPER(setprio)(CPUHexagonState *env, uint32_t thread, uint32_t prio) { + CPUState *cs; + + BQL_LOCK_GUARD(); + CPU_FOREACH(cs) { + HexagonCPU *found_cpu = HEXAGON_CPU(cs); + CPUHexagonState *found_env = &found_cpu->env; + if (thread == found_env->threadId) { + SET_SYSTEM_FIELD(found_env, HEX_SREG_STID, STID_PRIO, prio); + qemu_log_mask(CPU_LOG_INT, "%s: tid %d prio = 0x%x\n", + __func__, found_env->threadId, prio); + resched(env); + return; + } + } g_assert_not_reached(); } From d13160efdad8f6dec09179d6eef25b76514d2890 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 20:27:35 -0700 Subject: [PATCH 1118/1179] target/hexagon: Add sysemu_ops, cpu_get_phys_page_debug() Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index a2cb8198651d..1d9911db39f3 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -20,6 +20,7 @@ #include "cpu.h" #include "internal.h" #include "exec/exec-all.h" +#include "exec/cputlb.h" #include "exec/translation-block.h" #include "qapi/error.h" #include "hw/qdev-properties.h" @@ -489,6 +490,24 @@ static void find_qemu_subpage(vaddr *addr, hwaddr *phys, int page_size) *phys += offset; } +static hwaddr hexagon_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) +{ + CPUHexagonState *env = cpu_env(cs); + hwaddr phys_addr; + int prot; + int page_size = 0; + int32_t excp = 0; + int mmu_idx = MMU_KERNEL_IDX; + + if (get_physical_address(env, &phys_addr, &prot, &page_size, &excp, + addr, 0, mmu_idx)) { + find_qemu_subpage(&addr, &phys_addr, page_size); + return phys_addr; + } + + return -1; +} + #define INVALID_BADVA 0xbadabada @@ -595,6 +614,13 @@ static bool hexagon_tlb_fill(CPUState *cs, vaddr address, int size, } +#include "hw/core/sysemu-cpu-ops.h" + +static const struct SysemuCPUOps hexagon_sysemu_ops = { + .has_work = hexagon_cpu_has_work, + .get_phys_page_debug = hexagon_cpu_get_phys_page_debug, +}; + static bool hexagon_cpu_exec_interrupt(CPUState *cs, int interrupt_request) { CPUHexagonState *env = cpu_env(cs); @@ -624,6 +650,8 @@ static const TCGCPUOps hexagon_tcg_ops = { #if !defined(CONFIG_USER_ONLY) .cpu_exec_interrupt = hexagon_cpu_exec_interrupt, .tlb_fill = hexagon_tlb_fill, + .cpu_exec_halt = hexagon_cpu_has_work, + .do_interrupt = hexagon_cpu_do_interrupt, #endif /* !CONFIG_USER_ONLY */ }; @@ -652,9 +680,12 @@ static void hexagon_cpu_class_init(ObjectClass *c, void *data) cc->gdb_core_xml_file = "hexagon-core.xml"; cc->disas_set_info = hexagon_cpu_disas_set_info; #ifndef CONFIG_USER_ONLY + cc->sysemu_ops = &hexagon_sysemu_ops; dc->vmsd = &vmstate_hexagon_cpu; #endif +#ifdef CONFIG_TCG cc->tcg_ops = &hexagon_tcg_ops; +#endif } #ifndef CONFIG_USER_ONLY From 30d7806677ae9b2d3d488e2983ac36bc92ed3196 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 20:33:40 -0700 Subject: [PATCH 1119/1179] target/hexagon: Add exec-start-addr prop Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 7 ++----- target/hexagon/cpu.h | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 1d9911db39f3..6dc786f97a39 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -67,6 +67,7 @@ static const Property hexagon_cpu_properties[] = { DEFINE_PROP_UINT32("l2vic-base-addr", HexagonCPU, l2vic_base_addr, 0xffffffffULL), DEFINE_PROP_UINT32("hvx-contexts", HexagonCPU, hvx_contexts, 0), + DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffffULL), #endif DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, @@ -362,8 +363,6 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) mmu_reset(env); arch_set_system_reg(env, HEX_SREG_HTID, cs->cpu_index); hexagon_cpu_soft_reset(env); - memset(env->t_sreg, 0, sizeof(target_ulong) * NUM_SREGS); - memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS); env->threadId = cs->cpu_index; env->tlb_lock_state = HEX_LOCK_UNLOCKED; env->k0_lock_state = HEX_LOCK_UNLOCKED; @@ -372,6 +371,7 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) env->next_PC = 0; env->wait_next_pc = 0; env->cause_code = -1; + arch_set_thread_reg(env, HEX_REG_PC, cpu->boot_addr); #endif } @@ -415,9 +415,6 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) #ifndef CONFIG_USER_ONLY CPUHexagonState *env = cpu_env(cs); hex_mmu_realize(env); -#endif - cpu_reset(cs); -#ifndef CONFIG_USER_ONLY if (cs->cpu_index == 0) { env->g_sreg = g_new0(target_ulong, NUM_SREGS); env->g_pcycle_base = g_malloc0(sizeof(*env->g_pcycle_base)); diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 02f148054cd1..9a5a0bb78af7 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -198,6 +198,7 @@ struct ArchCPU { uint32_t num_tlbs; uint32_t l2vic_base_addr; uint32_t hvx_contexts; + uint32_t boot_addr; #endif }; From f61be7c4949f5ee129b8722be998856614419c5e Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 4 Sep 2024 21:18:54 -0700 Subject: [PATCH 1120/1179] target/hexagon: Add hexagon_cpu_mmu_index() Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 29 +++++++++++++++++++++----- target/hexagon/cpu_helper.c | 41 +++++++++++++++++++++++++++++++++++++ target/hexagon/cpu_helper.h | 1 + 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 6dc786f97a39..07ea3f54f287 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -289,20 +289,16 @@ bool hexagon_thread_is_enabled(CPUHexagonState *env) return E_bit; } -#endif static bool hexagon_cpu_has_work(CPUState *cs) { -#ifndef CONFIG_USER_ONLY CPUHexagonState *env = cpu_env(cs); return hexagon_thread_is_enabled(env) && (cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI | CPU_INTERRUPT_K0_UNLOCK | CPU_INTERRUPT_TLB_UNLOCK)); -#else - return true; -#endif } +#endif static void hexagon_restore_state_to_opc(CPUState *cs, const TranslationBlock *tb, @@ -652,6 +648,28 @@ static const TCGCPUOps hexagon_tcg_ops = { #endif /* !CONFIG_USER_ONLY */ }; +static int hexagon_cpu_mmu_index(CPUState *cs, bool ifetch) +{ +#ifndef CONFIG_USER_ONLY + BQL_LOCK_GUARD(); + CPUHexagonState *env = cpu_env(cs); + uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + uint8_t mmuen = GET_SYSCFG_FIELD(SYSCFG_MMUEN, syscfg); + if (!mmuen) { + return MMU_KERNEL_IDX; + } + + int cpu_mode = get_cpu_mode(env); + if (cpu_mode == HEX_CPU_MODE_MONITOR) { + return MMU_KERNEL_IDX; + } else if (cpu_mode == HEX_CPU_MODE_GUEST) { + return MMU_GUEST_IDX; + } +#endif + + return MMU_USER_IDX; +} + static void hexagon_cpu_class_init(ObjectClass *c, void *data) { @@ -668,6 +686,7 @@ static void hexagon_cpu_class_init(ObjectClass *c, void *data) &mcc->parent_phases); cc->class_by_name = hexagon_cpu_class_by_name; + cc->mmu_index = hexagon_cpu_mmu_index; cc->dump_state = hexagon_dump_state; cc->set_pc = hexagon_cpu_set_pc; cc->get_pc = hexagon_cpu_get_pc; diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index c8ed9b3cc5db..8de61ac05360 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -395,4 +395,45 @@ void hexagon_stop_thread(CPUHexagonState *env) } } +static int sys_in_monitor_mode_ssr(uint32_t ssr) +{ + if ((GET_SSR_FIELD(SSR_EX, ssr) != 0) || + ((GET_SSR_FIELD(SSR_EX, ssr) == 0) && (GET_SSR_FIELD(SSR_UM, ssr) == 0))) + return 1; + return 0; +} + +static int sys_in_guest_mode_ssr(uint32_t ssr) +{ + if ((GET_SSR_FIELD(SSR_EX, ssr) == 0) && + (GET_SSR_FIELD(SSR_UM, ssr) != 0) && + (GET_SSR_FIELD(SSR_GM, ssr) != 0)) + return 1; + return 0; +} + +static int sys_in_user_mode_ssr(uint32_t ssr) +{ + if ((GET_SSR_FIELD(SSR_EX, ssr) == 0) && + (GET_SSR_FIELD(SSR_UM, ssr) != 0) && + (GET_SSR_FIELD(SSR_GM, ssr) == 0)) + return 1; + return 0; +} + +int get_cpu_mode(CPUHexagonState *env) + +{ + uint32_t ssr = arch_get_system_reg(env, HEX_SREG_SSR); + + if (sys_in_monitor_mode_ssr(ssr)) { + return HEX_CPU_MODE_MONITOR; + } else if (sys_in_guest_mode_ssr(ssr)) { + return HEX_CPU_MODE_GUEST; + } else if (sys_in_user_mode_ssr(ssr)) { + return HEX_CPU_MODE_USER; + } + return HEX_CPU_MODE_MONITOR; +} + #endif diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index 0723485e79b0..0a5134204f3d 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -15,6 +15,7 @@ void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t); void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t); void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t); void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old); +int get_cpu_mode(CPUHexagonState *env); int get_exe_mode(CPUHexagonState *env); void clear_wait_mode(CPUHexagonState *env); void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause); From c7c312db598ed3db997d682345387b40bb478db0 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 5 Sep 2024 18:27:04 -0700 Subject: [PATCH 1121/1179] target/hexagon: Decode trap1, rte as COF Also: handle rte instructions at the end of the packet. Signed-off-by: Brian Cain --- target/hexagon/decode.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/target/hexagon/decode.c b/target/hexagon/decode.c index 23deba2426f8..5d0beeeaf2f1 100644 --- a/target/hexagon/decode.c +++ b/target/hexagon/decode.c @@ -193,6 +193,8 @@ static bool decode_opcode_can_jump(int opcode) if ((GET_ATTRIB(opcode, A_JUMP)) || (GET_ATTRIB(opcode, A_CALL)) || (opcode == J2_trap0) || + (opcode == J2_trap1) || + (opcode == J2_rte) || (opcode == J2_pause)) { /* Exception to A_JUMP attribute */ if (opcode == J4_hintjumpr) { @@ -371,6 +373,18 @@ static void decode_shuffle_for_execution(Packet *packet) break; } } + /* + * And at the very very very end, move any RTE's, since they update + * user/supervisor mode. + */ +#if !defined(CONFIG_USER_ONLY) + for (i = 0; i < last_insn; i++) { + if (packet->insn[i].opcode == J2_rte) { + decode_send_insn_to(packet, i, last_insn); + break; + } + } +#endif } static void From 8b250e35655d10296fe32db31e01fd6e2ed897ee Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sun, 8 Sep 2024 11:56:26 -0700 Subject: [PATCH 1122/1179] target/hexagon: Implement hexagon_find_last_irq() Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index d0dc4afac7e9..f3ffac81b68c 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1637,7 +1637,13 @@ static void modify_syscfg(CPUHexagonState *env, uint32_t val) static uint32_t hexagon_find_last_irq(CPUHexagonState *env, uint32_t vid) { - g_assert_not_reached(); + int offset = (vid == HEX_SREG_VID) ? L2VIC_VID_0 : L2VIC_VID_1; + CPUState *cs = env_cpu(env); + HexagonCPU *cpu = HEXAGON_CPU(cs); + const hwaddr pend_mem = cpu->l2vic_base_addr + offset; + uint32_t irq; + cpu_physical_memory_read(pend_mem, &irq, sizeof(irq)); + return irq; } static void hexagon_read_timer(CPUHexagonState *env, uint32_t *low, From a7a836b92dfcbf8159dcd761bd9a433f40e415f3 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 9 Sep 2024 09:34:18 -0700 Subject: [PATCH 1123/1179] target/hexagon: Implement modify_ssr, resched, pending_interrupt Signed-off-by: Brian Cain --- target/hexagon/helper.h | 3 +++ target/hexagon/op_helper.c | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index 730eaf8b9a0f..3df663baeb05 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -129,4 +129,7 @@ DEF_HELPER_1(stop, void, env) DEF_HELPER_2(wait, void, env, i32) DEF_HELPER_2(resume, void, env, i32) DEF_HELPER_2(nmi, void, env, i32) +DEF_HELPER_1(resched, void, env) +DEF_HELPER_3(modify_ssr, void, env, i32, i32) +DEF_HELPER_1(pending_interrupt, void, env) #endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index f3ffac81b68c..702c3dd3c643 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1178,6 +1178,15 @@ float64 HELPER(dfmpyhh)(CPUHexagonState *env, float64 RxxV, return RxxV; } +#ifndef CONFIG_USER_ONLY +void HELPER(modify_ssr)(CPUHexagonState *env, uint32_t new, uint32_t old) +{ + BQL_LOCK_GUARD(); + hexagon_modify_ssr(env, new, old); +} +#endif + + /* Histogram instructions */ void HELPER(vhist)(CPUHexagonState *env) @@ -1516,6 +1525,11 @@ static inline QEMU_ALWAYS_INLINE void resched(CPUHexagonState *env) } } +void HELPER(resched)(CPUHexagonState *env) +{ + resched(env); +} + void HELPER(wait)(CPUHexagonState *env, target_ulong PC) { BQL_LOCK_GUARD(); @@ -1793,6 +1807,12 @@ void HELPER(nmi)(CPUHexagonState *env, uint32_t thread_mask) { g_assert_not_reached(); } + +void HELPER(pending_interrupt)(CPUHexagonState *env) +{ + BQL_LOCK_GUARD(); + hex_interrupt_update(env); +} #endif From 4b0ca01523ccfba0effcc038680230ba5a72f6bb Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 9 Sep 2024 10:07:30 -0700 Subject: [PATCH 1124/1179] target/hexagon: Add pkt_ends_tb to translation Signed-off-by: Brian Cain --- target/hexagon/translate.c | 99 +++++++++++++++++++++++++++++++++++++- target/hexagon/translate.h | 1 + 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 060df6e5eb62..475726388a3f 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -259,6 +259,18 @@ static bool check_for_attrib(Packet *pkt, int attrib) return false; } +#ifndef CONFIG_USER_ONLY +static bool check_for_opcode(Packet *pkt, uint16_t opcode) +{ + for (int i = 0; i < pkt->num_insns; i++) { + if (pkt->insn[i].opcode == opcode) { + return true; + } + } + return false; +} +#endif + static bool need_slot_cancelled(Packet *pkt) { /* We only need slot_cancelled for conditional store instructions */ @@ -272,6 +284,90 @@ static bool need_slot_cancelled(Packet *pkt) return false; } +#ifndef CONFIG_USER_ONLY +static bool sreg_write_to_global(int reg_num) +{ + return reg_num == HEX_SREG_SSR || + reg_num == HEX_SREG_STID || + reg_num == HEX_SREG_IMASK || + reg_num == HEX_SREG_IPENDAD || + reg_num == HEX_SREG_BESTWAIT || + reg_num == HEX_SREG_SCHEDCFG; +} + +static bool has_sreg_write_to_global(Packet const *pkt) +{ + for (int i = 0; i < pkt->num_insns; i++) { + Insn const *insn = &pkt->insn[i]; + uint16_t opcode = insn->opcode; + if (opcode == Y2_tfrsrcr) { + /* Write to a single sreg */ + int reg_num = insn->regno[0]; + if (sreg_write_to_global(reg_num)) { + return true; + } + } else if (opcode == Y4_tfrspcp) { + /* Write to a sreg pair */ + int reg_num = insn->regno[0]; + if (sreg_write_to_global(reg_num)) { + return true; + } + if (sreg_write_to_global(reg_num + 1)) { + return true; + } + } + } + return false; +} +#endif + +static bool pkt_ends_tb(Packet *pkt) +{ + if (pkt->pkt_has_cof) { + return true; + } +#ifndef CONFIG_USER_ONLY + /* System mode instructions that end TLB */ + if (check_for_opcode(pkt, Y2_swi) || + check_for_opcode(pkt, Y2_cswi) || + check_for_opcode(pkt, Y2_ciad) || + check_for_opcode(pkt, Y4_siad) || + check_for_opcode(pkt, Y2_wait) || + check_for_opcode(pkt, Y2_resume) || + check_for_opcode(pkt, Y2_iassignw) || + check_for_opcode(pkt, Y2_setimask) || + check_for_opcode(pkt, Y4_nmi) || + check_for_opcode(pkt, Y2_setprio) || + check_for_opcode(pkt, Y2_start) || + check_for_opcode(pkt, Y2_stop) || + check_for_opcode(pkt, Y2_k0lock) || + check_for_opcode(pkt, Y2_k0unlock) || + check_for_opcode(pkt, Y2_tlblock) || + check_for_opcode(pkt, Y2_tlbunlock) || + check_for_opcode(pkt, Y2_break) || + check_for_opcode(pkt, Y2_isync) || + check_for_opcode(pkt, Y2_syncht) || + check_for_opcode(pkt, Y2_tlbp) || + check_for_opcode(pkt, Y2_tlbw) || + check_for_opcode(pkt, Y5_ctlbw) || + check_for_opcode(pkt, Y5_tlbasidi)) { + return true; + } + + /* + * Check for sreg writes that would end the TB + */ + if (check_for_attrib(pkt, A_IMPLICIT_WRITES_SSR)) { + return true; + } + if (has_sreg_write_to_global(pkt)) { + return true; + } +#endif + return false; +} + + static bool need_next_PC(DisasContext *ctx) { Packet *pkt = ctx->pkt; @@ -473,6 +569,7 @@ static void gen_start_packet(DisasContext *ctx) tcg_gen_movi_tl(hex_slot_cancelled, 0); } ctx->branch_taken = NULL; + ctx->pkt_ends_tb = pkt_ends_tb(pkt); if (pkt->pkt_has_cof) { ctx->branch_taken = tcg_temp_new(); if (pkt->pkt_has_multi_cof) { @@ -927,7 +1024,7 @@ static void gen_commit_packet(DisasContext *ctx) pkt->vhist_insn->generate(ctx); } - if (pkt->pkt_has_cof) { + if (ctx->pkt_ends_tb || ctx->base.is_jmp == DISAS_NORETURN) { gen_end_tb(ctx); } } diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index 9bc4b3ce8b33..c9533fee1f5c 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h @@ -84,6 +84,7 @@ typedef struct DisasContext { TCGv branch_taken; TCGv dczero_addr; bool pcycle_enabled; + bool pkt_ends_tb; uint32_t num_cycles; } DisasContext; From 0a248ed2174358650e5c013acf3d1b5d58a5a966 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 9 Sep 2024 09:35:18 -0700 Subject: [PATCH 1125/1179] target/hexagon: Add next_PC, {s,g}reg writes Signed-off-by: Brian Cain --- target/hexagon/cpu.h | 2 +- target/hexagon/genptr.c | 7 +- target/hexagon/translate.c | 142 ++++++++++++++++++++++++++++++++----- target/hexagon/translate.h | 2 + 4 files changed, 132 insertions(+), 21 deletions(-) diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 9a5a0bb78af7..340e0a83a5ba 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -146,9 +146,9 @@ typedef struct CPUArchState { hex_lock_state_t k0_lock_state; target_ulong tlb_lock_count; target_ulong k0_lock_count; - target_ulong next_PC; CPUHexagonTLBContext *hex_tlb; #endif + target_ulong next_PC; target_ulong new_value_usr; MemLog mem_log_stores[STORES_MAX]; diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c index 5554c9515c4d..afc7e5f3a5ab 100644 --- a/target/hexagon/genptr.c +++ b/target/hexagon/genptr.c @@ -634,14 +634,15 @@ static void gen_write_new_pc_addr(DisasContext *ctx, TCGv addr, tcg_gen_brcondi_tl(cond, pred, 0, pred_false); } + TCGv PC_wr = ctx->need_next_pc ? hex_next_PC : hex_gpr[HEX_REG_PC]; if (ctx->pkt->pkt_has_multi_cof) { /* If there are multiple branches in a packet, ignore the second one */ - tcg_gen_movcond_tl(TCG_COND_NE, hex_gpr[HEX_REG_PC], + tcg_gen_movcond_tl(TCG_COND_NE, PC_wr, ctx->branch_taken, tcg_constant_tl(0), - hex_gpr[HEX_REG_PC], addr); + PC_wr, addr); tcg_gen_movi_tl(ctx->branch_taken, 1); } else { - tcg_gen_mov_tl(hex_gpr[HEX_REG_PC], addr); + tcg_gen_mov_tl(PC_wr, addr); } if (cond != TCG_COND_ALWAYS) { diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 475726388a3f..d4b22acb729f 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -49,6 +49,7 @@ static const AnalyzeInsn opcode_analyze[XX_LAST_OPCODE] = { TCGv hex_gpr[TOTAL_PER_THREAD_REGS]; TCGv hex_pred[NUM_PREGS]; TCGv hex_slot_cancelled; +TCGv hex_next_PC; TCGv hex_new_value_usr; TCGv hex_store_addr[STORES_MAX]; TCGv hex_store_width[STORES_MAX]; @@ -61,12 +62,14 @@ TCGv_i64 hex_cycle_count; TCGv hex_vstore_addr[VSTORES_MAX]; TCGv hex_vstore_size[VSTORES_MAX]; TCGv hex_vstore_pending[VSTORES_MAX]; +static bool need_next_PC(DisasContext *ctx); #ifndef CONFIG_USER_ONLY TCGv hex_greg[NUM_GREGS]; TCGv hex_t_sreg[NUM_SREGS]; TCGv_ptr hex_g_sreg_ptr; TCGv hex_g_sreg[NUM_SREGS]; +TCGv hex_cause_code; #endif static const char * const hexagon_prednames[] = { @@ -184,6 +187,9 @@ static void gen_end_tb(DisasContext *ctx) gen_exec_counters(ctx); + if (ctx->need_next_pc) { + tcg_gen_mov_tl(hex_gpr[HEX_REG_PC], hex_next_PC); + } if (ctx->branch_cond != TCG_COND_NEVER) { if (ctx->branch_cond != TCG_COND_ALWAYS) { TCGLabel *skip = gen_new_label(); @@ -371,18 +377,24 @@ static bool pkt_ends_tb(Packet *pkt) static bool need_next_PC(DisasContext *ctx) { Packet *pkt = ctx->pkt; - - /* Check for conditional control flow or HW loop end */ - for (int i = 0; i < pkt->num_insns; i++) { - uint16_t opcode = pkt->insn[i].opcode; - if (GET_ATTRIB(opcode, A_CONDEXEC) && GET_ATTRIB(opcode, A_COF)) { - return true; - } - if (GET_ATTRIB(opcode, A_HWLOOP0_END) || - GET_ATTRIB(opcode, A_HWLOOP1_END)) { - return true; + if (pkt->pkt_has_cof || ctx->pkt_ends_tb) { + for (int i = 0; i < pkt->num_insns; i++) { + uint16_t opcode = pkt->insn[i].opcode; + if ((GET_ATTRIB(opcode, A_CONDEXEC) && GET_ATTRIB(opcode, A_COF)) || + GET_ATTRIB(opcode, A_HWLOOP0_END) || + GET_ATTRIB(opcode, A_HWLOOP1_END)) { + return true; + } } } + /* + * We end the TB on some instructions that do not change the flow (for + * other reasons). In these cases, we must set pc too, as the insn won't + * do it themselves. + */ + if (ctx->pkt_ends_tb && !check_for_attrib(pkt, A_COF)) { + return true; + } return false; } @@ -523,7 +535,14 @@ static void analyze_packet(DisasContext *ctx) static void gen_start_packet(DisasContext *ctx) { Packet *pkt = ctx->pkt; +#ifndef CONFIG_USER_ONLY + target_ulong next_PC = (check_for_opcode(pkt, Y2_k0lock) || + check_for_opcode(pkt, Y2_tlblock)) ? + ctx->base.pc_next : + ctx->base.pc_next + pkt->encod_pkt_size_in_bytes; +#else target_ulong next_PC = ctx->base.pc_next + pkt->encod_pkt_size_in_bytes; +#endif int i; /* Clear out the disassembly context */ @@ -531,6 +550,10 @@ static void gen_start_packet(DisasContext *ctx) ctx->reg_log_idx = 0; bitmap_zero(ctx->regs_written, TOTAL_PER_THREAD_REGS); bitmap_zero(ctx->predicated_regs, TOTAL_PER_THREAD_REGS); +#ifndef CONFIG_USER_ONLY + ctx->greg_log_idx = 0; + ctx->sreg_log_idx = 0; +#endif ctx->preg_log_idx = 0; bitmap_zero(ctx->pregs_written, NUM_PREGS); ctx->future_vregs_idx = 0; @@ -563,21 +586,41 @@ static void gen_start_packet(DisasContext *ctx) * gen phase, so clear it again. */ bitmap_zero(ctx->pregs_written, NUM_PREGS); +#ifndef CONFIG_USER_ONLY + for (i = 0; i < NUM_SREGS; i++) { + ctx->t_sreg_new_value[i] = NULL; + } + for (i = 0; i < ctx->sreg_log_idx; i++) { + int reg_num = ctx->sreg_log[i]; + if (reg_num < HEX_SREG_GLB_START) { + ctx->t_sreg_new_value[reg_num] = tcg_temp_new(); + tcg_gen_mov_tl(ctx->t_sreg_new_value[reg_num], hex_t_sreg[reg_num]); + } + } + for (i = 0; i < NUM_GREGS; i++) { + ctx->greg_new_value[i] = NULL; + } + for (i = 0; i < ctx->greg_log_idx; i++) { + int reg_num = ctx->greg_log[i]; + ctx->greg_new_value[reg_num] = tcg_temp_new(); + } +#endif /* Initialize the runtime state for packet semantics */ if (need_slot_cancelled(pkt)) { tcg_gen_movi_tl(hex_slot_cancelled, 0); } ctx->branch_taken = NULL; - ctx->pkt_ends_tb = pkt_ends_tb(pkt); if (pkt->pkt_has_cof) { ctx->branch_taken = tcg_temp_new(); - if (pkt->pkt_has_multi_cof) { - tcg_gen_movi_tl(ctx->branch_taken, 0); - } - if (need_next_PC(ctx)) { - tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], next_PC); - } + } + if (pkt->pkt_has_multi_cof) { + tcg_gen_movi_tl(ctx->branch_taken, 0); + } + ctx->pkt_ends_tb = pkt_ends_tb(pkt); + ctx->need_next_pc = need_next_PC(ctx); + if (ctx->need_next_pc) { + tcg_gen_movi_tl(hex_next_PC, next_PC); } /* Preload the predicated registers into get_result_gpr(ctx, i) */ @@ -713,6 +756,59 @@ static void gen_reg_writes(DisasContext *ctx) } } +#ifndef CONFIG_USER_ONLY +static void gen_greg_writes(DisasContext *ctx) +{ + int i; + + for (i = 0; i < ctx->greg_log_idx; i++) { + int reg_num = ctx->greg_log[i]; + + tcg_gen_mov_tl(hex_greg[reg_num], ctx->greg_new_value[reg_num]); + } +} + + +static void gen_sreg_writes(DisasContext *ctx) +{ + int i; + + TCGv old_reg = tcg_temp_new(); + for (i = 0; i < ctx->sreg_log_idx; i++) { + int reg_num = ctx->sreg_log[i]; + + if (reg_num == HEX_SREG_SSR) { + tcg_gen_mov_tl(old_reg, hex_t_sreg[reg_num]); + tcg_gen_mov_tl(hex_t_sreg[reg_num], ctx->t_sreg_new_value[reg_num]); + gen_helper_modify_ssr(tcg_env, ctx->t_sreg_new_value[reg_num], + old_reg); + /* This can change processor state, so end the TB */ + ctx->base.is_jmp = DISAS_NORETURN; + } else if ((reg_num == HEX_SREG_STID) || + (reg_num == HEX_SREG_IMASK) || + (reg_num == HEX_SREG_IPENDAD)) { + if (reg_num < HEX_SREG_GLB_START) { + tcg_gen_mov_tl(old_reg, hex_t_sreg[reg_num]); + tcg_gen_mov_tl(hex_t_sreg[reg_num], + ctx->t_sreg_new_value[reg_num]); + } + /* This can change the interrupt state, so end the TB */ + gen_helper_pending_interrupt(tcg_env); + ctx->base.is_jmp = DISAS_NORETURN; + } else if ((reg_num == HEX_SREG_BESTWAIT) || + (reg_num == HEX_SREG_SCHEDCFG)) { + /* This can trigger resched interrupt, so end the TB */ + gen_helper_resched(tcg_env); + ctx->base.is_jmp = DISAS_NORETURN; + } + + if (reg_num < HEX_SREG_GLB_START) { + tcg_gen_mov_tl(hex_t_sreg[reg_num], ctx->t_sreg_new_value[reg_num]); + } + } +} +#endif + static void gen_pred_writes(DisasContext *ctx) { /* Early exit if not needed or the log is empty */ @@ -1012,6 +1108,10 @@ static void gen_commit_packet(DisasContext *ctx) process_store_log(ctx); gen_reg_writes(ctx); +#if !defined(CONFIG_USER_ONLY) + gen_greg_writes(ctx); + gen_sreg_writes(ctx); +#endif gen_pred_writes(ctx); if (pkt->pkt_has_hvx) { gen_commit_hvx(ctx); @@ -1073,6 +1173,7 @@ static void hexagon_tr_init_disas_context(DisasContextBase *dcbase, ctx->is_tight_loop = FIELD_EX32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP); ctx->short_circuit = hex_cpu->short_circuit; ctx->pcycle_enabled = FIELD_EX32(hex_flags, TB_FLAGS, PCYCLE_ENABLED); + ctx->need_next_pc = false; } static void hexagon_tr_tb_start(DisasContextBase *db, CPUState *cpu) @@ -1201,6 +1302,13 @@ void hexagon_translate_init(void) offsetof(CPUHexagonState, llsc_val_i64), "llsc_val_i64"); hex_cycle_count = tcg_global_mem_new_i64(tcg_env, offsetof(CPUHexagonState, t_cycle_count), "t_cycle_count"); +#ifndef CONFIG_USER_ONLY + hex_cause_code = tcg_global_mem_new(tcg_env, + offsetof(CPUHexagonState, cause_code), "cause_code"); +#endif + hex_next_PC = tcg_global_mem_new(tcg_env, + offsetof(CPUHexagonState, next_PC), "next_PC"); + for (i = 0; i < STORES_MAX; i++) { snprintf(store_addr_names[i], NAME_LEN, "store_addr_%d", i); hex_store_addr[i] = tcg_global_mem_new(tcg_env, diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index c9533fee1f5c..ad1a2f404534 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h @@ -85,6 +85,7 @@ typedef struct DisasContext { TCGv dczero_addr; bool pcycle_enabled; bool pkt_ends_tb; + bool need_next_pc; uint32_t num_cycles; } DisasContext; @@ -306,6 +307,7 @@ static inline void ctx_log_qreg_read(DisasContext *ctx, } extern TCGv hex_gpr[TOTAL_PER_THREAD_REGS]; +extern TCGv hex_next_PC; extern TCGv hex_pred[NUM_PREGS]; extern TCGv hex_slot_cancelled; extern TCGv hex_new_value_usr; From 5801c287260c7858d3a77f13bbb4217a70c9b669 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 9 Sep 2024 10:48:04 -0700 Subject: [PATCH 1126/1179] target/hexagon: Add implicit sysreg writes Signed-off-by: Brian Cain --- target/hexagon/translate.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index d4b22acb729f..ff881d10602b 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -426,6 +426,16 @@ static void mark_implicit_reg_write(DisasContext *ctx, int attrib, int rnum) } } +#ifndef CONFIG_USER_ONLY +static void mark_implicit_sreg_write(DisasContext *ctx, int attrib, int snum) +{ + uint16_t opcode = ctx->insn->opcode; + if (GET_ATTRIB(opcode, attrib)) { + ctx_log_sreg_write(ctx, snum); + } +} +#endif + static void mark_implicit_reg_writes(DisasContext *ctx) { mark_implicit_reg_write(ctx, A_IMPLICIT_WRITES_FP, HEX_REG_FP); @@ -437,6 +447,12 @@ static void mark_implicit_reg_writes(DisasContext *ctx) mark_implicit_reg_write(ctx, A_IMPLICIT_WRITES_SA1, HEX_REG_SA1); mark_implicit_reg_write(ctx, A_IMPLICIT_WRITES_USR, HEX_REG_USR); mark_implicit_reg_write(ctx, A_FPOP, HEX_REG_USR); + +#ifndef CONFIG_USER_ONLY + mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SGP0, HEX_SREG_SGP0); + mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SGP1, HEX_SREG_SGP1); + mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SSR, HEX_SREG_SSR); +#endif } static void mark_implicit_pred_write(DisasContext *ctx, int attrib, int pnum) From f9a4cd9e267f2c1ca6144cd5680ce1000bad50b9 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 11 Sep 2024 14:05:59 -0700 Subject: [PATCH 1127/1179] target/hexagon: Define system, guest reg names Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 29 +++++++++++++++++++++++++++++ target/hexagon/internal.h | 2 ++ 2 files changed, 31 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 07ea3f54f287..19ea171c343d 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -86,6 +86,35 @@ const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS] = { "c24", "c25", "c26", "c27", "c28", "c29", "c30", "c31", }; +#ifndef CONFIG_USER_ONLY +const char * const hexagon_sregnames[] = { + "sgp0", "sgp1", "stid", "elr", "badva0", + "badva1", "ssr", "ccr", "htid", "badva", + "imask", "gevb", "vwctrl", "s13", "s14", + "s15", "evb", "modectl", "syscfg", "segment", + "ipendad", "vid", "vid1", "bestwait", "s24", + "schedcfg", "s26", "cfgbase", "diag", "rev", + "pcyclelo", "pcyclehi", "isdbst", "isdbcfg0", "isdbcfg1", + "livelock", "brkptpc0", "brkptcfg0", "brkptpc1", "brkptcfg1", + "isdbmbxin", "isdbmbxout", "isdben", "isdbgpr", "pmucnt4", + "pmucnt5", "pmucnt6", "pmucnt7", "pmucnt0", "pmucnt1", + "pmucnt2", "pmucnt3", "pmuevtcfg", "pmustid0", "pmuevtcfg1", + "pmustid1", "timerlo", "timerhi", "pmucfg", "rgdr2", + "rgdr", "turkey", "duck", "chicken", +}; + +G_STATIC_ASSERT(NUM_SREGS == ARRAY_SIZE(hexagon_sregnames)); + +const char * const hexagon_gregnames[] = { + "gelr", "gsr", "gosp", "gbadva", "gcommit1t", + "gcommit2t", "gcommit3t", "gcommit4t", "gcommit5t", "gcommit6t", + "gpcycle1t", "gpcycle2t", "gpcycle3t", "gpcycle4t", "gpcycle5t", + "gpcycle6t", "gpmucnt4", "gpmucnt5", "gpmucnt6", "gpmucnt7", + "gcommit7t", "gcommit8t", "gpcycle7t", "gpcycle8t", "gpcyclelo", + "gpcyclehi", "gpmucnt0", "gpmucnt1", "gpmucnt2", "gpmucnt3", + "g30", "g31", +}; +#endif /* * One of the main debugging techniques is to use "-d cpu" and compare against * LLDB output when single stepping. However, the target and qemu put the diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h index 120cfde7b958..fd2397b9ef0e 100644 --- a/target/hexagon/internal.h +++ b/target/hexagon/internal.h @@ -34,6 +34,8 @@ void hexagon_debug_qreg(CPUHexagonState *env, int regnum); void hexagon_debug(CPUHexagonState *env); extern const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS]; +extern const char * const hexagon_sregnames[]; +extern const char * const hexagon_gregnames[]; void G_NORETURN do_raise_exception(CPUHexagonState *env, uint32_t exception, From aa7e50f432fde0f3f9f1fe09eb4e26b797a6bb91 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 11 Sep 2024 14:06:15 -0700 Subject: [PATCH 1128/1179] target/hexagon: initialize sys/guest reg TCGvs Signed-off-by: Brian Cain --- target/hexagon/translate.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index ff881d10602b..248ed60f2986 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -1295,6 +1295,26 @@ void hexagon_translate_init(void) opcode_init(); +#ifndef CONFIG_USER_ONLY + for (i = 0; i < NUM_GREGS; i++) { + hex_greg[i] = tcg_global_mem_new(tcg_env, + offsetof(CPUHexagonState, greg[i]), + hexagon_gregnames[i]); + } + hex_g_sreg_ptr = tcg_global_mem_new_ptr(tcg_env, + offsetof(CPUHexagonState, g_sreg), "hex_g_sreg_ptr"); + for (i = 0; i < NUM_SREGS; i++) { + if (i < HEX_SREG_GLB_START) { + hex_t_sreg[i] = tcg_global_mem_new(tcg_env, + offsetof(CPUHexagonState, t_sreg[i]), + hexagon_sregnames[i]); + } else { + hex_g_sreg[i] = tcg_global_mem_new(hex_g_sreg_ptr, + i * sizeof(target_ulong), + hexagon_sregnames[i]); + } + } +#endif for (i = 0; i < TOTAL_PER_THREAD_REGS; i++) { hex_gpr[i] = tcg_global_mem_new(tcg_env, offsetof(CPUHexagonState, gpr[i]), From e50db34cdddd5800ad7ba790ae0e9cc7b11bd7ba Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 12 Sep 2024 07:22:51 -0700 Subject: [PATCH 1129/1179] target/hexagon: Add TLB, k0 {un,}lock Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 104 ++++++++++++++++++++++++++++++++++++ target/hexagon/sys_macros.h | 8 +-- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 702c3dd3c643..f3b14fbf58f6 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1184,6 +1184,110 @@ void HELPER(modify_ssr)(CPUHexagonState *env, uint32_t new, uint32_t old) BQL_LOCK_GUARD(); hexagon_modify_ssr(env, new, old); } + +static void hex_k0_lock(CPUHexagonState *env) +{ + BQL_LOCK_GUARD(); + g_assert((env->k0_lock_count == 0) || (env->k0_lock_count == 1)); + + uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + if (GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg)) { + if (env->k0_lock_state == HEX_LOCK_QUEUED) { + env->next_PC += 4; + env->k0_lock_count++; + env->k0_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 1); + return; + } + if (env->k0_lock_state == HEX_LOCK_OWNER) { + qemu_log_mask(LOG_GUEST_ERROR, + "Double k0lock at PC: 0x%x, thread may hang\n", + env->next_PC); + env->next_PC += 4; + CPUState *cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + return; + } + env->k0_lock_state = HEX_LOCK_WAITING; + CPUState *cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + } else { + env->next_PC += 4; + env->k0_lock_count++; + env->k0_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 1); + } + +} + +static void hex_k0_unlock(CPUHexagonState *env) +{ + BQL_LOCK_GUARD(); + g_assert((env->k0_lock_count == 0) || (env->k0_lock_count == 1)); + + /* Nothing to do if the k0 isn't locked by this thread */ + uint32_t syscfg = arch_get_system_reg(env, HEX_SREG_SYSCFG); + if ((GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg) == 0) || + (env->k0_lock_state != HEX_LOCK_OWNER)) { + qemu_log_mask(LOG_GUEST_ERROR, + "thread %d attempted to unlock k0 without having the " + "lock, k0_lock state = %d, syscfg:k0 = %d\n", + env->threadId, env->k0_lock_state, + GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg)); + g_assert(env->k0_lock_state != HEX_LOCK_WAITING); + return; + } + + env->k0_lock_count--; + env->k0_lock_state = HEX_LOCK_UNLOCKED; + SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 0); + + /* Look for a thread to unlock */ + unsigned int this_threadId = env->threadId; + CPUHexagonState *unlock_thread = NULL; + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *thread = cpu_env(cs); + + /* + * The hardware implements round-robin fairness, so we look for threads + * starting at env->threadId + 1 and incrementing modulo the number of + * threads. + * + * To implement this, we check if thread is a earlier in the modulo + * sequence than unlock_thread. + * if unlock thread is higher than this thread + * thread must be between this thread and unlock_thread + * else + * thread higher than this thread is ahead of unlock_thread + * thread must be lower then unlock thread + */ + if (thread->k0_lock_state == HEX_LOCK_WAITING) { + if (!unlock_thread) { + unlock_thread = thread; + } else if (unlock_thread->threadId > this_threadId) { + if (this_threadId < thread->threadId && + thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } else { + if (thread->threadId > this_threadId) { + unlock_thread = thread; + } + if (thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } + } + } + if (unlock_thread) { + cs = env_cpu(unlock_thread); + unlock_thread->k0_lock_state = HEX_LOCK_QUEUED; + SET_SYSCFG_FIELD(unlock_thread, SYSCFG_K0LOCK, 1); + cpu_interrupt(cs, CPU_INTERRUPT_K0_UNLOCK); + } + +} #endif diff --git a/target/hexagon/sys_macros.h b/target/hexagon/sys_macros.h index 3c4c3c7aa5ec..e5dc1ce0ab9f 100644 --- a/target/hexagon/sys_macros.h +++ b/target/hexagon/sys_macros.h @@ -143,11 +143,11 @@ #define fDCINVIDX(REG) #define fDCINVA(REG) do { REG = REG; } while (0) /* Nothing to do in qemu */ -#define fSET_TLB_LOCK() g_assert_not_reached() -#define fCLEAR_TLB_LOCK() g_assert_not_reached() +#define fSET_TLB_LOCK() hex_tlb_lock(env); +#define fCLEAR_TLB_LOCK() hex_tlb_unlock(env); -#define fSET_K0_LOCK() g_assert_not_reached() -#define fCLEAR_K0_LOCK() g_assert_not_reached() +#define fSET_K0_LOCK() hex_k0_lock(env); +#define fCLEAR_K0_LOCK() hex_k0_unlock(env); #define fTLB_IDXMASK(INDEX) \ ((INDEX) & (fPOW2_ROUNDUP(fCAST4u(env_archcpu(env)->num_tlbs)) - 1)) From 586fb1d7e152701d77c872394056af101a7d20b8 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 12 Sep 2024 14:10:04 -0700 Subject: [PATCH 1130/1179] target/hexagon: Define gen_precise_exception() Add PC to raise_exception helper Replace the fGEN_TCG_J2_trap0 macro override with the fTRAP()-generated system helper instead. Signed-off-by: Brian Cain --- target/hexagon/gen_tcg.h | 7 ------- target/hexagon/helper.h | 2 +- target/hexagon/op_helper.c | 10 ++++------ target/hexagon/translate.c | 13 ++++++++----- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/target/hexagon/gen_tcg.h b/target/hexagon/gen_tcg.h index 71f8a0e2d084..146aadc73764 100644 --- a/target/hexagon/gen_tcg.h +++ b/target/hexagon/gen_tcg.h @@ -1370,13 +1370,6 @@ #define fGEN_TCG_S2_storew_rl_st_vi(SHORTCODE) SHORTCODE #define fGEN_TCG_S4_stored_rl_st_vi(SHORTCODE) SHORTCODE -#define fGEN_TCG_J2_trap0(SHORTCODE) \ - do { \ - uiV = uiV; \ - tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->pkt->pc); \ - TCGv excp = tcg_constant_tl(HEX_EVENT_TRAP0); \ - gen_helper_raise_exception(tcg_env, excp); \ - } while (0) #endif #define fGEN_TCG_A2_nop(SHORTCODE) do { } while (0) diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index 3df663baeb05..5bcb2f48097c 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -18,7 +18,7 @@ #include "internal.h" #include "helper_protos_generated.h.inc" -DEF_HELPER_FLAGS_2(raise_exception, TCG_CALL_NO_RETURN, noreturn, env, i32) +DEF_HELPER_FLAGS_3(raise_exception, TCG_CALL_NO_RETURN, noreturn, env, i32, i32) DEF_HELPER_2(commit_store, void, env, int) DEF_HELPER_3(gather_store, void, env, i32, int) DEF_HELPER_1(commit_hvx_stores, void, env) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index f3b14fbf58f6..3bd4e2a87233 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -69,15 +69,13 @@ G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t exception, uintptr_t pc) { - CPUState *cs = env_cpu(env); - qemu_log_mask(CPU_LOG_INT, "%s: %d\n", __func__, exception); - cs->exception_index = exception; - cpu_loop_exit_restore(cs, pc); + do_raise_exception(env, exception, pc, 0); } -G_NORETURN void HELPER(raise_exception)(CPUHexagonState *env, uint32_t excp) +G_NORETURN void HELPER(raise_exception)(CPUHexagonState *env, uint32_t excp, + target_ulong PC) { - hexagon_raise_exception_err(env, excp, 0); + hexagon_raise_exception_err(env, excp, PC); } void log_store32(CPUHexagonState *env, target_ulong addr, diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 248ed60f2986..f4133a10490e 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -124,9 +124,10 @@ intptr_t ctx_tmp_vreg_off(DisasContext *ctx, int regnum, return offset; } -static void gen_exception_raw(int excp) +static void gen_exception(int excp, target_ulong PC) { - gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp)); + gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp), + tcg_constant_tl(PC)); } #ifndef CONFIG_USER_ONLY @@ -221,9 +222,11 @@ static void gen_end_tb(DisasContext *ctx) void hex_gen_exception_end_tb(DisasContext *ctx, int excp) { - gen_exec_counters(ctx); - tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->next_PC); - gen_exception_raw(excp); +#ifdef CONFIG_USER_ONLY + gen_exception(excp, ctx->pkt->pc); +#else + gen_precise_exception(excp, ctx->pkt->pc); +#endif ctx->base.is_jmp = DISAS_NORETURN; } From 075acfd804a2a1373f154713afde5345be123fb2 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 17 Sep 2024 18:15:07 -0700 Subject: [PATCH 1131/1179] target/hexagon: Add TCG overrides for transfer insts Signed-off-by: Brian Cain --- target/hexagon/gen_tcg_sys.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/target/hexagon/gen_tcg_sys.h b/target/hexagon/gen_tcg_sys.h index 6d73a18db455..e56553462fb0 100644 --- a/target/hexagon/gen_tcg_sys.h +++ b/target/hexagon/gen_tcg_sys.h @@ -81,6 +81,31 @@ gen_helper_stop(tcg_env); \ } while (0) +#define fGEN_TCG_Y2_tfrscrr(SHORTCODE) \ + tcg_gen_mov_tl(RdV, SsV) + +#define fGEN_TCG_Y2_tfrsrcr(SHORTCODE) \ + tcg_gen_mov_tl(SdV, RsV) + +#define fGEN_TCG_Y4_tfrscpp(SHORTCODE) \ + tcg_gen_mov_i64(RddV, SssV) + +#define fGEN_TCG_Y4_tfrspcp(SHORTCODE) \ + tcg_gen_mov_i64(SddV, RssV) + +#define fGEN_TCG_G4_tfrgcrr(SHORTCODE) \ + tcg_gen_mov_tl(RdV, GsV) + +#define fGEN_TCG_G4_tfrgrcr(SHORTCODE) \ + tcg_gen_mov_tl(GdV, RsV) + +#define fGEN_TCG_G4_tfrgcpp(SHORTCODE) \ + tcg_gen_mov_i64(RddV, GssV) + +#define fGEN_TCG_G4_tfrgpcp(SHORTCODE) \ + tcg_gen_mov_i64(GddV, RssV) + + /* * rte (return from exception) * Clear the EX bit in SSR From f9be9458c0c1b6408607b10a623c2176b9ddd51b Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 18 Sep 2024 09:33:45 -0700 Subject: [PATCH 1132/1179] target/hexagon: Add support for loadw_phys Signed-off-by: Brian Cain --- target/hexagon/hex_common.py | 3 +++ target/hexagon/imported/encode_pp.def | 1 + target/hexagon/imported/ldst.idef | 3 +++ 3 files changed, 7 insertions(+) diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index e2842fff3fcd..1df5d8c19a03 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -266,6 +266,9 @@ def need_slot(tag): and "A_CVI_GATHER" not in attribdict[tag] and ("A_STORE" in attribdict[tag] or "A_LOAD" in attribdict[tag]) + and tag != "L4_loadw_phys" + and tag != "L6_memcpy" + and tag != "Y6_dmlink" ): return 1 else: diff --git a/target/hexagon/imported/encode_pp.def b/target/hexagon/imported/encode_pp.def index 37faf62b1b7e..41e4ab9e3a26 100644 --- a/target/hexagon/imported/encode_pp.def +++ b/target/hexagon/imported/encode_pp.def @@ -388,6 +388,7 @@ DEF_ENC32(L4_return_fnew_pnt, ICLASS_LD" 011 0 000 sssss PP1010vv ---ddddd") /** Load Acquire Store Release Encoding **/ +DEF_ENC32(L4_loadw_phys, ICLASS_LD" 001 0 000 sssss PP1ttttt -00ddddd") DEF_ENC32(L2_loadw_locked, ICLASS_LD" 001 0 000 sssss PP000--- 000ddddd") DEF_ENC32(L4_loadd_locked, ICLASS_LD" 001 0 000 sssss PP010--- 000ddddd") diff --git a/target/hexagon/imported/ldst.idef b/target/hexagon/imported/ldst.idef index 53198176a994..4e1e5d5326dd 100644 --- a/target/hexagon/imported/ldst.idef +++ b/target/hexagon/imported/ldst.idef @@ -203,6 +203,9 @@ Q6INSN(S2_storew_locked,"memw_locked(Rs32,Pd4)=Rt32", ATTRIBS(A_REGWRSIZE_4B,A_M Q6INSN(L4_loadd_locked,"Rdd32=memd_locked(Rs32)", ATTRIBS(A_REGWRSIZE_8B,A_MEMSIZE_8B,A_LOAD,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK), "Load double with lock", { fEA_REG(RsV); fLOAD_LOCKED(1,8,u,EA,RddV) }) +Q6INSN(L4_loadw_phys,"Rd32=memw_phys(Rs32,Rt32)", ATTRIBS(A_REGWRSIZE_4B,A_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_PRIV,A_MEMSIZE_4B,A_LOAD,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Load word from physical address", +{ fLOAD_PHYS(1,4,u,RsV,RtV,RdV); }) + Q6INSN(S4_stored_locked,"memd_locked(Rs32,Pd4)=Rtt32", ATTRIBS(A_REGWRSIZE_8B,A_MEMSIZE_8B,A_STORE,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_LATEPRED,A_NOTE_LATEPRED), "Store word with lock", { fEA_REG(RsV); fSTORE_LOCKED(1,8,EA,RttV,PdV) }) From cf7672d49237dbd40c4ea174d1feffa62d95e403 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Fri, 6 Dec 2024 10:30:26 -0300 Subject: [PATCH 1133/1179] target/hexagon: Add guest reg reading functionality Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/cpu.c | 19 ++++++++++++++++++- target/hexagon/op_helper.c | 19 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 19ea171c343d..4dd005af1950 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -736,7 +736,24 @@ static void hexagon_cpu_class_init(ObjectClass *c, void *data) #ifndef CONFIG_USER_ONLY uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg) { - g_assert_not_reached(); + target_ulong ssr = arch_get_system_reg(env, HEX_SREG_SSR); + int ssr_ce = GET_SSR_FIELD(SSR_CE, ssr); + + if (reg <= HEX_GREG_G3) { + return env->greg[reg]; + } + switch (reg) { + case HEX_GREG_GPCYCLELO: + return ssr_ce ? hexagon_get_sys_pcycle_count_low(env) : 0; + + case HEX_GREG_GPCYCLEHI: + return ssr_ce ? hexagon_get_sys_pcycle_count_high(env) : 0; + + default: + qemu_log_mask(LOG_UNIMP, "reading greg %" PRId32 + " not yet supported.\n", reg); + return 0; + } } #endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 3bd4e2a87233..28b555e87375 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1877,13 +1877,28 @@ uint64_t HELPER(sreg_read_pair)(CPUHexagonState *env, uint32_t reg) } uint32_t HELPER(greg_read)(CPUHexagonState *env, uint32_t reg) + { - g_assert_not_reached(); + return hexagon_greg_read(env, reg); } uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg) + { - g_assert_not_reached(); + if (reg == HEX_GREG_G0 || reg == HEX_GREG_G2) { + return (uint64_t)(env->greg[reg]) | + (((uint64_t)(env->greg[reg + 1])) << 32); + } + switch (reg) { + case HEX_GREG_GPCYCLELO: { + target_ulong ssr = arch_get_system_reg(env, HEX_SREG_SSR); + int ssr_ce = GET_SSR_FIELD(SSR_CE, ssr); + return ssr_ce ? hexagon_get_sys_pcycle_count(env) : 0; + } + default: + return (uint64_t)hexagon_greg_read(env, reg) | + ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32); + } } void HELPER(setprio)(CPUHexagonState *env, uint32_t thread, uint32_t prio) From 868055630b71694c78c9cf4cfce8eb455717fd0c Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 11 Dec 2024 14:08:53 -0300 Subject: [PATCH 1134/1179] target/hexagon: Add pcycle setting functionality Signed-off-by: Brian Cain Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/cpu.c | 10 +++++++--- target/hexagon/cpu_helper.c | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 4dd005af1950..785dd3d15d82 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -437,19 +437,23 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) #endif qemu_init_vcpu(cs); -#ifndef CONFIG_USER_ONLY CPUHexagonState *env = cpu_env(cs); +#ifndef CONFIG_USER_ONLY hex_mmu_realize(env); if (cs->cpu_index == 0) { env->g_sreg = g_new0(target_ulong, NUM_SREGS); - env->g_pcycle_base = g_malloc0(sizeof(*env->g_pcycle_base)); } else { CPUState *cpu0 = qemu_get_cpu(0); CPUHexagonState *env0 = cpu_env(cpu0); env->g_sreg = env0->g_sreg; - env->g_pcycle_base = env0->g_pcycle_base; } #endif + if (cs->cpu_index == 0) { + env->g_pcycle_base = g_malloc0(sizeof(*env->g_pcycle_base)); + } else { + CPUState *cpu0 = qemu_get_cpu(0); + env->g_pcycle_base = cpu_env(cpu0)->g_pcycle_base; + } mcc->parent_realize(dev, errp); } diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index 8de61ac05360..bb240a9d8f50 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -71,18 +71,29 @@ uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env) void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t cycles_hi) { - g_assert_not_reached(); + uint64_t cur_cycles = hexagon_get_sys_pcycle_count(env); + uint64_t cycles = + ((uint64_t)cycles_hi << 32) | extract64(cur_cycles, 0, 32); + hexagon_set_sys_pcycle_count(env, cycles); } void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t cycles_lo) { - g_assert_not_reached(); + uint64_t cur_cycles = hexagon_get_sys_pcycle_count(env); + uint64_t cycles = extract64(cur_cycles, 32, 32) | cycles_lo; + hexagon_set_sys_pcycle_count(env, cycles); } void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t cycles) { - g_assert_not_reached(); + *(env->g_pcycle_base) = cycles; + + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *env_ = cpu_env(cs); + env_->t_cycle_count = 0; + } } static void set_wait_mode(CPUHexagonState *env) From ad8187b9d3bc4581862b88c8629e4c27dc79c169 Mon Sep 17 00:00:00 2001 From: Sid Manning Date: Tue, 7 Nov 2023 19:44:05 -0800 Subject: [PATCH 1135/1179] hw/intc: Add l2vic interrupt controller Co-authored-by: Matheus Tavares Bernardino Co-authored-by: Damien Hedde Signed-off-by: Brian Cain --- MAINTAINERS | 2 + docs/devel/hexagon-l2vic.rst | 59 +++++ docs/devel/index-internals.rst | 1 + hw/intc/Kconfig | 3 + hw/intc/l2vic.c | 417 +++++++++++++++++++++++++++++++++ hw/intc/meson.build | 2 + hw/intc/trace-events | 4 + include/hw/intc/l2vic.h | 37 +++ 8 files changed, 525 insertions(+) create mode 100644 docs/devel/hexagon-l2vic.rst create mode 100644 hw/intc/l2vic.c create mode 100644 include/hw/intc/l2vic.h diff --git a/MAINTAINERS b/MAINTAINERS index b2319ebbd6a9..8ae8403c025e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -225,6 +225,7 @@ Hexagon TCG CPUs M: Brian Cain S: Supported F: target/hexagon/ +F: hw/intc/l2vic.[ch] X: target/hexagon/idef-parser/ X: target/hexagon/gen_idef_parser_funcs.py F: linux-user/hexagon/ @@ -235,6 +236,7 @@ F: docker/dockerfiles/debian-hexagon-cross.docker F: gdb-xml/hexagon*.xml F: docs/system/target-hexagon.rst F: docs/devel/hexagon-sys.rst +F: docs/devel/hexagon-l2vic.rst T: git https://github.com/quic/qemu.git hex-next Hexagon idef-parser diff --git a/docs/devel/hexagon-l2vic.rst b/docs/devel/hexagon-l2vic.rst new file mode 100644 index 000000000000..088563627445 --- /dev/null +++ b/docs/devel/hexagon-l2vic.rst @@ -0,0 +1,59 @@ +Hexagon L2 Vectored Interrupt Controller +======================================== + + +.. code-block:: none + + +-------+ + | | +----------------+ + | l2vic | | hexagon core | + | | | | + | +-----| | | + ------> |VID0 >------------->irq2 -\ | + ------> | | | | | + ... > | | | | | + ------> | | | | + | +-----| | / | | \ | + | ... | | | | | | | + | +-----| | t0 t1 t2 t3 ...| + ------> |VIDN | | | + ------> | | | | + ------> | | | | + ------> | | | | + | +-----| | | + | | |Global SREG File| + | State | | | + | [ ]|<============|=>[VID ] | + | [ ]|<============|=>[VID1] | + | [ ]| | | + | [ ]| | | + | | | | + +-------+ +----------------+ + +L2VIC/Core Integration +---------------------- + +* hexagon core supports 8 external interrupt sources +* l2vic supports 1024 input interrupts mapped among 4 output interrupts +* l2vic has four output signals: { VID0, VID1, VID2, VID3 } +* l2vic device has a bank of registers per-VID that can be used to query + the status or assert new interrupts. +* Interrupts are 'steered' to threads based on { thread priority, 'EX' state, + thread interrupt mask, thread interrupt enable, global interrupt enable, + etc. }. +* Any hardware thread could conceivably handle any input interrupt, dependent + on state. +* The system register transfer instruction can read the VID0-VID3 values from + the l2vic when reading from hexagon core system registers "VID" and "VID1". +* When l2vic VID0 has multiple active interrupts, it pulses the VID0 output + IRQ and stores the IRQ number for the VID0 register field. Only after this + interrupt is cleared can the l2vic pulse the VID0 output IRQ again and provide + the next interrupt number on the VID0 register. +* The ``ciad`` instruction clears the l2vic input interrupt and un-disables the + core interrupt. If some/an l2vic VID0 interrupt is pending when this occurs, + the next interrupt should fire and any subseqeunt reads of the VID register + should reflect the newly raised interrupt. +* In QEMU, on an external interrupt or an unmasked-pending interrupt, + all vCPUs are triggered (has_work==true) and each will grab the IO lock + while considering the steering logic to determine whether they're the thread + that must handle the interrupt. diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index 0471db80645f..6620497595ab 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst @@ -15,6 +15,7 @@ Details about QEMU's various subsystems including how to add features to them. clocks ebpf_rss hexagon-sys + hexagon-l2vic migration/index multi-process reset diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index 7547528f2c27..a5b136e2fa72 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -8,6 +8,9 @@ config I8259 config PL190 bool +config L2VIC + bool + config IOAPIC bool select I8259 diff --git a/hw/intc/l2vic.c b/hw/intc/l2vic.c new file mode 100644 index 000000000000..1c450179dd6d --- /dev/null +++ b/hw/intc/l2vic.c @@ -0,0 +1,417 @@ +/* + * QEMU L2VIC Interrupt Controller + * + * Arm PrimeCell PL190 Vector Interrupt Controller was used as a reference. + * Copyright(c) 2020-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/intc/l2vic.h" +#include "trace.h" + +#define L2VICA(s, n) (s[(n) >> 2]) + +#define TYPE_L2VIC "l2vic" +OBJECT_DECLARE_SIMPLE_TYPE(L2VICState, L2VIC) + +#define SLICE_MAX (L2VIC_INTERRUPT_MAX / 32) + +typedef struct L2VICState { + SysBusDevice parent_obj; + + QemuMutex active; + MemoryRegion iomem; + MemoryRegion fast_iomem; + uint32_t level; + /* + * offset 0:vid group 0 etc, 10 bits in each group + * are used: + */ + uint32_t vid_group[4]; + uint32_t vid0; + /* Clear Status of Active Edge interrupt, not used: */ + uint32_t int_clear[SLICE_MAX] QEMU_ALIGNED(16); + /* Enable interrupt source */ + uint32_t int_enable[SLICE_MAX] QEMU_ALIGNED(16); + /* Clear (set to 0) corresponding bit in int_enable */ + uint32_t int_enable_clear; + /* Set (to 1) corresponding bit in int_enable */ + uint32_t int_enable_set; + /* Present for debugging, not used */ + uint32_t int_pending[SLICE_MAX] QEMU_ALIGNED(16); + /* Generate an interrupt */ + uint32_t int_soft; + /* Which enabled interrupt is active */ + uint32_t int_status[SLICE_MAX] QEMU_ALIGNED(16); + /* Edge or Level interrupt */ + uint32_t int_type[SLICE_MAX] QEMU_ALIGNED(16); + /* L2 interrupt group 0-3 0x600-0x7FF */ + uint32_t int_group_n0[SLICE_MAX] QEMU_ALIGNED(16); + uint32_t int_group_n1[SLICE_MAX] QEMU_ALIGNED(16); + uint32_t int_group_n2[SLICE_MAX] QEMU_ALIGNED(16); + uint32_t int_group_n3[SLICE_MAX] QEMU_ALIGNED(16); + qemu_irq irq[8]; +} L2VICState; + + +/* + * Find out if this irq is associated with a group other than + * the default group + */ +static uint32_t *get_int_group(L2VICState *s, int irq) +{ + int n = irq & 0x1f; + if (n < 8) { + return s->int_group_n0; + } + if (n < 16) { + return s->int_group_n1; + } + if (n < 24) { + return s->int_group_n2; + } + return s->int_group_n3; +} + +static int find_slice(int irq) +{ + return irq / 32; +} + +static int get_vid(L2VICState *s, int irq) +{ + uint32_t *group = get_int_group(s, irq); + uint32_t slice = group[find_slice(irq)]; + /* Mask with 0x7 to remove the GRP:EN bit */ + uint32_t val = slice >> ((irq & 0x7) * 4); + if (val & 0x8) { + return val & 0x7; + } else { + return 0; + } +} + +static inline bool vid_active(L2VICState *s) + +{ + /* scan all 1024 bits in int_status arrary */ + const int size = sizeof(s->int_status) * CHAR_BIT; + const int active_irq = find_first_bit((unsigned long *)s->int_status, size); + return ((active_irq != size)) ? true : false; +} + +static bool l2vic_update(L2VICState *s, int irq) +{ + if (vid_active(s)) { + return true; + } + + bool pending = test_bit(irq, (unsigned long *)s->int_pending); + bool enable = test_bit(irq, (unsigned long *)s->int_enable); + if (pending && enable) { + int vid = get_vid(s, irq); + set_bit(irq, (unsigned long *)s->int_status); + clear_bit(irq, (unsigned long *)s->int_pending); + clear_bit(irq, (unsigned long *)s->int_enable); + /* ensure the irq line goes low after going high */ + s->vid0 = irq; + s->vid_group[get_vid(s, irq)] = irq; + + /* already low: now call pulse */ + /* pulse: calls qemu_upper() and then qemu_lower()) */ + qemu_irq_pulse(s->irq[vid + 2]); + trace_l2vic_delivered(irq, vid); + return true; + } + return false; +} + +static void l2vic_update_all(L2VICState *s) +{ + for (int i = 0; i < L2VIC_INTERRUPT_MAX; i++) { + if (l2vic_update(s, i) == true) { + /* once vid is active, no-one else can set it until ciad */ + return; + } + } +} + +static void l2vic_set_irq(void *opaque, int irq, int level) +{ + L2VICState *s = (L2VICState *)opaque; + if (level) { + qemu_mutex_lock(&s->active); + set_bit(irq, (unsigned long *)s->int_pending); + qemu_mutex_unlock(&s->active); + } + l2vic_update(s, irq); +} + +static void l2vic_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + L2VICState *s = (L2VICState *)opaque; + qemu_mutex_lock(&s->active); + trace_l2vic_reg_write((unsigned)offset, (uint32_t)val); + + if (offset == L2VIC_VID_0) { + if ((int)val != L2VIC_CIAD_INSTRUCTION) { + s->vid0 = val; + } else { + /* ciad issued: clear int_status */ + clear_bit(s->vid0, (unsigned long *)s->int_status); + } + } else if (offset >= L2VIC_INT_ENABLEn && + offset < (L2VIC_INT_ENABLE_CLEARn)) { + L2VICA(s->int_enable, offset - L2VIC_INT_ENABLEn) = val; + } else if (offset >= L2VIC_INT_ENABLE_CLEARn && + offset < L2VIC_INT_ENABLE_SETn) { + L2VICA(s->int_enable, offset - L2VIC_INT_ENABLE_CLEARn) &= ~val; + } else if (offset >= L2VIC_INT_ENABLE_SETn && offset < L2VIC_INT_TYPEn) { + L2VICA(s->int_enable, offset - L2VIC_INT_ENABLE_SETn) |= val; + } else if (offset >= L2VIC_INT_TYPEn && offset < L2VIC_INT_TYPEn + 0x80) { + L2VICA(s->int_type, offset - L2VIC_INT_TYPEn) = val; + } else if (offset >= L2VIC_INT_STATUSn && offset < L2VIC_INT_CLEARn) { + L2VICA(s->int_status, offset - L2VIC_INT_STATUSn) = val; + } else if (offset >= L2VIC_INT_CLEARn && offset < L2VIC_SOFT_INTn) { + L2VICA(s->int_clear, offset - L2VIC_INT_CLEARn) = val; + } else if (offset >= L2VIC_INT_PENDINGn && + offset < L2VIC_INT_PENDINGn + 0x80) { + L2VICA(s->int_pending, offset - L2VIC_INT_PENDINGn) = val; + } else if (offset >= L2VIC_SOFT_INTn && offset < L2VIC_INT_PENDINGn) { + L2VICA(s->int_enable, offset - L2VIC_SOFT_INTn) |= val; + /* + * Need to reverse engineer the actual irq number. + */ + int irq = find_first_bit((unsigned long *)&val, + sizeof(s->int_enable[0]) * CHAR_BIT); + hwaddr byteoffset = offset - L2VIC_SOFT_INTn; + g_assert(irq != sizeof(s->int_enable[0]) * CHAR_BIT); + irq += byteoffset * 8; + + /* The soft-int interface only works with edge-triggered interrupts */ + if (test_bit(irq, (unsigned long *)s->int_type)) { + qemu_mutex_unlock(&s->active); + l2vic_set_irq(opaque, irq, 1); + qemu_mutex_lock(&s->active); + } + } else if (offset >= L2VIC_INT_GRPn_0 && offset < L2VIC_INT_GRPn_1) { + L2VICA(s->int_group_n0, offset - L2VIC_INT_GRPn_0) = val; + } else if (offset >= L2VIC_INT_GRPn_1 && offset < L2VIC_INT_GRPn_2) { + L2VICA(s->int_group_n1, offset - L2VIC_INT_GRPn_1) = val; + } else if (offset >= L2VIC_INT_GRPn_2 && offset < L2VIC_INT_GRPn_3) { + L2VICA(s->int_group_n2, offset - L2VIC_INT_GRPn_2) = val; + } else if (offset >= L2VIC_INT_GRPn_3 && offset < L2VIC_INT_GRPn_3 + 0x80) { + L2VICA(s->int_group_n3, offset - L2VIC_INT_GRPn_3) = val; + } else { + qemu_log_mask(LOG_UNIMP, "%s: offset %x unimplemented\n", __func__, + (int)offset); + } + l2vic_update_all(s); + qemu_mutex_unlock(&s->active); + return; +} + +static uint64_t l2vic_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t value; + L2VICState *s = (L2VICState *)opaque; + qemu_mutex_lock(&s->active); + + if (offset == L2VIC_VID_GRP_0) { + value = s->vid_group[0]; + } else if (offset == L2VIC_VID_GRP_1) { + value = s->vid_group[1]; + } else if (offset == L2VIC_VID_GRP_2) { + value = s->vid_group[2]; + } else if (offset == L2VIC_VID_GRP_3) { + value = s->vid_group[3]; + } else if (offset == L2VIC_VID_0) { + value = s->vid0; + } else if (offset >= L2VIC_INT_ENABLEn && + offset < L2VIC_INT_ENABLE_CLEARn) { + value = L2VICA(s->int_enable, offset - L2VIC_INT_ENABLEn); + } else if (offset >= L2VIC_INT_ENABLE_CLEARn && + offset < L2VIC_INT_ENABLE_SETn) { + value = 0; + } else if (offset >= L2VIC_INT_ENABLE_SETn && offset < L2VIC_INT_TYPEn) { + value = 0; + } else if (offset >= L2VIC_INT_TYPEn && offset < L2VIC_INT_TYPEn + 0x80) { + value = L2VICA(s->int_type, offset - L2VIC_INT_TYPEn); + } else if (offset >= L2VIC_INT_STATUSn && offset < L2VIC_INT_CLEARn) { + value = L2VICA(s->int_status, offset - L2VIC_INT_STATUSn); + } else if (offset >= L2VIC_INT_CLEARn && offset < L2VIC_SOFT_INTn) { + value = L2VICA(s->int_clear, offset - L2VIC_INT_CLEARn); + } else if (offset >= L2VIC_SOFT_INTn && offset < L2VIC_INT_PENDINGn) { + value = 0; + } else if (offset >= L2VIC_INT_PENDINGn && + offset < L2VIC_INT_PENDINGn + 0x80) { + value = L2VICA(s->int_pending, offset - L2VIC_INT_PENDINGn); + } else if (offset >= L2VIC_INT_GRPn_0 && offset < L2VIC_INT_GRPn_1) { + value = L2VICA(s->int_group_n0, offset - L2VIC_INT_GRPn_0); + } else if (offset >= L2VIC_INT_GRPn_1 && offset < L2VIC_INT_GRPn_2) { + value = L2VICA(s->int_group_n1, offset - L2VIC_INT_GRPn_1); + } else if (offset >= L2VIC_INT_GRPn_2 && offset < L2VIC_INT_GRPn_3) { + value = L2VICA(s->int_group_n2, offset - L2VIC_INT_GRPn_2); + } else if (offset >= L2VIC_INT_GRPn_3 && offset < L2VIC_INT_GRPn_3 + 0x80) { + value = L2VICA(s->int_group_n3, offset - L2VIC_INT_GRPn_3); + } else { + value = 0; + qemu_log_mask(LOG_GUEST_ERROR, "L2VIC: %s: offset 0x%x\n", __func__, + (int)offset); + } + + trace_l2vic_reg_read((unsigned)offset, value); + qemu_mutex_unlock(&s->active); + + return value; +} + +static const MemoryRegionOps l2vic_ops = { + .read = l2vic_read, + .write = l2vic_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +#define FASTL2VIC_ENABLE 0x0 +#define FASTL2VIC_DISABLE 0x1 +#define FASTL2VIC_INT 0x2 + +static void fastl2vic_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + if (offset == 0) { + uint32_t cmd = (val >> 16) & 0x3; + uint32_t irq = val & 0x3ff; + uint32_t slice = (irq / 32) * 4; + val = 1 << (irq % 32); + + if (cmd == FASTL2VIC_ENABLE) { + l2vic_write(opaque, L2VIC_INT_ENABLE_SETn + slice, val, size); + } else if (cmd == FASTL2VIC_DISABLE) { + l2vic_write(opaque, L2VIC_INT_ENABLE_CLEARn + slice, val, size); + } else if (cmd == FASTL2VIC_INT) { + l2vic_write(opaque, L2VIC_SOFT_INTn + slice, val, size); + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write cmd %" PRId32 "\n", + __func__, cmd); + return; + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write offset 0x%08" HWADDR_PRIx + "\n", __func__, offset); +} + +static const MemoryRegionOps fastl2vic_ops = { + .write = fastl2vic_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +static void l2vic_reset_hold(Object *obj, G_GNUC_UNUSED ResetType res_type) +{ + L2VICState *s = L2VIC(obj); + memset(s->int_clear, 0, sizeof(s->int_clear)); + memset(s->int_enable, 0, sizeof(s->int_enable)); + memset(s->int_pending, 0, sizeof(s->int_pending)); + memset(s->int_status, 0, sizeof(s->int_status)); + memset(s->int_type, 0, sizeof(s->int_type)); + memset(s->int_group_n0, 0, sizeof(s->int_group_n0)); + memset(s->int_group_n1, 0, sizeof(s->int_group_n1)); + memset(s->int_group_n2, 0, sizeof(s->int_group_n2)); + memset(s->int_group_n3, 0, sizeof(s->int_group_n3)); + s->int_soft = 0; + s->vid0 = 0; + + l2vic_update_all(s); +} + + +static void reset_irq_handler(void *opaque, int irq, int level) +{ + L2VICState *s = (L2VICState *)opaque; + Object *obj = OBJECT(opaque); + if (level) { + l2vic_reset_hold(obj, RESET_TYPE_COLD); + } + l2vic_update_all(s); +} + +static void l2vic_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + L2VICState *s = L2VIC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + int i; + + memory_region_init_io(&s->iomem, obj, &l2vic_ops, s, "l2vic", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + memory_region_init_io(&s->fast_iomem, obj, &fastl2vic_ops, s, "fast", + 0x10000); + sysbus_init_mmio(sbd, &s->fast_iomem); + + qdev_init_gpio_in(dev, l2vic_set_irq, L2VIC_INTERRUPT_MAX); + qdev_init_gpio_in_named(dev, reset_irq_handler, "reset", 1); + for (i = 0; i < 8; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } + qemu_mutex_init(&s->active); /* TODO: Remove this is an experiment */ +} + +static const VMStateDescription vmstate_l2vic = { + .name = "l2vic", + .version_id = 1, + .minimum_version_id = 1, + .fields = + (VMStateField[]){ + VMSTATE_UINT32(level, L2VICState), + VMSTATE_UINT32_ARRAY(vid_group, L2VICState, 4), + VMSTATE_UINT32(vid0, L2VICState), + VMSTATE_UINT32_ARRAY(int_enable, L2VICState, SLICE_MAX), + VMSTATE_UINT32(int_enable_clear, L2VICState), + VMSTATE_UINT32(int_enable_set, L2VICState), + VMSTATE_UINT32_ARRAY(int_type, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_status, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_clear, L2VICState, SLICE_MAX), + VMSTATE_UINT32(int_soft, L2VICState), + VMSTATE_UINT32_ARRAY(int_pending, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n0, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n1, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n2, L2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_group_n3, L2VICState, SLICE_MAX), + VMSTATE_END_OF_LIST() } +}; + +static void l2vic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->vmsd = &vmstate_l2vic; + rc->phases.hold = l2vic_reset_hold; +} + +static const TypeInfo l2vic_info = { + .name = TYPE_L2VIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(L2VICState), + .instance_init = l2vic_init, + .class_init = l2vic_class_init, +}; + +static void l2vic_register_types(void) +{ + type_register_static(&l2vic_info); +} + +type_init(l2vic_register_types) diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 602da304b02d..35f4a7bad5ef 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -67,6 +67,8 @@ specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('xics_spapr.c', 'spapr_xi specific_ss.add(when: 'CONFIG_XIVE', if_true: files('xive.c')) specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'], if_true: files('spapr_xive_kvm.c')) + +specific_ss.add(when: 'CONFIG_L2VIC', if_true: files('l2vic.c')) specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c')) specific_ss.add(when: 'CONFIG_LOONGSON_IPI_COMMON', if_true: files('loongson_ipi_common.c')) specific_ss.add(when: 'CONFIG_LOONGSON_IPI', if_true: files('loongson_ipi.c')) diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 0ba9a02e7397..daffdbc0f482 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -308,6 +308,10 @@ sh_intc_register(const char *s, int id, unsigned short v, int c, int m) "%s %u - sh_intc_read(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " -> 0x%lx" sh_intc_write(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " <- 0x%lx" sh_intc_set(int id, int enable) "setting interrupt group %d to %d" +# l2vic.c +l2vic_reg_write(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32 +l2vic_reg_read(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32 +l2vic_delivered(int irq, int vid) "l2vic: delivered %d (vid %d)" # loongson_ipi.c loongson_ipi_read(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64 diff --git a/include/hw/intc/l2vic.h b/include/hw/intc/l2vic.h new file mode 100644 index 000000000000..ed8ccf33b1f8 --- /dev/null +++ b/include/hw/intc/l2vic.h @@ -0,0 +1,37 @@ +/* + * QEMU L2VIC Interrupt Controller + * + * Copyright(c) 2020-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define L2VIC_VID_GRP_0 0x0 /* Read */ +#define L2VIC_VID_GRP_1 0x4 /* Read */ +#define L2VIC_VID_GRP_2 0x8 /* Read */ +#define L2VIC_VID_GRP_3 0xC /* Read */ +#define L2VIC_VID_0 0x10 /* Read SOFTWARE DEFINED */ +#define L2VIC_VID_1 0x14 /* Read SOFTWARE DEFINED NOT YET USED */ +#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */ +#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */ +#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */ +#define L2VIC_INT_TYPEn 0x280 /* Read/Write */ +#define L2VIC_INT_STATUSn 0x380 /* Read */ +#define L2VIC_INT_CLEARn 0x400 /* Write */ +#define L2VIC_SOFT_INTn 0x480 /* Write */ +#define L2VIC_INT_PENDINGn 0x500 /* Read */ +#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */ +#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */ +#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */ +#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */ + +#define L2VIC_INTERRUPT_MAX 1024 +#define L2VIC_CIAD_INSTRUCTION -1 +/* + * Note about l2vic groups: + * Each interrupt to L2VIC can be configured to associate with one of + * four groups. + * Group 0 interrupts go to IRQ2 via VID 0 (SSR: 0xC2, the default) + * Group 1 interrupts go to IRQ3 via VID 1 (SSR: 0xC3) + * Group 2 interrupts go to IRQ4 via VID 2 (SSR: 0xC4) + * Group 3 interrupts go to IRQ5 via VID 3 (SSR: 0xC5) + */ From d6117a25c0b9df05c09bd7582e9a5a701507782b Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sat, 2 Dec 2023 10:09:53 -0800 Subject: [PATCH 1136/1179] hw/hexagon: Add machine configs for sysemu Co-authored-by: Mike Lambert Co-authored-by: Sid Manning Signed-off-by: Brian Cain --- MAINTAINERS | 2 + hw/Kconfig | 1 + hw/hexagon/Kconfig | 6 + hw/hexagon/hexagon_dsp.c | 177 +++++++++++++++++++++++++ hw/hexagon/machine_cfg_v66g_1024.h.inc | 64 +++++++++ hw/hexagon/meson.build | 5 + hw/meson.build | 1 + include/hw/hexagon/hexagon.h | 151 +++++++++++++++++++++ qapi/machine.json | 2 +- target/hexagon/machine.c | 1 - 10 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 hw/hexagon/Kconfig create mode 100644 hw/hexagon/hexagon_dsp.c create mode 100644 hw/hexagon/machine_cfg_v66g_1024.h.inc create mode 100644 hw/hexagon/meson.build create mode 100644 include/hw/hexagon/hexagon.h diff --git a/MAINTAINERS b/MAINTAINERS index 8ae8403c025e..98418e2c1cbe 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -226,6 +226,8 @@ M: Brian Cain S: Supported F: target/hexagon/ F: hw/intc/l2vic.[ch] +F: hw/hexagon/ +F: include/hw/hexagon/ X: target/hexagon/idef-parser/ X: target/hexagon/gen_idef_parser_funcs.py F: linux-user/hexagon/ diff --git a/hw/Kconfig b/hw/Kconfig index 9a86a6a28a64..4dc7914c13f5 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -67,6 +67,7 @@ source sparc/Kconfig source sparc64/Kconfig source tricore/Kconfig source xtensa/Kconfig +source hexagon/Kconfig # Symbols used by multiple targets config TEST_DEVICES diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig new file mode 100644 index 000000000000..3fc14756e6df --- /dev/null +++ b/hw/hexagon/Kconfig @@ -0,0 +1,6 @@ +config HEX_DSP + bool + default y + depends on HEXAGON && TCG + imply PTIMER + select L2VIC # Vector PIC diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c new file mode 100644 index 000000000000..9f18cb6e3ad1 --- /dev/null +++ b/hw/hexagon/hexagon_dsp.c @@ -0,0 +1,177 @@ +/* + * Hexagon DSP Subsystem emulation. This represents a generic DSP + * subsystem with few peripherals, like the Compute DSP. + * + * Copyright (c) 2020-2024 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "exec/address-spaces.h" +#include "hw/hw.h" +#include "hw/boards.h" +#include "hw/qdev-properties.h" +#include "hw/hexagon/hexagon.h" +#include "hw/loader.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "elf.h" +#include "cpu.h" +#include "include/migration/cpu.h" +#include "include/system/system.h" +#include "target/hexagon/internal.h" +#include "system/reset.h" + +#include "machine_cfg_v66g_1024.h.inc" + +static void hex_symbol_callback(const char *st_name, int st_info, + uint64_t st_value, uint64_t st_size) +{ +} + +/* Board init. */ +static struct hexagon_board_boot_info hexagon_binfo; + +static void hexagon_load_kernel(HexagonCPU *cpu) +{ + uint64_t pentry; + long kernel_size; + + kernel_size = load_elf_ram_sym(hexagon_binfo.kernel_filename, NULL, NULL, + NULL, &pentry, NULL, NULL, + &hexagon_binfo.kernel_elf_flags, 0, EM_HEXAGON, 0, 0, + &address_space_memory, false, hex_symbol_callback); + + if (kernel_size <= 0) { + error_report("no kernel file '%s'", + hexagon_binfo.kernel_filename); + exit(1); + } + + qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", pentry); +} + +static void hexagon_init_bootstrap(MachineState *machine, HexagonCPU *cpu) +{ + if (machine->kernel_filename) { + hexagon_load_kernel(cpu); + } +} + +static void do_cpu_reset(void *opaque) +{ + HexagonCPU *cpu = opaque; + CPUState *cs = CPU(cpu); + cpu_reset(cs); +} + +static void hexagon_common_init(MachineState *machine, Rev_t rev, + hexagon_machine_config *m_cfg) +{ + memset(&hexagon_binfo, 0, sizeof(hexagon_binfo)); + if (machine->kernel_filename) { + hexagon_binfo.ram_size = machine->ram_size; + hexagon_binfo.kernel_filename = machine->kernel_filename; + } + + machine->enable_graphics = 0; + + MemoryRegion *address_space = get_system_memory(); + + MemoryRegion *sram = g_new(MemoryRegion, 1); + memory_region_init_ram(sram, NULL, "ddr.ram", + machine->ram_size, &error_fatal); + memory_region_add_subregion(address_space, 0x0, sram); + + Error **errp = NULL; + + for (int i = 0; i < machine->smp.cpus; i++) { + HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type)); + qemu_register_reset(do_cpu_reset, cpu); + + /* + * CPU #0 is the only CPU running at boot, others must be + * explicitly enabled via start instruction. + */ + qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0)); + qdev_prop_set_uint32(DEVICE(cpu), "l2vic-base-addr", m_cfg->l2vic_base); + qdev_prop_set_uint32(DEVICE(cpu), "config-table-addr", m_cfg->cfgbase); + qdev_prop_set_uint32(DEVICE(cpu), "hvx-contexts", + m_cfg->cfgtable.ext_contexts); + qdev_prop_set_uint32(DEVICE(cpu), "jtlb-entries", + m_cfg->cfgtable.jtlb_size_entries); + + + if (i == 0) { + hexagon_init_bootstrap(machine, cpu); + if (!qdev_realize_and_unref(DEVICE(cpu), NULL, errp)) { + return; + } + DeviceState *l2vic_dev; + l2vic_dev = sysbus_create_varargs("l2vic", m_cfg->l2vic_base, + /* IRQ#, Evnt#,CauseCode */ + qdev_get_gpio_in(DEVICE(cpu), 0), + qdev_get_gpio_in(DEVICE(cpu), 1), + qdev_get_gpio_in(DEVICE(cpu), 2), + qdev_get_gpio_in(DEVICE(cpu), 3), + qdev_get_gpio_in(DEVICE(cpu), 4), + qdev_get_gpio_in(DEVICE(cpu), 5), + qdev_get_gpio_in(DEVICE(cpu), 6), + qdev_get_gpio_in(DEVICE(cpu), 7), + NULL); + sysbus_mmio_map(SYS_BUS_DEVICE(l2vic_dev), 1, + m_cfg->cfgtable.fastl2vic_base << 16); + } else if (!qdev_realize_and_unref(DEVICE(cpu), NULL, errp)) { + return; + } + + } +} + +static void init_mc(MachineClass *mc) +{ + mc->block_default_type = IF_SD; + mc->default_ram_size = 4 * GiB; + mc->no_parallel = 1; + mc->no_floppy = 1; + mc->no_cdrom = 1; + mc->no_serial = 1; + mc->is_default = false; + mc->max_cpus = 8; +} + +/* ----------------------------------------------------------------- */ +/* Core-specific configuration settings are defined below this line. */ +/* Config table values defined in machine_configs.h.inc */ +/* ----------------------------------------------------------------- */ + +static void v66g_1024_config_init(MachineState *machine) +{ + hexagon_common_init(machine, v66_rev, &v66g_1024); +} + +static void v66g_1024_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "Hexagon V66G_1024"; + mc->init = v66g_1024_config_init; + init_mc(mc); + mc->is_default = true; + mc->default_cpu_type = TYPE_HEXAGON_CPU_V66; + mc->default_cpus = 4; +} + +static const TypeInfo hexagon_machine_types[] = { + { + .name = MACHINE_TYPE_NAME("V66G_1024"), + .parent = TYPE_MACHINE, + .class_init = v66g_1024_init, + }, +}; + +DEFINE_TYPES(hexagon_machine_types) diff --git a/hw/hexagon/machine_cfg_v66g_1024.h.inc b/hw/hexagon/machine_cfg_v66g_1024.h.inc new file mode 100644 index 000000000000..604cc7777cbf --- /dev/null +++ b/hw/hexagon/machine_cfg_v66g_1024.h.inc @@ -0,0 +1,64 @@ + +static hexagon_machine_config v66g_1024 = { + .cfgbase = 0xd8180000, + .l2tcm_size = 0x00000000, + .l2vic_base = 0xfc910000, + .l2vic_size = 0x00001000, + .csr_base = 0xfc900000, + .qtmr_rg0 = 0xfc921000, + .qtmr_rg1 = 0xfc922000, + .cfgtable = { + .l2tcm_base = 0x0000d800, + .reserved0 = 0x0000d400, + .subsystem_base = 0x0000fc90, + .etm_base = 0x0000d805, + .l2cfg_base = 0x0000d81a, + .reserved1 = 0x00000000, + .l1s0_base = 0x0000d820, + .axi2_lowaddr = 0x00003000, + .streamer_base = 0x00000000, + .reserved2 = 0x0000d819, + .fastl2vic_base = 0x0000d81e, + .jtlb_size_entries = 0x00000080, + .coproc_present = 0x00000001, + .ext_contexts = 0x00000004, + .vtcm_base = 0x0000d820, + .vtcm_size_kb = 0x00000100, + .l2tag_size = 0x00000400, + .l2ecomem_size = 0x00000400, + .thread_enable_mask = 0x0000000f, + .eccreg_base = 0x0000d81f, + .l2line_size = 0x00000080, + .tiny_core = 0x00000000, + .l2itcm_size = 0x00000000, + .l2itcm_base = 0x0000d820, + .reserved3 = 0x00000000, + .dtm_present = 0x00000000, + .dma_version = 0x00000000, + .hvx_vec_log_length = 0x00000080, + .core_id = 0x00000000, + .core_count = 0x00000000, + .coproc2_reg0 = 0x00000000, + .coproc2_reg1 = 0x00000000, + .v2x_mode = 0x00000000, + .coproc2_reg2 = 0x00000000, + .coproc2_reg3 = 0x00000000, + .coproc2_reg4 = 0x00000000, + .coproc2_reg5 = 0x00000000, + .coproc2_reg6 = 0x00000000, + .coproc2_reg7 = 0x00000000, + .acd_preset = 0x00000000, + .mnd_preset = 0x00000000, + .l1d_size_kb = 0x00000000, + .l1i_size_kb = 0x00000000, + .l1d_write_policy = 0x00000000, + .vtcm_bank_width = 0x00000000, + .reserved3 = 0x00000000, + .reserved4 = 0x00000000, + .reserved5 = 0x00000000, + .coproc2_cvt_mpy_size = 0x00000000, + .consistency_domain = 0x00000000, + .capacity_domain = 0x00000000, + .axi3_lowaddr = 0x00000000, + }, +}; diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build new file mode 100644 index 000000000000..2ef3dbcd3492 --- /dev/null +++ b/hw/hexagon/meson.build @@ -0,0 +1,5 @@ +hexagon_ss = ss.source_set() +hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hexagon_dsp.c',)) + +hw_arch += {'hexagon': hexagon_ss} + diff --git a/hw/meson.build b/hw/meson.build index b91f761fe08a..6aaf469f95e4 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -66,3 +66,4 @@ subdir('sparc') subdir('sparc64') subdir('tricore') subdir('xtensa') +subdir('hexagon') diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h new file mode 100644 index 000000000000..0afaac3b1f85 --- /dev/null +++ b/include/hw/hexagon/hexagon.h @@ -0,0 +1,151 @@ +/* + * Hexagon Baseboard System emulation. + * + * Copyright (c) 2020-2024 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#ifndef HW_HEXAGON_H +#define HW_HEXAGON_H + +#include "exec/memory.h" + +struct hexagon_board_boot_info { + uint64_t ram_size; + const char *kernel_filename; + uint32_t kernel_elf_flags; +}; + +typedef enum { + unknown_rev = 0, + v66_rev = 0xa666, + v67_rev = 0x2667, + v68_rev = 0x8d68, + v69_rev = 0x8c69, + v71_rev = 0x8c71, + v73_rev = 0x8c73, + v73m_rev = 0xcc73, +} Rev_t; +#define HEXAGON_LATEST_REV v73 +#define HEXAGON_LATEST_REV_UPPER V73 + +/* + * Config table address bases represent bits [35:16]. + */ +#define HEXAGON_CFG_ADDR_BASE(addr) (((addr) >> 16) & 0x0fffff) + +#define HEXAGON_CFGSPACE_ENTRIES (128) + +typedef union { + struct { + /* Base address of L2TCM space */ + uint32_t l2tcm_base; + uint32_t reserved0; + /* Base address of subsystem space */ + uint32_t subsystem_base; + /* Base address of ETM space */ + uint32_t etm_base; + /* Base address of L2 configuration space */ + uint32_t l2cfg_base; + uint32_t reserved1; + /* Base address of L1S */ + uint32_t l1s0_base; + /* Base address of AXI2 */ + uint32_t axi2_lowaddr; + /* Base address of streamer base */ + uint32_t streamer_base; + uint32_t reserved2; + /* Base address of fast L2VIC */ + uint32_t fastl2vic_base; + /* Number of entries in JTLB */ + uint32_t jtlb_size_entries; + /* Coprocessor type */ + uint32_t coproc_present; + /* Number of extension execution contexts available */ + uint32_t ext_contexts; + /* Base address of Hexagon Vector Tightly Coupled Memory (VTCM) */ + uint32_t vtcm_base; + /* Size of VTCM (in KB) */ + uint32_t vtcm_size_kb; + /* L2 tag size */ + uint32_t l2tag_size; + /* Amount of physical L2 memory in released version */ + uint32_t l2ecomem_size; + /* Hardware threads available on the core */ + uint32_t thread_enable_mask; + /* Base address of the ECC registers */ + uint32_t eccreg_base; + /* L2 line size */ + uint32_t l2line_size; + /* Small Core processor (also implies audio extension) */ + uint32_t tiny_core; + /* Size of L2TCM */ + uint32_t l2itcm_size; + /* Base address of L2-ITCM */ + uint32_t l2itcm_base; + uint32_t reserved3; + /* DTM is present */ + uint32_t dtm_present; + /* Version of the DMA */ + uint32_t dma_version; + /* Native HVX vector length in log of bytes */ + uint32_t hvx_vec_log_length; + /* Core ID of the multi-core */ + uint32_t core_id; + /* Number of multi-core cores */ + uint32_t core_count; + uint32_t coproc2_reg0; + uint32_t coproc2_reg1; + /* Supported HVX vector length */ + uint32_t v2x_mode; + uint32_t coproc2_reg2; + uint32_t coproc2_reg3; + uint32_t coproc2_reg4; + uint32_t coproc2_reg5; + uint32_t coproc2_reg6; + uint32_t coproc2_reg7; + /* Voltage droop mitigation technique parameter */ + uint32_t acd_preset; + /* Voltage droop mitigation technique parameter */ + uint32_t mnd_preset; + /* L1 data cache size (in KB) */ + uint32_t l1d_size_kb; + /* L1 instruction cache size in (KB) */ + uint32_t l1i_size_kb; + /* L1 data cache write policy: see HexagonL1WritePolicy */ + uint32_t l1d_write_policy; + /* VTCM bank width */ + uint32_t vtcm_bank_width; + uint32_t reserved4; + uint32_t reserved5; + uint32_t reserved6; + uint32_t coproc2_cvt_mpy_size; + uint32_t consistency_domain; + uint32_t capacity_domain; + uint32_t axi3_lowaddr; + uint32_t coproc2_int8_subcolumns; + uint32_t corecfg_present; + uint32_t coproc2_fp16_acc_exp; + uint32_t AXIM2_secondary_base; + }; + uint32_t raw[HEXAGON_CFGSPACE_ENTRIES]; +} hexagon_config_table; + +typedef struct { + /* Base address of config table */ + uint32_t cfgbase; + /* Size of L2 TCM */ + uint32_t l2tcm_size; + /* Base address of L2VIC */ + uint32_t l2vic_base; + /* Size of L2VIC region */ + uint32_t l2vic_size; + /* QTimer csr base */ + uint32_t csr_base; + uint32_t qtmr_rg0; + uint32_t qtmr_rg1; + hexagon_config_table cfgtable; +} hexagon_machine_config; + +#endif diff --git a/qapi/machine.json b/qapi/machine.json index a6b8795b09ed..a7070bad4d52 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -33,7 +33,7 @@ # Since: 3.0 ## { 'enum' : 'SysEmuTarget', - 'data' : [ 'aarch64', 'alpha', 'arm', 'avr', 'hppa', 'i386', + 'data' : [ 'aarch64', 'alpha', 'arm', 'avr', 'hexagon', 'hppa', 'i386', 'loongarch64', 'm68k', 'microblaze', 'microblazeel', 'mips', 'mips64', 'mips64el', 'mipsel', 'or1k', 'ppc', 'ppc64', 'riscv32', 'riscv64', 'rx', 's390x', 'sh4', diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c index 4baa22d51f8e..79e9b7effa5e 100644 --- a/target/hexagon/machine.c +++ b/target/hexagon/machine.c @@ -79,7 +79,6 @@ const VMStateDescription vmstate_hexagon_cpu = { VMSTATE_UINT64(env.t_cycle_count, HexagonCPU), VMSTATE_POINTER(env.g_pcycle_base, HexagonCPU, 0, vmstate_info_uint64_ptr, uint64_t *), - VMSTATE_END_OF_LIST() }, }; From d46152734399e472d948099173ee6e107c7f6f9d Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 15 Oct 2024 18:56:30 -0700 Subject: [PATCH 1137/1179] hw/hexagon: Add v68, sa8775-cdsp0 defs Signed-off-by: Brian Cain --- hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc | 64 ++++++++++++++++++++++ hw/hexagon/machine_cfg_v68n_1024.h.inc | 65 +++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc create mode 100644 hw/hexagon/machine_cfg_v68n_1024.h.inc diff --git a/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc new file mode 100644 index 000000000000..d8fa961f6d09 --- /dev/null +++ b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc @@ -0,0 +1,64 @@ + +static hexagon_machine_config SA8775P_cdsp0 = { + .cfgbase = 0x24000000 + 0x180000, + .l2tcm_size = 0x00000000, + .l2vic_base = 0x26300000 + 0x90000, + .l2vic_size = 0x00001000, + .csr_base = 0x26300000, + .qtmr_rg0 = 0x26300000 + 0xA1000, + .qtmr_rg1 = 0x26300000 + 0xA2000, + .cfgtable = { + .l2tcm_base = 0x00002400, + .reserved0 = 0x00000000, + .subsystem_base = 0x00002638, + .etm_base = 0x00002419, + .l2cfg_base = 0x0000241a, + .reserved1 = 0x0000241b, + .l1s0_base = 0x00002500, + .axi2_lowaddr = 0x00000000, + .streamer_base = 0x00000000, + .reserved2 = 0x00000000, + .fastl2vic_base = 0x0000241e, + .jtlb_size_entries = 0x00000080, + .coproc_present = 0x00000001, + .ext_contexts = 0x00000004, + .vtcm_base = 0x00002500, + .vtcm_size_kb = 0x00002000, + .l2tag_size = 0x00000400, + .l2ecomem_size = 0x00000000, + .thread_enable_mask = 0x0000003f, + .eccreg_base = 0x0000241f, + .l2line_size = 0x00000080, + .tiny_core = 0x00000000, + .l2itcm_size = 0x00000000, + .l2itcm_base = 0x00002400, + .reserved3 = 0x00000000, + .dtm_present = 0x00000000, + .dma_version = 0x00000003, + .hvx_vec_log_length = 0x00000007, + .core_id = 0x00000000, + .core_count = 0x00000000, + .coproc2_reg0 = 0x00000040, + .coproc2_reg1 = 0x00000020, + .v2x_mode = 0x00000001, + .coproc2_reg2 = 0x00000008, + .coproc2_reg3 = 0x00000020, + .coproc2_reg4 = 0x00000000, + .coproc2_reg5 = 0x00000002, + .coproc2_reg6 = 0x00000016, + .coproc2_reg7 = 0x00000006, + .acd_preset = 0x00000001, + .mnd_preset = 0x00000000, + .l1d_size_kb = 0x00000010, + .l1i_size_kb = 0x00000020, + .l1d_write_policy = 0x00000002, + .vtcm_bank_width = 0x00000080, + .reserved3 = 0x00000001, + .reserved4 = 0x00000000, + .reserved5 = 0x00000003, + .coproc2_cvt_mpy_size = 0x0000000a, + .consistency_domain = 0x000000e0, + .capacity_domain = 0x00000080, + .axi3_lowaddr = 0x00000000, + }, +}; diff --git a/hw/hexagon/machine_cfg_v68n_1024.h.inc b/hw/hexagon/machine_cfg_v68n_1024.h.inc new file mode 100644 index 000000000000..60eb112a1199 --- /dev/null +++ b/hw/hexagon/machine_cfg_v68n_1024.h.inc @@ -0,0 +1,65 @@ + +static hexagon_machine_config v68n_1024 = { + .cfgbase = 0xde000000, + .l2tcm_size = 0x00000000, + .l2vic_base = 0xfc910000, + .l2vic_size = 0x00001000, + .csr_base = 0xfc900000, + .qtmr_rg0 = 0xfc921000, + .qtmr_rg1 = 0xfc922000, + .cfgtable = { + .l2tcm_base = 0x0000d800, + .reserved0 = 0x00000000, + .subsystem_base = 0x0000fc90, + .etm_base = 0x0000d819, + .l2cfg_base = 0x0000d81a, + .reserved1 = 0x00000000, + .l1s0_base = 0x0000d840, + .axi2_lowaddr = 0x00003000, + .streamer_base = 0x0000d81c, + .reserved2 = 0x0000d81d, + .fastl2vic_base = 0x0000d81e, + .jtlb_size_entries = 0x00000080, + .coproc_present = 0x00000001, + .ext_contexts = 0x00000004, + .vtcm_base = 0x0000d840, + .vtcm_size_kb = 0x00001000, + .l2tag_size = 0x00000400, + .l2ecomem_size = 0x00000400, + .thread_enable_mask = 0x0000003f, + .eccreg_base = 0x0000d81f, + .l2line_size = 0x00000080, + .tiny_core = 0x00000000, + .l2itcm_size = 0x00000000, + .l2itcm_base = 0x0000d820, + .reserved3 = 0x00000000, + .dtm_present = 0x00000000, + .dma_version = 0x00000001, + .hvx_vec_log_length = 0x00000007, + .core_id = 0x00000000, + .core_count = 0x00000000, + .coproc2_reg0 = 0x00000040, + .coproc2_reg1 = 0x00000020, + .v2x_mode = 0x1f1f1f1f, + .coproc2_reg2 = 0x1f1f1f1f, + .coproc2_reg3 = 0x1f1f1f1f, + .coproc2_reg4 = 0x1f1f1f1f, + .coproc2_reg5 = 0x1f1f1f1f, + .coproc2_reg6 = 0x1f1f1f1f, + .coproc2_reg7 = 0x1f1f1f1f, + .acd_preset = 0x1f1f1f1f, + .mnd_preset = 0x1f1f1f1f, + .l1d_size_kb = 0x1f1f1f1f, + .l1i_size_kb = 0x1f1f1f1f, + .l1d_write_policy = 0x1f1f1f1f, + .vtcm_bank_width = 0x1f1f1f1f, + .reserved3 = 0x1f1f1f1f, + .reserved4 = 0x1f1f1f1f, + .reserved5 = 0x1f1f1f1f, + .coproc2_cvt_mpy_size = 0x1f1f1f1f, + .consistency_domain = 0x1f1f1f1f, + .capacity_domain = 0x1f1f1f1f, + .axi3_lowaddr = 0x1f1f1f1f, + }, +}; + From 8f13a190bc95b7743b64e7ab6a3cd9915cdc0b9e Mon Sep 17 00:00:00 2001 From: Sid Manning Date: Wed, 18 Dec 2024 09:23:33 -0800 Subject: [PATCH 1138/1179] hw/hexagon: Add support for cfgbase Signed-off-by: Sid Manning --- hw/hexagon/hexagon_dsp.c | 10 ++++++++++ target/hexagon/cpu.c | 6 ++++++ target/hexagon/cpu.h | 1 + 3 files changed, 17 insertions(+) diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c index 9f18cb6e3ad1..c4962a98bc1c 100644 --- a/hw/hexagon/hexagon_dsp.c +++ b/hw/hexagon/hexagon_dsp.c @@ -82,6 +82,12 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev, MemoryRegion *address_space = get_system_memory(); + MemoryRegion *config_table_rom = g_new(MemoryRegion, 1); + memory_region_init_rom(config_table_rom, NULL, "config_table.rom", + sizeof(m_cfg->cfgtable), &error_fatal); + memory_region_add_subregion(address_space, m_cfg->cfgbase, + config_table_rom); + MemoryRegion *sram = g_new(MemoryRegion, 1); memory_region_init_ram(sram, NULL, "ddr.ram", machine->ram_size, &error_fatal); @@ -130,6 +136,10 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev, } } + + rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, + sizeof(m_cfg->cfgtable), m_cfg->cfgbase, + &address_space_memory); } static void init_mc(MachineClass *mc) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 785dd3d15d82..87eabad3dcd5 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -68,6 +68,8 @@ static const Property hexagon_cpu_properties[] = { 0xffffffffULL), DEFINE_PROP_UINT32("hvx-contexts", HexagonCPU, hvx_contexts, 0), DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffffULL), + DEFINE_PROP_UINT64("config-table-addr", HexagonCPU, config_table_addr, + 0xffffffffULL), #endif DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, @@ -357,6 +359,8 @@ void hexagon_cpu_soft_reset(CPUHexagonState *env) } #endif + +#define HEXAGON_CFG_ADDR_BASE(addr) (((addr) >> 16) & 0x0fffff) static void hexagon_cpu_reset_hold(Object *obj, ResetType type) { CPUState *cs = CPU(obj); @@ -397,6 +401,8 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) env->wait_next_pc = 0; env->cause_code = -1; arch_set_thread_reg(env, HEX_REG_PC, cpu->boot_addr); + arch_set_system_reg(env, HEX_SREG_CFGBASE, + HEXAGON_CFG_ADDR_BASE(cpu->config_table_addr)); #endif } diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 340e0a83a5ba..f5b92d33a156 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -199,6 +199,7 @@ struct ArchCPU { uint32_t l2vic_base_addr; uint32_t hvx_contexts; uint32_t boot_addr; + uint64_t config_table_addr; #endif }; From 73027d54ec9af487e6916e868d9925a322c72fd7 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 22 Oct 2024 13:50:07 -0700 Subject: [PATCH 1139/1179] hw/hexagon: Modify "Standalone" symbols These symbols are used by Hexagon Standalone OS to indicate whether the program should halt and wait for interrupts at startup. For QEMU, we want these programs to just continue crt0 startup through to the user program's main(). Signed-off-by: Brian Cain --- hw/hexagon/hexagon_dsp.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c index c4962a98bc1c..34bbe98149cf 100644 --- a/hw/hexagon/hexagon_dsp.c +++ b/hw/hexagon/hexagon_dsp.c @@ -28,9 +28,17 @@ #include "machine_cfg_v66g_1024.h.inc" +static hwaddr isdb_secure_flag; +static hwaddr isdb_trusted_flag; static void hex_symbol_callback(const char *st_name, int st_info, uint64_t st_value, uint64_t st_size) { + if (!g_strcmp0("isdb_secure_flag", st_name)) { + isdb_secure_flag = st_value; + } + if (!g_strcmp0("isdb_trusted_flag", st_name)) { + isdb_trusted_flag = st_value; + } } /* Board init. */ @@ -59,6 +67,13 @@ static void hexagon_init_bootstrap(MachineState *machine, HexagonCPU *cpu) { if (machine->kernel_filename) { hexagon_load_kernel(cpu); + uint32_t mem = 1; + if (isdb_secure_flag) { + cpu_physical_memory_write(isdb_secure_flag, &mem, sizeof(mem)); + } + if (isdb_trusted_flag) { + cpu_physical_memory_write(isdb_trusted_flag, &mem, sizeof(mem)); + } } } From 956a58492a3702bdc97ea8555926e9758c7df026 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sat, 2 Dec 2023 10:09:53 -0800 Subject: [PATCH 1140/1179] target/hexagon: add build config for softmmu Signed-off-by: Brian Cain --- MAINTAINERS | 1 + configs/devices/hexagon-softmmu/default.mak | 7 +++++++ configs/targets/hexagon-softmmu.mak | 6 ++++++ target/Kconfig | 1 + target/hexagon/Kconfig | 2 ++ target/hexagon/cpu.h | 4 ---- target/hexagon/meson.build | 9 +++++++++ 7 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 configs/devices/hexagon-softmmu/default.mak create mode 100644 configs/targets/hexagon-softmmu.mak create mode 100644 target/hexagon/Kconfig diff --git a/MAINTAINERS b/MAINTAINERS index 98418e2c1cbe..67c3c779e81c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -234,6 +234,7 @@ F: linux-user/hexagon/ F: tests/tcg/hexagon/ F: disas/hexagon.c F: configs/targets/hexagon-linux-user/default.mak +F: configs/devices/hexagon-softmmu/default.mak F: docker/dockerfiles/debian-hexagon-cross.docker F: gdb-xml/hexagon*.xml F: docs/system/target-hexagon.rst diff --git a/configs/devices/hexagon-softmmu/default.mak b/configs/devices/hexagon-softmmu/default.mak new file mode 100644 index 000000000000..08e709aea72f --- /dev/null +++ b/configs/devices/hexagon-softmmu/default.mak @@ -0,0 +1,7 @@ +# Default configuration for hexagon-softmmu + +# Uncomment the following lines to disable these optional devices: + +# Boards are selected by default, uncomment to keep out of the build. +# CONFIG_HEX_DSP=y +# CONFIG_L2VIC=y diff --git a/configs/targets/hexagon-softmmu.mak b/configs/targets/hexagon-softmmu.mak new file mode 100644 index 000000000000..8c208bf46884 --- /dev/null +++ b/configs/targets/hexagon-softmmu.mak @@ -0,0 +1,6 @@ +# Default configuration for hexagon-softmmu + +TARGET_ARCH=hexagon +TARGET_SUPPORTS_MTTCG=y +TARGET_XML_FILES=gdb-xml/hexagon-core.xml gdb-xml/hexagon-hvx.xml gdb-xml/hexagon-sys.xml +TARGET_LONG_BITS=32 diff --git a/target/Kconfig b/target/Kconfig index d0c7b59d9c71..37781146b9bb 100644 --- a/target/Kconfig +++ b/target/Kconfig @@ -16,6 +16,7 @@ source sh4/Kconfig source sparc/Kconfig source tricore/Kconfig source xtensa/Kconfig +source hexagon/Kconfig config TARGET_BIG_ENDIAN bool diff --git a/target/hexagon/Kconfig b/target/hexagon/Kconfig new file mode 100644 index 000000000000..7e556f350633 --- /dev/null +++ b/target/hexagon/Kconfig @@ -0,0 +1,2 @@ +config HEXAGON + bool diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index f5b92d33a156..0608d3265cd1 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -32,10 +32,6 @@ #include "mmvec/mmvec.h" #include "hw/registerfields.h" -#ifndef CONFIG_USER_ONLY -#error "Hexagon does not support system emulation" -#endif - #ifndef CONFIG_USER_ONLY #include "reg_fields.h" typedef struct CPUHexagonTLBContext CPUHexagonTLBContext; diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build index aa729a3683f1..d2b56b9e65e5 100644 --- a/target/hexagon/meson.build +++ b/target/hexagon/meson.build @@ -245,6 +245,7 @@ decodetree_trans_funcs_generated = custom_target( command: [python, files('gen_trans_funcs.py'), semantics_generated, '@OUTPUT@'], ) hexagon_ss.add(decodetree_trans_funcs_generated) +hexagon_softmmu_ss = ss.source_set() hexagon_ss.add(files( 'cpu.c', @@ -264,6 +265,13 @@ hexagon_ss.add(files( 'mmvec/system_ext_mmvec.c', )) +hexagon_softmmu_ss.add(files( + 'hex_mmu.c', + 'hex_interrupts.c', + 'hexswi.c', + 'machine.c', +)) + # # Step 4.5 # We use flex/bison based idef-parser to generate TCG code for a lot @@ -401,3 +409,4 @@ analyze_funcs_generated = custom_target( hexagon_ss.add(analyze_funcs_generated) target_arch += {'hexagon': hexagon_ss} +target_system_arch += {'hexagon': hexagon_softmmu_ss} From 7ca0105aea8f5b69a2c37aef227f592740989c4b Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sun, 28 Jul 2024 22:13:33 -0700 Subject: [PATCH 1141/1179] hw/hexagon: Define hexagon "virt" machine Signed-off-by: Brian Cain --- configs/devices/hexagon-softmmu/default.mak | 1 + configs/targets/hexagon-softmmu.mak | 1 + hw/hexagon/Kconfig | 8 + hw/hexagon/meson.build | 2 + hw/hexagon/virt.c | 393 ++++++++++++++++++++ include/hw/hexagon/virt.h | 41 ++ target/hexagon/cpu.c | 2 +- 7 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 hw/hexagon/virt.c create mode 100644 include/hw/hexagon/virt.h diff --git a/configs/devices/hexagon-softmmu/default.mak b/configs/devices/hexagon-softmmu/default.mak index 08e709aea72f..37b4f9f3237a 100644 --- a/configs/devices/hexagon-softmmu/default.mak +++ b/configs/devices/hexagon-softmmu/default.mak @@ -3,5 +3,6 @@ # Uncomment the following lines to disable these optional devices: # Boards are selected by default, uncomment to keep out of the build. +# CONFIG_HEX_VIRT=y # CONFIG_HEX_DSP=y # CONFIG_L2VIC=y diff --git a/configs/targets/hexagon-softmmu.mak b/configs/targets/hexagon-softmmu.mak index 8c208bf46884..9f8fca1dc162 100644 --- a/configs/targets/hexagon-softmmu.mak +++ b/configs/targets/hexagon-softmmu.mak @@ -4,3 +4,4 @@ TARGET_ARCH=hexagon TARGET_SUPPORTS_MTTCG=y TARGET_XML_FILES=gdb-xml/hexagon-core.xml gdb-xml/hexagon-hvx.xml gdb-xml/hexagon-sys.xml TARGET_LONG_BITS=32 +TARGET_NEED_FDT=y diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig index 3fc14756e6df..f3f011573105 100644 --- a/hw/hexagon/Kconfig +++ b/hw/hexagon/Kconfig @@ -4,3 +4,11 @@ config HEX_DSP depends on HEXAGON && TCG imply PTIMER select L2VIC # Vector PIC + +config HEX_VIRT + bool + default y + depends on HEX_DSP && FDT + select DEVICE_TREE + select VIRTIO_MMIO + select PL011 diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build index 2ef3dbcd3492..649ad6dc02b3 100644 --- a/hw/hexagon/meson.build +++ b/hw/hexagon/meson.build @@ -3,3 +3,5 @@ hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hexagon_dsp.c',)) hw_arch += {'hexagon': hexagon_ss} +hexagon_ss.add(when: 'CONFIG_HEX_VIRT', if_true: files('virt.c',)) + diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c new file mode 100644 index 000000000000..43fdce1fce00 --- /dev/null +++ b/hw/hexagon/virt.c @@ -0,0 +1,393 @@ +/* + * Hexagon virt emulation + * + * Copyright (c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "exec/address-spaces.h" +#include "hw/char/pl011.h" +#include "hw/core/sysbus-fdt.h" +#include "hw/hexagon/hexagon.h" +#include "hw/hexagon/virt.h" +#include "hw/loader.h" +#include "hw/qdev-properties.h" +#include "hw/register.h" +#include "qemu/error-report.h" +#include "qemu/guest-random.h" +#include "qemu/units.h" +#include "elf.h" +#include "machine_cfg_v68n_1024.h.inc" +#include "system/device_tree.h" +#include "system/reset.h" +#include "system/system.h" +#include + +static const int VIRTIO_DEV_COUNT = 2; + +static const MemMapEntry base_memmap[] = { + [VIRT_UART0] = { 0x10000000, 0x00000200 }, + [VIRT_MMIO] = { 0x11000000, 0x1000000, }, + [VIRT_GPT] = { 0xab000000, 0x00001000 }, + [VIRT_FDT] = { 0x99900000, 0x00000200 }, +}; + +static const int irqmap[] = { + [VIRT_MMIO] = 18, /* ...to 18 + VIRTIO_DEV_COUNT - 1 */ + [VIRT_GPT] = 12, + [VIRT_UART0] = 15, + [VIRT_QTMR0] = 2, + [VIRT_QTMR1] = 4, +}; + + +static void create_fdt(HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + void *fdt = create_device_tree(&vms->fdt_size); + + if (!fdt) { + error_report("create_device_tree() failed"); + exit(1); + } + + ms->fdt = fdt; + + qemu_fdt_setprop_string(fdt, "/", "compatible", "linux,hexagon-virt"); + qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2); + qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x1); + qemu_fdt_setprop_string(fdt, "/", "model", "linux,hexagon-virt"); + + qemu_fdt_setprop_string(fdt, "/", "model", "hexagon-virt,qemu"); + qemu_fdt_setprop_string(fdt, "/", "compatible", "qcom,sm8150"); + + qemu_fdt_add_subnode(fdt, "/soc"); + qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2); + qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x1); + qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0); + + qemu_fdt_add_subnode(fdt, "/chosen"); + + uint8_t rng_seed[32]; + qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed)); + qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed)); +} + +static void fdt_add_hvx(HexagonVirtMachineState *vms, + const hexagon_machine_config *m_cfg, Error **errp) +{ + const MachineState *ms = MACHINE(vms); + uint32_t vtcm_size_bytes = m_cfg->cfgtable.vtcm_size_kb * 1024; + if (vtcm_size_bytes > 0) { + memory_region_init_ram(&vms->vtcm, NULL, "vtcm.ram", vtcm_size_bytes, + errp); + memory_region_add_subregion(vms->sys, m_cfg->cfgtable.vtcm_base << 16, + &vms->vtcm); + + qemu_fdt_add_subnode(ms->fdt, "/soc/vtcm"); + qemu_fdt_setprop_string(ms->fdt, "/soc/vtcm", "compatible", + "qcom,hexagon_vtcm"); + + assert(sizeof(m_cfg->cfgtable.vtcm_base) == sizeof(uint32_t)); + qemu_fdt_setprop_cells(ms->fdt, "/soc/vtcm", "reg", 0, + m_cfg->cfgtable.vtcm_base << 16, + vtcm_size_bytes); + } + + if (m_cfg->cfgtable.ext_contexts > 0) { + qemu_fdt_add_subnode(ms->fdt, "/soc/hvx"); + qemu_fdt_setprop_string(ms->fdt, "/soc/hvx", "compatible", + "qcom,hexagon-hvx"); + qemu_fdt_setprop_cells(ms->fdt, "/soc/hvx", "qcom,hvx-max-ctxts", + m_cfg->cfgtable.ext_contexts); + qemu_fdt_setprop_cells(ms->fdt, "/soc/hvx", "qcom,hvx-vlength", + m_cfg->cfgtable.hvx_vec_log_length); + } +} + +static int32_t irq_hvm_ic_phandle = -1; +static void fdt_add_hvm_pic_node(HexagonVirtMachineState *vms, + const hexagon_machine_config *m_cfg) +{ + MachineState *ms = MACHINE(vms); + irq_hvm_ic_phandle = qemu_fdt_alloc_phandle(ms->fdt); + + qemu_fdt_setprop_cell(ms->fdt, "/soc", "interrupt-parent", + irq_hvm_ic_phandle); + + qemu_fdt_add_subnode(ms->fdt, "/soc/interrupt-controller"); + qemu_fdt_setprop_cell(ms->fdt, "/soc/interrupt-controller", + "#address-cells", 2); + qemu_fdt_setprop_cell(ms->fdt, "/soc/interrupt-controller", + "#interrupt-cells", 2); + qemu_fdt_setprop_string(ms->fdt, "/soc/interrupt-controller", "compatible", + "qcom,h2-pic,hvm-pic"); + qemu_fdt_setprop(ms->fdt, "/soc/interrupt-controller", + "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, "/soc/interrupt-controller", "phandle", + irq_hvm_ic_phandle); + + sysbus_mmio_map(SYS_BUS_DEVICE(vms->l2vic), 1, + m_cfg->cfgtable.fastl2vic_base << 16); +} + + +static void fdt_add_gpt_node(HexagonVirtMachineState *vms) +{ + g_autofree char *name = NULL; + MachineState *ms = MACHINE(vms); + + name = g_strdup_printf("/soc/gpt@%" PRIx64, + (int64_t)base_memmap[VIRT_GPT].base); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", + "qcom,h2-timer,hvm-timer"); + qemu_fdt_setprop_cells(ms->fdt, name, "interrupts", irqmap[VIRT_GPT], 0); + qemu_fdt_setprop_cells(ms->fdt, name, "reg", 0x0, + base_memmap[VIRT_GPT].base, + base_memmap[VIRT_GPT].size); +} + +static int32_t clock_phandle = -1; +static void fdt_add_clocks(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + clock_phandle = qemu_fdt_alloc_phandle(ms->fdt); + qemu_fdt_add_subnode(ms->fdt, "/apb-pclk"); + qemu_fdt_setprop_string(ms->fdt, "/apb-pclk", "compatible", "fixed-clock"); + qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "#clock-cells", 0x0); + qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "clock-frequency", 24000000); + qemu_fdt_setprop_string(ms->fdt, "/apb-pclk", "clock-output-names", + "clk24mhz"); + qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "phandle", clock_phandle); +} + +static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart) +{ + char *nodename; + hwaddr base = base_memmap[uart].base; + hwaddr size = base_memmap[uart].size; + assert(uart == 0); + int irq = irqmap[VIRT_UART0 + uart]; + const char compat[] = "arm,pl011\0arm,primecell"; + const char clocknames[] = "uartclk\0apb_pclk"; + MachineState *ms = MACHINE(vms); + + pl011_create(base, qdev_get_gpio_in(vms->l2vic, irq), serial_hd(0)); + + nodename = g_strdup_printf("/pl011@%" PRIx64, base); + qemu_fdt_add_subnode(ms->fdt, nodename); + + /* Note that we can't use setprop_string because of the embedded NUL */ + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", 32 + irq, 0); + qemu_fdt_setprop_cells(ms->fdt, nodename, "clocks", clock_phandle, + clock_phandle); + qemu_fdt_setprop(ms->fdt, nodename, "clock-names", clocknames, + sizeof(clocknames)); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + irq_hvm_ic_phandle); + + qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", nodename); + qemu_fdt_add_subnode(ms->fdt, "/aliases"); + qemu_fdt_setprop_string(ms->fdt, "/aliases", "serial0", nodename); + + g_free(nodename); +} + +static void fdt_add_cpu_nodes(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + qemu_fdt_add_subnode(ms->fdt, "/cpus"); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#address-cells", 0x1); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#size-cells", 0x0); + + /* cpu nodes */ + for (int num = ms->smp.cpus - 1; num >= 0; num--) { + char *nodename = g_strdup_printf("/cpus/cpu@%d", num); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "cpu"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "reg", num); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", + qemu_fdt_alloc_phandle(ms->fdt)); + g_free(nodename); + } +} + + +static void fdt_add_virtio_devices(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + /* VirtIO MMIO devices */ + for (int i = 0; i < VIRTIO_DEV_COUNT; i++) { + char *nodename; + int irq = irqmap[VIRT_MMIO] + i; + size_t size = base_memmap[VIRT_MMIO].size; + hwaddr base = base_memmap[VIRT_MMIO].base + i * size; + + nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, base); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "virtio,mmio"); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 1, + size); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", irq, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + irq_hvm_ic_phandle); + + sysbus_create_simple( + "virtio-mmio", base, + qdev_get_gpio_in(vms->l2vic, irqmap[VIRT_MMIO] + i)); + + g_free(nodename); + } +} + +static void virt_instance_init(Object *obj) +{ + HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(obj); + + create_fdt(vms); +} + +void hexagon_load_fdt(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + hwaddr fdt_addr = base_memmap[VIRT_FDT].base; + uint32_t fdtsize = vms->fdt_size; + + /* copy in the device tree */ + rom_add_blob_fixed_as("fdt", ms->fdt, fdtsize, fdt_addr, + &address_space_memory); + qemu_register_reset_nosnapshotload( + qemu_fdt_randomize_seeds, + rom_ptr_for_as(&address_space_memory, fdt_addr, fdtsize)); +} + +static uint64_t load_kernel(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + uint64_t entry = 0; + if (load_elf_ram_sym(ms->kernel_filename, NULL, NULL, NULL, &entry, NULL, + NULL, NULL, 0, EM_HEXAGON, 0, 0, &address_space_memory, + false, NULL) > 0) { + return entry; + } + error_report("error loading '%s'", ms->kernel_filename); + exit(1); +} + +static void do_cpu_reset(void *opaque) +{ + HexagonCPU *cpu = opaque; + CPUState *cs = CPU(cpu); + cpu_reset(cs); +} + +static void virt_init(MachineState *ms) +{ + HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(ms); + Error **errp = NULL; + const hexagon_machine_config *m_cfg = &v68n_1024; + + qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", ms->kernel_cmdline); + + vms->sys = get_system_memory(); + + memory_region_init_ram(&vms->ram, NULL, "ddr.ram", ms->ram_size, errp); + memory_region_add_subregion(vms->sys, 0x0, &vms->ram); + + if (m_cfg->l2tcm_size) { + memory_region_init_ram(&vms->tcm, NULL, "tcm.ram", m_cfg->l2tcm_size, + errp); + memory_region_add_subregion(vms->sys, m_cfg->cfgtable.l2tcm_base << 16, + &vms->tcm); + } + + memory_region_init_rom(&vms->cfgtable, NULL, "config_table.rom", + sizeof(m_cfg->cfgtable), errp); + memory_region_add_subregion(vms->sys, m_cfg->cfgbase, &vms->cfgtable); + fdt_add_hvx(vms, m_cfg, errp); + const char *cpu_model = ms->cpu_type; + + if (!cpu_model) { + cpu_model = HEXAGON_CPU_TYPE_NAME("v73"); + } + + HexagonCPU *cpu_0 = NULL; + for (int i = 0; i < ms->smp.cpus; i++) { + HexagonCPU *cpu = HEXAGON_CPU(object_new(ms->cpu_type)); + qemu_register_reset(do_cpu_reset, cpu); + + if (i == 0) { + cpu_0 = cpu; + if (ms->kernel_filename) { + uint64_t entry = load_kernel(vms); + + qdev_prop_set_uint32(DEVICE(cpu_0), "exec-start-addr", entry); + } + } + qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0)); + qdev_prop_set_uint32(DEVICE(cpu), "hvx-contexts", + m_cfg->cfgtable.ext_contexts); + qdev_prop_set_uint32(DEVICE(cpu), "config-table-addr", m_cfg->cfgbase); + qdev_prop_set_uint32(DEVICE(cpu), "l2vic-base-addr", m_cfg->l2vic_base); + qdev_prop_set_uint32(DEVICE(cpu), "jtlb-entries", + m_cfg->cfgtable.jtlb_size_entries); + + if (!qdev_realize_and_unref(DEVICE(cpu), NULL, errp)) { + return; + } + } + vms->l2vic = sysbus_create_varargs( + "l2vic", m_cfg->l2vic_base, qdev_get_gpio_in(DEVICE(cpu_0), 0), + qdev_get_gpio_in(DEVICE(cpu_0), 1), qdev_get_gpio_in(DEVICE(cpu_0), 2), + qdev_get_gpio_in(DEVICE(cpu_0), 3), qdev_get_gpio_in(DEVICE(cpu_0), 4), + qdev_get_gpio_in(DEVICE(cpu_0), 5), qdev_get_gpio_in(DEVICE(cpu_0), 6), + qdev_get_gpio_in(DEVICE(cpu_0), 7), NULL); + + fdt_add_hvm_pic_node(vms, m_cfg); + fdt_add_virtio_devices(vms); + fdt_add_cpu_nodes(vms); + fdt_add_clocks(vms); + fdt_add_uart(vms, VIRT_UART0); + fdt_add_gpt_node(vms); + + rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, + sizeof(m_cfg->cfgtable), m_cfg->cfgbase, + &address_space_memory); + + + hexagon_load_fdt(vms); +} + + +static void virt_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->init = virt_init; + mc->default_cpu_type = HEXAGON_CPU_TYPE_NAME("v73"); + mc->default_ram_size = 4 * GiB; + mc->max_cpus = 8; + mc->default_cpus = 8; + mc->is_default = false; + mc->default_kernel_irqchip_split = false; + mc->block_default_type = IF_VIRTIO; + mc->default_boot_order = NULL; + mc->no_cdrom = 1; + mc->numa_mem_supported = false; + mc->default_nic = "virtio-mmio-bus"; +} + + +static const TypeInfo virt_machine_types[] = { { + .name = TYPE_HEXAGON_VIRT_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(HexagonVirtMachineState), + .class_init = virt_class_init, + .instance_init = virt_instance_init, +} }; + +DEFINE_TYPES(virt_machine_types) diff --git a/include/hw/hexagon/virt.h b/include/hw/hexagon/virt.h new file mode 100644 index 000000000000..0c165a786d30 --- /dev/null +++ b/include/hw/hexagon/virt.h @@ -0,0 +1,41 @@ +/* + * Definitions for hexagon virt board. + * + * Copyright (c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_HEXAGONVIRT_H +#define HW_HEXAGONVIRT_H + +#include "hw/boards.h" +#include "target/hexagon/cpu.h" + +struct HexagonVirtMachineState { + /*< private >*/ + MachineState parent_obj; + + int fdt_size; + MemoryRegion *sys; + MemoryRegion cfgtable; + MemoryRegion ram; + MemoryRegion tcm; + MemoryRegion vtcm; + DeviceState *l2vic; +}; + +void hexagon_load_fdt(const struct HexagonVirtMachineState *vms); + +enum { + VIRT_UART0, + VIRT_QTMR0, + VIRT_QTMR1, + VIRT_GPT, + VIRT_MMIO, + VIRT_FDT, +}; + +#define TYPE_HEXAGON_VIRT_MACHINE MACHINE_TYPE_NAME("virt") +OBJECT_DECLARE_SIMPLE_TYPE(HexagonVirtMachineState, HEXAGON_VIRT_MACHINE) + +#endif /* HW_HEXAGONVIRT_H */ diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 87eabad3dcd5..ea59e1966d6d 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -1,5 +1,5 @@ /* - * Copyright(c) 2019-2023 Qualcomm Innovation Center, Inc. All Rights Reserved. + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. * * 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 From b6781d6976fc767834fab06b6c954df709796c9d Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 25 Oct 2024 20:49:14 -0700 Subject: [PATCH 1142/1179] tests/functional: Add a hexagon minivm test Signed-off-by: Brian Cain --- MAINTAINERS | 1 + tests/functional/meson.build | 8 +++++ tests/functional/test_hexagon_minivm.py | 42 +++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100755 tests/functional/test_hexagon_minivm.py diff --git a/MAINTAINERS b/MAINTAINERS index 67c3c779e81c..b1aee024fcb2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -240,6 +240,7 @@ F: gdb-xml/hexagon*.xml F: docs/system/target-hexagon.rst F: docs/devel/hexagon-sys.rst F: docs/devel/hexagon-l2vic.rst +F: tests/functional/test_hexagon_minivm.py T: git https://github.com/quic/qemu.git hex-next Hexagon idef-parser diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 96d282892798..1b0409179890 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -148,6 +148,14 @@ tests_i386_system_quick = [ 'migration', ] +test_timeouts += { + 'hexagon_minivm': 180, +} + +tests_hexagon_system_quick = [ + 'hexagon_minivm', +] + tests_i386_system_thorough = [ 'i386_tuxrun', ] diff --git a/tests/functional/test_hexagon_minivm.py b/tests/functional/test_hexagon_minivm.py new file mode 100755 index 000000000000..2ba92bcce383 --- /dev/null +++ b/tests/functional/test_hexagon_minivm.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# +# Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +from glob import glob +from qemu_test import QemuSystemTest, Asset +from qemu_test import wait_for_console_pattern + +class MiniVMTest(QemuSystemTest): + + timeout = 180 + GUEST_ENTRY = 0xc0000000 + + REPO = 'https://artifacts.codelinaro.org/artifactory' + ASSET_TARBALL = \ + Asset(f'{REPO}/codelinaro-toolchain-for-hexagon/' + '19.1.5/hexagon_minivm_2024_Dec_15.tar.gz', + 'd7920b5ff14bed5a10b23ada7d4eb927ede08635281f25067e0d5711feee2c2a') + + def test_minivm(self): + self.set_machine('virt') + self.archive_extract(self.ASSET_TARBALL) + rootfs_path = f'{self.workdir}/hexagon-unknown-linux-musl-rootfs' + kernel_path = f'{rootfs_path}/boot/minivm' + + assert(os.path.exists(kernel_path)) + for test_bin_path in glob(f'{rootfs_path}/boot/test_*'): + print(f'# Testing "{os.path.basename(test_bin_path)}"') + + vm = self.get_vm() + vm.add_args('-kernel', kernel_path, + '-device', + f'loader,addr={hex(self.GUEST_ENTRY)},file={test_bin_path}') + vm.launch() + vm.wait() + self.assertEqual(vm.exitcode(), 0) + +if __name__ == '__main__': + QemuSystemTest.main() From accfc12bce69c9028262baab6f3b80abcd0366f1 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 9 Sep 2024 09:42:54 -0700 Subject: [PATCH 1143/1179] target/hexagon: s/pkt_has_store/pkt_has_scalar_store To remove any confusion with HVX or other potential store instructions, we'll qualify this context var with "scalar". Signed-off-by: Brian Cain --- target/hexagon/decode.c | 4 ++-- target/hexagon/gen_helper_funcs.py | 2 +- target/hexagon/genptr.c | 3 ++- target/hexagon/idef-parser/README.rst | 2 +- target/hexagon/idef-parser/parser-helpers.c | 4 ++-- target/hexagon/insn.h | 4 ++-- target/hexagon/macros.h | 8 ++++---- target/hexagon/op_helper.c | 4 ++-- target/hexagon/translate.c | 9 +++++---- 9 files changed, 21 insertions(+), 19 deletions(-) diff --git a/target/hexagon/decode.c b/target/hexagon/decode.c index 5d0beeeaf2f1..41bf03c9b513 100644 --- a/target/hexagon/decode.c +++ b/target/hexagon/decode.c @@ -238,9 +238,9 @@ static void decode_set_insn_attr_fields(Packet *pkt) if (GET_ATTRIB(opcode, A_SCALAR_STORE) && !GET_ATTRIB(opcode, A_MEMSIZE_0B)) { if (pkt->insn[i].slot == 0) { - pkt->pkt_has_store_s0 = true; + pkt->pkt_has_scalar_store_s0 = true; } else { - pkt->pkt_has_store_s1 = true; + pkt->pkt_has_scalar_store_s1 = true; } } } diff --git a/target/hexagon/gen_helper_funcs.py b/target/hexagon/gen_helper_funcs.py index dd8ab6059855..32e3bac74625 100755 --- a/target/hexagon/gen_helper_funcs.py +++ b/target/hexagon/gen_helper_funcs.py @@ -69,7 +69,7 @@ def gen_helper_function(f, tag, tagregs, tagimms): if hex_common.need_slot(tag): if "A_LOAD" in hex_common.attribdict[tag]: f.write(hex_common.code_fmt(f"""\ - bool pkt_has_store_s1 = slotval & 0x1; + bool pkt_has_scalar_store_s1 = slotval & 0x1; """)) f.write(hex_common.code_fmt(f"""\ uint32_t slot = slotval >> 1; diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c index afc7e5f3a5ab..f38968271b17 100644 --- a/target/hexagon/genptr.c +++ b/target/hexagon/genptr.c @@ -558,7 +558,8 @@ static inline void gen_store_conditional8(DisasContext *ctx, #ifndef CONFIG_HEXAGON_IDEF_PARSER static TCGv gen_slotval(DisasContext *ctx) { - int slotval = (ctx->pkt->pkt_has_store_s1 & 1) | (ctx->insn->slot << 1); + int slotval = + (ctx->pkt->pkt_has_scalar_store_s1 & 1) | (ctx->insn->slot << 1); return tcg_constant_tl(slotval); } #endif diff --git a/target/hexagon/idef-parser/README.rst b/target/hexagon/idef-parser/README.rst index 7199177ee33e..235e3debee3c 100644 --- a/target/hexagon/idef-parser/README.rst +++ b/target/hexagon/idef-parser/README.rst @@ -637,7 +637,7 @@ tinycode for the Hexagon ``add`` instruction :: ---- 00021094 - mov_i32 pkt_has_store_s1,$0x0 + mov_i32 pkt_has_scalar_store_s1,$0x0 add_i32 tmp0,r2,r2 mov_i32 loc2,tmp0 mov_i32 new_r1,loc2 diff --git a/target/hexagon/idef-parser/parser-helpers.c b/target/hexagon/idef-parser/parser-helpers.c index a7dcd85fe43d..3316c230f8a5 100644 --- a/target/hexagon/idef-parser/parser-helpers.c +++ b/target/hexagon/idef-parser/parser-helpers.c @@ -1725,7 +1725,7 @@ void gen_cancel(Context *c, YYLTYPE *locp) void gen_load_cancel(Context *c, YYLTYPE *locp) { - OUT(c, locp, "if (insn->slot == 0 && pkt->pkt_has_store_s1) {\n"); + OUT(c, locp, "if (insn->slot == 0 && pkt->pkt_has_scalar_store_s1) {\n"); OUT(c, locp, "ctx->s1_store_processed = false;\n"); OUT(c, locp, "process_store(ctx, 1);\n"); OUT(c, locp, "}\n"); @@ -1750,7 +1750,7 @@ void gen_load(Context *c, YYLTYPE *locp, HexValue *width, /* Lookup the effective address EA */ find_variable(c, locp, ea, ea); - OUT(c, locp, "if (insn->slot == 0 && pkt->pkt_has_store_s1) {\n"); + OUT(c, locp, "if (insn->slot == 0 && pkt->pkt_has_scalar_store_s1) {\n"); OUT(c, locp, "probe_noshuf_load(", ea, ", ", width, ", ctx->mem_idx);\n"); OUT(c, locp, "process_store(ctx, 1);\n"); OUT(c, locp, "}\n"); diff --git a/target/hexagon/insn.h b/target/hexagon/insn.h index 24dcf7fe9f38..5d59430da9e1 100644 --- a/target/hexagon/insn.h +++ b/target/hexagon/insn.h @@ -66,8 +66,8 @@ struct Packet { bool pkt_has_dczeroa; - bool pkt_has_store_s0; - bool pkt_has_store_s1; + bool pkt_has_scalar_store_s0; + bool pkt_has_scalar_store_s1; bool pkt_has_hvx; Insn *vhist_insn; diff --git a/target/hexagon/macros.h b/target/hexagon/macros.h index afbbe8e26524..06c1dd2f407e 100644 --- a/target/hexagon/macros.h +++ b/target/hexagon/macros.h @@ -82,7 +82,7 @@ */ #define CHECK_NOSHUF(VA, SIZE) \ do { \ - if (insn->slot == 0 && ctx->pkt->pkt_has_store_s1) { \ + if (insn->slot == 0 && ctx->pkt->pkt_has_scalar_store_s1) { \ probe_noshuf_load(VA, SIZE, ctx->mem_idx); \ process_store(ctx, 1); \ } \ @@ -93,11 +93,11 @@ TCGLabel *noshuf_label = gen_new_label(); \ tcg_gen_brcondi_tl(TCG_COND_EQ, PRED, 0, noshuf_label); \ GET_EA; \ - if (insn->slot == 0 && ctx->pkt->pkt_has_store_s1) { \ + if (insn->slot == 0 && ctx->pkt->pkt_has_scalar_store_s1) { \ probe_noshuf_load(EA, SIZE, ctx->mem_idx); \ } \ gen_set_label(noshuf_label); \ - if (insn->slot == 0 && ctx->pkt->pkt_has_store_s1) { \ + if (insn->slot == 0 && ctx->pkt->pkt_has_scalar_store_s1) { \ process_store(ctx, 1); \ } \ } while (0) @@ -524,7 +524,7 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #define fLOAD(NUM, SIZE, SIGN, EA, DST) \ do { \ - check_noshuf(env, pkt_has_store_s1, slot, EA, SIZE, GETPC()); \ + check_noshuf(env, pkt_has_scalar_store_s1, slot, EA, SIZE, GETPC()); \ DST = (size##SIZE##SIGN##_t)MEM_LOAD##SIZE(env, EA, GETPC()); \ } while (0) #endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 28b555e87375..dd26a0e3e0a1 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -490,11 +490,11 @@ void HELPER(probe_pkt_scalar_hvx_stores)(CPUHexagonState *env, int mask) * If the load is in slot 0 and there is a store in slot1 (that * wasn't cancelled), we have to do the store first. */ -static void check_noshuf(CPUHexagonState *env, bool pkt_has_store_s1, +static void check_noshuf(CPUHexagonState *env, bool pkt_has_scalar_store_s1, uint32_t slot, target_ulong vaddr, int size, uintptr_t ra) { - if (slot == 0 && pkt_has_store_s1 && + if (slot == 0 && pkt_has_scalar_store_s1 && ((env->slot_cancelled & (1 << 1)) == 0)) { probe_read(env, vaddr, size, MMU_USER_IDX, ra); commit_store(env, 1, ra); diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index f4133a10490e..35765d48ba11 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -932,11 +932,11 @@ static void process_store_log(DisasContext *ctx) * the memory accesses overlap. */ Packet *pkt = ctx->pkt; - if (pkt->pkt_has_store_s1) { + if (pkt->pkt_has_scalar_store_s1) { g_assert(!pkt->pkt_has_dczeroa); process_store(ctx, 1); } - if (pkt->pkt_has_store_s0) { + if (pkt->pkt_has_scalar_store_s0) { g_assert(!pkt->pkt_has_dczeroa); process_store(ctx, 0); } @@ -1063,8 +1063,9 @@ static void gen_commit_packet(DisasContext *ctx) * involved in committing the packet. */ Packet *pkt = ctx->pkt; - bool has_store_s0 = pkt->pkt_has_store_s0; - bool has_store_s1 = (pkt->pkt_has_store_s1 && !ctx->s1_store_processed); + bool has_store_s0 = pkt->pkt_has_scalar_store_s0; + bool has_store_s1 = + (pkt->pkt_has_scalar_store_s1 && !ctx->s1_store_processed); bool has_hvx_store = pkt_has_hvx_store(pkt); if (pkt->pkt_has_dczeroa) { /* From dcdbedc88e3d74a0ba4ac444f39f0b5370881613 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 2 Jan 2025 19:47:54 -0800 Subject: [PATCH 1144/1179] target/hexagon: Add a QTimer address prop This property will be used by a future commit. Signed-off-by: Brian Cain --- target/hexagon/cpu.c | 2 ++ target/hexagon/cpu.h | 1 + 2 files changed, 3 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index ea59e1966d6d..42a1af3009ab 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -66,6 +66,8 @@ static const Property hexagon_cpu_properties[] = { DEFINE_PROP_UINT32("jtlb-entries", HexagonCPU, num_tlbs, MAX_TLB_ENTRIES), DEFINE_PROP_UINT32("l2vic-base-addr", HexagonCPU, l2vic_base_addr, 0xffffffffULL), + DEFINE_PROP_UINT32("qtimer-base-addr", HexagonCPU, qtimer_base_addr, + 0xffffffffULL), DEFINE_PROP_UINT32("hvx-contexts", HexagonCPU, hvx_contexts, 0), DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffffULL), DEFINE_PROP_UINT64("config-table-addr", HexagonCPU, config_table_addr, diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 0608d3265cd1..50265da40dc9 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -193,6 +193,7 @@ struct ArchCPU { #ifndef CONFIG_USER_ONLY uint32_t num_tlbs; uint32_t l2vic_base_addr; + uint32_t qtimer_base_addr; uint32_t hvx_contexts; uint32_t boot_addr; uint64_t config_table_addr; From a83401d3ddc935be6eb090ea46b2d94d67361a1c Mon Sep 17 00:00:00 2001 From: Sid Manning Date: Tue, 7 Nov 2023 17:01:28 -0800 Subject: [PATCH 1145/1179] hw/timer: Add QTimer device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: QTimer was implemented before ARM SSE Timer was upstreamed, there may be opportunity to use that device instead. Co-authored-by: Damien Hedde Co-authored-by: Tobias Röhmel Signed-off-by: Brian Cain --- MAINTAINERS | 2 + hw/hexagon/hexagon_dsp.c | 1 - hw/hexagon/virt.c | 22 ++ hw/timer/meson.build | 2 + hw/timer/qct-qtimer.c | 519 ++++++++++++++++++++++++++++++++++ include/hw/timer/qct-qtimer.h | 85 ++++++ 6 files changed, 630 insertions(+), 1 deletion(-) create mode 100644 hw/timer/qct-qtimer.c create mode 100644 include/hw/timer/qct-qtimer.h diff --git a/MAINTAINERS b/MAINTAINERS index b1aee024fcb2..dbcad875fa61 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -227,7 +227,9 @@ S: Supported F: target/hexagon/ F: hw/intc/l2vic.[ch] F: hw/hexagon/ +F: hw/timer/qct-qtimer.c F: include/hw/hexagon/ +F: include/hw/timer/qct-qtimer.h X: target/hexagon/idef-parser/ X: target/hexagon/gen_idef_parser_funcs.py F: linux-user/hexagon/ diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c index 34bbe98149cf..198f98399336 100644 --- a/hw/hexagon/hexagon_dsp.c +++ b/hw/hexagon/hexagon_dsp.c @@ -3,7 +3,6 @@ * subsystem with few peripherals, like the Compute DSP. * * Copyright (c) 2020-2024 Qualcomm Innovation Center, Inc. All Rights Reserved. - * * SPDX-License-Identifier: GPL-2.0-or-later */ diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c index 43fdce1fce00..b991bc94a838 100644 --- a/hw/hexagon/virt.c +++ b/hw/hexagon/virt.c @@ -14,6 +14,7 @@ #include "hw/loader.h" #include "hw/qdev-properties.h" #include "hw/register.h" +#include "hw/timer/qct-qtimer.h" #include "qemu/error-report.h" #include "qemu/guest-random.h" #include "qemu/units.h" @@ -244,6 +245,26 @@ static void fdt_add_virtio_devices(const HexagonVirtMachineState *vms) } } +static void create_qtimer(HexagonVirtMachineState *vms, + const hexagon_machine_config *m_cfg) +{ + Error **errp = NULL; + QCTQtimerState *qtimer = QCT_QTIMER(qdev_new(TYPE_QCT_QTIMER)); + + object_property_set_uint(OBJECT(qtimer), "nr_frames", 2, errp); + object_property_set_uint(OBJECT(qtimer), "nr_views", 1, errp); + object_property_set_uint(OBJECT(qtimer), "cnttid", 0x111, errp); + sysbus_realize_and_unref(SYS_BUS_DEVICE(qtimer), errp); + + + sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 0, m_cfg->qtmr_rg1); + sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 1, m_cfg->qtmr_rg0); + sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), 0, + qdev_get_gpio_in(vms->l2vic, irqmap[VIRT_QTMR0])); + sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), 1, + qdev_get_gpio_in(vms->l2vic, irqmap[VIRT_QTMR1])); +} + static void virt_instance_init(Object *obj) { HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(obj); @@ -353,6 +374,7 @@ static void virt_init(MachineState *ms) fdt_add_clocks(vms); fdt_add_uart(vms, VIRT_UART0); fdt_add_gpt_node(vms); + create_qtimer(vms, m_cfg); rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, sizeof(m_cfg->cfgtable), m_cfg->cfgbase, diff --git a/hw/timer/meson.build b/hw/timer/meson.build index f5f9eed2d0a9..6c30bf602226 100644 --- a/hw/timer/meson.build +++ b/hw/timer/meson.build @@ -34,3 +34,5 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c')) system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c')) specific_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c')) + +specific_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('qct-qtimer.c')) diff --git a/hw/timer/qct-qtimer.c b/hw/timer/qct-qtimer.c new file mode 100644 index 000000000000..413f7249eef0 --- /dev/null +++ b/hw/timer/qct-qtimer.c @@ -0,0 +1,519 @@ +/* + * Qualcomm QCT QTimer + * + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/timer/qct-qtimer.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/timer.h" + +/* Common timer implementation. */ + +#define QTIMER_MEM_SIZE_BYTES 0x1000 +#define QTIMER_MEM_REGION_SIZE_BYTES 0x1000 +#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL +#define QTMR_TIMER_INDEX_MASK (0xf000) +#define HIGH_32(val) (0x0ffffffffULL & (val >> 32)) +#define LOW_32(val) (0x0ffffffffULL & val) + +/* + * QTimer version reg: + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Major | Minor | Step | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static unsigned int TIMER_VERSION = 0x20020000; + +/* + * qct_qtimer_read/write: + * if offset < 0x1000 read restricted registers: + * QCT_QTIMER_AC_CNTFREQ/CNTSR/CNTTID/CNTACR/CNTOFF_(LO/HI)/QCT_QTIMER_VERSION + */ +static uint64_t qct_qtimer_read(void *opaque, hwaddr offset, unsigned size) +{ + QCTQtimerState *s = (QCTQtimerState *)opaque; + uint32_t frame = 0; + + switch (offset) { + case QCT_QTIMER_AC_CNTFRQ: + return s->freq; + case QCT_QTIMER_AC_CNTSR: + return s->secure; + case QCT_QTIMER_AC_CNTTID: + return s->cnttid; + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END: + frame = (offset - 0x40) / 0x4; + if (frame >= s->nr_frames) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: QCT_QTIMER_AC_CNT: Bad offset %x\n", __func__, + (int)offset); + return 0x0; + } + return s->timer[frame].cnt_ctrl; + case QCT_QTIMER_VERSION: + return TIMER_VERSION; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: QCT_QTIMER_AC_CNT: Bad offset %x\n", + __func__, (int)offset); + return 0x0; + } + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%x\n", __func__, + (int)offset); + return 0; +} + +static void qct_qtimer_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + QCTQtimerState *s = (QCTQtimerState *)opaque; + uint32_t frame = 0; + + if (offset < 0x1000) { + switch (offset) { + case QCT_QTIMER_AC_CNTFRQ: + s->freq = value; + return; + case QCT_QTIMER_AC_CNTSR: + if (value > 0xFF) + qemu_log_mask(LOG_GUEST_ERROR, + "%s: QCT_QTIMER_AC_CNTSR: Bad value %x\n", + __func__, (int)value); + else + s->secure = value; + return; + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END: + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 0x4; + if (frame >= s->nr_frames) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: QCT_QTIMER_AC_CNT: Bad offset %x\n", + __func__, (int)offset); + return; + } + s->timer[frame].cnt_ctrl = value; + return; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: QCT_QTIMER_AC_CNT: Bad offset %x\n", __func__, + (int)offset); + return; + } + } else + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %x\n", __func__, + (int)offset); +} + +static const MemoryRegionOps qct_qtimer_ops = { + .read = qct_qtimer_read, + .write = qct_qtimer_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_qct_qtimer = { + .name = "qct-qtimer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]){ VMSTATE_END_OF_LIST() } +}; + +static void qct_qtimer_init(Object *obj) +{ + QCTQtimerState *s = QCT_QTIMER(obj); + + object_property_add_uint32_ptr(obj, "secure", &s->secure, + OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "frame_id", &s->frame_id, + OBJ_PROP_FLAG_READ); +} + +static void hex_timer_update(QCTHextimerState *s) +{ + /* Update interrupts. */ + int level = s->int_level && (s->control & QCT_QTIMER_CNTP_CTL_ENABLE); + qemu_set_irq(s->irq, level); +} + +static MemTxResult hex_timer_read(void *opaque, hwaddr offset, uint64_t *data, + unsigned size, MemTxAttrs attrs) +{ + QCTQtimerState *qct_s = (QCTQtimerState *)opaque; + uint32_t slot_nr = (offset & 0xF000) >> 12; + uint32_t reg_offset = offset & 0xFFF; + uint32_t view = slot_nr % qct_s->nr_views; + uint32_t frame = slot_nr / qct_s->nr_views; + + if (frame >= qct_s->nr_frames) { + *data = 0; + return MEMTX_ACCESS_ERROR; + } + QCTHextimerState *s = &qct_s->timer[frame]; + + + /* + * This is the case where we have 2 views, but the second one is not + * implemented. + */ + if (view && !(qct_s->cnttid & (0x4 << (frame * 4)))) { + *data = 0; + return MEMTX_OK; + } + + switch (reg_offset) { + case (QCT_QTIMER_CNT_FREQ): /* Ticks/Second */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RFRQ)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !((s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN) || + (s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0VCTEN))) { + return MEMTX_ACCESS_ERROR; + } + + *data = s->freq; + return MEMTX_OK; + case (QCT_QTIMER_CNTP_CVAL_LO): /* TimerLoad */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + *data = LOW_32((s->cntval)); + return MEMTX_OK; + case (QCT_QTIMER_CNTP_CVAL_HI): /* TimerLoad */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + *data = HIGH_32((s->cntval)); + return MEMTX_OK; + case QCT_QTIMER_CNTPCT_LO: + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) { + return MEMTX_ACCESS_ERROR; + } + + *data = LOW_32((s->cntpct + (ptimer_get_count(s->timer)))); + return MEMTX_OK; + case QCT_QTIMER_CNTPCT_HI: + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) { + return MEMTX_ACCESS_ERROR; + } + + *data = HIGH_32((s->cntpct + (ptimer_get_count(s->timer)))); + return MEMTX_OK; + case (QCT_QTIMER_CNTP_TVAL): /* CVAL - CNTP */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + *data = + (s->cntval - (HIGH_32((s->cntpct + (ptimer_get_count(s->timer)))) + + LOW_32((s->cntpct + (ptimer_get_count(s->timer)))))); + return MEMTX_OK; + case (QCT_QTIMER_CNTP_CTL): /* TimerMIS */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + *data = s->int_level; + return MEMTX_OK; + case QCT_QTIMER_CNTPL0ACR: + if (view) { + *data = 0; + } else { + *data = s->cntpl0acr; + } + return MEMTX_OK; + + case QCT_QTIMER_VERSION: + *data = TIMER_VERSION; + return MEMTX_OK; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %x\n", __func__, + (int)offset); + *data = 0; + return MEMTX_ACCESS_ERROR; + } +} + +/* + * Reset the timer limit after settings have changed. + * May only be called from inside a ptimer transaction block. + */ +static void hex_timer_recalibrate(QCTHextimerState *s, int reload) +{ + uint64_t limit; + /* Periodic. */ + limit = s->limit; + ptimer_set_limit(s->timer, limit, reload); +} + +static MemTxResult hex_timer_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size, MemTxAttrs attrs) +{ + QCTQtimerState *qct_s = (QCTQtimerState *)opaque; + uint32_t slot_nr = (offset & 0xF000) >> 12; + uint32_t reg_offset = offset & 0xFFF; + uint32_t view = slot_nr % qct_s->nr_views; + uint32_t frame = slot_nr / qct_s->nr_views; + + if (frame >= qct_s->nr_frames) { + return MEMTX_ACCESS_ERROR; + } + QCTHextimerState *s = &qct_s->timer[frame]; + + /* + * This is the case where we have 2 views, but the second one is not + * implemented. + */ + if (view && !(qct_s->cnttid & (0x4 << (frame * 4)))) { + return MEMTX_OK; + } + + switch (reg_offset) { + case (QCT_QTIMER_CNTP_CVAL_LO): /* TimerLoad */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + + s->int_level = 0; + s->cntval = value; + ptimer_transaction_begin(s->timer); + if (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) { + /* + * Pause the timer if it is running. This may cause some + * inaccuracy due to rounding, but avoids other issues. + */ + ptimer_stop(s->timer); + } + hex_timer_recalibrate(s, 1); + if (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) { + ptimer_run(s->timer, 0); + } + ptimer_transaction_commit(s->timer); + break; + case (QCT_QTIMER_CNTP_CVAL_HI): + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + break; + case (QCT_QTIMER_CNTP_CTL): /* Timer control register */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + ptimer_transaction_begin(s->timer); + if (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) { + /* + * Pause the timer if it is running. This may cause some + * inaccuracy due to rounding, but avoids other issues. + */ + ptimer_stop(s->timer); + } + s->control = value; + hex_timer_recalibrate(s, s->control & QCT_QTIMER_CNTP_CTL_ENABLE); + ptimer_set_freq(s->timer, s->freq); + ptimer_set_period(s->timer, 1); + if (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) { + ptimer_run(s->timer, 0); + } + ptimer_transaction_commit(s->timer); + break; + case (QCT_QTIMER_CNTP_TVAL): /* CVAL - CNTP */ + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { + return MEMTX_ACCESS_ERROR; + } + + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { + return MEMTX_ACCESS_ERROR; + } + + ptimer_transaction_begin(s->timer); + if (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) { + /* + * Pause the timer if it is running. This may cause some + * inaccuracy due to rounding, but avoids other issues. + */ + ptimer_stop(s->timer); + } + s->cntval = s->cntpct + value; + ptimer_set_freq(s->timer, s->freq); + ptimer_set_period(s->timer, 1); + if (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) { + ptimer_run(s->timer, 0); + } + ptimer_transaction_commit(s->timer); + break; + case QCT_QTIMER_CNTPL0ACR: + if (view) { + break; + } + + s->cntpl0acr = value; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %x\n", __func__, + (int)offset); + return MEMTX_ACCESS_ERROR; + } + hex_timer_update(s); + return MEMTX_OK; +} + +static void hex_timer_tick(void *opaque) +{ + QCTHextimerState *s = (QCTHextimerState *)opaque; + if ((s->cntpct >= s->cntval) && (s->int_level != 1)) { + s->int_level = 1; + hex_timer_update(s); + return; + } + s->cntpct += s->limit; +} + +static const MemoryRegionOps hex_timer_ops = { + .read_with_attrs = hex_timer_read, + .write_with_attrs = hex_timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_hex_timer = { + .name = "hex_timer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]){ VMSTATE_UINT32(control, QCTHextimerState), + VMSTATE_UINT32(cnt_ctrl, QCTHextimerState), + VMSTATE_UINT64(cntpct, QCTHextimerState), + VMSTATE_UINT64(cntval, QCTHextimerState), + VMSTATE_UINT64(limit, QCTHextimerState), + VMSTATE_UINT32(int_level, QCTHextimerState), + VMSTATE_PTIMER(timer, QCTHextimerState), + VMSTATE_END_OF_LIST() } +}; + +static void qct_qtimer_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + QCTQtimerState *s = QCT_QTIMER(dev); + unsigned int i; + + if (s->nr_frames > QCT_QTIMER_TIMER_FRAME_ELTS) { + error_setg(errp, "nr_frames too high"); + return; + } + + if (s->nr_views > QCT_QTIMER_TIMER_VIEW_ELTS) { + error_setg(errp, "nr_views too high"); + return; + } + + memory_region_init_io(&s->iomem, OBJECT(sbd), &qct_qtimer_ops, s, "qutimer", + QTIMER_MEM_SIZE_BYTES); + sysbus_init_mmio(sbd, &s->iomem); + + memory_region_init_io(&s->view_iomem, OBJECT(sbd), &hex_timer_ops, s, + "qutimer_views", + QTIMER_MEM_SIZE_BYTES * s->nr_frames * s->nr_views); + sysbus_init_mmio(sbd, &s->view_iomem); + + for (i = 0; i < s->nr_frames; i++) { + s->timer[i].limit = 1; + s->timer[i].control = QCT_QTIMER_CNTP_CTL_ENABLE; + s->timer[i].cnt_ctrl = + (QCT_QTIMER_AC_CNTACR_RWPT | QCT_QTIMER_AC_CNTACR_RWVT | + QCT_QTIMER_AC_CNTACR_RVOFF | QCT_QTIMER_AC_CNTACR_RFRQ | + QCT_QTIMER_AC_CNTACR_RPVCT | QCT_QTIMER_AC_CNTACR_RPCT); + s->timer[i].qtimer = s; + s->timer[i].freq = QTIMER_DEFAULT_FREQ_HZ; + + s->secure |= (1 << i); + + sysbus_init_irq(sbd, &(s->timer[i].irq)); + + (s->timer[i]).timer = + ptimer_init(hex_timer_tick, &s->timer[i], PTIMER_POLICY_LEGACY); + vmstate_register(NULL, VMSTATE_INSTANCE_ID_ANY, &vmstate_hex_timer, + &s->timer[i]); + } +} + +static const Property qct_qtimer_properties[] = { + DEFINE_PROP_UINT32("freq", QCTQtimerState, freq, QTIMER_DEFAULT_FREQ_HZ), + DEFINE_PROP_UINT32("nr_frames", QCTQtimerState, nr_frames, 2), + DEFINE_PROP_UINT32("nr_views", QCTQtimerState, nr_views, 1), + DEFINE_PROP_UINT32("cnttid", QCTQtimerState, cnttid, 0x11), +}; + +static void qct_qtimer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + + device_class_set_props(k, qct_qtimer_properties); + k->realize = qct_qtimer_realize; + k->vmsd = &vmstate_qct_qtimer; +} + +static const TypeInfo qct_qtimer_info = { + .name = TYPE_QCT_QTIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(QCTQtimerState), + .instance_init = qct_qtimer_init, + .class_init = qct_qtimer_class_init, +}; + +static void qct_qtimer_register_types(void) +{ + type_register_static(&qct_qtimer_info); +} + +type_init(qct_qtimer_register_types) diff --git a/include/hw/timer/qct-qtimer.h b/include/hw/timer/qct-qtimer.h new file mode 100644 index 000000000000..90f7981ccf8d --- /dev/null +++ b/include/hw/timer/qct-qtimer.h @@ -0,0 +1,85 @@ +/* + * Qualcomm QCT QTimer + * + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef TIMER_QCT_QTIMER_H +#define TIMER_QCT_QTIMER_H + +#include "hw/ptimer.h" +#include "hw/sysbus.h" + +#define TYPE_QCT_QTIMER "qct-qtimer" +#define TYPE_QCT_HEXTIMER "qct-hextimer" +OBJECT_DECLARE_SIMPLE_TYPE(QCTQtimerState, QCT_QTIMER) +OBJECT_DECLARE_SIMPLE_TYPE(QCTHextimerState, QCT_HEXTIMER) + +struct QCTHextimerState { + QCTQtimerState *qtimer; + ptimer_state *timer; + uint64_t cntval; /* + * Physical timer compare value interrupt when cntpct > + * cntval + */ + uint64_t cntpct; /* Physical counter */ + uint32_t control; + uint32_t cnt_ctrl; + uint32_t cntpl0acr; + uint64_t limit; + uint32_t freq; + uint32_t int_level; + qemu_irq irq; +}; + +#define QCT_QTIMER_TIMER_FRAME_ELTS (8) +#define QCT_QTIMER_TIMER_VIEW_ELTS (2) +struct QCTQtimerState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion view_iomem; + uint32_t secure; + struct QCTHextimerState timer[QCT_QTIMER_TIMER_FRAME_ELTS]; + uint32_t frame_id; + uint32_t freq; + uint32_t nr_frames; + uint32_t nr_views; + uint32_t cnttid; +}; + +#define QCT_QTIMER_AC_CNTFRQ (0x000) +#define QCT_QTIMER_AC_CNTSR (0x004) +#define QCT_QTIMER_AC_CNTSR_NSN_1 (1 << 0) +#define QCT_QTIMER_AC_CNTSR_NSN_2 (1 << 1) +#define QCT_QTIMER_AC_CNTSR_NSN_3 (1 << 2) +#define QCT_QTIMER_AC_CNTTID (0x08) +#define QCT_QTIMER_AC_CNTACR_0 (0x40) +#define QCT_QTIMER_AC_CNTACR_1 (0x44) +#define QCT_QTIMER_AC_CNTACR_2 (0x48) +#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */ +#define QCT_QTIMER_AC_CNTACR_RWVT (1 << 4) /* R/W of CNTV_* regs */ +#define QCT_QTIMER_AC_CNTACR_RVOFF (1 << 3) /* R/W of CNTVOFF register */ +#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */ +#define QCT_QTIMER_AC_CNTACR_RPVCT (1 << 1) /* R/W of CNTVCT register */ +#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */ +#define QCT_QTIMER_VERSION (0x0fd0) +#define QCT_QTIMER_CNTPCT_LO (0x000) +#define QCT_QTIMER_CNTPCT_HI (0x004) +#define QCT_QTIMER_CNT_FREQ (0x010) +#define QCT_QTIMER_CNTPL0ACR (0x014) +#define QCT_QTIMER_CNTPL0ACR_PL0CTEN (1 << 9) +#define QCT_QTIMER_CNTPL0ACR_PL0TVEN (1 << 8) +#define QCT_QTIMER_CNTPL0ACR_PL0VCTEN (1 << 1) +#define QCT_QTIMER_CNTPL0ACR_PL0PCTEN (1 << 0) +#define QCT_QTIMER_CNTP_CVAL_LO (0x020) +#define QCT_QTIMER_CNTP_CVAL_HI (0x024) +#define QCT_QTIMER_CNTP_TVAL (0x028) +#define QCT_QTIMER_CNTP_CTL (0x02c) +#define QCT_QTIMER_CNTP_CTL_ISTAT (1 << 2) +#define QCT_QTIMER_CNTP_CTL_INTEN (1 << 1) +#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0) +#define QCT_QTIMER_AC_CNTACR_START 0x40 +#define QCT_QTIMER_AC_CNTACR_END 0x5C + +#endif /* TIMER_QCT_QTIMER_H */ From 6b6f61d2330f7655bb04e928f90dd1bf0d129ccd Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 8 Jul 2024 18:51:06 -0700 Subject: [PATCH 1146/1179] docs: Add hexagon VM info Signed-off-by: Brian Cain --- MAINTAINERS | 1 + docs/devel/hexagon-vm.rst | 190 +++++++++++++++++++++++++++++++++ docs/devel/index-internals.rst | 1 + docs/system/target-hexagon.rst | 11 ++ 4 files changed, 203 insertions(+) create mode 100644 docs/devel/hexagon-vm.rst diff --git a/MAINTAINERS b/MAINTAINERS index dbcad875fa61..f185fa31f1a7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -243,6 +243,7 @@ F: docs/system/target-hexagon.rst F: docs/devel/hexagon-sys.rst F: docs/devel/hexagon-l2vic.rst F: tests/functional/test_hexagon_minivm.py +F: docs/devel/hexagon-vm.rst T: git https://github.com/quic/qemu.git hex-next Hexagon idef-parser diff --git a/docs/devel/hexagon-vm.rst b/docs/devel/hexagon-vm.rst new file mode 100644 index 000000000000..fb16d56d59de --- /dev/null +++ b/docs/devel/hexagon-vm.rst @@ -0,0 +1,190 @@ +Hexagon Virtual Machine +======================= + +The hexagon virtual machine is a hypervisor that can partition a single +Hexagon DSP among multiple guest operating systems, and abstracts the +specific details of a DSP architectural revision for the sake of consistency +among generations. + +Events +------ + +The guest operating system should register the Guest Event Vector Base +via the ``vmsetvec`` virtual instruction at system startup. The vector table +and handlers are determined by the guest OS. + +Guests return from event handlers with ``vmrte``. This instruction will restore +the mode (user versus guest), interrupt enable state, PC, SP. + +.. list-table:: Event types + :header-rows: 1 + + * - Number + - Name + - Description + - Maskable + - Detail + * - 0 + - Reserved + - + - + - + * - 1 + - Machine check event + - unrecoverable VM state + - No + - execution terminates if unhandled + * - 2 + - General exception + - internal hardware or software exception + - No + - + * - 3-4 + - Reserved + - + - + - + * - 5 + - ``trap0`` + - ``trap0`` instruction + - No + - + * - 6 + - Reserved + - + - + - + * - 7 + - Interrupt + - external interrupts + - Yes + - increasing interrupt numbers have descending priority + +Startup +------- +In order to transition to user-mode, the guest OS must set the ``UM`` bit in +the guest status register and specify the address to start executing in +user mode in the guest event link register. + +Virtual Instructions +-------------------- + +.. list-table:: Virtual Instructions + :header-rows: 1 + + * - Instruction + - Behavior + - Operand + - Input + - Output + * - vmversion + - returns the VM version + - 0x0 + - requested VM version + - provided VM version + * - vmrte + - return from event + - 0x1 + - Event info in g3:0 + - N/A + * - vmsetvec + - set event vector + - 0x2 + - r0 is set to vector table addr + - r0 is 0 on success, 1 otherwise + * - vmsetie + - set interrupt enabled + - 0x3 + - r0 is set to 1 to enable, 0 to disable + - previous IE bit is stored as LSB of r0 + * - vmgetie + - get interrupt enabled + - 0x4 + - N/A + - current IE bit is stored as LSB of r0 + * - vmintop + - interrupt operation + - 0x5 + - r0 = Interrupt Op, r1-r4: Depends on Op + - r0 - value depends on operation + * - vmclrmap + - clear virtual memory map + - 0xa + - r0 = Interrupt Op, r1-r4: Depends on Op + - r0 - value depends on operation + * - vmnewmap + - set new virtual memory map + - 0xb + - + r0 contains logical address of new segment table + + r1 = type of translations: 0 indicates a logical address of a zero-terminated linear list, 1 indicates a set of page tables. + - r0 contains 0 on success, otherwise negative error code + * - vmcache + - VM cache control: not modeled + - 0xd + - + r0 contains the operation to be performed + + r1 = Starting virtual address + + r2 contains the length in bytes + - r0 contains 0 on success, otherwise -1. Cache behavior is not modeled so this operation always succeeds. + * - vmgettime + - Get virtual machine time + - 0xe + - N/A + - r0 contains the least significant 32 bits of timestamp, r1 contains the most significant 32 bits of timestamp + * - vmsettime + - Set virtual machine time + - 0xf + - r0 contains the least significant 32 bits of timestamp, r1 contains the most significant 32 bits of timestamp + - N/A + * - vmwait + - wait for interrupt + - 0x10 + - N/A + - r0 contains the interrupt number of the interrupt waking the guest + * - vmyield + - voluntarily yield VM task + - 0x11 + - N/A + - N/A + * - vmstart + - Create new virtual processor instance + - 0x12 + - r0 contains the starting execution address, r1 contains the starting stack pointer + - r0 contains the Virtual processor number of new virtual processor on success, otherwise -1 + * - vmstop + - terminate current virtual processor instance + - 0x13 + - N/A + - N/A + * - vmvpid + - get the virtual processor ID + - 0x14 + - N/A + - r0 contains the virtual processor number of virtual processor executing the instruction + * - vmsetregs + - Set guest registers + - 0x15 + - r0-3 hold g0-3 values + - N/A + * - vmgetregs + - Get guest registers + - 0x16 + - N/A + - r0-3 hold g0-3 values + * - vmtimerop + - perform an operation on a system timer + - 0x18 + - + getfreq = 0 + + getres = 1 + + gettime = 2 + + gettimeout = 3 + + settimeout = 4 + + deltatimeout = 5 + - r0 contains result of the timer operation call + * - vmgetinfo + - Get system info + - 0x1a + - Index of the system info parameter: + + + build_id = 0 + + info_boot_flags = 1 + - value of the indicated system info parameter diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index 6620497595ab..82f788682bb4 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst @@ -16,6 +16,7 @@ Details about QEMU's various subsystems including how to add features to them. ebpf_rss hexagon-sys hexagon-l2vic + hexagon-vm migration/index multi-process reset diff --git a/docs/system/target-hexagon.rst b/docs/system/target-hexagon.rst index b2ffee91eb02..894337a533cd 100644 --- a/docs/system/target-hexagon.rst +++ b/docs/system/target-hexagon.rst @@ -92,6 +92,17 @@ The ``trap0`` instruction can activate these semihosting calls so that the guest software can access the host console and filesystem. Semihosting is not yet implemented in QEMU hexagon. +Hexagon Virtual Machine +----------------------- + +The hexagon virtual machine is a hypervisor that can partition a single +Hexagon DSP among multiple guest operating systems, and abstracts the +specific details of a DSP architectural revision for the sake of consistency +among generations. + +[minivm](https://github.com/quic/hexagonMVM) is a reference implementation +of this VM interface. + Hexagon Features ================ From 741f72dd5cf246e06135220c5a037c634ac40559 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 2 Jan 2025 19:53:06 -0800 Subject: [PATCH 1147/1179] target/hexagon: Implement hexagon_read_timer() Signed-off-by: Brian Cain --- target/hexagon/op_helper.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index dd26a0e3e0a1..8eacb3b04115 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -37,6 +37,7 @@ #ifndef CONFIG_USER_ONLY #include "hex_mmu.h" #include "hw/intc/l2vic.h" +#include "hw/timer/qct-qtimer.h" #include "hex_interrupts.h" #include "hexswi.h" #endif @@ -1765,7 +1766,13 @@ static uint32_t hexagon_find_last_irq(CPUHexagonState *env, uint32_t vid) static void hexagon_read_timer(CPUHexagonState *env, uint32_t *low, uint32_t *high) { - qemu_log_mask(LOG_UNIMP, "reading timer_hi/lo not yet supported\n"); + CPUState *cs = env_cpu(env); + HexagonCPU *cpu = HEXAGON_CPU(cs); + const hwaddr low_addr = cpu->qtimer_base_addr + QCT_QTIMER_CNTPCT_LO; + const hwaddr high_addr = cpu->qtimer_base_addr + QCT_QTIMER_CNTPCT_HI; + + cpu_physical_memory_read(low_addr, low, sizeof(*low)); + cpu_physical_memory_read(high_addr, high, sizeof(*high)); } static inline QEMU_ALWAYS_INLINE void sreg_write(CPUHexagonState *env, From 6d2a61858af8c2cd534c56feb43f04b3365941b3 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Tue, 28 Jan 2025 11:17:06 -0800 Subject: [PATCH 1148/1179] semihosting: add the "usefs" feature This will be used by Hexagon semihosting. Signed-off-by: Matheus Tavares Bernardino --- include/semihosting/semihost.h | 6 ++++++ qemu-options.hx | 7 ++++++- semihosting/config.c | 11 +++++++++++ semihosting/syscalls.c | 13 +++++++++++-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/include/semihosting/semihost.h b/include/semihosting/semihost.h index 97d2a2ba996d..6e0776610651 100644 --- a/include/semihosting/semihost.h +++ b/include/semihosting/semihost.h @@ -51,6 +51,11 @@ static inline const char *semihosting_get_cmdline(void) { return NULL; } + +static inline const char *semihosting_get_usefs(void) +{ + return NULL; +} #else /* !CONFIG_USER_ONLY */ /** * semihosting_enabled: @@ -63,6 +68,7 @@ SemihostingTarget semihosting_get_target(void); const char *semihosting_get_arg(int i); int semihosting_get_argc(void); const char *semihosting_get_cmdline(void); +const char *semihosting_get_usefs(void); void semihosting_arg_fallback(const char *file, const char *cmd); /* for vl.c hooks */ void qemu_semihosting_enable(void); diff --git a/qemu-options.hx b/qemu-options.hx index dc694a99a30a..9aee48a72572 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -5128,7 +5128,7 @@ DEF("semihosting-config", HAS_ARG, QEMU_OPTION_semihosting_config, QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_MIPS | QEMU_ARCH_RISCV) SRST -``-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,userspace=on|off][,arg=str[,...]]`` +``-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,userspace=on|off][,usefs=][,arg=str[,...]]`` Enable and configure :ref:`Semihosting` (ARM, M68K, Xtensa, MIPS, RISC-V only). @@ -5152,6 +5152,11 @@ SRST only be used if all guest code is trusted (for example, in bare-metal test case code). + ``usefs=`` + Sets a fallback directory to be used by the open semihosting call. If + the requested file is not found QEMU will search again at the given + path. + ``arg=str1,arg=str2,...`` Allows the user to pass input arguments, and can be used multiple times to build up a list. The old-style diff --git a/semihosting/config.c b/semihosting/config.c index 56283b5c3c38..a64a8dfd27da 100644 --- a/semihosting/config.c +++ b/semihosting/config.c @@ -46,6 +46,9 @@ QemuOptsList qemu_semihosting_config_opts = { }, { .name = "arg", .type = QEMU_OPT_STRING, + }, { + .name = "usefs", + .type = QEMU_OPT_STRING, }, { /* end of list */ } }, @@ -58,6 +61,7 @@ typedef struct SemihostingConfig { char **argv; int argc; const char *cmdline; /* concatenated argv */ + const char *usefs; } SemihostingConfig; static SemihostingConfig semihosting; @@ -94,6 +98,11 @@ const char *semihosting_get_cmdline(void) return semihosting.cmdline; } +const char *semihosting_get_usefs(void) +{ + return semihosting.usefs; +} + static int add_semihosting_arg(void *opaque, const char *name, const char *val, Error **errp) @@ -144,6 +153,8 @@ int qemu_semihosting_config_options(const char *optstr) true); semihosting.userspace_enabled = qemu_opt_get_bool(opts, "userspace", false); + semihosting.usefs = qemu_opt_get(opts, "usefs"); + const char *target = qemu_opt_get(opts, "target"); /* setup of chardev is deferred until they are initialised */ semihost_chardev = qemu_opt_get(opts, "chardev"); diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index f6451d9bb0e6..ef717912a866 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -261,7 +261,8 @@ static void host_open(CPUState *cs, gdb_syscall_complete_cb complete, { CPUArchState *env G_GNUC_UNUSED = cpu_env(cs); char *p; - int ret, host_flags = O_BINARY; + int ret, err, host_flags = O_BINARY; + const char *usefs = semihosting_get_usefs(); ret = validate_lock_user_string(&p, cs, fname, fname_len); if (ret < 0) { @@ -287,9 +288,17 @@ static void host_open(CPUState *cs, gdb_syscall_complete_cb complete, } ret = open(p, host_flags, mode); + err = errno; + if (ret < 0 && err == ENOENT && usefs) { + g_autoptr(GString) usefs_fname = g_string_new(NULL); + g_string_append_printf(usefs_fname, "%s/%s", usefs, p); + ret = open(usefs_fname->str, host_flags, mode); + err = errno; + } + if (ret < 0) { qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to open %s\n", __func__, p); - complete(cs, -1, errno); + complete(cs, -1, err); } else { int guestfd = alloc_guestfd(); associate_guestfd(guestfd, ret); From 28c35de8ebe894eb2d63a242526c2ef8dc370187 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 06:22:26 -0800 Subject: [PATCH 1149/1179] semihosting: add option for extended open() modes These modes are defined and will be used by hexagon semihosting. Signed-off-by: Matheus Tavares Bernardino --- semihosting/arm-compat-semi.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 86e5260e504b..e26bccb25eaf 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -85,7 +85,15 @@ #define O_BINARY 0 #endif -static int gdb_open_modeflags[12] = { +#include "common-semi-target.h" + +#ifdef SEMIHOSTING_EXT_OPEN_MODES +#define GDB_OPEN_MODES_NR 14 +#else +#define GDB_OPEN_MODES_NR 12 +#endif + +static int gdb_open_modeflags[GDB_OPEN_MODES_NR] = { GDB_O_RDONLY, GDB_O_RDONLY, GDB_O_RDWR, @@ -98,6 +106,10 @@ static int gdb_open_modeflags[12] = { GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, +#ifdef SEMIHOSTING_EXT_OPEN_MODES + GDB_O_RDWR | GDB_O_CREAT, + GDB_O_RDWR | GDB_O_CREAT | GDB_O_EXCL, +#endif }; #ifndef CONFIG_USER_ONLY @@ -386,7 +398,7 @@ void do_common_semihosting(CPUState *cs) if (!s) { goto do_fault; } - if (arg1 >= 12) { + if (arg1 >= GDB_OPEN_MODES_NR) { unlock_user(s, arg0, 0); common_semi_cb(cs, -1, EINVAL); break; From 4723707abb85eff3839fa85c0104f804f01282ee Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 06:25:16 -0800 Subject: [PATCH 1150/1179] semihosting: extract GET_ARG() to its own function This allows for future archs implementing semihosting to define their custom "GET_ARG". In particular, this will be used for Hexagon. Signed-off-by: Matheus Tavares Bernardino --- semihosting/arm-compat-semi.c | 15 ++++----------- target/arm/common-semi-target.h | 11 +++++++++++ target/riscv/common-semi-target.h | 11 +++++++++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index e26bccb25eaf..925683efe5bf 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -192,17 +192,10 @@ static LayoutInfo common_semi_find_bases(CPUState *cs) * error indication (0 on success, non-0 for error) which the caller * should check. */ - -#define GET_ARG(n) do { \ - if (is_64bit_semihosting(env)) { \ - if (get_user_u64(arg ## n, args + (n) * 8)) { \ - goto do_fault; \ - } \ - } else { \ - if (get_user_u32(arg ## n, args + (n) * 4)) { \ - goto do_fault; \ - } \ - } \ +#define GET_ARG(n) do { \ + if (common_semi_read_arg_word(env, &arg ## n, args, n)) { \ + goto do_fault; \ + } \ } while (0) #define SET_ARG(n, val) \ diff --git a/target/arm/common-semi-target.h b/target/arm/common-semi-target.h index da51f2d7f540..69429a45c652 100644 --- a/target/arm/common-semi-target.h +++ b/target/arm/common-semi-target.h @@ -12,6 +12,17 @@ #include "target/arm/cpu-qom.h" +static inline bool common_semi_read_arg_word(CPUArchState *env, + target_ulong *save_to, + target_ulong args_addr, + int arg_num) +{ + if (is_64bit_semihosting(env)) { + return get_user_u64(*save_to, args_addr + (arg_num) * 8)); + } + return get_user_u32(*save_to, args_addr + (arg_num) * 4)); +} + static inline target_ulong common_semi_arg(CPUState *cs, int argno) { ARMCPU *cpu = ARM_CPU(cs); diff --git a/target/riscv/common-semi-target.h b/target/riscv/common-semi-target.h index 7c8a59e0cc3c..ef6929bdfc5a 100644 --- a/target/riscv/common-semi-target.h +++ b/target/riscv/common-semi-target.h @@ -11,6 +11,17 @@ #ifndef TARGET_RISCV_COMMON_SEMI_TARGET_H #define TARGET_RISCV_COMMON_SEMI_TARGET_H +static inline bool common_semi_read_arg_word(CPUArchState *env, + target_ulong *save_to, + target_ulong args_addr, + int arg_num) +{ + if (is_64bit_semihosting(env)) { + return get_user_u64(*save_to, args_addr + (arg_num) * 8)); + } + return get_user_u32(*save_to, args_addr + (arg_num) * 4)); +} + static inline target_ulong common_semi_arg(CPUState *cs, int argno) { RISCVCPU *cpu = RISCV_CPU(cs); From 1507f40ab0d137b9d5167eb3e03165d99c928f3b Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 06:26:35 -0800 Subject: [PATCH 1151/1179] semihosting: add optional callbacks To be used by Hexagon. Signed-off-by: Matheus Tavares Bernardino --- semihosting/arm-compat-semi.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 925683efe5bf..0990dd704ee0 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -85,6 +85,21 @@ #define O_BINARY 0 #endif +struct semihosting_opt_callbacks { + void (*set_err)(CPUState *cs, target_ulong err); + void (*prepare_for_read)(CPUState *cs, target_ulong fd, target_ulong buf, + target_ulong len); +} opt_callbacks; + +#define SEMIHOSTING_REGISTER_OPT_CALLBACKS(callbacks) \ + struct semihosting_opt_callbacks opt_callbacks = callbacks; + +#define CALL_OPT_CALLBACK(FN, ARGS...) do { \ + if (opt_callbacks.FN) { \ + opt_callbacks.FN(ARGS); \ + } \ +} while (0) + #include "common-semi-target.h" #ifdef SEMIHOSTING_EXT_OPEN_MODES @@ -236,6 +251,7 @@ static void common_semi_cb(CPUState *cs, uint64_t ret, int err) ts->swi_errno = err; #else syscall_err = err; + CALL_OPT_CALLBACK(set_err, cs, err); #endif } common_semi_set_ret(cs, ret); @@ -471,6 +487,7 @@ void do_common_semihosting(CPUState *cs) GET_ARG(0); GET_ARG(1); GET_ARG(2); + CALL_OPT_CALLBACK(prepare_for_read, cs, arg0, arg1, arg2); semihost_sys_read(cs, common_semi_rw_cb, arg0, arg1, arg2); break; From 2d2ac383f46d3e66e6e2b063df53481fdbf5fcdc Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 06:37:17 -0800 Subject: [PATCH 1152/1179] semihosting: add config opt to use stdio To be used by Hexagon semihosting. Signed-off-by: Matheus Tavares Bernardino --- semihosting/guestfd.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/semihosting/guestfd.c b/semihosting/guestfd.c index d3241434c516..4d846f4e5d10 100644 --- a/semihosting/guestfd.c +++ b/semihosting/guestfd.c @@ -23,6 +23,18 @@ GuestFD console_in_gf; GuestFD console_out_gf; #endif +static void semihosting_use_stdio(void) +{ + console_in_gf.type = GuestFDHost; + console_in_gf.hostfd = 0; + console_out_gf.type = GuestFDHost; + console_out_gf.hostfd = 1; + guestfd_array = g_array_set_size(guestfd_array, 3); + associate_guestfd(0, 0); + associate_guestfd(1, 1); + associate_guestfd(2, 2); +} + void qemu_semihosting_guestfd_init(void) { /* New entries zero-initialized, i.e. type GuestFDUnused */ @@ -36,8 +48,12 @@ void qemu_semihosting_guestfd_init(void) console_out_gf.type = GuestFDGDB; console_out_gf.hostfd = 2; } else { +#ifdef CONFIG_SEMIHOSTING_USE_STDIO + semihosting_use_stdio(); +#else console_in_gf.type = GuestFDConsole; console_out_gf.type = GuestFDConsole; +#endif } #else /* Otherwise, the stdio file descriptors apply. */ From 8b899c7b76d80ebedd02c361e0796412d743fe71 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 06:52:40 -0800 Subject: [PATCH 1153/1179] Hexagon: add aux functions for guest mem load/store Will be used for semihosting. Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/cpu_helper.c | 129 ++++++++++++++++++++++++++++++++++++ target/hexagon/cpu_helper.h | 6 ++ 2 files changed, 135 insertions(+) diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index bb240a9d8f50..78d010817eac 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -30,6 +30,135 @@ #ifndef CONFIG_USER_ONLY +static bool hexagon_read_memory_small(CPUHexagonState *env, target_ulong addr, + int byte_count, unsigned char *dstbuf, + int mmu_idx, uintptr_t retaddr) + { + /* handle small sizes */ + switch (byte_count) { + case 1: + *dstbuf = cpu_ldub_mmuidx_ra(env, addr, mmu_idx, retaddr); + return true; + + case 2: + if (QEMU_IS_ALIGNED(addr, 2)) { + *(unsigned short *)dstbuf = + cpu_lduw_mmuidx_ra(env, addr, mmu_idx, retaddr); + return true; + } + break; + + case 4: + if (QEMU_IS_ALIGNED(addr, 4)) { + *(uint32_t *)dstbuf = + cpu_ldl_mmuidx_ra(env, addr, mmu_idx, retaddr); + return true; + } + break; + + case 8: + if (QEMU_IS_ALIGNED(addr, 8)) { + *(uint64_t *)dstbuf = + cpu_ldq_mmuidx_ra(env, addr, mmu_idx, retaddr); + return true; + } + break; + + default: + /* larger request, handle elsewhere */ + return false; + } + + /* not aligned, copy bytes */ + for (int i = 0; i < byte_count; ++i) { + *dstbuf++ = cpu_ldub_mmuidx_ra(env, addr++, mmu_idx, retaddr); + } + return true; +} + +void hexagon_read_memory(CPUHexagonState *env, target_ulong vaddr, int size, + void *retptr, uintptr_t retaddr) +{ + BQL_LOCK_GUARD(); + CPUState *cs = env_cpu(env); + unsigned mmu_idx = cpu_mmu_index(cs, false); + if (!hexagon_read_memory_small(env, vaddr, size, retptr, mmu_idx, retaddr)) { + cpu_abort(cs, "%s: ERROR: bad size = %d!\n", __func__, size); + } +} + +static bool hexagon_write_memory_small(CPUHexagonState *env, target_ulong addr, + int byte_count, unsigned char *srcbuf, + int mmu_idx, uintptr_t retaddr) +{ + /* handle small sizes */ + switch (byte_count) { + case 1: + cpu_stb_mmuidx_ra(env, addr, *srcbuf, mmu_idx, retaddr); + return true; + + case 2: + if (QEMU_IS_ALIGNED(addr, 2)) { + cpu_stw_mmuidx_ra(env, addr, *(uint16_t *)srcbuf, mmu_idx, retaddr); + return true; + } + break; + + case 4: + if (QEMU_IS_ALIGNED(addr, 4)) { + cpu_stl_mmuidx_ra(env, addr, *(uint32_t *)srcbuf, mmu_idx, retaddr); + return true; + } + break; + + case 8: + if (QEMU_IS_ALIGNED(addr, 8)) { + cpu_stq_mmuidx_ra(env, addr, *(uint64_t *)srcbuf, mmu_idx, retaddr); + return true; + } + break; + + default: + /* larger request, handle elsewhere */ + return false; + } + + /* not aligned, copy bytes */ + for (int i = 0; i < byte_count; ++i) { + cpu_stb_mmuidx_ra(env, addr++, *srcbuf++, mmu_idx, retaddr); + } + + return true; +} + +void hexagon_write_memory(CPUHexagonState *env, target_ulong vaddr, + int size, uint64_t data, uintptr_t retaddr) +{ + CPUState *cs = env_cpu(env); + unsigned mmu_idx = cpu_mmu_index(cs, false); + if (!hexagon_write_memory_small(env, vaddr, size, (unsigned char *)&data, + mmu_idx, retaddr)) { + cpu_abort(cs, "%s: ERROR: bad size = %d!\n", __func__, size); + } +} + +static inline uint32_t page_start(uint32_t addr) +{ + uint32_t page_align = ~(TARGET_PAGE_SIZE - 1); + return addr & page_align; +} + +void hexagon_touch_memory(CPUHexagonState *env, uint32_t start_addr, + uint32_t length, uintptr_t retaddr) +{ + unsigned int warm; + uint32_t first = page_start(start_addr); + uint32_t last = page_start(start_addr + length - 1); + for (uint32_t page = first; page <= last; page += TARGET_PAGE_SIZE) { + hexagon_read_memory(env, page, 1, &warm, retaddr); + } +} + uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index) { g_assert_not_reached(); diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h index 0a5134204f3d..f86f5e744fd4 100644 --- a/target/hexagon/cpu_helper.h +++ b/target/hexagon/cpu_helper.h @@ -7,6 +7,12 @@ #ifndef HEXAGON_CPU_HELPER_H #define HEXAGON_CPU_HELPER_H +void hexagon_read_memory(CPUHexagonState *env, target_ulong vaddr, int size, + void *retptr, uintptr_t retaddr); +void hexagon_write_memory(CPUHexagonState *env, target_ulong vaddr, + int size, uint64_t data, uintptr_t retaddr); +void hexagon_touch_memory(CPUHexagonState *env, uint32_t start_addr, + uint32_t length, uintptr_t retaddr); uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index); uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env); uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env); From 39c75215a493104ddd96899564bcc3d70d7b452f Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 06:49:40 -0800 Subject: [PATCH 1154/1179] Hexagon: add semihosting support Signed-off-by: Matheus Tavares Bernardino --- configs/targets/hexagon-softmmu.mak | 3 + hw/hexagon/Kconfig | 1 + hw/hexagon/hexagon_dsp.c | 2 + qemu-options.hx | 8 +-- target/hexagon/common-semi-target.h | 87 +++++++++++++++++++++++++++++ target/hexagon/hexswi.c | 18 +++++- 6 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 target/hexagon/common-semi-target.h diff --git a/configs/targets/hexagon-softmmu.mak b/configs/targets/hexagon-softmmu.mak index 9f8fca1dc162..03cf1306a348 100644 --- a/configs/targets/hexagon-softmmu.mak +++ b/configs/targets/hexagon-softmmu.mak @@ -5,3 +5,6 @@ TARGET_SUPPORTS_MTTCG=y TARGET_XML_FILES=gdb-xml/hexagon-core.xml gdb-xml/hexagon-hvx.xml gdb-xml/hexagon-sys.xml TARGET_LONG_BITS=32 TARGET_NEED_FDT=y +CONFIG_SEMIHOSTING=y +CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y +CONFIG_SEMIHOSTING_USE_STDIO=y diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig index f3f011573105..9a2369974e09 100644 --- a/hw/hexagon/Kconfig +++ b/hw/hexagon/Kconfig @@ -4,6 +4,7 @@ config HEX_DSP depends on HEXAGON && TCG imply PTIMER select L2VIC # Vector PIC + select ARM_COMPATIBLE_SEMIHOSTING config HEX_VIRT bool diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c index 198f98399336..eca310c0e345 100644 --- a/hw/hexagon/hexagon_dsp.c +++ b/hw/hexagon/hexagon_dsp.c @@ -24,6 +24,7 @@ #include "include/system/system.h" #include "target/hexagon/internal.h" #include "system/reset.h" +#include "include/semihosting/semihost.h" #include "machine_cfg_v66g_1024.h.inc" @@ -166,6 +167,7 @@ static void init_mc(MachineClass *mc) mc->no_serial = 1; mc->is_default = false; mc->max_cpus = 8; + qemu_semihosting_enable(); } /* ----------------------------------------------------------------- */ diff --git a/qemu-options.hx b/qemu-options.hx index 9aee48a72572..888b3092bef7 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -5110,7 +5110,7 @@ ERST DEF("semihosting", 0, QEMU_OPTION_semihosting, "-semihosting semihosting mode\n", QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | - QEMU_ARCH_MIPS | QEMU_ARCH_RISCV) + QEMU_ARCH_MIPS | QEMU_ARCH_RISCV | QEMU_ARCH_HEXAGON) SRST ``-semihosting`` Enable :ref:`Semihosting` mode (ARM, M68K, Xtensa, MIPS, RISC-V only). @@ -5126,11 +5126,11 @@ DEF("semihosting-config", HAS_ARG, QEMU_OPTION_semihosting_config, "-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,userspace=on|off][,arg=str[,...]]\n" \ " semihosting configuration\n", QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | -QEMU_ARCH_MIPS | QEMU_ARCH_RISCV) +QEMU_ARCH_MIPS | QEMU_ARCH_RISCV | QEMU_ARCH_HEXAGON) SRST ``-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,userspace=on|off][,usefs=][,arg=str[,...]]`` - Enable and configure :ref:`Semihosting` (ARM, M68K, Xtensa, MIPS, RISC-V - only). + Enable and configure :ref:`Semihosting` (ARM, M68K, Xtensa, MIPS, RISC-V, + Hexagon only). .. warning:: Note that this allows guest direct access to the host filesystem, so diff --git a/target/hexagon/common-semi-target.h b/target/hexagon/common-semi-target.h new file mode 100644 index 000000000000..759aaeba905f --- /dev/null +++ b/target/hexagon/common-semi-target.h @@ -0,0 +1,87 @@ +/* + * Target-specific parts of semihosting/arm-compat-semi.c. + * + * Copyright(c) 2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef TARGET_HEXAGON_COMMON_SEMI_TARGET_H +#define TARGET_HEXAGON_COMMON_SEMI_TARGET_H + +#include "cpu.h" +#include "cpu_helper.h" +#include "qemu/log.h" +#include "semihosting/uaccess.h" + +static inline bool common_semi_read_arg_word(CPUArchState *env, + target_ulong *save_to, + target_ulong args_addr, + int arg_num) +{ + hexagon_read_memory(env, args_addr + (arg_num) * 4, 4, save_to, 0); + return false; +} + +static inline target_ulong common_semi_arg(CPUState *cs, int argno) +{ + CPUHexagonState *env = cpu_env(cs); + return arch_get_thread_reg(env, HEX_REG_R00 + argno); +} + +static inline void common_semi_set_ret(CPUState *cs, target_ulong ret) +{ + CPUHexagonState *env = cpu_env(cs); + arch_set_thread_reg(env, HEX_REG_R00, ret); +} + +static inline void hex_semi_set_err(CPUState *cs, target_ulong err) +{ + CPUHexagonState *env = cpu_env(cs); + arch_set_thread_reg(env, HEX_REG_R01, err); +} + +static inline bool common_semi_sys_exit_extended(CPUState *cs, int nr) +{ + return false; +} + +static inline bool is_64bit_semihosting(CPUArchState *env) +{ + return false; +} + +static inline target_ulong common_semi_stack_bottom(CPUState *cs) +{ + CPUHexagonState *env = cpu_env(cs); + return arch_get_thread_reg(env, HEX_REG_SP); +} + +static inline bool common_semi_has_synccache(CPUArchState *env) +{ + return false; +} + +static inline void hex_prepare_for_read(CPUState *cs, target_ulong fd, + target_ulong buf, target_ulong len) +{ + CPUHexagonState *env = cpu_env(cs); + /* + * Need to make sure the page we are going to write to is available. + * The file pointer advances with the read. If the write to bufaddr + * faults the swi function will be restarted but the file pointer + * will be wrong. + */ + hexagon_touch_memory(env, buf, len, 0); +} + +const struct semihosting_opt_callbacks hex_opt_callbacks = { + .prepare_for_read = hex_prepare_for_read, + .set_err = hex_semi_set_err, +}; + +SEMIHOSTING_REGISTER_OPT_CALLBACKS(hex_opt_callbacks) + +#define SEMIHOSTING_EXT_OPEN_MODES + +#endif diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c index 5fcf9b2be933..daa9f965145e 100644 --- a/target/hexagon/hexswi.c +++ b/target/hexagon/hexswi.c @@ -22,10 +22,25 @@ #ifndef CONFIG_USER_ONLY #include "hex_mmu.h" #include "hexswi.h" +#include "semihosting/common-semi.h" #endif #ifndef CONFIG_USER_ONLY +#define HEX_SYS_EXCEPTION 0x18 + +static void sim_handle_trap0(CPUHexagonState *env) +{ + g_assert(bql_locked()); + target_ulong what_swi = arch_get_thread_reg(env, HEX_REG_R00); + + if (what_swi == HEX_SYS_EXCEPTION) { + arch_set_system_reg(env, HEX_SREG_MODECTL, 0); + exit(arch_get_thread_reg(env, HEX_REG_R02)); + } + + do_common_semihosting(cs); +} static void set_addresses(CPUHexagonState *env, target_ulong pc_offset, target_ulong exception_index) @@ -88,8 +103,7 @@ void hexagon_cpu_do_interrupt(CPUState *cs) switch (cs->exception_index) { case HEX_EVENT_TRAP0: if (env->cause_code == 0) { - qemu_log_mask(LOG_UNIMP, - "trap0 is unhandled, no semihosting available\n"); + sim_handle_trap0(env); } hexagon_ssr_set_cause(env, env->cause_code); From ed8d25f9450231fc04c836991d948cd956e094cd Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 07:29:55 -0800 Subject: [PATCH 1155/1179] Hexagon: add main arch-specific semihosting operations Signed-off-by: Matheus Tavares Bernardino --- include/semihosting/common-semi.h | 1 + include/semihosting/syscalls.h | 2 + semihosting/arm-compat-semi.c | 2 +- semihosting/syscalls.c | 27 ++++ target/hexagon/hexswi.c | 253 +++++++++++++++++++++++++++++- 5 files changed, 281 insertions(+), 4 deletions(-) diff --git a/include/semihosting/common-semi.h b/include/semihosting/common-semi.h index 0a91db7c4149..58dfb99d7a5b 100644 --- a/include/semihosting/common-semi.h +++ b/include/semihosting/common-semi.h @@ -34,6 +34,7 @@ #ifndef COMMON_SEMI_H #define COMMON_SEMI_H +void common_semi_cb(CPUState *cs, uint64_t ret, int err); void do_common_semihosting(CPUState *cs); #endif /* COMMON_SEMI_H */ diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 6627c45fb281..dec2ee0ad4ac 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -75,4 +75,6 @@ void semihost_sys_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_poll_one(CPUState *cs, gdb_syscall_complete_cb complete, int fd, GIOCondition cond, int timeout); +void semihost_sys_ftruncate(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, off_t len); #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 0990dd704ee0..e4825a866718 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -243,7 +243,7 @@ static inline uint32_t get_swi_errno(CPUState *cs) #endif } -static void common_semi_cb(CPUState *cs, uint64_t ret, int err) +void common_semi_cb(CPUState *cs, uint64_t ret, int err) { if (err) { #ifdef CONFIG_USER_ONLY diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index ef717912a866..e790c79efe85 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -13,6 +13,7 @@ #include "semihosting/guestfd.h" #include "semihosting/syscalls.h" #include "semihosting/console.h" +#include "semihosting/semihost.h" #ifdef CONFIG_USER_ONLY #include "qemu.h" #else @@ -551,6 +552,13 @@ static void host_poll_one(CPUState *cs, gdb_syscall_complete_cb complete, } #endif +static void host_ftruncate(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, off_t len) +{ + int err = ftruncate(gf->hostfd, len); + complete(cs, err, err < 0 ? errno : 0); +} + /* * Static file semihosting syscall implementations. */ @@ -992,3 +1000,22 @@ void semihost_sys_poll_one(CPUState *cs, gdb_syscall_complete_cb complete, } } #endif + +void semihost_sys_ftruncate(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, off_t len) +{ + GuestFD *gf = get_guestfd(fd); + if (!gf) { + complete(cs, -1, EBADF); + return; + } + + switch (gf->type) { + case GuestFDHost: + host_ftruncate(cs, complete, gf, len); + break; + default: + fprintf(stderr, "ftruncate call not implemented for this semihosting mode.\n"); + g_assert_not_reached(); + } +} diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c index daa9f965145e..b67996eb7aef 100644 --- a/target/hexagon/hexswi.c +++ b/target/hexagon/hexswi.c @@ -23,23 +23,270 @@ #include "hex_mmu.h" #include "hexswi.h" #include "semihosting/common-semi.h" +#include "semihosting/syscalls.h" +#include "semihosting/guestfd.h" #endif #ifndef CONFIG_USER_ONLY -#define HEX_SYS_EXCEPTION 0x18 +/* non-arm-compatible semihosting calls */ +#define HEXAGON_SPECIFIC_SWI_FLAGS \ + DEF_SWI_FLAG(EXCEPTION, 0x18) \ + DEF_SWI_FLAG(READ_CYCLES, 0x40) \ + DEF_SWI_FLAG(PROF_ON, 0x41) \ + DEF_SWI_FLAG(PROF_OFF, 0x42) \ + DEF_SWI_FLAG(WRITECREG, 0x43) \ + DEF_SWI_FLAG(READ_TCYCLES, 0x44) \ + DEF_SWI_FLAG(READ_ICOUNT, 0x47) \ + DEF_SWI_FLAG(PROF_STATSRESET, 0x48) \ + DEF_SWI_FLAG(DUMP_PMU_STATS, 0x4a) \ + DEF_SWI_FLAG(READ_PCYCLES, 0x52) \ + DEF_SWI_FLAG(FTELL, 0x100) \ + DEF_SWI_FLAG(FSTAT, 0x101) \ + DEF_SWI_FLAG(STAT, 0x103) \ + DEF_SWI_FLAG(GETCWD, 0x104) \ + DEF_SWI_FLAG(ACCESS, 0x105) \ + DEF_SWI_FLAG(EXEC, 0x185) \ + DEF_SWI_FLAG(FTRUNC, 0x186) + +#define DEF_SWI_FLAG(name, val) HEX_SYS_ ##name = val, +enum hex_swi_flag { + HEXAGON_SPECIFIC_SWI_FLAGS +}; +#undef DEF_SWI_FLAG + +#define DEF_SWI_FLAG(_, val) case val: +static inline bool is_hexagon_specific_swi_flag(enum hex_swi_flag what_swi) +{ + switch (what_swi) { + HEXAGON_SPECIFIC_SWI_FLAGS + return true; + } + return false; +} +#undef DEF_SWI_FLAG + +/* We start from 1 as 0 is used to signal an error from opendir() */ +static const int DIR_INDEX_OFFSET = 1; + +static void common_semi_ftell_cb(CPUState *cs, uint64_t ret, int err) +{ + if (err) { + ret = -1; + } + common_semi_cb(cs, ret, err); +} static void sim_handle_trap0(CPUHexagonState *env) { g_assert(bql_locked()); target_ulong what_swi = arch_get_thread_reg(env, HEX_REG_R00); + target_ulong swi_info = arch_get_thread_reg(env, HEX_REG_R01); + uintptr_t retaddr = 0; + CPUState *cs = env_cpu(env); + + if (!is_hexagon_specific_swi_flag(what_swi)) { + do_common_semihosting(cs); + return; + } + + switch (what_swi) { - if (what_swi == HEX_SYS_EXCEPTION) { + case HEX_SYS_EXCEPTION: arch_set_system_reg(env, HEX_SREG_MODECTL, 0); exit(arch_get_thread_reg(env, HEX_REG_R02)); + break; + + case HEX_SYS_WRITECREG: + fprintf(stdout, "%c", swi_info); + fflush(stdout); + common_semi_cb(cs, 0, 0); + break; + + case HEX_SYS_STAT: + case HEX_SYS_FSTAT: + { + /* + * This must match the caller's definition, it would be in the + * caller's angel.h or equivalent header. + */ + struct __SYS_STAT { + uint64_t dev; + uint64_t ino; + uint32_t mode; + uint32_t nlink; + uint64_t rdev; + uint32_t size; + uint32_t __pad1; + uint32_t atime; + uint32_t mtime; + uint32_t ctime; + uint32_t __pad2; + } sys_stat; + struct stat st_buf; + uint8_t *st_bufptr = (uint8_t *)&sys_stat; + int rc, err = 0; + char filename[BUFSIZ]; + target_ulong physicalFilenameAddr; + target_ulong statBufferAddr; + hexagon_read_memory(env, swi_info, 4, &physicalFilenameAddr, retaddr); + + if (what_swi == HEX_SYS_STAT) { + int i = 0; + do { + hexagon_read_memory(env, physicalFilenameAddr + i, 1, + &filename[i], retaddr); + i++; + } while ((i < BUFSIZ) && filename[i - 1]); + rc = stat(filename, &st_buf); + err = errno; + } else{ + int fd = physicalFilenameAddr; + GuestFD *gf = get_guestfd(fd); + if (gf->type != GuestFDHost) { + fprintf(stderr, "fstat semihosting only implemented for native mode.\n"); + g_assert_not_reached(); + } + rc = fstat(gf->hostfd, &st_buf); + err = errno; + } + if (rc == 0) { + sys_stat.dev = st_buf.st_dev; + sys_stat.ino = st_buf.st_ino; + sys_stat.mode = st_buf.st_mode; + sys_stat.nlink = (uint32_t) st_buf.st_nlink; + sys_stat.rdev = st_buf.st_rdev; + sys_stat.size = (uint32_t) st_buf.st_size; +#if defined(__linux__) + sys_stat.atime = (uint32_t) st_buf.st_atim.tv_sec; + sys_stat.mtime = (uint32_t) st_buf.st_mtim.tv_sec; + sys_stat.ctime = (uint32_t) st_buf.st_ctim.tv_sec; +#elif defined(_WIN32) + sys_stat.atime = st_buf.st_atime; + sys_stat.mtime = st_buf.st_mtime; + sys_stat.ctime = st_buf.st_ctime; +#endif + } + hexagon_read_memory(env, swi_info + 4, 4, &statBufferAddr, retaddr); + + for (int i = 0; i < sizeof(sys_stat); i++) { + hexagon_write_memory(env, statBufferAddr + i, 1, st_bufptr[i], + retaddr); + } + common_semi_cb(cs, rc, err); + } + break; + + case HEX_SYS_FTRUNC: + { + int fd; + off_t size_limit; + hexagon_read_memory(env, swi_info, 4, &fd, retaddr); + hexagon_read_memory(env, swi_info + 4, 8, &size_limit, retaddr); + semihost_sys_ftruncate(cs, common_semi_cb, fd, size_limit); } + break; - do_common_semihosting(cs); + case HEX_SYS_ACCESS: + { + char filename[BUFSIZ]; + uint32_t FileNameAddr; + uint32_t BufferMode; + int rc, err = 0; + + int i = 0; + + hexagon_read_memory(env, swi_info, 4, &FileNameAddr, retaddr); + do { + hexagon_read_memory(env, FileNameAddr + i, 1, &filename[i], retaddr); + i++; + } while ((i < BUFSIZ) && (filename[i - 1])); + filename[i] = 0; + + hexagon_read_memory(env, swi_info + 4, 4, &BufferMode, retaddr); + + rc = access(filename, BufferMode); + if (rc != 0) { + err = errno; + } + common_semi_cb(cs, rc, err); + } + break; + + case HEX_SYS_GETCWD: + { + char cwdPtr[PATH_MAX]; + uint32_t BufferAddr; + uint32_t BufferSize; + uint32_t rc = 0, err = 0; + + hexagon_read_memory(env, swi_info, 4, &BufferAddr, retaddr); + hexagon_read_memory(env, swi_info + 4, 4, &BufferSize, retaddr); + + if (!getcwd(cwdPtr, PATH_MAX)) { + err = errno; + } else { + size_t cwd_size = strlen(cwdPtr); + if (cwd_size > BufferSize) { + err = ERANGE; + } else { + for (int i = 0; i < cwd_size; i++) { + hexagon_write_memory(env, BufferAddr + i, 1, + (uint64_t)cwdPtr[i], retaddr); + } + rc = BufferAddr; + } + } + common_semi_cb(cs, rc, err); + break; + } + + case HEX_SYS_EXEC: + { + qemu_log_mask(LOG_UNIMP, "SYS_EXEC is deprecated\n"); + common_semi_cb(cs, -1, ENOSYS); + } + break; + + case HEX_SYS_FTELL: + { + int fd; + hexagon_read_memory(env, swi_info, 4, &fd, retaddr); + semihost_sys_lseek(cs, common_semi_ftell_cb, fd, 0, GDB_SEEK_CUR); + } + break; + + case HEX_SYS_READ_CYCLES: + case HEX_SYS_READ_TCYCLES: + case HEX_SYS_READ_ICOUNT: + { + arch_set_thread_reg(env, HEX_REG_R00, 0); + arch_set_thread_reg(env, HEX_REG_R01, 0); + break; + } + + case HEX_SYS_READ_PCYCLES: + { + arch_set_thread_reg(env, HEX_REG_R00, + arch_get_system_reg(env, HEX_SREG_PCYCLELO)); + arch_set_thread_reg(env, HEX_REG_R01, + arch_get_system_reg(env, HEX_SREG_PCYCLEHI)); + break; + } + + case HEX_SYS_PROF_ON: + case HEX_SYS_PROF_OFF: + case HEX_SYS_PROF_STATSRESET: + case HEX_SYS_DUMP_PMU_STATS: + common_semi_cb(cs, -1, ENOSYS); + qemu_log_mask(LOG_UNIMP, "SWI call %x is unimplemented in QEMU\n", + what_swi); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "error: unknown swi call 0x%x\n", what_swi); + cpu_abort(cs, "Hexagon Unsupported swi call 0x%x\n", what_swi); + } } static void set_addresses(CPUHexagonState *env, target_ulong pc_offset, From 0c2c80988f6ccf4b505b47520482e7e03cdca373 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 07:30:53 -0800 Subject: [PATCH 1156/1179] Hexagon: add COREDUMP semihosting operation Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/cpu.c | 2 +- target/hexagon/hexswi.c | 131 ++++++++++++++++++++++++++++++++++++++ target/hexagon/internal.h | 1 + 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 42a1af3009ab..40e3c9d0f70f 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -228,7 +228,7 @@ void hexagon_debug_qreg(CPUHexagonState *env, int regnum) print_qreg(stdout, env, regnum, false); } -static void hexagon_dump(CPUHexagonState *env, FILE *f, int flags) +void hexagon_dump(CPUHexagonState *env, FILE *f, int flags) { HexagonCPU *cpu = env_archcpu(env); diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c index b67996eb7aef..611725e6b165 100644 --- a/target/hexagon/hexswi.c +++ b/target/hexagon/hexswi.c @@ -41,6 +41,7 @@ DEF_SWI_FLAG(PROF_STATSRESET, 0x48) \ DEF_SWI_FLAG(DUMP_PMU_STATS, 0x4a) \ DEF_SWI_FLAG(READ_PCYCLES, 0x52) \ + DEF_SWI_FLAG(COREDUMP, 0xCD) \ DEF_SWI_FLAG(FTELL, 0x100) \ DEF_SWI_FLAG(FSTAT, 0x101) \ DEF_SWI_FLAG(STAT, 0x103) \ @@ -77,6 +78,132 @@ static void common_semi_ftell_cb(CPUState *cs, uint64_t ret, int err) common_semi_cb(cs, ret, err); } +static void coredump(CPUHexagonState *env) +{ + uint32_t ssr = arch_get_system_reg(env, HEX_SREG_SSR); + printf("CRASH!\n"); + printf("I think the exception was: "); + switch (GET_SSR_FIELD(SSR_CAUSE, ssr)) { + case 0x43: + printf("0x43, NMI"); + break; + case 0x42: + printf("0x42, Data abort"); + break; + case 0x44: + printf("0x44, Multi TLB match"); + break; + case HEX_CAUSE_BIU_PRECISE: + printf("0x%x, Bus Error (Precise BIU error)", + HEX_CAUSE_BIU_PRECISE); + break; + case HEX_CAUSE_DOUBLE_EXCEPT: + printf("0x%x, Exception observed when EX = 1 (double exception)", + HEX_CAUSE_DOUBLE_EXCEPT); + break; + case HEX_CAUSE_FETCH_NO_XPAGE: + printf("0x%x, Privilege violation: User/Guest mode execute" + " to page with no execute permissions", + HEX_CAUSE_FETCH_NO_XPAGE); + break; + case HEX_CAUSE_FETCH_NO_UPAGE: + printf("0x%x, Privilege violation: " + "User mode exececute to page with no user permissions", + HEX_CAUSE_FETCH_NO_UPAGE); + break; + case HEX_CAUSE_INVALID_PACKET: + printf("0x%x, Invalid packet", + HEX_CAUSE_INVALID_PACKET); + break; + case HEX_CAUSE_PRIV_USER_NO_GINSN: + printf("0x%x, Privilege violation: guest mode insn in user mode", + HEX_CAUSE_PRIV_USER_NO_GINSN); + break; + case HEX_CAUSE_PRIV_USER_NO_SINSN: + printf("0x%x, Privilege violation: " + "monitor mode insn ins user/guest mode", + HEX_CAUSE_PRIV_USER_NO_SINSN); + break; + case HEX_CAUSE_REG_WRITE_CONFLICT: + printf("0x%x, Multiple writes to same register", + HEX_CAUSE_REG_WRITE_CONFLICT); + break; + case HEX_CAUSE_PC_NOT_ALIGNED: + printf("0x%x, PC not aligned", + HEX_CAUSE_PC_NOT_ALIGNED); + break; + case HEX_CAUSE_MISALIGNED_LOAD: + printf("0x%x, Misaligned Load @ 0x%x", + HEX_CAUSE_MISALIGNED_LOAD, + arch_get_system_reg(env, HEX_SREG_BADVA)); + break; + case HEX_CAUSE_MISALIGNED_STORE: + printf("0x%x, Misaligned Store @ 0x%x", + HEX_CAUSE_MISALIGNED_STORE, + arch_get_system_reg(env, HEX_SREG_BADVA)); + break; + case HEX_CAUSE_PRIV_NO_READ: + printf("0x%x, Privilege violation: " + "user/guest read permission @ 0x%x", + HEX_CAUSE_PRIV_NO_READ, + arch_get_system_reg(env, HEX_SREG_BADVA)); + break; + case HEX_CAUSE_PRIV_NO_WRITE: + printf("0x%x, Privilege violation: " + "user/guest write permission @ 0x%x", + HEX_CAUSE_PRIV_NO_WRITE, + arch_get_system_reg(env, HEX_SREG_BADVA)); + break; + case HEX_CAUSE_PRIV_NO_UREAD: + printf("0x%x, Privilege violation: user read permission @ 0x%x", + HEX_CAUSE_PRIV_NO_UREAD, + arch_get_system_reg(env, HEX_SREG_BADVA)); + break; + case HEX_CAUSE_PRIV_NO_UWRITE: + printf("0x%x, Privilege violation: user write permission @ 0x%x", + HEX_CAUSE_PRIV_NO_UWRITE, + arch_get_system_reg(env, HEX_SREG_BADVA)); + break; + case HEX_CAUSE_COPROC_LDST: + printf("0x%x, Coprocessor VMEM address error. @ 0x%x", + HEX_CAUSE_COPROC_LDST, + arch_get_system_reg(env, HEX_SREG_BADVA)); + break; + case HEX_CAUSE_STACK_LIMIT: + printf("0x%x, Stack limit check error", HEX_CAUSE_STACK_LIMIT); + break; + case HEX_CAUSE_FPTRAP_CAUSE_BADFLOAT: + printf("0x%X, Floating-Point: Execution of Floating-Point " + "instruction resulted in exception", + HEX_CAUSE_FPTRAP_CAUSE_BADFLOAT); + break; + case HEX_CAUSE_NO_COPROC_ENABLE: + printf("0x%x, Illegal Execution of Coprocessor Instruction", + HEX_CAUSE_NO_COPROC_ENABLE); + break; + case HEX_CAUSE_NO_COPROC2_ENABLE: + printf("0x%x, " + "Illegal Execution of Secondary Coprocessor Instruction", + HEX_CAUSE_NO_COPROC2_ENABLE); + break; + case HEX_CAUSE_UNSUPORTED_HVX_64B: + printf("0x%x, " + "Unsuported Execution of Coprocessor Instruction with 64bits Mode On", + HEX_CAUSE_UNSUPORTED_HVX_64B); + break; + case HEX_CAUSE_VWCTRL_WINDOW_MISS: + printf("0x%x, " + "Thread accessing a region outside VWCTRL window", + HEX_CAUSE_VWCTRL_WINDOW_MISS); + break; + default: + printf("Don't know"); + break; + } + printf("\nRegister Dump:\n"); + hexagon_dump(env, stdout, 0); +} + static void sim_handle_trap0(CPUHexagonState *env) { g_assert(bql_locked()); @@ -248,6 +375,10 @@ static void sim_handle_trap0(CPUHexagonState *env) } break; + case HEX_SYS_COREDUMP: + coredump(env); + break; + case HEX_SYS_FTELL: { int fd; diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h index fd2397b9ef0e..ff89c9cda43f 100644 --- a/target/hexagon/internal.h +++ b/target/hexagon/internal.h @@ -32,6 +32,7 @@ int hexagon_hvx_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n); void hexagon_debug_vreg(CPUHexagonState *env, int regnum); void hexagon_debug_qreg(CPUHexagonState *env, int regnum); void hexagon_debug(CPUHexagonState *env); +void hexagon_dump(CPUHexagonState *env, FILE *f, int flags); extern const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS]; extern const char * const hexagon_sregnames[]; From 4af457c08c8f87e7280a93e4fa9f270762140f30 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 07:31:58 -0800 Subject: [PATCH 1157/1179] Hexagon: add {OPEN|READ|CLOSE}_DIR semihosting operations Signed-off-by: Matheus Tavares Bernardino --- hw/hexagon/hexagon_dsp.c | 2 ++ target/hexagon/cpu.h | 1 + target/hexagon/hexswi.c | 78 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c index eca310c0e345..348977a542fa 100644 --- a/hw/hexagon/hexagon_dsp.c +++ b/hw/hexagon/hexagon_dsp.c @@ -112,6 +112,7 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev, for (int i = 0; i < machine->smp.cpus; i++) { HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type)); + CPUHexagonState *env = &cpu->env; qemu_register_reset(do_cpu_reset, cpu); /* @@ -147,6 +148,7 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev, sysbus_mmio_map(SYS_BUS_DEVICE(l2vic_dev), 1, m_cfg->cfgtable.fastl2vic_base << 16); } else if (!qdev_realize_and_unref(DEVICE(cpu), NULL, errp)) { + env->dir_list = NULL; return; } diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 50265da40dc9..ea618802a929 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -143,6 +143,7 @@ typedef struct CPUArchState { target_ulong tlb_lock_count; target_ulong k0_lock_count; CPUHexagonTLBContext *hex_tlb; + GList *dir_list; #endif target_ulong next_PC; target_ulong new_value_usr; diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c index 611725e6b165..a08d7f68917c 100644 --- a/target/hexagon/hexswi.c +++ b/target/hexagon/hexswi.c @@ -47,6 +47,9 @@ DEF_SWI_FLAG(STAT, 0x103) \ DEF_SWI_FLAG(GETCWD, 0x104) \ DEF_SWI_FLAG(ACCESS, 0x105) \ + DEF_SWI_FLAG(OPENDIR, 0x180) \ + DEF_SWI_FLAG(CLOSEDIR, 0x181) \ + DEF_SWI_FLAG(READDIR, 0x182) \ DEF_SWI_FLAG(EXEC, 0x185) \ DEF_SWI_FLAG(FTRUNC, 0x186) @@ -375,6 +378,81 @@ static void sim_handle_trap0(CPUHexagonState *env) } break; + case HEX_SYS_OPENDIR: + { + DIR *dir; + char buf[BUFSIZ]; + int rc = 0, err = 0; + + int i = 0; + do { + hexagon_read_memory(env, swi_info + i, 1, &buf[i], retaddr); + i++; + } while (buf[i - 1]); + + dir = opendir(buf); + if (dir != NULL) { + env->dir_list = g_list_append(env->dir_list, dir); + rc = g_list_index(env->dir_list, dir) + DIR_INDEX_OFFSET; + } else { + err = errno; + } + common_semi_cb(cs, rc, err); + break; + } + + case HEX_SYS_READDIR: + { + struct dirent *host_dir_entry = NULL; + int dir_index = swi_info - DIR_INDEX_OFFSET; + DIR *dir = g_list_nth_data(env->dir_list, dir_index); + uint32_t rc = 0, err = 0; + + if (dir) { + errno = 0; + host_dir_entry = readdir(dir); + if (host_dir_entry == NULL) { + err = errno; + } + } else { + err = EBADF; + } + + if (host_dir_entry) { + uint32_t guest_dir_entry = arch_get_thread_reg(env, HEX_REG_R02); + hexagon_write_memory(env, guest_dir_entry, 4, host_dir_entry->d_ino, + retaddr); + for (int i = 0; i < sizeof(host_dir_entry->d_name); i++) { + hexagon_write_memory(env, guest_dir_entry + 4 + i, 1, + host_dir_entry->d_name[i], retaddr); + if (!host_dir_entry->d_name[i]) { + break; + } + } + rc = guest_dir_entry; + } + common_semi_cb(cs, rc, err); + break; + } + + case HEX_SYS_CLOSEDIR: + { + DIR *dir; + int ret = 0, err = 0; + + dir = g_list_nth_data(env->dir_list, swi_info); + if (dir != NULL) { + ret = closedir(dir); + if (ret != 0) { + err = errno; + } + } else { + err = EBADF; + } + common_semi_cb(cs, ret, err); + break; + } + case HEX_SYS_COREDUMP: coredump(env); break; From 060b9cbcd1741bc8236a0e102fe67a8ed4d6d8c9 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 29 Jan 2025 07:39:10 -0800 Subject: [PATCH 1158/1179] Hexagon: add semihosting check-tcg test This also adds the a minimal crt0/libc for hexagon, allowing us to build and run standalone system emulation tests in the future. Signed-off-by: Matheus Tavares Bernardino --- tests/tcg/hexagon/Makefile.softmmu-target | 71 + tests/tcg/hexagon/system/crt0/crt0.S | 103 ++ tests/tcg/hexagon/system/crt0/crt0.inc | 25 + .../tcg/hexagon/system/crt0/crt0_standalone.S | 1206 +++++++++++++++++ .../hexagon/system/crt0/hexagon_standalone.h | 103 ++ tests/tcg/hexagon/system/crt0/min_libc.c | 359 +++++ tests/tcg/hexagon/system/crt0/pte.S | 80 ++ tests/tcg/hexagon/system/crt0/tlb.c | 198 +++ tests/tcg/hexagon/system/semihost.c | 297 ++++ tests/tcg/hexagon/system/strutils.h | 25 + 10 files changed, 2467 insertions(+) create mode 100644 tests/tcg/hexagon/Makefile.softmmu-target create mode 100644 tests/tcg/hexagon/system/crt0/crt0.S create mode 100755 tests/tcg/hexagon/system/crt0/crt0.inc create mode 100644 tests/tcg/hexagon/system/crt0/crt0_standalone.S create mode 100644 tests/tcg/hexagon/system/crt0/hexagon_standalone.h create mode 100644 tests/tcg/hexagon/system/crt0/min_libc.c create mode 100644 tests/tcg/hexagon/system/crt0/pte.S create mode 100644 tests/tcg/hexagon/system/crt0/tlb.c create mode 100644 tests/tcg/hexagon/system/semihost.c create mode 100644 tests/tcg/hexagon/system/strutils.h diff --git a/tests/tcg/hexagon/Makefile.softmmu-target b/tests/tcg/hexagon/Makefile.softmmu-target new file mode 100644 index 000000000000..f965f4f4fac6 --- /dev/null +++ b/tests/tcg/hexagon/Makefile.softmmu-target @@ -0,0 +1,71 @@ +## +## Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. +## +## SPDX-License-Identifier: GPL-2.0-or-later +## + +# -*- Mode: makefile -*- +# +# Hexagon SoftMMU tests - included from tests/tcg/Makefile +# + +HEXAGON_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/hexagon/system + +# Set search path for all sources +VPATH += $(HEXAGON_SYSTEM_SRC) + +########### Compiling options +# We force -O0 to avoid optimizations that would break the +# libc simplifications we made at min_libc.c +# +CFLAGS=-mv73 -U__linux__ -G0 -nodefaultlibs -nostdlib -static -fno-PIC -O0 -g -Werror +LDFLAGS=-lclang_rt.builtins-hexagon + +########### QEMU options +QEMU_BASE_MACHINE=-M V66G_1024 -semihosting-config usefs=$(SRC_PATH)/tests/tcg/hexagon/system +QEMU_OPTS+=-display none + +QEMU_OPTS+=$(QEMU_BASE_MACHINE) -kernel + +crt0.o: crt0/crt0.S crt0/crt0.inc +crt0_standalone.o: crt0/crt0_standalone.S crt0/crt0.inc +pte.o: crt0/pte.S +min_libc.o: crt0/min_libc.c +tlb.o: crt0/tlb.c + +CRT0_OBJS=crt0.o crt0_standalone.o pte.o min_libc.o tlb.o + +TESTS += \ + semihost \ + $() + +$(TESTS): $(CRT0_OBJS) + +# Build and link the tests +echo-and-run = echo $(1) && $(1) +define build_fn + @if test "$(3)" = LINK; then extra="$(LDFLAGS)"; else extra=-c; fi && \ + $(call echo-and-run, $(CC) $(CFLAGS) $(1) -o $(2) $$extra) +endef + +$(CRT0_OBJS): + $(call build_fn,$<,$@) +$(TESTS): + $(call build_fn,$^,$@,LINK) + +%.o: %.S + $(call build_fn,$<,$@) +%.o: %.c + $(call build_fn,$<,$@) + +semihost.o: semihost.c strutils.h +semihost: semihost.o + +############# Custom test rules + +run-semihost: semihost + mkdir -p _semihost_dir + touch _semihost_dir/fileA _semihost_dir/fileB + $(call run-test, $<, $(QEMU) --append "arg1 arg2" $(QEMU_OPTS) $< \ + > $<.stdout) + $(call quiet-command, grep -q "PASS" $<.stdout, "GREP", "PASS") diff --git a/tests/tcg/hexagon/system/crt0/crt0.S b/tests/tcg/hexagon/system/crt0/crt0.S new file mode 100644 index 000000000000..8a40e39536eb --- /dev/null +++ b/tests/tcg/hexagon/system/crt0/crt0.S @@ -0,0 +1,103 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "crt0.inc" + .equ DEFAULT_HEAP_SIZE, 0x4000000 /* 64MB */ + .equ DEFAULT_STACK_SIZE, 0x100000 /* 1MB */ + + .section .start, "ax", @progbits + .subsection 0 + .org 0 + + .global _start + .type _start, @function + .p2align 5 +_start: + jump hexagon_start_init + jump hexagon_start_main + .size _start, . - _start + +/*----------------------------------------------------------------------------*/ + + .global hexagon_pre_main + .type hexagon_pre_main, @function + +hexagon_pre_main: + /* Mark first stack frame. */ + fp = #0 + + ReadFrom heapBase, r4 + + AddrOf DEFAULT_HEAP_SIZE + r5 = r0 + + r5 = add (r4, r5) /* Calculate aligned heap top. */ + r5 = add (r5, #15) + r5 = and (r5, #-16) + WriteTo heapLimit, r5 + + /* Set up stack. */ + AddrOf DEFAULT_STACK_SIZE + r7 = r0 + + r6 = add (r5, r7) /* Assume stack after heap. */ + r6 = and (r6, #-16) + + WriteTo stackBase, r6 + + ReadFrom stackBase, r6 + + r7 = sub (r6, r7) /* Desired stack size. */ + r7 = add (r7, #15) + r7 = and (r7, #-16) + WriteTo stackLimit, r7 + + /* Set stack up. */ + ReadFrom stackBase, r0 + sp = and (r0, #-16) /* Align top of stack. */ + + /* Zero up BSS. */ + AddrOf __bss_start, r0 + AddrOf _end, r2 + AddrOf memset, r28 /* bzero () is deprecated. */ + { r1 = #0 + r2 = sub (r2, r0) + callr r28 } + .size hexagon_pre_main, . - hexagon_pre_main + +/*----------------------------------------------------------------------------*/ + + .global hexagon_start_main + .type hexagon_start_main, @function +hexagon_start_main: + AddrOf _start_main, r28 + callr r28 + /*Stop all threads to terminate execution */ + r0 = #0x3f + stop (r0) + .size hexagon_start_main, . - hexagon_start_main + +/*----------------------------------------------------------------------------*/ + + .data + .global heapBase + .global heapLimit + .global stackBase + .global stackLimit + .global setHeapAngelCallParams + +.HeapParams: +heapBase: + .word end /* Provided by the linker script. */ +heapLimit: + .word end + (DEFAULT_HEAP_SIZE & -16) +stackBase: + .word 0 +stackLimit: + .word end + ((DEFAULT_HEAP_SIZE + 15) & -16) + +setHeapAngelCallParams: + .word .HeapParams diff --git a/tests/tcg/hexagon/system/crt0/crt0.inc b/tests/tcg/hexagon/system/crt0/crt0.inc new file mode 100755 index 000000000000..a28d68c51cd5 --- /dev/null +++ b/tests/tcg/hexagon/system/crt0/crt0.inc @@ -0,0 +1,25 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + .macro AddrOf Var, To = r0 + \To\() = ## (\Var) + .endm + + .macro ReadFrom Var, To = r0 + AddrOf \Var, \To + \To = memw (\To) + .endm + + .macro WriteTo Var, From = r0, Ptr = r1 + .ifnc "\From", "\Ptr" + AddrOf \Var, \Ptr + memw (\Ptr) = \From + \From = memw (\Ptr) + .else + .print "Macro arguments \"From\" and \"Ptr\" cannot be the same." + .err + .endif + .endm diff --git a/tests/tcg/hexagon/system/crt0/crt0_standalone.S b/tests/tcg/hexagon/system/crt0/crt0_standalone.S new file mode 100644 index 000000000000..a3ca6ea95da2 --- /dev/null +++ b/tests/tcg/hexagon/system/crt0/crt0_standalone.S @@ -0,0 +1,1206 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "crt0.inc" + .equ TLB_FIXED_ENTRIES, 6 + + .org 0x20 /* This must be at address 0x20 */ +EventVectorBase: + .word .EventVectors + +/* This can vary based on the revid of the part: + 64, 128, 192. Most are 128 */ +_NumTLBEntries: + .word 127 + +TLBMapTable: + .word UPTE_START + +CoreDump: + .word RegDump + + .subsection 0 + + /* Make sure that data and code don't end up in the same L2 cache-line. */ + .p2align 6, 0 + + .global hexagon_start_init + .type hexagon_start_init, @function +hexagon_start_init: +.Init: + /* Clean up house (make sure that R0 is initialized before DCKILL). */ + dckill + isync + ickill + isync + +.InitSSR: + /* SFD = 0, IE = 0, UM = 0, EX = 0, ASID = 0 */ + r0 = #0 + ssr = r0 + isync + + /* Setup events */ +.InitVector: + ReadFrom EventVectorBase + evb = r0 + +.InitStack: + ReadFrom exc_stack_tops + sgp0 = r0 + +.InitFramekey: + r0 = #0 + framekey = r0 + + /* Configure cycle counter. */ +.InitPcycle: + r1 = #1 + r0 = syscfg + r0 = insert (r1, #1, #6) + syscfg = r0 + + /* Configure IMT/DMT. */ +.InitDMT: + r1 = #1 + r0 = syscfg + r0 = insert (r1, #1, #15) + syscfg = r0 +.InitQoS: + r1 = #1 + r0 = syscfg + r0 = insert (r1, #1, #13) + syscfg = r0 +1: +.InitXE: + r1 = #1 + r0 = ssr + r0 = insert (r1, #1, #31) + ssr = r0 + + //{ 0x4066, 0x4, 0x7F, 0, 4 }, // v66a_512 + { + r0 = #0x2c // JTLB size + r2 = cfgbase + } + r1 = asl(r2, #5) + r0 = memw_phys(r0, r1) + { + r0 = add(r0, #-1); + memw(##_tlbmax) = r0.new + } + + { + r0 = #0x40 // L2 Tag size + r2 = cfgbase + } + r0 = memw_phys(r0, r1) + r1 = #0; + p0 = cmp.eq(r0, #0x400) + { + if (p0) r1 = #5 + if (p0) jump 1f + } + p0 = cmp.eq(r0, #0x200) + { + if (p0) r1 = #4 + if (p0) jump 1f + } + p0 = cmp.eq(r0, #0x100) + { + if (p0) r1 = #3 + if (p0) jump 1f + } + p0 = cmp.eq(r0, #0x080) + { + if (p0) r1 = #2 + if (p0) jump 1f + } +1: + memw(##_l2cfg) = r1 + +/* L2 config sequence: + * 1 - Disable prefetching by clearing HFd/i bits in ssr/ccr + */ + r0 = ccr + r3 = #0 + r0 = insert (r3, #4, #16) /* Clear HFi, HFd, HFiL2 HFdL2 bits */ + ccr = r0 + + /* Configure L2 cache. */ + r0 = syscfg + r0 = insert (r3, #3, #16) /* Set L2 size to 0 via L2CFG. */ + + +/* L2 config sequence: + * 2 - execute an isync which is aligned to a 32byte boundary. + */ + .p2alignl 5, 0x7f00c000 + isync + +/* L2 config sequence: + * 3 - execute an syncht insn to insure there are no outstanding + * memory transactions. + */ + syncht + +/* L2 config sequence: + * 4 - Set the desired L2 size for < V4 (set to 0 for >= V4). + */ + syscfg = r0 + isync + +/* L2 config sequence: + * 5 - Execute the L2KILL insn to initiate the cache. + */ + l2kill + syncht + +/* L2 config sequence: + * 6 - Set the desired L2 size. + */ + r2 = memw(##_l2cfg) + r3 = #0x5 + r3 = min (r2, r3) /* min between desired and hwmax */ + r0 = insert (r3, #4, #16) /* Set L2 size via L2CFG. */ + syscfg = r0 + isync + + /* Configure L1 caches. */ +.InitCache: + r1 = #0 + r1 = #1 + r2 = syscfg + r2 = insert (r1, #1, #1) + r2 = insert (r0, #1, #2) + + r1 = #1 + r2 = insert (r1, #1, #23) + + syscfg = r2 + isync + + /* BEGIN code to turn on translation */ +.InitTLB: + // V65 an later use a table for this stuff, should get a table for all of it! + r0 = memw(##_tlbmax) + + /* Clear TLB and store the number of TLBs */ + { + r3:2 = combine(#0,#0) + memw(##_NumTLBEntries) = r0 + } + + loop0(.InitTLBLoop, r0) +.falign +.InitTLBLoop: + tlbw(r3:2,r0) + r0 = add (r0, #-1) + {}:endloop0 + isync + +.InitTLBGlobal: /* Fixed entry for everything. */ + AddrOf _start, r2 + r2 = lsr (r2, #12) + + AddrOf 0xc3f00000, r1 /* Global, 1-1 mapping. */ + AddrOf 0xf7000000, r0 /* Full perms, fully cacheable WB */ + r1 = or (r1, r2) /* 1M translation */ + r0 |= asl (r2,#1) + r0 = setbit(r0,#4) + r0 = and(r0,#-16) + r2 = #0 + tlbw(r1:0,r3) + + /* TODO Should there be a TLB entry for TCM too? */ + + r0 = syscfg + r0 = setbit (r0, #0) /* Turn the MMU on. */ + syscfg = r0 + isync + +.InitInt: + /* Set up rising edge triggered interrupts */ + r0 = #0 + imask = r0 + r1 = #-1 + cswi (r1) + + /* Enable interrupts globally. */ + r0 = ssr + r0 = setbit (r0, #18) + ssr= r0 + + r0 = syscfg + r0 = setbit (r0, #4) + syscfg = r0 + isync + + /* Set up input params to Angel call */ + r0 = #22 + AddrOf setHeapAngelCallParams, r1 + trap0 (#0) + +.PreMain: + AddrOf hexagon_pre_main, r28 + jumpr r28 + .size hexagon_start_init, . - hexagon_start_init + +.global qdsp6_start_init +.set qdsp6_start_init, \ + hexagon_start_init + +/* (At this point the machine is mostly ready for normal execution */ + + /* This code is jumped to when we start a new thread. */ + /* It reads some values out of memory and uses them */ + /* to begin execution. */ + /* The code supports going to a function of the type: */ + /* void foo (void *arg); */ + /* or */ + /* void foo (int arg); */ + /* All we have to do is get the location of "foo", the */ + /* value for "arg", and set up the stack. */ + /* This stuff has been set up for us by thread_create, below.*/ + /* Under the OS, we have no need for this, it is merely for */ + /* trying multithreaded applications on the raw hardware. */ + + .p2align 4 + .weak thread_stop + .type thread_stop, @function +thread_stop: +{ + r0 = htid + r1 = #1 +} + r1 = lsl (r1, r0) + stop (r1) + + .p2align 4 + + .type event_handle_reset, @function + +event_handle_reset: + r1 = htid /* do not alter until final register initialization */ + + { + r28 = ##(start_pc) + r29 = ##(start_sp) + } + + r2 = #0 /* UM = 0 EX = 0 IE = 0 ASID = 0 */ + ssr = r2 + isync + imask = r2 + + r2 = ##(exc_stack_tops) + r2 = memw (r2+r1<<#2) + sgp0 = r2 + + /* Initialize GP to the start of the global data area. */ + //r2 = ##(_SDA_BASE_) + //gp = r2 + + r2.h = #4 + r2.l = #0 + ssr = r2 /* Turn on interrupts */ + + r3 = #1 + r2 = ssr + r2 = insert (r3, #1, #31) + ssr = r2 + + r2.h = #0x1 /* Enable cache fetching */ + usr = r2 + + r0 = #1 + r2 = #1 + r0 |= asl (r2, #1) + r2 = ccr + r2 = insert (r0, #2, #16) + /* Enable dcfetch and l2fetch. */ + r2 = setbit (r2, #20) + ccr = r2 + + isync + + { + r2 = ##framekey_tbl + r3 = ##stack_size + } + { + r2 = memw(r2+r1<<#2) /* load framekey from memory array */ + r3 = memw(r3+r1<<#2) /* load stack_size from memory array */ + } + { + framekey = r2 /* store into framekey register */ + r2 = memw (sp+r1<<#2) + } + r3 = sub(r2, r3) /* framelimt = sp-stack_size) */ + framelimit = r3 /* store into framelimit register */ + + { + r28 = memw (r28+r1<<#2) + sp = memw (sp+r1<<#2) + fp = #0 + } + + { + r0 = ##(start_param) + lr = ##(thread_stop) + } + fp = #0 + r1 = htid + r0 = memw (r0+r1<<#2) + + jump thread_start + + .size event_handle_reset, . - event_handle_reset + + .global __coredump + .type coredump, @function + .set __coredump, coredump +coredump: + r0 = ssr + r0 = clrbit (r0, #16) /* UM = 0 */ + r0 = clrbit (r0, #17) /* EX = 0 */ + ssr = r0 + isync + r0 = #0xCD + trap0 (#0) + r2 = #-1 + r0 = #-1 + stop (r0) + .size event_core_dump, . - event_core_dump + + .type event_handle_nmi, @function +event_handle_nmi: + r0 = #1 + stid = r0 + jump coredump + .size event_handle_nmi, . - event_handle_nmi + + .type event_handle_error, @function +event_handle_error: + r0 = #2 + stid = r0 + jump coredump + .size event_handle_error, . - event_handle_error + + .type event_handle_rsvd, @function +event_handle_rsvd: + r0.h = #0xdead + r0.l = #0xbeef + stid = r0 + jump coredump + .size event_handle_rsvd, . - event_handle_rsvd + + .global thread_start + .type thread_start, @function +thread_start: + jumpr r28 + .size thread_start, . - thread_start + + /* TLB HANDLING */ + /* There are a few strategies we have tried for TLB handling. */ + /* The first is just to map every page 1:1 for virtual:physical */ + /* This means we have nothing to look up but no flexibility */ + /* The strategy implemented here is to divide memory into */ + /* a bunch of 1MB pages. Each page is by default set to the */ + /* corresponding physical 1M page, but the translation (and the */ + /* cacheability) can be changed with the add_translation function*/ + /* below. */ + /* We have to keep the table in memory, and it's down in the data*/ + /* section. */ + /* The page at address 0 is always kept in the TLB. */ + /* You will run into problems if the data gets pushed out into */ + /* another page, because you don't have a translation for the */ + /* data you need to do the translation! */ + /* The solution is to put the translation table (and probably */ + /* the TLB fill code) in special section (s) that go near address 0 */ + /* You can set that up in the linker script. */ + /* TLB miss because of eXecution */ + /* See HEXAGON Architecture System-Level Spec for more information */ + + + + .subsection 0 + + .p2align 6 + .global event_handle_tlbmissx + .type event_handle_tlbmissx, @function + +event_handle_tlbmissx: + crswap (sp, sgp0) + sp = add (sp, #-64) + /* Save off state */ + { + memd (sp + #0) = r1:0 + memd (sp + #8) = r3:2 + } + { + memd (sp + #16) = r5:4 + memd (sp + #24) = r7:6 + } + { + memd (sp + #32) = r9:8 + r9 = p3:0 + } + r8 = ssr + r7 = elr + p1 = tstbit (r8, #0) + { + /* Calculate 4K page index */ + r7 = lsr (r7, #12) + /* Check for next page hit */ + if (!p1) jump 1f + r0 = ##(__tlb_idx) + } + r7 = add (r7, #1) +1: + { + r1 = memw(##_tlb_fixed_entries) /* First non-fixed entry. */ + r3 = memw(##_NumTLBEntries) + } + /* Atomically increment index */ + /* NEVER overwrite fixed entries */ +1: + r6 = memw_locked (r0) + { + r6 = add (r6, #1) + /* This was hard coded to p0 = cmp.ge(r6, #NUM_TLB_ENTRIES) + Now we are using 2 registers so switch to the equivalent + p0 = !cmp.gt(r3, r6) */ + p0 = !cmp.gt (r3, r6) + } + /* Will never store a number greater than + _NumTLBEntries in &__tlb_idx */ + r6 = mux (p0, r1, r6) + memw_locked (r0, p0) = r6 + if (!p0) jump 1b /* Retry, lost reservation. */ + + { + r7 = lsr (r7, #8) /* 1M page index */ + r3 = memw (##TLBMapTable) + } + r3 = addasl (r3, r7, #1) + { + r3 = memh (r3) + r7 = asl (r7, #8) /* VPN */ + } + r5 = extractu (r3, #12, #4) + { + r4 = extractu (r3, #4, #0) + r0 = #0x0010 /* 1M */ + r1 = #0 + } + { + r4 = asl (r4, #24) + r1.h = #0xc000 + r0.h = #0xf000 + } +1: + { + r1 = or (r1, r7) /* c000_0000 + VPN */ + r0 |= asl(r5,#9) /* f000_0000 + PPD */ + } + r0 = or (r0, r4) + /* Get Lock */ + tlblock + r5 = tlbp(r1) + p0 = tstbit (r5, #31) + if (!p0) jump 1f + + tlbw(r1:0,r6) + isync + +1: + tlbunlock + + p3:0 = r9 + { + r9:8 = memd (sp + #32) + r7:6 = memd (sp + #24) + } + { + r5:4 = memd (sp + #16) + r3:2 = memd (sp + #8) + } + { + r1:0 = memd (sp + #0) + sp = add (sp, #64) + } + crswap (sp, sgp0) + rte + + .size .event_handle_tlbmissx, . - event_handle_tlbmissx + + /* TLB Miss RW */ + /* Basically the same as TLB MissX, but we get */ + /* The address from BADVA instead of EVB... see the */ + /* HEXAGON Architecture System-level Spec for more details. */ + + .p2align 6 + + .global event_handle_tlbmissrw + .type event_handle_tlbmissrw, @function + +event_handle_tlbmissrw: + crswap (sp, sgp0) + sp = add (sp, #-64) + { + memd (sp + #0) = r1:0 + memd (sp + #8) = r3:2 + } + { + memd (sp + #16) = r5:4 + memd (sp + #24) = r7:6 + } + { + memd (sp + #32) = r9:8 + r8 = ssr + } + r7 = badva + r9 = p3:0 + { + r0 = ##__tlb_idx + r1 = memw(##_tlb_fixed_entries) + } + { + r7 = lsr (r7, #20) + r3 = memw(##_NumTLBEntries) /* 31, 63, 127, or 191 */ + } + /* Atomically increment index */ + /* NEVER overwrite entry 0 */ +1: + r6 = memw_locked (r0) + { + r6 = add (r6, #1) + /* This was hard coded to p0 = cmp.ge(r6, #NUM_TLB_ENTRIES) + Now we are using 2 registers so switch to the equivalent + p0 = !cmp.gt(r3, r6) */ + p0 = !cmp.gt (r3, r6) + } + /* Will never store a number greater than + _NumTLBEntries in &__tlb_idx */ + r6 = mux (p0, r1, r6) + memw_locked (r0, p0) = r6 + if (!p0) jump 1b /* Retry, lost reservation. */ + + r3 = memw (##TLBMapTable) + r3 = addasl (r3, r7, #1) + { + r3 = memh (r3) + r7 = asl (r7, #8) /* VPN */ + } + + r4 = extractu (r3, #4, #0) +.L_OK: + { + r5 = extractu (r3, #12, #4) + r0 = #0x0010 /* 1M */ + r1 = #0 + } + { + r4 = asl (r4, #24) + r1.h = #0xc000 + r0.h = #0xf000 + } +1: + { + r1 = or (r1, r7) /* R5: VPN | C000_0000 */ + r0 |= asl(r5,#9) /* R4: PPD | F000_0000 */ + } + r0 = or (r0, r4) + + tlblock + r5 = tlbp(r1) + p0 = tstbit (r5, #31) + if (!p0) jump 1f + + tlbw(r1:0,r6) + isync + jump 2f +1: + // If we take a miss around a user defined page they need to + // manually create another page or not touch the regions above + // and below their page within a 1M boundary. + r4 = memw(##_tlb_fixed_entries) + p0 = cmp.gt(r4, r5) // r4>r5 == r5 0k L2 cache */ + .byte 0x2 /* rev: 0x1xxx: 128K L2 -> 128k L2 cache */ + .byte 0x3 /* rev: 0x2xxx: 256K L2 -> 256k L2 cache */ + .byte 0x3 /* rev: 0x3xxx: Not valid at this time */ + .byte 0x4 /* rev: 0x4xxx: 512K L2 -> 512k L2 cache */ + .byte 0x4 /* rev: 0x5xxx: Not valid at this time */ + .byte 0x4 /* rev: 0x6xxx: 768K L2 -> 512k L2 cache */ + .byte 0x4 /* rev: 0x7xxx: Not valid at this time */ + .byte 0x5 /* rev: 0x8xxx: 1024K L2 -> 1024 L2 cache */ + .byte 0x4 /* rev: 0x9xxx: Not valid at this time */ + .byte 0x5 /* rev: 0xAxxx: 1536K L2 -> 1024 L2 cache */ + .byte 0x4 /* rev: 0xBxxx: Not valid at this time */ + .byte 0x4 /* rev: 0xCxxx: Not valid at this time */ + .byte 0x4 /* rev: 0xDxxx: Not valid at this time */ + .byte 0x4 /* rev: 0xExxx: Not valid at this time */ + .byte 0x4 /* rev: 0xFxxx: Not valid at this time */ + + + /* Data used for TLB refill */ + + .p2align 6, 0 + + .global __tlb_lock + .set __tlb_lock, tlb_lock +tlb_lock: + .word 0 + .global __tlb_idx + .set __tlb_idx, tlb_idx +tlb_idx: + .word TLB_FIXED_ENTRIES - 1 + + .global _tlb_fixed_entries +_tlb_fixed_entries: + .word TLB_FIXED_ENTRIES diff --git a/tests/tcg/hexagon/system/crt0/hexagon_standalone.h b/tests/tcg/hexagon/system/crt0/hexagon_standalone.h new file mode 100644 index 000000000000..01ca41349f0f --- /dev/null +++ b/tests/tcg/hexagon/system/crt0/hexagon_standalone.h @@ -0,0 +1,103 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include + +#ifndef _TLB_H +#define _TLB_H + +typedef enum { + SHIFT_4K = 0, + SHIFT_16K, + SHIFT_64K, + SHIFT_256K, + SHIFT_1M, + SHIFT_4M, + SHIFT_16M, + SHIFT_64M, + SHIFT_256M, + SHIFT_1G, +} PageShift; + +typedef enum { + PAGE_4K = 1 << SHIFT_4K, + PAGE_16K = 1 << SHIFT_16K, + PAGE_64K = 1 << SHIFT_64K, + PAGE_256K = 1 << SHIFT_256K, + PAGE_1M = 1 << SHIFT_1M, + PAGE_4M = 1 << SHIFT_4M, + PAGE_16M = 1 << SHIFT_16M, + PAGE_64M = 1 << SHIFT_64M, + PAGE_256M = 1 << SHIFT_256M, + PAGE_1G = 1 << SHIFT_1G, +} PageSize; + + +/* + * TLB entry format: + * + * TLBHI: + * 63 | 62 | 61 | 60:59 | 58 -- 52 | 51 -------- 32 | + * V | G | EP PPNex | ASID | Virtual Page # | + * ------------------------------------------- + * + * V - Valid bit. + * G - Global bit. If set ASID is ignored and the page + * is globally accessible. + * EP - Extra Physical Bit + * PPNex - Extended Physical Page. (V73 and beyond) + * ASID - Address Space Identifier. + * Virtual Page - Virtual Page number. It has a minimum 4K alignment. + * This means the input value is right shifted 12 bits + * and that is what is placed into this field. + * + * TLBLO: + * 31 | 30 | 29 | 28 | 27 -- 24 | 23 --------- 1 | 0 | + * X | W | R | U | C | Physical Page # | S | + * ---------------------------------------------------- + * + * X - Execute Enabled + * W - Write Enabled + * R - Read Enabled + * U - User mode accessible + * C - Cacheablilty attributes: L1/L2 Cacheable Writeback/thru + * Physical Page - Physical Page # + * + */ + +typedef union { + struct { + uint64_t S:1; + uint64_t PPN:23; + uint64_t CacheAttr:4; + uint64_t XWRU:4; + uint64_t VirtualPage:20; + uint64_t ASID:7; +#if __HEXAGON_ARCH__ < 73 + uint64_t A0:1; + uint64_t A1:1; +#else + uint64_t PPN_EX:2; +#endif + uint64_t EP:1; + uint64_t VG:2; + }; + uint64_t raw; +} TLBEntry; + + +#define TLB_NOT_FOUND 0x80000000 + +int add_translation_extended(int index, void *va, uint64_t pa, + unsigned int page_size, unsigned int xwru, + unsigned int cccc, unsigned int asid, + unsigned int aa, unsigned int vg); +void add_translation_fixed(int index, void *va, void *pa, int cccc, + int permissions); +void add_translation(void *va, void *pa, int cccc); + +#endif /* _TLB_H */ diff --git a/tests/tcg/hexagon/system/crt0/min_libc.c b/tests/tcg/hexagon/system/crt0/min_libc.c new file mode 100644 index 000000000000..f44ee49f8f44 --- /dev/null +++ b/tests/tcg/hexagon/system/crt0/min_libc.c @@ -0,0 +1,359 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Small cheat: take size_t, NULL, and other type/symbol definitions from the + * hexagon toolchain. We cannot link with the libc, though, as the actual + * implementation for functions like printf and open are defined for Linux, and + * we are running on "bare metal". + */ +#include +#include +#include +#include + +FILE *const stdout = (FILE *)1; + +void exit(int code) +{ + asm volatile( + "r2 = %0\n" + "stop(r0)\n" + : + : "r"(code) + : "r2"); + __builtin_unreachable(); +} + +/* The assert() macro will use this. */ +void __assert_fail(const char *assertion, const char *file, int line, + const char *function) +{ + printf("ASSERT fail '%s' at file '%s' line %d function %s\n", + assertion, file, line, function); + exit(1); +} + +void *memset(void *b, int c, size_t len) +{ + for (size_t i = 0; i < len; i++) { + ((unsigned char *)b)[i] = (unsigned char)c; + } + return b; +} + +int memcmp(const void *p1, const void *p2, size_t n) +{ + const char *s1 = p1; + const char *s2 = p2; + for ( ; n && (*s1 == *s2); s1++, s2++, n--) { + /* empty */ + } + return n ? *(unsigned char *)s1 - *(unsigned char *)s2 : 0; +} + +int bcmp(const void *s1, const void *s2, size_t n) +{ + return __builtin_bcmp(s1, s2, n); +} + + +#define HEX_SYS_WRITEC 0x03 +#define HEX_SYS_WRITE0 0x04 +#define HEX_SYS_GET_CMDLINE 0x15 + +/* + * Macro flavors: + * - DIRECT_SWI takes up to two args an put them at r1 and r2. + * - SWI takes up to four args and puts them in an array, placing the + * array address at r1. + */ + +static int swi_ret, swi_err, swi_args[4]; +#define DO_SWI(CODE, ARG0, ARG1) \ + do { \ + asm volatile( \ + "r0 = %2\n" \ + "r1 = %3\n" \ + "r2 = %4\n" \ + "trap0(#0)\n" \ + "%0 = r0\n" \ + "%1 = r1\n" \ + : "=r"(swi_ret), "=r"(swi_err) \ + : "r"(CODE), "r"(ARG0), "r"(ARG1) \ + : "r0", "r1", "r2", "memory" \ + ); \ + } while (0) + +#define SWI0(CODE) DO_SWI(CODE, swi_args, 0) +#define SWI1(CODE, ARG0) \ + do { swi_args[0] = (uint32_t)(ARG0); SWI0(CODE); } while (0) +#define SWI2(CODE, ARG0, ARG1) \ + do { swi_args[1] = (uint32_t)(ARG1); SWI1(CODE, ARG0); } while (0) +#define SWI3(CODE, ARG0, ARG1, ARG2) \ + do { swi_args[2] = (uint32_t)(ARG2); SWI2(CODE, ARG0, ARG1); } while (0) +#define SWI4(CODE, ARG0, ARG1, ARG2, ARG3) \ + do { swi_args[3] = (uint32_t)(ARG3); SWI3(CODE, ARG0, ARG1, ARG2); } while (0) + +#define GET_MACRO_5(_1, _2, _3, _4, _5, NAME, ...) NAME +#define SWI(...) \ + ({ GET_MACRO_5(__VA_ARGS__, SWI4, SWI3, SWI2, SWI1, SWI0)(__VA_ARGS__); \ + swi_ret; }) + +#define DIRECT_SWI0(CODE) DO_SWI(CODE, 0, 0) +#define DIRECT_SWI1(CODE, ARG1) DO_SWI(CODE, ARG1, 0) +#define DIRECT_SWI2(CODE, ARG1, ARG2) DO_SWI(CODE, ARG1, ARG2) + +#define GET_MACRO_3(_1, _2, _3, NAME, ...) NAME +#define DIRECT_SWI(...) \ + ({ GET_MACRO_3(__VA_ARGS__, DIRECT_SWI2, DIRECT_SWI1, DIRECT_SWI0)(__VA_ARGS__); \ + swi_ret; }) + +int puts(const char *str) +{ + DIRECT_SWI(HEX_SYS_WRITE0, str); + DIRECT_SWI(HEX_SYS_WRITE0, "\n"); + return 0; +} + +int fputs(const char *str, FILE *f) +{ + assert(f == stdout); /* Only stdout is supported. */ + DIRECT_SWI(HEX_SYS_WRITE0, str); + return 0; +} + +size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *f) +{ + assert(f == stdout); /* Only stdout is supported. */ + for (size_t i = 0; i < size * nitems; i++) { + DIRECT_SWI(HEX_SYS_WRITEC, &ptr[i]); + } + return size * nitems; +} + +int putchar(int c) +{ + DIRECT_SWI(HEX_SYS_WRITEC, &c); + return c; +} + +static char *num_to_s(uint64_t signed_num, uint64_t base) +{ + static char buffer[1024]; + char *bptr = buffer; + uint64_t num; + + if (base == 16) { + num = signed_num; + } else if (base == 10) { + if (signed_num < 0) { + *bptr++ = '-'; + signed_num *= -1; + } + num = signed_num; + } else { + puts("fatal: num_to_s expects base 16 or 10"); + exit(1); + } + + if (!num) { + return "0"; + } + + uint64_t divider = 1; + for (uint64_t n = num; n >= base; n /= base) { + divider *= base; + } + + while (num) { + unsigned int digit = num / divider; + if (digit) { + num %= divider; + divider /= base; + if (digit >= 10) { + *bptr++ = 'a' + (digit - 10); + } else { + *bptr++ = '0' + digit; + } + while (num < divider) { + *bptr++ = '0'; + divider /= base; + } + } else { + divider /= base; + } + } + + *bptr = '\0'; + return buffer; +} + +static int advance_prefix(const char **str_ptr, char *prefix) +{ + const char *str = *str_ptr; + while (*str && *str == *prefix) { + str++; + prefix++; + } + str--; + if (!*prefix) { + *str_ptr = str; + return 1; + } + return 0; +} + +static char *pad0(char *str, int n) +{ + static char buffer[1024]; + int len = strlen(str); + assert(n < 1024); + + int i; + for (i = 0; i < n - len; i++) { + buffer[i] = '0'; + } + strcpy(&buffer[i], str); + return buffer; +} + +/* + * Very simple implementation. No error checking. + * Supported formats are: + * %d, %s, %c, %x, %016llx + */ +int printf(const char *format, ...) +{ + va_list ap; + __builtin_va_start(ap, format); + for (const char *ptr = format; *ptr; ptr++) { + if (*ptr == '%') { + ptr++; + switch (*ptr) { + case 'd': + case 'x': + case 'p': + { + int num = __builtin_va_arg(ap, int); + fputs(num_to_s(num, *ptr == 'd' ? 10 : 16), stdout); + break; + } + case 's': + fputs(__builtin_va_arg(ap, char *), stdout); + break; + case 'c': + putchar(__builtin_va_arg(ap, int)); + break; + case '%': + putchar('%'); + break; + case '0': + if (advance_prefix(&ptr, "016llx")) { + uint64_t num = __builtin_va_arg(ap, uint64_t); + fputs(pad0(num_to_s(num, 16), 16), stdout); + break; + } + /* else: fallthrough */ + default: + fputs("fatal: unknown printf modifier '", stdout); + putchar(*ptr); + puts("'"); + exit(1); + } + } else { + putchar(*ptr); + } + } + __builtin_va_end(ap); + return 1; +} + +size_t strlen(const char *s) +{ + size_t len = 0; + for ( ; *s; s++) { + len++; + } + return len; +} + +char *strcpy(char *dst, const char *src) +{ + int i; + for (i = 0; src[i]; i++) { + dst[i] = src[i]; + } + dst[i] = '\0'; + return dst; +} + +int strcmp(const char *s1, const char *s2) +{ + for ( ; *s1 && (*s1 == *s2); s1++, s2++) { + /* empty */ + } + return *(unsigned char *)s1 - *(unsigned char *)s2; +} + +char *strrchr(const char *s, int c) +{ + for (int i = strlen(s) - 1; i >= 0; i--) { + if (s[i] == c) { + return (char *)&s[i]; + } + } + return NULL; +} + +#define MAX_ARGS 15 +/* + * Very simplistic implementation, using static buffers, and assuming no + * args will contain spaces. + */ +static inline char **getcmdline(int *argc) +{ + static char *args[MAX_ARGS] = { NULL }; + char buf[4096]; + char *c; + int id = 0; + + assert(!SWI(HEX_SYS_GET_CMDLINE, buf, sizeof(buf))); + + *argc = 1; + for (c = buf; *c; c++) { + if (*c == ' ' && *(c + 1)) { + (*argc)++; + } + } + assert(*argc <= MAX_ARGS); + + if (*argc == 0) { + return args; + } + + args[id++] = buf; + for (c = buf; *c; c++) { + if (*c == ' ') { + *c = '\0'; + if (id < *argc) { + args[id++] = c + 1; + } + } + } + return args; +} + +int main(int argc, char **argv, char **envp); +void _start_main(void) +{ + int argc; + char **argv = getcmdline(&argc); + /* For now, we ignore envp */ + char *envp[] = { NULL }; + exit(main(argc, argv, envp)); + exit(1); +} diff --git a/tests/tcg/hexagon/system/crt0/pte.S b/tests/tcg/hexagon/system/crt0/pte.S new file mode 100644 index 000000000000..406e45389118 --- /dev/null +++ b/tests/tcg/hexagon/system/crt0/pte.S @@ -0,0 +1,80 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + .section .start, "awx", @progbits + .p2align 3 + .subsection 1 +/* This is the translation table */ +/* We make a table of 2^12 entries */ +/* Each entry is a .hword (16 bits) */ +/* Each entry is initialized to 0 in the 4 LSBs (cached WB, see system spec) */ +/* Each entry is initialized to 1:1 Virtual:Physical in the upper 12 bits. */ +/* We use the preprocessor to avoid copy-paste errors and to avoid */ +/* an 8192-line addition to the file. */ + + .set __UPTE_START, UPTE_START + .weak __UPTE_START, UPTE_START +UPTE_START: +#define TLBENTRY(X) .hword ((((X) >> 16) & (0xfff0)) | 0x7); + +#define TLB_1M(X) TLBENTRY ((X) << 20) +#define TLB_16M(X) \ + TLB_1M (((X) << 4) + 0) \ + TLB_1M (((X) << 4) + 1) \ + TLB_1M (((X) << 4) + 2) \ + TLB_1M (((X) << 4) + 3) \ + TLB_1M (((X) << 4) + 4) \ + TLB_1M (((X) << 4) + 5) \ + TLB_1M (((X) << 4) + 6) \ + TLB_1M (((X) << 4) + 7) \ + TLB_1M (((X) << 4) + 8) \ + TLB_1M (((X) << 4) + 9) \ + TLB_1M (((X) << 4) + 10) \ + TLB_1M (((X) << 4) + 11) \ + TLB_1M (((X) << 4) + 12) \ + TLB_1M (((X) << 4) + 13) \ + TLB_1M (((X) << 4) + 14) \ + TLB_1M (((X) << 4) + 15) + +#define TLB_256M(X) \ + TLB_16M (((X) << 4) + 0) \ + TLB_16M (((X) << 4) + 1) \ + TLB_16M (((X) << 4) + 2) \ + TLB_16M (((X) << 4) + 3) \ + TLB_16M (((X) << 4) + 4) \ + TLB_16M (((X) << 4) + 5) \ + TLB_16M (((X) << 4) + 6) \ + TLB_16M (((X) << 4) + 7) \ + TLB_16M (((X) << 4) + 8) \ + TLB_16M (((X) << 4) + 9) \ + TLB_16M (((X) << 4) + 10) \ + TLB_16M (((X) << 4) + 11) \ + TLB_16M (((X) << 4) + 12) \ + TLB_16M (((X) << 4) + 13) \ + TLB_16M (((X) << 4) + 14) \ + TLB_16M (((X) << 4) + 15) + +#define TLB_4G \ + TLB_256M (0) \ + TLB_256M (1) \ + TLB_256M (2) \ + TLB_256M (3) \ + TLB_256M (4) \ + TLB_256M (5) \ + TLB_256M (6) \ + TLB_256M (7) \ + TLB_256M (8) \ + TLB_256M (9) \ + TLB_256M (10) \ + TLB_256M (11) \ + TLB_256M (12) \ + TLB_256M (13) \ + TLB_256M (14) \ + TLB_256M (15) + +TLB_4G + + .size UPTE_START, . - UPTE_START diff --git a/tests/tcg/hexagon/system/crt0/tlb.c b/tests/tcg/hexagon/system/crt0/tlb.c new file mode 100644 index 000000000000..00e07761dbe9 --- /dev/null +++ b/tests/tcg/hexagon/system/crt0/tlb.c @@ -0,0 +1,198 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include "hexagon_standalone.h" + +/* + * The following 2 functions use global addressing mode + * to avoid GP relative overflows. + */ +static inline uint32_t get_tlb_fixed_entries(void) +{ + uint32_t *addr; + asm volatile ("%0=##_tlb_fixed_entries\n\t" + : "=r"(addr)); + return *addr; +} +static inline uint32_t *get_UPTE_START(void) +{ + uint32_t addr; + asm volatile ("%0=##__UPTE_START\n\t" + : "=r"(addr)); + return (uint32_t *)addr; +} + +static inline uint32_t get_ssr(void) +{ + uint32_t reg; + asm volatile ("%0=ssr\n\t" + : "=r"(reg)); + return reg; +} + + +static inline int64_t read_tlb_entry(int index) +{ + uint64_t reg; + asm volatile ("%[reg]=tlbr(%[index])" + : [reg] "=r" (reg) + : [index] "r" (index)); + asm volatile ("isync"); + return reg; +} + + +static inline void write_tlb_entry(TLBEntry tlb, int index) +{ + uint64_t entry = tlb.raw; + asm volatile ("tlblock\n" + "tlbw(%[entry], %[index])\n" + "isync\n" + "tlbunlock\n" + : + : [entry] "r" (entry), [index] "r" (index)); +} + +static inline int32_t tlb_probe(uint32_t va) +{ + uint32_t VirtualPageNumber = va >> 12; + uint32_t ASID = (get_ssr() >> 8) & 0x7f; + uint32_t probe = ((ASID << 20) | VirtualPageNumber) & 0x7ffffff; + uint32_t result = 0; + asm volatile ("%[result]=tlbp(%[probe])" + : [result] "=r" (result) + : [probe] "r" (probe)); + + return result; +} + + +static inline void tlb_invalidate(uint32_t va) +{ + int entry = tlb_probe(va); + if (entry == TLB_NOT_FOUND) { + return; + } + + TLBEntry tlb; + tlb.raw = read_tlb_entry(entry); + tlb.raw = tlb.raw & ~(1ull << 63); /* Clear the V bit. */ + write_tlb_entry(tlb, entry); +} + + +static inline TLBEntry basic_entry(uint32_t va, uint64_t pa, PageSize pagesize) +{ + TLBEntry T; + uint64_t PPN; + T.raw = 0ull; + T.VirtualPage = va >> 12; /* 63-51 */ +#if __HEXAGON_ARCH__ > 72 + T.PPN_EX = (pa & (3ull << 36)) >> 36; +#endif + T.EP = (pa & (1ull << 35)) >> 35; + PPN = pa >> 12ull; + PPN = (PPN << 1ull) | pagesize; + if (pagesize == 1) { + T.S = 1; + } + T.raw |= PPN; + return T; +} +/* + * function: mkentry + * description: + * - Given just a Physical Address (pa) and a Virtual Address (va) + * create a default entry. + * - A user wanting to change the cache attributes or permissions + * can do so prior to writing the entry. + */ +static TLBEntry mkentry(uint32_t va, uint64_t pa, PageSize pagesize) +{ + + /* Make an entry and set some reasonable defaults */ + TLBEntry T = basic_entry(va, pa, pagesize); + + T.CacheAttr = 0x7; + T.XWRU = 0x6; + T.VG = 0x3; + return T; +} + +int add_translation_extended(int index, void *va, uint64_t pa, + unsigned int page_size, unsigned int xwru, + unsigned int cccc, unsigned int asid, + unsigned int aa, unsigned int vg) +{ + uint32_t num_entries = get_tlb_fixed_entries(); + + if ((index < 1) || (index > (num_entries - 1))) { + return -1; + } + + tlb_invalidate((uint32_t)va); + TLBEntry T; + T = basic_entry((uint32_t)va, pa, page_size); + T.ASID = ((uint64_t)asid & 0x7f); + T.CacheAttr = ((uint64_t)cccc & 0xf); + T.XWRU = ((uint64_t)xwru & 0xf); + T.VG = ((uint64_t)vg & 0x3); +#if __HEXAGON_ARCH__ < 73 + T.raw |= ((uint64_t)aa & 0x3) << 59ull; +#endif + write_tlb_entry(T, index); + + return 0; +} + + +void add_translation_fixed(int index, void *va, void *pa, int cccc, + int permissions) +{ + tlb_invalidate((uint32_t)va); + add_translation_extended(index, va, (uint64_t)pa, PAGE_1M, permissions, cccc, + 0, 0, 3); +} + +/* + * The following deals with the PTE software structure. The actual entry will + * not be placed into the TLB until an address fault occurrs. + */ + +typedef union { + struct { + uint16_t cache:4; + uint16_t pa:12; + }; + uint16_t PTE_raw; +} SMALL_PTE; + +static SMALL_PTE *findPTEAddr(uint32_t va) +{ + uint32_t *PTE = get_UPTE_START(); + int index = va >> 20; + return (SMALL_PTE *)PTE + index; +} +static SMALL_PTE findPTEValue(uint32_t va) +{ + SMALL_PTE *A = findPTEAddr(va); + return *A; +} + +/* This function adds a translation into the mapping table, see above */ +/* Because we use 1MB pages, we only need to translate 12 bits. */ +/* We keep those 12 bits plus 4 bits (where we keep the C field, */ +/* see the System-level architecture spec on TLB entries) in */ +/* a 16-bit entry in the table. */ +/* We index into the table using the upper 12 bits. */ +/* As a note, 2 bytes x 2^12 entries == 8KB table */ +void add_translation(void *va, void *pa, int cccc) +{ + SMALL_PTE *S = findPTEAddr((uint32_t)va); + S->pa = (uint32_t)pa >> 20; + S->cache = cccc; +} diff --git a/tests/tcg/hexagon/system/semihost.c b/tests/tcg/hexagon/system/semihost.c new file mode 100644 index 000000000000..7a0fa0cb73ff --- /dev/null +++ b/tests/tcg/hexagon/system/semihost.c @@ -0,0 +1,297 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "strutils.h" + +/* Defines in order of testing */ + +/* env/CLI-related */ +#define HEX_SYS_GET_CMDLINE 0x15 +#define HEX_SYS_GETCWD 0x104 + +/* File manipulation */ +#define HEX_SYS_TMPNAM 0x0d +#define HEX_SYS_OPEN 0x01 +#define HEX_SYS_ACCESS 0x105 +#define HEX_SYS_ISTTY 0x09 +#define HEX_SYS_WRITE 0x05 +#define HEX_SYS_SEEK 0x0a +#define HEX_SYS_READ 0x06 +#define HEX_SYS_FTELL 0x100 +#define HEX_SYS_FSTAT 0x101 +#define HEX_SYS_FTRUNC 0x186 +#define HEX_SYS_FLEN 0x0c +#define HEX_SYS_CLOSE 0x02 +#define HEX_SYS_ERRNO 0x13 +#define HEX_SYS_RENAME 0x0f +#define HEX_SYS_STAT 0x103 +#define HEX_SYS_REMOVE 0x0e + +/* Time */ +#define HEX_SYS_CLOCK 0x10 +#define HEX_SYS_TIME 0x11 + +/* dirent */ +#define HEX_SYS_OPENDIR 0x180 +#define HEX_SYS_CLOSEDIR 0x181 +#define HEX_SYS_READDIR 0x182 + +/* STDOUT */ +#define HEX_SYS_WRITEC 0x03 +#define HEX_SYS_WRITE0 0x04 +#define HEX_SYS_WRITECREG 0x43 + +static uint32_t ret, err, args[4]; + +/* + * Macro flavors: + * - DIRECT_SWI takes up to two args an put them at r1 and r2. + * - SWI takes up to four args and puts them in an array, placing the + * array address at r1. + */ + +#define DO_SWI(CODE, ARG0, ARG1) \ + do { \ + asm volatile( \ + "r0 = %2\n" \ + "r1 = %3\n" \ + "r2 = %4\n" \ + "trap0(#0)\n" \ + "%0 = r0\n" \ + "%1 = r1\n" \ + : "=r"(ret), "=r"(err) \ + : "r"(CODE), "r"(ARG0), "r"(ARG1) \ + : "r0", "r1", "r2", "memory" \ + ); \ + } while (0) + +#define SWI0(CODE) DO_SWI(CODE, args, 0) +#define SWI1(CODE, ARG0) \ + do { args[0] = (uint32_t)(ARG0); SWI0(CODE); } while (0) +#define SWI2(CODE, ARG0, ARG1) \ + do { args[1] = (uint32_t)(ARG1); SWI1(CODE, ARG0); } while (0) +#define SWI3(CODE, ARG0, ARG1, ARG2) \ + do { args[2] = (uint32_t)(ARG2); SWI2(CODE, ARG0, ARG1); } while (0) +#define SWI4(CODE, ARG0, ARG1, ARG2, ARG3) \ + do { args[3] = (uint32_t)(ARG3); SWI3(CODE, ARG0, ARG1, ARG2); } while (0) + +#define GET_MACRO_5(_1, _2, _3, _4, _5, NAME, ...) NAME +#define SWI(...) \ + GET_MACRO_5(__VA_ARGS__, SWI4, SWI3, SWI2, SWI1, SWI0)(__VA_ARGS__) + +#define DIRECT_SWI0(CODE) DO_SWI(CODE, 0, 0) +#define DIRECT_SWI1(CODE, ARG1) DO_SWI(CODE, ARG1, 0) +#define DIRECT_SWI2(CODE, ARG1, ARG2) DO_SWI(CODE, ARG1, ARG2) + +#define GET_MACRO_3(_1, _2, _3, NAME, ...) NAME +#define DIRECT_SWI(...) \ + GET_MACRO_3(__VA_ARGS__, DIRECT_SWI2, DIRECT_SWI1, DIRECT_SWI0)(__VA_ARGS__) + +#define is_path_sep(C) ((C) == '/' || (C) == '\\') + +static int path_ends_with(const char *str, const char *suffix) +{ + const char *str_cursor = str + strlen(str) - 1; + const char *suffix_cursor = suffix + strlen(suffix) - 1; + while (str_cursor >= str && suffix_cursor >= suffix) { + /* is_path_sep handles the semihosting-on-Windows case */ + if (*str_cursor != *suffix_cursor && + !(is_path_sep(*str_cursor) && is_path_sep(*suffix_cursor))) { + return 0; + } + str_cursor--; + suffix_cursor--; + } + return 1; +} + +/* + * This must match the caller's definition, it would be in the + * caller's angel.h or equivalent header. + */ +struct __SYS_STAT { + uint64_t dev; + uint64_t ino; + uint32_t mode; + uint32_t nlink; + uint64_t rdev; + uint32_t size; + uint32_t __pad1; + uint32_t atime; + uint32_t mtime; + uint32_t ctime; + uint32_t __pad2; +}; + +int main(int argc, char **argv) +{ + /* GET_CMDLINE */ + char argv_concat[1024]; + char *cursor = argv_concat; + for (int i = 0; i < argc; i++) { + strcpy(cursor, argv[i]); + cursor += strlen(argv[i]); + *cursor = ' '; + cursor++; + } + *(cursor - 1) = '\0'; + char buf[4096]; + SWI(HEX_SYS_GET_CMDLINE, buf, sizeof(buf)); + assert(!ret && !strcmp(buf, argv_concat)); + + /* GETCWD */ + const char *expected_cwd = "tests/tcg/hexagon-softmmu"; + SWI(HEX_SYS_GETCWD, buf, sizeof(buf)); + assert(ret && path_ends_with(buf, expected_cwd)); + + /* TMPNAM */ + char fname[4096]; + SWI(HEX_SYS_TMPNAM, fname, 0, sizeof(fname)); + assert(!ret); + + /* OPEN */ + /* 13 is O_RDWR | O_CREAT | O_EXCL */ + SWI(HEX_SYS_OPEN, fname, 13, strlen(fname)); + int fd = (int)ret; + assert(fd >= 0); + + /* ACCESS */ + SWI(HEX_SYS_ACCESS, fname, R_OK); + assert(!ret); + /* ACCESS with error */ + SWI(HEX_SYS_ACCESS, "non-existent-semihost-file", R_OK); + assert(ret); + assert(err == ENOENT); + + /* ISTTY */ + SWI(HEX_SYS_ISTTY, fd); + assert(!ret); + + /* WRITE */ + char *str = "hello"; + SWI(HEX_SYS_WRITE, fd, str, strlen(str)); + assert(!ret); + + /* SEEK */ + SWI(HEX_SYS_SEEK, fd, 0); + assert(!ret); + + /* READ */ + int n = strlen(str); + SWI(HEX_SYS_READ, fd, buf, n); + buf[n] = '\0'; + assert(!ret && !strcmp(str, buf)); + + /* FTELL */ + SWI(HEX_SYS_FTELL, fd); + assert(ret == strlen(str)); + + /* FSTAT */ + struct __SYS_STAT st; + SWI(HEX_SYS_FSTAT, fd, &st); + assert(!ret); + assert(st.atime && st.ctime && st.mtime); + assert(st.size == strlen(str)); + assert((st.mode & S_IFMT) == S_IFREG); + + /* FTRUNC */ + SWI(HEX_SYS_FTRUNC, fd, 1, 0); + assert(!ret); + + /* FLEN */ + SWI(HEX_SYS_FLEN, fd); + assert(ret == 1); + + /* CLOSE */ + SWI(HEX_SYS_CLOSE, fd); + assert(!ret); + + /* CLOSE w/ error && ERRNO */ + SWI(HEX_SYS_CLOSE, fd); + assert(ret); + assert(err == EBADF); + SWI(HEX_SYS_ERRNO); + assert(ret == EBADF); + + /* RENAME */ + char ogfname[4096]; + int len = strlen(fname); + strcpy(ogfname, fname); + fname[len - 1] = (fname[len - 1] == 'a' ? 'b' : 'a'); + SWI(HEX_SYS_RENAME, ogfname, len, fname, len); + assert(!ret); + + /* STAT */ + SWI(HEX_SYS_STAT, fname, &st); + assert(!ret); + assert(st.atime && st.ctime && st.mtime); + assert(st.size == 1); + assert((st.mode & S_IFMT) == S_IFREG); + + /* REMOVE */ + SWI(HEX_SYS_REMOVE, fname, strlen(fname)); + assert(!ret); + + /* STAT w/ error */ + SWI(HEX_SYS_STAT, fname, &st); + assert(ret); + assert(err == ENOENT); + + /* TIME && CLOCK */ + SWI(HEX_SYS_TIME); + assert(ret); + SWI(HEX_SYS_CLOCK); + assert(ret); + + /* OPENDIR */ + char *dname = "./_semihost_dir"; + DIRECT_SWI(HEX_SYS_OPENDIR, dname); + assert(ret); + int dir_index = ret; + + /* READDIR */ + char *expected_files[4] = { ".", "..", "fileA", "fileB" }; + char found_files_buffer[4][256]; + char *found_files[4]; + for (int i = 0; 1; i++) { + struct __attribute__((__packed__)) { int32_t _; char d_name[256]; } dirent; + DIRECT_SWI(HEX_SYS_READDIR, dir_index, &dirent); + if (!ret) { + break; + } + assert(i < 4); + found_files[i] = found_files_buffer[i]; + strcpy(found_files[i], dirent.d_name); + } + + sort_str_arr(found_files, 4); + for (int i = 0; i < 4; i++) { + assert(!strcmp(found_files[i], expected_files[i])); + } + + /* CLOSEDIR */ + DIRECT_SWI(HEX_SYS_CLOSEDIR, dir_index); + assert(!ret); + + /* WRITEC, WRITECREG, WRITE0 */ + /* We use DO_SWI directly here to bypass the args array */ + char *pass = "PASS\n"; + DIRECT_SWI(HEX_SYS_WRITEC, &pass[0]); + DIRECT_SWI(HEX_SYS_WRITECREG, pass[1]); + DIRECT_SWI(HEX_SYS_WRITE0, &pass[2]); + + return 0; +} diff --git a/tests/tcg/hexagon/system/strutils.h b/tests/tcg/hexagon/system/strutils.h new file mode 100644 index 000000000000..14f4a290b817 --- /dev/null +++ b/tests/tcg/hexagon/system/strutils.h @@ -0,0 +1,25 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef STRUTILS_H +#define STRUTILS_H + +#include + +void sort_str_arr(char **arr, size_t n) +{ + for (int i = 0; i < n - 1; i++) { + for (int j = 0; j < n - i - 1; j++) { + if (strcmp(arr[j], arr[j + 1]) > 0) { + char *tmp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = tmp; + } + } + } +} + +#endif From 03cac5985e540080300ef58fd1e2ae7de94d266f Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 08:34:33 -0800 Subject: [PATCH 1159/1179] target/hexagon: fill in the 'rev' system register This register should store the revision identifier for the running Hexagon arch cpu. Let's save the cpu revision and fill the register with it. Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/cpu.c | 22 ++++++++++++++++------ target/hexagon/cpu.h | 1 + 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 40e3c9d0f70f..2e7799dd0295 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -30,6 +30,7 @@ #include "cpu_helper.h" #include "max.h" #include "hex_mmu.h" +#include "hw/hexagon/hexagon.h" #ifndef CONFIG_USER_ONLY #include "macros.h" @@ -39,12 +40,19 @@ #include "hexswi.h" #endif -static void hexagon_v66_cpu_init(Object *obj) { } -static void hexagon_v67_cpu_init(Object *obj) { } -static void hexagon_v68_cpu_init(Object *obj) { } -static void hexagon_v69_cpu_init(Object *obj) { } -static void hexagon_v71_cpu_init(Object *obj) { } -static void hexagon_v73_cpu_init(Object *obj) { } +#define DEFINE_STD_CPU_INIT_FUNC(REV) \ + static void hexagon_##REV##_cpu_init(Object *obj) \ + { \ + HexagonCPU *cpu = HEXAGON_CPU(obj); \ + cpu->rev_reg = REV##_rev; \ + } + +DEFINE_STD_CPU_INIT_FUNC(v66) +DEFINE_STD_CPU_INIT_FUNC(v67) +DEFINE_STD_CPU_INIT_FUNC(v68) +DEFINE_STD_CPU_INIT_FUNC(v69) +DEFINE_STD_CPU_INIT_FUNC(v71) +DEFINE_STD_CPU_INIT_FUNC(v73) static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model) { @@ -73,6 +81,7 @@ static const Property hexagon_cpu_properties[] = { DEFINE_PROP_UINT64("config-table-addr", HexagonCPU, config_table_addr, 0xffffffffULL), #endif + DEFINE_PROP_UINT32("dsp-rev", HexagonCPU, rev_reg, 0), DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, qdev_prop_uint32, target_ulong), @@ -388,6 +397,7 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) memset(env->greg, 0, sizeof(target_ulong) * NUM_GREGS); if (cs->cpu_index == 0) { + arch_set_system_reg(env, HEX_SREG_REV, cpu->rev_reg); arch_set_system_reg(env, HEX_SREG_MODECTL, 0x1); *(env->g_pcycle_base) = 0; } diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index ea618802a929..8b334068e295 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -188,6 +188,7 @@ struct ArchCPU { CPUHexagonState env; + uint32_t rev_reg; bool lldb_compat; target_ulong lldb_stack_adjust; bool short_circuit; From 47c3323f8c525829fb150aec15fad3d91c21d822 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 08:49:36 -0800 Subject: [PATCH 1160/1179] target/hexagon: print full name of control regs Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/cpu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 2e7799dd0295..80485c3cc1cb 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -94,9 +94,10 @@ const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS] = { "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", "sa0", "lc0", "sa1", "lc1", "p3_0", "c5", "m0", "m1", - "usr", "pc", "ugp", "gp", "cs0", "cs1", "c14", "c15", - "c16", "c17", "c18", "c19", "pkt_cnt", "insn_cnt", "hvx_cnt", "c23", - "c24", "c25", "c26", "c27", "c28", "c29", "c30", "c31", + "usr", "pc", "ugp", "gp", "cs0", "cs1", "upcyclelo", "upcyclehi", + "framelimit", "framekey", "pktcountlo", "pktcounthi", "upmucnt0", + "upmucnt1", "upmucnt2", "upmucnt3", "upmucnt4", "upmucnt5", "upmucnt6", + "upmucnt7", "c28", "c29", "utimerlo", "utimerhi", }; #ifndef CONFIG_USER_ONLY From f90a6aa4c59f882ac807447957001eda02e8c267 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 08:50:51 -0800 Subject: [PATCH 1161/1179] target/hexagon: fix system register names with -d in_asm Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/printinsn.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/target/hexagon/printinsn.c b/target/hexagon/printinsn.c index 4865cdd133b5..f780092586cf 100644 --- a/target/hexagon/printinsn.c +++ b/target/hexagon/printinsn.c @@ -24,16 +24,17 @@ static const char *sreg2str(unsigned int reg) { - if (reg < TOTAL_PER_THREAD_REGS) { - return hexagon_regnames[reg]; - } else { - return "???"; +#ifndef CONFIG_USER_ONLY + if (reg < NUM_SREGS) { + return hexagon_sregnames[reg]; } +#endif + return "???"; } static const char *creg2str(unsigned int reg) { - return sreg2str(reg + HEX_REG_SA0); + return hexagon_regnames[reg + HEX_REG_SA0]; } static void snprintinsn(GString *buf, Insn *insn) From d744f707765beb55ea5be77b57b6087e4e30c51d Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 08:54:31 -0800 Subject: [PATCH 1162/1179] target/hexagon: reset registers on cpu_reset Signed-off-by: Matheus Tavares Bernardino --- target/hexagon/cpu.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 80485c3cc1cb..c649aef99ee2 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -402,6 +402,15 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) arch_set_system_reg(env, HEX_SREG_MODECTL, 0x1); *(env->g_pcycle_base) = 0; } + + memset(env->gpr, 0, sizeof(target_ulong) * TOTAL_PER_THREAD_REGS); + memset(env->pred, 0, sizeof(target_ulong) * NUM_PREGS); + memset(env->VRegs, 0, sizeof(MMVector) * NUM_VREGS); + memset(env->QRegs, 0, sizeof(MMQReg) * NUM_QREGS); + memset(env->vstore_pending, 0, sizeof(target_ulong) * VSTORES_MAX); + env->t_cycle_count = 0; + env->vtcm_pending = false; + mmu_reset(env); arch_set_system_reg(env, HEX_SREG_HTID, cs->cpu_index); hexagon_cpu_soft_reset(env); From f58f9761be67445104d819e28132bf4b29df5cf7 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 09:14:00 -0800 Subject: [PATCH 1163/1179] tests/tcg/hexagon: add MMU tests Signed-off-by: Matheus Tavares Bernardino --- tests/tcg/hexagon/Makefile.softmmu-target | 25 +- tests/tcg/hexagon/system/mmu.h | 718 ++++++++++++++++++++ tests/tcg/hexagon/system/mmu_asids.c | 80 +++ tests/tcg/hexagon/system/mmu_overlap.c | 65 ++ tests/tcg/hexagon/system/reg_fields_def.h | 87 +++ tests/tcg/hexagon/system/tlb-miss-tlblock.S | 156 +++++ 6 files changed, 1128 insertions(+), 3 deletions(-) create mode 100644 tests/tcg/hexagon/system/mmu.h create mode 100644 tests/tcg/hexagon/system/mmu_asids.c create mode 100644 tests/tcg/hexagon/system/mmu_overlap.c create mode 100644 tests/tcg/hexagon/system/reg_fields_def.h create mode 100644 tests/tcg/hexagon/system/tlb-miss-tlblock.S diff --git a/tests/tcg/hexagon/Makefile.softmmu-target b/tests/tcg/hexagon/Makefile.softmmu-target index f965f4f4fac6..7e561efd2582 100644 --- a/tests/tcg/hexagon/Makefile.softmmu-target +++ b/tests/tcg/hexagon/Makefile.softmmu-target @@ -35,11 +35,18 @@ tlb.o: crt0/tlb.c CRT0_OBJS=crt0.o crt0_standalone.o pte.o min_libc.o tlb.o -TESTS += \ +TESTS_BUILT_WITH_DEFAULT_RULES = \ semihost \ + mmu_overlap \ + mmu_asids \ + $() + +TESTS += \ + $(TESTS_BUILT_WITH_DEFAULT_RULES) \ + tlb-miss-tlblock \ $() -$(TESTS): $(CRT0_OBJS) +$(TESTS_BUILT_WITH_DEFAULT_RULES): $(CRT0_OBJS) # Build and link the tests echo-and-run = echo $(1) && $(1) @@ -50,7 +57,7 @@ endef $(CRT0_OBJS): $(call build_fn,$<,$@) -$(TESTS): +$(TESTS_BUILT_WITH_DEFAULT_RULES): $(call build_fn,$^,$@,LINK) %.o: %.S @@ -58,8 +65,20 @@ $(TESTS): %.o: %.c $(call build_fn,$<,$@) +mmu.h: ../hex_test.h + semihost.o: semihost.c strutils.h semihost: semihost.o +mmu_overlap.o: mmu_overlap.c mmu.h +mmu_overlap: mmu_overlap.o +mmu_asids.o: mmu_asids.c mmu.h +mmu_asids: mmu_asids.o + +############# Custom build options + +# We don't want to link this one with crt0 files +tlb-miss-tlblock: tlb-miss-tlblock.o + $(CC) $(CFLAGS) $< -o $@ -nostartfiles -Wl,-Ttext,0x9b800000 -Wl,-entry,0x9b800000 ############# Custom test rules diff --git a/tests/tcg/hexagon/system/mmu.h b/tests/tcg/hexagon/system/mmu.h new file mode 100644 index 000000000000..0856c94ab5dd --- /dev/null +++ b/tests/tcg/hexagon/system/mmu.h @@ -0,0 +1,718 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef MMU_H +#define MMU_H +#include +#include +#include +#include "crt0/hexagon_standalone.h" + +/* + * Helpers for MMU tests + */ + +#define TARGET_PAGE_BITS 12 +#ifndef TLB_NOT_FOUND +#define TLB_NOT_FOUND (1 << 31) +#endif + +static inline uint32_t page_start(uint32_t addr, uint32_t page_size_bits) +{ + uint32_t page_size = 1 << page_size_bits; + uint32_t page_align = ~(page_size - 1); + return addr & page_align; +} + +/* + * The Hexagon standalone runtime leaves TLB entries 1-5 reserved for + * user-defined entries. We'll set them up to map virtual addresses at + * 1MB offsets above the actual physical address + * PA == VA - (entry_num * 1MB) + * + * We'll define some macros/functions to help with the manipulation + */ + +#define ONE_MB (1 << 20) +#define TWO_MB (2 * ONE_MB) +#define THREE_MB (3 * ONE_MB) +#define FOUR_MB (4 * ONE_MB) +#define FIVE_MB (5 * ONE_MB) + +#define ONE_MB_ENTRY 1 +#define TWO_MB_ENTRY 2 +#define THREE_MB_ENTRY 3 +#define FOUR_MB_ENTRY 4 +#define FIVE_MB_ENTRY 5 + +static inline uint32_t tlb_entry_num(uint32_t va) +{ + return va >> 20; +} + +#define fZXTN(N, M, VAL) ((VAL) & ((1LL << (N)) - 1)) +#define fEXTRACTU_BITS(INREG, WIDTH, OFFSET) \ + (fZXTN(WIDTH, 32, (INREG >> OFFSET))) + +#define fINSERT_BITS(REG, WIDTH, OFFSET, INVAL) \ + do { \ + REG = ((REG) & ~(((1LL << (WIDTH)) - 1) << (OFFSET))) | \ + (((INVAL) & ((1LL << (WIDTH)) - 1)) << (OFFSET)); \ + } while (0) + +#define GET_FIELD(ENTRY, FIELD) \ + fEXTRACTU_BITS(ENTRY, reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset) +#define SET_FIELD(ENTRY, FIELD, VAL) \ + fINSERT_BITS(ENTRY, reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset, (VAL)) + +typedef struct { + int offset; + int width; +} reg_field_t; + +enum reg_fields_enum { +#define DEF_REG_FIELD(TAG, NAME, START, WIDTH, DESCRIPTION) \ + TAG, +#include "reg_fields_def.h" + NUM_REG_FIELDS +#undef DEF_REG_FIELD +}; + +static const reg_field_t reg_field_info[] = { +#define DEF_REG_FIELD(TAG, NAME, START, WIDTH, DESCRIPTION) \ + { START, WIDTH }, + +#include "reg_fields_def.h" + + { 0, 0 } +#undef DEF_REG_FIELD +}; + +/* + * PPD (physical page descriptor) is formed by putting the PTE_PA35 field + * in the MSB of the PPD + */ +#define GET_PPD(ENTRY) \ + ((GET_FIELD((ENTRY), PTE_PPD) | \ + (GET_FIELD((ENTRY), PTE_PA35) << reg_field_info[PTE_PPD].width))) + +#define NUM_PGSIZE_TYPES (SHIFT_1G + 1) + +static const char *pgsize_str(PageSize pgsize) +{ + static const char *size_str[NUM_PGSIZE_TYPES] = { + "4K", + "16K", + "64K", + "256K", + "1M", + "4M", + "16M", + "64M", + "256M", + "1G" + }; + assert(pgsize); + return size_str[__builtin_ctz(pgsize)]; +} + +static const uint64_t encmask_2_mask[] = { + 0x0fffLL, /* 4k, 0000 */ + 0x3fffLL, /* 16k, 0001 */ + 0xffffLL, /* 64k, 0010 */ + 0x3ffffLL, /* 256k, 0011 */ + 0xfffffLL, /* 1m, 0100 */ + 0x3fffffLL, /* 4m, 0101 */ + 0xffffffLL, /* 16M, 0110 */ + 0xffffffffLL, /* RSVD, 0111 */ +}; + +static inline int hex_tlb_pgsize(uint64_t entry) +{ + assert(entry != 0); + int size = __builtin_ctzll(entry); + assert(size < NUM_PGSIZE_TYPES); + return size; +} + +static inline uint32_t hex_tlb_page_size(uint64_t entry) +{ + return 1 << (TARGET_PAGE_BITS + 2 * hex_tlb_pgsize(entry)); +} + +static inline uint64_t hex_tlb_phys_page_num(uint64_t entry) +{ + uint32_t ppd = GET_PPD(entry); + return ppd >> 1; +} + +static inline uint64_t hex_tlb_phys_addr(uint64_t entry) +{ + uint64_t pagemask = encmask_2_mask[hex_tlb_pgsize(entry)]; + uint64_t pagenum = hex_tlb_phys_page_num(entry); + uint64_t PA = (pagenum << TARGET_PAGE_BITS) & (~pagemask); + return PA; +} + +static inline uint64_t hex_tlb_virt_addr(uint64_t entry) +{ + return GET_FIELD(entry, PTE_VPN) << TARGET_PAGE_BITS; +} + +static inline uint64_t create_mmu_entry(uint8_t G, uint8_t A0, uint8_t A1, + uint8_t ASID, uint32_t VA, + uint8_t X, int8_t W, uint8_t R, + uint8_t U, uint8_t C, uint64_t PA, + PageSize SZ) +{ + uint64_t entry = 0; + SET_FIELD(entry, PTE_V, 1); + SET_FIELD(entry, PTE_G, G); + SET_FIELD(entry, PTE_ATR0, A0); + SET_FIELD(entry, PTE_ATR1, A1); + SET_FIELD(entry, PTE_ASID, ASID); + SET_FIELD(entry, PTE_VPN, VA >> TARGET_PAGE_BITS); + SET_FIELD(entry, PTE_X, X); + SET_FIELD(entry, PTE_W, W); + SET_FIELD(entry, PTE_R, R); + SET_FIELD(entry, PTE_U, U); + SET_FIELD(entry, PTE_C, C); + SET_FIELD(entry, PTE_PA35, (PA >> (TARGET_PAGE_BITS + 35)) & 1); + SET_FIELD(entry, PTE_PPD, ((PA >> (TARGET_PAGE_BITS - 1)))); + entry |= SZ; + return entry; +} + +static inline uint64_t tlbr(uint32_t i) +{ + uint64_t ret; + asm volatile ("%0 = tlbr(%1)\n\t" : "=r"(ret) : "r"(i)); + return ret; +} + +static inline uint32_t ctlbw(uint64_t entry, uint32_t idx) +{ + uint32_t ret; + asm volatile ("%0 = ctlbw(%1, %2)\n\t" : "=r"(ret) : "r"(entry), "r"(idx)); + return ret; +} + +static inline uint32_t tlbp(uint32_t asid, uint32_t VA) +{ + uint32_t x = ((asid & 0x7f) << 20) | ((VA >> 12) & 0xfffff); + uint32_t ret; + asm volatile ("%0 = tlbp(%1)\n\t" : "=r"(ret) : "r"(x)); + return ret; +} + +static inline void tlbw(uint64_t entry, uint32_t idx) +{ + asm volatile ("tlbw(%0, %1)\n\t" :: "r"(entry), "r"(idx)); +} + +static inline uint32_t tlboc(uint64_t entry) +{ + uint32_t ret; + asm volatile ("%0 = tlboc(%1)\n\t" : "=r"(ret) : "r"(entry)); + return ret; +} + +void tlbinvasid(uint32_t entry_hi) +{ + asm volatile ("tlbinvasid(%0)\n\t" :: "r"(entry_hi)); +} + +static inline void enter_user_mode(void) +{ + asm volatile ("r0 = ssr\n\t" + "r0 = clrbit(r0, #17) // EX\n\t" + "r0 = setbit(r0, #16) // UM\n\t" + "r0 = clrbit(r0, #19) // GM\n\t" + "ssr = r0\n\t" : : : "r0"); +} + +static inline void enter_kernel_mode(void) +{ + asm volatile ("r0 = ssr\n\t" + "r0 = clrbit(r0, #17) // EX\n\t" + "r0 = clrbit(r0, #16) // UM\n\t" + "r0 = clrbit(r0, #19) // GM\n\t" + "ssr = r0\n\t" : : : "r0"); +} + +static inline uint32_t *getevb() +{ + uint32_t reg; + asm volatile ("%0 = evb\n\t" : "=r"(reg)); + return (uint32_t *)reg; +} + +static inline void setevb(void *new_evb) +{ + asm volatile("evb = %0\n\t" : : "r"(new_evb)); +} + +static inline uint32_t getbadva() +{ + uint32_t badva; + asm volatile ("%0 = badva\n\t" : "=r"(badva)); + return badva; +} + +static void inc_elr(uint32_t inc) +{ + + asm volatile ("r1 = %0\n\t" + "r2 = elr\n\t" + "r1 = add(r2, r1)\n\t" + "elr = r1\n\t" + : : "r"(inc) : "r1", "r2"); +} + +static inline void do_coredump(void) +{ + asm volatile("r0 = #2\n\t" + "stid = r0\n\t" + "jump __coredump\n\t" : : : "r0"); +} + +static inline uint32_t getssr(void) +{ + uint32_t ret; + asm volatile ("%0 = ssr\n\t" : "=r"(ret)); + return ret; +} + +static inline void setssr(uint32_t new_ssr) +{ + asm volatile ("ssr = %0\n\t" :: "r"(new_ssr)); +} + +static inline void set_asid(uint32_t asid) +{ + uint32_t ssr = getssr(); + SET_FIELD(ssr, SSR_ASID, asid); + setssr(ssr); +} + +int err; +#include "../hex_test.h" + +static void *old_evb; + +typedef uint64_t exception_vector[2]; +static exception_vector my_exceptions; + +static inline void clear_exception_vector(exception_vector excp) +{ + excp[0] = 0; + excp[1] = 0; +} + +static inline void set_exception_vector_bit(exception_vector excp, uint32_t bit) +{ + if (bit < 64) { + excp[0] |= 1LL << bit; + } else if (bit < 128) { + excp[1] |= 1LL << (bit - 64); + } +} + +#define check_exception_vector(excp, expect) \ + do { \ + check64(excp[0], expect[0]); \ + check64(excp[1], expect[1]); \ + } while (0) + +static inline void print_exception_vector(exception_vector excp) +{ + printf("exceptions (0x%016llx 0x%016llx):", excp[1], excp[0]); + for (int i = 0; i < 64; i++) { + if (excp[0] & (1LL << i)) { + printf(" 0x%x", i); + } + } + for (int i = 0; i < 64; i++) { + if (excp[1] & (1LL << i)) { + printf(" 0x%x", i + 64); + } + } + printf("\n"); +} + +/* volatile because it is written through different MMU mappings */ +typedef volatile int mmu_variable; +mmu_variable data = 0xdeadbeef; + +typedef int (*func_t)(void); +/* volatile because it will be invoked via different MMU mappings */ +typedef volatile func_t mmu_func_t; + +/* + * Create a function that returns its (virtual) address + * Write it fully in assembly so we don't have to worry about + * which optimization level we are compiled with + */ +extern int func_return_pc(void); +asm( +".global func_return_pc\n" +".balign 4\n" +".type func_return_pc, @function\n" +"func_return_pc:\n" +" r0 = pc\n" +" jumpr r31\n" +".size func_return_pc, . - func_return_pc\n" +); + +enum { + TLB_U = (1 << 0), + TLB_R = (1 << 1), + TLB_W = (1 << 2), + TLB_X = (1 << 3), +}; + +#define HEX_CAUSE_FETCH_NO_XPAGE 0x011 +#define HEX_CAUSE_FETCH_NO_UPAGE 0x012 +#define HEX_CAUSE_PRIV_NO_READ 0x022 +#define HEX_CAUSE_PRIV_NO_WRITE 0x023 +#define HEX_CAUSE_PRIV_NO_UREAD 0x024 +#define HEX_CAUSE_PRIV_NO_UWRITE 0x025 +#define HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH 0x044 +#define HEX_CAUSE_TLBMISSX_NORMAL 0x060 +#define HEX_CAUSE_TLBMISSX_NEXTPAGE 0x061 +#define HEX_CAUSE_TLBMISSRW_READ 0x070 +#define HEX_CAUSE_TLBMISSRW_WRITE 0x071 + +/* + * The following lets us override the default exception handlers + * This can be handy for adding code to check that they are called as well + * as special handling needed for the test to succeed. + * + * MY_EVENT_HANDLE Use this to define your own event handler + * DEFAULT_EVENT_HANDLE Use this to point to the default handler + * my_event_vectors New event vector table + * install_my_event_vectors Change from the default event handlers + */ + +extern void *my_event_vectors; + +#define MY_EVENT_HANDLE(name, helper) \ +void name(void) \ +{ \ + asm volatile("crswap(sp, sgp0)\n\t" \ + "memd(sp++#8) = r1:0\n\t" \ + "memd(sp++#8) = r3:2\n\t" \ + "memd(sp++#8) = r5:4\n\t" \ + "memd(sp++#8) = r7:6\n\t" \ + "memd(sp++#8) = r9:8\n\t" \ + "memd(sp++#8) = r11:10\n\t" \ + "memd(sp++#8) = r13:12\n\t" \ + "memd(sp++#8) = r15:14\n\t" \ + "memd(sp++#8) = r17:16\n\t" \ + "memd(sp++#8) = r19:18\n\t" \ + "memd(sp++#8) = r21:20\n\t" \ + "memd(sp++#8) = r23:22\n\t" \ + "memd(sp++#8) = r25:24\n\t" \ + "memd(sp++#8) = r27:26\n\t" \ + "memd(sp++#8) = r31:30\n\t" \ + "r0 = ssr\n\t" \ + "call " #helper "\n\t" \ + "sp = add(sp, #-8)\n\t" \ + "r31:30 = memd(sp++#-8)\n\t" \ + "r27:26 = memd(sp++#-8)\n\t" \ + "r25:24 = memd(sp++#-8)\n\t" \ + "r23:22 = memd(sp++#-8)\n\t" \ + "r21:20 = memd(sp++#-8)\n\t" \ + "r19:18 = memd(sp++#-8)\n\t" \ + "r17:16 = memd(sp++#-8)\n\t" \ + "r15:14 = memd(sp++#-8)\n\t" \ + "r13:12 = memd(sp++#-8)\n\t" \ + "r11:10 = memd(sp++#-8)\n\t" \ + "r9:8 = memd(sp++#-8)\n\t" \ + "r7:6 = memd(sp++#-8)\n\t" \ + "r5:4 = memd(sp++#-8)\n\t" \ + "r3:2 = memd(sp++#-8)\n\t" \ + "r1:0 = memd(sp)\n\t" \ + "crswap(sp, sgp0);\n\t" \ + "rte\n\t"); \ +} + +#ifndef NO_DEFAULT_EVENT_HANDLES + +#define DEFAULT_EVENT_HANDLE(name, offset) \ +void name(void) \ +{ \ + asm volatile("r0 = %0\n\t" \ + "r0 = add(r0, #" #offset ")\n\t" \ + "jumpr r0\n\t" \ + : : "r"(old_evb) : "r0"); \ +} + + +/* Use these values as the offset for DEFAULT_EVENT_HANDLE */ +asm ( +".set HANDLE_RESET_OFFSET, 0x00\n\t" +".set HANDLE_NMI_OFFSET, 0x04\n\t" +".set HANDLE_ERROR_OFFSET, 0x08\n\t" +".set HANDLE_RSVD_OFFSET, 0x0c\n\t" +".set HANDLE_TLBMISSX_OFFSET, 0x10\n\t" +".set HANDLE_TLBMISSRW_OFFSET, 0x18\n\t" +".set HANDLE_TRAP0_OFFSET, 0x20\n\t" +".set HANDLE_TRAP1_OFFSET, 0x24\n\t" +".set HANDLE_FPERROR_OFFSET, 0x28\n\t" +".set HANDLE_INT_OFFSET, 0x40\n\t" +); + +asm( +".align 0x1000\n\t" +"my_event_vectors:\n\t" + "jump my_event_handle_reset\n\t" + "jump my_event_handle_nmi\n\t" + "jump my_event_handle_error\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_tlbmissx\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_tlbmissrw\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_trap0\n\t" + "jump my_event_handle_trap1\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_fperror\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_rsvd\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" + "jump my_event_handle_int\n\t" +); + +#define DEFAULT_EVENT_HANDLES \ +DEFAULT_EVENT_HANDLE(my_event_handle_error, HANDLE_ERROR_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_nmi, HANDLE_NMI_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_tlbmissrw, HANDLE_TLBMISSRW_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_tlbmissx, HANDLE_TLBMISSX_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_reset, HANDLE_RESET_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_rsvd, HANDLE_RSVD_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_trap0, HANDLE_TRAP0_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_trap1, HANDLE_TRAP1_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_int, HANDLE_INT_OFFSET) \ +DEFAULT_EVENT_HANDLE(my_event_handle_fperror, HANDLE_FPERROR_OFFSET) + +#endif /* NO_DEFAULT_EVENT_HANDLES */ + +/* When a permission error happens, add the permission to the TLB entry */ +void my_event_handle_error_helper(uint32_t ssr) +{ + uint32_t cause = GET_FIELD(ssr, SSR_CAUSE); + uint32_t badva = getbadva(); + uint32_t entry_num = tlb_entry_num(badva); + uint64_t entry; + + set_exception_vector_bit(my_exceptions, cause); + + switch (cause) { + case HEX_CAUSE_FETCH_NO_XPAGE: + entry = tlbr(entry_num); + SET_FIELD(entry, PTE_X, 1); + tlbw(entry, entry_num); + break; + case HEX_CAUSE_FETCH_NO_UPAGE: + entry = tlbr(entry_num); + SET_FIELD(entry, PTE_U, 1); + tlbw(entry, entry_num); + break; + case HEX_CAUSE_PRIV_NO_READ: + entry = tlbr(entry_num); + SET_FIELD(entry, PTE_R, 1); + tlbw(entry, entry_num); + break; + case HEX_CAUSE_PRIV_NO_WRITE: + entry = tlbr(entry_num); + SET_FIELD(entry, PTE_W, 1); + tlbw(entry, entry_num); + break; + case HEX_CAUSE_PRIV_NO_UREAD: + entry = tlbr(entry_num); + SET_FIELD(entry, PTE_U, 1); + tlbw(entry, entry_num); + break; + case HEX_CAUSE_PRIV_NO_UWRITE: + entry = tlbr(entry_num); + SET_FIELD(entry, PTE_U, 1); + tlbw(entry, entry_num); + break; + default: + do_coredump(); + break; + } +} + +void my_event_handle_nmi_helper(uint32_t ssr) +{ + uint32_t cause = GET_FIELD(ssr, SSR_CAUSE); + + set_exception_vector_bit(my_exceptions, cause); + + switch (cause) { + case HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH: + break; + default: + do_coredump(); + break; + } +} + +/* + * When a TLB miss happens, create a mapping + * We'll set different read/write/execute permissions + * for different entry numbers. + */ +void my_event_handle_tlbmissrw_helper(uint32_t ssr) +{ + uint32_t cause = GET_FIELD(ssr, SSR_CAUSE); + uint32_t badva = getbadva(); + uint32_t entry_num = tlb_entry_num(badva); + uint32_t VA = page_start(badva, TARGET_PAGE_BITS); + uint32_t PA = VA - (entry_num * ONE_MB); + + uint64_t entry = + create_mmu_entry(1, 0, 0, 0, VA, 0, 0, 0, 1, 0x3, PA, PAGE_4K); + if (entry_num == TWO_MB_ENTRY) { + SET_FIELD(entry, PTE_R, 1); + } + if (entry_num == THREE_MB_ENTRY) { + SET_FIELD(entry, PTE_W, 1); + } + + set_exception_vector_bit(my_exceptions, cause); + + switch (cause) { + case HEX_CAUSE_TLBMISSRW_READ: + tlbw(entry, entry_num); + break; + case HEX_CAUSE_TLBMISSRW_WRITE: + tlbw(entry, entry_num); + break; + default: + do_coredump(); + break; + } +} + +void my_event_handle_tlbmissx_helper(uint32_t ssr) +{ + uint32_t cause = GET_FIELD(ssr, SSR_CAUSE); + uint32_t badva = getbadva(); + uint32_t entry_num = tlb_entry_num(badva); + uint32_t VA = page_start(badva, TARGET_PAGE_BITS); + uint32_t PA = VA - (entry_num * ONE_MB); + + uint64_t entry = + create_mmu_entry(1, 0, 0, 0, VA, 0, 0, 0, 1, 0x3, PA, PAGE_4K); + + set_exception_vector_bit(my_exceptions, cause); + + switch (cause) { + case HEX_CAUSE_TLBMISSX_NORMAL: + tlbw(entry, entry_num); + break; + default: + do_coredump(); + break; + } +} + +static inline void install_my_event_vectors(void) +{ + old_evb = getevb(); + setevb(&my_event_vectors); +} + +#define MAKE_GOTO(name) \ +void goto_##name(void) \ +{ \ + asm volatile("r0 = ##" #name "\n\t" \ + "jumpr r0\n\t" \ + : : : "r0"); \ +} + +#define MAKE_ERR_HANDLER(name, helper_fn) \ + MY_EVENT_HANDLE(name, helper_fn) \ + MAKE_GOTO(name) + +#define INSTALL_ERR_HANDLER(name) { \ + /* + * Install our own privelege exception handler. + * The normal behavior is to coredump + * Read and decode the jump displacemnts from evb + * ASSUME negative displacement which is the standard. + */ \ + uint32_t *evb_err = getevb() + 2; \ + uint32_t err_distance = -(0xfe000000 | *evb_err) << 1; \ + uint32_t err_handler = (uint32_t)evb_err - err_distance; \ + memcpy((void *)err_handler, goto_##name, 12); \ +} while (0) + +static inline void remove_trans(int index) +{ + uint64_t entry = tlbr(index); + SET_FIELD(entry, PTE_V, 0); + tlbw(entry, index); +} + +static inline void clear_overlapping_entry(unsigned int asid, uint32_t va) +{ + int32_t index = tlbp(asid, va); + if (index != TLB_NOT_FOUND) { + remove_trans(index); + } +} + +static void add_trans(int index, uint32_t va, uint64_t pa, + PageSize page_size, uint8_t xwru, + unsigned int asid, uint8_t V, uint8_t G) +{ + if (V) { + clear_overlapping_entry(asid, va); + } + assert(!add_translation_extended(index, (void *)va, pa, page_size, + xwru, 0, asid, 0, + ((V & 1) << 1) | (G & 1))); +} + +#endif diff --git a/tests/tcg/hexagon/system/mmu_asids.c b/tests/tcg/hexagon/system/mmu_asids.c new file mode 100644 index 000000000000..34f25c25a3d7 --- /dev/null +++ b/tests/tcg/hexagon/system/mmu_asids.c @@ -0,0 +1,80 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include + + +#define DEBUG 0 + +#include "mmu.h" + +DEFAULT_EVENT_HANDLES + +void test_asids(void) +{ + uint32_t addr = (uint32_t)&data; + uint32_t page = page_start(addr, TARGET_PAGE_BITS); + uint32_t offset = FIVE_MB; + uint32_t new_addr = addr + offset; + uint32_t new_page = page + offset; + uint64_t entry = + create_mmu_entry(0, 0, 0, 1, new_page, 1, 1, 1, 0, 7, page, PAGE_4K); + /* + * Create a TLB entry for ASID=1 + * Write it at index 1 + * Check that it is present + * Invalidate the ASID + * Check that it is not found + */ + tlbw(entry, 1); + check32(tlboc(entry), 1); + tlbinvasid(entry >> 32); + check32(tlboc(entry), TLB_NOT_FOUND); + + /* + * Re-install the entry + * Put ourselves in ASID=1 + * Do a load and a store + */ + data = 0xdeadbeef; + tlbw(entry, 1); + set_asid(1); + check32(*(mmu_variable *)new_addr, 0xdeadbeef); + *(mmu_variable *)new_addr = 0xcafebabe; + check32(data, 0xcafebabe); + + /* + * Make sure a load from ASID 2 gets a different value. + * The standalone runtime will create a VA==PA entry on + * a TLB miss, so the load will be reading from uninitialized + * memory. + */ + set_asid(2); + data = 0xdeadbeef; + check32_ne(*(mmu_variable *)new_addr, 0xdeadbeef); + + /* + * Invalidate the ASID and make sure a loads from ASID 1 + * gets a different value. + */ + tlbinvasid(entry >> 32); + set_asid(1); + data = 0xcafebabe; + check32_ne(*(mmu_variable *)new_addr, 0xcafebabe); +} + +int main() +{ + puts("Hexagon MMU ASID test"); + + test_asids(); + + printf("%s\n", ((err) ? "FAIL" : "PASS")); + return err; +} diff --git a/tests/tcg/hexagon/system/mmu_overlap.c b/tests/tcg/hexagon/system/mmu_overlap.c new file mode 100644 index 000000000000..73d0565abed4 --- /dev/null +++ b/tests/tcg/hexagon/system/mmu_overlap.c @@ -0,0 +1,65 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include + +#define DEBUG 0 + +#include "mmu.h" + +DEFAULT_EVENT_HANDLES + +void test_overlap(void) +{ + uint32_t addr = (uint32_t)&data; + uint32_t page = page_start(addr, 20); + uint32_t offset = FIVE_MB; + uint32_t new_page = page + offset; + uint32_t new_addr = addr + offset; + uint8_t data_perm = TLB_X | TLB_W | TLB_R | TLB_U; + uint64_t entry; + + add_trans(1, new_page, page, PAGE_1M, data_perm, 0, 1, 1); + check32(tlbp(0, new_addr), 1); + + /* Check an entry that overlaps with the one we just created */ + entry = + create_mmu_entry(1, 0, 0, 0, new_page, 1, 1, 1, 0, 7, page, PAGE_4K); + check32(tlboc(entry), 1); + /* Check that conditional TLB write (ctlbw) does NOT write the new entry */ + check32(ctlbw(entry, 2), 0x1); + + /* Create an entry that does not overlap with the one we just created */ + entry = create_mmu_entry(1, 0, 0, 0, new_page + ONE_MB, 1, 1, 1, 0, 7, page, + PAGE_4K); + check32(tlboc(entry), TLB_NOT_FOUND); + /* Check that conditional TLB write (ctlbw) does write the new entry */ + check32(ctlbw(entry, 2), TLB_NOT_FOUND); + + /* Create an entry that overalps both of these entries */ + entry = + create_mmu_entry(1, 0, 0, 0, new_page, 1, 1, 1, 0, 7, page, PAGE_4M); + check32(tlboc(entry), 0xffffffff); + + /* Clear the TLB entries */ + remove_trans(1); + check32(tlbp(0, new_addr), TLB_NOT_FOUND); + remove_trans(2); + check32(tlbp(0, (new_addr + ONE_MB)), TLB_NOT_FOUND); +} + +int main() +{ + puts("Hexagon MMU overlap test"); + + test_overlap(); + + printf("%s\n", ((err) ? "FAIL" : "PASS")); + return err; +} diff --git a/tests/tcg/hexagon/system/reg_fields_def.h b/tests/tcg/hexagon/system/reg_fields_def.h new file mode 100644 index 000000000000..ff2769a1399d --- /dev/null +++ b/tests/tcg/hexagon/system/reg_fields_def.h @@ -0,0 +1,87 @@ +/* PTE (aka TLB entry) fields */ +DEF_REG_FIELD(PTE_PPD, + "PPD", 0, 24, + "Physical page number that the corresponding virtual page maps to.") +DEF_REG_FIELD(PTE_C, + "C", 24, 4, + "Cacheability attributes for the page.") +DEF_REG_FIELD(PTE_U, + "U", 28, 1, + "User mode permitted.") +DEF_REG_FIELD(PTE_R, + "R", 29, 1, + "Read-enable.") +DEF_REG_FIELD(PTE_W, + "W", 30, 1, + "Write-enable.") +DEF_REG_FIELD(PTE_X, + "X", 31, 1, + "Execute-enable.") +DEF_REG_FIELD(PTE_VPN, + "VPN", 32, 20, + "Virtual page number that is matched against the load or store address.") +DEF_REG_FIELD(PTE_ASID, + "ASID", 52, 7, + "7-bit address space identifier (tag extender)") +DEF_REG_FIELD(PTE_ATR0, + "ATR0", 59, 1, + "General purpose attribute bit kept as an attribute of each cache line.") +DEF_REG_FIELD(PTE_ATR1, + "ATR1", 60, 1, + "General purpose attribute bit kept as an attribute of each cache line.") +DEF_REG_FIELD(PTE_PA35, + "PA35", 61, 1, + "The Extra Physical bit is the most-significant physical address bit.") +DEF_REG_FIELD(PTE_G, + "G", 62, 1, + "Global bit. If set, then the ASID is ignored in the match.") +DEF_REG_FIELD(PTE_V, + "V", 63, 1, + "Valid bit. indicates whether this entry should be used for matching.") + +/* SSR fields */ +DEF_REG_FIELD(SSR_CAUSE, + "cause", 0, 8, + "8-bit field that contains the reason for various exception.") +DEF_REG_FIELD(SSR_ASID, + "asid", 8, 7, + "7-bit field that contains the Address Space Identifier.") +DEF_REG_FIELD(SSR_UM, + "um", 16, 1, + "read-write bit.") +DEF_REG_FIELD(SSR_EX, + "ex", 17, 1, + "set when an interrupt or exception is accepted.") +DEF_REG_FIELD(SSR_IE, + "ie", 18, 1, + "indicates whether the global interrupt is enabled.") +DEF_REG_FIELD(SSR_GM, + "gm", 19, 1, + "Guest mode bit.") +DEF_REG_FIELD(SSR_V0, + "v0", 20, 1, + "if BADVA0 register contents are from a valid slot 0 instruction.") +DEF_REG_FIELD(SSR_V1, + "v1", 21, 1, + "if BADVA1 register contents are from a valid slot 1 instruction.") +DEF_REG_FIELD(SSR_BVS, + "bvs", 22, 1, + "BADVA Selector.") +DEF_REG_FIELD(SSR_CE, + "ce", 23, 1, + "grants user or guest read permissions to the PCYCLE register aliases.") +DEF_REG_FIELD(SSR_PE, + "pe", 24, 1, + "grants guest read permissions to the PMU register aliases.") +DEF_REG_FIELD(SSR_BP, + "bp", 25, 1, + "Internal Bus Priority bit.") +DEF_REG_FIELD(SSR_XA, + "xa", 27, 3, + "Extension Active, which control operation of an attached coprocessor.") +DEF_REG_FIELD(SSR_SS, + "ss", 30, 1, + "Single Step, which enables single-step exceptions.") +DEF_REG_FIELD(SSR_XE, + "xe", 31, 1, + "Coprocessor Enable, which enables use of an attached coprocessor.") diff --git a/tests/tcg/hexagon/system/tlb-miss-tlblock.S b/tests/tcg/hexagon/system/tlb-miss-tlblock.S new file mode 100644 index 000000000000..fe07aca47b37 --- /dev/null +++ b/tests/tcg/hexagon/system/tlb-miss-tlblock.S @@ -0,0 +1,156 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Test Purpose: + * Verify that tlbmissx and tlbmissrw do not set the syscfg.tl bit + * The HW spec says: + * "TLBLOCK is acquired automatically whenever a hardware thread raises a + * TLB miss-RW or TLBmiss-X exception." + * The casual reader would assume that a miss handler would implicitly have + * the lock, that apparently + * isn't the case. + */ + +.global start +start: + r0 = ##evb + evb = r0 + r0 = ##0 + ssr = r0 + jump #setup + +#define tlb_index r11 +#define stack r29 +#define data r18 +tlb_index = ##0x00000007 + +.org 0x100 + +evb: + jump #reset + jump #nmi + jump #error + jump #0 + jump #tlbmissx + jump #0 + jump #tlbmissrw + + +setup: + { + r1 = ##0xc009b800 + r0 = ##0xf7137010 + } + tlb_index = add(tlb_index, #1) + tlbw(r1:0,tlb_index) + +/* Enable MMU */ + r2 = ##0x0085a07f + syscfg = r2 + +/* Test setup */ + r12 = #0x12 + r0 = #0x6 + r7 = ##0x77777777 + r6 = ##0x66666666 + data = ##0xf2000000 + stack = ##0x9ba01000 + jump ##.L_server_loop + +/* event vector handlers */ +reset: + r2 = #1 + stop(r0) +nmi: + r2 = #1 + stop(r0) +error: + r2 = #1 + stop(r0) + + +/* + * Can only handle a single ex fault. + */ +tlbmissx: + r0 = syscfg + r1 = #0x800 +/* + * Fail if we automatically start setting SYSCFG:TL again + */ + r0 = and(r0, r1) + { + p0 = cmp.eq(r0, r1); if (p0.new) jump:t .Lfailmissx + } + { + r1 = ##0xc009b900 + r0 = ##0xf7137210 + } + tlb_index = add(tlb_index, #1) + tlbw(r1:0,tlb_index) + tlbunlock + rte + stop(r0); +.Lfailmissx: + r2 = #1 + stop(r2); + +/* + * Can only handle a stack fault and a data fault + */ +tlbmissrw: + r0 = syscfg + r1 = #0x800 +/* + * Fail if we automatically start setting SYSCFG:TL again + */ + r0 = and(r0, r1) + { + p0 = cmp.eq(r0, r1); if (p0.new) jump:t .Lfailmissrw + } + r0 = badva + p0 = cmp.eq (stack, r0) // missed the stack + if (!p0) jump .Ldata + { + r1 = ##0xc009ba00 + r0 = ##0xf7137210 + } + jump #.Ldone +.Ldata: + { + r1 = ##0xc00f2000 + r0 = ##0xf71e4010 + } +.Ldone: + tlb_index = add(tlb_index, #1) + tlbw(r1:0,tlb_index) + tlbunlock + rte +.Lfailmissrw: + r2 = #1 + stop(r2); + + + +.org 0x100000 + nop +.Lpass: + r2 = #0 + stop(r0); + trap0(#0x18) +.L_server_loop: +{ + p0 = cmp.eq(r0,#-0x1) + if (!p0.new) jump:t .Lpass + memd(stack) = r7:6; // S1 store to stack will also fault + memw(data) = r12; // S0 store will fault +} +/* + * We should not get here: + */ + r2 = #1 + stop(r0); From 2d874e7a75972d81d5050ff50b82f725f386349b Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 09:21:23 -0800 Subject: [PATCH 1164/1179] tests/tcg/hexagon: add interrupt and priority tests Signed-off-by: Matheus Tavares Bernardino --- tests/tcg/hexagon/Makefile.softmmu-target | 4 ++ tests/tcg/hexagon/system/ciad-siad.c | 50 +++++++++++++++++++++++ tests/tcg/hexagon/system/monitor_insts.S | 18 ++++++++ tests/tcg/hexagon/system/standalone_hw.c | 43 +++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 tests/tcg/hexagon/system/ciad-siad.c create mode 100644 tests/tcg/hexagon/system/monitor_insts.S create mode 100644 tests/tcg/hexagon/system/standalone_hw.c diff --git a/tests/tcg/hexagon/Makefile.softmmu-target b/tests/tcg/hexagon/Makefile.softmmu-target index 7e561efd2582..7fe39ef690ae 100644 --- a/tests/tcg/hexagon/Makefile.softmmu-target +++ b/tests/tcg/hexagon/Makefile.softmmu-target @@ -39,6 +39,8 @@ TESTS_BUILT_WITH_DEFAULT_RULES = \ semihost \ mmu_overlap \ mmu_asids \ + standalone_hw \ + ciad-siad \ $() TESTS += \ @@ -73,6 +75,8 @@ mmu_overlap.o: mmu_overlap.c mmu.h mmu_overlap: mmu_overlap.o mmu_asids.o: mmu_asids.c mmu.h mmu_asids: mmu_asids.o +ciad-siad: ciad-siad.o +standalone_hw: standalone_hw.o monitor_insts.o ############# Custom build options diff --git a/tests/tcg/hexagon/system/ciad-siad.c b/tests/tcg/hexagon/system/ciad-siad.c new file mode 100644 index 000000000000..e3fbb7a506dc --- /dev/null +++ b/tests/tcg/hexagon/system/ciad-siad.c @@ -0,0 +1,50 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include + + +static inline void siad(uint32_t val) +{ + asm volatile ("siad(%0);" + : : "r"(val)); + return; +} +static inline void ciad(uint32_t val) +{ + asm volatile ("ciad(%0);" + : : "r"(val)); + return; +} + +static inline uint32_t getipendad() +{ + uint32_t reg; + asm volatile ("%0=s20;" + : "=r"(reg)); + return reg; +} +int +main(int argc, char *argv[]) +{ + siad(4); + int ipend = getipendad(); + if (ipend != (0x4 << 16)) { + goto fail; + } + ciad(4); + ipend = getipendad(); + if (ipend) { + goto fail; + } + + printf("PASS\n"); + return 0; +fail: + printf("FAIL\n"); + return 1; +} diff --git a/tests/tcg/hexagon/system/monitor_insts.S b/tests/tcg/hexagon/system/monitor_insts.S new file mode 100644 index 000000000000..8027068511f1 --- /dev/null +++ b/tests/tcg/hexagon/system/monitor_insts.S @@ -0,0 +1,18 @@ +/* + * Copyright(c) 2020-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + .text + .type test_set_prio, @function + .global test_set_prio + +test_set_prio: + r0 = #3 + r1 = #1 + p0 = cmp.eq(r0,r1) + setprio(p0, r0) + jumpr lr + + .size test_set_prio, . - test_set_prio diff --git a/tests/tcg/hexagon/system/standalone_hw.c b/tests/tcg/hexagon/system/standalone_hw.c new file mode 100644 index 000000000000..c67343204a80 --- /dev/null +++ b/tests/tcg/hexagon/system/standalone_hw.c @@ -0,0 +1,43 @@ +#include +#include + +void test_set_prio(); + +void inst_test() +{ + asm volatile("dczeroa(r0)\n\t" + "dccleanidx(r0)\n\t" + "dcinvidx(r0)\n\t" + "r1 = dctagr(r0)\n\t" + "dctagw(r0, r1)\n\t" + "dcfetch(r0)\n\t" + "dccleaninvidx(r0)\n\t" + "l2gclean\n\t" + "l2gclean(r1:0)\n\t" + "l2gcleaninv\n\t" + "l2gcleaninv(r1:0)\n\t" + "l2gunlock\n\t" + "l2kill\n\t" + "trace(r0)\n\t" + "pause(#1)\n\t" + ); + + asm volatile("r0 = #0\n\t" + "r1 = iassignr(r0)\n\t" + /* Set interrupt 0 to disabled on all threads */ + "r0 = #0\n\t" + "iassignw(r0)\n\t"); + + test_set_prio(); + printf("Executed monitor mode instructions\n"); +} + +int main(int argc, const char *argv[]) +{ + inst_test(); + printf("Hello, World: (argc: %d)\n", argc); + assert(argc >= 1); + for (int i = 0; i < argc; i++) { + printf("\t> '%s'\n", argv[i]); + } +} From 635bc9464e8ade25d064a1f85611090aad001ab9 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 09:22:54 -0800 Subject: [PATCH 1165/1179] tests/tcg/hexagon: add tests for system registers Signed-off-by: Matheus Tavares Bernardino --- tests/tcg/hexagon/Makefile.softmmu-target | 5 + tests/tcg/hexagon/system/badva.c | 335 ++++++++++++++++++++++ tests/tcg/hexagon/system/vid_reg.c | 36 +++ 3 files changed, 376 insertions(+) create mode 100644 tests/tcg/hexagon/system/badva.c create mode 100644 tests/tcg/hexagon/system/vid_reg.c diff --git a/tests/tcg/hexagon/Makefile.softmmu-target b/tests/tcg/hexagon/Makefile.softmmu-target index 7fe39ef690ae..3187194b0a61 100644 --- a/tests/tcg/hexagon/Makefile.softmmu-target +++ b/tests/tcg/hexagon/Makefile.softmmu-target @@ -41,6 +41,8 @@ TESTS_BUILT_WITH_DEFAULT_RULES = \ mmu_asids \ standalone_hw \ ciad-siad \ + badva \ + vid_reg \ $() TESTS += \ @@ -77,6 +79,9 @@ mmu_asids.o: mmu_asids.c mmu.h mmu_asids: mmu_asids.o ciad-siad: ciad-siad.o standalone_hw: standalone_hw.o monitor_insts.o +vid_reg: vid_reg.o +badva.o: badva.c ../hex_test.h crt0/hexagon_standalone.h +badva: badva.o ############# Custom build options diff --git a/tests/tcg/hexagon/system/badva.c b/tests/tcg/hexagon/system/badva.c new file mode 100644 index 000000000000..1351269d1077 --- /dev/null +++ b/tests/tcg/hexagon/system/badva.c @@ -0,0 +1,335 @@ +/* + * Copyright(c) 2019-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "crt0/hexagon_standalone.h" + +#define DEBUG 0 + +int err; +#include "../hex_test.h" + +/* volatile because it is written through different MMU mappings */ +typedef volatile int mmu_variable; +mmu_variable data0 = 0xdeadbeef; +mmu_variable data1 = 0xabcdef01; + +#define ONE_MB (1 << 20) +#define INVALID_BADVA 0xbadabada + +static uint32_t read_badva(void) +{ + uint32_t ret; + __asm__ __volatile__("%0 = badva\n\t" : "=r"(ret)); + return ret; +} + +static uint32_t read_badva0(void) +{ + uint32_t ret; + __asm__ __volatile__("%0 = badva0\n\t" : "=r"(ret)); + return ret; +} + +static uint32_t read_badva1(void) +{ + uint32_t ret; + __asm__ __volatile__("%0 = badva1\n\t" : "=r"(ret)); + return ret; +} + +static uint32_t read_ssr(void) +{ + uint32_t ret; + __asm__ __volatile__("%0 = ssr\n\t" : "=r"(ret)); + return ret; +} + +static void write_badva0(uint32_t val) +{ + __asm__ __volatile__("badva0=%0;" : : "r"(val)); + return; +} + +static void write_badva1(uint32_t val) +{ + __asm__ __volatile__("badva1=%0;" : : "r"(val)); + return; +} + +#define SSR_V0_BIT 20 +#define SSR_V1_BIT 21 +#define SSR_BVS_BIT 21 + +static uint32_t read_ssr_v0(void) +{ + return (read_ssr() >> SSR_V0_BIT) & 0x1; +} + +static uint32_t read_ssr_v1(void) +{ + return (read_ssr() >> SSR_V1_BIT) & 0x1; +} + +static uint32_t read_ssr_bvs(void) +{ + return (read_ssr() >> SSR_BVS_BIT) & 0x1; +} + +static void dual_store(mmu_variable *p, mmu_variable *q, uint32_t pval, + uint32_t qval) +{ +#if DEBUG + printf("dual_store:\t0x%p, 0x%p, 0x%lx, 0x%lx\n", p, q, pval, qval); +#endif + + __asm__ __volatile__("r6 = #0\n\t" + "badva0 = r6\n\t" + "badva1 = r6\n\t" + "r6 = ssr\n\t" + "r6 = clrbit(r6, #%4) // V0\n\t" + "r6 = clrbit(r6, #%5) // V1\n\t" + "r6 = clrbit(r6, #%6) // BVS\n\t" + "ssr = r6\n\t" + "{\n\t" + " memw(%0) = %2 // slot 1\n\t" + " memw(%1) = %3 // slot 0\n\t" + "}\n\t" + : "=m"(*p), "=m"(*q) + : "r"(pval), "r"(qval), "i"(SSR_V0_BIT), + "i"(SSR_V1_BIT), "i"(SSR_BVS_BIT) + : "r6"); +} + +static void dual_load(mmu_variable *p, mmu_variable *q, uint32_t *pval, + uint32_t *qval) +{ + uint32_t val0, val1; + +#if DEBUG + printf("dual_load:\t0x%p, 0x%p\n", p, q); +#endif + + __asm__ __volatile__("r6 = #0\n\t" + "badva0 = r6\n\t" + "badva1 = r6\n\t" + "r6 = ssr\n\t" + "r6 = clrbit(r6, #%4) // V0\n\t" + "r6 = clrbit(r6, #%5) // V1\n\t" + "r6 = clrbit(r6, #%6) // BVS\n\t" + "ssr = r6\n\t" + "{\n\t" + " %1 = memw(%3) // slot 1\n\t" + " %0 = memw(%2) // slot 0\n\t" + "}\n\t" + : "=r"(val0), "=r"(val1) + : "m"(*p), "m"(*q), "i"(SSR_V0_BIT), "i"(SSR_V1_BIT), + "i"(SSR_BVS_BIT) + : "r6"); + +#if DEBUG + printf("\t\t0x%lx, 0x%lx\n", val0, val1); +#endif + + *pval = val0; + *qval = val1; +} + +static void load_store(mmu_variable *p, mmu_variable *q, uint32_t *pval, + uint32_t qval) +{ + uint32_t val; + +#if DEBUG + printf("load_store:\t0x%p, 0x%p, 0x%lx\n", p, q, qval); +#endif + + __asm__ __volatile__("r6 = #0\n\t" + "badva0 = r6\n\t" + "badva1 = r6\n\t" + "r6 = ssr\n\t" + "r6 = clrbit(r6, #%4) // V0\n\t" + "r6 = clrbit(r6, #%5) // V1\n\t" + "r6 = clrbit(r6, #%6) // BVS\n\t" + "ssr = r6\n\t" + "{\n\t" + " %0 = memw(%2) // slot 1\n\t" + " memw(%1) = %3 // slot 0\n\t" + "}\n\t" + : "=r"(val), "=m"(*q) + : "m"(*p), "r"(qval), "i"(SSR_V0_BIT), "i"(SSR_V1_BIT), + "i"(SSR_BVS_BIT) + : "r6"); + +#if DEBUG + printf("\t\t0x%lx\n", val); +#endif + + *pval = val; +} + +enum { + TLB_U = (1 << 0), + TLB_R = (1 << 1), + TLB_W = (1 << 2), + TLB_X = (1 << 3), +}; + +uint32_t add_trans_pgsize(uint32_t page_size_bits) +{ + switch (page_size_bits) { + case 12: /* 4KB */ + return 1; + case 14: /* 16KB */ + return 2; + case 16: /* 64KB */ + return 4; + case 18: /* 256KB */ + return 8; + case 20: /* 1MB */ + return 16; + case 22: /* 4MB */ + return 32; + case 24: /* 16MB */ + return 64; + default: + return 1; + } +} + +int mb_counter = 1; + +static mmu_variable *map_data_address(mmu_variable *p, uint32_t data_offset) +{ + uint32_t page_size_bits = 12; + uint32_t page_size = 1 << page_size_bits; + uint32_t page_align = ~(page_size - 1); + + uint32_t data_addr = (uint32_t)p; + uint32_t data_page = data_addr & page_align; + + uint32_t new_data_page = data_page + data_offset; + uint32_t read_data_addr = data_addr + data_offset; + unsigned int data_perm = TLB_X | TLB_W | TLB_U; + add_translation((void *)new_data_page, (void *)data_page, 0); + + return (mmu_variable *)read_data_addr; +} + +static void test_dual_store(void) +{ + data0 = 0x12345678; + data1 = 0x87654321; + + mmu_variable *new_data0 = map_data_address(&data0, mb_counter * ONE_MB); + mb_counter++; + mmu_variable *new_data1 = map_data_address(&data1, mb_counter * ONE_MB); + mb_counter++; + + dual_store(new_data0, new_data1, 0x1, 0x2); + if (read_badva() == (uint32_t)new_data0) { + check32(read_badva0(), (uint32_t)new_data0); + check32(read_badva1(), INVALID_BADVA); + check32(read_ssr_v0(), 1); + check32(read_ssr_v1(), 0); + check32(read_ssr_bvs(), 0); + } else if (read_badva() == (uint32_t)new_data1) { + check32(read_badva0(), INVALID_BADVA); + check32(read_badva1(), (uint32_t)new_data1); + check32(read_ssr_v0(), 0); + check32(read_ssr_v1(), 1); + check32(read_ssr_bvs(), 1); + } else { + /* Something went wrong! */ + check32(0, 1); + } + check32(data0, 0x1); + check32(data1, 0x2); +} + +static void test_dual_load(void) +{ + uint32_t val0, val1; + + data0 = 0xaabbccdd; + data1 = 0xeeff0011; + + mmu_variable *new_data0 = map_data_address(&data0, mb_counter * ONE_MB); + mb_counter++; + mmu_variable *new_data1 = map_data_address(&data1, mb_counter * ONE_MB); + mb_counter++; + + dual_load(new_data0, new_data1, &val0, &val1); + if (read_badva() == (uint32_t)new_data0) { + check32(read_badva0(), (uint32_t)new_data0); + check32(read_badva1(), INVALID_BADVA); + check32(read_ssr_v0(), 1); + check32(read_ssr_v1(), 0); + check32(read_ssr_bvs(), 0); + } else if (read_badva() == (uint32_t)new_data1) { + check32(read_badva0(), INVALID_BADVA); + check32(read_badva1(), (uint32_t)new_data1); + check32(read_ssr_v0(), 0); + check32(read_ssr_v1(), 1); + check32(read_ssr_bvs(), 1); + } else { + /* Something went wrong! */ + check32(0, 1); + } + check32(val0, 0xaabbccdd); + check32(val1, 0xeeff0011); +} + +static void test_load_store(void) +{ + uint32_t val; + + data0 = 0x11223344; + data1 = 0x55667788; + + mmu_variable *new_data0 = map_data_address(&data0, mb_counter * ONE_MB); + mb_counter++; + mmu_variable *new_data1 = map_data_address(&data1, mb_counter * ONE_MB); + mb_counter++; + + load_store(new_data0, new_data1, &val, 0x123); + if (read_badva() == (uint32_t)new_data1) { + check32(read_badva0(), (uint32_t)new_data1); + check32(read_badva1(), INVALID_BADVA); + check32(read_ssr_v0(), 1); + check32(read_ssr_v1(), 0); + check32(read_ssr_bvs(), 0); + } else if (read_badva() == (uint32_t)new_data0) { + check32(read_badva0(), INVALID_BADVA); + check32(read_badva1(), (uint32_t)new_data0); + check32(read_ssr_v0(), 0); + check32(read_ssr_v1(), 1); + check32(read_ssr_bvs(), 1); + } else { + /* Something went wrong! */ + check32(0, 1); + } + check32(val, 0x11223344); + check32(data1, 0x123); +} +static void test_badva_write(void) +{ + uint32_t va = 0x11223344; + write_badva0(va); + check32(read_badva(), va); +} + +int main() +{ + puts("Hexagon badva test"); + + test_dual_store(); + test_dual_load(); + test_load_store(); + test_badva_write(); + + printf("%s\n", ((err) ? "FAIL" : "PASS")); + return err; +} diff --git a/tests/tcg/hexagon/system/vid_reg.c b/tests/tcg/hexagon/system/vid_reg.c new file mode 100644 index 000000000000..25f266f98b2d --- /dev/null +++ b/tests/tcg/hexagon/system/vid_reg.c @@ -0,0 +1,36 @@ +/* + * Verify vid reads/writes really update the register. + */ + +#include +#include +#include + +static inline uint32_t getvid() +{ + uint32_t reg; + asm volatile("%0=vid;" : "=r"(reg)); + return reg; +} +static inline void setvid(uint32_t val) +{ + asm volatile("vid=%0;" : : "r"(val)); + return; +} +int main() +{ + uint32_t testval = 0x3ff03ff; + setvid(testval); + if (testval != getvid()) { + printf("ERROR: vid read returned: 0x%x\n", getvid()); + } + assert(testval == getvid()); + + /* L2VIC_NO_PENDING (0xffffffff) should not update the vid */ + setvid(0xffffffff); + if (testval != getvid()) { + printf("ERROR: vid read returned: 0x%x\n", getvid()); + } + + assert(testval == getvid()); +} From 08f40fecf62244114baffaba8f943b5727af5bc5 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 09:33:47 -0800 Subject: [PATCH 1166/1179] tests/tcg/hexagon: add HVX tests Signed-off-by: Matheus Tavares Bernardino --- tests/tcg/hexagon/Makefile.softmmu-target | 9 + tests/tcg/hexagon/system/cfgtable.h | 39 + tests/tcg/hexagon/system/hvx-multi.c | 119 ++ tests/tcg/hexagon/system/standalone_vec.c | 1419 +++++++++++++++++++++ 4 files changed, 1586 insertions(+) create mode 100644 tests/tcg/hexagon/system/cfgtable.h create mode 100644 tests/tcg/hexagon/system/hvx-multi.c create mode 100644 tests/tcg/hexagon/system/standalone_vec.c diff --git a/tests/tcg/hexagon/Makefile.softmmu-target b/tests/tcg/hexagon/Makefile.softmmu-target index 3187194b0a61..3f070bfea91b 100644 --- a/tests/tcg/hexagon/Makefile.softmmu-target +++ b/tests/tcg/hexagon/Makefile.softmmu-target @@ -43,6 +43,8 @@ TESTS_BUILT_WITH_DEFAULT_RULES = \ ciad-siad \ badva \ vid_reg \ + hvx-multi \ + standalone_vec \ $() TESTS += \ @@ -80,11 +82,18 @@ mmu_asids: mmu_asids.o ciad-siad: ciad-siad.o standalone_hw: standalone_hw.o monitor_insts.o vid_reg: vid_reg.o +hvx-multi.o: hvx-multi.c ../hvx_misc.h +hvx-multi: hvx-multi.o +standalone_vec.o: standalone_vec.c cfgtable.h +standalone_vec: standalone_vec.o badva.o: badva.c ../hex_test.h crt0/hexagon_standalone.h badva: badva.o ############# Custom build options +standalone_vec.o: CFLAGS+= -mv69 -O2 -mhvx -fvectorize +hvx-multi.o: CFLAGS+= -O2 -mhvx + # We don't want to link this one with crt0 files tlb-miss-tlblock: tlb-miss-tlblock.o $(CC) $(CFLAGS) $< -o $@ -nostartfiles -Wl,-Ttext,0x9b800000 -Wl,-entry,0x9b800000 diff --git a/tests/tcg/hexagon/system/cfgtable.h b/tests/tcg/hexagon/system/cfgtable.h new file mode 100644 index 000000000000..fff84ef56950 --- /dev/null +++ b/tests/tcg/hexagon/system/cfgtable.h @@ -0,0 +1,39 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CFGTABLE_H +#define CFGTABLE_H + +#include + +static uint32_t read_cfgtable_field(uint32_t offset) +{ + uint32_t val; + asm volatile("r0 = cfgbase\n\t" + "r0 = asl(r0, #5)\n\t" + "%0 = memw_phys(%1, r0)\n\t" + : "=r"(val) + : "r"(offset) + : "r0"); + return val; +} + +#define GET_SUBSYSTEM_BASE() (read_cfgtable_field(0x8) << 16) +#define GET_FASTL2VIC_BASE() (read_cfgtable_field(0x28) << 16) + +static uintptr_t get_vtcm_base(void) +{ +#if __HEXAGON_ARCH__ == 65 + return 0xD8200000L; +#elif __HEXAGON_ARCH__ >= 66 + int vtcm_offset = 0x038; + return read_cfgtable_field(vtcm_offset) << 16; +#else +#error "unsupported hexagon revision" +#endif +} + +#endif /* CFGTABLE_H */ diff --git a/tests/tcg/hexagon/system/hvx-multi.c b/tests/tcg/hexagon/system/hvx-multi.c new file mode 100644 index 000000000000..0d2e90c2c79b --- /dev/null +++ b/tests/tcg/hexagon/system/hvx-multi.c @@ -0,0 +1,119 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include + +int err; + +#include "../hvx_misc.h" + +void set_hvx_context(int n) +{ + uint32_t ssr_context_bits = n << 27; + asm volatile( + "r1 = ssr\n" + "r1 = and(r1, ##0xc7ffffff)\n" + "r1 = or(r1, %0)\n" + "ssr = r1\r" + "isync\n" + : + : "r"(ssr_context_bits) + : "r1" + ); +} + +void setv0(int n) +{ + asm volatile( + "v0 = vsplat(%0)\n" + : : "r"(n) : "v0" + ); +} + +void store_v0(MMVector *v) +{ + asm volatile( + "vmemu(%0) = v0\n" + : + : "r"(v) + : "memory" + ); +} + +uint32_t get_num_contexts(void) +{ + const int EXT_CONTEXT_OFFSET = 13; + unsigned int cfgbase; + asm volatile("%0 = cfgbase\n" : "=r"(cfgbase)); + uint32_t *cfgtable = (uint32_t *)(cfgbase << 16); + return *(cfgtable + EXT_CONTEXT_OFFSET); +} + +uint32_t get_rev(void) +{ + uint32_t rev; + asm volatile("%0 = rev\n" : "=r"(rev)); + return rev; +} + +/* + * This test verifies that each new context is properly selected and is + * independent of the thread. + */ +int main() +{ + int num_contexts = get_num_contexts(); + printf("rev=v%x, HVX-contexts=%d\n", (int)(get_rev() & 0xff), num_contexts); + memset(&output[0], 0, 8 * sizeof(MMVector)); + + /* First set v0 on all the contexts. */ + for (int i = 0; i < num_contexts; i++) { + set_hvx_context(i); + setv0(i + 1); + } + + /* + * Now each context should have its own v0 value. Save it to memory. We + * check all possible SSR.XA values to make sure the "aliases" are + * implemented correctly. + */ + for (int i = 0; i < 8; i++) { + set_hvx_context(i); + store_v0(&output[i]); + } + + + /* + * Set expected values: + * + * num contexts + * SSR.XA 2 4 6 8 + * 000 HVX Context 0 HVX Context 0 HVX Context 0 HVX Context 0 + * 001 HVX Context 1 HVX Context 1 HVX Context 1 HVX Context 1 + * 010 HVX Context 0 HVX Context 2 HVX Context 2 HVX Context 2 + * 011 HVX Context 1 HVX Context 3 HVX Context 3 HVX Context 3 + * 100 HVX Context 0 HVX Context 0 HVX Context 4 HVX Context 4 + * 101 HVX Context 1 HVX Context 1 HVX Context 5 HVX Context 5 + * 110 HVX Context 0 HVX Context 2 HVX Context 2 HVX Context 6 + * 111 HVX Context 1 HVX Context 3 HVX Context 3 HVX Context 7 + */ + for (int i = 0; i < 8; i++) { + int expected = (i % num_contexts) + 1; + /* Exception for num_contexts=6 */ + if (num_contexts == 6 && i >= 6) { + expected = (i - 6 + 2) + 1; + } + for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) { + expect[i].w[j] = expected; + } + } + + check_output_w(__LINE__, 8); + puts(err ? "FAIL" : "PASS"); + return !!err; +} diff --git a/tests/tcg/hexagon/system/standalone_vec.c b/tests/tcg/hexagon/system/standalone_vec.c new file mode 100644 index 000000000000..eb1b2ef4830c --- /dev/null +++ b/tests/tcg/hexagon/system/standalone_vec.c @@ -0,0 +1,1419 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include + +#include +#include + +#include "cfgtable.h" + +int err; + +#ifdef __linux__ +#define VTCM_SIZE_KB (2048) +#define VTCM_BYTES_PER_KB (1024) + +static char vtcm_buffer[VTCM_SIZE_KB * VTCM_BYTES_PER_KB] + __attribute__((aligned(0x10000))); +#endif + +/* define the number of rows/cols in a square matrix */ +#define MATRIX_SIZE 64 + +/* define the size of the scatter buffer */ +#define SCATTER_BUFFER_SIZE (MATRIX_SIZE * MATRIX_SIZE) + +#define SCATTER16_BUF_SIZE (2 * SCATTER_BUFFER_SIZE) +#define SCATTER32_BUF_SIZE (4 * SCATTER_BUFFER_SIZE) + +#define GATHER16_BUF_SIZE (2 * MATRIX_SIZE) +#define GATHER32_BUF_SIZE (4 * MATRIX_SIZE) + +uintptr_t VTCM_BASE_ADDRESS; +uintptr_t VTCM_SCATTER16_ADDRESS; +uintptr_t VTCM_GATHER16_ADDRESS; +uintptr_t VTCM_SCATTER32_ADDRESS; +uintptr_t VTCM_GATHER32_ADDRESS; +uintptr_t VTCM_SCATTER16_32_ADDRESS; +uintptr_t VTCM_GATHER16_32_ADDRESS; + +/* the vtcm base address */ +unsigned char *vtcm_base; + +/* scatter gather 16 bit elements using 16 bit offsets */ +unsigned short *vscatter16; +unsigned short *vgather16; +unsigned short vscatter16_ref[SCATTER_BUFFER_SIZE]; +unsigned short vgather16_ref[MATRIX_SIZE]; + +/* scatter gather 32 bit elements using 32 bit offsets */ +unsigned int *vscatter32; +unsigned int *vgather32; +unsigned int vscatter32_ref[SCATTER_BUFFER_SIZE]; +unsigned int vgather32_ref[MATRIX_SIZE]; + +/* scatter gather 16 bit elements using 32 bit offsets */ +unsigned short *vscatter16_32; +unsigned short *vgather16_32; +unsigned short vscatter16_32_ref[SCATTER_BUFFER_SIZE]; +unsigned short vgather16_32_ref[MATRIX_SIZE]; + + +/* declare the arrays of offsets */ +unsigned short half_offsets[MATRIX_SIZE]; +unsigned int word_offsets[MATRIX_SIZE]; + +/* declare the arrays of values */ +unsigned short half_values[MATRIX_SIZE]; +unsigned short half_acc_values[MATRIX_SIZE]; +unsigned short half_q_values[MATRIX_SIZE]; +unsigned int word_values[MATRIX_SIZE]; +unsigned int word_acc_values[MATRIX_SIZE]; +unsigned int word_q_values[MATRIX_SIZE]; + +/* declare the array of predicates */ +unsigned short half_predicates[MATRIX_SIZE]; +unsigned int word_predicates[MATRIX_SIZE]; + +/* make this big enough for all the intrinsics */ +unsigned int region_len = 4 * SCATTER_BUFFER_SIZE - 1; + +/* optionally add sync instructions */ +#define SYNC_VECTOR 1 + +/* optionally print cycle counts */ +#define PRINT_CYCLE_COUNTS 0 + +#if PRINT_CYCLE_COUNTS +unsigned long long start_cycles; +#define START_CYCLES start_cycles = hexagon_sim_read_pcycles(); +#define PRINT_CYCLES(x) printf(x, hexagon_sim_read_pcycles() - start_cycles); +#else +#define START_CYCLES +#define PRINT_CYCLES(x) +#endif + +/* define a scratch area for debug and prefill */ +#define SCRATCH_SIZE 0x8800 + +#define FILL_CHAR '.' + +/* fill vtcm scratch with ee */ +void prefill_vtcm_scratch(void) +{ + memset((void *)VTCM_BASE_ADDRESS, FILL_CHAR, SCRATCH_SIZE * sizeof(char)); +} + +/* print vtcm scratch buffer */ +void print_vtcm_scratch_16(void) +{ + unsigned short *vtmp = (unsigned short *)VTCM_BASE_ADDRESS; + + printf("\n\nPrinting the vtcm scratch in half words"); + + for (int i = 0; i < SCRATCH_SIZE; i++) { + if ((i % MATRIX_SIZE) == 0) { + printf("\n"); + } + for (int j = 0; j < 2; j++) { + printf("%c", (char)((vtmp[i] >> j * 8) & 0xff)); + } + + printf(" "); + } +} + +/* print vtcm scratch buffer */ +void print_vtcm_scratch_32(void) +{ + unsigned int *vtmp = (unsigned int *)VTCM_BASE_ADDRESS; + + printf("\n\nPrinting the vtcm scratch in words"); + + for (int i = 0; i < SCRATCH_SIZE; i++) { + if ((i % MATRIX_SIZE) == 0) { + printf("\n"); + } + for (int j = 0; j < 4; j++) { + printf("%c", (char)((vtmp[i] >> j * 8) & 0xff)); + } + + printf(" "); + } +} + + +/* create byte offsets to be a diagonal of the matrix with 16 bit elements */ +void create_offsets_and_values_16(void) +{ + unsigned short half_element = 0; + unsigned short half_q_element = 0; + char letter = 'A'; + char q_letter = '@'; + + for (int i = 0; i < MATRIX_SIZE; i++) { + half_offsets[i] = i * (2 * MATRIX_SIZE + 2); + + half_element = 0; + half_q_element = 0; + for (int j = 0; j < 2; j++) { + half_element |= letter << j * 8; + half_q_element |= q_letter << j * 8; + } + + half_values[i] = half_element; + half_acc_values[i] = ((i % 10) << 8) + (i % 10); + half_q_values[i] = half_q_element; + + letter++; + /* reset to 'A' */ + if (letter == 'M') { + letter = 'A'; + } + } +} + +/* create a predicate mask for the half word scatter */ +void create_preds_16() +{ + for (int i = 0; i < MATRIX_SIZE; i++) { + half_predicates[i] = (i % 3 == 0 || i % 5 == 0) ? ~0 : 0; + } +} + + +/* create byte offsets to be a diagonal of the matrix with 32 bit elements */ +void create_offsets_and_values_32(void) +{ + unsigned int word_element = 0; + unsigned int word_q_element = 0; + char letter = 'A'; + char q_letter = '&'; + + for (int i = 0; i < MATRIX_SIZE; i++) { + word_offsets[i] = i * (4 * MATRIX_SIZE + 4); + + word_element = 0; + word_q_element = 0; + for (int j = 0; j < 4; j++) { + word_element |= letter << j * 8; + word_q_element |= q_letter << j * 8; + } + + word_values[i] = word_element; + word_acc_values[i] = ((i % 10) << 8) + (i % 10); + word_q_values[i] = word_q_element; + + letter++; + /* reset to 'A' */ + if (letter == 'M') { + letter = 'A'; + } + } +} + +/* create a predicate mask for the word scatter */ +void create_preds_32() +{ + for (int i = 0; i < MATRIX_SIZE; i++) { + word_predicates[i] = (i % 4 == 0 || i % 7 == 0) ? ~0 : 0; + } +} + + +void dump_buf(char *str, void *addr, int element_size, int byte_len) + +{ + unsigned short *sptr = addr; + unsigned int *ptr = addr; + + printf("\n\nBuffer: %s\n", str); + for (int i = 0; i < byte_len / element_size; ++ptr, ++sptr, ++i) { + if (i != 0 && (i % 16) == 0) { + printf("\n"); + } + if (element_size == 2) { + printf("%c ", *sptr); + } else if (element_size == 4) { + printf("%4.4x ", *ptr); + } + } +} + +/* + * create byte offsets to be a diagonal of the matrix with 16 bit elements and + * 32 bit offsets + */ +void create_offsets_and_values_16_32(void) +{ + unsigned int half_element = 0; + unsigned short half_q_element = 0; + char letter = 'D'; + char q_letter = '$'; + + for (int i = 0; i < MATRIX_SIZE; i++) { + word_offsets[i] = i * (2 * MATRIX_SIZE + 2); + + half_element = 0; + half_q_element = 0; + for (int j = 0; j < 2; j++) { + half_element |= letter << j * 8; + half_q_element |= q_letter << j * 8; + } + + half_values[i] = half_element; + half_acc_values[i] = ((i % 10) << 8) + (i % 10); + half_q_values[i] = half_q_element; + + letter++; + /* reset to 'A' */ + if (letter == 'P') { + letter = 'D'; + } + } + + /* + * dump_buf("word_offsets", word_offsets, sizeof(*word_offsets), + * sizeof(word_offsets)); dump_buf("half_offsets", half_offsets, + * sizeof(*half_offsets), sizeof(half_offsets)); + */ +} + +void create_preds_16_32() +{ + for (int i = 0; i < MATRIX_SIZE; i++) { + half_predicates[i] = (i % 2 == 0 || i % 13 == 0) ? ~0 : 0; + } +} + +#define SCATTER_RELEASE(ADDR) \ + asm volatile("vmem(%0 + #0):scatter_release\n" : : "r"(ADDR)); + +/* scatter the 16 bit elements using intrinsics */ +void vector_scatter_16(void) +{ + START_CYCLES; + + /* copy the offsets and values to vectors */ + HVX_Vector offsets = *(HVX_Vector *)half_offsets; + HVX_Vector values = *(HVX_Vector *)half_values; + + /* do the scatter */ + Q6_vscatter_RMVhV(VTCM_SCATTER16_ADDRESS, region_len, offsets, values); + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter16); + /* + * This dummy load from vscatter16 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter16; +#endif + + PRINT_CYCLES("\nVector Scatter 16 cycles = %llu\n"); +} + +/* scatter-accumulate the 16 bit elements using intrinsics */ +void vector_scatter_acc_16(void) +{ + START_CYCLES; + + /* copy the offsets and values to vectors */ + HVX_Vector offsets = *(HVX_Vector *)half_offsets; + HVX_Vector values = *(HVX_Vector *)half_acc_values; + + /* do the scatter */ + Q6_vscatteracc_RMVhV(VTCM_SCATTER16_ADDRESS, region_len, offsets, values); + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter16); + /* + * This dummy load from vscatter16 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter16; +#endif + + PRINT_CYCLES("\nVector Scatter Acc 16 cycles = %llu\n"); +} + +/* scatter the 16 bit elements using intrinsics */ +void vector_scatter_q_16(void) +{ + START_CYCLES; + + /* copy the offsets and values to vectors */ + HVX_Vector offsets = *(HVX_Vector *)half_offsets; + HVX_Vector values = *(HVX_Vector *)half_q_values; + HVX_Vector pred_reg = *(HVX_Vector *)half_predicates; + HVX_VectorPred preds = Q6_Q_vand_VR(pred_reg, ~0); + + /* do the scatter */ + Q6_vscatter_QRMVhV(preds, VTCM_SCATTER16_ADDRESS, region_len, offsets, + values); + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter16); + /* + * This dummy load from vscatter16 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter16; +#endif + + PRINT_CYCLES("\nVector Scatter Q 16 cycles = %llu\n"); +} + +/* scatter the 32 bit elements using intrinsics */ +void vector_scatter_32(void) +{ + START_CYCLES; + + /* copy the offsets and values to vectors */ + HVX_Vector offsetslo = *(HVX_Vector *)word_offsets; + HVX_Vector offsetshi = *(HVX_Vector *)&word_offsets[MATRIX_SIZE / 2]; + HVX_Vector valueslo = *(HVX_Vector *)word_values; + HVX_Vector valueshi = *(HVX_Vector *)&word_values[MATRIX_SIZE / 2]; + + /* do the scatter */ + Q6_vscatter_RMVwV(VTCM_SCATTER32_ADDRESS, region_len, offsetslo, valueslo); + Q6_vscatter_RMVwV(VTCM_SCATTER32_ADDRESS, region_len, offsetshi, valueshi); + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter32); + /* + * This dummy load from vscatter32 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter32; +#endif + + PRINT_CYCLES("\nVector Scatter 32 cycles = %llu\n"); +} + +/* scatter-acc the 32 bit elements using intrinsics */ +void vector_scatter_acc_32(void) +{ + START_CYCLES; + + /* copy the offsets and values to vectors */ + HVX_Vector offsetslo = *(HVX_Vector *)word_offsets; + HVX_Vector offsetshi = *(HVX_Vector *)&word_offsets[MATRIX_SIZE / 2]; + HVX_Vector valueslo = *(HVX_Vector *)word_acc_values; + HVX_Vector valueshi = *(HVX_Vector *)&word_acc_values[MATRIX_SIZE / 2]; + + /* do the scatter */ + Q6_vscatteracc_RMVwV(VTCM_SCATTER32_ADDRESS, region_len, offsetslo, + valueslo); + Q6_vscatteracc_RMVwV(VTCM_SCATTER32_ADDRESS, region_len, offsetshi, + valueshi); + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter32); + /* + * This dummy load from vscatter32 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter32; +#endif + + PRINT_CYCLES("\nVector Scatter Acc 32 cycles = %llu\n"); +} + +/* scatter the 32 bit elements using intrinsics */ +void vector_scatter_q_32(void) +{ + START_CYCLES; + + /* copy the offsets and values to vectors */ + HVX_Vector offsetslo = *(HVX_Vector *)word_offsets; + HVX_Vector offsetshi = *(HVX_Vector *)&word_offsets[MATRIX_SIZE / 2]; + HVX_Vector valueslo = *(HVX_Vector *)word_q_values; + HVX_Vector valueshi = *(HVX_Vector *)&word_q_values[MATRIX_SIZE / 2]; + HVX_Vector pred_reglo = *(HVX_Vector *)word_predicates; + HVX_Vector pred_reghi = *(HVX_Vector *)&word_predicates[MATRIX_SIZE / 2]; + HVX_VectorPred predslo = Q6_Q_vand_VR(pred_reglo, ~0); + HVX_VectorPred predshi = Q6_Q_vand_VR(pred_reghi, ~0); + + /* do the scatter */ + Q6_vscatter_QRMVwV(predslo, VTCM_SCATTER32_ADDRESS, region_len, offsetslo, + valueslo); + Q6_vscatter_QRMVwV(predshi, VTCM_SCATTER32_ADDRESS, region_len, offsetshi, + valueshi); + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter16); + /* + * This dummy load from vscatter16 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter16; +#endif + + PRINT_CYCLES("\nVector Scatter Q 16 cycles = %llu\n"); +} + +void print_vector(char *str, HVX_Vector *v) + +{ + unsigned char *ptr = (unsigned char *)v; + + printf("\n\nVector: %s\n", str); + for (int i = 0; i < sizeof(HVX_Vector) * 4; ++ptr, ++i) { + if (i != 0 && (i % 16) == 0) { + printf("\n"); + } + printf("%c ", *ptr); + } + printf("\n"); +} + +void print_vectorpair(char *str, HVX_VectorPair *v) + +{ + unsigned char *ptr = (unsigned char *)v; + + printf("\n\nVectorPair: %s\n", str); + for (int i = 0; i < sizeof(HVX_VectorPair); ++ptr, ++i) { + if (i != 0 && (i % 16) == 0) { + printf("\n"); + } + printf("%c ", *ptr); + } + printf("\n"); +} + +/* scatter the 16 bit elements with 32 bit offsets using intrinsics */ +void vector_scatter_16_32(void) +{ + START_CYCLES; + + /* get the word offsets in a vector pair */ + HVX_VectorPair offsets = *(HVX_VectorPair *)word_offsets; + /* print_vectorpair("word_offsets", (HVX_VectorPair *)&word_offsets); */ + + /* these values need to be shuffled for the RMWwV scatter */ + HVX_Vector values = *(HVX_Vector *)half_values; + values = Q6_Vh_vshuff_Vh(values); + /* print_vector("values", (HVX_Vector *)&values); */ + + /* do the scatter */ + Q6_vscatter_RMWwV(VTCM_SCATTER16_32_ADDRESS, region_len, offsets, values); + /* print_vector("scatter16_32_address", (HVX_Vector */ + /* *)VTCM_SCATTER16_32_ADDRESS); */ + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter16_32); + /* + * This dummy load from vscatter16_32 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter16_32; +#endif + + PRINT_CYCLES("\nVector Scatter 16_32 cycles = %llu\n"); +} + +/* scatter-acc the 16 bit elements with 32 bit offsets using intrinsics */ +void vector_scatter_acc_16_32(void) +{ + START_CYCLES; + + /* get the word offsets in a vector pair */ + HVX_VectorPair offsets = *(HVX_VectorPair *)word_offsets; + /* print_vectorpair("word_offsets", (HVX_VectorPair *)&word_offsets); */ + + /* these values need to be shuffled for the RMWwV scatter */ + HVX_Vector values = *(HVX_Vector *)half_acc_values; + values = Q6_Vh_vshuff_Vh(values); + /* print_vector("values", (HVX_Vector *)&values); */ + + /* do the scatter */ + Q6_vscatteracc_RMWwV(VTCM_SCATTER16_32_ADDRESS, region_len, offsets, + values); + /* print_vector("scatter16_32_address", (HVX_Vector */ + /* *)VTCM_SCATTER16_32_ADDRESS); */ + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter16_32); + /* + * This dummy load from vscatter16_32 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter16_32; +#endif + + PRINT_CYCLES("\nVector Scatter Acc 16_32 cycles = %llu\n"); +} + +/* scatter-acc the 16 bit elements with 32 bit offsets using intrinsics */ +void vector_scatter_q_16_32(void) +{ + START_CYCLES; + + /* get the word offsets in a vector pair */ + HVX_VectorPair offsets = *(HVX_VectorPair *)word_offsets; + /* print_vectorpair("word_offsets", (HVX_VectorPair *)&word_offsets); */ + + /* these values need to be shuffled for the RMWwV scatter */ + HVX_Vector values = *(HVX_Vector *)half_q_values; + values = Q6_Vh_vshuff_Vh(values); + /* print_vector("values", (HVX_Vector *)&values); */ + + HVX_Vector pred_reg = *(HVX_Vector *)half_predicates; + pred_reg = Q6_Vh_vshuff_Vh(pred_reg); + HVX_VectorPred preds = Q6_Q_vand_VR(pred_reg, ~0); + + /* do the scatter */ + Q6_vscatter_QRMWwV(preds, VTCM_SCATTER16_32_ADDRESS, region_len, offsets, + values); + /* print_vector("scatter16_32_address", (HVX_Vector */ + /* *)VTCM_SCATTER16_32_ADDRESS); */ + +#if SYNC_VECTOR + /* do the sync operation */ + SCATTER_RELEASE(vscatter16_32); + /* + * This dummy load from vscatter16_32 is to complete the synchronization. + * Normally this load would be deferred as long as possible to minimize + * stalls. + */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vscatter16_32; +#endif + + PRINT_CYCLES("\nVector Scatter Q 16_32 cycles = %llu\n"); +} + + +/* gather the elements from the scatter16 buffer */ +void vector_gather_16(void) +{ + START_CYCLES; + + HVX_Vector *vgather = (HVX_Vector *)VTCM_GATHER16_ADDRESS; + HVX_Vector offsets = *(HVX_Vector *)half_offsets; + + /* do the gather to the gather16 buffer */ + Q6_vgather_ARMVh(vgather, VTCM_SCATTER16_ADDRESS, region_len, offsets); + + +#if SYNC_VECTOR + /* This dummy read of vgather will stall until completion */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vgather; +#endif + + PRINT_CYCLES("\nVector Gather 16 cycles = %llu\n"); +} + +static unsigned short gather_q_16_init(void) +{ + char letter = '?'; + return letter | (letter << 8); +} + +void vector_gather_q_16(void) +{ + START_CYCLES; + + HVX_Vector *vgather = (HVX_Vector *)VTCM_GATHER16_ADDRESS; + HVX_Vector offsets = *(HVX_Vector *)half_offsets; + HVX_Vector pred_reg = *(HVX_Vector *)half_predicates; + HVX_VectorPred preds = Q6_Q_vand_VR(pred_reg, ~0); + + *vgather = Q6_Vh_vsplat_R(gather_q_16_init()); + /* do the gather to the gather16 buffer */ + Q6_vgather_AQRMVh(vgather, preds, VTCM_SCATTER16_ADDRESS, region_len, + offsets); + + +#if SYNC_VECTOR + /* This dummy read of vgather will stall until completion */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vgather; +#endif + + PRINT_CYCLES("\nVector Gather Q 16 cycles = %llu\n"); +} + + +/* gather the elements from the scatter32 buffer */ +void vector_gather_32(void) +{ + START_CYCLES; + + HVX_Vector *vgatherlo = (HVX_Vector *)VTCM_GATHER32_ADDRESS; + HVX_Vector *vgatherhi = + (HVX_Vector *)(VTCM_GATHER32_ADDRESS + (MATRIX_SIZE * 2)); + HVX_Vector offsetslo = *(HVX_Vector *)word_offsets; + HVX_Vector offsetshi = *(HVX_Vector *)&word_offsets[MATRIX_SIZE / 2]; + + /* do the gather to vgather */ + Q6_vgather_ARMVw(vgatherlo, VTCM_SCATTER32_ADDRESS, region_len, offsetslo); + Q6_vgather_ARMVw(vgatherhi, VTCM_SCATTER32_ADDRESS, region_len, offsetshi); + +#if SYNC_VECTOR + /* This dummy read of vgatherhi will stall until completion */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vgatherhi; +#endif + + PRINT_CYCLES("\nVector Gather 32 cycles = %llu\n"); +} + +static unsigned int gather_q_32_init(void) +{ + char letter = '?'; + return letter | (letter << 8) | (letter << 16) | (letter << 24); +} + +void vector_gather_q_32(void) +{ + START_CYCLES; + + HVX_Vector *vgatherlo = (HVX_Vector *)VTCM_GATHER32_ADDRESS; + HVX_Vector *vgatherhi = + (HVX_Vector *)(VTCM_GATHER32_ADDRESS + (MATRIX_SIZE * 2)); + HVX_Vector offsetslo = *(HVX_Vector *)word_offsets; + HVX_Vector offsetshi = *(HVX_Vector *)&word_offsets[MATRIX_SIZE / 2]; + HVX_Vector pred_reglo = *(HVX_Vector *)word_predicates; + HVX_VectorPred predslo = Q6_Q_vand_VR(pred_reglo, ~0); + HVX_Vector pred_reghi = *(HVX_Vector *)&word_predicates[MATRIX_SIZE / 2]; + HVX_VectorPred predshi = Q6_Q_vand_VR(pred_reghi, ~0); + + *vgatherlo = Q6_Vh_vsplat_R(gather_q_32_init()); + *vgatherhi = Q6_Vh_vsplat_R(gather_q_32_init()); + /* do the gather to vgather */ + Q6_vgather_AQRMVw(vgatherlo, predslo, VTCM_SCATTER32_ADDRESS, region_len, + offsetslo); + Q6_vgather_AQRMVw(vgatherhi, predshi, VTCM_SCATTER32_ADDRESS, region_len, + offsetshi); + +#if SYNC_VECTOR + /* This dummy read of vgatherhi will stall until completion */ + volatile HVX_Vector vDummy = *(HVX_Vector *)vgatherhi; +#endif + + PRINT_CYCLES("\nVector Gather Q 32 cycles = %llu\n"); +} + +/* gather the elements from the scatter16_32 buffer */ +void vector_gather_16_32(void) +{ + START_CYCLES; + + /* get the vtcm address to gather from */ + HVX_Vector *vgather = (HVX_Vector *)VTCM_GATHER16_32_ADDRESS; + + /* get the word offsets in a vector pair */ + HVX_VectorPair offsets = *(HVX_VectorPair *)word_offsets; + + /* do the gather to vgather */ + Q6_vgather_ARMWw(vgather, VTCM_SCATTER16_32_ADDRESS, region_len, offsets); + + /* the read of gather will stall until completion */ + volatile HVX_Vector values = *(HVX_Vector *)vgather; + + /* deal the elements to get the order back */ + values = Q6_Vh_vdeal_Vh(values); + + /* write it back to vtcm address */ + *(HVX_Vector *)vgather = values; + + + PRINT_CYCLES("\nVector Gather 16_32 cycles = %llu\n"); +} + +void vector_gather_q_16_32(void) +{ + START_CYCLES; + + /* get the vtcm address to gather from */ + HVX_Vector *vgather = (HVX_Vector *)VTCM_GATHER16_32_ADDRESS; + + /* get the word offsets in a vector pair */ + HVX_VectorPair offsets = *(HVX_VectorPair *)word_offsets; + HVX_Vector pred_reg = *(HVX_Vector *)half_predicates; + pred_reg = Q6_Vh_vshuff_Vh(pred_reg); + HVX_VectorPred preds = Q6_Q_vand_VR(pred_reg, ~0); + + *vgather = Q6_Vh_vsplat_R(gather_q_16_init()); + /* do the gather to vgather */ + Q6_vgather_AQRMWw(vgather, preds, VTCM_SCATTER16_32_ADDRESS, region_len, + offsets); + + /* the read of gather will stall until completion */ + volatile HVX_Vector values = *(HVX_Vector *)vgather; + + /* deal the elements to get the order back */ + values = Q6_Vh_vdeal_Vh(values); + + /* write it back to vtcm address */ + *(HVX_Vector *)vgather = values; + + + PRINT_CYCLES("\nVector Gather Q 16_32 cycles = %llu\n"); +} + + +static void check_buffer(const char *name, void *c, void *r, size_t size) +{ + char *check = (char *)c; + char *ref = (char *)r; + /* printf("check buffer %s 0x%x, 0x%x, %d\n", name, check, ref, size); */ + for (int i = 0; i < size; i++) { + if (check[i] != ref[i]) { + printf("Error %s [%d]: 0x%x (%c) != 0x%x (%c)\n", name, i, check[i], + check[i], ref[i], ref[i]); + err++; + } + } +} + + +/* + * These scalar functions are the C equivalents of the vector functions that + * use HVX + */ + +/* scatter the 16 bit elements using C */ +void scalar_scatter_16(unsigned short *vscatter16) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vscatter16[half_offsets[i] / 2] = half_values[i]; + } + + PRINT_CYCLES("\nScalar Scatter 16 cycles = %llu\n"); +} + +void check_scatter_16() +{ + memset(vscatter16_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); + scalar_scatter_16(vscatter16_ref); + check_buffer("check_scatter_16", vscatter16, vscatter16_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); +} + +/* scatter the 16 bit elements using C */ +void scalar_scatter_acc_16(unsigned short *vscatter16) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vscatter16[half_offsets[i] / 2] += half_acc_values[i]; + } + + PRINT_CYCLES("\nScalar Scatter Acc 16 cycles = %llu\n"); +} + +/* scatter the 16 bit elements using C */ +void scalar_scatter_q_16(unsigned short *vscatter16) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; i++) { + if (half_predicates[i]) { + vscatter16[half_offsets[i] / 2] = half_q_values[i]; + } + } + + PRINT_CYCLES("\nScalar Scatter Q 16 cycles = %llu\n"); +} + + +void check_scatter_acc_16() +{ + memset(vscatter16_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); + scalar_scatter_16(vscatter16_ref); + scalar_scatter_acc_16(vscatter16_ref); + check_buffer("check_scatter_acc_16", vscatter16, vscatter16_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); +} + +void check_scatter_q_16() +{ + memset(vscatter16_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); + scalar_scatter_16(vscatter16_ref); + scalar_scatter_acc_16(vscatter16_ref); + scalar_scatter_q_16(vscatter16_ref); + check_buffer("check_scatter_q_16", vscatter16, vscatter16_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); +} + +/* scatter the 32 bit elements using C */ +void scalar_scatter_32(unsigned int *vscatter32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vscatter32[word_offsets[i] / 4] = word_values[i]; + } + + PRINT_CYCLES("\n\nScalar Scatter 32 cycles = %llu\n"); +} + +/* scatter the 32 bit elements using C */ +void scalar_scatter_acc_32(unsigned int *vscatter32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vscatter32[word_offsets[i] / 4] += word_acc_values[i]; + } + + PRINT_CYCLES("\nScalar Scatter Acc 32 cycles = %llu\n"); +} + +/* scatter the 32 bit elements using C */ +void scalar_scatter_q_32(unsigned int *vscatter32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; i++) { + if (word_predicates[i]) { + vscatter32[word_offsets[i] / 4] = word_q_values[i]; + } + } + + PRINT_CYCLES("\nScalar Scatter Q 32 cycles = %llu\n"); +} + +void check_scatter_32() +{ + memset(vscatter32_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned int)); + scalar_scatter_32(vscatter32_ref); + check_buffer("check_scatter_32", vscatter32, vscatter32_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned int)); +} + +void check_scatter_acc_32() +{ + memset(vscatter32_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned int)); + scalar_scatter_32(vscatter32_ref); + scalar_scatter_acc_32(vscatter32_ref); + check_buffer("check_scatter_acc_32", vscatter32, vscatter32_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned int)); +} + +void check_scatter_q_32() +{ + memset(vscatter32_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned int)); + scalar_scatter_32(vscatter32_ref); + scalar_scatter_acc_32(vscatter32_ref); + scalar_scatter_q_32(vscatter32_ref); + check_buffer("check_scatter_q_32", vscatter32, vscatter32_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned int)); +} + +/* scatter the 32 bit elements using C */ +void scalar_scatter_16_32(unsigned short *vscatter16_32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vscatter16_32[word_offsets[i] / 2] = half_values[i]; + } + + PRINT_CYCLES("\n\nScalar Scatter 16_32 cycles = %llu\n"); +} + +/* scatter the 32 bit elements using C */ +void scalar_scatteracc_16_32(unsigned short *vscatter16_32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vscatter16_32[word_offsets[i] / 2] += half_acc_values[i]; + } + + PRINT_CYCLES("\n\nScalar Scatter Acc 16_32 cycles = %llu\n"); +} + +void scalar_scatter_q_16_32(unsigned short *vscatter16_32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; i++) { + if (half_predicates[i]) { + vscatter16_32[word_offsets[i] / 2] = half_q_values[i]; + } + } + + PRINT_CYCLES("\nScalar Scatter Q 16_32 cycles = %llu\n"); +} + +void check_scatter_16_32() +{ + memset(vscatter16_32_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); + scalar_scatter_16_32(vscatter16_32_ref); + check_buffer("check_scatter_16_32", vscatter16_32, vscatter16_32_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); +} + +void check_scatter_acc_16_32() +{ + memset(vscatter16_32_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); + scalar_scatter_16_32(vscatter16_32_ref); + scalar_scatteracc_16_32(vscatter16_32_ref); + check_buffer("check_scatter_acc_16_32", vscatter16_32, vscatter16_32_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); +} + +void check_scatter_q_16_32() +{ + memset(vscatter16_32_ref, FILL_CHAR, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); + scalar_scatter_16_32(vscatter16_32_ref); + scalar_scatteracc_16_32(vscatter16_32_ref); + scalar_scatter_q_16_32(vscatter16_32_ref); + check_buffer("check_scatter_q_16_32", vscatter16_32, vscatter16_32_ref, + SCATTER_BUFFER_SIZE * sizeof(unsigned short)); +} + +/* gather the elements from the scatter buffer using C */ +void scalar_gather_16(unsigned short *vgather16) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vgather16[i] = vscatter16[half_offsets[i] / 2]; + } + + PRINT_CYCLES("\n\nScalar Gather 16 cycles = %llu\n"); +} + +void scalar_gather_q_16(unsigned short *vgather16) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + if (half_predicates[i]) { + vgather16[i] = vscatter16[half_offsets[i] / 2]; + } + } + + PRINT_CYCLES("\n\nScalar Gather Q 16 cycles = %llu\n"); +} + +void check_gather_16() +{ + memset(vgather16_ref, 0, MATRIX_SIZE * sizeof(unsigned short)); + scalar_gather_16(vgather16_ref); + check_buffer("check_gather_16", vgather16, vgather16_ref, + MATRIX_SIZE * sizeof(unsigned short)); +} + +void check_gather_q_16() +{ + memset(vgather16_ref, gather_q_16_init(), + MATRIX_SIZE * sizeof(unsigned short)); + scalar_gather_q_16(vgather16_ref); + check_buffer("check_gather_q_16", vgather16, vgather16_ref, + MATRIX_SIZE * sizeof(unsigned short)); +} + +/* gather the elements from the scatter buffer using C */ +void scalar_gather_32(unsigned int *vgather32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vgather32[i] = vscatter32[word_offsets[i] / 4]; + } + + PRINT_CYCLES("\n\nScalar Gather 32 cycles = %llu\n"); +} + +void scalar_gather_q_32(unsigned int *vgather32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + if (word_predicates[i]) { + vgather32[i] = vscatter32[word_offsets[i] / 4]; + } + } + + PRINT_CYCLES("\n\nScalar Gather Q 32 cycles = %llu\n"); +} + + +void check_gather_32(void) +{ + memset(vgather32_ref, 0, MATRIX_SIZE * sizeof(unsigned int)); + scalar_gather_32(vgather32_ref); + check_buffer("check_gather_32", vgather32, vgather32_ref, + MATRIX_SIZE * sizeof(unsigned int)); +} + +void check_gather_q_32(void) +{ + memset(vgather32_ref, gather_q_32_init(), + MATRIX_SIZE * sizeof(unsigned int)); + scalar_gather_q_32(vgather32_ref); + check_buffer("check_gather_q_32", vgather32, vgather32_ref, + MATRIX_SIZE * sizeof(unsigned int)); +} + +/* gather the elements from the scatter buffer using C */ +void scalar_gather_16_32(unsigned short *vgather16_32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + vgather16_32[i] = vscatter16_32[word_offsets[i] / 2]; + } + + PRINT_CYCLES("\n\nScalar Gather 16_32 cycles = %llu\n"); +} + +void scalar_gather_q_16_32(unsigned short *vgather16_32) +{ + START_CYCLES; + + for (int i = 0; i < MATRIX_SIZE; ++i) { + if (half_predicates[i]) { + vgather16_32[i] = vscatter16_32[word_offsets[i] / 2]; + } + } + + PRINT_CYCLES("\n\nScalar Gather Q 16_32 cycles = %llu\n"); +} + +void check_gather_16_32(void) +{ + memset(vgather16_32_ref, 0, MATRIX_SIZE * sizeof(unsigned short)); + scalar_gather_16_32(vgather16_32_ref); + check_buffer("check_gather_16_32", vgather16_32, vgather16_32_ref, + MATRIX_SIZE * sizeof(unsigned short)); +} + +void check_gather_q_16_32(void) +{ + memset(vgather16_32_ref, gather_q_16_init(), + MATRIX_SIZE * sizeof(unsigned short)); + scalar_gather_q_16_32(vgather16_32_ref); + check_buffer("check_gather_q_16_32", vgather16_32, vgather16_32_ref, + MATRIX_SIZE * sizeof(unsigned short)); +} + +/* These functions print the buffers to the display */ + +/* print scatter16 buffer */ +void print_scatter16_buffer(void) +{ +#if PRINT_DATA + /* + * printf("\n\nPrinting the 16 bit scatter buffer at 0x%08x", + * VTCM_SCATTER16_ADDRESS); + */ + printf("\n\nPrinting the 16 bit scatter buffer"); + + for (int i = 0; i < SCATTER_BUFFER_SIZE; i++) { + if ((i % MATRIX_SIZE) == 0) { + printf("\n"); + } + + for (int j = 0; j < 2; j++) { + printf("%c", (char)((vscatter16[i] >> j * 8) & 0xff)); + } + + printf(" "); + } + printf("\n"); +#endif +} + +/* print the gather 16 buffer */ +void print_gather_result_16(void) +{ +#if PRINT_DATA + /* + * printf("\n\nPrinting the 16 bit gather result at 0x%08x\n", + * VTCM_GATHER16_ADDRESS); + */ + printf("\n\nPrinting the 16 bit gather result\n"); + + for (int i = 0; i < MATRIX_SIZE; i++) { + for (int j = 0; j < 2; j++) { + printf("%c", (char)((vgather16[i] >> j * 8) & 0xff)); + } + + printf(" "); + } + printf("\n"); +#endif +} + +/* print the scatter32 buffer */ +void print_scatter32_buffer(void) +{ +#if PRINT_DATA + /* + * printf("\n\nPrinting the 32 bit scatter buffer at 0x%08x", + * VTCM_SCATTER32_ADDRESS); + */ + printf("\n\nPrinting the 32 bit scatter buffer"); + + for (int i = 0; i < SCATTER_BUFFER_SIZE; i++) { + if ((i % MATRIX_SIZE) == 0) { + printf("\n"); + } + + for (int j = 0; j < 4; j++) { + printf("%c", (char)((vscatter32[i] >> j * 8) & 0xff)); + } + + printf(" "); + } + printf("\n"); +#endif +} + + +/* print the gather 32 buffer */ +void print_gather_result_32(void) +{ +#if PRINT_DATA + /* + * printf("\n\nPrinting the 32 bit gather result at 0x%08x\n", + * VTCM_GATHER32_ADDRESS); + */ + printf("\n\nPrinting the 32 bit gather result\n"); + + for (int i = 0; i < MATRIX_SIZE; i++) { + for (int j = 0; j < 4; j++) { + printf("%c", (char)((vgather32[i] >> j * 8) & 0xff)); + } + + printf(" "); + } + printf("\n"); +#endif +} + +/* print the scatter16_32 buffer */ +void print_scatter16_32_buffer(void) +{ +#if PRINT_DATA + /* + * printf("\n\nPrinting the 16_32 bit scatter buffer at 0x%08x", + * VTCM_SCATTER16_32_ADDRESS); + */ + printf("\n\nPrinting the 16_32 bit scatter buffer"); + + for (int i = 0; i < SCATTER_BUFFER_SIZE; i++) { + if ((i % MATRIX_SIZE) == 0) { + printf("\n"); + } + + for (int j = 0; j < 2; j++) { + printf("%c", (unsigned char)((vscatter16_32[i] >> j * 8) & 0xff)); + } + + printf(" "); + } + printf("\n"); +#endif +} + +/* print the gather 16_32 buffer */ +void print_gather_result_16_32(void) +{ +#if PRINT_DATA + /* + * printf("\n\nPrinting the 16_32 bit gather result at 0x%08x\n", + * VTCM_GATHER16_32_ADDRESS); + */ + printf("\n\nPrinting the 16_32 bit gather result\n"); + + for (int i = 0; i < MATRIX_SIZE; i++) { + for (int j = 0; j < 2; j++) { + printf("%c", (unsigned char)((vgather16_32[i] >> j * 8) & 0xff)); + } + + printf(" "); + } + printf("\n"); +#endif +} + +/* + * set up the tcm address translation + * Note: This method is only for the standalone environment + * SDK users should use the "VTCM Manager" to use VTCM + */ +void setup_tcm(void) +{ + VTCM_BASE_ADDRESS = get_vtcm_base(); + + uint64_t pa = VTCM_BASE_ADDRESS; + void *va = (void *)VTCM_BASE_ADDRESS; + + VTCM_SCATTER16_ADDRESS = VTCM_BASE_ADDRESS; + VTCM_GATHER16_ADDRESS = VTCM_BASE_ADDRESS + SCATTER16_BUF_SIZE; + VTCM_SCATTER32_ADDRESS = VTCM_GATHER16_ADDRESS + GATHER16_BUF_SIZE; + VTCM_GATHER32_ADDRESS = VTCM_SCATTER32_ADDRESS + SCATTER32_BUF_SIZE; + VTCM_SCATTER16_32_ADDRESS = VTCM_GATHER32_ADDRESS + GATHER32_BUF_SIZE; + VTCM_GATHER16_32_ADDRESS = VTCM_SCATTER16_32_ADDRESS + SCATTER16_BUF_SIZE; + + /* the vtcm base address */ + vtcm_base = (unsigned char *)VTCM_BASE_ADDRESS; + + /* scatter gather 16 bit elements using 16 bit offsets */ + vscatter16 = (unsigned short *)VTCM_SCATTER16_ADDRESS; + vgather16 = (unsigned short *)VTCM_GATHER16_ADDRESS; + + /* scatter gather 32 bit elements using 32 bit offsets */ + vscatter32 = (unsigned int *)VTCM_SCATTER32_ADDRESS; + vgather32 = (unsigned int *)VTCM_GATHER32_ADDRESS; + + /* scatter gather 16 bit elements using 32 bit offsets */ + vscatter16_32 = (unsigned short *)VTCM_SCATTER16_32_ADDRESS; + vgather16_32 = (unsigned short *)VTCM_GATHER16_32_ADDRESS; +} + +void inst_test() +{ + /* Should NOT throw an error when paranoid-commit-state turned on */ + uint32_t R; + asm volatile("release(%0):at\n\t" : : "r"(R)); +} + + +int main() +{ + setup_tcm(); + prefill_vtcm_scratch(); + + /* 16 bit elements with 16 bit offsets */ + create_offsets_and_values_16(); + create_preds_16(); + +#if PRINT_CYCLE_COUNTS + scalar_scatter_16(vscatter16); +#endif + vector_scatter_16(); + print_scatter16_buffer(); + check_scatter_16(); + + +#if PRINT_CYCLE_COUNTS + scalar_gather_16(vgather16); +#endif + vector_gather_16(); + print_gather_result_16(); + check_gather_16(); + + vector_gather_q_16(); + print_gather_result_16(); + check_gather_q_16(); + + vector_scatter_acc_16(); + print_scatter16_buffer(); + check_scatter_acc_16(); + + vector_scatter_q_16(); + print_scatter16_buffer(); + check_scatter_q_16(); + + /* 32 bit elements with 32 bit offsets */ + create_offsets_and_values_32(); + create_preds_32(); + +#if PRINT_CYCLE_COUNTS + scalar_scatter_32(vscatter32); +#endif + + vector_scatter_32(); + + print_scatter32_buffer(); + check_scatter_32(); + +#if PRINT_CYCLE_COUNTS + scalar_gather_32(vgather32); +#endif + + vector_gather_32(); + + print_gather_result_32(); + check_gather_32(); + + vector_gather_q_32(); + print_gather_result_32(); + check_gather_q_32(); + + vector_scatter_acc_32(); + print_scatter32_buffer(); + check_scatter_acc_32(); + + vector_scatter_q_32(); + print_scatter32_buffer(); + check_scatter_q_32(); + + /* 16 bit elements with 32 bit offsets */ + create_offsets_and_values_16_32(); + create_preds_16_32(); + +#if PRINT_CYCLE_COUNTS + scalar_scatter_16_32(); +#endif + vector_scatter_16_32(); + + print_scatter16_32_buffer(); + check_scatter_16_32(); + +#if PRINT_CYCLE_COUNTS + scalar_gather_16_32(vgather16_32); +#endif + + vector_gather_16_32(); + + print_gather_result_16_32(); + check_gather_16_32(); + + vector_gather_q_16_32(); + print_gather_result_16_32(); + check_gather_q_16_32(); + + vector_scatter_acc_16_32(); + print_scatter16_32_buffer(); + check_scatter_acc_16_32(); + + vector_scatter_q_16_32(); + print_scatter16_32_buffer(); + check_scatter_q_16_32(); + + inst_test(); + printf("%s\n", ((err) ? "FAIL" : "PASS")); + return err; +} From bf9981b9e6da3e2bfb5ae1187fdb1fb09980ed38 Mon Sep 17 00:00:00 2001 From: Matheus Tavares Bernardino Date: Wed, 5 Feb 2025 09:37:24 -0800 Subject: [PATCH 1167/1179] tests/tcg/hexagon: add l2vic tests Signed-off-by: Matheus Tavares Bernardino --- tests/tcg/hexagon/Makefile.softmmu-target | 6 ++ tests/tcg/hexagon/system/fastl2vic.c | 73 ++++++++++++++++++ tests/tcg/hexagon/system/int_range.c | 94 +++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 tests/tcg/hexagon/system/fastl2vic.c create mode 100644 tests/tcg/hexagon/system/int_range.c diff --git a/tests/tcg/hexagon/Makefile.softmmu-target b/tests/tcg/hexagon/Makefile.softmmu-target index 3f070bfea91b..0b12f7485b62 100644 --- a/tests/tcg/hexagon/Makefile.softmmu-target +++ b/tests/tcg/hexagon/Makefile.softmmu-target @@ -45,6 +45,8 @@ TESTS_BUILT_WITH_DEFAULT_RULES = \ vid_reg \ hvx-multi \ standalone_vec \ + fastl2vic \ + int_range \ $() TESTS += \ @@ -88,6 +90,10 @@ standalone_vec.o: standalone_vec.c cfgtable.h standalone_vec: standalone_vec.o badva.o: badva.c ../hex_test.h crt0/hexagon_standalone.h badva: badva.o +fastl2vic.o: fastl2vic.c cfgtable.h +fastl2vic: fastl2vic.o +int_range.o: int_range.c cfgtable.h +int_range: int_range.o ############# Custom build options diff --git a/tests/tcg/hexagon/system/fastl2vic.c b/tests/tcg/hexagon/system/fastl2vic.c new file mode 100644 index 000000000000..a115ae73f799 --- /dev/null +++ b/tests/tcg/hexagon/system/fastl2vic.c @@ -0,0 +1,73 @@ +/* + * Copyright(c) 2024-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Test the fastl2vic interface. + * + * hexagon-sim a.out --subsystem_base=0xfab0 --cosim_file q6ss.cfg + */ + +#include "crt0/hexagon_standalone.h" + +#include "cfgtable.h" + +#define CSR_BASE 0xfab00000 +#define L2VIC_BASE ((CSR_BASE) + 0x10000) +#define L2VIC_INT_ENABLE(b, n) \ + ((unsigned int *) ((b) + 0x100 + 4 * (n / 32))) +#define L2VIC_INT_ENABLE_SET(b, n) \ + ((unsigned int *) ((b) + 0x200 + 4 * (n / 32))) + +int main() +{ + int ret = 0; + unsigned int irq_bit; + + /* setup the fastl2vic interface and setup an indirect mapping */ + volatile uint32_t *A = (uint32_t *)0x888e0000; + add_translation_extended(3, (void *)A, GET_FASTL2VIC_BASE(), 16, 7, 4, 0, 0, 3); + + uint32_t l2vic_base = GET_SUBSYSTEM_BASE() + 0x10000; + + /* set and verify an interrupt using the L2VIC_BASE */ + irq_bit = (1 << (66 % 32)); + *L2VIC_INT_ENABLE_SET(l2vic_base, 66) = irq_bit; + if (*L2VIC_INT_ENABLE(l2vic_base, 64) != 0x4) { + ret = __LINE__; + } + + /* set and verify an interrupt using the FASTL2VIC interface */ + *A = 68; + if (*L2VIC_INT_ENABLE(l2vic_base, 64) != 0x14) { + ret = __LINE__; + } + *A = 67; + if (*L2VIC_INT_ENABLE(l2vic_base, 64) != 0x1C) { + ret = __LINE__; + } + + + /* Now clear the lines */ + *A = ((1 << 16) | 68); + if (*L2VIC_INT_ENABLE(l2vic_base, 64) != 0xC) { + ret = __LINE__; + } + *A = ((1 << 16) | 66); + if (*L2VIC_INT_ENABLE(l2vic_base, 64) != 0x8) { + ret = __LINE__; + } + *A = ((1 << 16) | 67); + if (*L2VIC_INT_ENABLE(l2vic_base, 64) != 0x0) { + ret = __LINE__; + } + + if (ret) { + printf("%s: FAIL, last failure near line %d\n", __FILE__, ret); + } else { + printf("PASS\n"); + } + return ret; +} diff --git a/tests/tcg/hexagon/system/int_range.c b/tests/tcg/hexagon/system/int_range.c new file mode 100644 index 000000000000..688355886362 --- /dev/null +++ b/tests/tcg/hexagon/system/int_range.c @@ -0,0 +1,94 @@ +/* + * Copyright(c) 2023-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Test the range of the l2vic interface. + */ + + +#include +#include +#include +#include "cfgtable.h" + +#define L2VIC_INT_ENABLE(b, n) \ + ((volatile unsigned int *)((b) + 0x100 + 4 * (n / 32))) /* device mem */ + +#define L2VIC_INT_ENABLE_SET(b, n) \ + ((volatile unsigned int *)((b) + 0x200 + 4 * (n / 32))) /* device mem */ + +#define L2VIC_INT_ENABLE_CLEAR(b, n) \ + ((volatile unsigned int *)((b) + 0x180 + 4 * (n / 32))) /* device mem */ + +#define L2VIC_SOFT_INT_SET(b, n) \ + ((volatile unsigned int *)((b) + 0x480 + 4 * (n / 32))) /* device mem */ + +#define L2VIC_INT_TYPE(b, n) \ + ((volatile unsigned int *)((b) + 0x280 + 4 * (n / 32))) /* device mem */ + +volatile int pass; /* must use volatile */ +int g_irq; +volatile uint32_t g_l2vic_base; /* must use volatile */ + + +/* + * When complete the irqlog will contain the value of the vid when the + * handler was active. + */ +#define INTMAX 1024 +#define LEFT_SET 666 + +int main() +{ + unsigned int irq_bit; + unsigned int left_set = 0; + int ret = 0; + + /* setup the fastl2vic interface and setup an indirect mapping */ + g_l2vic_base = GET_SUBSYSTEM_BASE() + 0x10000; + + /* Setup interrupts */ + for (int irq = 1; irq < INTMAX; irq++) { + irq_bit = (1 << (irq % 32)); + *L2VIC_INT_ENABLE(g_l2vic_base, irq) |= irq_bit; + } + + /* Read them all back and check */ + for (int irq = 1; irq < INTMAX; irq++) { + if ((*L2VIC_INT_ENABLE(g_l2vic_base, irq) & (1 << (irq % 32))) != + (1 << irq % 32)) { + printf("%d: ERROR: irq: %d: 0x%x\n", __LINE__, irq, + *L2VIC_INT_ENABLE(g_l2vic_base, irq)); + ret = 1; + } + } + /* Clear them all, except int 1 and LEFT_SET (test) */ + for (int irq = 1; irq < INTMAX; irq++) { + if (!(irq % LEFT_SET)) { + continue; + } + irq_bit = (1 << (irq % 32)); + *L2VIC_INT_ENABLE_CLEAR(g_l2vic_base, irq) |= irq_bit; + } + + /* make sure just LEFT_SET is set */ + for (int irq = 0; irq < INTMAX; irq++) { + if ((*L2VIC_INT_ENABLE(g_l2vic_base, irq) & (1 << (irq % 32))) != + (0 << irq % 32)) { + if (irq != LEFT_SET) { + printf("%d: ERROR: irq: %d: 0x%x\n", __LINE__, irq, + *L2VIC_INT_ENABLE(g_l2vic_base, irq)); + ret = 1; + } else { + left_set = irq; + } + } + } + if (left_set == LEFT_SET) { + printf("PASS\n"); + } + return ret; +} From 95156cb97ec32cac932801491fad9547a9b9ca7a Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 23 Sep 2024 21:37:36 -0700 Subject: [PATCH 1168/1179] target/hexagon: add utimer reg impl Signed-off-by: Brian Cain --- target/hexagon/cpu_helper.c | 4 ++ target/hexagon/genptr.c | 9 +++++ target/hexagon/helper.h | 2 + target/hexagon/op_helper.c | 65 +++++++++++++++++++++++++++++++ tests/tcg/hexagon/Makefile.target | 2 + tests/tcg/hexagon/reg_mut.c | 6 +-- tests/tcg/hexagon/utimer.c | 50 ++++++++++++++++++++++++ 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 tests/tcg/hexagon/utimer.c diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c index 78d010817eac..6d9bf34aff37 100644 --- a/target/hexagon/cpu_helper.c +++ b/target/hexagon/cpu_helper.c @@ -176,6 +176,8 @@ uint32_t arch_get_system_reg(CPUHexagonState *env, uint32_t reg) return reg < HEX_SREG_GLB_START ? env->t_sreg[reg] : env->g_sreg[reg]; } +#endif + uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env) { uint64_t cycles = 0; @@ -225,6 +227,8 @@ void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t cycles) } } +#ifndef CONFIG_USER_ONLY + static void set_wait_mode(CPUHexagonState *env) { g_assert(bql_locked()); diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c index f38968271b17..1dde04529bbe 100644 --- a/target/hexagon/genptr.c +++ b/target/hexagon/genptr.c @@ -346,6 +346,11 @@ static inline void gen_read_ctrl_reg(DisasContext *ctx, const int reg_num, } else if (reg_num == HEX_REG_QEMU_HVX_CNT) { tcg_gen_addi_tl(dest, hex_gpr[HEX_REG_QEMU_HVX_CNT], ctx->num_hvx_insns); + } else if ((reg_num == HEX_REG_PKTCNTLO) + || (reg_num == HEX_REG_PKTCNTHI) + || (reg_num == HEX_REG_UTIMERLO) + || (reg_num == HEX_REG_UTIMERHI)) { + gen_helper_creg_read(dest, tcg_env, tcg_constant_tl(reg_num)); } else { tcg_gen_mov_tl(dest, hex_gpr[reg_num]); } @@ -374,6 +379,10 @@ static inline void gen_read_ctrl_reg_pair(DisasContext *ctx, const int reg_num, tcg_gen_addi_tl(hvx_cnt, hex_gpr[HEX_REG_QEMU_HVX_CNT], ctx->num_hvx_insns); tcg_gen_concat_i32_i64(dest, hvx_cnt, hex_gpr[reg_num + 1]); + } else if ((reg_num == HEX_REG_PKTCNTLO) + || (reg_num == HEX_REG_UTIMERLO) + || (reg_num == HEX_REG_UPCYCLELO)) { + gen_helper_creg_read_pair(dest, tcg_env, tcg_constant_i32(reg_num)); } else { tcg_gen_concat_i32_i64(dest, hex_gpr[reg_num], diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index 5bcb2f48097c..b381e0e116b3 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -108,6 +108,8 @@ DEF_HELPER_2(probe_pkt_scalar_store_s0, void, env, int) DEF_HELPER_2(probe_hvx_stores, void, env, int) DEF_HELPER_2(probe_pkt_scalar_hvx_stores, void, env, int) +DEF_HELPER_2(creg_read, i32, env, i32) +DEF_HELPER_2(creg_read_pair, i64, env, i32) #if !defined(CONFIG_USER_ONLY) DEF_HELPER_2(swi, void, env, i32) DEF_HELPER_2(cswi, void, env, i32) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 8eacb3b04115..7a83e8975031 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -18,6 +18,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "qemu/main-loop.h" +#include "qemu/timer.h" #include "exec/exec-all.h" #include "exec/cpu_ldst.h" #include "exec/helper-proto.h" @@ -1775,6 +1776,43 @@ static void hexagon_read_timer(CPUHexagonState *env, uint32_t *low, cpu_physical_memory_read(high_addr, high, sizeof(*high)); } +static inline bool ssr_ce_enabled(CPUHexagonState *env) +{ + target_ulong ssr = arch_get_system_reg(env, HEX_SREG_SSR); + return GET_SSR_FIELD(SSR_CE, ssr); +} + +static uint32_t creg_read(CPUHexagonState *env, uint32_t reg) +{ + uint32_t low, high; + switch (reg) { + case HEX_REG_UPCYCLELO: + return ssr_ce_enabled(env) ? hexagon_get_sys_pcycle_count_low(env) : 0; + case HEX_REG_UPCYCLEHI: + return ssr_ce_enabled(env) ? hexagon_get_sys_pcycle_count_high(env) : 0; + case HEX_REG_UTIMERLO: + hexagon_read_timer(env, &low, &high); + return low; + case HEX_REG_UTIMERHI: + hexagon_read_timer(env, &low, &high); + return high; + default: + return env->gpr[reg]; + } +} + +uint32_t HELPER(creg_read)(CPUHexagonState *env, uint32_t reg) +{ + return creg_read(env, reg); +} + +uint64_t HELPER(creg_read_pair)(CPUHexagonState *env, uint32_t reg) +{ + return (uint64_t)creg_read(env, reg) | + (((uint64_t)creg_read(env, reg + 1)) << 32); +} + + static inline QEMU_ALWAYS_INLINE void sreg_write(CPUHexagonState *env, uint32_t reg, uint32_t val) @@ -1939,6 +1977,33 @@ void HELPER(pending_interrupt)(CPUHexagonState *env) } #endif +#ifdef CONFIG_USER_ONLY +uint32_t HELPER(creg_read)(CPUHexagonState *env, uint32_t reg) +{ + /* These are handled directly by gen_read_ctrl_reg(). */ + g_assert(reg != HEX_REG_UPCYCLELO && reg != HEX_REG_UPCYCLEHI); + + if (reg == HEX_REG_UTIMERHI) { + return cpu_get_host_ticks() >> 32; + } else if (reg == HEX_REG_UTIMERLO) { + return extract32(cpu_get_host_ticks(), 0, 32); + } + return 0; +} + +uint64_t HELPER(creg_read_pair)(CPUHexagonState *env, uint32_t reg) +{ + if (reg == HEX_REG_UPCYCLELO) { + /* Pretend SSR[CE] is always set. */ + return hexagon_get_sys_pcycle_count(env); + } + if (reg == HEX_REG_UTIMERLO) { + return cpu_get_host_ticks(); + } + return 0; +} +#endif + /* These macros can be referenced in the generated helper functions */ #define warn(...) /* Nothing */ diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target index e5182c01d8a0..44dd927b5937 100644 --- a/tests/tcg/hexagon/Makefile.target +++ b/tests/tcg/hexagon/Makefile.target @@ -52,6 +52,7 @@ HEX_TESTS += hvx_misc HEX_TESTS += hvx_histogram HEX_TESTS += invalid-slots HEX_TESTS += unaligned_pc +HEX_TESTS += utimer run-and-check-exception = $(call run-test,$2,$3 2>$2.stderr; \ test $$? -eq 1 && grep -q "exception $(strip $1)" $2.stderr) @@ -109,6 +110,7 @@ preg_alias: preg_alias.c hex_test.h read_write_overlap: read_write_overlap.c hex_test.h reg_mut: reg_mut.c hex_test.h unaligned_pc: unaligned_pc.c +utimer: utimer.c hex_test.h # This test has to be compiled for the -mv67t target usr: usr.c hex_test.h diff --git a/tests/tcg/hexagon/reg_mut.c b/tests/tcg/hexagon/reg_mut.c index c5a39e55100d..45db9ae5cd15 100644 --- a/tests/tcg/hexagon/reg_mut.c +++ b/tests/tcg/hexagon/reg_mut.c @@ -77,10 +77,10 @@ static inline void write_control_registers(void) check32(result, 0x00000000); WRITE_REG_NOCLOBBER(result, "utimerlo", 0xffffffff); - check32(result, 0x00000000); + check32_ne(result, 0xffffffff); WRITE_REG_NOCLOBBER(result, "utimerhi", 0xffffffff); - check32(result, 0x00000000); + check32_ne(result, 0xffffffff); /* * PC is special. Setting it to these values @@ -107,7 +107,7 @@ static inline void write_control_register_pairs(void) check64(result, 0x0000000000000000); WRITE_REG_NOCLOBBER(result, "c31:30", 0xffffffffffffffff); - check64(result, 0x0000000000000000); + check64_ne(result, 0xffffffffffffffff); WRITE_REG_PAIR_ENCODED(result, "c9:8", (uint64_t) 0x0000000000000000, C9_8_EQ_R1_0); diff --git a/tests/tcg/hexagon/utimer.c b/tests/tcg/hexagon/utimer.c new file mode 100644 index 000000000000..ae3bca320192 --- /dev/null +++ b/tests/tcg/hexagon/utimer.c @@ -0,0 +1,50 @@ +/* + * Copyright(c) 2022-2023 Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +static int err; + +#include "hex_test.h" + +static uint64_t get_time() +{ + uint64_t time; + asm volatile("%0 = utimer\n\t" + : "=r"(time) + : + : + ); + return time; +} + +static uint64_t get_time_from_regs() +{ + uint32_t time_low; + uint32_t time_high; + asm volatile("%0 = utimerhi\n\t" + "%1 = utimerlo\n\t" + : "=r"(time_high), "=r"(time_low) + : + : + ); + return ((uint64_t)time_high << 32) | (uint64_t)time_low; +} + + +int main() +{ + err = 0; + + uint64_t t0 = get_time(); + check64_ne(t0, 0); + + uint64_t t1 = get_time_from_regs(); + check64_ne(t1, 0); + + puts(err ? "FAIL" : "PASS"); + return err; +} From 2a134878551892dc8fc3c7ab145b247dfc4cbd91 Mon Sep 17 00:00:00 2001 From: Taylor Simpson Date: Tue, 29 Oct 2024 11:49:47 -0600 Subject: [PATCH 1169/1179] Hexagon (target/hexagon) Make "info tlb" work in qemu monitor Add the #if defined (TARGET_HEXAGON) to hmp-commands-info.hx Prefix each TLB entry with the index Signed-off-by: Taylor Simpson --- hmp-commands-info.hx | 3 ++- target/hexagon/hex_mmu.c | 5 ++--- target/hexagon/meson.build | 1 + target/hexagon/monitor.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 target/hexagon/monitor.c diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx index c59cd6637b97..ecdbdf623d11 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx @@ -196,7 +196,8 @@ SRST ERST #if defined(TARGET_I386) || defined(TARGET_SH4) || defined(TARGET_SPARC) || \ - defined(TARGET_PPC) || defined(TARGET_XTENSA) || defined(TARGET_M68K) + defined(TARGET_PPC) || defined(TARGET_XTENSA) || defined(TARGET_M68K) || \ + defined(TARGET_HEXAGON) { .name = "tlb", .args_type = "", diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c index 5ba0c3e1e8df..923018949b2a 100644 --- a/target/hexagon/hex_mmu.c +++ b/target/hexagon/hex_mmu.c @@ -144,12 +144,11 @@ static bool hex_dump_mmu_entry(FILE *f, uint64_t entry) void dump_mmu(CPUHexagonState *env) { - int i; - HexagonCPU *cpu = env_archcpu(env); - for (i = 0; i < cpu->num_tlbs; i++) { + for (uint32_t i = 0; i < cpu->num_tlbs; i++) { uint64_t entry = env->hex_tlb->entries[i]; if (GET_TLB_FIELD(entry, PTE_V)) { + qemu_printf("[%03" PRIu32 "] ", i); qemu_printf("0x%016" PRIx64 ": ", entry); uint64_t PA = hex_tlb_phys_addr(entry); uint64_t VA = hex_tlb_virt_addr(entry); diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build index d2b56b9e65e5..642c052d6e1c 100644 --- a/target/hexagon/meson.build +++ b/target/hexagon/meson.build @@ -270,6 +270,7 @@ hexagon_softmmu_ss.add(files( 'hex_interrupts.c', 'hexswi.c', 'machine.c', + 'monitor.c', )) # diff --git a/target/hexagon/monitor.c b/target/hexagon/monitor.c new file mode 100644 index 000000000000..534ca2abe63a --- /dev/null +++ b/target/hexagon/monitor.c @@ -0,0 +1,36 @@ +/* + * Copyright(c) 2022-2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#include "qemu/osdep.h" +#include "cpu.h" +#include "cpu_bits.h" +#include "monitor/monitor.h" +#include "monitor/hmp-target.h" +#include "monitor/hmp.h" +#include "hex_mmu.h" + +const MonitorDef monitor_defs[] = { + { NULL }, +}; + +const MonitorDef *target_monitor_defs(void) +{ + return monitor_defs; +} + +void hmp_info_tlb(Monitor *mon, const QDict *qdict) +{ +#if !defined(CONFIG_USER_ONLY) + CPUArchState *env = mon_get_cpu_env(mon); + if (!env) { + monitor_printf(mon, "No CPU available\n"); + return; + } + + dump_mmu(env); +#endif +} From f61c4157f113d798ddf35f98a448a6989247a9b6 Mon Sep 17 00:00:00 2001 From: Marco Liebel Date: Thu, 27 Feb 2025 10:42:02 -0800 Subject: [PATCH 1170/1179] target/hexagon: Add instruction definitions Signed-off-by: Marco Liebel --- target/hexagon/imported/mmvec/ext.idef | 370 +++++++++++++++++++++++++ 1 file changed, 370 insertions(+) diff --git a/target/hexagon/imported/mmvec/ext.idef b/target/hexagon/imported/mmvec/ext.idef index 03d31f6181d7..1b7c5afb42f7 100644 --- a/target/hexagon/imported/mmvec/ext.idef +++ b/target/hexagon/imported/mmvec/ext.idef @@ -1400,6 +1400,376 @@ ITERATOR_INSN2_MPY_SLOT_DOUBLE_VEC(32,vmpyhus_acc, "Vxx32+=vmpyhus(Vu32,Vv32)"," VxxV.v[1].w[i] += fMPY16SU(fGETHALF(1, VuV.w[i]), fGETUHALF(1, VvV.uw[i]))) +/* VMPY_QF32 */ +/* multiply qf32 input, produce qf32 output*/ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(32,vmpy_qf32,"Vd32.qf32=vmpy(Vu32.qf32,Vv32.qf32)","Vector multiply: qf32 output from qf32 input", + fHIDE(unfloat )u = fPARSEQF32(VuV.qf32[i]); + fHIDE(unfloat )v = fPARSEQF32(VvV.qf32[i]); + VdV.qf32[i] = fRNDSATQF32(u.exp+v.exp, u.sig*v.sig, 0)) + +/* VMPY_QF32_SF */ +/* multiply ieee sf input, produce qf32 output*/ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(32,vmpy_qf32_sf,"Vd32.qf32=vmpy(Vu32.sf,Vv32.sf)","Vector multiply: qf32 output from IEEE sf input", + fHIDE(unfloat )u = fPARSESF(VuV.sf[i]); + fHIDE(unfloat )v = fPARSESF(VvV.sf[i]); + VdV.qf32[i] = fRNDSATQF32(u.exp+v.exp, u.sig*v.sig, 0); + if(u.sign^v.sign) VdV.qf32[i] = fNEGQF32(VdV.qf32[i])) + + +/* VMPY_QF16 */ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(16,vmpy_qf16,"Vd32.qf16=vmpy(Vu32.qf16,Vv32.qf16)","Vector multiply: qf16 output from qf16 inupt", + fHIDE(unfloat )u = fPARSEQF16(VuV.qf16[i]); + fHIDE(unfloat )v = fPARSEQF16(VvV.qf16[i]); + VdV.qf16[i] = fRNDSATQF16(u.exp+v.exp, u.sig*v.sig, 0)) + +/* VMPY_QF16_HF */ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(16,vmpy_qf16_hf,"Vd32.qf16=vmpy(Vu32.hf,Vv32.hf)","Vector multiply: qf16 output from ieee hf input", + fHIDE(unfloat )u = fPARSEHF(VuV.hf[i]); + fHIDE(unfloat )v = fPARSEHF(VvV.hf[i]); + VdV.qf16[i] = fRNDSATQF16(u.exp+v.exp, u.sig*v.sig, 0); + if(u.sign^v.sign) VdV.qf16[i] = fNEGQF16(VdV.qf16[i])) + +/* VMPY_QF16_with_QF16_HF */ +/* get the magnitude of qf16 before multiply */ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(16,vmpy_qf16_mix_hf,"Vd32.qf16=vmpy(Vu32.qf16,Vv32.hf)","Vector multiply: qf16 output from mixed input of qf16 and ieee hf", + fHIDE(unfloat )u = fPARSEQF16(VuV.qf16[i]); + fHIDE(unfloat )v = fPARSEHF(VvV.hf[i]); + VdV.qf16[i] = fRNDSATQF16(u.exp+v.exp, u.sig*v.sig, 0); + if(v.sign) VdV.qf16[i] = fNEGQF16(VdV.qf16[i])) + +/* VMPY_QF32_QF16 */ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(32,vmpy_qf32_qf16,"Vdd32.qf32=vmpy(Vu32.qf16,Vv32.qf16)","Vector multiply: double qf32 output from qf16 input", + fHIDE(unfloat )u0 = fPARSEQF16(VuV.w[i] & 0xFFFF); + fHIDE(unfloat )u1 = fPARSEQF16((VuV.w[i]>>16) & 0xFFFF); + fHIDE(unfloat )v0 = fPARSEQF16(VvV.w[i] & 0xFFFF); + fHIDE(unfloat )v1 = fPARSEQF16((VvV.w[i]>>16) & 0xFFFF); + VddV.v[0].qf32[i] = fRNDSATQF32(u0.exp+v0.exp, u0.sig*v0.sig, 0); + VddV.v[1].qf32[i] = fRNDSATQF32(u1.exp+v1.exp, u1.sig*v1.sig, 0)) + +/* VMPY_QF32_HF */ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(32,vmpy_qf32_hf,"Vdd32.qf32=vmpy(Vu32.hf,Vv32.hf)","Vector multiply: double qf32 output from ieee hf input", + fHIDE(unfloat )u0 = fPARSEHF(VuV.w[i] & 0xFFFF); + fHIDE(unfloat )u1 = fPARSEHF((VuV.w[i]>>16) & 0xFFFF); + fHIDE(unfloat )v0 = fPARSEHF(VvV.w[i] & 0xFFFF); + fHIDE(unfloat )v1 = fPARSEHF((VvV.w[i]>>16) & 0xFFFF); + VddV.v[0].qf32[i] = fRNDSATQF32(u0.exp+v0.exp, u0.sig*v0.sig, 0); + VddV.v[1].qf32[i] = fRNDSATQF32(u1.exp+v1.exp, u1.sig*v1.sig, 0); + if(u0.sign^v0.sign) VddV.v[0].qf32[i] = fNEGQF32(VddV.v[0].qf32[i]); + if(u1.sign^v1.sign) VddV.v[1].qf32[i] = fNEGQF32(VddV.v[1].qf32[i])) + +/* VMPY_QF32_with_QF16_HF */ +ITERATOR_INSN_MPY_SLOT_DOUBLE_VEC(32,vmpy_qf32_mix_hf,"Vdd32.qf32=vmpy(Vu32.qf16,Vv32.hf)","Vector multiply: double qf32 output from mixed input of qf16 and ieee hf", + fHIDE(unfloat )u0 = fPARSEQF16(VuV.w[i] & 0xFFFF); + fHIDE(unfloat )u1 = fPARSEQF16((VuV.w[i]>>16) & 0xFFFF); + fHIDE(unfloat )v0 = fPARSEHF(VvV.w[i] & 0xFFFF); + fHIDE(unfloat )v1 = fPARSEHF((VvV.w[i]>>16) & 0xFFFF); + VddV.v[0].qf32[i] = fRNDSATQF32(u0.exp+v0.exp, u0.sig*v0.sig, 0); + VddV.v[1].qf32[i] = fRNDSATQF32(u1.exp+v1.exp, u1.sig*v1.sig, 0); + if(v0.sign) VddV.v[0].qf32[i] = fNEGQF32(VddV.v[0].qf32[i]); + if(v1.sign) VddV.v[1].qf32[i] = fNEGQF32(VddV.v[1].qf32[i])) + +/* VADD_QF32 */ +ITERATOR_INSN_SHIFT_SLOT(32,vadd_qf32,"Vd32.qf32=vadd(Vu32.qf32,Vv32.qf32)","Vector addition of qf32 input", + fHIDE(unfloat )u = fPARSEQF32(VuV.qf32[i]); + fHIDE(unfloat )v = fPARSEQF32(VvV.qf32[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_SF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + VdV.qf32[i] = fRNDSATQF32(exp, sig, sig_low)) + +/* VADD_SF */ +ITERATOR_INSN_SHIFT_SLOT(32,vadd_sf,"Vd32.qf32=vadd(Vu32.sf,Vv32.sf)","Vector addition of sf input", + fHIDE(unfloat )u = fPARSESF(VuV.sf[i]); + fHIDE(unfloat )v = fPARSESF(VvV.sf[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_SF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + } + else if((u.sign==0) && (v.sign==1)) + { + sig = sig_u - sig_v; + sig_low = (u.exp>v.exp) ? (sig_u-sig)-sig_v : sig_u-(sig_v+sig); + } + else{ + sig = sig_v - sig_u; + sig_low = (v.exp>u.exp) ? (sig_v-sig)-sig_u : sig_v-(sig_u+sig); + } + VdV.qf32[i] = fRNDSATQF32(exp, sig, sig_low); + //printf("ARCHSIM: output:%x\\n", VdV.qf32[i]); + if(u.sign && v.sign) VdV.qf32[i] = fNEGQF32(VdV.qf32[i])) + +/* VADD_QF32_MIX */ +ITERATOR_INSN_SHIFT_SLOT(32,vadd_qf32_mix,"Vd32.qf32=vadd(Vu32.qf32,Vv32.sf)","Vector addition of mixed qf32 and sf", + fHIDE(unfloat )u = fPARSEQF32(VuV.qf32[i]); + fHIDE(unfloat )v = fPARSESF(VvV.sf[i]); + if(v.sign) v.sig = (-1.0)*v.sig; + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_SF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + VdV.qf32[i] = fRNDSATQF32(exp, sig, sig_low)) + +/* VSUB_QF32 */ +ITERATOR_INSN_SHIFT_SLOT(32,vsub_qf32,"Vd32.qf32=vsub(Vu32.qf32,Vv32.qf32)","Vector subtraction of qf32 input", + fHIDE(unfloat )u = fPARSEQF32(VuV.qf32[i]); + fHIDE(unfloat )v = fPARSEQF32(VvV.qf32[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_SF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)-sig_v : (sig_u-(sig_v+sig)); + VdV.qf32[i] = fRNDSATQF32(exp, sig, sig_low)) + +/* VSUB_SF */ +ITERATOR_INSN_SHIFT_SLOT(32,vsub_sf,"Vd32.qf32=vsub(Vu32.sf,Vv32.sf)","Vector subtraction of ieee sf input", + fHIDE(unfloat )u = fPARSESF(VuV.sf[i]); + fHIDE(unfloat )v = fPARSESF(VvV.sf[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_SF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)-sig_v : (sig_u-(sig_v+sig)); + } + else if(u.sign ^ v.sign){ + sig = sig_u + sig_v; + sig_low = (u.exp>v.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + } + else{ + sig = sig_v - sig_u; + sig_low = (v.exp>u.exp) ? (sig_v-sig)-sig_u : sig_v-(sig_u+sig); + } + VdV.qf32[i] = fRNDSATQF32(exp, sig, sig_low); + if((u.sign==1) && (v.sign==0)) VdV.qf32[i] = fNEGQF32(VdV.qf32[i])) + +/* VSUB_QF32_MIX */ +ITERATOR_INSN_SHIFT_SLOT(32,vsub_qf32_mix,"Vd32.qf32=vsub(Vu32.qf32,Vv32.sf)","Vector subtraction of mixed qf32 input and sf", + fHIDE(unfloat )u = fPARSEQF32(VuV.qf32[i]); + fHIDE(unfloat )v = fPARSESF(VvV.sf[i]); + if(v.sign) v.sig = (-1.0)*v.sig; + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_SF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)-sig_v : (sig_u-(sig_v+sig)); + VdV.qf32[i] = fRNDSATQF32(exp, sig, sig_low)) + +/* VADD_QF16 */ +ITERATOR_INSN_SHIFT_SLOT(16,vadd_qf16,"Vd32.qf16=vadd(Vu32.qf16,Vv32.qf16)","Vector addition of qf16 input", + fHIDE(unfloat )u = fPARSEQF16(VuV.qf16[i]); + fHIDE(unfloat )v = fPARSEQF16(VvV.qf16[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_HF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + VdV.qf16[i] = fRNDSATQF16(exp, sig, sig_low)) + +/* VADD_HF */ +ITERATOR_INSN_SHIFT_SLOT(16,vadd_hf,"Vd32.qf16=vadd(Vu32.hf,Vv32.hf)","Vector addition of hf input", + fHIDE(unfloat )u = fPARSEHF(VuV.hf[i]); + fHIDE(unfloat )v = fPARSEHF(VvV.hf[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_HF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + } + else if((u.sign==0) && (v.sign==1)) + { + sig = sig_u - sig_v; + sig_low = (u.exp>v.exp) ? (sig_u-sig)-sig_v : sig_u-(sig_v+sig); + } + else{ + sig = sig_v - sig_u; + sig_low = (v.exp>u.exp) ? (sig_v-sig)-sig_u : sig_v-(sig_u+sig); + } + VdV.qf16[i] = fRNDSATQF16(exp, sig, sig_low); + if(u.sign && v.sign) + VdV.qf16[i] = fNEGQF16(VdV.qf16[i])) + +/* VADD_QF16_MIX */ +ITERATOR_INSN_SHIFT_SLOT(16,vadd_qf16_mix,"Vd32.qf16=vadd(Vu32.qf16,Vv32.hf)","Vector addition of mixed qf16 and hf", + fHIDE(unfloat )u = fPARSEQF16(VuV.qf16[i]); + fHIDE(unfloat )v = fPARSEHF(VvV.hf[i]); + if(v.sign) v.sig = (-1.0)*v.sig; + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_HF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + VdV.qf16[i] = fRNDSATQF16(exp, sig, sig_low)) + +/* VSUB_QF16 */ +ITERATOR_INSN_SHIFT_SLOT(16,vsub_qf16,"Vd32.qf16=vsub(Vu32.qf16,Vv32.qf16)","Vector subtraction of qf16 input", + fHIDE(unfloat )u = fPARSEQF16(VuV.qf16[i]); + fHIDE(unfloat )v = fPARSEQF16(VvV.qf16[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_HF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)-sig_v : (sig_u-(sig_v+sig)); + VdV.qf16[i] = fRNDSATQF16(exp, sig, sig_low)) + +/* VSUB_HF */ +ITERATOR_INSN_SHIFT_SLOT(16,vsub_hf,"Vd32.qf16=vsub(Vu32.hf,Vv32.hf)","Vector subtraction of hf input", + fHIDE(unfloat )u = fPARSEHF(VuV.hf[i]); + fHIDE(unfloat )v = fPARSEHF(VvV.hf[i]); + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_HF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)-sig_v : (sig_u-(sig_v+sig)); + } + else if(u.sign ^ v.sign){ + sig = sig_u + sig_v; + sig_low = (u.exp>v.exp) ? (sig_u-sig)+sig_v : (sig_v-sig)+sig_u; + } + else{ + sig = sig_v - sig_u; + sig_low = (v.exp>u.exp) ? (sig_v-sig)-sig_u : sig_v-(sig_u+sig); + } + VdV.qf16[i] = fRNDSATQF16(exp, sig, sig_low); + if((u.sign==1) && (v.sign==0)) + VdV.qf16[i] = fNEGQF16(VdV.qf16[i])) + + +/* VSUB_QF16_MIXED */ +ITERATOR_INSN_SHIFT_SLOT(16,vsub_qf16_mix,"Vd32.qf16=vsub(Vu32.qf16,Vv32.hf)","Vector subtraction of mixed qf16 and hf", + fHIDE(unfloat )u = fPARSEQF16(VuV.qf16[i]); + fHIDE(unfloat )v = fPARSEHF(VvV.hf[i]); + if(v.sign) v.sig = (-1.0)*v.sig; + fHIDE(size2s_t exp=0;) + if (u.exp>v.exp) { + exp = u.exp+((u.sig==0.0)? (-(FRAC_HF+1)):ilogb(u.sig)); + if (expv.exp) ? (sig_u-sig)-sig_v : (sig_u-(sig_v+sig)); + VdV.qf16[i] = fRNDSATQF16(exp, sig, sig_low)) + +// FP Convert QF32/W/UW to ieee SF +ITERATOR_INSN_SHIFT_SLOT(32,vconv_sf_qf32,"Vd32.sf=Vu32.qf32","Vector conversion of qf32 format to ieee SF", + fHIDE(unfloat )u = fPARSEQF32(VuV.qf32[i]); + VdV.sf[i] = fRNDSATSF(u.exp, u.sig)) + +// FP Convert QF16/H/UH to ieee HF +ITERATOR_INSN_SHIFT_SLOT(16,vconv_hf_qf16,"Vd32.hf=Vu32.qf16","Vector conversion of qf16 format to ieee HF", + fHIDE(unfloat )u = fPARSEQF16(VuV.qf16[i]); + VdV.hf[i] = fRNDSATHF(u.exp, u.sig)) + +// FP Convert double QF32 to two packed ieee HF in one vector +ITERATOR_INSN_SHIFT_SLOT(32,vconv_hf_qf32,"Vd32.hf=Vuu32.qf32","Vector conversion of double qf32 to ieee HF", + fHIDE(unfloat )u0 = fPARSEQF32(VuuV.v[0].qf32[i]); + fHIDE(unfloat )u1 = fPARSEQF32(VuuV.v[1].qf32[i]); + VdV.hf[2*i] = fRNDSATHF(u0.exp, u0.sig); + VdV.hf[2*i+1] = fRNDSATHF(u1.exp, u1.sig)) ITERATOR_INSN2_MPY_SLOT_DOUBLE_VEC(16,vmpyih,"Vd32=vmpyih(Vu32,Vv32)","Vd32.h=vmpyi(Vu32.h,Vv32.h)", From 4481d3e6096e952d4636768b31e76fdc34baf2b9 Mon Sep 17 00:00:00 2001 From: Marco Liebel Date: Thu, 27 Feb 2025 10:50:30 -0800 Subject: [PATCH 1171/1179] FIXME: target/hexagon: Add qfloat files Signed-off-by: Marco Liebel --- target/hexagon/meson.build | 2 + target/hexagon/mmvec/kvx_ieee.c | 1460 ++++++++++++++ target/hexagon/mmvec/kvx_ieee.h | 141 ++ target/hexagon/mmvec/kvx_mac_reduce.c | 1156 +++++++++++ target/hexagon/mmvec/macros_auto.h | 221 +++ target/hexagon/mmvec/mmvec.h | 5 + target/hexagon/mmvec/mmvec_qfloat.c | 2563 +++++++++++++++++++++++++ target/hexagon/mmvec/mmvec_qfloat.h | 199 ++ target/hexagon/op_helper.c | 2 + 9 files changed, 5749 insertions(+) create mode 100644 target/hexagon/mmvec/kvx_ieee.c create mode 100644 target/hexagon/mmvec/kvx_ieee.h create mode 100644 target/hexagon/mmvec/kvx_mac_reduce.c create mode 100644 target/hexagon/mmvec/macros_auto.h create mode 100644 target/hexagon/mmvec/mmvec_qfloat.c create mode 100644 target/hexagon/mmvec/mmvec_qfloat.h diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build index 642c052d6e1c..280b5dc58ac5 100644 --- a/target/hexagon/meson.build +++ b/target/hexagon/meson.build @@ -263,6 +263,8 @@ hexagon_ss.add(files( 'fma_emu.c', 'mmvec/decode_ext_mmvec.c', 'mmvec/system_ext_mmvec.c', + 'mmvec/mmvec_qfloat.c', + 'mmvec/kvx_ieee.c', )) hexagon_softmmu_ss.add(files( diff --git a/target/hexagon/mmvec/kvx_ieee.c b/target/hexagon/mmvec/kvx_ieee.c new file mode 100644 index 000000000000..3e67230f62e4 --- /dev/null +++ b/target/hexagon/mmvec/kvx_ieee.c @@ -0,0 +1,1460 @@ +/* + * Copyright(c) 2019-2021 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * 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 . + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "kvx_ieee.h" +#include "kvx_mac_reduce.c" +#include "qemu/host-utils.h" + +uint32_t shiftRightJam32( uint32_t a, uint_fast16_t dist ) +{ + return + (dist < 31) ? a>>dist | ((uint32_t) (a<<(-dist & 31)) != 0) : (a != 0); +} + +uint_fast8_t countLeadingZeros16( uint16_t a ) +{ + return clz16(a); +} + +struct exp8_sig16 normSubnormalF16Sig( uint_fast16_t sig ) +{ + int_fast8_t shiftDist; + struct exp8_sig16 z; + + shiftDist = countLeadingZeros16( sig ) - 5; + z.exp = 1 - shiftDist; + z.sig = sig<>4; + sig &= ~(uint_fast16_t) (! (roundBits ^ 8) & roundNearEven); + if ( ! sig ) exp = 0; + + return packToF16UI( sign, exp, sig ); + +} + + +uint32_t fp_mult_sf_sf (uint32_t op1, uint32_t op2) +{ + + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("fp_mult_sf_sf"); + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF32UI(op1) || isNaNF32UI(op2)) + return FP32_DEF_NAN; + + u_op1.ui = op1; + u_op2.ui = op2; + a = u_op1.f; + b = u_op2.f; + rslt = a*b; + u_rslt.f = rslt; + result = u_rslt.ui; + + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint32_t fp_add_sf_sf (uint32_t op1, uint32_t op2) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("fp_add_sf_sf"); + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF32UI(op1) || isNaNF32UI(op2)) + return FP32_DEF_NAN; + + u_op1.ui = op1; + u_op2.ui = op2; + a = u_op1.f; + b = u_op2.f; + rslt = a+b; + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint32_t fp_sub_sf_sf (uint32_t op1, uint32_t op2) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF32UI(op1) || isNaNF32UI(op2)) + return FP32_DEF_NAN; + + u_op1.ui = op1; + u_op2.ui = op2; + a = u_op1.f; + b = u_op2.f; + rslt = a-b; + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +//-------------------------------------------------------------- +//Function to convert FP32 to FP16 +//-------------------------------------------------------------- + +uint16_t f32_to_f16 ( uint32_t a) +{ + bool sign; + int_fast16_t exp; + uint_fast32_t frac; + uint_fast16_t frac16; + + sign = signF32UI( a ); + exp = expF32UI ( a ); + frac = fracF32UI( a ); + + // Inf and NaN case + if ( exp == 0xFF ) { + if ( frac ) { + return FP16_DEF_NAN; + } else { + return packToF16UI( sign, 0x1F, 0 ); + } + } + + /*------------------------------------------------------------------------ + frac>>9 : keeping 14 bit of precision out ot 23 bits in FP32 + (frac & 0x1FF) != 0) : setting the sticky bit required for rounding + *------------------------------------------------------------------------*/ + frac16 = frac>>9 | ((frac & 0x1FF) != 0); + + //If input was a Zero + if ( ! (exp | frac16) ) { + return packToF16UI( sign, 0, 0 ); + } + + return roundPackToF16( sign, exp - 0x71, frac16 | 0x4000 ); + +} + +//-------------------------------------------------------------- +//Function to convert FP16 to FP32 +//-------------------------------------------------------------- + +uint32_t f16_to_f32( uint16_t a ) +{ + bool sign; + int_fast8_t exp; + uint_fast16_t frac; + struct exp8_sig16 normExpSig; + + sign = signF16UI( a ); + exp = expF16UI ( a ); + frac = fracF16UI( a ); + + + if ( exp == 0x1F ) { + if ( frac ) { + return FP32_DEF_NAN; + } else { + return packToF32UI( sign, 0xFF, 0 ); + } + } + + + if ( ! exp ) { + if ( ! frac ) { + return packToF32UI( sign, 0, 0 ); + } + normExpSig = normSubnormalF16Sig( frac ); + exp = normExpSig.exp - 1; + frac = normExpSig.sig; + } + + + return packToF32UI( sign, exp + 0x70, (uint_fast32_t) frac<<13 ); + +} + +uint16_t fp_mult_hf_hf (uint16_t op1, uint16_t op2) +{ + + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result_f32; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP16_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + rslt = a*b; + u_rslt.f = rslt; + result_f32 = u_rslt.ui; + + result = f32_to_f16(result_f32); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint16_t fp_add_hf_hf (uint16_t op1, uint16_t op2) +{ + + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result_f32; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP16_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + rslt = a+b; + u_rslt.f = rslt; + result_f32 = u_rslt.ui; + + result = f32_to_f16(result_f32); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint16_t fp_sub_hf_hf (uint16_t op1, uint16_t op2) +{ + + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result_f32; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP16_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + rslt = a-b; + u_rslt.f = rslt; + result_f32 = u_rslt.ui; + + result = f32_to_f16(result_f32); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint32_t fp_mult_sf_hf (uint16_t op1, uint16_t op2) +{ + + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP32_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + rslt = a*b; + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint32_t fp_add_sf_hf (uint16_t op1, uint16_t op2) +{ + + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP32_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + rslt = a+b; + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint32_t fp_sub_sf_hf (uint16_t op1, uint16_t op2) +{ + + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP32_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + rslt = a-b; + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint32_t fp_mult_sf_bf_acc (uint16_t op1, uint16_t op2, uint32_t acc) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_acc; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + double a,b,facc,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%04x\n",op1); + printf("Debug : op2 =0x%04x\n",op2); + printf("Debug : acc =0x%08x\n",acc); + #endif + + op1_f32 = ((uint32_t)op1) << 16; + op2_f32 = ((uint32_t)op2) << 16; + + if(isNaNF32UI(op1_f32) || isNaNF32UI(op2_f32) || isNaNF32UI(acc)) + return FP32_DEF_NAN; + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + u_acc.ui = acc; + a = u_op1.f; + b = u_op2.f; + facc = u_acc.f; + //rslt = fma(a,b,facc); + rslt = (a * b) + facc; + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : facc = %f\n",facc); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%04x\n",result); + #endif + + return result; +} + +uint32_t fp_mult_sf_bf (uint16_t op1, uint16_t op2) +{ + uint32_t op1_f32; + uint32_t op2_f32; + op1_f32 = ((uint32_t)op1) << 16; + op2_f32 = ((uint32_t)op2) << 16; + return fp_mult_sf_sf(op1_f32, op2_f32); +} + +uint32_t fp_add_sf_bf (uint16_t op1, uint16_t op2) +{ + uint32_t op1_f32; + uint32_t op2_f32; + op1_f32 = ((uint32_t)op1) << 16; + op2_f32 = ((uint32_t)op2) << 16; + return fp_add_sf_sf(op1_f32, op2_f32); +} + +uint32_t fp_sub_sf_bf (uint16_t op1, uint16_t op2) +{ + uint32_t op1_f32; + uint32_t op2_f32; + op1_f32 = ((uint32_t)op1) << 16; + op2_f32 = ((uint32_t)op2) << 16; + return fp_sub_sf_sf(op1_f32, op2_f32); +} + +uint16_t f16_to_uh( uint16_t op1) +{ + union ui32_f32 u_op1; + + float a,frac; + uint32_t op1_f32; + uint16_t result; + + //converting a NaN to an integral ----> Vx4Rslt is +MAX_INT + if(isNaNF16UI(op1)) + { + result = UHW_MAX; + goto end; + } + //converting a negative floating-point value to + //unsigned integer U(h|b) ----> (Vx4Rslt is 0) + if(signF16UI(op1)) + { + result = 0x0; + goto end; + } + //converting ±Inf to an integral ----> Vx4Rslt is ±MAX_INT + if(isInfF16UI(op1)) + { + result = UHW_MAX; + goto end; + } + //out of range FP to integer ------> Vx4Rslt is ±MAX_INT + + //The default float-to-integer conversion in C does not + //round to the nearest integer, but instead truncates toward zero. + op1_f32 = f16_to_f32(op1); + u_op1.ui = op1_f32; + a = u_op1.f; + frac = a - (float)((uint16_t) a); + //round to the nearest + result = (uint16_t) (a + 0.5); + //Ties to Even + if(frac == 0.5) + { + if((result % 2)) result--; + } + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : a frac = %f\n",frac); + #endif + + end: + #ifdef DEBUG + printf("Debug : result =0x%x\n",result); + #endif + return result; +} + +int16_t f16_to_h( uint16_t op1) +{ + union ui32_f32 u_op1; + + float a,frac; + uint32_t op1_f32; + int16_t result; + + //converting a NaN to an integral ----> Vx4Rslt is +MAX_INT + if(isNaNF16UI(op1)) + { + result = HW_MAX; + goto end; + } + //converting ±Inf to an integral ----> Vx4Rslt is ±MAX_INT + if(isInfF16UI(op1)) + { + result = signF16UI(op1) ? HW_MIN : HW_MAX; + goto end; + } + + //The default float-to-integer conversion in C does not round + //to the nearest integer, but instead truncates toward zero. + op1_f32 = f16_to_f32(op1); + u_op1.ui = op1_f32; + a = u_op1.f; + + //out of range FP to integer ------> Vx4Rslt is ±MAX_INT + if(a > (float)(HW_MAX)) + { + result = HW_MAX; + goto end; + } + if(a < (float)(HW_MIN)) + { + result = HW_MIN; + goto end; + } + + frac = fabs(a - (float)((int16_t) a)); + //round to the nearest + result = (a > 0) ? ((int16_t) (a + 0.5)) : ((int16_t) (a - 0.5)); + //Ties to Even + if(frac == 0.5) + { + if((result % 2)) + { + if(a > 0) result--; + if(a < 0) result++; + } + } + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : a frac = %f\n",frac); + #endif + + end: + #ifdef DEBUG + printf("Debug : result =0x%04x\n",result); + #endif + return result; +} + +uint8_t f16_to_ub( uint16_t op1) +{ + union ui32_f32 u_op1; + + float a,frac; + uint32_t op1_f32; + uint8_t result; + + //converting a NaN to an integral ----> Vx4Rslt is +MAX_INT + if(isNaNF16UI(op1)) + { + result = UBYTE_MAX; + goto end; + } + //converting a negative floating-point value to + //unsigned integer U(h|b) ----> (Vx4Rslt is 0) + if(signF16UI(op1)) + { + result = 0x0; + goto end; + } + //converting ±Inf to an integral ----> Vx4Rslt is ±MAX_INT + if(isInfF16UI(op1)) + { + result = UBYTE_MAX; + goto end; + } + + //The default float-to-integer conversion in C does + //not round to the nearest integer, but instead truncates toward zero. + op1_f32 = f16_to_f32(op1); + u_op1.ui = op1_f32; + a = u_op1.f; + + //out of range FP to integer ------> Vx4Rslt is ±MAX_INT + if( a > (float)(UBYTE_MAX)) + { + result = UBYTE_MAX; + goto end; + } + + frac = a - (float)((uint16_t) a); + //round to the nearest + result = (uint8_t) (a + 0.5); + //Ties to Even + if(frac == 0.5) + { + if((result % 2)) + { + if(a > 0) result--; + if(a < 0) result++; + } + } + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : a frac = %f\n",frac); + #endif + + end: + #ifdef DEBUG + printf("Debug : result =0x%x\n",result); + #endif + return result; +} + +int8_t f16_to_b( uint16_t op1) +{ + union ui32_f32 u_op1; + + float a,frac; + uint32_t op1_f32; + int16_t result; + + //converting a NaN to an integral ----> Vx4Rslt is +MAX_INT + if(isNaNF16UI(op1)) + { + result = BYTE_MAX; + goto end; + } + //converting ±Inf to an integral ----> Vx4Rslt is ±MAX_INT + if(isInfF16UI(op1)) + { + result = signF16UI(op1) ? BYTE_MIN : BYTE_MAX; + goto end; + } + + //The default float-to-integer conversion in C does not + //round to the nearest integer, but instead truncates toward zero. + op1_f32 = f16_to_f32(op1); + u_op1.ui = op1_f32; + a = u_op1.f; + + //out of range FP to integer ------> Vx4Rslt is ±MAX_INT + if(a > (float)(BYTE_MAX)) + { + result = BYTE_MAX; + goto end; + } + if(a < (float)(BYTE_MIN)) + { + result = BYTE_MIN; + goto end; + } + + frac = fabs(a - (float)((int16_t) a)); + //round to the nearest + result = (a > 0) ? ((int16_t) (a + 0.5)) : ((int16_t) (a - 0.5)); + //Ties to Even + if(frac == 0.5) + { + if((result % 2)) + { + if(a > 0) result--; + if(a < 0) result++; + } + } + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : a frac = %f\n",frac); + #endif + + end: + #ifdef DEBUG + printf("Debug : result =0x%04x\n",result); + #endif + return result; +} + +uint16_t uh_to_f16(uint16_t op1) +{ + union ui32_f32 u_op1; + + float a; + uint32_t rslt; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + #endif + + a = (float) op1; + u_op1.f = a; + rslt = u_op1.ui; + result = f32_to_f16(rslt); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : rslt = 0x%08x\n",rslt); + printf("Debug : result =0x%04x\n",result); + #endif + + return result; +} + +uint16_t h_to_f16 (int16_t op1) +{ + union ui32_f32 u_op1; + + float a; + uint32_t rslt; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + #endif + + a = (float) op1; + u_op1.f = a; + rslt = u_op1.ui; + result = f32_to_f16(rslt); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : rslt = 0x%08x\n",rslt); + printf("Debug : result =0x%04x\n",result); + #endif + + return result; +} + +uint16_t ub_to_f16(uint8_t op1) +{ + union ui32_f32 u_op1; + + float a; + uint32_t rslt; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + #endif + + a = (float) op1; + u_op1.f = a; + rslt = u_op1.ui; + result = f32_to_f16(rslt); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : rslt = 0x%08x\n",rslt); + printf("Debug : result =0x%04x\n",result); + #endif + + return result; +} + +uint16_t b_to_f16 (int8_t op1) +{ + union ui32_f32 u_op1; + + float a; + uint32_t rslt; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + #endif + + a = (float) op1; + u_op1.f = a; + rslt = u_op1.ui; + result = f32_to_f16(rslt); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : rslt = 0x%08x\n",rslt); + printf("Debug : result =0x%04x\n",result); + #endif + + return result; +} + +uint16_t sf_to_bf (int32_t op1) +{ + uint32_t rslt = op1; + if((rslt & 0x1FFFF) == 0x08000){ + //break; // do not round up if exactly .5 and even already + } + else if ((rslt & 0x8000) == 0x8000){ + rslt += 0x8000; //rounding to nearest number + } + rslt = isNaNF32UI(op1) ? FP32_DEF_NAN : rslt; + uint16_t result = (rslt >> 16); + return result; +} + +uint32_t fp_vdmpy (uint16_t op1_u,uint16_t op1_l,uint16_t op2_u,uint16_t op2_l) +{ + union ui32_f32 u_op; + union ui32_f32 u_rslt; + + uint32_t op1_u_f32, op1_l_f32, op2_u_f32, op2_l_f32; + float f_op1_u, f_op1_l, f_op2_u, f_op2_l; + double f_prod_l, f_prod_u, rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1_u =0x%04x\n",op1_u); + printf("Debug : op1_l =0x%04x\n",op1_l); + printf("Debug : op2_u =0x%04x\n",op2_u); + printf("Debug : op2_l =0x%04x\n",op2_l); + #endif + + if(isNaNF16UI(op1_u) || isNaNF16UI(op1_l) || isNaNF16UI(op2_u) || + isNaNF16UI(op2_l)) + { result = FP32_DEF_NAN; + goto end; + } + + op1_u_f32 = f16_to_f32(op1_u); + op1_l_f32 = f16_to_f32(op1_l); + op2_u_f32 = f16_to_f32(op2_u); + op2_l_f32 = f16_to_f32(op2_l); + + u_op.ui = op1_u_f32; + f_op1_u = u_op.f; + + u_op.ui = op1_l_f32; + f_op1_l = u_op.f; + + u_op.ui = op2_l_f32; + f_op2_l = u_op.f; + + u_op.ui = op2_u_f32; + f_op2_u = u_op.f; + + f_prod_l = f_op1_l * f_op2_l; + f_prod_u = f_op1_u * f_op2_u; + rslt = f_prod_u + f_prod_l; + + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : f_op1_u = %f\n",f_op1_u); + printf("Debug : f_op1_l = %f\n",f_op1_l); + printf("Debug : f_op2_u = %f\n",f_op2_u); + printf("Debug : f_op2_l = %f\n",f_op2_l); + printf("Debug : f_prod_l = %f\n",f_prod_l); + printf("Debug : f_prod_u = %f\n",f_prod_u); + printf("Debug : rslt = %f\n",rslt); + #endif + +end: + #ifdef DEBUG + printf("Debug : result =0x%08x\n",result); + #endif + return result; +} + +uint32_t fp_vdmpy_acc_dumb (uint32_t acc,uint16_t op1_u,uint16_t op1_l, + uint16_t op2_u,uint16_t op2_l) +{ + union ui32_f32 u_op; + union ui32_f32 u_acc; + union ui32_f32 u_rslt; + + uint32_t op1_u_f32, op1_l_f32, op2_u_f32, op2_l_f32; + float f_op1_u, f_op1_l, f_op2_u, f_op2_l, f_acc; + long double f_prod_l, f_prod_u, rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1_u =0x%04x\n",op1_u); + printf("Debug : op1_l =0x%04x\n",op1_l); + printf("Debug : op2_u =0x%04x\n",op2_u); + printf("Debug : op2_l =0x%04x\n",op2_l); + printf("Debug : acc =0x%08x\n",acc); + #endif + + op1_u_f32 = f16_to_f32(op1_u); + op1_l_f32 = f16_to_f32(op1_l); + op2_u_f32 = f16_to_f32(op2_u); + op2_l_f32 = f16_to_f32(op2_l); + + u_op.ui = op1_u_f32; + f_op1_u = u_op.f; + + u_op.ui = op1_l_f32; + f_op1_l = u_op.f; + + u_op.ui = op2_l_f32; + f_op2_l = u_op.f; + + u_op.ui = op2_u_f32; + f_op2_u = u_op.f; + + u_acc.ui = acc; + f_acc = u_acc.f; + + f_prod_l = (long double)(f_op1_l * f_op2_l); + f_prod_u = (long double)(f_op1_u * f_op2_u); + rslt = (long double)((long double)f_acc + f_prod_u + f_prod_l); + + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : f_op1_u = %f\n",f_op1_u); + printf("Debug : f_op1_l = %f\n",f_op1_l); + printf("Debug : f_op2_u = %f\n",f_op2_u); + printf("Debug : f_op2_l = %f\n",f_op2_l); + printf("Debug : f_acc = %f\n",f_acc); + printf("Debug : f_prod_l = %Lf\n",f_prod_l); + printf("Debug : f_prod_u = %Lf\n",f_prod_u); + printf("Debug : rslt = %Lf\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint16_t fp_min_hf(uint16_t op1,uint16_t op2) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result_f32; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP16_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + + rslt = (a>b) ? b : a; + // +0 is evaluated equal to -0 in C. Handeling that case separatly + if( (fabs(a) == 0.0f) && (fabs(b) == 0.0f) && (signF16UI(op1) != + signF16UI(op2)) ) + { + rslt = signF16UI(op1) ? a : b; + } + u_rslt.f = rslt; + result_f32 = u_rslt.ui; + + result = f32_to_f16(result_f32); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; + +} + +uint32_t fp_min_sf(uint32_t op1,uint32_t op2) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF32UI(op1) || isNaNF32UI(op2)) + return FP32_DEF_NAN; + + u_op1.ui = op1; + u_op2.ui = op2; + a = u_op1.f; + b = u_op2.f; + rslt = (a>b) ? b : a; + // +0 is evaluated equal to -0 in C. Handeling that case separatly + if( (fabs(a) == 0.0f) && (fabs(b) == 0.0f) && + (signF32UI(op1) != signF32UI(op2)) ) + { + rslt = signF32UI(op1) ? a : b; + } + u_rslt.f = rslt; + result = u_rslt.ui; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint16_t fp_min_bf(uint16_t op1,uint16_t op2) +{ + uint32_t op1_f32; + uint32_t op2_f32; + + uint32_t result_f32; + uint16_t result; + + op1_f32 = ((uint32_t)op1) << 16; + op2_f32 = ((uint32_t)op2) << 16; + + result_f32 = fp_min_sf(op1_f32, op2_f32); + result_f32 = result_f32 >> 16; + result = result_f32 & 0xFFFF; + return result; +} + + +uint16_t fp_max_hf(uint16_t op1,uint16_t op2) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,rslt; + uint32_t result_f32; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2)) + return FP16_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + a = u_op1.f; + b = u_op2.f; + + rslt = (a>b) ? a : b; + // +0 is evaluated equal to -0 in C. Handeling that case separatly + if( (fabs(a) == 0.0f) && + (fabs(b) == 0.0f) && (signF16UI(op1) != signF16UI(op2)) ) + { + rslt = signF16UI(op1) ? b : a; + } + u_rslt.f = rslt; + result_f32 = u_rslt.ui; + + result = f32_to_f16(result_f32); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; + +} + +uint32_t fp_max_sf(uint32_t op1,uint32_t op2) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_rslt; + + float a,b,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%08x\n",op1); + printf("Debug : op2 =0x%08x\n",op2); + #endif + + if(isNaNF32UI(op1) || isNaNF32UI(op2)) + return FP32_DEF_NAN; + + u_op1.ui = op1; + u_op2.ui = op2; + a = u_op1.f; + b = u_op2.f; + rslt = (a>b) ? a : b; + // +0 is evaluated equal to -0 in C. Handeling that case separatly + if( (fabs(a) == 0.0f) && (fabs(b) == 0.0f) && + (signF32UI(op1) != signF32UI(op2)) ) + { + rslt = signF32UI(op1) ? b : a; + } + u_rslt.f = rslt; + result = u_rslt.ui; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); + #endif + + return result; +} + +uint16_t fp_max_bf(uint16_t op1,uint16_t op2) +{ + uint32_t op1_f32; + uint32_t op2_f32; + + uint32_t result_f32; + uint16_t result; + + op1_f32 = ((uint32_t)op1) << 16; + op2_f32 = ((uint32_t)op2) << 16; + + result_f32 = fp_max_sf(op1_f32, op2_f32); + result_f32 = result_f32 >> 16; + result = result_f32 & 0xFFFF; + return result; +} + +uint16_t fp_abs_bf(uint16_t op1) +{ + union ui32_f32 u_op1; + + float result_f; + uint32_t result_f32; + uint16_t result; + + u_op1.ui = ((uint32_t)op1) << 16; + + result_f = fabs(u_op1.f); + u_op1.f = result_f; + result_f32 = u_op1.ui >> 16; + result = result_f32 & 0xFFFF; + return result; +} + +uint16_t fp_neg_bf(uint16_t op1) +{ + union ui32_f32 u_op1; + + float result_f; + uint32_t result_f32; + uint16_t result; + + u_op1.ui = ((uint32_t)op1) << 16; + + result_f = -(u_op1.f); + u_op1.f = result_f; + result_f32 = u_op1.ui >> 16; + result = result_f32 & 0xFFFF; + return result; +} + +//float fmaf( float x, float y, float z ); +uint16_t fp_mult_hf_hf_acc_dumb (uint16_t op1, uint16_t op2, uint16_t acc) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_acc; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + uint32_t acc_f32; + + float a,b,facc,rslt; + uint32_t result_f32; + uint16_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%04x\n",op1); + printf("Debug : op2 =0x%04x\n",op2); + printf("Debug : acc =0x%04x\n",acc); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2) || isNaNF16UI(acc)) + return FP16_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + acc_f32 = f16_to_f32(acc); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + u_acc.ui = acc_f32; + a = u_op1.f; + b = u_op2.f; + facc = u_acc.f; + //rslt = fma(a,b,facc); + rslt = (a * b) + facc; + u_rslt.f = rslt; + result_f32 = u_rslt.ui; + + result = f32_to_f16(result_f32); + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : facc = %f\n",facc); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%04x\n",result); + #endif + + return result; +} + +uint32_t fp_mult_sf_hf_acc (uint16_t op1, uint16_t op2, uint32_t acc) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_acc; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + + float a,b,facc,rslt; + uint32_t result; + + #ifdef DEBUG + printf("Debug : op1 =0x%04x\n",op1); + printf("Debug : op2 =0x%04x\n",op2); + printf("Debug : acc =0x%08x\n",acc); + #endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2) || isNaNF32UI(acc)) + return FP32_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + u_acc.ui = acc; + a = u_op1.f; + b = u_op2.f; + facc = u_acc.f; + //rslt = fma(a,b,facc); + rslt = (a * b) + facc; + u_rslt.f = rslt; + result = u_rslt.ui; + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + + #ifdef DEBUG + printf("Debug : a = %f\n",a); + printf("Debug : b = %f\n",b); + printf("Debug : facc = %f\n",facc); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%04x\n",result); + #endif + + return result; +} diff --git a/target/hexagon/mmvec/kvx_ieee.h b/target/hexagon/mmvec/kvx_ieee.h new file mode 100644 index 000000000000..ad80b7023925 --- /dev/null +++ b/target/hexagon/mmvec/kvx_ieee.h @@ -0,0 +1,141 @@ +/* + * Copyright(c) 2019-2021 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * 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 . + */ + +#ifndef KVX_COMPACT_H +#define KVX_COMPACT_H 1 + +#include +#include "hex_arch_types.h" + +//Double precision +#define signF64UI( a ) ((bool) ((uint64_t) (a)>>63)) +#define expF64UI( a ) ((int_fast16_t) ((a)>>52) & 0x7FF) +#define fracF64UI( a ) ((a) & UINT64_C( 0x000FFFFFFFFFFFFF )) +#define packToF64UI( sign, exp, sig ) ((uint64_t) (((uint_fast64_t) (sign)<<63) + ((uint_fast64_t) (exp)<<52) + (sig))) +#define isNaNF64UI( a ) (((~(a) & UINT64_C( 0x7FF0000000000000 )) == 0) && ((a) & UINT64_C( 0x000FFFFFFFFFFFFF ))) + +//SF defines +#define FP32_DEF_NAN 0x7FFFFFFF +#define isNaNF32UI( a ) (((~(a) & 0x7F800000) == 0) && ((a) & 0x007FFFFF)) +#define isInfF32UI( a ) (((~(a) & 0x7F800000) == 0) && (((a) & 0x007FFFFF) == 0)) +#define signF32UI( a ) ((bool) ((uint32_t) (a)>>31)) +#define expF32UI( a ) ((int_fast16_t) ((a)>>23) & 0xFF) +#define fracF32UI( a ) ((a) & 0x007FFFFF) +#define packToF32UI( sign, exp, sig ) (((uint32_t) (sign)<<31) + ((uint32_t) (exp)<<23) + (sig)) + +//HF defines +#define FP16_DEF_NAN 0x7FFF +#define isNaNF16UI( a ) (((~(a) & 0x7C00) == 0) && ((a) & 0x03FF)) +#define isInfF16UI( a ) (((~(a) & 0x7C00) == 0) && (((a) & 0x03FF) == 0)) +#define signF16UI( a ) ((bool) ((uint16_t) (a)>>15)) +#define expF16UI( a ) ((int_fast8_t) ((a)>>10) & 0x1F) +#define fracF16UI( a ) ((a) & 0x03FF) +#define packToF16UI( sign, exp, sig ) (((uint16_t) (sign)<<15) + ((uint16_t) (exp)<<10) + (sig)) + +#define UHW_MIN 0 +#define UHW_MAX 65535 +#define HW_MIN -32768 +#define HW_MAX 32767 + +#define UBYTE_MIN 0 +#define UBYTE_MAX 255 +#define BYTE_MIN -128 +#define BYTE_MAX 127 + +//union ui16_f16 { uint16_t ui; float16_t f; }; +union ui32_f32 { uint32_t ui; float f; }; +union ui64_f64 { uint64_t ui; double f; }; +struct exp8_sig16 { int_fast8_t exp; uint_fast16_t sig; }; + +uint32_t shiftRightJam32( uint32_t a, uint_fast16_t dist ); +uint_fast8_t countLeadingZeros16( uint16_t a ); +struct exp8_sig16 normSubnormalF16Sig( uint_fast16_t sig ); +uint16_t roundPackToF16( bool sign, int_fast16_t exp, uint_fast16_t sig ); + +//-------------------------------------------------------------------------- +// IEEE - FP Convert instructions +//-------------------------------------------------------------------------- +uint16_t f32_to_f16 ( uint32_t a); +uint32_t f16_to_f32( uint16_t a ); + +uint16_t f16_to_uh( uint16_t op1); +int16_t f16_to_h ( uint16_t op1); +uint8_t f16_to_ub( uint16_t op1); +int8_t f16_to_b ( uint16_t op1); + +uint16_t uh_to_f16(uint16_t op1); +uint16_t h_to_f16 (int16_t op1); +uint16_t ub_to_f16(uint8_t op1); +uint16_t b_to_f16 (int8_t op1); + +uint16_t sf_to_bf (int32_t op1); + +//-------------------------------------------------------------------------- +// IEEE - FP ADD/SUB/MPY instructions +//-------------------------------------------------------------------------- + +//size4s_t fp_mult(size4s_t input_1, size4s_t input_2); +uint32_t fp_mult_sf_sf (uint32_t op1, uint32_t op2); +uint32_t fp_add_sf_sf (uint32_t op1, uint32_t op2); +uint32_t fp_sub_sf_sf (uint32_t op1, uint32_t op2); + +uint16_t fp_mult_hf_hf (uint16_t op1, uint16_t op2); +uint16_t fp_add_hf_hf (uint16_t op1, uint16_t op2); +uint16_t fp_sub_hf_hf (uint16_t op1, uint16_t op2); + +uint32_t fp_mult_sf_hf (uint16_t op1, uint16_t op2); +uint32_t fp_add_sf_hf (uint16_t op1, uint16_t op2); +uint32_t fp_sub_sf_hf (uint16_t op1, uint16_t op2); + +uint32_t fp_mult_sf_bf (uint16_t op1, uint16_t op2); +uint32_t fp_add_sf_bf (uint16_t op1, uint16_t op2); +uint32_t fp_sub_sf_bf (uint16_t op1, uint16_t op2); + +//-------------------------------------------------------------------------- +// IEEE - FP Accumulate instructions +//-------------------------------------------------------------------------- + +uint16_t fp_mult_hf_hf_acc (uint16_t op1, uint16_t op2, uint16_t acc); +uint32_t fp_mult_sf_bf_acc (uint16_t op1, uint16_t op2, uint32_t acc); +uint32_t fp_mult_sf_hf_acc (uint16_t op1, uint16_t op2, uint32_t acc); + +//-------------------------------------------------------------------------- +// IEEE - FP Reduce instructions +//-------------------------------------------------------------------------- + +uint32_t fp_vdmpy (uint16_t op1_u,uint16_t op1_l,uint16_t op2_u,uint16_t op2_l); +uint32_t fp_vdmpy_acc (uint32_t acc,uint16_t op1_u,uint16_t op1_l,uint16_t op2_u,uint16_t op2_l); + +//-------------------------------------------------------------------------- +// IEEE - FP Select instructions +//-------------------------------------------------------------------------- + +uint16_t fp_min_hf(uint16_t op1,uint16_t op2); +uint16_t fp_max_hf(uint16_t op1,uint16_t op2); +uint32_t fp_min_sf(uint32_t op1,uint32_t op2); +uint32_t fp_max_sf(uint32_t op1,uint32_t op2); +uint16_t fp_min_bf(uint16_t op1,uint16_t op2); +uint16_t fp_max_bf(uint16_t op1,uint16_t op2); +uint16_t fp_abs_bf(uint16_t op1); +uint16_t fp_neg_bf(uint16_t op1); + +//-------------------------------------------------------------------------- +// IEEE - FP Experiment Implementations +//-------------------------------------------------------------------------- +uint16_t fp_mult_hf_hf_acc_dumb (uint16_t op1, uint16_t op2, uint16_t acc); +uint32_t fp_vdmpy_acc_dumb (uint32_t acc,uint16_t op1_u,uint16_t op1_l,uint16_t op2_u,uint16_t op2_l); +#endif diff --git a/target/hexagon/mmvec/kvx_mac_reduce.c b/target/hexagon/mmvec/kvx_mac_reduce.c new file mode 100644 index 000000000000..e11e41ae5891 --- /dev/null +++ b/target/hexagon/mmvec/kvx_mac_reduce.c @@ -0,0 +1,1156 @@ +/* + * Copyright(c) 2019-2021 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * 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 . + */ + +#include "qemu/osdep.h" +#include "kvx_ieee.h" + +#define DF_MANTBITS() 52 +#define SF_MANTBITS() 23 +#define HF_MANTBITS() 10 + +#define DF_INF_EXP 0x7ff +#define DF_BIAS 1023 + +#define SF_INF_EXP 0xff +#define SF_BIAS 127 + +#define HF_INF_EXP 0x1f +#define HF_BIAS 15 + +#define WAY_BIG_EXP 4096 + +#define isz(X) (fabs(X) == 0.0f) + + +typedef union { + double f; + size8u_t i; +#ifndef SLOWLARIS + struct { + size8u_t mant:52; + size8u_t exp:11; + size8u_t sign:1; + } x; +#else + struct { + size8u_t sign:1; + size8u_t exp:11; + size8u_t mant:52; + } x; +#endif +} df_t; + +typedef union { + float f; + size4u_t i; +#ifndef SLOWLARIS + struct { + size4u_t mant:23; + size4u_t exp:8; + size4u_t sign:1; + } x; +#else + struct { + size4u_t sign:1; + size4u_t exp:8; + size4u_t mant:23; + } x; +#endif +} sf_t; + +typedef struct { + union { + size8u_t low; + struct { +#ifndef SLOWLARIS + size4u_t w0; + size4u_t w1; +#else + size4u_t w1; + size4u_t w0; +#endif + }; + }; + union { + size8u_t high; + struct { +#ifndef SLOWLARIS + size4u_t w2; + size4u_t w3; +#else + size4u_t w3; + size4u_t w2; +#endif + }; + }; +} int128_t; + +typedef struct { + int128_t mant; + size4s_t exp; + size1u_t sign; + size1u_t guard; + size1u_t round; + size1u_t sticky; +} xf_t; + +static inline void xf_init(xf_t * p) +{ + p->mant.low = 0; + p->mant.high = 0; + p->exp = 0; + p->sign = 0; + p->guard = 0; + p->round = 0; + p->sticky = 0; +} + +size8u_t df_getmant_kvx(df_t a); +size8u_t df_getmant_kvx(df_t a) +{ + //int class = fpclassify(a.f); + //switch (class) { + //case FP_NORMAL: + return (a.x.mant | 1ULL << 52); + //case FP_ZERO: + // return 0; + //case FP_SUBNORMAL: + // return a.x.mant; + //default: + // return -1; + //}; +} + +size4s_t df_getexp_kvx(df_t a); +size4s_t df_getexp_kvx(df_t a) +{ + //int class = fpclassify(a.f); + //switch (class) { + //case FP_NORMAL: + return a.x.exp; + //case FP_SUBNORMAL: + // return a.x.exp + 1; + //default: + // return -1; + //}; +} + +size8u_t sf_getmant_kvx(sf_t a); +size8u_t sf_getmant_kvx(sf_t a) +{ + //case FP_ZERO: + if((a.x.mant == 0) && (a.x.exp == 0)) + return 0; + //case FP_SUBNORMAL: + else if((a.x.mant != 0) && (a.x.exp == 0)) + return a.x.mant; + //case FP_NORMAL: + else if((a.x.exp != 0xFF) && (a.x.exp != 0)) + return (a.x.mant | 1ULL << 23); + //default: + else + return -1; +} + +size4s_t sf_getexp_kvx(sf_t a); +size4s_t sf_getexp_kvx(sf_t a) +{ + //case FP_SUBNORMAL: + if((a.x.mant != 0) && (a.x.exp == 0)) + return a.x.exp + 1; + //case FP_NORMAL: + else if((a.x.exp != 0xFF) && (a.x.exp != 0)) + return a.x.exp; + //default: + else + return -1; +} + +static inline void xf_debug(const char *msg, xf_t a) +{ +#ifdef DEBUG + printf("%s %c0x%016llx_%016llx /%d/%d/%d p%d\n", msg, + a.sign ? '-' : '+', a.mant.high, a.mant.low, a.guard, + a.round, a.sticky, a.exp); +#endif +} + +static inline int128_t int128_shl(int128_t a, size4u_t amt) +{ + int128_t ret; + if (amt == 0) + return a; + if (amt > 128) { + ret.high = 0; + ret.low = 0; + return ret; + } + if (amt >= 64) { + amt -= 64; + a.high = a.low; + a.low = 0; + } + ret.high = a.high << amt; + ret.high |= (a.low >> (64 - amt)); + ret.low = a.low << amt; + return ret; +} + +static inline int128_t int128_shr(int128_t a, size4u_t amt) +{ + int128_t ret; + if (amt == 0) + return a; + if (amt > 128) { + ret.high = 0; + ret.low = 0; + return ret; + } + if (amt >= 64) { + amt -= 64; + a.low = a.high; + a.high = 0; + } + ret.low = a.low >> amt; + ret.low |= (a.high << (64 - amt)); + ret.high = a.high >> amt; + return ret; +} + + +#define int128_gt kvx_int128_gt +static inline int kvx_int128_gt(int128_t a, int128_t b) +{ + if (a.high == b.high) + return (a.low > b.low); + return (a.high > b.high); +} + +static inline xf_t xf_norm_left(xf_t a) +{ + a.exp--; + a.mant = int128_shl(a.mant, 1); + a.mant.low |= a.guard; + a.guard = a.round; + a.round = a.sticky; + return a; +} + +static inline xf_t xf_norm_right(xf_t a, int amt) +{ + if (amt > 130) { + a.sticky |= + a.round | a.guard | (a.mant.low != 0) | (a.mant.high != 0); + a.guard = a.round = a.mant.high = a.mant.low = 0; + a.exp += amt; + return a; + + } + while (amt >= 64) { + a.sticky |= a.round | a.guard | (a.mant.low != 0); + a.guard = (a.mant.low >> 63) & 1; + a.round = (a.mant.low >> 62) & 1; + a.mant.low = a.mant.high; + a.mant.high = 0; + a.exp += 64; + amt -= 64; + } + while (amt > 0) { + a.exp++; + a.sticky |= a.round; + a.round = a.guard; + a.guard = a.mant.low & 1; + a.mant = int128_shr(a.mant, 1); + amt--; + } + return a; +} + +#define int128_add kvx_int128_add +static inline int128_t kvx_int128_add(int128_t a, int128_t b) +{ + int128_t ret; + ret.low = a.low + b.low; + if ((ret.low < a.low) || (ret.low < b.low)) { + /* carry into high part */ + a.high += 1; + } + ret.high = a.high + b.high; + return ret; +} + +#define int128_sub kvx_int128_sub +static inline int128_t kvx_int128_sub(int128_t a, int128_t b, int borrow) +{ + int128_t ret; + ret.low = a.low - b.low; + if (ret.low > a.low) { + /* borrow into high part */ + a.high -= 1; + } + ret.high = a.high - b.high; + if (borrow == 0) { + return ret; + } else { + a.high = 0; + a.low = 1; + return int128_sub(ret, a, 0); + } +} + +/* Return an infinity with the same sign as a */ +static inline df_t infinite_df_t(xf_t a) +{ + df_t ret; + ret.x.sign = a.sign; + ret.x.exp = DF_INF_EXP; + ret.x.mant = 0ULL; + return ret; +} + +/* Return a maximum finite value with the same sign as a */ +static inline df_t maxfinite_df_t(xf_t a) +{ + df_t ret; + ret.x.sign = a.sign; + ret.x.exp = DF_INF_EXP - 1; + ret.x.mant = 0x000fffffffffffffULL; + return ret; +} + +static inline df_t f2df_t(double in) +{ + df_t ret; + ret.f = in; + return ret; +} + +/* Return an infinity with the same sign as a */ +static inline sf_t infinite_sf_t(xf_t a) +{ + sf_t ret; + ret.x.sign = a.sign; + ret.x.exp = SF_INF_EXP; + ret.x.mant = 0ULL; + return ret; +} + +/* Return a maximum finite value with the same sign as a */ +static inline sf_t maxfinite_sf_t(xf_t a) +{ + sf_t ret; + ret.x.sign = a.sign; + ret.x.exp = SF_INF_EXP - 1; + ret.x.mant = 0x007fffffUL; + return ret; +} + +static inline sf_t f2sf_t(float in) +{ + sf_t ret; + ret.f = in; + return ret; +} + +#define GEN_XF_ROUND(TYPE,MANTBITS,INF_EXP) \ +TYPE xf_round_kvx_##TYPE(xf_t a); \ +TYPE xf_round_kvx_##TYPE(xf_t a) \ +{ \ + TYPE ret; \ + ret.i = 0; \ + ret.x.sign = a.sign; \ + if ((a.mant.high == 0) && (a.mant.low == 0) \ + && ((a.guard | a.round | a.sticky) == 0)) { \ + /* result zero */ \ + /*switch (fegetround()) { */\ + /*case FE_DOWNWARD: */\ + /* return f2##TYPE(-0.0); */\ + /*default: */\ + if(a.sign) return f2##TYPE(-0.0); \ + else return f2##TYPE(0.0); \ + /*} */\ + } \ + /* Normalize right */ \ + /* We want MANTBITS bits of mantissa plus the leading one. */ \ + /* That means that we want MANTBITS+1 bits, or 0x000000000000FF_FFFF */ \ + /* So we need to normalize right while the high word is non-zero and \ + * while the low word is nonzero when masked with 0xffe0_0000_0000_0000 */ \ + xf_debug("input: ", a); \ + while ((a.mant.high != 0) || ((a.mant.low >> (MANTBITS+1)) != 0)) { \ + a = xf_norm_right(a, 1); \ + } \ + xf_debug("norm_right: ", a); \ + /* OK, now normalize left */ \ + /* We want to normalize left until we have a leading one in bit 24 */ \ + /* Theoretically, we only need to shift a maximum of one to the left if we \ + * shifted out lots of bits from B, or if we had no shift / 1 shift sticky shoudl be 0 \ + */ \ + while ((a.mant.low & (1ULL << MANTBITS)) == 0) { \ + a = xf_norm_left(a); \ + } \ + xf_debug("norm_left: ", a); \ + /* OK, now we might need to denormalize because of potential underflow. We need \ + * to do this before rounding, and rounding might make us normal again */ \ + while (a.exp <= 0) { \ + a = xf_norm_right(a, 1 - a.exp); \ + /* Do we have underflow? That's when we get an inexact answer because we \ + * ran out of bits in a denormal. */ \ + if (a.guard || a.round || a.sticky) { \ + /*feraiseexcept(FE_UNDERFLOW);*/ \ + } \ + } \ + xf_debug("norm_denorm: ", a); \ + /* OK, we're relatively canonical... now we need to round */ \ + if (a.guard || a.round || a.sticky) { \ + /*feraiseexcept(FE_INEXACT);*/ \ + /*switch (fegetround()) { */\ + /*case FE_TOWARDZERO: */\ + /* Chop and we're done */ \ + /* break; */\ + /*case FE_UPWARD: */\ + /* if (a.sign == 0) a.mant.low += 1; */\ + /* break; */\ + /*case FE_DOWNWARD: */\ + /* if (a.sign != 0) a.mant.low += 1; */\ + /* break; */\ + /*default: */\ + if (a.round || a.sticky) { \ + /* round up if guard is 1, down if guard is zero */ \ + a.mant.low += a.guard; \ + } else if (a.guard) { \ + /* exactly .5, round up if odd */ \ + a.mant.low += (a.mant.low & 1); \ + } \ + /*break; */\ + /*}*/ \ + } \ + xf_debug("post_round: ", a); \ + /* OK, now we might have carried all the way up. So we might need to shr once */ \ + /* at least we know that the lsb should be zero if we rounded and got a carry out... */ \ + if ((a.mant.low >> (MANTBITS+1)) != 0) { \ + a = xf_norm_right(a, 1); \ + } \ + xf_debug("once_norm_right: ", a); \ + /* Overflow? */ \ + if (a.exp >= INF_EXP) { \ + /* Yep, inf result */ \ + xf_debug("inf: ", a); \ + /*feraiseexcept(FE_OVERFLOW);*/ \ + /*feraiseexcept(FE_INEXACT);*/ \ + /*switch (fegetround()) { */\ + /*case FE_TOWARDZERO: */\ + /* return maxfinite_##TYPE(a); */\ + /*case FE_UPWARD: */\ + /* if (a.sign == 0) */\ + /* return infinite_##TYPE(a); */\ + /* else */\ + /* return maxfinite_##TYPE(a); */\ + /*case FE_DOWNWARD: */\ + /* if (a.sign != 0) */\ + /* return infinite_##TYPE(a); */\ + /* else */\ + /* return maxfinite_##TYPE(a); */\ + /*default: */\ + return infinite_##TYPE(a); \ + /*} */\ + } \ + /* Underflow? */ \ + if (a.mant.low & (1ULL << MANTBITS)) { \ + /* Leading one means: No, we're normal. So, we should be done... */ \ + xf_debug("norm: ", a); \ + ret.x.exp = a.exp; \ + ret.x.mant = a.mant.low; \ + return ret; \ + } \ + xf_debug("denorm: ", a); \ + if (a.exp != 1) \ + /*printf("a.exp == %d\n", a.exp);*/ \ + assert(a.exp == 1); \ + ret.x.exp = 0; \ + ret.x.mant = a.mant.low; \ + return ret; \ +} + +#define GEN_HF_ROUND(TYPE,MANTBITS,INF_EXP) \ +TYPE hf_round_##TYPE(xf_t a); \ +TYPE hf_round_##TYPE(xf_t a) \ +{ \ + TYPE ret; \ + ret.i = 0; \ + ret.x.sign = a.sign; \ + if ((a.mant.high == 0) && (a.mant.low == 0) \ + && ((a.guard | a.round | a.sticky) == 0)) { \ + /* result zero */ \ + /*switch (fegetround()) { */\ + /*case FE_DOWNWARD: */\ + /* return f2##TYPE(-0.0); */\ + /*default: */\ + if(a.sign) return f2##TYPE(-0.0); \ + else return f2##TYPE(0.0); \ + /*} */\ + } \ + /* Normalize right */ \ + /* We want MANTBITS bits of mantissa plus the leading one. */ \ + /* That means that we want MANTBITS+1 bits, or 0x000000000000FF_FFFF */ \ + /* So we need to normalize right while the high word is non-zero and \ + * while the low word is nonzero when masked with 0xffe0_0000_0000_0000 */ \ + xf_debug("input: ", a); \ + while ((a.mant.high != 0) || ((a.mant.low >> (MANTBITS+1)) != 0)) { \ + a = xf_norm_right(a, 1); \ + } \ + xf_debug("norm_right: ", a); \ + /* OK, now normalize left */ \ + /* We want to normalize left until we have a leading one in bit 24 */ \ + /* Theoretically, we only need to shift a maximum of one to the left if we \ + * shifted out lots of bits from B, or if we had no shift / 1 shift sticky shoudl be 0 \ + */ \ + while ((a.mant.low & (1ULL << MANTBITS)) == 0) { \ + a = xf_norm_left(a); \ + } \ + xf_debug("norm_left: ", a); \ + /* OK, now we might need to denormalize because of potential underflow. We need \ + * to do this before rounding, and rounding might make us normal again */ \ + while (a.exp <= 0) { \ + a = xf_norm_right(a, 1 - a.exp); \ + /* Do we have underflow? That's when we get an inexact answer because we \ + * ran out of bits in a denormal. */ \ + if (a.guard || a.round || a.sticky) { \ + /*feraiseexcept(FE_UNDERFLOW);*/ \ + } \ + } \ + xf_debug("norm_denorm: ", a); \ + /* OK, we're relatively canonical... now we need to round */ \ + /*if (a.guard || a.round || a.sticky) { */\ + /*feraiseexcept(FE_INEXACT);*/ \ + /*switch (fegetround()) { */\ + /*case FE_TOWARDZERO: */\ + /* Chop and we're done */ \ + /* break; */\ + /*case FE_UPWARD: */\ + /* if (a.sign == 0) a.mant.low += 1; */\ + /* break; */\ + /*case FE_DOWNWARD: */\ + /* if (a.sign != 0) a.mant.low += 1; */\ + /* break; */\ + /*default: */\ + if (a.round || a.sticky || a.guard) { \ + /* round up if guard is 1, down if guard is zero */ \ + if ((a.mant.low & 0xFFF) == 0) a.mant.low += 1; \ + /* } else if (a.guard) {*/ \ + /* exactly .5, round up if odd */ \ + /* a.mant.low += (a.mant.low & 1); */\ + } \ + /*break; */\ + /*}*/ \ + /*} */\ + xf_debug("post_round: ", a); \ + /* OK, now we might have carried all the way up. So we might need to shr once */ \ + /* at least we know that the lsb should be zero if we rounded and got a carry out... */ \ + if ((a.mant.low >> (MANTBITS+1)) != 0) { \ + a = xf_norm_right(a, 1); \ + } \ + xf_debug("once_norm_right: ", a); \ + /* Overflow? */ \ + if (a.exp >= INF_EXP) { \ + /* Yep, inf result */ \ + xf_debug("inf: ", a); \ + /*feraiseexcept(FE_OVERFLOW);*/ \ + /*feraiseexcept(FE_INEXACT);*/ \ + /*switch (fegetround()) { */\ + /*case FE_TOWARDZERO: */\ + /* return maxfinite_##TYPE(a); */\ + /*case FE_UPWARD: */\ + /* if (a.sign == 0) */\ + /* return infinite_##TYPE(a); */\ + /* else */\ + /* return maxfinite_##TYPE(a); */\ + /*case FE_DOWNWARD: */\ + /* if (a.sign != 0) */\ + /* return infinite_##TYPE(a); */\ + /* else */\ + /* return maxfinite_##TYPE(a); */\ + /*default: */\ + return infinite_##TYPE(a); \ + /*} */\ + } \ + /* Underflow? */ \ + if (a.mant.low & (1ULL << MANTBITS)) { \ + /* Leading one means: No, we're normal. So, we should be done... */ \ + xf_debug("norm: ", a); \ + ret.x.exp = a.exp; \ + ret.x.mant = a.mant.low; \ + return ret; \ + } \ + xf_debug("denorm: ", a); \ + if (a.exp != 1) \ + /*printf("a.exp == %d\n", a.exp);*/ \ + assert(a.exp == 1); \ + ret.x.exp = 0; \ + ret.x.mant = a.mant.low; \ + return ret; \ +} + + +GEN_XF_ROUND(df_t,DF_MANTBITS(),DF_INF_EXP) +GEN_XF_ROUND(sf_t,SF_MANTBITS(),SF_INF_EXP) +GEN_HF_ROUND(sf_t,SF_MANTBITS(),SF_INF_EXP) + +#define int128_mult_6464 kvx_int128_mult_6464 +static inline int128_t kvx_int128_mult_6464(size8u_t ai, size8u_t bi) +{ + int128_t ret; + int128_t a, b; + size8u_t pp0, pp1a, pp1b, pp1s, pp2; + +#ifdef DEBUG + printf("ai/bi: 0x%016llx/0x%016llx\n", ai, bi); +#endif + a.high = b.high = 0; + a.low = ai; + b.low = bi; + pp0 = (size8u_t) a.w0 * (size8u_t) b.w0; + pp1a = (size8u_t) a.w1 * (size8u_t) b.w0; + pp1b = (size8u_t) b.w1 * (size8u_t) a.w0; + pp2 = (size8u_t) a.w1 * (size8u_t) b.w1; +#ifdef DEBUG + printf("pp2/1b/1a/0: 0x%016llx/0x%016llx/0x%016llx/0x%016llx\n", + pp2, pp1b, pp1a, pp0); +#endif + pp1s = pp1a + pp1b; + if ((pp1s < pp1a) || (pp1s < pp1b)) { + pp2 += (1ULL << 32); + } + ret.low = pp0 + (pp1s << 32); + if ((ret.low < pp0) || (ret.low < (pp1s << 32))) + pp2 += 1; + ret.high = pp2 + (pp1s >> 32); +#ifdef DEBUG + printf("pp1s/rethi/retlo: 0x%016llx/0x%016llx/0x%016llx\n", + pp1s, ret.high, ret.low); +#endif + return ret; +} + +xf_t xf_add_kvx(xf_t a, xf_t b); + +xf_t xf_sub_kvx(xf_t a, xf_t b, int negate); +xf_t xf_sub_kvx(xf_t a, xf_t b, int negate) +{ + xf_t ret; + xf_init(&ret); + int borrow; + xf_debug("-->Sub/a: ", a); + xf_debug("-->Sub/b: ", b); + if (a.sign != b.sign) { + b.sign = !b.sign; + return xf_add_kvx(a, b); + } + if (b.exp > a.exp) { + /* small - big == - (big - small) */ + return xf_sub_kvx(b, a, !negate); + } + if ((b.exp == a.exp) && (int128_gt(b.mant, a.mant))) { + /* small - big == - (big - small) */ + return xf_sub_kvx(b, a, !negate); + } + xf_debug("OK: Sub/a: ", a); + xf_debug("OK: Sub/b: ", b); + while (a.exp > b.exp) { + /* Try to normalize exponents: shrink a exponent and grow mantissa */ + if (a.mant.high & (1ULL << 62)) { + /* Can't grow a any more */ + break; + } else { + a = xf_norm_left(a); + } + } + xf_debug("norm_l: Sub/a: ", a); + xf_debug("norm_l: Sub/b: ", b); + while (a.exp > b.exp) { + /* Try to normalize exponents: grow b exponent and shrink mantissa */ + /* Keep around shifted out bits... we might need those later */ + b = xf_norm_right(b, a.exp - b.exp); + } + xf_debug("norm_r: Sub/a: ", a); + xf_debug("norm_r: Sub/b: ", b); + if ((int128_gt(b.mant, a.mant))) { + xf_debug("retry: Sub/a: ", a); + xf_debug("retry: Sub/b: ", b); + return xf_sub_kvx(b, a, !negate); + } + /* OK, now things should be normalized! */ + ret.sign = a.sign; + ret.exp = a.exp; + assert(!int128_gt(b.mant, a.mant)); + borrow = (b.round << 2) | (b.guard << 1) | b.sticky; + ret.mant = int128_sub(a.mant, b.mant, (borrow != 0)); + borrow = 0 - borrow; + ret.guard = (borrow >> 2) & 1; + ret.round = (borrow >> 1) & 1; + ret.sticky = (borrow >> 0) & 1; + if (negate) + ret.sign = !ret.sign; + //According to the IEEE standard, Zero result in a subtraction should always be positive + if ((ret.sign) && ((ret.mant.high == 0) && (ret.mant.low == 0) && ((ret.guard | ret.round | ret.sticky) == 0))) + ret.sign = !ret.sign; + xf_debug("ret: Sub ", ret); + return ret; +} + + +xf_t xf_add_kvx(xf_t a, xf_t b) +{ + xf_t ret; + xf_init(&ret); + xf_debug("-->Add/a: ", a); + xf_debug("-->Add/b: ", b); + if (a.sign != b.sign) { + b.sign = !b.sign; + return xf_sub_kvx(a, b, 0); + } + if (b.exp > a.exp) { + /* small + big == (big + small) */ + return xf_add_kvx(b, a); + } + if ((b.exp == a.exp) && int128_gt(b.mant, a.mant)) { + /* small + big == (big + small) */ + return xf_add_kvx(b, a); + } + xf_debug("OK? Add/a: ", a); + xf_debug("OK? Add/b: ", b); + while (a.exp > b.exp) { + /* Try to normalize exponents: shrink a exponent and grow mantissa */ + if (a.mant.high & (1ULL << 62)) { + /* Can't grow a any more */ + break; + } else { + a = xf_norm_left(a); + } + } + xf_debug("norm_l: Add/a: ", a); + xf_debug("norm_l: Add/b: ", b); + while (a.exp > b.exp) { + /* Try to normalize exponents: grow b exponent and shrink mantissa */ + /* Keep around shifted out bits... we might need those later */ + b = xf_norm_right(b, a.exp - b.exp); + } + xf_debug("norm_r: Add/a: ", a); + xf_debug("norm_r: Add/b: ", b); + /* OK, now things should be normalized! */ + if (int128_gt(b.mant, a.mant)) { + xf_debug("retry: Add/a: ", a); + xf_debug("retry: Add/b: ", b); + return xf_add_kvx(b, a); + }; + ret.sign = a.sign; + ret.exp = a.exp; + assert(!int128_gt(b.mant, a.mant)); + ret.mant = int128_add(a.mant, b.mant); + ret.guard = b.guard; + ret.round = b.round; + ret.sticky = b.sticky; + xf_debug("ret: Add ", ret); + return ret; +} + + +float internal_fma_kvx(float a_in, float b_in, float c_in, int scale); +float internal_fma_kvx(float a_in, float b_in, float c_in, int scale) +{ + sf_t a, b, c; + xf_t prod; + xf_t acc; + xf_t result; +#if 0 + df_t t; + fexcept_t flags_tmp; +#endif + xf_init(&prod); + xf_init(&acc); + xf_init(&result); + a.f = a_in; + b.f = b_in; + c.f = c_in; +// printf("internal_fma_kvxx: 0x%016x * 0x%016x + 0x%016x sc: %d\n", +// fUNFLOAT(a_in), fUNFLOAT(b_in), fUNFLOAT(c_in), scale); +// if (isinf(a.f) || isinf(b.f) || isinf(c.f)) +// return special_fmaf(a, b, c); +// if (isnan(a.f) || isnan(b.f) || isnan(c.f)) +// return special_fmaf(a, b, c); + if ((scale == 0) && (isz(a.f) || isz(b.f))) + return (a.f * b.f + c.f); + /* Is a*b exact? If so, we don't have to go the slow way */ + /* EJP: axe this for simplicity? */ +#if 0 + fegetexceptflag(&flags_tmp, FE_ALL_EXCEPT); + feclearexcept(FE_ALL_EXCEPT); + t.f = a.f * b.f; + if (0 && (scale == 0) && isfinite(t.f) + && fetestexcept(FE_ALL_EXCEPT) == 0) { + /* It's exactly correct, we can just do the add and return */ + fesetexceptflag(&flags_tmp, FE_ALL_EXCEPT); + asm volatile (""); + t.f = (t.f + c.f); + return t.f; + } + fesetexceptflag(&flags_tmp, FE_ALL_EXCEPT); +#endif + /* (a * 2**b) * (c * 2**d) == a*c * 2**(b+d) */ + prod.mant = int128_mult_6464(sf_getmant_kvx(a), sf_getmant_kvx(b)); + /* Note: extracting the mantissa into an int is multiplying by 2**23, so adjust here: */ + prod.exp = sf_getexp_kvx(a) + sf_getexp_kvx(b) - SF_BIAS - 23; + prod.sign = a.x.sign ^ b.x.sign; + if (isz(a.f) || isz(b.f)) prod.exp = -2*WAY_BIG_EXP; + xf_debug("prod: ", prod); + if ((scale > 0) /*&& (fpclassify(c.f) == FP_SUBNORMAL)*/) { + acc.mant = int128_mult_6464(0,0); + acc.exp = -WAY_BIG_EXP; + acc.sign = c.x.sign; + acc.sticky = 1; + xf_debug("special denorm acc: ",acc); + result = xf_add_kvx(prod,acc); + } else if (!isz(c.f)) { + acc.mant = int128_mult_6464(sf_getmant_kvx(c), 1); + acc.exp = sf_getexp_kvx(c); + acc.sign = c.x.sign; + xf_debug("acc: ", acc); + result = xf_add_kvx(prod, acc); + } else { + result = prod; + } + xf_debug("sum: ", result); +#ifdef DEBUG + printf("Scaling: %d\n", scale); +#endif + result.exp += scale; + xf_debug("post-scale: ", result); + return hf_round_sf_t(result).f; +} + +// result = (a*c) + (b*d) + acc +float internal_vdmpy_acc(float a_in, float b_in, float c_in, float d_in, float acc_in, int scale); +float internal_vdmpy_acc(float a_in, float b_in, float c_in, float d_in, float acc_in, int scale) +{ + sf_t a, b, c, d, accm; + xf_t prod1; //a*c + xf_t prod2; //b*d + xf_t acc; + xf_t result_temp; + xf_t result; + + xf_init(&prod1); + xf_init(&prod2); + xf_init(&acc); + xf_init(&result_temp); + xf_init(&result); + + a.f = a_in; + b.f = b_in; + c.f = c_in; + d.f = d_in; + accm.f = acc_in; + + /* (a * 2**b) * (c * 2**d) == a*c * 2**(b+d) */ + prod1.mant = int128_mult_6464(sf_getmant_kvx(a), sf_getmant_kvx(c)); + /* Note: extracting the mantissa into an int is multiplying by 2**23, so adjust here: */ + prod1.exp = sf_getexp_kvx(a) + sf_getexp_kvx(c) - SF_BIAS - 23; + prod1.sign = a.x.sign ^ c.x.sign; + + /* (a * 2**b) * (c * 2**d) == a*c * 2**(b+d) */ + prod2.mant = int128_mult_6464(sf_getmant_kvx(b), sf_getmant_kvx(d)); + /* Note: extracting the mantissa into an int is multiplying by 2**23, so adjust here: */ + prod2.exp = sf_getexp_kvx(b) + sf_getexp_kvx(d) - SF_BIAS - 23; + prod2.sign = b.x.sign ^ d.x.sign; + + + if (isz(a.f) || isz(c.f)) prod1.exp = -2*WAY_BIG_EXP; + if (isz(b.f) || isz(d.f)) prod2.exp = -2*WAY_BIG_EXP; + + xf_debug("prod1: ", prod1); + xf_debug("prod2: ", prod2); + + if ((scale > 0) /*&& (fpclassify(c.f) == FP_SUBNORMAL)*/) { + acc.mant = int128_mult_6464(0,0); + acc.exp = -WAY_BIG_EXP; + acc.sign = c.x.sign; + acc.sticky = 1; + xf_debug("special denorm acc: ",acc); + //result = xf_add_kvx(prod,acc); + } else /*if (!isz(accm.f)) */{ + acc.mant = int128_mult_6464(sf_getmant_kvx(accm), 1); + acc.exp = sf_getexp_kvx(accm); + acc.sign = accm.x.sign; + xf_debug("acc: ", acc); + //result = xf_add_kvx(prod, acc); + } /*else { + result = xf_add_kvx(prod1, prod2); + }*/ + + //Add the 3 numbers: prod1 prod2 acc + //result_temp = xf_add_kvx(prod1,prod2); + //result = xf_add_kvx(result_temp,acc); + result_temp = xf_add_kvx(prod1,prod2); + result = xf_add_kvx(result_temp,acc); + + xf_debug("sum: ", result); +#ifdef DEBUG + printf("Scaling: %d\n", scale); +#endif + result.exp += scale; + xf_debug("post-scale: ", result); + return xf_round_kvx_sf_t(result).f; +} + + +uint32_t fp_vdmpy_acc (uint32_t acc,uint16_t op1_u,uint16_t op1_l,uint16_t op2_u,uint16_t op2_l) +{ + union ui32_f32 u_op; + union ui32_f32 u_acc; + union ui32_f32 u_rslt; + + uint32_t op1_u_f32, op1_l_f32, op2_u_f32, op2_l_f32; + float f_op1_u, f_op1_l, f_op2_u, f_op2_l, f_acc; + float f_prod_l = 0, f_prod_u = 0, rslt; + uint32_t result; + +#ifdef DEBUG + printf("Debug : op1_u =0x%04x\n",op1_u); + printf("Debug : op1_l =0x%04x\n",op1_l); + printf("Debug : op2_u =0x%04x\n",op2_u); + printf("Debug : op2_l =0x%04x\n",op2_l); + printf("Debug : acc =0x%08x\n",acc); +#endif + + if(isNaNF16UI(op1_u) || isNaNF16UI(op1_l) || isNaNF16UI(op2_u) || isNaNF16UI(op2_l) || isNaNF32UI(acc)) + return FP32_DEF_NAN; + + op1_u_f32 = f16_to_f32(op1_u); + op1_l_f32 = f16_to_f32(op1_l); + op2_u_f32 = f16_to_f32(op2_u); + op2_l_f32 = f16_to_f32(op2_l); + +#ifdef DEBUG + printf("Debug : op1_u_f32 =0x%08x\n",op1_u_f32); + printf("Debug : op1_l_f32 =0x%08x\n",op1_l_f32); + printf("Debug : op2_u_f32 =0x%08x\n",op2_u_f32); + printf("Debug : op2_l_f32 =0x%08x\n",op2_l_f32); +#endif + + u_op.ui = op1_u_f32; + f_op1_u = u_op.f; + + u_op.ui = op1_l_f32; + f_op1_l = u_op.f; + + u_op.ui = op2_l_f32; + f_op2_l = u_op.f; + + u_op.ui = op2_u_f32; + f_op2_u = u_op.f; + + u_acc.ui = acc; + f_acc = u_acc.f; + +#ifdef DEBUG + printf("Debug_0 : f_op1_u = %f\n",f_op1_u); + printf("Debug_0 : f_op1_l = %f\n",f_op1_l); + printf("Debug_0 : f_op2_u = %f\n",f_op2_u); + printf("Debug_0 : f_op2_l = %f\n",f_op2_l); + printf("Debug_0 : f_acc = %f\n",f_acc); +#endif + + f_prod_l = (f_op1_l * f_op2_l); + f_prod_u = (f_op1_u * f_op2_u); + + if(isInfF16UI(op1_u) || isInfF16UI(op1_l) || isInfF16UI(op2_u) || isInfF16UI(op2_l) || isInfF32UI(acc)) + { + rslt = (f_prod_u + f_prod_l + f_acc); +#ifdef DEBUG + printf("Debug_inf : rslt = %f\n",rslt); +#endif + u_rslt.f = rslt; + result = u_rslt.ui; +#ifdef DEBUG + printf("Debug_inf : result =0x%08x\n",result); +#endif + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; +#ifdef DEBUG + printf("Debug_inf : result final =0x%08x\n",result); +#endif + return result; + } + + //If any of the below is a zero, we can use easy approach + if(isz(f_prod_l) || isz(f_prod_u) || isz(f_acc)) + { + rslt = (f_prod_u + f_prod_l + f_acc); +#ifdef DEBUG + printf("Debug_inf : rslt = %f\n",rslt); +#endif + u_rslt.f = rslt; + result = u_rslt.ui; +#ifdef DEBUG + printf("Debug_inf : result =0x%08x\n",result); +#endif + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; +#ifdef DEBUG + printf("Debug_inf : result final =0x%08x\n",result); +#endif + return result; + } + + +////---------------------------------------------------------------------------------------------------- +// f_prod_l = (f_op1_l * f_op2_l); +// f_prod_u = (f_op1_u * f_op2_u); +// +// printf("Debug_1 : f_prod_l = %f\n",f_prod_l); +// printf("Debug_1 : f_prod_u = %f\n",f_prod_u); +// +// rslt = (f_prod_u + f_prod_l + f_acc); +// printf("Debug_1 : rslt = %f\n",rslt); +// u_rslt.f = rslt; +// result = u_rslt.ui; +// printf("Debug_1 : result =0x%08x\n",result); +////---------------------------------------------------------------------------------------------------- + + rslt = internal_vdmpy_acc(f_op1_u, f_op1_l,f_op2_u,f_op2_l,f_acc,0); + u_rslt.f = rslt; + result = u_rslt.ui; +#ifdef DEBUG + printf("Debug_2 : rslt = %f\n",rslt); + printf("Debug_2 : result =0x%08x\n",result); +#endif + + result = isNaNF32UI(result) ? FP32_DEF_NAN : result; + +#ifdef DEBUG + printf("Debug : f_op1_u = %f\n",f_op1_u); + printf("Debug : f_op1_l = %f\n",f_op1_l); + printf("Debug : f_op2_u = %f\n",f_op2_u); + printf("Debug : f_op2_l = %f\n",f_op2_l); + printf("Debug : f_acc = %f\n",f_acc); + printf("Debug : f_prod_l = %f\n",f_prod_l); + printf("Debug : f_prod_u = %f\n",f_prod_u); + printf("Debug : rslt = %f\n",rslt); + printf("Debug : result =0x%08x\n",result); +#endif + + return result; +} + + +uint16_t fp_mult_hf_hf_acc (uint16_t op1, uint16_t op2, uint16_t acc) +{ + union ui32_f32 u_op1; + union ui32_f32 u_op2; + union ui32_f32 u_acc; + union ui32_f32 u_rslt; + + uint32_t op1_f32; + uint32_t op2_f32; + uint32_t acc_f32; + + float a,b,facc,rslt; + uint32_t result_f32; + uint16_t result; + +#ifdef DEBUG + printf("Debug : op1 =0x%04x\n",op1); + printf("Debug : op2 =0x%04x\n",op2); + printf("Debug : acc =0x%04x\n",acc); +#endif + + if(isNaNF16UI(op1) || isNaNF16UI(op2) || isNaNF16UI(acc)) + return FP16_DEF_NAN; + + op1_f32 = f16_to_f32(op1); + op2_f32 = f16_to_f32(op2); + acc_f32 = f16_to_f32(acc); + +#ifdef DEBUG + printf("Debug : op1_f32 = 0x%08x\n",op1_f32); + printf("Debug : op2_f32 = 0x%08x\n",op2_f32); + printf("Debug : acc_f32 = 0x%08x\n",acc_f32); +#endif + + u_op1.ui = op1_f32; + u_op2.ui = op2_f32; + u_acc.ui = acc_f32; + a = u_op1.f; + b = u_op2.f; + facc = u_acc.f; + +#ifdef DEBUG + printf("Debug_1 : a = %f\n",a); + printf("Debug_1 : b = %f\n",b); + printf("Debug_1 : facc = %f\n",facc); +#endif + + if(isInfF16UI(op1) || isInfF16UI(op2) || isInfF16UI(acc)) + { + rslt = (a * b) + facc; +#ifdef DEBUG + printf("Debug_inf : rslt = %f\n",rslt); +#endif + u_rslt.f = rslt; + result_f32 = u_rslt.ui; + result = f32_to_f16(result_f32); +#ifdef DEBUG + printf("Debug_inf : result_f32 =0x%08x\n",result_f32); + printf("Debug_inf : result =0x%04x\n",result); +#endif + result = isNaNF16UI(result) ? FP16_DEF_NAN : result; +#ifdef DEBUG + printf("Debug_inf : result final =0x%04x\n",result); +#endif + return result; + } + +// //---------------------------------------------------------------------------------------------------- +// rslt = (a * b) + facc; +// u_rslt.f = rslt; +// result_f32 = u_rslt.ui; +// printf("Debug_3 : result_f32 =0x%08x\n",result_f32); +// result = f32_to_f16(result_f32); +// printf("Debug_3 : result =0x%04x\n",result); +// //---------------------------------------------------------------------------------------------------- + + //rslt = fma(a,b,facc); + rslt = internal_fma_kvx(a, b, facc, 0); + u_rslt.f = rslt; + result_f32 = u_rslt.ui; +#ifdef DEBUG + printf("Debug_2 : rslt = %f\n",rslt); + printf("Debug_2 : result_f32 =0x%08x\n",result_f32); +#endif + + result = f32_to_f16(result_f32); + +#ifdef DEBUG + printf("Debug_2 : result =0x%04x\n",result); +#endif + + result = isNaNF16UI(result) ? FP16_DEF_NAN : result; + +#ifdef DEBUG + printf("Debug_2 : result final =0x%04x\n",result); +#endif + + return result; +} + diff --git a/target/hexagon/mmvec/macros_auto.h b/target/hexagon/mmvec/macros_auto.h new file mode 100644 index 000000000000..479cb225c70c --- /dev/null +++ b/target/hexagon/mmvec/macros_auto.h @@ -0,0 +1,221 @@ +/* + * Copyright(c) 2019-2021 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * 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 . + */ + +#ifndef HEXAGON_MMVEC_MACROS_AUTO_H +#define HEXAGON_MMVEC_MACROS_AUTO_H + + +#include "mmvec/macros.h" + +#include "q6v_defines.h" +#pragma GCC diagnostic ignored "-Wtype-limits" +#define fDUMPQ(STR,REG) do { printf(STR ":" #REG ": 0x%016llx\n",REG.ud[0]); } while (0) +#define fRT8NOTE() +#define fEXPERIMENTAL() +#define fBFLOAT() +#define fCVI_VX_NO_TMP_LD() +#define fNOTQ(VAL) ({mmqreg_t _ret ={0}; int _i_; for (_i_ = 0; _i_ < fVECSIZE()/64; _i_++) _ret.ud[_i_] = ~VAL.ud[_i_]; _ret;}) +#define fGETQBITS(REG,WIDTH,MASK,BITNO) ((MASK) & (REG.w[(BITNO)>>5] >> ((BITNO) & 0x1f))) +#define fGETQBIT(REG,BITNO) fGETQBITS(REG,1,1,BITNO) +#define fGENMASKW(QREG,IDX) (((fGETQBIT(QREG,(IDX*4+0)) ? 0xFF : 0x0) << 0) |((fGETQBIT(QREG,(IDX*4+1)) ? 0xFF : 0x0) << 8) |((fGETQBIT(QREG,(IDX*4+2)) ? 0xFF : 0x0) << 16) |((fGETQBIT(QREG,(IDX*4+3)) ? 0xFF : 0x0) << 24)) +#define fGET10BIT(COE,VAL,POS) { COE = (((((fGETUBYTE(3,VAL) >> (2 * POS)) & 3) << 8) | fGETUBYTE(POS,VAL)) << 6); COE >>= 6; } +#define fVMAX(X,Y) (X>Y) ? X : Y +#define fREAD_VEC(DST,IDX) (DST = READ_VREG(fMODCIRCU((IDX),5))) +#define fREAD_ZVEC(DST,IDX) (DST = READ_ZREG(fMODCIRCU((IDX),5))) +#define fREAD_ZVEC_WORD(DST,IDX) { mmvector_t ZReg = READ_ZREG(0); DST = ZReg.uw[IDX]; } +#define fREAD_ZVEC_ALL(DST,N,NZ) { int __idx = 0; for (__idx = 0; __idx < NZ/N; __idx++) { memcpy(&DST[N*__idx], &THREAD2STRUCT->ZRegs[__idx], N); } } +#define fZREGB(Z,IDX) ((size1s_t)Z[IDX]) +#define fZREGUB(Z,IDX) ((size1u_t)Z[IDX]) +#define fZREGH(Z,IDX) ((size2s_t)Z[IDX]) +#define fZREGUB(Z,IDX) ((size1u_t)Z[IDX]) +#define fGETNIBBLE(IDX,SRC) ( fSXTN(4,8,(SRC >> (4*IDX)) & 0xF) ) +#define fGETCRUMB(IDX,SRC) ( fSXTN(2,8,(SRC >> (2*IDX)) & 0x3) ) +#define fGETCRUMB_SYMMETRIC(IDX,SRC) ( (fGETCRUMB(IDX,SRC)>=0 ? (2-fGETCRUMB(IDX,SRC)) : fGETCRUMB(IDX,SRC) ) ) +#define fWRITE_VEC(IDX,VAR) (WRITE_VREG(fMODCIRCU((IDX),5),VAR)) +#define fGENMASKH(QREG,IDX) (((fGETQBIT(QREG,(IDX*2+0)) ? 0xFF : 0x0) << 0) |((fGETQBIT(QREG,(IDX*2+1)) ? 0xFF : 0x0) << 8)) +#define fGETMASKW(VREG,QREG,IDX) (VREG.w[IDX] & fGENMASKW((QREG),IDX)) +#define fGETMASKH(VREG,QREG,IDX) (VREG.h[IDX] & fGENMASKH((QREG),IDX)) +#define fCONDMASK8(QREG,IDX,YESVAL,NOVAL) (fGETQBIT(QREG,IDX) ? (YESVAL) : (NOVAL)) +#define fCONDMASK16(QREG,IDX,YESVAL,NOVAL) ((fGENMASKH(QREG,IDX) & (YESVAL)) | (fGENMASKH(fNOTQ(QREG),IDX) & (NOVAL))) +#define fCONDMASK32(QREG,IDX,YESVAL,NOVAL) ((fGENMASKW(QREG,IDX) & (YESVAL)) | (fGENMASKW(fNOTQ(QREG),IDX) & (NOVAL))) +#define fSETQBITS(REG,WIDTH,MASK,BITNO,VAL) do { size4u_t __TMP = (VAL); REG.w[(BITNO)>>5] &= ~((MASK) << ((BITNO) & 0x1f)); REG.w[(BITNO)>>5] |= (((__TMP) & (MASK)) << ((BITNO) & 0x1f)); } while (0) +#define fSETQBIT(REG,BITNO,VAL) fSETQBITS(REG,1,1,BITNO,VAL) +#define fVBYTES() (fVECSIZE()) +#define fVHALVES() (fVECSIZE()/2) +#define fVWORDS() (fVECSIZE()/4) +#define fVDWORDS() (fVECSIZE()/8) +#define fVALIGN(ADDR, LOG2_ALIGNMENT) ( ADDR = ADDR & ~(LOG2_ALIGNMENT-1)) +#define fVLASTBYTE(ADDR, LOG2_ALIGNMENT) ( ADDR = ADDR | (LOG2_ALIGNMENT-1)) +#define fVELEM(WIDTH) ((fVECSIZE()*8)/WIDTH) +#define fVECLOGSIZE() (MAX_VEC_SIZE_LOGBYTES) +#define fVBUF_IDX(EA) (((EA) >> fVECLOGSIZE()) & 0xFF) +#define fREAD_VBUF(IDX,WIDX) READ_VBUF(IDX,WIDX) +#define fLOG_VBUF(IDX,VAL,WIDX) LOG_VBUF(IDX,VAL,WIDX) +#define fVECSIZE() (1<VRegs_updated & (((VRegMask)1)<future_VRegs[VNUM] : mmvec_zero_vector()) +#define fV_AL_CHECK(EA,MASK) if ((EA) & (MASK)) { warn("aligning misaligned vector. PC=%08x EA=%08x",thread->Regs[REG_PC],(EA)); } +#define fSCATTER_INIT( REGION_START, LENGTH, ELEMENT_SIZE) { mem_vector_scatter_init(thread, insn, REGION_START, LENGTH, ELEMENT_SIZE); if (EXCEPTION_DETECTED) return; } +#define fGATHER_INIT( REGION_START, LENGTH, ELEMENT_SIZE) { mem_vector_gather_init(thread, insn, REGION_START, LENGTH, ELEMENT_SIZE); if (EXCEPTION_DETECTED) return; } +#ifdef CONFIG_USER_ONLY +#define fSCATTER_FINISH(OP) +#define fGATHER_FINISH() +#else +#define fSCATTER_FINISH(OP) { if (EXCEPTION_DETECTED) return; mem_vector_scatter_finish(thread, insn, OP); } +#define fGATHER_FINISH() { if (EXCEPTION_DETECTED) return; mem_vector_gather_finish(thread, insn); } +#endif +#define CHECK_VTCM_PAGE(FLAG, BASE, LENGTH, OFFSET, ALIGNMENT) { int slot = insn->slot; paddr_t pa = thread->mem_access[slot].paddr+OFFSET; pa = pa & ~(ALIGNMENT-1); FLAG = (pa < (thread->mem_access[slot].paddr+LENGTH)); } +#define COUNT_OUT_OF_BOUNDS(FLAG, SIZE) { if (!FLAG) { THREAD2STRUCT->vtcm_log.oob_access += SIZE; warn("Scatter/Gather out of bounds of region"); } } +#define fLOG_SCATTER_OP(SIZE) { thread->vtcm_log.op = 1; thread->vtcm_log.op_size = SIZE; } +#define fVLOG_VTCM_GATHER_WORD(EA,OFFSET,IDX, LEN) { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 4, IDX, 1); } +#define fVLOG_VTCM_GATHER_HALFWORD(EA,OFFSET,IDX, LEN) { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, IDX, 1); } +#define fVLOG_VTCM_GATHER_HALFWORD_DV(EA,OFFSET,IDX,IDX2,IDX_H, LEN) { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), 1); } +#define fVLOG_VTCM_GATHER_WORDQ(EA,OFFSET,IDX, Q, LEN) { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 4, IDX, fGETQBIT(QsV,4*IDX+i0)); } +#define fVLOG_VTCM_GATHER_HALFWORDQ(EA,OFFSET,IDX, Q, LEN) { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, IDX, fGETQBIT(QsV,2*IDX+i0)); } +#define fVLOG_VTCM_GATHER_HALFWORDQ_DV(EA,OFFSET,IDX,IDX2,IDX_H, Q, LEN) { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), fGETQBIT(QsV,2*IDX+i0)); } +#define DEBUG_LOG_ADDR(OFFSET) { if (thread->processor_ptr->arch_proc_options->mmvec_network_addr_log2) { int slot = insn->slot; paddr_t pa = thread->mem_access[slot].paddr+OFFSET; } } +//#define SCATTER_OP_WRITE_TO_MEM(TYPE) { for (int i = 0; i < mmvecx->vtcm_log.size; i+=sizeof(TYPE)) { if ( mmvecx->vtcm_log.mask.ub[i] != 0) { TYPE dst = 0; TYPE inc = 0; for(int j = 0; j < sizeof(TYPE); j++) { dst |= (sim_mem_read1(thread->system_ptr, thread->threadId, mmvecx->vtcm_log.pa[i+j]) << (8*j)); inc |= mmvecx->vtcm_log.data.ub[j+i] << (8*j); mmvecx->vtcm_log.mask.ub[j+i] = 0; mmvecx->vtcm_log.data.ub[j+i] = 0; mmvecx->vtcm_log.offsets.ub[j+i] = 0; } dst += inc; for(int j = 0; j < sizeof(TYPE); j++) { sim_mem_write1(thread->system_ptr,thread->threadId, mmvecx->vtcm_log.pa[i+j], (dst >> (8*j))& 0xFF ); } } } } +#define fVLOG_VTCM_HALFWORD(EA,OFFSET,IN,IDX, LEN) { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, IDX, 1, IN); } +#define fVLOG_VTCM_WORD(EA,OFFSET,IN,IDX,LEN) { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 4, IDX, 1, IN); } +#define fVLOG_VTCM_HALFWORDQ(EA,OFFSET,IN,IDX,Q,LEN) { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, IDX, fGETQBIT(QsV,2*IDX+i0), IN); } +#define fVLOG_VTCM_WORDQ(EA,OFFSET,IN,IDX,Q,LEN) { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 4, IDX, fGETQBIT(QsV,4*IDX+i0), IN); } +#define fVLOG_VTCM_HALFWORD_DV(EA,OFFSET,IN,IDX,IDX2,IDX_H, LEN) { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), 1, IN); } +#define fVLOG_VTCM_HALFWORDQ_DV(EA,OFFSET,IN,IDX,Q,IDX2,IDX_H, LEN) { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), fGETQBIT(QsV,2*IDX+i0), IN); } +#define fSTORERELEASE(EA,TYPE) { fV_AL_CHECK(EA,fVECSIZE()-1); mem_store_release(thread, insn, fVECSIZE(), EA&~(fVECSIZE()-1), EA, TYPE, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#define fVFETCH_AL(EA) { fV_AL_CHECK(EA,fVECSIZE()-1); mem_fetch_vector(thread, insn, EA&~(fVECSIZE()-1), slot, fVECSIZE()); } +#define fLOADMMV_AL(EA, ALIGNMENT, LEN, DST) { fV_AL_CHECK(EA,ALIGNMENT-1); /*thread->last_pkt->double_access_vec = 0;*/ mem_load_vector_oddva(thread, 0, EA&~(ALIGNMENT-1), EA, slot, LEN, &DST.ub[0], LEN, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#ifdef QEMU_GENERATE +#define fLOADMMV(EA, DST) gen_vreg_load(ctx, DST##_off, EA, true) +#else +#define fLOADMMV(EA, DST) fLOADMMV_AL(EA,fVECSIZE(),fVECSIZE(),DST) +#endif +#define fLOADMMZ(EA,DST) { mmvector_t load_vec; fV_AL_CHECK(EA,fVECSIZE()-1); mem_load_vector_oddva(thread, 0, EA&~(fVECSIZE()-1), EA, slot, fVECSIZE(), &load_vec.ub[0], fVECSIZE(), fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); int idx = (EA & 0x80)>0; DST.v[idx] = load_vec; } +#define fLOADZ_LOAD(EA,EAU,WIDTH,DST) {/* thread->last_pkt->ext_slot_cancelled = 0; thread->last_pkt->double_access_vec = 0;*/ int etm_size = ((EA % width) ==0) ? fVECSIZE() : 0; if (thread->processor_ptr->options->testgen_mode) etm_size = ((EA % width) ==0) ? WIDTH : 0; mem_load_vector_oddva(thread, 0, EA, EAU, slot, WIDTH, &DST.ub[0], etm_size, fUSE_LOOKUP_ADDRESS()); } +#define fELSE_CANCELZ() else { /*if (thread->last_pkt) { thread->mem_access[slot].dropped_z = 1; thread->last_pkt->ext_slot_cancelled |= (1<processor_ptr)); } +#define fLOADMMVQ(EA,DST,QVAL) do { int __i; fLOADMMV_AL(EA,fVECSIZE(),fVECSIZE(),DST); fVFOREACH(8,__i) if (!fGETQBIT(QVAL,__i)) DST.b[__i] = 0; } while (0) +#define fLOADMMVNQ(EA,DST,QVAL) do { int __i; fLOADMMV_AL(EA,fVECSIZE(),fVECSIZE(),DST); fVFOREACH(8,__i) if (fGETQBIT(QVAL,__i)) DST.b[__i] = 0; } while (0) +#define fLOADMMVU_AL(EA, ALIGNMENT, LEN, DST) { size4u_t size2 = (EA)&(ALIGNMENT-1); size4u_t size1 = LEN-size2; /*thread->last_pkt->double_access_vec = 1;*/ mem_load_vector_oddva(thread, 0, EA+size1, EA+fVECSIZE(), 1, size2, &DST.ub[size1], size2, fUSE_LOOKUP_ADDRESS()); mem_load_vector_oddva(thread, 0, EA, EA, 0, size1, &DST.ub[0], size1, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#ifdef QEMU_GENERATE +#define fLOADMMVU(EA, DST) gen_vreg_load(ctx, DST##_off, EA, false) +#else +#define fLOADMMVU(EA, DST) { /*thread->last_pkt->pkt_has_vtcm_access = 0; thread->last_pkt->pkt_access_count = 0;*/ if ( (EA & (fVECSIZE()-1)) == 0) { /*thread->last_pkt->pkt_has_vmemu_access = 0; thread->last_pkt->double_access = 0;*/ fLOADMMV_AL(EA,fVECSIZE(),fVECSIZE(),DST); } else { /*thread->last_pkt->pkt_has_vmemu_access = 1; thread->last_pkt->double_access = 1;*/ fLOADMMVU_AL(EA,fVECSIZE(),fVECSIZE(),DST); } } +#endif +#define fSTOREMMV_AL(EA, ALIGNMENT, LEN, SRC) { fV_AL_CHECK(EA,ALIGNMENT-1); mem_store_vector_oddva(thread, 0, EA&~(ALIGNMENT-1), EA, slot, LEN, &SRC.ub[0], 0, 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#ifdef QEMU_GENERATE +#define fSTOREMMV(EA, SRC) gen_vreg_store(ctx, EA, SRC##_off, insn->slot, true) +#else +#define fSTOREMMV(EA, SRC) fSTOREMMV_AL(EA,fVECSIZE(),fVECSIZE(),SRC) +#endif +#define fSTOREMMVQ_AL(EA, ALIGNMENT, LEN, SRC, MASK) do { mmvector_t maskvec; int i; for (i = 0; i < fVECSIZE(); i++) maskvec.ub[i] = fGETQBIT(MASK,i); mem_store_vector_oddva(thread, 0, EA&~(ALIGNMENT-1), EA, slot, LEN, &SRC.ub[0], &maskvec.ub[0], 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } while (0) +#ifdef QEMU_GENERATE +#define fSTOREMMVQ(EA, SRC, MASK) \ + gen_vreg_masked_store(ctx, EA, SRC##_off, MASK##_off, insn->slot, false) +#else +#define fSTOREMMVQ(EA, SRC, MASK) fSTOREMMVQ_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK) +#endif +#define fSTOREMMVNQ_AL(EA, ALIGNMENT, LEN, SRC, MASK) { mmvector_t maskvec; int i; for (i = 0; i < fVECSIZE(); i++) maskvec.ub[i] = fGETQBIT(MASK,i); fV_AL_CHECK(EA,ALIGNMENT-1); mem_store_vector_oddva(thread, 0, EA&~(ALIGNMENT-1), EA, slot, LEN, &SRC.ub[0], &maskvec.ub[0], 1, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#ifdef QEMU_GENERATE +#define fSTOREMMVNQ(EA, SRC, MASK) \ + gen_vreg_masked_store(ctx, EA, SRC##_off, MASK##_off, insn->slot, true) +#else +#define fSTOREMMVNQ(EA, SRC, MASK) fSTOREMMVNQ_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK) +#endif +#define fSTOREMMVU_AL(EA, ALIGNMENT, LEN, SRC) { size4u_t size1 = ALIGNMENT-((EA)&(ALIGNMENT-1)); size4u_t size2; if (size1>LEN) size1 = LEN; size2 = LEN-size1; mem_store_vector_oddva(thread, 0, EA+size1, EA+fVECSIZE(), 1, size2, &SRC.ub[size1], 0, 0, fUSE_LOOKUP_ADDRESS()); mem_store_vector_oddva(thread, 0, EA, EA, 0, size1, &SRC.ub[0], 0, 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#ifdef QEMU_GENERATE +#define fSTOREMMVU(EA, SRC) \ + gen_vreg_store(ctx, EA, SRC##_off, insn->slot, false) +#else +#define fSTOREMMVU(EA, SRC) { /*thread->last_pkt->pkt_has_vtcm_access = 0; thread->last_pkt->pkt_access_count = 0;*/ if ( (EA & (fVECSIZE()-1)) == 0) { /*thread->last_pkt->double_access = 0;*/ fSTOREMMV_AL(EA,fVECSIZE(),fVECSIZE(),SRC); } else { /*thread->last_pkt->double_access = 1; thread->last_pkt->pkt_has_vmemu_access = 1;*/ fSTOREMMVU_AL(EA,fVECSIZE(),fVECSIZE(),SRC); } } +#endif +#define fSTOREMMVQU_AL(EA, ALIGNMENT, LEN, SRC, MASK) { size4u_t size1 = ALIGNMENT-((EA)&(ALIGNMENT-1)); size4u_t size2; mmvector_t maskvec; int i; for (i = 0; i < fVECSIZE(); i++) maskvec.ub[i] = fGETQBIT(MASK,i); if (size1>LEN) size1 = LEN; size2 = LEN-size1; mem_store_vector_oddva(thread, 0, EA+size1, EA+fVECSIZE(), 1, size2, &SRC.ub[size1], &maskvec.ub[size1], 0, fUSE_LOOKUP_ADDRESS()); mem_store_vector_oddva(thread, 0, EA, 0, size1, &SRC.ub[0], &maskvec.ub[0], 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#define fSTOREMMVQU(EA, SRC, MASK) { /*thread->last_pkt->pkt_has_vtcm_access = 0; thread->last_pkt->pkt_access_count = 0;*/ if ( (EA & (fVECSIZE()-1)) == 0) { /*thread->last_pkt->double_access = 0;*/ fSTOREMMVQ_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK); } else { /*thread->last_pkt->double_access = 1; thread->last_pkt->pkt_has_vmemu_access = 1;*/ fSTOREMMVQU_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK); } } +#define fSTOREMMVNQU_AL(EA, ALIGNMENT, LEN, SRC, MASK) { size4u_t size1 = ALIGNMENT-((EA)&(ALIGNMENT-1)); size4u_t size2; mmvector_t maskvec; int i; for (i = 0; i < fVECSIZE(); i++) maskvec.ub[i] = fGETQBIT(MASK,i); if (size1>LEN) size1 = LEN; size2 = LEN-size1; mem_store_vector_oddva(thread, 0, EA+size1, EA+fVECSIZE(), 1, size2, &SRC.ub[size1], &maskvec.ub[size1], 1, fUSE_LOOKUP_ADDRESS()); mem_store_vector_oddva(thread, 0, EA, EA, 0, size1, &SRC.ub[0], &maskvec.ub[0], 1, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } +#define fSTOREMMVNQU(EA, SRC, MASK) { /*thread->last_pkt->pkt_has_vtcm_access = 0; thread->last_pkt->pkt_access_count = 0;*/ if ( (EA & (fVECSIZE()-1)) == 0) { /*thread->last_pkt->double_access = 0;*/ fSTOREMMVNQ_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK); } else { /*thread->last_pkt->double_access = 1; thread->last_pkt->pkt_has_vmemu_access = 1;*/ fSTOREMMVNQU_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK); } } +#define fVFOREACH(WIDTH, VAR) for (VAR = 0; VAR < fVELEM(WIDTH); VAR++) +#define fVARRAY_ELEMENT_ACCESS(ARRAY, TYPE, INDEX) ARRAY.v[(INDEX) / (fVECSIZE()/(sizeof(ARRAY.TYPE[0])))].TYPE[(INDEX) % (fVECSIZE()/(sizeof(ARRAY.TYPE[0])))] +#define fVNEWCANCEL(REGNUM) do { THREAD2STRUCT->VRegs_select &= ~(1<<(REGNUM)); } while (0) +#define fTMPVDATA() mmvec_vtmp_data(thread) +#define fVSATDW(U,V) fVSATW( ( ( ((long long)U)<<32 ) | fZXTN(32,64,V) ) ) +#define fVASL_SATHI(U,V) fVSATW(((U)<<1) | ((V)>>31)) +#define fVUADDSAT(WIDTH,U,V) fVSATUN( WIDTH, fZXTN(WIDTH, 2*WIDTH, U) + fZXTN(WIDTH, 2*WIDTH, V)) +#define fVSADDSAT(WIDTH,U,V) ({size8s_t tmp5 = fSXTN(WIDTH, 2*WIDTH, U); size8s_t tmp6 = fSXTN(WIDTH, 2*WIDTH, V); size8s_t tmp7 = tmp5 + tmp6; fVSATN( WIDTH, tmp7); }) +#define fVUSUBSAT(WIDTH,U,V) fVSATUN( WIDTH, fZXTN(WIDTH, 2*WIDTH, U) - fZXTN(WIDTH, 2*WIDTH, V)) +#define fVSSUBSAT(WIDTH,U,V) fVSATN( WIDTH, fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V)) +#define fVAVGU(WIDTH,U,V) ((fZXTN(WIDTH, 2*WIDTH, U) + fZXTN(WIDTH, 2*WIDTH, V))>>1) +#define fVAVGURND(WIDTH,U,V) ((fZXTN(WIDTH, 2*WIDTH, U) + fZXTN(WIDTH, 2*WIDTH, V)+1)>>1) +#define fVNAVGU(WIDTH,U,V) ((fZXTN(WIDTH, 2*WIDTH, U) - fZXTN(WIDTH, 2*WIDTH, V))>>1) +#define fVNAVGURNDSAT(WIDTH,U,V) fVSATUN(WIDTH,((fZXTN(WIDTH, 2*WIDTH, U) - fZXTN(WIDTH, 2*WIDTH, V)+1)>>1)) +#define fVAVGS(WIDTH,U,V) ((fSXTN(WIDTH, 2*WIDTH, U) + fSXTN(WIDTH, 2*WIDTH, V))>>1) +#define fVAVGSRND(WIDTH,U,V) ((fSXTN(WIDTH, 2*WIDTH, U) + fSXTN(WIDTH, 2*WIDTH, V)+1)>>1) +#define fVNAVGS(WIDTH,U,V) ((fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V))>>1) +#define fVNAVGSRND(WIDTH,U,V) ((fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V)+1)>>1) +#define fVNAVGSRNDSAT(WIDTH,U,V) fVSATN(WIDTH,((fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V)+1)>>1)) +#define fVNOROUND(VAL,SHAMT) VAL +#define fVNOSAT(VAL) VAL +#define fVROUND(VAL,SHAMT) ((VAL) + (((SHAMT)>0)?(1LL<<((SHAMT)-1)):0)) +#define fCARRY_FROM_ADD32(A,B,C) (((fZXTN(32,64,A)+fZXTN(32,64,B)+C) >> 32) & 1) +#define fUARCH_NOTE_PUMP_4X() +#define fUARCH_NOTE_PUMP_2X() +#define UNLIKELY(X) __builtin_expect((X), 0) +#define fVDOCHKPAGECROSS(BASE,SUM) if (UNLIKELY(thread->timing_on)) { thread->mem_access[slot].check_page_crosses = 1; thread->mem_access[slot].page_cross_base = BASE; thread->mem_access[slot].page_cross_sum = SUM; } +#define fPARSEQF32(A) parse_qf32(A) +#define fRNDSATQF32(A,B,C) rnd_sat_qf32(A,B,C) +#define fPARSEQF16(A) parse_qf16(A) +#define fRNDSATQF16(A,B,C) rnd_sat_qf16(A,B,C) +#define fPARSESF(A) parse_sf(A) +#define fRNDSATSF(A,B) rnd_sat_sf(A,B) +#define fPARSEHF(A) parse_hf(A) +#define fRNDSATHF(A,B) rnd_sat_hf(A,B) +#define fRNDSATW(A,B) rnd_sat_w(A,B) +#define fRNDSATUW(A,B) rnd_sat_uw(A,B) +#define fRNDSATH(A,B) rnd_sat_h(A,B) +#define fRNDSATUH(A,B) rnd_sat_uh(A,B) +#define fRNDSATB(A,B) rnd_sat_b(A,B) +#define fRNDSATUB(A,B) rnd_sat_ub(A,B) +#define fNEGQF32(A) negate32(A) +#define fNEGQF16(A) negate16(A) +#define fNEGSF(A) negate_sf(A) +#define fNEGHF(A) negate_hf(A) +#define fCMPGT_QF32(A,B) cmpgt_qf32(A,B) +#define fCMPGT_QF16(A,B) cmpgt_qf16(A,B) +#define fCMPGT_SF(A,B) cmpgt_sf(A,B) +#define fCMPGT_HF(A,B) cmpgt_hf(A,B) +#define fCMPGT_BF(A,B) cmpgt_sf(((int)A) << 16,((int)B) << 16) +#define fCMPGT_QF32_SF(A,B) cmpgt_qf32_sf(A,B) +#define fCMPGT_QF16_HF(A,B) cmpgt_qf16_hf(A,B) +#define fMAX_QF32(X,Y) max_qf32(X,Y) +#define fMIN_QF32(X,Y) min_qf32(X,Y) +#define fMAX_QF32_SF(X,Y) max_qf32_sf(X,Y) +#define fMIN_QF32_SF(X,Y) min_qf32_sf(X,Y) +#define fMAX_QF16(X,Y) max_qf16(X,Y) +#define fMIN_QF16(X,Y) min_qf16(X,Y) +#define fMAX_QF16_HF(X,Y) max_qf16_hf(X,Y) +#define fMIN_QF16_HF(X,Y) min_qf16_hf(X,Y) +#define fMAX_SF(X,Y) max_sf(X,Y) +#define fMIN_SF(X,Y) min_sf(X,Y) +#define fMAX_HF(X,Y) max_hf(X,Y) +#define fMIN_HF(X,Y) min_hf(X,Y) + +#define fSTOREDOUBLEMMV(EA, SRC) fSTOREMMV_AL(EA,fVECSIZE(),2*fVECSIZE(),SRC) +#endif diff --git a/target/hexagon/mmvec/mmvec.h b/target/hexagon/mmvec/mmvec.h index 52d470709c02..906bf16d8258 100644 --- a/target/hexagon/mmvec/mmvec.h +++ b/target/hexagon/mmvec/mmvec.h @@ -38,6 +38,11 @@ typedef union { int16_t h[MAX_VEC_SIZE_BYTES / 2]; uint8_t ub[MAX_VEC_SIZE_BYTES / 1]; int8_t b[MAX_VEC_SIZE_BYTES / 1]; + int32_t qf32[MAX_VEC_SIZE_BYTES / 4]; + int16_t qf16[MAX_VEC_SIZE_BYTES / 2]; + int32_t sf[MAX_VEC_SIZE_BYTES / 4]; + int16_t hf[MAX_VEC_SIZE_BYTES / 2]; + int16_t bf[MAX_VEC_SIZE_BYTES / 2]; } MMVector; typedef union { diff --git a/target/hexagon/mmvec/mmvec_qfloat.c b/target/hexagon/mmvec/mmvec_qfloat.c new file mode 100644 index 000000000000..060ac4b14d8f --- /dev/null +++ b/target/hexagon/mmvec/mmvec_qfloat.c @@ -0,0 +1,2563 @@ +/* + * Copyright(c) 2019-2020 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * 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 . + */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#if !defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#include "qemu/osdep.h" +#include "mmvec_qfloat.h" +#include + +#define UNUSED(var) do { (void)var; } while (0) + +//Take one's complement of the mantissa for QF32 +size4s_t negate32(size4s_t in) +{ + size4s_t out; + out = in>>8; + out = ~out; + out = (out<<8) | (in & 0xFF); + return out; +} +//Take one's complement of the mantissa for QF16 +size2s_t negate16(size2s_t in) +{ + size2s_t out; + out = in>>5; + out = ~out; + out = (out<<5) | (in & 0x1F); + return out; +} +//Change sign for SF +size4s_t negate_sf(size4s_t in) +{ + size4s_t out; + int sign; + sign = (in>>31) & 1; + sign = ~sign; + out = (sign<<31) | (in & 0x7FFFFFFF); + return out; +} +//Change sign for SF +size2s_t negate_hf(size2s_t in) +{ + size2s_t out; + int sign; + sign = (in>>15) & 1; + sign = ~sign; + out = (sign<<15) | (in & 0x7FFF); + return out; +} +unfloat parse_qf16(size2s_t in) +{ + unfloat out; + + out.sign = (in>>15) & 0x1; + + out.exp = (size1s_t)(0x00 | (in & 0x1F)); + out.exp = out.exp - BIAS_QF16; + + /*implied LSB=1*/ + size2s_t signif; + /*take signif and sign extend, add LSB=1*/ + signif= ((size4s_t)in >> 4) | 1; + + out.sig = (double)signif * epsilon_hf; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF16_parse]in=%x, exp=%d, sig=%10.20f\n", in,out.exp,out.sig); + printf("[ARCH_QF16_parse]exp_d=%d, sig_d=%10.20f\n", ilogb(out.sig),ldexp(out.sig, -ilogb(out.sig))); +#endif + return out; +} +//Take signed int and generate sign, exp and ***signed sig +unfloat parse_qf32(size4s_t in) +{ + unfloat out; + + out.sign = (in>>31) & 0x1; + + out.exp = (size2s_t)(0x0000 | (in & 0xFF)); + out.exp = out.exp - BIAS_QF32; + + /*implied LSB=1*/ + size4s_t signif; + /*take signif and sign extend, add LSB=1*/ + signif= ((size8s_t)in >> 7) | 1; + + out.sig = (double)signif * epsilon; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_parse]in=%x, exp=%d, sig=%10.20f\n", in,out.exp,out.sig); + printf("[ARCH_QF32_parse]exp_d=%d, sig_d=%10.20f\n", ilogb(out.sig),ldexp(out.sig, -ilogb(out.sig))); +#endif + return out; +} + +unfloat parse_hf(size2s_t in) +{ + unfloat out; + + out.sign = (in>>15) & 0x1; + out.exp = (size1s_t)( (0x00 | (in>>10)) & 0x1F); + + size2u_t sig; + //take signif and sign extend + sig = (size2u_t)(in & 0x3FF); + + /*implied MSB=1*/ + if(out.exp>0) + sig = (1<<10) | sig; + + out.exp = out.exp - BIAS_HF; + if(out.exp>31) & 0x1; + out.exp = (size2s_t)( (0x0000 | (in>>23)) & 0xFF); + + size4u_t sig; + //take signif and sign extend + sig = (size4u_t)(in & 0x7FFFFF); + + /*implied MSB=1*/ + if(out.exp>0) + sig = (1<<23) | sig; + + out.exp = out.exp - BIAS_SF; + + if(out.exp=0.0)? 0:1; + +#ifndef DEBUG_MMVEC_QF + UNUSED(R_low); +#endif + + int prod_ovf=0; + if(fabs(sig)>=2.0L && sig != -2.0L) + prod_ovf = 1; + + int E_MIN=E_MIN_QF32; + int E_MAX=E_MAX_QF32; + int BIAS=BIAS_QF32; + double _epsilon=epsilon; + double _units=units; + if(ft==QF32) + { + E_MIN = E_MIN_QF32; + E_MAX = E_MAX_QF32; + BIAS = BIAS_QF32; + _epsilon = epsilon; + _units= units; + } + else if(ft==QF16) + { + E_MIN = E_MIN_QF16; + E_MAX = E_MAX_QF16; + BIAS = BIAS_QF16; + _epsilon = epsilon_hf; + _units= units_hf; + } + else if(ft==SF) + { + E_MIN = E_MIN_SF; + E_MAX = E_MAX_SF; + BIAS = BIAS_SF; + _epsilon = epsilon; + _units= units; + } + else if(ft==HF) + { + E_MIN = E_MIN_HF; + E_MAX = E_MAX_HF; + BIAS = BIAS_HF; + _epsilon = epsilon_hf; + _units= units_hf; + } + + //Set scale factor + if((exp == (E_MIN-1)) || (prod_ovf && (exp0.0) + R_low = 0.25; + else if(sig_low<0.0) + R_low = -0.25; + else + R_low = 0; + + //R2 = floor((R1+R_low)/4.0)*4.0; + //R3 = (R1+R_low) - R2; + R2 = floor(R1/4.0)*4.0; + R3 = R1 - R2; + + //Check for exp overflow/underflow + if(exp>=(E_MAX+1) || (prod_ovf && exp==E_MAX)) + { + exp_ovf=1; + } + else if(exp<=(E_MIN-2)) + { + exp_undf=1; + } + else if(exp == E_MAX)//exp=E_MAX + { + //if(R3-2.0)+sig_low<=0.0 + if((R3==0.0) && (sig_low<0.0)) + { + sig_f = sig_s + (3.0-R3-4.0)*_epsilon; + } + else if((R3<2.0) || (R3==2.0 && sig_low<=0.0)) + //if(R3<=2.0) + { + sig_f = sig_s + (1.0-R3)*_epsilon; + } + else + { + sig_f = sig_s + (3.0-R3)*_epsilon; + } + } + else if(exp == (E_MIN-1)) + { + exp_adj = 1; + if((R3==0.0) && (sig_low<0.0)) + { + sig_f = sig_s + (3.0-R3-4.0)*_epsilon; + } + else if((R3<2.0) || (R3==2.0 && sig_low<=0.0)) + //if(R3<=2.0) + { + sig_f = sig_s + (1.0-R3)*_epsilon; + } + else + { + sig_f = sig_s + (3.0-R3)*_epsilon; + } + } + else if(prod_ovf && (exp < E_MAX)) + { + exp_adj = 1; + if((R3==0.0) && (sig_low<0.0)) + { + sig_f = sig_s + (3.0-R3-4.0)*_epsilon; + } + else if((R3<2.0) || (R3==2.0 && sig_low<=0.0)) + //if(R3<=2.0) + { + sig_f = sig_s + (1.0-R3)*_epsilon; + } + else + { + sig_f = sig_s + (3.0-R3)*_epsilon; + } + } + else if(!prod_ovf) + { + if((R3==0.0) && (sig_low<0.0)) + { + sig_f = sig_s + (3.0-R3-4.0)*_epsilon; + } + else if((R3<1.5) || (R3==1.5 && sig_low<=0.0)) + //if(R3<=1.5) + { + sig_f = sig_s + (1.0-R3)*_epsilon; + } + //else if(R3<=2.5) + else if((R3<2.5) || (R3==2.5 && sig_low<=0.0)) + { + sig_f = (sig + (2.0-R3)*_epsilon)*0.5; + exp_adj=1; + } + else + { + sig_f = sig_s + (3.0-R3)*_epsilon; + } + } + //get the binary bits from the double-precision significand + //Either sig is positive or negative, IEEE double sig has magnitude + //Check for sign at the last stage and take 2's complement if negative + uint64_t sig_64_org, sig_64; + sig_64_org = *(uint64_t *)&sig_f; + sig_64 = sig_64_org; + uint32_t sig_32=0; + int32_t sig_32_out=0; + + int exp_df; + + exp_df = (sig_64_org >> 52) & 0x7FF; + exp_df = exp_df - BIAS_DF; + + if(exp_ovf) + { + exp=E_MAX+BIAS; + if(ft==QF32 || ft==SF) + sig_32 = (sign-1) & 0x7FFFFF; + else if(ft==QF16 || ft==HF) + sig_32 = (sign-1) & 0x3FF; + } + else if(exp_undf) + { + exp=E_MIN+BIAS; + if(ft==QF32 || ft==SF) + sig_32 = ((-1)*sign) & 0x7FFFFF; + else if(ft==QF16 || ft==HF) + sig_32 = ((-1)*sign) & 0x3FF; + } + else + { + exp += BIAS+exp_adj; + //Add MSB, generates 53bits (52+1) + sig_64 = (sig_64_org & 0xFFFFFFFFFFFFF) | 0x10000000000000; + //Shift out exponent 11 bits + sig_64 = sig_64<<11; + sig_64 = (exp_df>=0)? (sig_64 << exp_df):(sig_64>>abs(exp_df)); + if(ft==QF32) + { + sig_64 = sig_64 >> 41; + sig_32 = sig_64 & 0x7FFFFF; + } + else if(ft==QF16) + { + sig_64 = sig_64 >> 54; + sig_32 = sig_64 & 0x3FF; + } + + if(sign) + sig_32 = ~sig_32; + } + + sig_32_out = (sign<<23) | sig_32; + + if(ft==QF16 ||ft==HF) + sig_32_out = (sign<<10) | sig_32; + + + if( (ft ==QF16) || (ft==QF32)) { + if ((sig == 0.0) && (sig_low == 0.0)) { + exp = 0; + //printf("Squash to zero!\n"); + } + + } + + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF_rnd_sat]sign=%d exp_in=%d sig=%10.30f sig_low=%10.30f\n",sign, *exp_in, sig, sig_low); + printf("[ARCH_QF_rnd_sat]sig_s=%10.30f, sig_f=%10.30f\n",sig_s, sig_f); + printf("[ARCH_QF_rnd_sat]prod_ovf=%d exp_adj=%d exp_ovf=%d exp_undf=%d\n",prod_ovf,exp_adj, exp_ovf, exp_undf); + printf("[ARCH_QF_rnd_sat]sig_64_org=%lx sig_64=%lx sig_32=%x exp_df=%d exp=%d\n",sig_64_org, sig_64, sig_32, exp_df, exp); + printf("[ARCH_QF_rnd_sat]R1=%10.30f R_low=%1.128f R2=%10.30f R3=%10.30f eps=%10.30f\n",R1,R_low,R2,R3,_epsilon); + + double final = ldexp(sig_f, (exp-BIAS)); + printf("[ARCH_QF_norm] sig_f:%10.30f, exp-BIAS:%d, ldexp:%10.128f \n",sig_f, exp-BIAS, final); + printf("[ARCH_QF_norm] sig_32_out:%x, exp:%x \n",sig_32_out, exp); +#endif + + *exp_in = exp; + return sig_32_out; +} + +//size4s_t rnd_sat_qf32(int sign, int exp, double sig, double sig_low) +size4s_t rnd_sat_qf32(int exp, double sig, double sig_low) +{ + + //size4u_t sig_32=rnd_sat_qf_sig(sign, &exp, sig, sig_low, QF32); + //size4u_t sig_32=rnd_sat_qf_sig(&exp, sig, sig_low, QF32); + size4s_t sig_32=rnd_sat_qf_sig(&exp, sig, sig_low, QF32); + + size4s_t result; + //result = (sign<<31) | (sig_32 <<8) | (exp & 0xFF); + result = (sig_32 <<8) | (exp & 0xFF); + + return result; +} + + +size4u_t get_ieee_sig(int *exp, double sig, f_type ft); +size4u_t get_ieee_sig(int *exp, double sig, f_type ft) +{ + //Extract bits from double precision significand + uint64_t sig_64_org=0, sig_52=0, sig_53=0; + double value = 0.0; + int exp_d=0, exp_org=*exp; + int E_MIN; + E_MIN = (ft==SF)? E_MIN_SF: E_MIN_HF; + double _epsilon; + _epsilon = (ft==SF)? epsilon: epsilon_hf; + uint32_t sig_32=0; + size4s_t signif=0; + //int sign = (sig>=0.0)? 0:1; + + value = ldexp(sig, exp_org); + + sig_64_org = *(uint64_t *)&value; + exp_d = (sig_64_org >> 52) & 0x7FF; + exp_d = exp_d - BIAS_DF; + sig_52 = (sig_64_org & 0xFFFFFFFFFFFFF); + sig_53 = sig_52 | 0x10000000000000; + + //Check if exp is one less than the MIN + //shifting right the excess amount of bits from E_MIN + int shift = E_MIN - exp_d; + + int lsb =0; + int rem =0; + int sticky =0; + int sig_f =0; +#ifndef DEBUG_MMVEC_QF + UNUSED(lsb); + UNUSED(rem); + UNUSED(sticky); + UNUSED(sig_f); + UNUSED(_epsilon); +#endif + + if(exp_d <= (E_MIN-1)) + { + sig_53 = sig_53 >> shift; + } + + if(shift >=53) + sig_53=0; + + double R1, R2, R3; + if(ft==SF) + { + signif = sig_53 >> 29; + sig_32 = signif & 0x7FFFFF; + + lsb = signif & 1; + rem = (sig_53 >>28) & 1; + sticky = (sig_53 & 0xFFFFFFF)? 1:0; + + R1 = sig_53/pow(2,29); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + + if(fabs(value) >= SF_MAX) + { + //sig_32 = (1-sign)*0x7FFFFF; + sig_32 = 0x7FFFFF; + } + else if((R3>0.5 && R3<1.0) || (R3>=1.5)) + { + if(sig_32 == 0x7FFFFF) + { + sig_32 = 0; + exp_d = exp_d +1; + } + else + sig_32 = sig_32 +1; + } + sig_f = 0x800000 | (sig_32 & 0x7FFFFF); + } + else + { + signif = sig_53 >> 42; + sig_32 = signif & 0x3FF; + + lsb = signif & 1; + rem = (sig_53 >> 41) & 1; + sticky = (sig_53 & 0x1FFFFFFFFFF)? 1:0; + + R1 = sig_53/pow(2,42); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + + //if((rem==1 && sticky==1) || (lsb==1 && rem==1)) + if(fabs(value) >= HF_MAX) + { + //sig_32 = (1-sign)*0x3FF; + sig_32 = 0x3FF; + } + else if((R3>0.5 && R3<1.0) || (R3>=1.5)) + { + if(sig_32 == 0x3FF) + { + sig_32 = 0; + exp_d = exp_d +1; + } + else + sig_32 = sig_32 +1; + } + sig_f = 0x400 | (sig_32 & 0x3FF); + + } + + if(sig ==0.0 && exp_org == (E_MIN-1)) + { + sig_64_org = 0; + exp_d = 0; + sig_32=0; + sig_f =0; + } + *exp = exp_d; + + + +#ifdef DEBUG_MMVEC_QF + int sign = (sig>=0.0)? 0: 1; + double param = (double)sig_f*_epsilon; + if(sign) param = (-1.0)*param; + int exp_f = (exp_d<=E_MIN-1)? E_MIN: exp_d; + double final = ldexp(param, exp_f); + int exp_1 = (value != 0.0)? ilogb(value): 0; + int exp_2 = (exp_1 > E_MIN)? exp_1: E_MIN; + double sig_1 = ldexp(value, exp_1-exp_2); + + printf("[IEEE_sig]exp_1=%d, exp_2=%d, sig_1=%10.20f\n",exp_1,exp_2,sig_1); + printf("[IEEE_sig]exp_org=%d, sig=%10.20f, value=%10.20f, shift=%d\n",exp_org, sig, value, shift); + printf("[IEEE_sig]sign=%d exp_d=%d sig_64_org=%lx sig_52=%lx sig_53=%lx sig_32=%x signif=%x sig_f=%x\n",sign, exp_d, sig_64_org, sig_52, sig_53, sig_32, signif, sig_f); + printf("[IEEE_sig]lsb=%d, rem=%d, sticky=%d\n",lsb, rem, sticky); + printf("[IEEE_sig] param:%10.20f, exp_d:%d, exp_f:%d, ldexp:%10.20f \n",param, exp_d, exp_f, final); + printf("[IEEE_sig]R1=%lf, R2=%lf, R3=%lf\n",R1, R2, R3); +#endif + + return sig_32; +} + +size2s_t rnd_sat_hf_rint(int exp_in, double sig_in); +size2s_t rnd_sat_hf_rint(int exp_in, double sig_in) +{ + // normalize and decompose again limiting to EMIN of target + double val=0.0; + double den=0.0; + double sig=0.0; + double mant=0.0; + int exp=0, exp_d=0, exp_ub=0; + size2s_t result=0; + + val = ldexp(sig_in, exp); // normalize - convert to simple float (double) + exp_d = (val != 0.0)? ilogb(val): 0; + exp_ub = (exp_d> E_MIN_HF)? exp_d: E_MIN_HF; // EMIN=-14 for fp16 + den = ldexp(val, -exp_ub); // denormalized if we hit EMIN + int sign = (sig<0)? 1:0; + sig = fabs(den); + // round to final mantissa + mant = rint(ldexp(sig, FRAC_HF)); // FRAC=10 for fp16; RNE + // post-round exponent adjust + exp = exp_ub + BIAS_HF; // BIAS=15 for fp16 + // -1 for -1.0 (denorm) or +1 for >=2.0 (round up to next exponent) + int exp_mant = (mant != 0.0)? ilogb(mant): 0; + int exp_adj = (exp_mant-FRAC_HF > -1)? (exp_mant - FRAC_HF): -1; + exp = exp - exp_adj; + // overflow + if (exp>E_MAX_HF) { // +16 for fp16 w/o inf/nan + exp = E_MAX_HF; + mant = -1; + } + // final result// better to use a struct for fp16 instead +// result = (mant&((1<=0.0)? 0:1; + //size4u_t sig_32=0;//rnd_sat_ieee_sig(&exp, sig, sig_low, SF); + size4u_t sig_32 = get_ieee_sig(&exp, sig, HF); + + //exp is unbiased + size2s_t result; + if(exp==(E_MIN_HF-1) && sig==0.0) + { + result = 0; + } + else if(exp > E_MAX_HF) + { + result = (sign<<15) | (0x1F << 10) | 0x3FF; + } + //else if((exp < E_MIN_HF-11) ||((exp == E_MIN_HF-11) && (sig_32 ==0))) + //{ + // result = (sign<<15); + //} + else + { + exp = exp + BIAS_HF; + if(exp < 0) + exp = 0; + else if(exp > 31) + exp = 31; + result = (sign<<15) | ((exp & 0x1F) << 10) | sig_32; + } + + + return result; +} + + +//Take signed sig, produce normalized ieee sf output +size4s_t rnd_sat_sf(int exp, double sig) +{ + + int sign = (sig>=0.0)? 0: 1; + size4u_t sig_32 = get_ieee_sig(&exp, sig, SF); + + size4s_t result; + + if(exp==0 && sig==0.0) + { + result = 0; + } + else + { + exp = exp + BIAS_SF; + if(exp < 0) + exp = 0; + else if(exp > 255) + exp = 255; + result = (sign<<31) | ((exp & 0xFF)<< 23) | (sig_32 & 0x7FFFFF); + } + + return result; +} + +//size2s_t rnd_sat_qf16(int sign, int exp_ab, double sig, double sig_low) +size2s_t rnd_sat_qf16(int exp_ab, double sig, double sig_low) +{ + int exp=exp_ab; + + + //size4u_t sig_32=rnd_sat_qf_sig(&exp, sig, sig_low, QF16); + //printf("sig low=%f sig=%f\n", sig, sig_low); + size4s_t sig_32=rnd_sat_qf_sig(&exp, sig, sig_low, QF16); + + size2s_t result; + result = (sig_32<<5) | (exp & 0x1F); + //result = (sign_ab<<15) | (sig_16<<5) | (exp_ab & 0x1F); + + return result; +} + +size4s_t mpy_qf32(size4s_t in_a, size4s_t in_b ) { + size2s_t exp; + double sig; + + unfloat a, b; + + //Get double precision significands and unbiased exp + a = parse_qf32(in_a); + b = parse_qf32(in_b); + + //Unbiased: after removing bias + exp = a.exp + b.exp; + sig = a.sig * b.sig; + + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_pre_rnd] a.sig:%10.20f, b.sig:%10.20f, sig:%10.20f, ilogb(sig):%d, exp:%d\n", a.sig, b.sig, sig, ilogb(sig), exp); +#endif + + size4s_t result; + //result = rnd_sat_qf32(sign, exp_ab, sig_ab, 0.0); + result = rnd_sat_qf32(exp, sig, 0.0); + + return result; +} + +size4s_t mpy_qf32_sf(size4s_t in_a, size4s_t in_b ) { + int sign; + size2s_t exp; + double sig; + unfloat a, b; + + //Get double precision significands and unbiased exp + a = parse_sf(in_a); + b = parse_sf(in_b); + + //Unbiased: after removing bias + sign = a.sign ^ b.sign; + exp = a.exp + b.exp; + sig = a.sig * b.sig; + + size4s_t result; + result = rnd_sat_qf32(exp, sig, 0.0); + if(sign) result = negate32(result); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_SF_parse]sign:%d, a.sig:%10.20f, b.sig:%10.20f, sig:%10.20f exp:%d\n",sign, a.sig, b.sig, sig, exp); +#endif + return result; +} + +size4s_t mpy_qf32_mix_sf(size4s_t in_a, size4s_t in_b ) { + size2s_t exp; + double sig; + unfloat a, b; + + //Get double precision significands and unbiased exp + a = parse_qf32(in_a); + b = parse_sf(in_b); + + //Unbiased: after removing bias + exp = a.exp + b.exp; + sig = a.sig * b.sig; + + size4s_t result; + result = rnd_sat_qf32(exp, sig, 0.0); + if(b.sign) result = negate32(result); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_SF_parse]a.sign:%d, a.sig:%10.20f, b.sign:%d, b.sig:%10.20f, sig:%10.20f exp:%d\n",a.sign, a.sig, b.sign, b.sig, sig, exp); +#endif + return result; +} + +//QF32 output out of two QF16 muls +size8s_t mpy_qf32_qf16(size4s_t in_a, size4s_t in_b ) { + + double sig_0, sig_1; + int exp_0, exp_1; + + unfloat u0,u1,v0,v1; + + u0 = parse_qf16((in_a & 0xFFFF)); + u1 = parse_qf16(((in_a>>16) & 0xFFFF)); + v0 = parse_qf16((in_b & 0xFFFF)); + v1 = parse_qf16(((in_b>>16) & 0xFFFF)); + + //Unbiased: after removing bias + exp_0 = u0.exp + v0.exp; + exp_1 = u1.exp + v1.exp; + sig_0 = u0.sig * v0.sig; + sig_1 = u1.sig * v1.sig; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_QF16_parse]u0.exp:%d, u0.sig:%10.20f, v0.exp:%d, v0.sig:%10.20f, sig_0:%10.20f exp_0:%d\n", u0.exp, u0.sig, v0.exp, v0.sig, sig_0, exp_0); + printf("[ARCH_QF32_QF16_parse]u1.exp:%d, u1.sig:%10.20f, v1.exp:%d, v1.sig:%10.20f, sig_1:%10.20f exp_1:%d\n", u1.exp, u1.sig, v1.exp, v1.sig, sig_1, exp_1); +#endif + + size4s_t result_0, result_1; + size8s_t result; + result_0 = rnd_sat_qf32(exp_0, sig_0, 0.0); + result_1 = rnd_sat_qf32(exp_1, sig_1, 0.0); + + result = ((size8s_t)result_1 <<32) | (result_0 &0xFFFFFFFF); +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_QF16_norm]result_1:%x, result_0:%x, result:%llx\n",result_1, result_0, result); +#endif + + return result; +} + +//QF32 output out of two HF muls +size8s_t mpy_qf32_hf(size4s_t in_a, size4s_t in_b ) { + + double sig_0, sig_1; + int exp_0, exp_1; + + unfloat u0,u1,v0,v1; + + u0 = parse_hf((in_a & 0xFFFF)); + u1 = parse_hf(((in_a>>16) & 0xFFFF)); + v0 = parse_hf((in_b & 0xFFFF)); + v1 = parse_hf(((in_b>>16) & 0xFFFF)); + + //Unbiased: after removing bias + exp_0 = u0.exp + v0.exp; + exp_1 = u1.exp + v1.exp; + sig_0 = u0.sig * v0.sig; + sig_1 = u1.sig * v1.sig; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_HF_parse]u0.exp:%d, u0.sig:%10.20f, v0.exp:%d, v0.sig:%10.20f, sig_0:%10.20f exp_0:%d\n", u0.exp, u0.sig, v0.exp, v0.sig, sig_0, exp_0); + printf("[ARCH_QF32_HF_parse]u1.exp:%d, u1.sig:%10.20f, v1.exp:%d, v1.sig:%10.20f, sig_1:%10.20f exp_1:%d\n", u1.exp, u1.sig, v1.exp, v1.sig, sig_1, exp_1); +#endif + size4s_t result_0, result_1; + size8s_t result; + result_0 = rnd_sat_qf32(exp_0, sig_0, 0.0); + result_1 = rnd_sat_qf32(exp_1, sig_1, 0.0); + + if(u0.sign ^ v0.sign) + result_0 = negate32(result_0); + + if(u1.sign ^ v1.sign) + result_1 = negate32(result_1); + + result = ((size8s_t)result_1 <<32) | (result_0 & 0xFFFFFFFF); +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_HF_norm]result_1:%x, result_0:%x, result:%llx\n",result_1, result_0, result); +#endif + + return result; +} + +//QF32 output out of mix of QF16 and HF muls +size8s_t mpy_qf32_mix_hf(size4s_t in_a, size4s_t in_b ) { + + double sig_0, sig_1; + int exp_0, exp_1; + + unfloat u0,u1,v0,v1; + + u0 = parse_qf16((in_a & 0xFFFF)); + u1 = parse_qf16(((in_a>>16) & 0xFFFF)); + v0 = parse_hf((in_b & 0xFFFF)); + v1 = parse_hf(((in_b>>16) & 0xFFFF)); + + //Unbiased: after removing bias + exp_0 = u0.exp + v0.exp; + exp_1 = u1.exp + v1.exp; + sig_0 = u0.sig * v0.sig; + sig_1 = u1.sig * v1.sig; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_mix_hf_parse]u0.exp:%d, u0.sig:%10.20f, v0.exp:%d, v0.sig:%10.20f, sig_0:%10.20f exp_0:%d\n", u0.exp, u0.sig, v0.exp, v0.sig, sig_0, exp_0); + printf("[ARCH_QF32_mix_hf_parse]u1.exp:%d, u1.sig:%10.20f, v1.exp:%d, v1.sig:%10.20f, sig_1:%10.20f exp_1:%d\n", u1.exp, u1.sig, v1.exp, v1.sig, sig_1, exp_1); +#endif + + size4s_t result_0, result_1; + size8s_t result; + result_0 = rnd_sat_qf32(exp_0, sig_0, 0.0); + result_1 = rnd_sat_qf32(exp_1, sig_1, 0.0); + + if(v0.sign) + result_0 = negate32(result_0); + if(v1.sign) + result_1 = negate32(result_1); + + result = ((size8s_t)result_1 <<32) | (result_0 & 0xFFFFFFFF); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF32_mix_hf_norm]result_1:%x, result_0:%x, result:%llx\n",result_1, result_0, result); +#endif + + return result; +} + +/* VMPY_QF16 */ +//ITERATOR_INSN_MPY_SLOT(16,vmpy_qf16,"Vd32.qf16=vmpy(Vu32.qf16,Vv32.qf16)", +//"Vector multiply of qf16 format", +size2s_t mpy_qf16(size2s_t in_a, size2s_t in_b ) { + size1s_t exp; + double sig; + + unfloat a, b; + + //Get double precision significands and unbiased exp + a = parse_qf16(in_a); + b = parse_qf16(in_b); + + //Unbiased: after removing bias + exp = a.exp + b.exp; + sig = a.sig * b.sig; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_QF16_parse] a.exp:%d, a.sig:%10.20f, b.exp:%d, b.sig:%10.20f, sig:%10.20f exp:%d\n", a.exp, a.sig, b.exp, b.sig, sig, exp); +#endif + + size2s_t result; + result = rnd_sat_qf16(exp, sig, 0.0); + + return result; +} + +size2s_t mpy_qf16_hf(size2s_t in_a, size2s_t in_b ) { + int sign; + size2s_t exp; + double sig; + + unfloat a, b; + + //Get double precision significands and unbiased exp + a = parse_hf(in_a); + b = parse_hf(in_b); + + //Unbiased: after removing bias + exp = a.exp + b.exp; + sig = a.sig * b.sig; + sign = a.sign^b.sign; + + size2s_t result; + result = rnd_sat_qf16(exp, sig, 0.0); + if(sign) result = negate16(result); +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_HF_parse]a.exp:%d, a.sig:%10.20f, b.exp:%d, b.sig:%10.20f, sig:%10.20f exp:%d\n",a.exp, a.sig, b.exp, b.sig, sig, exp); +#endif + + return result; +} + +size2s_t mpy_qf16_mix_hf(size2s_t in_a, size2s_t in_b ) { + size2s_t exp; + double sig; + unfloat a, b; + + //Get double precision significands and unbiased exp + a = parse_qf16(in_a); + b = parse_hf(in_b); + + //Unbiased: after removing bias + exp = a.exp + b.exp; + sig = a.sig * b.sig; + + size2s_t result; + result = rnd_sat_qf16(exp, sig, 0.0); + if(b.sign) result = negate16(result); +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_HF_parse]a.exp:%d, a.sig:%10.20f, b.exp:%d, b.sig:%10.20f, sig:%10.20f exp:%d\n",a.exp, a.sig, b.exp, b.sig, sig, exp); +#endif + + return result; +} + +size4s_t add_qf32(size4s_t in_a, size4s_t in_b ) { + size2s_t exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_qf32(in_a); + b = parse_qf32(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_SF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + //sig_low = (b.sign)? (-1.0*epsilon): epsilon; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_add_qf32] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_add_qf32] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); +#endif + + size4s_t result; + + result = rnd_sat_qf32(exp_ab, sig_ab, sig_low); + + return result; +} + + +size4s_t add_sf(size4s_t in_a, size4s_t in_b ) { + size2s_t exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_sf(in_a); + b = parse_sf(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_SF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + } + else if(a.sign==0 && b.sign==1) + { + sig_ab = sig_a - sig_b; + sig_low = (a.exp>b.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + } + else// if(a.sign==1 && b.sign==0) + { + sig_ab = sig_b - sig_a; + sig_low = (b.exp>a.exp) ? ((sig_b-sig_ab)-sig_a) : (sig_b -(sig_a+sig_ab)); + } + + size4s_t result; + result = rnd_sat_qf32(exp_ab, sig_ab, sig_low); + + if((a.sign==1) && (b.sign== 1)) + result = negate32(result); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_add_sf] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_add_sf] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_b-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_b-sig_ab,sig_low); + printf("[ARCH_add_sf] result:%x \n\n", result); +#endif + + + return result; +} + +size4s_t add_qf32_mix(size4s_t in_a, size4s_t in_b ) { + int exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_qf32(in_a); + b = parse_sf(in_b); + + if(b.sign) b.sig = (-1.0)*b.sig; + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_SF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + //sig_low = (b.sign)? (-1.0*epsilon): epsilon; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_add_qf32_mix] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_add_qf32_mix] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); +#endif + + size4s_t result; + + result = rnd_sat_qf32(exp_ab, sig_ab, sig_low); + + return result; +} + +size4s_t sub_qf32(size4s_t in_a, size4s_t in_b ) { + size2s_t exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_qf32(in_a); + b = parse_qf32(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_SF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + //sig_low = (b.sign)? (-1.0*epsilon): epsilon; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_sub_qf32] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_sub_qf32] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); + printf("[ARCH_sub_qf32] a:%10.30f, a_adj:%10.30f, fabs(sig_b):%f\n", ldexp(a.sig, a.exp), ldexp(sig_a, exp_ab), fabs(sig_b)); +#endif + + size4s_t result; + + result = rnd_sat_qf32(exp_ab, sig_ab, sig_low); + + return result; +} + +size4s_t sub_sf(size4s_t in_a, size4s_t in_b ) { + size2s_t exp_ab; + unfloat a, b; + + //Get double precision significands + a = parse_sf(in_a); + b = parse_sf(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_SF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + } + else if(a.sign ^ b.sign) + { + sig_ab = sig_a + sig_b; + sig_low = (a.exp>b.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + } + else// if(a.sign && b.sign) + { + sig_ab = sig_b - sig_a; + sig_low = (b.exp>a.exp) ? ((sig_b-sig_ab)-sig_a) : (sig_b -(sig_a+sig_ab)); + } + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_sub_sf] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_sub_sf] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_b-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_b-sig_ab,sig_low); +#endif + + size4s_t result; + + result = rnd_sat_qf32(exp_ab, sig_ab, sig_low); + + if((a.sign==1) && (b.sign==0)) + result = negate32(result); + + return result; +} + +size4s_t sub_qf32_mix(size4s_t in_a, size4s_t in_b ) { + size2s_t exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_qf32(in_a); + b = parse_sf(in_b); + + if(b.sign) b.sig = (-1.0)*b.sig; + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_SF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_ab-sig_a)-sig_b) : ((sig_ab-sig_b)-sig_a); + //sig_low = (a.exp>b.exp) ? ((sig_ab-sig_a)+sig_b) : (sig_a-(sig_b+sig_ab)); + sig_low = (a.exp>b.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_sub_qf32_mix] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_sub_qf32_mix] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); +#endif + + size4s_t result; + + result = rnd_sat_qf32(exp_ab, sig_ab, sig_low); + + return result; +} +//add_qf16 +size2s_t add_qf16(size2s_t in_a, size2s_t in_b ) { + size1s_t exp_ab; + unfloat a, b; + + //Get double precision significands + a = parse_qf16(in_a); + b = parse_qf16(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_HF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + //sig_low = (b.sign)? (-1.0*epsilon): epsilon; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_add_qf16] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_add_qf16] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); +#endif + + size2s_t result; + + result = rnd_sat_qf16(exp_ab, sig_ab, sig_low); + + return result; +} + +size2s_t add_hf(size2s_t in_a, size2s_t in_b ) { + size1s_t exp_ab; + unfloat a, b; + + //Get double precision significands + a = parse_hf(in_a); + b = parse_hf(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_HF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + } + else if(a.sign==0 && b.sign==1) + { + sig_ab = sig_a - sig_b; + sig_low = (a.exp>b.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + } + else// if(a.sign==1 && b.sign==0) + { + sig_ab = sig_b - sig_a; + sig_low = (b.exp>a.exp) ? ((sig_b-sig_ab)-sig_a) : (sig_b -(sig_a+sig_ab)); + } + + size2s_t result; + + result = rnd_sat_qf16(exp_ab, sig_ab, sig_low); + if((a.sign==1) && (b.sign== 1)) + result = negate16(result); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_add_hf] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_add_hf] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_b-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_b-sig_ab,sig_low); + printf("[ARCH_add_sf] result:%x \n\n", result); +#endif + + + return result; +} + +size2s_t add_qf16_mix(size2s_t in_a, size2s_t in_b ) { + size1s_t exp_ab; + unfloat a, b; + + //Get double precision significands + a = parse_qf16(in_a); + b = parse_hf(in_b); + + if(b.sign) b.sig = (-1.0)*b.sig; + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_HF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + //sig_low = (b.sign)? (-1.0*epsilon): epsilon; + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_add_qf16_mix] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_add_qf16_mix] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); +#endif + + size2s_t result; + + result = rnd_sat_qf16(exp_ab, sig_ab, sig_low); + + return result; +} + +size2s_t sub_qf16(size2s_t in_a, size2s_t in_b ) { + size1s_t exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_qf16(in_a); + b = parse_qf16(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_HF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + //sig_low = (a.exp>b.exp) ? ((sig_ab-sig_a)+sig_b) : (sig_a-(sig_b+sig_ab)); + sig_low = (a.exp>b.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_sub_qf16] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_sub_qf16] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); + printf("[ARCH_sub_qf32] a:%10.30f, a_adj:%10.30f, fabs(sig_b):%f\n", ldexp(a.sig, a.exp), ldexp(sig_a, exp_ab), fabs(sig_b)); +#endif + + size2s_t result; + + result = rnd_sat_qf16(exp_ab, sig_ab, sig_low); + + return result; +} + + +size2s_t sub_hf(size2s_t in_a, size2s_t in_b ) { + size1s_t exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_hf(in_a); + b = parse_hf(in_b); + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_HF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + } + else if(a.sign ^ b.sign) + { + sig_ab = sig_a + sig_b; + sig_low = (a.exp>b.exp) ? ((sig_a-sig_ab)+sig_b) : ((sig_b-sig_ab)+sig_a); + } + else// if(a.sign && b.sign) + { + sig_ab = sig_b - sig_a; + sig_low = (b.exp>a.exp) ? ((sig_b-sig_ab)-sig_a) : (sig_b -(sig_a+sig_ab)); + } + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_sub_hf] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_sub_hf] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.30f, sig_ab:%1.30f, sig_ab-sig_a:%1.30f, sig_low:%1.30f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_ab-sig_a,sig_low); +#endif + + size2s_t result; + + result = rnd_sat_qf16(exp_ab, sig_ab, sig_low); + if((a.sign==1) && (b.sign==0)) + result = negate16(result); + + return result; +} + +size2s_t sub_qf16_mix(size2s_t in_a, size2s_t in_b ) { + size1s_t exp_ab; + + unfloat a, b; + + //Get double precision significands + a = parse_qf16(in_a); + b = parse_hf(in_b); + + if(b.sign) b.sig = (-1.0)*b.sig; + + if(a.exp>b.exp){ + exp_ab = a.exp+((a.sig==0.0)? (-(FRAC_HF+1)):ilogb(a.sig)); + if(exp_abb.exp) ? ((sig_ab-sig_a)-sig_b) : ((sig_ab-sig_b)-sig_a); + //sig_low = (a.exp>b.exp) ? ((sig_ab-sig_a)+sig_b) : (sig_a-(sig_b+sig_ab)); + sig_low = (a.exp>b.exp) ? ((sig_a-sig_ab)-sig_b) : (sig_a -(sig_b+sig_ab)); + +#ifdef DEBUG_MMVEC_QF + printf("[ARCH_sub_qf16_mix] a.exp:%d, b.exp:%d, exp_ab:%d, ilogb(a.sig):%d, ilogb(b.sig):%d\n", a.exp,b.exp,exp_ab, ilogb(a.sig), ilogb(b.sig)); + printf("[ARCH_sub_qf16_mix] a.sig:%10.30f, b.sig:%10.30f, sig_a:%10.30f, sig_b:%1.128f, sig_ab:%1.128f, sig_a-sig_ab:%1.128f, sig_low:%1.128f\n", a.sig, b.sig, sig_a, sig_b, sig_ab, sig_a-sig_ab,sig_low); +#endif + + size2s_t result; + + result = rnd_sat_qf16(exp_ab, sig_ab, sig_low); + + return result; +} + +//FP conversion QF32 to IEEE SF +size4s_t conv_sf_qf32(size4s_t a) +{ + + size4s_t result; + unfloat u = parse_qf32(a); + + result = rnd_sat_sf(u.exp, u.sig); + +#ifdef DEBUG_MMVEC_QF + double final = ldexp(u.sig, u.exp); + printf("[SF_parse_conv_sf_qf32] u.sig:%lf, u.exp:%d, ldexp:%10.20f \n",u.sig, u.exp, final); +#endif + + return result; +} + +//FP conversion W to IEEE SF +size4s_t conv_sf_w(size4s_t a) +{ + + size4s_t result; + int exp=0; + double sig=0.0; + if(a !=0) + { + exp = ilogb(a); + sig = (double)a/scalbn(1.0, exp); + } + result = rnd_sat_sf(exp, sig); + +#ifdef DEBUG_MMVEC_QF + double final = ldexp(sig, exp); + printf("[SF_parse_conv_sf_w] sig:%lf, exp:%d, ldexp:%10.20f \n",sig, exp, final); +#endif + + return result; +} + +//FP conversion UW to IEEE SF +size4s_t conv_sf_uw(size4u_t a) +{ + + size4s_t result; + int exp=0; + double sig=0.0; + if(a !=0) + { + exp = ilogb(a); + sig = (double)(unsigned)a/scalbn(1.0, exp); + } + result = rnd_sat_sf(exp, sig); + +//#ifdef DEBUG_MMVEC_QF +// double final = ldexp(sig, exp); +// printf("[SF_parse_conv_sf_uw] sig:%lf, exp:%d, ldexp:%10.20f \n",sig, exp, final); +//#endif + + return result; +} + +//FP conversion QF16 to IEEE HF +size2s_t conv_hf_qf16(size2s_t a) +{ + + size2s_t result; + unfloat u = parse_qf16(a); + + result = rnd_sat_hf(u.exp, u.sig); + +//#ifdef DEBUG_MMVEC_QF +// double final = ldexp(u.sig, u.exp); +// printf("[HF_parse_conv_hf_qf16] u.sig:%lf, u.exp:%d, ldexp:%10.20f \n",u.sig, u.exp, final); +//#endif + + return result; +} + +//FP conversion H to IEEE HF +size2s_t conv_hf_h(size2s_t a) +{ + size2s_t result; + int exp=0; + double sig=0.0; + if(a !=0) + { + exp = ilogb(a); + sig = (double)a/scalbn(1.0, exp); + } + result = rnd_sat_hf(exp, sig); + +#ifdef DEBUG_MMVEC_QF + double final = ldexp(sig, exp); + double f_rint = rint(final); + printf("[HF_parse_conv_hf_h] sig:%lf, exp:%d, ldexp:%10.20f, rint:%lf \n",sig, exp, final, f_rint); +#endif + return result; +} + +//FP conversion UH to IEEE HF +size2s_t conv_hf_uh(size2u_t a) +{ + + size2s_t result; + int exp=0; + double sig=0.0; + if(a !=0) + { + exp = ilogb(a); + sig = (double)(unsigned)a/scalbn(1.0, exp); + } + result = rnd_sat_hf(exp, sig); + +//#ifdef DEBUG_MMVEC_QF +// double final = ldexp(sig, exp); +// printf("[SF_parse_conv_hf_uh] sig:%lf, exp:%d, ldexp:%10.20f \n",sig, exp, final); +//#endif + + return result; +} + +//FP conversion two QF32 to two QF16 +size4s_t conv_hf_qf32(size8s_t a) +{ + + size2s_t result0, result1; + size4s_t result; + size4s_t a0, a1; + a0 = a & 0xFFFFFFFF; + a1 = (a>>32) & 0xFFFFFFFF; + + unfloat u0 = parse_qf32(a0); + unfloat u1 = parse_qf32(a1); + + result0 = rnd_sat_hf(u0.exp, u0.sig); + result1 = rnd_sat_hf(u1.exp, u1.sig); + + result = ((size4s_t)result1 << 16) | (result0 & 0xFFFF); + +/* +#ifdef DEBUG_MMVEC_QF + double final0 = ldexp(u0.sig, u0.exp); + double final1 = ldexp(u1.sig, u1.exp); + + printf("[HF_parse_conv_hf_qf32] u0.sig:%lf, u0.exp:%d, ldexp0:%10.20f \n",u0.sig, u0.exp, final0); + printf("[HF_parse_conv_hf_qf32] u1.sig:%lf, u1.exp:%d, ldexp1:%10.20f \n",u1.sig, u1.exp, final1); +#endif +*/ + + return result; +} + +//FP conversion two W to two IEEE HF +size4s_t conv_hf_w(size8s_t a) +{ + size2s_t result0, result1; + size4s_t result; + size4s_t a0, a1; + a0 = a & 0xFFFFFFFF; + a1 = (a>>32) & 0xFFFFFFFF; + + int exp0=0, exp1=0; + double sig0=0.0, sig1=0.0; + if(a0 !=0) + { + exp0 = ilogb(a0); + sig0 = (double)a0/scalbn(1.0, exp0); + } + if(a1 !=0) + { + exp1 = ilogb(a1); + sig1 = (double)a1/scalbn(1.0, exp1); + } + result0 = rnd_sat_hf(exp0, sig0); + result1 = rnd_sat_hf(exp1, sig1); + + result = ((size4s_t)result1 << 16) | (result0 & 0xFFFF); + +/* +#ifdef DEBUG_MMVEC_QF + double final0 = ldexp(sig0, exp0); + double final1 = ldexp(sig1, exp1); + + printf("[HF_parse_conv_hf_w] sig0:%lf, exp0:%d, ldexp0:%10.20f \n",sig0, exp0, final0); + printf("[HF_parse_conv_hf_w] sig1:%lf, exp1:%d, ldexp1:%10.20f \n",sig1, exp1, final1); +#endif +*/ + return result; +} + +//FP conversion two UW to two IEEE HF +size4s_t conv_hf_uw(size8u_t a) +{ + size2s_t result0, result1; + size4s_t result; + size4u_t a0, a1; + a0 = a & 0xFFFFFFFF; + a1 = (a>>32) & 0xFFFFFFFF; + + int exp0=0, exp1=0; + double sig0=0.0, sig1=0.0; + if(a0 !=0) + { + exp0 = ilogb(a0); + sig0 = (double)(unsigned)a0/scalbn(1.0, exp0); + } + if(a1 !=0) + { + exp1 = ilogb(a1); + sig1 = (double)(unsigned)a1/scalbn(1.0, exp1); + } + result0 = rnd_sat_hf(exp0, sig0); + result1 = rnd_sat_hf(exp1, sig1); + + result = ((size4s_t)result1 << 16) | (result0 & 0xFFFF); +/* +#ifdef DEBUG_MMVEC_QF + double final0 = ldexp(sig0, exp0); + double final1 = ldexp(sig1, exp1); + + printf("[HF_parse_conv_hf_uw] sig0:%lf, exp0:%d, ldexp0:%10.20f \n",sig0, exp0, final0); + printf("[HF_parse_conv_hf_uw] sig1:%lf, exp1:%d, ldexp1:%10.20f \n",sig1, exp1, final1); +#endif +*/ + return result; +} + +size4s_t rnd_sat_w(int exp, double sig) +{ + size4s_t result=0; + size4s_t W_MAX = 0x7fffffff; + size4s_t W_MIN = 0x80000000; + + int sign = (sig>=0.0)? 0: 1; + + double R1=0.0; + double R2=0.0; + double R3=0.0; + if(exp > 30) + { + result = (sign)? W_MIN:W_MAX; + result = (sign <<31) | result; + } + else + { + R1 = ldexp(sig, exp); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + if(sign==0) + { + if(R3<=0.5) + result = (size4s_t) R1; + else if(R3>0.5 && R3<1.5) + result = (size4s_t) round(R1); + else if(R3>=1.5) + result = (size4s_t) R1+1; + } + else + result = (size4s_t)round(R1); + } + +#ifdef DEBUG_MMVEC_QF + printf("[RND_conv_w_qf32] sig:%lf, exp:%d, R1:%10.20f, R2:%10.20f, R3:%10.20f, result:%x(%d)\n",sig, exp, R1, R2, R3, result, result); +#endif + + return result; +} + +size4u_t rnd_sat_uw(int exp, double sig) +{ + size4u_t result=0; + size4u_t W_MAX = 0xffffffff; + + double R1=0.0; + double R2=0.0; + double R3=0.0; + if(sig<0.0) + result = 0; + else if(exp > 31) + { + result = W_MAX; + } + else + { + R1 = ldexp(sig, exp); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + if(R3<=0.5) + result = (size4s_t) R1; + else if(R3>0.5 && R3<1.5) + result = (size4s_t) round(R1); + else if(R3>=1.5) + result = (size4s_t) R1+1; + } + +#ifdef DEBUG_MMVEC_QF + printf("[RND_conv_uw_qf32] sig:%lf, exp:%d, R1:%10.20f, R2:%10.20f, R3:%10.20f, result:%x(%d)\n",sig, exp, R1, R2, R3, result, result); +#endif + + return result; +} + +size2s_t rnd_sat_h(int exp, double sig) +{ + size2s_t result=0; + size2s_t W_MAX = 0x7fff; + size2s_t W_MIN = 0x8000; + + int sign = (sig>=0.0)? 0: 1; + + double R1=0.0; + double R2=0.0; + double R3=0.0; + if(exp > 14) + { + result = (sign)? W_MIN:W_MAX; + result = (sign <<15) | result; + } + else + { + R1 = ldexp(sig, exp); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + if(sign==0) + { + if(R3<=0.5) + result = (size2s_t) R1; + else if(R3>0.5 && R3<1.5) + result = (size2s_t) round(R1); + else if(R3>=1.5) + result = (size2s_t) R1+1; + } + else + { + if(R3<=0.5 && R3 !=0.0) + result = (size2s_t)R1 -1; + else if(R3>0.5 && R3<1.5) + result = (size2s_t)round(R1); + else// if(R3>=1.5) + result = (size2s_t)R1; + } + } + +#ifdef DEBUG_MMVEC_QF + printf("[RND_conv_h_qf16] sig:%lf, exp:%d, R1:%10.20f, R2:%10.20f, R3:%10.20f, result:%x(%d)\n",sig, exp, R1, R2, R3, result, result); +#endif + + return result; +} + +size2u_t rnd_sat_uh(int exp, double sig) +{ + size2u_t result=0; + size2u_t W_MAX = 0xffff; + + double R1=0.0; + double R2=0.0; + double R3=0.0; + if(sig<0.0) + result = 0; + else if(exp > 15) + { + result = W_MAX; + } + else + { + R1 = ldexp(sig, exp); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + if(R3<=0.5) + result = (size2s_t) R1; + else if(R3>0.5 && R3<1.5) + result = (size2s_t) round(R1); + else if(R3>=1.5) + result = (size2s_t) R1+1; + } + +#ifdef DEBUG_MMVEC_QF + printf("[RND_conv_uh_qf16] sig:%lf, exp:%d, R1:%10.20f, R2:%10.20f, R3:%10.20f, result:%x(%d)\n",sig, exp, R1, R2, R3, result, result); +#endif + + return result; +} + +size1s_t rnd_sat_b(int exp, double sig) +{ + size1s_t result=0; + size1s_t W_MAX = 0x7f; + size1s_t W_MIN = 0x80; + + int sign = (sig>=0.0)? 0: 1; + + double R1=0.0; + double R2=0.0; + double R3=0.0; + if(exp > 6) + { + result = (sign)? W_MIN:W_MAX; + result = (sign <<7) | result; + } + else + { + R1 = ldexp(sig, exp); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + if(sign==0) + { + if(R3<=0.5) + result = (size1s_t) R1; + else if(R3>0.5 && R3<1.5) + result = (size1s_t) round(R1); + else if(R3>=1.5) + result = (size1s_t) R1+1; + } + else + { + if(R3<=0.5 && R3 !=0.0) + result = (size1s_t)R1 -1; + else if(R3>0.5 && R3<1.5) + result = (size1s_t)round(R1); + else// if(R3>=1.5) + result = (size1s_t)R1; + } + } + +#ifdef DEBUG_MMVEC_QF + printf("[RND_conv_b_qf16] sig:%lf, exp:%d, R1:%10.20f, R2:%10.20f, R3:%10.20f, result:%x(%d)\n",sig, exp, R1, R2, R3, result, result); +#endif + + return result; +} + +size1u_t rnd_sat_ub(int exp, double sig) +{ + size1u_t result=0; + size1u_t W_MAX = 0xff; + + double R1=0.0; + double R2=0.0; + double R3=0.0; + if(sig<0.0) + result = 0; + else if(exp > 7) + { + result = W_MAX; + } + else + { + R1 = ldexp(sig, exp); + R2 = floor(R1/2.0)*2; + R3 = R1 - R2; + + if(R3<=0.5) + result = (size1s_t) R1; + else if(R3>0.5 && R3<1.5) + result = (size1s_t) round(R1); + else if(R3>=1.5) + result = (size1s_t) R1+1; + } + +#ifdef DEBUG_MMVEC_QF + printf("[RND_conv_ub_qf16] sig:%lf, exp:%d, R1:%10.20f, R2:%10.20f, R3:%10.20f, result:%x(%d)\n",sig, exp, R1, R2, R3, result, result); +#endif + + return result; +} + +//FP conversion QF32 to 32bit W +size4s_t conv_w_qf32(size4s_t a) +{ + + size4s_t result; + unfloat u = parse_qf32(a); + + result = rnd_sat_w(u.exp, u.sig); + + return result; +} + +size4s_t conv_w_sf(size4s_t op1) +{ + sf_union input; + size4s_t W_MAX = 0x7fffffff; + size4s_t W_MIN = 0x80000000; + input.i = op1; + size4s_t result; + + if(isNaNF32(op1) || isInfF32(op1) || (input.f >= (float)W_MAX) || (input.f <= (float)W_MIN)) + { + if(input.x.sign == 1){ + result = W_MIN; + } + else{ + result = W_MAX; + } + } + else{ + //convert and round to the zero + result = (int)input.f; + } + +#ifdef DEBUG_MMVEC_QF + printf("Debug : result =0x%08x\n",result); +#endif + return result; +} + +size2s_t conv_h_hf(size2s_t op1) +{ + sf_union input; + size4s_t op1_ext = op1; + size2s_t HW_MAX = 0x7fff; + size2s_t HW_MIN = 0x8000; + input.i = ((op1_ext & 0x8000) << 16) + (((op1_ext & 0x7c00) + 0x1c000) << 13) + ((op1_ext & 0x03ff) << 13); //grabbing sign, exp, and significand and ocnverting to sf32 format + size2s_t result; + + if(isNaNF16(op1) || isInfF16(op1) || (input.f >= (float)HW_MAX) || (input.f <= (float)HW_MIN)) + { + if(input.x.sign == 1){ + result = HW_MIN; + } + else{ + result = HW_MAX; + } + } + else{ + //convert and round to the zero + result = (short)input.f; + } + +#ifdef DEBUG_MMVEC_QF + printf("Debug : result =0x%08x\n",result); +#endif + return result; +} + +//FP conversion QF32 to 32bit UW +size4u_t conv_uw_qf32(size4s_t a) +{ + + size4u_t result; + unfloat u = parse_qf32(a); + + result = rnd_sat_uw(u.exp, u.sig); + + return result; +} + +//FP conversion QF16 to 16bit H +size2s_t conv_h_qf16(size2s_t a) +{ + + size2s_t result; + unfloat u = parse_qf16(a); + + result = rnd_sat_h(u.exp, u.sig); + + return result; +} + +//FP conversion QF32 to 32bit UW +size2u_t conv_uh_qf16(size2s_t a) +{ + + size2u_t result; + unfloat u = parse_qf16(a); + + result = rnd_sat_uh(u.exp, u.sig); + + return result; +} + +//FP conversion double QF32 to double H +size4s_t conv_h_qf32(size8s_t a) +{ + size2s_t result0, result1; + size4s_t result; + size4s_t a0, a1; + a0 = a & 0xFFFFFFFF; + a1 = (a>>32) & 0xFFFFFFFF; + + unfloat u0 = parse_qf32(a0); + unfloat u1 = parse_qf32(a1); + + result0 = rnd_sat_h(u0.exp, u0.sig); + result1 = rnd_sat_h(u1.exp, u1.sig); + + result = ((size4s_t)result1 << 16) | (result0 & 0xFFFF); + +#ifdef DEBUG_MMVEC_QF + double final0 = ldexp(u0.sig, u0.exp); + double final1 = ldexp(u1.sig, u1.exp); + + printf("[H_parse_conv_h_qf32] u0.sig:%lf, u0.exp:%d, ldexp0:%10.20f \n",u0.sig, u0.exp, final0); + printf("[H_parse_conv_h_qf32] u1.sig:%lf, u1.exp:%d, ldexp1:%10.20f \n",u1.sig, u1.exp, final1); +#endif + + return result; +} + +//FP conversion QF32 to 32bit UW +size4u_t conv_uh_qf32(size8s_t a) +{ + size2u_t result0, result1; + size4u_t result; + size4s_t a0, a1; + a0 = a & 0xFFFFFFFF; + a1 = (a>>32) & 0xFFFFFFFF; + + unfloat u0 = parse_qf32(a0); + unfloat u1 = parse_qf32(a1); + + result0 = rnd_sat_uh(u0.exp, u0.sig); + result1 = rnd_sat_uh(u1.exp, u1.sig); + + result = ((size4u_t)result1 << 16) | (result0 & 0xFFFF); + +#ifdef DEBUG_MMVEC_QF + double final0 = ldexp(u0.sig, u0.exp); + double final1 = ldexp(u1.sig, u1.exp); + + printf("[UH_parse_conv_uh_qf32] u0.sig:%lf, u0.exp:%d, ldexp0:%10.20f \n",u0.sig, u0.exp, final0); + printf("[UH_parse_conv_uh_qf32] u1.sig:%lf, u1.exp:%d, ldexp1:%10.20f \n",u1.sig, u1.exp, final1); +#endif + + return result; +} + +//FP conversion double QF16 to double B +size2s_t conv_b_qf16(size4s_t a) +{ + size1s_t result0, result1; + size2s_t result; + size2s_t a0, a1; + a0 = a & 0xFFFF; + a1 = (a>>16) & 0xFFFF; + + unfloat u0 = parse_qf16(a0); + unfloat u1 = parse_qf16(a1); + + result0 = rnd_sat_b(u0.exp, u0.sig); + result1 = rnd_sat_b(u1.exp, u1.sig); + + result = ((size2s_t)result1 << 8) | (result0 & 0xFF); + +#ifdef DEBUG_MMVEC_QF + double final0 = ldexp(u0.sig, u0.exp); + double final1 = ldexp(u1.sig, u1.exp); + + printf("[B_parse_conv_b_qf16] u0.sig:%lf, u0.exp:%d, ldexp0:%10.20f \n",u0.sig, u0.exp, final0); + printf("[B_parse_conv_b_qf16] u1.sig:%lf, u1.exp:%d, ldexp1:%10.20f \n",u1.sig, u1.exp, final1); +#endif + + return result; +} + +//FP conversion QF32 to 32bit UW +size2u_t conv_ub_qf16(size4s_t a) +{ + size1u_t result0, result1; + size2u_t result; + size2s_t a0, a1; + a0 = a & 0xFFFF; + a1 = (a>>16) & 0xFFFF; + + unfloat u0 = parse_qf16(a0); + unfloat u1 = parse_qf16(a1); + + result0 = rnd_sat_ub(u0.exp, u0.sig); + result1 = rnd_sat_ub(u1.exp, u1.sig); + + result = ((size2u_t)result1 << 8) | (result0 & 0xFF); + +#ifdef DEBUG_MMVEC_QF + double final0 = ldexp(u0.sig, u0.exp); + double final1 = ldexp(u1.sig, u1.exp); + + printf("[UB_parse_conv_ub_qf16] u0.sig:%lf, u0.exp:%d, ldexp0:%10.20f \n",u0.sig, u0.exp, final0); + printf("[UB_parse_conv_ub_qf16] u1.sig:%lf, u1.exp:%d, ldexp1:%10.20f \n",u1.sig, u1.exp, final1); +#endif + + return result; +} + +//Neg/Abs +size4s_t neg_qf32(size4s_t a) +{ + size4s_t result; + result = negate32(a); + return result; +} +size4s_t abs_qf32(size4s_t a) +{ + size4s_t result; + if((a>>31) & 1) + result = negate32(a); + else + result = a; + return result; +} +size2s_t neg_qf16(size2s_t a) +{ + size2s_t result; + result = negate16(a); + return result; +} +size2s_t abs_qf16(size2s_t a) +{ + size2s_t result; + if((a>>15) & 1) + result = negate16(a); + else + result = a; + return result; +} +size4s_t neg_sf(size4s_t a) +{ + size4s_t result; + result = negate_sf(a); + return result; +} +size4s_t abs_sf(size4s_t a) +{ + size4s_t result; + if((a>>31) & 1) + result = negate_sf(a); + else + result = a; + return result; +} +size2s_t neg_hf(size2s_t a) +{ + size2s_t result; + result = negate_hf(a); + return result; +} +size2s_t abs_hf(size2s_t a) +{ + size2s_t result; + if((a>>15) & 1) + result = negate_hf(a); + else + result = a; + return result; +} + +//FP Compare +int cmpgt_fp(unfloat a, unfloat b) +{ + int result=0; + double a_d, b_d; + a_d = ldexp(a.sig, a.exp); + b_d = ldexp(b.sig, b.exp); + + //Filter out +0/-0 by checking the sign + if(a_d > b_d) + result=1; + +#ifdef DEBUG_MMVEC_QF + printf("[CMPGT]a:%10.30f, b:%10.30f\n",a_d, b_d); +#endif + + return result; +} + +int cmpgt_qf32(size4s_t in_a, size4s_t in_b) +{ + unfloat a, b; + a= parse_qf32(in_a); + b= parse_qf32(in_b); + + int result=0; + + result = cmpgt_fp(a,b); + + return result; +} + +int cmpgt_qf16(size2s_t in_a, size2s_t in_b) +{ + + unfloat a, b; + a= parse_qf16(in_a); + b= parse_qf16(in_b); + + int result=0; + result = cmpgt_fp(a,b); + + return result; +} + +int cmpgt_sf(size4s_t in_a, size4s_t in_b) +{ + + unfloat a, b; + a= parse_sf(in_a); + b= parse_sf(in_b); + + if(a.sign) + a.sig = (-1.0)*a.sig; + if(b.sign) + b.sig = (-1.0)*b.sig; + + int result=0; + result = cmpgt_fp(a,b); + + return result; +} + +int cmpgt_hf(size2s_t in_a, size2s_t in_b) +{ + + unfloat a, b; + a= parse_hf(in_a); + b= parse_hf(in_b); + + if(a.sign) + a.sig = (-1.0)*a.sig; + if(b.sign) + b.sig = (-1.0)*b.sig; + + int result=0; + result = cmpgt_fp(a,b); + + return result; +} + +int cmpgt_qf32_sf(size4s_t in_a, size4s_t in_b) +{ + unfloat a = parse_qf32(in_a); + unfloat b = parse_sf(in_b); + if(b.sign) + b.sig = (-1.0)*b.sig; + + int result=0; + result = cmpgt_fp(a,b); + + return result; +} + +int cmpgt_qf16_hf(size2s_t in_a, size2s_t in_b) +{ + unfloat a = parse_qf16(in_a); + unfloat b = parse_hf(in_b); + if(b.sign) + b.sig = (-1.0)*b.sig; + + int result=0; + result = cmpgt_fp(a,b); + return result; +} +//max/min + //if a==b, a is returned +size4s_t max_qf32( size4s_t in_a, size4s_t in_b) { return cmpgt_qf32( in_b, in_a) ? in_b : in_a; } +size2s_t max_qf16( size2s_t in_a, size2s_t in_b) { return cmpgt_qf16( in_b, in_a) ? in_b : in_a; } + + + +size4s_t is_check_zero_sf(size4s_t in_a); +size4s_t is_check_zero_sf(size4s_t in_a) { + return (in_a == 0) || ((in_a & 0xFFFFFFFF) == 0x80000000); +} +size2s_t is_check_zero_hf(size2s_t in_a); +size2s_t is_check_zero_hf(size2s_t in_a) { + return (in_a == 0) || ((in_a & 0xFFFF) == 0x8000); +} + +size4s_t max_sf( size4s_t in_a, size4s_t in_b) { + if (is_check_zero_sf(in_a) && is_check_zero_sf(in_b) ) { + return (in_a == 0) ? in_a : in_b; // Return in_a if it's positive 0, otherwise return the other one + } + return cmpgt_sf( in_b, in_a) ? in_b : in_a; + +} +size2s_t max_hf( size2s_t in_a, size2s_t in_b) +{ + if (is_check_zero_hf(in_a) && is_check_zero_hf(in_b) ) { + return (in_a == 0) ? in_a : in_b; + } + return cmpgt_hf( in_b, in_a) ? in_b : in_a; +} + + +//size2s_t max_qf16_hf( size2s_t in_a, size2s_t in_b) { return cmpgt_qf16_hf( in_b, in_a) ? in_b : in_a; } +//size4s_t max_qf32_sf( size4s_t in_a, size4s_t in_b) { return cmpgt_qf32_sf( in_b, in_a) ? in_b : in_a; } + +size4s_t min_qf32( size4s_t in_a, size4s_t in_b) { return cmpgt_qf32( in_a, in_b) ? in_b : in_a; } +size2s_t min_qf16( size2s_t in_a, size2s_t in_b) { return cmpgt_qf16( in_a, in_b) ? in_b : in_a; } + +size4s_t min_sf( size4s_t in_a, size4s_t in_b) { + if (is_check_zero_sf(in_a) && is_check_zero_sf(in_b) ) { + return (in_a == 0) ? in_b : in_a; + } + return cmpgt_sf( in_a, in_b) ? in_b : in_a; +} +size2s_t min_hf( size2s_t in_a, size2s_t in_b) { + if (is_check_zero_hf(in_a) && is_check_zero_hf(in_b) ) { + return (in_a == 0) ? in_b : in_a; + } + return cmpgt_hf( in_a, in_b) ? in_b : in_a; +} +//size2s_t min_qf16_hf( size2s_t in_a, size2s_t in_b) { return cmpgt_qf16_hf( in_a, in_b) ? in_b : in_a; } +//size4s_t min_qf32_sf( size4s_t in_a, size4s_t in_b) { return cmpgt_qf32_sf( in_a, in_b) ? in_b : in_a; } + + +size4s_t max_qf32_sf(size4s_t in_a, size4s_t in_b) +{ + size4s_t result=0; + unfloat a,b; + a= parse_qf32(in_a); + b= parse_sf(in_b); + if(b.sign) + b.sig = (-1)*b.sig; + + double a_d, b_d; + a_d = ldexp(a.sig, a.exp); + b_d = ldexp(b.sig, b.exp); + + if(a_d >= b_d) + result = in_a; + else + result = in_b; + +#ifdef DEBUG_MMVEC_QF + printf("[max_qf32_sf]a:%10.30f, b:%10.30f\n",a_d, b_d); +#endif + + return result; +} +size4s_t min_qf32_sf(size4s_t in_a, size4s_t in_b) +{ + size4s_t result=0; + unfloat a,b; + a= parse_qf32(in_a); + b= parse_sf(in_b); + if(b.sign) + b.sig = (-1)*b.sig; + double a_d, b_d; + a_d = ldexp(a.sig, a.exp); + b_d = ldexp(b.sig, b.exp); + if(a_d <= b_d) + result = in_a; + else + result = in_b; +#ifdef DEBUG_MMVEC_QF + printf("[min_qf32_sf]a:%10.30f, b:%10.30f\n",a_d, b_d); +#endif + return result; +} + +size2s_t max_qf16_hf(size2s_t in_a, size2s_t in_b) +{ + size2s_t result=0; + unfloat a,b; + a= parse_qf16(in_a); + b= parse_hf(in_b); + if(b.sign) + b.sig = (-1)*b.sig; + double a_d, b_d; + a_d = ldexp(a.sig, a.exp); + b_d = ldexp(b.sig, b.exp); + if(a_d >= b_d) + result = in_a; + else + result = in_b; +#ifdef DEBUG_MMVEC_QF + printf("[max_qf16_hf]a:%10.30f, b:%10.30f\n",a_d, b_d); +#endif + return result; +} +size2s_t min_qf16_hf(size2s_t in_a, size2s_t in_b) +{ + size2s_t result=0; + unfloat a,b; + a= parse_qf16(in_a); + b= parse_hf(in_b); + if(b.sign) + b.sig = (-1)*b.sig; + double a_d, b_d; + a_d = ldexp(a.sig, a.exp); + b_d = ldexp(b.sig, b.exp); + if(a_d <= b_d) + result = in_a; + else + result = in_b; +#ifdef DEBUG_MMVEC_QF + printf("[min_qf16_hf]a:%10.30f, b:%10.30f\n",a_d, b_d); +#endif + return result; +} diff --git a/target/hexagon/mmvec/mmvec_qfloat.h b/target/hexagon/mmvec/mmvec_qfloat.h new file mode 100644 index 000000000000..dc15cd17408b --- /dev/null +++ b/target/hexagon/mmvec/mmvec_qfloat.h @@ -0,0 +1,199 @@ +/* + * Copyright(c) 2019-2021 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * 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 . + */ + +#ifndef MMVEC_QFLOAT_H +#define MMVEC_QFLOAT_H 1 + +#define HF_MAX 131008 //pow(2,17)-pow(2,6) =(2-1.0/pow(2,10))*pow(2,16) +#define HF_MIN 1.0/pow(2,24) +#define SF_MAX pow(2,129)-pow(2,105) //(2-1.0/pow(2,23))*pow(2,128) +#define SF_MIN 1.0/pow(2,149) + +#define E_MAX_QF32 128 +#define E_MIN_QF32 -127 +#define E_MAX_QF16 16 +#define E_MIN_QF16 -15 +#define E_MAX_SF 128 +#define E_MIN_SF -126 +#define E_MAX_HF 16 +#define E_MIN_HF -14 +#define BIAS_QF32 127 +#define BIAS_QF16 15 +#define BIAS_DF 1023 +#define BIAS_SF 127 +#define BIAS_HF 15 +#define FRAC_HF 10 +#define FRAC_SF 23 +#define isNaNF32( a ) (((~(a) & 0x7F800000) == 0) && ((a) & 0x007FFFFF)) +#define isInfF32( a ) (((~(a) & 0x7F800000) == 0) && (((a) & 0x007FFFFF) == 0)) +#define isNaNF16( a ) (((~(a) & 0x7C00) == 0) && ((a) & 0x03FF)) +#define isInfF16( a ) (((~(a) & 0x7C00) == 0) && (((a) & 0x03FF) == 0)) + +//#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) +//#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) + +#include "cpu.h" +#include "hex_arch_types.h" + +#define epsilon 1.0/pow(2,23) +#define units 1.0*pow(2,23) +#define epsilon_hf 1.0/pow(2,10) +#define units_hf 1.0*pow(2,10) + +typedef struct{ + int sign; + int exp; + double sig; +} unfloat; //Un-Normalized Float + +typedef struct{ + int sign; + int sig; + int exp; +} qf_t; + +typedef struct{ + int32_t sig : 24; + uint32_t exp : 8; +} qf32_t; + +typedef struct{ + int32_t sig : 11; + uint32_t exp : 5; +} qf16_t; + +typedef enum float_type{ + QF32, + QF16, + SF, + HF +} f_type; + +typedef union { + float f; + size4u_t i; + struct { + size4u_t mant:23; + size4u_t exp:8; + size4u_t sign:1; + } x; +} sf_union; + +//MPY +size4s_t mpy_qf32(size4s_t a, size4s_t b); +size4s_t mpy_qf32_sf(size4s_t a, size4s_t b); +size4s_t mpy_qf32_mix_sf(size4s_t a, size4s_t b); +size2s_t mpy_qf16(size2s_t a, size2s_t b); +size2s_t mpy_qf16_hf(size2s_t a, size2s_t b); +size2s_t mpy_qf16_mix_hf(size2s_t a, size2s_t b); +size8s_t mpy_qf32_qf16(size4s_t a, size4s_t b); +size8s_t mpy_qf32_hf(size4s_t a, size4s_t b); +size8s_t mpy_qf32_mix_hf(size4s_t a, size4s_t b); + +unfloat parse_qf32(size4s_t a); +unfloat parse_qf16(size2s_t a); +unfloat parse_sf(size4s_t a); +unfloat parse_hf(size2s_t a); +size4s_t rnd_sat_qf32(int exp, double sig, double sig_low); +size2s_t rnd_sat_qf16(int exp, double sig, double sig_low); +size4s_t rnd_sat_sf(int exp, double sig); +size2s_t rnd_sat_hf(int exp, double sig); +size4s_t rnd_sat_w(int exp, double sig); +size4u_t rnd_sat_uw(int exp, double sig); +size2s_t rnd_sat_h(int exp, double sig); +size2u_t rnd_sat_uh(int exp, double sig); +size1s_t rnd_sat_b(int exp, double sig); +size1u_t rnd_sat_ub(int exp, double sig); +size4s_t negate32(size4s_t); +size2s_t negate16(size2s_t); +size4s_t negate_sf(size4s_t); +size2s_t negate_hf(size2s_t); + +//ADD +size4s_t add_qf32(size4s_t a, size4s_t b); +size4s_t add_sf(size4s_t a, size4s_t b); +size4s_t add_qf32_mix(size4s_t a, size4s_t b); +size2s_t add_qf16(size2s_t a, size2s_t b); +size2s_t add_hf(size2s_t a, size2s_t b); +size2s_t add_qf16_mix(size2s_t a, size2s_t b); + +//SUB +size4s_t sub_qf32(size4s_t a, size4s_t b); +size4s_t sub_sf(size4s_t a, size4s_t b); +size4s_t sub_qf32_mix(size4s_t a, size4s_t b); +size2s_t sub_qf16(size2s_t a, size2s_t b); +size2s_t sub_hf(size2s_t a, size2s_t b); +size2s_t sub_qf16_mix(size2s_t a, size2s_t b); + +//Convert +size4s_t conv_sf_qf32(size4s_t a); +size4s_t conv_sf_w(size4s_t a); +size4s_t conv_sf_uw(size4u_t a); +size2s_t conv_hf_qf16(size2s_t a); +size2s_t conv_hf_h(size2s_t a); +size2s_t conv_hf_uh(size2u_t a); +size4s_t conv_hf_qf32(size8s_t a); +size4s_t conv_hf_w(size8s_t a); +size4s_t conv_hf_uw(size8u_t a); + +size4s_t conv_w_qf32(size4s_t a); +size4u_t conv_uw_qf32(size4s_t a); +size2s_t conv_h_qf16(size2s_t a); +size2u_t conv_uh_qf16(size2s_t a); +size4s_t conv_h_qf32(size8s_t a); +size4u_t conv_uh_qf32(size8s_t a); +size2s_t conv_b_qf16(size4s_t a); +size2u_t conv_ub_qf16(size4s_t a); + +size4s_t conv_w_sf(size4s_t a); +// size4u_t conv_uw_sf(size4s_t a); +size2s_t conv_h_hf(size2s_t a); +// size2u_t conv_uh_sf(size2s_t a); + +//Neg/Abs +size4s_t neg_qf32(size4s_t a); +size4s_t abs_qf32(size4s_t a); +size2s_t neg_qf16(size2s_t a); +size2s_t abs_qf16(size2s_t a); +size4s_t neg_sf(size4s_t a); +size4s_t abs_sf(size4s_t a); +size2s_t neg_hf(size2s_t a); +size2s_t abs_hf(size2s_t a); + +//Compare +int cmpgt_fp(unfloat a, unfloat b); +int cmpgt_qf32(size4s_t a, size4s_t b); +int cmpgt_qf16(size2s_t a, size2s_t b); +int cmpgt_sf(size4s_t a, size4s_t b); +int cmpgt_hf(size2s_t a, size2s_t b); +int cmpgt_qf32_sf(size4s_t a, size4s_t b); +int cmpgt_qf16_hf(size2s_t a, size2s_t b); + +//max/min +size4s_t max_qf32(size4s_t a, size4s_t b); +size4s_t min_qf32(size4s_t a, size4s_t b); +size4s_t max_qf32_sf(size4s_t a, size4s_t b); +size4s_t min_qf32_sf(size4s_t a, size4s_t b); +size4s_t max_sf(size4s_t a, size4s_t b); +size4s_t min_sf(size4s_t a, size4s_t b); +size2s_t max_qf16(size2s_t a, size2s_t b); +size2s_t min_qf16(size2s_t a, size2s_t b); +size2s_t max_qf16_hf(size2s_t a, size2s_t b); +size2s_t min_qf16_hf(size2s_t a, size2s_t b); +size2s_t max_hf(size2s_t a, size2s_t b); +size2s_t min_hf(size2s_t a, size2s_t b); +#endif diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 7a83e8975031..e6f11fd5f990 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -32,6 +32,7 @@ #include "fma_emu.h" #include "mmvec/mmvec.h" #include "mmvec/macros.h" +#include "mmvec/mmvec_qfloat.h" #include "op_helper.h" #include "cpu_helper.h" #include "translate.h" @@ -2012,4 +2013,5 @@ uint64_t HELPER(creg_read_pair)(CPUHexagonState *env, uint32_t reg) #define BOGUS_HELPER(tag) \ printf("ERROR: bogus helper: " #tag "\n") +#include "mmvec/kvx_ieee.h" #include "helper_funcs_generated.c.inc" From 0105db05c37e11883df5583168fb613ef3a90bc8 Mon Sep 17 00:00:00 2001 From: Marco Liebel Date: Thu, 27 Feb 2025 12:00:02 -0800 Subject: [PATCH 1172/1179] target/hexagon: Add macro imports The number of parameters for `DEF_MACRO` changed and needed to be updated too. Signed-off-by: Marco Liebel --- target/hexagon/gen_semantics.c | 2 +- target/hexagon/imported/mmvec/macros.def | 955 ++++++++++++++++++++--- target/hexagon/mmvec/macros.h | 13 + 3 files changed, 844 insertions(+), 126 deletions(-) diff --git a/target/hexagon/gen_semantics.c b/target/hexagon/gen_semantics.c index 4a2bdd70e9cc..ed66ae4ec241 100644 --- a/target/hexagon/gen_semantics.c +++ b/target/hexagon/gen_semantics.c @@ -106,7 +106,7 @@ int main(int argc, char *argv[]) /* * Process the macros for HVX */ -#define DEF_MACRO(MNAME, BEH, ATTRS) \ +#define DEF_MACRO(MNAME, PARAMS, SDESC, LDESC, BEH, ATTRS) \ fprintf(outfile, "MACROATTRIB( \\\n" \ " \"%s\", \\\n" \ " \"\"\"%s\"\"\", \\\n" \ diff --git a/target/hexagon/imported/mmvec/macros.def b/target/hexagon/imported/mmvec/macros.def index 7e5438a99802..e9524aa56d1e 100755 --- a/target/hexagon/imported/mmvec/macros.def +++ b/target/hexagon/imported/mmvec/macros.def @@ -15,46 +15,76 @@ * along with this program; if not, see . */ -DEF_MACRO(fDUMPQ, +DEF_MACRO(fDUMPQ,(STR,REG), + "dump REG", + "dump REG", do { printf(STR ":" #REG ": 0x%016llx\n",REG.ud[0]); } while (0), () ) -DEF_MACRO(fUSE_LOOKUP_ADDRESS_BY_REV, - PROC->arch_proc_options->mmvec_use_full_va_for_lookup, +DEF_MACRO(fUSE_LOOKUP_ADDRESS_BY_REV,(PROC), + "", + "Use full VA address for lookup and exception based on REV ", + PROC->arch_proc_options->HVX_USE_FULL_VA, () ) -DEF_MACRO(fUSE_LOOKUP_ADDRESS, +DEF_MACRO(fUSE_LOOKUP_ADDRESS,(), + "", + "Use full VA address for lookup and exception", 1, () ) -DEF_MACRO(fNOTQ, +DEF_MACRO(fRT8NOTE, (), + "", + "", + , + (A_NOTE_RT8) +) + +DEF_MACRO(fCVI_VX_NO_TMP_LD, (), + "", + "", + , + (A_CVI_VX_NO_TMP_LD) +) +DEF_MACRO(fNOTQ,(VAL), + "~VAL", + "~VAL", + /* Will break Visual Studio? */ ({mmqreg_t _ret = {0}; int _i_; for (_i_ = 0; _i_ < fVECSIZE()/64; _i_++) _ret.ud[_i_] = ~VAL.ud[_i_]; _ret;}), () ) -DEF_MACRO(fGETQBITS, +DEF_MACRO(fGETQBITS,(REG,WIDTH,MASK,BITNO), + "REG[BITNO+WIDTH-1:BITNO]", + "Get MASK bits at BITNO from REG", ((MASK) & (REG.w[(BITNO)>>5] >> ((BITNO) & 0x1f))), () ) -DEF_MACRO(fGETQBIT, +DEF_MACRO(fGETQBIT,(REG,BITNO), + "REG[BITNO]", + "Get bit BITNO from REG", fGETQBITS(REG,1,1,BITNO), () ) -DEF_MACRO(fGENMASKW, +DEF_MACRO(fGENMASKW,(QREG,IDX), + "maskw(QREG,IDX)", + "Generate mask from QREG for word IDX", (((fGETQBIT(QREG,(IDX*4+0)) ? 0xFF : 0x0) << 0) |((fGETQBIT(QREG,(IDX*4+1)) ? 0xFF : 0x0) << 8) |((fGETQBIT(QREG,(IDX*4+2)) ? 0xFF : 0x0) << 16) |((fGETQBIT(QREG,(IDX*4+3)) ? 0xFF : 0x0) << 24)), () ) -DEF_MACRO(fGET10BIT, +DEF_MACRO(fGET10BIT,(COE,VAL,POS), + "COE=(((((fGETUBYTE(3,VAL) >> (2 * POS)) & 3) << 8) | fGETUBYTE(POS,VAL)) << 6) >> 6;", + "Get 10-bit coefficient from current word value and byte position", { COE = (((((fGETUBYTE(3,VAL) >> (2 * POS)) & 3) << 8) | fGETUBYTE(POS,VAL)) << 6); COE >>= 6; @@ -62,62 +92,160 @@ DEF_MACRO(fGET10BIT, () ) -DEF_MACRO(fVMAX, +DEF_MACRO(fVMAX,(X,Y), + "max(X,Y)", + "", (X>Y) ? X : Y, () ) -DEF_MACRO(fGETNIBBLE, +DEF_MACRO(fREAD_VEC, + (DST,IDX), + "DST=VREG[IDX]", /* short desc */ + "Read Vector IDX", /* long desc */ + (DST = READ_VREG(fMODCIRCU((IDX),5))), + () +) +DEF_MACRO(fREAD_ZVEC, + (DST,IDX), + "DST=ZREG[IDX]", /* short desc */ + "Read Vector IDX", /* long desc */ + (DST = READ_ZREG(fMODCIRCU((IDX),5))), + () +) + +DEF_MACRO(fREAD_ZVEC_WORD, + (DST,IDX), + "DST=ZReg.uw[IDX]", /* short desc */ + "Read Z Vector IDX", /* long desc */ + { + mmvector_t ZReg = READ_ZREG(0); + DST = ZReg.uw[IDX]; + + }, + () +) +DEF_MACRO(fREAD_ZVEC_ALL, + (DST,N,NZ), + "", /* short desc */ + "Read Z Vector IDX", /* long desc */ + { + int __idx = 0; + for (__idx = 0; __idx < NZ/N; __idx++) { + memcpy(&DST[N*__idx], &THREAD2STRUCT->ZRegs[__idx], N); + } + }, + () +) +DEF_MACRO(fZREGB, + (Z,IDX), + "ZREG.b[IDX]", /* short desc */ + "Read Z IDX", /* long desc */ + ((size1s_t)Z[IDX]), + () +) +DEF_MACRO(fZREGUB, + (Z,IDX), + "ZREG.ub[IDX]", /* short desc */ + "Read Z IDX", /* long desc */ + ((size1u_t)Z[IDX]), + () +) +DEF_MACRO(fZREGH, + (Z,IDX), + "ZREG.h[IDX]", /* short desc */ + "Read Z IDX", /* long desc */ + ((size2s_t)Z[IDX]), + () +) +DEF_MACRO(fZREGUB, + (Z,IDX), + "ZREG.ub[IDX]", /* short desc */ + "Read Z IDX", /* long desc */ + ((size1u_t)Z[IDX]), + () +) + +DEF_MACRO(fGETNIBBLE,(IDX,SRC), + "SRC.s4[IDX]", + "Get nibble", ( fSXTN(4,8,(SRC >> (4*IDX)) & 0xF) ), () ) -DEF_MACRO(fGETCRUMB, +DEF_MACRO(fGETCRUMB,(IDX,SRC), + "SRC.s2[IDX]", + "Get 2bits", ( fSXTN(2,8,(SRC >> (2*IDX)) & 0x3) ), () ) -DEF_MACRO(fGETCRUMB_SYMMETRIC, +DEF_MACRO(fGETCRUMB_SYMMETRIC,(IDX,SRC), + "SRC.s2[IDX] >= 0 ? (2-SRC.s2[IDX]) : SRC.s2[IDX]", + "Get 2bits", ( (fGETCRUMB(IDX,SRC)>=0 ? (2-fGETCRUMB(IDX,SRC)) : fGETCRUMB(IDX,SRC) ) ), () ) +//#define ZERO_OFFSET_2B +(fGETCRUMB(z,VuV.uw[i])>=0) #define ZERO_OFFSET_2B + -DEF_MACRO(fGENMASKH, +DEF_MACRO(fWRITE_VEC, + (IDX,VAR), + "VREG[IDX]=VAR", /* short desc */ + "Write Vector IDX", /* long desc */ + (WRITE_VREG(fMODCIRCU((IDX),5),VAR)), + () +) + +DEF_MACRO(fGENMASKH,(QREG,IDX), + "maskh(QREG,IDX)", + "generate mask from QREG for halfword IDX", (((fGETQBIT(QREG,(IDX*2+0)) ? 0xFF : 0x0) << 0) |((fGETQBIT(QREG,(IDX*2+1)) ? 0xFF : 0x0) << 8)), () ) -DEF_MACRO(fGETMASKW, +DEF_MACRO(fGETMASKW,(VREG,QREG,IDX), + "VREG.w[IDX] & fGENMASKW(QREG,IDX)", + "Mask word IDX from VREG using QREG", (VREG.w[IDX] & fGENMASKW((QREG),IDX)), () ) -DEF_MACRO(fGETMASKH, +DEF_MACRO(fGETMASKH,(VREG,QREG,IDX), + "VREG.h[IDX] & fGENMASKH(QREG,IDX)", + "Mask word IDX from VREG using QREG", (VREG.h[IDX] & fGENMASKH((QREG),IDX)), () ) -DEF_MACRO(fCONDMASK8, +DEF_MACRO(fCONDMASK8,(QREG,IDX,YESVAL,NOVAL), + "QREG.IDX ? YESVAL : NOVAL", + "QREG.IDX ? YESVAL : NOVAL", (fGETQBIT(QREG,IDX) ? (YESVAL) : (NOVAL)), () ) -DEF_MACRO(fCONDMASK16, +DEF_MACRO(fCONDMASK16,(QREG,IDX,YESVAL,NOVAL), + "select_bytes(QREG,IDX,YESVAL,NOVAL)", + "select_bytes(QREG,IDX,YESVAL,NOVAL)", ((fGENMASKH(QREG,IDX) & (YESVAL)) | (fGENMASKH(fNOTQ(QREG),IDX) & (NOVAL))), () ) -DEF_MACRO(fCONDMASK32, +DEF_MACRO(fCONDMASK32,(QREG,IDX,YESVAL,NOVAL), + "select_bytes(QREG,IDX,YESVAL,NOVAL)", + "select_bytes(QREG,IDX,YESVAL,NOVAL)", ((fGENMASKW(QREG,IDX) & (YESVAL)) | (fGENMASKW(fNOTQ(QREG),IDX) & (NOVAL))), () ) -DEF_MACRO(fSETQBITS, +DEF_MACRO(fSETQBITS,(REG,WIDTH,MASK,BITNO,VAL), + "REG[BITNO+WIDTH-1:BITNO] = VAL", + "Put bits into REG", do { size4u_t __TMP = (VAL); REG.w[(BITNO)>>5] &= ~((MASK) << ((BITNO) & 0x1f)); @@ -126,58 +254,101 @@ DEF_MACRO(fSETQBITS, () ) -DEF_MACRO(fSETQBIT, +DEF_MACRO(fSETQBIT,(REG,BITNO,VAL), + "REG[BITNO]=VAL", + "Put bit into REG", fSETQBITS(REG,1,1,BITNO,VAL), () ) -DEF_MACRO(fVBYTES, +DEF_MACRO(fVBYTES,(), + "VWIDTH", + "Number of bytes in a vector", (fVECSIZE()), () ) -DEF_MACRO(fVHALVES, +DEF_MACRO(fVHALVES,(), + "VWIDTH/2", + "Number of halves in a vector", (fVECSIZE()/2), () ) -DEF_MACRO(fVWORDS, +DEF_MACRO(fVWORDS,(), + "VWIDTH/2", + "Number of words in a vector", (fVECSIZE()/4), () ) -DEF_MACRO(fVDWORDS, +DEF_MACRO(fVDWORDS,(), + "VWIDTH/8", + "Number of double words in a vector", (fVECSIZE()/8), () ) -DEF_MACRO(fVALIGN, +DEF_MACRO(fVALIGN, (ADDR, LOG2_ALIGNMENT), + "ADDR = ADDR & ~(LOG2_ALIGNMENT-1)", + "Align to Element Size", ( ADDR = ADDR & ~(LOG2_ALIGNMENT-1)), () ) -DEF_MACRO(fVLASTBYTE, +DEF_MACRO(fVLASTBYTE, (ADDR, LOG2_ALIGNMENT), + "ADDR = ADDR | (LOG2_ALIGNMENT-1)", + "Set LSB of length to last byte", ( ADDR = ADDR | (LOG2_ALIGNMENT-1)), () ) -DEF_MACRO(fVELEM, +DEF_MACRO(fVELEM, (WIDTH), + "VBITS/WIDTH", + "Number of WIDTH-bit elements in a vector", ((fVECSIZE()*8)/WIDTH), () ) -DEF_MACRO(fVECLOGSIZE, +DEF_MACRO(fVECLOGSIZE,(), + "log2(VECTOR_SIZE)", + "Log base 2 of the number of bytes in a vector", (mmvec_current_veclogsize(thread)), () ) -DEF_MACRO(fVECSIZE, +DEF_MACRO(fVBUF_IDX,(EA), + "(EA >> log2(VECTOR_SIZE)) & 0xFF", + "(EA >> log2(VECTOR_SIZE)) & 0xFF", + (((EA) >> fVECLOGSIZE()) & 0xFF), + (A_FAKEINSN) +) + +DEF_MACRO(fREAD_VBUF,(IDX,WIDX), + "vbuf[IDX].w[WIDX]", + "vbuf[IDX].w[WIDX]", + READ_VBUF(IDX,WIDX), + (A_FAKEINSN) +) + +DEF_MACRO(fLOG_VBUF,(IDX,VAL,WIDX), + "vbuf[IDX].w[WIDX] = VAL", + "vbuf[IDX].w[WIDX] = VAL", + LOG_VBUF(IDX,VAL,WIDX), + (A_FAKEINSN) +) + +DEF_MACRO(fVECSIZE,(), + "VBYTES", + "Number of bytes in a vector currently", (1<VRegs_updated & (((VRegMask)1)<future_VRegs[VNUM] : mmvec_zero_vector()), (A_DOTNEWVALUE,A_RESTRICT_SLOT0ONLY) ) DEF_MACRO( fV_AL_CHECK, + (EA,MASK), + "", + "", if ((EA) & (MASK)) { warn("aligning misaligned vector. PC=%08x EA=%08x",thread->Regs[REG_PC],(EA)); }, () ) -DEF_MACRO(fSCATTER_INIT, +DEF_MACRO(fSCATTER_INIT, ( REGION_START, LENGTH, ELEMENT_SIZE), + "", + "", { mem_vector_scatter_init(thread, insn, REGION_START, LENGTH, ELEMENT_SIZE); if (EXCEPTION_DETECTED) return; }, - (A_STORE,A_MEMLIKE,A_RESTRICT_SLOT0ONLY) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST,A_RESTRICT_SLOT0ONLY) ) -DEF_MACRO(fGATHER_INIT, +DEF_MACRO(fGATHER_INIT, ( REGION_START, LENGTH, ELEMENT_SIZE), + "", + "", { mem_vector_gather_init(thread, insn, REGION_START, LENGTH, ELEMENT_SIZE); if (EXCEPTION_DETECTED) return; }, - (A_LOAD,A_MEMLIKE,A_RESTRICT_SLOT1ONLY) + (A_LOAD,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fSCATTER_FINISH, +DEF_MACRO(fSCATTER_FINISH, (OP), + "", + "", { if (EXCEPTION_DETECTED) return; mem_vector_scatter_finish(thread, insn, OP); @@ -229,7 +413,9 @@ DEF_MACRO(fSCATTER_FINISH, () ) -DEF_MACRO(fGATHER_FINISH, +DEF_MACRO(fGATHER_FINISH, (), + "", + "", { if (EXCEPTION_DETECTED) return; mem_vector_gather_finish(thread, insn); @@ -238,7 +424,9 @@ DEF_MACRO(fGATHER_FINISH, ) -DEF_MACRO(CHECK_VTCM_PAGE, +DEF_MACRO(CHECK_VTCM_PAGE, (FLAG, BASE, LENGTH, OFFSET, ALIGNMENT), + "FLAG=((BASE+OFFSET) < (BASE+LENGTH))", + "FLAG=((BASE+OFFSET) < (BASE+LENGTH))", { int slot = insn->slot; paddr_t pa = thread->mem_access[slot].paddr+OFFSET; @@ -247,7 +435,9 @@ DEF_MACRO(CHECK_VTCM_PAGE, }, () ) -DEF_MACRO(COUNT_OUT_OF_BOUNDS, +DEF_MACRO(COUNT_OUT_OF_BOUNDS, (FLAG, SIZE), + " ", + "", { if (!FLAG) { @@ -258,7 +448,9 @@ DEF_MACRO(COUNT_OUT_OF_BOUNDS, () ) -DEF_MACRO(fLOG_SCATTER_OP, +DEF_MACRO(fLOG_SCATTER_OP, (SIZE), + " ", + " ", { // Log the size and indicate that the extension ext.c file needs to increment right before memory write THREAD2STRUCT->vtcm_log.op = 1; @@ -269,7 +461,9 @@ DEF_MACRO(fLOG_SCATTER_OP, -DEF_MACRO(fVLOG_VTCM_WORD_INCREMENT, +DEF_MACRO(fVLOG_VTCM_WORD_INCREMENT, (EA,OFFSET,INC,IDX,ALIGNMENT,LEN), + "if (RtV <= EA <= RtV + LEN) *EA += INC.uw[IDX] ", + "if (RtV <= EA <= RtV + LEN) *EA += INC.uw[IDX] ", { int slot = insn->slot; int log_bank = 0; @@ -287,7 +481,9 @@ DEF_MACRO(fVLOG_VTCM_WORD_INCREMENT, () ) -DEF_MACRO(fVLOG_VTCM_HALFWORD_INCREMENT, +DEF_MACRO(fVLOG_VTCM_HALFWORD_INCREMENT, (EA,OFFSET,INC,IDX,ALIGNMENT,LEN), + "if (RtV <= EA <= RtV + LEN) *EA += INC.uh[IDX] ", + "if (RtV <= EA <= RtV + LEN) *EA += INC.uh[IDX] ", { int slot = insn->slot; int log_bank = 0; @@ -304,7 +500,9 @@ DEF_MACRO(fVLOG_VTCM_HALFWORD_INCREMENT, () ) -DEF_MACRO(fVLOG_VTCM_HALFWORD_INCREMENT_DV, +DEF_MACRO(fVLOG_VTCM_HALFWORD_INCREMENT_DV, (EA,OFFSET,INC,IDX,IDX2,IDX_H,ALIGNMENT,LEN), + "if (RtV <= EA <= RtV + LEN) *EA += INC.w[IDX2].uh[IDX_H] ", + "if (RtV <= EA <= RtV + LEN) *EA += INC.w[IDX2].uh[IDX_H] ", { int slot = insn->slot; int log_bank = 0; @@ -323,7 +521,9 @@ DEF_MACRO(fVLOG_VTCM_HALFWORD_INCREMENT_DV, -DEF_MACRO(GATHER_FUNCTION, +DEF_MACRO(GATHER_FUNCTION, (EA,OFFSET,IDX, LEN, ELEMENT_SIZE, BANK_IDX, QVAL), +"", +"", { int slot = insn->slot; int i0; @@ -336,6 +536,9 @@ DEF_MACRO(GATHER_FUNCTION, log_byte = ((OFFSET>=0)&&((pa+i0)<=pa_high)) && QVAL; log_bank |= (log_byte<system_ptr, thread->threadId, thread->mem_access[slot].paddr+OFFSET+i0); +#ifdef VERIFICATION + warn("Gather[%d] sim_mem_read1 pa:%llx val: %x", ELEMENT_SIZE*IDX+i0, thread->mem_access[slot].paddr+OFFSET+i0, B); +#endif THREAD2STRUCT->tmp_VRegs[0].ub[ELEMENT_SIZE*IDX+i0] = B; LOG_VTCM_BYTE(pa+i0,log_byte,B,ELEMENT_SIZE*IDX+i0); } @@ -346,38 +549,50 @@ DEF_MACRO(GATHER_FUNCTION, -DEF_MACRO(fVLOG_VTCM_GATHER_WORD, +DEF_MACRO(fVLOG_VTCM_GATHER_WORD, (EA,OFFSET,IDX, LEN), + "if (RtV <= EA <= RtV + LEN) TEMP.uw[IDX] = *EA ", + "if (RtV <= EA <= RtV + LEN) TEMP.uw[IDX] = *EA ", { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 4, IDX, 1); }, () ) -DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORD, +DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORD, (EA,OFFSET,IDX, LEN), + " if (RtV <= EA <= RtV + LEN) TEMP.uh[IDX] = *EA ", + " if (RtV <= EA <= RtV + LEN) TEMP.uh[IDX] = *EA ", { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, IDX, 1); }, () ) -DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORD_DV, +DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORD_DV, (EA,OFFSET,IDX,IDX2,IDX_H, LEN), + "if (RtV <= EA <= RtV + LEN) TEMP.uw[IDX2].uh[IDX_H] = *EA ", + "if (RtV <= EA <= RtV + LEN) TEMP.uw[IDX2].uh[IDX_H] = *EA ", { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), 1); }, () ) -DEF_MACRO(fVLOG_VTCM_GATHER_WORDQ, +DEF_MACRO(fVLOG_VTCM_GATHER_WORDQ, (EA,OFFSET,IDX, Q, LEN), + " if ( (RtV <= EA <= RtV + LEN) & Q) TEMP.uw[IDX] = *EA ", + " if ( (RtV <= EA <= RtV + LEN) & Q) TEMP.uw[IDX] = *EA ", { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 4, IDX, fGETQBIT(QsV,4*IDX+i0)); }, () ) -DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORDQ, +DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORDQ, (EA,OFFSET,IDX, Q, LEN), + " if ( (RtV <= EA <= RtV + LEN) & Q) TEMP.uh[IDX] = *EA ", + " if ( (RtV <= EA <= RtV + LEN) & Q) TEMP.uh[IDX] = *EA ", { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, IDX, fGETQBIT(QsV,2*IDX+i0)); }, () ) -DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORDQ_DV, +DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORDQ_DV, (EA,OFFSET,IDX,IDX2,IDX_H, Q, LEN), + " if ( (RtV <= EA <= RtV + LEN) & Q) TEMP.uw[IDX2].uh[IDX_H] = *EA ", + " if ( (RtV <= EA <= RtV + LEN) & Q) TEMP.uw[IDX2].uh[IDX_H] = *EA ", { GATHER_FUNCTION(EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), fGETQBIT(QsV,2*IDX+i0)); }, @@ -385,7 +600,9 @@ DEF_MACRO(fVLOG_VTCM_GATHER_HALFWORDQ_DV, ) -DEF_MACRO(DEBUG_LOG_ADDR, +DEF_MACRO(DEBUG_LOG_ADDR, (OFFSET), + " ", + " ", { if (thread->processor_ptr->arch_proc_options->mmvec_network_addr_log2) @@ -393,6 +610,7 @@ DEF_MACRO(DEBUG_LOG_ADDR, int slot = insn->slot; paddr_t pa = thread->mem_access[slot].paddr+OFFSET; + // pa = pa & ~(ALIGNMENT-1); } }, () @@ -404,7 +622,9 @@ DEF_MACRO(DEBUG_LOG_ADDR, -DEF_MACRO(SCATTER_OP_WRITE_TO_MEM, +DEF_MACRO(SCATTER_OP_WRITE_TO_MEM, (TYPE), + " Read, accumulate, and write to VTCM", + " ", { for (int i = 0; i < mmvecx->vtcm_log.size; i+=sizeof(TYPE)) { @@ -430,7 +650,9 @@ DEF_MACRO(SCATTER_OP_WRITE_TO_MEM, () ) -DEF_MACRO(SCATTER_FUNCTION, +DEF_MACRO(SCATTER_FUNCTION, (EA,OFFSET,IDX, LEN, ELEMENT_SIZE, BANK_IDX, QVAL, IN), +"", +"", { int slot = insn->slot; int i0; @@ -449,26 +671,34 @@ DEF_MACRO(SCATTER_FUNCTION, () ) -DEF_MACRO(fVLOG_VTCM_HALFWORD, +DEF_MACRO(fVLOG_VTCM_HALFWORD, (EA,OFFSET,IN,IDX, LEN), + "if (RtV <= EA <= RtV + LEN) *EA = IN.uh[IDX] ", + "if (RtV <= EA <= RtV + LEN) *EA = IN.uh[IDX] ", { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, IDX, 1, IN); }, () ) -DEF_MACRO(fVLOG_VTCM_WORD, +DEF_MACRO(fVLOG_VTCM_WORD, (EA,OFFSET,IN,IDX,LEN), + "if (RtV <= EA <= RtV + LEN) *EA = IN.uw[IDX] ", + "if (RtV <= EA <= RtV + LEN) *EA = IN.uw[IDX] ", { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 4, IDX, 1, IN); }, () ) -DEF_MACRO(fVLOG_VTCM_HALFWORDQ, +DEF_MACRO(fVLOG_VTCM_HALFWORDQ, (EA,OFFSET,IN,IDX,Q,LEN), + " if ( (RtV <= EA <= RtV + LEN) & Q) *EA = IN.uh[IDX] ", + " if ( (RtV <= EA <= RtV + LEN) & Q) *EA = IN.uh[IDX] ", { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, IDX, fGETQBIT(QsV,2*IDX+i0), IN); }, () ) -DEF_MACRO(fVLOG_VTCM_WORDQ, +DEF_MACRO(fVLOG_VTCM_WORDQ, (EA,OFFSET,IN,IDX,Q,LEN), + " if ( (RtV <= EA <= RtV + LEN) & Q) *EA = IN.uw[IDX] ", + " if ( (RtV <= EA <= RtV + LEN) & Q) *EA = IN.uw[IDX] ", { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 4, IDX, fGETQBIT(QsV,4*IDX+i0), IN); }, @@ -479,14 +709,18 @@ DEF_MACRO(fVLOG_VTCM_WORDQ, -DEF_MACRO(fVLOG_VTCM_HALFWORD_DV, +DEF_MACRO(fVLOG_VTCM_HALFWORD_DV, (EA,OFFSET,IN,IDX,IDX2,IDX_H, LEN), + "if (RtV <= EA <= RtV + LEN) *EA = IN.w[IDX2].uh[IDX_H] ", + "if (RtV <= EA <= RtV + LEN) *EA = IN.w[IDX2].uh[IDX_H] ", { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), 1, IN); }, () ) -DEF_MACRO(fVLOG_VTCM_HALFWORDQ_DV, +DEF_MACRO(fVLOG_VTCM_HALFWORDQ_DV, (EA,OFFSET,IN,IDX,Q,IDX2,IDX_H, LEN), + " if ( (RtV <= EA <= RtV + LEN) & Q) *EA = IN.w[IDX2].uh[IDX_H] ", + " if ( (RtV <= EA <= RtV + LEN) & Q) *EA = IN.w[IDX2].uh[IDX_H] ", { SCATTER_FUNCTION (EA,OFFSET,IDX, LEN, 2, (2*IDX2+IDX_H), fGETQBIT(QsV,2*IDX+i0), IN); }, @@ -498,39 +732,161 @@ DEF_MACRO(fVLOG_VTCM_HALFWORDQ_DV, -DEF_MACRO(fSTORERELEASE, +DEF_MACRO(fSTORERELEASE, (EA,TYPE), + "char* addr = EA&~(ALIGNMENT-1); Zero Byte Store Release (Non-blocking Sync)", + "Zero Byte Store Release (Sync)", { fV_AL_CHECK(EA,fVECSIZE()-1); mem_store_release(thread, insn, fVECSIZE(), EA&~(fVECSIZE()-1), EA, TYPE, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); }, - (A_STORE,A_MEMLIKE) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fVFETCH_AL, +DEF_MACRO(fVFETCH_AL, (EA), + "Prefetch vector into L2 cache at EA", + "Prefetch vector into L2 cache at EA", { fV_AL_CHECK(EA,fVECSIZE()-1); mem_fetch_vector(thread, insn, EA&~(fVECSIZE()-1), insn->slot, fVECSIZE()); }, - (A_LOAD,A_MEMLIKE) + (A_LOAD,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST,A_RESTRICT_NOSLOT1_STORE) ) -DEF_MACRO(fLOADMMV_AL, +DEF_MACRO(fLOADMMV_AL, (EA, ALIGNMENT, LEN, DST), + "char* addr = EA&~(ALIGNMENT-1); for (i=0; ilast_pkt->double_access_vec = 0; mem_load_vector_oddva(thread, insn, EA&~(ALIGNMENT-1), EA, insn->slot, LEN, &DST.ub[0], LEN, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); }, - (A_LOAD,A_MEMLIKE) + (A_LOAD,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST,A_RESTRICT_NOSLOT1_STORE) ) -DEF_MACRO(fLOADMMV, +DEF_MACRO(fLOADMMV, (EA, DST), + "DST = *(EA&~(ALIGNMENT-1))", + "Load vector from memory at EA (forced alignment) to DST.", fLOADMMV_AL(EA,fVECSIZE(),fVECSIZE(),DST), () ) -DEF_MACRO(fLOADMMVQ, +DEF_MACRO(fLOADMMZ, (EA,DST), + "DST[EA[7]] = *(EA)", + "Load splatter register from memory at EA (forced alignment) to DST.", + { + mmvector_t load_vec; + fV_AL_CHECK(EA,fVECSIZE()-1); + mem_load_vector_oddva(thread, insn, EA&~(fVECSIZE()-1), EA, insn->slot, fVECSIZE(), &load_vec.ub[0], fVECSIZE(), fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + int idx = (EA & 0x80)>0; + DST.v[idx] = load_vec; + + }, + () +) +DEF_MACRO(fLOADZ_LOAD, (EA,EAU,WIDTH,DST), + "", + "", + { + thread->last_pkt->ext_slot_cancelled = 0; + thread->last_pkt->double_access_vec = 0; + int etm_size = ((EA % width) ==0) ? fVECSIZE() : 0; + if (thread->processor_ptr->options->testgen_mode) + etm_size = ((EA % width) ==0) ? WIDTH : 0; + + mem_load_vector_oddva(thread, insn, EA, EAU, insn->slot, WIDTH, &DST.ub[0], etm_size, fUSE_LOOKUP_ADDRESS()); + }, + (A_LOAD,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST,A_RESTRICT_NOSLOT1_STORE) +) + +DEF_MACRO(fELSE_CANCELZ, (), + "", + "", + else { + if (thread->last_pkt) { + thread->mem_access[insn->slot].dropped_z = 1; + thread->last_pkt->ext_slot_cancelled |= (1<slot); + } + }, + (A_LOAD,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST,A_RESTRICT_NOSLOT1_STORE) +) + + + + +DEF_MACRO(fPOST_INC4, (R), + "R+=4", + "", + R+=4; + , + (A_CVI_Z_INC_4) +) +DEF_MACRO(fPOST_INC8, (R), + "R+=8", + "", + R+=8; + , + (A_CVI_Z_INC_8) +) +DEF_MACRO(fPOST_INC16, (R), + "R+=16", + "", + R+=16; + , + (A_CVI_Z_INC_16) +) + +DEF_MACRO(fEXTRACTZ, + (DST,IDX), + "DST=ZREG[IDX]", /* short desc */ + "Read Vector IDX", /* long desc */ + (DST = READ_ZREG(fMODCIRCU((IDX),5))), + () +) + +DEF_MACRO(fLOADZ_UPDATE, (EA,WIDTH,ZN,N,SRC), + "for(i = 0; i < width; i++) ZREG.b[(EA+i)%ZN] = *(EA+i)", + "Load splatter register from memory at EA (forced alignment) to DST.", + { + mmvector_t Z[2]; + Z[0] = READ_ZREG(0); + Z[1] = READ_ZREG(1); + for(int k = 0; k < WIDTH; k++) { + int element_idx = (EA+k)%N; + int z_idx = ((EA+k)%ZN)/N; + Z[z_idx].ub[element_idx] = SRC.ub[k]; + } + + WRITE_EXT_ZREG(0,Z[0],0); + WRITE_EXT_ZREG(1,Z[1],0); + }, + (A_LOAD,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST,A_RESTRICT_NOSLOT1_STORE) +) +DEF_MACRO(fSTOREZ, (EA,WIDTH,ZN,N), + "for(i = 0; i < width; i++) *(EA+i) = ZREG.b[(EA+i)%ZN]", + "Store splatter register from memory at EA (forced alignment) to DST.", + { + mmvector_t store_vec; + mmvector_t maskvec = {0}; + mmvector_t Z[2]; + Z[0] = READ_ZREG(0); + Z[1] = READ_ZREG(1); + + for(int k = 0; k < WIDTH; k++) { + int element_idx = (EA+k)%N; + int z_idx = ((EA+k)%ZN)/N; + store_vec.ub[k] = Z[z_idx].ub[element_idx]; + maskvec.ub[k] = 1; + } + mem_store_vector_oddva(thread, insn, EA, EA, insn->slot, WIDTH, &store_vec.ub[0], &maskvec.ub[0], 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + }, + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) +) + +DEF_MACRO(fLOADMMVQ, (EA,DST,QVAL), + "DST = vmux(QVAL,*(EA&~(ALIGNMENT-1)),0)", + "Load vector from memory at EA (forced alignment) to DST.", do { int __i; fLOADMMV_AL(EA,fVECSIZE(),fVECSIZE(),DST); @@ -539,7 +895,9 @@ DEF_MACRO(fLOADMMVQ, () ) -DEF_MACRO(fLOADMMVNQ, +DEF_MACRO(fLOADMMVNQ, (EA,DST,QVAL), + "DST = vmux(QVAL,0,*(EA&~(ALIGNMENT-1)))", + "Load vector from memory at EA (forced alignment) to DST.", do { int __i; fLOADMMV_AL(EA,fVECSIZE(),fVECSIZE(),DST); @@ -548,7 +906,9 @@ DEF_MACRO(fLOADMMVNQ, () ) -DEF_MACRO(fLOADMMVU_AL, +DEF_MACRO(fLOADMMVU_AL, (EA, ALIGNMENT, LEN, DST), + "char* addr = EA; for (i=0; iprocessor_ptr)); }, - (A_LOAD,A_MEMLIKE) + (A_LOAD,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST,A_RESTRICT_NOSLOT1_STORE) ) -DEF_MACRO(fLOADMMVU, +DEF_MACRO(fLOADMMVU, (EA, DST), + "DST = *EA", + "Load vector from memory at EA (unaligned) to DST.", { /* if address happens to be aligned, only do aligned load */ thread->last_pkt->pkt_has_vtcm_access = 0; @@ -579,63 +941,79 @@ DEF_MACRO(fLOADMMVU, () ) -DEF_MACRO(fSTOREMMV_AL, +DEF_MACRO(fSTOREMMV_AL, (EA, ALIGNMENT, LEN, SRC), + "char* addr = EA&~(ALIGNMENT-1); for (i=0; islot, LEN, &SRC.ub[0], 0, 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + mem_store_vector_oddva(thread, insn, EA&~(ALIGNMENT-1), EA, insn->slot, LEN, SRC.ub, 0, 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); }, - (A_STORE,A_MEMLIKE) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fSTOREMMV, +DEF_MACRO(fSTOREMMV, (EA, SRC), + "*(EA&~(ALIGNMENT-1)) = SRC", + "Store vector SRC to memory at EA (unaligned).", fSTOREMMV_AL(EA,fVECSIZE(),fVECSIZE(),SRC), () ) -DEF_MACRO(fSTOREMMVQ_AL, +DEF_MACRO(fSTOREMMVQ_AL, (EA, ALIGNMENT, LEN, SRC, MASK), + "char* addr = EA&~(ALIGNMENT-1); for (i=0; islot, LEN, &SRC.ub[0], &maskvec.ub[0], 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + mem_store_vector_oddva(thread, insn, EA&~(ALIGNMENT-1), EA, insn->slot, LEN, SRC.ub, &maskvec.ub[0], 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); } while (0), - (A_STORE,A_MEMLIKE) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fSTOREMMVQ, +DEF_MACRO(fSTOREMMVQ, (EA, SRC, MASK), + "*(EA&~(ALIGNMENT-1)) = SRC", + "Masked store vector SRC to memory at EA (forced alignment).", fSTOREMMVQ_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK), () ) -DEF_MACRO(fSTOREMMVNQ_AL, +DEF_MACRO(fSTOREMMVNQ_AL, (EA, ALIGNMENT, LEN, SRC, MASK), + "char* addr = EA&~(ALIGNMENT-1); for (i=0; islot, LEN, &SRC.ub[0], &maskvec.ub[0], 1, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + mem_store_vector_oddva(thread, insn, EA&~(ALIGNMENT-1), EA, insn->slot, LEN, SRC.ub, &maskvec.ub[0], 1, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); }, - (A_STORE,A_MEMLIKE) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fSTOREMMVNQ, +DEF_MACRO(fSTOREMMVNQ, (EA, SRC, MASK), + "*(EA&~(ALIGNMENT-1)) = SRC", + "Masked negated store vector SRC to memory at EA (forced alignment).", fSTOREMMVNQ_AL(EA,fVECSIZE(),fVECSIZE(),SRC,MASK), () ) -DEF_MACRO(fSTOREMMVU_AL, +DEF_MACRO(fSTOREMMVU_AL, (EA, ALIGNMENT, LEN, SRC), + "char* addr = EA; for (i=0; iLEN) size1 = LEN; size2 = LEN-size1; mem_store_vector_oddva(thread, insn, EA+size1, EA+fVECSIZE(), /* slot */ 1, size2, &SRC.ub[size1], 0, 0, fUSE_LOOKUP_ADDRESS()); - mem_store_vector_oddva(thread, insn, EA, EA, /* slot */ 0, size1, &SRC.ub[0], 0, 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + mem_store_vector_oddva(thread, insn, EA, EA, /* slot */ 0, size1, SRC.ub, 0, 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); }, - (A_STORE,A_MEMLIKE) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fSTOREMMVU, +DEF_MACRO(fSTOREMMVU, (EA, SRC), + "*EA = SRC", + "Store vector SRC to memory at EA (unaligned).", { thread->last_pkt->pkt_has_vtcm_access = 0; thread->last_pkt->pkt_access_count = 0; @@ -651,7 +1029,9 @@ DEF_MACRO(fSTOREMMVU, () ) -DEF_MACRO(fSTOREMMVQU_AL, +DEF_MACRO(fSTOREMMVQU_AL, (EA, ALIGNMENT, LEN, SRC, MASK), + "char* addr = EA; for (i=0; iLEN) size1 = LEN; size2 = LEN-size1; mem_store_vector_oddva(thread, insn, EA+size1, EA+fVECSIZE(),/* slot */ 1, size2, &SRC.ub[size1], &maskvec.ub[size1], 0, fUSE_LOOKUP_ADDRESS()); - mem_store_vector_oddva(thread, insn, EA, /* slot */ 0, size1, &SRC.ub[0], &maskvec.ub[0], 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + mem_store_vector_oddva(thread, insn, EA, /* slot */ 0, size1, SRC.ub, &maskvec.ub[0], 0, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); }, - (A_STORE,A_MEMLIKE) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fSTOREMMVQU, +DEF_MACRO(fSTOREMMVQU, (EA, SRC, MASK), + "*EA = SRC", + "Store vector SRC to memory at EA (unaligned).", { thread->last_pkt->pkt_has_vtcm_access = 0; thread->last_pkt->pkt_access_count = 0; @@ -682,7 +1064,9 @@ DEF_MACRO(fSTOREMMVQU, () ) -DEF_MACRO(fSTOREMMVNQU_AL, +DEF_MACRO(fSTOREMMVNQU_AL, (EA, ALIGNMENT, LEN, SRC, MASK), + "char* addr = EA; for (i=0; iLEN) size1 = LEN; size2 = LEN-size1; mem_store_vector_oddva(thread, insn, EA+size1, EA+fVECSIZE(), /* slot */ 1, size2, &SRC.ub[size1], &maskvec.ub[size1], 1, fUSE_LOOKUP_ADDRESS()); - mem_store_vector_oddva(thread, insn, EA, EA, /* slot */ 0, size1, &SRC.ub[0], &maskvec.ub[0], 1, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); + mem_store_vector_oddva(thread, insn, EA, EA, /* slot */ 0, size1, SRC.ub, &maskvec.ub[0], 1, fUSE_LOOKUP_ADDRESS_BY_REV(thread->processor_ptr)); }, - (A_STORE,A_MEMLIKE) + (A_STORE,A_MEMLIKE,A_RESTRICT_SINGLE_MEM_FIRST) ) -DEF_MACRO(fSTOREMMVNQU, +DEF_MACRO(fSTOREMMVNQU, (EA, SRC, MASK), + "*EA = SRC", + "Store vector SRC to memory at EA (unaligned).", { thread->last_pkt->pkt_has_vtcm_access = 0; thread->last_pkt->pkt_access_count = 0; @@ -716,127 +1102,446 @@ DEF_MACRO(fSTOREMMVNQU, -DEF_MACRO(fVFOREACH, +DEF_MACRO(fVFOREACH,(WIDTH, VAR), + "for (VAR = 0; VAR < VELEM(WIDTH); VAR++)", + "For VAR in each WIDTH-bit vector index", for (VAR = 0; VAR < fVELEM(WIDTH); VAR++), /* NOTHING */ ) -DEF_MACRO(fVARRAY_ELEMENT_ACCESS, +DEF_MACRO(fVARRAY_ELEMENT_ACCESS, (ARRAY, TYPE, INDEX), + "ARRAY.TYPE[INDEX]", + "Access element of type TYPE at position INDEX of flattened ARRAY", ARRAY.v[(INDEX) / (fVECSIZE()/(sizeof(ARRAY.TYPE[0])))].TYPE[(INDEX) % (fVECSIZE()/(sizeof(ARRAY.TYPE[0])))], () ) -DEF_MACRO(fVNEWCANCEL, +DEF_MACRO(fVNEWCANCEL,(REGNUM), + "Ignore current value for register REGNUM", + "Ignore current value for register REGNUM", do { THREAD2STRUCT->VRegs_select &= ~(1<<(REGNUM)); } while (0), () ) -DEF_MACRO(fTMPVDATA, +DEF_MACRO(fTMPVDATA,(), + "Data from .tmp load", + "Data from .tmp load and clear tmp status", mmvec_vtmp_data(thread), - (A_CVI) + (A_CVI,A_CVI_REQUIRES_TMPLOAD) ) -DEF_MACRO(fVSATDW, +DEF_MACRO(fVSATDW, (U,V), + "usat_32(U:V)", + "Use 32-bits of U as MSW and 32-bits of V as LSW and saturate the resultant 64-bits to 32 bits", fVSATW( ( ( ((long long)U)<<32 ) | fZXTN(32,64,V) ) ), /* attribs */ ) -DEF_MACRO(fVASL_SATHI, +DEF_MACRO(fVASL_SATHI, (U,V), + "uasl_sathi(U:V)", + "Use 32-bits of U as MSW and 32-bits of V as LSW, left shift by 1 and saturate the result and take high word", fVSATW(((U)<<1) | ((V)>>31)), /* attribs */ ) -DEF_MACRO(fVUADDSAT, +DEF_MACRO(fVUADDSAT,(WIDTH,U,V), + "usat_##WIDTH(U+V)", + "Add WIDTH-bit values U and V with saturation", fVSATUN( WIDTH, fZXTN(WIDTH, 2*WIDTH, U) + fZXTN(WIDTH, 2*WIDTH, V)), /* attribs */ ) -DEF_MACRO(fVSADDSAT, - fVSATN( WIDTH, fSXTN(WIDTH, 2*WIDTH, U) + fSXTN(WIDTH, 2*WIDTH, V)), +DEF_MACRO(fVSADDSAT,(WIDTH,U,V), + "sat_##WIDTH(U+V)", + "Add WIDTH-bit values U and V with saturation", + ({size8s_t tmp5 = fSXTN(WIDTH, 2*WIDTH, U); + size8s_t tmp6 = fSXTN(WIDTH, 2*WIDTH, V); + size8s_t tmp7 = tmp5 + tmp6; + fVSATN( WIDTH, tmp7); + }), /* attribs */ ) -DEF_MACRO(fVUSUBSAT, +DEF_MACRO(fVUSUBSAT,(WIDTH,U,V), + "usat_##WIDTH(U-V)", + "sub WIDTH-bit values U and V with saturation", fVSATUN( WIDTH, fZXTN(WIDTH, 2*WIDTH, U) - fZXTN(WIDTH, 2*WIDTH, V)), /* attribs */ ) -DEF_MACRO(fVSSUBSAT, +DEF_MACRO(fVSSUBSAT,(WIDTH,U,V), + "sat_##WIDTH(U-V)", + "sub WIDTH-bit values U and V with saturation", fVSATN( WIDTH, fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V)), /* attribs */ ) -DEF_MACRO(fVAVGU, +DEF_MACRO(fVAVGU,(WIDTH,U,V), + "(U+V)/2", + "average WIDTH-bit values U and V with saturation", ((fZXTN(WIDTH, 2*WIDTH, U) + fZXTN(WIDTH, 2*WIDTH, V))>>1), /* attribs */ ) -DEF_MACRO(fVAVGURND, +DEF_MACRO(fVAVGURND,(WIDTH,U,V), + "(U+V+1)/2", + "average WIDTH-bit values U and V with saturation", ((fZXTN(WIDTH, 2*WIDTH, U) + fZXTN(WIDTH, 2*WIDTH, V)+1)>>1), /* attribs */ ) -DEF_MACRO(fVNAVGU, +DEF_MACRO(fVNAVGU,(WIDTH,U,V), + "(U-V)/2", + "average WIDTH-bit values U and V with saturation", ((fZXTN(WIDTH, 2*WIDTH, U) - fZXTN(WIDTH, 2*WIDTH, V))>>1), /* attribs */ ) -DEF_MACRO(fVNAVGURNDSAT, +DEF_MACRO(fVNAVGURNDSAT,(WIDTH,U,V), + "(U-V+1)/2", + "average WIDTH-bit values U and V with saturation", fVSATUN(WIDTH,((fZXTN(WIDTH, 2*WIDTH, U) - fZXTN(WIDTH, 2*WIDTH, V)+1)>>1)), /* attribs */ ) -DEF_MACRO(fVAVGS, +DEF_MACRO(fVAVGS,(WIDTH,U,V), + "(U+V)/2", + "average WIDTH-bit values U and V with saturation", ((fSXTN(WIDTH, 2*WIDTH, U) + fSXTN(WIDTH, 2*WIDTH, V))>>1), /* attribs */ ) -DEF_MACRO(fVAVGSRND, +DEF_MACRO(fVAVGSRND,(WIDTH,U,V), + "(U+V+1)/2", + "average WIDTH-bit values U and V with saturation", ((fSXTN(WIDTH, 2*WIDTH, U) + fSXTN(WIDTH, 2*WIDTH, V)+1)>>1), /* attribs */ ) -DEF_MACRO(fVNAVGS, +DEF_MACRO(fVNAVGS,(WIDTH,U,V), + "(U-V)/2", + "average WIDTH-bit values U and V with saturation", ((fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V))>>1), /* attribs */ ) -DEF_MACRO(fVNAVGSRND, +DEF_MACRO(fVNAVGSRND,(WIDTH,U,V), + "(U-V+1)/2", + "average WIDTH-bit values U and negative V followed by rounding", ((fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V)+1)>>1), /* attribs */ ) -DEF_MACRO(fVNAVGSRNDSAT, +DEF_MACRO(fVNAVGSRNDSAT,(WIDTH,U,V), + "(U-V+1)/2", + "average WIDTH-bit values U and V with saturation", fVSATN(WIDTH,((fSXTN(WIDTH, 2*WIDTH, U) - fSXTN(WIDTH, 2*WIDTH, V)+1)>>1)), /* attribs */ ) -DEF_MACRO(fVNOROUND, +DEF_MACRO(fVNOROUND,(VAL,SHAMT), + "VAL", + "VAL", VAL, /* NOTHING */ ) -DEF_MACRO(fVNOSAT, +DEF_MACRO(fVNOSAT,(VAL), + "VAL", + "VAL", VAL, /* NOTHING */ ) -DEF_MACRO(fVROUND, +DEF_MACRO(fVROUND,(VAL,SHAMT), + "VAL + (1<<(SHAMT-1))", + "VAL + RNDBIT", ((VAL) + (((SHAMT)>0)?(1LL<<((SHAMT)-1)):0)), /* NOTHING */ ) -DEF_MACRO(fCARRY_FROM_ADD32, +DEF_MACRO(fCARRY_FROM_ADD32,(A,B,C), + "carry_from(A,B,C)", + "carry_from(A,B,C)", (((fZXTN(32,64,A)+fZXTN(32,64,B)+C) >> 32) & 1), /* NOTHING */ ) -DEF_MACRO(fUARCH_NOTE_PUMP_4X, +DEF_MACRO(fUARCH_NOTE_PUMP_4X,(), + "", + "", , - () + (A_CVI_PUMP_4X) ) -DEF_MACRO(fUARCH_NOTE_PUMP_2X, +DEF_MACRO(fUARCH_NOTE_PUMP_2X,(), + "", + "", , + (A_CVI_PUMP_2X) +) + +DEF_MACRO(fVDOCHKPAGECROSS,(BASE,SUM), + "", + "", + if (UNLIKELY(thread->timing_on)) { + thread->mem_access[slot].check_page_crosses = 1; + thread->mem_access[slot].page_cross_base = BASE; + thread->mem_access[slot].page_cross_sum = SUM; + }, + (A_EA_PAGECROSS) +) + +/* FP instructions */ +/*Qfloat Macros for muls*/ +DEF_MACRO(fPARSEQF32,(A), + "A", + "Parsing QF32 to extract exp/sig", + parse_qf32(A), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATQF32,(A,B,C), + "rnd_sat(A,B,C)", + "Rnd/Sat/Norm of Vector Multiply of two QF32 inputs", + rnd_sat_qf32(A,B,C), + (A_HVX_FLT) +) + +DEF_MACRO(fPARSEQF16,(A), + "A", + "Parsing QF16 to extract exp/sig", + parse_qf16(A), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATQF16,(A,B,C), + "rnd_sat(A,B,C)", + "Rnd/Sat/Norm of Vector Multiply of two QF16 inputs", + rnd_sat_qf16(A,B,C), () ) +/*Qfloat Macros for others*/ +DEF_MACRO(fPARSESF,(A), + "A", + "Parsing IEEE SF to extract sign/exp/sig", + parse_sf(A), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATSF,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector Multiply of two IEEE SF inputs", + rnd_sat_sf(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fPARSEHF,(A), + "A", + "Parsing IEEE HF to extract sign/exp/sig", + parse_hf(A), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATHF,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector Multiply of two IEEE HF inputs", + rnd_sat_hf(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATW,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector convert of W inputs", + rnd_sat_w(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATUW,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector convert of UW inputs", + rnd_sat_uw(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATH,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector convert of H inputs", + rnd_sat_h(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATUH,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector convert of UW inputs", + rnd_sat_uh(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATB,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector convert of B inputs", + rnd_sat_b(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fRNDSATUB,(A,B), + "rnd_sat(A,B)", + "Rnd/Sat/Norm of Vector convert of UB inputs", + rnd_sat_ub(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fNEGQF32,(A), + "-(A)", + "Take Ones complement", + negate32(A), + (A_HVX_FLT) +) + +DEF_MACRO(fNEGQF16,(A), + "-(A)", + "Take Ones complement", + negate16(A), + (A_HVX_FLT) +) + +DEF_MACRO(fNEGSF,(A), + "-(A)", + "Change sign", + negate_sf(A), + (A_HVX_FLT) +) +DEF_MACRO(fNEGHF,(A), + "-(A)", + "Change sign", + negate_hf(A), + (A_HVX_FLT) +) + +//FP vector compare +DEF_MACRO(fCMPGT_QF32,(A,B), + "(A > B)", + "Vector compare of QF32 format", + cmpgt_qf32(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fCMPGT_QF16,(A,B), + "(A > B)", + "Vector compare of QF16 format", + cmpgt_qf16(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fCMPGT_SF,(A,B), + "(A > B)", + "Vector compare of SF format", + cmpgt_sf(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fCMPGT_HF,(A,B), + "(A > B)", + "Vector compare of HF format", + cmpgt_hf(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fCMPGT_BF,(A,B), + "(A > B)", + "Vector compare of BF format", + cmpgt_sf(((int)A) << 16,((int)B) << 16), + (A_HVX_FLT) +) + +DEF_MACRO(fCMPGT_QF32_SF,(A,B), + "(A > B)", + "Vector compare of QF32/SF format", + cmpgt_qf32_sf(A,B), + (A_HVX_FLT) +) + +DEF_MACRO(fCMPGT_QF16_HF,(A,B), + "(A > B)", + "Vector compare of QF16/HF format", + cmpgt_qf16_hf(A,B), + (A_HVX_FLT) +) + +//VMAX/VMIN_QF32/QF16 +DEF_MACRO(fMAX_QF32,(X,Y), + "max(X,Y)", + "", + max_qf32(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMIN_QF32,(X,Y), + "min(X,Y)", + "", + min_qf32(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMAX_QF32_SF,(X,Y), + "max(X,Y)", + "", + max_qf32_sf(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMIN_QF32_SF,(X,Y), + "min(X,Y)", + "", + min_qf32_sf(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMAX_QF16,(X,Y), + "max(X,Y)", + "", + max_qf16(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMIN_QF16,(X,Y), + "min(X,Y)", + "", + min_qf16(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMAX_QF16_HF,(X,Y), + "max(X,Y)", + "", + max_qf16_hf(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMIN_QF16_HF,(X,Y), + "min(X,Y)", + "", + min_qf16_hf(X,Y), + (A_HVX_FLT) +) + +//MAX/MIN_SF/HF +DEF_MACRO(fMAX_SF,(X,Y), + "max(X,Y)", + "", + max_sf(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMIN_SF,(X,Y), + "min(X,Y)", + "", + min_sf(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMAX_HF,(X,Y), + "max(X,Y)", + "", + max_hf(X,Y), + (A_HVX_FLT) +) +DEF_MACRO(fMIN_HF,(X,Y), + "min(X,Y)", + "", + min_hf(X,Y), + (A_HVX_FLT) +) + diff --git a/target/hexagon/mmvec/macros.h b/target/hexagon/mmvec/macros.h index bcd4a1e8973c..645ec9280972 100644 --- a/target/hexagon/mmvec/macros.h +++ b/target/hexagon/mmvec/macros.h @@ -354,3 +354,16 @@ } while (0); #endif + +#define fPARSEHF(A) parse_hf(A) +#define fPARSESF(A) parse_sf(A) +#define fPARSEQF16(A) parse_qf16(A) +#define fPARSEQF32(A) parse_qf32(A) + +#define fRNDSATHF(A,B) rnd_sat_hf(A,B) +#define fRNDSATSF(A,B) rnd_sat_sf(A,B) +#define fRNDSATQF16(A,B,C) rnd_sat_qf16(A,B,C) +#define fRNDSATQF32(A,B,C) rnd_sat_qf32(A,B,C) + +#define fNEGQF16(A) negate16(A) +#define fNEGQF32(A) negate32(A) From 9ac9acde980b25bc56d772df270a823a3a7e2716 Mon Sep 17 00:00:00 2001 From: Marco Liebel Date: Wed, 5 Mar 2025 12:54:22 -0800 Subject: [PATCH 1173/1179] target/hexagon: Update encoding of vunpackob Signed-off-by: Marco Liebel --- target/hexagon/imported/mmvec/encode_ext.def | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/hexagon/imported/mmvec/encode_ext.def b/target/hexagon/imported/mmvec/encode_ext.def index 402438f566c1..698299842163 100644 --- a/target/hexagon/imported/mmvec/encode_ext.def +++ b/target/hexagon/imported/mmvec/encode_ext.def @@ -647,7 +647,7 @@ DEF_ENC(V6_vsubububb_sat, ICLASS_CJ" 1 110 101 vvvvv PP 0 uuuuu 101 ddddd") DEF_ENC(V6_vmpyewuh_64, ICLASS_CJ" 1 110 101 vvvvv PP 0 uuuuu 110 ddddd") DEF_FIELDROW_DESC32( ICLASS_CJ" 1 110 --0 ----- PP 1 ----- ----- ---","Vx32=Vu32") -DEF_ENC(V6_vunpackob, ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 000 xxxxx") // +DEF_ENC(V6_vunpackob, ICLASS_CJ" 1 110 --0 --000 PP 1 uuuuu 000 xxxxx") // DEF_ENC(V6_vunpackoh, ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 001 xxxxx") // //DEF_ENC(V6_vunpackow, ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 010 xxxxx") // From e34591deaa2ceb16082145465a88fff45da3c217 Mon Sep 17 00:00:00 2001 From: Marco Liebel Date: Wed, 5 Mar 2025 11:55:40 -0800 Subject: [PATCH 1174/1179] target/hexagon: Add encodings Signed-off-by: Marco Liebel --- target/hexagon/imported/mmvec/encode_ext.def | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/target/hexagon/imported/mmvec/encode_ext.def b/target/hexagon/imported/mmvec/encode_ext.def index 698299842163..9df920476441 100644 --- a/target/hexagon/imported/mmvec/encode_ext.def +++ b/target/hexagon/imported/mmvec/encode_ext.def @@ -804,5 +804,31 @@ DEF_ENC(V6_vmpyewuh, ICLASS_CJ" 1 111 111 vvvvv PP 0 uuuuu 101 ddddd") DEF_ENC(V6_vmpyowh, ICLASS_CJ" 1 111 111 vvvvv PP 0 uuuuu 111 ddddd") DEF_ENC(V6_vmpyuhvs,"00011111110vvvvvPP1uuuuu111ddddd") +DEF_ENC(V6_vadd_hf,"00011111011vvvvvPP1uuuuu011ddddd") +DEF_ENC(V6_vadd_sf,"00011111101vvvvvPP1uuuuu001ddddd") +DEF_ENC(V6_vadd_qf16,"00011111011vvvvvPP1uuuuu010ddddd") +DEF_ENC(V6_vadd_qf16_mix,"00011111011vvvvvPP1uuuuu100ddddd") +DEF_ENC(V6_vadd_qf32,"00011111101vvvvvPP1uuuuu000ddddd") +DEF_ENC(V6_vadd_qf32_mix,"00011111101vvvvvPP1uuuuu010ddddd") + +DEF_ENC(V6_vconv_hf_qf16,"00011110--0--100PP1uuuuu011ddddd") +DEF_ENC(V6_vconv_hf_qf32,"00011110--0--100PP1uuuuu110ddddd") +DEF_ENC(V6_vconv_sf_qf32,"00011110--0--100PP1uuuuu000ddddd") + +DEF_ENC(V6_vmpy_qf16,"00011111111vvvvvPP1uuuuu011ddddd") +DEF_ENC(V6_vmpy_qf16_hf,"00011111111vvvvvPP1uuuuu100ddddd") +DEF_ENC(V6_vmpy_qf16_mix_hf,"00011111111vvvvvPP1uuuuu101ddddd") +DEF_ENC(V6_vmpy_qf32,"00011111111vvvvvPP1uuuuu000ddddd") +DEF_ENC(V6_vmpy_qf32_hf,"00011111111vvvvvPP1uuuuu111ddddd") +DEF_ENC(V6_vmpy_qf32_mix_hf,"00011111100vvvvvPP1uuuuu000ddddd") +DEF_ENC(V6_vmpy_qf32_qf16,"00011111111vvvvvPP1uuuuu110ddddd") +DEF_ENC(V6_vmpy_qf32_sf,"00011111111vvvvvPP1uuuuu001ddddd") + +DEF_ENC(V6_vsub_hf,"00011111011vvvvvPP1uuuuu110ddddd") +DEF_ENC(V6_vsub_sf,"00011111101vvvvvPP1uuuuu100ddddd") +DEF_ENC(V6_vsub_qf32,"00011111101vvvvvPP1uuuuu011ddddd") +DEF_ENC(V6_vsub_qf32_mix,"00011111101vvvvvPP1uuuuu101ddddd") +DEF_ENC(V6_vsub_qf16,"00011111011vvvvvPP1uuuuu101ddddd") +DEF_ENC(V6_vsub_qf16_mix,"00011111011vvvvvPP1uuuuu111ddddd") #endif /* NO MMVEC */ From a1f5ecf5f3f503b0bb36aaf3a8224876401f153c Mon Sep 17 00:00:00 2001 From: Marco Liebel Date: Wed, 5 Mar 2025 11:03:59 -0800 Subject: [PATCH 1175/1179] target/hexagon: Add simple qfloat test Signed-off-by: Marco Liebel --- tests/tcg/hexagon/hvx_misc.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/tcg/hexagon/hvx_misc.c b/tests/tcg/hexagon/hvx_misc.c index 90c3733da071..319d7c0dd052 100644 --- a/tests/tcg/hexagon/hvx_misc.c +++ b/tests/tcg/hexagon/hvx_misc.c @@ -495,6 +495,28 @@ void test_store_new() check_output_w(__LINE__, 1); } +void test_qfloat() +{ + asm volatile( + "r0 = #0xf\n" + "v0 = vsplat(r0)\n" + "v1 = vsplat(r0)\n" + "{\n" + " v2.qf16 = vadd(v0.qf16, v1.qf16)\n" + "}\n" + "vmem(%0) = v2\n" + : + : "r"(&output[0]) + : "r0", "v0", "v1", "v2", "memory" + ); + + for (int i = 0; i < MAX_VEC_SIZE_BYTES / 4; i++) { + expect[0].w[i] = 0x10010; + } + + check_output_w(__LINE__, 1); +} + int main() { init_buffers(); @@ -538,6 +560,8 @@ int main() test_store_new(); + test_qfloat(); + puts(err ? "FAIL" : "PASS"); return err ? 1 : 0; } From 8ad89325e83884df11f776f883a1d280a97c6605 Mon Sep 17 00:00:00 2001 From: Sid Manning Date: Fri, 7 Mar 2025 12:22:42 -0800 Subject: [PATCH 1176/1179] FIXME: Merge qtmr_rg0/rg1 into just qtmr_region A single mapping is made by qct-qtimer.c and the extraneous region caused confusion. FIXME: fold this change into the previous commit(s) that introduce this --- hw/hexagon/hexagon_dsp.c | 1 + hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc | 3 +-- hw/hexagon/machine_cfg_v66g_1024.h.inc | 3 +-- hw/hexagon/machine_cfg_v68n_1024.h.inc | 3 +-- hw/hexagon/virt.c | 4 ++-- include/hw/hexagon/hexagon.h | 3 +-- 6 files changed, 7 insertions(+), 10 deletions(-) diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c index 348977a542fa..f4440de80ce0 100644 --- a/hw/hexagon/hexagon_dsp.c +++ b/hw/hexagon/hexagon_dsp.c @@ -122,6 +122,7 @@ static void hexagon_common_init(MachineState *machine, Rev_t rev, qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0)); qdev_prop_set_uint32(DEVICE(cpu), "l2vic-base-addr", m_cfg->l2vic_base); qdev_prop_set_uint32(DEVICE(cpu), "config-table-addr", m_cfg->cfgbase); + qdev_prop_set_uint32(DEVICE(cpu), "qtimer-base-addr", m_cfg->qtmr_region); qdev_prop_set_uint32(DEVICE(cpu), "hvx-contexts", m_cfg->cfgtable.ext_contexts); qdev_prop_set_uint32(DEVICE(cpu), "jtlb-entries", diff --git a/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc index d8fa961f6d09..70b1eabfe961 100644 --- a/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc +++ b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc @@ -5,8 +5,7 @@ static hexagon_machine_config SA8775P_cdsp0 = { .l2vic_base = 0x26300000 + 0x90000, .l2vic_size = 0x00001000, .csr_base = 0x26300000, - .qtmr_rg0 = 0x26300000 + 0xA1000, - .qtmr_rg1 = 0x26300000 + 0xA2000, + .qtmr_region = 0x26300000 + 0xA1000, .cfgtable = { .l2tcm_base = 0x00002400, .reserved0 = 0x00000000, diff --git a/hw/hexagon/machine_cfg_v66g_1024.h.inc b/hw/hexagon/machine_cfg_v66g_1024.h.inc index 604cc7777cbf..8f2a593bb860 100644 --- a/hw/hexagon/machine_cfg_v66g_1024.h.inc +++ b/hw/hexagon/machine_cfg_v66g_1024.h.inc @@ -5,8 +5,7 @@ static hexagon_machine_config v66g_1024 = { .l2vic_base = 0xfc910000, .l2vic_size = 0x00001000, .csr_base = 0xfc900000, - .qtmr_rg0 = 0xfc921000, - .qtmr_rg1 = 0xfc922000, + .qtmr_region = 0xfc921000, .cfgtable = { .l2tcm_base = 0x0000d800, .reserved0 = 0x0000d400, diff --git a/hw/hexagon/machine_cfg_v68n_1024.h.inc b/hw/hexagon/machine_cfg_v68n_1024.h.inc index 60eb112a1199..257c133df8f3 100644 --- a/hw/hexagon/machine_cfg_v68n_1024.h.inc +++ b/hw/hexagon/machine_cfg_v68n_1024.h.inc @@ -5,8 +5,7 @@ static hexagon_machine_config v68n_1024 = { .l2vic_base = 0xfc910000, .l2vic_size = 0x00001000, .csr_base = 0xfc900000, - .qtmr_rg0 = 0xfc921000, - .qtmr_rg1 = 0xfc922000, + .qtmr_region = 0xfc921000, .cfgtable = { .l2tcm_base = 0x0000d800, .reserved0 = 0x00000000, diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c index b991bc94a838..1e7ac4e5b70b 100644 --- a/hw/hexagon/virt.c +++ b/hw/hexagon/virt.c @@ -257,8 +257,7 @@ static void create_qtimer(HexagonVirtMachineState *vms, sysbus_realize_and_unref(SYS_BUS_DEVICE(qtimer), errp); - sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 0, m_cfg->qtmr_rg1); - sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 1, m_cfg->qtmr_rg0); + sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 1, m_cfg->qtmr_region); sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), 0, qdev_get_gpio_in(vms->l2vic, irqmap[VIRT_QTMR0])); sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), 1, @@ -354,6 +353,7 @@ static void virt_init(MachineState *ms) m_cfg->cfgtable.ext_contexts); qdev_prop_set_uint32(DEVICE(cpu), "config-table-addr", m_cfg->cfgbase); qdev_prop_set_uint32(DEVICE(cpu), "l2vic-base-addr", m_cfg->l2vic_base); + qdev_prop_set_uint32(DEVICE(cpu), "qtimer-base-addr", m_cfg->qtmr_region); qdev_prop_set_uint32(DEVICE(cpu), "jtlb-entries", m_cfg->cfgtable.jtlb_size_entries); diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h index 0afaac3b1f85..ce356325fcd7 100644 --- a/include/hw/hexagon/hexagon.h +++ b/include/hw/hexagon/hexagon.h @@ -143,8 +143,7 @@ typedef struct { uint32_t l2vic_size; /* QTimer csr base */ uint32_t csr_base; - uint32_t qtmr_rg0; - uint32_t qtmr_rg1; + uint32_t qtmr_region; hexagon_config_table cfgtable; } hexagon_machine_config; From 806002a178037028477a925371c367a60d99651f Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sat, 8 Mar 2025 20:42:51 -0800 Subject: [PATCH 1177/1179] FIXME: Add unimplemented DMA instructions * TODO: forward the instruction tag to the unimp log? * TODO: why do we need_env() for these? * TODO: filter out some attributes? These instructions are unimplemented for now, they are used by h2. --- target/hexagon/hex_common.py | 1 + target/hexagon/imported/encode_pp.def | 12 +++++++- target/hexagon/imported/system.idef | 41 ++++++++++++++++++++++++++- target/hexagon/macros.h | 2 ++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index 1df5d8c19a03..a10f48a093e9 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -253,6 +253,7 @@ def is_hvx_insn(tag): def need_env(tag): return ("A_STORE" in attribdict[tag] or "A_LOAD" in attribdict[tag] or + "A_DMA" in attribdict[tag] or "A_CVI_GATHER" in attribdict[tag] or "A_CVI_SCATTER" in attribdict[tag] or "A_IMPLICIT_WRITES_USR" in attribdict[tag] or diff --git a/target/hexagon/imported/encode_pp.def b/target/hexagon/imported/encode_pp.def index 41e4ab9e3a26..2c45388ab629 100644 --- a/target/hexagon/imported/encode_pp.def +++ b/target/hexagon/imported/encode_pp.def @@ -788,6 +788,17 @@ DEF_ENC32(Y6_diag, ICLASS_CR" 0010 010sssss PP------ 001-----") DEF_ENC32(Y6_diag0, ICLASS_CR" 0010 010sssss PP-ttttt 010-----") DEF_ENC32(Y6_diag1, ICLASS_CR" 0010 010sssss PP-ttttt 011-----") +DEF_ENC32(Y6_dmcfgrd,"10101000000sssssPP------101ddddd") +DEF_ENC32(Y6_dmcfgwr,"10101000000sssssPP-ttttt110-----") +DEF_ENC32(Y6_dmlink,"10100110000sssssPP-ttttt010-----") +DEF_ENC32(Y6_dmpause,"10101000000-----PP------011ddddd") +DEF_ENC32(Y6_dmpoll,"10101000000-----PP------010ddddd") +DEF_ENC32(Y6_dmresume,"10100110000sssssPP------100-----") +DEF_ENC32(Y6_dmstart,"10100110000sssssPP------001-----") +DEF_ENC32(Y6_dmsyncht,"10101000000-----PP-----0111ddddd") +DEF_ENC32(Y6_dmtlbsynch,"10101000000-----PP-----1111ddddd") +DEF_ENC32(Y6_dmwait,"10101000000-----PP------001ddddd") + DEF_FIELDROW_DESC32( ICLASS_CR" 0011 -------- PP------ --------","[#3] Cdd=Rss ") DEF_ENC32(A4_tfrpcp, ICLASS_CR" 0011 001sssss PP------ ---ddddd") DEF_ENC32(G4_tfrgpcp, ICLASS_CR" 0011 000sssss PP------ ---ddddd") @@ -2230,4 +2241,3 @@ DEF_ENC32(M4_mpyri_addr_u2, ICLASS_ALU64" 1111 0ii sssss PPiddddd iiiuuuuu") DEF_ENC32(M4_mpyri_addr, ICLASS_ALU64" 1111 1ii sssss PPiddddd iiiuuuuu") - diff --git a/target/hexagon/imported/system.idef b/target/hexagon/imported/system.idef index fd7ef18b3e34..aa57149a1ceb 100644 --- a/target/hexagon/imported/system.idef +++ b/target/hexagon/imported/system.idef @@ -256,6 +256,46 @@ Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_P 0); /*extra attrib flags*/ }) +Q6INSN(Y6_dmstart,"dmstart(Rs32)",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_SYNC_MARKER,A_NO_TIMING_LOG),"DMA Start", { + fUNIMP(); +}) + +Q6INSN(Y6_dmlink,"dmlink(Rs32,Rt32)",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_SYNC_MARKER,A_NO_TIMING_LOG),"DMA Link", { + fUNIMP(); +}) + +Q6INSN(Y6_dmpoll,"Rd32=dmpoll",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Poll", { + fUNIMP(); +}) + +Q6INSN(Y6_dmwait,"Rd32=dmwait",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Wait", { + fUNIMP(); +}) + +Q6INSN(Y6_dmsyncht,"Rd32=dmsyncht",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA SynchT",{ + fUNIMP(); +}) +Q6INSN(Y6_dmtlbsynch,"Rd32=dmtlbsynch",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA TLB Synch",{ + fUNIMP(); +}) + +Q6INSN(Y6_dmcfgrd,"Rd32=dmcfgrd(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG), + "DMA Config Read", { + fUNIMP(); +}) + +Q6INSN(Y6_dmcfgwr,"dmcfgwr(Rs32,Rt32)",ATTRIBS(A_NOTE_PRIV,A_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG), + "DMA Config Write", { + fUNIMP(); +}) + +Q6INSN(Y6_dmpause,"Rd32=dmpause",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Pause",{ + fUNIMP(); +}) + +Q6INSN(Y6_dmresume,"dmresume(Rs32)",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_SYNC_MARKER,A_NO_TIMING_LOG),"DMA Resume",{ + fUNIMP(); +}) Q6INSN(Y5_l2fetch,"l2fetch(Rs32,Rtt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK),"L2 Cache Prefetch", @@ -283,4 +323,3 @@ Q6INSN(Y5_l2gcleaninv,"l2gcleaninv",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A Q6INSN(Y6_l2gcleanpa,"l2gclean(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean by PA Range",{fL2CLEANPA(RttV);}) Q6INSN(Y6_l2gcleaninvpa,"l2gcleaninv(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean and Invalidate by PA Range",{fL2CLEANINVPA(RttV);}) - diff --git a/target/hexagon/macros.h b/target/hexagon/macros.h index 06c1dd2f407e..01469a28a0cc 100644 --- a/target/hexagon/macros.h +++ b/target/hexagon/macros.h @@ -687,3 +687,5 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #endif #define fPREDUSE_TIMING() + +#define fUNIMP() qemu_log_mask(LOG_UNIMP, "Unimplemented instruction\n") From d0afff7942abcbd35effcd14bac3be8908a66878 Mon Sep 17 00:00:00 2001 From: Sid Manning Date: Thu, 20 Mar 2025 13:57:44 -0700 Subject: [PATCH 1178/1179] fixup! target/hexagon: Implement ciad helper Signed-off-by: Sid Manning --- target/hexagon/op_helper.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index e6f11fd5f990..983f0f0c3b0c 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -1458,7 +1458,7 @@ void HELPER(vwhist128qm)(CPUHexagonState *env, int32_t uiV) } #ifndef CONFIG_USER_ONLY -static void hexagon_set_vid(CPUHexagonState *env, uint32_t offset, int val) +static void hexagon_set_vid(CPUHexagonState *env, uint32_t offset, uint32_t val) { g_assert((offset == L2VIC_VID_0) || (offset == L2VIC_VID_1)); CPUState *cs = env_cpu(env); @@ -1469,13 +1469,18 @@ static void hexagon_set_vid(CPUHexagonState *env, uint32_t offset, int val) static void hexagon_clear_last_irq(CPUHexagonState *env, uint32_t offset) { - /* - * currently only l2vic is the only attached it uses vid0, remove - * the assert below if anther is added - */ hexagon_set_vid(env, offset, L2VIC_CIAD_INSTRUCTION); } +/* + * ciad - clear interrupt auto disable + * - When taking an interrupt the hardware will set ipend.iad to + * prevent another thread from servicing the interrupt. At the completion + * of service software uses ciad to clear the bit indicating the + * interrupt can be accepted again. + * - ciad also handshakes with the l2vic allowing a new vid on the vector + * port. + */ void HELPER(ciad)(CPUHexagonState *env, uint32_t mask) { uint32_t ipendad; From c3149881f5e5c485424fdc431a2794010b61e915 Mon Sep 17 00:00:00 2001 From: Sid Manning Date: Thu, 20 Mar 2025 13:32:51 -0700 Subject: [PATCH 1179/1179] fixup! target/hexagon: Define register fields for system regs Signed-off-by: Sid Manning --- target/hexagon/reg_fields_def.h.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/hexagon/reg_fields_def.h.inc b/target/hexagon/reg_fields_def.h.inc index 50b8c26f8bfa..d2c706d56b55 100644 --- a/target/hexagon/reg_fields_def.h.inc +++ b/target/hexagon/reg_fields_def.h.inc @@ -45,7 +45,7 @@ DEF_REG_FIELD(IPENDAD_IPEND, 0, 16) DEF_REG_FIELD(SCHEDCFG_EN, 8, 1) DEF_REG_FIELD(SCHEDCFG_INTNO, 0, 4) -DEF_REG_FIELD(BESTWAIT_PRIO, 0, 10) +DEF_REG_FIELD(BESTWAIT_PRIO, 0, 9) /* PTE (aka TLB entry) fields */

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 zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* 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 Date: Wed, 6 Nov 2024 15:32:28 +1100 Subject: [PATCH 0884/1179] pseries: Update SLOF firmware image This adds TPM pass through API. Also, moves SLOF from github to gitlab. Signed-off-by: Alexey Kardashevskiy Signed-off-by: Nicholas Piggin --- pc-bios/README | 4 ++-- pc-bios/slof.bin | Bin 995000 -> 996184 bytes roms/SLOF | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pc-bios/README b/pc-bios/README index 700dcaab523f..a08e034fc32b 100644 --- a/pc-bios/README +++ b/pc-bios/README @@ -13,8 +13,8 @@ - SLOF (Slimline Open Firmware) is a free IEEE 1275 Open Firmware implementation for certain IBM POWER hardware. The sources are at - https://github.com/aik/SLOF, and the image currently in qemu is - built from git tag qemu-slof-20230918. + https://gitlab.com/slof/slof, and the image currently in qemu is + built from git tag qemu-slof-20241106. - VOF (Virtual Open Firmware) is a minimalistic firmware to work with -machine pseries,x-vof=on. When enabled, the firmware acts as a slim shim and diff --git a/pc-bios/slof.bin b/pc-bios/slof.bin index 27fed09f49a79eaba394a961cce5ddf065902259..4314e17b9ddc964c905a7b107d9aa7a651a43890 100644 GIT binary patch delta 217628 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 ewBp>*H+|uGYR%fZ8P^tdE-_Os7_MVwFYu2pS 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