mirror of
https://github.com/xemu-project/xemu.git
synced 2026-07-11 01:24:41 +02:00
Merge tag 'pull-10.1-maintainer-june-2025-020725-1' of https://gitlab.com/stsquad/qemu into staging
Maintainer updates for June (gitlab, semihosting, plugins, virtio-gpu)
- mark s390x runner system tests as allow_fail
- build semihosting once
- add register write support to plugins
- add virtual memory write support to plugins
- add harder memory read/write support to plugins
- add patcher plugin and tests
- re-stock virtio-gpu MAINTAINERS
- fix context init for Venus fences
* tag 'pull-10.1-maintainer-june-2025-020725-1' of https://gitlab.com/stsquad/qemu:
virtio-gpu: support context init multiple timeline
MAINTAINERS: add Akihiko and Dmitry as reviewers
MAINTAINERS: add myself to virtio-gpu for Odd Fixes
plugins: Update plugin version and add notes
plugins: Add patcher plugin and test
tests/tcg: Remove copy-pasted notes and from i386 and add x86_64 system tests to tests
plugins: Add memory hardware address read/write API
plugins: Add memory virtual address write API
plugins: Add enforcement of QEMU_PLUGIN_CB flags in register R/W callbacks
plugins: Add register write API
gdbstub: Expose gdb_write_register function to consumers of gdbstub
semihosting/uaccess: Compile once
semihosting/uaccess: Remove uses of target_ulong type
tests/functional: Add PCI hotplug test for aarch64
gitlab: mark s390x-system to allow failures
Conflicts:
tests/functional/meson.build
Context conflict with commit 7bc86ccbb5 ("tests/functional: test
device passthrough on aarch64"), keep both changes to
tests_aarch64_system_thorough[].
This commit is contained in:
@@ -31,7 +31,9 @@ ubuntu-22.04-s390x-all-system:
|
||||
timeout: 75m
|
||||
rules:
|
||||
- if: '$CI_PROJECT_NAMESPACE == "qemu-project" && $CI_COMMIT_BRANCH =~ /^staging/'
|
||||
allow_failure: true
|
||||
- if: "$S390X_RUNNER_AVAILABLE"
|
||||
allow_failure: true
|
||||
script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
|
||||
+10
-1
@@ -2091,6 +2091,12 @@ S: Supported
|
||||
F: include/hw/pci/pcie_doe.h
|
||||
F: hw/pci/pcie_doe.c
|
||||
|
||||
ARM PCI Hotplug
|
||||
M: Gustavo Romero <gustavo.romero@linaro.org>
|
||||
L: qemu-arm@nongnu.org
|
||||
S: Supported
|
||||
F: tests/functional/test_aarch64_hotplug_pci.py
|
||||
|
||||
ACPI/SMBIOS
|
||||
M: Michael S. Tsirkin <mst@redhat.com>
|
||||
M: Igor Mammedov <imammedo@redhat.com>
|
||||
@@ -2680,7 +2686,10 @@ F: hw/display/ramfb*.c
|
||||
F: include/hw/display/ramfb.h
|
||||
|
||||
virtio-gpu
|
||||
S: Orphan
|
||||
M: Alex Bennée <alex.bennee@linaro.org>
|
||||
R: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
|
||||
R: Dmitry Osipenko <dmitry.osipenko@collabora.com>
|
||||
S: Odd Fixes
|
||||
F: hw/display/virtio-gpu*
|
||||
F: hw/display/virtio-vga.*
|
||||
F: include/hw/virtio/virtio-gpu.h
|
||||
|
||||
@@ -117,10 +117,20 @@ static TCGv_i32 gen_cpu_index(void)
|
||||
static void gen_udata_cb(struct qemu_plugin_regular_cb *cb)
|
||||
{
|
||||
TCGv_i32 cpu_index = gen_cpu_index();
|
||||
enum qemu_plugin_cb_flags cb_flags =
|
||||
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
|
||||
TCGv_i32 flags = tcg_constant_i32(cb_flags);
|
||||
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
|
||||
tcg_gen_st_i32(flags, tcg_env,
|
||||
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
|
||||
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
|
||||
tcgv_i32_temp(cpu_index),
|
||||
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
|
||||
tcg_gen_st_i32(clear_flags, tcg_env,
|
||||
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
|
||||
tcg_temp_free_i32(cpu_index);
|
||||
tcg_temp_free_i32(flags);
|
||||
tcg_temp_free_i32(clear_flags);
|
||||
}
|
||||
|
||||
static TCGv_ptr gen_plugin_u64_ptr(qemu_plugin_u64 entry)
|
||||
@@ -173,10 +183,20 @@ static void gen_udata_cond_cb(struct qemu_plugin_conditional_cb *cb)
|
||||
tcg_gen_ld_i64(val, ptr, 0);
|
||||
tcg_gen_brcondi_i64(cond, val, cb->imm, after_cb);
|
||||
TCGv_i32 cpu_index = gen_cpu_index();
|
||||
enum qemu_plugin_cb_flags cb_flags =
|
||||
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
|
||||
TCGv_i32 flags = tcg_constant_i32(cb_flags);
|
||||
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
|
||||
tcg_gen_st_i32(flags, tcg_env,
|
||||
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
|
||||
tcg_gen_call2(cb->f.vcpu_udata, cb->info, NULL,
|
||||
tcgv_i32_temp(cpu_index),
|
||||
tcgv_ptr_temp(tcg_constant_ptr(cb->userp)));
|
||||
tcg_gen_st_i32(clear_flags, tcg_env,
|
||||
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
|
||||
tcg_temp_free_i32(cpu_index);
|
||||
tcg_temp_free_i32(flags);
|
||||
tcg_temp_free_i32(clear_flags);
|
||||
gen_set_label(after_cb);
|
||||
|
||||
tcg_temp_free_i64(val);
|
||||
@@ -210,12 +230,22 @@ static void gen_mem_cb(struct qemu_plugin_regular_cb *cb,
|
||||
qemu_plugin_meminfo_t meminfo, TCGv_i64 addr)
|
||||
{
|
||||
TCGv_i32 cpu_index = gen_cpu_index();
|
||||
enum qemu_plugin_cb_flags cb_flags =
|
||||
tcg_call_to_qemu_plugin_cb_flags(cb->info->flags);
|
||||
TCGv_i32 flags = tcg_constant_i32(cb_flags);
|
||||
TCGv_i32 clear_flags = tcg_constant_i32(QEMU_PLUGIN_CB_NO_REGS);
|
||||
tcg_gen_st_i32(flags, tcg_env,
|
||||
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
|
||||
tcg_gen_call4(cb->f.vcpu_mem, cb->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_gen_st_i32(clear_flags, tcg_env,
|
||||
offsetof(CPUState, neg.plugin_cb_flags) - sizeof(CPUState));
|
||||
tcg_temp_free_i32(cpu_index);
|
||||
tcg_temp_free_i32(flags);
|
||||
tcg_temp_free_i32(clear_flags);
|
||||
}
|
||||
|
||||
static void inject_cb(struct qemu_plugin_dyn_cb *cb)
|
||||
|
||||
+1
-1
@@ -535,7 +535,7 @@ int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg)
|
||||
int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg)
|
||||
{
|
||||
GDBRegisterState *r;
|
||||
|
||||
|
||||
@@ -970,6 +970,15 @@ void virtio_gpu_virgl_process_cmd(VirtIOGPU *g,
|
||||
}
|
||||
|
||||
trace_virtio_gpu_fence_ctrl(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
|
||||
#if VIRGL_VERSION_MAJOR >= 1
|
||||
if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX) {
|
||||
virgl_renderer_context_create_fence(cmd->cmd_hdr.ctx_id,
|
||||
VIRGL_RENDERER_FENCE_FLAG_MERGEABLE,
|
||||
cmd->cmd_hdr.ring_idx,
|
||||
cmd->cmd_hdr.fence_id);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
virgl_renderer_create_fence(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
|
||||
}
|
||||
|
||||
@@ -983,6 +992,11 @@ static void virgl_write_fence(void *opaque, uint32_t fence)
|
||||
* the guest can end up emitting fences out of order
|
||||
* so we should check all fenced cmds not just the first one.
|
||||
*/
|
||||
#if VIRGL_VERSION_MAJOR >= 1
|
||||
if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (cmd->cmd_hdr.fence_id > fence) {
|
||||
continue;
|
||||
}
|
||||
@@ -997,6 +1011,29 @@ static void virgl_write_fence(void *opaque, uint32_t fence)
|
||||
}
|
||||
}
|
||||
|
||||
#if VIRGL_VERSION_MAJOR >= 1
|
||||
static void virgl_write_context_fence(void *opaque, uint32_t ctx_id,
|
||||
uint32_t ring_idx, uint64_t fence_id) {
|
||||
VirtIOGPU *g = opaque;
|
||||
struct virtio_gpu_ctrl_command *cmd, *tmp;
|
||||
|
||||
QTAILQ_FOREACH_SAFE(cmd, &g->fenceq, next, tmp) {
|
||||
if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX &&
|
||||
cmd->cmd_hdr.ctx_id == ctx_id && cmd->cmd_hdr.ring_idx == ring_idx &&
|
||||
cmd->cmd_hdr.fence_id <= fence_id) {
|
||||
trace_virtio_gpu_fence_resp(cmd->cmd_hdr.fence_id);
|
||||
virtio_gpu_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA);
|
||||
QTAILQ_REMOVE(&g->fenceq, cmd, next);
|
||||
g_free(cmd);
|
||||
g->inflight--;
|
||||
if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
|
||||
trace_virtio_gpu_dec_inflight_fences(g->inflight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static virgl_renderer_gl_context
|
||||
virgl_create_context(void *opaque, int scanout_idx,
|
||||
struct virgl_renderer_gl_ctx_param *params)
|
||||
@@ -1031,11 +1068,18 @@ static int virgl_make_context_current(void *opaque, int scanout_idx,
|
||||
}
|
||||
|
||||
static struct virgl_renderer_callbacks virtio_gpu_3d_cbs = {
|
||||
#if VIRGL_VERSION_MAJOR >= 1
|
||||
.version = 3,
|
||||
#else
|
||||
.version = 1,
|
||||
#endif
|
||||
.write_fence = virgl_write_fence,
|
||||
.create_gl_context = virgl_create_context,
|
||||
.destroy_gl_context = virgl_destroy_context,
|
||||
.make_current = virgl_make_context_current,
|
||||
#if VIRGL_VERSION_MAJOR >= 1
|
||||
.write_context_fence = virgl_write_context_fence,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void virtio_gpu_print_stats(void *opaque)
|
||||
|
||||
@@ -124,6 +124,20 @@ const GDBFeature *gdb_find_static_feature(const char *xmlname);
|
||||
*/
|
||||
int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg);
|
||||
|
||||
/**
|
||||
* gdb_write_register() - Write a register associated with a CPU.
|
||||
* @cpu: The CPU associated with the register.
|
||||
* @buf: The buffer that the register contents will be set to.
|
||||
* @reg: The register's number returned by gdb_find_feature_register().
|
||||
*
|
||||
* The size of @buf must be at least the size of the register being
|
||||
* written.
|
||||
*
|
||||
* Return: The number of written bytes, or 0 if an error occurred (for
|
||||
* example, an unknown register was provided).
|
||||
*/
|
||||
int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg);
|
||||
|
||||
/**
|
||||
* typedef GDBRegDesc - a register description from gdbstub
|
||||
*/
|
||||
|
||||
@@ -368,6 +368,7 @@ typedef struct CPUNegativeOffsetState {
|
||||
GArray *plugin_mem_cbs;
|
||||
uint64_t plugin_mem_value_low;
|
||||
uint64_t plugin_mem_value_high;
|
||||
int32_t plugin_cb_flags;
|
||||
#endif
|
||||
IcountDecr icount_decr;
|
||||
bool can_do_io;
|
||||
|
||||
@@ -209,6 +209,21 @@ void qemu_plugin_user_prefork_lock(void);
|
||||
*/
|
||||
void qemu_plugin_user_postfork(bool is_child);
|
||||
|
||||
enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags);
|
||||
|
||||
static inline void qemu_plugin_set_cb_flags(CPUState *cpu,
|
||||
enum qemu_plugin_cb_flags flags)
|
||||
{
|
||||
assert(cpu);
|
||||
cpu->neg.plugin_cb_flags = flags;
|
||||
}
|
||||
|
||||
static inline enum qemu_plugin_cb_flags qemu_plugin_get_cb_flags(void)
|
||||
{
|
||||
assert(current_cpu);
|
||||
return current_cpu->neg.plugin_cb_flags;
|
||||
}
|
||||
|
||||
#else /* !CONFIG_PLUGIN */
|
||||
|
||||
static inline void qemu_plugin_add_opts(void)
|
||||
|
||||
+162
-14
@@ -65,11 +65,18 @@ typedef uint64_t qemu_plugin_id_t;
|
||||
*
|
||||
* version 4:
|
||||
* - added qemu_plugin_read_memory_vaddr
|
||||
*
|
||||
* version 5:
|
||||
* - added qemu_plugin_write_memory_vaddr
|
||||
* - added qemu_plugin_read_memory_hwaddr
|
||||
* - added qemu_plugin_write_memory_hwaddr
|
||||
* - added qemu_plugin_write_register
|
||||
* - added qemu_plugin_translate_vaddr
|
||||
*/
|
||||
|
||||
extern QEMU_PLUGIN_EXPORT int qemu_plugin_version;
|
||||
|
||||
#define QEMU_PLUGIN_VERSION 4
|
||||
#define QEMU_PLUGIN_VERSION 5
|
||||
|
||||
/**
|
||||
* struct qemu_info_t - system information for plugins
|
||||
@@ -254,9 +261,6 @@ typedef struct {
|
||||
* @QEMU_PLUGIN_CB_NO_REGS: callback does not access the CPU's regs
|
||||
* @QEMU_PLUGIN_CB_R_REGS: callback reads the CPU's regs
|
||||
* @QEMU_PLUGIN_CB_RW_REGS: callback reads and writes the CPU's regs
|
||||
*
|
||||
* Note: currently QEMU_PLUGIN_CB_RW_REGS is unused, plugins cannot change
|
||||
* system register state.
|
||||
*/
|
||||
enum qemu_plugin_cb_flags {
|
||||
QEMU_PLUGIN_CB_NO_REGS,
|
||||
@@ -871,7 +875,8 @@ struct qemu_plugin_register;
|
||||
/**
|
||||
* typedef qemu_plugin_reg_descriptor - register descriptions
|
||||
*
|
||||
* @handle: opaque handle for retrieving value with qemu_plugin_read_register
|
||||
* @handle: opaque handle for retrieving value with qemu_plugin_read_register or
|
||||
* writing value with qemu_plugin_write_register
|
||||
* @name: register name
|
||||
* @feature: optional feature descriptor, can be NULL
|
||||
*/
|
||||
@@ -893,6 +898,51 @@ typedef struct {
|
||||
QEMU_PLUGIN_API
|
||||
GArray *qemu_plugin_get_registers(void);
|
||||
|
||||
/**
|
||||
* qemu_plugin_read_register() - read register for current vCPU
|
||||
*
|
||||
* @handle: a @qemu_plugin_reg_handle handle
|
||||
* @buf: A GByteArray for the data owned by the plugin
|
||||
*
|
||||
* This function is only available in a context that register read access is
|
||||
* explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag, if called inside a
|
||||
* callback that can be registered with a qemu_plugin_cb_flags argument. This
|
||||
* function can also be used in any callback context that does not use a flags
|
||||
* argument, such as in a callback registered with
|
||||
* qemu_plugin_register_vcpu_init_cb(), except for callbacks registered with
|
||||
* qemu_plugin_register_atexit_cb() and qemu_plugin_register_flush_cb().
|
||||
*
|
||||
* Returns the size of the read register. The content of @buf is in target byte
|
||||
* order. On failure returns -1.
|
||||
*/
|
||||
QEMU_PLUGIN_API
|
||||
int qemu_plugin_read_register(struct qemu_plugin_register *handle,
|
||||
GByteArray *buf);
|
||||
|
||||
/**
|
||||
* qemu_plugin_write_register() - write register for current vCPU
|
||||
*
|
||||
* @handle: a @qemu_plugin_reg_handle handle
|
||||
* @buf: A GByteArray for the data owned by the plugin
|
||||
*
|
||||
* This function is only available in a context that register read access is
|
||||
* explicitly requested via the QEMU_PLUGIN_CB_RW_REGS flag, if called inside a
|
||||
* callback that can be registered with a qemu_plugin_cb_flags argument. This
|
||||
* function can also be used in any callback context that does not use a flags
|
||||
* argument, such as in a callback registered with
|
||||
* qemu_plugin_register_vcpu_init_cb(), except for callbacks registered with
|
||||
* qemu_plugin_register_atexit_cb() and qemu_plugin_register_flush_cb().
|
||||
*
|
||||
* The size of @buf must be at least the size of the requested register.
|
||||
* Attempting to write a register with @buf smaller than the register size
|
||||
* will result in a crash or other undesired behavior.
|
||||
*
|
||||
* Returns the number of bytes written. On failure returns 0.
|
||||
*/
|
||||
QEMU_PLUGIN_API
|
||||
int qemu_plugin_write_register(struct qemu_plugin_register *handle,
|
||||
GByteArray *buf);
|
||||
|
||||
/**
|
||||
* qemu_plugin_read_memory_vaddr() - read from memory using a virtual address
|
||||
*
|
||||
@@ -916,20 +966,118 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr,
|
||||
GByteArray *data, size_t len);
|
||||
|
||||
/**
|
||||
* qemu_plugin_read_register() - read register for current vCPU
|
||||
* qemu_plugin_write_memory_vaddr() - write to memory using a virtual address
|
||||
*
|
||||
* @handle: a @qemu_plugin_reg_handle handle
|
||||
* @buf: A GByteArray for the data owned by the plugin
|
||||
* @addr: A virtual address to write to
|
||||
* @data: A byte array containing the data to write
|
||||
*
|
||||
* This function is only available in a context that register read access is
|
||||
* explicitly requested via the QEMU_PLUGIN_CB_R_REGS flag.
|
||||
* The contents of @data will be written to memory starting at the virtual
|
||||
* address @addr.
|
||||
*
|
||||
* Returns the size of the read register. The content of @buf is in target byte
|
||||
* order. On failure returns -1.
|
||||
* This function does not guarantee consistency of writes, nor does it ensure
|
||||
* that pending writes are flushed either before or after the write takes place,
|
||||
* so callers should take care to only call this function in vCPU context (i.e.
|
||||
* in callbacks) and avoid depending on the existence of data written using this
|
||||
* function which may be overwritten afterward.
|
||||
*
|
||||
* Returns true on success and false on failure.
|
||||
*/
|
||||
QEMU_PLUGIN_API
|
||||
int qemu_plugin_read_register(struct qemu_plugin_register *handle,
|
||||
GByteArray *buf);
|
||||
bool qemu_plugin_write_memory_vaddr(uint64_t addr,
|
||||
GByteArray *data);
|
||||
|
||||
/**
|
||||
* enum qemu_plugin_hwaddr_operation_result - result of a memory operation
|
||||
*
|
||||
* @QEMU_PLUGIN_HWADDR_OPERATION_OK: hwaddr operation succeeded
|
||||
* @QEMU_PLUGIN_HWADDR_OPERATION_ERROR: unexpected error occurred
|
||||
* @QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR: error in memory device
|
||||
* @QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED: permission error
|
||||
* @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS: address was invalid
|
||||
* @QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE: invalid address space
|
||||
*/
|
||||
enum qemu_plugin_hwaddr_operation_result {
|
||||
QEMU_PLUGIN_HWADDR_OPERATION_OK,
|
||||
QEMU_PLUGIN_HWADDR_OPERATION_ERROR,
|
||||
QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR,
|
||||
QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED,
|
||||
QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS,
|
||||
QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE,
|
||||
};
|
||||
|
||||
/**
|
||||
* qemu_plugin_read_memory_hwaddr() - read from memory using a hardware address
|
||||
*
|
||||
* @addr: The physical address to read from
|
||||
* @data: A byte array to store data into
|
||||
* @len: The number of bytes to read, starting from @addr
|
||||
*
|
||||
* @len bytes of data is read from the current memory space for the current
|
||||
* vCPU starting at @addr and stored into @data. If @data is not large enough to
|
||||
* hold @len bytes, it will be expanded to the necessary size, reallocating if
|
||||
* necessary. @len must be greater than 0.
|
||||
*
|
||||
* This function does not ensure writes are flushed prior to reading, so
|
||||
* callers should take care when calling this function in plugin callbacks to
|
||||
* avoid attempting to read data which may not yet be written and should use
|
||||
* the memory callback API instead.
|
||||
*
|
||||
* This function is only valid for softmmu targets.
|
||||
*
|
||||
* Returns a qemu_plugin_hwaddr_operation_result indicating the result of the
|
||||
* operation.
|
||||
*/
|
||||
QEMU_PLUGIN_API
|
||||
enum qemu_plugin_hwaddr_operation_result
|
||||
qemu_plugin_read_memory_hwaddr(uint64_t addr, GByteArray *data, size_t len);
|
||||
|
||||
/**
|
||||
* qemu_plugin_write_memory_hwaddr() - write to memory using a hardware address
|
||||
*
|
||||
* @addr: A physical address to write to
|
||||
* @data: A byte array containing the data to write
|
||||
*
|
||||
* The contents of @data will be written to memory starting at the hardware
|
||||
* address @addr in the current address space for the current vCPU.
|
||||
*
|
||||
* This function does not guarantee consistency of writes, nor does it ensure
|
||||
* that pending writes are flushed either before or after the write takes place,
|
||||
* so callers should take care when calling this function in plugin callbacks to
|
||||
* avoid depending on the existence of data written using this function which
|
||||
* may be overwritten afterward. In addition, this function requires that the
|
||||
* pages containing the address are not locked. Practically, this means that you
|
||||
* should not write instruction memory in a current translation block inside a
|
||||
* callback registered with qemu_plugin_register_vcpu_tb_trans_cb.
|
||||
*
|
||||
* You can, for example, write instruction memory in a current translation block
|
||||
* in a callback registered with qemu_plugin_register_vcpu_tb_exec_cb, although
|
||||
* be aware that the write will not be flushed until after the translation block
|
||||
* has finished executing. In general, this function should be used to write
|
||||
* data memory or to patch code at a known address, not in a current translation
|
||||
* block.
|
||||
*
|
||||
* This function is only valid for softmmu targets.
|
||||
*
|
||||
* Returns a qemu_plugin_hwaddr_operation_result indicating the result of the
|
||||
* operation.
|
||||
*/
|
||||
QEMU_PLUGIN_API
|
||||
enum qemu_plugin_hwaddr_operation_result
|
||||
qemu_plugin_write_memory_hwaddr(uint64_t addr, GByteArray *data);
|
||||
|
||||
/**
|
||||
* qemu_plugin_translate_vaddr() - translate virtual address for current vCPU
|
||||
*
|
||||
* @vaddr: virtual address to translate
|
||||
* @hwaddr: pointer to store the physical address
|
||||
*
|
||||
* This function is only valid in vCPU context (i.e. in callbacks) and is only
|
||||
* valid for softmmu targets.
|
||||
*
|
||||
* Returns true on success and false on failure.
|
||||
*/
|
||||
QEMU_PLUGIN_API
|
||||
bool qemu_plugin_translate_vaddr(uint64_t vaddr, uint64_t *hwaddr);
|
||||
|
||||
/**
|
||||
* qemu_plugin_scoreboard_new() - alloc a new scoreboard
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
#endif
|
||||
|
||||
#include "exec/cpu-common.h"
|
||||
#include "exec/cpu-defs.h"
|
||||
#include "exec/tswap.h"
|
||||
#include "exec/page-protection.h"
|
||||
#include "exec/vaddr.h"
|
||||
|
||||
/**
|
||||
* get_user_u64:
|
||||
@@ -89,8 +89,8 @@
|
||||
*
|
||||
* The returned pointer should be freed using uaccess_unlock_user().
|
||||
*/
|
||||
void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
|
||||
target_ulong len, bool copy);
|
||||
void *uaccess_lock_user(CPUArchState *env, vaddr addr,
|
||||
size_t len, bool copy);
|
||||
/**
|
||||
* lock_user:
|
||||
*
|
||||
@@ -103,7 +103,7 @@ void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
|
||||
*
|
||||
* The returned string should be freed using uaccess_unlock_user().
|
||||
*/
|
||||
char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr);
|
||||
char *uaccess_lock_user_string(CPUArchState *env, vaddr addr);
|
||||
/**
|
||||
* uaccess_lock_user_string:
|
||||
*
|
||||
@@ -112,10 +112,10 @@ char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr);
|
||||
#define lock_user_string(p) uaccess_lock_user_string(env, p)
|
||||
|
||||
void uaccess_unlock_user(CPUArchState *env, void *p,
|
||||
target_ulong addr, target_ulong len);
|
||||
vaddr addr, size_t len);
|
||||
#define unlock_user(s, args, len) uaccess_unlock_user(env, s, args, len)
|
||||
|
||||
ssize_t uaccess_strlen_user(CPUArchState *env, target_ulong addr);
|
||||
ssize_t uaccess_strlen_user(CPUArchState *env, vaddr addr);
|
||||
#define target_strlen(p) uaccess_strlen_user(env, p)
|
||||
|
||||
#endif /* SEMIHOSTING_SOFTMMU_UACCESS_H */
|
||||
|
||||
+133
-2
@@ -39,6 +39,7 @@
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "qemu/log.h"
|
||||
#include "system/memory.h"
|
||||
#include "tcg/tcg.h"
|
||||
#include "exec/gdbstub.h"
|
||||
#include "exec/target_page.h"
|
||||
@@ -433,6 +434,29 @@ GArray *qemu_plugin_get_registers(void)
|
||||
return create_register_handles(regs);
|
||||
}
|
||||
|
||||
int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
if (qemu_plugin_get_cb_flags() == QEMU_PLUGIN_CB_NO_REGS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1);
|
||||
}
|
||||
|
||||
int qemu_plugin_write_register(struct qemu_plugin_register *reg,
|
||||
GByteArray *buf)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1);
|
||||
}
|
||||
|
||||
bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
@@ -453,11 +477,118 @@ bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len)
|
||||
return true;
|
||||
}
|
||||
|
||||
int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf)
|
||||
bool qemu_plugin_write_memory_vaddr(uint64_t addr, GByteArray *data)
|
||||
{
|
||||
g_assert(current_cpu);
|
||||
|
||||
return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1);
|
||||
if (data->len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = cpu_memory_rw_debug(current_cpu, addr, data->data,
|
||||
data->len, true);
|
||||
|
||||
if (result < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum qemu_plugin_hwaddr_operation_result
|
||||
qemu_plugin_read_memory_hwaddr(hwaddr addr, GByteArray *data, size_t len)
|
||||
{
|
||||
#ifdef CONFIG_SOFTMMU
|
||||
if (len == 0) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
|
||||
g_assert(current_cpu);
|
||||
|
||||
|
||||
int as_idx = cpu_asidx_from_attrs(current_cpu, MEMTXATTRS_UNSPECIFIED);
|
||||
AddressSpace *as = cpu_get_address_space(current_cpu, as_idx);
|
||||
|
||||
if (as == NULL) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE;
|
||||
}
|
||||
|
||||
g_byte_array_set_size(data, len);
|
||||
MemTxResult res = address_space_rw(as, addr,
|
||||
MEMTXATTRS_UNSPECIFIED, data->data,
|
||||
data->len, false);
|
||||
|
||||
switch (res) {
|
||||
case MEMTX_OK:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_OK;
|
||||
case MEMTX_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR;
|
||||
case MEMTX_DECODE_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS;
|
||||
case MEMTX_ACCESS_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED;
|
||||
default:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
#else
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
enum qemu_plugin_hwaddr_operation_result
|
||||
qemu_plugin_write_memory_hwaddr(hwaddr addr, GByteArray *data)
|
||||
{
|
||||
#ifdef CONFIG_SOFTMMU
|
||||
if (data->len == 0) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
|
||||
g_assert(current_cpu);
|
||||
|
||||
int as_idx = cpu_asidx_from_attrs(current_cpu, MEMTXATTRS_UNSPECIFIED);
|
||||
AddressSpace *as = cpu_get_address_space(current_cpu, as_idx);
|
||||
|
||||
if (as == NULL) {
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE;
|
||||
}
|
||||
|
||||
MemTxResult res = address_space_rw(as, addr,
|
||||
MEMTXATTRS_UNSPECIFIED, data->data,
|
||||
data->len, true);
|
||||
switch (res) {
|
||||
case MEMTX_OK:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_OK;
|
||||
case MEMTX_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR;
|
||||
case MEMTX_DECODE_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS;
|
||||
case MEMTX_ACCESS_ERROR:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED;
|
||||
default:
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
}
|
||||
#else
|
||||
return QEMU_PLUGIN_HWADDR_OPERATION_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool qemu_plugin_translate_vaddr(uint64_t vaddr, uint64_t *hwaddr)
|
||||
{
|
||||
#ifdef CONFIG_SOFTMMU
|
||||
g_assert(current_cpu);
|
||||
|
||||
uint64_t res = cpu_get_phys_page_debug(current_cpu, vaddr);
|
||||
|
||||
if (res == (uint64_t)-1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*hwaddr = res | (vaddr & ~TARGET_PAGE_MASK);
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "qemu/lockable.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/plugin.h"
|
||||
#include "qemu/qemu-plugin.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "qemu/rcu.h"
|
||||
@@ -266,7 +267,9 @@ static void qemu_plugin_vcpu_init__async(CPUState *cpu, run_on_cpu_data unused)
|
||||
plugin_grow_scoreboards__locked(cpu);
|
||||
qemu_rec_mutex_unlock(&plugin.lock);
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_INIT);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_init_hook(CPUState *cpu)
|
||||
@@ -279,7 +282,9 @@ void qemu_plugin_vcpu_exit_hook(CPUState *cpu)
|
||||
{
|
||||
bool success;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_EXIT);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
|
||||
assert(cpu->cpu_index != UNASSIGNED_CPU_INDEX);
|
||||
qemu_rec_mutex_lock(&plugin.lock);
|
||||
@@ -367,6 +372,7 @@ void plugin_register_dyn_cb__udata(GArray **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,
|
||||
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
|
||||
/*
|
||||
* Match qemu_plugin_vcpu_udata_cb_t:
|
||||
* void (*)(uint32_t, void *)
|
||||
@@ -396,6 +402,7 @@ void plugin_register_dyn_cond_cb__udata(GArray **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,
|
||||
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
|
||||
/*
|
||||
* Match qemu_plugin_vcpu_udata_cb_t:
|
||||
* void (*)(uint32_t, void *)
|
||||
@@ -434,6 +441,7 @@ void plugin_register_vcpu_mem_cb(GArray **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,
|
||||
[QEMU_PLUGIN_CB_RW_REGS].flags = 0,
|
||||
/*
|
||||
* Match qemu_plugin_vcpu_mem_cb_t:
|
||||
* void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *)
|
||||
@@ -473,7 +481,9 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb)
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_tb_trans_cb_t func = cb->f.vcpu_tb_trans;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
func(cb->ctx->id, tb);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +508,9 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2,
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +532,9 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret)
|
||||
QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) {
|
||||
qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret;
|
||||
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
func(cb->ctx->id, cpu->cpu_index, num, ret);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,14 +542,18 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu)
|
||||
{
|
||||
/* idle and resume cb may be called before init, ignore in this case */
|
||||
if (cpu->cpu_index < plugin.num_vcpus) {
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_IDLE);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_plugin_vcpu_resume_cb(CPUState *cpu)
|
||||
{
|
||||
if (cpu->cpu_index < plugin.num_vcpus) {
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS);
|
||||
plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,9 +633,13 @@ void qemu_plugin_vcpu_mem_cb(CPUState *cpu, uint64_t vaddr,
|
||||
switch (cb->type) {
|
||||
case PLUGIN_CB_MEM_REGULAR:
|
||||
if (rw & cb->regular.rw) {
|
||||
qemu_plugin_set_cb_flags(cpu,
|
||||
tcg_call_to_qemu_plugin_cb_flags(cb->regular.info->flags));
|
||||
|
||||
cb->regular.f.vcpu_mem(cpu->cpu_index,
|
||||
make_plugin_meminfo(oi, rw),
|
||||
vaddr, cb->regular.userp);
|
||||
qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS);
|
||||
}
|
||||
break;
|
||||
case PLUGIN_CB_INLINE_ADD_U64:
|
||||
@@ -760,3 +782,14 @@ void plugin_scoreboard_free(struct qemu_plugin_scoreboard *score)
|
||||
g_array_free(score->data, TRUE);
|
||||
g_free(score);
|
||||
}
|
||||
|
||||
enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags)
|
||||
{
|
||||
if (flags & TCG_CALL_NO_RWG) {
|
||||
return QEMU_PLUGIN_CB_NO_REGS;
|
||||
} else if (flags & TCG_CALL_NO_WG) {
|
||||
return QEMU_PLUGIN_CB_R_REGS;
|
||||
} else {
|
||||
return QEMU_PLUGIN_CB_RW_REGS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,12 @@ specific_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files(
|
||||
'syscalls.c',
|
||||
))
|
||||
|
||||
specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_SYSTEM_ONLY'], if_true: files(
|
||||
'uaccess.c',
|
||||
))
|
||||
|
||||
common_ss.add(when: 'CONFIG_SEMIHOSTING', if_false: files('stubs-all.c'))
|
||||
user_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files('user.c'))
|
||||
system_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files(
|
||||
'config.c',
|
||||
'console.c',
|
||||
'uaccess.c',
|
||||
), if_false: files(
|
||||
'stubs-system.c',
|
||||
))
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
#include "exec/tlb-flags.h"
|
||||
#include "semihosting/uaccess.h"
|
||||
|
||||
void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
|
||||
target_ulong len, bool copy)
|
||||
void *uaccess_lock_user(CPUArchState *env, vaddr addr,
|
||||
size_t len, bool copy)
|
||||
{
|
||||
void *p = malloc(len);
|
||||
if (p && copy) {
|
||||
@@ -27,7 +27,7 @@ void *uaccess_lock_user(CPUArchState *env, target_ulong addr,
|
||||
return p;
|
||||
}
|
||||
|
||||
ssize_t uaccess_strlen_user(CPUArchState *env, target_ulong addr)
|
||||
ssize_t uaccess_strlen_user(CPUArchState *env, vaddr addr)
|
||||
{
|
||||
int mmu_idx = cpu_mmu_index(env_cpu(env), false);
|
||||
size_t len = 0;
|
||||
@@ -75,7 +75,7 @@ ssize_t uaccess_strlen_user(CPUArchState *env, target_ulong addr)
|
||||
}
|
||||
}
|
||||
|
||||
char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr)
|
||||
char *uaccess_lock_user_string(CPUArchState *env, vaddr addr)
|
||||
{
|
||||
ssize_t len = uaccess_strlen_user(env, addr);
|
||||
if (len < 0) {
|
||||
@@ -85,7 +85,7 @@ char *uaccess_lock_user_string(CPUArchState *env, target_ulong addr)
|
||||
}
|
||||
|
||||
void uaccess_unlock_user(CPUArchState *env, void *p,
|
||||
target_ulong addr, target_ulong len)
|
||||
vaddr addr, size_t len)
|
||||
{
|
||||
if (len) {
|
||||
cpu_memory_rw_debug(env_cpu(env), addr, p, len, 1);
|
||||
|
||||
@@ -85,6 +85,7 @@ tests_aarch64_system_thorough = [
|
||||
'aarch64_aspeed_ast2700',
|
||||
'aarch64_aspeed_ast2700fc',
|
||||
'aarch64_device_passthrough',
|
||||
'aarch64_hotplug_pci',
|
||||
'aarch64_imx8mp_evk',
|
||||
'aarch64_raspi3',
|
||||
'aarch64_raspi4',
|
||||
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# The test hotplugs a PCI device and checks it on a Linux guest.
|
||||
#
|
||||
# Copyright (c) 2025 Linaro Ltd.
|
||||
#
|
||||
# Author:
|
||||
# Gustavo Romero <gustavo.romero@linaro.org>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from qemu_test import LinuxKernelTest, Asset, exec_command_and_wait_for_pattern
|
||||
from qemu_test import BUILD_DIR
|
||||
|
||||
class HotplugPCI(LinuxKernelTest):
|
||||
|
||||
ASSET_KERNEL = Asset(
|
||||
('https://ftp.debian.org/debian/dists/stable/main/installer-arm64/'
|
||||
'20230607+deb12u11/images/netboot/debian-installer/arm64/linux'),
|
||||
'd92a60392ce1e379ca198a1a820899f8f0d39a62d047c41ab79492f81541a9d9')
|
||||
|
||||
ASSET_INITRD = Asset(
|
||||
('https://ftp.debian.org/debian/dists/stable/main/installer-arm64/'
|
||||
'20230607+deb12u11/images/netboot/debian-installer/arm64/initrd.gz'),
|
||||
'9f817f76951f3237bca8216bee35267bfb826815687f4b2fcdd5e6c2a917790c')
|
||||
|
||||
def test_hotplug_pci(self):
|
||||
|
||||
self.set_machine('virt')
|
||||
|
||||
self.vm.add_args('-m', '512M',
|
||||
'-cpu', 'cortex-a57',
|
||||
'-append',
|
||||
'console=ttyAMA0,115200 init=/bin/sh',
|
||||
'-device',
|
||||
'pcie-root-port,bus=pcie.0,chassis=1,slot=1,id=pcie.1',
|
||||
'-bios',
|
||||
self.build_file('pc-bios', 'edk2-aarch64-code.fd'))
|
||||
|
||||
# BusyBox prompt
|
||||
prompt = "~ #"
|
||||
self.launch_kernel(self.ASSET_KERNEL.fetch(),
|
||||
self.ASSET_INITRD.fetch(),
|
||||
wait_for=prompt)
|
||||
|
||||
# Check for initial state: 2 network adapters, lo and enp0s1.
|
||||
exec_command_and_wait_for_pattern(self,
|
||||
'ls /sys/class/net | wc -l',
|
||||
'2')
|
||||
|
||||
# Hotplug one network adapter to the root port, i.e. pcie.1 bus.
|
||||
self.vm.cmd('device_add',
|
||||
driver='virtio-net-pci',
|
||||
bus='pcie.1',
|
||||
addr=0,
|
||||
id='na')
|
||||
# Wait for the kernel to recognize the new device.
|
||||
self.wait_for_console_pattern('virtio-pci')
|
||||
self.wait_for_console_pattern('virtio_net')
|
||||
|
||||
# Check if there is a new network adapter.
|
||||
exec_command_and_wait_for_pattern(self,
|
||||
'ls /sys/class/net | wc -l',
|
||||
'3')
|
||||
|
||||
self.vm.cmd('device_del', id='na')
|
||||
exec_command_and_wait_for_pattern(self,
|
||||
'ls /sys/class/net | wc -l',
|
||||
'2')
|
||||
|
||||
if __name__ == '__main__':
|
||||
LinuxKernelTest.main()
|
||||
@@ -151,7 +151,12 @@ ifeq ($(CONFIG_PLUGIN),y)
|
||||
PLUGIN_SRC=$(SRC_PATH)/tests/tcg/plugins
|
||||
PLUGIN_LIB=../plugins
|
||||
VPATH+=$(PLUGIN_LIB)
|
||||
PLUGINS=$(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c)))
|
||||
# Some plugins need to be disabled for all tests to avoid exponential explosion.
|
||||
# For example, libpatch.so only needs to run against the arch-specific patch
|
||||
# target test, so we explicitly run it in the arch-specific Makefile.
|
||||
DISABLE_PLUGINS=libpatch.so
|
||||
PLUGINS=$(filter-out $(DISABLE_PLUGINS), \
|
||||
$(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c))))
|
||||
|
||||
# We need to ensure expand the run-plugin-TEST-with-PLUGIN
|
||||
# pre-requistes manually here as we can't use stems to handle it. We
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
t = []
|
||||
if get_option('plugins')
|
||||
foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall']
|
||||
foreach i : ['bb', 'empty', 'inline', 'insn', 'mem', 'reset', 'syscall', 'patch']
|
||||
if host_os == 'windows'
|
||||
t += shared_module(i, files(i + '.c') + '../../../contrib/plugins/win32_linker.c',
|
||||
include_directories: '../../../include/qemu',
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* This plugin patches instructions matching a pattern to a different
|
||||
* instruction as they execute
|
||||
*
|
||||
*/
|
||||
|
||||
#include "glib.h"
|
||||
#include "glibconfig.h"
|
||||
|
||||
#include <qemu-plugin.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
|
||||
|
||||
static bool use_hwaddr;
|
||||
static GByteArray *target_data;
|
||||
static GByteArray *patch_data;
|
||||
|
||||
/**
|
||||
* Parse a string of hexadecimal digits into a GByteArray. The string must be
|
||||
* even length
|
||||
*/
|
||||
static GByteArray *str_to_bytes(const char *str)
|
||||
{
|
||||
size_t len = strlen(str);
|
||||
|
||||
if (len == 0 || len % 2 != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GByteArray *bytes = g_byte_array_new();
|
||||
char byte[3] = {0};
|
||||
guint8 value = 0;
|
||||
|
||||
for (size_t i = 0; i < len; i += 2) {
|
||||
byte[0] = str[i];
|
||||
byte[1] = str[i + 1];
|
||||
value = (guint8)g_ascii_strtoull(byte, NULL, 16);
|
||||
g_byte_array_append(bytes, &value, 1);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void patch_hwaddr(unsigned int vcpu_index, void *userdata)
|
||||
{
|
||||
uintptr_t addr = (uintptr_t) userdata;
|
||||
g_autoptr(GString) str = g_string_new(NULL);
|
||||
g_string_printf(str, "patching: @0x%"
|
||||
PRIxPTR "\n",
|
||||
addr);
|
||||
qemu_plugin_outs(str->str);
|
||||
|
||||
enum qemu_plugin_hwaddr_operation_result result =
|
||||
qemu_plugin_write_memory_hwaddr(addr, patch_data);
|
||||
|
||||
|
||||
if (result != QEMU_PLUGIN_HWADDR_OPERATION_OK) {
|
||||
g_autoptr(GString) errmsg = g_string_new(NULL);
|
||||
g_string_printf(errmsg, "Failed to write memory: %d\n", result);
|
||||
qemu_plugin_outs(errmsg->str);
|
||||
return;
|
||||
}
|
||||
|
||||
GByteArray *read_data = g_byte_array_new();
|
||||
|
||||
result = qemu_plugin_read_memory_hwaddr(addr, read_data,
|
||||
patch_data->len);
|
||||
|
||||
qemu_plugin_outs("Reading memory...\n");
|
||||
|
||||
if (result != QEMU_PLUGIN_HWADDR_OPERATION_OK) {
|
||||
g_autoptr(GString) errmsg = g_string_new(NULL);
|
||||
g_string_printf(errmsg, "Failed to read memory: %d\n", result);
|
||||
qemu_plugin_outs(errmsg->str);
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp(patch_data->data, read_data->data, patch_data->len) != 0) {
|
||||
qemu_plugin_outs("Failed to read back written data\n");
|
||||
}
|
||||
|
||||
qemu_plugin_outs("Success!\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void patch_vaddr(unsigned int vcpu_index, void *userdata)
|
||||
{
|
||||
uintptr_t addr = (uintptr_t) userdata;
|
||||
uint64_t hwaddr = 0;
|
||||
if (!qemu_plugin_translate_vaddr(addr, &hwaddr)) {
|
||||
qemu_plugin_outs("Failed to translate vaddr\n");
|
||||
return;
|
||||
}
|
||||
g_autoptr(GString) str = g_string_new(NULL);
|
||||
g_string_printf(str, "patching: @0x%"
|
||||
PRIxPTR " hw: @0x%" PRIx64 "\n",
|
||||
addr, hwaddr);
|
||||
qemu_plugin_outs(str->str);
|
||||
|
||||
qemu_plugin_outs("Writing memory (vaddr)...\n");
|
||||
|
||||
if (!qemu_plugin_write_memory_vaddr(addr, patch_data)) {
|
||||
qemu_plugin_outs("Failed to write memory\n");
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_plugin_outs("Reading memory (vaddr)...\n");
|
||||
|
||||
g_autoptr(GByteArray) read_data = g_byte_array_new();
|
||||
|
||||
if (!qemu_plugin_read_memory_vaddr(addr, read_data, patch_data->len)) {
|
||||
qemu_plugin_outs("Failed to read memory\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp(patch_data->data, read_data->data, patch_data->len) != 0) {
|
||||
qemu_plugin_outs("Failed to read back written data\n");
|
||||
}
|
||||
|
||||
qemu_plugin_outs("Success!\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback on translation of a translation block.
|
||||
*/
|
||||
static void vcpu_tb_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
|
||||
{
|
||||
g_autoptr(GByteArray) insn_data = g_byte_array_new();
|
||||
uintptr_t addr = 0;
|
||||
|
||||
for (size_t i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
|
||||
struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
|
||||
uint64_t vaddr = qemu_plugin_insn_vaddr(insn);
|
||||
|
||||
if (use_hwaddr) {
|
||||
uint64_t hwaddr = 0;
|
||||
if (!qemu_plugin_translate_vaddr(vaddr, &hwaddr)) {
|
||||
qemu_plugin_outs("Failed to translate vaddr\n");
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* As we cannot emulate 64 bit systems on 32 bit hosts we
|
||||
* should never see the top bits set, hence we can safely
|
||||
* cast to uintptr_t.
|
||||
*/
|
||||
g_assert(hwaddr <= UINTPTR_MAX);
|
||||
addr = (uintptr_t) hwaddr;
|
||||
} else {
|
||||
g_assert(vaddr <= UINTPTR_MAX);
|
||||
addr = (uintptr_t) vaddr;
|
||||
}
|
||||
|
||||
g_byte_array_set_size(insn_data, qemu_plugin_insn_size(insn));
|
||||
qemu_plugin_insn_data(insn, insn_data->data, insn_data->len);
|
||||
|
||||
if (insn_data->len >= target_data->len &&
|
||||
!memcmp(insn_data->data, target_data->data,
|
||||
MIN(target_data->len, insn_data->len))) {
|
||||
if (use_hwaddr) {
|
||||
qemu_plugin_register_vcpu_tb_exec_cb(tb, patch_hwaddr,
|
||||
QEMU_PLUGIN_CB_NO_REGS,
|
||||
(void *) addr);
|
||||
} else {
|
||||
qemu_plugin_register_vcpu_tb_exec_cb(tb, patch_vaddr,
|
||||
QEMU_PLUGIN_CB_NO_REGS,
|
||||
(void *) addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: <lib>,target=<bytes>,patch=<new_bytes>"
|
||||
"[,use_hwaddr=true|false]");
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the plugin is installed
|
||||
*/
|
||||
QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
|
||||
const qemu_info_t *info, int argc,
|
||||
char **argv)
|
||||
{
|
||||
|
||||
use_hwaddr = true;
|
||||
target_data = NULL;
|
||||
patch_data = NULL;
|
||||
|
||||
if (argc > 4) {
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
char *opt = argv[i];
|
||||
g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
|
||||
if (g_strcmp0(tokens[0], "use_hwaddr") == 0) {
|
||||
if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &use_hwaddr)) {
|
||||
fprintf(stderr,
|
||||
"Failed to parse boolean argument use_hwaddr\n");
|
||||
return -1;
|
||||
}
|
||||
} else if (g_strcmp0(tokens[0], "target") == 0) {
|
||||
target_data = str_to_bytes(tokens[1]);
|
||||
if (!target_data) {
|
||||
fprintf(stderr,
|
||||
"Failed to parse target bytes.\n");
|
||||
return -1;
|
||||
}
|
||||
} else if (g_strcmp0(tokens[0], "patch") == 0) {
|
||||
patch_data = str_to_bytes(tokens[1]);
|
||||
if (!patch_data) {
|
||||
fprintf(stderr, "Failed to parse patch bytes.\n");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unknown argument: %s\n", tokens[0]);
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target_data) {
|
||||
fprintf(stderr, "target argument is required\n");
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!patch_data) {
|
||||
fprintf(stderr, "patch argument is required\n");
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (target_data->len != patch_data->len) {
|
||||
fprintf(stderr, "Target and patch data must be the same length\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans_cb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
#
|
||||
# x86 system tests
|
||||
#
|
||||
# This currently builds only for i386. The common C code is built
|
||||
# with standard compiler flags however so we can support both by
|
||||
# adding additional boot files for x86_64.
|
||||
# x86_64 system tests
|
||||
#
|
||||
|
||||
I386_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/i386/system
|
||||
X64_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/x86_64/system
|
||||
X64_SYSTEM_TESTS=$(patsubst $(X64_SYSTEM_SRC)/%.c, %, $(wildcard $(X64_SYSTEM_SRC)/*.c))
|
||||
|
||||
VPATH+=$(X64_SYSTEM_SRC)
|
||||
|
||||
# These objects provide the basic boot code and helper functions for all tests
|
||||
CRT_OBJS=boot.o
|
||||
@@ -18,7 +16,7 @@ LDFLAGS=-Wl,-T$(LINK_SCRIPT) -Wl,-melf_x86_64
|
||||
CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC)
|
||||
LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc
|
||||
|
||||
TESTS+=$(MULTIARCH_TESTS)
|
||||
TESTS+=$(MULTIARCH_TESTS) $(X64_SYSTEM_TESTS)
|
||||
EXTRA_RUNS+=$(MULTIARCH_RUNS)
|
||||
|
||||
# building head blobs
|
||||
@@ -35,3 +33,12 @@ memory: CFLAGS+=-DCHECK_UNALIGNED=1
|
||||
|
||||
# Running
|
||||
QEMU_OPTS+=-device isa-debugcon,chardev=output -device isa-debug-exit,iobase=0xf4,iosize=0x4 -kernel
|
||||
|
||||
ifeq ($(CONFIG_PLUGIN),y)
|
||||
run-plugin-patch-target-with-libpatch.so: \
|
||||
PLUGIN_ARGS=$(COMMA)target=ffc0$(COMMA)patch=9090$(COMMA)use_hwaddr=true
|
||||
run-plugin-patch-target-with-libpatch.so: \
|
||||
CHECK_PLUGIN_OUTPUT_COMMAND=$(X64_SYSTEM_SRC)/validate-patch.py $@.out
|
||||
run-plugin-patch-target-with-libpatch.so: patch-target libpatch.so
|
||||
EXTRA_RUNS+=run-plugin-patch-target-with-libpatch.so
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* This test target increments a value 100 times. The patcher converts the
|
||||
* inc instruction to a nop, so it only increments the value once.
|
||||
*
|
||||
*/
|
||||
#include <minilib.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ml_printf("Running test...\n");
|
||||
unsigned int x = 0;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
asm volatile (
|
||||
"inc %[x]"
|
||||
: [x] "+a" (x)
|
||||
);
|
||||
}
|
||||
ml_printf("Value: %d\n", x);
|
||||
return 0;
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# validate-patch.py: check the patch applies
|
||||
#
|
||||
# This program takes two inputs:
|
||||
# - the plugin output
|
||||
# - the binary output
|
||||
#
|
||||
# Copyright (C) 2024
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Process the arguments, injest the program and plugin out and
|
||||
verify they match up and report if they do not.
|
||||
"""
|
||||
parser = ArgumentParser(description="Validate patch")
|
||||
parser.add_argument('test_output',
|
||||
help="The output from the test itself")
|
||||
parser.add_argument('plugin_output',
|
||||
help="The output from plugin")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.test_output, 'r') as f:
|
||||
test_data = f.read()
|
||||
with open(args.plugin_output, 'r') as f:
|
||||
plugin_data = f.read()
|
||||
if "Value: 1" in test_data:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user