Merge tag 'next-pull-request' of https://gitlab.com/peterx/qemu into staging migration/mem pull for 11.2 v2: fixes macos build error - Dongli's patch to add cpr-transfer support for HMP - Fabiano's doc update for migration on security issues - Gavin's fix for MMIO access support for memory APIs, reverting ram_device ops - Sam's migration test build fix for !ASN1 - Peter's a few migration hardening fixes # -----BEGIN PGP SIGNATURE----- # # iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCan3KARIccGV0ZXJ4QHJl # ZGhhdC5jb20ACgkQO1/MzfOr1wa76QD/eBLnPtDvmpNHNH3+bm/3XC3zwyy7v69U # bGK3ocwI3sQA/j9o5FCc7xDCA0QaW6RMeerlLXvXR0uwH46UESKKDloF # =/Jbs # -----END PGP SIGNATURE----- # gpg: Signature made Thu 13 Aug 2026 06:43:29 AM PDT # gpg: using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706 # gpg: issuer "peterx@redhat.com" # gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [unknown] # gpg: aka "Peter Xu <peterx@redhat.com>" [unknown] # gpg: WARNING: The key's User ID is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D D1A9 3B5F CCCD F3AB D706 * tag 'next-pull-request' of https://gitlab.com/peterx/qemu: migration: Fix rare hang of migration_channel_read_peek() migration/ram: Check for RAMBlock size mismatch when parsing migration/multifd: Replace assert() with error_setg() in recv paths migration/multifd: Validate next_packet_size in zlib/zstd recv tests/qtest/migration: Only build tls_no_hostname test with TASN1 system/memory: Make ram device region directly accessible system/memory: Use qemu_ram_move() for directly accessible regions system/memory: Use memmove() for directly accessible regions migration/cpr: Add HMP support for cpr-transfer docs: Add security considerations for migration Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
diff --git a/MAINTAINERS b/MAINTAINERS index 902db77..f249be7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS
@@ -250,6 +250,12 @@ R: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com> S: Supported F: target/hexagon/ +F: hw/intc/hex-l2vic.c +F: include/hw/intc/hex-l2vic.h +F: tests/qtest/l2vic-test.c +F: hw/timer/qct-qtimer.c +F: include/hw/timer/qct-qtimer.h +F: tests/qtest/qct-qtimer-test.c X: target/hexagon/idef-parser/ X: target/hexagon/gen_idef_parser_funcs.py F: linux-user/hexagon/ @@ -262,6 +268,7 @@ F: docs/system/target-hexagon.rst F: docs/system/hexagon/ F: docs/devel/hexagon-sys.rst +F: docs/devel/hexagon-l2vic.rst T: git https://github.com/qualcomm/qemu.git hex-next Hexagon idef-parser @@ -1300,6 +1307,15 @@ S: Maintained F: rust/hw/char/pl011/ +Axiado SoCs and EVKs +M: Kuan-Jui Chiu <kchiu@axiado.com> +L: qemu-arm@nongnu.org +S: Maintained +F: hw/arm/ax3000*.c +F: hw/*/axiado*.c +F: include/hw/arm/ax3000*.h +F: include/hw/*/axiado*.h + AVR Machines ------------- @@ -2160,6 +2176,7 @@ IDE M: John Snow <jsnow@redhat.com> +M: Denis V. Lunev <den@openvz.org> L: qemu-block@nongnu.org S: Odd Fixes F: include/hw/ide/
diff --git a/VERSION b/VERSION index 68d8f15..cf942bd 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@ -11.1.0 +11.1.50
diff --git a/crypto/cipher-gcrypt.c.inc b/crypto/cipher-gcrypt.c.inc index 12eb9dd..fce09a3c 100644 --- a/crypto/cipher-gcrypt.c.inc +++ b/crypto/cipher-gcrypt.c.inc
@@ -65,6 +65,8 @@ return GCRY_CIPHER_MODE_CBC; case QCRYPTO_CIPHER_MODE_CTR: return GCRY_CIPHER_MODE_CTR; + case QCRYPTO_CIPHER_MODE_GCM: + return GCRY_CIPHER_MODE_GCM; default: return GCRY_CIPHER_MODE_NONE; } @@ -104,6 +106,10 @@ case QCRYPTO_CIPHER_MODE_XTS: case QCRYPTO_CIPHER_MODE_CTR: return true; + case QCRYPTO_CIPHER_MODE_GCM: + /* GCM requires a 128-bit block cipher. */ + return gcry_cipher_get_algo_blklen( + qcrypto_cipher_alg_to_gcry_alg(alg)) == 16; default: return false; } @@ -228,6 +234,99 @@ .cipher_free = qcrypto_gcrypt_ctx_free, }; +/* + * GCM is an AEAD stream mode: the IV/nonce need not match the block size, + * the message length need not be a multiple of the block size, associated + * data is fed with gcry_cipher_authenticate() and the authentication tag is + * read back with gcry_cipher_gettag(). + */ +static int qcrypto_gcrypt_gcm_setiv(QCryptoCipher *cipher, + const uint8_t *iv, size_t niv, + Error **errp) +{ + QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base); + gcry_error_t err; + + 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 0; +} + +static int qcrypto_gcrypt_gcm_setaad(QCryptoCipher *cipher, + const uint8_t *aad, size_t len, + Error **errp) +{ + QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base); + gcry_error_t err; + + err = gcry_cipher_authenticate(ctx->handle, aad, len); + if (err != 0) { + error_setg(errp, "Cannot set AAD: %s", gcry_strerror(err)); + return -1; + } + + return 0; +} + +static int qcrypto_gcrypt_gcm_encrypt(QCryptoCipher *cipher, const void *in, + void *out, size_t len, Error **errp) +{ + QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base); + gcry_error_t err; + + 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; +} + +static int qcrypto_gcrypt_gcm_decrypt(QCryptoCipher *cipher, const void *in, + void *out, size_t len, Error **errp) +{ + QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base); + gcry_error_t err; + + 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; +} + +static int qcrypto_gcrypt_gcm_gettag(QCryptoCipher *cipher, + uint8_t *tag, size_t len, Error **errp) +{ + QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base); + gcry_error_t err; + + err = gcry_cipher_gettag(ctx->handle, tag, len); + if (err != 0) { + error_setg(errp, "Cannot get tag: %s", gcry_strerror(err)); + return -1; + } + + return 0; +} + +static const struct QCryptoCipherDriver qcrypto_gcrypt_gcm_driver = { + .cipher_encrypt = qcrypto_gcrypt_gcm_encrypt, + .cipher_decrypt = qcrypto_gcrypt_gcm_decrypt, + .cipher_setiv = qcrypto_gcrypt_gcm_setiv, + .cipher_setaad = qcrypto_gcrypt_gcm_setaad, + .cipher_gettag = qcrypto_gcrypt_gcm_gettag, + .cipher_free = qcrypto_gcrypt_ctx_free, +}; + static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg, QCryptoCipherMode mode, const uint8_t *key, @@ -259,6 +358,8 @@ if (mode == QCRYPTO_CIPHER_MODE_CTR) { drv = &qcrypto_gcrypt_ctr_driver; + } else if (mode == QCRYPTO_CIPHER_MODE_GCM) { + drv = &qcrypto_gcrypt_gcm_driver; } else { drv = &qcrypto_gcrypt_driver; }
diff --git a/crypto/cipher-gnutls.c.inc b/crypto/cipher-gnutls.c.inc index a8263ff..963b328 100644 --- a/crypto/cipher-gnutls.c.inc +++ b/crypto/cipher-gnutls.c.inc
@@ -48,6 +48,15 @@ default: return false; } + case QCRYPTO_CIPHER_MODE_GCM: + switch (alg) { + case QCRYPTO_CIPHER_ALGO_AES_128: + case QCRYPTO_CIPHER_ALGO_AES_192: + case QCRYPTO_CIPHER_ALGO_AES_256: + return true; + default: + return false; + } default: return false; } @@ -223,6 +232,147 @@ .cipher_free = qcrypto_gnutls_cipher_free, }; +/* + * GCM is an AEAD stream mode: the nonce need not match the block size, the + * message length need not be a multiple of the block size, associated data is + * fed with gnutls_cipher_add_auth() and the authentication tag is read back + * with gnutls_cipher_tag(). + */ +static int +qcrypto_gnutls_cipher_encrypt_gcm(QCryptoCipher *cipher, + const void *in, void *out, + size_t len, Error **errp) +{ + QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base); + int err; + + err = gnutls_cipher_encrypt2(ctx->handle, in, len, out, len); + if (err != 0) { + error_setg(errp, "Cannot encrypt data: %s", gnutls_strerror(err)); + return -1; + } + + return 0; +} + +static int +qcrypto_gnutls_cipher_decrypt_gcm(QCryptoCipher *cipher, + const void *in, void *out, + size_t len, Error **errp) +{ + QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base); + int err; + + err = gnutls_cipher_decrypt2(ctx->handle, in, len, out, len); + if (err != 0) { + error_setg(errp, "Cannot decrypt data: %s", gnutls_strerror(err)); + return -1; + } + + return 0; +} + +static int +qcrypto_gnutls_cipher_setiv_gcm(QCryptoCipher *cipher, + const uint8_t *iv, size_t niv, + Error **errp) +{ + QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base); + + gnutls_cipher_set_iv(ctx->handle, (void *)iv, niv); + + return 0; +} + +static int +qcrypto_gnutls_cipher_setaad_gcm(QCryptoCipher *cipher, + const uint8_t *aad, size_t len, + Error **errp) +{ + QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base); + int err; + + err = gnutls_cipher_add_auth(ctx->handle, aad, len); + if (err != 0) { + error_setg(errp, "Cannot add associated data: %s", + gnutls_strerror(err)); + return -1; + } + + return 0; +} + +static int +qcrypto_gnutls_cipher_gettag_gcm(QCryptoCipher *cipher, + uint8_t *tag, size_t len, + Error **errp) +{ + QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base); + int err; + + err = gnutls_cipher_tag(ctx->handle, tag, len); + if (err != 0) { + error_setg(errp, "Cannot get authentication tag: %s", + gnutls_strerror(err)); + return -1; + } + + return 0; +} + +static struct QCryptoCipherDriver gnutls_gcm_driver = { + .cipher_encrypt = qcrypto_gnutls_cipher_encrypt_gcm, + .cipher_decrypt = qcrypto_gnutls_cipher_decrypt_gcm, + .cipher_setiv = qcrypto_gnutls_cipher_setiv_gcm, + .cipher_setaad = qcrypto_gnutls_cipher_setaad_gcm, + .cipher_gettag = qcrypto_gnutls_cipher_gettag_gcm, + .cipher_free = qcrypto_gnutls_cipher_free, +}; + +static QCryptoCipher * +qcrypto_gnutls_aes_gcm_ctx_new(QCryptoCipherAlgo alg, const uint8_t *key, + size_t nkey, Error **errp) +{ + gnutls_datum_t gkey = { (unsigned char *)key, nkey }; + gnutls_cipher_algorithm_t galg = GNUTLS_CIPHER_UNKNOWN; + QCryptoCipherGnutls *ctx; + int err; + + switch (alg) { + case QCRYPTO_CIPHER_ALGO_AES_128: + galg = GNUTLS_CIPHER_AES_128_GCM; + break; + case QCRYPTO_CIPHER_ALGO_AES_192: + galg = GNUTLS_CIPHER_AES_192_GCM; + break; + case QCRYPTO_CIPHER_ALGO_AES_256: + galg = GNUTLS_CIPHER_AES_256_GCM; + break; + default: + error_setg(errp, "Unsupported cipher algorithm %s with GCM mode", + QCryptoCipherAlgo_str(alg)); + return NULL; + } + + if (!qcrypto_cipher_validate_key_length(alg, QCRYPTO_CIPHER_MODE_GCM, + nkey, errp)) { + return NULL; + } + + ctx = g_new0(QCryptoCipherGnutls, 1); + ctx->base.driver = &gnutls_gcm_driver; + ctx->blocksize = 16; + + err = gnutls_cipher_init(&ctx->handle, galg, &gkey, NULL); + if (err != 0) { + error_setg(errp, "Cannot initialize cipher: %s", gnutls_strerror(err)); + g_free(ctx); + return NULL; + } + + return &ctx->base; +} + static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg, QCryptoCipherMode mode, const uint8_t *key, @@ -234,6 +384,10 @@ gnutls_cipher_algorithm_t galg = GNUTLS_CIPHER_UNKNOWN; int err; + if (mode == QCRYPTO_CIPHER_MODE_GCM) { + return qcrypto_gnutls_aes_gcm_ctx_new(alg, key, nkey, errp); + } + switch (mode) { case QCRYPTO_CIPHER_MODE_XTS: switch (alg) {
diff --git a/crypto/cipher-nettle.c.inc b/crypto/cipher-nettle.c.inc index 1afdc39..d4847f0 100644 --- a/crypto/cipher-nettle.c.inc +++ b/crypto/cipher-nettle.c.inc
@@ -27,6 +27,7 @@ #include <nettle/twofish.h> #include <nettle/ctr.h> #include <nettle/xts.h> +#include <nettle/gcm.h> #ifdef CONFIG_CRYPTO_SM4 #include <nettle/sm4.h> #endif @@ -410,6 +411,125 @@ sm4_encrypt_native, sm4_decrypt_native) #endif +/* + * GCM is an AEAD mode built on AES (128-bit block only). Drive it through the + * generic gcm_* interface, using the block cipher's encrypt function for both + * directions; associated data is fed with gcm_update() and the authentication + * tag is produced by gcm_digest(). + */ +typedef struct QCryptoNettleAESGCM { + QCryptoCipher base; + struct gcm_key gcm_key; + struct gcm_ctx gcm_ctx; + union { + struct aes128_ctx aes128; + struct aes192_ctx aes192; + struct aes256_ctx aes256; + } cipher; + nettle_cipher_func *encrypt; +} QCryptoNettleAESGCM; + +static int qcrypto_nettle_aes_gcm_setiv(QCryptoCipher *cipher, + const uint8_t *iv, size_t niv, + Error **errp) +{ + QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base); + + gcm_set_iv(&ctx->gcm_ctx, &ctx->gcm_key, niv, iv); + return 0; +} + +static int qcrypto_nettle_aes_gcm_setaad(QCryptoCipher *cipher, + const uint8_t *aad, size_t len, + Error **errp) +{ + QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base); + + gcm_update(&ctx->gcm_ctx, &ctx->gcm_key, len, aad); + return 0; +} + +static int qcrypto_nettle_aes_gcm_encrypt(QCryptoCipher *cipher, + const void *in, void *out, + size_t len, Error **errp) +{ + QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base); + + gcm_encrypt(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt, + len, out, in); + return 0; +} + +static int qcrypto_nettle_aes_gcm_decrypt(QCryptoCipher *cipher, + const void *in, void *out, + size_t len, Error **errp) +{ + QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base); + + gcm_decrypt(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt, + len, out, in); + return 0; +} + +static int qcrypto_nettle_aes_gcm_gettag(QCryptoCipher *cipher, + uint8_t *tag, size_t len, + Error **errp) +{ + QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base); + + gcm_digest(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt, + len, tag); + return 0; +} + +static const struct QCryptoCipherDriver qcrypto_nettle_aes_gcm_driver = { + .cipher_encrypt = qcrypto_nettle_aes_gcm_encrypt, + .cipher_decrypt = qcrypto_nettle_aes_gcm_decrypt, + .cipher_setiv = qcrypto_nettle_aes_gcm_setiv, + .cipher_setaad = qcrypto_nettle_aes_gcm_setaad, + .cipher_gettag = qcrypto_nettle_aes_gcm_gettag, + .cipher_free = qcrypto_cipher_ctx_free, +}; + +static QCryptoCipher *qcrypto_nettle_aes_gcm_ctx_new(QCryptoCipherAlgo alg, + const uint8_t *key, + size_t nkey, + Error **errp) +{ + QCryptoNettleAESGCM *ctx; + + if (!qcrypto_cipher_validate_key_length(alg, QCRYPTO_CIPHER_MODE_GCM, + nkey, errp)) { + return NULL; + } + + ctx = g_new0(QCryptoNettleAESGCM, 1); + ctx->base.driver = &qcrypto_nettle_aes_gcm_driver; + + switch (alg) { + case QCRYPTO_CIPHER_ALGO_AES_128: + aes128_set_encrypt_key(&ctx->cipher.aes128, key); + ctx->encrypt = aes128_encrypt_native; + break; + case QCRYPTO_CIPHER_ALGO_AES_192: + aes192_set_encrypt_key(&ctx->cipher.aes192, key); + ctx->encrypt = aes192_encrypt_native; + break; + case QCRYPTO_CIPHER_ALGO_AES_256: + aes256_set_encrypt_key(&ctx->cipher.aes256, key); + ctx->encrypt = aes256_encrypt_native; + break; + default: + error_setg(errp, "Unsupported cipher algorithm %s with GCM mode", + QCryptoCipherAlgo_str(alg)); + g_free(ctx); + return NULL; + } + + gcm_set_key(&ctx->gcm_key, &ctx->cipher, ctx->encrypt); + return &ctx->base; +} + bool qcrypto_cipher_supports(QCryptoCipherAlgo alg, QCryptoCipherMode mode) { @@ -440,6 +560,10 @@ case QCRYPTO_CIPHER_MODE_XTS: case QCRYPTO_CIPHER_MODE_CTR: return true; + case QCRYPTO_CIPHER_MODE_GCM: + return alg == QCRYPTO_CIPHER_ALGO_AES_128 || + alg == QCRYPTO_CIPHER_ALGO_AES_192 || + alg == QCRYPTO_CIPHER_ALGO_AES_256; default: return false; } @@ -451,6 +575,10 @@ size_t nkey, Error **errp) { + if (mode == QCRYPTO_CIPHER_MODE_GCM) { + return qcrypto_nettle_aes_gcm_ctx_new(alg, key, nkey, errp); + } + switch (mode) { case QCRYPTO_CIPHER_MODE_ECB: case QCRYPTO_CIPHER_MODE_CBC:
diff --git a/crypto/cipher.c b/crypto/cipher.c index 229710f..1dc912b 100644 --- a/crypto/cipher.c +++ b/crypto/cipher.c
@@ -66,6 +66,7 @@ [QCRYPTO_CIPHER_MODE_CBC] = true, [QCRYPTO_CIPHER_MODE_XTS] = true, [QCRYPTO_CIPHER_MODE_CTR] = true, + [QCRYPTO_CIPHER_MODE_GCM] = true, }; @@ -204,6 +205,37 @@ } +int qcrypto_cipher_setaad(QCryptoCipher *cipher, + const uint8_t *aad, size_t len, + Error **errp) +{ + const QCryptoCipherDriver *drv = cipher->driver; + + if (!drv->cipher_setaad) { + error_setg(errp, "The cipher mode does not support associated data"); + return -1; + } + + return drv->cipher_setaad(cipher, aad, len, errp); +} + + +int qcrypto_cipher_gettag(QCryptoCipher *cipher, + uint8_t *tag, size_t len, + Error **errp) +{ + const QCryptoCipherDriver *drv = cipher->driver; + + if (!drv->cipher_gettag) { + error_setg(errp, + "The cipher mode does not produce an authentication tag"); + return -1; + } + + return drv->cipher_gettag(cipher, tag, len, errp); +} + + void qcrypto_cipher_free(QCryptoCipher *cipher) { if (cipher) {
diff --git a/crypto/cipherpriv.h b/crypto/cipherpriv.h index 64737ce..c4f995f 100644 --- a/crypto/cipherpriv.h +++ b/crypto/cipherpriv.h
@@ -34,6 +34,14 @@ const uint8_t *iv, size_t niv, Error **errp); + int (*cipher_setaad)(QCryptoCipher *cipher, + const uint8_t *aad, size_t len, + Error **errp); + + int (*cipher_gettag)(QCryptoCipher *cipher, + uint8_t *tag, size_t len, + Error **errp); + void (*cipher_free)(QCryptoCipher *cipher); };
diff --git a/disas/hexagon.c b/disas/hexagon.c index 36b8321..e2d3804 100644 --- a/disas/hexagon.c +++ b/disas/hexagon.c
@@ -31,7 +31,6 @@ int print_insn_hexagon(bfd_vma memaddr, struct disassemble_info *info) { - const HexagonCPUDef *hex_def = (const HexagonCPUDef *)info->target_info; uint32_t words[PACKET_WORDS_MAX]; bool found_end = false; GString *buf; @@ -58,8 +57,9 @@ return PACKET_WORDS_MAX * sizeof(uint32_t); } + const HexagonCPUConfig *cfg = info->target_info; buf = g_string_sized_new(PACKET_BUFFER_LEN); - len = disassemble_hexagon(words, i, memaddr, buf, hex_def); + len = disassemble_hexagon(words, i, memaddr, buf, cfg); (*info->fprintf_func)(info->stream, "%s", buf->str); g_string_free(buf, true);
diff --git a/docs/devel/hexagon-l2vic.rst b/docs/devel/hexagon-l2vic.rst new file mode 100644 index 0000000..9cb2a86 --- /dev/null +++ b/docs/devel/hexagon-l2vic.rst
@@ -0,0 +1,55 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Hexagon L2 Vectored Interrupt Controller +======================================== + + +.. code-block:: none + + +-------------+ +----------------------+ + | l2vic | | hexagon core | + | | | | + IRQ in ---->| | | | + IRQ in ---->| VID0 -|----------------->| irq2 | + ... ---->| | | | | + IRQ in ---->| | | v | + | ... | | <int steering> | + | | | / | | \ | + IRQ in ---->| | | t0 t1 t2 t3 ...| + IRQ in ---->| VIDN -| | | + ... ---->| | | | + IRQ in ---->| | | Global SREG File | + | | | | + | State | | | + | [ ] <--|==================|==> [ VID ] | + | [ ] <--|==================|==> [ VID1 ] | + | | | | + +-------------+ +----------------------+ + +L2VIC/Core Integration +---------------------- + +* hexagon core supports 8 external interrupt sources +* l2vic supports 1024 input interrupts mapped among 4 output interrupts +* l2vic has four output signals: { VID0, VID1, VID2, VID3 } +* l2vic device has a bank of registers per-VID that can be used to query + the status or assert new interrupts. +* Interrupts are 'steered' to threads based on { thread priority, 'EX' state, + thread interrupt mask, thread interrupt enable, global interrupt enable, + etc. }. +* Any hardware thread could conceivably handle any input interrupt, dependent + on state. +* The system register transfer instruction can read the VID0-VID3 values from + the l2vic when reading from hexagon core system registers "VID" and "VID1". +* When l2vic VID0 has multiple active interrupts, it pulses the VID0 output + IRQ and stores the IRQ number for the VID0 register field. Only after this + interrupt is cleared can the l2vic pulse the VID0 output IRQ again and provide + the next interrupt number on the VID0 register. +* The ``ciad`` instruction clears the l2vic input interrupt and un-disables the + core interrupt. If some/an l2vic VID0 interrupt is pending when this occurs, + the next interrupt should fire and any subsequent reads of the VID register + should reflect the newly raised interrupt. +* In QEMU, on an external interrupt or an unmasked-pending interrupt, + all vCPUs are triggered (has_work==true) and each will grab the IO lock + while considering the steering logic to determine whether they're the thread + that must handle the interrupt.
diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index a8f5e31..763cda1 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst
@@ -15,6 +15,7 @@ clocks ebpf_rss hexagon-sys + hexagon-l2vic migration/index multi-process reset
diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst index 9930974..cc42db9 100644 --- a/docs/system/arm/emulation.rst +++ b/docs/system/arm/emulation.rst
@@ -188,6 +188,7 @@ - FEAT_SME_I16I64 (16-bit to 64-bit integer widening outer product instructions) - FEAT_SME_LUTv2 (Lookup table instructions with 4-bit indices and 8-bit elements) - FEAT_SME_MOP4 (Quarter-tile outer product instructions) +- FEAT_SME_TMOP (Structured sparsity outer product instructions) - FEAT_SSVE_AES (Streaming SVE Mode Advanced Encryption Standard and 128-bit polynomial multiply long instructions) - FEAT_SSVE_FEXPA (Streaming FEXPA instruction) - FEAT_SSVE_FP8DOT2 (SVE2 FP8 2-way dot product to half-precision instructions in Streaming SVE mode)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 82e0bc2..260d2f0 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig
@@ -533,7 +533,9 @@ bool default y depends on TCG && ARM + imply GENERIC_LOADER imply PCI_DEVICES + imply E1000E_PCI_EXPRESS select DS1338 select FTGMAC100 select I2C @@ -551,14 +553,17 @@ select TMP105 select TMP421 select EMC141X + select OR_IRQ select UNIMP select LED select PMBUS select MAX31785 + select ADC128D818 select FSI_APB2OPB_ASPEED select AT24C - select PCI_EXPRESS select PCI_EXPRESS_ASPEED + select USB_EHCI_SYSBUS + select SDHCI config MPS2 bool @@ -725,3 +730,18 @@ select UNIMP select SSE_COUNTER select SSE_TIMER + +config AXIADO_SOC + bool + select ARM_GIC + select CADENCE # UART + select AXIADO_CLK + select CADENCE_GPIO + select AXIADO_SDHCI + select UNIMP + +config AXIADO_EVK + bool + default y + depends on TCG && ARM + select AXIADO_SOC
diff --git a/hw/arm/armsse.c b/hw/arm/armsse.c index ddb210c..55fbc2c 100644 --- a/hw/arm/armsse.c +++ b/hw/arm/armsse.c
@@ -409,7 +409,7 @@ .name = "s32kwatchdog", .type = TYPE_CMSDK_APB_WATCHDOG, .index = 0, - .addr = 0x4802e000, + .addr = 0x5802e000, .ppc = NO_PPC, .irq = NMI_0, .slowclk = true, @@ -452,7 +452,7 @@ .name = "CPU0CORE_PPU", .type = TYPE_UNIMPLEMENTED_DEVICE, .index = 2, - .addr = 0x50023000, + .addr = 0x58023000, .size = 0x1000, .ppc = NO_PPC, .irq = NO_IRQ, @@ -461,7 +461,7 @@ .name = "MGMT_PPU", .type = TYPE_UNIMPLEMENTED_DEVICE, .index = 3, - .addr = 0x50028000, + .addr = 0x58028000, .size = 0x1000, .ppc = NO_PPC, .irq = NO_IRQ, @@ -470,7 +470,7 @@ .name = "DEBUG_PPU", .type = TYPE_UNIMPLEMENTED_DEVICE, .index = 4, - .addr = 0x50029000, + .addr = 0x58029000, .size = 0x1000, .ppc = NO_PPC, .irq = NO_IRQ,
diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index a48c442..a9238e6 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c
@@ -327,7 +327,7 @@ int uart_first = aspeed_uart_first(sc->uarts_base); int uart_last = aspeed_uart_last(sc->uarts_base, sc->uarts_num); - if (sscanf(value, "uart%u", &val) != 1) { + if (sscanf(value, "uart%d", &val) != 1 || val < 0) { error_setg(errp, "Bad value for \"uart\" property"); return; }
diff --git a/hw/arm/aspeed_ast2600_anacapa.c b/hw/arm/aspeed_ast2600_anacapa.c index a1c8111..65d6b0f 100644 --- a/hw/arm/aspeed_ast2600_anacapa.c +++ b/hw/arm/aspeed_ast2600_anacapa.c
@@ -1,13 +1,14 @@ /* * Facebook Anacapa * - * Copyright (c) Meta Platforms, Inc. and affiliates. + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "qapi/error.h" +#include "hw/sensor/adc128d818.h" #include "hw/arm/machines-qom.h" #include "hw/arm/aspeed.h" #include "hw/arm/aspeed_soc.h" @@ -15,7 +16,6 @@ #include "hw/gpio/pca9552.h" #include "hw/nvram/eeprom_at24c.h" -/* Anacapa hardware value */ #define ANACAPA_BMC_HW_STRAP1 0x00002002 #define ANACAPA_BMC_HW_STRAP2 0x00000000 #define ANACAPA_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB) @@ -221,6 +221,17 @@ }; static const size_t hpm_brd_id_eeprom_len = sizeof(hpm_brd_id_eeprom); +static void anacapa_add_adc128d818(I2CBus *bus, uint8_t addr, + const char *description) +{ + DeviceState *dev = DEVICE(i2c_slave_new(TYPE_ADC128D818, addr)); + g_autofree char *childname = g_strdup_printf("0x%02x", addr); + + qdev_prop_set_string(dev, "description", description); + object_property_add_child(OBJECT(bus), childname, OBJECT(dev)); + i2c_slave_realize_and_unref(I2C_SLAVE(dev), bus, &error_fatal); +} + static void anacapa_bmc_i2c_init(AspeedMachineState *bmc) { /* Reference: aspeed-bmc-facebook-anacapa.dts */ @@ -242,7 +253,7 @@ /* &i2c1 */ /* eeprom@50 */ at24c_eeprom_init(i2c[1], 0x50, 256 * KiB); - /* i2c-mux@70 (PCA9546) — 4 channels, empty */ + /* i2c-mux@70 (PCA9546) - 4 channels, empty */ i2c_slave_create_simple(i2c[1], TYPE_PCA9546, 0x70); /* &i2c4 */ @@ -259,7 +270,8 @@ i2c_mux = i2c_slave_create_simple(i2c[8], TYPE_PCA9546, 0x72); /* i2c8mux ch0 */ - /* adc128d818@1f — no model */ + /* adc128d818@1f - R-PDB ADC (mode 1: 8 voltage channels) */ + anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 0), 0x1f, "i2c8:0:1f"); /* pca9555@22 */ i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0), TYPE_PCA9552, 0x22); @@ -305,7 +317,7 @@ /* i2c-mux@71 (PCA9548) */ i2c_mux = i2c_slave_create_simple(i2c[11], TYPE_PCA9548, 0x71); - /* i2c11mux ch0-ch4 — empty */ + /* i2c11mux ch0-ch4 - empty */ /* i2c11mux ch5 */ /* pca9555@22 */ @@ -320,7 +332,8 @@ i2c_mux = i2c_slave_create_simple(i2c[13], TYPE_PCA9548, 0x70); /* i2c13mux ch3 */ - /* adc128d818@1f - no model */ + /* adc128d818@1f - MB ADC (mode 1: 8 voltage channels) */ + anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 3), 0x1f, "i2c13:3:1f"); /* i2c13mux ch4 */ /* eeprom@51 */ @@ -328,7 +341,7 @@ hpm_brd_id_eeprom, hpm_brd_id_eeprom_len); /* i2c13mux ch7 */ - /* nfc@28 — no model */ + /* nfc@28 - no model */ } static void aspeed_machine_anacapa_class_init(ObjectClass *oc,
diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c index 65495a5..f714c9d 100644 --- a/hw/arm/aspeed_ast2600_catalina.c +++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -472,7 +472,16 @@ /* &i2c0 */ /* i2c-mux@71 (PCA9546) on i2c0 */ - i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x71); + i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x71); + + /* i2c0mux0ch0 */ + /* IOB0 NIC0 temperature-sensor@1f - tmp421 */ + i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0), + TYPE_TMP421, 0x1f); + /* i2c0mux0ch2 */ + /* IOB0 NIC1 temperature-sensor@1f - tmp421 */ + i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 2), + TYPE_TMP421, 0x1f); /* i2c-mux@72 (PCA9546) on i2c0 */ i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x72); @@ -489,7 +498,16 @@ i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x73); /* i2c-mux@75 (PCA9546) on i2c0 */ - i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x75); + i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x75); + + /* i2c0mux3ch0 */ + /* IOB1 NIC0 temperature-sensor@1f - tmp421 */ + i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0), + TYPE_TMP421, 0x1f); + /* i2c0mux3ch2 */ + /* IOB1 NIC1 temperature-sensor@1f - tmp421 */ + i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 2), + TYPE_TMP421, 0x1f); /* i2c-mux@76 (PCA9546) on i2c0 */ i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x76); @@ -533,7 +551,7 @@ TYPE_PCA9554, 0x27); /* io_expander6 - pca9555@25 */ i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 6), - TYPE_PCA9552, 0x25); + TYPE_PCA9555, 0x25); /* eeprom@51 */ at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, 6), 0x51, 8 * KiB, osfp_eeprom, osfp_eeprom_len); @@ -544,14 +562,16 @@ fio_eeprom, fio_eeprom_len); /* temperature-sensor@4b - tmp75 */ i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 7), TYPE_TMP75, 0x4b); + /* temperature-sensor@4f - tmp75 (FIO remote) */ + i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 7), TYPE_TMP75, 0x4f); /* &i2c2 */ /* io_expander0 - pca9555@20 */ - i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x20); + i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x20); /* io_expander0 - pca9555@21 */ - i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x21); + i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x21); /* io_expander0 - pca9555@27 */ - i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x27); + i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x27); /* eeprom@50 */ at24c_eeprom_init(i2c[2], 0x50, 8 * KiB); /* eeprom@51 */ @@ -564,21 +584,17 @@ /* eeprom@52 */ at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, 6), 0x52, 8 * KiB, hdd_eeprom, hdd_eeprom_len); - /* i2c5mux0ch7 */ - /* ina230@40 - no model */ - /* ina230@41 - no model */ - /* ina230@44 - no model */ - /* ina230@45 - no model */ + /* i2c5mux0ch7 - empty */ /* &i2c6 */ /* io_expander3 - pca9555@21 */ - i2c_slave_create_simple(i2c[6], TYPE_PCA9552, 0x21); + i2c_slave_create_simple(i2c[6], TYPE_PCA9555, 0x21); /* rtc@6f - nct3018y */ i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x6f); /* &i2c9 */ /* io_expander4 - pca9555@4f */ - i2c_slave_create_simple(i2c[9], TYPE_PCA9552, 0x4f); + i2c_slave_create_simple(i2c[9], TYPE_PCA9555, 0x4f); /* temperature-sensor@4b - tpm75 */ i2c_slave_create_simple(i2c[9], TYPE_TMP75, 0x4b); /* eeprom@50 */ @@ -615,17 +631,17 @@ /* &i2c14 */ /* io_expander9 - pca9555@10 */ - i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x10); + i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x10); /* io_expander10 - pca9555@11 */ - i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x11); + i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x11); /* io_expander11 - pca9555@12 */ - i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x12); + i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x12); /* io_expander12 - pca9555@13 */ - i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x13); + i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x13); /* io_expander13 - pca9555@14 */ - i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x14); + i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x14); /* io_expander14 - pca9555@15 */ - i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x15); + i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x15); /* &i2c15 */ /* temperature-sensor@1f - tmp421 */
diff --git a/hw/arm/aspeed_ast27x0-fc.c b/hw/arm/aspeed_ast27x0-fc.c index 7d9fade..058cea4 100644 --- a/hw/arm/aspeed_ast27x0-fc.c +++ b/hw/arm/aspeed_ast27x0-fc.c
@@ -134,11 +134,9 @@ return true; } -static bool ast2700fc_ssp_init(MachineState *machine, Error **errp) +static bool ast2700fc_ssp_init(Ast2700FCState *s, AspeedSoCState *psp, + Error **errp) { - Ast2700FCState *s = AST2700FC(machine); - AspeedSoCState *psp = ASPEED_SOC(&s->ca35); - s->ssp_sysclk = clock_new(OBJECT(s), "SSP_SYSCLK"); clock_set_hz(s->ssp_sysclk, 200000000ULL); @@ -158,7 +156,11 @@ object_property_set_link(OBJECT(&s->ssp), "sram", OBJECT(&psp->sram), &error_abort); object_property_set_link(OBJECT(&s->ssp), "scu", - OBJECT(&psp->scu), &error_abort); + OBJECT(&s->ca35.scu), &error_abort); + object_property_set_link(OBJECT(&s->ssp), "scuio", + OBJECT(&psp->scuio), &error_abort); + object_property_set_link(OBJECT(&s->ssp), "fmc", + OBJECT(&psp->fmc), &error_abort); if (!qdev_realize(DEVICE(&s->ssp), NULL, errp)) { return false; } @@ -166,11 +168,9 @@ return true; } -static bool ast2700fc_tsp_init(MachineState *machine, Error **errp) +static bool ast2700fc_tsp_init(Ast2700FCState *s, AspeedSoCState *psp, + Error **errp) { - Ast2700FCState *s = AST2700FC(machine); - AspeedSoCState *psp = ASPEED_SOC(&s->ca35); - s->tsp_sysclk = clock_new(OBJECT(s), "TSP_SYSCLK"); clock_set_hz(s->tsp_sysclk, 200000000ULL); @@ -190,7 +190,11 @@ object_property_set_link(OBJECT(&s->tsp), "sram", OBJECT(&psp->sram), &error_abort); object_property_set_link(OBJECT(&s->tsp), "scu", - OBJECT(&psp->scu), &error_abort); + OBJECT(&s->ca35.scu), &error_abort); + object_property_set_link(OBJECT(&s->tsp), "scuio", + OBJECT(&psp->scuio), &error_abort); + object_property_set_link(OBJECT(&s->tsp), "fmc", + OBJECT(&psp->fmc), &error_abort); if (!qdev_realize(DEVICE(&s->tsp), NULL, errp)) { return false; } @@ -200,9 +204,19 @@ static void ast2700fc_init(MachineState *machine) { + Ast2700FCState *s = AST2700FC(machine); + AspeedSoCState *psp; + ast2700fc_ca35_init(machine, &error_abort); - ast2700fc_ssp_init(machine, &error_abort); - ast2700fc_tsp_init(machine, &error_abort); + + /* + * SSP and TSP use resources owned by the PSP SoC, such as UART, + * SRAM, SCU and SCUIO. Therefore the PSP SoC must be realized + * before the coprocessors are initialized. + */ + psp = ASPEED_SOC(&s->ca35); + ast2700fc_ssp_init(s, psp, &error_abort); + ast2700fc_tsp_init(s, psp, &error_abort); } static void ast2700fc_class_init(ObjectClass *oc, const void *data)
diff --git a/hw/arm/aspeed_ast27x0-ssp.c b/hw/arm/aspeed_ast27x0-ssp.c index 68a8ab2..e036530 100644 --- a/hw/arm/aspeed_ast27x0-ssp.c +++ b/hw/arm/aspeed_ast27x0-ssp.c
@@ -27,6 +27,7 @@ [ASPEED_DEV_TIMER1] = 0x72C10000, [ASPEED_DEV_UART4] = 0x72C1A000, [ASPEED_DEV_IPC0] = 0x72C1C000, + [ASPEED_DEV_FMC] = 0x74000000, [ASPEED_DEV_PRIC1] = 0x74100000, [ASPEED_DEV_SCUIO] = 0x74C02000, [ASPEED_DEV_OTP] = 0x74C07000, @@ -142,8 +143,6 @@ TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "ipc1", &a->ipc[1], TYPE_UNIMPLEMENTED_DEVICE); - object_initialize_child(obj, "scuio", &a->scuio, - TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "pric0", &a->pric[0], TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "pric1", &a->pric[1], @@ -167,6 +166,24 @@ return; } + if (!a->scu) { + error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR + ": 'scu' link is not set"); + return; + } + + if (!a->scuio) { + error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR + ": 'scuio' link is not set"); + return; + } + + if (!a->fmc) { + error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR + ": 'fmc' link is not set"); + return; + } + /* AST27X0 SSP Core */ armv7m = DEVICE(&a->armv7m); qdev_prop_set_uint32(armv7m, "num-irq", 256); @@ -195,11 +212,18 @@ &s->sram_alias); /* SCU */ - memory_region_init_alias(&s->scu_alias, OBJECT(s), "scu.alias", - &s->scu->iomem, 0, - memory_region_size(&s->scu->iomem)); + memory_region_init_alias(&a->scu_alias, OBJECT(a), "scu.alias", + &a->scu->parent_obj.iomem, 0, + memory_region_size(&a->scu->parent_obj.iomem)); memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU], - &s->scu_alias); + &a->scu_alias); + + /* SCUIO */ + memory_region_init_alias(&a->scuio_alias, OBJECT(a), "scuio.alias", + &a->scuio->iomem, 0, + memory_region_size(&a->scuio->iomem)); + memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCUIO], + &a->scuio_alias); /* INTC */ if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) { @@ -252,6 +276,13 @@ sysbus_connect_irq(SYS_BUS_DEVICE(s->uart), 0, aspeed_soc_ast27x0ssp_get_irq(s, s->uart_dev)); + /* FMC */ + memory_region_init_alias(&a->fmc_alias, OBJECT(a), "fmc.alias", + &a->fmc->mmio, 0, + memory_region_size(&a->fmc->mmio)); + memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_FMC], + &a->fmc_alias); + aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->timerctrl), "aspeed.timerctrl", sc->memmap[ASPEED_DEV_TIMER1], 0x200); @@ -261,9 +292,6 @@ aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->ipc[1]), "aspeed.ipc1", sc->memmap[ASPEED_DEV_IPC1], 0x1000); - aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->scuio), - "aspeed.scuio", - sc->memmap[ASPEED_DEV_SCUIO], 0x1000); aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->pric[0]), "aspeed.pric0", sc->memmap[ASPEED_DEV_PRIC0], 0x1000); @@ -275,6 +303,15 @@ sc->memmap[ASPEED_DEV_OTP], 0x800); } +static const Property aspeed_27x0_coprocessor_properties[] = { + DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu, + TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *), + DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio, + TYPE_ASPEED_SCU, AspeedSCUState *), + DEFINE_PROP_LINK("fmc", Aspeed27x0CoprocessorState, fmc, TYPE_ASPEED_SMC, + AspeedSMCState *), +}; + static void aspeed_soc_ast27x0ssp_class_init(ObjectClass *klass, const void *data) { @@ -288,6 +325,7 @@ /* Reason: The Aspeed Coprocessor can only be instantiated from a board */ dc->user_creatable = false; dc->realize = aspeed_soc_ast27x0ssp_realize; + device_class_set_props(dc, aspeed_27x0_coprocessor_properties); sc->valid_cpu_types = valid_cpu_types; sc->irqmap = aspeed_soc_ast27x0ssp_irqmap;
diff --git a/hw/arm/aspeed_ast27x0-tsp.c b/hw/arm/aspeed_ast27x0-tsp.c index b8a4f7c..39ba062 100644 --- a/hw/arm/aspeed_ast27x0-tsp.c +++ b/hw/arm/aspeed_ast27x0-tsp.c
@@ -27,6 +27,7 @@ [ASPEED_DEV_TIMER1] = 0x72C10000, [ASPEED_DEV_UART4] = 0x72C1A000, [ASPEED_DEV_IPC0] = 0x72C1C000, + [ASPEED_DEV_FMC] = 0x74000000, [ASPEED_DEV_PRIC1] = 0x74100000, [ASPEED_DEV_SCUIO] = 0x74C02000, [ASPEED_DEV_OTP] = 0x74C07000, @@ -142,8 +143,6 @@ TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "ipc1", &a->ipc[1], TYPE_UNIMPLEMENTED_DEVICE); - object_initialize_child(obj, "scuio", &a->scuio, - TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "pric0", &a->pric[0], TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "pric1", &a->pric[1], @@ -167,6 +166,24 @@ return; } + if (!a->scu) { + error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR + ": 'scu' link is not set"); + return; + } + + if (!a->scuio) { + error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR + ": 'scuio' link is not set"); + return; + } + + if (!a->fmc) { + error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR + ": 'fmc' link is not set"); + return; + } + /* AST27X0 TSP Core */ armv7m = DEVICE(&a->armv7m); qdev_prop_set_uint32(armv7m, "num-irq", 256); @@ -195,11 +212,18 @@ &s->sram_alias); /* SCU */ - memory_region_init_alias(&s->scu_alias, OBJECT(s), "scu.alias", - &s->scu->iomem, 0, - memory_region_size(&s->scu->iomem)); + memory_region_init_alias(&a->scu_alias, OBJECT(a), "scu.alias", + &a->scu->parent_obj.iomem, 0, + memory_region_size(&a->scu->parent_obj.iomem)); memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU], - &s->scu_alias); + &a->scu_alias); + + /* SCUIO */ + memory_region_init_alias(&a->scuio_alias, OBJECT(a), "scuio.alias", + &a->scuio->iomem, 0, + memory_region_size(&a->scuio->iomem)); + memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCUIO], + &a->scuio_alias); /* INTC */ if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) { @@ -252,6 +276,13 @@ sysbus_connect_irq(SYS_BUS_DEVICE(s->uart), 0, aspeed_soc_ast27x0tsp_get_irq(s, s->uart_dev)); + /* FMC */ + memory_region_init_alias(&a->fmc_alias, OBJECT(a), "fmc.alias", + &a->fmc->mmio, 0, + memory_region_size(&a->fmc->mmio)); + memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_FMC], + &a->fmc_alias); + aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->timerctrl), "aspeed.timerctrl", sc->memmap[ASPEED_DEV_TIMER1], 0x200); @@ -261,9 +292,6 @@ aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->ipc[1]), "aspeed.ipc1", sc->memmap[ASPEED_DEV_IPC1], 0x1000); - aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->scuio), - "aspeed.scuio", - sc->memmap[ASPEED_DEV_SCUIO], 0x1000); aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->pric[0]), "aspeed.pric0", sc->memmap[ASPEED_DEV_PRIC0], 0x1000); @@ -275,6 +303,15 @@ sc->memmap[ASPEED_DEV_OTP], 0x800); } +static const Property aspeed_27x0_coprocessor_properties[] = { + DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu, + TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *), + DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio, + TYPE_ASPEED_SCU, AspeedSCUState *), + DEFINE_PROP_LINK("fmc", Aspeed27x0CoprocessorState, fmc, TYPE_ASPEED_SMC, + AspeedSMCState *), +}; + static void aspeed_soc_ast27x0tsp_class_init(ObjectClass *klass, const void *data) { @@ -288,6 +325,7 @@ /* Reason: The Aspeed Coprocessor can only be instantiated from a board */ dc->user_creatable = false; dc->realize = aspeed_soc_ast27x0tsp_realize; + device_class_set_props(dc, aspeed_27x0_coprocessor_properties); sc->valid_cpu_types = valid_cpu_types; sc->irqmap = aspeed_soc_ast27x0tsp_irqmap;
diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index dddd7d2..6365dbd 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c
@@ -435,12 +435,12 @@ object_initialize_child(obj, "gic", &a->gic, gicv3_class_name()); - object_initialize_child(obj, "scu", &s->scu, TYPE_ASPEED_2700_SCU); - qdev_prop_set_uint32(DEVICE(&s->scu), "silicon-rev", + object_initialize_child(obj, "scu", &a->scu, TYPE_ASPEED_2700_SCU); + qdev_prop_set_uint32(DEVICE(&a->scu), "silicon-rev", sc->silicon_rev); - object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu), + object_property_add_alias(obj, "hw-strap1", OBJECT(&a->scu), "hw-strap1"); - object_property_add_alias(obj, "hw-prot-key", OBJECT(&s->scu), + object_property_add_alias(obj, "hw-prot-key", OBJECT(&a->scu), "hw-prot-key"); object_initialize_child(obj, "scuio", &s->scuio, TYPE_ASPEED_2700_SCUIO); @@ -808,10 +808,10 @@ sc->memmap[ASPEED_DEV_VBOOTROM], &s->vbootrom); /* SCU */ - if (!sysbus_realize(SYS_BUS_DEVICE(&s->scu), errp)) { + if (!sysbus_realize(SYS_BUS_DEVICE(&a->scu), errp)) { return; } - aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->scu), 0, + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&a->scu), 0, sc->memmap[ASPEED_DEV_SCU]); /* SCU1 */ @@ -870,6 +870,11 @@ /* EHCI */ for (i = 0; i < sc->ehcis_num; i++) { + object_property_set_int(OBJECT(&s->ehci[i]), "ctrldssegment-default", + sc->memmap[ASPEED_DEV_SDRAM] >> 32, + &error_abort); + object_property_set_bool(OBJECT(&s->ehci[i]), "caps-64bit-addr", true, + &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->ehci[i]), errp)) { return; } @@ -929,7 +934,7 @@ AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(&s->wdt[i]); hwaddr wdt_offset = sc->memmap[ASPEED_DEV_WDT] + i * awc->iosize; - object_property_set_link(OBJECT(&s->wdt[i]), "scu", OBJECT(&s->scu), + object_property_set_link(OBJECT(&s->wdt[i]), "scu", OBJECT(&a->scu), &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) { return; @@ -1032,7 +1037,7 @@ aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_EMMC)); /* Timer */ - object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&s->scu), + object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&a->scu), &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->timerctrl), errp)) { return;
diff --git a/hw/arm/aspeed_coprocessor_common.c b/hw/arm/aspeed_coprocessor_common.c index a0a4c73..43026d2 100644 --- a/hw/arm/aspeed_coprocessor_common.c +++ b/hw/arm/aspeed_coprocessor_common.c
@@ -27,8 +27,6 @@ TYPE_MEMORY_REGION, MemoryRegion *), DEFINE_PROP_LINK("sram", AspeedCoprocessorState, sram, TYPE_MEMORY_REGION, MemoryRegion *), - DEFINE_PROP_LINK("scu", AspeedCoprocessorState, scu, TYPE_ASPEED_SCU, - AspeedSCUState *), DEFINE_PROP_LINK("uart", AspeedCoprocessorState, uart, TYPE_SERIAL_MM, SerialMM *), DEFINE_PROP_INT32("uart-dev", AspeedCoprocessorState, uart_dev, 0),
diff --git a/hw/arm/ax3000-boards.c b/hw/arm/ax3000-boards.c new file mode 100644 index 0000000..4b8deec --- /dev/null +++ b/hw/arm/ax3000-boards.c
@@ -0,0 +1,56 @@ +/* + * Axiado Boards + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/arm/ax3000-boards.h" +#include "hw/arm/boot.h" +#include "hw/arm/machines-qom.h" +#include "qemu/error-report.h" +#include "qom/object.h" + +static struct arm_boot_info ax3000_binfo = { + .loader_start = AX3000_DRAM0_BASE, + .board_id = -1, +}; + +static void ax3000_machine_init(MachineState *machine) +{ + Ax3000MachineState *ams = AX3000_MACHINE(machine); + + ams->soc = AX3000_SOC(object_new(TYPE_AX3000_SOC)); + object_property_add_child(OBJECT(machine), "soc", OBJECT(ams->soc)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(ams->soc), &error_fatal); + + ax3000_binfo.ram_size = machine->ram_size; + arm_load_kernel(&ams->soc->cpu[0], machine, &ax3000_binfo); +} + +static void ax3000_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->init = ax3000_machine_init; + mc->default_cpus = AX3000_NUM_CPUS; + mc->min_cpus = AX3000_NUM_CPUS; + mc->max_cpus = AX3000_NUM_CPUS; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a53"); +} + +static const TypeInfo ax3000_machine_types[] = { + { + .name = TYPE_AX3000_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(Ax3000MachineState), + .class_size = sizeof(Ax3000MachineClass), + .class_init = ax3000_machine_class_init, + .interfaces = aarch64_machine_interfaces, + .abstract = true, + } +}; + +DEFINE_TYPES(ax3000_machine_types)
diff --git a/hw/arm/ax3000-evk.c b/hw/arm/ax3000-evk.c new file mode 100644 index 0000000..a170848 --- /dev/null +++ b/hw/arm/ax3000-evk.c
@@ -0,0 +1,27 @@ +/* + * Axiado Evaluation Kit Emulation + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/arm/ax3000-boards.h" + +static void axiado_scm3003_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "Axiado SCM3003 EVK Board"; +} + +static const TypeInfo ax3000_evk_types[] = { + { + .name = MACHINE_TYPE_NAME("axiado-scm3003"), + .parent = TYPE_AX3000_MACHINE, + .class_init = axiado_scm3003_class_init, + } +}; + +DEFINE_TYPES(ax3000_evk_types)
diff --git a/hw/arm/ax3000-soc.c b/hw/arm/ax3000-soc.c new file mode 100644 index 0000000..71e31c6 --- /dev/null +++ b/hw/arm/ax3000-soc.c
@@ -0,0 +1,242 @@ +/* + * Axiado SoC AX3000 + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "system/address-spaces.h" +#include "hw/arm/bsa.h" +#include "hw/arm/ax3000-soc.h" +#include "hw/misc/unimp.h" +#include "system/system.h" +#include "qobject/qlist.h" +#include "qom/object.h" +#include "hw/core/boards.h" + +static void ax3000_init(Object *obj) +{ + Ax3000SoCState *s = AX3000_SOC(obj); + Ax3000SoCClass *sc = AX3000_SOC_GET_CLASS(s); + + for (int i = 0; i < sc->num_cpus; i++) { + g_autofree char *name = g_strdup_printf("cpu%d", i); + object_initialize_child(obj, name, &s->cpu[i], + ARM_CPU_TYPE_NAME("cortex-a53")); + } + + object_initialize_child(obj, "gic", &s->gic, gicv3_class_name()); + + for (int i = 0; i < AX3000_NUM_UARTS; i++) { + g_autofree char *name = g_strdup_printf("uart%d", i); + object_initialize_child(obj, name, &s->uart[i], TYPE_CADENCE_UART); + } + + object_initialize_child(obj, "clk", &s->ax3000_clk, TYPE_AX3000_CLK); + object_initialize_child(obj, "sdhci0", &s->sdhci0, TYPE_AXIADO_SDHCI); + + for (int i = 0; i < AX3000_NUM_GPIOS; i++) { + g_autofree char *name = g_strdup_printf("gpio%d", i); + object_initialize_child(obj, name, &s->gpio[i], TYPE_CADENCE_GPIO); + } +} + +static void ax3000_realize(DeviceState *dev, Error **errp) +{ + Ax3000SoCState *s = AX3000_SOC(dev); + Ax3000SoCClass *sc = AX3000_SOC_GET_CLASS(s); + SysBusDevice *gic_sbd = SYS_BUS_DEVICE(&s->gic); + DeviceState *gic_dev = DEVICE(&s->gic); + QList *redist_region_count; + SysBusDevice *sdhci0_sbd; + DeviceState *card; + DriveInfo *dinfo; + + /* CPUs */ + for (int i = 0; i < sc->num_cpus; i++) { + object_property_set_int(OBJECT(&s->cpu[i]), "cntfrq", 8000000, + &error_abort); + + if (object_property_find(OBJECT(&s->cpu[i]), "has_el3")) { + object_property_set_bool(OBJECT(&s->cpu[i]), "has_el3", + false, &error_abort); + } + + if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) { + return; + } + } + + /* GIC */ + qdev_prop_set_uint32(gic_dev, "num-cpu", sc->num_cpus); + qdev_prop_set_uint32(gic_dev, "num-irq", + AX3000_NUM_IRQS + GIC_INTERNAL); + + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, sc->num_cpus); + qdev_prop_set_array(gic_dev, "redist-region-count", redist_region_count); + + if (!sysbus_realize(gic_sbd, errp)) { + return; + } + + sysbus_mmio_map(gic_sbd, 0, AX3000_GIC_DIST_BASE); + sysbus_mmio_map(gic_sbd, 1, AX3000_GIC_REDIST_BASE); + + /* + * Mapping from the output timer irq lines from the CPU to the + * GIC PPI inputs. + */ + const int timer_irqs[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ + }; + + /* + * Wire the outputs from each CPU's generic timer and the GICv3 + * maintenance interrupt signal to the appropriate GIC PPI inputs, and + * the GIC's IRQ/FIQ interrupt outputs to the CPU's inputs. + */ + for (int i = 0; i < sc->num_cpus; i++) { + DeviceState *cpu_dev = DEVICE(&s->cpu[i]); + int intidbase = AX3000_NUM_IRQS + i * GIC_INTERNAL; + qemu_irq irq; + + for (int j = 0; j < ARRAY_SIZE(timer_irqs); j++) { + irq = qdev_get_gpio_in(gic_dev, intidbase + timer_irqs[j]); + qdev_connect_gpio_out(cpu_dev, j, irq); + } + + irq = qdev_get_gpio_in(gic_dev, intidbase + ARCH_GIC_MAINT_IRQ); + qdev_connect_gpio_out_named(cpu_dev, "gicv3-maintenance-interrupt", + 0, irq); + + sysbus_connect_irq(gic_sbd, i, + qdev_get_gpio_in(cpu_dev, ARM_CPU_IRQ)); + sysbus_connect_irq(gic_sbd, i + sc->num_cpus, + qdev_get_gpio_in(cpu_dev, ARM_CPU_FIQ)); + sysbus_connect_irq(gic_sbd, i + 2 * sc->num_cpus, + qdev_get_gpio_in(cpu_dev, ARM_CPU_VIRQ)); + sysbus_connect_irq(gic_sbd, i + 3 * sc->num_cpus, + qdev_get_gpio_in(cpu_dev, ARM_CPU_VFIQ)); + } + + /* DRAM */ + const struct { + hwaddr addr; + size_t size; + const char *name; + } dram_table[] = { + { AX3000_DRAM0_BASE, AX3000_DRAM0_SIZE, "dram0" }, + { AX3000_DRAM1_BASE, AX3000_DRAM1_SIZE, "dram1" } + }; + + for (int i = 0; i < AX3000_NUM_BANKS; i++) { + memory_region_init_ram(&s->dram[i], OBJECT(s), dram_table[i].name, + dram_table[i].size, &error_fatal); + memory_region_add_subregion(get_system_memory(), dram_table[i].addr, + &s->dram[i]); + } + + /* UARTs */ + const struct { + hwaddr addr; + unsigned int irq; + } serial_table[] = { + { AX3000_UART0_BASE, AX3000_UART0_IRQ }, + { AX3000_UART1_BASE, AX3000_UART1_IRQ }, + { AX3000_UART2_BASE, AX3000_UART2_IRQ }, + { AX3000_UART3_BASE, AX3000_UART3_IRQ } + }; + + for (int i = 0; i < AX3000_NUM_UARTS; i++) { + qdev_prop_set_chr(DEVICE(&s->uart[i]), "chardev", serial_hd(i)); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, serial_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, + qdev_get_gpio_in(gic_dev, serial_table[i].irq)); + } + + /* Timer control */ + create_unimplemented_device("ax3000.timerctrl", AX3000_TIMER_CTRL, 32); + + /* Clock control */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ax3000_clk), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ax3000_clk), 0, AX3000_PLL_BASE); + + /* SDHCI */ + sdhci0_sbd = SYS_BUS_DEVICE(&s->sdhci0); + if (!sysbus_realize(sdhci0_sbd, errp)) { + return; + } + + sysbus_mmio_map(sdhci0_sbd, 0, AX3000_SDHCI0_BASE); + sysbus_mmio_map(sdhci0_sbd, 1, AX3000_EMMC_PHY_BASE); + sysbus_connect_irq(sdhci0_sbd, 0, + qdev_get_gpio_in(gic_dev, AX3000_SDHCI0_IRQ)); + + dinfo = drive_get(IF_SD, 0, 0); + if (dinfo) { + card = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(card, "drive", + blk_by_legacy_dinfo(dinfo), + &error_fatal); + qdev_realize_and_unref(card, s->sdhci0.sd_bus, &error_fatal); + } + + /* GPIOs */ + const struct { + hwaddr addr; + unsigned int irq; + } gpio_table[] = { + { AX3000_GPIO0_BASE, AX3000_GPIO0_IRQ }, + { AX3000_GPIO1_BASE, AX3000_GPIO1_IRQ }, + { AX3000_GPIO2_BASE, AX3000_GPIO2_IRQ }, + { AX3000_GPIO3_BASE, AX3000_GPIO3_IRQ }, + { AX3000_GPIO4_BASE, AX3000_GPIO4_IRQ }, + { AX3000_GPIO5_BASE, AX3000_GPIO5_IRQ }, + { AX3000_GPIO6_BASE, AX3000_GPIO6_IRQ }, + { AX3000_GPIO7_BASE, AX3000_GPIO7_IRQ } + }; + + for (int i = 0; i < AX3000_NUM_GPIOS; i++) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0, gpio_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0, + qdev_get_gpio_in(gic_dev, gpio_table[i].irq)); + } +} + +static void ax3000_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + Ax3000SoCClass *sc = AX3000_SOC_CLASS(oc); + + dc->desc = "Axiado SoC AX3000"; + dc->realize = ax3000_realize; + sc->num_cpus = AX3000_NUM_CPUS; +} + +static const TypeInfo axiado_soc_types[] = { + { + .name = TYPE_AX3000_SOC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Ax3000SoCState), + .instance_init = ax3000_init, + .class_init = ax3000_class_init, + } +}; + +DEFINE_TYPES(axiado_soc_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 4233a80..8ee5307 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build
@@ -110,6 +110,12 @@ arm_common_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c')) arm_common_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c')) +arm_common_ss.add(when: ['CONFIG_AXIADO_SOC', 'TARGET_AARCH64'], if_true: files( + 'ax3000-soc.c')) +arm_common_ss.add(when: ['CONFIG_AXIADO_EVK', 'TARGET_AARCH64'], if_true: files( + 'ax3000-boards.c', + 'ax3000-evk.c')) + arm_common_ss.add(files('boot.c')) hw_common_arch += {'arm': arm_common_ss}
diff --git a/hw/arm/mps2-tz.c b/hw/arm/mps2-tz.c index f101c1b..d6d1786 100644 --- a/hw/arm/mps2-tz.c +++ b/hw/arm/mps2-tz.c
@@ -1048,6 +1048,19 @@ const PPCInfo an547_ppcs[] = { { .name = "apb_ppcexp0", .ports = { + { /* port 0 USER MEM APB0 */ }, + { /* port 1 USER MEM APB0 */ }, + { /* port 2 reserved */ }, + { /* port 3 reserved */ }, + { /* port 4 NPU APB0 */ }, + { /* port 5 NPU APB1 */ }, + { /* port 6 reserved */ }, + { /* port 7 reserved */ }, + { /* port 8 reserved */ }, + { /* port 9 reserved */ }, + { /* port 10 reserved */ }, + { /* port 11 reserved */ }, + { /* port 12 reserved */ }, { "ssram-mpc", make_mpc, &mms->mpc[0], 0x57000000, 0x1000 }, { "qspi-mpc", make_mpc, &mms->mpc[1], 0x57001000, 0x1000 }, { "ddr-mpc", make_mpc, &mms->mpc[2], 0x57002000, 0x1000 }, @@ -1100,6 +1113,14 @@ { /* port 7 USER AHB interface 3 */ }, { "eth-usb", make_eth_usb, NULL, 0x41400000, 0x200000, { 49 } }, }, + }, { + .name = "ahb_ppcexp1", + .ports = { + { /* port 0 reserved */ }, + { "dma1", make_dma, &mms->dma[1], 0x41201000, 0x1000, { 62, 60, 61 } }, + { "dma2", make_dma, &mms->dma[2], 0x41202000, 0x1000, { 65, 63, 64 } }, + { "dma3", make_dma, &mms->dma[3], 0x41203000, 0x1000, { 68, 66, 67 } }, + }, }, };
diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c index 06aeb8d..e1595a8 100644 --- a/hw/arm/raspi4b.c +++ b/hw/arm/raspi4b.c
@@ -85,7 +85,7 @@ ram_size = board_ram_size(info->board_id); - if (info->ram_size > UPPER_RAM_BASE) { + if (ram_size > UPPER_RAM_BASE) { raspi_add_memory_node(fdt, UPPER_RAM_BASE, ram_size - UPPER_RAM_BASE); } }
diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 219597c..e7a56e3 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c
@@ -4441,10 +4441,17 @@ } type_init(machvirt_machine_init); -static void virt_machine_11_1_options(MachineClass *mc) +static void virt_machine_11_2_options(MachineClass *mc) { } -DEFINE_VIRT_MACHINE_AS_LATEST(11, 1) +DEFINE_VIRT_MACHINE_AS_LATEST(11, 2) + +static void virt_machine_11_1_options(MachineClass *mc) +{ + virt_machine_11_2_options(mc); + compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len); +} +DEFINE_VIRT_MACHINE(11, 1) static void virt_machine_11_0_options(MachineClass *mc) {
diff --git a/hw/char/sclpconsole-lm.c b/hw/char/sclpconsole-lm.c index 9a16896..f6ed282 100644 --- a/hw/char/sclpconsole-lm.c +++ b/hw/char/sclpconsole-lm.c
@@ -243,7 +243,8 @@ SCLPConsoleLM *scon = SCLPLM_CONSOLE(event); len = be16_to_cpu(data->mdb.header.length); - if (len < sizeof(data->mdb.header)) { + if (len < sizeof(data->mdb.header) || + len > be16_to_cpu(data->header.length) - sizeof(EventBufferHeader)) { return SCLP_RC_INCONSISTENT_LENGTHS; } len -= sizeof(data->mdb.header);
diff --git a/hw/core/machine.c b/hw/core/machine.c index 73b4d82..4e55119 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c
@@ -40,6 +40,9 @@ #include "qemu/audio.h" #include "hw/arm/smmuv3.h" +GlobalProperty hw_compat_11_1[] = {}; +const size_t hw_compat_11_1_len = G_N_ELEMENTS(hw_compat_11_1); + GlobalProperty hw_compat_11_0[] = { { "virtio-mmio", VIRTIO_QUEUE_SIZE_OVERRIDE, "1024" }, { "chardev-vc", "encoding", "cp437" }, @@ -51,6 +54,8 @@ { TYPE_ARM_SMMUV3, "ssidsize", "0" }, { TYPE_ARM_SMMUV3, "oas", "44" }, { "migration", "switchover-ack-legacy", "on" }, + { "sysbus-ehci-usb", "x-migrate-fetch-addr-64bit", "off" }, + { "pci-ehci-usb", "x-migrate-fetch-addr-64bit", "off" }, }; const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig index a209294..fcc7c70 100644 --- a/hw/gpio/Kconfig +++ b/hw/gpio/Kconfig
@@ -30,3 +30,6 @@ config ZAURUS_SCOOP bool + +config CADENCE_GPIO + bool
diff --git a/hw/gpio/cadence_gpio.c b/hw/gpio/cadence_gpio.c new file mode 100644 index 0000000..ce8256b --- /dev/null +++ b/hw/gpio/cadence_gpio.c
@@ -0,0 +1,292 @@ +/* + * Cadence GPIO emulation. + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/gpio/cadence_gpio.h" +#include "hw/core/irq.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "trace.h" + +static void cdns_gpio_update_irq(CadenceGPIOState *s) +{ + qemu_set_irq(s->irq, s->isr ? 1 : 0); +} + +static void cdns_gpio_update_isr_with_inpvr(CadenceGPIOState *s, uint32_t new) +{ + uint32_t new_isr = 0; + uint32_t any_edges, rising_edges, falling_edges, deassert_mask; + + /* + * If ITR is set, this is level triggered: + * set corresponding ISR bits when IVR matches new inpvr value. + */ + new_isr |= s->itr & ~(s->ivr ^ new); + + /* + * If ITR is not set, this is edge-triggered: + * If IOAR bit is set, trigger on any edge; + * otherwise trigger on rising edge if IVR is set, + * trigger on falling edge if IVR bit is 0. + */ + any_edges = s->ioar & (s->inpvr ^ new); + rising_edges = s->ivr & ~s->inpvr & new; + falling_edges = ~s->ivr & s->inpvr & ~new; + new_isr |= ~s->itr & (any_edges | rising_edges | falling_edges); + + /* + * In bypass mode or if this isn't an input pin, the corresponding ISR + * bit is forced to zero. + */ + deassert_mask = s->bmr | ~s->dmr | s->imr; + + new_isr &= ~deassert_mask; + s->isr = new_isr; + + cdns_gpio_update_irq(s); +} + +static void cdns_gpio_update_isr(CadenceGPIOState *s) +{ + cdns_gpio_update_isr_with_inpvr(s, s->inpvr); +} + +static void cdns_gpio_set(void *opaque, int line, int level) +{ + CadenceGPIOState *s = CADENCE_GPIO(opaque); + uint32_t new_inpvr = deposit32(s->inpvr, line, 1, level ? 1 : 0); + + trace_cdns_gpio_set(DEVICE(s)->canonical_path, line, level); + + cdns_gpio_update_isr_with_inpvr(s, new_inpvr); + + /* Sync INPVR with new value */ + s->inpvr = new_inpvr; +} + +static inline void cdns_gpio_update_output_irq(CadenceGPIOState *s) +{ + uint32_t is_output = ~s->bmr & ~s->dmr & s->oer; + + for (int i = 0; i < CDNS_GPIO_NUM; i++) { + if (extract32(is_output, i, 1)) { + /* Forward the output value to corresponding irq */ + qemu_set_irq(s->output[i], extract32(s->ovr, i, 1)); + } + } +} + +static uint64_t cdns_gpio_read(void *opaque, hwaddr offset, unsigned size) +{ + CadenceGPIOState *s = CADENCE_GPIO(opaque); + uint32_t reg_value = 0x0; + + switch (offset) { + case CDNS_GPIO_BYPASS_MODE: + reg_value = s->bmr; + break; + + case CDNS_GPIO_DIRECTION_MODE: + reg_value = s->dmr; + break; + + case CDNS_GPIO_OUTPUT_EN: + reg_value = s->oer; + break; + + case CDNS_GPIO_OUTPUT_VALUE: + reg_value = s->ovr; + break; + + case CDNS_GPIO_INPUT_VALUE: + reg_value = s->inpvr; + break; + + case CDNS_GPIO_IRQ_MASK: + reg_value = s->imr; + break; + + case CDNS_GPIO_IRQ_STATUS: + reg_value = s->isr; + break; + + case CDNS_GPIO_IRQ_TYPE: + reg_value = s->itr; + break; + + case CDNS_GPIO_IRQ_VALUE: + reg_value = s->ivr; + break; + + case CDNS_GPIO_IRQ_ANY_EDGE: + reg_value = s->ioar; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset); + break; + } + + trace_cdns_gpio_read(DEVICE(s)->canonical_path, offset, reg_value); + + return reg_value; +} + +static void cdns_gpio_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + CadenceGPIOState *s = CADENCE_GPIO(opaque); + + trace_cdns_gpio_write(DEVICE(s)->canonical_path, offset, value); + + switch (offset) { + case CDNS_GPIO_BYPASS_MODE: + s->bmr = value; + cdns_gpio_update_output_irq(s); + cdns_gpio_update_isr(s); + break; + + case CDNS_GPIO_DIRECTION_MODE: + s->dmr = value; + cdns_gpio_update_output_irq(s); + cdns_gpio_update_isr(s); + break; + + case CDNS_GPIO_OUTPUT_EN: + s->oer = value; + cdns_gpio_update_output_irq(s); + break; + + case CDNS_GPIO_OUTPUT_VALUE: + s->ovr = value; + cdns_gpio_update_output_irq(s); + break; + + case CDNS_GPIO_IRQ_EN: + s->imr &= ~value; + cdns_gpio_update_isr(s); + break; + + case CDNS_GPIO_IRQ_DIS: + s->imr |= value; + cdns_gpio_update_isr(s); + break; + + case CDNS_GPIO_IRQ_TYPE: + s->itr = value; + break; + + case CDNS_GPIO_IRQ_VALUE: + s->ivr = value; + break; + + case CDNS_GPIO_IRQ_ANY_EDGE: + s->ioar = value; + break; + + case CDNS_GPIO_INPUT_VALUE: + case CDNS_GPIO_IRQ_MASK: + case CDNS_GPIO_IRQ_STATUS: + /* Read-Only */ + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset); + break; + } +} + +static const MemoryRegionOps cdns_gpio_ops = { + .read = cdns_gpio_read, + .write = cdns_gpio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +static const VMStateDescription vmstate_cdns_gpio = { + .name = TYPE_CADENCE_GPIO, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(bmr, CadenceGPIOState), + VMSTATE_UINT32(dmr, CadenceGPIOState), + VMSTATE_UINT32(oer, CadenceGPIOState), + VMSTATE_UINT32(ovr, CadenceGPIOState), + VMSTATE_UINT32(inpvr, CadenceGPIOState), + VMSTATE_UINT32(imr, CadenceGPIOState), + VMSTATE_UINT32(isr, CadenceGPIOState), + VMSTATE_UINT32(itr, CadenceGPIOState), + VMSTATE_UINT32(ivr, CadenceGPIOState), + VMSTATE_UINT32(ioar, CadenceGPIOState), + VMSTATE_END_OF_LIST() + } +}; + +static void cdns_gpio_reset(DeviceState *dev) +{ + CadenceGPIOState *s = CADENCE_GPIO(dev); + + s->bmr = 0; + s->dmr = 0; + s->oer = 0; + s->ovr = 0; + s->inpvr = 0; + s->imr = 0xffffffff; + s->isr = 0; + s->itr = 0; + s->ivr = 0; + s->ioar = 0; +} + +static void cdns_gpio_init(Object *obj) +{ + CadenceGPIOState *s = CADENCE_GPIO(obj); + + memory_region_init_io(&s->iomem, obj, &cdns_gpio_ops, s, + TYPE_CADENCE_GPIO, CDNS_GPIO_REG_SIZE); + + qdev_init_gpio_in(DEVICE(s), cdns_gpio_set, CDNS_GPIO_NUM); + qdev_init_gpio_out(DEVICE(s), s->output, CDNS_GPIO_NUM); + + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static void cdns_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, cdns_gpio_reset); + dc->vmsd = &vmstate_cdns_gpio; + dc->desc = "Cadence GPIO controller"; +} + +static const TypeInfo cdns_gpio_info = { + .name = TYPE_CADENCE_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(CadenceGPIOState), + .instance_init = cdns_gpio_init, + .class_init = cdns_gpio_class_init, +}; + +static void cdns_gpio_register_types(void) +{ + type_register_static(&cdns_gpio_info); +} + +type_init(cdns_gpio_register_types)
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build index 6a67ee9..0555f44 100644 --- a/hw/gpio/meson.build +++ b/hw/gpio/meson.build
@@ -19,3 +19,4 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sgpio.c')) system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c')) system_ss.add(when: 'CONFIG_PCF8574', if_true: files('pcf8574.c')) +system_ss.add(when: 'CONFIG_CADENCE_GPIO', if_true: files('cadence_gpio.c'))
diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c index b13ac9f..719149b 100644 --- a/hw/gpio/pca9552.c +++ b/hw/gpio/pca9552.c
@@ -1,7 +1,10 @@ /* - * PCA9552 I2C LED blinker + * PCA955X I2C LED blinker and I/O expanders * * https://www.nxp.com/docs/en/application-note/AN264.pdf + * https://www.nxp.com/docs/en/data-sheet/PCA9552.pdf + * https://www.nxp.com/docs/en/data-sheet/PCA9555.pdf + * https://www.nxp.com/docs/en/data-sheet/PCA9535_PCA9535C.pdf * * Copyright (c) 2017-2018, IBM Corporation. * Copyright (c) 2020 Philippe Mathieu-Daudé @@ -12,9 +15,9 @@ #include "qemu/osdep.h" #include "qemu/log.h" -#include "qemu/module.h" #include "qemu/bitops.h" #include "hw/core/qdev-properties.h" +#include "hw/i2c/i2c.h" #include "hw/gpio/pca9552.h" #include "hw/gpio/pca9552_regs.h" #include "hw/core/irq.h" @@ -24,6 +27,25 @@ #include "trace.h" #include "qom/object.h" +#define PCA955X_NR_REGS 10 +#define PCA955X_PIN_COUNT_MAX 16 + +OBJECT_DECLARE_TYPE(PCA955xState, PCA955xClass, PCA955X) + +struct PCA955xState { + /*< private >*/ + I2CSlave parent_obj; + /*< public >*/ + + uint8_t len; + uint8_t pointer; + + uint8_t regs[PCA955X_NR_REGS]; + qemu_irq gpio_out[PCA955X_PIN_COUNT_MAX]; + uint8_t ext_state[PCA955X_PIN_COUNT_MAX]; + char *description; /* For debugging purpose only */ +}; + struct PCA955xClass { /*< private >*/ I2CSlaveClass parent_class; @@ -33,10 +55,7 @@ uint8_t max_reg; bool has_led_support; }; -typedef struct PCA955xClass PCA955xClass; -DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X, - TYPE_PCA955X) /* * Note: The LED_ON and LED_OFF configuration values for the PCA955X * chips are the reverse of the PCA953X family of chips. @@ -49,6 +68,7 @@ #define PCA9552_PIN_HIZ 0x1 static const char *led_state[] = {"on", "off", "pwm0", "pwm1"}; +static const char *pin_state[] = {"low", "high"}; static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin) { @@ -148,9 +168,12 @@ /* PCA9535: Simple GPIO behavior */ uint8_t config_reg = PCA9535_CONFIG0 + (i / 8); uint8_t output_reg = PCA9535_OUTPUT0 + (i / 8); - uint8_t polarity_reg = PCA9535_POLARITY0 + (i / 8); - /* Check if pin is configured as input */ + /* + * The input register holds the raw pin logic level; the + * polarity inversion register is only applied when the input + * port is read (see pca955x_read()). + */ if (s->regs[config_reg] & bit_mask) { /* Input mode - reflect external state */ if (s->ext_state[i] == PCA9552_PIN_LOW) { @@ -160,12 +183,8 @@ } } else { /* Output mode - reflect output register value */ - uint8_t output_bit = s->regs[output_reg] & bit_mask; - uint8_t polarity_bit = s->regs[polarity_reg] & bit_mask; - - /* Apply polarity inversion if set */ s->regs[input_reg] = (s->regs[input_reg] & ~bit_mask) | - ((output_bit ^ polarity_bit) & bit_mask); + (s->regs[output_reg] & bit_mask); } } @@ -187,6 +206,18 @@ return 0xFF; } + /* + * On the GPIO variants, reading an input port returns the raw pin + * levels XORed with the polarity inversion register, as specified by + * the datasheet. + */ + if (!k->has_led_support && + (reg == PCA9535_INPUT0 || reg == PCA9535_INPUT1)) { + uint8_t polarity_reg = PCA9535_POLARITY0 + (reg - PCA9535_INPUT0); + + return s->regs[reg] ^ s->regs[polarity_reg]; + } + return s->regs[reg]; } @@ -229,14 +260,26 @@ } /* - * When Auto-Increment is on, the register address is incremented - * after each byte is sent to or received by the device. The index - * rollovers to 0 when the maximum register address is reached. + * Advance the command pointer after each byte sent to or received from the + * device. + * + * The LED variant auto-increments only when the AI bit (bit 4) is set in the + * command byte, rolling over to 0 once the maximum register address is + * reached. + * + * The GPIO variants auto-increment on every access, toggling bit 0 so the + * pointer stays within the addressed register pair + * (input/output/polarity/config), as specified by their datasheet. */ static void pca955x_autoinc(PCA955xState *s) { PCA955xClass *k = PCA955X_GET_CLASS(s); + if (!k->has_led_support) { + s->pointer ^= 0x1; + return; + } + if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) { uint8_t reg = s->pointer & 0xf; @@ -245,12 +288,25 @@ } } +/* + * The LED variant addresses its registers with a 4-bit command field, while + * the GPIO variants only decode 3 bits (the command wraps into the 8-register + * window). + */ +static inline uint8_t pca955x_cmd_reg(PCA955xState *s) +{ + PCA955xClass *k = PCA955X_GET_CLASS(s); + + return s->pointer & (k->has_led_support ? 0xf : 0x7); +} + static uint8_t pca955x_recv(I2CSlave *i2c) { PCA955xState *s = PCA955X(i2c); + PCA955xClass *k = PCA955X_GET_CLASS(s); uint8_t ret; - ret = pca955x_read(s, s->pointer & 0xf); + ret = pca955x_read(s, pca955x_cmd_reg(s)); /* * From the Specs: @@ -262,7 +318,7 @@ * I don't know what should be done in this case, so throw an * error. */ - if (s->pointer == PCA9552_AUTOINC) { + if (k->has_led_support && s->pointer == PCA9552_AUTOINC) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Autoincrement read starting with register 0\n", __func__); @@ -282,7 +338,7 @@ s->pointer = data; s->len++; } else { - pca955x_write(s, s->pointer & 0xf, data); + pca955x_write(s, pca955x_cmd_reg(s), data); pca955x_autoinc(s); } @@ -373,6 +429,79 @@ pca955x_write(s, reg, val); } +static void pca955x_set_ext_state(PCA955xState *s, int pin, int level); + +static void pca955x_get_pin(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + PCA955xClass *k = PCA955X_GET_CLASS(obj); + PCA955xState *s = PCA955X(obj); + int pin, rc; + uint8_t input_reg, state; + + rc = sscanf(name, "pin%2d", &pin); + if (rc != 1) { + error_setg(errp, "%s: error reading %s", __func__, name); + return; + } + if (pin < 0 || pin >= k->pin_count) { + error_setg(errp, "%s invalid pin %s", __func__, name); + return; + } + + /* + * Report the raw pin logic level; polarity inversion is a read-time + * transform applied to the INPUT register, not to the pin state itself. + */ + input_reg = PCA9535_INPUT0 + (pin / 8); + state = (s->regs[input_reg] >> (pin % 8)) & 0x1; + visit_type_str(v, name, (char **)&pin_state[state], errp); +} + +static void pca955x_set_pin(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + PCA955xClass *k = PCA955X_GET_CLASS(obj); + PCA955xState *s = PCA955X(obj); + int pin, rc; + uint8_t state, config_reg; + g_autofree char *state_str = NULL; + + if (!visit_type_str(v, name, &state_str, errp)) { + return; + } + rc = sscanf(name, "pin%2d", &pin); + if (rc != 1) { + error_setg(errp, "%s: error reading %s", __func__, name); + return; + } + if (pin < 0 || pin >= k->pin_count) { + error_setg(errp, "%s invalid pin %s", __func__, name); + return; + } + + for (state = 0; state < ARRAY_SIZE(pin_state); state++) { + if (!strcmp(state_str, pin_state[state])) { + break; + } + } + if (state >= ARRAY_SIZE(pin_state)) { + error_setg(errp, "%s invalid pin state %s", __func__, state_str); + return; + } + + /* Only input-configured pins can be driven by an external device. */ + config_reg = PCA9535_CONFIG0 + (pin / 8); + if (!((s->regs[config_reg] >> (pin % 8)) & 0x1)) { + qemu_log_mask(LOG_UNIMP, + "%s: pin %d is configured as output, ignoring set\n", + s->description, pin); + return; + } + + pca955x_set_ext_state(s, pin, state != PCA9552_PIN_LOW); +} + static const VMStateDescription pca9552_vmstate = { .name = "PCA9552", .version_id = 0, @@ -382,14 +511,14 @@ VMSTATE_UINT8(pointer, PCA955xState), VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS), VMSTATE_UINT8_ARRAY(ext_state, PCA955xState, PCA955X_PIN_COUNT_MAX), - VMSTATE_I2C_SLAVE(i2c, PCA955xState), + VMSTATE_I2C_SLAVE(parent_obj, PCA955xState), VMSTATE_END_OF_LIST() } }; -static void pca9552_reset(DeviceState *dev) +static void pca9552_reset_hold(Object *obj, ResetType type) { - PCA955xState *s = PCA955X(dev); + PCA955xState *s = PCA955X(obj); s->regs[PCA9552_PSC0] = 0xFF; s->regs[PCA9552_PWM0] = 0x80; @@ -407,9 +536,9 @@ s->len = 0; } -static void pca9535_reset(DeviceState *dev) +static void pca9535_reset_hold(Object *obj, ResetType type) { - PCA955xState *s = PCA955X(dev); + PCA955xState *s = PCA955X(obj); s->regs[PCA9535_INPUT0] = 0xFF; /* All inputs high (pull-ups) */ s->regs[PCA9535_INPUT1] = 0xFF; /* All inputs high (pull-ups) */ @@ -430,15 +559,22 @@ static void pca955x_initfn(Object *obj) { PCA955xClass *k = PCA955X_GET_CLASS(obj); - int led; assert(k->pin_count <= PCA955X_PIN_COUNT_MAX); - for (led = 0; led < k->pin_count; led++) { + for (int ix = 0; ix < k->pin_count; ix++) { char *name; - name = g_strdup_printf("led%d", led); - object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led, - NULL, NULL); + if (k->has_led_support) { + /* LED variant: expose the LED selector state as led%d. */ + name = g_strdup_printf("led%d", ix); + object_property_add(obj, name, "bool", + pca955x_get_led, pca955x_set_led, NULL, NULL); + } else { + /* GPIO variant: expose the pin logic level as pin%d. */ + name = g_strdup_printf("pin%d", ix); + object_property_add(obj, name, "str", + pca955x_get_pin, pca955x_set_pin, NULL, NULL); + } g_free(name); } } @@ -469,7 +605,7 @@ PCA955xState *s = PCA955X(dev); if (!s->description) { - s->description = g_strdup("pca-unspecified"); + s->description = g_strdup(object_get_typename(OBJECT(dev))); } qdev_init_gpio_out(dev, s->gpio_out, k->pin_count); @@ -492,57 +628,57 @@ device_class_set_props(dc, pca955x_properties); } -static const TypeInfo pca955x_info = { - .name = TYPE_PCA955X, - .parent = TYPE_I2C_SLAVE, - .instance_init = pca955x_initfn, - .instance_size = sizeof(PCA955xState), - .class_init = pca955x_class_init, - .class_size = sizeof(PCA955xClass), - .abstract = true, -}; - static void pca9552_class_init(ObjectClass *oc, const void *data) { DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); PCA955xClass *pc = PCA955X_CLASS(oc); - device_class_set_legacy_reset(dc, pca9552_reset); + rc->phases.hold = pca9552_reset_hold; dc->vmsd = &pca9552_vmstate; pc->max_reg = PCA9552_LS3; pc->pin_count = 16; pc->has_led_support = true; } -static void pca9535_class_init(ObjectClass *oc, const void *data) +static void pca95x5_class_init(ObjectClass *oc, const void *data) { DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); PCA955xClass *pc = PCA955X_CLASS(oc); - device_class_set_legacy_reset(dc, pca9535_reset); + rc->phases.hold = pca9535_reset_hold; dc->vmsd = &pca9552_vmstate; pc->max_reg = PCA9535_CONFIG1; pc->pin_count = 16; pc->has_led_support = false; } -static const TypeInfo pca9552_info = { - .name = TYPE_PCA9552, - .parent = TYPE_PCA955X, - .class_init = pca9552_class_init, +static const TypeInfo pca955x_types[] = { + { + .name = TYPE_PCA955X, + .parent = TYPE_I2C_SLAVE, + .instance_init = pca955x_initfn, + .instance_size = sizeof(PCA955xState), + .class_init = pca955x_class_init, + .class_size = sizeof(PCA955xClass), + .abstract = true, + }, + { + .name = TYPE_PCA9552, + .parent = TYPE_PCA955X, + .class_init = pca9552_class_init, + }, + { + .name = TYPE_PCA9535, + .parent = TYPE_PCA955X, + .class_init = pca95x5_class_init, + }, + { + .name = TYPE_PCA9555, + .parent = TYPE_PCA955X, + .class_init = pca95x5_class_init, + } }; -static const TypeInfo pca9535_info = { - .name = TYPE_PCA9535, - .parent = TYPE_PCA955X, - .class_init = pca9535_class_init, -}; - -static void pca955x_register_types(void) -{ - type_register_static(&pca955x_info); - type_register_static(&pca9552_info); - type_register_static(&pca9535_info); -} - -type_init(pca955x_register_types) +DEFINE_TYPES(pca955x_types)
diff --git a/hw/gpio/pca9554.c b/hw/gpio/pca9554.c index 8427e01..904698c 100644 --- a/hw/gpio/pca9554.c +++ b/hw/gpio/pca9554.c
@@ -24,6 +24,8 @@ /*< private >*/ I2CSlaveClass parent_class; /*< public >*/ + + uint8_t pin_count; }; typedef struct PCA9554Class PCA9554Class; @@ -37,46 +39,36 @@ static void pca9554_update_pin_input(PCA9554State *s) { + PCA9554Class *pc = PCA9554_GET_CLASS(s); int i; uint8_t config = s->regs[PCA9554_CONFIG]; uint8_t output = s->regs[PCA9554_OUTPUT]; - uint8_t internal_state = config | output; - for (i = 0; i < PCA9554_PIN_COUNT; i++) { + for (i = 0; i < pc->pin_count; i++) { uint8_t bit_mask = 1 << i; - uint8_t internal_pin_state = (internal_state >> i) & 0x1; uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask; uint8_t new_value; - switch (internal_pin_state) { - case PCA9554_PIN_LOW: - s->regs[PCA9554_INPUT] &= ~bit_mask; - break; - case PCA9554_PIN_HIZ: + if (config & bit_mask) { /* - * pullup sets it to a logical 1 unless - * external device drives it low. + * Input: the pin is Hi-Z with a pull-up, so it reads high + * unless an external device drives it low. */ if (s->ext_state[i] == PCA9554_PIN_LOW) { s->regs[PCA9554_INPUT] &= ~bit_mask; } else { - s->regs[PCA9554_INPUT] |= bit_mask; + s->regs[PCA9554_INPUT] |= bit_mask; } - break; - default: - break; + } else { + /* Output: the push-pull stage drives the output register level. */ + s->regs[PCA9554_INPUT] = (s->regs[PCA9554_INPUT] & ~bit_mask) | + (output & bit_mask); } - /* update irq state only if pin state changed */ + /* drive the per-pin GPIO output only if the pin level changed */ new_value = s->regs[PCA9554_INPUT] & bit_mask; if (new_value != old_value) { - if (new_value) { - /* changed from 0 to 1 */ - qemu_set_irq(s->gpio_out[i], 1); - } else { - /* changed from 1 to 0 */ - qemu_set_irq(s->gpio_out[i], 0); - } + qemu_set_irq(s->gpio_out[i], !!new_value); } } } @@ -99,6 +91,12 @@ static void pca9554_write(PCA9554State *s, uint8_t reg, uint8_t data) { + PCA9554Class *pc = PCA9554_GET_CLASS(s); + uint8_t pin_mask = (1 << pc->pin_count) - 1; + + /* Variants narrower than 8 bits ignore the unimplemented upper pins. */ + data &= pin_mask; + switch (reg) { case PCA9554_OUTPUT: case PCA9554_CONFIG: @@ -145,6 +143,14 @@ return 0; } +static void pca9554_set_ext_state(PCA9554State *s, int pin, int level) +{ + if (s->ext_state[pin] != level) { + s->ext_state[pin] = level; + pca9554_update_pin_input(s); + } +} + static void pca9554_get_pin(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) { @@ -157,14 +163,18 @@ error_setg(errp, "%s: error reading %s", __func__, name); return; } - if (pin < 0 || pin >= PCA9554_PIN_COUNT) { + if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) { error_setg(errp, "%s invalid pin %s", __func__, name); return; } - state = pca9554_read(s, PCA9554_CONFIG); - state |= pca9554_read(s, PCA9554_OUTPUT); - state = (state >> pin) & 0x1; + /* + * Report the physical pin level. The input register is kept in sync by + * pca9554_update_pin_input(): output pins mirror the OUTPUT register and + * input pins reflect the externally driven (or pulled-up) level, so it + * holds the wire level regardless of the configured direction. + */ + state = (s->regs[PCA9554_INPUT] >> pin) & 0x1; visit_type_str(v, name, (char **)&pin_state[state], errp); } @@ -184,7 +194,7 @@ error_setg(errp, "%s: error reading %s", __func__, name); return; } - if (pin < 0 || pin >= PCA9554_PIN_COUNT) { + if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) { error_setg(errp, "%s invalid pin %s", __func__, name); return; } @@ -199,20 +209,34 @@ return; } - /* First, modify the output register bit */ - val = pca9554_read(s, PCA9554_OUTPUT); - mask = 0x1 << pin; - if (state == PCA9554_PIN_LOW) { - val &= ~(mask); + if (s->hw_dir) { + /* Warn and ignore if the guest has configured this pin as output */ + if (!((s->regs[PCA9554_CONFIG] >> pin) & 0x1)) { + qemu_log_mask(LOG_UNIMP, + "%s: pin %d is configured as output, " + "ignoring external set\n", + s->description, pin); + return; + } + /* Drive the external input level */ + pca9554_set_ext_state(s, pin, state != PCA9554_PIN_LOW); } else { - val |= mask; - } - pca9554_write(s, PCA9554_OUTPUT, val); + /* Legacy behavior: force output mode and drive */ + /* First, modify the output register bit */ + val = pca9554_read(s, PCA9554_OUTPUT); + mask = 0x1 << pin; + if (state == PCA9554_PIN_LOW) { + val &= ~(mask); + } else { + val |= mask; + } + pca9554_write(s, PCA9554_OUTPUT, val); - /* Then, clear the config register bit for output mode */ - val = pca9554_read(s, PCA9554_CONFIG); - val &= ~mask; - pca9554_write(s, PCA9554_CONFIG, val); + /* Then, clear the config register bit for output mode */ + val = pca9554_read(s, PCA9554_CONFIG); + val &= ~mask; + pca9554_write(s, PCA9554_CONFIG, val); + } } static const VMStateDescription pca9554_vmstate = { @@ -232,13 +256,15 @@ static void pca9554_reset(DeviceState *dev) { PCA9554State *s = PCA9554(dev); + PCA9554Class *pc = PCA9554_GET_CLASS(s); + uint8_t pin_mask = (1 << pc->pin_count) - 1; - s->regs[PCA9554_INPUT] = 0xFF; - s->regs[PCA9554_OUTPUT] = 0xFF; + s->regs[PCA9554_INPUT] = pin_mask; + s->regs[PCA9554_OUTPUT] = pin_mask; s->regs[PCA9554_POLARITY] = 0x0; /* No pins are inverted */ - s->regs[PCA9554_CONFIG] = 0xFF; /* All pins are inputs */ + s->regs[PCA9554_CONFIG] = pin_mask; /* All pins are inputs */ - memset(s->ext_state, PCA9554_PIN_HIZ, PCA9554_PIN_COUNT); + memset(s->ext_state, PCA9554_PIN_HIZ, pc->pin_count); pca9554_update_pin_input(s); s->pointer = 0x0; @@ -247,55 +273,51 @@ static void pca9554_initfn(Object *obj) { + PCA9554Class *pc = PCA9554_GET_CLASS(obj); int pin; - for (pin = 0; pin < PCA9554_PIN_COUNT; pin++) { + for (pin = 0; pin < pc->pin_count; pin++) { char *name; name = g_strdup_printf("pin%d", pin); - object_property_add(obj, name, "bool", pca9554_get_pin, pca9554_set_pin, + object_property_add(obj, name, "str", pca9554_get_pin, pca9554_set_pin, NULL, NULL); g_free(name); } } -static void pca9554_set_ext_state(PCA9554State *s, int pin, int level) -{ - if (s->ext_state[pin] != level) { - s->ext_state[pin] = level; - pca9554_update_pin_input(s); - } -} - static void pca9554_gpio_in_handler(void *opaque, int pin, int level) { - PCA9554State *s = PCA9554(opaque); + PCA9554Class *pc = PCA9554_GET_CLASS(s); - assert((pin >= 0) && (pin < PCA9554_PIN_COUNT)); + assert((pin >= 0) && (pin < pc->pin_count)); pca9554_set_ext_state(s, pin, level); } static void pca9554_realize(DeviceState *dev, Error **errp) { PCA9554State *s = PCA9554(dev); + PCA9554Class *pc = PCA9554_GET_CLASS(s); if (!s->description) { - s->description = g_strdup("pca9554"); + s->description = g_strdup(object_get_typename(OBJECT(dev))); } - qdev_init_gpio_out(dev, s->gpio_out, PCA9554_PIN_COUNT); - qdev_init_gpio_in(dev, pca9554_gpio_in_handler, PCA9554_PIN_COUNT); + qdev_init_gpio_out(dev, s->gpio_out, pc->pin_count); + qdev_init_gpio_in(dev, pca9554_gpio_in_handler, pc->pin_count); } static const Property pca9554_properties[] = { DEFINE_PROP_STRING("description", PCA9554State, description), + DEFINE_PROP_BOOL("hw-dir", PCA9554State, hw_dir, false), }; static void pca9554_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + PCA9554Class *pc = PCA9554_CLASS(klass); k->event = pca9554_event; k->recv = pca9554_recv; @@ -304,21 +326,32 @@ device_class_set_legacy_reset(dc, pca9554_reset); dc->vmsd = &pca9554_vmstate; device_class_set_props(dc, pca9554_properties); + + pc->pin_count = PCA9554_PIN_COUNT; } -static const TypeInfo pca9554_info = { - .name = TYPE_PCA9554, - .parent = TYPE_I2C_SLAVE, - .instance_init = pca9554_initfn, - .instance_size = sizeof(PCA9554State), - .class_init = pca9554_class_init, - .class_size = sizeof(PCA9554Class), - .abstract = false, +static void pca9536_class_init(ObjectClass *klass, const void *data) +{ + PCA9554Class *pc = PCA9554_CLASS(klass); + + pc->pin_count = PCA9536_PIN_COUNT; +} + +static const TypeInfo pca9554_types[] = { + { + .name = TYPE_PCA9554, + .parent = TYPE_I2C_SLAVE, + .instance_init = pca9554_initfn, + .instance_size = sizeof(PCA9554State), + .class_init = pca9554_class_init, + .class_size = sizeof(PCA9554Class), + .abstract = false, + }, + { + .name = TYPE_PCA9536, + .parent = TYPE_PCA9554, + .class_init = pca9536_class_init, + } }; -static void pca9554_register_types(void) -{ - type_register_static(&pca9554_info); -} - -type_init(pca9554_register_types) +DEFINE_TYPES(pca9554_types);
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events index cea896b..80ca783 100644 --- a/hw/gpio/trace-events +++ b/hw/gpio/trace-events
@@ -46,3 +46,8 @@ stm32l4x5_gpio_write(char *gpio, uint64_t addr, uint64_t data) "GPIO%s addr: 0x%" PRIx64 " val: 0x%" PRIx64 "" stm32l4x5_gpio_update_idr(char *gpio, uint32_t old_idr, uint32_t new_idr) "GPIO%s from: 0x%x to: 0x%x" stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s disconnected pins: 0x%x levels: 0x%x" + +# cadence_gpio.c +cdns_gpio_read(const char *path, uint64_t offset, uint32_t value) "%s:reg[0x%04" PRIx64 "] -> 0x%" PRIx32 +cdns_gpio_write(const char *path, uint64_t offset, uint64_t value) "%s:reg[0x%04" PRIx64 "] <- 0x%04" PRIx64 +cdns_gpio_set(const char *path, int line, int level) "%s:[%d] <- %d"
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig index 52065ab..83b2763 100644 --- a/hw/hexagon/Kconfig +++ b/hw/hexagon/Kconfig
@@ -2,6 +2,9 @@ bool default y depends on HEXAGON + select CPU_CLUSTER + select HEX_L2VIC + select HEX_QTIMER config HEX_VIRT bool
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c new file mode 100644 index 0000000..4e3a418 --- /dev/null +++ b/hw/hexagon/hex-subsys.c
@@ -0,0 +1,175 @@ +/* + * Hexagon subsystem helpers shared between the machine models. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/hexagon/hex-subsys.h" +#include "hw/hexagon/hexagon_globalreg.h" +#include "hw/hexagon/hexagon_tlb.h" +#include "hw/intc/hex-l2vic.h" +#include "hw/timer/qct-qtimer.h" +#include "hw/cpu/cluster.h" +#include "hw/core/loader.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev.h" +#include "hw/core/sysbus.h" +#include "system/address-spaces.h" + +#define HEX_L2VIC_CPU_IRQS 8 + +/* Number of QTimer frames instantiated for every Hexagon machine. */ +#define HEX_QTIMER_NR_FRAMES 3 + +#define HEX_QTIMER_L2VIC_IRQ_BASE 2 + +static DeviceState *l2vic_create(HexagonCommonMachineState *hms, + const struct hexagon_machine_config *m_cfg) +{ + DeviceState *l2vic = qdev_new(TYPE_HEX_L2VIC); + + object_property_add_child(OBJECT(hms), "l2vic", OBJECT(l2vic)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(l2vic), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(l2vic), 0, m_cfg->l2vic_base); + sysbus_mmio_map(SYS_BUS_DEVICE(l2vic), 1, + m_cfg->cfgtable.fastl2vic_base << 16); + + return l2vic; +} + +static void l2vic_connect_cpu(DeviceState *l2vic, DeviceState *cpu) +{ + int i; + + for (i = 0; i < HEX_L2VIC_CPU_IRQS; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(l2vic), i, qdev_get_gpio_in(cpu, i)); + } +} + +static DeviceState *qtimer_create(HexagonCommonMachineState *hms, + const struct hexagon_machine_config *m_cfg) +{ + DeviceState *qtimer = qdev_new(TYPE_QCT_QTIMER); + + object_property_add_child(OBJECT(hms), "qtimer", OBJECT(qtimer)); + qdev_prop_set_uint32(qtimer, "nr_frames", HEX_QTIMER_NR_FRAMES); + sysbus_realize_and_unref(SYS_BUS_DEVICE(qtimer), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 0, m_cfg->csr_base); + sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 1, m_cfg->qtmr_region); + for (unsigned int i = 0; i < HEX_QTIMER_NR_FRAMES; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), i, + qdev_get_gpio_in(hms->l2vic, + HEX_QTIMER_L2VIC_IRQ_BASE + i)); + } + + return qtimer; +} + +static DeviceState *globalreg_create(HexagonCommonMachineState *hms, + const struct hexagon_machine_config *m_cfg, + Rev_t rev) +{ + DeviceState *glob_regs = qdev_new(TYPE_HEXAGON_GLOBALREG); + + object_property_add_child(OBJECT(hms), "global-regs", OBJECT(glob_regs)); + qdev_prop_set_uint64(glob_regs, "config-table-addr", m_cfg->cfgbase); + qdev_prop_set_uint32(glob_regs, "dsp-rev", rev); + object_property_set_link(OBJECT(glob_regs), "l2vic", OBJECT(hms->l2vic), + &error_fatal); + object_property_set_link(OBJECT(glob_regs), "qtimer", OBJECT(hms->qtimer), + &error_fatal); + sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs), &error_fatal); + + return glob_regs; +} + +static DeviceState *tlb_create(HexagonCommonMachineState *hms, + const struct hexagon_machine_config *m_cfg) +{ + DeviceState *tlb = qdev_new(TYPE_HEXAGON_TLB); + + object_property_add_child(OBJECT(hms), "tlb", OBJECT(tlb)); + qdev_prop_set_uint32(tlb, "num-entries", m_cfg->cfgtable.jtlb_size_entries); + sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb), &error_fatal); + + return tlb; +} + +static DeviceState *cluster_create(HexagonCommonMachineState *hms) +{ + DeviceState *cluster = qdev_new(TYPE_CPU_CLUSTER); + + object_property_add_child(OBJECT(hms), "cluster", OBJECT(cluster)); + qdev_prop_set_uint32(cluster, "cluster-id", 0); + + return cluster; +} + +void hex_subsys_create(HexagonCommonMachineState *hms, + const struct hexagon_machine_config *m_cfg, Rev_t rev) +{ + MachineState *machine = MACHINE(hms); + MemoryRegion *sysmem = get_system_memory(); + + /* Main DDR at the reset vector. */ + memory_region_init_ram(&hms->ram, NULL, "ddr.ram", machine->ram_size, + &error_fatal); + memory_region_add_subregion(sysmem, 0x0, &hms->ram); + + /* Config-table ROM and the blob that backs it. */ + memory_region_init_rom(&hms->cfgtable_rom, NULL, "config_table.rom", + sizeof(m_cfg->cfgtable), &error_fatal); + memory_region_add_subregion(sysmem, m_cfg->cfgbase, &hms->cfgtable_rom); + rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, + sizeof(m_cfg->cfgtable), m_cfg->cfgbase, + &address_space_memory); + + if (m_cfg->cfgtable.vtcm_size_kb > 0) { + memory_region_init_ram(&hms->vtcm, NULL, "vtcm.ram", + m_cfg->cfgtable.vtcm_size_kb * 1024, + &error_fatal); + memory_region_add_subregion(sysmem, m_cfg->cfgtable.vtcm_base << 16, + &hms->vtcm); + } + + hms->cluster = cluster_create(hms); + hms->l2vic = l2vic_create(hms, m_cfg); + hms->qtimer = qtimer_create(hms, m_cfg); + hms->glob_regs = globalreg_create(hms, m_cfg, rev); + hms->tlb = tlb_create(hms, m_cfg); +} + +void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu) +{ + object_property_add_child(OBJECT(hms->cluster), "cpu[*]", OBJECT(cpu)); + object_property_set_link(OBJECT(cpu), "global-regs", + OBJECT(hms->glob_regs), &error_fatal); + object_property_set_link(OBJECT(cpu), "tlb", OBJECT(hms->tlb), + &error_fatal); + object_property_set_link(OBJECT(cpu), "l2vic", OBJECT(hms->l2vic), + &error_fatal); +} + +void hex_subsys_realize_cluster(HexagonCommonMachineState *hms) +{ + /* + * The cluster must be realized after its CPUs have been parented into it + * (see hex_subsys_add_cpu()) but before any CPU is itself realized, since + * qdev_realize_and_unref() on a CPU latches cluster_index into the TCG + * cflags at that point. + */ + qdev_realize_and_unref(hms->cluster, NULL, &error_fatal); +} + +void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu, + bool boot_cpu) +{ + qdev_realize_and_unref(cpu, NULL, &error_fatal); + + if (boot_cpu) { + l2vic_connect_cpu(hms->l2vic, cpu); + } +}
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c index aa49399..20306c2 100644 --- a/hw/hexagon/hexagon_dsp.c +++ b/hw/hexagon/hexagon_dsp.c
@@ -14,8 +14,7 @@ #include "hw/core/boards.h" #include "hw/core/qdev-properties.h" #include "hw/hexagon/hexagon.h" -#include "hw/hexagon/hexagon_globalreg.h" -#include "hw/hexagon/hexagon_tlb.h" +#include "hw/hexagon/hex-subsys.h" #include "hw/core/loader.h" #include "qapi/error.h" #include "qemu/error-report.h" @@ -108,9 +107,6 @@ { HexagonCommonMachineState *hms = HEXAGON_COMMON_MACHINE(machine); HexagonDspMachineState *dms = HEXAGON_DSP_MACHINE(machine); - MemoryRegion *address_space; - DeviceState *glob_regs_dev; - DeviceState *tlb_dev; memset(&hexagon_binfo, 0, sizeof(hexagon_binfo)); if (machine->kernel_filename) { @@ -120,29 +116,9 @@ machine->enable_graphics = 0; - address_space = get_system_memory(); + hex_subsys_create(hms, m_cfg, rev); - memory_region_init_rom(&hms->cfgtable_rom, NULL, "config_table.rom", - sizeof(m_cfg->cfgtable), &error_fatal); - memory_region_add_subregion(address_space, m_cfg->cfgbase, - &hms->cfgtable_rom); - - memory_region_init_ram(&hms->ram, NULL, "ddr.ram", - machine->ram_size, &error_fatal); - memory_region_add_subregion(address_space, 0x0, &hms->ram); - - glob_regs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG); - object_property_add_child(OBJECT(machine), "global-regs", - OBJECT(glob_regs_dev)); - qdev_prop_set_uint64(glob_regs_dev, "config-table-addr", m_cfg->cfgbase); - qdev_prop_set_uint32(glob_regs_dev, "dsp-rev", rev); - sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs_dev), &error_fatal); - - tlb_dev = qdev_new(TYPE_HEXAGON_TLB); - object_property_add_child(OBJECT(machine), "tlb", OBJECT(tlb_dev)); - qdev_prop_set_uint32(tlb_dev, "num-entries", - m_cfg->cfgtable.jtlb_size_entries); - sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal); + g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, machine->smp.cpus); for (int i = 0; i < machine->smp.cpus; i++) { HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type)); @@ -156,16 +132,15 @@ if (i == 0) { hexagon_init_bootstrap(dms, cpu); } - object_property_set_link(OBJECT(cpu), "global-regs", - OBJECT(glob_regs_dev), &error_fatal); - object_property_set_link(OBJECT(cpu), "tlb", - OBJECT(tlb_dev), &error_fatal); - qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal); + hex_subsys_add_cpu(hms, DEVICE(cpu)); + cpus[i] = cpu; } - rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, - sizeof(m_cfg->cfgtable), m_cfg->cfgbase, - &address_space_memory); + hex_subsys_realize_cluster(hms); + + for (int i = 0; i < machine->smp.cpus; i++) { + hex_subsys_realize_cpu(hms, DEVICE(cpus[i]), (i == 0)); + } } static void init_mc(MachineClass *mc)
diff --git a/hw/hexagon/hexagon_globalreg.c b/hw/hexagon/hexagon_globalreg.c index b5e5913..61621cf 100644 --- a/hw/hexagon/hexagon_globalreg.c +++ b/hw/hexagon/hexagon_globalreg.c
@@ -11,6 +11,8 @@ #include "hw/core/qdev-properties.h" #include "hw/core/sysbus.h" #include "hw/core/resettable.h" +#include "hw/intc/hex-l2vic.h" +#include "hw/timer/qct-qtimer.h" #include "migration/vmstate.h" #include "qom/object.h" #include "target/hexagon/cpu.h" @@ -135,6 +137,38 @@ return new_val; } +static inline bool is_vid_reg(uint32_t reg) +{ + return reg == HEX_SREG_VID || reg == HEX_SREG_VID1; +} + +static inline bool is_timer_reg(uint32_t reg) +{ + return reg == HEX_SREG_TIMERLO || reg == HEX_SREG_TIMERHI; +} + +static uint32_t get_reg_value(HexagonGlobalRegState *s, uint32_t reg) +{ + if (is_vid_reg(reg)) { + return l2vic_read_vid(s->l2vic, reg == HEX_SREG_VID ? 0 : 1); + } + if (is_timer_reg(reg)) { + return reg == HEX_SREG_TIMERLO ? + qct_qtimer_get_timer_lo(s->qtimer) : + qct_qtimer_get_timer_hi(s->qtimer); + } + return s->regs[reg]; +} + +static void set_reg_value(HexagonGlobalRegState *s, uint32_t reg, + uint32_t value) +{ + s->regs[reg] = value; + if (is_vid_reg(reg)) { + l2vic_update_vid(s->l2vic, reg == HEX_SREG_VID ? 0 : 1, value); + } +} + uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg, uint32_t htid) { @@ -146,7 +180,7 @@ g_assert(reg < NUM_SREGS); g_assert(reg >= HEX_SREG_GLB_START); - value = s->regs[reg]; + value = get_reg_value(s, reg); trace_hexagon_globalreg_read(htid, get_sreg_name(reg), value); return value; @@ -160,7 +194,7 @@ } g_assert(reg < NUM_SREGS); g_assert(reg >= HEX_SREG_GLB_START); - s->regs[reg] = value; + set_reg_value(s, reg, value); trace_hexagon_globalreg_write(htid, get_sreg_name(reg), value); } @@ -168,6 +202,7 @@ uint32_t value) { uint32_t reg_mask; + uint32_t cur_val; if (!s) { return value; @@ -175,9 +210,10 @@ g_assert(reg < NUM_SREGS); g_assert(reg >= HEX_SREG_GLB_START); reg_mask = global_sreg_immut_masks[reg]; + cur_val = get_reg_value(s, reg); return reg_mask == IMMUTABLE ? - s->regs[reg] : - apply_write_mask(value, s->regs[reg], reg_mask); + cur_val : + apply_write_mask(value, cur_val, reg_mask); } void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg, @@ -186,7 +222,7 @@ if (!s) { return; } - s->regs[reg] = hexagon_globalreg_masked_value(s, reg, value); + set_reg_value(s, reg, hexagon_globalreg_masked_value(s, reg, value)); } uint64_t hexagon_globalreg_get_pcycle_base(HexagonGlobalRegState *s) @@ -256,6 +292,20 @@ do_hexagon_globalreg_reset(s); } +static void hexagon_globalreg_realize(DeviceState *dev, Error **errp) +{ + HexagonGlobalRegState *s = HEXAGON_GLOBALREG(dev); + + if (!s->l2vic) { + error_setg(errp, "hexagon_globalreg: 'l2vic' link property not set"); + return; + } + if (!s->qtimer) { + error_setg(errp, "hexagon_globalreg: 'qtimer' link property not set"); + return; + } +} + static const VMStateDescription vmstate_hexagon_globalreg = { .name = "hexagon_globalreg", .version_id = 1, @@ -275,6 +325,10 @@ }; static const Property hexagon_globalreg_properties[] = { + DEFINE_PROP_LINK("l2vic", HexagonGlobalRegState, l2vic, + TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *), + DEFINE_PROP_LINK("qtimer", HexagonGlobalRegState, qtimer, + TYPE_QCT_QTIMER_INTERFACE, QctQtimerInterface *), DEFINE_PROP_UINT32("boot-evb", HexagonGlobalRegState, boot_evb, 0x0), DEFINE_PROP_UINT64("config-table-addr", HexagonGlobalRegState, config_table_addr, 0xffffffffULL), @@ -295,6 +349,7 @@ ResettableClass *rc = RESETTABLE_CLASS(klass); rc->phases.hold = hexagon_globalreg_reset_hold; + dc->realize = hexagon_globalreg_realize; dc->vmsd = &vmstate_hexagon_globalreg; dc->user_creatable = false; device_class_set_props(dc, hexagon_globalreg_properties);
diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build index bade3a3..720a5d54 100644 --- a/hw/hexagon/meson.build +++ b/hw/hexagon/meson.build
@@ -1,6 +1,7 @@ hexagon_ss = ss.source_set() hexagon_ss.add(files('hexagon_tlb.c')) hexagon_ss.add(files('hexagon_globalreg.c')) +hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hex-subsys.c')) hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hexagon_dsp.c')) hexagon_ss.add(when: 'CONFIG_HEX_VIRT', if_true: files('virt.c'))
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c index b750472..64d8366 100644 --- a/hw/hexagon/virt.c +++ b/hw/hexagon/virt.c
@@ -13,8 +13,7 @@ #include "hw/core/clock.h" #include "hw/core/sysbus-fdt.h" #include "hw/hexagon/hexagon.h" -#include "hw/hexagon/hexagon_globalreg.h" -#include "hw/hexagon/hexagon_tlb.h" +#include "hw/hexagon/hex-subsys.h" #include "hw/core/loader.h" #include "hw/core/qdev-properties.h" #include "hw/core/qdev-clock.h" @@ -31,11 +30,20 @@ enum { VIRT_UART0, + VIRT_MMIO, VIRT_FDT, }; +/* + * Virtio IRQs run from VIRTIO_IRQ_BASE to + * VIRTIO_IRQ_BASE + VIRTIO_DEV_COUNT - 1 + */ +static const int VIRTIO_IRQ_BASE = 16; +static const int VIRT_UART0_IRQ = 15; + static const MemMapEntry base_memmap[] = { [VIRT_UART0] = { 0x10000000, 0x00000200 }, + [VIRT_MMIO] = { 0x11000000, 0x00001000 }, [VIRT_FDT] = { 0x99800000, 0x00400000 }, }; @@ -68,17 +76,36 @@ qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed)); } +static int32_t fdt_add_l2vic(HexagonVirtMachineState *vms, + const struct hexagon_machine_config *m_cfg) +{ + MachineState *ms = MACHINE(vms); + int32_t l2vic_phandle = qemu_fdt_alloc_phandle(ms->fdt); + char *nodename = g_strdup_printf("/soc/interrupt-controller@%x", + m_cfg->l2vic_base); + const char compat[] = "qcom,h2-pic\0hvm-pic"; + + qemu_fdt_setprop_cell(ms->fdt, "/soc", "interrupt-parent", l2vic_phandle); + + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 0x1); + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, + m_cfg->l2vic_base, m_cfg->l2vic_size); + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", l2vic_phandle); + + g_free(nodename); + return l2vic_phandle; +} + static void fdt_add_hvx(HexagonVirtMachineState *vms, const struct hexagon_machine_config *m_cfg) { const MachineState *ms = MACHINE(vms); uint32_t vtcm_size_bytes = m_cfg->cfgtable.vtcm_size_kb * 1024; if (vtcm_size_bytes > 0) { - memory_region_init_ram(&vms->vtcm, NULL, "vtcm.ram", vtcm_size_bytes, - &error_fatal); - memory_region_add_subregion(vms->sys, m_cfg->cfgtable.vtcm_base << 16, - &vms->vtcm); - qemu_fdt_add_subnode(ms->fdt, "/soc/vtcm"); qemu_fdt_setprop_string(ms->fdt, "/soc/vtcm", "compatible", "qcom,hexagon_vtcm"); @@ -117,7 +144,7 @@ } static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart, - int32_t clk_phandle) + int32_t clk_phandle, int32_t l2vic_phandle) { char *nodename; hwaddr base = base_memmap[uart].base; @@ -135,6 +162,8 @@ qdev_connect_clock_in(dev, "clk", vms->apb_clk); sysbus_realize_and_unref(s, &error_fatal); sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, + qdev_get_gpio_in(vms->parent_obj.l2vic, VIRT_UART0_IRQ)); nodename = g_strdup_printf("/pl011@%" PRIx64, base); qemu_fdt_add_subnode(ms->fdt, nodename); @@ -142,6 +171,9 @@ /* Note that we can't use setprop_string because of the embedded NUL */ qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupts", VIRT_UART0_IRQ); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + l2vic_phandle); qemu_fdt_setprop_cells(ms->fdt, nodename, "clocks", clk_phandle, clk_phandle); qemu_fdt_setprop(ms->fdt, nodename, "clock-names", clocknames, @@ -173,7 +205,38 @@ } } +static void create_virtio_devices(HexagonVirtMachineState *vms, + int32_t l2vic_phandle) +{ + MachineState *ms = MACHINE(vms); + hwaddr size = base_memmap[VIRT_MMIO].size; + for (int i = 0; i < VIRTIO_DEV_COUNT; i++) { + int irq = VIRTIO_IRQ_BASE + i; + hwaddr base = base_memmap[VIRT_MMIO].base + i * size; + char *nodename = g_strdup_printf("/soc/virtio_mmio@%" PRIx64, base); + DeviceState *dev = qdev_new("virtio-mmio"); + SysBusDevice *s = SYS_BUS_DEVICE(dev); + + object_property_add_child(OBJECT(MACHINE(vms)), "virtio-mmio[*]", + OBJECT(dev)); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, + qdev_get_gpio_in(vms->parent_obj.l2vic, irq)); + vms->virtio_mmio[i] = dev; + + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", + "virtio,mmio"); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupts", irq); + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", + l2vic_phandle); + + g_free(nodename); + } +} void hexagon_load_fdt(const HexagonVirtMachineState *vms) { @@ -230,10 +293,8 @@ { HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(ms); const struct hexagon_machine_config *m_cfg = &v68n_1024; - DeviceState *gsregs_dev; - DeviceState *tlb_dev; - DeviceState *cpu0; int32_t clk_phandle; + int32_t l2vic_phandle; create_fdt(vms); qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", ms->kernel_cmdline); @@ -244,9 +305,7 @@ vms->apb_clk = clock_new(OBJECT(ms), "apb-pclk"); clock_set_hz(vms->apb_clk, 24000000); - memory_region_init_ram(&vms->parent_obj.ram, NULL, "ddr.ram", - ms->ram_size, &error_fatal); - memory_region_add_subregion(vms->sys, 0x0, &vms->parent_obj.ram); + hex_subsys_create(&vms->parent_obj, m_cfg, v68_rev); if (m_cfg->l2tcm_size) { memory_region_init_ram(&vms->tcm, NULL, "tcm.ram", m_cfg->l2tcm_size, @@ -255,56 +314,41 @@ &vms->tcm); } - memory_region_init_rom(&vms->parent_obj.cfgtable_rom, NULL, - "config_table.rom", sizeof(m_cfg->cfgtable), - &error_fatal); - memory_region_add_subregion(vms->sys, m_cfg->cfgbase, - &vms->parent_obj.cfgtable_rom); fdt_add_hvx(vms, m_cfg); - gsregs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG); - object_property_add_child(OBJECT(ms), "global-regs", OBJECT(gsregs_dev)); - qdev_prop_set_uint64(gsregs_dev, "config-table-addr", m_cfg->cfgbase); - qdev_prop_set_uint32(gsregs_dev, "dsp-rev", v68_rev); - sysbus_realize_and_unref(SYS_BUS_DEVICE(gsregs_dev), &error_fatal); + l2vic_phandle = fdt_add_l2vic(vms, m_cfg); + create_virtio_devices(vms, l2vic_phandle); - tlb_dev = qdev_new(TYPE_HEXAGON_TLB); - object_property_add_child(OBJECT(ms), "tlb", OBJECT(tlb_dev)); - qdev_prop_set_uint32(tlb_dev, "num-entries", - m_cfg->cfgtable.jtlb_size_entries); - sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal); + g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, ms->smp.cpus); - cpu0 = NULL; for (int i = 0; i < ms->smp.cpus; i++) { HexagonCPU *cpu = HEXAGON_CPU(object_new(ms->cpu_type)); qemu_register_reset(do_cpu_reset, cpu); if (i == 0) { - cpu0 = DEVICE(cpu); if (ms->kernel_filename) { uint64_t entry = load_kernel(vms); - qdev_prop_set_uint32(cpu0, "exec-start-addr", entry); + qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", entry); } else if (ms->firmware) { uint64_t entry = load_bios(vms); - qdev_prop_set_uint32(cpu0, "exec-start-addr", entry); + qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", entry); } } qdev_prop_set_uint32(DEVICE(cpu), "htid", i); qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0)); - object_property_set_link(OBJECT(cpu), "global-regs", - OBJECT(gsregs_dev), &error_fatal); - object_property_set_link(OBJECT(cpu), "tlb", - OBJECT(tlb_dev), &error_fatal); - - qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal); + hex_subsys_add_cpu(&vms->parent_obj, DEVICE(cpu)); + cpus[i] = cpu; } + + hex_subsys_realize_cluster(&vms->parent_obj); + + for (int i = 0; i < ms->smp.cpus; i++) { + hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpus[i]), (i == 0)); + } + fdt_add_cpu_nodes(vms); clk_phandle = fdt_add_clocks(vms); - fdt_add_uart(vms, VIRT_UART0, clk_phandle); - - rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, - sizeof(m_cfg->cfgtable), m_cfg->cfgbase, - &address_space_memory); + fdt_add_uart(vms, VIRT_UART0, clk_phandle, l2vic_phandle); hexagon_load_fdt(vms); }
diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index 27afcae..68bdcd0 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c
@@ -159,6 +159,7 @@ case A_I2CS_INTR_CTRL: case A_I2CS_DMA_LEN_STS: case A_I2CS_INTR_STS: + case A_I2CC_VERSION_CTRL: value = bus->regs[offset / sizeof(*bus->regs)]; break; case A_I2CC_DMA_ADDR: @@ -295,6 +296,65 @@ return 0; } +/* + * In AST2700 buffer mode the master DMA command bits (TX/RX_DMA_EN) and the + * DMA length registers are reused, but data is moved through the controller + * internal SRAM pool at the offset programmed in I2CM_DMA_TX/RX_ADDR instead + * of DRAM. FUNC_CFG_DMA_EN selects between the two (set = DRAM). + */ +static bool aspeed_i2c_bus_dma_to_pool(AspeedI2CBus *bus) +{ + return aspeed_i2c_is_new_mode(bus->controller) && + !ARRAY_FIELD_EX32(bus->regs, I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN); +} + +static int aspeed_i2c_bus_send_dma_pool(AspeedI2CBus *bus) +{ + AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller); + uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus); + uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus); + uint32_t offset = bus->regs[R_I2CM_DMA_TX_ADDR]; + uint8_t *pool_base = aic->bus_pool_base(bus); + int ret = -1; + int i; + + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0); + for (i = 0; bus->regs[reg_dma_len] && + offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) { + trace_aspeed_i2c_bus_send("BUFF", i + 1, bus->regs[reg_dma_len], + pool_base[offset + i]); + ret = i2c_send(bus->bus, pool_base[offset + i]); + bus->regs[reg_dma_len]--; + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, i + 1); + if (ret) { + break; + } + } + SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_DMA_EN, 0); + return ret; +} + +static void aspeed_i2c_bus_recv_dma_pool(AspeedI2CBus *bus) +{ + AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller); + uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus); + uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus); + uint32_t offset = bus->regs[R_I2CM_DMA_RX_ADDR]; + uint8_t *pool_base = aic->bus_pool_base(bus); + int i; + + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0); + for (i = 0; bus->regs[reg_dma_len] && + offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) { + pool_base[offset + i] = i2c_recv(bus->bus); + trace_aspeed_i2c_bus_recv("BUFF", i + 1, bus->regs[reg_dma_len], + pool_base[offset + i]); + bus->regs[reg_dma_len]--; + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, i + 1); + } + SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_DMA_EN, 0); +} + static int aspeed_i2c_bus_send(AspeedI2CBus *bus) { AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller); @@ -320,6 +380,10 @@ } SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_BUFF_EN, 0); } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN)) { + /* In buffer mode the DMA moves data through the pool, not DRAM */ + if (aspeed_i2c_bus_dma_to_pool(bus)) { + return aspeed_i2c_bus_send_dma_pool(bus); + } /* In new mode, clear how many bytes we TXed */ if (aspeed_i2c_is_new_mode(bus->controller)) { ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0); @@ -385,6 +449,11 @@ SHARED_ARRAY_FIELD_DP32(bus->regs, reg_pool_ctrl, RX_COUNT, i & 0xff); SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_BUFF_EN, 0); } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN)) { + /* In buffer mode the DMA moves data through the pool, not DRAM */ + if (aspeed_i2c_bus_dma_to_pool(bus)) { + aspeed_i2c_bus_recv_dma_pool(bus); + return; + } /* In new mode, clear how many bytes we RXed */ if (aspeed_i2c_is_new_mode(bus->controller)) { ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0); @@ -854,6 +923,9 @@ I2CS_DMA_RX_ADDR_HI, ADDR_HI); break; + case A_I2CC_VERSION_CTRL: + bus->regs[R_I2CC_VERSION_CTRL] = value; + break; default: qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset); @@ -1497,6 +1569,13 @@ memset(s->regs, 0, sizeof(s->regs)); s->pending_intr_sts = 0; i2c_end_transfer(s->bus); + /* + * I2CC_VERSION_CTRL resets to all-ones. FUNC_CFG_DMA_EN is therefore set, + * so master DMA targets DRAM unless the guest clears it to select buffer + * mode. Guests unaware of buffer mode never touch this register and keep + * doing DRAM DMA. + */ + s->regs[R_I2CC_VERSION_CTRL] = 0xffffffff; } static void aspeed_i2c_bus_realize(DeviceState *dev, Error **errp)
diff --git a/hw/i2c/bcm2835_i2c.c b/hw/i2c/bcm2835_i2c.c index 34de1f3..5f60930 100644 --- a/hw/i2c/bcm2835_i2c.c +++ b/hw/i2c/bcm2835_i2c.c
@@ -222,7 +222,7 @@ s->bus = i2c_init_bus(dev, NULL); memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_i2c_ops, s, - TYPE_BCM2835_I2C, 0x24); + TYPE_BCM2835_I2C, 0x20); sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); }
diff --git a/hw/i386/pc.c b/hw/i386/pc.c index f064aa2..e9e4fc2 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c
@@ -74,6 +74,9 @@ #include "hw/xen/xen-bus.h" #endif +GlobalProperty pc_compat_11_1[] = {}; +const size_t pc_compat_11_1_len = G_N_ELEMENTS(pc_compat_11_1); + GlobalProperty pc_compat_11_0[] = {}; const size_t pc_compat_11_0_len = G_N_ELEMENTS(pc_compat_11_0);
diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index 82457bd..223a19c 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c
@@ -428,12 +428,21 @@ pc_piix_compat_defaults, pc_piix_compat_defaults_len); } -static void pc_i440fx_machine_11_1_options(MachineClass *m) +static void pc_i440fx_machine_11_2_options(MachineClass *m) { pc_i440fx_machine_options(m); } -DEFINE_I440FX_MACHINE_AS_LATEST(11, 1); +DEFINE_I440FX_MACHINE_AS_LATEST(11, 2); + +static void pc_i440fx_machine_11_1_options(MachineClass *m) +{ + pc_i440fx_machine_11_2_options(m); + compat_props_add(m->compat_props, hw_compat_11_1, hw_compat_11_1_len); + compat_props_add(m->compat_props, pc_compat_11_1, pc_compat_11_1_len); +} + +DEFINE_I440FX_MACHINE(11, 1); static void pc_i440fx_machine_11_0_options(MachineClass *m) {
diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 6c1e4ef..94cd711 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c
@@ -383,12 +383,21 @@ pc_q35_compat_defaults, pc_q35_compat_defaults_len); } -static void pc_q35_machine_11_1_options(MachineClass *m) +static void pc_q35_machine_11_2_options(MachineClass *m) { pc_q35_machine_options(m); } -DEFINE_Q35_MACHINE_AS_LATEST(11, 1); +DEFINE_Q35_MACHINE_AS_LATEST(11, 2); + +static void pc_q35_machine_11_1_options(MachineClass *m) +{ + pc_q35_machine_11_2_options(m); + compat_props_add(m->compat_props, hw_compat_11_1, hw_compat_11_1_len); + compat_props_add(m->compat_props, pc_compat_11_1, pc_compat_11_1_len); +} + +DEFINE_Q35_MACHINE(11, 1); static void pc_q35_machine_11_0_options(MachineClass *m) {
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index 636d00b..097de4e 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig
@@ -8,6 +8,9 @@ config PL190 bool +config HEX_L2VIC + bool + config IOAPIC bool select I8259
diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c new file mode 100644 index 0000000..f07ec85 --- /dev/null +++ b/hw/intc/hex-l2vic.c
@@ -0,0 +1,556 @@ +/* + * QEMU L2VIC Interrupt Controller + * + * Arm PrimeCell PL190 Vector Interrupt Controller was used as a reference. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/irq.h" +#include "hw/core/sysbus.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/bitmap.h" +#include "qemu/bitops.h" +#include "hw/intc/hex-l2vic.h" +#include "trace.h" + +#define L2VIC_VID_GRP_0 0x0 /* Read */ +#define L2VIC_VID_GRP_1 0x4 /* Read */ +#define L2VIC_VID_GRP_2 0x8 /* Read */ +#define L2VIC_VID_GRP_3 0xC /* Read */ +#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */ +#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */ +#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */ +#define L2VIC_INT_TYPEn 0x280 /* Read/Write */ +#define L2VIC_INT_STATUSn 0x380 /* Read */ +#define L2VIC_INT_CLEARn 0x400 /* Write */ +#define L2VIC_SOFT_INTn 0x480 /* Write */ +#define L2VIC_INT_PENDINGn 0x500 /* Read */ +#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */ +#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */ +#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */ +#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */ + +#define L2VIC_INTERRUPT_MAX 1024 +/* + * Note about l2vic groups: + * Each interrupt to L2VIC can be configured to associate with one of + * four groups. + * Group 0 interrupts go to IRQ2 via VID 0 (SSR: 0xC2, the default) + * Group 1 interrupts go to IRQ3 via VID 1 (SSR: 0xC3) + * Group 2 interrupts go to IRQ4 via VID 2 (SSR: 0xC4) + * Group 3 interrupts go to IRQ5 via VID 3 (SSR: 0xC5) + */ + +static void bitmap32_write_word(uint32_t *bitmap, int word_offset, uint32_t val) +{ + bitmap[word_offset] = val; +} + +static void bitmap32_clear_word(uint32_t *bitmap, int word_offset, + uint32_t mask) +{ + bitmap[word_offset] &= ~mask; +} + +static void bitmap32_set_word(uint32_t *bitmap, int word_offset, uint32_t mask) +{ + bitmap[word_offset] |= mask; +} + +static uint32_t bitmap32_read_word(uint32_t *bitmap, int word_offset) +{ + return bitmap[word_offset]; +} + +OBJECT_DECLARE_SIMPLE_TYPE(HexL2VICState, HEX_L2VIC) + +#define SLICE_MAX (L2VIC_INTERRUPT_MAX / 32) +#define L2VIC_REG_RANGE_SIZE 0x80 + +typedef struct HexL2VICState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion fast_iomem; + /* + * vid_group[i] is readable at L2VIC_VID_GRP_i (offset i*4): the irq + * last delivered through VID group i, 0-1023 so only 10 bits are used. + */ + uint32_t vid_group[4]; + /* + * Last irq delivered on any VID group; not specific to group 0. + * Used by the ciad path to clear the most-recently-delivered + * interrupt from int_status. + */ + uint32_t vid; + DECLARE_BITMAP32(int_enable, L2VIC_INTERRUPT_MAX); + /* Asserted interrupts awaiting delivery once no VID is active */ + DECLARE_BITMAP32(int_pending, L2VIC_INTERRUPT_MAX); + /* Which enabled interrupt is active */ + DECLARE_BITMAP32(int_status, L2VIC_INTERRUPT_MAX); + /* Edge or Level interrupt */ + DECLARE_BITMAP32(int_type, L2VIC_INTERRUPT_MAX); + DECLARE_BITMAP32(int_group_n[4], L2VIC_INTERRUPT_MAX); + qemu_irq irq[8]; +} HexL2VICState; + +typedef enum { + L2VIC_OP_WRITE, + L2VIC_OP_CLEAR, + L2VIC_OP_SET, + L2VIC_OP_NONE, +} L2VicWriteOp; + +typedef struct { + hwaddr base; + size_t state_offset; + L2VicWriteOp write_op; + bool write_only; +} L2VicRegRange; + +static const L2VicRegRange l2vic_reg_ranges[] = { + { L2VIC_INT_ENABLEn, offsetof(HexL2VICState, int_enable), + L2VIC_OP_WRITE, false }, + { L2VIC_INT_ENABLE_CLEARn, offsetof(HexL2VICState, int_enable), + L2VIC_OP_CLEAR, true }, + { L2VIC_INT_ENABLE_SETn, offsetof(HexL2VICState, int_enable), + L2VIC_OP_SET, true }, + { L2VIC_INT_TYPEn, offsetof(HexL2VICState, int_type), + L2VIC_OP_WRITE, false }, + { L2VIC_INT_STATUSn, offsetof(HexL2VICState, int_status), + L2VIC_OP_NONE, false }, + { L2VIC_INT_CLEARn, offsetof(HexL2VICState, int_status), + L2VIC_OP_CLEAR, true }, + { L2VIC_SOFT_INTn, offsetof(HexL2VICState, int_pending), + L2VIC_OP_NONE, true }, + { L2VIC_INT_PENDINGn, offsetof(HexL2VICState, int_pending), + L2VIC_OP_WRITE, false }, + { L2VIC_INT_GRPn_0, offsetof(HexL2VICState, int_group_n[0]), + L2VIC_OP_WRITE, false }, + { L2VIC_INT_GRPn_1, offsetof(HexL2VICState, int_group_n[1]), + L2VIC_OP_WRITE, false }, + { L2VIC_INT_GRPn_2, offsetof(HexL2VICState, int_group_n[2]), + L2VIC_OP_WRITE, false }, + { L2VIC_INT_GRPn_3, offsetof(HexL2VICState, int_group_n[3]), + L2VIC_OP_WRITE, false }, +}; + +static uint32_t *l2vic_state_bitmap(HexL2VICState *s, size_t state_offset) +{ + return (uint32_t *)((char *)s + state_offset); +} + +static bool l2vic_reg_read_range(HexL2VICState *s, hwaddr offset, + uint64_t *value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) { + const L2VicRegRange *r = &l2vic_reg_ranges[i]; + + if (offset >= r->base && + offset < r->base + L2VIC_REG_RANGE_SIZE) { + if (r->write_only) { + *value = 0; + } else { + uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset); + *value = bitmap32_read_word(bitmap, + (offset - r->base) >> 2); + } + return true; + } + } + return false; +} + +static bool l2vic_reg_write_range(HexL2VICState *s, hwaddr offset, + uint32_t val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) { + const L2VicRegRange *r = &l2vic_reg_ranges[i]; + + if (offset >= r->base && + offset < r->base + L2VIC_REG_RANGE_SIZE) { + uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset); + int word = (offset - r->base) >> 2; + + switch (r->write_op) { + case L2VIC_OP_WRITE: + bitmap32_write_word(bitmap, word, val); + break; + case L2VIC_OP_CLEAR: + bitmap32_clear_word(bitmap, word, val); + break; + case L2VIC_OP_SET: + bitmap32_set_word(bitmap, word, val); + break; + case L2VIC_OP_NONE: + /* Read-only or handled elsewhere; ignore the write. */ + break; + default: + g_assert_not_reached(); + } + return true; + } + } + return false; +} + +/* + * The four INT_GRPn_* register arrays are interleaved across irqs in + * blocks of 8: irq 0-7 live in group_n[0], irq 8-15 in group_n[1], irq + * 16-23 in group_n[2], irq 24-31 in group_n[3], irq 32-39 back in + * group_n[0], and so on. + */ +static uint32_t *get_int_group(HexL2VICState *s, int irq) +{ + return s->int_group_n[extract32(irq, 3, 2)]; +} + +static int find_slice(int irq) +{ + return irq / 32; +} + +static int get_vid(HexL2VICState *s, int irq) +{ + uint32_t *group = get_int_group(s, irq); + uint32_t slice = group[find_slice(irq)]; + uint32_t vid; + /* + * Each irq occupies a 4-bit field: bit 3 is the group-enable bit, + * bits 0-2 select the VID group. Shift down to this irq's field. + */ + uint32_t val = slice >> ((irq & 0x7) * 4); + + if (!(val & 0x8)) { + return 0; + } + vid = val & 0x7; + if (vid >= ARRAY_SIZE(s->vid_group)) { + qemu_log_mask(LOG_GUEST_ERROR, + "L2VIC: irq %d requests invalid vid group %u\n", + irq, vid); + return 0; + } + return vid; +} + +static inline bool vid_active(HexL2VICState *s) +{ + const uint32_t size = L2VIC_INTERRUPT_MAX; + const uint32_t active_irq = find_first_bit32(s->int_status, size); + return active_irq != size; +} + +static bool l2vic_update(HexL2VICState *s, int irq) +{ + bool pending; + bool enable; + + if (vid_active(s)) { + return true; + } + + pending = test_bit32(irq, s->int_pending); + enable = test_bit32(irq, s->int_enable); + if (pending && enable) { + int vid = get_vid(s, irq); + set_bit32(irq, s->int_status); + clear_bit32(irq, s->int_pending); + /* + * Only auto-disable for edge-triggered interrupts (type=1). + * Level-triggered interrupts (type=0, the default) keep their + * enable bit set across deliveries -- the firmware enables once + * and expects the interrupt to remain enabled. + */ + if (test_bit32(irq, s->int_type)) { + clear_bit32(irq, s->int_enable); + } + s->vid = irq; + s->vid_group[vid] = irq; + + qemu_irq_pulse(s->irq[vid + 2]); + trace_hex_l2vic_delivered(irq, vid); + return true; + } + return false; +} + +static void l2vic_update_all(HexL2VICState *s) +{ + for (int i = 0; i < L2VIC_INTERRUPT_MAX; i++) { + if (l2vic_update(s, i)) { + /* once vid is active, no-one else can set it until ciad */ + return; + } + } +} + +static void l2vic_set_irq(void *opaque, int irq, int level) +{ + HexL2VICState *s = (HexL2VICState *)opaque; + + if (level) { + set_bit32(irq, s->int_pending); + } + l2vic_update(s, irq); +} + +static void l2vic_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + HexL2VICState *s = (HexL2VICState *)opaque; + + trace_hex_l2vic_reg_write((unsigned)offset, (uint32_t)val); + + if (!l2vic_reg_write_range(s, offset, val)) { + qemu_log_mask(LOG_UNIMP, + "%s: offset 0x%" HWADDR_PRIx " unimplemented\n", + __func__, offset); + } + + /* SOFT_INT also sets pending for edge-triggered interrupts */ + if (offset >= L2VIC_SOFT_INTn && + offset < L2VIC_SOFT_INTn + L2VIC_REG_RANGE_SIZE && val) { + int base_irq = ((offset - L2VIC_SOFT_INTn) >> 2) * 32; + uint32_t bits = val; + int bit; + + while ((bit = ctz32(bits)) < 32) { + int irq = base_irq + bit; + + if (test_bit32(irq, s->int_type)) { + set_bit32(irq, s->int_pending); + } + bits &= ~(1u << bit); + } + } + + l2vic_update_all(s); +} + +static uint64_t l2vic_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t value; + HexL2VICState *s = (HexL2VICState *)opaque; + + if (offset <= L2VIC_VID_GRP_3) { + value = s->vid_group[offset >> 2]; + } else if (!l2vic_reg_read_range(s, offset, &value)) { + value = 0; + qemu_log_mask(LOG_GUEST_ERROR, + "L2VIC: %s: offset 0x%" HWADDR_PRIx "\n", __func__, + offset); + } + + trace_hex_l2vic_reg_read((unsigned)offset, (uint32_t)value); + return value; +} + +static const MemoryRegionOps l2vic_ops = { + .read = l2vic_read, + .write = l2vic_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +#define FASTL2VIC_ENABLE 0x0 +#define FASTL2VIC_DISABLE 0x1 +#define FASTL2VIC_INT 0x2 + +static void fastl2vic_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + if (offset == 0) { + uint32_t cmd = (val >> 16) & 0x3; + uint32_t irq = val & 0x3ff; + uint32_t slice = (irq / 32) * 4; + val = 1 << (irq % 32); + + if (cmd == FASTL2VIC_ENABLE) { + l2vic_write(opaque, L2VIC_INT_ENABLE_SETn + slice, val, size); + } else if (cmd == FASTL2VIC_DISABLE) { + l2vic_write(opaque, L2VIC_INT_ENABLE_CLEARn + slice, val, size); + } else if (cmd == FASTL2VIC_INT) { + l2vic_write(opaque, L2VIC_SOFT_INTn + slice, val, size); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid write cmd %" PRId32 "\n", + __func__, cmd); + } + return; + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write offset 0x%08" HWADDR_PRIx + "\n", __func__, offset); +} + +static uint64_t fastl2vic_read(void *opaque, hwaddr offset, unsigned size) +{ + return 0; +} + +static const MemoryRegionOps fastl2vic_ops = { + .read = fastl2vic_read, + .write = fastl2vic_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +static uint32_t l2vic_interface_read_vid_impl(HexL2VicInterface *iface, + uint32_t group) +{ + HexL2VICState *s = HEX_L2VIC(iface); + uint32_t result = 0; + + if (group == 0) { + /* VID register combines vid_group[0] (VID0) and vid_group[1] (VID1) */ + result = deposit32(result, 0, 16, s->vid_group[0]); + result = deposit32(result, 16, 16, s->vid_group[1]); + } else if (group == 1) { + /* VID1 register combines vid_group[2] (VID2) and vid_group[3] (VID3) */ + result = deposit32(result, 0, 16, s->vid_group[2]); + result = deposit32(result, 16, 16, s->vid_group[3]); + } + return result; +} + +static void l2vic_interface_update_vid_impl(HexL2VicInterface *iface, + uint32_t group, uint32_t value) +{ + HexL2VICState *s = HEX_L2VIC(iface); + + if (group == 0) { + s->vid_group[0] = extract32(value, 0, 16); + s->vid_group[1] = extract32(value, 16, 16); + } else if (group == 1) { + s->vid_group[2] = extract32(value, 0, 16); + s->vid_group[3] = extract32(value, 16, 16); + } + + l2vic_update_all(s); +} + +static void l2vic_interface_clear_interrupt_impl(HexL2VicInterface *iface) +{ + HexL2VICState *s = HEX_L2VIC(iface); + + if (s->vid < L2VIC_INTERRUPT_MAX) { + clear_bit32(s->vid, s->int_status); + } + l2vic_update_all(s); +} + +static void l2vic_reset_hold(Object *obj, ResetType type G_GNUC_UNUSED) +{ + HexL2VICState *s = HEX_L2VIC(obj); + + memset(s->int_enable, 0, sizeof(s->int_enable)); + memset(s->int_pending, 0, sizeof(s->int_pending)); + memset(s->int_status, 0, sizeof(s->int_status)); + memset(s->int_type, 0, sizeof(s->int_type)); + memset(s->int_group_n, 0, sizeof(s->int_group_n)); + memset(s->vid_group, 0, sizeof(s->vid_group)); + s->vid = 0; + + l2vic_update_all(s); +} + +static void reset_irq_handler(void *opaque, int irq, int level) +{ + Object *obj = OBJECT(opaque); + + if (level) { + l2vic_reset_hold(obj, RESET_TYPE_COLD); + } +} + +static void l2vic_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + HexL2VICState *s = HEX_L2VIC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + int i; + + memory_region_init_io(&s->iomem, obj, &l2vic_ops, s, "l2vic", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + memory_region_init_io(&s->fast_iomem, obj, &fastl2vic_ops, s, "fast", + 0x10000); + sysbus_init_mmio(sbd, &s->fast_iomem); + + qdev_init_gpio_in(dev, l2vic_set_irq, L2VIC_INTERRUPT_MAX); + qdev_init_gpio_in_named(dev, reset_irq_handler, "reset", 1); + for (i = 0; i < 8; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } +} + +static const VMStateDescription vmstate_l2vic = { + .name = "l2vic", + .version_id = 1, + .minimum_version_id = 1, + .fields = + (VMStateField[]){ + VMSTATE_UINT32_ARRAY(vid_group, HexL2VICState, 4), + VMSTATE_UINT32(vid, HexL2VICState), + VMSTATE_UINT32_ARRAY(int_enable, HexL2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_type, HexL2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_status, HexL2VICState, SLICE_MAX), + VMSTATE_UINT32_ARRAY(int_pending, HexL2VICState, SLICE_MAX), + VMSTATE_UINT32_2DARRAY(int_group_n, HexL2VICState, 4, SLICE_MAX), + VMSTATE_END_OF_LIST() } +}; + +static void l2vic_interface_class_init(ObjectClass *klass, const void *data) +{ + HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_CLASS(klass); + + k->read_vid = l2vic_interface_read_vid_impl; + k->update_vid = l2vic_interface_update_vid_impl; + k->clear_interrupt = l2vic_interface_clear_interrupt_impl; +} + +static void l2vic_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->vmsd = &vmstate_l2vic; + rc->phases.hold = l2vic_reset_hold; +} + +static const TypeInfo l2vic_interface_info = { + .name = TYPE_HEX_L2VIC_INTERFACE, + .parent = TYPE_INTERFACE, + .class_size = sizeof(HexL2VicInterfaceClass), + .class_init = l2vic_interface_class_init, +}; + +static const TypeInfo l2vic_info = { + .name = TYPE_HEX_L2VIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HexL2VICState), + .instance_init = l2vic_init, + .class_init = l2vic_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HEX_L2VIC_INTERFACE }, + { } + }, +}; + +static const TypeInfo l2vic_types[] = { + l2vic_interface_info, + l2vic_info, +}; + +DEFINE_TYPES(l2vic_types)
diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 377de78..a1401cb 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build
@@ -74,6 +74,8 @@ specific_ss.add(when: 'CONFIG_XIVE', if_true: files('xive.c')) specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'], if_true: files('spapr_xive_kvm.c')) + +specific_ss.add(when: 'CONFIG_HEX_L2VIC', if_true: files('hex-l2vic.c')) specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c')) specific_ss.add(when: 'CONFIG_LOONGSON_IPI_COMMON', if_true: files('loongson_ipi_common.c')) specific_ss.add(when: 'CONFIG_LOONGSON_IPI', if_true: files('loongson_ipi.c'))
diff --git a/hw/intc/trace-events b/hw/intc/trace-events index e7d6c30..7512c6e 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events
@@ -337,6 +337,10 @@ sh_intc_read(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " -> 0x%lx" sh_intc_write(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " <- 0x%lx" sh_intc_set(int id, int enable) "setting interrupt group %d to %d" +# hex-l2vic.c +hex_l2vic_reg_write(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32 +hex_l2vic_reg_read(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32 +hex_l2vic_delivered(int irq, int vid) "l2vic: delivered %d (vid %d)" # loongson_ipi.c loongson_ipi_read(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 6693dea..9cc7992 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c
@@ -1465,7 +1465,6 @@ mc->default_ram_id = "loongarch.ram"; mc->desc = "QEMU LoongArch Virtual Machine"; mc->max_cpus = LOONGARCH_MAX_CPUS; - mc->is_default = 1; mc->default_kernel_irqchip_split = false; mc->block_default_type = IF_VIRTIO; mc->default_boot_order = "c"; @@ -1546,6 +1545,7 @@ MACHINE_VER_DEPRECATION(__VA_ARGS__); \ if (latest) { \ mc->alias = "virt"; \ + mc->is_default = true; \ } \ } \ static const TypeInfo MACHINE_VER_SYM(info, virt, __VA_ARGS__) = \ @@ -1600,7 +1600,14 @@ type_init(machvirt_machine_init); -static void virt_machine_11_1_options(MachineClass *mc) +static void virt_machine_11_2_options(MachineClass *mc) { } -DEFINE_VIRT_MACHINE_AS_LATEST(11, 1) +DEFINE_VIRT_MACHINE_AS_LATEST(11, 2) + +static void virt_machine_11_1_options(MachineClass *mc) +{ + virt_machine_11_2_options(mc); + compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len); +} +DEFINE_VIRT_MACHINE(11, 1)
diff --git a/hw/m68k/virt.c b/hw/m68k/virt.c index 51158ce..2749709 100644 --- a/hw/m68k/virt.c +++ b/hw/m68k/virt.c
@@ -367,10 +367,17 @@ #define DEFINE_VIRT_MACHINE(major, minor) \ DEFINE_VIRT_MACHINE_IMPL(false, major, minor) -static void virt_machine_11_1_options(MachineClass *mc) +static void virt_machine_11_2_options(MachineClass *mc) { } -DEFINE_VIRT_MACHINE_AS_LATEST(11, 1) +DEFINE_VIRT_MACHINE_AS_LATEST(11, 2) + +static void virt_machine_11_1_options(MachineClass *mc) +{ + virt_machine_11_2_options(mc); + compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len); +} +DEFINE_VIRT_MACHINE(11, 1) static void virt_machine_11_0_options(MachineClass *mc) {
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 1543ee6..b8860dd 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig
@@ -257,4 +257,7 @@ config XLNX_ZYNQ_DDRC bool +config AXIADO_CLK + bool + source macio/Kconfig
diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index c61efe5..8de05a9 100644 --- a/hw/misc/aspeed_hace.c +++ b/hw/misc/aspeed_hace.c
@@ -18,11 +18,58 @@ #include "qapi/error.h" #include "migration/vmstate.h" #include "crypto/hash.h" +#include "crypto/cipher.h" #include "hw/core/qdev-properties.h" #include "hw/core/irq.h" #include "trace.h" -#define R_CRYPT_CMD (0x10 / 4) +/* Crypto engine registers */ +#define R_CRYPT_SRC (0x00 / 4) +#define R_CRYPT_DEST (0x04 / 4) +#define R_CRYPT_CONTEXT (0x08 / 4) +#define R_CRYPT_DATA_LEN (0x0c / 4) +/* HACE0C[27:0] holds the crypto data length */ +#define CRYPT_DATA_LEN_MASK 0x0FFFFFFF +#define R_CRYPT_CMD (0x10 / 4) +/* AES-GCM associated data length (HACE14) and tag write buffer (HACE18) */ +#define R_CRYPT_GCM_ADD_LEN (0x14 / 4) +#define R_CRYPT_GCM_TAG (0x18 / 4) +/* Crypto engine command register (HACE10) bits */ +#define CRYPT_CMD_ENCRYPT BIT(7) +#define CRYPT_CMD_ISR_EN BIT(12) +#define CRYPT_CMD_DES_SELECT BIT(16) +#define CRYPT_CMD_TRIPLE_DES BIT(17) +#define CRYPT_CMD_SRC_SG_CTRL BIT(18) +/* Operation mode HACE10[6:4] */ +#define CRYPT_CMD_OP_MODE_MASK (0x7 << 4) +#define CRYPT_CMD_ECB (0x0 << 4) +#define CRYPT_CMD_CBC (0x1 << 4) +#define CRYPT_CMD_CTR (0x4 << 4) +#define CRYPT_CMD_GCM (0x5 << 4) +/* AES key length HACE10[3:2] */ +#define CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2) +#define CRYPT_CMD_AES256 (0x2 << 2) +#define CRYPT_CMD_AES192 (0x1 << 2) +#define CRYPT_CMD_AES128 (0x0 << 2) + +/* + * Crypto context buffer layout (HACE08). The IV is at the start of the buffer + * (DES places its 8 byte IV at offset 8) and the cipher key at offset 0x10. + */ +#define CRYPT_CTX_IV_OFFSET 0x00 +#define CRYPT_CTX_DES_IV_OFFSET 0x08 +#define CRYPT_CTX_KEY_OFFSET 0x10 +#define CRYPT_CTX_SIZE 0x30 + +/* AES-GCM uses a 96-bit IV and a 128-bit authentication tag */ +#define CRYPT_GCM_IV_LEN 12 +#define CRYPT_GCM_TAG_LEN 16 + +/* AST2700 64-bit DMA high address registers for the crypto command */ +#define R_CRYPT_SRC_HI (0x80 / 4) +#define R_CRYPT_DEST_HI (0x84 / 4) +#define R_CRYPT_CONTEXT_HI (0x88 / 4) +#define R_CRYPT_GCM_TAG_HI (0x8c / 4) #define R_STATUS (0x1c / 4) #define HASH_IRQ BIT(9) @@ -65,7 +112,6 @@ /* Other cmd bits */ #define HASH_IRQ_EN BIT(9) #define HASH_SG_EN BIT(18) -#define CRYPT_IRQ_EN BIT(12) /* Scatter-gather data list */ #define SG_LIST_LEN_SIZE 4 #define SG_LIST_LEN_MASK 0x0FFFFFFF @@ -501,6 +547,368 @@ } } +static bool crypt_aes_alg(uint32_t cmd, QCryptoCipherAlgo *alg, size_t *keylen) +{ + switch (cmd & CRYPT_CMD_AES_KEY_LEN_MASK) { + case CRYPT_CMD_AES128: + *alg = QCRYPTO_CIPHER_ALGO_AES_128; + *keylen = 16; + break; + case CRYPT_CMD_AES192: + *alg = QCRYPTO_CIPHER_ALGO_AES_192; + *keylen = 24; + break; + case CRYPT_CMD_AES256: + *alg = QCRYPTO_CIPHER_ALGO_AES_256; + *keylen = 32; + break; + default: + return false; + } + + return true; +} + +/* + * Decode the crypto command register into a libqcrypto algorithm/mode pair + * and the block/IV geometry. Returns false for unsupported selections. + */ +static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg, + QCryptoCipherMode *mode, size_t *keylen, + size_t *blocklen, size_t *iv_offset) +{ + if (cmd & CRYPT_CMD_DES_SELECT) { + *blocklen = 8; + *iv_offset = CRYPT_CTX_DES_IV_OFFSET; + if (cmd & CRYPT_CMD_TRIPLE_DES) { + *alg = QCRYPTO_CIPHER_ALGO_3DES; + *keylen = 24; + } else { + *alg = QCRYPTO_CIPHER_ALGO_DES; + *keylen = 8; + } + } else { + *blocklen = 16; + *iv_offset = CRYPT_CTX_IV_OFFSET; + if (!crypt_aes_alg(cmd, alg, keylen)) { + return false; + } + } + + switch (cmd & CRYPT_CMD_OP_MODE_MASK) { + case CRYPT_CMD_ECB: + *mode = QCRYPTO_CIPHER_MODE_ECB; + break; + case CRYPT_CMD_CBC: + *mode = QCRYPTO_CIPHER_MODE_CBC; + break; + case CRYPT_CMD_CTR: + *mode = QCRYPTO_CIPHER_MODE_CTR; + break; + case CRYPT_CMD_GCM: + *mode = QCRYPTO_CIPHER_MODE_GCM; + break; + default: + return false; + } + + return true; +} + +/* + * Direct access mode: the source/destination register (HACE00/HACE04) points + * at a single contiguous buffer in DRAM. Copy @len bytes between it and the + * bounce buffer @buf; when @to_dram is true @buf is written out, otherwise it + * is read in. Returns true on success. + */ +static bool crypt_prepare_direct(AspeedHACEState *s, uint64_t addr, + uint8_t *buf, uint32_t len, bool to_dram) +{ + return !address_space_rw(&s->dram_as, addr, MEMTXATTRS_UNSPECIFIED, + buf, len, to_dram); +} + +/* + * Scatter-gather mode: the source/destination register points at an SG list + * whose entries are a length word (SG_LIST_LEN_LAST flags the final entry) + * followed by a DRAM address, matching the hash engine layout. Gather @len + * bytes into @buf, or scatter @buf back out when @to_dram is true. + * Returns true on success. + */ +static bool crypt_prepare_sg(AspeedHACEState *s, uint64_t addr, + uint8_t *buf, uint32_t len, bool to_dram) +{ + uint32_t copied = 0; + uint32_t sg_addr; + uint32_t sg_len; + uint32_t entry; + int i; + + for (i = 0; i < ASPEED_HACE_MAX_SG && copied < len; i++) { + entry = address_space_ldl_le(&s->dram_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + sg_addr = address_space_ldl_le(&s->dram_as, addr + SG_LIST_LEN_SIZE, + MEMTXATTRS_UNSPECIFIED, NULL); + sg_len = entry & SG_LIST_LEN_MASK; + + sg_addr &= SG_LIST_ADDR_MASK; + addr += SG_LIST_ENTRY_SIZE; + + if (sg_len > len - copied) { + sg_len = len - copied; + } + if (address_space_rw(&s->dram_as, sg_addr, MEMTXATTRS_UNSPECIFIED, + buf + copied, sg_len, to_dram)) { + return false; + } + copied += sg_len; + + if (entry & SG_LIST_LEN_LAST) { + break; + } + } + + return copied == len; +} + +/* + * Add @add to the big-endian counter block @ctr (@len bytes) in place, so the + * CTR mode counter can be advanced by the number of blocks just consumed. + */ +static void crypt_be_add(uint8_t *ctr, size_t len, uint64_t add) +{ + size_t i = len; + + while (i > 0 && add) { + i--; + add += ctr[i]; + ctr[i] = add & 0xff; + add >>= 8; + } +} + +static uint64_t crypt_get_addr(AspeedHACEState *s, int reg, int reg_hi) +{ + AspeedHACEClass *ahc = ASPEED_HACE_GET_CLASS(s); + uint64_t addr; + + addr = deposit64(0, 0, 32, s->regs[reg]); + if (ahc->has_dma64) { + addr = deposit64(addr, 32, 32, s->regs[reg_hi]); + } + + return addr; +} + +/* + * Perform an AES/DES/3DES ECB/CBC/CTR or AES-GCM operation. The source and + * destination are either single contiguous buffers (direct access mode) or + * scatter-gather lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the + * IV/key come from the context buffer (HACE08). For CBC and CTR the resulting + * chaining state is written back to the context buffer so the driver can + * continue; for GCM the authentication tag is written to the tag buffer. + */ +static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) +{ + bool sg_mode = cmd & CRYPT_CMD_SRC_SG_CTRL; + uint32_t len = s->regs[R_CRYPT_DATA_LEN]; + bool encrypt = cmd & CRYPT_CMD_ENCRYPT; + g_autoptr(QCryptoCipher) cipher = NULL; + g_autofree uint8_t *src_buf = NULL; + g_autofree uint8_t *dst_buf = NULL; + uint8_t tag[CRYPT_GCM_TAG_LEN]; + uint8_t ctx[CRYPT_CTX_SIZE]; + Error *local_err = NULL; + QCryptoCipherMode mode; + QCryptoCipherAlgo alg; + const uint8_t *next_iv; + uint64_t ctx_addr; + uint64_t src_addr; + uint64_t dst_addr; + uint64_t tag_addr; + uint32_t aad_len; + size_t iv_offset; + size_t blocklen; + size_t buf_len; + size_t keylen; + size_t ivlen; + bool status; + + if (len == 0) { + return; + } + + if (!crypt_decode_cmd(cmd, &alg, &mode, &keylen, &blocklen, &iv_offset)) { + qemu_log_mask(LOG_UNIMP, + "%s: Unsupported crypt command 0x%x\n", __func__, cmd); + return; + } + + if (!qcrypto_cipher_supports(alg, mode)) { + qemu_log_mask(LOG_UNIMP, + "%s: cipher mode not supported by the crypto backend\n", + __func__); + return; + } + + /* GCM uses a 96-bit IV; the block modes use a full-block IV. */ + ivlen = (mode == QCRYPTO_CIPHER_MODE_GCM) ? CRYPT_GCM_IV_LEN : blocklen; + + /* + * The hardware GCM path is only exercised without associated data (the + * driver falls back to software when there is any), so AAD is not modelled. + */ + aad_len = s->regs[R_CRYPT_GCM_ADD_LEN]; + if (mode == QCRYPTO_CIPHER_MODE_GCM && aad_len != 0) { + qemu_log_mask(LOG_UNIMP, + "%s: GCM associated data is not implemented\n", __func__); + return; + } + + /* Fetch the IV and key from the context buffer in DRAM. */ + ctx_addr = crypt_get_addr(s, R_CRYPT_CONTEXT, R_CRYPT_CONTEXT_HI); + if (address_space_read(&s->dram_as, ctx_addr, MEMTXATTRS_UNSPECIFIED, + ctx, sizeof(ctx))) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to read context, addr=0x%" HWADDR_PRIx "\n", + __func__, ctx_addr); + return; + } + + if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) { + hace_hexdump("context", (char *)ctx, sizeof(ctx)); + } + + cipher = qcrypto_cipher_new(alg, mode, ctx + CRYPT_CTX_KEY_OFFSET, keylen, + &local_err); + if (cipher == NULL) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher new failed: %s\n", + __func__, error_get_pretty(local_err)); + error_free(local_err); + return; + } + + if (mode != QCRYPTO_CIPHER_MODE_ECB && + qcrypto_cipher_setiv(cipher, ctx + iv_offset, ivlen, + &local_err) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv failed: %s\n", + __func__, error_get_pretty(local_err)); + error_free(local_err); + return; + } + + /* + * Round the working buffers up to a whole block. Block modes are already + * block-aligned; the stream-like CTR mode may leave a partial final block + * that the engine still processes a full block at a time. GCM handles a + * partial final block itself, so it operates on the exact length. + */ + buf_len = (mode == QCRYPTO_CIPHER_MODE_GCM) ? + len : QEMU_ALIGN_UP(len, blocklen); + src_buf = g_malloc0(buf_len); + dst_buf = g_malloc0(buf_len); + + /* Gather the source into the bounce buffer, per the selected mode. */ + src_addr = crypt_get_addr(s, R_CRYPT_SRC, R_CRYPT_SRC_HI); + if (sg_mode) { + status = crypt_prepare_sg(s, src_addr, src_buf, len, false); + } else { + status = crypt_prepare_direct(s, src_addr, src_buf, len, false); + } + if (!status) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to read src, addr=0x%" HWADDR_PRIx "\n", + __func__, src_addr); + return; + } + + if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) { + hace_hexdump("src", (char *)src_buf, len); + } + + if (encrypt) { + if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, buf_len, + &local_err) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: encrypt failed: %s\n", + __func__, error_get_pretty(local_err)); + error_free(local_err); + return; + } + } else { + if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, buf_len, + &local_err) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: decrypt failed: %s\n", + __func__, error_get_pretty(local_err)); + error_free(local_err); + return; + } + } + + /* Scatter the result back out, per the selected mode. */ + dst_addr = crypt_get_addr(s, R_CRYPT_DEST, R_CRYPT_DEST_HI); + if (sg_mode) { + status = crypt_prepare_sg(s, dst_addr, dst_buf, len, true); + } else { + status = crypt_prepare_direct(s, dst_addr, dst_buf, len, true); + } + if (!status) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to write dst, addr=0x%" HWADDR_PRIx "\n", + __func__, dst_addr); + return; + } + + if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) { + hace_hexdump("dst", (char *)dst_buf, len); + } + + if (mode == QCRYPTO_CIPHER_MODE_CBC) { + /* + * CBC chains on the last ciphertext block: the final block of the + * output when encrypting, or of the input when decrypting. Write it + * back as the IV for the next request. + */ + next_iv = (encrypt ? dst_buf : src_buf) + buf_len - blocklen; + if (address_space_write(&s->dram_as, ctx_addr + iv_offset, + MEMTXATTRS_UNSPECIFIED, next_iv, blocklen)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n", + __func__, ctx_addr + iv_offset); + } + } else if (mode == QCRYPTO_CIPHER_MODE_CTR) { + /* + * CTR chains on the counter, which advances by one per block. Add the + * number of blocks processed (buf_len / blocklen) and write it back. + */ + crypt_be_add(ctx + iv_offset, blocklen, buf_len / blocklen); + if (address_space_write(&s->dram_as, ctx_addr + iv_offset, + MEMTXATTRS_UNSPECIFIED, ctx + iv_offset, + blocklen)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n", + __func__, ctx_addr + iv_offset); + } + } else if (mode == QCRYPTO_CIPHER_MODE_GCM) { + /* + * GCM authenticates the message and writes the resulting tag to the + * dedicated tag buffer (HACE18/HACE8C). + */ + if (qcrypto_cipher_gettag(cipher, tag, sizeof(tag), &local_err) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher gettag failed: " + "%s\n", __func__, error_get_pretty(local_err)); + error_free(local_err); + return; + } + tag_addr = crypt_get_addr(s, R_CRYPT_GCM_TAG, R_CRYPT_GCM_TAG_HI); + if (address_space_write(&s->dram_as, tag_addr, MEMTXATTRS_UNSPECIFIED, + tag, sizeof(tag))) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to write tag, addr=0x%" HWADDR_PRIx "\n", + __func__, tag_addr); + } + } +} + static uint64_t aspeed_hace_read(void *opaque, hwaddr addr, unsigned int size) { AspeedHACEState *s = ASPEED_HACE(opaque); @@ -531,16 +939,24 @@ qemu_irq_lower(s->irq); } } - if (ahc->raise_crypt_interrupt_workaround) { - if (data & CRYPT_IRQ) { - data &= ~CRYPT_IRQ; + if (data & CRYPT_IRQ) { + data &= ~CRYPT_IRQ; - if (s->regs[addr] & CRYPT_IRQ) { - qemu_irq_lower(s->irq); - } + if (s->regs[addr] & CRYPT_IRQ) { + qemu_irq_lower(s->irq); } } break; + case R_CRYPT_SRC: + case R_CRYPT_DEST: + case R_CRYPT_CONTEXT: + case R_CRYPT_GCM_TAG: + data &= ahc->src_mask; + break; + case R_CRYPT_DATA_LEN: + case R_CRYPT_GCM_ADD_LEN: + data &= CRYPT_DATA_LEN_MASK; + break; case R_HASH_SRC: data &= ahc->src_mask; break; @@ -589,13 +1005,12 @@ break; } case R_CRYPT_CMD: - qemu_log_mask(LOG_UNIMP, "%s: Crypt commands not implemented\n", - __func__); - if (ahc->raise_crypt_interrupt_workaround) { - s->regs[R_STATUS] |= CRYPT_IRQ; - if (data & CRYPT_IRQ_EN) { - qemu_irq_raise(s->irq); - } + do_crypt_operation(s, data); + + /* Hardware raises the crypt interrupt once the command finishes. */ + s->regs[R_STATUS] |= CRYPT_IRQ; + if (data & CRYPT_CMD_ISR_EN) { + qemu_irq_raise(s->irq); } break; case R_HASH_SRC_HI: @@ -607,6 +1022,16 @@ case R_HASH_KEY_BUFF_HI: data &= ahc->key_hi_mask; break; + case R_CRYPT_SRC_HI: + data &= ahc->src_hi_mask; + break; + case R_CRYPT_DEST_HI: + case R_CRYPT_GCM_TAG_HI: + data &= ahc->dest_hi_mask; + break; + case R_CRYPT_CONTEXT_HI: + data &= ahc->key_hi_mask; + break; default: break; } @@ -782,12 +1207,6 @@ ahc->dest_hi_mask = 0x00000003; ahc->key_hi_mask = 0x00000003; - /* - * Currently, it does not support the CRYPT command. Instead, it only - * sends an interrupt to notify the firmware that the crypt command - * has completed. It is a temporary workaround. - */ - ahc->raise_crypt_interrupt_workaround = true; ahc->has_dma64 = true; }
diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index 5dbf81c..ca93c36 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c
@@ -930,6 +930,11 @@ s->regs[AST2700_HW_STRAP1] = s->hw_strap1; } +static void aspeed_2700_scu_realize(DeviceState *dev, Error **errp) +{ + aspeed_scu_realize(dev, errp); +} + static void aspeed_2700_scu_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -937,6 +942,7 @@ AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass); dc->desc = "ASPEED 2700 System Control Unit"; + dc->realize = aspeed_2700_scu_realize; rc->phases.hold = aspeed_ast2700_scu_reset_hold; asc->resets = ast2700_a0_resets; asc->calc_hpll = aspeed_2600_scu_calc_hpll; @@ -1063,6 +1069,16 @@ [AST2700_SCUIO_FREQ_CNT_CTL] = 0x00000080, }; +static void aspeed_ast2700_scuio_reset_hold(Object *obj, ResetType type) +{ + AspeedSCUState *s = ASPEED_SCU(obj); + AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(obj); + + memcpy(s->regs, asc->resets, asc->nr_regs * 4); + s->regs[AST2700_SILICON_REV] = s->silicon_rev; + s->regs[AST2700_HW_STRAP1] = s->hw_strap1; +} + static void aspeed_2700_scuio_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -1070,7 +1086,7 @@ AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass); dc->desc = "ASPEED 2700 System Control Unit I/O"; - rc->phases.hold = aspeed_ast2700_scu_reset_hold; + rc->phases.hold = aspeed_ast2700_scuio_reset_hold; asc->resets = ast2700_a0_resets_io; asc->calc_hpll = aspeed_2600_scu_calc_hpll; asc->get_apb = aspeed_2700_scuio_get_apb_freq; @@ -1161,7 +1177,7 @@ { .name = TYPE_ASPEED_2700_SCU, .parent = TYPE_ASPEED_SCU, - .instance_size = sizeof(AspeedSCUState), + .instance_size = sizeof(Aspeed2700SCUState), .class_init = aspeed_2700_scu_class_init, }, {
diff --git a/hw/misc/axiado_clk.c b/hw/misc/axiado_clk.c new file mode 100644 index 0000000..090beb3 --- /dev/null +++ b/hw/misc/axiado_clk.c
@@ -0,0 +1,79 @@ +/* + * Axiado Clock Control + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/axiado_clk.h" +#include "qemu/log.h" + +#define CLKRST_CPU_PLL_POSTDIV_OFFSET 0x0C +#define CLKRST_CPU_PLL_STS_OFFSET 0x14 + +static uint64_t pll_read(void *opaque, hwaddr offset, unsigned size) +{ + switch (offset) { + case CLKRST_CPU_PLL_POSTDIV_OFFSET: + return 0x20891b; + case CLKRST_CPU_PLL_STS_OFFSET: + return 0x01; + default: + qemu_log_mask(LOG_UNIMP, + "Register 0x%" HWADDR_PRIx " not implemented\n", offset); + break; + } + return 0x00; +} + +static void pll_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) +{ + qemu_log_mask(LOG_UNIMP, + "Register 0x%" HWADDR_PRIx " not implemented\n", offset); +} + +static const MemoryRegionOps pll_ops = { + .read = pll_read, + .write = pll_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +static void ax3000_clk_init(Object *obj) +{ + Ax3000ClkState *s = AX3000_CLK(obj); + + memory_region_init_io(&s->pll_ctrl, obj, &pll_ops, s, + TYPE_AX3000_CLK, AX3000_CLK_PLL_CTRL_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->pll_ctrl); +} + +static void ax3000_clk_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "Axiado AX3000 Clock Control"; +} + +static const TypeInfo ax3000_clk_info = { + .parent = TYPE_SYS_BUS_DEVICE, + .name = TYPE_AX3000_CLK, + .instance_size = sizeof(Ax3000ClkState), + .instance_init = ax3000_clk_init, + .class_init = ax3000_clk_class_init, +}; + +static void axiado_clk_register_type(void) +{ + type_register_static(&ax3000_clk_info); +} +type_init(axiado_clk_register_type);
diff --git a/hw/misc/bcm2835_powermgt.c b/hw/misc/bcm2835_powermgt.c index 3ec7aba..7b01be4 100644 --- a/hw/misc/bcm2835_powermgt.c +++ b/hw/misc/bcm2835_powermgt.c
@@ -19,10 +19,40 @@ #define PASSWORD_MASK 0xff000000 #define R_RSTC 0x1c -#define V_RSTC_RESET 0x20 +#define V_RSTC_WRCFG_MASK 0x30 +#define V_RSTC_FULL_RESET 0x20 #define R_RSTS 0x20 #define V_RSTS_POWEROFF 0x555 /* Linux uses partition 63 to indicate halt. */ #define R_WDOG 0x24 +#define V_WDOG_TIME_MASK 0xfffff +#define WDOG_TICKS_PER_SECOND 65536 + +static void bcm2835_powermgt_expire(void *opaque) +{ + BCM2835PowerMgtState *s = opaque; + + if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) { + qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); + } else { + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); + } +} + +static void bcm2835_powermgt_update_wdog(BCM2835PowerMgtState *s) +{ + uint64_t timeout_ns; + + if ((s->rstc & V_RSTC_WRCFG_MASK) != V_RSTC_FULL_RESET || + s->wdog == 0) { + timer_del(s->wdog_timer); + return; + } + + timeout_ns = muldiv64(s->wdog, NANOSECONDS_PER_SECOND, + WDOG_TICKS_PER_SECOND); + timer_mod(s->wdog_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout_ns); +} static uint64_t bcm2835_powermgt_read(void *opaque, hwaddr offset, unsigned size) @@ -70,13 +100,7 @@ switch (offset) { case R_RSTC: s->rstc = value; - if (value & V_RSTC_RESET) { - if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) { - qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); - } else { - qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); - } - } + bcm2835_powermgt_update_wdog(s); break; case R_RSTS: qemu_log_mask(LOG_UNIMP, @@ -84,9 +108,8 @@ s->rsts = value; break; case R_WDOG: - qemu_log_mask(LOG_UNIMP, - "bcm2835_powermgt_write: WDOG\n"); - s->wdog = value; + s->wdog = value & V_WDOG_TIME_MASK; + bcm2835_powermgt_update_wdog(s); break; default: @@ -107,12 +130,13 @@ static const VMStateDescription vmstate_bcm2835_powermgt = { .name = TYPE_BCM2835_POWERMGT, - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (const VMStateField[]) { VMSTATE_UINT32(rstc, BCM2835PowerMgtState), VMSTATE_UINT32(rsts, BCM2835PowerMgtState), VMSTATE_UINT32(wdog, BCM2835PowerMgtState), + VMSTATE_TIMER_PTR(wdog_timer, BCM2835PowerMgtState), VMSTATE_END_OF_LIST() } }; @@ -124,6 +148,8 @@ memory_region_init_io(&s->iomem, obj, &bcm2835_powermgt_ops, s, TYPE_BCM2835_POWERMGT, 0x200); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); + s->wdog_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + bcm2835_powermgt_expire, s); } static void bcm2835_powermgt_reset(DeviceState *dev) @@ -134,6 +160,7 @@ s->rstc = 0x00000102; s->rsts = 0x00001000; s->wdog = 0x00000000; + timer_del(s->wdog_timer); } static void bcm2835_powermgt_class_init(ObjectClass *klass, const void *data)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 23265f6..e86d9ad 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build
@@ -1,4 +1,5 @@ system_ss.add(when: 'CONFIG_APPLESMC', if_true: files('applesmc.c')) + system_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c')) system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c')) system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c')) @@ -168,3 +169,5 @@ # HPPA devices system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c')) + +system_ss.add(when: 'CONFIG_AXIADO_CLK', if_true: files('axiado_clk.c'))
diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig index 8cbb830..bda6e16 100644 --- a/hw/pci-host/Kconfig +++ b/hw/pci-host/Kconfig
@@ -49,6 +49,7 @@ config PCI_EXPRESS_ASPEED bool select PCI_EXPRESS + select PCIE_PORT config PCI_EXPRESS_Q35 bool
diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index b79828b..7e284ed 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c
@@ -4764,14 +4764,25 @@ DEFINE_SPAPR_MACHINE_IMPL(false, major, minor) /* - * pseries-11.1 + * pseries-11.2 */ -static void spapr_machine_11_1_class_options(MachineClass *mc) +static void spapr_machine_11_2_class_options(MachineClass *mc) { /* Defaults for the latest behaviour inherited from the base class */ } -DEFINE_SPAPR_MACHINE_AS_LATEST(11, 1); +DEFINE_SPAPR_MACHINE_AS_LATEST(11, 2); + +/* + * pseries-11.1 + */ +static void spapr_machine_11_1_class_options(MachineClass *mc) +{ + spapr_machine_11_2_class_options(mc); + compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len); +} + +DEFINE_SPAPR_MACHINE(11, 1); /* * pseries-11.0
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h index fac3076..ef9c063 100644 --- a/hw/s390x/ipl.h +++ b/hw/s390x/ipl.h
@@ -124,6 +124,12 @@ return false; } + if (offsetof(IplParameterBlock, pv.components) + + ipib_pv->num_comp * sizeof(IPLBlockPVComp) > + be32_to_cpu(iplb->len)) { + return false; + } + for (i = 0; i < ipib_pv->num_comp; i++) { /* Addr must be 4k aligned */ if (ipib_pv->components[i].addr & ~TARGET_PAGE_MASK) {
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index 25a9fa4..55131c1 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c
@@ -925,14 +925,26 @@ DEFINE_CCW_MACHINE_IMPL(false, major, minor) +static void ccw_machine_11_2_instance_options(MachineState *machine) +{ +} + +static void ccw_machine_11_2_class_options(MachineClass *mc) +{ +} +DEFINE_CCW_MACHINE_AS_LATEST(11, 2); + static void ccw_machine_11_1_instance_options(MachineState *machine) { + ccw_machine_11_2_instance_options(machine); } static void ccw_machine_11_1_class_options(MachineClass *mc) { + ccw_machine_11_2_class_options(mc); + compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len); } -DEFINE_CCW_MACHINE_AS_LATEST(11, 1); +DEFINE_CCW_MACHINE(11, 1); static void ccw_machine_11_0_instance_options(MachineState *machine) {
diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index 633b9af..c69bf24 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig
@@ -23,3 +23,7 @@ config CADENCE_SDHCI bool select SDHCI + +config AXIADO_SDHCI + bool + select SDHCI
diff --git a/hw/sd/axiado_sdhci.c b/hw/sd/axiado_sdhci.c new file mode 100644 index 0000000..2e88bff --- /dev/null +++ b/hw/sd/axiado_sdhci.c
@@ -0,0 +1,119 @@ +/* + * Axiado SD Host Controller with embedded PHY + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/sd/axiado_sdhci.h" +#include "sdhci-internal.h" +#include "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "qemu/log.h" + +#define EMMC_PHY_ID 0x00 +#define EMMC_PHY_STATUS 0x50 + +#define DLL_RDY (1u << 0) +#define CAL_DONE (1u << 6) + +static uint64_t emmc_phy_read(void *opaque, hwaddr offset, unsigned size) +{ + uint32_t val = 0x00; + + switch (offset) { + case EMMC_PHY_ID: + val = 0x3dff6870; + break; + case EMMC_PHY_STATUS: + val = DLL_RDY | CAL_DONE; + break; + default: + qemu_log_mask(LOG_UNIMP, + "Register 0x%" HWADDR_PRIx " not implemented\n", offset); + break; + } + + return val; +} + +static void emmc_phy_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + qemu_log_mask(LOG_UNIMP, + "Register 0x%" HWADDR_PRIx " not implemented\n", offset); +} + +static const MemoryRegionOps emmc_phy_ops = { + .read = emmc_phy_read, + .write = emmc_phy_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +static void axiado_sdhci_realize(DeviceState *dev, Error **errp) +{ + AxiadoSDHCIState *s = AXIADO_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sdhci_sbd; + + qdev_prop_set_uint64(DEVICE(&s->sdhci), "capareg", 0x216737eed0b0); + qdev_prop_set_uint64(DEVICE(&s->sdhci), "sd-spec-version", 3); + + sdhci_sbd = SYS_BUS_DEVICE(&s->sdhci); + if (!sysbus_realize(sdhci_sbd, errp)) { + return; + } + + sysbus_init_mmio(sbd, sysbus_mmio_get_region(sdhci_sbd, 0)); + + /* Propagate IRQ from SDHCI and SD bus */ + sysbus_pass_irq(sbd, sdhci_sbd); + s->sd_bus = qdev_get_child_bus(DEVICE(sdhci_sbd), "sd-bus"); + + /* Initialize eMMC PHY MMIO */ + memory_region_init_io(&s->emmc_phy, OBJECT(s), &emmc_phy_ops, s, + "axiado.emmc-phy", 0x1000); + + sysbus_init_mmio(sbd, &s->emmc_phy); +} + +static void axiado_sdhci_instance_init(Object *obj) +{ + AxiadoSDHCIState *s = AXIADO_SDHCI(obj); + + object_initialize_child(OBJECT(s), "sdhci", &s->sdhci, + TYPE_SYSBUS_SDHCI); +} + +static void axiado_sdhci_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = axiado_sdhci_realize; + dc->desc = "Axiado SD Host Controller with eMMC PHY"; +} + +static const TypeInfo axiado_sdhci_info = { + .name = TYPE_AXIADO_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AxiadoSDHCIState), + .instance_init = axiado_sdhci_instance_init, + .class_init = axiado_sdhci_class_init, +}; + +static void axiado_sdhci_register_types(void) +{ + type_register_static(&axiado_sdhci_info); +} + +type_init(axiado_sdhci_register_types);
diff --git a/hw/sd/meson.build b/hw/sd/meson.build index b43d45b..ebf09e3 100644 --- a/hw/sd/meson.build +++ b/hw/sd/meson.build
@@ -10,3 +10,4 @@ system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sdhost.c')) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_sdhci.c')) system_ss.add(when: 'CONFIG_CADENCE_SDHCI', if_true: files('cadence_sdhci.c')) +system_ss.add(when: 'CONFIG_AXIADO_SDHCI', if_true: files('axiado_sdhci.c'))
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig index bc6331b..b459ac2 100644 --- a/hw/sensor/Kconfig +++ b/hw/sensor/Kconfig
@@ -1,3 +1,7 @@ +config ADC128D818 + bool + depends on I2C + config TMP105 bool depends on I2C
diff --git a/hw/sensor/adc128d818.c b/hw/sensor/adc128d818.c new file mode 100644 index 0000000..c65508c --- /dev/null +++ b/hw/sensor/adc128d818.c
@@ -0,0 +1,696 @@ +/* + * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qom/object.h" +#include "hw/sensor/adc128d818.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "hw/i2c/i2c.h" +#include "migration/vmstate.h" +#include "trace.h" + + +/* Register addresses */ +#define REG_CONFIG 0x00 +#define REG_INT_STATUS 0x01 +#define REG_INT_MASK 0x03 +#define REG_CONV_RATE 0x07 +#define REG_CH_DISABLE 0x08 +#define REG_ONE_SHOT 0x09 +#define REG_DEEP_SHUTDOWN 0x0a +#define REG_ADV_CONFIG 0x0b +#define REG_BUSY_STATUS 0x0c + +/* Channel Reading Registers (16-bit, read-only) */ +#define REG_CH_READING_BASE 0x20 +#define REG_CH_READING_LAST 0x27 + +/* Limit Registers (8-bit, read/write) */ +#define REG_LIMIT_BASE 0x2a +#define REG_LIMIT_LAST 0x39 + +/* ID Registers (read-only) */ +#define REG_MANUFACTURER_ID 0x3e +#define REG_REVISION_ID 0x3f + +/* Configuration Register (0x00) bitfields */ +#define CONFIG_START BIT(0) +#define CONFIG_INT_ENABLE BIT(1) +#define CONFIG_INT_CLEAR BIT(3) +#define CONFIG_INITIALIZATION BIT(7) +#define CONFIG_WR_MASK \ + (CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR) + +/* Advanced Configuration Register (0x0B) bitfields */ +#define ADV_CONFIG_EXT_REF_EN BIT(0) +#define ADV_CONFIG_MODE_SHIFT 1 +#define ADV_CONFIG_MODE_MASK (0x3 << ADV_CONFIG_MODE_SHIFT) +#define ADV_CONFIG_WR_MASK \ + (ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_MASK) + +/* Busy Status Register (0x0C) bitfields */ +#define BUSY_STATUS_NOT_READY BIT(1) + +/* Conversion Rate Register (0x07) bitfields */ +#define CONV_RATE_MASK 0x01 + +/* Deep Shutdown Register (0x0A) bitfields */ +#define DEEP_SHUTDOWN_EN 0x01 + +/* Device constants */ +#define ADC128D818_NUM_CHANNELS 8 +#define ADC128D818_NUM_REGS 0x40 + +#define ADC128D818_INTERNAL_VREF_MV 2560 +#define ADC128D818_MAX_VDD_MV 5500 +#define ADC128D818_MANUFACTURER_ID_VAL 0x01 +#define ADC128D818_REVISION_ID_VAL 0x09 + +/* ADC resolution */ +#define ADC128D818_ADC_RESOLUTION 4096 +#define ADC128D818_ADC_MAX 4095 + +/* Temperature: 0.5 deg C per LSb = 500 milli-degrees per LSb */ +#define ADC128D818_TEMP_LSB_MC 500 +#define ADC128D818_TEMP_RAW_MIN (-256) +#define ADC128D818_TEMP_RAW_MAX 255 + + +OBJECT_DECLARE_SIMPLE_TYPE(ADC128D818State, ADC128D818) + +struct ADC128D818State { + I2CSlave parent_obj; + + qemu_irq irq; + + uint8_t len; + uint8_t pointer; + uint8_t rx_byte; + + uint8_t regs[ADC128D818_NUM_REGS]; + uint16_t channel[ADC128D818_NUM_CHANNELS]; + + int16_t ain[ADC128D818_NUM_CHANNELS]; /* mV */ + int32_t temperature; /* milli-degrees Celsius */ + uint16_t ext_vref; /* mV, 0 means not connected */ + bool temp_alarm; /* temperature high-limit alarm latched */ + + char *description; +}; + +static uint16_t adc128d818_get_vref(const ADC128D818State *s) +{ + if (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_EXT_REF_EN) { + if (s->ext_vref > 0u) { + return s->ext_vref; + } + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: external VREF selected but not" + " connected, falling back to internal\n", + __func__, s->description); + } + + return ADC128D818_INTERNAL_VREF_MV; +} + +static uint8_t adc128d818_get_mode(const ADC128D818State *s) +{ + return (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_MODE_MASK) >> + ADV_CONFIG_MODE_SHIFT; +} + +static bool adc128d818_is_temp_channel(const ADC128D818State *s, unsigned ch) +{ + if (ch != 7u) { + return false; + } + + return adc128d818_get_mode(s) != 1u; +} + +static bool adc128d818_is_reserved_channel(const ADC128D818State *s, + unsigned ch) +{ + switch (adc128d818_get_mode(s)) { + case 2u: + return ch >= 4u && ch <= 6u; + case 3u: + return ch == 6u; + default: + return false; + } +} + +static int16_t adc128d818_channel_voltage(const ADC128D818State *s, unsigned ch) +{ + switch (adc128d818_get_mode(s)) { + case 2u: + switch (ch) { + case 0u: + return (int16_t)(s->ain[0] - s->ain[1]); + case 1u: + return (int16_t)(s->ain[3] - s->ain[2]); + case 2u: + return (int16_t)(s->ain[4] - s->ain[5]); + case 3u: + return (int16_t)(s->ain[7] - s->ain[6]); + default: + return 0; + } + case 3u: + switch (ch) { + case 4u: + return (int16_t)(s->ain[4] - s->ain[5]); + case 5u: + return (int16_t)(s->ain[7] - s->ain[6]); + default: + return s->ain[ch]; + } + default: + return s->ain[ch]; + } +} + +static void adc128d818_update_irq(ADC128D818State *s) +{ + uint8_t cfg = s->regs[REG_CONFIG]; + uint8_t active; + bool level; + + active = s->regs[REG_INT_STATUS] & ~s->regs[REG_INT_MASK]; + + /* INT pin is active-low */ + level = !((cfg & CONFIG_INT_ENABLE) && !(cfg & CONFIG_INT_CLEAR) && + (active != 0u)); + + trace_adc128d818_irq(s->description, level); + qemu_set_irq(s->irq, level); +} + +static bool adc128d818_monitoring_active(const ADC128D818State *s) +{ + if (s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN) { + return false; + } + if (!(s->regs[REG_CONFIG] & CONFIG_START)) { + return false; + } + if (s->regs[REG_CONFIG] & CONFIG_INT_CLEAR) { + return false; + } + + return true; +} + +static void adc128d818_check_limits(ADC128D818State *s) +{ + uint8_t disabled = s->regs[REG_CH_DISABLE]; + uint8_t int_status = 0u; + + for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) { + if ((disabled & (1u << ch)) || + adc128d818_is_reserved_channel(s, ch)) { + continue; + } + + if (adc128d818_is_temp_channel(s, ch)) { + int raw = s->temperature / ADC128D818_TEMP_LSB_MC; + int thot; + int thyst; + + raw = MAX(ADC128D818_TEMP_RAW_MIN, + MIN(ADC128D818_TEMP_RAW_MAX, raw)); + thot = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u] * 2; + thyst = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u + 1u] * 2; + + if (raw > thot) { + s->temp_alarm = true; + } else if (raw <= thyst) { + s->temp_alarm = false; + } + if (s->temp_alarm) { + int_status |= (1u << ch); + } + } else { + uint8_t msb = (uint8_t)(s->channel[ch] >> 8u); + uint8_t high_lim = s->regs[REG_LIMIT_BASE + ch * 2u]; + uint8_t low_lim = s->regs[REG_LIMIT_BASE + ch * 2u + 1u]; + + if (msb > high_lim || msb <= low_lim) { + int_status |= (1u << ch); + } + } + } + + s->regs[REG_INT_STATUS] = int_status; + adc128d818_update_irq(s); +} + +static void adc128d818_convert(ADC128D818State *s) +{ + uint8_t disabled; + uint16_t vref; + + disabled = s->regs[REG_CH_DISABLE]; + vref = adc128d818_get_vref(s); + + for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) { + if ((disabled & (1u << ch)) || + adc128d818_is_reserved_channel(s, ch)) { + continue; + } + + if (adc128d818_is_temp_channel(s, ch)) { + int32_t raw = s->temperature / ADC128D818_TEMP_LSB_MC; + + raw = + MAX(ADC128D818_TEMP_RAW_MIN, MIN(ADC128D818_TEMP_RAW_MAX, raw)); + s->channel[ch] = (uint16_t)(((unsigned)raw & 0x1FFu) << 7u); + } else { + int16_t vin = adc128d818_channel_voltage(s, ch); + int32_t dout; + + dout = vin * (int32_t)ADC128D818_ADC_RESOLUTION / vref; + dout = MAX(0, MIN((int32_t)ADC128D818_ADC_MAX, dout)); + s->channel[ch] = (uint16_t)(dout << 4u); + } + + trace_adc128d818_convert(s->description, ch, s->channel[ch]); + } + + s->regs[REG_BUSY_STATUS] &= ~BUSY_STATUS_NOT_READY; + + adc128d818_check_limits(s); +} + +static uint8_t adc128d818_read_channel(ADC128D818State *s, unsigned ch) +{ + uint8_t val; + + if (s->rx_byte == 0u) { + val = (uint8_t)(s->channel[ch] >> 8u); + trace_adc128d818_read_channel(s->description, ch, s->channel[ch]); + } else { + val = (uint8_t)(s->channel[ch] & 0xFFu); + } + s->rx_byte ^= 1u; + + return val; +} + +static uint8_t adc128d818_read_reg(ADC128D818State *s, uint8_t reg) +{ + uint8_t val; + + switch (reg) { + case REG_INT_STATUS: + val = s->regs[REG_INT_STATUS]; + s->regs[REG_INT_STATUS] = 0x00u; + if (adc128d818_monitoring_active(s)) { + adc128d818_check_limits(s); + } else { + adc128d818_update_irq(s); + } + trace_adc128d818_read(s->description, reg, val); + return val; + case REG_CONFIG: + case REG_INT_MASK: + case REG_CONV_RATE: + case REG_CH_DISABLE: + case REG_ONE_SHOT: + case REG_DEEP_SHUTDOWN: + case REG_ADV_CONFIG: + case REG_BUSY_STATUS: + case REG_LIMIT_BASE ... REG_LIMIT_LAST: + case REG_MANUFACTURER_ID: + case REG_REVISION_ID: + trace_adc128d818_read(s->description, reg, s->regs[reg]); + return s->regs[reg]; + case REG_CH_READING_BASE ... REG_CH_READING_LAST: + return adc128d818_read_channel(s, reg - REG_CH_READING_BASE); + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: read from undefined register 0x%02x\n", + __func__, s->description, reg); + return 0x00u; + } +} + +static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val); + +static void adc128d818_reset_regs(ADC128D818State *s) +{ + memset(s->regs, 0, sizeof(s->regs)); + memset(s->channel, 0, sizeof(s->channel)); + s->temp_alarm = false; + + s->regs[REG_CONFIG] = 0x08u; + s->regs[REG_BUSY_STATUS] = 0x02u; + s->regs[REG_MANUFACTURER_ID] = ADC128D818_MANUFACTURER_ID_VAL; + s->regs[REG_REVISION_ID] = ADC128D818_REVISION_ID_VAL; + + for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) { + s->regs[REG_LIMIT_BASE + ch * 2u] = 0xFFu; + } + + s->pointer = 0x00u; + s->len = 0u; + s->rx_byte = 0u; + + adc128d818_update_irq(s); +} + +static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val) +{ + trace_adc128d818_write(s->description, reg, val); + + switch (reg) { + case REG_CONFIG: + if (val & CONFIG_INITIALIZATION) { + trace_adc128d818_reset(s->description, "reg"); + adc128d818_reset_regs(s); + break; + } + s->regs[REG_CONFIG] = val & CONFIG_WR_MASK; + if ((val & CONFIG_START) && !(val & CONFIG_INT_CLEAR) && + !(s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN)) { + adc128d818_convert(s); + } + adc128d818_update_irq(s); + break; + case REG_INT_MASK: + s->regs[REG_INT_MASK] = val; + adc128d818_update_irq(s); + break; + case REG_CONV_RATE: + if (s->regs[REG_CONFIG] & CONFIG_START) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: CONV_RATE written while running\n", + __func__, s->description); + break; + } + s->regs[REG_CONV_RATE] = val & CONV_RATE_MASK; + break; + case REG_CH_DISABLE: + if (s->regs[REG_CONFIG] & CONFIG_START) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: CH_DISABLE written while running\n", + __func__, s->description); + break; + } + s->regs[REG_CH_DISABLE] = val; + memset(s->channel, 0, sizeof(s->channel)); + s->regs[REG_INT_STATUS] = 0x00u; + s->temp_alarm = false; + adc128d818_update_irq(s); + break; + case REG_ONE_SHOT: + if (!(s->regs[REG_CONFIG] & CONFIG_START)) { + adc128d818_convert(s); + } + break; + case REG_DEEP_SHUTDOWN: + if ((val & DEEP_SHUTDOWN_EN) && (s->regs[REG_CONFIG] & CONFIG_START)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: DEEP_SHUTDOWN set while running\n", + __func__, s->description); + break; + } + s->regs[REG_DEEP_SHUTDOWN] = val & DEEP_SHUTDOWN_EN; + break; + case REG_ADV_CONFIG: + if (s->regs[REG_CONFIG] & CONFIG_START) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: ADV_CONFIG written while running\n", + __func__, s->description); + break; + } + s->regs[REG_ADV_CONFIG] = val & ADV_CONFIG_WR_MASK; + memset(s->channel, 0, sizeof(s->channel)); + s->regs[REG_INT_STATUS] = 0x00u; + s->temp_alarm = false; + adc128d818_update_irq(s); + break; + case REG_LIMIT_BASE ... REG_LIMIT_LAST: + s->regs[reg] = val; + break; + case REG_INT_STATUS: + case REG_BUSY_STATUS: + case REG_MANUFACTURER_ID: + case REG_REVISION_ID: + case REG_CH_READING_BASE ... REG_CH_READING_LAST: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: write to read-only register 0x%02x\n", + __func__, s->description, reg); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: write to undefined register 0x%02x\n", + __func__, s->description, reg); + break; + } +} + +static uint8_t adc128d818_recv(I2CSlave *i2c) +{ + ADC128D818State *s = ADC128D818(i2c); + + return adc128d818_read_reg(s, s->pointer); +} + +static int adc128d818_send(I2CSlave *i2c, uint8_t data) +{ + ADC128D818State *s = ADC128D818(i2c); + + if (s->len == 0u) { + s->pointer = data; + s->len++; + } else { + adc128d818_write_reg(s, s->pointer, data); + } + + return 0; +} + +static int adc128d818_event(I2CSlave *i2c, enum i2c_event event) +{ + ADC128D818State *s = ADC128D818(i2c); + + s->len = 0u; + s->rx_byte = 0u; + + return 0; +} + +static void adc128d818_get_ain(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + ADC128D818State *s = ADC128D818(obj); + int64_t value; + int ch_num; + int rc; + + rc = sscanf(name, "ain%d", &ch_num); + if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) { + error_setg(errp, "%s: %s: invalid channel '%s'", __func__, + s->description, name); + return; + } + + value = s->ain[ch_num]; + visit_type_int(v, name, &value, errp); +} + +static void adc128d818_set_ain(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + ADC128D818State *s = ADC128D818(obj); + int64_t value; + int ch_num; + int rc; + + if (!visit_type_int(v, name, &value, errp)) { + return; + } + + rc = sscanf(name, "ain%d", &ch_num); + if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) { + error_setg(errp, "%s: %s: invalid channel '%s'", __func__, + s->description, name); + return; + } + + if (value < INT16_MIN || value > INT16_MAX) { + error_setg(errp, "%s: %s: value %" PRId64 " out of range for '%s'", + __func__, s->description, value, name); + return; + } + + s->ain[ch_num] = (int16_t)value; + + if (adc128d818_monitoring_active(s)) { + adc128d818_convert(s); + } +} + +static void adc128d818_get_temperature( + Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) +{ + ADC128D818State *s = ADC128D818(obj); + int64_t value = s->temperature; + + visit_type_int(v, name, &value, errp); +} + +static void adc128d818_set_temperature( + Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) +{ + ADC128D818State *s = ADC128D818(obj); + int64_t value; + + if (!visit_type_int(v, name, &value, errp)) { + return; + } + + if (value < INT32_MIN || value > INT32_MAX) { + error_setg(errp, "%s: %s: value %" PRId64 " out of range", __func__, + s->description, value); + return; + } + + s->temperature = (int32_t)value; + + if (adc128d818_monitoring_active(s)) { + adc128d818_convert(s); + } +} + +static const VMStateDescription adc128d818_vmstate = { + .name = "ADC128D818", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(len, ADC128D818State), + VMSTATE_UINT8(pointer, ADC128D818State), + VMSTATE_UINT8(rx_byte, ADC128D818State), + VMSTATE_UINT8_ARRAY(regs, ADC128D818State, + ADC128D818_NUM_REGS), + VMSTATE_UINT16_ARRAY(channel, ADC128D818State, + ADC128D818_NUM_CHANNELS), + VMSTATE_INT16_ARRAY(ain, ADC128D818State, + ADC128D818_NUM_CHANNELS), + VMSTATE_INT32(temperature, ADC128D818State), + VMSTATE_UINT16(ext_vref, ADC128D818State), + VMSTATE_BOOL(temp_alarm, ADC128D818State), + VMSTATE_I2C_SLAVE(parent_obj, ADC128D818State), + VMSTATE_END_OF_LIST() + } +}; + +static void adc128d818_reset_hold(Object *obj, ResetType type) +{ + ADC128D818State *s = ADC128D818(obj); + + trace_adc128d818_reset(s->description, "hw"); + adc128d818_reset_regs(s); +} + +static void adc128d818_get_ext_vref( + Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) +{ + ADC128D818State *s = ADC128D818(obj); + int64_t value = (int64_t)s->ext_vref; + + visit_type_int(v, name, &value, errp); +} + +static void adc128d818_set_ext_vref( + Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) +{ + ADC128D818State *s = ADC128D818(obj); + int64_t value; + + if (!visit_type_int(v, name, &value, errp)) { + return; + } + + if (value < 0 || value > ADC128D818_MAX_VDD_MV) { + error_setg(errp, + "%s: %s: ext-vref-mv %" PRId64 " out of range (0..%u mV)", + __func__, s->description, value, ADC128D818_MAX_VDD_MV); + return; + } + + s->ext_vref = (uint16_t)value; + + if (adc128d818_monitoring_active(s)) { + adc128d818_convert(s); + } +} + +static void adc128d818_initfn(Object *obj) +{ + for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) { + char *name = g_strdup_printf("ain%u", ch); + + object_property_add(obj, name, "int", adc128d818_get_ain, + adc128d818_set_ain, NULL, NULL); + g_free(name); + } + + object_property_add(obj, "temperature", "int", adc128d818_get_temperature, + adc128d818_set_temperature, NULL, NULL); + object_property_add(obj, "ext-vref-mv", "int", adc128d818_get_ext_vref, + adc128d818_set_ext_vref, NULL, NULL); +} + +static void adc128d818_realize(DeviceState *dev, Error **errp) +{ + ADC128D818State *s = ADC128D818(dev); + + if (!s->description) { + s->description = g_strdup(object_get_typename(OBJECT(dev))); + } + + qdev_init_gpio_out(dev, &s->irq, 1u); +} + +static const Property adc128d818_properties[] = { + DEFINE_PROP_STRING("description", ADC128D818State, description), +}; + +static void adc128d818_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *ic = I2C_SLAVE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + ic->event = adc128d818_event; + ic->recv = adc128d818_recv; + ic->send = adc128d818_send; + dc->realize = adc128d818_realize; + rc->phases.hold = adc128d818_reset_hold; + dc->vmsd = &adc128d818_vmstate; + device_class_set_props(dc, adc128d818_properties); +} + +static const TypeInfo adc128d818_types[] = { + { + .name = TYPE_ADC128D818, + .parent = TYPE_I2C_SLAVE, + .instance_init = adc128d818_initfn, + .instance_size = sizeof(ADC128D818State), + .class_init = adc128d818_class_init, + }, +}; + +DEFINE_TYPES(adc128d818_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build index 420fdc3..fe36c9e 100644 --- a/hw/sensor/meson.build +++ b/hw/sensor/meson.build
@@ -1,3 +1,4 @@ +system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c')) system_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c')) system_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c')) system_ss.add(when: 'CONFIG_DPS310', if_true: files('dps310.c'))
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events index a3fe54f..5a3630f 100644 --- a/hw/sensor/trace-events +++ b/hw/sensor/trace-events
@@ -1,5 +1,13 @@ # See docs/devel/tracing.rst for syntax documentation. +# adc128d818.c +adc128d818_read(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x" +adc128d818_read_channel(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x" +adc128d818_write(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x" +adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x" +adc128d818_irq(const char *id, bool level) "%s level %u" +adc128d818_reset(const char *id, const char *source) "%s %s" + # tmp105.c tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x" tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c index c8cc6cf..bf596f7 100644 --- a/hw/ssi/aspeed_smc.c +++ b/hw/ssi/aspeed_smc.c
@@ -163,6 +163,9 @@ /* Read Timing Compensation Register */ #define R_TIMINGS (0x94 / 4) +/* Data fifo */ +#define R_DATA_FIFO (0x200 / 4) + /* SPI controller registers and bits (AST2400) */ #define R_SPI_CONF (0x00 / 4) #define SPI_CONF_ENABLE_W0 0 @@ -209,6 +212,7 @@ #define ASPEED_SMC_FEATURE_DMA_GRANT 0x2 #define ASPEED_SMC_FEATURE_WDT_CONTROL 0x4 #define ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH 0x08 +#define ASPEED_SMC_FEATURE_DATA_FIFO 0x10 static inline bool aspeed_smc_has_dma(const AspeedSMCClass *asc) { @@ -225,6 +229,11 @@ return !!(asc->features & ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH); } +static inline bool aspeed_smc_has_data_fifo(const AspeedSMCClass *asc) +{ + return !!(asc->features & ASPEED_SMC_FEATURE_DATA_FIFO); +} + #define aspeed_smc_error(fmt, ...) \ qemu_log_mask(LOG_GUEST_ERROR, "%s: " fmt "\n", __func__, ## __VA_ARGS__) @@ -664,6 +673,7 @@ { AspeedSMCState *s = ASPEED_SMC(opaque); AspeedSMCClass *asc = ASPEED_SMC_GET_CLASS(opaque); + int cs; addr >>= 2; @@ -689,6 +699,18 @@ trace_aspeed_smc_read(addr << 2, size, s->regs[addr]); *data = s->regs[addr]; + } else if (aspeed_smc_has_data_fifo(asc) && addr >= R_DATA_FIFO) { + cs = asc->data_fifo_offset_to_cs(s, addr << 2); + if (cs >= 0) { + /* + * Data fifo mode only supports SPI user mode. + * The flash address is provided by the SPI command/address cycles, + * the MMIO addr parameter is ignored. + */ + return aspeed_smc_flash_read(&s->flashes[cs], 0, data, size, attrs); + } + aspeed_smc_error("Invalid data fifo offset %" HWADDR_PRIx, addr << 2); + return MEMTX_ERROR; } else { qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n", __func__, addr); @@ -1063,6 +1085,19 @@ } else if (aspeed_smc_has_dma(asc) && aspeed_smc_has_dma64(asc) && addr == R_DMA_DRAM_ADDR_HIGH) { s->regs[addr] = DMA_DRAM_ADDR_HIGH(value); + } else if (aspeed_smc_has_data_fifo(asc) && addr >= R_DATA_FIFO) { + int cs = asc->data_fifo_offset_to_cs(s, addr << 2); + if (cs >= 0) { + /* + * Data fifo mode only supports SPI user mode. + * The flash address is provided by the SPI command/address cycles, + * the MMIO addr parameter is ignored. + */ + return aspeed_smc_flash_write(&s->flashes[cs], 0, data, size, + attrs); + } + aspeed_smc_error("Invalid data fifo offset %" HWADDR_PRIx, addr << 2); + return MEMTX_ERROR; } else { qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n", __func__, addr); @@ -1183,8 +1218,8 @@ static const VMStateDescription vmstate_aspeed_smc = { .name = "aspeed.smc", - .version_id = 3, - .minimum_version_id = 1, + .version_id = 4, + .minimum_version_id = 2, .fields = (const VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, AspeedSMCState, ASPEED_SMC_R_MAX), VMSTATE_UNUSED_V(2, 2), /* was snoop_index/snoop_dummies */ @@ -1808,6 +1843,39 @@ } } +/* + * Convert a data fifo offset to a chip select (CS). + * + * Data fifo access starts at 0x200. The data fifo offset index is + * calculated by subtracting the data fifo base offset from the MMIO address. + * + * The data fifo offset index increments by 1 for every 16MB of flash address + * space. Each offset step therefore represents a 16MB address decode range. + * + * The CS is determined by matching the data fifo offset index against the + * segment start address of each CS. + * + * Returns the CS index on success, or -1 if the offset is invalid. + */ +static int aspeed_2700_smc_data_fifo_offset_to_cs(const AspeedSMCState *s, + uint32_t offset) +{ + AspeedSMCClass *asc = ASPEED_SMC_GET_CLASS(s); + uint32_t start_offset; + uint32_t fifo_offset; + int i; + + for (i = 0; i < asc->cs_num_max; i++) { + start_offset = (s->regs[R_SEG_ADDR0 + i] & 0x0000ffff) << 16; + fifo_offset = start_offset / 0x1000000; + if (fifo_offset == offset - (R_DATA_FIFO << 2)) { + return i; + } + } + + return -1; +} + static const uint32_t aspeed_2700_fmc_resets[ASPEED_SMC_R_MAX] = { [R_CONF] = (CONF_FLASH_TYPE_SPI << CONF_FLASH_TYPE0 | CONF_FLASH_TYPE_SPI << CONF_FLASH_TYPE1), @@ -1842,6 +1910,27 @@ { 0x0, 0 }, /* disabled */ }; +/* + * AST2700 supports data fifo mode with a base data fifo start offset of 0x200. + * + * The data fifo start offset increments by 1 for every 16MB of flash address + * space. Each offset step therefore represents a 16MB address decode range. + * + * Assuming each chip select (CS) can use the maximum flash size of 256MB: + * 256MB / 16MB = 0x10 offset steps per CS. + * + * Data fifo start offset for CSn: + * 0x200 + (n * 0x10) + * + * Examples: + * CS0: 0x200 + * CS1: 0x210 + * CS2: 0x220 + * CS3: 0x230 + * + * asc->nregs should be set to: 0x200 + (asc->cs_num_max * 0x10) + * to cover all possible data fifo regions. + */ static void aspeed_2700_fmc_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -1861,14 +1950,16 @@ asc->flash_window_base = 0x100000000; asc->flash_window_size = 1 * GiB; asc->features = ASPEED_SMC_FEATURE_DMA | - ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH; + ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH | + ASPEED_SMC_FEATURE_DATA_FIFO; asc->dma_flash_mask = 0x2FFFFFFC; asc->dma_dram_mask = 0xFFFFFFFC; asc->dma_start_length = 1; - asc->nregs = ASPEED_SMC_R_MAX; + asc->nregs = (0x200 + (asc->cs_num_max * 0x10)) >> 2; asc->segment_to_reg = aspeed_2700_smc_segment_to_reg; asc->reg_to_segment = aspeed_2700_smc_reg_to_segment; asc->dma_ctrl = aspeed_2600_smc_dma_ctrl; + asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs; asc->reg_ops = &aspeed_2700_smc_flash_ops; } @@ -1896,14 +1987,16 @@ asc->flash_window_base = 0x180000000; asc->flash_window_size = 1 * GiB; asc->features = ASPEED_SMC_FEATURE_DMA | - ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH; + ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH | + ASPEED_SMC_FEATURE_DATA_FIFO; asc->dma_flash_mask = 0x2FFFFFFC; asc->dma_dram_mask = 0xFFFFFFFC; asc->dma_start_length = 1; - asc->nregs = ASPEED_SMC_R_MAX; + asc->nregs = (0x200 + (asc->cs_num_max * 0x10)) >> 2; asc->segment_to_reg = aspeed_2700_smc_segment_to_reg; asc->reg_to_segment = aspeed_2700_smc_reg_to_segment; asc->dma_ctrl = aspeed_2600_smc_dma_ctrl; + asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs; asc->reg_ops = &aspeed_2700_smc_flash_ops; } @@ -1930,14 +2023,16 @@ asc->flash_window_base = 0x200000000; asc->flash_window_size = 1 * GiB; asc->features = ASPEED_SMC_FEATURE_DMA | - ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH; + ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH | + ASPEED_SMC_FEATURE_DATA_FIFO; asc->dma_flash_mask = 0x2FFFFFFC; asc->dma_dram_mask = 0xFFFFFFFC; asc->dma_start_length = 1; - asc->nregs = ASPEED_SMC_R_MAX; + asc->nregs = (0x200 + (asc->cs_num_max * 0x10)) >> 2; asc->segment_to_reg = aspeed_2700_smc_segment_to_reg; asc->reg_to_segment = aspeed_2700_smc_reg_to_segment; asc->dma_ctrl = aspeed_2600_smc_dma_ctrl; + asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs; asc->reg_ops = &aspeed_2700_smc_flash_ops; } @@ -1964,14 +2059,16 @@ asc->flash_window_base = 0x280000000; asc->flash_window_size = 1 * GiB; asc->features = ASPEED_SMC_FEATURE_DMA | - ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH; + ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH | + ASPEED_SMC_FEATURE_DATA_FIFO; asc->dma_flash_mask = 0x0FFFFFFC; asc->dma_dram_mask = 0xFFFFFFFC; asc->dma_start_length = 1; - asc->nregs = ASPEED_SMC_R_MAX; + asc->nregs = (0x200 + (asc->cs_num_max * 0x10)) >> 2; asc->segment_to_reg = aspeed_2700_smc_segment_to_reg; asc->reg_to_segment = aspeed_2700_smc_reg_to_segment; asc->dma_ctrl = aspeed_2600_smc_dma_ctrl; + asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs; asc->reg_ops = &aspeed_2700_smc_flash_ops; }
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig index b3d823c..e1b751a 100644 --- a/hw/timer/Kconfig +++ b/hw/timer/Kconfig
@@ -65,3 +65,6 @@ config AVR_TIMER16 bool + +config HEX_QTIMER + bool
diff --git a/hw/timer/meson.build b/hw/timer/meson.build index 201b5d8..8323efa 100644 --- a/hw/timer/meson.build +++ b/hw/timer/meson.build
@@ -34,3 +34,5 @@ system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c')) system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c')) + +system_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c'))
diff --git a/hw/timer/qct-qtimer.c b/hw/timer/qct-qtimer.c new file mode 100644 index 0000000..f628e8d --- /dev/null +++ b/hw/timer/qct-qtimer.c
@@ -0,0 +1,667 @@ +/* + * Qualcomm QCT QTimer + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/sysbus.h" +#include "hw/timer/qct-qtimer.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "trace.h" + +#define QTIMER_MEM_SIZE_BYTES 0x1000 +#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL + +#define QCT_QTIMER_TIMER_FRAME_ELTS (16) +#define QCT_QTIMER_TIMER_VIEW_ELTS (2) + +#define QCT_QTIMER_AC_CNTFRQ (0x000) +#define QCT_QTIMER_AC_CNTSR (0x004) +#define QCT_QTIMER_AC_CNTTID_0 (0x08) +#define QCT_QTIMER_AC_CNTACR_START (0x40) +#define QCT_QTIMER_AC_CNTACR_END (0x5c) +#define QCT_QTIMER_AC_CNTTID_1 (0x108) +#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */ +#define QCT_QTIMER_AC_CNTACR_RWVT (1 << 4) /* R/W of CNTV_* regs */ +#define QCT_QTIMER_AC_CNTACR_RVOFF (1 << 3) /* R/W of CNTVOFF register */ +#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */ +#define QCT_QTIMER_AC_CNTACR_RPVCT (1 << 1) /* R/W of CNTVCT register */ +#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */ +#define QCT_QTIMER_VERSION (0x0fd0) + +#define QCT_QTIMER_CNTPCT_LO (0x000) +#define QCT_QTIMER_CNTPCT_HI (0x004) +#define QCT_QTIMER_CNT_FREQ (0x010) +#define QCT_QTIMER_CNTPL0ACR (0x014) +#define QCT_QTIMER_CNTPL0ACR_PL0CTEN (1 << 9) +#define QCT_QTIMER_CNTPL0ACR_PL0TVEN (1 << 8) +#define QCT_QTIMER_CNTPL0ACR_PL0VCTEN (1 << 1) +#define QCT_QTIMER_CNTPL0ACR_PL0PCTEN (1 << 0) +#define QCT_QTIMER_CNTP_CVAL_LO (0x020) +#define QCT_QTIMER_CNTP_CVAL_HI (0x024) +#define QCT_QTIMER_CNT_MASK 0x00ffffffffffffffULL +#define QCT_QTIMER_CNT_HI_BITS 24 +#define QCT_QTIMER_CNTP_TVAL (0x028) +#define QCT_QTIMER_CNTP_CTL (0x02c) +#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0) +#define QCT_QTIMER_CNTP_CTL_INTEN (1 << 1) +#define QCT_QTIMER_CNTP_CTL_ISTAT (1 << 2) + +OBJECT_DECLARE_SIMPLE_TYPE(QCTQtimerState, QCT_QTIMER) + +typedef struct QCTHextimerState { + QCTQtimerState *qtimer; + QEMUTimer *timer; /* one-shot deadline timer */ + int64_t offset_ns; /* QEMU_CLOCK_VIRTUAL ns at which cntpct == 0 */ + uint64_t cntval; /* 64-bit physical timer compare value */ + uint32_t control; + uint32_t cnt_ctrl; + uint32_t cntpl0acr; + uint32_t int_level; + qemu_irq irq; +} QCTHextimerState; + +struct QCTQtimerState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion view_iomem; + uint32_t secure; + QCTHextimerState timer[QCT_QTIMER_TIMER_FRAME_ELTS]; + uint32_t freq_hz; + uint32_t nr_frames; + uint32_t nr_views; + uint32_t frame_stride; + uint32_t freq_scale; +}; + +/* + * QTimer version register: + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Major | Minor | Step | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +#define QCT_QTIMER_VERSION_VALUE 0x20020000 + +static uint32_t qct_qtimer_cnttid(QCTQtimerState *s, unsigned int half) +{ + uint32_t nibble = 0x1 | (s->nr_views > 1 ? 0x4 : 0x0); + uint32_t base = half * 8; + uint32_t cnttid = 0; + unsigned int i; + + for (i = 0; i < 8; i++) { + if (base + i < s->nr_frames) { + cnttid |= nibble << (i * 4); + } + } + return cnttid; +} + +/* Counter value derived on-demand from QEMU_CLOCK_VIRTUAL. */ +static uint64_t hex_timer_now(QCTHextimerState *s) +{ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + uint32_t scale; + uint64_t scaled_elapsed; + + if (now <= s->offset_ns) { + return 0; + } + scale = MAX(s->qtimer->freq_scale, 1u); + scaled_elapsed = (uint64_t)(now - s->offset_ns) / scale; + return muldiv64(scaled_elapsed, s->qtimer->freq_hz, + NANOSECONDS_PER_SECOND) & + QCT_QTIMER_CNT_MASK; +} + +/* Arm (or disarm) the one-shot deadline timer. */ +static void hex_timer_rearm(QCTHextimerState *s) +{ + uint32_t scale; + uint64_t base_ns; + int64_t deadline_ns; + + if (!(s->control & QCT_QTIMER_CNTP_CTL_ENABLE)) { + timer_del(s->timer); + return; + } + + scale = MAX(s->qtimer->freq_scale, 1u); + /* + * Round the ticks-to-ns conversion up so that hex_timer_now(), which + * truncates when it divides elapsed ns by scale, is guaranteed to + * report >= cntval once this deadline fires. A truncating conversion + * here could re-arm at the same deadline forever when scale > 1. + */ + base_ns = muldiv64_round_up(s->cntval, NANOSECONDS_PER_SECOND, + s->qtimer->freq_hz); + if (base_ns > + ((uint64_t)INT64_MAX - (uint64_t)s->offset_ns) / scale) { + timer_del(s->timer); + return; + } + deadline_ns = s->offset_ns + (int64_t)(base_ns * scale); + timer_mod(s->timer, deadline_ns); +} + +static void hex_timer_update(QCTHextimerState *s) +{ + int level = s->int_level && + (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) && + !(s->control & QCT_QTIMER_CNTP_CTL_INTEN); + + trace_qtimer_interrupt(); + qemu_set_irq(s->irq, level); +} + +/* + * Access-control (AC) region: offsets below 0x1000, gates CNTFRQ/CNTSR/ + * CNTTID/CNTACR per frame plus the shared VERSION register. + */ +static uint64_t qct_qtimer_ac_read(void *opaque, hwaddr offset, unsigned size) +{ + QCTQtimerState *s = opaque; + uint32_t frame; + + switch (offset) { + case QCT_QTIMER_AC_CNTFRQ: + return s->freq_hz; + case QCT_QTIMER_AC_CNTSR: + return s->secure; + case QCT_QTIMER_AC_CNTTID_0: + return qct_qtimer_cnttid(s, 0); + case QCT_QTIMER_AC_CNTTID_1: + return qct_qtimer_cnttid(s, 1); + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END: + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4; + if (frame >= s->nr_frames) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n", + __func__, (int)offset); + return 0; + } + return s->timer[frame].cnt_ctrl; + case QCT_QTIMER_VERSION: + return QCT_QTIMER_VERSION_VALUE; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, + (int)offset); + return 0; + } +} + +static void qct_qtimer_ac_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + QCTQtimerState *s = opaque; + uint32_t frame; + + switch (offset) { + case QCT_QTIMER_AC_CNTFRQ: + if (value == 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTFRQ value 0\n", + __func__); + return; + } + s->freq_hz = value; + return; + case QCT_QTIMER_AC_CNTSR: + if (value > 0xff) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTSR value 0x%x\n", + __func__, (int)value); + return; + } + s->secure = value; + return; + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END: + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4; + if (frame >= s->nr_frames) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n", + __func__, (int)offset); + return; + } + s->timer[frame].cnt_ctrl = value; + return; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, + (int)offset); + return; + } +} + +static const MemoryRegionOps qct_qtimer_ac_ops = { + .read = qct_qtimer_ac_read, + .write = qct_qtimer_ac_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +/* + * View region: a flat array of (frame, view) slots, each frame_stride + * bytes wide, holding the per-frame CNTPCT/CNTP_CVAL/CNTP_TVAL/CNTP_CTL + * register set. + */ +static QCTHextimerState *qct_qtimer_demux(QCTQtimerState *s, hwaddr offset, + uint32_t *reg_offset, + uint32_t *view) +{ + uint32_t stride = s->frame_stride; + uint32_t stride_shift = ctz32(stride); + uint32_t slot_nr = offset >> stride_shift; + uint32_t frame = slot_nr / s->nr_views; + + *reg_offset = offset & (stride - 1); + *view = slot_nr % s->nr_views; + if (frame >= s->nr_frames) { + return NULL; + } + return &s->timer[frame]; +} + +/* Frames 8+ are described by CNTTID_1; each frame's 2nd view is a gated bit. */ +static bool qct_qtimer_view_visible(QCTQtimerState *s, uint32_t frame, + uint32_t view) +{ + uint32_t cnttid = qct_qtimer_cnttid(s, frame < 8 ? 0 : 1); + uint32_t frame_idx = frame < 8 ? frame : frame - 8; + + return !view || (cnttid & (0x4 << (frame_idx * 4))); +} + +static bool access_ok(QCTHextimerState *s, uint32_t reg_offset, uint32_t view) +{ + uint32_t acr; + uint32_t pl0acr; + + switch (reg_offset) { + case QCT_QTIMER_CNT_FREQ: + acr = QCT_QTIMER_AC_CNTACR_RFRQ; + pl0acr = QCT_QTIMER_CNTPL0ACR_PL0PCTEN | + QCT_QTIMER_CNTPL0ACR_PL0VCTEN; + break; + case QCT_QTIMER_CNTPCT_LO: + case QCT_QTIMER_CNTPCT_HI: + acr = QCT_QTIMER_AC_CNTACR_RPCT; + pl0acr = QCT_QTIMER_CNTPL0ACR_PL0PCTEN; + break; + case QCT_QTIMER_CNTP_CVAL_LO: + case QCT_QTIMER_CNTP_CVAL_HI: + case QCT_QTIMER_CNTP_TVAL: + case QCT_QTIMER_CNTP_CTL: + acr = QCT_QTIMER_AC_CNTACR_RWPT; + pl0acr = QCT_QTIMER_CNTPL0ACR_PL0CTEN; + break; + default: + /* CNTPL0ACR and VERSION are ungated. */ + return true; + } + + if (!(s->cnt_ctrl & acr)) { + return false; + } + return !view || (s->cntpl0acr & pl0acr); +} + +static MemTxResult hex_timer_read(void *opaque, hwaddr offset, uint64_t *data, + unsigned size, MemTxAttrs attrs) +{ + QCTQtimerState *qs = opaque; + uint32_t reg_offset; + uint32_t view; + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view); + uint32_t frame; + + if (!s) { + *data = 0; + return MEMTX_ACCESS_ERROR; + } + frame = s - qs->timer; + + trace_qtimer_read(offset); + + if (!qct_qtimer_view_visible(qs, frame, view)) { + *data = 0; + return MEMTX_OK; + } + + if (!access_ok(s, reg_offset, view)) { + return MEMTX_ACCESS_ERROR; + } + + switch (reg_offset) { + case QCT_QTIMER_CNT_FREQ: + *data = s->qtimer->freq_hz; + return MEMTX_OK; + case QCT_QTIMER_CNTP_CVAL_LO: + *data = extract64(s->cntval, 0, 32); + return MEMTX_OK; + case QCT_QTIMER_CNTP_CVAL_HI: + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */ + *data = extract64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS); + return MEMTX_OK; + case QCT_QTIMER_CNTPCT_LO: + *data = extract64(hex_timer_now(s), 0, 32); + return MEMTX_OK; + case QCT_QTIMER_CNTPCT_HI: + *data = extract64(hex_timer_now(s), 32, QCT_QTIMER_CNT_HI_BITS); + return MEMTX_OK; + case QCT_QTIMER_CNTP_TVAL: + *data = (uint32_t)(int32_t)(int64_t)(s->cntval - hex_timer_now(s)); + return MEMTX_OK; + case QCT_QTIMER_CNTP_CTL: + /* + * CNTP_CTL: bit 0 EN, bit 1 IMASK, bit 2 ISTAT (interrupt + * pending). ISTAT tracks int_level and is read-only. + */ + *data = s->control | ((s->int_level & 0x1) << 2); + return MEMTX_OK; + case QCT_QTIMER_CNTPL0ACR: + *data = view ? 0 : s->cntpl0acr; + return MEMTX_OK; + case QCT_QTIMER_VERSION: + *data = QCT_QTIMER_VERSION_VALUE; + return MEMTX_OK; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, + (int)offset); + *data = 0; + return MEMTX_ACCESS_ERROR; + } +} + +static MemTxResult hex_timer_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size, + MemTxAttrs attrs) +{ + QCTQtimerState *qs = opaque; + uint32_t reg_offset; + uint32_t view; + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view); + uint32_t frame; + + if (!s) { + return MEMTX_ACCESS_ERROR; + } + frame = s - qs->timer; + + trace_qtimer_write(offset, value); + + if (!qct_qtimer_view_visible(qs, frame, view)) { + return MEMTX_OK; + } + + if (!access_ok(s, reg_offset, view)) { + return MEMTX_ACCESS_ERROR; + } + + switch (reg_offset) { + case QCT_QTIMER_CNTP_CVAL_LO: + s->int_level = 0; + s->cntval = deposit64(s->cntval, 0, 32, value); + hex_timer_rearm(s); + break; + case QCT_QTIMER_CNTP_CVAL_HI: + s->int_level = 0; + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */ + s->cntval = deposit64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS, value) & + QCT_QTIMER_CNT_MASK; + hex_timer_rearm(s); + break; + case QCT_QTIMER_CNTP_CTL: + /* ISTAT (bit 2) is read-only; keep SW writes from polluting it. */ + s->control = value & ~QCT_QTIMER_CNTP_CTL_ISTAT; + hex_timer_rearm(s); + break; + case QCT_QTIMER_CNTP_TVAL: + /* TVAL write: CVAL = CNTPCT + TVAL (TVAL is signed 32-bit). */ + s->int_level = 0; + s->cntval = (hex_timer_now(s) + (int64_t)(int32_t)value) & + QCT_QTIMER_CNT_MASK; + hex_timer_rearm(s); + break; + case QCT_QTIMER_CNTPL0ACR: + if (!view) { + s->cntpl0acr = value; + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, + (int)offset); + return MEMTX_ACCESS_ERROR; + } + hex_timer_update(s); + return MEMTX_OK; +} + +static void hex_timer_tick(void *opaque) +{ + QCTHextimerState *s = opaque; + uint64_t now = hex_timer_now(s); + uint64_t diff56 = (now - s->cntval) & QCT_QTIMER_CNT_MASK; + int64_t signed_diff = (int64_t)(diff56 << 8) >> 8; + + if (signed_diff >= 0) { + s->int_level = 1; + hex_timer_update(s); + } else { + hex_timer_rearm(s); + } +} + +static const MemoryRegionOps hex_timer_ops = { + .read_with_attrs = hex_timer_read, + .write_with_attrs = hex_timer_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + .unaligned = false, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const VMStateDescription vmstate_qct_hextimer = { + .name = "qct-hextimer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(control, QCTHextimerState), + VMSTATE_UINT32(cnt_ctrl, QCTHextimerState), + VMSTATE_INT64(offset_ns, QCTHextimerState), + VMSTATE_UINT64(cntval, QCTHextimerState), + VMSTATE_UINT32(cntpl0acr, QCTHextimerState), + VMSTATE_UINT32(int_level, QCTHextimerState), + VMSTATE_TIMER_PTR(timer, QCTHextimerState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_qct_qtimer = { + .name = "qct-qtimer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(freq_hz, QCTQtimerState), + VMSTATE_UINT32(secure, QCTQtimerState), + VMSTATE_STRUCT_VARRAY_UINT32(timer, QCTQtimerState, nr_frames, + 1, vmstate_qct_hextimer, QCTHextimerState), + VMSTATE_END_OF_LIST() + } +}; + +static void qct_qtimer_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + QCTQtimerState *s = QCT_QTIMER(dev); + unsigned int i; + + if (s->nr_frames > QCT_QTIMER_TIMER_FRAME_ELTS) { + error_setg(errp, "nr_frames too high"); + return; + } + if (s->nr_views > QCT_QTIMER_TIMER_VIEW_ELTS) { + error_setg(errp, "nr_views too high"); + return; + } + if (s->freq_hz == 0) { + error_setg(errp, "freq-hz must be nonzero"); + return; + } + if (s->frame_stride == 0 || !is_power_of_2(s->frame_stride)) { + error_setg(errp, "frame_stride must be a nonzero power of two"); + return; + } + + memory_region_init_io(&s->iomem, OBJECT(s), &qct_qtimer_ac_ops, s, + "qct-qtimer-ac", QTIMER_MEM_SIZE_BYTES); + sysbus_init_mmio(sbd, &s->iomem); + + memory_region_init_io(&s->view_iomem, OBJECT(s), &hex_timer_ops, s, + "qct-qtimer-view", + (uint64_t)s->frame_stride * s->nr_frames * + s->nr_views); + sysbus_init_mmio(sbd, &s->view_iomem); + + for (i = 0; i < s->nr_frames; i++) { + QCTHextimerState *t = &s->timer[i]; + + t->qtimer = s; + s->secure |= (1 << i); + + sysbus_init_irq(sbd, &t->irq); + t->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hex_timer_tick, t); + } +} + +static void qct_qtimer_unrealize(DeviceState *dev) +{ + QCTQtimerState *s = QCT_QTIMER(dev); + unsigned int i; + + for (i = 0; i < s->nr_frames; i++) { + QCTHextimerState *t = &s->timer[i]; + + if (t->timer) { + timer_free(t->timer); + t->timer = NULL; + } + } +} + +static void qct_qtimer_reset_hold(Object *obj, ResetType type) +{ + QCTQtimerState *s = QCT_QTIMER(obj); + unsigned int i; + + for (i = 0; i < s->nr_frames; i++) { + QCTHextimerState *t = &s->timer[i]; + + /* + * Per TRM: CTL = 0 (EN=0, IMASK=0, ISTAT=0), CVAL = 0 so that + * TVAL (= CVAL - CNTPCT) also reads 0 at reset. The QEMUTimer is + * only armed when SW sets CTL.EN=1, so cntval=0 does not cause a + * spurious fire before SW programs the compare value. + */ + t->control = 0; + t->cnt_ctrl = QCT_QTIMER_AC_CNTACR_RWPT | QCT_QTIMER_AC_CNTACR_RWVT | + QCT_QTIMER_AC_CNTACR_RVOFF | QCT_QTIMER_AC_CNTACR_RFRQ | + QCT_QTIMER_AC_CNTACR_RPVCT | QCT_QTIMER_AC_CNTACR_RPCT; + t->cntval = 0; + t->cntpl0acr = 0; + t->int_level = 0; + t->offset_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + timer_del(t->timer); + qemu_set_irq(t->irq, 0); + } +} + +static const Property qct_qtimer_properties[] = { + DEFINE_PROP_UINT32("freq-hz", QCTQtimerState, freq_hz, + QTIMER_DEFAULT_FREQ_HZ), + DEFINE_PROP_UINT32("freq-scale", QCTQtimerState, freq_scale, 1), + DEFINE_PROP_UINT32("nr_frames", QCTQtimerState, nr_frames, 2), + DEFINE_PROP_UINT32("nr_views", QCTQtimerState, nr_views, 1), + DEFINE_PROP_UINT32("frame_stride", QCTQtimerState, frame_stride, 0x1000), +}; + +static void qct_qtimer_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + device_class_set_props(dc, qct_qtimer_properties); + dc->realize = qct_qtimer_realize; + dc->unrealize = qct_qtimer_unrealize; + dc->vmsd = &vmstate_qct_qtimer; + rc->phases.hold = qct_qtimer_reset_hold; +} + +/* QTimer interface implementation, backing HEX_SREG_TIMERLO/TIMERHI */ +static uint32_t qct_qtimer_get_timer_lo_impl(const QctQtimerInterface *obj) +{ + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj); + + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 0, 32) + : 0; +} + +static uint32_t qct_qtimer_get_timer_hi_impl(const QctQtimerInterface *obj) +{ + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj); + + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 32, 32) + : 0; +} + +static void qct_qtimer_interface_class_init(ObjectClass *klass, + const void *data) +{ + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_CLASS(klass); + + k->get_timer_lo = qct_qtimer_get_timer_lo_impl; + k->get_timer_hi = qct_qtimer_get_timer_hi_impl; +} + +static const TypeInfo qct_qtimer_types[] = { + { + .name = TYPE_QCT_QTIMER_INTERFACE, + .parent = TYPE_INTERFACE, + .class_size = sizeof(QctQtimerInterfaceClass), + .class_init = qct_qtimer_interface_class_init, + }, + { + .name = TYPE_QCT_QTIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(QCTQtimerState), + .class_init = qct_qtimer_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_QCT_QTIMER_INTERFACE }, + { } + }, + }, +}; + +DEFINE_TYPES(qct_qtimer_types)
diff --git a/hw/timer/trace-events b/hw/timer/trace-events index 634ba1d..636310f 100644 --- a/hw/timer/trace-events +++ b/hw/timer/trace-events
@@ -128,3 +128,8 @@ imx_epit_read(const char *name, uint32_t value) "(%s) = 0x%08x" imx_epit_write(const char *name, uint64_t value) "(%s, value = 0x%08" PRIx64 ")" imx_epit_cmp(uint32_t sr) "sr was %d" + +# qct-qtimer.c +qtimer_interrupt(void) "qtimer interrupt line updated" +qtimer_read(uint64_t offset) "offset 0x%" PRIx64 +qtimer_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index 28a60e4..451a918 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c
@@ -72,7 +72,7 @@ } EHCI_STATES; /* macros for accessing fields within next link pointer entry */ -#define NLPTR_GET(x) ((x) & 0xffffffe0) +#define NLPTR_GET(x) ((x) & ~0x1fULL) #define NLPTR_TYPE_GET(x) (((x) >> 1) & 3) #define NLPTR_TBIT(x) ((x) & 1) /* 1=invalid, 0=valid */ @@ -96,6 +96,17 @@ *data = val; \ } while (0) +/* + * EHCIqh / EHCIqtd / EHCIitd are sized to always include the extended + * high buffer pointer fields from EHCI 1.0 Appendix B. When 64-bit + * addressing capability is not advertised to the guest, the descriptors + * in guest memory only have the classic 32-bit layout, so DMA transfers + * must not read or write past that boundary. + */ +#define EHCI_QH_DWORDS_32 (offsetof(EHCIqh, bufptr_hi) / sizeof(uint32_t)) +#define EHCI_QTD_DWORDS_32 (offsetof(EHCIqtd, bufptr_hi) / sizeof(uint32_t)) +#define EHCI_ITD_DWORDS_32 (offsetof(EHCIitd, bufptr_hi) / sizeof(uint32_t)) + static const char *ehci_state_names[] = { [EST_INACTIVE] = "INACTIVE", [EST_ACTIVE] = "ACTIVE", @@ -147,6 +158,38 @@ return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr); } +static uint64_t ehci_get_buf_addr(const EHCIState *s, uint32_t hi, + uint32_t lo, uint32_t lo_mask) +{ + uint64_t addr = lo & lo_mask; + + if (s->caps_64bit_addr) { + addr = deposit64(addr, 32, 32, hi); + } + + return addr; +} + +static uint64_t ehci_get_desc_addr(const EHCIState *s, uint32_t lo) +{ + return ehci_get_buf_addr(s, s->ctrldssegment, lo, UINT32_MAX); +} + +static uint32_t ehci_qh_dwords(const EHCIState *s) +{ + return s->caps_64bit_addr ? (sizeof(EHCIqh) >> 2) : EHCI_QH_DWORDS_32; +} + +static uint32_t ehci_qtd_dwords(const EHCIState *s) +{ + return s->caps_64bit_addr ? (sizeof(EHCIqtd) >> 2) : EHCI_QTD_DWORDS_32; +} + +static uint32_t ehci_itd_dwords(const EHCIState *s) +{ + return s->caps_64bit_addr ? (sizeof(EHCIitd) >> 2) : EHCI_ITD_DWORDS_32; +} + static void ehci_trace_usbsts(uint32_t mask, int state) { /* interrupts */ @@ -287,7 +330,7 @@ return async ? s->astate : s->pstate; } -static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr) +static void ehci_set_fetch_addr(EHCIState *s, int async, uint64_t addr) { if (async) { s->a_fetch_addr = addr; @@ -296,7 +339,7 @@ } } -static int ehci_get_fetch_addr(EHCIState *s, int async) +static uint64_t ehci_get_fetch_addr(EHCIState *s, int async) { return async ? s->a_fetch_addr : s->p_fetch_addr; } @@ -373,7 +416,7 @@ } /* Get an array of dwords from main memory */ -static inline int get_dwords(EHCIState *ehci, uint32_t addr, +static inline int get_dwords(EHCIState *ehci, uint64_t addr, uint32_t *buf, int num) { int i; @@ -395,7 +438,7 @@ } /* Put an array of dwords in to main memory */ -static inline int put_dwords(EHCIState *ehci, uint32_t addr, +static inline int put_dwords(EHCIState *ehci, uint64_t addr, uint32_t *buf, int num) { int i; @@ -440,7 +483,7 @@ (qh->current_qtd != q->qh.current_qtd) || (q->async && qh->next_qtd != q->qh.next_qtd) || (memcmp(&qh->altnext_qtd, &q->qh.altnext_qtd, - 7 * sizeof(uint32_t)) != 0) || + EHCI_QH_OVERLAY_COUNT * sizeof(uint32_t)) != 0) || (q->dev != NULL && q->dev->addr != devaddr)) { return false; } else { @@ -455,7 +498,8 @@ (p->qtd.next != qtd->next)) || (!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd->altnext)) || p->qtd.token != qtd->token || - p->qtd.bufptr[0] != qtd->bufptr[0]) { + p->qtd.bufptr[0] != qtd->bufptr[0] || + p->qtd.bufptr_hi[0] != qtd->bufptr_hi[0]) { return false; } else { return true; @@ -487,10 +531,12 @@ int state; /* Verify the qh + qtd, like we do when going through fetchqh & fetchqtd */ + memset(&qh, 0, sizeof(qh)); + memset(&qtd, 0, sizeof(qtd)); get_dwords(q->ehci, NLPTR_GET(q->qhaddr), - (uint32_t *) &qh, sizeof(EHCIqh) >> 2); + (uint32_t *) &qh, ehci_qh_dwords(q->ehci)); get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), - (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2); + (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci)); if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) { p->async = EHCI_ASYNC_INITIALIZED; ehci_free_packet(p); @@ -549,7 +595,7 @@ /* queue management */ -static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async) +static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint64_t addr, int async) { EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; EHCIQueue *q; @@ -622,7 +668,7 @@ g_free(q); } -static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr, +static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint64_t addr, int async) { EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; @@ -1109,6 +1155,16 @@ } break; + case CTRLDSSEGMENT: + if (!s->caps_64bit_addr) { + qemu_log_mask(LOG_GUEST_ERROR, + "ehci: write to CTRLDSSEGMENT while " + "64-bit addressing capability is disabled\n"); + return; + } + val |= s->ctrldssegment_default; + break; + case ASYNCLISTADDR: if (ehci_async_enabled(s)) { qemu_log_mask(LOG_GUEST_ERROR, @@ -1134,8 +1190,8 @@ static void ehci_flush_qh(EHCIQueue *q) { uint32_t *qh = (uint32_t *) &q->qh; - uint32_t dwords = sizeof(EHCIqh) >> 2; - uint32_t addr = NLPTR_GET(q->qhaddr); + uint32_t dwords = ehci_qh_dwords(q->ehci); + uint64_t addr = NLPTR_GET(q->qhaddr); put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3); } @@ -1174,6 +1230,7 @@ for (i = 0; i < 5; i++) { q->qh.bufptr[i] = p->qtd.bufptr[i]; + q->qh.bufptr_hi[i] = p->qtd.bufptr_hi[i]; } if (!(q->qh.epchar & QH_EPCHAR_DTC)) { @@ -1207,7 +1264,8 @@ return -1; } - page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK; + page = ehci_get_buf_addr(p->queue->ehci, p->qtd.bufptr_hi[cpage], + p->qtd.bufptr[cpage], QTD_BUFPTR_MASK); page += offset; plen = bytes; if (plen > 4096 - offset) { @@ -1406,12 +1464,13 @@ /* 4.7.2 */ static int ehci_process_itd(EHCIState *ehci, EHCIitd *itd, - uint32_t addr) + uint64_t addr) { USBDevice *dev; USBEndpoint *ep; uint32_t i, len, pid, dir, devaddr, endp; - uint32_t pg, off, ptr1, ptr2, max, mult; + uint32_t pg, off, max, mult; + uint64_t ptr1, ptr2; ehci->periodic_sched_active = PERIODIC_ACTIVE; @@ -1434,7 +1493,8 @@ return -1; } - ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK); + ptr1 = ehci_get_buf_addr(ehci, itd->bufptr_hi[pg], + itd->bufptr[pg], ITD_BUFPTR_MASK); qemu_sglist_init(&ehci->isgl, ehci->device, 2, ehci->as); if (off + len > 4096) { /* transfer crosses page border */ @@ -1442,7 +1502,9 @@ qemu_sglist_destroy(&ehci->isgl); return -1; /* avoid page pg + 1 */ } - ptr2 = (itd->bufptr[pg + 1] & ITD_BUFPTR_MASK); + ptr2 = ehci_get_buf_addr(ehci, itd->bufptr_hi[pg + 1], + itd->bufptr[pg + 1], + ITD_BUFPTR_MASK); uint32_t len2 = off + len - 4096; uint32_t len1 = len - len2; qemu_sglist_add(&ehci->isgl, ptr1 + off, len1); @@ -1528,7 +1590,9 @@ EHCIqh qh; int i = 0; int again = 0; - uint32_t entry = ehci->asynclistaddr; + uint64_t entry = 0; + + entry = ehci_get_desc_addr(ehci, ehci->asynclistaddr); /* set reclamation flag at start event (4.8.6) */ if (async) { @@ -1538,9 +1602,10 @@ ehci_queues_rip_unused(ehci, async); /* Find the head of the list (4.9.1.1) */ + memset(&qh, 0, sizeof(qh)); for (i = 0; i < MAX_QH; i++) { if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh, - sizeof(EHCIqh) >> 2) < 0) { + ehci_qh_dwords(ehci)) < 0) { return 0; } ehci_trace_qh(NULL, NLPTR_GET(entry), &qh); @@ -1556,8 +1621,8 @@ goto out; } - entry = qh.next; - if (entry == ehci->asynclistaddr) { + entry = ehci_get_desc_addr(ehci, qh.next); + if (entry == ehci_get_desc_addr(ehci, ehci->asynclistaddr)) { break; } } @@ -1578,7 +1643,7 @@ static int ehci_state_fetchentry(EHCIState *ehci, int async) { int again = 0; - uint32_t entry = ehci_get_fetch_addr(ehci, async); + uint64_t entry = ehci_get_fetch_addr(ehci, async); if (NLPTR_TBIT(entry)) { ehci_set_state(ehci, async, EST_ACTIVE); @@ -1611,8 +1676,8 @@ default: /* TODO: handle FSTN type */ qemu_log_mask(LOG_GUEST_ERROR, - "FETCHENTRY: entry at 0x%x is of type %u " - "which is not supported yet\n", + "FETCHENTRY: entry at %" PRIx64 " is of type %" PRIu64 + " which is not supported yet\n", entry, NLPTR_TYPE_GET(entry)); return -1; } @@ -1623,7 +1688,7 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) { - uint32_t entry; + uint64_t entry; EHCIQueue *q; EHCIqh qh; @@ -1641,8 +1706,9 @@ goto out; } + memset(&qh, 0, sizeof(qh)); if (get_dwords(ehci, NLPTR_GET(q->qhaddr), - (uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) { + (uint32_t *) &qh, ehci_qh_dwords(ehci)) < 0) { q = NULL; goto out; } @@ -1683,7 +1749,7 @@ } if (trace_event_get_state_backends(TRACE_USB_EHCI_FETCHQH_DBG)) { - if (q->qhaddr != q->qh.next) { + if (q->qhaddr != ehci_get_desc_addr(ehci, q->qh.next)) { trace_usb_ehci_fetchqh_dbg(q->qhaddr, q->qh.epchar & QH_EPCHAR_H, q->qh.token & QTD_TOKEN_HALT, @@ -1698,7 +1764,7 @@ } else if ((q->qh.token & QTD_TOKEN_ACTIVE) && (NLPTR_TBIT(q->qh.current_qtd) == 0) && (q->qh.current_qtd != 0)) { - q->qtdaddr = q->qh.current_qtd; + q->qtdaddr = ehci_get_desc_addr(ehci, q->qh.current_qtd); ehci_set_state(ehci, async, EST_FETCHQTD); } else { @@ -1712,14 +1778,15 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async) { - uint32_t entry; + uint64_t entry; EHCIitd itd; assert(!async); entry = ehci_get_fetch_addr(ehci, async); + memset(&itd, 0, sizeof(itd)); if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, - sizeof(EHCIitd) >> 2) < 0) { + ehci_itd_dwords(ehci)) < 0) { return -1; } ehci_trace_itd(ehci, entry, &itd); @@ -1729,8 +1796,8 @@ } put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, - sizeof(EHCIitd) >> 2); - ehci_set_fetch_addr(ehci, async, itd.next); + ehci_itd_dwords(ehci)); + ehci_set_fetch_addr(ehci, async, ehci_get_desc_addr(ehci, itd.next)); ehci_set_state(ehci, async, EST_FETCHENTRY); return 1; @@ -1738,7 +1805,7 @@ static int ehci_state_fetchsitd(EHCIState *ehci, int async) { - uint32_t entry; + uint64_t entry; EHCIsitd sitd; assert(!async); @@ -1757,7 +1824,7 @@ warn_report("Skipping active siTD"); } - ehci_set_fetch_addr(ehci, async, sitd.next); + ehci_set_fetch_addr(ehci, async, ehci_get_desc_addr(ehci, sitd.next)); ehci_set_state(ehci, async, EST_FETCHENTRY); return 1; } @@ -1776,14 +1843,14 @@ */ if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) && (NLPTR_TBIT(q->qh.altnext_qtd) == 0)) { - q->qtdaddr = q->qh.altnext_qtd; + q->qtdaddr = ehci_get_desc_addr(q->ehci, q->qh.altnext_qtd); ehci_set_state(q->ehci, q->async, EST_FETCHQTD); /* * next qTD is valid */ } else if (NLPTR_TBIT(q->qh.next_qtd) == 0) { - q->qtdaddr = q->qh.next_qtd; + q->qtdaddr = ehci_get_desc_addr(q->ehci, q->qh.next_qtd); ehci_set_state(q->ehci, q->async, EST_FETCHQTD); /* @@ -1802,17 +1869,21 @@ EHCIqtd qtd; EHCIPacket *p; int again = 1; - uint32_t addr; + uint64_t addr; addr = NLPTR_GET(q->qtdaddr); if (get_dwords(q->ehci, addr + 8, &qtd.token, 1) < 0) { return 0; } barrier(); + memset(qtd.bufptr_hi, 0, sizeof(qtd.bufptr_hi)); if (get_dwords(q->ehci, addr + 0, &qtd.next, 1) < 0 || get_dwords(q->ehci, addr + 4, &qtd.altnext, 1) < 0 || get_dwords(q->ehci, addr + 12, qtd.bufptr, - ARRAY_SIZE(qtd.bufptr)) < 0) { + ARRAY_SIZE(qtd.bufptr)) < 0 || + (q->ehci->caps_64bit_addr && + get_dwords(q->ehci, addr + offsetof(EHCIqtd, bufptr_hi), + qtd.bufptr_hi, ARRAY_SIZE(qtd.bufptr_hi)) < 0)) { return 0; } ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd); @@ -1866,10 +1937,12 @@ static int ehci_state_horizqh(EHCIQueue *q) { + uint64_t addr; int again = 0; - if (ehci_get_fetch_addr(q->ehci, q->async) != q->qh.next) { - ehci_set_fetch_addr(q->ehci, q->async, q->qh.next); + addr = ehci_get_desc_addr(q->ehci, q->qh.next); + if (ehci_get_fetch_addr(q->ehci, q->async) != addr) { + ehci_set_fetch_addr(q->ehci, q->async, addr); ehci_set_state(q->ehci, q->async, EST_FETCHENTRY); again = 1; } else { @@ -1885,13 +1958,13 @@ USBEndpoint *ep = p->packet.ep; EHCIQueue *q = p->queue; EHCIqtd qtd = p->qtd; - uint32_t qtdaddr; + uint64_t qtdaddr; for (;;) { if (NLPTR_TBIT(qtd.next) != 0) { break; } - qtdaddr = qtd.next; + qtdaddr = ehci_get_desc_addr(q->ehci, qtd.next); /* * Detect circular td lists, Windows creates these, counting on the * active bit going low after execution to make the queue stop. @@ -1901,8 +1974,9 @@ goto leave; } } + memset(qtd.bufptr_hi, 0, sizeof(qtd.bufptr_hi)); if (get_dwords(q->ehci, NLPTR_GET(qtdaddr), - (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2) < 0) { + (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci)) < 0) { return -1; } ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd); @@ -2008,7 +2082,8 @@ static int ehci_state_writeback(EHCIQueue *q) { EHCIPacket *p = QTAILQ_FIRST(&q->packets); - uint32_t *qtd, addr; + uint32_t *qtd; + uint64_t addr; int again = 0; /* Write back the QTD from the QH area */ @@ -2194,6 +2269,8 @@ uint32_t entry; uint32_t list; const int async = 0; + uint64_t entry64; + uint64_t list64; /* 4.6 */ @@ -2218,12 +2295,14 @@ break; } list |= ((ehci->frindex & 0x1ff8) >> 1); - - if (get_dwords(ehci, list, &entry, 1) < 0) { + list64 = ehci_get_desc_addr(ehci, list); + if (get_dwords(ehci, list64, &entry, 1) < 0) { break; } - trace_usb_ehci_periodic_state_advance(ehci->frindex / 8, list, entry); - ehci_set_fetch_addr(ehci, async, entry); + entry64 = ehci_get_desc_addr(ehci, entry); + trace_usb_ehci_periodic_state_advance(ehci->frindex / 8, + list64, entry64); + ehci_set_fetch_addr(ehci, async, entry64); ehci_set_state(ehci, async, EST_FETCHENTRY); ehci_advance_state(ehci, async); ehci_queues_rip_unused(ehci, async); @@ -2414,6 +2493,18 @@ .wakeup_endpoint = ehci_wakeup_endpoint, }; +static bool ehci_fetch_addr_64_needed(void *opaque, int version_id) +{ + EHCIState *s = opaque; + + return s->migrate_fetch_addr_64bit; +} + +static bool ehci_fetch_addr_32_needed(void *opaque, int version_id) +{ + return !ehci_fetch_addr_64_needed(opaque, version_id); +} + static int usb_ehci_pre_save(void *opaque) { EHCIState *ehci = opaque; @@ -2424,6 +2515,11 @@ ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS; ehci->frindex = new_frindex; + if (!ehci->migrate_fetch_addr_64bit) { + ehci->migrate_a_fetch_addr = ehci->a_fetch_addr; + ehci->migrate_p_fetch_addr = ehci->p_fetch_addr; + } + return 0; } @@ -2444,6 +2540,11 @@ } } + if (!s->migrate_fetch_addr_64bit) { + s->a_fetch_addr = s->migrate_a_fetch_addr; + s->p_fetch_addr = s->migrate_p_fetch_addr; + } + return 0; } @@ -2504,8 +2605,14 @@ /* schedule state */ VMSTATE_UINT32(astate, EHCIState), VMSTATE_UINT32(pstate, EHCIState), - VMSTATE_UINT32(a_fetch_addr, EHCIState), - VMSTATE_UINT32(p_fetch_addr, EHCIState), + VMSTATE_UINT32_TEST(migrate_a_fetch_addr, EHCIState, + ehci_fetch_addr_32_needed), + VMSTATE_UINT32_TEST(migrate_p_fetch_addr, EHCIState, + ehci_fetch_addr_32_needed), + VMSTATE_UINT64_TEST(a_fetch_addr, EHCIState, + ehci_fetch_addr_64_needed), + VMSTATE_UINT64_TEST(p_fetch_addr, EHCIState, + ehci_fetch_addr_64_needed), VMSTATE_END_OF_LIST() } }; @@ -2524,6 +2631,9 @@ s->maxframes); return; } + if (s->caps_64bit_addr) { + s->caps[0x08] |= BIT(0); + } memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps); memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg); @@ -2583,7 +2693,7 @@ s->caps[0x05] = 0x00; /* No companion ports at present */ s->caps[0x06] = 0x00; s->caps[0x07] = 0x00; - s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */ + s->caps[0x08] = 0x80; /* We can cache whole frame */ s->caps[0x0a] = 0x00; s->caps[0x0b] = 0x00;
diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h index d038ee1..b5ac9c8 100644 --- a/hw/usb/hcd-ehci.h +++ b/hw/usb/hcd-ehci.h
@@ -63,6 +63,7 @@ #define ITD_BUFPTR_MAXPKT_SH 0 #define ITD_BUFPTR_MULT_MASK 0x00000003 #define ITD_BUFPTR_MULT_SH 0 + uint32_t bufptr_hi[7]; } EHCIitd; /* @@ -139,8 +140,12 @@ uint32_t bufptr[5]; /* Standard buffer pointer */ #define QTD_BUFPTR_MASK 0xfffff000 #define QTD_BUFPTR_SH 12 + uint32_t bufptr_hi[5]; } EHCIqtd; +/* QH overlay: altnext_qtd, token, bufptr[5], bufptr_hi[5] */ +#define EHCI_QH_OVERLAY_COUNT 12 + /* * EHCI spec version 1.0 Section 3.6 */ @@ -194,6 +199,7 @@ #define BUFPTR_FRAMETAG_MASK 0x0000001f #define BUFPTR_SBYTES_MASK 0x00000fe0 #define BUFPTR_SBYTES_SH 5 + uint32_t bufptr_hi[5]; } EHCIqh; enum async_state { @@ -208,7 +214,7 @@ QTAILQ_ENTRY(EHCIPacket) next; EHCIqtd qtd; /* copy of current QTD (being worked on) */ - uint32_t qtdaddr; /* address QTD read from */ + uint64_t qtdaddr; /* address QTD read from */ USBPacket packet; QEMUSGList sgl; @@ -229,8 +235,8 @@ * when guest removes an entry (doorbell, handshake sequence) */ EHCIqh qh; /* copy of current QH (being worked on) */ - uint32_t qhaddr; /* address QH read from */ - uint32_t qtdaddr; /* address QTD read from */ + uint64_t qhaddr; /* address QH read from */ + uint64_t qtdaddr; /* address QTD read from */ int last_pid; /* pid of last packet executed */ USBDevice *dev; QTAILQ_HEAD(, EHCIPacket) packets; @@ -256,6 +262,13 @@ /* properties */ uint32_t maxframes; + /* + * Controls migration stream compatibility for old machine types. + * Old machine types only transfer 32-bit fetch addresses. + */ + bool migrate_fetch_addr_64bit; + bool caps_64bit_addr; + uint32_t ctrldssegment_default; /* * EHCI spec version 1.0 Section 2.3 @@ -293,9 +306,18 @@ EHCIQueueHead aqueues; EHCIQueueHead pqueues; - /* which address to look at next */ - uint32_t a_fetch_addr; - uint32_t p_fetch_addr; + /* + * which address to look at next + * + * Migration compatibility fields for old machine types that only + * support 32-bit fetch addresses in the migration stream. + * + * New machine types migrate the full 64-bit runtime fetch address. + */ + uint32_t migrate_a_fetch_addr; + uint32_t migrate_p_fetch_addr; + uint64_t a_fetch_addr; + uint64_t p_fetch_addr; USBPacket ipacket; QEMUSGList isgl; @@ -308,7 +330,13 @@ }; #define DEFINE_EHCI_COMMON_PROPERTIES(_state) \ - DEFINE_PROP_UINT32("maxframes", _state, ehci.maxframes, 128) + DEFINE_PROP_UINT32("maxframes", _state, ehci.maxframes, 128), \ + DEFINE_PROP_BOOL("x-migrate-fetch-addr-64bit", _state, \ + ehci.migrate_fetch_addr_64bit, true), \ + DEFINE_PROP_BOOL("caps-64bit-addr", _state, \ + ehci.caps_64bit_addr, false), \ + DEFINE_PROP_UINT32("ctrldssegment-default", _state, \ + ehci.ctrldssegment_default, 0) extern const VMStateDescription vmstate_ehci;
diff --git a/hw/usb/trace-events b/hw/usb/trace-events index 0d4318d..67249d6 100644 --- a/hw/usb/trace-events +++ b/hw/usb/trace-events
@@ -86,15 +86,15 @@ usb_ehci_portsc_change(uint32_t addr, uint32_t port, uint32_t new, uint32_t old) "ch mmio 0x%04x [port %d] = 0x%x (old: 0x%x)" usb_ehci_usbsts(const char *sts, int state) "usbsts %s %d" usb_ehci_state(const char *schedule, const char *state) "%s schedule %s" -usb_ehci_qh_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ 0x%08x: next 0x%08x qtds 0x%08x,0x%08x,0x%08x" -usb_ehci_qh_fields(uint32_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ 0x%08x - rl %d, mplen %d, eps %d, ep %d, dev %d" -usb_ehci_qh_bits(uint32_t addr, int c, int h, int dtc, int i) "QH @ 0x%08x - c %d, h %d, dtc %d, i %d" +usb_ehci_qh_ptrs(void *q, uint64_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ 0x%" PRIx64 ": next 0x%08x qtds 0x%08x,0x%08x,0x%08x" +usb_ehci_qh_fields(uint64_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ 0x%" PRIx64 " - rl %d, mplen %d, eps %d, ep %d, dev %d" +usb_ehci_qh_bits(uint64_t addr, int c, int h, int dtc, int i) "QH @ 0x%" PRIx64 " - c %d, h %d, dtc %d, i %d" usb_ehci_qh_tbytes(uint32_t tbytes) "updating tbytes to %d" -usb_ehci_qtd_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ 0x%08x: next 0x%08x altnext 0x%08x" -usb_ehci_qtd_fields(uint32_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ 0x%08x - tbytes %d, cpage %d, cerr %d, pid %d" -usb_ehci_qtd_bits(uint32_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ 0x%08x - ioc %d, active %d, halt %d, babble %d, xacterr %d" -usb_ehci_itd(uint32_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ 0x%08x: next 0x%08x - mplen %d, mult %d, ep %d, dev %d" -usb_ehci_sitd(uint32_t addr, uint32_t nxt, uint32_t active) "ITD @ 0x%08x: next 0x%08x - active %d" +usb_ehci_qtd_ptrs(void *q, uint64_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ 0x%" PRIx64 ": next 0x%08x altnext 0x%08x" +usb_ehci_qtd_fields(uint64_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ 0x%" PRIx64 " - tbytes %d, cpage %d, cerr %d, pid %d" +usb_ehci_qtd_bits(uint64_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ 0x%" PRIx64 " - ioc %d, active %d, halt %d, babble %d, xacterr %d" +usb_ehci_itd(uint64_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ 0x%" PRIx64 ": next 0x%08x - mplen %d, mult %d, ep %d, dev %d" +usb_ehci_sitd(uint64_t addr, uint32_t nxt, uint32_t active) "SITD @ 0x%" PRIx64 ": next 0x%08x - active %d" usb_ehci_port_attach(uint32_t port, const char *owner, const char *device) "attach port #%d, owner %s, device %s" usb_ehci_port_detach(uint32_t port, const char *owner) "detach port #%d, owner %s" usb_ehci_port_reset(uint32_t port, int enable) "reset port #%d - %d" @@ -104,16 +104,16 @@ usb_ehci_port_disable(uint32_t port) "port #%d" usb_ehci_queue_action(void *q, const char *action) "q %p: %s" usb_ehci_packet_action(void *q, void *p, const char *action) "q %p p %p: %s" -usb_ehci_packet_submit(uint32_t qhaddr, uint32_t next, uint32_t qtdaddr, int pid, size_t len, int endp, int status, int actual_length) "qh=0x%x, next=0x%x, qtd=0x%x, pid=0x%x, len=%zd, endp=0x%x, status=%d, actual_length=%d" +usb_ehci_packet_submit(uint64_t qhaddr, uint32_t next, uint64_t qtdaddr, int pid, size_t len, int endp, int status, int actual_length) "qh=0x%" PRIx64 ", next=0x%x, qtd=0x%" PRIx64 ", pid=0x%x, len=%zd, endp=0x%x, status=%d, actual_length=%d" usb_ehci_irq(uint32_t level, uint32_t frindex, uint32_t sts, uint32_t mask) "level %d, frindex 0x%04x, sts 0x%x, mask 0x%x" usb_ehci_guest_bug(const char *reason) "%s" usb_ehci_doorbell_ring(void) "" usb_ehci_doorbell_ack(void) "" usb_ehci_dma_error(void) "" -usb_ehci_execute_complete(uint32_t qhaddr, uint32_t next, uint32_t qtdaddr, int status, int actual_length) "qhaddr=0x%x, next=0x%x, qtdaddr=0x%x, status=%d, actual_length=%d" -usb_ehci_fetchqh_reclaim_done(uint32_t qhaddr) "QH 0x%08x H-bit set, reclamation status reset - done processing" -usb_ehci_fetchqh_dbg(uint32_t qhaddr, uint32_t h, uint32_t halt, uint32_t active, uint32_t next) "QH 0x%08x (h 0x%x halt 0x%x active 0x%x) next 0x%08x" -usb_ehci_periodic_state_advance(uint32_t frame, uint32_t list, uint32_t entry) "frame=%d, list=0x%x, entry=0x%x" +usb_ehci_execute_complete(uint64_t qhaddr, uint32_t next, uint64_t qtdaddr, int status, int actual_length) "qhaddr=0x%" PRIx64 ", next=0x%x, qtdaddr=0x%" PRIx64 ", status=%d, actual_length=%d" +usb_ehci_fetchqh_reclaim_done(uint64_t qhaddr) "QH 0x%" PRIx64 " H-bit set, reclamation status reset - done processing" +usb_ehci_fetchqh_dbg(uint64_t qhaddr, uint32_t h, uint32_t halt, uint32_t active, uint32_t next) "QH 0x%" PRIx64 " (h 0x%x halt 0x%x active 0x%x) next 0x%08x" +usb_ehci_periodic_state_advance(uint32_t frame, uint64_t list, uint64_t entry) "frame=%d, list=0x%" PRIx64 ", entry=0x%" PRIx64 usb_ehci_skipped_uframes(uint64_t skipped_uframes) "skipped %" PRIu64 " uframes" usb_ehci_log(const char *msg) "%s"
diff --git a/include/crypto/cipher.h b/include/crypto/cipher.h index 9293931..2e36141 100644 --- a/include/crypto/cipher.h +++ b/include/crypto/cipher.h
@@ -235,4 +235,40 @@ const uint8_t *iv, size_t niv, Error **errp); +/** + * qcrypto_cipher_setaad: + * @cipher: the cipher object + * @aad: the associated data to authenticate + * @len: the length of @aad + * @errp: pointer to a NULL-initialized error object + * + * For AEAD modes such as GCM, feed the associated data (AAD) that is + * authenticated but not encrypted. It must be called after + * qcrypto_cipher_setiv() and before the first encrypt/decrypt call. It is + * an error to call this on a mode that is not an AEAD mode. + * + * Returns: 0 on success, -1 on error + */ +int qcrypto_cipher_setaad(QCryptoCipher *cipher, + const uint8_t *aad, size_t len, + Error **errp); + +/** + * qcrypto_cipher_gettag: + * @cipher: the cipher object + * @tag: buffer to receive the authentication tag + * @len: the length of @tag + * @errp: pointer to a NULL-initialized error object + * + * For AEAD modes such as GCM, read back the authentication tag computed + * over the associated data and the message. It must be called after the + * encrypt/decrypt operation. It is an error to call this on a mode that is + * not an AEAD mode. + * + * Returns: 0 on success, -1 on error + */ +int qcrypto_cipher_gettag(QCryptoCipher *cipher, + uint8_t *tag, size_t len, + Error **errp); + #endif /* QCRYPTO_CIPHER_H */
diff --git a/include/hw/arm/aspeed_ast1700.h b/include/hw/arm/aspeed_ast1700.h index f7bd4e8..39c5977 100644 --- a/include/hw/arm/aspeed_ast1700.h +++ b/include/hw/arm/aspeed_ast1700.h
@@ -41,7 +41,7 @@ MemoryRegion sram; AspeedSMCState spi; AspeedADCState adc; - AspeedSCUState scu; + Aspeed2700SCUState scu; AspeedGPIOState gpio; AspeedSGPIOState sgpiom[AST1700_SGPIO_NUM]; AspeedI2CState i2c;
diff --git a/include/hw/arm/aspeed_coprocessor.h b/include/hw/arm/aspeed_coprocessor.h index ac58a5f..acb8b06 100644 --- a/include/hw/arm/aspeed_coprocessor.h +++ b/include/hw/arm/aspeed_coprocessor.h
@@ -20,11 +20,8 @@ MemoryRegion *sram; MemoryRegion sram_alias; MemoryRegion uart_alias; - MemoryRegion scu_alias; Clock *sysclk; - AspeedSCUState *scu; - AspeedSCUState scuio; AspeedTimerCtrlState timerctrl; SerialMM *uart; int uart_dev; @@ -47,11 +44,22 @@ AspeedCoprocessorState parent; AspeedINTCState intc[2]; UnimplementedDeviceState ipc[2]; - UnimplementedDeviceState scuio; UnimplementedDeviceState pric[2]; UnimplementedDeviceState otp; ARMv7MState armv7m; + + /* + * SCU, SCUIO and FMC are not owned by this coprocessor: they are + * shared with the main PSP SoC, and only aliased into this + * coprocessor's own address space here. + */ + MemoryRegion scu_alias; + MemoryRegion scuio_alias; + MemoryRegion fmc_alias; + Aspeed2700SCUState *scu; + AspeedSCUState *scuio; + AspeedSMCState *fmc; }; #define TYPE_ASPEED27X0SSP_COPROCESSOR "aspeed27x0ssp-coprocessor"
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index 41dc04e..cd68c7f 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h
@@ -151,6 +151,7 @@ AspeedINTCState intcioexp[ASPEED_IOEXP_NUM]; GICv3State gic; MemoryRegion dram_empty; + Aspeed2700SCUState scu; }; #define TYPE_ASPEED27X0_SOC "aspeed27x0-soc"
diff --git a/include/hw/arm/ax3000-boards.h b/include/hw/arm/ax3000-boards.h new file mode 100644 index 0000000..4a63266 --- /dev/null +++ b/include/hw/arm/ax3000-boards.h
@@ -0,0 +1,28 @@ +/* + * Axiado Boards + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef AXIADO_BOARD_H +#define AXIADO_BOARD_H + +#include "hw/core/boards.h" +#include "hw/arm/ax3000-soc.h" + +#define TYPE_AX3000_MACHINE MACHINE_TYPE_NAME("ax3000") +OBJECT_DECLARE_TYPE(Ax3000MachineState, Ax3000MachineClass, AX3000_MACHINE) + +typedef struct Ax3000MachineState { + MachineState parent; + + Ax3000SoCState *soc; +} Ax3000MachineState; + +typedef struct Ax3000MachineClass { + MachineClass parent; + +} Ax3000MachineClass; +#endif
diff --git a/include/hw/arm/ax3000-soc.h b/include/hw/arm/ax3000-soc.h new file mode 100644 index 0000000..344fcb6 --- /dev/null +++ b/include/hw/arm/ax3000-soc.h
@@ -0,0 +1,98 @@ +/* + * Axiado SoC AX3000 + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef AXIADO_AX3000_H +#define AXIADO_AX3000_H + +#include "cpu.h" +#include "hw/intc/arm_gicv3_common.h" +#include "hw/char/cadence_uart.h" +#include "hw/misc/axiado_clk.h" +#include "hw/gpio/cadence_gpio.h" +#include "hw/sd/axiado_sdhci.h" +#include "hw/core/sysbus.h" +#include "qemu/units.h" + +#define TYPE_AX3000_SOC "ax3000" +OBJECT_DECLARE_TYPE(Ax3000SoCState, Ax3000SoCClass, AX3000_SOC) + +#define AX3000_DRAM0_BASE 0x3C000000 +#define AX3000_DRAM0_SIZE (1088 * MiB) +#define AX3000_DRAM1_BASE 0x400000000 +#define AX3000_DRAM1_SIZE (2 * GiB) + +#define AX3000_GIC_DIST_BASE 0x80300000 +#define AX3000_GIC_DIST_SIZE (64 * KiB) +#define AX3000_GIC_REDIST_BASE 0x80380000 +#define AX3000_GIC_REDIST_SIZE (512 * KiB) + +#define AX3000_UART0_BASE 0x80520000 +#define AX3000_UART1_BASE 0x805a0000 +#define AX3000_UART2_BASE 0x80620000 +#define AX3000_UART3_BASE 0x80520800 + +#define AX3000_SDHCI0_BASE 0x86000000 +#define AX3000_EMMC_PHY_BASE 0x80801C00 + +#define AX3000_GPIO0_BASE 0x80500000 +#define AX3000_GPIO1_BASE 0x80580000 +#define AX3000_GPIO2_BASE 0x80600000 +#define AX3000_GPIO3_BASE 0x80680000 +#define AX3000_GPIO4_BASE 0x80700000 +#define AX3000_GPIO5_BASE 0x80780000 +#define AX3000_GPIO6_BASE 0x80800000 +#define AX3000_GPIO7_BASE 0x80880000 + +#define AX3000_TIMER_CTRL 0x8A020000 +#define AX3000_PLL_BASE 0x80000000 + +enum Ax3000Configuration { + AX3000_NUM_CPUS = 4, + AX3000_NUM_IRQS = 224, + AX3000_NUM_BANKS = 2, + AX3000_NUM_UARTS = 4, + AX3000_NUM_GPIOS = 8, +}; + +typedef struct Ax3000SoCState { + SysBusDevice parent; + + ARMCPU cpu[AX3000_NUM_CPUS]; + GICv3State gic; + MemoryRegion dram[AX3000_NUM_BANKS]; + Ax3000ClkState ax3000_clk; + CadenceUARTState uart[AX3000_NUM_UARTS]; + CadenceGPIOState gpio[AX3000_NUM_GPIOS]; + AxiadoSDHCIState sdhci0; +} Ax3000SoCState; + +typedef struct Ax3000SoCClass { + SysBusDeviceClass parent; + + uint32_t num_cpus; +} Ax3000SoCClass; + +enum Ax3000Irqs { + AX3000_UART0_IRQ = 112, + AX3000_UART1_IRQ = 113, + AX3000_UART2_IRQ = 114, + AX3000_UART3_IRQ = 170, + + AX3000_SDHCI0_IRQ = 123, + + AX3000_GPIO0_IRQ = 183, + AX3000_GPIO1_IRQ = 184, + AX3000_GPIO2_IRQ = 185, + AX3000_GPIO3_IRQ = 186, + AX3000_GPIO4_IRQ = 187, + AX3000_GPIO5_IRQ = 188, + AX3000_GPIO6_IRQ = 189, + AX3000_GPIO7_IRQ = 190, +}; + +#endif /* AXIADO_AX3000_H */
diff --git a/include/hw/core/boards.h b/include/hw/core/boards.h index 29c6893..a436d48 100644 --- a/include/hw/core/boards.h +++ b/include/hw/core/boards.h
@@ -815,6 +815,9 @@ } } +extern GlobalProperty hw_compat_11_1[]; +extern const size_t hw_compat_11_1_len; + extern GlobalProperty hw_compat_11_0[]; extern const size_t hw_compat_11_0_len;
diff --git a/include/hw/gpio/cadence_gpio.h b/include/hw/gpio/cadence_gpio.h new file mode 100644 index 0000000..69646ba --- /dev/null +++ b/include/hw/gpio/cadence_gpio.h
@@ -0,0 +1,53 @@ +/* + * Cadence GPIO registers definition. + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CADENCE_GPIO_H +#define CADENCE_GPIO_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_CADENCE_GPIO "cadence_gpio" +OBJECT_DECLARE_SIMPLE_TYPE(CadenceGPIOState, CADENCE_GPIO) + +#define CDNS_GPIO_REG_SIZE 0x400 +#define CDNS_GPIO_NUM 32 + +#define CDNS_GPIO_BYPASS_MODE 0x00 +#define CDNS_GPIO_DIRECTION_MODE 0x04 +#define CDNS_GPIO_OUTPUT_EN 0x08 +#define CDNS_GPIO_OUTPUT_VALUE 0x0c +#define CDNS_GPIO_INPUT_VALUE 0x10 +#define CDNS_GPIO_IRQ_MASK 0x14 +#define CDNS_GPIO_IRQ_EN 0x18 +#define CDNS_GPIO_IRQ_DIS 0x1c +#define CDNS_GPIO_IRQ_STATUS 0x20 +#define CDNS_GPIO_IRQ_TYPE 0x24 +#define CDNS_GPIO_IRQ_VALUE 0x28 +#define CDNS_GPIO_IRQ_ANY_EDGE 0x2c + +struct CadenceGPIOState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t bmr; + uint32_t dmr; + uint32_t oer; + uint32_t ovr; + uint32_t inpvr; + uint32_t imr; + uint32_t isr; + uint32_t itr; + uint32_t ivr; + uint32_t ioar; + qemu_irq irq; + qemu_irq output[CDNS_GPIO_NUM]; +}; + +#endif /* CADENCE_GPIO_H */
diff --git a/include/hw/gpio/pca9552.h b/include/hw/gpio/pca9552.h index 43b1752..5299c13 100644 --- a/include/hw/gpio/pca9552.h +++ b/include/hw/gpio/pca9552.h
@@ -1,39 +1,18 @@ /* - * PCA9552 I2C LED blinker + * PCA955X I2C LED blinker and I/O expanders * * Copyright (c) 2017-2018, IBM Corporation. * * 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 PCA9552_H -#define PCA9552_H -#include "hw/i2c/i2c.h" -#include "qom/object.h" +#ifndef HW_GPIO_PCA9552_H +#define HW_GPIO_PCA9552_H -#define TYPE_PCA9552 "pca9552" #define TYPE_PCA955X "pca955x" +#define TYPE_PCA9552 "pca9552" #define TYPE_PCA9535 "pca9535" -typedef struct PCA955xState PCA955xState; -DECLARE_INSTANCE_CHECKER(PCA955xState, PCA955X, - TYPE_PCA955X) - -#define PCA955X_NR_REGS 10 -#define PCA955X_PIN_COUNT_MAX 16 - -struct PCA955xState { - /*< private >*/ - I2CSlave i2c; - /*< public >*/ - - uint8_t len; - uint8_t pointer; - - uint8_t regs[PCA955X_NR_REGS]; - qemu_irq gpio_out[PCA955X_PIN_COUNT_MAX]; - uint8_t ext_state[PCA955X_PIN_COUNT_MAX]; - char *description; /* For debugging purpose only */ -}; +#define TYPE_PCA9555 "pca9555" #endif
diff --git a/include/hw/gpio/pca9554.h b/include/hw/gpio/pca9554.h index 54bfc4c..ac83537 100644 --- a/include/hw/gpio/pca9554.h +++ b/include/hw/gpio/pca9554.h
@@ -12,12 +12,14 @@ #include "qom/object.h" #define TYPE_PCA9554 "pca9554" +#define TYPE_PCA9536 "pca9536" typedef struct PCA9554State PCA9554State; DECLARE_INSTANCE_CHECKER(PCA9554State, PCA9554, TYPE_PCA9554) #define PCA9554_NR_REGS 4 #define PCA9554_PIN_COUNT 8 +#define PCA9536_PIN_COUNT 4 struct PCA9554State { /*< private >*/ @@ -31,6 +33,7 @@ qemu_irq gpio_out[PCA9554_PIN_COUNT]; uint8_t ext_state[PCA9554_PIN_COUNT]; char *description; /* For debugging purpose only */ + bool hw_dir; /* Honor pin direction */ }; #endif
diff --git a/include/hw/hexagon/hex-subsys.h b/include/hw/hexagon/hex-subsys.h new file mode 100644 index 0000000..5792f7b --- /dev/null +++ b/include/hw/hexagon/hex-subsys.h
@@ -0,0 +1,32 @@ +/* + * Hexagon subsystem helpers shared between the machine models. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_HEXAGON_HEX_SUBSYS_H +#define HW_HEXAGON_HEX_SUBSYS_H + +#include "hw/hexagon/hexagon.h" +#include "hw/core/qdev.h" + +/* Create the subsystem shared by every Hexagon machine. */ +void hex_subsys_create(HexagonCommonMachineState *hms, + const struct hexagon_machine_config *m_cfg, Rev_t rev); + +/* + * Parent a CPU into the subsystem's cluster and wire its links. Call for + * every CPU before hex_subsys_realize_cluster(), then realize each CPU with + * hex_subsys_realize_cpu(). CPU[0] receives the L2VIC outputs. + */ +void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu); + +/* Realize the CPU cluster, once all CPUs have been parented into it. */ +void hex_subsys_realize_cluster(HexagonCommonMachineState *hms); + +/* Realize a CPU previously parented via hex_subsys_add_cpu(). */ +void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu, + bool boot_cpu); + +#endif /* HW_HEXAGON_HEX_SUBSYS_H */
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h index 1034b09..3d7b3cb 100644 --- a/include/hw/hexagon/hexagon.h +++ b/include/hw/hexagon/hexagon.h
@@ -156,6 +156,12 @@ MemoryRegion ram; MemoryRegion cfgtable_rom; + MemoryRegion vtcm; + DeviceState *cluster; + DeviceState *l2vic; + DeviceState *qtimer; + DeviceState *glob_regs; + DeviceState *tlb; }; #endif
diff --git a/include/hw/hexagon/hexagon_globalreg.h b/include/hw/hexagon/hexagon_globalreg.h index 9500998..07437df 100644 --- a/include/hw/hexagon/hexagon_globalreg.h +++ b/include/hw/hexagon/hexagon_globalreg.h
@@ -10,6 +10,8 @@ #include "hw/core/qdev.h" #include "hw/core/sysbus.h" +#include "hw/intc/hex-l2vic.h" +#include "hw/timer/qct-qtimer.h" #include "qom/object.h" #include "target/hexagon/cpu.h" @@ -22,6 +24,12 @@ /* Array of system registers */ uint32_t regs[NUM_SREGS]; + /* L2VIC interface used to back the VID/VID1 registers */ + HexL2VicInterface *l2vic; + + /* QTimer interface used to back the TIMERLO/TIMERHI registers */ + QctQtimerInterface *qtimer; + /* Global performance cycle counter base */ uint64_t g_pcycle_base;
diff --git a/include/hw/hexagon/virt.h b/include/hw/hexagon/virt.h index fcb4776..4ca2f1e 100644 --- a/include/hw/hexagon/virt.h +++ b/include/hw/hexagon/virt.h
@@ -11,15 +11,17 @@ #include "hw/hexagon/hexagon.h" #include "target/hexagon/cpu.h" +#define VIRTIO_DEV_COUNT 8 + struct HexagonVirtMachineState { HexagonCommonMachineState parent_obj; int fdt_size; MemoryRegion *sys; MemoryRegion tcm; - MemoryRegion vtcm; MemoryRegion bios; Clock *apb_clk; + DeviceState *virtio_mmio[VIRTIO_DEV_COUNT]; }; void hexagon_load_fdt(const struct HexagonVirtMachineState *vms);
diff --git a/include/hw/i2c/aspeed_i2c.h b/include/hw/i2c/aspeed_i2c.h index 156998e..05937a7 100644 --- a/include/hw/i2c/aspeed_i2c.h +++ b/include/hw/i2c/aspeed_i2c.h
@@ -231,6 +231,8 @@ FIELD(I2CS_DMA_TX_ADDR_HI, ADDR_HI, 0, 7) REG32(I2CS_DMA_RX_ADDR_HI, 0x6c) FIELD(I2CS_DMA_RX_ADDR_HI, ADDR_HI, 0, 7) +REG32(I2CC_VERSION_CTRL, 0x94) + FIELD(I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN, 2, 1) struct AspeedI2CState;
diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h index d4b6d3e..ac03da9 100644 --- a/include/hw/i386/pc.h +++ b/include/hw/i386/pc.h
@@ -209,6 +209,9 @@ /* sgx.c */ void pc_machine_init_sgx_epc(PCMachineState *pcms); +extern GlobalProperty pc_compat_11_1[]; +extern const size_t pc_compat_11_1_len; + extern GlobalProperty pc_compat_11_0[]; extern const size_t pc_compat_11_0_len;
diff --git a/include/hw/intc/hex-l2vic.h b/include/hw/intc/hex-l2vic.h new file mode 100644 index 0000000..edc2782 --- /dev/null +++ b/include/hw/intc/hex-l2vic.h
@@ -0,0 +1,61 @@ +/* + * QEMU L2VIC Interrupt Controller + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_INTC_HEX_L2VIC_H +#define HW_INTC_HEX_L2VIC_H + +#include "qom/object.h" + +#define TYPE_HEX_L2VIC "hex-l2vic" +/* + * L2VIC Interface for CPU/GlobalReg interaction + */ +#define TYPE_HEX_L2VIC_INTERFACE "hex-l2vic-if" + +typedef struct HexL2VicInterface HexL2VicInterface; + +typedef struct HexL2VicInterfaceClass { + InterfaceClass parent_class; + + uint32_t (*read_vid)(HexL2VicInterface *l2vic, uint32_t group); + + /* + * Write the VID: unpack the fields into per-group VIDs. This does + * not deliver or clear any interrupt; a pending interrupt stays + * gated until ciad. + */ + void (*update_vid)(HexL2VicInterface *l2vic, uint32_t group, + uint32_t value); + + /* Clear interrupt using CIAD instruction */ + void (*clear_interrupt)(HexL2VicInterface *l2vic); +} HexL2VicInterfaceClass; + +DECLARE_OBJ_CHECKERS(HexL2VicInterface, HexL2VicInterfaceClass, + HEX_L2VIC_INTERFACE, TYPE_HEX_L2VIC_INTERFACE); + +static inline uint32_t l2vic_read_vid(HexL2VicInterface *l2vic, + uint32_t group) +{ + HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic); + return k->read_vid(l2vic, group); +} + +static inline void l2vic_update_vid(HexL2VicInterface *l2vic, uint32_t group, + uint32_t value) +{ + HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic); + k->update_vid(l2vic, group, value); +} + +static inline void l2vic_clear_interrupt(HexL2VicInterface *l2vic) +{ + HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic); + k->clear_interrupt(l2vic); +} + +#endif /* HW_INTC_HEX_L2VIC_H */
diff --git a/include/hw/misc/aspeed_hace.h b/include/hw/misc/aspeed_hace.h index b5416b0..9b0e768 100644 --- a/include/hw/misc/aspeed_hace.h +++ b/include/hw/misc/aspeed_hace.h
@@ -49,7 +49,6 @@ uint32_t key_mask; uint32_t hash_mask; uint64_t nr_regs; - bool raise_crypt_interrupt_workaround; uint32_t src_hi_mask; uint32_t dest_hi_mask; uint32_t key_hi_mask;
diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h index c30940a..9045494 100644 --- a/include/hw/misc/aspeed_scu.h +++ b/include/hw/misc/aspeed_scu.h
@@ -20,6 +20,7 @@ #define TYPE_ASPEED_2500_SCU TYPE_ASPEED_SCU "-ast2500" #define TYPE_ASPEED_2600_SCU TYPE_ASPEED_SCU "-ast2600" #define TYPE_ASPEED_2700_SCU TYPE_ASPEED_SCU "-ast2700" +OBJECT_DECLARE_SIMPLE_TYPE(Aspeed2700SCUState, ASPEED_2700_SCU) #define TYPE_ASPEED_2700_SCUIO TYPE_ASPEED_SCU "io" "-ast2700" #define TYPE_ASPEED_1030_SCU TYPE_ASPEED_SCU "-ast1030" @@ -41,6 +42,10 @@ uint32_t hw_prot_key; }; +struct Aspeed2700SCUState { + AspeedSCUState parent_obj; +}; + #define AST2400_A1_SILICON_REV 0x02010303U #define AST2500_A1_SILICON_REV 0x04010303U #define AST2600_A3_SILICON_REV 0x05030303U
diff --git a/include/hw/misc/axiado_clk.h b/include/hw/misc/axiado_clk.h new file mode 100644 index 0000000..6e12a50 --- /dev/null +++ b/include/hw/misc/axiado_clk.h
@@ -0,0 +1,26 @@ +/* + * Axiado AX3000 Clock Control + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef AXIADO_AX3000_CLK_H +#define AXIADO_AX3000_CLK_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_AX3000_CLK "ax3000-clk" +OBJECT_DECLARE_SIMPLE_TYPE(Ax3000ClkState, AX3000_CLK) + +#define AX3000_CLK_PLL_CTRL_SIZE 0x1000 + +typedef struct Ax3000ClkState { + SysBusDevice parent; + + MemoryRegion pll_ctrl; +} Ax3000ClkState; + +#endif /* AXIADO_AX3000_CLK_H */
diff --git a/include/hw/misc/bcm2835_powermgt.h b/include/hw/misc/bcm2835_powermgt.h index fb0740c..d1903a9 100644 --- a/include/hw/misc/bcm2835_powermgt.h +++ b/include/hw/misc/bcm2835_powermgt.h
@@ -12,6 +12,7 @@ #define BCM2835_POWERMGT_H #include "hw/core/sysbus.h" +#include "qemu/timer.h" #include "qom/object.h" #define TYPE_BCM2835_POWERMGT "bcm2835-powermgt" @@ -24,6 +25,7 @@ uint32_t rstc; uint32_t rsts; uint32_t wdog; + QEMUTimer *wdog_timer; }; #endif
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h index 8d3c83a..b390f2f 100644 --- a/include/hw/s390x/ipl/qipl.h +++ b/include/hw/s390x/ipl/qipl.h
@@ -20,6 +20,8 @@ #define LOADPARM_LEN 8 #define NO_LOADPARM "\0\0\0\0\0\0\0\0" +#define MAX_BOOT_ENTRIES 32 + enum S390IplType { S390_IPL_TYPE_FCP = 0x00, S390_IPL_TYPE_CCW = 0x02,
diff --git a/include/hw/sd/axiado_sdhci.h b/include/hw/sd/axiado_sdhci.h new file mode 100644 index 0000000..85afeba --- /dev/null +++ b/include/hw/sd/axiado_sdhci.h
@@ -0,0 +1,21 @@ +/* + * Axiado SD Host Controller + * + * Author: Kuan-Jui Chiu <kchiu@axiado.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "hw/sd/sdhci.h" +#include "qom/object.h" + +#define TYPE_AXIADO_SDHCI "axiado-sdhci" +OBJECT_DECLARE_SIMPLE_TYPE(AxiadoSDHCIState, AXIADO_SDHCI) + +typedef struct AxiadoSDHCIState { + SysBusDevice parent; + + SDHCIState sdhci; + MemoryRegion emmc_phy; + BusState *sd_bus; +} AxiadoSDHCIState;
diff --git a/include/hw/sensor/adc128d818.h b/include/hw/sensor/adc128d818.h new file mode 100644 index 0000000..10c34b9 --- /dev/null +++ b/include/hw/sensor/adc128d818.h
@@ -0,0 +1,14 @@ +/* + * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SENSOR_ADC128D818_H +#define HW_SENSOR_ADC128D818_H + +#define TYPE_ADC128D818 "adc128d818" + +#endif
diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h index a273365..5f391fc 100644 --- a/include/hw/ssi/aspeed_smc.h +++ b/include/hw/ssi/aspeed_smc.h
@@ -47,7 +47,7 @@ #define TYPE_ASPEED_SMC "aspeed.smc" OBJECT_DECLARE_TYPE(AspeedSMCState, AspeedSMCClass, ASPEED_SMC) -#define ASPEED_SMC_R_MAX (0x100 / 4) +#define ASPEED_SMC_R_MAX (0x300 / 4) #define ASPEED_SMC_CS_MAX 5 struct AspeedSMCState { @@ -114,6 +114,7 @@ AspeedSegments *seg); void (*dma_ctrl)(AspeedSMCState *s, uint32_t value); int (*addr_width)(const AspeedSMCState *s); + int (*data_fifo_offset_to_cs)(const AspeedSMCState *s, uint32_t offset); const MemoryRegionOps *reg_ops; };
diff --git a/include/hw/timer/qct-qtimer.h b/include/hw/timer/qct-qtimer.h new file mode 100644 index 0000000..53d8291 --- /dev/null +++ b/include/hw/timer/qct-qtimer.h
@@ -0,0 +1,43 @@ +/* + * Qualcomm QCT QTimer + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_TIMER_QCT_QTIMER_H +#define HW_TIMER_QCT_QTIMER_H + +#include "qom/object.h" + +#define TYPE_QCT_QTIMER "qct-qtimer" + +/* QTimer interface for external access from hexagon_globalreg */ +#define TYPE_QCT_QTIMER_INTERFACE "qct-qtimer-if" + +typedef struct QctQtimerInterface QctQtimerInterface; + +typedef struct QctQtimerInterfaceClass { + InterfaceClass parent_class; + + /* Read the live physical counter, backing HEX_SREG_TIMERLO/TIMERHI */ + uint32_t (*get_timer_lo)(const QctQtimerInterface *qtimer); + uint32_t (*get_timer_hi)(const QctQtimerInterface *qtimer); +} QctQtimerInterfaceClass; + +DECLARE_OBJ_CHECKERS(QctQtimerInterface, QctQtimerInterfaceClass, + QCT_QTIMER_INTERFACE, TYPE_QCT_QTIMER_INTERFACE); + +static inline uint32_t qct_qtimer_get_timer_lo(const QctQtimerInterface *qtimer) +{ + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer); + return k->get_timer_lo(qtimer); +} + +static inline uint32_t qct_qtimer_get_timer_hi(const QctQtimerInterface *qtimer) +{ + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer); + return k->get_timer_hi(qtimer); +} + +#endif /* HW_TIMER_QCT_QTIMER_H */
diff --git a/include/qemu/bitops.h b/include/qemu/bitops.h index c7b838a..f7363a5 100644 --- a/include/qemu/bitops.h +++ b/include/qemu/bitops.h
@@ -43,10 +43,9 @@ * be some guest-visible register view of the bit array. * * We do not currently implement uint32_t versions of find_last_bit(), - * find_next_bit(), find_next_zero_bit(), find_first_bit() or - * find_first_zero_bit(), because we haven't yet needed them. If you - * need them you should implement them similarly to the 'unsigned long' - * versions. + * find_next_bit(), find_next_zero_bit() or find_first_zero_bit(), + * because we haven't yet needed them. If you need them you should + * implement them similarly to the 'unsigned long' versions. * * You can declare a bitmap to be used with these functions via the * DECLARE_BITMAP and DECLARE_BITMAP32 macros in bitmap.h. @@ -383,6 +382,29 @@ } /** + * find_first_bit32 - find the first set bit in a memory region + * @addr: The address to start the search at + * @size: The maximum size to search + * + * Returns the bit number of the first set bit, + * or @size if there is no set bit in the bitmap. + */ +static inline uint32_t find_first_bit32(const uint32_t *addr, uint32_t size) +{ + uint32_t result; + + for (result = 0; result < size; result += 32) { + uint32_t tmp = *addr++; + if (tmp) { + result += ctz32(tmp); + return result < size ? result : size; + } + } + /* Not found */ + return size; +} + +/** * DOC: Miscellaneous bit operations on single values * * These functions are a collection of useful operations
diff --git a/include/qemu/host-utils.h b/include/qemu/host-utils.h index 2e8da7f..1db9dbb 100644 --- a/include/qemu/host-utils.h +++ b/include/qemu/host-utils.h
@@ -607,10 +607,10 @@ } /** - * sadd32_saturate - addition with saturation + * sadd32_saturate - 32-bit signed addition with saturation * @x, @y: addends * - * Computes @x + @y, and saturates rathern than truncating the result. + * Computes @x + @y, and saturates rather than truncating the result. */ static inline int32_t sadd32_saturate(int32_t x, int32_t y) { @@ -622,10 +622,10 @@ } /** - * sadd64_saturate - addition with saturation + * sadd64_saturate - 64-bit signed addition with saturation * @x, @y: addends * - * Computes @x + @y, and saturates rathern than truncating the result. + * Computes @x + @y, and saturates rather than truncating the result. */ static inline int64_t sadd64_saturate(int64_t x, int64_t y) { @@ -637,31 +637,31 @@ } /** - * ssub32_saturate - subtraction with saturation + * ssub32_saturate - 32-bit signed subtraction with saturation * @x, @y: addends * - * Computes @x + @y, and saturates rathern than truncating the result. + * Computes @x - @y, and saturates rather than truncating the result. */ -static inline bool ssub32_saturate(int32_t x, int32_t y) +static inline int32_t ssub32_saturate(int32_t x, int32_t y) { int32_t ret; if (ssub32_overflow(x, y, &ret)) { - ret = x < 0 ? INT32_MAX : INT32_MIN; + ret = x < 0 ? INT32_MIN : INT32_MAX; } return ret; } /** - * ssub64_saturate - subtraction with saturation + * ssub64_saturate - 64-bit signed subtraction with saturation * @x, @y: addends * - * Computes @x + @y, and saturates rathern than truncating the result. + * Computes @x - @y, and saturates rather than truncating the result. */ -static inline bool ssub64_saturate(int64_t x, int64_t y) +static inline int64_t ssub64_saturate(int64_t x, int64_t y) { int64_t ret; if (ssub64_overflow(x, y, &ret)) { - ret = x < 0 ? INT64_MAX : INT64_MIN; + ret = x < 0 ? INT64_MIN : INT64_MAX; } return ret; }
diff --git a/linux-user/aarch64/elfload.c b/linux-user/aarch64/elfload.c index 64e25a0..48be495 100644 --- a/linux-user/aarch64/elfload.c +++ b/linux-user/aarch64/elfload.c
@@ -177,6 +177,7 @@ GET_FEATURE_ID(aa64_ssve_fexpa, ARM_HWCAP_A64_SME_SFEXPA); GET_FEATURE_ID(aa64_fprcvt, ARM_HWCAP_A64_FPRCVT); GET_FEATURE_ID(aa64_sme_mop4, ARM_HWCAP_A64_SME_SMOP4); + GET_FEATURE_ID(aa64_sme_tmop, ARM_HWCAP_A64_SME_STMOP); return hwcaps; }
diff --git a/linux-user/alpha/elfload.c b/linux-user/alpha/elfload.c index 7be9e46..c251751 100644 --- a/linux-user/alpha/elfload.c +++ b/linux-user/alpha/elfload.c
@@ -32,3 +32,13 @@ { return "ev67"; } + +void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUAlphaState *env) +{ + int i; + + for (i = 0; i < 31; i++) { + r->fpr[i] = tswap64(env->fir[i]); + } + r->fpcr = tswap64(cpu_alpha_load_fpcr((CPUAlphaState *)env)); +}
diff --git a/linux-user/alpha/target_elf.h b/linux-user/alpha/target_elf.h index dd90c6f..5efe7f3 100644 --- a/linux-user/alpha/target_elf.h +++ b/linux-user/alpha/target_elf.h
@@ -19,6 +19,17 @@ * r0-r30 at indices 0-30, pc at 31, ps at 32. * r31 (hardwired zero) is not stored; pc occupies index 31. */ +/* + * The floating-point note holds $f0 through $f30 and then the control + * register in the slot $f31 would occupy; $f31 reads as zero. + */ +#define HAVE_ELF_CORE_FPREGS 1 + +typedef struct target_elf_fpregset_t { + uint64_t fpr[31]; /* $f0-$f30 */ + uint64_t fpcr; /* the slot for $f31 */ +} target_elf_fpregset_t; + typedef struct target_elf_gregset_t { abi_ulong regs[31]; /* integer registers r0-r30 [0..30] */ abi_ulong pc; /* program counter [31] */
diff --git a/linux-user/elfload.c b/linux-user/elfload.c index e7c56af..88508de 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c
@@ -1887,6 +1887,17 @@ bswap_phdr(phdr, 1); } +#ifdef HAVE_ELF_CORE_FPREGS +static void fill_fpregset_note(void *data, CPUState *cpu) +{ + /* Fill locally and copy: note memory is only aligned to 4. */ + target_elf_fpregset_t fpregs = {}; + + elf_core_copy_fpregs(&fpregs, cpu_env(cpu)); + memcpy(data, &fpregs, sizeof(fpregs)); +} +#endif + static void fill_prstatus_note(void *data, CPUState *cpu, int signr) { /* @@ -2166,6 +2177,9 @@ offset += size_note("CORE", ts->info->auxv_len); offset += size_note("CORE", sizeof(struct target_elf_prpsinfo)); offset += size_note("CORE", sizeof(struct target_elf_prstatus)) * cpus; +#ifdef HAVE_ELF_CORE_FPREGS + offset += size_note("CORE", sizeof(target_elf_fpregset_t)) * cpus; +#endif note_size = offset - note_offset; data_offset = TARGET_PAGE_ALIGN(offset); @@ -2222,6 +2236,11 @@ dptr = fill_note(&hptr, NT_PRSTATUS, "CORE", sizeof(struct target_elf_prstatus)); fill_prstatus_note(dptr, cpu_iter, cpu_iter == cpu ? signr : 0); +#ifdef HAVE_ELF_CORE_FPREGS + dptr = fill_note(&hptr, NT_FPREGSET, "CORE", + sizeof(target_elf_fpregset_t)); + fill_fpregset_note(dptr, cpu_iter); +#endif } if (dump_write(fd, header, data_offset) < 0) {
diff --git a/linux-user/hppa/elfload.c b/linux-user/hppa/elfload.c index ff4301b..dd5b0b3 100644 --- a/linux-user/hppa/elfload.c +++ b/linux-user/hppa/elfload.c
@@ -17,6 +17,13 @@ return "PARISC"; } +void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUArchState *env) +{ + for (int i = 0; i < 32; i++) { + r->fpr[i] = tswap64(env->fr[i]); + } +} + void elf_core_copy_regs(target_elf_gregset_t *r, const CPUArchState *env) { int i;
diff --git a/linux-user/hppa/target_elf.h b/linux-user/hppa/target_elf.h index 22547b1..4357873 100644 --- a/linux-user/hppa/target_elf.h +++ b/linux-user/hppa/target_elf.h
@@ -39,4 +39,13 @@ #define STACK_ALIGNMENT 64 #define VDSO_HEADER "vdso.c.inc" +#define HAVE_ELF_CORE_FPREGS 1 + +/* + * Matches the kernel's elf_fpregset_t (ELF_NFPREG = 32): fr0-fr31. + */ +typedef struct target_elf_fpregset_t { + uint64_t fpr[32]; +} target_elf_fpregset_t; + #endif
diff --git a/linux-user/loader.h b/linux-user/loader.h index da9ad28..5ddd140 100644 --- a/linux-user/loader.h +++ b/linux-user/loader.h
@@ -109,6 +109,9 @@ struct target_elf_gregset_t; void elf_core_copy_regs(struct target_elf_gregset_t *, const CPUArchState *); +/* Only defined by a target whose target_elf.h sets HAVE_ELF_CORE_FPREGS. */ +struct target_elf_fpregset_t; +void elf_core_copy_fpregs(struct target_elf_fpregset_t *, const CPUArchState *); typedef struct { const uint8_t *image;
diff --git a/linux-user/mips/elfload.c b/linux-user/mips/elfload.c index ce2c451..1d62ae0 100644 --- a/linux-user/mips/elfload.c +++ b/linux-user/mips/elfload.c
@@ -130,6 +130,14 @@ #undef MATCH_PLATFORM_INSN +void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUMIPSState *env) +{ + for (int i = 0; i < 32; i++) { + r->fpr[i] = tswap64(env->active_fpu.fpr[i].d); + } + r->fcsr = tswap32(env->active_fpu.fcr31); +} + /* See linux kernel: arch/mips/kernel/process.c:elf_dump_regs. */ #ifndef TARGET_MIPS64 void elf_core_copy_regs(target_elf_gregset_t *r, const CPUMIPSState *env)
diff --git a/linux-user/mips/target_elf.h b/linux-user/mips/target_elf.h index 157306f..426f905 100644 --- a/linux-user/mips/target_elf.h +++ b/linux-user/mips/target_elf.h
@@ -26,4 +26,17 @@ }; } target_elf_gregset_t; +#define HAVE_ELF_CORE_FPREGS 1 + +/* + * Matches the kernel's elf_fpregset_t (ELF_NFPREG = 33): + * fpr[0..31] hold f0-f31; fcsr occupies the low 32 bits of slot 32. + * pad rounds the struct to 33 × 8 bytes = 264 bytes. + */ +typedef struct target_elf_fpregset_t { + uint64_t fpr[32]; + uint32_t fcsr; + uint32_t pad; +} target_elf_fpregset_t; + #endif
diff --git a/linux-user/mips64/target_elf.h b/linux-user/mips64/target_elf.h index 061471a..efb5d97 100644 --- a/linux-user/mips64/target_elf.h +++ b/linux-user/mips64/target_elf.h
@@ -32,4 +32,17 @@ }; } target_elf_gregset_t; +#define HAVE_ELF_CORE_FPREGS 1 + +/* + * Matches the kernel's elf_fpregset_t (ELF_NFPREG = 33): + * fpr[0..31] hold f0-f31; fcsr occupies the low 32 bits of slot 32. + * pad rounds the struct to 33 × 8 bytes = 264 bytes. + */ +typedef struct target_elf_fpregset_t { + uint64_t fpr[32]; + uint32_t fcsr; + uint32_t pad; +} target_elf_fpregset_t; + #endif
diff --git a/linux-user/riscv/elfload.c b/linux-user/riscv/elfload.c index afe103a..1bc8bea 100644 --- a/linux-user/riscv/elfload.c +++ b/linux-user/riscv/elfload.c
@@ -11,6 +11,15 @@ return "max"; } +void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPURISCVState *env) +{ + for (int i = 0; i < 32; i++) { + r->fpr[i] = tswap64(env->fpr[i]); + } + r->fcsr = tswap32(((uint32_t)env->frm << FSR_RD_SHIFT) | + (riscv_cpu_get_fflags((CPURISCVState *)env) << FSR_AEXC_SHIFT)); +} + void elf_core_copy_regs(target_elf_gregset_t *r, const CPURISCVState *env) { r->pc = tswapal(env->pc);
diff --git a/linux-user/riscv/target_elf.h b/linux-user/riscv/target_elf.h index 859f726..185b81a 100644 --- a/linux-user/riscv/target_elf.h +++ b/linux-user/riscv/target_elf.h
@@ -27,4 +27,15 @@ abi_ulong regs[31]; } target_elf_gregset_t; +#define HAVE_ELF_CORE_FPREGS 1 + +/* + * Matches struct __riscv_d_ext_state from uapi/asm/ptrace.h: + * f0-f31 as 64-bit values followed by fcsr. + */ +typedef struct target_elf_fpregset_t { + uint64_t fpr[32]; + uint32_t fcsr; +} target_elf_fpregset_t; + #endif
diff --git a/linux-user/sh4/cpu_loop.c b/linux-user/sh4/cpu_loop.c index ee2958d..0815b9c 100644 --- a/linux-user/sh4/cpu_loop.c +++ b/linux-user/sh4/cpu_loop.c
@@ -64,6 +64,13 @@ cpu_exec_step_atomic(cs); arch_interrupt = false; break; + case 0x180: + /* Illegal instruction */ + /* fallthrough */ + case 0x1a0: + /* Illegal instruction in delay slot */ + force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC, env->pc); + break; default: fprintf(stderr, "Unhandled trap: 0x%x\n", trapnr); cpu_dump_state(cs, stderr, 0);
diff --git a/linux-user/sh4/elfload.c b/linux-user/sh4/elfload.c index f03ce49..4160162 100644 --- a/linux-user/sh4/elfload.c +++ b/linux-user/sh4/elfload.c
@@ -52,6 +52,16 @@ return hwcap; } +void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUSH4State *env) +{ + for (int i = 0; i < 16; i++) { + r->fpregs[i] = tswap32(env->fregs[i]); + r->xfpregs[i] = tswap32(env->fregs[16 + i]); + } + r->fpscr = tswap32(env->fpscr); + r->fpul = tswap32(env->fpul); +} + void elf_core_copy_regs(target_elf_gregset_t *r, const CPUSH4State *env) { for (int i = 0; i < 16; i++) {
diff --git a/linux-user/sh4/signal.c b/linux-user/sh4/signal.c index 00290d6..047174a 100644 --- a/linux-user/sh4/signal.c +++ b/linux-user/sh4/signal.c
@@ -109,7 +109,7 @@ the SP, otherwise we would be pushing the signal context to invalid memory. */ regs->gregs[15] = regs->gregs[1]; - } else if (regs->flags & TB_FLAG_DELAY_SLOT) { + } else if (regs->flags & (TB_FLAG_DELAY_SLOT | TB_FLAG_DELAY_SLOT_COND)) { /* If we are in a delay slot, push the previous instruction. */ regs->pc -= 2; } @@ -206,6 +206,8 @@ __put_user(set->sig[i + 1], &frame->extramask[i]); } + regs->fpscr = FPSCR_PR; + /* Set up to return from userspace. If provided, use a stub already in userspace. */ if (ka->sa_flags & TARGET_SA_RESTORER) { @@ -258,6 +260,8 @@ __put_user(set->sig[i], &frame->uc.tuc_sigmask.sig[i]); } + regs->fpscr = FPSCR_PR; + /* Set up to return from userspace. If provided, use a stub already in userspace. */ if (ka->sa_flags & TARGET_SA_RESTORER) {
diff --git a/linux-user/sh4/target_elf.h b/linux-user/sh4/target_elf.h index 3fcb63d..5923022 100644 --- a/linux-user/sh4/target_elf.h +++ b/linux-user/sh4/target_elf.h
@@ -25,4 +25,16 @@ struct target_pt_regs pt; } target_elf_gregset_t; +#define HAVE_ELF_CORE_FPREGS 1 + +/* + * Matches struct user_fpu_struct from arch/sh/include/asm/user.h. + */ +typedef struct target_elf_fpregset_t { + uint32_t fpregs[16]; + uint32_t xfpregs[16]; + uint32_t fpscr; + uint32_t fpul; +} target_elf_fpregset_t; + #endif
diff --git a/linux-user/strace.list b/linux-user/strace.list index 25ace05..e952f15 100644 --- a/linux-user/strace.list +++ b/linux-user/strace.list
@@ -1737,3 +1737,6 @@ #ifdef TARGET_NR_fspick { TARGET_NR_fspick, "fspick", "%s(%d,%s,%d)", NULL, NULL }, #endif +#ifdef TARGET_NR_mount_setattr +{ TARGET_NR_mount_setattr, "mount_setattr", "%s(%d,%s,%d,%p,%d)", NULL, NULL }, +#endif
diff --git a/linux-user/syscall.c b/linux-user/syscall.c index dc02868..cfa68df 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c
@@ -9735,6 +9735,13 @@ int, __to_dfd, const char *, __to_pathname, unsigned int, flag) #endif +#if defined(TARGET_NR_mount_setattr) && defined(__NR_mount_setattr) +#define __NR_sys_mount_setattr __NR_mount_setattr +_syscall5(int, sys_mount_setattr, int, dfd, const char *, path, + unsigned int, flags, struct mount_attr_ver0 *, uattr, + size_t, usize) +#endif + #if defined(TARGET_NR_fsopen) && defined(__NR_fsopen) #define __NR_sys_fsopen __NR_fsopen _syscall2(int, sys_fsopen, const char *, fs_name, unsigned int, flags); @@ -14480,6 +14487,43 @@ return do_map_shadow_stack(cpu_env, arg1, arg2, arg3); #endif +#if defined(TARGET_NR_mount_setattr) && defined(__NR_mount_setattr) + case TARGET_NR_mount_setattr: + { + struct mount_attr_ver0 attr = {}; + abi_ulong usize = arg5; + + if (usize < sizeof(struct target_mount_attr_ver0)) { + return -TARGET_EINVAL; + } + ret = copy_struct_from_user(&attr, sizeof(attr), arg4, usize); + if (ret) { + if (ret == -TARGET_E2BIG) { + qemu_log_mask(LOG_UNIMP, + "Unimplemented mount_setattr mount_attr " + "size: " TARGET_ABI_FMT_lu "\n", usize); + } + return ret; + } + /* + * MOUNT_ATTR_* and the MS_* propagation flags have the same + * values on all targets, so only byte order needs fixing up. + */ + attr.attr_set = tswap64(attr.attr_set); + attr.attr_clr = tswap64(attr.attr_clr); + attr.propagation = tswap64(attr.propagation); + attr.userns_fd = tswap64(attr.userns_fd); + + p = lock_user_string(arg2); + if (!p) { + return -TARGET_EFAULT; + } + ret = get_errno(sys_mount_setattr(arg1, p, arg3, &attr, + sizeof(attr))); + unlock_user(p, arg2, 0); + } + return ret; +#endif #if defined(TARGET_NR_fsopen) && defined(__NR_fsopen) case TARGET_NR_fsopen: {
diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index e033c7d..e28853c 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h
@@ -2770,6 +2770,19 @@ abi_ullong mode; abi_ullong resolve; }; +/* from kernel's include/uapi/linux/mount.h */ +struct mount_attr_ver0 { + uint64_t attr_set; + uint64_t attr_clr; + uint64_t propagation; + uint64_t userns_fd; +}; +struct target_mount_attr_ver0 { + abi_ullong attr_set; + abi_ullong attr_clr; + abi_ullong propagation; + abi_ullong userns_fd; +}; #ifndef RESOLVE_NO_MAGICLINKS #define RESOLVE_NO_MAGICLINKS 0x02 #endif
diff --git a/pc-bios/s390-ccw.img b/pc-bios/s390-ccw.img index e24a93e1..291cd77 100644 --- a/pc-bios/s390-ccw.img +++ b/pc-bios/s390-ccw.img Binary files differ
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c index 420ee32..8151226 100644 --- a/pc-bios/s390-ccw/bootmap.c +++ b/pc-bios/s390-ccw/bootmap.c
@@ -61,6 +61,7 @@ static void *s2_prev_blk = _s2; static void *s2_cur_blk = _s2 + MAX_SECTOR_SIZE; static void *s2_next_blk = _s2 + MAX_SECTOR_SIZE * 2; +static void *s2_end = _s2 + sizeof(_s2); static inline int verify_boot_info(BootInfo *bip) { @@ -308,7 +309,8 @@ } } - return menu_get_zipl_boot_index(s2_cur_blk + banner_offset); + return menu_get_zipl_boot_index(s2_cur_blk + banner_offset, + s2_end); } prev_block_nr = cur_block_nr; @@ -902,7 +904,7 @@ if (dir_rem[level] == 0) { /* Nothing remaining */ level--; - if (virtio_read(sec_loc[level], temp)) { + if (level >= 0 && virtio_read(sec_loc[level], temp)) { puts("Failed to read ISO directory"); return -EIO; }
diff --git a/pc-bios/s390-ccw/helper.h b/pc-bios/s390-ccw/helper.h index 8e3dfcb..d9b7da4 100644 --- a/pc-bios/s390-ccw/helper.h +++ b/pc-bios/s390-ccw/helper.h
@@ -45,4 +45,14 @@ } } +static inline size_t strnlen(const char *s, size_t maxlen) +{ + size_t len = 0; + + while (len < maxlen && s[len]) { + len++; + } + return len; +} + #endif
diff --git a/pc-bios/s390-ccw/menu.c b/pc-bios/s390-ccw/menu.c index eeaff78..9b81154 100644 --- a/pc-bios/s390-ccw/menu.c +++ b/pc-bios/s390-ccw/menu.c
@@ -16,6 +16,7 @@ #include "s390-ccw.h" #include "sclp.h" #include "s390-time.h" +#include "helper.h" #define KEYCODE_NO_INP '\0' #define KEYCODE_ESCAPE '\033' @@ -26,6 +27,9 @@ #define ZIPL_TIMEOUT_OFFSET 138 #define ZIPL_FLAG_OFFSET 140 +/* Max printable chars for a zipl boot menu entry */ +#define ZIPL_ENTRY_MAX 80 + #define TOD_CLOCK_MILLISECOND 0x3e8000 #define LOW_CORE_EXTERNAL_INT_ADDR 0x86 @@ -176,21 +180,31 @@ return boot_index; } -/* Returns the entry number that was printed */ +/* Returns the entry number that was printed, or -1 on invalid entry */ static int zipl_print_entry(const char *data, size_t len) { - char buf[len + 2]; + char buf[ZIPL_ENTRY_MAX + 2]; + const char *p; + + if (len > ZIPL_ENTRY_MAX) { + len = ZIPL_ENTRY_MAX; + } ebcdic_to_ascii(data, buf, len); buf[len] = '\n'; buf[len + 1] = '\0'; + p = (buf[0] == ' ') ? buf + 1 : buf; + if (!isdigit((unsigned char)*p)) { + return -1; + } + printf("%s", buf); - return buf[0] == ' ' ? atoi(buf + 1) : atoi(buf); + return atoi(p); } -int menu_get_zipl_boot_index(const char *menu_data) +int menu_get_zipl_boot_index(const char *menu_data, const char *menu_data_end) { size_t len; int entry; @@ -206,16 +220,28 @@ timeout = zipl_timeout * 1000; } - /* Print banner */ + if (menu_data >= menu_data_end) { + return 0; /* Boot default */ + } + + /* Skip banner */ + len = strnlen(menu_data, menu_data_end - menu_data); + menu_data += len + 1; + if (menu_data >= menu_data_end || !(*menu_data)) { + return 0; /* No entries, boot default */ + } + puts("s390-ccw zIPL Boot Menu\n"); - menu_data += strlen(menu_data) + 1; /* Print entries */ - while (*menu_data) { - len = strlen(menu_data); + while (menu_data < menu_data_end && *menu_data) { + len = strnlen(menu_data, menu_data_end - menu_data); entry = zipl_print_entry(menu_data, len); menu_data += len + 1; + if (entry < 0 || entry >= MAX_BOOT_ENTRIES) { + continue; + } valid_entries[entry] = true; if (entry == 0) {
diff --git a/pc-bios/s390-ccw/netmain.c b/pc-bios/s390-ccw/netmain.c index 651cedf..791854f 100644 --- a/pc-bios/s390-ccw/netmain.c +++ b/pc-bios/s390-ccw/netmain.c
@@ -40,6 +40,9 @@ #define DEFAULT_BOOT_RETRIES 10 #define DEFAULT_TFTP_RETRIES 20 +/* Index 0 is reserved for default alias, start PXE cfg indices at 1 */ +#define PXECFG_MAX (MAX_BOOT_ENTRIES - 1) + extern char _start[]; #define KERNEL_ADDR ((void *)0L) @@ -381,13 +384,13 @@ static int net_try_pxelinux_cfg(filename_ip_t *fn_ip) { - struct pl_cfg_entry entries[MAX_BOOT_ENTRIES]; + struct pl_cfg_entry entries[PXECFG_MAX]; int num_ent, def_ent = 0; num_ent = pxelinux_load_parse_cfg(fn_ip, mac, get_uuid(), DEFAULT_TFTP_RETRIES, cfgbuf, sizeof(cfgbuf), - entries, MAX_BOOT_ENTRIES, &def_ent); + entries, PXECFG_MAX, &def_ent); return net_select_and_load_kernel(fn_ip, num_ent, def_ent, entries); } @@ -470,11 +473,11 @@ * a magic comment string. */ if (!strncasecmp("# pxelinux", cfgbuf, 10)) { - struct pl_cfg_entry entries[MAX_BOOT_ENTRIES]; + struct pl_cfg_entry entries[PXECFG_MAX]; int num_ent, def_ent = 0; num_ent = pxelinux_parse_cfg(cfgbuf, sizeof(cfgbuf), entries, - MAX_BOOT_ENTRIES, &def_ent); + PXECFG_MAX, &def_ent); return net_select_and_load_kernel(fn_ip, num_ent, def_ent, entries); }
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h index 1e1f717..25aac91 100644 --- a/pc-bios/s390-ccw/s390-ccw.h +++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -76,14 +76,12 @@ /* menu.c */ void menu_set_parms(uint8_t boot_menu_flag, uint32_t boot_menu_timeout); -int menu_get_zipl_boot_index(const char *menu_data); +int menu_get_zipl_boot_index(const char *menu_data, const char *menu_data_end); bool menu_is_enabled_zipl(void); int menu_get_enum_boot_index(bool *valid_entries); bool menu_is_enabled_enum(void); int menu_get_boot_index(bool *valid_entries); -#define MAX_BOOT_ENTRIES 31 - __attribute__ ((__noreturn__)) static inline void panic(const char *string) {
diff --git a/qapi/crypto.json b/qapi/crypto.json index 2b55bef..6e3a98f 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json
@@ -121,10 +121,12 @@ # # @ctr: Counter (Since 2.8) # +# @gcm: Galois/Counter Mode (Since 11.2) +# # Since: 2.6 ## { 'enum': 'QCryptoCipherMode', - 'data': ['ecb', 'cbc', 'xts', 'ctr']} + 'data': ['ecb', 'cbc', 'xts', 'ctr', 'gcm']} ## # @QCryptoIVGenAlgo:
diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h index fb5ed25..280ead6 100644 --- a/target/arm/cpu-features.h +++ b/target/arm/cpu-features.h
@@ -1590,6 +1590,11 @@ return FIELD_EX64_IDREG(id, ID_AA64SMFR0, SMOP4); } +static inline bool isar_feature_aa64_sme_tmop(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64SMFR0, STMOP); +} + static inline bool isar_feature_aa64_ssve_aes(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64SMFR0, AES); @@ -1837,6 +1842,26 @@ return isar_feature_aa64_sme_mop4(id) && isar_feature_aa64_sme_i16i64(id); } +static inline bool isar_feature_aa64_sme_tmop_b16b16(const ARMISARegisters *id) +{ + return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_b16b16(id); +} + +static inline bool isar_feature_aa64_sme_tmop_f16f16(const ARMISARegisters *id) +{ + return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_f16f16(id); +} + +static inline bool isar_feature_aa64_sme_tmop_f8f16(const ARMISARegisters *id) +{ + return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_f8f16(id); +} + +static inline bool isar_feature_aa64_sme_tmop_f8f32(const ARMISARegisters *id) +{ + return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_f8f32(id); +} + /* * Feature tests for "does this exist in either 32-bit or 64-bit?" */
diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c index 8a3ebf6..24f3494 100644 --- a/target/arm/tcg/cpu64.c +++ b/target/arm/tcg/cpu64.c
@@ -1384,8 +1384,9 @@ SET_IDREG(isar, ID_AA64DFR0, t); t = GET_IDREG(isar, ID_AA64SMFR0); - t = FIELD_DP64(t, ID_AA64SMFR0, SFEXPA, 1); /* FEAT_SSVE_FEXPA */ t = FIELD_DP64(t, ID_AA64SMFR0, SMOP4, 1); /* FEAT_SME_MOP4 */ + t = FIELD_DP64(t, ID_AA64SMFR0, STMOP, 1); /* FEAT_SME_TMOP */ + t = FIELD_DP64(t, ID_AA64SMFR0, SFEXPA, 1); /* FEAT_SSVE_FEXPA */ t = FIELD_DP64(t, ID_AA64SMFR0, AES, 1); /* FEAT_SSVE_AES */ t = FIELD_DP64(t, ID_AA64SMFR0, SF8DP2, 1); /* FEAT_SSVE_FP8DOT2 */ t = FIELD_DP64(t, ID_AA64SMFR0, SF8DP4, 1); /* FEAT_SSVE_FP8DOT4 */
diff --git a/target/arm/tcg/fp8_helper.c b/target/arm/tcg/fp8_helper.c index 4b046f5..0be811e 100644 --- a/target/arm/tcg/fp8_helper.c +++ b/target/arm/tcg/fp8_helper.c
@@ -1005,3 +1005,53 @@ FP8MulContext ctx = fp8_mul_start(env, 0xf); sme_mop4(vza, vzn, vzm, &ctx, desc, sizeof(float16), inner_fmop4a_hb); } + +void HELPER(sme_ftmopa_hb)(void *vza, void *vzn, void *vzm, void *vzk, + CPUArchState *env, uint32_t desc) +{ + FP8MulContext ctx = fp8_mul_start(env, 0xf); + intptr_t oprsz = simd_maxsz(desc); + intptr_t dim = oprsz >> MO_16; + intptr_t index = simd_data(desc); + intptr_t ctrl_base = (index * oprsz) >> 1; + uint8_t *zn0 = vzn, *zn1 = vzn + sizeof(ARMVectorReg); + uint16_t *za = vza, *zm = vzm; + uint64_t *zk = vzk; + + for (intptr_t row = 0; row < dim; row++) { + uint16_t *za_row = za + tile_vslice_offset(row); + + for (intptr_t col = 0; col < dim; col++) { + uint16_t e2 = zm[H2(col)]; + uint16_t *e3 = za_row + H2(col); + uint16_t e1 = 0; + + /* + * Four control bits select two elements. The two elements + * may be non-contiguous, so assemble them locally into e1. + * Pseudo-code has a double loop running forward, with a + * test for (i < 2) to limit construction to 2 elements. + * Easier to run a single loop backward, shifting extra + * elements off the top of our uint16_t. + */ + uint64_t this_ctrl = extractn(zk, ctrl_base + col * 4, 4); + for (int i = 3; i >= 0; i--) { + if (this_ctrl & (1 << i)) { + bool e = i & 1; + bool r = i & 2; + uint8_t *p = (r ? zn1 : zn0) + H1(2 * row + e); + e1 = (e1 << 8) | *p; + } + } + + *e3 = f8dotadd_h(e1, e2, 2, *e3, &ctx); + } + } +} + +void HELPER(sme_ftmopa_sb)(void *vza, void *vzn, void *vzm, void *vzk, + CPUArchState *env, uint32_t desc) +{ + FP8MulContext ctx = fp8_mul_start(env, 0xf); + sme_tmop_4way_sb(vza, vzn, vzm, vzk, &ctx, desc, inner_fmop4a_sb); +}
diff --git a/target/arm/tcg/helper-fp8-defs.h b/target/arm/tcg/helper-fp8-defs.h index dedbd85..e2cf2ba 100644 --- a/target/arm/tcg/helper-fp8-defs.h +++ b/target/arm/tcg/helper-fp8-defs.h
@@ -47,3 +47,6 @@ DEF_HELPER_FLAGS_5(sme_fmop4a_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32) DEF_HELPER_FLAGS_5(sme_fmop4a_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32) + +DEF_HELPER_FLAGS_6(sme_ftmopa_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32) +DEF_HELPER_FLAGS_6(sme_ftmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32)
diff --git a/target/arm/tcg/helper-sme-defs.h b/target/arm/tcg/helper-sme-defs.h index 7fc7129..b8cb643 100644 --- a/target/arm/tcg/helper-sme-defs.h +++ b/target/arm/tcg/helper-sme-defs.h
@@ -404,3 +404,18 @@ DEF_HELPER_FLAGS_4(sme_usmop4s_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_4(sme_usmop4a_dh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_4(sme_usmop4s_dh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) + +DEF_HELPER_FLAGS_6(sme_bftmopa_hh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32) +DEF_HELPER_FLAGS_6(sme_ftmopa_hh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32) +DEF_HELPER_FLAGS_6(sme_ftmopa_ss, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32) + +DEF_HELPER_FLAGS_6(sme_bftmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32) +DEF_HELPER_FLAGS_6(sme_ftmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32) + +DEF_HELPER_FLAGS_5(sme_stmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_5(sme_utmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32) + +DEF_HELPER_FLAGS_5(sme_stmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_5(sme_sutmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_5(sme_utmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_5(sme_ustmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
diff --git a/target/arm/tcg/sme.decode b/target/arm/tcg/sme.decode index 53e7e67..e8176ff 100644 --- a/target/arm/tcg/sme.decode +++ b/target/arm/tcg/sme.decode
@@ -1131,3 +1131,31 @@ USMOP4_sb 1000 0001 000. ...0 1000 00.. ..0. 00.. @mop4_o2 USMOP4_dh 1010 0001 110. ...0 0000 00.. ..0. 1... @mop4_o3 + +# SME TMOP Sparse outer products + +&tmop zad zn zm zk idx +%tmop_zk 10:3 !function=expand_tmop_zk + +@tmop_o1 .... .... ... zm:5 ... ... .... idx:2 ... zad:1 \ + &tmop zn=%zn_ax2 zk=%tmop_zk +@tmop_o2 .... .... ... zm:5 ... ... .... idx:2 .. zad:2 \ + &tmop zn=%zn_ax2 zk=%tmop_zk + +BFTMOPA_hh 1000 0001 011 ..... 000 ... .... .. 100 . @tmop_o1 +FTMOPA_hh 1000 0001 010 ..... 000 ... .... .. 100 . @tmop_o1 +FTMOPA_ss 1000 0000 010 ..... 000 ... .... .. 00 .. @tmop_o2 + +BFTMOPA_sh 1000 0001 010 ..... 000 ... .... .. 00 .. @tmop_o2 +FTMOPA_sh 1000 0001 011 ..... 000 ... .... .. 00 .. @tmop_o2 + +FTMOPA_hb 1000 0000 011 ..... 000 ... .... .. 100 . @tmop_o1 +FTMOPA_sb 1000 0000 011 ..... 000 ... .... .. 00 .. @tmop_o2 + +STMOPA_sh 1000 0000 010 ..... 100 ... .... .. 10 .. @tmop_o2 +UTMOPA_sh 1000 0001 010 ..... 100 ... .... .. 10 .. @tmop_o2 + +STMOPA_sb 1000 0000 010 ..... 100 ... .... .. 00 .. @tmop_o2 +SUTMOPA_sb 1000 0000 011 ..... 100 ... .... .. 00 .. @tmop_o2 +USTMOPA_sb 1000 0001 010 ..... 100 ... .... .. 00 .. @tmop_o2 +UTMOPA_sb 1000 0001 011 ..... 100 ... .... .. 00 .. @tmop_o2
diff --git a/target/arm/tcg/sme_helper.c b/target/arm/tcg/sme_helper.c index 23bb816..9de9542 100644 --- a/target/arm/tcg/sme_helper.c +++ b/target/arm/tcg/sme_helper.c
@@ -2636,6 +2636,137 @@ } } +/* + * Sparse outer product, non-widening. ESZ in {16, 32}. + */ +static void sme_tmop(void *vza, void *vzn, void *vzm, uint64_t *zk, + void *fn_opaque, uint32_t desc, MemOp esz, + void (*fn)(void *, void *, void *, void *)) +{ + intptr_t oprsz = simd_maxsz(desc); + intptr_t index = simd_data(desc); + intptr_t esize = 1 << esz; + intptr_t host_adj = HOST_BIG_ENDIAN ? 8 - esize : 0; + /* Base in bits for op3[index*:csize], csize = (VL * 2) / esize. */ + intptr_t ctrl_base = index * oprsz * 2; + /* Create a zero for use with the largest esz. */ + uint32_t zero = 0; + + for (intptr_t row = 0; row < oprsz; row += esize) { + void *vza_row = vza + tile_vslice_offset(row); + + for (intptr_t col = 0; col < oprsz; col += esize) { + void *e2 = vzm + (col ^ host_adj); + void *e3 = vza_row + (col ^ host_adj); + + /* + * Two control bits select one element: + * Zn[row], if [0] is set, + * Zn+1[row], if [1] is set, + * 0, otherwise. + * Compute the address of that element. + */ + void *e1 = &zero; + uint64_t this_ctrl = extractn(zk, (ctrl_base + 2 * col) >> esz, 2); + if (this_ctrl) { + e1 = vzn + (row ^ host_adj); + if (!(this_ctrl & 1)) { + e1 += sizeof(ARMVectorReg); + } + } + fn(e3, e1, e2, fn_opaque); + } + } +} + +/* + * Sparse outer product, widening 2-way, 16 to 32-bit. + */ +static void sme_tmop_2way_sh(uint32_t *za, uint16_t *zn0, uint32_t *zm, + uint64_t *zk, void *fn_opaque, uint32_t desc, + void (*fn)(void *, void *, void *, void *)) +{ + intptr_t oprsz = simd_maxsz(desc); + intptr_t dim = oprsz >> MO_32; + intptr_t index = simd_data(desc); + intptr_t ctrl_base = (index * oprsz) >> 1; + uint16_t *zn1 = zn0 + sizeof(ARMVectorReg) / 2; + + for (intptr_t row = 0; row < dim; row++) { + uint32_t *za_row = za + tile_vslice_offset(row); + + for (intptr_t col = 0; col < dim; col++) { + uint32_t *e2 = zm + H4(col); + uint32_t *e3 = za_row + H4(col); + uint32_t e1 = 0; + + /* + * Four control bits select two elements. The two elements + * may be non-contiguous, so assemble them locally into e1. + * Pseudo-code has a double loop running forward, with a + * test for (i < 2) to limit construction to 2 elements. + * Easier to run a single loop backward, shifting extra + * elements off the top of our uint32_t. + */ + uint64_t this_ctrl = extractn(zk, ctrl_base + col * 4, 4); + for (int i = 3; i >= 0; i--) { + if (this_ctrl & (1 << i)) { + bool e = i & 1; + bool r = i & 2; + uint16_t *p = (r ? zn1 : zn0) + H2(2 * row + e); + e1 = (e1 << 16) | *p; + } + } + + fn(e3, &e1, e2, fn_opaque); + } + } +} + +void sme_tmop_4way_sb(uint32_t *za, uint8_t *zn0, uint32_t *zm, + uint64_t *zk, void *fn_opaque, uint32_t desc, + void (*fn)(void *, void *, void *, void *)) +{ + intptr_t oprsz = simd_maxsz(desc); + intptr_t dim = oprsz >> MO_32; + intptr_t index = simd_data(desc); + intptr_t ctrl_base = (index * oprsz) >> 1; + uint8_t *zn1 = zn0 + sizeof(ARMVectorReg); + + for (intptr_t row = 0; row < dim; row++) { + uint32_t *za_row = za + tile_vslice_offset(row); + + for (intptr_t col = 0; col < dim; col++) { + uint32_t *e2 = zm + H4(col); + uint32_t *e3 = za_row + H4(col); + uint16_t e1l = 0, e1h = 0; + uint32_t e1; + + /* + * Eight control bits select two elements from each row. + * The elements may be non-contiguous, so assemble them + * locally into e1. + * Pseudo-code has a triple loop running forward, with a + * test for (i < 2) to limit construction to 2 elements. + * Easier to run a single loop backward, shifting extra + * elements off the top. + */ + uint64_t this_ctrl = extractn(zk, ctrl_base + col * 8, 8); + for (int e = 3; e >= 0; e--) { + if (this_ctrl & (0x01 << e)) { + e1l = (e1l << 8) | zn0[H1(4 * row + e)]; + } + if (this_ctrl & (0x10 << e)) { + e1h = (e1h << 8) | zn1[H1(4 * row + e)]; + } + } + e1 = (e1h << 16) | e1l; + + fn(e3, &e1, e2, fn_opaque); + } + } +} + static void inner_fmop4a_hh(void *vd, void *vn, void *vm, void *vinfo) { float16 *d = vd, *n = vn, *m = vm; @@ -2650,6 +2781,12 @@ sme_mop4(vza, vzn, vzm, fpst, desc, sizeof(float16), inner_fmop4a_hh); } +void HELPER(sme_ftmopa_hh)(void *vza, void *vzn, void *vzm, void *vzk, + float_status *fpst, uint32_t desc) +{ + sme_tmop(vza, vzn, vzm, vzk, fpst, desc, MO_16, inner_fmop4a_hh); +} + static void inner_fmop4s_hh(void *vd, void *vn, void *vm, void *vinfo) { float16 *d = vd, *n = vn, *m = vm; @@ -2692,6 +2829,12 @@ sme_mop4(vza, vzn, vzm, fpst, desc, sizeof(float32), inner_fmop4a_ss); } +void HELPER(sme_ftmopa_ss)(void *vza, void *vzn, void *vzm, void *vzk, + float_status *fpst, uint32_t desc) +{ + sme_tmop(vza, vzn, vzm, vzk, fpst, desc, MO_32, inner_fmop4a_ss); +} + static void inner_fmop4s_ss(void *vd, void *vn, void *vm, void *vinfo) { float32 *d = vd, *n = vn, *m = vm; @@ -2776,6 +2919,12 @@ sme_mop4(vza, vzn, vzm, fpst, desc, sizeof(bfloat16), inner_bfmop4a_hh); } +void HELPER(sme_bftmopa_hh)(void *vza, void *vzn, void *vzm, void *vzk, + float_status *fpst, uint32_t desc) +{ + sme_tmop(vza, vzn, vzm, vzk, fpst, desc, MO_16, inner_bfmop4a_hh); +} + static void inner_bfmop4s_hh(void *vd, void *vn, void *vm, void *vinfo) { bfloat16 *d = vd, *n = vn, *m = vm; @@ -2832,6 +2981,16 @@ : inner_bfmop4a_sh); } +void HELPER(sme_bftmopa_sh)(void *vza, void *vzn, void *vzm, void *vzk, + CPUArchState *env, uint32_t desc) +{ + float_status fpst; + + sme_tmop_2way_sh(vza, vzn, vzm, vzk, &fpst, desc, + is_ebf(env, &fpst) ? inner_ebf_bfmop4a_sh + : inner_bfmop4a_sh); +} + static void inner_bfmop4s_sh(void *vd, void *vn, void *vm, void *vinfo) { float32 *d = vd; @@ -2905,6 +3064,12 @@ sme_mop4(vza, vzn, vzm, env, desc, sizeof(float32), inner_fmop4a_sh); } +void HELPER(sme_ftmopa_sh)(void *vza, void *vzn, void *vzm, void *vzk, + CPUArchState *env, uint32_t desc) +{ + sme_tmop_2way_sh(vza, vzn, vzm, vzk, env, desc, inner_fmop4a_sh); +} + static void inner_fmop4s_sh(void *vd, void *vn, void *vm, void *vinfo) { float32 *d = vd; @@ -2958,6 +3123,18 @@ #undef IMOP4_2WAY +#define ITMOP_2WAY(TNAME, MNAME) \ +void HELPER(sme_##TNAME)(void *vza, void *vzn, void *vzm, \ + void *vzk, uint32_t desc) \ +{ \ + sme_tmop_2way_sh(vza, vzn, vzm, vzk, NULL, desc, inner_##MNAME); \ +} + +ITMOP_2WAY(stmopa_sh, smop4a_sh) +ITMOP_2WAY(utmopa_sh, umop4a_sh) + +#undef ITMOP_2WAY + #define IMOP4_4WAY(NAME, OP, TYPED, TYPEN, TYPEM) \ static void inner_##NAME(void *vd, void *vn, void *vm, void *vinfo) \ { \ @@ -2991,3 +3168,17 @@ IMOP4_4WAY(usmop4s_dh, -, int64_t, uint16_t, int16_t) #undef IMOP4_4WAY + +#define ITMOP_4WAY(TNAME, MNAME) \ +void HELPER(sme_##TNAME)(void *vza, void *vzn, void *vzm, \ + void *vzk, uint32_t desc) \ +{ \ + sme_tmop_4way_sb(vza, vzn, vzm, vzk, NULL, desc, inner_##MNAME); \ +} + +ITMOP_4WAY(stmopa_sb, smop4a_sb) +ITMOP_4WAY(utmopa_sb, umop4a_sb) +ITMOP_4WAY(sutmopa_sb, sumop4a_sb) +ITMOP_4WAY(ustmopa_sb, usmop4a_sb) + +#undef ITMOP_4WAY
diff --git a/target/arm/tcg/translate-sme.c b/target/arm/tcg/translate-sme.c index 0794794..768b76f 100644 --- a/target/arm/tcg/translate-sme.c +++ b/target/arm/tcg/translate-sme.c
@@ -31,6 +31,12 @@ * Include the generated decoder. */ +static int expand_tmop_zk(DisasContext *s, int x) +{ + /* Pseudocode for 1:K:1:zk. */ + return 0b10100 | ((x & 4) << 1) | (x & 3); +} + #include "decode-sme.c.inc" static bool sme2_zt0_enabled_check(DisasContext *s) @@ -2132,3 +2138,76 @@ a->s ? gen_helper_sme_usmop4s_sb : gen_helper_sme_usmop4a_sb) TRANS_FEAT(USMOP4_dh, aa64_sme_mop4_i16i64, do_mop4_int, a, MO_64, a->s ? gen_helper_sme_usmop4s_dh : gen_helper_sme_usmop4a_dh) + +static bool do_tmop_fp(DisasContext *s, arg_tmop *a, MemOp esz, + int e_fpst, gen_helper_gvec_4_ptr *fn) +{ + if (sme_smza_enabled_check(s)) { + int svl = streaming_vec_reg_size(s); + uint32_t desc = simd_desc(svl, svl, a->idx); + TCGv_ptr za = get_tile(s, esz, a->zad); + TCGv_ptr zn = vec_full_reg_ptr(s, a->zn); + TCGv_ptr zm = vec_full_reg_ptr(s, a->zm); + TCGv_ptr zk = vec_full_reg_ptr(s, a->zm); + TCGv_ptr fpst = (e_fpst >= 0 ? fpstatus_ptr(e_fpst) : tcg_env); + + fn(za, zn, zm, zk, fpst, tcg_constant_i32(desc)); + } + return true; +} + +TRANS_FEAT(BFTMOPA_hh, aa64_sme_tmop_b16b16, do_tmop_fp, + a, MO_16, FPST_ZA, gen_helper_sme_bftmopa_hh) +TRANS_FEAT(FTMOPA_hh, aa64_sme_tmop_f16f16, do_tmop_fp, + a, MO_16, FPST_ZA_F16, gen_helper_sme_ftmopa_hh) +TRANS_FEAT(FTMOPA_ss, aa64_sme_tmop, do_tmop_fp, + a, MO_32, FPST_ZA, gen_helper_sme_ftmopa_ss) + +TRANS_FEAT(BFTMOPA_sh, aa64_sme_tmop, do_tmop_fp, + a, MO_32, FPST_ENV, gen_helper_sme_bftmopa_sh) +TRANS_FEAT(FTMOPA_sh, aa64_sme_tmop, do_tmop_fp, + a, MO_32, FPST_ENV, gen_helper_sme_ftmopa_sh) + +static bool do_tmop_fp8(DisasContext *s, arg_tmop *a, MemOp esz, + gen_helper_gvec_4_ptr *fn) +{ + if (!fpmr_access_check(s)) { + return true; + } + return do_tmop_fp(s, a, esz, FPST_ENV, fn); +} + +TRANS_FEAT(FTMOPA_hb, aa64_sme_tmop_f8f16, do_tmop_fp8, + a, MO_16, gen_helper_sme_ftmopa_hb) +TRANS_FEAT(FTMOPA_sb, aa64_sme_tmop_f8f32, do_tmop_fp8, + a, MO_32, gen_helper_sme_ftmopa_sb) + +static bool do_tmop_int(DisasContext *s, arg_tmop *a, MemOp esz, + gen_helper_gvec_4 *fn) +{ + if (sme_smza_enabled_check(s)) { + int svl = streaming_vec_reg_size(s); + uint32_t desc = simd_desc(svl, svl, a->idx); + TCGv_ptr za = get_tile(s, esz, a->zad); + TCGv_ptr zn = vec_full_reg_ptr(s, a->zn); + TCGv_ptr zm = vec_full_reg_ptr(s, a->zm); + TCGv_ptr zk = vec_full_reg_ptr(s, a->zm); + + fn(za, zn, zm, zk, tcg_constant_i32(desc)); + } + return true; +} + +TRANS_FEAT(STMOPA_sh, aa64_sme_tmop, do_tmop_int, + a, MO_32, gen_helper_sme_stmopa_sh) +TRANS_FEAT(UTMOPA_sh, aa64_sme_tmop, do_tmop_int, + a, MO_32, gen_helper_sme_utmopa_sh) + +TRANS_FEAT(STMOPA_sb, aa64_sme_tmop, do_tmop_int, + a, MO_32, gen_helper_sme_stmopa_sb) +TRANS_FEAT(SUTMOPA_sb, aa64_sme_tmop, do_tmop_int, + a, MO_32, gen_helper_sme_sutmopa_sb) +TRANS_FEAT(USTMOPA_sb, aa64_sme_tmop, do_tmop_int, + a, MO_32, gen_helper_sme_ustmopa_sb) +TRANS_FEAT(UTMOPA_sb, aa64_sme_tmop, do_tmop_int, + a, MO_32, gen_helper_sme_utmopa_sb)
diff --git a/target/arm/tcg/vec_internal.h b/target/arm/tcg/vec_internal.h index 038a2a3..8f6d8a9 100644 --- a/target/arm/tcg/vec_internal.h +++ b/target/arm/tcg/vec_internal.h
@@ -555,4 +555,11 @@ uint32_t desc, size_t esize, void (*fn)(void *, void *, void *, void *)); +/* + * Perform SME sparse outer product, 4-way, 8 to 32-bit. + */ +void sme_tmop_4way_sb(uint32_t *za, uint8_t *zn0, uint32_t *zm, + uint64_t *zk, void *fn_opaque, uint32_t desc, + void (*fn)(void *, void *, void *, void *)); + #endif /* TARGET_ARM_VEC_INTERNAL_H */
diff --git a/target/hexagon/arch.c b/target/hexagon/arch.c index 0a400bf..3c41735 100644 --- a/target/hexagon/arch.c +++ b/target/hexagon/arch.c
@@ -199,6 +199,10 @@ set_float_rounding_mode( softfloat_roundingmodes[fREAD_REG_FIELD(USR, USR_FPRND)], &env->fp_status); + /* + * No need to check env->hvx_fp_status, these instructions don't + * raise exceptions nor interact with usr fields. + */ } #ifdef CONFIG_USER_ONLY @@ -237,6 +241,10 @@ SOFTFLOAT_TEST_FLAG(float_flag_overflow, FPOVFF, FPOVFE); SOFTFLOAT_TEST_FLAG(float_flag_underflow, FPUNFF, FPUNFE); } + /* + * No need to check env->hvx_fp_status, these instructions don't + * raise exceptions nor interact with usr fields. + */ } int arch_sf_recip_common(float32 *Rs, float32 *Rt, float32 *Rd, int *adjust,
diff --git a/target/hexagon/attribs_def.h.inc b/target/hexagon/attribs_def.h.inc index 6c55063..d12f6ac4 100644 --- a/target/hexagon/attribs_def.h.inc +++ b/target/hexagon/attribs_def.h.inc
@@ -84,6 +84,7 @@ DEF_ATTRIB(CVI_SCATTER_RELEASE, "CVI Store Release for scatter", "", "") DEF_ATTRIB(CVI_TMP_DST, "CVI instruction that doesn't write a register", "", "") DEF_ATTRIB(CVI_SLOT23, "Can execute in slot 2 or slot 3 (HVX)", "", "") +DEF_ATTRIB(CVI_VA_2SRC, "Execs on multimedia vector engine; requires two srcs", "", "") DEF_ATTRIB(VTCM_ALLBANK_ACCESS, "Allocates in all VTCM schedulers.", "", "") @@ -196,5 +197,13 @@ DEF_ATTRIB(RESTRICT_NOSLOT1_STORE, "Packet must not have slot 1 store", "", "") DEF_ATTRIB(RESTRICT_LATEPRED, "Predicate can not be used as a .new.", "", "") +/* HVX IEEE FP extension attributes */ +DEF_ATTRIB(HVX_IEEE_FP, "HVX IEEE FP extension instruction", "", "") +DEF_ATTRIB(HVX_IEEE_FP_ACC, "HVX IEEE FP accumulate instruction", "", "") +DEF_ATTRIB(HVX_IEEE_FP_OUT_16, "HVX IEEE FP 16-bit output", "", "") +DEF_ATTRIB(HVX_IEEE_FP_OUT_32, "HVX IEEE FP 32-bit output", "", "") +DEF_ATTRIB(CVI_VX_NO_TMP_LD, "HVX multiply without tmp load", "", "") +DEF_ATTRIB(HVX_FLT, "This a floating point HVX instruction.", "", "") + /* Keep this as the last attribute: */ DEF_ATTRIB(ZZ_LASTATTRIB, "Last attribute in the file", "", "")
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 12d27ce..0ddd898e 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c
@@ -65,14 +65,17 @@ DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB, HexagonTLBState *), DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffff), + DEFINE_PROP_LINK("l2vic", HexagonCPU, l2vic, + TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *), DEFINE_PROP_LINK("global-regs", HexagonCPU, globalregs, TYPE_HEXAGON_GLOBALREG, HexagonGlobalRegState *), DEFINE_PROP_UINT32("htid", HexagonCPU, htid, 0), #endif - DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), - DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, - qdev_prop_uint32, target_ulong), - DEFINE_PROP_BOOL("short-circuit", HexagonCPU, short_circuit, true), + DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, cfg.lldb_compat, false), + DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, cfg.lldb_stack_adjust, + 0, qdev_prop_uint32, target_ulong), + DEFINE_PROP_BOOL("short-circuit", HexagonCPU, cfg.short_circuit, true), + DEFINE_PROP_BOOL("ieee-fp", HexagonCPU, cfg.ieee_fp_extension, true), }; const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS] = { @@ -124,7 +127,7 @@ static target_ulong adjust_stack_ptrs(CPUHexagonState *env, target_ulong addr) { HexagonCPU *cpu = env_archcpu(env); - target_ulong stack_adjust = cpu->lldb_stack_adjust; + target_ulong stack_adjust = cpu->cfg.lldb_stack_adjust; target_ulong stack_start = env->stack_start; target_ulong stack_size = 0x10000; @@ -236,7 +239,7 @@ { HexagonCPU *cpu = env_archcpu(env); - if (cpu->lldb_compat) { + if (cpu->cfg.lldb_compat) { /* * When comparing with LLDB, it doesn't step through single-cycle * hardware loops the same way. So, we just skip them here @@ -417,6 +420,9 @@ set_float_detect_tininess(float_tininess_before_rounding, &env->fp_status); /* Default NaN value: sign bit set, all frac bits set */ set_float_default_nan_pattern(0b11111111, &env->fp_status); + + set_default_nan_mode(1, &env->hvx_fp_status); + set_float_default_nan_pattern(0b01111111, &env->hvx_fp_status); #ifndef CONFIG_USER_ONLY memset(env->t_sreg, 0, sizeof(uint32_t) * NUM_SREGS); memset(env->greg, 0, sizeof(uint32_t) * NUM_GREGS); @@ -441,12 +447,13 @@ const HexagonCPU *cpu = HEXAGON_CPU(cs); info->print_insn = print_insn_hexagon; info->endian = BFD_ENDIAN_LITTLE; - info->target_info = HEXAGON_CPU_GET_CLASS(cpu)->hex_def; + info->target_info = &cpu->cfg; } static void hexagon_cpu_realize(DeviceState *dev, Error **errp) { CPUState *cs = CPU(dev); + HexagonCPU *cpu = HEXAGON_CPU(dev); HexagonCPUClass *mcc = HEXAGON_CPU_GET_CLASS(dev); Error *local_err = NULL; @@ -456,6 +463,8 @@ return; } + cpu->cfg.hex_def = mcc->hex_def; + gdb_register_coprocessor(cs, hexagon_hvx_gdb_read_register, hexagon_hvx_gdb_write_register, gdb_find_static_feature("hexagon-hvx.xml"));
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 7694fd9..c50fbb3 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h
@@ -39,6 +39,7 @@ #include "qemu/bitmap.h" #include "target/hexagon/reg_fields.h" +#include "hw/intc/hex-l2vic.h" #define NUM_PREGS 4 #define TOTAL_PER_THREAD_REGS 64 @@ -151,6 +152,7 @@ MemLog mem_log_stores[STORES_MAX]; float_status fp_status; + float_status hvx_fp_status; target_ulong llsc_addr; target_ulong llsc_val; @@ -185,24 +187,22 @@ const HexagonCPUDef *hex_def; } HexagonCPUClass; +#include "cpu_bits.h" + struct ArchCPU { CPUState parent_obj; CPUHexagonState env; - - bool lldb_compat; - target_ulong lldb_stack_adjust; - bool short_circuit; + HexagonCPUConfig cfg; #ifndef CONFIG_USER_ONLY HexagonTLBState *tlb; uint32_t boot_addr; HexagonGlobalRegState *globalregs; uint32_t htid; + HexL2VicInterface *l2vic; #endif }; -#include "cpu_bits.h" - FIELD(TB_FLAGS, IS_TIGHT_LOOP, 0, 1) FIELD(TB_FLAGS, MMU_INDEX, 1, 3) FIELD(TB_FLAGS, PCYCLE_ENABLED, 4, 1)
diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h index 164e74c..a8fba4a 100644 --- a/target/hexagon/cpu_bits.h +++ b/target/hexagon/cpu_bits.h
@@ -21,6 +21,14 @@ #include "qemu/bitops.h" #include "cpu-qom.h" +typedef struct HexagonCPUConfig { + bool lldb_compat; + uint32_t lldb_stack_adjust; + bool short_circuit; + bool ieee_fp_extension; + const HexagonCPUDef *hex_def; +} HexagonCPUConfig; + #define PCALIGN 4 #define PCALIGN_MASK (PCALIGN - 1) @@ -123,7 +131,7 @@ return ((bits == 0x3) || (bits == 0x0)); } -int disassemble_hexagon(uint32_t *words, int nwords, bfd_vma pc, GString *buf, - const HexagonCPUDef *hex_def); +int disassemble_hexagon(uint32_t *words, int nwords, bfd_vma pc, + GString *buf, const HexagonCPUConfig *cfg); #endif
diff --git a/target/hexagon/decode.c b/target/hexagon/decode.c index 6eddcca..b12e91f 100644 --- a/target/hexagon/decode.c +++ b/target/hexagon/decode.c
@@ -549,21 +549,35 @@ return bits == 0x2; } +/* + * Check that the packet's instructions can be grouped into slots: walk them + * in encoding order handing out slots in strictly decreasing order, and fail + * if an instruction has no valid slot at or below the running slot. Two + * instructions may legally share a slot, so this does not require unique + * slots, only that every instruction fits. + */ static bool has_valid_slot_assignment(Packet *pkt) { - int used_slots = 0; - for (int i = 0; i < pkt->num_insns; i++) { - int slot_mask; - Insn *insn = &pkt->insn[i]; - if (decode_opcode_ends_loop(insn->opcode)) { + int i; + int slot = 3; + + for (i = 0; i < pkt->num_insns; i++) { + SlotMask valid_slots; + if (decode_opcode_ends_loop(pkt->insn[i].opcode)) { /* We overload slot 0 for endloop. */ continue; } - slot_mask = 1 << insn->slot; - if (used_slots & slot_mask) { + if (slot < 0) { return false; } - used_slots |= slot_mask; + valid_slots = get_valid_slots(pkt, i); + while (!(valid_slots & (1 << slot))) { + if (slot <= 0) { + return false; + } + slot--; + } + slot--; } return true; } @@ -842,7 +856,7 @@ /* Used for "-d in_asm" logging */ int disassemble_hexagon(uint32_t *words, int nwords, bfd_vma pc, - GString *buf, const HexagonCPUDef *hex_def) + GString *buf, const HexagonCPUConfig *cfg) { HexagonCPUDef any_def = { .hex_version = HEX_VER_ANY, /* Allow decode to accept anything */ @@ -853,7 +867,7 @@ ctx.hex_def = &any_def; if (decode_packet(&ctx, nwords, words, &ctx.pkt, true) > 0) { - snprint_a_pkt_disas(buf, &ctx.pkt, words, pc, hex_def); + snprint_a_pkt_disas(buf, &ctx.pkt, words, pc, cfg); return ctx.pkt.encod_pkt_size_in_bytes; } else { for (int i = 0; i < nwords; i++) {
diff --git a/target/hexagon/gen_printinsn.py b/target/hexagon/gen_printinsn.py index d5f9699..cf1a12a 100755 --- a/target/hexagon/gen_printinsn.py +++ b/target/hexagon/gen_printinsn.py
@@ -28,13 +28,14 @@ ## Generate data for printing each instruction (format string + operands) ## def regprinter(m): - str = m.group(1) - str += ":".join(["%d"] * len(m.group(2))) - str += m.group(3) if ("S" in m.group(1)) and (len(m.group(2)) == 1): - str += "/%s" + str = "%s" elif ("C" in m.group(1)) and (len(m.group(2)) == 1): - str += "/%s" + str = "%s" + else: + str = m.group(1) + str += ":".join(["%d"] * len(m.group(2))) + str += m.group(3) return str @@ -142,11 +143,12 @@ def main(): else: regno = ri if len(b) == 1: - f.write(f", insn->regno[{regno}]") if "S" in a: f.write(f", sreg2str(insn->regno[{regno}])") elif "C" in a: f.write(f", creg2str(insn->regno[{regno}])") + else: + f.write(f", insn->regno[{regno}]") elif len(b) == 2: f.write(f", insn->regno[{regno}] + 1" f", insn->regno[{regno}]") else:
diff --git a/target/hexagon/gen_tcg_funcs.py b/target/hexagon/gen_tcg_funcs.py index 6d5d99c..2592acf 100755 --- a/target/hexagon/gen_tcg_funcs.py +++ b/target/hexagon/gen_tcg_funcs.py
@@ -23,6 +23,15 @@ import hex_common from textwrap import dedent +def gen_disabled_ieee_insn(f, tag, regs): + f.write(" if (!ctx->ieee_fp_extension) {\n") + for regtype, regid in regs: + reg = hex_common.get_register(tag, regtype, regid) + if reg.is_hvx_reg() and reg.is_written(): + reg.gen_zero(f) + f.write(" return;\n") + f.write(" }\n") + ## ## Generate the TCG code to call the helper ## For A2_add: Rd32=add(Rs32,Rt32), { RdV=RsV+RtV;} @@ -74,7 +83,25 @@ def gen_tcg_func(f, tag, regs, imms): i = 1 if immlett.isupper() else 0 f.write(f" int {hex_common.imm_name(immlett)} = insn->immed[{i}];\n") + if "A_HVX_IEEE_FP" in hex_common.attribdict[tag]: + gen_disabled_ieee_insn(f, tag, regs) + if hex_common.is_idef_parser_enabled(tag): + gpr_operands = [ + hex_common.get_register(tag, regtype, regid) + for regtype, regid in regs + if hex_common.get_register(tag, regtype, regid).may_alias_gpr() + ] + dests = [reg for reg in gpr_operands if reg.is_written()] + for reg in gpr_operands: + if reg.is_written() or not reg.is_read(): + continue + src = reg.reg_tcg() + for dest in dests: + f.write(hex_common.code_fmt(f"""\ + {src} = gen_unalias_gpr_src({src}, {dest.reg_tcg()}); + """)) + declared = [] ## Handle registers for regtype, regid in regs:
diff --git a/target/hexagon/gen_tcg_hvx.h b/target/hexagon/gen_tcg_hvx.h index 0da64d4..2a342cd 100644 --- a/target/hexagon/gen_tcg_hvx.h +++ b/target/hexagon/gen_tcg_hvx.h
@@ -234,6 +234,176 @@ tcg_gen_gvec_sub(MO_32, VddV_off, VuuV_off, VvvV_off, \ sizeof(MMVector) * 2, sizeof(MMVector) * 2) +#define fGEN_TCG_V6_vaddbsat(SHORTCODE) \ + tcg_gen_gvec_ssadd(MO_8, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vaddhsat(SHORTCODE) \ + tcg_gen_gvec_ssadd(MO_16, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vaddwsat(SHORTCODE) \ + tcg_gen_gvec_ssadd(MO_32, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vaddubsat(SHORTCODE) \ + tcg_gen_gvec_usadd(MO_8, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vadduhsat(SHORTCODE) \ + tcg_gen_gvec_usadd(MO_16, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vadduwsat(SHORTCODE) \ + tcg_gen_gvec_usadd(MO_32, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_PAIR_ADDSUB(OP, VECE, DST, SRC_A, SRC_B) \ + tcg_gen_gvec_##OP(VECE, DST, SRC_A, SRC_B, \ + 2 * VECTOR_SIZE_BYTE, 2 * VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vaddbsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(ssadd, MO_8, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vaddhsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(ssadd, MO_16, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vaddwsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(ssadd, MO_32, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vaddubsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(usadd, MO_8, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vadduhsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(usadd, MO_16, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vadduwsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(usadd, MO_32, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vsubbsat(SHORTCODE) \ + tcg_gen_gvec_sssub(MO_8, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vsubhsat(SHORTCODE) \ + tcg_gen_gvec_sssub(MO_16, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vsubwsat(SHORTCODE) \ + tcg_gen_gvec_sssub(MO_32, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vsububsat(SHORTCODE) \ + tcg_gen_gvec_ussub(MO_8, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vsubuhsat(SHORTCODE) \ + tcg_gen_gvec_ussub(MO_16, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vsubuwsat(SHORTCODE) \ + tcg_gen_gvec_ussub(MO_32, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vsubbsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(sssub, MO_8, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vsubhsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(sssub, MO_16, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vsubwsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(sssub, MO_32, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vsububsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(ussub, MO_8, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vsubuhsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(ussub, MO_16, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vsubuwsat_dv(SHORTCODE) \ + fGEN_TCG_PAIR_ADDSUB(ussub, MO_32, VddV_off, VuuV_off, VvvV_off) + +#define fGEN_TCG_V6_vmpyih(SHORTCODE) \ + tcg_gen_gvec_mul(MO_16, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vabsdiffub(SHORTCODE) \ + gen_gvec_uabsdiff(MO_8, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vabsdiffuh(SHORTCODE) \ + gen_gvec_uabsdiff(MO_16, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vabsdiffh(SHORTCODE) \ + gen_gvec_sabsdiff(MO_16, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_V6_vabsdiffw(SHORTCODE) \ + gen_gvec_sabsdiff(MO_32, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE) + +#define fGEN_TCG_VEC_AVG(VECE, SHIFT_FN) \ + do { \ + intptr_t tmpoff = offsetof(CPUHexagonState, vtmp); \ + tcg_gen_gvec_and(MO_64, tmpoff, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + tcg_gen_gvec_xor(MO_64, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + SHIFT_FN(VECE, VdV_off, VdV_off, 1, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + tcg_gen_gvec_add(VECE, VdV_off, tmpoff, VdV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + } while (0) + +#define fGEN_TCG_V6_vavgub(SHORTCODE) \ + fGEN_TCG_VEC_AVG(MO_8, tcg_gen_gvec_shri) + +#define fGEN_TCG_V6_vavguh(SHORTCODE) \ + fGEN_TCG_VEC_AVG(MO_16, tcg_gen_gvec_shri) + +#define fGEN_TCG_V6_vavguw(SHORTCODE) \ + fGEN_TCG_VEC_AVG(MO_32, tcg_gen_gvec_shri) + +#define fGEN_TCG_V6_vavgb(SHORTCODE) \ + fGEN_TCG_VEC_AVG(MO_8, tcg_gen_gvec_sari) + +#define fGEN_TCG_V6_vavgh(SHORTCODE) \ + fGEN_TCG_VEC_AVG(MO_16, tcg_gen_gvec_sari) + +#define fGEN_TCG_V6_vavgw(SHORTCODE) \ + fGEN_TCG_VEC_AVG(MO_32, tcg_gen_gvec_sari) + +#define fGEN_TCG_VEC_AVGRND(VECE, SHIFT_FN) \ + do { \ + intptr_t tmpoff = offsetof(CPUHexagonState, vtmp); \ + tcg_gen_gvec_or(MO_64, tmpoff, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + tcg_gen_gvec_xor(MO_64, VdV_off, VuV_off, VvV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + SHIFT_FN(VECE, VdV_off, VdV_off, 1, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + tcg_gen_gvec_sub(VECE, VdV_off, tmpoff, VdV_off, \ + VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \ + } while (0) + +#define fGEN_TCG_V6_vavgubrnd(SHORTCODE) \ + fGEN_TCG_VEC_AVGRND(MO_8, tcg_gen_gvec_shri) + +#define fGEN_TCG_V6_vavguhrnd(SHORTCODE) \ + fGEN_TCG_VEC_AVGRND(MO_16, tcg_gen_gvec_shri) + +#define fGEN_TCG_V6_vavguwrnd(SHORTCODE) \ + fGEN_TCG_VEC_AVGRND(MO_32, tcg_gen_gvec_shri) + +#define fGEN_TCG_V6_vavgbrnd(SHORTCODE) \ + fGEN_TCG_VEC_AVGRND(MO_8, tcg_gen_gvec_sari) + +#define fGEN_TCG_V6_vavghrnd(SHORTCODE) \ + fGEN_TCG_VEC_AVGRND(MO_16, tcg_gen_gvec_sari) + +#define fGEN_TCG_V6_vavgwrnd(SHORTCODE) \ + fGEN_TCG_VEC_AVGRND(MO_32, tcg_gen_gvec_sari) + /* Vector shift right - various forms */ #define fGEN_TCG_V6_vasrh(SHORTCODE) \ do { \
diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c index 3f31037..2a98b13 100644 --- a/target/hexagon/genptr.c +++ b/target/hexagon/genptr.c
@@ -37,6 +37,76 @@ #include "genptr.h" +static void gen_sabsdiff_i32(TCGv_i32 d, TCGv_i32 a, TCGv_i32 b) +{ + TCGv_i32 t = tcg_temp_new_i32(); + + tcg_gen_sub_i32(t, a, b); + tcg_gen_sub_i32(d, b, a); + tcg_gen_movcond_i32(TCG_COND_LT, d, a, b, d, t); +} + +static void gen_sabsdiff_vec(unsigned vece, TCGv_vec d, TCGv_vec a, TCGv_vec b) +{ + TCGv_vec t = tcg_temp_new_vec_matching(d); + + tcg_gen_smin_vec(vece, t, a, b); + tcg_gen_smax_vec(vece, d, a, b); + tcg_gen_sub_vec(vece, d, d, t); +} + +void gen_gvec_sabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs, + uint32_t bofs, uint32_t oprsz, uint32_t maxsz) +{ + static const TCGOpcode vecop_list[] = { + INDEX_op_sub_vec, INDEX_op_smin_vec, INDEX_op_smax_vec, 0 + }; + static const GVecGen3 ops[4] = { + [MO_16] = { .fniv = gen_sabsdiff_vec, + .fno = gen_helper_gvec_sabsdiff_h, + .opt_opc = vecop_list, + .vece = MO_16 }, + [MO_32] = { .fni4 = gen_sabsdiff_i32, + .fniv = gen_sabsdiff_vec, + .fno = gen_helper_gvec_sabsdiff_w, + .opt_opc = vecop_list, + .vece = MO_32 }, + }; + + tcg_debug_assert(vece == MO_16 || vece == MO_32); + tcg_gen_gvec_3(dofs, aofs, bofs, oprsz, maxsz, &ops[vece]); +} + +static void gen_uabsdiff_vec(unsigned vece, TCGv_vec d, TCGv_vec a, TCGv_vec b) +{ + TCGv_vec t = tcg_temp_new_vec_matching(d); + + tcg_gen_umin_vec(vece, t, a, b); + tcg_gen_umax_vec(vece, d, a, b); + tcg_gen_sub_vec(vece, d, d, t); +} + +void gen_gvec_uabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs, + uint32_t bofs, uint32_t oprsz, uint32_t maxsz) +{ + static const TCGOpcode vecop_list[] = { + INDEX_op_sub_vec, INDEX_op_umin_vec, INDEX_op_umax_vec, 0 + }; + static const GVecGen3 ops[4] = { + [MO_8] = { .fniv = gen_uabsdiff_vec, + .fno = gen_helper_gvec_uabsdiff_b, + .opt_opc = vecop_list, + .vece = MO_8 }, + [MO_16] = { .fniv = gen_uabsdiff_vec, + .fno = gen_helper_gvec_uabsdiff_h, + .opt_opc = vecop_list, + .vece = MO_16 }, + }; + + tcg_debug_assert(vece == MO_8 || vece == MO_16); + tcg_gen_gvec_3(dofs, aofs, bofs, oprsz, maxsz, &ops[vece]); +} + TCGv gen_read_reg(TCGv result, int num) { tcg_gen_mov_tl(result, hex_gpr[num]); @@ -91,6 +161,17 @@ } } +TCGv gen_unalias_gpr_src(TCGv src, TCGv dst) +{ + if (src != dst) { + return src; + } + + TCGv tmp = tcg_temp_new(); + tcg_gen_mov_tl(tmp, src); + return tmp; +} + static TCGv_i64 get_result_gpr_pair(DisasContext *ctx, int rnum) { TCGv_i64 result = tcg_temp_new_i64();
diff --git a/target/hexagon/genptr.h b/target/hexagon/genptr.h index 45ee038..f498398 100644 --- a/target/hexagon/genptr.h +++ b/target/hexagon/genptr.h
@@ -36,6 +36,7 @@ TCGv gen_read_reg(TCGv result, int num); TCGv gen_read_preg(TCGv pred, uint8_t num); TCGv get_result_gpr(DisasContext *ctx, int rnum); +TCGv gen_unalias_gpr_src(TCGv src, TCGv dst); TCGv get_result_pred(DisasContext *ctx, int pnum); void gen_pred_write(DisasContext *ctx, int pnum, TCGv val); void gen_set_usr_field(DisasContext *ctx, int field, TCGv val); @@ -58,6 +59,10 @@ void gen_set_half(int N, TCGv result, TCGv src); void gen_set_half_i64(int N, TCGv_i64 result, TCGv src); void probe_noshuf_load(TCGv va, int s, int mi); +void gen_gvec_sabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs, + uint32_t bofs, uint32_t oprsz, uint32_t maxsz); +void gen_gvec_uabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs, + uint32_t bofs, uint32_t oprsz, uint32_t maxsz); extern const target_ulong reg_immut_masks[TOTAL_PER_THREAD_REGS];
diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index 033e561..78dc28c 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h
@@ -108,6 +108,11 @@ DEF_HELPER_2(probe_hvx_stores, void, env, int) DEF_HELPER_2(probe_pkt_scalar_hvx_stores, void, env, int) +DEF_HELPER_FLAGS_4(gvec_sabsdiff_h, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(gvec_sabsdiff_w, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(gvec_uabsdiff_b, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(gvec_uabsdiff_h, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) + #if !defined(CONFIG_USER_ONLY) DEF_HELPER_3(raise_stack_overflow, void, env, i32, i32) DEF_HELPER_2(swi, void, env, i32)
diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index d91a653..e33d43e 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py
@@ -253,6 +253,8 @@ def need_env(tag): "A_LOAD" in attribdict[tag] or "A_CVI_GATHER" in attribdict[tag] or "A_CVI_SCATTER" in attribdict[tag] or + "A_HVX_IEEE_FP" in attribdict[tag] or + "A_HVX_FLT" in attribdict[tag] or "A_IMPLICIT_WRITES_USR" in attribdict[tag] or "A_PRIV" in attribdict[tag] or "J2_trap" in tag) @@ -388,6 +390,8 @@ def decl_reg_num(self, f, regno): """)) def idef_arg(self, declared): declared.append(self.reg_tcg()) + def may_alias_gpr(self): + return False def helper_arg(self): return HelperArg( self.helper_proto_type(), @@ -495,6 +499,8 @@ def is_new(self): return False class GprDest(Register, Single, Dest): + def may_alias_gpr(self): + return True def decl_tcg(self, f, tag, regno): self.decl_reg_num(f, regno) f.write(code_fmt(f"""\ @@ -510,6 +516,8 @@ def analyze_write(self, f, tag, regno): """)) class GprSource(Register, Single, OldSource): + def may_alias_gpr(self): + return True def decl_tcg(self, f, tag, regno): self.decl_reg_num(f, regno) f.write(code_fmt(f"""\ @@ -531,6 +539,8 @@ def analyze_read(self, f, regno): """)) class GprReadWrite(Register, Single, ReadWrite): + def may_alias_gpr(self): + return True def decl_tcg(self, f, tag, regno): self.decl_reg_num(f, regno) f.write(code_fmt(f"""\ @@ -557,6 +567,8 @@ def analyze_write(self, f, tag, regno): """)) class ControlDest(Register, Single, Dest): + def may_alias_gpr(self): + return True def decl_reg_num(self, f, regno): f.write(code_fmt(f"""\ const int {self.reg_num} = insn->regno[{regno}] + HEX_REG_SA0; @@ -593,6 +605,8 @@ def analyze_read(self, f, regno): """)) class ModifierSource(Register, Single, OldSource): + def may_alias_gpr(self): + return True def decl_reg_num(self, f, regno): f.write(code_fmt(f"""\ const int {self.reg_num} = insn->regno[{regno}] + HEX_REG_M0; @@ -772,6 +786,11 @@ def decl_tcg(self, f, tag, regno): TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr(); tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()}); """)) + def gen_zero(self, f): + f.write(code_fmt(f"""\ + tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()}, + sizeof(MMVector), sizeof(MMVector), 0); + """)) def gen_write(self, f, tag): pass def helper_hvx_desc(self, f): @@ -838,6 +857,11 @@ def decl_tcg(self, f, tag, regno): TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr(); tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()}); """)) + def gen_zero(self, f): + f.write(code_fmt(f"""\ + tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()}, + sizeof(MMVector), sizeof(MMVector), 0); + """)) def gen_write(self, f, tag): pass def helper_hvx_desc(self, f): @@ -870,6 +894,11 @@ def decl_tcg(self, f, tag, regno): vreg_src_off(ctx, {self.reg_num}), sizeof(MMVector), sizeof(MMVector)); """)) + def gen_zero(self, f): + f.write(code_fmt(f"""\ + tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()}, + sizeof(MMVector), sizeof(MMVector), 0); + """)) def gen_write(self, f, tag): f.write(code_fmt(f"""\ gen_vreg_write(ctx, {self.hvx_off()}, {self.reg_num}, @@ -903,6 +932,11 @@ def decl_tcg(self, f, tag, regno): TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr(); tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()}); """)) + def gen_zero(self, f): + f.write(code_fmt(f"""\ + tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()}, + sizeof(MMVectorPair), sizeof(MMVectorPair), 0); + """)) def gen_write(self, f, tag): pass def helper_hvx_desc(self, f): @@ -962,6 +996,11 @@ def decl_tcg(self, f, tag, regno): TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr(); tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()}); """)) + def gen_zero(self, f): + f.write(code_fmt(f"""\ + tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()}, + sizeof(MMVectorPair), sizeof(MMVectorPair), 0); + """)) def gen_write(self, f, tag): f.write(code_fmt(f"""\ gen_vreg_write_pair(ctx, {self.hvx_off()}, {self.reg_num},
diff --git a/target/hexagon/imported/mmvec/encode_ext.def b/target/hexagon/imported/mmvec/encode_ext.def index 402438f..16f043b 100644 --- a/target/hexagon/imported/mmvec/encode_ext.def +++ b/target/hexagon/imported/mmvec/encode_ext.def
@@ -647,36 +647,36 @@ DEF_ENC(V6_vmpyewuh_64, ICLASS_CJ" 1 110 101 vvvvv PP 0 uuuuu 110 ddddd") DEF_FIELDROW_DESC32( ICLASS_CJ" 1 110 --0 ----- PP 1 ----- ----- ---","Vx32=Vu32") -DEF_ENC(V6_vunpackob, ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 000 xxxxx") // -DEF_ENC(V6_vunpackoh, ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 001 xxxxx") // +DEF_ENC(V6_vunpackob, ICLASS_CJ" 1 110 --0 --000 PP 1 uuuuu 000 xxxxx") // +DEF_ENC(V6_vunpackoh, ICLASS_CJ" 1 110 --0 --000 PP 1 uuuuu 001 xxxxx") // //DEF_ENC(V6_vunpackow, ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 010 xxxxx") // -DEF_ENC(V6_vhist, ICLASS_CJ" 1 110 --0 ---00 PP 1 -000- 100 -----") -DEF_ENC(V6_vwhist256, ICLASS_CJ" 1 110 --0 ---00 PP 1 -0010 100 -----") -DEF_ENC(V6_vwhist256_sat, ICLASS_CJ" 1 110 --0 ---00 PP 1 -0011 100 -----") -DEF_ENC(V6_vwhist128, ICLASS_CJ" 1 110 --0 ---00 PP 1 -010- 100 -----") -DEF_ENC(V6_vwhist128m, ICLASS_CJ" 1 110 --0 ---00 PP 1 -011i 100 -----") +DEF_ENC(V6_vhist, ICLASS_CJ" 1 110 --0 --000 PP 1 -000- 100 -----") +DEF_ENC(V6_vwhist256, ICLASS_CJ" 1 110 --0 --000 PP 1 -0010 100 -----") +DEF_ENC(V6_vwhist256_sat, ICLASS_CJ" 1 110 --0 --000 PP 1 -0011 100 -----") +DEF_ENC(V6_vwhist128, ICLASS_CJ" 1 110 --0 --000 PP 1 -010- 100 -----") +DEF_ENC(V6_vwhist128m, ICLASS_CJ" 1 110 --0 --000 PP 1 -011i 100 -----") DEF_FIELDROW_DESC32( ICLASS_CJ" 1 110 --0 ----- PP 1 ----- ----- ---","if (Qv4) Vx32=Vu32") -DEF_ENC(V6_vaddbq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 000 xxxxx") // -DEF_ENC(V6_vaddhq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 001 xxxxx") // -DEF_ENC(V6_vaddwq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 010 xxxxx") // -DEF_ENC(V6_vaddbnq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 011 xxxxx") // -DEF_ENC(V6_vaddhnq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 100 xxxxx") // -DEF_ENC(V6_vaddwnq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 101 xxxxx") // -DEF_ENC(V6_vsubbq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 110 xxxxx") // -DEF_ENC(V6_vsubhq, ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 111 xxxxx") // +DEF_ENC(V6_vaddbq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 000 xxxxx") // +DEF_ENC(V6_vaddhq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 001 xxxxx") // +DEF_ENC(V6_vaddwq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 010 xxxxx") // +DEF_ENC(V6_vaddbnq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 011 xxxxx") // +DEF_ENC(V6_vaddhnq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 100 xxxxx") // +DEF_ENC(V6_vaddwnq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 101 xxxxx") // +DEF_ENC(V6_vsubbq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 110 xxxxx") // +DEF_ENC(V6_vsubhq, ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 111 xxxxx") // -DEF_ENC(V6_vsubwq, ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 000 xxxxx") // -DEF_ENC(V6_vsubbnq, ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 001 xxxxx") // -DEF_ENC(V6_vsubhnq, ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 010 xxxxx") // -DEF_ENC(V6_vsubwnq, ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 011 xxxxx") // +DEF_ENC(V6_vsubwq, ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 000 xxxxx") // +DEF_ENC(V6_vsubbnq, ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 001 xxxxx") // +DEF_ENC(V6_vsubhnq, ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 010 xxxxx") // +DEF_ENC(V6_vsubwnq, ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 011 xxxxx") // -DEF_ENC(V6_vhistq, ICLASS_CJ" 1 110 vv0 ---10 PP 1 --00- 100 -----") -DEF_ENC(V6_vwhist256q, ICLASS_CJ" 1 110 vv0 ---10 PP 1 --010 100 -----") -DEF_ENC(V6_vwhist256q_sat, ICLASS_CJ" 1 110 vv0 ---10 PP 1 --011 100 -----") -DEF_ENC(V6_vwhist128q, ICLASS_CJ" 1 110 vv0 ---10 PP 1 --10- 100 -----") -DEF_ENC(V6_vwhist128qm, ICLASS_CJ" 1 110 vv0 ---10 PP 1 --11i 100 -----") +DEF_ENC(V6_vhistq, ICLASS_CJ" 1 110 vv0 --010 PP 1 --00- 100 -----") +DEF_ENC(V6_vwhist256q, ICLASS_CJ" 1 110 vv0 --010 PP 1 --010 100 -----") +DEF_ENC(V6_vwhist256q_sat, ICLASS_CJ" 1 110 vv0 --010 PP 1 --011 100 -----") +DEF_ENC(V6_vwhist128q, ICLASS_CJ" 1 110 vv0 --010 PP 1 --10- 100 -----") +DEF_ENC(V6_vwhist128qm, ICLASS_CJ" 1 110 vv0 --010 PP 1 --11i 100 -----") DEF_ENC(V6_vandvqv, ICLASS_CJ" 1 110 vv0 ---11 PP 1 uuuuu 000 ddddd") @@ -804,5 +804,83 @@ DEF_ENC(V6_vmpyowh, ICLASS_CJ" 1 111 111 vvvvv PP 0 uuuuu 111 ddddd") DEF_ENC(V6_vmpyuhvs,"00011111110vvvvvPP1uuuuu111ddddd") +/* IEEE FP multiply instructions */ +DEF_ENC(V6_vmpy_sf_sf,"00011111100vvvvvPP1uuuuu001ddddd") +DEF_ENC(V6_vmpy_sf_hf,"00011111100vvvvvPP1uuuuu010ddddd") +DEF_ENC(V6_vmpy_hf_hf,"00011111100vvvvvPP1uuuuu011ddddd") +DEF_ENC(V6_vdmpy_sf_hf,"00011111101vvvvvPP1uuuuu110ddddd") + +/* IEEE FP multiply-accumulate instructions */ +DEF_ENC(V6_vmpy_sf_hf_acc,"00011100010vvvvvPP1uuuuu001xxxxx") +DEF_ENC(V6_vmpy_hf_hf_acc,"00011100010vvvvvPP1uuuuu010xxxxx") +DEF_ENC(V6_vdmpy_sf_hf_acc,"00011100010vvvvvPP1uuuuu011xxxxx") + +/* IEEE FP add/sub instructions */ +DEF_ENC(V6_vadd_sf_sf,"00011111100vvvvvPP1uuuuu110ddddd") +DEF_ENC(V6_vsub_sf_sf,"00011111100vvvvvPP1uuuuu111ddddd") +DEF_ENC(V6_vadd_sf_hf,"00011111100vvvvvPP1uuuuu100ddddd") +DEF_ENC(V6_vsub_sf_hf,"00011111100vvvvvPP1uuuuu101ddddd") +DEF_ENC(V6_vadd_hf_hf,"00011111101vvvvvPP1uuuuu111ddddd") +DEF_ENC(V6_vsub_hf_hf,"00011111011vvvvvPP1uuuuu000ddddd") + +/* IEEE FP min/max instructions */ +DEF_ENC(V6_vfmin_hf,"00011100011vvvvvPP1uuuuu000ddddd") +DEF_ENC(V6_vfmin_sf,"00011100011vvvvvPP1uuuuu001ddddd") +DEF_ENC(V6_vfmax_hf,"00011100011vvvvvPP1uuuuu010ddddd") +DEF_ENC(V6_vfmax_sf,"00011100011vvvvvPP1uuuuu011ddddd") +DEF_ENC(V6_vmax_sf,"00011111110vvvvvPP1uuuuu001ddddd") +DEF_ENC(V6_vmin_sf,"00011111110vvvvvPP1uuuuu010ddddd") +DEF_ENC(V6_vmax_hf,"00011111110vvvvvPP1uuuuu011ddddd") +DEF_ENC(V6_vmin_hf,"00011111110vvvvvPP1uuuuu100ddddd") + +/* IEEE FP move, negate, abs instructions */ +DEF_ENC(V6_vassign_fp,"00011110--0-0110PP1uuuuu001ddddd") +DEF_ENC(V6_vfneg_hf,"00011110--0-0110PP1uuuuu010ddddd") +DEF_ENC(V6_vfneg_sf,"00011110--0-0110PP1uuuuu011ddddd") +DEF_ENC(V6_vabs_hf,"00011110--0-0110PP1uuuuu100ddddd") +DEF_ENC(V6_vabs_sf,"00011110--0-0110PP1uuuuu101ddddd") + +/* IEEE FP vcvt instructions */ +DEF_ENC(V6_vcvt_sf_hf,"00011110--0-0100PP1uuuuu100ddddd") +DEF_ENC(V6_vcvt_hf_sf,"00011111011vvvvvPP1uuuuu001ddddd") +DEF_ENC(V6_vcvt_hf_ub,"00011110--0-0100PP1uuuuu001ddddd") +DEF_ENC(V6_vcvt_hf_b,"00011110--0-0100PP1uuuuu010ddddd") +DEF_ENC(V6_vcvt_hf_uh,"00011110--0-0100PP1uuuuu101ddddd") +DEF_ENC(V6_vcvt_hf_h,"00011110--0-0100PP1uuuuu111ddddd") +DEF_ENC(V6_vcvt_uh_hf,"00011110--0--101PP1uuuuu000ddddd") +DEF_ENC(V6_vcvt_h_hf,"00011110--0-0110PP1uuuuu000ddddd") +DEF_ENC(V6_vcvt_ub_hf,"00011111110vvvvvPP1uuuuu101ddddd") +DEF_ENC(V6_vcvt_b_hf,"00011111110vvvvvPP1uuuuu110ddddd") + +/* IEEE FP vconv instructions */ +DEF_ENC(V6_vconv_sf_w,"00011110--0--101PP1uuuuu011ddddd") +DEF_ENC(V6_vconv_w_sf,"00011110--0--101PP1uuuuu001ddddd") +DEF_ENC(V6_vconv_hf_h,"00011110--0--101PP1uuuuu100ddddd") +DEF_ENC(V6_vconv_h_hf,"00011110--0--101PP1uuuuu010ddddd") + +/* IEEE FP compare instructions */ +DEF_ENC(V6_vgtsf,"00011100100vvvvvPP1uuuuu011100dd") +DEF_ENC(V6_vgthf,"00011100100vvvvvPP1uuuuu011101dd") +DEF_ENC(V6_vgtsf_and,"00011100100vvvvvPP1uuuuu110010xx") +DEF_ENC(V6_vgthf_and,"00011100100vvvvvPP1uuuuu110011xx") +DEF_ENC(V6_vgtsf_or,"00011100100vvvvvPP1uuuuu001100xx") +DEF_ENC(V6_vgthf_or,"00011100100vvvvvPP1uuuuu001101xx") +DEF_ENC(V6_vgtsf_xor,"00011100100vvvvvPP1uuuuu111010xx") +DEF_ENC(V6_vgthf_xor,"00011100100vvvvvPP1uuuuu111011xx") + +/* BFLOAT instructions */ +DEF_ENC(V6_vmpy_sf_bf,"00011101010vvvvvPP1uuuuu100ddddd") +DEF_ENC(V6_vmpy_sf_bf_acc,"00011101000vvvvvPP1uuuuu000xxxxx") +DEF_ENC(V6_vadd_sf_bf,"00011101010vvvvvPP1uuuuu110ddddd") +DEF_ENC(V6_vsub_sf_bf,"00011101010vvvvvPP1uuuuu101ddddd") +DEF_ENC(V6_vmax_bf,"00011101010vvvvvPP1uuuuu111ddddd") +DEF_ENC(V6_vmin_bf,"00011101010vvvvvPP1uuuuu000ddddd") +DEF_ENC(V6_vcvt_bf_sf,"00011101010vvvvvPP1uuuuu011ddddd") + +/* BFLOAT compare instructions */ +DEF_ENC(V6_vgtbf,"00011100100vvvvvPP1uuuuu011110dd") +DEF_ENC(V6_vgtbf_and,"00011100100vvvvvPP1uuuuu110100xx") +DEF_ENC(V6_vgtbf_or,"00011100100vvvvvPP1uuuuu001110xx") +DEF_ENC(V6_vgtbf_xor,"00011100100vvvvvPP1uuuuu111100xx") #endif /* NO MMVEC */
diff --git a/target/hexagon/imported/mmvec/ext.idef b/target/hexagon/imported/mmvec/ext.idef index 03d31f6..857aa61 100644 --- a/target/hexagon/imported/mmvec/ext.idef +++ b/target/hexagon/imported/mmvec/ext.idef
@@ -43,7 +43,9 @@ EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA), \ DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) - +#define ITERATOR_INSN_ANY_SLOT_2SRC(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) #define ITERATOR_INSN2_ANY_SLOT(WIDTH,TAG,SYNTAX,SYNTAX2,DESCR,CODE) \ ITERATOR_INSN_ANY_SLOT(WIDTH,TAG,SYNTAX2,DESCR,CODE) @@ -61,6 +63,9 @@ EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS), \ DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) +#define ITERATOR_INSN_SHIFT_SLOT_FLT(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS,A_HVX_FLT), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) #define ITERATOR_INSN_SHIFT3_SLOT(WIDTH,TAG,SYNTAX,DESCR,CODE) \ EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS,A_CVI_VS_3SRC,A_NOTE_SHIFT_RESOURCE,A_NOTE_NOVP,A_NOTE_VA_UNARY), \ @@ -2895,9 +2900,371 @@ } } ) +/* KVX - IEEE FP Instructions */ +/* Single pipe, 32-bit output */ +#define ITERATOR_INSN_IEEE_FP_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ +ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_32), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) +/* Single pipe, 16-bit output */ +#define ITERATOR_INSN_IEEE_FP_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ +ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) +/* Two pipes: P2 & P3, single output: P2, 32-bit output */ +#define ITERATOR_INSN_IEEE_FP_DOUBLE_SINGLE_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ +ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_32), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* Two pipes: P2 & P3, two outputs, 32-bit output */ +#define ITERATOR_INSN_IEEE_FP_DOUBLE_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ +ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_32), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* + * single pipe, accumulate instruction, produces 16-bit output, requires 16-bit + * accumulate input + */ +#define ITERATOR_INSN_IEEE_FP_ACC_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ +ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_ACC,A_HVX_IEEE_FP_OUT_16,A_CVI_VX_NO_TMP_LD), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* + * single pipe, accumulate instruction, produces 32-bit output, requires 32-bit + * accumulate input + */ +#define ITERATOR_INSN_IEEE_FP_ACC_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ +ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_ACC,A_HVX_IEEE_FP_OUT_32,A_CVI_VX_NO_TMP_LD), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* IEEE FP multiply instructions */ +ITERATOR_INSN_IEEE_FP_DOUBLE_SINGLE_32(32, vmpy_sf_sf, + "Vd32.sf=vmpy(Vu32.sf,Vv32.sf)", "Vector IEEE mul: sf", + VdV.sf[i] = float32_mul(VuV.sf[i], VvV.sf[i], &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_hf, + "Vdd32.sf=vmpy(Vu32.hf,Vv32.hf)", "Vector IEEE mul: hf widen to sf", + VddV.v[0].sf[i] = fp_mult_sf_hf(VuV.hf[2*i], VvV.hf[2*i], &env->hvx_fp_status); + VddV.v[1].sf[i] = fp_mult_sf_hf(VuV.hf[2*i+1], VvV.hf[2*i+1], &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_16(16, vmpy_hf_hf, "Vd32.hf=vmpy(Vu32.hf,Vv32.hf)", + "Vector IEEE mul: hf", + VdV.hf[i] = float16_mul(VuV.hf[i], VvV.hf[i], &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_32(32, vdmpy_sf_hf, "Vd32.sf=vdmpy(Vu32.hf,Vv32.hf)", + "Vector IEEE mul reduction: hf widen to sf", + VdV.sf[i] = fp_vdmpy(VuV.hf[2*i+1], VuV.hf[2*i], VvV.hf[2*i+1], + VvV.hf[2*i], &env->hvx_fp_status)) + +/* IEEE FP multiply-accumulate instructions */ +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_hf_acc, + "Vxx32.sf+=vmpy(Vu32.hf,Vv32.hf)", "Vector IEEE fma: hf widen to sf", + VxxV.v[0].sf[i] = float32_muladd(f16_to_f32(VuV.hf[2*i]), + f16_to_f32(VvV.hf[2*i]), + VxxV.v[0].sf[i], 0, &env->hvx_fp_status); + VxxV.v[1].sf[i] = float32_muladd(f16_to_f32(VuV.hf[2*i+1]), + f16_to_f32(VvV.hf[2*i+1]), + VxxV.v[1].sf[i], 0, &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_ACC_16(16, vmpy_hf_hf_acc, + "Vx32.hf+=vmpy(Vu32.hf,Vv32.hf)", "Vector IEEE fma: hf", + VxV.hf[i] = float16_muladd(VuV.hf[i], VvV.hf[i], VxV.hf[i], 0, &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_ACC_32(32, vdmpy_sf_hf_acc, + "Vx32.sf+=vdmpy(Vu32.hf,Vv32.hf)", "Vector IEEE fma reduce: hf widen to sf", + VxV.sf[i] = float32_add(fp_vdmpy(VuV.hf[2*i+1], VuV.hf[2*i], + VvV.hf[2*i+1], VvV.hf[2*i], + &env->hvx_fp_status), + VxV.sf[i], &env->hvx_fp_status)) + +/* IEEE FP add/sub instructions */ +ITERATOR_INSN_IEEE_FP_32(32, vadd_sf_sf, "Vd32.sf=vadd(Vu32.sf,Vv32.sf)", + "Vector IEEE add: sf", + VdV.sf[i] = float32_add(VuV.sf[i], VvV.sf[i], &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_32(32, vsub_sf_sf, "Vd32.sf=vsub(Vu32.sf,Vv32.sf)", + "Vector IEEE sub: sf", + VdV.sf[i] = float32_sub(VuV.sf[i], VvV.sf[i], &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_16(16, vadd_hf_hf, "Vd32.hf=vadd(Vu32.hf,Vv32.hf)", + "Vector IEEE add: hf", + VdV.hf[i] = float16_add(VuV.hf[i], VvV.hf[i], &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_16(16, vsub_hf_hf, "Vd32.hf=vsub(Vu32.hf,Vv32.hf)", + "Vector IEEE sub: hf", + VdV.hf[i] = float16_sub(VuV.hf[i], VvV.hf[i], &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vadd_sf_hf, + "Vdd32.sf=vadd(Vu32.hf,Vv32.hf)", "Vector IEEE add: hf widen to sf", + VddV.v[0].sf[i] = float32_add(f16_to_f32(VuV.hf[2*i]), + f16_to_f32(VvV.hf[2*i]), &env->hvx_fp_status); + VddV.v[1].sf[i] = float32_add(f16_to_f32(VuV.hf[2*i+1]), + f16_to_f32(VvV.hf[2*i+1]), &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vsub_sf_hf, + "Vdd32.sf=vsub(Vu32.hf,Vv32.hf)", "Vector IEEE sub: hf widen to sf", + VddV.v[0].sf[i] = float32_sub(f16_to_f32(VuV.hf[2*i]), + f16_to_f32(VvV.hf[2*i]), &env->hvx_fp_status); + VddV.v[1].sf[i] = float32_sub(f16_to_f32(VuV.hf[2*i+1]), + f16_to_f32(VvV.hf[2*i+1]), &env->hvx_fp_status)) + +#define ITERATOR_INSN_IEEE_FP_16_32_LATE(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ + ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16,A_HVX_IEEE_FP_OUT_32), \ + DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* IEEE FP min/max instructions */ +ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vfmin_hf, "Vd32.hf=vfmin(Vu32.hf,Vv32.hf)", \ + "Vector IEEE min: hf", VdV.hf[i] = float16_min(VuV.hf[i], VvV.hf[i], \ + &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vfmin_sf, "Vd32.sf=vfmin(Vu32.sf,Vv32.sf)", \ + "Vector IEEE min: sf", VdV.sf[i] = float32_min(VuV.sf[i], VvV.sf[i], \ + &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vfmax_hf, "Vd32.hf=vfmax(Vu32.hf,Vv32.hf)", \ + "Vector IEEE max: hf", VdV.hf[i] = float16_max(VuV.hf[i], VvV.hf[i], \ + &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vfmax_sf, "Vd32.sf=vfmax(Vu32.sf,Vv32.sf)", \ + "Vector IEEE max: sf", VdV.sf[i] = float32_max(VuV.sf[i], VvV.sf[i], \ + &env->hvx_fp_status)) + +ITERATOR_INSN_ANY_SLOT_2SRC(32,vmax_sf,"Vd32.sf=vmax(Vu32.sf,Vv32.sf)", \ + "Vector max of sf input", VdV.sf[i] = qf_max_sf(VuV.sf[i], VvV.sf[i], \ + &env->hvx_fp_status)) +ITERATOR_INSN_ANY_SLOT_2SRC(32,vmin_sf,"Vd32.sf=vmin(Vu32.sf,Vv32.sf)", \ + "Vector min of sf input", VdV.sf[i] = qf_min_sf(VuV.sf[i], VvV.sf[i], \ + &env->hvx_fp_status)) +ITERATOR_INSN_ANY_SLOT_2SRC(16,vmax_hf,"Vd32.hf=vmax(Vu32.hf,Vv32.hf)", \ + "Vector max of hf input", VdV.hf[i] = qf_max_hf(VuV.hf[i], VvV.hf[i], \ + &env->hvx_fp_status)) +ITERATOR_INSN_ANY_SLOT_2SRC(16,vmin_hf,"Vd32.hf=vmin(Vu32.hf,Vv32.hf)", \ + "Vector min of hf input", VdV.hf[i] = qf_min_hf(VuV.hf[i], VvV.hf[i], \ + &env->hvx_fp_status)) + +/* IEEE FP move, negate, abs instructions */ +ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vassign_fp, "Vd32.w=vfmv(Vu32.w)", \ + "Vector IEEE move", VdV.w[i] = VuV.w[i]) +ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vfneg_hf, "Vd32.hf=vfneg(Vu32.hf)", \ + "Vector IEEE neg: hf", VdV.hf[i] = float16_chs(VuV.hf[i])) +ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vfneg_sf, "Vd32.sf=vfneg(Vu32.sf)", \ + "Vector IEEE neg: sf", VdV.sf[i] = float32_chs(VuV.sf[i])) +ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vabs_hf, "Vd32.hf=vabs(Vu32.hf)", \ + "Vector IEEE abs: hf", VdV.hf[i] = float16_abs(VuV.hf[i])) +ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vabs_sf, "Vd32.sf=vabs(Vu32.sf)", \ + "Vector IEEE abs: sf", VdV.sf[i] = float32_abs(VuV.sf[i])) + +/* Two pipes: P2 & P3, two outputs, 16-bit */ +#define ITERATOR_INSN_IEEE_FP_DOUBLE_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ +ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_16), \ +DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* Two pipes: P2 & P3, two outputs, 32-bit output */ +#define ITERATOR_INSN_IEEE_FP_DOUBLE_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ + ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_32), \ + DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* Single pipe, 16-bit output */ +#define ITERATOR_INSN_IEEE_FP_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ + ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16), \ + DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/* single pipe, output can feed 16- or 32-bit accumulate */ +#define ITERATOR_INSN_IEEE_FP_16_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \ +EXTINSN(V6_##TAG, SYNTAX, \ + ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16,A_HVX_IEEE_FP_OUT_32), \ + DESCR, DO_FOR_EACH_CODE(WIDTH, CODE)) + +/****************************************************************************** + * IEEE FP convert instructions + ******************************************************************************/ + +ITERATOR_INSN_IEEE_FP_DOUBLE_16(32, vcvt_hf_ub, "Vdd32.hf=vcvt(Vu32.ub)", + "Vector IEEE cvt from int: ub widen to hf", + VddV.v[0].hf[2*i] = uint64_to_float16_scalbn(VuV.ub[4*i], float_round_nearest_even, 0); + VddV.v[0].hf[2*i+1] = uint64_to_float16_scalbn(VuV.ub[4*i+1], float_round_nearest_even, 0); + VddV.v[1].hf[2*i] = uint64_to_float16_scalbn(VuV.ub[4*i+2], float_round_nearest_even, 0); + VddV.v[1].hf[2*i+1] = uint64_to_float16_scalbn(VuV.ub[4*i+3], float_round_nearest_even, 0)) + +ITERATOR_INSN_IEEE_FP_DOUBLE_16(32, vcvt_hf_b, "Vdd32.hf=vcvt(Vu32.b)", + "Vector IEEE cvt from int: b widen to hf", + VddV.v[0].hf[2*i] = int64_to_float16_scalbn(VuV.b[4*i], float_round_nearest_even, 0); + VddV.v[0].hf[2*i+1] = int64_to_float16_scalbn(VuV.b[4*i+1], float_round_nearest_even, 0); + VddV.v[1].hf[2*i] = int64_to_float16_scalbn(VuV.b[4*i+2], float_round_nearest_even, 0); + VddV.v[1].hf[2*i+1] = int64_to_float16_scalbn(VuV.b[4*i+3], float_round_nearest_even, 0)) + +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vcvt_sf_hf, "Vdd32.sf=vcvt(Vu32.hf)", + "Vector IEEE cvt: hf widen to sf", + VddV.v[0].sf[i] = f16_to_f32(VuV.hf[2*i]); + VddV.v[1].sf[i] = f16_to_f32(VuV.hf[2*i+1])) + +ITERATOR_INSN_IEEE_FP_16(16, vcvt_hf_uh, "Vd32.hf=vcvt(Vu32.uh)", + "Vector IEEE cvt from int: uh to hf", + VdV.hf[i] = uint64_to_float16_scalbn(VuV.uh[i], float_round_nearest_even, 0)) +ITERATOR_INSN_IEEE_FP_16(16, vcvt_hf_h, "Vd32.hf=vcvt(Vu32.h)", + "Vector IEEE cvt from int: h to hf", + VdV.hf[i] = int64_to_float16_scalbn(VuV.h[i], float_round_nearest_even, 0)) +ITERATOR_INSN_IEEE_FP_16_32(16, vcvt_uh_hf, "Vd32.uh=vcvt(Vu32.hf)", + "Vector IEEE cvt to int: hf to uh", + VdV.uh[i] = float16_to_uint16_scalbn(VuV.hf[i], float_round_nearest_even, 0, &env->hvx_fp_status)) +ITERATOR_INSN_IEEE_FP_16_32(16, vcvt_h_hf, "Vd32.h=vcvt(Vu32.hf)", + "Vector IEEE cvt to int: hf to h", + VdV.h[i] = float16_to_int16_scalbn(VuV.hf[i], float_round_nearest_even, 0, &env->hvx_fp_status)) + +ITERATOR_INSN_IEEE_FP_16(32, vcvt_hf_sf, "Vd32.hf=vcvt(Vu32.sf,Vv32.sf)", + "Vector IEEE cvt: sf to hf", + VdV.hf[2*i] = f32_to_f16(VuV.sf[i]); + VdV.hf[2*i+1] = f32_to_f16(VvV.sf[i])) + +ITERATOR_INSN_IEEE_FP_16_32(32, vcvt_ub_hf, "Vd32.ub=vcvt(Vu32.hf,Vv32.hf)", "Vector cvt to int: hf narrow to ub", + VdV.ub[4*i] = float16_to_uint8_scalbn(VuV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status); + VdV.ub[4*i+1] = float16_to_uint8_scalbn(VuV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status); + VdV.ub[4*i+2] = float16_to_uint8_scalbn(VvV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status); + VdV.ub[4*i+3] = float16_to_uint8_scalbn(VvV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status)) + +ITERATOR_INSN_IEEE_FP_16_32(32, vcvt_b_hf, "Vd32.b=vcvt(Vu32.hf,Vv32.hf)", + "Vector cvt to int: hf narrow to b", + VdV.b[4*i] = float16_to_int8_scalbn(VuV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status); + VdV.b[4*i+1] = float16_to_int8_scalbn(VuV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status); + VdV.b[4*i+2] = float16_to_int8_scalbn(VvV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status); + VdV.b[4*i+3] = float16_to_int8_scalbn(VvV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status)) + +ITERATOR_INSN_SHIFT_SLOT_FLT(32, vconv_w_sf,"Vd32.w=Vu32.sf", + "Vector conversion of sf32 format to int w", + VdV.w[i] = conv_w_sf(VuV.sf[i], &env->hvx_fp_status)) + +ITERATOR_INSN_SHIFT_SLOT_FLT(16, vconv_h_hf,"Vd32.h=Vu32.hf", + "Vector conversion of hf16 format to int hw", + VdV.h[i] = conv_h_hf(VuV.hf[i], &env->hvx_fp_status)) + +ITERATOR_INSN_SHIFT_SLOT_FLT(32, vconv_sf_w,"Vd32.sf=Vu32.w", + "Vector conversion of int w format to sf32", + VdV.sf[i] = int32_to_float32(VuV.w[i], &env->hvx_fp_status)) + +ITERATOR_INSN_SHIFT_SLOT_FLT(16, vconv_hf_h,"Vd32.hf=Vu32.h", + "Vector conversion of int hw format to hf16", + VdV.hf[i] = float16_val(int16_to_float16(VuV.h[i], &env->hvx_fp_status))) + +/****************************************************************************** + * IEEE FP compare instructions + ******************************************************************************/ + +#define VCMPGT_SF(DEST, ASRC, ASRCOP, CMP, N, SRC, MASK, WIDTH) \ +{ \ + for (fHIDE(int) i = 0; i < fVBYTES(); i += WIDTH) { \ + fHIDE(int) VAL = fCMPGT_SF(VuV.SRC[i/WIDTH],VvV.SRC[i/WIDTH]) ? MASK : 0; \ + fSETQBITS(DEST,WIDTH,MASK,i,ASRC ASRCOP VAL); \ + } \ +} + +#define VCMPGT_HF(DEST, ASRC, ASRCOP, CMP, N, SRC, MASK, WIDTH) \ +{ \ + for (fHIDE(int) i = 0; i < fVBYTES(); i += WIDTH) { \ + fHIDE(int) VAL = fCMPGT_HF(VuV.SRC[i/WIDTH],VvV.SRC[i/WIDTH]) ? MASK : 0; \ + fSETQBITS(DEST,WIDTH,MASK,i,ASRC ASRCOP VAL); \ + } \ +} + +#define VCMPGT_BF(DEST, ASRC, ASRCOP, CMP, N, SRC, MASK, WIDTH) \ +{ \ + fBFLOAT(); \ + for (fHIDE(int) i = 0; i < fVBYTES(); i += WIDTH) { \ + fHIDE(int) VAL = fCMPGT_BF(VuV.SRC[i/WIDTH],VvV.SRC[i/WIDTH]) ? MASK : 0; \ + fSETQBITS(DEST,WIDTH,MASK,i,ASRC ASRCOP VAL); \ + } \ +} + +/* Vector SF compare */ +#define MMVEC_CMPGT_SF(TYPE,TYPE2,DESCR,N,MASK,WIDTH,SRC) \ + EXTINSN(V6_vgt##TYPE##_and, "Qx4&=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-and", \ + VCMPGT_SF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), &, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE##_xor, "Qx4^=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-xor", \ + VCMPGT_SF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), ^, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE##_or, "Qx4|=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-or", \ + VCMPGT_SF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), |, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE, "Qd4=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than", \ + VCMPGT_SF(QdV, , , ">", N, SRC, MASK, WIDTH)) + +/* Vector HF compare */ +#define MMVEC_CMPGT_HF(TYPE,TYPE2,DESCR,N,MASK,WIDTH,SRC) \ + EXTINSN(V6_vgt##TYPE##_and, "Qx4&=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-and", \ + VCMPGT_HF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), &, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE##_xor, "Qx4^=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-xor", \ + VCMPGT_HF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), ^, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE##_or, "Qx4|=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-or", \ + VCMPGT_HF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), |, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE, "Qd4=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than", \ + VCMPGT_HF(QdV, , , ">", N, SRC, MASK, WIDTH)) + +/* Vector BF compare */ +#define MMVEC_CMPGT_BF(TYPE,TYPE2,DESCR,N,MASK,WIDTH,SRC) \ + EXTINSN(V6_vgt##TYPE##_and, "Qx4&=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")",\ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-and", \ + VCMPGT_BF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), &, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE##_xor, "Qx4^=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-xor", \ + VCMPGT_BF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), ^, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE##_or, "Qx4|=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than with predicate-or", \ + VCMPGT_BF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), |, ">", N, SRC, MASK, WIDTH)) \ + EXTINSN(V6_vgt##TYPE, "Qd4=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \ + ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \ + DESCR" greater than", \ + VCMPGT_BF(QdV, , , ">", N, SRC, MASK, WIDTH)) + +MMVEC_CMPGT_SF(sf,"sf","Vector sf Compare ", fVELEM(32), 0xF, 4, sf) +MMVEC_CMPGT_HF(hf,"hf","Vector hf Compare ", fVELEM(16), 0x3, 2, hf) +MMVEC_CMPGT_BF(bf,"bf","Vector bf Compare ", fVELEM(16), 0x3, 2, bf) + +/****************************************************************************** + BFloat arithmetic and max/min instructions + ******************************************************************************/ + +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vadd_sf_bf, + "Vdd32.sf=vadd(Vu32.bf,Vv32.bf)", "Vector IEEE add: bf widen to sf", + VddV.v[0].sf[i] = fp_add_sf_bf(VuV.bf[2*i], VvV.bf[2*i]); + VddV.v[1].sf[i] = fp_add_sf_bf(VuV.bf[2*i+1], VvV.bf[2*i+1]); fBFLOAT()) +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vsub_sf_bf, + "Vdd32.sf=vsub(Vu32.bf,Vv32.bf)", "Vector IEEE sub: bf widen to sf", + VddV.v[0].sf[i] = fp_sub_sf_bf(VuV.bf[2*i], VvV.bf[2*i]); + VddV.v[1].sf[i] = fp_sub_sf_bf(VuV.bf[2*i+1], VvV.bf[2*i+1]); fBFLOAT()) +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_bf, + "Vdd32.sf=vmpy(Vu32.bf,Vv32.bf)", "Vector IEEE mul: hf widen to sf", + VddV.v[0].sf[i] = fp_mult_sf_bf(VuV.bf[2*i], VvV.bf[2*i]); + VddV.v[1].sf[i] = fp_mult_sf_bf(VuV.bf[2*i+1], VvV.bf[2*i+1]); fBFLOAT()) +ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_bf_acc, + "Vxx32.sf+=vmpy(Vu32.bf,Vv32.bf)", "Vector IEEE fma: hf widen to sf", + VxxV.v[0].sf[i] = fp_mult_sf_bf_acc(VuV.bf[2*i], VvV.bf[2*i], VxxV.v[0].sf[i]); + VxxV.v[1].sf[i] = fp_mult_sf_bf_acc(VuV.bf[2*i+1], VvV.bf[2*i+1], VxxV.v[1].sf[i]); + fCVI_VX_NO_TMP_LD(); fBFLOAT()) +ITERATOR_INSN_IEEE_FP_16(32, vcvt_bf_sf, + "Vd32.bf=vcvt(Vu32.sf,Vv32.sf)", "Vector IEEE cvt: sf to bf", + VdV.bf[2*i] = f32_to_bf16(VuV.sf[i], &env->hvx_fp_status); + VdV.bf[2*i+1] = f32_to_bf16(VvV.sf[i], &env->hvx_fp_status); fBFLOAT()) + +ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vmax_bf, "Vd32.bf=vmax(Vu32.bf,Vv32.bf)", + "Vector IEEE max: bf", VdV.bf[i] = fp_max_bf(VuV.bf[i], VvV.bf[i]); + fBFLOAT()) +ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vmin_bf, "Vd32.bf=vmin(Vu32.bf,Vv32.bf)", + "Vector IEEE min: bf", VdV.bf[i] = fp_min_bf(VuV.bf[i], VvV.bf[i]); + fBFLOAT()) /****************************************************************************** DEBUG Vector/Register Printing
diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build index 59cb09c..4c921ee 100644 --- a/target/hexagon/meson.build +++ b/target/hexagon/meson.build
@@ -252,6 +252,7 @@ 'fma_emu.c', 'mmvec/decode_ext_mmvec.c', 'mmvec/system_ext_mmvec.c', + 'mmvec/hvx_ieee_fp.c', )) hexagon_softmmu_ss.add(files(
diff --git a/target/hexagon/mmvec/hvx_ieee_fp.c b/target/hexagon/mmvec/hvx_ieee_fp.c new file mode 100644 index 0000000..d7751ad --- /dev/null +++ b/target/hexagon/mmvec/hvx_ieee_fp.c
@@ -0,0 +1,137 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hvx_ieee_fp.h" + +float32 fp_mult_sf_hf(float16 a1, float16 a2, float_status *fp_status) +{ + return float32_mul(float16_to_float32(a1, true, fp_status), + float16_to_float32(a2, true, fp_status), fp_status); +} + +float32 fp_vdmpy(float16 a1, float16 a2, float16 a3, float16 a4, + float_status *fp_status) +{ + return float32_add(fp_mult_sf_hf(a1, a3, fp_status), + fp_mult_sf_hf(a2, a4, fp_status), fp_status); +} + +#define float32_is_pos_nan(X) (float32_is_any_nan(X) && !float32_is_neg(X)) +#define float32_is_neg_nan(X) (float32_is_any_nan(X) && float32_is_neg(X)) +#define float16_is_pos_nan(X) (float16_is_any_nan(X) && !float16_is_neg(X)) +#define float16_is_neg_nan(X) (float16_is_any_nan(X) && float16_is_neg(X)) + +/* Qfloat min/max treat +NaN as greater than +INF and -NaN as smaller than -INF */ +float32 qf_max_sf(float32 a1, float32 a2, float_status *fp_status) +{ + if (float32_is_pos_nan(a1) || float32_is_neg_nan(a2)) { + return a1; + } + if (float32_is_pos_nan(a2) || float32_is_neg_nan(a1)) { + return a2; + } + return float32_max(a1, a2, fp_status); +} + +float32 qf_min_sf(float32 a1, float32 a2, float_status *fp_status) +{ + if (float32_is_pos_nan(a1) || float32_is_neg_nan(a2)) { + return a2; + } + if (float32_is_pos_nan(a2) || float32_is_neg_nan(a1)) { + return a1; + } + return float32_min(a1, a2, fp_status); +} + +float16 qf_max_hf(float16 a1, float16 a2, float_status *fp_status) +{ + if (float16_is_pos_nan(a1) || float16_is_neg_nan(a2)) { + return a1; + } + if (float16_is_pos_nan(a2) || float16_is_neg_nan(a1)) { + return a2; + } + return float16_max(a1, a2, fp_status); +} + +float16 qf_min_hf(float16 a1, float16 a2, float_status *fp_status) +{ + if (float16_is_pos_nan(a1) || float16_is_neg_nan(a2)) { + return a2; + } + if (float16_is_pos_nan(a2) || float16_is_neg_nan(a1)) { + return a1; + } + return float16_min(a1, a2, fp_status); +} + +int32_t conv_w_sf(float32 a, float_status *fp_status) +{ + /* float32_to_int32 converts any NaN to MAX, hexagon looks at the sign. */ + if (float32_is_any_nan(a)) { + return float32_is_neg(a) ? INT32_MIN : INT32_MAX; + } + return float32_to_int32_round_to_zero(a, fp_status); +} + +int16_t conv_h_hf(float16 a, float_status *fp_status) +{ + /* float16_to_int16 converts any NaN to MAX, hexagon looks at the sign. */ + if (float16_is_any_nan(a)) { + return float16_is_neg(a) ? INT16_MIN : INT16_MAX; + } + return float16_to_int16_round_to_zero(a, fp_status); +} + +/* + * Returns true if f1 > f2, where at least one of the elements is guaranteed + * to be NaN. + * Up to v73, Hexagon HVX IEEE FP follows this order: + * QNaN > SNaN > +Inf > numbers > -Inf > SNaN_neg > QNaN_neg + */ +static bool float32_nan_compare(float32 f1, float32 f2, float_status *fp_status) +{ + /* opposite signs case */ + if (float32_is_neg(f1) != float32_is_neg(f2)) { + return !float32_is_neg(f1); + } + + /* same sign case */ + bool result = (float32_is_any_nan(f1) && !float32_is_any_nan(f2)) || + (float32_is_quiet_nan(f1, fp_status) && !float32_is_quiet_nan(f2, fp_status)); + return float32_is_neg(f1) ? !result : result; +} + +static bool float16_nan_compare(float16 f1, float16 f2, float_status *fp_status) +{ + /* opposite signs case */ + if (float16_is_neg(f1) != float16_is_neg(f2)) { + return !float16_is_neg(f1); + } + + /* same sign case */ + bool result = (float16_is_any_nan(f1) && !float16_is_any_nan(f2)) || + (float16_is_quiet_nan(f1, fp_status) && !float16_is_quiet_nan(f2, fp_status)); + return float16_is_neg(f1) ? !result : result; +} + +uint32_t cmpgt_sf(float32 a1, float32 a2, float_status *fp_status) +{ + if (float32_is_any_nan(a1) || float32_is_any_nan(a2)) { + return float32_nan_compare(a1, a2, fp_status); + } + return float32_compare(a1, a2, fp_status) == float_relation_greater; +} + +uint16_t cmpgt_hf(float16 a1, float16 a2, float_status *fp_status) +{ + if (float16_is_any_nan(a1) || float16_is_any_nan(a2)) { + return float16_nan_compare(a1, a2, fp_status); + } + return float16_compare(a1, a2, fp_status) == float_relation_greater; +}
diff --git a/target/hexagon/mmvec/hvx_ieee_fp.h b/target/hexagon/mmvec/hvx_ieee_fp.h new file mode 100644 index 0000000..b7e379b --- /dev/null +++ b/target/hexagon/mmvec/hvx_ieee_fp.h
@@ -0,0 +1,69 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_HVX_IEEE_H +#define HEXAGON_HVX_IEEE_H + +#include "fpu/softfloat.h" + +#define FP32_DEF_NAN 0x7FFFFFFF + +#define f16_to_f32(A) float16_to_float32((A), true, &env->hvx_fp_status) +#define f32_to_f16(A) float32_to_float16((A), true, &env->hvx_fp_status) +#define bf16_to_f32(A) bfloat16_to_float32(A, &env->hvx_fp_status) + +float32 fp_mult_sf_hf(float16 a1, float16 a2, float_status *fp_status); +float32 fp_vdmpy(float16 a1, float16 a2, float16 a3, float16 a4, + float_status *fp_status); + +/* Qfloat min/max treat +NaN as greater than +INF and -NaN as smaller than -INF */ +float32 qf_max_sf(float32 a1, float32 a2, float_status *fp_status); +float32 qf_min_sf(float32 a1, float32 a2, float_status *fp_status); +float16 qf_max_hf(float16 a1, float16 a2, float_status *fp_status); +float16 qf_min_hf(float16 a1, float16 a2, float_status *fp_status); + +int32_t conv_w_sf(float32 a, float_status *fp_status); +int16_t conv_h_hf(float16 a, float_status *fp_status); + +/* IEEE - FP compare instructions */ +uint32_t cmpgt_sf(float32 a1, float32 a2, float_status *fp_status); +uint16_t cmpgt_hf(float16 a1, float16 a2, float_status *fp_status); + +/* IEEE BFloat instructions */ + +#define fp_mult_sf_bf(A, B) \ + float32_mul(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status) + +#define fp_add_sf_bf(A, B) \ + float32_add(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status) + +#define fp_sub_sf_bf(A, B) \ + float32_sub(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status) + +#define fp_mult_sf_bf_acc(f1, f2, f3) \ + float32_muladd(bf16_to_f32(f1), bf16_to_f32(f2), f3, 0, &env->hvx_fp_status) + +static inline bfloat16 f32_to_bf16(float32 A, float_status *fp_status) +{ + uint32_t rslt = A; + if ((rslt & 0x1FFFF) == 0x08000) { + /* do not round up if exactly .5 and even already */ + } else if ((rslt & 0x8000) == 0x8000) { + rslt += 0x8000; /* rounding to nearest number */ + } + rslt = float32_is_any_nan(A) ? FP32_DEF_NAN : rslt; + return float32_to_bfloat16(rslt, fp_status); +} + +#define fp_min_bf(A, B) \ + f32_to_bf16(float32_min(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status), \ + &env->hvx_fp_status); + +#define fp_max_bf(A, B) \ + f32_to_bf16(float32_max(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status), \ + &env->hvx_fp_status); + +#endif
diff --git a/target/hexagon/mmvec/macros.h b/target/hexagon/mmvec/macros.h index c7840fb..b36f833 100644 --- a/target/hexagon/mmvec/macros.h +++ b/target/hexagon/mmvec/macros.h
@@ -23,6 +23,10 @@ #include "mmvec/system_ext_mmvec.h" #include "accel/tcg/getpc.h" #include "accel/tcg/probe.h" +#include "mmvec/hvx_ieee_fp.h" + +#define fBFLOAT() +#define fCVI_VX_NO_TMP_LD() #ifndef QEMU_GENERATE #define VdV (*(MMVector *restrict)(VdV_void)) @@ -353,6 +357,11 @@ do { \ COE = (sextract32(VAL, 24 + 2 * POS, 2) << 8) | \ extract32(VAL, POS * 8, 8); \ - } while (0); + } while (0) \ + ; + +#define fCMPGT_SF(A, B) cmpgt_sf(A, B, &env->hvx_fp_status) +#define fCMPGT_HF(A, B) cmpgt_hf(A, B, &env->hvx_fp_status) +#define fCMPGT_BF(A, B) fCMPGT_SF((uint32_t)(A) << 16, (uint32_t)(B) << 16) #endif
diff --git a/target/hexagon/mmvec/mmvec.h b/target/hexagon/mmvec/mmvec.h index 4a4f6cc..8e72f2f 100644 --- a/target/hexagon/mmvec/mmvec.h +++ b/target/hexagon/mmvec/mmvec.h
@@ -41,6 +41,9 @@ int16_t h[MAX_VEC_SIZE_BYTES / 2]; uint8_t ub[MAX_VEC_SIZE_BYTES / 1]; int8_t b[MAX_VEC_SIZE_BYTES / 1]; + float32 sf[MAX_VEC_SIZE_BYTES / 4]; + float16 hf[MAX_VEC_SIZE_BYTES / 2]; + bfloat16 bf[MAX_VEC_SIZE_BYTES / 2]; } MMVector; typedef union {
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 3ce223c..23894ff 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c
@@ -35,11 +35,13 @@ #include "mmvec/macros.h" #include "op_helper.h" #include "cpu_helper.h" +#include "tcg/tcg-gvec-desc.h" #include "translate.h" #ifndef CONFIG_USER_ONLY #include "hw/hexagon/hexagon_globalreg.h" #include "hex_mmu.h" #include "hw/hexagon/hexagon_tlb.h" +#include "hw/intc/hex-l2vic.h" #include "hex_interrupts.h" #include "hexswi.h" #endif @@ -1564,7 +1566,20 @@ void HELPER(ciad)(CPUHexagonState *env, uint32_t mask) { - g_assert_not_reached(); + uint32_t ipendad; + uint32_t iad; + HexagonCPU *cpu; + + BQL_LOCK_GUARD(); + cpu = env_archcpu(env); + ipendad = hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + iad = fGET_FIELD(ipendad, IPENDAD_IAD); + fSET_FIELD(ipendad, IPENDAD_IAD, iad & ~(mask)); + hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD, + ipendad, env->threadId); + l2vic_clear_interrupt(cpu->l2vic); + hex_interrupt_update(env); } void HELPER(siad)(CPUHexagonState *env, uint32_t mask) @@ -1947,3 +1962,23 @@ printf("ERROR: bogus helper: " #tag "\n") #include "helper_funcs_generated.c.inc" + +#define DO_ABSDIFF(NAME, TYPE, UTYPE) \ +void HELPER(NAME)(void *vd, void *vn, void *vm, uint32_t desc) \ +{ \ + intptr_t i, oprsz = simd_oprsz(desc); \ + UTYPE *d = vd; \ + TYPE *n = vn, *m = vm; \ + \ + for (i = 0; i < oprsz / sizeof(TYPE); i++) { \ + d[i] = n[i] < m[i] ? (UTYPE)m[i] - (UTYPE)n[i] \ + : (UTYPE)n[i] - (UTYPE)m[i]; \ + } \ +} + +DO_ABSDIFF(gvec_sabsdiff_h, int16_t, uint16_t) +DO_ABSDIFF(gvec_sabsdiff_w, int32_t, uint32_t) +DO_ABSDIFF(gvec_uabsdiff_b, uint8_t, uint8_t) +DO_ABSDIFF(gvec_uabsdiff_h, uint16_t, uint16_t) + +#undef DO_ABSDIFF
diff --git a/target/hexagon/printinsn.c b/target/hexagon/printinsn.c index a7e46f4..023ea12 100644 --- a/target/hexagon/printinsn.c +++ b/target/hexagon/printinsn.c
@@ -23,18 +23,105 @@ #include "internal.h" #include "decode.h" -static const char *sreg2str(unsigned int reg) +/* + * Used when there is some sort of error and we can't figure out the real + * system register name + */ +static const char *const generic_sreg_names[256] = { + "S000", "S001", "S002", "S003", "S004", "S005", "S006", "S007", + "S008", "S009", "S010", "S011", "S012", "S013", "S014", "S015", + "S016", "S017", "S018", "S019", "S020", "S021", "S022", "S023", + "S024", "S025", "S026", "S027", "S028", "S029", "S030", "S031", + "S032", "S033", "S034", "S035", "S036", "S037", "S038", "S039", + "S040", "S041", "S042", "S043", "S044", "S045", "S046", "S047", + "S048", "S049", "S050", "S051", "S052", "S053", "S054", "S055", + "S056", "S057", "S058", "S059", "S060", "S061", "S062", "S063", + "S064", "S065", "S066", "S067", "S068", "S069", "S070", "S071", + "S072", "S073", "S074", "S075", "S076", "S077", "S078", "S079", + "S080", "S081", "S082", "S083", "S084", "S085", "S086", "S087", + "S088", "S089", "S090", "S091", "S092", "S093", "S094", "S095", + "S096", "S097", "S098", "S099", "S100", "S101", "S102", "S103", + "S104", "S105", "S106", "S107", "S108", "S109", "S110", "S111", + "S112", "S113", "S114", "S115", "S116", "S117", "S118", "S119", + "S120", "S121", "S122", "S123", "S124", "S125", "S126", "S127", + "S128", "S129", "S130", "S131", "S132", "S133", "S134", "S135", + "S136", "S137", "S138", "S139", "S140", "S141", "S142", "S143", + "S144", "S145", "S146", "S147", "S148", "S149", "S150", "S151", + "S152", "S153", "S154", "S155", "S156", "S157", "S158", "S159", + "S160", "S161", "S162", "S163", "S164", "S165", "S166", "S167", + "S168", "S169", "S170", "S171", "S172", "S173", "S174", "S175", + "S176", "S177", "S178", "S179", "S180", "S181", "S182", "S183", + "S184", "S185", "S186", "S187", "S188", "S189", "S190", "S191", + "S192", "S193", "S194", "S195", "S196", "S197", "S198", "S199", + "S200", "S201", "S202", "S203", "S204", "S205", "S206", "S207", + "S208", "S209", "S210", "S211", "S212", "S213", "S214", "S215", + "S216", "S217", "S218", "S219", "S220", "S221", "S222", "S223", + "S224", "S225", "S226", "S227", "S228", "S229", "S230", "S231", + "S232", "S233", "S234", "S235", "S236", "S237", "S238", "S239", + "S240", "S241", "S242", "S243", "S244", "S245", "S246", "S247", + "S248", "S249", "S250", "S251", "S252", "S253", "S254", "S255", +}; + +static const char *sreg2str(uint8_t reg) { - if (reg < TOTAL_PER_THREAD_REGS) { - return hexagon_regnames[reg]; +#ifndef CONFIG_USER_ONLY + if (reg < NUM_SREGS) { + return hexagon_sregnames[reg]; } else { - return "???"; + return generic_sreg_names[reg]; } +#else + return generic_sreg_names[reg]; +#endif } -static const char *creg2str(unsigned int reg) +/* + * Used when there is some sort of error and we can't figure out the real + * control register name + */ +static const char *const generic_creg_names[256] = { + "C000", "C001", "C002", "C003", "C004", "C005", "C006", "C007", + "C008", "C009", "C010", "C011", "C012", "C013", "C014", "C015", + "C016", "C017", "C018", "C019", "C020", "C021", "C022", "C023", + "C024", "C025", "C026", "C027", "C028", "C029", "C030", "C031", + "C032", "C033", "C034", "C035", "C036", "C037", "C038", "C039", + "C040", "C041", "C042", "C043", "C044", "C045", "C046", "C047", + "C048", "C049", "C050", "C051", "C052", "C053", "C054", "C055", + "C056", "C057", "C058", "C059", "C060", "C061", "C062", "C063", + "C064", "C065", "C066", "C067", "C068", "C069", "C070", "C071", + "C072", "C073", "C074", "C075", "C076", "C077", "C078", "C079", + "C080", "C081", "C082", "C083", "C084", "C085", "C086", "C087", + "C088", "C089", "C090", "C091", "C092", "C093", "C094", "C095", + "C096", "C097", "C098", "C099", "C100", "C101", "C102", "C103", + "C104", "C105", "C106", "C107", "C108", "C109", "C110", "C111", + "C112", "C113", "C114", "C115", "C116", "C117", "C118", "C119", + "C120", "C121", "C122", "C123", "C124", "C125", "C126", "C127", + "C128", "C129", "C130", "C131", "C132", "C133", "C134", "C135", + "C136", "C137", "C138", "C139", "C140", "C141", "C142", "C143", + "C144", "C145", "C146", "C147", "C148", "C149", "C150", "C151", + "C152", "C153", "C154", "C155", "C156", "C157", "C158", "C159", + "C160", "C161", "C162", "C163", "C164", "C165", "C166", "C167", + "C168", "C169", "C170", "C171", "C172", "C173", "C174", "C175", + "C176", "C177", "C178", "C179", "C180", "C181", "C182", "C183", + "C184", "C185", "C186", "C187", "C188", "C189", "C190", "C191", + "C192", "C193", "C194", "C195", "C196", "C197", "C198", "C199", + "C200", "C201", "C202", "C203", "C204", "C205", "C206", "C207", + "C208", "C209", "C210", "C211", "C212", "C213", "C214", "C215", + "C216", "C217", "C218", "C219", "C220", "C221", "C222", "C223", + "C224", "C225", "C226", "C227", "C228", "C229", "C230", "C231", + "C232", "C233", "C234", "C235", "C236", "C237", "C238", "C239", + "C240", "C241", "C242", "C243", "C244", "C245", "C246", "C247", + "C248", "C249", "C250", "C251", "C252", "C253", "C254", "C255", +}; + +static const char *creg2str(uint8_t reg) { - return sreg2str(reg + HEX_REG_SA0); + uint8_t gpr = reg + HEX_REG_SA0; + if (gpr < TOTAL_PER_THREAD_REGS) { + return hexagon_regnames[gpr]; + } else { + return generic_creg_names[reg]; + } } static void snprintinsn(GString *buf, Insn *insn) @@ -52,7 +139,7 @@ } void snprint_a_pkt_disas(GString *buf, Packet *pkt, uint32_t *words, - target_ulong pc, const HexagonCPUDef *hex_def) + target_ulong pc, const HexagonCPUConfig *cfg) { bool has_endloop0 = false; bool has_endloop1 = false; @@ -84,12 +171,17 @@ } g_string_append(buf, "\t"); - if (opcode_supported(pkt->insn[i].opcode, hex_def)) { + if (opcode_supported(pkt->insn[i].opcode, cfg->hex_def)) { snprintinsn(buf, &(pkt->insn[i])); } else { g_string_append(buf, "<invalid>"); } + if (!cfg->ieee_fp_extension && + GET_ATTRIB(pkt->insn[i].opcode, A_HVX_IEEE_FP)) { + g_string_append(buf, " (disabled: no ieee_fp)"); + } + if (i < pkt->num_insns - 1) { /* * Subinstructions are two instructions encoded
diff --git a/target/hexagon/printinsn.h b/target/hexagon/printinsn.h index de962b5..c838940 100644 --- a/target/hexagon/printinsn.h +++ b/target/hexagon/printinsn.h
@@ -22,6 +22,6 @@ #include "insn.h" void snprint_a_pkt_disas(GString *buf, Packet *pkt, uint32_t *words, - target_ulong pc, const HexagonCPUDef *hex_def); + target_ulong pc, const HexagonCPUConfig *cfg); #endif
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 199b4f8..06a8159 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c
@@ -1209,8 +1209,9 @@ ctx->num_hvx_insns = 0; ctx->branch_cond = TCG_COND_NEVER; ctx->is_tight_loop = FIELD_EX32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP); - ctx->short_circuit = hex_cpu->short_circuit; + ctx->short_circuit = hex_cpu->cfg.short_circuit; ctx->hex_def = HEXAGON_CPU_GET_CLASS(hex_cpu)->hex_def; + ctx->ieee_fp_extension = hex_cpu->cfg.ieee_fp_extension; #ifndef CONFIG_USER_ONLY ctx->num_cycles = 0; ctx->pcycle_enabled = FIELD_EX32(hex_flags, TB_FLAGS, PCYCLE_ENABLED); @@ -1267,7 +1268,7 @@ * so end the TLB after every packet. */ HexagonCPU *hex_cpu = env_archcpu(env); - if (hex_cpu->lldb_compat && qemu_loglevel_mask(CPU_LOG_TB_CPU)) { + if (hex_cpu->cfg.lldb_compat && qemu_loglevel_mask(CPU_LOG_TB_CPU)) { ctx->base.is_jmp = DISAS_TOO_MANY; } }
diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index 2fca157..3c5773e 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h
@@ -81,6 +81,7 @@ target_ulong branch_dest; bool is_tight_loop; bool short_circuit; + bool ieee_fp_extension; bool read_after_write; bool has_hvx_overlap; TCGv new_value[TOTAL_PER_THREAD_REGS];
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 4272b67..644c45f 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c
@@ -5022,7 +5022,7 @@ kvm_msr_entry_add(cpu, MSR_IA32_U_CET, 0); kvm_msr_entry_add(cpu, MSR_IA32_S_CET, 0); - if (env->features[FEAT_7_0_EDX] & CPUID_7_0_ECX_CET_SHSTK) { + if (env->features[FEAT_7_0_ECX] & CPUID_7_0_ECX_CET_SHSTK) { kvm_msr_entry_add(cpu, MSR_IA32_PL0_SSP, 0); kvm_msr_entry_add(cpu, MSR_IA32_PL1_SSP, 0); kvm_msr_entry_add(cpu, MSR_IA32_PL2_SSP, 0);
diff --git a/target/s390x/diag.c b/target/s390x/diag.c index 80f0958..46d191b 100644 --- a/target/s390x/diag.c +++ b/target/s390x/diag.c
@@ -185,7 +185,7 @@ return false; } - if (kvm_enabled() && kvm_s390_get_hpage_1m()) { + if (kvm_enabled() && kvm_s390_get_hpage()) { error_report("Protected VMs can currently not be backed with " "huge pages"); env->regs[r1 + 1] = DIAG_308_RC_INVAL_FOR_PV;
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c index 72031a5..803b878 100644 --- a/target/s390x/kvm/kvm.c +++ b/target/s390x/kvm/kvm.c
@@ -145,7 +145,7 @@ static int cap_mem_op_extension; static int cap_s390_irq; static int cap_ri; -static int cap_hpage_1m; +static int cap_hpage; static int cap_vcpu_resets; static int cap_protected; static int cap_zpci_op; @@ -231,7 +231,7 @@ .attr = KVM_S390_VM_MEM_ENABLE_CMMA, }; - if (cap_hpage_1m) { + if (cap_hpage) { warn_report("CMM will not be enabled because it is not " "compatible with huge memory backings."); return; @@ -292,30 +292,28 @@ } } -void kvm_s390_set_max_pagesize(uint64_t pagesize, Error **errp) +static bool kvm_s390_pgsize_cap(uint32_t capa, const char *s, Error **errp) { - if (pagesize == 4 * KiB) { - return; + if (kvm_vm_enable_cap(kvm_state, capa, 0)) { + error_setg(errp, "Memory backing with %s pages was specified, " + "but KVM does not support this memory backing", s); + return false; } - - if (pagesize != 1 * MiB) { - error_setg(errp, "Memory backing with 2G pages was specified, " - "but KVM does not support this memory backing"); - return; - } - - if (kvm_vm_enable_cap(kvm_state, KVM_CAP_S390_HPAGE_1M, 0)) { - error_setg(errp, "Memory backing with 1M pages was specified, " - "but KVM does not support this memory backing"); - return; - } - - cap_hpage_1m = 1; + return true; } -int kvm_s390_get_hpage_1m(void) +void kvm_s390_set_max_pagesize(uint64_t pagesize, Error **errp) { - return cap_hpage_1m; + if (pagesize == MiB) { + cap_hpage = kvm_s390_pgsize_cap(KVM_CAP_S390_HPAGE_1M, "1M", errp); + } else if (pagesize != 4 * KiB) { + cap_hpage = 2 * kvm_s390_pgsize_cap(KVM_CAP_S390_HPAGE_2G, "2G", errp); + } +} + +int kvm_s390_get_hpage(void) +{ + return cap_hpage; } static void ccw_machine_class_foreach(ObjectClass *oc, void *opaque)
diff --git a/target/s390x/kvm/kvm_s390x.h b/target/s390x/kvm/kvm_s390x.h index 7b1cce3..3c4fa04 100644 --- a/target/s390x/kvm/kvm_s390x.h +++ b/target/s390x/kvm/kvm_s390x.h
@@ -25,7 +25,7 @@ int kvm_s390_set_cpu_state(S390CPU *cpu, uint8_t cpu_state); void kvm_s390_vcpu_interrupt_pre_save(S390CPU *cpu); int kvm_s390_vcpu_interrupt_post_load(S390CPU *cpu); -int kvm_s390_get_hpage_1m(void); +int kvm_s390_get_hpage(void); int kvm_s390_get_protected_dump(void); int kvm_s390_get_ri(void); int kvm_s390_get_zpci_op(void);
diff --git a/target/s390x/kvm/stubs.c b/target/s390x/kvm/stubs.c index 196127b..ebf3c83 100644 --- a/target/s390x/kvm/stubs.c +++ b/target/s390x/kvm/stubs.c
@@ -143,7 +143,7 @@ g_assert_not_reached(); } -int kvm_s390_get_hpage_1m(void) +int kvm_s390_get_hpage(void) { g_assert_not_reached(); }
diff --git a/target/s390x/tcg/crypto_helper.c b/target/s390x/tcg/crypto_helper.c index 8fe0a22..6a5dbe1 100644 --- a/target/s390x/tcg/crypto_helper.c +++ b/target/s390x/tcg/crypto_helper.c
@@ -16,6 +16,7 @@ #include "qemu/guest-random.h" #include "s390x-internal.h" #include "tcg_s390x.h" +#include "exec/cpu-common.h" #include "exec/helper-proto.h" #include "accel/tcg/cpu-ldst-common.h" #include "accel/tcg/cpu-mmu-index.h" @@ -242,8 +243,8 @@ return !len ? 0 : 3; } -static void fill_buf_random(CPUS390XState *env, const int mmu_idx, uintptr_t ra, - uint64_t *buf_reg, uint64_t *len_reg) +static int fill_buf_random(CPUS390XState *env, const int mmu_idx, uintptr_t ra, + uint64_t *buf_reg, uint64_t *len_reg) { const MemOpIdx oi = make_memop_idx(MO_8, mmu_idx); uint8_t tmp[256]; @@ -265,7 +266,13 @@ --*len_reg; } len -= block; + + if (cpu_loop_exit_requested(env_cpu(env))) { + break; + } } + + return len == 0 ? 0 : 3; } uint32_t HELPER(msa)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t r3, @@ -278,6 +285,7 @@ uint8_t subfunc[16] = { 0 }; uint64_t param_addr; MemOpIdx oi; + int cc; switch (type) { case S390_FEAT_TYPE_KMAC: @@ -308,9 +316,13 @@ return cpacf_sha512(env, mmu_idx, ra, env->regs[1], &env->regs[r2], &env->regs[r2 + 1], type); case 114: /* CPACF_PRNO_TRNG */ - fill_buf_random(env, mmu_idx, ra, &env->regs[r1], &env->regs[r1 + 1]); - fill_buf_random(env, mmu_idx, ra, &env->regs[r2], &env->regs[r2 + 1]); - break; + cc = fill_buf_random(env, mmu_idx, ra, + &env->regs[r1], &env->regs[r1 + 1]); + if (cc == 0) { + cc = fill_buf_random(env, mmu_idx, ra, + &env->regs[r2], &env->regs[r2 + 1]); + } + return cc; default: /* we don't implement any other subfunction yet */ g_assert_not_reached();
diff --git a/target/s390x/tcg/insn-data.h.inc b/target/s390x/tcg/insn-data.h.inc index 0d5392e..1ea7224 100644 --- a/target/s390x/tcg/insn-data.h.inc +++ b/target/s390x/tcg/insn-data.h.inc
@@ -887,8 +887,8 @@ C(0xe32f, STRVG, RXY_a, Z, la2, r1_o, new, m1_64, rev64, 0) /* STORE CLOCK */ - F(0xb205, STCK, S, Z, la2, 0, new, m1_64, stck, 0, IF_IO) - F(0xb27c, STCKF, S, SCF, la2, 0, new, m1_64, stck, 0, IF_IO) + F(0xb205, STCK, S, Z, la2, 0, new, 0, stck, 0, IF_IO) + F(0xb27c, STCKF, S, SCF, la2, 0, new, 0, stck, 0, IF_IO) /* STORE CLOCK EXTENDED */ F(0xb278, STCKE, S, Z, 0, a2, 0, 0, stcke, 0, IF_IO)
diff --git a/target/s390x/tcg/int_helper.c b/target/s390x/tcg/int_helper.c index fbda396..5aedd14 100644 --- a/target/s390x/tcg/int_helper.c +++ b/target/s390x/tcg/int_helper.c
@@ -39,7 +39,8 @@ int32_t b = b64; int64_t q, r; - if (b == 0) { + /* Catch divide by zero, and non-representable quotient (MIN / -1). */ + if (b == 0 || (b == -1 && a == (1ll << 63))) { tcg_s390_program_interrupt(env, PGM_FIXPT_DIVIDE, GETPC()); }
diff --git a/target/s390x/tcg/translate.c b/target/s390x/tcg/translate.c index 82165ac..1b60231 100644 --- a/target/s390x/tcg/translate.c +++ b/target/s390x/tcg/translate.c
@@ -4108,7 +4108,9 @@ static DisasJumpType op_stck(DisasContext *s, DisasOps *o) { gen_helper_stck(o->out, tcg_env); + tcg_gen_qemu_st_i64(o->out, o->addr1, get_mem_index(s), MO_BEUQ); /* ??? We don't implement clock states. */ + /* Set the CC after the store; a suppressed store must preserve it. */ gen_op_movi_cc(s, 0); return DISAS_NEXT; }
diff --git a/tests/functional/aarch64/test_aspeed_ast2700a1.py b/tests/functional/aarch64/test_aspeed_ast2700a1.py index ddddd2d..2113c78 100755 --- a/tests/functional/aarch64/test_aspeed_ast2700a1.py +++ b/tests/functional/aarch64/test_aspeed_ast2700a1.py
@@ -85,13 +85,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True): exec_command_and_wait_for_pattern(self, 'root', 'Password:') exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#') - ASSET_SDK_V1101_AST2700A1 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-image.tar.gz', - '859808828531a51931aad3b4e70b28143eebb3cde1838ba7d8e7a2b844c8a1ab') + ASSET_SDK_V1103_AST2700A1 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-a1-image.tar.gz', + '540961dc380709d852e957c5817cd7ee0dbb0a66f3aa17413eac7b67518afbfe') - ASSET_SDK_V1101_AST2700A1_DCSCM = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-dcscm-image.tar.gz', - '4654eabad75da3fd33635cd6d29b7635181daefee7294b68feb124b9d4c24116') + ASSET_SDK_V1103_AST2700A1_DCSCM = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-a1-dcscm-image.tar.gz', + '5f7c139330fcefa6025bc7565a20fd5ea42cacebf810d56dc9a76b07cf69b3e1') def do_ast2700_i2c_test(self, bus_id): bus_str = str(bus_id) @@ -118,6 +118,11 @@ def do_ast2700_pcie_test(self): 'ip addr show dev eth2', 'inet 10.0.2.15/24') + def do_ast2700_usb_ehci_test(self): + exec_command_and_wait_for_pattern(self, + 'lsusb', + 'QEMU QEMU USB Keyboard') + def start_ast2700_test(self, name, bus_id): num_cpu = 4 load_images_list = [ @@ -127,7 +132,8 @@ def start_ast2700_test(self, name, bus_id): }, { 'addr': '0x430000000', - 'file': self.scratch_file(name, 'bl31.bin') + 'file': self.scratch_file(name, 'trusted-firmware-a', + 'bl31.bin') }, { 'addr': '0x430080000', @@ -154,34 +160,36 @@ def start_ast2700_test_vbootrom(self, name, bus_id): self.do_test_aarch64_aspeed_sdk_start( self.scratch_file(name, 'image-bmc'), bus_id) - def test_aarch64_ast2700a1_evb_sdk_v11_01(self): + def test_aarch64_ast2700a1_evb_sdk_v11_03(self): self.set_machine('ast2700a1-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2700A1) + self.archive_extract(self.ASSET_SDK_V1103_AST2700A1) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') + self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3') self.start_ast2700_test('ast2700-a1-image', 1) self.verify_openbmc_boot_and_login('ast2700-a1') self.do_ast2700_i2c_test(1) self.do_ast2700_pcie_test() + self.do_ast2700_usb_ehci_test() - def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_01(self): + def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_03(self): self.set_machine('ast2700a1-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2700A1) + self.archive_extract(self.ASSET_SDK_V1103_AST2700A1) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') self.start_ast2700_test_vbootrom('ast2700-a1-image', 1) self.verify_vbootrom_firmware_flow() self.verify_openbmc_boot_start() - def test_aarch64_ast2700a1_evb_ioexp_v11_01(self): + def test_aarch64_ast2700a1_evb_ioexp_v11_03(self): self.set_machine('ast2700a1-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2700A1_DCSCM) + self.archive_extract(self.ASSET_SDK_V1103_AST2700A1_DCSCM) self.vm.set_machine('ast2700a1-evb,fmc-model=w25q512jv') self.vm.add_args('-device', 'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16')
diff --git a/tests/functional/aarch64/test_aspeed_ast2700a2.py b/tests/functional/aarch64/test_aspeed_ast2700a2.py index 0fe04a6..01c7c4f 100755 --- a/tests/functional/aarch64/test_aspeed_ast2700a2.py +++ b/tests/functional/aarch64/test_aspeed_ast2700a2.py
@@ -85,13 +85,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True): exec_command_and_wait_for_pattern(self, 'root', 'Password:') exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#') - ASSET_SDK_V1101_AST2700A2 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz', - 'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487') + ASSET_SDK_V1103_AST2700A2 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-default-image.tar.gz', + 'b91450d53da234591060cfb926fa30f7534ce20eaab766cb0f80ec332f8f0adb') - ASSET_SDK_V1101_AST2700A2_DCSCM = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-dcscm-image.tar.gz', - 'b92ece9ca733dfd7a20193a12582f743b77f1898116b6d6f1abe57ac8db01c56') + ASSET_SDK_V1103_AST2700A2_DCSCM = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-dcscm-image.tar.gz', + '7afd8323fc95097c14872b90d68c5cae5d078530c704591b192b74bd892c1dbf') def do_ast2700_i2c_test(self, bus_id): bus_str = str(bus_id) @@ -121,6 +121,11 @@ def do_ast2700_pcie_test(self): 'ip addr show dev eth2', 'inet 10.0.2.15/24') + def do_ast2700_usb_ehci_test(self): + exec_command_and_wait_for_pattern(self, + 'lsusb', + 'QEMU QEMU USB Keyboard') + def start_ast2700_test(self, name, bus_id): num_cpu = 4 load_images_list = [ @@ -130,7 +135,8 @@ def start_ast2700_test(self, name, bus_id): }, { 'addr': '0x430000000', - 'file': self.scratch_file(name, 'bl31.bin') + 'file': self.scratch_file(name, 'trusted-firmware-a', + 'bl31.bin') }, { 'addr': '0x430080000', @@ -157,34 +163,36 @@ def start_ast2700_test_vbootrom(self, name, bus_id): self.do_test_aarch64_aspeed_sdk_start( self.scratch_file(name, 'image-bmc'), bus_id) - def test_aarch64_ast2700a2_evb_sdk_v11_01(self): + def test_aarch64_ast2700a2_evb_sdk_v11_03(self): self.set_machine('ast2700a2-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2700A2) + self.archive_extract(self.ASSET_SDK_V1103_AST2700A2) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') + self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3') self.start_ast2700_test('ast2700-default-image', 1) self.verify_openbmc_boot_and_login('ast2700-default') self.do_ast2700_i2c_test(1) self.do_ast2700_pcie_test() + self.do_ast2700_usb_ehci_test() - def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_01(self): + def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_03(self): self.set_machine('ast2700a2-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2700A2) + self.archive_extract(self.ASSET_SDK_V1103_AST2700A2) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') self.start_ast2700_test_vbootrom('ast2700-default-image', 1) self.verify_vbootrom_firmware_flow() self.verify_openbmc_boot_start() - def test_aarch64_ast2700a2_evb_ioexp_v11_01(self): + def test_aarch64_ast2700a2_evb_ioexp_v11_03(self): self.set_machine('ast2700a2-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2700A2_DCSCM) + self.archive_extract(self.ASSET_SDK_V1103_AST2700A2_DCSCM) self.vm.set_machine('ast2700a2-evb,fmc-model=w25q512jv') self.vm.add_args('-device', 'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16')
diff --git a/tests/functional/aarch64/test_aspeed_ast2700fc.py b/tests/functional/aarch64/test_aspeed_ast2700fc.py index 86270e6..704477d 100755 --- a/tests/functional/aarch64/test_aspeed_ast2700fc.py +++ b/tests/functional/aarch64/test_aspeed_ast2700fc.py
@@ -66,9 +66,9 @@ def load_ast2700fc_coprocessor(self, name): self.vm.add_args('-device', f'loader,file={file},cpu-num={cpu_num}') - ASSET_SDK_V1101_AST2700 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz', - 'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487') + ASSET_SDK_V1103_AST2700 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-default-image.tar.gz', + 'b91450d53da234591060cfb926fa30f7534ce20eaab766cb0f80ec332f8f0adb') def do_ast2700_i2c_test(self): exec_command_and_wait_for_pattern(self, @@ -101,7 +101,7 @@ def do_ast2700fc_ssp_test(self): exec_command_and_wait_for_pattern(self, '\012', 'ssp_tsp:~$') exec_command_and_wait_for_pattern(self, 'version', - 'Zephyr version 3.7.1') + 'Zephyr version 3.7.2') exec_command_and_wait_for_pattern(self, 'md 72c02000 1', '[72c02000] 06020103') @@ -112,7 +112,7 @@ def do_ast2700fc_tsp_test(self): exec_command_and_wait_for_pattern(self, '\012', 'tsp:~$') exec_command_and_wait_for_pattern(self, 'version', - 'Zephyr version 3.7.1') + 'Zephyr version 3.7.2') exec_command_and_wait_for_pattern(self, 'md 72c02000 1', '[72c02000] 06020103') @@ -125,7 +125,8 @@ def start_ast2700fc_test(self, name): }, { 'addr': '0x430000000', - 'file': self.scratch_file(name, 'bl31.bin') + 'file': self.scratch_file(name, 'trusted-firmware-a', + 'bl31.bin') }, { 'addr': '0x430080000', @@ -153,11 +154,11 @@ def start_ast2700fc_test_vbootrom(self, name): self.do_test_aarch64_aspeed_sdk_start( self.scratch_file(name, 'image-bmc')) - def test_aarch64_ast2700fc_sdk_v11_01(self): + def test_aarch64_ast2700fc_sdk_v11_03(self): self.set_machine('ast2700fc') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2700) + self.archive_extract(self.ASSET_SDK_V1103_AST2700) self.start_ast2700fc_test('ast2700-default-image') self.verify_openbmc_boot_and_login('ast2700-default') self.do_ast2700_i2c_test() @@ -165,10 +166,10 @@ def test_aarch64_ast2700fc_sdk_v11_01(self): self.do_ast2700fc_ssp_test() self.do_ast2700fc_tsp_test() - def test_aarch64_ast2700fc_sdk_vbootrom_v11_01(self): + def test_aarch64_ast2700fc_sdk_vbootrom_v11_03(self): self.set_machine('ast2700fc') - self.archive_extract(self.ASSET_SDK_V1101_AST2700) + self.archive_extract(self.ASSET_SDK_V1103_AST2700) self.start_ast2700fc_test_vbootrom('ast2700-default-image') self.verify_openbmc_boot_and_login('ast2700-default') self.do_ast2700fc_ssp_test()
diff --git a/tests/functional/aarch64/test_raspi4.py b/tests/functional/aarch64/test_raspi4.py index 7a4302b..bacecbb 100755 --- a/tests/functional/aarch64/test_raspi4.py +++ b/tests/functional/aarch64/test_raspi4.py
@@ -86,6 +86,23 @@ def test_arm_raspi4_initrd(self): 'BCM2835') exec_command_and_wait_for_pattern(self, 'cat /proc/iomem', 'cprman@7e101000') + + # We used to get the RAM size wrong; guard against a regression + # (see git history for details). + mem_total_kb = None + cmd_output = exec_command_and_wait_for_pattern( + self, 'cat /proc/meminfo', 'MemAvailable') + for line in cmd_output.decode('ascii', errors='replace').splitlines(): + if line.startswith('MemTotal:'): + mem_total_kb = int(line.split()[1]) + break + self.assertIsNotNone(mem_total_kb, 'MemTotal line not found') + self.assertGreater(mem_total_kb, 1900000, + 'guest RAM (%d kB) is far below the ~1.9 GiB ' + 'expected for a 2 GiB raspi4b -- the second ' + 'memory node above the 1 GiB peripheral hole ' + 'is probably not being added' % mem_total_kb) + exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted') # TODO: Raspberry Pi4 doesn't shut down properly with recent kernels # Wait for VM to shut down gracefully
diff --git a/tests/functional/arm/test_aspeed_ast1030.py b/tests/functional/arm/test_aspeed_ast1030.py index 03fee55..83a96ec 100755 --- a/tests/functional/arm/test_aspeed_ast1030.py +++ b/tests/functional/arm/test_aspeed_ast1030.py
@@ -12,17 +12,17 @@ class AST1030Machine(AspeedTest): - ASSET_ZEPHYR_3_06 = Asset( + ASSET_ZEPHYR_3_08 = Asset( ('https://github.com/AspeedTech-BMC' - '/zephyr/releases/download/v00.03.06/ast1030-evb-demo.zip'), - '056f37fcd9f165308cedca3a08f2bed37ed40c0a1402c4fa515613b80a369f38') + '/zephyr/releases/download/v00.03.08/ast1030-evb-demo.zip'), + '9eac3691bc7bce1b912bbe2ae4e36608a6532ff8d607f4d1e44b88407a48d4e5') - def test_arm_ast1030_zephyros_3_06(self): + def test_arm_ast1030_zephyros_3_08(self): self.set_machine('ast1030-evb') kernel_name = "ast1030-evb-demo/zephyr.elf" kernel_file = self.archive_extract( - self.ASSET_ZEPHYR_3_06, member=kernel_name) + self.ASSET_ZEPHYR_3_08, member=kernel_name) self.vm.set_console() self.vm.add_args('-kernel', kernel_file, '-nographic') @@ -72,7 +72,7 @@ def test_arm_ast1030_otp_blockdev_device(self): self.vm.set_machine("ast1030-evb") kernel_name = "ast1030-evb-demo/zephyr.elf" - kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_06, + kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_08, member=kernel_name) otp_img = self.generate_otpmem_image()
diff --git a/tests/functional/arm/test_aspeed_ast1060.py b/tests/functional/arm/test_aspeed_ast1060.py index 833cfb8..d715825 100755 --- a/tests/functional/arm/test_aspeed_ast1060.py +++ b/tests/functional/arm/test_aspeed_ast1060.py
@@ -11,18 +11,18 @@ class AST1060Machine(AspeedTest): - ASSET_ASPEED_AST1060_PROT_3_05 = Asset( + ASSET_ASPEED_AST1060_PROT_3_07 = Asset( ('https://github.com/AspeedTech-BMC' - '/aspeed-zephyr-project/releases/download/v03.05' - '/ast1060_prot_v03.05.tgz'), - '63b36d7420290726ca80477de254474b7cb79539a42819bb1fe2665d598dadb5') + '/aspeed-zephyr-project/releases/download/v03.07' + '/ast1060_prot_v03.07.tgz'), + '55a7f51f0b77051a0ef2ada993a16c5033768e1b8e8be3babfc52c303eecd07f') - def test_arm_ast1060_prot_3_05(self): + def test_arm_ast1060_prot_3_07(self): self.set_machine('ast1060-evb') kernel_name = "ast1060_prot/zephyr.bin" kernel_file = self.archive_extract( - self.ASSET_ASPEED_AST1060_PROT_3_05, member=kernel_name) + self.ASSET_ASPEED_AST1060_PROT_3_07, member=kernel_name) self.vm.set_console() self.vm.add_args('-kernel', kernel_file, '-nographic') @@ -35,7 +35,7 @@ def test_arm_ast1060_otp_blockdev_device(self): self.vm.set_machine("ast1060-evb") kernel_name = "ast1060_prot/zephyr.bin" - kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_05, + kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_07, member=kernel_name) otp_img = self.generate_otpmem_image()
diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk.py b/tests/functional/arm/test_aspeed_ast2500_sdk.py index 6ab498b..95df32b 100755 --- a/tests/functional/arm/test_aspeed_ast2500_sdk.py +++ b/tests/functional/arm/test_aspeed_ast2500_sdk.py
@@ -10,14 +10,14 @@ class AST2500Machine(AspeedTest): - ASSET_SDK_V1101_AST2500 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-obmc.tar.gz', - '3faa1188198da2216837be4b53861c483a58c3ad63784089720bf8421e157da1') + ASSET_SDK_V1103_AST2500 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2500-default-obmc.tar.gz', + '8e20cafddca04d73b799918d6f35b08c83c9f024e223a317b0ad71b97b84842f') def test_arm_ast2500_evb_sdk(self): self.set_machine('ast2500-evb') - self.archive_extract(self.ASSET_SDK_V1101_AST2500) + self.archive_extract(self.ASSET_SDK_V1103_AST2500) self.do_test_arm_aspeed_sdk_start( self.scratch_file("ast2500-default", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py index 8d39dc6..516e96e 100755 --- a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py +++ b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py
@@ -10,14 +10,14 @@ class AST2500Machine(AspeedTest): - ASSET_SDK_V1101_AST2500_515 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-515-obmc.tar.gz', - 'b848ff620d2e9c83e2fb4736b4d1c39b82fdb041058cd42be42c3b177bf38eb9') + ASSET_SDK_V1103_AST2500_515 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2500-default-515-obmc.tar.gz', + 'f17d3b0a5157bcf73c21c4981f838ea0b76c6406cc4a6409267d57d61758ebb6') def test_arm_ast2500_evb_sdk_515(self): self.set_machine('ast2500-evb') - self.archive_extract(self.ASSET_SDK_V1101_AST2500_515) + self.archive_extract(self.ASSET_SDK_V1103_AST2500_515) self.do_test_arm_aspeed_sdk_start( self.scratch_file("ast2500-default-515", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk.py b/tests/functional/arm/test_aspeed_ast2600_sdk.py index cabbe23..4fc594d 100755 --- a/tests/functional/arm/test_aspeed_ast2600_sdk.py +++ b/tests/functional/arm/test_aspeed_ast2600_sdk.py
@@ -14,9 +14,9 @@ class AST2600Machine(AspeedTest): - ASSET_SDK_V1101_AST2600 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz', - '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c') + ASSET_SDK_V1103_AST2600 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-image.tar.gz', + '47e3656a14bf7a4de28d3dfbf48bc2325443bc42d270f3bc82646f92f6dea165') def do_ast2600_pcie_test(self): exec_command_and_wait_for_pattern(self, @@ -49,7 +49,7 @@ def test_arm_ast2600_evb_sdk(self): self.set_machine('ast2600-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1101_AST2600) + self.archive_extract(self.ASSET_SDK_V1103_AST2600) self.vm.add_args('-device', 'tmp105,bus=aspeed.i2c.bus.5,address=0x4d,id=tmp-test')
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py index a8e7faf..f5b14de 100755 --- a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py +++ b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py
@@ -10,14 +10,14 @@ class AST2600Machine(AspeedTest): - ASSET_SDK_V1101_AST2600_515 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-515-image.tar.gz', - 'f3ccf1c08db71cf891637fc73131b80b2c0c0e005c06d5dcae0cf74fc458b43c') + ASSET_SDK_V1103_AST2600_515 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-515-image.tar.gz', + 'c79d0197106f146476e82bb878e5438f6569bd30f3b53fbb520b59bc54f6b7dc') def test_arm_ast2600_evb_sdk_515(self): self.set_machine('ast2600-evb') - self.archive_extract(self.ASSET_SDK_V1101_AST2600_515) + self.archive_extract(self.ASSET_SDK_V1103_AST2600_515) self.do_test_arm_aspeed_sdk_start( self.scratch_file("ast2600-default-515-image", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py index f24dea1..5813c59 100755 --- a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py +++ b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py
@@ -12,15 +12,15 @@ class AST2600Machine(AspeedTest): - ASSET_SDK_V1101_AST2600 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz', - '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c') + ASSET_SDK_V1103_AST2600 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-image.tar.gz', + '47e3656a14bf7a4de28d3dfbf48bc2325443bc42d270f3bc82646f92f6dea165') def test_arm_ast2600_otp_blockdev_device(self): self.vm.set_machine("ast2600-evb") self.require_netdev('user') - image_path = self.archive_extract(self.ASSET_SDK_V1101_AST2600) + image_path = self.archive_extract(self.ASSET_SDK_V1103_AST2600) otp_img = self.generate_otpmem_image() self.vm.set_console()
diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py index 54a1412..0834398 100755 --- a/tests/functional/hexagon/test_arch_tests.py +++ b/tests/functional/hexagon/test_arch_tests.py
@@ -52,6 +52,44 @@ def test_guest_mode(self) -> None: """ self.run_uart_test("test_guest_mode") + def test_int_steering(self) -> None: + """Tests interrupt steering via priority-based routing to + specific threads using STID priority and iassignw. + """ + self.run_uart_test("test_int_steering") + + def test_cache(self) -> None: + """Tests cache operations: dckill/ickill, l2kill, dczeroa, + dccleaninva, cache disable/enable, barriers, and dcinva/dccleana. + """ + self.run_uart_test("test_cache") + + def test_l2vic(self) -> None: + """Tests the L2VIC interrupt controller: enable readback, + interrupt type readback, VID capture, and the fast interface. + """ + self.run_uart_test("test_l2vic") + + def test_threads(self) -> None: + """Tests hardware thread management: start/stop, MODECTL state, + per-thread HTID, shared memory, wait/resume, STID priority, and + SCHEDCFG/BESTWAIT readback. + """ + self.run_uart_test("test_threads") + + def test_tlb_mmu(self) -> None: + """Tests TLB/MMU operations: write/read/probe/invalidate, + global entries, multiple entries, overwrite, ASID matching, + and permission checks. + """ + self.run_uart_test("test_tlb_mmu") + + def test_user_mode(self) -> None: + """Tests user mode / privilege transitions: supervisor mode, + SSR UM/IE/XE/CE/PE bits, and the trap0 user-mode exit handler. + """ + self.run_uart_test("test_user_mode") + if __name__ == "__main__": QemuSystemTest.main()
diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c new file mode 100644 index 0000000..91eda5c --- /dev/null +++ b/tests/qtest/adc128d818-test.c
@@ -0,0 +1,856 @@ +/* + * QTest testcase for the ADC128D818 ADC + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "libqos/i2c.h" +#include "libqos/qgraph.h" +#include "libqtest-single.h" +#include "qobject/qdict.h" + +#define ADC128D818_TEST_ID "adc128d818-test" +#define ADC128D818_TEST_ADDR 0x1f + +/* Register addresses */ +#define REG_CONFIG 0x00 +#define REG_INT_STATUS 0x01 +#define REG_INT_MASK 0x03 +#define REG_CONV_RATE 0x07 +#define REG_CH_DISABLE 0x08 +#define REG_ONE_SHOT 0x09 +#define REG_DEEP_SHUTDOWN 0x0a +#define REG_ADV_CONFIG 0x0b +#define REG_BUSY_STATUS 0x0c + +/* Channel Reading Registers (16-bit, read-only) */ +#define REG_CH_READING_BASE 0x20 + +/* Limit Registers (8-bit, read/write) */ +#define REG_LIMIT_BASE 0x2a + +/* ID Registers (read-only) */ +#define REG_MANUFACTURER_ID 0x3e +#define REG_REVISION_ID 0x3f + +/* Configuration Register (0x00) bitfields */ +#define CONFIG_START BIT(0) +#define CONFIG_INT_ENABLE BIT(1) +#define CONFIG_INT_CLEAR BIT(3) +#define CONFIG_INITIALIZATION BIT(7) + +/* Advanced Configuration Register (0x0b) bitfields */ +#define ADV_CONFIG_EXT_REF_EN BIT(0) +#define ADV_CONFIG_MODE_1 (1 << 1) +#define ADV_CONFIG_MODE_2 (2 << 1) +#define ADV_CONFIG_MODE_3 (3 << 1) + +/* Number of channels */ +#define NUM_CHANNELS 8 + +/* Internal VREF in mV */ +#define INTERNAL_VREF_MV 2560 + +/* QMP helpers for setting device properties */ + +static void qmp_adc128d818_set(const char *property, int value) +{ + QDict *resp; + + resp = qmp("{ 'execute': 'qom-set', 'arguments':" + " { 'path': %s, 'property': %s, 'value': %d } }", + ADC128D818_TEST_ID, property, value); + g_assert(qdict_haskey(resp, "return")); + qobject_unref(resp); +} + +static int qmp_adc128d818_get(const char *property) +{ + QDict *resp; + int ret; + + resp = qmp("{ 'execute': 'qom-get', 'arguments':" + " { 'path': %s, 'property': %s } }", + ADC128D818_TEST_ID, property); + g_assert(qdict_haskey(resp, "return")); + ret = qdict_get_int(resp, "return"); + qobject_unref(resp); + return ret; +} + +/* Manufacturer and Revision ID registers */ +static void test_id_registers(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(dev, REG_MANUFACTURER_ID), ==, 0x01); + g_assert_cmphex(i2c_get8(dev, REG_REVISION_ID), ==, 0x09); +} + +/* Power-on-reset default values */ +static void test_defaults(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + unsigned ch; + + g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08); + g_assert_cmphex(i2c_get8(dev, REG_INT_STATUS), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_ADV_CONFIG), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02); + + for (ch = 0u; ch < NUM_CHANNELS; ch++) { + g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u + 1u), ==, 0x00); + } +} + +/* Software reset via INITIALIZATION bit */ +static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, REG_INT_MASK, 0xAA); + i2c_set8(dev, REG_CH_DISABLE, 0x55); + i2c_set8(dev, REG_LIMIT_BASE, 0x42); + + g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0xAA); + g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x55); + g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x42); + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08); + g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02); +} + +/* Verify ain property readback via QMP */ +static void test_ain_property(void *obj, void *data, QGuestAllocator *alloc) +{ + int value; + + qmp_adc128d818_set("ain3", 1500); + value = qmp_adc128d818_get("ain3"); + g_test_message("Set ain3 = 1500 mV, readback = %d mV", value); + g_assert_cmpint(value, ==, 1500); + + qmp_adc128d818_set("temperature", 37500); + value = qmp_adc128d818_get("temperature"); + g_test_message("Set temperature = 37500 mC, readback = %d mC", value); + g_assert_cmpint(value, ==, 37500); +} + +/* Voltage conversion */ +static void test_voltage_conversion(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + qmp_adc128d818_set("ain0", 1280); + g_test_message("Injected ain0 = 1280 mV"); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Read ch0: raw 0x%04x -> %u mV", reading, + (reading >> 4u) * INTERNAL_VREF_MV / 4096u); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("ain1", 2560); + g_test_message("Injected ain1 = 2560 mV"); + reading = i2c_get16(dev, REG_CH_READING_BASE + 1u); + g_test_message("Read ch1: raw 0x%04x -> %u mV", reading, + (reading >> 4u) * INTERNAL_VREF_MV / 4096u); + g_assert_cmphex(reading, ==, 0xFFF0); + + qmp_adc128d818_set("ain2", 0); + g_test_message("Injected ain2 = 0 mV"); + reading = i2c_get16(dev, REG_CH_READING_BASE + 2u); + g_test_message("Read ch2: raw 0x%04x -> %u mV", reading, + (reading >> 4u) * INTERNAL_VREF_MV / 4096u); + g_assert_cmphex(reading, ==, 0x0000); +} + +/* Temperature conversion (mode 0, channel 7 = temperature) */ +static void +test_temperature_conversion(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + qmp_adc128d818_set("temperature", 25000); + g_test_message("Injected temperature = 25000 mC (25.0 deg C)"); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("Read ch7: raw 0x%04x -> %d mC", reading, + (int16_t)(reading & 0xFF80u) * 500 / 128); + g_assert_cmphex(reading, ==, 0x1900); +} + +/* Channels with distinct voltages */ +static void test_all_channels(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + static const uint16_t ain_mv[NUM_CHANNELS] = { + 0, 320, 640, 960, 1280, 1920, 2240, 2560 + }; + static const uint16_t expect[NUM_CHANNELS] = { + 0x0000, 0x2000, 0x4000, 0x6000, 0x8000, 0xC000, 0xE000, 0xFFF0 + }; + uint16_t reading; + unsigned ch; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1); + + for (ch = 0u; ch < NUM_CHANNELS; ch++) { + char name[8]; + snprintf(name, sizeof(name), "ain%u", ch); + qmp_adc128d818_set(name, ain_mv[ch]); + } + + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + for (ch = 0u; ch < NUM_CHANNELS; ch++) { + reading = i2c_get16(dev, REG_CH_READING_BASE + ch); + g_test_message("ch%u: ain %u mV -> raw 0x%04x (expect 0x%04x)", + ch, ain_mv[ch], reading, expect[ch]); + g_assert_cmphex(reading, ==, expect[ch]); + } +} + +/* Voltage conversion edge cases */ +static void test_voltage_edges(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1); + + qmp_adc128d818_set("ain0", 3000); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Over-range 3000 mV: raw 0x%04x (expect 0xFFF0)", reading); + g_assert_cmphex(reading, ==, 0xFFF0); + + qmp_adc128d818_set("ain1", 1); + reading = i2c_get16(dev, REG_CH_READING_BASE + 1u); + g_test_message("1 mV: raw 0x%04x (expect 0x0010)", reading); + g_assert_cmphex(reading, ==, 0x0010); + + qmp_adc128d818_set("ain2", 640); + reading = i2c_get16(dev, REG_CH_READING_BASE + 2u); + g_test_message("640 mV (quarter): raw 0x%04x (expect 0x4000)", reading); + g_assert_cmphex(reading, ==, 0x4000); + + qmp_adc128d818_set("ain3", 1920); + reading = i2c_get16(dev, REG_CH_READING_BASE + 3u); + g_test_message("1920 mV (3/4): raw 0x%04x (expect 0xC000)", reading); + g_assert_cmphex(reading, ==, 0xC000); +} + +/* Temperature conversion edge cases */ +static void test_temperature_edges(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + qmp_adc128d818_set("temperature", 0); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("0 C: raw 0x%04x (expect 0x0000)", reading); + g_assert_cmphex(reading, ==, 0x0000); + + qmp_adc128d818_set("temperature", -25000); + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("-25 C: raw 0x%04x (expect 0xE700)", reading); + g_assert_cmphex(reading, ==, 0xE700); + + qmp_adc128d818_set("temperature", 127500); + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("+127.5 C: raw 0x%04x (expect 0x7F80)", reading); + g_assert_cmphex(reading, ==, 0x7F80); + + qmp_adc128d818_set("temperature", -128000); + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("-128 C: raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("temperature", 200000); + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("200 C (clamped): raw 0x%04x (expect 0x7F80)", reading); + g_assert_cmphex(reading, ==, 0x7F80); + + qmp_adc128d818_set("temperature", -200000); + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("-200 C (clamped): raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); +} + +/* External voltage reference */ +static void test_ext_vref(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1); + + qmp_adc128d818_set("ext-vref-mv", 4096); + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_1); + + qmp_adc128d818_set("ain0", 1000); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("1000 mV / 4096 mV VREF: raw 0x%04x (expect 0x3E80)", + reading); + g_assert_cmphex(reading, ==, 0x3E80); + + qmp_adc128d818_set("ain1", 2048); + reading = i2c_get16(dev, REG_CH_READING_BASE + 1u); + g_test_message("2048 mV / 4096 mV VREF: raw 0x%04x (expect 0x8000)", + reading); + g_assert_cmphex(reading, ==, 0x8000); +} + +/* Interrupt status set on limit violation; persists while fault remains */ +static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t status; + + i2c_set8(dev, REG_LIMIT_BASE, 0x10); + g_test_message("Set ch0 high limit = 0x10"); + + qmp_adc128d818_set("ain0", 2560); + g_test_message("Injected ain0 = 2560 mV (exceeds limit)"); + + i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE); + + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("INT_STATUS = 0x%02x (expect bit 0 set)", status); + g_assert_cmphex(status & 0x01u, ==, 0x01); + + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("INT_STATUS after re-read = 0x%02x (expect bit 0 still set)", + status); + g_assert_cmphex(status & 0x01u, ==, 0x01); + + qmp_adc128d818_set("ain0", 80); + g_test_message("Injected ain0 = 80 mV (within limit)"); + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("INT_STATUS after fault cleared = 0x%02x " + "(expect bit 0 clear)", status); + g_assert_cmphex(status & 0x01u, ==, 0x00); +} + +/* INT_CLEAR stops the round-robin monitoring loop */ +static void test_int_clear(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t status; + + i2c_set8(dev, REG_LIMIT_BASE, 0x10); + qmp_adc128d818_set("ain0", 2560); + + i2c_set8(dev, REG_CONFIG, + CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR); + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("INT_STATUS with INT_CLEAR set = 0x%02x (expect 0x00)", + status); + g_assert_cmphex(status, ==, 0x00); + + i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE); + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("INT_STATUS after INT_CLEAR cleared = 0x%02x (expect bit 0)", + status); + g_assert_cmphex(status & 0x01u, ==, 0x01); +} + +/* Low-limit interrupt triggers correctly */ +static void test_low_limit(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t status; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + i2c_set8(dev, REG_LIMIT_BASE + 3u, 0x80); + g_test_message("Set ch1 low limit = 0x80"); + + i2c_set8(dev, REG_LIMIT_BASE + 5u, 0x80); + g_test_message("Set ch2 low limit = 0x80"); + + qmp_adc128d818_set("ain1", 640); + qmp_adc128d818_set("ain2", 1280); + qmp_adc128d818_set("ain0", 1280); + i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE); + + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("INT_STATUS = 0x%02x (expect bits 1 and 2 set)", status); + g_assert_cmphex(status & 0x02u, ==, 0x02); + g_assert_cmphex(status & 0x04u, ==, 0x04); + + g_assert_cmphex(status & 0x01u, ==, 0x00); +} + +/* Temperature high-limit interrupt with hysteresis */ +static void test_temp_hysteresis(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t status; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u, 0x32); + i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u + 1u, 0x28); + + qmp_adc128d818_set("temperature", 25000); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("25 C: INT_STATUS = 0x%02x (temp bit expect clear)", status); + g_assert_cmphex(status & 0x80u, ==, 0x00); + + qmp_adc128d818_set("temperature", 55000); + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("55 C: INT_STATUS = 0x%02x (temp bit expect set)", status); + g_assert_cmphex(status & 0x80u, ==, 0x80); + + qmp_adc128d818_set("temperature", 45000); + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("45 C (hysteresis): INT_STATUS = 0x%02x " + "(temp bit expect set)", status); + g_assert_cmphex(status & 0x80u, ==, 0x80); + + qmp_adc128d818_set("temperature", 35000); + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("35 C: INT_STATUS = 0x%02x (temp bit expect clear)", status); + g_assert_cmphex(status & 0x80u, ==, 0x00); +} + +/* Channel disable prevents conversion */ +static void test_channel_disable(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CH_DISABLE, 0x01); + g_test_message("Disabled channel 0"); + + qmp_adc128d818_set("ain0", 1280); + g_test_message("Injected ain0 = 1280 mV (disabled)"); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Read ch0 (disabled): raw 0x%04x", reading); + g_assert_cmphex(reading, ==, 0x0000); + + qmp_adc128d818_set("ain1", 1280); + g_test_message("Injected ain1 = 1280 mV (enabled)"); + reading = i2c_get16(dev, REG_CH_READING_BASE + 1u); + g_test_message("Read ch1 (enabled): raw 0x%04x -> %u mV", reading, + (reading >> 4u) * INTERNAL_VREF_MV / 4096u); + g_assert_cmphex(reading, ==, 0x8000); +} + +/* One-shot conversion in shutdown mode */ +static void test_one_shot(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + qmp_adc128d818_set("ain0", 1280); + g_test_message("Injected ain0 = 1280 mV (device stopped)"); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Read ch0 before one-shot: raw 0x%04x", reading); + g_assert_cmphex(reading, ==, 0x0000); + + g_assert_cmphex(i2c_get8(dev, REG_ONE_SHOT), ==, 0x00); + + i2c_set8(dev, REG_ONE_SHOT, 0x00); + g_test_message("Triggered one-shot conversion with value 0x00"); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Read ch0 after one-shot: raw 0x%04x -> %u mV", reading, + (reading >> 4u) * INTERNAL_VREF_MV / 4096u); + g_assert_cmphex(reading, ==, 0x8000); +} + +/* Mode 1 makes channel 7 a voltage input instead of temperature */ +static void test_mode_selection(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1); + g_test_message("Set mode 1 (all voltage channels)"); + + qmp_adc128d818_set("ain7", 1280); + qmp_adc128d818_set("temperature", 50000); + g_test_message("Injected ain7 = 1280 mV, temperature = 50000 mC"); + + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("Read ch7 (mode 1): raw 0x%04x -> %u mV", reading, + (reading >> 4u) * INTERNAL_VREF_MV / 4096u); + g_assert_cmphex(reading, ==, 0x8000); +} + +/* Mode 2 - 4 pseudo-differential pairs */ +static void test_mode2_diff(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2); + g_test_message("Set mode 2 (4 pseudo-differential pairs)"); + + qmp_adc128d818_set("ain0", 2000); + qmp_adc128d818_set("ain1", 720); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Pair 0 (IN0-IN1): raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("ain3", 1920); + qmp_adc128d818_set("ain2", 640); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 1u); + g_test_message("Pair 1 (IN3-IN2): raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("ain4", 1500); + qmp_adc128d818_set("ain5", 220); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 2u); + g_test_message("Pair 2 (IN4-IN5): raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("ain7", 2560); + qmp_adc128d818_set("ain6", 1280); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 3u); + g_test_message("Pair 3 (IN7-IN6): raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 4u); + g_test_message("Reserved ch4: raw 0x%04x (expect 0x0000)", reading); + g_assert_cmphex(reading, ==, 0x0000); + + qmp_adc128d818_set("ain0", 500); + qmp_adc128d818_set("ain1", 1000); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Pair 0 negative dV: raw 0x%04x (expect 0x0000)", reading); + g_assert_cmphex(reading, ==, 0x0000); +} + +/* Mode 3 - 4 single-ended + 2 pseudo-differential pairs */ +static void test_mode3_mixed(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_3); + g_test_message("Set mode 3 (4 single-ended + 2 differential)"); + + qmp_adc128d818_set("ain0", 1280); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Ch0 single-ended: raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("ain4", 1500); + qmp_adc128d818_set("ain5", 220); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 4u); + g_test_message("Ch4 diff (IN4-IN5): raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("ain7", 2560); + qmp_adc128d818_set("ain6", 1280); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 5u); + g_test_message("Ch5 diff (IN7-IN6): raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 6u); + g_test_message("Reserved ch6: raw 0x%04x (expect 0x0000)", reading); + g_assert_cmphex(reading, ==, 0x0000); + + qmp_adc128d818_set("temperature", 25000); + + reading = i2c_get16(dev, REG_CH_READING_BASE + 7u); + g_test_message("Ch7 temperature: raw 0x%04x (expect 0x1900)", reading); + g_assert_cmphex(reading, ==, 0x1900); +} + +/* Mode change resets channel readings and interrupt status */ +static void test_mode_change_reset(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + uint8_t status; + + qmp_adc128d818_set("ain0", 1280); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Before mode change, ch0: raw 0x%04x", reading); + g_assert_cmphex(reading, !=, 0x0000); + + i2c_set8(dev, REG_CONFIG, 0x00); + i2c_set8(dev, REG_LIMIT_BASE, 0x10); + qmp_adc128d818_set("ain0", 2560); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + i2c_set8(dev, REG_CONFIG, 0x00); + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("After mode change, ch0: raw 0x%04x (expect 0x0000)", + reading); + g_assert_cmphex(reading, ==, 0x0000); + + status = i2c_get8(dev, REG_INT_STATUS); + g_test_message("After mode change, INT_STATUS: 0x%02x (expect 0x00)", + status); + g_assert_cmphex(status, ==, 0x00); + + g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x10); + g_test_message("Limit register preserved after mode change"); +} + +/* QOM property changes trigger correct differential conversion */ +static void test_diff_qom_trigger(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2); + + qmp_adc128d818_set("ain0", 0); + qmp_adc128d818_set("ain1", 0); + qmp_adc128d818_set("ain2", 0); + qmp_adc128d818_set("ain3", 0); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + qmp_adc128d818_set("ain0", 2000); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("After ain0=2000, ain1=0: pair0 = 0x%04x (expect 0xC800)", + reading); + g_assert_cmphex(reading, ==, 0xC800); + + qmp_adc128d818_set("ain1", 720); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("After ain1=720: pair0 = 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); + + qmp_adc128d818_set("ain3", 1920); + qmp_adc128d818_set("ain2", 640); + reading = i2c_get16(dev, REG_CH_READING_BASE + 1u); + g_test_message("Pair 1 (IN3-IN2) via QOM: 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); +} + +/* One-shot conversion works in deep shutdown */ +static void test_deep_shutdown(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + qmp_adc128d818_set("ain0", 1280); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_assert_cmphex(reading, ==, 0x8000); + + i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01); + g_test_message("DEEP_SHUTDOWN write while running rejected"); + g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00); + + i2c_set8(dev, REG_CONFIG, 0x00); + i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01); + qmp_adc128d818_set("ain0", 0); + + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Deep shutdown, no one-shot: raw 0x%04x (expect 0x8000)", + reading); + g_assert_cmphex(reading, ==, 0x8000); + + i2c_set8(dev, REG_ONE_SHOT, 0x01); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("Deep shutdown one-shot: raw 0x%04x (expect 0x0000)", + reading); + g_assert_cmphex(reading, ==, 0x0000); + + g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x01); + + i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x00); + qmp_adc128d818_set("ain0", 1280); + i2c_set8(dev, REG_ONE_SHOT, 0x01); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("After exit shutdown: raw 0x%04x (expect 0x8000)", reading); + g_assert_cmphex(reading, ==, 0x8000); +} + +/* BUSY_STATUS NOT_READY clears after first conversion */ +static void test_busy_status(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x02); + g_test_message("After reset: BUSY_STATUS = 0x%02x (NOT_READY set)", + i2c_get8(dev, REG_BUSY_STATUS)); + + qmp_adc128d818_set("ain0", 0); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + + g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x00); + g_test_message("After conversion: BUSY_STATUS = 0x%02x (NOT_READY cleared)", + i2c_get8(dev, REG_BUSY_STATUS)); +} + +/* Programming Channel Disable resets channel readings */ +static void test_chan_disable_clears(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + qmp_adc128d818_set("ain0", 1280); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000); + + i2c_set8(dev, REG_CONFIG, 0x00); + i2c_set8(dev, REG_CH_DISABLE, 0x02); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("After CH_DISABLE write: ch0 raw 0x%04x (expect 0x0000)", + reading); + g_assert_cmphex(reading, ==, 0x0000); +} + +/* Programming Advanced Configuration always resets channel readings */ +static void test_adv_config_clears(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint16_t reading; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + qmp_adc128d818_set("ain0", 1280); + i2c_set8(dev, REG_CONFIG, CONFIG_START); + g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000); + + i2c_set8(dev, REG_CONFIG, 0x00); + i2c_set8(dev, REG_ADV_CONFIG, 0x00); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("After same-mode ADV_CONFIG: ch0 0x%04x (expect 0x0000)", + reading); + g_assert_cmphex(reading, ==, 0x0000); + + i2c_set8(dev, REG_CONFIG, CONFIG_START); + g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000); + i2c_set8(dev, REG_CONFIG, 0x00); + qmp_adc128d818_set("ext-vref-mv", 4096); + i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN); + reading = i2c_get16(dev, REG_CH_READING_BASE); + g_test_message("After ext-vref toggle: ch0 0x%04x (expect 0x0000)", + reading); + g_assert_cmphex(reading, ==, 0x0000); +} + +/* Conversion Rate register may only be programmed while in shutdown */ +static void test_conv_rate(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION); + + i2c_set8(dev, REG_CONV_RATE, 0x01); + g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01); + + i2c_set8(dev, REG_CONFIG, CONFIG_START); + i2c_set8(dev, REG_CONV_RATE, 0x00); + g_test_message("CONV_RATE while running: 0x%02x (expect unchanged 0x01)", + i2c_get8(dev, REG_CONV_RATE)); + g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01); +} + +static void adc128d818_register_nodes(void) +{ + QOSGraphEdgeOptions opts = { + .extra_device_opts = "id=" ADC128D818_TEST_ID + ",address=0x1f" + }; + add_qi2c_address(&opts, &(QI2CAddress) { ADC128D818_TEST_ADDR }); + + qos_node_create_driver("adc128d818", i2c_device_create); + qos_node_consumes("adc128d818", "i2c-bus", &opts); + + qos_add_test("id-registers", "adc128d818", test_id_registers, NULL); + qos_add_test("defaults", "adc128d818", test_defaults, NULL); + qos_add_test("soft-reset", "adc128d818", test_soft_reset, NULL); + qos_add_test("ain-property", "adc128d818", test_ain_property, NULL); + qos_add_test("voltage-conversion", "adc128d818", test_voltage_conversion, + NULL); + qos_add_test("temperature-conversion", "adc128d818", + test_temperature_conversion, NULL); + qos_add_test("all-channels", "adc128d818", test_all_channels, NULL); + qos_add_test("voltage-edges", "adc128d818", test_voltage_edges, NULL); + qos_add_test("temperature-edges", "adc128d818", test_temperature_edges, + NULL); + qos_add_test("ext-vref", "adc128d818", test_ext_vref, NULL); + qos_add_test("interrupt-status", "adc128d818", test_interrupt_status, NULL); + qos_add_test("int-clear", "adc128d818", test_int_clear, NULL); + qos_add_test("low-limit", "adc128d818", test_low_limit, NULL); + qos_add_test("temp-hysteresis", "adc128d818", test_temp_hysteresis, NULL); + qos_add_test("channel-disable", "adc128d818", test_channel_disable, NULL); + qos_add_test("one-shot", "adc128d818", test_one_shot, NULL); + qos_add_test("mode-selection", "adc128d818", test_mode_selection, NULL); + qos_add_test("mode2-diff", "adc128d818", test_mode2_diff, NULL); + qos_add_test("mode3-mixed", "adc128d818", test_mode3_mixed, NULL); + qos_add_test("mode-change-reset", "adc128d818", test_mode_change_reset, + NULL); + qos_add_test("diff-qom-trigger", "adc128d818", test_diff_qom_trigger, NULL); + qos_add_test("deep-shutdown", "adc128d818", test_deep_shutdown, NULL); + qos_add_test("busy-status", "adc128d818", test_busy_status, NULL); + qos_add_test("chan-disable-clears", "adc128d818", test_chan_disable_clears, + NULL); + qos_add_test("adv-config-clears", "adc128d818", test_adv_config_clears, + NULL); + qos_add_test("conv-rate", "adc128d818", test_conv_rate, NULL); +} +libqos_init(adc128d818_register_nodes);
diff --git a/tests/qtest/aspeed-hace-utils.c b/tests/qtest/aspeed-hace-utils.c index 25450a2..260eec0 100644 --- a/tests/qtest/aspeed-hace-utils.c +++ b/tests/qtest/aspeed-hace-utils.c
@@ -9,6 +9,7 @@ #include "libqtest.h" #include "qemu/bitops.h" #include "qemu/bswap.h" +#include "crypto/cipher.h" #include "aspeed-hace-utils.h" /* @@ -645,3 +646,695 @@ qtest_quit(s); } +/* + * Crypto engine register layout (offsets from the HACE base). + */ +#define HACE_CRYPTO_SRC 0x00 +#define HACE_CRYPTO_DEST 0x04 +#define HACE_CRYPTO_CONTEXT 0x08 +#define HACE_CRYPTO_DATA_LEN 0x0c +#define HACE_CRYPTO_CMD 0x10 +#define HACE_CRYPTO_GCM_ADD_LEN 0x14 +#define HACE_CRYPTO_GCM_TAG 0x18 + +/* Crypto command bits */ +#define HACE_CMD_ENCRYPT BIT(7) +#define HACE_CMD_ISR_EN BIT(12) +#define HACE_CMD_DES_SELECT BIT(16) +#define HACE_CMD_TRIPLE_DES BIT(17) +#define HACE_CMD_SRC_SG_CTRL BIT(18) +#define HACE_CMD_DST_SG_CTRL BIT(19) +#define HACE_CMD_OP_MODE_MASK (0x7 << 4) +#define HACE_CMD_ECB (0x0 << 4) +#define HACE_CMD_CBC (0x1 << 4) +#define HACE_CMD_CTR (0x4 << 4) +#define HACE_CMD_GCM (0x5 << 4) +#define HACE_CMD_AES128 (0x0 << 2) +#define HACE_CMD_AES256 (0x2 << 2) + +/* Context buffer layout: IV (DES at +8), key at +0x10 */ +#define HACE_CTX_KEY_OFFSET 0x10 +#define HACE_CTX_SIZE 0x30 + +/* + * Crypto known-answer test vectors, taken verbatim from the Linux kernel + * crypto self-test templates in crypto/testmgr.h: + * + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/crypto/testmgr.h?h=v6.18 + * + * The originating template is noted above each block. CTR and the longer CBC + * vectors are truncated to a single block (still a valid known-answer test as + * the first block only depends on the IV). + */ + +/* aes_tv_template[0] (FIPS-197) */ +static const uint8_t aes128_ecb_key[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; +static const uint8_t aes128_ecb_ptext[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; +static const uint8_t aes128_ecb_ctext[16] = { + 0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, + 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a }; + +/* aes_cbc_tv_template[0] (RFC 3602) */ +static const uint8_t aes128_cbc_key[16] = { + 0x06, 0xa9, 0x21, 0x40, 0x36, 0xb8, 0xa1, 0x5b, + 0x51, 0x2e, 0x03, 0xd5, 0x34, 0x12, 0x00, 0x06 }; +static const uint8_t aes128_cbc_iv[16] = { + 0x3d, 0xaf, 0xba, 0x42, 0x9d, 0x9e, 0xb4, 0x30, + 0xb4, 0x22, 0xda, 0x80, 0x2c, 0x9f, 0xac, 0x41 }; +static const uint8_t aes128_cbc_ptext[16] = { + 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 }; +static const uint8_t aes128_cbc_ctext[16] = { + 0xe3, 0x53, 0x77, 0x9c, 0x10, 0x79, 0xae, 0xb8, + 0x27, 0x08, 0x94, 0x2d, 0xbe, 0x77, 0x18, 0x1a }; +static const uint8_t aes128_cbc_ivout[16] = { + 0xe3, 0x53, 0x77, 0x9c, 0x10, 0x79, 0xae, 0xb8, + 0x27, 0x08, 0x94, 0x2d, 0xbe, 0x77, 0x18, 0x1a }; + +/* des_tv_template[0] (Applied Cryptography) */ +static const uint8_t des_ecb_key[8] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef }; +static const uint8_t des_ecb_ptext[8] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xe7 }; +static const uint8_t des_ecb_ctext[8] = { + 0xc9, 0x57, 0x44, 0x25, 0x6a, 0x5e, 0xd3, 0x1d }; + +/* des_cbc_tv_template[0] (OpenSSL), first block */ +static const uint8_t des_cbc_key[8] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef }; +static const uint8_t des_cbc_iv[8] = { + 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 }; +static const uint8_t des_cbc_ptext[8] = { + 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x20 }; +static const uint8_t des_cbc_ctext[8] = { + 0xcc, 0xd1, 0x73, 0xff, 0xab, 0x20, 0x39, 0xf4 }; + +/* des3_ede_tv_template[0] (OpenSSL) */ +static const uint8_t tdes_ecb_key[24] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 }; +static const uint8_t tdes_ecb_ptext[8] = { + 0x73, 0x6f, 0x6d, 0x65, 0x64, 0x61, 0x74, 0x61 }; +static const uint8_t tdes_ecb_ctext[8] = { + 0x18, 0xd7, 0x48, 0xe5, 0x63, 0x62, 0x05, 0x72 }; + +/* des3_ede_cbc_tv_template[0] (OpenSSL), first block */ +static const uint8_t tdes_cbc_key[24] = { + 0xe9, 0xc0, 0xff, 0x2e, 0x76, 0x0b, 0x64, 0x24, + 0x44, 0x4d, 0x99, 0x5a, 0x12, 0xd6, 0x40, 0xc0, + 0xea, 0xc2, 0x84, 0xe8, 0x14, 0x95, 0xdb, 0xe8 }; +static const uint8_t tdes_cbc_iv[8] = { + 0x7d, 0x33, 0x88, 0x93, 0x0f, 0x93, 0xb2, 0x42 }; +static const uint8_t tdes_cbc_ptext[8] = { + 0x6f, 0x54, 0x20, 0x6f, 0x61, 0x4d, 0x79, 0x6e }; +static const uint8_t tdes_cbc_ctext[8] = { + 0x0e, 0x2d, 0xb6, 0x97, 0x3c, 0x56, 0x33, 0xf4 }; + +/* aes_ctr_tv_template[0] (NIST SP800-38A F.5.1), first block */ +static const uint8_t aes128_ctr_key[16] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; +static const uint8_t aes128_ctr_iv[16] = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; +static const uint8_t aes128_ctr_ptext[16] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a }; +static const uint8_t aes128_ctr_ctext[16] = { + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce }; +static const uint8_t aes128_ctr_ivout[16] = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xff, 0x00 }; + +/* des_ctr_tv_template[0] (Crypto++), first block */ +static const uint8_t des_ctr_key[8] = { + 0xc9, 0x83, 0xa6, 0xc9, 0xec, 0x0f, 0x32, 0x55 }; +static const uint8_t des_ctr_iv[8] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd }; +static const uint8_t des_ctr_ptext[8] = { + 0x50, 0xb9, 0x22, 0xae, 0x17, 0x80, 0x0c, 0x75 }; +static const uint8_t des_ctr_ctext[8] = { + 0x2f, 0x96, 0x06, 0x0f, 0x50, 0xc9, 0x68, 0x03 }; +static const uint8_t des_ctr_ivout[8] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe }; + +/* des3_ede_ctr_tv_template[0] (Crypto++), first block */ +static const uint8_t tdes_ctr_key[24] = { + 0x9c, 0xd6, 0xf3, 0x9c, 0xb9, 0x5a, 0x67, 0x00, + 0x5a, 0x67, 0x00, 0x2d, 0xce, 0xeb, 0x2d, 0xce, + 0xeb, 0xb4, 0x51, 0x72, 0xb4, 0x51, 0x72, 0x1f }; +static const uint8_t tdes_ctr_iv[8] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; +static const uint8_t tdes_ctr_ptext[8] = { + 0x05, 0xec, 0x77, 0xfb, 0x42, 0xd5, 0x59, 0x20 }; +static const uint8_t tdes_ctr_ctext[8] = { + 0x07, 0xc2, 0x08, 0x20, 0x72, 0x1f, 0x49, 0xef }; +static const uint8_t tdes_ctr_ivout[8] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* + * aes_gcm_tv_template[2] (AES-128) and [9] (AES-256), from the McGrew & Viega + * GCM spec (also NIST SP 800-38D), no AAD. Both cases share this plaintext/IV. + */ +static const uint8_t aes_gcm_ptext[64] = { + 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, + 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, + 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, + 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, + 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, + 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, + 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, + 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 }; +static const uint8_t aes_gcm_iv[12] = { + 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, + 0xde, 0xca, 0xf8, 0x88 }; + +/* aes_gcm_tv_template[2] (AES-128) */ +static const uint8_t aes128_gcm_key[16] = { + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 }; +static const uint8_t aes128_gcm_ctext[64] = { + 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, + 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, + 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, + 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, + 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, + 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, + 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, + 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; +static const uint8_t aes128_gcm_tag[16] = { + 0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6, + 0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4 }; + +/* aes_gcm_tv_template[9] (AES-256) */ +static const uint8_t aes256_gcm_key[32] = { + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08, + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 }; +static const uint8_t aes256_gcm_ctext[64] = { + 0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07, + 0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d, + 0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9, + 0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa, + 0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d, + 0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38, + 0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a, + 0xbc, 0xc9, 0xf6, 0x62, 0x89, 0x80, 0x15, 0xad }; +static const uint8_t aes256_gcm_tag[16] = { + 0xb0, 0x94, 0xda, 0xc5, 0xd9, 0x34, 0x71, 0xbd, + 0xec, 0x1a, 0x50, 0x22, 0x70, 0xe3, 0xcc, 0x6c }; + +typedef struct CryptTest { + QCryptoCipherMode mode; + QCryptoCipherAlgo alg; + /* expected context IV after encrypt, or NULL */ + const uint8_t *iv_out; + const uint8_t *ptext; + const uint8_t *ctext; + /* expected GCM authentication tag, or NULL for non-AEAD modes */ + const uint8_t *tag; + const uint8_t *key; + const uint8_t *iv; + const char *name; + size_t keylen; + size_t taglen; + /* algorithm | mode | key size selection */ + uint32_t cmd; + size_t ivlen; + size_t len; +} CryptTest; + +static const CryptTest crypt_tests[] = { + { + .name = "aes128-ecb", + .cmd = HACE_CMD_AES128 | HACE_CMD_ECB, + .alg = QCRYPTO_CIPHER_ALGO_AES_128, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = aes128_ecb_key, + .keylen = sizeof(aes128_ecb_key), + .ptext = aes128_ecb_ptext, + .ctext = aes128_ecb_ctext, + .len = sizeof(aes128_ecb_ptext), + }, + { + .name = "aes128-cbc", + .cmd = HACE_CMD_AES128 | HACE_CMD_CBC, + .alg = QCRYPTO_CIPHER_ALGO_AES_128, + .mode = QCRYPTO_CIPHER_MODE_CBC, + .key = aes128_cbc_key, + .keylen = sizeof(aes128_cbc_key), + .iv = aes128_cbc_iv, + .ivlen = sizeof(aes128_cbc_iv), + .ptext = aes128_cbc_ptext, + .ctext = aes128_cbc_ctext, + .iv_out = aes128_cbc_ivout, + .len = sizeof(aes128_cbc_ptext), + }, + { + .name = "des-ecb", + .cmd = HACE_CMD_DES_SELECT | HACE_CMD_ECB, + .alg = QCRYPTO_CIPHER_ALGO_DES, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = des_ecb_key, + .keylen = sizeof(des_ecb_key), + .ptext = des_ecb_ptext, + .ctext = des_ecb_ctext, + .len = sizeof(des_ecb_ptext), + }, + { + .name = "des-cbc", + .cmd = HACE_CMD_DES_SELECT | HACE_CMD_CBC, + .alg = QCRYPTO_CIPHER_ALGO_DES, + .mode = QCRYPTO_CIPHER_MODE_CBC, + .key = des_cbc_key, + .keylen = sizeof(des_cbc_key), + .iv = des_cbc_iv, + .ivlen = sizeof(des_cbc_iv), + .ptext = des_cbc_ptext, + .ctext = des_cbc_ctext, + .len = sizeof(des_cbc_ptext), + }, + { + .name = "des3_ede-ecb", + .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_ECB, + .alg = QCRYPTO_CIPHER_ALGO_3DES, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = tdes_ecb_key, + .keylen = sizeof(tdes_ecb_key), + .ptext = tdes_ecb_ptext, + .ctext = tdes_ecb_ctext, + .len = sizeof(tdes_ecb_ptext), + }, + { + .name = "des3_ede-cbc", + .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_CBC, + .alg = QCRYPTO_CIPHER_ALGO_3DES, + .mode = QCRYPTO_CIPHER_MODE_CBC, + .key = tdes_cbc_key, + .keylen = sizeof(tdes_cbc_key), + .iv = tdes_cbc_iv, + .ivlen = sizeof(tdes_cbc_iv), + .ptext = tdes_cbc_ptext, + .ctext = tdes_cbc_ctext, + .len = sizeof(tdes_cbc_ptext), + }, + { + .name = "aes128-ctr", + .cmd = HACE_CMD_AES128 | HACE_CMD_CTR, + .alg = QCRYPTO_CIPHER_ALGO_AES_128, + .mode = QCRYPTO_CIPHER_MODE_CTR, + .key = aes128_ctr_key, + .keylen = sizeof(aes128_ctr_key), + .iv = aes128_ctr_iv, + .ivlen = sizeof(aes128_ctr_iv), + .ptext = aes128_ctr_ptext, + .ctext = aes128_ctr_ctext, + .iv_out = aes128_ctr_ivout, + .len = sizeof(aes128_ctr_ptext), + }, + { + .name = "des-ctr", + .cmd = HACE_CMD_DES_SELECT | HACE_CMD_CTR, + .alg = QCRYPTO_CIPHER_ALGO_DES, + .mode = QCRYPTO_CIPHER_MODE_CTR, + .key = des_ctr_key, + .keylen = sizeof(des_ctr_key), + .iv = des_ctr_iv, + .ivlen = sizeof(des_ctr_iv), + .ptext = des_ctr_ptext, + .ctext = des_ctr_ctext, + .iv_out = des_ctr_ivout, + .len = sizeof(des_ctr_ptext), + }, + { + .name = "des3_ede-ctr", + .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_CTR, + .alg = QCRYPTO_CIPHER_ALGO_3DES, + .mode = QCRYPTO_CIPHER_MODE_CTR, + .key = tdes_ctr_key, + .keylen = sizeof(tdes_ctr_key), + .iv = tdes_ctr_iv, + .ivlen = sizeof(tdes_ctr_iv), + .ptext = tdes_ctr_ptext, + .ctext = tdes_ctr_ctext, + .iv_out = tdes_ctr_ivout, + .len = sizeof(tdes_ctr_ptext), + }, + { + .name = "aes128-gcm", + .cmd = HACE_CMD_AES128 | HACE_CMD_GCM, + .alg = QCRYPTO_CIPHER_ALGO_AES_128, + .mode = QCRYPTO_CIPHER_MODE_GCM, + .key = aes128_gcm_key, + .keylen = sizeof(aes128_gcm_key), + .iv = aes_gcm_iv, + .ivlen = sizeof(aes_gcm_iv), + .ptext = aes_gcm_ptext, + .ctext = aes128_gcm_ctext, + .tag = aes128_gcm_tag, + .taglen = sizeof(aes128_gcm_tag), + .len = sizeof(aes_gcm_ptext), + }, + { + .name = "aes256-gcm", + .cmd = HACE_CMD_AES256 | HACE_CMD_GCM, + .alg = QCRYPTO_CIPHER_ALGO_AES_256, + .mode = QCRYPTO_CIPHER_MODE_GCM, + .key = aes256_gcm_key, + .keylen = sizeof(aes256_gcm_key), + .iv = aes_gcm_iv, + .ivlen = sizeof(aes_gcm_iv), + .ptext = aes_gcm_ptext, + .ctext = aes256_gcm_ctext, + .tag = aes256_gcm_tag, + .taglen = sizeof(aes256_gcm_tag), + .len = sizeof(aes_gcm_ptext), + }, +}; + +/* DRAM offsets for the crypto test source, destination and context buffers. */ +#define CRYPT_OFF_SRC 0x10000 +#define CRYPT_OFF_DST 0x20000 +#define CRYPT_OFF_CTX 0x30000 +/* Scatter-gather list offsets (each list has CRYPT_SG_FRAGS entries). */ +#define CRYPT_OFF_SRC_SG 0x40000 +#define CRYPT_OFF_DST_SG 0x50000 +/* + * The scatter-gather tests split each buffer into CRYPT_SG_FRAGS fragments, + * each placed CRYPT_SG_FRAG_STRIDE apart so the fragments never abut. The gaps + * make the test fail if the engine ignores the list and reads one contiguous + * block. + */ +#define CRYPT_SG_FRAGS 3 +#define CRYPT_SG_FRAG_STRIDE 0x1000 +/* DRAM offset for the AES-GCM authentication tag write buffer. */ +#define CRYPT_OFF_TAG 0x60000 + +/* Describes one registered crypto test (qtest_add_data_func() data pointer). */ +typedef struct AspeedCryptoTest { + const char *machine; + uint64_t dram; + uint32_t base; + int index; + bool sg; +} AspeedCryptoTest; + +/* Map a command's operation mode (HACE10[6:4]) to a CRYPT_MODE_* flag. */ +static uint32_t crypt_mode_flag(uint32_t cmd) +{ + switch (cmd & HACE_CMD_OP_MODE_MASK) { + case HACE_CMD_ECB: + return CRYPT_MODE_ECB; + case HACE_CMD_CBC: + return CRYPT_MODE_CBC; + case HACE_CMD_CTR: + return CRYPT_MODE_CTR; + case HACE_CMD_GCM: + return CRYPT_MODE_GCM; + default: + return 0; + } +} + +static void crypt_write_ctx(QTestState *s, uint64_t ctx_addr, + const CryptTest *t) +{ + size_t iv_off = (t->cmd & HACE_CMD_DES_SELECT) ? 8 : 0; + uint8_t ctx[HACE_CTX_SIZE] = { 0 }; + + if (t->iv) { + memcpy(ctx + iv_off, t->iv, t->ivlen); + } + memcpy(ctx + HACE_CTX_KEY_OFFSET, t->key, t->keylen); + qtest_memwrite(s, ctx_addr, ctx, sizeof(ctx)); +} + +/* Run one crypto operation in direct access mode and read back the result. */ +static void crypt_run_direct(QTestState *s, uint32_t base, uint64_t dram, + const CryptTest *t, bool encrypt, uint8_t *out) +{ + const uint8_t *in = encrypt ? t->ptext : t->ctext; + uint32_t cmd = t->cmd | HACE_CMD_ISR_EN; + uint64_t src = dram + CRYPT_OFF_SRC; + uint64_t dst = dram + CRYPT_OFF_DST; + uint64_t ctx = dram + CRYPT_OFF_CTX; + + if (encrypt) { + cmd |= HACE_CMD_ENCRYPT; + } + + crypt_write_ctx(s, ctx, t); + qtest_memwrite(s, src, in, t->len); + + qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src); + qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst); + qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx); + qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len); + qtest_writel(s, base + HACE_CRYPTO_CMD, cmd); + + g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==, + HACE_CRYPTO_ISR); + qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR); + + qtest_memread(s, dst, out, t->len); +} + +/* + * Byte range [*frag_off, *frag_off + *frag_len) of fragment @index when an + * @len-byte buffer is split into CRYPT_SG_FRAGS pieces; the last piece takes + * the remainder of an uneven split. + */ +static void crypt_frag_range(uint32_t len, int index, + uint32_t *frag_off, uint32_t *frag_len) +{ + uint32_t base = len / CRYPT_SG_FRAGS; + + *frag_off = base * index; + *frag_len = (index == CRYPT_SG_FRAGS - 1) ? len - *frag_off : base; +} + +/* + * Scatter [in, len) across CRYPT_SG_FRAGS buffers based at @base_off and spaced + * CRYPT_SG_FRAG_STRIDE apart, then build the SG list describing them at @list. + * When @in is NULL only the list is built (used for the destination, which the + * engine fills in). + */ +static void crypt_make_sg(QTestState *s, uint64_t dram, uint32_t base_off, + uint64_t list, const uint8_t *in, uint32_t len) +{ + struct AspeedSgList sg[CRYPT_SG_FRAGS]; + uint32_t frag_off; + uint32_t frag_len; + uint64_t buf; + int i; + + for (i = 0; i < CRYPT_SG_FRAGS; i++) { + crypt_frag_range(len, i, &frag_off, &frag_len); + buf = dram + base_off + i * CRYPT_SG_FRAG_STRIDE; + + if (in) { + qtest_memwrite(s, buf, in + frag_off, frag_len); + } + sg[i].len = cpu_to_le32(frag_len | (i == CRYPT_SG_FRAGS - 1 ? + SG_LIST_LEN_LAST : 0)); + sg[i].addr = cpu_to_le32((uint32_t)buf); + } + + qtest_memwrite(s, list, sg, sizeof(sg)); +} + +/* Gather a scatter-gathered result back from the CRYPT_SG_FRAGS buffers. */ +static void crypt_gather_sg(QTestState *s, uint64_t dram, uint32_t base_off, + uint8_t *out, uint32_t len) +{ + uint32_t frag_off; + uint32_t frag_len; + int i; + + for (i = 0; i < CRYPT_SG_FRAGS; i++) { + crypt_frag_range(len, i, &frag_off, &frag_len); + qtest_memread(s, dram + base_off + i * CRYPT_SG_FRAG_STRIDE, + out + frag_off, frag_len); + } +} + +/* + * Run one block-cipher (ECB/CBC/CTR) operation in scatter-gather mode and read + * back the result. The source and destination are each split across + * CRYPT_SG_FRAGS non-adjacent DRAM buffers described by an SG list; the gaps + * ensure the test fails if the engine ignores the list and reads one + * contiguous block. + */ +static void crypt_run_sg(QTestState *s, uint32_t base, uint64_t dram, + const CryptTest *t, bool encrypt, uint8_t *out) +{ + const uint8_t *in = encrypt ? t->ptext : t->ctext; + uint64_t src_sg = dram + CRYPT_OFF_SRC_SG; + uint64_t dst_sg = dram + CRYPT_OFF_DST_SG; + uint64_t ctx = dram + CRYPT_OFF_CTX; + uint32_t cmd = t->cmd | HACE_CMD_ISR_EN | HACE_CMD_SRC_SG_CTRL | + HACE_CMD_DST_SG_CTRL; + + if (encrypt) { + cmd |= HACE_CMD_ENCRYPT; + } + + crypt_write_ctx(s, ctx, t); + crypt_make_sg(s, dram, CRYPT_OFF_SRC, src_sg, in, t->len); + crypt_make_sg(s, dram, CRYPT_OFF_DST, dst_sg, NULL, t->len); + + qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src_sg); + qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst_sg); + qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx); + qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len); + qtest_writel(s, base + HACE_CRYPTO_CMD, cmd); + + g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==, + HACE_CRYPTO_ISR); + qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR); + + crypt_gather_sg(s, dram, CRYPT_OFF_DST, out, t->len); +} + +/* + * Run one AES-GCM operation in scatter-gather mode: like crypt_run_sg() but + * also program the tag write buffer (HACE18) with no associated data, and read + * the authentication tag back into @out_tag. + */ +static void crypt_run_gcm(QTestState *s, uint32_t base, uint64_t dram, + const CryptTest *t, bool encrypt, uint8_t *out, + uint8_t *out_tag) +{ + const uint8_t *in = encrypt ? t->ptext : t->ctext; + uint64_t src_sg = dram + CRYPT_OFF_SRC_SG; + uint64_t dst_sg = dram + CRYPT_OFF_DST_SG; + uint64_t ctx = dram + CRYPT_OFF_CTX; + uint32_t cmd = t->cmd | HACE_CMD_ISR_EN | HACE_CMD_SRC_SG_CTRL | + HACE_CMD_DST_SG_CTRL; + + if (encrypt) { + cmd |= HACE_CMD_ENCRYPT; + } + + crypt_write_ctx(s, ctx, t); + crypt_make_sg(s, dram, CRYPT_OFF_SRC, src_sg, in, t->len); + crypt_make_sg(s, dram, CRYPT_OFF_DST, dst_sg, NULL, t->len); + + qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src_sg); + qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst_sg); + qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx); + qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len); + qtest_writel(s, base + HACE_CRYPTO_GCM_ADD_LEN, 0); + qtest_writel(s, base + HACE_CRYPTO_GCM_TAG, + (uint32_t)(dram + CRYPT_OFF_TAG)); + qtest_writel(s, base + HACE_CRYPTO_CMD, cmd); + + g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==, + HACE_CRYPTO_ISR); + qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR); + + crypt_gather_sg(s, dram, CRYPT_OFF_DST, out, t->len); + qtest_memread(s, dram + CRYPT_OFF_TAG, out_tag, t->taglen); +} + +static void aspeed_test_crypto(const void *data) +{ + const AspeedCryptoTest *c = data; + const CryptTest *t = &crypt_tests[c->index]; + QTestState *s = qtest_init(c->machine); + uint8_t out[64]; + uint8_t iv[16]; + size_t iv_off; + + g_assert_cmpuint(t->len, <=, sizeof(out)); + + /* Encrypt: ptext -> ctext */ + if (c->sg) { + crypt_run_sg(s, c->base, c->dram, t, true, out); + } else { + crypt_run_direct(s, c->base, c->dram, t, true, out); + } + g_assert_cmpmem(out, t->len, t->ctext, t->len); + + if (t->iv_out) { + iv_off = (t->cmd & HACE_CMD_DES_SELECT) ? 8 : 0; + qtest_memread(s, c->dram + CRYPT_OFF_CTX + iv_off, iv, t->ivlen); + g_assert_cmpmem(iv, t->ivlen, t->iv_out, t->ivlen); + } + + /* Decrypt: ctext -> ptext */ + if (c->sg) { + crypt_run_sg(s, c->base, c->dram, t, false, out); + } else { + crypt_run_direct(s, c->base, c->dram, t, false, out); + } + g_assert_cmpmem(out, t->len, t->ptext, t->len); + + qtest_quit(s); +} + +static void aspeed_test_crypto_gcm(const void *data) +{ + const AspeedCryptoTest *c = data; + const CryptTest *t = &crypt_tests[c->index]; + QTestState *s = qtest_init(c->machine); + uint8_t out[64]; + uint8_t tag[16]; + + g_assert_cmpuint(t->len, <=, sizeof(out)); + + /* Encrypt: ptext -> ctext, then check the authentication tag. */ + crypt_run_gcm(s, c->base, c->dram, t, true, out, tag); + g_assert_cmpmem(out, t->len, t->ctext, t->len); + g_assert_cmpmem(tag, t->taglen, t->tag, t->taglen); + + /* Decrypt: ctext -> ptext, the recomputed tag must match. */ + crypt_run_gcm(s, c->base, c->dram, t, false, out, tag); + g_assert_cmpmem(out, t->len, t->ptext, t->len); + g_assert_cmpmem(tag, t->taglen, t->tag, t->taglen); + + qtest_quit(s); +} + +void aspeed_add_crypto_tests(const char *prefix, const char *machine, + uint32_t base, uint64_t dram, uint32_t modes, + bool sg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(crypt_tests); i++) { + bool is_gcm = crypt_tests[i].mode == QCRYPTO_CIPHER_MODE_GCM; + g_autofree char *path = NULL; + AspeedCryptoTest *t; + + if (!(modes & crypt_mode_flag(crypt_tests[i].cmd))) { + continue; + } + + if (!qcrypto_cipher_supports(crypt_tests[i].alg, + crypt_tests[i].mode)) { + g_printerr("# skip unsupported %s\n", crypt_tests[i].name); + continue; + } + + path = g_strdup_printf("%s/hace/crypto/%s", prefix, + crypt_tests[i].name); + t = g_new0(AspeedCryptoTest, 1); + t->machine = machine; + t->base = base; + t->dram = dram; + t->index = i; + t->sg = sg; + qtest_add_data_func_full(path, t, + is_gcm ? aspeed_test_crypto_gcm : + aspeed_test_crypto, g_free); + } +} +
diff --git a/tests/qtest/aspeed-hace-utils.h b/tests/qtest/aspeed-hace-utils.h index 27ab2bb..a5601a3 100644 --- a/tests/qtest/aspeed-hace-utils.h +++ b/tests/qtest/aspeed-hace-utils.h
@@ -79,5 +79,25 @@ void aspeed_test_addresses(const char *machine, const uint32_t base, const struct AspeedMasks *expected); +/* + * Cipher modes a SoC's crypto engine supports, for aspeed_add_crypto_tests(). + */ +enum { + CRYPT_MODE_ECB = 1 << 0, + CRYPT_MODE_CBC = 1 << 1, + CRYPT_MODE_CTR = 1 << 2, + CRYPT_MODE_GCM = 1 << 3, +}; + +/* + * Register the crypto known-answer tests that @modes selects (a mask of + * CRYPT_MODE_*) for the given machine. Each test is named + * "<prefix>/hace/crypto/<mode>". @sg selects scatter-gather mode (used by the + * AST2600 and later) instead of the AST2500 direct access mode. + */ +void aspeed_add_crypto_tests(const char *prefix, const char *machine, + uint32_t base, uint64_t dram, uint32_t modes, + bool sg); + #endif /* TESTS_ASPEED_HACE_UTILS_H */
diff --git a/tests/qtest/aspeed-smc-utils.c b/tests/qtest/aspeed-smc-utils.c index c27d09e..1463322 100644 --- a/tests/qtest/aspeed-smc-utils.c +++ b/tests/qtest/aspeed-smc-utils.c
@@ -4,23 +4,7 @@ * * Copyright (C) 2016 IBM Corp. * - * 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. + * SPDX-License-Identifier: MIT */ #include "qemu/osdep.h" @@ -73,6 +57,28 @@ return qtest_readl(data->s, data->flash_base + offset); } +/* + * Data FIFO port, in spi_base's register bank (not flash_base). Accesses + * through the FIFO require the complete user-mode transaction (opcode, + * address, and data). Assumes CS0, whose FIFO slot is at R_DATA_FIFO. + */ +static inline void datafifo_writeb(const AspeedSMCTestData *data, + uint8_t value) +{ + qtest_writeb(data->s, data->spi_base + R_DATA_FIFO, value); +} + +static inline void datafifo_writel(const AspeedSMCTestData *data, + uint32_t value) +{ + spi_writel(data, R_DATA_FIFO, value); +} + +static inline uint32_t datafifo_readl(const AspeedSMCTestData *data) +{ + return spi_readl(data, R_DATA_FIFO); +} + static void spi_conf(const AspeedSMCTestData *data, uint32_t value) { uint32_t conf = spi_readl(data, R_CONF); @@ -107,6 +113,29 @@ spi_writel(data, ctrl_reg, ctrl); } +/* Set FREADMODE with a fast read command and 1 dummy byte */ +static void spi_ctrl_set_fast_read(const AspeedSMCTestData *data, uint8_t cmd) +{ + uint32_t ctrl_reg = R_CTRL0 + data->cs * 4; + uint32_t ctrl = spi_readl(data, ctrl_reg); + uint32_t iomode = 0; + + if (cmd == DOR) { + iomode = CTRL_IO_DUAL_DATA; + } else if (cmd == QOR) { + iomode = CTRL_IO_QUAD_DATA; + } + + ctrl &= ~(CTRL_USERMODE | (0xff << 16) | + (0x3 << CTRL_DUMMY_LOW_SHIFT) | + (0x1 << CTRL_DUMMY_HIGH_SHIFT) | + CTRL_IO_MODE_MASK); + ctrl |= CTRL_FREADMODE | (cmd << 16) | + (1 << CTRL_DUMMY_LOW_SHIFT) | + iomode; + spi_writel(data, ctrl_reg, ctrl); +} + static void spi_ctrl_start_user(const AspeedSMCTestData *data) { uint32_t ctrl_reg = R_CTRL0 + data->cs * 4; @@ -186,6 +215,9 @@ } } +typedef void (*read_page_mem_fn)(const AspeedSMCTestData *data, + uint32_t addr, uint32_t *page); + static void write_page_mem(const AspeedSMCTestData *data, uint32_t addr, uint32_t write_value) { @@ -327,9 +359,9 @@ flash_reset(test_data); } -void aspeed_smc_test_write_page(const void *data) +static void test_write_page(const AspeedSMCTestData *test_data, + read_page_mem_fn reader) { - const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data; uint32_t my_page_addr = test_data->page_addr; uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE; uint32_t page[FLASH_PAGE_SIZE / 4]; @@ -350,13 +382,13 @@ spi_ctrl_stop_user(test_data); /* Check what was written */ - read_page(test_data, my_page_addr, page); + reader(test_data, my_page_addr, page); for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { g_assert_cmphex(page[i], ==, my_page_addr + i * 4); } /* Check some other page. It should be full of 0xff */ - read_page(test_data, some_page_addr, page); + reader(test_data, some_page_addr, page); for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { g_assert_cmphex(page[i], ==, 0xffffffff); } @@ -364,9 +396,14 @@ flash_reset(test_data); } -void aspeed_smc_test_read_page_mem(const void *data) +void aspeed_smc_test_write_page(const void *data) { - const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data; + test_write_page(data, read_page); +} + +static void test_read_page_mem(const AspeedSMCTestData *test_data, + read_page_mem_fn reader) +{ uint32_t my_page_addr = test_data->page_addr; uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE; uint32_t page[FLASH_PAGE_SIZE / 4]; @@ -393,13 +430,13 @@ spi_conf_remove(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs)); /* Check what was written */ - read_page_mem(test_data, my_page_addr, page); + reader(test_data, my_page_addr, page); for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { g_assert_cmphex(page[i], ==, my_page_addr + i * 4); } /* Check some other page. It should be full of 0xff */ - read_page_mem(test_data, some_page_addr, page); + reader(test_data, some_page_addr, page); for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { g_assert_cmphex(page[i], ==, 0xffffffff); } @@ -407,6 +444,11 @@ flash_reset(test_data); } +void aspeed_smc_test_read_page_mem(const void *data) +{ + test_read_page_mem(data, read_page_mem); +} + void aspeed_smc_test_write_page_mem(const void *data) { const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data; @@ -684,3 +726,205 @@ flash_reset(test_data); } +static void read_page_mem_fast_read(const AspeedSMCTestData *data, + uint32_t addr, uint32_t *page) +{ + int i; + + spi_ctrl_set_fast_read(data, FAST_READ); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + page[i] = make_be32(flash_readl(data, addr + i * 4)); + } +} + +void aspeed_smc_test_read_page_mem_fast_read(const void *data) +{ + test_read_page_mem(data, read_page_mem_fast_read); +} + +static void read_page_fast_read(const AspeedSMCTestData *data, + uint32_t addr, uint32_t *page) +{ + int i; + + spi_ctrl_start_user(data); + + flash_writeb(data, 0, EN_4BYTE_ADDR); + flash_writeb(data, 0, FAST_READ); + flash_writel(data, 0, make_be32(addr)); + /* 1 dummy byte for standard SPI fast-read */ + flash_writeb(data, 0, 0x00); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + page[i] = make_be32(flash_readl(data, 0)); + } + spi_ctrl_stop_user(data); +} + +void aspeed_smc_test_write_page_fast_read(const void *data) +{ + test_write_page(data, read_page_fast_read); +} + +static void read_page_mem_dor(const AspeedSMCTestData *data, + uint32_t addr, uint32_t *page) +{ + int i; + + spi_ctrl_set_fast_read(data, DOR); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + page[i] = make_be32(flash_readl(data, addr + i * 4)); + } +} + +void aspeed_smc_test_read_page_mem_dor(const void *data) +{ + test_read_page_mem(data, read_page_mem_dor); +} + +static void read_page_dor(const AspeedSMCTestData *data, + uint32_t addr, uint32_t *page) +{ + int i; + + spi_ctrl_start_user(data); + + flash_writeb(data, 0, EN_4BYTE_ADDR); + flash_writeb(data, 0, DOR); + flash_writel(data, 0, make_be32(addr)); + /* 1 dummy byte for standard SPI DOR */ + flash_writeb(data, 0, 0x00); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + page[i] = make_be32(flash_readl(data, 0)); + } + spi_ctrl_stop_user(data); +} + +void aspeed_smc_test_write_page_dor(const void *data) +{ + test_write_page(data, read_page_dor); +} + +static void read_page_mem_qor(const AspeedSMCTestData *data, + uint32_t addr, uint32_t *page) +{ + int i; + + spi_ctrl_set_fast_read(data, QOR); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + page[i] = make_be32(flash_readl(data, addr + i * 4)); + } +} + +void aspeed_smc_test_read_page_mem_qor(const void *data) +{ + test_read_page_mem(data, read_page_mem_qor); +} + +static void read_page_qor(const AspeedSMCTestData *data, + uint32_t addr, uint32_t *page) +{ + int i; + + spi_ctrl_start_user(data); + + flash_writeb(data, 0, EN_4BYTE_ADDR); + flash_writeb(data, 0, QOR); + flash_writel(data, 0, make_be32(addr)); + /* 1 dummy byte for standard SPI QOR */ + flash_writeb(data, 0, 0x00); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + page[i] = make_be32(flash_readl(data, 0)); + } + spi_ctrl_stop_user(data); +} + +void aspeed_smc_test_write_page_qor(const void *data) +{ + test_write_page(data, read_page_qor); +} + +void aspeed_smc_test_write_page_datafifo(const void *data) +{ + const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data; + uint32_t my_page_addr = test_data->page_addr; + uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE; + uint32_t page[FLASH_PAGE_SIZE / 4]; + int i; + + spi_conf(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs)); + + /* + * Send the complete user-mode transaction (opcode, address, data) + * through the Data FIFO port. + */ + spi_ctrl_start_user(test_data); + datafifo_writeb(test_data, EN_4BYTE_ADDR); + datafifo_writeb(test_data, WREN); + datafifo_writeb(test_data, PP); + datafifo_writel(test_data, make_be32(my_page_addr)); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + datafifo_writel(test_data, make_be32(my_page_addr + i * 4)); + } + spi_ctrl_stop_user(test_data); + + /* Check what was written, using the regular read path */ + read_page(test_data, my_page_addr, page); + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + g_assert_cmphex(page[i], ==, my_page_addr + i * 4); + } + + /* Check some other page. It should be full of 0xff */ + read_page(test_data, some_page_addr, page); + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + g_assert_cmphex(page[i], ==, 0xffffffff); + } + + flash_reset(test_data); +} + +void aspeed_smc_test_read_page_datafifo(const void *data) +{ + const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data; + uint32_t my_page_addr = test_data->page_addr; + uint32_t page[FLASH_PAGE_SIZE / 4]; + int i; + + spi_conf(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs)); + + /* Write the page the regular way */ + spi_ctrl_start_user(test_data); + flash_writeb(test_data, 0, EN_4BYTE_ADDR); + flash_writeb(test_data, 0, WREN); + flash_writeb(test_data, 0, PP); + flash_writel(test_data, 0, make_be32(my_page_addr)); + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + flash_writel(test_data, 0, make_be32(my_page_addr + i * 4)); + } + spi_ctrl_stop_user(test_data); + + /* + * Read it back through the data FIFO port, again sending the whole + * transaction (opcode, address, data) through it. + */ + spi_ctrl_start_user(test_data); + datafifo_writeb(test_data, EN_4BYTE_ADDR); + datafifo_writeb(test_data, READ); + datafifo_writel(test_data, make_be32(my_page_addr)); + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + page[i] = make_be32(datafifo_readl(test_data)); + } + spi_ctrl_stop_user(test_data); + + for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) { + g_assert_cmphex(page[i], ==, my_page_addr + i * 4); + } + + flash_reset(test_data); +}
diff --git a/tests/qtest/aspeed-smc-utils.h b/tests/qtest/aspeed-smc-utils.h index e2fd8ff..e4f538e 100644 --- a/tests/qtest/aspeed-smc-utils.h +++ b/tests/qtest/aspeed-smc-utils.h
@@ -4,23 +4,7 @@ * * Copyright (C) 2016 IBM Corp. * - * 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. + * SPDX-License-Identifier: MIT */ #ifndef TESTS_ASPEED_SMC_UTILS_H @@ -44,7 +28,13 @@ #define CTRL_FREADMODE 0x1 #define CTRL_WRITEMODE 0x2 #define CTRL_USERMODE 0x3 +#define CTRL_IO_QUAD_DATA BIT(30) +#define CTRL_IO_DUAL_DATA BIT(29) +#define CTRL_DUMMY_LOW_SHIFT 6 +#define CTRL_DUMMY_HIGH_SHIFT 14 #define SR_WEL BIT(1) +/* Data fifo */ +#define R_DATA_FIFO 0x200 /* * Flash commands @@ -55,6 +45,9 @@ WRDI = 0x4, BULK_ERASE = 0xc7, READ = 0x03, + FAST_READ = 0x0b, + DOR = 0x3b, + QOR = 0x6b, PP = 0x02, WRSR = 0x1, WREN = 0x6, @@ -90,5 +83,13 @@ void aspeed_smc_test_write_block_protect(const void *data); void aspeed_smc_test_write_block_protect_bottom_bit(const void *data); void aspeed_smc_test_write_page_qpi(const void *data); +void aspeed_smc_test_read_page_mem_fast_read(const void *data); +void aspeed_smc_test_write_page_fast_read(const void *data); +void aspeed_smc_test_read_page_mem_dor(const void *data); +void aspeed_smc_test_write_page_dor(const void *data); +void aspeed_smc_test_read_page_mem_qor(const void *data); +void aspeed_smc_test_write_page_qor(const void *data); +void aspeed_smc_test_write_page_datafifo(const void *data); +void aspeed_smc_test_read_page_datafifo(const void *data); #endif /* TESTS_ASPEED_SMC_UTILS_H */
diff --git a/tests/qtest/aspeed_hace-test.c b/tests/qtest/aspeed_hace-test.c index 3877702..42130df 100644 --- a/tests/qtest/aspeed_hace-test.c +++ b/tests/qtest/aspeed_hace-test.c
@@ -210,6 +210,12 @@ qtest_add_func("ast1030/hace/sha384_accum", test_sha384_accum_ast1030); qtest_add_func("ast1030/hace/sha256_accum", test_sha256_accum_ast1030); + /* The AST1030 reuses the AST2600 crypto engine (scatter-gather, CTR). */ + aspeed_add_crypto_tests("ast1030", "-machine ast1030-evb", 0x7e6d0000, + 0x00000000, + CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR, + true); + qtest_add_func("ast2600/hace/addresses", test_addresses_ast2600); qtest_add_func("ast2600/hace/sha512", test_sha512_ast2600); qtest_add_func("ast2600/hace/sha384", test_sha384_ast2600); @@ -224,11 +230,23 @@ qtest_add_func("ast2600/hace/sha384_accum", test_sha384_accum_ast2600); qtest_add_func("ast2600/hace/sha256_accum", test_sha256_accum_ast2600); + /* The AST2600 crypto engine uses scatter-gather mode and adds CTR. */ + aspeed_add_crypto_tests("ast2600", "-machine ast2600-evb", 0x1e6d0000, + 0x80000000, + CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR, + true); + qtest_add_func("ast2500/hace/addresses", test_addresses_ast2500); qtest_add_func("ast2500/hace/sha512", test_sha512_ast2500); qtest_add_func("ast2500/hace/sha256", test_sha256_ast2500); qtest_add_func("ast2500/hace/md5", test_md5_ast2500); + /* + * The AST2500 crypto engine uses direct access mode and supports ECB/CBC. + */ + aspeed_add_crypto_tests("ast2500", "-machine ast2500-evb", 0x1e6e3000, + 0x80000000, CRYPT_MODE_ECB | CRYPT_MODE_CBC, false); + qtest_add_func("ast2400/hace/addresses", test_addresses_ast2400); qtest_add_func("ast2400/hace/sha512", test_sha512_ast2400); qtest_add_func("ast2400/hace/sha256", test_sha256_ast2400);
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c index 39af1df..59c96bd 100644 --- a/tests/qtest/aspeed_smc-test.c +++ b/tests/qtest/aspeed_smc-test.c
@@ -4,23 +4,7 @@ * * Copyright (C) 2016 IBM Corp. * - * 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. + * SPDX-License-Identifier: MIT */ #include "qemu/osdep.h" @@ -68,6 +52,23 @@ data, aspeed_smc_test_read_status_reg); qtest_add_data_func("/ast2400/smc/status_reg_write_protection", data, aspeed_smc_test_status_reg_write_protection); + qtest_add_data_func("/ast2400/smc/read_page_mem_fast_read", + data, aspeed_smc_test_read_page_mem_fast_read); + qtest_add_data_func("/ast2400/smc/write_page_fast_read", + data, aspeed_smc_test_write_page_fast_read); + qtest_add_data_func("/ast2400/smc/read_page_mem_dor", + data, aspeed_smc_test_read_page_mem_dor); + qtest_add_data_func("/ast2400/smc/write_page_dor", + data, aspeed_smc_test_write_page_dor); + qtest_add_data_func("/ast2400/smc/read_page_mem_qor", + data, aspeed_smc_test_read_page_mem_qor); + qtest_add_data_func("/ast2400/smc/write_page_qor", + data, aspeed_smc_test_write_page_qor); + /* + * Block protect tests must be run last because the block protect + * state is not cleared by reset_memory() and silently prevents + * subsequent flash writes. + */ qtest_add_data_func("/ast2400/smc/write_block_protect", data, aspeed_smc_test_write_block_protect); qtest_add_data_func("/ast2400/smc/write_block_protect_bottom_bit", @@ -115,6 +116,18 @@ data, aspeed_smc_test_read_status_reg); qtest_add_data_func("/ast2500/smc/write_page_qpi", data, aspeed_smc_test_write_page_qpi); + qtest_add_data_func("/ast2500/smc/read_page_mem_fast_read", + data, aspeed_smc_test_read_page_mem_fast_read); + qtest_add_data_func("/ast2500/smc/write_page_fast_read", + data, aspeed_smc_test_write_page_fast_read); + qtest_add_data_func("/ast2500/smc/read_page_mem_dor", + data, aspeed_smc_test_read_page_mem_dor); + qtest_add_data_func("/ast2500/smc/write_page_dor", + data, aspeed_smc_test_write_page_dor); + qtest_add_data_func("/ast2500/smc/read_page_mem_qor", + data, aspeed_smc_test_read_page_mem_qor); + qtest_add_data_func("/ast2500/smc/write_page_qor", + data, aspeed_smc_test_write_page_qor); } static void test_ast2600_evb(AspeedSMCTestData *data) @@ -158,6 +171,18 @@ data, aspeed_smc_test_read_status_reg); qtest_add_data_func("/ast2600/smc/write_page_qpi", data, aspeed_smc_test_write_page_qpi); + qtest_add_data_func("/ast2600/smc/read_page_mem_fast_read", + data, aspeed_smc_test_read_page_mem_fast_read); + qtest_add_data_func("/ast2600/smc/write_page_fast_read", + data, aspeed_smc_test_write_page_fast_read); + qtest_add_data_func("/ast2600/smc/read_page_mem_dor", + data, aspeed_smc_test_read_page_mem_dor); + qtest_add_data_func("/ast2600/smc/write_page_dor", + data, aspeed_smc_test_write_page_dor); + qtest_add_data_func("/ast2600/smc/read_page_mem_qor", + data, aspeed_smc_test_read_page_mem_qor); + qtest_add_data_func("/ast2600/smc/write_page_qor", + data, aspeed_smc_test_write_page_qor); } static void test_ast1030_evb(AspeedSMCTestData *data) @@ -201,6 +226,18 @@ data, aspeed_smc_test_read_status_reg); qtest_add_data_func("/ast1030/smc/write_page_qpi", data, aspeed_smc_test_write_page_qpi); + qtest_add_data_func("/ast1030/smc/read_page_mem_fast_read", + data, aspeed_smc_test_read_page_mem_fast_read); + qtest_add_data_func("/ast1030/smc/write_page_fast_read", + data, aspeed_smc_test_write_page_fast_read); + qtest_add_data_func("/ast1030/smc/read_page_mem_dor", + data, aspeed_smc_test_read_page_mem_dor); + qtest_add_data_func("/ast1030/smc/write_page_dor", + data, aspeed_smc_test_write_page_dor); + qtest_add_data_func("/ast1030/smc/read_page_mem_qor", + data, aspeed_smc_test_read_page_mem_qor); + qtest_add_data_func("/ast1030/smc/write_page_qor", + data, aspeed_smc_test_write_page_qor); } int main(int argc, char **argv)
diff --git a/tests/qtest/ast2700-hace-test.c b/tests/qtest/ast2700-hace-test.c index 508a34d..3f0217d 100644 --- a/tests/qtest/ast2700-hace-test.c +++ b/tests/qtest/ast2700-hace-test.c
@@ -94,5 +94,14 @@ qtest_add_func("ast2700/hace/sha384_accum", test_sha384_accum_ast2700); qtest_add_func("ast2700/hace/sha256_accum", test_sha256_accum_ast2700); + /* + * The AST2700 crypto engine uses scatter-gather with 64-bit DMA and adds + * AES-GCM on top of the ECB/CBC/CTR modes shared with the AST2600. + */ + aspeed_add_crypto_tests("ast2700", "-machine ast2700-evb", 0x12070000, + 0x400000000, + CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR | + CRYPT_MODE_GCM, true); + return g_test_run(); }
diff --git a/tests/qtest/ast2700-smc-test.c b/tests/qtest/ast2700-smc-test.c index 33fc472..925dbcf 100644 --- a/tests/qtest/ast2700-smc-test.c +++ b/tests/qtest/ast2700-smc-test.c
@@ -52,6 +52,22 @@ data, aspeed_smc_test_read_status_reg); qtest_add_data_func("/ast2700/smc/write_page_qpi", data, aspeed_smc_test_write_page_qpi); + qtest_add_data_func("/ast2700/smc/read_page_mem_fast_read", + data, aspeed_smc_test_read_page_mem_fast_read); + qtest_add_data_func("/ast2700/smc/write_page_fast_read", + data, aspeed_smc_test_write_page_fast_read); + qtest_add_data_func("/ast2700/smc/read_page_mem_dor", + data, aspeed_smc_test_read_page_mem_dor); + qtest_add_data_func("/ast2700/smc/write_page_dor", + data, aspeed_smc_test_write_page_dor); + qtest_add_data_func("/ast2700/smc/read_page_mem_qor", + data, aspeed_smc_test_read_page_mem_qor); + qtest_add_data_func("/ast2700/smc/write_page_qor", + data, aspeed_smc_test_write_page_qor); + qtest_add_data_func("/ast2700/smc/write_page_datafifo", + data, aspeed_smc_test_write_page_datafifo); + qtest_add_data_func("/ast2700/smc/read_page_datafifo", + data, aspeed_smc_test_read_page_datafifo); } int main(int argc, char **argv)
diff --git a/tests/qtest/fuzz-virtio-scsi-test.c b/tests/qtest/fuzz-virtio-scsi-test.c index e37b48b..102a5cc 100644 --- a/tests/qtest/fuzz-virtio-scsi-test.c +++ b/tests/qtest/fuzz-virtio-scsi-test.c
@@ -19,7 +19,7 @@ { QTestState *s; - s = qtest_init("-M pc-q35-5.2 -m 512M " + s = qtest_init("-M q35 -m 512M " "-device virtio-scsi,num_queues=8,addr=03.0 "); qtest_outl(s, 0xcf8, 0x80001811);
diff --git a/tests/qtest/l2vic-test.c b/tests/qtest/l2vic-test.c new file mode 100644 index 0000000..adb4dc7 --- /dev/null +++ b/tests/qtest/l2vic-test.c
@@ -0,0 +1,249 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + * + * QTest testcase for the L2VIC Interrupt Controller + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" +#include "hw/hexagon/hexagon.h" + +#include "hw/hexagon/machine_cfg_v66g_1024.h.inc" +#include "hw/hexagon/machine_cfg_v68n_1024.h.inc" + +/* L2VIC register offsets exercised by this test */ +#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */ +#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */ +#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */ +#define L2VIC_INT_TYPEn 0x280 /* Read/Write */ +#define L2VIC_INT_STATUSn 0x380 /* Read */ +#define L2VIC_INT_CLEARn 0x400 /* Write */ +#define L2VIC_SOFT_INTn 0x480 /* Write */ +#define L2VIC_INT_PENDINGn 0x500 /* Read */ +#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */ +#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */ +#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */ +#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */ + +/* + * VID group readback: records which irq last fired through each VID + * group. Outputs themselves are momentary pulses (see l2vic_update()), + * so these registers -- not the qtest IRQ level snapshot -- are how the + * test observes VID steering. + */ +#define L2VIC_VID_GRP_0 0x0 +#define L2VIC_VID_GRP_1 0x4 +#define L2VIC_VID_GRP_2 0x8 +#define L2VIC_VID_GRP_3 0xC + +typedef struct { + const char *machine; + const struct hexagon_machine_config *cfg; +} L2VICMachineCfg; + +static const L2VICMachineCfg l2vic_machines[] = { + { "virt", &v68n_1024 }, + { "V66G_1024", &v66g_1024 }, +}; + +static uint32_t l2vic_read32(uint64_t base, uint32_t offset) +{ + return readl(base + offset); +} + +static void l2vic_write32(uint64_t base, uint32_t offset, uint32_t value) +{ + writel(base + offset, value); +} + +static void test_l2vic_register_access(uint64_t base) +{ + uint32_t val; + + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x1, ==, 0x1); + + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x1, ==, 0x0); +} + +static void test_l2vic_interrupt_enable(uint64_t base) +{ + uint32_t val; + + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val, ==, 0); + + /* Enable IRQ 0 and 2 */ + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x5); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x5, ==, 0x5); + + /* Disable IRQ 0, leaving IRQ 2 enabled */ + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1); + val = l2vic_read32(base, L2VIC_INT_ENABLEn); + g_assert_cmpuint(val & 0x1, ==, 0x0); + g_assert_cmpuint(val & 0x4, ==, 0x4); +} + +static void test_l2vic_basic_functionality(uint64_t base) +{ + l2vic_read32(base, L2VIC_INT_ENABLEn); + l2vic_read32(base, L2VIC_INT_PENDINGn); + l2vic_read32(base, L2VIC_INT_STATUSn); + l2vic_read32(base, L2VIC_INT_TYPEn); + + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0); + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0); +} + +/* + * IRQs 0-7 pack their group-enable/VID-select nibbles into + * L2VIC_INT_GRPn_0 (int_group_n[0]), 4 bits per irq: bit 3 enables + * VID steering, bits 0-2 select the VID group (0-3), which pulses + * output line vid+2. + */ +static void l2vic_set_vid_group(uint64_t base, int irq, int vid) +{ + uint32_t val = l2vic_read32(base, L2VIC_INT_GRPn_0); + uint32_t nibble = 0x8 | (vid & 0x7); + + val &= ~(0xFu << (irq * 4)); + val |= nibble << (irq * 4); + l2vic_write32(base, L2VIC_INT_GRPn_0, val); +} + +static void test_l2vic_irq_outputs(uint64_t base) +{ + uint32_t val; + + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_TYPEn, 0); + l2vic_write32(base, L2VIC_INT_GRPn_0, 0); + + /* Group 0 / IRQ2: soft interrupts require edge-triggered config */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x1); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x1); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x1, ==, 0x1); + /* Default VID group (0) records the delivering irq, output line 2 */ + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_0), ==, 0); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x1); + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x1, ==, 0x0); + + /* IRQ1 steered to VID group 1 -> output line 3 */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x2); + l2vic_set_vid_group(base, 1, 1); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x2); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x2); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x2, ==, 0x2); + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_1), ==, 1); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x2); + + /* IRQ4 steered to VID group 2 -> output line 4 */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x10); + l2vic_set_vid_group(base, 4, 2); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x10); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x10); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x10, ==, 0x10); + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_2), ==, 4); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x10); + + /* IRQ5 steered to VID group 3 -> output line 5 */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x20); + l2vic_set_vid_group(base, 5, 3); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x20, ==, 0x20); + g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_3), ==, 5); + + l2vic_write32(base, L2VIC_INT_CLEARn, 0x20); + + /* Restore defaults; the block below reuses IRQ 3-5 without VID steering */ + l2vic_write32(base, L2VIC_INT_GRPn_0, 0); + l2vic_write32(base, L2VIC_INT_TYPEn, 0); + + /* Multiple pending: at most one active at a time */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0xF); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0xF); + l2vic_write32(base, L2VIC_SOFT_INTn, 0xF); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0xF, !=, 0x0); + + /* + * Only one irq becomes active per delivery; each clear unblocks the + * next pending one, so drain until all four have been delivered. + */ + while ((val = l2vic_read32(base, L2VIC_INT_STATUSn)) & 0xF) { + l2vic_write32(base, L2VIC_INT_CLEARn, val & 0xF); + } + + /* Level-triggered sources ignore soft interrupts */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x0); + l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20); + + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x20, ==, 0x0); + + /* Same source, now edge-triggered, does fire */ + l2vic_write32(base, L2VIC_INT_TYPEn, 0x20); + l2vic_write32(base, L2VIC_SOFT_INTn, 0x20); + val = l2vic_read32(base, L2VIC_INT_STATUSn); + g_assert_cmpuint(val & 0x20, ==, 0x20); + + l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF); + l2vic_write32(base, L2VIC_INT_GRPn_0, 0); + l2vic_write32(base, L2VIC_INT_GRPn_1, 0); + l2vic_write32(base, L2VIC_INT_GRPn_2, 0); + l2vic_write32(base, L2VIC_INT_GRPn_3, 0); +} + +static void test_l2vic_on_machine(gconstpointer data) +{ + const L2VICMachineCfg *mc = data; + g_autofree char *args = g_strdup_printf("-machine %s", mc->machine); + uint64_t base = mc->cfg->l2vic_base; + + qtest_start(args); + + test_l2vic_register_access(base); + test_l2vic_interrupt_enable(base); + test_l2vic_basic_functionality(base); + test_l2vic_irq_outputs(base); + + qtest_end(); +} + +int main(int argc, char **argv) +{ + size_t i; + + g_test_init(&argc, &argv, NULL); + + for (i = 0; i < ARRAY_SIZE(l2vic_machines); i++) { + g_autofree char *path = g_strdup_printf("/l2vic/%s/all-tests", + l2vic_machines[i].machine); + qtest_add_data_func(path, &l2vic_machines[i], test_l2vic_on_machine); + } + + return g_test_run(); +}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 56ff860..f7c7d06 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build
@@ -299,11 +299,12 @@ ['iommu-riscv-test'] : []) + \ (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : []) -qtests_hexagon = ['boot-serial-test'] +qtests_hexagon = ['boot-serial-test', 'l2vic-test', 'qct-qtimer-test'] qos_test_ss = ss.source_set() qos_test_ss.add( 'ac97-test.c', + 'adc128d818-test.c', 'adm1272-test.c', 'adm1266-test.c', 'ds1338-test.c', @@ -318,6 +319,8 @@ 'tulip-test.c', 'nvme-test.c', 'pca9552-test.c', + 'pca9554-test.c', + 'pca9555-test.c', 'pci-test.c', 'pcnet-test.c', 'rs5c372-test.c', @@ -389,9 +392,11 @@ endif qtests = { - 'aspeed_hace-test': files('aspeed-hace-utils.c', 'aspeed_hace-test.c'), + 'aspeed_hace-test': [files('aspeed-hace-utils.c', 'aspeed_hace-test.c'), + crypto], 'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'), - 'ast2700-hace-test': files('aspeed-hace-utils.c', 'ast2700-hace-test.c'), + 'ast2700-hace-test': [files('aspeed-hace-utils.c', 'ast2700-hace-test.c'), + crypto], 'ast2700-smc-test': files('aspeed-smc-utils.c', 'ast2700-smc-test.c'), 'bios-tables-test': [io, 'boot-sector.c', 'acpi-utils.c', 'tpm-emu.c'], 'cdrom-test': files('boot-sector.c'),
diff --git a/tests/qtest/pca9552-test.c b/tests/qtest/pca9552-test.c index 7474957..3718dfb 100644 --- a/tests/qtest/pca9552-test.c +++ b/tests/qtest/pca9552-test.c
@@ -77,6 +77,76 @@ g_assert_cmphex(value, ==, 0xEF); } +/* Verify the power-on reset defaults. */ +static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + + /* Prescalers, PWM duty cycles and LED selectors (all LEDs off) */ + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PSC0), ==, 0xFF); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PWM0), ==, 0x80); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PSC1), ==, 0xFF); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PWM1), ==, 0x80); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS0), ==, 0x55); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS1), ==, 0x55); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS2), ==, 0x55); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS3), ==, 0x55); + + /* All LEDs off, so every pin floats high through its pull-up */ + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_INPUT0), ==, 0xFF); + g_assert_cmphex(i2c_get8(i2cdev, PCA9552_INPUT1), ==, 0xFF); +} + +/* + * The PCA9552 only advances the command pointer when the AI bit is set, and + * it wraps modulo the full 10-register map. + */ +static void test_autoinc_requires_ai_bit(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + uint8_t reg; + uint8_t resp; + + /* + * With the AI bit, reading from LS3 (register 9) rolls over to INPUT0 + * (register 0), not to a sibling in a register pair. All LEDs are off + * after reset so the input ports read 0xFF. + */ + reg = PCA9552_LS3 | PCA9552_AUTOINC; + qi2c_send(i2cdev, ®, 1); + qi2c_recv(i2cdev, &resp, 1); /* LS3 */ + g_assert_cmphex(resp, ==, 0x55); + qi2c_recv(i2cdev, &resp, 1); /* wraps to INPUT0 */ + g_assert_cmphex(resp, ==, 0xFF); + qi2c_recv(i2cdev, &resp, 1); /* INPUT1 */ + g_assert_cmphex(resp, ==, 0xFF); + + /* + * Without the AI bit the pointer must not advance: repeated reads keep + * returning the same register. + */ + i2c_set8(i2cdev, PCA9552_LS0, 0x54); + reg = PCA9552_LS0; + qi2c_send(i2cdev, ®, 1); + qi2c_recv(i2cdev, &resp, 1); + g_assert_cmphex(resp, ==, 0x54); + qi2c_recv(i2cdev, &resp, 1); + g_assert_cmphex(resp, ==, 0x54); +} + +/* + * The PCA9552 decodes a 4-bit command and has no register past LS3 (9), so + * addressing register 0x0A reads back 0xFF. + */ +static void test_command_out_of_range(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(i2cdev, 0x0A), ==, 0xFF); +} + static void pca9552_register_nodes(void) { QOSGraphEdgeOptions opts = { @@ -89,5 +159,11 @@ qos_add_test("tx-rx", "pca9552", send_and_receive, NULL); qos_add_test("rx-autoinc", "pca9552", receive_autoinc, NULL); + qos_add_test("reset-defaults", "pca9552", test_reset_defaults, NULL); + qos_add_test("autoinc-requires-ai-bit", "pca9552", + test_autoinc_requires_ai_bit, NULL); + qos_add_test("command-out-of-range", "pca9552", test_command_out_of_range, + NULL); } + libqos_init(pca9552_register_nodes);
diff --git a/tests/qtest/pca9554-test.c b/tests/qtest/pca9554-test.c new file mode 100644 index 0000000..5366e71 --- /dev/null +++ b/tests/qtest/pca9554-test.c
@@ -0,0 +1,223 @@ +/* + * QTest testcase for the PCA9554/PCA9536 I/O port expanders + * + * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com) + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/gpio/pca9554_regs.h" +#include "libqos/i2c.h" +#include "libqos/qgraph.h" + +#define PCA9554_TEST_ADDR 0x20 + +/* Verify power-on reset defaults match the PCA9554 datasheet. */ +static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + /* All pins are inputs, pulled high, with no polarity inversion. */ + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0xFF); +} + +/* + * A pin configured as output (config=0) drives its OUTPUT register level onto + * the pin (push-pull), which the INPUT register reflects. A pin configured as + * input (config=1) floats high through its pull-up. + */ +static void test_output_drives_input(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + /* Low nibble output, high nibble input (pull-up). */ + i2c_set8(dev, PCA9554_CONFIG, 0xF0); + i2c_set8(dev, PCA9554_OUTPUT, 0xFA); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFA); + + /* All outputs, driven low then high. */ + i2c_set8(dev, PCA9554_CONFIG, 0x00); + i2c_set8(dev, PCA9554_OUTPUT, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00); + + i2c_set8(dev, PCA9554_OUTPUT, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF); +} + +/* + * With all pins configured as inputs the pull-ups make the INPUT register read + * all ones regardless of the OUTPUT register; switching a pin to output with + * output=0 drives it low. + */ +static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF); + + i2c_set8(dev, PCA9554_OUTPUT, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF); + + i2c_set8(dev, PCA9554_CONFIG, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00); +} + +/* + * Polarity inversion: reading INPUT returns the XOR of the pin levels and the + * polarity register. + */ +static void test_polarity_inversion(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF); + + i2c_set8(dev, PCA9554_POLARITY, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00); + + i2c_set8(dev, PCA9554_POLARITY, 0x0F); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xF0); +} + +/* Polarity inversion combined with output-driven pins. */ +static void test_polarity_with_output(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, PCA9554_CONFIG, 0x00); + i2c_set8(dev, PCA9554_OUTPUT, 0xA5); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xA5); + + i2c_set8(dev, PCA9554_POLARITY, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x5A); + + /* Inversion only affects the INPUT read, not the OUTPUT register. */ + g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xA5); +} + +/* + * The PCA9554 has no auto-increment: the command pointer never advances, so + * multi-byte reads and writes all target the addressed register. + */ +static void test_no_autoincrement(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t buf[2]; + + /* Distinct values in adjacent registers. */ + i2c_set8(dev, PCA9554_OUTPUT, 0xAA); + i2c_set8(dev, PCA9554_POLARITY, 0x33); + + /* Two reads from OUTPUT return OUTPUT twice, not OUTPUT then POLARITY. */ + i2c_read_block(dev, PCA9554_OUTPUT, buf, 2); + g_assert_cmphex(buf[0], ==, 0xAA); + g_assert_cmphex(buf[1], ==, 0xAA); + + /* The second written byte overwrites OUTPUT; POLARITY is untouched. */ + buf[0] = 0x12; + buf[1] = 0x34; + i2c_write_block(dev, PCA9554_OUTPUT, buf, 2); + g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x34); + g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x33); +} + +/* + * The PCA9536 shares the PCA9554 register map but only has four pins, so its + * reset defaults and pin logic are masked to the low nibble. + */ +static void test_pca9536_reset_defaults(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F); + g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0F); + g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x0F); +} + +/* Only the four low pins are driven; the upper nibble stays low. */ +static void test_pca9536_output_drives_input(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, PCA9554_CONFIG, 0x00); + + i2c_set8(dev, PCA9554_OUTPUT, 0x0A); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0A); + + i2c_set8(dev, PCA9554_OUTPUT, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00); +} + +/* + * The four upper bits address pins that do not exist on the PCA9536, so writes + * to the register map discard them: the writable registers read back with bits + * [7:4] cleared, and driving them onto the pins never surfaces in INPUT. + */ +static void test_pca9536_ignores_upper_bits(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + /* Bits [7:4] are dropped on write; bits [3:0] survive. */ + i2c_set8(dev, PCA9554_OUTPUT, 0xFA); + g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0A); + + i2c_set8(dev, PCA9554_POLARITY, 0xF5); + g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x05); + + i2c_set8(dev, PCA9554_CONFIG, 0xF3); + g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x03); + + /* + * With all four pins as outputs, driving 0xFF only affects the low + * nibble. + */ + i2c_set8(dev, PCA9554_POLARITY, 0x00); + i2c_set8(dev, PCA9554_CONFIG, 0x00); + i2c_set8(dev, PCA9554_OUTPUT, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F); +} + +static void pca9554_register_nodes(void) +{ + QOSGraphEdgeOptions opts = { + .extra_device_opts = "address=0x20" + }; + add_qi2c_address(&opts, &(QI2CAddress) { PCA9554_TEST_ADDR }); + + qos_node_create_driver("pca9554", i2c_device_create); + qos_node_consumes("pca9554", "i2c-bus", &opts); + + qos_add_test("reset-defaults", "pca9554", test_reset_defaults, NULL); + qos_add_test("output-drives-input", "pca9554", test_output_drives_input, + NULL); + qos_add_test("input-pullup", "pca9554", test_input_pullup, NULL); + qos_add_test("polarity-inversion", "pca9554", test_polarity_inversion, + NULL); + qos_add_test("polarity-with-output", "pca9554", test_polarity_with_output, + NULL); + qos_add_test("no-autoincrement", "pca9554", test_no_autoincrement, NULL); + + qos_node_create_driver("pca9536", i2c_device_create); + qos_node_consumes("pca9536", "i2c-bus", &opts); + + qos_add_test("reset-defaults", "pca9536", test_pca9536_reset_defaults, + NULL); + qos_add_test("output-drives-input", "pca9536", + test_pca9536_output_drives_input, NULL); + qos_add_test("ignores-upper-bits", "pca9536", + test_pca9536_ignores_upper_bits, NULL); +} + +libqos_init(pca9554_register_nodes);
diff --git a/tests/qtest/pca9555-test.c b/tests/qtest/pca9555-test.c new file mode 100644 index 0000000..84d771b --- /dev/null +++ b/tests/qtest/pca9555-test.c
@@ -0,0 +1,251 @@ +/* + * QTest testcase for the PCA9555 16-bit I/O port expander + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/gpio/pca9552_regs.h" +#include "libqos/i2c.h" +#include "libqos/qgraph.h" + +#define PCA9555_TEST_ADDR 0x20 + +/* Verify power-on reset defaults match the PCA9555 datasheet. */ +static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY0), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY1), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF); +} + +/* + * When a pin is configured as output and driven low (output=0, config=0), + * the input register should reflect 0 for that pin. + * When driven high (output=1, config=0), input should reflect 1. + * When configured as input (config=1), PCA5555 pull-up makes it read 1. + */ +static void test_output_drives_input(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, PCA9535_CONFIG0, 0xF0); + i2c_set8(dev, PCA9535_OUTPUT0, 0xFA); + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFA); + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF); + + i2c_set8(dev, PCA9535_CONFIG0, 0x00); + i2c_set8(dev, PCA9535_OUTPUT0, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00); + + i2c_set8(dev, PCA9535_OUTPUT0, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF); +} + +/* + * When all pins are inputs (config=0xFF) and no external driver, + * PCA9555 pull-ups should make the input register read all ones. + * Switching a pin to output mode with output=0 should drive it low. + */ +static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF); + + i2c_set8(dev, PCA9535_OUTPUT0, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF); + + i2c_set8(dev, PCA9535_CONFIG0, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00); +} + +/* + * Test that both ports are independent: changing port 0 registers + * should not affect port 1 and vice versa. + */ +static void test_port_independence(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, PCA9535_CONFIG0, 0x00); + i2c_set8(dev, PCA9535_OUTPUT0, 0x00); + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF); + + i2c_set8(dev, PCA9535_CONFIG1, 0x00); + i2c_set8(dev, PCA9535_OUTPUT1, 0xAA); + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xAA); + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x00); + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xAA); +} + +/* + * Polarity inversion: reading INPUT with polarity bits set should + * return the XOR of the actual input state and the polarity register. + */ +static void test_polarity_inversion(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF); + + i2c_set8(dev, PCA9535_POLARITY0, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00); + + i2c_set8(dev, PCA9535_POLARITY0, 0x0F); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xF0); + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF); + + i2c_set8(dev, PCA9535_POLARITY1, 0xAA); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0x55); +} + +/* Polarity inversion combined with output-driven pins. */ +static void test_polarity_with_output(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, PCA9535_CONFIG0, 0x00); + i2c_set8(dev, PCA9535_OUTPUT0, 0xA5); + + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xA5); + + i2c_set8(dev, PCA9535_POLARITY0, 0xFF); + g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x5A); + + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xA5); +} + +/* + * The PCA9555 auto-increments by toggling bit 0 of the command pointer + * within a register pair. Reading two bytes from INPUT0 should yield + * INPUT0 then INPUT1. + */ +static void test_auto_increment_read(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t buf[2]; + + i2c_set8(dev, PCA9535_CONFIG0, 0x00); + i2c_set8(dev, PCA9535_CONFIG1, 0x00); + i2c_set8(dev, PCA9535_OUTPUT0, 0xAA); + i2c_set8(dev, PCA9535_OUTPUT1, 0x55); + + i2c_read_block(dev, PCA9535_INPUT0, buf, 2); + g_assert_cmphex(buf[0], ==, 0xAA); + g_assert_cmphex(buf[1], ==, 0x55); + + i2c_read_block(dev, PCA9535_OUTPUT0, buf, 2); + g_assert_cmphex(buf[0], ==, 0xAA); + g_assert_cmphex(buf[1], ==, 0x55); +} + +/* + * Auto-increment write: writing two data bytes after a command byte + * should write to port 0 then port 1 of the addressed register pair. + */ +static void test_auto_increment_write(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t buf[2]; + + buf[0] = 0x12; + buf[1] = 0x34; + i2c_write_block(dev, PCA9535_OUTPUT0, buf, 2); + + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x12); + g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0x34); + + buf[0] = 0x0F; + buf[1] = 0xF0; + i2c_write_block(dev, PCA9535_CONFIG0, buf, 2); + + g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0x0F); + g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xF0); +} + +/* + * Auto-increment toggles within the pair: starting from port 1 should + * wrap back to port 0 (toggle bit 0). + */ +static void test_auto_increment_toggle(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + uint8_t buf[2]; + + i2c_set8(dev, PCA9535_OUTPUT0, 0xAA); + i2c_set8(dev, PCA9535_OUTPUT1, 0x55); + + i2c_read_block(dev, PCA9535_OUTPUT1, buf, 2); + g_assert_cmphex(buf[0], ==, 0x55); + g_assert_cmphex(buf[1], ==, 0xAA); +} + +/* + * Verify the command byte wraps at 3 bits: register addresses + * beyond 7 should alias to the same register (bits [2:0] only). + */ +static void test_command_wrapping(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *dev = (QI2CDevice *)obj; + + i2c_set8(dev, PCA9535_OUTPUT0, 0x42); + + g_assert_cmphex(i2c_get8(dev, 0x0A), ==, 0x42); +} + +static void pca9555_register_nodes(void) +{ + QOSGraphEdgeOptions opts = { + .extra_device_opts = "address=0x20" + }; + add_qi2c_address(&opts, &(QI2CAddress) { PCA9555_TEST_ADDR }); + + qos_node_create_driver("pca9555", i2c_device_create); + qos_node_consumes("pca9555", "i2c-bus", &opts); + + qos_add_test("reset-defaults", "pca9555", test_reset_defaults, NULL); + qos_add_test("output-drives-input", "pca9555", test_output_drives_input, + NULL); + qos_add_test("input-pullup", "pca9555", test_input_pullup, NULL); + qos_add_test("port-independence", "pca9555", test_port_independence, NULL); + qos_add_test("polarity-inversion", "pca9555", test_polarity_inversion, + NULL); + qos_add_test("polarity-with-output", "pca9555", test_polarity_with_output, + NULL); + qos_add_test("auto-increment-read", "pca9555", test_auto_increment_read, + NULL); + qos_add_test("auto-increment-write", "pca9555", test_auto_increment_write, + NULL); + qos_add_test("auto-increment-toggle", "pca9555", test_auto_increment_toggle, + NULL); + qos_add_test("command-wrapping", "pca9555", test_command_wrapping, NULL); +} + +libqos_init(pca9555_register_nodes);
diff --git a/tests/qtest/qct-qtimer-test.c b/tests/qtest/qct-qtimer-test.c new file mode 100644 index 0000000..fd8bb0a --- /dev/null +++ b/tests/qtest/qct-qtimer-test.c
@@ -0,0 +1,385 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + * + * QTest testcase for the QCT QTimer + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" +#include "hw/hexagon/hexagon.h" +#include "qemu/bitops.h" + +#include "hw/hexagon/machine_cfg_v68n_1024.h.inc" +#include "hw/hexagon/machine_cfg_v66g_1024.h.inc" + +#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL + +#define QCT_QTIMER_CNTPCT_LO (0x000) +#define QCT_QTIMER_CNTPCT_HI (0x004) +#define QCT_QTIMER_CNT_FREQ (0x010) +#define QCT_QTIMER_CNTP_CVAL_LO (0x020) +#define QCT_QTIMER_CNTP_TVAL (0x028) +#define QCT_QTIMER_CNTP_CTL (0x02c) +#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0) + +#define QTIMER_FRAME_STRIDE 0x1000 +/* Frames instantiated by hex-subsys, and the L2VIC input frame 0 drives. */ +#define QTIMER_NR_FRAMES 3 +#define QTIMER_L2VIC_IRQ_BASE 2 + +#define QCT_QTIMER_AC_CNTFRQ (0x000) +#define QCT_QTIMER_AC_CNTSR (0x004) +#define QCT_QTIMER_AC_CNTTID_0 (0x08) +#define QCT_QTIMER_AC_CNTACR_START (0x40) +#define QCT_QTIMER_AC_CNTACR_ALL (0x3f) +#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */ +#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */ +#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */ + +static uint64_t qtimer_view_base; +static uint64_t qtimer_ac_base; + +#define TIMER_TEST_OFFSET 1000 +/* TIMER_TEST_OFFSET ticks expressed in nanoseconds of QEMU_CLOCK_VIRTUAL */ +#define TIMER_TEST_NS \ + ((TIMER_TEST_OFFSET * 1000000000ULL) / QTIMER_DEFAULT_FREQ_HZ) + +static uint32_t qtimer_read32(uint64_t base, uint32_t offset) +{ + return readl(base + offset); +} + +static void qtimer_write32(uint64_t base, uint32_t offset, uint32_t value) +{ + writel(base + offset, value); +} + +static uint64_t qtimer_read64(uint64_t base, uint32_t offset) +{ + uint32_t lo = qtimer_read32(base, offset); + uint32_t hi = qtimer_read32(base, offset + 4); + + return ((uint64_t)hi << 32) | lo; +} + +static void qtimer_write64(uint64_t base, uint32_t offset, uint64_t value) +{ + qtimer_write32(base, offset, extract64(value, 0, 32)); + qtimer_write32(base, offset + 4, extract64(value, 32, 32)); +} + +static void test_qtimer_basic_access(void) +{ + uint32_t val; + + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ); + g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ); +} + +static void test_qtimer_multiple_frames(void) +{ + uint32_t val; + uint64_t frame0_base = qtimer_view_base; + uint64_t frame1_base = qtimer_view_base + 0x1000; + + val = qtimer_read32(frame0_base, QCT_QTIMER_CNT_FREQ); + g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ); + + val = qtimer_read32(frame1_base, QCT_QTIMER_CNT_FREQ); + g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ); +} + +static void test_qtimer_register_reads(void) +{ + qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ); + qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO); + qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL); +} + +static void test_qtimer_control_registers(void) +{ + uint32_t ctl_val; + uint64_t cval_before, cval_after; + + cval_before = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + + qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL, 1000); + + qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1); + ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + g_assert_cmpuint(ctl_val & 1, ==, 1); + + /* CVAL should be greater than before since we set TVAL */ + cval_after = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO); + g_assert_cmpuint(cval_after, >, cval_before); + + qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0); + ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + g_assert_cmpuint(ctl_val & 1, ==, 0); +} + +static void test_qtimer_cval_access(void) +{ + uint64_t current_time, test_cval, read_cval; + + current_time = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + test_cval = current_time + 10000; + + qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, test_cval); + read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO); + g_assert_cmpuint(read_cval, ==, test_cval); +} + +static void test_qtimer_counter_progression(void) +{ + uint32_t freq; + uint64_t count1, count2; + + /* + * In qtest mode the virtual clock does not advance on its own, so + * reading the counter twice must give the same value. + */ + count1 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + count2 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + g_assert_cmpuint(count2, ==, count1); + + freq = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ); + g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ); +} + +static void test_qtimer_timer_behavior(void) +{ + uint64_t current_count, target_count, read_cval, new_count; + uint64_t ctl_val, count_after_disable; + + current_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + + target_count = current_count + TIMER_TEST_OFFSET; + qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, target_count); + + read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO); + g_assert_cmpuint(read_cval, ==, target_count); + + qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1); + + ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + /* EN set, IMASK clear, ISTAT not yet pending */ + g_assert_cmpuint(ctl_val, ==, 0x1); + + /* Step forward but not past the target */ + qtest_clock_step(global_qtest, TIMER_TEST_NS / 2); + new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + g_assert_cmpuint(new_count, >=, current_count); + + /* Step past the target */ + qtest_clock_step(global_qtest, TIMER_TEST_NS); + new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + g_assert_cmpuint(new_count, >=, target_count); + + qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0); + + ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + /* EN cleared, ISTAT set since new_count >= target_count */ + g_assert_cmpuint(ctl_val, ==, 0x4); + + /* + * CNTPCT runs independently of CNTP_CTL.EN: only the compare/IRQ + * logic is gated by EN, so the counter must keep advancing. + */ + qtest_clock_step(global_qtest, TIMER_TEST_NS / 2); + count_after_disable = qtimer_read64(qtimer_view_base, + QCT_QTIMER_CNTPCT_LO); + g_assert_cmpuint(count_after_disable, >, new_count); + + /* ISTAT remains set while CNTPCT >= CVAL, even with EN=0 */ + ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + g_assert_cmpuint(ctl_val, ==, 0x4); +} + +/* Test the access-control region: CNTFRQ, CNTSR, CNTTID_0, CNTACR frame 0 */ +static void test_qtimer_ac_region(void) +{ + uint32_t freq, sr, tid0, acr0; + + freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ); + g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ); + + /* A write of 0 to CNTFRQ must be ignored (freq-hz must stay nonzero). */ + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ, 0); + freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ); + g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ); + + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR, 0x3); + sr = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR); + g_assert_cmpuint(sr, ==, 0x3); + + tid0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTTID_0); + g_assert_cmpuint(tid0, ==, 0x111); + + /* CNTACR for frame 0 defaults to full read/write permissions. */ + acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START); + g_assert_cmpuint(acr0, !=, 0); + + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, 0); + acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START); + g_assert_cmpuint(acr0, ==, 0); + + /* Restore full permissions so later view-region tests keep working. */ + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, + QCT_QTIMER_AC_CNTACR_ALL); +} + +static void test_qtimer_access_denied(void) +{ + uint32_t acr_all = QCT_QTIMER_AC_CNTACR_ALL; + uint64_t cval, saved_cval; + uint32_t val; + + /* Park CVAL at a known nonzero value while access is still permitted. */ + saved_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO) + 10000; + qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, saved_cval); + g_assert_cmpuint(qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO), + ==, saved_cval); + + /* Clearing RFRQ denies CNTFRQ reads. */ + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, + acr_all & ~QCT_QTIMER_AC_CNTACR_RFRQ); + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ); + g_assert_cmpuint(val, ==, 0); + + /* Clearing RPCT denies CNTPCT reads, both halves. */ + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, + acr_all & ~QCT_QTIMER_AC_CNTACR_RPCT); + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTPCT_LO); + g_assert_cmpuint(val, ==, 0); + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTPCT_HI); + g_assert_cmpuint(val, ==, 0); + + /* + * Clearing RWPT denies the whole CNTP_* set: CVAL/TVAL/CTL reads all + * read back 0 even though CVAL holds saved_cval. + */ + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, + acr_all & ~QCT_QTIMER_AC_CNTACR_RWPT); + cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO); + g_assert_cmpuint(cval, ==, 0); + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL); + g_assert_cmpuint(val, ==, 0); + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + g_assert_cmpuint(val, ==, 0); + + /* Denied writes must be dropped rather than applied. */ + qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, 0x1234); + qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL, 0x5678); + qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1); + + /* Restoring RWPT reveals that CVAL still holds the pre-denial value. */ + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, acr_all); + cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO); + g_assert_cmpuint(cval, ==, saved_cval); + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL); + g_assert_cmpuint(val & 1, ==, 0); + + /* CNTFRQ and CNTPCT work again once permissions are back. */ + val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ); + g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ); + + /* + * An unimplemented offset in the view region is also an access error, + * and reads back as 0. + */ + val = qtimer_read32(qtimer_view_base, 0x100); + g_assert_cmpuint(val, ==, 0); + + /* Leave the frame with full permissions for any later test. */ + qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, acr_all); +} + +static void test_qtimer_frame_irq_routing(void) +{ + unsigned int frame, other; + uint64_t frame_base; + + qtest_irq_intercept_in(global_qtest, "/machine/l2vic"); + + for (frame = 0; frame < QTIMER_NR_FRAMES; frame++) { + frame_base = qtimer_view_base + frame * QTIMER_FRAME_STRIDE; + + qtimer_write32(frame_base, QCT_QTIMER_CNTP_TVAL, TIMER_TEST_OFFSET); + qtimer_write32(frame_base, QCT_QTIMER_CNTP_CTL, + QCT_QTIMER_CNTP_CTL_ENABLE); + g_assert_false(qtest_get_irq(global_qtest, + QTIMER_L2VIC_IRQ_BASE + frame)); + + /* Step past the deadline so the frame raises its interrupt. */ + qtest_clock_step(global_qtest, TIMER_TEST_NS * 2); + g_assert_true(qtest_get_irq(global_qtest, + QTIMER_L2VIC_IRQ_BASE + frame)); + + /* No other frame's line may be disturbed. */ + for (other = 0; other < QTIMER_NR_FRAMES; other++) { + if (other != frame) { + g_assert_false(qtest_get_irq(global_qtest, + QTIMER_L2VIC_IRQ_BASE + other)); + } + } + + /* Clearing EN drops the interrupt, leaving a clean slate. */ + qtimer_write32(frame_base, QCT_QTIMER_CNTP_CTL, 0); + g_assert_false(qtest_get_irq(global_qtest, + QTIMER_L2VIC_IRQ_BASE + frame)); + } +} + +typedef struct { + const char *machine; + const struct hexagon_machine_config *cfg; +} QtimerMachineCfg; + +static const QtimerMachineCfg qtimer_machines[] = { + { "virt", &v68n_1024 }, + { "V66G_1024", &v66g_1024 }, +}; + +static void test_qtimer_on_machine(gconstpointer data) +{ + const QtimerMachineCfg *mc = data; + g_autofree char *args = g_strdup_printf( + "-machine %s -global qct-qtimer.freq-scale=1", mc->machine); + + qtimer_view_base = mc->cfg->qtmr_region; + qtimer_ac_base = mc->cfg->csr_base; + + qtest_start(args); + + test_qtimer_basic_access(); + test_qtimer_multiple_frames(); + test_qtimer_register_reads(); + test_qtimer_control_registers(); + test_qtimer_cval_access(); + test_qtimer_counter_progression(); + test_qtimer_timer_behavior(); + test_qtimer_ac_region(); + test_qtimer_access_denied(); + test_qtimer_frame_irq_routing(); + + qtest_end(); +} + +int main(int argc, char **argv) +{ + size_t i; + + g_test_init(&argc, &argv, NULL); + + for (i = 0; i < ARRAY_SIZE(qtimer_machines); i++) { + g_autofree char *path = g_strdup_printf("/qct-qtimer/%s/all-tests", + qtimer_machines[i].machine); + qtest_add_data_func(path, &qtimer_machines[i], test_qtimer_on_machine); + } + + return g_test_run(); +}
diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target index a2a0ffc..61adf63 100644 --- a/tests/tcg/hexagon/Makefile.target +++ b/tests/tcg/hexagon/Makefile.target
@@ -50,7 +50,12 @@ HEX_TESTS += scatter_gather HEX_TESTS += hvx_misc HEX_TESTS += hvx_histogram +HEX_TESTS += fp_hvx +HEX_TESTS += fp_hvx_cvt +HEX_TESTS += fp_hvx_cmp +HEX_TESTS += fp_hvx_disabled HEX_TESTS += invalid-slots +HEX_TESTS += valid-slots HEX_TESTS += invalid-encoding HEX_TESTS += multiple-writes HEX_TESTS += unaligned_pc @@ -135,6 +140,16 @@ v69_hvx: v69_hvx.c hvx_misc.h v69_hvx: CFLAGS += -mhvx -Wno-unused-function v73_scalar: CFLAGS += -Wno-unused-function +fp_hvx: fp_hvx.c hvx_misc.h hex_test.h +fp_hvx: CFLAGS += -mhvx -mhvx-ieee-fp +fp_hvx_disabled: fp_hvx_disabled.c hvx_misc.h hex_test.h +fp_hvx_disabled: CFLAGS += -mhvx -mhvx-ieee-fp +fp_hvx_cvt: fp_hvx_cvt.c hvx_misc.h hex_test.h +fp_hvx_cvt: CFLAGS += -mhvx -mhvx-ieee-fp +fp_hvx_cmp: fp_hvx_cmp.c hvx_misc.h hex_test.h +fp_hvx_cmp: CFLAGS += -mhvx -mhvx-ieee-fp + +run-fp_hvx_disabled: QEMU_OPTS += -cpu v73,ieee-fp=false hvx_histogram: hvx_histogram.c hvx_histogram_row.S $(CC) $(CFLAGS) $(CROSS_CC_GUEST_CFLAGS) $^ -o $@ $(LDFLAGS)
diff --git a/tests/tcg/hexagon/fp_hvx.c b/tests/tcg/hexagon/fp_hvx.c new file mode 100644 index 0000000..4543a0a --- /dev/null +++ b/tests/tcg/hexagon/fp_hvx.c
@@ -0,0 +1,226 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <hexagon_types.h> +#include <hvx_hexagon_protos.h> + +int err; +#include "hvx_misc.h" + +#if __HEXAGON_ARCH__ > 75 +#error "After v75, compiler will replace some FP HVX instructions." +#endif + +/****************************************************************************** + * NAN handling + *****************************************************************************/ + +#define isnan(X) \ + (sizeof(X) == bytes_hf ? ((raw_hf(X) & ~0x8000) > 0x7c00) : \ + ((raw_sf(X) & ~(1 << 31)) > 0x7f800000UL)) + +#define CHECK_NAN(A, DEF_NAN) (isnan(A) ? DEF_NAN : (A)) +#define NAN_SF float_sf(0x7FFFFFFF) +#define NAN_HF float_hf(0x7FFF) +#define NAN_BF float_hf(0x7FFF) + +/****************************************************************************** + * Binary operations + *****************************************************************************/ + +#define DEF_TEST_OP_2(vop, op, type_res, type_arg) \ + static void test_##vop##_##type_res##_##type_arg(void) \ + { \ + memset(expect, 0xff, sizeof(expect)); \ + memset(output, 0xff, sizeof(output)); \ + for (int i = 0; i < BUFSIZE; i++) { \ + HVX_Vector *hvx_output = (HVX_Vector *)&output[i]; \ + HVX_Vector hvx_buffer0 = *(HVX_Vector *)&buffer0[i]; \ + HVX_Vector hvx_buffer1 = *(HVX_Vector *)&buffer1[i]; \ + *hvx_output = \ + Q6_V##type_res##_##vop##_V##type_arg##V##type_arg(hvx_buffer0, \ + hvx_buffer1); \ + for (int j = 0; j < MAX_VEC_SIZE_BYTES / bytes_##type_res; j++) { \ + expect[i].type_res[j] = \ + raw_##type_res(op(float_##type_arg(buffer0[i].type_arg[j]), \ + float_##type_arg(buffer1[i].type_arg[j]))); \ + } \ + } \ + check_output_##type_res(__LINE__, BUFSIZE); \ + } + +#define SUM(X, Y, DEF_NAN) CHECK_NAN((X) + (Y), DEF_NAN) +#define SUB(X, Y, DEF_NAN) CHECK_NAN((X) - (Y), DEF_NAN) +#define MULT(X, Y, DEF_NAN) CHECK_NAN((X) * (Y), DEF_NAN) + +#define SUM_SF(X, Y) SUM(X, Y, NAN_SF) +#define SUM_HF(X, Y) SUM(X, Y, NAN_HF) +#define SUB_SF(X, Y) SUB(X, Y, NAN_SF) +#define SUB_HF(X, Y) SUB(X, Y, NAN_HF) +#define MULT_SF(X, Y) MULT(X, Y, NAN_SF) +#define MULT_HF(X, Y) MULT(X, Y, NAN_HF) + +DEF_TEST_OP_2(vadd, SUM_SF, sf, sf); +DEF_TEST_OP_2(vadd, SUM_HF, hf, hf); +DEF_TEST_OP_2(vsub, SUB_SF, sf, sf); +DEF_TEST_OP_2(vsub, SUB_HF, hf, hf); +DEF_TEST_OP_2(vmpy, MULT_SF, sf, sf); +DEF_TEST_OP_2(vmpy, MULT_HF, hf, hf); + +#define signbit_fp(X) \ + (sizeof(X) == bytes_hf ? ((raw_hf(X) & 0x8000) != 0) : \ + ((raw_sf(X) & 0x80000000) != 0)) + +#define STD_MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define STD_MAX(X, Y) ((X) > (Y) ? (X) : (Y)) + +#define MIN(X, Y, DEF_NAN) \ + ((isnan(X) || isnan(Y)) ? DEF_NAN : \ + ((X) != (Y)) ? STD_MIN(X, Y) : (signbit_fp(X) ? (X) : (Y))) /* -0 < +0 */ +#define MAX(X, Y, DEF_NAN) \ + ((isnan(X) || isnan(Y)) ? DEF_NAN : \ + ((X) != (Y)) ? STD_MAX(X, Y) : (signbit_fp(X) ? (Y) : (X))) /* -0 < +0 */ + +#define MIN_HF(X, Y) MIN(X, Y, NAN_HF) +#define MAX_HF(X, Y) MAX(X, Y, NAN_HF) +#define MIN_SF(X, Y) MIN(X, Y, NAN_SF) +#define MAX_SF(X, Y) MAX(X, Y, NAN_SF) +#define MIN_BF(X, Y) MIN(X, Y, NAN_BF) +#define MAX_BF(X, Y) MAX(X, Y, NAN_BF) + +DEF_TEST_OP_2(vfmin, MIN_SF, sf, sf); +DEF_TEST_OP_2(vfmax, MAX_SF, sf, sf); +DEF_TEST_OP_2(vfmin, MIN_HF, hf, hf); +DEF_TEST_OP_2(vfmax, MAX_HF, hf, hf); +DEF_TEST_OP_2(vmin, MIN_BF, bf, bf); +DEF_TEST_OP_2(vmax, MAX_BF, bf, bf); + +#define DEF_TEST_OP_2_INTERLEAVED(vop, op, type_res, type_arg) \ + static void test_##vop##_##type_res##_##type_arg(void) \ + { \ + memset(expect, 0xff, sizeof(expect)); \ + memset(output, 0xff, sizeof(output)); \ + for (int i = 0; i < BUFSIZE / 2; i++) { \ + HVX_VectorPair *hvx_output = (HVX_VectorPair *)&output[2 * i]; \ + HVX_Vector hvx_buffer0 = *(HVX_Vector *)&buffer0[i]; \ + HVX_Vector hvx_buffer1 = *(HVX_Vector *)&buffer1[i]; \ + *hvx_output = \ + Q6_W##type_res##_##vop##_V##type_arg##V##type_arg(hvx_buffer0, \ + hvx_buffer1); \ + for (int j = 0; j < MAX_VEC_SIZE_BYTES / bytes_##type_res; j++) { \ + expect[2 * i].type_res[j] = \ + raw_##type_res(op(float_##type_arg(buffer0[i].type_arg[2 * j]), \ + float_##type_arg(buffer1[i].type_arg[2 * j]))); \ + expect[2 * i + 1].type_res[j] = \ + raw_##type_res(op(float_##type_arg(buffer0[i].type_arg[2 * j + 1]), \ + float_##type_arg(buffer1[i].type_arg[2 * j + 1]))); \ + } \ + } \ + check_output_##type_res(__LINE__, BUFSIZE); \ + } + +DEF_TEST_OP_2_INTERLEAVED(vadd, SUM_SF, sf, bf); +DEF_TEST_OP_2_INTERLEAVED(vsub, SUB_SF, sf, bf); +DEF_TEST_OP_2_INTERLEAVED(vmpy, MULT_SF, sf, bf); + +/****************************************************************************** + * Other tests + *****************************************************************************/ + +static void test_vdmpy_sf_hf(bool acc) +{ + memset(expect, 0xff, sizeof(expect)); + + for (int i = 0; i < BUFSIZE; i++) { + HVX_Vector hvx_buffer0 = *(HVX_Vector *)&buffer0[i]; + HVX_Vector hvx_buffer1 = *(HVX_Vector *)&buffer1[i]; + HVX_Vector *hvx_output = (HVX_Vector *)&output[i]; + + uint32_t PREFIL_VAL = 0x111222; + *hvx_output = Q6_V_vsplat_R(PREFIL_VAL); + + if (!acc) { + *hvx_output = Q6_Vsf_vdmpy_VhfVhf(hvx_buffer0, hvx_buffer1); + } else { + *hvx_output = Q6_Vsf_vdmpyacc_VsfVhfVhf(*hvx_output, hvx_buffer0, + hvx_buffer1); + } + + for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) { + float a1 = float_hf_to_sf(float_hf(buffer0[i].hf[2 * j + 1])); + float a2 = float_hf_to_sf(float_hf(buffer0[i].hf[2 * j])); + float a3 = float_hf_to_sf(float_hf(buffer1[i].hf[2 * j + 1])); + float a4 = float_hf_to_sf(float_hf(buffer1[i].hf[2 * j])); + /* + * Note, IEEE FP specifies +0.0 + -0.0 == +0.0. So we use -0.0 in + * the default case to preserve the zero sign. + */ + float prev = acc ? float_sf(PREFIL_VAL) : -0.0; + expect[i].sf[j] = raw_sf(CHECK_NAN((a1 * a3) + (a2 * a4) + prev, NAN_SF)); + } + } + check_output_sf(__LINE__, BUFSIZE); +} + +static void test_new(void) +{ + asm volatile("r0 = #%2\n" + "v0 = vsplat(r0)\n" + "vmem(%1 + #0) = v0\n" + "r1 = #%3\n" + "v1 = vsplat(r1)\n" + "v2 = vsplat(r1)\n" + "{\n" + " v0.sf = vadd(v1.sf, v2.sf)\n" + " vmem(%0 + #0) = v0.new\n" + "}\n" + : + : "r"(output), "r"(expect), "i"(SF_two), "i"(SF_one) + : "r0", "r1", "v0", "v1", "v2", "memory"); + check_output_w(__LINE__, 1); +} + +int main(void) +{ + init_buffers_fp(); + + /* add/sub */ + test_vadd_sf_sf(); + test_vadd_hf_hf(); + test_vsub_sf_sf(); + test_vsub_hf_hf(); + + /* multiply */ + test_vmpy_sf_sf(); + test_vmpy_hf_hf(); + + /* dot product */ + test_vdmpy_sf_hf(false); + test_vdmpy_sf_hf(true); + + test_new(); + + /* min/max */ + test_vfmin_sf_sf(); + test_vfmin_hf_hf(); + test_vfmax_sf_sf(); + test_vfmax_hf_hf(); + + /* bfloat */ + init_buffers_bf(); + test_vmin_bf_bf(); + test_vmax_bf_bf(); + test_vadd_sf_bf(); + test_vsub_sf_bf(); + test_vmpy_sf_bf(); + + puts(err ? "FAIL" : "PASS"); + return err ? 1 : 0; +}
diff --git a/tests/tcg/hexagon/fp_hvx_cmp.c b/tests/tcg/hexagon/fp_hvx_cmp.c new file mode 100644 index 0000000..63fb423 --- /dev/null +++ b/tests/tcg/hexagon/fp_hvx_cmp.c
@@ -0,0 +1,275 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <assert.h> +#include <hexagon_types.h> +#include <hvx_hexagon_protos.h> + +#if __HEXAGON_ARCH__ > 75 +#error "After v75, compiler will replace some FP HVX instructions." +#endif + +int err; +#include "hvx_misc.h" +#include "hex_test.h" + +#define MAX_TESTS_hf (MAX_VEC_SIZE_BYTES / 2) +#define MAX_TESTS_sf (MAX_VEC_SIZE_BYTES / 4) +#define MAX_TESTS_bf (MAX_VEC_SIZE_BYTES / 2) + +#define TRUE_MASK_sf 0xffffffff +#define TRUE_MASK_hf 0xffff +#define TRUE_MASK_bf 0xffff + +static const char *comparisons[MAX_TESTS_sf][2]; +static HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; +static HVX_Vector buffers[2], true_vec, false_vec; +static int exp_index; + +#define ADD_TEST_CMP(TYPE, VAL1, VAL2, EXP) do { \ + ((MMVector *)&buffers[0])->TYPE[exp_index] = VAL1; \ + ((MMVector *)&buffers[1])->TYPE[exp_index] = VAL2; \ + expect[0].TYPE[exp_index] = EXP ? TRUE_MASK_##TYPE : 0; \ + comparisons[exp_index][0] = #VAL1; \ + comparisons[exp_index][1] = #VAL2; \ + assert(exp_index < MAX_TESTS_##TYPE); \ + exp_index++; \ +} while (0) + +#define TEST_CMP_GT(TYPE, VAL1, VAL2) do { \ + ADD_TEST_CMP(TYPE, VAL1, VAL2, true); \ + ADD_TEST_CMP(TYPE, VAL2, VAL1, false); \ +} while (0) + +#define PREP_TEST() do { \ + memset(&buffers, 0, sizeof(buffers)); \ + memset(expect, 0, sizeof(expect)); \ + exp_index = 0; \ +} while (0) + +#define CHECK(TYPE, TYPESZ) do { \ + HVX_VectorPred pred = Q6_Q_vcmp_gt_V##TYPE##V##TYPE(buffers[0], buffers[1]); \ + *hvx_output = Q6_V_vmux_QVV(pred, true_vec, false_vec); \ + for (int j = 0; j < MAX_VEC_SIZE_BYTES / TYPESZ; j++) { \ + if (output[0].TYPE[j] != expect[0].TYPE[j]) { \ + printf("ERROR: expected %s %s %s\n", comparisons[j][0], \ + (expect[0].TYPE[j] != 0 ? ">" : "<="), comparisons[j][1]); \ + err++; \ + } \ + } \ +} while (0) + +static void test_cmp_sf(void) +{ + /* + * General ordering for sf: + * QNaN > SNaN > +Inf > numbers > -Inf > SNaN_neg > QNaN_neg + */ + + /* Test equality */ + PREP_TEST(); + ADD_TEST_CMP(sf, raw_sf(2.2), raw_sf(2.2), false); + ADD_TEST_CMP(sf, SF_SNaN, SF_SNaN, false); + CHECK(sf, 4); + + /* Common numbers */ + PREP_TEST(); + TEST_CMP_GT(sf, raw_sf(2.2), raw_sf(2.1)); + TEST_CMP_GT(sf, raw_sf(0), raw_sf(-2.2)); + CHECK(sf, 4); + + /* Infinity vs Infinity/NaN */ + PREP_TEST(); + TEST_CMP_GT(sf, SF_QNaN, SF_INF); + TEST_CMP_GT(sf, SF_SNaN, SF_INF); + TEST_CMP_GT(sf, SF_INF, SF_INF_neg); + TEST_CMP_GT(sf, SF_INF, SF_SNaN_neg); + TEST_CMP_GT(sf, SF_INF, SF_QNaN_neg); + TEST_CMP_GT(sf, SF_INF_neg, SF_SNaN_neg); + TEST_CMP_GT(sf, SF_INF_neg, SF_QNaN_neg); + TEST_CMP_GT(sf, SF_SNaN, SF_INF_neg); + TEST_CMP_GT(sf, SF_QNaN, SF_INF_neg); + CHECK(sf, 4); + + /* NaN vs NaN */ + PREP_TEST(); + TEST_CMP_GT(sf, SF_QNaN, SF_SNaN); + TEST_CMP_GT(sf, SF_SNaN, SF_SNaN_neg); + TEST_CMP_GT(sf, SF_SNaN_neg, SF_QNaN_neg); + CHECK(sf, 4); + + /* NaN vs non-NaN */ + PREP_TEST(); + TEST_CMP_GT(sf, SF_QNaN, SF_one); + TEST_CMP_GT(sf, SF_SNaN, SF_one); + TEST_CMP_GT(sf, SF_one, SF_QNaN_neg); + TEST_CMP_GT(sf, SF_one, SF_SNaN_neg); + CHECK(sf, 4); +} + +static void test_cmp_hf(void) +{ + /* + * General ordering for hf: + * QNaN > SNaN > +Inf > numbers > -Inf > QSNaN_neg > QNaN_neg + */ + + /* Test equality */ + PREP_TEST(); + ADD_TEST_CMP(hf, raw_hf((_Float16)2.2), raw_hf((_Float16)2.2), false); + ADD_TEST_CMP(hf, HF_SNaN, HF_SNaN, false); + CHECK(hf, 2); + + /* Common numbers */ + PREP_TEST(); + TEST_CMP_GT(hf, raw_hf((_Float16)2.2), raw_hf((_Float16)2.1)); + TEST_CMP_GT(hf, raw_hf((_Float16)0), raw_hf((_Float16) - 2.2)); + CHECK(hf, 2); + + /* Infinity vs Infinity/NaN */ + PREP_TEST(); + TEST_CMP_GT(hf, HF_QNaN, HF_INF); + TEST_CMP_GT(hf, HF_SNaN, HF_INF); + TEST_CMP_GT(hf, HF_INF, HF_INF_neg); + TEST_CMP_GT(hf, HF_INF, HF_SNaN_neg); + TEST_CMP_GT(hf, HF_INF, HF_QNaN_neg); + TEST_CMP_GT(hf, HF_INF_neg, HF_SNaN_neg); + TEST_CMP_GT(hf, HF_INF_neg, HF_QNaN_neg); + TEST_CMP_GT(hf, HF_SNaN, HF_INF_neg); + TEST_CMP_GT(hf, HF_QNaN, HF_INF_neg); + CHECK(hf, 2); + + /* NaN vs NaN */ + PREP_TEST(); + TEST_CMP_GT(hf, HF_QNaN, HF_SNaN); + TEST_CMP_GT(hf, HF_SNaN, HF_SNaN_neg); + TEST_CMP_GT(hf, HF_SNaN_neg, HF_QNaN_neg); + CHECK(hf, 2); + + /* NaN vs non-NaN */ + PREP_TEST(); + TEST_CMP_GT(hf, HF_QNaN, HF_one); + TEST_CMP_GT(hf, HF_SNaN, HF_one); + TEST_CMP_GT(hf, HF_one, HF_QNaN_neg); + TEST_CMP_GT(hf, HF_one, HF_SNaN_neg); + CHECK(hf, 2); +} + +static void test_cmp_bf(void) +{ + /* + * General ordering for bf: + * QNaN > SNaN > +Inf > numbers > -Inf > SNaN_neg > QNaN_neg + */ + + /* Test equality */ + PREP_TEST(); + ADD_TEST_CMP(bf, 0, 0, false); + ADD_TEST_CMP(bf, BF_SNaN, BF_SNaN, false); + CHECK(bf, 2); + + /* Common numbers */ + PREP_TEST(); + TEST_CMP_GT(bf, BF_two, BF_one); + TEST_CMP_GT(bf, BF_one, BF_zero); + CHECK(bf, 2); + + /* Infinity vs Infinity/NaN */ + PREP_TEST(); + TEST_CMP_GT(bf, BF_QNaN, BF_INF); + TEST_CMP_GT(bf, BF_SNaN, BF_INF); + TEST_CMP_GT(bf, BF_INF, BF_INF_neg); + TEST_CMP_GT(bf, BF_INF, BF_SNaN_neg); + TEST_CMP_GT(bf, BF_INF, BF_QNaN_neg); + TEST_CMP_GT(bf, BF_INF_neg, BF_SNaN_neg); + TEST_CMP_GT(bf, BF_INF_neg, BF_QNaN_neg); + TEST_CMP_GT(bf, BF_SNaN, BF_INF_neg); + TEST_CMP_GT(bf, BF_QNaN, BF_INF_neg); + CHECK(bf, 2); + + /* NaN vs NaN */ + PREP_TEST(); + TEST_CMP_GT(bf, BF_QNaN, BF_SNaN); + TEST_CMP_GT(bf, BF_SNaN, BF_SNaN_neg); + TEST_CMP_GT(bf, BF_SNaN_neg, BF_QNaN_neg); + CHECK(bf, 2); + + /* NaN vs non-NaN */ + PREP_TEST(); + TEST_CMP_GT(bf, BF_QNaN, BF_one); + TEST_CMP_GT(bf, BF_SNaN, BF_one); + TEST_CMP_GT(bf, BF_one, BF_QNaN_neg); + TEST_CMP_GT(bf, BF_one, BF_SNaN_neg); + CHECK(bf, 2); +} + +static void check_byte_pred(HVX_VectorPred pred, int byte_idx, uint8_t exp_mask, + int line) +{ + /* + * Note: ((uint8_t *)&pred)[N] returns the expanded value of bit N: + * 0xFF if bit is set, 0x00 if clear. + */ + for (int i = 0; i < 8; i++) { + int idx = byte_idx * 8 + i; + int val = ((uint8_t *)&pred)[idx]; + int exp = (exp_mask >> i) & 1 ? 0xff : 0x00; + if (exp != val) { + printf("ERROR line %d: pred bit %d is 0x%x, should be 0x%x\n", + line, idx, val, exp); + err++; + } + } +} + +#define CHECK_BYTE_PRED(PRED, BYTE, EXP) check_byte_pred(PRED, BYTE, EXP, __LINE__) + +static void test_cmp_variants(void) +{ + HVX_VectorPred pred; + + /* + * Setup: comparison result will have bits 4-7 set (0xF0 in pred byte 0) + * - sf[0]: SF_zero > SF_one = false -> bits 0-3 = 0 + * - sf[1]: SF_one > SF_zero = true -> bits 4-7 = 1 + */ + PREP_TEST(); + ADD_TEST_CMP(sf, SF_zero, SF_one, false); + ADD_TEST_CMP(sf, SF_one, SF_zero, true); + + /* greater and: 0xF0 & 0xF0 = 0xF0 */ + memset(&pred, 0xF0, sizeof(pred)); + pred = Q6_Q_vcmp_gtand_QVsfVsf(pred, buffers[0], buffers[1]); + CHECK_BYTE_PRED(pred, 0, 0xF0); + + /* greater or: 0x0F | 0xF0 = 0xFF */ + memset(&pred, 0x0F, sizeof(pred)); + pred = Q6_Q_vcmp_gtor_QVsfVsf(pred, buffers[0], buffers[1]); + CHECK_BYTE_PRED(pred, 0, 0xFF); + + /* greater xor: 0xFF ^ 0xF0 = 0x0F */ + memset(&pred, 0xFF, sizeof(pred)); + pred = Q6_Q_vcmp_gtxacc_QVsfVsf(pred, buffers[0], buffers[1]); + CHECK_BYTE_PRED(pred, 0, 0x0F); +} + +int main(void) +{ + memset(&true_vec, 0xff, sizeof(true_vec)); + memset(&false_vec, 0, sizeof(false_vec)); + + test_cmp_sf(); + test_cmp_hf(); + test_cmp_bf(); + test_cmp_variants(); + + puts(err ? "FAIL" : "PASS"); + return err ? 1 : 0; +}
diff --git a/tests/tcg/hexagon/fp_hvx_cvt.c b/tests/tcg/hexagon/fp_hvx_cvt.c new file mode 100644 index 0000000..7196bc9 --- /dev/null +++ b/tests/tcg/hexagon/fp_hvx_cvt.c
@@ -0,0 +1,219 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <hexagon_types.h> +#include <hvx_hexagon_protos.h> + +#if __HEXAGON_ARCH__ > 75 +#error "After v75, compiler will replace some FP HVX instructions." +#endif + +int err; +#include "hvx_misc.h" +#include "hex_test.h" + +#define NAN_BF 0x7FFF + +#define TEST_EXP(TO, FROM, VAL, EXP) do { \ + ((MMVector *)&buffer)->FROM[index] = VAL; \ + expect[0].TO[index] = EXP; \ + index++; \ +} while (0) + +#define DEF_TEST_CVT(TO, FROM, TESTS) \ + static void test_vcvt_##TO##_##FROM(void) \ + { \ + HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; \ + HVX_Vector buffer; \ + int index = 0; \ + memset(&buffer, 0, sizeof(buffer)); \ + memset(expect, 0, sizeof(expect)); \ + TESTS \ + * hvx_output = Q6_V##TO##_vcvt_V##FROM(buffer); \ + check_output_##TO(__LINE__, 1); \ + } + +DEF_TEST_CVT(uh, hf, { \ + TEST_EXP(uh, hf, HF_QNaN, UINT16_MAX); \ + TEST_EXP(uh, hf, HF_SNaN, UINT16_MAX); \ + TEST_EXP(uh, hf, HF_QNaN_neg, UINT16_MAX); \ + TEST_EXP(uh, hf, HF_INF, UINT16_MAX); \ + TEST_EXP(uh, hf, HF_INF_neg, 0); \ + TEST_EXP(uh, hf, HF_neg_two, 0); \ + TEST_EXP(uh, hf, HF_zero_neg, 0); \ + TEST_EXP(uh, hf, raw_hf((_Float16)2.1), 2); \ + TEST_EXP(uh, hf, HF_one_recip, 1); \ +}) + +DEF_TEST_CVT(h, hf, { \ + TEST_EXP(h, hf, HF_QNaN, INT16_MAX); \ + TEST_EXP(h, hf, HF_SNaN, INT16_MAX); \ + TEST_EXP(h, hf, HF_QNaN_neg, INT16_MAX); \ + TEST_EXP(h, hf, HF_INF, INT16_MAX); \ + TEST_EXP(h, hf, HF_INF_neg, INT16_MIN); \ + TEST_EXP(h, hf, HF_neg_two, -2); \ + TEST_EXP(h, hf, HF_zero_neg, 0); \ + TEST_EXP(h, hf, raw_hf((_Float16)2.1), 2); \ + TEST_EXP(h, hf, HF_one_recip, 1); \ +}) + +/* + * Some cvt operations take two vectors as input and perform the following: + * VdV.TO[4*i] = OP(VuV.FROM[2*i]); + * VdV.TO[4*i+1] = OP(VuV.FROM[2*i+1]); + * VdV.TO[4*i+2] = OP(VvV.FROM[2*i]); + * VdV.TO[4*i+3] = OP(VvV.FROM[2*i+1])) + * We use bf_index and index in a way that the tests are always done either + * using the first or third line of the above snippet. + */ +#define TEST_EXP_2(TO, FROM, VAL, EXP) do { \ + ((MMVector *)&buffers[bf_index])->FROM[2 * index] = VAL; \ + expect[0].TO[(4 * index) + (2 * bf_index)] = EXP; \ + index++; \ + bf_index = (bf_index + 1) % 2; \ +} while (0) + +#define DEF_TEST_CVT_2(TO, FROM, TESTS) \ + static void test_vcvt_##TO##_##FROM(void) \ + { \ + HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; \ + HVX_Vector buffers[2]; \ + int index = 0, bf_index = 0; \ + memset(&buffers, 0, sizeof(buffers)); \ + memset(expect, 0, sizeof(expect)); \ + TESTS \ + * hvx_output = Q6_V##TO##_vcvt_V##FROM##V##FROM(buffers[0], buffers[1]); \ + check_output_##TO(__LINE__, 1); \ + } + +DEF_TEST_CVT_2(ub, hf, { \ + TEST_EXP_2(ub, hf, HF_QNaN, UINT8_MAX); \ + TEST_EXP_2(ub, hf, HF_SNaN, UINT8_MAX); \ + TEST_EXP_2(ub, hf, HF_QNaN_neg, UINT8_MAX); \ + TEST_EXP_2(ub, hf, HF_INF, UINT8_MAX); \ + TEST_EXP_2(ub, hf, HF_INF_neg, 0); \ + TEST_EXP_2(ub, hf, HF_small_neg, 0); \ + TEST_EXP_2(ub, hf, HF_neg_two, 0); \ + TEST_EXP_2(ub, hf, HF_zero_neg, 0); \ + TEST_EXP_2(ub, hf, raw_hf((_Float16)2.1), 2); \ + TEST_EXP_2(ub, hf, HF_one_recip, 1); \ +}) + +DEF_TEST_CVT_2(b, hf, { \ + TEST_EXP_2(b, hf, HF_QNaN, INT8_MAX); \ + TEST_EXP_2(b, hf, HF_SNaN, INT8_MAX); \ + TEST_EXP_2(b, hf, HF_QNaN_neg, INT8_MAX); \ + TEST_EXP_2(b, hf, HF_INF, INT8_MAX); \ + TEST_EXP_2(b, hf, HF_INF_neg, INT8_MIN); \ + TEST_EXP_2(b, hf, HF_small_neg, 0); \ + TEST_EXP_2(b, hf, HF_neg_two, -2); \ + TEST_EXP_2(b, hf, HF_zero_neg, 0); \ + TEST_EXP_2(b, hf, raw_hf((_Float16)2.1), 2); \ + TEST_EXP_2(b, hf, HF_one_recip, 1); \ +}) + +#define DEF_TEST_VCONV(TO, FROM, TESTS) \ + static void test_vconv_##TO##_##FROM(void) \ + { \ + HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; \ + HVX_Vector buffer; \ + int index = 0; \ + memset(&buffer, 0, sizeof(buffer)); \ + memset(expect, 0, sizeof(expect)); \ + TESTS \ + * hvx_output = Q6_V##TO##_equals_V##FROM(buffer); \ + check_output_##TO(__LINE__, 1); \ + } + +DEF_TEST_VCONV(w, sf, { \ + TEST_EXP(w, sf, SF_QNaN, INT32_MAX); \ + TEST_EXP(w, sf, SF_SNaN, INT32_MAX); \ + TEST_EXP(w, sf, SF_QNaN_neg, INT32_MIN); \ + TEST_EXP(w, sf, SF_INF, INT32_MAX); \ + TEST_EXP(w, sf, SF_INF_neg, INT32_MIN); \ + TEST_EXP(w, sf, SF_small_neg, 0); \ + TEST_EXP(w, sf, SF_neg_two, -2); \ + TEST_EXP(w, sf, SF_zero_neg, 0); \ + TEST_EXP(w, sf, raw_sf(2.1f), 2); \ + TEST_EXP(w, sf, raw_sf(2.8f), 2); \ +}) + +DEF_TEST_VCONV(h, hf, { \ + TEST_EXP(h, hf, HF_QNaN, INT16_MAX); \ + TEST_EXP(h, hf, HF_SNaN, INT16_MAX); \ + TEST_EXP(h, hf, HF_QNaN_neg, INT16_MIN); \ + TEST_EXP(h, hf, HF_INF, INT16_MAX); \ + TEST_EXP(h, hf, HF_INF_neg, INT16_MIN); \ + TEST_EXP(h, hf, HF_small_neg, 0); \ + TEST_EXP(h, hf, HF_neg_two, -2); \ + TEST_EXP(h, hf, HF_zero_neg, 0); \ + TEST_EXP(h, hf, raw_hf((_Float16)2.1), 2); \ + TEST_EXP(h, hf, raw_hf((_Float16)2.8), 2); \ +}) + +DEF_TEST_VCONV(hf, h, { \ + TEST_EXP(hf, h, 0, HF_zero); \ + TEST_EXP(hf, h, 2, HF_two); \ + TEST_EXP(hf, h, -2, HF_neg_two); \ + TEST_EXP(hf, h, 2049, raw_hf((_Float16)2048)); /* rounds DOWN */ \ + TEST_EXP(hf, h, 2051, raw_hf((_Float16)2052)); /* rounds UP */ \ +}) + +DEF_TEST_VCONV(sf, w, { \ + TEST_EXP(sf, w, 0, SF_zero); \ + TEST_EXP(sf, w, 2, SF_two); \ + TEST_EXP(sf, w, -2, SF_neg_two); \ + TEST_EXP(sf, w, 16777217, raw_sf((float)16777216)); /* rounds DOWN */ \ + TEST_EXP(sf, w, 16777219, raw_sf((float)16777220)); /* rounds UP */ \ +}) + +#define TEST_EXP_BF(VAL, EXP) do { \ + ((MMVector *)&buffers[1])->sf[index] = VAL; \ + ((MMVector *)&buffers[0])->sf[index] = VAL; \ + expect[0].bf[2 * index] = EXP; \ + expect[0].bf[2 * index + 1] = EXP; \ + index++; \ +} while (0) + +static void test_vconv_bf_sf(void) +{ + HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; + HVX_Vector buffers[2]; + int index = 0; + memset(&buffers, 0, sizeof(buffers)); + memset(expect, 0, sizeof(expect)); + + TEST_EXP_BF(SF_QNaN, NAN_BF); + TEST_EXP_BF(SF_SNaN, NAN_BF); + TEST_EXP_BF(SF_QNaN_neg, NAN_BF); + TEST_EXP_BF(SF_INF, BF_INF); + TEST_EXP_BF(SF_INF_neg, BF_INF_neg); + TEST_EXP_BF(SF_one, BF_one); + TEST_EXP_BF(SF_zero_neg, BF_zero_neg); + + *hvx_output = Q6_Vbf_vcvt_VsfVsf(buffers[0], buffers[1]); + check_output_hf(__LINE__, 1); +} + +int main(void) +{ + test_vcvt_uh_hf(); + test_vcvt_h_hf(); + test_vcvt_ub_hf(); + test_vcvt_b_hf(); + test_vconv_w_sf(); + test_vconv_sf_w(); + test_vconv_h_hf(); + test_vconv_hf_h(); + test_vconv_bf_sf(); + + puts(err ? "FAIL" : "PASS"); + return err ? 1 : 0; +}
diff --git a/tests/tcg/hexagon/fp_hvx_disabled.c b/tests/tcg/hexagon/fp_hvx_disabled.c new file mode 100644 index 0000000..388a42e --- /dev/null +++ b/tests/tcg/hexagon/fp_hvx_disabled.c
@@ -0,0 +1,57 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <string.h> +#include <hexagon_types.h> +#include <hvx_hexagon_protos.h> + +int err; +#include "hvx_misc.h" + +static void test_disabled(void) +{ + memset(output, 0xAA, sizeof(output)); + memset(expect, 0, sizeof(expect)); + asm volatile("r0 = #0xff\n" + "v0 = vsplat(r0)\n" + "r1 = #0x1\n" + "v1 = vsplat(r1)\n" + "v2 = vsplat(r1)\n" + "v0.sf = vadd(v1.sf, v2.sf)\n" + "vmem(%0 + #0) = v0\n" + : + : "r"(output) + : "r0", "r1", "v0", "v1", "v2", "memory"); + check_output_w(__LINE__, 1); +} + +static void test_disabled_with_new(void) +{ + memset(output, 0xAA, sizeof(output)); + memset(expect, 0, sizeof(expect)); + asm volatile("r0 = #0xff\n" + "v0 = vsplat(r0)\n" + "r1 = #0x1\n" + "v1 = vsplat(r1)\n" + "v2 = vsplat(r1)\n" + "{\n" + " v0.sf = vadd(v1.sf, v2.sf)\n" + " vmem(%0 + #0) = v0.new\n" + "}\n" + : + : "r"(output) + : "r0", "r1", "v0", "v1", "v2", "memory"); + check_output_w(__LINE__, 1); +} + +int main(void) +{ + test_disabled(); + test_disabled_with_new(); + puts(err ? "FAIL" : "PASS"); + return err ? 1 : 0; +}
diff --git a/tests/tcg/hexagon/hex_test.h b/tests/tcg/hexagon/hex_test.h index cfed06a..f86e6e1 100644 --- a/tests/tcg/hexagon/hex_test.h +++ b/tests/tcg/hexagon/hex_test.h
@@ -19,6 +19,8 @@ #ifndef HEX_TEST_H #define HEX_TEST_H +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + static inline void __check32(int line, uint32_t val, uint32_t expect) { if (val != expect) { @@ -109,7 +111,36 @@ "usr = r2\n\t" /* Some useful floating point values */ +const uint16_t HF_INF = 0x7c00; +const uint16_t HF_INF_neg = 0xfc00; +const uint16_t HF_QNaN = 0x7e00; +const uint16_t HF_SNaN = 0x7d00; +const uint16_t HF_SNaN_neg = 0xfd00; +const uint16_t HF_QNaN_neg = 0xfe00; +const uint16_t HF_zero = 0x0000; +const uint16_t HF_zero_neg = 0x8000; +const uint16_t HF_one = 0x3c00; +const uint16_t HF_one_recip = 0x3bf9; +const uint16_t HF_two = 0x4000; +const uint16_t HF_small_neg = 0x8010; +const uint16_t HF_any = 0x3c00; +const uint16_t HF_neg_two = 0xc000; + +const uint16_t BF_INF = 0x7f80; +const uint16_t BF_INF_neg = 0xff80; +const uint16_t BF_QNaN = 0x7fc0; +const uint16_t BF_SNaN = 0x7f81; +const uint16_t BF_QNaN_neg = 0xffc0; +const uint16_t BF_SNaN_neg = 0xff81; +const uint16_t BF_HEX_NaN = 0x7fff; +const uint16_t BF_zero = 0x0000; +const uint16_t BF_zero_neg = 0x8000; +const uint16_t BF_one = 0x3f80; +const uint16_t BF_two = 0x4000; +const uint16_t BF_four = 0x4080; + const uint32_t SF_INF = 0x7f800000; +const uint32_t SF_INF_neg = 0xff800000; const uint32_t SF_QNaN = 0x7fc00000; const uint32_t SF_QNaN_special = 0x7f800001; const uint32_t SF_SNaN = 0x7fb00000; @@ -128,6 +159,7 @@ const uint32_t SF_any = 0x3f800000; const uint32_t SF_denorm = 0x00000001; const uint32_t SF_random = 0x346001d6; +const uint32_t SF_neg_two = 0xc0000000; const uint64_t DF_QNaN = 0x7ff8000000000000ULL; const uint64_t DF_SNaN = 0x7ff7000000000000ULL;
diff --git a/tests/tcg/hexagon/hvx_misc.c b/tests/tcg/hexagon/hvx_misc.c index 90c3733..32a3661 100644 --- a/tests/tcg/hexagon/hvx_misc.c +++ b/tests/tcg/hexagon/hvx_misc.c
@@ -20,6 +20,8 @@ #include <stdbool.h> #include <string.h> #include <limits.h> +#include <hexagon_types.h> +#include <hvx_hexagon_protos.h> int err; @@ -315,6 +317,34 @@ TEST_VEC_OP2(vor, vor, , d, 8, |) TEST_VEC_OP1(vnot, vnot, , d, 8, ~) +#define TEST_VEC_ABSDIFF(NAME, INTRINSIC, SRC_FIELD, DST_FIELD, \ + CHECK_FIELD, FIELDSZ) \ +static inline void test_##NAME(void) \ +{ \ + HVX_Vector v0; \ + HVX_Vector v1; \ + HVX_Vector vres; \ + for (int i = 0; i < BUFSIZE; i++) { \ + memcpy(&v0, &buffer0[i], sizeof(MMVector)); \ + memcpy(&v1, &buffer1[i], sizeof(MMVector)); \ + vres = INTRINSIC(v0, v1); \ + memcpy(&output[i], &vres, sizeof(MMVector)); \ + } \ + for (int i = 0; i < BUFSIZE; i++) { \ + for (int j = 0; j < MAX_VEC_SIZE_BYTES / FIELDSZ; j++) { \ + int64_t diff = (int64_t)buffer0[i].SRC_FIELD[j] - \ + (int64_t)buffer1[i].SRC_FIELD[j]; \ + expect[i].DST_FIELD[j] = diff < 0 ? -diff : diff; \ + } \ + } \ + check_output_##CHECK_FIELD(__LINE__, BUFSIZE); \ +} + +TEST_VEC_ABSDIFF(vabsdiffub, Q6_Vub_vabsdiff_VubVub, ub, ub, b, 1) +TEST_VEC_ABSDIFF(vabsdiffuh, Q6_Vuh_vabsdiff_VuhVuh, uh, uh, h, 2) +TEST_VEC_ABSDIFF(vabsdiffh, Q6_Vuh_vabsdiff_VhVh, h, uh, h, 2) +TEST_VEC_ABSDIFF(vabsdiffw, Q6_Vuw_vabsdiff_VwVw, w, uw, w, 4) + TEST_PRED_OP2(pred_or, or, |, "") TEST_PRED_OP2(pred_or_n, or, |, "!") TEST_PRED_OP2(pred_and, and, &, "") @@ -386,6 +416,47 @@ check_output_w(__LINE__, 2); } +static void test_vsubwsat(void) +{ + const int32_t x0 = INT32_MIN; + const int32_t y0 = 1; + const int32_t x1 = INT32_MAX; + const int32_t y1 = -1; + HVX_Vector v0; + HVX_Vector v1; + HVX_Vector vres; + + /* INT32_MIN - 1 underflows and must saturate to INT32_MIN */ + memset(expect, 0x12, sizeof(MMVector)); + memset(output, 0x34, sizeof(MMVector)); + + v0 = Q6_V_vsplat_R(x0); + v1 = Q6_V_vsplat_R(y0); + vres = Q6_Vw_vsub_VwVw_sat(v0, v1); + memcpy(&output[0], &vres, sizeof(MMVector)); + + for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) { + expect[0].w[j] = INT32_MIN; + } + + check_output_w(__LINE__, 1); + + /* INT32_MAX - (-1) overflows and must saturate to INT32_MAX */ + memset(expect, 0x12, sizeof(MMVector)); + memset(output, 0x34, sizeof(MMVector)); + + v0 = Q6_V_vsplat_R(x1); + v1 = Q6_V_vsplat_R(y1); + vres = Q6_Vw_vsub_VwVw_sat(v0, v1); + memcpy(&output[0], &vres, sizeof(MMVector)); + + for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) { + expect[0].w[j] = INT32_MAX; + } + + check_output_w(__LINE__, 1); +} + static void test_load_tmp_predicated(void) { void *p0 = buffer0; @@ -530,6 +601,12 @@ test_vadduwsat(); test_vsubuwsat_dv(); + test_vsubwsat(); + + test_vabsdiffub(); + test_vabsdiffuh(); + test_vabsdiffh(); + test_vabsdiffw(); test_load_tmp_predicated(); test_load_cur_predicated();
diff --git a/tests/tcg/hexagon/hvx_misc.h b/tests/tcg/hexagon/hvx_misc.h index 2e86834..c21ea97 100644 --- a/tests/tcg/hexagon/hvx_misc.h +++ b/tests/tcg/hexagon/hvx_misc.h
@@ -18,6 +18,8 @@ #ifndef HVX_MISC_H #define HVX_MISC_H +#include "hex_test.h" + static inline void check(int line, int i, int j, uint64_t result, uint64_t expect) { @@ -34,9 +36,12 @@ uint64_t ud[MAX_VEC_SIZE_BYTES / 8]; int64_t d[MAX_VEC_SIZE_BYTES / 8]; uint32_t uw[MAX_VEC_SIZE_BYTES / 4]; + uint32_t sf[MAX_VEC_SIZE_BYTES / 4]; /* convenience alias */ int32_t w[MAX_VEC_SIZE_BYTES / 4]; uint16_t uh[MAX_VEC_SIZE_BYTES / 2]; + uint16_t hf[MAX_VEC_SIZE_BYTES / 2]; /* convenience alias */ int16_t h[MAX_VEC_SIZE_BYTES / 2]; + uint16_t bf[MAX_VEC_SIZE_BYTES / 2]; uint8_t ub[MAX_VEC_SIZE_BYTES / 1]; int8_t b[MAX_VEC_SIZE_BYTES / 1]; } MMVector; @@ -63,8 +68,13 @@ CHECK_OUTPUT_FUNC(d, 8) CHECK_OUTPUT_FUNC(w, 4) +CHECK_OUTPUT_FUNC(sf, 4) CHECK_OUTPUT_FUNC(h, 2) +CHECK_OUTPUT_FUNC(uh, 2) +CHECK_OUTPUT_FUNC(hf, 2) +CHECK_OUTPUT_FUNC(ub, 1) CHECK_OUTPUT_FUNC(b, 1) +CHECK_OUTPUT_FUNC(bf, 2) static inline void init_buffers(void) { @@ -81,6 +91,58 @@ } } +static const uint32_t FP_VALUES[] = { + SF_INF, SF_INF_neg, SF_QNaN, SF_QNaN_special, SF_SNaN, SF_QNaN_neg, + SF_SNaN_neg, SF_HEX_NaN, SF_zero, SF_zero_neg, SF_one, SF_one_recip, + SF_one_invsqrta, SF_two, SF_four, SF_small_neg, SF_large_pos, SF_any, + SF_denorm, SF_random, SF_neg_two, +}; +#define FP_VALUES_MAX ARRAY_SIZE(FP_VALUES) + +static const uint16_t BF_VALUES[] = { + BF_INF, BF_INF_neg, BF_QNaN, BF_SNaN, BF_QNaN_neg, BF_SNaN_neg, + BF_HEX_NaN, BF_zero, BF_zero_neg, BF_one, BF_two, BF_four, +}; +#define BF_VALUES_MAX ARRAY_SIZE(BF_VALUES) + +static inline void init_buffers_fp(void) +{ + _Static_assert(BUFSIZE * (MAX_VEC_SIZE_BYTES / 4) > + FP_VALUES_MAX * FP_VALUES_MAX, + "test arrays can't fit all FP_VALUES combinations"); + int counter1 = 0, counter2 = 0; + for (int i = 0; i < BUFSIZE; i++) { + for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) { + buffer0[i].sf[j] = FP_VALUES[counter1]; + buffer1[i].sf[j] = FP_VALUES[counter2]; + counter2++; + if (counter2 == FP_VALUES_MAX) { + counter2 = 0; + counter1 = (counter1 + 1) % FP_VALUES_MAX; + } + } + } +} + +static inline void init_buffers_bf(void) +{ + _Static_assert(BUFSIZE * (MAX_VEC_SIZE_BYTES / 2) > + BF_VALUES_MAX * BF_VALUES_MAX, + "test arrays can't fit all BF_VALUES combinations"); + int counter1 = 0, counter2 = 0; + for (int i = 0; i < BUFSIZE; i++) { + for (int j = 0; j < MAX_VEC_SIZE_BYTES / 2; j++) { + buffer0[i].bf[j] = BF_VALUES[counter1]; + buffer1[i].bf[j] = BF_VALUES[counter2]; + counter2++; + if (counter2 == BF_VALUES_MAX) { + counter2 = 0; + counter1 = (counter1 + 1) % BF_VALUES_MAX; + } + } + } +} + #define VEC_OP1(ASM, EL, IN, OUT) \ asm("v2 = vmem(%0 + #0)\n\t" \ "v2" #EL " = " #ASM "(v2" #EL ")\n\t" \ @@ -175,4 +237,15 @@ check_output_b(__LINE__, BUFSIZE); \ } +#define float_sf(x) ({ typeof(x) _x = (x); *((float *)&(_x)); }) +#define float_hf(x) ({ typeof(x) _x = (x); *((_Float16 *) &(_x)); }) +#define float_bf(x) ({ uint32_t _u = ((uint32_t)(x)) << 16; *((float *)&(_u)); }) +#define raw_sf(x) ({ typeof(x) _x = (x); *((uint32_t *)&(_x)); }) +#define raw_hf(x) ({ typeof(x) _x = (x); *((uint16_t *)&(_x)); }) +#define raw_bf(x) ({ typeof(x) _x = (x); (uint16_t)(*((uint32_t *)&(_x)) >> 16); }) +#define float_hf_to_sf(x) ((float)x) +#define bytes_hf 2 +#define bytes_sf 4 +#define bytes_bf 2 + #endif
diff --git a/tests/tcg/hexagon/invalid-slots.c b/tests/tcg/hexagon/invalid-slots.c index 607027f..f2dace2 100644 --- a/tests/tcg/hexagon/invalid-slots.c +++ b/tests/tcg/hexagon/invalid-slots.c
@@ -55,6 +55,75 @@ return sig; } +/* Load then indirect jump, load encoded first: no high slot left for jump. */ +static int test_invalid_slots_highslot(void) +{ + int sig; + + asm volatile( + "r0 = #0\n" + "r1 = ##1f\n" + "memw(%1) = r1\n" + "r3 = #mem\n" + ".word 0x91834006\n" /* { r6 = memw(r3+#0); */ + ".word 0x529fc000\n" /* jumpr r31 } */ + "1:\n" + "%0 = r0\n" + : "=r"(sig) + : "r"(&resume_pc) + : "r0", "r1", "r3", "r6", "memory"); + + return sig; +} + +/* + * Three predicate-logical ops: each is restricted to slots 2 and 3, so the + * fourth-and-fifth-slot-free packet still has only two slots for three ops. + * No change-of-flow is involved, so the only reason to reject it is the slot + * conflict. + */ +static int test_invalid_slots_crslot23(void) +{ + int sig; + + asm volatile( + "r0 = #0\n" + "r1 = ##1f\n" + "memw(%1) = r1\n" + ".word 0x6b024100\n" /* { p0 = and(p1, p2); */ + ".word 0x6b224103\n" /* p3 = or(p1, p2); */ + ".word 0x6b42c301\n" /* p1 = xor(p2, p3) } */ + "1:\n" + "%0 = r0\n" + : "=r"(sig) + : "r"(&resume_pc) + : "r0", "r1", "p0", "p1", "p3", "memory"); + + return sig; +} + +/* Three transfers plus a duplex: five ops for four slots. */ +static int test_invalid_slots_five(void) +{ + int sig; + + asm volatile( + "r0 = #0\n" + "r1 = ##1f\n" + "memw(%1) = r1\n" + ".word 0x78004020\n" /* { r0 = #1; */ + ".word 0x78004041\n" /* r1 = #2; */ + ".word 0x78004062\n" /* r2 = #3; */ + ".word 0x28452856\n" /* r5 = #4; r6 = #5 } */ + "1:\n" + "%0 = r0\n" + : "=r"(sig) + : "r"(&resume_pc) + : "r0", "r1", "r2", "r5", "r6", "memory"); + + return sig; +} + int main() { struct sigaction act; @@ -65,6 +134,9 @@ assert(sigaction(SIGILL, &act, NULL) == 0); assert(test_invalid_slots() == SIGILL); + assert(test_invalid_slots_highslot() == SIGILL); + assert(test_invalid_slots_crslot23() == SIGILL); + assert(test_invalid_slots_five() == SIGILL); puts("PASS"); return EXIT_SUCCESS;
diff --git a/tests/tcg/hexagon/read_write_overlap.c b/tests/tcg/hexagon/read_write_overlap.c index 95c54cc..7eaf75f 100644 --- a/tests/tcg/hexagon/read_write_overlap.c +++ b/tests/tcg/hexagon/read_write_overlap.c
@@ -115,12 +115,59 @@ check32(swiz(0x11223344), 0x44332211); } +#define CMPY(NAME, ASM) \ +static inline uint32_t NAME##_rd_eq_rs(uint32_t x, uint32_t y) \ +{ \ + uint32_t res; \ + asm("r7 = %1\n\t" \ + ASM("r7", "%2") "\n\t" \ + "%0 = r7\n\t" \ + : "=r"(res) : "r"(x), "r"(y) : "r7"); \ + return res; \ +} \ +static inline uint32_t NAME##_rd_eq_rt(uint32_t x, uint32_t y) \ +{ \ + uint32_t res; \ + asm("r7 = %2\n\t" \ + ASM("%1", "r7") "\n\t" \ + "%0 = r7\n\t" \ + : "=r"(res) : "r"(x), "r"(y) : "r7"); \ + return res; \ +} + +#define CMPY_RND_SAT(RS, RT) "r7 = cmpy(" RS "," RT "):rnd:sat" +#define CMPY_S1_RND_SAT(RS, RT) "r7 = cmpy(" RS "," RT "):<<1:rnd:sat" +#define CMPYC_RND_SAT(RS, RT) "r7 = cmpy(" RS "," RT "*):rnd:sat" +#define CMPYC_S1_RND_SAT(RS, RT) "r7 = cmpy(" RS "," RT "*):<<1:rnd:sat" + +CMPY(cmpyrs_s0, CMPY_RND_SAT) +CMPY(cmpyrs_s1, CMPY_S1_RND_SAT) +CMPY(cmpyrsc_s0, CMPYC_RND_SAT) +CMPY(cmpyrsc_s1, CMPYC_S1_RND_SAT) + +static void test_cmpy(void) +{ + check32(cmpyrs_s0_rd_eq_rs(0x32195ce2, 0xef862430), 0x011b105b); + check32(cmpyrs_s0_rd_eq_rt(0x32195ce2, 0xef862430), 0x011b105b); + check32(cmpyrs_s1_rd_eq_rs(0x32195ce2, 0xef862430), 0x023520b5); + check32(cmpyrs_s1_rd_eq_rt(0x32195ce2, 0xef862430), 0x023520b5); + check32(cmpyrsc_s0_rd_eq_rs(0x32195ce2, 0xef862430), 0x0d0f09e8); + check32(cmpyrsc_s0_rd_eq_rt(0x32195ce2, 0xef862430), 0x0d0f09e8); + check32(cmpyrsc_s1_rd_eq_rs(0x32195ce2, 0xef862430), 0x1a1f13d0); + check32(cmpyrsc_s1_rd_eq_rt(0x32195ce2, 0xef862430), 0x1a1f13d0); + + /* Both halves saturate */ + check32(cmpyrs_s1_rd_eq_rs(0x80008000, 0x80008000), 0x7fff0000); + check32(cmpyrsc_s1_rd_eq_rs(0x7fff8001, 0x80017fff), 0x00008000); +} + int main() { test_insert(); test_insert_rp(); test_asr_r_svw_trun(); test_swiz(); + test_cmpy(); puts(err ? "FAIL" : "PASS"); return err ? EXIT_FAILURE : EXIT_SUCCESS;
diff --git a/tests/tcg/hexagon/valid-slots.c b/tests/tcg/hexagon/valid-slots.c new file mode 100644 index 0000000..70d9b0b --- /dev/null +++ b/tests/tcg/hexagon/valid-slots.c
@@ -0,0 +1,62 @@ +/* + * Regression tests for valid packets that qemu incorrectly rejected as + * invalid. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdint.h> + +int err; + +#include "hex_test.h" + +/* volatile to keep the load from being optimized away */ +static volatile int buf[2] = { 0x1234, 0 }; + +/* Load and register transfer in one packet, load encoded first. */ +static int32_t load_imm_pair(void) +{ + int32_t out; + /* { r6 = memw(r3+#-4); r7 = #0x4ae6 } */ + asm volatile( + "{ r3 = %1 }\n\t" + ".word 0x97837fe6\n\t" + ".word 0x7845dcc7\n\t" + "{ %0 = r6 }\n\t" + : "=r"(out) : "r"(&buf[1]) : "r3", "r6", "r7"); + return out; +} + +static int32_t dcbuf[8] __attribute__((aligned(32))); + +/* Slot-0-only op (dczeroa) packed last with three transfers. */ +static void slot0_restricted(int32_t *out) +{ + asm volatile( + "{ %0 = #0x11\n\t" + " %1 = #0x22\n\t" + " %2 = #0x33\n\t" + " dczeroa(%3) }\n\t" + : "=r"(out[0]), "=r"(out[1]), "=r"(out[2]) + : "r"(dcbuf) : "memory"); +} + +int main() +{ + int32_t r[3]; + + check32(load_imm_pair(), 0x1234); + + dcbuf[0] = 0x5a5a5a5a; + slot0_restricted(r); + check32(r[0], 0x11); + check32(r[1], 0x22); + check32(r[2], 0x33); + check32(dcbuf[0], 0); /* dczeroa cleared the line */ + + puts(err ? "FAIL" : "PASS"); + return err; +}
diff --git a/tests/tcg/s390x/Makefile.target b/tests/tcg/s390x/Makefile.target index 0ca030d..97c0f02 100644 --- a/tests/tcg/s390x/Makefile.target +++ b/tests/tcg/s390x/Makefile.target
@@ -50,6 +50,7 @@ TESTS+=ts TESTS+=ex-smc TESTS+=divide-to-integer +TESTS+=stckf cdsg: CFLAGS+=-pthread cdsg: LDFLAGS+=-pthread @@ -75,6 +76,7 @@ Z13_TESTS+=vstl Z13_TESTS+=vrep Z13_TESTS+=precise-smc-user +Z13_TESTS+=prno-trng $(Z13_TESTS): CFLAGS+=-march=z13 -O2 TESTS+=$(Z13_TESTS)
diff --git a/tests/tcg/s390x/div.c b/tests/tcg/s390x/div.c index 6ad9900..124c9ec 100644 --- a/tests/tcg/s390x/div.c +++ b/tests/tcg/s390x/div.c
@@ -1,6 +1,15 @@ #include <assert.h> +#include <signal.h> #include <stdint.h> +/* Set asynchronously by the signal handler. */ +static volatile int signum; + +static void signal_handler(int n) +{ + signum = n; +} + static void test_dr(void) { register int32_t r0 asm("r0") = -1; @@ -65,11 +74,39 @@ assert(r == 1); } +/* + * The most negative dividend divided by -1 yields a quotient that does not + * fit into 32 bits, so DR must raise a fixed-point-divide exception. + */ +static void test_dr_overflow(void) +{ + struct sigaction act = { .sa_handler = signal_handler }; + register int32_t r0 asm("r0"); + register int32_t r1 asm("r1"); + int32_t b = -1; + int err; + + err = sigaction(SIGFPE, &act, NULL); + assert(err == 0); + signum = -1; + + r0 = 0x80000000; + r1 = 0; + asm volatile("dr %[r0],%[b]" + : [r0] "+r" (r0), [r1] "+r" (r1) + : [b] "r" (b) + : "cc"); + assert(signum == SIGFPE); + + signal(SIGFPE, SIG_DFL); +} + int main(void) { test_dr(); test_dlr(); test_dsgr(); test_dlgr(); + test_dr_overflow(); return 0; }
diff --git a/tests/tcg/s390x/prno-trng.c b/tests/tcg/s390x/prno-trng.c new file mode 100644 index 0000000..43eea5d --- /dev/null +++ b/tests/tcg/s390x/prno-trng.c
@@ -0,0 +1,67 @@ +/* + * Test that PERFORM RANDOM NUMBER OPERATION TRNG is interruptible. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include <assert.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <asm/ucontext.h> + +static unsigned char buf1[16 * 1024 * 1024]; +static unsigned char buf2[16 * 1024 * 1024]; + +static volatile sig_atomic_t interrupted; + +static void sigprof_handler(int sig, siginfo_t *info, void *ucontext) +{ + struct ucontext *uc = ucontext; + unsigned long addr = uc->uc_mcontext.regs.psw.addr; + + if (*(unsigned short *)(addr - 4) == 0xb93c) { + interrupted++; + } +} + +static void prno_trng(void *b1, unsigned long l1, void *b2, unsigned long l2) +{ + register unsigned long r0 asm("r0") = 114; /* TRNG */ + register unsigned long r2 asm("r2") = (unsigned long)b1; + register unsigned long r3 asm("r3") = l1; + register unsigned long r4 asm("r4") = (unsigned long)b2; + register unsigned long r5 asm("r5") = l2; + + asm volatile("0: ppno %[r2],%[r4]\n" /* prno alias for old toolchains */ + " jo 0b" + : [r2] "+r" (r2), [r3] "+r" (r3) + , [r4] "+r" (r4), [r5] "+r" (r5) + : "r" (r0) + : "cc", "memory"); +} + +int main(void) +{ + struct itimerval it = { + .it_interval = { .tv_usec = 10000 }, /* 0.01s */ + .it_value = { .tv_usec = 10000 }, + }; + struct sigaction act = { + .sa_sigaction = sigprof_handler, + .sa_flags = SA_SIGINFO, + }; + int err; + + err = sigaction(SIGPROF, &act, NULL); + assert(err == 0); + err = setitimer(ITIMER_PROF, &it, NULL); + assert(err == 0); + + prno_trng(buf1, sizeof(buf1), buf2, sizeof(buf2)); + printf("interrupted %d times\n", interrupted); + assert(interrupted >= 3); + + return EXIT_SUCCESS; +}
diff --git a/tests/tcg/s390x/stckf.c b/tests/tcg/s390x/stckf.c new file mode 100644 index 0000000..51c8c9d --- /dev/null +++ b/tests/tcg/s390x/stckf.c
@@ -0,0 +1,44 @@ +/* + * Test that a faulting STORE CLOCK FAST does not clobber the condition code. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include <assert.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> + +static void handle_sigsegv(int sig, siginfo_t *info, void *ucontext) +{ + mcontext_t *mcontext = &((ucontext_t *)ucontext)->uc_mcontext; + + /* The condition code must be the one set by SLGR, not garbage. */ + _exit(((mcontext->psw.mask >> 44) & 3) == 3 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +int main(void) +{ + struct sigaction act = { + .sa_sigaction = handle_sigsegv, + .sa_flags = SA_SIGINFO, + }; + int err; + + err = sigaction(SIGSEGV, &act, NULL); + assert(err == 0); + + asm volatile( + "lghi %%r1,100\n" + "lghi %%r2,0\n" + "clgr %%r1,%%r2\n" /* CC_OP_LTUGTU_64 */ + /* cc_src=100 is not valid for CC_OP_SUBU */ + "ipm %%r0\n" /* force cc_src to env */ + "lghi %%r3,5\n" + "lghi %%r4,3\n" + "slgr %%r3,%%r4\n" /* CC_OP_SUBU, cc=3 */ + "lghi %%r5,0\n" + "stckf 0(%%r5)\n" /* faults; cc must stay 3 */ + : : : "r0", "r1", "r2", "r3", "r4", "r5", "cc", "memory"); + + return EXIT_FAILURE; +}
diff --git a/tests/unit/test-crypto-cipher.c b/tests/unit/test-crypto-cipher.c index 1331d55..420c826 100644 --- a/tests/unit/test-crypto-cipher.c +++ b/tests/unit/test-crypto-cipher.c
@@ -810,6 +810,230 @@ qcrypto_cipher_free(cipher); } +typedef struct QCryptoCipherGcmTestData QCryptoCipherGcmTestData; +struct QCryptoCipherGcmTestData { + const char *path; + QCryptoCipherAlgo alg; + const char *key; + const char *iv; + /* associated data, or NULL for none */ + const char *aad; + const char *plaintext; + const char *ciphertext; + const char *tag; +}; + +/* + * AES-GCM test vectors from "The Galois/Counter Mode of Operation (GCM)" + * (McGrew & Viega, also NIST SP 800-38D), with a 96-bit IV and a 128-bit + * tag. Each entry's "Test case N" label is the numbered test case from that + * document (Appendix B / the GCM specification's test vectors). + */ +static QCryptoCipherGcmTestData gcm_test_data[] = { + { + /* Test case 2 */ + .path = "/crypto/cipher/aes-gcm-128-2", + .alg = QCRYPTO_CIPHER_ALGO_AES_128, + .key = "00000000000000000000000000000000", + .iv = "000000000000000000000000", + .plaintext = "00000000000000000000000000000000", + .ciphertext = "0388dace60b6a392f328c2b971b2fe78", + .tag = "ab6e47d42cec13bdf53a67b21257bddf", + }, + { + /* Test case 3 (no AAD) */ + .path = "/crypto/cipher/aes-gcm-128-3", + .alg = QCRYPTO_CIPHER_ALGO_AES_128, + .key = "feffe9928665731c6d6a8f9467308308", + .iv = "cafebabefacedbaddecaf888", + .plaintext = + "d9313225f88406e5a55909c5aff5269a" + "86a7a9531534f7da2e4c303d8a318a72" + "1c3c0c95956809532fcf0e2449a6b525" + "b16aedf5aa0de657ba637b391aafd255", + .ciphertext = + "42831ec2217774244b7221b784d0d49c" + "e3aa212f2c02a4e035c17e2329aca12e" + "21d514b25466931c7d8f6a5aac84aa05" + "1ba30b396a0aac973d58e091473f5985", + .tag = "4d5c2af327cd64a62cf35abd2ba6fab4", + }, + { + /* Test case 4 (with AAD) */ + .path = "/crypto/cipher/aes-gcm-128-4", + .alg = QCRYPTO_CIPHER_ALGO_AES_128, + .key = "feffe9928665731c6d6a8f9467308308", + .iv = "cafebabefacedbaddecaf888", + .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2", + .plaintext = + "d9313225f88406e5a55909c5aff5269a" + "86a7a9531534f7da2e4c303d8a318a72" + "1c3c0c95956809532fcf0e2449a6b525" + "b16aedf5aa0de657ba637b39", + .ciphertext = + "42831ec2217774244b7221b784d0d49c" + "e3aa212f2c02a4e035c17e2329aca12e" + "21d514b25466931c7d8f6a5aac84aa05" + "1ba30b396a0aac973d58e091", + .tag = "5bc94fbc3221a5db94fae95ae7121a47", + }, + { + /* Test case 15 (AES-256, no AAD) */ + .path = "/crypto/cipher/aes-gcm-256-15", + .alg = QCRYPTO_CIPHER_ALGO_AES_256, + .key = + "feffe9928665731c6d6a8f9467308308" + "feffe9928665731c6d6a8f9467308308", + .iv = "cafebabefacedbaddecaf888", + .plaintext = + "d9313225f88406e5a55909c5aff5269a" + "86a7a9531534f7da2e4c303d8a318a72" + "1c3c0c95956809532fcf0e2449a6b525" + "b16aedf5aa0de657ba637b391aafd255", + .ciphertext = + "522dc1f099567d07f47f37a32a84427d" + "643a8cdcbfe5c0c97598a2bd2555d1aa" + "8cb08e48590dbb3da7b08b1056828838" + "c5f61e6393ba7a0abcc9f662898015ad", + .tag = "b094dac5d93471bdec1a502270e3cc6c", + }, + { + /* Test case 16 (AES-256, with AAD) */ + .path = "/crypto/cipher/aes-gcm-256-16", + .alg = QCRYPTO_CIPHER_ALGO_AES_256, + .key = + "feffe9928665731c6d6a8f9467308308" + "feffe9928665731c6d6a8f9467308308", + .iv = "cafebabefacedbaddecaf888", + .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2", + .plaintext = + "d9313225f88406e5a55909c5aff5269a" + "86a7a9531534f7da2e4c303d8a318a72" + "1c3c0c95956809532fcf0e2449a6b525" + "b16aedf5aa0de657ba637b39", + .ciphertext = + "522dc1f099567d07f47f37a32a84427d" + "643a8cdcbfe5c0c97598a2bd2555d1aa" + "8cb08e48590dbb3da7b08b1056828838" + "c5f61e6393ba7a0abcc9f662", + .tag = "76fc6ece0f4e1768cddf8853bb2d551b", + }, +}; + +static void test_cipher_gcm(const void *opaque) +{ + const QCryptoCipherGcmTestData *data = opaque; + g_autofree uint8_t *key = NULL; + g_autofree uint8_t *iv = NULL; + g_autofree uint8_t *aad = NULL; + g_autofree uint8_t *ptext = NULL; + g_autofree uint8_t *ctext = NULL; + g_autofree uint8_t *tagexp = NULL; + g_autofree uint8_t *out = NULL; + uint8_t tag[16]; + size_t nkey; + size_t niv; + size_t naad = 0; + size_t nptext; + size_t nctext; + size_t ntag; + QCryptoCipher *cipher; + + nkey = unhex_string(data->key, &key); + niv = unhex_string(data->iv, &iv); + nptext = unhex_string(data->plaintext, &ptext); + nctext = unhex_string(data->ciphertext, &ctext); + ntag = unhex_string(data->tag, &tagexp); + if (data->aad) { + naad = unhex_string(data->aad, &aad); + } + + g_assert_cmpint(nptext, ==, nctext); + g_assert_cmpint(ntag, ==, sizeof(tag)); + out = g_new0(uint8_t, nptext); + + /* Encrypt: plaintext -> ciphertext, then read back the tag. */ + cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM, + key, nkey, &error_abort); + g_assert(cipher != NULL); + g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0); + if (naad) { + g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0); + } + g_assert(qcrypto_cipher_encrypt(cipher, ptext, out, nptext, + &error_abort) == 0); + g_assert_cmpmem(out, nptext, ctext, nctext); + g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag), + &error_abort) == 0); + g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag); + qcrypto_cipher_free(cipher); + + /* Decrypt: ciphertext -> plaintext, recomputed tag must match. */ + memset(out, 0, nptext); + cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM, + key, nkey, &error_abort); + g_assert(cipher != NULL); + g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0); + if (naad) { + g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0); + } + g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext, + &error_abort) == 0); + g_assert_cmpmem(out, nctext, ptext, nptext); + g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag), + &error_abort) == 0); + g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag); + qcrypto_cipher_free(cipher); +} + +/* + * Corrupt one ciphertext byte and confirm the recomputed GCM tag no longer + * matches: the authentication tag must detect tampering. + */ +static void test_cipher_gcm_tamper(const void *opaque) +{ + const QCryptoCipherGcmTestData *data = opaque; + g_autofree uint8_t *key = NULL; + g_autofree uint8_t *iv = NULL; + g_autofree uint8_t *aad = NULL; + g_autofree uint8_t *ctext = NULL; + g_autofree uint8_t *tagexp = NULL; + g_autofree uint8_t *out = NULL; + uint8_t tag[16]; + size_t nkey; + size_t niv; + size_t naad = 0; + size_t nctext; + size_t ntag; + QCryptoCipher *cipher; + + nkey = unhex_string(data->key, &key); + niv = unhex_string(data->iv, &iv); + nctext = unhex_string(data->ciphertext, &ctext); + ntag = unhex_string(data->tag, &tagexp); + if (data->aad) { + naad = unhex_string(data->aad, &aad); + } + out = g_new0(uint8_t, nctext); + + /* Flip one ciphertext bit before decrypting. */ + ctext[0] ^= 0x01; + + cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM, + key, nkey, &error_abort); + g_assert(cipher != NULL); + g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0); + if (naad) { + g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0); + } + g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext, + &error_abort) == 0); + g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag), + &error_abort) == 0); + g_assert(memcmp(tag, tagexp, ntag) != 0); + qcrypto_cipher_free(cipher); +} + int main(int argc, char **argv) { size_t i; @@ -828,6 +1052,22 @@ } } + for (i = 0; i < G_N_ELEMENTS(gcm_test_data); i++) { + if (qcrypto_cipher_supports(gcm_test_data[i].alg, + QCRYPTO_CIPHER_MODE_GCM)) { + g_autofree char *tamper = g_strdup_printf("%s/tamper", + gcm_test_data[i].path); + + g_test_add_data_func(gcm_test_data[i].path, &gcm_test_data[i], + test_cipher_gcm); + g_test_add_data_func(tamper, &gcm_test_data[i], + test_cipher_gcm_tamper); + } else { + g_printerr("# skip unsupported %s:gcm\n", + QCryptoCipherAlgo_str(gcm_test_data[i].alg)); + } + } + if (qcrypto_cipher_supports(QCRYPTO_CIPHER_ALGO_AES_256, QCRYPTO_CIPHER_MODE_CBC)) { g_test_add_func("/crypto/cipher/null-iv",