aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--MAINTAINERS25
-rw-r--r--Makefile.target2
-rw-r--r--accel/tcg/cputlb.c2
-rw-r--r--accel/tcg/translate-all.c21
-rw-r--r--arch_init.c4
-rw-r--r--block/block-backend.c23
-rw-r--r--block/block-copy.c182
-rw-r--r--block/commit.c5
-rw-r--r--block/copy-on-read.c8
-rw-r--r--block/crypto.c8
-rw-r--r--block/file-posix.c11
-rw-r--r--block/file-win32.c3
-rw-r--r--block/gluster.c1
-rw-r--r--block/io.c29
-rw-r--r--block/iscsi.c10
-rw-r--r--block/mirror.c198
-rw-r--r--block/nfs.c2
-rw-r--r--block/nvme.c155
-rw-r--r--block/parallels.c18
-rw-r--r--block/qcow.c9
-rw-r--r--block/qcow2-refcount.c2
-rw-r--r--block/qcow2-snapshot.c323
-rw-r--r--block/qcow2.c200
-rw-r--r--block/qcow2.h17
-rw-r--r--block/qed.c8
-rw-r--r--block/raw-format.c5
-rw-r--r--block/rbd.c1
-rw-r--r--block/sheepdog.c5
-rw-r--r--block/ssh.c3
-rw-r--r--block/trace-events9
-rw-r--r--block/vdi.c2
-rw-r--r--block/vhdx-log.c4
-rw-r--r--block/vhdx.c7
-rw-r--r--block/vmdk.c8
-rw-r--r--block/vpc.c2
-rw-r--r--blockdev.c2
-rwxr-xr-xconfigure63
-rw-r--r--contrib/gitdm/domain-map1
-rw-r--r--contrib/gitdm/group-map-individuals2
-rw-r--r--crypto/Makefile.objs2
-rw-r--r--crypto/cipher-gcrypt.c97
-rw-r--r--crypto/cipher-nettle.c18
-rw-r--r--default-configs/m68k-softmmu.mak1
-rw-r--r--docs/virtio-net-failover.rst68
-rw-r--r--exec-vary.c108
-rw-r--r--exec.c34
-rw-r--r--gdb-xml/riscv-32bit-virtual.xml11
-rw-r--r--gdb-xml/riscv-64bit-virtual.xml11
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/Makefile.objs1
-rw-r--r--hw/block/Kconfig3
-rw-r--r--hw/block/Makefile.objs1
-rw-r--r--hw/block/swim.c489
-rw-r--r--hw/block/virtio-blk.c4
-rw-r--r--hw/core/qdev.c25
-rw-r--r--hw/display/Kconfig5
-rw-r--r--hw/display/Makefile.objs1
-rw-r--r--hw/display/macfb.c477
-rw-r--r--hw/m68k/Kconfig10
-rw-r--r--hw/m68k/Makefile.objs1
-rw-r--r--hw/m68k/bootinfo.h114
-rw-r--r--hw/m68k/q800.c401
-rw-r--r--hw/misc/Kconfig5
-rw-r--r--hw/misc/Makefile.objs1
-rw-r--r--hw/misc/mac_via.c964
-rw-r--r--hw/net/Kconfig5
-rw-r--r--hw/net/Makefile.objs1
-rw-r--r--hw/net/dp8393x.c88
-rw-r--r--hw/net/trace-events14
-rw-r--r--hw/net/tulip.c1029
-rw-r--r--hw/net/tulip.h267
-rw-r--r--hw/net/virtio-net.c346
-rw-r--r--hw/nubus/Kconfig2
-rw-r--r--hw/nubus/Makefile.objs4
-rw-r--r--hw/nubus/mac-nubus-bridge.c45
-rw-r--r--hw/nubus/nubus-bridge.c34
-rw-r--r--hw/nubus/nubus-bus.c111
-rw-r--r--hw/nubus/nubus-device.c215
-rw-r--r--hw/pci/pci.c32
-rw-r--r--hw/pci/pcie.c6
-rw-r--r--hw/riscv/Kconfig1
-rw-r--r--hw/riscv/boot.c11
-rw-r--r--hw/riscv/sifive_u.c105
-rw-r--r--hw/riscv/spike.c2
-rw-r--r--hw/riscv/virt.c129
-rw-r--r--hw/scsi/esp.c336
-rw-r--r--hw/vfio/pci.c26
-rw-r--r--hw/vfio/pci.h1
-rw-r--r--hw/virtio/vhost.c4
-rw-r--r--hw/virtio/virtio.c72
-rw-r--r--include/block/block-copy.h5
-rw-r--r--include/block/block.h6
-rw-r--r--include/block/block_int.h17
-rw-r--r--include/block/nvme.h19
-rw-r--r--include/exec/cpu-all.h33
-rw-r--r--include/hw/block/swim.h76
-rw-r--r--include/hw/display/macfb.h64
-rw-r--r--include/hw/misc/mac_via.h115
-rw-r--r--include/hw/nubus/mac-nubus-bridge.h24
-rw-r--r--include/hw/nubus/nubus.h69
-rw-r--r--include/hw/pci/pci.h4
-rw-r--r--include/hw/pci/pci_ids.h1
-rw-r--r--include/hw/qdev-core.h30
-rw-r--r--include/hw/riscv/sifive_u.h12
-rw-r--r--include/hw/riscv/spike.h4
-rw-r--r--include/hw/riscv/virt.h14
-rw-r--r--include/hw/scsi/esp.h15
-rw-r--r--include/hw/virtio/virtio-net.h14
-rw-r--r--include/hw/virtio/virtio.h14
-rw-r--r--include/migration/vmstate.h2
-rw-r--r--include/qemu-common.h6
-rw-r--r--include/qemu/co-shared-resource.h71
-rw-r--r--include/qemu/compiler.h7
-rw-r--r--include/sysemu/block-backend.h8
-rw-r--r--linux-user/riscv/cpu_loop.c3
-rw-r--r--migration/migration.c21
-rw-r--r--migration/migration.h3
-rw-r--r--migration/savevm.c31
-rw-r--r--migration/savevm.h2
-rw-r--r--net/colo-compare.c6
-rw-r--r--pc-bios/openbios-ppcbin696908 -> 696912 bytes
-rw-r--r--pc-bios/openbios-sparc32bin382048 -> 382048 bytes
-rw-r--r--pc-bios/openbios-sparc64bin1593408 -> 1593408 bytes
-rw-r--r--python/qemu/machine.py26
-rw-r--r--python/qemu/qtest.py9
-rw-r--r--qapi/migration.json24
-rw-r--r--qapi/net.json21
-rw-r--r--qdev-monitor.c43
-rw-r--r--qemu-deprecated.texi12
-rw-r--r--qemu-img.c46
-rw-r--r--qemu-io-cmds.c7
-rw-r--r--qemu-options.hx2
m---------roms/openbios0
-rw-r--r--scripts/qapi/doc.py75
-rw-r--r--scripts/qapi/expr.py32
-rw-r--r--scripts/qapi/parser.py29
-rw-r--r--scripts/qapi/schema.py97
-rw-r--r--target/riscv/cpu.c2
-rw-r--r--target/riscv/cpu.h7
-rw-r--r--target/riscv/cpu_helper.c28
-rw-r--r--target/riscv/csr.c5
-rw-r--r--target/riscv/gdbstub.c36
-rw-r--r--target/riscv/pmp.c13
-rw-r--r--tcg/tci.c15
-rw-r--r--tests/Makefile.include7
-rw-r--r--tests/acceptance/avocado_qemu/__init__.py48
-rw-r--r--tests/acceptance/boot_linux_console.py191
-rw-r--r--tests/acceptance/linux_ssh_mips_malta.py18
-rw-r--r--tests/acceptance/machine_sparc_leon3.py34
-rw-r--r--tests/acceptance/ppc_prep_40p.py82
-rw-r--r--tests/benchmark-crypto-cipher.c39
-rw-r--r--tests/benchmark-crypto-hash.c17
-rw-r--r--tests/libqos/libqos.c3
-rw-r--r--tests/qapi-schema/doc-bad-alternate-member.err2
-rw-r--r--tests/qapi-schema/doc-bad-boxed-command-arg.err1
-rw-r--r--tests/qapi-schema/doc-bad-boxed-command-arg.json14
-rw-r--r--tests/qapi-schema/doc-bad-boxed-command-arg.out0
-rw-r--r--tests/qapi-schema/doc-bad-command-arg.err2
-rw-r--r--tests/qapi-schema/doc-bad-enum-member.err1
-rw-r--r--tests/qapi-schema/doc-bad-enum-member.json8
-rw-r--r--tests/qapi-schema/doc-bad-enum-member.out0
-rw-r--r--tests/qapi-schema/doc-bad-event-arg.err1
-rw-r--r--tests/qapi-schema/doc-bad-event-arg.json7
-rw-r--r--tests/qapi-schema/doc-bad-event-arg.out0
-rw-r--r--tests/qapi-schema/doc-bad-feature.err1
-rw-r--r--tests/qapi-schema/doc-bad-feature.json9
-rw-r--r--tests/qapi-schema/doc-bad-feature.out0
-rw-r--r--tests/qapi-schema/doc-bad-union-member.err2
-rw-r--r--tests/qapi-schema/doc-good.json14
-rw-r--r--tests/qapi-schema/doc-good.out27
-rw-r--r--tests/qapi-schema/doc-good.texi27
-rw-r--r--tests/qapi-schema/doc-undoc-feature.err2
-rw-r--r--tests/qapi-schema/doc-undoc-feature.json9
-rw-r--r--tests/qapi-schema/doc-undoc-feature.out0
-rwxr-xr-xtests/qapi-schema/test-qapi.py2
-rwxr-xr-xtests/qemu-iotests/0836
-rw-r--r--tests/qemu-iotests/083.out34
-rwxr-xr-xtests/qemu-iotests/09319
-rwxr-xr-xtests/qemu-iotests/13614
-rwxr-xr-xtests/qemu-iotests/1408
-rw-r--r--tests/qemu-iotests/140.out2
-rwxr-xr-xtests/qemu-iotests/1436
-rw-r--r--tests/qemu-iotests/143.out2
-rwxr-xr-xtests/qemu-iotests/1472
-rwxr-xr-xtests/qemu-iotests/1812
-rwxr-xr-xtests/qemu-iotests/1824
-rwxr-xr-xtests/qemu-iotests/1832
-rwxr-xr-xtests/qemu-iotests/1924
-rw-r--r--tests/qemu-iotests/192.out2
-rwxr-xr-xtests/qemu-iotests/1944
-rwxr-xr-xtests/qemu-iotests/2012
-rwxr-xr-xtests/qemu-iotests/2052
-rwxr-xr-xtests/qemu-iotests/2082
-rwxr-xr-xtests/qemu-iotests/2093
-rw-r--r--tests/qemu-iotests/2222
-rwxr-xr-xtests/qemu-iotests/22314
-rwxr-xr-xtests/qemu-iotests/2404
-rwxr-xr-xtests/qemu-iotests/2412
-rw-r--r--tests/qemu-iotests/2452
-rwxr-xr-xtests/qemu-iotests/261523
-rw-r--r--tests/qemu-iotests/261.out346
-rwxr-xr-xtests/qemu-iotests/2642
-rwxr-xr-xtests/qemu-iotests/2674
-rw-r--r--tests/qemu-iotests/267.out2
-rwxr-xr-xtests/qemu-iotests/check15
-rw-r--r--tests/qemu-iotests/common.filter7
-rw-r--r--tests/qemu-iotests/common.nbd2
-rw-r--r--tests/qemu-iotests/common.rc20
-rw-r--r--tests/qemu-iotests/group1
-rw-r--r--tests/qemu-iotests/iotests.py68
-rw-r--r--tests/test-block-iothread.c8
-rw-r--r--util/Makefile.objs1
-rw-r--r--util/hbitmap.c8
-rw-r--r--util/qemu-co-shared-resource.c76
-rw-r--r--vl.c9
216 files changed, 9179 insertions, 971 deletions
diff --git a/.travis.yml b/.travis.yml
index 34bc8134f5..678e33decc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -271,7 +271,7 @@ matrix:
# Acceptance (Functional) tests
- env:
- - CONFIG="--python=/usr/bin/python3 --target-list=x86_64-softmmu,mips-softmmu,mips64el-softmmu,aarch64-softmmu,arm-softmmu,s390x-softmmu,alpha-softmmu,ppc64-softmmu,m68k-softmmu"
+ - CONFIG="--python=/usr/bin/python3 --target-list=x86_64-softmmu,mips-softmmu,mips64el-softmmu,aarch64-softmmu,arm-softmmu,s390x-softmmu,alpha-softmmu,ppc-softmmu,ppc64-softmmu,m68k-softmmu,sparc-softmmu"
- TEST_CMD="make check-acceptance"
after_failure:
- cat tests/results/latest/job.log
diff --git a/MAINTAINERS b/MAINTAINERS
index a038988a23..92961faa0e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -917,6 +917,20 @@ F: hw/m68k/next-*.c
F: hw/display/next-fb.c
F: include/hw/m68k/next-cube.h
+q800
+M: Laurent Vivier <laurent@vivier.eu>
+S: Maintained
+F: hw/m68k/q800.c
+F: hw/misc/mac_via.c
+F: hw/nubus/*
+F: hw/display/macfb.c
+F: hw/block/swim.c
+F: hw/m68k/bootinfo.h
+F: include/hw/misc/mac_via.h
+F: include/hw/nubus/*
+F: include/hw/display/macfb.h
+F: include/hw/block/swim.h
+
MicroBlaze Machines
-------------------
petalogix_s3adsp1800
@@ -1068,6 +1082,7 @@ F: hw/rtc/m48t59-isa.c
F: include/hw/isa/pc87312.h
F: include/hw/rtc/m48t59.h
F: pc-bios/ppc_rom.bin
+F: tests/acceptance/ppc_prep_40p.py
sPAPR
M: David Gibson <david@gibson.dropbear.id.au>
@@ -1173,6 +1188,7 @@ S: Maintained
F: hw/sparc/leon3.c
F: hw/*/grlib*
F: include/hw/*/grlib*
+F: tests/acceptance/machine_sparc_leon3.py
S390 Machines
-------------
@@ -1421,6 +1437,7 @@ S: Odd Fixes
F: hw/net/
F: include/hw/net/
F: tests/virtio-net-test.c
+F: docs/virtio-net-failover.rst
T: git https://github.com/jasowang/qemu.git net
Parallel NOR Flash devices
@@ -1652,6 +1669,12 @@ M: Stefan Weil <sw@weilnetz.de>
S: Maintained
F: hw/net/eepro100.c
+tulip
+M: Sven Schnelle <svens@stackframe.org>
+S: Maintained
+F: hw/net/tulip.c
+F: hw/net/tulip.h
+
Generic Loader
M: Alistair Francis <alistair@alistair23.me>
S: Maintained
@@ -2001,7 +2024,7 @@ Python scripts
M: Eduardo Habkost <ehabkost@redhat.com>
M: Cleber Rosa <crosa@redhat.com>
S: Odd fixes
-F: scripts/qmp/*
+F: python/qemu/*py
F: scripts/*.py
F: tests/*.py
diff --git a/Makefile.target b/Makefile.target
index 5de6c95efb..24d79d26eb 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -107,7 +107,7 @@ obj-y += trace/
#########################################################
# cpu emulator library
-obj-y += exec.o
+obj-y += exec.o exec-vary.o
obj-y += accel/
obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/tcg-op-vec.o tcg/tcg-op-gvec.o
obj-$(CONFIG_TCG) += tcg/tcg-common.o tcg/optimize.o
diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c
index c7a986d7cb..68487dceb5 100644
--- a/accel/tcg/cputlb.c
+++ b/accel/tcg/cputlb.c
@@ -1204,7 +1204,7 @@ void *tlb_vaddr_to_host(CPUArchState *env, abi_ptr addr,
MMUAccessType access_type, int mmu_idx)
{
CPUTLBEntry *entry = tlb_entry(env, mmu_idx, addr);
- uintptr_t tlb_addr, page;
+ target_ulong tlb_addr, page;
size_t elt_ofs;
switch (access_type) {
diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c
index 6d1b0ecd69..9f48da9472 100644
--- a/accel/tcg/translate-all.c
+++ b/accel/tcg/translate-all.c
@@ -1156,23 +1156,6 @@ void tcg_exec_init(unsigned long tb_size)
#endif
}
-/*
- * Allocate a new translation block. Flush the translation buffer if
- * too many translation blocks or too much generated code.
- */
-static TranslationBlock *tb_alloc(target_ulong pc)
-{
- TranslationBlock *tb;
-
- assert_memory_lock();
-
- tb = tcg_tb_alloc(tcg_ctx);
- if (unlikely(tb == NULL)) {
- return NULL;
- }
- return tb;
-}
-
/* call with @p->lock held */
static inline void invalidate_page_bitmap(PageDesc *p)
{
@@ -1692,6 +1675,7 @@ TranslationBlock *tb_gen_code(CPUState *cpu,
TCGProfile *prof = &tcg_ctx->prof;
int64_t ti;
#endif
+
assert_memory_lock();
phys_pc = get_page_addr_code(env, pc);
@@ -1717,7 +1701,7 @@ TranslationBlock *tb_gen_code(CPUState *cpu,
}
buffer_overflow:
- tb = tb_alloc(pc);
+ tb = tcg_tb_alloc(tcg_ctx);
if (unlikely(!tb)) {
/* flush must be done */
tb_flush(cpu);
@@ -1733,6 +1717,7 @@ TranslationBlock *tb_gen_code(CPUState *cpu,
tb->cs_base = cs_base;
tb->flags = flags;
tb->cflags = cflags;
+ tb->orig_tb = NULL;
tb->trace_vcpu_dstate = *cpu->trace_dstate;
tcg_ctx->tb_cflags = cflags;
tb_overflow:
diff --git a/arch_init.c b/arch_init.c
index 0a1531124c..705d0b94ad 100644
--- a/arch_init.c
+++ b/arch_init.c
@@ -38,6 +38,10 @@
int graphic_width = 1024;
int graphic_height = 768;
int graphic_depth = 8;
+#elif defined(TARGET_M68K)
+int graphic_width = 800;
+int graphic_height = 600;
+int graphic_depth = 8;
#else
int graphic_width = 800;
int graphic_height = 600;
diff --git a/block/block-backend.c b/block/block-backend.c
index eb22ff306e..8b8f2a80a0 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -1178,9 +1178,10 @@ int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset,
return ret;
}
-int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
- unsigned int bytes, QEMUIOVector *qiov,
- BdrvRequestFlags flags)
+int coroutine_fn blk_co_pwritev_part(BlockBackend *blk, int64_t offset,
+ unsigned int bytes,
+ QEMUIOVector *qiov, size_t qiov_offset,
+ BdrvRequestFlags flags)
{
int ret;
BlockDriverState *bs;
@@ -1207,11 +1208,19 @@ int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
flags |= BDRV_REQ_FUA;
}
- ret = bdrv_co_pwritev(blk->root, offset, bytes, qiov, flags);
+ ret = bdrv_co_pwritev_part(blk->root, offset, bytes, qiov, qiov_offset,
+ flags);
bdrv_dec_in_flight(bs);
return ret;
}
+int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
+ unsigned int bytes, QEMUIOVector *qiov,
+ BdrvRequestFlags flags)
+{
+ return blk_co_pwritev_part(blk, offset, bytes, qiov, 0, flags);
+}
+
typedef struct BlkRwCo {
BlockBackend *blk;
int64_t offset;
@@ -2063,15 +2072,15 @@ int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf,
BDRV_REQ_WRITE_COMPRESSED);
}
-int blk_truncate(BlockBackend *blk, int64_t offset, PreallocMode prealloc,
- Error **errp)
+int blk_truncate(BlockBackend *blk, int64_t offset, bool exact,
+ PreallocMode prealloc, Error **errp)
{
if (!blk_is_available(blk)) {
error_setg(errp, "No medium inserted");
return -ENOMEDIUM;
}
- return bdrv_truncate(blk->root, offset, prealloc, errp);
+ return bdrv_truncate(blk->root, offset, exact, prealloc, errp);
}
static void blk_pdiscard_entry(void *opaque)
diff --git a/block/block-copy.c b/block/block-copy.c
index 066e3a7274..c39cc9cffe 100644
--- a/block/block-copy.c
+++ b/block/block-copy.c
@@ -18,6 +18,11 @@
#include "qapi/error.h"
#include "block/block-copy.h"
#include "sysemu/block-backend.h"
+#include "qemu/units.h"
+
+#define BLOCK_COPY_MAX_COPY_RANGE (16 * MiB)
+#define BLOCK_COPY_MAX_BUFFER (1 * MiB)
+#define BLOCK_COPY_MAX_MEM (128 * MiB)
static void coroutine_fn block_copy_wait_inflight_reqs(BlockCopyState *s,
int64_t start,
@@ -61,6 +66,7 @@ void block_copy_state_free(BlockCopyState *s)
}
bdrv_release_dirty_bitmap(s->copy_bitmap);
+ shres_destroy(s->mem);
g_free(s);
}
@@ -71,8 +77,9 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
BlockCopyState *s;
BdrvDirtyBitmap *copy_bitmap;
uint32_t max_transfer =
- MIN_NON_ZERO(INT_MAX, MIN_NON_ZERO(source->bs->bl.max_transfer,
- target->bs->bl.max_transfer));
+ MIN_NON_ZERO(INT_MAX,
+ MIN_NON_ZERO(source->bs->bl.max_transfer,
+ target->bs->bl.max_transfer));
copy_bitmap = bdrv_create_dirty_bitmap(source->bs, cluster_size, NULL,
errp);
@@ -89,19 +96,31 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
.cluster_size = cluster_size,
.len = bdrv_dirty_bitmap_size(copy_bitmap),
.write_flags = write_flags,
+ .mem = shres_create(BLOCK_COPY_MAX_MEM),
};
- s->copy_range_size = QEMU_ALIGN_DOWN(max_transfer, cluster_size),
- /*
- * Set use_copy_range, consider the following:
- * 1. Compression is not supported for copy_range.
- * 2. copy_range does not respect max_transfer (it's a TODO), so we factor
- * that in here. If max_transfer is smaller than the job->cluster_size,
- * we do not use copy_range (in that case it's zero after aligning down
- * above).
- */
- s->use_copy_range =
- !(write_flags & BDRV_REQ_WRITE_COMPRESSED) && s->copy_range_size > 0;
+ if (max_transfer < cluster_size) {
+ /*
+ * copy_range does not respect max_transfer. We don't want to bother
+ * with requests smaller than block-copy cluster size, so fallback to
+ * buffered copying (read and write respect max_transfer on their
+ * behalf).
+ */
+ s->use_copy_range = false;
+ s->copy_size = cluster_size;
+ } else if (write_flags & BDRV_REQ_WRITE_COMPRESSED) {
+ /* Compression is not supported for copy_range */
+ s->use_copy_range = false;
+ s->copy_size = MAX(cluster_size, BLOCK_COPY_MAX_BUFFER);
+ } else {
+ /*
+ * copy_range does not respect max_transfer (it's a TODO), so we factor
+ * that in here.
+ */
+ s->use_copy_range = true;
+ s->copy_size = MIN(MAX(cluster_size, BLOCK_COPY_MAX_COPY_RANGE),
+ QEMU_ALIGN_DOWN(max_transfer, cluster_size));
+ }
QLIST_INIT(&s->inflight_reqs);
@@ -120,79 +139,71 @@ void block_copy_set_callbacks(
}
/*
- * Copy range to target with a bounce buffer and return the bytes copied. If
- * error occurred, return a negative error number
+ * block_copy_do_copy
+ *
+ * Do copy of cluser-aligned chunk. @end is allowed to exceed s->len only to
+ * cover last cluster when s->len is not aligned to clusters.
+ *
+ * No sync here: nor bitmap neighter intersecting requests handling, only copy.
+ *
+ * Returns 0 on success.
*/
-static int coroutine_fn block_copy_with_bounce_buffer(BlockCopyState *s,
- int64_t start,
- int64_t end,
- bool *error_is_read,
- void **bounce_buffer)
+static int coroutine_fn block_copy_do_copy(BlockCopyState *s,
+ int64_t start, int64_t end,
+ bool *error_is_read)
{
int ret;
- int nbytes;
+ int nbytes = MIN(end, s->len) - start;
+ void *bounce_buffer = NULL;
assert(QEMU_IS_ALIGNED(start, s->cluster_size));
- bdrv_reset_dirty_bitmap(s->copy_bitmap, start, s->cluster_size);
- nbytes = MIN(s->cluster_size, s->len - start);
- if (!*bounce_buffer) {
- *bounce_buffer = qemu_blockalign(s->source->bs, s->cluster_size);
+ assert(QEMU_IS_ALIGNED(end, s->cluster_size));
+ assert(end < s->len || end == QEMU_ALIGN_UP(s->len, s->cluster_size));
+
+ if (s->use_copy_range) {
+ ret = bdrv_co_copy_range(s->source, start, s->target, start, nbytes,
+ 0, s->write_flags);
+ if (ret < 0) {
+ trace_block_copy_copy_range_fail(s, start, ret);
+ s->use_copy_range = false;
+ s->copy_size = MAX(s->cluster_size, BLOCK_COPY_MAX_BUFFER);
+ /* Fallback to read+write with allocated buffer */
+ } else {
+ goto out;
+ }
}
- ret = bdrv_co_pread(s->source, start, nbytes, *bounce_buffer, 0);
+ /*
+ * In case of failed copy_range request above, we may proceed with buffered
+ * request larger than BLOCK_COPY_MAX_BUFFER. Still, further requests will
+ * be properly limited, so don't care too much.
+ */
+
+ bounce_buffer = qemu_blockalign(s->source->bs, nbytes);
+
+ ret = bdrv_co_pread(s->source, start, nbytes, bounce_buffer, 0);
if (ret < 0) {
- trace_block_copy_with_bounce_buffer_read_fail(s, start, ret);
+ trace_block_copy_read_fail(s, start, ret);
if (error_is_read) {
*error_is_read = true;
}
- goto fail;
+ goto out;
}
- ret = bdrv_co_pwrite(s->target, start, nbytes, *bounce_buffer,
+ ret = bdrv_co_pwrite(s->target, start, nbytes, bounce_buffer,
s->write_flags);
if (ret < 0) {
- trace_block_copy_with_bounce_buffer_write_fail(s, start, ret);
+ trace_block_copy_write_fail(s, start, ret);
if (error_is_read) {
*error_is_read = false;
}
- goto fail;
+ goto out;
}
- return nbytes;
-fail:
- bdrv_set_dirty_bitmap(s->copy_bitmap, start, s->cluster_size);
- return ret;
-
-}
-
-/*
- * Copy range to target and return the bytes copied. If error occurred, return a
- * negative error number.
- */
-static int coroutine_fn block_copy_with_offload(BlockCopyState *s,
- int64_t start,
- int64_t end)
-{
- int ret;
- int nr_clusters;
- int nbytes;
-
- assert(QEMU_IS_ALIGNED(s->copy_range_size, s->cluster_size));
- assert(QEMU_IS_ALIGNED(start, s->cluster_size));
- nbytes = MIN(s->copy_range_size, MIN(end, s->len) - start);
- nr_clusters = DIV_ROUND_UP(nbytes, s->cluster_size);
- bdrv_reset_dirty_bitmap(s->copy_bitmap, start,
- s->cluster_size * nr_clusters);
- ret = bdrv_co_copy_range(s->source, start, s->target, start, nbytes,
- 0, s->write_flags);
- if (ret < 0) {
- trace_block_copy_with_offload_fail(s, start, ret);
- bdrv_set_dirty_bitmap(s->copy_bitmap, start,
- s->cluster_size * nr_clusters);
- return ret;
- }
+out:
+ qemu_vfree(bounce_buffer);
- return nbytes;
+ return ret;
}
/*
@@ -271,7 +282,6 @@ int coroutine_fn block_copy(BlockCopyState *s,
{
int ret = 0;
int64_t end = bytes + start; /* bytes */
- void *bounce_buffer = NULL;
int64_t status_bytes;
BlockCopyInFlightReq req;
@@ -289,7 +299,7 @@ int coroutine_fn block_copy(BlockCopyState *s,
block_copy_inflight_req_begin(s, &req, start, end);
while (start < end) {
- int64_t dirty_end;
+ int64_t next_zero, chunk_end;
if (!bdrv_dirty_bitmap_get(s->copy_bitmap, start)) {
trace_block_copy_skip(s, start);
@@ -297,10 +307,14 @@ int coroutine_fn block_copy(BlockCopyState *s,
continue; /* already copied */
}
- dirty_end = bdrv_dirty_bitmap_next_zero(s->copy_bitmap, start,
- (end - start));
- if (dirty_end < 0) {
- dirty_end = end;
+ chunk_end = MIN(end, start + s->copy_size);
+
+ next_zero = bdrv_dirty_bitmap_next_zero(s->copy_bitmap, start,
+ chunk_end - start);
+ if (next_zero >= 0) {
+ assert(next_zero > start); /* start is dirty */
+ assert(next_zero < chunk_end); /* no need to do MIN() */
+ chunk_end = next_zero;
}
if (s->skip_unallocated) {
@@ -311,34 +325,26 @@ int coroutine_fn block_copy(BlockCopyState *s,
continue;
}
/* Clamp to known allocated region */
- dirty_end = MIN(dirty_end, start + status_bytes);
+ chunk_end = MIN(chunk_end, start + status_bytes);
}
trace_block_copy_process(s, start);
- if (s->use_copy_range) {
- ret = block_copy_with_offload(s, start, dirty_end);
- if (ret < 0) {
- s->use_copy_range = false;
- }
- }
- if (!s->use_copy_range) {
- ret = block_copy_with_bounce_buffer(s, start, dirty_end,
- error_is_read, &bounce_buffer);
- }
+ bdrv_reset_dirty_bitmap(s->copy_bitmap, start, chunk_end - start);
+
+ co_get_from_shres(s->mem, chunk_end - start);
+ ret = block_copy_do_copy(s, start, chunk_end, error_is_read);
+ co_put_to_shres(s->mem, chunk_end - start);
if (ret < 0) {
+ bdrv_set_dirty_bitmap(s->copy_bitmap, start, chunk_end - start);
break;
}
- start += ret;
- s->progress_bytes_callback(ret, s->progress_opaque);
+ s->progress_bytes_callback(chunk_end - start, s->progress_opaque);
+ start = chunk_end;
ret = 0;
}
- if (bounce_buffer) {
- qemu_vfree(bounce_buffer);
- }
-
block_copy_inflight_req_end(&req);
return ret;
diff --git a/block/commit.c b/block/commit.c
index bc8454463d..23c90b3b91 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -155,7 +155,7 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
}
if (base_len < len) {
- ret = blk_truncate(s->base, len, PREALLOC_MODE_OFF, NULL);
+ ret = blk_truncate(s->base, len, false, PREALLOC_MODE_OFF, NULL);
if (ret) {
goto out;
}
@@ -471,7 +471,8 @@ int bdrv_commit(BlockDriverState *bs)
* grow the backing file image if possible. If not possible,
* we must return an error */
if (length > backing_length) {
- ret = blk_truncate(backing, length, PREALLOC_MODE_OFF, &local_err);
+ ret = blk_truncate(backing, length, false, PREALLOC_MODE_OFF,
+ &local_err);
if (ret < 0) {
error_report_err(local_err);
goto ro_cleanup;
diff --git a/block/copy-on-read.c b/block/copy-on-read.c
index 6631f30205..e95223d3cb 100644
--- a/block/copy-on-read.c
+++ b/block/copy-on-read.c
@@ -73,13 +73,6 @@ static int64_t cor_getlength(BlockDriverState *bs)
}
-static int coroutine_fn cor_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
-{
- return bdrv_co_truncate(bs->file, offset, prealloc, errp);
-}
-
-
static int coroutine_fn cor_co_preadv(BlockDriverState *bs,
uint64_t offset, uint64_t bytes,
QEMUIOVector *qiov, int flags)
@@ -139,7 +132,6 @@ static BlockDriver bdrv_copy_on_read = {
.bdrv_child_perm = cor_child_perm,
.bdrv_getlength = cor_getlength,
- .bdrv_co_truncate = cor_co_truncate,
.bdrv_co_preadv = cor_co_preadv,
.bdrv_co_pwritev = cor_co_pwritev,
diff --git a/block/crypto.c b/block/crypto.c
index 7eb698774e..24823835c1 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -113,8 +113,8 @@ static ssize_t block_crypto_init_func(QCryptoBlock *block,
* available to the guest, so we must take account of that
* which will be used by the crypto header
*/
- return blk_truncate(data->blk, data->size + headerlen, data->prealloc,
- errp);
+ return blk_truncate(data->blk, data->size + headerlen, false,
+ data->prealloc, errp);
}
@@ -297,7 +297,7 @@ static int block_crypto_co_create_generic(BlockDriverState *bs,
}
static int coroutine_fn
-block_crypto_co_truncate(BlockDriverState *bs, int64_t offset,
+block_crypto_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp)
{
BlockCrypto *crypto = bs->opaque;
@@ -311,7 +311,7 @@ block_crypto_co_truncate(BlockDriverState *bs, int64_t offset,
offset += payload_offset;
- return bdrv_co_truncate(bs->file, offset, prealloc, errp);
+ return bdrv_co_truncate(bs->file, offset, exact, prealloc, errp);
}
static void block_crypto_close(BlockDriverState *bs)
diff --git a/block/file-posix.c b/block/file-posix.c
index 5d1995a07c..0b7e904d48 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -2020,7 +2020,8 @@ raw_regular_truncate(BlockDriverState *bs, int fd, int64_t offset,
}
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+ bool exact, PreallocMode prealloc,
+ Error **errp)
{
BDRVRawState *s = bs->opaque;
struct stat st;
@@ -2033,6 +2034,7 @@ static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
}
if (S_ISREG(st.st_mode)) {
+ /* Always resizes to the exact @offset */
return raw_regular_truncate(bs, s->fd, offset, prealloc, errp);
}
@@ -2043,7 +2045,12 @@ static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
}
if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
- if (offset > raw_getlength(bs)) {
+ int64_t cur_length = raw_getlength(bs);
+
+ if (offset != cur_length && exact) {
+ error_setg(errp, "Cannot resize device files");
+ return -ENOTSUP;
+ } else if (offset > cur_length) {
error_setg(errp, "Cannot grow device files");
return -EINVAL;
}
diff --git a/block/file-win32.c b/block/file-win32.c
index 41f55dfece..77e8ff7b68 100644
--- a/block/file-win32.c
+++ b/block/file-win32.c
@@ -468,7 +468,8 @@ static void raw_close(BlockDriverState *bs)
}
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+ bool exact, PreallocMode prealloc,
+ Error **errp)
{
BDRVRawState *s = bs->opaque;
LONG low, high;
diff --git a/block/gluster.c b/block/gluster.c
index 64028b2cba..4fa4a77a47 100644
--- a/block/gluster.c
+++ b/block/gluster.c
@@ -1225,6 +1225,7 @@ static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
static coroutine_fn int qemu_gluster_co_truncate(BlockDriverState *bs,
int64_t offset,
+ bool exact,
PreallocMode prealloc,
Error **errp)
{
diff --git a/block/io.c b/block/io.c
index e46d9e8b97..02659f994d 100644
--- a/block/io.c
+++ b/block/io.c
@@ -3291,8 +3291,12 @@ static void bdrv_parent_cb_resize(BlockDriverState *bs)
/**
* Truncate file to 'offset' bytes (needed only for file protocols)
+ *
+ * If 'exact' is true, the file must be resized to exactly the given
+ * 'offset'. Otherwise, it is sufficient for the node to be at least
+ * 'offset' bytes in length.
*/
-int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset,
+int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp)
{
BlockDriverState *bs = child->bs;
@@ -3347,20 +3351,19 @@ int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset,
goto out;
}
- if (!drv->bdrv_co_truncate) {
- if (bs->file && drv->is_filter) {
- ret = bdrv_co_truncate(bs->file, offset, prealloc, errp);
- goto out;
- }
+ if (drv->bdrv_co_truncate) {
+ ret = drv->bdrv_co_truncate(bs, offset, exact, prealloc, errp);
+ } else if (bs->file && drv->is_filter) {
+ ret = bdrv_co_truncate(bs->file, offset, exact, prealloc, errp);
+ } else {
error_setg(errp, "Image format driver does not support resize");
ret = -ENOTSUP;
goto out;
}
-
- ret = drv->bdrv_co_truncate(bs, offset, prealloc, errp);
if (ret < 0) {
goto out;
}
+
ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS);
if (ret < 0) {
error_setg_errno(errp, -ret, "Could not refresh total sector count");
@@ -3382,6 +3385,7 @@ out:
typedef struct TruncateCo {
BdrvChild *child;
int64_t offset;
+ bool exact;
PreallocMode prealloc;
Error **errp;
int ret;
@@ -3390,18 +3394,19 @@ typedef struct TruncateCo {
static void coroutine_fn bdrv_truncate_co_entry(void *opaque)
{
TruncateCo *tco = opaque;
- tco->ret = bdrv_co_truncate(tco->child, tco->offset, tco->prealloc,
- tco->errp);
+ tco->ret = bdrv_co_truncate(tco->child, tco->offset, tco->exact,
+ tco->prealloc, tco->errp);
aio_wait_kick();
}
-int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc,
- Error **errp)
+int bdrv_truncate(BdrvChild *child, int64_t offset, bool exact,
+ PreallocMode prealloc, Error **errp)
{
Coroutine *co;
TruncateCo tco = {
.child = child,
.offset = offset,
+ .exact = exact,
.prealloc = prealloc,
.errp = errp,
.ret = NOT_DONE,
diff --git a/block/iscsi.c b/block/iscsi.c
index 2ced15066a..2aea7e3f13 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -2123,9 +2123,11 @@ static void iscsi_reopen_commit(BDRVReopenState *reopen_state)
}
static int coroutine_fn iscsi_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+ bool exact, PreallocMode prealloc,
+ Error **errp)
{
IscsiLun *iscsilun = bs->opaque;
+ int64_t cur_length;
Error *local_err = NULL;
if (prealloc != PREALLOC_MODE_OFF) {
@@ -2145,7 +2147,11 @@ static int coroutine_fn iscsi_co_truncate(BlockDriverState *bs, int64_t offset,
return -EIO;
}
- if (offset > iscsi_getlength(bs)) {
+ cur_length = iscsi_getlength(bs);
+ if (offset != cur_length && exact) {
+ error_setg(errp, "Cannot resize iSCSI devices");
+ return -ENOTSUP;
+ } else if (offset > cur_length) {
error_setg(errp, "Cannot grow iSCSI devices");
return -EINVAL;
}
diff --git a/block/mirror.c b/block/mirror.c
index a6c50caea4..f0f2d9dff1 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -620,11 +620,11 @@ static int mirror_exit_common(Job *job)
{
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
BlockJob *bjob = &s->common;
- MirrorBDSOpaque *bs_opaque = s->mirror_top_bs->opaque;
+ MirrorBDSOpaque *bs_opaque;
AioContext *replace_aio_context = NULL;
- BlockDriverState *src = s->mirror_top_bs->backing->bs;
- BlockDriverState *target_bs = blk_bs(s->target);
- BlockDriverState *mirror_top_bs = s->mirror_top_bs;
+ BlockDriverState *src;
+ BlockDriverState *target_bs;
+ BlockDriverState *mirror_top_bs;
Error *local_err = NULL;
bool abort = job->ret < 0;
int ret = 0;
@@ -634,6 +634,11 @@ static int mirror_exit_common(Job *job)
}
s->prepared = true;
+ mirror_top_bs = s->mirror_top_bs;
+ bs_opaque = mirror_top_bs->opaque;
+ src = mirror_top_bs->backing->bs;
+ target_bs = blk_bs(s->target);
+
if (bdrv_chain_contains(src, target_bs)) {
bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs);
}
@@ -873,8 +878,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
}
if (s->bdev_length > base_length) {
- ret = blk_truncate(s->target, s->bdev_length, PREALLOC_MODE_OFF,
- NULL);
+ ret = blk_truncate(s->target, s->bdev_length, false,
+ PREALLOC_MODE_OFF, NULL);
if (ret < 0) {
goto immediate_exit;
}
@@ -1181,84 +1186,107 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
uint64_t offset, uint64_t bytes,
QEMUIOVector *qiov, int flags)
{
- QEMUIOVector target_qiov;
- uint64_t dirty_offset = offset;
- uint64_t dirty_bytes;
+ int ret;
+ size_t qiov_offset = 0;
+ int64_t bitmap_offset, bitmap_end;
- if (qiov) {
- qemu_iovec_init(&target_qiov, qiov->niov);
+ if (!QEMU_IS_ALIGNED(offset, job->granularity) &&
+ bdrv_dirty_bitmap_get(job->dirty_bitmap, offset))
+ {
+ /*
+ * Dirty unaligned padding: ignore it.
+ *
+ * Reasoning:
+ * 1. If we copy it, we can't reset corresponding bit in
+ * dirty_bitmap as there may be some "dirty" bytes still not
+ * copied.
+ * 2. It's already dirty, so skipping it we don't diverge mirror
+ * progress.
+ *
+ * Note, that because of this, guest write may have no contribution
+ * into mirror converge, but that's not bad, as we have background
+ * process of mirroring. If under some bad circumstances (high guest
+ * IO load) background process starve, we will not converge anyway,
+ * even if each write will contribute, as guest is not guaranteed to
+ * rewrite the whole disk.
+ */
+ qiov_offset = QEMU_ALIGN_UP(offset, job->granularity) - offset;
+ if (bytes <= qiov_offset) {
+ /* nothing to do after shrink */
+ return;
+ }
+ offset += qiov_offset;
+ bytes -= qiov_offset;
}
- while (true) {
- bool valid_area;
- int ret;
+ if (!QEMU_IS_ALIGNED(offset + bytes, job->granularity) &&
+ bdrv_dirty_bitmap_get(job->dirty_bitmap, offset + bytes - 1))
+ {
+ uint64_t tail = (offset + bytes) % job->granularity;
- bdrv_dirty_bitmap_lock(job->dirty_bitmap);
- dirty_bytes = MIN(offset + bytes - dirty_offset, INT_MAX);
- valid_area = bdrv_dirty_bitmap_next_dirty_area(job->dirty_bitmap,
- &dirty_offset,
- &dirty_bytes);
- if (!valid_area) {
- bdrv_dirty_bitmap_unlock(job->dirty_bitmap);
- break;
+ if (bytes <= tail) {
+ /* nothing to do after shrink */
+ return;
}
+ bytes -= tail;
+ }
- bdrv_reset_dirty_bitmap_locked(job->dirty_bitmap,
- dirty_offset, dirty_bytes);
- bdrv_dirty_bitmap_unlock(job->dirty_bitmap);
-
- job_progress_increase_remaining(&job->common.job, dirty_bytes);
-
- assert(dirty_offset - offset <= SIZE_MAX);
- if (qiov) {
- qemu_iovec_reset(&target_qiov);
- qemu_iovec_concat(&target_qiov, qiov,
- dirty_offset - offset, dirty_bytes);
- }
+ /*
+ * Tails are either clean or shrunk, so for bitmap resetting
+ * we safely align the range down.
+ */
+ bitmap_offset = QEMU_ALIGN_UP(offset, job->granularity);
+ bitmap_end = QEMU_ALIGN_DOWN(offset + bytes, job->granularity);
+ if (bitmap_offset < bitmap_end) {
+ bdrv_reset_dirty_bitmap(job->dirty_bitmap, bitmap_offset,
+ bitmap_end - bitmap_offset);
+ }
- switch (method) {
- case MIRROR_METHOD_COPY:
- ret = blk_co_pwritev(job->target, dirty_offset, dirty_bytes,
- qiov ? &target_qiov : NULL, flags);
- break;
+ job_progress_increase_remaining(&job->common.job, bytes);
- case MIRROR_METHOD_ZERO:
- assert(!qiov);
- ret = blk_co_pwrite_zeroes(job->target, dirty_offset, dirty_bytes,
- flags);
- break;
+ switch (method) {
+ case MIRROR_METHOD_COPY:
+ ret = blk_co_pwritev_part(job->target, offset, bytes,
+ qiov, qiov_offset, flags);
+ break;
- case MIRROR_METHOD_DISCARD:
- assert(!qiov);
- ret = blk_co_pdiscard(job->target, dirty_offset, dirty_bytes);
- break;
+ case MIRROR_METHOD_ZERO:
+ assert(!qiov);
+ ret = blk_co_pwrite_zeroes(job->target, offset, bytes, flags);
+ break;
- default:
- abort();
- }
+ case MIRROR_METHOD_DISCARD:
+ assert(!qiov);
+ ret = blk_co_pdiscard(job->target, offset, bytes);
+ break;
- if (ret >= 0) {
- job_progress_update(&job->common.job, dirty_bytes);
- } else {
- BlockErrorAction action;
+ default:
+ abort();
+ }
- bdrv_set_dirty_bitmap(job->dirty_bitmap, dirty_offset, dirty_bytes);
- job->actively_synced = false;
+ if (ret >= 0) {
+ job_progress_update(&job->common.job, bytes);
+ } else {
+ BlockErrorAction action;
- action = mirror_error_action(job, false, -ret);
- if (action == BLOCK_ERROR_ACTION_REPORT) {
- if (!job->ret) {
- job->ret = ret;
- }
- break;
+ /*
+ * We failed, so we should mark dirty the whole area, aligned up.
+ * Note that we don't care about shrunk tails if any: they were dirty
+ * at function start, and they must be still dirty, as we've locked
+ * the region for in-flight op.
+ */
+ bitmap_offset = QEMU_ALIGN_DOWN(offset, job->granularity);
+ bitmap_end = QEMU_ALIGN_UP(offset + bytes, job->granularity);
+ bdrv_set_dirty_bitmap(job->dirty_bitmap, bitmap_offset,
+ bitmap_end - bitmap_offset);
+ job->actively_synced = false;
+
+ action = mirror_error_action(job, false, -ret);
+ if (action == BLOCK_ERROR_ACTION_REPORT) {
+ if (!job->ret) {
+ job->ret = ret;
}
}
-
- dirty_offset += dirty_bytes;
- }
-
- if (qiov) {
- qemu_iovec_destroy(&target_qiov);
}
}
@@ -1465,15 +1493,6 @@ static void bdrv_mirror_top_child_perm(BlockDriverState *bs, BdrvChild *c,
*nshared = BLK_PERM_ALL;
}
-static void bdrv_mirror_top_refresh_limits(BlockDriverState *bs, Error **errp)
-{
- MirrorBDSOpaque *s = bs->opaque;
-
- if (s && s->job && s->job->copy_mode == MIRROR_COPY_MODE_WRITE_BLOCKING) {
- bs->bl.request_alignment = s->job->granularity;
- }
-}
-
/* Dummy node that provides consistent read to its users without requiring it
* from its backing file and that allows writes on the backing file chain. */
static BlockDriver bdrv_mirror_top = {
@@ -1486,7 +1505,6 @@ static BlockDriver bdrv_mirror_top = {
.bdrv_co_block_status = bdrv_co_block_status_from_backing,
.bdrv_refresh_filename = bdrv_mirror_top_refresh_filename,
.bdrv_child_perm = bdrv_mirror_top_child_perm,
- .bdrv_refresh_limits = bdrv_mirror_top_refresh_limits,
};
static BlockJob *mirror_start_job(
@@ -1634,29 +1652,13 @@ static BlockJob *mirror_start_job(
s->should_complete = true;
}
- /*
- * Must be called before we start tracking writes, but after
- *
- * ((MirrorBlockJob *)
- * ((MirrorBDSOpaque *)
- * mirror_top_bs->opaque
- * )->job
- * )->copy_mode
- *
- * has the correct value.
- * (We start tracking writes as of the following
- * bdrv_create_dirty_bitmap() call.)
- */
- bdrv_refresh_limits(mirror_top_bs, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- goto fail;
- }
-
s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
if (!s->dirty_bitmap) {
goto fail;
}
+ if (s->copy_mode == MIRROR_COPY_MODE_WRITE_BLOCKING) {
+ bdrv_disable_dirty_bitmap(s->dirty_bitmap);
+ }
ret = block_job_add_bdrv(&s->common, "source", bs, 0,
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE |
diff --git a/block/nfs.c b/block/nfs.c
index 40f23495a0..9a6311e270 100644
--- a/block/nfs.c
+++ b/block/nfs.c
@@ -752,7 +752,7 @@ static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
}
static int coroutine_fn
-nfs_file_co_truncate(BlockDriverState *bs, int64_t offset,
+nfs_file_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp)
{
NFSClient *client = bs->opaque;
diff --git a/block/nvme.c b/block/nvme.c
index 910872ec59..d41c4bda6e 100644
--- a/block/nvme.c
+++ b/block/nvme.c
@@ -112,6 +112,9 @@ typedef struct {
uint64_t max_transfer;
bool plugged;
+ bool supports_write_zeroes;
+ bool supports_discard;
+
CoMutex dma_map_lock;
CoQueue dma_flush_queue;
@@ -423,6 +426,7 @@ static void nvme_identify(BlockDriverState *bs, int namespace, Error **errp)
NvmeIdNs *idns;
NvmeLBAF *lbaf;
uint8_t *resp;
+ uint16_t oncs;
int r;
uint64_t iova;
NvmeCmd cmd = {
@@ -460,6 +464,10 @@ static void nvme_identify(BlockDriverState *bs, int namespace, Error **errp)
s->max_transfer = MIN_NON_ZERO(s->max_transfer,
s->page_size / sizeof(uint64_t) * s->page_size);
+ oncs = le16_to_cpu(idctrl->oncs);
+ s->supports_write_zeroes = !!(oncs & NVME_ONCS_WRITE_ZEROS);
+ s->supports_discard = !!(oncs & NVME_ONCS_DSM);
+
memset(resp, 0, 4096);
cmd.cdw10 = 0;
@@ -472,6 +480,12 @@ static void nvme_identify(BlockDriverState *bs, int namespace, Error **errp)
s->nsze = le64_to_cpu(idns->nsze);
lbaf = &idns->lbaf[NVME_ID_NS_FLBAS_INDEX(idns->flbas)];
+ if (NVME_ID_NS_DLFEAT_WRITE_ZEROES(idns->dlfeat) &&
+ NVME_ID_NS_DLFEAT_READ_BEHAVIOR(idns->dlfeat) ==
+ NVME_ID_NS_DLFEAT_READ_BEHAVIOR_ZEROES) {
+ bs->supported_write_flags |= BDRV_REQ_MAY_UNMAP;
+ }
+
if (lbaf->ms) {
error_setg(errp, "Namespaces with metadata are not yet supported");
goto out;
@@ -766,6 +780,8 @@ static int nvme_file_open(BlockDriverState *bs, QDict *options, int flags,
int ret;
BDRVNVMeState *s = bs->opaque;
+ bs->supported_write_flags = BDRV_REQ_FUA;
+
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
qemu_opts_absorb_qdict(opts, options, &error_abort);
device = qemu_opt_get(opts, NVME_BLOCK_OPT_DEVICE);
@@ -794,7 +810,6 @@ static int nvme_file_open(BlockDriverState *bs, QDict *options, int flags,
goto fail;
}
}
- bs->supported_write_flags = BDRV_REQ_FUA;
return 0;
fail:
nvme_close(bs);
@@ -1088,6 +1103,140 @@ static coroutine_fn int nvme_co_flush(BlockDriverState *bs)
}
+static coroutine_fn int nvme_co_pwrite_zeroes(BlockDriverState *bs,
+ int64_t offset,
+ int bytes,
+ BdrvRequestFlags flags)
+{
+ BDRVNVMeState *s = bs->opaque;
+ NVMeQueuePair *ioq = s->queues[1];
+ NVMeRequest *req;
+
+ uint32_t cdw12 = ((bytes >> s->blkshift) - 1) & 0xFFFF;
+
+ if (!s->supports_write_zeroes) {
+ return -ENOTSUP;
+ }
+
+ NvmeCmd cmd = {
+ .opcode = NVME_CMD_WRITE_ZEROS,
+ .nsid = cpu_to_le32(s->nsid),
+ .cdw10 = cpu_to_le32((offset >> s->blkshift) & 0xFFFFFFFF),
+ .cdw11 = cpu_to_le32(((offset >> s->blkshift) >> 32) & 0xFFFFFFFF),
+ };
+
+ NVMeCoData data = {
+ .ctx = bdrv_get_aio_context(bs),
+ .ret = -EINPROGRESS,
+ };
+
+ if (flags & BDRV_REQ_MAY_UNMAP) {
+ cdw12 |= (1 << 25);
+ }
+
+ if (flags & BDRV_REQ_FUA) {
+ cdw12 |= (1 << 30);
+ }
+
+ cmd.cdw12 = cpu_to_le32(cdw12);
+
+ trace_nvme_write_zeroes(s, offset, bytes, flags);
+ assert(s->nr_queues > 1);
+ req = nvme_get_free_req(ioq);
+ assert(req);
+
+ nvme_submit_command(s, ioq, req, &cmd, nvme_rw_cb, &data);
+
+ data.co = qemu_coroutine_self();
+ while (data.ret == -EINPROGRESS) {
+ qemu_coroutine_yield();
+ }
+
+ trace_nvme_rw_done(s, true, offset, bytes, data.ret);
+ return data.ret;
+}
+
+
+static int coroutine_fn nvme_co_pdiscard(BlockDriverState *bs,
+ int64_t offset,
+ int bytes)
+{
+ BDRVNVMeState *s = bs->opaque;
+ NVMeQueuePair *ioq = s->queues[1];
+ NVMeRequest *req;
+ NvmeDsmRange *buf;
+ QEMUIOVector local_qiov;
+ int ret;
+
+ NvmeCmd cmd = {
+ .opcode = NVME_CMD_DSM,
+ .nsid = cpu_to_le32(s->nsid),
+ .cdw10 = cpu_to_le32(0), /*number of ranges - 0 based*/
+ .cdw11 = cpu_to_le32(1 << 2), /*deallocate bit*/
+ };
+
+ NVMeCoData data = {
+ .ctx = bdrv_get_aio_context(bs),
+ .ret = -EINPROGRESS,
+ };
+
+ if (!s->supports_discard) {
+ return -ENOTSUP;
+ }
+
+ assert(s->nr_queues > 1);
+
+ buf = qemu_try_blockalign0(bs, s->page_size);
+ if (!buf) {
+ return -ENOMEM;
+ }
+
+ buf->nlb = cpu_to_le32(bytes >> s->blkshift);
+ buf->slba = cpu_to_le64(offset >> s->blkshift);
+ buf->cattr = 0;
+
+ qemu_iovec_init(&local_qiov, 1);
+ qemu_iovec_add(&local_qiov, buf, 4096);
+
+ req = nvme_get_free_req(ioq);
+ assert(req);
+
+ qemu_co_mutex_lock(&s->dma_map_lock);
+ ret = nvme_cmd_map_qiov(bs, &cmd, req, &local_qiov);
+ qemu_co_mutex_unlock(&s->dma_map_lock);
+
+ if (ret) {
+ req->busy = false;
+ goto out;
+ }
+
+ trace_nvme_dsm(s, offset, bytes);
+
+ nvme_submit_command(s, ioq, req, &cmd, nvme_rw_cb, &data);
+
+ data.co = qemu_coroutine_self();
+ while (data.ret == -EINPROGRESS) {
+ qemu_coroutine_yield();
+ }
+
+ qemu_co_mutex_lock(&s->dma_map_lock);
+ ret = nvme_cmd_unmap_qiov(bs, &local_qiov);
+ qemu_co_mutex_unlock(&s->dma_map_lock);
+
+ if (ret) {
+ goto out;
+ }
+
+ ret = data.ret;
+ trace_nvme_dsm_done(s, offset, bytes, ret);
+out:
+ qemu_iovec_destroy(&local_qiov);
+ qemu_vfree(buf);
+ return ret;
+
+}
+
+
static int nvme_reopen_prepare(BDRVReopenState *reopen_state,
BlockReopenQueue *queue, Error **errp)
{
@@ -1192,6 +1341,10 @@ static BlockDriver bdrv_nvme = {
.bdrv_co_preadv = nvme_co_preadv,
.bdrv_co_pwritev = nvme_co_pwritev,
+
+ .bdrv_co_pwrite_zeroes = nvme_co_pwrite_zeroes,
+ .bdrv_co_pdiscard = nvme_co_pdiscard,
+
.bdrv_co_flush_to_disk = nvme_co_flush,
.bdrv_reopen_prepare = nvme_reopen_prepare,
diff --git a/block/parallels.c b/block/parallels.c
index f1dfb03eef..7a01997659 100644
--- a/block/parallels.c
+++ b/block/parallels.c
@@ -203,7 +203,7 @@ static int64_t allocate_clusters(BlockDriverState *bs, int64_t sector_num,
} else {
ret = bdrv_truncate(bs->file,
(s->data_end + space) << BDRV_SECTOR_BITS,
- PREALLOC_MODE_OFF, NULL);
+ false, PREALLOC_MODE_OFF, NULL);
}
if (ret < 0) {
return ret;
@@ -487,7 +487,12 @@ static int coroutine_fn parallels_co_check(BlockDriverState *bs,
res->leaks += count;
if (fix & BDRV_FIX_LEAKS) {
Error *local_err = NULL;
- ret = bdrv_truncate(bs->file, res->image_end_offset,
+
+ /*
+ * In order to really repair the image, we must shrink it.
+ * That means we have to pass exact=true.
+ */
+ ret = bdrv_truncate(bs->file, res->image_end_offset, true,
PREALLOC_MODE_OFF, &local_err);
if (ret < 0) {
error_report_err(local_err);
@@ -563,11 +568,6 @@ static int coroutine_fn parallels_co_create(BlockdevCreateOptions* opts,
blk_set_allow_write_beyond_eof(blk, true);
/* Create image format */
- ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp);
- if (ret < 0) {
- goto out;
- }
-
bat_entries = DIV_ROUND_UP(total_size, cl_size);
bat_sectors = DIV_ROUND_UP(bat_entry_off(bat_entries), cl_size);
bat_sectors = (bat_sectors * cl_size) >> BDRV_SECTOR_BITS;
@@ -885,7 +885,9 @@ static void parallels_close(BlockDriverState *bs)
if ((bs->open_flags & BDRV_O_RDWR) && !(bs->open_flags & BDRV_O_INACTIVE)) {
s->header->inuse = 0;
parallels_update_header(bs);
- bdrv_truncate(bs->file, s->data_end << BDRV_SECTOR_BITS,
+
+ /* errors are ignored, so we might as well pass exact=true */
+ bdrv_truncate(bs->file, s->data_end << BDRV_SECTOR_BITS, true,
PREALLOC_MODE_OFF, NULL);
}
diff --git a/block/qcow.c b/block/qcow.c
index 5bdf72ba33..fce8989868 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -480,7 +480,7 @@ static int get_cluster_offset(BlockDriverState *bs,
return -E2BIG;
}
ret = bdrv_truncate(bs->file, cluster_offset + s->cluster_size,
- PREALLOC_MODE_OFF, NULL);
+ false, PREALLOC_MODE_OFF, NULL);
if (ret < 0) {
return ret;
}
@@ -858,11 +858,6 @@ static int coroutine_fn qcow_co_create(BlockdevCreateOptions *opts,
blk_set_allow_write_beyond_eof(qcow_blk, true);
/* Create image format */
- ret = blk_truncate(qcow_blk, 0, PREALLOC_MODE_OFF, errp);
- if (ret < 0) {
- goto exit;
- }
-
memset(&header, 0, sizeof(header));
header.magic = cpu_to_be32(QCOW_MAGIC);
header.version = cpu_to_be32(QCOW_VERSION);
@@ -1038,7 +1033,7 @@ static int qcow_make_empty(BlockDriverState *bs)
if (bdrv_pwrite_sync(bs->file, s->l1_table_offset, s->l1_table,
l1_length) < 0)
return -1;
- ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length,
+ ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length, false,
PREALLOC_MODE_OFF, NULL);
if (ret < 0)
return ret;
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index 0d64bf5a5e..f67ac6b2d8 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -2016,7 +2016,7 @@ static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res,
goto resize_fail;
}
- ret = bdrv_truncate(bs->file, offset + s->cluster_size,
+ ret = bdrv_truncate(bs->file, offset + s->cluster_size, false,
PREALLOC_MODE_OFF, &local_err);
if (ret < 0) {
error_report_err(local_err);
diff --git a/block/qcow2-snapshot.c b/block/qcow2-snapshot.c
index d0e7fa9311..5ab64da1ec 100644
--- a/block/qcow2-snapshot.c
+++ b/block/qcow2-snapshot.c
@@ -29,29 +29,64 @@
#include "qemu/error-report.h"
#include "qemu/cutils.h"
+static void qcow2_free_single_snapshot(BlockDriverState *bs, int i)
+{
+ BDRVQcow2State *s = bs->opaque;
+
+ assert(i >= 0 && i < s->nb_snapshots);
+ g_free(s->snapshots[i].name);
+ g_free(s->snapshots[i].id_str);
+ g_free(s->snapshots[i].unknown_extra_data);
+ memset(&s->snapshots[i], 0, sizeof(s->snapshots[i]));
+}
+
void qcow2_free_snapshots(BlockDriverState *bs)
{
BDRVQcow2State *s = bs->opaque;
int i;
for(i = 0; i < s->nb_snapshots; i++) {
- g_free(s->snapshots[i].name);
- g_free(s->snapshots[i].id_str);
+ qcow2_free_single_snapshot(bs, i);
}
g_free(s->snapshots);
s->snapshots = NULL;
s->nb_snapshots = 0;
}
-int qcow2_read_snapshots(BlockDriverState *bs)
+/*
+ * If @repair is true, try to repair a broken snapshot table instead
+ * of just returning an error:
+ *
+ * - If the snapshot table was too long, set *nb_clusters_reduced to
+ * the number of snapshots removed off the end.
+ * The caller will update the on-disk nb_snapshots accordingly;
+ * this leaks clusters, but is safe.
+ * (The on-disk information must be updated before
+ * qcow2_check_refcounts(), because that function relies on
+ * s->nb_snapshots to reflect the on-disk value.)
+ *
+ * - If there were snapshots with too much extra metadata, increment
+ * *extra_data_dropped for each.
+ * This requires the caller to eventually rewrite the whole snapshot
+ * table, which requires cluster allocation. Therefore, this should
+ * be done only after qcow2_check_refcounts() made sure the refcount
+ * structures are valid.
+ * (In the meantime, the image is still valid because
+ * qcow2_check_refcounts() does not do anything with snapshots'
+ * extra data.)
+ */
+static int qcow2_do_read_snapshots(BlockDriverState *bs, bool repair,
+ int *nb_clusters_reduced,
+ int *extra_data_dropped,
+ Error **errp)
{
BDRVQcow2State *s = bs->opaque;
QCowSnapshotHeader h;
QCowSnapshotExtraData extra;
QCowSnapshot *sn;
int i, id_str_size, name_size;
- int64_t offset;
- uint32_t extra_data_size;
+ int64_t offset, pre_sn_offset;
+ uint64_t table_length = 0;
int ret;
if (!s->nb_snapshots) {
@@ -64,10 +99,16 @@ int qcow2_read_snapshots(BlockDriverState *bs)
s->snapshots = g_new0(QCowSnapshot, s->nb_snapshots);
for(i = 0; i < s->nb_snapshots; i++) {
+ bool truncate_unknown_extra_data = false;
+
+ pre_sn_offset = offset;
+ table_length = ROUND_UP(table_length, 8);
+
/* Read statically sized part of the snapshot header */
offset = ROUND_UP(offset, 8);
ret = bdrv_pread(bs->file, offset, &h, sizeof(h));
if (ret < 0) {
+ error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail;
}
@@ -79,33 +120,77 @@ int qcow2_read_snapshots(BlockDriverState *bs)
sn->date_sec = be32_to_cpu(h.date_sec);
sn->date_nsec = be32_to_cpu(h.date_nsec);
sn->vm_clock_nsec = be64_to_cpu(h.vm_clock_nsec);
- extra_data_size = be32_to_cpu(h.extra_data_size);
+ sn->extra_data_size = be32_to_cpu(h.extra_data_size);
id_str_size = be16_to_cpu(h.id_str_size);
name_size = be16_to_cpu(h.name_size);
- /* Read extra data */
+ if (sn->extra_data_size > QCOW_MAX_SNAPSHOT_EXTRA_DATA) {
+ if (!repair) {
+ ret = -EFBIG;
+ error_setg(errp, "Too much extra metadata in snapshot table "
+ "entry %i", i);
+ error_append_hint(errp, "You can force-remove this extra "
+ "metadata with qemu-img check -r all\n");
+ goto fail;
+ }
+
+ fprintf(stderr, "Discarding too much extra metadata in snapshot "
+ "table entry %i (%" PRIu32 " > %u)\n",
+ i, sn->extra_data_size, QCOW_MAX_SNAPSHOT_EXTRA_DATA);
+
+ (*extra_data_dropped)++;
+ truncate_unknown_extra_data = true;
+ }
+
+ /* Read known extra data */
ret = bdrv_pread(bs->file, offset, &extra,
- MIN(sizeof(extra), extra_data_size));
+ MIN(sizeof(extra), sn->extra_data_size));
if (ret < 0) {
+ error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail;
}
- offset += extra_data_size;
+ offset += MIN(sizeof(extra), sn->extra_data_size);
- if (extra_data_size >= 8) {
+ if (sn->extra_data_size >= endof(QCowSnapshotExtraData,
+ vm_state_size_large)) {
sn->vm_state_size = be64_to_cpu(extra.vm_state_size_large);
}
- if (extra_data_size >= 16) {
+ if (sn->extra_data_size >= endof(QCowSnapshotExtraData, disk_size)) {
sn->disk_size = be64_to_cpu(extra.disk_size);
} else {
sn->disk_size = bs->total_sectors * BDRV_SECTOR_SIZE;
}
+ if (sn->extra_data_size > sizeof(extra)) {
+ uint64_t extra_data_end;
+ size_t unknown_extra_data_size;
+
+ extra_data_end = offset + sn->extra_data_size - sizeof(extra);
+
+ if (truncate_unknown_extra_data) {
+ sn->extra_data_size = QCOW_MAX_SNAPSHOT_EXTRA_DATA;
+ }
+
+ /* Store unknown extra data */
+ unknown_extra_data_size = sn->extra_data_size - sizeof(extra);
+ sn->unknown_extra_data = g_malloc(unknown_extra_data_size);
+ ret = bdrv_pread(bs->file, offset, sn->unknown_extra_data,
+ unknown_extra_data_size);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "Failed to read snapshot table");
+ goto fail;
+ }
+ offset = extra_data_end;
+ }
+
/* Read snapshot ID */
sn->id_str = g_malloc(id_str_size + 1);
ret = bdrv_pread(bs->file, offset, sn->id_str, id_str_size);
if (ret < 0) {
+ error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail;
}
offset += id_str_size;
@@ -115,14 +200,47 @@ int qcow2_read_snapshots(BlockDriverState *bs)
sn->name = g_malloc(name_size + 1);
ret = bdrv_pread(bs->file, offset, sn->name, name_size);
if (ret < 0) {
+ error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail;
}
offset += name_size;
sn->name[name_size] = '\0';
- if (offset - s->snapshots_offset > QCOW_MAX_SNAPSHOTS_SIZE) {
- ret = -EFBIG;
- goto fail;
+ /* Note that the extra data may have been truncated */
+ table_length += sizeof(h) + sn->extra_data_size + id_str_size +
+ name_size;
+ if (!repair) {
+ assert(table_length == offset - s->snapshots_offset);
+ }
+
+ if (table_length > QCOW_MAX_SNAPSHOTS_SIZE ||
+ offset - s->snapshots_offset > INT_MAX)
+ {
+ if (!repair) {
+ ret = -EFBIG;
+ error_setg(errp, "Snapshot table is too big");
+ error_append_hint(errp, "You can force-remove all %u "
+ "overhanging snapshots with qemu-img check "
+ "-r all\n", s->nb_snapshots - i);
+ goto fail;
+ }
+
+ fprintf(stderr, "Discarding %u overhanging snapshots (snapshot "
+ "table is too big)\n", s->nb_snapshots - i);
+
+ *nb_clusters_reduced += (s->nb_snapshots - i);
+
+ /* Discard current snapshot also */
+ qcow2_free_single_snapshot(bs, i);
+
+ /*
+ * This leaks all the rest of the snapshot table and the
+ * snapshots' clusters, but we run in check -r all mode,
+ * so qcow2_check_refcounts() will take care of it.
+ */
+ s->nb_snapshots = i;
+ offset = pre_sn_offset;
+ break;
}
}
@@ -135,8 +253,13 @@ fail:
return ret;
}
+int qcow2_read_snapshots(BlockDriverState *bs, Error **errp)
+{
+ return qcow2_do_read_snapshots(bs, false, NULL, NULL, errp);
+}
+
/* add at the end of the file a new list of snapshots */
-static int qcow2_write_snapshots(BlockDriverState *bs)
+int qcow2_write_snapshots(BlockDriverState *bs)
{
BDRVQcow2State *s = bs->opaque;
QCowSnapshot *sn;
@@ -156,7 +279,7 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
sn = s->snapshots + i;
offset = ROUND_UP(offset, 8);
offset += sizeof(h);
- offset += sizeof(extra);
+ offset += MAX(sizeof(extra), sn->extra_data_size);
offset += strlen(sn->id_str);
offset += strlen(sn->name);
@@ -203,7 +326,8 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
h.date_sec = cpu_to_be32(sn->date_sec);
h.date_nsec = cpu_to_be32(sn->date_nsec);
h.vm_clock_nsec = cpu_to_be64(sn->vm_clock_nsec);
- h.extra_data_size = cpu_to_be32(sizeof(extra));
+ h.extra_data_size = cpu_to_be32(MAX(sizeof(extra),
+ sn->extra_data_size));
memset(&extra, 0, sizeof(extra));
extra.vm_state_size_large = cpu_to_be64(sn->vm_state_size);
@@ -228,6 +352,22 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
}
offset += sizeof(extra);
+ if (sn->extra_data_size > sizeof(extra)) {
+ size_t unknown_extra_data_size =
+ sn->extra_data_size - sizeof(extra);
+
+ /* qcow2_read_snapshots() ensures no unbounded allocation */
+ assert(unknown_extra_data_size <= BDRV_REQUEST_MAX_BYTES);
+ assert(sn->unknown_extra_data);
+
+ ret = bdrv_pwrite(bs->file, offset, sn->unknown_extra_data,
+ unknown_extra_data_size);
+ if (ret < 0) {
+ goto fail;
+ }
+ offset += unknown_extra_data_size;
+ }
+
ret = bdrv_pwrite(bs->file, offset, sn->id_str, id_str_size);
if (ret < 0) {
goto fail;
@@ -251,7 +391,7 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
}
QEMU_BUILD_BUG_ON(offsetof(QCowHeader, snapshots_offset) !=
- offsetof(QCowHeader, nb_snapshots) + sizeof(header_data.nb_snapshots));
+ endof(QCowHeader, nb_snapshots));
header_data.nb_snapshots = cpu_to_be32(s->nb_snapshots);
header_data.snapshots_offset = cpu_to_be64(snapshots_offset);
@@ -277,6 +417,151 @@ fail:
return ret;
}
+int coroutine_fn qcow2_check_read_snapshot_table(BlockDriverState *bs,
+ BdrvCheckResult *result,
+ BdrvCheckMode fix)
+{
+ BDRVQcow2State *s = bs->opaque;
+ Error *local_err = NULL;
+ int nb_clusters_reduced = 0;
+ int extra_data_dropped = 0;
+ int ret;
+ struct {
+ uint32_t nb_snapshots;
+ uint64_t snapshots_offset;
+ } QEMU_PACKED snapshot_table_pointer;
+
+ /* qcow2_do_open() discards this information in check mode */
+ ret = bdrv_pread(bs->file, offsetof(QCowHeader, nb_snapshots),
+ &snapshot_table_pointer, sizeof(snapshot_table_pointer));
+ if (ret < 0) {
+ result->check_errors++;
+ fprintf(stderr, "ERROR failed to read the snapshot table pointer from "
+ "the image header: %s\n", strerror(-ret));
+ return ret;
+ }
+
+ s->snapshots_offset = be64_to_cpu(snapshot_table_pointer.snapshots_offset);
+ s->nb_snapshots = be32_to_cpu(snapshot_table_pointer.nb_snapshots);
+
+ if (s->nb_snapshots > QCOW_MAX_SNAPSHOTS && (fix & BDRV_FIX_ERRORS)) {
+ fprintf(stderr, "Discarding %u overhanging snapshots\n",
+ s->nb_snapshots - QCOW_MAX_SNAPSHOTS);
+
+ nb_clusters_reduced += s->nb_snapshots - QCOW_MAX_SNAPSHOTS;
+ s->nb_snapshots = QCOW_MAX_SNAPSHOTS;
+ }
+
+ ret = qcow2_validate_table(bs, s->snapshots_offset, s->nb_snapshots,
+ sizeof(QCowSnapshotHeader),
+ sizeof(QCowSnapshotHeader) * QCOW_MAX_SNAPSHOTS,
+ "snapshot table", &local_err);
+ if (ret < 0) {
+ result->check_errors++;
+ error_reportf_err(local_err, "ERROR ");
+
+ if (s->nb_snapshots > QCOW_MAX_SNAPSHOTS) {
+ fprintf(stderr, "You can force-remove all %u overhanging snapshots "
+ "with qemu-img check -r all\n",
+ s->nb_snapshots - QCOW_MAX_SNAPSHOTS);
+ }
+
+ /* We did not read the snapshot table, so invalidate this information */
+ s->snapshots_offset = 0;
+ s->nb_snapshots = 0;
+
+ return ret;
+ }
+
+ qemu_co_mutex_unlock(&s->lock);
+ ret = qcow2_do_read_snapshots(bs, fix & BDRV_FIX_ERRORS,
+ &nb_clusters_reduced, &extra_data_dropped,
+ &local_err);
+ qemu_co_mutex_lock(&s->lock);
+ if (ret < 0) {
+ result->check_errors++;
+ error_reportf_err(local_err,
+ "ERROR failed to read the snapshot table: ");
+
+ /* We did not read the snapshot table, so invalidate this information */
+ s->snapshots_offset = 0;
+ s->nb_snapshots = 0;
+
+ return ret;
+ }
+ result->corruptions += nb_clusters_reduced + extra_data_dropped;
+
+ if (nb_clusters_reduced) {
+ /*
+ * Update image header now, because:
+ * (1) qcow2_check_refcounts() relies on s->nb_snapshots to be
+ * the same as what the image header says,
+ * (2) this leaks clusters, but qcow2_check_refcounts() will
+ * fix that.
+ */
+ assert(fix & BDRV_FIX_ERRORS);
+
+ snapshot_table_pointer.nb_snapshots = cpu_to_be32(s->nb_snapshots);
+ ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, nb_snapshots),
+ &snapshot_table_pointer.nb_snapshots,
+ sizeof(snapshot_table_pointer.nb_snapshots));
+ if (ret < 0) {
+ result->check_errors++;
+ fprintf(stderr, "ERROR failed to update the snapshot count in the "
+ "image header: %s\n", strerror(-ret));
+ return ret;
+ }
+
+ result->corruptions_fixed += nb_clusters_reduced;
+ result->corruptions -= nb_clusters_reduced;
+ }
+
+ /*
+ * All of v3 images' snapshot table entries need to have at least
+ * 16 bytes of extra data.
+ */
+ if (s->qcow_version >= 3) {
+ int i;
+ for (i = 0; i < s->nb_snapshots; i++) {
+ if (s->snapshots[i].extra_data_size <
+ sizeof_field(QCowSnapshotExtraData, vm_state_size_large) +
+ sizeof_field(QCowSnapshotExtraData, disk_size))
+ {
+ result->corruptions++;
+ fprintf(stderr, "%s snapshot table entry %i is incomplete\n",
+ fix & BDRV_FIX_ERRORS ? "Repairing" : "ERROR", i);
+ }
+ }
+ }
+
+ return 0;
+}
+
+int coroutine_fn qcow2_check_fix_snapshot_table(BlockDriverState *bs,
+ BdrvCheckResult *result,
+ BdrvCheckMode fix)
+{
+ BDRVQcow2State *s = bs->opaque;
+ int ret;
+
+ if (result->corruptions && (fix & BDRV_FIX_ERRORS)) {
+ qemu_co_mutex_unlock(&s->lock);
+ ret = qcow2_write_snapshots(bs);
+ qemu_co_mutex_lock(&s->lock);
+ if (ret < 0) {
+ result->check_errors++;
+ fprintf(stderr, "ERROR failed to update snapshot table: %s\n",
+ strerror(-ret));
+ return ret;
+ }
+
+ result->corruptions_fixed += result->corruptions;
+ result->corruptions = 0;
+ }
+
+ return 0;
+}
+
static void find_new_snapshot_id(BlockDriverState *bs,
char *id_str, int id_str_size)
{
@@ -370,6 +655,7 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
sn->date_sec = sn_info->date_sec;
sn->date_nsec = sn_info->date_nsec;
sn->vm_clock_nsec = sn_info->vm_clock_nsec;
+ sn->extra_data_size = sizeof(QCowSnapshotExtraData);
/* Allocate the L1 table of the snapshot and copy the current one there. */
l1_table_offset = qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t));
@@ -641,6 +927,7 @@ int qcow2_snapshot_delete(BlockDriverState *bs,
* The snapshot is now unused, clean up. If we fail after this point, we
* won't recover but just leak clusters.
*/
+ g_free(sn.unknown_extra_data);
g_free(sn.id_str);
g_free(sn.name);
diff --git a/block/qcow2.c b/block/qcow2.c
index 0bc69e6996..7c18721741 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -570,11 +570,47 @@ int qcow2_mark_consistent(BlockDriverState *bs)
return 0;
}
+static void qcow2_add_check_result(BdrvCheckResult *out,
+ const BdrvCheckResult *src,
+ bool set_allocation_info)
+{
+ out->corruptions += src->corruptions;
+ out->leaks += src->leaks;
+ out->check_errors += src->check_errors;
+ out->corruptions_fixed += src->corruptions_fixed;
+ out->leaks_fixed += src->leaks_fixed;
+
+ if (set_allocation_info) {
+ out->image_end_offset = src->image_end_offset;
+ out->bfi = src->bfi;
+ }
+}
+
static int coroutine_fn qcow2_co_check_locked(BlockDriverState *bs,
BdrvCheckResult *result,
BdrvCheckMode fix)
{
- int ret = qcow2_check_refcounts(bs, result, fix);
+ BdrvCheckResult snapshot_res = {};
+ BdrvCheckResult refcount_res = {};
+ int ret;
+
+ memset(result, 0, sizeof(*result));
+
+ ret = qcow2_check_read_snapshot_table(bs, &snapshot_res, fix);
+ if (ret < 0) {
+ qcow2_add_check_result(result, &snapshot_res, false);
+ return ret;
+ }
+
+ ret = qcow2_check_refcounts(bs, &refcount_res, fix);
+ qcow2_add_check_result(result, &refcount_res, true);
+ if (ret < 0) {
+ qcow2_add_check_result(result, &snapshot_res, false);
+ return ret;
+ }
+
+ ret = qcow2_check_fix_snapshot_table(bs, &snapshot_res, fix);
+ qcow2_add_check_result(result, &snapshot_res, false);
if (ret < 0) {
return ret;
}
@@ -1410,17 +1446,22 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
goto fail;
}
- /* The total size in bytes of the snapshot table is checked in
- * qcow2_read_snapshots() because the size of each snapshot is
- * variable and we don't know it yet.
- * Here we only check the offset and number of snapshots. */
- ret = qcow2_validate_table(bs, header.snapshots_offset,
- header.nb_snapshots,
- sizeof(QCowSnapshotHeader),
- sizeof(QCowSnapshotHeader) * QCOW_MAX_SNAPSHOTS,
- "Snapshot table", errp);
- if (ret < 0) {
- goto fail;
+ if (!(flags & BDRV_O_CHECK)) {
+ /*
+ * The total size in bytes of the snapshot table is checked in
+ * qcow2_read_snapshots() because the size of each snapshot is
+ * variable and we don't know it yet.
+ * Here we only check the offset and number of snapshots.
+ */
+ ret = qcow2_validate_table(bs, header.snapshots_offset,
+ header.nb_snapshots,
+ sizeof(QCowSnapshotHeader),
+ sizeof(QCowSnapshotHeader) *
+ QCOW_MAX_SNAPSHOTS,
+ "Snapshot table", errp);
+ if (ret < 0) {
+ goto fail;
+ }
}
/* read the level 1 table */
@@ -1580,14 +1621,19 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
s->image_backing_file = g_strdup(bs->auto_backing_file);
}
- /* Internal snapshots */
- s->snapshots_offset = header.snapshots_offset;
- s->nb_snapshots = header.nb_snapshots;
+ /*
+ * Internal snapshots; skip reading them in check mode, because
+ * we do not need them then, and we do not want to abort because
+ * of a broken table.
+ */
+ if (!(flags & BDRV_O_CHECK)) {
+ s->snapshots_offset = header.snapshots_offset;
+ s->nb_snapshots = header.nb_snapshots;
- ret = qcow2_read_snapshots(bs);
- if (ret < 0) {
- error_setg_errno(errp, -ret, "Could not read snapshots");
- goto fail;
+ ret = qcow2_read_snapshots(bs, errp);
+ if (ret < 0) {
+ goto fail;
+ }
}
/* Clear unknown autoclear feature bits */
@@ -3028,8 +3074,8 @@ static int coroutine_fn preallocate_co(BlockDriverState *bs, uint64_t offset,
if (mode == PREALLOC_MODE_METADATA) {
mode = PREALLOC_MODE_OFF;
}
- ret = bdrv_co_truncate(s->data_file, host_offset + cur_bytes, mode,
- errp);
+ ret = bdrv_co_truncate(s->data_file, host_offset + cur_bytes, false,
+ mode, errp);
if (ret < 0) {
return ret;
}
@@ -3345,12 +3391,6 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
}
blk_set_allow_write_beyond_eof(blk, true);
- /* Clear the protocol layer and preallocate it if necessary */
- ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp);
- if (ret < 0) {
- goto out;
- }
-
/* Write the header */
QEMU_BUILD_BUG_ON((1 << MIN_CLUSTER_BITS) < sizeof(*header));
header = g_malloc0(cluster_size);
@@ -3449,7 +3489,8 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
}
/* Okay, now that we have a valid image, let's give it the right size */
- ret = blk_truncate(blk, qcow2_opts->size, qcow2_opts->preallocation, errp);
+ ret = blk_truncate(blk, qcow2_opts->size, false, qcow2_opts->preallocation,
+ errp);
if (ret < 0) {
error_prepend(errp, "Could not resize image: ");
goto out;
@@ -3897,7 +3938,8 @@ fail:
}
static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+ bool exact, PreallocMode prealloc,
+ Error **errp)
{
BDRVQcow2State *s = bs->opaque;
uint64_t old_length;
@@ -3985,8 +4027,15 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
if ((last_cluster + 1) * s->cluster_size < old_file_size) {
Error *local_err = NULL;
+ /*
+ * Do not pass @exact here: It will not help the user if
+ * we get an error here just because they wanted to shrink
+ * their qcow2 image (on a block device) with qemu-img.
+ * (And on the qcow2 layer, the @exact requirement is
+ * always fulfilled, so there is no need to pass it on.)
+ */
bdrv_co_truncate(bs->file, (last_cluster + 1) * s->cluster_size,
- PREALLOC_MODE_OFF, &local_err);
+ false, PREALLOC_MODE_OFF, &local_err);
if (local_err) {
warn_reportf_err(local_err,
"Failed to truncate the tail of the image: ");
@@ -4003,7 +4052,12 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
switch (prealloc) {
case PREALLOC_MODE_OFF:
if (has_data_file(bs)) {
- ret = bdrv_co_truncate(s->data_file, offset, prealloc, errp);
+ /*
+ * If the caller wants an exact resize, the external data
+ * file should be resized to the exact target size, too,
+ * so we pass @exact here.
+ */
+ ret = bdrv_co_truncate(s->data_file, offset, exact, prealloc, errp);
if (ret < 0) {
goto fail;
}
@@ -4088,7 +4142,8 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
/* Allocate the data area */
new_file_size = allocation_start +
nb_new_data_clusters * s->cluster_size;
- ret = bdrv_co_truncate(bs->file, new_file_size, prealloc, errp);
+ /* Image file grows, so @exact does not matter */
+ ret = bdrv_co_truncate(bs->file, new_file_size, false, prealloc, errp);
if (ret < 0) {
error_prepend(errp, "Failed to resize underlying file: ");
qcow2_free_clusters(bs, allocation_start,
@@ -4191,7 +4246,7 @@ qcow2_co_pwritev_compressed_part(BlockDriverState *bs,
if (len < 0) {
return len;
}
- return bdrv_co_truncate(bs->file, len, PREALLOC_MODE_OFF, NULL);
+ return bdrv_co_truncate(bs->file, len, false, PREALLOC_MODE_OFF, NULL);
}
if (offset_into_cluster(s, offset)) {
@@ -4428,7 +4483,7 @@ static int make_completely_empty(BlockDriverState *bs)
goto fail;
}
- ret = bdrv_truncate(bs->file, (3 + l1_clusters) * s->cluster_size,
+ ret = bdrv_truncate(bs->file, (3 + l1_clusters) * s->cluster_size, false,
PREALLOC_MODE_OFF, &local_err);
if (ret < 0) {
error_report_err(local_err);
@@ -4913,12 +4968,74 @@ static int qcow2_downgrade(BlockDriverState *bs, int target_version,
return 0;
}
+/*
+ * Upgrades an image's version. While newer versions encompass all
+ * features of older versions, some things may have to be presented
+ * differently.
+ */
+static int qcow2_upgrade(BlockDriverState *bs, int target_version,
+ BlockDriverAmendStatusCB *status_cb, void *cb_opaque,
+ Error **errp)
+{
+ BDRVQcow2State *s = bs->opaque;
+ bool need_snapshot_update;
+ int current_version = s->qcow_version;
+ int i;
+ int ret;
+
+ /* This is qcow2_upgrade(), not qcow2_downgrade() */
+ assert(target_version > current_version);
+
+ /* There are no other versions (yet) that you can upgrade to */
+ assert(target_version == 3);
+
+ status_cb(bs, 0, 2, cb_opaque);
+
+ /*
+ * In v2, snapshots do not need to have extra data. v3 requires
+ * the 64-bit VM state size and the virtual disk size to be
+ * present.
+ * qcow2_write_snapshots() will always write the list in the
+ * v3-compliant format.
+ */
+ need_snapshot_update = false;
+ for (i = 0; i < s->nb_snapshots; i++) {
+ if (s->snapshots[i].extra_data_size <
+ sizeof_field(QCowSnapshotExtraData, vm_state_size_large) +
+ sizeof_field(QCowSnapshotExtraData, disk_size))
+ {
+ need_snapshot_update = true;
+ break;
+ }
+ }
+ if (need_snapshot_update) {
+ ret = qcow2_write_snapshots(bs);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Failed to update the snapshot table");
+ return ret;
+ }
+ }
+ status_cb(bs, 1, 2, cb_opaque);
+
+ s->qcow_version = target_version;
+ ret = qcow2_update_header(bs);
+ if (ret < 0) {
+ s->qcow_version = current_version;
+ error_setg_errno(errp, -ret, "Failed to update the image header");
+ return ret;
+ }
+ status_cb(bs, 2, 2, cb_opaque);
+
+ return 0;
+}
+
typedef enum Qcow2AmendOperation {
/* This is the value Qcow2AmendHelperCBInfo::last_operation will be
* statically initialized to so that the helper CB can discern the first
* invocation from an operation change */
QCOW2_NO_OPERATION = 0,
+ QCOW2_UPGRADING,
QCOW2_CHANGING_REFCOUNT_ORDER,
QCOW2_DOWNGRADING,
} Qcow2AmendOperation;
@@ -5101,17 +5218,16 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
helper_cb_info = (Qcow2AmendHelperCBInfo){
.original_status_cb = status_cb,
.original_cb_opaque = cb_opaque,
- .total_operations = (new_version < old_version)
+ .total_operations = (new_version != old_version)
+ (s->refcount_bits != refcount_bits)
};
/* Upgrade first (some features may require compat=1.1) */
if (new_version > old_version) {
- s->qcow_version = new_version;
- ret = qcow2_update_header(bs);
+ helper_cb_info.current_operation = QCOW2_UPGRADING;
+ ret = qcow2_upgrade(bs, new_version, &qcow2_amend_helper_cb,
+ &helper_cb_info, errp);
if (ret < 0) {
- s->qcow_version = old_version;
- error_setg_errno(errp, -ret, "Failed to update the image header");
return ret;
}
}
@@ -5207,7 +5323,11 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
return ret;
}
- ret = blk_truncate(blk, new_size, PREALLOC_MODE_OFF, errp);
+ /*
+ * Amending image options should ensure that the image has
+ * exactly the given new values, so pass exact=true here.
+ */
+ ret = blk_truncate(blk, new_size, true, PREALLOC_MODE_OFF, errp);
blk_unref(blk);
if (ret < 0) {
return ret;
diff --git a/block/qcow2.h b/block/qcow2.h
index 5cccd87162..601c2e4c82 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -61,6 +61,9 @@
* space for snapshot names and IDs */
#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS)
+/* Maximum amount of extra data per snapshot table entry to accept */
+#define QCOW_MAX_SNAPSHOT_EXTRA_DATA 1024
+
/* Bitmap header extension constraints */
#define QCOW2_MAX_BITMAPS 65535
#define QCOW2_MAX_BITMAP_DIRECTORY_SIZE (1024 * QCOW2_MAX_BITMAPS)
@@ -181,6 +184,10 @@ typedef struct QCowSnapshot {
uint32_t date_sec;
uint32_t date_nsec;
uint64_t vm_clock_nsec;
+ /* Size of all extra data, including QCowSnapshotExtraData if available */
+ uint32_t extra_data_size;
+ /* Data beyond QCowSnapshotExtraData, if any */
+ void *unknown_extra_data;
} QCowSnapshot;
struct Qcow2Cache;
@@ -708,7 +715,15 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs,
Error **errp);
void qcow2_free_snapshots(BlockDriverState *bs);
-int qcow2_read_snapshots(BlockDriverState *bs);
+int qcow2_read_snapshots(BlockDriverState *bs, Error **errp);
+int qcow2_write_snapshots(BlockDriverState *bs);
+
+int coroutine_fn qcow2_check_read_snapshot_table(BlockDriverState *bs,
+ BdrvCheckResult *result,
+ BdrvCheckMode fix);
+int coroutine_fn qcow2_check_fix_snapshot_table(BlockDriverState *bs,
+ BdrvCheckResult *result,
+ BdrvCheckMode fix);
/* qcow2-cache.c functions */
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables,
diff --git a/block/qed.c b/block/qed.c
index 0d8fd507aa..d8c4e5fb1e 100644
--- a/block/qed.c
+++ b/block/qed.c
@@ -673,8 +673,11 @@ static int coroutine_fn bdrv_qed_co_create(BlockdevCreateOptions *opts,
l1_size = header.cluster_size * header.table_size;
- /* File must start empty and grow, check truncate is supported */
- ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp);
+ /*
+ * The QED format associates file length with allocation status,
+ * so a new file (which is empty) must have a length of 0.
+ */
+ ret = blk_truncate(blk, 0, true, PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto out;
}
@@ -1461,6 +1464,7 @@ static int coroutine_fn bdrv_qed_co_pwrite_zeroes(BlockDriverState *bs,
static int coroutine_fn bdrv_qed_co_truncate(BlockDriverState *bs,
int64_t offset,
+ bool exact,
PreallocMode prealloc,
Error **errp)
{
diff --git a/block/raw-format.c b/block/raw-format.c
index 42c28cc29a..3a76ec7dd2 100644
--- a/block/raw-format.c
+++ b/block/raw-format.c
@@ -370,7 +370,8 @@ static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
}
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+ bool exact, PreallocMode prealloc,
+ Error **errp)
{
BDRVRawState *s = bs->opaque;
@@ -386,7 +387,7 @@ static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
s->size = offset;
offset += s->offset;
- return bdrv_co_truncate(bs->file, offset, prealloc, errp);
+ return bdrv_co_truncate(bs->file, offset, exact, prealloc, errp);
}
static void raw_eject(BlockDriverState *bs, bool eject_flag)
diff --git a/block/rbd.c b/block/rbd.c
index c71e45d7c3..027cbcc695 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -1087,6 +1087,7 @@ static int64_t qemu_rbd_getlength(BlockDriverState *bs)
static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs,
int64_t offset,
+ bool exact,
PreallocMode prealloc,
Error **errp)
{
diff --git a/block/sheepdog.c b/block/sheepdog.c
index 773dfc6ab1..cfa84338a2 100644
--- a/block/sheepdog.c
+++ b/block/sheepdog.c
@@ -2285,7 +2285,8 @@ static int64_t sd_getlength(BlockDriverState *bs)
}
static int coroutine_fn sd_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+ bool exact, PreallocMode prealloc,
+ Error **errp)
{
BDRVSheepdogState *s = bs->opaque;
int ret, fd;
@@ -2601,7 +2602,7 @@ static coroutine_fn int sd_co_writev(BlockDriverState *bs, int64_t sector_num,
assert(!flags);
if (offset > s->inode.vdi_size) {
- ret = sd_co_truncate(bs, offset, PREALLOC_MODE_OFF, NULL);
+ ret = sd_co_truncate(bs, offset, false, PREALLOC_MODE_OFF, NULL);
if (ret < 0) {
return ret;
}
diff --git a/block/ssh.c b/block/ssh.c
index 84d01e892b..b4375cf7d2 100644
--- a/block/ssh.c
+++ b/block/ssh.c
@@ -1295,7 +1295,8 @@ static int64_t ssh_getlength(BlockDriverState *bs)
}
static int coroutine_fn ssh_co_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+ bool exact, PreallocMode prealloc,
+ Error **errp)
{
BDRVSSHState *s = bs->opaque;
diff --git a/block/trace-events b/block/trace-events
index b8d70f5242..6ba86decca 100644
--- a/block/trace-events
+++ b/block/trace-events
@@ -45,9 +45,9 @@ backup_do_cow_return(void *job, int64_t offset, uint64_t bytes, int ret) "job %p
block_copy_skip(void *bcs, int64_t start) "bcs %p start %"PRId64
block_copy_skip_range(void *bcs, int64_t start, uint64_t bytes) "bcs %p start %"PRId64" bytes %"PRId64
block_copy_process(void *bcs, int64_t start) "bcs %p start %"PRId64
-block_copy_with_bounce_buffer_read_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
-block_copy_with_bounce_buffer_write_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
-block_copy_with_offload_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
+block_copy_copy_range_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
+block_copy_read_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
+block_copy_write_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
# ../blockdev.c
qmp_block_job_cancel(void *job) "job %p"
@@ -152,9 +152,12 @@ nvme_submit_command_raw(int c0, int c1, int c2, int c3, int c4, int c5, int c6,
nvme_handle_event(void *s) "s %p"
nvme_poll_cb(void *s) "s %p"
nvme_prw_aligned(void *s, int is_write, uint64_t offset, uint64_t bytes, int flags, int niov) "s %p is_write %d offset %"PRId64" bytes %"PRId64" flags %d niov %d"
+nvme_write_zeroes(void *s, uint64_t offset, uint64_t bytes, int flags) "s %p offset %"PRId64" bytes %"PRId64" flags %d"
nvme_qiov_unaligned(const void *qiov, int n, void *base, size_t size, int align) "qiov %p n %d base %p size 0x%zx align 0x%x"
nvme_prw_buffered(void *s, uint64_t offset, uint64_t bytes, int niov, int is_write) "s %p offset %"PRId64" bytes %"PRId64" niov %d is_write %d"
nvme_rw_done(void *s, int is_write, uint64_t offset, uint64_t bytes, int ret) "s %p is_write %d offset %"PRId64" bytes %"PRId64" ret %d"
+nvme_dsm(void *s, uint64_t offset, uint64_t bytes) "s %p offset %"PRId64" bytes %"PRId64""
+nvme_dsm_done(void *s, uint64_t offset, uint64_t bytes, int ret) "s %p offset %"PRId64" bytes %"PRId64" ret %d"
nvme_dma_map_flush(void *s) "s %p"
nvme_free_req_queue_wait(void *q) "q %p"
nvme_cmd_map_qiov(void *s, void *cmd, void *req, void *qiov, int entries) "s %p cmd %p req %p qiov %p entries %d"
diff --git a/block/vdi.c b/block/vdi.c
index 806ba7f53c..0142da7233 100644
--- a/block/vdi.c
+++ b/block/vdi.c
@@ -874,7 +874,7 @@ static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options,
}
if (image_type == VDI_TYPE_STATIC) {
- ret = blk_truncate(blk, offset + blocks * block_size,
+ ret = blk_truncate(blk, offset + blocks * block_size, false,
PREALLOC_MODE_OFF, errp);
if (ret < 0) {
error_prepend(errp, "Failed to statically allocate file");
diff --git a/block/vhdx-log.c b/block/vhdx-log.c
index fdd3a7adc3..13a49c2a33 100644
--- a/block/vhdx-log.c
+++ b/block/vhdx-log.c
@@ -557,8 +557,8 @@ static int vhdx_log_flush(BlockDriverState *bs, BDRVVHDXState *s,
ret = -EINVAL;
goto exit;
}
- ret = bdrv_truncate(bs->file, new_file_size, PREALLOC_MODE_OFF,
- NULL);
+ ret = bdrv_truncate(bs->file, new_file_size, false,
+ PREALLOC_MODE_OFF, NULL);
if (ret < 0) {
goto exit;
}
diff --git a/block/vhdx.c b/block/vhdx.c
index 371f226286..f02d2611be 100644
--- a/block/vhdx.c
+++ b/block/vhdx.c
@@ -1263,7 +1263,7 @@ static int vhdx_allocate_block(BlockDriverState *bs, BDRVVHDXState *s,
return -EINVAL;
}
- return bdrv_truncate(bs->file, *new_offset + s->block_size,
+ return bdrv_truncate(bs->file, *new_offset + s->block_size, false,
PREALLOC_MODE_OFF, NULL);
}
@@ -1702,12 +1702,13 @@ static int vhdx_create_bat(BlockBackend *blk, BDRVVHDXState *s,
if (type == VHDX_TYPE_DYNAMIC) {
/* All zeroes, so we can just extend the file - the end of the BAT
* is the furthest thing we have written yet */
- ret = blk_truncate(blk, data_file_offset, PREALLOC_MODE_OFF, errp);
+ ret = blk_truncate(blk, data_file_offset, false, PREALLOC_MODE_OFF,
+ errp);
if (ret < 0) {
goto exit;
}
} else if (type == VHDX_TYPE_FIXED) {
- ret = blk_truncate(blk, data_file_offset + image_size,
+ ret = blk_truncate(blk, data_file_offset + image_size, false,
PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto exit;
diff --git a/block/vmdk.c b/block/vmdk.c
index fed3b50c8a..20e909d997 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -2076,7 +2076,7 @@ vmdk_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
return length;
}
length = QEMU_ALIGN_UP(length, BDRV_SECTOR_SIZE);
- ret = bdrv_truncate(s->extents[i].file, length,
+ ret = bdrv_truncate(s->extents[i].file, length, false,
PREALLOC_MODE_OFF, NULL);
if (ret < 0) {
return ret;
@@ -2118,7 +2118,7 @@ static int vmdk_init_extent(BlockBackend *blk,
int gd_buf_size;
if (flat) {
- ret = blk_truncate(blk, filesize, PREALLOC_MODE_OFF, errp);
+ ret = blk_truncate(blk, filesize, false, PREALLOC_MODE_OFF, errp);
goto exit;
}
magic = cpu_to_be32(VMDK4_MAGIC);
@@ -2181,7 +2181,7 @@ static int vmdk_init_extent(BlockBackend *blk,
goto exit;
}
- ret = blk_truncate(blk, le64_to_cpu(header.grain_offset) << 9,
+ ret = blk_truncate(blk, le64_to_cpu(header.grain_offset) << 9, false,
PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto exit;
@@ -2523,7 +2523,7 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
/* bdrv_pwrite write padding zeros to align to sector, we don't need that
* for description file */
if (desc_offset == 0) {
- ret = blk_truncate(blk, desc_len, PREALLOC_MODE_OFF, errp);
+ ret = blk_truncate(blk, desc_len, false, PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto exit;
}
diff --git a/block/vpc.c b/block/vpc.c
index 5cd3890780..a65550298e 100644
--- a/block/vpc.c
+++ b/block/vpc.c
@@ -898,7 +898,7 @@ static int create_fixed_disk(BlockBackend *blk, uint8_t *buf,
/* Add footer to total size */
total_size += HEADER_SIZE;
- ret = blk_truncate(blk, total_size, PREALLOC_MODE_OFF, errp);
+ ret = blk_truncate(blk, total_size, false, PREALLOC_MODE_OFF, errp);
if (ret < 0) {
return ret;
}
diff --git a/blockdev.c b/blockdev.c
index ba491e3ef5..8e029e9c01 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3204,7 +3204,7 @@ void qmp_block_resize(bool has_device, const char *device,
}
bdrv_drained_begin(bs);
- ret = blk_truncate(blk, size, PREALLOC_MODE_OFF, errp);
+ ret = blk_truncate(blk, size, false, PREALLOC_MODE_OFF, errp);
bdrv_drained_end(bs);
out:
diff --git a/configure b/configure
index f77c4dc09a..72553f98ea 100755
--- a/configure
+++ b/configure
@@ -473,8 +473,11 @@ gtk_gl="no"
tls_priority="NORMAL"
gnutls=""
nettle=""
+nettle_xts="no"
gcrypt=""
gcrypt_hmac="no"
+gcrypt_xts="no"
+qemu_private_xts="yes"
auth_pam=""
vte=""
virglrenderer=""
@@ -2877,6 +2880,19 @@ if test "$nettle" != "no"; then
pass="yes"
fi
fi
+ if test "$pass" = "yes"
+ then
+ cat > $TMPC << EOF
+#include <nettle/xts.h>
+int main(void) {
+ return 0;
+}
+EOF
+ if compile_prog "$nettle_cflags" "$nettle_libs" ; then
+ nettle_xts=yes
+ qemu_private_xts=no
+ fi
+ fi
if test "$pass" = "no" && test "$nettle" = "yes"; then
feature_not_found "nettle" "Install nettle devel >= 2.7.1"
else
@@ -2919,6 +2935,18 @@ EOF
if compile_prog "$gcrypt_cflags" "$gcrypt_libs" ; then
gcrypt_hmac=yes
fi
+ cat > $TMPC << EOF
+#include <gcrypt.h>
+int main(void) {
+ gcry_cipher_hd_t handle;
+ gcry_cipher_open(&handle, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_XTS, 0);
+ return 0;
+}
+EOF
+ if compile_prog "$gcrypt_cflags" "$gcrypt_libs" ; then
+ gcrypt_xts=yes
+ qemu_private_xts=no
+ fi
elif test "$gcrypt" = "yes"; then
feature_not_found "gcrypt" "Install gcrypt devel >= 1.5.0"
else
@@ -5585,6 +5613,21 @@ if compile_prog "" "" ; then
fi
########################################
+# See if __attribute__((alias)) is supported.
+# This false for Xcode 9, but has been remedied for Xcode 10.
+# Unfortunately, travis uses Xcode 9 by default.
+
+attralias=no
+cat > $TMPC << EOF
+int x = 1;
+extern const int y __attribute__((alias("x")));
+int main(void) { return 0; }
+EOF
+if compile_prog "" "" ; then
+ attralias=yes
+fi
+
+########################################
# check if getauxval is available.
getauxval=no
@@ -6392,7 +6435,16 @@ echo "VTE support $vte $(echo_version $vte $vteversion)"
echo "TLS priority $tls_priority"
echo "GNUTLS support $gnutls"
echo "libgcrypt $gcrypt"
+if test "$gcrypt" = "yes"
+then
+ echo " hmac $gcrypt_hmac"
+ echo " XTS $gcrypt_xts"
+fi
echo "nettle $nettle $(echo_version $nettle $nettle_version)"
+if test "$nettle" = "yes"
+then
+ echo " XTS $nettle_xts"
+fi
echo "libtasn1 $tasn1"
echo "PAM $auth_pam"
echo "iconv support $iconv"
@@ -6871,6 +6923,9 @@ if test "$nettle" = "yes" ; then
echo "CONFIG_NETTLE=y" >> $config_host_mak
echo "CONFIG_NETTLE_VERSION_MAJOR=${nettle_version%%.*}" >> $config_host_mak
fi
+if test "$qemu_private_xts" = "yes" ; then
+ echo "CONFIG_QEMU_PRIVATE_XTS=y" >> $config_host_mak
+fi
if test "$tasn1" = "yes" ; then
echo "CONFIG_TASN1=y" >> $config_host_mak
fi
@@ -7150,6 +7205,10 @@ if test "$vector16" = "yes" ; then
echo "CONFIG_VECTOR16=y" >> $config_host_mak
fi
+if test "$attralias" = "yes" ; then
+ echo "CONFIG_ATTRIBUTE_ALIAS=y" >> $config_host_mak
+fi
+
if test "$getauxval" = "yes" ; then
echo "CONFIG_GETAUXVAL=y" >> $config_host_mak
fi
@@ -7614,13 +7673,13 @@ case "$target_name" in
TARGET_BASE_ARCH=riscv
TARGET_ABI_DIR=riscv
mttcg=yes
- gdb_xml_files="riscv-32bit-cpu.xml riscv-32bit-fpu.xml riscv-32bit-csr.xml"
+ gdb_xml_files="riscv-32bit-cpu.xml riscv-32bit-fpu.xml riscv-32bit-csr.xml riscv-32bit-virtual.xml"
;;
riscv64)
TARGET_BASE_ARCH=riscv
TARGET_ABI_DIR=riscv
mttcg=yes
- gdb_xml_files="riscv-64bit-cpu.xml riscv-64bit-fpu.xml riscv-64bit-csr.xml"
+ gdb_xml_files="riscv-64bit-cpu.xml riscv-64bit-fpu.xml riscv-64bit-csr.xml riscv-64bit-virtual.xml"
;;
sh4|sh4eb)
TARGET_ARCH=sh4
diff --git a/contrib/gitdm/domain-map b/contrib/gitdm/domain-map
index 9efe066ec9..dd79147c76 100644
--- a/contrib/gitdm/domain-map
+++ b/contrib/gitdm/domain-map
@@ -5,6 +5,7 @@
#
amd.com AMD
+cmss.chinamobile.com China Mobile
citrix.com Citrix
greensocs.com GreenSocs
fujitsu.com Fujitsu
diff --git a/contrib/gitdm/group-map-individuals b/contrib/gitdm/group-map-individuals
index 1c84717438..cf8a2ce367 100644
--- a/contrib/gitdm/group-map-individuals
+++ b/contrib/gitdm/group-map-individuals
@@ -14,3 +14,5 @@ noring@nocrew.org
samuel.thibault@ens-lyon.org
aurelien@aurel32.net
balaton@eik.bme.hu
+e.emanuelegiuseppe@gmail.com
+andrew.smirnov@gmail.com
diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 7fe2fa9da2..cdb01f9de9 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -31,7 +31,7 @@ crypto-obj-y += ivgen-essiv.o
crypto-obj-y += ivgen-plain.o
crypto-obj-y += ivgen-plain64.o
crypto-obj-y += afsplit.o
-crypto-obj-y += xts.o
+crypto-obj-$(CONFIG_QEMU_PRIVATE_XTS) += xts.o
crypto-obj-y += block.o
crypto-obj-y += block-qcow.o
crypto-obj-y += block-luks.o
diff --git a/crypto/cipher-gcrypt.c b/crypto/cipher-gcrypt.c
index 5cece9b244..2864099527 100644
--- a/crypto/cipher-gcrypt.c
+++ b/crypto/cipher-gcrypt.c
@@ -19,7 +19,9 @@
*/
#include "qemu/osdep.h"
+#ifdef CONFIG_QEMU_PRIVATE_XTS
#include "crypto/xts.h"
+#endif
#include "cipherpriv.h"
#include <gcrypt.h>
@@ -59,10 +61,12 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg,
typedef struct QCryptoCipherGcrypt QCryptoCipherGcrypt;
struct QCryptoCipherGcrypt {
gcry_cipher_hd_t handle;
- gcry_cipher_hd_t tweakhandle;
size_t blocksize;
+#ifdef CONFIG_QEMU_PRIVATE_XTS
+ gcry_cipher_hd_t tweakhandle;
/* Initialization vector or Counter */
uint8_t *iv;
+#endif
};
static void
@@ -74,10 +78,12 @@ qcrypto_gcrypt_cipher_free_ctx(QCryptoCipherGcrypt *ctx,
}
gcry_cipher_close(ctx->handle);
+#ifdef CONFIG_QEMU_PRIVATE_XTS
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
gcry_cipher_close(ctx->tweakhandle);
}
g_free(ctx->iv);
+#endif
g_free(ctx);
}
@@ -94,8 +100,14 @@ static QCryptoCipherGcrypt *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
switch (mode) {
case QCRYPTO_CIPHER_MODE_ECB:
+ gcrymode = GCRY_CIPHER_MODE_ECB;
+ break;
case QCRYPTO_CIPHER_MODE_XTS:
+#ifdef CONFIG_QEMU_PRIVATE_XTS
gcrymode = GCRY_CIPHER_MODE_ECB;
+#else
+ gcrymode = GCRY_CIPHER_MODE_XTS;
+#endif
break;
case QCRYPTO_CIPHER_MODE_CBC:
gcrymode = GCRY_CIPHER_MODE_CBC;
@@ -172,6 +184,7 @@ static QCryptoCipherGcrypt *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
gcry_strerror(err));
goto error;
}
+#ifdef CONFIG_QEMU_PRIVATE_XTS
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
err = gcry_cipher_open(&ctx->tweakhandle, gcryalg, gcrymode, 0);
if (err != 0) {
@@ -180,6 +193,7 @@ static QCryptoCipherGcrypt *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
goto error;
}
}
+#endif
if (alg == QCRYPTO_CIPHER_ALG_DES_RFB) {
/* We're using standard DES cipher from gcrypt, so we need
@@ -191,6 +205,7 @@ static QCryptoCipherGcrypt *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
g_free(rfbkey);
ctx->blocksize = 8;
} else {
+#ifdef CONFIG_QEMU_PRIVATE_XTS
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
nkey /= 2;
err = gcry_cipher_setkey(ctx->handle, key, nkey);
@@ -201,8 +216,11 @@ static QCryptoCipherGcrypt *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
}
err = gcry_cipher_setkey(ctx->tweakhandle, key + nkey, nkey);
} else {
+#endif
err = gcry_cipher_setkey(ctx->handle, key, nkey);
+#ifdef CONFIG_QEMU_PRIVATE_XTS
}
+#endif
if (err != 0) {
error_setg(errp, "Cannot set key: %s",
gcry_strerror(err));
@@ -228,6 +246,7 @@ static QCryptoCipherGcrypt *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
}
}
+#ifdef CONFIG_QEMU_PRIVATE_XTS
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
if (ctx->blocksize != XTS_BLOCK_SIZE) {
error_setg(errp,
@@ -237,6 +256,7 @@ static QCryptoCipherGcrypt *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
}
ctx->iv = g_new0(uint8_t, ctx->blocksize);
}
+#endif
return ctx;
@@ -253,6 +273,7 @@ qcrypto_gcrypt_cipher_ctx_free(QCryptoCipher *cipher)
}
+#ifdef CONFIG_QEMU_PRIVATE_XTS
static void qcrypto_gcrypt_xts_encrypt(const void *ctx,
size_t length,
uint8_t *dst,
@@ -272,6 +293,7 @@ static void qcrypto_gcrypt_xts_decrypt(const void *ctx,
err = gcry_cipher_decrypt((gcry_cipher_hd_t)ctx, dst, length, src, length);
g_assert(err == 0);
}
+#endif
static int
qcrypto_gcrypt_cipher_encrypt(QCryptoCipher *cipher,
@@ -289,20 +311,23 @@ qcrypto_gcrypt_cipher_encrypt(QCryptoCipher *cipher,
return -1;
}
+#ifdef CONFIG_QEMU_PRIVATE_XTS
if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) {
xts_encrypt(ctx->handle, ctx->tweakhandle,
qcrypto_gcrypt_xts_encrypt,
qcrypto_gcrypt_xts_decrypt,
ctx->iv, len, out, in);
- } else {
- err = gcry_cipher_encrypt(ctx->handle,
- out, len,
- in, len);
- if (err != 0) {
- error_setg(errp, "Cannot encrypt data: %s",
- gcry_strerror(err));
- return -1;
- }
+ return 0;
+ }
+#endif
+
+ err = gcry_cipher_encrypt(ctx->handle,
+ out, len,
+ in, len);
+ if (err != 0) {
+ error_setg(errp, "Cannot encrypt data: %s",
+ gcry_strerror(err));
+ return -1;
}
return 0;
@@ -325,20 +350,23 @@ qcrypto_gcrypt_cipher_decrypt(QCryptoCipher *cipher,
return -1;
}
+#ifdef CONFIG_QEMU_PRIVATE_XTS
if (cipher->mode == QCRYPTO_CIPHER_MODE_XTS) {
xts_decrypt(ctx->handle, ctx->tweakhandle,
qcrypto_gcrypt_xts_encrypt,
qcrypto_gcrypt_xts_decrypt,
ctx->iv, len, out, in);
- } else {
- err = gcry_cipher_decrypt(ctx->handle,
- out, len,
- in, len);
- if (err != 0) {
- error_setg(errp, "Cannot decrypt data: %s",
- gcry_strerror(err));
- return -1;
- }
+ return 0;
+ }
+#endif
+
+ err = gcry_cipher_decrypt(ctx->handle,
+ out, len,
+ in, len);
+ if (err != 0) {
+ error_setg(errp, "Cannot decrypt data: %s",
+ gcry_strerror(err));
+ return -1;
}
return 0;
@@ -358,24 +386,27 @@ qcrypto_gcrypt_cipher_setiv(QCryptoCipher *cipher,
return -1;
}
+#ifdef CONFIG_QEMU_PRIVATE_XTS
if (ctx->iv) {
memcpy(ctx->iv, iv, niv);
- } else {
- if (cipher->mode == QCRYPTO_CIPHER_MODE_CTR) {
- err = gcry_cipher_setctr(ctx->handle, iv, niv);
- if (err != 0) {
- error_setg(errp, "Cannot set Counter: %s",
+ return 0;
+ }
+#endif
+
+ if (cipher->mode == QCRYPTO_CIPHER_MODE_CTR) {
+ err = gcry_cipher_setctr(ctx->handle, iv, niv);
+ if (err != 0) {
+ error_setg(errp, "Cannot set Counter: %s",
gcry_strerror(err));
- return -1;
- }
- } else {
- gcry_cipher_reset(ctx->handle);
- err = gcry_cipher_setiv(ctx->handle, iv, niv);
- if (err != 0) {
- error_setg(errp, "Cannot set IV: %s",
+ return -1;
+ }
+ } else {
+ gcry_cipher_reset(ctx->handle);
+ err = gcry_cipher_setiv(ctx->handle, iv, niv);
+ if (err != 0) {
+ error_setg(errp, "Cannot set IV: %s",
gcry_strerror(err));
- return -1;
- }
+ return -1;
}
}
diff --git a/crypto/cipher-nettle.c b/crypto/cipher-nettle.c
index d7411bb8ff..7e9a4cc199 100644
--- a/crypto/cipher-nettle.c
+++ b/crypto/cipher-nettle.c
@@ -19,7 +19,9 @@
*/
#include "qemu/osdep.h"
+#ifdef CONFIG_QEMU_PRIVATE_XTS
#include "crypto/xts.h"
+#endif
#include "cipherpriv.h"
#include <nettle/nettle-types.h>
@@ -30,6 +32,9 @@
#include <nettle/serpent.h>
#include <nettle/twofish.h>
#include <nettle/ctr.h>
+#ifndef CONFIG_QEMU_PRIVATE_XTS
+#include <nettle/xts.h>
+#endif
typedef void (*QCryptoCipherNettleFuncWrapper)(const void *ctx,
size_t length,
@@ -626,9 +631,15 @@ qcrypto_nettle_cipher_encrypt(QCryptoCipher *cipher,
break;
case QCRYPTO_CIPHER_MODE_XTS:
+#ifdef CONFIG_QEMU_PRIVATE_XTS
xts_encrypt(ctx->ctx, ctx->ctx_tweak,
ctx->alg_encrypt_wrapper, ctx->alg_encrypt_wrapper,
ctx->iv, len, out, in);
+#else
+ xts_encrypt_message(ctx->ctx, ctx->ctx_tweak,
+ ctx->alg_encrypt_native,
+ ctx->iv, len, out, in);
+#endif
break;
case QCRYPTO_CIPHER_MODE_CTR:
@@ -673,9 +684,16 @@ qcrypto_nettle_cipher_decrypt(QCryptoCipher *cipher,
break;
case QCRYPTO_CIPHER_MODE_XTS:
+#ifdef CONFIG_QEMU_PRIVATE_XTS
xts_decrypt(ctx->ctx, ctx->ctx_tweak,
ctx->alg_encrypt_wrapper, ctx->alg_decrypt_wrapper,
ctx->iv, len, out, in);
+#else
+ xts_decrypt_message(ctx->ctx, ctx->ctx_tweak,
+ ctx->alg_decrypt_native,
+ ctx->alg_encrypt_native,
+ ctx->iv, len, out, in);
+#endif
break;
case QCRYPTO_CIPHER_MODE_CTR:
ctr_crypt(ctx->ctx, ctx->alg_encrypt_native,
diff --git a/default-configs/m68k-softmmu.mak b/default-configs/m68k-softmmu.mak
index d67ab8b96d..6629fd2aa3 100644
--- a/default-configs/m68k-softmmu.mak
+++ b/default-configs/m68k-softmmu.mak
@@ -7,3 +7,4 @@ CONFIG_SEMIHOSTING=y
CONFIG_AN5206=y
CONFIG_MCF5208=y
CONFIG_NEXTCUBE=y
+CONFIG_Q800=y
diff --git a/docs/virtio-net-failover.rst b/docs/virtio-net-failover.rst
new file mode 100644
index 0000000000..22f64c7bc8
--- /dev/null
+++ b/docs/virtio-net-failover.rst
@@ -0,0 +1,68 @@
+========================
+QEMU virtio-net standby (net_failover)
+========================
+
+This document explains the setup and usage of virtio-net standby feature which
+is used to create a net_failover pair of devices.
+
+The general idea is that we have a pair of devices, a (vfio-)pci and a
+virtio-net device. Before migration the vfio device is unplugged and data flows
+through the virtio-net device, on the target side another vfio-pci device is
+plugged in to take over the data-path. In the guest the net_failover kernel
+module will pair net devices with the same MAC address.
+
+The two devices are called primary and standby device. The fast hardware based
+networking device is called the primary device and the virtio-net device is the
+standby device.
+
+Restrictions
+------------
+
+Currently only PCIe devices are allowed as primary devices, this restriction
+can be lifted in the future with enhanced QEMU support. Also, only networking
+devices are allowed as primary device. The user needs to ensure that primary
+and standby devices are not plugged into the same PCIe slot.
+
+Usecase
+-------
+
+ Virtio-net standby allows easy migration while using a passed-through fast
+ networking device by falling back to a virtio-net device for the duration of
+ the migration. It is like a simple version of a bond, the difference is that it
+ requires no configuration in the guest. When a guest is live-migrated to
+ another host QEMU will unplug the primary device via the PCIe based hotplug
+ handler and traffic will go through the virtio-net device. On the target
+ system the primary device will be automatically plugged back and the
+ net_failover module registers it again as the primary device.
+
+Usage
+-----
+
+ The primary device can be hotplugged or be part of the startup configuration
+
+ -device virtio-net-pci,netdev=hostnet1,id=net1,mac=52:54:00:6f:55:cc, \
+ bus=root2,failover=on
+
+ With the parameter failover=on the VIRTIO_NET_F_STANDBY feature will be enabled.
+
+ -device vfio-pci,host=5e:00.2,id=hostdev0,bus=root1,failover_pair_id=net1
+
+ failover_pair_id references the id of the virtio-net standby device. This
+ is only for pairing the devices within QEMU. The guest kernel module
+ net_failover will match devices with identical MAC addresses.
+
+Hotplug
+-------
+
+ Both primary and standby device can be hotplugged via the QEMU monitor. Note
+ that if the virtio-net device is plugged first a warning will be issued that it
+ couldn't find the primary device.
+
+Migration
+---------
+
+ A new migration state wait-unplug was added for this feature. If failover primary
+ devices are present in the configuration, migration will go into this state.
+ It will wait until the device unplug is completed in the guest and then move into
+ active state. On the target system the primary devices will be automatically hotplugged
+ when the feature bit was negotiated for the virtio-net standby device.
diff --git a/exec-vary.c b/exec-vary.c
new file mode 100644
index 0000000000..ff905f2a8f
--- /dev/null
+++ b/exec-vary.c
@@ -0,0 +1,108 @@
+/*
+ * Variable page size handling
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+
+#define IN_EXEC_VARY 1
+
+#include "exec/exec-all.h"
+
+#ifdef TARGET_PAGE_BITS_VARY
+# ifdef CONFIG_ATTRIBUTE_ALIAS
+/*
+ * We want to declare the "target_page" variable as const, which tells
+ * the compiler that it can cache any value that it reads across calls.
+ * This avoids multiple assertions and multiple reads within any one user.
+ *
+ * This works because we finish initializing the data before we ever read
+ * from the "target_page" symbol.
+ *
+ * This also requires that we have a non-constant symbol by which we can
+ * perform the actual initialization, and which forces the data to be
+ * allocated within writable memory. Thus "init_target_page", and we use
+ * that symbol exclusively in the two functions that initialize this value.
+ *
+ * The "target_page" symbol is created as an alias of "init_target_page".
+ */
+static TargetPageBits init_target_page;
+
+/*
+ * Note that this is *not* a redundant decl, this is the definition of
+ * the "target_page" symbol. The syntax for this definition requires
+ * the use of the extern keyword. This seems to be a GCC bug in
+ * either the syntax for the alias attribute or in -Wredundant-decls.
+ *
+ * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91765
+ */
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wredundant-decls"
+
+extern const TargetPageBits target_page
+ __attribute__((alias("init_target_page")));
+
+# pragma GCC diagnostic pop
+# else
+/*
+ * When aliases are not supported then we force two different declarations,
+ * by way of suppressing the header declaration with IN_EXEC_VARY.
+ * We assume that on such an old compiler, LTO cannot be used, and so the
+ * compiler cannot not detect the mismatched declarations, and all is well.
+ */
+TargetPageBits target_page;
+# define init_target_page target_page
+# endif
+#endif
+
+bool set_preferred_target_page_bits(int bits)
+{
+ /*
+ * The target page size is the lowest common denominator for all
+ * the CPUs in the system, so we can only make it smaller, never
+ * larger. And we can't make it smaller once we've committed to
+ * a particular size.
+ */
+#ifdef TARGET_PAGE_BITS_VARY
+ assert(bits >= TARGET_PAGE_BITS_MIN);
+ if (init_target_page.bits == 0 || init_target_page.bits > bits) {
+ if (init_target_page.decided) {
+ return false;
+ }
+ init_target_page.bits = bits;
+ }
+#endif
+ return true;
+}
+
+void finalize_target_page_bits(void)
+{
+#ifdef TARGET_PAGE_BITS_VARY
+ if (init_target_page.bits == 0) {
+ init_target_page.bits = TARGET_PAGE_BITS_MIN;
+ }
+ init_target_page.mask = (target_long)-1 << init_target_page.bits;
+ init_target_page.decided = true;
+
+ /*
+ * For the benefit of an -flto build, prevent the compiler from
+ * hoisting a read from target_page before we finish initializing.
+ */
+ barrier();
+#endif
+}
diff --git a/exec.c b/exec.c
index 6e9787e7cd..ffdb518535 100644
--- a/exec.c
+++ b/exec.c
@@ -91,11 +91,6 @@ AddressSpace address_space_memory;
static MemoryRegion io_mem_unassigned;
#endif
-#ifdef TARGET_PAGE_BITS_VARY
-int target_page_bits;
-bool target_page_bits_decided;
-#endif
-
CPUTailQ cpus = QTAILQ_HEAD_INITIALIZER(cpus);
/* current CPU in the current thread. It is only valid inside
@@ -109,37 +104,8 @@ int use_icount;
uintptr_t qemu_host_page_size;
intptr_t qemu_host_page_mask;
-bool set_preferred_target_page_bits(int bits)
-{
- /* The target page size is the lowest common denominator for all
- * the CPUs in the system, so we can only make it smaller, never
- * larger. And we can't make it smaller once we've committed to
- * a particular size.
- */
-#ifdef TARGET_PAGE_BITS_VARY
- assert(bits >= TARGET_PAGE_BITS_MIN);
- if (target_page_bits == 0 || target_page_bits > bits) {
- if (target_page_bits_decided) {
- return false;
- }
- target_page_bits = bits;
- }
-#endif
- return true;
-}
-
#if !defined(CONFIG_USER_ONLY)
-static void finalize_target_page_bits(void)
-{
-#ifdef TARGET_PAGE_BITS_VARY
- if (target_page_bits == 0) {
- target_page_bits = TARGET_PAGE_BITS_MIN;
- }
- target_page_bits_decided = true;
-#endif
-}
-
typedef struct PhysPageEntry PhysPageEntry;
struct PhysPageEntry {
diff --git a/gdb-xml/riscv-32bit-virtual.xml b/gdb-xml/riscv-32bit-virtual.xml
new file mode 100644
index 0000000000..905f1c555d
--- /dev/null
+++ b/gdb-xml/riscv-32bit-virtual.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2018-2019 Free Software Foundation, Inc.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved. -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.riscv.virtual">
+ <reg name="priv" bitsize="32"/>
+</feature>
diff --git a/gdb-xml/riscv-64bit-virtual.xml b/gdb-xml/riscv-64bit-virtual.xml
new file mode 100644
index 0000000000..62d86c237b
--- /dev/null
+++ b/gdb-xml/riscv-64bit-virtual.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2018-2019 Free Software Foundation, Inc.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved. -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.riscv.virtual">
+ <reg name="priv" bitsize="64"/>
+</feature>
diff --git a/hw/Kconfig b/hw/Kconfig
index 4b53fee4d0..b9685b3944 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -21,6 +21,7 @@ source isa/Kconfig
source mem/Kconfig
source misc/Kconfig
source net/Kconfig
+source nubus/Kconfig
source nvram/Kconfig
source pci-bridge/Kconfig
source pci-host/Kconfig
diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index fd9750e5f2..66eef20561 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -38,6 +38,7 @@ devices-dirs-y += virtio/
devices-dirs-y += watchdog/
devices-dirs-y += xen/
devices-dirs-$(CONFIG_MEM_DEVICE) += mem/
+devices-dirs-$(CONFIG_NUBUS) += nubus/
devices-dirs-y += semihosting/
devices-dirs-y += smbios/
endif
diff --git a/hw/block/Kconfig b/hw/block/Kconfig
index df96dc5dcc..2d17f481ad 100644
--- a/hw/block/Kconfig
+++ b/hw/block/Kconfig
@@ -37,3 +37,6 @@ config VHOST_USER_BLK
# Only PCI devices are provided for now
default y if VIRTIO_PCI
depends on VIRTIO && VHOST_USER && LINUX
+
+config SWIM
+ bool
diff --git a/hw/block/Makefile.objs b/hw/block/Makefile.objs
index f5f643f0cc..28c2495a00 100644
--- a/hw/block/Makefile.objs
+++ b/hw/block/Makefile.objs
@@ -8,6 +8,7 @@ common-obj-$(CONFIG_XEN) += xen-block.o
common-obj-$(CONFIG_ECC) += ecc.o
common-obj-$(CONFIG_ONENAND) += onenand.o
common-obj-$(CONFIG_NVME_PCI) += nvme.o
+common-obj-$(CONFIG_SWIM) += swim.o
obj-$(CONFIG_SH4) += tc58128.o
diff --git a/hw/block/swim.c b/hw/block/swim.c
new file mode 100644
index 0000000000..c6d117e89b
--- /dev/null
+++ b/hw/block/swim.c
@@ -0,0 +1,489 @@
+/*
+ * QEMU Macintosh floppy disk controller emulator (SWIM)
+ *
+ * Copyright (c) 2014-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Only the basic support: it allows to switch from IWM (Integrated WOZ
+ * Machine) mode to the SWIM mode and makes the linux driver happy.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "qapi/error.h"
+#include "sysemu/block-backend.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/block/block.h"
+#include "hw/block/swim.h"
+#include "hw/qdev-properties.h"
+
+/* IWM registers */
+
+#define IWM_PH0L 0
+#define IWM_PH0H 1
+#define IWM_PH1L 2
+#define IWM_PH1H 3
+#define IWM_PH2L 4
+#define IWM_PH2H 5
+#define IWM_PH3L 6
+#define IWM_PH3H 7
+#define IWM_MTROFF 8
+#define IWM_MTRON 9
+#define IWM_INTDRIVE 10
+#define IWM_EXTDRIVE 11
+#define IWM_Q6L 12
+#define IWM_Q6H 13
+#define IWM_Q7L 14
+#define IWM_Q7H 15
+
+/* SWIM registers */
+
+#define SWIM_WRITE_DATA 0
+#define SWIM_WRITE_MARK 1
+#define SWIM_WRITE_CRC 2
+#define SWIM_WRITE_PARAMETER 3
+#define SWIM_WRITE_PHASE 4
+#define SWIM_WRITE_SETUP 5
+#define SWIM_WRITE_MODE0 6
+#define SWIM_WRITE_MODE1 7
+
+#define SWIM_READ_DATA 8
+#define SWIM_READ_MARK 9
+#define SWIM_READ_ERROR 10
+#define SWIM_READ_PARAMETER 11
+#define SWIM_READ_PHASE 12
+#define SWIM_READ_SETUP 13
+#define SWIM_READ_STATUS 14
+#define SWIM_READ_HANDSHAKE 15
+
+#define REG_SHIFT 9
+
+#define SWIM_MODE_IWM 0
+#define SWIM_MODE_SWIM 1
+
+/* bits in phase register */
+
+#define SWIM_SEEK_NEGATIVE 0x074
+#define SWIM_STEP 0x071
+#define SWIM_MOTOR_ON 0x072
+#define SWIM_MOTOR_OFF 0x076
+#define SWIM_INDEX 0x073
+#define SWIM_EJECT 0x077
+#define SWIM_SETMFM 0x171
+#define SWIM_SETGCR 0x175
+#define SWIM_RELAX 0x033
+#define SWIM_LSTRB 0x008
+#define SWIM_CA_MASK 0x077
+
+/* Select values for swim_select and swim_readbit */
+
+#define SWIM_READ_DATA_0 0x074
+#define SWIM_TWOMEG_DRIVE 0x075
+#define SWIM_SINGLE_SIDED 0x076
+#define SWIM_DRIVE_PRESENT 0x077
+#define SWIM_DISK_IN 0x170
+#define SWIM_WRITE_PROT 0x171
+#define SWIM_TRACK_ZERO 0x172
+#define SWIM_TACHO 0x173
+#define SWIM_READ_DATA_1 0x174
+#define SWIM_MFM_MODE 0x175
+#define SWIM_SEEK_COMPLETE 0x176
+#define SWIM_ONEMEG_MEDIA 0x177
+
+/* Bits in handshake register */
+
+#define SWIM_MARK_BYTE 0x01
+#define SWIM_CRC_ZERO 0x02
+#define SWIM_RDDATA 0x04
+#define SWIM_SENSE 0x08
+#define SWIM_MOTEN 0x10
+#define SWIM_ERROR 0x20
+#define SWIM_DAT2BYTE 0x40
+#define SWIM_DAT1BYTE 0x80
+
+/* bits in setup register */
+
+#define SWIM_S_INV_WDATA 0x01
+#define SWIM_S_3_5_SELECT 0x02
+#define SWIM_S_GCR 0x04
+#define SWIM_S_FCLK_DIV2 0x08
+#define SWIM_S_ERROR_CORR 0x10
+#define SWIM_S_IBM_DRIVE 0x20
+#define SWIM_S_GCR_WRITE 0x40
+#define SWIM_S_TIMEOUT 0x80
+
+/* bits in mode register */
+
+#define SWIM_CLFIFO 0x01
+#define SWIM_ENBL1 0x02
+#define SWIM_ENBL2 0x04
+#define SWIM_ACTION 0x08
+#define SWIM_WRITE_MODE 0x10
+#define SWIM_HEDSEL 0x20
+#define SWIM_MOTON 0x80
+
+static void fd_recalibrate(FDrive *drive)
+{
+}
+
+static void swim_change_cb(void *opaque, bool load, Error **errp)
+{
+ FDrive *drive = opaque;
+
+ if (!load) {
+ blk_set_perm(drive->blk, 0, BLK_PERM_ALL, &error_abort);
+ } else {
+ if (!blkconf_apply_backend_options(drive->conf,
+ blk_is_read_only(drive->blk), false,
+ errp)) {
+ return;
+ }
+ }
+}
+
+static const BlockDevOps swim_block_ops = {
+ .change_media_cb = swim_change_cb,
+};
+
+static Property swim_drive_properties[] = {
+ DEFINE_PROP_INT32("unit", SWIMDrive, unit, -1),
+ DEFINE_BLOCK_PROPERTIES(SWIMDrive, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void swim_drive_realize(DeviceState *qdev, Error **errp)
+{
+ SWIMDrive *dev = SWIM_DRIVE(qdev);
+ SWIMBus *bus = SWIM_BUS(qdev->parent_bus);
+ FDrive *drive;
+ int ret;
+
+ if (dev->unit == -1) {
+ for (dev->unit = 0; dev->unit < SWIM_MAX_FD; dev->unit++) {
+ drive = &bus->ctrl->drives[dev->unit];
+ if (!drive->blk) {
+ break;
+ }
+ }
+ }
+
+ if (dev->unit >= SWIM_MAX_FD) {
+ error_setg(errp, "Can't create floppy unit %d, bus supports "
+ "only %d units", dev->unit, SWIM_MAX_FD);
+ return;
+ }
+
+ drive = &bus->ctrl->drives[dev->unit];
+ if (drive->blk) {
+ error_setg(errp, "Floppy unit %d is in use", dev->unit);
+ return;
+ }
+
+ if (!dev->conf.blk) {
+ /* Anonymous BlockBackend for an empty drive */
+ dev->conf.blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
+ ret = blk_attach_dev(dev->conf.blk, qdev);
+ assert(ret == 0);
+ }
+
+ blkconf_blocksizes(&dev->conf);
+ if (dev->conf.logical_block_size != 512 ||
+ dev->conf.physical_block_size != 512)
+ {
+ error_setg(errp, "Physical and logical block size must "
+ "be 512 for floppy");
+ return;
+ }
+
+ /*
+ * rerror/werror aren't supported by fdc and therefore not even registered
+ * with qdev. So set the defaults manually before they are used in
+ * blkconf_apply_backend_options().
+ */
+ dev->conf.rerror = BLOCKDEV_ON_ERROR_AUTO;
+ dev->conf.werror = BLOCKDEV_ON_ERROR_AUTO;
+
+ if (!blkconf_apply_backend_options(&dev->conf,
+ blk_is_read_only(dev->conf.blk),
+ false, errp)) {
+ return;
+ }
+
+ /*
+ * 'enospc' is the default for -drive, 'report' is what blk_new() gives us
+ * for empty drives.
+ */
+ if (blk_get_on_error(dev->conf.blk, 0) != BLOCKDEV_ON_ERROR_ENOSPC &&
+ blk_get_on_error(dev->conf.blk, 0) != BLOCKDEV_ON_ERROR_REPORT) {
+ error_setg(errp, "fdc doesn't support drive option werror");
+ return;
+ }
+ if (blk_get_on_error(dev->conf.blk, 1) != BLOCKDEV_ON_ERROR_REPORT) {
+ error_setg(errp, "fdc doesn't support drive option rerror");
+ return;
+ }
+
+ drive->conf = &dev->conf;
+ drive->blk = dev->conf.blk;
+ drive->swimctrl = bus->ctrl;
+
+ blk_set_dev_ops(drive->blk, &swim_block_ops, drive);
+}
+
+static void swim_drive_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->realize = swim_drive_realize;
+ set_bit(DEVICE_CATEGORY_STORAGE, k->categories);
+ k->bus_type = TYPE_SWIM_BUS;
+ k->props = swim_drive_properties;
+ k->desc = "virtual SWIM drive";
+}
+
+static const TypeInfo swim_drive_info = {
+ .name = TYPE_SWIM_DRIVE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(SWIMDrive),
+ .class_init = swim_drive_class_init,
+};
+
+static const TypeInfo swim_bus_info = {
+ .name = TYPE_SWIM_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(SWIMBus),
+};
+
+static void iwmctrl_write(void *opaque, hwaddr reg, uint64_t value,
+ unsigned size)
+{
+ SWIMCtrl *swimctrl = opaque;
+
+ reg >>= REG_SHIFT;
+
+ swimctrl->regs[reg >> 1] = reg & 1;
+
+ if (swimctrl->regs[IWM_Q6] &&
+ swimctrl->regs[IWM_Q7]) {
+ if (swimctrl->regs[IWM_MTR]) {
+ /* data register */
+ swimctrl->iwm_data = value;
+ } else {
+ /* mode register */
+ swimctrl->iwm_mode = value;
+ /* detect sequence to switch from IWM mode to SWIM mode */
+ switch (swimctrl->iwm_switch) {
+ case 0:
+ if (value == 0x57) {
+ swimctrl->iwm_switch++;
+ }
+ break;
+ case 1:
+ if (value == 0x17) {
+ swimctrl->iwm_switch++;
+ }
+ break;
+ case 2:
+ if (value == 0x57) {
+ swimctrl->iwm_switch++;
+ }
+ break;
+ case 3:
+ if (value == 0x57) {
+ swimctrl->mode = SWIM_MODE_SWIM;
+ swimctrl->iwm_switch = 0;
+ }
+ break;
+ }
+ }
+ }
+}
+
+static uint64_t iwmctrl_read(void *opaque, hwaddr reg, unsigned size)
+{
+ SWIMCtrl *swimctrl = opaque;
+
+ reg >>= REG_SHIFT;
+
+ swimctrl->regs[reg >> 1] = reg & 1;
+
+ return 0;
+}
+
+static void swimctrl_write(void *opaque, hwaddr reg, uint64_t value,
+ unsigned size)
+{
+ SWIMCtrl *swimctrl = opaque;
+
+ if (swimctrl->mode == SWIM_MODE_IWM) {
+ iwmctrl_write(opaque, reg, value, size);
+ return;
+ }
+
+ reg >>= REG_SHIFT;
+
+ switch (reg) {
+ case SWIM_WRITE_PHASE:
+ swimctrl->swim_phase = value;
+ break;
+ case SWIM_WRITE_MODE0:
+ swimctrl->swim_mode &= ~value;
+ break;
+ case SWIM_WRITE_MODE1:
+ swimctrl->swim_mode |= value;
+ break;
+ case SWIM_WRITE_DATA:
+ case SWIM_WRITE_MARK:
+ case SWIM_WRITE_CRC:
+ case SWIM_WRITE_PARAMETER:
+ case SWIM_WRITE_SETUP:
+ break;
+ }
+}
+
+static uint64_t swimctrl_read(void *opaque, hwaddr reg, unsigned size)
+{
+ SWIMCtrl *swimctrl = opaque;
+ uint32_t value = 0;
+
+ if (swimctrl->mode == SWIM_MODE_IWM) {
+ return iwmctrl_read(opaque, reg, size);
+ }
+
+ reg >>= REG_SHIFT;
+
+ switch (reg) {
+ case SWIM_READ_PHASE:
+ value = swimctrl->swim_phase;
+ break;
+ case SWIM_READ_HANDSHAKE:
+ if (swimctrl->swim_phase == SWIM_DRIVE_PRESENT) {
+ /* always answer "no drive present" */
+ value = SWIM_SENSE;
+ }
+ break;
+ case SWIM_READ_DATA:
+ case SWIM_READ_MARK:
+ case SWIM_READ_ERROR:
+ case SWIM_READ_PARAMETER:
+ case SWIM_READ_SETUP:
+ case SWIM_READ_STATUS:
+ break;
+ }
+
+ return value;
+}
+
+static const MemoryRegionOps swimctrl_mem_ops = {
+ .write = swimctrl_write,
+ .read = swimctrl_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void sysbus_swim_reset(DeviceState *d)
+{
+ SWIM *sys = SWIM(d);
+ SWIMCtrl *ctrl = &sys->ctrl;
+ int i;
+
+ ctrl->mode = 0;
+ ctrl->iwm_switch = 0;
+ for (i = 0; i < 8; i++) {
+ ctrl->regs[i] = 0;
+ }
+ ctrl->iwm_data = 0;
+ ctrl->iwm_mode = 0;
+ ctrl->swim_phase = 0;
+ ctrl->swim_mode = 0;
+ for (i = 0; i < SWIM_MAX_FD; i++) {
+ fd_recalibrate(&ctrl->drives[i]);
+ }
+}
+
+static void sysbus_swim_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ SWIM *sbs = SWIM(obj);
+ SWIMCtrl *swimctrl = &sbs->ctrl;
+
+ memory_region_init_io(&swimctrl->iomem, obj, &swimctrl_mem_ops, swimctrl,
+ "swim", 0x2000);
+ sysbus_init_mmio(sbd, &swimctrl->iomem);
+}
+
+static void sysbus_swim_realize(DeviceState *dev, Error **errp)
+{
+ SWIM *sys = SWIM(dev);
+ SWIMCtrl *swimctrl = &sys->ctrl;
+
+ qbus_create_inplace(&swimctrl->bus, sizeof(SWIMBus), TYPE_SWIM_BUS, dev,
+ NULL);
+ swimctrl->bus.ctrl = swimctrl;
+}
+
+static const VMStateDescription vmstate_fdrive = {
+ .name = "fdrive",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription vmstate_swim = {
+ .name = "swim",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(mode, SWIMCtrl),
+ /* IWM mode */
+ VMSTATE_INT32(iwm_switch, SWIMCtrl),
+ VMSTATE_UINT16_ARRAY(regs, SWIMCtrl, 8),
+ VMSTATE_UINT8(iwm_data, SWIMCtrl),
+ VMSTATE_UINT8(iwm_mode, SWIMCtrl),
+ /* SWIM mode */
+ VMSTATE_UINT8(swim_phase, SWIMCtrl),
+ VMSTATE_UINT8(swim_mode, SWIMCtrl),
+ /* Drives */
+ VMSTATE_STRUCT_ARRAY(drives, SWIMCtrl, SWIM_MAX_FD, 1,
+ vmstate_fdrive, FDrive),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription vmstate_sysbus_swim = {
+ .name = "SWIM",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(ctrl, SWIM, 0, vmstate_swim, SWIMCtrl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void sysbus_swim_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = sysbus_swim_realize;
+ dc->reset = sysbus_swim_reset;
+ dc->vmsd = &vmstate_sysbus_swim;
+}
+
+static const TypeInfo sysbus_swim_info = {
+ .name = TYPE_SWIM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SWIM),
+ .instance_init = sysbus_swim_init,
+ .class_init = sysbus_swim_class_init,
+};
+
+static void swim_register_types(void)
+{
+ type_register_static(&sysbus_swim_info);
+ type_register_static(&swim_bus_info);
+ type_register_static(&swim_drive_info);
+}
+
+type_init(swim_register_types)
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index dd14b9a489..9fa2eaf890 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -42,9 +42,9 @@
*/
static VirtIOFeature feature_sizes[] = {
{.flags = 1ULL << VIRTIO_BLK_F_DISCARD,
- .end = virtio_endof(struct virtio_blk_config, discard_sector_alignment)},
+ .end = endof(struct virtio_blk_config, discard_sector_alignment)},
{.flags = 1ULL << VIRTIO_BLK_F_WRITE_ZEROES,
- .end = virtio_endof(struct virtio_blk_config, write_zeroes_may_unmap)},
+ .end = endof(struct virtio_blk_config, write_zeroes_may_unmap)},
{}
};
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index cbad6c1d55..cf1ba28fe3 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -212,6 +212,30 @@ void device_listener_unregister(DeviceListener *listener)
QTAILQ_REMOVE(&device_listeners, listener, link);
}
+bool qdev_should_hide_device(QemuOpts *opts)
+{
+ int rc = -1;
+ DeviceListener *listener;
+
+ QTAILQ_FOREACH(listener, &device_listeners, link) {
+ if (listener->should_be_hidden) {
+ /*
+ * should_be_hidden_will return
+ * 1 if device matches opts and it should be hidden
+ * 0 if device matches opts and should not be hidden
+ * -1 if device doesn't match ops
+ */
+ rc = listener->should_be_hidden(listener, opts);
+ }
+
+ if (rc > 0) {
+ break;
+ }
+ }
+
+ return rc > 0;
+}
+
void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id,
int required_for_version)
{
@@ -972,6 +996,7 @@ static void device_initfn(Object *obj)
dev->instance_id_alias = -1;
dev->realized = false;
+ dev->allow_unplug_during_migration = false;
object_property_add_bool(obj, "realized",
device_get_realized, device_set_realized, NULL);
diff --git a/hw/display/Kconfig b/hw/display/Kconfig
index cbdf7b1a67..c500d1fc6d 100644
--- a/hw/display/Kconfig
+++ b/hw/display/Kconfig
@@ -132,3 +132,8 @@ config ATI_VGA
select VGA
select BITBANG_I2C
select DDC
+
+config MACFB
+ bool
+ select FRAMEBUFFER
+ depends on NUBUS
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
index 5a4066383b..f2182e3bef 100644
--- a/hw/display/Makefile.objs
+++ b/hw/display/Makefile.objs
@@ -26,6 +26,7 @@ common-obj-$(CONFIG_EXYNOS4) += exynos4210_fimd.o
common-obj-$(CONFIG_FRAMEBUFFER) += framebuffer.o
obj-$(CONFIG_MILKYMIST) += milkymist-vgafb.o
common-obj-$(CONFIG_ZAURUS) += tc6393xb.o
+common-obj-$(CONFIG_MACFB) += macfb.o
obj-$(CONFIG_MILKYMIST_TMU2) += milkymist-tmu2.o
milkymist-tmu2.o-cflags := $(X11_CFLAGS) $(OPENGL_CFLAGS)
diff --git a/hw/display/macfb.c b/hw/display/macfb.c
new file mode 100644
index 0000000000..f4fa8e3206
--- /dev/null
+++ b/hw/display/macfb.c
@@ -0,0 +1,477 @@
+/*
+ * QEMU Motorola 680x0 Macintosh Video Card Emulation
+ * Copyright (c) 2012-2018 Laurent Vivier
+ *
+ * some parts from QEMU G364 framebuffer Emulator.
+ * Copyright (c) 2007-2011 Herve Poussineau
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "hw/nubus/nubus.h"
+#include "hw/display/macfb.h"
+#include "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+
+#define VIDEO_BASE 0x00001000
+#define DAFB_BASE 0x00800000
+
+#define MACFB_PAGE_SIZE 4096
+#define MACFB_VRAM_SIZE (4 * MiB)
+
+#define DAFB_RESET 0x200
+#define DAFB_LUT 0x213
+
+
+typedef void macfb_draw_line_func(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width);
+
+static inline uint8_t macfb_read_byte(MacfbState *s, uint32_t addr)
+{
+ return s->vram[addr & s->vram_bit_mask];
+}
+
+/* 1-bit color */
+static void macfb_draw_line1(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ int bit = x & 7;
+ int idx = (macfb_read_byte(s, addr) >> (7 - bit)) & 1;
+ r = g = b = ((1 - idx) << 7);
+ addr += (bit == 7);
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 2-bit color */
+static void macfb_draw_line2(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ int bit = (x & 3);
+ int idx = (macfb_read_byte(s, addr) >> ((3 - bit) << 1)) & 3;
+ r = s->color_palette[idx * 3];
+ g = s->color_palette[idx * 3 + 1];
+ b = s->color_palette[idx * 3 + 2];
+ addr += (bit == 3);
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 4-bit color */
+static void macfb_draw_line4(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ int bit = x & 1;
+ int idx = (macfb_read_byte(s, addr) >> ((1 - bit) << 2)) & 15;
+ r = s->color_palette[idx * 3];
+ g = s->color_palette[idx * 3 + 1];
+ b = s->color_palette[idx * 3 + 2];
+ addr += (bit == 1);
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 8-bit color */
+static void macfb_draw_line8(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ r = s->color_palette[macfb_read_byte(s, addr) * 3];
+ g = s->color_palette[macfb_read_byte(s, addr) * 3 + 1];
+ b = s->color_palette[macfb_read_byte(s, addr) * 3 + 2];
+ addr++;
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 16-bit color */
+static void macfb_draw_line16(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ uint16_t pixel;
+ pixel = (macfb_read_byte(s, addr) << 8) | macfb_read_byte(s, addr + 1);
+ r = ((pixel >> 10) & 0x1f) << 3;
+ g = ((pixel >> 5) & 0x1f) << 3;
+ b = (pixel & 0x1f) << 3;
+ addr += 2;
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 24-bit color */
+static void macfb_draw_line24(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ r = macfb_read_byte(s, addr);
+ g = macfb_read_byte(s, addr + 1);
+ b = macfb_read_byte(s, addr + 2);
+ addr += 3;
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+
+enum {
+ MACFB_DRAW_LINE1,
+ MACFB_DRAW_LINE2,
+ MACFB_DRAW_LINE4,
+ MACFB_DRAW_LINE8,
+ MACFB_DRAW_LINE16,
+ MACFB_DRAW_LINE24,
+ MACFB_DRAW_LINE_NB,
+};
+
+static macfb_draw_line_func * const
+ macfb_draw_line_table[MACFB_DRAW_LINE_NB] = {
+ macfb_draw_line1,
+ macfb_draw_line2,
+ macfb_draw_line4,
+ macfb_draw_line8,
+ macfb_draw_line16,
+ macfb_draw_line24,
+};
+
+static int macfb_check_dirty(MacfbState *s, DirtyBitmapSnapshot *snap,
+ ram_addr_t addr, int len)
+{
+ return memory_region_snapshot_get_dirty(&s->mem_vram, snap, addr, len);
+}
+
+static void macfb_draw_graphic(MacfbState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ DirtyBitmapSnapshot *snap = NULL;
+ ram_addr_t page;
+ uint32_t v = 0;
+ int y, ymin;
+ int macfb_stride = (s->depth * s->width + 7) / 8;
+ macfb_draw_line_func *macfb_draw_line;
+
+ switch (s->depth) {
+ case 1:
+ v = MACFB_DRAW_LINE1;
+ break;
+ case 2:
+ v = MACFB_DRAW_LINE2;
+ break;
+ case 4:
+ v = MACFB_DRAW_LINE4;
+ break;
+ case 8:
+ v = MACFB_DRAW_LINE8;
+ break;
+ case 16:
+ v = MACFB_DRAW_LINE16;
+ break;
+ case 24:
+ v = MACFB_DRAW_LINE24;
+ break;
+ }
+
+ macfb_draw_line = macfb_draw_line_table[v];
+ assert(macfb_draw_line != NULL);
+
+ snap = memory_region_snapshot_and_clear_dirty(&s->mem_vram, 0x0,
+ memory_region_size(&s->mem_vram),
+ DIRTY_MEMORY_VGA);
+
+ ymin = -1;
+ page = 0;
+ for (y = 0; y < s->height; y++, page += macfb_stride) {
+ if (macfb_check_dirty(s, snap, page, macfb_stride)) {
+ uint8_t *data_display;
+
+ data_display = surface_data(surface) + y * surface_stride(surface);
+ macfb_draw_line(s, data_display, page, s->width);
+
+ if (ymin < 0) {
+ ymin = y;
+ }
+ } else {
+ if (ymin >= 0) {
+ dpy_gfx_update(s->con, 0, ymin, s->width, y - ymin);
+ ymin = -1;
+ }
+ }
+ }
+
+ if (ymin >= 0) {
+ dpy_gfx_update(s->con, 0, ymin, s->width, y - ymin);
+ }
+
+ g_free(snap);
+}
+
+static void macfb_invalidate_display(void *opaque)
+{
+ MacfbState *s = opaque;
+
+ memory_region_set_dirty(&s->mem_vram, 0, MACFB_VRAM_SIZE);
+}
+
+static void macfb_update_display(void *opaque)
+{
+ MacfbState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (s->width == 0 || s->height == 0) {
+ return;
+ }
+
+ if (s->width != surface_width(surface) ||
+ s->height != surface_height(surface)) {
+ qemu_console_resize(s->con, s->width, s->height);
+ }
+
+ macfb_draw_graphic(s);
+}
+
+static void macfb_reset(MacfbState *s)
+{
+ int i;
+
+ s->palette_current = 0;
+ for (i = 0; i < 256; i++) {
+ s->color_palette[i * 3] = 255 - i;
+ s->color_palette[i * 3 + 1] = 255 - i;
+ s->color_palette[i * 3 + 2] = 255 - i;
+ }
+ memset(s->vram, 0, MACFB_VRAM_SIZE);
+ macfb_invalidate_display(s);
+}
+
+static uint64_t macfb_ctrl_read(void *opaque,
+ hwaddr addr,
+ unsigned int size)
+{
+ return 0;
+}
+
+static void macfb_ctrl_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned int size)
+{
+ MacfbState *s = opaque;
+ switch (addr) {
+ case DAFB_RESET:
+ s->palette_current = 0;
+ break;
+ case DAFB_LUT:
+ s->color_palette[s->palette_current++] = val;
+ if (s->palette_current % 3) {
+ macfb_invalidate_display(s);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps macfb_ctrl_ops = {
+ .read = macfb_ctrl_read,
+ .write = macfb_ctrl_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static int macfb_post_load(void *opaque, int version_id)
+{
+ macfb_invalidate_display(opaque);
+ return 0;
+}
+
+static const VMStateDescription vmstate_macfb = {
+ .name = "macfb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .post_load = macfb_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(color_palette, MacfbState, 256 * 3),
+ VMSTATE_UINT32(palette_current, MacfbState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps macfb_ops = {
+ .invalidate = macfb_invalidate_display,
+ .gfx_update = macfb_update_display,
+};
+
+static void macfb_common_realize(DeviceState *dev, MacfbState *s, Error **errp)
+{
+ DisplaySurface *surface;
+
+ if (s->depth != 1 && s->depth != 2 && s->depth != 4 && s->depth != 8 &&
+ s->depth != 16 && s->depth != 24) {
+ error_setg(errp, "unknown guest depth %d", s->depth);
+ return;
+ }
+
+ s->con = graphic_console_init(dev, 0, &macfb_ops, s);
+ surface = qemu_console_surface(s->con);
+
+ if (surface_bits_per_pixel(surface) != 32) {
+ error_setg(errp, "unknown host depth %d",
+ surface_bits_per_pixel(surface));
+ return;
+ }
+
+ memory_region_init_io(&s->mem_ctrl, NULL, &macfb_ctrl_ops, s, "macfb-ctrl",
+ 0x1000);
+
+ memory_region_init_ram_nomigrate(&s->mem_vram, OBJECT(s), "macfb-vram",
+ MACFB_VRAM_SIZE, errp);
+ s->vram = memory_region_get_ram_ptr(&s->mem_vram);
+ s->vram_bit_mask = MACFB_VRAM_SIZE - 1;
+ vmstate_register_ram(&s->mem_vram, dev);
+ memory_region_set_coalescing(&s->mem_vram);
+}
+
+static void macfb_sysbus_realize(DeviceState *dev, Error **errp)
+{
+ MacfbSysBusState *s = MACFB(dev);
+ MacfbState *ms = &s->macfb;
+
+ macfb_common_realize(dev, ms, errp);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &ms->mem_ctrl);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &ms->mem_vram);
+}
+
+const uint8_t macfb_rom[] = {
+ 255, 0, 0, 0,
+};
+
+static void macfb_nubus_realize(DeviceState *dev, Error **errp)
+{
+ NubusDevice *nd = NUBUS_DEVICE(dev);
+ MacfbNubusState *s = NUBUS_MACFB(dev);
+ MacfbNubusDeviceClass *ndc = MACFB_NUBUS_GET_CLASS(dev);
+ MacfbState *ms = &s->macfb;
+
+ ndc->parent_realize(dev, errp);
+
+ macfb_common_realize(dev, ms, errp);
+ memory_region_add_subregion(&nd->slot_mem, DAFB_BASE, &ms->mem_ctrl);
+ memory_region_add_subregion(&nd->slot_mem, VIDEO_BASE, &ms->mem_vram);
+
+ nubus_register_rom(nd, macfb_rom, sizeof(macfb_rom), 1, 9, 0xf);
+}
+
+static void macfb_sysbus_reset(DeviceState *d)
+{
+ MacfbSysBusState *s = MACFB(d);
+ macfb_reset(&s->macfb);
+}
+
+static void macfb_nubus_reset(DeviceState *d)
+{
+ MacfbNubusState *s = NUBUS_MACFB(d);
+ macfb_reset(&s->macfb);
+}
+
+static Property macfb_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("width", MacfbSysBusState, macfb.width, 640),
+ DEFINE_PROP_UINT32("height", MacfbSysBusState, macfb.height, 480),
+ DEFINE_PROP_UINT8("depth", MacfbSysBusState, macfb.depth, 8),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property macfb_nubus_properties[] = {
+ DEFINE_PROP_UINT32("width", MacfbNubusState, macfb.width, 640),
+ DEFINE_PROP_UINT32("height", MacfbNubusState, macfb.height, 480),
+ DEFINE_PROP_UINT8("depth", MacfbNubusState, macfb.depth, 8),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void macfb_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = macfb_sysbus_realize;
+ dc->desc = "SysBus Macintosh framebuffer";
+ dc->reset = macfb_sysbus_reset;
+ dc->vmsd = &vmstate_macfb;
+ dc->props = macfb_sysbus_properties;
+}
+
+static void macfb_nubus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ MacfbNubusDeviceClass *ndc = MACFB_NUBUS_DEVICE_CLASS(klass);
+
+ device_class_set_parent_realize(dc, macfb_nubus_realize,
+ &ndc->parent_realize);
+ dc->desc = "Nubus Macintosh framebuffer";
+ dc->reset = macfb_nubus_reset;
+ dc->vmsd = &vmstate_macfb;
+ dc->props = macfb_nubus_properties;
+}
+
+static TypeInfo macfb_sysbus_info = {
+ .name = TYPE_MACFB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MacfbSysBusState),
+ .class_init = macfb_sysbus_class_init,
+};
+
+static TypeInfo macfb_nubus_info = {
+ .name = TYPE_NUBUS_MACFB,
+ .parent = TYPE_NUBUS_DEVICE,
+ .instance_size = sizeof(MacfbNubusState),
+ .class_init = macfb_nubus_class_init,
+ .class_size = sizeof(MacfbNubusDeviceClass),
+};
+
+static void macfb_register_types(void)
+{
+ type_register_static(&macfb_sysbus_info);
+ type_register_static(&macfb_nubus_info);
+}
+
+type_init(macfb_register_types)
diff --git a/hw/m68k/Kconfig b/hw/m68k/Kconfig
index a74fac5abd..c757e7dfa4 100644
--- a/hw/m68k/Kconfig
+++ b/hw/m68k/Kconfig
@@ -12,3 +12,13 @@ config NEXTCUBE
bool
select FRAMEBUFFER
select ESCC
+
+config Q800
+ bool
+ select MAC_VIA
+ select NUBUS
+ select MACFB
+ select SWIM
+ select ESCC
+ select ESP
+ select DP8393X
diff --git a/hw/m68k/Makefile.objs b/hw/m68k/Makefile.objs
index f25854730d..b2c9e5ab12 100644
--- a/hw/m68k/Makefile.objs
+++ b/hw/m68k/Makefile.objs
@@ -1,3 +1,4 @@
obj-$(CONFIG_AN5206) += an5206.o mcf5206.o
obj-$(CONFIG_MCF5208) += mcf5208.o mcf_intc.o
obj-$(CONFIG_NEXTCUBE) += next-kbd.o next-cube.o
+obj-$(CONFIG_Q800) += q800.o
diff --git a/hw/m68k/bootinfo.h b/hw/m68k/bootinfo.h
new file mode 100644
index 0000000000..5f8ded2686
--- /dev/null
+++ b/hw/m68k/bootinfo.h
@@ -0,0 +1,114 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+ *
+ * Bootinfo tags from linux bootinfo.h and bootinfo-mac.h:
+ * This is an easily parsable and extendable structure containing all
+ * information to be passed from the bootstrap to the kernel
+ *
+ * This structure is copied right after the kernel by the bootstrap
+ * routine.
+ */
+
+#ifndef HW_M68K_BOOTINFO_H
+#define HW_M68K_BOOTINFO_H
+struct bi_record {
+ uint16_t tag; /* tag ID */
+ uint16_t size; /* size of record */
+ uint32_t data[0]; /* data */
+};
+
+/* machine independent tags */
+
+#define BI_LAST 0x0000 /* last record */
+#define BI_MACHTYPE 0x0001 /* machine type (u_long) */
+#define BI_CPUTYPE 0x0002 /* cpu type (u_long) */
+#define BI_FPUTYPE 0x0003 /* fpu type (u_long) */
+#define BI_MMUTYPE 0x0004 /* mmu type (u_long) */
+#define BI_MEMCHUNK 0x0005 /* memory chunk address and size */
+ /* (struct mem_info) */
+#define BI_RAMDISK 0x0006 /* ramdisk address and size */
+ /* (struct mem_info) */
+#define BI_COMMAND_LINE 0x0007 /* kernel command line parameters */
+ /* (string) */
+
+/* Macintosh-specific tags (all u_long) */
+
+#define BI_MAC_MODEL 0x8000 /* Mac Gestalt ID (model type) */
+#define BI_MAC_VADDR 0x8001 /* Mac video base address */
+#define BI_MAC_VDEPTH 0x8002 /* Mac video depth */
+#define BI_MAC_VROW 0x8003 /* Mac video rowbytes */
+#define BI_MAC_VDIM 0x8004 /* Mac video dimensions */
+#define BI_MAC_VLOGICAL 0x8005 /* Mac video logical base */
+#define BI_MAC_SCCBASE 0x8006 /* Mac SCC base address */
+#define BI_MAC_BTIME 0x8007 /* Mac boot time */
+#define BI_MAC_GMTBIAS 0x8008 /* Mac GMT timezone offset */
+#define BI_MAC_MEMSIZE 0x8009 /* Mac RAM size (sanity check) */
+#define BI_MAC_CPUID 0x800a /* Mac CPU type (sanity check) */
+#define BI_MAC_ROMBASE 0x800b /* Mac system ROM base address */
+
+/* Macintosh hardware profile data */
+
+#define BI_MAC_VIA1BASE 0x8010 /* Mac VIA1 base address (always present) */
+#define BI_MAC_VIA2BASE 0x8011 /* Mac VIA2 base address (type varies) */
+#define BI_MAC_VIA2TYPE 0x8012 /* Mac VIA2 type (VIA, RBV, OSS) */
+#define BI_MAC_ADBTYPE 0x8013 /* Mac ADB interface type */
+#define BI_MAC_ASCBASE 0x8014 /* Mac Apple Sound Chip base address */
+#define BI_MAC_SCSI5380 0x8015 /* Mac NCR 5380 SCSI (base address, multi) */
+#define BI_MAC_SCSIDMA 0x8016 /* Mac SCSI DMA (base address) */
+#define BI_MAC_SCSI5396 0x8017 /* Mac NCR 53C96 SCSI (base address, multi) */
+#define BI_MAC_IDETYPE 0x8018 /* Mac IDE interface type */
+#define BI_MAC_IDEBASE 0x8019 /* Mac IDE interface base address */
+#define BI_MAC_NUBUS 0x801a /* Mac Nubus type (none, regular, pseudo) */
+#define BI_MAC_SLOTMASK 0x801b /* Mac Nubus slots present */
+#define BI_MAC_SCCTYPE 0x801c /* Mac SCC serial type (normal, IOP) */
+#define BI_MAC_ETHTYPE 0x801d /* Mac builtin ethernet type (Sonic, MACE */
+#define BI_MAC_ETHBASE 0x801e /* Mac builtin ethernet base address */
+#define BI_MAC_PMU 0x801f /* Mac power management / poweroff hardware */
+#define BI_MAC_IOP_SWIM 0x8020 /* Mac SWIM floppy IOP */
+#define BI_MAC_IOP_ADB 0x8021 /* Mac ADB IOP */
+
+#define BOOTINFO0(as, base, id) \
+ do { \
+ stw_phys(as, base, id); \
+ base += 2; \
+ stw_phys(as, base, sizeof(struct bi_record)); \
+ base += 2; \
+ } while (0)
+
+#define BOOTINFO1(as, base, id, value) \
+ do { \
+ stw_phys(as, base, id); \
+ base += 2; \
+ stw_phys(as, base, sizeof(struct bi_record) + 4); \
+ base += 2; \
+ stl_phys(as, base, value); \
+ base += 4; \
+ } while (0)
+
+#define BOOTINFO2(as, base, id, value1, value2) \
+ do { \
+ stw_phys(as, base, id); \
+ base += 2; \
+ stw_phys(as, base, sizeof(struct bi_record) + 8); \
+ base += 2; \
+ stl_phys(as, base, value1); \
+ base += 4; \
+ stl_phys(as, base, value2); \
+ base += 4; \
+ } while (0)
+
+#define BOOTINFOSTR(as, base, id, string) \
+ do { \
+ int i; \
+ stw_phys(as, base, id); \
+ base += 2; \
+ stw_phys(as, base, \
+ (sizeof(struct bi_record) + strlen(string) + 2) & ~1); \
+ base += 2; \
+ for (i = 0; string[i]; i++) { \
+ stb_phys(as, base++, string[i]); \
+ } \
+ stb_phys(as, base++, 0); \
+ base = (parameters_base + 1) & ~1; \
+ } while (0)
+#endif
diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c
new file mode 100644
index 0000000000..2b4842f8c6
--- /dev/null
+++ b/hw/m68k/q800.c
@@ -0,0 +1,401 @@
+/*
+ * QEMU Motorla 680x0 Macintosh hardware System Emulator
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu-common.h"
+#include "sysemu/sysemu.h"
+#include "cpu.h"
+#include "hw/hw.h"
+#include "hw/boards.h"
+#include "hw/irq.h"
+#include "elf.h"
+#include "hw/loader.h"
+#include "ui/console.h"
+#include "exec/address-spaces.h"
+#include "hw/char/escc.h"
+#include "hw/sysbus.h"
+#include "hw/scsi/esp.h"
+#include "bootinfo.h"
+#include "hw/misc/mac_via.h"
+#include "hw/input/adb.h"
+#include "hw/nubus/mac-nubus-bridge.h"
+#include "hw/display/macfb.h"
+#include "hw/block/swim.h"
+#include "net/net.h"
+#include "qapi/error.h"
+#include "sysemu/qtest.h"
+#include "sysemu/runstate.h"
+#include "sysemu/reset.h"
+
+#define MACROM_ADDR 0x40000000
+#define MACROM_SIZE 0x00100000
+
+#define MACROM_FILENAME "MacROM.bin"
+
+#define Q800_MACHINE_ID 35
+#define Q800_CPU_ID (1 << 2)
+#define Q800_FPU_ID (1 << 2)
+#define Q800_MMU_ID (1 << 2)
+
+#define MACH_MAC 3
+#define Q800_MAC_CPU_ID 2
+
+#define VIA_BASE 0x50f00000
+#define SONIC_PROM_BASE 0x50f08000
+#define SONIC_BASE 0x50f0a000
+#define SCC_BASE 0x50f0c020
+#define ESP_BASE 0x50f10000
+#define ESP_PDMA 0x50f10100
+#define ASC_BASE 0x50F14000
+#define SWIM_BASE 0x50F1E000
+#define NUBUS_SUPER_SLOT_BASE 0x60000000
+#define NUBUS_SLOT_BASE 0xf0000000
+
+/*
+ * the video base, whereas it a Nubus address,
+ * is needed by the kernel to have early display and
+ * thus provided by the bootloader
+ */
+#define VIDEO_BASE 0xf9001000
+
+#define MAC_CLOCK 3686418
+
+/*
+ * The GLUE (General Logic Unit) is an Apple custom integrated circuit chip
+ * that performs a variety of functions (RAM management, clock generation, ...).
+ * The GLUE chip receives interrupt requests from various devices,
+ * assign priority to each, and asserts one or more interrupt line to the
+ * CPU.
+ */
+
+typedef struct {
+ M68kCPU *cpu;
+ uint8_t ipr;
+} GLUEState;
+
+static void GLUE_set_irq(void *opaque, int irq, int level)
+{
+ GLUEState *s = opaque;
+ int i;
+
+ if (level) {
+ s->ipr |= 1 << irq;
+ } else {
+ s->ipr &= ~(1 << irq);
+ }
+
+ for (i = 7; i >= 0; i--) {
+ if ((s->ipr >> i) & 1) {
+ m68k_set_irq_level(s->cpu, i + 1, i + 25);
+ return;
+ }
+ }
+ m68k_set_irq_level(s->cpu, 0, 0);
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ M68kCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ cpu_reset(cs);
+ cpu->env.aregs[7] = ldl_phys(cs->as, 0);
+ cpu->env.pc = ldl_phys(cs->as, 4);
+}
+
+static void q800_init(MachineState *machine)
+{
+ M68kCPU *cpu = NULL;
+ int linux_boot;
+ int32_t kernel_size;
+ uint64_t elf_entry;
+ char *filename;
+ int bios_size;
+ ram_addr_t initrd_base;
+ int32_t initrd_size;
+ MemoryRegion *rom;
+ MemoryRegion *ram;
+ ram_addr_t ram_size = machine->ram_size;
+ const char *kernel_filename = machine->kernel_filename;
+ const char *initrd_filename = machine->initrd_filename;
+ const char *kernel_cmdline = machine->kernel_cmdline;
+ hwaddr parameters_base;
+ CPUState *cs;
+ DeviceState *dev;
+ DeviceState *via_dev;
+ SysBusESPState *sysbus_esp;
+ ESPState *esp;
+ SysBusDevice *sysbus;
+ BusState *adb_bus;
+ NubusBus *nubus;
+ GLUEState *irq;
+ qemu_irq *pic;
+
+ linux_boot = (kernel_filename != NULL);
+
+ if (ram_size > 1 * GiB) {
+ error_report("Too much memory for this machine: %" PRId64 " MiB, "
+ "maximum 1024 MiB", ram_size / MiB);
+ exit(1);
+ }
+
+ /* init CPUs */
+ cpu = M68K_CPU(cpu_create(machine->cpu_type));
+ qemu_register_reset(main_cpu_reset, cpu);
+
+ ram = g_malloc(sizeof(*ram));
+ memory_region_init_ram(ram, NULL, "m68k_mac.ram", ram_size, &error_abort);
+ memory_region_add_subregion(get_system_memory(), 0, ram);
+
+ /* IRQ Glue */
+
+ irq = g_new0(GLUEState, 1);
+ irq->cpu = cpu;
+ pic = qemu_allocate_irqs(GLUE_set_irq, irq, 8);
+
+ /* VIA */
+
+ via_dev = qdev_create(NULL, TYPE_MAC_VIA);
+ qdev_init_nofail(via_dev);
+ sysbus = SYS_BUS_DEVICE(via_dev);
+ sysbus_mmio_map(sysbus, 0, VIA_BASE);
+ qdev_connect_gpio_out_named(DEVICE(sysbus), "irq", 0, pic[0]);
+ qdev_connect_gpio_out_named(DEVICE(sysbus), "irq", 1, pic[1]);
+
+
+ adb_bus = qdev_get_child_bus(via_dev, "adb.0");
+ dev = qdev_create(adb_bus, TYPE_ADB_KEYBOARD);
+ qdev_init_nofail(dev);
+ dev = qdev_create(adb_bus, TYPE_ADB_MOUSE);
+ qdev_init_nofail(dev);
+
+ /* MACSONIC */
+
+ if (nb_nics > 1) {
+ error_report("q800 can only have one ethernet interface");
+ exit(1);
+ }
+
+ qemu_check_nic_model(&nd_table[0], "dp83932");
+
+ /*
+ * MacSonic driver needs an Apple MAC address
+ * Valid prefix are:
+ * 00:05:02 Apple
+ * 00:80:19 Dayna Communications, Inc.
+ * 00:A0:40 Apple
+ * 08:00:07 Apple
+ * (Q800 use the last one)
+ */
+ nd_table[0].macaddr.a[0] = 0x08;
+ nd_table[0].macaddr.a[1] = 0x00;
+ nd_table[0].macaddr.a[2] = 0x07;
+
+ dev = qdev_create(NULL, "dp8393x");
+ qdev_set_nic_properties(dev, &nd_table[0]);
+ qdev_prop_set_uint8(dev, "it_shift", 2);
+ qdev_prop_set_bit(dev, "big_endian", true);
+ qdev_prop_set_ptr(dev, "dma_mr", get_system_memory());
+ qdev_init_nofail(dev);
+ sysbus = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(sysbus, 0, SONIC_BASE);
+ sysbus_mmio_map(sysbus, 1, SONIC_PROM_BASE);
+ sysbus_connect_irq(sysbus, 0, pic[2]);
+
+ /* SCC */
+
+ dev = qdev_create(NULL, TYPE_ESCC);
+ qdev_prop_set_uint32(dev, "disabled", 0);
+ qdev_prop_set_uint32(dev, "frequency", MAC_CLOCK);
+ qdev_prop_set_uint32(dev, "it_shift", 1);
+ qdev_prop_set_bit(dev, "bit_swap", true);
+ qdev_prop_set_chr(dev, "chrA", serial_hd(0));
+ qdev_prop_set_chr(dev, "chrB", serial_hd(1));
+ qdev_prop_set_uint32(dev, "chnBtype", 0);
+ qdev_prop_set_uint32(dev, "chnAtype", 0);
+ qdev_init_nofail(dev);
+ sysbus = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(sysbus, 0, pic[3]);
+ sysbus_connect_irq(sysbus, 1, pic[3]);
+ sysbus_mmio_map(sysbus, 0, SCC_BASE);
+
+ /* SCSI */
+
+ dev = qdev_create(NULL, TYPE_ESP);
+ sysbus_esp = ESP_STATE(dev);
+ esp = &sysbus_esp->esp;
+ esp->dma_memory_read = NULL;
+ esp->dma_memory_write = NULL;
+ esp->dma_opaque = NULL;
+ sysbus_esp->it_shift = 4;
+ esp->dma_enabled = 1;
+ qdev_init_nofail(dev);
+
+ sysbus = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in_named(via_dev,
+ "via2-irq",
+ VIA2_IRQ_SCSI_BIT));
+ sysbus_connect_irq(sysbus, 1,
+ qdev_get_gpio_in_named(via_dev, "via2-irq",
+ VIA2_IRQ_SCSI_DATA_BIT));
+ sysbus_mmio_map(sysbus, 0, ESP_BASE);
+ sysbus_mmio_map(sysbus, 1, ESP_PDMA);
+
+ scsi_bus_legacy_handle_cmdline(&esp->bus);
+
+ /* SWIM floppy controller */
+
+ dev = qdev_create(NULL, TYPE_SWIM);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, SWIM_BASE);
+
+ /* NuBus */
+
+ dev = qdev_create(NULL, TYPE_MAC_NUBUS_BRIDGE);
+ qdev_init_nofail(dev);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, NUBUS_SUPER_SLOT_BASE);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, NUBUS_SLOT_BASE);
+
+ nubus = MAC_NUBUS_BRIDGE(dev)->bus;
+
+ /* framebuffer in nubus slot #9 */
+
+ dev = qdev_create(BUS(nubus), TYPE_NUBUS_MACFB);
+ qdev_prop_set_uint32(dev, "width", graphic_width);
+ qdev_prop_set_uint32(dev, "height", graphic_height);
+ qdev_prop_set_uint8(dev, "depth", graphic_depth);
+ qdev_init_nofail(dev);
+
+ cs = CPU(cpu);
+ if (linux_boot) {
+ uint64_t high;
+ kernel_size = load_elf(kernel_filename, NULL, NULL, NULL,
+ &elf_entry, NULL, &high, 1,
+ EM_68K, 0, 0);
+ if (kernel_size < 0) {
+ error_report("could not load kernel '%s'", kernel_filename);
+ exit(1);
+ }
+ stl_phys(cs->as, 4, elf_entry); /* reset initial PC */
+ parameters_base = (high + 1) & ~1;
+
+ BOOTINFO1(cs->as, parameters_base, BI_MACHTYPE, MACH_MAC);
+ BOOTINFO1(cs->as, parameters_base, BI_FPUTYPE, Q800_FPU_ID);
+ BOOTINFO1(cs->as, parameters_base, BI_MMUTYPE, Q800_MMU_ID);
+ BOOTINFO1(cs->as, parameters_base, BI_CPUTYPE, Q800_CPU_ID);
+ BOOTINFO1(cs->as, parameters_base, BI_MAC_CPUID, Q800_MAC_CPU_ID);
+ BOOTINFO1(cs->as, parameters_base, BI_MAC_MODEL, Q800_MACHINE_ID);
+ BOOTINFO1(cs->as, parameters_base,
+ BI_MAC_MEMSIZE, ram_size >> 20); /* in MB */
+ BOOTINFO2(cs->as, parameters_base, BI_MEMCHUNK, 0, ram_size);
+ BOOTINFO1(cs->as, parameters_base, BI_MAC_VADDR, VIDEO_BASE);
+ BOOTINFO1(cs->as, parameters_base, BI_MAC_VDEPTH, graphic_depth);
+ BOOTINFO1(cs->as, parameters_base, BI_MAC_VDIM,
+ (graphic_height << 16) | graphic_width);
+ BOOTINFO1(cs->as, parameters_base, BI_MAC_VROW,
+ (graphic_width * graphic_depth + 7) / 8);
+ BOOTINFO1(cs->as, parameters_base, BI_MAC_SCCBASE, SCC_BASE);
+
+ if (kernel_cmdline) {
+ BOOTINFOSTR(cs->as, parameters_base, BI_COMMAND_LINE,
+ kernel_cmdline);
+ }
+
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_size = get_image_size(initrd_filename);
+ if (initrd_size < 0) {
+ error_report("could not load initial ram disk '%s'",
+ initrd_filename);
+ exit(1);
+ }
+
+ initrd_base = (ram_size - initrd_size) & TARGET_PAGE_MASK;
+ load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ BOOTINFO2(cs->as, parameters_base, BI_RAMDISK, initrd_base,
+ initrd_size);
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ BOOTINFO0(cs->as, parameters_base, BI_LAST);
+ } else {
+ uint8_t *ptr;
+ /* allocate and load BIOS */
+ rom = g_malloc(sizeof(*rom));
+ memory_region_init_ram(rom, NULL, "m68k_mac.rom", MACROM_SIZE,
+ &error_abort);
+ if (bios_name == NULL) {
+ bios_name = MACROM_FILENAME;
+ }
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ memory_region_set_readonly(rom, true);
+ memory_region_add_subregion(get_system_memory(), MACROM_ADDR, rom);
+
+ /* Load MacROM binary */
+ if (filename) {
+ bios_size = load_image_targphys(filename, MACROM_ADDR, MACROM_SIZE);
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+
+ /* Remove qtest_enabled() check once firmware files are in the tree */
+ if (!qtest_enabled()) {
+ if (bios_size < 0 || bios_size > MACROM_SIZE) {
+ error_report("could not load MacROM '%s'", bios_name);
+ exit(1);
+ }
+
+ ptr = rom_ptr(MACROM_ADDR, MACROM_SIZE);
+ stl_phys(cs->as, 0, ldl_p(ptr)); /* reset initial SP */
+ stl_phys(cs->as, 4,
+ MACROM_ADDR + ldl_p(ptr + 4)); /* reset initial PC */
+ }
+ }
+}
+
+static void q800_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ mc->desc = "Macintosh Quadra 800";
+ mc->init = q800_init;
+ mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040");
+ mc->max_cpus = 1;
+ mc->is_default = 0;
+ mc->block_default_type = IF_SCSI;
+}
+
+static const TypeInfo q800_machine_typeinfo = {
+ .name = MACHINE_TYPE_NAME("q800"),
+ .parent = TYPE_MACHINE,
+ .class_init = q800_machine_class_init,
+};
+
+static void q800_machine_register_types(void)
+{
+ type_register_static(&q800_machine_typeinfo);
+}
+
+type_init(q800_machine_register_types)
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 51754bb47c..2164646553 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -120,4 +120,9 @@ config AUX
config UNIMP
bool
+config MAC_VIA
+ bool
+ select MOS6522
+ select ADB
+
source macio/Kconfig
diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs
index c89f3816a5..ba898a5781 100644
--- a/hw/misc/Makefile.objs
+++ b/hw/misc/Makefile.objs
@@ -79,5 +79,6 @@ common-obj-$(CONFIG_ASPEED_SOC) += aspeed_xdma.o
common-obj-$(CONFIG_ASPEED_SOC) += aspeed_scu.o aspeed_sdmc.o
common-obj-$(CONFIG_MSF2) += msf2-sysreg.o
common-obj-$(CONFIG_NRF51_SOC) += nrf51_rng.o
+obj-$(CONFIG_MAC_VIA) += mac_via.o
common-obj-$(CONFIG_GRLIB) += grlib_ahb_apb_pnp.o
diff --git a/hw/misc/mac_via.c b/hw/misc/mac_via.c
new file mode 100644
index 0000000000..f3f130ad96
--- /dev/null
+++ b/hw/misc/mac_via.c
@@ -0,0 +1,964 @@
+/*
+ * QEMU m68k Macintosh VIA device support
+ *
+ * Copyright (c) 2011-2018 Laurent Vivier
+ * Copyright (c) 2018 Mark Cave-Ayland
+ *
+ * Some parts from hw/misc/macio/cuda.c
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * some parts from linux-2.6.29, arch/m68k/include/asm/mac_via.h
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "migration/vmstate.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "qemu/timer.h"
+#include "hw/misc/mac_via.h"
+#include "hw/misc/mos6522.h"
+#include "hw/input/adb.h"
+#include "sysemu/runstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+
+
+/*
+ * VIAs: There are two in every machine,
+ */
+
+#define VIA_SIZE (0x2000)
+
+/*
+ * Not all of these are true post MacII I think.
+ * CSA: probably the ones CHRP marks as 'unused' change purposes
+ * when the IWM becomes the SWIM.
+ * http://www.rs6000.ibm.com/resource/technology/chrpio/via5.mak.html
+ * ftp://ftp.austin.ibm.com/pub/technology/spec/chrp/inwork/CHRP_IORef_1.0.pdf
+ *
+ * also, http://developer.apple.com/technotes/hw/hw_09.html claims the
+ * following changes for IIfx:
+ * VIA1A_vSccWrReq not available and that VIA1A_vSync has moved to an IOP.
+ * Also, "All of the functionality of VIA2 has been moved to other chips".
+ */
+
+#define VIA1A_vSccWrReq 0x80 /*
+ * SCC write. (input)
+ * [CHRP] SCC WREQ: Reflects the state of the
+ * Wait/Request pins from the SCC.
+ * [Macintosh Family Hardware]
+ * as CHRP on SE/30,II,IIx,IIcx,IIci.
+ * on IIfx, "0 means an active request"
+ */
+#define VIA1A_vRev8 0x40 /*
+ * Revision 8 board ???
+ * [CHRP] En WaitReqB: Lets the WaitReq_L
+ * signal from port B of the SCC appear on
+ * the PA7 input pin. Output.
+ * [Macintosh Family] On the SE/30, this
+ * is the bit to flip screen buffers.
+ * 0=alternate, 1=main.
+ * on II,IIx,IIcx,IIci,IIfx this is a bit
+ * for Rev ID. 0=II,IIx, 1=IIcx,IIci,IIfx
+ */
+#define VIA1A_vHeadSel 0x20 /*
+ * Head select for IWM.
+ * [CHRP] unused.
+ * [Macintosh Family] "Floppy disk
+ * state-control line SEL" on all but IIfx
+ */
+#define VIA1A_vOverlay 0x10 /*
+ * [Macintosh Family] On SE/30,II,IIx,IIcx
+ * this bit enables the "Overlay" address
+ * map in the address decoders as it is on
+ * reset for mapping the ROM over the reset
+ * vector. 1=use overlay map.
+ * On the IIci,IIfx it is another bit of the
+ * CPU ID: 0=normal IIci, 1=IIci with parity
+ * feature or IIfx.
+ * [CHRP] En WaitReqA: Lets the WaitReq_L
+ * signal from port A of the SCC appear
+ * on the PA7 input pin (CHRP). Output.
+ * [MkLinux] "Drive Select"
+ * (with 0x20 being 'disk head select')
+ */
+#define VIA1A_vSync 0x08 /*
+ * [CHRP] Sync Modem: modem clock select:
+ * 1: select the external serial clock to
+ * drive the SCC's /RTxCA pin.
+ * 0: Select the 3.6864MHz clock to drive
+ * the SCC cell.
+ * [Macintosh Family] Correct on all but IIfx
+ */
+
+/*
+ * Macintosh Family Hardware sez: bits 0-2 of VIA1A are volume control
+ * on Macs which had the PWM sound hardware. Reserved on newer models.
+ * On IIci,IIfx, bits 1-2 are the rest of the CPU ID:
+ * bit 2: 1=IIci, 0=IIfx
+ * bit 1: 1 on both IIci and IIfx.
+ * MkLinux sez bit 0 is 'burnin flag' in this case.
+ * CHRP sez: VIA1A bits 0-2 and 5 are 'unused': if programmed as
+ * inputs, these bits will read 0.
+ */
+#define VIA1A_vVolume 0x07 /* Audio volume mask for PWM */
+#define VIA1A_CPUID0 0x02 /* CPU id bit 0 on RBV, others */
+#define VIA1A_CPUID1 0x04 /* CPU id bit 0 on RBV, others */
+#define VIA1A_CPUID2 0x10 /* CPU id bit 0 on RBV, others */
+#define VIA1A_CPUID3 0x40 /* CPU id bit 0 on RBV, others */
+
+/*
+ * Info on VIA1B is from Macintosh Family Hardware & MkLinux.
+ * CHRP offers no info.
+ */
+#define VIA1B_vSound 0x80 /*
+ * Sound enable (for compatibility with
+ * PWM hardware) 0=enabled.
+ * Also, on IIci w/parity, shows parity error
+ * 0=error, 1=OK.
+ */
+#define VIA1B_vMystery 0x40 /*
+ * On IIci, parity enable. 0=enabled,1=disabled
+ * On SE/30, vertical sync interrupt enable.
+ * 0=enabled. This vSync interrupt shows up
+ * as a slot $E interrupt.
+ */
+#define VIA1B_vADBS2 0x20 /* ADB state input bit 1 (unused on IIfx) */
+#define VIA1B_vADBS1 0x10 /* ADB state input bit 0 (unused on IIfx) */
+#define VIA1B_vADBInt 0x08 /* ADB interrupt 0=interrupt (unused on IIfx)*/
+#define VIA1B_vRTCEnb 0x04 /* Enable Real time clock. 0=enabled. */
+#define VIA1B_vRTCClk 0x02 /* Real time clock serial-clock line. */
+#define VIA1B_vRTCData 0x01 /* Real time clock serial-data line. */
+
+/*
+ * VIA2 A register is the interrupt lines raised off the nubus
+ * slots.
+ * The below info is from 'Macintosh Family Hardware.'
+ * MkLinux calls the 'IIci internal video IRQ' below the 'RBV slot 0 irq.'
+ * It also notes that the slot $9 IRQ is the 'Ethernet IRQ' and
+ * defines the 'Video IRQ' as 0x40 for the 'EVR' VIA work-alike.
+ * Perhaps OSS uses vRAM1 and vRAM2 for ADB.
+ */
+
+#define VIA2A_vRAM1 0x80 /* RAM size bit 1 (IIci: reserved) */
+#define VIA2A_vRAM0 0x40 /* RAM size bit 0 (IIci: internal video IRQ) */
+#define VIA2A_vIRQE 0x20 /* IRQ from slot $E */
+#define VIA2A_vIRQD 0x10 /* IRQ from slot $D */
+#define VIA2A_vIRQC 0x08 /* IRQ from slot $C */
+#define VIA2A_vIRQB 0x04 /* IRQ from slot $B */
+#define VIA2A_vIRQA 0x02 /* IRQ from slot $A */
+#define VIA2A_vIRQ9 0x01 /* IRQ from slot $9 */
+
+/*
+ * RAM size bits decoded as follows:
+ * bit1 bit0 size of ICs in bank A
+ * 0 0 256 kbit
+ * 0 1 1 Mbit
+ * 1 0 4 Mbit
+ * 1 1 16 Mbit
+ */
+
+/*
+ * Register B has the fun stuff in it
+ */
+
+#define VIA2B_vVBL 0x80 /*
+ * VBL output to VIA1 (60.15Hz) driven by
+ * timer T1.
+ * on IIci, parity test: 0=test mode.
+ * [MkLinux] RBV_PARODD: 1=odd,0=even.
+ */
+#define VIA2B_vSndJck 0x40 /*
+ * External sound jack status.
+ * 0=plug is inserted. On SE/30, always 0
+ */
+#define VIA2B_vTfr0 0x20 /* Transfer mode bit 0 ack from NuBus */
+#define VIA2B_vTfr1 0x10 /* Transfer mode bit 1 ack from NuBus */
+#define VIA2B_vMode32 0x08 /*
+ * 24/32bit switch - doubles as cache flush
+ * on II, AMU/PMMU control.
+ * if AMU, 0=24bit to 32bit translation
+ * if PMMU, 1=PMMU is accessing page table.
+ * on SE/30 tied low.
+ * on IIx,IIcx,IIfx, unused.
+ * on IIci/RBV, cache control. 0=flush cache.
+ */
+#define VIA2B_vPower 0x04 /*
+ * Power off, 0=shut off power.
+ * on SE/30 this signal sent to PDS card.
+ */
+#define VIA2B_vBusLk 0x02 /*
+ * Lock NuBus transactions, 0=locked.
+ * on SE/30 sent to PDS card.
+ */
+#define VIA2B_vCDis 0x01 /*
+ * Cache control. On IIci, 1=disable cache card
+ * on others, 0=disable processor's instruction
+ * and data caches.
+ */
+
+/* interrupt flags */
+
+#define IRQ_SET 0x80
+
+/* common */
+
+#define VIA_IRQ_TIMER1 0x40
+#define VIA_IRQ_TIMER2 0x20
+
+/*
+ * Apple sez: http://developer.apple.com/technotes/ov/ov_04.html
+ * Another example of a valid function that has no ROM support is the use
+ * of the alternate video page for page-flipping animation. Since there
+ * is no ROM call to flip pages, it is necessary to go play with the
+ * right bit in the VIA chip (6522 Versatile Interface Adapter).
+ * [CSA: don't know which one this is, but it's one of 'em!]
+ */
+
+/*
+ * 6522 registers - see databook.
+ * CSA: Assignments for VIA1 confirmed from CHRP spec.
+ */
+
+/* partial address decode. 0xYYXX : XX part for RBV, YY part for VIA */
+/* Note: 15 VIA regs, 8 RBV regs */
+
+#define vBufB 0x0000 /* [VIA/RBV] Register B */
+#define vBufAH 0x0200 /* [VIA only] Buffer A, with handshake. DON'T USE! */
+#define vDirB 0x0400 /* [VIA only] Data Direction Register B. */
+#define vDirA 0x0600 /* [VIA only] Data Direction Register A. */
+#define vT1CL 0x0800 /* [VIA only] Timer one counter low. */
+#define vT1CH 0x0a00 /* [VIA only] Timer one counter high. */
+#define vT1LL 0x0c00 /* [VIA only] Timer one latches low. */
+#define vT1LH 0x0e00 /* [VIA only] Timer one latches high. */
+#define vT2CL 0x1000 /* [VIA only] Timer two counter low. */
+#define vT2CH 0x1200 /* [VIA only] Timer two counter high. */
+#define vSR 0x1400 /* [VIA only] Shift register. */
+#define vACR 0x1600 /* [VIA only] Auxilary control register. */
+#define vPCR 0x1800 /* [VIA only] Peripheral control register. */
+ /*
+ * CHRP sez never ever to *write* this.
+ * Mac family says never to *change* this.
+ * In fact we need to initialize it once at start.
+ */
+#define vIFR 0x1a00 /* [VIA/RBV] Interrupt flag register. */
+#define vIER 0x1c00 /* [VIA/RBV] Interrupt enable register. */
+#define vBufA 0x1e00 /* [VIA/RBV] register A (no handshake) */
+
+/* from linux 2.6 drivers/macintosh/via-macii.c */
+
+/* Bits in ACR */
+
+#define VIA1ACR_vShiftCtrl 0x1c /* Shift register control bits */
+#define VIA1ACR_vShiftExtClk 0x0c /* Shift on external clock */
+#define VIA1ACR_vShiftOut 0x10 /* Shift out if 1 */
+
+/*
+ * Apple Macintosh Family Hardware Refenece
+ * Table 19-10 ADB transaction states
+ */
+
+#define ADB_STATE_NEW 0
+#define ADB_STATE_EVEN 1
+#define ADB_STATE_ODD 2
+#define ADB_STATE_IDLE 3
+
+#define VIA1B_vADB_StateMask (VIA1B_vADBS1 | VIA1B_vADBS2)
+#define VIA1B_vADB_StateShift 4
+
+#define VIA_TIMER_FREQ (783360)
+#define VIA_ADB_POLL_FREQ 50 /* XXX: not real */
+
+/* VIA returns time offset from Jan 1, 1904, not 1970 */
+#define RTC_OFFSET 2082844800
+
+static void via1_VBL_update(MOS6522Q800VIA1State *v1s)
+{
+ MOS6522State *s = MOS6522(v1s);
+
+ /* 60 Hz irq */
+ v1s->next_VBL = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 16630) /
+ 16630 * 16630;
+
+ if (s->ier & VIA1_IRQ_VBLANK) {
+ timer_mod(v1s->VBL_timer, v1s->next_VBL);
+ } else {
+ timer_del(v1s->VBL_timer);
+ }
+}
+
+static void via1_one_second_update(MOS6522Q800VIA1State *v1s)
+{
+ MOS6522State *s = MOS6522(v1s);
+
+ v1s->next_second = (qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000) /
+ 1000 * 1000;
+ if (s->ier & VIA1_IRQ_ONE_SECOND) {
+ timer_mod(v1s->one_second_timer, v1s->next_second);
+ } else {
+ timer_del(v1s->one_second_timer);
+ }
+}
+
+static void via1_VBL(void *opaque)
+{
+ MOS6522Q800VIA1State *v1s = opaque;
+ MOS6522State *s = MOS6522(v1s);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(s);
+
+ s->ifr |= VIA1_IRQ_VBLANK;
+ mdc->update_irq(s);
+
+ via1_VBL_update(v1s);
+}
+
+static void via1_one_second(void *opaque)
+{
+ MOS6522Q800VIA1State *v1s = opaque;
+ MOS6522State *s = MOS6522(v1s);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(s);
+
+ s->ifr |= VIA1_IRQ_ONE_SECOND;
+ mdc->update_irq(s);
+
+ via1_one_second_update(v1s);
+}
+
+static void via1_irq_request(void *opaque, int irq, int level)
+{
+ MOS6522Q800VIA1State *v1s = opaque;
+ MOS6522State *s = MOS6522(v1s);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(s);
+
+ if (level) {
+ s->ifr |= 1 << irq;
+ } else {
+ s->ifr &= ~(1 << irq);
+ }
+
+ mdc->update_irq(s);
+}
+
+static void via2_irq_request(void *opaque, int irq, int level)
+{
+ MOS6522Q800VIA2State *v2s = opaque;
+ MOS6522State *s = MOS6522(v2s);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(s);
+
+ if (level) {
+ s->ifr |= 1 << irq;
+ } else {
+ s->ifr &= ~(1 << irq);
+ }
+
+ mdc->update_irq(s);
+}
+
+static void via1_rtc_update(MacVIAState *m)
+{
+ MOS6522Q800VIA1State *v1s = &m->mos6522_via1;
+ MOS6522State *s = MOS6522(v1s);
+
+ if (s->b & VIA1B_vRTCEnb) {
+ return;
+ }
+
+ if (s->dirb & VIA1B_vRTCData) {
+ /* send bits to the RTC */
+ if (!(v1s->last_b & VIA1B_vRTCClk) && (s->b & VIA1B_vRTCClk)) {
+ m->data_out <<= 1;
+ m->data_out |= s->b & VIA1B_vRTCData;
+ m->data_out_cnt++;
+ }
+ } else {
+ /* receive bits from the RTC */
+ if ((v1s->last_b & VIA1B_vRTCClk) &&
+ !(s->b & VIA1B_vRTCClk) &&
+ m->data_in_cnt) {
+ s->b = (s->b & ~VIA1B_vRTCData) |
+ ((m->data_in >> 7) & VIA1B_vRTCData);
+ m->data_in <<= 1;
+ m->data_in_cnt--;
+ }
+ }
+
+ if (m->data_out_cnt == 8) {
+ m->data_out_cnt = 0;
+
+ if (m->cmd == 0) {
+ if (m->data_out & 0x80) {
+ /* this is a read command */
+ uint32_t time = m->tick_offset +
+ (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) /
+ NANOSECONDS_PER_SECOND);
+ if (m->data_out == 0x81) { /* seconds register 0 */
+ m->data_in = time & 0xff;
+ m->data_in_cnt = 8;
+ } else if (m->data_out == 0x85) { /* seconds register 1 */
+ m->data_in = (time >> 8) & 0xff;
+ m->data_in_cnt = 8;
+ } else if (m->data_out == 0x89) { /* seconds register 2 */
+ m->data_in = (time >> 16) & 0xff;
+ m->data_in_cnt = 8;
+ } else if (m->data_out == 0x8d) { /* seconds register 3 */
+ m->data_in = (time >> 24) & 0xff;
+ m->data_in_cnt = 8;
+ } else if ((m->data_out & 0xf3) == 0xa1) {
+ /* PRAM address 0x10 -> 0x13 */
+ int addr = (m->data_out >> 2) & 0x03;
+ m->data_in = v1s->PRAM[addr];
+ m->data_in_cnt = 8;
+ } else if ((m->data_out & 0xf3) == 0xa1) {
+ /* PRAM address 0x00 -> 0x0f */
+ int addr = (m->data_out >> 2) & 0x0f;
+ m->data_in = v1s->PRAM[addr];
+ m->data_in_cnt = 8;
+ } else if ((m->data_out & 0xf8) == 0xb8) {
+ /* extended memory designator and sector number */
+ m->cmd = m->data_out;
+ }
+ } else {
+ /* this is a write command */
+ m->cmd = m->data_out;
+ }
+ } else {
+ if (m->cmd & 0x80) {
+ if ((m->cmd & 0xf8) == 0xb8) {
+ /* extended memory designator and sector number */
+ int sector = m->cmd & 0x07;
+ int addr = (m->data_out >> 2) & 0x1f;
+
+ m->data_in = v1s->PRAM[sector * 8 + addr];
+ m->data_in_cnt = 8;
+ }
+ } else if (!m->wprotect) {
+ /* this is a write command */
+ if (m->alt != 0) {
+ /* extended memory designator and sector number */
+ int sector = m->cmd & 0x07;
+ int addr = (m->alt >> 2) & 0x1f;
+
+ v1s->PRAM[sector * 8 + addr] = m->data_out;
+
+ m->alt = 0;
+ } else if (m->cmd == 0x01) { /* seconds register 0 */
+ /* FIXME */
+ } else if (m->cmd == 0x05) { /* seconds register 1 */
+ /* FIXME */
+ } else if (m->cmd == 0x09) { /* seconds register 2 */
+ /* FIXME */
+ } else if (m->cmd == 0x0d) { /* seconds register 3 */
+ /* FIXME */
+ } else if (m->cmd == 0x31) {
+ /* Test Register */
+ } else if (m->cmd == 0x35) {
+ /* Write Protect register */
+ m->wprotect = m->data_out & 1;
+ } else if ((m->cmd & 0xf3) == 0xa1) {
+ /* PRAM address 0x10 -> 0x13 */
+ int addr = (m->cmd >> 2) & 0x03;
+ v1s->PRAM[addr] = m->data_out;
+ } else if ((m->cmd & 0xf3) == 0xa1) {
+ /* PRAM address 0x00 -> 0x0f */
+ int addr = (m->cmd >> 2) & 0x0f;
+ v1s->PRAM[addr] = m->data_out;
+ } else if ((m->cmd & 0xf8) == 0xb8) {
+ /* extended memory designator and sector number */
+ m->alt = m->cmd;
+ }
+ }
+ }
+ m->data_out = 0;
+ }
+}
+
+static int adb_via_poll(MacVIAState *s, int state, uint8_t *data)
+{
+ if (state != ADB_STATE_IDLE) {
+ return 0;
+ }
+
+ if (s->adb_data_in_size < s->adb_data_in_index) {
+ return 0;
+ }
+
+ if (s->adb_data_out_index != 0) {
+ return 0;
+ }
+
+ s->adb_data_in_index = 0;
+ s->adb_data_out_index = 0;
+ s->adb_data_in_size = adb_poll(&s->adb_bus, s->adb_data_in, 0xffff);
+
+ if (s->adb_data_in_size) {
+ *data = s->adb_data_in[s->adb_data_in_index++];
+ qemu_irq_raise(s->adb_data_ready);
+ }
+
+ return s->adb_data_in_size;
+}
+
+static int adb_via_send(MacVIAState *s, int state, uint8_t data)
+{
+ switch (state) {
+ case ADB_STATE_NEW:
+ s->adb_data_out_index = 0;
+ break;
+ case ADB_STATE_EVEN:
+ if ((s->adb_data_out_index & 1) == 0) {
+ return 0;
+ }
+ break;
+ case ADB_STATE_ODD:
+ if (s->adb_data_out_index & 1) {
+ return 0;
+ }
+ break;
+ case ADB_STATE_IDLE:
+ return 0;
+ }
+
+ assert(s->adb_data_out_index < sizeof(s->adb_data_out) - 1);
+
+ s->adb_data_out[s->adb_data_out_index++] = data;
+ qemu_irq_raise(s->adb_data_ready);
+ return 1;
+}
+
+static int adb_via_receive(MacVIAState *s, int state, uint8_t *data)
+{
+ switch (state) {
+ case ADB_STATE_NEW:
+ return 0;
+
+ case ADB_STATE_EVEN:
+ if (s->adb_data_in_size <= 0) {
+ qemu_irq_raise(s->adb_data_ready);
+ return 0;
+ }
+
+ if (s->adb_data_in_index >= s->adb_data_in_size) {
+ *data = 0;
+ qemu_irq_raise(s->adb_data_ready);
+ return 1;
+ }
+
+ if ((s->adb_data_in_index & 1) == 0) {
+ return 0;
+ }
+
+ break;
+
+ case ADB_STATE_ODD:
+ if (s->adb_data_in_size <= 0) {
+ qemu_irq_raise(s->adb_data_ready);
+ return 0;
+ }
+
+ if (s->adb_data_in_index >= s->adb_data_in_size) {
+ *data = 0;
+ qemu_irq_raise(s->adb_data_ready);
+ return 1;
+ }
+
+ if (s->adb_data_in_index & 1) {
+ return 0;
+ }
+
+ break;
+
+ case ADB_STATE_IDLE:
+ if (s->adb_data_out_index == 0) {
+ return 0;
+ }
+
+ s->adb_data_in_size = adb_request(&s->adb_bus, s->adb_data_in,
+ s->adb_data_out,
+ s->adb_data_out_index);
+ s->adb_data_out_index = 0;
+ s->adb_data_in_index = 0;
+ if (s->adb_data_in_size < 0) {
+ *data = 0xff;
+ qemu_irq_raise(s->adb_data_ready);
+ return -1;
+ }
+
+ if (s->adb_data_in_size == 0) {
+ return 0;
+ }
+
+ break;
+ }
+
+ assert(s->adb_data_in_index < sizeof(s->adb_data_in) - 1);
+
+ *data = s->adb_data_in[s->adb_data_in_index++];
+ qemu_irq_raise(s->adb_data_ready);
+ if (*data == 0xff || *data == 0) {
+ return 0;
+ }
+ return 1;
+}
+
+static void via1_adb_update(MacVIAState *m)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(&m->mos6522_via1);
+ MOS6522State *s = MOS6522(v1s);
+ int state;
+ int ret;
+
+ state = (s->b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift;
+
+ if (s->acr & VIA1ACR_vShiftOut) {
+ /* output mode */
+ ret = adb_via_send(m, state, s->sr);
+ if (ret > 0) {
+ s->b &= ~VIA1B_vADBInt;
+ } else {
+ s->b |= VIA1B_vADBInt;
+ }
+ } else {
+ /* input mode */
+ ret = adb_via_receive(m, state, &s->sr);
+ if (ret > 0 && s->sr != 0xff) {
+ s->b &= ~VIA1B_vADBInt;
+ } else {
+ s->b |= VIA1B_vADBInt;
+ }
+ }
+}
+
+static void via_adb_poll(void *opaque)
+{
+ MacVIAState *m = opaque;
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(&m->mos6522_via1);
+ MOS6522State *s = MOS6522(v1s);
+ int state;
+
+ if (s->b & VIA1B_vADBInt) {
+ state = (s->b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift;
+ if (adb_via_poll(m, state, &s->sr)) {
+ s->b &= ~VIA1B_vADBInt;
+ }
+ }
+
+ timer_mod(m->adb_poll_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (NANOSECONDS_PER_SECOND / VIA_ADB_POLL_FREQ));
+}
+
+static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque);
+ MOS6522State *ms = MOS6522(s);
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+
+ /*
+ * If IRQs are disabled, timers are disabled, but we need to update
+ * VIA1_IRQ_VBLANK and VIA1_IRQ_ONE_SECOND bits in the IFR
+ */
+
+ if (now >= s->next_VBL) {
+ ms->ifr |= VIA1_IRQ_VBLANK;
+ via1_VBL_update(s);
+ }
+ if (now >= s->next_second) {
+ ms->ifr |= VIA1_IRQ_ONE_SECOND;
+ via1_one_second_update(s);
+ }
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque);
+ MOS6522State *ms = MOS6522(v1s);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+
+ via1_one_second_update(v1s);
+ via1_VBL_update(v1s);
+}
+
+static const MemoryRegionOps mos6522_q800_via1_ops = {
+ .read = mos6522_q800_via1_read,
+ .write = mos6522_q800_via1_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static uint64_t mos6522_q800_via2_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MOS6522Q800VIA2State *s = MOS6522_Q800_VIA2(opaque);
+ MOS6522State *ms = MOS6522(s);
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_q800_via2_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ MOS6522Q800VIA2State *s = MOS6522_Q800_VIA2(opaque);
+ MOS6522State *ms = MOS6522(s);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+}
+
+static const MemoryRegionOps mos6522_q800_via2_ops = {
+ .read = mos6522_q800_via2_read,
+ .write = mos6522_q800_via2_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void mac_via_reset(DeviceState *dev)
+{
+ MacVIAState *m = MAC_VIA(dev);
+ MOS6522Q800VIA1State *v1s = &m->mos6522_via1;
+
+ timer_mod(m->adb_poll_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (NANOSECONDS_PER_SECOND / VIA_ADB_POLL_FREQ));
+
+ timer_del(v1s->VBL_timer);
+ v1s->next_VBL = 0;
+ timer_del(v1s->one_second_timer);
+ v1s->next_second = 0;
+}
+
+static void mac_via_realize(DeviceState *dev, Error **errp)
+{
+ MacVIAState *m = MAC_VIA(dev);
+ MOS6522State *ms;
+ struct tm tm;
+
+ /* Init VIAs 1 and 2 */
+ sysbus_init_child_obj(OBJECT(dev), "via1", &m->mos6522_via1,
+ sizeof(m->mos6522_via1), TYPE_MOS6522_Q800_VIA1);
+
+ sysbus_init_child_obj(OBJECT(dev), "via2", &m->mos6522_via2,
+ sizeof(m->mos6522_via2), TYPE_MOS6522_Q800_VIA2);
+
+ /* Pass through mos6522 output IRQs */
+ ms = MOS6522(&m->mos6522_via1);
+ object_property_add_alias(OBJECT(dev), "irq[0]", OBJECT(ms),
+ SYSBUS_DEVICE_GPIO_IRQ "[0]", &error_abort);
+ ms = MOS6522(&m->mos6522_via2);
+ object_property_add_alias(OBJECT(dev), "irq[1]", OBJECT(ms),
+ SYSBUS_DEVICE_GPIO_IRQ "[0]", &error_abort);
+
+ /* Pass through mos6522 input IRQs */
+ qdev_pass_gpios(DEVICE(&m->mos6522_via1), dev, "via1-irq");
+ qdev_pass_gpios(DEVICE(&m->mos6522_via2), dev, "via2-irq");
+
+ /* VIA 1 */
+ m->mos6522_via1.one_second_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ via1_one_second,
+ &m->mos6522_via1);
+ m->mos6522_via1.VBL_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, via1_VBL,
+ &m->mos6522_via1);
+
+ qemu_get_timedate(&tm, 0);
+ m->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET;
+
+ m->adb_poll_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, via_adb_poll, m);
+ m->adb_data_ready = qdev_get_gpio_in_named(dev, "via1-irq",
+ VIA1_IRQ_ADB_READY_BIT);
+}
+
+static void mac_via_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MacVIAState *m = MAC_VIA(obj);
+
+ /* MMIO */
+ memory_region_init(&m->mmio, obj, "mac-via", 2 * VIA_SIZE);
+ sysbus_init_mmio(sbd, &m->mmio);
+
+ memory_region_init_io(&m->via1mem, obj, &mos6522_q800_via1_ops,
+ &m->mos6522_via1, "via1", VIA_SIZE);
+ memory_region_add_subregion(&m->mmio, 0x0, &m->via1mem);
+
+ memory_region_init_io(&m->via2mem, obj, &mos6522_q800_via2_ops,
+ &m->mos6522_via2, "via2", VIA_SIZE);
+ memory_region_add_subregion(&m->mmio, VIA_SIZE, &m->via2mem);
+
+ /* ADB */
+ qbus_create_inplace((BusState *)&m->adb_bus, sizeof(m->adb_bus),
+ TYPE_ADB_BUS, DEVICE(obj), "adb.0");
+}
+
+static const VMStateDescription vmstate_mac_via = {
+ .name = "mac-via",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ /* VIAs */
+ VMSTATE_STRUCT(mos6522_via1.parent_obj, MacVIAState, 0, vmstate_mos6522,
+ MOS6522State),
+ VMSTATE_UINT8(mos6522_via1.last_b, MacVIAState),
+ VMSTATE_BUFFER(mos6522_via1.PRAM, MacVIAState),
+ VMSTATE_TIMER_PTR(mos6522_via1.one_second_timer, MacVIAState),
+ VMSTATE_INT64(mos6522_via1.next_second, MacVIAState),
+ VMSTATE_TIMER_PTR(mos6522_via1.VBL_timer, MacVIAState),
+ VMSTATE_INT64(mos6522_via1.next_VBL, MacVIAState),
+ VMSTATE_STRUCT(mos6522_via2.parent_obj, MacVIAState, 0, vmstate_mos6522,
+ MOS6522State),
+ /* RTC */
+ VMSTATE_UINT32(tick_offset, MacVIAState),
+ VMSTATE_UINT8(data_out, MacVIAState),
+ VMSTATE_INT32(data_out_cnt, MacVIAState),
+ VMSTATE_UINT8(data_in, MacVIAState),
+ VMSTATE_UINT8(data_in_cnt, MacVIAState),
+ VMSTATE_UINT8(cmd, MacVIAState),
+ VMSTATE_INT32(wprotect, MacVIAState),
+ VMSTATE_INT32(alt, MacVIAState),
+ /* ADB */
+ VMSTATE_TIMER_PTR(adb_poll_timer, MacVIAState),
+ VMSTATE_INT32(adb_data_in_size, MacVIAState),
+ VMSTATE_INT32(adb_data_in_index, MacVIAState),
+ VMSTATE_INT32(adb_data_out_index, MacVIAState),
+ VMSTATE_BUFFER(adb_data_in, MacVIAState),
+ VMSTATE_BUFFER(adb_data_out, MacVIAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mac_via_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = mac_via_realize;
+ dc->reset = mac_via_reset;
+ dc->vmsd = &vmstate_mac_via;
+}
+
+static TypeInfo mac_via_info = {
+ .name = TYPE_MAC_VIA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MacVIAState),
+ .instance_init = mac_via_init,
+ .class_init = mac_via_class_init,
+};
+
+/* VIA 1 */
+static void mos6522_q800_via1_portB_write(MOS6522State *s)
+{
+ MOS6522Q800VIA1State *v1s = container_of(s, MOS6522Q800VIA1State,
+ parent_obj);
+ MacVIAState *m = container_of(v1s, MacVIAState, mos6522_via1);
+
+ via1_rtc_update(m);
+ via1_adb_update(m);
+
+ v1s->last_b = s->b;
+}
+
+static void mos6522_q800_via1_reset(DeviceState *dev)
+{
+ MOS6522State *ms = MOS6522(dev);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(ms);
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = VIA_TIMER_FREQ;
+ ms->timers[1].frequency = VIA_TIMER_FREQ;
+
+ ms->b = VIA1B_vADB_StateMask | VIA1B_vADBInt | VIA1B_vRTCEnb;
+}
+
+static void mos6522_q800_via1_init(Object *obj)
+{
+ qdev_init_gpio_in_named(DEVICE(obj), via1_irq_request, "via1-irq",
+ VIA1_IRQ_NB);
+}
+
+static void mos6522_q800_via1_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_CLASS(oc);
+
+ dc->reset = mos6522_q800_via1_reset;
+ mdc->portB_write = mos6522_q800_via1_portB_write;
+}
+
+static const TypeInfo mos6522_q800_via1_type_info = {
+ .name = TYPE_MOS6522_Q800_VIA1,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522Q800VIA1State),
+ .instance_init = mos6522_q800_via1_init,
+ .class_init = mos6522_q800_via1_class_init,
+};
+
+/* VIA 2 */
+static void mos6522_q800_via2_portB_write(MOS6522State *s)
+{
+ if (s->dirb & VIA2B_vPower && (s->b & VIA2B_vPower) == 0) {
+ /* shutdown */
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ }
+}
+
+static void mos6522_q800_via2_reset(DeviceState *dev)
+{
+ MOS6522State *ms = MOS6522(dev);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(ms);
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = VIA_TIMER_FREQ;
+ ms->timers[1].frequency = VIA_TIMER_FREQ;
+
+ ms->dirb = 0;
+ ms->b = 0;
+}
+
+static void mos6522_q800_via2_init(Object *obj)
+{
+ qdev_init_gpio_in_named(DEVICE(obj), via2_irq_request, "via2-irq",
+ VIA2_IRQ_NB);
+}
+
+static void mos6522_q800_via2_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_DEVICE_CLASS(oc);
+
+ dc->reset = mos6522_q800_via2_reset;
+ mdc->portB_write = mos6522_q800_via2_portB_write;
+}
+
+static const TypeInfo mos6522_q800_via2_type_info = {
+ .name = TYPE_MOS6522_Q800_VIA2,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522Q800VIA2State),
+ .instance_init = mos6522_q800_via2_init,
+ .class_init = mos6522_q800_via2_class_init,
+};
+
+static void mac_via_register_types(void)
+{
+ type_register_static(&mos6522_q800_via1_type_info);
+ type_register_static(&mos6522_q800_via2_type_info);
+ type_register_static(&mac_via_info);
+}
+
+type_init(mac_via_register_types);
diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index 4ef86dc3a5..3856417d42 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -24,6 +24,11 @@ config PCNET_PCI
config PCNET_COMMON
bool
+config TULIP
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+
config E1000_PCI
bool
default y if PCI_DEVICES
diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs
index 9904273b06..7907d2c199 100644
--- a/hw/net/Makefile.objs
+++ b/hw/net/Makefile.objs
@@ -13,6 +13,7 @@ common-obj-$(CONFIG_E1000E_PCI_EXPRESS) += e1000e.o e1000e_core.o e1000x_common.
common-obj-$(CONFIG_RTL8139_PCI) += rtl8139.o
common-obj-$(CONFIG_VMXNET3_PCI) += net_tx_pkt.o net_rx_pkt.o
common-obj-$(CONFIG_VMXNET3_PCI) += vmxnet3.o
+common-obj-$(CONFIG_TULIP) += tulip.o
common-obj-$(CONFIG_SMC91C111) += smc91c111.o
common-obj-$(CONFIG_LAN9118) += lan9118.o
diff --git a/hw/net/dp8393x.c b/hw/net/dp8393x.c
index a5678e11fa..693e244ce6 100644
--- a/hw/net/dp8393x.c
+++ b/hw/net/dp8393x.c
@@ -153,6 +153,7 @@ typedef struct dp8393xState {
/* Hardware */
uint8_t it_shift;
+ bool big_endian;
qemu_irq irq;
#ifdef DEBUG_SONIC
int irq_level;
@@ -223,6 +224,29 @@ static uint32_t dp8393x_wt(dp8393xState *s)
return s->regs[SONIC_WT1] << 16 | s->regs[SONIC_WT0];
}
+static uint16_t dp8393x_get(dp8393xState *s, int width, uint16_t *base,
+ int offset)
+{
+ uint16_t val;
+
+ if (s->big_endian) {
+ val = be16_to_cpu(base[offset * width + width - 1]);
+ } else {
+ val = le16_to_cpu(base[offset * width]);
+ }
+ return val;
+}
+
+static void dp8393x_put(dp8393xState *s, int width, uint16_t *base, int offset,
+ uint16_t val)
+{
+ if (s->big_endian) {
+ base[offset * width + width - 1] = cpu_to_be16(val);
+ } else {
+ base[offset * width] = cpu_to_le16(val);
+ }
+}
+
static void dp8393x_update_irq(dp8393xState *s)
{
int level = (s->regs[SONIC_IMR] & s->regs[SONIC_ISR]) ? 1 : 0;
@@ -254,12 +278,12 @@ static void dp8393x_do_load_cam(dp8393xState *s)
/* Fill current entry */
address_space_rw(&s->as, dp8393x_cdp(s),
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
- s->cam[index][0] = data[1 * width] & 0xff;
- s->cam[index][1] = data[1 * width] >> 8;
- s->cam[index][2] = data[2 * width] & 0xff;
- s->cam[index][3] = data[2 * width] >> 8;
- s->cam[index][4] = data[3 * width] & 0xff;
- s->cam[index][5] = data[3 * width] >> 8;
+ s->cam[index][0] = dp8393x_get(s, width, data, 1) & 0xff;
+ s->cam[index][1] = dp8393x_get(s, width, data, 1) >> 8;
+ s->cam[index][2] = dp8393x_get(s, width, data, 2) & 0xff;
+ s->cam[index][3] = dp8393x_get(s, width, data, 2) >> 8;
+ s->cam[index][4] = dp8393x_get(s, width, data, 3) & 0xff;
+ s->cam[index][5] = dp8393x_get(s, width, data, 3) >> 8;
DPRINTF("load cam[%d] with %02x%02x%02x%02x%02x%02x\n", index,
s->cam[index][0], s->cam[index][1], s->cam[index][2],
s->cam[index][3], s->cam[index][4], s->cam[index][5]);
@@ -272,7 +296,7 @@ static void dp8393x_do_load_cam(dp8393xState *s)
/* Read CAM enable */
address_space_rw(&s->as, dp8393x_cdp(s),
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
- s->regs[SONIC_CE] = data[0 * width];
+ s->regs[SONIC_CE] = dp8393x_get(s, width, data, 0);
DPRINTF("load cam done. cam enable mask 0x%04x\n", s->regs[SONIC_CE]);
/* Done */
@@ -293,10 +317,10 @@ static void dp8393x_do_read_rra(dp8393xState *s)
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
/* Update SONIC registers */
- s->regs[SONIC_CRBA0] = data[0 * width];
- s->regs[SONIC_CRBA1] = data[1 * width];
- s->regs[SONIC_RBWC0] = data[2 * width];
- s->regs[SONIC_RBWC1] = data[3 * width];
+ s->regs[SONIC_CRBA0] = dp8393x_get(s, width, data, 0);
+ s->regs[SONIC_CRBA1] = dp8393x_get(s, width, data, 1);
+ s->regs[SONIC_RBWC0] = dp8393x_get(s, width, data, 2);
+ s->regs[SONIC_RBWC1] = dp8393x_get(s, width, data, 3);
DPRINTF("CRBA0/1: 0x%04x/0x%04x, RBWC0/1: 0x%04x/0x%04x\n",
s->regs[SONIC_CRBA0], s->regs[SONIC_CRBA1],
s->regs[SONIC_RBWC0], s->regs[SONIC_RBWC1]);
@@ -411,12 +435,12 @@ static void dp8393x_do_transmit_packets(dp8393xState *s)
tx_len = 0;
/* Update registers */
- s->regs[SONIC_TCR] = data[0 * width] & 0xf000;
- s->regs[SONIC_TPS] = data[1 * width];
- s->regs[SONIC_TFC] = data[2 * width];
- s->regs[SONIC_TSA0] = data[3 * width];
- s->regs[SONIC_TSA1] = data[4 * width];
- s->regs[SONIC_TFS] = data[5 * width];
+ s->regs[SONIC_TCR] = dp8393x_get(s, width, data, 0) & 0xf000;
+ s->regs[SONIC_TPS] = dp8393x_get(s, width, data, 1);
+ s->regs[SONIC_TFC] = dp8393x_get(s, width, data, 2);
+ s->regs[SONIC_TSA0] = dp8393x_get(s, width, data, 3);
+ s->regs[SONIC_TSA1] = dp8393x_get(s, width, data, 4);
+ s->regs[SONIC_TFS] = dp8393x_get(s, width, data, 5);
/* Handle programmable interrupt */
if (s->regs[SONIC_TCR] & SONIC_TCR_PINT) {
@@ -442,9 +466,9 @@ static void dp8393x_do_transmit_packets(dp8393xState *s)
address_space_rw(&s->as,
dp8393x_ttda(s) + sizeof(uint16_t) * (4 + 3 * i) * width,
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
- s->regs[SONIC_TSA0] = data[0 * width];
- s->regs[SONIC_TSA1] = data[1 * width];
- s->regs[SONIC_TFS] = data[2 * width];
+ s->regs[SONIC_TSA0] = dp8393x_get(s, width, data, 0);
+ s->regs[SONIC_TSA1] = dp8393x_get(s, width, data, 1);
+ s->regs[SONIC_TFS] = dp8393x_get(s, width, data, 2);
}
}
@@ -471,7 +495,8 @@ static void dp8393x_do_transmit_packets(dp8393xState *s)
s->regs[SONIC_TCR] |= SONIC_TCR_PTX;
/* Write status */
- data[0 * width] = s->regs[SONIC_TCR] & 0x0fff; /* status */
+ dp8393x_put(s, width, data, 0,
+ s->regs[SONIC_TCR] & 0x0fff); /* status */
size = sizeof(uint16_t) * width;
address_space_rw(&s->as,
dp8393x_ttda(s),
@@ -485,8 +510,8 @@ static void dp8393x_do_transmit_packets(dp8393xState *s)
sizeof(uint16_t) *
(4 + 3 * s->regs[SONIC_TFC]) * width,
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
- s->regs[SONIC_CTDA] = data[0 * width] & ~0x1;
- if (data[0 * width] & 0x1) {
+ s->regs[SONIC_CTDA] = dp8393x_get(s, width, data, 0) & ~0x1;
+ if (dp8393x_get(s, width, data, 0) & 0x1) {
/* EOL detected */
break;
}
@@ -749,7 +774,7 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
address = dp8393x_crda(s) + sizeof(uint16_t) * 5 * width;
address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
(uint8_t *)data, size, 0);
- if (data[0 * width] & 0x1) {
+ if (dp8393x_get(s, width, data, 0) & 0x1) {
/* Still EOL ; stop reception */
return -1;
} else {
@@ -793,11 +818,11 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
/* Write status to memory */
DPRINTF("Write status at %08x\n", dp8393x_crda(s));
- data[0 * width] = s->regs[SONIC_RCR]; /* status */
- data[1 * width] = rx_len; /* byte count */
- data[2 * width] = s->regs[SONIC_TRBA0]; /* pkt_ptr0 */
- data[3 * width] = s->regs[SONIC_TRBA1]; /* pkt_ptr1 */
- data[4 * width] = s->regs[SONIC_RSC]; /* seq_no */
+ dp8393x_put(s, width, data, 0, s->regs[SONIC_RCR]); /* status */
+ dp8393x_put(s, width, data, 1, rx_len); /* byte count */
+ dp8393x_put(s, width, data, 2, s->regs[SONIC_TRBA0]); /* pkt_ptr0 */
+ dp8393x_put(s, width, data, 3, s->regs[SONIC_TRBA1]); /* pkt_ptr1 */
+ dp8393x_put(s, width, data, 4, s->regs[SONIC_RSC]); /* seq_no */
size = sizeof(uint16_t) * 5 * width;
address_space_rw(&s->as, dp8393x_crda(s),
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 1);
@@ -806,12 +831,12 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
size = sizeof(uint16_t) * width;
address_space_rw(&s->as, dp8393x_crda(s) + sizeof(uint16_t) * 5 * width,
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, size, 0);
- s->regs[SONIC_LLFA] = data[0 * width];
+ s->regs[SONIC_LLFA] = dp8393x_get(s, width, data, 0);
if (s->regs[SONIC_LLFA] & 0x1) {
/* EOL detected */
s->regs[SONIC_ISR] |= SONIC_ISR_RDE;
} else {
- data[0 * width] = 0; /* in_use */
+ dp8393x_put(s, width, data, 0, 0); /* in_use */
address_space_rw(&s->as, dp8393x_crda(s) + sizeof(uint16_t) * 6 * width,
MEMTXATTRS_UNSPECIFIED, (uint8_t *)data, sizeof(uint16_t), 1);
s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
@@ -924,6 +949,7 @@ static Property dp8393x_properties[] = {
DEFINE_NIC_PROPERTIES(dp8393xState, conf),
DEFINE_PROP_PTR("dma_mr", dp8393xState, dma_mr),
DEFINE_PROP_UINT8("it_shift", dp8393xState, it_shift, 0),
+ DEFINE_PROP_BOOL("big_endian", dp8393xState, big_endian, false),
DEFINE_PROP_END_OF_LIST(),
};
diff --git a/hw/net/trace-events b/hw/net/trace-events
index 58665655cc..e70f12bee1 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -367,3 +367,17 @@ virtio_net_announce_notify(void) ""
virtio_net_announce_timer(int round) "%d"
virtio_net_handle_announce(int round) "%d"
virtio_net_post_load_device(void)
+
+# tulip.c
+tulip_reg_write(uint64_t addr, const char *name, int size, uint64_t val) "addr 0x%02"PRIx64" (%s) size %d value 0x%08"PRIx64
+tulip_reg_read(uint64_t addr, const char *name, int size, uint64_t val) "addr 0x%02"PRIx64" (%s) size %d value 0x%08"PRIx64
+tulip_receive(const uint8_t *buf, size_t len) "buf %p size %zu"
+tulip_descriptor(const char *prefix, uint32_t addr, uint32_t status, uint32_t control, uint32_t len1, uint32_t len2, uint32_t buf1, uint32_t buf2) "%s 0x%08x: status 0x%08x control 0x%03x len1 %4d len2 %4d buf1 0x%08x buf2 0x%08x"
+tulip_rx_state(const char *state) "RX %s"
+tulip_tx_state(const char *state) "TX %s"
+tulip_irq(uint32_t mask, uint32_t en, const char *state) "mask 0x%08x ie 0x%08x %s"
+tulip_mii_write(int phy, int reg, uint16_t data) "phy 0x%x reg 0x%x data 0x%04x"
+tulip_mii_read(int phy, int reg, uint16_t data) "phy 0x%x, reg 0x%x data 0x%04x"
+tulip_reset(void) ""
+tulip_setup_frame(void) ""
+tulip_setup_filter(int n, uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) "%d: %02x:%02x:%02x:%02x:%02x:%02x"
diff --git a/hw/net/tulip.c b/hw/net/tulip.c
new file mode 100644
index 0000000000..f85f54341f
--- /dev/null
+++ b/hw/net/tulip.c
@@ -0,0 +1,1029 @@
+/*
+ * QEMU TULIP Emulation
+ *
+ * Copyright (c) 2019 Sven Schnelle <svens@stackframe.org>
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/nvram/eeprom93xx.h"
+#include "migration/vmstate.h"
+#include "sysemu/sysemu.h"
+#include "tulip.h"
+#include "trace.h"
+#include "net/eth.h"
+
+typedef struct TULIPState {
+ PCIDevice dev;
+ MemoryRegion io;
+ MemoryRegion memory;
+ NICConf c;
+ qemu_irq irq;
+ NICState *nic;
+ eeprom_t *eeprom;
+ uint32_t csr[16];
+
+ /* state for MII */
+ uint32_t old_csr9;
+ uint32_t mii_word;
+ uint32_t mii_bitcnt;
+
+ hwaddr current_rx_desc;
+ hwaddr current_tx_desc;
+
+ uint8_t rx_frame[2048];
+ uint8_t tx_frame[2048];
+ uint16_t tx_frame_len;
+ uint16_t rx_frame_len;
+ uint16_t rx_frame_size;
+
+ uint32_t rx_status;
+ uint8_t filter[16][6];
+} TULIPState;
+
+static const VMStateDescription vmstate_pci_tulip = {
+ .name = "tulip",
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, TULIPState),
+ VMSTATE_UINT32_ARRAY(csr, TULIPState, 16),
+ VMSTATE_UINT32(old_csr9, TULIPState),
+ VMSTATE_UINT32(mii_word, TULIPState),
+ VMSTATE_UINT32(mii_bitcnt, TULIPState),
+ VMSTATE_UINT64(current_rx_desc, TULIPState),
+ VMSTATE_UINT64(current_tx_desc, TULIPState),
+ VMSTATE_BUFFER(rx_frame, TULIPState),
+ VMSTATE_BUFFER(tx_frame, TULIPState),
+ VMSTATE_UINT16(rx_frame_len, TULIPState),
+ VMSTATE_UINT16(tx_frame_len, TULIPState),
+ VMSTATE_UINT16(rx_frame_size, TULIPState),
+ VMSTATE_UINT32(rx_status, TULIPState),
+ VMSTATE_UINT8_2DARRAY(filter, TULIPState, 16, 6),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tulip_desc_read(TULIPState *s, hwaddr p,
+ struct tulip_descriptor *desc)
+{
+ if (s->csr[0] & CSR0_DBO) {
+ desc->status = ldl_be_pci_dma(&s->dev, p);
+ desc->control = ldl_be_pci_dma(&s->dev, p + 4);
+ desc->buf_addr1 = ldl_be_pci_dma(&s->dev, p + 8);
+ desc->buf_addr2 = ldl_be_pci_dma(&s->dev, p + 12);
+ } else {
+ desc->status = ldl_le_pci_dma(&s->dev, p);
+ desc->control = ldl_le_pci_dma(&s->dev, p + 4);
+ desc->buf_addr1 = ldl_le_pci_dma(&s->dev, p + 8);
+ desc->buf_addr2 = ldl_le_pci_dma(&s->dev, p + 12);
+ }
+}
+
+static void tulip_desc_write(TULIPState *s, hwaddr p,
+ struct tulip_descriptor *desc)
+{
+ if (s->csr[0] & CSR0_DBO) {
+ stl_be_pci_dma(&s->dev, p, desc->status);
+ stl_be_pci_dma(&s->dev, p + 4, desc->control);
+ stl_be_pci_dma(&s->dev, p + 8, desc->buf_addr1);
+ stl_be_pci_dma(&s->dev, p + 12, desc->buf_addr2);
+ } else {
+ stl_le_pci_dma(&s->dev, p, desc->status);
+ stl_le_pci_dma(&s->dev, p + 4, desc->control);
+ stl_le_pci_dma(&s->dev, p + 8, desc->buf_addr1);
+ stl_le_pci_dma(&s->dev, p + 12, desc->buf_addr2);
+ }
+}
+
+static void tulip_update_int(TULIPState *s)
+{
+ uint32_t ie = s->csr[5] & s->csr[7];
+ bool assert = false;
+
+ s->csr[5] &= ~(CSR5_AIS | CSR5_NIS);
+
+ if (ie & (CSR5_TI | CSR5_TU | CSR5_RI | CSR5_GTE | CSR5_ERI)) {
+ s->csr[5] |= CSR5_NIS;
+ }
+
+ if (ie & (CSR5_LC | CSR5_GPI | CSR5_FBE | CSR5_LNF | CSR5_ETI | CSR5_RWT |
+ CSR5_RPS | CSR5_RU | CSR5_UNF | CSR5_LNP_ANC | CSR5_TJT |
+ CSR5_TPS)) {
+ s->csr[5] |= CSR5_AIS;
+ }
+
+ assert = s->csr[5] & s->csr[7] & (CSR5_AIS | CSR5_NIS);
+ trace_tulip_irq(s->csr[5], s->csr[7], assert ? "assert" : "deassert");
+ qemu_set_irq(s->irq, assert);
+}
+
+static bool tulip_rx_stopped(TULIPState *s)
+{
+ return ((s->csr[5] >> CSR5_RS_SHIFT) & CSR5_RS_MASK) == CSR5_RS_STOPPED;
+}
+
+static void tulip_dump_tx_descriptor(TULIPState *s,
+ struct tulip_descriptor *desc)
+{
+ trace_tulip_descriptor("TX ", s->current_tx_desc,
+ desc->status, desc->control >> 22,
+ desc->control & 0x7ff, (desc->control >> 11) & 0x7ff,
+ desc->buf_addr1, desc->buf_addr2);
+}
+
+static void tulip_dump_rx_descriptor(TULIPState *s,
+ struct tulip_descriptor *desc)
+{
+ trace_tulip_descriptor("RX ", s->current_rx_desc,
+ desc->status, desc->control >> 22,
+ desc->control & 0x7ff, (desc->control >> 11) & 0x7ff,
+ desc->buf_addr1, desc->buf_addr2);
+}
+
+static void tulip_next_rx_descriptor(TULIPState *s,
+ struct tulip_descriptor *desc)
+{
+ if (desc->control & RDES1_RER) {
+ s->current_rx_desc = s->csr[3];
+ } else if (desc->control & RDES1_RCH) {
+ s->current_rx_desc = desc->buf_addr2;
+ } else {
+ s->current_rx_desc += sizeof(struct tulip_descriptor) +
+ (((s->csr[0] >> CSR0_DSL_SHIFT) & CSR0_DSL_MASK) << 2);
+ }
+ s->current_rx_desc &= ~3ULL;
+}
+
+static void tulip_copy_rx_bytes(TULIPState *s, struct tulip_descriptor *desc)
+{
+ int len1 = (desc->control >> RDES1_BUF1_SIZE_SHIFT) & RDES1_BUF1_SIZE_MASK;
+ int len2 = (desc->control >> RDES1_BUF2_SIZE_SHIFT) & RDES1_BUF2_SIZE_MASK;
+ int len;
+
+ if (s->rx_frame_len && len1) {
+ if (s->rx_frame_len > len1) {
+ len = len1;
+ } else {
+ len = s->rx_frame_len;
+ }
+ pci_dma_write(&s->dev, desc->buf_addr1, s->rx_frame +
+ (s->rx_frame_size - s->rx_frame_len), len);
+ s->rx_frame_len -= len;
+ }
+
+ if (s->rx_frame_len && len2) {
+ if (s->rx_frame_len > len2) {
+ len = len2;
+ } else {
+ len = s->rx_frame_len;
+ }
+ pci_dma_write(&s->dev, desc->buf_addr2, s->rx_frame +
+ (s->rx_frame_size - s->rx_frame_len), len);
+ s->rx_frame_len -= len;
+ }
+}
+
+static bool tulip_filter_address(TULIPState *s, const uint8_t *addr)
+{
+ static const char broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ bool ret = false;
+ int i;
+
+ for (i = 0; i < 16 && ret == false; i++) {
+ if (!memcmp(&s->filter[i], addr, ETH_ALEN)) {
+ ret = true;
+ }
+ }
+
+ if (!memcmp(addr, broadcast, ETH_ALEN)) {
+ return true;
+ }
+
+ if (s->csr[6] & (CSR6_PR | CSR6_RA)) {
+ /* Promiscuous mode enabled */
+ s->rx_status |= RDES0_FF;
+ return true;
+ }
+
+ if ((s->csr[6] & CSR6_PM) && (addr[0] & 1)) {
+ /* Pass all Multicast enabled */
+ s->rx_status |= RDES0_MF;
+ return true;
+ }
+
+ if (s->csr[6] & CSR6_IF) {
+ ret ^= true;
+ }
+ return ret;
+}
+
+static ssize_t tulip_receive(TULIPState *s, const uint8_t *buf, size_t size)
+{
+ struct tulip_descriptor desc;
+
+ trace_tulip_receive(buf, size);
+
+ if (size < 14 || size > 2048 || s->rx_frame_len || tulip_rx_stopped(s)) {
+ return 0;
+ }
+
+ if (!tulip_filter_address(s, buf)) {
+ return size;
+ }
+
+ do {
+ tulip_desc_read(s, s->current_rx_desc, &desc);
+ tulip_dump_rx_descriptor(s, &desc);
+
+ if (!(desc.status & RDES0_OWN)) {
+ s->csr[5] |= CSR5_RU;
+ tulip_update_int(s);
+ return s->rx_frame_size - s->rx_frame_len;
+ }
+ desc.status = 0;
+
+ if (!s->rx_frame_len) {
+ s->rx_frame_size = size + 4;
+ s->rx_status = RDES0_LS |
+ ((s->rx_frame_size & RDES0_FL_MASK) << RDES0_FL_SHIFT);
+ desc.status |= RDES0_FS;
+ memcpy(s->rx_frame, buf, size);
+ s->rx_frame_len = s->rx_frame_size;
+ }
+
+ tulip_copy_rx_bytes(s, &desc);
+
+ if (!s->rx_frame_len) {
+ desc.status |= s->rx_status;
+ s->csr[5] |= CSR5_RI;
+ tulip_update_int(s);
+ }
+ tulip_dump_rx_descriptor(s, &desc);
+ tulip_desc_write(s, s->current_rx_desc, &desc);
+ tulip_next_rx_descriptor(s, &desc);
+ } while (s->rx_frame_len);
+ return size;
+}
+
+static ssize_t tulip_receive_nc(NetClientState *nc,
+ const uint8_t *buf, size_t size)
+{
+ return tulip_receive(qemu_get_nic_opaque(nc), buf, size);
+}
+
+
+static NetClientInfo net_tulip_info = {
+ .type = NET_CLIENT_DRIVER_NIC,
+ .size = sizeof(NICState),
+ .receive = tulip_receive_nc,
+};
+
+static const char *tulip_reg_name(const hwaddr addr)
+{
+ switch (addr) {
+ case CSR(0):
+ return "CSR0";
+
+ case CSR(1):
+ return "CSR1";
+
+ case CSR(2):
+ return "CSR2";
+
+ case CSR(3):
+ return "CSR3";
+
+ case CSR(4):
+ return "CSR4";
+
+ case CSR(5):
+ return "CSR5";
+
+ case CSR(6):
+ return "CSR6";
+
+ case CSR(7):
+ return "CSR7";
+
+ case CSR(8):
+ return "CSR8";
+
+ case CSR(9):
+ return "CSR9";
+
+ case CSR(10):
+ return "CSR10";
+
+ case CSR(11):
+ return "CSR11";
+
+ case CSR(12):
+ return "CSR12";
+
+ case CSR(13):
+ return "CSR13";
+
+ case CSR(14):
+ return "CSR14";
+
+ case CSR(15):
+ return "CSR15";
+
+ default:
+ break;
+ }
+ return "";
+}
+
+static const char *tulip_rx_state_name(int state)
+{
+ switch (state) {
+ case CSR5_RS_STOPPED:
+ return "STOPPED";
+
+ case CSR5_RS_RUNNING_FETCH:
+ return "RUNNING/FETCH";
+
+ case CSR5_RS_RUNNING_CHECK_EOR:
+ return "RUNNING/CHECK EOR";
+
+ case CSR5_RS_RUNNING_WAIT_RECEIVE:
+ return "WAIT RECEIVE";
+
+ case CSR5_RS_SUSPENDED:
+ return "SUSPENDED";
+
+ case CSR5_RS_RUNNING_CLOSE:
+ return "RUNNING/CLOSE";
+
+ case CSR5_RS_RUNNING_FLUSH:
+ return "RUNNING/FLUSH";
+
+ case CSR5_RS_RUNNING_QUEUE:
+ return "RUNNING/QUEUE";
+
+ default:
+ break;
+ }
+ return "";
+}
+
+static const char *tulip_tx_state_name(int state)
+{
+ switch (state) {
+ case CSR5_TS_STOPPED:
+ return "STOPPED";
+
+ case CSR5_TS_RUNNING_FETCH:
+ return "RUNNING/FETCH";
+
+ case CSR5_TS_RUNNING_WAIT_EOT:
+ return "RUNNING/WAIT EOT";
+
+ case CSR5_TS_RUNNING_READ_BUF:
+ return "RUNNING/READ BUF";
+
+ case CSR5_TS_RUNNING_SETUP:
+ return "RUNNING/SETUP";
+
+ case CSR5_TS_SUSPENDED:
+ return "SUSPENDED";
+
+ case CSR5_TS_RUNNING_CLOSE:
+ return "RUNNING/CLOSE";
+
+ default:
+ break;
+ }
+ return "";
+}
+
+static void tulip_update_rs(TULIPState *s, int state)
+{
+ s->csr[5] &= ~(CSR5_RS_MASK << CSR5_RS_SHIFT);
+ s->csr[5] |= (state & CSR5_RS_MASK) << CSR5_RS_SHIFT;
+ trace_tulip_rx_state(tulip_rx_state_name(state));
+}
+
+static uint16_t tulip_mdi_default[] = {
+ /* MDI Registers 0 - 6, 7 */
+ 0x3100, 0xf02c, 0x7810, 0x0000, 0x0501, 0x4181, 0x0000, 0x0000,
+ /* MDI Registers 8 - 15 */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* MDI Registers 16 - 31 */
+ 0x0003, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+};
+
+/* Readonly mask for MDI (PHY) registers */
+static const uint16_t tulip_mdi_mask[] = {
+ 0x0000, 0xffff, 0xffff, 0xffff, 0xc01f, 0xffff, 0xffff, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0fff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+};
+
+static uint16_t tulip_mii_read(TULIPState *s, int phy, int reg)
+{
+ uint16_t ret = 0;
+ if (phy == 1) {
+ ret = tulip_mdi_default[reg];
+ }
+ trace_tulip_mii_read(phy, reg, ret);
+ return ret;
+}
+
+static void tulip_mii_write(TULIPState *s, int phy, int reg, uint16_t data)
+{
+ trace_tulip_mii_write(phy, reg, data);
+
+ if (phy != 1) {
+ return;
+ }
+
+ tulip_mdi_default[reg] &= ~tulip_mdi_mask[reg];
+ tulip_mdi_default[reg] |= (data & tulip_mdi_mask[reg]);
+}
+
+static void tulip_mii(TULIPState *s)
+{
+ uint32_t changed = s->old_csr9 ^ s->csr[9];
+ uint16_t data;
+ int op, phy, reg;
+
+ if (!(changed & CSR9_MDC)) {
+ return;
+ }
+
+ if (!(s->csr[9] & CSR9_MDC)) {
+ return;
+ }
+
+ s->mii_bitcnt++;
+ s->mii_word <<= 1;
+
+ if (s->csr[9] & CSR9_MDO && (s->mii_bitcnt < 16 ||
+ !(s->csr[9] & CSR9_MII))) {
+ /* write op or address bits */
+ s->mii_word |= 1;
+ }
+
+ if (s->mii_bitcnt >= 16 && (s->csr[9] & CSR9_MII)) {
+ if (s->mii_word & 0x8000) {
+ s->csr[9] |= CSR9_MDI;
+ } else {
+ s->csr[9] &= ~CSR9_MDI;
+ }
+ }
+
+ if (s->mii_word == 0xffffffff) {
+ s->mii_bitcnt = 0;
+ } else if (s->mii_bitcnt == 16) {
+ op = (s->mii_word >> 12) & 0x0f;
+ phy = (s->mii_word >> 7) & 0x1f;
+ reg = (s->mii_word >> 2) & 0x1f;
+
+ if (op == 6) {
+ s->mii_word = tulip_mii_read(s, phy, reg);
+ }
+ } else if (s->mii_bitcnt == 32) {
+ op = (s->mii_word >> 28) & 0x0f;
+ phy = (s->mii_word >> 23) & 0x1f;
+ reg = (s->mii_word >> 18) & 0x1f;
+ data = s->mii_word & 0xffff;
+
+ if (op == 5) {
+ tulip_mii_write(s, phy, reg, data);
+ }
+ }
+}
+
+static uint32_t tulip_csr9_read(TULIPState *s)
+{
+ if (s->csr[9] & CSR9_SR) {
+ if (eeprom93xx_read(s->eeprom)) {
+ s->csr[9] |= CSR9_SR_DO;
+ } else {
+ s->csr[9] &= ~CSR9_SR_DO;
+ }
+ }
+
+ tulip_mii(s);
+ return s->csr[9];
+}
+
+static void tulip_update_ts(TULIPState *s, int state)
+{
+ s->csr[5] &= ~(CSR5_TS_MASK << CSR5_TS_SHIFT);
+ s->csr[5] |= (state & CSR5_TS_MASK) << CSR5_TS_SHIFT;
+ trace_tulip_tx_state(tulip_tx_state_name(state));
+}
+
+static uint64_t tulip_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TULIPState *s = opaque;
+ uint64_t data = 0;
+
+ switch (addr) {
+ case CSR(9):
+ data = tulip_csr9_read(s);
+ break;
+
+ case CSR(12):
+ /* Fake autocompletion complete until we have PHY emulation */
+ data = 5 << CSR12_ANS_SHIFT;
+ break;
+
+ default:
+ if (addr & 7) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read access at unknown address"
+ " 0x%"PRIx64"\n", __func__, addr);
+ } else {
+ data = s->csr[addr >> 3];
+ }
+ break;
+ }
+ trace_tulip_reg_read(addr, tulip_reg_name(addr), size, data);
+ return data;
+}
+
+static void tulip_tx(TULIPState *s, struct tulip_descriptor *desc)
+{
+ if (s->tx_frame_len) {
+ if ((s->csr[6] >> CSR6_OM_SHIFT) & CSR6_OM_MASK) {
+ /* Internal or external Loopback */
+ tulip_receive(s, s->tx_frame, s->tx_frame_len);
+ } else {
+ qemu_send_packet(qemu_get_queue(s->nic),
+ s->tx_frame, s->tx_frame_len);
+ }
+ }
+
+ if (desc->control & TDES1_IC) {
+ s->csr[5] |= CSR5_TI;
+ tulip_update_int(s);
+ }
+}
+
+static void tulip_copy_tx_buffers(TULIPState *s, struct tulip_descriptor *desc)
+{
+ int len1 = (desc->control >> TDES1_BUF1_SIZE_SHIFT) & TDES1_BUF1_SIZE_MASK;
+ int len2 = (desc->control >> TDES1_BUF2_SIZE_SHIFT) & TDES1_BUF2_SIZE_MASK;
+
+ if (len1) {
+ pci_dma_read(&s->dev, desc->buf_addr1,
+ s->tx_frame + s->tx_frame_len, len1);
+ s->tx_frame_len += len1;
+ }
+
+ if (len2) {
+ pci_dma_read(&s->dev, desc->buf_addr2,
+ s->tx_frame + s->tx_frame_len, len2);
+ s->tx_frame_len += len2;
+ }
+ desc->status = (len1 + len2) ? 0 : 0x7fffffff;
+}
+
+static void tulip_setup_filter_addr(TULIPState *s, uint8_t *buf, int n)
+{
+ int offset = n * 12;
+
+ s->filter[n][0] = buf[offset];
+ s->filter[n][1] = buf[offset + 1];
+
+ s->filter[n][2] = buf[offset + 4];
+ s->filter[n][3] = buf[offset + 5];
+
+ s->filter[n][4] = buf[offset + 8];
+ s->filter[n][5] = buf[offset + 9];
+
+ trace_tulip_setup_filter(n, s->filter[n][5], s->filter[n][4],
+ s->filter[n][3], s->filter[n][2], s->filter[n][1], s->filter[n][0]);
+}
+
+static void tulip_setup_frame(TULIPState *s,
+ struct tulip_descriptor *desc)
+{
+ uint8_t buf[4096];
+ int len = (desc->control >> TDES1_BUF1_SIZE_SHIFT) & TDES1_BUF1_SIZE_MASK;
+ int i;
+
+ trace_tulip_setup_frame();
+
+ if (len == 192) {
+ pci_dma_read(&s->dev, desc->buf_addr1, buf, len);
+ for (i = 0; i < 16; i++) {
+ tulip_setup_filter_addr(s, buf, i);
+ }
+ }
+
+ desc->status = 0x7fffffff;
+
+ if (desc->control & TDES1_IC) {
+ s->csr[5] |= CSR5_TI;
+ tulip_update_int(s);
+ }
+}
+
+static void tulip_next_tx_descriptor(TULIPState *s,
+ struct tulip_descriptor *desc)
+{
+ if (desc->control & TDES1_TER) {
+ s->current_tx_desc = s->csr[4];
+ } else if (desc->control & TDES1_TCH) {
+ s->current_tx_desc = desc->buf_addr2;
+ } else {
+ s->current_tx_desc += sizeof(struct tulip_descriptor) +
+ (((s->csr[0] >> CSR0_DSL_SHIFT) & CSR0_DSL_MASK) << 2);
+ }
+ s->current_tx_desc &= ~3ULL;
+}
+
+static uint32_t tulip_ts(TULIPState *s)
+{
+ return (s->csr[5] >> CSR5_TS_SHIFT) & CSR5_TS_MASK;
+}
+
+static void tulip_xmit_list_update(TULIPState *s)
+{
+ struct tulip_descriptor desc;
+
+ if (tulip_ts(s) != CSR5_TS_SUSPENDED) {
+ return;
+ }
+
+ for (;;) {
+ tulip_desc_read(s, s->current_tx_desc, &desc);
+ tulip_dump_tx_descriptor(s, &desc);
+
+ if (!(desc.status & TDES0_OWN)) {
+ tulip_update_ts(s, CSR5_TS_SUSPENDED);
+ s->csr[5] |= CSR5_TU;
+ tulip_update_int(s);
+ return;
+ }
+
+ if (desc.control & TDES1_SET) {
+ tulip_setup_frame(s, &desc);
+ } else {
+ if (desc.control & TDES1_FS) {
+ s->tx_frame_len = 0;
+ }
+
+ tulip_copy_tx_buffers(s, &desc);
+
+ if (desc.control & TDES1_LS) {
+ tulip_tx(s, &desc);
+ }
+ }
+ tulip_desc_write(s, s->current_tx_desc, &desc);
+ tulip_next_tx_descriptor(s, &desc);
+ }
+}
+
+static void tulip_csr9_write(TULIPState *s, uint32_t old_val,
+ uint32_t new_val)
+{
+ if (new_val & CSR9_SR) {
+ eeprom93xx_write(s->eeprom,
+ !!(new_val & CSR9_SR_CS),
+ !!(new_val & CSR9_SR_SK),
+ !!(new_val & CSR9_SR_DI));
+ }
+}
+
+static void tulip_reset(TULIPState *s)
+{
+ trace_tulip_reset();
+
+ s->csr[0] = 0xfe000000;
+ s->csr[1] = 0xffffffff;
+ s->csr[2] = 0xffffffff;
+ s->csr[5] = 0xf0000000;
+ s->csr[6] = 0x32000040;
+ s->csr[7] = 0xf3fe0000;
+ s->csr[8] = 0xe0000000;
+ s->csr[9] = 0xfff483ff;
+ s->csr[11] = 0xfffe0000;
+ s->csr[12] = 0x000000c6;
+ s->csr[13] = 0xffff0000;
+ s->csr[14] = 0xffffffff;
+ s->csr[15] = 0x8ff00000;
+}
+
+static void tulip_qdev_reset(DeviceState *dev)
+{
+ PCIDevice *d = PCI_DEVICE(dev);
+ TULIPState *s = TULIP(d);
+
+ tulip_reset(s);
+}
+
+static void tulip_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ TULIPState *s = opaque;
+ trace_tulip_reg_write(addr, tulip_reg_name(addr), size, data);
+
+ switch (addr) {
+ case CSR(0):
+ s->csr[0] = data;
+ if (data & CSR0_SWR) {
+ tulip_reset(s);
+ tulip_update_int(s);
+ }
+ break;
+
+ case CSR(1):
+ tulip_xmit_list_update(s);
+ break;
+
+ case CSR(2):
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ break;
+
+ case CSR(3):
+ s->csr[3] = data & ~3ULL;
+ s->current_rx_desc = s->csr[3];
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ break;
+
+ case CSR(4):
+ s->csr[4] = data & ~3ULL;
+ s->current_tx_desc = s->csr[4];
+ tulip_xmit_list_update(s);
+ break;
+
+ case CSR(5):
+ /* Status register, write clears bit */
+ s->csr[5] &= ~(data & (CSR5_TI | CSR5_TPS | CSR5_TU | CSR5_TJT |
+ CSR5_LNP_ANC | CSR5_UNF | CSR5_RI | CSR5_RU |
+ CSR5_RPS | CSR5_RWT | CSR5_ETI | CSR5_GTE |
+ CSR5_LNF | CSR5_FBE | CSR5_ERI | CSR5_AIS |
+ CSR5_NIS | CSR5_GPI | CSR5_LC));
+ tulip_update_int(s);
+ break;
+
+ case CSR(6):
+ s->csr[6] = data;
+ if (s->csr[6] & CSR6_SR) {
+ tulip_update_rs(s, CSR5_RS_RUNNING_WAIT_RECEIVE);
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ } else {
+ tulip_update_rs(s, CSR5_RS_STOPPED);
+ }
+
+ if (s->csr[6] & CSR6_ST) {
+ tulip_update_ts(s, CSR5_TS_SUSPENDED);
+ tulip_xmit_list_update(s);
+ } else {
+ tulip_update_ts(s, CSR5_TS_STOPPED);
+ }
+ break;
+
+ case CSR(7):
+ s->csr[7] = data;
+ tulip_update_int(s);
+ break;
+
+ case CSR(8):
+ s->csr[9] = data;
+ break;
+
+ case CSR(9):
+ tulip_csr9_write(s, s->csr[9], data);
+ /* don't clear MII read data */
+ s->csr[9] &= CSR9_MDI;
+ s->csr[9] |= (data & ~CSR9_MDI);
+ tulip_mii(s);
+ s->old_csr9 = s->csr[9];
+ break;
+
+ case CSR(10):
+ s->csr[10] = data;
+ break;
+
+ case CSR(11):
+ s->csr[11] = data;
+ break;
+
+ case CSR(12):
+ /* SIA Status register, some bits are cleared by writing 1 */
+ s->csr[12] &= ~(data & (CSR12_MRA | CSR12_TRA | CSR12_ARA));
+ break;
+
+ case CSR(13):
+ s->csr[13] = data;
+ break;
+
+ case CSR(14):
+ s->csr[14] = data;
+ break;
+
+ case CSR(15):
+ s->csr[15] = data;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: write to CSR at unknown address "
+ "0x%"PRIx64"\n", __func__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps tulip_ops = {
+ .read = tulip_read,
+ .write = tulip_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void tulip_idblock_crc(TULIPState *s, uint16_t *srom)
+{
+ int word, n;
+ int bit;
+ unsigned char bitval, crc;
+ const int len = 9;
+ n = 0;
+ crc = -1;
+
+ for (word = 0; word < len; word++) {
+ for (bit = 15; bit >= 0; bit--) {
+ if ((word == (len - 1)) && (bit == 7)) {
+ /*
+ * Insert the correct CRC result into input data stream
+ * in place.
+ */
+ srom[len - 1] = (srom[len - 1] & 0xff00) | (unsigned short)crc;
+ break;
+ }
+ n++;
+ bitval = ((srom[word] >> bit) & 1) ^ ((crc >> 7) & 1);
+ crc = crc << 1;
+ if (bitval == 1) {
+ crc ^= 6;
+ crc |= 0x01;
+ }
+ }
+ }
+}
+
+static uint16_t tulip_srom_crc(TULIPState *s, uint8_t *eeprom, size_t len)
+{
+ unsigned long crc = 0xffffffff;
+ unsigned long flippedcrc = 0;
+ unsigned char currentbyte;
+ unsigned int msb, bit, i;
+
+ for (i = 0; i < len; i++) {
+ currentbyte = eeprom[i];
+ for (bit = 0; bit < 8; bit++) {
+ msb = (crc >> 31) & 1;
+ crc <<= 1;
+ if (msb ^ (currentbyte & 1)) {
+ crc ^= 0x04c11db6;
+ crc |= 0x00000001;
+ }
+ currentbyte >>= 1;
+ }
+ }
+
+ for (i = 0; i < 32; i++) {
+ flippedcrc <<= 1;
+ bit = crc & 1;
+ crc >>= 1;
+ flippedcrc += bit;
+ }
+ return (flippedcrc ^ 0xffffffff) & 0xffff;
+}
+
+static const uint8_t eeprom_default[128] = {
+ 0x3c, 0x10, 0x4f, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x56, 0x08, 0x04, 0x01, 0x00, 0x80, 0x48, 0xb3,
+ 0x0e, 0xa7, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x08,
+ 0x01, 0x8d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x78,
+ 0xe0, 0x01, 0x00, 0x50, 0x00, 0x18, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x6b,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x48, 0xb3, 0x0e, 0xa7, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static void tulip_fill_eeprom(TULIPState *s)
+{
+ uint16_t *eeprom = eeprom93xx_data(s->eeprom);
+ memcpy(eeprom, eeprom_default, 128);
+
+ /* patch in our mac address */
+ eeprom[10] = cpu_to_le16(s->c.macaddr.a[0] | (s->c.macaddr.a[1] << 8));
+ eeprom[11] = cpu_to_le16(s->c.macaddr.a[2] | (s->c.macaddr.a[3] << 8));
+ eeprom[12] = cpu_to_le16(s->c.macaddr.a[4] | (s->c.macaddr.a[5] << 8));
+ tulip_idblock_crc(s, eeprom);
+ eeprom[63] = cpu_to_le16(tulip_srom_crc(s, (uint8_t *)eeprom, 126));
+}
+
+static void pci_tulip_realize(PCIDevice *pci_dev, Error **errp)
+{
+ TULIPState *s = DO_UPCAST(TULIPState, dev, pci_dev);
+ uint8_t *pci_conf;
+
+ pci_conf = s->dev.config;
+ pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
+
+ s->eeprom = eeprom93xx_new(&pci_dev->qdev, 64);
+ tulip_fill_eeprom(s);
+
+ memory_region_init_io(&s->io, OBJECT(&s->dev), &tulip_ops, s,
+ "tulip-io", 128);
+
+ memory_region_init_io(&s->memory, OBJECT(&s->dev), &tulip_ops, s,
+ "tulip-mem", 128);
+
+ pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+ pci_register_bar(&s->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->memory);
+
+ s->irq = pci_allocate_irq(&s->dev);
+
+ qemu_macaddr_default_if_unset(&s->c.macaddr);
+
+ s->nic = qemu_new_nic(&net_tulip_info, &s->c,
+ object_get_typename(OBJECT(pci_dev)),
+ pci_dev->qdev.id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->c.macaddr.a);
+}
+
+static void pci_tulip_exit(PCIDevice *pci_dev)
+{
+ TULIPState *s = DO_UPCAST(TULIPState, dev, pci_dev);
+
+ qemu_del_nic(s->nic);
+ qemu_free_irq(s->irq);
+ eeprom93xx_free(&pci_dev->qdev, s->eeprom);
+}
+
+static void tulip_instance_init(Object *obj)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(obj);
+ TULIPState *d = DO_UPCAST(TULIPState, dev, pci_dev);
+
+ device_add_bootindex_property(obj, &d->c.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ &pci_dev->qdev, NULL);
+}
+
+static Property tulip_properties[] = {
+ DEFINE_NIC_PROPERTIES(TULIPState, c),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tulip_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_tulip_realize;
+ k->exit = pci_tulip_exit;
+ k->vendor_id = PCI_VENDOR_ID_DEC;
+ k->device_id = PCI_DEVICE_ID_DEC_21143;
+ k->subsystem_vendor_id = 0x103c;
+ k->subsystem_id = 0x104f;
+ k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+ dc->vmsd = &vmstate_pci_tulip;
+ dc->props = tulip_properties;
+ dc->reset = tulip_qdev_reset;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo tulip_info = {
+ .name = TYPE_TULIP,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(TULIPState),
+ .class_init = tulip_class_init,
+ .instance_init = tulip_instance_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void tulip_register_types(void)
+{
+ type_register_static(&tulip_info);
+}
+
+type_init(tulip_register_types)
diff --git a/hw/net/tulip.h b/hw/net/tulip.h
new file mode 100644
index 0000000000..97521b21db
--- /dev/null
+++ b/hw/net/tulip.h
@@ -0,0 +1,267 @@
+#ifndef HW_TULIP_H
+#define HW_TULIP_H
+
+#include "qemu/units.h"
+#include "net/net.h"
+
+#define TYPE_TULIP "tulip"
+#define TULIP(obj) OBJECT_CHECK(TULIPState, (obj), TYPE_TULIP)
+
+#define CSR(_x) ((_x) << 3)
+
+#define CSR0_SWR BIT(0)
+#define CSR0_BAR BIT(1)
+#define CSR0_DSL_SHIFT 2
+#define CSR0_DSL_MASK 0x1f
+#define CSR0_BLE BIT(7)
+#define CSR0_PBL_SHIFT 8
+#define CSR0_PBL_MASK 0x3f
+#define CSR0_CAC_SHIFT 14
+#define CSR0_CAC_MASK 0x3
+#define CSR0_DAS 0x10000
+#define CSR0_TAP_SHIFT 17
+#define CSR0_TAP_MASK 0x7
+#define CSR0_DBO 0x100000
+#define CSR1_TPD 0x01
+#define CSR0_RLE BIT(23)
+#define CSR0_WIE BIT(24)
+
+#define CSR2_RPD 0x01
+
+#define CSR5_TI BIT(0)
+#define CSR5_TPS BIT(1)
+#define CSR5_TU BIT(2)
+#define CSR5_TJT BIT(3)
+#define CSR5_LNP_ANC BIT(4)
+#define CSR5_UNF BIT(5)
+#define CSR5_RI BIT(6)
+#define CSR5_RU BIT(7)
+#define CSR5_RPS BIT(8)
+#define CSR5_RWT BIT(9)
+#define CSR5_ETI BIT(10)
+#define CSR5_GTE BIT(11)
+#define CSR5_LNF BIT(12)
+#define CSR5_FBE BIT(13)
+#define CSR5_ERI BIT(14)
+#define CSR5_AIS BIT(15)
+#define CSR5_NIS BIT(16)
+#define CSR5_RS_SHIFT 17
+#define CSR5_RS_MASK 7
+#define CSR5_TS_SHIFT 20
+#define CSR5_TS_MASK 7
+
+#define CSR5_TS_STOPPED 0
+#define CSR5_TS_RUNNING_FETCH 1
+#define CSR5_TS_RUNNING_WAIT_EOT 2
+#define CSR5_TS_RUNNING_READ_BUF 3
+#define CSR5_TS_RUNNING_SETUP 5
+#define CSR5_TS_SUSPENDED 6
+#define CSR5_TS_RUNNING_CLOSE 7
+
+#define CSR5_RS_STOPPED 0
+#define CSR5_RS_RUNNING_FETCH 1
+#define CSR5_RS_RUNNING_CHECK_EOR 2
+#define CSR5_RS_RUNNING_WAIT_RECEIVE 3
+#define CSR5_RS_SUSPENDED 4
+#define CSR5_RS_RUNNING_CLOSE 5
+#define CSR5_RS_RUNNING_FLUSH 6
+#define CSR5_RS_RUNNING_QUEUE 7
+
+#define CSR5_EB_SHIFT 23
+#define CSR5_EB_MASK 7
+
+#define CSR5_GPI BIT(26)
+#define CSR5_LC BIT(27)
+
+#define CSR6_HP BIT(0)
+#define CSR6_SR BIT(1)
+#define CSR6_HO BIT(2)
+#define CSR6_PB BIT(3)
+#define CSR6_IF BIT(4)
+#define CSR6_SB BIT(5)
+#define CSR6_PR BIT(6)
+#define CSR6_PM BIT(7)
+#define CSR6_FKD BIT(8)
+#define CSR6_FD BIT(9)
+
+#define CSR6_OM_SHIFT 10
+#define CSR6_OM_MASK 3
+#define CSR6_OM_NORMAL 0
+#define CSR6_OM_INT_LOOPBACK 1
+#define CSR6_OM_EXT_LOOPBACK 2
+
+#define CSR6_FC BIT(12)
+#define CSR6_ST BIT(13)
+
+
+#define CSR6_TR_SHIFT 14
+#define CSR6_TR_MASK 3
+#define CSR6_TR_72 0
+#define CSR6_TR_96 1
+#define CSR6_TR_128 2
+#define CSR6_TR_160 3
+
+#define CSR6_CA BIT(17)
+#define CSR6_RA BIT(30)
+#define CSR6_SC BIT(31)
+
+#define CSR7_TIM BIT(0)
+#define CSR7_TSM BIT(1)
+#define CSR7_TUM BIT(2)
+#define CSR7_TJM BIT(3)
+#define CSR7_LPM BIT(4)
+#define CSR7_UNM BIT(5)
+#define CSR7_RIM BIT(6)
+#define CSR7_RUM BIT(7)
+#define CSR7_RSM BIT(8)
+#define CSR7_RWM BIT(9)
+#define CSR7_TMM BIT(11)
+#define CSR7_LFM BIT(12)
+#define CSR7_SEM BIT(13)
+#define CSR7_ERM BIT(14)
+#define CSR7_AIM BIT(15)
+#define CSR7_NIM BIT(16)
+
+#define CSR8_MISSED_FRAME_OVL BIT(16)
+#define CSR8_MISSED_FRAME_CNT_MASK 0xffff
+
+#define CSR9_DATA_MASK 0xff
+#define CSR9_SR_CS BIT(0)
+#define CSR9_SR_SK BIT(1)
+#define CSR9_SR_DI BIT(2)
+#define CSR9_SR_DO BIT(3)
+#define CSR9_REG BIT(10)
+#define CSR9_SR BIT(11)
+#define CSR9_BR BIT(12)
+#define CSR9_WR BIT(13)
+#define CSR9_RD BIT(14)
+#define CSR9_MOD BIT(15)
+#define CSR9_MDC BIT(16)
+#define CSR9_MDO BIT(17)
+#define CSR9_MII BIT(18)
+#define CSR9_MDI BIT(19)
+
+#define CSR11_CON BIT(16)
+#define CSR11_TIMER_MASK 0xffff
+
+#define CSR12_MRA BIT(0)
+#define CSR12_LS100 BIT(1)
+#define CSR12_LS10 BIT(2)
+#define CSR12_APS BIT(3)
+#define CSR12_ARA BIT(8)
+#define CSR12_TRA BIT(9)
+#define CSR12_NSN BIT(10)
+#define CSR12_TRF BIT(11)
+#define CSR12_ANS_SHIFT 12
+#define CSR12_ANS_MASK 7
+#define CSR12_LPN BIT(15)
+#define CSR12_LPC_SHIFT 16
+#define CSR12_LPC_MASK 0xffff
+
+#define CSR13_SRL BIT(0)
+#define CSR13_CAC BIT(2)
+#define CSR13_AUI BIT(3)
+#define CSR13_SDM_SHIFT 4
+#define CSR13_SDM_MASK 0xfff
+
+#define CSR14_ECEN BIT(0)
+#define CSR14_LBK BIT(1)
+#define CSR14_DREN BIT(2)
+#define CSR14_LSE BIT(3)
+#define CSR14_CPEN_SHIFT 4
+#define CSR14_CPEN_MASK 3
+#define CSR14_MBO BIT(6)
+#define CSR14_ANE BIT(7)
+#define CSR14_RSQ BIT(8)
+#define CSR14_CSQ BIT(9)
+#define CSR14_CLD BIT(10)
+#define CSR14_SQE BIT(11)
+#define CSR14_LTE BIT(12)
+#define CSR14_APE BIT(13)
+#define CSR14_SPP BIT(14)
+#define CSR14_TAS BIT(15)
+
+#define CSR15_JBD BIT(0)
+#define CSR15_HUJ BIT(1)
+#define CSR15_JCK BIT(2)
+#define CSR15_ABM BIT(3)
+#define CSR15_RWD BIT(4)
+#define CSR15_RWR BIT(5)
+#define CSR15_LE1 BIT(6)
+#define CSR15_LV1 BIT(7)
+#define CSR15_TSCK BIT(8)
+#define CSR15_FUSQ BIT(9)
+#define CSR15_FLF BIT(10)
+#define CSR15_LSD BIT(11)
+#define CSR15_DPST BIT(12)
+#define CSR15_FRL BIT(13)
+#define CSR15_LE2 BIT(14)
+#define CSR15_LV2 BIT(15)
+
+#define RDES0_OF BIT(0)
+#define RDES0_CE BIT(1)
+#define RDES0_DB BIT(2)
+#define RDES0_RJ BIT(4)
+#define RDES0_FT BIT(5)
+#define RDES0_CS BIT(6)
+#define RDES0_TL BIT(7)
+#define RDES0_LS BIT(8)
+#define RDES0_FS BIT(9)
+#define RDES0_MF BIT(10)
+#define RDES0_RF BIT(11)
+#define RDES0_DT_SHIFT 12
+#define RDES0_DT_MASK 3
+#define RDES0_LE BIT(14)
+#define RDES0_ES BIT(15)
+#define RDES0_FL_SHIFT 16
+#define RDES0_FL_MASK 0x3fff
+#define RDES0_FF BIT(30)
+#define RDES0_OWN BIT(31)
+
+#define RDES1_BUF1_SIZE_SHIFT 0
+#define RDES1_BUF1_SIZE_MASK 0x7ff
+
+#define RDES1_BUF2_SIZE_SHIFT 11
+#define RDES1_BUF2_SIZE_MASK 0x7ff
+#define RDES1_RCH BIT(24)
+#define RDES1_RER BIT(25)
+
+#define TDES0_DE BIT(0)
+#define TDES0_UF BIT(1)
+#define TDES0_LF BIT(2)
+#define TDES0_CC_SHIFT 3
+#define TDES0_CC_MASK 0xf
+#define TDES0_HF BIT(7)
+#define TDES0_EC BIT(8)
+#define TDES0_LC BIT(9)
+#define TDES0_NC BIT(10)
+#define TDES0_LO BIT(11)
+#define TDES0_TO BIT(14)
+#define TDES0_ES BIT(15)
+#define TDES0_OWN BIT(31)
+
+#define TDES1_BUF1_SIZE_SHIFT 0
+#define TDES1_BUF1_SIZE_MASK 0x7ff
+
+#define TDES1_BUF2_SIZE_SHIFT 11
+#define TDES1_BUF2_SIZE_MASK 0x7ff
+
+#define TDES1_FT0 BIT(22)
+#define TDES1_DPD BIT(23)
+#define TDES1_TCH BIT(24)
+#define TDES1_TER BIT(25)
+#define TDES1_AC BIT(26)
+#define TDES1_SET BIT(27)
+#define TDES1_FT1 BIT(28)
+#define TDES1_FS BIT(29)
+#define TDES1_LS BIT(30)
+#define TDES1_IC BIT(31)
+
+struct tulip_descriptor {
+ uint32_t status;
+ uint32_t control;
+ uint32_t buf_addr1;
+ uint32_t buf_addr2;
+};
+
+#endif
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index 9f11422337..97a5113f7e 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -12,6 +12,7 @@
*/
#include "qemu/osdep.h"
+#include "qemu/atomic.h"
#include "qemu/iov.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
@@ -21,6 +22,10 @@
#include "net/tap.h"
#include "qemu/error-report.h"
#include "qemu/timer.h"
+#include "qemu/option.h"
+#include "qemu/option_int.h"
+#include "qemu/config-file.h"
+#include "qapi/qmp/qdict.h"
#include "hw/virtio/virtio-net.h"
#include "net/vhost_net.h"
#include "net/announce.h"
@@ -28,11 +33,15 @@
#include "qapi/error.h"
#include "qapi/qapi-events-net.h"
#include "hw/qdev-properties.h"
+#include "qapi/qapi-types-migration.h"
+#include "qapi/qapi-events-migration.h"
#include "hw/virtio/virtio-access.h"
#include "migration/misc.h"
#include "standard-headers/linux/ethtool.h"
#include "sysemu/sysemu.h"
#include "trace.h"
+#include "monitor/qdev.h"
+#include "hw/pci/pci.h"
#define VIRTIO_NET_VM_VERSION 11
@@ -90,15 +99,15 @@ static inline __virtio16 *virtio_net_rsc_ext_num_dupacks(
static VirtIOFeature feature_sizes[] = {
{.flags = 1ULL << VIRTIO_NET_F_MAC,
- .end = virtio_endof(struct virtio_net_config, mac)},
+ .end = endof(struct virtio_net_config, mac)},
{.flags = 1ULL << VIRTIO_NET_F_STATUS,
- .end = virtio_endof(struct virtio_net_config, status)},
+ .end = endof(struct virtio_net_config, status)},
{.flags = 1ULL << VIRTIO_NET_F_MQ,
- .end = virtio_endof(struct virtio_net_config, max_virtqueue_pairs)},
+ .end = endof(struct virtio_net_config, max_virtqueue_pairs)},
{.flags = 1ULL << VIRTIO_NET_F_MTU,
- .end = virtio_endof(struct virtio_net_config, mtu)},
+ .end = endof(struct virtio_net_config, mtu)},
{.flags = 1ULL << VIRTIO_NET_F_SPEED_DUPLEX,
- .end = virtio_endof(struct virtio_net_config, duplex)},
+ .end = endof(struct virtio_net_config, duplex)},
{}
};
@@ -746,9 +755,99 @@ static inline uint64_t virtio_net_supported_guest_offloads(VirtIONet *n)
return virtio_net_guest_offloads_by_features(vdev->guest_features);
}
+static void failover_add_primary(VirtIONet *n, Error **errp)
+{
+ Error *err = NULL;
+
+ n->primary_device_opts = qemu_opts_find(qemu_find_opts("device"),
+ n->primary_device_id);
+ if (n->primary_device_opts) {
+ n->primary_dev = qdev_device_add(n->primary_device_opts, &err);
+ if (err) {
+ qemu_opts_del(n->primary_device_opts);
+ }
+ if (n->primary_dev) {
+ n->primary_bus = n->primary_dev->parent_bus;
+ if (err) {
+ qdev_unplug(n->primary_dev, &err);
+ qdev_set_id(n->primary_dev, "");
+
+ }
+ }
+ } else {
+ error_setg(errp, "Primary device not found");
+ error_append_hint(errp, "Virtio-net failover will not work. Make "
+ "sure primary device has parameter"
+ " failover_pair_id=<virtio-net-id>\n");
+}
+ if (err) {
+ error_propagate(errp, err);
+ }
+}
+
+static int is_my_primary(void *opaque, QemuOpts *opts, Error **errp)
+{
+ VirtIONet *n = opaque;
+ int ret = 0;
+
+ const char *standby_id = qemu_opt_get(opts, "failover_pair_id");
+
+ if (standby_id != NULL && (g_strcmp0(standby_id, n->netclient_name) == 0)) {
+ n->primary_device_id = g_strdup(opts->id);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static DeviceState *virtio_net_find_primary(VirtIONet *n, Error **errp)
+{
+ DeviceState *dev = NULL;
+ Error *err = NULL;
+
+ if (qemu_opts_foreach(qemu_find_opts("device"),
+ is_my_primary, n, &err)) {
+ if (err) {
+ error_propagate(errp, err);
+ return NULL;
+ }
+ if (n->primary_device_id) {
+ dev = qdev_find_recursive(sysbus_get_default(),
+ n->primary_device_id);
+ } else {
+ error_setg(errp, "Primary device id not found");
+ return NULL;
+ }
+ }
+ return dev;
+}
+
+
+
+static DeviceState *virtio_connect_failover_devices(VirtIONet *n,
+ DeviceState *dev,
+ Error **errp)
+{
+ DeviceState *prim_dev = NULL;
+ Error *err = NULL;
+
+ prim_dev = virtio_net_find_primary(n, &err);
+ if (prim_dev) {
+ n->primary_device_id = g_strdup(prim_dev->id);
+ n->primary_device_opts = prim_dev->opts;
+ } else {
+ if (err) {
+ error_propagate(errp, err);
+ }
+ }
+
+ return prim_dev;
+}
+
static void virtio_net_set_features(VirtIODevice *vdev, uint64_t features)
{
VirtIONet *n = VIRTIO_NET(vdev);
+ Error *err = NULL;
int i;
if (n->mtu_bypass_backend &&
@@ -790,6 +889,28 @@ static void virtio_net_set_features(VirtIODevice *vdev, uint64_t features)
} else {
memset(n->vlans, 0xff, MAX_VLAN >> 3);
}
+
+ if (virtio_has_feature(features, VIRTIO_NET_F_STANDBY)) {
+ qapi_event_send_failover_negotiated(n->netclient_name);
+ atomic_set(&n->primary_should_be_hidden, false);
+ failover_add_primary(n, &err);
+ if (err) {
+ n->primary_dev = virtio_connect_failover_devices(n, n->qdev, &err);
+ if (err) {
+ goto out_err;
+ }
+ failover_add_primary(n, &err);
+ if (err) {
+ goto out_err;
+ }
+ }
+ }
+ return;
+
+out_err:
+ if (err) {
+ warn_report_err(err);
+ }
}
static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd,
@@ -1369,12 +1490,9 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
static ssize_t virtio_net_do_receive(NetClientState *nc, const uint8_t *buf,
size_t size)
{
- ssize_t r;
+ RCU_READ_LOCK_GUARD();
- rcu_read_lock();
- r = virtio_net_receive_rcu(nc, buf, size);
- rcu_read_unlock();
- return r;
+ return virtio_net_receive_rcu(nc, buf, size);
}
static void virtio_net_rsc_extract_unit4(VirtioNetRscChain *chain,
@@ -2333,9 +2451,13 @@ static int virtio_net_post_load_device(void *opaque, int version_id)
n->curr_guest_offloads = virtio_net_supported_guest_offloads(n);
}
- if (peer_has_vnet_hdr(n)) {
- virtio_net_apply_guest_offloads(n);
- }
+ /*
+ * curr_guest_offloads will be later overwritten by the
+ * virtio_set_features_nocheck call done from the virtio_load.
+ * Here we make sure it is preserved and restored accordingly
+ * in the virtio_net_post_load_virtio callback.
+ */
+ n->saved_guest_offloads = n->curr_guest_offloads;
virtio_net_set_queues(n);
@@ -2370,6 +2492,22 @@ static int virtio_net_post_load_device(void *opaque, int version_id)
return 0;
}
+static int virtio_net_post_load_virtio(VirtIODevice *vdev)
+{
+ VirtIONet *n = VIRTIO_NET(vdev);
+ /*
+ * The actual needed state is now in saved_guest_offloads,
+ * see virtio_net_post_load_device for detail.
+ * Restore it back and apply the desired offloads.
+ */
+ n->curr_guest_offloads = n->saved_guest_offloads;
+ if (peer_has_vnet_hdr(n)) {
+ virtio_net_apply_guest_offloads(n);
+ }
+
+ return 0;
+}
+
/* tx_waiting field of a VirtIONetQueue */
static const VMStateDescription vmstate_virtio_net_queue_tx_waiting = {
.name = "virtio-net-queue-tx_waiting",
@@ -2630,6 +2768,150 @@ void virtio_net_set_netclient_name(VirtIONet *n, const char *name,
n->netclient_type = g_strdup(type);
}
+static bool failover_unplug_primary(VirtIONet *n)
+{
+ HotplugHandler *hotplug_ctrl;
+ PCIDevice *pci_dev;
+ Error *err = NULL;
+
+ hotplug_ctrl = qdev_get_hotplug_handler(n->primary_dev);
+ if (hotplug_ctrl) {
+ pci_dev = PCI_DEVICE(n->primary_dev);
+ pci_dev->partially_hotplugged = true;
+ hotplug_handler_unplug_request(hotplug_ctrl, n->primary_dev, &err);
+ if (err) {
+ error_report_err(err);
+ return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static bool failover_replug_primary(VirtIONet *n, Error **errp)
+{
+ HotplugHandler *hotplug_ctrl;
+ PCIDevice *pdev = PCI_DEVICE(n->primary_dev);
+
+ if (!pdev->partially_hotplugged) {
+ return true;
+ }
+ if (!n->primary_device_opts) {
+ n->primary_device_opts = qemu_opts_from_qdict(
+ qemu_find_opts("device"),
+ n->primary_device_dict, errp);
+ }
+ if (n->primary_device_opts) {
+ if (n->primary_dev) {
+ n->primary_bus = n->primary_dev->parent_bus;
+ }
+ qdev_set_parent_bus(n->primary_dev, n->primary_bus);
+ n->primary_should_be_hidden = false;
+ qemu_opt_set_bool(n->primary_device_opts,
+ "partially_hotplugged", true, errp);
+ hotplug_ctrl = qdev_get_hotplug_handler(n->primary_dev);
+ if (hotplug_ctrl) {
+ hotplug_handler_pre_plug(hotplug_ctrl, n->primary_dev, errp);
+ hotplug_handler_plug(hotplug_ctrl, n->primary_dev, errp);
+ }
+ if (!n->primary_dev) {
+ error_setg(errp, "virtio_net: couldn't find primary device");
+ }
+ }
+ return *errp != NULL;
+}
+
+static void virtio_net_handle_migration_primary(VirtIONet *n,
+ MigrationState *s)
+{
+ bool should_be_hidden;
+ Error *err = NULL;
+
+ should_be_hidden = atomic_read(&n->primary_should_be_hidden);
+
+ if (!n->primary_dev) {
+ n->primary_dev = virtio_connect_failover_devices(n, n->qdev, &err);
+ if (!n->primary_dev) {
+ return;
+ }
+ }
+
+ if (migration_in_setup(s) && !should_be_hidden &&
+ n->primary_dev) {
+ if (failover_unplug_primary(n)) {
+ vmstate_unregister(n->primary_dev, qdev_get_vmsd(n->primary_dev),
+ n->primary_dev);
+ qapi_event_send_unplug_primary(n->primary_device_id);
+ atomic_set(&n->primary_should_be_hidden, true);
+ } else {
+ warn_report("couldn't unplug primary device");
+ }
+ } else if (migration_has_failed(s)) {
+ /* We already unplugged the device let's plugged it back */
+ if (!failover_replug_primary(n, &err)) {
+ if (err) {
+ error_report_err(err);
+ }
+ }
+ }
+}
+
+static void virtio_net_migration_state_notifier(Notifier *notifier, void *data)
+{
+ MigrationState *s = data;
+ VirtIONet *n = container_of(notifier, VirtIONet, migration_state);
+ virtio_net_handle_migration_primary(n, s);
+}
+
+static int virtio_net_primary_should_be_hidden(DeviceListener *listener,
+ QemuOpts *device_opts)
+{
+ VirtIONet *n = container_of(listener, VirtIONet, primary_listener);
+ bool match_found;
+ bool hide;
+
+ n->primary_device_dict = qemu_opts_to_qdict(device_opts,
+ n->primary_device_dict);
+ if (n->primary_device_dict) {
+ g_free(n->standby_id);
+ n->standby_id = g_strdup(qdict_get_try_str(n->primary_device_dict,
+ "failover_pair_id"));
+ }
+ if (device_opts && g_strcmp0(n->standby_id, n->netclient_name) == 0) {
+ match_found = true;
+ } else {
+ match_found = false;
+ hide = false;
+ g_free(n->standby_id);
+ n->primary_device_dict = NULL;
+ goto out;
+ }
+
+ n->primary_device_opts = device_opts;
+
+ /* primary_should_be_hidden is set during feature negotiation */
+ hide = atomic_read(&n->primary_should_be_hidden);
+
+ if (n->primary_device_dict) {
+ g_free(n->primary_device_id);
+ n->primary_device_id = g_strdup(qdict_get_try_str(
+ n->primary_device_dict, "id"));
+ if (!n->primary_device_id) {
+ warn_report("primary_device_id not set");
+ }
+ }
+
+out:
+ if (match_found && hide) {
+ return 1;
+ } else if (match_found && !hide) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
static void virtio_net_device_realize(DeviceState *dev, Error **errp)
{
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
@@ -2660,6 +2942,16 @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp)
n->host_features |= (1ULL << VIRTIO_NET_F_SPEED_DUPLEX);
}
+ if (n->failover) {
+ n->primary_listener.should_be_hidden =
+ virtio_net_primary_should_be_hidden;
+ atomic_set(&n->primary_should_be_hidden, true);
+ device_listener_register(&n->primary_listener);
+ n->migration_state.notify = virtio_net_migration_state_notifier;
+ add_migration_state_change_notifier(&n->migration_state);
+ n->host_features |= (1ULL << VIRTIO_NET_F_STANDBY);
+ }
+
virtio_net_set_config_size(n, n->host_features);
virtio_init(vdev, "virtio-net", VIRTIO_ID_NET, n->config_size);
@@ -2782,6 +3074,13 @@ static void virtio_net_device_unrealize(DeviceState *dev, Error **errp)
g_free(n->mac_table.macs);
g_free(n->vlans);
+ if (n->failover) {
+ g_free(n->primary_device_id);
+ g_free(n->standby_id);
+ qobject_unref(n->primary_device_dict);
+ n->primary_device_dict = NULL;
+ }
+
max_queues = n->multiqueue ? n->max_queues : 1;
for (i = 0; i < max_queues; i++) {
virtio_net_del_queue(n, i);
@@ -2819,6 +3118,23 @@ static int virtio_net_pre_save(void *opaque)
return 0;
}
+static bool primary_unplug_pending(void *opaque)
+{
+ DeviceState *dev = opaque;
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIONet *n = VIRTIO_NET(vdev);
+
+ return n->primary_dev ? n->primary_dev->pending_deleted_event : false;
+}
+
+static bool dev_unplug_pending(void *opaque)
+{
+ DeviceState *dev = opaque;
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);
+
+ return vdc->primary_unplug_pending(dev);
+}
+
static const VMStateDescription vmstate_virtio_net = {
.name = "virtio-net",
.minimum_version_id = VIRTIO_NET_VM_VERSION,
@@ -2828,6 +3144,7 @@ static const VMStateDescription vmstate_virtio_net = {
VMSTATE_END_OF_LIST()
},
.pre_save = virtio_net_pre_save,
+ .dev_unplug_pending = dev_unplug_pending,
};
static Property virtio_net_properties[] = {
@@ -2889,6 +3206,7 @@ static Property virtio_net_properties[] = {
true),
DEFINE_PROP_INT32("speed", VirtIONet, net_conf.speed, SPEED_UNKNOWN),
DEFINE_PROP_STRING("duplex", VirtIONet, net_conf.duplex_str),
+ DEFINE_PROP_BOOL("failover", VirtIONet, failover, false),
DEFINE_PROP_END_OF_LIST(),
};
@@ -2912,7 +3230,9 @@ static void virtio_net_class_init(ObjectClass *klass, void *data)
vdc->guest_notifier_mask = virtio_net_guest_notifier_mask;
vdc->guest_notifier_pending = virtio_net_guest_notifier_pending;
vdc->legacy_features |= (0x1 << VIRTIO_NET_F_GSO);
+ vdc->post_load = virtio_net_post_load_virtio;
vdc->vmsd = &vmstate_virtio_net_device;
+ vdc->primary_unplug_pending = primary_unplug_pending;
}
static const TypeInfo virtio_net_info = {
diff --git a/hw/nubus/Kconfig b/hw/nubus/Kconfig
new file mode 100644
index 0000000000..8fb8b22189
--- /dev/null
+++ b/hw/nubus/Kconfig
@@ -0,0 +1,2 @@
+config NUBUS
+ bool
diff --git a/hw/nubus/Makefile.objs b/hw/nubus/Makefile.objs
new file mode 100644
index 0000000000..135ba7878d
--- /dev/null
+++ b/hw/nubus/Makefile.objs
@@ -0,0 +1,4 @@
+common-obj-y += nubus-device.o
+common-obj-y += nubus-bus.o
+common-obj-y += nubus-bridge.o
+common-obj-$(CONFIG_Q800) += mac-nubus-bridge.o
diff --git a/hw/nubus/mac-nubus-bridge.c b/hw/nubus/mac-nubus-bridge.c
new file mode 100644
index 0000000000..7c329300b8
--- /dev/null
+++ b/hw/nubus/mac-nubus-bridge.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2013-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/nubus/mac-nubus-bridge.h"
+
+
+static void mac_nubus_bridge_init(Object *obj)
+{
+ MacNubusState *s = MAC_NUBUS_BRIDGE(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ s->bus = NUBUS_BUS(qbus_create(TYPE_NUBUS_BUS, DEVICE(s), NULL));
+
+ sysbus_init_mmio(sbd, &s->bus->super_slot_io);
+ sysbus_init_mmio(sbd, &s->bus->slot_io);
+}
+
+static void mac_nubus_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Nubus bridge";
+}
+
+static const TypeInfo mac_nubus_bridge_info = {
+ .name = TYPE_MAC_NUBUS_BRIDGE,
+ .parent = TYPE_NUBUS_BRIDGE,
+ .instance_init = mac_nubus_bridge_init,
+ .instance_size = sizeof(MacNubusState),
+ .class_init = mac_nubus_bridge_class_init,
+};
+
+static void mac_nubus_bridge_register_types(void)
+{
+ type_register_static(&mac_nubus_bridge_info);
+}
+
+type_init(mac_nubus_bridge_register_types)
diff --git a/hw/nubus/nubus-bridge.c b/hw/nubus/nubus-bridge.c
new file mode 100644
index 0000000000..cd8c6a91eb
--- /dev/null
+++ b/hw/nubus/nubus-bridge.c
@@ -0,0 +1,34 @@
+/*
+ * QEMU Macintosh Nubus
+ *
+ * Copyright (c) 2013-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/nubus/nubus.h"
+
+static void nubus_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->fw_name = "nubus";
+}
+
+static const TypeInfo nubus_bridge_info = {
+ .name = TYPE_NUBUS_BRIDGE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = nubus_bridge_class_init,
+};
+
+static void nubus_register_types(void)
+{
+ type_register_static(&nubus_bridge_info);
+}
+
+type_init(nubus_register_types)
diff --git a/hw/nubus/nubus-bus.c b/hw/nubus/nubus-bus.c
new file mode 100644
index 0000000000..942a6d5342
--- /dev/null
+++ b/hw/nubus/nubus-bus.c
@@ -0,0 +1,111 @@
+/*
+ * QEMU Macintosh Nubus
+ *
+ * Copyright (c) 2013-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/nubus/nubus.h"
+#include "hw/sysbus.h"
+#include "qapi/error.h"
+
+
+static NubusBus *nubus_find(void)
+{
+ /* Returns NULL unless there is exactly one nubus device */
+ return NUBUS_BUS(object_resolve_path_type("", TYPE_NUBUS_BUS, NULL));
+}
+
+static void nubus_slot_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ /* read only */
+}
+
+
+static uint64_t nubus_slot_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ return 0;
+}
+
+static const MemoryRegionOps nubus_slot_ops = {
+ .read = nubus_slot_read,
+ .write = nubus_slot_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void nubus_super_slot_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ /* read only */
+}
+
+static uint64_t nubus_super_slot_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ return 0;
+}
+
+static const MemoryRegionOps nubus_super_slot_ops = {
+ .read = nubus_super_slot_read,
+ .write = nubus_super_slot_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void nubus_realize(BusState *bus, Error **errp)
+{
+ if (!nubus_find()) {
+ error_setg(errp, "at most one %s device is permitted", TYPE_NUBUS_BUS);
+ return;
+ }
+}
+
+static void nubus_init(Object *obj)
+{
+ NubusBus *nubus = NUBUS_BUS(obj);
+
+ memory_region_init_io(&nubus->super_slot_io, obj, &nubus_super_slot_ops,
+ nubus, "nubus-super-slots",
+ NUBUS_SUPER_SLOT_NB * NUBUS_SUPER_SLOT_SIZE);
+
+ memory_region_init_io(&nubus->slot_io, obj, &nubus_slot_ops,
+ nubus, "nubus-slots",
+ NUBUS_SLOT_NB * NUBUS_SLOT_SIZE);
+
+ nubus->current_slot = NUBUS_FIRST_SLOT;
+}
+
+static void nubus_class_init(ObjectClass *oc, void *data)
+{
+ BusClass *bc = BUS_CLASS(oc);
+
+ bc->realize = nubus_realize;
+}
+
+static const TypeInfo nubus_bus_info = {
+ .name = TYPE_NUBUS_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(NubusBus),
+ .instance_init = nubus_init,
+ .class_init = nubus_class_init,
+};
+
+static void nubus_register_types(void)
+{
+ type_register_static(&nubus_bus_info);
+}
+
+type_init(nubus_register_types)
diff --git a/hw/nubus/nubus-device.c b/hw/nubus/nubus-device.c
new file mode 100644
index 0000000000..01ccad9e8e
--- /dev/null
+++ b/hw/nubus/nubus-device.c
@@ -0,0 +1,215 @@
+/*
+ * QEMU Macintosh Nubus
+ *
+ * Copyright (c) 2013-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/nubus/nubus.h"
+#include "qapi/error.h"
+
+
+/* The Format Block Structure */
+
+#define FBLOCK_DIRECTORY_OFFSET 0
+#define FBLOCK_LENGTH 4
+#define FBLOCK_CRC 8
+#define FBLOCK_REVISION_LEVEL 12
+#define FBLOCK_FORMAT 13
+#define FBLOCK_TEST_PATTERN 14
+#define FBLOCK_RESERVED 18
+#define FBLOCK_BYTE_LANES 19
+
+#define FBLOCK_SIZE 20
+#define FBLOCK_PATTERN_VAL 0x5a932bc7
+
+static uint64_t nubus_fblock_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ NubusDevice *dev = opaque;
+ uint64_t val;
+
+#define BYTE(v, b) (((v) >> (24 - 8 * (b))) & 0xff)
+ switch (addr) {
+ case FBLOCK_BYTE_LANES:
+ val = dev->byte_lanes;
+ val |= (val ^ 0xf) << 4;
+ break;
+ case FBLOCK_RESERVED:
+ val = 0x00;
+ break;
+ case FBLOCK_TEST_PATTERN...FBLOCK_TEST_PATTERN + 3:
+ val = BYTE(FBLOCK_PATTERN_VAL, addr - FBLOCK_TEST_PATTERN);
+ break;
+ case FBLOCK_FORMAT:
+ val = dev->rom_format;
+ break;
+ case FBLOCK_REVISION_LEVEL:
+ val = dev->rom_rev;
+ break;
+ case FBLOCK_CRC...FBLOCK_CRC + 3:
+ val = BYTE(dev->rom_crc, addr - FBLOCK_CRC);
+ break;
+ case FBLOCK_LENGTH...FBLOCK_LENGTH + 3:
+ val = BYTE(dev->rom_length, addr - FBLOCK_LENGTH);
+ break;
+ case FBLOCK_DIRECTORY_OFFSET...FBLOCK_DIRECTORY_OFFSET + 3:
+ val = BYTE(dev->directory_offset, addr - FBLOCK_DIRECTORY_OFFSET);
+ break;
+ default:
+ val = 0;
+ break;
+ }
+ return val;
+}
+
+static void nubus_fblock_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ /* read only */
+}
+
+static const MemoryRegionOps nubus_format_block_ops = {
+ .read = nubus_fblock_read,
+ .write = nubus_fblock_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ }
+};
+
+static void nubus_register_format_block(NubusDevice *dev)
+{
+ char *fblock_name;
+
+ fblock_name = g_strdup_printf("nubus-slot-%d-format-block",
+ dev->slot_nb);
+
+ hwaddr fblock_offset = memory_region_size(&dev->slot_mem) - FBLOCK_SIZE;
+ memory_region_init_io(&dev->fblock_io, NULL, &nubus_format_block_ops,
+ dev, fblock_name, FBLOCK_SIZE);
+ memory_region_add_subregion(&dev->slot_mem, fblock_offset,
+ &dev->fblock_io);
+
+ g_free(fblock_name);
+}
+
+static void mac_nubus_rom_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ /* read only */
+}
+
+static uint64_t mac_nubus_rom_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ NubusDevice *dev = opaque;
+
+ return dev->rom[addr];
+}
+
+static const MemoryRegionOps mac_nubus_rom_ops = {
+ .read = mac_nubus_rom_read,
+ .write = mac_nubus_rom_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+
+void nubus_register_rom(NubusDevice *dev, const uint8_t *rom, uint32_t size,
+ int revision, int format, uint8_t byte_lanes)
+{
+ hwaddr rom_offset;
+ char *rom_name;
+
+ /* FIXME : really compute CRC */
+ dev->rom_length = 0;
+ dev->rom_crc = 0;
+
+ dev->rom_rev = revision;
+ dev->rom_format = format;
+
+ dev->byte_lanes = byte_lanes;
+ dev->directory_offset = -size;
+
+ /* ROM */
+
+ dev->rom = rom;
+ rom_name = g_strdup_printf("nubus-slot-%d-rom", dev->slot_nb);
+ memory_region_init_io(&dev->rom_io, NULL, &mac_nubus_rom_ops,
+ dev, rom_name, size);
+ memory_region_set_readonly(&dev->rom_io, true);
+
+ rom_offset = memory_region_size(&dev->slot_mem) - FBLOCK_SIZE +
+ dev->directory_offset;
+ memory_region_add_subregion(&dev->slot_mem, rom_offset, &dev->rom_io);
+
+ g_free(rom_name);
+}
+
+static void nubus_device_realize(DeviceState *dev, Error **errp)
+{
+ NubusBus *nubus = NUBUS_BUS(qdev_get_parent_bus(DEVICE(dev)));
+ NubusDevice *nd = NUBUS_DEVICE(dev);
+ char *name;
+ hwaddr slot_offset;
+
+ if (nubus->current_slot < NUBUS_FIRST_SLOT ||
+ nubus->current_slot > NUBUS_LAST_SLOT) {
+ error_setg(errp, "Cannot register nubus card, not enough slots");
+ return;
+ }
+
+ nd->slot_nb = nubus->current_slot++;
+ name = g_strdup_printf("nubus-slot-%d", nd->slot_nb);
+
+ if (nd->slot_nb < NUBUS_FIRST_SLOT) {
+ /* Super */
+ slot_offset = (nd->slot_nb - 6) * NUBUS_SUPER_SLOT_SIZE;
+
+ memory_region_init(&nd->slot_mem, OBJECT(dev), name,
+ NUBUS_SUPER_SLOT_SIZE);
+ memory_region_add_subregion(&nubus->super_slot_io, slot_offset,
+ &nd->slot_mem);
+ } else {
+ /* Normal */
+ slot_offset = nd->slot_nb * NUBUS_SLOT_SIZE;
+
+ memory_region_init(&nd->slot_mem, OBJECT(dev), name, NUBUS_SLOT_SIZE);
+ memory_region_add_subregion(&nubus->slot_io, slot_offset,
+ &nd->slot_mem);
+ }
+
+ g_free(name);
+ nubus_register_format_block(nd);
+}
+
+static void nubus_device_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = nubus_device_realize;
+ dc->bus_type = TYPE_NUBUS_BUS;
+}
+
+static const TypeInfo nubus_device_type_info = {
+ .name = TYPE_NUBUS_DEVICE,
+ .parent = TYPE_DEVICE,
+ .abstract = true,
+ .instance_size = sizeof(NubusDevice),
+ .class_init = nubus_device_class_init,
+};
+
+static void nubus_register_types(void)
+{
+ type_register_static(&nubus_device_type_info);
+}
+
+type_init(nubus_register_types)
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index aa05c2b9b2..c68498c0de 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -75,6 +75,8 @@ static Property pci_props[] = {
QEMU_PCIE_LNKSTA_DLLLA_BITNR, true),
DEFINE_PROP_BIT("x-pcie-extcap-init", PCIDevice, cap_present,
QEMU_PCIE_EXTCAP_INIT_BITNR, true),
+ DEFINE_PROP_STRING("failover_pair_id", PCIDevice,
+ failover_pair_id),
DEFINE_PROP_END_OF_LIST()
};
@@ -2077,6 +2079,7 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
ObjectClass *klass = OBJECT_CLASS(pc);
Error *local_err = NULL;
bool is_default_rom;
+ uint16_t class_id;
/* initialize cap_present for pci_is_express() and pci_config_size(),
* Note that hybrid PCIs are not set automatically and need to manage
@@ -2101,6 +2104,35 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
}
}
+ if (pci_dev->failover_pair_id) {
+ if (!pci_bus_is_express(pci_get_bus(pci_dev))) {
+ error_setg(errp, "failover primary device must be on "
+ "PCIExpress bus");
+ error_propagate(errp, local_err);
+ pci_qdev_unrealize(DEVICE(pci_dev), NULL);
+ return;
+ }
+ class_id = pci_get_word(pci_dev->config + PCI_CLASS_DEVICE);
+ if (class_id != PCI_CLASS_NETWORK_ETHERNET) {
+ error_setg(errp, "failover primary device is not an "
+ "Ethernet device");
+ error_propagate(errp, local_err);
+ pci_qdev_unrealize(DEVICE(pci_dev), NULL);
+ return;
+ }
+ if (!(pci_dev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION)
+ && (PCI_FUNC(pci_dev->devfn) == 0)) {
+ qdev->allow_unplug_during_migration = true;
+ } else {
+ error_setg(errp, "failover: primary device must be in its own "
+ "PCI slot");
+ error_propagate(errp, local_err);
+ pci_qdev_unrealize(DEVICE(pci_dev), NULL);
+ return;
+ }
+ qdev->allow_unplug_during_migration = true;
+ }
+
/* rom loading */
is_default_rom = false;
if (pci_dev->romfile == NULL && pc->romfile != NULL) {
diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c
index a6beb567bd..08718188bb 100644
--- a/hw/pci/pcie.c
+++ b/hw/pci/pcie.c
@@ -456,6 +456,10 @@ static void pcie_unplug_device(PCIBus *bus, PCIDevice *dev, void *opaque)
{
HotplugHandler *hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(dev));
+ if (dev->partially_hotplugged) {
+ dev->qdev.pending_deleted_event = false;
+ return;
+ }
hotplug_handler_unplug(hotplug_ctrl, DEVICE(dev), &error_abort);
object_unparent(OBJECT(dev));
}
@@ -473,6 +477,8 @@ void pcie_cap_slot_unplug_request_cb(HotplugHandler *hotplug_dev,
return;
}
+ dev->pending_deleted_event = true;
+
/* In case user cancel the operation of multi-function hot-add,
* remove the function that is unexposed to guest individually,
* without interaction with guest.
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index fb19b2df3a..b12660b9f8 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -36,4 +36,5 @@ config RISCV_VIRT
select SERIAL
select VIRTIO_MMIO
select PCI_EXPRESS_GENERIC_BRIDGE
+ select PFLASH_CFI01
select SIFIVE
diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
index 2e92fb0680..7fee98d2f8 100644
--- a/hw/riscv/boot.c
+++ b/hw/riscv/boot.c
@@ -38,7 +38,7 @@ void riscv_find_and_load_firmware(MachineState *machine,
const char *default_machine_firmware,
hwaddr firmware_load_addr)
{
- char *firmware_filename;
+ char *firmware_filename = NULL;
if (!machine->firmware) {
/*
@@ -70,14 +70,11 @@ void riscv_find_and_load_firmware(MachineState *machine,
* if no -bios option is set without breaking anything.
*/
firmware_filename = riscv_find_firmware(default_machine_firmware);
- } else {
- firmware_filename = machine->firmware;
- if (strcmp(firmware_filename, "none")) {
- firmware_filename = riscv_find_firmware(firmware_filename);
- }
+ } else if (strcmp(machine->firmware, "none")) {
+ firmware_filename = riscv_find_firmware(machine->firmware);
}
- if (strcmp(firmware_filename, "none")) {
+ if (firmware_filename) {
/* If not "none" load the firmware */
riscv_load_firmware(firmware_filename, firmware_load_addr);
g_free(firmware_filename);
diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c
index 9f8e84bf2e..9552abf4dd 100644
--- a/hw/riscv/sifive_u.c
+++ b/hw/riscv/sifive_u.c
@@ -65,11 +65,13 @@ static const struct MemmapEntry {
[SIFIVE_U_DEBUG] = { 0x0, 0x100 },
[SIFIVE_U_MROM] = { 0x1000, 0x11000 },
[SIFIVE_U_CLINT] = { 0x2000000, 0x10000 },
+ [SIFIVE_U_L2LIM] = { 0x8000000, 0x2000000 },
[SIFIVE_U_PLIC] = { 0xc000000, 0x4000000 },
[SIFIVE_U_PRCI] = { 0x10000000, 0x1000 },
[SIFIVE_U_UART0] = { 0x10010000, 0x1000 },
[SIFIVE_U_UART1] = { 0x10011000, 0x1000 },
[SIFIVE_U_OTP] = { 0x10070000, 0x1000 },
+ [SIFIVE_U_FLASH0] = { 0x20000000, 0x10000000 },
[SIFIVE_U_DRAM] = { 0x80000000, 0x0 },
[SIFIVE_U_GEM] = { 0x10090000, 0x2000 },
[SIFIVE_U_GEM_MGMT] = { 0x100a0000, 0x1000 },
@@ -151,8 +153,6 @@ static void create_fdt(SiFiveUState *s, const struct MemmapEntry *memmap,
char *intc = g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu);
char *isa;
qemu_fdt_add_subnode(fdt, nodename);
- qemu_fdt_setprop_cell(fdt, nodename, "clock-frequency",
- SIFIVE_U_CLOCK_FREQ);
/* cpu 0 is the management hart that does not have mmu */
if (cpu != 0) {
qemu_fdt_setprop_string(fdt, nodename, "mmu-type", "riscv,sv48");
@@ -272,6 +272,10 @@ static void create_fdt(SiFiveUState *s, const struct MemmapEntry *memmap,
s->soc.gem.conf.macaddr.a, ETH_ALEN);
qemu_fdt_setprop_cell(fdt, nodename, "#address-cells", 1);
qemu_fdt_setprop_cell(fdt, nodename, "#size-cells", 0);
+
+ qemu_fdt_add_subnode(fdt, "/aliases");
+ qemu_fdt_setprop_string(fdt, "/aliases", "ethernet0", nodename);
+
g_free(nodename);
nodename = g_strdup_printf("/soc/ethernet@%lx/ethernet-phy@0",
@@ -299,7 +303,6 @@ static void create_fdt(SiFiveUState *s, const struct MemmapEntry *memmap,
qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", cmdline);
}
- qemu_fdt_add_subnode(fdt, "/aliases");
qemu_fdt_setprop_string(fdt, "/aliases", "serial0", nodename);
g_free(nodename);
@@ -308,10 +311,11 @@ static void create_fdt(SiFiveUState *s, const struct MemmapEntry *memmap,
static void riscv_sifive_u_init(MachineState *machine)
{
const struct MemmapEntry *memmap = sifive_u_memmap;
-
- SiFiveUState *s = g_new0(SiFiveUState, 1);
+ SiFiveUState *s = RISCV_U_MACHINE(machine);
MemoryRegion *system_memory = get_system_memory();
MemoryRegion *main_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *flash0 = g_new(MemoryRegion, 1);
+ target_ulong start_addr = memmap[SIFIVE_U_DRAM].base;
int i;
/* Initialize SoC */
@@ -327,6 +331,12 @@ static void riscv_sifive_u_init(MachineState *machine)
memory_region_add_subregion(system_memory, memmap[SIFIVE_U_DRAM].base,
main_mem);
+ /* register QSPI0 Flash */
+ memory_region_init_ram(flash0, NULL, "riscv.sifive.u.flash0",
+ memmap[SIFIVE_U_FLASH0].size, &error_fatal);
+ memory_region_add_subregion(system_memory, memmap[SIFIVE_U_FLASH0].base,
+ flash0);
+
/* create device tree */
create_fdt(s, memmap, machine->ram_size, machine->kernel_cmdline);
@@ -348,6 +358,10 @@ static void riscv_sifive_u_init(MachineState *machine)
}
}
+ if (s->start_in_flash) {
+ start_addr = memmap[SIFIVE_U_FLASH0].base;
+ }
+
/* reset vector */
uint32_t reset_vec[8] = {
0x00000297, /* 1: auipc t0, %pcrel_hi(dtb) */
@@ -360,7 +374,7 @@ static void riscv_sifive_u_init(MachineState *machine)
#endif
0x00028067, /* jr t0 */
0x00000000,
- memmap[SIFIVE_U_DRAM].base, /* start: .dword DRAM_BASE */
+ start_addr, /* start: .dword */
0x00000000,
/* dtb: */
};
@@ -424,6 +438,33 @@ static void riscv_sifive_u_soc_init(Object *obj)
TYPE_CADENCE_GEM);
}
+static bool sifive_u_get_start_in_flash(Object *obj, Error **errp)
+{
+ SiFiveUState *s = RISCV_U_MACHINE(obj);
+
+ return s->start_in_flash;
+}
+
+static void sifive_u_set_start_in_flash(Object *obj, bool value, Error **errp)
+{
+ SiFiveUState *s = RISCV_U_MACHINE(obj);
+
+ s->start_in_flash = value;
+}
+
+static void riscv_sifive_u_machine_instance_init(Object *obj)
+{
+ SiFiveUState *s = RISCV_U_MACHINE(obj);
+
+ s->start_in_flash = false;
+ object_property_add_bool(obj, "start-in-flash", sifive_u_get_start_in_flash,
+ sifive_u_set_start_in_flash, NULL);
+ object_property_set_description(obj, "start-in-flash",
+ "Set on to tell QEMU's ROM to jump to " \
+ "flash. Otherwise QEMU will jump to DRAM",
+ NULL);
+}
+
static void riscv_sifive_u_soc_realize(DeviceState *dev, Error **errp)
{
MachineState *ms = MACHINE(qdev_get_machine());
@@ -431,6 +472,7 @@ static void riscv_sifive_u_soc_realize(DeviceState *dev, Error **errp)
const struct MemmapEntry *memmap = sifive_u_memmap;
MemoryRegion *system_memory = get_system_memory();
MemoryRegion *mask_rom = g_new(MemoryRegion, 1);
+ MemoryRegion *l2lim_mem = g_new(MemoryRegion, 1);
qemu_irq plic_gpios[SIFIVE_U_PLIC_NUM_SOURCES];
char *plic_hart_config;
size_t plic_hart_config_len;
@@ -459,6 +501,20 @@ static void riscv_sifive_u_soc_realize(DeviceState *dev, Error **errp)
memory_region_add_subregion(system_memory, memmap[SIFIVE_U_MROM].base,
mask_rom);
+ /*
+ * Add L2-LIM at reset size.
+ * This should be reduced in size as the L2 Cache Controller WayEnable
+ * register is incremented. Unfortunately I don't see a nice (or any) way
+ * to handle reducing or blocking out the L2 LIM while still allowing it
+ * be re returned to all enabled after a reset. For the time being, just
+ * leave it enabled all the time. This won't break anything, but will be
+ * too generous to misbehaving guests.
+ */
+ memory_region_init_ram(l2lim_mem, NULL, "riscv.sifive.u.l2lim",
+ memmap[SIFIVE_U_L2LIM].size, &error_fatal);
+ memory_region_add_subregion(system_memory, memmap[SIFIVE_U_L2LIM].base,
+ l2lim_mem);
+
/* create PLIC hart topology configuration string */
plic_hart_config_len = (strlen(SIFIVE_U_PLIC_HART_CONFIG) + 1) *
ms->smp.cpus;
@@ -522,17 +578,6 @@ static void riscv_sifive_u_soc_realize(DeviceState *dev, Error **errp)
memmap[SIFIVE_U_GEM_MGMT].base, memmap[SIFIVE_U_GEM_MGMT].size);
}
-static void riscv_sifive_u_machine_init(MachineClass *mc)
-{
- mc->desc = "RISC-V Board compatible with SiFive U SDK";
- mc->init = riscv_sifive_u_init;
- mc->max_cpus = SIFIVE_U_MANAGEMENT_CPU_COUNT + SIFIVE_U_COMPUTE_CPU_COUNT;
- mc->min_cpus = SIFIVE_U_MANAGEMENT_CPU_COUNT + 1;
- mc->default_cpus = mc->min_cpus;
-}
-
-DEFINE_MACHINE("sifive_u", riscv_sifive_u_machine_init)
-
static void riscv_sifive_u_soc_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
@@ -556,3 +601,29 @@ static void riscv_sifive_u_soc_register_types(void)
}
type_init(riscv_sifive_u_soc_register_types)
+
+static void riscv_sifive_u_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "RISC-V Board compatible with SiFive U SDK";
+ mc->init = riscv_sifive_u_init;
+ mc->max_cpus = SIFIVE_U_MANAGEMENT_CPU_COUNT + SIFIVE_U_COMPUTE_CPU_COUNT;
+ mc->min_cpus = SIFIVE_U_MANAGEMENT_CPU_COUNT + 1;
+ mc->default_cpus = mc->min_cpus;
+}
+
+static const TypeInfo riscv_sifive_u_machine_typeinfo = {
+ .name = MACHINE_TYPE_NAME("sifive_u"),
+ .parent = TYPE_MACHINE,
+ .class_init = riscv_sifive_u_machine_class_init,
+ .instance_init = riscv_sifive_u_machine_instance_init,
+ .instance_size = sizeof(SiFiveUState),
+};
+
+static void riscv_sifive_u_machine_init_register_types(void)
+{
+ type_register_static(&riscv_sifive_u_machine_typeinfo);
+}
+
+type_init(riscv_sifive_u_machine_init_register_types)
diff --git a/hw/riscv/spike.c b/hw/riscv/spike.c
index d60415d190..8bbffbcd0f 100644
--- a/hw/riscv/spike.c
+++ b/hw/riscv/spike.c
@@ -102,8 +102,6 @@ static void create_fdt(SpikeState *s, const struct MemmapEntry *memmap,
char *intc = g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu);
char *isa = riscv_isa_string(&s->soc.harts[cpu]);
qemu_fdt_add_subnode(fdt, nodename);
- qemu_fdt_setprop_cell(fdt, nodename, "clock-frequency",
- SPIKE_CLOCK_FREQ);
qemu_fdt_setprop_string(fdt, nodename, "mmu-type", "riscv,sv48");
qemu_fdt_setprop_string(fdt, nodename, "riscv,isa", isa);
qemu_fdt_setprop_string(fdt, nodename, "compatible", "riscv");
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index d36f5625ec..cc8f311e6b 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -26,6 +26,7 @@
#include "hw/boards.h"
#include "hw/loader.h"
#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
#include "hw/char/serial.h"
#include "target/riscv/cpu.h"
#include "hw/riscv/riscv_hart.h"
@@ -61,12 +62,77 @@ static const struct MemmapEntry {
[VIRT_PLIC] = { 0xc000000, 0x4000000 },
[VIRT_UART0] = { 0x10000000, 0x100 },
[VIRT_VIRTIO] = { 0x10001000, 0x1000 },
+ [VIRT_FLASH] = { 0x20000000, 0x2000000 },
[VIRT_DRAM] = { 0x80000000, 0x0 },
[VIRT_PCIE_MMIO] = { 0x40000000, 0x40000000 },
[VIRT_PCIE_PIO] = { 0x03000000, 0x00010000 },
[VIRT_PCIE_ECAM] = { 0x30000000, 0x10000000 },
};
+#define VIRT_FLASH_SECTOR_SIZE (256 * KiB)
+
+static PFlashCFI01 *virt_flash_create1(RISCVVirtState *s,
+ const char *name,
+ const char *alias_prop_name)
+{
+ /*
+ * Create a single flash device. We use the same parameters as
+ * the flash devices on the ARM virt board.
+ */
+ DeviceState *dev = qdev_create(NULL, TYPE_PFLASH_CFI01);
+
+ qdev_prop_set_uint64(dev, "sector-length", VIRT_FLASH_SECTOR_SIZE);
+ qdev_prop_set_uint8(dev, "width", 4);
+ qdev_prop_set_uint8(dev, "device-width", 2);
+ qdev_prop_set_bit(dev, "big-endian", false);
+ qdev_prop_set_uint16(dev, "id0", 0x89);
+ qdev_prop_set_uint16(dev, "id1", 0x18);
+ qdev_prop_set_uint16(dev, "id2", 0x00);
+ qdev_prop_set_uint16(dev, "id3", 0x00);
+ qdev_prop_set_string(dev, "name", name);
+
+ object_property_add_child(OBJECT(s), name, OBJECT(dev),
+ &error_abort);
+ object_property_add_alias(OBJECT(s), alias_prop_name,
+ OBJECT(dev), "drive", &error_abort);
+
+ return PFLASH_CFI01(dev);
+}
+
+static void virt_flash_create(RISCVVirtState *s)
+{
+ s->flash[0] = virt_flash_create1(s, "virt.flash0", "pflash0");
+ s->flash[1] = virt_flash_create1(s, "virt.flash1", "pflash1");
+}
+
+static void virt_flash_map1(PFlashCFI01 *flash,
+ hwaddr base, hwaddr size,
+ MemoryRegion *sysmem)
+{
+ DeviceState *dev = DEVICE(flash);
+
+ assert(size % VIRT_FLASH_SECTOR_SIZE == 0);
+ assert(size / VIRT_FLASH_SECTOR_SIZE <= UINT32_MAX);
+ qdev_prop_set_uint32(dev, "num-blocks", size / VIRT_FLASH_SECTOR_SIZE);
+ qdev_init_nofail(dev);
+
+ memory_region_add_subregion(sysmem, base,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(dev),
+ 0));
+}
+
+static void virt_flash_map(RISCVVirtState *s,
+ MemoryRegion *sysmem)
+{
+ hwaddr flashsize = virt_memmap[VIRT_FLASH].size / 2;
+ hwaddr flashbase = virt_memmap[VIRT_FLASH].base;
+
+ virt_flash_map1(s->flash[0], flashbase, flashsize,
+ sysmem);
+ virt_flash_map1(s->flash[1], flashbase + flashsize, flashsize,
+ sysmem);
+}
+
static void create_pcie_irq_map(void *fdt, char *nodename,
uint32_t plic_phandle)
{
@@ -121,6 +187,8 @@ static void create_fdt(RISCVVirtState *s, const struct MemmapEntry *memmap,
char *nodename;
uint32_t plic_phandle, phandle = 1;
int i;
+ hwaddr flashsize = virt_memmap[VIRT_FLASH].size / 2;
+ hwaddr flashbase = virt_memmap[VIRT_FLASH].base;
fdt = s->fdt = create_device_tree(&s->fdt_size);
if (!fdt) {
@@ -161,8 +229,6 @@ static void create_fdt(RISCVVirtState *s, const struct MemmapEntry *memmap,
char *intc = g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu);
char *isa = riscv_isa_string(&s->soc.harts[cpu]);
qemu_fdt_add_subnode(fdt, nodename);
- qemu_fdt_setprop_cell(fdt, nodename, "clock-frequency",
- VIRT_CLOCK_FREQ);
qemu_fdt_setprop_string(fdt, nodename, "mmu-type", "riscv,sv48");
qemu_fdt_setprop_string(fdt, nodename, "riscv,isa", isa);
qemu_fdt_setprop_string(fdt, nodename, "compatible", "riscv");
@@ -316,6 +382,15 @@ static void create_fdt(RISCVVirtState *s, const struct MemmapEntry *memmap,
qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", cmdline);
}
g_free(nodename);
+
+ nodename = g_strdup_printf("/flash@%" PRIx64, flashbase);
+ qemu_fdt_add_subnode(s->fdt, nodename);
+ qemu_fdt_setprop_string(s->fdt, nodename, "compatible", "cfi-flash");
+ qemu_fdt_setprop_sized_cells(s->fdt, nodename, "reg",
+ 2, flashbase, 2, flashsize,
+ 2, flashbase + flashsize, 2, flashsize);
+ qemu_fdt_setprop_cell(s->fdt, nodename, "bank-width", 4);
+ g_free(nodename);
}
@@ -362,13 +437,13 @@ static inline DeviceState *gpex_pcie_init(MemoryRegion *sys_mem,
static void riscv_virt_board_init(MachineState *machine)
{
const struct MemmapEntry *memmap = virt_memmap;
-
- RISCVVirtState *s = g_new0(RISCVVirtState, 1);
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(machine);
MemoryRegion *system_memory = get_system_memory();
MemoryRegion *main_mem = g_new(MemoryRegion, 1);
MemoryRegion *mask_rom = g_new(MemoryRegion, 1);
char *plic_hart_config;
size_t plic_hart_config_len;
+ target_ulong start_addr = memmap[VIRT_DRAM].base;
int i;
unsigned int smp_cpus = machine->smp.cpus;
@@ -415,6 +490,14 @@ static void riscv_virt_board_init(MachineState *machine)
}
}
+ if (drive_get(IF_PFLASH, 0, 0)) {
+ /*
+ * Pflash was supplied, let's overwrite the address we jump to after
+ * reset to the base of the flash.
+ */
+ start_addr = virt_memmap[VIRT_FLASH].base;
+ }
+
/* reset vector */
uint32_t reset_vec[8] = {
0x00000297, /* 1: auipc t0, %pcrel_hi(dtb) */
@@ -427,7 +510,7 @@ static void riscv_virt_board_init(MachineState *machine)
#endif
0x00028067, /* jr t0 */
0x00000000,
- memmap[VIRT_DRAM].base, /* start: .dword memmap[VIRT_DRAM].base */
+ start_addr, /* start: .dword */
0x00000000,
/* dtb: */
};
@@ -496,15 +579,43 @@ static void riscv_virt_board_init(MachineState *machine)
0, qdev_get_gpio_in(DEVICE(s->plic), UART0_IRQ), 399193,
serial_hd(0), DEVICE_LITTLE_ENDIAN);
+ virt_flash_create(s);
+
+ for (i = 0; i < ARRAY_SIZE(s->flash); i++) {
+ /* Map legacy -drive if=pflash to machine properties */
+ pflash_cfi01_legacy_drive(s->flash[i],
+ drive_get(IF_PFLASH, 0, i));
+ }
+ virt_flash_map(s, system_memory);
+
g_free(plic_hart_config);
}
-static void riscv_virt_board_machine_init(MachineClass *mc)
+static void riscv_virt_machine_instance_init(Object *obj)
{
- mc->desc = "RISC-V VirtIO Board (Privileged ISA v1.10)";
+}
+
+static void riscv_virt_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "RISC-V VirtIO board";
mc->init = riscv_virt_board_init;
- mc->max_cpus = 8; /* hardcoded limit in BBL */
+ mc->max_cpus = 8;
mc->default_cpu_type = VIRT_CPU;
}
-DEFINE_MACHINE("virt", riscv_virt_board_machine_init)
+static const TypeInfo riscv_virt_machine_typeinfo = {
+ .name = MACHINE_TYPE_NAME("virt"),
+ .parent = TYPE_MACHINE,
+ .class_init = riscv_virt_machine_class_init,
+ .instance_init = riscv_virt_machine_instance_init,
+ .instance_size = sizeof(RISCVVirtState),
+};
+
+static void riscv_virt_machine_init_register_types(void)
+{
+ type_register_static(&riscv_virt_machine_typeinfo);
+}
+
+type_init(riscv_virt_machine_init_register_types)
diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c
index 841d79b60e..f8fc30cccb 100644
--- a/hw/scsi/esp.c
+++ b/hw/scsi/esp.c
@@ -38,6 +38,8 @@
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
* and
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR53C9X.txt
+ *
+ * On Macintosh Quadra it is a NCR53C96.
*/
static void esp_raise_irq(ESPState *s)
@@ -58,6 +60,16 @@ static void esp_lower_irq(ESPState *s)
}
}
+static void esp_raise_drq(ESPState *s)
+{
+ qemu_irq_raise(s->irq_data);
+}
+
+static void esp_lower_drq(ESPState *s)
+{
+ qemu_irq_lower(s->irq_data);
+}
+
void esp_dma_enable(ESPState *s, int irq, int level)
{
if (level) {
@@ -84,6 +96,58 @@ void esp_request_cancelled(SCSIRequest *req)
}
}
+static void set_pdma(ESPState *s, enum pdma_origin_id origin,
+ uint32_t index, uint32_t len)
+{
+ s->pdma_origin = origin;
+ s->pdma_start = index;
+ s->pdma_cur = index;
+ s->pdma_len = len;
+}
+
+static uint8_t *get_pdma_buf(ESPState *s)
+{
+ switch (s->pdma_origin) {
+ case PDMA:
+ return s->pdma_buf;
+ case TI:
+ return s->ti_buf;
+ case CMD:
+ return s->cmdbuf;
+ case ASYNC:
+ return s->async_buf;
+ }
+ return NULL;
+}
+
+static int get_cmd_cb(ESPState *s)
+{
+ int target;
+
+ target = s->wregs[ESP_WBUSID] & BUSID_DID;
+
+ s->ti_size = 0;
+ s->ti_rptr = 0;
+ s->ti_wptr = 0;
+
+ if (s->current_req) {
+ /* Started a new command before the old one finished. Cancel it. */
+ scsi_req_cancel(s->current_req);
+ s->async_len = 0;
+ }
+
+ s->current_dev = scsi_device_find(&s->bus, 0, target, 0);
+ if (!s->current_dev) {
+ /* No such drive */
+ s->rregs[ESP_RSTAT] = 0;
+ s->rregs[ESP_RINTR] = INTR_DC;
+ s->rregs[ESP_RSEQ] = SEQ_0;
+ esp_raise_irq(s);
+ return -1;
+ }
+ return 0;
+}
+
static uint32_t get_cmd(ESPState *s, uint8_t *buf, uint8_t buflen)
{
uint32_t dmalen;
@@ -97,7 +161,14 @@ static uint32_t get_cmd(ESPState *s, uint8_t *buf, uint8_t buflen)
if (dmalen > buflen) {
return 0;
}
- s->dma_memory_read(s->dma_opaque, buf, dmalen);
+ if (s->dma_memory_read) {
+ s->dma_memory_read(s->dma_opaque, buf, dmalen);
+ } else {
+ memcpy(s->pdma_buf, buf, dmalen);
+ set_pdma(s, PDMA, 0, dmalen);
+ esp_raise_drq(s);
+ return 0;
+ }
} else {
dmalen = s->ti_size;
if (dmalen > TI_BUFSZ) {
@@ -108,23 +179,7 @@ static uint32_t get_cmd(ESPState *s, uint8_t *buf, uint8_t buflen)
}
trace_esp_get_cmd(dmalen, target);
- s->ti_size = 0;
- s->ti_rptr = 0;
- s->ti_wptr = 0;
-
- if (s->current_req) {
- /* Started a new command before the old one finished. Cancel it. */
- scsi_req_cancel(s->current_req);
- s->async_len = 0;
- }
-
- s->current_dev = scsi_device_find(&s->bus, 0, target, 0);
- if (!s->current_dev) {
- // No such drive
- s->rregs[ESP_RSTAT] = 0;
- s->rregs[ESP_RINTR] = INTR_DC;
- s->rregs[ESP_RSEQ] = SEQ_0;
- esp_raise_irq(s);
+ if (get_cmd_cb(s) < 0) {
return 0;
}
return dmalen;
@@ -165,6 +220,16 @@ static void do_cmd(ESPState *s, uint8_t *buf)
do_busid_cmd(s, &buf[1], busid);
}
+static void satn_pdma_cb(ESPState *s)
+{
+ if (get_cmd_cb(s) < 0) {
+ return;
+ }
+ if (s->pdma_cur != s->pdma_start) {
+ do_cmd(s, get_pdma_buf(s) + s->pdma_start);
+ }
+}
+
static void handle_satn(ESPState *s)
{
uint8_t buf[32];
@@ -174,11 +239,22 @@ static void handle_satn(ESPState *s)
s->dma_cb = handle_satn;
return;
}
+ s->pdma_cb = satn_pdma_cb;
len = get_cmd(s, buf, sizeof(buf));
if (len)
do_cmd(s, buf);
}
+static void s_without_satn_pdma_cb(ESPState *s)
+{
+ if (get_cmd_cb(s) < 0) {
+ return;
+ }
+ if (s->pdma_cur != s->pdma_start) {
+ do_busid_cmd(s, get_pdma_buf(s) + s->pdma_start, 0);
+ }
+}
+
static void handle_s_without_atn(ESPState *s)
{
uint8_t buf[32];
@@ -188,18 +264,36 @@ static void handle_s_without_atn(ESPState *s)
s->dma_cb = handle_s_without_atn;
return;
}
+ s->pdma_cb = s_without_satn_pdma_cb;
len = get_cmd(s, buf, sizeof(buf));
if (len) {
do_busid_cmd(s, buf, 0);
}
}
+static void satn_stop_pdma_cb(ESPState *s)
+{
+ if (get_cmd_cb(s) < 0) {
+ return;
+ }
+ s->cmdlen = s->pdma_cur - s->pdma_start;
+ if (s->cmdlen) {
+ trace_esp_handle_satn_stop(s->cmdlen);
+ s->do_cmd = 1;
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_CD;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ esp_raise_irq(s);
+ }
+}
+
static void handle_satn_stop(ESPState *s)
{
if (s->dma && !s->dma_enabled) {
s->dma_cb = handle_satn_stop;
return;
}
+ s->pdma_cb = satn_stop_pdma_cb;;
s->cmdlen = get_cmd(s, s->cmdbuf, sizeof(s->cmdbuf));
if (s->cmdlen) {
trace_esp_handle_satn_stop(s->cmdlen);
@@ -211,16 +305,31 @@ static void handle_satn_stop(ESPState *s)
}
}
+static void write_response_pdma_cb(ESPState *s)
+{
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ esp_raise_irq(s);
+}
+
static void write_response(ESPState *s)
{
trace_esp_write_response(s->status);
s->ti_buf[0] = s->status;
s->ti_buf[1] = 0;
if (s->dma) {
- s->dma_memory_write(s->dma_opaque, s->ti_buf, 2);
- s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
- s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
- s->rregs[ESP_RSEQ] = SEQ_CD;
+ if (s->dma_memory_write) {
+ s->dma_memory_write(s->dma_opaque, s->ti_buf, 2);
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ } else {
+ set_pdma(s, TI, 0, 2);
+ s->pdma_cb = write_response_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
} else {
s->ti_size = 2;
s->ti_rptr = 0;
@@ -242,6 +351,41 @@ static void esp_dma_done(ESPState *s)
esp_raise_irq(s);
}
+static void do_dma_pdma_cb(ESPState *s)
+{
+ int to_device = (s->ti_size < 0);
+ int len = s->pdma_cur - s->pdma_start;
+ if (s->do_cmd) {
+ s->ti_size = 0;
+ s->cmdlen = 0;
+ s->do_cmd = 0;
+ do_cmd(s, s->cmdbuf);
+ return;
+ }
+ s->dma_left -= len;
+ s->async_buf += len;
+ s->async_len -= len;
+ if (to_device) {
+ s->ti_size += len;
+ } else {
+ s->ti_size -= len;
+ }
+ if (s->async_len == 0) {
+ scsi_req_continue(s->current_req);
+ /*
+ * If there is still data to be read from the device then
+ * complete the DMA operation immediately. Otherwise defer
+ * until the scsi layer has completed.
+ */
+ if (to_device || s->dma_left != 0 || s->ti_size == 0) {
+ return;
+ }
+ }
+
+ /* Partially filled a scsi buffer. Complete immediately. */
+ esp_dma_done(s);
+}
+
static void esp_do_dma(ESPState *s)
{
uint32_t len;
@@ -249,10 +393,26 @@ static void esp_do_dma(ESPState *s)
len = s->dma_left;
if (s->do_cmd) {
+ /*
+ * handle_ti_cmd() case: esp_do_dma() is called only from
+ * handle_ti_cmd() with do_cmd != NULL (see the assert())
+ */
trace_esp_do_dma(s->cmdlen, len);
assert (s->cmdlen <= sizeof(s->cmdbuf) &&
len <= sizeof(s->cmdbuf) - s->cmdlen);
- s->dma_memory_read(s->dma_opaque, &s->cmdbuf[s->cmdlen], len);
+ if (s->dma_memory_read) {
+ s->dma_memory_read(s->dma_opaque, &s->cmdbuf[s->cmdlen], len);
+ } else {
+ set_pdma(s, CMD, s->cmdlen, len);
+ s->pdma_cb = do_dma_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
+ trace_esp_handle_ti_cmd(s->cmdlen);
+ s->ti_size = 0;
+ s->cmdlen = 0;
+ s->do_cmd = 0;
+ do_cmd(s, s->cmdbuf);
return;
}
if (s->async_len == 0) {
@@ -264,9 +424,23 @@ static void esp_do_dma(ESPState *s)
}
to_device = (s->ti_size < 0);
if (to_device) {
- s->dma_memory_read(s->dma_opaque, s->async_buf, len);
+ if (s->dma_memory_read) {
+ s->dma_memory_read(s->dma_opaque, s->async_buf, len);
+ } else {
+ set_pdma(s, ASYNC, 0, len);
+ s->pdma_cb = do_dma_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
} else {
- s->dma_memory_write(s->dma_opaque, s->async_buf, len);
+ if (s->dma_memory_write) {
+ s->dma_memory_write(s->dma_opaque, s->async_buf, len);
+ } else {
+ set_pdma(s, ASYNC, 0, len);
+ s->pdma_cb = do_dma_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
}
s->dma_left -= len;
s->async_buf += len;
@@ -373,8 +547,7 @@ static void handle_ti(ESPState *s)
s->dma_left = minlen;
s->rregs[ESP_RSTAT] &= ~STAT_TC;
esp_do_dma(s);
- }
- if (s->do_cmd) {
+ } else if (s->do_cmd) {
trace_esp_handle_ti_cmd(s->cmdlen);
s->ti_size = 0;
s->cmdlen = 0;
@@ -401,6 +574,7 @@ void esp_hard_reset(ESPState *s)
static void esp_soft_reset(ESPState *s)
{
qemu_irq_lower(s->irq);
+ qemu_irq_lower(s->irq_data);
esp_hard_reset(s);
}
@@ -590,6 +764,28 @@ static bool esp_mem_accepts(void *opaque, hwaddr addr,
return (size == 1) || (is_write && size == 4);
}
+static bool esp_pdma_needed(void *opaque)
+{
+ ESPState *s = opaque;
+ return s->dma_memory_read == NULL && s->dma_memory_write == NULL &&
+ s->dma_enabled;
+}
+
+static const VMStateDescription vmstate_esp_pdma = {
+ .name = "esp/pdma",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = esp_pdma_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(pdma_buf, ESPState),
+ VMSTATE_INT32(pdma_origin, ESPState),
+ VMSTATE_UINT32(pdma_len, ESPState),
+ VMSTATE_UINT32(pdma_start, ESPState),
+ VMSTATE_UINT32(pdma_cur, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
const VMStateDescription vmstate_esp = {
.name ="esp",
.version_id = 4,
@@ -611,6 +807,10 @@ const VMStateDescription vmstate_esp = {
VMSTATE_UINT32(do_cmd, ESPState),
VMSTATE_UINT32(dma_left, ESPState),
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription * []) {
+ &vmstate_esp_pdma,
+ NULL
}
};
@@ -641,6 +841,82 @@ static const MemoryRegionOps sysbus_esp_mem_ops = {
.valid.accepts = esp_mem_accepts,
};
+static void sysbus_esp_pdma_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = &sysbus->esp;
+ uint32_t dmalen;
+ uint8_t *buf = get_pdma_buf(s);
+
+ dmalen = s->rregs[ESP_TCLO];
+ dmalen |= s->rregs[ESP_TCMID] << 8;
+ dmalen |= s->rregs[ESP_TCHI] << 16;
+ if (dmalen == 0 || s->pdma_len == 0) {
+ return;
+ }
+ switch (size) {
+ case 1:
+ buf[s->pdma_cur++] = val;
+ s->pdma_len--;
+ dmalen--;
+ break;
+ case 2:
+ buf[s->pdma_cur++] = val >> 8;
+ buf[s->pdma_cur++] = val;
+ s->pdma_len -= 2;
+ dmalen -= 2;
+ break;
+ }
+ s->rregs[ESP_TCLO] = dmalen & 0xff;
+ s->rregs[ESP_TCMID] = dmalen >> 8;
+ s->rregs[ESP_TCHI] = dmalen >> 16;
+ if (s->pdma_len == 0 && s->pdma_cb) {
+ esp_lower_drq(s);
+ s->pdma_cb(s);
+ s->pdma_cb = NULL;
+ }
+}
+
+static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = &sysbus->esp;
+ uint8_t *buf = get_pdma_buf(s);
+ uint64_t val = 0;
+
+ if (s->pdma_len == 0) {
+ return 0;
+ }
+ switch (size) {
+ case 1:
+ val = buf[s->pdma_cur++];
+ s->pdma_len--;
+ break;
+ case 2:
+ val = buf[s->pdma_cur++];
+ val = (val << 8) | buf[s->pdma_cur++];
+ s->pdma_len -= 2;
+ break;
+ }
+
+ if (s->pdma_len == 0 && s->pdma_cb) {
+ esp_lower_drq(s);
+ s->pdma_cb(s);
+ s->pdma_cb = NULL;
+ }
+ return val;
+}
+
+static const MemoryRegionOps sysbus_esp_pdma_ops = {
+ .read = sysbus_esp_pdma_read,
+ .write = sysbus_esp_pdma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 2,
+};
+
static const struct SCSIBusInfo esp_scsi_info = {
.tcq = false,
.max_target = ESP_MAX_DEVS,
@@ -673,12 +949,16 @@ static void sysbus_esp_realize(DeviceState *dev, Error **errp)
ESPState *s = &sysbus->esp;
sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->irq_data);
assert(sysbus->it_shift != -1);
s->chip_id = TCHI_FAS100A;
memory_region_init_io(&sysbus->iomem, OBJECT(sysbus), &sysbus_esp_mem_ops,
- sysbus, "esp", ESP_REGS << sysbus->it_shift);
+ sysbus, "esp-regs", ESP_REGS << sysbus->it_shift);
sysbus_init_mmio(sbd, &sysbus->iomem);
+ memory_region_init_io(&sysbus->pdma, OBJECT(sysbus), &sysbus_esp_pdma_ops,
+ sysbus, "esp-pdma", 2);
+ sysbus_init_mmio(sbd, &sysbus->pdma);
qdev_init_gpio_in(dev, sysbus_esp_gpio_demux, 2);
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index 12fac39804..e6569a7968 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -40,6 +40,7 @@
#include "pci.h"
#include "trace.h"
#include "qapi/error.h"
+#include "migration/blocker.h"
#define TYPE_VFIO_PCI "vfio-pci"
#define PCI_VFIO(obj) OBJECT_CHECK(VFIOPCIDevice, obj, TYPE_VFIO_PCI)
@@ -2732,6 +2733,17 @@ static void vfio_realize(PCIDevice *pdev, Error **errp)
return;
}
+ if (!pdev->failover_pair_id) {
+ error_setg(&vdev->migration_blocker,
+ "VFIO device doesn't support migration");
+ ret = migrate_add_blocker(vdev->migration_blocker, &err);
+ if (err) {
+ error_propagate(errp, err);
+ error_free(vdev->migration_blocker);
+ return;
+ }
+ }
+
vdev->vbasedev.name = g_path_get_basename(vdev->vbasedev.sysfsdev);
vdev->vbasedev.ops = &vfio_pci_ops;
vdev->vbasedev.type = VFIO_DEVICE_TYPE_PCI;
@@ -3008,6 +3020,10 @@ out_teardown:
vfio_bars_exit(vdev);
error:
error_prepend(errp, VFIO_MSG_PREFIX, vdev->vbasedev.name);
+ if (vdev->migration_blocker) {
+ migrate_del_blocker(vdev->migration_blocker);
+ error_free(vdev->migration_blocker);
+ }
}
static void vfio_instance_finalize(Object *obj)
@@ -3019,6 +3035,10 @@ static void vfio_instance_finalize(Object *obj)
vfio_bars_finalize(vdev);
g_free(vdev->emulated_config_bits);
g_free(vdev->rom);
+ if (vdev->migration_blocker) {
+ migrate_del_blocker(vdev->migration_blocker);
+ error_free(vdev->migration_blocker);
+ }
/*
* XXX Leaking igd_opregion is not an oversight, we can't remove the
* fw_cfg entry therefore leaking this allocation seems like the safest
@@ -3151,11 +3171,6 @@ static Property vfio_pci_dev_properties[] = {
DEFINE_PROP_END_OF_LIST(),
};
-static const VMStateDescription vfio_pci_vmstate = {
- .name = "vfio-pci",
- .unmigratable = 1,
-};
-
static void vfio_pci_dev_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
@@ -3163,7 +3178,6 @@ static void vfio_pci_dev_class_init(ObjectClass *klass, void *data)
dc->reset = vfio_pci_reset;
dc->props = vfio_pci_dev_properties;
- dc->vmsd = &vfio_pci_vmstate;
dc->desc = "VFIO-based PCI device assignment";
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
pdc->realize = vfio_realize;
diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h
index 834a90d646..b329d50338 100644
--- a/hw/vfio/pci.h
+++ b/hw/vfio/pci.h
@@ -168,6 +168,7 @@ typedef struct VFIOPCIDevice {
bool no_vfio_ioeventfd;
bool enable_ramfb;
VFIODisplay *dpy;
+ Error *migration_blocker;
} VFIOPCIDevice;
uint32_t vfio_pci_read_config(PCIDevice *pdev, uint32_t addr, int len);
diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
index 99de5f196f..4da0d5a6c5 100644
--- a/hw/virtio/vhost.c
+++ b/hw/virtio/vhost.c
@@ -924,7 +924,7 @@ int vhost_device_iotlb_miss(struct vhost_dev *dev, uint64_t iova, int write)
uint64_t uaddr, len;
int ret = -EFAULT;
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
trace_vhost_iotlb_miss(dev, 1);
@@ -956,8 +956,6 @@ int vhost_device_iotlb_miss(struct vhost_dev *dev, uint64_t iova, int write)
trace_vhost_iotlb_miss(dev, 2);
out:
- rcu_read_unlock();
-
return ret;
}
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index 2e91dec567..762df12f4c 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -387,7 +387,8 @@ static inline void vring_set_avail_event(VirtQueue *vq, uint16_t val)
static void virtio_queue_split_set_notification(VirtQueue *vq, int enable)
{
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
+
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_RING_F_EVENT_IDX)) {
vring_set_avail_event(vq, vring_avail_idx(vq));
} else if (enable) {
@@ -399,7 +400,6 @@ static void virtio_queue_split_set_notification(VirtQueue *vq, int enable)
/* Expose avail event/used flags before caller checks the avail idx. */
smp_mb();
}
- rcu_read_unlock();
}
static void virtio_queue_packed_set_notification(VirtQueue *vq, int enable)
@@ -408,7 +408,7 @@ static void virtio_queue_packed_set_notification(VirtQueue *vq, int enable)
VRingPackedDescEvent e;
VRingMemoryRegionCaches *caches;
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
caches = vring_get_region_caches(vq);
vring_packed_event_read(vq->vdev, &caches->used, &e);
@@ -429,7 +429,6 @@ static void virtio_queue_packed_set_notification(VirtQueue *vq, int enable)
/* Expose avail event/used flags before caller checks the avail idx. */
smp_mb();
}
- rcu_read_unlock();
}
void virtio_queue_set_notification(VirtQueue *vq, int enable)
@@ -577,9 +576,8 @@ static int virtio_queue_split_empty(VirtQueue *vq)
return 0;
}
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
empty = vring_avail_idx(vq) == vq->last_avail_idx;
- rcu_read_unlock();
return empty;
}
@@ -601,12 +599,8 @@ static int virtio_queue_packed_empty_rcu(VirtQueue *vq)
static int virtio_queue_packed_empty(VirtQueue *vq)
{
- bool empty;
-
- rcu_read_lock();
- empty = virtio_queue_packed_empty_rcu(vq);
- rcu_read_unlock();
- return empty;
+ RCU_READ_LOCK_GUARD();
+ return virtio_queue_packed_empty_rcu(vq);
}
int virtio_queue_empty(VirtQueue *vq)
@@ -859,10 +853,9 @@ void virtqueue_flush(VirtQueue *vq, unsigned int count)
void virtqueue_push(VirtQueue *vq, const VirtQueueElement *elem,
unsigned int len)
{
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
virtqueue_fill(vq, elem, len, 0);
virtqueue_flush(vq, 1);
- rcu_read_unlock();
}
/* Called within rcu_read_lock(). */
@@ -943,7 +936,8 @@ static void virtqueue_split_get_avail_bytes(VirtQueue *vq,
int64_t len = 0;
int rc;
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
+
idx = vq->last_avail_idx;
total_bufs = in_total = out_total = 0;
@@ -1033,7 +1027,6 @@ done:
if (out_bytes) {
*out_bytes = out_total;
}
- rcu_read_unlock();
return;
err:
@@ -1083,7 +1076,7 @@ static void virtqueue_packed_get_avail_bytes(VirtQueue *vq,
VRingPackedDesc desc;
bool wrap_counter;
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
idx = vq->last_avail_idx;
wrap_counter = vq->last_avail_wrap_counter;
total_bufs = in_total = out_total = 0;
@@ -1176,7 +1169,6 @@ done:
if (out_bytes) {
*out_bytes = out_total;
}
- rcu_read_unlock();
return;
err:
@@ -1360,7 +1352,7 @@ static void *virtqueue_split_pop(VirtQueue *vq, size_t sz)
VRingDesc desc;
int rc;
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
if (virtio_queue_empty_rcu(vq)) {
goto done;
}
@@ -1469,7 +1461,6 @@ static void *virtqueue_split_pop(VirtQueue *vq, size_t sz)
trace_virtqueue_pop(vq, elem, elem->in_num, elem->out_num);
done:
address_space_cache_destroy(&indirect_desc_cache);
- rcu_read_unlock();
return elem;
@@ -1494,7 +1485,7 @@ static void *virtqueue_packed_pop(VirtQueue *vq, size_t sz)
uint16_t id;
int rc;
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
if (virtio_queue_packed_empty_rcu(vq)) {
goto done;
}
@@ -1600,7 +1591,6 @@ static void *virtqueue_packed_pop(VirtQueue *vq, size_t sz)
trace_virtqueue_pop(vq, elem, elem->in_num, elem->out_num);
done:
address_space_cache_destroy(&indirect_desc_cache);
- rcu_read_unlock();
return elem;
@@ -2437,13 +2427,10 @@ static bool virtio_should_notify(VirtIODevice *vdev, VirtQueue *vq)
void virtio_notify_irqfd(VirtIODevice *vdev, VirtQueue *vq)
{
- bool should_notify;
- rcu_read_lock();
- should_notify = virtio_should_notify(vdev, vq);
- rcu_read_unlock();
-
- if (!should_notify) {
- return;
+ WITH_RCU_READ_LOCK_GUARD() {
+ if (!virtio_should_notify(vdev, vq)) {
+ return;
+ }
}
trace_virtio_notify_irqfd(vdev, vq);
@@ -2475,13 +2462,10 @@ static void virtio_irq(VirtQueue *vq)
void virtio_notify(VirtIODevice *vdev, VirtQueue *vq)
{
- bool should_notify;
- rcu_read_lock();
- should_notify = virtio_should_notify(vdev, vq);
- rcu_read_unlock();
-
- if (!should_notify) {
- return;
+ WITH_RCU_READ_LOCK_GUARD() {
+ if (!virtio_should_notify(vdev, vq)) {
+ return;
+ }
}
trace_virtio_notify(vdev, vq);
@@ -3032,7 +3016,7 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
vdev->start_on_kick = true;
}
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
for (i = 0; i < num; i++) {
if (vdev->vq[i].vring.desc) {
uint16_t nheads;
@@ -3087,7 +3071,13 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
}
}
}
- rcu_read_unlock();
+
+ if (vdc->post_load) {
+ ret = vdc->post_load(vdev);
+ if (ret) {
+ return ret;
+ }
+ }
return 0;
}
@@ -3290,12 +3280,11 @@ static void virtio_queue_packed_restore_last_avail_idx(VirtIODevice *vdev,
static void virtio_queue_split_restore_last_avail_idx(VirtIODevice *vdev,
int n)
{
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
if (vdev->vq[n].vring.desc) {
vdev->vq[n].last_avail_idx = vring_used_idx(&vdev->vq[n]);
vdev->vq[n].shadow_avail_idx = vdev->vq[n].last_avail_idx;
}
- rcu_read_unlock();
}
void virtio_queue_restore_last_avail_idx(VirtIODevice *vdev, int n)
@@ -3315,11 +3304,10 @@ static void virtio_queue_packed_update_used_idx(VirtIODevice *vdev, int n)
static void virtio_split_packed_update_used_idx(VirtIODevice *vdev, int n)
{
- rcu_read_lock();
+ RCU_READ_LOCK_GUARD();
if (vdev->vq[n].vring.desc) {
vdev->vq[n].used_idx = vring_used_idx(&vdev->vq[n]);
}
- rcu_read_unlock();
}
void virtio_queue_update_used_idx(VirtIODevice *vdev, int n)
diff --git a/include/block/block-copy.h b/include/block/block-copy.h
index e2e135ff1b..0a161724d7 100644
--- a/include/block/block-copy.h
+++ b/include/block/block-copy.h
@@ -16,6 +16,7 @@
#define BLOCK_COPY_H
#include "block/block.h"
+#include "qemu/co-shared-resource.h"
typedef struct BlockCopyInFlightReq {
int64_t start_byte;
@@ -37,7 +38,7 @@ typedef struct BlockCopyState {
BdrvDirtyBitmap *copy_bitmap;
int64_t cluster_size;
bool use_copy_range;
- int64_t copy_range_size;
+ int64_t copy_size;
uint64_t len;
QLIST_HEAD(, BlockCopyInFlightReq) inflight_reqs;
@@ -69,6 +70,8 @@ typedef struct BlockCopyState {
*/
ProgressResetCallbackFunc progress_reset_callback;
void *progress_opaque;
+
+ SharedResource *mem;
} BlockCopyState;
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
diff --git a/include/block/block.h b/include/block/block.h
index 89606bd9f8..1df9848e74 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -346,10 +346,10 @@ BlockDriverState *bdrv_find_backing_image(BlockDriverState *bs,
const char *backing_file);
void bdrv_refresh_filename(BlockDriverState *bs);
-int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset,
+int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp);
-int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc,
- Error **errp);
+int bdrv_truncate(BdrvChild *child, int64_t offset, bool exact,
+ PreallocMode prealloc, Error **errp);
int64_t bdrv_nb_sectors(BlockDriverState *bs);
int64_t bdrv_getlength(BlockDriverState *bs);
diff --git a/include/block/block_int.h b/include/block/block_int.h
index ca4ccac4c1..02dc0034a2 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -334,8 +334,23 @@ struct BlockDriver {
* bdrv_parse_filename.
*/
const char *protocol_name;
+
+ /*
+ * Truncate @bs to @offset bytes using the given @prealloc mode
+ * when growing. Modes other than PREALLOC_MODE_OFF should be
+ * rejected when shrinking @bs.
+ *
+ * If @exact is true, @bs must be resized to exactly @offset.
+ * Otherwise, it is sufficient for @bs (if it is a host block
+ * device and thus there is no way to resize it) to be at least
+ * @offset bytes in length.
+ *
+ * If @exact is true and this function fails but would succeed
+ * with @exact = false, it should return -ENOTSUP.
+ */
int coroutine_fn (*bdrv_co_truncate)(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp);
+ bool exact, PreallocMode prealloc,
+ Error **errp);
int64_t (*bdrv_getlength)(BlockDriverState *bs);
bool has_variable_length;
diff --git a/include/block/nvme.h b/include/block/nvme.h
index 3ec8efcc43..ab5943b90a 100644
--- a/include/block/nvme.h
+++ b/include/block/nvme.h
@@ -653,12 +653,29 @@ typedef struct NvmeIdNs {
uint8_t mc;
uint8_t dpc;
uint8_t dps;
- uint8_t res30[98];
+
+ uint8_t nmic;
+ uint8_t rescap;
+ uint8_t fpi;
+ uint8_t dlfeat;
+
+ uint8_t res34[94];
NvmeLBAF lbaf[16];
uint8_t res192[192];
uint8_t vs[3712];
} NvmeIdNs;
+
+/*Deallocate Logical Block Features*/
+#define NVME_ID_NS_DLFEAT_GUARD_CRC(dlfeat) ((dlfeat) & 0x10)
+#define NVME_ID_NS_DLFEAT_WRITE_ZEROES(dlfeat) ((dlfeat) & 0x08)
+
+#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR(dlfeat) ((dlfeat) & 0x7)
+#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR_UNDEFINED 0
+#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR_ZEROES 1
+#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR_ONES 2
+
+
#define NVME_ID_NS_NSFEAT_THIN(nsfeat) ((nsfeat & 0x1))
#define NVME_ID_NS_FLBAS_EXTENDED(flbas) ((flbas >> 4) & 0x1)
#define NVME_ID_NS_FLBAS_INDEX(flbas) ((flbas & 0xf))
diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h
index ad9ab85eb3..e96781a455 100644
--- a/include/exec/cpu-all.h
+++ b/include/exec/cpu-all.h
@@ -210,17 +210,31 @@ static inline void stl_phys_notdirty(AddressSpace *as, hwaddr addr, uint32_t val
/* page related stuff */
#ifdef TARGET_PAGE_BITS_VARY
-extern bool target_page_bits_decided;
-extern int target_page_bits;
-#define TARGET_PAGE_BITS ({ assert(target_page_bits_decided); \
- target_page_bits; })
+typedef struct {
+ bool decided;
+ int bits;
+ target_long mask;
+} TargetPageBits;
+#if defined(CONFIG_ATTRIBUTE_ALIAS) || !defined(IN_EXEC_VARY)
+extern const TargetPageBits target_page;
+#else
+extern TargetPageBits target_page;
+#endif
+#ifdef CONFIG_DEBUG_TCG
+#define TARGET_PAGE_BITS ({ assert(target_page.decided); target_page.bits; })
+#define TARGET_PAGE_MASK ({ assert(target_page.decided); target_page.mask; })
+#else
+#define TARGET_PAGE_BITS target_page.bits
+#define TARGET_PAGE_MASK target_page.mask
+#endif
+#define TARGET_PAGE_SIZE (-(int)TARGET_PAGE_MASK)
#else
#define TARGET_PAGE_BITS_MIN TARGET_PAGE_BITS
+#define TARGET_PAGE_SIZE (1 << TARGET_PAGE_BITS)
+#define TARGET_PAGE_MASK ((target_long)-1 << TARGET_PAGE_BITS)
#endif
-#define TARGET_PAGE_SIZE (1 << TARGET_PAGE_BITS)
-#define TARGET_PAGE_MASK ~(TARGET_PAGE_SIZE - 1)
-#define TARGET_PAGE_ALIGN(addr) (((addr) + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK)
+#define TARGET_PAGE_ALIGN(addr) ROUND_UP((addr), TARGET_PAGE_SIZE)
/* Using intptr_t ensures that qemu_*_page_mask is sign-extended even
* when intptr_t is 32-bit and we are aligning a long long.
@@ -228,9 +242,8 @@ extern int target_page_bits;
extern uintptr_t qemu_host_page_size;
extern intptr_t qemu_host_page_mask;
-#define HOST_PAGE_ALIGN(addr) (((addr) + qemu_host_page_size - 1) & qemu_host_page_mask)
-#define REAL_HOST_PAGE_ALIGN(addr) (((addr) + qemu_real_host_page_size - 1) & \
- qemu_real_host_page_mask)
+#define HOST_PAGE_ALIGN(addr) ROUND_UP((addr), qemu_host_page_size)
+#define REAL_HOST_PAGE_ALIGN(addr) ROUND_UP((addr), qemu_real_host_page_size)
/* same as PROT_xxx */
#define PAGE_READ 0x0001
diff --git a/include/hw/block/swim.h b/include/hw/block/swim.h
new file mode 100644
index 0000000000..6add3499d0
--- /dev/null
+++ b/include/hw/block/swim.h
@@ -0,0 +1,76 @@
+/*
+ * QEMU Macintosh floppy disk controller emulator (SWIM)
+ *
+ * Copyright (c) 2014-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef SWIM_H
+#define SWIM_H
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+
+#define SWIM_MAX_FD 2
+
+typedef struct SWIMDrive SWIMDrive;
+typedef struct SWIMBus SWIMBus;
+typedef struct SWIMCtrl SWIMCtrl;
+
+#define TYPE_SWIM_DRIVE "swim-drive"
+#define SWIM_DRIVE(obj) OBJECT_CHECK(SWIMDrive, (obj), TYPE_SWIM_DRIVE)
+
+struct SWIMDrive {
+ DeviceState qdev;
+ int32_t unit;
+ BlockConf conf;
+};
+
+#define TYPE_SWIM_BUS "swim-bus"
+#define SWIM_BUS(obj) OBJECT_CHECK(SWIMBus, (obj), TYPE_SWIM_BUS)
+
+struct SWIMBus {
+ BusState bus;
+ struct SWIMCtrl *ctrl;
+};
+
+typedef struct FDrive {
+ SWIMCtrl *swimctrl;
+ BlockBackend *blk;
+ BlockConf *conf;
+} FDrive;
+
+struct SWIMCtrl {
+ MemoryRegion iomem;
+ FDrive drives[SWIM_MAX_FD];
+ int mode;
+ /* IWM mode */
+ int iwm_switch;
+ uint16_t regs[8];
+#define IWM_PH0 0
+#define IWM_PH1 1
+#define IWM_PH2 2
+#define IWM_PH3 3
+#define IWM_MTR 4
+#define IWM_DRIVE 5
+#define IWM_Q6 6
+#define IWM_Q7 7
+ uint8_t iwm_data;
+ uint8_t iwm_mode;
+ /* SWIM mode */
+ uint8_t swim_phase;
+ uint8_t swim_mode;
+ SWIMBus bus;
+};
+
+#define TYPE_SWIM "swim"
+#define SWIM(obj) OBJECT_CHECK(SWIM, (obj), TYPE_SWIM)
+
+typedef struct SWIM {
+ SysBusDevice parent_obj;
+ SWIMCtrl ctrl;
+} SWIM;
+#endif
diff --git a/include/hw/display/macfb.h b/include/hw/display/macfb.h
new file mode 100644
index 0000000000..26367ae2c4
--- /dev/null
+++ b/include/hw/display/macfb.h
@@ -0,0 +1,64 @@
+/*
+ * QEMU Motorola 680x0 Macintosh Video Card Emulation
+ * Copyright (c) 2012-2018 Laurent Vivier
+ *
+ * some parts from QEMU G364 framebuffer Emulator.
+ * Copyright (c) 2007-2011 Herve Poussineau
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef MACFB_H
+#define MACFB_H
+
+#include "qemu/osdep.h"
+#include "exec/memory.h"
+#include "ui/console.h"
+
+typedef struct MacfbState {
+ MemoryRegion mem_vram;
+ MemoryRegion mem_ctrl;
+ QemuConsole *con;
+
+ uint8_t *vram;
+ uint32_t vram_bit_mask;
+ uint32_t palette_current;
+ uint8_t color_palette[256 * 3];
+ uint32_t width, height; /* in pixels */
+ uint8_t depth;
+} MacfbState;
+
+#define TYPE_MACFB "sysbus-macfb"
+#define MACFB(obj) \
+ OBJECT_CHECK(MacfbSysBusState, (obj), TYPE_MACFB)
+
+typedef struct {
+ SysBusDevice busdev;
+
+ MacfbState macfb;
+} MacfbSysBusState;
+
+#define MACFB_NUBUS_DEVICE_CLASS(class) \
+ OBJECT_CLASS_CHECK(MacfbNubusDeviceClass, (class), TYPE_NUBUS_MACFB)
+#define MACFB_NUBUS_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(MacfbNubusDeviceClass, (obj), TYPE_NUBUS_MACFB)
+
+typedef struct MacfbNubusDeviceClass {
+ DeviceClass parent_class;
+
+ DeviceRealize parent_realize;
+} MacfbNubusDeviceClass;
+
+#define TYPE_NUBUS_MACFB "nubus-macfb"
+#define NUBUS_MACFB(obj) \
+ OBJECT_CHECK(MacfbNubusState, (obj), TYPE_NUBUS_MACFB)
+
+typedef struct {
+ NubusDevice busdev;
+
+ MacfbState macfb;
+} MacfbNubusState;
+
+#endif
diff --git a/include/hw/misc/mac_via.h b/include/hw/misc/mac_via.h
new file mode 100644
index 0000000000..3f86fcb7e1
--- /dev/null
+++ b/include/hw/misc/mac_via.h
@@ -0,0 +1,115 @@
+/*
+ *
+ * Copyright (c) 2011-2018 Laurent Vivier
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef HW_MISC_MAC_VIA_H
+#define HW_MISC_MAC_VIA_H
+
+#include "exec/memory.h"
+#include "hw/sysbus.h"
+#include "hw/misc/mos6522.h"
+
+
+/* VIA 1 */
+#define VIA1_IRQ_ONE_SECOND_BIT 0
+#define VIA1_IRQ_VBLANK_BIT 1
+#define VIA1_IRQ_ADB_READY_BIT 2
+#define VIA1_IRQ_ADB_DATA_BIT 3
+#define VIA1_IRQ_ADB_CLOCK_BIT 4
+
+#define VIA1_IRQ_NB 8
+
+#define VIA1_IRQ_ONE_SECOND (1 << VIA1_IRQ_ONE_SECOND_BIT)
+#define VIA1_IRQ_VBLANK (1 << VIA1_IRQ_VBLANK_BIT)
+#define VIA1_IRQ_ADB_READY (1 << VIA1_IRQ_ADB_READY_BIT)
+#define VIA1_IRQ_ADB_DATA (1 << VIA1_IRQ_ADB_DATA_BIT)
+#define VIA1_IRQ_ADB_CLOCK (1 << VIA1_IRQ_ADB_CLOCK_BIT)
+
+
+#define TYPE_MOS6522_Q800_VIA1 "mos6522-q800-via1"
+#define MOS6522_Q800_VIA1(obj) OBJECT_CHECK(MOS6522Q800VIA1State, (obj), \
+ TYPE_MOS6522_Q800_VIA1)
+
+typedef struct MOS6522Q800VIA1State {
+ /*< private >*/
+ MOS6522State parent_obj;
+
+ qemu_irq irqs[VIA1_IRQ_NB];
+ uint8_t last_b;
+ uint8_t PRAM[256];
+
+ /* external timers */
+ QEMUTimer *one_second_timer;
+ int64_t next_second;
+ QEMUTimer *VBL_timer;
+ int64_t next_VBL;
+} MOS6522Q800VIA1State;
+
+
+/* VIA 2 */
+#define VIA2_IRQ_SCSI_DATA_BIT 0
+#define VIA2_IRQ_SLOT_BIT 1
+#define VIA2_IRQ_UNUSED_BIT 2
+#define VIA2_IRQ_SCSI_BIT 3
+#define VIA2_IRQ_ASC_BIT 4
+
+#define VIA2_IRQ_NB 8
+
+#define VIA2_IRQ_SCSI_DATA (1 << VIA2_IRQ_SCSI_DATA_BIT)
+#define VIA2_IRQ_SLOT (1 << VIA2_IRQ_SLOT_BIT)
+#define VIA2_IRQ_UNUSED (1 << VIA2_IRQ_SCSI_BIT)
+#define VIA2_IRQ_SCSI (1 << VIA2_IRQ_UNUSED_BIT)
+#define VIA2_IRQ_ASC (1 << VIA2_IRQ_ASC_BIT)
+
+#define TYPE_MOS6522_Q800_VIA2 "mos6522-q800-via2"
+#define MOS6522_Q800_VIA2(obj) OBJECT_CHECK(MOS6522Q800VIA2State, (obj), \
+ TYPE_MOS6522_Q800_VIA2)
+
+typedef struct MOS6522Q800VIA2State {
+ /*< private >*/
+ MOS6522State parent_obj;
+} MOS6522Q800VIA2State;
+
+
+#define TYPE_MAC_VIA "mac_via"
+#define MAC_VIA(obj) OBJECT_CHECK(MacVIAState, (obj), TYPE_MAC_VIA)
+
+typedef struct MacVIAState {
+ SysBusDevice busdev;
+
+ /* MMIO */
+ MemoryRegion mmio;
+ MemoryRegion via1mem;
+ MemoryRegion via2mem;
+
+ /* VIAs */
+ MOS6522Q800VIA1State mos6522_via1;
+ MOS6522Q800VIA2State mos6522_via2;
+
+ /* RTC */
+ uint32_t tick_offset;
+
+ uint8_t data_out;
+ int data_out_cnt;
+ uint8_t data_in;
+ uint8_t data_in_cnt;
+ uint8_t cmd;
+ int wprotect;
+ int alt;
+
+ /* ADB */
+ ADBBusState adb_bus;
+ QEMUTimer *adb_poll_timer;
+ qemu_irq adb_data_ready;
+ int adb_data_in_size;
+ int adb_data_in_index;
+ int adb_data_out_index;
+ uint8_t adb_data_in[128];
+ uint8_t adb_data_out[16];
+} MacVIAState;
+
+#endif
diff --git a/include/hw/nubus/mac-nubus-bridge.h b/include/hw/nubus/mac-nubus-bridge.h
new file mode 100644
index 0000000000..ce9c789d99
--- /dev/null
+++ b/include/hw/nubus/mac-nubus-bridge.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2013-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef HW_NUBUS_MAC_H
+#define HW_NUBUS_MAC_H
+
+#include "hw/nubus/nubus.h"
+
+#define TYPE_MAC_NUBUS_BRIDGE "mac-nubus-bridge"
+#define MAC_NUBUS_BRIDGE(obj) OBJECT_CHECK(MacNubusState, (obj), \
+ TYPE_MAC_NUBUS_BRIDGE)
+
+typedef struct MacNubusState {
+ SysBusDevice sysbus_dev;
+
+ NubusBus *bus;
+} MacNubusState;
+
+#endif
diff --git a/include/hw/nubus/nubus.h b/include/hw/nubus/nubus.h
new file mode 100644
index 0000000000..a8634e54c5
--- /dev/null
+++ b/include/hw/nubus/nubus.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2013-2018 Laurent Vivier <laurent@vivier.eu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef HW_NUBUS_NUBUS_H
+#define HW_NUBUS_NUBUS_H
+
+#include "hw/qdev-properties.h"
+#include "exec/address-spaces.h"
+
+#define NUBUS_SUPER_SLOT_SIZE 0x10000000U
+#define NUBUS_SUPER_SLOT_NB 0x9
+
+#define NUBUS_SLOT_SIZE 0x01000000
+#define NUBUS_SLOT_NB 0xF
+
+#define NUBUS_FIRST_SLOT 0x9
+#define NUBUS_LAST_SLOT 0xF
+
+#define TYPE_NUBUS_DEVICE "nubus-device"
+#define NUBUS_DEVICE(obj) \
+ OBJECT_CHECK(NubusDevice, (obj), TYPE_NUBUS_DEVICE)
+
+#define TYPE_NUBUS_BUS "nubus-bus"
+#define NUBUS_BUS(obj) OBJECT_CHECK(NubusBus, (obj), TYPE_NUBUS_BUS)
+
+#define TYPE_NUBUS_BRIDGE "nubus-bridge"
+#define NUBUS_BRIDGE(obj) OBJECT_CHECK(NubusBridge, (obj), TYPE_NUBUS_BRIDGE)
+
+typedef struct NubusBus {
+ BusState qbus;
+
+ MemoryRegion super_slot_io;
+ MemoryRegion slot_io;
+
+ int current_slot;
+} NubusBus;
+
+typedef struct NubusDevice {
+ DeviceState qdev;
+
+ int slot_nb;
+ MemoryRegion slot_mem;
+
+ /* Format Block */
+
+ MemoryRegion fblock_io;
+
+ uint32_t rom_length;
+ uint32_t rom_crc;
+ uint8_t rom_rev;
+ uint8_t rom_format;
+ uint8_t byte_lanes;
+ int32_t directory_offset;
+
+ /* ROM */
+
+ MemoryRegion rom_io;
+ const uint8_t *rom;
+} NubusDevice;
+
+void nubus_register_rom(NubusDevice *dev, const uint8_t *rom, uint32_t size,
+ int revision, int format, uint8_t byte_lanes);
+
+#endif
diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h
index f3f0ffd5fb..db75c6dfd0 100644
--- a/include/hw/pci/pci.h
+++ b/include/hw/pci/pci.h
@@ -265,6 +265,7 @@ typedef struct PCIReqIDCache PCIReqIDCache;
struct PCIDevice {
DeviceState qdev;
+ bool partially_hotplugged;
/* PCI config space */
uint8_t *config;
@@ -352,6 +353,9 @@ struct PCIDevice {
MSIVectorUseNotifier msix_vector_use_notifier;
MSIVectorReleaseNotifier msix_vector_release_notifier;
MSIVectorPollNotifier msix_vector_poll_notifier;
+
+ /* ID of standby device in net_failover pair */
+ char *failover_pair_id;
};
void pci_register_bar(PCIDevice *pci_dev, int region_num,
diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h
index 0abe27a53a..11f8ab7149 100644
--- a/include/hw/pci/pci_ids.h
+++ b/include/hw/pci/pci_ids.h
@@ -164,6 +164,7 @@
#define PCI_DEVICE_ID_LSI_SAS0079 0x0079
#define PCI_VENDOR_ID_DEC 0x1011
+#define PCI_DEVICE_ID_DEC_21143 0x0019
#define PCI_DEVICE_ID_DEC_21154 0x0026
#define PCI_VENDOR_ID_CIRRUS 0x1013
diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index aa123f88cb..1518495b1e 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -78,6 +78,19 @@ typedef void (*BusUnrealize)(BusState *bus, Error **errp);
* respective parent types.
* </para>
* </note>
+ *
+ * # Hiding a device #
+ * To hide a device, a DeviceListener function should_be_hidden() needs to
+ * be registered.
+ * It can be used to defer adding a device and therefore hide it from the
+ * guest. The handler registering to this DeviceListener can save the QOpts
+ * passed to it for re-using it later and must return that it wants the device
+ * to be/remain hidden or not. When the handler function decides the device
+ * shall not be hidden it will be added in qdev_device_add() and
+ * realized as any other device. Otherwise qdev_device_add() will return early
+ * without adding the device. The guest will not see a "hidden" device
+ * until it was marked don't hide and qdev_device_add called again.
+ *
*/
typedef struct DeviceClass {
/*< private >*/
@@ -143,6 +156,7 @@ struct DeviceState {
bool pending_deleted_event;
QemuOpts *opts;
int hotplugged;
+ bool allow_unplug_during_migration;
BusState *parent_bus;
QLIST_HEAD(, NamedGPIOList) gpios;
QLIST_HEAD(, BusState) child_bus;
@@ -154,6 +168,12 @@ struct DeviceState {
struct DeviceListener {
void (*realize)(DeviceListener *listener, DeviceState *dev);
void (*unrealize)(DeviceListener *listener, DeviceState *dev);
+ /*
+ * This callback is called upon init of the DeviceState and allows to
+ * inform qdev that a device should be hidden, depending on the device
+ * opts, for example, to hide a standby device.
+ */
+ int (*should_be_hidden)(DeviceListener *listener, QemuOpts *device_opts);
QTAILQ_ENTRY(DeviceListener) link;
};
@@ -451,4 +471,14 @@ static inline bool qbus_is_hotpluggable(BusState *bus)
void device_listener_register(DeviceListener *listener);
void device_listener_unregister(DeviceListener *listener);
+/**
+ * @qdev_should_hide_device:
+ * @opts: QemuOpts as passed on cmdline.
+ *
+ * Check if a device should be added.
+ * When a device is added via qdev_device_add() this will be called,
+ * and return if the device should be added now or not.
+ */
+bool qdev_should_hide_device(QemuOpts *opts);
+
#endif
diff --git a/include/hw/riscv/sifive_u.h b/include/hw/riscv/sifive_u.h
index e4df298c23..82667b5746 100644
--- a/include/hw/riscv/sifive_u.h
+++ b/include/hw/riscv/sifive_u.h
@@ -44,25 +44,34 @@ typedef struct SiFiveUSoCState {
CadenceGEMState gem;
} SiFiveUSoCState;
+#define TYPE_RISCV_U_MACHINE MACHINE_TYPE_NAME("sifive_u")
+#define RISCV_U_MACHINE(obj) \
+ OBJECT_CHECK(SiFiveUState, (obj), TYPE_RISCV_U_MACHINE)
+
typedef struct SiFiveUState {
/*< private >*/
- SysBusDevice parent_obj;
+ MachineState parent_obj;
/*< public >*/
SiFiveUSoCState soc;
+
void *fdt;
int fdt_size;
+
+ bool start_in_flash;
} SiFiveUState;
enum {
SIFIVE_U_DEBUG,
SIFIVE_U_MROM,
SIFIVE_U_CLINT,
+ SIFIVE_U_L2LIM,
SIFIVE_U_PLIC,
SIFIVE_U_PRCI,
SIFIVE_U_UART0,
SIFIVE_U_UART1,
SIFIVE_U_OTP,
+ SIFIVE_U_FLASH0,
SIFIVE_U_DRAM,
SIFIVE_U_GEM,
SIFIVE_U_GEM_MGMT
@@ -75,7 +84,6 @@ enum {
};
enum {
- SIFIVE_U_CLOCK_FREQ = 1000000000,
SIFIVE_U_HFCLK_FREQ = 33333333,
SIFIVE_U_RTCCLK_FREQ = 1000000
};
diff --git a/include/hw/riscv/spike.h b/include/hw/riscv/spike.h
index 03d870363c..dc770421bc 100644
--- a/include/hw/riscv/spike.h
+++ b/include/hw/riscv/spike.h
@@ -38,10 +38,6 @@ enum {
SPIKE_DRAM
};
-enum {
- SPIKE_CLOCK_FREQ = 1000000000
-};
-
#if defined(TARGET_RISCV32)
#define SPIKE_V1_09_1_CPU TYPE_RISCV_CPU_RV32GCSU_V1_09_1
#define SPIKE_V1_10_0_CPU TYPE_RISCV_CPU_RV32GCSU_V1_10_0
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 6e5fbe5d3b..b17048a93a 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -21,14 +21,21 @@
#include "hw/riscv/riscv_hart.h"
#include "hw/sysbus.h"
+#include "hw/block/flash.h"
+
+#define TYPE_RISCV_VIRT_MACHINE MACHINE_TYPE_NAME("virt")
+#define RISCV_VIRT_MACHINE(obj) \
+ OBJECT_CHECK(RISCVVirtState, (obj), TYPE_RISCV_VIRT_MACHINE)
typedef struct {
/*< private >*/
- SysBusDevice parent_obj;
+ MachineState parent;
/*< public >*/
RISCVHartArrayState soc;
DeviceState *plic;
+ PFlashCFI01 *flash[2];
+
void *fdt;
int fdt_size;
} RISCVVirtState;
@@ -41,6 +48,7 @@ enum {
VIRT_PLIC,
VIRT_UART0,
VIRT_VIRTIO,
+ VIRT_FLASH,
VIRT_DRAM,
VIRT_PCIE_MMIO,
VIRT_PCIE_PIO,
@@ -55,10 +63,6 @@ enum {
VIRTIO_NDEV = 0x35 /* Arbitrary maximum number of interrupts */
};
-enum {
- VIRT_CLOCK_FREQ = 1000000000
-};
-
#define VIRT_PLIC_HART_CONFIG "MS"
#define VIRT_PLIC_NUM_SOURCES 127
#define VIRT_PLIC_NUM_PRIORITIES 7
diff --git a/include/hw/scsi/esp.h b/include/hw/scsi/esp.h
index adab63d1c9..6ba47dac41 100644
--- a/include/hw/scsi/esp.h
+++ b/include/hw/scsi/esp.h
@@ -14,10 +14,18 @@ typedef void (*ESPDMAMemoryReadWriteFunc)(void *opaque, uint8_t *buf, int len);
typedef struct ESPState ESPState;
+enum pdma_origin_id {
+ PDMA,
+ TI,
+ CMD,
+ ASYNC,
+};
+
struct ESPState {
uint8_t rregs[ESP_REGS];
uint8_t wregs[ESP_REGS];
qemu_irq irq;
+ qemu_irq irq_data;
uint8_t chip_id;
bool tchi_written;
int32_t ti_size;
@@ -48,6 +56,12 @@ struct ESPState {
ESPDMAMemoryReadWriteFunc dma_memory_write;
void *dma_opaque;
void (*dma_cb)(ESPState *s);
+ uint8_t pdma_buf[32];
+ int pdma_origin;
+ uint32_t pdma_len;
+ uint32_t pdma_start;
+ uint32_t pdma_cur;
+ void (*pdma_cb)(ESPState *s);
};
#define TYPE_ESP "esp"
@@ -59,6 +73,7 @@ typedef struct {
/*< public >*/
MemoryRegion iomem;
+ MemoryRegion pdma;
uint32_t it_shift;
ESPState esp;
} SysBusESPState;
diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h
index b96f0c643f..96c68d4a92 100644
--- a/include/hw/virtio/virtio-net.h
+++ b/include/hw/virtio/virtio-net.h
@@ -18,6 +18,7 @@
#include "standard-headers/linux/virtio_net.h"
#include "hw/virtio/virtio.h"
#include "net/announce.h"
+#include "qemu/option_int.h"
#define TYPE_VIRTIO_NET "virtio-net-device"
#define VIRTIO_NET(obj) \
@@ -43,6 +44,7 @@ typedef struct virtio_net_conf
int32_t speed;
char *duplex_str;
uint8_t duplex;
+ char *primary_id_str;
} virtio_net_conf;
/* Coalesced packets type & status */
@@ -182,9 +184,21 @@ struct VirtIONet {
char *netclient_name;
char *netclient_type;
uint64_t curr_guest_offloads;
+ /* used on saved state restore phase to preserve the curr_guest_offloads */
+ uint64_t saved_guest_offloads;
AnnounceTimer announce_timer;
bool needs_vnet_hdr_swap;
bool mtu_bypass_backend;
+ QemuOpts *primary_device_opts;
+ QDict *primary_device_dict;
+ DeviceState *primary_dev;
+ BusState *primary_bus;
+ char *primary_device_id;
+ char *standby_id;
+ bool primary_should_be_hidden;
+ bool failover;
+ DeviceListener primary_listener;
+ Notifier migration_state;
};
void virtio_net_set_netclient_name(VirtIONet *n, const char *name,
diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h
index 5560f4a5ea..3448d67d2a 100644
--- a/include/hw/virtio/virtio.h
+++ b/include/hw/virtio/virtio.h
@@ -37,13 +37,6 @@ static inline hwaddr vring_align(hwaddr addr,
return QEMU_ALIGN_UP(addr, align);
}
-/*
- * Calculate the number of bytes up to and including the given 'field' of
- * 'container'.
- */
-#define virtio_endof(container, field) \
- (offsetof(container, field) + sizeof_field(container, field))
-
typedef struct VirtIOFeature {
uint64_t flags;
size_t end;
@@ -160,7 +153,14 @@ typedef struct VirtioDeviceClass {
*/
void (*save)(VirtIODevice *vdev, QEMUFile *f);
int (*load)(VirtIODevice *vdev, QEMUFile *f, int version_id);
+ /* Post load hook in vmsd is called early while device is processed, and
+ * when VirtIODevice isn't fully initialized. Devices should use this instead,
+ * unless they specifically want to verify the migration stream as it's
+ * processed, e.g. for bounds checking.
+ */
+ int (*post_load)(VirtIODevice *vdev);
const VMStateDescription *vmsd;
+ bool (*primary_unplug_pending)(void *opaque);
} VirtioDeviceClass;
void virtio_instance_init_common(Object *proxy_obj, void *data,
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index b9ee563aa4..ac4f46a67d 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -186,6 +186,8 @@ struct VMStateDescription {
int (*pre_save)(void *opaque);
int (*post_save)(void *opaque);
bool (*needed)(void *opaque);
+ bool (*dev_unplug_pending)(void *opaque);
+
const VMStateField *fields;
const VMStateDescription **subsections;
};
diff --git a/include/qemu-common.h b/include/qemu-common.h
index 8d84db90b0..082da59e85 100644
--- a/include/qemu-common.h
+++ b/include/qemu-common.h
@@ -75,6 +75,12 @@ void cpu_exec_step_atomic(CPUState *cpu);
bool set_preferred_target_page_bits(int bits);
/**
+ * finalize_target_page_bits:
+ * Commit the final value set by set_preferred_target_page_bits.
+ */
+void finalize_target_page_bits(void);
+
+/**
* Sends a (part of) iovec down a socket, yielding when the socket is full, or
* Receives data into a (part of) iovec from a socket,
* yielding when there is no data in the socket.
diff --git a/include/qemu/co-shared-resource.h b/include/qemu/co-shared-resource.h
new file mode 100644
index 0000000000..4e4503004c
--- /dev/null
+++ b/include/qemu/co-shared-resource.h
@@ -0,0 +1,71 @@
+/*
+ * Helper functionality for distributing a fixed total amount of
+ * an abstract resource among multiple coroutines.
+ *
+ * Copyright (c) 2019 Virtuozzo International GmbH
+ *
+ * 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 QEMU_CO_SHARED_RESOURCE_H
+#define QEMU_CO_SHARED_RESOURCE_H
+
+
+typedef struct SharedResource SharedResource;
+
+/*
+ * Create SharedResource structure
+ *
+ * @total: total amount of some resource to be shared between clients
+ *
+ * Note: this API is not thread-safe.
+ */
+SharedResource *shres_create(uint64_t total);
+
+/*
+ * Release SharedResource structure
+ *
+ * This function may only be called once everything allocated by all
+ * clients has been deallocated.
+ */
+void shres_destroy(SharedResource *s);
+
+/*
+ * Try to allocate an amount of @n. Return true on success, and false
+ * if there is too little left of the collective resource to fulfill
+ * the request.
+ */
+bool co_try_get_from_shres(SharedResource *s, uint64_t n);
+
+/*
+ * Allocate an amount of @n, and, if necessary, yield until
+ * that becomes possible.
+ */
+void coroutine_fn co_get_from_shres(SharedResource *s, uint64_t n);
+
+/*
+ * Deallocate an amount of @n. The total amount allocated by a caller
+ * does not need to be deallocated/released with a single call, but may
+ * be split over several calls. For example, get(4), get(3), and then
+ * put(5), put(2).
+ */
+void coroutine_fn co_put_to_shres(SharedResource *s, uint64_t n);
+
+
+#endif /* QEMU_CO_SHARED_RESOURCE_H */
diff --git a/include/qemu/compiler.h b/include/qemu/compiler.h
index 7b93c73340..85c02c16d3 100644
--- a/include/qemu/compiler.h
+++ b/include/qemu/compiler.h
@@ -60,6 +60,13 @@
#define sizeof_field(type, field) sizeof(((type *)0)->field)
+/*
+ * Calculate the number of bytes up to and including the given 'field' of
+ * 'container'.
+ */
+#define endof(container, field) \
+ (offsetof(container, field) + sizeof_field(container, field))
+
/* Convert from a base type to a parent type, with compile time checking. */
#ifdef __GNUC__
#define DO_UPCAST(type, field, dev) ( __extension__ ( { \
diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
index 368d53af77..b198deca0b 100644
--- a/include/sysemu/block-backend.h
+++ b/include/sysemu/block-backend.h
@@ -121,6 +121,10 @@ void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops, void *opaque);
int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset,
unsigned int bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags);
+int coroutine_fn blk_co_pwritev_part(BlockBackend *blk, int64_t offset,
+ unsigned int bytes,
+ QEMUIOVector *qiov, size_t qiov_offset,
+ BdrvRequestFlags flags);
int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
unsigned int bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags);
@@ -233,8 +237,8 @@ int coroutine_fn blk_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
int bytes, BdrvRequestFlags flags);
int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf,
int bytes);
-int blk_truncate(BlockBackend *blk, int64_t offset, PreallocMode prealloc,
- Error **errp);
+int blk_truncate(BlockBackend *blk, int64_t offset, bool exact,
+ PreallocMode prealloc, Error **errp);
int blk_pdiscard(BlockBackend *blk, int64_t offset, int bytes);
int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,
int64_t pos, int size);
diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c
index 12aa3c0f16..aa9e437875 100644
--- a/linux-user/riscv/cpu_loop.c
+++ b/linux-user/riscv/cpu_loop.c
@@ -89,6 +89,7 @@ void cpu_loop(CPURISCVState *env)
case RISCV_EXCP_STORE_PAGE_FAULT:
signum = TARGET_SIGSEGV;
sigcode = TARGET_SEGV_MAPERR;
+ sigaddr = env->badaddr;
break;
case EXCP_DEBUG:
gdbstep:
@@ -108,7 +109,7 @@ void cpu_loop(CPURISCVState *env)
.si_code = sigcode,
._sifields._sigfault._addr = sigaddr
};
- queue_signal(env, info.si_signo, QEMU_SI_KILL, &info);
+ queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
}
process_pending_signals(env);
diff --git a/migration/migration.c b/migration/migration.c
index 4133ed2684..354ad072fa 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -52,6 +52,7 @@
#include "hw/qdev-properties.h"
#include "monitor/monitor.h"
#include "net/announce.h"
+#include "qemu/queue.h"
#define MAX_THROTTLE (32 << 20) /* Migration transfer speed throttling */
@@ -819,6 +820,7 @@ bool migration_is_setup_or_active(int state)
case MIGRATION_STATUS_SETUP:
case MIGRATION_STATUS_PRE_SWITCHOVER:
case MIGRATION_STATUS_DEVICE:
+ case MIGRATION_STATUS_WAIT_UNPLUG:
return true;
default:
@@ -954,6 +956,9 @@ static void fill_source_migration_info(MigrationInfo *info)
case MIGRATION_STATUS_CANCELLED:
info->has_status = true;
break;
+ case MIGRATION_STATUS_WAIT_UNPLUG:
+ info->has_status = true;
+ break;
}
info->status = s->state;
}
@@ -1694,6 +1699,7 @@ bool migration_is_idle(void)
case MIGRATION_STATUS_COLO:
case MIGRATION_STATUS_PRE_SWITCHOVER:
case MIGRATION_STATUS_DEVICE:
+ case MIGRATION_STATUS_WAIT_UNPLUG:
return false;
case MIGRATION_STATUS__MAX:
g_assert_not_reached();
@@ -3264,6 +3270,19 @@ static void *migration_thread(void *opaque)
qemu_savevm_state_setup(s->to_dst_file);
+ if (qemu_savevm_nr_failover_devices()) {
+ migrate_set_state(&s->state, MIGRATION_STATUS_SETUP,
+ MIGRATION_STATUS_WAIT_UNPLUG);
+
+ while (s->state == MIGRATION_STATUS_WAIT_UNPLUG &&
+ qemu_savevm_state_guest_unplug_pending()) {
+ qemu_sem_timedwait(&s->wait_unplug_sem, 250);
+ }
+
+ migrate_set_state(&s->state, MIGRATION_STATUS_WAIT_UNPLUG,
+ MIGRATION_STATUS_ACTIVE);
+ }
+
s->setup_time = qemu_clock_get_ms(QEMU_CLOCK_HOST) - setup_start;
migrate_set_state(&s->state, MIGRATION_STATUS_SETUP,
MIGRATION_STATUS_ACTIVE);
@@ -3511,6 +3530,7 @@ static void migration_instance_finalize(Object *obj)
qemu_mutex_destroy(&ms->qemu_file_lock);
g_free(params->tls_hostname);
g_free(params->tls_creds);
+ qemu_sem_destroy(&ms->wait_unplug_sem);
qemu_sem_destroy(&ms->rate_limit_sem);
qemu_sem_destroy(&ms->pause_sem);
qemu_sem_destroy(&ms->postcopy_pause_sem);
@@ -3556,6 +3576,7 @@ static void migration_instance_init(Object *obj)
qemu_sem_init(&ms->postcopy_pause_rp_sem, 0);
qemu_sem_init(&ms->rp_state.rp_sem, 0);
qemu_sem_init(&ms->rate_limit_sem, 0);
+ qemu_sem_init(&ms->wait_unplug_sem, 0);
qemu_mutex_init(&ms->qemu_file_lock);
}
diff --git a/migration/migration.h b/migration/migration.h
index 4f2fe193dc..79b3dda146 100644
--- a/migration/migration.h
+++ b/migration/migration.h
@@ -206,6 +206,9 @@ struct MigrationState
/* Flag set once the migration thread called bdrv_inactivate_all */
bool block_inactive;
+ /* Migration is waiting for guest to unplug device */
+ QemuSemaphore wait_unplug_sem;
+
/* Migration is paused due to pause-before-switchover */
QemuSemaphore pause_sem;
diff --git a/migration/savevm.c b/migration/savevm.c
index 8d95e261f6..966a9c3bdb 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -1113,6 +1113,37 @@ void qemu_savevm_state_header(QEMUFile *f)
}
}
+int qemu_savevm_nr_failover_devices(void)
+{
+ SaveStateEntry *se;
+ int n = 0;
+
+ QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {
+ if (se->vmsd && se->vmsd->dev_unplug_pending) {
+ n++;
+ }
+ }
+
+ return n;
+}
+
+bool qemu_savevm_state_guest_unplug_pending(void)
+{
+ SaveStateEntry *se;
+ int n = 0;
+
+ QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {
+ if (!se->vmsd || !se->vmsd->dev_unplug_pending) {
+ continue;
+ }
+ if (se->vmsd->dev_unplug_pending(se->opaque)) {
+ n++;
+ }
+ }
+
+ return n > 0;
+}
+
void qemu_savevm_state_setup(QEMUFile *f)
{
SaveStateEntry *se;
diff --git a/migration/savevm.h b/migration/savevm.h
index 51a4b9caa8..c42b9c80ee 100644
--- a/migration/savevm.h
+++ b/migration/savevm.h
@@ -31,6 +31,8 @@
bool qemu_savevm_state_blocked(Error **errp);
void qemu_savevm_state_setup(QEMUFile *f);
+int qemu_savevm_nr_failover_devices(void);
+bool qemu_savevm_state_guest_unplug_pending(void);
int qemu_savevm_state_resume_prepare(MigrationState *s);
void qemu_savevm_state_header(QEMUFile *f);
int qemu_savevm_state_iterate(QEMUFile *f, bool postcopy);
diff --git a/net/colo-compare.c b/net/colo-compare.c
index 7489840bde..7ee17f2cf8 100644
--- a/net/colo-compare.c
+++ b/net/colo-compare.c
@@ -319,7 +319,7 @@ static bool colo_mark_tcp_pkt(Packet *ppkt, Packet *spkt,
*mark = 0;
if (ppkt->tcp_seq == spkt->tcp_seq && ppkt->seq_end == spkt->seq_end) {
- if (colo_compare_packet_payload(ppkt, spkt,
+ if (!colo_compare_packet_payload(ppkt, spkt,
ppkt->header_size, spkt->header_size,
ppkt->payload_size)) {
*mark = COLO_COMPARE_FREE_SECONDARY | COLO_COMPARE_FREE_PRIMARY;
@@ -329,7 +329,7 @@ static bool colo_mark_tcp_pkt(Packet *ppkt, Packet *spkt,
/* one part of secondary packet payload still need to be compared */
if (!after(ppkt->seq_end, spkt->seq_end)) {
- if (colo_compare_packet_payload(ppkt, spkt,
+ if (!colo_compare_packet_payload(ppkt, spkt,
ppkt->header_size + ppkt->offset,
spkt->header_size + spkt->offset,
ppkt->payload_size - ppkt->offset)) {
@@ -348,7 +348,7 @@ static bool colo_mark_tcp_pkt(Packet *ppkt, Packet *spkt,
/* primary packet is longer than secondary packet, compare
* the same part and mark the primary packet offset
*/
- if (colo_compare_packet_payload(ppkt, spkt,
+ if (!colo_compare_packet_payload(ppkt, spkt,
ppkt->header_size + ppkt->offset,
spkt->header_size + spkt->offset,
spkt->payload_size - spkt->offset)) {
diff --git a/pc-bios/openbios-ppc b/pc-bios/openbios-ppc
index 28f6d4b2e3..1c9ab09af7 100644
--- a/pc-bios/openbios-ppc
+++ b/pc-bios/openbios-ppc
Binary files differ
diff --git a/pc-bios/openbios-sparc32 b/pc-bios/openbios-sparc32
index 964893f088..2ba8660dde 100644
--- a/pc-bios/openbios-sparc32
+++ b/pc-bios/openbios-sparc32
Binary files differ
diff --git a/pc-bios/openbios-sparc64 b/pc-bios/openbios-sparc64
index 0e19138039..99420eb815 100644
--- a/pc-bios/openbios-sparc64
+++ b/pc-bios/openbios-sparc64
Binary files differ
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 128a3d1dc2..a4631d6934 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -71,7 +71,7 @@ class QEMUMachine(object):
def __init__(self, binary, args=None, wrapper=None, name=None,
test_dir="/var/tmp", monitor_address=None,
- socket_scm_helper=None):
+ socket_scm_helper=None, sock_dir=None):
'''
Initialize a QEMUMachine
@@ -90,6 +90,8 @@ class QEMUMachine(object):
wrapper = []
if name is None:
name = "qemu-%d" % os.getpid()
+ if sock_dir is None:
+ sock_dir = test_dir
self._name = name
self._monitor_address = monitor_address
self._vm_monitor = None
@@ -106,12 +108,14 @@ class QEMUMachine(object):
self._qemu_full_args = None
self._test_dir = test_dir
self._temp_dir = None
+ self._sock_dir = sock_dir
self._launched = False
self._machine = None
self._console_set = False
self._console_device_type = None
self._console_address = None
self._console_socket = None
+ self._remove_files = []
# just in case logging wasn't configured by the main script:
logging.basicConfig()
@@ -236,8 +240,9 @@ class QEMUMachine(object):
if self._machine is not None:
args.extend(['-machine', self._machine])
if self._console_set:
- self._console_address = os.path.join(self._temp_dir,
+ self._console_address = os.path.join(self._sock_dir,
self._name + "-console.sock")
+ self._remove_files.append(self._console_address)
chardev = ('socket,id=console,path=%s,server,nowait' %
self._console_address)
args.extend(['-chardev', chardev])
@@ -253,8 +258,9 @@ class QEMUMachine(object):
if self._monitor_address is not None:
self._vm_monitor = self._monitor_address
else:
- self._vm_monitor = os.path.join(self._temp_dir,
+ self._vm_monitor = os.path.join(self._sock_dir,
self._name + "-monitor.sock")
+ self._remove_files.append(self._vm_monitor)
self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
self._qemu_log_file = open(self._qemu_log_path, 'wb')
@@ -271,14 +277,13 @@ class QEMUMachine(object):
self._qemu_log_path = None
- if self._console_socket is not None:
- self._console_socket.close()
- self._console_socket = None
-
if self._temp_dir is not None:
shutil.rmtree(self._temp_dir)
self._temp_dir = None
+ while len(self._remove_files) > 0:
+ self._remove_if_exists(self._remove_files.pop())
+
def launch(self):
"""
Launch the VM and make sure we cleanup and expose the
@@ -333,6 +338,13 @@ class QEMUMachine(object):
"""
Terminate the VM and clean up
"""
+ # If we keep the console socket open, we may deadlock waiting
+ # for QEMU to exit, while QEMU is waiting for the socket to
+ # become writeable.
+ if self._console_socket is not None:
+ self._console_socket.close()
+ self._console_socket = None
+
if self.is_running():
try:
if not has_quit:
diff --git a/python/qemu/qtest.py b/python/qemu/qtest.py
index 3f1d2cb325..d24ad04256 100644
--- a/python/qemu/qtest.py
+++ b/python/qemu/qtest.py
@@ -84,14 +84,17 @@ class QEMUQtestMachine(QEMUMachine):
'''A QEMU VM'''
def __init__(self, binary, args=None, name=None, test_dir="/var/tmp",
- socket_scm_helper=None):
+ socket_scm_helper=None, sock_dir=None):
if name is None:
name = "qemu-%d" % os.getpid()
+ if sock_dir is None:
+ sock_dir = test_dir
super(QEMUQtestMachine,
self).__init__(binary, args, name=name, test_dir=test_dir,
- socket_scm_helper=socket_scm_helper)
+ socket_scm_helper=socket_scm_helper,
+ sock_dir=sock_dir)
self._qtest = None
- self._qtest_path = os.path.join(test_dir, name + "-qtest.sock")
+ self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")
def _base_args(self):
args = super(QEMUQtestMachine, self)._base_args()
diff --git a/qapi/migration.json b/qapi/migration.json
index 82feb5bd39..b7348d0c8b 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -133,6 +133,9 @@
# @device: During device serialisation when pause-before-switchover is enabled
# (since 2.11)
#
+# @wait-unplug: wait for device unplug request by guest OS to be completed.
+# (since 4.2)
+#
# Since: 2.3
#
##
@@ -140,7 +143,7 @@
'data': [ 'none', 'setup', 'cancelling', 'cancelled',
'active', 'postcopy-active', 'postcopy-paused',
'postcopy-recover', 'completed', 'failed', 'colo',
- 'pre-switchover', 'device' ] }
+ 'pre-switchover', 'device', 'wait-unplug' ] }
##
# @MigrationInfo:
@@ -1448,3 +1451,22 @@
# Since: 3.0
##
{ 'command': 'migrate-pause', 'allow-oob': true }
+
+##
+# @UNPLUG_PRIMARY:
+#
+# Emitted from source side of a migration when migration state is
+# WAIT_UNPLUG. Device was unplugged by guest operating system.
+# Device resources in QEMU are kept on standby to be able to re-plug it in case
+# of migration failure.
+#
+# @device-id: QEMU device id of the unplugged device
+#
+# Since: 4.2
+#
+# Example:
+# {"event": "UNPLUG_PRIMARY", "data": {"device-id": "hostdev0"} }
+#
+##
+{ 'event': 'UNPLUG_PRIMARY',
+ 'data': { 'device-id': 'str' } }
diff --git a/qapi/net.json b/qapi/net.json
index 728990f4fb..335295be50 100644
--- a/qapi/net.json
+++ b/qapi/net.json
@@ -723,8 +723,6 @@
# Trigger generation of broadcast RARP frames to update network switches.
# This can be useful when network bonds fail-over the active slave.
#
-# @params: AnnounceParameters giving timing and repetition count of announce
-#
# Example:
#
# -> { "execute": "announce-self",
@@ -737,3 +735,22 @@
##
{ 'command': 'announce-self', 'boxed': true,
'data' : 'AnnounceParameters'}
+
+##
+# @FAILOVER_NEGOTIATED:
+#
+# Emitted when VIRTIO_NET_F_STANDBY was enabled during feature negotiation.
+# Failover primary devices which were hidden (not hotplugged when requested)
+# before will now be hotplugged by the virtio-net standby device.
+#
+# device-id: QEMU device id of the unplugged device
+# Since: 4.2
+#
+# Example:
+#
+# <- { "event": "FAILOVER_NEGOTIATED",
+# "data": "net1" }
+#
+##
+{ 'event': 'FAILOVER_NEGOTIATED',
+ 'data': {'device-id': 'str'} }
diff --git a/qdev-monitor.c b/qdev-monitor.c
index 148df9cacf..e6b112eb0a 100644
--- a/qdev-monitor.c
+++ b/qdev-monitor.c
@@ -32,9 +32,11 @@
#include "qemu/help_option.h"
#include "qemu/option.h"
#include "qemu/qemu-print.h"
+#include "qemu/option_int.h"
#include "sysemu/block-backend.h"
#include "sysemu/sysemu.h"
#include "migration/misc.h"
+#include "migration/migration.h"
/*
* Aliases were a bad idea from the start. Let's keep them
@@ -562,13 +564,36 @@ void qdev_set_id(DeviceState *dev, const char *id)
}
}
+static int is_failover_device(void *opaque, const char *name, const char *value,
+ Error **errp)
+{
+ if (strcmp(name, "failover_pair_id") == 0) {
+ QemuOpts *opts = (QemuOpts *)opaque;
+
+ if (qdev_should_hide_device(opts)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static bool should_hide_device(QemuOpts *opts)
+{
+ if (qemu_opt_foreach(opts, is_failover_device, opts, NULL) == 0) {
+ return false;
+ }
+ return true;
+}
+
DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
{
DeviceClass *dc;
const char *driver, *path;
- DeviceState *dev;
+ DeviceState *dev = NULL;
BusState *bus = NULL;
Error *err = NULL;
+ bool hide;
driver = qemu_opt_get(opts, "driver");
if (!driver) {
@@ -602,11 +627,17 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
return NULL;
}
}
- if (qdev_hotplug && bus && !qbus_is_hotpluggable(bus)) {
+ hide = should_hide_device(opts);
+
+ if ((hide || qdev_hotplug) && bus && !qbus_is_hotpluggable(bus)) {
error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name);
return NULL;
}
+ if (hide) {
+ return NULL;
+ }
+
if (!migration_is_idle()) {
error_setg(errp, "device_add not allowed while migrating");
return NULL;
@@ -648,8 +679,10 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
err_del_dev:
error_propagate(errp, err);
- object_unparent(OBJECT(dev));
- object_unref(OBJECT(dev));
+ if (dev) {
+ object_unparent(OBJECT(dev));
+ object_unref(OBJECT(dev));
+ }
return NULL;
}
@@ -818,7 +851,7 @@ void qdev_unplug(DeviceState *dev, Error **errp)
return;
}
- if (!migration_is_idle()) {
+ if (!migration_is_idle() && !dev->allow_unplug_during_migration) {
error_setg(errp, "device_del not allowed while migrating");
return;
}
diff --git a/qemu-deprecated.texi b/qemu-deprecated.texi
index 7239e0959d..f727bd3932 100644
--- a/qemu-deprecated.texi
+++ b/qemu-deprecated.texi
@@ -149,6 +149,18 @@ QEMU 4.1 has three options, please migrate to one of these three:
@section QEMU Machine Protocol (QMP) commands
+@subsection change (since 2.5.0)
+
+Use ``blockdev-change-medium'' or ``change-vnc-password'' instead.
+
+@subsection migrate_set_downtime and migrate_set_speed (since 2.8.0)
+
+Use ``migrate-set-parameters'' instead.
+
+@subsection migrate-set-cache-size and query-migrate-cache-size (since 2.11.0)
+
+Use ``migrate-set-parameters'' and ``query-migrate-parameters'' instead.
+
@subsection query-block result field dirty-bitmaps[i].status (since 4.0)
The ``status'' field of the ``BlockDirtyInfo'' structure, returned by
diff --git a/qemu-img.c b/qemu-img.c
index 8b03ef8171..95a24b9762 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -3656,7 +3656,7 @@ static int img_resize(int argc, char **argv)
Error *err = NULL;
int c, ret, relative;
const char *filename, *fmt, *size;
- int64_t n, total_size, current_size, new_size;
+ int64_t n, total_size, current_size;
bool quiet = false;
BlockBackend *blk = NULL;
PreallocMode prealloc = PREALLOC_MODE_OFF;
@@ -3831,43 +3831,17 @@ static int img_resize(int argc, char **argv)
}
}
- ret = blk_truncate(blk, total_size, prealloc, &err);
- if (ret < 0) {
+ /*
+ * The user expects the image to have the desired size after
+ * resizing, so pass @exact=true. It is of no use to report
+ * success when the image has not actually been resized.
+ */
+ ret = blk_truncate(blk, total_size, true, prealloc, &err);
+ if (!ret) {
+ qprintf(quiet, "Image resized.\n");
+ } else {
error_report_err(err);
- goto out;
- }
-
- new_size = blk_getlength(blk);
- if (new_size < 0) {
- error_report("Failed to verify truncated image length: %s",
- strerror(-new_size));
- ret = -1;
- goto out;
- }
-
- /* Some block drivers implement a truncation method, but only so
- * the user can cause qemu to refresh the image's size from disk.
- * The idea is that the user resizes the image outside of qemu and
- * then invokes block_resize to inform qemu about it.
- * (This includes iscsi and file-posix for device files.)
- * Of course, that is not the behavior someone invoking
- * qemu-img resize would find useful, so we catch that behavior
- * here and tell the user. */
- if (new_size != total_size && new_size == current_size) {
- error_report("Image was not resized; resizing may not be supported "
- "for this image");
- ret = -1;
- goto out;
}
-
- if (new_size != total_size) {
- warn_report("Image should have been resized to %" PRIi64
- " bytes, but was resized to %" PRIi64 " bytes",
- total_size, new_size);
- }
-
- qprintf(quiet, "Image resized.\n");
-
out:
blk_unref(blk);
if (ret) {
diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index 349256a5fe..1b7e700020 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -1710,7 +1710,12 @@ static int truncate_f(BlockBackend *blk, int argc, char **argv)
return offset;
}
- ret = blk_truncate(blk, offset, PREALLOC_MODE_OFF, &local_err);
+ /*
+ * qemu-io is a debugging tool, so let us be strict here and pass
+ * exact=true. It is better to err on the "emit more errors" side
+ * than to be overly permissive.
+ */
+ ret = blk_truncate(blk, offset, true, PREALLOC_MODE_OFF, &local_err);
if (ret < 0) {
error_report_err(local_err);
return ret;
diff --git a/qemu-options.hx b/qemu-options.hx
index 8faea5bfb8..1fc2470e2f 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1859,7 +1859,7 @@ ETEXI
DEF("g", 1, QEMU_OPTION_g ,
"-g WxH[xDEPTH] Set the initial graphical resolution and depth\n",
- QEMU_ARCH_PPC | QEMU_ARCH_SPARC)
+ QEMU_ARCH_PPC | QEMU_ARCH_SPARC | QEMU_ARCH_M68K)
STEXI
@item -g @var{width}x@var{height}[x@var{depth}]
@findex -g
diff --git a/roms/openbios b/roms/openbios
-Subproject f28e16f9aab36f723df525e8a2a1a798b18e19b
+Subproject 7e5b89e4295063d8eba55b9c8ce8bc681c2d129
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 6d5726cf6e..6f1c17f71f 100644
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -12,7 +12,7 @@ from qapi.gen import QAPIGenDoc, QAPISchemaVisitor
MSG_FMT = """
@deftypefn {type} {{}} {name}
-{body}
+{body}{members}{features}{sections}
@end deftypefn
""".format
@@ -20,7 +20,7 @@ MSG_FMT = """
TYPE_FMT = """
@deftp {{{type}}} {name}
-{body}
+{body}{members}{features}{sections}
@end deftp
""".format
@@ -149,7 +149,8 @@ def texi_member(member, desc, suffix):
suffix, desc, texi_if(member.ifcond, prefix='@*'))
-def texi_members(doc, what, base, variants, member_func):
+def texi_members(doc, what, base=None, variants=None,
+ member_func=texi_member):
"""Format the table of members"""
items = ''
for section in doc.args.values():
@@ -182,6 +183,14 @@ def texi_members(doc, what, base, variants, member_func):
return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items)
+def texi_arguments(doc, boxed_arg_type):
+ if boxed_arg_type:
+ assert not doc.args
+ return ('\n@b{Arguments:} the members of @code{%s}\n'
+ % boxed_arg_type.name)
+ return texi_members(doc, 'Arguments')
+
+
def texi_features(doc):
"""Format the table of features"""
items = ''
@@ -208,12 +217,22 @@ def texi_sections(doc, ifcond):
return body
-def texi_entity(doc, what, ifcond, base=None, variants=None,
- member_func=texi_member):
- return (texi_body(doc)
- + texi_members(doc, what, base, variants, member_func)
- + texi_features(doc)
- + texi_sections(doc, ifcond))
+def texi_type(typ, doc, ifcond, members):
+ return TYPE_FMT(type=typ,
+ name=doc.symbol,
+ body=texi_body(doc),
+ members=members,
+ features=texi_features(doc),
+ sections=texi_sections(doc, ifcond))
+
+
+def texi_msg(typ, doc, ifcond, members):
+ return MSG_FMT(type=typ,
+ name=doc.symbol,
+ body=texi_body(doc),
+ members=members,
+ features=texi_features(doc),
+ sections=texi_sections(doc, ifcond))
class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
@@ -227,48 +246,36 @@ class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
def visit_enum_type(self, name, info, ifcond, members, prefix):
doc = self.cur_doc
- self._gen.add(TYPE_FMT(type='Enum',
- name=doc.symbol,
- body=texi_entity(doc, 'Values', ifcond,
- member_func=texi_enum_value)))
+ self._gen.add(texi_type('Enum', doc, ifcond,
+ texi_members(doc, 'Values',
+ member_func=texi_enum_value)))
def visit_object_type(self, name, info, ifcond, base, members, variants,
features):
doc = self.cur_doc
if base and base.is_implicit():
base = None
- self._gen.add(TYPE_FMT(type='Object',
- name=doc.symbol,
- body=texi_entity(doc, 'Members', ifcond,
- base, variants)))
+ self._gen.add(texi_type('Object', doc, ifcond,
+ texi_members(doc, 'Members', base, variants)))
def visit_alternate_type(self, name, info, ifcond, variants):
doc = self.cur_doc
- self._gen.add(TYPE_FMT(type='Alternate',
- name=doc.symbol,
- body=texi_entity(doc, 'Members', ifcond)))
+ self._gen.add(texi_type('Alternate', doc, ifcond,
+ texi_members(doc, 'Members')))
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig,
features):
doc = self.cur_doc
- if boxed:
- body = texi_body(doc)
- body += ('\n@b{Arguments:} the members of @code{%s}\n'
- % arg_type.name)
- body += texi_features(doc)
- body += texi_sections(doc, ifcond)
- else:
- body = texi_entity(doc, 'Arguments', ifcond)
- self._gen.add(MSG_FMT(type='Command',
- name=doc.symbol,
- body=body))
+ self._gen.add(texi_msg('Command', doc, ifcond,
+ texi_arguments(doc,
+ arg_type if boxed else None)))
def visit_event(self, name, info, ifcond, arg_type, boxed):
doc = self.cur_doc
- self._gen.add(MSG_FMT(type='Event',
- name=doc.symbol,
- body=texi_entity(doc, 'Arguments', ifcond)))
+ self._gen.add(texi_msg('Event', doc, ifcond,
+ texi_arguments(doc,
+ arg_type if boxed else None)))
def symbol(self, doc, entity):
if self._gen._body:
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index 7c7394f835..d7a289eded 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -95,12 +95,6 @@ def check_flags(expr, info):
info, "flag '%s' may only use true value" % key)
-def normalize_if(expr):
- ifcond = expr.get('if')
- if isinstance(ifcond, str):
- expr['if'] = [ifcond]
-
-
def check_if(expr, info, source):
def check_if_str(ifcond, info):
@@ -126,6 +120,7 @@ def check_if(expr, info, source):
check_if_str(elt, info)
else:
check_if_str(ifcond, info)
+ expr['if'] = [ifcond]
def normalize_members(members):
@@ -175,21 +170,16 @@ def check_type(value, info, source,
raise QAPISemError(info, "%s uses reserved name" % key_source)
check_keys(arg, info, key_source, ['type'], ['if'])
check_if(arg, info, key_source)
- normalize_if(arg)
check_type(arg['type'], info, key_source, allow_array=True)
-def normalize_features(features):
- if isinstance(features, list):
- features[:] = [f if isinstance(f, dict) else {'name': f}
- for f in features]
-
-
def check_features(features, info):
if features is None:
return
if not isinstance(features, list):
raise QAPISemError(info, "'features' must be an array")
+ features[:] = [f if isinstance(f, dict) else {'name': f}
+ for f in features]
for f in features:
source = "'features' member"
assert isinstance(f, dict)
@@ -198,13 +188,6 @@ def check_features(features, info):
source = "%s '%s'" % (source, f['name'])
check_name_str(f['name'], info, source)
check_if(f, info, source)
- normalize_if(f)
-
-
-def normalize_enum(expr):
- if isinstance(expr['data'], list):
- expr['data'] = [m if isinstance(m, dict) else {'name': m}
- for m in expr['data']]
def check_enum(expr, info):
@@ -219,6 +202,8 @@ def check_enum(expr, info):
permit_upper = name in info.pragma.name_case_whitelist
+ members[:] = [m if isinstance(m, dict) else {'name': m}
+ for m in members]
for member in members:
source = "'data' member"
check_keys(member, info, source, ['name'], ['if'])
@@ -227,7 +212,6 @@ def check_enum(expr, info):
check_name_str(member['name'], info, source,
enum_member=True, permit_upper=permit_upper)
check_if(member, info, source)
- normalize_if(member)
def check_struct(expr, info):
@@ -259,7 +243,6 @@ def check_union(expr, info):
check_name_str(key, info, source)
check_keys(value, info, source, ['type'], ['if'])
check_if(value, info, source)
- normalize_if(value)
check_type(value['type'], info, source, allow_array=not base)
@@ -273,7 +256,6 @@ def check_alternate(expr, info):
check_name_str(key, info, source)
check_keys(value, info, source, ['type'], ['if'])
check_if(value, info, source)
- normalize_if(value)
check_type(value['type'], info, source)
@@ -339,7 +321,6 @@ def check_exprs(exprs):
if meta == 'enum':
check_keys(expr, info, meta,
['enum', 'data'], ['if', 'prefix'])
- normalize_enum(expr)
check_enum(expr, info)
elif meta == 'union':
check_keys(expr, info, meta,
@@ -357,7 +338,6 @@ def check_exprs(exprs):
check_keys(expr, info, meta,
['struct', 'data'], ['base', 'if', 'features'])
normalize_members(expr['data'])
- normalize_features(expr.get('features'))
check_struct(expr, info)
elif meta == 'command':
check_keys(expr, info, meta,
@@ -366,7 +346,6 @@ def check_exprs(exprs):
'gen', 'success-response', 'allow-oob',
'allow-preconfig'])
normalize_members(expr.get('data'))
- normalize_features(expr.get('features'))
check_command(expr, info)
elif meta == 'event':
check_keys(expr, info, meta,
@@ -376,7 +355,6 @@ def check_exprs(exprs):
else:
assert False, 'unexpected meta type'
- normalize_if(expr)
check_if(expr, info, meta)
check_flags(expr, info)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index e800876ad1..342792e410 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -555,16 +555,31 @@ class QAPIDoc(object):
self.args[member.name] = QAPIDoc.ArgSection(member.name)
self.args[member.name].connect(member)
+ def connect_feature(self, feature):
+ if feature.name not in self.features:
+ raise QAPISemError(feature.info,
+ "feature '%s' lacks documentation"
+ % feature.name)
+ self.features[feature.name] = QAPIDoc.ArgSection(feature.name)
+ self.features[feature.name].connect(feature)
+
def check_expr(self, expr):
if self.has_section('Returns') and 'command' not in expr:
raise QAPISemError(self.info,
"'Returns:' is only valid for commands")
def check(self):
- bogus = [name for name, section in self.args.items()
- if not section.member]
- if bogus:
- raise QAPISemError(
- self.info,
- "the following documented members are not in "
- "the declaration: %s" % ", ".join(bogus))
+
+ def check_args_section(args, info, what):
+ bogus = [name for name, section in args.items()
+ if not section.member]
+ if bogus:
+ raise QAPISemError(
+ self.info,
+ "documented member%s '%s' %s not exist"
+ % ("s" if len(bogus) > 1 else "",
+ "', '".join(bogus),
+ "do" if len(bogus) > 1 else "does"))
+
+ check_args_section(self.args, self.info, 'members')
+ check_args_section(self.features, self.info, 'features')
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index f7d68a35f4..cf0045f34e 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -27,8 +27,11 @@ from qapi.parser import QAPISchemaParser
class QAPISchemaEntity(object):
meta = None
- def __init__(self, name, info, doc, ifcond=None):
+ def __init__(self, name, info, doc, ifcond=None, features=None):
assert name is None or isinstance(name, str)
+ for f in features or []:
+ assert isinstance(f, QAPISchemaFeature)
+ f.set_defined_in(name)
self.name = name
self._module = None
# For explicitly defined entities, info points to the (explicit)
@@ -39,6 +42,7 @@ class QAPISchemaEntity(object):
self.info = info
self.doc = doc
self._ifcond = ifcond or []
+ self.features = features or []
self._checked = False
def c_name(self):
@@ -49,8 +53,21 @@ class QAPISchemaEntity(object):
if self.info:
self._module = os.path.relpath(self.info.fname,
os.path.dirname(schema.fname))
+ seen = {}
+ for f in self.features:
+ f.check_clash(self.info, seen)
+ if self.doc:
+ self.doc.connect_feature(f)
+
self._checked = True
+ def connect_doc(self, doc=None):
+ pass
+
+ def check_doc(self):
+ if self.doc:
+ self.doc.check()
+
@property
def ifcond(self):
assert self._checked
@@ -217,8 +234,12 @@ class QAPISchemaEnumType(QAPISchemaType):
seen = {}
for m in self.members:
m.check_clash(self.info, seen)
- if self.doc:
- self.doc.connect_member(m)
+
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ for m in self.members:
+ doc.connect_member(m)
def is_implicit(self):
# See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
@@ -296,7 +317,7 @@ class QAPISchemaObjectType(QAPISchemaType):
# struct has local_members, optional base, and no variants
# flat union has base, variants, and no local_members
# simple union has local_members, variants, and no base
- QAPISchemaType.__init__(self, name, info, doc, ifcond)
+ QAPISchemaType.__init__(self, name, info, doc, ifcond, features)
self.meta = 'union' if variants else 'struct'
assert base is None or isinstance(base, str)
for m in local_members:
@@ -305,15 +326,11 @@ class QAPISchemaObjectType(QAPISchemaType):
if variants is not None:
assert isinstance(variants, QAPISchemaObjectTypeVariants)
variants.set_defined_in(name)
- for f in features:
- assert isinstance(f, QAPISchemaFeature)
- f.set_defined_in(name)
self._base_name = base
self.base = None
self.local_members = local_members
self.variants = variants
self.members = None
- self.features = features
def check(self, schema):
# This calls another type T's .check() exactly when the C
@@ -345,22 +362,12 @@ class QAPISchemaObjectType(QAPISchemaType):
for m in self.local_members:
m.check(schema)
m.check_clash(self.info, seen)
- if self.doc:
- self.doc.connect_member(m)
members = seen.values()
if self.variants:
self.variants.check(schema, seen)
self.variants.check_clash(self.info, seen)
- # Features are in a name space separate from members
- seen = {}
- for f in self.features:
- f.check_clash(self.info, seen)
-
- if self.doc:
- self.doc.check()
-
self.members = members # mark completed
# Check that the members of this type do not cause duplicate JSON members,
@@ -372,6 +379,14 @@ class QAPISchemaObjectType(QAPISchemaType):
for m in self.members:
m.check_clash(info, seen)
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ if self.base and self.base.is_implicit():
+ self.base.connect_doc(doc)
+ for m in self.local_members:
+ doc.connect_member(m)
+
@property
def ifcond(self):
assert self._checked
@@ -639,10 +654,12 @@ class QAPISchemaAlternateType(QAPISchemaType):
"%s can't be distinguished from '%s'"
% (v.describe(self.info), types_seen[qt]))
types_seen[qt] = v.name
- if self.doc:
- self.doc.connect_member(v)
- if self.doc:
- self.doc.check()
+
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ for v in self.variants.variants:
+ doc.connect_member(v)
def c_type(self):
return c_name(self.name) + pointer_suffix
@@ -662,12 +679,9 @@ class QAPISchemaCommand(QAPISchemaEntity):
def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
gen, success_response, boxed, allow_oob, allow_preconfig,
features):
- QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
+ QAPISchemaEntity.__init__(self, name, info, doc, ifcond, features)
assert not arg_type or isinstance(arg_type, str)
assert not ret_type or isinstance(ret_type, str)
- for f in features:
- assert isinstance(f, QAPISchemaFeature)
- f.set_defined_in(name)
self._arg_type_name = arg_type
self.arg_type = None
self._ret_type_name = ret_type
@@ -677,7 +691,6 @@ class QAPISchemaCommand(QAPISchemaEntity):
self.boxed = boxed
self.allow_oob = allow_oob
self.allow_preconfig = allow_preconfig
- self.features = features
def check(self, schema):
QAPISchemaEntity.check(self, schema)
@@ -707,10 +720,11 @@ class QAPISchemaCommand(QAPISchemaEntity):
"command's 'returns' cannot take %s"
% self.ret_type.describe())
- # Features are in a name space separate from members
- seen = {}
- for f in self.features:
- f.check_clash(self.info, seen)
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ if self.arg_type and self.arg_type.is_implicit():
+ self.arg_type.connect_doc(doc)
def visit(self, visitor):
QAPISchemaEntity.visit(self, visitor)
@@ -748,6 +762,12 @@ class QAPISchemaEvent(QAPISchemaEntity):
"event's 'data' can take %s only with 'boxed': true"
% self.arg_type.describe())
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ if self.arg_type and self.arg_type.is_implicit():
+ self.arg_type.connect_doc(doc)
+
def visit(self, visitor):
QAPISchemaEntity.visit(self, visitor)
visitor.visit_event(self.name, self.info, self.ifcond,
@@ -873,8 +893,7 @@ class QAPISchema(object):
self._def_entity(QAPISchemaArrayType(name, info, element_type))
return name
- def _make_implicit_object_type(self, name, info, doc, ifcond,
- role, members):
+ def _make_implicit_object_type(self, name, info, ifcond, role, members):
if not members:
return None
# See also QAPISchemaObjectTypeMember.describe()
@@ -892,7 +911,7 @@ class QAPISchema(object):
# TODO kill simple unions or implement the disjunction
assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
else:
- self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
+ self._def_entity(QAPISchemaObjectType(name, info, None, ifcond,
None, members, None, []))
return name
@@ -939,7 +958,7 @@ class QAPISchema(object):
assert len(typ) == 1
typ = self._make_array_type(typ[0], info)
typ = self._make_implicit_object_type(
- typ, info, None, self.lookup_type(typ),
+ typ, info, self.lookup_type(typ),
'wrapper', [self._make_member('data', typ, None, info)])
return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
@@ -952,7 +971,7 @@ class QAPISchema(object):
tag_member = None
if isinstance(base, dict):
base = self._make_implicit_object_type(
- name, info, doc, ifcond,
+ name, info, ifcond,
'base', self._make_members(base, info))
if tag_name:
variants = [self._make_variant(key, value['type'],
@@ -999,7 +1018,7 @@ class QAPISchema(object):
features = expr.get('features', [])
if isinstance(data, OrderedDict):
data = self._make_implicit_object_type(
- name, info, doc, ifcond, 'arg', self._make_members(data, info))
+ name, info, ifcond, 'arg', self._make_members(data, info))
if isinstance(rets, list):
assert len(rets) == 1
rets = self._make_array_type(rets[0], info)
@@ -1015,7 +1034,7 @@ class QAPISchema(object):
ifcond = expr.get('if')
if isinstance(data, OrderedDict):
data = self._make_implicit_object_type(
- name, info, doc, ifcond, 'arg', self._make_members(data, info))
+ name, info, ifcond, 'arg', self._make_members(data, info))
self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
def _def_exprs(self, exprs):
@@ -1043,6 +1062,8 @@ class QAPISchema(object):
def check(self):
for ent in self._entity_list:
ent.check(self)
+ ent.connect_doc()
+ ent.check_doc()
def visit(self, visitor):
visitor.visit_begin(self)
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index f13e298a36..3939963b71 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -484,7 +484,7 @@ static void riscv_cpu_class_init(ObjectClass *c, void *data)
cc->gdb_stop_before_watchpoint = true;
cc->disas_set_info = riscv_cpu_disas_set_info;
#ifndef CONFIG_USER_ONLY
- cc->do_unassigned_access = riscv_cpu_unassigned_access;
+ cc->do_transaction_failed = riscv_cpu_do_transaction_failed;
cc->do_unaligned_access = riscv_cpu_do_unaligned_access;
cc->get_phys_page_debug = riscv_cpu_get_phys_page_debug;
#endif
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 124ed33ee4..8c64c68538 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -264,8 +264,11 @@ void riscv_cpu_do_unaligned_access(CPUState *cs, vaddr addr,
bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
MMUAccessType access_type, int mmu_idx,
bool probe, uintptr_t retaddr);
-void riscv_cpu_unassigned_access(CPUState *cpu, hwaddr addr, bool is_write,
- bool is_exec, int unused, unsigned size);
+void riscv_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr,
+ vaddr addr, unsigned size,
+ MMUAccessType access_type,
+ int mmu_idx, MemTxAttrs attrs,
+ MemTxResult response, uintptr_t retaddr);
char *riscv_isa_string(RISCVCPU *cpu);
void riscv_cpu_list(void);
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index 87dd6a6ece..f13131a51b 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -169,7 +169,8 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical,
/* NOTE: the env->pc value visible here will not be
* correct, but the value visible to the exception handler
* (riscv_cpu_do_interrupt) is correct */
-
+ MemTxResult res;
+ MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
int mode = mmu_idx;
if (mode == PRV_M && access_type != MMU_INST_FETCH) {
@@ -256,11 +257,16 @@ restart:
1 << MMU_DATA_LOAD, PRV_S)) {
return TRANSLATE_PMP_FAIL;
}
+
#if defined(TARGET_RISCV32)
- target_ulong pte = ldl_phys(cs->as, pte_addr);
+ target_ulong pte = address_space_ldl(cs->as, pte_addr, attrs, &res);
#elif defined(TARGET_RISCV64)
- target_ulong pte = ldq_phys(cs->as, pte_addr);
+ target_ulong pte = address_space_ldq(cs->as, pte_addr, attrs, &res);
#endif
+ if (res != MEMTX_OK) {
+ return TRANSLATE_FAIL;
+ }
+
hwaddr ppn = pte >> PTE_PPN_SHIFT;
if (!(pte & PTE_V)) {
@@ -402,20 +408,23 @@ hwaddr riscv_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
return phys_addr;
}
-void riscv_cpu_unassigned_access(CPUState *cs, hwaddr addr, bool is_write,
- bool is_exec, int unused, unsigned size)
+void riscv_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr,
+ vaddr addr, unsigned size,
+ MMUAccessType access_type,
+ int mmu_idx, MemTxAttrs attrs,
+ MemTxResult response, uintptr_t retaddr)
{
RISCVCPU *cpu = RISCV_CPU(cs);
CPURISCVState *env = &cpu->env;
- if (is_write) {
+ if (access_type == MMU_DATA_STORE) {
cs->exception_index = RISCV_EXCP_STORE_AMO_ACCESS_FAULT;
} else {
cs->exception_index = RISCV_EXCP_LOAD_ACCESS_FAULT;
}
env->badaddr = addr;
- riscv_raise_exception(&cpu->env, cs->exception_index, GETPC());
+ riscv_raise_exception(&cpu->env, cs->exception_index, retaddr);
}
void riscv_cpu_do_unaligned_access(CPUState *cs, vaddr addr,
@@ -446,9 +455,9 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
MMUAccessType access_type, int mmu_idx,
bool probe, uintptr_t retaddr)
{
-#ifndef CONFIG_USER_ONLY
RISCVCPU *cpu = RISCV_CPU(cs);
CPURISCVState *env = &cpu->env;
+#ifndef CONFIG_USER_ONLY
hwaddr pa = 0;
int prot;
bool pmp_violation = false;
@@ -499,7 +508,10 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
case MMU_DATA_STORE:
cs->exception_index = RISCV_EXCP_STORE_PAGE_FAULT;
break;
+ default:
+ g_assert_not_reached();
}
+ env->badaddr = address;
cpu_loop_exit_restore(cs, retaddr);
#endif
}
diff --git a/target/riscv/csr.c b/target/riscv/csr.c
index f767ad24be..974c9c20b5 100644
--- a/target/riscv/csr.c
+++ b/target/riscv/csr.c
@@ -801,7 +801,10 @@ int riscv_csrrw(CPURISCVState *env, int csrno, target_ulong *ret_value,
#if !defined(CONFIG_USER_ONLY)
int csr_priv = get_field(csrno, 0x300);
int read_only = get_field(csrno, 0xC00) == 3;
- if ((write_mask && read_only) || (env->priv < csr_priv)) {
+ if ((!env->debugger) && (env->priv < csr_priv)) {
+ return -1;
+ }
+ if (write_mask && read_only) {
return -1;
}
#endif
diff --git a/target/riscv/gdbstub.c b/target/riscv/gdbstub.c
index ded140e8d8..1a7947e019 100644
--- a/target/riscv/gdbstub.c
+++ b/target/riscv/gdbstub.c
@@ -373,6 +373,32 @@ static int riscv_gdb_set_csr(CPURISCVState *env, uint8_t *mem_buf, int n)
return 0;
}
+static int riscv_gdb_get_virtual(CPURISCVState *cs, uint8_t *mem_buf, int n)
+{
+ if (n == 0) {
+#ifdef CONFIG_USER_ONLY
+ return gdb_get_regl(mem_buf, 0);
+#else
+ return gdb_get_regl(mem_buf, cs->priv);
+#endif
+ }
+ return 0;
+}
+
+static int riscv_gdb_set_virtual(CPURISCVState *cs, uint8_t *mem_buf, int n)
+{
+ if (n == 0) {
+#ifndef CONFIG_USER_ONLY
+ cs->priv = ldtul_p(mem_buf) & 0x3;
+ if (cs->priv == PRV_H) {
+ cs->priv = PRV_S;
+ }
+#endif
+ return sizeof(target_ulong);
+ }
+ return 0;
+}
+
void riscv_cpu_register_gdb_regs_for_features(CPUState *cs)
{
RISCVCPU *cpu = RISCV_CPU(cs);
@@ -384,7 +410,10 @@ void riscv_cpu_register_gdb_regs_for_features(CPUState *cs)
}
gdb_register_coprocessor(cs, riscv_gdb_get_csr, riscv_gdb_set_csr,
- 4096, "riscv-32bit-csr.xml", 0);
+ 240, "riscv-32bit-csr.xml", 0);
+
+ gdb_register_coprocessor(cs, riscv_gdb_get_virtual, riscv_gdb_set_virtual,
+ 1, "riscv-32bit-virtual.xml", 0);
#elif defined(TARGET_RISCV64)
if (env->misa & RVF) {
gdb_register_coprocessor(cs, riscv_gdb_get_fpu, riscv_gdb_set_fpu,
@@ -392,6 +421,9 @@ void riscv_cpu_register_gdb_regs_for_features(CPUState *cs)
}
gdb_register_coprocessor(cs, riscv_gdb_get_csr, riscv_gdb_set_csr,
- 4096, "riscv-64bit-csr.xml", 0);
+ 240, "riscv-64bit-csr.xml", 0);
+
+ gdb_register_coprocessor(cs, riscv_gdb_get_virtual, riscv_gdb_set_virtual,
+ 1, "riscv-64bit-virtual.xml", 0);
#endif
}
diff --git a/target/riscv/pmp.c b/target/riscv/pmp.c
index d4f1007109..0e6b640fbd 100644
--- a/target/riscv/pmp.c
+++ b/target/riscv/pmp.c
@@ -223,6 +223,7 @@ bool pmp_hart_has_privs(CPURISCVState *env, target_ulong addr,
{
int i = 0;
int ret = -1;
+ int pmp_size = 0;
target_ulong s = 0;
target_ulong e = 0;
pmp_priv_t allowed_privs = 0;
@@ -232,11 +233,21 @@ bool pmp_hart_has_privs(CPURISCVState *env, target_ulong addr,
return true;
}
+ /*
+ * if size is unknown (0), assume that all bytes
+ * from addr to the end of the page will be accessed.
+ */
+ if (size == 0) {
+ pmp_size = -(addr | TARGET_PAGE_MASK);
+ } else {
+ pmp_size = size;
+ }
+
/* 1.10 draft priv spec states there is an implicit order
from low to high */
for (i = 0; i < MAX_RISCV_PMPS; i++) {
s = pmp_is_in_range(env, i, addr);
- e = pmp_is_in_range(env, i, addr + size - 1);
+ e = pmp_is_in_range(env, i, addr + pmp_size - 1);
/* partially inside */
if ((s + e) == 1) {
diff --git a/tcg/tci.c b/tcg/tci.c
index 33edca1903..a6208653e8 100644
--- a/tcg/tci.c
+++ b/tcg/tci.c
@@ -128,6 +128,12 @@ static void tci_write_reg8(tcg_target_ulong *regs, TCGReg index, uint8_t value)
}
static void
+tci_write_reg16(tcg_target_ulong *regs, TCGReg index, uint16_t value)
+{
+ tci_write_reg(regs, index, value);
+}
+
+static void
tci_write_reg32(tcg_target_ulong *regs, TCGReg index, uint32_t value)
{
tci_write_reg(regs, index, value);
@@ -585,6 +591,8 @@ uintptr_t tcg_qemu_tb_exec(CPUArchState *env, uint8_t *tb_ptr)
tci_write_reg8(regs, t0, *(uint8_t *)(t1 + t2));
break;
case INDEX_op_ld8s_i32:
+ TODO();
+ break;
case INDEX_op_ld16u_i32:
TODO();
break;
@@ -854,7 +862,14 @@ uintptr_t tcg_qemu_tb_exec(CPUArchState *env, uint8_t *tb_ptr)
tci_write_reg8(regs, t0, *(uint8_t *)(t1 + t2));
break;
case INDEX_op_ld8s_i64:
+ TODO();
+ break;
case INDEX_op_ld16u_i64:
+ t0 = *tb_ptr++;
+ t1 = tci_read_r(regs, &tb_ptr);
+ t2 = tci_read_s32(&tb_ptr);
+ tci_write_reg16(regs, t0, *(uint16_t *)(t1 + t2));
+ break;
case INDEX_op_ld16s_i64:
TODO();
break;
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 13afe0261d..c79402ab75 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -140,7 +140,7 @@ check-unit-y += tests/test-base64$(EXESUF)
check-unit-$(call land,$(CONFIG_BLOCK),$(if $(CONFIG_NETTLE),y,$(CONFIG_GCRYPT))) += tests/test-crypto-pbkdf$(EXESUF)
check-unit-$(CONFIG_BLOCK) += tests/test-crypto-ivgen$(EXESUF)
check-unit-$(CONFIG_BLOCK) += tests/test-crypto-afsplit$(EXESUF)
-check-unit-$(CONFIG_BLOCK) += tests/test-crypto-xts$(EXESUF)
+check-unit-$(if $(CONFIG_BLOCK),$(CONFIG_QEMU_PRIVATE_XTS)) += tests/test-crypto-xts$(EXESUF)
check-unit-$(CONFIG_BLOCK) += tests/test-crypto-block$(EXESUF)
check-unit-y += tests/test-logging$(EXESUF)
check-unit-$(call land,$(CONFIG_BLOCK),$(CONFIG_REPLICATION)) += tests/test-replication$(EXESUF)
@@ -341,7 +341,11 @@ qapi-schema += base-cycle-indirect.json
qapi-schema += command-int.json
qapi-schema += comments.json
qapi-schema += doc-bad-alternate-member.json
+qapi-schema += doc-bad-boxed-command-arg.json
qapi-schema += doc-bad-command-arg.json
+qapi-schema += doc-bad-enum-member.json
+qapi-schema += doc-bad-event-arg.json
+qapi-schema += doc-bad-feature.json
qapi-schema += doc-bad-section.json
qapi-schema += doc-bad-symbol.json
qapi-schema += doc-bad-union-member.json
@@ -365,6 +369,7 @@ qapi-schema += doc-missing-expr.json
qapi-schema += doc-missing-space.json
qapi-schema += doc-missing.json
qapi-schema += doc-no-symbol.json
+qapi-schema += doc-undoc-feature.json
qapi-schema += double-type.json
qapi-schema += duplicate-key.json
qapi-schema += empty.json
diff --git a/tests/acceptance/avocado_qemu/__init__.py b/tests/acceptance/avocado_qemu/__init__.py
index bd41e0443c..9a57c020d8 100644
--- a/tests/acceptance/avocado_qemu/__init__.py
+++ b/tests/acceptance/avocado_qemu/__init__.py
@@ -8,9 +8,11 @@
# This work is licensed under the terms of the GNU GPL, version 2 or
# later. See the COPYING file in the top-level directory.
+import logging
import os
import sys
import uuid
+import tempfile
import avocado
@@ -53,6 +55,50 @@ def pick_default_qemu_bin(arch=None):
return qemu_bin_from_src_dir_path
+def wait_for_console_pattern(test, success_message, failure_message=None):
+ """
+ Waits for messages to appear on the console, while logging the content
+
+ :param test: an Avocado test containing a VM that will have its console
+ read and probed for a success or failure message
+ :type test: :class:`avocado_qemu.Test`
+ :param success_message: if this message appears, test succeeds
+ :param failure_message: if this message appears, test fails
+ """
+ console = test.vm.console_socket.makefile()
+ console_logger = logging.getLogger('console')
+ while True:
+ msg = console.readline().strip()
+ if not msg:
+ continue
+ console_logger.debug(msg)
+ if success_message in msg:
+ break
+ if failure_message and failure_message in msg:
+ console.close()
+ fail = 'Failure message found in console: %s' % failure_message
+ test.fail(fail)
+
+
+def exec_command_and_wait_for_pattern(test, command,
+ success_message, failure_message=None):
+ """
+ Send a command to a console (appending CRLF characters), then wait
+ for success_message to appear on the console, while logging the.
+ content. Mark the test as failed if failure_message is found instead.
+
+ :param test: an Avocado test containing a VM that will have its console
+ read and probed for a success or failure message
+ :type test: :class:`avocado_qemu.Test`
+ :param command: the command to send
+ :param success_message: if this message appears, test succeeds
+ :param failure_message: if this message appears, test fails
+ """
+ command += '\r'
+ test.vm.console_socket.sendall(command.encode())
+ wait_for_console_pattern(test, success_message, failure_message)
+
+
class Test(avocado.Test):
def setUp(self):
self._vms = {}
@@ -69,7 +115,7 @@ class Test(avocado.Test):
self.cancel("No QEMU binary defined or found in the source tree")
def _new_vm(self, *args):
- vm = QEMUMachine(self.qemu_bin)
+ vm = QEMUMachine(self.qemu_bin, sock_dir=tempfile.mkdtemp())
if args:
vm.add_args(*args)
return vm
diff --git a/tests/acceptance/boot_linux_console.py b/tests/acceptance/boot_linux_console.py
index 8a9a314ab4..4e9ac0ecc3 100644
--- a/tests/acceptance/boot_linux_console.py
+++ b/tests/acceptance/boot_linux_console.py
@@ -9,12 +9,14 @@
# later. See the COPYING file in the top-level directory.
import os
-import logging
import lzma
import gzip
import shutil
+from avocado import skipUnless
from avocado_qemu import Test
+from avocado_qemu import exec_command_and_wait_for_pattern
+from avocado_qemu import wait_for_console_pattern
from avocado.utils import process
from avocado.utils import archive
@@ -29,31 +31,9 @@ class BootLinuxConsole(Test):
KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 '
- def wait_for_console_pattern(self, success_message,
- failure_message='Kernel panic - not syncing'):
- """
- Waits for messages to appear on the console, while logging the content
-
- :param success_message: if this message appears, test succeeds
- :param failure_message: if this message appears, test fails
- """
- console = self.vm.console_socket.makefile()
- console_logger = logging.getLogger('console')
- while True:
- msg = console.readline().strip()
- if not msg:
- continue
- console_logger.debug(msg)
- if success_message in msg:
- break
- if failure_message in msg:
- fail = 'Failure message found in console: %s' % failure_message
- self.fail(fail)
-
- def exec_command_and_wait_for_pattern(self, command, success_message):
- command += '\n'
- self.vm.console_socket.sendall(command.encode())
- self.wait_for_console_pattern(success_message)
+ def wait_for_console_pattern(self, success_message):
+ wait_for_console_pattern(self, success_message,
+ failure_message='Kernel panic - not syncing')
def extract_from_deb(self, deb, path):
"""
@@ -166,10 +146,7 @@ class BootLinuxConsole(Test):
initrd_hash = 'bf806e17009360a866bf537f6de66590de349a99'
initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash)
initrd_path = self.workdir + "rootfs.cpio"
-
- with gzip.open(initrd_path_gz, 'rb') as f_in:
- with open(initrd_path, 'wb') as f_out:
- shutil.copyfileobj(f_in, f_out)
+ archive.gzip_uncompress(initrd_path_gz, initrd_path)
self.vm.set_machine('malta')
self.vm.set_console()
@@ -183,12 +160,53 @@ class BootLinuxConsole(Test):
self.vm.launch()
self.wait_for_console_pattern('Boot successful.')
- self.exec_command_and_wait_for_pattern('cat /proc/cpuinfo',
- 'BogoMIPS')
- self.exec_command_and_wait_for_pattern('uname -a',
- 'Debian')
- self.exec_command_and_wait_for_pattern('reboot',
- 'reboot: Restarting system')
+ exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo',
+ 'BogoMIPS')
+ exec_command_and_wait_for_pattern(self, 'uname -a',
+ 'Debian')
+ exec_command_and_wait_for_pattern(self, 'reboot',
+ 'reboot: Restarting system')
+
+ @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code')
+ def test_mips64el_malta_5KEc_cpio(self):
+ """
+ :avocado: tags=arch:mips64el
+ :avocado: tags=machine:malta
+ :avocado: tags=endian:little
+ """
+ kernel_url = ('https://github.com/philmd/qemu-testing-blob/'
+ 'raw/9ad2df38/mips/malta/mips64el/'
+ 'vmlinux-3.19.3.mtoman.20150408')
+ kernel_hash = '00d1d268fb9f7d8beda1de6bebcc46e884d71754'
+ kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
+ initrd_url = ('https://github.com/groeck/linux-build-test/'
+ 'raw/8584a59e/rootfs/'
+ 'mipsel64/rootfs.mipsel64r1.cpio.gz')
+ initrd_hash = '1dbb8a396e916847325284dbe2151167'
+ initrd_path_gz = self.fetch_asset(initrd_url, algorithm='md5',
+ asset_hash=initrd_hash)
+ initrd_path = self.workdir + "rootfs.cpio"
+ archive.gzip_uncompress(initrd_path_gz, initrd_path)
+
+ self.vm.set_machine('malta')
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE
+ + 'console=ttyS0 console=tty '
+ + 'rdinit=/sbin/init noreboot')
+ self.vm.add_args('-cpu', '5KEc',
+ '-kernel', kernel_path,
+ '-initrd', initrd_path,
+ '-append', kernel_command_line,
+ '-no-reboot')
+ self.vm.launch()
+ wait_for_console_pattern(self, 'Boot successful.')
+
+ exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo',
+ 'MIPS 5KE')
+ exec_command_and_wait_for_pattern(self, 'uname -a',
+ '3.19.3.mtoman.20150408')
+ exec_command_and_wait_for_pattern(self, 'reboot',
+ 'reboot: Restarting system')
def do_test_mips_malta32el_nanomips(self, kernel_url, kernel_hash):
kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
@@ -316,6 +334,83 @@ class BootLinuxConsole(Test):
self.vm.launch()
self.wait_for_console_pattern('init started: BusyBox')
+ def do_test_arm_raspi2(self, uart_id):
+ """
+ The kernel can be rebuilt using the kernel source referenced
+ and following the instructions on the on:
+ https://www.raspberrypi.org/documentation/linux/kernel/building.md
+ """
+ serial_kernel_cmdline = {
+ 0: 'earlycon=pl011,0x3f201000 console=ttyAMA0',
+ }
+ deb_url = ('http://archive.raspberrypi.org/debian/'
+ 'pool/main/r/raspberrypi-firmware/'
+ 'raspberrypi-kernel_1.20190215-1_armhf.deb')
+ deb_hash = 'cd284220b32128c5084037553db3c482426f3972'
+ deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash)
+ kernel_path = self.extract_from_deb(deb_path, '/boot/kernel7.img')
+ dtb_path = self.extract_from_deb(deb_path, '/boot/bcm2709-rpi-2-b.dtb')
+
+ self.vm.set_machine('raspi2')
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+ serial_kernel_cmdline[uart_id])
+ self.vm.add_args('-kernel', kernel_path,
+ '-dtb', dtb_path,
+ '-append', kernel_command_line)
+ self.vm.launch()
+ console_pattern = 'Kernel command line: %s' % kernel_command_line
+ self.wait_for_console_pattern(console_pattern)
+
+ def test_arm_raspi2_uart0(self):
+ """
+ :avocado: tags=arch:arm
+ :avocado: tags=machine:raspi2
+ :avocado: tags=device:pl011
+ """
+ self.do_test_arm_raspi2(0)
+
+ def test_arm_exynos4210_initrd(self):
+ """
+ :avocado: tags=arch:arm
+ :avocado: tags=machine:smdkc210
+ """
+ deb_url = ('https://snapshot.debian.org/archive/debian/'
+ '20190928T224601Z/pool/main/l/linux/'
+ 'linux-image-4.19.0-6-armmp_4.19.67-2+deb10u1_armhf.deb')
+ deb_hash = 'fa9df4a0d38936cb50084838f2cb933f570d7d82'
+ deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash)
+ kernel_path = self.extract_from_deb(deb_path,
+ '/boot/vmlinuz-4.19.0-6-armmp')
+ dtb_path = '/usr/lib/linux-image-4.19.0-6-armmp/exynos4210-smdkv310.dtb'
+ dtb_path = self.extract_from_deb(deb_path, dtb_path)
+
+ initrd_url = ('https://github.com/groeck/linux-build-test/raw/'
+ '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/'
+ 'arm/rootfs-armv5.cpio.gz')
+ initrd_hash = '2b50f1873e113523967806f4da2afe385462ff9b'
+ initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash)
+ initrd_path = os.path.join(self.workdir, 'rootfs.cpio')
+ archive.gzip_uncompress(initrd_path_gz, initrd_path)
+
+ self.vm.set_machine('smdkc210')
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+ 'earlycon=exynos4210,0x13800000 earlyprintk ' +
+ 'console=ttySAC0,115200n8 ' +
+ 'random.trust_cpu=off cryptomgr.notests ' +
+ 'cpuidle.off=1 panic=-1 noreboot')
+
+ self.vm.add_args('-kernel', kernel_path,
+ '-dtb', dtb_path,
+ '-initrd', initrd_path,
+ '-append', kernel_command_line,
+ '-no-reboot')
+ self.vm.launch()
+
+ self.wait_for_console_pattern('Boot successful.')
+ # TODO user command, for now the uart is stuck
+
def test_s390x_s390_ccw_virtio(self):
"""
:avocado: tags=arch:s390x
@@ -378,3 +473,27 @@ class BootLinuxConsole(Test):
self.vm.launch()
console_pattern = 'Kernel command line: %s' % kernel_command_line
self.wait_for_console_pattern(console_pattern)
+
+ def test_m68k_q800(self):
+ """
+ :avocado: tags=arch:m68k
+ :avocado: tags=machine:q800
+ """
+ deb_url = ('http://ftp.ports.debian.org/debian-ports/pool-m68k/main'
+ '/l/linux/kernel-image-5.2.0-2-m68k-di_5.2.9-2_m68k.udeb')
+ deb_hash = '0797e05129595f22f3c0142db5e199769a723bf9'
+ deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash)
+ kernel_path = self.extract_from_deb(deb_path,
+ '/boot/vmlinux-5.2.0-2-m68k')
+
+ self.vm.set_machine('q800')
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+ 'console=ttyS0 vga=off')
+ self.vm.add_args('-kernel', kernel_path,
+ '-append', kernel_command_line)
+ self.vm.launch()
+ console_pattern = 'Kernel command line: %s' % kernel_command_line
+ self.wait_for_console_pattern(console_pattern)
+ console_pattern = 'No filesystem could mount root'
+ self.wait_for_console_pattern(console_pattern)
diff --git a/tests/acceptance/linux_ssh_mips_malta.py b/tests/acceptance/linux_ssh_mips_malta.py
index aa12001942..fc13f9e4d4 100644
--- a/tests/acceptance/linux_ssh_mips_malta.py
+++ b/tests/acceptance/linux_ssh_mips_malta.py
@@ -13,6 +13,7 @@ import time
from avocado import skipUnless
from avocado_qemu import Test
+from avocado_qemu import wait_for_console_pattern
from avocado.utils import process
from avocado.utils import archive
from avocado.utils import ssh
@@ -69,19 +70,6 @@ class LinuxSSH(Test):
def setUp(self):
super(LinuxSSH, self).setUp()
- def wait_for_console_pattern(self, success_message,
- failure_message='Oops'):
- console = self.vm.console_socket.makefile()
- console_logger = logging.getLogger('console')
- while True:
- msg = console.readline()
- console_logger.debug(msg.strip())
- if success_message in msg:
- break
- if failure_message in msg:
- fail = 'Failure message found in console: %s' % failure_message
- self.fail(fail)
-
def get_portfwd(self):
res = self.vm.command('human-monitor-command',
command_line='info usernet')
@@ -137,7 +125,7 @@ class LinuxSSH(Test):
self.log.info('VM launched, waiting for sshd')
console_pattern = 'Starting OpenBSD Secure Shell server: sshd'
- self.wait_for_console_pattern(console_pattern)
+ wait_for_console_pattern(self, console_pattern, 'Oops')
self.log.info('sshd ready')
self.ssh_connect('root', 'root')
@@ -145,7 +133,7 @@ class LinuxSSH(Test):
def shutdown_via_ssh(self):
self.ssh_command('poweroff')
self.ssh_disconnect_vm()
- self.wait_for_console_pattern('Power down')
+ wait_for_console_pattern(self, 'Power down', 'Oops')
def ssh_command_output_contains(self, cmd, exp):
stdout, _ = self.ssh_command(cmd)
diff --git a/tests/acceptance/machine_sparc_leon3.py b/tests/acceptance/machine_sparc_leon3.py
new file mode 100644
index 0000000000..298f1e25e6
--- /dev/null
+++ b/tests/acceptance/machine_sparc_leon3.py
@@ -0,0 +1,34 @@
+# Functional test that boots a Leon3 machine and checks its serial console.
+#
+# Copyright (c) Philippe Mathieu-Daudé <f4bug@amsat.org>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later. See the COPYING file in the top-level directory.
+
+from avocado_qemu import Test
+from avocado_qemu import wait_for_console_pattern
+
+
+class Leon3Machine(Test):
+
+ timeout = 60
+
+ def test_leon3_helenos_uimage(self):
+ """
+ :avocado: tags=arch:sparc
+ :avocado: tags=machine:leon3
+ :avocado: tags=binfmt:uimage
+ """
+ kernel_url = ('http://www.helenos.org/releases/'
+ 'HelenOS-0.6.0-sparc32-leon3.bin')
+ kernel_hash = 'a88c9cfdb8430c66650e5290a08765f9bf049a30'
+ kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
+
+ self.vm.set_machine('leon3_generic')
+ self.vm.set_console()
+ self.vm.add_args('-kernel', kernel_path)
+
+ self.vm.launch()
+
+ wait_for_console_pattern(self, 'Copyright (c) 2001-2014 HelenOS project')
+ wait_for_console_pattern(self, 'Booting the kernel ...')
diff --git a/tests/acceptance/ppc_prep_40p.py b/tests/acceptance/ppc_prep_40p.py
new file mode 100644
index 0000000000..6f507fb0a6
--- /dev/null
+++ b/tests/acceptance/ppc_prep_40p.py
@@ -0,0 +1,82 @@
+# Functional test that boots a PReP/40p machine and checks its serial console.
+#
+# Copyright (c) Philippe Mathieu-Daudé <f4bug@amsat.org>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later. See the COPYING file in the top-level directory.
+
+import os
+
+from avocado import skipIf
+from avocado import skipUnless
+from avocado_qemu import Test
+from avocado_qemu import wait_for_console_pattern
+
+
+class IbmPrep40pMachine(Test):
+
+ timeout = 60
+
+ # 12H0455 PPS Firmware Licensed Materials
+ # Property of IBM (C) Copyright IBM Corp. 1994.
+ # All rights reserved.
+ # U.S. Government Users Restricted Rights - Use, duplication or disclosure
+ # restricted by GSA ADP Schedule Contract with IBM Corp.
+ @skipIf(os.getenv('CONTINUOUS_INTEGRATION'), 'Running on Travis-CI')
+ @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code')
+ def test_factory_firmware_and_netbsd(self):
+ """
+ :avocado: tags=arch:ppc
+ :avocado: tags=machine:40p
+ :avocado: tags=slowness:high
+ """
+ bios_url = ('ftp://ftp.boulder.ibm.com/rs6000/firmware/'
+ '7020-40p/P12H0456.IMG')
+ bios_hash = '1775face4e6dc27f3a6ed955ef6eb331bf817f03'
+ bios_path = self.fetch_asset(bios_url, asset_hash=bios_hash)
+ drive_url = ('https://ftp.netbsd.org/pub/NetBSD/NetBSD-archive/'
+ 'NetBSD-4.0/prep/installation/floppy/generic_com0.fs')
+ drive_hash = 'dbcfc09912e71bd5f0d82c7c1ee43082fb596ceb'
+ drive_path = self.fetch_asset(drive_url, asset_hash=drive_hash)
+
+ self.vm.set_machine('40p')
+ self.vm.set_console()
+ self.vm.add_args('-bios', bios_path,
+ '-fda', drive_path)
+ self.vm.launch()
+ os_banner = 'NetBSD 4.0 (GENERIC) #0: Sun Dec 16 00:49:40 PST 2007'
+ wait_for_console_pattern(self, os_banner)
+ wait_for_console_pattern(self, 'Model: IBM PPS Model 6015')
+
+ def test_openbios_192m(self):
+ """
+ :avocado: tags=arch:ppc
+ :avocado: tags=machine:40p
+ """
+ self.vm.set_machine('40p')
+ self.vm.set_console()
+ self.vm.add_args('-m', '192') # test fw_cfg
+
+ self.vm.launch()
+ wait_for_console_pattern(self, '>> OpenBIOS')
+ wait_for_console_pattern(self, '>> Memory: 192M')
+ wait_for_console_pattern(self, '>> CPU type PowerPC,604')
+
+ @skipIf(os.getenv('CONTINUOUS_INTEGRATION'), 'Running on Travis-CI')
+ def test_openbios_and_netbsd(self):
+ """
+ :avocado: tags=arch:ppc
+ :avocado: tags=machine:40p
+ """
+ drive_url = ('https://ftp.netbsd.org/pub/NetBSD/iso/7.1.2/'
+ 'NetBSD-7.1.2-prep.iso')
+ drive_hash = 'ac6fa2707d888b36d6fa64de6e7fe48e'
+ drive_path = self.fetch_asset(drive_url, asset_hash=drive_hash,
+ algorithm='md5')
+ self.vm.set_machine('40p')
+ self.vm.set_console()
+ self.vm.add_args('-cdrom', drive_path,
+ '-boot', 'd')
+
+ self.vm.launch()
+ wait_for_console_pattern(self, 'NetBSD/prep BOOT, Revision 1.9')
diff --git a/tests/benchmark-crypto-cipher.c b/tests/benchmark-crypto-cipher.c
index 67fdf8c31d..53032334ec 100644
--- a/tests/benchmark-crypto-cipher.c
+++ b/tests/benchmark-crypto-cipher.c
@@ -21,11 +21,12 @@ static void test_cipher_speed(size_t chunk_size,
{
QCryptoCipher *cipher;
Error *err = NULL;
- double total = 0.0;
uint8_t *key = NULL, *iv = NULL;
uint8_t *plaintext = NULL, *ciphertext = NULL;
size_t nkey;
size_t niv;
+ const size_t total = 2 * GiB;
+ size_t remain;
if (!qcrypto_cipher_supports(alg, mode)) {
return;
@@ -58,33 +59,34 @@ static void test_cipher_speed(size_t chunk_size,
&err) == 0);
g_test_timer_start();
- do {
+ remain = total;
+ while (remain) {
g_assert(qcrypto_cipher_encrypt(cipher,
plaintext,
ciphertext,
chunk_size,
&err) == 0);
- total += chunk_size;
- } while (g_test_timer_elapsed() < 1.0);
+ remain -= chunk_size;
+ }
+ g_test_timer_elapsed();
- total /= MiB;
g_print("Enc chunk %zu bytes ", chunk_size);
- g_print("%.2f MB/sec ", total / g_test_timer_last());
+ g_print("%.2f MB/sec ", (double)total / MiB / g_test_timer_last());
- total = 0.0;
g_test_timer_start();
- do {
+ remain = total;
+ while (remain) {
g_assert(qcrypto_cipher_decrypt(cipher,
plaintext,
ciphertext,
chunk_size,
&err) == 0);
- total += chunk_size;
- } while (g_test_timer_elapsed() < 1.0);
+ remain -= chunk_size;
+ }
+ g_test_timer_elapsed();
- total /= MiB;
g_print("Dec chunk %zu bytes ", chunk_size);
- g_print("%.2f MB/sec ", total / g_test_timer_last());
+ g_print("%.2f MB/sec ", (double)total / MiB / g_test_timer_last());
qcrypto_cipher_free(cipher);
g_free(plaintext);
@@ -161,15 +163,26 @@ static void test_cipher_speed_xts_aes_256(const void *opaque)
int main(int argc, char **argv)
{
+ char *alg = NULL;
+ char *size = NULL;
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
#define ADD_TEST(mode, cipher, keysize, chunk) \
- g_test_add_data_func( \
+ if ((!alg || g_str_equal(alg, #mode)) && \
+ (!size || g_str_equal(size, #chunk))) \
+ g_test_add_data_func( \
"/crypto/cipher/" #mode "-" #cipher "-" #keysize "/chunk-" #chunk, \
(void *)chunk, \
test_cipher_speed_ ## mode ## _ ## cipher ## _ ## keysize)
+ if (argc >= 2) {
+ alg = argv[1];
+ }
+ if (argc >= 3) {
+ size = argv[2];
+ }
+
#define ADD_TESTS(chunk) \
do { \
ADD_TEST(ecb, aes, 128, chunk); \
diff --git a/tests/benchmark-crypto-hash.c b/tests/benchmark-crypto-hash.c
index 9b6f7a9155..7f659f7323 100644
--- a/tests/benchmark-crypto-hash.c
+++ b/tests/benchmark-crypto-hash.c
@@ -20,7 +20,8 @@ static void test_hash_speed(const void *opaque)
size_t chunk_size = (size_t)opaque;
uint8_t *in = NULL, *out = NULL;
size_t out_len = 0;
- double total = 0.0;
+ const size_t total = 2 * GiB;
+ size_t remain;
struct iovec iov;
int ret;
@@ -31,20 +32,20 @@ static void test_hash_speed(const void *opaque)
iov.iov_len = chunk_size;
g_test_timer_start();
- do {
+ remain = total;
+ while (remain) {
ret = qcrypto_hash_bytesv(QCRYPTO_HASH_ALG_SHA256,
&iov, 1, &out, &out_len,
NULL);
g_assert(ret == 0);
- total += chunk_size;
- } while (g_test_timer_elapsed() < 5.0);
+ remain -= chunk_size;
+ }
+ g_test_timer_elapsed();
- total /= MiB;
g_print("sha256: ");
- g_print("Testing chunk_size %zu bytes ", chunk_size);
- g_print("done: %.2f MB in %.2f secs: ", total, g_test_timer_last());
- g_print("%.2f MB/sec\n", total / g_test_timer_last());
+ g_print("Hash %zu GB chunk size %zu bytes ", total / GiB, chunk_size);
+ g_print("%.2f MB/sec ", (double)total / MiB / g_test_timer_last());
g_free(out);
g_free(in);
diff --git a/tests/libqos/libqos.c b/tests/libqos/libqos.c
index d71557c5cb..f229eb2cb8 100644
--- a/tests/libqos/libqos.c
+++ b/tests/libqos/libqos.c
@@ -125,7 +125,8 @@ void migrate(QOSState *from, QOSState *to, const char *uri)
break;
}
- if ((strcmp(st, "setup") == 0) || (strcmp(st, "active") == 0)) {
+ if ((strcmp(st, "setup") == 0) || (strcmp(st, "active") == 0)
+ || (strcmp(st, "wait-unplug") == 0)) {
qobject_unref(rsp);
g_usleep(5000);
continue;
diff --git a/tests/qapi-schema/doc-bad-alternate-member.err b/tests/qapi-schema/doc-bad-alternate-member.err
index a1c282f935..d7286bb57c 100644
--- a/tests/qapi-schema/doc-bad-alternate-member.err
+++ b/tests/qapi-schema/doc-bad-alternate-member.err
@@ -1 +1 @@
-doc-bad-alternate-member.json:3: the following documented members are not in the declaration: aa, bb
+doc-bad-alternate-member.json:3: documented members 'aa', 'bb' do not exist
diff --git a/tests/qapi-schema/doc-bad-boxed-command-arg.err b/tests/qapi-schema/doc-bad-boxed-command-arg.err
new file mode 100644
index 0000000000..7137af3ec9
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-boxed-command-arg.err
@@ -0,0 +1 @@
+doc-bad-boxed-command-arg.json:9: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-boxed-command-arg.json b/tests/qapi-schema/doc-bad-boxed-command-arg.json
new file mode 100644
index 0000000000..bd143241ec
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-boxed-command-arg.json
@@ -0,0 +1,14 @@
+# Boxed arguments are not to be documented with the command
+
+##
+# @Args:
+# @a: an argument
+##
+{ 'struct': 'Args', 'data': { 'a': 'int' } }
+
+##
+# @cmd-boxed:
+# @a: bogus
+##
+{ 'command': 'cmd-boxed', 'boxed': true,
+ 'data': 'Args' }
diff --git a/tests/qapi-schema/doc-bad-boxed-command-arg.out b/tests/qapi-schema/doc-bad-boxed-command-arg.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-boxed-command-arg.out
diff --git a/tests/qapi-schema/doc-bad-command-arg.err b/tests/qapi-schema/doc-bad-command-arg.err
index 153ea0330a..18ed076cef 100644
--- a/tests/qapi-schema/doc-bad-command-arg.err
+++ b/tests/qapi-schema/doc-bad-command-arg.err
@@ -1 +1 @@
-doc-bad-command-arg.json:3: the following documented members are not in the declaration: b
+doc-bad-command-arg.json:3: documented member 'b' does not exist
diff --git a/tests/qapi-schema/doc-bad-enum-member.err b/tests/qapi-schema/doc-bad-enum-member.err
new file mode 100644
index 0000000000..7efeb47363
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-enum-member.err
@@ -0,0 +1 @@
+doc-bad-enum-member.json:3: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-enum-member.json b/tests/qapi-schema/doc-bad-enum-member.json
new file mode 100644
index 0000000000..9cab35c6e8
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-enum-member.json
@@ -0,0 +1,8 @@
+# Members listed in the doc comment must exist in the actual schema
+
+##
+# @Foo:
+# @a: a
+# @b: b
+##
+{ 'enum': 'Foo', 'data': [ 'b' ] }
diff --git a/tests/qapi-schema/doc-bad-enum-member.out b/tests/qapi-schema/doc-bad-enum-member.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-enum-member.out
diff --git a/tests/qapi-schema/doc-bad-event-arg.err b/tests/qapi-schema/doc-bad-event-arg.err
new file mode 100644
index 0000000000..d13cacf21f
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-event-arg.err
@@ -0,0 +1 @@
+doc-bad-event-arg.json:3: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-event-arg.json b/tests/qapi-schema/doc-bad-event-arg.json
new file mode 100644
index 0000000000..23c83cc81f
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-event-arg.json
@@ -0,0 +1,7 @@
+# Arguments listed in the doc comment must exist in the actual schema
+
+##
+# @FOO:
+# @a: a
+##
+{ 'event': 'FOO' }
diff --git a/tests/qapi-schema/doc-bad-event-arg.out b/tests/qapi-schema/doc-bad-event-arg.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-event-arg.out
diff --git a/tests/qapi-schema/doc-bad-feature.err b/tests/qapi-schema/doc-bad-feature.err
new file mode 100644
index 0000000000..e4c62adfa3
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-feature.err
@@ -0,0 +1 @@
+doc-bad-feature.json:3: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-feature.json b/tests/qapi-schema/doc-bad-feature.json
new file mode 100644
index 0000000000..3d49b8e607
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-feature.json
@@ -0,0 +1,9 @@
+# Features listed in the doc comment must exist in the actual schema
+
+##
+# @foo:
+#
+# Features:
+# @a: a
+##
+{ 'command': 'foo' }
diff --git a/tests/qapi-schema/doc-bad-feature.out b/tests/qapi-schema/doc-bad-feature.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-feature.out
diff --git a/tests/qapi-schema/doc-bad-union-member.err b/tests/qapi-schema/doc-bad-union-member.err
index 8b9d36eab1..6dd2726a65 100644
--- a/tests/qapi-schema/doc-bad-union-member.err
+++ b/tests/qapi-schema/doc-bad-union-member.err
@@ -1 +1 @@
-doc-bad-union-member.json:3: the following documented members are not in the declaration: a, b
+doc-bad-union-member.json:3: documented members 'a', 'b' do not exist
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
index 7dc21e58a3..d992e713d9 100644
--- a/tests/qapi-schema/doc-good.json
+++ b/tests/qapi-schema/doc-good.json
@@ -99,6 +99,14 @@
'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
##
+# @Alternate:
+# @i: an integer
+# @b is undocumented
+##
+{ 'alternate': 'Alternate',
+ 'data': { 'i': 'int', 'b': 'bool' } }
+
+##
# == Another subsection
##
@@ -149,3 +157,9 @@
{ 'command': 'cmd-boxed', 'boxed': true,
'data': 'Object',
'features': [ 'cmd-feat1', 'cmd-feat2' ] }
+
+##
+# @EVT-BOXED:
+##
+{ 'event': 'EVT-BOXED', 'boxed': true,
+ 'data': 'Object' }
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index f78fdef6a9..4c9406a464 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -42,6 +42,10 @@ object SugaredUnion
case one: q_obj_Variant1-wrapper
case two: q_obj_Variant2-wrapper
if ['IFTWO']
+alternate Alternate
+ tag type
+ case i: int
+ case b: bool
object q_obj_cmd-arg
member arg1: int optional=False
member arg2: str optional=True
@@ -54,6 +58,8 @@ command cmd-boxed Object -> None
gen=True success_response=True boxed=True oob=False preconfig=False
feature cmd-feat1
feature cmd-feat2
+event EVT-BOXED Object
+ boxed=True
doc freeform
body=
= Section
@@ -120,6 +126,8 @@ A paragraph
Another paragraph (but no @var: line)
arg=var1
+ feature=variant1-feat
+a feature
doc symbol=Variant2
body=
@@ -131,6 +139,14 @@ doc symbol=SugaredUnion
arg=type
+doc symbol=Alternate
+ body=
+
+ arg=i
+an integer
+@b is undocumented
+ arg=b
+
doc freeform
body=
== Another subsection
@@ -144,6 +160,10 @@ the second
argument
arg=arg3
+ feature=cmd-feat1
+a feature
+ feature=cmd-feat2
+another feature
section=Note
@arg3 is undocumented
section=Returns
@@ -166,7 +186,14 @@ Duis aute irure dolor
doc symbol=cmd-boxed
body=
If you're bored enough to read this, go see a video of boxed cats
+ feature=cmd-feat1
+a feature
+ feature=cmd-feat2
+another feature
section=Example
-> in
<- out
+doc symbol=EVT-BOXED
+ body=
+
diff --git a/tests/qapi-schema/doc-good.texi b/tests/qapi-schema/doc-good.texi
index 2ce8b883c9..d4b15dabf0 100644
--- a/tests/qapi-schema/doc-good.texi
+++ b/tests/qapi-schema/doc-good.texi
@@ -170,6 +170,23 @@ One of @t{"one"}, @t{"two"}
@end deftp
+
+@deftp {Alternate} Alternate
+
+
+
+@b{Members:}
+@table @asis
+@item @code{i: int}
+an integer
+@code{b} is undocumented
+@item @code{b: boolean}
+Not documented
+@end table
+
+@end deftp
+
+
@subsection Another subsection
@@ -258,3 +275,13 @@ another feature
@end deftypefn
+
+
+@deftypefn Event {} EVT-BOXED
+
+
+
+@b{Arguments:} the members of @code{Object}
+
+@end deftypefn
+
diff --git a/tests/qapi-schema/doc-undoc-feature.err b/tests/qapi-schema/doc-undoc-feature.err
new file mode 100644
index 0000000000..62fc82d2b9
--- /dev/null
+++ b/tests/qapi-schema/doc-undoc-feature.err
@@ -0,0 +1,2 @@
+doc-undoc-feature.json: In command 'foo':
+doc-undoc-feature.json:9: feature 'undoc' lacks documentation
diff --git a/tests/qapi-schema/doc-undoc-feature.json b/tests/qapi-schema/doc-undoc-feature.json
new file mode 100644
index 0000000000..c52f88e2cd
--- /dev/null
+++ b/tests/qapi-schema/doc-undoc-feature.json
@@ -0,0 +1,9 @@
+# Doc comment must cover all features
+
+##
+# @foo:
+#
+# Features:
+# @doc: documented feature
+##
+{ 'command': 'foo', 'features': ['undoc', 'doc'] }
diff --git a/tests/qapi-schema/doc-undoc-feature.out b/tests/qapi-schema/doc-undoc-feature.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-undoc-feature.out
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 2bd9fd8742..bad14edb47 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -117,6 +117,8 @@ def test_frontend(fname):
print(' body=\n%s' % doc.body.text)
for arg, section in doc.args.items():
print(' arg=%s\n%s' % (arg, section.text))
+ for feat, section in doc.features.items():
+ print(' feature=%s\n%s' % (feat, section.text))
for section in doc.sections:
print(' section=%s\n%s' % (section.name, section.text))
diff --git a/tests/qemu-iotests/083 b/tests/qemu-iotests/083
index b270550d3e..10fdfc8ebb 100755
--- a/tests/qemu-iotests/083
+++ b/tests/qemu-iotests/083
@@ -28,7 +28,7 @@ status=1 # failure is the default!
_cleanup()
{
- rm -f nbd.sock
+ rm -f "$SOCK_DIR/nbd.sock"
rm -f nbd-fault-injector.out
rm -f nbd-fault-injector.conf
}
@@ -80,10 +80,10 @@ EOF
if [ "$proto" = "tcp" ]; then
nbd_addr="127.0.0.1:0"
else
- nbd_addr="$TEST_DIR/nbd.sock"
+ nbd_addr="$SOCK_DIR/nbd.sock"
fi
- rm -f "$TEST_DIR/nbd.sock"
+ rm -f "$SOCK_DIR/nbd.sock"
echo > "$TEST_DIR/nbd-fault-injector.out"
$PYTHON nbd-fault-injector.py $extra_args "$nbd_addr" "$TEST_DIR/nbd-fault-injector.conf" >"$TEST_DIR/nbd-fault-injector.out" 2>&1 &
diff --git a/tests/qemu-iotests/083.out b/tests/qemu-iotests/083.out
index eee6dd1379..2090ee693c 100644
--- a/tests/qemu-iotests/083.out
+++ b/tests/qemu-iotests/083.out
@@ -110,43 +110,43 @@ read failed: Input/output error
=== Check disconnect before neg1 ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect after neg1 ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 8 neg1 ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 16 neg1 ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect before export ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect after export ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 4 export ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 12 export ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 16 export ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect before neg2 ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect after neg2 ===
@@ -154,11 +154,11 @@ read failed: Input/output error
=== Check disconnect 8 neg2 ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 10 neg2 ===
-qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect before request ===
@@ -195,23 +195,23 @@ read 512/512 bytes at offset 0
=== Check disconnect before neg-classic ===
-qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 8 neg-classic ===
-qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 16 neg-classic ===
-qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 24 neg-classic ===
-qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 28 neg-classic ===
-qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
+qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect after neg-classic ===
diff --git a/tests/qemu-iotests/093 b/tests/qemu-iotests/093
index 3c4f5173ce..f03fa24a07 100755
--- a/tests/qemu-iotests/093
+++ b/tests/qemu-iotests/093
@@ -24,7 +24,7 @@ import iotests
nsec_per_sec = 1000000000
class ThrottleTestCase(iotests.QMPTestCase):
- test_img = "null-aio://"
+ test_driver = "null-aio"
max_drives = 3
def blockstats(self, device):
@@ -35,10 +35,14 @@ class ThrottleTestCase(iotests.QMPTestCase):
return stat['rd_bytes'], stat['rd_operations'], stat['wr_bytes'], stat['wr_operations']
raise Exception("Device not found for blockstats: %s" % device)
+ def required_drivers(self):
+ return [self.test_driver]
+
+ @iotests.skip_if_unsupported(required_drivers)
def setUp(self):
self.vm = iotests.VM()
for i in range(0, self.max_drives):
- self.vm.add_drive(self.test_img, "file.read-zeroes=on")
+ self.vm.add_drive(self.test_driver + "://", "file.read-zeroes=on")
self.vm.launch()
def tearDown(self):
@@ -264,16 +268,15 @@ class ThrottleTestCase(iotests.QMPTestCase):
self.assertEqual(self.blockstats('drive1')[0], 4096)
class ThrottleTestCoroutine(ThrottleTestCase):
- test_img = "null-co://"
+ test_driver = "null-co"
class ThrottleTestGroupNames(iotests.QMPTestCase):
- test_img = "null-aio://"
max_drives = 3
def setUp(self):
self.vm = iotests.VM()
for i in range(0, self.max_drives):
- self.vm.add_drive(self.test_img,
+ self.vm.add_drive("null-co://",
"throttling.iops-total=100,file.read-zeroes=on")
self.vm.launch()
@@ -376,10 +379,10 @@ class ThrottleTestRemovableMedia(iotests.QMPTestCase):
def test_removable_media(self):
# Add a couple of dummy nodes named cd0 and cd1
- result = self.vm.qmp("blockdev-add", driver="null-aio",
+ result = self.vm.qmp("blockdev-add", driver="null-co",
read_zeroes=True, node_name="cd0")
self.assert_qmp(result, 'return', {})
- result = self.vm.qmp("blockdev-add", driver="null-aio",
+ result = self.vm.qmp("blockdev-add", driver="null-co",
read_zeroes=True, node_name="cd1")
self.assert_qmp(result, 'return', {})
@@ -426,4 +429,6 @@ class ThrottleTestRemovableMedia(iotests.QMPTestCase):
if __name__ == '__main__':
+ if 'null-co' not in iotests.supported_formats():
+ iotests.notrun('null-co driver support missing')
iotests.main(supported_fmts=["raw"])
diff --git a/tests/qemu-iotests/136 b/tests/qemu-iotests/136
index a46a7b7630..012ea111ac 100755
--- a/tests/qemu-iotests/136
+++ b/tests/qemu-iotests/136
@@ -30,7 +30,7 @@ bad_offset = bad_sector * 512
blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf')
class BlockDeviceStatsTestCase(iotests.QMPTestCase):
- test_img = "null-aio://"
+ test_driver = "null-aio"
total_rd_bytes = 0
total_rd_ops = 0
total_wr_bytes = 0
@@ -67,6 +67,10 @@ sector = "%d"
''' % (bad_sector, bad_sector))
file.close()
+ def required_drivers(self):
+ return [self.test_driver]
+
+ @iotests.skip_if_unsupported(required_drivers)
def setUp(self):
drive_args = []
drive_args.append("stats-intervals.0=%d" % interval_length)
@@ -76,8 +80,8 @@ sector = "%d"
(self.account_failed and "on" or "off"))
drive_args.append("file.image.read-zeroes=on")
self.create_blkdebug_file()
- self.vm = iotests.VM().add_drive('blkdebug:%s:%s' %
- (blkdebug_file, self.test_img),
+ self.vm = iotests.VM().add_drive('blkdebug:%s:%s://' %
+ (blkdebug_file, self.test_driver),
','.join(drive_args))
self.vm.launch()
# Set an initial value for the clock
@@ -337,7 +341,9 @@ class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase):
account_failed = True
class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase):
- test_img = "null-co://"
+ test_driver = "null-co"
if __name__ == '__main__':
+ if 'null-co' not in iotests.supported_formats():
+ iotests.notrun('null-co driver support missing')
iotests.main(supported_fmts=["raw"])
diff --git a/tests/qemu-iotests/140 b/tests/qemu-iotests/140
index b965b1dd5d..8d2ce5d9e3 100755
--- a/tests/qemu-iotests/140
+++ b/tests/qemu-iotests/140
@@ -34,7 +34,7 @@ _cleanup()
{
_cleanup_qemu
_cleanup_test_img
- rm -f "$TEST_DIR/nbd"
+ rm -f "$SOCK_DIR/nbd"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -69,7 +69,7 @@ _send_qemu_cmd $QEMU_HANDLE \
_send_qemu_cmd $QEMU_HANDLE \
"{ 'execute': 'nbd-server-start',
'arguments': { 'addr': { 'type': 'unix',
- 'data': { 'path': '$TEST_DIR/nbd' }}}}" \
+ 'data': { 'path': '$SOCK_DIR/nbd' }}}}" \
'return'
_send_qemu_cmd $QEMU_HANDLE \
@@ -78,7 +78,7 @@ _send_qemu_cmd $QEMU_HANDLE \
'return'
$QEMU_IO_PROG -f raw -r -c 'read -P 42 0 64k' \
- "nbd+unix:///drv?socket=$TEST_DIR/nbd" 2>&1 \
+ "nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \
| _filter_qemu_io | _filter_nbd
_send_qemu_cmd $QEMU_HANDLE \
@@ -87,7 +87,7 @@ _send_qemu_cmd $QEMU_HANDLE \
'return'
$QEMU_IO_PROG -f raw -r -c close \
- "nbd+unix:///drv?socket=$TEST_DIR/nbd" 2>&1 \
+ "nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \
| _filter_qemu_io | _filter_nbd
_send_qemu_cmd $QEMU_HANDLE \
diff --git a/tests/qemu-iotests/140.out b/tests/qemu-iotests/140.out
index 67fe44a3e3..2511eb7369 100644
--- a/tests/qemu-iotests/140.out
+++ b/tests/qemu-iotests/140.out
@@ -8,7 +8,7 @@ wrote 65536/65536 bytes at offset 0
read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
{"return": {}}
-qemu-io: can't open device nbd+unix:///drv?socket=TEST_DIR/nbd: Requested export not available
+qemu-io: can't open device nbd+unix:///drv?socket=SOCK_DIR/nbd: Requested export not available
server reported: export 'drv' not present
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
diff --git a/tests/qemu-iotests/143 b/tests/qemu-iotests/143
index 92249ac8da..f649b36195 100755
--- a/tests/qemu-iotests/143
+++ b/tests/qemu-iotests/143
@@ -29,7 +29,7 @@ status=1 # failure is the default!
_cleanup()
{
_cleanup_qemu
- rm -f "$TEST_DIR/nbd"
+ rm -f "$SOCK_DIR/nbd"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -51,12 +51,12 @@ _send_qemu_cmd $QEMU_HANDLE \
_send_qemu_cmd $QEMU_HANDLE \
"{ 'execute': 'nbd-server-start',
'arguments': { 'addr': { 'type': 'unix',
- 'data': { 'path': '$TEST_DIR/nbd' }}}}" \
+ 'data': { 'path': '$SOCK_DIR/nbd' }}}}" \
'return'
# This should just result in a client error, not in the server crashing
$QEMU_IO_PROG -f raw -c quit \
- "nbd+unix:///no_such_export?socket=$TEST_DIR/nbd" 2>&1 \
+ "nbd+unix:///no_such_export?socket=$SOCK_DIR/nbd" 2>&1 \
| _filter_qemu_io | _filter_nbd
_send_qemu_cmd $QEMU_HANDLE \
diff --git a/tests/qemu-iotests/143.out b/tests/qemu-iotests/143.out
index ee71b5aa42..037d34a409 100644
--- a/tests/qemu-iotests/143.out
+++ b/tests/qemu-iotests/143.out
@@ -1,7 +1,7 @@
QA output created by 143
{"return": {}}
{"return": {}}
-qemu-io: can't open device nbd+unix:///no_such_export?socket=TEST_DIR/nbd: Requested export not available
+qemu-io: can't open device nbd+unix:///no_such_export?socket=SOCK_DIR/nbd: Requested export not available
server reported: export 'no_such_export' not present
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
diff --git a/tests/qemu-iotests/147 b/tests/qemu-iotests/147
index ab8480b9a4..03fc2fabcf 100755
--- a/tests/qemu-iotests/147
+++ b/tests/qemu-iotests/147
@@ -32,7 +32,7 @@ NBD_IPV6_PORT_START = NBD_PORT_END
NBD_IPV6_PORT_END = NBD_IPV6_PORT_START + 1024
test_img = os.path.join(iotests.test_dir, 'test.img')
-unix_socket = os.path.join(iotests.test_dir, 'nbd.socket')
+unix_socket = os.path.join(iotests.sock_dir, 'nbd.socket')
def flatten_sock_addr(crumpled_address):
diff --git a/tests/qemu-iotests/181 b/tests/qemu-iotests/181
index e317e63422..378c2899d1 100755
--- a/tests/qemu-iotests/181
+++ b/tests/qemu-iotests/181
@@ -26,7 +26,7 @@ echo "QA output created by $seq"
status=1 # failure is the default!
-MIG_SOCKET="${TEST_DIR}/migrate"
+MIG_SOCKET="${SOCK_DIR}/migrate"
_cleanup()
{
diff --git a/tests/qemu-iotests/182 b/tests/qemu-iotests/182
index 7f494eb9bb..1ccb850055 100755
--- a/tests/qemu-iotests/182
+++ b/tests/qemu-iotests/182
@@ -31,7 +31,7 @@ _cleanup()
{
_cleanup_test_img
rm -f "$TEST_IMG.overlay"
- rm -f "$TEST_DIR/nbd.socket"
+ rm -f "$SOCK_DIR/nbd.socket"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -133,7 +133,7 @@ success_or_failure=y _send_qemu_cmd $QEMU_HANDLE \
'addr': {
'type': 'unix',
'data': {
- 'path': '$TEST_DIR/nbd.socket'
+ 'path': '$SOCK_DIR/nbd.socket'
} } } }" \
'return' \
'error'
diff --git a/tests/qemu-iotests/183 b/tests/qemu-iotests/183
index 04fb344d08..bced83fae0 100755
--- a/tests/qemu-iotests/183
+++ b/tests/qemu-iotests/183
@@ -26,7 +26,7 @@ echo "QA output created by $seq"
status=1 # failure is the default!
-MIG_SOCKET="${TEST_DIR}/migrate"
+MIG_SOCKET="${SOCK_DIR}/migrate"
_cleanup()
{
diff --git a/tests/qemu-iotests/192 b/tests/qemu-iotests/192
index 034432272f..d2ba55dd90 100755
--- a/tests/qemu-iotests/192
+++ b/tests/qemu-iotests/192
@@ -31,7 +31,7 @@ _cleanup()
{
_cleanup_qemu
_cleanup_test_img
- rm -f "$TEST_DIR/nbd"
+ rm -f "$SOCK_DIR/nbd"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -66,7 +66,7 @@ else
QEMU_COMM_TIMEOUT=1
fi
-_send_qemu_cmd $h "nbd_server_start unix:$TEST_DIR/nbd" "(qemu)"
+_send_qemu_cmd $h "nbd_server_start unix:$SOCK_DIR/nbd" "(qemu)"
_send_qemu_cmd $h "nbd_server_add -w drive0" "(qemu)"
_send_qemu_cmd $h "q" "(qemu)"
diff --git a/tests/qemu-iotests/192.out b/tests/qemu-iotests/192.out
index 1e0be4c4d7..b9429dbe36 100644
--- a/tests/qemu-iotests/192.out
+++ b/tests/qemu-iotests/192.out
@@ -1,7 +1,7 @@
QA output created by 192
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
QEMU X.Y.Z monitor - type 'help' for more information
-(qemu) nbd_server_start unix:TEST_DIR/nbd
+(qemu) nbd_server_start unix:SOCK_DIR/nbd
(qemu) nbd_server_add -w drive0
(qemu) q
*** done
diff --git a/tests/qemu-iotests/194 b/tests/qemu-iotests/194
index d746ab1e21..72e47e8833 100755
--- a/tests/qemu-iotests/194
+++ b/tests/qemu-iotests/194
@@ -26,8 +26,8 @@ iotests.verify_platform(['linux'])
with iotests.FilePath('source.img') as source_img_path, \
iotests.FilePath('dest.img') as dest_img_path, \
- iotests.FilePath('migration.sock') as migration_sock_path, \
- iotests.FilePath('nbd.sock') as nbd_sock_path, \
+ iotests.FilePaths(['migration.sock', 'nbd.sock'], iotests.sock_dir) as \
+ [migration_sock_path, nbd_sock_path], \
iotests.VM('source') as source_vm, \
iotests.VM('dest') as dest_vm:
diff --git a/tests/qemu-iotests/201 b/tests/qemu-iotests/201
index 7abf740fe4..86fa37e714 100755
--- a/tests/qemu-iotests/201
+++ b/tests/qemu-iotests/201
@@ -24,7 +24,7 @@ echo "QA output created by $seq"
status=1 # failure is the default!
-MIG_SOCKET="${TEST_DIR}/migrate"
+MIG_SOCKET="${SOCK_DIR}/migrate"
# get standard environment, filters and checks
. ./common.rc
diff --git a/tests/qemu-iotests/205 b/tests/qemu-iotests/205
index 76f6c5fa2b..4bb2c21e8b 100755
--- a/tests/qemu-iotests/205
+++ b/tests/qemu-iotests/205
@@ -24,7 +24,7 @@ import iotests
import time
from iotests import qemu_img_create, qemu_io, filter_qemu_io, QemuIoInteractive
-nbd_sock = os.path.join(iotests.test_dir, 'nbd_sock')
+nbd_sock = os.path.join(iotests.sock_dir, 'nbd_sock')
nbd_uri = 'nbd+unix:///exp?socket=' + nbd_sock
disk = os.path.join(iotests.test_dir, 'disk')
diff --git a/tests/qemu-iotests/208 b/tests/qemu-iotests/208
index 1e202388dc..546eb1de3e 100755
--- a/tests/qemu-iotests/208
+++ b/tests/qemu-iotests/208
@@ -26,7 +26,7 @@ iotests.verify_image_format(supported_fmts=['generic'])
with iotests.FilePath('disk.img') as disk_img_path, \
iotests.FilePath('disk-snapshot.img') as disk_snapshot_img_path, \
- iotests.FilePath('nbd.sock') as nbd_sock_path, \
+ iotests.FilePath('nbd.sock', iotests.sock_dir) as nbd_sock_path, \
iotests.VM() as vm:
img_size = '10M'
diff --git a/tests/qemu-iotests/209 b/tests/qemu-iotests/209
index 259e991ec6..e0f464bcbe 100755
--- a/tests/qemu-iotests/209
+++ b/tests/qemu-iotests/209
@@ -24,7 +24,8 @@ from iotests import qemu_img_create, qemu_io, qemu_img_verbose, qemu_nbd, \
iotests.verify_image_format(supported_fmts=['qcow2'])
-disk, nbd_sock = file_path('disk', 'nbd-sock')
+disk = file_path('disk')
+nbd_sock = file_path('nbd-sock', base_dir=iotests.sock_dir)
nbd_uri = 'nbd+unix:///exp?socket=' + nbd_sock
qemu_img_create('-f', iotests.imgfmt, disk, '1M')
diff --git a/tests/qemu-iotests/222 b/tests/qemu-iotests/222
index 0ead56d574..3f9f934ad8 100644
--- a/tests/qemu-iotests/222
+++ b/tests/qemu-iotests/222
@@ -48,7 +48,7 @@ remainder = [("0xd5", "0x108000", "32k"), # Right-end of partial-left [1]
with iotests.FilePath('base.img') as base_img_path, \
iotests.FilePath('fleece.img') as fleece_img_path, \
- iotests.FilePath('nbd.sock') as nbd_sock_path, \
+ iotests.FilePath('nbd.sock', iotests.sock_dir) as nbd_sock_path, \
iotests.VM() as vm:
log('--- Setting up images ---')
diff --git a/tests/qemu-iotests/223 b/tests/qemu-iotests/223
index 2ba3d8124b..b5a80e50bb 100755
--- a/tests/qemu-iotests/223
+++ b/tests/qemu-iotests/223
@@ -28,7 +28,7 @@ _cleanup()
nbd_server_stop
_cleanup_test_img
_cleanup_qemu
- rm -f "$TEST_DIR/nbd"
+ rm -f "$SOCK_DIR/nbd"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -125,11 +125,11 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n"}}' "error" # Attempt add without server
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix",
- "data":{"path":"'"$TEST_DIR/nbd"'"}}}}' "return"
+ "data":{"path":"'"$SOCK_DIR/nbd"'"}}}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix",
- "data":{"path":"'"$TEST_DIR/nbd"1'"}}}}' "error" # Attempt second server
-$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd"
+ "data":{"path":"'"$SOCK_DIR/nbd"1'"}}}}' "error" # Attempt second server
+$QEMU_NBD_PROG -L -k "$SOCK_DIR/nbd"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "bitmap":"b"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
@@ -145,14 +145,14 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2", "writable":true,
"bitmap":"b2"}}' "return"
-$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd"
+$QEMU_NBD_PROG -L -k "$SOCK_DIR/nbd"
echo
echo "=== Contrast normal status to large granularity dirty-bitmap ==="
echo
QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
-IMG="driver=nbd,export=n,server.type=unix,server.path=$TEST_DIR/nbd"
+IMG="driver=nbd,export=n,server.type=unix,server.path=$SOCK_DIR/nbd"
$QEMU_IO -r -c 'r -P 0x22 512 512' -c 'r -P 0 512k 512k' -c 'r -P 0x11 1m 1m' \
-c 'r -P 0x33 2m 2m' --image-opts "$IMG" | _filter_qemu_io
$QEMU_IMG map --output=json --image-opts \
@@ -164,7 +164,7 @@ echo
echo "=== Contrast to small granularity dirty-bitmap ==="
echo
-IMG="driver=nbd,export=n2,server.type=unix,server.path=$TEST_DIR/nbd"
+IMG="driver=nbd,export=n2,server.type=unix,server.path=$SOCK_DIR/nbd"
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map
diff --git a/tests/qemu-iotests/240 b/tests/qemu-iotests/240
index f73bc07d80..8b4337b58d 100755
--- a/tests/qemu-iotests/240
+++ b/tests/qemu-iotests/240
@@ -29,7 +29,7 @@ status=1 # failure is the default!
_cleanup()
{
- rm -f "$TEST_DIR/nbd"
+ rm -f "$SOCK_DIR/nbd"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -135,7 +135,7 @@ echo
run_qemu <<EOF
{ "execute": "qmp_capabilities" }
{ "execute": "blockdev-add", "arguments": {"driver": "null-co", "read-zeroes": true, "node-name": "hd0", "read-only": true}}
-{ "execute": "nbd-server-start", "arguments": {"addr":{"type":"unix","data":{"path":"$TEST_DIR/nbd"}}}}
+{ "execute": "nbd-server-start", "arguments": {"addr":{"type":"unix","data":{"path":"$SOCK_DIR/nbd"}}}}
{ "execute": "nbd-server-add", "arguments": {"device":"hd0"}}
{ "execute": "object-add", "arguments": {"qom-type": "iothread", "id": "iothread0"}}
{ "execute": "device_add", "arguments": {"id": "scsi0", "driver": "${virtio_scsi}", "iothread": "iothread0"}}
diff --git a/tests/qemu-iotests/241 b/tests/qemu-iotests/241
index 58b64ebf41..8dae8d39e4 100755
--- a/tests/qemu-iotests/241
+++ b/tests/qemu-iotests/241
@@ -23,8 +23,6 @@ echo "QA output created by $seq"
status=1 # failure is the default!
-nbd_unix_socket=$TEST_DIR/test_qemu_nbd_socket
-
_cleanup()
{
_cleanup_test_img
diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245
index 41218d5f1d..e66a23c5f0 100644
--- a/tests/qemu-iotests/245
+++ b/tests/qemu-iotests/245
@@ -598,7 +598,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
##################
###### null ######
##################
- opts = {'driver': 'null-aio', 'node-name': 'root', 'size': 1024}
+ opts = {'driver': 'null-co', 'node-name': 'root', 'size': 1024}
result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
self.assert_qmp(result, 'return', {})
diff --git a/tests/qemu-iotests/261 b/tests/qemu-iotests/261
new file mode 100755
index 0000000000..fb96bcfbe2
--- /dev/null
+++ b/tests/qemu-iotests/261
@@ -0,0 +1,523 @@
+#!/usr/bin/env bash
+#
+# Test case for qcow2's handling of extra data in snapshot table entries
+# (and more generally, how certain cases of broken snapshot tables are
+# handled)
+#
+# Copyright (C) 2019 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/>.
+#
+
+# creator
+owner=mreitz@redhat.com
+
+seq=$(basename $0)
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_test_img
+ rm -f "$TEST_IMG".v{2,3}.orig
+ rm -f "$TEST_DIR"/sn{0,1,2}{,-pre,-extra,-post}
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+# This tests qocw2-specific low-level functionality
+_supported_fmt qcow2
+_supported_proto file
+_supported_os Linux
+# (1) We create a v2 image that supports nothing but refcount_bits=16
+# (2) We do some refcount management on our own which expects
+# refcount_bits=16
+_unsupported_imgopts 'refcount_bits=\([^1]\|.\([^6]\|$\)\)'
+
+# Parameters:
+# $1: image filename
+# $2: snapshot table entry offset in the image
+snapshot_table_entry_size()
+{
+ id_len=$(peek_file_be "$1" $(($2 + 12)) 2)
+ name_len=$(peek_file_be "$1" $(($2 + 14)) 2)
+ extra_len=$(peek_file_be "$1" $(($2 + 36)) 4)
+
+ full_len=$((40 + extra_len + id_len + name_len))
+ echo $(((full_len + 7) / 8 * 8))
+}
+
+# Parameter:
+# $1: image filename
+print_snapshot_table()
+{
+ nb_entries=$(peek_file_be "$1" 60 4)
+ offset=$(peek_file_be "$1" 64 8)
+
+ echo "Snapshots in $1:" | _filter_testdir | _filter_imgfmt
+
+ for ((i = 0; i < nb_entries; i++)); do
+ id_len=$(peek_file_be "$1" $((offset + 12)) 2)
+ name_len=$(peek_file_be "$1" $((offset + 14)) 2)
+ extra_len=$(peek_file_be "$1" $((offset + 36)) 4)
+
+ extra_ofs=$((offset + 40))
+ id_ofs=$((extra_ofs + extra_len))
+ name_ofs=$((id_ofs + id_len))
+
+ echo " [$i]"
+ echo " ID: $(peek_file_raw "$1" $id_ofs $id_len)"
+ echo " Name: $(peek_file_raw "$1" $name_ofs $name_len)"
+ echo " Extra data size: $extra_len"
+ if [ $extra_len -ge 8 ]; then
+ echo " VM state size: $(peek_file_be "$1" $extra_ofs 8)"
+ fi
+ if [ $extra_len -ge 16 ]; then
+ echo " Disk size: $(peek_file_be "$1" $((extra_ofs + 8)) 8)"
+ fi
+ if [ $extra_len -gt 16 ]; then
+ echo ' Unknown extra data:' \
+ "$(peek_file_raw "$1" $((extra_ofs + 16)) $((extra_len - 16)) \
+ | tr -d '\0')"
+ fi
+
+ offset=$((offset + $(snapshot_table_entry_size "$1" $offset)))
+ done
+}
+
+# Mark clusters as allocated; works only in refblock 0 (i.e. before
+# cluster #32768).
+# Parameters:
+# $1: Start offset of what to allocate
+# $2: End offset (exclusive)
+refblock0_allocate()
+{
+ reftable_ofs=$(peek_file_be "$TEST_IMG" 48 8)
+ refblock_ofs=$(peek_file_be "$TEST_IMG" $reftable_ofs 8)
+
+ cluster=$(($1 / 65536))
+ ecluster=$((($2 + 65535) / 65536))
+
+ while [ $cluster -lt $ecluster ]; do
+ if [ $cluster -ge 32768 ]; then
+ echo "*** Abort: Cluster $cluster exceeds refblock 0 ***"
+ exit 1
+ fi
+ poke_file "$TEST_IMG" $((refblock_ofs + cluster * 2)) '\x00\x01'
+ cluster=$((cluster + 1))
+ done
+}
+
+
+echo
+echo '=== Create v2 template ==='
+echo
+
+# Create v2 image with a snapshot table with three entries:
+# [0]: No extra data (valid with v2, not valid with v3)
+# [1]: Has extra data unknown to qemu
+# [2]: Has the 64-bit VM state size, but not the disk size (again,
+# valid with v2, not valid with v3)
+
+TEST_IMG="$TEST_IMG.v2.orig" IMGOPTS='compat=0.10' _make_test_img 64M
+$QEMU_IMG snapshot -c sn0 "$TEST_IMG.v2.orig"
+$QEMU_IMG snapshot -c sn1 "$TEST_IMG.v2.orig"
+$QEMU_IMG snapshot -c sn2 "$TEST_IMG.v2.orig"
+
+# Copy out all existing snapshot table entries
+sn_table_ofs=$(peek_file_be "$TEST_IMG.v2.orig" 64 8)
+
+# ofs: Snapshot table entry offset
+# eds: Extra data size
+# ids: Name + ID size
+# len: Total entry length
+sn0_ofs=$sn_table_ofs
+sn0_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 36)) 4)
+sn0_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 12)) 2) +
+ $(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 14)) 2)))
+sn0_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn0_ofs)
+sn1_ofs=$((sn0_ofs + sn0_len))
+sn1_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 36)) 4)
+sn1_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 12)) 2) +
+ $(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 14)) 2)))
+sn1_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn1_ofs)
+sn2_ofs=$((sn1_ofs + sn1_len))
+sn2_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 36)) 4)
+sn2_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 12)) 2) +
+ $(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 14)) 2)))
+sn2_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn2_ofs)
+
+# Data before extra data
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-pre" bs=1 skip=$sn0_ofs count=40 \
+ &> /dev/null
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-pre" bs=1 skip=$sn1_ofs count=40 \
+ &> /dev/null
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-pre" bs=1 skip=$sn2_ofs count=40 \
+ &> /dev/null
+
+# Extra data
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-extra" bs=1 \
+ skip=$((sn0_ofs + 40)) count=$sn0_eds &> /dev/null
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-extra" bs=1 \
+ skip=$((sn1_ofs + 40)) count=$sn1_eds &> /dev/null
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-extra" bs=1 \
+ skip=$((sn2_ofs + 40)) count=$sn2_eds &> /dev/null
+
+# Data after extra data
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-post" bs=1 \
+ skip=$((sn0_ofs + 40 + sn0_eds)) count=$sn0_ids \
+ &> /dev/null
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-post" bs=1 \
+ skip=$((sn1_ofs + 40 + sn1_eds)) count=$sn1_ids \
+ &> /dev/null
+dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-post" bs=1 \
+ skip=$((sn2_ofs + 40 + sn2_eds)) count=$sn2_ids \
+ &> /dev/null
+
+# Amend them, one by one
+# Set sn0's extra data size to 0
+poke_file "$TEST_DIR/sn0-pre" 36 '\x00\x00\x00\x00'
+truncate -s 0 "$TEST_DIR/sn0-extra"
+# Grow sn0-post to pad
+truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn0-pre") - 40)) \
+ "$TEST_DIR/sn0-post"
+
+# Set sn1's extra data size to 42
+poke_file "$TEST_DIR/sn1-pre" 36 '\x00\x00\x00\x2a'
+truncate -s 42 "$TEST_DIR/sn1-extra"
+poke_file "$TEST_DIR/sn1-extra" 16 'very important data'
+# Grow sn1-post to pad
+truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn1-pre") - 82)) \
+ "$TEST_DIR/sn1-post"
+
+# Set sn2's extra data size to 8
+poke_file "$TEST_DIR/sn2-pre" 36 '\x00\x00\x00\x08'
+truncate -s 8 "$TEST_DIR/sn2-extra"
+# Grow sn2-post to pad
+truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn2-pre") - 48)) \
+ "$TEST_DIR/sn2-post"
+
+# Construct snapshot table
+cat "$TEST_DIR"/sn0-{pre,extra,post} \
+ "$TEST_DIR"/sn1-{pre,extra,post} \
+ "$TEST_DIR"/sn2-{pre,extra,post} \
+ | dd of="$TEST_IMG.v2.orig" bs=1 seek=$sn_table_ofs conv=notrunc \
+ &> /dev/null
+
+# Done!
+TEST_IMG="$TEST_IMG.v2.orig" _check_test_img
+print_snapshot_table "$TEST_IMG.v2.orig"
+
+echo
+echo '=== Upgrade to v3 ==='
+echo
+
+cp "$TEST_IMG.v2.orig" "$TEST_IMG.v3.orig"
+$QEMU_IMG amend -o compat=1.1 "$TEST_IMG.v3.orig"
+TEST_IMG="$TEST_IMG.v3.orig" _check_test_img
+print_snapshot_table "$TEST_IMG.v3.orig"
+
+echo
+echo '=== Repair botched v3 ==='
+echo
+
+# Force the v2 file to be v3. v3 requires each snapshot table entry
+# to have at least 16 bytes of extra data, so it will not comply to
+# the qcow2 v3 specification; but we can fix that.
+cp "$TEST_IMG.v2.orig" "$TEST_IMG"
+
+# Set version
+poke_file "$TEST_IMG" 4 '\x00\x00\x00\x03'
+# Increase header length (necessary for v3)
+poke_file "$TEST_IMG" 100 '\x00\x00\x00\x68'
+# Set refcount order (necessary for v3)
+poke_file "$TEST_IMG" 96 '\x00\x00\x00\x04'
+
+_check_test_img -r all
+print_snapshot_table "$TEST_IMG"
+
+
+# From now on, just test the qcow2 version we are supposed to test.
+# (v3 by default, v2 by choice through $IMGOPTS.)
+# That works because we always write all known extra data when
+# updating the snapshot table, independent of the version.
+
+if echo "$IMGOPTS" | grep -q 'compat=\(0\.10\|v2\)' 2> /dev/null; then
+ subver=v2
+else
+ subver=v3
+fi
+
+echo
+echo '=== Add new snapshot ==='
+echo
+
+cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
+$QEMU_IMG snapshot -c sn3 "$TEST_IMG"
+_check_test_img
+print_snapshot_table "$TEST_IMG"
+
+echo
+echo '=== Remove different snapshots ==='
+
+for sn in sn0 sn1 sn2; do
+ echo
+ echo "--- $sn ---"
+
+ cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
+ $QEMU_IMG snapshot -d $sn "$TEST_IMG"
+ _check_test_img
+ print_snapshot_table "$TEST_IMG"
+done
+
+echo
+echo '=== Reject too much unknown extra data ==='
+echo
+
+cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
+$QEMU_IMG snapshot -c sn3 "$TEST_IMG"
+
+sn_table_ofs=$(peek_file_be "$TEST_IMG" 64 8)
+sn0_ofs=$sn_table_ofs
+sn1_ofs=$((sn0_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn0_ofs)))
+sn2_ofs=$((sn1_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn1_ofs)))
+sn3_ofs=$((sn2_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn2_ofs)))
+
+# 64 kB of extra data should be rejected
+# (Note that this also induces a refcount error, because it spills
+# over to the next cluster. That's a good way to test that we can
+# handle simultaneous snapshot table and refcount errors.)
+poke_file "$TEST_IMG" $((sn3_ofs + 36)) '\x00\x01\x00\x00'
+
+# Print error
+_img_info
+echo
+_check_test_img
+echo
+
+# Should be repairable
+_check_test_img -r all
+
+echo
+echo '=== Snapshot table too big ==='
+echo
+
+sn_table_ofs=$(peek_file_be "$TEST_IMG.v3.orig" 64 8)
+
+# Fill a snapshot with 1 kB of extra data, a 65535-char ID, and a
+# 65535-char name, and repeat it as many times as necessary to fill
+# 64 MB (the maximum supported by qemu)
+
+touch "$TEST_DIR/sn0"
+
+# Full size (fixed + extra + ID + name + padding)
+sn_size=$((40 + 1024 + 65535 + 65535 + 2))
+
+# We only need the fixed part, though.
+truncate -s 40 "$TEST_DIR/sn0"
+
+# 65535-char ID string
+poke_file "$TEST_DIR/sn0" 12 '\xff\xff'
+# 65535-char name
+poke_file "$TEST_DIR/sn0" 14 '\xff\xff'
+# 1 kB of extra data
+poke_file "$TEST_DIR/sn0" 36 '\x00\x00\x04\x00'
+
+# Create test image
+_make_test_img 64M
+
+# Hook up snapshot table somewhere safe (at 1 MB)
+poke_file "$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
+
+offset=1048576
+size_written=0
+sn_count=0
+while [ $size_written -le $((64 * 1048576)) ]; do
+ dd if="$TEST_DIR/sn0" of="$TEST_IMG" bs=1 seek=$offset conv=notrunc \
+ &> /dev/null
+ offset=$((offset + sn_size))
+ size_written=$((size_written + sn_size))
+ sn_count=$((sn_count + 1))
+done
+truncate -s "$offset" "$TEST_IMG"
+
+# Give the last snapshot (the one to be removed) an L1 table so we can
+# see how that is handled when repairing the image
+# (Put it two clusters before 1 MB, and one L2 table one cluster
+# before 1 MB)
+poke_file "$TEST_IMG" $((offset - sn_size + 0)) \
+ '\x00\x00\x00\x00\x00\x0e\x00\x00'
+poke_file "$TEST_IMG" $((offset - sn_size + 8)) \
+ '\x00\x00\x00\x01'
+
+# Hook up the L2 table
+poke_file "$TEST_IMG" $((1048576 - 2 * 65536)) \
+ '\x80\x00\x00\x00\x00\x0f\x00\x00'
+
+# Make sure all of the clusters we just hooked up are allocated:
+# - The snapshot table
+# - The last snapshot's L1 and L2 table
+refblock0_allocate $((1048576 - 2 * 65536)) $offset
+
+poke_file "$TEST_IMG" 60 \
+ "$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
+
+# Print error
+_img_info
+echo
+_check_test_img
+echo
+
+# Should be repairable
+_check_test_img -r all
+
+echo
+echo "$((sn_count - 1)) snapshots should remain:"
+echo " qemu-img info reports $(_img_info | grep -c '^ \{34\}') snapshots"
+echo " Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"
+
+echo
+echo '=== Snapshot table too big with one entry with too much extra data ==='
+echo
+
+# For this test, we reuse the image from the previous case, which has
+# a snapshot table that is right at the limit.
+# Our layout looks like this:
+# - (a number of snapshot table entries)
+# - One snapshot with $extra_data_size extra data
+# - One normal snapshot that breaks the 64 MB boundary
+# - One normal snapshot beyond the 64 MB boundary
+#
+# $extra_data_size is calculated so that simply by virtue of it
+# decreasing to 1 kB, the penultimate snapshot will fit into 64 MB
+# limit again. The final snapshot will always be beyond the limit, so
+# that we can see that the repair algorithm does still determine the
+# limit to be somewhere, even when truncating one snapshot's extra
+# data.
+
+# The last case has removed the last snapshot, so calculate
+# $old_offset to get the current image's real length
+old_offset=$((offset - sn_size))
+
+# The layout from the previous test had one snapshot beyond the 64 MB
+# limit; we want the same (after the oversized extra data has been
+# truncated to 1 kB), so we drop the last three snapshots and
+# construct them from scratch.
+offset=$((offset - 3 * sn_size))
+sn_count=$((sn_count - 3))
+
+# Assuming we had already written one of the three snapshots
+# (necessary so we can calculate $extra_data_size next).
+size_written=$((size_written - 2 * sn_size))
+
+# Increase the extra data size so we go past the limit
+# (The -1024 comes from the 1 kB of extra data we already have)
+extra_data_size=$((64 * 1048576 + 8 - sn_size - (size_written - 1024)))
+
+poke_file "$TEST_IMG" $((offset + 36)) \
+ "$(printf '%08x' $extra_data_size | sed -e 's/\(..\)/\\x\1/g')"
+
+offset=$((offset + sn_size - 1024 + extra_data_size))
+size_written=$((size_written - 1024 + extra_data_size))
+sn_count=$((sn_count + 1))
+
+# Write the two normal snapshots
+for ((i = 0; i < 2; i++)); do
+ dd if="$TEST_DIR/sn0" of="$TEST_IMG" bs=1 seek=$offset conv=notrunc \
+ &> /dev/null
+ offset=$((offset + sn_size))
+ size_written=$((size_written + sn_size))
+ sn_count=$((sn_count + 1))
+
+ if [ $i = 0 ]; then
+ # Check that the penultimate snapshot is beyond the 64 MB limit
+ echo "Snapshot table size should equal $((64 * 1048576 + 8)):" \
+ $size_written
+ echo
+ fi
+done
+
+truncate -s $offset "$TEST_IMG"
+refblock0_allocate $old_offset $offset
+
+poke_file "$TEST_IMG" 60 \
+ "$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
+
+# Print error
+_img_info
+echo
+_check_test_img
+echo
+
+# Just truncating the extra data should be sufficient to shorten the
+# snapshot table so only one snapshot exceeds the extra size
+_check_test_img -r all
+
+echo
+echo '=== Too many snapshots ==='
+echo
+
+# Create a v2 image, for speeds' sake: All-zero snapshot table entries
+# are only valid in v2.
+IMGOPTS='compat=0.10' _make_test_img 64M
+
+# Hook up snapshot table somewhere safe (at 1 MB)
+poke_file "$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
+# "Create" more than 65536 snapshots (twice that many here)
+poke_file "$TEST_IMG" 60 '\x00\x02\x00\x00'
+
+# 40-byte all-zero snapshot table entries are valid snapshots, but
+# only in v2 (v3 needs 16 bytes of extra data, so we would have to
+# write 131072x '\x10').
+truncate -s $((1048576 + 40 * 131072)) "$TEST_IMG"
+
+# But let us give one of the snapshots to be removed an L1 table so
+# we can see how that is handled when repairing the image.
+# (Put it two clusters before 1 MB, and one L2 table one cluster
+# before 1 MB)
+poke_file "$TEST_IMG" $((1048576 + 40 * 65536 + 0)) \
+ '\x00\x00\x00\x00\x00\x0e\x00\x00'
+poke_file "$TEST_IMG" $((1048576 + 40 * 65536 + 8)) \
+ '\x00\x00\x00\x01'
+
+# Hook up the L2 table
+poke_file "$TEST_IMG" $((1048576 - 2 * 65536)) \
+ '\x80\x00\x00\x00\x00\x0f\x00\x00'
+
+# Make sure all of the clusters we just hooked up are allocated:
+# - The snapshot table
+# - The last snapshot's L1 and L2 table
+refblock0_allocate $((1048576 - 2 * 65536)) $((1048576 + 40 * 131072))
+
+# Print error
+_img_info
+echo
+_check_test_img
+echo
+
+# Should be repairable
+_check_test_img -r all
+
+echo
+echo '65536 snapshots should remain:'
+echo " qemu-img info reports $(_img_info | grep -c '^ \{34\}') snapshots"
+echo " Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"
+
+# success, all done
+echo "*** done"
+status=0
diff --git a/tests/qemu-iotests/261.out b/tests/qemu-iotests/261.out
new file mode 100644
index 0000000000..2600354566
--- /dev/null
+++ b/tests/qemu-iotests/261.out
@@ -0,0 +1,346 @@
+QA output created by 261
+
+=== Create v2 template ===
+
+Formatting 'TEST_DIR/t.IMGFMT.v2.orig', fmt=IMGFMT size=67108864
+No errors were found on the image.
+Snapshots in TEST_DIR/t.IMGFMT.v2.orig:
+ [0]
+ ID: 1
+ Name: sn0
+ Extra data size: 0
+ [1]
+ ID: 2
+ Name: sn1
+ Extra data size: 42
+ VM state size: 0
+ Disk size: 67108864
+ Unknown extra data: very important data
+ [2]
+ ID: 3
+ Name: sn2
+ Extra data size: 8
+ VM state size: 0
+
+=== Upgrade to v3 ===
+
+No errors were found on the image.
+Snapshots in TEST_DIR/t.IMGFMT.v3.orig:
+ [0]
+ ID: 1
+ Name: sn0
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+ [1]
+ ID: 2
+ Name: sn1
+ Extra data size: 42
+ VM state size: 0
+ Disk size: 67108864
+ Unknown extra data: very important data
+ [2]
+ ID: 3
+ Name: sn2
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+
+=== Repair botched v3 ===
+
+Repairing snapshot table entry 0 is incomplete
+Repairing snapshot table entry 2 is incomplete
+The following inconsistencies were found and repaired:
+
+ 0 leaked clusters
+ 2 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+Snapshots in TEST_DIR/t.IMGFMT:
+ [0]
+ ID: 1
+ Name: sn0
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+ [1]
+ ID: 2
+ Name: sn1
+ Extra data size: 42
+ VM state size: 0
+ Disk size: 67108864
+ Unknown extra data: very important data
+ [2]
+ ID: 3
+ Name: sn2
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+
+=== Add new snapshot ===
+
+No errors were found on the image.
+Snapshots in TEST_DIR/t.IMGFMT:
+ [0]
+ ID: 1
+ Name: sn0
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+ [1]
+ ID: 2
+ Name: sn1
+ Extra data size: 42
+ VM state size: 0
+ Disk size: 67108864
+ Unknown extra data: very important data
+ [2]
+ ID: 3
+ Name: sn2
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+ [3]
+ ID: 4
+ Name: sn3
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+
+=== Remove different snapshots ===
+
+--- sn0 ---
+No errors were found on the image.
+Snapshots in TEST_DIR/t.IMGFMT:
+ [0]
+ ID: 2
+ Name: sn1
+ Extra data size: 42
+ VM state size: 0
+ Disk size: 67108864
+ Unknown extra data: very important data
+ [1]
+ ID: 3
+ Name: sn2
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+
+--- sn1 ---
+No errors were found on the image.
+Snapshots in TEST_DIR/t.IMGFMT:
+ [0]
+ ID: 1
+ Name: sn0
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+ [1]
+ ID: 3
+ Name: sn2
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+
+--- sn2 ---
+No errors were found on the image.
+Snapshots in TEST_DIR/t.IMGFMT:
+ [0]
+ ID: 1
+ Name: sn0
+ Extra data size: 16
+ VM state size: 0
+ Disk size: 67108864
+ [1]
+ ID: 2
+ Name: sn1
+ Extra data size: 42
+ VM state size: 0
+ Disk size: 67108864
+ Unknown extra data: very important data
+
+=== Reject too much unknown extra data ===
+
+qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Too much extra metadata in snapshot table entry 3
+You can force-remove this extra metadata with qemu-img check -r all
+
+qemu-img: ERROR failed to read the snapshot table: Too much extra metadata in snapshot table entry 3
+You can force-remove this extra metadata with qemu-img check -r all
+qemu-img: Check failed: File too large
+
+Discarding too much extra metadata in snapshot table entry 3 (65536 > 1024)
+ERROR cluster 10 refcount=0 reference=1
+Rebuilding refcount structure
+Repairing cluster 1 refcount=1 reference=0
+Repairing cluster 2 refcount=1 reference=0
+The following inconsistencies were found and repaired:
+
+ 0 leaked clusters
+ 2 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+
+=== Snapshot table too big ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Snapshot table is too big
+You can force-remove all 1 overhanging snapshots with qemu-img check -r all
+
+qemu-img: ERROR failed to read the snapshot table: Snapshot table is too big
+You can force-remove all 1 overhanging snapshots with qemu-img check -r all
+qemu-img: Check failed: File too large
+
+Discarding 1 overhanging snapshots (snapshot table is too big)
+Leaked cluster 14 refcount=1 reference=0
+Leaked cluster 15 refcount=1 reference=0
+Leaked cluster 1039 refcount=1 reference=0
+Leaked cluster 1040 refcount=1 reference=0
+Repairing cluster 14 refcount=1 reference=0
+Repairing cluster 15 refcount=1 reference=0
+Repairing cluster 1039 refcount=1 reference=0
+Repairing cluster 1040 refcount=1 reference=0
+The following inconsistencies were found and repaired:
+
+ 4 leaked clusters
+ 1 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+
+507 snapshots should remain:
+ qemu-img info reports 507 snapshots
+ Image header reports 507 snapshots
+
+=== Snapshot table too big with one entry with too much extra data ===
+
+Snapshot table size should equal 67108872: 67108872
+
+qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Too much extra metadata in snapshot table entry 505
+You can force-remove this extra metadata with qemu-img check -r all
+
+qemu-img: ERROR failed to read the snapshot table: Too much extra metadata in snapshot table entry 505
+You can force-remove this extra metadata with qemu-img check -r all
+qemu-img: Check failed: File too large
+
+Discarding too much extra metadata in snapshot table entry 505 (116944 > 1024)
+Discarding 1 overhanging snapshots (snapshot table is too big)
+Leaked cluster 1041 refcount=1 reference=0
+Leaked cluster 1042 refcount=1 reference=0
+Repairing cluster 1041 refcount=1 reference=0
+Repairing cluster 1042 refcount=1 reference=0
+The following inconsistencies were found and repaired:
+
+ 2 leaked clusters
+ 2 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+
+=== Too many snapshots ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Snapshot table too large
+
+qemu-img: ERROR snapshot table too large
+You can force-remove all 65536 overhanging snapshots with qemu-img check -r all
+qemu-img: Check failed: File too large
+
+Discarding 65536 overhanging snapshots
+Leaked cluster 14 refcount=1 reference=0
+Leaked cluster 15 refcount=1 reference=0
+Leaked cluster 56 refcount=1 reference=0
+Leaked cluster 57 refcount=1 reference=0
+Leaked cluster 58 refcount=1 reference=0
+Leaked cluster 59 refcount=1 reference=0
+Leaked cluster 60 refcount=1 reference=0
+Leaked cluster 61 refcount=1 reference=0
+Leaked cluster 62 refcount=1 reference=0
+Leaked cluster 63 refcount=1 reference=0
+Leaked cluster 64 refcount=1 reference=0
+Leaked cluster 65 refcount=1 reference=0
+Leaked cluster 66 refcount=1 reference=0
+Leaked cluster 67 refcount=1 reference=0
+Leaked cluster 68 refcount=1 reference=0
+Leaked cluster 69 refcount=1 reference=0
+Leaked cluster 70 refcount=1 reference=0
+Leaked cluster 71 refcount=1 reference=0
+Leaked cluster 72 refcount=1 reference=0
+Leaked cluster 73 refcount=1 reference=0
+Leaked cluster 74 refcount=1 reference=0
+Leaked cluster 75 refcount=1 reference=0
+Leaked cluster 76 refcount=1 reference=0
+Leaked cluster 77 refcount=1 reference=0
+Leaked cluster 78 refcount=1 reference=0
+Leaked cluster 79 refcount=1 reference=0
+Leaked cluster 80 refcount=1 reference=0
+Leaked cluster 81 refcount=1 reference=0
+Leaked cluster 82 refcount=1 reference=0
+Leaked cluster 83 refcount=1 reference=0
+Leaked cluster 84 refcount=1 reference=0
+Leaked cluster 85 refcount=1 reference=0
+Leaked cluster 86 refcount=1 reference=0
+Leaked cluster 87 refcount=1 reference=0
+Leaked cluster 88 refcount=1 reference=0
+Leaked cluster 89 refcount=1 reference=0
+Leaked cluster 90 refcount=1 reference=0
+Leaked cluster 91 refcount=1 reference=0
+Leaked cluster 92 refcount=1 reference=0
+Leaked cluster 93 refcount=1 reference=0
+Leaked cluster 94 refcount=1 reference=0
+Leaked cluster 95 refcount=1 reference=0
+Repairing cluster 14 refcount=1 reference=0
+Repairing cluster 15 refcount=1 reference=0
+Repairing cluster 56 refcount=1 reference=0
+Repairing cluster 57 refcount=1 reference=0
+Repairing cluster 58 refcount=1 reference=0
+Repairing cluster 59 refcount=1 reference=0
+Repairing cluster 60 refcount=1 reference=0
+Repairing cluster 61 refcount=1 reference=0
+Repairing cluster 62 refcount=1 reference=0
+Repairing cluster 63 refcount=1 reference=0
+Repairing cluster 64 refcount=1 reference=0
+Repairing cluster 65 refcount=1 reference=0
+Repairing cluster 66 refcount=1 reference=0
+Repairing cluster 67 refcount=1 reference=0
+Repairing cluster 68 refcount=1 reference=0
+Repairing cluster 69 refcount=1 reference=0
+Repairing cluster 70 refcount=1 reference=0
+Repairing cluster 71 refcount=1 reference=0
+Repairing cluster 72 refcount=1 reference=0
+Repairing cluster 73 refcount=1 reference=0
+Repairing cluster 74 refcount=1 reference=0
+Repairing cluster 75 refcount=1 reference=0
+Repairing cluster 76 refcount=1 reference=0
+Repairing cluster 77 refcount=1 reference=0
+Repairing cluster 78 refcount=1 reference=0
+Repairing cluster 79 refcount=1 reference=0
+Repairing cluster 80 refcount=1 reference=0
+Repairing cluster 81 refcount=1 reference=0
+Repairing cluster 82 refcount=1 reference=0
+Repairing cluster 83 refcount=1 reference=0
+Repairing cluster 84 refcount=1 reference=0
+Repairing cluster 85 refcount=1 reference=0
+Repairing cluster 86 refcount=1 reference=0
+Repairing cluster 87 refcount=1 reference=0
+Repairing cluster 88 refcount=1 reference=0
+Repairing cluster 89 refcount=1 reference=0
+Repairing cluster 90 refcount=1 reference=0
+Repairing cluster 91 refcount=1 reference=0
+Repairing cluster 92 refcount=1 reference=0
+Repairing cluster 93 refcount=1 reference=0
+Repairing cluster 94 refcount=1 reference=0
+Repairing cluster 95 refcount=1 reference=0
+The following inconsistencies were found and repaired:
+
+ 42 leaked clusters
+ 65536 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+
+65536 snapshots should remain:
+ qemu-img info reports 65536 snapshots
+ Image header reports 65536 snapshots
+*** done
diff --git a/tests/qemu-iotests/264 b/tests/qemu-iotests/264
index c8cd97ae2b..131366422b 100755
--- a/tests/qemu-iotests/264
+++ b/tests/qemu-iotests/264
@@ -24,6 +24,8 @@ import iotests
from iotests import qemu_img_create, qemu_io_silent_check, file_path, \
qemu_nbd_popen, log
+iotests.verify_image_format(supported_fmts=['qcow2'])
+
disk_a, disk_b, nbd_sock = file_path('disk_a', 'disk_b', 'nbd-sock')
nbd_uri = 'nbd+unix:///?socket=' + nbd_sock
size = 5 * 1024 * 1024
diff --git a/tests/qemu-iotests/267 b/tests/qemu-iotests/267
index d37a67c012..170e173c0a 100755
--- a/tests/qemu-iotests/267
+++ b/tests/qemu-iotests/267
@@ -29,7 +29,7 @@ status=1 # failure is the default!
_cleanup()
{
_cleanup_test_img
- rm -f "$TEST_DIR/nbd"
+ rm -f "$SOCK_DIR/nbd"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -143,7 +143,7 @@ echo
IMGOPTS="backing_file=$TEST_IMG.base" _make_test_img $size
cat <<EOF |
-nbd_server_start unix:$TEST_DIR/nbd
+nbd_server_start unix:$SOCK_DIR/nbd
nbd_server_add -w backing-fmt
savevm snap0
info snapshots
diff --git a/tests/qemu-iotests/267.out b/tests/qemu-iotests/267.out
index 9d812e3c72..8dddb4baa4 100644
--- a/tests/qemu-iotests/267.out
+++ b/tests/qemu-iotests/267.out
@@ -161,7 +161,7 @@ Internal snapshots on backing file:
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base
Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT.base,node-name=backing-file -blockdev driver=IMGFMT,file=backing-file,node-name=backing-fmt -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=file -blockdev driver=IMGFMT,file=file,backing=backing-fmt,node-name=fmt
QEMU X.Y.Z monitor - type 'help' for more information
-(qemu) nbd_server_start unix:TEST_DIR/nbd
+(qemu) nbd_server_start unix:SOCK_DIR/nbd
(qemu) nbd_server_add -w backing-fmt
(qemu) savevm snap0
(qemu) info snapshots
diff --git a/tests/qemu-iotests/check b/tests/qemu-iotests/check
index 588c453a94..71fe38834e 100755
--- a/tests/qemu-iotests/check
+++ b/tests/qemu-iotests/check
@@ -97,6 +97,7 @@ IMGFMT -- $FULL_IMGFMT_DETAILS
IMGPROTO -- $IMGPROTO
PLATFORM -- $FULL_HOST_DETAILS
TEST_DIR -- $TEST_DIR
+SOCK_DIR -- $SOCK_DIR
SOCKET_SCM_HELPER -- $SOCKET_SCM_HELPER
EOF
@@ -116,10 +117,14 @@ set_prog_path()
if [ -z "$TEST_DIR" ]; then
TEST_DIR=$PWD/scratch
fi
+mkdir -p "$TEST_DIR" || _init_error 'Failed to create TEST_DIR'
-if [ ! -e "$TEST_DIR" ]; then
- mkdir "$TEST_DIR"
+tmp_sock_dir=false
+if [ -z "$SOCK_DIR" ]; then
+ SOCK_DIR=$(mktemp -d)
+ tmp_sock_dir=true
fi
+mkdir -p "$SOCK_DIR" || _init_error 'Failed to create SOCK_DIR'
diff="diff -u"
verbose=false
@@ -534,6 +539,7 @@ if [ -z "$SAMPLE_IMG_DIR" ]; then
fi
export TEST_DIR
+export SOCK_DIR
export SAMPLE_IMG_DIR
if [ -s $tmp.list ]
@@ -716,6 +722,11 @@ END { if (NR > 0) {
rm -f "${TEST_DIR}"/*.out "${TEST_DIR}"/*.err "${TEST_DIR}"/*.time
rm -f "${TEST_DIR}"/check.pid "${TEST_DIR}"/check.sts
rm -f $tmp.*
+
+ if $tmp_sock_dir
+ then
+ rm -rf "$SOCK_DIR"
+ fi
}
trap "_wrapup; exit \$status" 0 1 2 3 15
diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter
index 9f418b4881..f870e00e44 100644
--- a/tests/qemu-iotests/common.filter
+++ b/tests/qemu-iotests/common.filter
@@ -43,7 +43,8 @@ _filter_qom_path()
# replace occurrences of the actual TEST_DIR value with TEST_DIR
_filter_testdir()
{
- $SED -e "s#$TEST_DIR/#TEST_DIR/#g"
+ $SED -e "s#$TEST_DIR/#TEST_DIR/#g" \
+ -e "s#$SOCK_DIR/#SOCK_DIR/#g"
}
# replace occurrences of the actual IMGFMT value with IMGFMT
@@ -124,6 +125,7 @@ _filter_img_create()
$SED -e "s#$REMOTE_TEST_DIR#TEST_DIR#g" \
-e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \
-e "s#$TEST_DIR#TEST_DIR#g" \
+ -e "s#$SOCK_DIR#SOCK_DIR#g" \
-e "s#$IMGFMT#IMGFMT#g" \
-e 's#nbd:127.0.0.1:10810#TEST_DIR/t.IMGFMT#g' \
-e "s# encryption=off##g" \
@@ -160,6 +162,7 @@ _filter_img_info()
$SED -e "s#$REMOTE_TEST_DIR#TEST_DIR#g" \
-e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \
-e "s#$TEST_DIR#TEST_DIR#g" \
+ -e "s#$SOCK_DIR#SOCK_DIR#g" \
-e "s#$IMGFMT#IMGFMT#g" \
-e 's#nbd://127.0.0.1:10810$#TEST_DIR/t.IMGFMT#g' \
-e 's#json.*vdisk-id.*vxhs"}}#TEST_DIR/t.IMGFMT#' \
@@ -218,7 +221,7 @@ _filter_nbd()
# Filter out the TCP port number since this changes between runs.
$SED -e '/nbd\/.*\.c:/d' \
-e 's#127\.0\.0\.1:[0-9]*#127.0.0.1:PORT#g' \
- -e "s#?socket=$TEST_DIR#?socket=TEST_DIR#g" \
+ -e "s#?socket=$SOCK_DIR#?socket=SOCK_DIR#g" \
-e 's#\(foo\|PORT/\?\|.sock\): Failed to .*$#\1#'
}
diff --git a/tests/qemu-iotests/common.nbd b/tests/qemu-iotests/common.nbd
index 24b01b60aa..a8cae8fe2c 100644
--- a/tests/qemu-iotests/common.nbd
+++ b/tests/qemu-iotests/common.nbd
@@ -19,7 +19,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-nbd_unix_socket="${TEST_DIR}/qemu-nbd.sock"
+nbd_unix_socket="${SOCK_DIR}/qemu-nbd.sock"
nbd_tcp_addr="127.0.0.1"
nbd_pid_file="${TEST_DIR}/qemu-nbd.pid"
nbd_stderr_fifo="${TEST_DIR}/qemu-nbd.fifo"
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index 12b4751848..fa7bae2422 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -53,6 +53,26 @@ poke_file()
printf "$3" | dd "of=$1" bs=1 "seek=$2" conv=notrunc &>/dev/null
}
+# peek_file_le 'test.img' 512 2 => 65534
+peek_file_le()
+{
+ # Wrap in echo $() to strip spaces
+ echo $(od -j"$2" -N"$3" --endian=little -An -vtu"$3" "$1")
+}
+
+# peek_file_be 'test.img' 512 2 => 65279
+peek_file_be()
+{
+ # Wrap in echo $() to strip spaces
+ echo $(od -j"$2" -N"$3" --endian=big -An -vtu"$3" "$1")
+}
+
+# peek_file_raw 'test.img' 512 2 => '\xff\xfe'
+peek_file_raw()
+{
+ dd if="$1" bs=1 skip="$2" count="$3" status=none
+}
+
if ! . ./common.config
then
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index af322af756..28871604cd 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -274,6 +274,7 @@
257 rw
258 rw quick
260 rw quick
+261 rw
262 rw quick migration
263 rw quick
264 rw
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 709def4d5d..075f4739da 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -57,6 +57,7 @@ qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
imgfmt = os.environ.get('IMGFMT', 'raw')
imgproto = os.environ.get('IMGPROTO', 'file')
test_dir = os.environ.get('TEST_DIR')
+sock_dir = os.environ.get('SOCK_DIR')
output_dir = os.environ.get('OUTPUT_DIR', '.')
cachemode = os.environ.get('CACHEMODE')
qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
@@ -385,10 +386,10 @@ class FilePaths(object):
qemu_img('create', img_path, '1G')
# migration_sock_path is automatically deleted
"""
- def __init__(self, names):
+ def __init__(self, names, base_dir=test_dir):
self.paths = []
for name in names:
- self.paths.append(os.path.join(test_dir, file_pattern(name)))
+ self.paths.append(os.path.join(base_dir, file_pattern(name)))
def __enter__(self):
return self.paths
@@ -405,8 +406,8 @@ class FilePath(FilePaths):
"""
FilePath is a specialization of FilePaths that takes a single filename.
"""
- def __init__(self, name):
- super(FilePath, self).__init__([name])
+ def __init__(self, name, base_dir=test_dir):
+ super(FilePath, self).__init__([name], base_dir)
def __enter__(self):
return self.paths[0]
@@ -419,7 +420,7 @@ def file_path_remover():
pass
-def file_path(*names):
+def file_path(*names, base_dir=test_dir):
''' Another way to get auto-generated filename that cleans itself up.
Use is as simple as:
@@ -435,7 +436,7 @@ def file_path(*names):
paths = []
for name in names:
filename = file_pattern(name)
- path = os.path.join(test_dir, filename)
+ path = os.path.join(base_dir, filename)
file_path_remover.paths.append(path)
paths.append(path)
@@ -456,7 +457,8 @@ class VM(qtest.QEMUQtestMachine):
name = "qemu%s-%d" % (path_suffix, os.getpid())
super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
test_dir=test_dir,
- socket_scm_helper=socket_scm_helper)
+ socket_scm_helper=socket_scm_helper,
+ sock_dir=sock_dir)
self._num_drives = 0
def add_object(self, opts):
@@ -838,6 +840,11 @@ class QMPTestCase(unittest.TestCase):
return self.pause_wait(job_id)
return result
+ def case_skip(self, reason):
+ '''Skip this test case'''
+ case_notrun(reason)
+ self.skipTest(reason)
+
def notrun(reason):
'''Skip this test suite'''
@@ -849,7 +856,11 @@ def notrun(reason):
sys.exit(0)
def case_notrun(reason):
- '''Skip this test case'''
+ '''Mark this test case as not having been run (without actually
+ skipping it, that is left to the caller). See
+ QMPTestCase.case_skip() for a variant that actually skips the
+ current test case.'''
+
# Each test in qemu-iotests has a number ("seq")
seq = os.path.basename(sys.argv[0])
@@ -912,22 +923,34 @@ def qemu_pipe(*args):
def supported_formats(read_only=False):
'''Set 'read_only' to True to check ro-whitelist
Otherwise, rw-whitelist is checked'''
- format_message = qemu_pipe("-drive", "format=help")
- line = 1 if read_only else 0
- return format_message.splitlines()[line].split(":")[1].split()
+
+ if not hasattr(supported_formats, "formats"):
+ supported_formats.formats = {}
+
+ if read_only not in supported_formats.formats:
+ format_message = qemu_pipe("-drive", "format=help")
+ line = 1 if read_only else 0
+ supported_formats.formats[read_only] = \
+ format_message.splitlines()[line].split(":")[1].split()
+
+ return supported_formats.formats[read_only]
def skip_if_unsupported(required_formats=[], read_only=False):
'''Skip Test Decorator
Runs the test if all the required formats are whitelisted'''
def skip_test_decorator(func):
- def func_wrapper(*args, **kwargs):
- usf_list = list(set(required_formats) -
- set(supported_formats(read_only)))
+ def func_wrapper(test_case: QMPTestCase, *args, **kwargs):
+ if callable(required_formats):
+ fmts = required_formats(test_case)
+ else:
+ fmts = required_formats
+
+ usf_list = list(set(fmts) - set(supported_formats(read_only)))
if usf_list:
- case_notrun('{}: formats {} are not whitelisted'.format(
- args[0], usf_list))
+ test_case.case_skip('{}: formats {} are not whitelisted'.format(
+ test_case, usf_list))
else:
- return func(*args, **kwargs)
+ return func(test_case, *args, **kwargs)
return func_wrapper
return skip_test_decorator
@@ -950,8 +973,15 @@ def execute_unittest(output, verbosity, debug):
unittest.main(testRunner=runner)
finally:
if not debug:
- sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s',
- r'Ran \1 tests', output.getvalue()))
+ out = output.getvalue()
+ out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out)
+
+ # Hide skipped tests from the reference output
+ out = re.sub(r'OK \(skipped=\d+\)', 'OK', out)
+ out_first_line, out_rest = out.split('\n', 1)
+ out = out_first_line.replace('s', '.') + '\n' + out_rest
+
+ sys.stderr.write(out)
def execute_test(test_function=None,
supported_fmts=[], supported_oses=['linux'],
diff --git a/tests/test-block-iothread.c b/tests/test-block-iothread.c
index cfe30bab21..0c861809f0 100644
--- a/tests/test-block-iothread.c
+++ b/tests/test-block-iothread.c
@@ -45,7 +45,7 @@ static int coroutine_fn bdrv_test_co_pdiscard(BlockDriverState *bs,
}
static int coroutine_fn
-bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset,
+bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp)
{
return 0;
@@ -185,18 +185,18 @@ static void test_sync_op_truncate(BdrvChild *c)
int ret;
/* Normal success path */
- ret = bdrv_truncate(c, 65536, PREALLOC_MODE_OFF, NULL);
+ ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, NULL);
g_assert_cmpint(ret, ==, 0);
/* Early error: Negative offset */
- ret = bdrv_truncate(c, -2, PREALLOC_MODE_OFF, NULL);
+ ret = bdrv_truncate(c, -2, false, PREALLOC_MODE_OFF, NULL);
g_assert_cmpint(ret, ==, -EINVAL);
/* Error: Read-only image */
c->bs->read_only = true;
c->bs->open_flags &= ~BDRV_O_RDWR;
- ret = bdrv_truncate(c, 65536, PREALLOC_MODE_OFF, NULL);
+ ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, NULL);
g_assert_cmpint(ret, ==, -EACCES);
c->bs->read_only = false;
diff --git a/util/Makefile.objs b/util/Makefile.objs
index 41bf59d127..df124af1c5 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -37,6 +37,7 @@ util-obj-y += rcu.o
util-obj-$(CONFIG_MEMBARRIER) += sys_membarrier.o
util-obj-y += qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o
util-obj-y += qemu-coroutine-sleep.o
+util-obj-y += qemu-co-shared-resource.o
util-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
util-obj-y += buffer.o
util-obj-y += timed-average.o
diff --git a/util/hbitmap.c b/util/hbitmap.c
index 66db87c6ff..242c6e519c 100644
--- a/util/hbitmap.c
+++ b/util/hbitmap.c
@@ -387,6 +387,10 @@ void hbitmap_set(HBitmap *hb, uint64_t start, uint64_t count)
uint64_t first, n;
uint64_t last = start + count - 1;
+ if (count == 0) {
+ return;
+ }
+
trace_hbitmap_set(hb, start, count,
start >> hb->granularity, last >> hb->granularity);
@@ -478,6 +482,10 @@ void hbitmap_reset(HBitmap *hb, uint64_t start, uint64_t count)
uint64_t last = start + count - 1;
uint64_t gran = 1ULL << hb->granularity;
+ if (count == 0) {
+ return;
+ }
+
assert(QEMU_IS_ALIGNED(start, gran));
assert(QEMU_IS_ALIGNED(count, gran) || (start + count == hb->orig_size));
diff --git a/util/qemu-co-shared-resource.c b/util/qemu-co-shared-resource.c
new file mode 100644
index 0000000000..1c83cd9d29
--- /dev/null
+++ b/util/qemu-co-shared-resource.c
@@ -0,0 +1,76 @@
+/*
+ * Helper functionality for distributing a fixed total amount of
+ * an abstract resource among multiple coroutines.
+ *
+ * Copyright (c) 2019 Virtuozzo International GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/coroutine.h"
+#include "qemu/co-shared-resource.h"
+
+struct SharedResource {
+ uint64_t total;
+ uint64_t available;
+
+ CoQueue queue;
+};
+
+SharedResource *shres_create(uint64_t total)
+{
+ SharedResource *s = g_new0(SharedResource, 1);
+
+ s->total = s->available = total;
+ qemu_co_queue_init(&s->queue);
+
+ return s;
+}
+
+void shres_destroy(SharedResource *s)
+{
+ assert(s->available == s->total);
+ g_free(s);
+}
+
+bool co_try_get_from_shres(SharedResource *s, uint64_t n)
+{
+ if (s->available >= n) {
+ s->available -= n;
+ return true;
+ }
+
+ return false;
+}
+
+void coroutine_fn co_get_from_shres(SharedResource *s, uint64_t n)
+{
+ assert(n <= s->total);
+ while (!co_try_get_from_shres(s, n)) {
+ qemu_co_queue_wait(&s->queue, NULL);
+ }
+}
+
+void coroutine_fn co_put_to_shres(SharedResource *s, uint64_t n)
+{
+ assert(s->total - s->available >= n);
+ s->available += n;
+ qemu_co_queue_restart_all(&s->queue);
+}
diff --git a/vl.c b/vl.c
index b87c15d71a..6a65a64bfd 100644
--- a/vl.c
+++ b/vl.c
@@ -2208,10 +2208,12 @@ static int device_init_func(void *opaque, QemuOpts *opts, Error **errp)
DeviceState *dev;
dev = qdev_device_add(opts, errp);
- if (!dev) {
+ if (!dev && *errp) {
+ error_report_err(*errp);
return -1;
+ } else if (dev) {
+ object_unref(OBJECT(dev));
}
- object_unref(OBJECT(dev));
return 0;
}
@@ -3232,7 +3234,8 @@ int main(int argc, char **argv, char **envp)
if (*p == 'x') {
p++;
depth = strtol(p, (char **)&p, 10);
- if (depth != 8 && depth != 15 && depth != 16 &&
+ if (depth != 1 && depth != 2 && depth != 4 &&
+ depth != 8 && depth != 15 && depth != 16 &&
depth != 24 && depth != 32)
goto graphic_error;
} else if (*p == '\0') {