summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.gitlab-ci.d/check-patch.py6
-rw-r--r--crypto/block.c17
-rw-r--r--crypto/hmac-gcrypt.c12
-rw-r--r--crypto/hmac-glib.c10
-rw-r--r--crypto/ivgen-essiv.c15
-rw-r--r--crypto/ivgen.c6
-rw-r--r--crypto/secret_keyring.c5
-rw-r--r--crypto/x509-utils.c4
-rw-r--r--docs/about/deprecated.rst21
-rw-r--r--docs/system/security.rst10
-rw-r--r--hw/hexagon/hexagon_dsp.c23
-rw-r--r--hw/hexagon/hexagon_tlb.c10
-rw-r--r--hw/intc/hex-l2vic.c13
-rw-r--r--io/channel-socket.c2
-rw-r--r--io/channel-websock.c10
-rw-r--r--meson.build6
-rw-r--r--target/hexagon/gen_tcg.h28
-rw-r--r--target/hexagon/genptr.c1
-rw-r--r--target/hexagon/hex_interrupts.c2
-rw-r--r--target/hexagon/hex_mmu.c12
-rw-r--r--target/hexagon/hexswi.c1
-rw-r--r--target/hexagon/imported/encode_pp.def11
-rw-r--r--target/hexagon/imported/system.idef11
-rw-r--r--target/hexagon/op_helper.c9
-rw-r--r--target/hexagon/tag_rev_info.c.inc10
-rw-r--r--target/hexagon/translate.c22
-rwxr-xr-xtests/functional/hexagon/test_arch_tests.py6
-rwxr-xr-xtests/functional/hexagon/test_systests.py17
-rw-r--r--tests/tcg/meson.build4
-rw-r--r--tests/unit/meson.build1
-rw-r--r--tests/unit/test-io-channel-websock.c249
31 files changed, 475 insertions, 79 deletions
diff --git a/.gitlab-ci.d/check-patch.py b/.gitlab-ci.d/check-patch.py
index be13e6f77d..45be77295d 100755
--- a/.gitlab-ci.d/check-patch.py
+++ b/.gitlab-ci.d/check-patch.py
@@ -46,7 +46,11 @@ errors = False
print("\nChecking all commits since %s...\n" % ancestor, flush=True)
-ret = subprocess.run(["scripts/checkpatch.pl", "--terse", ancestor + "..."])
+# We don't want "noise" for clean patches, but do want to see
+# the full commit hash for each violation, along with the
+# offending patch content
+ret = subprocess.run(["scripts/checkpatch.pl", "--emacs", "--quiet",
+ ancestor + "..."])
if ret.returncode != 0:
print(" ❌ FAIL one or more commits failed scripts/checkpatch.pl")
diff --git a/crypto/block.c b/crypto/block.c
index 96c83e60b9..42558f3caf 100644
--- a/crypto/block.c
+++ b/crypto/block.c
@@ -55,7 +55,7 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
unsigned int flags,
Error **errp)
{
- QCryptoBlock *block = g_new0(QCryptoBlock, 1);
+ g_autofree QCryptoBlock *block = g_new0(QCryptoBlock, 1);
qemu_mutex_init(&block->mutex);
@@ -65,7 +65,6 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
!qcrypto_block_drivers[options->format]) {
error_setg(errp, "Unsupported block driver %s",
QCryptoBlockFormat_str(options->format));
- g_free(block);
return NULL;
}
@@ -74,11 +73,10 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
if (block->driver->open(block, options, optprefix,
readfunc, opaque, flags, errp) < 0)
{
- g_free(block);
return NULL;
}
- return block;
+ return g_steal_pointer(&block);
}
@@ -90,7 +88,7 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
unsigned int flags,
Error **errp)
{
- QCryptoBlock *block = g_new0(QCryptoBlock, 1);
+ g_autofree QCryptoBlock *block = g_new0(QCryptoBlock, 1);
qemu_mutex_init(&block->mutex);
@@ -100,7 +98,6 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
!qcrypto_block_drivers[options->format]) {
error_setg(errp, "Unsupported block driver %s",
QCryptoBlockFormat_str(options->format));
- g_free(block);
return NULL;
}
@@ -109,11 +106,10 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
if (block->driver->create(block, options, optprefix, initfunc,
writefunc, opaque, errp) < 0) {
- g_free(block);
return NULL;
}
- return block;
+ return g_steal_pointer(&block);
}
@@ -185,17 +181,16 @@ int qcrypto_block_amend_options(QCryptoBlock *block,
QCryptoBlockInfo *qcrypto_block_get_info(QCryptoBlock *block,
Error **errp)
{
- QCryptoBlockInfo *info = g_new0(QCryptoBlockInfo, 1);
+ g_autofree QCryptoBlockInfo *info = g_new0(QCryptoBlockInfo, 1);
info->format = block->format;
if (block->driver->get_info &&
block->driver->get_info(block, info, errp) < 0) {
- g_free(info);
return NULL;
}
- return info;
+ return g_steal_pointer(&info);
}
diff --git a/crypto/hmac-gcrypt.c b/crypto/hmac-gcrypt.c
index e428d17479..44631fb348 100644
--- a/crypto/hmac-gcrypt.c
+++ b/crypto/hmac-gcrypt.c
@@ -50,7 +50,7 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
const uint8_t *key, size_t nkey,
Error **errp)
{
- QCryptoHmacGcrypt *ctx;
+ g_autofree QCryptoHmacGcrypt *ctx = NULL;
gcry_error_t err;
if (!qcrypto_hmac_supports(alg)) {
@@ -66,7 +66,7 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
if (err != 0) {
error_setg(errp, "Cannot initialize hmac: %s",
gcry_strerror(err));
- goto error;
+ return NULL;
}
err = gcry_mac_setkey(ctx->handle, (const void *)key, nkey);
@@ -74,14 +74,10 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
error_setg(errp, "Cannot set key: %s",
gcry_strerror(err));
gcry_mac_close(ctx->handle);
- goto error;
+ return NULL;
}
- return ctx;
-
-error:
- g_free(ctx);
- return NULL;
+ return g_steal_pointer(&ctx);
}
static void
diff --git a/crypto/hmac-glib.c b/crypto/hmac-glib.c
index b845133a05..1f17769c1c 100644
--- a/crypto/hmac-glib.c
+++ b/crypto/hmac-glib.c
@@ -46,7 +46,7 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
const uint8_t *key, size_t nkey,
Error **errp)
{
- QCryptoHmacGlib *ctx;
+ g_autofree QCryptoHmacGlib *ctx = NULL;
if (!qcrypto_hmac_supports(alg)) {
error_setg(errp, "Unsupported hmac algorithm %s",
@@ -60,14 +60,10 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
(const uint8_t *)key, nkey);
if (!ctx->ghmac) {
error_setg(errp, "Cannot initialize hmac and set key");
- goto error;
+ return NULL;
}
- return ctx;
-
-error:
- g_free(ctx);
- return NULL;
+ return g_steal_pointer(&ctx);
}
static void
diff --git a/crypto/ivgen-essiv.c b/crypto/ivgen-essiv.c
index 3d5a188795..d5fa269888 100644
--- a/crypto/ivgen-essiv.c
+++ b/crypto/ivgen-essiv.c
@@ -31,10 +31,10 @@ static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
const uint8_t *key, size_t nkey,
Error **errp)
{
- uint8_t *salt;
+ g_autofree uint8_t *salt = NULL;
size_t nhash;
size_t nsalt;
- QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
+ g_autofree QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
/* Not necessarily the same as nkey */
nsalt = qcrypto_cipher_get_key_len(ivgen->cipher);
@@ -46,8 +46,6 @@ static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
if (qcrypto_hash_bytes(ivgen->hash, (const gchar *)key, nkey,
&salt, &nhash,
errp) < 0) {
- g_free(essiv);
- g_free(salt);
return -1;
}
@@ -57,13 +55,10 @@ static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
salt, MIN(nhash, nsalt),
errp);
if (!essiv->cipher) {
- g_free(essiv);
- g_free(salt);
return -1;
}
- g_free(salt);
- ivgen->private = essiv;
+ ivgen->private = g_steal_pointer(&essiv);
return 0;
}
@@ -75,7 +70,7 @@ static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
{
QCryptoIVGenESSIV *essiv = ivgen->private;
size_t ndata = qcrypto_cipher_get_block_len(ivgen->cipher);
- uint8_t *data = g_new(uint8_t, ndata);
+ g_autofree uint8_t *data = g_new(uint8_t, ndata);
sector = cpu_to_le64(sector);
memcpy(data, (uint8_t *)&sector, MIN(sizeof(sector), ndata));
@@ -88,7 +83,6 @@ static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
data,
ndata,
errp) < 0) {
- g_free(data);
return -1;
}
@@ -99,7 +93,6 @@ static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
if (ndata < niv) {
memset(iv + ndata, 0, niv - ndata);
}
- g_free(data);
return 0;
}
diff --git a/crypto/ivgen.c b/crypto/ivgen.c
index 6b7d24d889..9f1f7d7dca 100644
--- a/crypto/ivgen.c
+++ b/crypto/ivgen.c
@@ -33,7 +33,7 @@ QCryptoIVGen *qcrypto_ivgen_new(QCryptoIVGenAlgo alg,
const uint8_t *key, size_t nkey,
Error **errp)
{
- QCryptoIVGen *ivgen = g_new0(QCryptoIVGen, 1);
+ g_autofree QCryptoIVGen *ivgen = g_new0(QCryptoIVGen, 1);
ivgen->algorithm = alg;
ivgen->cipher = cipheralg;
@@ -51,16 +51,14 @@ QCryptoIVGen *qcrypto_ivgen_new(QCryptoIVGenAlgo alg,
break;
default:
error_setg(errp, "Unknown block IV generator algorithm %d", alg);
- g_free(ivgen);
return NULL;
}
if (ivgen->driver->init(ivgen, key, nkey, errp) < 0) {
- g_free(ivgen);
return NULL;
}
- return ivgen;
+ return g_steal_pointer(&ivgen);
}
diff --git a/crypto/secret_keyring.c b/crypto/secret_keyring.c
index 78d7f09b3b..3b332276ef 100644
--- a/crypto/secret_keyring.c
+++ b/crypto/secret_keyring.c
@@ -41,7 +41,7 @@ qcrypto_secret_keyring_load_data(QCryptoSecretCommon *sec_common,
Error **errp)
{
QCryptoSecretKeyring *secret = QCRYPTO_SECRET_KEYRING(sec_common);
- uint8_t *buffer = NULL;
+ g_autofree uint8_t *buffer = NULL;
long retcode;
*output = NULL;
@@ -61,12 +61,11 @@ qcrypto_secret_keyring_load_data(QCryptoSecretCommon *sec_common,
retcode = keyctl_read(secret->serial, buffer, retcode);
if (retcode < 0) {
- g_free(buffer);
goto keyctl_error;
}
*outputlen = retcode;
- *output = buffer;
+ *output = g_steal_pointer(&buffer);
return;
keyctl_error:
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index e4767f9838..edcc44de80 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -319,13 +319,15 @@ int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
int curve_id;
algo = qcrypto_x509_get_pk_algorithm(cert, size, errp);
+ if (algo < 0) {
+ return -1;
+ }
if (algo != GNUTLS_PK_ECDSA) {
return 0;
}
curve_id = qcrypto_x509_get_ecc_curve(cert, size, errp);
if (curve_id == -1) {
- error_setg(errp, "Failed to get ECC curve");
return -1;
}
diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst
index 05e4ce8cf1..98c32991c9 100644
--- a/docs/about/deprecated.rst
+++ b/docs/about/deprecated.rst
@@ -434,6 +434,27 @@ ABI is long-obsolete. We are therefore deprecating both OABI support
and NWFPE emulation, and they will be removed in a future QEMU
release.
+Build features
+--------------
+
+Crypto AF_ALG backend (since 11.2)
+----------------------------------
+
+The use of the AF_ALG backend for cryptography has been deprecated
+with no replacement.
+
+The AF_ALG interface is deprecated by Linux 7.2 and all support
+for hardware accelerators has been removed. It will thus always be
+slower than userspace crypto due to the overhead of copying data
+to kernel space. The GNUTLS, Nettle and GCrypt libraries supported
+by QEMU all include a variety of hardware optimized crypto
+implementations which should suffice for typical needs.
+
+For the virtio-crypto device, the 'cryptodev-backend-lkcf' backend
+can offload some operations to the kernel via the keyctl syscall,
+and the 'cryptodev-vhost-user' backend can offload the device
+backend to an external process which can integrate with crypto
+accelerators.
Backwards compatibility
-----------------------
diff --git a/docs/system/security.rst b/docs/system/security.rst
index af626a4230..8c42d1a6d8 100644
--- a/docs/system/security.rst
+++ b/docs/system/security.rst
@@ -143,6 +143,16 @@ an issue as a normal bug.
which case plain manipulation of the stream is not considered as
an attack vector.
+* **uninitialized stack variables**. If the bug scenario relies on
+ undefined behaviour from stack variables that lack explicit
+ initialization, it will not usually be considered a security flaw.
+ The build system adds '-ftrivial-auto-var-init=zero', which is
+ available in both the supported compilers (GCC and CLang) and
+ ensures all stack variables have implicit zero-initializers.
+ This eliminates undefined behaviour and usually gives the
+ correct desired initialization value, eliminating most of the
+ bug scenarios wrt uninitialized stack variables.
+
* **low severity impact**. As a catch all rule, issues which
are judged to have a "low" severity impact on the system will
usually not justify handling as security bugs, nor assignment
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
index 5b2b6e312b..ff110234b0 100644
--- a/hw/hexagon/hexagon_dsp.c
+++ b/hw/hexagon/hexagon_dsp.c
@@ -29,6 +29,7 @@
#include "semihosting/semihost.h"
#include "machine_cfg_v66g_1024.h.inc"
+#include "machine_cfg_v68n_1024.h.inc"
#define TYPE_HEXAGON_DSP_MACHINE "hexagon-dsp-machine"
OBJECT_DECLARE_SIMPLE_TYPE(HexagonDspMachineState, HEXAGON_DSP_MACHINE)
@@ -181,6 +182,23 @@ static void v66g_1024_init(ObjectClass *oc, const void *data)
mc->default_cpus = 4;
}
+static void v68n_1024_config_init(MachineState *machine)
+{
+ hexagon_common_init(machine, v68_rev, &v68n_1024);
+}
+
+static void v68n_1024_init(ObjectClass *oc, const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "Hexagon V68N_1024";
+ mc->alias = "sim";
+ mc->init = v68n_1024_config_init;
+ init_mc(mc);
+ mc->default_cpu_type = TYPE_HEXAGON_CPU_V68;
+ mc->default_cpus = 6;
+}
+
static const TypeInfo hexagon_machine_types[] = {
{
.name = TYPE_HEXAGON_COMMON_MACHINE,
@@ -199,6 +217,11 @@ static const TypeInfo hexagon_machine_types[] = {
.parent = TYPE_HEXAGON_DSP_MACHINE,
.class_init = v66g_1024_init,
},
+ {
+ .name = MACHINE_TYPE_NAME("V68N_1024"),
+ .parent = TYPE_HEXAGON_DSP_MACHINE,
+ .class_init = v68n_1024_init,
+ },
};
DEFINE_TYPES(hexagon_machine_types)
diff --git a/hw/hexagon/hexagon_tlb.c b/hw/hexagon/hexagon_tlb.c
index c76805abac..157d03e51b 100644
--- a/hw/hexagon/hexagon_tlb.c
+++ b/hw/hexagon/hexagon_tlb.c
@@ -326,6 +326,16 @@ bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
for (uint32_t i = 0; i < tlb->num_entries; i++) {
if (hex_tlb_entry_match(tlb->entries[i], asid, VA, access_type,
PA, prot, size, excp, cause_code, mmu_idx)) {
+ if (*excp == 0) {
+ for (i++; i < tlb->num_entries; i++) {
+ if (hex_tlb_entry_match_noperm(tlb->entries[i], asid,
+ VA)) {
+ *excp = HEX_EVENT_IMPRECISE;
+ *cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH;
+ break;
+ }
+ }
+ }
return true;
}
}
diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c
index a986f0bdf3..736f1be3b1 100644
--- a/hw/intc/hex-l2vic.c
+++ b/hw/intc/hex-l2vic.c
@@ -92,7 +92,7 @@ typedef struct HexL2VICState {
DECLARE_BITMAP32(int_pending, L2VIC_INTERRUPT_MAX);
/* Which enabled interrupt is active */
DECLARE_BITMAP32(int_status, L2VIC_INTERRUPT_MAX);
- /* Edge or Level interrupt */
+ /* 1 for edge-triggered, 0 for level-triggered */
DECLARE_BITMAP32(int_type, L2VIC_INTERRUPT_MAX);
DECLARE_BITMAP32(int_group_n[4], L2VIC_INTERRUPT_MAX);
qemu_irq irq[8];
@@ -249,6 +249,11 @@ static inline bool vid_active(HexL2VICState *s)
return active_irq != size;
}
+static bool edge_triggered_irq(HexL2VICState *s, int irq)
+{
+ return test_bit32(irq, s->int_type);
+}
+
static bool l2vic_update(HexL2VICState *s, int irq)
{
bool pending;
@@ -270,7 +275,7 @@ static bool l2vic_update(HexL2VICState *s, int irq)
* enable bit set across deliveries -- the firmware enables once
* and expects the interrupt to remain enabled.
*/
- if (test_bit32(irq, s->int_type)) {
+ if (edge_triggered_irq(s, irq)) {
clear_bit32(irq, s->int_enable);
}
s->vid = irq;
@@ -299,7 +304,7 @@ static void l2vic_set_irq(void *opaque, int irq, int level)
if (level) {
set_bit32(irq, s->int_pending);
- } else if (!test_bit32(irq, s->int_type)) {
+ } else if (!edge_triggered_irq(s, irq)) {
clear_bit32(irq, s->int_pending);
}
l2vic_update(s, irq);
@@ -328,7 +333,7 @@ static void l2vic_write(void *opaque, hwaddr offset, uint64_t val,
while ((bit = ctz32(bits)) < 32) {
int irq = base_irq + bit;
- if (test_bit32(irq, s->int_type)) {
+ if (edge_triggered_irq(s, irq)) {
set_bit32(irq, s->int_pending);
}
bits &= ~(1u << bit);
diff --git a/io/channel-socket.c b/io/channel-socket.c
index 12773b832c..7920cee639 100644
--- a/io/channel-socket.c
+++ b/io/channel-socket.c
@@ -667,7 +667,7 @@ static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
retry:
ret = sendmsg(sioc->fd, &msg, sflags);
- if (ret <= 0) {
+ if (ret < 0) {
switch (errno) {
case EAGAIN:
return QIO_CHANNEL_ERR_BLOCK;
diff --git a/io/channel-websock.c b/io/channel-websock.c
index 1929abf56a..461abcae48 100644
--- a/io/channel-websock.c
+++ b/io/channel-websock.c
@@ -230,7 +230,7 @@ qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
tmp = strchr(buffer, ' ');
if (!tmp) {
error_setg(errp, "Missing HTTP path delimiter");
- return 0;
+ goto bad_request;
}
*tmp = '\0';
@@ -492,6 +492,9 @@ static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
buffer_reserve(&ioc->encinput, want);
ret = qio_channel_read(ioc->master,
(char *)buffer_end(&ioc->encinput), want, errp);
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ return 0;
+ }
if (ret < 0) {
return -1;
}
@@ -562,6 +565,11 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
wioc->encoutput.offset,
&err);
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ /* Socket buffer is full, the G_IO_OUT watch stays armed */
+ return TRUE;
+ }
+
if (ret < 0) {
trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
diff --git a/meson.build b/meson.build
index 2a022c4d85..ec9b05414a 100644
--- a/meson.build
+++ b/meson.build
@@ -5059,3 +5059,9 @@ if not actually_reloc and (host_os == 'windows' or get_option('relocatable'))
message('QEMU will have to be installed under ' + get_option('prefix') + '.')
message('Use --disable-relocatable to remove this warning.')
endif
+
+if get_option('crypto_afalg').enabled()
+ warning('Use of the AF_ALG crypto backend is deprecated, ' +
+ 'since Linux 7.2 has deprecated the AF_ALG interface ' +
+ 'and removed its ability to use hardware accelerators.')
+endif
diff --git a/target/hexagon/gen_tcg.h b/target/hexagon/gen_tcg.h
index 1d25391282..5f7df66ae6 100644
--- a/target/hexagon/gen_tcg.h
+++ b/target/hexagon/gen_tcg.h
@@ -1329,6 +1329,34 @@
do { } while (0)
#define fGEN_TCG_Y2_syncht(SHORTCODE) \
do { } while (0)
+
+#define fGEN_TCG_DMA_UNIMP() \
+ qemu_log_mask(LOG_UNIMP, "'%s' is not implemented\n", \
+ opcode_names[insn->opcode])
+#define fGEN_TCG_Y6_dmstart(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); RsV = RsV; } while (0)
+#define fGEN_TCG_Y6_dmresume(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); RsV = RsV; } while (0)
+#define fGEN_TCG_Y6_dmlink(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); RsV = RsV; RtV = RtV; } while (0)
+#define fGEN_TCG_Y6_dmcfgwr(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); RsV = RsV; RtV = RtV; } while (0)
+#define fGEN_TCG_Y6_dmcfgrd(SHORTCODE) \
+ do { \
+ fGEN_TCG_DMA_UNIMP(); \
+ RsV = RsV; \
+ tcg_gen_movi_tl(RdV, 0); \
+ } while (0)
+#define fGEN_TCG_Y6_dmpoll(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); tcg_gen_movi_tl(RdV, 0); } while (0)
+#define fGEN_TCG_Y6_dmwait(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); tcg_gen_movi_tl(RdV, 0); } while (0)
+#define fGEN_TCG_Y6_dmpause(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); tcg_gen_movi_tl(RdV, 0); } while (0)
+#define fGEN_TCG_Y6_dmsyncht(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); tcg_gen_movi_tl(RdV, 0); } while (0)
+#define fGEN_TCG_Y6_dmtlbsynch(SHORTCODE) \
+ do { fGEN_TCG_DMA_UNIMP(); tcg_gen_movi_tl(RdV, 0); } while (0)
#define fGEN_TCG_Y2_dcfetchbo(SHORTCODE) \
do { \
RsV = RsV; \
diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c
index 1f109d44de..9f53fcb6b9 100644
--- a/target/hexagon/genptr.c
+++ b/target/hexagon/genptr.c
@@ -16,6 +16,7 @@
*/
#include "qemu/osdep.h"
+#include "qemu/log.h"
#include "cpu.h"
#include "internal.h"
#include "tcg/tcg-op.h"
diff --git a/target/hexagon/hex_interrupts.c b/target/hexagon/hex_interrupts.c
index 7dde1294b2..2b2834af95 100644
--- a/target/hexagon/hex_interrupts.c
+++ b/target/hexagon/hex_interrupts.c
@@ -441,7 +441,7 @@ void hex_interrupt_update(CPUHexagonState *env)
const int exe_mode = get_exe_mode(hex_env);
if (exe_mode != HEX_EXE_MODE_OFF) {
cpu_interrupt(cs, CPU_INTERRUPT_SWI);
- cpu_resume(cs);
+ qemu_cpu_kick(cs);
}
}
}
diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c
index d6258c673d..d6ee7c4079 100644
--- a/target/hexagon/hex_mmu.c
+++ b/target/hexagon/hex_mmu.c
@@ -71,10 +71,16 @@ bool hex_tlb_find_match(CPUHexagonState *env, uint32_t VA,
uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr);
int cause_code = 0;
+ bool found;
- bool found = hexagon_tlb_find_match(cpu->tlb, asid, VA, access_type,
- PA, prot, size, excp, &cause_code,
- mmu_idx);
+ env->imprecise_exception = 0;
+ found = hexagon_tlb_find_match(cpu->tlb, asid, VA, access_type,
+ PA, prot, size, excp, &cause_code,
+ mmu_idx);
+ if (*excp == HEX_EVENT_IMPRECISE) {
+ env->imprecise_exception = *excp;
+ *excp = 0;
+ }
if (cause_code) {
env->cause_code = cause_code;
}
diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c
index 75f0a9cc52..4705e915ae 100644
--- a/target/hexagon/hexswi.c
+++ b/target/hexagon/hexswi.c
@@ -934,6 +934,7 @@ void hexagon_cpu_do_interrupt(CPUState *cs)
break;
case HEX_EVENT_IMPRECISE:
+ env->imprecise_exception = 0;
if (get_exe_mode(env) == HEX_EXE_MODE_WAIT) {
env->gpr[HEX_REG_PC] = env->wait_next_pc - 4;
clear_wait_mode(env);
diff --git a/target/hexagon/imported/encode_pp.def b/target/hexagon/imported/encode_pp.def
index 1c64495d51..d9e8ea2aeb 100644
--- a/target/hexagon/imported/encode_pp.def
+++ b/target/hexagon/imported/encode_pp.def
@@ -529,6 +529,17 @@ DEF_ENC32(Y5_l2fetch, ICLASS_ST" 011 01 00sssss PP-ttttt --------")
DEF_ENC32(Y6_l2gcleanpa, ICLASS_ST" 011 01 01----- PP-ttttt --------")
DEF_ENC32(Y6_l2gcleaninvpa,ICLASS_ST" 011 01 10----- PP-ttttt --------")
+DEF_ENC32(Y6_dmcfgrd, "10101000000sssssPP------101ddddd")
+DEF_ENC32(Y6_dmcfgwr, "10101000000sssssPP-ttttt110-----")
+DEF_ENC32(Y6_dmlink, "10100110000sssssPP-ttttt010-----")
+DEF_ENC32(Y6_dmpause, "10101000000-----PP------011ddddd")
+DEF_ENC32(Y6_dmpoll, "10101000000-----PP------010ddddd")
+DEF_ENC32(Y6_dmresume, "10100110000sssssPP------100-----")
+DEF_ENC32(Y6_dmstart, "10100110000sssssPP------001-----")
+DEF_ENC32(Y6_dmsyncht, "10101000000-----PP-----0111ddddd")
+DEF_ENC32(Y6_dmtlbsynch,"10101000000-----PP-----1111ddddd")
+DEF_ENC32(Y6_dmwait, "10101000000-----PP------001ddddd")
+
/*******************************/
/* */
diff --git a/target/hexagon/imported/system.idef b/target/hexagon/imported/system.idef
index 9e85bed0a6..02aee8ce8e 100644
--- a/target/hexagon/imported/system.idef
+++ b/target/hexagon/imported/system.idef
@@ -222,6 +222,17 @@ Q6INSN(Y2_isync,"isync",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Memory Syn
Q6INSN(Y2_barrier,"barrier",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK),"Memory Barrier",{fBARRIER();})
Q6INSN(Y2_syncht,"syncht",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET),"Memory Synchronization",{fSYNCH();})
+Q6INSN(Y6_dmstart,"dmstart(Rs32)",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Start",{RsV=RsV;})
+Q6INSN(Y6_dmlink,"dmlink(Rs32,Rt32)",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Link",{RsV=RsV; RtV=RtV;})
+Q6INSN(Y6_dmpoll,"Rd32=dmpoll",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Poll",{RdV=0;})
+Q6INSN(Y6_dmwait,"Rd32=dmwait",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Wait",{RdV=0;})
+Q6INSN(Y6_dmsyncht,"Rd32=dmsyncht",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA SynchT",{RdV=0;})
+Q6INSN(Y6_dmtlbsynch,"Rd32=dmtlbsynch",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA TLB Synch",{RdV=0;})
+Q6INSN(Y6_dmcfgrd,"Rd32=dmcfgrd(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Config Read",{RsV=RsV; RdV=0;})
+Q6INSN(Y6_dmcfgwr,"dmcfgwr(Rs32,Rt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Config Write",{RsV=RsV; RtV=RtV;})
+Q6INSN(Y6_dmpause,"Rd32=dmpause",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Pause",{RdV=0;})
+Q6INSN(Y6_dmresume,"dmresume(Rs32)",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_DMA,A_RESTRICT_SLOT0ONLY,A_NO_TIMING_LOG),"DMA Resume",{RsV=RsV;})
+
Q6INSN(Y2_dcfetchbo,"dcfetch(Rs32+#u11:3)",ATTRIBS(A_RESTRICT_PREFERSLOT0,A_DCFETCH,A_RESTRICT_NOSLOT1_STORE),"Data Cache Prefetch",{fEA_RI(RsV,uiV); fDCFETCH(EA);})
Q6INSN(Y2_dckill,"dckill",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_DCFLUSHOP),"Data Cache Invalidate",{fDCKILL();})
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index 71555a7ba3..7ad99ced7a 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -1889,6 +1889,13 @@ static inline QEMU_ALWAYS_INLINE uint32_t sreg_read(CPUHexagonState *env,
HexagonCPU *cpu;
g_assert(bql_locked());
+ if (reg == HEX_SREG_BADVA) {
+ uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+ if (GET_SSR_FIELD(SSR_BVS, ssr)) {
+ return env->t_sreg[HEX_SREG_BADVA1];
+ }
+ return env->t_sreg[HEX_SREG_BADVA0];
+ }
if (reg < HEX_SREG_GLB_START) {
return env->t_sreg[reg];
}
@@ -1920,6 +1927,8 @@ uint32_t HELPER(greg_read)(CPUHexagonState *env, uint32_t reg)
uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg)
{
+ g_assert((reg & 1) == 0);
+
if (reg == HEX_GREG_G0 || reg == HEX_GREG_G2) {
return (uint64_t)(env->greg[reg]) |
(((uint64_t)(env->greg[reg + 1])) << 32);
diff --git a/target/hexagon/tag_rev_info.c.inc b/target/hexagon/tag_rev_info.c.inc
index 11c90f86ad..a91b91a23e 100644
--- a/target/hexagon/tag_rev_info.c.inc
+++ b/target/hexagon/tag_rev_info.c.inc
@@ -575,6 +575,16 @@ static const struct tag_rev_info tag_rev_info[XX_LAST_OPCODE] = {
[J2_jumprh] = { .introduced = 0x73, .removed = HEX_VER_NONE },
[L2_loadw_aq] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
[L4_loadd_aq] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmcfgrd] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmcfgwr] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmlink] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmpause] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmpoll] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmresume] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmstart] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmsyncht] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmtlbsynch] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
+ [Y6_dmwait] = { .introduced = HEX_VER_V68, .removed = HEX_VER_NONE },
[M7_dcmpyiw] = { .introduced = HEX_VER_V67, .removed = HEX_VER_NONE },
[M7_dcmpyiw_acc] = { .introduced = HEX_VER_V67, .removed = HEX_VER_NONE },
[M7_dcmpyiwc] = { .introduced = HEX_VER_V67, .removed = HEX_VER_NONE },
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c
index be75e5fdeb..5d3d67e5d3 100644
--- a/target/hexagon/translate.c
+++ b/target/hexagon/translate.c
@@ -1065,20 +1065,14 @@ static void update_exec_counters(DisasContext *ctx)
* A tlbp instruction may detect multiple TLB matches and set a pending
* imprecise exception. Raise it after the packet that ran the tlbp.
*/
-static void check_imprecise_exception(Packet *pkt)
+static void check_imprecise_exception(DisasContext *ctx)
{
- for (int i = 0; i < pkt->num_insns; i++) {
- if (pkt->insn[i].opcode == Y2_tlbp) {
- TCGv PC = tcg_constant_tl(pkt->pc);
- TCGLabel *label = gen_new_label();
- tcg_gen_brcondi_tl(TCG_COND_EQ, hex_imprecise_exception,
- 0, label);
- gen_helper_raise_exception(tcg_env,
- hex_imprecise_exception, PC);
- gen_set_label(label);
- return;
- }
- }
+ TCGv PC = tcg_constant_tl(ctx->pkt.pc);
+ TCGLabel *label = gen_new_label();
+
+ tcg_gen_brcondi_tl(TCG_COND_EQ, hex_imprecise_exception, 0, label);
+ gen_helper_raise_exception(tcg_env, hex_imprecise_exception, PC);
+ gen_set_label(label);
}
#endif
@@ -1182,7 +1176,7 @@ static void gen_commit_packet(DisasContext *ctx)
}
#ifndef CONFIG_USER_ONLY
- check_imprecise_exception(&ctx->pkt);
+ check_imprecise_exception(ctx);
#endif
if (ctx->pkt_ends_tb || ctx->base.is_jmp == DISAS_NORETURN) {
diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py
index 8e71386c18..5d7ce48743 100755
--- a/tests/functional/hexagon/test_arch_tests.py
+++ b/tests/functional/hexagon/test_arch_tests.py
@@ -17,12 +17,12 @@ class ArchTestsUart(QemuSystemTest):
Tests output results via UART.
"""
- timeout = 60
+ timeout = 180
ASSET_TARBALL = Asset(
"https://github.com/qualcomm/qemu-hexagon-testing/releases/"
- "download/v0.2.12/arch_tests_uart.tar.gz",
- "871a339bf78cac0ebaf1b2509bfcd5b249ad8190be33e0cf848283b2f6915323",
+ "download/v0.2.14/arch_tests_uart.tar.gz",
+ "ce93cb90b9d757c1946dfe8fe6abcec8292b08a66546ac51b2dd48650b05fa91",
)
def run_uart_test(self, test_name: str,
diff --git a/tests/functional/hexagon/test_systests.py b/tests/functional/hexagon/test_systests.py
index f36e015f50..983ee1672c 100755
--- a/tests/functional/hexagon/test_systests.py
+++ b/tests/functional/hexagon/test_systests.py
@@ -21,8 +21,8 @@ class SysTestsStandaloneTests(QemuSystemTest):
SYSTEST_TIMEOUT_SEC = 30
ASSET_TARBALL = Asset(
- "https://github.com/qualcomm/qemu-hexagon-testing/releases/download/v0.2.11/systests_standalone.tar.gz",
- "b5777aa65245de7710a7a08d717953c1362be7c8b60d9014c9fee8b17610ad1c",
+ "https://github.com/qualcomm/qemu-hexagon-testing/releases/download/v0.2.14/systests_standalone.tar.gz",
+ "f0c535d746384126954757b6ce54452c5fe82624618c79862495eec24718a6ff",
)
def setUp(self):
@@ -34,7 +34,7 @@ class SysTestsStandaloneTests(QemuSystemTest):
self.assertTrue(os.path.exists(path))
return path
- def run_exit_zero(self, binary_name, *extra_args, machine="V66G_1024"):
+ def run_exit_zero(self, binary_name, *extra_args, machine="sim"):
self.set_machine(machine)
self.set_vm_arg("-display", "none")
self.set_vm_arg("-kernel", self.binary(binary_name))
@@ -47,7 +47,7 @@ class SysTestsStandaloneTests(QemuSystemTest):
f"code {self.vm.exitcode()}, expected 0")
def run_console_pattern(self, binary_name, pattern, *extra_args,
- machine="V66G_1024"):
+ machine="sim"):
self.set_machine(machine)
self.set_vm_arg("-display", "none")
self.set_vm_arg("-kernel", self.binary(binary_name))
@@ -90,5 +90,14 @@ class SysTestsStandaloneTests(QemuSystemTest):
def test_semihost(self):
self.run_console_pattern("semihost", "PASS", "-append", "arg1", "arg2")
+ def test_dtg_interrupt(self):
+ self.run_exit_zero("dtg_interrupt")
+
+ def test_mmu_multi_tlb(self):
+ self.run_exit_zero("mmu_multi_tlb")
+
+ def test_timer_reg(self):
+ self.run_exit_zero("timer_reg")
+
if __name__ == "__main__":
QemuSystemTest.main()
diff --git a/tests/tcg/meson.build b/tests/tcg/meson.build
index 243cdb5fc8..d41a228fb3 100644
--- a/tests/tcg/meson.build
+++ b/tests/tcg/meson.build
@@ -263,8 +263,8 @@ foreach target, plan: tcg_tests
endif
if not has_cc and has_docker
- build_test_depends = image_targets[cc_dockerfile]
- build_test_depend_files = dockerfile
+ build_test_depends += image_targets[cc_dockerfile]
+ build_test_depend_files += dockerfile
mount = meson.project_source_root()
mount = mount + ':' + mount
here = meson.project_build_root()
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index 3a9866c1f2..e47bc7225a 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -115,6 +115,7 @@ if have_block
endif
if host_os != 'windows'
tests += {
+ 'test-io-channel-websock': [io],
'test-image-locking': [testblock],
'test-nested-aio-poll': [],
}
diff --git a/tests/unit/test-io-channel-websock.c b/tests/unit/test-io-channel-websock.c
new file mode 100644
index 0000000000..88da24f993
--- /dev/null
+++ b/tests/unit/test-io-channel-websock.c
@@ -0,0 +1,249 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QEMU I/O channel websock test
+ *
+ * Copyright (c) 2026 Virtuozzo International GmbH
+ */
+
+#include "qemu/osdep.h"
+#include "io/channel-websock.h"
+#include "io/channel-socket.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qemu/sockets.h"
+#include "qom/object.h"
+
+#define TYPE_QIO_CHANNEL_STALL "qio-channel-stall"
+OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelStall, QIO_CHANNEL_STALL)
+
+/*
+ * Reports QIO_CHANNEL_ERR_BLOCK for the first @rstalls reads and @wstalls
+ * writes, the way a TLS channel does when a record arrives split across TCP
+ * segments or the socket cannot take the whole reply at once.
+ */
+struct QIOChannelStall {
+ QIOChannel parent;
+ QIOChannel *master;
+ unsigned rstalls;
+ unsigned wstalls;
+};
+
+static ssize_t qio_channel_stall_readv(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ int **fds,
+ size_t *nfds,
+ int flags,
+ Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ if (sioc->rstalls) {
+ sioc->rstalls--;
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ return qio_channel_readv_full(sioc->master, iov, niov, fds, nfds,
+ flags, errp);
+}
+
+static ssize_t qio_channel_stall_writev(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ int *fds,
+ size_t nfds,
+ int flags,
+ Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ if (sioc->wstalls) {
+ sioc->wstalls--;
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ return qio_channel_writev_full(sioc->master, iov, niov, fds, nfds,
+ flags, errp);
+}
+
+static int qio_channel_stall_set_blocking(QIOChannel *ioc, bool enabled,
+ Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ return qio_channel_set_blocking(sioc->master, enabled, errp) ? 0 : -1;
+}
+
+static int qio_channel_stall_close(QIOChannel *ioc, Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ return qio_channel_close(sioc->master, errp);
+}
+
+static GSource *qio_channel_stall_create_watch(QIOChannel *ioc,
+ GIOCondition condition)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ return qio_channel_create_watch(sioc->master, condition);
+}
+
+static void qio_channel_stall_finalize(Object *obj)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(obj);
+
+ object_unref(OBJECT(sioc->master));
+}
+
+static void qio_channel_stall_class_init(ObjectClass *klass,
+ const void *class_data G_GNUC_UNUSED)
+{
+ QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+ ioc_klass->io_writev = qio_channel_stall_writev;
+ ioc_klass->io_readv = qio_channel_stall_readv;
+ ioc_klass->io_set_blocking = qio_channel_stall_set_blocking;
+ ioc_klass->io_close = qio_channel_stall_close;
+ ioc_klass->io_create_watch = qio_channel_stall_create_watch;
+}
+
+static const TypeInfo qio_channel_stall_info = {
+ .parent = TYPE_QIO_CHANNEL,
+ .name = TYPE_QIO_CHANNEL_STALL,
+ .instance_size = sizeof(QIOChannelStall),
+ .instance_finalize = qio_channel_stall_finalize,
+ .class_init = qio_channel_stall_class_init,
+};
+
+static QIOChannelStall *qio_channel_stall_new(QIOChannel *master,
+ unsigned rstalls,
+ unsigned wstalls)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(
+ object_new(TYPE_QIO_CHANNEL_STALL));
+
+ object_ref(OBJECT(master));
+ sioc->master = master;
+ sioc->rstalls = rstalls;
+ sioc->wstalls = wstalls;
+
+ return sioc;
+}
+
+typedef struct {
+ bool finished;
+ bool failed;
+} QIOChannelWebsockHandshake;
+
+static void test_websock_handshake_done(QIOTask *task, gpointer opaque)
+{
+ QIOChannelWebsockHandshake *res = opaque;
+
+ res->finished = true;
+ res->failed = qio_task_propagate_error(task, NULL);
+}
+
+/*
+ * Drives a server-side handshake against @request and returns whatever
+ * the server wrote back, NUL terminated. The handshake is expected to
+ * fail; the point of the test is the HTTP response that goes with it.
+ */
+static char *test_websock_handshake_reply(const char *request,
+ unsigned rstalls, unsigned wstalls)
+{
+ QIOChannelWebsockHandshake res = { false, false };
+ QIOChannelSocket *cli, *srv;
+ QIOChannelStall *stall;
+ QIOChannelWebsock *wioc;
+ GMainContext *mainloop;
+ int channel[2];
+ char *reply;
+ ssize_t got;
+
+ g_assert(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0);
+
+ cli = qio_channel_socket_new_fd(channel[0], &error_abort);
+ srv = qio_channel_socket_new_fd(channel[1], &error_abort);
+ qio_channel_set_blocking(QIO_CHANNEL(srv), false, &error_abort);
+ qio_channel_set_blocking(QIO_CHANNEL(cli), false, &error_abort);
+
+ stall = qio_channel_stall_new(QIO_CHANNEL(srv), rstalls, wstalls);
+ wioc = qio_channel_websock_new_server(QIO_CHANNEL(stall));
+ qio_channel_websock_handshake(wioc, test_websock_handshake_done,
+ &res, NULL);
+
+ qio_channel_write_all(QIO_CHANNEL(cli), request, strlen(request),
+ &error_abort);
+
+ mainloop = g_main_context_default();
+ while (!res.finished) {
+ g_main_context_iteration(mainloop, TRUE);
+ }
+ g_assert(res.failed);
+
+ reply = g_malloc0(1024);
+ got = qio_channel_read(QIO_CHANNEL(cli), reply, 1023, &error_abort);
+ if (got > 0) {
+ reply[got] = '\0';
+ }
+
+ object_unref(OBJECT(wioc));
+ object_unref(OBJECT(stall));
+ object_unref(OBJECT(srv));
+ object_unref(OBJECT(cli));
+
+ return reply;
+}
+
+static void test_websock_bad_request(const void *opaque)
+{
+ const char *request = opaque;
+ g_autofree char *reply = test_websock_handshake_reply(request, 0, 0);
+
+ g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n"));
+}
+
+static void test_websock_stalled_read(const void *opaque)
+{
+ const char *request = opaque;
+ g_autofree char *reply = test_websock_handshake_reply(request, 1, 0);
+
+ g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n"));
+}
+
+static void test_websock_stalled_write(const void *opaque)
+{
+ const char *request = opaque;
+ g_autofree char *reply = test_websock_handshake_reply(request, 0, 1);
+
+ g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n"));
+}
+
+int main(int argc, char **argv)
+{
+ module_call_init(MODULE_INIT_QOM);
+ type_register_static(&qio_channel_stall_info);
+ g_test_init(&argc, &argv, NULL);
+
+#define TEST_BAD_REQUEST(name, request) \
+ g_test_add_data_func("/io/channel/websock/bad-request/" name, \
+ request, test_websock_bad_request)
+
+ /*
+ * A greeting with no space at all used to leave the response buffer
+ * empty, which drove the handshake into a zero length write.
+ */
+ TEST_BAD_REQUEST("no-space", "stats\r\nx\r\n\r\n");
+ TEST_BAD_REQUEST("method-only", "GET\r\nx\r\n\r\n");
+ TEST_BAD_REQUEST("no-version", "GET /\r\nx\r\n\r\n");
+ TEST_BAD_REQUEST("bad-method", "POST / HTTP/1.1\r\nx: y\r\n\r\n");
+ TEST_BAD_REQUEST("bad-version", "GET / HTTP/1.0\r\nx: y\r\n\r\n");
+
+ /* A read which blocks before any header arrives is not a fatal error. */
+ g_test_add_data_func("/io/channel/websock/stalled-read",
+ "stats\r\nx\r\n\r\n", test_websock_stalled_read);
+ g_test_add_data_func("/io/channel/websock/stalled-write",
+ "stats\r\nx\r\n\r\n", test_websock_stalled_write);
+
+ return g_test_run();
+}