diff options
95 files changed, 3590 insertions, 1890 deletions
diff --git a/.gitlab-ci.d/cirrus.yml b/.gitlab-ci.d/cirrus.yml index 4671f069c3..75df1273bc 100644 --- a/.gitlab-ci.d/cirrus.yml +++ b/.gitlab-ci.d/cirrus.yml @@ -57,6 +57,7 @@ x64-freebsd-13-build: CIRRUS_VM_RAM: 8G UPDATE_COMMAND: pkg update; pkg upgrade -y INSTALL_COMMAND: pkg install -y + CONFIGURE_ARGS: --target-list-exclude=arm-softmmu,i386-softmmu,microblaze-softmmu,mips64el-softmmu,mipsel-softmmu,mips-softmmu,ppc-softmmu,sh4eb-softmmu,xtensa-softmmu TEST_TARGETS: check aarch64-macos-13-base-build: @@ -72,6 +73,7 @@ aarch64-macos-13-base-build: INSTALL_COMMAND: brew install PATH_EXTRA: /opt/homebrew/ccache/libexec:/opt/homebrew/gettext/bin PKG_CONFIG_PATH: /opt/homebrew/curl/lib/pkgconfig:/opt/homebrew/ncurses/lib/pkgconfig:/opt/homebrew/readline/lib/pkgconfig + CONFIGURE_ARGS: --target-list-exclude=arm-softmmu,i386-softmmu,microblazeel-softmmu,mips64-softmmu,mipsel-softmmu,mips-softmmu,ppc-softmmu,sh4-softmmu,xtensaeb-softmmu TEST_TARGETS: check-unit check-block check-qapi-schema check-softfloat check-qtest-x86_64 aarch64-macos-14-base-build: @@ -89,40 +91,3 @@ aarch64-macos-14-base-build: PKG_CONFIG_PATH: /opt/homebrew/curl/lib/pkgconfig:/opt/homebrew/ncurses/lib/pkgconfig:/opt/homebrew/readline/lib/pkgconfig TEST_TARGETS: check-unit check-block check-qapi-schema check-softfloat check-qtest-x86_64 QEMU_JOB_OPTIONAL: 1 - - -# The following jobs run VM-based tests via KVM on a Linux-based Cirrus-CI job -.cirrus_kvm_job: - extends: .base_job_template - stage: build - image: registry.gitlab.com/libvirt/libvirt-ci/cirrus-run:master - needs: [] - timeout: 80m - script: - - sed -e "s|[@]CI_REPOSITORY_URL@|$CI_REPOSITORY_URL|g" - -e "s|[@]CI_COMMIT_REF_NAME@|$CI_COMMIT_REF_NAME|g" - -e "s|[@]CI_COMMIT_SHA@|$CI_COMMIT_SHA|g" - -e "s|[@]NAME@|$NAME|g" - -e "s|[@]CONFIGURE_ARGS@|$CONFIGURE_ARGS|g" - -e "s|[@]TEST_TARGETS@|$TEST_TARGETS|g" - <.gitlab-ci.d/cirrus/kvm-build.yml >.gitlab-ci.d/cirrus/$NAME.yml - - cat .gitlab-ci.d/cirrus/$NAME.yml - - cirrus-run -v --show-build-log always .gitlab-ci.d/cirrus/$NAME.yml - variables: - QEMU_JOB_CIRRUS: 1 - QEMU_JOB_OPTIONAL: 1 - - -x86-netbsd: - extends: .cirrus_kvm_job - variables: - NAME: netbsd - CONFIGURE_ARGS: --target-list=x86_64-softmmu,ppc64-softmmu,aarch64-softmmu - TEST_TARGETS: check - -x86-openbsd: - extends: .cirrus_kvm_job - variables: - NAME: openbsd - CONFIGURE_ARGS: --target-list=i386-softmmu,riscv64-softmmu,mips64-softmmu - TEST_TARGETS: check diff --git a/.gitlab-ci.d/cirrus/kvm-build.yml b/.gitlab-ci.d/cirrus/kvm-build.yml deleted file mode 100644 index a93881aa8b..0000000000 --- a/.gitlab-ci.d/cirrus/kvm-build.yml +++ /dev/null @@ -1,31 +0,0 @@ -container: - image: fedora:35 - cpu: 4 - memory: 8Gb - kvm: true - -env: - CIRRUS_CLONE_DEPTH: 1 - CI_REPOSITORY_URL: "@CI_REPOSITORY_URL@" - CI_COMMIT_REF_NAME: "@CI_COMMIT_REF_NAME@" - CI_COMMIT_SHA: "@CI_COMMIT_SHA@" - -@NAME@_task: - @NAME@_vm_cache: - folder: $HOME/.cache/qemu-vm - install_script: - - dnf update -y - - dnf install -y git make openssh-clients qemu-img qemu-system-x86 wget meson - clone_script: - - git clone --depth 100 "$CI_REPOSITORY_URL" . - - git fetch origin "$CI_COMMIT_REF_NAME" - - git reset --hard "$CI_COMMIT_SHA" - build_script: - - if [ -f $HOME/.cache/qemu-vm/images/@NAME@.img ]; then - make vm-build-@NAME@ J=$(getconf _NPROCESSORS_ONLN) - EXTRA_CONFIGURE_OPTS="@CONFIGURE_ARGS@" - BUILD_TARGET="@TEST_TARGETS@" ; - else - make vm-build-@NAME@ J=$(getconf _NPROCESSORS_ONLN) BUILD_TARGET=help - EXTRA_CONFIGURE_OPTS="--disable-system --disable-user --disable-tools" ; - fi diff --git a/.gitlab-ci.d/custom-runners.yml b/.gitlab-ci.d/custom-runners.yml index a0e79acd39..29e52df283 100644 --- a/.gitlab-ci.d/custom-runners.yml +++ b/.gitlab-ci.d/custom-runners.yml @@ -29,7 +29,7 @@ junit: build/meson-logs/testlog.junit.xml include: - - local: '/.gitlab-ci.d/custom-runners/ubuntu-20.04-s390x.yml' + - local: '/.gitlab-ci.d/custom-runners/ubuntu-22.04-s390x.yml' - local: '/.gitlab-ci.d/custom-runners/ubuntu-22.04-aarch64.yml' - local: '/.gitlab-ci.d/custom-runners/ubuntu-22.04-aarch32.yml' - local: '/.gitlab-ci.d/custom-runners/centos-stream-8-x86_64.yml' diff --git a/.gitlab-ci.d/custom-runners/ubuntu-20.04-s390x.yml b/.gitlab-ci.d/custom-runners/ubuntu-22.04-s390x.yml index cdae6c5212..105981879f 100644 --- a/.gitlab-ci.d/custom-runners/ubuntu-20.04-s390x.yml +++ b/.gitlab-ci.d/custom-runners/ubuntu-22.04-s390x.yml @@ -1,34 +1,32 @@ -# All ubuntu-20.04 jobs should run successfully in an environment +# All ubuntu-22.04 jobs should run successfully in an environment # setup by the scripts/ci/setup/build-environment.yml task -# "Install basic packages to build QEMU on Ubuntu 20.04/20.04" +# "Install basic packages to build QEMU on Ubuntu 22.04" -ubuntu-20.04-s390x-all-linux-static: +ubuntu-22.04-s390x-all-linux-static: extends: .custom_runner_template needs: [] stage: build tags: - - ubuntu_20.04 + - ubuntu_22.04 - s390x rules: - if: '$CI_PROJECT_NAMESPACE == "qemu-project" && $CI_COMMIT_BRANCH =~ /^staging/' - if: "$S390X_RUNNER_AVAILABLE" script: - # --disable-libssh is needed because of https://bugs.launchpad.net/qemu/+bug/1838763 - # --disable-glusterfs is needed because there's no static version of those libs in distro supplied packages - mkdir build - cd build - - ../configure --enable-debug --static --disable-system --disable-glusterfs --disable-libssh + - ../configure --enable-debug --static --disable-system || { cat config.log meson-logs/meson-log.txt; exit 1; } - make --output-sync -j`nproc` - make --output-sync check-tcg - make --output-sync -j`nproc` check -ubuntu-20.04-s390x-all: +ubuntu-22.04-s390x-all: extends: .custom_runner_template needs: [] stage: build tags: - - ubuntu_20.04 + - ubuntu_22.04 - s390x timeout: 75m rules: @@ -42,12 +40,12 @@ ubuntu-20.04-s390x-all: - make --output-sync -j`nproc` - make --output-sync -j`nproc` check -ubuntu-20.04-s390x-alldbg: +ubuntu-22.04-s390x-alldbg: extends: .custom_runner_template needs: [] stage: build tags: - - ubuntu_20.04 + - ubuntu_22.04 - s390x rules: - if: '$CI_PROJECT_NAMESPACE == "qemu-project" && $CI_COMMIT_BRANCH =~ /^staging/' @@ -65,12 +63,12 @@ ubuntu-20.04-s390x-alldbg: - make --output-sync -j`nproc` - make --output-sync -j`nproc` check -ubuntu-20.04-s390x-clang: +ubuntu-22.04-s390x-clang: extends: .custom_runner_template needs: [] stage: build tags: - - ubuntu_20.04 + - ubuntu_22.04 - s390x rules: - if: '$CI_PROJECT_NAMESPACE == "qemu-project" && $CI_COMMIT_BRANCH =~ /^staging/' @@ -87,11 +85,11 @@ ubuntu-20.04-s390x-clang: - make --output-sync -j`nproc` - make --output-sync -j`nproc` check -ubuntu-20.04-s390x-tci: +ubuntu-22.04-s390x-tci: needs: [] stage: build tags: - - ubuntu_20.04 + - ubuntu_22.04 - s390x rules: - if: '$CI_PROJECT_NAMESPACE == "qemu-project" && $CI_COMMIT_BRANCH =~ /^staging/' @@ -107,12 +105,12 @@ ubuntu-20.04-s390x-tci: || { cat config.log meson-logs/meson-log.txt; exit 1; } - make --output-sync -j`nproc` -ubuntu-20.04-s390x-notcg: +ubuntu-22.04-s390x-notcg: extends: .custom_runner_template needs: [] stage: build tags: - - ubuntu_20.04 + - ubuntu_22.04 - s390x rules: - if: '$CI_PROJECT_NAMESPACE == "qemu-project" && $CI_COMMIT_BRANCH =~ /^staging/' diff --git a/accel/tcg/plugin-gen.c b/accel/tcg/plugin-gen.c index cd78ef94a1..3db74ae9bf 100644 --- a/accel/tcg/plugin-gen.c +++ b/accel/tcg/plugin-gen.c @@ -14,36 +14,14 @@ * Injecting the desired instrumentation could be done with a second * translation pass that combined the instrumentation requests, but that * would be ugly and inefficient since we would decode the guest code twice. - * Instead, during TB translation we add "empty" instrumentation calls for all - * possible instrumentation events, and then once we collect the instrumentation - * requests from plugins, we either "fill in" those empty events or remove them - * if they have no requests. - * - * When "filling in" an event we first copy the empty callback's TCG ops. This - * might seem unnecessary, but it is done to support an arbitrary number - * of callbacks per event. Take for example a regular instruction callback. - * We first generate a callback to an empty helper function. Then, if two - * plugins register one callback each for this instruction, we make two copies - * of the TCG ops generated for the empty callback, substituting the function - * pointer that points to the empty helper function with the plugins' desired - * callback functions. After that we remove the empty callback's ops. - * - * Note that the location in TCGOp.args[] of the pointer to a helper function - * varies across different guest and host architectures. Instead of duplicating - * the logic that figures this out, we rely on the fact that the empty - * callbacks point to empty functions that are unique pointers in the program. - * Thus, to find the right location we just have to look for a match in - * TCGOp.args[]. This is the main reason why we first copy an empty callback's - * TCG ops and then fill them in; regardless of whether we have one or many - * callbacks for that event, the logic to add all of them is the same. - * - * When generating more than one callback per event, we make a small - * optimization to avoid generating redundant operations. For instance, for the - * second and all subsequent callbacks of an event, we do not need to reload the - * CPU's index into a TCG temp, since the first callback did it already. + * Instead, during TB translation we add "plugin_cb" marker opcodes + * for all possible instrumentation events, and then once we collect the + * instrumentation requests from plugins, we generate code for those markers + * or remove them if they have no requests. */ #include "qemu/osdep.h" #include "qemu/plugin.h" +#include "qemu/log.h" #include "cpu.h" #include "tcg/tcg.h" #include "tcg/tcg-temp-internal.h" @@ -51,819 +29,278 @@ #include "exec/exec-all.h" #include "exec/plugin-gen.h" #include "exec/translator.h" -#include "exec/helper-proto-common.h" - -#define HELPER_H "accel/tcg/plugin-helpers.h" -#include "exec/helper-info.c.inc" -#undef HELPER_H - -/* - * plugin_cb_start TCG op args[]: - * 0: enum plugin_gen_from - * 1: enum plugin_gen_cb - * 2: set to 1 for mem callback that is a write, 0 otherwise. - */ enum plugin_gen_from { PLUGIN_GEN_FROM_TB, PLUGIN_GEN_FROM_INSN, - PLUGIN_GEN_FROM_MEM, PLUGIN_GEN_AFTER_INSN, - PLUGIN_GEN_N_FROMS, -}; - -enum plugin_gen_cb { - PLUGIN_GEN_CB_UDATA, - PLUGIN_GEN_CB_UDATA_R, - PLUGIN_GEN_CB_INLINE, - PLUGIN_GEN_CB_MEM, - PLUGIN_GEN_ENABLE_MEM_HELPER, - PLUGIN_GEN_DISABLE_MEM_HELPER, - PLUGIN_GEN_N_CBS, + PLUGIN_GEN_AFTER_TB, }; -/* - * These helpers are stubs that get dynamically switched out for calls - * direct to the plugin if they are subscribed to. - */ -void HELPER(plugin_vcpu_udata_cb_no_wg)(uint32_t cpu_index, void *udata) -{ } +/* called before finishing a TB with exit_tb, goto_tb or goto_ptr */ +void plugin_gen_disable_mem_helpers(void) +{ + if (tcg_ctx->plugin_insn) { + tcg_gen_plugin_cb(PLUGIN_GEN_AFTER_TB); + } +} -void HELPER(plugin_vcpu_udata_cb_no_rwg)(uint32_t cpu_index, void *udata) -{ } +static void gen_enable_mem_helper(struct qemu_plugin_tb *ptb, + struct qemu_plugin_insn *insn) +{ + GArray *arr; + size_t len; -void HELPER(plugin_vcpu_mem_cb)(unsigned int vcpu_index, - qemu_plugin_meminfo_t info, uint64_t vaddr, - void *userdata) -{ } + /* + * Tracking memory accesses performed from helpers requires extra work. + * If an instruction is emulated with helpers, we do two things: + * (1) copy the CB descriptors, and keep track of it so that they can be + * freed later on, and (2) point CPUState.plugin_mem_cbs to the + * descriptors, so that we can read them at run-time + * (i.e. when the helper executes). + * This run-time access is performed from qemu_plugin_vcpu_mem_cb. + * + * Note that plugin_gen_disable_mem_helpers undoes (2). Since it + * is possible that the code we generate after the instruction is + * dead, we also add checks before generating tb_exit etc. + */ + if (!insn->calls_helpers) { + return; + } -static void gen_empty_udata_cb(void (*gen_helper)(TCGv_i32, TCGv_ptr)) -{ - TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - TCGv_ptr udata = tcg_temp_ebb_new_ptr(); + if (!insn->mem_cbs || !insn->mem_cbs->len) { + insn->mem_helper = false; + return; + } + insn->mem_helper = true; + ptb->mem_helper = true; - tcg_gen_movi_ptr(udata, 0); - tcg_gen_ld_i32(cpu_index, tcg_env, - -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); - gen_helper(cpu_index, udata); + /* + * TODO: It seems like we should be able to use ref/unref + * to avoid needing to actually copy this array. + * Alternately, perhaps we could allocate new memory adjacent + * to the TranslationBlock itself, so that we do not have to + * actively manage the lifetime after this. + */ + len = insn->mem_cbs->len; + arr = g_array_sized_new(false, false, + sizeof(struct qemu_plugin_dyn_cb), len); + memcpy(arr->data, insn->mem_cbs->data, + len * sizeof(struct qemu_plugin_dyn_cb)); + qemu_plugin_add_dyn_cb_arr(arr); - tcg_temp_free_ptr(udata); - tcg_temp_free_i32(cpu_index); + tcg_gen_st_ptr(tcg_constant_ptr((intptr_t)arr), tcg_env, + offsetof(CPUState, plugin_mem_cbs) - + offsetof(ArchCPU, env)); } -static void gen_empty_udata_cb_no_wg(void) +static void gen_disable_mem_helper(void) { - gen_empty_udata_cb(gen_helper_plugin_vcpu_udata_cb_no_wg); + tcg_gen_st_ptr(tcg_constant_ptr(0), tcg_env, + offsetof(CPUState, plugin_mem_cbs) - + offsetof(ArchCPU, env)); } -static void gen_empty_udata_cb_no_rwg(void) +static void gen_udata_cb(struct qemu_plugin_dyn_cb *cb) { - gen_empty_udata_cb(gen_helper_plugin_vcpu_udata_cb_no_rwg); + TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); + + tcg_gen_ld_i32(cpu_index, tcg_env, + -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); + tcg_gen_call2(cb->regular.f.vcpu_udata, cb->regular.info, NULL, + tcgv_i32_temp(cpu_index), + tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); + tcg_temp_free_i32(cpu_index); } -/* - * For now we only support addi_i64. - * When we support more ops, we can generate one empty inline cb for each. - */ -static void gen_empty_inline_cb(void) +static void gen_inline_cb(struct qemu_plugin_dyn_cb *cb) { + GArray *arr = cb->inline_insn.entry.score->data; + size_t offset = cb->inline_insn.entry.offset; TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - TCGv_ptr cpu_index_as_ptr = tcg_temp_ebb_new_ptr(); TCGv_i64 val = tcg_temp_ebb_new_i64(); TCGv_ptr ptr = tcg_temp_ebb_new_ptr(); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); - /* second operand will be replaced by immediate value */ - tcg_gen_mul_i32(cpu_index, cpu_index, cpu_index); - tcg_gen_ext_i32_ptr(cpu_index_as_ptr, cpu_index); + tcg_gen_muli_i32(cpu_index, cpu_index, g_array_get_element_size(arr)); + tcg_gen_ext_i32_ptr(ptr, cpu_index); + tcg_temp_free_i32(cpu_index); - tcg_gen_movi_ptr(ptr, 0); - tcg_gen_add_ptr(ptr, ptr, cpu_index_as_ptr); - tcg_gen_ld_i64(val, ptr, 0); - /* second operand will be replaced by immediate value */ - tcg_gen_add_i64(val, val, val); + tcg_gen_addi_ptr(ptr, ptr, (intptr_t)arr->data); + tcg_gen_ld_i64(val, ptr, offset); + tcg_gen_addi_i64(val, val, cb->inline_insn.imm); + tcg_gen_st_i64(val, ptr, offset); - tcg_gen_st_i64(val, ptr, 0); - tcg_temp_free_ptr(ptr); tcg_temp_free_i64(val); - tcg_temp_free_ptr(cpu_index_as_ptr); - tcg_temp_free_i32(cpu_index); + tcg_temp_free_ptr(ptr); } -static void gen_empty_mem_cb(TCGv_i64 addr, uint32_t info) +static void gen_mem_cb(struct qemu_plugin_dyn_cb *cb, + qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { TCGv_i32 cpu_index = tcg_temp_ebb_new_i32(); - TCGv_i32 meminfo = tcg_temp_ebb_new_i32(); - TCGv_ptr udata = tcg_temp_ebb_new_ptr(); - tcg_gen_movi_i32(meminfo, info); - tcg_gen_movi_ptr(udata, 0); tcg_gen_ld_i32(cpu_index, tcg_env, -offsetof(ArchCPU, env) + offsetof(CPUState, cpu_index)); - - gen_helper_plugin_vcpu_mem_cb(cpu_index, meminfo, addr, udata); - - tcg_temp_free_ptr(udata); - tcg_temp_free_i32(meminfo); + tcg_gen_call4(cb->regular.f.vcpu_mem, cb->regular.info, NULL, + tcgv_i32_temp(cpu_index), + tcgv_i32_temp(tcg_constant_i32(meminfo)), + tcgv_i64_temp(addr), + tcgv_ptr_temp(tcg_constant_ptr(cb->userp))); tcg_temp_free_i32(cpu_index); } -/* - * Share the same function for enable/disable. When enabling, the NULL - * pointer will be overwritten later. - */ -static void gen_empty_mem_helper(void) -{ - TCGv_ptr ptr = tcg_temp_ebb_new_ptr(); - - tcg_gen_movi_ptr(ptr, 0); - tcg_gen_st_ptr(ptr, tcg_env, offsetof(CPUState, plugin_mem_cbs) - - offsetof(ArchCPU, env)); - tcg_temp_free_ptr(ptr); -} - -static void gen_plugin_cb_start(enum plugin_gen_from from, - enum plugin_gen_cb type, unsigned wr) -{ - tcg_gen_plugin_cb_start(from, type, wr); -} - -static void gen_wrapped(enum plugin_gen_from from, - enum plugin_gen_cb type, void (*func)(void)) -{ - gen_plugin_cb_start(from, type, 0); - func(); - tcg_gen_plugin_cb_end(); -} +static void inject_cb(struct qemu_plugin_dyn_cb *cb) -static void plugin_gen_empty_callback(enum plugin_gen_from from) { - switch (from) { - case PLUGIN_GEN_AFTER_INSN: - gen_wrapped(from, PLUGIN_GEN_DISABLE_MEM_HELPER, - gen_empty_mem_helper); + switch (cb->type) { + case PLUGIN_CB_REGULAR: + gen_udata_cb(cb); break; - case PLUGIN_GEN_FROM_INSN: - /* - * Note: plugin_gen_inject() relies on ENABLE_MEM_HELPER being - * the first callback of an instruction - */ - gen_wrapped(from, PLUGIN_GEN_ENABLE_MEM_HELPER, - gen_empty_mem_helper); - /* fall through */ - case PLUGIN_GEN_FROM_TB: - gen_wrapped(from, PLUGIN_GEN_CB_UDATA, gen_empty_udata_cb_no_rwg); - gen_wrapped(from, PLUGIN_GEN_CB_UDATA_R, gen_empty_udata_cb_no_wg); - gen_wrapped(from, PLUGIN_GEN_CB_INLINE, gen_empty_inline_cb); + case PLUGIN_CB_INLINE: + gen_inline_cb(cb); break; default: g_assert_not_reached(); } } -void plugin_gen_empty_mem_callback(TCGv_i64 addr, uint32_t info) +static void inject_mem_cb(struct qemu_plugin_dyn_cb *cb, + enum qemu_plugin_mem_rw rw, + qemu_plugin_meminfo_t meminfo, TCGv_i64 addr) { - enum qemu_plugin_mem_rw rw = get_plugin_meminfo_rw(info); - - gen_plugin_cb_start(PLUGIN_GEN_FROM_MEM, PLUGIN_GEN_CB_MEM, rw); - gen_empty_mem_cb(addr, info); - tcg_gen_plugin_cb_end(); - - gen_plugin_cb_start(PLUGIN_GEN_FROM_MEM, PLUGIN_GEN_CB_INLINE, rw); - gen_empty_inline_cb(); - tcg_gen_plugin_cb_end(); -} - -static TCGOp *find_op(TCGOp *op, TCGOpcode opc) -{ - while (op) { - if (op->opc == opc) { - return op; + if (cb->rw & rw) { + switch (cb->type) { + case PLUGIN_CB_MEM_REGULAR: + gen_mem_cb(cb, meminfo, addr); + break; + default: + inject_cb(cb); + break; } - op = QTAILQ_NEXT(op, link); } - return NULL; -} - -static TCGOp *rm_ops_range(TCGOp *begin, TCGOp *end) -{ - TCGOp *ret = QTAILQ_NEXT(end, link); - - QTAILQ_REMOVE_SEVERAL(&tcg_ctx->ops, begin, end, link); - return ret; -} - -/* remove all ops until (and including) plugin_cb_end */ -static TCGOp *rm_ops(TCGOp *op) -{ - TCGOp *end_op = find_op(op, INDEX_op_plugin_cb_end); - - tcg_debug_assert(end_op); - return rm_ops_range(op, end_op); -} - -static TCGOp *copy_op_nocheck(TCGOp **begin_op, TCGOp *op) -{ - TCGOp *old_op = QTAILQ_NEXT(*begin_op, link); - unsigned nargs = old_op->nargs; - - *begin_op = old_op; - op = tcg_op_insert_after(tcg_ctx, op, old_op->opc, nargs); - memcpy(op->args, old_op->args, sizeof(op->args[0]) * nargs); - - return op; -} - -static TCGOp *copy_op(TCGOp **begin_op, TCGOp *op, TCGOpcode opc) -{ - op = copy_op_nocheck(begin_op, op); - tcg_debug_assert((*begin_op)->opc == opc); - return op; -} - -static TCGOp *copy_const_ptr(TCGOp **begin_op, TCGOp *op, void *ptr) -{ - if (UINTPTR_MAX == UINT32_MAX) { - /* mov_i32 */ - op = copy_op(begin_op, op, INDEX_op_mov_i32); - op->args[1] = tcgv_i32_arg(tcg_constant_i32((uintptr_t)ptr)); - } else { - /* mov_i64 */ - op = copy_op(begin_op, op, INDEX_op_mov_i64); - op->args[1] = tcgv_i64_arg(tcg_constant_i64((uintptr_t)ptr)); - } - return op; -} - -static TCGOp *copy_ld_i32(TCGOp **begin_op, TCGOp *op) -{ - return copy_op(begin_op, op, INDEX_op_ld_i32); -} - -static TCGOp *copy_ext_i32_ptr(TCGOp **begin_op, TCGOp *op) -{ - if (UINTPTR_MAX == UINT32_MAX) { - op = copy_op(begin_op, op, INDEX_op_mov_i32); - } else { - op = copy_op(begin_op, op, INDEX_op_ext_i32_i64); - } - return op; -} - -static TCGOp *copy_add_ptr(TCGOp **begin_op, TCGOp *op) -{ - if (UINTPTR_MAX == UINT32_MAX) { - op = copy_op(begin_op, op, INDEX_op_add_i32); - } else { - op = copy_op(begin_op, op, INDEX_op_add_i64); - } - return op; -} - -static TCGOp *copy_ld_i64(TCGOp **begin_op, TCGOp *op) -{ - if (TCG_TARGET_REG_BITS == 32) { - /* 2x ld_i32 */ - op = copy_ld_i32(begin_op, op); - op = copy_ld_i32(begin_op, op); - } else { - /* ld_i64 */ - op = copy_op(begin_op, op, INDEX_op_ld_i64); - } - return op; -} - -static TCGOp *copy_st_i64(TCGOp **begin_op, TCGOp *op) -{ - if (TCG_TARGET_REG_BITS == 32) { - /* 2x st_i32 */ - op = copy_op(begin_op, op, INDEX_op_st_i32); - op = copy_op(begin_op, op, INDEX_op_st_i32); - } else { - /* st_i64 */ - op = copy_op(begin_op, op, INDEX_op_st_i64); - } - return op; -} - -static TCGOp *copy_add_i64(TCGOp **begin_op, TCGOp *op, uint64_t v) -{ - if (TCG_TARGET_REG_BITS == 32) { - /* all 32-bit backends must implement add2_i32 */ - g_assert(TCG_TARGET_HAS_add2_i32); - op = copy_op(begin_op, op, INDEX_op_add2_i32); - op->args[4] = tcgv_i32_arg(tcg_constant_i32(v)); - op->args[5] = tcgv_i32_arg(tcg_constant_i32(v >> 32)); - } else { - op = copy_op(begin_op, op, INDEX_op_add_i64); - op->args[2] = tcgv_i64_arg(tcg_constant_i64(v)); - } - return op; -} - -static TCGOp *copy_mul_i32(TCGOp **begin_op, TCGOp *op, uint32_t v) -{ - op = copy_op(begin_op, op, INDEX_op_mul_i32); - op->args[2] = tcgv_i32_arg(tcg_constant_i32(v)); - return op; -} - -static TCGOp *copy_st_ptr(TCGOp **begin_op, TCGOp *op) -{ - if (UINTPTR_MAX == UINT32_MAX) { - /* st_i32 */ - op = copy_op(begin_op, op, INDEX_op_st_i32); - } else { - /* st_i64 */ - op = copy_st_i64(begin_op, op); - } - return op; -} - -static TCGOp *copy_call(TCGOp **begin_op, TCGOp *op, void *func, int *cb_idx) -{ - TCGOp *old_op; - int func_idx; - - /* copy all ops until the call */ - do { - op = copy_op_nocheck(begin_op, op); - } while (op->opc != INDEX_op_call); - - /* fill in the op call */ - old_op = *begin_op; - TCGOP_CALLI(op) = TCGOP_CALLI(old_op); - TCGOP_CALLO(op) = TCGOP_CALLO(old_op); - tcg_debug_assert(op->life == 0); - - func_idx = TCGOP_CALLO(op) + TCGOP_CALLI(op); - *cb_idx = func_idx; - op->args[func_idx] = (uintptr_t)func; - - return op; -} - -/* - * When we append/replace ops here we are sensitive to changing patterns of - * TCGOps generated by the tcg_gen_FOO calls when we generated the - * empty callbacks. This will assert very quickly in a debug build as - * we assert the ops we are replacing are the correct ones. - */ -static TCGOp *append_udata_cb(const struct qemu_plugin_dyn_cb *cb, - TCGOp *begin_op, TCGOp *op, int *cb_idx) -{ - /* const_ptr */ - op = copy_const_ptr(&begin_op, op, cb->userp); - - /* copy the ld_i32, but note that we only have to copy it once */ - if (*cb_idx == -1) { - op = copy_op(&begin_op, op, INDEX_op_ld_i32); - } else { - begin_op = QTAILQ_NEXT(begin_op, link); - tcg_debug_assert(begin_op && begin_op->opc == INDEX_op_ld_i32); - } - - /* call */ - op = copy_call(&begin_op, op, cb->f.vcpu_udata, cb_idx); - - return op; -} - -static TCGOp *append_inline_cb(const struct qemu_plugin_dyn_cb *cb, - TCGOp *begin_op, TCGOp *op, - int *unused) -{ - char *ptr = cb->inline_insn.entry.score->data->data; - size_t elem_size = g_array_get_element_size( - cb->inline_insn.entry.score->data); - size_t offset = cb->inline_insn.entry.offset; - - op = copy_ld_i32(&begin_op, op); - op = copy_mul_i32(&begin_op, op, elem_size); - op = copy_ext_i32_ptr(&begin_op, op); - op = copy_const_ptr(&begin_op, op, ptr + offset); - op = copy_add_ptr(&begin_op, op); - op = copy_ld_i64(&begin_op, op); - op = copy_add_i64(&begin_op, op, cb->inline_insn.imm); - op = copy_st_i64(&begin_op, op); - return op; -} - -static TCGOp *append_mem_cb(const struct qemu_plugin_dyn_cb *cb, - TCGOp *begin_op, TCGOp *op, int *cb_idx) -{ - enum plugin_gen_cb type = begin_op->args[1]; - - tcg_debug_assert(type == PLUGIN_GEN_CB_MEM); - - /* const_i32 == mov_i32 ("info", so it remains as is) */ - op = copy_op(&begin_op, op, INDEX_op_mov_i32); - - /* const_ptr */ - op = copy_const_ptr(&begin_op, op, cb->userp); - - /* copy the ld_i32, but note that we only have to copy it once */ - if (*cb_idx == -1) { - op = copy_op(&begin_op, op, INDEX_op_ld_i32); - } else { - begin_op = QTAILQ_NEXT(begin_op, link); - tcg_debug_assert(begin_op && begin_op->opc == INDEX_op_ld_i32); - } - - if (type == PLUGIN_GEN_CB_MEM) { - /* call */ - op = copy_call(&begin_op, op, cb->f.vcpu_udata, cb_idx); - } - - return op; -} - -typedef TCGOp *(*inject_fn)(const struct qemu_plugin_dyn_cb *cb, - TCGOp *begin_op, TCGOp *op, int *intp); -typedef bool (*op_ok_fn)(const TCGOp *op, const struct qemu_plugin_dyn_cb *cb); - -static bool op_ok(const TCGOp *op, const struct qemu_plugin_dyn_cb *cb) -{ - return true; } -static bool op_rw(const TCGOp *op, const struct qemu_plugin_dyn_cb *cb) -{ - int w; - - w = op->args[2]; - return !!(cb->rw & (w + 1)); -} - -static void inject_cb_type(const GArray *cbs, TCGOp *begin_op, - inject_fn inject, op_ok_fn ok) +static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) { - TCGOp *end_op; - TCGOp *op; - int cb_idx = -1; - int i; - - if (!cbs || cbs->len == 0) { - rm_ops(begin_op); - return; - } - - end_op = find_op(begin_op, INDEX_op_plugin_cb_end); - tcg_debug_assert(end_op); - - op = end_op; - for (i = 0; i < cbs->len; i++) { - struct qemu_plugin_dyn_cb *cb = - &g_array_index(cbs, struct qemu_plugin_dyn_cb, i); + TCGOp *op, *next; + int insn_idx = -1; - if (!ok(begin_op, cb)) { - continue; + if (unlikely(qemu_loglevel_mask(LOG_TB_OP_PLUGIN) + && qemu_log_in_addr_range(plugin_tb->vaddr))) { + FILE *logfile = qemu_log_trylock(); + if (logfile) { + fprintf(logfile, "OP before plugin injection:\n"); + tcg_dump_ops(tcg_ctx, logfile, false); + fprintf(logfile, "\n"); + qemu_log_unlock(logfile); } - op = inject(cb, begin_op, op, &cb_idx); - } - rm_ops_range(begin_op, end_op); -} - -static void -inject_udata_cb(const GArray *cbs, TCGOp *begin_op) -{ - inject_cb_type(cbs, begin_op, append_udata_cb, op_ok); -} - -static void -inject_inline_cb(const GArray *cbs, TCGOp *begin_op, op_ok_fn ok) -{ - inject_cb_type(cbs, begin_op, append_inline_cb, ok); -} - -static void -inject_mem_cb(const GArray *cbs, TCGOp *begin_op) -{ - inject_cb_type(cbs, begin_op, append_mem_cb, op_rw); -} - -/* we could change the ops in place, but we can reuse more code by copying */ -static void inject_mem_helper(TCGOp *begin_op, GArray *arr) -{ - TCGOp *orig_op = begin_op; - TCGOp *end_op; - TCGOp *op; - - end_op = find_op(begin_op, INDEX_op_plugin_cb_end); - tcg_debug_assert(end_op); - - /* const ptr */ - op = copy_const_ptr(&begin_op, end_op, arr); - - /* st_ptr */ - op = copy_st_ptr(&begin_op, op); - - rm_ops_range(orig_op, end_op); -} - -/* - * Tracking memory accesses performed from helpers requires extra work. - * If an instruction is emulated with helpers, we do two things: - * (1) copy the CB descriptors, and keep track of it so that they can be - * freed later on, and (2) point CPUState.plugin_mem_cbs to the descriptors, so - * that we can read them at run-time (i.e. when the helper executes). - * This run-time access is performed from qemu_plugin_vcpu_mem_cb. - * - * Note that plugin_gen_disable_mem_helpers undoes (2). Since it - * is possible that the code we generate after the instruction is - * dead, we also add checks before generating tb_exit etc. - */ -static void inject_mem_enable_helper(struct qemu_plugin_tb *ptb, - struct qemu_plugin_insn *plugin_insn, - TCGOp *begin_op) -{ - GArray *cbs[2]; - GArray *arr; - size_t n_cbs, i; - - cbs[0] = plugin_insn->cbs[PLUGIN_CB_MEM][PLUGIN_CB_REGULAR]; - cbs[1] = plugin_insn->cbs[PLUGIN_CB_MEM][PLUGIN_CB_INLINE]; - - n_cbs = 0; - for (i = 0; i < ARRAY_SIZE(cbs); i++) { - n_cbs += cbs[i]->len; - } - - plugin_insn->mem_helper = plugin_insn->calls_helpers && n_cbs; - if (likely(!plugin_insn->mem_helper)) { - rm_ops(begin_op); - return; } - ptb->mem_helper = true; - arr = g_array_sized_new(false, false, - sizeof(struct qemu_plugin_dyn_cb), n_cbs); - - for (i = 0; i < ARRAY_SIZE(cbs); i++) { - g_array_append_vals(arr, cbs[i]->data, cbs[i]->len); - } - - qemu_plugin_add_dyn_cb_arr(arr); - inject_mem_helper(begin_op, arr); -} - -static void inject_mem_disable_helper(struct qemu_plugin_insn *plugin_insn, - TCGOp *begin_op) -{ - if (likely(!plugin_insn->mem_helper)) { - rm_ops(begin_op); - return; - } - inject_mem_helper(begin_op, NULL); -} - -/* called before finishing a TB with exit_tb, goto_tb or goto_ptr */ -void plugin_gen_disable_mem_helpers(void) -{ /* - * We could emit the clearing unconditionally and be done. However, this can - * be wasteful if for instance plugins don't track memory accesses, or if - * most TBs don't use helpers. Instead, emit the clearing iff the TB calls - * helpers that might access guest memory. - * - * Note: we do not reset plugin_tb->mem_helper here; a TB might have several - * exit points, and we want to emit the clearing from all of them. + * While injecting code, we cannot afford to reuse any ebb temps + * that might be live within the existing opcode stream. + * The simplest solution is to release them all and create new. */ - if (!tcg_ctx->plugin_tb->mem_helper) { - return; - } - tcg_gen_st_ptr(tcg_constant_ptr(NULL), tcg_env, - offsetof(CPUState, plugin_mem_cbs) - offsetof(ArchCPU, env)); -} - -static void plugin_gen_tb_udata(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op) -{ - inject_udata_cb(ptb->cbs[PLUGIN_CB_REGULAR], begin_op); -} - -static void plugin_gen_tb_udata_r(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op) -{ - inject_udata_cb(ptb->cbs[PLUGIN_CB_REGULAR_R], begin_op); -} - -static void plugin_gen_tb_inline(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op) -{ - inject_inline_cb(ptb->cbs[PLUGIN_CB_INLINE], begin_op, op_ok); -} + memset(tcg_ctx->free_temps, 0, sizeof(tcg_ctx->free_temps)); -static void plugin_gen_insn_udata(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op, int insn_idx) -{ - struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx); - - inject_udata_cb(insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_REGULAR], begin_op); -} - -static void plugin_gen_insn_udata_r(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op, int insn_idx) -{ - struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx); - - inject_udata_cb(insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_REGULAR_R], begin_op); -} - -static void plugin_gen_insn_inline(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op, int insn_idx) -{ - struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx); - inject_inline_cb(insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_INLINE], - begin_op, op_ok); -} - -static void plugin_gen_mem_regular(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op, int insn_idx) -{ - struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx); - inject_mem_cb(insn->cbs[PLUGIN_CB_MEM][PLUGIN_CB_REGULAR], begin_op); -} - -static void plugin_gen_mem_inline(const struct qemu_plugin_tb *ptb, - TCGOp *begin_op, int insn_idx) -{ - const GArray *cbs; - struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx); - - cbs = insn->cbs[PLUGIN_CB_MEM][PLUGIN_CB_INLINE]; - inject_inline_cb(cbs, begin_op, op_rw); -} - -static void plugin_gen_enable_mem_helper(struct qemu_plugin_tb *ptb, - TCGOp *begin_op, int insn_idx) -{ - struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx); - inject_mem_enable_helper(ptb, insn, begin_op); -} - -static void plugin_gen_disable_mem_helper(struct qemu_plugin_tb *ptb, - TCGOp *begin_op, int insn_idx) -{ - struct qemu_plugin_insn *insn = g_ptr_array_index(ptb->insns, insn_idx); - inject_mem_disable_helper(insn, begin_op); -} - -/* #define DEBUG_PLUGIN_GEN_OPS */ -static void pr_ops(void) -{ -#ifdef DEBUG_PLUGIN_GEN_OPS - TCGOp *op; - int i = 0; - - QTAILQ_FOREACH(op, &tcg_ctx->ops, link) { - const char *name = ""; - const char *type = ""; - - if (op->opc == INDEX_op_plugin_cb_start) { - switch (op->args[0]) { - case PLUGIN_GEN_FROM_TB: - name = "tb"; - break; - case PLUGIN_GEN_FROM_INSN: - name = "insn"; - break; - case PLUGIN_GEN_FROM_MEM: - name = "mem"; - break; - case PLUGIN_GEN_AFTER_INSN: - name = "after insn"; - break; - default: - break; - } - switch (op->args[1]) { - case PLUGIN_GEN_CB_UDATA: - type = "udata"; - break; - case PLUGIN_GEN_CB_INLINE: - type = "inline"; - break; - case PLUGIN_GEN_CB_MEM: - type = "mem"; - break; - case PLUGIN_GEN_ENABLE_MEM_HELPER: - type = "enable mem helper"; - break; - case PLUGIN_GEN_DISABLE_MEM_HELPER: - type = "disable mem helper"; - break; - default: - break; - } - } - printf("op[%2i]: %s %s %s\n", i, tcg_op_defs[op->opc].name, name, type); - i++; - } -#endif -} - -static void plugin_gen_inject(struct qemu_plugin_tb *plugin_tb) -{ - TCGOp *op; - int insn_idx = -1; - - pr_ops(); - - QTAILQ_FOREACH(op, &tcg_ctx->ops, link) { + QTAILQ_FOREACH_SAFE(op, &tcg_ctx->ops, link, next) { switch (op->opc) { case INDEX_op_insn_start: insn_idx++; break; - case INDEX_op_plugin_cb_start: + + case INDEX_op_plugin_cb: { enum plugin_gen_from from = op->args[0]; - enum plugin_gen_cb type = op->args[1]; + struct qemu_plugin_insn *insn = NULL; + const GArray *cbs; + int i, n; + + if (insn_idx >= 0) { + insn = g_ptr_array_index(plugin_tb->insns, insn_idx); + } + + tcg_ctx->emit_before_op = op; switch (from) { - case PLUGIN_GEN_FROM_TB: - { - g_assert(insn_idx == -1); - - switch (type) { - case PLUGIN_GEN_CB_UDATA: - plugin_gen_tb_udata(plugin_tb, op); - break; - case PLUGIN_GEN_CB_UDATA_R: - plugin_gen_tb_udata_r(plugin_tb, op); - break; - case PLUGIN_GEN_CB_INLINE: - plugin_gen_tb_inline(plugin_tb, op); - break; - default: - g_assert_not_reached(); + case PLUGIN_GEN_AFTER_TB: + if (plugin_tb->mem_helper) { + gen_disable_mem_helper(); } break; - } - case PLUGIN_GEN_FROM_INSN: - { - g_assert(insn_idx >= 0); - - switch (type) { - case PLUGIN_GEN_CB_UDATA: - plugin_gen_insn_udata(plugin_tb, op, insn_idx); - break; - case PLUGIN_GEN_CB_UDATA_R: - plugin_gen_insn_udata_r(plugin_tb, op, insn_idx); - break; - case PLUGIN_GEN_CB_INLINE: - plugin_gen_insn_inline(plugin_tb, op, insn_idx); - break; - case PLUGIN_GEN_ENABLE_MEM_HELPER: - plugin_gen_enable_mem_helper(plugin_tb, op, insn_idx); - break; - default: - g_assert_not_reached(); + + case PLUGIN_GEN_AFTER_INSN: + assert(insn != NULL); + if (insn->mem_helper) { + gen_disable_mem_helper(); } break; - } - case PLUGIN_GEN_FROM_MEM: - { - g_assert(insn_idx >= 0); - - switch (type) { - case PLUGIN_GEN_CB_MEM: - plugin_gen_mem_regular(plugin_tb, op, insn_idx); - break; - case PLUGIN_GEN_CB_INLINE: - plugin_gen_mem_inline(plugin_tb, op, insn_idx); - break; - default: - g_assert_not_reached(); - } + case PLUGIN_GEN_FROM_TB: + assert(insn == NULL); + + cbs = plugin_tb->cbs; + for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) { + inject_cb( + &g_array_index(cbs, struct qemu_plugin_dyn_cb, i)); + } break; - } - case PLUGIN_GEN_AFTER_INSN: - { - g_assert(insn_idx >= 0); - - switch (type) { - case PLUGIN_GEN_DISABLE_MEM_HELPER: - plugin_gen_disable_mem_helper(plugin_tb, op, insn_idx); - break; - default: - g_assert_not_reached(); + + case PLUGIN_GEN_FROM_INSN: + assert(insn != NULL); + + gen_enable_mem_helper(plugin_tb, insn); + + cbs = insn->insn_cbs; + for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) { + inject_cb( + &g_array_index(cbs, struct qemu_plugin_dyn_cb, i)); } break; - } + default: g_assert_not_reached(); } + + tcg_ctx->emit_before_op = NULL; + tcg_op_remove(tcg_ctx, op); + break; + } + + case INDEX_op_plugin_mem_cb: + { + TCGv_i64 addr = temp_tcgv_i64(arg_temp(op->args[0])); + qemu_plugin_meminfo_t meminfo = op->args[1]; + enum qemu_plugin_mem_rw rw = + (qemu_plugin_mem_is_store(meminfo) + ? QEMU_PLUGIN_MEM_W : QEMU_PLUGIN_MEM_R); + struct qemu_plugin_insn *insn; + const GArray *cbs; + int i, n; + + assert(insn_idx >= 0); + insn = g_ptr_array_index(plugin_tb->insns, insn_idx); + + tcg_ctx->emit_before_op = op; + + cbs = insn->mem_cbs; + for (i = 0, n = (cbs ? cbs->len : 0); i < n; i++) { + inject_mem_cb(&g_array_index(cbs, struct qemu_plugin_dyn_cb, i), + rw, meminfo, addr); + } + + tcg_ctx->emit_before_op = NULL; + tcg_op_remove(tcg_ctx, op); break; } + default: /* plugins don't care about any other ops */ break; } } - pr_ops(); } bool plugin_gen_tb_start(CPUState *cpu, const DisasContextBase *db, @@ -873,13 +310,10 @@ bool plugin_gen_tb_start(CPUState *cpu, const DisasContextBase *db, if (test_bit(QEMU_PLUGIN_EV_VCPU_TB_TRANS, cpu->plugin_state->event_mask)) { struct qemu_plugin_tb *ptb = tcg_ctx->plugin_tb; - int i; /* reset callbacks */ - for (i = 0; i < PLUGIN_N_CB_SUBTYPES; i++) { - if (ptb->cbs[i]) { - g_array_set_size(ptb->cbs[i], 0); - } + if (ptb->cbs) { + g_array_set_size(ptb->cbs, 0); } ptb->n = 0; @@ -892,7 +326,7 @@ bool plugin_gen_tb_start(CPUState *cpu, const DisasContextBase *db, ptb->mem_only = mem_only; ptb->mem_helper = false; - plugin_gen_empty_callback(PLUGIN_GEN_FROM_TB); + tcg_gen_plugin_cb(PLUGIN_GEN_FROM_TB); } tcg_ctx->plugin_insn = NULL; @@ -903,11 +337,34 @@ bool plugin_gen_tb_start(CPUState *cpu, const DisasContextBase *db, void plugin_gen_insn_start(CPUState *cpu, const DisasContextBase *db) { struct qemu_plugin_tb *ptb = tcg_ctx->plugin_tb; - struct qemu_plugin_insn *pinsn; + struct qemu_plugin_insn *insn; + size_t n = db->num_insns; + vaddr pc; + + assert(n >= 1); + ptb->n = n; + if (n <= ptb->insns->len) { + insn = g_ptr_array_index(ptb->insns, n - 1); + g_byte_array_set_size(insn->data, 0); + } else { + assert(n - 1 == ptb->insns->len); + insn = g_new0(struct qemu_plugin_insn, 1); + insn->data = g_byte_array_sized_new(4); + g_ptr_array_add(ptb->insns, insn); + } - pinsn = qemu_plugin_tb_insn_get(ptb, db->pc_next); - tcg_ctx->plugin_insn = pinsn; - plugin_gen_empty_callback(PLUGIN_GEN_FROM_INSN); + tcg_ctx->plugin_insn = insn; + insn->calls_helpers = false; + insn->mem_helper = false; + if (insn->insn_cbs) { + g_array_set_size(insn->insn_cbs, 0); + } + if (insn->mem_cbs) { + g_array_set_size(insn->mem_cbs, 0); + } + + pc = db->pc_next; + insn->vaddr = pc; /* * Detect page crossing to get the new host address. @@ -915,21 +372,23 @@ void plugin_gen_insn_start(CPUState *cpu, const DisasContextBase *db) * fetching instructions from a region not backed by RAM. */ if (ptb->haddr1 == NULL) { - pinsn->haddr = NULL; + insn->haddr = NULL; } else if (is_same_page(db, db->pc_next)) { - pinsn->haddr = ptb->haddr1 + pinsn->vaddr - ptb->vaddr; + insn->haddr = ptb->haddr1 + pc - ptb->vaddr; } else { if (ptb->vaddr2 == -1) { ptb->vaddr2 = TARGET_PAGE_ALIGN(db->pc_first); get_page_addr_code_hostp(cpu_env(cpu), ptb->vaddr2, &ptb->haddr2); } - pinsn->haddr = ptb->haddr2 + pinsn->vaddr - ptb->vaddr2; + insn->haddr = ptb->haddr2 + pc - ptb->vaddr2; } + + tcg_gen_plugin_cb(PLUGIN_GEN_FROM_INSN); } void plugin_gen_insn_end(void) { - plugin_gen_empty_callback(PLUGIN_GEN_AFTER_INSN); + tcg_gen_plugin_cb(PLUGIN_GEN_AFTER_INSN); } /* diff --git a/accel/tcg/plugin-helpers.h b/accel/tcg/plugin-helpers.h deleted file mode 100644 index 11796436f3..0000000000 --- a/accel/tcg/plugin-helpers.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifdef CONFIG_PLUGIN -DEF_HELPER_FLAGS_2(plugin_vcpu_udata_cb_no_wg, TCG_CALL_NO_WG | TCG_CALL_PLUGIN, void, i32, ptr) -DEF_HELPER_FLAGS_2(plugin_vcpu_udata_cb_no_rwg, TCG_CALL_NO_RWG | TCG_CALL_PLUGIN, void, i32, ptr) -DEF_HELPER_FLAGS_4(plugin_vcpu_mem_cb, TCG_CALL_NO_RWG | TCG_CALL_PLUGIN, void, i32, i32, i64, ptr) -#endif diff --git a/backends/cryptodev-builtin.c b/backends/cryptodev-builtin.c index a514bbb310..940104ee55 100644 --- a/backends/cryptodev-builtin.c +++ b/backends/cryptodev-builtin.c @@ -23,6 +23,7 @@ #include "qemu/osdep.h" #include "sysemu/cryptodev.h" +#include "qemu/error-report.h" #include "qapi/error.h" #include "standard-headers/linux/virtio_crypto.h" #include "crypto/cipher.h" @@ -396,8 +397,8 @@ static int cryptodev_builtin_create_session( case VIRTIO_CRYPTO_HASH_CREATE_SESSION: case VIRTIO_CRYPTO_MAC_CREATE_SESSION: default: - error_setg(&local_error, "Unsupported opcode :%" PRIu32 "", - sess_info->op_code); + error_report("Unsupported opcode :%" PRIu32 "", + sess_info->op_code); return -VIRTIO_CRYPTO_NOTSUPP; } @@ -554,8 +555,8 @@ static int cryptodev_builtin_operation( if (op_info->session_id >= MAX_NUM_SESSIONS || builtin->sessions[op_info->session_id] == NULL) { - error_setg(&local_error, "Cannot find a valid session id: %" PRIu64 "", - op_info->session_id); + error_report("Cannot find a valid session id: %" PRIu64 "", + op_info->session_id); return -VIRTIO_CRYPTO_INVSESS; } diff --git a/docs/system/arm/b-l475e-iot01a.rst b/docs/system/arm/b-l475e-iot01a.rst index a76c9976c5..2adcc4b4c1 100644 --- a/docs/system/arm/b-l475e-iot01a.rst +++ b/docs/system/arm/b-l475e-iot01a.rst @@ -12,7 +12,7 @@ USART, I2C, SPI, CAN and USB OTG, as well as a variety of sensors. Supported devices """"""""""""""""" -Currently B-L475E-IOT01A machine's only supports the following devices: +Currently B-L475E-IOT01A machines support the following devices: - Cortex-M4F based STM32L4x5 SoC - STM32L4x5 EXTI (Extended interrupts and events controller) @@ -20,6 +20,7 @@ Currently B-L475E-IOT01A machine's only supports the following devices: - STM32L4x5 RCC (Reset and clock control) - STM32L4x5 GPIOs (General-purpose I/Os) - STM32L4x5 USARTs, UARTs and LPUART (Serial ports) +- optional 8x8 led display (based on DM163 driver) Missing devices """"""""""""""" diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst index a9ae7ede9f..7fcea54d8d 100644 --- a/docs/system/arm/emulation.rst +++ b/docs/system/arm/emulation.rst @@ -8,36 +8,60 @@ Armv8 versions of the A-profile architecture. It also has support for the following architecture extensions: - FEAT_AA32BF16 (AArch32 BFloat16 instructions) +- FEAT_AA32EL0 (Support for AArch32 at EL0) +- FEAT_AA32EL1 (Support for AArch32 at EL1) +- FEAT_AA32EL2 (Support for AArch32 at EL2) +- FEAT_AA32EL3 (Support for AArch32 at EL3) - FEAT_AA32HPD (AArch32 hierarchical permission disables) - FEAT_AA32I8MM (AArch32 Int8 matrix multiplication instructions) +- FEAT_AA64EL0 (Support for AArch64 at EL0) +- FEAT_AA64EL1 (Support for AArch64 at EL1) +- FEAT_AA64EL2 (Support for AArch64 at EL2) +- FEAT_AA64EL3 (Support for AArch64 at EL3) +- FEAT_AdvSIMD (Advanced SIMD Extension) - FEAT_AES (AESD and AESE instructions) +- FEAT_Armv9_Crypto (Armv9 Cryptographic Extension) +- FEAT_ASID16 (16 bit ASID) - FEAT_BBM at level 2 (Translation table break-before-make levels) - FEAT_BF16 (AArch64 BFloat16 instructions) - FEAT_BTI (Branch Target Identification) +- FEAT_CCIDX (Extended cache index) - FEAT_CRC32 (CRC32 instructions) +- FEAT_Crypto (Cryptographic Extension) - FEAT_CSV2 (Cache speculation variant 2) - FEAT_CSV2_1p1 (Cache speculation variant 2, version 1.1) - FEAT_CSV2_1p2 (Cache speculation variant 2, version 1.2) - FEAT_CSV2_2 (Cache speculation variant 2, version 2) +- FEAT_CSV2_3 (Cache speculation variant 2, version 3) - FEAT_CSV3 (Cache speculation variant 3) - FEAT_DGH (Data gathering hint) - FEAT_DIT (Data Independent Timing instructions) - FEAT_DPB (DC CVAP instruction) +- FEAT_DPB2 (DC CVADP instruction) +- FEAT_Debugv8p1 (Debug with VHE) - FEAT_Debugv8p2 (Debug changes for v8.2) - FEAT_Debugv8p4 (Debug changes for v8.4) - FEAT_DotProd (Advanced SIMD dot product instructions) - FEAT_DoubleFault (Double Fault Extension) - FEAT_E0PD (Preventing EL0 access to halves of address maps) - FEAT_ECV (Enhanced Counter Virtualization) +- FEAT_EL0 (Support for execution at EL0) +- FEAT_EL1 (Support for execution at EL1) +- FEAT_EL2 (Support for execution at EL2) +- FEAT_EL3 (Support for execution at EL3) - FEAT_EPAC (Enhanced pointer authentication) -- FEAT_ETS (Enhanced Translation Synchronization) +- FEAT_ETS2 (Enhanced Translation Synchronization) - FEAT_EVT (Enhanced Virtualization Traps) +- FEAT_F32MM (Single-precision Matrix Multiplication) +- FEAT_F64MM (Double-precision Matrix Multiplication) - FEAT_FCMA (Floating-point complex number instructions) - FEAT_FGT (Fine-Grained Traps) - FEAT_FHM (Floating-point half-precision multiplication instructions) +- FEAT_FP (Floating Point extensions) - FEAT_FP16 (Half-precision floating-point data processing) - FEAT_FPAC (Faulting on AUT* instructions) - FEAT_FPACCOMBINE (Faulting on combined pointer authentication instructions) +- FEAT_FPACC_SPEC (Speculative behavior of combined pointer authentication instructions) - FEAT_FRINTTS (Floating-point to integer instructions) - FEAT_FlagM (Flag manipulation instructions v2) - FEAT_FlagM2 (Enhancements to flag manipulation instructions) @@ -60,10 +84,13 @@ the following architecture extensions: - FEAT_LSE (Large System Extensions) - FEAT_LSE2 (Large System Extensions v2) - FEAT_LVA (Large Virtual Address space) +- FEAT_MixedEnd (Mixed-endian support) +- FEAT_MixdEndEL0 (Mixed-endian support at EL0) - FEAT_MOPS (Standardization of memory operations) - FEAT_MTE (Memory Tagging Extension) - FEAT_MTE2 (Memory Tagging Extension) - FEAT_MTE3 (MTE Asymmetric Fault Handling) +- FEAT_MTE_ASYM_FAULT (Memory tagging asymmetric faults) - FEAT_NMI (Non-maskable Interrupt) - FEAT_NV (Nested Virtualization) - FEAT_NV2 (Enhanced nested virtualization support) @@ -76,6 +103,7 @@ the following architecture extensions: - FEAT_PAuth (Pointer authentication) - FEAT_PAuth2 (Enhancements to pointer authentication) - FEAT_PMULL (PMULL, PMULL2 instructions) +- FEAT_PMUv3 (PMU extension version 3) - FEAT_PMUv3p1 (PMU Extensions v3.1) - FEAT_PMUv3p4 (PMU Extensions v3.4) - FEAT_PMUv3p5 (PMU Extensions v3.5) @@ -97,8 +125,18 @@ the following architecture extensions: - FEAT_SME_FA64 (Full A64 instruction set in Streaming SVE mode) - FEAT_SME_F64F64 (Double-precision floating-point outer product instructions) - FEAT_SME_I16I64 (16-bit to 64-bit integer widening outer product instructions) +- FEAT_SVE (Scalable Vector Extension) +- FEAT_SVE_AES (Scalable Vector AES instructions) +- FEAT_SVE_BitPerm (Scalable Vector Bit Permutes instructions) +- FEAT_SVE_PMULL128 (Scalable Vector PMULL instructions) +- FEAT_SVE_SHA3 (Scalable Vector SHA3 instructions) +- FEAT_SVE_SM4 (Scalable Vector SM4 instructions) +- FEAT_SVE2 (Scalable Vector Extension version 2) - FEAT_SPECRES (Speculation restriction instructions) - FEAT_SSBS (Speculative Store Bypass Safe) +- FEAT_TGran16K (Support for 16KB memory translation granule size at stage 1) +- FEAT_TGran4K (Support for 4KB memory translation granule size at stage 1) +- FEAT_TGran64K (Support for 64KB memory translation granule size at stage 1) - FEAT_TIDCP1 (EL0 use of IMPLEMENTATION DEFINED functionality) - FEAT_TLBIOS (TLB invalidate instructions in Outer Shareable domain) - FEAT_TLBIRANGE (TLB invalidate range instructions) @@ -109,8 +147,6 @@ the following architecture extensions: - FEAT_VHE (Virtualization Host Extensions) - FEAT_VMID16 (16-bit VMID) - FEAT_XNX (Translation table stage 2 Unprivileged Execute-never) -- SVE (The Scalable Vector Extension) -- SVE2 (The Scalable Vector Extension v2) For information on the specifics of these extensions, please refer to the `Armv8-A Arm Architecture Reference Manual diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index e8b6e5e5eb..fe1f9643bd 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -468,6 +468,7 @@ config B_L475E_IOT01A default y depends on TCG && ARM select STM32L4X5_SOC + imply DM163 config STM32L4X5_SOC bool diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c index d862aa43fc..5002a40f06 100644 --- a/hw/arm/b-l475e-iot01a.c +++ b/hw/arm/b-l475e-iot01a.c @@ -2,8 +2,8 @@ * B-L475E-IOT01A Discovery Kit machine * (B-L475E-IOT01A IoT Node) * - * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr> - * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr> + * Copyright (c) 2023-2024 Arnaud Minier <arnaud.minier@telecom-paris.fr> + * Copyright (c) 2023-2024 Inès Varhol <ines.varhol@telecom-paris.fr> * * SPDX-License-Identifier: GPL-2.0-or-later * @@ -27,38 +27,111 @@ #include "hw/boards.h" #include "hw/qdev-properties.h" #include "qemu/error-report.h" -#include "hw/arm/stm32l4x5_soc.h" #include "hw/arm/boot.h" +#include "hw/core/split-irq.h" +#include "hw/arm/stm32l4x5_soc.h" +#include "hw/gpio/stm32l4x5_gpio.h" +#include "hw/display/dm163.h" + +/* B-L475E-IOT01A implementation is inspired from netduinoplus2 and arduino */ + +/* + * There are actually 14 input pins in the DM163 device. + * Here the DM163 input pin EN isn't connected to the STM32L4x5 + * GPIOs as the IM120417002 colors shield doesn't actually use + * this pin to drive the RGB matrix. + */ +#define NUM_DM163_INPUTS 13 + +static const unsigned dm163_input[NUM_DM163_INPUTS] = { + 1 * GPIO_NUM_PINS + 2, /* ROW0 PB2 */ + 0 * GPIO_NUM_PINS + 15, /* ROW1 PA15 */ + 0 * GPIO_NUM_PINS + 2, /* ROW2 PA2 */ + 0 * GPIO_NUM_PINS + 7, /* ROW3 PA7 */ + 0 * GPIO_NUM_PINS + 6, /* ROW4 PA6 */ + 0 * GPIO_NUM_PINS + 5, /* ROW5 PA5 */ + 1 * GPIO_NUM_PINS + 0, /* ROW6 PB0 */ + 0 * GPIO_NUM_PINS + 3, /* ROW7 PA3 */ + 0 * GPIO_NUM_PINS + 4, /* SIN (SDA) PA4 */ + 1 * GPIO_NUM_PINS + 1, /* DCK (SCK) PB1 */ + 2 * GPIO_NUM_PINS + 3, /* RST_B (RST) PC3 */ + 2 * GPIO_NUM_PINS + 4, /* LAT_B (LAT) PC4 */ + 2 * GPIO_NUM_PINS + 5, /* SELBK (SB) PC5 */ +}; -/* B-L475E-IOT01A implementation is derived from netduinoplus2 */ +#define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a") +OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A) -static void b_l475e_iot01a_init(MachineState *machine) +typedef struct Bl475eMachineState { + MachineState parent_obj; + + Stm32l4x5SocState soc; + SplitIRQ gpio_splitters[NUM_DM163_INPUTS]; + DM163State dm163; +} Bl475eMachineState; + +static void bl475e_init(MachineState *machine) { + Bl475eMachineState *s = B_L475E_IOT01A(machine); const Stm32l4x5SocClass *sc; - DeviceState *dev; + DeviceState *dev, *gpio_out_splitter; + unsigned gpio, pin; + + object_initialize_child(OBJECT(machine), "soc", &s->soc, + TYPE_STM32L4X5XG_SOC); + sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); - dev = qdev_new(TYPE_STM32L4X5XG_SOC); - object_property_add_child(OBJECT(machine), "soc", OBJECT(dev)); - sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sc = STM32L4X5_SOC_GET_CLASS(&s->soc); + armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0, + sc->flash_size); - sc = STM32L4X5_SOC_GET_CLASS(dev); - armv7m_load_kernel(ARM_CPU(first_cpu), - machine->kernel_filename, - 0, sc->flash_size); + if (object_class_by_name(TYPE_DM163)) { + object_initialize_child(OBJECT(machine), "dm163", + &s->dm163, TYPE_DM163); + dev = DEVICE(&s->dm163); + qdev_realize(dev, NULL, &error_abort); + + for (unsigned i = 0; i < NUM_DM163_INPUTS; i++) { + object_initialize_child(OBJECT(machine), "gpio-out-splitters[*]", + &s->gpio_splitters[i], TYPE_SPLIT_IRQ); + gpio_out_splitter = DEVICE(&s->gpio_splitters[i]); + qdev_prop_set_uint32(gpio_out_splitter, "num-lines", 2); + qdev_realize(gpio_out_splitter, NULL, &error_fatal); + + qdev_connect_gpio_out(gpio_out_splitter, 0, + qdev_get_gpio_in(DEVICE(&s->soc), dm163_input[i])); + qdev_connect_gpio_out(gpio_out_splitter, 1, + qdev_get_gpio_in(dev, i)); + gpio = dm163_input[i] / GPIO_NUM_PINS; + pin = dm163_input[i] % GPIO_NUM_PINS; + qdev_connect_gpio_out(DEVICE(&s->soc.gpio[gpio]), pin, + qdev_get_gpio_in(DEVICE(gpio_out_splitter), 0)); + } + } } -static void b_l475e_iot01a_machine_init(MachineClass *mc) +static void bl475e_machine_init(ObjectClass *oc, void *data) { + MachineClass *mc = MACHINE_CLASS(oc); static const char *machine_valid_cpu_types[] = { ARM_CPU_TYPE_NAME("cortex-m4"), NULL }; mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)"; - mc->init = b_l475e_iot01a_init; + mc->init = bl475e_init; mc->valid_cpu_types = machine_valid_cpu_types; /* SRAM pre-allocated as part of the SoC instantiation */ mc->default_ram_size = 0; } -DEFINE_MACHINE("b-l475e-iot01a", b_l475e_iot01a_machine_init) +static const TypeInfo bl475e_machine_type[] = { + { + .name = TYPE_B_L475E_IOT01A, + .parent = TYPE_MACHINE, + .instance_size = sizeof(Bl475eMachineState), + .class_init = bl475e_machine_init, + } +}; + +DEFINE_TYPES(bl475e_machine_type) diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index cc68b5d8f1..9f2d96c733 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -24,6 +24,7 @@ #include "hw/qdev-clock.h" #include "hw/qdev-properties.h" #include "qapi/error.h" +#include "qemu/bswap.h" #include "qemu/units.h" #include "sysemu/sysemu.h" #include "target/arm/cpu-qom.h" @@ -386,7 +387,7 @@ static void npcm7xx_init_fuses(NPCM7xxState *s) * The initial mask of disabled modules indicates the chip derivative (e.g. * NPCM750 or NPCM730). */ - value = tswap32(nc->disabled_modules); + value = cpu_to_le32(nc->disabled_modules); npcm7xx_otp_array_write(&s->fuse_array, &value, NPCM7XX_FUSE_DERIVATIVE, sizeof(value)); } diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c index f5709d6c14..57c337fd92 100644 --- a/hw/arm/sbsa-ref.c +++ b/hw/arm/sbsa-ref.c @@ -60,6 +60,19 @@ #define NUM_SMMU_IRQS 4 #define NUM_SATA_PORTS 6 +/* + * Generic timer frequency in Hz (which drives both the CPU generic timers + * and the SBSA watchdog-timer). Older versions of the TF-A firmware + * typically used with sbsa-ref (including the binaries in our Avocado test + * Aarch64SbsarefMachine.test_sbsaref_alpine_linux_max_pauth_impdef + * assume it is this value. + * + * TODO: this value is not architecturally correct for an Armv8.6 or + * better CPU, so we should move to 1GHz once the TF-A fix above has + * made it into a release and into our Avocado test. + */ +#define SBSA_GTIMER_HZ 62500000 + enum { SBSA_FLASH, SBSA_MEM, @@ -530,6 +543,7 @@ static void create_wdt(const SBSAMachineState *sms) SysBusDevice *s = SYS_BUS_DEVICE(dev); int irq = sbsa_ref_irqmap[SBSA_GWDT_WS0]; + qdev_prop_set_uint64(dev, "clock-frequency", SBSA_GTIMER_HZ); sysbus_realize_and_unref(s, &error_fatal); sysbus_mmio_map(s, 0, rbase); sysbus_mmio_map(s, 1, cbase); @@ -767,6 +781,8 @@ static void sbsa_ref_init(MachineState *machine) &error_abort); } + object_property_set_int(cpuobj, "cntfrq", SBSA_GTIMER_HZ, &error_abort); + object_property_set_link(cpuobj, "memory", OBJECT(sysmem), &error_abort); diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c index 39924822f3..38f7a2d5d9 100644 --- a/hw/arm/stm32l4x5_soc.c +++ b/hw/arm/stm32l4x5_soc.c @@ -1,8 +1,8 @@ /* * STM32L4x5 SoC family * - * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr> - * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr> + * Copyright (c) 2023-2024 Arnaud Minier <arnaud.minier@telecom-paris.fr> + * Copyright (c) 2023-2024 Inès Varhol <ines.varhol@telecom-paris.fr> * * SPDX-License-Identifier: GPL-2.0-or-later * @@ -250,6 +250,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp) } } + qdev_pass_gpios(DEVICE(&s->syscfg), dev_soc, NULL); + /* EXTI device */ busdev = SYS_BUS_DEVICE(&s->exti); if (!sysbus_realize(busdev, errp)) { diff --git a/hw/char/stm32l4x5_usart.c b/hw/char/stm32l4x5_usart.c index 2627aab832..02f666308c 100644 --- a/hw/char/stm32l4x5_usart.c +++ b/hw/char/stm32l4x5_usart.c @@ -617,6 +617,7 @@ static const TypeInfo stm32l4x5_usart_types[] = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(Stm32l4x5UsartBaseState), .instance_init = stm32l4x5_usart_base_init, + .class_size = sizeof(Stm32l4x5UsartBaseClass), .class_init = stm32l4x5_usart_base_class_init, .abstract = true, }, { diff --git a/hw/core/clock.c b/hw/core/clock.c index a19c7db7df..e212865307 100644 --- a/hw/core/clock.c +++ b/hw/core/clock.c @@ -108,7 +108,6 @@ static void clock_propagate_period(Clock *clk, bool call_callbacks) void clock_propagate(Clock *clk) { - assert(clk->source == NULL); trace_clock_propagate(CLOCK_PATH(clk)); clock_propagate_period(clk, true); } diff --git a/hw/core/machine.c b/hw/core/machine.c index 0dec48e802..4ff60911e7 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -33,7 +33,9 @@ #include "hw/virtio/virtio-iommu.h" #include "audio/audio.h" -GlobalProperty hw_compat_9_0[] = {}; +GlobalProperty hw_compat_9_0[] = { + {"arm-cpu", "backcompat-cntfrq", "true" }, +}; const size_t hw_compat_9_0_len = G_N_ELEMENTS(hw_compat_9_0); GlobalProperty hw_compat_8_2[] = { diff --git a/hw/display/Kconfig b/hw/display/Kconfig index 234c7de027..a4552c8ed7 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -140,3 +140,6 @@ config XLNX_DISPLAYPORT bool # defaults to "N", enabled by specific boards depends on PIXMAN + +config DM163 + bool diff --git a/hw/display/dm163.c b/hw/display/dm163.c new file mode 100644 index 0000000000..f92aee371d --- /dev/null +++ b/hw/display/dm163.c @@ -0,0 +1,349 @@ +/* + * QEMU DM163 8x3-channel constant current led driver + * driving columns of associated 8x8 RGB matrix. + * + * Copyright (C) 2024 Samuel Tardieu <sam@rfc1149.net> + * Copyright (C) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr> + * Copyright (C) 2024 Inès Varhol <ines.varhol@telecom-paris.fr> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * The reference used for the DM163 is the following : + * http://www.siti.com.tw/product/spec/LED/DM163.pdf + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/display/dm163.h" +#include "ui/console.h" +#include "trace.h" + +#define LED_SQUARE_SIZE 100 +/* Number of frames a row stays visible after being turned off. */ +#define ROW_PERSISTENCE 3 +#define TURNED_OFF_ROW (COLOR_BUFFER_SIZE - 1) + +static const VMStateDescription vmstate_dm163 = { + .name = TYPE_DM163, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3), + VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3), + VMSTATE_UINT16_ARRAY(latched_outputs, DM163State, DM163_NUM_LEDS), + VMSTATE_UINT16_ARRAY(outputs, DM163State, DM163_NUM_LEDS), + VMSTATE_UINT8(dck, DM163State), + VMSTATE_UINT8(en_b, DM163State), + VMSTATE_UINT8(lat_b, DM163State), + VMSTATE_UINT8(rst_b, DM163State), + VMSTATE_UINT8(selbk, DM163State), + VMSTATE_UINT8(sin, DM163State), + VMSTATE_UINT8(activated_rows, DM163State), + VMSTATE_UINT32_2DARRAY(buffer, DM163State, COLOR_BUFFER_SIZE, + RGB_MATRIX_NUM_COLS), + VMSTATE_UINT8(last_buffer_idx, DM163State), + VMSTATE_UINT8_ARRAY(buffer_idx_of_row, DM163State, RGB_MATRIX_NUM_ROWS), + VMSTATE_UINT8_ARRAY(row_persistence_delay, DM163State, + RGB_MATRIX_NUM_ROWS), + VMSTATE_END_OF_LIST() + } +}; + +static void dm163_reset_hold(Object *obj, ResetType type) +{ + DM163State *s = DM163(obj); + + s->sin = 0; + s->dck = 0; + s->rst_b = 0; + /* Ensuring the first falling edge of lat_b isn't missed */ + s->lat_b = 1; + s->selbk = 0; + s->en_b = 0; + /* Reset stops the PWM, not the shift and latched registers. */ + memset(s->outputs, 0, sizeof(s->outputs)); + + s->activated_rows = 0; + s->redraw = 0; + trace_dm163_redraw(s->redraw); + for (unsigned i = 0; i < COLOR_BUFFER_SIZE; i++) { + memset(s->buffer[i], 0, sizeof(s->buffer[0])); + } + s->last_buffer_idx = 0; + memset(s->buffer_idx_of_row, TURNED_OFF_ROW, sizeof(s->buffer_idx_of_row)); + memset(s->row_persistence_delay, 0, sizeof(s->row_persistence_delay)); +} + +static void dm163_dck_gpio_handler(void *opaque, int line, int new_state) +{ + DM163State *s = opaque; + + if (new_state && !s->dck) { + /* + * On raising dck, sample selbk to get the bank to use, and + * sample sin for the bit to enter into the bank shift buffer. + */ + uint64_t *sb = + s->selbk ? s->bank1_shift_register : s->bank0_shift_register; + /* Output the outgoing bit on sout */ + const bool sout = (s->selbk ? sb[2] & MAKE_64BIT_MASK(63, 1) : + sb[2] & MAKE_64BIT_MASK(15, 1)) != 0; + qemu_set_irq(s->sout, sout); + /* Enter sin into the shift buffer */ + sb[2] = (sb[2] << 1) | ((sb[1] >> 63) & 1); + sb[1] = (sb[1] << 1) | ((sb[0] >> 63) & 1); + sb[0] = (sb[0] << 1) | s->sin; + } + + s->dck = new_state; + trace_dm163_dck(new_state); +} + +static void dm163_propagate_outputs(DM163State *s) +{ + s->last_buffer_idx = (s->last_buffer_idx + 1) % RGB_MATRIX_NUM_ROWS; + /* Values are output when reset is high and enable is low. */ + if (s->rst_b && !s->en_b) { + memcpy(s->outputs, s->latched_outputs, sizeof(s->outputs)); + } else { + memset(s->outputs, 0, sizeof(s->outputs)); + } + for (unsigned x = 0; x < RGB_MATRIX_NUM_COLS; x++) { + /* Grouping the 3 RGB channels in a pixel value */ + const uint16_t b = extract16(s->outputs[3 * x + 0], 6, 8); + const uint16_t g = extract16(s->outputs[3 * x + 1], 6, 8); + const uint16_t r = extract16(s->outputs[3 * x + 2], 6, 8); + uint32_t rgba = 0; + + trace_dm163_channels(3 * x + 2, r); + trace_dm163_channels(3 * x + 1, g); + trace_dm163_channels(3 * x + 0, b); + + rgba = deposit32(rgba, 0, 8, r); + rgba = deposit32(rgba, 8, 8, g); + rgba = deposit32(rgba, 16, 8, b); + + /* Led values are sent from the last one to the first one */ + s->buffer[s->last_buffer_idx][RGB_MATRIX_NUM_COLS - x - 1] = rgba; + } + for (unsigned row = 0; row < RGB_MATRIX_NUM_ROWS; row++) { + if (s->activated_rows & (1 << row)) { + s->buffer_idx_of_row[row] = s->last_buffer_idx; + s->redraw |= (1 << row); + trace_dm163_redraw(s->redraw); + } + } +} + +static void dm163_en_b_gpio_handler(void *opaque, int line, int new_state) +{ + DM163State *s = opaque; + + s->en_b = new_state; + dm163_propagate_outputs(s); + trace_dm163_en_b(new_state); +} + +static uint8_t dm163_bank0(const DM163State *s, uint8_t led) +{ + /* + * Bank 0 uses 6 bits per led, so a value may be stored accross + * two uint64_t entries. + */ + const uint8_t low_bit = 6 * led; + const uint8_t low_word = low_bit / 64; + const uint8_t high_word = (low_bit + 5) / 64; + const uint8_t low_shift = low_bit % 64; + + if (low_word == high_word) { + /* Simple case: the value belongs to one entry. */ + return extract64(s->bank0_shift_register[low_word], low_shift, 6); + } + + const uint8_t nb_bits_in_low_word = 64 - low_shift; + const uint8_t nb_bits_in_high_word = 6 - nb_bits_in_low_word; + + const uint64_t bits_in_low_word = \ + extract64(s->bank0_shift_register[low_word], low_shift, + nb_bits_in_low_word); + const uint64_t bits_in_high_word = \ + extract64(s->bank0_shift_register[high_word], 0, + nb_bits_in_high_word); + uint8_t val = 0; + + val = deposit32(val, 0, nb_bits_in_low_word, bits_in_low_word); + val = deposit32(val, nb_bits_in_low_word, nb_bits_in_high_word, + bits_in_high_word); + + return val; +} + +static uint8_t dm163_bank1(const DM163State *s, uint8_t led) +{ + const uint64_t entry = s->bank1_shift_register[led / RGB_MATRIX_NUM_COLS]; + return extract64(entry, 8 * (led % RGB_MATRIX_NUM_COLS), 8); +} + +static void dm163_lat_b_gpio_handler(void *opaque, int line, int new_state) +{ + DM163State *s = opaque; + + if (s->lat_b && !new_state) { + for (int led = 0; led < DM163_NUM_LEDS; led++) { + s->latched_outputs[led] = dm163_bank0(s, led) * dm163_bank1(s, led); + } + dm163_propagate_outputs(s); + } + + s->lat_b = new_state; + trace_dm163_lat_b(new_state); +} + +static void dm163_rst_b_gpio_handler(void *opaque, int line, int new_state) +{ + DM163State *s = opaque; + + s->rst_b = new_state; + dm163_propagate_outputs(s); + trace_dm163_rst_b(new_state); +} + +static void dm163_selbk_gpio_handler(void *opaque, int line, int new_state) +{ + DM163State *s = opaque; + + s->selbk = new_state; + trace_dm163_selbk(new_state); +} + +static void dm163_sin_gpio_handler(void *opaque, int line, int new_state) +{ + DM163State *s = opaque; + + s->sin = new_state; + trace_dm163_sin(new_state); +} + +static void dm163_rows_gpio_handler(void *opaque, int line, int new_state) +{ + DM163State *s = opaque; + + if (new_state) { + s->activated_rows |= (1 << line); + s->buffer_idx_of_row[line] = s->last_buffer_idx; + s->redraw |= (1 << line); + trace_dm163_redraw(s->redraw); + } else { + s->activated_rows &= ~(1 << line); + s->row_persistence_delay[line] = ROW_PERSISTENCE; + } + trace_dm163_activated_rows(s->activated_rows); +} + +static void dm163_invalidate_display(void *opaque) +{ + DM163State *s = (DM163State *)opaque; + s->redraw = 0xFF; + trace_dm163_redraw(s->redraw); +} + +static void update_row_persistence_delay(DM163State *s, unsigned row) +{ + if (s->row_persistence_delay[row]) { + s->row_persistence_delay[row]--; + } else { + /* + * If the ROW_PERSISTENCE delay is up, + * the row is turned off. + */ + s->buffer_idx_of_row[row] = TURNED_OFF_ROW; + s->redraw |= (1 << row); + trace_dm163_redraw(s->redraw); + } +} + +static uint32_t *update_display_of_row(DM163State *s, uint32_t *dest, + unsigned row) +{ + for (unsigned _ = 0; _ < LED_SQUARE_SIZE; _++) { + for (int x = 0; x < RGB_MATRIX_NUM_COLS * LED_SQUARE_SIZE; x++) { + /* UI layer guarantees that there's 32 bits per pixel (Mar 2024) */ + *dest++ = s->buffer[s->buffer_idx_of_row[row]][x / LED_SQUARE_SIZE]; + } + } + + dpy_gfx_update(s->console, 0, LED_SQUARE_SIZE * row, + RGB_MATRIX_NUM_COLS * LED_SQUARE_SIZE, LED_SQUARE_SIZE); + s->redraw &= ~(1 << row); + trace_dm163_redraw(s->redraw); + + return dest; +} + +static void dm163_update_display(void *opaque) +{ + DM163State *s = (DM163State *)opaque; + DisplaySurface *surface = qemu_console_surface(s->console); + uint32_t *dest; + + dest = surface_data(surface); + for (unsigned row = 0; row < RGB_MATRIX_NUM_ROWS; row++) { + update_row_persistence_delay(s, row); + if (!extract8(s->redraw, row, 1)) { + dest += LED_SQUARE_SIZE * LED_SQUARE_SIZE * RGB_MATRIX_NUM_COLS; + continue; + } + dest = update_display_of_row(s, dest, row); + } +} + +static const GraphicHwOps dm163_ops = { + .invalidate = dm163_invalidate_display, + .gfx_update = dm163_update_display, +}; + +static void dm163_realize(DeviceState *dev, Error **errp) +{ + DM163State *s = DM163(dev); + + qdev_init_gpio_in(dev, dm163_rows_gpio_handler, RGB_MATRIX_NUM_ROWS); + qdev_init_gpio_in(dev, dm163_sin_gpio_handler, 1); + qdev_init_gpio_in(dev, dm163_dck_gpio_handler, 1); + qdev_init_gpio_in(dev, dm163_rst_b_gpio_handler, 1); + qdev_init_gpio_in(dev, dm163_lat_b_gpio_handler, 1); + qdev_init_gpio_in(dev, dm163_selbk_gpio_handler, 1); + qdev_init_gpio_in(dev, dm163_en_b_gpio_handler, 1); + qdev_init_gpio_out_named(dev, &s->sout, "sout", 1); + + s->console = graphic_console_init(dev, 0, &dm163_ops, s); + qemu_console_resize(s->console, RGB_MATRIX_NUM_COLS * LED_SQUARE_SIZE, + RGB_MATRIX_NUM_ROWS * LED_SQUARE_SIZE); +} + +static void dm163_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->desc = "DM163"; + dc->vmsd = &vmstate_dm163; + dc->realize = dm163_realize; + rc->phases.hold = dm163_reset_hold; + set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); +} + +static const TypeInfo dm163_types[] = { + { + .name = TYPE_DM163, + .parent = TYPE_DEVICE, + .instance_size = sizeof(DM163State), + .class_init = dm163_class_init + } +}; + +DEFINE_TYPES(dm163_types) diff --git a/hw/display/meson.build b/hw/display/meson.build index 4751aab3ba..7893b94c8e 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -38,6 +38,7 @@ system_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c')) system_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c')) system_ss.add(when: 'CONFIG_VIRTIO', if_true: files('virtio-dmabuf.c')) +system_ss.add(when: 'CONFIG_DM163', if_true: files('dm163.c')) if (config_all_devices.has_key('CONFIG_VGA_CIRRUS') or config_all_devices.has_key('CONFIG_VGA_PCI') or diff --git a/hw/display/trace-events b/hw/display/trace-events index 2336a0ca15..781f8a3320 100644 --- a/hw/display/trace-events +++ b/hw/display/trace-events @@ -177,3 +177,17 @@ macfb_ctrl_write(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%"PRI macfb_sense_read(uint32_t value) "video sense: 0x%"PRIx32 macfb_sense_write(uint32_t value) "video sense: 0x%"PRIx32 macfb_update_mode(uint32_t width, uint32_t height, uint8_t depth) "setting mode to width %"PRId32 " height %"PRId32 " size %d" + +# dm163.c +dm163_redraw(uint8_t redraw) "0x%02x" +dm163_dck(unsigned new_state) "dck : %u" +dm163_en_b(unsigned new_state) "en_b : %u" +dm163_rst_b(unsigned new_state) "rst_b : %u" +dm163_lat_b(unsigned new_state) "lat_b : %u" +dm163_sin(unsigned new_state) "sin : %u" +dm163_selbk(unsigned new_state) "selbk : %u" +dm163_activated_rows(int new_state) "Activated rows : 0x%" PRIx32 "" +dm163_bits_ppi(unsigned dest_width) "dest_width : %u" +dm163_leds(int led, uint32_t value) "led %d: 0x%x" +dm163_channels(int channel, uint8_t value) "channel %d: 0x%x" +dm163_refresh_rate(uint32_t rr) "refresh rate %d" diff --git a/hw/dma/xlnx_dpdma.c b/hw/dma/xlnx_dpdma.c index 1f5cd64ed1..530717d188 100644 --- a/hw/dma/xlnx_dpdma.c +++ b/hw/dma/xlnx_dpdma.c @@ -175,24 +175,24 @@ static uint64_t xlnx_dpdma_desc_get_source_address(DPDMADescriptor *desc, switch (frag) { case 0: - addr = desc->source_address - + (extract32(desc->address_extension, 16, 12) << 20); + addr = (uint64_t)desc->source_address + + (extract64(desc->address_extension, 16, 16) << 32); break; case 1: - addr = desc->source_address2 - + (extract32(desc->address_extension_23, 0, 12) << 8); + addr = (uint64_t)desc->source_address2 + + (extract64(desc->address_extension_23, 0, 16) << 32); break; case 2: - addr = desc->source_address3 - + (extract32(desc->address_extension_23, 16, 12) << 20); + addr = (uint64_t)desc->source_address3 + + (extract64(desc->address_extension_23, 16, 16) << 32); break; case 3: - addr = desc->source_address4 - + (extract32(desc->address_extension_45, 0, 12) << 8); + addr = (uint64_t)desc->source_address4 + + (extract64(desc->address_extension_45, 0, 16) << 32); break; case 4: - addr = desc->source_address5 - + (extract32(desc->address_extension_45, 16, 12) << 20); + addr = (uint64_t)desc->source_address5 + + (extract64(desc->address_extension_45, 16, 16) << 32); break; default: addr = 0; diff --git a/hw/ide/core.c b/hw/ide/core.c index e8cb2dac92..08d9218455 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -1623,11 +1623,24 @@ static bool cmd_read_native_max(IDEState *s, uint8_t cmd) /* Refuse if no sectors are addressable (e.g. medium not inserted) */ if (s->nb_sectors == 0) { ide_abort_command(s); - return true; - } + } else { + /* + * Save the active drive parameters, which may have been + * limited from their native counterparts by, e.g., INITIALIZE + * DEVICE PARAMETERS or SET MAX ADDRESS. + */ + const int aheads = s->heads; + const int asectors = s->sectors; - ide_cmd_lba48_transform(s, lba48); - ide_set_sector(s, s->nb_sectors - 1); + s->heads = s->drive_heads; + s->sectors = s->drive_sectors; + + ide_cmd_lba48_transform(s, lba48); + ide_set_sector(s, s->nb_sectors - 1); + + s->heads = aheads; + s->sectors = asectors; + } return true; } diff --git a/hw/loongarch/boot.c b/hw/loongarch/boot.c new file mode 100644 index 0000000000..7d1630b2e7 --- /dev/null +++ b/hw/loongarch/boot.c @@ -0,0 +1,336 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * LoongArch boot helper functions. + * + * Copyright (c) 2023 Loongson Technology Corporation Limited + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "target/loongarch/cpu.h" +#include "hw/loongarch/virt.h" +#include "hw/loader.h" +#include "elf.h" +#include "qemu/error-report.h" +#include "sysemu/reset.h" +#include "sysemu/qtest.h" + +ram_addr_t initrd_offset; +uint64_t initrd_size; + +static const unsigned int slave_boot_code[] = { + /* Configure reset ebase. */ + 0x0400302c, /* csrwr $t0, LOONGARCH_CSR_EENTRY */ + + /* Disable interrupt. */ + 0x0380100c, /* ori $t0, $zero,0x4 */ + 0x04000180, /* csrxchg $zero, $t0, LOONGARCH_CSR_CRMD */ + + /* Clear mailbox. */ + 0x1400002d, /* lu12i.w $t1, 1(0x1) */ + 0x038081ad, /* ori $t1, $t1, CORE_BUF_20 */ + 0x06481da0, /* iocsrwr.d $zero, $t1 */ + + /* Enable IPI interrupt. */ + 0x1400002c, /* lu12i.w $t0, 1(0x1) */ + 0x0400118c, /* csrxchg $t0, $t0, LOONGARCH_CSR_ECFG */ + 0x02fffc0c, /* addi.d $t0, $r0,-1(0xfff) */ + 0x1400002d, /* lu12i.w $t1, 1(0x1) */ + 0x038011ad, /* ori $t1, $t1, CORE_EN_OFF */ + 0x064819ac, /* iocsrwr.w $t0, $t1 */ + 0x1400002d, /* lu12i.w $t1, 1(0x1) */ + 0x038081ad, /* ori $t1, $t1, CORE_BUF_20 */ + + /* Wait for wakeup <.L11>: */ + 0x06488000, /* idle 0x0 */ + 0x03400000, /* andi $zero, $zero, 0x0 */ + 0x064809ac, /* iocsrrd.w $t0, $t1 */ + 0x43fff59f, /* beqz $t0, -12(0x7ffff4) # 48 <.L11> */ + + /* Read and clear IPI interrupt. */ + 0x1400002d, /* lu12i.w $t1, 1(0x1) */ + 0x064809ac, /* iocsrrd.w $t0, $t1 */ + 0x1400002d, /* lu12i.w $t1, 1(0x1) */ + 0x038031ad, /* ori $t1, $t1, CORE_CLEAR_OFF */ + 0x064819ac, /* iocsrwr.w $t0, $t1 */ + + /* Disable IPI interrupt. */ + 0x1400002c, /* lu12i.w $t0, 1(0x1) */ + 0x04001180, /* csrxchg $zero, $t0, LOONGARCH_CSR_ECFG */ + + /* Read mail buf and jump to specified entry */ + 0x1400002d, /* lu12i.w $t1, 1(0x1) */ + 0x038081ad, /* ori $t1, $t1, CORE_BUF_20 */ + 0x06480dac, /* iocsrrd.d $t0, $t1 */ + 0x00150181, /* move $ra, $t0 */ + 0x4c000020, /* jirl $zero, $ra,0 */ +}; + +static inline void *guidcpy(void *dst, const void *src) +{ + return memcpy(dst, src, sizeof(efi_guid_t)); +} + +static void init_efi_boot_memmap(struct efi_system_table *systab, + void *p, void *start) +{ + unsigned i; + struct efi_boot_memmap *boot_memmap = p; + efi_guid_t tbl_guid = LINUX_EFI_BOOT_MEMMAP_GUID; + + /* efi_configuration_table 1 */ + guidcpy(&systab->tables[0].guid, &tbl_guid); + systab->tables[0].table = (struct efi_configuration_table *)(p - start); + systab->nr_tables = 1; + + boot_memmap->desc_size = sizeof(efi_memory_desc_t); + boot_memmap->desc_ver = 1; + boot_memmap->map_size = 0; + + efi_memory_desc_t *map = p + sizeof(struct efi_boot_memmap); + for (i = 0; i < memmap_entries; i++) { + map = (void *)boot_memmap + sizeof(*map); + map[i].type = memmap_table[i].type; + map[i].phys_addr = ROUND_UP(memmap_table[i].address, 64 * KiB); + map[i].num_pages = ROUND_DOWN(memmap_table[i].address + + memmap_table[i].length - map[i].phys_addr, 64 * KiB); + p += sizeof(efi_memory_desc_t); + } +} + +static void init_efi_initrd_table(struct efi_system_table *systab, + void *p, void *start) +{ + efi_guid_t tbl_guid = LINUX_EFI_INITRD_MEDIA_GUID; + struct efi_initrd *initrd_table = p; + + /* efi_configuration_table 2 */ + guidcpy(&systab->tables[1].guid, &tbl_guid); + systab->tables[1].table = (struct efi_configuration_table *)(p - start); + systab->nr_tables = 2; + + initrd_table->base = initrd_offset; + initrd_table->size = initrd_size; +} + +static void init_efi_fdt_table(struct efi_system_table *systab) +{ + efi_guid_t tbl_guid = DEVICE_TREE_GUID; + + /* efi_configuration_table 3 */ + guidcpy(&systab->tables[2].guid, &tbl_guid); + systab->tables[2].table = (void *)FDT_BASE; + systab->nr_tables = 3; +} + +static void init_systab(struct loongarch_boot_info *info, void *p, void *start) +{ + void *bp_tables_start; + struct efi_system_table *systab = p; + + info->a2 = p - start; + + systab->hdr.signature = EFI_SYSTEM_TABLE_SIGNATURE; + systab->hdr.revision = EFI_SPECIFICATION_VERSION; + systab->hdr.revision = sizeof(struct efi_system_table), + systab->fw_revision = FW_VERSION << 16 | FW_PATCHLEVEL << 8; + systab->runtime = 0; + systab->boottime = 0; + systab->nr_tables = 0; + + p += ROUND_UP(sizeof(struct efi_system_table), 64 * KiB); + + systab->tables = p; + bp_tables_start = p; + + init_efi_boot_memmap(systab, p, start); + p += ROUND_UP(sizeof(struct efi_boot_memmap) + + sizeof(efi_memory_desc_t) * memmap_entries, 64 * KiB); + init_efi_initrd_table(systab, p, start); + p += ROUND_UP(sizeof(struct efi_initrd), 64 * KiB); + init_efi_fdt_table(systab); + + systab->tables = (struct efi_configuration_table *)(bp_tables_start - start); +} + +static void init_cmdline(struct loongarch_boot_info *info, void *p, void *start) +{ + hwaddr cmdline_addr = p - start; + + info->a0 = 1; + info->a1 = cmdline_addr; + + memcpy(p, info->kernel_cmdline, COMMAND_LINE_SIZE); +} + +static uint64_t cpu_loongarch_virt_to_phys(void *opaque, uint64_t addr) +{ + return addr & MAKE_64BIT_MASK(0, TARGET_PHYS_ADDR_SPACE_BITS); +} + +static int64_t load_kernel_info(struct loongarch_boot_info *info) +{ + uint64_t kernel_entry, kernel_low, kernel_high; + ssize_t kernel_size; + + kernel_size = load_elf(info->kernel_filename, NULL, + cpu_loongarch_virt_to_phys, NULL, + &kernel_entry, &kernel_low, + &kernel_high, NULL, 0, + EM_LOONGARCH, 1, 0); + + if (kernel_size < 0) { + error_report("could not load kernel '%s': %s", + info->kernel_filename, + load_elf_strerror(kernel_size)); + exit(1); + } + + if (info->initrd_filename) { + initrd_size = get_image_size(info->initrd_filename); + if (initrd_size > 0) { + initrd_offset = ROUND_UP(kernel_high + 4 * kernel_size, 64 * KiB); + + if (initrd_offset + initrd_size > info->ram_size) { + error_report("memory too small for initial ram disk '%s'", + info->initrd_filename); + exit(1); + } + + initrd_size = load_image_targphys(info->initrd_filename, initrd_offset, + info->ram_size - initrd_offset); + } + + if (initrd_size == (target_ulong)-1) { + error_report("could not load initial ram disk '%s'", + info->initrd_filename); + exit(1); + } + } else { + initrd_size = 0; + } + + return kernel_entry; +} + +static void reset_load_elf(void *opaque) +{ + LoongArchCPU *cpu = opaque; + CPULoongArchState *env = &cpu->env; + + cpu_reset(CPU(cpu)); + if (env->load_elf) { + if (cpu == LOONGARCH_CPU(first_cpu)) { + env->gpr[4] = env->boot_info->a0; + env->gpr[5] = env->boot_info->a1; + env->gpr[6] = env->boot_info->a2; + } + cpu_set_pc(CPU(cpu), env->elf_address); + } +} + +static void fw_cfg_add_kernel_info(struct loongarch_boot_info *info, + FWCfgState *fw_cfg) +{ + /* + * Expose the kernel, the command line, and the initrd in fw_cfg. + * We don't process them here at all, it's all left to the + * firmware. + */ + load_image_to_fw_cfg(fw_cfg, + FW_CFG_KERNEL_SIZE, FW_CFG_KERNEL_DATA, + info->kernel_filename, + false); + + if (info->initrd_filename) { + load_image_to_fw_cfg(fw_cfg, + FW_CFG_INITRD_SIZE, FW_CFG_INITRD_DATA, + info->initrd_filename, false); + } + + if (info->kernel_cmdline) { + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, + strlen(info->kernel_cmdline) + 1); + fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, + info->kernel_cmdline); + } +} + +static void loongarch_firmware_boot(LoongArchMachineState *lams, + struct loongarch_boot_info *info) +{ + fw_cfg_add_kernel_info(info, lams->fw_cfg); +} + +static void init_boot_rom(struct loongarch_boot_info *info, void *p) +{ + void *start = p; + + init_cmdline(info, p, start); + p += COMMAND_LINE_SIZE; + + init_systab(info, p, start); +} + +static void loongarch_direct_kernel_boot(struct loongarch_boot_info *info) +{ + void *p, *bp; + int64_t kernel_addr = 0; + LoongArchCPU *lacpu; + CPUState *cs; + + if (info->kernel_filename) { + kernel_addr = load_kernel_info(info); + } else { + if(!qtest_enabled()) { + error_report("Need kernel filename\n"); + exit(1); + } + } + + /* Load cmdline and system tables at [0 - 1 MiB] */ + p = g_malloc0(1 * MiB); + bp = p; + init_boot_rom(info, p); + rom_add_blob_fixed_as("boot_info", bp, 1 * MiB, 0, &address_space_memory); + + /* Load slave boot code at pflash0 . */ + void *boot_code = g_malloc0(VIRT_FLASH0_SIZE); + memcpy(boot_code, &slave_boot_code, sizeof(slave_boot_code)); + rom_add_blob_fixed("boot_code", boot_code, VIRT_FLASH0_SIZE, VIRT_FLASH0_BASE); + + CPU_FOREACH(cs) { + lacpu = LOONGARCH_CPU(cs); + lacpu->env.load_elf = true; + if (cs == first_cpu) { + lacpu->env.elf_address = kernel_addr; + } else { + lacpu->env.elf_address = VIRT_FLASH0_BASE; + } + lacpu->env.boot_info = info; + } + + g_free(boot_code); + g_free(bp); +} + +void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info) +{ + LoongArchMachineState *lams = LOONGARCH_MACHINE(ms); + int i; + + /* register reset function */ + for (i = 0; i < ms->smp.cpus; i++) { + qemu_register_reset(reset_load_elf, LOONGARCH_CPU(qemu_get_cpu(i))); + } + + info->kernel_filename = ms->kernel_filename; + info->kernel_cmdline = ms->kernel_cmdline; + info->initrd_filename = ms->initrd_filename; + + if (lams->bios_loaded) { + loongarch_firmware_boot(lams, info); + } else { + loongarch_direct_kernel_boot(info); + } +} diff --git a/hw/loongarch/meson.build b/hw/loongarch/meson.build index c0421502ab..d306d82c2e 100644 --- a/hw/loongarch/meson.build +++ b/hw/loongarch/meson.build @@ -1,6 +1,7 @@ loongarch_ss = ss.source_set() loongarch_ss.add(files( 'fw_cfg.c', + 'boot.c', )) loongarch_ss.add(when: 'CONFIG_LOONGARCH_VIRT', if_true: [files('virt.c'), fdt]) loongarch_ss.add(when: 'CONFIG_ACPI', if_true: files('acpi-build.c')) diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 441d764843..c0999878df 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -46,14 +46,6 @@ #include "hw/block/flash.h" #include "qemu/error-report.h" - -struct loaderparams { - uint64_t ram_size; - const char *kernel_filename; - const char *kernel_cmdline; - const char *initrd_filename; -}; - static PFlashCFI01 *virt_flash_create1(LoongArchMachineState *lams, const char *name, const char *alias_prop_name) @@ -114,6 +106,101 @@ static void virt_flash_map(LoongArchMachineState *lams, virt_flash_map1(flash1, VIRT_FLASH1_BASE, VIRT_FLASH1_SIZE, sysmem); } +static void fdt_add_cpuic_node(LoongArchMachineState *lams, + uint32_t *cpuintc_phandle) +{ + MachineState *ms = MACHINE(lams); + char *nodename; + + *cpuintc_phandle = qemu_fdt_alloc_phandle(ms->fdt); + nodename = g_strdup_printf("/cpuic"); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", *cpuintc_phandle); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", + "loongson,cpu-interrupt-controller"); + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 1); + g_free(nodename); +} + +static void fdt_add_eiointc_node(LoongArchMachineState *lams, + uint32_t *cpuintc_phandle, + uint32_t *eiointc_phandle) +{ + MachineState *ms = MACHINE(lams); + char *nodename; + hwaddr extioi_base = APIC_BASE; + hwaddr extioi_size = EXTIOI_SIZE; + + *eiointc_phandle = qemu_fdt_alloc_phandle(ms->fdt); + nodename = g_strdup_printf("/eiointc@%" PRIx64, extioi_base); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", *eiointc_phandle); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", + "loongson,ls2k2000-eiointc"); + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 1); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + *cpuintc_phandle); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupts", 3); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0x0, + extioi_base, 0x0, extioi_size); + g_free(nodename); +} + +static void fdt_add_pch_pic_node(LoongArchMachineState *lams, + uint32_t *eiointc_phandle, + uint32_t *pch_pic_phandle) +{ + MachineState *ms = MACHINE(lams); + char *nodename; + hwaddr pch_pic_base = VIRT_PCH_REG_BASE; + hwaddr pch_pic_size = VIRT_PCH_REG_SIZE; + + *pch_pic_phandle = qemu_fdt_alloc_phandle(ms->fdt); + nodename = g_strdup_printf("/platic@%" PRIx64, pch_pic_base); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", *pch_pic_phandle); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", + "loongson,pch-pic-1.0"); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, + pch_pic_base, 0, pch_pic_size); + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 2); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + *eiointc_phandle); + qemu_fdt_setprop_cell(ms->fdt, nodename, "loongson,pic-base-vec", 0); + g_free(nodename); +} + +static void fdt_add_pch_msi_node(LoongArchMachineState *lams, + uint32_t *eiointc_phandle, + uint32_t *pch_msi_phandle) +{ + MachineState *ms = MACHINE(lams); + char *nodename; + hwaddr pch_msi_base = VIRT_PCH_MSI_ADDR_LOW; + hwaddr pch_msi_size = VIRT_PCH_MSI_SIZE; + + *pch_msi_phandle = qemu_fdt_alloc_phandle(ms->fdt); + nodename = g_strdup_printf("/msi@%" PRIx64, pch_msi_base); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", *pch_msi_phandle); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", + "loongson,pch-msi-1.0"); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", + 0, pch_msi_base, + 0, pch_msi_size); + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + *eiointc_phandle); + qemu_fdt_setprop_cell(ms->fdt, nodename, "loongson,msi-base-vec", + VIRT_PCH_PIC_IRQ_NUM); + qemu_fdt_setprop_cell(ms->fdt, nodename, "loongson,msi-num-vecs", + EXTIOI_IRQS - VIRT_PCH_PIC_IRQ_NUM); + g_free(nodename); +} + static void fdt_add_flash_node(LoongArchMachineState *lams) { MachineState *ms = MACHINE(lams); @@ -144,7 +231,8 @@ static void fdt_add_flash_node(LoongArchMachineState *lams) g_free(nodename); } -static void fdt_add_rtc_node(LoongArchMachineState *lams) +static void fdt_add_rtc_node(LoongArchMachineState *lams, + uint32_t *pch_pic_phandle) { char *nodename; hwaddr base = VIRT_RTC_REG_BASE; @@ -153,12 +241,18 @@ static void fdt_add_rtc_node(LoongArchMachineState *lams) nodename = g_strdup_printf("/rtc@%" PRIx64, base); qemu_fdt_add_subnode(ms->fdt, nodename); - qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "loongson,ls7a-rtc"); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", + "loongson,ls7a-rtc"); qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", + VIRT_RTC_IRQ - VIRT_GSI_BASE , 0x4); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + *pch_pic_phandle); g_free(nodename); } -static void fdt_add_uart_node(LoongArchMachineState *lams) +static void fdt_add_uart_node(LoongArchMachineState *lams, + uint32_t *pch_pic_phandle) { char *nodename; hwaddr base = VIRT_UART_BASE; @@ -171,6 +265,10 @@ static void fdt_add_uart_node(LoongArchMachineState *lams) qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0x0, base, 0x0, size); qemu_fdt_setprop_cell(ms->fdt, nodename, "clock-frequency", 100000000); qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", nodename); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", + VIRT_UART_IRQ - VIRT_GSI_BASE, 0x4); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + *pch_pic_phandle); g_free(nodename); } @@ -265,7 +363,62 @@ static void fdt_add_fw_cfg_node(const LoongArchMachineState *lams) g_free(nodename); } -static void fdt_add_pcie_node(const LoongArchMachineState *lams) +static void fdt_add_pcie_irq_map_node(const LoongArchMachineState *lams, + char *nodename, + uint32_t *pch_pic_phandle) +{ + int pin, dev; + uint32_t irq_map_stride = 0; + uint32_t full_irq_map[GPEX_NUM_IRQS *GPEX_NUM_IRQS * 10] = {}; + uint32_t *irq_map = full_irq_map; + const MachineState *ms = MACHINE(lams); + + /* This code creates a standard swizzle of interrupts such that + * each device's first interrupt is based on it's PCI_SLOT number. + * (See pci_swizzle_map_irq_fn()) + * + * We only need one entry per interrupt in the table (not one per + * possible slot) seeing the interrupt-map-mask will allow the table + * to wrap to any number of devices. + */ + + for (dev = 0; dev < GPEX_NUM_IRQS; dev++) { + int devfn = dev * 0x8; + + for (pin = 0; pin < GPEX_NUM_IRQS; pin++) { + int irq_nr = 16 + ((pin + PCI_SLOT(devfn)) % GPEX_NUM_IRQS); + int i = 0; + + /* Fill PCI address cells */ + irq_map[i] = cpu_to_be32(devfn << 8); + i += 3; + + /* Fill PCI Interrupt cells */ + irq_map[i] = cpu_to_be32(pin + 1); + i += 1; + + /* Fill interrupt controller phandle and cells */ + irq_map[i++] = cpu_to_be32(*pch_pic_phandle); + irq_map[i++] = cpu_to_be32(irq_nr); + + if (!irq_map_stride) { + irq_map_stride = i; + } + irq_map += irq_map_stride; + } + } + + + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-map", full_irq_map, + GPEX_NUM_IRQS * GPEX_NUM_IRQS * + irq_map_stride * sizeof(uint32_t)); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupt-map-mask", + 0x1800, 0, 0, 0x7); +} + +static void fdt_add_pcie_node(const LoongArchMachineState *lams, + uint32_t *pch_pic_phandle, + uint32_t *pch_msi_phandle) { char *nodename; hwaddr base_mmio = VIRT_PCI_MEM_BASE; @@ -296,34 +449,11 @@ static void fdt_add_pcie_node(const LoongArchMachineState *lams) 2, base_pio, 2, size_pio, 1, FDT_PCI_RANGE_MMIO, 2, base_mmio, 2, base_mmio, 2, size_mmio); - g_free(nodename); -} - -static void fdt_add_irqchip_node(LoongArchMachineState *lams) -{ - MachineState *ms = MACHINE(lams); - char *nodename; - uint32_t irqchip_phandle; + qemu_fdt_setprop_cells(ms->fdt, nodename, "msi-map", + 0, *pch_msi_phandle, 0, 0x10000); - irqchip_phandle = qemu_fdt_alloc_phandle(ms->fdt); - qemu_fdt_setprop_cell(ms->fdt, "/", "interrupt-parent", irqchip_phandle); + fdt_add_pcie_irq_map_node(lams, nodename, pch_pic_phandle); - nodename = g_strdup_printf("/intc@%lx", VIRT_IOAPIC_REG_BASE); - qemu_fdt_add_subnode(ms->fdt, nodename); - qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 3); - qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); - qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x2); - qemu_fdt_setprop_cell(ms->fdt, nodename, "#size-cells", 0x2); - qemu_fdt_setprop(ms->fdt, nodename, "ranges", NULL, 0); - - qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", - "loongarch,ls7a"); - - qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", - 2, VIRT_IOAPIC_REG_BASE, - 2, PCH_PIC_ROUTE_ENTRY_OFFSET); - - qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", irqchip_phandle); g_free(nodename); } @@ -333,7 +463,7 @@ static void fdt_add_memory_node(MachineState *ms, char *nodename = g_strdup_printf("/memory@%" PRIx64, base); qemu_fdt_add_subnode(ms->fdt, nodename); - qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 2, base, 2, size); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, 0, size); qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "memory"); if (ms->numa_state && ms->numa_state->num_nodes) { @@ -386,15 +516,8 @@ static void virt_powerdown_req(Notifier *notifier, void *opaque) acpi_send_event(s->acpi_ged, ACPI_POWER_DOWN_STATUS); } -struct memmap_entry { - uint64_t address; - uint64_t length; - uint32_t type; - uint32_t reserved; -}; - -static struct memmap_entry *memmap_table; -static unsigned memmap_entries; +struct memmap_entry *memmap_table; +unsigned memmap_entries; static void memmap_add_entry(uint64_t address, uint64_t length, uint32_t type) { @@ -412,31 +535,6 @@ static void memmap_add_entry(uint64_t address, uint64_t length, uint32_t type) memmap_entries++; } -static uint64_t cpu_loongarch_virt_to_phys(void *opaque, uint64_t addr) -{ - return addr & MAKE_64BIT_MASK(0, TARGET_PHYS_ADDR_SPACE_BITS); -} - -static int64_t load_kernel_info(const struct loaderparams *loaderparams) -{ - uint64_t kernel_entry, kernel_low, kernel_high; - ssize_t kernel_size; - - kernel_size = load_elf(loaderparams->kernel_filename, NULL, - cpu_loongarch_virt_to_phys, NULL, - &kernel_entry, &kernel_low, - &kernel_high, NULL, 0, - EM_LOONGARCH, 1, 0); - - if (kernel_size < 0) { - error_report("could not load kernel '%s': %s", - loaderparams->kernel_filename, - load_elf_strerror(kernel_size)); - exit(1); - } - return kernel_entry; -} - static DeviceState *create_acpi_ged(DeviceState *pch_pic, LoongArchMachineState *lams) { DeviceState *dev; @@ -487,7 +585,10 @@ static DeviceState *create_platform_bus(DeviceState *pch_pic) return dev; } -static void loongarch_devices_init(DeviceState *pch_pic, LoongArchMachineState *lams) +static void loongarch_devices_init(DeviceState *pch_pic, + LoongArchMachineState *lams, + uint32_t *pch_pic_phandle, + uint32_t *pch_msi_phandle) { MachineClass *mc = MACHINE_GET_CLASS(lams); DeviceState *gpex_dev; @@ -533,11 +634,14 @@ static void loongarch_devices_init(DeviceState *pch_pic, LoongArchMachineState * gpex_set_irq_num(GPEX_HOST(gpex_dev), i, 16 + i); } + /* Add pcie node */ + fdt_add_pcie_node(lams, pch_pic_phandle, pch_msi_phandle); + serial_mm_init(get_system_memory(), VIRT_UART_BASE, 0, qdev_get_gpio_in(pch_pic, VIRT_UART_IRQ - VIRT_GSI_BASE), 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); - fdt_add_uart_node(lams); + fdt_add_uart_node(lams, pch_pic_phandle); /* Network init */ pci_init_nic_devices(pci_bus, mc->default_nic); @@ -550,7 +654,7 @@ static void loongarch_devices_init(DeviceState *pch_pic, LoongArchMachineState * sysbus_create_simple("ls7a_rtc", VIRT_RTC_REG_BASE, qdev_get_gpio_in(pch_pic, VIRT_RTC_IRQ - VIRT_GSI_BASE)); - fdt_add_rtc_node(lams); + fdt_add_rtc_node(lams, pch_pic_phandle); /* acpi ged */ lams->acpi_ged = create_acpi_ged(pch_pic, lams); @@ -568,6 +672,7 @@ static void loongarch_irq_init(LoongArchMachineState *lams) CPULoongArchState *env; CPUState *cpu_state; int cpu, pin, i, start, num; + uint32_t cpuintc_phandle, eiointc_phandle, pch_pic_phandle, pch_msi_phandle; /* * The connection of interrupts: @@ -602,6 +707,9 @@ static void loongarch_irq_init(LoongArchMachineState *lams) memory_region_add_subregion(&lams->system_iocsr, MAIL_SEND_ADDR, sysbus_mmio_get_region(SYS_BUS_DEVICE(ipi), 1)); + /* Add cpu interrupt-controller */ + fdt_add_cpuic_node(lams, &cpuintc_phandle); + for (cpu = 0; cpu < ms->smp.cpus; cpu++) { cpu_state = qemu_get_cpu(cpu); cpudev = DEVICE(cpu_state); @@ -633,6 +741,9 @@ static void loongarch_irq_init(LoongArchMachineState *lams) } } + /* Add Extend I/O Interrupt Controller node */ + fdt_add_eiointc_node(lams, &cpuintc_phandle, &eiointc_phandle); + pch_pic = qdev_new(TYPE_LOONGARCH_PCH_PIC); num = VIRT_PCH_PIC_IRQ_NUM; qdev_prop_set_uint32(pch_pic, "pch_pic_irq_num", num); @@ -652,6 +763,9 @@ static void loongarch_irq_init(LoongArchMachineState *lams) qdev_connect_gpio_out(DEVICE(d), i, qdev_get_gpio_in(extioi, i)); } + /* Add PCH PIC node */ + fdt_add_pch_pic_node(lams, &eiointc_phandle, &pch_pic_phandle); + pch_msi = qdev_new(TYPE_LOONGARCH_PCH_MSI); start = num; num = EXTIOI_IRQS - start; @@ -666,7 +780,10 @@ static void loongarch_irq_init(LoongArchMachineState *lams) qdev_get_gpio_in(extioi, i + start)); } - loongarch_devices_init(pch_pic, lams); + /* Add PCH MSI node */ + fdt_add_pch_msi_node(lams, &eiointc_phandle, &pch_msi_phandle); + + loongarch_devices_init(pch_pic, lams, &pch_pic_phandle, &pch_msi_phandle); } static void loongarch_firmware_init(LoongArchMachineState *lams) @@ -717,67 +834,6 @@ static void loongarch_firmware_init(LoongArchMachineState *lams) } } -static void reset_load_elf(void *opaque) -{ - LoongArchCPU *cpu = opaque; - CPULoongArchState *env = &cpu->env; - - cpu_reset(CPU(cpu)); - if (env->load_elf) { - cpu_set_pc(CPU(cpu), env->elf_address); - } -} - -static void fw_cfg_add_kernel_info(const struct loaderparams *loaderparams, - FWCfgState *fw_cfg) -{ - /* - * Expose the kernel, the command line, and the initrd in fw_cfg. - * We don't process them here at all, it's all left to the - * firmware. - */ - load_image_to_fw_cfg(fw_cfg, - FW_CFG_KERNEL_SIZE, FW_CFG_KERNEL_DATA, - loaderparams->kernel_filename, - false); - - if (loaderparams->initrd_filename) { - load_image_to_fw_cfg(fw_cfg, - FW_CFG_INITRD_SIZE, FW_CFG_INITRD_DATA, - loaderparams->initrd_filename, false); - } - - if (loaderparams->kernel_cmdline) { - fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, - strlen(loaderparams->kernel_cmdline) + 1); - fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, - loaderparams->kernel_cmdline); - } -} - -static void loongarch_firmware_boot(LoongArchMachineState *lams, - const struct loaderparams *loaderparams) -{ - fw_cfg_add_kernel_info(loaderparams, lams->fw_cfg); -} - -static void loongarch_direct_kernel_boot(LoongArchMachineState *lams, - const struct loaderparams *loaderparams) -{ - MachineState *machine = MACHINE(lams); - int64_t kernel_addr = 0; - LoongArchCPU *lacpu; - int i; - - kernel_addr = load_kernel_info(loaderparams); - if (!machine->firmware) { - for (i = 0; i < machine->smp.cpus; i++) { - lacpu = LOONGARCH_CPU(qemu_get_cpu(i)); - lacpu->env.load_elf = true; - lacpu->env.elf_address = kernel_addr; - } - } -} static void loongarch_qemu_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) @@ -828,12 +884,10 @@ static void loongarch_init(MachineState *machine) int nb_numa_nodes = machine->numa_state->num_nodes; NodeInfo *numa_info = machine->numa_state->nodes; int i; - hwaddr fdt_base; const CPUArchIdList *possible_cpus; MachineClass *mc = MACHINE_GET_CLASS(machine); CPUState *cpu; char *ramName = NULL; - struct loaderparams loaderparams = { }; if (!cpu_model) { cpu_model = LOONGARCH_CPU_TYPE_NAME("la464"); @@ -936,28 +990,11 @@ static void loongarch_init(MachineState *machine) sizeof(struct memmap_entry) * (memmap_entries)); } fdt_add_fw_cfg_node(lams); - loaderparams.ram_size = ram_size; - loaderparams.kernel_filename = machine->kernel_filename; - loaderparams.kernel_cmdline = machine->kernel_cmdline; - loaderparams.initrd_filename = machine->initrd_filename; - /* load the kernel. */ - if (loaderparams.kernel_filename) { - if (lams->bios_loaded) { - loongarch_firmware_boot(lams, &loaderparams); - } else { - loongarch_direct_kernel_boot(lams, &loaderparams); - } - } fdt_add_flash_node(lams); - /* register reset function */ - for (i = 0; i < machine->smp.cpus; i++) { - lacpu = LOONGARCH_CPU(qemu_get_cpu(i)); - qemu_register_reset(reset_load_elf, lacpu); - } + /* Initialize the IO interrupt subsystem */ loongarch_irq_init(lams); - fdt_add_irqchip_node(lams); - platform_bus_add_all_fdt_nodes(machine->fdt, "/intc", + platform_bus_add_all_fdt_nodes(machine->fdt, "/platic", VIRT_PLATFORM_BUS_BASEADDRESS, VIRT_PLATFORM_BUS_SIZE, VIRT_PLATFORM_BUS_IRQ); @@ -967,7 +1004,6 @@ static void loongarch_init(MachineState *machine) lams->powerdown_notifier.notify = virt_powerdown_req; qemu_register_powerdown_notifier(&lams->powerdown_notifier); - fdt_add_pcie_node(lams); /* * Since lowmem region starts from 0 and Linux kernel legacy start address * at 2 MiB, FDT base address is located at 1 MiB to avoid NULL pointer @@ -975,9 +1011,14 @@ static void loongarch_init(MachineState *machine) * 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. */ - fdt_base = 1 * MiB; qemu_fdt_dumpdtb(machine->fdt, lams->fdt_size); - rom_add_blob_fixed("fdt", machine->fdt, lams->fdt_size, fdt_base); + rom_add_blob_fixed_as("fdt", machine->fdt, lams->fdt_size, FDT_BASE, + &address_space_memory); + qemu_register_reset_nosnapshotload(qemu_fdt_randomize_seeds, + rom_ptr_for_as(&address_space_memory, FDT_BASE, lams->fdt_size)); + + lams->bootinfo.ram_size = ram_size; + loongarch_load_kernel(machine, &lams->bootinfo); } bool loongarch_is_acpi_enabled(LoongArchMachineState *lams) diff --git a/hw/misc/edu.c b/hw/misc/edu.c index 2a976ca2b1..fa052c44db 100644 --- a/hw/misc/edu.c +++ b/hw/misc/edu.c @@ -23,6 +23,7 @@ */ #include "qemu/osdep.h" +#include "qemu/log.h" #include "qemu/units.h" #include "hw/pci/pci.h" #include "hw/hw.h" @@ -103,25 +104,25 @@ static void edu_lower_irq(EduState *edu, uint32_t val) } } -static bool within(uint64_t addr, uint64_t start, uint64_t end) +static void edu_check_range(uint64_t xfer_start, uint64_t xfer_size, + uint64_t dma_start, uint64_t dma_size) { - return start <= addr && addr < end; -} - -static void edu_check_range(uint64_t addr, uint64_t size1, uint64_t start, - uint64_t size2) -{ - uint64_t end1 = addr + size1; - uint64_t end2 = start + size2; - - if (within(addr, start, end2) && - end1 > addr && end1 <= end2) { + uint64_t xfer_end = xfer_start + xfer_size; + uint64_t dma_end = dma_start + dma_size; + + /* + * 1. ensure we aren't overflowing + * 2. ensure that xfer is within dma address range + */ + if (dma_end >= dma_start && xfer_end >= xfer_start && + xfer_start >= dma_start && xfer_end <= dma_end) { return; } - hw_error("EDU: DMA range 0x%016"PRIx64"-0x%016"PRIx64 - " out of bounds (0x%016"PRIx64"-0x%016"PRIx64")!", - addr, end1 - 1, start, end2 - 1); + qemu_log_mask(LOG_GUEST_ERROR, + "EDU: DMA range 0x%016"PRIx64"-0x%016"PRIx64 + " out of bounds (0x%016"PRIx64"-0x%016"PRIx64")!", + xfer_start, xfer_end - 1, dma_start, dma_end - 1); } static dma_addr_t edu_clamp_addr(const EduState *edu, dma_addr_t addr) @@ -129,7 +130,9 @@ static dma_addr_t edu_clamp_addr(const EduState *edu, dma_addr_t addr) dma_addr_t res = addr & edu->dma_mask; if (addr != res) { - printf("EDU: clamping DMA %#.16"PRIx64" to %#.16"PRIx64"!\n", addr, res); + qemu_log_mask(LOG_GUEST_ERROR, + "EDU: clamping DMA 0x%016"PRIx64" to 0x%016"PRIx64"!", + addr, res); } return res; diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index eccdb852a0..bac78a32bb 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -126,6 +126,10 @@ static MemTxResult ufs_dma_read_req_upiu(UfsRequest *req) copy_size = sizeof(UtpUpiuHeader) + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + data_segment_length; + if (copy_size > sizeof(req->req_upiu)) { + copy_size = sizeof(req->req_upiu); + } + ret = ufs_addr_read(u, req_upiu_base_addr, &req->req_upiu, copy_size); if (ret) { trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); @@ -225,6 +229,10 @@ static MemTxResult ufs_dma_write_rsp_upiu(UfsRequest *req) copy_size = rsp_upiu_byte_len; } + if (copy_size > sizeof(req->rsp_upiu)) { + copy_size = sizeof(req->rsp_upiu); + } + ret = ufs_addr_write(u, rsp_upiu_base_addr, &req->rsp_upiu, copy_size); if (ret) { trace_ufs_err_dma_write_rsp_upiu(req->slot, rsp_upiu_base_addr); diff --git a/hw/watchdog/sbsa_gwdt.c b/hw/watchdog/sbsa_gwdt.c index 96895d7636..d437535cc6 100644 --- a/hw/watchdog/sbsa_gwdt.c +++ b/hw/watchdog/sbsa_gwdt.c @@ -18,6 +18,7 @@ #include "qemu/osdep.h" #include "sysemu/reset.h" #include "sysemu/watchdog.h" +#include "hw/qdev-properties.h" #include "hw/watchdog/sbsa_gwdt.h" #include "qemu/timer.h" #include "migration/vmstate.h" @@ -109,7 +110,7 @@ static void sbsa_gwdt_update_timer(SBSA_GWDTState *s, WdtRefreshType rtype) timeout = s->woru; timeout <<= 32; timeout |= s->worl; - timeout = muldiv64(timeout, NANOSECONDS_PER_SECOND, SBSA_TIMER_FREQ); + timeout = muldiv64(timeout, NANOSECONDS_PER_SECOND, s->freq); timeout += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); if ((rtype == EXPLICIT_REFRESH) || ((rtype == TIMEOUT_REFRESH) && @@ -261,6 +262,17 @@ static void wdt_sbsa_gwdt_realize(DeviceState *dev, Error **errp) dev); } +static Property wdt_sbsa_gwdt_props[] = { + /* + * Timer frequency in Hz. This must match the frequency used by + * the CPU's generic timer. Default 62.5Hz matches QEMU's legacy + * CPU timer frequency default. + */ + DEFINE_PROP_UINT64("clock-frequency", struct SBSA_GWDTState, freq, + 62500000), + DEFINE_PROP_END_OF_LIST(), +}; + static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -271,6 +283,7 @@ static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, void *data) set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories); dc->vmsd = &vmstate_sbsa_gwdt; dc->desc = "SBSA-compliant generic watchdog device"; + device_class_set_props(dc, wdt_sbsa_gwdt_props); } static const TypeInfo wdt_sbsa_gwdt_info = { diff --git a/include/exec/helper-gen-common.h b/include/exec/helper-gen-common.h index 5d6d78a625..834590dc4e 100644 --- a/include/exec/helper-gen-common.h +++ b/include/exec/helper-gen-common.h @@ -11,8 +11,4 @@ #include "exec/helper-gen.h.inc" #undef HELPER_H -#define HELPER_H "accel/tcg/plugin-helpers.h" -#include "exec/helper-gen.h.inc" -#undef HELPER_H - #endif /* HELPER_GEN_COMMON_H */ diff --git a/include/exec/helper-gen.h.inc b/include/exec/helper-gen.h.inc index d9fd3ed72a..dabe138e20 100644 --- a/include/exec/helper-gen.h.inc +++ b/include/exec/helper-gen.h.inc @@ -14,7 +14,8 @@ extern TCGHelperInfo glue(helper_info_, name); \ static inline void glue(gen_helper_, name)(dh_retvar_decl0(ret)) \ { \ - tcg_gen_call0(&glue(helper_info_, name), dh_retvar(ret)); \ + tcg_gen_call0(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret)); \ } #define DEF_HELPER_FLAGS_1(name, flags, ret, t1) \ @@ -22,7 +23,8 @@ extern TCGHelperInfo glue(helper_info_, name); \ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t1, 1)) \ { \ - tcg_gen_call1(&glue(helper_info_, name), dh_retvar(ret), \ + tcg_gen_call1(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret), \ dh_arg(t1, 1)); \ } @@ -31,7 +33,8 @@ extern TCGHelperInfo glue(helper_info_, name); \ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t1, 1), dh_arg_decl(t2, 2)) \ { \ - tcg_gen_call2(&glue(helper_info_, name), dh_retvar(ret), \ + tcg_gen_call2(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret), \ dh_arg(t1, 1), dh_arg(t2, 2)); \ } @@ -40,7 +43,8 @@ extern TCGHelperInfo glue(helper_info_, name); \ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t1, 1), dh_arg_decl(t2, 2), dh_arg_decl(t3, 3)) \ { \ - tcg_gen_call3(&glue(helper_info_, name), dh_retvar(ret), \ + tcg_gen_call3(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret), \ dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3)); \ } @@ -50,7 +54,8 @@ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t1, 1), dh_arg_decl(t2, 2), \ dh_arg_decl(t3, 3), dh_arg_decl(t4, 4)) \ { \ - tcg_gen_call4(&glue(helper_info_, name), dh_retvar(ret), \ + tcg_gen_call4(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret), \ dh_arg(t1, 1), dh_arg(t2, 2), \ dh_arg(t3, 3), dh_arg(t4, 4)); \ } @@ -61,7 +66,8 @@ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t1, 1), dh_arg_decl(t2, 2), dh_arg_decl(t3, 3), \ dh_arg_decl(t4, 4), dh_arg_decl(t5, 5)) \ { \ - tcg_gen_call5(&glue(helper_info_, name), dh_retvar(ret), \ + tcg_gen_call5(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret), \ dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3), \ dh_arg(t4, 4), dh_arg(t5, 5)); \ } @@ -72,7 +78,8 @@ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t1, 1), dh_arg_decl(t2, 2), dh_arg_decl(t3, 3), \ dh_arg_decl(t4, 4), dh_arg_decl(t5, 5), dh_arg_decl(t6, 6)) \ { \ - tcg_gen_call6(&glue(helper_info_, name), dh_retvar(ret), \ + tcg_gen_call6(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret), \ dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3), \ dh_arg(t4, 4), dh_arg(t5, 5), dh_arg(t6, 6)); \ } @@ -84,7 +91,8 @@ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t4, 4), dh_arg_decl(t5, 5), dh_arg_decl(t6, 6), \ dh_arg_decl(t7, 7)) \ { \ - tcg_gen_call7(&glue(helper_info_, name), dh_retvar(ret), \ + tcg_gen_call7(glue(helper_info_,name).func, \ + &glue(helper_info_,name), dh_retvar(ret), \ dh_arg(t1, 1), dh_arg(t2, 2), dh_arg(t3, 3), \ dh_arg(t4, 4), dh_arg(t5, 5), dh_arg(t6, 6), \ dh_arg(t7, 7)); \ diff --git a/include/exec/helper-proto-common.h b/include/exec/helper-proto-common.h index 8b67170a22..16782ef46c 100644 --- a/include/exec/helper-proto-common.h +++ b/include/exec/helper-proto-common.h @@ -13,8 +13,4 @@ #include "exec/helper-proto.h.inc" #undef HELPER_H -#define HELPER_H "accel/tcg/plugin-helpers.h" -#include "exec/helper-proto.h.inc" -#undef HELPER_H - #endif /* HELPER_PROTO_COMMON_H */ diff --git a/include/exec/plugin-gen.h b/include/exec/plugin-gen.h index c4552b5061..f333f33198 100644 --- a/include/exec/plugin-gen.h +++ b/include/exec/plugin-gen.h @@ -25,7 +25,6 @@ void plugin_gen_insn_start(CPUState *cpu, const struct DisasContextBase *db); void plugin_gen_insn_end(void); void plugin_gen_disable_mem_helpers(void); -void plugin_gen_empty_mem_callback(TCGv_i64 addr, uint32_t info); #else /* !CONFIG_PLUGIN */ @@ -48,9 +47,6 @@ static inline void plugin_gen_tb_end(CPUState *cpu, size_t num_insns) static inline void plugin_gen_disable_mem_helpers(void) { } -static inline void plugin_gen_empty_mem_callback(TCGv_i64 addr, uint32_t info) -{ } - #endif /* CONFIG_PLUGIN */ #endif /* QEMU_PLUGIN_GEN_H */ diff --git a/include/hw/display/dm163.h b/include/hw/display/dm163.h new file mode 100644 index 0000000000..4377f77bb7 --- /dev/null +++ b/include/hw/display/dm163.h @@ -0,0 +1,59 @@ +/* + * QEMU DM163 8x3-channel constant current led driver + * driving columns of associated 8x8 RGB matrix. + * + * Copyright (C) 2024 Samuel Tardieu <sam@rfc1149.net> + * Copyright (C) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr> + * Copyright (C) 2024 Inès Varhol <ines.varhol@telecom-paris.fr> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_DISPLAY_DM163_H +#define HW_DISPLAY_DM163_H + +#include "qom/object.h" +#include "hw/qdev-core.h" + +#define TYPE_DM163 "dm163" +OBJECT_DECLARE_SIMPLE_TYPE(DM163State, DM163); + +#define RGB_MATRIX_NUM_ROWS 8 +#define RGB_MATRIX_NUM_COLS 8 +#define DM163_NUM_LEDS (RGB_MATRIX_NUM_COLS * 3) +/* The last row is filled with 0 (turned off row) */ +#define COLOR_BUFFER_SIZE (RGB_MATRIX_NUM_ROWS + 1) + +typedef struct DM163State { + DeviceState parent_obj; + + /* DM163 driver */ + uint64_t bank0_shift_register[3]; + uint64_t bank1_shift_register[3]; + uint16_t latched_outputs[DM163_NUM_LEDS]; + uint16_t outputs[DM163_NUM_LEDS]; + qemu_irq sout; + + uint8_t sin; + uint8_t dck; + uint8_t rst_b; + uint8_t lat_b; + uint8_t selbk; + uint8_t en_b; + + /* IM120417002 colors shield */ + uint8_t activated_rows; + + /* 8x8 RGB matrix */ + QemuConsole *console; + uint8_t redraw; + /* Rows currently being displayed on the matrix. */ + /* The last row is filled with 0 (turned off row) */ + uint32_t buffer[COLOR_BUFFER_SIZE][RGB_MATRIX_NUM_COLS]; + uint8_t last_buffer_idx; + uint8_t buffer_idx_of_row[RGB_MATRIX_NUM_ROWS]; + /* Used to simulate retinal persistence of rows */ + uint8_t row_persistence_delay[RGB_MATRIX_NUM_ROWS]; +} DM163State; + +#endif /* HW_DISPLAY_DM163_H */ diff --git a/include/hw/intc/loongarch_extioi.h b/include/hw/intc/loongarch_extioi.h index a0a46b888c..410c6e1121 100644 --- a/include/hw/intc/loongarch_extioi.h +++ b/include/hw/intc/loongarch_extioi.h @@ -39,6 +39,7 @@ #define EXTIOI_COREISR_END (0xB20 - APIC_OFFSET) #define EXTIOI_COREMAP_START (0xC00 - APIC_OFFSET) #define EXTIOI_COREMAP_END (0xD00 - APIC_OFFSET) +#define EXTIOI_SIZE 0x800 typedef struct ExtIOICore { uint32_t coreisr[EXTIOI_IRQS_GROUP_COUNT]; diff --git a/include/hw/loongarch/boot.h b/include/hw/loongarch/boot.h new file mode 100644 index 0000000000..4ebcc89dcf --- /dev/null +++ b/include/hw/loongarch/boot.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Definitions for LoongArch boot. + * + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef HW_LOONGARCH_BOOT_H +#define HW_LOONGARCH_BOOT_H + +/* UEFI 2.10 */ +#define EFI_SYSTEM_TABLE_SIGNATURE 0x5453595320494249 +#define EFI_2_100_SYSTEM_TABLE_REVISION ((2<<16) | (100)) +#define EFI_SPECIFICATION_VERSION EFI_SYSTEM_TABLE_REVISION +#define EFI_SYSTEM_TABLE_REVISION EFI_2_100_SYSTEM_TABLE_REVISION + +#define FW_VERSION 0x1 +#define FW_PATCHLEVEL 0x0 + +typedef struct { + uint8_t b[16]; +} efi_guid_t QEMU_ALIGNED(8); + +#define EFI_GUID(a, b, c, d...) (efi_guid_t){ { \ + (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \ + (b) & 0xff, ((b) >> 8) & 0xff, \ + (c) & 0xff, ((c) >> 8) & 0xff, d } } + +#define LINUX_EFI_BOOT_MEMMAP_GUID \ + EFI_GUID(0x800f683f, 0xd08b, 0x423a, 0xa2, 0x93, \ + 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4) + +#define LINUX_EFI_INITRD_MEDIA_GUID \ + EFI_GUID(0x5568e427, 0x68fc, 0x4f3d, 0xac, 0x74, \ + 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68) + +#define DEVICE_TREE_GUID \ + EFI_GUID(0xb1b621d5, 0xf19c, 0x41a5, 0x83, 0x0b, \ + 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0) + +struct efi_config_table { + efi_guid_t guid; + uint64_t *ptr; + const char name[16]; +}; + +typedef struct { + uint64_t signature; + uint32_t revision; + uint32_t headersize; + uint32_t crc32; + uint32_t reserved; +} efi_table_hdr_t; + +struct efi_configuration_table { + efi_guid_t guid; + void *table; +}; + +struct efi_system_table { + efi_table_hdr_t hdr; + uint64_t fw_vendor; /* physical addr of CHAR16 vendor string */ + uint32_t fw_revision; + uint64_t con_in_handle; + uint64_t *con_in; + uint64_t con_out_handle; + uint64_t *con_out; + uint64_t stderr_handle; + uint64_t stderr_placeholder; + uint64_t *runtime; + uint64_t *boottime; + uint64_t nr_tables; + struct efi_configuration_table *tables; +}; + +typedef struct { + uint32_t type; + uint32_t pad; + uint64_t phys_addr; + uint64_t virt_addr; + uint64_t num_pages; + uint64_t attribute; +} efi_memory_desc_t; + +struct efi_boot_memmap { + uint64_t map_size; + uint64_t desc_size; + uint32_t desc_ver; + uint64_t map_key; + uint64_t buff_size; + efi_memory_desc_t map[32]; +}; + +struct efi_initrd { + uint64_t base; + uint64_t size; +}; + +struct loongarch_boot_info { + uint64_t ram_size; + const char *kernel_filename; + const char *kernel_cmdline; + const char *initrd_filename; + uint64_t a0, a1, a2; +}; + +void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info); + +#endif /* HW_LOONGARCH_BOOT_H */ diff --git a/include/hw/loongarch/virt.h b/include/hw/loongarch/virt.h index 252f7df7f4..4e14bf6060 100644 --- a/include/hw/loongarch/virt.h +++ b/include/hw/loongarch/virt.h @@ -13,6 +13,7 @@ #include "qemu/queue.h" #include "hw/intc/loongarch_ipi.h" #include "hw/block/flash.h" +#include "hw/loongarch/boot.h" #define LOONGARCH_MAX_CPUS 256 @@ -32,6 +33,20 @@ #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 COMMAND_LINE_SIZE 512 + +#define FDT_BASE 0x100000 + +extern struct memmap_entry *memmap_table; +extern unsigned memmap_entries; + +struct memmap_entry { + uint64_t address; + uint64_t length; + uint32_t type; + uint32_t reserved; +}; + struct LoongArchMachineState { /*< private >*/ MachineState parent_obj; @@ -55,6 +70,7 @@ struct LoongArchMachineState { MemoryRegion system_iocsr; MemoryRegion iocsr_mem; AddressSpace as_iocsr; + struct loongarch_boot_info bootinfo; }; #define TYPE_LOONGARCH_MACHINE MACHINE_TYPE_NAME("virt") diff --git a/include/hw/pci-host/ls7a.h b/include/hw/pci-host/ls7a.h index e753449593..cd7c9ec7bc 100644 --- a/include/hw/pci-host/ls7a.h +++ b/include/hw/pci-host/ls7a.h @@ -24,6 +24,8 @@ #define VIRT_PCH_REG_BASE 0x10000000UL #define VIRT_IOAPIC_REG_BASE (VIRT_PCH_REG_BASE) #define VIRT_PCH_MSI_ADDR_LOW 0x2FF00000UL +#define VIRT_PCH_REG_SIZE 0x400 +#define VIRT_PCH_MSI_SIZE 0x8 /* * GSI_BASE is hard-coded with 64 in linux kernel, else kernel fails to boot diff --git a/include/hw/watchdog/sbsa_gwdt.h b/include/hw/watchdog/sbsa_gwdt.h index 70b137de30..4bdc6c6fdb 100644 --- a/include/hw/watchdog/sbsa_gwdt.h +++ b/include/hw/watchdog/sbsa_gwdt.h @@ -55,8 +55,6 @@ #define SBSA_GWDT_RMMIO_SIZE 0x1000 #define SBSA_GWDT_CMMIO_SIZE 0x1000 -#define SBSA_TIMER_FREQ 62500000 /* Hz */ - typedef struct SBSA_GWDTState { /* <private> */ SysBusDevice parent_obj; @@ -67,6 +65,7 @@ typedef struct SBSA_GWDTState { qemu_irq irq; QEMUTimer *timer; + uint64_t freq; uint32_t id; uint32_t wcs; diff --git a/include/qemu/cutils.h b/include/qemu/cutils.h index 92c927a6a3..741dade7cf 100644 --- a/include/qemu/cutils.h +++ b/include/qemu/cutils.h @@ -187,9 +187,39 @@ char *freq_to_str(uint64_t freq_hz); /* used to print char* safely */ #define STR_OR_NULL(str) ((str) ? (str) : "null") -bool buffer_is_zero(const void *buf, size_t len); +/* + * Check if a buffer is all zeroes. + */ + +bool buffer_is_zero_ool(const void *vbuf, size_t len); +bool buffer_is_zero_ge256(const void *vbuf, size_t len); bool test_buffer_is_zero_next_accel(void); +static inline bool buffer_is_zero_sample3(const char *buf, size_t len) +{ + /* + * For any reasonably sized buffer, these three samples come from + * three different cachelines. In qemu-img usage, we find that + * each byte eliminates more than half of all buffer testing. + * It is therefore critical to performance that the byte tests + * short-circuit, so that we do not pull in additional cache lines. + * Do not "optimize" this to !(a | b | c). + */ + return !buf[0] && !buf[len - 1] && !buf[len / 2]; +} + +#ifdef __OPTIMIZE__ +static inline bool buffer_is_zero(const void *buf, size_t len) +{ + return (__builtin_constant_p(len) && len >= 256 + ? buffer_is_zero_sample3(buf, len) && + buffer_is_zero_ge256(buf, len) + : buffer_is_zero_ool(buf, len)); +} +#else +#define buffer_is_zero buffer_is_zero_ool +#endif + /* * Implementation of ULEB128 (http://en.wikipedia.org/wiki/LEB128) * Input is limited to 14-bit numbers diff --git a/include/qemu/log.h b/include/qemu/log.h index df59bfabcd..e10e24cd4f 100644 --- a/include/qemu/log.h +++ b/include/qemu/log.h @@ -36,6 +36,7 @@ bool qemu_log_separate(void); #define LOG_STRACE (1 << 19) #define LOG_PER_THREAD (1 << 20) #define CPU_LOG_TB_VPU (1 << 21) +#define LOG_TB_OP_PLUGIN (1 << 22) /* Lock/unlock output. */ diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h index 41db748eda..18062528c1 100644 --- a/include/qemu/plugin.h +++ b/include/qemu/plugin.h @@ -67,16 +67,9 @@ union qemu_plugin_cb_sig { }; enum plugin_dyn_cb_type { - PLUGIN_CB_INSN, - PLUGIN_CB_MEM, - PLUGIN_N_CB_TYPES, -}; - -enum plugin_dyn_cb_subtype { PLUGIN_CB_REGULAR, - PLUGIN_CB_REGULAR_R, + PLUGIN_CB_MEM_REGULAR, PLUGIN_CB_INLINE, - PLUGIN_N_CB_SUBTYPES, }; /* @@ -85,14 +78,17 @@ enum plugin_dyn_cb_subtype { * instance of a callback to be called upon the execution of a particular TB. */ struct qemu_plugin_dyn_cb { - union qemu_plugin_cb_sig f; void *userp; - enum plugin_dyn_cb_subtype type; + enum plugin_dyn_cb_type type; /* @rw applies to mem callbacks only (both regular and inline) */ enum qemu_plugin_mem_rw rw; /* fields specific to each dyn_cb type go here */ union { struct { + union qemu_plugin_cb_sig f; + TCGHelperInfo *info; + } regular; + struct { qemu_plugin_u64 entry; enum qemu_plugin_op op; uint64_t imm; @@ -105,7 +101,8 @@ struct qemu_plugin_insn { GByteArray *data; uint64_t vaddr; void *haddr; - GArray *cbs[PLUGIN_N_CB_TYPES][PLUGIN_N_CB_SUBTYPES]; + GArray *insn_cbs; + GArray *mem_cbs; bool calls_helpers; /* if set, the instruction calls helpers that might access guest memory */ @@ -132,21 +129,6 @@ static inline void qemu_plugin_insn_cleanup_fn(gpointer data) g_byte_array_free(insn->data, true); } -static inline struct qemu_plugin_insn *qemu_plugin_insn_alloc(void) -{ - int i, j; - struct qemu_plugin_insn *insn = g_new0(struct qemu_plugin_insn, 1); - insn->data = g_byte_array_sized_new(4); - - for (i = 0; i < PLUGIN_N_CB_TYPES; i++) { - for (j = 0; j < PLUGIN_N_CB_SUBTYPES; j++) { - insn->cbs[i][j] = g_array_new(false, false, - sizeof(struct qemu_plugin_dyn_cb)); - } - } - return insn; -} - /* Internal context for this TranslationBlock */ struct qemu_plugin_tb { GPtrArray *insns; @@ -160,41 +142,10 @@ struct qemu_plugin_tb { /* if set, the TB calls helpers that might access guest memory */ bool mem_helper; - GArray *cbs[PLUGIN_N_CB_SUBTYPES]; + GArray *cbs; }; /** - * qemu_plugin_tb_insn_get(): get next plugin record for translation. - * @tb: the internal tb context - * @pc: address of instruction - */ -static inline -struct qemu_plugin_insn *qemu_plugin_tb_insn_get(struct qemu_plugin_tb *tb, - uint64_t pc) -{ - struct qemu_plugin_insn *insn; - int i, j; - - if (unlikely(tb->n == tb->insns->len)) { - struct qemu_plugin_insn *new_insn = qemu_plugin_insn_alloc(); - g_ptr_array_add(tb->insns, new_insn); - } - insn = g_ptr_array_index(tb->insns, tb->n++); - g_byte_array_set_size(insn->data, 0); - insn->calls_helpers = false; - insn->mem_helper = false; - insn->vaddr = pc; - - for (i = 0; i < PLUGIN_N_CB_TYPES; i++) { - for (j = 0; j < PLUGIN_N_CB_SUBTYPES; j++) { - g_array_set_size(insn->cbs[i][j], 0); - } - } - - return insn; -} - -/** * struct CPUPluginState - per-CPU state for plugins * @event_mask: plugin event bitmap. Modified only via async work. */ diff --git a/include/tcg/helper-info.h b/include/tcg/helper-info.h index 7c27d6164a..909fe73afa 100644 --- a/include/tcg/helper-info.h +++ b/include/tcg/helper-info.h @@ -12,6 +12,9 @@ #ifdef CONFIG_TCG_INTERPRETER #include <ffi.h> #endif +#include "tcg-target-reg-bits.h" + +#define MAX_CALL_IARGS 7 /* * Describe the calling convention of a given argument type. diff --git a/include/tcg/tcg-op-common.h b/include/tcg/tcg-op-common.h index 2d932a515e..009e2778c5 100644 --- a/include/tcg/tcg-op-common.h +++ b/include/tcg/tcg-op-common.h @@ -74,8 +74,8 @@ void tcg_gen_goto_tb(unsigned idx); */ void tcg_gen_lookup_and_goto_ptr(void); -void tcg_gen_plugin_cb_start(unsigned from, unsigned type, unsigned wr); -void tcg_gen_plugin_cb_end(void); +void tcg_gen_plugin_cb(unsigned from); +void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned meminfo); /* 32 bit ops */ diff --git a/include/tcg/tcg-opc.h b/include/tcg/tcg-opc.h index b80227fa1c..546eb49c11 100644 --- a/include/tcg/tcg-opc.h +++ b/include/tcg/tcg-opc.h @@ -197,8 +197,8 @@ DEF(exit_tb, 0, 0, 1, TCG_OPF_BB_EXIT | TCG_OPF_BB_END) DEF(goto_tb, 0, 0, 1, TCG_OPF_BB_EXIT | TCG_OPF_BB_END) DEF(goto_ptr, 0, 1, 0, TCG_OPF_BB_EXIT | TCG_OPF_BB_END) -DEF(plugin_cb_start, 0, 0, 3, TCG_OPF_NOT_PRESENT) -DEF(plugin_cb_end, 0, 0, 0, TCG_OPF_NOT_PRESENT) +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, diff --git a/include/tcg/tcg.h b/include/tcg/tcg.h index 05a1912f8a..135e36d729 100644 --- a/include/tcg/tcg.h +++ b/include/tcg/tcg.h @@ -39,8 +39,6 @@ /* XXX: make safe guess about sizes */ #define MAX_OP_PER_INSTR 266 -#define MAX_CALL_IARGS 7 - #define CPU_TEMP_BUF_NLONGS 128 #define TCG_STATIC_FRAME_SIZE (CPU_TEMP_BUF_NLONGS * sizeof(long)) @@ -355,8 +353,6 @@ typedef TCGv_ptr TCGv_env; #define TCG_CALL_NO_SIDE_EFFECTS 0x0004 /* Helper is G_NORETURN. */ #define TCG_CALL_NO_RETURN 0x0008 -/* Helper is part of Plugins. */ -#define TCG_CALL_PLUGIN 0x0010 /* convenience version of most used call flags */ #define TCG_CALL_NO_RWG TCG_CALL_NO_READ_GLOBALS @@ -854,19 +850,22 @@ typedef struct TCGTargetOpDef { bool tcg_op_supported(TCGOpcode op); -void tcg_gen_call0(TCGHelperInfo *, TCGTemp *ret); -void tcg_gen_call1(TCGHelperInfo *, TCGTemp *ret, TCGTemp *); -void tcg_gen_call2(TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *); -void tcg_gen_call3(TCGHelperInfo *, TCGTemp *ret, TCGTemp *, - TCGTemp *, TCGTemp *); -void tcg_gen_call4(TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *, +void tcg_gen_call0(void *func, TCGHelperInfo *, TCGTemp *ret); +void tcg_gen_call1(void *func, TCGHelperInfo *, TCGTemp *ret, TCGTemp *); +void tcg_gen_call2(void *func, TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *); -void tcg_gen_call5(TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *, +void tcg_gen_call3(void *func, TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *, TCGTemp *); -void tcg_gen_call6(TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *, +void tcg_gen_call4(void *func, TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *, TCGTemp *, TCGTemp *); -void tcg_gen_call7(TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *, +void tcg_gen_call5(void *func, TCGHelperInfo *, TCGTemp *ret, TCGTemp *, TCGTemp *, TCGTemp *, TCGTemp *, TCGTemp *); +void tcg_gen_call6(void *func, TCGHelperInfo *, TCGTemp *ret, + TCGTemp *, TCGTemp *, TCGTemp *, TCGTemp *, + TCGTemp *, TCGTemp *); +void tcg_gen_call7(void *func, TCGHelperInfo *, TCGTemp *ret, + TCGTemp *, TCGTemp *, TCGTemp *, TCGTemp *, + TCGTemp *, TCGTemp *, TCGTemp *); TCGOp *tcg_emit_op(TCGOpcode opc, unsigned nargs); void tcg_op_remove(TCGContext *s, TCGOp *op); @@ -1071,5 +1070,6 @@ static inline const TCGOpcode *tcg_swap_vecop_list(const TCGOpcode *n) } bool tcg_can_emit_vecop_list(const TCGOpcode *, TCGType, unsigned); +void tcg_dump_ops(TCGContext *s, FILE *f, bool have_prefs); #endif /* TCG_H */ diff --git a/net/slirp.c b/net/slirp.c index 25b49c4526..eb9a456ed4 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -718,7 +718,12 @@ static SlirpState *slirp_lookup(Monitor *mon, const char *id) void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict) { - struct in_addr host_addr = { .s_addr = INADDR_ANY }; + struct sockaddr_in host_addr = { + .sin_family = AF_INET, + .sin_addr = { + .s_addr = INADDR_ANY, + }, + }; int host_port; char buf[256]; const char *src_str, *p; @@ -755,15 +760,21 @@ void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict) if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { goto fail_syntax; } - if (buf[0] != '\0' && !inet_aton(buf, &host_addr)) { + if (buf[0] != '\0' && !inet_aton(buf, &host_addr.sin_addr)) { goto fail_syntax; } if (qemu_strtoi(p, NULL, 10, &host_port)) { goto fail_syntax; } + host_addr.sin_port = htons(host_port); - err = slirp_remove_hostfwd(s->slirp, is_udp, host_addr, host_port); +#if SLIRP_CHECK_VERSION(4, 5, 0) + err = slirp_remove_hostxfwd(s->slirp, (struct sockaddr *) &host_addr, + sizeof(host_addr), is_udp ? SLIRP_HOSTFWD_UDP : 0); +#else + err = slirp_remove_hostfwd(s->slirp, is_udp, host_addr.sin_addr, host_port); +#endif monitor_printf(mon, "host forwarding rule for %s %s\n", src_str, err ? "not found" : "removed"); @@ -775,13 +786,24 @@ void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict) static int slirp_hostfwd(SlirpState *s, const char *redir_str, Error **errp) { - struct in_addr host_addr = { .s_addr = INADDR_ANY }; - struct in_addr guest_addr = { .s_addr = 0 }; + struct sockaddr_in host_addr = { + .sin_family = AF_INET, + .sin_addr = { + .s_addr = INADDR_ANY, + }, + }; + struct sockaddr_in guest_addr = { + .sin_family = AF_INET, + .sin_addr = { + .s_addr = 0, + }, + }; + int err; int host_port, guest_port; const char *p; char buf[256]; int is_udp; - char *end; + const char *end; const char *fail_reason = "Unknown reason"; p = redir_str; @@ -802,7 +824,7 @@ static int slirp_hostfwd(SlirpState *s, const char *redir_str, Error **errp) fail_reason = "Missing : separator"; goto fail_syntax; } - if (buf[0] != '\0' && !inet_aton(buf, &host_addr)) { + if (buf[0] != '\0' && !inet_aton(buf, &host_addr.sin_addr)) { fail_reason = "Bad host address"; goto fail_syntax; } @@ -811,29 +833,41 @@ static int slirp_hostfwd(SlirpState *s, const char *redir_str, Error **errp) fail_reason = "Bad host port separator"; goto fail_syntax; } - host_port = strtol(buf, &end, 0); - if (*end != '\0' || host_port < 0 || host_port > 65535) { + err = qemu_strtoi(buf, &end, 0, &host_port); + if (err || host_port < 0 || host_port > 65535) { fail_reason = "Bad host port"; goto fail_syntax; } + host_addr.sin_port = htons(host_port); if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { fail_reason = "Missing guest address"; goto fail_syntax; } - if (buf[0] != '\0' && !inet_aton(buf, &guest_addr)) { + if (buf[0] != '\0' && !inet_aton(buf, &guest_addr.sin_addr)) { fail_reason = "Bad guest address"; goto fail_syntax; } - guest_port = strtol(p, &end, 0); - if (*end != '\0' || guest_port < 1 || guest_port > 65535) { + err = qemu_strtoi(p, &end, 0, &guest_port); + if (err || guest_port < 1 || guest_port > 65535) { fail_reason = "Bad guest port"; goto fail_syntax; } + guest_addr.sin_port = htons(guest_port); + +#if SLIRP_CHECK_VERSION(4, 5, 0) + err = slirp_add_hostxfwd(s->slirp, + (struct sockaddr *) &host_addr, sizeof(host_addr), + (struct sockaddr *) &guest_addr, sizeof(guest_addr), + is_udp ? SLIRP_HOSTFWD_UDP : 0); +#else + err = slirp_add_hostfwd(s->slirp, is_udp, + host_addr.sin_addr, host_port, + guest_addr.sin_addr, guest_port); +#endif - if (slirp_add_hostfwd(s->slirp, is_udp, host_addr, host_port, guest_addr, - guest_port) < 0) { + if (err < 0) { error_setg(errp, "Could not set up host forwarding rule '%s'", redir_str); return -1; diff --git a/plugins/api.c b/plugins/api.c index eaee344d8e..2144da1fe8 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -92,12 +92,7 @@ void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, void *udata) { if (!tb->mem_only) { - int index = flags == QEMU_PLUGIN_CB_R_REGS || - flags == QEMU_PLUGIN_CB_RW_REGS ? - PLUGIN_CB_REGULAR_R : PLUGIN_CB_REGULAR; - - plugin_register_dyn_cb__udata(&tb->cbs[index], - cb, flags, udata); + plugin_register_dyn_cb__udata(&tb->cbs, cb, flags, udata); } } @@ -108,8 +103,7 @@ void qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( uint64_t imm) { if (!tb->mem_only) { - plugin_register_inline_op_on_entry( - &tb->cbs[PLUGIN_CB_INLINE], 0, op, entry, imm); + plugin_register_inline_op_on_entry(&tb->cbs, 0, op, entry, imm); } } @@ -119,12 +113,7 @@ void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn, void *udata) { if (!insn->mem_only) { - int index = flags == QEMU_PLUGIN_CB_R_REGS || - flags == QEMU_PLUGIN_CB_RW_REGS ? - PLUGIN_CB_REGULAR_R : PLUGIN_CB_REGULAR; - - plugin_register_dyn_cb__udata(&insn->cbs[PLUGIN_CB_INSN][index], - cb, flags, udata); + plugin_register_dyn_cb__udata(&insn->insn_cbs, cb, flags, udata); } } @@ -135,8 +124,7 @@ void qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( uint64_t imm) { if (!insn->mem_only) { - plugin_register_inline_op_on_entry( - &insn->cbs[PLUGIN_CB_INSN][PLUGIN_CB_INLINE], 0, op, entry, imm); + plugin_register_inline_op_on_entry(&insn->insn_cbs, 0, op, entry, imm); } } @@ -151,8 +139,7 @@ void qemu_plugin_register_vcpu_mem_cb(struct qemu_plugin_insn *insn, enum qemu_plugin_mem_rw rw, void *udata) { - plugin_register_vcpu_mem_cb(&insn->cbs[PLUGIN_CB_MEM][PLUGIN_CB_REGULAR], - cb, flags, rw, udata); + plugin_register_vcpu_mem_cb(&insn->mem_cbs, cb, flags, rw, udata); } void qemu_plugin_register_vcpu_mem_inline_per_vcpu( @@ -162,8 +149,7 @@ void qemu_plugin_register_vcpu_mem_inline_per_vcpu( qemu_plugin_u64 entry, uint64_t imm) { - plugin_register_inline_op_on_entry( - &insn->cbs[PLUGIN_CB_MEM][PLUGIN_CB_INLINE], rw, op, entry, imm); + plugin_register_inline_op_on_entry(&insn->mem_cbs, rw, op, entry, imm); } void qemu_plugin_register_vcpu_tb_trans_cb(qemu_plugin_id_t id, diff --git a/plugins/core.c b/plugins/core.c index 09c98382f5..081323dafc 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -307,7 +307,7 @@ static struct qemu_plugin_dyn_cb *plugin_get_dyn_cb(GArray **arr) GArray *cbs = *arr; if (!cbs) { - cbs = g_array_sized_new(false, false, + cbs = g_array_sized_new(false, true, sizeof(struct qemu_plugin_dyn_cb), 1); *arr = cbs; } @@ -338,12 +338,25 @@ void plugin_register_dyn_cb__udata(GArray **arr, enum qemu_plugin_cb_flags flags, void *udata) { - struct qemu_plugin_dyn_cb *dyn_cb = plugin_get_dyn_cb(arr); + static TCGHelperInfo info[3] = { + [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, + [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, + /* + * Match qemu_plugin_vcpu_udata_cb_t: + * void (*)(uint32_t, void *) + */ + [0 ... 2].typemask = (dh_typemask(void, 0) | + dh_typemask(i32, 1) | + dh_typemask(ptr, 2)) + }; + struct qemu_plugin_dyn_cb *dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->userp = udata; - /* Note flags are discarded as unused. */ - dyn_cb->f.vcpu_udata = cb; dyn_cb->type = PLUGIN_CB_REGULAR; + dyn_cb->regular.f.vcpu_udata = cb; + + assert((unsigned)flags < ARRAY_SIZE(info)); + dyn_cb->regular.info = &info[flags]; } void plugin_register_vcpu_mem_cb(GArray **arr, @@ -352,14 +365,38 @@ void plugin_register_vcpu_mem_cb(GArray **arr, enum qemu_plugin_mem_rw rw, void *udata) { - struct qemu_plugin_dyn_cb *dyn_cb; + /* + * Expect that the underlying type for enum qemu_plugin_meminfo_t + * is either int32_t or uint32_t, aka int or unsigned int. + */ + QEMU_BUILD_BUG_ON( + !__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) && + !__builtin_types_compatible_p(qemu_plugin_meminfo_t, int32_t)); + + static TCGHelperInfo info[3] = { + [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, + [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, + /* + * Match qemu_plugin_vcpu_mem_cb_t: + * void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *) + */ + [0 ... 2].typemask = + (dh_typemask(void, 0) | + dh_typemask(i32, 1) | + (__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) + ? dh_typemask(i32, 2) : dh_typemask(s32, 2)) | + dh_typemask(i64, 3) | + dh_typemask(ptr, 4)) + }; - dyn_cb = plugin_get_dyn_cb(arr); + struct qemu_plugin_dyn_cb *dyn_cb = plugin_get_dyn_cb(arr); dyn_cb->userp = udata; - /* Note flags are discarded as unused. */ - dyn_cb->type = PLUGIN_CB_REGULAR; + dyn_cb->type = PLUGIN_CB_MEM_REGULAR; dyn_cb->rw = rw; - dyn_cb->f.generic = cb; + dyn_cb->regular.f.vcpu_mem = cb; + + assert((unsigned)flags < ARRAY_SIZE(info)); + dyn_cb->regular.info = &info[flags]; } /* @@ -510,9 +547,9 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr, break; } switch (cb->type) { - case PLUGIN_CB_REGULAR: - cb->f.vcpu_mem(cpu->cpu_index, make_plugin_meminfo(oi, rw), - vaddr, cb->userp); + case PLUGIN_CB_MEM_REGULAR: + cb->regular.f.vcpu_mem(cpu->cpu_index, make_plugin_meminfo(oi, rw), + vaddr, cb->userp); break; case PLUGIN_CB_INLINE: exec_inline_op(cb, cpu->cpu_index); diff --git a/qga/commands-common-ssh.c b/qga/commands-common-ssh.c new file mode 100644 index 0000000000..537869fb98 --- /dev/null +++ b/qga/commands-common-ssh.c @@ -0,0 +1,50 @@ +/* + * 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 "qapi/error.h" +#include "commands-common-ssh.h" + +GStrv read_authkeys(const char *path, Error **errp) +{ + g_autoptr(GError) err = NULL; + g_autofree char *contents = NULL; + + if (!g_file_get_contents(path, &contents, NULL, &err)) { + error_setg(errp, "failed to read '%s': %s", path, err->message); + return NULL; + } + + return g_strsplit(contents, "\n", -1); +} + +bool check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp) +{ + size_t n = 0; + strList *k; + + for (k = keys; k != NULL; k = k->next) { + if (!check_openssh_pub_key(k->value, errp)) { + return false; + } + n++; + } + + if (nkeys) { + *nkeys = n; + } + return true; +} + +bool check_openssh_pub_key(const char *key, Error **errp) +{ + /* simple sanity-check, we may want more? */ + if (!key || key[0] == '#' || strchr(key, '\n')) { + error_setg(errp, "invalid OpenSSH public key: '%s'", key); + return false; + } + + return true; +} diff --git a/qga/commands-common-ssh.h b/qga/commands-common-ssh.h new file mode 100644 index 0000000000..14d955fa84 --- /dev/null +++ b/qga/commands-common-ssh.h @@ -0,0 +1,10 @@ +/* + * 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 "qapi/qapi-builtin-types.h" + +GStrv read_authkeys(const char *path, Error **errp); +bool check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp); +bool check_openssh_pub_key(const char *key, Error **errp); diff --git a/qga/commands-posix-ssh.c b/qga/commands-posix-ssh.c index 236f80de44..dd2ecb453a 100644 --- a/qga/commands-posix-ssh.c +++ b/qga/commands-posix-ssh.c @@ -9,6 +9,7 @@ #include <locale.h> #include <pwd.h> +#include "commands-common-ssh.h" #include "qapi/error.h" #include "qga-qapi-commands.h" @@ -81,37 +82,6 @@ mkdir_for_user(const char *path, const struct passwd *p, } static bool -check_openssh_pub_key(const char *key, Error **errp) -{ - /* simple sanity-check, we may want more? */ - if (!key || key[0] == '#' || strchr(key, '\n')) { - error_setg(errp, "invalid OpenSSH public key: '%s'", key); - return false; - } - - return true; -} - -static bool -check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp) -{ - size_t n = 0; - strList *k; - - for (k = keys; k != NULL; k = k->next) { - if (!check_openssh_pub_key(k->value, errp)) { - return false; - } - n++; - } - - if (nkeys) { - *nkeys = n; - } - return true; -} - -static bool write_authkeys(const char *path, const GStrv keys, const struct passwd *p, Error **errp) { @@ -139,21 +109,6 @@ write_authkeys(const char *path, const GStrv keys, return true; } -static GStrv -read_authkeys(const char *path, Error **errp) -{ - g_autoptr(GError) err = NULL; - g_autofree char *contents = NULL; - - if (!g_file_get_contents(path, &contents, NULL, &err)) { - error_setg(errp, "failed to read '%s': %s", path, err->message); - return NULL; - } - - return g_strsplit(contents, "\n", -1); - -} - void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys, bool has_reset, bool reset, diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 26008db497..7a065c4085 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -76,12 +76,159 @@ static void ga_wait_child(pid_t pid, int *status, Error **errp) g_assert(rpid == pid); } +static ssize_t ga_pipe_read_str(int fd[2], char **str) +{ + ssize_t n, len = 0; + char buf[1024]; + + close(fd[1]); + fd[1] = -1; + while ((n = read(fd[0], buf, sizeof(buf))) != 0) { + if (n < 0) { + if (errno == EINTR) { + continue; + } else { + len = -errno; + break; + } + } + *str = g_realloc(*str, len + n + 1); + memcpy(*str + len, buf, n); + len += n; + *str[len] = '\0'; + } + close(fd[0]); + fd[0] = -1; + + return len; +} + +/* + * Helper to run command with input/output redirection, + * sending string to stdin and taking error message from + * stdout/err. + */ +static int ga_run_command(const char *argv[], const char *in_str, + const char *action, Error **errp) +{ + pid_t pid; + int status; + int retcode = -1; + int infd[2] = { -1, -1 }; + int outfd[2] = { -1, -1 }; + char *str = NULL; + ssize_t len = 0; + + if ((in_str && !g_unix_open_pipe(infd, FD_CLOEXEC, NULL)) || + !g_unix_open_pipe(outfd, FD_CLOEXEC, NULL)) { + error_setg(errp, "cannot create pipe FDs"); + goto out; + } + + pid = fork(); + if (pid == 0) { + char *cherr = NULL; + + setsid(); + + if (in_str) { + /* Redirect stdin to infd. */ + close(infd[1]); + dup2(infd[0], 0); + close(infd[0]); + } else { + reopen_fd_to_null(0); + } + + /* Redirect stdout/stderr to outfd. */ + close(outfd[0]); + dup2(outfd[1], 1); + dup2(outfd[1], 2); + close(outfd[1]); + + execvp(argv[0], (char *const *)argv); + + /* Write the cause of failed exec to pipe for the parent to read it. */ + cherr = g_strdup_printf("failed to exec '%s'", argv[0]); + perror(cherr); + g_free(cherr); + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + goto out; + } + + if (in_str) { + close(infd[0]); + infd[0] = -1; + if (qemu_write_full(infd[1], in_str, strlen(in_str)) != + strlen(in_str)) { + error_setg_errno(errp, errno, "%s: cannot write to stdin pipe", + action); + goto out; + } + close(infd[1]); + infd[1] = -1; + } + + len = ga_pipe_read_str(outfd, &str); + if (len < 0) { + error_setg_errno(errp, -len, "%s: cannot read from stdout/stderr pipe", + action); + goto out; + } + + ga_wait_child(pid, &status, errp); + if (*errp) { + goto out; + } + + if (!WIFEXITED(status)) { + if (len) { + error_setg(errp, "child process has terminated abnormally: %s", + str); + } else { + error_setg(errp, "child process has terminated abnormally"); + } + goto out; + } + + retcode = WEXITSTATUS(status); + + if (WEXITSTATUS(status)) { + if (len) { + error_setg(errp, "child process has failed to %s: %s", + action, str); + } else { + error_setg(errp, "child process has failed to %s: exit status %d", + action, WEXITSTATUS(status)); + } + goto out; + } + +out: + g_free(str); + + if (infd[0] != -1) { + close(infd[0]); + } + if (infd[1] != -1) { + close(infd[1]); + } + if (outfd[0] != -1) { + close(outfd[0]); + } + if (outfd[1] != -1) { + close(outfd[1]); + } + + return retcode; +} + void qmp_guest_shutdown(const char *mode, Error **errp) { const char *shutdown_flag; Error *local_err = NULL; - pid_t pid; - int status; #ifdef CONFIG_SOLARIS const char *powerdown_flag = "-i5"; @@ -110,67 +257,31 @@ void qmp_guest_shutdown(const char *mode, Error **errp) return; } - pid = fork(); - if (pid == 0) { - /* child, start the shutdown */ - setsid(); - reopen_fd_to_null(0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - + const char *argv[] = {"/sbin/shutdown", #ifdef CONFIG_SOLARIS - execl("/sbin/shutdown", "shutdown", shutdown_flag, "-g0", "-y", - "hypervisor initiated shutdown", (char *)NULL); + shutdown_flag, "-g0", "-y", #elif defined(CONFIG_BSD) - execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", - "hypervisor initiated shutdown", (char *)NULL); + shutdown_flag, "+0", #else - execl("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0", - "hypervisor initiated shutdown", (char *)NULL); + "-h", shutdown_flag, "+0", #endif - _exit(EXIT_FAILURE); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - return; - } + "hypervisor initiated shutdown", (char *) NULL}; - ga_wait_child(pid, &status, &local_err); + ga_run_command(argv, NULL, "shutdown", &local_err); if (local_err) { error_propagate(errp, local_err); return; } - if (!WIFEXITED(status)) { - error_setg(errp, "child process has terminated abnormally"); - return; - } - - if (WEXITSTATUS(status)) { - error_setg(errp, "child process has failed to shutdown"); - return; - } - /* succeeded */ } void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp) { int ret; - int status; - pid_t pid; Error *local_err = NULL; struct timeval tv; - static const char hwclock_path[] = "/sbin/hwclock"; - static int hwclock_available = -1; - - if (hwclock_available < 0) { - hwclock_available = (access(hwclock_path, X_OK) == 0); - } - - if (!hwclock_available) { - error_setg(errp, QERR_UNSUPPORTED); - return; - } + const char *argv[] = {"/sbin/hwclock", has_time ? "-w" : "-s", NULL}; /* If user has passed a time, validate and set it. */ if (has_time) { @@ -201,37 +312,12 @@ void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp) * just need to synchronize the hardware clock. However, if no time was * passed, user is requesting the opposite: set the system time from the * hardware clock (RTC). */ - pid = fork(); - if (pid == 0) { - setsid(); - reopen_fd_to_null(0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - - /* Use '/sbin/hwclock -w' to set RTC from the system time, - * or '/sbin/hwclock -s' to set the system time from RTC. */ - execl(hwclock_path, "hwclock", has_time ? "-w" : "-s", NULL); - _exit(EXIT_FAILURE); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - return; - } - - ga_wait_child(pid, &status, &local_err); + ga_run_command(argv, NULL, "set hardware clock to system time", + &local_err); if (local_err) { error_propagate(errp, local_err); return; } - - if (!WIFEXITED(status)) { - error_setg(errp, "child process has terminated abnormally"); - return; - } - - if (WEXITSTATUS(status)) { - error_setg(errp, "hwclock failed to set hardware clock to system time"); - return; - } } typedef enum { @@ -650,8 +736,6 @@ static const char *fsfreeze_hook_arg_string[] = { static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp) { - int status; - pid_t pid; const char *hook; const char *arg_str = fsfreeze_hook_arg_string[arg]; Error *local_err = NULL; @@ -660,42 +744,15 @@ static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp) if (!hook) { return; } - if (access(hook, X_OK) != 0) { - error_setg_errno(errp, errno, "can't access fsfreeze hook '%s'", hook); - return; - } - - slog("executing fsfreeze hook with arg '%s'", arg_str); - pid = fork(); - if (pid == 0) { - setsid(); - reopen_fd_to_null(0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - execl(hook, hook, arg_str, NULL); - _exit(EXIT_FAILURE); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - return; - } + const char *argv[] = {hook, arg_str, NULL}; - ga_wait_child(pid, &status, &local_err); + slog("executing fsfreeze hook with arg '%s'", arg_str); + ga_run_command(argv, NULL, "execute fsfreeze hook", &local_err); if (local_err) { error_propagate(errp, local_err); return; } - - if (!WIFEXITED(status)) { - error_setg(errp, "fsfreeze hook has terminated abnormally"); - return; - } - - status = WEXITSTATUS(status); - if (status) { - error_setg(errp, "fsfreeze hook has failed with status %d", status); - return; - } } /* @@ -1569,8 +1626,10 @@ static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount, nonroot_total = used + buf.f_bavail; fs->used_bytes = used * fr_size; fs->total_bytes = nonroot_total * fr_size; + fs->total_bytes_privileged = buf.f_blocks * fr_size; fs->has_total_bytes = true; + fs->has_total_bytes_privileged = true; fs->has_used_bytes = true; } @@ -1869,52 +1928,21 @@ static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp) static void linux_sys_state_suspend(SuspendMode mode, Error **errp) { - Error *local_err = NULL; + g_autoptr(GError) local_gerr = NULL; const char *sysfile_strs[3] = {"disk", "mem", NULL}; const char *sysfile_str = sysfile_strs[mode]; - pid_t pid; - int status; if (!sysfile_str) { error_setg(errp, "unknown guest suspend mode"); return; } - pid = fork(); - if (!pid) { - /* child */ - int fd; - - setsid(); - reopen_fd_to_null(0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - - fd = open(LINUX_SYS_STATE_FILE, O_WRONLY); - if (fd < 0) { - _exit(EXIT_FAILURE); - } - - if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) { - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - return; - } - - ga_wait_child(pid, &status, &local_err); - if (local_err) { - error_propagate(errp, local_err); + if (!g_file_set_contents(LINUX_SYS_STATE_FILE, sysfile_str, + -1, &local_gerr)) { + error_setg(errp, "suspend: cannot write to '%s': %s", + LINUX_SYS_STATE_FILE, local_gerr->message); return; } - - if (WEXITSTATUS(status)) { - error_setg(errp, "child process has failed to suspend"); - } - } static void guest_suspend(SuspendMode mode, Error **errp) @@ -2123,14 +2151,8 @@ void qmp_guest_set_user_password(const char *username, Error **errp) { Error *local_err = NULL; - char *passwd_path = NULL; - pid_t pid; - int status; - int datafd[2] = { -1, -1 }; - char *rawpasswddata = NULL; + g_autofree char *rawpasswddata = NULL; size_t rawpasswdlen; - char *chpasswddata = NULL; - size_t chpasswdlen; rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp); if (!rawpasswddata) { @@ -2141,95 +2163,31 @@ void qmp_guest_set_user_password(const char *username, if (strchr(rawpasswddata, '\n')) { error_setg(errp, "forbidden characters in raw password"); - goto out; + return; } if (strchr(username, '\n') || strchr(username, ':')) { error_setg(errp, "forbidden characters in username"); - goto out; - } - -#ifdef __FreeBSD__ - chpasswddata = g_strdup(rawpasswddata); - passwd_path = g_find_program_in_path("pw"); -#else - chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata); - passwd_path = g_find_program_in_path("chpasswd"); -#endif - - chpasswdlen = strlen(chpasswddata); - - if (!passwd_path) { - error_setg(errp, "cannot find 'passwd' program in PATH"); - goto out; - } - - if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) { - error_setg(errp, "cannot create pipe FDs"); - goto out; + return; } - pid = fork(); - if (pid == 0) { - close(datafd[1]); - /* child */ - setsid(); - dup2(datafd[0], 0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - #ifdef __FreeBSD__ - const char *h_arg; - h_arg = (crypted) ? "-H" : "-h"; - execl(passwd_path, "pw", "usermod", "-n", username, h_arg, "0", NULL); + g_autofree char *chpasswdata = g_strdup(rawpasswddata); + const char *crypt_flag = crypted ? "-H" : "-h"; + const char *argv[] = {"pw", "usermod", "-n", username, + crypt_flag, "0", NULL}; #else - if (crypted) { - execl(passwd_path, "chpasswd", "-e", NULL); - } else { - execl(passwd_path, "chpasswd", NULL); - } + g_autofree char *chpasswddata = g_strdup_printf("%s:%s\n", username, + rawpasswddata); + const char *crypt_flag = crypted ? "-e" : NULL; + const char *argv[] = {"chpasswd", crypt_flag, NULL}; #endif - _exit(EXIT_FAILURE); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - goto out; - } - close(datafd[0]); - datafd[0] = -1; - - if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) { - error_setg_errno(errp, errno, "cannot write new account password"); - goto out; - } - close(datafd[1]); - datafd[1] = -1; - ga_wait_child(pid, &status, &local_err); + ga_run_command(argv, chpasswddata, "set user password", &local_err); if (local_err) { error_propagate(errp, local_err); - goto out; - } - - if (!WIFEXITED(status)) { - error_setg(errp, "child process has terminated abnormally"); - goto out; - } - - if (WEXITSTATUS(status)) { - error_setg(errp, "child process has failed to set user password"); - goto out; - } - -out: - g_free(chpasswddata); - g_free(rawpasswddata); - g_free(passwd_path); - if (datafd[0] != -1) { - close(datafd[0]); - } - if (datafd[1] != -1) { - close(datafd[1]); + return; } } #else /* __linux__ || __FreeBSD__ */ diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 6242737b00..6fee0e1e6f 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -1143,6 +1143,7 @@ static GuestFilesystemInfo *build_guest_fsinfo(char *guid, Error **errp) fs = g_malloc(sizeof(*fs)); fs->name = g_strdup(guid); fs->has_total_bytes = false; + fs->has_total_bytes_privileged = false; fs->has_used_bytes = false; if (len == 0) { fs->mountpoint = g_strdup("System Reserved"); diff --git a/qga/commands-windows-ssh.c b/qga/commands-windows-ssh.c new file mode 100644 index 0000000000..6a642e3ba8 --- /dev/null +++ b/qga/commands-windows-ssh.c @@ -0,0 +1,712 @@ +/* + * QEMU Guest Agent win32-specific command implementations for SSH keys. + * The implementation is opinionated and expects the SSH implementation to + * be OpenSSH. + * + * Copyright Schweitzer Engineering Laboratories. 2024 + * + * Authors: + * Aidan Leuck <aidan_leuck@selinc.com> + * + * 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 <aclapi.h> +#include <qga-qapi-types.h> + +#include "commands-common-ssh.h" +#include "commands-windows-ssh.h" +#include "guest-agent-core.h" +#include "limits.h" +#include "lmaccess.h" +#include "lmapibuf.h" +#include "lmerr.h" +#include "qapi/error.h" + +#include "qga-qapi-commands.h" +#include "sddl.h" +#include "shlobj.h" +#include "userenv.h" + +#define AUTHORIZED_KEY_FILE "authorized_keys" +#define AUTHORIZED_KEY_FILE_ADMIN "administrators_authorized_keys" +#define LOCAL_SYSTEM_SID "S-1-5-18" +#define ADMIN_SID "S-1-5-32-544" + +/* + * Frees userInfo structure. This implements the g_auto cleanup + * for the structure. + */ +void free_userInfo(PWindowsUserInfo info) +{ + g_free(info->sshDirectory); + g_free(info->authorizedKeyFile); + LocalFree(info->SSID); + g_free(info->username); + g_free(info); +} + +/* + * Gets the admin SSH folder for OpenSSH. OpenSSH does not store + * the authorized_key file in the users home directory for security reasons and + * instead stores it at %PROGRAMDATA%/ssh. This function returns the path to + * that directory on the users machine + * + * parameters: + * errp -> error structure to set when an error occurs + * returns: The path to the ssh folder in %PROGRAMDATA% or NULL if an error + * occurred. + */ +static char *get_admin_ssh_folder(Error **errp) +{ + /* Allocate memory for the program data path */ + g_autofree char *programDataPath = NULL; + char *authkeys_path = NULL; + PWSTR pgDataW = NULL; + g_autoptr(GError) gerr = NULL; + + /* Get the KnownFolderPath on the machine. */ + HRESULT folderResult = + SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &pgDataW); + if (folderResult != S_OK) { + error_setg(errp, "Failed to retrieve ProgramData folder"); + return NULL; + } + + /* Convert from a wide string back to a standard character string. */ + programDataPath = g_utf16_to_utf8(pgDataW, -1, NULL, NULL, &gerr); + CoTaskMemFree(pgDataW); + if (!programDataPath) { + error_setg(errp, + "Failed converting ProgramData folder path to UTF-16 %s", + gerr->message); + return NULL; + } + + /* Build the path to the file. */ + authkeys_path = g_build_filename(programDataPath, "ssh", NULL); + return authkeys_path; +} + +/* + * Gets the path to the SSH folder for the specified user. If the user is an + * admin it returns the ssh folder located at %PROGRAMDATA%/ssh. If the user is + * not an admin it returns %USERPROFILE%/.ssh + * + * parameters: + * username -> Username to get the SSH folder for + * isAdmin -> Whether the user is an admin or not + * errp -> Error structure to set any errors that occur. + * returns: path to the ssh folder as a string. + */ +static char *get_ssh_folder(const char *username, const bool isAdmin, + Error **errp) +{ + DWORD maxSize = MAX_PATH; + g_autofree char *profilesDir = g_new0(char, maxSize); + + if (isAdmin) { + return get_admin_ssh_folder(errp); + } + + /* If not an Admin the SSH key is in the user directory. */ + /* Get the user profile directory on the machine. */ + BOOL ret = GetProfilesDirectory(profilesDir, &maxSize); + if (!ret) { + error_setg_win32(errp, GetLastError(), + "failed to retrieve profiles directory"); + return NULL; + } + + /* Builds the filename */ + return g_build_filename(profilesDir, username, ".ssh", NULL); +} + +/* + * Creates an entry for the user so they can access the ssh folder in their + * userprofile. + * + * parameters: + * userInfo -> Information about the current user + * pACL -> Pointer to an ACL structure + * errp -> Error structure to set any errors that occur + * returns -> 1 on success, 0 otherwise + */ +static bool create_acl_user(PWindowsUserInfo userInfo, PACL *pACL, Error **errp) +{ + const int aclSize = 1; + PACL newACL = NULL; + EXPLICIT_ACCESS eAccess[1]; + PSID userPSID = NULL; + + /* Get a pointer to the internal SID object in Windows */ + bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID); + if (!converted) { + error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID", + userInfo->username); + goto error; + } + + /* Set the permissions for the user. */ + eAccess[0].grfAccessPermissions = GENERIC_ALL; + eAccess[0].grfAccessMode = SET_ACCESS; + eAccess[0].grfInheritance = NO_INHERITANCE; + eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER; + eAccess[0].Trustee.ptstrName = (LPTSTR)userPSID; + + /* Set the ACL entries */ + DWORD setResult; + + /* + * If we are given a pointer that is already initialized, then we can merge + * the existing entries instead of overwriting them. + */ + if (*pACL) { + setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &newACL); + } else { + setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &newACL); + } + + if (setResult != ERROR_SUCCESS) { + error_setg_win32(errp, GetLastError(), + "failed to set ACL entries for user %s %lu", + userInfo->username, setResult); + goto error; + } + + /* Free any old memory since we are going to overwrite the users pointer. */ + LocalFree(*pACL); + *pACL = newACL; + + LocalFree(userPSID); + return true; +error: + LocalFree(userPSID); + return false; +} + +/* + * Creates a base ACL for both normal users and admins to share + * pACL -> Pointer to an ACL structure + * errp -> Error structure to set any errors that occur + * returns: 1 on success, 0 otherwise + */ +static bool create_acl_base(PACL *pACL, Error **errp) +{ + PSID adminGroupPSID = NULL; + PSID systemPSID = NULL; + + const int aclSize = 2; + EXPLICIT_ACCESS eAccess[2]; + + /* Create an entry for the system user. */ + const char *systemSID = LOCAL_SYSTEM_SID; + bool converted = ConvertStringSidToSid(systemSID, &systemPSID); + if (!converted) { + error_setg_win32(errp, GetLastError(), "failed to retrieve system SID"); + goto error; + } + + /* set permissions for system user */ + eAccess[0].grfAccessPermissions = GENERIC_ALL; + eAccess[0].grfAccessMode = SET_ACCESS; + eAccess[0].grfInheritance = NO_INHERITANCE; + eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER; + eAccess[0].Trustee.ptstrName = (LPTSTR)systemPSID; + + /* Create an entry for the admin user. */ + const char *adminSID = ADMIN_SID; + converted = ConvertStringSidToSid(adminSID, &adminGroupPSID); + if (!converted) { + error_setg_win32(errp, GetLastError(), "failed to retrieve Admin SID"); + goto error; + } + + /* Set permissions for admin group. */ + eAccess[1].grfAccessPermissions = GENERIC_ALL; + eAccess[1].grfAccessMode = SET_ACCESS; + eAccess[1].grfInheritance = NO_INHERITANCE; + eAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + eAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; + eAccess[1].Trustee.ptstrName = (LPTSTR)adminGroupPSID; + + /* Put the entries in an ACL object. */ + PACL pNewACL = NULL; + DWORD setResult; + + /* + *If we are given a pointer that is already initialized, then we can merge + *the existing entries instead of overwriting them. + */ + if (*pACL) { + setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &pNewACL); + } else { + setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &pNewACL); + } + + if (setResult != ERROR_SUCCESS) { + error_setg_win32(errp, GetLastError(), + "failed to set base ACL entries for system user and " + "admin group %lu", + setResult); + goto error; + } + + LocalFree(adminGroupPSID); + LocalFree(systemPSID); + + /* Free any old memory since we are going to overwrite the users pointer. */ + LocalFree(*pACL); + + *pACL = pNewACL; + + return true; + +error: + LocalFree(adminGroupPSID); + LocalFree(systemPSID); + return false; +} + +/* + * Sets the access control on the authorized_keys file and any ssh folders that + * need to be created. For administrators the required permissions on the + * file/folders are that only administrators and the LocalSystem account can + * access the folders. For normal user accounts only the specified user, + * LocalSystem and Administrators can have access to the key. + * + * parameters: + * userInfo -> pointer to structure that contains information about the user + * PACL -> pointer to an access control structure that will be set upon + * successful completion of the function. + * errp -> error structure that will be set upon error. + * returns: 1 upon success 0 upon failure. + */ +static bool create_acl(PWindowsUserInfo userInfo, PACL *pACL, Error **errp) +{ + /* + * Creates a base ACL that both admins and users will share + * This adds the Administrators group and the SYSTEM group + */ + if (!create_acl_base(pACL, errp)) { + return false; + } + + /* + * If the user is not an admin give the user creating the key permission to + * access the file. + */ + if (!userInfo->isAdmin) { + if (!create_acl_user(userInfo, pACL, errp)) { + return false; + } + + return true; + } + + return true; +} +/* + * Create the SSH directory for the user and d sets appropriate permissions. + * In general the directory will be %PROGRAMDATA%/ssh if the user is an admin. + * %USERPOFILE%/.ssh if not an admin + * + * parameters: + * userInfo -> Contains information about the user + * errp -> Structure that will contain errors if the function fails. + * returns: zero upon failure, 1 upon success + */ +static bool create_ssh_directory(WindowsUserInfo *userInfo, Error **errp) +{ + PACL pNewACL = NULL; + g_autofree PSECURITY_DESCRIPTOR pSD = NULL; + + /* Gets the appropriate ACL for the user */ + if (!create_acl(userInfo, &pNewACL, errp)) { + goto error; + } + + /* Allocate memory for a security descriptor */ + pSD = g_malloc(SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) { + error_setg_win32(errp, GetLastError(), + "Failed to initialize security descriptor"); + goto error; + } + + /* Associate the security descriptor with the ACL permissions. */ + if (!SetSecurityDescriptorDacl(pSD, TRUE, pNewACL, FALSE)) { + error_setg_win32(errp, GetLastError(), + "Failed to set security descriptor ACL"); + goto error; + } + + /* Set the security attributes on the folder */ + SECURITY_ATTRIBUTES sAttr; + sAttr.bInheritHandle = FALSE; + sAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + sAttr.lpSecurityDescriptor = pSD; + + /* Create the directory with the created permissions */ + BOOL created = CreateDirectory(userInfo->sshDirectory, &sAttr); + if (!created) { + error_setg_win32(errp, GetLastError(), "failed to create directory %s", + userInfo->sshDirectory); + goto error; + } + + /* Free memory */ + LocalFree(pNewACL); + return true; +error: + LocalFree(pNewACL); + return false; +} + +/* + * Sets permissions on the authorized_key_file that is created. + * + * parameters: userInfo -> Information about the user + * errp -> error structure that will contain errors upon failure + * returns: 1 upon success, zero upon failure. + */ +static bool set_file_permissions(PWindowsUserInfo userInfo, Error **errp) +{ + PACL pACL = NULL; + PSID userPSID; + + /* Creates the access control structure */ + if (!create_acl(userInfo, &pACL, errp)) { + goto error; + } + + /* Get the PSID structure for the user based off the string SID. */ + bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID); + if (!converted) { + error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID", + userInfo->username); + goto error; + } + + /* Prevents permissions from being inherited and use the DACL provided. */ + const SE_OBJECT_TYPE securityBitFlags = + DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION; + + /* Set the ACL on the file. */ + if (SetNamedSecurityInfo(userInfo->authorizedKeyFile, SE_FILE_OBJECT, + securityBitFlags, userPSID, NULL, pACL, + NULL) != ERROR_SUCCESS) { + error_setg_win32(errp, GetLastError(), + "failed to set file security for file %s", + userInfo->authorizedKeyFile); + goto error; + } + + LocalFree(pACL); + LocalFree(userPSID); + return true; + +error: + LocalFree(pACL); + LocalFree(userPSID); + + return false; +} + +/* + * Writes the specified keys to the authenticated keys file. + * parameters: + * userInfo: Information about the user we are writing the authkeys file to. + * authkeys: Array of keys to write to disk + * errp: Error structure that will contain any errors if they occur. + * returns: 1 if successful, 0 otherwise. + */ +static bool write_authkeys(WindowsUserInfo *userInfo, GStrv authkeys, + Error **errp) +{ + g_autofree char *contents = NULL; + g_autoptr(GError) err = NULL; + + contents = g_strjoinv("\n", authkeys); + + if (!g_file_set_contents(userInfo->authorizedKeyFile, contents, -1, &err)) { + error_setg(errp, "failed to write to '%s': %s", + userInfo->authorizedKeyFile, err->message); + return false; + } + + if (!set_file_permissions(userInfo, errp)) { + return false; + } + + return true; +} + +/* + * Retrieves information about a Windows user by their username + * + * parameters: + * userInfo -> Double pointer to a WindowsUserInfo structure. Upon success, it + * will be allocated with information about the user and need to be freed. + * username -> Name of the user to lookup. + * errp -> Contains any errors that occur. + * returns: 1 upon success, 0 upon failure. + */ +static bool get_user_info(PWindowsUserInfo *userInfo, const char *username, + Error **errp) +{ + DWORD infoLevel = 4; + LPUSER_INFO_4 uBuf = NULL; + g_autofree wchar_t *wideUserName = NULL; + g_autoptr(GError) gerr = NULL; + PSID psid = NULL; + + /* + * Converts a string to a Windows wide string since the GetNetUserInfo + * function requires it. + */ + wideUserName = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr); + if (!wideUserName) { + goto error; + } + + /* allocate data */ + PWindowsUserInfo uData = g_new0(WindowsUserInfo, 1); + + /* Set pointer so it can be cleaned up by the callee, even upon error. */ + *userInfo = uData; + + /* Find the information */ + NET_API_STATUS result = + NetUserGetInfo(NULL, wideUserName, infoLevel, (LPBYTE *)&uBuf); + if (result != NERR_Success) { + /* Give a friendlier error message if the user was not found. */ + if (result == NERR_UserNotFound) { + error_setg(errp, "User %s was not found", username); + goto error; + } + + error_setg(errp, + "Received unexpected error when asking for user info: Error " + "Code %lu", + result); + goto error; + } + + /* Get information from the buffer returned by NetUserGetInfo. */ + uData->username = g_strdup(username); + uData->isAdmin = uBuf->usri4_priv == USER_PRIV_ADMIN; + psid = uBuf->usri4_user_sid; + + char *sidStr = NULL; + + /* + * We store the string representation of the SID not SID structure in + * memory. Callees wanting to use the SID structure should call + * ConvertStringSidToSID. + */ + if (!ConvertSidToStringSid(psid, &sidStr)) { + error_setg_win32(errp, GetLastError(), + "failed to get SID string for user %s", username); + goto error; + } + + /* Store the SSID */ + uData->SSID = sidStr; + + /* Get the SSH folder for the user. */ + char *sshFolder = get_ssh_folder(username, uData->isAdmin, errp); + if (sshFolder == NULL) { + goto error; + } + + /* Get the authorized key file path */ + const char *authorizedKeyFile = + uData->isAdmin ? AUTHORIZED_KEY_FILE_ADMIN : AUTHORIZED_KEY_FILE; + char *authorizedKeyPath = + g_build_filename(sshFolder, authorizedKeyFile, NULL); + uData->sshDirectory = sshFolder; + uData->authorizedKeyFile = authorizedKeyPath; + + /* Free */ + NetApiBufferFree(uBuf); + return true; +error: + if (uBuf) { + NetApiBufferFree(uBuf); + } + + return false; +} + +/* + * Gets the list of authorized keys for a user. + * + * parameters: + * username -> Username to retrieve the keys for. + * errp -> Error structure that will display any errors through QMP. + * returns: List of keys associated with the user. + */ +GuestAuthorizedKeys *qmp_guest_ssh_get_authorized_keys(const char *username, + Error **errp) +{ + GuestAuthorizedKeys *keys = NULL; + g_auto(GStrv) authKeys = NULL; + g_autoptr(GuestAuthorizedKeys) ret = NULL; + g_auto(PWindowsUserInfo) userInfo = NULL; + + /* Gets user information */ + if (!get_user_info(&userInfo, username, errp)) { + return NULL; + } + + /* Reads authkeys for the user */ + authKeys = read_authkeys(userInfo->authorizedKeyFile, errp); + if (authKeys == NULL) { + return NULL; + } + + /* Set the GuestAuthorizedKey struct with keys from the file */ + ret = g_new0(GuestAuthorizedKeys, 1); + for (int i = 0; authKeys[i] != NULL; i++) { + g_strstrip(authKeys[i]); + if (!authKeys[i][0] || authKeys[i][0] == '#') { + continue; + } + + QAPI_LIST_PREPEND(ret->keys, g_strdup(authKeys[i])); + } + + /* + * Steal the pointer because it is up for the callee to deallocate the + * memory. + */ + keys = g_steal_pointer(&ret); + return keys; +} + +/* + * Adds an ssh key for a user. + * + * parameters: + * username -> User to add the SSH key to + * strList -> Array of keys to add to the list + * has_reset -> Whether the keys have been reset + * reset -> Boolean to reset the keys (If this is set the existing list will be + * cleared) and the other key reset. errp -> Pointer to an error structure that + * will get returned over QMP if anything goes wrong. + */ +void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys, + bool has_reset, bool reset, Error **errp) +{ + g_auto(PWindowsUserInfo) userInfo = NULL; + g_auto(GStrv) authkeys = NULL; + strList *k; + size_t nkeys, nauthkeys; + + /* Make sure the keys given are valid */ + if (!check_openssh_pub_keys(keys, &nkeys, errp)) { + return; + } + + /* Gets user information */ + if (!get_user_info(&userInfo, username, errp)) { + return; + } + + /* Determine whether we should reset the keys */ + reset = has_reset && reset; + if (!reset) { + /* Read existing keys into memory */ + authkeys = read_authkeys(userInfo->authorizedKeyFile, NULL); + } + + /* Check that the SSH key directory exists for the user. */ + if (!g_file_test(userInfo->sshDirectory, G_FILE_TEST_IS_DIR)) { + BOOL success = create_ssh_directory(userInfo, errp); + if (!success) { + return; + } + } + + /* Reallocates the buffer to fit the new keys. */ + nauthkeys = authkeys ? g_strv_length(authkeys) : 0; + authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *)); + + /* zero out the memory for the reallocated buffer */ + memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *)); + + /* Adds the keys */ + for (k = keys; k != NULL; k = k->next) { + /* Check that the key doesn't already exist */ + if (g_strv_contains((const gchar *const *)authkeys, k->value)) { + continue; + } + + authkeys[nauthkeys++] = g_strdup(k->value); + } + + /* Write the authkeys to the file. */ + write_authkeys(userInfo, authkeys, errp); +} + +/* + * Removes an SSH key for a user + * + * parameters: + * username -> Username to remove the key from + * strList -> List of strings to remove + * errp -> Contains any errors that occur. + */ +void qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys, + Error **errp) +{ + g_auto(PWindowsUserInfo) userInfo = NULL; + g_autofree struct passwd *p = NULL; + g_autofree GStrv new_keys = NULL; /* do not own the strings */ + g_auto(GStrv) authkeys = NULL; + GStrv a; + size_t nkeys = 0; + + /* Validates the keys passed in by the user */ + if (!check_openssh_pub_keys(keys, NULL, errp)) { + return; + } + + /* Gets user information */ + if (!get_user_info(&userInfo, username, errp)) { + return; + } + + /* Reads the authkeys for the user */ + authkeys = read_authkeys(userInfo->authorizedKeyFile, errp); + if (authkeys == NULL) { + return; + } + + /* Create a new buffer to hold the keys */ + new_keys = g_new0(char *, g_strv_length(authkeys) + 1); + for (a = authkeys; *a != NULL; a++) { + strList *k; + + /* Filters out keys that are equal to ones the user specified. */ + for (k = keys; k != NULL; k = k->next) { + if (g_str_equal(k->value, *a)) { + break; + } + } + + if (k != NULL) { + continue; + } + + new_keys[nkeys++] = *a; + } + + /* Write the new authkeys to the file. */ + write_authkeys(userInfo, new_keys, errp); +} diff --git a/qga/commands-windows-ssh.h b/qga/commands-windows-ssh.h new file mode 100644 index 0000000000..40ac67c4d9 --- /dev/null +++ b/qga/commands-windows-ssh.h @@ -0,0 +1,26 @@ +/* + * Header file for commands-windows-ssh.c + * + * Copyright Schweitzer Engineering Laboratories. 2024 + * + * Authors: + * Aidan Leuck <aidan_leuck@selinc.com> + * + * 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 <glib/gstrfuncs.h> +#include <stdbool.h> +typedef struct WindowsUserInfo { + char *sshDirectory; + char *authorizedKeyFile; + char *username; + char *SSID; + bool isAdmin; +} WindowsUserInfo; + +typedef WindowsUserInfo *PWindowsUserInfo; + +void free_userInfo(PWindowsUserInfo info); +G_DEFINE_AUTO_CLEANUP_FREE_FUNC(PWindowsUserInfo, free_userInfo, NULL); diff --git a/qga/meson.build b/qga/meson.build index 1c3d2a3d1b..587ec4e5e8 100644 --- a/qga/meson.build +++ b/qga/meson.build @@ -66,13 +66,15 @@ qga_ss.add(files( 'guest-agent-command-state.c', 'main.c', 'cutils.c', + 'commands-common-ssh.c' )) if host_os == 'windows' qga_ss.add(files( 'channel-win32.c', 'commands-win32.c', 'service-win32.c', - 'vss-win32.c' + 'vss-win32.c', + 'commands-windows-ssh.c' )) else qga_ss.add(files( @@ -93,7 +95,7 @@ gen_tlb = [] qga_libs = [] if host_os == 'windows' qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwininet', '-liphlpapi', '-lnetapi32', - '-lsetupapi', '-lcfgmgr32'] + '-lsetupapi', '-lcfgmgr32', '-luserenv'] if have_qga_vss qga_libs += ['-lole32', '-loleaut32', '-lshlwapi', '-lstdc++', '-Wl,--enable-stdcall-fixup'] subdir('vss-win32') @@ -181,13 +183,12 @@ test_env = environment() test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) -# disable qga-ssh-test for now. glib's G_TEST_OPTION_ISOLATE_DIRS triggers +# disable qga-ssh-test with fuzzing: glib's G_TEST_OPTION_ISOLATE_DIRS triggers # the leak detector in build-oss-fuzz Gitlab CI test. we should re-enable # this when an alternative is implemented or when the underlying glib # issue is identified/fix -#if host_os != 'windows' -if false - srcs = [files('commands-posix-ssh.c')] +if host_os != 'windows' and not get_option('fuzzing') + srcs = [files('commands-common-ssh.c', 'commands-posix-ssh.c')] i = 0 foreach output: qga_qapi_outputs if output.startswith('qga-qapi-types') or output.startswith('qga-qapi-visit') diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index d5af155007..b3de1fb6b3 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -1026,7 +1026,10 @@ # # @used-bytes: file system used bytes (since 3.0) # -# @total-bytes: non-root file system total bytes (since 3.0) +# @total-bytes: filesystem capacity in bytes for unprivileged users (since 3.0) +# +# @total-bytes-privileged: filesystem capacity in bytes for privileged users +# (since 9.1) # # @disk: an array of disk hardware information that the volume lies # on, which may be empty if the disk type is not supported @@ -1036,7 +1039,7 @@ { 'struct': 'GuestFilesystemInfo', 'data': {'name': 'str', 'mountpoint': 'str', 'type': 'str', '*used-bytes': 'uint64', '*total-bytes': 'uint64', - 'disk': ['GuestDiskAddress']} } + '*total-bytes-privileged': 'uint64', 'disk': ['GuestDiskAddress']} } ## # @guest-get-fsinfo: @@ -1567,9 +1570,8 @@ { 'struct': 'GuestAuthorizedKeys', 'data': { 'keys': ['str'] - }, - 'if': 'CONFIG_POSIX' } - + } +} ## # @guest-ssh-get-authorized-keys: @@ -1585,8 +1587,8 @@ ## { 'command': 'guest-ssh-get-authorized-keys', 'data': { 'username': 'str' }, - 'returns': 'GuestAuthorizedKeys', - 'if': 'CONFIG_POSIX' } + 'returns': 'GuestAuthorizedKeys' +} ## # @guest-ssh-add-authorized-keys: @@ -1604,8 +1606,8 @@ # Since: 5.2 ## { 'command': 'guest-ssh-add-authorized-keys', - 'data': { 'username': 'str', 'keys': ['str'], '*reset': 'bool' }, - 'if': 'CONFIG_POSIX' } + 'data': { 'username': 'str', 'keys': ['str'], '*reset': 'bool' } +} ## # @guest-ssh-remove-authorized-keys: @@ -1622,8 +1624,8 @@ # Since: 5.2 ## { 'command': 'guest-ssh-remove-authorized-keys', - 'data': { 'username': 'str', 'keys': ['str'] }, - 'if': 'CONFIG_POSIX' } + 'data': { 'username': 'str', 'keys': ['str'] } +} ## # @GuestDiskStats: diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 7026895074..ff373a7083 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -435,8 +435,8 @@ if ($chk_branch) { my @patches; my %git_commits = (); my $HASH; - open($HASH, "-|", "git", "log", "--reverse", "--no-merges", "--format=%H %s", $ARGV[0]) || - die "$P: git log --reverse --no-merges --format='%H %s' $ARGV[0] failed - $!\n"; + open($HASH, "-|", "git", "log", "--reverse", "--no-merges", "--no-mailmap", "--format=%H %s", $ARGV[0]) || + die "$P: git log --reverse --no-merges --no-mailmap --format='%H %s' $ARGV[0] failed - $!\n"; for my $line (<$HASH>) { $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; @@ -460,7 +460,7 @@ if ($chk_branch) { "-c", "diff.renamelimit=0", "-c", "diff.renames=True", "-c", "diff.algorithm=histogram", - "show", + "show", "--no-mailmap", "--patch-with-stat", $hash) || die "$P: git show $hash - $!\n"; while (<$FILE>) { @@ -1573,7 +1573,7 @@ sub process { $is_patch = 1; } - if ($line =~ /^(Author|From): .* via .*<qemu-devel\@nongnu.org>/) { + if ($line =~ /^(Author|From): .* via .*<qemu-\w+\@nongnu\.org>/) { ERROR("Author email address is mangled by the mailing list\n" . $herecurr); } @@ -3078,6 +3078,9 @@ sub process { if ($line =~ /\b(g_)?assert\(0\)/) { ERROR("use g_assert_not_reached() instead of assert(0)\n" . $herecurr); } + if ($line =~ /\bstrerrorname_np\(/) { + ERROR("use strerror() instead of strerrorname_np()\n" . $herecurr); + } my $non_exit_glib_asserts = qr{g_assert_cmpstr| g_assert_cmpint| g_assert_cmpuint| diff --git a/scripts/ci/setup/build-environment.yml b/scripts/ci/setup/build-environment.yml index f344d1a850..de0d866a1e 100644 --- a/scripts/ci/setup/build-environment.yml +++ b/scripts/ci/setup/build-environment.yml @@ -95,7 +95,6 @@ - libpam0g-dev - libpcre2-dev - libpixman-1-dev - - libpmem-dev - libpng-dev - libpulse-dev - librbd-dev @@ -107,7 +106,6 @@ - libslirp-dev - libsnappy-dev - libspice-protocol-dev - - libspice-server-dev - libssh-dev - libsystemd-dev - libtasn1-6-dev @@ -119,7 +117,6 @@ - libvdeplug-dev - libvirglrenderer-dev - libvte-2.91-dev - - libxen-dev - libxml2-dev - libzstd-dev - llvm @@ -156,6 +153,19 @@ - ansible_facts['distribution'] == 'Ubuntu' - ansible_facts['distribution_version'] == '22.04' + # not all packages are available for all architectures + - name: Install additional packages to build QEMU on Ubuntu 22.04 + package: + name: + - libpmem-dev + - libspice-server-dev + - libxen-dev + state: present + when: + - ansible_facts['distribution'] == 'Ubuntu' + - ansible_facts['distribution_version'] == '22.04' + - ansible_facts['architecture'] == 'aarch64' or ansible_facts['architecture'] == 'x86_64' + - name: Install armhf cross-compile packages to build QEMU on AArch64 Ubuntu 22.04 package: name: diff --git a/stubs/meson.build b/stubs/meson.build index 8ee1fd5753..3b9d42023c 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -21,12 +21,12 @@ if have_block stub_ss.add(files('migr-blocker.c')) stub_ss.add(files('physmem.c')) stub_ss.add(files('ram-block.c')) - stub_ss.add(files('replay-tools.c')) stub_ss.add(files('runstate-check.c')) stub_ss.add(files('uuid.c')) endif if have_block or have_ga + stub_ss.add(files('replay-tools.c')) # stubs for hooks in util/main-loop.c, util/async.c etc. stub_ss.add(files('cpus-get-virtual-clock.c')) stub_ss.add(files('icount.c')) @@ -45,6 +45,10 @@ if have_block or have_ga stub_ss.add(files('qmp-quit.c')) endif +if have_ga + stub_ss.add(files('error-printf.c')) +endif + if have_block or have_user stub_ss.add(files('qtest.c')) stub_ss.add(files('vm-stop.c')) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index a152def241..fdc3eda318 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -1506,9 +1506,12 @@ static void arm_cpu_initfn(Object *obj) } } +/* + * 0 means "unset, use the default value". That default might vary depending + * on the CPU type, and is set in the realize fn. + */ static Property arm_cpu_gt_cntfrq_property = - DEFINE_PROP_UINT64("cntfrq", ARMCPU, gt_cntfrq_hz, - NANOSECONDS_PER_SECOND / GTIMER_SCALE); + DEFINE_PROP_UINT64("cntfrq", ARMCPU, gt_cntfrq_hz, 0); static Property arm_cpu_reset_cbar_property = DEFINE_PROP_UINT64("reset-cbar", ARMCPU, reset_cbar, 0); @@ -1954,6 +1957,26 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) return; } + if (!cpu->gt_cntfrq_hz) { + /* + * 0 means "the board didn't set a value, use the default". (We also + * get here for the CONFIG_USER_ONLY case.) + * ARMv8.6 and later CPUs architecturally must use a 1GHz timer; before + * that it was an IMPDEF choice, and QEMU initially picked 62.5MHz, + * which gives a 16ns tick period. + * + * We will use the back-compat value: + * - for QEMU CPU types added before we standardized on 1GHz + * - for versioned machine types with a version of 9.0 or earlier + */ + if (arm_feature(env, ARM_FEATURE_BACKCOMPAT_CNTFRQ) || + cpu->backcompat_cntfrq) { + cpu->gt_cntfrq_hz = GTIMER_BACKCOMPAT_HZ; + } else { + cpu->gt_cntfrq_hz = GTIMER_DEFAULT_HZ; + } + } + #ifndef CONFIG_USER_ONLY /* The NVIC and M-profile CPU are two halves of a single piece of * hardware; trying to use one without the other is a command line @@ -2002,18 +2025,7 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) } { - uint64_t scale; - - if (arm_feature(env, ARM_FEATURE_GENERIC_TIMER)) { - if (!cpu->gt_cntfrq_hz) { - error_setg(errp, "Invalid CNTFRQ: %"PRId64"Hz", - cpu->gt_cntfrq_hz); - return; - } - scale = gt_cntfrq_period_ns(cpu); - } else { - scale = GTIMER_SCALE; - } + uint64_t scale = gt_cntfrq_period_ns(cpu); cpu->gt_timer[GTIMER_PHYS] = timer_new(QEMU_CLOCK_VIRTUAL, scale, arm_gt_ptimer_cb, cpu); @@ -2571,6 +2583,8 @@ static Property arm_cpu_properties[] = { mp_affinity, ARM64_AFFINITY_INVALID), DEFINE_PROP_INT32("node-id", ARMCPU, node_id, CPU_UNSET_NUMA_NODE_ID), DEFINE_PROP_INT32("core-count", ARMCPU, core_count, -1), + /* True to default to the backward-compat old CNTFRQ rather than 1Ghz */ + DEFINE_PROP_BOOL("backcompat-cntfrq", ARMCPU, backcompat_cntfrq, false), DEFINE_PROP_END_OF_LIST() }; diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 17efc5d565..a550bcd25f 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -956,6 +956,9 @@ struct ArchCPU { */ bool host_cpu_probe_failed; + /* QOM property to indicate we should use the back-compat CNTFRQ default */ + bool backcompat_cntfrq; + /* Specify the number of cores in this CPU cluster. Used for the L2CTLR * register. */ @@ -1011,6 +1014,7 @@ struct ArchCPU { uint64_t id_aa64mmfr0; uint64_t id_aa64mmfr1; uint64_t id_aa64mmfr2; + uint64_t id_aa64mmfr3; uint64_t id_aa64dfr0; uint64_t id_aa64dfr1; uint64_t id_aa64zfr0; @@ -2206,6 +2210,22 @@ FIELD(ID_AA64MMFR2, BBM, 52, 4) FIELD(ID_AA64MMFR2, EVT, 56, 4) FIELD(ID_AA64MMFR2, E0PD, 60, 4) +FIELD(ID_AA64MMFR3, TCRX, 0, 4) +FIELD(ID_AA64MMFR3, SCTLRX, 4, 4) +FIELD(ID_AA64MMFR3, S1PIE, 8, 4) +FIELD(ID_AA64MMFR3, S2PIE, 12, 4) +FIELD(ID_AA64MMFR3, S1POE, 16, 4) +FIELD(ID_AA64MMFR3, S2POE, 20, 4) +FIELD(ID_AA64MMFR3, AIE, 24, 4) +FIELD(ID_AA64MMFR3, MEC, 28, 4) +FIELD(ID_AA64MMFR3, D128, 32, 4) +FIELD(ID_AA64MMFR3, D128_2, 36, 4) +FIELD(ID_AA64MMFR3, SNERR, 40, 4) +FIELD(ID_AA64MMFR3, ANERR, 44, 4) +FIELD(ID_AA64MMFR3, SDERR, 52, 4) +FIELD(ID_AA64MMFR3, ADERR, 56, 4) +FIELD(ID_AA64MMFR3, SPEC_FPACC, 60, 4) + FIELD(ID_AA64DFR0, DEBUGVER, 0, 4) FIELD(ID_AA64DFR0, TRACEVER, 4, 4) FIELD(ID_AA64DFR0, PMUVER, 8, 4) @@ -2356,6 +2376,14 @@ enum arm_features { ARM_FEATURE_M_SECURITY, /* M profile Security Extension */ ARM_FEATURE_M_MAIN, /* M profile Main Extension */ ARM_FEATURE_V8_1M, /* M profile extras only in v8.1M and later */ + /* + * ARM_FEATURE_BACKCOMPAT_CNTFRQ makes the CPU default cntfrq be 62.5MHz + * if the board doesn't set a value, instead of 1GHz. It is for backwards + * compatibility and used only with CPU definitions that were already + * in QEMU before we changed the default. It should not be set on any + * CPU types added in future. + */ + ARM_FEATURE_BACKCOMPAT_CNTFRQ, /* 62.5MHz timer default */ }; static inline int arm_feature(CPUARMState *env, int feature) diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index 985b1efe16..c15d086049 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -599,6 +599,7 @@ static void aarch64_a57_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -656,6 +657,7 @@ static void aarch64_a53_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); diff --git a/target/arm/helper.c b/target/arm/helper.c index 6b224826fb..7587635960 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -2474,6 +2474,13 @@ static const ARMCPRegInfo v6k_cp_reginfo[] = { .resetvalue = 0 }, }; +static void arm_gt_cntfrq_reset(CPUARMState *env, const ARMCPRegInfo *opaque) +{ + ARMCPU *cpu = env_archcpu(env); + + cpu->env.cp15.c14_cntfrq = cpu->gt_cntfrq_hz; +} + #ifndef CONFIG_USER_ONLY static CPAccessResult gt_cntfrq_access(CPUARMState *env, const ARMCPRegInfo *ri, @@ -3228,13 +3235,6 @@ void arm_gt_hvtimer_cb(void *opaque) gt_recalc_timer(cpu, GTIMER_HYPVIRT); } -static void arm_gt_cntfrq_reset(CPUARMState *env, const ARMCPRegInfo *opaque) -{ - ARMCPU *cpu = env_archcpu(env); - - cpu->env.cp15.c14_cntfrq = cpu->gt_cntfrq_hz; -} - static const ARMCPRegInfo generic_timer_cp_reginfo[] = { /* * Note that CNTFRQ is purely reads-as-written for the benefit @@ -3514,7 +3514,7 @@ static const ARMCPRegInfo generic_timer_cp_reginfo[] = { .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 0, .opc2 = 0, .type = ARM_CP_CONST, .access = PL0_R /* no PL1_RW in linux-user */, .fieldoffset = offsetof(CPUARMState, cp15.c14_cntfrq), - .resetvalue = NANOSECONDS_PER_SECOND / GTIMER_SCALE, + .resetfn = arm_gt_cntfrq_reset, }, { .name = "CNTVCT_EL0", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 0, .opc2 = 2, @@ -9004,11 +9004,11 @@ void register_cp_regs_for_features(ARMCPU *cpu) .access = PL1_R, .type = ARM_CP_CONST, .accessfn = access_aa64_tid3, .resetvalue = cpu->isar.id_aa64mmfr2 }, - { .name = "ID_AA64MMFR3_EL1_RESERVED", .state = ARM_CP_STATE_AA64, + { .name = "ID_AA64MMFR3_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 0, .crn = 0, .crm = 7, .opc2 = 3, .access = PL1_R, .type = ARM_CP_CONST, .accessfn = access_aa64_tid3, - .resetvalue = 0 }, + .resetvalue = cpu->isar.id_aa64mmfr3 }, { .name = "ID_AA64MMFR4_EL1_RESERVED", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 0, .crn = 0, .crm = 7, .opc2 = 4, .access = PL1_R, .type = ARM_CP_CONST, @@ -9165,6 +9165,8 @@ void register_cp_regs_for_features(ARMCPU *cpu) .exported_bits = R_ID_AA64MMFR1_AFP_MASK }, { .name = "ID_AA64MMFR2_EL1", .exported_bits = R_ID_AA64MMFR2_AT_MASK }, + { .name = "ID_AA64MMFR3_EL1", + .exported_bits = 0 }, { .name = "ID_AA64MMFR*_EL1_RESERVED", .is_glob = true }, { .name = "ID_AA64DFR0_EL1", diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index db628c1cba..08d0757438 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -150,7 +150,6 @@ void hvf_arm_init_debug(void) #define HVF_SYSREG(crn, crm, op0, op1, op2) \ ENCODE_AA64_CP_REG(CP_REG_ARM64_SYSREG_CP, crn, crm, op0, op1, op2) -#define PL1_WRITE_MASK 0x4 #define SYSREG_OP0_SHIFT 20 #define SYSREG_OP0_MASK 0x3 @@ -498,6 +497,7 @@ static struct hvf_sreg_match hvf_sreg_match[] = { #endif { HV_SYS_REG_ID_AA64MMFR1_EL1, HVF_SYSREG(0, 7, 3, 0, 1) }, { HV_SYS_REG_ID_AA64MMFR2_EL1, HVF_SYSREG(0, 7, 3, 0, 2) }, + /* Add ID_AA64MMFR3_EL1 here when HVF supports it */ { HV_SYS_REG_MDSCR_EL1, HVF_SYSREG(0, 2, 2, 0, 2) }, { HV_SYS_REG_SCTLR_EL1, HVF_SYSREG(1, 0, 3, 0, 0) }, @@ -856,6 +856,7 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) { HV_SYS_REG_ID_AA64MMFR0_EL1, &host_isar.id_aa64mmfr0 }, { HV_SYS_REG_ID_AA64MMFR1_EL1, &host_isar.id_aa64mmfr1 }, { HV_SYS_REG_ID_AA64MMFR2_EL1, &host_isar.id_aa64mmfr2 }, + /* Add ID_AA64MMFR3_EL1 here when HVF supports it */ }; hv_vcpu_t fd; hv_return_t r = HV_SUCCESS; diff --git a/target/arm/internals.h b/target/arm/internals.h index e40ec453d5..ee3ebd383e 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -60,10 +60,19 @@ static inline bool excp_is_internal(int excp) || excp == EXCP_SEMIHOST; } -/* Scale factor for generic timers, ie number of ns per tick. - * This gives a 62.5MHz timer. +/* + * Default frequency for the generic timer, in Hz. + * ARMv8.6 and later CPUs architecturally must use a 1GHz timer; before + * that it was an IMPDEF choice, and QEMU initially picked 62.5MHz, + * which gives a 16ns tick period. + * + * We will use the back-compat value: + * - for QEMU CPU types added before we standardized on 1GHz + * - for versioned machine types with a version of 9.0 or earlier + * In any case, the machine model may override via the cntfrq property. */ -#define GTIMER_SCALE 16 +#define GTIMER_DEFAULT_HZ 1000000000 +#define GTIMER_BACKCOMPAT_HZ 62500000 /* Bit definitions for the v7M CONTROL register */ FIELD(V7M_CONTROL, NPRIV, 0, 1) diff --git a/target/arm/kvm.c b/target/arm/kvm.c index 21ebbf3b8f..7cf5cf31de 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -331,6 +331,8 @@ static bool kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) ARM64_SYS_REG(3, 0, 0, 7, 1)); err |= read_sys_reg64(fdarray[2], &ahcf->isar.id_aa64mmfr2, ARM64_SYS_REG(3, 0, 0, 7, 2)); + err |= read_sys_reg64(fdarray[2], &ahcf->isar.id_aa64mmfr3, + ARM64_SYS_REG(3, 0, 0, 7, 3)); /* * Note that if AArch32 support is not present in the host, diff --git a/target/arm/tcg/cpu32.c b/target/arm/tcg/cpu32.c index de8f2be941..bdd82d912a 100644 --- a/target/arm/tcg/cpu32.c +++ b/target/arm/tcg/cpu32.c @@ -67,7 +67,7 @@ void aa32_max_features(ARMCPU *cpu) cpu->isar.id_mmfr4 = t; t = cpu->isar.id_mmfr5; - t = FIELD_DP32(t, ID_MMFR5, ETS, 1); /* FEAT_ETS */ + t = FIELD_DP32(t, ID_MMFR5, ETS, 2); /* FEAT_ETS2 */ cpu->isar.id_mmfr5 = t; t = cpu->isar.id_pfr0; @@ -457,6 +457,7 @@ static void cortex_a7_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_THUMB2EE); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -505,6 +506,7 @@ static void cortex_a15_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_THUMB2EE); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -696,6 +698,7 @@ static void cortex_r52_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_PMSA); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_AUXCR); cpu->midr = 0x411fd133; /* r1p3 */ @@ -924,6 +927,7 @@ static void arm_max_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); set_feature(&cpu->env, ARM_FEATURE_EL3); diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c index 62c4663512..da41a44f75 100644 --- a/target/arm/tcg/cpu64.c +++ b/target/arm/tcg/cpu64.c @@ -63,6 +63,7 @@ static void aarch64_a35_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -231,6 +232,7 @@ static void aarch64_a55_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -299,6 +301,7 @@ static void aarch64_a72_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -354,6 +357,7 @@ static void aarch64_a76_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -423,6 +427,7 @@ static void aarch64_a64fx_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_EL2); set_feature(&cpu->env, ARM_FEATURE_EL3); @@ -592,6 +597,7 @@ static void aarch64_neoverse_n1_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -663,6 +669,7 @@ static void aarch64_neoverse_v1_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -885,6 +892,7 @@ static void aarch64_a710_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -982,6 +990,7 @@ static void aarch64_neoverse_n2_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8); set_feature(&cpu->env, ARM_FEATURE_NEON); set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER); + set_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); set_feature(&cpu->env, ARM_FEATURE_AARCH64); set_feature(&cpu->env, ARM_FEATURE_CBAR_RO); set_feature(&cpu->env, ARM_FEATURE_EL2); @@ -1078,6 +1087,15 @@ void aarch64_max_tcg_initfn(Object *obj) uint32_t u; /* + * Unset ARM_FEATURE_BACKCOMPAT_CNTFRQ, which we would otherwise default + * to because we started with aarch64_a57_initfn(). A 'max' CPU might + * be a v8.6-or-later one, in which case the cntfrq must be 1GHz; and + * because it is our "may change" CPU type we are OK with it not being + * backwards-compatible with how it worked in old QEMU. + */ + unset_feature(&cpu->env, ARM_FEATURE_BACKCOMPAT_CNTFRQ); + + /* * Reset MIDR so the guest doesn't mistake our 'max' CPU type for a real * one and try to apply errata workarounds or use impdef features we * don't provide. @@ -1159,7 +1177,7 @@ void aarch64_max_tcg_initfn(Object *obj) t = FIELD_DP64(t, ID_AA64PFR0, SVE, 1); t = FIELD_DP64(t, ID_AA64PFR0, SEL2, 1); /* FEAT_SEL2 */ t = FIELD_DP64(t, ID_AA64PFR0, DIT, 1); /* FEAT_DIT */ - t = FIELD_DP64(t, ID_AA64PFR0, CSV2, 2); /* FEAT_CSV2_2 */ + t = FIELD_DP64(t, ID_AA64PFR0, CSV2, 3); /* FEAT_CSV2_3 */ t = FIELD_DP64(t, ID_AA64PFR0, CSV3, 1); /* FEAT_CSV3 */ cpu->isar.id_aa64pfr0 = t; @@ -1174,7 +1192,7 @@ void aarch64_max_tcg_initfn(Object *obj) t = FIELD_DP64(t, ID_AA64PFR1, MTE, 3); /* FEAT_MTE3 */ t = FIELD_DP64(t, ID_AA64PFR1, RAS_FRAC, 0); /* FEAT_RASv1p1 + FEAT_DoubleFault */ t = FIELD_DP64(t, ID_AA64PFR1, SME, 1); /* FEAT_SME */ - t = FIELD_DP64(t, ID_AA64PFR1, CSV2_FRAC, 0); /* FEAT_CSV2_2 */ + t = FIELD_DP64(t, ID_AA64PFR1, CSV2_FRAC, 0); /* FEAT_CSV2_3 */ t = FIELD_DP64(t, ID_AA64PFR1, NMI, 1); /* FEAT_NMI */ cpu->isar.id_aa64pfr1 = t; @@ -1196,7 +1214,7 @@ void aarch64_max_tcg_initfn(Object *obj) t = FIELD_DP64(t, ID_AA64MMFR1, LO, 1); /* FEAT_LOR */ t = FIELD_DP64(t, ID_AA64MMFR1, PAN, 3); /* FEAT_PAN3 */ t = FIELD_DP64(t, ID_AA64MMFR1, XNX, 1); /* FEAT_XNX */ - t = FIELD_DP64(t, ID_AA64MMFR1, ETS, 1); /* FEAT_ETS */ + t = FIELD_DP64(t, ID_AA64MMFR1, ETS, 2); /* FEAT_ETS2 */ t = FIELD_DP64(t, ID_AA64MMFR1, HCX, 1); /* FEAT_HCX */ t = FIELD_DP64(t, ID_AA64MMFR1, TIDCP1, 1); /* FEAT_TIDCP1 */ cpu->isar.id_aa64mmfr1 = t; @@ -1217,6 +1235,10 @@ void aarch64_max_tcg_initfn(Object *obj) t = FIELD_DP64(t, ID_AA64MMFR2, E0PD, 1); /* FEAT_E0PD */ cpu->isar.id_aa64mmfr2 = t; + t = cpu->isar.id_aa64mmfr3; + t = FIELD_DP64(t, ID_AA64MMFR3, SPEC_FPACC, 1); /* FEAT_FPACC_SPEC */ + cpu->isar.id_aa64mmfr3 = t; + t = cpu->isar.id_aa64zfr0; t = FIELD_DP64(t, ID_AA64ZFR0, SVEVER, 1); t = FIELD_DP64(t, ID_AA64ZFR0, AES, 2); /* FEAT_SVE_PMULL128 */ diff --git a/target/arm/tcg/hflags.c b/target/arm/tcg/hflags.c index 5da1b0fc1d..f03977b4b0 100644 --- a/target/arm/tcg/hflags.c +++ b/target/arm/tcg/hflags.c @@ -38,8 +38,16 @@ static bool aprofile_require_alignment(CPUARMState *env, int el, uint64_t sctlr) } /* - * If translation is disabled, then the default memory type is - * Device(-nGnRnE) instead of Normal, which requires that alignment + * With PMSA, when the MPU is disabled, all memory types in the + * default map are Normal, so don't need aligment enforcing. + */ + if (arm_feature(env, ARM_FEATURE_PMSA)) { + return false; + } + + /* + * With VMSA, if translation is disabled, then the default memory type + * is Device(-nGnRnE) instead of Normal, which requires that alignment * be enforced. Since this affects all ram, it is most efficient * to handle this during translation. */ diff --git a/target/i386/cpu.c b/target/i386/cpu.c index fa1ea3735d..aa3b2d8391 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -5708,7 +5708,7 @@ static void x86_cpu_list_entry(gpointer data, gpointer user_data) desc = g_strdup_printf("%s (deprecated)", olddesc); } - qemu_printf("x86 %-20s %s\n", name, desc); + qemu_printf(" %-20s %s\n", name, desc); } /* list available CPU models and flags */ diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index bac84dca7a..1ebba043f4 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -92,7 +92,7 @@ void G_NORETURN do_raise_exception(CPULoongArchState *env, { CPUState *cs = env_cpu(env); - qemu_log_mask(CPU_LOG_INT, "%s: expection: %d (%s)\n", + qemu_log_mask(CPU_LOG_INT, "%s: exception: %d (%s)\n", __func__, exception, loongarch_exception_name(exception)); diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index abb01b2cc7..c5722670f5 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -359,6 +359,8 @@ typedef struct CPUArchState { uint32_t mp_state; /* Store ipistate to access from this struct */ DeviceState *ipistate; + + struct loongarch_boot_info *boot_info; #endif } CPULoongArchState; diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index 6d82f24c87..c11a69fd90 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -7063,7 +7063,7 @@ static void ppc_cpu_list_entry(gpointer data, gpointer user_data) } name = cpu_model_from_type(typename); - qemu_printf("PowerPC %-16s PVR %08x\n", name, pcc->pvr); + qemu_printf(" %-16s PVR %08x\n", name, pcc->pvr); for (i = 0; ppc_cpu_aliases[i].alias != NULL; i++) { PowerPCCPUAlias *alias = &ppc_cpu_aliases[i]; ObjectClass *alias_oc = ppc_cpu_class_by_name(alias->model); @@ -7076,10 +7076,10 @@ static void ppc_cpu_list_entry(gpointer data, gpointer user_data) * avoid printing the wrong alias here and use "preferred" instead */ if (strcmp(alias->alias, family->desc) == 0) { - qemu_printf("PowerPC %-16s (alias for preferred %s CPU)\n", + qemu_printf(" %-16s (alias for preferred %s CPU)\n", alias->alias, family->desc); } else { - qemu_printf("PowerPC %-16s (alias for %s)\n", + qemu_printf(" %-16s (alias for %s)\n", alias->alias, name); } } @@ -7090,6 +7090,7 @@ void ppc_cpu_list(void) { GSList *list; + qemu_printf("Available CPUs:\n"); list = object_class_get_list(TYPE_POWERPC_CPU, false); list = g_slist_sort(list, ppc_cpu_list_compare); g_slist_foreach(list, ppc_cpu_list_entry, NULL); @@ -7097,7 +7098,7 @@ void ppc_cpu_list(void) #ifdef CONFIG_KVM qemu_printf("\n"); - qemu_printf("PowerPC %s\n", "host"); + qemu_printf(" %s\n", "host"); #endif } diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index 49d2f3ad58..eaa36121c7 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -1054,8 +1054,8 @@ static void kvm_riscv_read_vlenb(RISCVCPU *cpu, KVMScratchCPU *kvmcpu, ret = ioctl(kvmcpu->cpufd, KVM_GET_ONE_REG, ®); if (ret != 0) { - error_report("Unable to read vlenb register, error code: %s", - strerrorname_np(errno)); + error_report("Unable to read vlenb register, error code: %d", + errno); exit(EXIT_FAILURE); } diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c index 8ed3bb6a27..efb508cd2e 100644 --- a/target/s390x/cpu_models.c +++ b/target/s390x/cpu_models.c @@ -355,9 +355,9 @@ static void s390_print_cpu_model_list_entry(gpointer data, gpointer user_data) /* strip off the -s390x-cpu */ g_strrstr(name, "-" TYPE_S390_CPU)[0] = 0; if (details->len) { - qemu_printf("s390 %-15s %-35s (%s)\n", name, scc->desc, details->str); + qemu_printf(" %-15s %-35s (%s)\n", name, scc->desc, details->str); } else { - qemu_printf("s390 %-15s %-35s\n", name, scc->desc); + qemu_printf(" %-15s %-35s\n", name, scc->desc); } g_free(name); } @@ -402,6 +402,7 @@ void s390_cpu_list(void) S390Feat feat; GSList *list; + qemu_printf("Available CPUs:\n"); list = object_class_get_list(TYPE_S390_CPU, false); list = g_slist_sort(list, s390_cpu_list_compare); g_slist_foreach(list, s390_print_cpu_model_list_entry, NULL); @@ -411,14 +412,14 @@ void s390_cpu_list(void) for (feat = 0; feat < S390_FEAT_MAX; feat++) { const S390FeatDef *def = s390_feat_def(feat); - qemu_printf("%-20s %s\n", def->name, def->desc); + qemu_printf(" %-20s %s\n", def->name, def->desc); } qemu_printf("\nRecognized feature groups:\n"); for (group = 0; group < S390_FEAT_GROUP_MAX; group++) { const S390FeatGroupDef *def = s390_feat_group_def(group); - qemu_printf("%-20s %s\n", def->name, def->desc); + qemu_printf(" %-20s %s\n", def->name, def->desc); } } @@ -510,7 +511,7 @@ static void check_compat_model_failed(Error **errp, return; } -static void check_compatibility(const S390CPUModel *max_model, +static bool check_compatibility(const S390CPUModel *max_model, const S390CPUModel *model, Error **errp) { ERRP_GUARD(); @@ -518,11 +519,11 @@ static void check_compatibility(const S390CPUModel *max_model, if (model->def->gen > max_model->def->gen) { check_compat_model_failed(errp, max_model, "Selected CPU generation is too new"); - return; + return false; } else if (model->def->gen == max_model->def->gen && model->def->ec_ga > max_model->def->ec_ga) { check_compat_model_failed(errp, max_model, "Selected CPU GA level is too new"); - return; + return false; } #ifndef CONFIG_USER_ONLY @@ -530,14 +531,14 @@ static void check_compatibility(const S390CPUModel *max_model, error_setg(errp, "The unpack facility is not compatible with " "the --only-migratable option. You must remove either " "the 'unpack' facility or the --only-migratable option"); - return; + return false; } #endif /* detect the missing features to properly report them */ bitmap_andnot(missing, model->features, max_model->features, S390_FEAT_MAX); if (bitmap_empty(missing, S390_FEAT_MAX)) { - return; + return true; } error_setg(errp, " "); @@ -546,11 +547,11 @@ static void check_compatibility(const S390CPUModel *max_model, "available in the current configuration: "); error_append_hint(errp, "Consider a different accelerator, QEMU, or kernel version\n"); + return false; } S390CPUModel *get_max_cpu_model(Error **errp) { - Error *err = NULL; static S390CPUModel max_model; static bool cached; @@ -559,16 +560,14 @@ S390CPUModel *get_max_cpu_model(Error **errp) } if (kvm_enabled()) { - kvm_s390_get_host_cpu_model(&max_model, &err); + if (!kvm_s390_get_host_cpu_model(&max_model, errp)) { + return NULL; + } } else { max_model.def = s390_find_cpu_def(QEMU_MAX_CPU_TYPE, QEMU_MAX_CPU_GEN, QEMU_MAX_CPU_EC_GA, NULL); bitmap_copy(max_model.features, qemu_max_cpu_feat, S390_FEAT_MAX); } - if (err) { - error_propagate(errp, err); - return NULL; - } cached = true; return &max_model; } @@ -576,7 +575,6 @@ S390CPUModel *get_max_cpu_model(Error **errp) void s390_realize_cpu_model(CPUState *cs, Error **errp) { ERRP_GUARD(); - Error *err = NULL; S390CPUClass *xcc = S390_CPU_GET_CLASS(cs); S390CPU *cpu = S390_CPU(cs); const S390CPUModel *max_model; @@ -605,9 +603,7 @@ void s390_realize_cpu_model(CPUState *cs, Error **errp) cpu->model->cpu_ver = max_model->cpu_ver; check_consistency(cpu->model); - check_compatibility(max_model, cpu->model, &err); - if (err) { - error_propagate(errp, err); + if (!check_compatibility(max_model, cpu->model, errp)) { return; } diff --git a/target/s390x/cpu_models.h b/target/s390x/cpu_models.h index a89c2a15ab..71d4bc2dd4 100644 --- a/target/s390x/cpu_models.h +++ b/target/s390x/cpu_models.h @@ -115,7 +115,7 @@ S390CPUDef const *s390_find_cpu_def(uint16_t type, uint8_t gen, uint8_t ec_ga, S390FeatBitmap features); bool kvm_s390_cpu_models_supported(void); -void kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp); -void kvm_s390_apply_cpu_model(const S390CPUModel *model, Error **errp); +bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp); +bool kvm_s390_apply_cpu_model(const S390CPUModel *model, Error **errp); #endif /* TARGET_S390X_CPU_MODELS_H */ diff --git a/target/s390x/cpu_models_sysemu.c b/target/s390x/cpu_models_sysemu.c index 2d99218069..15be729c3d 100644 --- a/target/s390x/cpu_models_sysemu.c +++ b/target/s390x/cpu_models_sysemu.c @@ -389,7 +389,6 @@ CpuModelBaselineInfo *qmp_query_cpu_model_baseline(CpuModelInfo *infoa, void apply_cpu_model(const S390CPUModel *model, Error **errp) { - Error *err = NULL; static S390CPUModel applied_model; static bool applied; @@ -405,9 +404,7 @@ void apply_cpu_model(const S390CPUModel *model, Error **errp) } if (kvm_enabled()) { - kvm_s390_apply_cpu_model(model, &err); - if (err) { - error_propagate(errp, err); + if (!kvm_s390_apply_cpu_model(model, errp)) { return; } } diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c index 4dcd757cdc..1b494ecc20 100644 --- a/target/s390x/kvm/kvm.c +++ b/target/s390x/kvm/kvm.c @@ -2375,7 +2375,7 @@ bool kvm_s390_cpu_models_supported(void) KVM_S390_VM_CPU_MACHINE_SUBFUNC); } -void kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp) +bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp) { struct kvm_s390_vm_cpu_machine prop = {}; struct kvm_device_attr attr = { @@ -2390,14 +2390,14 @@ void kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp) if (!kvm_s390_cpu_models_supported()) { error_setg(errp, "KVM doesn't support CPU models"); - return; + return false; } /* query the basic cpu model properties */ rc = kvm_vm_ioctl(kvm_state, KVM_GET_DEVICE_ATTR, &attr); if (rc) { error_setg(errp, "KVM: Error querying host CPU model: %d", rc); - return; + return false; } cpu_type = cpuid_type(prop.cpuid); @@ -2420,13 +2420,13 @@ void kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp) rc = query_cpu_feat(model->features); if (rc) { error_setg(errp, "KVM: Error querying CPU features: %d", rc); - return; + return false; } /* get supported cpu subfunctions indicated via query / test bit */ rc = query_cpu_subfunc(model->features); if (rc) { error_setg(errp, "KVM: Error querying CPU subfunctions: %d", rc); - return; + return false; } /* PTFF subfunctions might be indicated although kernel support missing */ @@ -2482,7 +2482,7 @@ void kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp) } if (!model->def) { error_setg(errp, "KVM: host CPU model could not be identified"); - return; + return false; } /* for now, we can only provide the AP feature with HW support */ if (ap_available()) { @@ -2506,6 +2506,7 @@ void kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp) /* strip of features that are not part of the maximum model */ bitmap_and(model->features, model->features, model->def->full_feat, S390_FEAT_MAX); + return true; } static int configure_uv_feat_guest(const S390FeatBitmap features) @@ -2542,7 +2543,7 @@ static void kvm_s390_configure_apie(bool interpret) } } -void kvm_s390_apply_cpu_model(const S390CPUModel *model, Error **errp) +bool kvm_s390_apply_cpu_model(const S390CPUModel *model, Error **errp) { struct kvm_s390_vm_cpu_processor prop = { .fac_list = { 0 }, @@ -2559,11 +2560,11 @@ void kvm_s390_apply_cpu_model(const S390CPUModel *model, Error **errp) if (kvm_s390_cmma_available()) { kvm_s390_enable_cmma(); } - return; + return true; } if (!kvm_s390_cpu_models_supported()) { error_setg(errp, "KVM doesn't support CPU models"); - return; + return false; } prop.cpuid = s390_cpuid_from_cpu_model(model); prop.ibc = s390_ibc_from_cpu_model(model); @@ -2573,19 +2574,19 @@ void kvm_s390_apply_cpu_model(const S390CPUModel *model, Error **errp) rc = kvm_vm_ioctl(kvm_state, KVM_SET_DEVICE_ATTR, &attr); if (rc) { error_setg(errp, "KVM: Error configuring the CPU model: %d", rc); - return; + return false; } /* configure cpu features indicated e.g. via SCLP */ rc = configure_cpu_feat(model->features); if (rc) { error_setg(errp, "KVM: Error configuring CPU features: %d", rc); - return; + return false; } /* configure cpu subfunctions indicated via query / test bit */ rc = configure_cpu_subfunc(model->features); if (rc) { error_setg(errp, "KVM: Error configuring CPU subfunctions: %d", rc); - return; + return false; } /* enable CMM via CMMA */ if (test_bit(S390_FEAT_CMM, model->features)) { @@ -2600,8 +2601,9 @@ void kvm_s390_apply_cpu_model(const S390CPUModel *model, Error **errp) rc = configure_uv_feat_guest(model->features); if (rc) { error_setg(errp, "KVM: Error configuring CPU UV features %d", rc); - return; + return false; } + return true; } void kvm_s390_restart_interrupt(S390CPU *cpu) diff --git a/tcg/tcg-op-ldst.c b/tcg/tcg-op-ldst.c index f11043b449..8510160258 100644 --- a/tcg/tcg-op-ldst.c +++ b/tcg/tcg-op-ldst.c @@ -161,14 +161,14 @@ plugin_gen_mem_callbacks(TCGv_i64 copy_addr, TCGTemp *orig_addr, MemOpIdx oi, copy_addr = tcg_temp_ebb_new_i64(); tcg_gen_extu_i32_i64(copy_addr, temp_tcgv_i32(orig_addr)); } - plugin_gen_empty_mem_callback(copy_addr, info); + tcg_gen_plugin_mem_cb(copy_addr, info); tcg_temp_free_i64(copy_addr); } else { if (copy_addr) { - plugin_gen_empty_mem_callback(copy_addr, info); + tcg_gen_plugin_mem_cb(copy_addr, info); tcg_temp_free_i64(copy_addr); } else { - plugin_gen_empty_mem_callback(temp_tcgv_i64(orig_addr), info); + tcg_gen_plugin_mem_cb(temp_tcgv_i64(orig_addr), info); } } } diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c index aa6bc6f57d..eff3728622 100644 --- a/tcg/tcg-op.c +++ b/tcg/tcg-op.c @@ -312,14 +312,14 @@ void tcg_gen_mb(TCGBar mb_type) } } -void tcg_gen_plugin_cb_start(unsigned from, unsigned type, unsigned wr) +void tcg_gen_plugin_cb(unsigned from) { - tcg_gen_op3(INDEX_op_plugin_cb_start, from, type, wr); + tcg_gen_op1(INDEX_op_plugin_cb, from); } -void tcg_gen_plugin_cb_end(void) +void tcg_gen_plugin_mem_cb(TCGv_i64 addr, unsigned meminfo) { - tcg_emit_op(INDEX_op_plugin_cb_end, 0); + tcg_gen_op2(INDEX_op_plugin_mem_cb, tcgv_i64_arg(addr), meminfo); } /* 32 bit ops */ diff --git a/tcg/tcg.c b/tcg/tcg.c index 6a32656cd4..d827c6d431 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -2251,7 +2251,8 @@ bool tcg_op_supported(TCGOpcode op) static TCGOp *tcg_op_alloc(TCGOpcode opc, unsigned nargs); -static void tcg_gen_callN(TCGHelperInfo *info, TCGTemp *ret, TCGTemp **args) +static void tcg_gen_callN(void *func, TCGHelperInfo *info, + TCGTemp *ret, TCGTemp **args) { TCGv_i64 extend_free[MAX_CALL_IARGS]; int n_extend = 0; @@ -2268,9 +2269,7 @@ static void tcg_gen_callN(TCGHelperInfo *info, TCGTemp *ret, TCGTemp **args) #ifdef CONFIG_PLUGIN /* Flag helpers that may affect guest state */ - if (tcg_ctx->plugin_insn && - !(info->flags & TCG_CALL_PLUGIN) && - !(info->flags & TCG_CALL_NO_SIDE_EFFECTS)) { + if (tcg_ctx->plugin_insn && !(info->flags & TCG_CALL_NO_SIDE_EFFECTS)) { tcg_ctx->plugin_insn->calls_helpers = true; } #endif @@ -2329,7 +2328,7 @@ static void tcg_gen_callN(TCGHelperInfo *info, TCGTemp *ret, TCGTemp **args) g_assert_not_reached(); } } - op->args[pi++] = (uintptr_t)info->func; + op->args[pi++] = (uintptr_t)func; op->args[pi++] = (uintptr_t)info; tcg_debug_assert(pi == total_args); @@ -2345,56 +2344,58 @@ static void tcg_gen_callN(TCGHelperInfo *info, TCGTemp *ret, TCGTemp **args) } } -void tcg_gen_call0(TCGHelperInfo *info, TCGTemp *ret) +void tcg_gen_call0(void *func, TCGHelperInfo *info, TCGTemp *ret) { - tcg_gen_callN(info, ret, NULL); + tcg_gen_callN(func, info, ret, NULL); } -void tcg_gen_call1(TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1) +void tcg_gen_call1(void *func, TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1) { - tcg_gen_callN(info, ret, &t1); + tcg_gen_callN(func, info, ret, &t1); } -void tcg_gen_call2(TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, TCGTemp *t2) +void tcg_gen_call2(void *func, TCGHelperInfo *info, TCGTemp *ret, + TCGTemp *t1, TCGTemp *t2) { TCGTemp *args[2] = { t1, t2 }; - tcg_gen_callN(info, ret, args); + tcg_gen_callN(func, info, ret, args); } -void tcg_gen_call3(TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, - TCGTemp *t2, TCGTemp *t3) +void tcg_gen_call3(void *func, TCGHelperInfo *info, TCGTemp *ret, + TCGTemp *t1, TCGTemp *t2, TCGTemp *t3) { TCGTemp *args[3] = { t1, t2, t3 }; - tcg_gen_callN(info, ret, args); + tcg_gen_callN(func, info, ret, args); } -void tcg_gen_call4(TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, - TCGTemp *t2, TCGTemp *t3, TCGTemp *t4) +void tcg_gen_call4(void *func, TCGHelperInfo *info, TCGTemp *ret, + TCGTemp *t1, TCGTemp *t2, TCGTemp *t3, TCGTemp *t4) { TCGTemp *args[4] = { t1, t2, t3, t4 }; - tcg_gen_callN(info, ret, args); + tcg_gen_callN(func, info, ret, args); } -void tcg_gen_call5(TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, +void tcg_gen_call5(void *func, TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, TCGTemp *t2, TCGTemp *t3, TCGTemp *t4, TCGTemp *t5) { TCGTemp *args[5] = { t1, t2, t3, t4, t5 }; - tcg_gen_callN(info, ret, args); + tcg_gen_callN(func, info, ret, args); } -void tcg_gen_call6(TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, TCGTemp *t2, - TCGTemp *t3, TCGTemp *t4, TCGTemp *t5, TCGTemp *t6) +void tcg_gen_call6(void *func, TCGHelperInfo *info, TCGTemp *ret, + TCGTemp *t1, TCGTemp *t2, TCGTemp *t3, + TCGTemp *t4, TCGTemp *t5, TCGTemp *t6) { TCGTemp *args[6] = { t1, t2, t3, t4, t5, t6 }; - tcg_gen_callN(info, ret, args); + tcg_gen_callN(func, info, ret, args); } -void tcg_gen_call7(TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, +void tcg_gen_call7(void *func, TCGHelperInfo *info, TCGTemp *ret, TCGTemp *t1, TCGTemp *t2, TCGTemp *t3, TCGTemp *t4, TCGTemp *t5, TCGTemp *t6, TCGTemp *t7) { TCGTemp *args[7] = { t1, t2, t3, t4, t5, t6, t7 }; - tcg_gen_callN(info, ret, args); + tcg_gen_callN(func, info, ret, args); } static void tcg_reg_alloc_start(TCGContext *s) @@ -2539,6 +2540,15 @@ static const char bswap_flag_name[][6] = { [TCG_BSWAP_IZ | TCG_BSWAP_OS] = "iz,os", }; +#ifdef CONFIG_PLUGIN +static const char * const plugin_from_name[] = { + "from-tb", + "from-insn", + "after-insn", + "after-tb", +}; +#endif + static inline bool tcg_regset_single(TCGRegSet d) { return (d & (d - 1)) == 0; @@ -2557,7 +2567,7 @@ static inline TCGReg tcg_regset_first(TCGRegSet d) #define ne_fprintf(...) \ ({ int ret_ = fprintf(__VA_ARGS__); ret_ >= 0 ? ret_ : 0; }) -static void tcg_dump_ops(TCGContext *s, FILE *f, bool have_prefs) +void tcg_dump_ops(TCGContext *s, FILE *f, bool have_prefs) { char buf[128]; TCGOp *op; @@ -2713,6 +2723,24 @@ static void tcg_dump_ops(TCGContext *s, FILE *f, bool have_prefs) i = k = 1; } break; +#ifdef CONFIG_PLUGIN + case INDEX_op_plugin_cb: + { + TCGArg from = op->args[k++]; + const char *name = NULL; + + if (from < ARRAY_SIZE(plugin_from_name)) { + name = plugin_from_name[from]; + } + if (name) { + col += ne_fprintf(f, "%s", name); + } else { + col += ne_fprintf(f, "$0x%" TCG_PRIlx, from); + } + i = 1; + } + break; +#endif default: i = 0; break; diff --git a/tcg/tci.c b/tcg/tci.c index 39adcb7d82..3afb223528 100644 --- a/tcg/tci.c +++ b/tcg/tci.c @@ -19,6 +19,7 @@ #include "qemu/osdep.h" #include "tcg/tcg.h" +#include "tcg/helper-info.h" #include "tcg/tcg-ldst.h" #include <ffi.h> diff --git a/tests/avocado/boot_linux_console.py b/tests/avocado/boot_linux_console.py index 180ac17326..c35fc5e9ba 100644 --- a/tests/avocado/boot_linux_console.py +++ b/tests/avocado/boot_linux_console.py @@ -646,12 +646,12 @@ class BootLinuxConsole(LinuxKernelTest): :avocado: tags=accel:tcg """ deb_url = ('https://apt.armbian.com/pool/main/l/' - 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + '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-5.10.16-sunxi') - dtb_path = '/usr/lib/linux-image-current-sunxi/sun4i-a10-cubieboard.dtb' + '/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/' @@ -690,12 +690,12 @@ class BootLinuxConsole(LinuxKernelTest): :avocado: tags=accel:tcg """ deb_url = ('https://apt.armbian.com/pool/main/l/' - 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + '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-5.10.16-sunxi') - dtb_path = '/usr/lib/linux-image-current-sunxi/sun4i-a10-cubieboard.dtb' + '/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) rootfs_url = ('https://github.com/groeck/linux-build-test/raw/' '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' @@ -872,13 +872,13 @@ class BootLinuxConsole(LinuxKernelTest): :avocado: tags=machine:bpim2u :avocado: tags=accel:tcg """ - deb_url = ('https://apt.armbian.com/pool/main/l/linux-5.10.16-sunxi/' - 'linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + 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-5.10.16-sunxi') - dtb_path = ('/usr/lib/linux-image-current-sunxi/' + '/boot/vmlinuz-6.6.16-current-sunxi') + dtb_path = ('/usr/lib/linux-image-6.6.16-current-sunxi/' 'sun8i-r40-bananapi-m2-ultra.dtb') dtb_path = self.extract_from_deb(deb_path, dtb_path) @@ -899,13 +899,13 @@ class BootLinuxConsole(LinuxKernelTest): :avocado: tags=accel:tcg :avocado: tags=machine:bpim2u """ - deb_url = ('https://apt.armbian.com/pool/main/l/linux-5.10.16-sunxi/' - 'linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + 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-5.10.16-sunxi') - dtb_path = ('/usr/lib/linux-image-current-sunxi/' + '/boot/vmlinuz-6.6.16-current-sunxi') + dtb_path = ('/usr/lib/linux-image-6.6.16-current-sunxi/' 'sun8i-r40-bananapi-m2-ultra.dtb') dtb_path = self.extract_from_deb(deb_path, dtb_path) initrd_url = ('https://github.com/groeck/linux-build-test/raw/' @@ -946,13 +946,13 @@ class BootLinuxConsole(LinuxKernelTest): """ self.require_netdev('user') - deb_url = ('https://apt.armbian.com/pool/main/l/linux-5.10.16-sunxi/' - 'linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + 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-5.10.16-sunxi') - dtb_path = ('/usr/lib/linux-image-current-sunxi/' + '/boot/vmlinuz-6.6.16-current-sunxi') + dtb_path = ('/usr/lib/linux-image-6.6.16-current-sunxi/' 'sun8i-r40-bananapi-m2-ultra.dtb') dtb_path = self.extract_from_deb(deb_path, dtb_path) rootfs_url = ('http://storage.kernelci.org/images/rootfs/buildroot/' @@ -1049,12 +1049,12 @@ class BootLinuxConsole(LinuxKernelTest): :avocado: tags=accel:tcg """ deb_url = ('https://apt.armbian.com/pool/main/l/' - 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + '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-5.10.16-sunxi') - dtb_path = '/usr/lib/linux-image-current-sunxi/sun8i-h3-orangepi-pc.dtb' + '/boot/vmlinuz-6.6.16-current-sunxi') + dtb_path = '/usr/lib/linux-image-6.6.16-current-sunxi/sun8i-h3-orangepi-pc.dtb' dtb_path = self.extract_from_deb(deb_path, dtb_path) self.vm.set_console() @@ -1075,12 +1075,12 @@ class BootLinuxConsole(LinuxKernelTest): :avocado: tags=machine:orangepi-pc """ deb_url = ('https://apt.armbian.com/pool/main/l/' - 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + '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-5.10.16-sunxi') - dtb_path = '/usr/lib/linux-image-current-sunxi/sun8i-h3-orangepi-pc.dtb' + '/boot/vmlinuz-6.6.16-current-sunxi') + dtb_path = '/usr/lib/linux-image-6.6.16-current-sunxi/sun8i-h3-orangepi-pc.dtb' dtb_path = self.extract_from_deb(deb_path, dtb_path) initrd_url = ('https://github.com/groeck/linux-build-test/raw/' '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' @@ -1121,12 +1121,12 @@ class BootLinuxConsole(LinuxKernelTest): self.require_netdev('user') deb_url = ('https://apt.armbian.com/pool/main/l/' - 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + '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-5.10.16-sunxi') - dtb_path = '/usr/lib/linux-image-current-sunxi/sun8i-h3-orangepi-pc.dtb' + '/boot/vmlinuz-6.6.16-current-sunxi') + dtb_path = '/usr/lib/linux-image-6.6.16-current-sunxi/sun8i-h3-orangepi-pc.dtb' dtb_path = self.extract_from_deb(deb_path, dtb_path) rootfs_url = ('http://storage.kernelci.org/images/rootfs/buildroot/' 'buildroot-baseline/20221116.0/armel/rootfs.ext2.xz') diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py index 2c81412dba..232d287c27 100644 --- a/tests/avocado/replay_kernel.py +++ b/tests/avocado/replay_kernel.py @@ -203,12 +203,12 @@ class ReplayKernelNormal(ReplayKernelBase): :avocado: tags=machine:cubieboard """ deb_url = ('https://apt.armbian.com/pool/main/l/' - 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') - deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + '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-5.10.16-sunxi') - dtb_path = '/usr/lib/linux-image-current-sunxi/sun4i-a10-cubieboard.dtb' + '/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/' diff --git a/tests/bench/bufferiszero-bench.c b/tests/bench/bufferiszero-bench.c new file mode 100644 index 0000000000..222695c1fa --- /dev/null +++ b/tests/bench/bufferiszero-bench.c @@ -0,0 +1,47 @@ +/* + * QEMU buffer_is_zero speed benchmark + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ +#include "qemu/osdep.h" +#include "qemu/cutils.h" +#include "qemu/units.h" + +static void test(const void *opaque) +{ + size_t max = 64 * KiB; + void *buf = g_malloc0(max); + int accel_index = 0; + + do { + if (accel_index != 0) { + g_test_message("%s", ""); /* gnu_printf Werror for simple "" */ + } + for (size_t len = 1 * KiB; len <= max; len *= 4) { + double total = 0.0; + + g_test_timer_start(); + do { + buffer_is_zero_ge256(buf, len); + total += len; + } while (g_test_timer_elapsed() < 0.5); + + total /= MiB; + g_test_message("buffer_is_zero #%d: %2zuKB %8.0f MB/sec", + accel_index, len / (size_t)KiB, + total / g_test_timer_last()); + } + accel_index++; + } while (test_buffer_is_zero_next_accel()); + + g_free(buf); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_data_func("/cutils/bufferiszero/speed", NULL, test); + return g_test_run(); +} diff --git a/tests/bench/meson.build b/tests/bench/meson.build index 7e76338a52..4cd7a2f6b5 100644 --- a/tests/bench/meson.build +++ b/tests/bench/meson.build @@ -21,6 +21,7 @@ benchs = {} if have_block benchs += { + 'bufferiszero-bench': [], 'benchmark-crypto-hash': [crypto], 'benchmark-crypto-hmac': [crypto], 'benchmark-crypto-cipher': [crypto], diff --git a/tests/qtest/dm163-test.c b/tests/qtest/dm163-test.c new file mode 100644 index 0000000000..3161c9208d --- /dev/null +++ b/tests/qtest/dm163-test.c @@ -0,0 +1,194 @@ +/* + * QTest testcase for DM163 + * + * Copyright (C) 2024 Samuel Tardieu <sam@rfc1149.net> + * Copyright (C) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr> + * Copyright (C) 2024 Inès Varhol <ines.varhol@telecom-paris.fr> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" + +enum DM163_INPUTS { + SIN = 8, + DCK = 9, + RST_B = 10, + LAT_B = 11, + SELBK = 12, + EN_B = 13 +}; + +#define DEVICE_NAME "/machine/dm163" +#define GPIO_OUT(name, value) qtest_set_irq_in(qts, DEVICE_NAME, NULL, name, \ + value) +#define GPIO_PULSE(name) \ + do { \ + GPIO_OUT(name, 1); \ + GPIO_OUT(name, 0); \ + } while (0) + + +static void rise_gpio_pin_dck(QTestState *qts) +{ + /* Configure output mode for pin PB1 */ + qtest_writel(qts, 0x48000400, 0xFFFFFEB7); + /* Write 1 in ODR for PB1 */ + qtest_writel(qts, 0x48000414, 0x00000002); +} + +static void lower_gpio_pin_dck(QTestState *qts) +{ + /* Configure output mode for pin PB1 */ + qtest_writel(qts, 0x48000400, 0xFFFFFEB7); + /* Write 0 in ODR for PB1 */ + qtest_writel(qts, 0x48000414, 0x00000000); +} + +static void rise_gpio_pin_selbk(QTestState *qts) +{ + /* Configure output mode for pin PC5 */ + qtest_writel(qts, 0x48000800, 0xFFFFF7FF); + /* Write 1 in ODR for PC5 */ + qtest_writel(qts, 0x48000814, 0x00000020); +} + +static void lower_gpio_pin_selbk(QTestState *qts) +{ + /* Configure output mode for pin PC5 */ + qtest_writel(qts, 0x48000800, 0xFFFFF7FF); + /* Write 0 in ODR for PC5 */ + qtest_writel(qts, 0x48000814, 0x00000000); +} + +static void rise_gpio_pin_lat_b(QTestState *qts) +{ + /* Configure output mode for pin PC4 */ + qtest_writel(qts, 0x48000800, 0xFFFFFDFF); + /* Write 1 in ODR for PC4 */ + qtest_writel(qts, 0x48000814, 0x00000010); +} + +static void lower_gpio_pin_lat_b(QTestState *qts) +{ + /* Configure output mode for pin PC4 */ + qtest_writel(qts, 0x48000800, 0xFFFFFDFF); + /* Write 0 in ODR for PC4 */ + qtest_writel(qts, 0x48000814, 0x00000000); +} + +static void rise_gpio_pin_rst_b(QTestState *qts) +{ + /* Configure output mode for pin PC3 */ + qtest_writel(qts, 0x48000800, 0xFFFFFF7F); + /* Write 1 in ODR for PC3 */ + qtest_writel(qts, 0x48000814, 0x00000008); +} + +static void lower_gpio_pin_rst_b(QTestState *qts) +{ + /* Configure output mode for pin PC3 */ + qtest_writel(qts, 0x48000800, 0xFFFFFF7F); + /* Write 0 in ODR for PC3 */ + qtest_writel(qts, 0x48000814, 0x00000000); +} + +static void rise_gpio_pin_sin(QTestState *qts) +{ + /* Configure output mode for pin PA4 */ + qtest_writel(qts, 0x48000000, 0xFFFFFDFF); + /* Write 1 in ODR for PA4 */ + qtest_writel(qts, 0x48000014, 0x00000010); +} + +static void lower_gpio_pin_sin(QTestState *qts) +{ + /* Configure output mode for pin PA4 */ + qtest_writel(qts, 0x48000000, 0xFFFFFDFF); + /* Write 0 in ODR for PA4 */ + qtest_writel(qts, 0x48000014, 0x00000000); +} + +static void test_dm163_bank(const void *opaque) +{ + const unsigned bank = (uintptr_t) opaque; + const int width = bank ? 192 : 144; + + QTestState *qts = qtest_initf("-M b-l475e-iot01a"); + qtest_irq_intercept_out_named(qts, DEVICE_NAME, "sout"); + GPIO_OUT(RST_B, 1); + GPIO_OUT(EN_B, 0); + GPIO_OUT(DCK, 0); + GPIO_OUT(SELBK, bank); + GPIO_OUT(LAT_B, 1); + + /* Fill bank with zeroes */ + GPIO_OUT(SIN, 0); + for (int i = 0; i < width; i++) { + GPIO_PULSE(DCK); + } + /* Fill bank with ones, check that we get the previous zeroes */ + GPIO_OUT(SIN, 1); + for (int i = 0; i < width; i++) { + GPIO_PULSE(DCK); + g_assert(!qtest_get_irq(qts, 0)); + } + + /* Pulse one more bit in the bank, check that we get a one */ + GPIO_PULSE(DCK); + g_assert(qtest_get_irq(qts, 0)); + + qtest_quit(qts); +} + +static void test_dm163_gpio_connection(void) +{ + QTestState *qts = qtest_init("-M b-l475e-iot01a"); + qtest_irq_intercept_in(qts, DEVICE_NAME); + + g_assert_false(qtest_get_irq(qts, SIN)); + g_assert_false(qtest_get_irq(qts, DCK)); + g_assert_false(qtest_get_irq(qts, RST_B)); + g_assert_false(qtest_get_irq(qts, LAT_B)); + g_assert_false(qtest_get_irq(qts, SELBK)); + + rise_gpio_pin_dck(qts); + g_assert_true(qtest_get_irq(qts, DCK)); + lower_gpio_pin_dck(qts); + g_assert_false(qtest_get_irq(qts, DCK)); + + rise_gpio_pin_lat_b(qts); + g_assert_true(qtest_get_irq(qts, LAT_B)); + lower_gpio_pin_lat_b(qts); + g_assert_false(qtest_get_irq(qts, LAT_B)); + + rise_gpio_pin_selbk(qts); + g_assert_true(qtest_get_irq(qts, SELBK)); + lower_gpio_pin_selbk(qts); + g_assert_false(qtest_get_irq(qts, SELBK)); + + rise_gpio_pin_rst_b(qts); + g_assert_true(qtest_get_irq(qts, RST_B)); + lower_gpio_pin_rst_b(qts); + g_assert_false(qtest_get_irq(qts, RST_B)); + + rise_gpio_pin_sin(qts); + g_assert_true(qtest_get_irq(qts, SIN)); + lower_gpio_pin_sin(qts); + g_assert_false(qtest_get_irq(qts, SIN)); + + g_assert_false(qtest_get_irq(qts, DCK)); + g_assert_false(qtest_get_irq(qts, LAT_B)); + g_assert_false(qtest_get_irq(qts, SELBK)); + g_assert_false(qtest_get_irq(qts, RST_B)); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + qtest_add_data_func("/dm163/bank0", (void *)0, test_dm163_bank); + qtest_add_data_func("/dm163/bank1", (void *)1, test_dm163_bank); + qtest_add_func("/dm163/gpio_connection", test_dm163_gpio_connection); + return g_test_run(); +} diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c index d6b4f6e36a..90ba6b298b 100644 --- a/tests/qtest/ide-test.c +++ b/tests/qtest/ide-test.c @@ -34,7 +34,8 @@ #include "hw/pci/pci_ids.h" #include "hw/pci/pci_regs.h" -#define TEST_IMAGE_SIZE 64 * 1024 * 1024 +/* Specified by ATA (physical) CHS geometry for ~64 MiB device. */ +#define TEST_IMAGE_SIZE ((130 * 16 * 63) * 512) #define IDE_PCI_DEV 1 #define IDE_PCI_FUNC 1 @@ -88,11 +89,13 @@ enum { enum { CMD_DSM = 0x06, CMD_DIAGNOSE = 0x90, + CMD_INIT_DP = 0x91, /* INITIALIZE DEVICE PARAMETERS */ CMD_READ_DMA = 0xc8, CMD_WRITE_DMA = 0xca, CMD_FLUSH_CACHE = 0xe7, CMD_IDENTIFY = 0xec, CMD_PACKET = 0xa0, + CMD_READ_NATIVE = 0xf8, /* READ NATIVE MAX ADDRESS */ CMDF_ABORT = 0x100, CMDF_NO_BM = 0x200, @@ -560,6 +563,46 @@ static void string_cpu_to_be16(uint16_t *s, size_t bytes) } } +static void test_specify(void) +{ + QTestState *qts; + QPCIDevice *dev; + QPCIBar bmdma_bar, ide_bar; + uint16_t cyls; + uint8_t heads, spt; + + qts = ide_test_start( + "-blockdev driver=file,node-name=hda,filename=%s " + "-device ide-hd,drive=hda,bus=ide.0,unit=0 ", + tmp_path[0]); + + dev = get_pci_device(qts, &bmdma_bar, &ide_bar); + + /* Initialize drive with zero sectors per track and one head. */ + qpci_io_writeb(dev, ide_bar, reg_nsectors, 0); + qpci_io_writeb(dev, ide_bar, reg_device, 0); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP); + + /* READ NATIVE MAX ADDRESS (CHS mode). */ + qpci_io_writeb(dev, ide_bar, reg_device, 0xa0); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_READ_NATIVE); + + heads = qpci_io_readb(dev, ide_bar, reg_device) & 0xf; + ++heads; + g_assert_cmpint(heads, ==, 16); + + cyls = qpci_io_readb(dev, ide_bar, reg_lba_high) << 8; + cyls |= qpci_io_readb(dev, ide_bar, reg_lba_middle); + ++cyls; + g_assert_cmpint(cyls, ==, 130); + + spt = qpci_io_readb(dev, ide_bar, reg_lba_low); + g_assert_cmpint(spt, ==, 63); + + ide_test_quit(qts); + free_pci_device(dev); +} + static void test_identify(void) { QTestState *qts; @@ -1077,6 +1120,8 @@ int main(int argc, char **argv) /* Run the tests */ g_test_init(&argc, &argv, NULL); + qtest_add_func("/ide/read_native", test_specify); + qtest_add_func("/ide/identify", test_identify); qtest_add_func("/ide/diagnostic", test_diagnostic); diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index b128fa5a4b..6f2f594ace 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -224,6 +224,8 @@ qtests_arm = \ (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \ (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \ (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \ + (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and + config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \ ['arm-cpu-features', 'boot-serial-test'] diff --git a/tests/qtest/stm32l4x5_gpio-test.c b/tests/qtest/stm32l4x5_gpio-test.c index 0f6bda54d3..72a7823406 100644 --- a/tests/qtest/stm32l4x5_gpio-test.c +++ b/tests/qtest/stm32l4x5_gpio-test.c @@ -43,6 +43,9 @@ #define OTYPER_PUSH_PULL 0 #define OTYPER_OPEN_DRAIN 1 +/* SoC forwards GPIOs to SysCfg */ +#define SYSCFG "/machine/soc" + const uint32_t moder_reset[NUM_GPIOS] = { 0xABFFFFFF, 0xFFFFFEBF, @@ -284,7 +287,7 @@ static void test_gpio_output_mode(const void *data) uint32_t gpio = test_gpio_addr(data); unsigned int gpio_id = get_gpio_id(gpio); - qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg"); + qtest_irq_intercept_in(global_qtest, SYSCFG); /* Set a bit in ODR and check nothing happens */ gpio_set_bit(gpio, ODR, pin, 1); @@ -319,7 +322,7 @@ static void test_gpio_input_mode(const void *data) uint32_t gpio = test_gpio_addr(data); unsigned int gpio_id = get_gpio_id(gpio); - qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg"); + qtest_irq_intercept_in(global_qtest, SYSCFG); /* Configure a line as input, raise it, and check that the pin is high */ gpio_set_2bits(gpio, MODER, pin, MODER_INPUT); @@ -348,7 +351,7 @@ static void test_pull_up_pull_down(const void *data) uint32_t gpio = test_gpio_addr(data); unsigned int gpio_id = get_gpio_id(gpio); - qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg"); + qtest_irq_intercept_in(global_qtest, SYSCFG); /* Configure a line as input with pull-up, check the line is set high */ gpio_set_2bits(gpio, MODER, pin, MODER_INPUT); @@ -378,7 +381,7 @@ static void test_push_pull(const void *data) uint32_t gpio = test_gpio_addr(data); uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio); - qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg"); + qtest_irq_intercept_in(global_qtest, SYSCFG); /* Setting a line high externally, configuring it in push-pull output */ /* And checking the pin was disconnected */ @@ -425,7 +428,7 @@ static void test_open_drain(const void *data) uint32_t gpio = test_gpio_addr(data); uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio); - qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg"); + qtest_irq_intercept_in(global_qtest, SYSCFG); /* Setting a line high externally, configuring it in open-drain output */ /* And checking the pin was disconnected */ diff --git a/tests/qtest/stm32l4x5_syscfg-test.c b/tests/qtest/stm32l4x5_syscfg-test.c index 59bac829b7..506ca08bc2 100644 --- a/tests/qtest/stm32l4x5_syscfg-test.c +++ b/tests/qtest/stm32l4x5_syscfg-test.c @@ -1,8 +1,8 @@ /* * QTest testcase for STM32L4x5_SYSCFG * - * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr> - * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr> + * Copyright (c) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr> + * Copyright (c) 2024 Inès Varhol <ines.varhol@telecom-paris.fr> * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. @@ -25,6 +25,10 @@ #define SYSCFG_SWPR2 0x28 #define INVALID_ADDR 0x2C +/* SoC forwards GPIOs to SysCfg */ +#define SYSCFG "/machine/soc" +#define EXTI "/machine/soc/exti" + static void syscfg_writel(unsigned int offset, uint32_t value) { writel(SYSCFG_BASE_ADDR + offset, value); @@ -37,8 +41,7 @@ static uint32_t syscfg_readl(unsigned int offset) static void syscfg_set_irq(int num, int level) { - qtest_set_irq_in(global_qtest, "/machine/soc/syscfg", - NULL, num, level); + qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level); } static void system_reset(void) @@ -197,7 +200,7 @@ static void test_interrupt(void) * Test that GPIO rising lines result in an irq * with the right configuration */ - qtest_irq_intercept_in(global_qtest, "/machine/soc/exti"); + qtest_irq_intercept_in(global_qtest, EXTI); /* GPIOA is the default source for EXTI lines 0 to 15 */ @@ -230,7 +233,7 @@ static void test_irq_pin_multiplexer(void) * Test that syscfg irq sets the right exti irq */ - qtest_irq_intercept_in(global_qtest, "/machine/soc/exti"); + qtest_irq_intercept_in(global_qtest, EXTI); syscfg_set_irq(0, 1); @@ -257,7 +260,7 @@ static void test_irq_gpio_multiplexer(void) * Test that an irq is generated only by the right GPIO */ - qtest_irq_intercept_in(global_qtest, "/machine/soc/exti"); + qtest_irq_intercept_in(global_qtest, EXTI); /* GPIOA is the default source for EXTI lines 0 to 15 */ diff --git a/util/bufferiszero.c b/util/bufferiszero.c index 3e6a5dfd63..74864f7b78 100644 --- a/util/bufferiszero.c +++ b/util/bufferiszero.c @@ -26,265 +26,290 @@ #include "qemu/bswap.h" #include "host/cpuinfo.h" -static bool -buffer_zero_int(const void *buf, size_t len) +typedef bool (*biz_accel_fn)(const void *, size_t); + +static bool buffer_is_zero_int_lt256(const void *buf, size_t len) { - if (unlikely(len < 8)) { - /* For a very small buffer, simply accumulate all the bytes. */ - const unsigned char *p = buf; - const unsigned char *e = buf + len; - unsigned char t = 0; - - do { - t |= *p++; - } while (p < e); - - return t == 0; - } else { - /* Otherwise, use the unaligned memory access functions to - handle the beginning and end of the buffer, with a couple - of loops handling the middle aligned section. */ - uint64_t t = ldq_he_p(buf); - const uint64_t *p = (uint64_t *)(((uintptr_t)buf + 8) & -8); - const uint64_t *e = (uint64_t *)(((uintptr_t)buf + len) & -8); - - for (; p + 8 <= e; p += 8) { - __builtin_prefetch(p + 8); - if (t) { - return false; - } - t = p[0] | p[1] | p[2] | p[3] | p[4] | p[5] | p[6] | p[7]; - } - while (p < e) { - t |= *p++; - } - t |= ldq_he_p(buf + len - 8); + uint64_t t; + const uint64_t *p, *e; - return t == 0; + /* + * Use unaligned memory access functions to handle + * the beginning and end of the buffer. + */ + if (unlikely(len <= 8)) { + return (ldl_he_p(buf) | ldl_he_p(buf + len - 4)) == 0; } -} -#if defined(CONFIG_AVX512F_OPT) || defined(CONFIG_AVX2_OPT) || defined(__SSE2__) -#include <immintrin.h> + t = ldq_he_p(buf) | ldq_he_p(buf + len - 8); + p = QEMU_ALIGN_PTR_DOWN(buf + 8, 8); + e = QEMU_ALIGN_PTR_DOWN(buf + len - 1, 8); -/* Note that each of these vectorized functions require len >= 64. */ + /* Read 0 to 31 aligned words from the middle. */ + while (p < e) { + t |= *p++; + } + return t == 0; +} -static bool __attribute__((target("sse2"))) -buffer_zero_sse2(const void *buf, size_t len) +static bool buffer_is_zero_int_ge256(const void *buf, size_t len) { - __m128i t = _mm_loadu_si128(buf); - __m128i *p = (__m128i *)(((uintptr_t)buf + 5 * 16) & -16); - __m128i *e = (__m128i *)(((uintptr_t)buf + len) & -16); - __m128i zero = _mm_setzero_si128(); - - /* Loop over 16-byte aligned blocks of 64. */ - while (likely(p <= e)) { - __builtin_prefetch(p); - t = _mm_cmpeq_epi8(t, zero); - if (unlikely(_mm_movemask_epi8(t) != 0xFFFF)) { + /* + * Use unaligned memory access functions to handle + * the beginning and end of the buffer. + */ + uint64_t t = ldq_he_p(buf) | ldq_he_p(buf + len - 8); + const uint64_t *p = QEMU_ALIGN_PTR_DOWN(buf + 8, 8); + const uint64_t *e = QEMU_ALIGN_PTR_DOWN(buf + len - 1, 8); + + /* Collect a partial block at the tail end. */ + t |= e[-7] | e[-6] | e[-5] | e[-4] | e[-3] | e[-2] | e[-1]; + + /* + * Loop over 64 byte blocks. + * With the head and tail removed, e - p >= 30, + * so the loop must iterate at least 3 times. + */ + do { + if (t) { return false; } - t = p[-4] | p[-3] | p[-2] | p[-1]; - p += 4; - } + t = p[0] | p[1] | p[2] | p[3] | p[4] | p[5] | p[6] | p[7]; + p += 8; + } while (p < e - 7); - /* Finish the aligned tail. */ - t |= e[-3]; - t |= e[-2]; - t |= e[-1]; + return t == 0; +} - /* Finish the unaligned tail. */ - t |= _mm_loadu_si128(buf + len - 16); +#if defined(CONFIG_AVX2_OPT) || defined(__SSE2__) +#include <immintrin.h> - return _mm_movemask_epi8(_mm_cmpeq_epi8(t, zero)) == 0xFFFF; -} +/* Helper for preventing the compiler from reassociating + chains of binary vector operations. */ +#define SSE_REASSOC_BARRIER(vec0, vec1) asm("" : "+x"(vec0), "+x"(vec1)) -#ifdef CONFIG_AVX2_OPT -static bool __attribute__((target("sse4"))) -buffer_zero_sse4(const void *buf, size_t len) +/* Note that these vectorized functions may assume len >= 256. */ + +static bool __attribute__((target("sse2"))) +buffer_zero_sse2(const void *buf, size_t len) { - __m128i t = _mm_loadu_si128(buf); - __m128i *p = (__m128i *)(((uintptr_t)buf + 5 * 16) & -16); - __m128i *e = (__m128i *)(((uintptr_t)buf + len) & -16); - - /* Loop over 16-byte aligned blocks of 64. */ - while (likely(p <= e)) { - __builtin_prefetch(p); - if (unlikely(!_mm_testz_si128(t, t))) { + /* Unaligned loads at head/tail. */ + __m128i v = *(__m128i_u *)(buf); + __m128i w = *(__m128i_u *)(buf + len - 16); + /* Align head/tail to 16-byte boundaries. */ + const __m128i *p = QEMU_ALIGN_PTR_DOWN(buf + 16, 16); + const __m128i *e = QEMU_ALIGN_PTR_DOWN(buf + len - 1, 16); + __m128i zero = { 0 }; + + /* Collect a partial block at tail end. */ + v |= e[-1]; w |= e[-2]; + SSE_REASSOC_BARRIER(v, w); + v |= e[-3]; w |= e[-4]; + SSE_REASSOC_BARRIER(v, w); + v |= e[-5]; w |= e[-6]; + SSE_REASSOC_BARRIER(v, w); + v |= e[-7]; v |= w; + + /* + * Loop over complete 128-byte blocks. + * With the head and tail removed, e - p >= 14, so the loop + * must iterate at least once. + */ + do { + v = _mm_cmpeq_epi8(v, zero); + if (unlikely(_mm_movemask_epi8(v) != 0xFFFF)) { return false; } - t = p[-4] | p[-3] | p[-2] | p[-1]; - p += 4; - } - - /* Finish the aligned tail. */ - t |= e[-3]; - t |= e[-2]; - t |= e[-1]; - - /* Finish the unaligned tail. */ - t |= _mm_loadu_si128(buf + len - 16); - - return _mm_testz_si128(t, t); + v = p[0]; w = p[1]; + SSE_REASSOC_BARRIER(v, w); + v |= p[2]; w |= p[3]; + SSE_REASSOC_BARRIER(v, w); + v |= p[4]; w |= p[5]; + SSE_REASSOC_BARRIER(v, w); + v |= p[6]; w |= p[7]; + SSE_REASSOC_BARRIER(v, w); + v |= w; + p += 8; + } while (p < e - 7); + + return _mm_movemask_epi8(_mm_cmpeq_epi8(v, zero)) == 0xFFFF; } +#ifdef CONFIG_AVX2_OPT static bool __attribute__((target("avx2"))) buffer_zero_avx2(const void *buf, size_t len) { - /* Begin with an unaligned head of 32 bytes. */ - __m256i t = _mm256_loadu_si256(buf); - __m256i *p = (__m256i *)(((uintptr_t)buf + 5 * 32) & -32); - __m256i *e = (__m256i *)(((uintptr_t)buf + len) & -32); - - /* Loop over 32-byte aligned blocks of 128. */ - while (p <= e) { - __builtin_prefetch(p); - if (unlikely(!_mm256_testz_si256(t, t))) { + /* Unaligned loads at head/tail. */ + __m256i v = *(__m256i_u *)(buf); + __m256i w = *(__m256i_u *)(buf + len - 32); + /* Align head/tail to 32-byte boundaries. */ + const __m256i *p = QEMU_ALIGN_PTR_DOWN(buf + 32, 32); + const __m256i *e = QEMU_ALIGN_PTR_DOWN(buf + len - 1, 32); + __m256i zero = { 0 }; + + /* Collect a partial block at tail end. */ + v |= e[-1]; w |= e[-2]; + SSE_REASSOC_BARRIER(v, w); + v |= e[-3]; w |= e[-4]; + SSE_REASSOC_BARRIER(v, w); + v |= e[-5]; w |= e[-6]; + SSE_REASSOC_BARRIER(v, w); + v |= e[-7]; v |= w; + + /* Loop over complete 256-byte blocks. */ + for (; p < e - 7; p += 8) { + /* PTEST is not profitable here. */ + v = _mm256_cmpeq_epi8(v, zero); + if (unlikely(_mm256_movemask_epi8(v) != 0xFFFFFFFF)) { return false; } - t = p[-4] | p[-3] | p[-2] | p[-1]; - p += 4; - } ; - - /* Finish the last block of 128 unaligned. */ - t |= _mm256_loadu_si256(buf + len - 4 * 32); - t |= _mm256_loadu_si256(buf + len - 3 * 32); - t |= _mm256_loadu_si256(buf + len - 2 * 32); - t |= _mm256_loadu_si256(buf + len - 1 * 32); + v = p[0]; w = p[1]; + SSE_REASSOC_BARRIER(v, w); + v |= p[2]; w |= p[3]; + SSE_REASSOC_BARRIER(v, w); + v |= p[4]; w |= p[5]; + SSE_REASSOC_BARRIER(v, w); + v |= p[6]; w |= p[7]; + SSE_REASSOC_BARRIER(v, w); + v |= w; + } - return _mm256_testz_si256(t, t); + return _mm256_movemask_epi8(_mm256_cmpeq_epi8(v, zero)) == 0xFFFFFFFF; } #endif /* CONFIG_AVX2_OPT */ -#ifdef CONFIG_AVX512F_OPT -static bool __attribute__((target("avx512f"))) -buffer_zero_avx512(const void *buf, size_t len) +static biz_accel_fn const accel_table[] = { + buffer_is_zero_int_ge256, + buffer_zero_sse2, +#ifdef CONFIG_AVX2_OPT + buffer_zero_avx2, +#endif +}; + +static unsigned best_accel(void) { - /* Begin with an unaligned head of 64 bytes. */ - __m512i t = _mm512_loadu_si512(buf); - __m512i *p = (__m512i *)(((uintptr_t)buf + 5 * 64) & -64); - __m512i *e = (__m512i *)(((uintptr_t)buf + len) & -64); - - /* Loop over 64-byte aligned blocks of 256. */ - while (p <= e) { - __builtin_prefetch(p); - if (unlikely(_mm512_test_epi64_mask(t, t))) { - return false; - } - t = p[-4] | p[-3] | p[-2] | p[-1]; - p += 4; + unsigned info = cpuinfo_init(); + +#ifdef CONFIG_AVX2_OPT + if (info & CPUINFO_AVX2) { + return 2; } +#endif + return info & CPUINFO_SSE2 ? 1 : 0; +} - t |= _mm512_loadu_si512(buf + len - 4 * 64); - t |= _mm512_loadu_si512(buf + len - 3 * 64); - t |= _mm512_loadu_si512(buf + len - 2 * 64); - t |= _mm512_loadu_si512(buf + len - 1 * 64); +#elif defined(__aarch64__) && defined(__ARM_NEON) +#include <arm_neon.h> - return !_mm512_test_epi64_mask(t, t); +/* + * Helper for preventing the compiler from reassociating + * chains of binary vector operations. + */ +#define REASSOC_BARRIER(vec0, vec1) asm("" : "+w"(vec0), "+w"(vec1)) + +static bool buffer_is_zero_simd(const void *buf, size_t len) +{ + uint32x4_t t0, t1, t2, t3; + + /* Align head/tail to 16-byte boundaries. */ + const uint32x4_t *p = QEMU_ALIGN_PTR_DOWN(buf + 16, 16); + const uint32x4_t *e = QEMU_ALIGN_PTR_DOWN(buf + len - 1, 16); + + /* Unaligned loads at head/tail. */ + t0 = vld1q_u32(buf) | vld1q_u32(buf + len - 16); + + /* Collect a partial block at tail end. */ + t1 = e[-7] | e[-6]; + t2 = e[-5] | e[-4]; + t3 = e[-3] | e[-2]; + t0 |= e[-1]; + REASSOC_BARRIER(t0, t1); + REASSOC_BARRIER(t2, t3); + t0 |= t1; + t2 |= t3; + REASSOC_BARRIER(t0, t2); + t0 |= t2; + /* + * Loop over complete 128-byte blocks. + * With the head and tail removed, e - p >= 14, so the loop + * must iterate at least once. + */ + do { + /* + * Reduce via UMAXV. Whatever the actual result, + * it will only be zero if all input bytes are zero. + */ + if (unlikely(vmaxvq_u32(t0) != 0)) { + return false; + } + + t0 = p[0] | p[1]; + t1 = p[2] | p[3]; + t2 = p[4] | p[5]; + t3 = p[6] | p[7]; + REASSOC_BARRIER(t0, t1); + REASSOC_BARRIER(t2, t3); + t0 |= t1; + t2 |= t3; + REASSOC_BARRIER(t0, t2); + t0 |= t2; + p += 8; + } while (p < e - 7); + + return vmaxvq_u32(t0) == 0; } -#endif /* CONFIG_AVX512F_OPT */ -/* - * Make sure that these variables are appropriately initialized when - * SSE2 is enabled on the compiler command-line, but the compiler is - * too old to support CONFIG_AVX2_OPT. - */ -#if defined(CONFIG_AVX512F_OPT) || defined(CONFIG_AVX2_OPT) -# define INIT_USED 0 -# define INIT_LENGTH 0 -# define INIT_ACCEL buffer_zero_int +#define best_accel() 1 +static biz_accel_fn const accel_table[] = { + buffer_is_zero_int_ge256, + buffer_is_zero_simd, +}; #else -# ifndef __SSE2__ -# error "ISA selection confusion" -# endif -# define INIT_USED CPUINFO_SSE2 -# define INIT_LENGTH 64 -# define INIT_ACCEL buffer_zero_sse2 +#define best_accel() 0 +static biz_accel_fn const accel_table[1] = { + buffer_is_zero_int_ge256 +}; #endif -static unsigned used_accel = INIT_USED; -static unsigned length_to_accel = INIT_LENGTH; -static bool (*buffer_accel)(const void *, size_t) = INIT_ACCEL; +static biz_accel_fn buffer_is_zero_accel; +static unsigned accel_index; -static unsigned __attribute__((noinline)) -select_accel_cpuinfo(unsigned info) +bool buffer_is_zero_ool(const void *buf, size_t len) { - /* Array is sorted in order of algorithm preference. */ - static const struct { - unsigned bit; - unsigned len; - bool (*fn)(const void *, size_t); - } all[] = { -#ifdef CONFIG_AVX512F_OPT - { CPUINFO_AVX512F, 256, buffer_zero_avx512 }, -#endif -#ifdef CONFIG_AVX2_OPT - { CPUINFO_AVX2, 128, buffer_zero_avx2 }, - { CPUINFO_SSE4, 64, buffer_zero_sse4 }, -#endif - { CPUINFO_SSE2, 64, buffer_zero_sse2 }, - { CPUINFO_ALWAYS, 0, buffer_zero_int }, - }; - - for (unsigned i = 0; i < ARRAY_SIZE(all); ++i) { - if (info & all[i].bit) { - length_to_accel = all[i].len; - buffer_accel = all[i].fn; - return all[i].bit; - } + if (unlikely(len == 0)) { + return true; + } + if (!buffer_is_zero_sample3(buf, len)) { + return false; + } + /* All bytes are covered for any len <= 3. */ + if (unlikely(len <= 3)) { + return true; } - return 0; -} - -#if defined(CONFIG_AVX512F_OPT) || defined(CONFIG_AVX2_OPT) -static void __attribute__((constructor)) init_accel(void) -{ - used_accel = select_accel_cpuinfo(cpuinfo_init()); -} -#endif /* CONFIG_AVX2_OPT */ -bool test_buffer_is_zero_next_accel(void) -{ - /* - * Accumulate the accelerators that we've already tested, and - * remove them from the set to test this round. We'll get back - * a zero from select_accel_cpuinfo when there are no more. - */ - unsigned used = select_accel_cpuinfo(cpuinfo & ~used_accel); - used_accel |= used; - return used; + if (likely(len >= 256)) { + return buffer_is_zero_accel(buf, len); + } + return buffer_is_zero_int_lt256(buf, len); } -static bool select_accel_fn(const void *buf, size_t len) +bool buffer_is_zero_ge256(const void *buf, size_t len) { - if (likely(len >= length_to_accel)) { - return buffer_accel(buf, len); - } - return buffer_zero_int(buf, len); + return buffer_is_zero_accel(buf, len); } -#else -#define select_accel_fn buffer_zero_int bool test_buffer_is_zero_next_accel(void) { + if (accel_index != 0) { + buffer_is_zero_accel = accel_table[--accel_index]; + return true; + } return false; } -#endif -/* - * Checks if a buffer is all zeroes - */ -bool buffer_is_zero(const void *buf, size_t len) +static void __attribute__((constructor)) init_accel(void) { - if (unlikely(len == 0)) { - return true; - } - - /* Fetch the beginning of the buffer while we select the accelerator. */ - __builtin_prefetch(buf); - - /* Use an optimized zero check if possible. Note that this also - includes a check for an unrolled loop over 64-bit integers. */ - return select_accel_fn(buf, len); + accel_index = best_accel(); + buffer_is_zero_accel = accel_table[accel_index]; } diff --git a/util/log.c b/util/log.c index d36c98da0b..6219819855 100644 --- a/util/log.c +++ b/util/log.c @@ -466,6 +466,10 @@ const QEMULogItem qemu_log_items[] = { "show micro ops after optimization" }, { CPU_LOG_TB_OP_IND, "op_ind", "show micro ops before indirect lowering" }, +#ifdef CONFIG_PLUGIN + { LOG_TB_OP_PLUGIN, "op_plugin", + "show micro ops before plugin injection" }, +#endif { CPU_LOG_INT, "int", "show interrupts/exceptions in short format" }, { CPU_LOG_EXEC, "exec", |