diff options
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 *)§or, 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(); +} |
