aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS39
-rw-r--r--block.c108
-rw-r--r--block/export/fuse.c121
-rw-r--r--block/nfs.c4
-rw-r--r--block/qcow2.c42
-rw-r--r--block/rbd.c749
-rw-r--r--block/replication.c7
-rw-r--r--block/ssh.c4
-rw-r--r--blockdev.c77
-rw-r--r--default-configs/devices/arm-softmmu.mak1
-rw-r--r--default-configs/devices/ppc-softmmu.mak2
-rw-r--r--docs/system/arm/stm32.rst66
-rw-r--r--docs/system/deprecated.rst50
-rw-r--r--docs/system/ppc/ppce500.rst10
-rw-r--r--docs/system/removed-features.rst31
-rw-r--r--docs/system/target-arm.rst1
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/adc/Kconfig3
-rw-r--r--hw/adc/max111x.c (renamed from hw/misc/max111x.c)2
-rw-r--r--hw/adc/meson.build2
-rw-r--r--hw/adc/zynq-xadc.c (renamed from hw/misc/zynq-xadc.c)2
-rw-r--r--hw/arm/Kconfig13
-rw-r--r--hw/arm/aspeed.c2
-rw-r--r--hw/arm/meson.build2
-rw-r--r--hw/arm/nseries.c5
-rw-r--r--hw/arm/pxa2xx.c2
-rw-r--r--hw/arm/spitz.c6
-rw-r--r--hw/arm/stellaris.c56
-rw-r--r--hw/arm/stm32f100_soc.c182
-rw-r--r--hw/arm/stm32vldiscovery.c66
-rw-r--r--hw/arm/virt.c3
-rw-r--r--hw/arm/xilinx_zynq.c2
-rw-r--r--hw/display/ati.c2
-rw-r--r--hw/display/sm501.c16
-rw-r--r--hw/display/xlnx_dp.c2
-rw-r--r--hw/gpio/pl061.c345
-rw-r--r--hw/gpio/trace-events9
-rw-r--r--hw/hyperv/vmbus.c20
-rw-r--r--hw/i2c/Kconfig4
-rw-r--r--hw/i2c/core.c76
-rw-r--r--hw/i2c/imx_i2c.c2
-rw-r--r--hw/i2c/meson.build1
-rw-r--r--hw/i2c/pm_smbus.c4
-rw-r--r--hw/i2c/pmbus_device.c1612
-rw-r--r--hw/i2c/ppc4xx_i2c.c15
-rw-r--r--hw/i2c/smbus_master.c22
-rw-r--r--hw/input/lm832x.c2
-rw-r--r--hw/intc/arm_gicv3_cpuif.c4
-rw-r--r--hw/intc/arm_gicv3_redist.c4
-rw-r--r--hw/ipmi/ipmi_bmc_sim.c4
-rw-r--r--hw/meson.build1
-rw-r--r--hw/misc/Kconfig15
-rw-r--r--hw/misc/auxbus.c68
-rw-r--r--hw/misc/meson.build6
-rw-r--r--hw/ppc/Kconfig5
-rw-r--r--hw/ppc/meson.build3
-rw-r--r--hw/ppc/pegasos2.c789
-rw-r--r--hw/ppc/spapr.c77
-rw-r--r--hw/ppc/spapr_caps.c41
-rw-r--r--hw/ppc/spapr_hcall.c24
-rw-r--r--hw/ppc/spapr_vof.c167
-rw-r--r--hw/ppc/trace-events24
-rw-r--r--hw/ppc/vof.c1053
-rw-r--r--hw/sensor/Kconfig19
-rw-r--r--hw/sensor/adm1272.c543
-rw-r--r--hw/sensor/emc141x.c (renamed from hw/misc/emc141x.c)2
-rw-r--r--hw/sensor/max34451.c775
-rw-r--r--hw/sensor/meson.build5
-rw-r--r--hw/sensor/tmp105.c (renamed from hw/misc/tmp105.c)2
-rw-r--r--hw/sensor/tmp421.c (renamed from hw/misc/tmp421.c)0
-rw-r--r--hw/vfio/common.c315
-rw-r--r--hw/virtio/vhost-user.c3
-rw-r--r--hw/virtio/virtio-mem.c391
-rw-r--r--include/block/block.h3
-rw-r--r--include/exec/memory.h324
-rw-r--r--include/hw/adc/max111x.h (renamed from include/hw/misc/max111x.h)0
-rw-r--r--include/hw/adc/zynq-xadc.h (renamed from include/hw/misc/zynq-xadc.h)0
-rw-r--r--include/hw/arm/stm32f100_soc.h57
-rw-r--r--include/hw/i2c/i2c.h46
-rw-r--r--include/hw/i2c/pmbus_device.h517
-rw-r--r--include/hw/input/lm832x.h28
-rw-r--r--include/hw/ppc/spapr.h31
-rw-r--r--include/hw/ppc/vof.h58
-rw-r--r--include/hw/sensor/emc141x_regs.h (renamed from include/hw/misc/emc141x_regs.h)0
-rw-r--r--include/hw/sensor/tmp105.h (renamed from hw/misc/tmp105.h)2
-rw-r--r--include/hw/sensor/tmp105_regs.h (renamed from include/hw/misc/tmp105_regs.h)0
-rw-r--r--include/hw/vfio/vfio-common.h12
-rw-r--r--include/hw/virtio/virtio-mem.h3
-rw-r--r--include/migration/vmstate.h1
-rw-r--r--include/standard-headers/asm-x86/kvm_para.h13
-rw-r--r--include/standard-headers/drm/drm_fourcc.h7
-rw-r--r--include/standard-headers/linux/ethtool.h4
-rw-r--r--include/standard-headers/linux/input-event-codes.h1
-rw-r--r--include/standard-headers/linux/virtio_ids.h2
-rw-r--r--include/standard-headers/linux/virtio_vsock.h9
-rw-r--r--linux-headers/asm-arm64/kvm.h11
-rw-r--r--linux-headers/asm-generic/mman-common.h3
-rw-r--r--linux-headers/asm-generic/unistd.h4
-rw-r--r--linux-headers/asm-mips/mman.h3
-rw-r--r--linux-headers/asm-mips/unistd_n32.h1
-rw-r--r--linux-headers/asm-mips/unistd_n64.h1
-rw-r--r--linux-headers/asm-mips/unistd_o32.h1
-rw-r--r--linux-headers/asm-powerpc/unistd_32.h1
-rw-r--r--linux-headers/asm-powerpc/unistd_64.h1
-rw-r--r--linux-headers/asm-s390/unistd_32.h1
-rw-r--r--linux-headers/asm-s390/unistd_64.h1
-rw-r--r--linux-headers/asm-x86/kvm.h13
-rw-r--r--linux-headers/asm-x86/unistd_32.h7
-rw-r--r--linux-headers/asm-x86/unistd_64.h7
-rw-r--r--linux-headers/asm-x86/unistd_x32.h7
-rw-r--r--linux-headers/linux/kvm.h105
-rw-r--r--linux-headers/linux/userfaultfd.h11
-rw-r--r--meson.build7
-rw-r--r--pc-bios/README4
-rw-r--r--pc-bios/u-boot.e500bin406920 -> 421720 bytes
-rw-r--r--pc-bios/vof-nvram.binbin0 -> 16384 bytes
-rwxr-xr-xpc-bios/vof.binbin0 -> 3456 bytes
-rw-r--r--pc-bios/vof/Makefile23
-rw-r--r--pc-bios/vof/bootmem.c14
-rw-r--r--pc-bios/vof/ci.c91
-rw-r--r--pc-bios/vof/entry.S49
-rw-r--r--pc-bios/vof/libc.c66
-rw-r--r--pc-bios/vof/main.c21
-rw-r--r--pc-bios/vof/vof.h41
-rw-r--r--pc-bios/vof/vof.lds48
-rw-r--r--qapi/block-core.json134
-rw-r--r--qapi/block-export.json33
-rw-r--r--qemu-img.c9
-rw-r--r--qemu-io-cmds.c7
m---------roms/u-boot0
-rw-r--r--softmmu/memory.c98
-rw-r--r--softmmu/physmem.c108
-rw-r--r--target/arm/helper.c16
-rw-r--r--target/ppc/arch_dump.c8
-rw-r--r--target/ppc/cpu-qom.h2
-rw-r--r--target/ppc/cpu.c2
-rw-r--r--target/ppc/cpu.h15
-rw-r--r--target/ppc/cpu_init.c64
-rw-r--r--target/ppc/excp_helper.c3
-rw-r--r--target/ppc/kvm.c12
-rw-r--r--target/ppc/kvm_ppc.h12
-rw-r--r--target/ppc/mmu-book3s-v3.c19
-rw-r--r--target/ppc/mmu-book3s-v3.h6
-rw-r--r--target/ppc/mmu-books.h30
-rw-r--r--target/ppc/mmu-hash32.c254
-rw-r--r--target/ppc/mmu-hash32.h8
-rw-r--r--target/ppc/mmu-hash64.c157
-rw-r--r--target/ppc/mmu-hash64.h6
-rw-r--r--target/ppc/mmu-radix64.c151
-rw-r--r--target/ppc/mmu-radix64.h6
-rw-r--r--target/ppc/mmu_helper.c215
-rw-r--r--target/ppc/translate.c5
-rwxr-xr-xtests/qemu-iotests/0404
-rwxr-xr-xtests/qemu-iotests/0416
-rwxr-xr-xtests/qemu-iotests/0613
-rw-r--r--tests/qemu-iotests/061.out3
-rw-r--r--tests/qemu-iotests/082.out6
-rwxr-xr-xtests/qemu-iotests/11418
-rw-r--r--tests/qemu-iotests/114.out11
-rwxr-xr-xtests/qemu-iotests/1559
-rwxr-xr-xtests/qemu-iotests/1654
-rwxr-xr-xtests/qemu-iotests/24578
-rw-r--r--tests/qemu-iotests/245.out4
-rwxr-xr-xtests/qemu-iotests/2484
-rw-r--r--tests/qemu-iotests/248.out2
-rwxr-xr-xtests/qemu-iotests/29611
-rwxr-xr-xtests/qemu-iotests/2984
-rwxr-xr-xtests/qemu-iotests/3014
-rw-r--r--tests/qemu-iotests/301.out16
-rwxr-xr-xtests/qemu-iotests/30820
-rw-r--r--tests/qemu-iotests/308.out6
-rw-r--r--tests/qemu-iotests/common.rc6
-rwxr-xr-xtests/qemu-iotests/tests/fuse-allow-other168
-rw-r--r--tests/qemu-iotests/tests/fuse-allow-other.out88
-rwxr-xr-xtests/qemu-iotests/tests/remove-bitmap-from-backing22
-rw-r--r--tests/qtest/adm1272-test.c445
-rw-r--r--tests/qtest/boot-serial-test.c37
-rw-r--r--tests/qtest/emc141x-test.c2
-rw-r--r--tests/qtest/max34451-test.c336
-rw-r--r--tests/qtest/meson.build2
-rw-r--r--tests/qtest/npcm7xx_smbus-test.c2
-rw-r--r--tests/qtest/rtas-test.c15
-rw-r--r--tests/qtest/tmp105-test.c2
-rw-r--r--util/mmap-alloc.c2
-rw-r--r--util/uri.c22
185 files changed, 11061 insertions, 1474 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 809830c..40d095d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -794,6 +794,7 @@ F: hw/input/tsc2005.c
F: hw/misc/cbus.c
F: hw/rtc/twl92230.c
F: include/hw/display/blizzard.h
+F: include/hw/input/lm832x.h
F: include/hw/input/tsc2xxx.h
F: include/hw/misc/cbus.h
F: tests/acceptance/machine_arm_n8x0.py
@@ -846,8 +847,8 @@ F: hw/display/tc6393xb.c
F: hw/gpio/max7310.c
F: hw/gpio/zaurus.c
F: hw/misc/mst_fpga.c
-F: hw/misc/max111x.c
-F: include/hw/misc/max111x.h
+F: hw/adc/max111x.c
+F: include/hw/adc/max111x.h
F: include/hw/arm/pxa.h
F: include/hw/arm/sharpsl.h
F: include/hw/display/tc6393xb.h
@@ -893,6 +894,13 @@ F: hw/*/stellaris*
F: include/hw/input/gamepad.h
F: docs/system/arm/stellaris.rst
+STM32VLDISCOVERY
+M: Alexandre Iooss <erdnaxe@crans.org>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/arm/stm32vldiscovery.c
+F: docs/system/arm/stm32.rst
+
Versatile Express
M: Peter Maydell <peter.maydell@linaro.org>
L: qemu-arm@nongnu.org
@@ -925,8 +933,10 @@ L: qemu-arm@nongnu.org
S: Maintained
F: hw/*/xilinx_*
F: hw/*/cadence_*
-F: hw/misc/zynq*
-F: include/hw/misc/zynq*
+F: hw/misc/zynq_slcr.c
+F: hw/adc/zynq-xadc.c
+F: include/hw/misc/zynq_slcr.h
+F: include/hw/adc/zynq-xadc.h
X: hw/ssi/xilinx_*
Xilinx ZynqMP and Versal
@@ -948,6 +958,12 @@ L: qemu-arm@nongnu.org
S: Maintained
F: hw/arm/virt-acpi-build.c
+STM32F100
+M: Alexandre Iooss <erdnaxe@crans.org>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/arm/stm32f100_soc.c
+
STM32F205
M: Alistair Francis <alistair@alistair23.me>
M: Peter Maydell <peter.maydell@linaro.org>
@@ -1360,6 +1376,18 @@ F: hw/pci-host/mv64361.c
F: hw/pci-host/mv643xx.h
F: include/hw/pci-host/mv64361.h
+Virtual Open Firmware (VOF)
+M: Alexey Kardashevskiy <aik@ozlabs.ru>
+R: David Gibson <david@gibson.dropbear.id.au>
+R: Greg Kurz <groug@kaod.org>
+L: qemu-ppc@nongnu.org
+S: Maintained
+F: hw/ppc/spapr_vof*
+F: hw/ppc/vof*
+F: include/hw/ppc/vof*
+F: pc-bios/vof/*
+F: pc-bios/vof*
+
RISC-V Machines
---------------
OpenTitan
@@ -3069,7 +3097,8 @@ S: Supported
F: block/vmdk.c
RBD
-M: Jason Dillaman <dillaman@redhat.com>
+M: Ilya Dryomov <idryomov@gmail.com>
+R: Peter Lieven <pl@kamp.de>
L: qemu-block@nongnu.org
S: Supported
F: block/rbd.c
diff --git a/block.c b/block.c
index acd35cb..be083f3 100644
--- a/block.c
+++ b/block.c
@@ -4095,6 +4095,19 @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
NULL, 0, keep_old_opts);
}
+void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue)
+{
+ if (bs_queue) {
+ BlockReopenQueueEntry *bs_entry, *next;
+ QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
+ qobject_unref(bs_entry->state.explicit_options);
+ qobject_unref(bs_entry->state.options);
+ g_free(bs_entry);
+ }
+ g_free(bs_queue);
+ }
+}
+
/*
* Reopen multiple BlockDriverStates atomically & transactionally.
*
@@ -4111,19 +4124,26 @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
*
* All affected nodes must be drained between bdrv_reopen_queue() and
* bdrv_reopen_multiple().
+ *
+ * To be called from the main thread, with all other AioContexts unlocked.
*/
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
{
int ret = -1;
BlockReopenQueueEntry *bs_entry, *next;
+ AioContext *ctx;
Transaction *tran = tran_new();
g_autoptr(GHashTable) found = NULL;
g_autoptr(GSList) refresh_list = NULL;
+ assert(qemu_get_current_aio_context() == qemu_get_aio_context());
assert(bs_queue != NULL);
QTAILQ_FOREACH(bs_entry, bs_queue, entry) {
+ ctx = bdrv_get_aio_context(bs_entry->state.bs);
+ aio_context_acquire(ctx);
ret = bdrv_flush(bs_entry->state.bs);
+ aio_context_release(ctx);
if (ret < 0) {
error_setg_errno(errp, -ret, "Error flushing drive");
goto abort;
@@ -4132,7 +4152,10 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
QTAILQ_FOREACH(bs_entry, bs_queue, entry) {
assert(bs_entry->state.bs->quiesce_counter > 0);
+ ctx = bdrv_get_aio_context(bs_entry->state.bs);
+ aio_context_acquire(ctx);
ret = bdrv_reopen_prepare(&bs_entry->state, bs_queue, tran, errp);
+ aio_context_release(ctx);
if (ret < 0) {
goto abort;
}
@@ -4175,7 +4198,10 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
* to first element.
*/
QTAILQ_FOREACH_REVERSE(bs_entry, bs_queue, entry) {
+ ctx = bdrv_get_aio_context(bs_entry->state.bs);
+ aio_context_acquire(ctx);
bdrv_reopen_commit(&bs_entry->state);
+ aio_context_release(ctx);
}
tran_commit(tran);
@@ -4184,7 +4210,10 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
BlockDriverState *bs = bs_entry->state.bs;
if (bs->drv->bdrv_reopen_commit_post) {
+ ctx = bdrv_get_aio_context(bs);
+ aio_context_acquire(ctx);
bs->drv->bdrv_reopen_commit_post(&bs_entry->state);
+ aio_context_release(ctx);
}
}
@@ -4195,38 +4224,52 @@ abort:
tran_abort(tran);
QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
if (bs_entry->prepared) {
+ ctx = bdrv_get_aio_context(bs_entry->state.bs);
+ aio_context_acquire(ctx);
bdrv_reopen_abort(&bs_entry->state);
+ aio_context_release(ctx);
}
- qobject_unref(bs_entry->state.explicit_options);
- qobject_unref(bs_entry->state.options);
}
cleanup:
- QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
- g_free(bs_entry);
- }
- g_free(bs_queue);
+ bdrv_reopen_queue_free(bs_queue);
return ret;
}
-int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only,
- Error **errp)
+int bdrv_reopen(BlockDriverState *bs, QDict *opts, bool keep_old_opts,
+ Error **errp)
{
- int ret;
+ AioContext *ctx = bdrv_get_aio_context(bs);
BlockReopenQueue *queue;
- QDict *opts = qdict_new();
-
- qdict_put_bool(opts, BDRV_OPT_READ_ONLY, read_only);
+ int ret;
bdrv_subtree_drained_begin(bs);
- queue = bdrv_reopen_queue(NULL, bs, opts, true);
+ if (ctx != qemu_get_aio_context()) {
+ aio_context_release(ctx);
+ }
+
+ queue = bdrv_reopen_queue(NULL, bs, opts, keep_old_opts);
ret = bdrv_reopen_multiple(queue, errp);
+
+ if (ctx != qemu_get_aio_context()) {
+ aio_context_acquire(ctx);
+ }
bdrv_subtree_drained_end(bs);
return ret;
}
+int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only,
+ Error **errp)
+{
+ QDict *opts = qdict_new();
+
+ qdict_put_bool(opts, BDRV_OPT_READ_ONLY, read_only);
+
+ return bdrv_reopen(bs, opts, true, errp);
+}
+
/*
* Take a BDRVReopenState and check if the value of 'backing' in the
* reopen_state->options QDict is valid or not.
@@ -4573,6 +4616,8 @@ static void bdrv_reopen_commit(BDRVReopenState *reopen_state)
/* set BDS specific flags now */
qobject_unref(bs->explicit_options);
qobject_unref(bs->options);
+ qobject_ref(reopen_state->explicit_options);
+ qobject_ref(reopen_state->options);
bs->explicit_options = reopen_state->explicit_options;
bs->options = reopen_state->options;
@@ -5074,7 +5119,7 @@ int coroutine_fn bdrv_co_check(BlockDriverState *bs,
* -ENOTSUP - format driver doesn't support changing the backing file
*/
int bdrv_change_backing_file(BlockDriverState *bs, const char *backing_file,
- const char *backing_fmt, bool warn)
+ const char *backing_fmt, bool require)
{
BlockDriver *drv = bs->drv;
int ret;
@@ -5088,10 +5133,8 @@ int bdrv_change_backing_file(BlockDriverState *bs, const char *backing_file,
return -EINVAL;
}
- if (warn && backing_file && !backing_fmt) {
- warn_report("Deprecated use of backing file without explicit "
- "backing format, use of this image requires "
- "potentially unsafe format probing");
+ if (require && backing_file && !backing_fmt) {
+ return -EINVAL;
}
if (drv->bdrv_change_backing_file != NULL) {
@@ -6601,24 +6644,11 @@ void bdrv_img_create(const char *filename, const char *fmt,
goto out;
} else {
if (!backing_fmt) {
- warn_report("Deprecated use of backing file without explicit "
- "backing format (detected format of %s)",
- bs->drv->format_name);
- if (bs->drv != &bdrv_raw) {
- /*
- * A probe of raw deserves the most attention:
- * leaving the backing format out of the image
- * will ensure bs->probed is set (ensuring we
- * don't accidentally commit into the backing
- * file), and allow more spots to warn the users
- * to fix their toolchain when opening this image
- * later. For other images, we can safely record
- * the format that we probed.
- */
- backing_fmt = bs->drv->format_name;
- qemu_opt_set(opts, BLOCK_OPT_BACKING_FMT, backing_fmt,
- NULL);
- }
+ error_setg(&local_err,
+ "Backing file specified without backing format");
+ error_append_hint(&local_err, "Detected format of %s.",
+ bs->drv->format_name);
+ goto out;
}
if (size == -1) {
/* Opened BS, have no size */
@@ -6635,9 +6665,9 @@ void bdrv_img_create(const char *filename, const char *fmt,
}
/* (backing_file && !(flags & BDRV_O_NO_BACKING)) */
} else if (backing_file && !backing_fmt) {
- warn_report("Deprecated use of unopened backing file without "
- "explicit backing format, use of this image requires "
- "potentially unsafe format probing");
+ error_setg(&local_err,
+ "Backing file specified without backing format");
+ goto out;
}
if (size == -1) {
diff --git a/block/export/fuse.c b/block/export/fuse.c
index 38f74c9..ada9e26 100644
--- a/block/export/fuse.c
+++ b/block/export/fuse.c
@@ -46,6 +46,12 @@ typedef struct FuseExport {
char *mountpoint;
bool writable;
bool growable;
+ /* Whether allow_other was used as a mount option or not */
+ bool allow_other;
+
+ mode_t st_mode;
+ uid_t st_uid;
+ gid_t st_gid;
} FuseExport;
static GHashTable *exports;
@@ -57,7 +63,7 @@ static void fuse_export_delete(BlockExport *exp);
static void init_exports_table(void);
static int setup_fuse_export(FuseExport *exp, const char *mountpoint,
- Error **errp);
+ bool allow_other, Error **errp);
static void read_from_fuse_export(void *opaque);
static bool is_regular_file(const char *path, Error **errp);
@@ -118,7 +124,29 @@ static int fuse_export_create(BlockExport *blk_exp,
exp->writable = blk_exp_args->writable;
exp->growable = args->growable;
- ret = setup_fuse_export(exp, args->mountpoint, errp);
+ /* set default */
+ if (!args->has_allow_other) {
+ args->allow_other = FUSE_EXPORT_ALLOW_OTHER_AUTO;
+ }
+
+ exp->st_mode = S_IFREG | S_IRUSR;
+ if (exp->writable) {
+ exp->st_mode |= S_IWUSR;
+ }
+ exp->st_uid = getuid();
+ exp->st_gid = getgid();
+
+ if (args->allow_other == FUSE_EXPORT_ALLOW_OTHER_AUTO) {
+ /* Ignore errors on our first attempt */
+ ret = setup_fuse_export(exp, args->mountpoint, true, NULL);
+ exp->allow_other = ret == 0;
+ if (ret < 0) {
+ ret = setup_fuse_export(exp, args->mountpoint, false, errp);
+ }
+ } else {
+ exp->allow_other = args->allow_other == FUSE_EXPORT_ALLOW_OTHER_ON;
+ ret = setup_fuse_export(exp, args->mountpoint, exp->allow_other, errp);
+ }
if (ret < 0) {
goto fail;
}
@@ -146,15 +174,20 @@ static void init_exports_table(void)
* Create exp->fuse_session and mount it.
*/
static int setup_fuse_export(FuseExport *exp, const char *mountpoint,
- Error **errp)
+ bool allow_other, Error **errp)
{
const char *fuse_argv[4];
char *mount_opts;
struct fuse_args fuse_args;
int ret;
- /* Needs to match what fuse_init() sets. Only max_read must be supplied. */
- mount_opts = g_strdup_printf("max_read=%zu", FUSE_MAX_BOUNCE_BYTES);
+ /*
+ * max_read needs to match what fuse_init() sets.
+ * max_write need not be supplied.
+ */
+ mount_opts = g_strdup_printf("max_read=%zu,default_permissions%s",
+ FUSE_MAX_BOUNCE_BYTES,
+ allow_other ? ",allow_other" : "");
fuse_argv[0] = ""; /* Dummy program name */
fuse_argv[1] = "-o";
@@ -316,7 +349,6 @@ static void fuse_getattr(fuse_req_t req, fuse_ino_t inode,
int64_t length, allocated_blocks;
time_t now = time(NULL);
FuseExport *exp = fuse_req_userdata(req);
- mode_t mode;
length = blk_getlength(exp->common.blk);
if (length < 0) {
@@ -331,17 +363,12 @@ static void fuse_getattr(fuse_req_t req, fuse_ino_t inode,
allocated_blocks = DIV_ROUND_UP(allocated_blocks, 512);
}
- mode = S_IFREG | S_IRUSR;
- if (exp->writable) {
- mode |= S_IWUSR;
- }
-
statbuf = (struct stat) {
.st_ino = inode,
- .st_mode = mode,
+ .st_mode = exp->st_mode,
.st_nlink = 1,
- .st_uid = getuid(),
- .st_gid = getgid(),
+ .st_uid = exp->st_uid,
+ .st_gid = exp->st_gid,
.st_size = length,
.st_blksize = blk_bs(exp->common.blk)->bl.request_alignment,
.st_blocks = allocated_blocks,
@@ -387,28 +414,76 @@ static int fuse_do_truncate(const FuseExport *exp, int64_t size,
}
/**
- * Let clients set file attributes. Only resizing is supported.
+ * Let clients set file attributes. Only resizing and changing
+ * permissions (st_mode, st_uid, st_gid) is allowed.
+ * Changing permissions is only allowed as far as it will actually
+ * permit access: Read-only exports cannot be given +w, and exports
+ * without allow_other cannot be given a different UID or GID, and
+ * they cannot be given non-owner access.
*/
static void fuse_setattr(fuse_req_t req, fuse_ino_t inode, struct stat *statbuf,
int to_set, struct fuse_file_info *fi)
{
FuseExport *exp = fuse_req_userdata(req);
+ int supported_attrs;
int ret;
- if (!exp->writable) {
- fuse_reply_err(req, EACCES);
- return;
+ supported_attrs = FUSE_SET_ATTR_SIZE | FUSE_SET_ATTR_MODE;
+ if (exp->allow_other) {
+ supported_attrs |= FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID;
}
- if (to_set & ~FUSE_SET_ATTR_SIZE) {
+ if (to_set & ~supported_attrs) {
fuse_reply_err(req, ENOTSUP);
return;
}
- ret = fuse_do_truncate(exp, statbuf->st_size, true, PREALLOC_MODE_OFF);
- if (ret < 0) {
- fuse_reply_err(req, -ret);
- return;
+ /* Do some argument checks first before committing to anything */
+ if (to_set & FUSE_SET_ATTR_MODE) {
+ /*
+ * Without allow_other, non-owners can never access the export, so do
+ * not allow setting permissions for them
+ */
+ if (!exp->allow_other &&
+ (statbuf->st_mode & (S_IRWXG | S_IRWXO)) != 0)
+ {
+ fuse_reply_err(req, EPERM);
+ return;
+ }
+
+ /* +w for read-only exports makes no sense, disallow it */
+ if (!exp->writable &&
+ (statbuf->st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
+ {
+ fuse_reply_err(req, EROFS);
+ return;
+ }
+ }
+
+ if (to_set & FUSE_SET_ATTR_SIZE) {
+ if (!exp->writable) {
+ fuse_reply_err(req, EACCES);
+ return;
+ }
+
+ ret = fuse_do_truncate(exp, statbuf->st_size, true, PREALLOC_MODE_OFF);
+ if (ret < 0) {
+ fuse_reply_err(req, -ret);
+ return;
+ }
+ }
+
+ if (to_set & FUSE_SET_ATTR_MODE) {
+ /* Ignore FUSE-supplied file type, only change the mode */
+ exp->st_mode = (statbuf->st_mode & 07777) | S_IFREG;
+ }
+
+ if (to_set & FUSE_SET_ATTR_UID) {
+ exp->st_uid = statbuf->st_uid;
+ }
+
+ if (to_set & FUSE_SET_ATTR_GID) {
+ exp->st_gid = statbuf->st_gid;
}
fuse_getattr(req, inode, fi);
diff --git a/block/nfs.c b/block/nfs.c
index 7dff64f..9aeaefb 100644
--- a/block/nfs.c
+++ b/block/nfs.c
@@ -147,9 +147,7 @@ out:
if (qp) {
query_params_free(qp);
}
- if (uri) {
- uri_free(uri);
- }
+ uri_free(uri);
return ret;
}
diff --git a/block/qcow2.c b/block/qcow2.c
index ee4530c..9f1b646 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1926,6 +1926,7 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
static int qcow2_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
+ BDRVQcow2State *s = state->bs->opaque;
Qcow2ReopenState *r;
int ret;
@@ -1956,6 +1957,16 @@ static int qcow2_reopen_prepare(BDRVReopenState *state,
}
}
+ /*
+ * Without an external data file, s->data_file points to the same BdrvChild
+ * as bs->file. It needs to be resynced after reopen because bs->file may
+ * be changed. We can't use it in the meantime.
+ */
+ if (!has_data_file(state->bs)) {
+ assert(s->data_file == state->bs->file);
+ s->data_file = NULL;
+ }
+
return 0;
fail:
@@ -1966,7 +1977,16 @@ fail:
static void qcow2_reopen_commit(BDRVReopenState *state)
{
+ BDRVQcow2State *s = state->bs->opaque;
+
qcow2_update_options_commit(state->bs, state->opaque);
+ if (!s->data_file) {
+ /*
+ * If we don't have an external data file, s->data_file was cleared by
+ * qcow2_reopen_prepare() and needs to be updated.
+ */
+ s->data_file = state->bs->file;
+ }
g_free(state->opaque);
}
@@ -1990,6 +2010,15 @@ static void qcow2_reopen_commit_post(BDRVReopenState *state)
static void qcow2_reopen_abort(BDRVReopenState *state)
{
+ BDRVQcow2State *s = state->bs->opaque;
+
+ if (!s->data_file) {
+ /*
+ * If we don't have an external data file, s->data_file was cleared by
+ * qcow2_reopen_prepare() and needs to be restored.
+ */
+ s->data_file = state->bs->file;
+ }
qcow2_update_options_abort(state->bs, state->opaque);
g_free(state->opaque);
}
@@ -5620,15 +5649,10 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
if (backing_file || backing_format) {
if (g_strcmp0(backing_file, s->image_backing_file) ||
g_strcmp0(backing_format, s->image_backing_format)) {
- warn_report("Deprecated use of amend to alter the backing file; "
- "use qemu-img rebase instead");
- }
- ret = qcow2_change_backing_file(bs,
- backing_file ?: s->image_backing_file,
- backing_format ?: s->image_backing_format);
- if (ret < 0) {
- error_setg_errno(errp, -ret, "Failed to change the backing file");
- return ret;
+ error_setg(errp, "Cannot amend the backing file");
+ error_append_hint(errp,
+ "You can use 'qemu-img rebase' instead.\n");
+ return -EINVAL;
}
}
diff --git a/block/rbd.c b/block/rbd.c
index 26f64cc..dcf82b1 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -55,49 +55,30 @@
* leading "\".
*/
-/* rbd_aio_discard added in 0.1.2 */
-#if LIBRBD_VERSION_CODE >= LIBRBD_VERSION(0, 1, 2)
-#define LIBRBD_SUPPORTS_DISCARD
-#else
-#undef LIBRBD_SUPPORTS_DISCARD
-#endif
-
#define OBJ_MAX_SIZE (1UL << OBJ_DEFAULT_OBJ_ORDER)
#define RBD_MAX_SNAPS 100
-/* The LIBRBD_SUPPORTS_IOVEC is defined in librbd.h */
-#ifdef LIBRBD_SUPPORTS_IOVEC
-#define LIBRBD_USE_IOVEC 1
-#else
-#define LIBRBD_USE_IOVEC 0
-#endif
+#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
+
+static const char rbd_luks_header_verification[
+ RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+ 'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
+};
+
+static const char rbd_luks2_header_verification[
+ RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+ 'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
+};
typedef enum {
RBD_AIO_READ,
RBD_AIO_WRITE,
RBD_AIO_DISCARD,
- RBD_AIO_FLUSH
+ RBD_AIO_FLUSH,
+ RBD_AIO_WRITE_ZEROES
} RBDAIOCmd;
-typedef struct RBDAIOCB {
- BlockAIOCB common;
- int64_t ret;
- QEMUIOVector *qiov;
- char *bounce;
- RBDAIOCmd cmd;
- int error;
- struct BDRVRBDState *s;
-} RBDAIOCB;
-
-typedef struct RADOSCB {
- RBDAIOCB *acb;
- struct BDRVRBDState *s;
- int64_t size;
- char *buf;
- int64_t ret;
-} RADOSCB;
-
typedef struct BDRVRBDState {
rados_t cluster;
rados_ioctx_t io_ctx;
@@ -106,8 +87,16 @@ typedef struct BDRVRBDState {
char *snap;
char *namespace;
uint64_t image_size;
+ uint64_t object_size;
} BDRVRBDState;
+typedef struct RBDTask {
+ BlockDriverState *bs;
+ Coroutine *co;
+ bool complete;
+ int64_t ret;
+} RBDTask;
+
static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
BlockdevOptionsRbd *opts, bool cache,
const char *keypairs, const char *secretid,
@@ -251,14 +240,6 @@ done:
return;
}
-
-static void qemu_rbd_refresh_limits(BlockDriverState *bs, Error **errp)
-{
- /* XXX Does RBD support AIO on less than 512-byte alignment? */
- bs->bl.request_alignment = 512;
-}
-
-
static int qemu_rbd_set_auth(rados_t cluster, BlockdevOptionsRbd *opts,
Error **errp)
{
@@ -340,16 +321,202 @@ static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json,
return ret;
}
-static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+static int qemu_rbd_convert_luks_options(
+ RbdEncryptionOptionsLUKSBase *luks_opts,
+ char **passphrase,
+ size_t *passphrase_len,
+ Error **errp)
+{
+ return qcrypto_secret_lookup(luks_opts->key_secret, (uint8_t **)passphrase,
+ passphrase_len, errp);
+}
+
+static int qemu_rbd_convert_luks_create_options(
+ RbdEncryptionCreateOptionsLUKSBase *luks_opts,
+ rbd_encryption_algorithm_t *alg,
+ char **passphrase,
+ size_t *passphrase_len,
+ Error **errp)
{
- if (LIBRBD_USE_IOVEC) {
- RBDAIOCB *acb = rcb->acb;
- iov_memset(acb->qiov->iov, acb->qiov->niov, offs, 0,
- acb->qiov->size - offs);
+ int r = 0;
+
+ r = qemu_rbd_convert_luks_options(
+ qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
+ passphrase, passphrase_len, errp);
+ if (r < 0) {
+ return r;
+ }
+
+ if (luks_opts->has_cipher_alg) {
+ switch (luks_opts->cipher_alg) {
+ case QCRYPTO_CIPHER_ALG_AES_128: {
+ *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
+ break;
+ }
+ case QCRYPTO_CIPHER_ALG_AES_256: {
+ *alg = RBD_ENCRYPTION_ALGORITHM_AES256;
+ break;
+ }
+ default: {
+ r = -ENOTSUP;
+ error_setg_errno(errp, -r, "unknown encryption algorithm: %u",
+ luks_opts->cipher_alg);
+ return r;
+ }
+ }
} else {
- memset(rcb->buf + offs, 0, rcb->size - offs);
+ /* default alg */
+ *alg = RBD_ENCRYPTION_ALGORITHM_AES256;
+ }
+
+ return 0;
+}
+
+static int qemu_rbd_encryption_format(rbd_image_t image,
+ RbdEncryptionCreateOptions *encrypt,
+ Error **errp)
+{
+ int r = 0;
+ g_autofree char *passphrase = NULL;
+ size_t passphrase_len;
+ rbd_encryption_format_t format;
+ rbd_encryption_options_t opts;
+ rbd_encryption_luks1_format_options_t luks_opts;
+ rbd_encryption_luks2_format_options_t luks2_opts;
+ size_t opts_size;
+ uint64_t raw_size, effective_size;
+
+ r = rbd_get_size(image, &raw_size);
+ if (r < 0) {
+ error_setg_errno(errp, -r, "cannot get raw image size");
+ return r;
+ }
+
+ switch (encrypt->format) {
+ case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+ memset(&luks_opts, 0, sizeof(luks_opts));
+ format = RBD_ENCRYPTION_FORMAT_LUKS1;
+ opts = &luks_opts;
+ opts_size = sizeof(luks_opts);
+ r = qemu_rbd_convert_luks_create_options(
+ qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
+ &luks_opts.alg, &passphrase, &passphrase_len, errp);
+ if (r < 0) {
+ return r;
+ }
+ luks_opts.passphrase = passphrase;
+ luks_opts.passphrase_size = passphrase_len;
+ break;
+ }
+ case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+ memset(&luks2_opts, 0, sizeof(luks2_opts));
+ format = RBD_ENCRYPTION_FORMAT_LUKS2;
+ opts = &luks2_opts;
+ opts_size = sizeof(luks2_opts);
+ r = qemu_rbd_convert_luks_create_options(
+ qapi_RbdEncryptionCreateOptionsLUKS2_base(
+ &encrypt->u.luks2),
+ &luks2_opts.alg, &passphrase, &passphrase_len, errp);
+ if (r < 0) {
+ return r;
+ }
+ luks2_opts.passphrase = passphrase;
+ luks2_opts.passphrase_size = passphrase_len;
+ break;
+ }
+ default: {
+ r = -ENOTSUP;
+ error_setg_errno(
+ errp, -r, "unknown image encryption format: %u",
+ encrypt->format);
+ return r;
+ }
+ }
+
+ r = rbd_encryption_format(image, format, opts, opts_size);
+ if (r < 0) {
+ error_setg_errno(errp, -r, "encryption format fail");
+ return r;
+ }
+
+ r = rbd_get_size(image, &effective_size);
+ if (r < 0) {
+ error_setg_errno(errp, -r, "cannot get effective image size");
+ return r;
+ }
+
+ r = rbd_resize(image, raw_size + (raw_size - effective_size));
+ if (r < 0) {
+ error_setg_errno(errp, -r, "cannot resize image after format");
+ return r;
+ }
+
+ return 0;
+}
+
+static int qemu_rbd_encryption_load(rbd_image_t image,
+ RbdEncryptionOptions *encrypt,
+ Error **errp)
+{
+ int r = 0;
+ g_autofree char *passphrase = NULL;
+ size_t passphrase_len;
+ rbd_encryption_luks1_format_options_t luks_opts;
+ rbd_encryption_luks2_format_options_t luks2_opts;
+ rbd_encryption_format_t format;
+ rbd_encryption_options_t opts;
+ size_t opts_size;
+
+ switch (encrypt->format) {
+ case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+ memset(&luks_opts, 0, sizeof(luks_opts));
+ format = RBD_ENCRYPTION_FORMAT_LUKS1;
+ opts = &luks_opts;
+ opts_size = sizeof(luks_opts);
+ r = qemu_rbd_convert_luks_options(
+ qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
+ &passphrase, &passphrase_len, errp);
+ if (r < 0) {
+ return r;
+ }
+ luks_opts.passphrase = passphrase;
+ luks_opts.passphrase_size = passphrase_len;
+ break;
+ }
+ case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+ memset(&luks2_opts, 0, sizeof(luks2_opts));
+ format = RBD_ENCRYPTION_FORMAT_LUKS2;
+ opts = &luks2_opts;
+ opts_size = sizeof(luks2_opts);
+ r = qemu_rbd_convert_luks_options(
+ qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
+ &passphrase, &passphrase_len, errp);
+ if (r < 0) {
+ return r;
+ }
+ luks2_opts.passphrase = passphrase;
+ luks2_opts.passphrase_size = passphrase_len;
+ break;
+ }
+ default: {
+ r = -ENOTSUP;
+ error_setg_errno(
+ errp, -r, "unknown image encryption format: %u",
+ encrypt->format);
+ return r;
+ }
}
+
+ r = rbd_encryption_load(image, format, opts, opts_size);
+ if (r < 0) {
+ error_setg_errno(errp, -r, "encryption load fail");
+ return r;
+ }
+
+ return 0;
}
+#endif
/* FIXME Deprecate and remove keypairs or make it available in QMP. */
static int qemu_rbd_do_create(BlockdevCreateOptions *options,
@@ -368,6 +535,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
return -EINVAL;
}
+#ifndef LIBRBD_SUPPORTS_ENCRYPTION
+ if (opts->has_encrypt) {
+ error_setg(errp, "RBD library does not support image encryption");
+ return -ENOTSUP;
+ }
+#endif
+
if (opts->has_cluster_size) {
int64_t objsize = opts->cluster_size;
if ((objsize - 1) & objsize) { /* not a power of 2? */
@@ -393,6 +567,28 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
goto out;
}
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+ if (opts->has_encrypt) {
+ rbd_image_t image;
+
+ ret = rbd_open(io_ctx, opts->location->image, &image, NULL);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "error opening image '%s' for encryption format",
+ opts->location->image);
+ goto out;
+ }
+
+ ret = qemu_rbd_encryption_format(image, opts->encrypt, errp);
+ rbd_close(image);
+ if (ret < 0) {
+ /* encryption format fail, try removing the image */
+ rbd_remove(io_ctx, opts->location->image);
+ goto out;
+ }
+ }
+#endif
+
ret = 0;
out:
rados_ioctx_destroy(io_ctx);
@@ -405,6 +601,43 @@ static int qemu_rbd_co_create(BlockdevCreateOptions *options, Error **errp)
return qemu_rbd_do_create(options, NULL, NULL, errp);
}
+static int qemu_rbd_extract_encryption_create_options(
+ QemuOpts *opts,
+ RbdEncryptionCreateOptions **spec,
+ Error **errp)
+{
+ QDict *opts_qdict;
+ QDict *encrypt_qdict;
+ Visitor *v;
+ int ret = 0;
+
+ opts_qdict = qemu_opts_to_qdict(opts, NULL);
+ qdict_extract_subqdict(opts_qdict, &encrypt_qdict, "encrypt.");
+ qobject_unref(opts_qdict);
+ if (!qdict_size(encrypt_qdict)) {
+ *spec = NULL;
+ goto exit;
+ }
+
+ /* Convert options into a QAPI object */
+ v = qobject_input_visitor_new_flat_confused(encrypt_qdict, errp);
+ if (!v) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ visit_type_RbdEncryptionCreateOptions(v, NULL, spec, errp);
+ visit_free(v);
+ if (!*spec) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+exit:
+ qobject_unref(encrypt_qdict);
+ return ret;
+}
+
static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
const char *filename,
QemuOpts *opts,
@@ -413,6 +646,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
BlockdevCreateOptions *create_options;
BlockdevCreateOptionsRbd *rbd_opts;
BlockdevOptionsRbd *loc;
+ RbdEncryptionCreateOptions *encrypt = NULL;
Error *local_err = NULL;
const char *keypairs, *password_secret;
QDict *options = NULL;
@@ -441,6 +675,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
goto exit;
}
+ ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
+ if (ret < 0) {
+ goto exit;
+ }
+ rbd_opts->encrypt = encrypt;
+ rbd_opts->has_encrypt = !!encrypt;
+
/*
* Caution: while qdict_get_try_str() is fine, getting non-string
* types would require more care. When @options come from -blockdev
@@ -469,53 +710,6 @@ exit:
return ret;
}
-/*
- * This aio completion is being called from rbd_finish_bh() and runs in qemu
- * BH context.
- */
-static void qemu_rbd_complete_aio(RADOSCB *rcb)
-{
- RBDAIOCB *acb = rcb->acb;
- int64_t r;
-
- r = rcb->ret;
-
- if (acb->cmd != RBD_AIO_READ) {
- if (r < 0) {
- acb->ret = r;
- acb->error = 1;
- } else if (!acb->error) {
- acb->ret = rcb->size;
- }
- } else {
- if (r < 0) {
- qemu_rbd_memset(rcb, 0);
- acb->ret = r;
- acb->error = 1;
- } else if (r < rcb->size) {
- qemu_rbd_memset(rcb, r);
- if (!acb->error) {
- acb->ret = rcb->size;
- }
- } else if (!acb->error) {
- acb->ret = r;
- }
- }
-
- g_free(rcb);
-
- if (!LIBRBD_USE_IOVEC) {
- if (acb->cmd == RBD_AIO_READ) {
- qemu_iovec_from_buf(acb->qiov, 0, acb->bounce, acb->qiov->size);
- }
- qemu_vfree(acb->bounce);
- }
-
- acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret));
-
- qemu_aio_unref(acb);
-}
-
static char *qemu_rbd_mon_host(BlockdevOptionsRbd *opts, Error **errp)
{
const char **vals;
@@ -702,6 +896,7 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
const QDictEntry *e;
Error *local_err = NULL;
char *keypairs, *secretid;
+ rbd_image_info_t info;
int r;
keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
@@ -766,30 +961,49 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
goto failed_open;
}
- r = rbd_get_size(s->image, &s->image_size);
+ if (opts->has_encrypt) {
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+ r = qemu_rbd_encryption_load(s->image, opts->encrypt, errp);
+ if (r < 0) {
+ goto failed_post_open;
+ }
+#else
+ r = -ENOTSUP;
+ error_setg(errp, "RBD library does not support image encryption");
+ goto failed_post_open;
+#endif
+ }
+
+ r = rbd_stat(s->image, &info, sizeof(info));
if (r < 0) {
- error_setg_errno(errp, -r, "error getting image size from %s",
+ error_setg_errno(errp, -r, "error getting image info from %s",
s->image_name);
- rbd_close(s->image);
- goto failed_open;
+ goto failed_post_open;
}
+ s->image_size = info.size;
+ s->object_size = info.obj_size;
/* If we are using an rbd snapshot, we must be r/o, otherwise
* leave as-is */
if (s->snap != NULL) {
r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
if (r < 0) {
- rbd_close(s->image);
- goto failed_open;
+ goto failed_post_open;
}
}
+#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
+ bs->supported_zero_flags = BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK;
+#endif
+
/* When extending regular files, we get zeros from the OS */
bs->supported_truncate_flags = BDRV_REQ_ZERO_WRITE;
r = 0;
goto out;
+failed_post_open:
+ rbd_close(s->image);
failed_open:
rados_ioctx_destroy(s->io_ctx);
g_free(s->snap);
@@ -849,229 +1063,213 @@ static int qemu_rbd_resize(BlockDriverState *bs, uint64_t size)
return 0;
}
-static const AIOCBInfo rbd_aiocb_info = {
- .aiocb_size = sizeof(RBDAIOCB),
-};
-
-static void rbd_finish_bh(void *opaque)
+static void qemu_rbd_finish_bh(void *opaque)
{
- RADOSCB *rcb = opaque;
- qemu_rbd_complete_aio(rcb);
+ RBDTask *task = opaque;
+ task->complete = true;
+ aio_co_wake(task->co);
}
/*
- * This is the callback function for rbd_aio_read and _write
+ * This is the completion callback function for all rbd aio calls
+ * started from qemu_rbd_start_co().
*
* Note: this function is being called from a non qemu thread so
* we need to be careful about what we do here. Generally we only
* schedule a BH, and do the rest of the io completion handling
- * from rbd_finish_bh() which runs in a qemu context.
+ * from qemu_rbd_finish_bh() which runs in a qemu context.
*/
-static void rbd_finish_aiocb(rbd_completion_t c, RADOSCB *rcb)
+static void qemu_rbd_completion_cb(rbd_completion_t c, RBDTask *task)
{
- RBDAIOCB *acb = rcb->acb;
-
- rcb->ret = rbd_aio_get_return_value(c);
+ task->ret = rbd_aio_get_return_value(c);
rbd_aio_release(c);
-
- replay_bh_schedule_oneshot_event(bdrv_get_aio_context(acb->common.bs),
- rbd_finish_bh, rcb);
-}
-
-static int rbd_aio_discard_wrapper(rbd_image_t image,
- uint64_t off,
- uint64_t len,
- rbd_completion_t comp)
-{
-#ifdef LIBRBD_SUPPORTS_DISCARD
- return rbd_aio_discard(image, off, len, comp);
-#else
- return -ENOTSUP;
-#endif
-}
-
-static int rbd_aio_flush_wrapper(rbd_image_t image,
- rbd_completion_t comp)
-{
-#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
- return rbd_aio_flush(image, comp);
-#else
- return -ENOTSUP;
-#endif
+ aio_bh_schedule_oneshot(bdrv_get_aio_context(task->bs),
+ qemu_rbd_finish_bh, task);
}
-static BlockAIOCB *rbd_start_aio(BlockDriverState *bs,
- int64_t off,
- QEMUIOVector *qiov,
- int64_t size,
- BlockCompletionFunc *cb,
- void *opaque,
- RBDAIOCmd cmd)
+static int coroutine_fn qemu_rbd_start_co(BlockDriverState *bs,
+ uint64_t offset,
+ uint64_t bytes,
+ QEMUIOVector *qiov,
+ int flags,
+ RBDAIOCmd cmd)
{
- RBDAIOCB *acb;
- RADOSCB *rcb = NULL;
+ BDRVRBDState *s = bs->opaque;
+ RBDTask task = { .bs = bs, .co = qemu_coroutine_self() };
rbd_completion_t c;
int r;
- BDRVRBDState *s = bs->opaque;
-
- acb = qemu_aio_get(&rbd_aiocb_info, bs, cb, opaque);
- acb->cmd = cmd;
- acb->qiov = qiov;
- assert(!qiov || qiov->size == size);
-
- rcb = g_new(RADOSCB, 1);
-
- if (!LIBRBD_USE_IOVEC) {
- if (cmd == RBD_AIO_DISCARD || cmd == RBD_AIO_FLUSH) {
- acb->bounce = NULL;
- } else {
- acb->bounce = qemu_try_blockalign(bs, qiov->size);
- if (acb->bounce == NULL) {
- goto failed;
- }
- }
- if (cmd == RBD_AIO_WRITE) {
- qemu_iovec_to_buf(acb->qiov, 0, acb->bounce, qiov->size);
- }
- rcb->buf = acb->bounce;
- }
-
- acb->ret = 0;
- acb->error = 0;
- acb->s = s;
+ assert(!qiov || qiov->size == bytes);
- rcb->acb = acb;
- rcb->s = acb->s;
- rcb->size = size;
- r = rbd_aio_create_completion(rcb, (rbd_callback_t) rbd_finish_aiocb, &c);
+ r = rbd_aio_create_completion(&task,
+ (rbd_callback_t) qemu_rbd_completion_cb, &c);
if (r < 0) {
- goto failed;
+ return r;
}
switch (cmd) {
- case RBD_AIO_WRITE: {
- /*
- * RBD APIs don't allow us to write more than actual size, so in order
- * to support growing images, we resize the image before write
- * operations that exceed the current size.
- */
- if (off + size > s->image_size) {
- r = qemu_rbd_resize(bs, off + size);
- if (r < 0) {
- goto failed_completion;
- }
- }
-#ifdef LIBRBD_SUPPORTS_IOVEC
- r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, off, c);
-#else
- r = rbd_aio_write(s->image, off, size, rcb->buf, c);
-#endif
- break;
- }
case RBD_AIO_READ:
-#ifdef LIBRBD_SUPPORTS_IOVEC
- r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, off, c);
-#else
- r = rbd_aio_read(s->image, off, size, rcb->buf, c);
-#endif
+ r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, offset, c);
+ break;
+ case RBD_AIO_WRITE:
+ r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, offset, c);
break;
case RBD_AIO_DISCARD:
- r = rbd_aio_discard_wrapper(s->image, off, size, c);
+ r = rbd_aio_discard(s->image, offset, bytes, c);
break;
case RBD_AIO_FLUSH:
- r = rbd_aio_flush_wrapper(s->image, c);
+ r = rbd_aio_flush(s->image, c);
break;
+#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
+ case RBD_AIO_WRITE_ZEROES: {
+ int zero_flags = 0;
+#ifdef RBD_WRITE_ZEROES_FLAG_THICK_PROVISION
+ if (!(flags & BDRV_REQ_MAY_UNMAP)) {
+ zero_flags = RBD_WRITE_ZEROES_FLAG_THICK_PROVISION;
+ }
+#endif
+ r = rbd_aio_write_zeroes(s->image, offset, bytes, c, zero_flags, 0);
+ break;
+ }
+#endif
default:
r = -EINVAL;
}
if (r < 0) {
- goto failed_completion;
+ error_report("rbd request failed early: cmd %d offset %" PRIu64
+ " bytes %" PRIu64 " flags %d r %d (%s)", cmd, offset,
+ bytes, flags, r, strerror(-r));
+ rbd_aio_release(c);
+ return r;
}
- return &acb->common;
-failed_completion:
- rbd_aio_release(c);
-failed:
- g_free(rcb);
- if (!LIBRBD_USE_IOVEC) {
- qemu_vfree(acb->bounce);
+ while (!task.complete) {
+ qemu_coroutine_yield();
}
- qemu_aio_unref(acb);
- return NULL;
+ if (task.ret < 0) {
+ error_report("rbd request failed: cmd %d offset %" PRIu64 " bytes %"
+ PRIu64 " flags %d task.ret %" PRIi64 " (%s)", cmd, offset,
+ bytes, flags, task.ret, strerror(-task.ret));
+ return task.ret;
+ }
+
+ /* zero pad short reads */
+ if (cmd == RBD_AIO_READ && task.ret < qiov->size) {
+ qemu_iovec_memset(qiov, task.ret, 0, qiov->size - task.ret);
+ }
+
+ return 0;
}
-static BlockAIOCB *qemu_rbd_aio_preadv(BlockDriverState *bs,
- uint64_t offset, uint64_t bytes,
- QEMUIOVector *qiov, int flags,
- BlockCompletionFunc *cb,
- void *opaque)
+static int
+coroutine_fn qemu_rbd_co_preadv(BlockDriverState *bs, uint64_t offset,
+ uint64_t bytes, QEMUIOVector *qiov,
+ int flags)
{
- return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
- RBD_AIO_READ);
+ return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_READ);
}
-static BlockAIOCB *qemu_rbd_aio_pwritev(BlockDriverState *bs,
- uint64_t offset, uint64_t bytes,
- QEMUIOVector *qiov, int flags,
- BlockCompletionFunc *cb,
- void *opaque)
+static int
+coroutine_fn qemu_rbd_co_pwritev(BlockDriverState *bs, uint64_t offset,
+ uint64_t bytes, QEMUIOVector *qiov,
+ int flags)
{
- return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
- RBD_AIO_WRITE);
+ BDRVRBDState *s = bs->opaque;
+ /*
+ * RBD APIs don't allow us to write more than actual size, so in order
+ * to support growing images, we resize the image before write
+ * operations that exceed the current size.
+ */
+ if (offset + bytes > s->image_size) {
+ int r = qemu_rbd_resize(bs, offset + bytes);
+ if (r < 0) {
+ return r;
+ }
+ }
+ return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_WRITE);
}
-#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
-static BlockAIOCB *qemu_rbd_aio_flush(BlockDriverState *bs,
- BlockCompletionFunc *cb,
- void *opaque)
+static int coroutine_fn qemu_rbd_co_flush(BlockDriverState *bs)
{
- return rbd_start_aio(bs, 0, NULL, 0, cb, opaque, RBD_AIO_FLUSH);
+ return qemu_rbd_start_co(bs, 0, 0, NULL, 0, RBD_AIO_FLUSH);
}
-#else
+static int coroutine_fn qemu_rbd_co_pdiscard(BlockDriverState *bs,
+ int64_t offset, int count)
+{
+ return qemu_rbd_start_co(bs, offset, count, NULL, 0, RBD_AIO_DISCARD);
+}
-static int qemu_rbd_co_flush(BlockDriverState *bs)
+#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
+static int
+coroutine_fn qemu_rbd_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
+ int count, BdrvRequestFlags flags)
{
-#if LIBRBD_VERSION_CODE >= LIBRBD_VERSION(0, 1, 1)
- /* rbd_flush added in 0.1.1 */
- BDRVRBDState *s = bs->opaque;
- return rbd_flush(s->image);
-#else
- return 0;
-#endif
+ return qemu_rbd_start_co(bs, offset, count, NULL, flags,
+ RBD_AIO_WRITE_ZEROES);
}
#endif
static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
{
BDRVRBDState *s = bs->opaque;
- rbd_image_info_t info;
+ bdi->cluster_size = s->object_size;
+ return 0;
+}
+
+static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
+ Error **errp)
+{
+ BDRVRBDState *s = bs->opaque;
+ ImageInfoSpecific *spec_info;
+ char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
int r;
- r = rbd_stat(s->image, &info, sizeof(info));
- if (r < 0) {
- return r;
+ if (s->image_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
+ r = rbd_read(s->image, 0,
+ RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
+ if (r < 0) {
+ error_setg_errno(errp, -r, "cannot read image start for probe");
+ return NULL;
+ }
}
- bdi->cluster_size = info.obj_size;
- return 0;
+ spec_info = g_new(ImageInfoSpecific, 1);
+ *spec_info = (ImageInfoSpecific){
+ .type = IMAGE_INFO_SPECIFIC_KIND_RBD,
+ .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
+ };
+
+ if (memcmp(buf, rbd_luks_header_verification,
+ RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+ spec_info->u.rbd.data->encryption_format =
+ RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
+ spec_info->u.rbd.data->has_encryption_format = true;
+ } else if (memcmp(buf, rbd_luks2_header_verification,
+ RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+ spec_info->u.rbd.data->encryption_format =
+ RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
+ spec_info->u.rbd.data->has_encryption_format = true;
+ } else {
+ spec_info->u.rbd.data->has_encryption_format = false;
+ }
+
+ return spec_info;
}
static int64_t qemu_rbd_getlength(BlockDriverState *bs)
{
BDRVRBDState *s = bs->opaque;
- rbd_image_info_t info;
int r;
- r = rbd_stat(s->image, &info, sizeof(info));
+ r = rbd_get_size(s->image, &s->image_size);
if (r < 0) {
return r;
}
- return info.size;
+ return s->image_size;
}
static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs,
@@ -1210,19 +1408,6 @@ static int qemu_rbd_snap_list(BlockDriverState *bs,
return snap_count;
}
-#ifdef LIBRBD_SUPPORTS_DISCARD
-static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs,
- int64_t offset,
- int bytes,
- BlockCompletionFunc *cb,
- void *opaque)
-{
- return rbd_start_aio(bs, offset, NULL, bytes, cb, opaque,
- RBD_AIO_DISCARD);
-}
-#endif
-
-#ifdef LIBRBD_SUPPORTS_INVALIDATE
static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs,
Error **errp)
{
@@ -1232,7 +1417,6 @@ static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs,
error_setg_errno(errp, -r, "Failed to invalidate the cache");
}
}
-#endif
static QemuOptsList qemu_rbd_create_opts = {
.name = "rbd-create-opts",
@@ -1253,6 +1437,22 @@ static QemuOptsList qemu_rbd_create_opts = {
.type = QEMU_OPT_STRING,
.help = "ID of secret providing the password",
},
+ {
+ .name = "encrypt.format",
+ .type = QEMU_OPT_STRING,
+ .help = "Encrypt the image, format choices: 'luks', 'luks2'",
+ },
+ {
+ .name = "encrypt.cipher-alg",
+ .type = QEMU_OPT_STRING,
+ .help = "Name of encryption cipher algorithm"
+ " (allowed values: aes-128, aes-256)",
+ },
+ {
+ .name = "encrypt.key-secret",
+ .type = QEMU_OPT_STRING,
+ .help = "ID of secret providing LUKS passphrase",
+ },
{ /* end of list */ }
}
};
@@ -1274,7 +1474,6 @@ static BlockDriver bdrv_rbd = {
.format_name = "rbd",
.instance_size = sizeof(BDRVRBDState),
.bdrv_parse_filename = qemu_rbd_parse_filename,
- .bdrv_refresh_limits = qemu_rbd_refresh_limits,
.bdrv_file_open = qemu_rbd_open,
.bdrv_close = qemu_rbd_close,
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
@@ -1282,31 +1481,25 @@ static BlockDriver bdrv_rbd = {
.bdrv_co_create_opts = qemu_rbd_co_create_opts,
.bdrv_has_zero_init = bdrv_has_zero_init_1,
.bdrv_get_info = qemu_rbd_getinfo,
+ .bdrv_get_specific_info = qemu_rbd_get_specific_info,
.create_opts = &qemu_rbd_create_opts,
.bdrv_getlength = qemu_rbd_getlength,
.bdrv_co_truncate = qemu_rbd_co_truncate,
.protocol_name = "rbd",
- .bdrv_aio_preadv = qemu_rbd_aio_preadv,
- .bdrv_aio_pwritev = qemu_rbd_aio_pwritev,
-
-#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
- .bdrv_aio_flush = qemu_rbd_aio_flush,
-#else
+ .bdrv_co_preadv = qemu_rbd_co_preadv,
+ .bdrv_co_pwritev = qemu_rbd_co_pwritev,
.bdrv_co_flush_to_disk = qemu_rbd_co_flush,
-#endif
-
-#ifdef LIBRBD_SUPPORTS_DISCARD
- .bdrv_aio_pdiscard = qemu_rbd_aio_pdiscard,
+ .bdrv_co_pdiscard = qemu_rbd_co_pdiscard,
+#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
+ .bdrv_co_pwrite_zeroes = qemu_rbd_co_pwrite_zeroes,
#endif
.bdrv_snapshot_create = qemu_rbd_snap_create,
.bdrv_snapshot_delete = qemu_rbd_snap_remove,
.bdrv_snapshot_list = qemu_rbd_snap_list,
.bdrv_snapshot_goto = qemu_rbd_snap_rollback,
-#ifdef LIBRBD_SUPPORTS_INVALIDATE
.bdrv_co_invalidate_cache = qemu_rbd_co_invalidate_cache,
-#endif
.strong_runtime_opts = qemu_rbd_strong_runtime_opts,
};
diff --git a/block/replication.c b/block/replication.c
index 52163f2..774e15d 100644
--- a/block/replication.c
+++ b/block/replication.c
@@ -390,7 +390,14 @@ static void reopen_backing_file(BlockDriverState *bs, bool writable,
}
if (reopen_queue) {
+ AioContext *ctx = bdrv_get_aio_context(bs);
+ if (ctx != qemu_get_aio_context()) {
+ aio_context_release(ctx);
+ }
bdrv_reopen_multiple(reopen_queue, errp);
+ if (ctx != qemu_get_aio_context()) {
+ aio_context_acquire(ctx);
+ }
}
bdrv_subtree_drained_end(s->hidden_disk->bs);
diff --git a/block/ssh.c b/block/ssh.c
index d008caf..e0fbb49 100644
--- a/block/ssh.c
+++ b/block/ssh.c
@@ -237,9 +237,7 @@ static int parse_uri(const char *filename, QDict *options, Error **errp)
return 0;
err:
- if (uri) {
- uri_free(uri);
- }
+ uri_free(uri);
return -EINVAL;
}
diff --git a/blockdev.c b/blockdev.c
index f08192d..3d8ac36 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1714,6 +1714,7 @@ static void drive_backup_prepare(BlkActionState *common, Error **errp)
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
+ state->bs = bs;
/* Paired with .clean() */
bdrv_drained_begin(bs);
@@ -1813,8 +1814,6 @@ static void drive_backup_prepare(BlkActionState *common, Error **errp)
}
}
- state->bs = bs;
-
state->job = do_backup_common(qapi_DriveBackup_base(backup),
bs, target_bs, aio_context,
common->block_job_txn, errp);
@@ -3560,46 +3559,60 @@ fail:
visit_free(v);
}
-void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp)
+void qmp_blockdev_reopen(BlockdevOptionsList *reopen_list, Error **errp)
{
- BlockDriverState *bs;
- AioContext *ctx;
- QObject *obj;
- Visitor *v = qobject_output_visitor_new(&obj);
- BlockReopenQueue *queue;
- QDict *qdict;
+ BlockReopenQueue *queue = NULL;
+ GSList *drained = NULL;
- /* Check for the selected node name */
- if (!options->has_node_name) {
- error_setg(errp, "node-name not specified");
- goto fail;
- }
+ /* Add each one of the BDS that we want to reopen to the queue */
+ for (; reopen_list != NULL; reopen_list = reopen_list->next) {
+ BlockdevOptions *options = reopen_list->value;
+ BlockDriverState *bs;
+ AioContext *ctx;
+ QObject *obj;
+ Visitor *v;
+ QDict *qdict;
- bs = bdrv_find_node(options->node_name);
- if (!bs) {
- error_setg(errp, "Failed to find node with node-name='%s'",
- options->node_name);
- goto fail;
- }
+ /* Check for the selected node name */
+ if (!options->has_node_name) {
+ error_setg(errp, "node-name not specified");
+ goto fail;
+ }
- /* Put all options in a QDict and flatten it */
- visit_type_BlockdevOptions(v, NULL, &options, &error_abort);
- visit_complete(v, &obj);
- qdict = qobject_to(QDict, obj);
+ bs = bdrv_find_node(options->node_name);
+ if (!bs) {
+ error_setg(errp, "Failed to find node with node-name='%s'",
+ options->node_name);
+ goto fail;
+ }
- qdict_flatten(qdict);
+ /* Put all options in a QDict and flatten it */
+ v = qobject_output_visitor_new(&obj);
+ visit_type_BlockdevOptions(v, NULL, &options, &error_abort);
+ visit_complete(v, &obj);
+ visit_free(v);
+
+ qdict = qobject_to(QDict, obj);
+
+ qdict_flatten(qdict);
+
+ ctx = bdrv_get_aio_context(bs);
+ aio_context_acquire(ctx);
+
+ bdrv_subtree_drained_begin(bs);
+ queue = bdrv_reopen_queue(queue, bs, qdict, false);
+ drained = g_slist_prepend(drained, bs);
+
+ aio_context_release(ctx);
+ }
/* Perform the reopen operation */
- ctx = bdrv_get_aio_context(bs);
- aio_context_acquire(ctx);
- bdrv_subtree_drained_begin(bs);
- queue = bdrv_reopen_queue(NULL, bs, qdict, false);
bdrv_reopen_multiple(queue, errp);
- bdrv_subtree_drained_end(bs);
- aio_context_release(ctx);
+ queue = NULL;
fail:
- visit_free(v);
+ bdrv_reopen_queue_free(queue);
+ g_slist_free_full(drained, (GDestroyNotify) bdrv_subtree_drained_end);
}
void qmp_blockdev_del(const char *node_name, Error **errp)
diff --git a/default-configs/devices/arm-softmmu.mak b/default-configs/devices/arm-softmmu.mak
index 0500156..cdc0e97 100644
--- a/default-configs/devices/arm-softmmu.mak
+++ b/default-configs/devices/arm-softmmu.mak
@@ -18,6 +18,7 @@ CONFIG_CHEETAH=y
CONFIG_SX1=y
CONFIG_NSERIES=y
CONFIG_STELLARIS=y
+CONFIG_STM32VLDISCOVERY=y
CONFIG_REALVIEW=y
CONFIG_VERSATILE=y
CONFIG_VEXPRESS=y
diff --git a/default-configs/devices/ppc-softmmu.mak b/default-configs/devices/ppc-softmmu.mak
index c2d4119..4535993 100644
--- a/default-configs/devices/ppc-softmmu.mak
+++ b/default-configs/devices/ppc-softmmu.mak
@@ -14,7 +14,7 @@ CONFIG_SAM460EX=y
CONFIG_MAC_OLDWORLD=y
CONFIG_MAC_NEWWORLD=y
-CONFIG_PEGASOS2=n
+CONFIG_PEGASOS2=y
# For PReP
CONFIG_PREP=y
diff --git a/docs/system/arm/stm32.rst b/docs/system/arm/stm32.rst
new file mode 100644
index 0000000..508b92c
--- /dev/null
+++ b/docs/system/arm/stm32.rst
@@ -0,0 +1,66 @@
+STMicroelectronics STM32 boards (``netduino2``, ``netduinoplus2``, ``stm32vldiscovery``)
+========================================================================================
+
+The `STM32`_ chips are a family of 32-bit ARM-based microcontroller by
+STMicroelectronics.
+
+.. _STM32: https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html
+
+The STM32F1 series is based on ARM Cortex-M3 core. The following machines are
+based on this chip :
+
+- ``stm32vldiscovery`` STM32VLDISCOVERY board with STM32F100RBT6 microcontroller
+
+The STM32F2 series is based on ARM Cortex-M3 core. The following machines are
+based on this chip :
+
+- ``netduino2`` Netduino 2 board with STM32F205RFT6 microcontroller
+
+The STM32F4 series is based on ARM Cortex-M4F core. This series is pin-to-pin
+compatible with STM32F2 series. The following machines are based on this chip :
+
+- ``netduinoplus2`` Netduino Plus 2 board with STM32F405RGT6 microcontroller
+
+There are many other STM32 series that are currently not supported by QEMU.
+
+Supported devices
+-----------------
+
+ * ARM Cortex-M3, Cortex M4F
+ * Analog to Digital Converter (ADC)
+ * EXTI interrupt
+ * Serial ports (USART)
+ * SPI controller
+ * System configuration (SYSCFG)
+ * Timer controller (TIMER)
+
+Missing devices
+---------------
+
+ * Camera interface (DCMI)
+ * Controller Area Network (CAN)
+ * Cycle Redundancy Check (CRC) calculation unit
+ * Digital to Analog Converter (DAC)
+ * DMA controller
+ * Ethernet controller
+ * Flash Interface Unit
+ * GPIO controller
+ * I2C controller
+ * Inter-Integrated Sound (I2S) controller
+ * Power supply configuration (PWR)
+ * Random Number Generator (RNG)
+ * Real-Time Clock (RTC) controller
+ * Reset and Clock Controller (RCC)
+ * Secure Digital Input/Output (SDIO) interface
+ * USB OTG
+ * Watchdog controller (IWDG, WWDG)
+
+Boot options
+------------
+
+The STM32 machines can be started using the ``-kernel`` option to load a
+firmware. Example:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -M stm32vldiscovery -kernel firmware.bin
diff --git a/docs/system/deprecated.rst b/docs/system/deprecated.rst
index 70e08ba..6d438f1 100644
--- a/docs/system/deprecated.rst
+++ b/docs/system/deprecated.rst
@@ -221,6 +221,24 @@ This machine is deprecated because we have enough AST2500 based OpenPOWER
machines. It can be easily replaced by the ``witherspoon-bmc`` or the
``romulus-bmc`` machines.
+Backend options
+---------------
+
+Using non-persistent backing file with pmem=on (since 6.1)
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+This option is used when ``memory-backend-file`` is consumed by emulated NVDIMM
+device. However enabling ``memory-backend-file.pmem`` option, when backing file
+is (a) not DAX capable or (b) not on a filesystem that support direct mapping
+of persistent memory, is not safe and may lead to data loss or corruption in case
+of host crash.
+Options are:
+
+ - modify VM configuration to set ``pmem=off`` to continue using fake NVDIMM
+ (without persistence guaranties) with backing file on non DAX storage
+ - move backing file to NVDIMM storage and keep ``pmem=on``
+ (to have NVDIMM with persistence guaranties).
+
Device options
--------------
@@ -282,38 +300,6 @@ this CPU is also deprecated.
Related binaries
----------------
-qemu-img amend to adjust backing file (since 5.1)
-'''''''''''''''''''''''''''''''''''''''''''''''''
-
-The use of ``qemu-img amend`` to modify the name or format of a qcow2
-backing image is deprecated; this functionality was never fully
-documented or tested, and interferes with other amend operations that
-need access to the original backing image (such as deciding whether a
-v3 zero cluster may be left unallocated when converting to a v2
-image). Rather, any changes to the backing chain should be performed
-with ``qemu-img rebase -u`` either before or after the remaining
-changes being performed by amend, as appropriate.
-
-qemu-img backing file without format (since 5.1)
-''''''''''''''''''''''''''''''''''''''''''''''''
-
-The use of ``qemu-img create``, ``qemu-img rebase``, or ``qemu-img
-convert`` to create or modify an image that depends on a backing file
-now recommends that an explicit backing format be provided. This is
-for safety: if QEMU probes a different format than what you thought,
-the data presented to the guest will be corrupt; similarly, presenting
-a raw image to a guest allows a potential security exploit if a future
-probe sees a non-raw image based on guest writes.
-
-To avoid the warning message, or even future refusal to create an
-unsafe image, you must pass ``-o backing_fmt=`` (or the shorthand
-``-F`` during create) to specify the intended backing format. You may
-use ``qemu-img rebase -u`` to retroactively add a backing format to an
-existing image. However, be aware that there are already potential
-security risks to blindly using ``qemu-img info`` to probe the format
-of an untrusted backing image, when deciding what format to add into
-an existing image.
-
Backwards compatibility
-----------------------
diff --git a/docs/system/ppc/ppce500.rst b/docs/system/ppc/ppce500.rst
index 7a815c1..afc58f6 100644
--- a/docs/system/ppc/ppce500.rst
+++ b/docs/system/ppc/ppce500.rst
@@ -19,6 +19,7 @@ The ``ppce500`` machine supports the following devices:
* Power-off functionality via one GPIO pin
* 1 Freescale MPC8xxx PCI host controller
* VirtIO devices via PCI bus
+* 1 Freescale Enhanced Triple Speed Ethernet controller (eTSEC)
Hardware configuration information
----------------------------------
@@ -121,7 +122,7 @@ To boot the 32-bit Linux kernel:
Running U-Boot
--------------
-U-Boot mainline v2021.04 release is tested at the time of writing. To build a
+U-Boot mainline v2021.07 release is tested at the time of writing. To build a
U-Boot mainline bootloader that can be booted by the ``ppce500`` machine, use
the qemu-ppce500_defconfig with similar commands as described above for Linux:
@@ -154,3 +155,10 @@ interface at PCI address 0.1.0, but we can switch that to an e1000 NIC by:
-display none -serial stdio \
-bios u-boot \
-nic tap,ifname=tap0,script=no,downscript=no,model=e1000
+
+The QEMU ``ppce500`` machine can also dynamically instantiate an eTSEC device
+if “-device eTSEC” is given to QEMU:
+
+.. code-block:: bash
+
+ -netdev tap,ifname=tap0,script=no,downscript=no,id=net0 -device eTSEC,netdev=net0
diff --git a/docs/system/removed-features.rst b/docs/system/removed-features.rst
index 2b21bd3..28bb035 100644
--- a/docs/system/removed-features.rst
+++ b/docs/system/removed-features.rst
@@ -491,6 +491,37 @@ topologies described with -smp include all possible cpus, i.e.
The ``enforce-config-section`` property was replaced by the
``-global migration.send-configuration={on|off}`` option.
+qemu-img amend to adjust backing file (removed in 6.1)
+''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+The use of ``qemu-img amend`` to modify the name or format of a qcow2
+backing image was never fully documented or tested, and interferes
+with other amend operations that need access to the original backing
+image (such as deciding whether a v3 zero cluster may be left
+unallocated when converting to a v2 image). Any changes to the
+backing chain should be performed with ``qemu-img rebase -u`` either
+before or after the remaining changes being performed by amend, as
+appropriate.
+
+qemu-img backing file without format (removed in 6.1)
+'''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+The use of ``qemu-img create``, ``qemu-img rebase``, or ``qemu-img
+convert`` to create or modify an image that depends on a backing file
+now requires that an explicit backing format be provided. This is
+for safety: if QEMU probes a different format than what you thought,
+the data presented to the guest will be corrupt; similarly, presenting
+a raw image to a guest allows a potential security exploit if a future
+probe sees a non-raw image based on guest writes.
+
+To avoid creating unsafe backing chains, you must pass ``-o
+backing_fmt=`` (or the shorthand ``-F`` during create) to specify the
+intended backing format. You may use ``qemu-img rebase -u`` to
+retroactively add a backing format to an existing image. However, be
+aware that there are already potential security risks to blindly using
+``qemu-img info`` to probe the format of an untrusted backing image,
+when deciding what format to add into an existing image.
+
Block devices
-------------
diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst
index 13b3eea..705b883 100644
--- a/docs/system/target-arm.rst
+++ b/docs/system/target-arm.rst
@@ -97,6 +97,7 @@ undocumented; you can get a complete list by running
arm/collie
arm/sx1
arm/stellaris
+ arm/stm32
arm/virt
arm/xlnx-versal-virt
diff --git a/hw/Kconfig b/hw/Kconfig
index 805860f..8cb7664 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -32,6 +32,7 @@ source remote/Kconfig
source rtc/Kconfig
source scsi/Kconfig
source sd/Kconfig
+source sensor/Kconfig
source smbios/Kconfig
source ssi/Kconfig
source timer/Kconfig
diff --git a/hw/adc/Kconfig b/hw/adc/Kconfig
index 25d2229..a825bd3 100644
--- a/hw/adc/Kconfig
+++ b/hw/adc/Kconfig
@@ -1,2 +1,5 @@
config STM32F2XX_ADC
bool
+
+config MAX111X
+ bool
diff --git a/hw/misc/max111x.c b/hw/adc/max111x.c
index 1b3234a..e8bf4cc 100644
--- a/hw/misc/max111x.c
+++ b/hw/adc/max111x.c
@@ -11,7 +11,7 @@
*/
#include "qemu/osdep.h"
-#include "hw/misc/max111x.h"
+#include "hw/adc/max111x.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
#include "qemu/module.h"
diff --git a/hw/adc/meson.build b/hw/adc/meson.build
index 6ddee23..ac4f093 100644
--- a/hw/adc/meson.build
+++ b/hw/adc/meson.build
@@ -1,2 +1,4 @@
softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
+softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq-xadc.c'))
+softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
diff --git a/hw/misc/zynq-xadc.c b/hw/adc/zynq-xadc.c
index 7b1972c..cfc7bab 100644
--- a/hw/misc/zynq-xadc.c
+++ b/hw/adc/zynq-xadc.c
@@ -15,7 +15,7 @@
#include "qemu/osdep.h"
#include "hw/irq.h"
-#include "hw/misc/zynq-xadc.h"
+#include "hw/adc/zynq-xadc.h"
#include "migration/vmstate.h"
#include "qemu/timer.h"
#include "qemu/log.h"
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 647b5c8..afe13e9 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -239,6 +239,10 @@ config STELLARIS
select STELLARIS_ENET # ethernet
select UNIMP
+config STM32VLDISCOVERY
+ bool
+ select STM32F100_SOC
+
config STRONGARM
bool
select PXA2XX
@@ -326,6 +330,12 @@ config RASPI
select SDHCI
select USB_DWC2
+config STM32F100_SOC
+ bool
+ select ARM_V7M
+ select STM32F2XX_USART
+ select STM32F2XX_SPI
+
config STM32F205_SOC
bool
select ARM_V7M
@@ -372,9 +382,12 @@ config XLNX_VERSAL
config NPCM7XX
bool
select A9MPCORE
+ select ADM1272
select ARM_GIC
select AT24C # EEPROM
+ select MAX34451
select PL310 # cache controller
+ select PMBUS
select SERIAL
select SSI
select UNIMP
diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c
index 1301e8f..9d43e26 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -17,7 +17,7 @@
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_eeprom.h"
#include "hw/misc/pca9552.h"
-#include "hw/misc/tmp105.h"
+#include "hw/sensor/tmp105.h"
#include "hw/misc/led.h"
#include "hw/qdev-properties.h"
#include "sysemu/block-backend.h"
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index be39117..721a8eb 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -24,6 +24,7 @@ arm_ss.add(when: 'CONFIG_Z2', if_true: files('z2.c'))
arm_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c'))
arm_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c'))
arm_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c'))
+arm_ss.add(when: 'CONFIG_STM32VLDISCOVERY', if_true: files('stm32vldiscovery.c'))
arm_ss.add(when: 'CONFIG_COLLIE', if_true: files('collie.c'))
arm_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c'))
arm_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c'))
@@ -39,6 +40,7 @@ arm_ss.add(when: 'CONFIG_STRONGARM', if_true: files('strongarm.c'))
arm_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10.c', 'cubieboard.c'))
arm_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3.c', 'orangepi.c'))
arm_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_peripherals.c', 'bcm2836.c', 'raspi.c'))
+arm_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'))
arm_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c'))
arm_ss.add(when: 'CONFIG_STM32F405_SOC', if_true: files('stm32f405_soc.c'))
arm_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp.c', 'xlnx-zcu102.c'))
diff --git a/hw/arm/nseries.c b/hw/arm/nseries.c
index 0aefa5d..906c915 100644
--- a/hw/arm/nseries.c
+++ b/hw/arm/nseries.c
@@ -34,9 +34,10 @@
#include "hw/boards.h"
#include "hw/i2c/i2c.h"
#include "hw/display/blizzard.h"
+#include "hw/input/lm832x.h"
#include "hw/input/tsc2xxx.h"
#include "hw/misc/cbus.h"
-#include "hw/misc/tmp105.h"
+#include "hw/sensor/tmp105.h"
#include "hw/qdev-properties.h"
#include "hw/block/flash.h"
#include "hw/hw.h"
@@ -416,7 +417,7 @@ static void n810_kbd_setup(struct n800_s *s)
/* Attach the LM8322 keyboard to the I2C bus,
* should happen in n8x0_i2c_setup and s->kbd be initialised here. */
s->kbd = DEVICE(i2c_slave_create_simple(omap_i2c_bus(s->mpu->i2c[0]),
- "lm8323", N810_LM8323_ADDR));
+ TYPE_LM8323, N810_LM8323_ADDR));
qdev_connect_gpio_out(s->kbd, 0, kbd_irq);
}
diff --git a/hw/arm/pxa2xx.c b/hw/arm/pxa2xx.c
index fdc4955..15a247e 100644
--- a/hw/arm/pxa2xx.c
+++ b/hw/arm/pxa2xx.c
@@ -1437,7 +1437,7 @@ static void pxa2xx_i2c_write(void *opaque, hwaddr addr,
break;
case ISAR:
- i2c_set_slave_address(I2C_SLAVE(s->slave), value & 0x7f);
+ i2c_slave_set_address(I2C_SLAVE(s->slave), value & 0x7f);
break;
case IDBR:
diff --git a/hw/arm/spitz.c b/hw/arm/spitz.c
index b45a929..5aab0b8 100644
--- a/hw/arm/spitz.c
+++ b/hw/arm/spitz.c
@@ -30,7 +30,7 @@
#include "audio/audio.h"
#include "hw/boards.h"
#include "hw/sysbus.h"
-#include "hw/misc/max111x.h"
+#include "hw/adc/max111x.h"
#include "migration/vmstate.h"
#include "exec/address-spaces.h"
#include "cpu.h"
@@ -769,9 +769,9 @@ static void spitz_wm8750_addr(void *opaque, int line, int level)
{
I2CSlave *wm = (I2CSlave *) opaque;
if (level)
- i2c_set_slave_address(wm, SPITZ_WM_ADDRH);
+ i2c_slave_set_address(wm, SPITZ_WM_ADDRH);
else
- i2c_set_slave_address(wm, SPITZ_WM_ADDRL);
+ i2c_slave_set_address(wm, SPITZ_WM_ADDRL);
}
static void spitz_i2c_setup(PXA2xxState *cpu)
diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c
index 8b4dab9..ad48cf2 100644
--- a/hw/arm/stellaris.c
+++ b/hw/arm/stellaris.c
@@ -1453,13 +1453,67 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board)
DeviceState *sddev;
DeviceState *ssddev;
- /* Some boards have both an OLED controller and SD card connected to
+ /*
+ * Some boards have both an OLED controller and SD card connected to
* the same SSI port, with the SD card chip select connected to a
* GPIO pin. Technically the OLED chip select is connected to the
* SSI Fss pin. We do not bother emulating that as both devices
* should never be selected simultaneously, and our OLED controller
* ignores stray 0xff commands that occur when deselecting the SD
* card.
+ *
+ * The h/w wiring is:
+ * - GPIO pin D0 is wired to the active-low SD card chip select
+ * - GPIO pin A3 is wired to the active-low OLED chip select
+ * - The SoC wiring of the PL061 "auxiliary function" for A3 is
+ * SSI0Fss ("frame signal"), which is an output from the SoC's
+ * SSI controller. The SSI controller takes SSI0Fss low when it
+ * transmits a frame, so it can work as a chip-select signal.
+ * - GPIO A4 is aux-function SSI0Rx, and wired to the SD card Tx
+ * (the OLED never sends data to the CPU, so no wiring needed)
+ * - GPIO A5 is aux-function SSI0Tx, and wired to the SD card Rx
+ * and the OLED display-data-in
+ * - GPIO A2 is aux-function SSI0Clk, wired to SD card and OLED
+ * serial-clock input
+ * So a guest that wants to use the OLED can configure the PL061
+ * to make pins A2, A3, A5 aux-function, so they are connected
+ * directly to the SSI controller. When the SSI controller sends
+ * data it asserts SSI0Fss which selects the OLED.
+ * A guest that wants to use the SD card configures A2, A4 and A5
+ * as aux-function, but leaves A3 as a software-controlled GPIO
+ * line. It asserts the SD card chip-select by using the PL061
+ * to control pin D0, and lets the SSI controller handle Clk, Tx
+ * and Rx. (The SSI controller asserts Fss during tx cycles as
+ * usual, but because A3 is not set to aux-function this is not
+ * forwarded to the OLED, and so the OLED stays unselected.)
+ *
+ * The QEMU implementation instead is:
+ * - GPIO pin D0 is wired to the active-low SD card chip select,
+ * and also to the OLED chip-select which is implemented
+ * as *active-high*
+ * - SSI controller signals go to the devices regardless of
+ * whether the guest programs A2, A4, A5 as aux-function or not
+ *
+ * The problem with this implementation is if the guest doesn't
+ * care about the SD card and only uses the OLED. In that case it
+ * may choose never to do anything with D0 (leaving it in its
+ * default floating state, which reliably leaves the card disabled
+ * because an SD card has a pullup on CS within the card itself),
+ * and only set up A2, A3, A5. This for us would mean the OLED
+ * never gets the chip-select assert it needs. We work around
+ * this with a manual raise of D0 here (despite board creation
+ * code being the wrong place to raise IRQ lines) to put the OLED
+ * into an initially selected state.
+ *
+ * In theory the right way to model this would be:
+ * - Implement aux-function support in the PL061, with an
+ * extra set of AFIN and AFOUT GPIO lines (set up so that
+ * if a GPIO line is in auxfn mode the main GPIO in and out
+ * track the AFIN and AFOUT lines)
+ * - Wire the AFOUT for D0 up to either a line from the
+ * SSI controller that's pulled low around every transmit,
+ * or at least to an always-0 line here on the board
+ * - Make the ssd0323 OLED controller chipselect active-low
*/
bus = qdev_get_child_bus(dev, "ssi");
diff --git a/hw/arm/stm32f100_soc.c b/hw/arm/stm32f100_soc.c
new file mode 100644
index 0000000..0c4a5c6
--- /dev/null
+++ b/hw/arm/stm32f100_soc.c
@@ -0,0 +1,182 @@
+/*
+ * STM32F100 SoC
+ *
+ * Copyright (c) 2021 Alexandre Iooss <erdnaxe@crans.org>
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/arm/boot.h"
+#include "exec/address-spaces.h"
+#include "hw/arm/stm32f100_soc.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/unimp.h"
+#include "sysemu/sysemu.h"
+
+/* stm32f100_soc implementation is derived from stm32f205_soc */
+
+static const uint32_t usart_addr[STM_NUM_USARTS] = { 0x40013800, 0x40004400,
+ 0x40004800 };
+static const uint32_t spi_addr[STM_NUM_SPIS] = { 0x40013000, 0x40003800 };
+
+static const int usart_irq[STM_NUM_USARTS] = {37, 38, 39};
+static const int spi_irq[STM_NUM_SPIS] = {35, 36};
+
+static void stm32f100_soc_initfn(Object *obj)
+{
+ STM32F100State *s = STM32F100_SOC(obj);
+ int i;
+
+ object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M);
+
+ for (i = 0; i < STM_NUM_USARTS; i++) {
+ object_initialize_child(obj, "usart[*]", &s->usart[i],
+ TYPE_STM32F2XX_USART);
+ }
+
+ for (i = 0; i < STM_NUM_SPIS; i++) {
+ object_initialize_child(obj, "spi[*]", &s->spi[i], TYPE_STM32F2XX_SPI);
+ }
+}
+
+static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
+{
+ STM32F100State *s = STM32F100_SOC(dev_soc);
+ DeviceState *dev, *armv7m;
+ SysBusDevice *busdev;
+ int i;
+
+ MemoryRegion *system_memory = get_system_memory();
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ MemoryRegion *flash = g_new(MemoryRegion, 1);
+ MemoryRegion *flash_alias = g_new(MemoryRegion, 1);
+
+ /*
+ * Init flash region
+ * Flash starts at 0x08000000 and then is aliased to boot memory at 0x0
+ */
+ memory_region_init_rom(flash, OBJECT(dev_soc), "STM32F100.flash",
+ FLASH_SIZE, &error_fatal);
+ memory_region_init_alias(flash_alias, OBJECT(dev_soc),
+ "STM32F100.flash.alias", flash, 0, FLASH_SIZE);
+ memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, flash);
+ memory_region_add_subregion(system_memory, 0, flash_alias);
+
+ /* Init SRAM region */
+ memory_region_init_ram(sram, NULL, "STM32F100.sram", SRAM_SIZE,
+ &error_fatal);
+ memory_region_add_subregion(system_memory, SRAM_BASE_ADDRESS, sram);
+
+ /* Init ARMv7m */
+ armv7m = DEVICE(&s->armv7m);
+ qdev_prop_set_uint32(armv7m, "num-irq", 61);
+ qdev_prop_set_string(armv7m, "cpu-type", s->cpu_type);
+ qdev_prop_set_bit(armv7m, "enable-bitband", true);
+ object_property_set_link(OBJECT(&s->armv7m), "memory",
+ OBJECT(get_system_memory()), &error_abort);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
+ return;
+ }
+
+ /* Attach UART (uses USART registers) and USART controllers */
+ for (i = 0; i < STM_NUM_USARTS; i++) {
+ dev = DEVICE(&(s->usart[i]));
+ qdev_prop_set_chr(dev, "chardev", serial_hd(i));
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->usart[i]), errp)) {
+ return;
+ }
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, usart_addr[i]);
+ sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, usart_irq[i]));
+ }
+
+ /* SPI 1 and 2 */
+ for (i = 0; i < STM_NUM_SPIS; i++) {
+ dev = DEVICE(&(s->spi[i]));
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi[i]), errp)) {
+ return;
+ }
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, spi_addr[i]);
+ sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, spi_irq[i]));
+ }
+
+ create_unimplemented_device("timer[2]", 0x40000000, 0x400);
+ create_unimplemented_device("timer[3]", 0x40000400, 0x400);
+ create_unimplemented_device("timer[4]", 0x40000800, 0x400);
+ create_unimplemented_device("timer[6]", 0x40001000, 0x400);
+ create_unimplemented_device("timer[7]", 0x40001400, 0x400);
+ create_unimplemented_device("RTC", 0x40002800, 0x400);
+ create_unimplemented_device("WWDG", 0x40002C00, 0x400);
+ create_unimplemented_device("IWDG", 0x40003000, 0x400);
+ create_unimplemented_device("I2C1", 0x40005400, 0x400);
+ create_unimplemented_device("I2C2", 0x40005800, 0x400);
+ create_unimplemented_device("BKP", 0x40006C00, 0x400);
+ create_unimplemented_device("PWR", 0x40007000, 0x400);
+ create_unimplemented_device("DAC", 0x40007400, 0x400);
+ create_unimplemented_device("CEC", 0x40007800, 0x400);
+ create_unimplemented_device("AFIO", 0x40010000, 0x400);
+ create_unimplemented_device("EXTI", 0x40010400, 0x400);
+ create_unimplemented_device("GPIOA", 0x40010800, 0x400);
+ create_unimplemented_device("GPIOB", 0x40010C00, 0x400);
+ create_unimplemented_device("GPIOC", 0x40011000, 0x400);
+ create_unimplemented_device("GPIOD", 0x40011400, 0x400);
+ create_unimplemented_device("GPIOE", 0x40011800, 0x400);
+ create_unimplemented_device("ADC1", 0x40012400, 0x400);
+ create_unimplemented_device("timer[1]", 0x40012C00, 0x400);
+ create_unimplemented_device("timer[15]", 0x40014000, 0x400);
+ create_unimplemented_device("timer[16]", 0x40014400, 0x400);
+ create_unimplemented_device("timer[17]", 0x40014800, 0x400);
+ create_unimplemented_device("DMA", 0x40020000, 0x400);
+ create_unimplemented_device("RCC", 0x40021000, 0x400);
+ create_unimplemented_device("Flash Int", 0x40022000, 0x400);
+ create_unimplemented_device("CRC", 0x40023000, 0x400);
+}
+
+static Property stm32f100_soc_properties[] = {
+ DEFINE_PROP_STRING("cpu-type", STM32F100State, cpu_type),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stm32f100_soc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = stm32f100_soc_realize;
+ device_class_set_props(dc, stm32f100_soc_properties);
+}
+
+static const TypeInfo stm32f100_soc_info = {
+ .name = TYPE_STM32F100_SOC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F100State),
+ .instance_init = stm32f100_soc_initfn,
+ .class_init = stm32f100_soc_class_init,
+};
+
+static void stm32f100_soc_types(void)
+{
+ type_register_static(&stm32f100_soc_info);
+}
+
+type_init(stm32f100_soc_types)
diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c
new file mode 100644
index 0000000..7e8191e
--- /dev/null
+++ b/hw/arm/stm32vldiscovery.c
@@ -0,0 +1,66 @@
+/*
+ * ST STM32VLDISCOVERY machine
+ *
+ * Copyright (c) 2021 Alexandre Iooss <erdnaxe@crans.org>
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/boards.h"
+#include "hw/qdev-properties.h"
+#include "qemu/error-report.h"
+#include "hw/arm/stm32f100_soc.h"
+#include "hw/arm/boot.h"
+
+/* stm32vldiscovery implementation is derived from netduinoplus2 */
+
+/* Main SYSCLK frequency in Hz (24MHz) */
+#define SYSCLK_FRQ 24000000ULL
+
+static void stm32vldiscovery_init(MachineState *machine)
+{
+ DeviceState *dev;
+
+ /*
+ * TODO: ideally we would model the SoC RCC and let it handle
+ * system_clock_scale, including its ability to define different
+ * possible SYSCLK sources.
+ */
+ system_clock_scale = NANOSECONDS_PER_SECOND / SYSCLK_FRQ;
+
+ dev = qdev_new(TYPE_STM32F100_SOC);
+ qdev_prop_set_string(dev, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ armv7m_load_kernel(ARM_CPU(first_cpu),
+ machine->kernel_filename,
+ FLASH_SIZE);
+}
+
+static void stm32vldiscovery_machine_init(MachineClass *mc)
+{
+ mc->desc = "ST STM32VLDISCOVERY (Cortex-M3)";
+ mc->init = stm32vldiscovery_init;
+}
+
+DEFINE_MACHINE("stm32vldiscovery", stm32vldiscovery_machine_init)
+
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 4b96f06..93ab9d2 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -895,6 +895,9 @@ static void create_gpio_devices(const VirtMachineState *vms, int gpio,
MachineState *ms = MACHINE(vms);
pl061_dev = qdev_new("pl061");
+ /* Pull lines down to 0 if not driven by the PL061 */
+ qdev_prop_set_uint32(pl061_dev, "pullups", 0);
+ qdev_prop_set_uint32(pl061_dev, "pulldowns", 0xff);
s = SYS_BUS_DEVICE(pl061_dev);
sysbus_realize_and_unref(s, &error_fatal);
memory_region_add_subregion(mem, base, sysbus_mmio_get_region(s, 0));
diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c
index 81af32d..245af81 100644
--- a/hw/arm/xilinx_zynq.c
+++ b/hw/arm/xilinx_zynq.c
@@ -26,7 +26,7 @@
#include "hw/boards.h"
#include "hw/block/flash.h"
#include "hw/loader.h"
-#include "hw/misc/zynq-xadc.h"
+#include "hw/adc/zynq-xadc.h"
#include "hw/ssi/ssi.h"
#include "hw/usb/chipidea.h"
#include "qemu/error-report.h"
diff --git a/hw/display/ati.c b/hw/display/ati.c
index 4c3ad8f..31f2275 100644
--- a/hw/display/ati.c
+++ b/hw/display/ati.c
@@ -968,7 +968,7 @@ static void ati_vga_realize(PCIDevice *dev, Error **errp)
I2CBus *i2cbus = i2c_init_bus(DEVICE(s), "ati-vga.ddc");
bitbang_i2c_init(&s->bbi2c, i2cbus);
I2CSlave *i2cddc = I2C_SLAVE(qdev_new(TYPE_I2CDDC));
- i2c_set_slave_address(i2cddc, 0x50);
+ i2c_slave_set_address(i2cddc, 0x50);
qdev_realize_and_unref(DEVICE(i2cddc), BUS(i2cbus), &error_abort);
/* mmio register space */
diff --git a/hw/display/sm501.c b/hw/display/sm501.c
index 8789722..663c37e 100644
--- a/hw/display/sm501.c
+++ b/hw/display/sm501.c
@@ -1033,16 +1033,18 @@ static void sm501_i2c_write(void *opaque, hwaddr addr, uint64_t value,
case SM501_I2C_CONTROL:
if (value & SM501_I2C_CONTROL_ENABLE) {
if (value & SM501_I2C_CONTROL_START) {
+ bool is_recv = s->i2c_addr & 1;
int res = i2c_start_transfer(s->i2c_bus,
s->i2c_addr >> 1,
- s->i2c_addr & 1);
- s->i2c_status |= (res ? SM501_I2C_STATUS_ERROR : 0);
- if (!res) {
+ is_recv);
+ if (res) {
+ s->i2c_status |= SM501_I2C_STATUS_ERROR;
+ } else {
int i;
for (i = 0; i <= s->i2c_byte_count; i++) {
- res = i2c_send_recv(s->i2c_bus, &s->i2c_data[i],
- !(s->i2c_addr & 1));
- if (res) {
+ if (is_recv) {
+ s->i2c_data[i] = i2c_recv(s->i2c_bus);
+ } else if (i2c_send(s->i2c_bus, s->i2c_data[i]) < 0) {
s->i2c_status |= SM501_I2C_STATUS_ERROR;
return;
}
@@ -1826,7 +1828,7 @@ static void sm501_init(SM501State *s, DeviceState *dev,
s->i2c_bus = i2c_init_bus(dev, "sm501.i2c");
/* ddc */
I2CDDCState *ddc = I2CDDC(qdev_new(TYPE_I2CDDC));
- i2c_set_slave_address(I2C_SLAVE(ddc), 0x50);
+ i2c_slave_set_address(I2C_SLAVE(ddc), 0x50);
qdev_realize_and_unref(DEVICE(ddc), BUS(s->i2c_bus), &error_abort);
/* mmio */
diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c
index 4fd6aeb..2bb7a54 100644
--- a/hw/display/xlnx_dp.c
+++ b/hw/display/xlnx_dp.c
@@ -1253,7 +1253,7 @@ static void xlnx_dp_init(Object *obj)
object_property_add_child(OBJECT(s), "dpcd", OBJECT(s->dpcd));
s->edid = I2CDDC(qdev_new("i2c-ddc"));
- i2c_set_slave_address(I2C_SLAVE(s->edid), 0x50);
+ i2c_slave_set_address(I2C_SLAVE(s->edid), 0x50);
object_property_add_child(OBJECT(s), "edid", OBJECT(s->edid));
fifo8_create(&s->rx_fifo, 16);
diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c
index e72e775..899be86 100644
--- a/hw/gpio/pl061.c
+++ b/hw/gpio/pl061.c
@@ -6,28 +6,39 @@
* Written by Paul Brook
*
* This code is licensed under the GPL.
+ *
+ * QEMU interface:
+ * + sysbus MMIO region 0: the device registers
+ * + sysbus IRQ: the GPIOINTR interrupt line
+ * + unnamed GPIO inputs 0..7: inputs to connect to the emulated GPIO lines
+ * + unnamed GPIO outputs 0..7: the emulated GPIO lines, considered as
+ * outputs
+ * + QOM property "pullups": an integer defining whether non-floating lines
+ * configured as inputs should be pulled up to logical 1 (ie whether in
+ * real hardware they have a pullup resistor on the line out of the PL061).
+ * This should be an 8-bit value, where bit 0 is 1 if GPIO line 0 should
+ * be pulled high, bit 1 configures line 1, and so on. The default is 0xff,
+ * indicating that all GPIO lines are pulled up to logical 1.
+ * + QOM property "pulldowns": an integer defining whether non-floating lines
+ * configured as inputs should be pulled down to logical 0 (ie whether in
+ * real hardware they have a pulldown resistor on the line out of the PL061).
+ * This should be an 8-bit value, where bit 0 is 1 if GPIO line 0 should
+ * be pulled low, bit 1 configures line 1, and so on. The default is 0x0.
+ * It is an error to set a bit in both "pullups" and "pulldowns". If a bit
+ * is 0 in both, then the line is considered to be floating, and it will
+ * not have qemu_set_irq() called on it when it is configured as an input.
*/
#include "qemu/osdep.h"
#include "hw/irq.h"
#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
#include "migration/vmstate.h"
+#include "qapi/error.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qom/object.h"
-
-//#define DEBUG_PL061 1
-
-#ifdef DEBUG_PL061
-#define DPRINTF(fmt, ...) \
-do { printf("pl061: " fmt , ## __VA_ARGS__); } while (0)
-#define BADF(fmt, ...) \
-do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
-#else
-#define DPRINTF(fmt, ...) do {} while(0)
-#define BADF(fmt, ...) \
-do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__);} while (0)
-#endif
+#include "trace.h"
static const uint8_t pl061_id[12] =
{ 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
@@ -67,7 +78,9 @@ struct PL061State {
qemu_irq irq;
qemu_irq out[N_GPIOS];
const unsigned char *id;
- uint32_t rsvd_start; /* reserved area: [rsvd_start, 0xfcc] */
+ /* Properties, for non-Luminary PL061 */
+ uint32_t pullups;
+ uint32_t pulldowns;
};
static const VMStateDescription vmstate_pl061 = {
@@ -100,26 +113,75 @@ static const VMStateDescription vmstate_pl061 = {
}
};
+static uint8_t pl061_floating(PL061State *s)
+{
+ /*
+ * Return mask of bits which correspond to pins configured as inputs
+ * and which are floating (neither pulled up to 1 nor down to 0).
+ */
+ uint8_t floating;
+
+ if (s->id == pl061_id_luminary) {
+ /*
+ * If both PUR and PDR bits are clear, there is neither a pullup
+ * nor a pulldown in place, and the output truly floats.
+ */
+ floating = ~(s->pur | s->pdr);
+ } else {
+ floating = ~(s->pullups | s->pulldowns);
+ }
+ return floating & ~s->dir;
+}
+
+static uint8_t pl061_pullups(PL061State *s)
+{
+ /*
+ * Return mask of bits which correspond to pins configured as inputs
+ * and which are pulled up to 1.
+ */
+ uint8_t pullups;
+
+ if (s->id == pl061_id_luminary) {
+ /*
+ * The Luminary variant of the PL061 has an extra registers which
+ * the guest can use to configure whether lines should be pullup
+ * or pulldown.
+ */
+ pullups = s->pur;
+ } else {
+ pullups = s->pullups;
+ }
+ return pullups & ~s->dir;
+}
+
static void pl061_update(PL061State *s)
{
uint8_t changed;
uint8_t mask;
uint8_t out;
int i;
-
- DPRINTF("dir = %d, data = %d\n", s->dir, s->data);
-
- /* Outputs float high. */
- /* FIXME: This is board dependent. */
- out = (s->data & s->dir) | ~s->dir;
+ uint8_t pullups = pl061_pullups(s);
+ uint8_t floating = pl061_floating(s);
+
+ trace_pl061_update(DEVICE(s)->canonical_path, s->dir, s->data,
+ pullups, floating);
+
+ /*
+ * Pins configured as output are driven from the data register;
+ * otherwise if they're pulled up they're 1, and if they're floating
+ * then we give them the same value they had previously, so we don't
+ * report any change to the other end.
+ */
+ out = (s->data & s->dir) | pullups | (s->old_out_data & floating);
changed = s->old_out_data ^ out;
if (changed) {
s->old_out_data = out;
for (i = 0; i < N_GPIOS; i++) {
mask = 1 << i;
if (changed & mask) {
- DPRINTF("Set output %d = %d\n", i, (out & mask) != 0);
- qemu_set_irq(s->out[i], (out & mask) != 0);
+ int level = (out & mask) != 0;
+ trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);
+ qemu_set_irq(s->out[i], level);
}
}
}
@@ -131,7 +193,8 @@ static void pl061_update(PL061State *s)
for (i = 0; i < N_GPIOS; i++) {
mask = 1 << i;
if (changed & mask) {
- DPRINTF("Changed input %d = %d\n", i, (s->data & mask) != 0);
+ trace_pl061_input_change(DEVICE(s)->canonical_path, i,
+ (s->data & mask) != 0);
if (!(s->isense & mask)) {
/* Edge interrupt */
@@ -150,7 +213,8 @@ static void pl061_update(PL061State *s)
/* Level interrupt */
s->istate |= ~(s->data ^ s->iev) & s->isense;
- DPRINTF("istate = %02X\n", s->istate);
+ trace_pl061_update_istate(DEVICE(s)->canonical_path,
+ s->istate, s->im, (s->istate & s->im) != 0);
qemu_set_irq(s->irq, (s->istate & s->im) != 0);
}
@@ -159,62 +223,114 @@ static uint64_t pl061_read(void *opaque, hwaddr offset,
unsigned size)
{
PL061State *s = (PL061State *)opaque;
+ uint64_t r = 0;
- if (offset < 0x400) {
- return s->data & (offset >> 2);
- }
- if (offset >= s->rsvd_start && offset <= 0xfcc) {
- goto err_out;
- }
- if (offset >= 0xfd0 && offset < 0x1000) {
- return s->id[(offset - 0xfd0) >> 2];
- }
switch (offset) {
+ case 0x0 ... 0x3ff: /* Data */
+ r = s->data & (offset >> 2);
+ break;
case 0x400: /* Direction */
- return s->dir;
+ r = s->dir;
+ break;
case 0x404: /* Interrupt sense */
- return s->isense;
+ r = s->isense;
+ break;
case 0x408: /* Interrupt both edges */
- return s->ibe;
+ r = s->ibe;
+ break;
case 0x40c: /* Interrupt event */
- return s->iev;
+ r = s->iev;
+ break;
case 0x410: /* Interrupt mask */
- return s->im;
+ r = s->im;
+ break;
case 0x414: /* Raw interrupt status */
- return s->istate;
+ r = s->istate;
+ break;
case 0x418: /* Masked interrupt status */
- return s->istate & s->im;
+ r = s->istate & s->im;
+ break;
case 0x420: /* Alternate function select */
- return s->afsel;
+ r = s->afsel;
+ break;
case 0x500: /* 2mA drive */
- return s->dr2r;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr2r;
+ break;
case 0x504: /* 4mA drive */
- return s->dr4r;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr4r;
+ break;
case 0x508: /* 8mA drive */
- return s->dr8r;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr8r;
+ break;
case 0x50c: /* Open drain */
- return s->odr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->odr;
+ break;
case 0x510: /* Pull-up */
- return s->pur;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->pur;
+ break;
case 0x514: /* Pull-down */
- return s->pdr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->pdr;
+ break;
case 0x518: /* Slew rate control */
- return s->slr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->slr;
+ break;
case 0x51c: /* Digital enable */
- return s->den;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->den;
+ break;
case 0x520: /* Lock */
- return s->locked;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->locked;
+ break;
case 0x524: /* Commit */
- return s->cr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->cr;
+ break;
case 0x528: /* Analog mode select */
- return s->amsel;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->amsel;
+ break;
+ case 0xfd0 ... 0xfff: /* ID registers */
+ r = s->id[(offset - 0xfd0) >> 2];
+ break;
default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_read: Bad offset %x\n", (int)offset);
break;
}
-err_out:
- qemu_log_mask(LOG_GUEST_ERROR,
- "pl061_read: Bad offset %x\n", (int)offset);
- return 0;
+
+ trace_pl061_read(DEVICE(s)->canonical_path, offset, r);
+ return r;
}
static void pl061_write(void *opaque, hwaddr offset,
@@ -223,16 +339,14 @@ static void pl061_write(void *opaque, hwaddr offset,
PL061State *s = (PL061State *)opaque;
uint8_t mask;
- if (offset < 0x400) {
+ trace_pl061_write(DEVICE(s)->canonical_path, offset, value);
+
+ switch (offset) {
+ case 0 ... 0x3ff:
mask = (offset >> 2) & s->dir;
s->data = (s->data & ~mask) | (value & mask);
pl061_update(s);
return;
- }
- if (offset >= s->rsvd_start) {
- goto err_out;
- }
- switch (offset) {
case 0x400: /* Direction */
s->dir = value & 0xff;
break;
@@ -256,56 +370,99 @@ static void pl061_write(void *opaque, hwaddr offset,
s->afsel = (s->afsel & ~mask) | (value & mask);
break;
case 0x500: /* 2mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->dr2r = value & 0xff;
break;
case 0x504: /* 4mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->dr4r = value & 0xff;
break;
case 0x508: /* 8mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->dr8r = value & 0xff;
break;
case 0x50c: /* Open drain */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->odr = value & 0xff;
break;
case 0x510: /* Pull-up */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->pur = value & 0xff;
break;
case 0x514: /* Pull-down */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->pdr = value & 0xff;
break;
case 0x518: /* Slew rate control */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->slr = value & 0xff;
break;
case 0x51c: /* Digital enable */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->den = value & 0xff;
break;
case 0x520: /* Lock */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->locked = (value != 0xacce551);
break;
case 0x524: /* Commit */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
if (!s->locked)
s->cr = value & 0xff;
break;
case 0x528:
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->amsel = value & 0xff;
break;
default:
- goto err_out;
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_write: Bad offset %x\n", (int)offset);
+ return;
}
pl061_update(s);
return;
-err_out:
- qemu_log_mask(LOG_GUEST_ERROR,
- "pl061_write: Bad offset %x\n", (int)offset);
}
-static void pl061_reset(DeviceState *dev)
+static void pl061_enter_reset(Object *obj, ResetType type)
{
- PL061State *s = PL061(dev);
+ PL061State *s = PL061(obj);
+
+ trace_pl061_reset(DEVICE(s)->canonical_path);
/* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */
+
+ /*
+ * FIXME: For the LM3S6965, not all of the PL061 instances have the
+ * same reset values for GPIOPUR, GPIOAFSEL and GPIODEN, so in theory
+ * we should allow the board to configure these via properties.
+ * In practice, we don't wire anything up to the affected GPIO lines
+ * (PB7, PC0, PC1, PC2, PC3 -- they're used for JTAG), so we can
+ * get away with this inaccuracy.
+ */
s->data = 0;
- s->old_out_data = 0;
s->old_in_data = 0;
s->dir = 0;
s->isense = 0;
@@ -327,6 +484,24 @@ static void pl061_reset(DeviceState *dev)
s->amsel = 0;
}
+static void pl061_hold_reset(Object *obj)
+{
+ PL061State *s = PL061(obj);
+ int i, level;
+ uint8_t floating = pl061_floating(s);
+ uint8_t pullups = pl061_pullups(s);
+
+ for (i = 0; i < N_GPIOS; i++) {
+ if (extract32(floating, i, 1)) {
+ continue;
+ }
+ level = extract32(pullups, i, 1);
+ trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);
+ qemu_set_irq(s->out[i], level);
+ }
+ s->old_out_data = pullups;
+}
+
static void pl061_set_irq(void * opaque, int irq, int level)
{
PL061State *s = (PL061State *)opaque;
@@ -352,7 +527,6 @@ static void pl061_luminary_init(Object *obj)
PL061State *s = PL061(obj);
s->id = pl061_id_luminary;
- s->rsvd_start = 0x52c;
}
static void pl061_init(Object *obj)
@@ -362,7 +536,6 @@ static void pl061_init(Object *obj)
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
s->id = pl061_id;
- s->rsvd_start = 0x424;
memory_region_init_io(&s->iomem, obj, &pl061_ops, s, "pl061", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
@@ -371,12 +544,40 @@ static void pl061_init(Object *obj)
qdev_init_gpio_out(dev, s->out, N_GPIOS);
}
+static void pl061_realize(DeviceState *dev, Error **errp)
+{
+ PL061State *s = PL061(dev);
+
+ if (s->pullups > 0xff) {
+ error_setg(errp, "pullups property must be between 0 and 0xff");
+ return;
+ }
+ if (s->pulldowns > 0xff) {
+ error_setg(errp, "pulldowns property must be between 0 and 0xff");
+ return;
+ }
+ if (s->pullups & s->pulldowns) {
+ error_setg(errp, "no bit may be set both in pullups and pulldowns");
+ return;
+ }
+}
+
+static Property pl061_props[] = {
+ DEFINE_PROP_UINT32("pullups", PL061State, pullups, 0xff),
+ DEFINE_PROP_UINT32("pulldowns", PL061State, pulldowns, 0x0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
static void pl061_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
dc->vmsd = &vmstate_pl061;
- dc->reset = &pl061_reset;
+ dc->realize = pl061_realize;
+ device_class_set_props(dc, pl061_props);
+ rc->phases.enter = pl061_enter_reset;
+ rc->phases.hold = pl061_hold_reset;
}
static const TypeInfo pl061_info = {
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index f0b6641..1dab99c 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -13,6 +13,15 @@ nrf51_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x
nrf51_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
nrf51_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+# pl061.c
+pl061_update(const char *id, uint32_t dir, uint32_t data, uint32_t pullups, uint32_t floating) "%s GPIODIR 0x%x GPIODATA 0x%x pullups 0x%x floating 0x%x"
+pl061_set_output(const char *id, int gpio, int level) "%s setting output %d to %d"
+pl061_input_change(const char *id, int gpio, int level) "%s input %d changed to %d"
+pl061_update_istate(const char *id, uint32_t istate, uint32_t im, int level) "%s GPIORIS 0x%x GPIOIE 0x%x interrupt level %d"
+pl061_read(const char *id, uint64_t offset, uint64_t r) "%s offset 0x%" PRIx64 " value 0x%" PRIx64
+pl061_write(const char *id, uint64_t offset, uint64_t value) "%s offset 0x%" PRIx64 " value 0x%" PRIx64
+pl061_reset(const char *id) "%s reset"
+
# sifive_gpio.c
sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
diff --git a/hw/hyperv/vmbus.c b/hw/hyperv/vmbus.c
index 984caf8..c9887d5 100644
--- a/hw/hyperv/vmbus.c
+++ b/hw/hyperv/vmbus.c
@@ -2372,6 +2372,14 @@ static void vmbus_dev_realize(DeviceState *dev, Error **errp)
assert(!qemu_uuid_is_null(&vdev->instanceid));
+ if (!qemu_uuid_is_null(&vdc->instanceid)) {
+ /* Class wants to only have a single instance with a fixed UUID */
+ if (!qemu_uuid_is_equal(&vdev->instanceid, &vdc->instanceid)) {
+ error_setg(&err, "instance id can't be changed");
+ goto error_out;
+ }
+ }
+
/* Check for instance id collision for this class id */
QTAILQ_FOREACH(child, &BUS(vmbus)->children, sibling) {
VMBusDevice *child_dev = VMBUS_DEVICE(child->child);
@@ -2438,18 +2446,22 @@ static void vmbus_dev_unrealize(DeviceState *dev)
free_channels(vdev);
}
+static Property vmbus_dev_props[] = {
+ DEFINE_PROP_UUID("instanceid", VMBusDevice, instanceid),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+
static void vmbus_dev_class_init(ObjectClass *klass, void *data)
{
DeviceClass *kdev = DEVICE_CLASS(klass);
+ device_class_set_props(kdev, vmbus_dev_props);
kdev->bus_type = TYPE_VMBUS;
kdev->realize = vmbus_dev_realize;
kdev->unrealize = vmbus_dev_unrealize;
kdev->reset = vmbus_dev_reset;
}
-static Property vmbus_dev_instanceid =
- DEFINE_PROP_UUID("instanceid", VMBusDevice, instanceid);
-
static void vmbus_dev_instance_init(Object *obj)
{
VMBusDevice *vdev = VMBUS_DEVICE(obj);
@@ -2458,8 +2470,6 @@ static void vmbus_dev_instance_init(Object *obj)
if (!qemu_uuid_is_null(&vdc->instanceid)) {
/* Class wants to only have a single instance with a fixed UUID */
vdev->instanceid = vdc->instanceid;
- } else {
- qdev_property_add_static(DEVICE(vdev), &vmbus_dev_instanceid);
}
}
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 8d120a2..8217cb5 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -32,3 +32,7 @@ config MPC_I2C
config PCA954X
bool
select I2C
+
+config PMBUS
+ bool
+ select SMBUS
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
index 3a7bae3..416372a 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -66,7 +66,7 @@ I2CBus *i2c_init_bus(DeviceState *parent, const char *name)
return bus;
}
-void i2c_set_slave_address(I2CSlave *dev, uint8_t address)
+void i2c_slave_set_address(I2CSlave *dev, uint8_t address)
{
dev->address = address;
}
@@ -114,8 +114,11 @@ bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
* protocol uses a start transfer to switch from write to read mode
* without releasing the bus. If that fails, the bus is still
* in a transaction.
+ *
+ * @event must be I2C_START_RECV or I2C_START_SEND.
*/
-int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
+static int i2c_do_start_transfer(I2CBus *bus, uint8_t address,
+ enum i2c_event event)
{
I2CSlaveClass *sc;
I2CNode *node;
@@ -157,7 +160,7 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
if (sc->event) {
trace_i2c_event("start", s->address);
- rv = sc->event(s, recv ? I2C_START_RECV : I2C_START_SEND);
+ rv = sc->event(s, event);
if (rv && !bus->broadcast) {
if (bus_scanned) {
/* First call, terminate the transfer. */
@@ -170,6 +173,23 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
return 0;
}
+int i2c_start_transfer(I2CBus *bus, uint8_t address, bool is_recv)
+{
+ return i2c_do_start_transfer(bus, address, is_recv
+ ? I2C_START_RECV
+ : I2C_START_SEND);
+}
+
+int i2c_start_recv(I2CBus *bus, uint8_t address)
+{
+ return i2c_do_start_transfer(bus, address, I2C_START_RECV);
+}
+
+int i2c_start_send(I2CBus *bus, uint8_t address)
+{
+ return i2c_do_start_transfer(bus, address, I2C_START_SEND);
+}
+
void i2c_end_transfer(I2CBus *bus)
{
I2CSlaveClass *sc;
@@ -188,50 +208,42 @@ void i2c_end_transfer(I2CBus *bus)
bus->broadcast = false;
}
-int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send)
+int i2c_send(I2CBus *bus, uint8_t data)
{
I2CSlaveClass *sc;
I2CSlave *s;
I2CNode *node;
int ret = 0;
- if (send) {
- QLIST_FOREACH(node, &bus->current_devs, next) {
- s = node->elt;
- sc = I2C_SLAVE_GET_CLASS(s);
- if (sc->send) {
- trace_i2c_send(s->address, *data);
- ret = ret || sc->send(s, *data);
- } else {
- ret = -1;
- }
- }
- return ret ? -1 : 0;
- } else {
- ret = 0xff;
- if (!QLIST_EMPTY(&bus->current_devs) && !bus->broadcast) {
- sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
- if (sc->recv) {
- s = QLIST_FIRST(&bus->current_devs)->elt;
- ret = sc->recv(s);
- trace_i2c_recv(s->address, ret);
- }
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ s = node->elt;
+ sc = I2C_SLAVE_GET_CLASS(s);
+ if (sc->send) {
+ trace_i2c_send(s->address, data);
+ ret = ret || sc->send(s, data);
+ } else {
+ ret = -1;
}
- *data = ret;
- return 0;
}
-}
-int i2c_send(I2CBus *bus, uint8_t data)
-{
- return i2c_send_recv(bus, &data, true);
+ return ret ? -1 : 0;
}
uint8_t i2c_recv(I2CBus *bus)
{
uint8_t data = 0xff;
+ I2CSlaveClass *sc;
+ I2CSlave *s;
+
+ if (!QLIST_EMPTY(&bus->current_devs) && !bus->broadcast) {
+ sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
+ if (sc->recv) {
+ s = QLIST_FIRST(&bus->current_devs)->elt;
+ data = sc->recv(s);
+ trace_i2c_recv(s->address, data);
+ }
+ }
- i2c_send_recv(bus, &data, false);
return data;
}
diff --git a/hw/i2c/imx_i2c.c b/hw/i2c/imx_i2c.c
index 2e02e1c..9792583 100644
--- a/hw/i2c/imx_i2c.c
+++ b/hw/i2c/imx_i2c.c
@@ -171,7 +171,7 @@ static void imx_i2c_write(void *opaque, hwaddr offset,
switch (offset) {
case IADR_ADDR:
s->iadr = value & IADR_MASK;
- /* i2c_set_slave_address(s->bus, (uint8_t)s->iadr); */
+ /* i2c_slave_set_address(s->bus, (uint8_t)s->iadr); */
break;
case IFDR_ADDR:
s->ifdr = value & IFDR_MASK;
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index dd3aef0..d3df273 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -15,4 +15,5 @@ i2c_ss.add(when: 'CONFIG_VERSATILE_I2C', if_true: files('versatile_i2c.c'))
i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c'))
i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c'))
i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c'))
+i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
softmmu_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
diff --git a/hw/i2c/pm_smbus.c b/hw/i2c/pm_smbus.c
index 06e1e53..d7eae54 100644
--- a/hw/i2c/pm_smbus.c
+++ b/hw/i2c/pm_smbus.c
@@ -128,14 +128,14 @@ static void smb_transaction(PMSMBus *s)
* So at least Linux may or may not set the read bit here.
* So just ignore the read bit for this command.
*/
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
goto error;
}
ret = i2c_send(bus, s->smb_data1);
if (ret) {
goto error;
}
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
goto error;
}
s->in_i2c_block_read = true;
diff --git a/hw/i2c/pmbus_device.c b/hw/i2c/pmbus_device.c
new file mode 100644
index 0000000..24f8f52
--- /dev/null
+++ b/hw/i2c/pmbus_device.c
@@ -0,0 +1,1612 @@
+/*
+ * PMBus wrapper over SMBus
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <math.h>
+#include <string.h>
+#include "hw/i2c/pmbus_device.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+
+uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value)
+{
+ /* R is usually negative to fit large readings into 16 bits */
+ uint16_t y = (c.m * value + c.b) * pow(10, c.R);
+ return y;
+}
+
+uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value)
+{
+ /* X = (Y * 10^-R - b) / m */
+ uint32_t x = (value / pow(10, c.R) - c.b) / c.m;
+ return x;
+}
+
+void pmbus_send(PMBusDevice *pmdev, const uint8_t *data, uint16_t len)
+{
+ if (pmdev->out_buf_len + len > SMBUS_DATA_MAX_LEN) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMBus device tried to send too much data");
+ len = 0;
+ }
+
+ for (int i = len - 1; i >= 0; i--) {
+ pmdev->out_buf[i + pmdev->out_buf_len] = data[len - i - 1];
+ }
+ pmdev->out_buf_len += len;
+}
+
+/* Internal only, convert unsigned ints to the little endian bus */
+static void pmbus_send_uint(PMBusDevice *pmdev, uint64_t data, uint8_t size)
+{
+ uint8_t bytes[8];
+ g_assert(size <= 8);
+
+ for (int i = 0; i < size; i++) {
+ bytes[i] = data & 0xFF;
+ data = data >> 8;
+ }
+ pmbus_send(pmdev, bytes, size);
+}
+
+void pmbus_send8(PMBusDevice *pmdev, uint8_t data)
+{
+ pmbus_send_uint(pmdev, data, 1);
+}
+
+void pmbus_send16(PMBusDevice *pmdev, uint16_t data)
+{
+ pmbus_send_uint(pmdev, data, 2);
+}
+
+void pmbus_send32(PMBusDevice *pmdev, uint32_t data)
+{
+ pmbus_send_uint(pmdev, data, 4);
+}
+
+void pmbus_send64(PMBusDevice *pmdev, uint64_t data)
+{
+ pmbus_send_uint(pmdev, data, 8);
+}
+
+void pmbus_send_string(PMBusDevice *pmdev, const char *data)
+{
+ size_t len = strlen(data);
+ g_assert(len > 0);
+ g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
+ pmdev->out_buf[len + pmdev->out_buf_len] = len;
+
+ for (int i = len - 1; i >= 0; i--) {
+ pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i];
+ }
+ pmdev->out_buf_len += len + 1;
+}
+
+
+static uint64_t pmbus_receive_uint(const uint8_t *buf, uint8_t len)
+{
+ uint64_t ret = 0;
+
+ /* Exclude command code from return value */
+ buf++;
+ len--;
+
+ for (int i = len - 1; i >= 0; i--) {
+ ret = ret << 8 | buf[i];
+ }
+ return ret;
+}
+
+uint8_t pmbus_receive8(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 1 byte, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint16_t pmbus_receive16(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 2 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint32_t pmbus_receive32(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 4 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint64_t pmbus_receive64(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 8) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 8 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+static uint8_t pmbus_out_buf_pop(PMBusDevice *pmdev)
+{
+ if (pmdev->out_buf_len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: tried to read from empty buffer",
+ __func__);
+ return 0xFF;
+ }
+ uint8_t data = pmdev->out_buf[pmdev->out_buf_len - 1];
+ pmdev->out_buf_len--;
+ return data;
+}
+
+static void pmbus_quick_cmd(SMBusDevice *smd, uint8_t read)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+
+ if (pmdc->quick_cmd) {
+ pmdc->quick_cmd(pmdev, read);
+ }
+}
+
+static void pmbus_pages_alloc(PMBusDevice *pmdev)
+{
+ /* some PMBus devices don't use the PAGE command, so they get 1 page */
+ PMBusDeviceClass *k = PMBUS_DEVICE_GET_CLASS(pmdev);
+ if (k->device_num_pages == 0) {
+ k->device_num_pages = 1;
+ }
+ pmdev->num_pages = k->device_num_pages;
+ pmdev->pages = g_new0(PMBusPage, k->device_num_pages);
+}
+
+void pmbus_check_limits(PMBusDevice *pmdev)
+{
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ if ((pmdev->pages[i].operation & PB_OP_ON) == 0) {
+ continue; /* don't check powered off devices */
+ }
+
+ if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_FAULT;
+ }
+
+ if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_FAULT;
+ }
+
+ if (pmdev->pages[i].read_vin > pmdev->pages[i].vin_ov_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_OV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vin < pmdev->pages[i].vin_uv_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_UV_WARN;
+ }
+
+ if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
+ pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_WARN;
+ }
+
+ if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
+ pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_FAULT;
+ }
+
+ if (pmdev->pages[i].read_pin > pmdev->pages[i].pin_op_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_PIN_OP_WARN;
+ }
+
+ if (pmdev->pages[i].read_temperature_1
+ > pmdev->pages[i].ot_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
+ pmdev->pages[i].status_temperature |= PB_STATUS_OT_FAULT;
+ }
+
+ if (pmdev->pages[i].read_temperature_1
+ > pmdev->pages[i].ot_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
+ pmdev->pages[i].status_temperature |= PB_STATUS_OT_WARN;
+ }
+ }
+}
+
+static uint8_t pmbus_receive_byte(SMBusDevice *smd)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+ uint8_t ret = 0xFF;
+ uint8_t index = pmdev->page;
+
+ if (pmdev->out_buf_len != 0) {
+ ret = pmbus_out_buf_pop(pmdev);
+ return ret;
+ }
+
+ switch (pmdev->code) {
+ case PMBUS_PAGE:
+ pmbus_send8(pmdev, pmdev->page);
+ break;
+
+ case PMBUS_OPERATION: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].operation);
+ break;
+
+ case PMBUS_ON_OFF_CONFIG: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].on_off_config);
+ break;
+
+ case PMBUS_PHASE: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].phase);
+ break;
+
+ case PMBUS_WRITE_PROTECT: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].write_protect);
+ break;
+
+ case PMBUS_CAPABILITY:
+ pmbus_send8(pmdev, pmdev->capability);
+ break;
+
+ case PMBUS_VOUT_MODE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_mode);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_COMMAND: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_command);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRIM: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_trim);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_CAL_OFFSET: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_cal_offset);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_HIGH: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_margin_high);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_LOW: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_margin_low);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRANSITION_RATE: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_transition_rate);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_DROOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_droop);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_LOOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_scale_loop);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_MONITOR: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_scale_monitor);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ /* TODO: implement coefficients support */
+
+ case PMBUS_POUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_ON: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_on);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OFF: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_off);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_CAL_GAIN: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_cal_gain);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_ov_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_ov_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_ov_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_uv_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_uv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_uv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_oc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_lv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_oc_lv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_uc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_uc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ot_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].ot_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ot_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ut_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ut_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].ut_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_ov_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].vin_ov_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_ov_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_uv_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_uv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].vin_uv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iin_oc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].iin_oc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iin_oc_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_op_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].pout_op_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_op_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_PIN_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].pin_op_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_BYTE: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_word & 0xFF);
+ break;
+
+ case PMBUS_STATUS_WORD: /* R/W word */
+ pmbus_send16(pmdev, pmdev->pages[index].status_word);
+ break;
+
+ case PMBUS_STATUS_VOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_vout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_IOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_iout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_INPUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN ||
+ pmdev->pages[index].page_flags & PB_HAS_IIN ||
+ pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_input);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_TEMPERATURE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_temperature);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_CML: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_cml);
+ break;
+
+ case PMBUS_STATUS_OTHER: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_other);
+ break;
+
+ case PMBUS_READ_EIN: /* Read-Only block 5 bytes */
+ if (pmdev->pages[index].page_flags & PB_HAS_EIN) {
+ pmbus_send(pmdev, pmdev->pages[index].read_ein, 5);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_EOUT: /* Read-Only block 5 bytes */
+ if (pmdev->pages[index].page_flags & PB_HAS_EOUT) {
+ pmbus_send(pmdev, pmdev->pages[index].read_eout, 5);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_VIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_vin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_IIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_iin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_VOUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_vout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_IOUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_iout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_1: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_1);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_2: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP2) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_2);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_3: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP3) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_3);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_POUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_pout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_PIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_pin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_REVISION: /* Read-Only byte */
+ pmbus_send8(pmdev, pmdev->pages[index].revision);
+ break;
+
+ case PMBUS_MFR_ID: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_id);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MODEL: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_model);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_REVISION: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_revision);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_LOCATION: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_location);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VIN_MIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_min);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_IIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_iin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_PIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_pin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VOUT_MIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_min);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VOUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_IOUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_iout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_POUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_pout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_1: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_1);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_2: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_2);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_3: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_3);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_CLEAR_FAULTS: /* Send Byte */
+ case PMBUS_PAGE_PLUS_WRITE: /* Block Write-only */
+ case PMBUS_STORE_DEFAULT_ALL: /* Send Byte */
+ case PMBUS_RESTORE_DEFAULT_ALL: /* Send Byte */
+ case PMBUS_STORE_DEFAULT_CODE: /* Write-only Byte */
+ case PMBUS_RESTORE_DEFAULT_CODE: /* Write-only Byte */
+ case PMBUS_STORE_USER_ALL: /* Send Byte */
+ case PMBUS_RESTORE_USER_ALL: /* Send Byte */
+ case PMBUS_STORE_USER_CODE: /* Write-only Byte */
+ case PMBUS_RESTORE_USER_CODE: /* Write-only Byte */
+ case PMBUS_QUERY: /* Write-Only */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from write only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+passthough:
+ default:
+ /* Pass through read request if not handled */
+ if (pmdc->receive_byte) {
+ ret = pmdc->receive_byte(pmdev);
+ }
+ break;
+ }
+
+ if (pmdev->out_buf_len != 0) {
+ ret = pmbus_out_buf_pop(pmdev);
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * PMBus clear faults command applies to all status registers, existing faults
+ * should separately get re-asserted.
+ */
+static void pmbus_clear_faults(PMBusDevice *pmdev)
+{
+ for (uint8_t i = 0; i < pmdev->num_pages; i++) {
+ pmdev->pages[i].status_word = 0;
+ pmdev->pages[i].status_vout = 0;
+ pmdev->pages[i].status_iout = 0;
+ pmdev->pages[i].status_input = 0;
+ pmdev->pages[i].status_temperature = 0;
+ pmdev->pages[i].status_cml = 0;
+ pmdev->pages[i].status_other = 0;
+ pmdev->pages[i].status_mfr_specific = 0;
+ pmdev->pages[i].status_fans_1_2 = 0;
+ pmdev->pages[i].status_fans_3_4 = 0;
+ }
+
+}
+
+/*
+ * PMBus operation is used to turn On and Off PSUs
+ * Therefore, default value for the Operation should be PB_OP_ON or 0x80
+ */
+static void pmbus_operation(PMBusDevice *pmdev)
+{
+ uint8_t index = pmdev->page;
+ if ((pmdev->pages[index].operation & PB_OP_ON) == 0) {
+ pmdev->pages[index].read_vout = 0;
+ pmdev->pages[index].read_iout = 0;
+ pmdev->pages[index].read_pout = 0;
+ return;
+ }
+
+ if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_HIGH)) {
+ pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_high;
+ }
+
+ if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_LOW)) {
+ pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_low;
+ }
+ pmbus_check_limits(pmdev);
+}
+
+static int pmbus_write_data(SMBusDevice *smd, uint8_t *buf, uint8_t len)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+ int ret = 0;
+ uint8_t index;
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ if (!pmdev->pages) { /* allocate memory for pages on first use */
+ pmbus_pages_alloc(pmdev);
+ }
+
+ pmdev->in_buf_len = len;
+ pmdev->in_buf = buf;
+
+ pmdev->code = buf[0]; /* PMBus command code */
+ if (len == 1) { /* Single length writes are command codes only */
+ return 0;
+ }
+
+ if (pmdev->code == PMBUS_PAGE) {
+ pmdev->page = pmbus_receive8(pmdev);
+ return 0;
+ }
+ /* loop through all the pages when 0xFF is received */
+ if (pmdev->page == PB_ALL_PAGES) {
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ pmdev->page = i;
+ pmbus_write_data(smd, buf, len);
+ }
+ pmdev->page = PB_ALL_PAGES;
+ return 0;
+ }
+
+ index = pmdev->page;
+
+ switch (pmdev->code) {
+ case PMBUS_OPERATION: /* R/W byte */
+ pmdev->pages[index].operation = pmbus_receive8(pmdev);
+ pmbus_operation(pmdev);
+ break;
+
+ case PMBUS_ON_OFF_CONFIG: /* R/W byte */
+ pmdev->pages[index].on_off_config = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_CLEAR_FAULTS: /* Send Byte */
+ pmbus_clear_faults(pmdev);
+ break;
+
+ case PMBUS_PHASE: /* R/W byte */
+ pmdev->pages[index].phase = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_PAGE_PLUS_WRITE: /* Block Write-only */
+ case PMBUS_WRITE_PROTECT: /* R/W byte */
+ pmdev->pages[index].write_protect = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_VOUT_MODE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
+ pmdev->pages[index].vout_mode = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_COMMAND: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_command = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRIM: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_trim = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_CAL_OFFSET: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_cal_offset = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_max = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_HIGH: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmdev->pages[index].vout_margin_high = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_LOW: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmdev->pages[index].vout_margin_low = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRANSITION_RATE: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_transition_rate = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_DROOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_droop = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_LOOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_scale_loop = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_MONITOR: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_scale_monitor = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_max = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_ON: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_on = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OFF: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_off = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_CAL_GAIN: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
+ pmdev->pages[index].iout_cal_gain = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_lv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_lv_fault_response
+ = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_uc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_uc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_PIN_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmdev->pages[index].pin_op_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_BYTE: /* R/W byte */
+ pmdev->pages[index].status_word = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_WORD: /* R/W word */
+ pmdev->pages[index].status_word = pmbus_receive16(pmdev);
+ break;
+
+ case PMBUS_STATUS_VOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].status_vout = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_IOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].status_iout = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_INPUT: /* R/W byte */
+ pmdev->pages[index].status_input = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_TEMPERATURE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].status_temperature = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_CML: /* R/W byte */
+ pmdev->pages[index].status_cml = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_OTHER: /* R/W byte */
+ pmdev->pages[index].status_other = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_PAGE_PLUS_READ: /* Block Read-only */
+ case PMBUS_CAPABILITY: /* Read-Only byte */
+ case PMBUS_COEFFICIENTS: /* Read-only block 5 bytes */
+ case PMBUS_READ_EIN: /* Read-Only block 5 bytes */
+ case PMBUS_READ_EOUT: /* Read-Only block 5 bytes */
+ case PMBUS_READ_VIN: /* Read-Only word */
+ case PMBUS_READ_IIN: /* Read-Only word */
+ case PMBUS_READ_VCAP: /* Read-Only word */
+ case PMBUS_READ_VOUT: /* Read-Only word */
+ case PMBUS_READ_IOUT: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_1: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_2: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_3: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_1: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_2: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_3: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_4: /* Read-Only word */
+ case PMBUS_READ_DUTY_CYCLE: /* Read-Only word */
+ case PMBUS_READ_FREQUENCY: /* Read-Only word */
+ case PMBUS_READ_POUT: /* Read-Only word */
+ case PMBUS_READ_PIN: /* Read-Only word */
+ case PMBUS_REVISION: /* Read-Only byte */
+ case PMBUS_APP_PROFILE_SUPPORT: /* Read-Only block-read */
+ case PMBUS_MFR_VIN_MIN: /* Read-Only word */
+ case PMBUS_MFR_VIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_IIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_PIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_VOUT_MIN: /* Read-Only word */
+ case PMBUS_MFR_VOUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_IOUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_POUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_TAMBIENT_MAX: /* Read-Only word */
+ case PMBUS_MFR_TAMBIENT_MIN: /* Read-Only word */
+ case PMBUS_MFR_EFFICIENCY_LL: /* Read-Only block 14 bytes */
+ case PMBUS_MFR_EFFICIENCY_HL: /* Read-Only block 14 bytes */
+ case PMBUS_MFR_PIN_ACCURACY: /* Read-Only byte */
+ case PMBUS_IC_DEVICE_ID: /* Read-Only block-read */
+ case PMBUS_IC_DEVICE_REV: /* Read-Only block-read */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to read-only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+passthrough:
+ /* Unimplimented registers get passed to the device */
+ default:
+ if (pmdc->write_data) {
+ ret = pmdc->write_data(pmdev, buf, len);
+ }
+ break;
+ }
+ pmbus_check_limits(pmdev);
+ pmdev->in_buf_len = 0;
+ return ret;
+}
+
+int pmbus_page_config(PMBusDevice *pmdev, uint8_t index, uint64_t flags)
+{
+ if (!pmdev->pages) { /* allocate memory for pages on first use */
+ pmbus_pages_alloc(pmdev);
+ }
+
+ /* The 0xFF page is special for commands applying to all pages */
+ if (index == PB_ALL_PAGES) {
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ pmdev->pages[i].page_flags = flags;
+ }
+ return 0;
+ }
+
+ if (index > pmdev->num_pages - 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: index %u is out of range\n",
+ __func__, index);
+ return -1;
+ }
+
+ pmdev->pages[index].page_flags = flags;
+
+ return 0;
+}
+
+/* TODO: include pmbus page info in vmstate */
+const VMStateDescription vmstate_pmbus_device = {
+ .name = TYPE_PMBUS_DEVICE,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_SMBUS_DEVICE(smb, PMBusDevice),
+ VMSTATE_UINT8(num_pages, PMBusDevice),
+ VMSTATE_UINT8(code, PMBusDevice),
+ VMSTATE_UINT8(page, PMBusDevice),
+ VMSTATE_UINT8(capability, PMBusDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pmbus_device_finalize(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ g_free(pmdev->pages);
+}
+
+static void pmbus_device_class_init(ObjectClass *klass, void *data)
+{
+ SMBusDeviceClass *k = SMBUS_DEVICE_CLASS(klass);
+
+ k->quick_cmd = pmbus_quick_cmd;
+ k->write_data = pmbus_write_data;
+ k->receive_byte = pmbus_receive_byte;
+}
+
+static const TypeInfo pmbus_device_type_info = {
+ .name = TYPE_PMBUS_DEVICE,
+ .parent = TYPE_SMBUS_DEVICE,
+ .instance_size = sizeof(PMBusDevice),
+ .instance_finalize = pmbus_device_finalize,
+ .abstract = true,
+ .class_size = sizeof(PMBusDeviceClass),
+ .class_init = pmbus_device_class_init,
+};
+
+static void pmbus_device_register_types(void)
+{
+ type_register_static(&pmbus_device_type_info);
+}
+
+type_init(pmbus_device_register_types)
diff --git a/hw/i2c/ppc4xx_i2c.c b/hw/i2c/ppc4xx_i2c.c
index c0a8e04..75d50f1 100644
--- a/hw/i2c/ppc4xx_i2c.c
+++ b/hw/i2c/ppc4xx_i2c.c
@@ -1,6 +1,8 @@
/*
* PPC4xx I2C controller emulation
*
+ * Documentation: PPC405GP User's Manual, Chapter 22. IIC Bus Interface
+ *
* Copyright (c) 2007 Jocelyn Mayer
* Copyright (c) 2012 François Revol
* Copyright (c) 2016-2018 BALATON Zoltan
@@ -238,11 +240,14 @@ static void ppc4xx_i2c_writeb(void *opaque, hwaddr addr, uint64_t value,
i2c->sts &= ~IIC_STS_ERR;
}
}
- if (!(i2c->sts & IIC_STS_ERR) &&
- i2c_send_recv(i2c->bus, &i2c->mdata[i], !recv)) {
- i2c->sts |= IIC_STS_ERR;
- i2c->extsts |= IIC_EXTSTS_XFRA;
- break;
+ if (!(i2c->sts & IIC_STS_ERR)) {
+ if (recv) {
+ i2c->mdata[i] = i2c_recv(i2c->bus);
+ } else if (i2c_send(i2c->bus, i2c->mdata[i]) < 0) {
+ i2c->sts |= IIC_STS_ERR;
+ i2c->extsts |= IIC_EXTSTS_XFRA;
+ break;
+ }
}
if (value & IIC_CNTL_RPST || !(value & IIC_CNTL_CHT)) {
i2c_end_transfer(i2c->bus);
diff --git a/hw/i2c/smbus_master.c b/hw/i2c/smbus_master.c
index dc43b86..6a53c34 100644
--- a/hw/i2c/smbus_master.c
+++ b/hw/i2c/smbus_master.c
@@ -29,7 +29,7 @@ int smbus_receive_byte(I2CBus *bus, uint8_t addr)
{
uint8_t data;
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
return -1;
}
data = i2c_recv(bus);
@@ -40,7 +40,7 @@ int smbus_receive_byte(I2CBus *bus, uint8_t addr)
int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
{
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, data);
@@ -51,11 +51,11 @@ int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
{
uint8_t data;
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
i2c_end_transfer(bus);
return -1;
}
@@ -67,7 +67,7 @@ int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
{
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
@@ -79,11 +79,11 @@ int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
{
uint16_t data;
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
i2c_end_transfer(bus);
return -1;
}
@@ -96,7 +96,7 @@ int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
int smbus_write_word(I2CBus *bus, uint8_t addr, uint8_t command, uint16_t data)
{
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
@@ -113,12 +113,12 @@ int smbus_read_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
int i;
if (send_cmd) {
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
}
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
if (send_cmd) {
i2c_end_transfer(bus);
}
@@ -149,7 +149,7 @@ int smbus_write_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
len = 32;
}
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
diff --git a/hw/input/lm832x.c b/hw/input/lm832x.c
index 4cb1e9d..19a646d 100644
--- a/hw/input/lm832x.c
+++ b/hw/input/lm832x.c
@@ -19,6 +19,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/input/lm832x.h"
#include "hw/i2c/i2c.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
@@ -27,7 +28,6 @@
#include "ui/console.h"
#include "qom/object.h"
-#define TYPE_LM8323 "lm8323"
OBJECT_DECLARE_SIMPLE_TYPE(LM823KbdState, LM8323)
struct LM823KbdState {
diff --git a/hw/intc/arm_gicv3_cpuif.c b/hw/intc/arm_gicv3_cpuif.c
index 3e0641a..a032d50 100644
--- a/hw/intc/arm_gicv3_cpuif.c
+++ b/hw/intc/arm_gicv3_cpuif.c
@@ -1227,7 +1227,7 @@ static void icv_dir_write(CPUARMState *env, const ARMCPRegInfo *ri,
trace_gicv3_icv_dir_write(gicv3_redist_affid(cs), value);
- if (irq >= cs->gic->num_irq) {
+ if (irq >= GICV3_MAXIRQ) {
/* Also catches special interrupt numbers and LPIs */
return;
}
@@ -1262,7 +1262,7 @@ static void icv_eoir_write(CPUARMState *env, const ARMCPRegInfo *ri,
trace_gicv3_icv_eoir_write(ri->crm == 8 ? 0 : 1,
gicv3_redist_affid(cs), value);
- if (irq >= cs->gic->num_irq) {
+ if (irq >= GICV3_MAXIRQ) {
/* Also catches special interrupt numbers and LPIs */
return;
}
diff --git a/hw/intc/arm_gicv3_redist.c b/hw/intc/arm_gicv3_redist.c
index 8645220..53da703 100644
--- a/hw/intc/arm_gicv3_redist.c
+++ b/hw/intc/arm_gicv3_redist.c
@@ -453,7 +453,7 @@ MemTxResult gicv3_redist_read(void *opaque, hwaddr offset, uint64_t *data,
if (r == MEMTX_ERROR) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid guest read at offset " TARGET_FMT_plx
- "size %u\n", __func__, offset, size);
+ " size %u\n", __func__, offset, size);
trace_gicv3_redist_badread(gicv3_redist_affid(cs), offset,
size, attrs.secure);
/* The spec requires that reserved registers are RAZ/WI;
@@ -510,7 +510,7 @@ MemTxResult gicv3_redist_write(void *opaque, hwaddr offset, uint64_t data,
if (r == MEMTX_ERROR) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid guest write at offset " TARGET_FMT_plx
- "size %u\n", __func__, offset, size);
+ " size %u\n", __func__, offset, size);
trace_gicv3_redist_badwrite(gicv3_redist_affid(cs), offset, data,
size, attrs.secure);
/* The spec requires that reserved registers are RAZ/WI;
diff --git a/hw/ipmi/ipmi_bmc_sim.c b/hw/ipmi/ipmi_bmc_sim.c
index 55fb81f..905e091 100644
--- a/hw/ipmi/ipmi_bmc_sim.c
+++ b/hw/ipmi/ipmi_bmc_sim.c
@@ -189,7 +189,7 @@ struct IPMIBmcSim {
uint8_t watchdog_use;
uint8_t watchdog_action;
uint8_t watchdog_pretimeout; /* In seconds */
- bool watchdog_expired;
+ uint8_t watchdog_expired;
uint16_t watchdog_timeout; /* in 100's of milliseconds */
bool watchdog_running;
@@ -2110,7 +2110,7 @@ static const VMStateDescription vmstate_ipmi_sim = {
VMSTATE_UINT8(watchdog_use, IPMIBmcSim),
VMSTATE_UINT8(watchdog_action, IPMIBmcSim),
VMSTATE_UINT8(watchdog_pretimeout, IPMIBmcSim),
- VMSTATE_BOOL(watchdog_expired, IPMIBmcSim),
+ VMSTATE_UINT8(watchdog_expired, IPMIBmcSim),
VMSTATE_UINT16(watchdog_timeout, IPMIBmcSim),
VMSTATE_BOOL(watchdog_running, IPMIBmcSim),
VMSTATE_BOOL(watchdog_preaction_ran, IPMIBmcSim),
diff --git a/hw/meson.build b/hw/meson.build
index ba0601e..b3366c8 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -31,6 +31,7 @@ subdir('rdma')
subdir('rtc')
subdir('scsi')
subdir('sd')
+subdir('sensor')
subdir('smbios')
subdir('ssi')
subdir('timer')
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index c71ed25..507058d 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -11,21 +11,6 @@ config ARMSSE_MHU
config ARMSSE_CPU_PWRCTRL
bool
-config MAX111X
- bool
-
-config TMP105
- bool
- depends on I2C
-
-config TMP421
- bool
- depends on I2C
-
-config EMC141X
- bool
- depends on I2C
-
config ISA_DEBUG
bool
depends on ISA_BUS
diff --git a/hw/misc/auxbus.c b/hw/misc/auxbus.c
index 6c099ae..434ff8d 100644
--- a/hw/misc/auxbus.c
+++ b/hw/misc/auxbus.c
@@ -106,7 +106,6 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
AUXReply ret = AUX_NACK;
I2CBus *i2c_bus = aux_get_i2c_bus(bus);
size_t i;
- bool is_write = false;
DPRINTF("request at address 0x%" PRIX32 ", command %u, len %u\n", address,
cmd, len);
@@ -117,11 +116,10 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
*/
case WRITE_AUX:
case READ_AUX:
- is_write = cmd == READ_AUX ? false : true;
for (i = 0; i < len; i++) {
if (!address_space_rw(&bus->aux_addr_space, address++,
MEMTXATTRS_UNSPECIFIED, data++, 1,
- is_write)) {
+ cmd == WRITE_AUX)) {
ret = AUX_I2C_ACK;
} else {
ret = AUX_NACK;
@@ -133,24 +131,37 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
* Classic I2C transactions..
*/
case READ_I2C:
+ if (i2c_bus_busy(i2c_bus)) {
+ i2c_end_transfer(i2c_bus);
+ }
+
+ if (i2c_start_recv(i2c_bus, address)) {
+ ret = AUX_I2C_NACK;
+ break;
+ }
+
+ ret = AUX_I2C_ACK;
+ for (i = 0; i < len; i++) {
+ data[i] = i2c_recv(i2c_bus);
+ }
+ i2c_end_transfer(i2c_bus);
+ break;
case WRITE_I2C:
- is_write = cmd == READ_I2C ? false : true;
if (i2c_bus_busy(i2c_bus)) {
i2c_end_transfer(i2c_bus);
}
- if (i2c_start_transfer(i2c_bus, address, is_write)) {
+ if (i2c_start_send(i2c_bus, address)) {
ret = AUX_I2C_NACK;
break;
}
ret = AUX_I2C_ACK;
- while (len > 0) {
- if (i2c_send_recv(i2c_bus, data++, is_write) < 0) {
+ for (i = 0; i < len; i++) {
+ if (i2c_send(i2c_bus, data[i]) < 0) {
ret = AUX_I2C_NACK;
break;
}
- len--;
}
i2c_end_transfer(i2c_bus);
break;
@@ -163,14 +174,12 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
* - We changed the address.
*/
case WRITE_I2C_MOT:
- case READ_I2C_MOT:
- is_write = cmd == READ_I2C_MOT ? false : true;
ret = AUX_I2C_NACK;
if (!i2c_bus_busy(i2c_bus)) {
/*
* No transactions started..
*/
- if (i2c_start_transfer(i2c_bus, address, is_write)) {
+ if (i2c_start_send(i2c_bus, address)) {
break;
}
} else if ((address != bus->last_i2c_address) ||
@@ -179,23 +188,48 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
* Transaction started but we need to restart..
*/
i2c_end_transfer(i2c_bus);
- if (i2c_start_transfer(i2c_bus, address, is_write)) {
+ if (i2c_start_send(i2c_bus, address)) {
break;
}
}
bus->last_transaction = cmd;
bus->last_i2c_address = address;
- while (len > 0) {
- if (i2c_send_recv(i2c_bus, data++, is_write) < 0) {
+ ret = AUX_I2C_ACK;
+ for (i = 0; i < len; i++) {
+ if (i2c_send(i2c_bus, data[i]) < 0) {
i2c_end_transfer(i2c_bus);
+ ret = AUX_I2C_NACK;
break;
}
- len--;
}
- if (len == 0) {
- ret = AUX_I2C_ACK;
+ break;
+ case READ_I2C_MOT:
+ ret = AUX_I2C_NACK;
+ if (!i2c_bus_busy(i2c_bus)) {
+ /*
+ * No transactions started..
+ */
+ if (i2c_start_recv(i2c_bus, address)) {
+ break;
+ }
+ } else if ((address != bus->last_i2c_address) ||
+ (bus->last_transaction != cmd)) {
+ /*
+ * Transaction started but we need to restart..
+ */
+ i2c_end_transfer(i2c_bus);
+ if (i2c_start_recv(i2c_bus, address)) {
+ break;
+ }
+ }
+
+ bus->last_transaction = cmd;
+ bus->last_i2c_address = address;
+ for (i = 0; i < len; i++) {
+ data[i] = i2c_recv(i2c_bus);
}
+ ret = AUX_I2C_ACK;
break;
default:
qemu_log_mask(LOG_UNIMP, "AUX cmd=%u not implemented\n", cmd);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index f89b5c1..a53b849 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -3,13 +3,9 @@ softmmu_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
softmmu_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
softmmu_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
softmmu_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
-softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
softmmu_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c'))
softmmu_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
softmmu_ss.add(when: 'CONFIG_SGA', if_true: files('sga.c'))
-softmmu_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
-softmmu_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
-softmmu_ss.add(when: 'CONFIG_EMC141X', if_true: files('emc141x.c'))
softmmu_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
softmmu_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
softmmu_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
@@ -85,7 +81,7 @@ softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_powermgt.c',
))
softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
-softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c', 'zynq-xadc.c'))
+softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-xramc.c'))
softmmu_ss.add(when: 'CONFIG_STM32F2XX_SYSCFG', if_true: files('stm32f2xx_syscfg.c'))
softmmu_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.c'))
diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
index 66e0b15..7fcafec 100644
--- a/hw/ppc/Kconfig
+++ b/hw/ppc/Kconfig
@@ -13,6 +13,7 @@ config PSERIES
select MSI_NONBROKEN
select FDT_PPC
select CHRP_NVRAM
+ select VOF
config SPAPR_RNG
bool
@@ -75,6 +76,7 @@ config PEGASOS2
select VT82C686
select IDE_VIA
select SMBUS_EEPROM
+ select VOF
# This should come with VT82C686
select ACPI_X86
@@ -144,3 +146,6 @@ config FW_CFG_PPC
config FDT_PPC
bool
+
+config VOF
+ bool
diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
index 597d974..aa4c8e6 100644
--- a/hw/ppc/meson.build
+++ b/hw/ppc/meson.build
@@ -84,4 +84,7 @@ ppc_ss.add(when: 'CONFIG_VIRTEX', if_true: files('virtex_ml507.c'))
# Pegasos2
ppc_ss.add(when: 'CONFIG_PEGASOS2', if_true: files('pegasos2.c'))
+ppc_ss.add(when: 'CONFIG_VOF', if_true: files('vof.c'))
+ppc_ss.add(when: ['CONFIG_VOF', 'CONFIG_PSERIES'], if_true: files('spapr_vof.c'))
+
hw_arch += {'ppc': ppc_ss}
diff --git a/hw/ppc/pegasos2.c b/hw/ppc/pegasos2.c
index 0bfd092..9a6ae86 100644
--- a/hw/ppc/pegasos2.c
+++ b/hw/ppc/pegasos2.c
@@ -1,7 +1,7 @@
/*
* QEMU PowerPC CHRP (Genesi/bPlan Pegasos II) hardware System Emulator
*
- * Copyright (c) 2018-2020 BALATON Zoltan
+ * Copyright (c) 2018-2021 BALATON Zoltan
*
* This work is licensed under the GNU GPL license version 2 or later.
*
@@ -34,26 +34,68 @@
#include "trace.h"
#include "qemu/datadir.h"
#include "sysemu/device_tree.h"
+#include "hw/ppc/vof.h"
-#define PROM_FILENAME "pegasos2.rom"
+#include <libfdt.h>
+
+#define PROM_FILENAME "vof.bin"
#define PROM_ADDR 0xfff00000
#define PROM_SIZE 0x80000
+#define KVMPPC_HCALL_BASE 0xf000
+#define KVMPPC_H_RTAS (KVMPPC_HCALL_BASE + 0x0)
+#define KVMPPC_H_VOF_CLIENT (KVMPPC_HCALL_BASE + 0x5)
+
+#define H_SUCCESS 0
+#define H_PRIVILEGE -3 /* Caller not privileged */
+#define H_PARAMETER -4 /* Parameter invalid, out-of-range or conflicting */
+
#define BUS_FREQ_HZ 133333333
+#define PCI0_MEM_BASE 0xc0000000
+#define PCI0_MEM_SIZE 0x20000000
+#define PCI0_IO_BASE 0xf8000000
+#define PCI0_IO_SIZE 0x10000
+
+#define PCI1_MEM_BASE 0x80000000
+#define PCI1_MEM_SIZE 0x40000000
+#define PCI1_IO_BASE 0xfe000000
+#define PCI1_IO_SIZE 0x10000
+
+#define TYPE_PEGASOS2_MACHINE MACHINE_TYPE_NAME("pegasos2")
+OBJECT_DECLARE_TYPE(Pegasos2MachineState, MachineClass, PEGASOS2_MACHINE)
+
+struct Pegasos2MachineState {
+ MachineState parent_obj;
+ PowerPCCPU *cpu;
+ DeviceState *mv;
+ Vof *vof;
+ void *fdt_blob;
+ uint64_t kernel_addr;
+ uint64_t kernel_entry;
+ uint64_t kernel_size;
+};
+
+static void *build_fdt(MachineState *machine, int *fdt_size);
+
static void pegasos2_cpu_reset(void *opaque)
{
PowerPCCPU *cpu = opaque;
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(current_machine);
cpu_reset(CPU(cpu));
cpu->env.spr[SPR_HID1] = 7ULL << 28;
+ if (pm->vof) {
+ cpu->env.gpr[1] = 2 * VOF_STACK_SIZE - 0x20;
+ cpu->env.nip = 0x100;
+ }
}
static void pegasos2_init(MachineState *machine)
{
- PowerPCCPU *cpu = NULL;
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(machine);
+ CPUPPCState *env;
MemoryRegion *rom = g_new(MemoryRegion, 1);
- DeviceState *mv;
PCIBus *pci_bus;
PCIDevice *dev;
I2CBus *i2c_bus;
@@ -63,15 +105,16 @@ static void pegasos2_init(MachineState *machine)
uint8_t *spd_data;
/* init CPU */
- cpu = POWERPC_CPU(cpu_create(machine->cpu_type));
- if (PPC_INPUT(&cpu->env) != PPC_FLAGS_INPUT_6xx) {
+ pm->cpu = POWERPC_CPU(cpu_create(machine->cpu_type));
+ env = &pm->cpu->env;
+ if (PPC_INPUT(env) != PPC_FLAGS_INPUT_6xx) {
error_report("Incompatible CPU, only 6xx bus supported");
exit(1);
}
/* Set time-base frequency */
- cpu_ppc_tb_init(&cpu->env, BUS_FREQ_HZ / 4);
- qemu_register_reset(pegasos2_cpu_reset, cpu);
+ cpu_ppc_tb_init(env, BUS_FREQ_HZ / 4);
+ qemu_register_reset(pegasos2_cpu_reset, pm->cpu);
/* RAM */
memory_region_add_subregion(get_system_memory(), 0, machine->ram);
@@ -82,30 +125,36 @@ static void pegasos2_init(MachineState *machine)
error_report("Could not find firmware '%s'", fwname);
exit(1);
}
+ if (!machine->firmware && !pm->vof) {
+ pm->vof = g_malloc0(sizeof(*pm->vof));
+ }
memory_region_init_rom(rom, NULL, "pegasos2.rom", PROM_SIZE, &error_fatal);
memory_region_add_subregion(get_system_memory(), PROM_ADDR, rom);
sz = load_elf(filename, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1,
PPC_ELF_MACHINE, 0, 0);
if (sz <= 0) {
- sz = load_image_targphys(filename, PROM_ADDR, PROM_SIZE);
+ sz = load_image_targphys(filename, pm->vof ? 0 : PROM_ADDR, PROM_SIZE);
}
if (sz <= 0 || sz > PROM_SIZE) {
error_report("Could not load firmware '%s'", filename);
exit(1);
}
g_free(filename);
+ if (pm->vof) {
+ pm->vof->fw_size = sz;
+ }
/* Marvell Discovery II system controller */
- mv = DEVICE(sysbus_create_simple(TYPE_MV64361, -1,
- ((qemu_irq *)cpu->env.irq_inputs)[PPC6xx_INPUT_INT]));
- pci_bus = mv64361_get_pci_bus(mv, 1);
+ pm->mv = DEVICE(sysbus_create_simple(TYPE_MV64361, -1,
+ ((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_INT]));
+ pci_bus = mv64361_get_pci_bus(pm->mv, 1);
/* VIA VT8231 South Bridge (multifunction PCI device) */
/* VT8231 function 0: PCI-to-ISA Bridge */
dev = pci_create_simple_multifunction(pci_bus, PCI_DEVFN(12, 0), true,
TYPE_VT8231_ISA);
qdev_connect_gpio_out(DEVICE(dev), 0,
- qdev_get_gpio_in_named(mv, "gpp", 31));
+ qdev_get_gpio_in_named(pm->mv, "gpp", 31));
/* VT8231 function 1: IDE Controller */
dev = pci_create_simple(pci_bus, PCI_DEVFN(12, 1), "via-ide");
@@ -127,18 +176,728 @@ static void pegasos2_init(MachineState *machine)
/* other PC hardware */
pci_vga_init(pci_bus);
+
+ if (machine->kernel_filename) {
+ sz = load_elf(machine->kernel_filename, NULL, NULL, NULL,
+ &pm->kernel_entry, &pm->kernel_addr, NULL, NULL, 1,
+ PPC_ELF_MACHINE, 0, 0);
+ if (sz <= 0) {
+ error_report("Could not load kernel '%s'",
+ machine->kernel_filename);
+ exit(1);
+ }
+ pm->kernel_size = sz;
+ if (!pm->vof) {
+ warn_report("Option -kernel may be ineffective with -bios.");
+ }
+ }
+ if (machine->kernel_cmdline && !pm->vof) {
+ warn_report("Option -append may be ineffective with -bios.");
+ }
+}
+
+static uint32_t pegasos2_pci_config_read(AddressSpace *as, int bus,
+ uint32_t addr, uint32_t len)
+{
+ hwaddr pcicfg = (bus ? 0xf1000c78 : 0xf1000cf8);
+ uint32_t val = 0xffffffff;
+
+ stl_le_phys(as, pcicfg, addr | BIT(31));
+ switch (len) {
+ case 4:
+ val = ldl_le_phys(as, pcicfg + 4);
+ break;
+ case 2:
+ val = lduw_le_phys(as, pcicfg + 4);
+ break;
+ case 1:
+ val = ldub_phys(as, pcicfg + 4);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid length\n", __func__);
+ break;
+ }
+ return val;
+}
+
+static void pegasos2_pci_config_write(AddressSpace *as, int bus, uint32_t addr,
+ uint32_t len, uint32_t val)
+{
+ hwaddr pcicfg = (bus ? 0xf1000c78 : 0xf1000cf8);
+
+ stl_le_phys(as, pcicfg, addr | BIT(31));
+ switch (len) {
+ case 4:
+ stl_le_phys(as, pcicfg + 4, val);
+ break;
+ case 2:
+ stw_le_phys(as, pcicfg + 4, val);
+ break;
+ case 1:
+ stb_phys(as, pcicfg + 4, val);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid length\n", __func__);
+ break;
+ }
+}
+
+static void pegasos2_machine_reset(MachineState *machine)
+{
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(machine);
+ AddressSpace *as = CPU(pm->cpu)->as;
+ void *fdt;
+ uint64_t d[2];
+ int sz;
+
+ qemu_devices_reset();
+ if (!pm->vof) {
+ return; /* Firmware should set up machine so nothing to do */
+ }
+
+ /* Otherwise, set up devices that board firmware would normally do */
+ stl_le_phys(as, 0xf1000000, 0x28020ff);
+ stl_le_phys(as, 0xf1000278, 0xa31fc);
+ stl_le_phys(as, 0xf100f300, 0x11ff0400);
+ stl_le_phys(as, 0xf100f10c, 0x80000000);
+ stl_le_phys(as, 0xf100001c, 0x8000000);
+ pegasos2_pci_config_write(as, 0, PCI_COMMAND, 2, PCI_COMMAND_IO |
+ PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+ pegasos2_pci_config_write(as, 1, PCI_COMMAND, 2, PCI_COMMAND_IO |
+ PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 0) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x9);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 0) << 8) |
+ 0x50, 1, 0x2);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x109);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ PCI_CLASS_PROG, 1, 0xf);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ 0x40, 1, 0xb);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ 0x50, 4, 0x17171717);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ PCI_COMMAND, 2, 0x87);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 2) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x409);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 3) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x409);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x9);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ 0x48, 4, 0xf00);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ 0x40, 4, 0x558020);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ 0x90, 4, 0xd00);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 5) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x309);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 6) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x309);
+
+ /* Device tree and VOF set up */
+ vof_init(pm->vof, machine->ram_size, &error_fatal);
+ if (vof_claim(pm->vof, 0, VOF_STACK_SIZE, VOF_STACK_SIZE) == -1) {
+ error_report("Memory allocation for stack failed");
+ exit(1);
+ }
+ if (pm->kernel_size &&
+ vof_claim(pm->vof, pm->kernel_addr, pm->kernel_size, 0) == -1) {
+ error_report("Memory for kernel is in use");
+ exit(1);
+ }
+ fdt = build_fdt(machine, &sz);
+ /* FIXME: VOF assumes entry is same as load address */
+ d[0] = cpu_to_be64(pm->kernel_entry);
+ d[1] = cpu_to_be64(pm->kernel_size - (pm->kernel_entry - pm->kernel_addr));
+ qemu_fdt_setprop(fdt, "/chosen", "qemu,boot-kernel", d, sizeof(d));
+
+ qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt));
+ g_free(pm->fdt_blob);
+ pm->fdt_blob = fdt;
+
+ vof_build_dt(fdt, pm->vof);
+ vof_client_open_store(fdt, pm->vof, "/chosen", "stdout", "/failsafe");
+ pm->cpu->vhyp = PPC_VIRTUAL_HYPERVISOR(machine);
}
-static void pegasos2_machine(MachineClass *mc)
+enum pegasos2_rtas_tokens {
+ RTAS_RESTART_RTAS = 0,
+ RTAS_NVRAM_FETCH = 1,
+ RTAS_NVRAM_STORE = 2,
+ RTAS_GET_TIME_OF_DAY = 3,
+ RTAS_SET_TIME_OF_DAY = 4,
+ RTAS_EVENT_SCAN = 6,
+ RTAS_CHECK_EXCEPTION = 7,
+ RTAS_READ_PCI_CONFIG = 8,
+ RTAS_WRITE_PCI_CONFIG = 9,
+ RTAS_DISPLAY_CHARACTER = 10,
+ RTAS_SET_INDICATOR = 11,
+ RTAS_POWER_OFF = 17,
+ RTAS_SUSPEND = 18,
+ RTAS_HIBERNATE = 19,
+ RTAS_SYSTEM_REBOOT = 20,
+};
+
+static target_ulong pegasos2_rtas(PowerPCCPU *cpu, Pegasos2MachineState *pm,
+ target_ulong args_real)
{
+ AddressSpace *as = CPU(cpu)->as;
+ uint32_t token = ldl_be_phys(as, args_real);
+ uint32_t nargs = ldl_be_phys(as, args_real + 4);
+ uint32_t nrets = ldl_be_phys(as, args_real + 8);
+ uint32_t args = args_real + 12;
+ uint32_t rets = args_real + 12 + nargs * 4;
+
+ if (nrets < 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Too few return values in RTAS call\n");
+ return H_PARAMETER;
+ }
+ switch (token) {
+ case RTAS_READ_PCI_CONFIG:
+ {
+ uint32_t addr, len, val;
+
+ if (nargs != 2 || nrets != 2) {
+ stl_be_phys(as, rets, -1);
+ return H_PARAMETER;
+ }
+ addr = ldl_be_phys(as, args);
+ len = ldl_be_phys(as, args + 4);
+ val = pegasos2_pci_config_read(as, !(addr >> 24),
+ addr & 0x0fffffff, len);
+ stl_be_phys(as, rets, 0);
+ stl_be_phys(as, rets + 4, val);
+ return H_SUCCESS;
+ }
+ case RTAS_WRITE_PCI_CONFIG:
+ {
+ uint32_t addr, len, val;
+
+ if (nargs != 3 || nrets != 1) {
+ stl_be_phys(as, rets, -1);
+ return H_PARAMETER;
+ }
+ addr = ldl_be_phys(as, args);
+ len = ldl_be_phys(as, args + 4);
+ val = ldl_be_phys(as, args + 8);
+ pegasos2_pci_config_write(as, !(addr >> 24),
+ addr & 0x0fffffff, len, val);
+ stl_be_phys(as, rets, 0);
+ return H_SUCCESS;
+ }
+ case RTAS_DISPLAY_CHARACTER:
+ if (nargs != 1 || nrets != 1) {
+ stl_be_phys(as, rets, -1);
+ return H_PARAMETER;
+ }
+ qemu_log_mask(LOG_UNIMP, "%c", ldl_be_phys(as, args));
+ stl_be_phys(as, rets, 0);
+ return H_SUCCESS;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Unknown RTAS token %u (args=%u, rets=%u)\n",
+ token, nargs, nrets);
+ stl_be_phys(as, rets, 0);
+ return H_SUCCESS;
+ }
+}
+
+static void pegasos2_hypercall(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
+{
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(vhyp);
+ CPUPPCState *env = &cpu->env;
+
+ /* The TCG path should also be holding the BQL at this point */
+ g_assert(qemu_mutex_iothread_locked());
+
+ if (msr_pr) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Hypercall made with MSR[PR]=1\n");
+ env->gpr[3] = H_PRIVILEGE;
+ } else if (env->gpr[3] == KVMPPC_H_RTAS) {
+ env->gpr[3] = pegasos2_rtas(cpu, pm, env->gpr[4]);
+ } else if (env->gpr[3] == KVMPPC_H_VOF_CLIENT) {
+ int ret = vof_client_call(MACHINE(pm), pm->vof, pm->fdt_blob,
+ env->gpr[4]);
+ env->gpr[3] = (ret ? H_PARAMETER : H_SUCCESS);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "Unsupported hypercall " TARGET_FMT_lx
+ "\n", env->gpr[3]);
+ env->gpr[3] = -1;
+ }
+}
+
+static void vhyp_nop(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
+{
+}
+
+static target_ulong vhyp_encode_hpt_for_kvm_pr(PPCVirtualHypervisor *vhyp)
+{
+ return POWERPC_CPU(current_cpu)->env.spr[SPR_SDR1];
+}
+
+static void pegasos2_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ PPCVirtualHypervisorClass *vhc = PPC_VIRTUAL_HYPERVISOR_CLASS(oc);
+
mc->desc = "Genesi/bPlan Pegasos II";
mc->init = pegasos2_init;
+ mc->reset = pegasos2_machine_reset;
mc->block_default_type = IF_IDE;
mc->default_boot_order = "cd";
mc->default_display = "std";
mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("7400_v2.9");
mc->default_ram_id = "pegasos2.ram";
mc->default_ram_size = 512 * MiB;
+
+ vhc->hypercall = pegasos2_hypercall;
+ vhc->cpu_exec_enter = vhyp_nop;
+ vhc->cpu_exec_exit = vhyp_nop;
+ vhc->encode_hpt_for_kvm_pr = vhyp_encode_hpt_for_kvm_pr;
+}
+
+static const TypeInfo pegasos2_machine_info = {
+ .name = TYPE_PEGASOS2_MACHINE,
+ .parent = TYPE_MACHINE,
+ .class_init = pegasos2_machine_class_init,
+ .instance_size = sizeof(Pegasos2MachineState),
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_PPC_VIRTUAL_HYPERVISOR },
+ { }
+ },
+};
+
+static void pegasos2_machine_register_types(void)
+{
+ type_register_static(&pegasos2_machine_info);
+}
+
+type_init(pegasos2_machine_register_types)
+
+/* FDT creation for passing to firmware */
+
+typedef struct {
+ void *fdt;
+ const char *path;
+} FDTInfo;
+
+/* We do everything in reverse order so it comes out right in the tree */
+
+static void dt_ide(PCIBus *bus, PCIDevice *d, FDTInfo *fi)
+{
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "device_type", "spi");
}
-DEFINE_MACHINE("pegasos2", pegasos2_machine)
+static void dt_usb(PCIBus *bus, PCIDevice *d, FDTInfo *fi)
+{
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#size-cells", 0);
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#address-cells", 1);
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "device_type", "usb");
+}
+
+static void dt_isa(PCIBus *bus, PCIDevice *d, FDTInfo *fi)
+{
+ GString *name = g_string_sized_new(64);
+ uint32_t cells[3];
+
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#size-cells", 1);
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#address-cells", 2);
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "device_type", "isa");
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "name", "isa");
+
+ /* addional devices */
+ g_string_printf(name, "%s/lpt@i3bc", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(7);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x3bc);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "lpt");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "lpt");
+
+ g_string_printf(name, "%s/fdc@i3f0", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(6);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x3f0);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "fdc");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "fdc");
+
+ g_string_printf(name, "%s/timer@i40", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x40);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "timer");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "timer");
+
+ g_string_printf(name, "%s/rtc@i70", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_string(fi->fdt, name->str, "compatible", "ds1385-rtc");
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(8);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x70);
+ cells[2] = cpu_to_be32(2);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "rtc");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "rtc");
+
+ g_string_printf(name, "%s/keyboard@i60", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ cells[0] = cpu_to_be32(1);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x60);
+ cells[2] = cpu_to_be32(5);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "keyboard");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "keyboard");
+
+ g_string_printf(name, "%s/8042@i60", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "#interrupt-cells", 2);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "#size-cells", 0);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "#address-cells", 1);
+ qemu_fdt_setprop_string(fi->fdt, name->str, "interrupt-controller", "");
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x60);
+ cells[2] = cpu_to_be32(5);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "8042");
+
+ g_string_printf(name, "%s/serial@i2f8", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(3);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x2f8);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "serial");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "serial");
+
+ g_string_free(name, TRUE);
+}
+
+static struct {
+ const char *id;
+ const char *name;
+ void (*dtf)(PCIBus *bus, PCIDevice *d, FDTInfo *fi);
+} device_map[] = {
+ { "pci11ab,6460", "host", NULL },
+ { "pci1106,8231", "isa", dt_isa },
+ { "pci1106,571", "ide", dt_ide },
+ { "pci1106,3044", "firewire", NULL },
+ { "pci1106,3038", "usb", dt_usb },
+ { "pci1106,8235", "other", NULL },
+ { "pci1106,3058", "sound", NULL },
+ { NULL, NULL }
+};
+
+static void add_pci_device(PCIBus *bus, PCIDevice *d, void *opaque)
+{
+ FDTInfo *fi = opaque;
+ GString *node = g_string_new(NULL);
+ uint32_t cells[(PCI_NUM_REGIONS + 1) * 5];
+ int i, j;
+ const char *name = NULL;
+ g_autofree const gchar *pn = g_strdup_printf("pci%x,%x",
+ pci_get_word(&d->config[PCI_VENDOR_ID]),
+ pci_get_word(&d->config[PCI_DEVICE_ID]));
+
+ for (i = 0; device_map[i].id; i++) {
+ if (!strcmp(pn, device_map[i].id)) {
+ name = device_map[i].name;
+ break;
+ }
+ }
+ g_string_printf(node, "%s/%s@%x", fi->path, (name ?: pn),
+ PCI_SLOT(d->devfn));
+ if (PCI_FUNC(d->devfn)) {
+ g_string_append_printf(node, ",%x", PCI_FUNC(d->devfn));
+ }
+
+ qemu_fdt_add_subnode(fi->fdt, node->str);
+ if (device_map[i].dtf) {
+ FDTInfo cfi = { fi->fdt, node->str };
+ device_map[i].dtf(bus, d, &cfi);
+ }
+ cells[0] = cpu_to_be32(d->devfn << 8);
+ cells[1] = 0;
+ cells[2] = 0;
+ cells[3] = 0;
+ cells[4] = 0;
+ j = 5;
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ if (!d->io_regions[i].size) {
+ continue;
+ }
+ cells[j] = cpu_to_be32(d->devfn << 8 | (PCI_BASE_ADDRESS_0 + i * 4));
+ if (d->io_regions[i].type & PCI_BASE_ADDRESS_SPACE_IO) {
+ cells[j] |= cpu_to_be32(1 << 24);
+ } else {
+ cells[j] |= cpu_to_be32(2 << 24);
+ if (d->io_regions[i].type & PCI_BASE_ADDRESS_MEM_PREFETCH) {
+ cells[j] |= cpu_to_be32(4 << 28);
+ }
+ }
+ cells[j + 1] = 0;
+ cells[j + 2] = 0;
+ cells[j + 3] = cpu_to_be32(d->io_regions[i].size >> 32);
+ cells[j + 4] = cpu_to_be32(d->io_regions[i].size);
+ j += 5;
+ }
+ qemu_fdt_setprop(fi->fdt, node->str, "reg", cells, j * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, node->str, "name", name ?: pn);
+ if (pci_get_byte(&d->config[PCI_INTERRUPT_PIN])) {
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "interrupts",
+ pci_get_byte(&d->config[PCI_INTERRUPT_PIN]));
+ }
+ /* Pegasos2 firmware has subsystem-id amd subsystem-vendor-id swapped */
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "subsystem-vendor-id",
+ pci_get_word(&d->config[PCI_SUBSYSTEM_ID]));
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "subsystem-id",
+ pci_get_word(&d->config[PCI_SUBSYSTEM_VENDOR_ID]));
+ cells[0] = pci_get_long(&d->config[PCI_CLASS_REVISION]);
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "class-code", cells[0] >> 8);
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "revision-id", cells[0] & 0xff);
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "device-id",
+ pci_get_word(&d->config[PCI_DEVICE_ID]));
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "vendor-id",
+ pci_get_word(&d->config[PCI_VENDOR_ID]));
+
+ g_string_free(node, TRUE);
+}
+
+static void *build_fdt(MachineState *machine, int *fdt_size)
+{
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(machine);
+ PowerPCCPU *cpu = pm->cpu;
+ PCIBus *pci_bus;
+ FDTInfo fi;
+ uint32_t cells[16];
+ void *fdt = create_device_tree(fdt_size);
+
+ fi.fdt = fdt;
+
+ /* root node */
+ qemu_fdt_setprop_string(fdt, "/", "CODEGEN,description",
+ "Pegasos CHRP PowerPC System");
+ qemu_fdt_setprop_string(fdt, "/", "CODEGEN,board", "Pegasos2");
+ qemu_fdt_setprop_string(fdt, "/", "CODEGEN,vendor", "bplan GmbH");
+ qemu_fdt_setprop_string(fdt, "/", "revision", "2B");
+ qemu_fdt_setprop_string(fdt, "/", "model", "Pegasos2");
+ qemu_fdt_setprop_string(fdt, "/", "device_type", "chrp");
+ qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 1);
+ qemu_fdt_setprop_string(fdt, "/", "name", "bplan,Pegasos2");
+
+ /* pci@c0000000 */
+ qemu_fdt_add_subnode(fdt, "/pci@c0000000");
+ cells[0] = 0;
+ cells[1] = 0;
+ qemu_fdt_setprop(fdt, "/pci@c0000000", "bus-range",
+ cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@c0000000", "pci-bridge-number", 1);
+ cells[0] = cpu_to_be32(PCI0_MEM_BASE);
+ cells[1] = cpu_to_be32(PCI0_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@c0000000", "reg", cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(0x01000000);
+ cells[1] = 0;
+ cells[2] = 0;
+ cells[3] = cpu_to_be32(PCI0_IO_BASE);
+ cells[4] = 0;
+ cells[5] = cpu_to_be32(PCI0_IO_SIZE);
+ cells[6] = cpu_to_be32(0x02000000);
+ cells[7] = 0;
+ cells[8] = cpu_to_be32(PCI0_MEM_BASE);
+ cells[9] = cpu_to_be32(PCI0_MEM_BASE);
+ cells[10] = 0;
+ cells[11] = cpu_to_be32(PCI0_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@c0000000", "ranges",
+ cells, 12 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@c0000000", "#size-cells", 2);
+ qemu_fdt_setprop_cell(fdt, "/pci@c0000000", "#address-cells", 3);
+ qemu_fdt_setprop_string(fdt, "/pci@c0000000", "device_type", "pci");
+ qemu_fdt_setprop_string(fdt, "/pci@c0000000", "name", "pci");
+
+ fi.path = "/pci@c0000000";
+ pci_bus = mv64361_get_pci_bus(pm->mv, 0);
+ pci_for_each_device_reverse(pci_bus, 0, add_pci_device, &fi);
+
+ /* pci@80000000 */
+ qemu_fdt_add_subnode(fdt, "/pci@80000000");
+ cells[0] = 0;
+ cells[1] = 0;
+ qemu_fdt_setprop(fdt, "/pci@80000000", "bus-range",
+ cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "pci-bridge-number", 0);
+ cells[0] = cpu_to_be32(PCI1_MEM_BASE);
+ cells[1] = cpu_to_be32(PCI1_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@80000000", "reg", cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "8259-interrupt-acknowledge",
+ 0xf1000cb4);
+ cells[0] = cpu_to_be32(0x01000000);
+ cells[1] = 0;
+ cells[2] = 0;
+ cells[3] = cpu_to_be32(PCI1_IO_BASE);
+ cells[4] = 0;
+ cells[5] = cpu_to_be32(PCI1_IO_SIZE);
+ cells[6] = cpu_to_be32(0x02000000);
+ cells[7] = 0;
+ cells[8] = cpu_to_be32(PCI1_MEM_BASE);
+ cells[9] = cpu_to_be32(PCI1_MEM_BASE);
+ cells[10] = 0;
+ cells[11] = cpu_to_be32(PCI1_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@80000000", "ranges",
+ cells, 12 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "#size-cells", 2);
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "#address-cells", 3);
+ qemu_fdt_setprop_string(fdt, "/pci@80000000", "device_type", "pci");
+ qemu_fdt_setprop_string(fdt, "/pci@80000000", "name", "pci");
+
+ fi.path = "/pci@80000000";
+ pci_bus = mv64361_get_pci_bus(pm->mv, 1);
+ pci_for_each_device_reverse(pci_bus, 0, add_pci_device, &fi);
+
+ qemu_fdt_add_subnode(fdt, "/failsafe");
+ qemu_fdt_setprop_string(fdt, "/failsafe", "device_type", "serial");
+ qemu_fdt_setprop_string(fdt, "/failsafe", "name", "failsafe");
+
+ qemu_fdt_add_subnode(fdt, "/rtas");
+ qemu_fdt_setprop_cell(fdt, "/rtas", "system-reboot", RTAS_SYSTEM_REBOOT);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "hibernate", RTAS_HIBERNATE);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "suspend", RTAS_SUSPEND);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "power-off", RTAS_POWER_OFF);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "set-indicator", RTAS_SET_INDICATOR);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "display-character",
+ RTAS_DISPLAY_CHARACTER);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "write-pci-config",
+ RTAS_WRITE_PCI_CONFIG);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "read-pci-config",
+ RTAS_READ_PCI_CONFIG);
+ /* Pegasos2 firmware misspells check-exception and guests use that */
+ qemu_fdt_setprop_cell(fdt, "/rtas", "check-execption",
+ RTAS_CHECK_EXCEPTION);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "event-scan", RTAS_EVENT_SCAN);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "set-time-of-day",
+ RTAS_SET_TIME_OF_DAY);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "get-time-of-day",
+ RTAS_GET_TIME_OF_DAY);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "nvram-store", RTAS_NVRAM_STORE);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "nvram-fetch", RTAS_NVRAM_FETCH);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "restart-rtas", RTAS_RESTART_RTAS);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-error-log-max", 0);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-event-scan-rate", 0);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-display-device", 0);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-size", 20);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-version", 1);
+
+ /* cpus */
+ qemu_fdt_add_subnode(fdt, "/cpus");
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#cpus", 1);
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 1);
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0);
+ qemu_fdt_setprop_string(fdt, "/cpus", "name", "cpus");
+
+ /* FIXME Get CPU name from CPU object */
+ const char *cp = "/cpus/PowerPC,G4";
+ qemu_fdt_add_subnode(fdt, cp);
+ qemu_fdt_setprop_cell(fdt, cp, "l2cr", 0);
+ qemu_fdt_setprop_cell(fdt, cp, "d-cache-size", 0x8000);
+ qemu_fdt_setprop_cell(fdt, cp, "d-cache-block-size",
+ cpu->env.dcache_line_size);
+ qemu_fdt_setprop_cell(fdt, cp, "d-cache-line-size",
+ cpu->env.dcache_line_size);
+ qemu_fdt_setprop_cell(fdt, cp, "i-cache-size", 0x8000);
+ qemu_fdt_setprop_cell(fdt, cp, "i-cache-block-size",
+ cpu->env.icache_line_size);
+ qemu_fdt_setprop_cell(fdt, cp, "i-cache-line-size",
+ cpu->env.icache_line_size);
+ if (cpu->env.id_tlbs) {
+ qemu_fdt_setprop_cell(fdt, cp, "i-tlb-sets", cpu->env.nb_ways);
+ qemu_fdt_setprop_cell(fdt, cp, "i-tlb-size", cpu->env.tlb_per_way);
+ qemu_fdt_setprop_cell(fdt, cp, "d-tlb-sets", cpu->env.nb_ways);
+ qemu_fdt_setprop_cell(fdt, cp, "d-tlb-size", cpu->env.tlb_per_way);
+ qemu_fdt_setprop_string(fdt, cp, "tlb-split", "");
+ }
+ qemu_fdt_setprop_cell(fdt, cp, "tlb-sets", cpu->env.nb_ways);
+ qemu_fdt_setprop_cell(fdt, cp, "tlb-size", cpu->env.nb_tlb);
+ qemu_fdt_setprop_string(fdt, cp, "state", "running");
+ if (cpu->env.insns_flags & PPC_ALTIVEC) {
+ qemu_fdt_setprop_string(fdt, cp, "altivec", "");
+ qemu_fdt_setprop_string(fdt, cp, "data-streams", "");
+ }
+ /*
+ * FIXME What flags do data-streams, external-control and
+ * performance-monitor depend on?
+ */
+ qemu_fdt_setprop_string(fdt, cp, "external-control", "");
+ if (cpu->env.insns_flags & PPC_FLOAT_FSQRT) {
+ qemu_fdt_setprop_string(fdt, cp, "general-purpose", "");
+ }
+ qemu_fdt_setprop_string(fdt, cp, "performance-monitor", "");
+ if (cpu->env.insns_flags & PPC_FLOAT_FRES) {
+ qemu_fdt_setprop_string(fdt, cp, "graphics", "");
+ }
+ qemu_fdt_setprop_cell(fdt, cp, "reservation-granule-size", 4);
+ qemu_fdt_setprop_cell(fdt, cp, "timebase-frequency",
+ cpu->env.tb_env->tb_freq);
+ qemu_fdt_setprop_cell(fdt, cp, "bus-frequency", BUS_FREQ_HZ);
+ qemu_fdt_setprop_cell(fdt, cp, "clock-frequency", BUS_FREQ_HZ * 7.5);
+ qemu_fdt_setprop_cell(fdt, cp, "cpu-version", cpu->env.spr[SPR_PVR]);
+ cells[0] = 0;
+ cells[1] = 0;
+ qemu_fdt_setprop(fdt, cp, "reg", cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fdt, cp, "device_type", "cpu");
+ qemu_fdt_setprop_string(fdt, cp, "name", strrchr(cp, '/') + 1);
+
+ /* memory */
+ qemu_fdt_add_subnode(fdt, "/memory@0");
+ cells[0] = 0;
+ cells[1] = cpu_to_be32(machine->ram_size);
+ qemu_fdt_setprop(fdt, "/memory@0", "reg", cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fdt, "/memory@0", "device_type", "memory");
+ qemu_fdt_setprop_string(fdt, "/memory@0", "name", "memory");
+
+ qemu_fdt_add_subnode(fdt, "/chosen");
+ qemu_fdt_setprop_string(fdt, "/chosen", "bootargs",
+ machine->kernel_cmdline ?: "");
+ qemu_fdt_setprop_string(fdt, "/chosen", "name", "chosen");
+
+ qemu_fdt_add_subnode(fdt, "/openprom");
+ qemu_fdt_setprop_string(fdt, "/openprom", "model", "Pegasos2,1.1");
+
+ return fdt;
+}
diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c
index 4dd90b7..a007be4 100644
--- a/hw/ppc/spapr.c
+++ b/hw/ppc/spapr.c
@@ -101,6 +101,7 @@
#define FDT_MAX_ADDR 0x80000000 /* FDT must stay below that */
#define FW_MAX_SIZE 0x400000
#define FW_FILE_NAME "slof.bin"
+#define FW_FILE_NAME_VOF "vof.bin"
#define FW_OVERHEAD 0x2800000
#define KERNEL_LOAD_ADDR FW_MAX_SIZE
@@ -880,6 +881,10 @@ static void spapr_dt_rtas(SpaprMachineState *spapr, void *fdt)
add_str(hypertas, "hcall-copy");
add_str(hypertas, "hcall-debug");
add_str(hypertas, "hcall-vphn");
+ if (spapr_get_cap(spapr, SPAPR_CAP_RPT_INVALIDATE) == SPAPR_CAP_ON) {
+ add_str(hypertas, "hcall-rpt-invalidate");
+ }
+
add_str(qemu_hypertas, "hcall-memop1");
if (!kvm_enabled() || kvmppc_spapr_use_multitce()) {
@@ -919,9 +924,13 @@ static void spapr_dt_rtas(SpaprMachineState *spapr, void *fdt)
*
* The extra 8 bytes is required because Linux's FWNMI error log check
* is off-by-one.
+ *
+ * RTAS_MIN_SIZE is required for the RTAS blob itself.
*/
- _FDT(fdt_setprop_cell(fdt, rtas, "rtas-size", RTAS_ERROR_LOG_MAX +
- ms->smp.max_cpus * sizeof(uint64_t)*2 + sizeof(uint64_t)));
+ _FDT(fdt_setprop_cell(fdt, rtas, "rtas-size", RTAS_MIN_SIZE +
+ RTAS_ERROR_LOG_MAX +
+ ms->smp.max_cpus * sizeof(uint64_t) * 2 +
+ sizeof(uint64_t)));
_FDT(fdt_setprop_cell(fdt, rtas, "rtas-error-log-max",
RTAS_ERROR_LOG_MAX));
_FDT(fdt_setprop_cell(fdt, rtas, "rtas-event-scan-rate",
@@ -1639,22 +1648,29 @@ static void spapr_machine_reset(MachineState *machine)
fdt_addr = MIN(spapr->rma_size, FDT_MAX_ADDR) - FDT_MAX_SIZE;
fdt = spapr_build_fdt(spapr, true, FDT_MAX_SIZE);
+ if (spapr->vof) {
+ spapr_vof_reset(spapr, fdt, &error_fatal);
+ /*
+ * Do not pack the FDT as the client may change properties.
+ * VOF client does not expect the FDT so we do not load it to the VM.
+ */
+ } else {
+ rc = fdt_pack(fdt);
+ /* Should only fail if we've built a corrupted tree */
+ assert(rc == 0);
- rc = fdt_pack(fdt);
-
- /* Should only fail if we've built a corrupted tree */
- assert(rc == 0);
-
- /* Load the fdt */
+ spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT,
+ 0, fdt_addr, 0);
+ cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt));
+ }
qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt));
- cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt));
+
g_free(spapr->fdt_blob);
spapr->fdt_size = fdt_totalsize(fdt);
spapr->fdt_initial_size = spapr->fdt_size;
spapr->fdt_blob = fdt;
/* Set up the entry state */
- spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT, 0, fdt_addr, 0);
first_ppc_cpu->env.gpr[5] = 0;
spapr->fwnmi_system_reset_addr = -1;
@@ -2018,6 +2034,7 @@ static const VMStateDescription vmstate_spapr = {
&vmstate_spapr_cap_ccf_assist,
&vmstate_spapr_cap_fwnmi,
&vmstate_spapr_fwnmi,
+ &vmstate_spapr_cap_rpt_invalidate,
NULL
}
};
@@ -2657,7 +2674,8 @@ static void spapr_machine_init(MachineState *machine)
SpaprMachineState *spapr = SPAPR_MACHINE(machine);
SpaprMachineClass *smc = SPAPR_MACHINE_GET_CLASS(machine);
MachineClass *mc = MACHINE_GET_CLASS(machine);
- const char *bios_name = machine->firmware ?: FW_FILE_NAME;
+ const char *bios_default = spapr->vof ? FW_FILE_NAME_VOF : FW_FILE_NAME;
+ const char *bios_name = machine->firmware ?: bios_default;
const char *kernel_filename = machine->kernel_filename;
const char *initrd_filename = machine->initrd_filename;
PCIHostState *phb;
@@ -3014,6 +3032,10 @@ static void spapr_machine_init(MachineState *machine)
}
qemu_cond_init(&spapr->fwnmi_machine_check_interlock_cond);
+ if (spapr->vof) {
+ spapr->vof->fw_size = fw_size; /* for claim() on itself */
+ spapr_register_hypercall(KVMPPC_H_VOF_CLIENT, spapr_h_vof_client);
+ }
}
#define DEFAULT_KVM_TYPE "auto"
@@ -3204,6 +3226,28 @@ static void spapr_set_resize_hpt(Object *obj, const char *value, Error **errp)
}
}
+static bool spapr_get_vof(Object *obj, Error **errp)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(obj);
+
+ return spapr->vof != NULL;
+}
+
+static void spapr_set_vof(Object *obj, bool value, Error **errp)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(obj);
+
+ if (spapr->vof) {
+ vof_cleanup(spapr->vof);
+ g_free(spapr->vof);
+ spapr->vof = NULL;
+ }
+ if (!value) {
+ return;
+ }
+ spapr->vof = g_malloc0(sizeof(*spapr->vof));
+}
+
static char *spapr_get_ic_mode(Object *obj, Error **errp)
{
SpaprMachineState *spapr = SPAPR_MACHINE(obj);
@@ -3329,6 +3373,11 @@ static void spapr_instance_init(Object *obj)
stringify(KERNEL_LOAD_ADDR)
" for -kernel is the default");
spapr->kernel_addr = KERNEL_LOAD_ADDR;
+
+ object_property_add_bool(obj, "x-vof", spapr_get_vof, spapr_set_vof);
+ object_property_set_description(obj, "x-vof",
+ "Enable Virtual Open Firmware (experimental)");
+
/* The machine class defines the default interrupt controller mode */
spapr->irq = smc->irq;
object_property_add_str(obj, "ic-mode", spapr_get_ic_mode,
@@ -4492,6 +4541,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data)
XICSFabricClass *xic = XICS_FABRIC_CLASS(oc);
InterruptStatsProviderClass *ispc = INTERRUPT_STATS_PROVIDER_CLASS(oc);
XiveFabricClass *xfc = XIVE_FABRIC_CLASS(oc);
+ VofMachineIfClass *vmc = VOF_MACHINE_CLASS(oc);
mc->desc = "pSeries Logical Partition (PAPR compliant)";
mc->ignore_boot_device_suffixes = true;
@@ -4573,6 +4623,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data)
smc->default_caps.caps[SPAPR_CAP_LARGE_DECREMENTER] = SPAPR_CAP_ON;
smc->default_caps.caps[SPAPR_CAP_CCF_ASSIST] = SPAPR_CAP_ON;
smc->default_caps.caps[SPAPR_CAP_FWNMI] = SPAPR_CAP_ON;
+ smc->default_caps.caps[SPAPR_CAP_RPT_INVALIDATE] = SPAPR_CAP_OFF;
spapr_caps_add_properties(smc);
smc->irq = &spapr_irq_dual;
smc->dr_phb_enabled = true;
@@ -4580,6 +4631,9 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data)
smc->smp_threads_vsmt = true;
smc->nr_xirqs = SPAPR_NR_XIRQS;
xfc->match_nvt = spapr_match_nvt;
+ vmc->client_architecture_support = spapr_vof_client_architecture_support;
+ vmc->quiesce = spapr_vof_quiesce;
+ vmc->setprop = spapr_vof_setprop;
}
static const TypeInfo spapr_machine_info = {
@@ -4599,6 +4653,7 @@ static const TypeInfo spapr_machine_info = {
{ TYPE_XICS_FABRIC },
{ TYPE_INTERRUPT_STATS_PROVIDER },
{ TYPE_XIVE_FABRIC },
+ { TYPE_VOF_MACHINE_IF },
{ }
},
};
diff --git a/hw/ppc/spapr_caps.c b/hw/ppc/spapr_caps.c
index d0c419b..ed7c077 100644
--- a/hw/ppc/spapr_caps.c
+++ b/hw/ppc/spapr_caps.c
@@ -582,6 +582,37 @@ static void cap_fwnmi_apply(SpaprMachineState *spapr, uint8_t val,
}
}
+static void cap_rpt_invalidate_apply(SpaprMachineState *spapr,
+ uint8_t val, Error **errp)
+{
+ ERRP_GUARD();
+
+ if (!val) {
+ /* capability disabled by default */
+ return;
+ }
+
+ if (tcg_enabled()) {
+ error_setg(errp, "No H_RPT_INVALIDATE support in TCG");
+ error_append_hint(errp,
+ "Try appending -machine cap-rpt-invalidate=off\n");
+ } else if (kvm_enabled()) {
+ if (!kvmppc_has_cap_mmu_radix()) {
+ error_setg(errp, "H_RPT_INVALIDATE only supported on Radix");
+ return;
+ }
+
+ if (!kvmppc_has_cap_rpt_invalidate()) {
+ error_setg(errp,
+ "KVM implementation does not support H_RPT_INVALIDATE");
+ error_append_hint(errp,
+ "Try appending -machine cap-rpt-invalidate=off\n");
+ } else {
+ kvmppc_enable_h_rpt_invalidate();
+ }
+ }
+}
+
SpaprCapabilityInfo capability_table[SPAPR_CAP_NUM] = {
[SPAPR_CAP_HTM] = {
.name = "htm",
@@ -690,6 +721,15 @@ SpaprCapabilityInfo capability_table[SPAPR_CAP_NUM] = {
.type = "bool",
.apply = cap_fwnmi_apply,
},
+ [SPAPR_CAP_RPT_INVALIDATE] = {
+ .name = "rpt-invalidate",
+ .description = "Allow H_RPT_INVALIDATE",
+ .index = SPAPR_CAP_RPT_INVALIDATE,
+ .get = spapr_cap_get_bool,
+ .set = spapr_cap_set_bool,
+ .type = "bool",
+ .apply = cap_rpt_invalidate_apply,
+ },
};
static SpaprCapabilities default_caps_with_cpu(SpaprMachineState *spapr,
@@ -830,6 +870,7 @@ SPAPR_CAP_MIG_STATE(nested_kvm_hv, SPAPR_CAP_NESTED_KVM_HV);
SPAPR_CAP_MIG_STATE(large_decr, SPAPR_CAP_LARGE_DECREMENTER);
SPAPR_CAP_MIG_STATE(ccf_assist, SPAPR_CAP_CCF_ASSIST);
SPAPR_CAP_MIG_STATE(fwnmi, SPAPR_CAP_FWNMI);
+SPAPR_CAP_MIG_STATE(rpt_invalidate, SPAPR_CAP_RPT_INVALIDATE);
void spapr_caps_init(SpaprMachineState *spapr)
{
diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c
index f25014a..0e9a5b2 100644
--- a/hw/ppc/spapr_hcall.c
+++ b/hw/ppc/spapr_hcall.c
@@ -1233,8 +1233,7 @@ target_ulong do_client_architecture_support(PowerPCCPU *cpu,
spapr_setup_hpt(spapr);
}
- fdt = spapr_build_fdt(spapr, false, fdt_bufsize);
-
+ fdt = spapr_build_fdt(spapr, spapr->vof != NULL, fdt_bufsize);
g_free(spapr->fdt_blob);
spapr->fdt_size = fdt_totalsize(fdt);
spapr->fdt_initial_size = spapr->fdt_size;
@@ -1277,6 +1276,25 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu,
return ret;
}
+target_ulong spapr_vof_client_architecture_support(MachineState *ms,
+ CPUState *cs,
+ target_ulong ovec_addr)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+
+ target_ulong ret = do_client_architecture_support(POWERPC_CPU(cs), spapr,
+ ovec_addr, FDT_MAX_SIZE);
+
+ /*
+ * This adds stdout and generates phandles for boottime and CAS FDTs.
+ * It is alright to update the FDT here as do_client_architecture_support()
+ * does not pack it.
+ */
+ spapr_vof_client_dt_finalize(spapr, spapr->fdt_blob);
+
+ return ret;
+}
+
static target_ulong h_get_cpu_characteristics(PowerPCCPU *cpu,
SpaprMachineState *spapr,
target_ulong opcode,
@@ -1299,6 +1317,8 @@ static target_ulong h_get_cpu_characteristics(PowerPCCPU *cpu,
behaviour |= H_CPU_BEHAV_L1D_FLUSH_PR;
break;
case SPAPR_CAP_FIXED:
+ behaviour |= H_CPU_BEHAV_NO_L1D_FLUSH_ENTRY;
+ behaviour |= H_CPU_BEHAV_NO_L1D_FLUSH_UACCESS;
break;
default: /* broken */
assert(safe_cache == SPAPR_CAP_BROKEN);
diff --git a/hw/ppc/spapr_vof.c b/hw/ppc/spapr_vof.c
new file mode 100644
index 0000000..40ce8fe
--- /dev/null
+++ b/hw/ppc/spapr_vof.c
@@ -0,0 +1,167 @@
+/*
+ * SPAPR machine hooks to Virtual Open Firmware,
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "hw/ppc/spapr_cpu_core.h"
+#include "hw/ppc/fdt.h"
+#include "hw/ppc/vof.h"
+#include "sysemu/sysemu.h"
+#include "qom/qom-qobject.h"
+#include "trace.h"
+
+target_ulong spapr_h_vof_client(PowerPCCPU *cpu, SpaprMachineState *spapr,
+ target_ulong opcode, target_ulong *_args)
+{
+ int ret = vof_client_call(MACHINE(spapr), spapr->vof, spapr->fdt_blob,
+ ppc64_phys_to_real(_args[0]));
+
+ if (ret) {
+ return H_PARAMETER;
+ }
+ return H_SUCCESS;
+}
+
+void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt)
+{
+ char *stdout_path = spapr_vio_stdout_path(spapr->vio_bus);
+
+ vof_build_dt(fdt, spapr->vof);
+
+ if (spapr->vof->bootargs) {
+ int chosen;
+
+ _FDT(chosen = fdt_path_offset(fdt, "/chosen"));
+ /*
+ * If the client did not change "bootargs", spapr_dt_chosen() must have
+ * stored machine->kernel_cmdline in it before getting here.
+ */
+ _FDT(fdt_setprop_string(fdt, chosen, "bootargs", spapr->vof->bootargs));
+ }
+
+ /*
+ * SLOF-less setup requires an open instance of stdout for early
+ * kernel printk. By now all phandles are settled so we can open
+ * the default serial console.
+ */
+ if (stdout_path) {
+ _FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdout",
+ stdout_path));
+ }
+}
+
+void spapr_vof_reset(SpaprMachineState *spapr, void *fdt, Error **errp)
+{
+ target_ulong stack_ptr;
+ Vof *vof = spapr->vof;
+ PowerPCCPU *first_ppc_cpu = POWERPC_CPU(first_cpu);
+
+ vof_init(vof, spapr->rma_size, errp);
+
+ stack_ptr = vof_claim(vof, 0, VOF_STACK_SIZE, VOF_STACK_SIZE);
+ if (stack_ptr == -1) {
+ error_setg(errp, "Memory allocation for stack failed");
+ return;
+ }
+ /* Stack grows downwards plus reserve space for the minimum stack frame */
+ stack_ptr += VOF_STACK_SIZE - 0x20;
+
+ if (spapr->kernel_size &&
+ vof_claim(vof, spapr->kernel_addr, spapr->kernel_size, 0) == -1) {
+ error_setg(errp, "Memory for kernel is in use");
+ return;
+ }
+
+ if (spapr->initrd_size &&
+ vof_claim(vof, spapr->initrd_base, spapr->initrd_size, 0) == -1) {
+ error_setg(errp, "Memory for initramdisk is in use");
+ return;
+ }
+
+ spapr_vof_client_dt_finalize(spapr, fdt);
+
+ spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT,
+ stack_ptr, spapr->initrd_base,
+ spapr->initrd_size);
+ /* VOF is 32bit BE so enforce MSR here */
+ first_ppc_cpu->env.msr &= ~((1ULL << MSR_SF) | (1ULL << MSR_LE));
+
+ /*
+ * At this point the expected allocation map is:
+ *
+ * 0..c38 - the initial firmware
+ * 8000..10000 - stack
+ * 400000.. - kernel
+ * 3ea0000.. - initramdisk
+ *
+ * We skip writing FDT as nothing expects it; OF client interface is
+ * going to be used for reading the device tree.
+ */
+}
+
+void spapr_vof_quiesce(MachineState *ms)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+
+ spapr->fdt_size = fdt_totalsize(spapr->fdt_blob);
+ spapr->fdt_initial_size = spapr->fdt_size;
+}
+
+bool spapr_vof_setprop(MachineState *ms, const char *path, const char *propname,
+ void *val, int vallen)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+
+ /*
+ * We only allow changing properties which we know how to update in QEMU
+ * OR
+ * the ones which we know that they need to survive during "quiesce".
+ */
+
+ if (strcmp(path, "/rtas") == 0) {
+ if (strcmp(propname, "linux,rtas-base") == 0 ||
+ strcmp(propname, "linux,rtas-entry") == 0) {
+ /* These need to survive quiesce so let them store in the FDT */
+ return true;
+ }
+ }
+
+ if (strcmp(path, "/chosen") == 0) {
+ if (strcmp(propname, "bootargs") == 0) {
+ Vof *vof = spapr->vof;
+
+ g_free(vof->bootargs);
+ vof->bootargs = g_strndup(val, vallen);
+ return true;
+ }
+ if (strcmp(propname, "linux,initrd-start") == 0) {
+ if (vallen == sizeof(uint32_t)) {
+ spapr->initrd_base = ldl_be_p(val);
+ return true;
+ }
+ if (vallen == sizeof(uint64_t)) {
+ spapr->initrd_base = ldq_be_p(val);
+ return true;
+ }
+ return false;
+ }
+ if (strcmp(propname, "linux,initrd-end") == 0) {
+ if (vallen == sizeof(uint32_t)) {
+ spapr->initrd_size = ldl_be_p(val) - spapr->initrd_base;
+ return true;
+ }
+ if (vallen == sizeof(uint64_t)) {
+ spapr->initrd_size = ldq_be_p(val) - spapr->initrd_base;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
index 0ba3e40..6e90a01 100644
--- a/hw/ppc/trace-events
+++ b/hw/ppc/trace-events
@@ -71,6 +71,30 @@ spapr_rtas_ibm_configure_connector_invalid(uint32_t index) "DRC index: 0x%"PRIx3
spapr_vio_h_reg_crq(uint64_t reg, uint64_t queue_addr, uint64_t queue_len) "CRQ for dev 0x%" PRIx64 " registered at 0x%" PRIx64 "/0x%" PRIx64
spapr_vio_free_crq(uint32_t reg) "CRQ for dev 0x%" PRIx32 " freed"
+# vof.c
+vof_error_str_truncated(const char *s, int len) "%s truncated to %d"
+vof_error_param(const char *method, int nargscheck, int nretcheck, int nargs, int nret) "%s takes/returns %d/%d, not %d/%d"
+vof_error_unknown_service(const char *service, int nargs, int nret) "\"%s\" args=%d rets=%d"
+vof_error_unknown_method(const char *method) "\"%s\""
+vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
+vof_error_unknown_path(const char *path) "\"%s\""
+vof_error_write(uint32_t ih) "ih=0x%x"
+vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
+vof_claim(uint32_t virt, uint32_t size, uint32_t align, uint32_t ret) "virt=0x%x size=0x%x align=0x%x => 0x%x"
+vof_release(uint32_t virt, uint32_t size, uint32_t ret) "virt=0x%x size=0x%x => 0x%x"
+vof_method(uint32_t ihandle, const char *method, uint32_t param, uint32_t ret, uint32_t ret2) "ih=0x%x \"%s\"(0x%x) => 0x%x 0x%x"
+vof_getprop(uint32_t ph, const char *prop, uint32_t ret, const char *val) "ph=0x%x \"%s\" => len=%d [%s]"
+vof_getproplen(uint32_t ph, const char *prop, uint32_t ret) "ph=0x%x \"%s\" => len=%d"
+vof_setprop(uint32_t ph, const char *prop, const char *val, uint32_t vallen, uint32_t ret) "ph=0x%x \"%s\" [%s] len=%d => ret=%d"
+vof_open(const char *path, uint32_t ph, uint32_t ih) "%s ph=0x%x => ih=0x%x"
+vof_interpret(const char *cmd, uint32_t param1, uint32_t param2, uint32_t ret, uint32_t ret2) "[%s] 0x%x 0x%x => 0x%x 0x%x"
+vof_package_to_path(uint32_t ph, const char *tmp, uint32_t ret) "ph=0x%x => %s len=%d"
+vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, uint32_t ret) "ih=0x%x ph=0x%x => %s len=%d"
+vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
+vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
+vof_avail(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
+vof_claimed(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
+
# ppc.c
ppc_tb_adjust(uint64_t offs1, uint64_t offs2, int64_t diff, int64_t seconds) "adjusted from 0x%"PRIx64" to 0x%"PRIx64", diff %"PRId64" (%"PRId64"s)"
diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
new file mode 100644
index 0000000..81f6596
--- /dev/null
+++ b/hw/ppc/vof.c
@@ -0,0 +1,1053 @@
+/*
+ * QEMU PowerPC Virtual Open Firmware.
+ *
+ * This implements client interface from OpenFirmware IEEE1275 on the QEMU
+ * side to leave only a very basic firmware in the VM.
+ *
+ * Copyright (c) 2021 IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "qemu/range.h"
+#include "qemu/units.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "exec/ram_addr.h"
+#include "exec/address-spaces.h"
+#include "hw/ppc/vof.h"
+#include "hw/ppc/fdt.h"
+#include "sysemu/runstate.h"
+#include "qom/qom-qobject.h"
+#include "trace.h"
+
+#include <libfdt.h>
+
+/*
+ * OF 1275 "nextprop" description suggests is it 32 bytes max but
+ * LoPAPR defines "ibm,query-interrupt-source-number" which is 33 chars long.
+ */
+#define OF_PROPNAME_LEN_MAX 64
+
+#define VOF_MAX_PATH 256
+#define VOF_MAX_SETPROPLEN 2048
+#define VOF_MAX_METHODLEN 256
+#define VOF_MAX_FORTHCODE 256
+#define VOF_VTY_BUF_SIZE 256
+
+typedef struct {
+ uint64_t start;
+ uint64_t size;
+} OfClaimed;
+
+typedef struct {
+ char *path; /* the path used to open the instance */
+ uint32_t phandle;
+} OfInstance;
+
+static int readstr(hwaddr pa, char *buf, int size)
+{
+ if (VOF_MEM_READ(pa, buf, size) != MEMTX_OK) {
+ return -1;
+ }
+ if (strnlen(buf, size) == size) {
+ buf[size - 1] = '\0';
+ trace_vof_error_str_truncated(buf, size);
+ return -1;
+ }
+ return 0;
+}
+
+static bool cmpservice(const char *s, unsigned nargs, unsigned nret,
+ const char *s1, unsigned nargscheck, unsigned nretcheck)
+{
+ if (strcmp(s, s1)) {
+ return false;
+ }
+ if ((nargscheck && (nargs != nargscheck)) ||
+ (nretcheck && (nret != nretcheck))) {
+ trace_vof_error_param(s, nargscheck, nretcheck, nargs, nret);
+ return false;
+ }
+
+ return true;
+}
+
+static void prop_format(char *tval, int tlen, const void *prop, int len)
+{
+ int i;
+ const unsigned char *c;
+ char *t;
+ const char bin[] = "...";
+
+ for (i = 0, c = prop; i < len; ++i, ++c) {
+ if (*c == '\0' && i == len - 1) {
+ strncpy(tval, prop, tlen - 1);
+ return;
+ }
+ if (*c < 0x20 || *c >= 0x80) {
+ break;
+ }
+ }
+
+ for (i = 0, c = prop, t = tval; i < len; ++i, ++c) {
+ if (t >= tval + tlen - sizeof(bin) - 1 - 2 - 1) {
+ strcpy(t, bin);
+ return;
+ }
+ if (i && i % 4 == 0 && i != len - 1) {
+ strcat(t, " ");
+ ++t;
+ }
+ t += sprintf(t, "%02X", *c & 0xFF);
+ }
+}
+
+static int get_path(const void *fdt, int offset, char *buf, int len)
+{
+ int ret;
+
+ ret = fdt_get_path(fdt, offset, buf, len - 1);
+ if (ret < 0) {
+ return ret;
+ }
+
+ buf[len - 1] = '\0';
+
+ return strlen(buf) + 1;
+}
+
+static int phandle_to_path(const void *fdt, uint32_t ph, char *buf, int len)
+{
+ int ret;
+
+ ret = fdt_node_offset_by_phandle(fdt, ph);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return get_path(fdt, ret, buf, len);
+}
+
+static int path_offset(const void *fdt, const char *path)
+{
+ g_autofree char *p = NULL;
+ char *at;
+
+ /*
+ * https://www.devicetree.org/open-firmware/bindings/ppc/release/ppc-2_1.html#HDR16
+ *
+ * "Conversion from numeric representation to text representation shall use
+ * the lower case forms of the hexadecimal digits in the range a..f,
+ * suppressing leading zeros".
+ */
+ p = g_strdup(path);
+ for (at = strchr(p, '@'); at && *at; ) {
+ if (*at == '/') {
+ at = strchr(at, '@');
+ } else {
+ *at = tolower(*at);
+ ++at;
+ }
+ }
+
+ return fdt_path_offset(fdt, p);
+}
+
+static uint32_t vof_finddevice(const void *fdt, uint32_t nodeaddr)
+{
+ char fullnode[VOF_MAX_PATH];
+ uint32_t ret = -1;
+ int offset;
+
+ if (readstr(nodeaddr, fullnode, sizeof(fullnode))) {
+ return (uint32_t) ret;
+ }
+
+ offset = path_offset(fdt, fullnode);
+ if (offset >= 0) {
+ ret = fdt_get_phandle(fdt, offset);
+ }
+ trace_vof_finddevice(fullnode, ret);
+ return (uint32_t) ret;
+}
+
+static const void *getprop(const void *fdt, int nodeoff, const char *propname,
+ int *proplen, bool *write0)
+{
+ const char *unit, *prop;
+ const void *ret = fdt_getprop(fdt, nodeoff, propname, proplen);
+
+ if (ret) {
+ if (write0) {
+ *write0 = false;
+ }
+ return ret;
+ }
+
+ if (strcmp(propname, "name")) {
+ return NULL;
+ }
+ /*
+ * We return a value for "name" from path if queried but property does not
+ * exist. @proplen does not include the unit part in this case.
+ */
+ prop = fdt_get_name(fdt, nodeoff, proplen);
+ if (!prop) {
+ *proplen = 0;
+ return NULL;
+ }
+
+ unit = memchr(prop, '@', *proplen);
+ if (unit) {
+ *proplen = unit - prop;
+ }
+ *proplen += 1;
+
+ /*
+ * Since it might be cut at "@" and there will be no trailing zero
+ * in the prop buffer, tell the caller to write zero at the end.
+ */
+ if (write0) {
+ *write0 = true;
+ }
+ return prop;
+}
+
+static uint32_t vof_getprop(const void *fdt, uint32_t nodeph, uint32_t pname,
+ uint32_t valaddr, uint32_t vallen)
+{
+ char propname[OF_PROPNAME_LEN_MAX + 1];
+ uint32_t ret = 0;
+ int proplen = 0;
+ const void *prop;
+ char trval[64] = "";
+ int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph);
+ bool write0;
+
+ if (nodeoff < 0) {
+ return -1;
+ }
+ if (readstr(pname, propname, sizeof(propname))) {
+ return -1;
+ }
+ prop = getprop(fdt, nodeoff, propname, &proplen, &write0);
+ if (prop) {
+ const char zero = 0;
+ int cb = MIN(proplen, vallen);
+
+ if (VOF_MEM_WRITE(valaddr, prop, cb) != MEMTX_OK ||
+ /* if that was "name" with a unit address, overwrite '@' with '0' */
+ (write0 &&
+ cb == proplen &&
+ VOF_MEM_WRITE(valaddr + cb - 1, &zero, 1) != MEMTX_OK)) {
+ ret = -1;
+ } else {
+ /*
+ * OF1275 says:
+ * "Size is either the actual size of the property, or -1 if name
+ * does not exist", hence returning proplen instead of cb.
+ */
+ ret = proplen;
+ /* Do not format a value if tracepoint is silent, for performance */
+ if (trace_event_get_state(TRACE_VOF_GETPROP) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+ prop_format(trval, sizeof(trval), prop, ret);
+ }
+ }
+ } else {
+ ret = -1;
+ }
+ trace_vof_getprop(nodeph, propname, ret, trval);
+
+ return ret;
+}
+
+static uint32_t vof_getproplen(const void *fdt, uint32_t nodeph, uint32_t pname)
+{
+ char propname[OF_PROPNAME_LEN_MAX + 1];
+ uint32_t ret = 0;
+ int proplen = 0;
+ const void *prop;
+ int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph);
+
+ if (nodeoff < 0) {
+ return -1;
+ }
+ if (readstr(pname, propname, sizeof(propname))) {
+ return -1;
+ }
+ prop = getprop(fdt, nodeoff, propname, &proplen, NULL);
+ if (prop) {
+ ret = proplen;
+ } else {
+ ret = -1;
+ }
+ trace_vof_getproplen(nodeph, propname, ret);
+
+ return ret;
+}
+
+static uint32_t vof_setprop(MachineState *ms, void *fdt, Vof *vof,
+ uint32_t nodeph, uint32_t pname,
+ uint32_t valaddr, uint32_t vallen)
+{
+ char propname[OF_PROPNAME_LEN_MAX + 1];
+ uint32_t ret = -1;
+ int offset;
+ char trval[64] = "";
+ char nodepath[VOF_MAX_PATH] = "";
+ Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF);
+ VofMachineIfClass *vmc;
+ g_autofree char *val = NULL;
+
+ if (vallen > VOF_MAX_SETPROPLEN) {
+ goto trace_exit;
+ }
+ if (readstr(pname, propname, sizeof(propname))) {
+ goto trace_exit;
+ }
+ offset = fdt_node_offset_by_phandle(fdt, nodeph);
+ if (offset < 0) {
+ goto trace_exit;
+ }
+ ret = get_path(fdt, offset, nodepath, sizeof(nodepath));
+ if (ret <= 0) {
+ goto trace_exit;
+ }
+
+ val = g_malloc0(vallen);
+ if (VOF_MEM_READ(valaddr, val, vallen) != MEMTX_OK) {
+ goto trace_exit;
+ }
+
+ if (!vmo) {
+ goto trace_exit;
+ }
+
+ vmc = VOF_MACHINE_GET_CLASS(vmo);
+ if (!vmc->setprop || !vmc->setprop(ms, nodepath, propname, val, vallen)) {
+ goto trace_exit;
+ }
+
+ ret = fdt_setprop(fdt, offset, propname, val, vallen);
+ if (ret) {
+ goto trace_exit;
+ }
+
+ if (trace_event_get_state(TRACE_VOF_SETPROP) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+ prop_format(trval, sizeof(trval), val, vallen);
+ }
+ ret = vallen;
+
+trace_exit:
+ trace_vof_setprop(nodeph, propname, trval, vallen, ret);
+
+ return ret;
+}
+
+static uint32_t vof_nextprop(const void *fdt, uint32_t phandle,
+ uint32_t prevaddr, uint32_t nameaddr)
+{
+ int offset, nodeoff = fdt_node_offset_by_phandle(fdt, phandle);
+ char prev[OF_PROPNAME_LEN_MAX + 1];
+ const char *tmp;
+
+ if (readstr(prevaddr, prev, sizeof(prev))) {
+ return -1;
+ }
+
+ fdt_for_each_property_offset(offset, fdt, nodeoff) {
+ if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) {
+ return 0;
+ }
+ if (prev[0] == '\0' || strcmp(prev, tmp) == 0) {
+ if (prev[0] != '\0') {
+ offset = fdt_next_property_offset(fdt, offset);
+ if (offset < 0) {
+ return 0;
+ }
+ }
+ if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) {
+ return 0;
+ }
+
+ if (VOF_MEM_WRITE(nameaddr, tmp, strlen(tmp) + 1) != MEMTX_OK) {
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static uint32_t vof_peer(const void *fdt, uint32_t phandle)
+{
+ int ret;
+
+ if (phandle == 0) {
+ ret = fdt_path_offset(fdt, "/");
+ } else {
+ ret = fdt_next_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle));
+ }
+
+ if (ret < 0) {
+ ret = 0;
+ } else {
+ ret = fdt_get_phandle(fdt, ret);
+ }
+
+ return ret;
+}
+
+static uint32_t vof_child(const void *fdt, uint32_t phandle)
+{
+ int ret = fdt_first_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle));
+
+ if (ret < 0) {
+ ret = 0;
+ } else {
+ ret = fdt_get_phandle(fdt, ret);
+ }
+
+ return ret;
+}
+
+static uint32_t vof_parent(const void *fdt, uint32_t phandle)
+{
+ int ret = fdt_parent_offset(fdt, fdt_node_offset_by_phandle(fdt, phandle));
+
+ if (ret < 0) {
+ ret = 0;
+ } else {
+ ret = fdt_get_phandle(fdt, ret);
+ }
+
+ return ret;
+}
+
+static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
+{
+ uint32_t ret = -1;
+ OfInstance *inst = NULL;
+
+ if (vof->of_instance_last == 0xFFFFFFFF) {
+ /* We do not recycle ihandles yet */
+ goto trace_exit;
+ }
+
+ inst = g_new0(OfInstance, 1);
+ inst->phandle = fdt_get_phandle(fdt, offset);
+ g_assert(inst->phandle);
+ ++vof->of_instance_last;
+
+ inst->path = g_strdup(path);
+ g_hash_table_insert(vof->of_instances,
+ GINT_TO_POINTER(vof->of_instance_last),
+ inst);
+ ret = vof->of_instance_last;
+
+trace_exit:
+ trace_vof_open(path, inst ? inst->phandle : 0, ret);
+
+ return ret;
+}
+
+uint32_t vof_client_open_store(void *fdt, Vof *vof, const char *nodename,
+ const char *prop, const char *path)
+{
+ int node = fdt_path_offset(fdt, nodename);
+ int inst, offset;
+
+ offset = fdt_path_offset(fdt, path);
+ if (offset < 0) {
+ trace_vof_error_unknown_path(path);
+ return offset;
+ }
+
+ inst = vof_do_open(fdt, vof, offset, path);
+
+ return fdt_setprop_cell(fdt, node, prop, inst);
+}
+
+static uint32_t vof_open(void *fdt, Vof *vof, uint32_t pathaddr)
+{
+ char path[VOF_MAX_PATH];
+ int offset;
+
+ if (readstr(pathaddr, path, sizeof(path))) {
+ return -1;
+ }
+
+ offset = path_offset(fdt, path);
+ if (offset < 0) {
+ trace_vof_error_unknown_path(path);
+ return offset;
+ }
+
+ return vof_do_open(fdt, vof, offset, path);
+}
+
+static void vof_close(Vof *vof, uint32_t ihandle)
+{
+ if (!g_hash_table_remove(vof->of_instances, GINT_TO_POINTER(ihandle))) {
+ trace_vof_error_unknown_ihandle_close(ihandle);
+ }
+}
+
+static uint32_t vof_instance_to_package(Vof *vof, uint32_t ihandle)
+{
+ gpointer instp = g_hash_table_lookup(vof->of_instances,
+ GINT_TO_POINTER(ihandle));
+ uint32_t ret = -1;
+
+ if (instp) {
+ ret = ((OfInstance *)instp)->phandle;
+ }
+ trace_vof_instance_to_package(ihandle, ret);
+
+ return ret;
+}
+
+static uint32_t vof_package_to_path(const void *fdt, uint32_t phandle,
+ uint32_t buf, uint32_t len)
+{
+ uint32_t ret = -1;
+ char tmp[VOF_MAX_PATH] = "";
+
+ ret = phandle_to_path(fdt, phandle, tmp, sizeof(tmp));
+ if (ret > 0) {
+ if (VOF_MEM_WRITE(buf, tmp, ret) != MEMTX_OK) {
+ ret = -1;
+ }
+ }
+
+ trace_vof_package_to_path(phandle, tmp, ret);
+
+ return ret;
+}
+
+static uint32_t vof_instance_to_path(void *fdt, Vof *vof, uint32_t ihandle,
+ uint32_t buf, uint32_t len)
+{
+ uint32_t ret = -1;
+ uint32_t phandle = vof_instance_to_package(vof, ihandle);
+ char tmp[VOF_MAX_PATH] = "";
+
+ if (phandle != -1) {
+ ret = phandle_to_path(fdt, phandle, tmp, sizeof(tmp));
+ if (ret > 0) {
+ if (VOF_MEM_WRITE(buf, tmp, ret) != MEMTX_OK) {
+ ret = -1;
+ }
+ }
+ }
+ trace_vof_instance_to_path(ihandle, phandle, tmp, ret);
+
+ return ret;
+}
+
+static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
+ uint32_t len)
+{
+ char tmp[VOF_VTY_BUF_SIZE];
+ unsigned cb;
+ OfInstance *inst = (OfInstance *)
+ g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle));
+
+ if (!inst) {
+ trace_vof_error_write(ihandle);
+ return -1;
+ }
+
+ for ( ; len > 0; len -= cb) {
+ cb = MIN(len, sizeof(tmp) - 1);
+ if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
+ return -1;
+ }
+
+ /* FIXME: there is no backend(s) yet so just call a trace */
+ if (trace_event_get_state(TRACE_VOF_WRITE) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+ tmp[cb] = '\0';
+ trace_vof_write(ihandle, cb, tmp);
+ }
+ }
+
+ return len;
+}
+
+static void vof_claimed_dump(GArray *claimed)
+{
+ int i;
+ OfClaimed c;
+
+ if (trace_event_get_state(TRACE_VOF_CLAIMED) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+
+ for (i = 0; i < claimed->len; ++i) {
+ c = g_array_index(claimed, OfClaimed, i);
+ trace_vof_claimed(c.start, c.start + c.size, c.size);
+ }
+ }
+}
+
+static bool vof_claim_avail(GArray *claimed, uint64_t virt, uint64_t size)
+{
+ int i;
+ OfClaimed c;
+
+ for (i = 0; i < claimed->len; ++i) {
+ c = g_array_index(claimed, OfClaimed, i);
+ if (ranges_overlap(c.start, c.size, virt, size)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void vof_claim_add(GArray *claimed, uint64_t virt, uint64_t size)
+{
+ OfClaimed newclaim;
+
+ newclaim.start = virt;
+ newclaim.size = size;
+ g_array_append_val(claimed, newclaim);
+}
+
+static gint of_claimed_compare_func(gconstpointer a, gconstpointer b)
+{
+ return ((OfClaimed *)a)->start - ((OfClaimed *)b)->start;
+}
+
+static void vof_dt_memory_available(void *fdt, GArray *claimed, uint64_t base)
+{
+ int i, n, offset, proplen = 0, sc, ac;
+ target_ulong mem0_end;
+ const uint8_t *mem0_reg;
+ g_autofree uint8_t *avail = NULL;
+ uint8_t *availcur;
+
+ if (!fdt || !claimed) {
+ return;
+ }
+
+ offset = fdt_path_offset(fdt, "/");
+ _FDT(offset);
+ ac = fdt_address_cells(fdt, offset);
+ g_assert(ac == 1 || ac == 2);
+ sc = fdt_size_cells(fdt, offset);
+ g_assert(sc == 1 || sc == 2);
+
+ offset = fdt_path_offset(fdt, "/memory@0");
+ _FDT(offset);
+
+ mem0_reg = fdt_getprop(fdt, offset, "reg", &proplen);
+ g_assert(mem0_reg && proplen == sizeof(uint32_t) * (ac + sc));
+ if (sc == 2) {
+ mem0_end = be64_to_cpu(*(uint64_t *)(mem0_reg + sizeof(uint32_t) * ac));
+ } else {
+ mem0_end = be32_to_cpu(*(uint32_t *)(mem0_reg + sizeof(uint32_t) * ac));
+ }
+
+ g_array_sort(claimed, of_claimed_compare_func);
+ vof_claimed_dump(claimed);
+
+ /*
+ * VOF resides in the first page so we do not need to check if there is
+ * available memory before the first claimed block
+ */
+ g_assert(claimed->len && (g_array_index(claimed, OfClaimed, 0).start == 0));
+
+ avail = g_malloc0(sizeof(uint32_t) * (ac + sc) * claimed->len);
+ for (i = 0, n = 0, availcur = avail; i < claimed->len; ++i) {
+ OfClaimed c = g_array_index(claimed, OfClaimed, i);
+ uint64_t start, size;
+
+ start = c.start + c.size;
+ if (i < claimed->len - 1) {
+ OfClaimed cn = g_array_index(claimed, OfClaimed, i + 1);
+
+ size = cn.start - start;
+ } else {
+ size = mem0_end - start;
+ }
+
+ if (ac == 2) {
+ *(uint64_t *) availcur = cpu_to_be64(start);
+ } else {
+ *(uint32_t *) availcur = cpu_to_be32(start);
+ }
+ availcur += sizeof(uint32_t) * ac;
+ if (sc == 2) {
+ *(uint64_t *) availcur = cpu_to_be64(size);
+ } else {
+ *(uint32_t *) availcur = cpu_to_be32(size);
+ }
+ availcur += sizeof(uint32_t) * sc;
+
+ if (size) {
+ trace_vof_avail(c.start + c.size, c.start + c.size + size, size);
+ ++n;
+ }
+ }
+ _FDT((fdt_setprop(fdt, offset, "available", avail, availcur - avail)));
+}
+
+/*
+ * OF1275:
+ * "Allocates size bytes of memory. If align is zero, the allocated range
+ * begins at the virtual address virt. Otherwise, an aligned address is
+ * automatically chosen and the input argument virt is ignored".
+ *
+ * In other words, exactly one of @virt and @align is non-zero.
+ */
+uint64_t vof_claim(Vof *vof, uint64_t virt, uint64_t size,
+ uint64_t align)
+{
+ uint64_t ret;
+
+ if (size == 0) {
+ ret = -1;
+ } else if (align == 0) {
+ if (!vof_claim_avail(vof->claimed, virt, size)) {
+ ret = -1;
+ } else {
+ ret = virt;
+ }
+ } else {
+ vof->claimed_base = QEMU_ALIGN_UP(vof->claimed_base, align);
+ while (1) {
+ if (vof->claimed_base >= vof->top_addr) {
+ error_report("Out of RMA memory for the OF client");
+ return -1;
+ }
+ if (vof_claim_avail(vof->claimed, vof->claimed_base, size)) {
+ break;
+ }
+ vof->claimed_base += size;
+ }
+ ret = vof->claimed_base;
+ }
+
+ if (ret != -1) {
+ vof->claimed_base = MAX(vof->claimed_base, ret + size);
+ vof_claim_add(vof->claimed, ret, size);
+ }
+ trace_vof_claim(virt, size, align, ret);
+
+ return ret;
+}
+
+static uint32_t vof_release(Vof *vof, uint64_t virt, uint64_t size)
+{
+ uint32_t ret = -1;
+ int i;
+ GArray *claimed = vof->claimed;
+ OfClaimed c;
+
+ for (i = 0; i < claimed->len; ++i) {
+ c = g_array_index(claimed, OfClaimed, i);
+ if (c.start == virt && c.size == size) {
+ g_array_remove_index(claimed, i);
+ ret = 0;
+ break;
+ }
+ }
+
+ trace_vof_release(virt, size, ret);
+
+ return ret;
+}
+
+static void vof_instantiate_rtas(Error **errp)
+{
+ error_setg(errp, "The firmware should have instantiated RTAS");
+}
+
+static uint32_t vof_call_method(MachineState *ms, Vof *vof, uint32_t methodaddr,
+ uint32_t ihandle, uint32_t param1,
+ uint32_t param2, uint32_t param3,
+ uint32_t param4, uint32_t *ret2)
+{
+ uint32_t ret = -1;
+ char method[VOF_MAX_METHODLEN] = "";
+ OfInstance *inst;
+
+ if (!ihandle) {
+ goto trace_exit;
+ }
+
+ inst = (OfInstance *)g_hash_table_lookup(vof->of_instances,
+ GINT_TO_POINTER(ihandle));
+ if (!inst) {
+ goto trace_exit;
+ }
+
+ if (readstr(methodaddr, method, sizeof(method))) {
+ goto trace_exit;
+ }
+
+ if (strcmp(inst->path, "/") == 0) {
+ if (strcmp(method, "ibm,client-architecture-support") == 0) {
+ Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF);
+
+ if (vmo) {
+ VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo);
+
+ g_assert(vmc->client_architecture_support);
+ ret = vmc->client_architecture_support(ms, first_cpu, param1);
+ }
+
+ *ret2 = 0;
+ }
+ } else if (strcmp(inst->path, "/rtas") == 0) {
+ if (strcmp(method, "instantiate-rtas") == 0) {
+ vof_instantiate_rtas(&error_fatal);
+ ret = 0;
+ *ret2 = param1; /* rtas-base */
+ }
+ } else {
+ trace_vof_error_unknown_method(method);
+ }
+
+trace_exit:
+ trace_vof_method(ihandle, method, param1, ret, *ret2);
+
+ return ret;
+}
+
+static uint32_t vof_call_interpret(uint32_t cmdaddr, uint32_t param1,
+ uint32_t param2, uint32_t *ret2)
+{
+ uint32_t ret = -1;
+ char cmd[VOF_MAX_FORTHCODE] = "";
+
+ /* No interpret implemented so just call a trace */
+ readstr(cmdaddr, cmd, sizeof(cmd));
+ trace_vof_interpret(cmd, param1, param2, ret, *ret2);
+
+ return ret;
+}
+
+static void vof_quiesce(MachineState *ms, void *fdt, Vof *vof)
+{
+ Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF);
+ /* After "quiesce", no change is expected to the FDT, pack FDT to ensure */
+ int rc = fdt_pack(fdt);
+
+ assert(rc == 0);
+
+ if (vmo) {
+ VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo);
+
+ if (vmc->quiesce) {
+ vmc->quiesce(ms);
+ }
+ }
+
+ vof_claimed_dump(vof->claimed);
+}
+
+static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof,
+ const char *service,
+ uint32_t *args, unsigned nargs,
+ uint32_t *rets, unsigned nrets)
+{
+ uint32_t ret = 0;
+
+ /* @nrets includes the value which this function returns */
+#define cmpserv(s, a, r) \
+ cmpservice(service, nargs, nrets, (s), (a), (r))
+
+ if (cmpserv("finddevice", 1, 1)) {
+ ret = vof_finddevice(fdt, args[0]);
+ } else if (cmpserv("getprop", 4, 1)) {
+ ret = vof_getprop(fdt, args[0], args[1], args[2], args[3]);
+ } else if (cmpserv("getproplen", 2, 1)) {
+ ret = vof_getproplen(fdt, args[0], args[1]);
+ } else if (cmpserv("setprop", 4, 1)) {
+ ret = vof_setprop(ms, fdt, vof, args[0], args[1], args[2], args[3]);
+ } else if (cmpserv("nextprop", 3, 1)) {
+ ret = vof_nextprop(fdt, args[0], args[1], args[2]);
+ } else if (cmpserv("peer", 1, 1)) {
+ ret = vof_peer(fdt, args[0]);
+ } else if (cmpserv("child", 1, 1)) {
+ ret = vof_child(fdt, args[0]);
+ } else if (cmpserv("parent", 1, 1)) {
+ ret = vof_parent(fdt, args[0]);
+ } else if (cmpserv("open", 1, 1)) {
+ ret = vof_open(fdt, vof, args[0]);
+ } else if (cmpserv("close", 1, 0)) {
+ vof_close(vof, args[0]);
+ } else if (cmpserv("instance-to-package", 1, 1)) {
+ ret = vof_instance_to_package(vof, args[0]);
+ } else if (cmpserv("package-to-path", 3, 1)) {
+ ret = vof_package_to_path(fdt, args[0], args[1], args[2]);
+ } else if (cmpserv("instance-to-path", 3, 1)) {
+ ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
+ } else if (cmpserv("write", 3, 1)) {
+ ret = vof_write(vof, args[0], args[1], args[2]);
+ } else if (cmpserv("claim", 3, 1)) {
+ ret = vof_claim(vof, args[0], args[1], args[2]);
+ if (ret != -1) {
+ vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base);
+ }
+ } else if (cmpserv("release", 2, 0)) {
+ ret = vof_release(vof, args[0], args[1]);
+ if (ret != -1) {
+ vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base);
+ }
+ } else if (cmpserv("call-method", 0, 0)) {
+ ret = vof_call_method(ms, vof, args[0], args[1], args[2], args[3],
+ args[4], args[5], rets);
+ } else if (cmpserv("interpret", 0, 0)) {
+ ret = vof_call_interpret(args[0], args[1], args[2], rets);
+ } else if (cmpserv("milliseconds", 0, 1)) {
+ ret = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ } else if (cmpserv("quiesce", 0, 0)) {
+ vof_quiesce(ms, fdt, vof);
+ } else if (cmpserv("exit", 0, 0)) {
+ error_report("Stopped as the VM requested \"exit\"");
+ vm_stop(RUN_STATE_PAUSED);
+ } else {
+ trace_vof_error_unknown_service(service, nargs, nrets);
+ ret = -1;
+ }
+
+#undef cmpserv
+
+ return ret;
+}
+
+/* Defined as Big Endian */
+struct prom_args {
+ uint32_t service;
+ uint32_t nargs;
+ uint32_t nret;
+ uint32_t args[10];
+} QEMU_PACKED;
+
+int vof_client_call(MachineState *ms, Vof *vof, void *fdt,
+ target_ulong args_real)
+{
+ struct prom_args args_be;
+ uint32_t args[ARRAY_SIZE(args_be.args)];
+ uint32_t rets[ARRAY_SIZE(args_be.args)] = { 0 }, ret;
+ char service[64];
+ unsigned nargs, nret, i;
+
+ if (VOF_MEM_READ(args_real, &args_be, sizeof(args_be)) != MEMTX_OK) {
+ return -EINVAL;
+ }
+ nargs = be32_to_cpu(args_be.nargs);
+ if (nargs >= ARRAY_SIZE(args_be.args)) {
+ return -EINVAL;
+ }
+
+ if (VOF_MEM_READ(be32_to_cpu(args_be.service), service, sizeof(service)) !=
+ MEMTX_OK) {
+ return -EINVAL;
+ }
+ if (strnlen(service, sizeof(service)) == sizeof(service)) {
+ /* Too long service name */
+ return -EINVAL;
+ }
+
+ for (i = 0; i < nargs; ++i) {
+ args[i] = be32_to_cpu(args_be.args[i]);
+ }
+
+ nret = be32_to_cpu(args_be.nret);
+ ret = vof_client_handle(ms, fdt, vof, service, args, nargs, rets, nret);
+ if (!nret) {
+ return 0;
+ }
+
+ args_be.args[nargs] = cpu_to_be32(ret);
+ for (i = 1; i < nret; ++i) {
+ args_be.args[nargs + i] = cpu_to_be32(rets[i - 1]);
+ }
+
+ if (VOF_MEM_WRITE(args_real + offsetof(struct prom_args, args[nargs]),
+ args_be.args + nargs, sizeof(args_be.args[0]) * nret) !=
+ MEMTX_OK) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vof_instance_free(gpointer data)
+{
+ OfInstance *inst = (OfInstance *)data;
+
+ g_free(inst->path);
+ g_free(inst);
+}
+
+void vof_init(Vof *vof, uint64_t top_addr, Error **errp)
+{
+ vof_cleanup(vof);
+
+ vof->of_instances = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ NULL, vof_instance_free);
+ vof->claimed = g_array_new(false, false, sizeof(OfClaimed));
+
+ /* Keep allocations in 32bit as CLI ABI can only return cells==32bit */
+ vof->top_addr = MIN(top_addr, 4 * GiB);
+ if (vof_claim(vof, 0, vof->fw_size, 0) == -1) {
+ error_setg(errp, "Memory for firmware is in use");
+ }
+}
+
+void vof_cleanup(Vof *vof)
+{
+ if (vof->claimed) {
+ g_array_unref(vof->claimed);
+ }
+ if (vof->of_instances) {
+ g_hash_table_unref(vof->of_instances);
+ }
+ vof->claimed = NULL;
+ vof->of_instances = NULL;
+}
+
+void vof_build_dt(void *fdt, Vof *vof)
+{
+ uint32_t phandle = fdt_get_max_phandle(fdt);
+ int offset, proplen = 0;
+ const void *prop;
+
+ /* Assign phandles to nodes without predefined phandles (like XICS/XIVE) */
+ for (offset = fdt_next_node(fdt, -1, NULL);
+ offset >= 0;
+ offset = fdt_next_node(fdt, offset, NULL)) {
+ prop = fdt_getprop(fdt, offset, "phandle", &proplen);
+ if (prop) {
+ continue;
+ }
+ ++phandle;
+ _FDT(fdt_setprop_cell(fdt, offset, "phandle", phandle));
+ }
+
+ vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base);
+}
+
+static const TypeInfo vof_machine_if_info = {
+ .name = TYPE_VOF_MACHINE_IF,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(VofMachineIfClass),
+};
+
+static void vof_machine_if_register_types(void)
+{
+ type_register_static(&vof_machine_if_info);
+}
+type_init(vof_machine_if_register_types)
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
new file mode 100644
index 0000000..a2b55a4
--- /dev/null
+++ b/hw/sensor/Kconfig
@@ -0,0 +1,19 @@
+config TMP105
+ bool
+ depends on I2C
+
+config TMP421
+ bool
+ depends on I2C
+
+config EMC141X
+ bool
+ depends on I2C
+
+config ADM1272
+ bool
+ depends on I2C
+
+config MAX34451
+ bool
+ depends on I2C
diff --git a/hw/sensor/adm1272.c b/hw/sensor/adm1272.c
new file mode 100644
index 0000000..7310c76
--- /dev/null
+++ b/hw/sensor/adm1272.c
@@ -0,0 +1,543 @@
+/*
+ * Analog Devices ADM1272 High Voltage Positive Hot Swap Controller and Digital
+ * Power Monitor with PMBus
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <string.h>
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_ADM1272 "adm1272"
+#define ADM1272(obj) OBJECT_CHECK(ADM1272State, (obj), TYPE_ADM1272)
+
+#define ADM1272_RESTART_TIME 0xCC
+#define ADM1272_MFR_PEAK_IOUT 0xD0
+#define ADM1272_MFR_PEAK_VIN 0xD1
+#define ADM1272_MFR_PEAK_VOUT 0xD2
+#define ADM1272_MFR_PMON_CONTROL 0xD3
+#define ADM1272_MFR_PMON_CONFIG 0xD4
+#define ADM1272_MFR_ALERT1_CONFIG 0xD5
+#define ADM1272_MFR_ALERT2_CONFIG 0xD6
+#define ADM1272_MFR_PEAK_TEMPERATURE 0xD7
+#define ADM1272_MFR_DEVICE_CONFIG 0xD8
+#define ADM1272_MFR_POWER_CYCLE 0xD9
+#define ADM1272_MFR_PEAK_PIN 0xDA
+#define ADM1272_MFR_READ_PIN_EXT 0xDB
+#define ADM1272_MFR_READ_EIN_EXT 0xDC
+
+#define ADM1272_HYSTERESIS_LOW 0xF2
+#define ADM1272_HYSTERESIS_HIGH 0xF3
+#define ADM1272_STATUS_HYSTERESIS 0xF4
+#define ADM1272_STATUS_GPIO 0xF5
+#define ADM1272_STRT_UP_IOUT_LIM 0xF6
+
+/* Defaults */
+#define ADM1272_OPERATION_DEFAULT 0x80
+#define ADM1272_CAPABILITY_DEFAULT 0xB0
+#define ADM1272_CAPABILITY_NO_PEC 0x30
+#define ADM1272_DIRECT_MODE 0x40
+#define ADM1272_HIGH_LIMIT_DEFAULT 0x0FFF
+#define ADM1272_PIN_OP_DEFAULT 0x7FFF
+#define ADM1272_PMBUS_REVISION_DEFAULT 0x22
+#define ADM1272_MFR_ID_DEFAULT "ADI"
+#define ADM1272_MODEL_DEFAULT "ADM1272-A1"
+#define ADM1272_MFR_DEFAULT_REVISION "25"
+#define ADM1272_DEFAULT_DATE "160301"
+#define ADM1272_RESTART_TIME_DEFAULT 0x64
+#define ADM1272_PMON_CONTROL_DEFAULT 0x1
+#define ADM1272_PMON_CONFIG_DEFAULT 0x3F35
+#define ADM1272_DEVICE_CONFIG_DEFAULT 0x8
+#define ADM1272_HYSTERESIS_HIGH_DEFAULT 0xFFFF
+#define ADM1272_STRT_UP_IOUT_LIM_DEFAULT 0x000F
+#define ADM1272_VOLT_DEFAULT 12000
+#define ADM1272_IOUT_DEFAULT 25000
+#define ADM1272_PWR_DEFAULT 300 /* 12V 25A */
+#define ADM1272_SHUNT 300 /* micro-ohms */
+#define ADM1272_VOLTAGE_COEFF_DEFAULT 1
+#define ADM1272_CURRENT_COEFF_DEFAULT 3
+#define ADM1272_PWR_COEFF_DEFAULT 7
+#define ADM1272_IOUT_OFFSET 0x5000
+#define ADM1272_IOUT_OFFSET 0x5000
+
+
+typedef struct ADM1272State {
+ PMBusDevice parent;
+
+ uint64_t ein_ext;
+ uint32_t pin_ext;
+ uint8_t restart_time;
+
+ uint16_t peak_vin;
+ uint16_t peak_vout;
+ uint16_t peak_iout;
+ uint16_t peak_temperature;
+ uint16_t peak_pin;
+
+ uint8_t pmon_control;
+ uint16_t pmon_config;
+ uint16_t alert1_config;
+ uint16_t alert2_config;
+ uint16_t device_config;
+
+ uint16_t hysteresis_low;
+ uint16_t hysteresis_high;
+ uint8_t status_hysteresis;
+ uint8_t status_gpio;
+
+ uint16_t strt_up_iout_lim;
+
+} ADM1272State;
+
+static const PMBusCoefficients adm1272_coefficients[] = {
+ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
+ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
+ [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */
+ [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */
+ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
+ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
+ [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */
+ [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */
+ [8] = { 42, 31871, -1 }, /* temperature */
+};
+
+static void adm1272_check_limits(ADM1272State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ if (pmdev->pages[0].read_vout > s->peak_vout) {
+ s->peak_vout = pmdev->pages[0].read_vout;
+ }
+
+ if (pmdev->pages[0].read_vin > s->peak_vin) {
+ s->peak_vin = pmdev->pages[0].read_vin;
+ }
+
+ if (pmdev->pages[0].read_iout > s->peak_iout) {
+ s->peak_iout = pmdev->pages[0].read_iout;
+ }
+
+ if (pmdev->pages[0].read_temperature_1 > s->peak_temperature) {
+ s->peak_temperature = pmdev->pages[0].read_temperature_1;
+ }
+
+ if (pmdev->pages[0].read_pin > s->peak_pin) {
+ s->peak_pin = pmdev->pages[0].read_pin;
+ }
+}
+
+static uint16_t adm1272_millivolts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_millivolts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_milliamps_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ /* Y = (m * r_sense * x - b) * 10^R */
+ c.m = c.m * ADM1272_SHUNT / 1000; /* micro-ohms */
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_milliamps(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_watts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_watts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static void adm1272_exit_reset(Object *obj)
+{
+ ADM1272State *s = ADM1272(obj);
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+ pmdev->page = 0;
+ pmdev->pages[0].operation = ADM1272_OPERATION_DEFAULT;
+
+
+ pmdev->capability = ADM1272_CAPABILITY_NO_PEC;
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].vout_mode = ADM1272_DIRECT_MODE;
+ pmdev->pages[0].vout_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vout_uv_warn_limit = 0;
+ pmdev->pages[0].iout_oc_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_fault_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_uv_warn_limit = 0;
+ pmdev->pages[0].pin_op_warn_limit = ADM1272_PIN_OP_DEFAULT;
+
+ pmdev->pages[0].status_word = 0;
+ pmdev->pages[0].status_vout = 0;
+ pmdev->pages[0].status_iout = 0;
+ pmdev->pages[0].status_input = 0;
+ pmdev->pages[0].status_temperature = 0;
+ pmdev->pages[0].status_mfr_specific = 0;
+
+ pmdev->pages[0].read_vin
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_vout
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_iout
+ = adm1272_milliamps_to_direct(ADM1272_IOUT_DEFAULT);
+ pmdev->pages[0].read_temperature_1 = 0;
+ pmdev->pages[0].read_pin = adm1272_watts_to_direct(ADM1272_PWR_DEFAULT);
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].mfr_id = ADM1272_MFR_ID_DEFAULT;
+ pmdev->pages[0].mfr_model = ADM1272_MODEL_DEFAULT;
+ pmdev->pages[0].mfr_revision = ADM1272_MFR_DEFAULT_REVISION;
+ pmdev->pages[0].mfr_date = ADM1272_DEFAULT_DATE;
+
+ s->pin_ext = 0;
+ s->ein_ext = 0;
+ s->restart_time = ADM1272_RESTART_TIME_DEFAULT;
+
+ s->peak_vin = 0;
+ s->peak_vout = 0;
+ s->peak_iout = 0;
+ s->peak_temperature = 0;
+ s->peak_pin = 0;
+
+ s->pmon_control = ADM1272_PMON_CONTROL_DEFAULT;
+ s->pmon_config = ADM1272_PMON_CONFIG_DEFAULT;
+ s->alert1_config = 0;
+ s->alert2_config = 0;
+ s->device_config = ADM1272_DEVICE_CONFIG_DEFAULT;
+
+ s->hysteresis_low = 0;
+ s->hysteresis_high = ADM1272_HYSTERESIS_HIGH_DEFAULT;
+ s->status_hysteresis = 0;
+ s->status_gpio = 0;
+
+ s->strt_up_iout_lim = ADM1272_STRT_UP_IOUT_LIM_DEFAULT;
+}
+
+static uint8_t adm1272_read_byte(PMBusDevice *pmdev)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ switch (pmdev->code) {
+ case ADM1272_RESTART_TIME:
+ pmbus_send8(pmdev, s->restart_time);
+ break;
+
+ case ADM1272_MFR_PEAK_IOUT:
+ pmbus_send16(pmdev, s->peak_iout);
+ break;
+
+ case ADM1272_MFR_PEAK_VIN:
+ pmbus_send16(pmdev, s->peak_vin);
+ break;
+
+ case ADM1272_MFR_PEAK_VOUT:
+ pmbus_send16(pmdev, s->peak_vout);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ pmbus_send8(pmdev, s->pmon_control);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ pmbus_send16(pmdev, s->pmon_config);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ pmbus_send16(pmdev, s->alert1_config);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ pmbus_send16(pmdev, s->alert2_config);
+ break;
+
+ case ADM1272_MFR_PEAK_TEMPERATURE:
+ pmbus_send16(pmdev, s->peak_temperature);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ pmbus_send16(pmdev, s->device_config);
+ break;
+
+ case ADM1272_MFR_PEAK_PIN:
+ pmbus_send16(pmdev, s->peak_pin);
+ break;
+
+ case ADM1272_MFR_READ_PIN_EXT:
+ pmbus_send32(pmdev, s->pin_ext);
+ break;
+
+ case ADM1272_MFR_READ_EIN_EXT:
+ pmbus_send64(pmdev, s->ein_ext);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ pmbus_send16(pmdev, s->hysteresis_low);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ pmbus_send16(pmdev, s->hysteresis_high);
+ break;
+
+ case ADM1272_STATUS_HYSTERESIS:
+ pmbus_send16(pmdev, s->status_hysteresis);
+ break;
+
+ case ADM1272_STATUS_GPIO:
+ pmbus_send16(pmdev, s->status_gpio);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ pmbus_send16(pmdev, s->strt_up_iout_lim);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ return 0xFF;
+ break;
+ }
+
+ return 0;
+}
+
+static int adm1272_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+
+ switch (pmdev->code) {
+
+ case ADM1272_RESTART_TIME:
+ s->restart_time = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ s->pmon_control = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ s->pmon_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ s->alert1_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ s->alert2_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ s->device_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_POWER_CYCLE:
+ adm1272_exit_reset((Object *)s);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ s->hysteresis_low = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ s->hysteresis_high = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ s->strt_up_iout_lim = pmbus_receive16(pmdev);
+ adm1272_check_limits(s);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0;
+}
+
+static void adm1272_get(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ uint16_t value;
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ value = adm1272_direct_to_millivolts(*(uint16_t *)opaque);
+ } else if (strcmp(name, "iout") == 0) {
+ value = adm1272_direct_to_milliamps(*(uint16_t *)opaque);
+ } else if (strcmp(name, "pin") == 0) {
+ value = adm1272_direct_to_watts(*(uint16_t *)opaque);
+ } else {
+ value = *(uint16_t *)opaque;
+ }
+
+ visit_type_uint16(v, name, &value, errp);
+}
+
+static void adm1272_set(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ ADM1272State *s = ADM1272(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ *internal = adm1272_millivolts_to_direct(value);
+ } else if (strcmp(name, "iout") == 0) {
+ *internal = adm1272_milliamps_to_direct(value);
+ } else if (strcmp(name, "pin") == 0) {
+ *internal = adm1272_watts_to_direct(value);
+ } else {
+ *internal = value;
+ }
+
+ adm1272_check_limits(s);
+}
+
+static const VMStateDescription vmstate_adm1272 = {
+ .name = "ADM1272",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_PMBUS_DEVICE(parent, ADM1272State),
+ VMSTATE_UINT64(ein_ext, ADM1272State),
+ VMSTATE_UINT32(pin_ext, ADM1272State),
+ VMSTATE_UINT8(restart_time, ADM1272State),
+
+ VMSTATE_UINT16(peak_vin, ADM1272State),
+ VMSTATE_UINT16(peak_vout, ADM1272State),
+ VMSTATE_UINT16(peak_iout, ADM1272State),
+ VMSTATE_UINT16(peak_temperature, ADM1272State),
+ VMSTATE_UINT16(peak_pin, ADM1272State),
+
+ VMSTATE_UINT8(pmon_control, ADM1272State),
+ VMSTATE_UINT16(pmon_config, ADM1272State),
+ VMSTATE_UINT16(alert1_config, ADM1272State),
+ VMSTATE_UINT16(alert2_config, ADM1272State),
+ VMSTATE_UINT16(device_config, ADM1272State),
+
+ VMSTATE_UINT16(hysteresis_low, ADM1272State),
+ VMSTATE_UINT16(hysteresis_high, ADM1272State),
+ VMSTATE_UINT8(status_hysteresis, ADM1272State),
+ VMSTATE_UINT8(status_gpio, ADM1272State),
+
+ VMSTATE_UINT16(strt_up_iout_lim, ADM1272State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adm1272_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VOUT | PB_HAS_VIN | PB_HAS_IOUT |
+ PB_HAS_PIN | PB_HAS_TEMPERATURE | PB_HAS_MFR_INFO;
+
+ pmbus_page_config(pmdev, 0, flags);
+
+ object_property_add(obj, "vin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vin);
+
+ object_property_add(obj, "vout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vout);
+
+ object_property_add(obj, "iout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_iout);
+
+ object_property_add(obj, "pin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_pin);
+
+}
+
+static void adm1272_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+
+ dc->desc = "Analog Devices ADM1272 Hot Swap controller";
+ dc->vmsd = &vmstate_adm1272;
+ k->write_data = adm1272_write_data;
+ k->receive_byte = adm1272_read_byte;
+ k->device_num_pages = 1;
+
+ rc->phases.exit = adm1272_exit_reset;
+}
+
+static const TypeInfo adm1272_info = {
+ .name = TYPE_ADM1272,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(ADM1272State),
+ .instance_init = adm1272_init,
+ .class_init = adm1272_class_init,
+};
+
+static void adm1272_register_types(void)
+{
+ type_register_static(&adm1272_info);
+}
+
+type_init(adm1272_register_types)
diff --git a/hw/misc/emc141x.c b/hw/sensor/emc141x.c
index f7c53d4..7ce8f4e 100644
--- a/hw/misc/emc141x.c
+++ b/hw/sensor/emc141x.c
@@ -25,7 +25,7 @@
#include "qapi/visitor.h"
#include "qemu/module.h"
#include "qom/object.h"
-#include "hw/misc/emc141x_regs.h"
+#include "hw/sensor/emc141x_regs.h"
#define SENSORS_COUNT_MAX 4
diff --git a/hw/sensor/max34451.c b/hw/sensor/max34451.c
new file mode 100644
index 0000000..a91d8bd
--- /dev/null
+++ b/hw/sensor/max34451.c
@@ -0,0 +1,775 @@
+/*
+ * Maxim MAX34451 PMBus 16-Channel V/I monitor and 12-Channel Sequencer/Marginer
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_MAX34451 "max34451"
+#define MAX34451(obj) OBJECT_CHECK(MAX34451State, (obj), TYPE_MAX34451)
+
+#define MAX34451_MFR_MODE 0xD1
+#define MAX34451_MFR_PSEN_CONFIG 0xD2
+#define MAX34451_MFR_VOUT_PEAK 0xD4
+#define MAX34451_MFR_IOUT_PEAK 0xD5
+#define MAX34451_MFR_TEMPERATURE_PEAK 0xD6
+#define MAX34451_MFR_VOUT_MIN 0xD7
+#define MAX34451_MFR_NV_LOG_CONFIG 0xD8
+#define MAX34451_MFR_FAULT_RESPONSE 0xD9
+#define MAX34451_MFR_FAULT_RETRY 0xDA
+#define MAX34451_MFR_NV_FAULT_LOG 0xDC
+#define MAX34451_MFR_TIME_COUNT 0xDD
+#define MAX34451_MFR_MARGIN_CONFIG 0xDF
+#define MAX34451_MFR_FW_SERIAL 0xE0
+#define MAX34451_MFR_IOUT_AVG 0xE2
+#define MAX34451_MFR_CHANNEL_CONFIG 0xE4
+#define MAX34451_MFR_TON_SEQ_MAX 0xE6
+#define MAX34451_MFR_PWM_CONFIG 0xE7
+#define MAX34451_MFR_SEQ_CONFIG 0xE8
+#define MAX34451_MFR_STORE_ALL 0xEE
+#define MAX34451_MFR_RESTORE_ALL 0xEF
+#define MAX34451_MFR_TEMP_SENSOR_CONFIG 0xF0
+#define MAX34451_MFR_STORE_SINGLE 0xFC
+#define MAX34451_MFR_CRC 0xFE
+
+#define MAX34451_NUM_MARGINED_PSU 12
+#define MAX34451_NUM_PWR_DEVICES 16
+#define MAX34451_NUM_TEMP_DEVICES 5
+#define MAX34451_NUM_PAGES 21
+
+#define DEFAULT_OP_ON 0x80
+#define DEFAULT_CAPABILITY 0x20
+#define DEFAULT_ON_OFF_CONFIG 0x1a
+#define DEFAULT_VOUT_MODE 0x40
+#define DEFAULT_TEMPERATURE 2500
+#define DEFAULT_SCALE 0x7FFF
+#define DEFAULT_OV_LIMIT 0x7FFF
+#define DEFAULT_OC_LIMIT 0x7FFF
+#define DEFAULT_OT_LIMIT 0x7FFF
+#define DEFAULT_VMIN 0x7FFF
+#define DEFAULT_TON_FAULT_LIMIT 0xFFFF
+#define DEFAULT_CHANNEL_CONFIG 0x20
+#define DEFAULT_TEXT 0x3130313031303130
+
+/**
+ * MAX34451State:
+ * @code: The command code received
+ * @page: Each page corresponds to a device monitored by the Max 34451
+ * The page register determines the available commands depending on device
+ ___________________________________________________________________________
+ | 0 | Power supply monitored by RS0, controlled by PSEN0, and |
+ | | margined with PWM0. |
+ |_______|___________________________________________________________________|
+ | 1 | Power supply monitored by RS1, controlled by PSEN1, and |
+ | | margined with PWM1. |
+ |_______|___________________________________________________________________|
+ | 2 | Power supply monitored by RS2, controlled by PSEN2, and |
+ | | margined with PWM2. |
+ |_______|___________________________________________________________________|
+ | 3 | Power supply monitored by RS3, controlled by PSEN3, and |
+ | | margined with PWM3. |
+ |_______|___________________________________________________________________|
+ | 4 | Power supply monitored by RS4, controlled by PSEN4, and |
+ | | margined with PWM4. |
+ |_______|___________________________________________________________________|
+ | 5 | Power supply monitored by RS5, controlled by PSEN5, and |
+ | | margined with PWM5. |
+ |_______|___________________________________________________________________|
+ | 6 | Power supply monitored by RS6, controlled by PSEN6, and |
+ | | margined with PWM6. |
+ |_______|___________________________________________________________________|
+ | 7 | Power supply monitored by RS7, controlled by PSEN7, and |
+ | | margined with PWM7. |
+ |_______|___________________________________________________________________|
+ | 8 | Power supply monitored by RS8, controlled by PSEN8, and |
+ | | optionally margined by OUT0 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 9 | Power supply monitored by RS9, controlled by PSEN9, and |
+ | | optionally margined by OUT1 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 10 | Power supply monitored by RS10, controlled by PSEN10, and |
+ | | optionally margined by OUT2 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 11 | Power supply monitored by RS11, controlled by PSEN11, and |
+ | | optionally margined by OUT3 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 12 | ADC channel 12 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 13 | ADC channel 13 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 14 | ADC channel 14 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 15 | ADC channel 15 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 16 | Internal temperature sensor. |
+ |_______|___________________________________________________________________|
+ | 17 | External DS75LV temperature sensor with I2C address 90h. |
+ |_______|___________________________________________________________________|
+ | 18 | External DS75LV temperature sensor with I2C address 92h. |
+ |_______|___________________________________________________________________|
+ | 19 | External DS75LV temperature sensor with I2C address 94h. |
+ |_______|___________________________________________________________________|
+ | 20 | External DS75LV temperature sensor with I2C address 96h. |
+ |_______|___________________________________________________________________|
+ | 21=E2=80=93254| Reserved. |
+ |_______|___________________________________________________________________|
+ | 255 | Applies to all pages. |
+ |_______|___________________________________________________________________|
+ *
+ * @operation: Turn on and off power supplies
+ * @on_off_config: Configure the power supply on and off transition behaviour
+ * @write_protect: protect against changes to the device's memory
+ * @vout_margin_high: the voltage when OPERATION is set to margin high
+ * @vout_margin_low: the voltage when OPERATION is set to margin low
+ * @vout_scale: scale ADC reading to actual device reading if different
+ * @iout_cal_gain: set ratio of the voltage at the ADC input to sensed current
+ */
+typedef struct MAX34451State {
+ PMBusDevice parent;
+
+ uint16_t power_good_on[MAX34451_NUM_PWR_DEVICES];
+ uint16_t power_good_off[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_delay[MAX34451_NUM_MARGINED_PSU];
+ uint16_t ton_max_fault_limit[MAX34451_NUM_MARGINED_PSU];
+ uint16_t toff_delay[MAX34451_NUM_MARGINED_PSU];
+ uint8_t status_mfr_specific[MAX34451_NUM_PWR_DEVICES];
+ /* Manufacturer specific function */
+ uint64_t mfr_location;
+ uint64_t mfr_date;
+ uint64_t mfr_serial;
+ uint16_t mfr_mode;
+ uint32_t psen_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t vout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t iout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t temperature_peak[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t vout_min[MAX34451_NUM_PWR_DEVICES];
+ uint16_t nv_log_config;
+ uint32_t fault_response[MAX34451_NUM_PWR_DEVICES];
+ uint16_t fault_retry;
+ uint32_t fault_log;
+ uint32_t time_count;
+ uint16_t margin_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t fw_serial;
+ uint16_t iout_avg[MAX34451_NUM_PWR_DEVICES];
+ uint16_t channel_config[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_seq_max[MAX34451_NUM_MARGINED_PSU];
+ uint32_t pwm_config[MAX34451_NUM_MARGINED_PSU];
+ uint32_t seq_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t temp_sensor_config[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t store_single;
+ uint16_t crc;
+} MAX34451State;
+
+
+static void max34451_check_limits(MAX34451State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ if (pmdev->pages[i].read_vout == 0) { /* PSU disabled */
+ continue;
+ }
+
+ if (pmdev->pages[i].read_vout > s->vout_peak[i]) {
+ s->vout_peak[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_vout < s->vout_min[i]) {
+ s->vout_min[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_iout > s->iout_peak[i]) {
+ s->iout_peak[i] = pmdev->pages[i].read_iout;
+ }
+ }
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ if (pmdev->pages[i + 16].read_temperature_1 > s->temperature_peak[i]) {
+ s->temperature_peak[i] = pmdev->pages[i + 16].read_temperature_1;
+ }
+ }
+}
+
+static uint8_t max34451_read_byte(PMBusDevice *pmdev)
+{
+ MAX34451State *s = MAX34451(pmdev);
+ switch (pmdev->code) {
+
+ case PMBUS_POWER_GOOD_ON:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_on[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_off[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_max_fault_limit[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->toff_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_STATUS_MFR_SPECIFIC:
+ if (pmdev->page < 16) {
+ pmbus_send8(pmdev, s->status_mfr_specific[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_MFR_ID:
+ pmbus_send8(pmdev, 0x4d); /* Maxim */
+ break;
+
+ case PMBUS_MFR_MODEL:
+ pmbus_send8(pmdev, 0x59);
+ break;
+
+ case PMBUS_MFR_LOCATION:
+ pmbus_send64(pmdev, s->mfr_location);
+ break;
+
+ case PMBUS_MFR_DATE:
+ pmbus_send64(pmdev, s->mfr_date);
+ break;
+
+ case PMBUS_MFR_SERIAL:
+ pmbus_send64(pmdev, s->mfr_serial);
+ break;
+
+ case MAX34451_MFR_MODE:
+ pmbus_send16(pmdev, s->mfr_mode);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->psen_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send16(pmdev, s->temperature_peak[pmdev->page % 16]);
+ } else {
+ pmbus_send16(pmdev, s->temperature_peak[0]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_min[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG:
+ pmbus_send16(pmdev, s->nv_log_config);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE:
+ if (pmdev->page < 16) {
+ pmbus_send32(pmdev, s->fault_response[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY:
+ pmbus_send32(pmdev, s->fault_retry);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ pmbus_send32(pmdev, s->fault_log);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT:
+ pmbus_send32(pmdev, s->time_count);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->margin_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FW_SERIAL:
+ if (pmdev->page == 255) {
+ pmbus_send16(pmdev, 1); /* Firmware revision */
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_AVG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_avg[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->channel_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_seq_max[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->pwm_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->seq_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send32(pmdev, s->temp_sensor_config[pmdev->page % 16]);
+ }
+ break;
+
+ case MAX34451_MFR_STORE_SINGLE:
+ pmbus_send32(pmdev, s->store_single);
+ break;
+
+ case MAX34451_MFR_CRC:
+ pmbus_send32(pmdev, s->crc);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0xFF;
+}
+
+static int max34451_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ MAX34451State *s = MAX34451(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+ uint8_t index = pmdev->page;
+
+ switch (pmdev->code) {
+ case MAX34451_MFR_STORE_ALL:
+ case MAX34451_MFR_RESTORE_ALL:
+ case MAX34451_MFR_STORE_SINGLE:
+ /*
+ * TODO: hardware behaviour is to move the contents of volatile
+ * memory to non-volatile memory.
+ */
+ break;
+
+ case PMBUS_POWER_GOOD_ON: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_on[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_off[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_max_fault_limit[pmdev->page]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->toff_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_MFR_LOCATION: /* R/W 64 */
+ s->mfr_location = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_DATE: /* R/W 64 */
+ s->mfr_date = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_SERIAL: /* R/W 64 */
+ s->mfr_serial = pmbus_receive64(pmdev);
+ break;
+
+ case MAX34451_MFR_MODE: /* R/W word */
+ s->mfr_mode = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->psen_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->iout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temperature_peak[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_min[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG: /* R/W word */
+ s->nv_log_config = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE: /* R/W 32 */
+ if (pmdev->page < 16) {
+ s->fault_response[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY: /* R/W word */
+ s->fault_retry = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT: /* R/W 32 */
+ s->time_count = pmbus_receive32(pmdev);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG: /* R/W word */
+ if (pmdev->page < 12) {
+ s->margin_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG: /* R/W word */
+ if (pmdev->page < 16) {
+ s->channel_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_seq_max[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->pwm_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->seq_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temp_sensor_config[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CRC: /* R/W word */
+ s->crc = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ case MAX34451_MFR_FW_SERIAL:
+ case MAX34451_MFR_IOUT_AVG:
+ /* Read only commands */
+ pmdev->pages[index].status_word |= PMBUS_STATUS_CML;
+ pmdev->pages[index].status_cml |= PB_CML_FAULT_INVALID_DATA;
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to read-only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+
+ return 0;
+}
+
+static void max34451_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ visit_type_uint16(v, name, (uint16_t *)opaque, errp);
+}
+
+static void max34451_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ MAX34451State *s = MAX34451(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ *internal = value;
+ max34451_check_limits(s);
+}
+
+/* used to init uint16_t arrays */
+static inline void *memset_word(void *s, uint16_t c, size_t n)
+{
+ size_t i;
+ uint16_t *p = s;
+
+ for (i = 0; i < n; i++) {
+ p[i] = c;
+ }
+
+ return s;
+}
+
+static void max34451_exit_reset(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ MAX34451State *s = MAX34451(obj);
+ pmdev->capability = DEFAULT_CAPABILITY;
+
+ for (int i = 0; i < MAX34451_NUM_PAGES; i++) {
+ pmdev->pages[i].operation = DEFAULT_OP_ON;
+ pmdev->pages[i].on_off_config = DEFAULT_ON_OFF_CONFIG;
+ pmdev->pages[i].revision = 0x11;
+ pmdev->pages[i].vout_mode = DEFAULT_VOUT_MODE;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmdev->pages[i].vout_scale_monitor = DEFAULT_SCALE;
+ pmdev->pages[i].vout_ov_fault_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].vout_ov_warn_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].iout_oc_warn_limit = DEFAULT_OC_LIMIT;
+ pmdev->pages[i].iout_oc_fault_limit = DEFAULT_OC_LIMIT;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmdev->pages[i].ton_max_fault_limit = DEFAULT_TON_FAULT_LIMIT;
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmdev->pages[i].read_temperature_1 = DEFAULT_TEMPERATURE;
+ pmdev->pages[i].ot_warn_limit = DEFAULT_OT_LIMIT;
+ pmdev->pages[i].ot_fault_limit = DEFAULT_OT_LIMIT;
+ }
+
+ memset_word(s->ton_max_fault_limit, DEFAULT_TON_FAULT_LIMIT,
+ MAX34451_NUM_MARGINED_PSU);
+ memset_word(s->channel_config, DEFAULT_CHANNEL_CONFIG,
+ MAX34451_NUM_PWR_DEVICES);
+ memset_word(s->vout_min, DEFAULT_VMIN, MAX34451_NUM_PWR_DEVICES);
+
+ s->mfr_location = DEFAULT_TEXT;
+ s->mfr_date = DEFAULT_TEXT;
+ s->mfr_serial = DEFAULT_TEXT;
+}
+
+static const VMStateDescription vmstate_max34451 = {
+ .name = TYPE_MAX34451,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_PMBUS_DEVICE(parent, MAX34451State),
+ VMSTATE_UINT16_ARRAY(power_good_on, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(power_good_off, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(ton_delay, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(ton_max_fault_limit, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(toff_delay, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT8_ARRAY(status_mfr_specific, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT64(mfr_location, MAX34451State),
+ VMSTATE_UINT64(mfr_date, MAX34451State),
+ VMSTATE_UINT64(mfr_serial, MAX34451State),
+ VMSTATE_UINT16(mfr_mode, MAX34451State),
+ VMSTATE_UINT32_ARRAY(psen_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(vout_peak, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(iout_peak, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(temperature_peak, MAX34451State,
+ MAX34451_NUM_TEMP_DEVICES),
+ VMSTATE_UINT16_ARRAY(vout_min, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16(nv_log_config, MAX34451State),
+ VMSTATE_UINT32_ARRAY(fault_response, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16(fault_retry, MAX34451State),
+ VMSTATE_UINT32(fault_log, MAX34451State),
+ VMSTATE_UINT32(time_count, MAX34451State),
+ VMSTATE_UINT16_ARRAY(margin_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16(fw_serial, MAX34451State),
+ VMSTATE_UINT16_ARRAY(iout_avg, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(channel_config, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(ton_seq_max, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT32_ARRAY(pwm_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT32_ARRAY(seq_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(temp_sensor_config, MAX34451State,
+ MAX34451_NUM_TEMP_DEVICES),
+ VMSTATE_UINT16(store_single, MAX34451State),
+ VMSTATE_UINT16(crc, MAX34451State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void max34451_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t psu_flags = PB_HAS_VOUT | PB_HAS_IOUT | PB_HAS_VOUT_MODE |
+ PB_HAS_IOUT_GAIN;
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmbus_page_config(pmdev, i, psu_flags);
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmbus_page_config(pmdev, i, psu_flags | PB_HAS_VOUT_MARGIN);
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmbus_page_config(pmdev, i, PB_HAS_TEMPERATURE | PB_HAS_VOUT_MODE);
+ }
+
+ /* get and set the voltage in millivolts, max is 32767 mV */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ object_property_add(obj, "vout[*]", "uint16",
+ max34451_get,
+ max34451_set, NULL, &pmdev->pages[i].read_vout);
+ }
+
+ /*
+ * get and set the temperature of the internal temperature sensor in
+ * centidegrees Celcius i.e.: 2500 -> 25.00 C, max is 327.67 C
+ */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ object_property_add(obj, "temperature[*]", "uint16",
+ max34451_get,
+ max34451_set,
+ NULL,
+ &pmdev->pages[i + 16].read_temperature_1);
+ }
+
+}
+
+static void max34451_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+ dc->desc = "Maxim MAX34451 16-Channel V/I monitor";
+ dc->vmsd = &vmstate_max34451;
+ k->write_data = max34451_write_data;
+ k->receive_byte = max34451_read_byte;
+ k->device_num_pages = MAX34451_NUM_PAGES;
+ rc->phases.exit = max34451_exit_reset;
+}
+
+static const TypeInfo max34451_info = {
+ .name = TYPE_MAX34451,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(MAX34451State),
+ .instance_init = max34451_init,
+ .class_init = max34451_class_init,
+};
+
+static void max34451_register_types(void)
+{
+ type_register_static(&max34451_info);
+}
+
+type_init(max34451_register_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
new file mode 100644
index 0000000..034e3e0
--- /dev/null
+++ b/hw/sensor/meson.build
@@ -0,0 +1,5 @@
+softmmu_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
+softmmu_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
+softmmu_ss.add(when: 'CONFIG_EMC141X', if_true: files('emc141x.c'))
+softmmu_ss.add(when: 'CONFIG_ADM1272', if_true: files('adm1272.c'))
+softmmu_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
diff --git a/hw/misc/tmp105.c b/hw/sensor/tmp105.c
index d299d9b..2056449 100644
--- a/hw/misc/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -22,7 +22,7 @@
#include "hw/i2c/i2c.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
-#include "tmp105.h"
+#include "hw/sensor/tmp105.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
#include "qemu/module.h"
diff --git a/hw/misc/tmp421.c b/hw/sensor/tmp421.c
index a3db57d..a3db57d 100644
--- a/hw/misc/tmp421.c
+++ b/hw/sensor/tmp421.c
diff --git a/hw/vfio/common.c b/hw/vfio/common.c
index ae5654f..3f0d111 100644
--- a/hw/vfio/common.c
+++ b/hw/vfio/common.c
@@ -36,6 +36,7 @@
#include "qemu/range.h"
#include "sysemu/kvm.h"
#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
#include "trace.h"
#include "qapi/error.h"
#include "migration/migration.h"
@@ -134,6 +135,29 @@ static const char *index_to_str(VFIODevice *vbasedev, int index)
}
}
+static int vfio_ram_block_discard_disable(VFIOContainer *container, bool state)
+{
+ switch (container->iommu_type) {
+ case VFIO_TYPE1v2_IOMMU:
+ case VFIO_TYPE1_IOMMU:
+ /*
+ * We support coordinated discarding of RAM via the RamDiscardManager.
+ */
+ return ram_block_uncoordinated_discard_disable(state);
+ default:
+ /*
+ * VFIO_SPAPR_TCE_IOMMU most probably works just fine with
+ * RamDiscardManager, however, it is completely untested.
+ *
+ * VFIO_SPAPR_TCE_v2_IOMMU with "DMA memory preregistering" does
+ * completely the opposite of managing mapping/pinning dynamically as
+ * required by RamDiscardManager. We would have to special-case sections
+ * with a RamDiscardManager.
+ */
+ return ram_block_discard_disable(state);
+ }
+}
+
int vfio_set_irq_signaling(VFIODevice *vbasedev, int index, int subindex,
int action, int fd, Error **errp)
{
@@ -569,6 +593,44 @@ static bool vfio_get_xlat_addr(IOMMUTLBEntry *iotlb, void **vaddr,
error_report("iommu map to non memory area %"HWADDR_PRIx"",
xlat);
return false;
+ } else if (memory_region_has_ram_discard_manager(mr)) {
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(mr);
+ MemoryRegionSection tmp = {
+ .mr = mr,
+ .offset_within_region = xlat,
+ .size = int128_make64(len),
+ };
+
+ /*
+ * Malicious VMs can map memory into the IOMMU, which is expected
+ * to remain discarded. vfio will pin all pages, populating memory.
+ * Disallow that. vmstate priorities make sure any RamDiscardManager
+ * were already restored before IOMMUs are restored.
+ */
+ if (!ram_discard_manager_is_populated(rdm, &tmp)) {
+ error_report("iommu map to discarded memory (e.g., unplugged via"
+ " virtio-mem): %"HWADDR_PRIx"",
+ iotlb->translated_addr);
+ return false;
+ }
+
+ /*
+ * Malicious VMs might trigger discarding of IOMMU-mapped memory. The
+ * pages will remain pinned inside vfio until unmapped, resulting in a
+ * higher memory consumption than expected. If memory would get
+ * populated again later, there would be an inconsistency between pages
+ * pinned by vfio and pages seen by QEMU. This is the case until
+ * unmapped from the IOMMU (e.g., during device reset).
+ *
+ * With malicious guests, we really only care about pinning more memory
+ * than expected. RLIMIT_MEMLOCK set for the user/process can never be
+ * exceeded and can be used to mitigate this problem.
+ */
+ warn_report_once("Using vfio with vIOMMUs and coordinated discarding of"
+ " RAM (e.g., virtio-mem) works, however, malicious"
+ " guests can trigger pinning of more memory than"
+ " intended via an IOMMU. It's possible to mitigate "
+ " by setting/adjusting RLIMIT_MEMLOCK.");
}
/*
@@ -649,6 +711,153 @@ out:
rcu_read_unlock();
}
+static void vfio_ram_discard_notify_discard(RamDiscardListener *rdl,
+ MemoryRegionSection *section)
+{
+ VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener,
+ listener);
+ const hwaddr size = int128_get64(section->size);
+ const hwaddr iova = section->offset_within_address_space;
+ int ret;
+
+ /* Unmap with a single call. */
+ ret = vfio_dma_unmap(vrdl->container, iova, size , NULL);
+ if (ret) {
+ error_report("%s: vfio_dma_unmap() failed: %s", __func__,
+ strerror(-ret));
+ }
+}
+
+static int vfio_ram_discard_notify_populate(RamDiscardListener *rdl,
+ MemoryRegionSection *section)
+{
+ VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener,
+ listener);
+ const hwaddr end = section->offset_within_region +
+ int128_get64(section->size);
+ hwaddr start, next, iova;
+ void *vaddr;
+ int ret;
+
+ /*
+ * Map in (aligned within memory region) minimum granularity, so we can
+ * unmap in minimum granularity later.
+ */
+ for (start = section->offset_within_region; start < end; start = next) {
+ next = ROUND_UP(start + 1, vrdl->granularity);
+ next = MIN(next, end);
+
+ iova = start - section->offset_within_region +
+ section->offset_within_address_space;
+ vaddr = memory_region_get_ram_ptr(section->mr) + start;
+
+ ret = vfio_dma_map(vrdl->container, iova, next - start,
+ vaddr, section->readonly);
+ if (ret) {
+ /* Rollback */
+ vfio_ram_discard_notify_discard(rdl, section);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void vfio_register_ram_discard_listener(VFIOContainer *container,
+ MemoryRegionSection *section)
+{
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
+ VFIORamDiscardListener *vrdl;
+
+ /* Ignore some corner cases not relevant in practice. */
+ g_assert(QEMU_IS_ALIGNED(section->offset_within_region, TARGET_PAGE_SIZE));
+ g_assert(QEMU_IS_ALIGNED(section->offset_within_address_space,
+ TARGET_PAGE_SIZE));
+ g_assert(QEMU_IS_ALIGNED(int128_get64(section->size), TARGET_PAGE_SIZE));
+
+ vrdl = g_new0(VFIORamDiscardListener, 1);
+ vrdl->container = container;
+ vrdl->mr = section->mr;
+ vrdl->offset_within_address_space = section->offset_within_address_space;
+ vrdl->size = int128_get64(section->size);
+ vrdl->granularity = ram_discard_manager_get_min_granularity(rdm,
+ section->mr);
+
+ g_assert(vrdl->granularity && is_power_of_2(vrdl->granularity));
+ g_assert(vrdl->granularity >= 1 << ctz64(container->pgsizes));
+
+ ram_discard_listener_init(&vrdl->listener,
+ vfio_ram_discard_notify_populate,
+ vfio_ram_discard_notify_discard, true);
+ ram_discard_manager_register_listener(rdm, &vrdl->listener, section);
+ QLIST_INSERT_HEAD(&container->vrdl_list, vrdl, next);
+
+ /*
+ * Sanity-check if we have a theoretically problematic setup where we could
+ * exceed the maximum number of possible DMA mappings over time. We assume
+ * that each mapped section in the same address space as a RamDiscardManager
+ * section consumes exactly one DMA mapping, with the exception of
+ * RamDiscardManager sections; i.e., we don't expect to have gIOMMU sections
+ * in the same address space as RamDiscardManager sections.
+ *
+ * We assume that each section in the address space consumes one memslot.
+ * We take the number of KVM memory slots as a best guess for the maximum
+ * number of sections in the address space we could have over time,
+ * also consuming DMA mappings.
+ */
+ if (container->dma_max_mappings) {
+ unsigned int vrdl_count = 0, vrdl_mappings = 0, max_memslots = 512;
+
+#ifdef CONFIG_KVM
+ if (kvm_enabled()) {
+ max_memslots = kvm_get_max_memslots();
+ }
+#endif
+
+ QLIST_FOREACH(vrdl, &container->vrdl_list, next) {
+ hwaddr start, end;
+
+ start = QEMU_ALIGN_DOWN(vrdl->offset_within_address_space,
+ vrdl->granularity);
+ end = ROUND_UP(vrdl->offset_within_address_space + vrdl->size,
+ vrdl->granularity);
+ vrdl_mappings += (end - start) / vrdl->granularity;
+ vrdl_count++;
+ }
+
+ if (vrdl_mappings + max_memslots - vrdl_count >
+ container->dma_max_mappings) {
+ warn_report("%s: possibly running out of DMA mappings. E.g., try"
+ " increasing the 'block-size' of virtio-mem devies."
+ " Maximum possible DMA mappings: %d, Maximum possible"
+ " memslots: %d", __func__, container->dma_max_mappings,
+ max_memslots);
+ }
+ }
+}
+
+static void vfio_unregister_ram_discard_listener(VFIOContainer *container,
+ MemoryRegionSection *section)
+{
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
+ VFIORamDiscardListener *vrdl = NULL;
+
+ QLIST_FOREACH(vrdl, &container->vrdl_list, next) {
+ if (vrdl->mr == section->mr &&
+ vrdl->offset_within_address_space ==
+ section->offset_within_address_space) {
+ break;
+ }
+ }
+
+ if (!vrdl) {
+ hw_error("vfio: Trying to unregister missing RAM discard listener");
+ }
+
+ ram_discard_manager_unregister_listener(rdm, &vrdl->listener);
+ QLIST_REMOVE(vrdl, next);
+ g_free(vrdl);
+}
+
static void vfio_listener_region_add(MemoryListener *listener,
MemoryRegionSection *section)
{
@@ -810,6 +1019,16 @@ static void vfio_listener_region_add(MemoryListener *listener,
/* Here we assume that memory_region_is_ram(section->mr)==true */
+ /*
+ * For RAM memory regions with a RamDiscardManager, we only want to map the
+ * actually populated parts - and update the mapping whenever we're notified
+ * about changes.
+ */
+ if (memory_region_has_ram_discard_manager(section->mr)) {
+ vfio_register_ram_discard_listener(container, section);
+ return;
+ }
+
vaddr = memory_region_get_ram_ptr(section->mr) +
section->offset_within_region +
(iova - section->offset_within_address_space);
@@ -947,6 +1166,10 @@ static void vfio_listener_region_del(MemoryListener *listener,
pgmask = (1ULL << ctz64(hostwin->iova_pgsizes)) - 1;
try_unmap = !((iova & pgmask) || (int128_get64(llsize) & pgmask));
+ } else if (memory_region_has_ram_discard_manager(section->mr)) {
+ vfio_unregister_ram_discard_listener(container, section);
+ /* Unregistering will trigger an unmap. */
+ try_unmap = false;
}
if (try_unmap) {
@@ -1108,6 +1331,49 @@ static void vfio_iommu_map_dirty_notify(IOMMUNotifier *n, IOMMUTLBEntry *iotlb)
rcu_read_unlock();
}
+static int vfio_ram_discard_get_dirty_bitmap(MemoryRegionSection *section,
+ void *opaque)
+{
+ const hwaddr size = int128_get64(section->size);
+ const hwaddr iova = section->offset_within_address_space;
+ const ram_addr_t ram_addr = memory_region_get_ram_addr(section->mr) +
+ section->offset_within_region;
+ VFIORamDiscardListener *vrdl = opaque;
+
+ /*
+ * Sync the whole mapped region (spanning multiple individual mappings)
+ * in one go.
+ */
+ return vfio_get_dirty_bitmap(vrdl->container, iova, size, ram_addr);
+}
+
+static int vfio_sync_ram_discard_listener_dirty_bitmap(VFIOContainer *container,
+ MemoryRegionSection *section)
+{
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
+ VFIORamDiscardListener *vrdl = NULL;
+
+ QLIST_FOREACH(vrdl, &container->vrdl_list, next) {
+ if (vrdl->mr == section->mr &&
+ vrdl->offset_within_address_space ==
+ section->offset_within_address_space) {
+ break;
+ }
+ }
+
+ if (!vrdl) {
+ hw_error("vfio: Trying to sync missing RAM discard listener");
+ }
+
+ /*
+ * We only want/can synchronize the bitmap for actually mapped parts -
+ * which correspond to populated parts. Replay all populated parts.
+ */
+ return ram_discard_manager_replay_populated(rdm, section,
+ vfio_ram_discard_get_dirty_bitmap,
+ &vrdl);
+}
+
static int vfio_sync_dirty_bitmap(VFIOContainer *container,
MemoryRegionSection *section)
{
@@ -1139,6 +1405,8 @@ static int vfio_sync_dirty_bitmap(VFIOContainer *container,
}
}
return 0;
+ } else if (memory_region_has_ram_discard_manager(section->mr)) {
+ return vfio_sync_ram_discard_listener_dirty_bitmap(container, section);
}
ram_addr = memory_region_get_ram_addr(section->mr) +
@@ -1732,15 +2000,25 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
* new memory, it will not yet set ram_block_discard_set_required() and
* therefore, neither stops us here or deals with the sudden memory
* consumption of inflated memory.
+ *
+ * We do support discarding of memory coordinated via the RamDiscardManager
+ * with some IOMMU types. vfio_ram_block_discard_disable() handles the
+ * details once we know which type of IOMMU we are using.
*/
- ret = ram_block_discard_disable(true);
- if (ret) {
- error_setg_errno(errp, -ret, "Cannot set discarding of RAM broken");
- return ret;
- }
QLIST_FOREACH(container, &space->containers, next) {
if (!ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &container->fd)) {
+ ret = vfio_ram_block_discard_disable(container, true);
+ if (ret) {
+ error_setg_errno(errp, -ret,
+ "Cannot set discarding of RAM broken");
+ if (ioctl(group->fd, VFIO_GROUP_UNSET_CONTAINER,
+ &container->fd)) {
+ error_report("vfio: error disconnecting group %d from"
+ " container", group->groupid);
+ }
+ return ret;
+ }
group->container = container;
QLIST_INSERT_HEAD(&container->group_list, group, container_next);
vfio_kvm_device_add_group(group);
@@ -1768,14 +2046,22 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
container->fd = fd;
container->error = NULL;
container->dirty_pages_supported = false;
+ container->dma_max_mappings = 0;
QLIST_INIT(&container->giommu_list);
QLIST_INIT(&container->hostwin_list);
+ QLIST_INIT(&container->vrdl_list);
ret = vfio_init_container(container, group->fd, errp);
if (ret) {
goto free_container_exit;
}
+ ret = vfio_ram_block_discard_disable(container, true);
+ if (ret) {
+ error_setg_errno(errp, -ret, "Cannot set discarding of RAM broken");
+ goto free_container_exit;
+ }
+
switch (container->iommu_type) {
case VFIO_TYPE1v2_IOMMU:
case VFIO_TYPE1_IOMMU:
@@ -1798,7 +2084,10 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
vfio_host_win_add(container, 0, (hwaddr)-1, info->iova_pgsizes);
container->pgsizes = info->iova_pgsizes;
+ /* The default in the kernel ("dma_entry_limit") is 65535. */
+ container->dma_max_mappings = 65535;
if (!ret) {
+ vfio_get_info_dma_avail(info, &container->dma_max_mappings);
vfio_get_iommu_info_migration(container, info);
}
g_free(info);
@@ -1820,7 +2109,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
if (ret) {
error_setg_errno(errp, errno, "failed to enable container");
ret = -errno;
- goto free_container_exit;
+ goto enable_discards_exit;
}
} else {
container->prereg_listener = vfio_prereg_listener;
@@ -1832,7 +2121,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
ret = -1;
error_propagate_prepend(errp, container->error,
"RAM memory listener initialization failed: ");
- goto free_container_exit;
+ goto enable_discards_exit;
}
}
@@ -1845,7 +2134,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
if (v2) {
memory_listener_unregister(&container->prereg_listener);
}
- goto free_container_exit;
+ goto enable_discards_exit;
}
if (v2) {
@@ -1860,7 +2149,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
if (ret) {
error_setg_errno(errp, -ret,
"failed to remove existing window");
- goto free_container_exit;
+ goto enable_discards_exit;
}
} else {
/* The default table uses 4K pages */
@@ -1901,6 +2190,9 @@ listener_release_exit:
vfio_kvm_device_del_group(group);
vfio_listener_release(container);
+enable_discards_exit:
+ vfio_ram_block_discard_disable(container, false);
+
free_container_exit:
g_free(container);
@@ -1908,7 +2200,6 @@ close_fd_exit:
close(fd);
put_space_exit:
- ram_block_discard_disable(false);
vfio_put_address_space(space);
return ret;
@@ -2030,7 +2321,7 @@ void vfio_put_group(VFIOGroup *group)
}
if (!group->ram_block_discard_allowed) {
- ram_block_discard_disable(false);
+ vfio_ram_block_discard_disable(group->container, false);
}
vfio_kvm_device_del_group(group);
vfio_disconnect_container(group);
@@ -2084,7 +2375,7 @@ int vfio_get_device(VFIOGroup *group, const char *name,
if (!group->ram_block_discard_allowed) {
group->ram_block_discard_allowed = true;
- ram_block_discard_disable(false);
+ vfio_ram_block_discard_disable(group->container, false);
}
}
diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c
index 1ac4a2e..29ea2b4 100644
--- a/hw/virtio/vhost-user.c
+++ b/hw/virtio/vhost-user.c
@@ -1913,7 +1913,10 @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque,
if (err < 0) {
return -EPROTO;
}
+ } else {
+ dev->max_queues = 1;
}
+
if (dev->num_queues && dev->max_queues < dev->num_queues) {
error_setg(errp, "The maximum number of queues supported by the "
"backend is %" PRIu64, dev->max_queues);
diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c
index 75aa7d6..df91e45 100644
--- a/hw/virtio/virtio-mem.c
+++ b/hw/virtio/virtio-mem.c
@@ -145,7 +145,173 @@ static bool virtio_mem_is_busy(void)
return migration_in_incoming_postcopy() || !migration_is_idle();
}
-static bool virtio_mem_test_bitmap(VirtIOMEM *vmem, uint64_t start_gpa,
+typedef int (*virtio_mem_range_cb)(const VirtIOMEM *vmem, void *arg,
+ uint64_t offset, uint64_t size);
+
+static int virtio_mem_for_each_unplugged_range(const VirtIOMEM *vmem, void *arg,
+ virtio_mem_range_cb cb)
+{
+ unsigned long first_zero_bit, last_zero_bit;
+ uint64_t offset, size;
+ int ret = 0;
+
+ first_zero_bit = find_first_zero_bit(vmem->bitmap, vmem->bitmap_size);
+ while (first_zero_bit < vmem->bitmap_size) {
+ offset = first_zero_bit * vmem->block_size;
+ last_zero_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
+ first_zero_bit + 1) - 1;
+ size = (last_zero_bit - first_zero_bit + 1) * vmem->block_size;
+
+ ret = cb(vmem, arg, offset, size);
+ if (ret) {
+ break;
+ }
+ first_zero_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
+ last_zero_bit + 2);
+ }
+ return ret;
+}
+
+/*
+ * Adjust the memory section to cover the intersection with the given range.
+ *
+ * Returns false if the intersection is empty, otherwise returns true.
+ */
+static bool virito_mem_intersect_memory_section(MemoryRegionSection *s,
+ uint64_t offset, uint64_t size)
+{
+ uint64_t start = MAX(s->offset_within_region, offset);
+ uint64_t end = MIN(s->offset_within_region + int128_get64(s->size),
+ offset + size);
+
+ if (end <= start) {
+ return false;
+ }
+
+ s->offset_within_address_space += start - s->offset_within_region;
+ s->offset_within_region = start;
+ s->size = int128_make64(end - start);
+ return true;
+}
+
+typedef int (*virtio_mem_section_cb)(MemoryRegionSection *s, void *arg);
+
+static int virtio_mem_for_each_plugged_section(const VirtIOMEM *vmem,
+ MemoryRegionSection *s,
+ void *arg,
+ virtio_mem_section_cb cb)
+{
+ unsigned long first_bit, last_bit;
+ uint64_t offset, size;
+ int ret = 0;
+
+ first_bit = s->offset_within_region / vmem->bitmap_size;
+ first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, first_bit);
+ while (first_bit < vmem->bitmap_size) {
+ MemoryRegionSection tmp = *s;
+
+ offset = first_bit * vmem->block_size;
+ last_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
+ first_bit + 1) - 1;
+ size = (last_bit - first_bit + 1) * vmem->block_size;
+
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ break;
+ }
+ ret = cb(&tmp, arg);
+ if (ret) {
+ break;
+ }
+ first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
+ last_bit + 2);
+ }
+ return ret;
+}
+
+static int virtio_mem_notify_populate_cb(MemoryRegionSection *s, void *arg)
+{
+ RamDiscardListener *rdl = arg;
+
+ return rdl->notify_populate(rdl, s);
+}
+
+static int virtio_mem_notify_discard_cb(MemoryRegionSection *s, void *arg)
+{
+ RamDiscardListener *rdl = arg;
+
+ rdl->notify_discard(rdl, s);
+ return 0;
+}
+
+static void virtio_mem_notify_unplug(VirtIOMEM *vmem, uint64_t offset,
+ uint64_t size)
+{
+ RamDiscardListener *rdl;
+
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ MemoryRegionSection tmp = *rdl->section;
+
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ continue;
+ }
+ rdl->notify_discard(rdl, &tmp);
+ }
+}
+
+static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset,
+ uint64_t size)
+{
+ RamDiscardListener *rdl, *rdl2;
+ int ret = 0;
+
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ MemoryRegionSection tmp = *rdl->section;
+
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ continue;
+ }
+ ret = rdl->notify_populate(rdl, &tmp);
+ if (ret) {
+ break;
+ }
+ }
+
+ if (ret) {
+ /* Notify all already-notified listeners. */
+ QLIST_FOREACH(rdl2, &vmem->rdl_list, next) {
+ MemoryRegionSection tmp = *rdl->section;
+
+ if (rdl2 == rdl) {
+ break;
+ }
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ continue;
+ }
+ rdl2->notify_discard(rdl2, &tmp);
+ }
+ }
+ return ret;
+}
+
+static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem)
+{
+ RamDiscardListener *rdl;
+
+ if (!vmem->size) {
+ return;
+ }
+
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ if (rdl->double_discard_supported) {
+ rdl->notify_discard(rdl, rdl->section);
+ } else {
+ virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_discard_cb);
+ }
+ }
+}
+
+static bool virtio_mem_test_bitmap(const VirtIOMEM *vmem, uint64_t start_gpa,
uint64_t size, bool plugged)
{
const unsigned long first_bit = (start_gpa - vmem->addr) / vmem->block_size;
@@ -198,7 +364,8 @@ static void virtio_mem_send_response_simple(VirtIOMEM *vmem,
virtio_mem_send_response(vmem, elem, &resp);
}
-static bool virtio_mem_valid_range(VirtIOMEM *vmem, uint64_t gpa, uint64_t size)
+static bool virtio_mem_valid_range(const VirtIOMEM *vmem, uint64_t gpa,
+ uint64_t size)
{
if (!QEMU_IS_ALIGNED(gpa, vmem->block_size)) {
return false;
@@ -219,19 +386,21 @@ static int virtio_mem_set_block_state(VirtIOMEM *vmem, uint64_t start_gpa,
uint64_t size, bool plug)
{
const uint64_t offset = start_gpa - vmem->addr;
- int ret;
+ RAMBlock *rb = vmem->memdev->mr.ram_block;
if (virtio_mem_is_busy()) {
return -EBUSY;
}
if (!plug) {
- ret = ram_block_discard_range(vmem->memdev->mr.ram_block, offset, size);
- if (ret) {
- error_report("Unexpected error discarding RAM: %s",
- strerror(-ret));
+ if (ram_block_discard_range(rb, offset, size)) {
return -EBUSY;
}
+ virtio_mem_notify_unplug(vmem, offset, size);
+ } else if (virtio_mem_notify_plug(vmem, offset, size)) {
+ /* Could be a mapping attempt resulted in memory getting populated. */
+ ram_block_discard_range(vmem->memdev->mr.ram_block, offset, size);
+ return -EBUSY;
}
virtio_mem_set_bitmap(vmem, start_gpa, size, plug);
return 0;
@@ -318,17 +487,16 @@ static void virtio_mem_resize_usable_region(VirtIOMEM *vmem,
static int virtio_mem_unplug_all(VirtIOMEM *vmem)
{
RAMBlock *rb = vmem->memdev->mr.ram_block;
- int ret;
if (virtio_mem_is_busy()) {
return -EBUSY;
}
- ret = ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb));
- if (ret) {
- error_report("Unexpected error discarding RAM: %s", strerror(-ret));
+ if (ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb))) {
return -EBUSY;
}
+ virtio_mem_notify_unplug_all(vmem);
+
bitmap_clear(vmem->bitmap, 0, vmem->bitmap_size);
if (vmem->size) {
vmem->size = 0;
@@ -551,7 +719,7 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp)
return;
}
- if (ram_block_discard_require(true)) {
+ if (ram_block_coordinated_discard_require(true)) {
error_setg(errp, "Discarding RAM is disabled");
return;
}
@@ -559,7 +727,7 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp)
ret = ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb));
if (ret) {
error_setg_errno(errp, -ret, "Unexpected error discarding RAM");
- ram_block_discard_require(false);
+ ram_block_coordinated_discard_require(false);
return;
}
@@ -577,6 +745,13 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp)
vmstate_register_ram(&vmem->memdev->mr, DEVICE(vmem));
qemu_register_reset(virtio_mem_system_reset, vmem);
precopy_add_notifier(&vmem->precopy_notifier);
+
+ /*
+ * Set ourselves as RamDiscardManager before the plug handler maps the
+ * memory region and exposes it via an address space.
+ */
+ memory_region_set_ram_discard_manager(&vmem->memdev->mr,
+ RAM_DISCARD_MANAGER(vmem));
}
static void virtio_mem_device_unrealize(DeviceState *dev)
@@ -584,6 +759,11 @@ static void virtio_mem_device_unrealize(DeviceState *dev)
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtIOMEM *vmem = VIRTIO_MEM(dev);
+ /*
+ * The unplug handler unmapped the memory region, it cannot be
+ * found via an address space anymore. Unset ourselves.
+ */
+ memory_region_set_ram_discard_manager(&vmem->memdev->mr, NULL);
precopy_remove_notifier(&vmem->precopy_notifier);
qemu_unregister_reset(virtio_mem_system_reset, vmem);
vmstate_unregister_ram(&vmem->memdev->mr, DEVICE(vmem));
@@ -591,43 +771,47 @@ static void virtio_mem_device_unrealize(DeviceState *dev)
virtio_del_queue(vdev, 0);
virtio_cleanup(vdev);
g_free(vmem->bitmap);
- ram_block_discard_require(false);
+ ram_block_coordinated_discard_require(false);
}
-static int virtio_mem_restore_unplugged(VirtIOMEM *vmem)
+static int virtio_mem_discard_range_cb(const VirtIOMEM *vmem, void *arg,
+ uint64_t offset, uint64_t size)
{
RAMBlock *rb = vmem->memdev->mr.ram_block;
- unsigned long first_zero_bit, last_zero_bit;
- uint64_t offset, length;
- int ret;
- /* Find consecutive unplugged blocks and discard the consecutive range. */
- first_zero_bit = find_first_zero_bit(vmem->bitmap, vmem->bitmap_size);
- while (first_zero_bit < vmem->bitmap_size) {
- offset = first_zero_bit * vmem->block_size;
- last_zero_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
- first_zero_bit + 1) - 1;
- length = (last_zero_bit - first_zero_bit + 1) * vmem->block_size;
+ return ram_block_discard_range(rb, offset, size) ? -EINVAL : 0;
+}
- ret = ram_block_discard_range(rb, offset, length);
- if (ret) {
- error_report("Unexpected error discarding RAM: %s",
- strerror(-ret));
- return -EINVAL;
- }
- first_zero_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
- last_zero_bit + 2);
- }
- return 0;
+static int virtio_mem_restore_unplugged(VirtIOMEM *vmem)
+{
+ /* Make sure all memory is really discarded after migration. */
+ return virtio_mem_for_each_unplugged_range(vmem, NULL,
+ virtio_mem_discard_range_cb);
}
static int virtio_mem_post_load(void *opaque, int version_id)
{
+ VirtIOMEM *vmem = VIRTIO_MEM(opaque);
+ RamDiscardListener *rdl;
+ int ret;
+
+ /*
+ * We started out with all memory discarded and our memory region is mapped
+ * into an address space. Replay, now that we updated the bitmap.
+ */
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_populate_cb);
+ if (ret) {
+ return ret;
+ }
+ }
+
if (migration_in_incoming_postcopy()) {
return 0;
}
- return virtio_mem_restore_unplugged(VIRTIO_MEM(opaque));
+ return virtio_mem_restore_unplugged(vmem);
}
typedef struct VirtIOMEMMigSanityChecks {
@@ -702,6 +886,7 @@ static const VMStateDescription vmstate_virtio_mem_device = {
.name = "virtio-mem-device",
.minimum_version_id = 1,
.version_id = 1,
+ .priority = MIG_PRI_VIRTIO_MEM,
.post_load = virtio_mem_post_load,
.fields = (VMStateField[]) {
VMSTATE_WITH_TMP(VirtIOMEM, VirtIOMEMMigSanityChecks,
@@ -872,28 +1057,19 @@ static void virtio_mem_set_block_size(Object *obj, Visitor *v, const char *name,
vmem->block_size = value;
}
-static void virtio_mem_precopy_exclude_unplugged(VirtIOMEM *vmem)
+static int virtio_mem_precopy_exclude_range_cb(const VirtIOMEM *vmem, void *arg,
+ uint64_t offset, uint64_t size)
{
void * const host = qemu_ram_get_host_addr(vmem->memdev->mr.ram_block);
- unsigned long first_zero_bit, last_zero_bit;
- uint64_t offset, length;
- /*
- * Find consecutive unplugged blocks and exclude them from migration.
- *
- * Note: Blocks cannot get (un)plugged during precopy, no locking needed.
- */
- first_zero_bit = find_first_zero_bit(vmem->bitmap, vmem->bitmap_size);
- while (first_zero_bit < vmem->bitmap_size) {
- offset = first_zero_bit * vmem->block_size;
- last_zero_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
- first_zero_bit + 1) - 1;
- length = (last_zero_bit - first_zero_bit + 1) * vmem->block_size;
+ qemu_guest_free_page_hint(host + offset, size);
+ return 0;
+}
- qemu_guest_free_page_hint(host + offset, length);
- first_zero_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
- last_zero_bit + 2);
- }
+static void virtio_mem_precopy_exclude_unplugged(VirtIOMEM *vmem)
+{
+ virtio_mem_for_each_unplugged_range(vmem, NULL,
+ virtio_mem_precopy_exclude_range_cb);
}
static int virtio_mem_precopy_notify(NotifierWithReturn *n, void *data)
@@ -918,6 +1094,7 @@ static void virtio_mem_instance_init(Object *obj)
notifier_list_init(&vmem->size_change_notifiers);
vmem->precopy_notifier.notify = virtio_mem_precopy_notify;
+ QLIST_INIT(&vmem->rdl_list);
object_property_add(obj, VIRTIO_MEM_SIZE_PROP, "size", virtio_mem_get_size,
NULL, NULL, NULL);
@@ -937,11 +1114,107 @@ static Property virtio_mem_properties[] = {
DEFINE_PROP_END_OF_LIST(),
};
+static uint64_t virtio_mem_rdm_get_min_granularity(const RamDiscardManager *rdm,
+ const MemoryRegion *mr)
+{
+ const VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+
+ g_assert(mr == &vmem->memdev->mr);
+ return vmem->block_size;
+}
+
+static bool virtio_mem_rdm_is_populated(const RamDiscardManager *rdm,
+ const MemoryRegionSection *s)
+{
+ const VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+ uint64_t start_gpa = vmem->addr + s->offset_within_region;
+ uint64_t end_gpa = start_gpa + int128_get64(s->size);
+
+ g_assert(s->mr == &vmem->memdev->mr);
+
+ start_gpa = QEMU_ALIGN_DOWN(start_gpa, vmem->block_size);
+ end_gpa = QEMU_ALIGN_UP(end_gpa, vmem->block_size);
+
+ if (!virtio_mem_valid_range(vmem, start_gpa, end_gpa - start_gpa)) {
+ return false;
+ }
+
+ return virtio_mem_test_bitmap(vmem, start_gpa, end_gpa - start_gpa, true);
+}
+
+struct VirtIOMEMReplayData {
+ void *fn;
+ void *opaque;
+};
+
+static int virtio_mem_rdm_replay_populated_cb(MemoryRegionSection *s, void *arg)
+{
+ struct VirtIOMEMReplayData *data = arg;
+
+ return ((ReplayRamPopulate)data->fn)(s, data->opaque);
+}
+
+static int virtio_mem_rdm_replay_populated(const RamDiscardManager *rdm,
+ MemoryRegionSection *s,
+ ReplayRamPopulate replay_fn,
+ void *opaque)
+{
+ const VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+ struct VirtIOMEMReplayData data = {
+ .fn = replay_fn,
+ .opaque = opaque,
+ };
+
+ g_assert(s->mr == &vmem->memdev->mr);
+ return virtio_mem_for_each_plugged_section(vmem, s, &data,
+ virtio_mem_rdm_replay_populated_cb);
+}
+
+static void virtio_mem_rdm_register_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl,
+ MemoryRegionSection *s)
+{
+ VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+ int ret;
+
+ g_assert(s->mr == &vmem->memdev->mr);
+ rdl->section = memory_region_section_new_copy(s);
+
+ QLIST_INSERT_HEAD(&vmem->rdl_list, rdl, next);
+ ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_populate_cb);
+ if (ret) {
+ error_report("%s: Replaying plugged ranges failed: %s", __func__,
+ strerror(-ret));
+ }
+}
+
+static void virtio_mem_rdm_unregister_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl)
+{
+ VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+
+ g_assert(rdl->section->mr == &vmem->memdev->mr);
+ if (vmem->size) {
+ if (rdl->double_discard_supported) {
+ rdl->notify_discard(rdl, rdl->section);
+ } else {
+ virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_discard_cb);
+ }
+ }
+
+ memory_region_section_free_copy(rdl->section);
+ rdl->section = NULL;
+ QLIST_REMOVE(rdl, next);
+}
+
static void virtio_mem_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
VirtIOMEMClass *vmc = VIRTIO_MEM_CLASS(klass);
+ RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass);
device_class_set_props(dc, virtio_mem_properties);
dc->vmsd = &vmstate_virtio_mem;
@@ -957,6 +1230,12 @@ static void virtio_mem_class_init(ObjectClass *klass, void *data)
vmc->get_memory_region = virtio_mem_get_memory_region;
vmc->add_size_change_notifier = virtio_mem_add_size_change_notifier;
vmc->remove_size_change_notifier = virtio_mem_remove_size_change_notifier;
+
+ rdmc->get_min_granularity = virtio_mem_rdm_get_min_granularity;
+ rdmc->is_populated = virtio_mem_rdm_is_populated;
+ rdmc->replay_populated = virtio_mem_rdm_replay_populated;
+ rdmc->register_listener = virtio_mem_rdm_register_listener;
+ rdmc->unregister_listener = virtio_mem_rdm_unregister_listener;
}
static const TypeInfo virtio_mem_info = {
@@ -966,6 +1245,10 @@ static const TypeInfo virtio_mem_info = {
.instance_init = virtio_mem_instance_init,
.class_init = virtio_mem_class_init,
.class_size = sizeof(VirtIOMEMClass),
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_RAM_DISCARD_MANAGER },
+ { }
+ },
};
static void virtio_register_types(void)
diff --git a/include/block/block.h b/include/block/block.h
index 7ec77ec..3477290 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -386,7 +386,10 @@ BlockDriverState *bdrv_new_open_driver(BlockDriver *drv, const char *node_name,
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
BlockDriverState *bs, QDict *options,
bool keep_old_opts);
+void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue);
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp);
+int bdrv_reopen(BlockDriverState *bs, QDict *opts, bool keep_old_opts,
+ Error **errp);
int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only,
Error **errp);
int bdrv_pwrite_zeroes(BdrvChild *child, int64_t offset,
diff --git a/include/exec/memory.h b/include/exec/memory.h
index b116f7c..c3d417d 100644
--- a/include/exec/memory.h
+++ b/include/exec/memory.h
@@ -42,6 +42,12 @@ typedef struct IOMMUMemoryRegionClass IOMMUMemoryRegionClass;
DECLARE_OBJ_CHECKERS(IOMMUMemoryRegion, IOMMUMemoryRegionClass,
IOMMU_MEMORY_REGION, TYPE_IOMMU_MEMORY_REGION)
+#define TYPE_RAM_DISCARD_MANAGER "qemu:ram-discard-manager"
+typedef struct RamDiscardManagerClass RamDiscardManagerClass;
+typedef struct RamDiscardManager RamDiscardManager;
+DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass,
+ RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER);
+
#ifdef CONFIG_FUZZ
void fuzz_dma_read_cb(size_t addr,
size_t len,
@@ -65,6 +71,28 @@ struct ReservedRegion {
unsigned type;
};
+/**
+ * struct MemoryRegionSection: describes a fragment of a #MemoryRegion
+ *
+ * @mr: the region, or %NULL if empty
+ * @fv: the flat view of the address space the region is mapped in
+ * @offset_within_region: the beginning of the section, relative to @mr's start
+ * @size: the size of the section; will not exceed @mr's boundaries
+ * @offset_within_address_space: the address of the first byte of the section
+ * relative to the region's address space
+ * @readonly: writes to this section are ignored
+ * @nonvolatile: this section is non-volatile
+ */
+struct MemoryRegionSection {
+ Int128 size;
+ MemoryRegion *mr;
+ FlatView *fv;
+ hwaddr offset_within_region;
+ hwaddr offset_within_address_space;
+ bool readonly;
+ bool nonvolatile;
+};
+
typedef struct IOMMUTLBEntry IOMMUTLBEntry;
/* See address_space_translate: bit 0 is read, bit 1 is write. */
@@ -448,6 +476,206 @@ struct IOMMUMemoryRegionClass {
Error **errp);
};
+typedef struct RamDiscardListener RamDiscardListener;
+typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl,
+ MemoryRegionSection *section);
+typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl,
+ MemoryRegionSection *section);
+
+struct RamDiscardListener {
+ /*
+ * @notify_populate:
+ *
+ * Notification that previously discarded memory is about to get populated.
+ * Listeners are able to object. If any listener objects, already
+ * successfully notified listeners are notified about a discard again.
+ *
+ * @rdl: the #RamDiscardListener getting notified
+ * @section: the #MemoryRegionSection to get populated. The section
+ * is aligned within the memory region to the minimum granularity
+ * unless it would exceed the registered section.
+ *
+ * Returns 0 on success. If the notification is rejected by the listener,
+ * an error is returned.
+ */
+ NotifyRamPopulate notify_populate;
+
+ /*
+ * @notify_discard:
+ *
+ * Notification that previously populated memory was discarded successfully
+ * and listeners should drop all references to such memory and prevent
+ * new population (e.g., unmap).
+ *
+ * @rdl: the #RamDiscardListener getting notified
+ * @section: the #MemoryRegionSection to get populated. The section
+ * is aligned within the memory region to the minimum granularity
+ * unless it would exceed the registered section.
+ */
+ NotifyRamDiscard notify_discard;
+
+ /*
+ * @double_discard_supported:
+ *
+ * The listener suppors getting @notify_discard notifications that span
+ * already discarded parts.
+ */
+ bool double_discard_supported;
+
+ MemoryRegionSection *section;
+ QLIST_ENTRY(RamDiscardListener) next;
+};
+
+static inline void ram_discard_listener_init(RamDiscardListener *rdl,
+ NotifyRamPopulate populate_fn,
+ NotifyRamDiscard discard_fn,
+ bool double_discard_supported)
+{
+ rdl->notify_populate = populate_fn;
+ rdl->notify_discard = discard_fn;
+ rdl->double_discard_supported = double_discard_supported;
+}
+
+typedef int (*ReplayRamPopulate)(MemoryRegionSection *section, void *opaque);
+
+/*
+ * RamDiscardManagerClass:
+ *
+ * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion
+ * regions are currently populated to be used/accessed by the VM, notifying
+ * after parts were discarded (freeing up memory) and before parts will be
+ * populated (consuming memory), to be used/acessed by the VM.
+ *
+ * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the
+ * #MemoryRegion isn't mapped yet; it cannot change while the #MemoryRegion is
+ * mapped.
+ *
+ * The #RamDiscardManager is intended to be used by technologies that are
+ * incompatible with discarding of RAM (e.g., VFIO, which may pin all
+ * memory inside a #MemoryRegion), and require proper coordination to only
+ * map the currently populated parts, to hinder parts that are expected to
+ * remain discarded from silently getting populated and consuming memory.
+ * Technologies that support discarding of RAM don't have to bother and can
+ * simply map the whole #MemoryRegion.
+ *
+ * An example #RamDiscardManager is virtio-mem, which logically (un)plugs
+ * memory within an assigned RAM #MemoryRegion, coordinated with the VM.
+ * Logically unplugging memory consists of discarding RAM. The VM agreed to not
+ * access unplugged (discarded) memory - especially via DMA. virtio-mem will
+ * properly coordinate with listeners before memory is plugged (populated),
+ * and after memory is unplugged (discarded).
+ *
+ * Listeners are called in multiples of the minimum granularity (unless it
+ * would exceed the registered range) and changes are aligned to the minimum
+ * granularity within the #MemoryRegion. Listeners have to prepare for memory
+ * becomming discarded in a different granularity than it was populated and the
+ * other way around.
+ */
+struct RamDiscardManagerClass {
+ /* private */
+ InterfaceClass parent_class;
+
+ /* public */
+
+ /**
+ * @get_min_granularity:
+ *
+ * Get the minimum granularity in which listeners will get notified
+ * about changes within the #MemoryRegion via the #RamDiscardManager.
+ *
+ * @rdm: the #RamDiscardManager
+ * @mr: the #MemoryRegion
+ *
+ * Returns the minimum granularity.
+ */
+ uint64_t (*get_min_granularity)(const RamDiscardManager *rdm,
+ const MemoryRegion *mr);
+
+ /**
+ * @is_populated:
+ *
+ * Check whether the given #MemoryRegionSection is completely populated
+ * (i.e., no parts are currently discarded) via the #RamDiscardManager.
+ * There are no alignment requirements.
+ *
+ * @rdm: the #RamDiscardManager
+ * @section: the #MemoryRegionSection
+ *
+ * Returns whether the given range is completely populated.
+ */
+ bool (*is_populated)(const RamDiscardManager *rdm,
+ const MemoryRegionSection *section);
+
+ /**
+ * @replay_populated:
+ *
+ * Call the #ReplayRamPopulate callback for all populated parts within the
+ * #MemoryRegionSection via the #RamDiscardManager.
+ *
+ * In case any call fails, no further calls are made.
+ *
+ * @rdm: the #RamDiscardManager
+ * @section: the #MemoryRegionSection
+ * @replay_fn: the #ReplayRamPopulate callback
+ * @opaque: pointer to forward to the callback
+ *
+ * Returns 0 on success, or a negative error if any notification failed.
+ */
+ int (*replay_populated)(const RamDiscardManager *rdm,
+ MemoryRegionSection *section,
+ ReplayRamPopulate replay_fn, void *opaque);
+
+ /**
+ * @register_listener:
+ *
+ * Register a #RamDiscardListener for the given #MemoryRegionSection and
+ * immediately notify the #RamDiscardListener about all populated parts
+ * within the #MemoryRegionSection via the #RamDiscardManager.
+ *
+ * In case any notification fails, no further notifications are triggered
+ * and an error is logged.
+ *
+ * @rdm: the #RamDiscardManager
+ * @rdl: the #RamDiscardListener
+ * @section: the #MemoryRegionSection
+ */
+ void (*register_listener)(RamDiscardManager *rdm,
+ RamDiscardListener *rdl,
+ MemoryRegionSection *section);
+
+ /**
+ * @unregister_listener:
+ *
+ * Unregister a previously registered #RamDiscardListener via the
+ * #RamDiscardManager after notifying the #RamDiscardListener about all
+ * populated parts becoming unpopulated within the registered
+ * #MemoryRegionSection.
+ *
+ * @rdm: the #RamDiscardManager
+ * @rdl: the #RamDiscardListener
+ */
+ void (*unregister_listener)(RamDiscardManager *rdm,
+ RamDiscardListener *rdl);
+};
+
+uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm,
+ const MemoryRegion *mr);
+
+bool ram_discard_manager_is_populated(const RamDiscardManager *rdm,
+ const MemoryRegionSection *section);
+
+int ram_discard_manager_replay_populated(const RamDiscardManager *rdm,
+ MemoryRegionSection *section,
+ ReplayRamPopulate replay_fn,
+ void *opaque);
+
+void ram_discard_manager_register_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl,
+ MemoryRegionSection *section);
+
+void ram_discard_manager_unregister_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl);
+
typedef struct CoalescedMemoryRange CoalescedMemoryRange;
typedef struct MemoryRegionIoeventfd MemoryRegionIoeventfd;
@@ -494,6 +722,7 @@ struct MemoryRegion {
const char *name;
unsigned ioeventfd_nb;
MemoryRegionIoeventfd *ioeventfds;
+ RamDiscardManager *rdm; /* Only for RAM */
};
struct IOMMUMemoryRegion {
@@ -825,28 +1054,6 @@ typedef bool (*flatview_cb)(Int128 start,
*/
void flatview_for_each_range(FlatView *fv, flatview_cb cb, void *opaque);
-/**
- * struct MemoryRegionSection: describes a fragment of a #MemoryRegion
- *
- * @mr: the region, or %NULL if empty
- * @fv: the flat view of the address space the region is mapped in
- * @offset_within_region: the beginning of the section, relative to @mr's start
- * @size: the size of the section; will not exceed @mr's boundaries
- * @offset_within_address_space: the address of the first byte of the section
- * relative to the region's address space
- * @readonly: writes to this section are ignored
- * @nonvolatile: this section is non-volatile
- */
-struct MemoryRegionSection {
- Int128 size;
- MemoryRegion *mr;
- FlatView *fv;
- hwaddr offset_within_region;
- hwaddr offset_within_address_space;
- bool readonly;
- bool nonvolatile;
-};
-
static inline bool MemoryRegionSection_eq(MemoryRegionSection *a,
MemoryRegionSection *b)
{
@@ -860,6 +1067,26 @@ static inline bool MemoryRegionSection_eq(MemoryRegionSection *a,
}
/**
+ * memory_region_section_new_copy: Copy a memory region section
+ *
+ * Allocate memory for a new copy, copy the memory region section, and
+ * properly take a reference on all relevant members.
+ *
+ * @s: the #MemoryRegionSection to copy
+ */
+MemoryRegionSection *memory_region_section_new_copy(MemoryRegionSection *s);
+
+/**
+ * memory_region_section_new_copy: Free a copied memory region section
+ *
+ * Free a copy of a memory section created via memory_region_section_new_copy().
+ * properly dropping references on all relevant members.
+ *
+ * @s: the #MemoryRegionSection to copy
+ */
+void memory_region_section_free_copy(MemoryRegionSection *s);
+
+/**
* memory_region_init: Initialize a memory region
*
* The region typically acts as a container for other memory regions. Use
@@ -2024,6 +2251,41 @@ bool memory_region_present(MemoryRegion *container, hwaddr addr);
bool memory_region_is_mapped(MemoryRegion *mr);
/**
+ * memory_region_get_ram_discard_manager: get the #RamDiscardManager for a
+ * #MemoryRegion
+ *
+ * The #RamDiscardManager cannot change while a memory region is mapped.
+ *
+ * @mr: the #MemoryRegion
+ */
+RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr);
+
+/**
+ * memory_region_has_ram_discard_manager: check whether a #MemoryRegion has a
+ * #RamDiscardManager assigned
+ *
+ * @mr: the #MemoryRegion
+ */
+static inline bool memory_region_has_ram_discard_manager(MemoryRegion *mr)
+{
+ return !!memory_region_get_ram_discard_manager(mr);
+}
+
+/**
+ * memory_region_set_ram_discard_manager: set the #RamDiscardManager for a
+ * #MemoryRegion
+ *
+ * This function must not be called for a mapped #MemoryRegion, a #MemoryRegion
+ * that does not cover RAM, or a #MemoryRegion that already has a
+ * #RamDiscardManager assigned.
+ *
+ * @mr: the #MemoryRegion
+ * @rdm: #RamDiscardManager to set
+ */
+void memory_region_set_ram_discard_manager(MemoryRegion *mr,
+ RamDiscardManager *rdm);
+
+/**
* memory_region_find: translate an address/size relative to a
* MemoryRegion into a #MemoryRegionSection.
*
@@ -2632,6 +2894,12 @@ static inline MemOp devend_memop(enum device_endian end)
int ram_block_discard_disable(bool state);
/*
+ * See ram_block_discard_disable(): only disable uncoordinated discards,
+ * keeping coordinated discards (via the RamDiscardManager) enabled.
+ */
+int ram_block_uncoordinated_discard_disable(bool state);
+
+/*
* Inhibit technologies that disable discarding of pages in RAM blocks.
*
* Returns 0 if successful. Returns -EBUSY if discards are already set to
@@ -2640,12 +2908,20 @@ int ram_block_discard_disable(bool state);
int ram_block_discard_require(bool state);
/*
- * Test if discarding of memory in ram blocks is disabled.
+ * See ram_block_discard_require(): only inhibit technologies that disable
+ * uncoordinated discarding of pages in RAM blocks, allowing co-existance with
+ * technologies that only inhibit uncoordinated discards (via the
+ * RamDiscardManager).
+ */
+int ram_block_coordinated_discard_require(bool state);
+
+/*
+ * Test if any discarding of memory in ram blocks is disabled.
*/
bool ram_block_discard_is_disabled(void);
/*
- * Test if discarding of memory in ram blocks is required to work reliably.
+ * Test if any discarding of memory in ram blocks is required to work reliably.
*/
bool ram_block_discard_is_required(void);
diff --git a/include/hw/misc/max111x.h b/include/hw/adc/max111x.h
index beff59c..beff59c 100644
--- a/include/hw/misc/max111x.h
+++ b/include/hw/adc/max111x.h
diff --git a/include/hw/misc/zynq-xadc.h b/include/hw/adc/zynq-xadc.h
index 2017b7a..2017b7a 100644
--- a/include/hw/misc/zynq-xadc.h
+++ b/include/hw/adc/zynq-xadc.h
diff --git a/include/hw/arm/stm32f100_soc.h b/include/hw/arm/stm32f100_soc.h
new file mode 100644
index 0000000..71bffcf
--- /dev/null
+++ b/include/hw/arm/stm32f100_soc.h
@@ -0,0 +1,57 @@
+/*
+ * STM32F100 SoC
+ *
+ * Copyright (c) 2021 Alexandre Iooss <erdnaxe@crans.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef HW_ARM_STM32F100_SOC_H
+#define HW_ARM_STM32F100_SOC_H
+
+#include "hw/char/stm32f2xx_usart.h"
+#include "hw/ssi/stm32f2xx_spi.h"
+#include "hw/arm/armv7m.h"
+#include "qom/object.h"
+
+#define TYPE_STM32F100_SOC "stm32f100-soc"
+OBJECT_DECLARE_SIMPLE_TYPE(STM32F100State, STM32F100_SOC)
+
+#define STM_NUM_USARTS 3
+#define STM_NUM_SPIS 2
+
+#define FLASH_BASE_ADDRESS 0x08000000
+#define FLASH_SIZE (128 * 1024)
+#define SRAM_BASE_ADDRESS 0x20000000
+#define SRAM_SIZE (8 * 1024)
+
+struct STM32F100State {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ char *cpu_type;
+
+ ARMv7MState armv7m;
+
+ STM32F2XXUsartState usart[STM_NUM_USARTS];
+ STM32F2XXSPIState spi[STM_NUM_SPIS];
+};
+
+#endif
diff --git a/include/hw/i2c/i2c.h b/include/hw/i2c/i2c.h
index ff4129e..5ca3b70 100644
--- a/include/hw/i2c/i2c.h
+++ b/include/hw/i2c/i2c.h
@@ -79,12 +79,44 @@ struct I2CBus {
};
I2CBus *i2c_init_bus(DeviceState *parent, const char *name);
-void i2c_set_slave_address(I2CSlave *dev, uint8_t address);
int i2c_bus_busy(I2CBus *bus);
-int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv);
+
+/**
+ * i2c_start_transfer: start a transfer on an I2C bus.
+ *
+ * @bus: #I2CBus to be used
+ * @address: address of the slave
+ * @is_recv: indicates the transfer direction
+ *
+ * When @is_recv is a known boolean constant, use the
+ * i2c_start_recv() or i2c_start_send() helper instead.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int i2c_start_transfer(I2CBus *bus, uint8_t address, bool is_recv);
+
+/**
+ * i2c_start_recv: start a 'receive' transfer on an I2C bus.
+ *
+ * @bus: #I2CBus to be used
+ * @address: address of the slave
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int i2c_start_recv(I2CBus *bus, uint8_t address);
+
+/**
+ * i2c_start_send: start a 'send' transfer on an I2C bus.
+ *
+ * @bus: #I2CBus to be used
+ * @address: address of the slave
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int i2c_start_send(I2CBus *bus, uint8_t address);
+
void i2c_end_transfer(I2CBus *bus);
void i2c_nack(I2CBus *bus);
-int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send);
int i2c_send(I2CBus *bus, uint8_t data);
uint8_t i2c_recv(I2CBus *bus);
bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
@@ -142,8 +174,12 @@ I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr);
*/
bool i2c_slave_realize_and_unref(I2CSlave *dev, I2CBus *bus, Error **errp);
-/* lm832x.c */
-void lm832x_key_event(DeviceState *dev, int key, int state);
+/**
+ * Set the I2C bus address of a slave device
+ * @dev: I2C slave device
+ * @address: I2C address of the slave when put on a bus
+ */
+void i2c_slave_set_address(I2CSlave *dev, uint8_t address);
extern const VMStateDescription vmstate_i2c_slave;
diff --git a/include/hw/i2c/pmbus_device.h b/include/hw/i2c/pmbus_device.h
new file mode 100644
index 0000000..62bd38c
--- /dev/null
+++ b/include/hw/i2c/pmbus_device.h
@@ -0,0 +1,517 @@
+/*
+ * QEMU PMBus device emulation
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_PMBUS_DEVICE_H
+#define HW_PMBUS_DEVICE_H
+
+#include "qemu/bitops.h"
+#include "hw/i2c/smbus_slave.h"
+
+enum pmbus_registers {
+ PMBUS_PAGE = 0x00, /* R/W byte */
+ PMBUS_OPERATION = 0x01, /* R/W byte */
+ PMBUS_ON_OFF_CONFIG = 0x02, /* R/W byte */
+ PMBUS_CLEAR_FAULTS = 0x03, /* Send Byte */
+ PMBUS_PHASE = 0x04, /* R/W byte */
+ PMBUS_PAGE_PLUS_WRITE = 0x05, /* Block Write-only */
+ PMBUS_PAGE_PLUS_READ = 0x06, /* Block Read-only */
+ PMBUS_WRITE_PROTECT = 0x10, /* R/W byte */
+ PMBUS_STORE_DEFAULT_ALL = 0x11, /* Send Byte */
+ PMBUS_RESTORE_DEFAULT_ALL = 0x12, /* Send Byte */
+ PMBUS_STORE_DEFAULT_CODE = 0x13, /* Write-only Byte */
+ PMBUS_RESTORE_DEFAULT_CODE = 0x14, /* Write-only Byte */
+ PMBUS_STORE_USER_ALL = 0x15, /* Send Byte */
+ PMBUS_RESTORE_USER_ALL = 0x16, /* Send Byte */
+ PMBUS_STORE_USER_CODE = 0x17, /* Write-only Byte */
+ PMBUS_RESTORE_USER_CODE = 0x18, /* Write-only Byte */
+ PMBUS_CAPABILITY = 0x19, /* Read-Only byte */
+ PMBUS_QUERY = 0x1A, /* Write-Only */
+ PMBUS_SMBALERT_MASK = 0x1B, /* Block read, Word write */
+ PMBUS_VOUT_MODE = 0x20, /* R/W byte */
+ PMBUS_VOUT_COMMAND = 0x21, /* R/W word */
+ PMBUS_VOUT_TRIM = 0x22, /* R/W word */
+ PMBUS_VOUT_CAL_OFFSET = 0x23, /* R/W word */
+ PMBUS_VOUT_MAX = 0x24, /* R/W word */
+ PMBUS_VOUT_MARGIN_HIGH = 0x25, /* R/W word */
+ PMBUS_VOUT_MARGIN_LOW = 0x26, /* R/W word */
+ PMBUS_VOUT_TRANSITION_RATE = 0x27, /* R/W word */
+ PMBUS_VOUT_DROOP = 0x28, /* R/W word */
+ PMBUS_VOUT_SCALE_LOOP = 0x29, /* R/W word */
+ PMBUS_VOUT_SCALE_MONITOR = 0x2A, /* R/W word */
+ PMBUS_COEFFICIENTS = 0x30, /* Read-only block 5 bytes */
+ PMBUS_POUT_MAX = 0x31, /* R/W word */
+ PMBUS_MAX_DUTY = 0x32, /* R/W word */
+ PMBUS_FREQUENCY_SWITCH = 0x33, /* R/W word */
+ PMBUS_VIN_ON = 0x35, /* R/W word */
+ PMBUS_VIN_OFF = 0x36, /* R/W word */
+ PMBUS_INTERLEAVE = 0x37, /* R/W word */
+ PMBUS_IOUT_CAL_GAIN = 0x38, /* R/W word */
+ PMBUS_IOUT_CAL_OFFSET = 0x39, /* R/W word */
+ PMBUS_FAN_CONFIG_1_2 = 0x3A, /* R/W byte */
+ PMBUS_FAN_COMMAND_1 = 0x3B, /* R/W word */
+ PMBUS_FAN_COMMAND_2 = 0x3C, /* R/W word */
+ PMBUS_FAN_CONFIG_3_4 = 0x3D, /* R/W byte */
+ PMBUS_FAN_COMMAND_3 = 0x3E, /* R/W word */
+ PMBUS_FAN_COMMAND_4 = 0x3F, /* R/W word */
+ PMBUS_VOUT_OV_FAULT_LIMIT = 0x40, /* R/W word */
+ PMBUS_VOUT_OV_FAULT_RESPONSE = 0x41, /* R/W byte */
+ PMBUS_VOUT_OV_WARN_LIMIT = 0x42, /* R/W word */
+ PMBUS_VOUT_UV_WARN_LIMIT = 0x43, /* R/W word */
+ PMBUS_VOUT_UV_FAULT_LIMIT = 0x44, /* R/W word */
+ PMBUS_VOUT_UV_FAULT_RESPONSE = 0x45, /* R/W byte */
+ PMBUS_IOUT_OC_FAULT_LIMIT = 0x46, /* R/W word */
+ PMBUS_IOUT_OC_FAULT_RESPONSE = 0x47, /* R/W byte */
+ PMBUS_IOUT_OC_LV_FAULT_LIMIT = 0x48, /* R/W word */
+ PMBUS_IOUT_OC_LV_FAULT_RESPONSE = 0x49, /* R/W byte */
+ PMBUS_IOUT_OC_WARN_LIMIT = 0x4A, /* R/W word */
+ PMBUS_IOUT_UC_FAULT_LIMIT = 0x4B, /* R/W word */
+ PMBUS_IOUT_UC_FAULT_RESPONSE = 0x4C, /* R/W byte */
+ PMBUS_OT_FAULT_LIMIT = 0x4F, /* R/W word */
+ PMBUS_OT_FAULT_RESPONSE = 0x50, /* R/W byte */
+ PMBUS_OT_WARN_LIMIT = 0x51, /* R/W word */
+ PMBUS_UT_WARN_LIMIT = 0x52, /* R/W word */
+ PMBUS_UT_FAULT_LIMIT = 0x53, /* R/W word */
+ PMBUS_UT_FAULT_RESPONSE = 0x54, /* R/W byte */
+ PMBUS_VIN_OV_FAULT_LIMIT = 0x55, /* R/W word */
+ PMBUS_VIN_OV_FAULT_RESPONSE = 0x56, /* R/W byte */
+ PMBUS_VIN_OV_WARN_LIMIT = 0x57, /* R/W word */
+ PMBUS_VIN_UV_WARN_LIMIT = 0x58, /* R/W word */
+ PMBUS_VIN_UV_FAULT_LIMIT = 0x59, /* R/W word */
+ PMBUS_VIN_UV_FAULT_RESPONSE = 0x5A, /* R/W byte */
+ PMBUS_IIN_OC_FAULT_LIMIT = 0x5B, /* R/W word */
+ PMBUS_IIN_OC_FAULT_RESPONSE = 0x5C, /* R/W byte */
+ PMBUS_IIN_OC_WARN_LIMIT = 0x5D, /* R/W word */
+ PMBUS_POWER_GOOD_ON = 0x5E, /* R/W word */
+ PMBUS_POWER_GOOD_OFF = 0x5F, /* R/W word */
+ PMBUS_TON_DELAY = 0x60, /* R/W word */
+ PMBUS_TON_RISE = 0x61, /* R/W word */
+ PMBUS_TON_MAX_FAULT_LIMIT = 0x62, /* R/W word */
+ PMBUS_TON_MAX_FAULT_RESPONSE = 0x63, /* R/W byte */
+ PMBUS_TOFF_DELAY = 0x64, /* R/W word */
+ PMBUS_TOFF_FALL = 0x65, /* R/W word */
+ PMBUS_TOFF_MAX_WARN_LIMIT = 0x66, /* R/W word */
+ PMBUS_POUT_OP_FAULT_LIMIT = 0x68, /* R/W word */
+ PMBUS_POUT_OP_FAULT_RESPONSE = 0x69, /* R/W byte */
+ PMBUS_POUT_OP_WARN_LIMIT = 0x6A, /* R/W word */
+ PMBUS_PIN_OP_WARN_LIMIT = 0x6B, /* R/W word */
+ PMBUS_STATUS_BYTE = 0x78, /* R/W byte */
+ PMBUS_STATUS_WORD = 0x79, /* R/W word */
+ PMBUS_STATUS_VOUT = 0x7A, /* R/W byte */
+ PMBUS_STATUS_IOUT = 0x7B, /* R/W byte */
+ PMBUS_STATUS_INPUT = 0x7C, /* R/W byte */
+ PMBUS_STATUS_TEMPERATURE = 0x7D, /* R/W byte */
+ PMBUS_STATUS_CML = 0x7E, /* R/W byte */
+ PMBUS_STATUS_OTHER = 0x7F, /* R/W byte */
+ PMBUS_STATUS_MFR_SPECIFIC = 0x80, /* R/W byte */
+ PMBUS_STATUS_FANS_1_2 = 0x81, /* R/W byte */
+ PMBUS_STATUS_FANS_3_4 = 0x82, /* R/W byte */
+ PMBUS_READ_EIN = 0x86, /* Read-Only block 5 bytes */
+ PMBUS_READ_EOUT = 0x87, /* Read-Only block 5 bytes */
+ PMBUS_READ_VIN = 0x88, /* Read-Only word */
+ PMBUS_READ_IIN = 0x89, /* Read-Only word */
+ PMBUS_READ_VCAP = 0x8A, /* Read-Only word */
+ PMBUS_READ_VOUT = 0x8B, /* Read-Only word */
+ PMBUS_READ_IOUT = 0x8C, /* Read-Only word */
+ PMBUS_READ_TEMPERATURE_1 = 0x8D, /* Read-Only word */
+ PMBUS_READ_TEMPERATURE_2 = 0x8E, /* Read-Only word */
+ PMBUS_READ_TEMPERATURE_3 = 0x8F, /* Read-Only word */
+ PMBUS_READ_FAN_SPEED_1 = 0x90, /* Read-Only word */
+ PMBUS_READ_FAN_SPEED_2 = 0x91, /* Read-Only word */
+ PMBUS_READ_FAN_SPEED_3 = 0x92, /* Read-Only word */
+ PMBUS_READ_FAN_SPEED_4 = 0x93, /* Read-Only word */
+ PMBUS_READ_DUTY_CYCLE = 0x94, /* Read-Only word */
+ PMBUS_READ_FREQUENCY = 0x95, /* Read-Only word */
+ PMBUS_READ_POUT = 0x96, /* Read-Only word */
+ PMBUS_READ_PIN = 0x97, /* Read-Only word */
+ PMBUS_REVISION = 0x98, /* Read-Only byte */
+ PMBUS_MFR_ID = 0x99, /* R/W block */
+ PMBUS_MFR_MODEL = 0x9A, /* R/W block */
+ PMBUS_MFR_REVISION = 0x9B, /* R/W block */
+ PMBUS_MFR_LOCATION = 0x9C, /* R/W block */
+ PMBUS_MFR_DATE = 0x9D, /* R/W block */
+ PMBUS_MFR_SERIAL = 0x9E, /* R/W block */
+ PMBUS_APP_PROFILE_SUPPORT = 0x9F, /* Read-Only block-read */
+ PMBUS_MFR_VIN_MIN = 0xA0, /* Read-Only word */
+ PMBUS_MFR_VIN_MAX = 0xA1, /* Read-Only word */
+ PMBUS_MFR_IIN_MAX = 0xA2, /* Read-Only word */
+ PMBUS_MFR_PIN_MAX = 0xA3, /* Read-Only word */
+ PMBUS_MFR_VOUT_MIN = 0xA4, /* Read-Only word */
+ PMBUS_MFR_VOUT_MAX = 0xA5, /* Read-Only word */
+ PMBUS_MFR_IOUT_MAX = 0xA6, /* Read-Only word */
+ PMBUS_MFR_POUT_MAX = 0xA7, /* Read-Only word */
+ PMBUS_MFR_TAMBIENT_MAX = 0xA8, /* Read-Only word */
+ PMBUS_MFR_TAMBIENT_MIN = 0xA9, /* Read-Only word */
+ PMBUS_MFR_EFFICIENCY_LL = 0xAA, /* Read-Only block 14 bytes */
+ PMBUS_MFR_EFFICIENCY_HL = 0xAB, /* Read-Only block 14 bytes */
+ PMBUS_MFR_PIN_ACCURACY = 0xAC, /* Read-Only byte */
+ PMBUS_IC_DEVICE_ID = 0xAD, /* Read-Only block-read */
+ PMBUS_IC_DEVICE_REV = 0xAE, /* Read-Only block-read */
+ PMBUS_MFR_MAX_TEMP_1 = 0xC0, /* R/W word */
+ PMBUS_MFR_MAX_TEMP_2 = 0xC1, /* R/W word */
+ PMBUS_MFR_MAX_TEMP_3 = 0xC2, /* R/W word */
+};
+
+/* STATUS_WORD */
+#define PB_STATUS_VOUT BIT(15)
+#define PB_STATUS_IOUT_POUT BIT(14)
+#define PB_STATUS_INPUT BIT(13)
+#define PB_STATUS_WORD_MFR BIT(12)
+#define PB_STATUS_POWER_GOOD_N BIT(11)
+#define PB_STATUS_FAN BIT(10)
+#define PB_STATUS_OTHER BIT(9)
+#define PB_STATUS_UNKNOWN BIT(8)
+/* STATUS_BYTE */
+#define PB_STATUS_BUSY BIT(7)
+#define PB_STATUS_OFF BIT(6)
+#define PB_STATUS_VOUT_OV BIT(5)
+#define PB_STATUS_IOUT_OC BIT(4)
+#define PB_STATUS_VIN_UV BIT(3)
+#define PB_STATUS_TEMPERATURE BIT(2)
+#define PB_STATUS_CML BIT(1)
+#define PB_STATUS_NONE_ABOVE BIT(0)
+
+/* STATUS_VOUT */
+#define PB_STATUS_VOUT_OV_FAULT BIT(7) /* Output Overvoltage Fault */
+#define PB_STATUS_VOUT_OV_WARN BIT(6) /* Output Overvoltage Warning */
+#define PB_STATUS_VOUT_UV_WARN BIT(5) /* Output Undervoltage Warning */
+#define PB_STATUS_VOUT_UV_FAULT BIT(4) /* Output Undervoltage Fault */
+#define PB_STATUS_VOUT_MAX BIT(3)
+#define PB_STATUS_VOUT_TON_MAX_FAULT BIT(2)
+#define PB_STATUS_VOUT_TOFF_MAX_WARN BIT(1)
+
+/* STATUS_IOUT */
+#define PB_STATUS_IOUT_OC_FAULT BIT(7) /* Output Overcurrent Fault */
+#define PB_STATUS_IOUT_OC_LV_FAULT BIT(6) /* Output OC And Low Voltage Fault */
+#define PB_STATUS_IOUT_OC_WARN BIT(5) /* Output Overcurrent Warning */
+#define PB_STATUS_IOUT_UC_FAULT BIT(4) /* Output Undercurrent Fault */
+#define PB_STATUS_CURR_SHARE BIT(3) /* Current Share Fault */
+#define PB_STATUS_PWR_LIM_MODE BIT(2) /* In Power Limiting Mode */
+#define PB_STATUS_POUT_OP_FAULT BIT(1) /* Output Overpower Fault */
+#define PB_STATUS_POUT_OP_WARN BIT(0) /* Output Overpower Warning */
+
+/* STATUS_INPUT */
+#define PB_STATUS_INPUT_VIN_OV_FAULT BIT(7) /* Input Overvoltage Fault */
+#define PB_STATUS_INPUT_VIN_OV_WARN BIT(6) /* Input Overvoltage Warning */
+#define PB_STATUS_INPUT_VIN_UV_WARN BIT(5) /* Input Undervoltage Warning */
+#define PB_STATUS_INPUT_VIN_UV_FAULT BIT(4) /* Input Undervoltage Fault */
+#define PB_STATUS_INPUT_IIN_OC_FAULT BIT(2) /* Input Overcurrent Fault */
+#define PB_STATUS_INPUT_IIN_OC_WARN BIT(1) /* Input Overcurrent Warning */
+#define PB_STATUS_INPUT_PIN_OP_WARN BIT(0) /* Input Overpower Warning */
+
+/* STATUS_TEMPERATURE */
+#define PB_STATUS_OT_FAULT BIT(7) /* Overtemperature Fault */
+#define PB_STATUS_OT_WARN BIT(6) /* Overtemperature Warning */
+#define PB_STATUS_UT_WARN BIT(5) /* Undertemperature Warning */
+#define PB_STATUS_UT_FAULT BIT(4) /* Undertemperature Fault */
+
+/* STATUS_CML */
+#define PB_CML_FAULT_INVALID_CMD BIT(7) /* Invalid/Unsupported Command */
+#define PB_CML_FAULT_INVALID_DATA BIT(6) /* Invalid/Unsupported Data */
+#define PB_CML_FAULT_PEC BIT(5) /* Packet Error Check Failed */
+#define PB_CML_FAULT_MEMORY BIT(4) /* Memory Fault Detected */
+#define PB_CML_FAULT_PROCESSOR BIT(3) /* Processor Fault Detected */
+#define PB_CML_FAULT_OTHER_COMM BIT(1) /* Other communication fault */
+#define PB_CML_FAULT_OTHER_MEM_LOGIC BIT(0) /* Other Memory Or Logic Fault */
+
+/* OPERATION*/
+#define PB_OP_ON BIT(7) /* PSU is switched on */
+#define PB_OP_MARGIN_HIGH BIT(5) /* PSU vout is set to margin high */
+#define PB_OP_MARGIN_LOW BIT(4) /* PSU vout is set to margin low */
+
+/* PAGES */
+#define PB_MAX_PAGES 0x1F
+#define PB_ALL_PAGES 0xFF
+
+#define TYPE_PMBUS_DEVICE "pmbus-device"
+OBJECT_DECLARE_TYPE(PMBusDevice, PMBusDeviceClass,
+ PMBUS_DEVICE)
+
+/* flags */
+#define PB_HAS_COEFFICIENTS BIT_ULL(9)
+#define PB_HAS_VIN BIT_ULL(10)
+#define PB_HAS_VOUT BIT_ULL(11)
+#define PB_HAS_VOUT_MARGIN BIT_ULL(12)
+#define PB_HAS_VIN_RATING BIT_ULL(13)
+#define PB_HAS_VOUT_RATING BIT_ULL(14)
+#define PB_HAS_VOUT_MODE BIT_ULL(15)
+#define PB_HAS_IOUT BIT_ULL(21)
+#define PB_HAS_IIN BIT_ULL(22)
+#define PB_HAS_IOUT_RATING BIT_ULL(23)
+#define PB_HAS_IIN_RATING BIT_ULL(24)
+#define PB_HAS_IOUT_GAIN BIT_ULL(25)
+#define PB_HAS_POUT BIT_ULL(30)
+#define PB_HAS_PIN BIT_ULL(31)
+#define PB_HAS_EIN BIT_ULL(32)
+#define PB_HAS_EOUT BIT_ULL(33)
+#define PB_HAS_POUT_RATING BIT_ULL(34)
+#define PB_HAS_PIN_RATING BIT_ULL(35)
+#define PB_HAS_TEMPERATURE BIT_ULL(40)
+#define PB_HAS_TEMP2 BIT_ULL(41)
+#define PB_HAS_TEMP3 BIT_ULL(42)
+#define PB_HAS_TEMP_RATING BIT_ULL(43)
+#define PB_HAS_MFR_INFO BIT_ULL(50)
+
+struct PMBusDeviceClass {
+ SMBusDeviceClass parent_class;
+ uint8_t device_num_pages;
+
+ /**
+ * Implement quick_cmd, receive byte, and write_data to support non-standard
+ * PMBus functionality
+ */
+ void (*quick_cmd)(PMBusDevice *dev, uint8_t read);
+ int (*write_data)(PMBusDevice *dev, const uint8_t *buf, uint8_t len);
+ uint8_t (*receive_byte)(PMBusDevice *dev);
+};
+
+/*
+ * According to the spec, each page may offer the full range of PMBus commands
+ * available for each output or non-PMBus device.
+ * Therefore, we can't assume that any registers will always be the same across
+ * all pages.
+ * The page 0xFF is intended for writes to all pages
+ */
+typedef struct PMBusPage {
+ uint64_t page_flags;
+
+ uint8_t page; /* R/W byte */
+ uint8_t operation; /* R/W byte */
+ uint8_t on_off_config; /* R/W byte */
+ uint8_t write_protect; /* R/W byte */
+ uint8_t phase; /* R/W byte */
+ uint8_t vout_mode; /* R/W byte */
+ uint16_t vout_command; /* R/W word */
+ uint16_t vout_trim; /* R/W word */
+ uint16_t vout_cal_offset; /* R/W word */
+ uint16_t vout_max; /* R/W word */
+ uint16_t vout_margin_high; /* R/W word */
+ uint16_t vout_margin_low; /* R/W word */
+ uint16_t vout_transition_rate; /* R/W word */
+ uint16_t vout_droop; /* R/W word */
+ uint16_t vout_scale_loop; /* R/W word */
+ uint16_t vout_scale_monitor; /* R/W word */
+ uint8_t coefficients[5]; /* Read-only block 5 bytes */
+ uint16_t pout_max; /* R/W word */
+ uint16_t max_duty; /* R/W word */
+ uint16_t frequency_switch; /* R/W word */
+ uint16_t vin_on; /* R/W word */
+ uint16_t vin_off; /* R/W word */
+ uint16_t iout_cal_gain; /* R/W word */
+ uint16_t iout_cal_offset; /* R/W word */
+ uint8_t fan_config_1_2; /* R/W byte */
+ uint16_t fan_command_1; /* R/W word */
+ uint16_t fan_command_2; /* R/W word */
+ uint8_t fan_config_3_4; /* R/W byte */
+ uint16_t fan_command_3; /* R/W word */
+ uint16_t fan_command_4; /* R/W word */
+ uint16_t vout_ov_fault_limit; /* R/W word */
+ uint8_t vout_ov_fault_response; /* R/W byte */
+ uint16_t vout_ov_warn_limit; /* R/W word */
+ uint16_t vout_uv_warn_limit; /* R/W word */
+ uint16_t vout_uv_fault_limit; /* R/W word */
+ uint8_t vout_uv_fault_response; /* R/W byte */
+ uint16_t iout_oc_fault_limit; /* R/W word */
+ uint8_t iout_oc_fault_response; /* R/W byte */
+ uint16_t iout_oc_lv_fault_limit; /* R/W word */
+ uint8_t iout_oc_lv_fault_response; /* R/W byte */
+ uint16_t iout_oc_warn_limit; /* R/W word */
+ uint16_t iout_uc_fault_limit; /* R/W word */
+ uint8_t iout_uc_fault_response; /* R/W byte */
+ uint16_t ot_fault_limit; /* R/W word */
+ uint8_t ot_fault_response; /* R/W byte */
+ uint16_t ot_warn_limit; /* R/W word */
+ uint16_t ut_warn_limit; /* R/W word */
+ uint16_t ut_fault_limit; /* R/W word */
+ uint8_t ut_fault_response; /* R/W byte */
+ uint16_t vin_ov_fault_limit; /* R/W word */
+ uint8_t vin_ov_fault_response; /* R/W byte */
+ uint16_t vin_ov_warn_limit; /* R/W word */
+ uint16_t vin_uv_warn_limit; /* R/W word */
+ uint16_t vin_uv_fault_limit; /* R/W word */
+ uint8_t vin_uv_fault_response; /* R/W byte */
+ uint16_t iin_oc_fault_limit; /* R/W word */
+ uint8_t iin_oc_fault_response; /* R/W byte */
+ uint16_t iin_oc_warn_limit; /* R/W word */
+ uint16_t power_good_on; /* R/W word */
+ uint16_t power_good_off; /* R/W word */
+ uint16_t ton_delay; /* R/W word */
+ uint16_t ton_rise; /* R/W word */
+ uint16_t ton_max_fault_limit; /* R/W word */
+ uint8_t ton_max_fault_response; /* R/W byte */
+ uint16_t toff_delay; /* R/W word */
+ uint16_t toff_fall; /* R/W word */
+ uint16_t toff_max_warn_limit; /* R/W word */
+ uint16_t pout_op_fault_limit; /* R/W word */
+ uint8_t pout_op_fault_response; /* R/W byte */
+ uint16_t pout_op_warn_limit; /* R/W word */
+ uint16_t pin_op_warn_limit; /* R/W word */
+ uint16_t status_word; /* R/W word */
+ uint8_t status_vout; /* R/W byte */
+ uint8_t status_iout; /* R/W byte */
+ uint8_t status_input; /* R/W byte */
+ uint8_t status_temperature; /* R/W byte */
+ uint8_t status_cml; /* R/W byte */
+ uint8_t status_other; /* R/W byte */
+ uint8_t status_mfr_specific; /* R/W byte */
+ uint8_t status_fans_1_2; /* R/W byte */
+ uint8_t status_fans_3_4; /* R/W byte */
+ uint8_t read_ein[5]; /* Read-Only block 5 bytes */
+ uint8_t read_eout[5]; /* Read-Only block 5 bytes */
+ uint16_t read_vin; /* Read-Only word */
+ uint16_t read_iin; /* Read-Only word */
+ uint16_t read_vcap; /* Read-Only word */
+ uint16_t read_vout; /* Read-Only word */
+ uint16_t read_iout; /* Read-Only word */
+ uint16_t read_temperature_1; /* Read-Only word */
+ uint16_t read_temperature_2; /* Read-Only word */
+ uint16_t read_temperature_3; /* Read-Only word */
+ uint16_t read_fan_speed_1; /* Read-Only word */
+ uint16_t read_fan_speed_2; /* Read-Only word */
+ uint16_t read_fan_speed_3; /* Read-Only word */
+ uint16_t read_fan_speed_4; /* Read-Only word */
+ uint16_t read_duty_cycle; /* Read-Only word */
+ uint16_t read_frequency; /* Read-Only word */
+ uint16_t read_pout; /* Read-Only word */
+ uint16_t read_pin; /* Read-Only word */
+ uint8_t revision; /* Read-Only byte */
+ const char *mfr_id; /* R/W block */
+ const char *mfr_model; /* R/W block */
+ const char *mfr_revision; /* R/W block */
+ const char *mfr_location; /* R/W block */
+ const char *mfr_date; /* R/W block */
+ const char *mfr_serial; /* R/W block */
+ const char *app_profile_support; /* Read-Only block-read */
+ uint16_t mfr_vin_min; /* Read-Only word */
+ uint16_t mfr_vin_max; /* Read-Only word */
+ uint16_t mfr_iin_max; /* Read-Only word */
+ uint16_t mfr_pin_max; /* Read-Only word */
+ uint16_t mfr_vout_min; /* Read-Only word */
+ uint16_t mfr_vout_max; /* Read-Only word */
+ uint16_t mfr_iout_max; /* Read-Only word */
+ uint16_t mfr_pout_max; /* Read-Only word */
+ uint16_t mfr_tambient_max; /* Read-Only word */
+ uint16_t mfr_tambient_min; /* Read-Only word */
+ uint8_t mfr_efficiency_ll[14]; /* Read-Only block 14 bytes */
+ uint8_t mfr_efficiency_hl[14]; /* Read-Only block 14 bytes */
+ uint8_t mfr_pin_accuracy; /* Read-Only byte */
+ uint16_t mfr_max_temp_1; /* R/W word */
+ uint16_t mfr_max_temp_2; /* R/W word */
+ uint16_t mfr_max_temp_3; /* R/W word */
+} PMBusPage;
+
+/* State */
+struct PMBusDevice {
+ SMBusDevice smb;
+
+ uint8_t num_pages;
+ uint8_t code;
+ uint8_t page;
+
+ /*
+ * PMBus registers are stored in a PMBusPage structure allocated by
+ * calling pmbus_pages_alloc()
+ */
+ PMBusPage *pages;
+ uint8_t capability;
+
+
+ int32_t in_buf_len;
+ uint8_t *in_buf;
+ int32_t out_buf_len;
+ uint8_t out_buf[SMBUS_DATA_MAX_LEN];
+};
+
+/**
+ * Direct mode coefficients
+ * @var m - mantissa
+ * @var b - offset
+ * @var R - exponent
+ */
+typedef struct PMBusCoefficients {
+ int32_t m; /* mantissa */
+ int64_t b; /* offset */
+ int32_t R; /* exponent */
+} PMBusCoefficients;
+
+/**
+ * Convert sensor values to direct mode format
+ *
+ * Y = (m * x - b) * 10^R
+ *
+ * @return uint32_t
+ */
+uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value);
+
+/**
+ * Convert direct mode formatted data into sensor reading
+ *
+ * X = (Y * 10^-R - b) / m
+ *
+ * @return uint32_t
+ */
+uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value);
+
+/**
+ * @brief Send a block of data over PMBus
+ * Assumes that the bytes in the block are already ordered correctly,
+ * also assumes the length has been prepended to the block if necessary
+ * | low_byte | ... | high_byte |
+ * @param state - maintains state of the PMBus device
+ * @param data - byte array to be sent by device
+ * @param len - number
+ */
+void pmbus_send(PMBusDevice *state, const uint8_t *data, uint16_t len);
+void pmbus_send8(PMBusDevice *state, uint8_t data);
+void pmbus_send16(PMBusDevice *state, uint16_t data);
+void pmbus_send32(PMBusDevice *state, uint32_t data);
+void pmbus_send64(PMBusDevice *state, uint64_t data);
+
+/**
+ * @brief Send a string over PMBus with length prepended.
+ * Length is calculated using str_len()
+ */
+void pmbus_send_string(PMBusDevice *state, const char *data);
+
+/**
+ * @brief Receive data over PMBus
+ * These methods help track how much data is being received over PMBus
+ * Log to GUEST_ERROR if too much or too little is sent.
+ */
+uint8_t pmbus_receive8(PMBusDevice *pmdev);
+uint16_t pmbus_receive16(PMBusDevice *pmdev);
+uint32_t pmbus_receive32(PMBusDevice *pmdev);
+uint64_t pmbus_receive64(PMBusDevice *pmdev);
+
+/**
+ * PMBus page config must be called before any page is first used.
+ * It will allocate memory for all the pages if needed.
+ * Passed in flags overwrite existing flags if any.
+ * @param page_index the page to which the flags are applied, setting page_index
+ * to 0xFF applies the passed in flags to all pages.
+ * @param flags
+ */
+int pmbus_page_config(PMBusDevice *pmdev, uint8_t page_index, uint64_t flags);
+
+/**
+ * Update the status registers when sensor values change.
+ * Useful if modifying sensors through qmp, this way status registers get
+ * updated
+ */
+void pmbus_check_limits(PMBusDevice *pmdev);
+
+extern const VMStateDescription vmstate_pmbus_device;
+
+#define VMSTATE_PMBUS_DEVICE(_field, _state) { \
+ .name = (stringify(_field)), \
+ .size = sizeof(PMBusDevice), \
+ .vmsd = &vmstate_pmbus_device, \
+ .flags = VMS_STRUCT, \
+ .offset = vmstate_offset_value(_state, _field, PMBusDevice), \
+}
+
+#endif
diff --git a/include/hw/input/lm832x.h b/include/hw/input/lm832x.h
new file mode 100644
index 0000000..2a58ccf
--- /dev/null
+++ b/include/hw/input/lm832x.h
@@ -0,0 +1,28 @@
+/*
+ * National Semiconductor LM8322/8323 GPIO keyboard & PWM chips.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HW_INPUT_LM832X
+#define HW_INPUT_LM832X
+
+#define TYPE_LM8323 "lm8323"
+
+void lm832x_key_event(DeviceState *dev, int key, int state);
+
+#endif
diff --git a/include/hw/ppc/spapr.h b/include/hw/ppc/spapr.h
index f05219f..637652a 100644
--- a/include/hw/ppc/spapr.h
+++ b/include/hw/ppc/spapr.h
@@ -12,6 +12,7 @@
#include "hw/ppc/spapr_xive.h" /* For SpaprXive */
#include "hw/ppc/xics.h" /* For ICSState */
#include "hw/ppc/spapr_tpm_proxy.h"
+#include "hw/ppc/vof.h"
struct SpaprVioBus;
struct SpaprPhbState;
@@ -74,8 +75,10 @@ typedef enum {
#define SPAPR_CAP_CCF_ASSIST 0x09
/* Implements PAPR FWNMI option */
#define SPAPR_CAP_FWNMI 0x0A
+/* Support H_RPT_INVALIDATE */
+#define SPAPR_CAP_RPT_INVALIDATE 0x0B
/* Num Caps */
-#define SPAPR_CAP_NUM (SPAPR_CAP_FWNMI + 1)
+#define SPAPR_CAP_NUM (SPAPR_CAP_RPT_INVALIDATE + 1)
/*
* Capability Values
@@ -180,6 +183,7 @@ struct SpaprMachineState {
uint64_t kernel_addr;
uint32_t initrd_base;
long initrd_size;
+ Vof *vof;
uint64_t rtc_offset; /* Now used only during incoming migration */
struct PPCTimebase tb;
bool has_graphics;
@@ -398,10 +402,13 @@ struct SpaprMachineState {
#define H_CPU_CHAR_THR_RECONF_TRIG PPC_BIT(6)
#define H_CPU_CHAR_CACHE_COUNT_DIS PPC_BIT(7)
#define H_CPU_CHAR_BCCTR_FLUSH_ASSIST PPC_BIT(9)
+
#define H_CPU_BEHAV_FAVOUR_SECURITY PPC_BIT(0)
#define H_CPU_BEHAV_L1D_FLUSH_PR PPC_BIT(1)
#define H_CPU_BEHAV_BNDS_CHK_SPEC_BAR PPC_BIT(2)
#define H_CPU_BEHAV_FLUSH_COUNT_CACHE PPC_BIT(5)
+#define H_CPU_BEHAV_NO_L1D_FLUSH_ENTRY PPC_BIT(7)
+#define H_CPU_BEHAV_NO_L1D_FLUSH_UACCESS PPC_BIT(8)
/* Each control block has to be on a 4K boundary */
#define H_CB_ALIGNMENT 4096
@@ -542,8 +549,9 @@ struct SpaprMachineState {
#define H_SCM_UNBIND_MEM 0x3F0
#define H_SCM_UNBIND_ALL 0x3FC
#define H_SCM_HEALTH 0x400
+#define H_RPT_INVALIDATE 0x448
-#define MAX_HCALL_OPCODE H_SCM_HEALTH
+#define MAX_HCALL_OPCODE H_RPT_INVALIDATE
/* The hcalls above are standardized in PAPR and implemented by pHyp
* as well.
@@ -558,7 +566,9 @@ struct SpaprMachineState {
/* Client Architecture support */
#define KVMPPC_H_CAS (KVMPPC_HCALL_BASE + 0x2)
#define KVMPPC_H_UPDATE_DT (KVMPPC_HCALL_BASE + 0x3)
-#define KVMPPC_HCALL_MAX KVMPPC_H_UPDATE_DT
+/* 0x4 was used for KVMPPC_H_UPDATE_PHANDLE in SLOF */
+#define KVMPPC_H_VOF_CLIENT (KVMPPC_HCALL_BASE + 0x5)
+#define KVMPPC_HCALL_MAX KVMPPC_H_VOF_CLIENT
/*
* The hcall range 0xEF00 to 0xEF80 is reserved for use in facilitating
@@ -770,7 +780,7 @@ void spapr_load_rtas(SpaprMachineState *spapr, void *fdt, hwaddr addr);
#define SPAPR_IS_PCI_LIOBN(liobn) (!!((liobn) & 0x80000000))
#define SPAPR_PCI_DMA_WINDOW_NUM(liobn) ((liobn) & 0xff)
-#define RTAS_SIZE 2048
+#define RTAS_MIN_SIZE 20 /* hv_rtas_size in SLOF */
#define RTAS_ERROR_LOG_MAX 2048
/* Offset from rtas-base where error log is placed */
@@ -932,6 +942,7 @@ extern const VMStateDescription vmstate_spapr_cap_nested_kvm_hv;
extern const VMStateDescription vmstate_spapr_cap_large_decr;
extern const VMStateDescription vmstate_spapr_cap_ccf_assist;
extern const VMStateDescription vmstate_spapr_cap_fwnmi;
+extern const VMStateDescription vmstate_spapr_cap_rpt_invalidate;
static inline uint8_t spapr_get_cap(SpaprMachineState *spapr, int cap)
{
@@ -956,4 +967,16 @@ bool spapr_check_pagesize(SpaprMachineState *spapr, hwaddr pagesize,
void spapr_set_all_lpcrs(target_ulong value, target_ulong mask);
hwaddr spapr_get_rtas_addr(void);
bool spapr_memory_hot_unplug_supported(SpaprMachineState *spapr);
+
+void spapr_vof_reset(SpaprMachineState *spapr, void *fdt, Error **errp);
+void spapr_vof_quiesce(MachineState *ms);
+bool spapr_vof_setprop(MachineState *ms, const char *path, const char *propname,
+ void *val, int vallen);
+target_ulong spapr_h_vof_client(PowerPCCPU *cpu, SpaprMachineState *spapr,
+ target_ulong opcode, target_ulong *args);
+target_ulong spapr_vof_client_architecture_support(MachineState *ms,
+ CPUState *cs,
+ target_ulong ovec_addr);
+void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt);
+
#endif /* HW_SPAPR_H */
diff --git a/include/hw/ppc/vof.h b/include/hw/ppc/vof.h
new file mode 100644
index 0000000..640be46
--- /dev/null
+++ b/include/hw/ppc/vof.h
@@ -0,0 +1,58 @@
+/*
+ * Virtual Open Firmware
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_VOF_H
+#define HW_VOF_H
+
+typedef struct Vof {
+ uint64_t top_addr; /* copied from rma_size */
+ GArray *claimed; /* array of SpaprOfClaimed */
+ uint64_t claimed_base;
+ GHashTable *of_instances; /* ihandle -> SpaprOfInstance */
+ uint32_t of_instance_last;
+ char *bootargs;
+ long fw_size;
+} Vof;
+
+int vof_client_call(MachineState *ms, Vof *vof, void *fdt,
+ target_ulong args_real);
+uint64_t vof_claim(Vof *vof, uint64_t virt, uint64_t size, uint64_t align);
+void vof_init(Vof *vof, uint64_t top_addr, Error **errp);
+void vof_cleanup(Vof *vof);
+void vof_build_dt(void *fdt, Vof *vof);
+uint32_t vof_client_open_store(void *fdt, Vof *vof, const char *nodename,
+ const char *prop, const char *path);
+
+#define TYPE_VOF_MACHINE_IF "vof-machine-if"
+
+typedef struct VofMachineIfClass VofMachineIfClass;
+DECLARE_CLASS_CHECKERS(VofMachineIfClass, VOF_MACHINE, TYPE_VOF_MACHINE_IF)
+
+struct VofMachineIfClass {
+ InterfaceClass parent;
+ target_ulong (*client_architecture_support)(MachineState *ms, CPUState *cs,
+ target_ulong vec);
+ void (*quiesce)(MachineState *ms);
+ bool (*setprop)(MachineState *ms, const char *path, const char *propname,
+ void *val, int vallen);
+};
+
+/*
+ * Initial stack size is from
+ * https://www.devicetree.org/open-firmware/bindings/ppc/release/ppc-2_1.html#REF27292
+ *
+ * "Client programs shall be invoked with a valid stack pointer (r1) with
+ * at least 32K bytes of memory available for stack growth".
+ */
+#define VOF_STACK_SIZE 0x8000
+
+#define VOF_MEM_READ(pa, buf, size) \
+ address_space_read(&address_space_memory, \
+ (pa), MEMTXATTRS_UNSPECIFIED, (buf), (size))
+#define VOF_MEM_WRITE(pa, buf, size) \
+ address_space_write(&address_space_memory, \
+ (pa), MEMTXATTRS_UNSPECIFIED, (buf), (size))
+
+#endif /* HW_VOF_H */
diff --git a/include/hw/misc/emc141x_regs.h b/include/hw/sensor/emc141x_regs.h
index 0560fb7..0560fb7 100644
--- a/include/hw/misc/emc141x_regs.h
+++ b/include/hw/sensor/emc141x_regs.h
diff --git a/hw/misc/tmp105.h b/include/hw/sensor/tmp105.h
index 7c97071..244e298 100644
--- a/hw/misc/tmp105.h
+++ b/include/hw/sensor/tmp105.h
@@ -15,7 +15,7 @@
#define QEMU_TMP105_H
#include "hw/i2c/i2c.h"
-#include "hw/misc/tmp105_regs.h"
+#include "hw/sensor/tmp105_regs.h"
#include "qom/object.h"
#define TYPE_TMP105 "tmp105"
diff --git a/include/hw/misc/tmp105_regs.h b/include/hw/sensor/tmp105_regs.h
index ef015ee..ef015ee 100644
--- a/include/hw/misc/tmp105_regs.h
+++ b/include/hw/sensor/tmp105_regs.h
diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h
index 6141162..8af11b0 100644
--- a/include/hw/vfio/vfio-common.h
+++ b/include/hw/vfio/vfio-common.h
@@ -88,9 +88,11 @@ typedef struct VFIOContainer {
uint64_t dirty_pgsizes;
uint64_t max_dirty_bitmap_size;
unsigned long pgsizes;
+ unsigned int dma_max_mappings;
QLIST_HEAD(, VFIOGuestIOMMU) giommu_list;
QLIST_HEAD(, VFIOHostDMAWindow) hostwin_list;
QLIST_HEAD(, VFIOGroup) group_list;
+ QLIST_HEAD(, VFIORamDiscardListener) vrdl_list;
QLIST_ENTRY(VFIOContainer) next;
} VFIOContainer;
@@ -102,6 +104,16 @@ typedef struct VFIOGuestIOMMU {
QLIST_ENTRY(VFIOGuestIOMMU) giommu_next;
} VFIOGuestIOMMU;
+typedef struct VFIORamDiscardListener {
+ VFIOContainer *container;
+ MemoryRegion *mr;
+ hwaddr offset_within_address_space;
+ hwaddr size;
+ uint64_t granularity;
+ RamDiscardListener listener;
+ QLIST_ENTRY(VFIORamDiscardListener) next;
+} VFIORamDiscardListener;
+
typedef struct VFIOHostDMAWindow {
hwaddr min_iova;
hwaddr max_iova;
diff --git a/include/hw/virtio/virtio-mem.h b/include/hw/virtio/virtio-mem.h
index 4eeb82d..9a6e348 100644
--- a/include/hw/virtio/virtio-mem.h
+++ b/include/hw/virtio/virtio-mem.h
@@ -67,6 +67,9 @@ struct VirtIOMEM {
/* don't migrate unplugged memory */
NotifierWithReturn precopy_notifier;
+
+ /* listeners to notify on plug/unplug activity. */
+ QLIST_HEAD(, RamDiscardListener) rdl_list;
};
struct VirtIOMEMClass {
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 8df7b69..017c036 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -153,6 +153,7 @@ typedef enum {
MIG_PRI_DEFAULT = 0,
MIG_PRI_IOMMU, /* Must happen before PCI devices */
MIG_PRI_PCI_BUS, /* Must happen before IOMMU */
+ MIG_PRI_VIRTIO_MEM, /* Must happen before IOMMU */
MIG_PRI_GICV3_ITS, /* Must happen before PCI devices */
MIG_PRI_GICV3, /* Must happen before the ITS */
MIG_PRI_MAX,
diff --git a/include/standard-headers/asm-x86/kvm_para.h b/include/standard-headers/asm-x86/kvm_para.h
index 215d01b..204cfb8 100644
--- a/include/standard-headers/asm-x86/kvm_para.h
+++ b/include/standard-headers/asm-x86/kvm_para.h
@@ -33,6 +33,8 @@
#define KVM_FEATURE_PV_SCHED_YIELD 13
#define KVM_FEATURE_ASYNC_PF_INT 14
#define KVM_FEATURE_MSI_EXT_DEST_ID 15
+#define KVM_FEATURE_HC_MAP_GPA_RANGE 16
+#define KVM_FEATURE_MIGRATION_CONTROL 17
#define KVM_HINTS_REALTIME 0
@@ -54,6 +56,7 @@
#define MSR_KVM_POLL_CONTROL 0x4b564d05
#define MSR_KVM_ASYNC_PF_INT 0x4b564d06
#define MSR_KVM_ASYNC_PF_ACK 0x4b564d07
+#define MSR_KVM_MIGRATION_CONTROL 0x4b564d08
struct kvm_steal_time {
uint64_t steal;
@@ -90,6 +93,16 @@ struct kvm_clock_pairing {
/* MSR_KVM_ASYNC_PF_INT */
#define KVM_ASYNC_PF_VEC_MASK GENMASK(7, 0)
+/* MSR_KVM_MIGRATION_CONTROL */
+#define KVM_MIGRATION_READY (1 << 0)
+
+/* KVM_HC_MAP_GPA_RANGE */
+#define KVM_MAP_GPA_RANGE_PAGE_SZ_4K 0
+#define KVM_MAP_GPA_RANGE_PAGE_SZ_2M (1 << 0)
+#define KVM_MAP_GPA_RANGE_PAGE_SZ_1G (1 << 1)
+#define KVM_MAP_GPA_RANGE_ENC_STAT(n) (n << 4)
+#define KVM_MAP_GPA_RANGE_ENCRYPTED KVM_MAP_GPA_RANGE_ENC_STAT(1)
+#define KVM_MAP_GPA_RANGE_DECRYPTED KVM_MAP_GPA_RANGE_ENC_STAT(0)
/* Operations for KVM_HC_MMU_OP */
#define KVM_MMU_OP_WRITE_PTE 1
diff --git a/include/standard-headers/drm/drm_fourcc.h b/include/standard-headers/drm/drm_fourcc.h
index a61ae52..352b51f 100644
--- a/include/standard-headers/drm/drm_fourcc.h
+++ b/include/standard-headers/drm/drm_fourcc.h
@@ -167,6 +167,13 @@ extern "C" {
#define DRM_FORMAT_RGBA1010102 fourcc_code('R', 'A', '3', '0') /* [31:0] R:G:B:A 10:10:10:2 little endian */
#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0') /* [31:0] B:G:R:A 10:10:10:2 little endian */
+/* 64 bpp RGB */
+#define DRM_FORMAT_XRGB16161616 fourcc_code('X', 'R', '4', '8') /* [63:0] x:R:G:B 16:16:16:16 little endian */
+#define DRM_FORMAT_XBGR16161616 fourcc_code('X', 'B', '4', '8') /* [63:0] x:B:G:R 16:16:16:16 little endian */
+
+#define DRM_FORMAT_ARGB16161616 fourcc_code('A', 'R', '4', '8') /* [63:0] A:R:G:B 16:16:16:16 little endian */
+#define DRM_FORMAT_ABGR16161616 fourcc_code('A', 'B', '4', '8') /* [63:0] A:B:G:R 16:16:16:16 little endian */
+
/*
* Floating point 64bpp RGB
* IEEE 754-2008 binary16 half-precision float
diff --git a/include/standard-headers/linux/ethtool.h b/include/standard-headers/linux/ethtool.h
index 218d944..053d3fa 100644
--- a/include/standard-headers/linux/ethtool.h
+++ b/include/standard-headers/linux/ethtool.h
@@ -233,7 +233,7 @@ enum tunable_id {
ETHTOOL_PFC_PREVENTION_TOUT, /* timeout in msecs */
/*
* Add your fresh new tunable attribute above and remember to update
- * tunable_strings[] in net/core/ethtool.c
+ * tunable_strings[] in net/ethtool/common.c
*/
__ETHTOOL_TUNABLE_COUNT,
};
@@ -297,7 +297,7 @@ enum phy_tunable_id {
ETHTOOL_PHY_EDPD,
/*
* Add your fresh new phy tunable attribute above and remember to update
- * phy_tunable_strings[] in net/core/ethtool.c
+ * phy_tunable_strings[] in net/ethtool/common.c
*/
__ETHTOOL_PHY_TUNABLE_COUNT,
};
diff --git a/include/standard-headers/linux/input-event-codes.h b/include/standard-headers/linux/input-event-codes.h
index c403b9c..b5e86b4 100644
--- a/include/standard-headers/linux/input-event-codes.h
+++ b/include/standard-headers/linux/input-event-codes.h
@@ -611,6 +611,7 @@
#define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */
#define KEY_ASSISTANT 0x247 /* AL Context-aware desktop assistant */
#define KEY_KBD_LAYOUT_NEXT 0x248 /* AC Next Keyboard Layout Select */
+#define KEY_EMOJI_PICKER 0x249 /* Show/hide emoji picker (HUTRR101) */
#define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */
#define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */
diff --git a/include/standard-headers/linux/virtio_ids.h b/include/standard-headers/linux/virtio_ids.h
index f0c35ce..4fe842c 100644
--- a/include/standard-headers/linux/virtio_ids.h
+++ b/include/standard-headers/linux/virtio_ids.h
@@ -54,7 +54,7 @@
#define VIRTIO_ID_SOUND 25 /* virtio sound */
#define VIRTIO_ID_FS 26 /* virtio filesystem */
#define VIRTIO_ID_PMEM 27 /* virtio pmem */
-#define VIRTIO_ID_BT 28 /* virtio bluetooth */
#define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
+#define VIRTIO_ID_BT 40 /* virtio bluetooth */
#endif /* _LINUX_VIRTIO_IDS_H */
diff --git a/include/standard-headers/linux/virtio_vsock.h b/include/standard-headers/linux/virtio_vsock.h
index be44321..3a23488 100644
--- a/include/standard-headers/linux/virtio_vsock.h
+++ b/include/standard-headers/linux/virtio_vsock.h
@@ -38,6 +38,9 @@
#include "standard-headers/linux/virtio_ids.h"
#include "standard-headers/linux/virtio_config.h"
+/* The feature bitmap for virtio vsock */
+#define VIRTIO_VSOCK_F_SEQPACKET 1 /* SOCK_SEQPACKET supported */
+
struct virtio_vsock_config {
uint64_t guest_cid;
} QEMU_PACKED;
@@ -65,6 +68,7 @@ struct virtio_vsock_hdr {
enum virtio_vsock_type {
VIRTIO_VSOCK_TYPE_STREAM = 1,
+ VIRTIO_VSOCK_TYPE_SEQPACKET = 2,
};
enum virtio_vsock_op {
@@ -91,4 +95,9 @@ enum virtio_vsock_shutdown {
VIRTIO_VSOCK_SHUTDOWN_SEND = 2,
};
+/* VIRTIO_VSOCK_OP_RW flags values */
+enum virtio_vsock_rw {
+ VIRTIO_VSOCK_SEQ_EOR = 1,
+};
+
#endif /* _LINUX_VIRTIO_VSOCK_H */
diff --git a/linux-headers/asm-arm64/kvm.h b/linux-headers/asm-arm64/kvm.h
index b6a0eaa..3d2ce99 100644
--- a/linux-headers/asm-arm64/kvm.h
+++ b/linux-headers/asm-arm64/kvm.h
@@ -184,6 +184,17 @@ struct kvm_vcpu_events {
__u32 reserved[12];
};
+struct kvm_arm_copy_mte_tags {
+ __u64 guest_ipa;
+ __u64 length;
+ void *addr;
+ __u64 flags;
+ __u64 reserved[2];
+};
+
+#define KVM_ARM_TAGS_TO_GUEST 0
+#define KVM_ARM_TAGS_FROM_GUEST 1
+
/* If you need to interpret the index values, here is the key: */
#define KVM_REG_ARM_COPROC_MASK 0x000000000FFF0000
#define KVM_REG_ARM_COPROC_SHIFT 16
diff --git a/linux-headers/asm-generic/mman-common.h b/linux-headers/asm-generic/mman-common.h
index f94f65d..1567a32 100644
--- a/linux-headers/asm-generic/mman-common.h
+++ b/linux-headers/asm-generic/mman-common.h
@@ -72,6 +72,9 @@
#define MADV_COLD 20 /* deactivate these pages */
#define MADV_PAGEOUT 21 /* reclaim these pages */
+#define MADV_POPULATE_READ 22 /* populate (prefault) page tables readable */
+#define MADV_POPULATE_WRITE 23 /* populate (prefault) page tables writable */
+
/* compatibility flags */
#define MAP_FILE 0
diff --git a/linux-headers/asm-generic/unistd.h b/linux-headers/asm-generic/unistd.h
index 6de5a7f..f211961 100644
--- a/linux-headers/asm-generic/unistd.h
+++ b/linux-headers/asm-generic/unistd.h
@@ -863,8 +863,8 @@ __SYSCALL(__NR_process_madvise, sys_process_madvise)
__SC_COMP(__NR_epoll_pwait2, sys_epoll_pwait2, compat_sys_epoll_pwait2)
#define __NR_mount_setattr 442
__SYSCALL(__NR_mount_setattr, sys_mount_setattr)
-#define __NR_quotactl_path 443
-__SYSCALL(__NR_quotactl_path, sys_quotactl_path)
+#define __NR_quotactl_fd 443
+__SYSCALL(__NR_quotactl_fd, sys_quotactl_fd)
#define __NR_landlock_create_ruleset 444
__SYSCALL(__NR_landlock_create_ruleset, sys_landlock_create_ruleset)
diff --git a/linux-headers/asm-mips/mman.h b/linux-headers/asm-mips/mman.h
index 57dc2ac..40b210c 100644
--- a/linux-headers/asm-mips/mman.h
+++ b/linux-headers/asm-mips/mman.h
@@ -98,6 +98,9 @@
#define MADV_COLD 20 /* deactivate these pages */
#define MADV_PAGEOUT 21 /* reclaim these pages */
+#define MADV_POPULATE_READ 22 /* populate (prefault) page tables readable */
+#define MADV_POPULATE_WRITE 23 /* populate (prefault) page tables writable */
+
/* compatibility flags */
#define MAP_FILE 0
diff --git a/linux-headers/asm-mips/unistd_n32.h b/linux-headers/asm-mips/unistd_n32.h
index fce51fe..09cd297 100644
--- a/linux-headers/asm-mips/unistd_n32.h
+++ b/linux-headers/asm-mips/unistd_n32.h
@@ -372,6 +372,7 @@
#define __NR_process_madvise (__NR_Linux + 440)
#define __NR_epoll_pwait2 (__NR_Linux + 441)
#define __NR_mount_setattr (__NR_Linux + 442)
+#define __NR_quotactl_fd (__NR_Linux + 443)
#define __NR_landlock_create_ruleset (__NR_Linux + 444)
#define __NR_landlock_add_rule (__NR_Linux + 445)
#define __NR_landlock_restrict_self (__NR_Linux + 446)
diff --git a/linux-headers/asm-mips/unistd_n64.h b/linux-headers/asm-mips/unistd_n64.h
index 0996001..780e0ce 100644
--- a/linux-headers/asm-mips/unistd_n64.h
+++ b/linux-headers/asm-mips/unistd_n64.h
@@ -348,6 +348,7 @@
#define __NR_process_madvise (__NR_Linux + 440)
#define __NR_epoll_pwait2 (__NR_Linux + 441)
#define __NR_mount_setattr (__NR_Linux + 442)
+#define __NR_quotactl_fd (__NR_Linux + 443)
#define __NR_landlock_create_ruleset (__NR_Linux + 444)
#define __NR_landlock_add_rule (__NR_Linux + 445)
#define __NR_landlock_restrict_self (__NR_Linux + 446)
diff --git a/linux-headers/asm-mips/unistd_o32.h b/linux-headers/asm-mips/unistd_o32.h
index 954303a..06a2b3b 100644
--- a/linux-headers/asm-mips/unistd_o32.h
+++ b/linux-headers/asm-mips/unistd_o32.h
@@ -418,6 +418,7 @@
#define __NR_process_madvise (__NR_Linux + 440)
#define __NR_epoll_pwait2 (__NR_Linux + 441)
#define __NR_mount_setattr (__NR_Linux + 442)
+#define __NR_quotactl_fd (__NR_Linux + 443)
#define __NR_landlock_create_ruleset (__NR_Linux + 444)
#define __NR_landlock_add_rule (__NR_Linux + 445)
#define __NR_landlock_restrict_self (__NR_Linux + 446)
diff --git a/linux-headers/asm-powerpc/unistd_32.h b/linux-headers/asm-powerpc/unistd_32.h
index 9155778..cd5a8a4 100644
--- a/linux-headers/asm-powerpc/unistd_32.h
+++ b/linux-headers/asm-powerpc/unistd_32.h
@@ -425,6 +425,7 @@
#define __NR_process_madvise 440
#define __NR_epoll_pwait2 441
#define __NR_mount_setattr 442
+#define __NR_quotactl_fd 443
#define __NR_landlock_create_ruleset 444
#define __NR_landlock_add_rule 445
#define __NR_landlock_restrict_self 446
diff --git a/linux-headers/asm-powerpc/unistd_64.h b/linux-headers/asm-powerpc/unistd_64.h
index 3cefa88..8458eff 100644
--- a/linux-headers/asm-powerpc/unistd_64.h
+++ b/linux-headers/asm-powerpc/unistd_64.h
@@ -397,6 +397,7 @@
#define __NR_process_madvise 440
#define __NR_epoll_pwait2 441
#define __NR_mount_setattr 442
+#define __NR_quotactl_fd 443
#define __NR_landlock_create_ruleset 444
#define __NR_landlock_add_rule 445
#define __NR_landlock_restrict_self 446
diff --git a/linux-headers/asm-s390/unistd_32.h b/linux-headers/asm-s390/unistd_32.h
index e8cd343..0c3cd29 100644
--- a/linux-headers/asm-s390/unistd_32.h
+++ b/linux-headers/asm-s390/unistd_32.h
@@ -415,6 +415,7 @@
#define __NR_process_madvise 440
#define __NR_epoll_pwait2 441
#define __NR_mount_setattr 442
+#define __NR_quotactl_fd 443
#define __NR_landlock_create_ruleset 444
#define __NR_landlock_add_rule 445
#define __NR_landlock_restrict_self 446
diff --git a/linux-headers/asm-s390/unistd_64.h b/linux-headers/asm-s390/unistd_64.h
index 86830e1..8dfc08b 100644
--- a/linux-headers/asm-s390/unistd_64.h
+++ b/linux-headers/asm-s390/unistd_64.h
@@ -363,6 +363,7 @@
#define __NR_process_madvise 440
#define __NR_epoll_pwait2 441
#define __NR_mount_setattr 442
+#define __NR_quotactl_fd 443
#define __NR_landlock_create_ruleset 444
#define __NR_landlock_add_rule 445
#define __NR_landlock_restrict_self 446
diff --git a/linux-headers/asm-x86/kvm.h b/linux-headers/asm-x86/kvm.h
index 0662f64..a6c327f 100644
--- a/linux-headers/asm-x86/kvm.h
+++ b/linux-headers/asm-x86/kvm.h
@@ -159,6 +159,19 @@ struct kvm_sregs {
__u64 interrupt_bitmap[(KVM_NR_INTERRUPTS + 63) / 64];
};
+struct kvm_sregs2 {
+ /* out (KVM_GET_SREGS2) / in (KVM_SET_SREGS2) */
+ struct kvm_segment cs, ds, es, fs, gs, ss;
+ struct kvm_segment tr, ldt;
+ struct kvm_dtable gdt, idt;
+ __u64 cr0, cr2, cr3, cr4, cr8;
+ __u64 efer;
+ __u64 apic_base;
+ __u64 flags;
+ __u64 pdptrs[4];
+};
+#define KVM_SREGS2_FLAGS_PDPTRS_VALID 1
+
/* for KVM_GET_FPU and KVM_SET_FPU */
struct kvm_fpu {
__u8 fpr[8][16];
diff --git a/linux-headers/asm-x86/unistd_32.h b/linux-headers/asm-x86/unistd_32.h
index 8f6ac8c..66e96c0 100644
--- a/linux-headers/asm-x86/unistd_32.h
+++ b/linux-headers/asm-x86/unistd_32.h
@@ -1,5 +1,5 @@
-#ifndef _ASM_X86_UNISTD_32_H
-#define _ASM_X86_UNISTD_32_H 1
+#ifndef _ASM_UNISTD_32_H
+#define _ASM_UNISTD_32_H
#define __NR_restart_syscall 0
#define __NR_exit 1
@@ -433,9 +433,10 @@
#define __NR_process_madvise 440
#define __NR_epoll_pwait2 441
#define __NR_mount_setattr 442
+#define __NR_quotactl_fd 443
#define __NR_landlock_create_ruleset 444
#define __NR_landlock_add_rule 445
#define __NR_landlock_restrict_self 446
-#endif /* _ASM_X86_UNISTD_32_H */
+#endif /* _ASM_UNISTD_32_H */
diff --git a/linux-headers/asm-x86/unistd_64.h b/linux-headers/asm-x86/unistd_64.h
index bb187a9..b8ff6f1 100644
--- a/linux-headers/asm-x86/unistd_64.h
+++ b/linux-headers/asm-x86/unistd_64.h
@@ -1,5 +1,5 @@
-#ifndef _ASM_X86_UNISTD_64_H
-#define _ASM_X86_UNISTD_64_H 1
+#ifndef _ASM_UNISTD_64_H
+#define _ASM_UNISTD_64_H
#define __NR_read 0
#define __NR_write 1
@@ -355,9 +355,10 @@
#define __NR_process_madvise 440
#define __NR_epoll_pwait2 441
#define __NR_mount_setattr 442
+#define __NR_quotactl_fd 443
#define __NR_landlock_create_ruleset 444
#define __NR_landlock_add_rule 445
#define __NR_landlock_restrict_self 446
-#endif /* _ASM_X86_UNISTD_64_H */
+#endif /* _ASM_UNISTD_64_H */
diff --git a/linux-headers/asm-x86/unistd_x32.h b/linux-headers/asm-x86/unistd_x32.h
index 4edd010..06a1097 100644
--- a/linux-headers/asm-x86/unistd_x32.h
+++ b/linux-headers/asm-x86/unistd_x32.h
@@ -1,5 +1,5 @@
-#ifndef _ASM_X86_UNISTD_X32_H
-#define _ASM_X86_UNISTD_X32_H 1
+#ifndef _ASM_UNISTD_X32_H
+#define _ASM_UNISTD_X32_H
#define __NR_read (__X32_SYSCALL_BIT + 0)
#define __NR_write (__X32_SYSCALL_BIT + 1)
@@ -308,6 +308,7 @@
#define __NR_process_madvise (__X32_SYSCALL_BIT + 440)
#define __NR_epoll_pwait2 (__X32_SYSCALL_BIT + 441)
#define __NR_mount_setattr (__X32_SYSCALL_BIT + 442)
+#define __NR_quotactl_fd (__X32_SYSCALL_BIT + 443)
#define __NR_landlock_create_ruleset (__X32_SYSCALL_BIT + 444)
#define __NR_landlock_add_rule (__X32_SYSCALL_BIT + 445)
#define __NR_landlock_restrict_self (__X32_SYSCALL_BIT + 446)
@@ -349,4 +350,4 @@
#define __NR_pwritev2 (__X32_SYSCALL_BIT + 547)
-#endif /* _ASM_X86_UNISTD_X32_H */
+#endif /* _ASM_UNISTD_X32_H */
diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
index 20d6a26..bcaf66c 100644
--- a/linux-headers/linux/kvm.h
+++ b/linux-headers/linux/kvm.h
@@ -280,6 +280,9 @@ struct kvm_xen_exit {
/* Encounter unexpected vm-exit reason */
#define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4
+/* Flags that describe what fields in emulation_failure hold valid data. */
+#define KVM_INTERNAL_ERROR_EMULATION_FLAG_INSTRUCTION_BYTES (1ULL << 0)
+
/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */
struct kvm_run {
/* in */
@@ -383,6 +386,25 @@ struct kvm_run {
__u32 ndata;
__u64 data[16];
} internal;
+ /*
+ * KVM_INTERNAL_ERROR_EMULATION
+ *
+ * "struct emulation_failure" is an overlay of "struct internal"
+ * that is used for the KVM_INTERNAL_ERROR_EMULATION sub-type of
+ * KVM_EXIT_INTERNAL_ERROR. Note, unlike other internal error
+ * sub-types, this struct is ABI! It also needs to be backwards
+ * compatible with "struct internal". Take special care that
+ * "ndata" is correct, that new fields are enumerated in "flags",
+ * and that each flag enumerates fields that are 64-bit aligned
+ * and sized (so that ndata+internal.data[] is valid/accurate).
+ */
+ struct {
+ __u32 suberror;
+ __u32 ndata;
+ __u64 flags;
+ __u8 insn_size;
+ __u8 insn_bytes[15];
+ } emulation_failure;
/* KVM_EXIT_OSI */
struct {
__u64 gprs[32];
@@ -1083,6 +1105,13 @@ struct kvm_ppc_resize_hpt {
#define KVM_CAP_SGX_ATTRIBUTE 196
#define KVM_CAP_VM_COPY_ENC_CONTEXT_FROM 197
#define KVM_CAP_PTP_KVM 198
+#define KVM_CAP_HYPERV_ENFORCE_CPUID 199
+#define KVM_CAP_SREGS2 200
+#define KVM_CAP_EXIT_HYPERCALL 201
+#define KVM_CAP_PPC_RPT_INVALIDATE 202
+#define KVM_CAP_BINARY_STATS_FD 203
+#define KVM_CAP_EXIT_ON_EMULATION_FAILURE 204
+#define KVM_CAP_ARM_MTE 205
#ifdef KVM_CAP_IRQ_ROUTING
@@ -1428,6 +1457,7 @@ struct kvm_s390_ucas_mapping {
/* Available with KVM_CAP_PMU_EVENT_FILTER */
#define KVM_SET_PMU_EVENT_FILTER _IOW(KVMIO, 0xb2, struct kvm_pmu_event_filter)
#define KVM_PPC_SVM_OFF _IO(KVMIO, 0xb3)
+#define KVM_ARM_MTE_COPY_TAGS _IOR(KVMIO, 0xb4, struct kvm_arm_copy_mte_tags)
/* ioctl for vm fd */
#define KVM_CREATE_DEVICE _IOWR(KVMIO, 0xe0, struct kvm_create_device)
@@ -1621,6 +1651,9 @@ struct kvm_xen_hvm_attr {
#define KVM_XEN_VCPU_GET_ATTR _IOWR(KVMIO, 0xca, struct kvm_xen_vcpu_attr)
#define KVM_XEN_VCPU_SET_ATTR _IOW(KVMIO, 0xcb, struct kvm_xen_vcpu_attr)
+#define KVM_GET_SREGS2 _IOR(KVMIO, 0xcc, struct kvm_sregs2)
+#define KVM_SET_SREGS2 _IOW(KVMIO, 0xcd, struct kvm_sregs2)
+
struct kvm_xen_vcpu_attr {
__u16 type;
__u16 pad[3];
@@ -1899,4 +1932,76 @@ struct kvm_dirty_gfn {
#define KVM_BUS_LOCK_DETECTION_OFF (1 << 0)
#define KVM_BUS_LOCK_DETECTION_EXIT (1 << 1)
+/**
+ * struct kvm_stats_header - Header of per vm/vcpu binary statistics data.
+ * @flags: Some extra information for header, always 0 for now.
+ * @name_size: The size in bytes of the memory which contains statistics
+ * name string including trailing '\0'. The memory is allocated
+ * at the send of statistics descriptor.
+ * @num_desc: The number of statistics the vm or vcpu has.
+ * @id_offset: The offset of the vm/vcpu stats' id string in the file pointed
+ * by vm/vcpu stats fd.
+ * @desc_offset: The offset of the vm/vcpu stats' descriptor block in the file
+ * pointd by vm/vcpu stats fd.
+ * @data_offset: The offset of the vm/vcpu stats' data block in the file
+ * pointed by vm/vcpu stats fd.
+ *
+ * This is the header userspace needs to read from stats fd before any other
+ * readings. It is used by userspace to discover all the information about the
+ * vm/vcpu's binary statistics.
+ * Userspace reads this header from the start of the vm/vcpu's stats fd.
+ */
+struct kvm_stats_header {
+ __u32 flags;
+ __u32 name_size;
+ __u32 num_desc;
+ __u32 id_offset;
+ __u32 desc_offset;
+ __u32 data_offset;
+};
+
+#define KVM_STATS_TYPE_SHIFT 0
+#define KVM_STATS_TYPE_MASK (0xF << KVM_STATS_TYPE_SHIFT)
+#define KVM_STATS_TYPE_CUMULATIVE (0x0 << KVM_STATS_TYPE_SHIFT)
+#define KVM_STATS_TYPE_INSTANT (0x1 << KVM_STATS_TYPE_SHIFT)
+#define KVM_STATS_TYPE_PEAK (0x2 << KVM_STATS_TYPE_SHIFT)
+#define KVM_STATS_TYPE_MAX KVM_STATS_TYPE_PEAK
+
+#define KVM_STATS_UNIT_SHIFT 4
+#define KVM_STATS_UNIT_MASK (0xF << KVM_STATS_UNIT_SHIFT)
+#define KVM_STATS_UNIT_NONE (0x0 << KVM_STATS_UNIT_SHIFT)
+#define KVM_STATS_UNIT_BYTES (0x1 << KVM_STATS_UNIT_SHIFT)
+#define KVM_STATS_UNIT_SECONDS (0x2 << KVM_STATS_UNIT_SHIFT)
+#define KVM_STATS_UNIT_CYCLES (0x3 << KVM_STATS_UNIT_SHIFT)
+#define KVM_STATS_UNIT_MAX KVM_STATS_UNIT_CYCLES
+
+#define KVM_STATS_BASE_SHIFT 8
+#define KVM_STATS_BASE_MASK (0xF << KVM_STATS_BASE_SHIFT)
+#define KVM_STATS_BASE_POW10 (0x0 << KVM_STATS_BASE_SHIFT)
+#define KVM_STATS_BASE_POW2 (0x1 << KVM_STATS_BASE_SHIFT)
+#define KVM_STATS_BASE_MAX KVM_STATS_BASE_POW2
+
+/**
+ * struct kvm_stats_desc - Descriptor of a KVM statistics.
+ * @flags: Annotations of the stats, like type, unit, etc.
+ * @exponent: Used together with @flags to determine the unit.
+ * @size: The number of data items for this stats.
+ * Every data item is of type __u64.
+ * @offset: The offset of the stats to the start of stat structure in
+ * struture kvm or kvm_vcpu.
+ * @unused: Unused field for future usage. Always 0 for now.
+ * @name: The name string for the stats. Its size is indicated by the
+ * &kvm_stats_header->name_size.
+ */
+struct kvm_stats_desc {
+ __u32 flags;
+ __s16 exponent;
+ __u16 size;
+ __u32 offset;
+ __u32 unused;
+ char name[];
+};
+
+#define KVM_GET_STATS_FD _IO(KVMIO, 0xce)
+
#endif /* __LINUX_KVM_H */
diff --git a/linux-headers/linux/userfaultfd.h b/linux-headers/linux/userfaultfd.h
index b9ac97b..8479af5 100644
--- a/linux-headers/linux/userfaultfd.h
+++ b/linux-headers/linux/userfaultfd.h
@@ -31,7 +31,8 @@
UFFD_FEATURE_MISSING_SHMEM | \
UFFD_FEATURE_SIGBUS | \
UFFD_FEATURE_THREAD_ID | \
- UFFD_FEATURE_MINOR_HUGETLBFS)
+ UFFD_FEATURE_MINOR_HUGETLBFS | \
+ UFFD_FEATURE_MINOR_SHMEM)
#define UFFD_API_IOCTLS \
((__u64)1 << _UFFDIO_REGISTER | \
(__u64)1 << _UFFDIO_UNREGISTER | \
@@ -80,8 +81,8 @@
struct uffdio_zeropage)
#define UFFDIO_WRITEPROTECT _IOWR(UFFDIO, _UFFDIO_WRITEPROTECT, \
struct uffdio_writeprotect)
-#define UFFDIO_CONTINUE _IOR(UFFDIO, _UFFDIO_CONTINUE, \
- struct uffdio_continue)
+#define UFFDIO_CONTINUE _IOWR(UFFDIO, _UFFDIO_CONTINUE, \
+ struct uffdio_continue)
/* read() structure */
struct uffd_msg {
@@ -185,6 +186,9 @@ struct uffdio_api {
* UFFD_FEATURE_MINOR_HUGETLBFS indicates that minor faults
* can be intercepted (via REGISTER_MODE_MINOR) for
* hugetlbfs-backed pages.
+ *
+ * UFFD_FEATURE_MINOR_SHMEM indicates the same support as
+ * UFFD_FEATURE_MINOR_HUGETLBFS, but for shmem-backed pages instead.
*/
#define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0)
#define UFFD_FEATURE_EVENT_FORK (1<<1)
@@ -196,6 +200,7 @@ struct uffdio_api {
#define UFFD_FEATURE_SIGBUS (1<<7)
#define UFFD_FEATURE_THREAD_ID (1<<8)
#define UFFD_FEATURE_MINOR_HUGETLBFS (1<<9)
+#define UFFD_FEATURE_MINOR_SHMEM (1<<10)
__u64 features;
__u64 ioctls;
diff --git a/meson.build b/meson.build
index 7e12de0..eb362ee 100644
--- a/meson.build
+++ b/meson.build
@@ -710,13 +710,16 @@ if not get_option('rbd').auto() or have_block
int main(void) {
rados_t cluster;
rados_create(&cluster, NULL);
+ #if LIBRBD_VERSION_CODE < LIBRBD_VERSION(1, 12, 0)
+ #error
+ #endif
return 0;
}''', dependencies: [librbd, librados])
rbd = declare_dependency(dependencies: [librbd, librados])
elif get_option('rbd').enabled()
- error('could not link librados')
+ error('librbd >= 1.12.0 required')
else
- warning('could not link librados, disabling')
+ warning('librbd >= 1.12.0 not found, disabling')
endif
endif
endif
diff --git a/pc-bios/README b/pc-bios/README
index c101c9a..d344e3b 100644
--- a/pc-bios/README
+++ b/pc-bios/README
@@ -16,6 +16,10 @@
https://github.com/aik/SLOF, and the image currently in qemu is
built from git tag qemu-slof-20210217.
+- VOF (Virtual Open Firmware) is a minimalistic firmware to work with
+ -machine pseries,x-vof=on. When enabled, the firmware acts as a slim shim and
+ QEMU implements parts of the IEEE 1275 Open Firmware interface.
+
- sgabios (the Serial Graphics Adapter option ROM) provides a means for
legacy x86 software to communicate with an attached serial console as
if a video card were attached. The master sources reside in a subversion
diff --git a/pc-bios/u-boot.e500 b/pc-bios/u-boot.e500
index d2e29f8..8e635c8 100644
--- a/pc-bios/u-boot.e500
+++ b/pc-bios/u-boot.e500
Binary files differ
diff --git a/pc-bios/vof-nvram.bin b/pc-bios/vof-nvram.bin
new file mode 100644
index 0000000..d183901
--- /dev/null
+++ b/pc-bios/vof-nvram.bin
Binary files differ
diff --git a/pc-bios/vof.bin b/pc-bios/vof.bin
new file mode 100755
index 0000000..300cb7c
--- /dev/null
+++ b/pc-bios/vof.bin
Binary files differ
diff --git a/pc-bios/vof/Makefile b/pc-bios/vof/Makefile
new file mode 100644
index 0000000..aa1678c
--- /dev/null
+++ b/pc-bios/vof/Makefile
@@ -0,0 +1,23 @@
+all: build-all
+
+build-all: vof.bin
+
+CROSS ?=
+CC = $(CROSS)gcc
+LD = $(CROSS)ld
+OBJCOPY = $(CROSS)objcopy
+
+%.o: %.S
+ $(CC) -m32 -mbig-endian -mcpu=power4 -c -o $@ $<
+
+%.o: %.c
+ $(CC) -m32 -mbig-endian -mcpu=power4 -c -fno-stack-protector -o $@ $<
+
+vof.elf: entry.o main.o ci.o bootmem.o libc.o
+ $(LD) -nostdlib -e_start -Tvof.lds -EB -o $@ $^
+
+%.bin: %.elf
+ $(OBJCOPY) -O binary -j .text -j .data -j .toc -j .got2 $^ $@
+
+clean:
+ rm -f *.o vof.bin vof.elf *~
diff --git a/pc-bios/vof/bootmem.c b/pc-bios/vof/bootmem.c
new file mode 100644
index 0000000..771b9e9
--- /dev/null
+++ b/pc-bios/vof/bootmem.c
@@ -0,0 +1,14 @@
+#include "vof.h"
+
+void boot_from_memory(uint64_t initrd, uint64_t initrdsize)
+{
+ uint64_t kern[2];
+ phandle chosen = ci_finddevice("/chosen");
+
+ if (ci_getprop(chosen, "qemu,boot-kernel", kern, sizeof(kern)) !=
+ sizeof(kern)) {
+ return;
+ }
+
+ do_boot(kern[0], initrd, initrdsize);
+}
diff --git a/pc-bios/vof/ci.c b/pc-bios/vof/ci.c
new file mode 100644
index 0000000..fc4821b
--- /dev/null
+++ b/pc-bios/vof/ci.c
@@ -0,0 +1,91 @@
+#include "vof.h"
+
+struct prom_args {
+ uint32_t service;
+ uint32_t nargs;
+ uint32_t nret;
+ uint32_t args[10];
+};
+
+typedef unsigned long prom_arg_t;
+
+#define ADDR(x) (uint32_t)(x)
+
+static int prom_handle(struct prom_args *pargs)
+{
+ void *rtasbase;
+ uint32_t rtassize = 0;
+ phandle rtas;
+
+ if (strcmp("call-method", (void *)(unsigned long)pargs->service)) {
+ return -1;
+ }
+
+ if (strcmp("instantiate-rtas", (void *)(unsigned long)pargs->args[0])) {
+ return -1;
+ }
+
+ rtas = ci_finddevice("/rtas");
+ /* rtas-size is set by QEMU depending of FWNMI support */
+ ci_getprop(rtas, "rtas-size", &rtassize, sizeof(rtassize));
+ if (rtassize < hv_rtas_size) {
+ return -1;
+ }
+
+ rtasbase = (void *)(unsigned long) pargs->args[2];
+
+ memcpy(rtasbase, hv_rtas, hv_rtas_size);
+ pargs->args[pargs->nargs] = 0;
+ pargs->args[pargs->nargs + 1] = pargs->args[2];
+
+ return 0;
+}
+
+void prom_entry(uint32_t args)
+{
+ if (prom_handle((void *)(unsigned long) args)) {
+ ci_entry(args);
+ }
+}
+
+static int call_ci(const char *service, int nargs, int nret, ...)
+{
+ int i;
+ struct prom_args args;
+ va_list list;
+
+ args.service = ADDR(service);
+ args.nargs = nargs;
+ args.nret = nret;
+
+ va_start(list, nret);
+ for (i = 0; i < nargs; i++) {
+ args.args[i] = va_arg(list, prom_arg_t);
+ }
+ va_end(list);
+
+ for (i = 0; i < nret; i++) {
+ args.args[nargs + i] = 0;
+ }
+
+ if (ci_entry((uint32_t)(&args)) < 0) {
+ return -1;
+ }
+
+ return (nret > 0) ? args.args[nargs] : 0;
+}
+
+void ci_panic(const char *str)
+{
+ call_ci("exit", 0, 0);
+}
+
+phandle ci_finddevice(const char *path)
+{
+ return call_ci("finddevice", 1, 1, path);
+}
+
+uint32_t ci_getprop(phandle ph, const char *propname, void *prop, int len)
+{
+ return call_ci("getprop", 4, 1, ph, propname, prop, len);
+}
diff --git a/pc-bios/vof/entry.S b/pc-bios/vof/entry.S
new file mode 100644
index 0000000..10a101f
--- /dev/null
+++ b/pc-bios/vof/entry.S
@@ -0,0 +1,49 @@
+#define LOAD32(rn, name) \
+ lis rn,name##@h; \
+ ori rn,rn,name##@l
+
+#define ENTRY(func_name) \
+ .text; \
+ .align 2; \
+ .globl .func_name; \
+ .func_name: \
+ .globl func_name; \
+ func_name:
+
+#define KVMPPC_HCALL_BASE 0xf000
+#define KVMPPC_H_RTAS (KVMPPC_HCALL_BASE + 0x0)
+#define KVMPPC_H_VOF_CLIENT (KVMPPC_HCALL_BASE + 0x5)
+
+ . = 0x100 /* Do exactly as SLOF does */
+
+ENTRY(_start)
+ LOAD32(2, __toc_start)
+ b entry_c
+
+ENTRY(_prom_entry)
+ LOAD32(2, __toc_start)
+ stwu %r1,-112(%r1)
+ stw %r31,104(%r1)
+ mflr %r31
+ bl prom_entry
+ nop
+ mtlr %r31
+ lwz %r31,104(%r1)
+ addi %r1,%r1,112
+ blr
+
+ENTRY(ci_entry)
+ mr 4,3
+ LOAD32(3,KVMPPC_H_VOF_CLIENT)
+ sc 1
+ blr
+
+/* This is the actual RTAS blob copied to the OS at instantiate-rtas */
+ENTRY(hv_rtas)
+ mr %r4,%r3
+ LOAD32(3,KVMPPC_H_RTAS)
+ sc 1
+ blr
+ .globl hv_rtas_size
+hv_rtas_size:
+ .long . - hv_rtas;
diff --git a/pc-bios/vof/libc.c b/pc-bios/vof/libc.c
new file mode 100644
index 0000000..fdbc30f
--- /dev/null
+++ b/pc-bios/vof/libc.c
@@ -0,0 +1,66 @@
+#include "vof.h"
+
+int strlen(const char *s)
+{
+ int len = 0;
+
+ while (*s != 0) {
+ len += 1;
+ s += 1;
+ }
+
+ return len;
+}
+
+int strcmp(const char *s1, const char *s2)
+{
+ while (*s1 != 0 && *s2 != 0) {
+ if (*s1 != *s2) {
+ break;
+ }
+ s1 += 1;
+ s2 += 1;
+ }
+
+ return *s1 - *s2;
+}
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+ char *cdest;
+ const char *csrc = src;
+
+ cdest = dest;
+ while (n-- > 0) {
+ *cdest++ = *csrc++;
+ }
+
+ return dest;
+}
+
+int memcmp(const void *ptr1, const void *ptr2, size_t n)
+{
+ const unsigned char *p1 = ptr1;
+ const unsigned char *p2 = ptr2;
+
+ while (n-- > 0) {
+ if (*p1 != *p2) {
+ return *p1 - *p2;
+ }
+ p1 += 1;
+ p2 += 1;
+ }
+
+ return 0;
+}
+
+void *memset(void *dest, int c, size_t size)
+{
+ unsigned char *d = (unsigned char *)dest;
+
+ while (size-- > 0) {
+ *d++ = (unsigned char)c;
+ }
+
+ return dest;
+}
diff --git a/pc-bios/vof/main.c b/pc-bios/vof/main.c
new file mode 100644
index 0000000..0f0f6b4
--- /dev/null
+++ b/pc-bios/vof/main.c
@@ -0,0 +1,21 @@
+#include "vof.h"
+
+void do_boot(unsigned long addr, unsigned long _r3, unsigned long _r4)
+{
+ register unsigned long r3 __asm__("r3") = _r3;
+ register unsigned long r4 __asm__("r4") = _r4;
+ register unsigned long r5 __asm__("r5") = (unsigned long) _prom_entry;
+
+ ((void (*)(void))(uint32_t)addr)();
+}
+
+void entry_c(void)
+{
+ register unsigned long r3 __asm__("r3");
+ register unsigned long r4 __asm__("r4");
+ register unsigned long r5 __asm__("r5");
+ uint64_t initrd = r3, initrdsize = r4;
+
+ boot_from_memory(initrd, initrdsize);
+ ci_panic("*** No boot target ***\n");
+}
diff --git a/pc-bios/vof/vof.h b/pc-bios/vof/vof.h
new file mode 100644
index 0000000..5f12c07
--- /dev/null
+++ b/pc-bios/vof/vof.h
@@ -0,0 +1,41 @@
+/*
+ * Virtual Open Firmware
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <stdarg.h>
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned long uint32_t;
+typedef unsigned long long uint64_t;
+#define NULL (0)
+typedef unsigned long ihandle;
+typedef unsigned long phandle;
+typedef int size_t;
+
+/* globals */
+extern void _prom_entry(void); /* OF CI entry point (i.e. this firmware) */
+
+void do_boot(unsigned long addr, unsigned long r3, unsigned long r4);
+
+/* libc */
+int strlen(const char *s);
+int strcmp(const char *s1, const char *s2);
+void *memcpy(void *dest, const void *src, size_t n);
+int memcmp(const void *ptr1, const void *ptr2, size_t n);
+void *memmove(void *dest, const void *src, size_t n);
+void *memset(void *dest, int c, size_t size);
+
+/* CI wrappers */
+void ci_panic(const char *str);
+phandle ci_finddevice(const char *path);
+uint32_t ci_getprop(phandle ph, const char *propname, void *prop, int len);
+
+/* booting from -kernel */
+void boot_from_memory(uint64_t initrd, uint64_t initrdsize);
+
+/* Entry points for CI and RTAS */
+extern uint32_t ci_entry(uint32_t params);
+extern unsigned long hv_rtas(unsigned long params);
+extern unsigned int hv_rtas_size;
diff --git a/pc-bios/vof/vof.lds b/pc-bios/vof/vof.lds
new file mode 100644
index 0000000..1506ab4
--- /dev/null
+++ b/pc-bios/vof/vof.lds
@@ -0,0 +1,48 @@
+OUTPUT_FORMAT("elf32-powerpc")
+OUTPUT_ARCH(powerpc:common)
+
+/* set the entry point */
+ENTRY ( __start )
+
+SECTIONS {
+ __executable_start = .;
+
+ .text : {
+ *(.text)
+ }
+
+ __etext = .;
+
+ . = ALIGN(8);
+
+ .data : {
+ *(.data)
+ *(.rodata .rodata.*)
+ *(.got1)
+ *(.sdata)
+ *(.opd)
+ }
+
+ /* FIXME bss at end ??? */
+
+ . = ALIGN(8);
+ __bss_start = .;
+ .bss : {
+ *(.sbss) *(.scommon)
+ *(.dynbss)
+ *(.bss)
+ }
+
+ . = ALIGN(8);
+ __bss_end = .;
+ __bss_size = (__bss_end - __bss_start);
+
+ . = ALIGN(256);
+ __toc_start = DEFINED (.TOC.) ? .TOC. : ADDR (.got) + 0x8000;
+ .got :
+ {
+ *(.toc .got)
+ }
+ . = ALIGN(8);
+ __toc_end = .;
+}
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 3114ba6..c7a3117 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -128,6 +128,18 @@
} }
##
+# @ImageInfoSpecificRbd:
+#
+# @encryption-format: Image encryption format
+#
+# Since: 6.1
+##
+{ 'struct': 'ImageInfoSpecificRbd',
+ 'data': {
+ '*encryption-format': 'RbdImageEncryptionFormat'
+ } }
+
+##
# @ImageInfoSpecific:
#
# A discriminated record of image format specific information structures.
@@ -141,7 +153,8 @@
# If we need to add block driver specific parameters for
# LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
# to define a ImageInfoSpecificLUKS
- 'luks': 'QCryptoBlockInfoLUKS'
+ 'luks': 'QCryptoBlockInfoLUKS',
+ 'rbd': 'ImageInfoSpecificRbd'
} }
##
@@ -3614,6 +3627,94 @@
'data': [ 'cephx', 'none' ] }
##
+# @RbdImageEncryptionFormat:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdImageEncryptionFormat',
+ 'data': [ 'luks', 'luks2' ] }
+
+##
+# @RbdEncryptionOptionsLUKSBase:
+#
+# @key-secret: ID of a QCryptoSecret object providing a passphrase
+# for unlocking the encryption
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKSBase',
+ 'data': { 'key-secret': 'str' } }
+
+##
+# @RbdEncryptionCreateOptionsLUKSBase:
+#
+# @cipher-alg: The encryption algorithm
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
+ 'base': 'RbdEncryptionOptionsLUKSBase',
+ 'data': { '*cipher-alg': 'QCryptoCipherAlgorithm' } }
+
+##
+# @RbdEncryptionOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS',
+ 'base': 'RbdEncryptionOptionsLUKSBase',
+ 'data': { } }
+
+##
+# @RbdEncryptionOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS2',
+ 'base': 'RbdEncryptionOptionsLUKSBase',
+ 'data': { } }
+
+##
+# @RbdEncryptionCreateOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
+ 'base': 'RbdEncryptionCreateOptionsLUKSBase',
+ 'data': { } }
+
+##
+# @RbdEncryptionCreateOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
+ 'base': 'RbdEncryptionCreateOptionsLUKSBase',
+ 'data': { } }
+
+##
+# @RbdEncryptionOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionOptions',
+ 'base': { 'format': 'RbdImageEncryptionFormat' },
+ 'discriminator': 'format',
+ 'data': { 'luks': 'RbdEncryptionOptionsLUKS',
+ 'luks2': 'RbdEncryptionOptionsLUKS2' } }
+
+##
+# @RbdEncryptionCreateOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionCreateOptions',
+ 'base': { 'format': 'RbdImageEncryptionFormat' },
+ 'discriminator': 'format',
+ 'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
+ 'luks2': 'RbdEncryptionCreateOptionsLUKS2' } }
+
+##
# @BlockdevOptionsRbd:
#
# @pool: Ceph pool name.
@@ -3628,6 +3729,8 @@
#
# @snapshot: Ceph snapshot name.
#
+# @encrypt: Image encryption options. (Since 6.1)
+#
# @user: Ceph id name.
#
# @auth-client-required: Acceptable authentication modes.
@@ -3650,6 +3753,7 @@
'image': 'str',
'*conf': 'str',
'*snapshot': 'str',
+ '*encrypt': 'RbdEncryptionOptions',
'*user': 'str',
'*auth-client-required': ['RbdAuthMode'],
'*key-secret': 'str',
@@ -4115,15 +4219,17 @@
{ 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true }
##
-# @x-blockdev-reopen:
+# @blockdev-reopen:
#
-# Reopens a block device using the given set of options. Any option
-# not specified will be reset to its default value regardless of its
-# previous status. If an option cannot be changed or a particular
+# Reopens one or more block devices using the given set of options.
+# Any option not specified will be reset to its default value regardless
+# of its previous status. If an option cannot be changed or a particular
# driver does not support reopening then the command will return an
-# error.
+# error. All devices in the list are reopened in one transaction, so
+# if one of them fails then the whole transaction is cancelled.
#
-# The top-level @node-name option (from BlockdevOptions) must be
+# The command receives a list of block devices to reopen. For each one
+# of them, the top-level @node-name option (from BlockdevOptions) must be
# specified and is used to select the block device to be reopened.
# Other @node-name options must be either omitted or set to the
# current name of the appropriate node. This command won't change any
@@ -4143,18 +4249,18 @@
#
# 4) NULL: the current child (if any) is detached.
#
-# Options (1) and (2) are supported in all cases, but at the moment
-# only @backing allows replacing or detaching an existing child.
+# Options (1) and (2) are supported in all cases. Option (3) is
+# supported for @file and @backing, and option (4) for @backing only.
#
# Unlike with blockdev-add, the @backing option must always be present
# unless the node being reopened does not have a backing file and its
# image does not have a default backing file name as part of its
# metadata.
#
-# Since: 4.0
+# Since: 6.1
##
-{ 'command': 'x-blockdev-reopen',
- 'data': 'BlockdevOptions', 'boxed': true }
+{ 'command': 'blockdev-reopen',
+ 'data': { 'options': ['BlockdevOptions'] } }
##
# @blockdev-del:
@@ -4403,13 +4509,15 @@
# point to a snapshot.
# @size: Size of the virtual disk in bytes
# @cluster-size: RBD object size
+# @encrypt: Image encryption options. (Since 6.1)
#
# Since: 2.12
##
{ 'struct': 'BlockdevCreateOptionsRbd',
'data': { 'location': 'BlockdevOptionsRbd',
'size': 'size',
- '*cluster-size' : 'size' } }
+ '*cluster-size' : 'size',
+ '*encrypt' : 'RbdEncryptionCreateOptions' } }
##
# @BlockdevVmdkSubformat:
diff --git a/qapi/block-export.json b/qapi/block-export.json
index e819e70..0ed6344 100644
--- a/qapi/block-export.json
+++ b/qapi/block-export.json
@@ -121,6 +121,23 @@
'*num-queues': 'uint16'} }
##
+# @FuseExportAllowOther:
+#
+# Possible allow_other modes for FUSE exports.
+#
+# @off: Do not pass allow_other as a mount option.
+#
+# @on: Pass allow_other as a mount option.
+#
+# @auto: Try mounting with allow_other first, and if that fails, retry
+# without allow_other.
+#
+# Since: 6.1
+##
+{ 'enum': 'FuseExportAllowOther',
+ 'data': ['off', 'on', 'auto'] }
+
+##
# @BlockExportOptionsFuse:
#
# Options for exporting a block graph node on some (file) mountpoint
@@ -132,11 +149,25 @@
# @growable: Whether writes beyond the EOF should grow the block node
# accordingly. (default: false)
#
+# @allow-other: If this is off, only qemu's user is allowed access to
+# this export. That cannot be changed even with chmod or
+# chown.
+# Enabling this option will allow other users access to
+# the export with the FUSE mount option "allow_other".
+# Note that using allow_other as a non-root user requires
+# user_allow_other to be enabled in the global fuse.conf
+# configuration file.
+# In auto mode (the default), the FUSE export driver will
+# first attempt to mount the export with allow_other, and
+# if that fails, try again without.
+# (since 6.1; default: auto)
+#
# Since: 6.0
##
{ 'struct': 'BlockExportOptionsFuse',
'data': { 'mountpoint': 'str',
- '*growable': 'bool' },
+ '*growable': 'bool',
+ '*allow-other': 'FuseExportAllowOther' },
'if': 'defined(CONFIG_FUSE)' }
##
diff --git a/qemu-img.c b/qemu-img.c
index 7956a89..7c4fc60 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -2508,8 +2508,10 @@ static int img_convert(int argc, char **argv)
if (out_baseimg_param) {
if (!qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT)) {
- warn_report("Deprecated use of backing file without explicit "
- "backing format");
+ error_report("Use of backing file requires explicit "
+ "backing format");
+ ret = -1;
+ goto out;
}
}
@@ -3765,6 +3767,9 @@ static int img_rebase(int argc, char **argv)
if (ret == -ENOSPC) {
error_report("Could not change the backing file to '%s': No "
"space left in the file header", out_baseimg);
+ } else if (ret == -EINVAL && out_baseimg && !out_basefmt) {
+ error_report("Could not change the backing file to '%s': backing "
+ "format must be specified", out_baseimg);
} else if (ret < 0) {
error_report("Could not change the backing file to '%s': %s",
out_baseimg, strerror(-ret));
diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index e8d862a..46593d6 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -2116,8 +2116,6 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv)
bool writethrough = !blk_enable_write_cache(blk);
bool has_rw_option = false;
bool has_cache_option = false;
-
- BlockReopenQueue *brq;
Error *local_err = NULL;
while ((c = getopt(argc, argv, "c:o:rw")) != -1) {
@@ -2210,10 +2208,7 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv)
qdict_put_bool(opts, BDRV_OPT_CACHE_NO_FLUSH, flags & BDRV_O_NO_FLUSH);
}
- bdrv_subtree_drained_begin(bs);
- brq = bdrv_reopen_queue(NULL, bs, opts, true);
- bdrv_reopen_multiple(brq, &local_err);
- bdrv_subtree_drained_end(bs);
+ bdrv_reopen(bs, opts, true, &local_err);
if (local_err) {
error_report_err(local_err);
diff --git a/roms/u-boot b/roms/u-boot
-Subproject b46dd116ce03e235f2a7d4843c6278e1da44b5e
+Subproject 840658b093976390e9537724f802281c9c8439f
diff --git a/softmmu/memory.c b/softmmu/memory.c
index 24a97c8..bfedaf9 100644
--- a/softmmu/memory.c
+++ b/softmmu/memory.c
@@ -2031,6 +2031,70 @@ int memory_region_iommu_num_indexes(IOMMUMemoryRegion *iommu_mr)
return imrc->num_indexes(iommu_mr);
}
+RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr)
+{
+ if (!memory_region_is_mapped(mr) || !memory_region_is_ram(mr)) {
+ return NULL;
+ }
+ return mr->rdm;
+}
+
+void memory_region_set_ram_discard_manager(MemoryRegion *mr,
+ RamDiscardManager *rdm)
+{
+ g_assert(memory_region_is_ram(mr) && !memory_region_is_mapped(mr));
+ g_assert(!rdm || !mr->rdm);
+ mr->rdm = rdm;
+}
+
+uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm,
+ const MemoryRegion *mr)
+{
+ RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm);
+
+ g_assert(rdmc->get_min_granularity);
+ return rdmc->get_min_granularity(rdm, mr);
+}
+
+bool ram_discard_manager_is_populated(const RamDiscardManager *rdm,
+ const MemoryRegionSection *section)
+{
+ RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm);
+
+ g_assert(rdmc->is_populated);
+ return rdmc->is_populated(rdm, section);
+}
+
+int ram_discard_manager_replay_populated(const RamDiscardManager *rdm,
+ MemoryRegionSection *section,
+ ReplayRamPopulate replay_fn,
+ void *opaque)
+{
+ RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm);
+
+ g_assert(rdmc->replay_populated);
+ return rdmc->replay_populated(rdm, section, replay_fn, opaque);
+}
+
+void ram_discard_manager_register_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl,
+ MemoryRegionSection *section)
+{
+ RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm);
+
+ g_assert(rdmc->register_listener);
+ rdmc->register_listener(rdm, rdl, section);
+}
+
+void ram_discard_manager_unregister_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl)
+{
+ RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm);
+
+ g_assert(rdmc->unregister_listener);
+ rdmc->unregister_listener(rdm, rdl);
+}
+
void memory_region_set_log(MemoryRegion *mr, bool log, unsigned client)
{
uint8_t mask = 1 << client;
@@ -2641,6 +2705,33 @@ MemoryRegionSection memory_region_find(MemoryRegion *mr,
return ret;
}
+MemoryRegionSection *memory_region_section_new_copy(MemoryRegionSection *s)
+{
+ MemoryRegionSection *tmp = g_new(MemoryRegionSection, 1);
+
+ *tmp = *s;
+ if (tmp->mr) {
+ memory_region_ref(tmp->mr);
+ }
+ if (tmp->fv) {
+ bool ret = flatview_ref(tmp->fv);
+
+ g_assert(ret);
+ }
+ return tmp;
+}
+
+void memory_region_section_free_copy(MemoryRegionSection *s)
+{
+ if (s->fv) {
+ flatview_unref(s->fv);
+ }
+ if (s->mr) {
+ memory_region_unref(s->mr);
+ }
+ g_free(s);
+}
+
bool memory_region_present(MemoryRegion *container, hwaddr addr)
{
MemoryRegion *mr;
@@ -3324,10 +3415,17 @@ static const TypeInfo iommu_memory_region_info = {
.abstract = true,
};
+static const TypeInfo ram_discard_manager_info = {
+ .parent = TYPE_INTERFACE,
+ .name = TYPE_RAM_DISCARD_MANAGER,
+ .class_size = sizeof(RamDiscardManagerClass),
+};
+
static void memory_register_types(void)
{
type_register_static(&memory_region_info);
type_register_static(&iommu_memory_region_info);
+ type_register_static(&ram_discard_manager_info);
}
type_init(memory_register_types)
diff --git a/softmmu/physmem.c b/softmmu/physmem.c
index 9b171c9..3c1912a 100644
--- a/softmmu/physmem.c
+++ b/softmmu/physmem.c
@@ -3684,56 +3684,106 @@ void mtree_print_dispatch(AddressSpaceDispatch *d, MemoryRegion *root)
}
}
-/*
- * If positive, discarding RAM is disabled. If negative, discarding RAM is
- * required to work and cannot be disabled.
- */
-static int ram_block_discard_disabled;
+/* Require any discards to work. */
+static unsigned int ram_block_discard_required_cnt;
+/* Require only coordinated discards to work. */
+static unsigned int ram_block_coordinated_discard_required_cnt;
+/* Disable any discards. */
+static unsigned int ram_block_discard_disabled_cnt;
+/* Disable only uncoordinated discards. */
+static unsigned int ram_block_uncoordinated_discard_disabled_cnt;
+static QemuMutex ram_block_discard_disable_mutex;
+
+static void ram_block_discard_disable_mutex_lock(void)
+{
+ static gsize initialized;
+
+ if (g_once_init_enter(&initialized)) {
+ qemu_mutex_init(&ram_block_discard_disable_mutex);
+ g_once_init_leave(&initialized, 1);
+ }
+ qemu_mutex_lock(&ram_block_discard_disable_mutex);
+}
+
+static void ram_block_discard_disable_mutex_unlock(void)
+{
+ qemu_mutex_unlock(&ram_block_discard_disable_mutex);
+}
int ram_block_discard_disable(bool state)
{
- int old;
+ int ret = 0;
+ ram_block_discard_disable_mutex_lock();
if (!state) {
- qatomic_dec(&ram_block_discard_disabled);
- return 0;
+ ram_block_discard_disabled_cnt--;
+ } else if (ram_block_discard_required_cnt ||
+ ram_block_coordinated_discard_required_cnt) {
+ ret = -EBUSY;
+ } else {
+ ram_block_discard_disabled_cnt++;
}
+ ram_block_discard_disable_mutex_unlock();
+ return ret;
+}
- do {
- old = qatomic_read(&ram_block_discard_disabled);
- if (old < 0) {
- return -EBUSY;
- }
- } while (qatomic_cmpxchg(&ram_block_discard_disabled,
- old, old + 1) != old);
- return 0;
+int ram_block_uncoordinated_discard_disable(bool state)
+{
+ int ret = 0;
+
+ ram_block_discard_disable_mutex_lock();
+ if (!state) {
+ ram_block_uncoordinated_discard_disabled_cnt--;
+ } else if (ram_block_discard_required_cnt) {
+ ret = -EBUSY;
+ } else {
+ ram_block_uncoordinated_discard_disabled_cnt++;
+ }
+ ram_block_discard_disable_mutex_unlock();
+ return ret;
}
int ram_block_discard_require(bool state)
{
- int old;
+ int ret = 0;
+ ram_block_discard_disable_mutex_lock();
if (!state) {
- qatomic_inc(&ram_block_discard_disabled);
- return 0;
+ ram_block_discard_required_cnt--;
+ } else if (ram_block_discard_disabled_cnt ||
+ ram_block_uncoordinated_discard_disabled_cnt) {
+ ret = -EBUSY;
+ } else {
+ ram_block_discard_required_cnt++;
}
+ ram_block_discard_disable_mutex_unlock();
+ return ret;
+}
- do {
- old = qatomic_read(&ram_block_discard_disabled);
- if (old > 0) {
- return -EBUSY;
- }
- } while (qatomic_cmpxchg(&ram_block_discard_disabled,
- old, old - 1) != old);
- return 0;
+int ram_block_coordinated_discard_require(bool state)
+{
+ int ret = 0;
+
+ ram_block_discard_disable_mutex_lock();
+ if (!state) {
+ ram_block_coordinated_discard_required_cnt--;
+ } else if (ram_block_discard_disabled_cnt) {
+ ret = -EBUSY;
+ } else {
+ ram_block_coordinated_discard_required_cnt++;
+ }
+ ram_block_discard_disable_mutex_unlock();
+ return ret;
}
bool ram_block_discard_is_disabled(void)
{
- return qatomic_read(&ram_block_discard_disabled) > 0;
+ return qatomic_read(&ram_block_discard_disabled_cnt) ||
+ qatomic_read(&ram_block_uncoordinated_discard_disabled_cnt);
}
bool ram_block_discard_is_required(void)
{
- return qatomic_read(&ram_block_discard_disabled) < 0;
+ return qatomic_read(&ram_block_discard_required_cnt) ||
+ qatomic_read(&ram_block_coordinated_discard_required_cnt);
}
diff --git a/target/arm/helper.c b/target/arm/helper.c
index a66c1f0..910ace4 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -6326,11 +6326,21 @@ static const ARMCPRegInfo debug_cp_reginfo[] = {
.access = PL1_RW, .accessfn = access_tda,
.fieldoffset = offsetof(CPUARMState, cp15.mdscr_el1),
.resetvalue = 0 },
- /* MDCCSR_EL0, aka DBGDSCRint. This is a read-only mirror of MDSCR_EL1.
+ /*
+ * MDCCSR_EL0[30:29] map to EDSCR[30:29]. Simply RAZ as the external
+ * Debug Communication Channel is not implemented.
+ */
+ { .name = "MDCCSR_EL0", .state = ARM_CP_STATE_AA64,
+ .opc0 = 2, .opc1 = 3, .crn = 0, .crm = 1, .opc2 = 0,
+ .access = PL0_R, .accessfn = access_tda,
+ .type = ARM_CP_CONST, .resetvalue = 0 },
+ /*
+ * DBGDSCRint[15,12,5:2] map to MDSCR_EL1[15,12,5:2]. Map all bits as
+ * it is unlikely a guest will care.
* We don't implement the configurable EL0 access.
*/
- { .name = "MDCCSR_EL0", .state = ARM_CP_STATE_BOTH,
- .cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = 1, .opc2 = 0,
+ { .name = "DBGDSCRint", .state = ARM_CP_STATE_AA32,
+ .cp = 14, .opc1 = 0, .crn = 0, .crm = 1, .opc2 = 0,
.type = ARM_CP_ALIAS,
.access = PL1_R, .accessfn = access_tda,
.fieldoffset = offsetof(CPUARMState, cp15.mdscr_el1), },
diff --git a/target/ppc/arch_dump.c b/target/ppc/arch_dump.c
index 9210e61..bb392f6 100644
--- a/target/ppc/arch_dump.c
+++ b/target/ppc/arch_dump.c
@@ -227,22 +227,20 @@ int cpu_get_dump_info(ArchDumpInfo *info,
const struct GuestPhysBlockList *guest_phys_blocks)
{
PowerPCCPU *cpu;
- PowerPCCPUClass *pcc;
if (first_cpu == NULL) {
return -1;
}
cpu = POWERPC_CPU(first_cpu);
- pcc = POWERPC_CPU_GET_CLASS(cpu);
info->d_machine = PPC_ELF_MACHINE;
info->d_class = ELFCLASS;
- if ((*pcc->interrupts_big_endian)(cpu)) {
- info->d_endian = ELFDATA2MSB;
- } else {
+ if (ppc_interrupts_little_endian(cpu)) {
info->d_endian = ELFDATA2LSB;
+ } else {
+ info->d_endian = ELFDATA2MSB;
}
/* 64KB is the max page size for pseries kernel */
if (strncmp(object_get_typename(qdev_get_machine()),
diff --git a/target/ppc/cpu-qom.h b/target/ppc/cpu-qom.h
index 06b6571..5800fa3 100644
--- a/target/ppc/cpu-qom.h
+++ b/target/ppc/cpu-qom.h
@@ -198,8 +198,6 @@ struct PowerPCCPUClass {
int n_host_threads;
void (*init_proc)(CPUPPCState *env);
int (*check_pow)(CPUPPCState *env);
- int (*handle_mmu_fault)(PowerPCCPU *cpu, vaddr eaddr, int rwx, int mmu_idx);
- bool (*interrupts_big_endian)(PowerPCCPU *cpu);
};
#ifndef CONFIG_USER_ONLY
diff --git a/target/ppc/cpu.c b/target/ppc/cpu.c
index 19d67b5..a292998 100644
--- a/target/ppc/cpu.c
+++ b/target/ppc/cpu.c
@@ -72,7 +72,7 @@ void ppc_store_sdr1(CPUPPCState *env, target_ulong value)
{
PowerPCCPU *cpu = env_archcpu(env);
qemu_log_mask(CPU_LOG_MMU, "%s: " TARGET_FMT_lx "\n", __func__, value);
- assert(!cpu->vhyp);
+ assert(!cpu->env.has_hv_mode || !cpu->vhyp);
#if defined(TARGET_PPC64)
if (mmu_is_64bit(env->mmu_model)) {
target_ulong sdr_mask = SDR_64_HTABORG | SDR_64_HTABSIZE;
diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h
index b4de0db..93d308a 100644
--- a/target/ppc/cpu.h
+++ b/target/ppc/cpu.h
@@ -2643,6 +2643,21 @@ static inline bool ppc_has_spr(PowerPCCPU *cpu, int spr)
return cpu->env.spr_cb[spr].name != NULL;
}
+static inline bool ppc_interrupts_little_endian(PowerPCCPU *cpu)
+{
+ PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cpu);
+
+ /*
+ * Only models that have an LPCR and know about LPCR_ILE can do little
+ * endian.
+ */
+ if (pcc->lpcr_mask & LPCR_ILE) {
+ return !!(cpu->env.spr[SPR_LPCR] & LPCR_ILE);
+ }
+
+ return false;
+}
+
void dump_mmu(CPUPPCState *env);
void ppc_maybe_bswap_register(CPUPPCState *env, uint8_t *mem_buf, int len);
diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c
index d0411e7..505a0ed 100644
--- a/target/ppc/cpu_init.c
+++ b/target/ppc/cpu_init.c
@@ -2666,18 +2666,6 @@ static int check_pow_hid0_74xx(CPUPPCState *env)
return 0;
}
-static bool ppc_cpu_interrupts_big_endian_always(PowerPCCPU *cpu)
-{
- return true;
-}
-
-#ifdef TARGET_PPC64
-static bool ppc_cpu_interrupts_big_endian_lpcr(PowerPCCPU *cpu)
-{
- return !(cpu->env.spr[SPR_LPCR] & LPCR_ILE);
-}
-#endif
-
/*****************************************************************************/
/* PowerPC implementations definitions */
@@ -4578,9 +4566,6 @@ POWERPC_FAMILY(601)(ObjectClass *oc, void *data)
(1ull << MSR_IR) |
(1ull << MSR_DR);
pcc->mmu_model = POWERPC_MMU_601;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_601;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_601;
@@ -4623,9 +4608,6 @@ POWERPC_FAMILY(601v)(ObjectClass *oc, void *data)
(1ull << MSR_IR) |
(1ull << MSR_DR);
pcc->mmu_model = POWERPC_MMU_601;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_601;
pcc->flags = POWERPC_FLAG_SE | POWERPC_FLAG_RTC_CLK | POWERPC_FLAG_HID0_LE;
@@ -4889,9 +4871,6 @@ POWERPC_FAMILY(604)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_604;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_604;
@@ -4973,9 +4952,6 @@ POWERPC_FAMILY(604E)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_604;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_604;
@@ -5044,9 +5020,6 @@ POWERPC_FAMILY(740)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_7x0;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_750;
@@ -5124,9 +5097,6 @@ POWERPC_FAMILY(750)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_7x0;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_750;
@@ -5327,9 +5297,6 @@ POWERPC_FAMILY(750cl)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_7x0;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_750;
@@ -5410,9 +5377,6 @@ POWERPC_FAMILY(750cx)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_7x0;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_750;
@@ -5498,9 +5462,6 @@ POWERPC_FAMILY(750fx)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_7x0;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_750;
@@ -5586,9 +5547,6 @@ POWERPC_FAMILY(750gx)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_7x0;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_750;
@@ -5828,9 +5786,6 @@ POWERPC_FAMILY(7400)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_74xx;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_7400;
@@ -5914,9 +5869,6 @@ POWERPC_FAMILY(7410)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_74xx;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_7400;
@@ -6743,9 +6695,6 @@ POWERPC_FAMILY(e600)(ObjectClass *oc, void *data)
(1ull << MSR_RI) |
(1ull << MSR_LE);
pcc->mmu_model = POWERPC_MMU_32B;
-#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash32_handle_mmu_fault;
-#endif
pcc->excp_model = POWERPC_EXCP_74xx;
pcc->bus_model = PPC_FLAGS_INPUT_6xx;
pcc->bfd_mach = bfd_mach_ppc_7400;
@@ -7505,7 +7454,6 @@ POWERPC_FAMILY(970)(ObjectClass *oc, void *data)
(1ull << MSR_RI);
pcc->mmu_model = POWERPC_MMU_64B;
#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash64_handle_mmu_fault;
pcc->hash64_opts = &ppc_hash64_opts_basic;
#endif
pcc->excp_model = POWERPC_EXCP_970;
@@ -7583,7 +7531,6 @@ POWERPC_FAMILY(POWER5P)(ObjectClass *oc, void *data)
LPCR_RMI | LPCR_HDICE;
pcc->mmu_model = POWERPC_MMU_2_03;
#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash64_handle_mmu_fault;
pcc->hash64_opts = &ppc_hash64_opts_basic;
pcc->lrg_decr_bits = 32;
#endif
@@ -7727,7 +7674,6 @@ POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
pcc->lpcr_pm = LPCR_P7_PECE0 | LPCR_P7_PECE1 | LPCR_P7_PECE2;
pcc->mmu_model = POWERPC_MMU_2_06;
#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash64_handle_mmu_fault;
pcc->hash64_opts = &ppc_hash64_opts_POWER7;
pcc->lrg_decr_bits = 32;
#endif
@@ -7740,7 +7686,6 @@ POWERPC_FAMILY(POWER7)(ObjectClass *oc, void *data)
POWERPC_FLAG_VSX;
pcc->l1_dcache_size = 0x8000;
pcc->l1_icache_size = 0x8000;
- pcc->interrupts_big_endian = ppc_cpu_interrupts_big_endian_lpcr;
}
static void init_proc_POWER8(CPUPPCState *env)
@@ -7904,7 +7849,6 @@ POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
LPCR_P8_PECE3 | LPCR_P8_PECE4;
pcc->mmu_model = POWERPC_MMU_2_07;
#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc_hash64_handle_mmu_fault;
pcc->hash64_opts = &ppc_hash64_opts_POWER7;
pcc->lrg_decr_bits = 32;
pcc->n_host_threads = 8;
@@ -7918,7 +7862,6 @@ POWERPC_FAMILY(POWER8)(ObjectClass *oc, void *data)
POWERPC_FLAG_VSX | POWERPC_FLAG_TM;
pcc->l1_dcache_size = 0x8000;
pcc->l1_icache_size = 0x8000;
- pcc->interrupts_big_endian = ppc_cpu_interrupts_big_endian_lpcr;
}
#ifdef CONFIG_SOFTMMU
@@ -8120,7 +8063,6 @@ POWERPC_FAMILY(POWER9)(ObjectClass *oc, void *data)
pcc->lpcr_pm = LPCR_PDEE | LPCR_HDEE | LPCR_EEE | LPCR_DEE | LPCR_OEE;
pcc->mmu_model = POWERPC_MMU_3_00;
#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc64_v3_handle_mmu_fault;
/* segment page size remain the same */
pcc->hash64_opts = &ppc_hash64_opts_POWER7;
pcc->radix_page_info = &POWER9_radix_page_info;
@@ -8136,7 +8078,6 @@ POWERPC_FAMILY(POWER9)(ObjectClass *oc, void *data)
POWERPC_FLAG_VSX | POWERPC_FLAG_TM | POWERPC_FLAG_SCV;
pcc->l1_dcache_size = 0x8000;
pcc->l1_icache_size = 0x8000;
- pcc->interrupts_big_endian = ppc_cpu_interrupts_big_endian_lpcr;
}
#ifdef CONFIG_SOFTMMU
@@ -8332,7 +8273,6 @@ POWERPC_FAMILY(POWER10)(ObjectClass *oc, void *data)
pcc->lpcr_pm = LPCR_PDEE | LPCR_HDEE | LPCR_EEE | LPCR_DEE | LPCR_OEE;
pcc->mmu_model = POWERPC_MMU_3_00;
#if defined(CONFIG_SOFTMMU)
- pcc->handle_mmu_fault = ppc64_v3_handle_mmu_fault;
/* segment page size remain the same */
pcc->hash64_opts = &ppc_hash64_opts_POWER7;
pcc->radix_page_info = &POWER10_radix_page_info;
@@ -8347,7 +8287,6 @@ POWERPC_FAMILY(POWER10)(ObjectClass *oc, void *data)
POWERPC_FLAG_VSX | POWERPC_FLAG_TM | POWERPC_FLAG_SCV;
pcc->l1_dcache_size = 0x8000;
pcc->l1_icache_size = 0x8000;
- pcc->interrupts_big_endian = ppc_cpu_interrupts_big_endian_lpcr;
}
#if !defined(CONFIG_USER_ONLY)
@@ -8908,9 +8847,11 @@ static void ppc_cpu_reset(DeviceState *dev)
#if !defined(CONFIG_USER_ONLY)
env->nip = env->hreset_vector | env->excp_prefix;
+#if defined(CONFIG_TCG)
if (env->mmu_model != POWERPC_MMU_REAL) {
ppc_tlb_invalidate_all(env);
}
+#endif /* CONFIG_TCG */
#endif
hreg_compute_hflags(env);
@@ -9094,7 +9035,6 @@ static void ppc_cpu_class_init(ObjectClass *oc, void *data)
device_class_set_parent_unrealize(dc, ppc_cpu_unrealize,
&pcc->parent_unrealize);
pcc->pvr_match = ppc_pvr_match_default;
- pcc->interrupts_big_endian = ppc_cpu_interrupts_big_endian_always;
device_class_set_props(dc, ppc_cpu_properties);
device_class_set_parent_reset(dc, ppc_cpu_reset, &pcc->parent_reset);
diff --git a/target/ppc/excp_helper.c b/target/ppc/excp_helper.c
index fd147e2..a79a0ed 100644
--- a/target/ppc/excp_helper.c
+++ b/target/ppc/excp_helper.c
@@ -1099,7 +1099,6 @@ void ppc_cpu_do_fwnmi_machine_check(CPUState *cs, target_ulong vector)
{
PowerPCCPU *cpu = POWERPC_CPU(cs);
CPUPPCState *env = &cpu->env;
- PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cpu);
target_ulong msr = 0;
/*
@@ -1108,7 +1107,7 @@ void ppc_cpu_do_fwnmi_machine_check(CPUState *cs, target_ulong vector)
*/
msr = (1ULL << MSR_ME);
msr |= env->msr & (1ULL << MSR_SF);
- if (!(*pcc->interrupts_big_endian)(cpu)) {
+ if (ppc_interrupts_little_endian(cpu)) {
msr |= (1ULL << MSR_LE);
}
diff --git a/target/ppc/kvm.c b/target/ppc/kvm.c
index 104a308..dc93b99 100644
--- a/target/ppc/kvm.c
+++ b/target/ppc/kvm.c
@@ -89,6 +89,7 @@ static int cap_ppc_count_cache_flush_assist;
static int cap_ppc_nested_kvm_hv;
static int cap_large_decr;
static int cap_fwnmi;
+static int cap_rpt_invalidate;
static uint32_t debug_inst_opcode;
@@ -152,6 +153,7 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
exit(1);
}
+ cap_rpt_invalidate = kvm_vm_check_extension(s, KVM_CAP_PPC_RPT_INVALIDATE);
kvm_ppc_register_host_cpu_type();
return 0;
@@ -2040,6 +2042,11 @@ void kvmppc_enable_h_page_init(void)
kvmppc_enable_hcall(kvm_state, H_PAGE_INIT);
}
+void kvmppc_enable_h_rpt_invalidate(void)
+{
+ kvmppc_enable_hcall(kvm_state, H_RPT_INVALIDATE);
+}
+
void kvmppc_set_papr(PowerPCCPU *cpu)
{
CPUState *cs = CPU(cpu);
@@ -2551,6 +2558,11 @@ int kvmppc_enable_cap_large_decr(PowerPCCPU *cpu, int enable)
return 0;
}
+int kvmppc_has_cap_rpt_invalidate(void)
+{
+ return cap_rpt_invalidate;
+}
+
PowerPCCPUClass *kvm_ppc_get_host_cpu_class(void)
{
uint32_t host_pvr = mfpvr();
diff --git a/target/ppc/kvm_ppc.h b/target/ppc/kvm_ppc.h
index 989f61a..ee9325b 100644
--- a/target/ppc/kvm_ppc.h
+++ b/target/ppc/kvm_ppc.h
@@ -24,6 +24,7 @@ void kvmppc_enable_logical_ci_hcalls(void);
void kvmppc_enable_set_mode_hcall(void);
void kvmppc_enable_clear_ref_mod_hcalls(void);
void kvmppc_enable_h_page_init(void);
+void kvmppc_enable_h_rpt_invalidate(void);
void kvmppc_set_papr(PowerPCCPU *cpu);
int kvmppc_set_compat(PowerPCCPU *cpu, uint32_t compat_pvr);
void kvmppc_set_mpic_proxy(PowerPCCPU *cpu, int mpic_proxy);
@@ -71,6 +72,7 @@ bool kvmppc_has_cap_nested_kvm_hv(void);
int kvmppc_set_cap_nested_kvm_hv(int enable);
int kvmppc_get_cap_large_decr(void);
int kvmppc_enable_cap_large_decr(PowerPCCPU *cpu, int enable);
+int kvmppc_has_cap_rpt_invalidate(void);
int kvmppc_enable_hwrng(void);
int kvmppc_put_books_sregs(PowerPCCPU *cpu);
PowerPCCPUClass *kvm_ppc_get_host_cpu_class(void);
@@ -150,6 +152,11 @@ static inline void kvmppc_enable_h_page_init(void)
{
}
+static inline void kvmppc_enable_h_rpt_invalidate(void)
+{
+ g_assert_not_reached();
+}
+
static inline void kvmppc_set_papr(PowerPCCPU *cpu)
{
}
@@ -381,6 +388,11 @@ static inline int kvmppc_enable_cap_large_decr(PowerPCCPU *cpu, int enable)
return -1;
}
+static inline int kvmppc_has_cap_rpt_invalidate(void)
+{
+ return false;
+}
+
static inline int kvmppc_enable_hwrng(void)
{
return -1;
diff --git a/target/ppc/mmu-book3s-v3.c b/target/ppc/mmu-book3s-v3.c
index c78fd8d..f4985ba 100644
--- a/target/ppc/mmu-book3s-v3.c
+++ b/target/ppc/mmu-book3s-v3.c
@@ -23,25 +23,6 @@
#include "mmu-book3s-v3.h"
#include "mmu-radix64.h"
-int ppc64_v3_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
- int mmu_idx)
-{
- if (ppc64_v3_radix(cpu)) { /* Guest uses radix */
- return ppc_radix64_handle_mmu_fault(cpu, eaddr, rwx, mmu_idx);
- } else { /* Guest uses hash */
- return ppc_hash64_handle_mmu_fault(cpu, eaddr, rwx, mmu_idx);
- }
-}
-
-hwaddr ppc64_v3_get_phys_page_debug(PowerPCCPU *cpu, vaddr eaddr)
-{
- if (ppc64_v3_radix(cpu)) {
- return ppc_radix64_get_phys_page_debug(cpu, eaddr);
- } else {
- return ppc_hash64_get_phys_page_debug(cpu, eaddr);
- }
-}
-
bool ppc64_v3_get_pate(PowerPCCPU *cpu, target_ulong lpid, ppc_v3_pate_t *entry)
{
uint64_t patb = cpu->env.spr[SPR_PTCR] & PTCR_PATB;
diff --git a/target/ppc/mmu-book3s-v3.h b/target/ppc/mmu-book3s-v3.h
index 7b89be5..d6d5ed8 100644
--- a/target/ppc/mmu-book3s-v3.h
+++ b/target/ppc/mmu-book3s-v3.h
@@ -21,6 +21,7 @@
#define PPC_MMU_BOOK3S_V3_H
#include "mmu-hash64.h"
+#include "mmu-books.h"
#ifndef CONFIG_USER_ONLY
@@ -67,11 +68,6 @@ static inline bool ppc64_v3_radix(PowerPCCPU *cpu)
return !!(cpu->env.spr[SPR_LPCR] & LPCR_HR);
}
-hwaddr ppc64_v3_get_phys_page_debug(PowerPCCPU *cpu, vaddr eaddr);
-
-int ppc64_v3_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
- int mmu_idx);
-
static inline hwaddr ppc_hash64_hpt_base(PowerPCCPU *cpu)
{
uint64_t base;
diff --git a/target/ppc/mmu-books.h b/target/ppc/mmu-books.h
new file mode 100644
index 0000000..0d12551
--- /dev/null
+++ b/target/ppc/mmu-books.h
@@ -0,0 +1,30 @@
+/*
+ * PowerPC BookS emulation generic mmu definitions for qemu.
+ *
+ * Copyright (c) 2021 Instituto de Pesquisas Eldorado (eldorado.org.br)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PPC_MMU_BOOKS_H
+#define PPC_MMU_BOOKS_H
+
+/*
+ * These correspond to the mmu_idx values computed in
+ * hreg_compute_hflags_value. See the tables therein
+ */
+static inline bool mmuidx_pr(int idx) { return !(idx & 1); }
+static inline bool mmuidx_real(int idx) { return idx & 2; }
+static inline bool mmuidx_hv(int idx) { return idx & 4; }
+#endif /* PPC_MMU_BOOKS_H */
diff --git a/target/ppc/mmu-hash32.c b/target/ppc/mmu-hash32.c
index 9f0a497..3957aab 100644
--- a/target/ppc/mmu-hash32.c
+++ b/target/ppc/mmu-hash32.c
@@ -25,9 +25,10 @@
#include "kvm_ppc.h"
#include "internal.h"
#include "mmu-hash32.h"
+#include "mmu-books.h"
#include "exec/log.h"
-/* #define DEBUG_BAT */
+/* #define DEBUG_BATS */
#ifdef DEBUG_BATS
# define LOG_BATS(...) qemu_log_mask(CPU_LOG_MMU, __VA_ARGS__)
@@ -86,25 +87,22 @@ static int ppc_hash32_pp_prot(int key, int pp, int nx)
return prot;
}
-static int ppc_hash32_pte_prot(PowerPCCPU *cpu,
+static int ppc_hash32_pte_prot(int mmu_idx,
target_ulong sr, ppc_hash_pte32_t pte)
{
- CPUPPCState *env = &cpu->env;
unsigned pp, key;
- key = !!(msr_pr ? (sr & SR32_KP) : (sr & SR32_KS));
+ key = !!(mmuidx_pr(mmu_idx) ? (sr & SR32_KP) : (sr & SR32_KS));
pp = pte.pte1 & HPTE32_R_PP;
return ppc_hash32_pp_prot(key, pp, !!(sr & SR32_NX));
}
-static target_ulong hash32_bat_size(PowerPCCPU *cpu,
+static target_ulong hash32_bat_size(int mmu_idx,
target_ulong batu, target_ulong batl)
{
- CPUPPCState *env = &cpu->env;
-
- if ((msr_pr && !(batu & BATU32_VP))
- || (!msr_pr && !(batu & BATU32_VS))) {
+ if ((mmuidx_pr(mmu_idx) && !(batu & BATU32_VP))
+ || (!mmuidx_pr(mmu_idx) && !(batu & BATU32_VS))) {
return 0;
}
@@ -137,14 +135,13 @@ static target_ulong hash32_bat_601_size(PowerPCCPU *cpu,
return BATU32_BEPI & ~((batl & BATL32_601_BL) << 17);
}
-static int hash32_bat_601_prot(PowerPCCPU *cpu,
+static int hash32_bat_601_prot(int mmu_idx,
target_ulong batu, target_ulong batl)
{
- CPUPPCState *env = &cpu->env;
int key, pp;
pp = batu & BATU32_601_PP;
- if (msr_pr == 0) {
+ if (mmuidx_pr(mmu_idx) == 0) {
key = !!(batu & BATU32_601_KS);
} else {
key = !!(batu & BATU32_601_KP);
@@ -153,7 +150,8 @@ static int hash32_bat_601_prot(PowerPCCPU *cpu,
}
static hwaddr ppc_hash32_bat_lookup(PowerPCCPU *cpu, target_ulong ea,
- MMUAccessType access_type, int *prot)
+ MMUAccessType access_type, int *prot,
+ int mmu_idx)
{
CPUPPCState *env = &cpu->env;
target_ulong *BATlt, *BATut;
@@ -177,7 +175,7 @@ static hwaddr ppc_hash32_bat_lookup(PowerPCCPU *cpu, target_ulong ea,
if (unlikely(env->mmu_model == POWERPC_MMU_601)) {
mask = hash32_bat_601_size(cpu, batu, batl);
} else {
- mask = hash32_bat_size(cpu, batu, batl);
+ mask = hash32_bat_size(mmu_idx, batu, batl);
}
LOG_BATS("%s: %cBAT%d v " TARGET_FMT_lx " BATu " TARGET_FMT_lx
" BATl " TARGET_FMT_lx "\n", __func__,
@@ -187,7 +185,7 @@ static hwaddr ppc_hash32_bat_lookup(PowerPCCPU *cpu, target_ulong ea,
hwaddr raddr = (batl & mask) | (ea & ~mask);
if (unlikely(env->mmu_model == POWERPC_MMU_601)) {
- *prot = hash32_bat_601_prot(cpu, batu, batl);
+ *prot = hash32_bat_601_prot(mmu_idx, batu, batl);
} else {
*prot = hash32_bat_prot(cpu, batu, batl);
}
@@ -199,6 +197,9 @@ static hwaddr ppc_hash32_bat_lookup(PowerPCCPU *cpu, target_ulong ea,
/* No hit */
#if defined(DEBUG_BATS)
if (qemu_log_enabled()) {
+ target_ulong *BATu, *BATl;
+ target_ulong BEPIl, BEPIu, bl;
+
LOG_BATS("no BAT match for " TARGET_FMT_lx ":\n", ea);
for (i = 0; i < 4; i++) {
BATu = &BATut[i];
@@ -218,14 +219,15 @@ static hwaddr ppc_hash32_bat_lookup(PowerPCCPU *cpu, target_ulong ea,
return -1;
}
-static int ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
- target_ulong eaddr,
- MMUAccessType access_type,
- hwaddr *raddr, int *prot)
+static bool ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
+ target_ulong eaddr,
+ MMUAccessType access_type,
+ hwaddr *raddr, int *prot, int mmu_idx,
+ bool guest_visible)
{
CPUState *cs = CPU(cpu);
CPUPPCState *env = &cpu->env;
- int key = !!(msr_pr ? (sr & SR32_KP) : (sr & SR32_KS));
+ int key = !!(mmuidx_pr(mmu_idx) ? (sr & SR32_KP) : (sr & SR32_KS));
qemu_log_mask(CPU_LOG_MMU, "direct store...\n");
@@ -238,17 +240,23 @@ static int ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
*/
*raddr = ((sr & 0xF) << 28) | (eaddr & 0x0FFFFFFF);
*prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
- return 0;
+ return true;
}
if (access_type == MMU_INST_FETCH) {
/* No code fetch is allowed in direct-store areas */
- cs->exception_index = POWERPC_EXCP_ISI;
- env->error_code = 0x10000000;
- return 1;
+ if (guest_visible) {
+ cs->exception_index = POWERPC_EXCP_ISI;
+ env->error_code = 0x10000000;
+ }
+ return false;
}
- switch (env->access_type) {
+ /*
+ * From ppc_cpu_get_phys_page_debug, env->access_type is not set.
+ * Assume ACCESS_INT for that case.
+ */
+ switch (guest_visible ? env->access_type : ACCESS_INT) {
case ACCESS_INT:
/* Integer load/store : only access allowed */
break;
@@ -257,7 +265,7 @@ static int ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
cs->exception_index = POWERPC_EXCP_ALIGN;
env->error_code = POWERPC_EXCP_ALIGN_FP;
env->spr[SPR_DAR] = eaddr;
- return 1;
+ return false;
case ACCESS_RES:
/* lwarx, ldarx or srwcx. */
env->error_code = 0;
@@ -267,7 +275,7 @@ static int ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
} else {
env->spr[SPR_DSISR] = 0x04000000;
}
- return 1;
+ return false;
case ACCESS_CACHE:
/*
* dcba, dcbt, dcbtst, dcbf, dcbi, dcbst, dcbz, or icbi
@@ -276,7 +284,7 @@ static int ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
* no-op, it's quite easy :-)
*/
*raddr = eaddr;
- return 0;
+ return true;
case ACCESS_EXT:
/* eciwx or ecowx */
cs->exception_index = POWERPC_EXCP_DSI;
@@ -287,16 +295,18 @@ static int ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
} else {
env->spr[SPR_DSISR] = 0x04100000;
}
- return 1;
+ return false;
default:
- cpu_abort(cs, "ERROR: instruction should not need "
- "address translation\n");
+ cpu_abort(cs, "ERROR: insn should not need address translation\n");
}
- if ((access_type == MMU_DATA_STORE || key != 1) &&
- (access_type == MMU_DATA_LOAD || key != 0)) {
+
+ *prot = key ? PAGE_READ | PAGE_WRITE : PAGE_READ;
+ if (*prot & prot_for_access_type(access_type)) {
*raddr = eaddr;
- return 0;
- } else {
+ return true;
+ }
+
+ if (guest_visible) {
cs->exception_index = POWERPC_EXCP_DSI;
env->error_code = 0;
env->spr[SPR_DAR] = eaddr;
@@ -305,8 +315,8 @@ static int ppc_hash32_direct_store(PowerPCCPU *cpu, target_ulong sr,
} else {
env->spr[SPR_DSISR] = 0x08000000;
}
- return 1;
}
+ return false;
}
hwaddr get_pteg_offset32(PowerPCCPU *cpu, hwaddr hash)
@@ -415,8 +425,9 @@ static hwaddr ppc_hash32_pte_raddr(target_ulong sr, ppc_hash_pte32_t pte,
return (rpn & ~mask) | (eaddr & mask);
}
-int ppc_hash32_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
- int mmu_idx)
+bool ppc_hash32_xlate(PowerPCCPU *cpu, vaddr eaddr, MMUAccessType access_type,
+ hwaddr *raddrp, int *psizep, int *protp, int mmu_idx,
+ bool guest_visible)
{
CPUState *cs = CPU(cpu);
CPUPPCState *env = &cpu->env;
@@ -425,48 +436,45 @@ int ppc_hash32_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
ppc_hash_pte32_t pte;
int prot;
int need_prot;
- MMUAccessType access_type;
hwaddr raddr;
- assert((rwx == 0) || (rwx == 1) || (rwx == 2));
- access_type = rwx;
- need_prot = prot_for_access_type(access_type);
+ /* There are no hash32 large pages. */
+ *psizep = TARGET_PAGE_BITS;
/* 1. Handle real mode accesses */
- if (access_type == MMU_INST_FETCH ? !msr_ir : !msr_dr) {
+ if (mmuidx_real(mmu_idx)) {
/* Translation is off */
- raddr = eaddr;
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
- PAGE_READ | PAGE_WRITE | PAGE_EXEC, mmu_idx,
- TARGET_PAGE_SIZE);
- return 0;
+ *raddrp = eaddr;
+ *protp = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+ return true;
}
+ need_prot = prot_for_access_type(access_type);
+
/* 2. Check Block Address Translation entries (BATs) */
if (env->nb_BATs != 0) {
- raddr = ppc_hash32_bat_lookup(cpu, eaddr, access_type, &prot);
+ raddr = ppc_hash32_bat_lookup(cpu, eaddr, access_type, protp, mmu_idx);
if (raddr != -1) {
- if (need_prot & ~prot) {
- if (access_type == MMU_INST_FETCH) {
- cs->exception_index = POWERPC_EXCP_ISI;
- env->error_code = 0x08000000;
- } else {
- cs->exception_index = POWERPC_EXCP_DSI;
- env->error_code = 0;
- env->spr[SPR_DAR] = eaddr;
- if (access_type == MMU_DATA_STORE) {
- env->spr[SPR_DSISR] = 0x0a000000;
+ if (need_prot & ~*protp) {
+ if (guest_visible) {
+ if (access_type == MMU_INST_FETCH) {
+ cs->exception_index = POWERPC_EXCP_ISI;
+ env->error_code = 0x08000000;
} else {
- env->spr[SPR_DSISR] = 0x08000000;
+ cs->exception_index = POWERPC_EXCP_DSI;
+ env->error_code = 0;
+ env->spr[SPR_DAR] = eaddr;
+ if (access_type == MMU_DATA_STORE) {
+ env->spr[SPR_DSISR] = 0x0a000000;
+ } else {
+ env->spr[SPR_DSISR] = 0x08000000;
+ }
}
}
- return 1;
+ return false;
}
-
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK,
- raddr & TARGET_PAGE_MASK, prot, mmu_idx,
- TARGET_PAGE_SIZE);
- return 0;
+ *raddrp = raddr;
+ return true;
}
}
@@ -475,67 +483,65 @@ int ppc_hash32_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
/* 4. Handle direct store segments */
if (sr & SR32_T) {
- if (ppc_hash32_direct_store(cpu, sr, eaddr, access_type,
- &raddr, &prot) == 0) {
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK,
- raddr & TARGET_PAGE_MASK, prot, mmu_idx,
- TARGET_PAGE_SIZE);
- return 0;
- } else {
- return 1;
- }
+ return ppc_hash32_direct_store(cpu, sr, eaddr, access_type,
+ raddrp, protp, mmu_idx, guest_visible);
}
/* 5. Check for segment level no-execute violation */
if (access_type == MMU_INST_FETCH && (sr & SR32_NX)) {
- cs->exception_index = POWERPC_EXCP_ISI;
- env->error_code = 0x10000000;
- return 1;
+ if (guest_visible) {
+ cs->exception_index = POWERPC_EXCP_ISI;
+ env->error_code = 0x10000000;
+ }
+ return false;
}
/* 6. Locate the PTE in the hash table */
pte_offset = ppc_hash32_htab_lookup(cpu, sr, eaddr, &pte);
if (pte_offset == -1) {
- if (access_type == MMU_INST_FETCH) {
- cs->exception_index = POWERPC_EXCP_ISI;
- env->error_code = 0x40000000;
- } else {
- cs->exception_index = POWERPC_EXCP_DSI;
- env->error_code = 0;
- env->spr[SPR_DAR] = eaddr;
- if (access_type == MMU_DATA_STORE) {
- env->spr[SPR_DSISR] = 0x42000000;
+ if (guest_visible) {
+ if (access_type == MMU_INST_FETCH) {
+ cs->exception_index = POWERPC_EXCP_ISI;
+ env->error_code = 0x40000000;
} else {
- env->spr[SPR_DSISR] = 0x40000000;
+ cs->exception_index = POWERPC_EXCP_DSI;
+ env->error_code = 0;
+ env->spr[SPR_DAR] = eaddr;
+ if (access_type == MMU_DATA_STORE) {
+ env->spr[SPR_DSISR] = 0x42000000;
+ } else {
+ env->spr[SPR_DSISR] = 0x40000000;
+ }
}
}
-
- return 1;
+ return false;
}
qemu_log_mask(CPU_LOG_MMU,
"found PTE at offset %08" HWADDR_PRIx "\n", pte_offset);
/* 7. Check access permissions */
- prot = ppc_hash32_pte_prot(cpu, sr, pte);
+ prot = ppc_hash32_pte_prot(mmu_idx, sr, pte);
if (need_prot & ~prot) {
/* Access right violation */
qemu_log_mask(CPU_LOG_MMU, "PTE access rejected\n");
- if (access_type == MMU_INST_FETCH) {
- cs->exception_index = POWERPC_EXCP_ISI;
- env->error_code = 0x08000000;
- } else {
- cs->exception_index = POWERPC_EXCP_DSI;
- env->error_code = 0;
- env->spr[SPR_DAR] = eaddr;
- if (access_type == MMU_DATA_STORE) {
- env->spr[SPR_DSISR] = 0x0a000000;
+ if (guest_visible) {
+ if (access_type == MMU_INST_FETCH) {
+ cs->exception_index = POWERPC_EXCP_ISI;
+ env->error_code = 0x08000000;
} else {
- env->spr[SPR_DSISR] = 0x08000000;
+ cs->exception_index = POWERPC_EXCP_DSI;
+ env->error_code = 0;
+ env->spr[SPR_DAR] = eaddr;
+ if (access_type == MMU_DATA_STORE) {
+ env->spr[SPR_DSISR] = 0x0a000000;
+ } else {
+ env->spr[SPR_DSISR] = 0x08000000;
+ }
}
}
- return 1;
+ return false;
}
qemu_log_mask(CPU_LOG_MMU, "PTE access granted !\n");
@@ -559,45 +565,7 @@ int ppc_hash32_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
/* 9. Determine the real address from the PTE */
- raddr = ppc_hash32_pte_raddr(sr, pte, eaddr);
-
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
- prot, mmu_idx, TARGET_PAGE_SIZE);
-
- return 0;
-}
-
-hwaddr ppc_hash32_get_phys_page_debug(PowerPCCPU *cpu, target_ulong eaddr)
-{
- CPUPPCState *env = &cpu->env;
- target_ulong sr;
- hwaddr pte_offset;
- ppc_hash_pte32_t pte;
- int prot;
-
- if (msr_dr == 0) {
- /* Translation is off */
- return eaddr;
- }
-
- if (env->nb_BATs != 0) {
- hwaddr raddr = ppc_hash32_bat_lookup(cpu, eaddr, 0, &prot);
- if (raddr != -1) {
- return raddr;
- }
- }
-
- sr = env->sr[eaddr >> 28];
-
- if (sr & SR32_T) {
- /* FIXME: Add suitable debug support for Direct Store segments */
- return -1;
- }
-
- pte_offset = ppc_hash32_htab_lookup(cpu, sr, eaddr, &pte);
- if (pte_offset == -1) {
- return -1;
- }
-
- return ppc_hash32_pte_raddr(sr, pte, eaddr) & TARGET_PAGE_MASK;
+ *raddrp = ppc_hash32_pte_raddr(sr, pte, eaddr);
+ *protp = prot;
+ return true;
}
diff --git a/target/ppc/mmu-hash32.h b/target/ppc/mmu-hash32.h
index 898021f..3892b69 100644
--- a/target/ppc/mmu-hash32.h
+++ b/target/ppc/mmu-hash32.h
@@ -4,9 +4,9 @@
#ifndef CONFIG_USER_ONLY
hwaddr get_pteg_offset32(PowerPCCPU *cpu, hwaddr hash);
-hwaddr ppc_hash32_get_phys_page_debug(PowerPCCPU *cpu, target_ulong addr);
-int ppc_hash32_handle_mmu_fault(PowerPCCPU *cpu, vaddr address, int rw,
- int mmu_idx);
+bool ppc_hash32_xlate(PowerPCCPU *cpu, vaddr eaddr, MMUAccessType access_type,
+ hwaddr *raddrp, int *psizep, int *protp, int mmu_idx,
+ bool guest_visible);
/*
* Segment register definitions
@@ -22,6 +22,8 @@ int ppc_hash32_handle_mmu_fault(PowerPCCPU *cpu, vaddr address, int rw,
* Block Address Translation (BAT) definitions
*/
+#define BATU32_BEPIU 0xf0000000
+#define BATU32_BEPIL 0x0ffe0000
#define BATU32_BEPI 0xfffe0000
#define BATU32_BL 0x00001ffc
#define BATU32_VS 0x00000002
diff --git a/target/ppc/mmu-hash64.c b/target/ppc/mmu-hash64.c
index 708dffc..19832c4 100644
--- a/target/ppc/mmu-hash64.c
+++ b/target/ppc/mmu-hash64.c
@@ -366,10 +366,9 @@ static inline int ppc_hash64_pte_noexec_guard(PowerPCCPU *cpu,
}
/* Check Basic Storage Protection */
-static int ppc_hash64_pte_prot(PowerPCCPU *cpu,
+static int ppc_hash64_pte_prot(int mmu_idx,
ppc_slb_t *slb, ppc_hash_pte64_t pte)
{
- CPUPPCState *env = &cpu->env;
unsigned pp, key;
/*
* Some pp bit combinations have undefined behaviour, so default
@@ -377,7 +376,7 @@ static int ppc_hash64_pte_prot(PowerPCCPU *cpu,
*/
int prot = 0;
- key = !!(msr_pr ? (slb->vsid & SLB_VSID_KP)
+ key = !!(mmuidx_pr(mmu_idx) ? (slb->vsid & SLB_VSID_KP)
: (slb->vsid & SLB_VSID_KS));
pp = (pte.pte1 & HPTE64_R_PP) | ((pte.pte1 & HPTE64_R_PP0) >> 61);
@@ -744,17 +743,17 @@ static bool ppc_hash64_use_vrma(CPUPPCState *env)
}
}
-static void ppc_hash64_set_isi(CPUState *cs, uint64_t error_code)
+static void ppc_hash64_set_isi(CPUState *cs, int mmu_idx, uint64_t error_code)
{
CPUPPCState *env = &POWERPC_CPU(cs)->env;
bool vpm;
- if (msr_ir) {
+ if (!mmuidx_real(mmu_idx)) {
vpm = !!(env->spr[SPR_LPCR] & LPCR_VPM1);
} else {
vpm = ppc_hash64_use_vrma(env);
}
- if (vpm && !msr_hv) {
+ if (vpm && !mmuidx_hv(mmu_idx)) {
cs->exception_index = POWERPC_EXCP_HISI;
} else {
cs->exception_index = POWERPC_EXCP_ISI;
@@ -762,17 +761,17 @@ static void ppc_hash64_set_isi(CPUState *cs, uint64_t error_code)
env->error_code = error_code;
}
-static void ppc_hash64_set_dsi(CPUState *cs, uint64_t dar, uint64_t dsisr)
+static void ppc_hash64_set_dsi(CPUState *cs, int mmu_idx, uint64_t dar, uint64_t dsisr)
{
CPUPPCState *env = &POWERPC_CPU(cs)->env;
bool vpm;
- if (msr_dr) {
+ if (!mmuidx_real(mmu_idx)) {
vpm = !!(env->spr[SPR_LPCR] & LPCR_VPM1);
} else {
vpm = ppc_hash64_use_vrma(env);
}
- if (vpm && !msr_hv) {
+ if (vpm && !mmuidx_hv(mmu_idx)) {
cs->exception_index = POWERPC_EXCP_HDSI;
env->spr[SPR_HDAR] = dar;
env->spr[SPR_HDSISR] = dsisr;
@@ -873,8 +872,9 @@ static int build_vrma_slbe(PowerPCCPU *cpu, ppc_slb_t *slb)
return -1;
}
-int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
- int rwx, int mmu_idx)
+bool ppc_hash64_xlate(PowerPCCPU *cpu, vaddr eaddr, MMUAccessType access_type,
+ hwaddr *raddrp, int *psizep, int *protp, int mmu_idx,
+ bool guest_visible)
{
CPUState *cs = CPU(cpu);
CPUPPCState *env = &cpu->env;
@@ -884,13 +884,9 @@ int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
hwaddr ptex;
ppc_hash_pte64_t pte;
int exec_prot, pp_prot, amr_prot, prot;
- MMUAccessType access_type;
int need_prot;
hwaddr raddr;
- assert((rwx == 0) || (rwx == 1) || (rwx == 2));
- access_type = rwx;
-
/*
* Note on LPCR usage: 970 uses HID4, but our special variant of
* store_spr copies relevant fields into env->spr[SPR_LPCR].
@@ -900,7 +896,7 @@ int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
*/
/* 1. Handle real mode accesses */
- if (access_type == MMU_INST_FETCH ? !msr_ir : !msr_dr) {
+ if (mmuidx_real(mmu_idx)) {
/*
* Translation is supposedly "off", but in real mode the top 4
* effective address bits are (mostly) ignored
@@ -912,7 +908,7 @@ int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
* In virtual hypervisor mode, there's nothing to do:
* EA == GPA == qemu guest address
*/
- } else if (msr_hv || !env->has_hv_mode) {
+ } else if (mmuidx_hv(mmu_idx) || !env->has_hv_mode) {
/* In HV mode, add HRMOR if top EA bit is clear */
if (!(eaddr >> 63)) {
raddr |= env->spr[SPR_HRMOR];
@@ -922,9 +918,11 @@ int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
slb = &vrma_slbe;
if (build_vrma_slbe(cpu, slb) != 0) {
/* Invalid VRMA setup, machine check */
- cs->exception_index = POWERPC_EXCP_MCHECK;
- env->error_code = 0;
- return 1;
+ if (guest_visible) {
+ cs->exception_index = POWERPC_EXCP_MCHECK;
+ env->error_code = 0;
+ }
+ return false;
}
goto skip_slb_search;
@@ -933,29 +931,33 @@ int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
/* Emulated old-style RMO mode, bounds check against RMLS */
if (raddr >= limit) {
+ if (!guest_visible) {
+ return false;
+ }
switch (access_type) {
case MMU_INST_FETCH:
- ppc_hash64_set_isi(cs, SRR1_PROTFAULT);
+ ppc_hash64_set_isi(cs, mmu_idx, SRR1_PROTFAULT);
break;
case MMU_DATA_LOAD:
- ppc_hash64_set_dsi(cs, eaddr, DSISR_PROTFAULT);
+ ppc_hash64_set_dsi(cs, mmu_idx, eaddr, DSISR_PROTFAULT);
break;
case MMU_DATA_STORE:
- ppc_hash64_set_dsi(cs, eaddr,
+ ppc_hash64_set_dsi(cs, mmu_idx, eaddr,
DSISR_PROTFAULT | DSISR_ISSTORE);
break;
default:
g_assert_not_reached();
}
- return 1;
+ return false;
}
raddr |= env->spr[SPR_RMOR];
}
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
- PAGE_READ | PAGE_WRITE | PAGE_EXEC, mmu_idx,
- TARGET_PAGE_SIZE);
- return 0;
+
+ *raddrp = raddr;
+ *protp = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+ *psizep = TARGET_PAGE_BITS;
+ return true;
}
/* 2. Translation is on, so look up the SLB */
@@ -968,6 +970,9 @@ int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
exit(1);
}
/* Segment still not found, generate the appropriate interrupt */
+ if (!guest_visible) {
+ return false;
+ }
switch (access_type) {
case MMU_INST_FETCH:
cs->exception_index = POWERPC_EXCP_ISEG;
@@ -982,34 +987,39 @@ int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr,
default:
g_assert_not_reached();
}
- return 1;
+ return false;
}
-skip_slb_search:
+ skip_slb_search:
/* 3. Check for segment level no-execute violation */
if (access_type == MMU_INST_FETCH && (slb->vsid & SLB_VSID_N)) {
- ppc_hash64_set_isi(cs, SRR1_NOEXEC_GUARD);
- return 1;
+ if (guest_visible) {
+ ppc_hash64_set_isi(cs, mmu_idx, SRR1_NOEXEC_GUARD);
+ }
+ return false;
}
/* 4. Locate the PTE in the hash table */
ptex = ppc_hash64_htab_lookup(cpu, slb, eaddr, &pte, &apshift);
if (ptex == -1) {
+ if (!guest_visible) {
+ return false;
+ }
switch (access_type) {
case MMU_INST_FETCH:
- ppc_hash64_set_isi(cs, SRR1_NOPTE);
+ ppc_hash64_set_isi(cs, mmu_idx, SRR1_NOPTE);
break;
case MMU_DATA_LOAD:
- ppc_hash64_set_dsi(cs, eaddr, DSISR_NOPTE);
+ ppc_hash64_set_dsi(cs, mmu_idx, eaddr, DSISR_NOPTE);
break;
case MMU_DATA_STORE:
- ppc_hash64_set_dsi(cs, eaddr, DSISR_NOPTE | DSISR_ISSTORE);
+ ppc_hash64_set_dsi(cs, mmu_idx, eaddr, DSISR_NOPTE | DSISR_ISSTORE);
break;
default:
g_assert_not_reached();
}
- return 1;
+ return false;
}
qemu_log_mask(CPU_LOG_MMU,
"found PTE at index %08" HWADDR_PRIx "\n", ptex);
@@ -1017,7 +1027,7 @@ skip_slb_search:
/* 5. Check access permissions */
exec_prot = ppc_hash64_pte_noexec_guard(cpu, pte);
- pp_prot = ppc_hash64_pte_prot(cpu, slb, pte);
+ pp_prot = ppc_hash64_pte_prot(mmu_idx, slb, pte);
amr_prot = ppc_hash64_amr_prot(cpu, pte);
prot = exec_prot & pp_prot & amr_prot;
@@ -1025,6 +1035,9 @@ skip_slb_search:
if (need_prot & ~prot) {
/* Access right violation */
qemu_log_mask(CPU_LOG_MMU, "PTE access rejected\n");
+ if (!guest_visible) {
+ return false;
+ }
if (access_type == MMU_INST_FETCH) {
int srr1 = 0;
if (PAGE_EXEC & ~exec_prot) {
@@ -1035,7 +1048,7 @@ skip_slb_search:
if (PAGE_EXEC & ~amr_prot) {
srr1 |= SRR1_IAMR; /* Access violates virt pg class key prot */
}
- ppc_hash64_set_isi(cs, srr1);
+ ppc_hash64_set_isi(cs, mmu_idx, srr1);
} else {
int dsisr = 0;
if (need_prot & ~pp_prot) {
@@ -1047,9 +1060,9 @@ skip_slb_search:
if (need_prot & ~amr_prot) {
dsisr |= DSISR_AMR;
}
- ppc_hash64_set_dsi(cs, eaddr, dsisr);
+ ppc_hash64_set_dsi(cs, mmu_idx, eaddr, dsisr);
}
- return 1;
+ return false;
}
qemu_log_mask(CPU_LOG_MMU, "PTE access granted !\n");
@@ -1073,66 +1086,10 @@ skip_slb_search:
/* 7. Determine the real address from the PTE */
- raddr = deposit64(pte.pte1 & HPTE64_R_RPN, 0, apshift, eaddr);
-
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
- prot, mmu_idx, 1ULL << apshift);
-
- return 0;
-}
-
-hwaddr ppc_hash64_get_phys_page_debug(PowerPCCPU *cpu, target_ulong addr)
-{
- CPUPPCState *env = &cpu->env;
- ppc_slb_t vrma_slbe;
- ppc_slb_t *slb;
- hwaddr ptex, raddr;
- ppc_hash_pte64_t pte;
- unsigned apshift;
-
- /* Handle real mode */
- if (msr_dr == 0) {
- /* In real mode the top 4 effective address bits are ignored */
- raddr = addr & 0x0FFFFFFFFFFFFFFFULL;
-
- if (cpu->vhyp) {
- /*
- * In virtual hypervisor mode, there's nothing to do:
- * EA == GPA == qemu guest address
- */
- return raddr;
- } else if ((msr_hv || !env->has_hv_mode) && !(addr >> 63)) {
- /* In HV mode, add HRMOR if top EA bit is clear */
- return raddr | env->spr[SPR_HRMOR];
- } else if (ppc_hash64_use_vrma(env)) {
- /* Emulated VRMA mode */
- slb = &vrma_slbe;
- if (build_vrma_slbe(cpu, slb) != 0) {
- return -1;
- }
- } else {
- target_ulong limit = rmls_limit(cpu);
-
- /* Emulated old-style RMO mode, bounds check against RMLS */
- if (raddr >= limit) {
- return -1;
- }
- return raddr | env->spr[SPR_RMOR];
- }
- } else {
- slb = slb_lookup(cpu, addr);
- if (!slb) {
- return -1;
- }
- }
-
- ptex = ppc_hash64_htab_lookup(cpu, slb, addr, &pte, &apshift);
- if (ptex == -1) {
- return -1;
- }
-
- return deposit64(pte.pte1 & HPTE64_R_RPN, 0, apshift, addr)
- & TARGET_PAGE_MASK;
+ *raddrp = deposit64(pte.pte1 & HPTE64_R_RPN, 0, apshift, eaddr);
+ *protp = prot;
+ *psizep = apshift;
+ return true;
}
void ppc_hash64_tlb_flush_hpte(PowerPCCPU *cpu, target_ulong ptex,
diff --git a/target/ppc/mmu-hash64.h b/target/ppc/mmu-hash64.h
index 4b8b8e7..c5b2f97 100644
--- a/target/ppc/mmu-hash64.h
+++ b/target/ppc/mmu-hash64.h
@@ -7,9 +7,9 @@
void dump_slb(PowerPCCPU *cpu);
int ppc_store_slb(PowerPCCPU *cpu, target_ulong slot,
target_ulong esid, target_ulong vsid);
-hwaddr ppc_hash64_get_phys_page_debug(PowerPCCPU *cpu, target_ulong addr);
-int ppc_hash64_handle_mmu_fault(PowerPCCPU *cpu, vaddr address, int rw,
- int mmu_idx);
+bool ppc_hash64_xlate(PowerPCCPU *cpu, vaddr eaddr, MMUAccessType access_type,
+ hwaddr *raddrp, int *psizep, int *protp, int mmu_idx,
+ bool guest_visible);
void ppc_hash64_tlb_flush_hpte(PowerPCCPU *cpu,
target_ulong pte_index,
target_ulong pte0, target_ulong pte1);
diff --git a/target/ppc/mmu-radix64.c b/target/ppc/mmu-radix64.c
index b6d191c..5b0e62e 100644
--- a/target/ppc/mmu-radix64.c
+++ b/target/ppc/mmu-radix64.c
@@ -155,7 +155,7 @@ static void ppc_radix64_raise_hsi(PowerPCCPU *cpu, MMUAccessType access_type,
static bool ppc_radix64_check_prot(PowerPCCPU *cpu, MMUAccessType access_type,
uint64_t pte, int *fault_cause, int *prot,
- bool partition_scoped)
+ int mmu_idx, bool partition_scoped)
{
CPUPPCState *env = &cpu->env;
int need_prot;
@@ -173,7 +173,8 @@ static bool ppc_radix64_check_prot(PowerPCCPU *cpu, MMUAccessType access_type,
/* Determine permissions allowed by Encoded Access Authority */
if (!partition_scoped && (pte & R_PTE_EAA_PRIV) && msr_pr) {
*prot = 0;
- } else if (msr_pr || (pte & R_PTE_EAA_PRIV) || partition_scoped) {
+ } else if (mmuidx_pr(mmu_idx) || (pte & R_PTE_EAA_PRIV) ||
+ partition_scoped) {
*prot = ppc_radix64_get_prot_eaa(pte);
} else { /* !msr_pr && !(pte & R_PTE_EAA_PRIV) && !partition_scoped */
*prot = ppc_radix64_get_prot_eaa(pte);
@@ -299,7 +300,7 @@ static int ppc_radix64_partition_scoped_xlate(PowerPCCPU *cpu,
ppc_v3_pate_t pate,
hwaddr *h_raddr, int *h_prot,
int *h_page_size, bool pde_addr,
- bool guest_visible)
+ int mmu_idx, bool guest_visible)
{
int fault_cause = 0;
hwaddr pte_addr;
@@ -310,7 +311,8 @@ static int ppc_radix64_partition_scoped_xlate(PowerPCCPU *cpu,
if (ppc_radix64_walk_tree(CPU(cpu)->as, g_raddr, pate.dw0 & PRTBE_R_RPDB,
pate.dw0 & PRTBE_R_RPDS, h_raddr, h_page_size,
&pte, &fault_cause, &pte_addr) ||
- ppc_radix64_check_prot(cpu, access_type, pte, &fault_cause, h_prot, true)) {
+ ppc_radix64_check_prot(cpu, access_type, pte,
+ &fault_cause, h_prot, mmu_idx, true)) {
if (pde_addr) { /* address being translated was that of a guest pde */
fault_cause |= DSISR_PRTABLE_FAULT;
}
@@ -332,7 +334,7 @@ static int ppc_radix64_process_scoped_xlate(PowerPCCPU *cpu,
vaddr eaddr, uint64_t pid,
ppc_v3_pate_t pate, hwaddr *g_raddr,
int *g_prot, int *g_page_size,
- bool guest_visible)
+ int mmu_idx, bool guest_visible)
{
CPUState *cs = CPU(cpu);
CPUPPCState *env = &cpu->env;
@@ -367,7 +369,8 @@ static int ppc_radix64_process_scoped_xlate(PowerPCCPU *cpu,
ret = ppc_radix64_partition_scoped_xlate(cpu, 0, eaddr, prtbe_addr,
pate, &h_raddr, &h_prot,
&h_page_size, true,
- guest_visible);
+ /* mmu_idx is 5 because we're translating from hypervisor scope */
+ 5, guest_visible);
if (ret) {
return ret;
}
@@ -407,7 +410,8 @@ static int ppc_radix64_process_scoped_xlate(PowerPCCPU *cpu,
ret = ppc_radix64_partition_scoped_xlate(cpu, 0, eaddr, pte_addr,
pate, &h_raddr, &h_prot,
&h_page_size, true,
- guest_visible);
+ /* mmu_idx is 5 because we're translating from hypervisor scope */
+ 5, guest_visible);
if (ret) {
return ret;
}
@@ -431,7 +435,8 @@ static int ppc_radix64_process_scoped_xlate(PowerPCCPU *cpu,
*g_raddr = (rpn & ~mask) | (eaddr & mask);
}
- if (ppc_radix64_check_prot(cpu, access_type, pte, &fault_cause, g_prot, false)) {
+ if (ppc_radix64_check_prot(cpu, access_type, pte, &fault_cause,
+ g_prot, mmu_idx, false)) {
/* Access denied due to protection */
if (guest_visible) {
ppc_radix64_raise_si(cpu, access_type, eaddr, fault_cause);
@@ -463,24 +468,53 @@ static int ppc_radix64_process_scoped_xlate(PowerPCCPU *cpu,
* | = On | Process Scoped | Scoped |
* +-------------+----------------+---------------+
*/
-static int ppc_radix64_xlate(PowerPCCPU *cpu, vaddr eaddr,
- MMUAccessType access_type,
- bool relocation,
- hwaddr *raddr, int *psizep, int *protp,
- bool guest_visible)
+bool ppc_radix64_xlate(PowerPCCPU *cpu, vaddr eaddr, MMUAccessType access_type,
+ hwaddr *raddr, int *psizep, int *protp, int mmu_idx,
+ bool guest_visible)
{
CPUPPCState *env = &cpu->env;
uint64_t lpid, pid;
ppc_v3_pate_t pate;
int psize, prot;
hwaddr g_raddr;
+ bool relocation;
+
+ assert(!(mmuidx_hv(mmu_idx) && cpu->vhyp));
+
+ relocation = !mmuidx_real(mmu_idx);
+
+ /* HV or virtual hypervisor Real Mode Access */
+ if (!relocation && (mmuidx_hv(mmu_idx) || cpu->vhyp)) {
+ /* In real mode top 4 effective addr bits (mostly) ignored */
+ *raddr = eaddr & 0x0FFFFFFFFFFFFFFFULL;
+
+ /* In HV mode, add HRMOR if top EA bit is clear */
+ if (mmuidx_hv(mmu_idx) || !env->has_hv_mode) {
+ if (!(eaddr >> 63)) {
+ *raddr |= env->spr[SPR_HRMOR];
+ }
+ }
+ *protp = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+ *psizep = TARGET_PAGE_BITS;
+ return true;
+ }
+
+ /*
+ * Check UPRT (we avoid the check in real mode to deal with
+ * transitional states during kexec.
+ */
+ if (guest_visible && !ppc64_use_proc_tbl(cpu)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "LPCR:UPRT not set in radix mode ! LPCR="
+ TARGET_FMT_lx "\n", env->spr[SPR_LPCR]);
+ }
/* Virtual Mode Access - get the fully qualified address */
if (!ppc_radix64_get_fully_qualified_addr(&cpu->env, eaddr, &lpid, &pid)) {
if (guest_visible) {
ppc_radix64_raise_segi(cpu, access_type, eaddr);
}
- return 1;
+ return false;
}
/* Get Process Table */
@@ -493,13 +527,13 @@ static int ppc_radix64_xlate(PowerPCCPU *cpu, vaddr eaddr,
if (guest_visible) {
ppc_radix64_raise_si(cpu, access_type, eaddr, DSISR_NOPTE);
}
- return 1;
+ return false;
}
if (!validate_pate(cpu, lpid, &pate)) {
if (guest_visible) {
ppc_radix64_raise_si(cpu, access_type, eaddr, DSISR_R_BADCONFIG);
}
- return 1;
+ return false;
}
}
@@ -517,9 +551,9 @@ static int ppc_radix64_xlate(PowerPCCPU *cpu, vaddr eaddr,
if (relocation) {
int ret = ppc_radix64_process_scoped_xlate(cpu, access_type, eaddr, pid,
pate, &g_raddr, &prot,
- &psize, guest_visible);
+ &psize, mmu_idx, guest_visible);
if (ret) {
- return ret;
+ return false;
}
*psizep = MIN(*psizep, psize);
*protp &= prot;
@@ -535,15 +569,15 @@ static int ppc_radix64_xlate(PowerPCCPU *cpu, vaddr eaddr,
* quadrants 1 or 2. Translates a guest real address to a host
* real address.
*/
- if (lpid || !msr_hv) {
+ if (lpid || !mmuidx_hv(mmu_idx)) {
int ret;
ret = ppc_radix64_partition_scoped_xlate(cpu, access_type, eaddr,
g_raddr, pate, raddr,
&prot, &psize, false,
- guest_visible);
+ mmu_idx, guest_visible);
if (ret) {
- return ret;
+ return false;
}
*psizep = MIN(*psizep, psize);
*protp &= prot;
@@ -552,78 +586,5 @@ static int ppc_radix64_xlate(PowerPCCPU *cpu, vaddr eaddr,
}
}
- return 0;
-}
-
-int ppc_radix64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
- int mmu_idx)
-{
- CPUState *cs = CPU(cpu);
- CPUPPCState *env = &cpu->env;
- int page_size, prot;
- bool relocation;
- MMUAccessType access_type;
- hwaddr raddr;
-
- assert(!(msr_hv && cpu->vhyp));
- assert((rwx == 0) || (rwx == 1) || (rwx == 2));
- access_type = rwx;
-
- relocation = (access_type == MMU_INST_FETCH ? msr_ir : msr_dr);
- /* HV or virtual hypervisor Real Mode Access */
- if (!relocation && (msr_hv || cpu->vhyp)) {
- /* In real mode top 4 effective addr bits (mostly) ignored */
- raddr = eaddr & 0x0FFFFFFFFFFFFFFFULL;
-
- /* In HV mode, add HRMOR if top EA bit is clear */
- if (msr_hv || !env->has_hv_mode) {
- if (!(eaddr >> 63)) {
- raddr |= env->spr[SPR_HRMOR];
- }
- }
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
- PAGE_READ | PAGE_WRITE | PAGE_EXEC, mmu_idx,
- TARGET_PAGE_SIZE);
- return 0;
- }
-
- /*
- * Check UPRT (we avoid the check in real mode to deal with
- * transitional states during kexec.
- */
- if (!ppc64_use_proc_tbl(cpu)) {
- qemu_log_mask(LOG_GUEST_ERROR,
- "LPCR:UPRT not set in radix mode ! LPCR="
- TARGET_FMT_lx "\n", env->spr[SPR_LPCR]);
- }
-
- /* Translate eaddr to raddr (where raddr is addr qemu needs for access) */
- if (ppc_radix64_xlate(cpu, eaddr, access_type, relocation, &raddr,
- &page_size, &prot, true)) {
- return 1;
- }
-
- tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
- prot, mmu_idx, 1UL << page_size);
- return 0;
-}
-
-hwaddr ppc_radix64_get_phys_page_debug(PowerPCCPU *cpu, target_ulong eaddr)
-{
- CPUPPCState *env = &cpu->env;
- int psize, prot;
- hwaddr raddr;
-
- /* Handle Real Mode */
- if ((msr_dr == 0) && (msr_hv || cpu->vhyp)) {
- /* In real mode top 4 effective addr bits (mostly) ignored */
- return eaddr & 0x0FFFFFFFFFFFFFFFULL;
- }
-
- if (ppc_radix64_xlate(cpu, eaddr, 0, msr_dr, &raddr, &psize,
- &prot, false)) {
- return -1;
- }
-
- return raddr & TARGET_PAGE_MASK;
+ return true;
}
diff --git a/target/ppc/mmu-radix64.h b/target/ppc/mmu-radix64.h
index f28c579..b70357c 100644
--- a/target/ppc/mmu-radix64.h
+++ b/target/ppc/mmu-radix64.h
@@ -44,9 +44,9 @@
#ifdef TARGET_PPC64
-int ppc_radix64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
- int mmu_idx);
-hwaddr ppc_radix64_get_phys_page_debug(PowerPCCPU *cpu, target_ulong addr);
+bool ppc_radix64_xlate(PowerPCCPU *cpu, vaddr eaddr, MMUAccessType access_type,
+ hwaddr *raddr, int *psizep, int *protp, int mmu_idx,
+ bool guest_visible);
static inline int ppc_radix64_get_prot_eaa(uint64_t pte)
{
diff --git a/target/ppc/mmu_helper.c b/target/ppc/mmu_helper.c
index 1ecb36e..869d24d 100644
--- a/target/ppc/mmu_helper.c
+++ b/target/ppc/mmu_helper.c
@@ -511,7 +511,7 @@ static int get_segment_6xx_tlb(CPUPPCState *env, mmu_ctx_t *ctx,
qemu_log("Page table: " TARGET_FMT_plx " len " TARGET_FMT_plx
"\n", ppc_hash32_hpt_base(cpu),
- ppc_hash32_hpt_mask(env) + 0x80);
+ ppc_hash32_hpt_mask(cpu) + 0x80);
for (curaddr = ppc_hash32_hpt_base(cpu);
curaddr < (ppc_hash32_hpt_base(cpu)
+ ppc_hash32_hpt_mask(cpu) + 0x80);
@@ -825,6 +825,7 @@ static int mmubooke_get_physical_address(CPUPPCState *env, mmu_ctx_t *ctx,
return ret;
}
+#ifdef CONFIG_TCG
static void booke206_flush_tlb(CPUPPCState *env, int flags,
const int check_iprot)
{
@@ -846,6 +847,7 @@ static void booke206_flush_tlb(CPUPPCState *env, int flags,
tlb_flush(env_cpu(env));
}
+#endif
static hwaddr booke206_tlb_to_page_size(CPUPPCState *env,
ppcmas_tlb_t *tlb)
@@ -1435,48 +1437,6 @@ static int get_physical_address(CPUPPCState *env, mmu_ctx_t *ctx,
}
#endif
-hwaddr ppc_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
-{
- PowerPCCPU *cpu = POWERPC_CPU(cs);
- CPUPPCState *env = &cpu->env;
- mmu_ctx_t ctx;
-
- switch (env->mmu_model) {
-#if defined(TARGET_PPC64)
- case POWERPC_MMU_64B:
- case POWERPC_MMU_2_03:
- case POWERPC_MMU_2_06:
- case POWERPC_MMU_2_07:
- return ppc_hash64_get_phys_page_debug(cpu, addr);
- case POWERPC_MMU_3_00:
- return ppc64_v3_get_phys_page_debug(cpu, addr);
-#endif
-
- case POWERPC_MMU_32B:
- case POWERPC_MMU_601:
- return ppc_hash32_get_phys_page_debug(cpu, addr);
-
- default:
- ;
- }
-
- if (unlikely(get_physical_address(env, &ctx, addr, MMU_DATA_LOAD,
- ACCESS_INT) != 0)) {
-
- /*
- * Some MMUs have separate TLBs for code and data. If we only
- * try an ACCESS_INT, we may not be able to read instructions
- * mapped by code TLBs, so we also try a ACCESS_CODE.
- */
- if (unlikely(get_physical_address(env, &ctx, addr, MMU_INST_FETCH,
- ACCESS_CODE) != 0)) {
- return -1;
- }
- }
-
- return ctx.raddr & TARGET_PAGE_MASK;
-}
-
static void booke206_update_mas_tlb_miss(CPUPPCState *env, target_ulong address,
MMUAccessType access_type, int mmu_idx)
{
@@ -1532,30 +1492,38 @@ static void booke206_update_mas_tlb_miss(CPUPPCState *env, target_ulong address,
}
/* Perform address translation */
-static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
- MMUAccessType access_type, int mmu_idx)
+/* TODO: Split this by mmu_model. */
+static bool ppc_jumbo_xlate(PowerPCCPU *cpu, vaddr eaddr,
+ MMUAccessType access_type,
+ hwaddr *raddrp, int *psizep, int *protp,
+ int mmu_idx, bool guest_visible)
{
- CPUState *cs = env_cpu(env);
- PowerPCCPU *cpu = POWERPC_CPU(cs);
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
mmu_ctx_t ctx;
int type;
- int ret = 0;
+ int ret;
if (access_type == MMU_INST_FETCH) {
/* code access */
type = ACCESS_CODE;
- } else {
+ } else if (guest_visible) {
/* data access */
type = env->access_type;
+ } else {
+ type = ACCESS_INT;
}
- ret = get_physical_address_wtlb(env, &ctx, address, access_type,
+
+ ret = get_physical_address_wtlb(env, &ctx, eaddr, access_type,
type, mmu_idx);
if (ret == 0) {
- tlb_set_page(cs, address & TARGET_PAGE_MASK,
- ctx.raddr & TARGET_PAGE_MASK, ctx.prot,
- mmu_idx, TARGET_PAGE_SIZE);
- ret = 0;
- } else if (ret < 0) {
+ *raddrp = ctx.raddr;
+ *protp = ctx.prot;
+ *psizep = TARGET_PAGE_BITS;
+ return true;
+ }
+
+ if (guest_visible) {
LOG_MMU_STATE(cs);
if (type == ACCESS_CODE) {
switch (ret) {
@@ -1565,7 +1533,7 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
case POWERPC_MMU_SOFT_6xx:
cs->exception_index = POWERPC_EXCP_IFTLB;
env->error_code = 1 << 18;
- env->spr[SPR_IMISS] = address;
+ env->spr[SPR_IMISS] = eaddr;
env->spr[SPR_ICMP] = 0x80000000 | ctx.ptem;
goto tlb_miss;
case POWERPC_MMU_SOFT_74xx:
@@ -1575,29 +1543,25 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
case POWERPC_MMU_SOFT_4xx_Z:
cs->exception_index = POWERPC_EXCP_ITLB;
env->error_code = 0;
- env->spr[SPR_40x_DEAR] = address;
+ env->spr[SPR_40x_DEAR] = eaddr;
env->spr[SPR_40x_ESR] = 0x00000000;
break;
case POWERPC_MMU_BOOKE206:
- booke206_update_mas_tlb_miss(env, address, 2, mmu_idx);
+ booke206_update_mas_tlb_miss(env, eaddr, 2, mmu_idx);
/* fall through */
case POWERPC_MMU_BOOKE:
cs->exception_index = POWERPC_EXCP_ITLB;
env->error_code = 0;
- env->spr[SPR_BOOKE_DEAR] = address;
+ env->spr[SPR_BOOKE_DEAR] = eaddr;
env->spr[SPR_BOOKE_ESR] = mmubooke206_esr(mmu_idx, MMU_DATA_LOAD);
- return -1;
+ break;
case POWERPC_MMU_MPC8xx:
- /* XXX: TODO */
cpu_abort(cs, "MPC8xx MMU model is not implemented\n");
- break;
case POWERPC_MMU_REAL:
cpu_abort(cs, "PowerPC in real mode should never raise "
"any MMU exceptions\n");
- return -1;
default:
cpu_abort(cs, "Unknown or invalid MMU model\n");
- return -1;
}
break;
case -2:
@@ -1634,7 +1598,7 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
cs->exception_index = POWERPC_EXCP_DLTLB;
env->error_code = 0;
}
- env->spr[SPR_DMISS] = address;
+ env->spr[SPR_DMISS] = eaddr;
env->spr[SPR_DCMP] = 0x80000000 | ctx.ptem;
tlb_miss:
env->error_code |= ctx.key << 19;
@@ -1652,7 +1616,7 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
tlb_miss_74xx:
/* Implement LRU algorithm */
env->error_code = ctx.key << 19;
- env->spr[SPR_TLBMISS] = (address & ~((target_ulong)0x3)) |
+ env->spr[SPR_TLBMISS] = (eaddr & ~((target_ulong)0x3)) |
((env->last_way + 1) & (env->nb_ways - 1));
env->spr[SPR_PTEHI] = 0x80000000 | ctx.ptem;
break;
@@ -1660,7 +1624,7 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
case POWERPC_MMU_SOFT_4xx_Z:
cs->exception_index = POWERPC_EXCP_DTLB;
env->error_code = 0;
- env->spr[SPR_40x_DEAR] = address;
+ env->spr[SPR_40x_DEAR] = eaddr;
if (access_type == MMU_DATA_STORE) {
env->spr[SPR_40x_ESR] = 0x00800000;
} else {
@@ -1670,23 +1634,20 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
case POWERPC_MMU_MPC8xx:
/* XXX: TODO */
cpu_abort(cs, "MPC8xx MMU model is not implemented\n");
- break;
case POWERPC_MMU_BOOKE206:
- booke206_update_mas_tlb_miss(env, address, access_type, mmu_idx);
+ booke206_update_mas_tlb_miss(env, eaddr, access_type, mmu_idx);
/* fall through */
case POWERPC_MMU_BOOKE:
cs->exception_index = POWERPC_EXCP_DTLB;
env->error_code = 0;
- env->spr[SPR_BOOKE_DEAR] = address;
+ env->spr[SPR_BOOKE_DEAR] = eaddr;
env->spr[SPR_BOOKE_ESR] = mmubooke206_esr(mmu_idx, access_type);
- return -1;
+ break;
case POWERPC_MMU_REAL:
cpu_abort(cs, "PowerPC in real mode should never raise "
"any MMU exceptions\n");
- return -1;
default:
cpu_abort(cs, "Unknown or invalid MMU model\n");
- return -1;
}
break;
case -2:
@@ -1695,16 +1656,16 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
env->error_code = 0;
if (env->mmu_model == POWERPC_MMU_SOFT_4xx
|| env->mmu_model == POWERPC_MMU_SOFT_4xx_Z) {
- env->spr[SPR_40x_DEAR] = address;
+ env->spr[SPR_40x_DEAR] = eaddr;
if (access_type == MMU_DATA_STORE) {
env->spr[SPR_40x_ESR] |= 0x00800000;
}
} else if ((env->mmu_model == POWERPC_MMU_BOOKE) ||
(env->mmu_model == POWERPC_MMU_BOOKE206)) {
- env->spr[SPR_BOOKE_DEAR] = address;
+ env->spr[SPR_BOOKE_DEAR] = eaddr;
env->spr[SPR_BOOKE_ESR] = mmubooke206_esr(mmu_idx, access_type);
} else {
- env->spr[SPR_DAR] = address;
+ env->spr[SPR_DAR] = eaddr;
if (access_type == MMU_DATA_STORE) {
env->spr[SPR_DSISR] = 0x0A000000;
} else {
@@ -1719,13 +1680,13 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
/* Floating point load/store */
cs->exception_index = POWERPC_EXCP_ALIGN;
env->error_code = POWERPC_EXCP_ALIGN_FP;
- env->spr[SPR_DAR] = address;
+ env->spr[SPR_DAR] = eaddr;
break;
case ACCESS_RES:
/* lwarx, ldarx or stwcx. */
cs->exception_index = POWERPC_EXCP_DSI;
env->error_code = 0;
- env->spr[SPR_DAR] = address;
+ env->spr[SPR_DAR] = eaddr;
if (access_type == MMU_DATA_STORE) {
env->spr[SPR_DSISR] = 0x06000000;
} else {
@@ -1736,7 +1697,7 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
/* eciwx or ecowx */
cs->exception_index = POWERPC_EXCP_DSI;
env->error_code = 0;
- env->spr[SPR_DAR] = address;
+ env->spr[SPR_DAR] = eaddr;
if (access_type == MMU_DATA_STORE) {
env->spr[SPR_DSISR] = 0x06100000;
} else {
@@ -1748,16 +1709,14 @@ static int cpu_ppc_handle_mmu_fault(CPUPPCState *env, target_ulong address,
cs->exception_index = POWERPC_EXCP_PROGRAM;
env->error_code =
POWERPC_EXCP_INVAL | POWERPC_EXCP_INVAL_INVAL;
- env->spr[SPR_DAR] = address;
+ env->spr[SPR_DAR] = eaddr;
break;
}
break;
}
}
- ret = 1;
}
-
- return ret;
+ return false;
}
#ifdef CONFIG_TCG
@@ -1798,9 +1757,6 @@ static inline void dump_store_bat(CPUPPCState *env, char ID, int ul, int nr,
void helper_store_ibatu(CPUPPCState *env, uint32_t nr, target_ulong value)
{
target_ulong mask;
-#if defined(FLUSH_ALL_TLBS)
- PowerPCCPU *cpu = env_archcpu(env);
-#endif
dump_store_bat(env, 'I', 0, nr, value);
if (env->IBAT[0][nr] != value) {
@@ -1834,9 +1790,6 @@ void helper_store_ibatl(CPUPPCState *env, uint32_t nr, target_ulong value)
void helper_store_dbatu(CPUPPCState *env, uint32_t nr, target_ulong value)
{
target_ulong mask;
-#if defined(FLUSH_ALL_TLBS)
- PowerPCCPU *cpu = env_archcpu(env);
-#endif
dump_store_bat(env, 'D', 0, nr, value);
if (env->DBAT[0][nr] != value) {
@@ -1871,7 +1824,6 @@ void helper_store_601_batu(CPUPPCState *env, uint32_t nr, target_ulong value)
{
target_ulong mask;
#if defined(FLUSH_ALL_TLBS)
- PowerPCCPU *cpu = env_archcpu(env);
int do_inval;
#endif
@@ -1916,7 +1868,6 @@ void helper_store_601_batl(CPUPPCState *env, uint32_t nr, target_ulong value)
#if !defined(FLUSH_ALL_TLBS)
target_ulong mask;
#else
- PowerPCCPU *cpu = env_archcpu(env);
int do_inval;
#endif
@@ -1952,6 +1903,7 @@ void helper_store_601_batl(CPUPPCState *env, uint32_t nr, target_ulong value)
}
#endif
+#ifdef CONFIG_TCG
/*****************************************************************************/
/* TLB management */
void ppc_tlb_invalidate_all(CPUPPCState *env)
@@ -1995,6 +1947,7 @@ void ppc_tlb_invalidate_all(CPUPPCState *env)
break;
}
}
+#endif
#ifdef CONFIG_TCG
void ppc_tlb_invalidate_one(CPUPPCState *env, target_ulong addr)
@@ -2942,26 +2895,76 @@ void helper_check_tlb_flush_global(CPUPPCState *env)
/*****************************************************************************/
-bool ppc_cpu_tlb_fill(CPUState *cs, vaddr addr, int size,
+static bool ppc_xlate(PowerPCCPU *cpu, vaddr eaddr, MMUAccessType access_type,
+ hwaddr *raddrp, int *psizep, int *protp,
+ int mmu_idx, bool guest_visible)
+{
+ switch (cpu->env.mmu_model) {
+#if defined(TARGET_PPC64)
+ case POWERPC_MMU_3_00:
+ if (ppc64_v3_radix(cpu)) {
+ return ppc_radix64_xlate(cpu, eaddr, access_type,
+ raddrp, psizep, protp, mmu_idx, guest_visible);
+ }
+ /* fall through */
+ case POWERPC_MMU_64B:
+ case POWERPC_MMU_2_03:
+ case POWERPC_MMU_2_06:
+ case POWERPC_MMU_2_07:
+ return ppc_hash64_xlate(cpu, eaddr, access_type,
+ raddrp, psizep, protp, mmu_idx, guest_visible);
+#endif
+
+ case POWERPC_MMU_32B:
+ case POWERPC_MMU_601:
+ return ppc_hash32_xlate(cpu, eaddr, access_type,
+ raddrp, psizep, protp, mmu_idx, guest_visible);
+
+ default:
+ return ppc_jumbo_xlate(cpu, eaddr, access_type, raddrp,
+ psizep, protp, mmu_idx, guest_visible);
+ }
+}
+
+hwaddr ppc_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
+{
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+ hwaddr raddr;
+ int s, p;
+
+ /*
+ * Some MMUs have separate TLBs for code and data. If we only
+ * try an MMU_DATA_LOAD, we may not be able to read instructions
+ * mapped by code TLBs, so we also try a MMU_INST_FETCH.
+ */
+ if (ppc_xlate(cpu, addr, MMU_DATA_LOAD, &raddr, &s, &p,
+ cpu_mmu_index(&cpu->env, false), false) ||
+ ppc_xlate(cpu, addr, MMU_INST_FETCH, &raddr, &s, &p,
+ cpu_mmu_index(&cpu->env, true), false)) {
+ return raddr & TARGET_PAGE_MASK;
+ }
+ return -1;
+}
+
+#ifdef CONFIG_TCG
+bool ppc_cpu_tlb_fill(CPUState *cs, vaddr eaddr, int size,
MMUAccessType access_type, int mmu_idx,
bool probe, uintptr_t retaddr)
{
PowerPCCPU *cpu = POWERPC_CPU(cs);
- PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cs);
- CPUPPCState *env = &cpu->env;
- int ret;
+ hwaddr raddr;
+ int page_size, prot;
- if (pcc->handle_mmu_fault) {
- ret = pcc->handle_mmu_fault(cpu, addr, access_type, mmu_idx);
- } else {
- ret = cpu_ppc_handle_mmu_fault(env, addr, access_type, mmu_idx);
+ if (ppc_xlate(cpu, eaddr, access_type, &raddr,
+ &page_size, &prot, mmu_idx, !probe)) {
+ tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
+ prot, mmu_idx, 1UL << page_size);
+ return true;
}
- if (unlikely(ret != 0)) {
- if (probe) {
- return false;
- }
- raise_exception_err_ra(env, cs->exception_index, env->error_code,
- retaddr);
+ if (probe) {
+ return false;
}
- return true;
+ raise_exception_err_ra(&cpu->env, cs->exception_index,
+ cpu->env.error_code, retaddr);
}
+#endif
diff --git a/target/ppc/translate.c b/target/ppc/translate.c
index f65d1e8..d1f482b 100644
--- a/target/ppc/translate.c
+++ b/target/ppc/translate.c
@@ -4940,6 +4940,11 @@ static void gen_mtcrf(DisasContext *ctx)
#if defined(TARGET_PPC64)
static void gen_mtmsrd(DisasContext *ctx)
{
+ if (unlikely(!is_book3s_arch2x(ctx))) {
+ gen_invalid(ctx);
+ return;
+ }
+
CHK_SV;
#if !defined(CONFIG_USER_ONLY)
diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040
index ba7cb34..f3677de 100755
--- a/tests/qemu-iotests/040
+++ b/tests/qemu-iotests/040
@@ -920,8 +920,8 @@ class TestCommitWithOverriddenBacking(iotests.QMPTestCase):
def setUp(self):
qemu_img('create', '-f', iotests.imgfmt, self.img_base_a, '1M')
qemu_img('create', '-f', iotests.imgfmt, self.img_base_b, '1M')
- qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a, \
- self.img_top)
+ qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a,
+ '-F', iotests.imgfmt, self.img_top)
self.vm = iotests.VM()
self.vm.launch()
diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
index 5cc02b2..db9f5dc 100755
--- a/tests/qemu-iotests/041
+++ b/tests/qemu-iotests/041
@@ -1295,8 +1295,10 @@ class TestReplaces(iotests.QMPTestCase):
class TestFilters(iotests.QMPTestCase):
def setUp(self):
qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M')
- qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, test_img)
- qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, target_img)
+ qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img,
+ '-F', iotests.imgfmt, test_img)
+ qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img,
+ '-F', iotests.imgfmt, target_img)
qemu_io('-c', 'write -P 1 0 512k', backing_img)
qemu_io('-c', 'write -P 2 512k 512k', test_img)
diff --git a/tests/qemu-iotests/061 b/tests/qemu-iotests/061
index e26d94a..9507c22 100755
--- a/tests/qemu-iotests/061
+++ b/tests/qemu-iotests/061
@@ -167,6 +167,9 @@ _make_test_img -o "compat=1.1" 64M
TEST_IMG="$TEST_IMG.base" _make_test_img -o "compat=1.1" 64M
$QEMU_IO -c "write -P 0x2a 0 128k" "$TEST_IMG.base" | _filter_qemu_io
$QEMU_IO -c "read -P 0 0 128k" "$TEST_IMG" | _filter_qemu_io
+$QEMU_IMG amend -o "backing_file=$TEST_IMG.base,backing_fmt=qcow2" \
+ "$TEST_IMG" && echo "unexpected pass"
+$QEMU_IMG rebase -u -b "$TEST_IMG.base" -F qcow2 "$TEST_IMG"
$QEMU_IMG amend -o "backing_file=$TEST_IMG.base,backing_fmt=qcow2" "$TEST_IMG"
$QEMU_IO -c "read -P 0x2a 0 128k" "$TEST_IMG" | _filter_qemu_io
_check_test_img
diff --git a/tests/qemu-iotests/061.out b/tests/qemu-iotests/061.out
index ee30da2..7ecbd4d 100644
--- a/tests/qemu-iotests/061.out
+++ b/tests/qemu-iotests/061.out
@@ -370,7 +370,8 @@ wrote 131072/131072 bytes at offset 0
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 131072/131072 bytes at offset 0
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead
+qemu-img: Cannot amend the backing file
+You can use 'qemu-img rebase' instead.
read 131072/131072 bytes at offset 0
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
No errors were found on the image.
diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out
index b70c12c..077ed0f 100644
--- a/tests/qemu-iotests/082.out
+++ b/tests/qemu-iotests/082.out
@@ -808,12 +808,14 @@ Amend options for 'qcow2':
size=<size> - Virtual disk size
Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2
-qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead
+qemu-img: Cannot amend the backing file
+You can use 'qemu-img rebase' instead.
Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2
Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,? TEST_DIR/t.qcow2
-qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead
+qemu-img: Cannot amend the backing file
+You can use 'qemu-img rebase' instead.
Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2
diff --git a/tests/qemu-iotests/114 b/tests/qemu-iotests/114
index 43cb0bc..de6fd32 100755
--- a/tests/qemu-iotests/114
+++ b/tests/qemu-iotests/114
@@ -44,16 +44,16 @@ _supported_os Linux
# qcow2.py does not work too well with external data files
_unsupported_imgopts data_file
-# Intentionally specify backing file without backing format; demonstrate
-# the difference in warning messages when backing file could be probed.
-# Note that only a non-raw probe result will affect the resulting image.
+# Older qemu-img could set up backing file without backing format; modern
+# qemu can't but we can use qcow2.py to simulate older files.
truncate -s $((64 * 1024 * 1024)) "$TEST_IMG.orig"
-_make_test_img -b "$TEST_IMG.orig" 64M
+_make_test_img -b "$TEST_IMG.orig" -F raw 64M
+$PYTHON qcow2.py "$TEST_IMG" del-header-ext 0xE2792ACA
TEST_IMG="$TEST_IMG.base" _make_test_img 64M
$QEMU_IMG convert -O qcow2 -B "$TEST_IMG.orig" "$TEST_IMG.orig" "$TEST_IMG"
-_make_test_img -b "$TEST_IMG.base" 64M
-_make_test_img -u -b "$TEST_IMG.base" 64M
+_make_test_img -b "$TEST_IMG.base" -F $IMGFMT 64M
+_make_test_img -u -b "$TEST_IMG.base" -F $IMGFMT 64M
# Set an invalid backing file format
$PYTHON qcow2.py "$TEST_IMG" add-header-ext 0xE2792ACA "foo"
@@ -64,9 +64,9 @@ _img_info
$QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir
$QEMU_IO -c "open -o backing.driver=$IMGFMT $TEST_IMG" -c "read 0 4k" | _filter_qemu_io
-# Rebase the image, to show that omitting backing format triggers a warning,
-# but probing now lets us use the backing file.
-$QEMU_IMG rebase -u -b "$TEST_IMG.base" "$TEST_IMG"
+# Rebase the image, to show that backing format is required.
+($QEMU_IMG rebase -u -b "$TEST_IMG.base" "$TEST_IMG" 2>&1 && echo "unexpected pass") | _filter_testdir
+$QEMU_IMG rebase -u -b "$TEST_IMG.base" -F $IMGFMT "$TEST_IMG"
$QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir
# success, all done
diff --git a/tests/qemu-iotests/114.out b/tests/qemu-iotests/114.out
index 0a37d20..f51dd9d 100644
--- a/tests/qemu-iotests/114.out
+++ b/tests/qemu-iotests/114.out
@@ -1,12 +1,9 @@
QA output created by 114
-qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of raw)
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.orig
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.orig backing_fmt=raw
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
-qemu-img: warning: Deprecated use of backing file without explicit backing format
-qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of IMGFMT)
+qemu-img: Use of backing file requires explicit backing format
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
-qemu-img: warning: Deprecated use of unopened backing file without explicit backing format, use of this image requires potentially unsafe format probing
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base
image: TEST_DIR/t.IMGFMT
file format: IMGFMT
virtual size: 64 MiB (67108864 bytes)
@@ -17,7 +14,7 @@ qemu-io: can't open device TEST_DIR/t.qcow2: Could not open backing file: Unknow
no file open, try 'help open'
read 4096/4096 bytes at offset 0
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-qemu-img: warning: Deprecated use of backing file without explicit backing format, use of this image requires potentially unsafe format probing
+qemu-img: Could not change the backing file to 'TEST_DIR/t.qcow2.base': backing format must be specified
read 4096/4096 bytes at offset 0
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
*** done
diff --git a/tests/qemu-iotests/155 b/tests/qemu-iotests/155
index bafef9d..eadda52 100755
--- a/tests/qemu-iotests/155
+++ b/tests/qemu-iotests/155
@@ -261,9 +261,12 @@ class TestBlockdevMirrorReopen(MirrorBaseClass):
result = self.vm.qmp('blockdev-add', node_name="backing",
driver="null-co")
self.assert_qmp(result, 'return', {})
- result = self.vm.qmp('x-blockdev-reopen', node_name="target",
- driver=iotests.imgfmt, file="target-file",
- backing="backing")
+ result = self.vm.qmp('blockdev-reopen', options=[{
+ 'node-name': "target",
+ 'driver': iotests.imgfmt,
+ 'file': "target-file",
+ 'backing': "backing"
+ }])
self.assert_qmp(result, 'return', {})
class TestBlockdevMirrorReopenIothread(TestBlockdevMirrorReopen):
diff --git a/tests/qemu-iotests/165 b/tests/qemu-iotests/165
index abc4ffa..ce49994 100755
--- a/tests/qemu-iotests/165
+++ b/tests/qemu-iotests/165
@@ -137,7 +137,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
assert sha256_1 == self.getSha256()
# Reopen to RW
- result = self.vm.qmp('x-blockdev-reopen', **{
+ result = self.vm.qmp('blockdev-reopen', options=[{
'node-name': 'node0',
'driver': iotests.imgfmt,
'file': {
@@ -145,7 +145,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
'filename': disk
},
'read-only': False
- })
+ }])
self.assert_qmp(result, 'return', {})
# Check that bitmap is reopened to RW and we can write to it.
diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245
index 0295129..bf8261e 100755
--- a/tests/qemu-iotests/245
+++ b/tests/qemu-iotests/245
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# group: rw
#
-# Test cases for the QMP 'x-blockdev-reopen' command
+# Test cases for the QMP 'blockdev-reopen' command
#
# Copyright (C) 2018-2019 Igalia, S.L.
# Author: Alberto Garcia <berto@igalia.com>
@@ -85,8 +85,18 @@ class TestBlockdevReopen(iotests.QMPTestCase):
"Expected output of %d qemu-io commands, found %d" %
(found, self.total_io_cmds))
- # Run x-blockdev-reopen with 'opts' but applying 'newopts'
- # on top of it. The original 'opts' dict is unmodified
+ # Run blockdev-reopen on a list of block devices
+ def reopenMultiple(self, opts, errmsg = None):
+ result = self.vm.qmp('blockdev-reopen', conv_keys=False, options=opts)
+ if errmsg:
+ self.assert_qmp(result, 'error/class', 'GenericError')
+ self.assert_qmp(result, 'error/desc', errmsg)
+ else:
+ self.assert_qmp(result, 'return', {})
+
+ # Run blockdev-reopen on a single block device (specified by
+ # 'opts') but applying 'newopts' on top of it. The original 'opts'
+ # dict is unmodified
def reopen(self, opts, newopts = {}, errmsg = None):
opts = copy.deepcopy(opts)
@@ -101,12 +111,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
subdict = opts[prefix]
subdict[key] = value
- result = self.vm.qmp('x-blockdev-reopen', conv_keys = False, **opts)
- if errmsg:
- self.assert_qmp(result, 'error/class', 'GenericError')
- self.assert_qmp(result, 'error/desc', errmsg)
- else:
- self.assert_qmp(result, 'return', {})
+ self.reopenMultiple([ opts ], errmsg)
# Run query-named-block-nodes and return the specified entry
@@ -142,10 +147,10 @@ class TestBlockdevReopen(iotests.QMPTestCase):
# We cannot change any of these
self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'")
self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''")
- self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string")
+ self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'options[0].node-name', expected: string")
self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'")
self.reopen(opts, {'driver': ''}, "Invalid parameter ''")
- self.reopen(opts, {'driver': None}, "Invalid parameter type for 'driver', expected: string")
+ self.reopen(opts, {'driver': None}, "Invalid parameter type for 'options[0].driver', expected: string")
self.reopen(opts, {'file': 'not-found'}, "Cannot find device='' nor node-name='not-found'")
self.reopen(opts, {'file': ''}, "Cannot find device='' nor node-name=''")
self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef")
@@ -154,9 +159,9 @@ class TestBlockdevReopen(iotests.QMPTestCase):
self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'")
self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'")
self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'")
- self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'file.filename', expected: string")
+ self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'options[0].file.filename', expected: string")
- # node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it
+ # node-name is optional in BlockdevOptions, but blockdev-reopen needs it
del opts['node-name']
self.reopen(opts, {}, "node-name not specified")
@@ -644,6 +649,53 @@ class TestBlockdevReopen(iotests.QMPTestCase):
'-c', 'read -P 0x40 0x40008 1',
'-c', 'read -P 0x80 0x40010 1', hd_path[0])
+ # Swap the disk images of two active block devices
+ def test_swap_files(self):
+ # Add hd0 and hd2 (none of them with backing files)
+ opts0 = hd_opts(0)
+ result = self.vm.qmp('blockdev-add', conv_keys = False, **opts0)
+ self.assert_qmp(result, 'return', {})
+
+ opts2 = hd_opts(2)
+ result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2)
+ self.assert_qmp(result, 'return', {})
+
+ # Write different data to both block devices
+ self.run_qemu_io("hd0", "write -P 0xa0 0 1k")
+ self.run_qemu_io("hd2", "write -P 0xa2 0 1k")
+
+ # Check that the data reads correctly
+ self.run_qemu_io("hd0", "read -P 0xa0 0 1k")
+ self.run_qemu_io("hd2", "read -P 0xa2 0 1k")
+
+ # It's not possible to make a block device use an image that
+ # is already being used by the other device.
+ self.reopen(opts0, {'file': 'hd2-file'},
+ "Permission conflict on node 'hd2-file': permissions "
+ "'write, resize' are both required by node 'hd2' (uses "
+ "node 'hd2-file' as 'file' child) and unshared by node "
+ "'hd0' (uses node 'hd2-file' as 'file' child).")
+ self.reopen(opts2, {'file': 'hd0-file'},
+ "Permission conflict on node 'hd0-file': permissions "
+ "'write, resize' are both required by node 'hd0' (uses "
+ "node 'hd0-file' as 'file' child) and unshared by node "
+ "'hd2' (uses node 'hd0-file' as 'file' child).")
+
+ # But we can swap the images if we reopen both devices at the
+ # same time
+ opts0['file'] = 'hd2-file'
+ opts2['file'] = 'hd0-file'
+ self.reopenMultiple([opts0, opts2])
+ self.run_qemu_io("hd0", "read -P 0xa2 0 1k")
+ self.run_qemu_io("hd2", "read -P 0xa0 0 1k")
+
+ # And we can of course come back to the original state
+ opts0['file'] = 'hd0-file'
+ opts2['file'] = 'hd2-file'
+ self.reopenMultiple([opts0, opts2])
+ self.run_qemu_io("hd0", "read -P 0xa0 0 1k")
+ self.run_qemu_io("hd2", "read -P 0xa2 0 1k")
+
# Misc reopen tests with different block drivers
@iotests.skip_if_unsupported(['quorum', 'throttle'])
def test_misc_drivers(self):
diff --git a/tests/qemu-iotests/245.out b/tests/qemu-iotests/245.out
index daf1e51..4eced19 100644
--- a/tests/qemu-iotests/245.out
+++ b/tests/qemu-iotests/245.out
@@ -17,8 +17,8 @@ read 1/1 bytes at offset 262152
read 1/1 bytes at offset 262160
1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-..............
+...............
----------------------------------------------------------------------
-Ran 24 tests
+Ran 25 tests
OK
diff --git a/tests/qemu-iotests/248 b/tests/qemu-iotests/248
index 4daaed1..2ec2416 100755
--- a/tests/qemu-iotests/248
+++ b/tests/qemu-iotests/248
@@ -62,8 +62,8 @@ vm.event_wait('JOB_STATUS_CHANGE', timeout=3.0,
vm.get_qmp_events()
del blockdev_opts['file']['size']
-vm.qmp_log('x-blockdev-reopen', filters=[filter_qmp_testfiles],
- **blockdev_opts)
+vm.qmp_log('blockdev-reopen', filters=[filter_qmp_testfiles],
+ options = [ blockdev_opts ])
vm.qmp_log('block-job-resume', device='drive0')
vm.event_wait('JOB_STATUS_CHANGE', timeout=1.0,
diff --git a/tests/qemu-iotests/248.out b/tests/qemu-iotests/248.out
index 369b25b..66e94cc 100644
--- a/tests/qemu-iotests/248.out
+++ b/tests/qemu-iotests/248.out
@@ -2,7 +2,7 @@
{"return": {}}
{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "on-target-error": "enospc", "sync": "full", "target": "target"}}
{"return": {}}
-{"execute": "x-blockdev-reopen", "arguments": {"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}}
+{"execute": "blockdev-reopen", "arguments": {"options": [{"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}]}}
{"return": {}}
{"execute": "block-job-resume", "arguments": {"device": "drive0"}}
{"return": {}}
diff --git a/tests/qemu-iotests/296 b/tests/qemu-iotests/296
index 7c65e98..099a3ee 100755
--- a/tests/qemu-iotests/296
+++ b/tests/qemu-iotests/296
@@ -118,10 +118,9 @@ class EncryptionSetupTestCase(iotests.QMPTestCase):
def openImageQmp(self, vm, id, file, secret,
readOnly = False, reOpen = False):
- command = 'x-blockdev-reopen' if reOpen else 'blockdev-add'
+ command = 'blockdev-reopen' if reOpen else 'blockdev-add'
- result = vm.qmp(command, **
- {
+ opts = {
'driver': iotests.imgfmt,
'node-name': id,
'read-only': readOnly,
@@ -131,7 +130,11 @@ class EncryptionSetupTestCase(iotests.QMPTestCase):
'filename': test_img,
}
}
- )
+
+ if reOpen:
+ result = vm.qmp(command, options=[opts])
+ else:
+ result = vm.qmp(command, **opts)
self.assert_qmp(result, 'return', {})
diff --git a/tests/qemu-iotests/298 b/tests/qemu-iotests/298
index d535946..fae7221 100755
--- a/tests/qemu-iotests/298
+++ b/tests/qemu-iotests/298
@@ -98,7 +98,7 @@ class TestPreallocateFilter(TestPreallocateBase):
self.check_big()
def test_reopen_opts(self):
- result = self.vm.qmp('x-blockdev-reopen', **{
+ result = self.vm.qmp('blockdev-reopen', options=[{
'node-name': 'disk',
'driver': iotests.imgfmt,
'file': {
@@ -112,7 +112,7 @@ class TestPreallocateFilter(TestPreallocateBase):
'filename': disk
}
}
- })
+ }])
self.assert_qmp(result, 'return', {})
self.vm.hmp_qemu_io('drive0', 'write 0 1M')
diff --git a/tests/qemu-iotests/301 b/tests/qemu-iotests/301
index 9f943ca..220de10 100755
--- a/tests/qemu-iotests/301
+++ b/tests/qemu-iotests/301
@@ -3,7 +3,7 @@
#
# Test qcow backing file warnings
#
-# Copyright (C) 2020 Red Hat, Inc.
+# Copyright (C) 2020-2021 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -46,7 +46,6 @@ echo "== qcow backed by qcow =="
TEST_IMG="$TEST_IMG.base" _make_test_img $size
_make_test_img -b "$TEST_IMG.base" $size
-_img_info
_make_test_img -b "$TEST_IMG.base" -F $IMGFMT $size
_img_info
@@ -71,7 +70,6 @@ echo "== qcow backed by raw =="
rm "$TEST_IMG.base"
truncate --size=$size "$TEST_IMG.base"
_make_test_img -b "$TEST_IMG.base" $size
-_img_info
_make_test_img -b "$TEST_IMG.base" -F raw $size
_img_info
diff --git a/tests/qemu-iotests/301.out b/tests/qemu-iotests/301.out
index 9004dad..e280658 100644
--- a/tests/qemu-iotests/301.out
+++ b/tests/qemu-iotests/301.out
@@ -2,13 +2,7 @@ QA output created by 301
== qcow backed by qcow ==
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=33554432
-qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of IMGFMT)
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
-image: TEST_DIR/t.IMGFMT
-file format: IMGFMT
-virtual size: 32 MiB (33554432 bytes)
-cluster_size: 512
-backing file: TEST_DIR/t.IMGFMT.base
+qemu-img: TEST_DIR/t.IMGFMT: Backing file specified without backing format
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
image: TEST_DIR/t.IMGFMT
file format: IMGFMT
@@ -36,13 +30,7 @@ cluster_size: 512
backing file: TEST_DIR/t.IMGFMT.base
== qcow backed by raw ==
-qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of raw)
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base
-image: TEST_DIR/t.IMGFMT
-file format: IMGFMT
-virtual size: 32 MiB (33554432 bytes)
-cluster_size: 512
-backing file: TEST_DIR/t.IMGFMT.base
+qemu-img: TEST_DIR/t.IMGFMT: Backing file specified without backing format
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=raw
image: TEST_DIR/t.IMGFMT
file format: IMGFMT
diff --git a/tests/qemu-iotests/308 b/tests/qemu-iotests/308
index f122065..6b386bd 100755
--- a/tests/qemu-iotests/308
+++ b/tests/qemu-iotests/308
@@ -58,6 +58,9 @@ _supported_os Linux # We need /dev/urandom
# $4: Node to export (defaults to 'node-format')
fuse_export_add()
{
+ # The grep -v is a filter for errors when /etc/fuse.conf does not contain
+ # user_allow_other. (The error is benign, but it is printed by fusermount
+ # on the first mount attempt, so our export code cannot hide it.)
_send_qemu_cmd $QEMU_HANDLE \
"{'execute': 'block-export-add',
'arguments': {
@@ -67,7 +70,8 @@ fuse_export_add()
$2
} }" \
"${3:-return}" \
- | _filter_imgfmt
+ | _filter_imgfmt \
+ | grep -v 'option allow_other only allowed if'
}
# $1: Export ID
@@ -166,6 +170,17 @@ fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP'"
# Check that the export presents the same data as the original image
$QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG"
+# Some quick chmod tests
+stat -c 'Permissions pre-chmod: %a' "$EXT_MP"
+
+# Verify that we cannot set +w
+chmod u+w "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
+stat -c 'Permissions post-+w: %a' "$EXT_MP"
+
+# But that we can set, say, +x (if we are so inclined)
+chmod u+x "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
+stat -c 'Permissions post-+x: %a' "$EXT_MP"
+
echo
echo '=== Mount over existing file ==='
@@ -215,7 +230,8 @@ echo '=== Writable export ==='
fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true"
# Check that writing to the read-only export fails
-$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" | _filter_qemu_io
+$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" 2>&1 \
+ | _filter_qemu_io | _filter_testdir | _filter_imgfmt
# But here it should work
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io
diff --git a/tests/qemu-iotests/308.out b/tests/qemu-iotests/308.out
index 466e7e0..fc47bb1 100644
--- a/tests/qemu-iotests/308.out
+++ b/tests/qemu-iotests/308.out
@@ -50,6 +50,10 @@ wrote 67108864/67108864 bytes at offset 0
} }
{"return": {}}
Images are identical.
+Permissions pre-chmod: 400
+chmod: changing permissions of 'TEST_DIR/t.IMGFMT.fuse': Read-only file system
+Permissions post-+w: 400
+Permissions post-+x: 500
=== Mount over existing file ===
{'execute': 'block-export-add',
@@ -91,7 +95,7 @@ virtual size: 0 B (0 bytes)
'mountpoint': 'TEST_DIR/t.IMGFMT.fuse', 'writable': true
} }
{"return": {}}
-write failed: Permission denied
+qemu-io: can't open device TEST_DIR/t.IMGFMT: Could not open 'TEST_DIR/t.IMGFMT': Permission denied
wrote 65536/65536 bytes at offset 1048576
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
wrote 65536/65536 bytes at offset 1048576
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index cbbf6d7..609d82d 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -512,9 +512,13 @@ _make_test_img()
# Usually, users would export formatted nodes. But we present fuse as a
# protocol-level driver here, so we have to leave the format to the
# client.
+ # Switch off allow-other, because in general we do not need it for
+ # iotests. The default allow-other=auto has the downside of printing a
+ # fusermount error on its first attempt if allow_other is not
+ # permissible, which we would need to filter.
QSD_NEED_PID=y $QSD \
--blockdev file,node-name=export-node,filename=$img_name,discard=unmap \
- --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on \
+ --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on,allow-other=off \
&
pidfile="$QEMU_TEST_DIR/qemu-storage-daemon.pid"
diff --git a/tests/qemu-iotests/tests/fuse-allow-other b/tests/qemu-iotests/tests/fuse-allow-other
new file mode 100755
index 0000000..19f494a
--- /dev/null
+++ b/tests/qemu-iotests/tests/fuse-allow-other
@@ -0,0 +1,168 @@
+#!/usr/bin/env bash
+# group: rw
+#
+# Test FUSE exports' allow-other option
+#
+# Copyright (C) 2021 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+seq=$(basename "$0")
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_qemu
+ _cleanup_test_img
+ rm -f "$EXT_MP"
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ../common.rc
+. ../common.filter
+. ../common.qemu
+
+_supported_fmt generic
+
+_supported_proto file # We create the FUSE export manually
+
+sudo -n -u nobody true || \
+ _notrun 'Password-less sudo as nobody required to test allow_other'
+
+# $1: Export ID
+# $2: Options (beyond the node-name and ID)
+# $3: Expected return value (defaults to 'return')
+# $4: Node to export (defaults to 'node-format')
+fuse_export_add()
+{
+ allow_other_not_supported='option allow_other only allowed if'
+
+ output=$(
+ success_or_failure=yes _send_qemu_cmd $QEMU_HANDLE \
+ "{'execute': 'block-export-add',
+ 'arguments': {
+ 'type': 'fuse',
+ 'id': '$1',
+ 'node-name': '${4:-node-format}',
+ $2
+ } }" \
+ "${3:-return}" \
+ "$allow_other_not_supported" \
+ | _filter_imgfmt
+ )
+
+ if echo "$output" | grep -q "$allow_other_not_supported"; then
+ # Shut down qemu gracefully so it can unmount the export
+ _send_qemu_cmd $QEMU_HANDLE \
+ "{'execute': 'quit'}" \
+ 'return'
+
+ wait=yes _cleanup_qemu
+
+ _notrun "allow_other not supported"
+ fi
+
+ echo "$output"
+}
+
+EXT_MP="$TEST_DIR/fuse-export"
+
+_make_test_img 64k
+touch "$EXT_MP"
+
+echo
+echo '=== Test permissions ==='
+
+# $1: allow-other value ('on'/'off'/'auto')
+run_permission_test()
+{
+ _launch_qemu \
+ -blockdev \
+ "$IMGFMT,node-name=node-format,file.driver=file,file.filename=$TEST_IMG"
+
+ _send_qemu_cmd $QEMU_HANDLE \
+ "{'execute': 'qmp_capabilities'}" \
+ 'return'
+
+ fuse_export_add 'export' \
+ "'mountpoint': '$EXT_MP',
+ 'allow-other': '$1'"
+
+ # Should always work
+ echo '(Removing all permissions)'
+ chmod 000 "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
+ stat -c 'Permissions post-chmod: %a' "$EXT_MP"
+
+ # Should always work
+ echo '(Granting u+r)'
+ chmod u+r "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
+ stat -c 'Permissions post-chmod: %a' "$EXT_MP"
+
+ # Should only work with allow-other: Otherwise, no permissions can be
+ # granted to the group or others
+ echo '(Granting read permissions for everyone)'
+ chmod 444 "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
+ stat -c 'Permissions post-chmod: %a' "$EXT_MP"
+
+ echo 'Doing operations as nobody:'
+ # Change to TEST_DIR, so nobody will not have to attempt a lookup
+ pushd "$TEST_DIR" >/dev/null
+
+ # This is already prevented by the permissions (without allow-other, FUSE
+ # exports always have o-r), but test it anyway
+ sudo -n -u nobody cat fuse-export >/dev/null
+
+ # If the only problem were the lack of permissions, we should still be able
+ # to stat the export as nobody; it should not work without allow-other,
+ # though
+ sudo -n -u nobody \
+ stat -c 'Permissions seen by nobody: %a' fuse-export 2>&1 \
+ | _filter_imgfmt
+
+ # To prove the point, revoke read permissions for others and try again
+ chmod o-r fuse-export 2>&1 | _filter_testdir | _filter_imgfmt
+
+ # Should fail
+ sudo -n -u nobody cat fuse-export >/dev/null
+ # Should work with allow_other
+ sudo -n -u nobody \
+ stat -c 'Permissions seen by nobody: %a' fuse-export 2>&1 \
+ | _filter_imgfmt
+
+ popd >/dev/null
+
+ _send_qemu_cmd $QEMU_HANDLE \
+ "{'execute': 'quit'}" \
+ 'return'
+
+ wait=yes _cleanup_qemu
+}
+
+# 'auto' should behave exactly like 'on', because 'on' tests that
+# allow_other works (otherwise, this test is skipped)
+for ao in off on auto; do
+ echo
+ echo "--- allow-other=$ao ---"
+
+ run_permission_test "$ao"
+done
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/tests/fuse-allow-other.out b/tests/qemu-iotests/tests/fuse-allow-other.out
new file mode 100644
index 0000000..543fa52
--- /dev/null
+++ b/tests/qemu-iotests/tests/fuse-allow-other.out
@@ -0,0 +1,88 @@
+QA output created by fuse-allow-other
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=65536
+
+=== Test permissions ===
+
+--- allow-other=off ---
+{'execute': 'qmp_capabilities'}
+{"return": {}}
+{'execute': 'block-export-add',
+ 'arguments': {
+ 'type': 'fuse',
+ 'id': 'export',
+ 'node-name': 'node-format',
+ 'mountpoint': 'TEST_DIR/fuse-export',
+ 'allow-other': 'off'
+ } }
+{"return": {}}
+(Removing all permissions)
+Permissions post-chmod: 0
+(Granting u+r)
+Permissions post-chmod: 400
+(Granting read permissions for everyone)
+chmod: changing permissions of 'TEST_DIR/fuse-export': Operation not permitted
+Permissions post-chmod: 400
+Doing operations as nobody:
+cat: fuse-export: Permission denied
+stat: cannot statx 'fuse-export': Permission denied
+cat: fuse-export: Permission denied
+stat: cannot statx 'fuse-export': Permission denied
+{'execute': 'quit'}
+{"return": {}}
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}}
+
+--- allow-other=on ---
+{'execute': 'qmp_capabilities'}
+{"return": {}}
+{'execute': 'block-export-add',
+ 'arguments': {
+ 'type': 'fuse',
+ 'id': 'export',
+ 'node-name': 'node-format',
+ 'mountpoint': 'TEST_DIR/fuse-export',
+ 'allow-other': 'on'
+ } }
+{"return": {}}
+(Removing all permissions)
+Permissions post-chmod: 0
+(Granting u+r)
+Permissions post-chmod: 400
+(Granting read permissions for everyone)
+Permissions post-chmod: 444
+Doing operations as nobody:
+Permissions seen by nobody: 444
+cat: fuse-export: Permission denied
+Permissions seen by nobody: 440
+{'execute': 'quit'}
+{"return": {}}
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}}
+
+--- allow-other=auto ---
+{'execute': 'qmp_capabilities'}
+{"return": {}}
+{'execute': 'block-export-add',
+ 'arguments': {
+ 'type': 'fuse',
+ 'id': 'export',
+ 'node-name': 'node-format',
+ 'mountpoint': 'TEST_DIR/fuse-export',
+ 'allow-other': 'auto'
+ } }
+{"return": {}}
+(Removing all permissions)
+Permissions post-chmod: 0
+(Granting u+r)
+Permissions post-chmod: 400
+(Granting read permissions for everyone)
+Permissions post-chmod: 444
+Doing operations as nobody:
+Permissions seen by nobody: 444
+cat: fuse-export: Permission denied
+Permissions seen by nobody: 440
+{'execute': 'quit'}
+{"return": {}}
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}}
+*** done
diff --git a/tests/qemu-iotests/tests/remove-bitmap-from-backing b/tests/qemu-iotests/tests/remove-bitmap-from-backing
index 0ea4c36..8d48fc0 100755
--- a/tests/qemu-iotests/tests/remove-bitmap-from-backing
+++ b/tests/qemu-iotests/tests/remove-bitmap-from-backing
@@ -41,25 +41,27 @@ log('Trying to remove persistent bitmap from r-o base node, should fail:')
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
new_base_opts = {
- 'node-name': 'base',
- 'driver': 'qcow2',
- 'file': {
- 'driver': 'file',
- 'filename': base
- },
- 'read-only': False
+ 'options': [{
+ 'node-name': 'base',
+ 'driver': 'qcow2',
+ 'file': {
+ 'driver': 'file',
+ 'filename': base
+ },
+ 'read-only': False
+ }]
}
# Don't want to bother with filtering qmp_log for reopen command
-result = vm.qmp('x-blockdev-reopen', **new_base_opts)
+result = vm.qmp('blockdev-reopen', **new_base_opts)
if result != {'return': {}}:
log('Failed to reopen: ' + str(result))
log('Remove persistent bitmap from base node reopened to RW:')
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
-new_base_opts['read-only'] = True
-result = vm.qmp('x-blockdev-reopen', **new_base_opts)
+new_base_opts['options'][0]['read-only'] = True
+result = vm.qmp('blockdev-reopen', **new_base_opts)
if result != {'return': {}}:
log('Failed to reopen: ' + str(result))
diff --git a/tests/qtest/adm1272-test.c b/tests/qtest/adm1272-test.c
new file mode 100644
index 0000000..63f8514
--- /dev/null
+++ b/tests/qtest/adm1272-test.c
@@ -0,0 +1,445 @@
+/*
+ * QTests for the ADM1272 hotswap controller
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <math.h>
+#include "hw/i2c/pmbus_device.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qnum.h"
+#include "qemu/bitops.h"
+
+#define TEST_ID "adm1272-test"
+#define TEST_ADDR (0x10)
+
+#define ADM1272_RESTART_TIME 0xCC
+#define ADM1272_MFR_PEAK_IOUT 0xD0
+#define ADM1272_MFR_PEAK_VIN 0xD1
+#define ADM1272_MFR_PEAK_VOUT 0xD2
+#define ADM1272_MFR_PMON_CONTROL 0xD3
+#define ADM1272_MFR_PMON_CONFIG 0xD4
+#define ADM1272_MFR_ALERT1_CONFIG 0xD5
+#define ADM1272_MFR_ALERT2_CONFIG 0xD6
+#define ADM1272_MFR_PEAK_TEMPERATURE 0xD7
+#define ADM1272_MFR_DEVICE_CONFIG 0xD8
+#define ADM1272_MFR_POWER_CYCLE 0xD9
+#define ADM1272_MFR_PEAK_PIN 0xDA
+#define ADM1272_MFR_READ_PIN_EXT 0xDB
+#define ADM1272_MFR_READ_EIN_EXT 0xDC
+
+#define ADM1272_HYSTERESIS_LOW 0xF2
+#define ADM1272_HYSTERESIS_HIGH 0xF3
+#define ADM1272_STATUS_HYSTERESIS 0xF4
+#define ADM1272_STATUS_GPIO 0xF5
+#define ADM1272_STRT_UP_IOUT_LIM 0xF6
+
+/* Defaults */
+#define ADM1272_OPERATION_DEFAULT 0x80
+#define ADM1272_CAPABILITY_DEFAULT 0xB0
+#define ADM1272_CAPABILITY_NO_PEC 0x30
+#define ADM1272_DIRECT_MODE 0x40
+#define ADM1272_HIGH_LIMIT_DEFAULT 0x0FFF
+#define ADM1272_PIN_OP_DEFAULT 0x7FFF
+#define ADM1272_PMBUS_REVISION_DEFAULT 0x22
+#define ADM1272_MFR_ID_DEFAULT "ADI"
+#define ADM1272_MODEL_DEFAULT "ADM1272-A1"
+#define ADM1272_MFR_DEFAULT_REVISION "25"
+#define ADM1272_DEFAULT_DATE "160301"
+#define ADM1272_RESTART_TIME_DEFAULT 0x64
+#define ADM1272_PMON_CONTROL_DEFAULT 0x1
+#define ADM1272_PMON_CONFIG_DEFAULT 0x3F35
+#define ADM1272_DEVICE_CONFIG_DEFAULT 0x8
+#define ADM1272_HYSTERESIS_HIGH_DEFAULT 0xFFFF
+#define ADM1272_STRT_UP_IOUT_LIM_DEFAULT 0x000F
+#define ADM1272_VOLT_DEFAULT 12000
+#define ADM1272_IOUT_DEFAULT 25000
+#define ADM1272_PWR_DEFAULT 300 /* 12V 25A */
+#define ADM1272_SHUNT 300 /* micro-ohms */
+#define ADM1272_VOLTAGE_COEFF_DEFAULT 1
+#define ADM1272_CURRENT_COEFF_DEFAULT 3
+#define ADM1272_PWR_COEFF_DEFAULT 7
+#define ADM1272_IOUT_OFFSET 0x5000
+#define ADM1272_IOUT_OFFSET 0x5000
+
+static const PMBusCoefficients adm1272_coefficients[] = {
+ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
+ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
+ [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */
+ [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */
+ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
+ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
+ [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */
+ [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */
+ [8] = { 42, 31871, -1 }, /* temperature */
+};
+
+uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value)
+{
+ /* R is usually negative to fit large readings into 16 bits */
+ uint16_t y = (c.m * value + c.b) * pow(10, c.R);
+ return y;
+}
+
+uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value)
+{
+ /* X = (Y * 10^-R - b) / m */
+ uint32_t x = (value / pow(10, c.R) - c.b) / c.m;
+ return x;
+}
+
+
+static uint16_t adm1272_millivolts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_millivolts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_milliamps_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ /* Y = (m * r_sense * x - b) * 10^R */
+ c.m = c.m * ADM1272_SHUNT / 1000; /* micro-ohms */
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_milliamps(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_watts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_watts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t qmp_adm1272_get(const char *id, const char *property)
+{
+ QDict *response;
+ uint64_t ret;
+
+ response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
+ "'property': %s } }", id, property);
+ g_assert(qdict_haskey(response, "return"));
+ ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+ qobject_unref(response);
+ return ret;
+}
+
+static void qmp_adm1272_set(const char *id,
+ const char *property,
+ uint16_t value)
+{
+ QDict *response;
+
+ response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
+ "'property': %s, 'value': %u } }", id, property, value);
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static uint16_t adm1272_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
+{
+ uint8_t resp[2];
+ i2c_read_block(i2cdev, reg, resp, sizeof(resp));
+ return (resp[1] << 8) | resp[0];
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static void adm1272_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
+{
+ uint8_t data[2];
+
+ data[0] = value & 255;
+ data[1] = value >> 8;
+ i2c_write_block(i2cdev, reg, data, sizeof(data));
+}
+
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ int16_t err;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ value = qmp_adm1272_get(TEST_ID, "vout");
+ err = ADM1272_VOLT_DEFAULT - value;
+ g_assert_cmpuint(abs(err), <, ADM1272_VOLT_DEFAULT / 20);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmphex(i2c_value, ==, ADM1272_OPERATION_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+ g_assert_cmphex(i2c_value, ==, ADM1272_DIRECT_MODE);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_PIN_OP_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PIN_OP_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_REVISION);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PMBUS_REVISION_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, ADM1272_MFR_PMON_CONTROL);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PMON_CONTROL_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_PMON_CONFIG);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PMON_CONFIG_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_DEVICE_CONFIG);
+ g_assert_cmphex(i2c_value, ==, ADM1272_DEVICE_CONFIG_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_HIGH);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HYSTERESIS_HIGH_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_STRT_UP_IOUT_LIM);
+ g_assert_cmphex(i2c_value, ==, ADM1272_STRT_UP_IOUT_LIM_DEFAULT);
+}
+
+/* test qmp access */
+static void test_tx_rx(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value, value, i2c_voltage, i2c_pwr, lossy_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ /* converting to direct mode is lossy - we generate the same loss here */
+ lossy_value =
+ adm1272_direct_to_millivolts(adm1272_millivolts_to_direct(1000));
+ qmp_adm1272_set(TEST_ID, "vin", 1000);
+ value = qmp_adm1272_get(TEST_ID, "vin");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ i2c_voltage = adm1272_direct_to_millivolts(i2c_value);
+ g_assert_cmpuint(value, ==, i2c_voltage);
+ g_assert_cmpuint(i2c_voltage, ==, lossy_value);
+
+ lossy_value =
+ adm1272_direct_to_millivolts(adm1272_millivolts_to_direct(1500));
+ qmp_adm1272_set(TEST_ID, "vout", 1500);
+ value = qmp_adm1272_get(TEST_ID, "vout");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ i2c_voltage = adm1272_direct_to_millivolts(i2c_value);
+ g_assert_cmpuint(value, ==, i2c_voltage);
+ g_assert_cmpuint(i2c_voltage, ==, lossy_value);
+
+ lossy_value =
+ adm1272_direct_to_milliamps(adm1272_milliamps_to_direct(1600));
+ qmp_adm1272_set(TEST_ID, "iout", 1600);
+ value = qmp_adm1272_get(TEST_ID, "iout");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ i2c_value = adm1272_direct_to_milliamps(i2c_value);
+ g_assert_cmphex(value, ==, i2c_value);
+ g_assert_cmphex(i2c_value, ==, lossy_value);
+
+ lossy_value =
+ adm1272_direct_to_watts(adm1272_watts_to_direct(320));
+ qmp_adm1272_set(TEST_ID, "pin", 320);
+ value = qmp_adm1272_get(TEST_ID, "pin");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ i2c_pwr = adm1272_direct_to_watts(i2c_value);
+ g_assert_cmphex(value, ==, i2c_pwr);
+ g_assert_cmphex(i2c_pwr, ==, lossy_value);
+}
+
+/* test r/w registers */
+static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT, 0xABCD);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xABCD);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT, 0xCDEF);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xCDEF);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT, 0x1234);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0x1234);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_OT_FAULT_LIMIT, 0x5678);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0x5678);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_OT_WARN_LIMIT, 0xABDC);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xABDC);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT, 0xCDEF);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xCDEF);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT, 0x2345);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0x2345);
+
+ i2c_set8(i2cdev, ADM1272_RESTART_TIME, 0xF8);
+ i2c_value = i2c_get8(i2cdev, ADM1272_RESTART_TIME);
+ g_assert_cmphex(i2c_value, ==, 0xF8);
+
+ i2c_set8(i2cdev, ADM1272_MFR_PMON_CONTROL, 0);
+ i2c_value = i2c_get8(i2cdev, ADM1272_MFR_PMON_CONTROL);
+ g_assert_cmpuint(i2c_value, ==, 0);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_PMON_CONFIG, 0xDEF0);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_PMON_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0xDEF0);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_ALERT1_CONFIG, 0x0123);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_ALERT1_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0x0123);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_ALERT2_CONFIG, 0x9876);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_ALERT2_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0x9876);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_DEVICE_CONFIG, 0x3456);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_DEVICE_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0x3456);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_HYSTERESIS_LOW, 0xCABA);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_LOW);
+ g_assert_cmphex(i2c_value, ==, 0xCABA);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_HYSTERESIS_HIGH, 0x6789);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_HIGH);
+ g_assert_cmphex(i2c_value, ==, 0x6789);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_STRT_UP_IOUT_LIM, 0x9876);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_STRT_UP_IOUT_LIM);
+ g_assert_cmphex(i2c_value, ==, 0x9876);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_OPERATION, 0xA);
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmphex(i2c_value, ==, 0xA);
+}
+
+/* test read-only registers */
+static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_init_value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_VIN, 0xBEEF);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0x1234);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_IOUT, 0x6547);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0x1597);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_PIN, 0xDEAD);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+}
+
+/* test voltage fault handling */
+static void test_voltage_faults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ uint8_t i2c_byte;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT,
+ adm1272_millivolts_to_direct(5000));
+ qmp_adm1272_set(TEST_ID, "vout", 5100);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_WARN) != 0);
+
+ qmp_adm1272_set(TEST_ID, "vout", 4500);
+ i2c_set8(i2cdev, PMBUS_CLEAR_FAULTS, 0);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_WARN) == 0);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT,
+ adm1272_millivolts_to_direct(4600));
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_UV_WARN) != 0);
+
+}
+
+static void adm1272_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" TEST_ID ",address=0x10"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { TEST_ADDR });
+
+ qos_node_create_driver("adm1272", i2c_device_create);
+ qos_node_consumes("adm1272", "i2c-bus", &opts);
+
+ qos_add_test("test_defaults", "adm1272", test_defaults, NULL);
+ qos_add_test("test_tx_rx", "adm1272", test_tx_rx, NULL);
+ qos_add_test("test_rw_regs", "adm1272", test_rw_regs, NULL);
+ qos_add_test("test_ro_regs", "adm1272", test_ro_regs, NULL);
+ qos_add_test("test_ov_faults", "adm1272", test_voltage_faults, NULL);
+}
+libqos_init(adm1272_register_nodes);
diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c
index d40addd..96849ce 100644
--- a/tests/qtest/boot-serial-test.c
+++ b/tests/qtest/boot-serial-test.c
@@ -94,6 +94,41 @@ static const uint8_t kernel_nrf51[] = {
0x1c, 0x25, 0x00, 0x40 /* 0x4000251c = UART TXD */
};
+static const uint8_t kernel_stm32vldiscovery[] = {
+ 0x00, 0x00, 0x00, 0x00, /* Stack top address */
+ 0x1d, 0x00, 0x00, 0x00, /* Reset handler address */
+ 0x00, 0x00, 0x00, 0x00, /* NMI */
+ 0x00, 0x00, 0x00, 0x00, /* Hard fault */
+ 0x00, 0x00, 0x00, 0x00, /* Memory management fault */
+ 0x00, 0x00, 0x00, 0x00, /* Bus fault */
+ 0x00, 0x00, 0x00, 0x00, /* Usage fault */
+ 0x0b, 0x4b, /* ldr r3, [pc, #44] Get RCC */
+ 0x44, 0xf2, 0x04, 0x02, /* movw r2, #16388 */
+ 0x1a, 0x60, /* str r2, [r3] */
+ 0x0a, 0x4b, /* ldr r3, [pc, #40] Get GPIOA */
+ 0x1a, 0x68, /* ldr r2, [r3] */
+ 0x22, 0xf0, 0xf0, 0x02, /* bic r2, r2, #240 */
+ 0x1a, 0x60, /* str r2, [r3] */
+ 0x1a, 0x68, /* ldr r2, [r3] */
+ 0x42, 0xf0, 0xb0, 0x02, /* orr r2, r2, #176 */
+ 0x1a, 0x60, /* str r2, [r3] */
+ 0x07, 0x4b, /* ldr r3, [pc, #26] Get BAUD */
+ 0x45, 0x22, /* movs r2, #69 */
+ 0x1a, 0x60, /* str r2, [r3] */
+ 0x06, 0x4b, /* ldr r3, [pc, #24] Get ENABLE */
+ 0x42, 0xf2, 0x08, 0x02, /* movw r2, #8200 */
+ 0x1a, 0x60, /* str r2, [r3] */
+ 0x05, 0x4b, /* ldr r3, [pc, #20] Get TXD */
+ 0x54, 0x22, /* movs r2, 'T' */
+ 0x1a, 0x60, /* str r2, [r3] */
+ 0xfe, 0xe7, /* b . */
+ 0x18, 0x10, 0x02, 0x40, /* 0x40021018 = RCC */
+ 0x04, 0x08, 0x01, 0x40, /* 0x40010804 = GPIOA */
+ 0x08, 0x38, 0x01, 0x40, /* 0x40013808 = USART1 BAUD */
+ 0x0c, 0x38, 0x01, 0x40, /* 0x4001380c = USART1 ENABLE */
+ 0x04, 0x38, 0x01, 0x40 /* 0x40013804 = USART1 TXD */
+};
+
typedef struct testdef {
const char *arch; /* Target architecture */
const char *machine; /* Name of the machine */
@@ -144,6 +179,8 @@ static testdef_t tests[] = {
{ "aarch64", "virt", "-cpu max", "TT", sizeof(kernel_aarch64),
kernel_aarch64 },
{ "arm", "microbit", "", "T", sizeof(kernel_nrf51), kernel_nrf51 },
+ { "arm", "stm32vldiscovery", "", "T",
+ sizeof(kernel_stm32vldiscovery), kernel_stm32vldiscovery },
{ NULL }
};
diff --git a/tests/qtest/emc141x-test.c b/tests/qtest/emc141x-test.c
index 7140588..8c86694 100644
--- a/tests/qtest/emc141x-test.c
+++ b/tests/qtest/emc141x-test.c
@@ -11,7 +11,7 @@
#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qapi/qmp/qdict.h"
-#include "hw/misc/emc141x_regs.h"
+#include "hw/sensor/emc141x_regs.h"
#define EMC1414_TEST_ID "emc1414-test"
diff --git a/tests/qtest/max34451-test.c b/tests/qtest/max34451-test.c
new file mode 100644
index 0000000..0c98d07
--- /dev/null
+++ b/tests/qtest/max34451-test.c
@@ -0,0 +1,336 @@
+/*
+ * QTests for the MAX34451 device
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qnum.h"
+#include "qemu/bitops.h"
+
+#define TEST_ID "max34451-test"
+#define TEST_ADDR (0x4e)
+
+#define MAX34451_MFR_VOUT_PEAK 0xD4
+#define MAX34451_MFR_IOUT_PEAK 0xD5
+#define MAX34451_MFR_TEMPERATURE_PEAK 0xD6
+#define MAX34451_MFR_VOUT_MIN 0xD7
+
+#define DEFAULT_VOUT 0
+#define DEFAULT_UV_LIMIT 0
+#define DEFAULT_TEMPERATURE 2500
+#define DEFAULT_SCALE 0x7FFF
+#define DEFAULT_OV_LIMIT 0x7FFF
+#define DEFAULT_OC_LIMIT 0x7FFF
+#define DEFAULT_OT_LIMIT 0x7FFF
+#define DEFAULT_VMIN 0x7FFF
+#define DEFAULT_TON_FAULT_LIMIT 0xFFFF
+#define DEFAULT_CHANNEL_CONFIG 0x20
+#define DEFAULT_TEXT 0x20
+
+#define MAX34451_NUM_PWR_DEVICES 16
+#define MAX34451_NUM_TEMP_DEVICES 5
+
+
+static uint16_t qmp_max34451_get(const char *id, const char *property)
+{
+ QDict *response;
+ uint16_t ret;
+ response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
+ "'property': %s } }", id, property);
+ g_assert(qdict_haskey(response, "return"));
+ ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+ qobject_unref(response);
+ return ret;
+}
+
+static void qmp_max34451_set(const char *id,
+ const char *property,
+ uint16_t value)
+{
+ QDict *response;
+
+ response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
+ "'property': %s, 'value': %u } }",
+ id, property, value);
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static uint16_t max34451_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
+{
+ uint8_t resp[2];
+ i2c_read_block(i2cdev, reg, resp, sizeof(resp));
+ return (resp[1] << 8) | resp[0];
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static void max34451_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
+{
+ uint8_t data[2];
+
+ data[0] = value & 255;
+ data[1] = value >> 8;
+ i2c_write_block(i2cdev, reg, data, sizeof(data));
+}
+
+/* Test default values */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ /* Default temperatures and temperature fault limits */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ path = g_strdup_printf("temperature[%d]", i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmpuint(value, ==, DEFAULT_TEMPERATURE);
+ g_free(path);
+
+ /* Temperature sensors start on page 16 */
+ i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_TEMPERATURE);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OT_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OT_LIMIT);
+ }
+
+ /* Default voltages and fault limits */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ path = g_strdup_printf("vout[%d]", i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmpuint(value, ==, DEFAULT_VOUT);
+ g_free(path);
+
+ i2c_set8(i2cdev, PMBUS_PAGE, i);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_VOUT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_UV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_UV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_MIN);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_VMIN);
+ }
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+ g_assert_cmphex(i2c_value, ==, 0x40); /* DIRECT mode */
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_REVISION);
+ g_assert_cmphex(i2c_value, ==, 0x11); /* Rev 1.1 */
+}
+
+/* Test setting temperature */
+static void test_temperature(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ path = g_strdup_printf("temperature[%d]", i);
+ qmp_max34451_set(TEST_ID, path, 0xBE00 + i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmphex(value, ==, 0xBE00 + i);
+ g_free(path);
+ }
+
+ /* compare qmp read with i2c read separately */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ /* temperature[0] is on page 16 */
+ i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmphex(i2c_value, ==, 0xBE00 + i);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_TEMPERATURE_PEAK);
+ g_assert_cmphex(i2c_value, ==, 0xBE00 + i);
+ }
+}
+
+/* Test setting voltage */
+static void test_voltage(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ path = g_strdup_printf("vout[%d]", i);
+ qmp_max34451_set(TEST_ID, path, 3000 + i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmpuint(value, ==, 3000 + i);
+ g_free(path);
+ }
+
+ /* compare qmp read with i2c read separately */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ i2c_set8(i2cdev, PMBUS_PAGE, i);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmpuint(i2c_value, ==, 3000 + i);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_PEAK);
+ g_assert_cmpuint(i2c_value, ==, 3000 + i);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_MIN);
+ g_assert_cmpuint(i2c_value, ==, 3000 + i);
+ }
+}
+
+/* Test setting some read/write registers */
+static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, PMBUS_PAGE, 11);
+ i2c_value = i2c_get8(i2cdev, PMBUS_PAGE);
+ g_assert_cmpuint(i2c_value, ==, 11);
+
+ i2c_set8(i2cdev, PMBUS_OPERATION, 1);
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmpuint(i2c_value, ==, 1);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_MARGIN_HIGH, 5000);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_MARGIN_HIGH);
+ g_assert_cmpuint(i2c_value, ==, 5000);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_MARGIN_LOW, 4000);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_MARGIN_LOW);
+ g_assert_cmpuint(i2c_value, ==, 4000);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT, 5500);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5500);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT, 5600);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5600);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT, 5700);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5700);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT, 5800);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5800);
+
+ max34451_i2c_set16(i2cdev, PMBUS_POWER_GOOD_ON, 5900);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_POWER_GOOD_ON);
+ g_assert_cmpuint(i2c_value, ==, 5900);
+
+ max34451_i2c_set16(i2cdev, PMBUS_POWER_GOOD_OFF, 6100);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_POWER_GOOD_OFF);
+ g_assert_cmpuint(i2c_value, ==, 6100);
+}
+
+/* Test that Read only registers can't be written */
+static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value, i2c_init_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, PMBUS_PAGE, 1); /* move to page 1 */
+ i2c_init_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
+ i2c_set8(i2cdev, PMBUS_CAPABILITY, 0xF9);
+ i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
+ g_assert_cmpuint(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ max34451_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0xDEAD);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmpuint(i2c_init_value, ==, i2c_value);
+ g_assert_cmphex(i2c_value, !=, 0xDEAD);
+
+ i2c_set8(i2cdev, PMBUS_PAGE, 16); /* move to page 16 */
+ i2c_init_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ max34451_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0xABBA);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmpuint(i2c_init_value, ==, i2c_value);
+ g_assert_cmphex(i2c_value, !=, 0xABBA);
+}
+
+/* test over voltage faults */
+static void test_ov_faults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ uint8_t i2c_byte;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+ /* Test ov fault reporting */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ path = g_strdup_printf("vout[%d]", i);
+ i2c_set8(i2cdev, PMBUS_PAGE, i);
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT, 5000);
+ qmp_max34451_set(TEST_ID, path, 5100);
+ g_free(path);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_FAULT) != 0);
+ }
+}
+
+/* test over temperature faults */
+static void test_ot_faults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ uint8_t i2c_byte;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ path = g_strdup_printf("temperature[%d]", i);
+ i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
+ max34451_i2c_set16(i2cdev, PMBUS_OT_FAULT_LIMIT, 6000);
+ qmp_max34451_set(TEST_ID, path, 6100);
+ g_free(path);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_TEMPERATURE);
+ g_assert_true((i2c_value & PB_STATUS_TEMPERATURE) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_OT_FAULT) != 0);
+ }
+}
+
+static void max34451_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" TEST_ID ",address=0x4e"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { TEST_ADDR });
+
+ qos_node_create_driver("max34451", i2c_device_create);
+ qos_node_consumes("max34451", "i2c-bus", &opts);
+
+ qos_add_test("test_defaults", "max34451", test_defaults, NULL);
+ qos_add_test("test_temperature", "max34451", test_temperature, NULL);
+ qos_add_test("test_voltage", "max34451", test_voltage, NULL);
+ qos_add_test("test_rw_regs", "max34451", test_rw_regs, NULL);
+ qos_add_test("test_ro_regs", "max34451", test_ro_regs, NULL);
+ qos_add_test("test_ov_faults", "max34451", test_ov_faults, NULL);
+ qos_add_test("test_ot_faults", "max34451", test_ot_faults, NULL);
+}
+libqos_init(max34451_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index b03e854..ee7347b 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -202,12 +202,14 @@ qtests_s390x = \
qos_test_ss = ss.source_set()
qos_test_ss.add(
'ac97-test.c',
+ 'adm1272-test.c',
'ds1338-test.c',
'e1000-test.c',
'e1000e-test.c',
'eepro100-test.c',
'es1370-test.c',
'ipoctal232-test.c',
+ 'max34451-test.c',
'megasas-test.c',
'ne2000-test.c',
'tulip-test.c',
diff --git a/tests/qtest/npcm7xx_smbus-test.c b/tests/qtest/npcm7xx_smbus-test.c
index 4f9f493..6b3038a 100644
--- a/tests/qtest/npcm7xx_smbus-test.c
+++ b/tests/qtest/npcm7xx_smbus-test.c
@@ -18,7 +18,7 @@
#include "qemu/bitops.h"
#include "libqos/i2c.h"
#include "libqos/libqtest.h"
-#include "hw/misc/tmp105_regs.h"
+#include "hw/sensor/tmp105_regs.h"
#define NR_SMBUS_DEVICES 16
#define SMBUS_ADDR(x) (0xf0080000 + 0x1000 * (x))
diff --git a/tests/qtest/rtas-test.c b/tests/qtest/rtas-test.c
index 16751db..5f1194a 100644
--- a/tests/qtest/rtas-test.c
+++ b/tests/qtest/rtas-test.c
@@ -5,7 +5,7 @@
#include "libqos/libqos-spapr.h"
#include "libqos/rtas.h"
-static void test_rtas_get_time_of_day(void)
+static void run_test_rtas_get_time_of_day(const char *machine)
{
QOSState *qs;
struct tm tm;
@@ -13,7 +13,7 @@ static void test_rtas_get_time_of_day(void)
uint64_t ret;
time_t t1, t2;
- qs = qtest_spapr_boot("-machine pseries");
+ qs = qtest_spapr_boot(machine);
t1 = time(NULL);
ret = qrtas_get_time_of_day(qs->qts, &qs->alloc, &tm, &ns);
@@ -24,6 +24,16 @@ static void test_rtas_get_time_of_day(void)
qtest_shutdown(qs);
}
+static void test_rtas_get_time_of_day(void)
+{
+ run_test_rtas_get_time_of_day("-machine pseries");
+}
+
+static void test_rtas_get_time_of_day_vof(void)
+{
+ run_test_rtas_get_time_of_day("-machine pseries,x-vof=on");
+}
+
int main(int argc, char *argv[])
{
const char *arch = qtest_get_arch();
@@ -35,6 +45,7 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
qtest_add_func("rtas/get-time-of-day", test_rtas_get_time_of_day);
+ qtest_add_func("rtas/get-time-of-day-vof", test_rtas_get_time_of_day_vof);
return g_test_run();
}
diff --git a/tests/qtest/tmp105-test.c b/tests/qtest/tmp105-test.c
index f930a96..3678646 100644
--- a/tests/qtest/tmp105-test.c
+++ b/tests/qtest/tmp105-test.c
@@ -13,7 +13,7 @@
#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qapi/qmp/qdict.h"
-#include "hw/misc/tmp105_regs.h"
+#include "hw/sensor/tmp105_regs.h"
#define TMP105_TEST_ID "tmp105-test"
#define TMP105_TEST_ADDR 0x49
diff --git a/util/mmap-alloc.c b/util/mmap-alloc.c
index 838e286..893d864 100644
--- a/util/mmap-alloc.c
+++ b/util/mmap-alloc.c
@@ -225,6 +225,8 @@ static void *mmap_activate(void *ptr, size_t size, int fd,
"crash.\n", file_name);
g_free(proc_link);
g_free(file_name);
+ warn_report("Using non DAX backing file with 'pmem=on' option"
+ " is deprecated");
}
/*
* If mmap failed with MAP_SHARED_VALIDATE | MAP_SYNC, we will try
diff --git a/util/uri.c b/util/uri.c
index 8bdef84..ff72c60 100644
--- a/util/uri.c
+++ b/util/uri.c
@@ -1340,7 +1340,7 @@ static void uri_clean(URI *uri)
/**
* uri_free:
- * @uri: pointer to an URI
+ * @uri: pointer to an URI, NULL is ignored
*
* Free up the URI struct
*/
@@ -1939,15 +1939,9 @@ step_7:
val = uri_to_string(res);
done:
- if (ref != NULL) {
- uri_free(ref);
- }
- if (bas != NULL) {
- uri_free(bas);
- }
- if (res != NULL) {
- uri_free(res);
- }
+ uri_free(ref);
+ uri_free(bas);
+ uri_free(res);
return val;
}
@@ -2190,12 +2184,8 @@ done:
if (remove_path != 0) {
ref->path = NULL;
}
- if (ref != NULL) {
- uri_free(ref);
- }
- if (bas != NULL) {
- uri_free(bas);
- }
+ uri_free(ref);
+ uri_free(bas);
return val;
}