diff options
| author | Peter Maydell <peter.maydell@linaro.org> | 2026-09-07 13:33:12 +0100 |
|---|---|---|
| committer | Peter Maydell <peter.maydell@linaro.org> | 2026-09-07 13:33:12 +0100 |
| commit | 35500e5c41aec76cde59befe750600dac7a9e37a (patch) | |
| tree | 108af14ab7700f9c29bc0b7ba8f513fbc5bace8a | |
| parent | 61e537b7ef2f8b899827c72126cada6161e5803d (diff) | |
| parent | 1c05d7e94da04129ea16147074fe73158fb85ff9 (diff) | |
| download | qemu-staging.tar.gz qemu-staging.zip | |
aspeed queue:
* Fixes AST2600 SCU protection key registers on reset
* Fixes I2C byte latching for SMBus block reads
* Hardens EHCI get_dwords()/put_dwords() with DMA error checking
* Adds a second random number generator to the AST2600 SCU
* Adds the ASPEED ACRY RSA accelerator model with ModExp qtest coverage
* Adds ECDSA sign/verify support to the QEMU crypto akcipher layer
(gcrypt, nettle backends) with tests
* Adds ECDSA verify command to the ASPEED SBC model with qtest coverage
* Adds the ASPEED UDC (USB Device Controller) with gadget device
and programmable endpoint DMA, wired on the AST2600
* Reworks the TMP105 temperature sensor: Resettable reset, fault
queue enforcement, TMP75/TMP175/LM75B variants, and qtest coverage
* Adds the Facebook SanMiguel BMC machine with EEPROM data
* Uses real TMP75/LM75B models on the Catalina and Fuji boards
* Adds optional device tree model checking to functional boot tests
# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEEoPZlSPBIlev+awtgUaNDx8/77KEFAmqdnl8ACgkQUaNDx8/7
# 7KGMzxAAgxO09nzutoyjvMRK+Tydd65h9TNxZTC68jzUjacGfU9Y+1+1OcgU25D1
# EUorhMrAGMe22zZ6Q6048HjBmw/xRxt4QB6A5CGIroSgVqQH6NAFalZz5jeCwZrm
# HMk2UAzf0/2euBAXRUFEtK/RpfdrSe1kNXcRX2lWkt5HdjfZJxcM/TmOtLl0vDDU
# BUMbmXLWvnv/W8OLiVmGwP8ufGey2HL2dsLn2Fs9riFF1ZUaJGFkKxEc7ApF6tOr
# 0YaY84lainh21Bp+IjXaV2Al8XCyyHpWKFrEYLEm9P3FfMEebIZbJu8he20cidiw
# R5LIgHpb6FcUZvVcfWyAfYK1+yRQDrL9PMQ8OdbiUgtW1WnvqSyQES4GWx55OmPc
# TSRDodPnETxF26n43ruIWdIGa6umgieT891UrI3CsCYc7JpSpujCgMiob6sLWjFD
# h65DNECnSl3qkK8kIneEhUK9MVIX62YskqAWkIF7rwy+Af7QkCcWuMKTGIpC+a/l
# H7SjxPWwy1XW9B+4dhF3teYxqb8iETM0BQwr2ZuAp3mbB/YkTMaIj5TfUpiGrClz
# RdkSc2njjqbeyQZSTV1P+M8cpWBYiHRJNhx58rH6qIpH6ATSZZAym6XCZV2wZDtU
# VJ7TwnOObQMogMp/9iThUd4jEtCcmEp1dxXF/KKXADso6EU6+Do=
# =sX4J
# -----END PGP SIGNATURE-----
# gpg: Signature made Sun Sep 6 18:09:51 2026 BST
# gpg: using RSA key A0F66548F04895EBFE6B0B6051A343C7CFFBECA1
# gpg: Good signature from "Cédric Le Goater <clg@redhat.com>" [full]
# gpg: aka "Cédric Le Goater <clg@kaod.org>" [full]
# Primary key fingerprint: A0F6 6548 F048 95EB FE6B 0B60 51A3 43C7 CFFB ECA1
* tag 'pull-aspeed-20260906' of https://github.com/legoater/qemu: (40 commits)
tests/functional: aspeed: optionally check the device tree model on boot
hw/arm: fuji: use the real TMP75 and LM75B temperature sensors
hw/arm: catalina: use the real TMP75 model
hw/arm: sanmiguel: populate EEPROM data
hw/arm: sanmiguel: add Facebook SanMiguel BMC machine
tests/qtest: tmp105: cover shutdown clearing the ALERT across variants
tests/qtest: tmp105: cover one-shot and fault-queue write immunity
tests/qtest: tmp105: cover the TMP75, TMP175 and LM75B variants
tests/qtest: tmp105: cover the ALERT fault queue
hw/sensor: tmp105: add TMP75, TMP175 and LM75B variants
hw/arm: aspeed: guard board-local temperature-sensor aliases
hw/sensor: tmp105: describe the temperature property
hw/sensor: tmp105: enforce the configurable fault queue
hw/sensor: tmp105: implement Resettable reset
hw/sensor: tmp105: name the parent object field parent_obj
hw/sensor: tmp105: make device state private to the implementation
hw/arm/aspeed_ast2600: Wire up the UDC
hw/usb/aspeed-udc: Add programmable endpoint DMA transfers
hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device
hw/usb/aspeed-udc: Add ASPEED UDC device controller
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
35 files changed, 4552 insertions, 195 deletions
diff --git a/crypto/akcipher-gcrypt.c.inc b/crypto/akcipher-gcrypt.c.inc index bcf030fdec..3e49cc1508 100644 --- a/crypto/akcipher-gcrypt.c.inc +++ b/crypto/akcipher-gcrypt.c.inc @@ -36,6 +36,13 @@ typedef struct QCryptoGcryptRSA { QCryptoHashAlgo hash_alg; } QCryptoGcryptRSA; +typedef struct QCryptoGcryptECDSA { + QCryptoAkCipher akcipher; + QCryptoAkCipherKeyType type; + gcry_sexp_t key; + QCryptoCurveID curve_id; +} QCryptoGcryptECDSA; + static void qcrypto_gcrypt_rsa_free(QCryptoAkCipher *akcipher) { QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher; @@ -53,6 +60,12 @@ static QCryptoGcryptRSA *qcrypto_gcrypt_rsa_new( const uint8_t *key, size_t keylen, Error **errp); +static QCryptoGcryptECDSA *qcrypto_gcrypt_ecdsa_new( + const QCryptoAkCipherOptionsECDSA *opt, + QCryptoAkCipherKeyType type, + const uint8_t *key, size_t keylen, + Error **errp); + QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts, QCryptoAkCipherKeyType type, const uint8_t *key, size_t keylen, @@ -63,6 +76,10 @@ QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts, return (QCryptoAkCipher *)qcrypto_gcrypt_rsa_new( &opts->u.rsa, type, key, keylen, errp); + case QCRYPTO_AK_CIPHER_ALGO_ECDSA: + return (QCryptoAkCipher *)qcrypto_gcrypt_ecdsa_new( + &opts->u.ecdsa, type, key, keylen, errp); + default: error_setg(errp, "Unsupported algorithm: %u", opts->alg); return NULL; @@ -565,6 +582,317 @@ error: } +/* + * ECDSA support (sign and verify) + * + * Keys and signatures use raw big-endian formats: + * - public key: Qx || Qy, each 'coord_len' bytes + * - private key: the scalar d, 'coord_len' bytes + * - signature: r || s, each 'coord_len' bytes + * - the input to sign/verify is a raw message digest + */ +static const char *qcrypto_gcrypt_ecdsa_curve_name(QCryptoCurveID curve_id) +{ + switch (curve_id) { + case QCRYPTO_CURVE_ID_PRIME256V1: + return "NIST P-256"; + + case QCRYPTO_CURVE_ID_SECP384R1: + return "NIST P-384"; + + default: + return NULL; + } +} + +static size_t qcrypto_gcrypt_ecdsa_coord_len(QCryptoCurveID curve_id) +{ + switch (curve_id) { + case QCRYPTO_CURVE_ID_PRIME256V1: + return 32; + + case QCRYPTO_CURVE_ID_SECP384R1: + return 48; + + default: + return 0; + } +} + +static void qcrypto_gcrypt_ecdsa_free(QCryptoAkCipher *akcipher) +{ + QCryptoGcryptECDSA *ecdsa = (QCryptoGcryptECDSA *)akcipher; + if (!ecdsa) { + return; + } + + gcry_sexp_release(ecdsa->key); + g_free(ecdsa); +} + +static int qcrypto_gcrypt_ecdsa_encrypt(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + void *out, size_t out_len, + Error **errp) +{ + error_setg(errp, "ECDSA does not support encryption"); + return -1; +} + +static int qcrypto_gcrypt_ecdsa_decrypt(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + void *out, size_t out_len, + Error **errp) +{ + error_setg(errp, "ECDSA does not support decryption"); + return -1; +} + +/* + * Write an MPI into a fixed-length, big-endian, left-zero-padded buffer. + * + * gcry_mpi_print(GCRYMPI_FMT_USG) emits only the minimal number of bytes (it + * drops leading zeros), but an ECDSA r/s component must occupy exactly the + * curve size. Zero-fill the leading bytes and right-align the value, so a + * component whose most significant byte is zero still lands at the correct + * offset in the r || s output. + * + * Returns -1 if the value does not fit in 'len' bytes. + */ +static int qcrypto_gcrypt_mpi_to_buf(gcry_mpi_t mpi, uint8_t *buf, size_t len) +{ + size_t nbytes = (gcry_mpi_get_nbits(mpi) + 7) / 8; + + if (nbytes > len) { + return -1; + } + memset(buf, 0, len - nbytes); + gcry_mpi_print(GCRYMPI_FMT_USG, buf + (len - nbytes), nbytes, NULL, mpi); + return 0; +} + +static int qcrypto_gcrypt_ecdsa_sign(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + void *out, size_t out_len, + Error **errp) +{ + QCryptoGcryptECDSA *ecdsa = (QCryptoGcryptECDSA *)akcipher; + size_t coord_len = qcrypto_gcrypt_ecdsa_coord_len(ecdsa->curve_id); + gcry_sexp_t dgst_sexp = NULL; + gcry_sexp_t sig_sexp = NULL; + gcry_sexp_t r_sexp = NULL; + gcry_sexp_t s_sexp = NULL; + gcry_mpi_t r_mpi = NULL; + gcry_mpi_t s_mpi = NULL; + gcry_error_t err; + int ret = -1; + + if (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE) { + error_setg(errp, "ECDSA sign requires a private key"); + return ret; + } + + if (in_len == 0 || in_len > akcipher->max_dgst_len) { + error_setg(errp, "Invalid digest length %zu", in_len); + return ret; + } + + if (out_len < coord_len * 2) { + error_setg(errp, "Signature buffer length %zu is less than %zu", + out_len, coord_len * 2); + return ret; + } + + err = gcry_sexp_build(&dgst_sexp, NULL, + "(data (flags raw) (value %b))", + (int)in_len, in); + if (gcry_err_code(err) != 0) { + error_setg(errp, "Failed to build digest: %s/%s", + gcry_strsource(err), gcry_strerror(err)); + goto cleanup; + } + + err = gcry_pk_sign(&sig_sexp, dgst_sexp, ecdsa->key); + if (gcry_err_code(err) != 0) { + error_setg(errp, "Failed to make signature: %s/%s", + gcry_strsource(err), gcry_strerror(err)); + goto cleanup; + } + + /* S-expression of signature: (sig-val (ecdsa (r r-mpi) (s s-mpi))) */ + r_sexp = gcry_sexp_find_token(sig_sexp, "r", 0); + s_sexp = gcry_sexp_find_token(sig_sexp, "s", 0); + if (!r_sexp || !s_sexp) { + error_setg(errp, "Invalid signature result"); + goto cleanup; + } + r_mpi = gcry_sexp_nth_mpi(r_sexp, 1, GCRYMPI_FMT_USG); + s_mpi = gcry_sexp_nth_mpi(s_sexp, 1, GCRYMPI_FMT_USG); + if (!r_mpi || !s_mpi) { + error_setg(errp, "Invalid signature result"); + goto cleanup; + } + + /* output is r || s, each zero-padded to the curve size */ + if (qcrypto_gcrypt_mpi_to_buf(r_mpi, out, coord_len) < 0 || + qcrypto_gcrypt_mpi_to_buf(s_mpi, (uint8_t *)out + coord_len, + coord_len) < 0) { + error_setg(errp, "Signature component is too large"); + goto cleanup; + } + ret = coord_len * 2; + +cleanup: + gcry_sexp_release(dgst_sexp); + gcry_sexp_release(sig_sexp); + gcry_sexp_release(r_sexp); + gcry_sexp_release(s_sexp); + gcry_mpi_release(r_mpi); + gcry_mpi_release(s_mpi); + return ret; +} + +static int qcrypto_gcrypt_ecdsa_verify(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + const void *in2, size_t in2_len, + Error **errp) +{ + QCryptoGcryptECDSA *ecdsa = (QCryptoGcryptECDSA *)akcipher; + size_t coord_len = qcrypto_gcrypt_ecdsa_coord_len(ecdsa->curve_id); + gcry_sexp_t sig_sexp = NULL; + gcry_sexp_t dgst_sexp = NULL; + gcry_error_t err; + int ret = -1; + + if (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC) { + error_setg(errp, "ECDSA verify requires a public key"); + return ret; + } + + /* signature is r || s */ + if (in_len != coord_len * 2) { + error_setg(errp, "Signature length %zu is not %zu", + in_len, coord_len * 2); + return ret; + } + + if (in2_len == 0 || in2_len > akcipher->max_dgst_len) { + error_setg(errp, "Invalid digest length %zu", in2_len); + return ret; + } + + err = gcry_sexp_build(&sig_sexp, NULL, + "(sig-val (ecdsa (r %b) (s %b)))", + (int)coord_len, in, + (int)coord_len, (const uint8_t *)in + coord_len); + if (gcry_err_code(err) != 0) { + error_setg(errp, "Failed to build signature: %s/%s", + gcry_strsource(err), gcry_strerror(err)); + goto cleanup; + } + + err = gcry_sexp_build(&dgst_sexp, NULL, + "(data (flags raw) (value %b))", + (int)in2_len, in2); + if (gcry_err_code(err) != 0) { + error_setg(errp, "Failed to build digest: %s/%s", + gcry_strsource(err), gcry_strerror(err)); + goto cleanup; + } + + err = gcry_pk_verify(sig_sexp, dgst_sexp, ecdsa->key); + if (gcry_err_code(err) != 0) { + error_setg(errp, "Failed to verify signature: %s/%s", + gcry_strsource(err), gcry_strerror(err)); + goto cleanup; + } + ret = 0; + +cleanup: + gcry_sexp_release(sig_sexp); + gcry_sexp_release(dgst_sexp); + return ret; +} + +QCryptoAkCipherDriver gcrypt_ecdsa = { + .encrypt = qcrypto_gcrypt_ecdsa_encrypt, + .decrypt = qcrypto_gcrypt_ecdsa_decrypt, + .sign = qcrypto_gcrypt_ecdsa_sign, + .verify = qcrypto_gcrypt_ecdsa_verify, + .free = qcrypto_gcrypt_ecdsa_free, +}; + +static QCryptoGcryptECDSA *qcrypto_gcrypt_ecdsa_new( + const QCryptoAkCipherOptionsECDSA *opt, + QCryptoAkCipherKeyType type, + const uint8_t *key, size_t keylen, + Error **errp) +{ + QCryptoGcryptECDSA *ecdsa; + const char *curve_name = qcrypto_gcrypt_ecdsa_curve_name(opt->curve_id); + size_t coord_len = qcrypto_gcrypt_ecdsa_coord_len(opt->curve_id); + g_autofree uint8_t *point = NULL; + gcry_error_t err; + + if (!curve_name || coord_len == 0) { + error_setg(errp, "Unsupported curve id: %u", opt->curve_id); + return NULL; + } + + ecdsa = g_new0(QCryptoGcryptECDSA, 1); + ecdsa->akcipher.driver = &gcrypt_ecdsa; + ecdsa->type = type; + ecdsa->curve_id = opt->curve_id; + ecdsa->akcipher.max_dgst_len = coord_len; + ecdsa->akcipher.max_signature_len = coord_len * 2; + + switch (type) { + case QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC: + /* public key: Qx || Qy */ + if (keylen != coord_len * 2) { + error_setg(errp, "Public key length %zu is not %zu", + keylen, coord_len * 2); + goto error; + } + /* build uncompressed EC point: 0x04 || Qx || Qy */ + point = g_malloc(1 + keylen); + point[0] = 0x04; + memcpy(point + 1, key, keylen); + err = gcry_sexp_build(&ecdsa->key, NULL, + "(public-key (ecc (curve %s) (q %b)))", + curve_name, (int)(1 + keylen), point); + break; + + case QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE: + /* private key: the scalar d */ + if (keylen != coord_len) { + error_setg(errp, "Private key length %zu is not %zu", + keylen, coord_len); + goto error; + } + err = gcry_sexp_build(&ecdsa->key, NULL, + "(private-key (ecc (curve %s) (d %b)))", + curve_name, (int)keylen, key); + break; + + default: + error_setg(errp, "Unknown akcipher key type %d", type); + goto error; + } + + if (gcry_err_code(err) != 0) { + error_setg(errp, "Failed to build ECDSA key: %s/%s", + gcry_strsource(err), gcry_strerror(err)); + goto error; + } + + return ecdsa; + +error: + qcrypto_gcrypt_ecdsa_free((QCryptoAkCipher *)ecdsa); + return NULL; +} + bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts) { switch (opts->alg) { @@ -589,7 +917,17 @@ bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts) return false; } + case QCRYPTO_AK_CIPHER_ALGO_ECDSA: + switch (opts->u.ecdsa.curve_id) { + case QCRYPTO_CURVE_ID_PRIME256V1: + case QCRYPTO_CURVE_ID_SECP384R1: + return true; + + default: + return false; + } + default: - return true; + return false; } } diff --git a/crypto/akcipher-nettle.c.inc b/crypto/akcipher-nettle.c.inc index 1d4bd6960e..a34845af5d 100644 --- a/crypto/akcipher-nettle.c.inc +++ b/crypto/akcipher-nettle.c.inc @@ -20,6 +20,10 @@ */ #include <nettle/rsa.h> +#include <nettle/ecdsa.h> +#include <nettle/ecc-curve.h> +#include <nettle/ecc.h> +#include <nettle/bignum.h> #include "qemu/osdep.h" #include "qemu/host-utils.h" @@ -55,6 +59,12 @@ static QCryptoAkCipher *qcrypto_nettle_rsa_new( const uint8_t *key, size_t keylen, Error **errp); +static QCryptoAkCipher *qcrypto_nettle_ecdsa_new( + const QCryptoAkCipherOptionsECDSA *opt, + QCryptoAkCipherKeyType type, + const uint8_t *key, size_t keylen, + Error **errp); + QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts, QCryptoAkCipherKeyType type, const uint8_t *key, size_t keylen, @@ -64,6 +74,10 @@ QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts, case QCRYPTO_AK_CIPHER_ALGO_RSA: return qcrypto_nettle_rsa_new(&opts->u.rsa, type, key, keylen, errp); + case QCRYPTO_AK_CIPHER_ALGO_ECDSA: + return qcrypto_nettle_ecdsa_new(&opts->u.ecdsa, type, key, keylen, + errp); + default: error_setg(errp, "Unsupported algorithm: %u", opts->alg); return NULL; @@ -422,6 +436,254 @@ error: } +/* + * ECDSA support (sign and verify) + * + * Keys and signatures use raw big-endian formats: + * - public key: Qx || Qy, each 'coord_len' bytes + * - private key: the scalar d, 'coord_len' bytes + * - signature: r || s, each 'coord_len' bytes + * - the input to sign/verify is a raw message digest + */ +typedef struct QCryptoNettleECDSA { + QCryptoAkCipher akcipher; + QCryptoAkCipherKeyType type; + struct ecc_point pub; + struct ecc_scalar priv; + QCryptoCurveID curve_id; +} QCryptoNettleECDSA; + +static const struct ecc_curve *qcrypto_nettle_ecdsa_curve( + QCryptoCurveID curve_id) +{ + switch (curve_id) { + case QCRYPTO_CURVE_ID_PRIME256V1: + return nettle_get_secp_256r1(); + + case QCRYPTO_CURVE_ID_SECP384R1: + return nettle_get_secp_384r1(); + + default: + return NULL; + } +} + +static size_t qcrypto_nettle_ecdsa_coord_len(QCryptoCurveID curve_id) +{ + switch (curve_id) { + case QCRYPTO_CURVE_ID_PRIME256V1: + return 32; + + case QCRYPTO_CURVE_ID_SECP384R1: + return 48; + + default: + return 0; + } +} + +static void qcrypto_nettle_ecdsa_free(QCryptoAkCipher *akcipher) +{ + QCryptoNettleECDSA *ecdsa = (QCryptoNettleECDSA *)akcipher; + if (!ecdsa) { + return; + } + + if (ecdsa->type == QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE) { + ecc_scalar_clear(&ecdsa->priv); + } else { + ecc_point_clear(&ecdsa->pub); + } + g_free(ecdsa); +} + +static int qcrypto_nettle_ecdsa_encrypt(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + void *out, size_t out_len, + Error **errp) +{ + error_setg(errp, "ECDSA does not support encryption"); + return -1; +} + +static int qcrypto_nettle_ecdsa_decrypt(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + void *out, size_t out_len, + Error **errp) +{ + error_setg(errp, "ECDSA does not support decryption"); + return -1; +} + +static int qcrypto_nettle_ecdsa_sign(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + void *out, size_t out_len, + Error **errp) +{ + QCryptoNettleECDSA *ecdsa = (QCryptoNettleECDSA *)akcipher; + size_t coord_len = qcrypto_nettle_ecdsa_coord_len(ecdsa->curve_id); + struct dsa_signature sig; + + if (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE) { + error_setg(errp, "ECDSA sign requires a private key"); + return -1; + } + + if (in_len == 0 || in_len > akcipher->max_dgst_len) { + error_setg(errp, "Invalid digest length %zu", in_len); + return -1; + } + + if (out_len < coord_len * 2) { + error_setg(errp, "Signature buffer length %zu is less than %zu", + out_len, coord_len * 2); + return -1; + } + + dsa_signature_init(&sig); + ecdsa_sign(&ecdsa->priv, NULL, wrap_nettle_random_func, in_len, in, &sig); + + /* output is r || s, each zero-padded to the curve size */ + nettle_mpz_get_str_256(coord_len, out, sig.r); + nettle_mpz_get_str_256(coord_len, (uint8_t *)out + coord_len, sig.s); + + dsa_signature_clear(&sig); + return coord_len * 2; +} + +static int qcrypto_nettle_ecdsa_verify(QCryptoAkCipher *akcipher, + const void *in, size_t in_len, + const void *in2, size_t in2_len, + Error **errp) +{ + QCryptoNettleECDSA *ecdsa = (QCryptoNettleECDSA *)akcipher; + size_t coord_len = qcrypto_nettle_ecdsa_coord_len(ecdsa->curve_id); + struct dsa_signature sig; + int ret = -1; + + if (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC) { + error_setg(errp, "ECDSA verify requires a public key"); + return ret; + } + + /* signature is r || s */ + if (in_len != coord_len * 2) { + error_setg(errp, "Signature length %zu is not %zu", + in_len, coord_len * 2); + return ret; + } + + if (in2_len == 0 || in2_len > akcipher->max_dgst_len) { + error_setg(errp, "Invalid digest length %zu", in2_len); + return ret; + } + + dsa_signature_init(&sig); + nettle_mpz_set_str_256_u(sig.r, coord_len, in); + nettle_mpz_set_str_256_u(sig.s, coord_len, (const uint8_t *)in + coord_len); + + if (ecdsa_verify(&ecdsa->pub, in2_len, in2, &sig) == 1) { + ret = 0; + } else { + error_setg(errp, "Failed to verify signature"); + } + + dsa_signature_clear(&sig); + return ret; +} + +QCryptoAkCipherDriver nettle_ecdsa = { + .encrypt = qcrypto_nettle_ecdsa_encrypt, + .decrypt = qcrypto_nettle_ecdsa_decrypt, + .sign = qcrypto_nettle_ecdsa_sign, + .verify = qcrypto_nettle_ecdsa_verify, + .free = qcrypto_nettle_ecdsa_free, +}; + +static QCryptoAkCipher *qcrypto_nettle_ecdsa_new( + const QCryptoAkCipherOptionsECDSA *opt, + QCryptoAkCipherKeyType type, + const uint8_t *key, size_t keylen, + Error **errp) +{ + QCryptoNettleECDSA *ecdsa; + const struct ecc_curve *curve = qcrypto_nettle_ecdsa_curve(opt->curve_id); + size_t coord_len = qcrypto_nettle_ecdsa_coord_len(opt->curve_id); + mpz_t x; + mpz_t y; + + if (!curve || coord_len == 0) { + error_setg(errp, "Unsupported curve id: %u", opt->curve_id); + return NULL; + } + + switch (type) { + case QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC: + if (keylen != coord_len * 2) { + error_setg(errp, "Public key length %zu is not %zu", + keylen, coord_len * 2); + return NULL; + } + break; + + case QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE: + if (keylen != coord_len) { + error_setg(errp, "Private key length %zu is not %zu", + keylen, coord_len); + return NULL; + } + break; + + default: + error_setg(errp, "Unknown akcipher key type %d", type); + return NULL; + } + + ecdsa = g_new0(QCryptoNettleECDSA, 1); + ecdsa->akcipher.driver = &nettle_ecdsa; + ecdsa->type = type; + ecdsa->curve_id = opt->curve_id; + ecdsa->akcipher.max_dgst_len = coord_len; + ecdsa->akcipher.max_signature_len = coord_len * 2; + + if (type == QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC) { + /* public key: Qx || Qy */ + ecc_point_init(&ecdsa->pub, curve); + mpz_init(x); + mpz_init(y); + nettle_mpz_set_str_256_u(x, coord_len, key); + nettle_mpz_set_str_256_u(y, coord_len, + (const uint8_t *)key + coord_len); + + if (!ecc_point_set(&ecdsa->pub, x, y)) { + error_setg(errp, "Invalid ECDSA public key (not on curve)"); + mpz_clear(x); + mpz_clear(y); + qcrypto_nettle_ecdsa_free((QCryptoAkCipher *)ecdsa); + return NULL; + } + + mpz_clear(x); + mpz_clear(y); + } else { + /* private key: the scalar d */ + ecc_scalar_init(&ecdsa->priv, curve); + mpz_init(x); + nettle_mpz_set_str_256_u(x, coord_len, key); + + if (!ecc_scalar_set(&ecdsa->priv, x)) { + error_setg(errp, "Invalid ECDSA private key"); + mpz_clear(x); + qcrypto_nettle_ecdsa_free((QCryptoAkCipher *)ecdsa); + return NULL; + } + + mpz_clear(x); + } + + return (QCryptoAkCipher *)ecdsa; +} + bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts) { switch (opts->alg) { @@ -445,6 +707,16 @@ bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts) } break; + case QCRYPTO_AK_CIPHER_ALGO_ECDSA: + switch (opts->u.ecdsa.curve_id) { + case QCRYPTO_CURVE_ID_PRIME256V1: + case QCRYPTO_CURVE_ID_SECP384R1: + return true; + + default: + return false; + } + default: return false; } diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 5db7ce057f..eae06369b0 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -520,6 +520,7 @@ config ASPEED_SOC imply GENERIC_LOADER imply PCI_DEVICES imply E1000E_PCI_EXPRESS + select ASPEED_UDC select DS1338 select FTGMAC100 select I2C diff --git a/hw/arm/aspeed_ast10x0.c b/hw/arm/aspeed_ast10x0.c index 93c81195b5..aeb5a4423d 100644 --- a/hw/arm/aspeed_ast10x0.c +++ b/hw/arm/aspeed_ast10x0.c @@ -163,8 +163,6 @@ static void aspeed_soc_ast10x0_init(Object *obj, const char *socname) object_initialize_child(obj, "hace", &s->hace, typename); object_initialize_child(obj, "iomem", &s->iomem, TYPE_UNIMPLEMENTED_DEVICE); - object_initialize_child(obj, "sbc-unimplemented", &s->sbc_unimplemented, - TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "pwm", &s->pwm, TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "espi", &s->espi, TYPE_UNIMPLEMENTED_DEVICE); object_initialize_child(obj, "udc", &s->udc, TYPE_UNIMPLEMENTED_DEVICE); @@ -222,11 +220,6 @@ static bool aspeed_soc_ast10x0_realize(Aspeed10x0SoCState *a, Error **errp) "aspeed.io", sc->memmap[ASPEED_DEV_IOMEM], ASPEED_SOC_IOMEM_SIZE); - aspeed_mmio_map_unimplemented(s->memory, - SYS_BUS_DEVICE(&s->sbc_unimplemented), - "aspeed.sbc", sc->memmap[ASPEED_DEV_SBC], - 0x40000); - /* AST10x0 CPU Core */ armv7m = DEVICE(&a->armv7m); qdev_prop_set_uint32(armv7m, "num-irq", 256); @@ -257,8 +250,11 @@ static bool aspeed_soc_ast10x0_realize(Aspeed10x0SoCState *a, Error **errp) error_propagate(errp, err); return false; } + memory_region_init(&s->sram_container[1], OBJECT(s), "sec.sram-container", + sc->sram_size[1]); + memory_region_add_subregion(&s->sram_container[1], 0, &s->sram[1]); memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SRAM1], - &s->sram[1]); + &s->sram_container[1]); /* SCU */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->scu), errp)) { @@ -357,6 +353,8 @@ static bool aspeed_soc_ast10x0_realize(Aspeed10x0SoCState *a, Error **errp) } /* Secure Boot Controller */ + object_property_set_link(OBJECT(&s->sbc), "sram", OBJECT(&s->sram[1]), + &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->sbc), errp)) { return false; } diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c index d1f18e471a..1a10d0fe1e 100644 --- a/hw/arm/aspeed_ast2600.c +++ b/hw/arm/aspeed_ast2600.c @@ -8,6 +8,7 @@ */ #include "qemu/osdep.h" +#include "qemu/units.h" #include "qapi/error.h" #include "hw/misc/unimp.h" #include "hw/arm/aspeed_soc.h" @@ -24,6 +25,7 @@ static const hwaddr aspeed_soc_ast2600_memmap[] = { [ASPEED_DEV_SPI_BOOT] = 0x00000000, [ASPEED_DEV_SRAM0] = 0x10000000, + [ASPEED_DEV_SRAM1] = 0x1E710000, /* ACRY SRAM */ [ASPEED_DEV_DPMCU] = 0x18000000, /* 0x16000000 0x17FFFFFF : AHB BUS do LPC Bus bridge */ [ASPEED_DEV_IOMEM] = 0x1E600000, @@ -32,6 +34,7 @@ static const hwaddr aspeed_soc_ast2600_memmap[] = { [ASPEED_DEV_SPI1] = 0x1E630000, [ASPEED_DEV_SPI2] = 0x1E631000, [ASPEED_DEV_EHCI1] = 0x1E6A1000, + [ASPEED_DEV_UDC] = 0x1E6A2000, [ASPEED_DEV_EHCI2] = 0x1E6A3000, [ASPEED_DEV_MII1] = 0x1E650000, [ASPEED_DEV_MII2] = 0x1E650008, @@ -50,6 +53,7 @@ static const hwaddr aspeed_soc_ast2600_memmap[] = { [ASPEED_DEV_DP] = 0x1E6EB000, [ASPEED_DEV_PCIE_PHY1] = 0x1E6ED200, [ASPEED_DEV_SBC] = 0x1E6F2000, + [ASPEED_DEV_ACRY] = 0x1E6FA000, [ASPEED_DEV_EMMC_BC] = 0x1E6f5000, [ASPEED_DEV_VIDEO] = 0x1E700000, [ASPEED_DEV_SDHCI] = 0x1E740000, @@ -142,6 +146,7 @@ static const int aspeed_soc_ast2600_irqmap[] = { [ASPEED_DEV_FSI1] = 100, [ASPEED_DEV_FSI2] = 101, [ASPEED_DEV_I3C] = 102, /* 102 -> 107 */ + [ASPEED_DEV_ACRY] = 160, }; static qemu_irq aspeed_soc_ast2600_get_irq(AspeedSoCState *s, int dev) @@ -214,6 +219,10 @@ static void aspeed_soc_ast2600_init(Object *obj) TYPE_PLATFORM_EHCI); } + object_initialize_child(obj, "udc", &a->udc, TYPE_ASPEED_UDC); + object_initialize_child(obj, "ehci2-udc-orgate", &a->ehci2_udc_orgate, + TYPE_OR_IRQ); + snprintf(typename, sizeof(typename), "aspeed.sdmc-%s", socname); object_initialize_child(obj, "sdmc", &s->sdmc, typename); object_property_add_alias(obj, "ram-size", OBJECT(&s->sdmc), @@ -267,6 +276,8 @@ static void aspeed_soc_ast2600_init(Object *obj) snprintf(typename, sizeof(typename), "aspeed.hace-%s", socname); object_initialize_child(obj, "hace", &s->hace, typename); + object_initialize_child(obj, "acry", &s->acry, TYPE_ASPEED_ACRY); + object_initialize_child(obj, "i3c", &s->i3c, TYPE_ASPEED_I3C); object_initialize_child(obj, "sbc", &s->sbc, TYPE_ASPEED_AST2600_SBC); @@ -361,6 +372,7 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) AspeedSoCState *s = ASPEED_SOC(dev); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); qemu_irq irq; + g_autofree char *sram1_name = NULL; g_autofree char *sram_name = NULL; int uart; @@ -444,6 +456,19 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SRAM0], &s->sram[0]); + /* ACRY SRAM */ + sram1_name = g_strdup_printf("aspeed.acry.sram.%d", + CPU(&a->cpu[0])->cpu_index); + if (!memory_region_init_ram(&s->sram[1], OBJECT(s), sram1_name, + sc->sram_size[1], errp)) { + return; + } + memory_region_init(&s->sram_container[1], OBJECT(s), + "aspeed.acry.sram-container", sc->sram_size[1]); + memory_region_add_subregion(&s->sram_container[1], 0, &s->sram[1]); + memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SRAM1], + &s->sram_container[1]); + /* DPMCU */ aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->dpmcu), "aspeed.dpmcu", @@ -561,6 +586,16 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) aspeed_soc_ast2600_get_irq(s, ASPEED_DEV_SPI1 + i)); } + /* + * EHCI2 and the UDC share one SoC interrupt line, so OR their outputs + * together and drive that GIC input from the OR gate. + */ + object_property_set_int(OBJECT(&a->ehci2_udc_orgate), "num-lines", 2, + &error_abort); + qdev_realize(DEVICE(&a->ehci2_udc_orgate), NULL, &error_abort); + qdev_connect_gpio_out(DEVICE(&a->ehci2_udc_orgate), 0, + aspeed_soc_ast2600_get_irq(s, ASPEED_DEV_EHCI2)); + /* EHCI */ for (i = 0; i < sc->ehcis_num; i++) { if (!sysbus_realize(SYS_BUS_DEVICE(&s->ehci[i]), errp)) { @@ -568,10 +603,26 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) } aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->ehci[i]), 0, sc->memmap[ASPEED_DEV_EHCI1 + i]); - sysbus_connect_irq(SYS_BUS_DEVICE(&s->ehci[i]), 0, - aspeed_soc_ast2600_get_irq(s, - ASPEED_DEV_EHCI1 + i)); } + /* + * EHCI1 has its own IRQ; EHCI2 shares the UDC's IRQ, so route it through + * the OR gate. + */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->ehci[0]), 0, + aspeed_soc_ast2600_get_irq(s, ASPEED_DEV_EHCI1)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->ehci[1]), 0, + qdev_get_gpio_in(DEVICE(&a->ehci2_udc_orgate), 0)); + + /* UDC - USB 2.0 Device Controller */ + object_property_set_link(OBJECT(&a->udc), "dram", OBJECT(s->dram_mr), + &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&a->udc), errp)) { + return; + } + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&a->udc), 0, + sc->memmap[ASPEED_DEV_UDC]); + sysbus_connect_irq(SYS_BUS_DEVICE(&a->udc), 0, + qdev_get_gpio_in(DEVICE(&a->ehci2_udc_orgate), 1)); /* SDMC - SDRAM Memory Controller */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->sdmc), errp)) { @@ -710,6 +761,19 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->hace), 0, aspeed_soc_ast2600_get_irq(s, ASPEED_DEV_HACE)); + /* ACRY */ + object_property_set_link(OBJECT(&s->acry), "dram", OBJECT(s->dram_mr), + &error_abort); + object_property_set_link(OBJECT(&s->acry), "sram", OBJECT(&s->sram[1]), + &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->acry), errp)) { + return; + } + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->acry), 0, + sc->memmap[ASPEED_DEV_ACRY]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->acry), 0, + aspeed_soc_ast2600_get_irq(s, ASPEED_DEV_ACRY)); + /* I3C */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->i3c), errp)) { return; @@ -765,6 +829,7 @@ static void aspeed_soc_ast2600_class_init(ObjectClass *oc, const void *data) sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2600_A3_SILICON_REV; sc->sram_size[0] = 0x16400; + sc->sram_size[1] = 64 * KiB; /* ACRY SRAM */ sc->spis_num = 2; sc->ehcis_num = 2; sc->wdts_num = 4; diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c index 23636ab698..ad2953dfea 100644 --- a/hw/arm/aspeed_ast2600_catalina.c +++ b/hw/arm/aspeed_ast2600_catalina.c @@ -21,7 +21,6 @@ #define CATALINA_BMC_HW_STRAP2 0x00000800 #define CATALINA_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB) -#define TYPE_TMP75 TYPE_TMP105 #define TYPE_TMP421 "tmp421" #define TYPE_DS1338 "ds1338" diff --git a/hw/arm/aspeed_ast2600_fuji.c b/hw/arm/aspeed_ast2600_fuji.c index 8a87c2dbee..dae0239387 100644 --- a/hw/arm/aspeed_ast2600_fuji.c +++ b/hw/arm/aspeed_ast2600_fuji.c @@ -14,8 +14,6 @@ #include "hw/sensor/tmp105.h" #include "hw/nvram/eeprom_at24c.h" -#define TYPE_LM75 TYPE_TMP105 -#define TYPE_TMP75 TYPE_TMP105 #define TYPE_TMP422 "tmp422" /* Fuji hardware value */ @@ -56,8 +54,8 @@ static void fuji_bmc_i2c_init(AspeedMachineState *bmc) get_pca9548_channels(i2c[40 + i], 0x76, &i2c[80 + i * 8]); } - i2c_slave_create_simple(i2c[17], TYPE_LM75, 0x4c); - i2c_slave_create_simple(i2c[17], TYPE_LM75, 0x4d); + i2c_slave_create_simple(i2c[17], TYPE_LM75B, 0x4c); + i2c_slave_create_simple(i2c[17], TYPE_LM75B, 0x4d); /* * EEPROM 24c64 size is 64Kbits or 8 Kbytes @@ -67,15 +65,15 @@ static void fuji_bmc_i2c_init(AspeedMachineState *bmc) at24c_eeprom_init(i2c[20], 0x50, 256); at24c_eeprom_init(i2c[22], 0x52, 256); - i2c_slave_create_simple(i2c[3], TYPE_LM75, 0x48); - i2c_slave_create_simple(i2c[3], TYPE_LM75, 0x49); - i2c_slave_create_simple(i2c[3], TYPE_LM75, 0x4a); + i2c_slave_create_simple(i2c[3], TYPE_LM75B, 0x48); + i2c_slave_create_simple(i2c[3], TYPE_LM75B, 0x49); + i2c_slave_create_simple(i2c[3], TYPE_LM75B, 0x4a); i2c_slave_create_simple(i2c[3], TYPE_TMP422, 0x4c); at24c_eeprom_init(i2c[8], 0x51, 8 * KiB); - i2c_slave_create_simple(i2c[8], TYPE_LM75, 0x4a); + i2c_slave_create_simple(i2c[8], TYPE_LM75B, 0x4a); - i2c_slave_create_simple(i2c[50], TYPE_LM75, 0x4c); + i2c_slave_create_simple(i2c[50], TYPE_LM75B, 0x4c); at24c_eeprom_init(i2c[50], 0x52, 8 * KiB); i2c_slave_create_simple(i2c[51], TYPE_TMP75, 0x48); i2c_slave_create_simple(i2c[52], TYPE_TMP75, 0x49); diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c new file mode 100644 index 0000000000..599ef1701f --- /dev/null +++ b/hw/arm/aspeed_ast2600_sanmiguel.c @@ -0,0 +1,403 @@ +/* + * Facebook SanMiguel BMC + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/arm/aspeed.h" +#include "hw/arm/aspeed_soc.h" +#include "hw/i2c/i2c_mux_pca954x.h" +#include "hw/gpio/pca9552.h" +#include "hw/gpio/pca9554.h" +#include "hw/nvram/eeprom_at24c.h" +#include "hw/sensor/tmp105.h" + +/* SanMiguel hardware values */ +#define SANMIGUEL_BMC_HW_STRAP1 0x00002002 +#define SANMIGUEL_BMC_HW_STRAP2 0x00000000 +#define SANMIGUEL_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB) + +#define TYPE_DS1338 "ds1338" + +/* + * "Front IO" FRU data. Generated with frugen. + * + * { + * "board": { + * "mfg": "Quanta", + * "pname": "San Miguel FIO EVT (QEMU)", + * "pn": "00000000000", + * "serial": "0000000000000", + * "date": "03/10/2026 00:00", + * "custom": ["19-101243"] + * }, + * "product": { + * "mfg": "Quanta", + * "pname": "CI-San Miguel", + * "pn": "10000000001", + * "ver": "EVT", + * "serial": "10000000000000001", + * "atag": "QEMU" + * } + * } + */ +static const uint8_t fio_eeprom[] = { + 0x01, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0xf4, 0x01, 0x09, 0x19, 0x90, + 0xd2, 0xf6, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xd9, 0x53, 0x61, + 0x6e, 0x20, 0x4d, 0x69, 0x67, 0x75, 0x65, 0x6c, 0x20, 0x46, 0x49, 0x4f, + 0x20, 0x45, 0x56, 0x54, 0x20, 0x28, 0x51, 0x45, 0x4d, 0x55, 0x29, 0x8a, + 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x89, 0x10, + 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x01, 0xc0, 0x87, 0x51, 0xd6, + 0x44, 0x50, 0x24, 0x51, 0x13, 0xc1, 0x00, 0xf5, 0x01, 0x08, 0x19, 0xc6, + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcd, 0x43, 0x49, 0x2d, 0x53, 0x61, + 0x6e, 0x20, 0x4d, 0x69, 0x67, 0x75, 0x65, 0x6c, 0x89, 0x11, 0x04, 0x41, + 0x10, 0x04, 0x41, 0x10, 0x14, 0x01, 0x83, 0xa5, 0x4d, 0x03, 0x8d, 0x11, + 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x11, + 0x83, 0x71, 0xd9, 0xd6, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; +static const size_t fio_eeprom_len = sizeof(fio_eeprom); + +/* + * "Power Distribution Board" FRU data. Generated with frugen. + * + * { + * "chassis": { + * "type": 23, + * "pn": "", + * "serial": "" + * }, + * "board": { + * "mfg": "Quanta", + * "pname": "San Miguel PDB EVT (QEMU)", + * "pn": "00000000000", + * "serial": "0000000000000", + * "date": "03/10/2026 00:00", + * "custom": ["19-101240"] + * }, + * "product": { + * "mfg": "Quanta", + * "pname": "CI-San Miguel", + * "pn": "10000000001", + * "ver": "EVT", + * "serial": "10000000000000001", + * "atag": "QEMU" + * } + * } + */ +static const uint8_t pdb_eeprom[] = { + 0x01, 0x00, 0x01, 0x02, 0x0b, 0x00, 0x00, 0xf1, 0x01, 0x01, 0x17, 0xc0, + 0xc0, 0xc1, 0x00, 0xa6, 0x01, 0x09, 0x19, 0x90, 0xd2, 0xf6, 0xc6, 0x51, + 0x75, 0x61, 0x6e, 0x74, 0x61, 0xd9, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, + 0x67, 0x75, 0x65, 0x6c, 0x20, 0x50, 0x44, 0x42, 0x20, 0x45, 0x56, 0x54, + 0x20, 0x28, 0x51, 0x45, 0x4d, 0x55, 0x29, 0x8a, 0x10, 0x04, 0x41, 0x10, + 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x89, 0x10, 0x04, 0x41, 0x10, 0x04, + 0x41, 0x10, 0x04, 0x01, 0xc0, 0x87, 0x51, 0xd6, 0x44, 0x50, 0x24, 0x51, + 0x10, 0xc1, 0x00, 0x00, 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, + 0x74, 0x61, 0xcd, 0x43, 0x49, 0x2d, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, + 0x67, 0x75, 0x65, 0x6c, 0x89, 0x11, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, + 0x14, 0x01, 0x83, 0xa5, 0x4d, 0x03, 0x8d, 0x11, 0x04, 0x41, 0x10, 0x04, + 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x11, 0x83, 0x71, 0xd9, 0xd6, + 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; +static const size_t pdb_eeprom_len = sizeof(pdb_eeprom); + +/* + * "Secure Control Module" FRU data. Generated with frugen. + * + * { + * "chassis": { + * "type": 23, + * "pn": "", + * "serial": "", + * "custom": [""] + * }, + * "board": { + * "mfg": "Quanta", + * "pname": "San Miguel SCM EVT (QEMU)", + * "pn": "00000000000", + * "serial": "0000000000000", + * "date": "03/10/2026 00:00", + * "custom": ["19-101242"] + * }, + * "product": { + * "mfg": "Quanta", + * "pname": "CI-San Miguel", + * "pn": "10000000001", + * "ver": "EVT", + * "serial": "10000000000000001", + * "atag": "QEMU" + * } + * } + */ +static const uint8_t scm_eeprom[] = { + 0x01, 0x00, 0x01, 0x02, 0x0b, 0x00, 0x00, 0xf1, 0x01, 0x01, 0x17, 0xc0, + 0xc0, 0xc0, 0xc1, 0xe6, 0x01, 0x09, 0x19, 0x90, 0xd2, 0xf6, 0xc6, 0x51, + 0x75, 0x61, 0x6e, 0x74, 0x61, 0xd9, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, + 0x67, 0x75, 0x65, 0x6c, 0x20, 0x53, 0x43, 0x4d, 0x20, 0x45, 0x56, 0x54, + 0x20, 0x28, 0x51, 0x45, 0x4d, 0x55, 0x29, 0x8a, 0x10, 0x04, 0x41, 0x10, + 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x89, 0x10, 0x04, 0x41, 0x10, 0x04, + 0x41, 0x10, 0x04, 0x01, 0xc0, 0x87, 0x51, 0xd6, 0x44, 0x50, 0x24, 0x51, + 0x12, 0xc1, 0x00, 0xf1, 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, + 0x74, 0x61, 0xcd, 0x43, 0x49, 0x2d, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, + 0x67, 0x75, 0x65, 0x6c, 0x89, 0x11, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, + 0x14, 0x01, 0x83, 0xa5, 0x4d, 0x03, 0x8d, 0x11, 0x04, 0x41, 0x10, 0x04, + 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x11, 0x83, 0x71, 0xd9, 0xd6, + 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; +static const size_t scm_eeprom_len = sizeof(scm_eeprom); + +/* + * "SMM Carrier" FRU data. Generated with frugen. + * + * { + * "chassis": { + * "type": 23, + * "pn": "", + * "serial": "" + * }, + * "board": { + * "mfg": "Quanta", + * "pname": "San Miguel SMM Carrier EVT (QEMU)", + * "pn": "00000000000", + * "serial": "0000000000000", + * "date": "03/11/2026 17:36", + * "custom": ["19-101238"] + * }, + * "product": { + * "mfg": "Quanta", + * "pname": "CI-San Miguel", + * "pn": "10000000001", + * "ver": "EVT", + * "serial": "10000000000000001", + * "atag": "QEMU" + * } + * } + */ +static const uint8_t smm_eeprom[] = { + 0x01, 0x00, 0x01, 0x02, 0x0c, 0x00, 0x00, 0xf0, 0x01, 0x01, 0x17, 0xc0, + 0xc0, 0xc1, 0x00, 0xa6, 0x01, 0x0a, 0x19, 0x4c, 0x85, 0xf7, 0xc6, 0x51, + 0x75, 0x61, 0x6e, 0x74, 0x61, 0xe1, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, + 0x67, 0x75, 0x65, 0x6c, 0x20, 0x53, 0x4d, 0x4d, 0x20, 0x43, 0x61, 0x72, + 0x72, 0x69, 0x65, 0x72, 0x20, 0x45, 0x56, 0x54, 0x20, 0x28, 0x51, 0x45, + 0x4d, 0x55, 0x29, 0x8a, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, + 0x41, 0x10, 0x89, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x01, + 0xc0, 0x87, 0x51, 0xd6, 0x44, 0x50, 0x24, 0x4d, 0x18, 0xc1, 0x00, 0x84, + 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcd, 0x43, + 0x49, 0x2d, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, 0x67, 0x75, 0x65, 0x6c, + 0x89, 0x11, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x14, 0x01, 0x83, 0xa5, + 0x4d, 0x03, 0x8d, 0x11, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, + 0x10, 0x04, 0x41, 0x11, 0x83, 0x71, 0xd9, 0xd6, 0xc0, 0xc1, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x9b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; +static const size_t smm_eeprom_len = sizeof(smm_eeprom); + +/* + * "SMM Extender" FRU data. Generated with frugen. + * + * { + * "chassis": { + * "type": 23, + * "pn": "", + * "serial": "" + * }, + * "board": { + * "mfg": "Quanta", + * "pname": "San Miguel SMM Extender EVT (QEMU)", + * "pn": "00000000000", + * "serial": "0000000000000", + * "date": "03/10/2026 00:00", + * "custom": ["19-101239"] + * }, + * "product": { + * "mfg": "Quanta", + * "pname": "CI-San Miguel", + * "pn": "10000000001", + * "ver": "EVT", + * "serial": "10000000000000001", + * "atag": "QEMU" + * } + * } + */ +static const uint8_t smm_ext_eeprom[] = { + 0x01, 0x00, 0x01, 0x02, 0x0c, 0x00, 0x00, 0xf0, 0x01, 0x01, 0x17, 0xc0, + 0xc0, 0xc1, 0x00, 0xa6, 0x01, 0x0a, 0x19, 0x90, 0xd2, 0xf6, 0xc6, 0x51, + 0x75, 0x61, 0x6e, 0x74, 0x61, 0xe2, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, + 0x67, 0x75, 0x65, 0x6c, 0x20, 0x53, 0x4d, 0x4d, 0x20, 0x45, 0x78, 0x74, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x45, 0x56, 0x54, 0x20, 0x28, 0x51, + 0x45, 0x4d, 0x55, 0x29, 0x8a, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, + 0x04, 0x41, 0x10, 0x89, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, + 0x01, 0xc0, 0x87, 0x51, 0xd6, 0x44, 0x50, 0x24, 0x4d, 0x19, 0xc1, 0x7b, + 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcd, 0x43, + 0x49, 0x2d, 0x53, 0x61, 0x6e, 0x20, 0x4d, 0x69, 0x67, 0x75, 0x65, 0x6c, + 0x89, 0x11, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x14, 0x01, 0x83, 0xa5, + 0x4d, 0x03, 0x8d, 0x11, 0x04, 0x41, 0x10, 0x04, 0x41, 0x10, 0x04, 0x41, + 0x10, 0x04, 0x41, 0x11, 0x83, 0x71, 0xd9, 0xd6, 0xc0, 0xc1, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x9b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; +static const size_t smm_ext_eeprom_len = sizeof(smm_ext_eeprom); + +static void sanmiguel_bmc_i2c_init(AspeedMachineState *bmc) +{ + /* Reference: aspeed-bmc-facebook-sanmiguel.dts */ + + AspeedSoCState *soc = bmc->soc; + I2CBus *i2c[16] = {}; + + for (unsigned idx = 0; idx < ARRAY_SIZE(i2c); idx++) { + i2c[idx] = aspeed_i2c_get_bus(&soc->i2c, idx); + } + + /* &i2c0 */ + /* ssif-bmc@10 — no QEMU model */ + + /* &i2c1 — empty */ + + /* &i2c2 — HPM0 */ + /* hpm0_ioexp_20: pca9555@20 */ + i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x20); + /* hpm0_ioexp_21: pca9555@21 */ + i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x21); + + /* &i2c3 — empty */ + /* &i2c4 — empty */ + + /* &i2c5 — SMM */ + /* smm_ioexp_20: pca9555@20 */ + i2c_slave_create_simple(i2c[5], TYPE_PCA9555, 0x20); + /* smm_ioexp_21: pca9555@21 */ + i2c_slave_create_simple(i2c[5], TYPE_PCA9555, 0x21); + /* smm_temp: lm75@48 */ + i2c_slave_create_simple(i2c[5], TYPE_TMP75, 0x48); + /* smm_fru: eeprom@50 (24c128 = 16 KiB) */ + at24c_eeprom_init_rom(i2c[5], 0x50, 16 * KiB, smm_eeprom, smm_eeprom_len); + + /* rtc@6f — nct3018y (ds1338 stand-in) */ + i2c_slave_create_simple(i2c[5], TYPE_DS1338, 0x6f); + + /* &i2c6 — HMC */ + /* hmc_ioexp: tca6408@20 (pca9554 stand-in) */ + i2c_slave_create_simple(i2c[6], TYPE_PCA9554, 0x20); + /* i2c-mux@70 (PCA9546) — 4 channels (imux16-19), all empty */ + i2c_slave_create_simple(i2c[6], TYPE_PCA9546, 0x70); + + /* &i2c7 — HPM1 */ + /* hpm1_ioexp_20: pca9555@20 */ + i2c_slave_create_simple(i2c[7], TYPE_PCA9555, 0x20); + /* hpm1_ioexp_21: pca9555@21 */ + i2c_slave_create_simple(i2c[7], TYPE_PCA9555, 0x21); + + /* &i2c8 — empty */ + + /* &i2c9 — PDB */ + /* mp5926@10-16, lm5066i@11-17 — no QEMU model (power monitors) */ + /* pdb_ioexp_20: pca9555@20 */ + i2c_slave_create_simple(i2c[9], TYPE_PCA9555, 0x20); + /* pdb_temp: lm75@4e */ + i2c_slave_create_simple(i2c[9], TYPE_TMP75, 0x4e); + /* pdb_fru: eeprom@50 (24c128 = 16 KiB) */ + at24c_eeprom_init_rom(i2c[9], 0x50, 16 * KiB, pdb_eeprom, pdb_eeprom_len); + + /* &i2c10 — SCM */ + /* scm_temp: lm75@48 */ + i2c_slave_create_simple(i2c[10], TYPE_TMP75, 0x48); + /* scm_fru: eeprom@50 (24c128 = 16 KiB) */ + at24c_eeprom_init_rom(i2c[10], 0x50, 16 * KiB, scm_eeprom, scm_eeprom_len); + + /* &i2c11 — Switch Config */ + /* sw_config: eeprom@50 (24c64 = 8 KiB) — blank (content not modelled) */ + at24c_eeprom_init(i2c[11], 0x50, 8 * KiB); + + /* &i2c12 — empty */ + + /* &i2c13 — SMM extension */ + /* mctp@10 — no QEMU model */ + /* smm_ext_ioexp: pca9554@38 */ + i2c_slave_create_simple(i2c[13], TYPE_PCA9554, 0x38); + /* smm_ext_fru: eeprom@55 (24c128 = 16 KiB) */ + at24c_eeprom_init_rom(i2c[13], 0x55, 16 * KiB, smm_ext_eeprom, + smm_ext_eeprom_len); + + /* &i2c14 — FIO */ + /* fio_ioexp: pca9555@20 */ + i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x20); + /* fio_fru: eeprom@50 (24c64 = 8 KiB) */ + at24c_eeprom_init_rom(i2c[14], 0x50, 8 * KiB, fio_eeprom, fio_eeprom_len); + + /* &i2c15 — empty */ +} + +static void aspeed_machine_sanmiguel_class_init(ObjectClass *oc, + const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc); + + mc->desc = "Facebook SanMiguel BMC (Cortex-A7)"; + amc->soc_name = "ast2600-a3"; + amc->hw_strap1 = SANMIGUEL_BMC_HW_STRAP1; + amc->hw_strap2 = SANMIGUEL_BMC_HW_STRAP2; + amc->fmc_model = "mx66l1g45g"; + amc->spi_model = "mx66l1g45g"; + amc->num_cs = 2; + amc->macs_mask = ASPEED_MAC0_ON; + amc->i2c_init = sanmiguel_bmc_i2c_init; + mc->default_ram_size = SANMIGUEL_BMC_RAM_SIZE; + aspeed_machine_class_init_cpus_defaults(mc); +} + +static const TypeInfo aspeed_ast2600_sanmiguel_types[] = { + { + .name = MACHINE_TYPE_NAME("sanmiguel-bmc"), + .parent = TYPE_ASPEED_MACHINE, + .class_init = aspeed_machine_sanmiguel_class_init, + } +}; + +DEFINE_TYPES(aspeed_ast2600_sanmiguel_types) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 78acb04c8b..bcc068de04 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -64,6 +64,7 @@ arm_common_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( 'aspeed_ast2600_fuji.c', 'aspeed_ast2600_gb200nvl.c', 'aspeed_ast2600_rainier.c', + 'aspeed_ast2600_sanmiguel.c', 'aspeed_ast10x0.c', 'aspeed_ast10x0_evb.c', 'aspeed_ast1040.c', diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index 68bdcd0e25..0bc4bb6fbe 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c @@ -159,6 +159,7 @@ static uint64_t aspeed_i2c_bus_new_read(AspeedI2CBus *bus, hwaddr offset, case A_I2CS_INTR_CTRL: case A_I2CS_DMA_LEN_STS: case A_I2CS_INTR_STS: + case A_I2CC_BYTE_DATA_LOG: case A_I2CC_VERSION_CTRL: value = bus->regs[offset / sizeof(*bus->regs)]; break; @@ -334,6 +335,27 @@ static int aspeed_i2c_bus_send_dma_pool(AspeedI2CBus *bus) return ret; } +/* + * Latch a received byte where firmware reads it back from: the receive byte + * buffer, only valid while the DMA buffer is disabled, and the byte data log, + * which AST2700 uses instead. Buffer and DMA transfers latch only the first + * byte, read back as the SMBus block length. + */ +static void aspeed_i2c_bus_latch_rx_byte(AspeedI2CBus *bus, uint8_t data) +{ + AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller); + uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus); + uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus); + + if (aic->has_byte_data_log) { + ARRAY_FIELD_DP32(bus->regs, I2CC_BYTE_DATA_LOG, RX_BUF, data); + } + + if (!SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN)) { + SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data); + } +} + static void aspeed_i2c_bus_recv_dma_pool(AspeedI2CBus *bus) { AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller); @@ -349,6 +371,9 @@ static void aspeed_i2c_bus_recv_dma_pool(AspeedI2CBus *bus) 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]); + if (i == 0) { + aspeed_i2c_bus_latch_rx_byte(bus, pool_base[offset]); + } bus->regs[reg_dma_len]--; ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, i + 1); } @@ -443,6 +468,9 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus) pool_base[i] = i2c_recv(bus->bus); trace_aspeed_i2c_bus_recv("BUF", i + 1, pool_rx_count, pool_base[i]); + if (i == 0) { + aspeed_i2c_bus_latch_rx_byte(bus, pool_base[0]); + } } /* Update RX count */ @@ -460,7 +488,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus) } aspeed_i2c_set_rx_dma_dram_offset(bus); - while (bus->regs[reg_dma_len]) { + for (i = 0; bus->regs[reg_dma_len]; i++) { MemTxResult result; data = i2c_recv(bus->bus); @@ -476,6 +504,10 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus) return; } + if (i == 0) { + aspeed_i2c_bus_latch_rx_byte(bus, data); + } + bus->dma_dram_offset++; bus->regs[reg_dma_len]--; /* In new mode, keep track of how many bytes we RXed */ @@ -489,7 +521,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus) } else { data = i2c_recv(bus->bus); trace_aspeed_i2c_bus_recv("BYTE", 1, 1, bus->regs[reg_byte_buf]); - SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data); + aspeed_i2c_bus_latch_rx_byte(bus, data); } } @@ -1758,6 +1790,7 @@ static void aspeed_1040_i2c_class_init(ObjectClass *klass, const void *data) aic->has_dma = true; aic->mem_size = 0x2000; aic->has_dma64 = true; + aic->has_byte_data_log = true; aic->dma_addr_lo_mask = 0x00ffffff; } @@ -1780,6 +1813,7 @@ static void aspeed_2700_i2c_class_init(ObjectClass *klass, const void *data) aic->has_dma = true; aic->mem_size = 0x2000; aic->has_dma64 = true; + aic->has_byte_data_log = true; aic->dma_addr_lo_mask = 0xffffffff; } diff --git a/hw/misc/aspeed_acry.c b/hw/misc/aspeed_acry.c new file mode 100644 index 0000000000..41eac7295f --- /dev/null +++ b/hw/misc/aspeed_acry.c @@ -0,0 +1,481 @@ +/* + * ASPEED ACRY Engine + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * The datasheet documents the ACRY engine as supporting both RSA and + * ECDSA, but ECDSA is broken on this hardware, so only RSA is modelled + * here. + */ + +#include "qemu/osdep.h" +#include "qemu/cutils.h" +#include "qemu/log.h" +#include "hw/misc/aspeed_acry.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "qapi/error.h" +#include "crypto/akcipher.h" +#include "crypto/der.h" +#include "trace.h" + +REG32(ACRY_TRIGGER, 0x000) + FIELD(ACRY_TRIGGER, RSA_START, 0, 1) +REG32(ACRY_DMA_SRC, 0x04C) +REG32(ACRY_DMA_LEN, 0x050) + FIELD(ACRY_DMA_LEN, DATALEN, 0, 16) +REG32(ACRY_INT_MASK, 0x3F8) + FIELD(ACRY_INT_MASK, RSA_DMA_MASK, 2, 1) + FIELD(ACRY_INT_MASK, RSA_ENG_MASK, 1, 1) +REG32(ACRY_STATUS, 0x3FC) + FIELD(ACRY_STATUS, RSA_DMA_DONE, 2, 1) + FIELD(ACRY_STATUS, RSA_ENG_DONE, 1, 1) + +/* + * Total size of the interleaved buffer. Data is 4 of every 12 + * dwords of a block, one third of the buffer, so the whole buffer is 3x + * the data region. + */ +#define ASPEED_ACRY_SRAM_SIZE (3 * ASPEED_ACRY_DATA_MAX_LEN) + +#define ASPEED_ACRY_BYTES_PER_DWORD 4 +#define ASPEED_ACRY_LANES_PER_BLOCK 4 +/* Each block holds 3 regions (exp, mod, data) of LANES_PER_BLOCK dwords. */ +#define ASPEED_ACRY_DWORDS_PER_BLOCK (3 * ASPEED_ACRY_LANES_PER_BLOCK) + +/* Dwords into each block where each operand's region starts. */ +#define ASPEED_ACRY_EXP_OFFSET (0 * ASPEED_ACRY_LANES_PER_BLOCK) +#define ASPEED_ACRY_MOD_OFFSET (1 * ASPEED_ACRY_LANES_PER_BLOCK) +#define ASPEED_ACRY_DATA_OFFSET (2 * ASPEED_ACRY_LANES_PER_BLOCK) + +static void aspeed_acry_hexdump(const char *desc, const uint8_t *buf, + size_t size) +{ + g_autoptr(GString) str = g_string_sized_new(64); + size_t len; + size_t i; + + for (i = 0; i < size; i += len) { + len = MIN(16, size - i); + g_string_truncate(str, 0); + qemu_hexdump_line(str, buf + i, len, 1, 4); + trace_aspeed_acry_hexdump(desc, i, str->str); + } +} + +/* + * The interleaved buffer is a series of 12-dword blocks, each split into + * three 4-dword regions - exp, mod, data: + * + * dword in block: 0 1 2 3 4 5 6 7 8 9 10 11 + * region: \---- exp ----/ \---- mod ----/ \---- data ----/ + * lane: 0 1 2 3 0 1 2 3 0 1 2 3 + * + * Successive blocks hold the next 4 dwords of each operand, so operand + * dword d is in block (d / 4), lane (d % 4). Dwords are little-endian, so + * byte b of dword D is at byte D * 4 + b. + * + * Return the buffer offset of byte 'op_byte' (op_byte = 0 = least + * significant) of the operand whose region starts 'region' dwords into + * each block (0 = exp, 4 = mod, 8 = data). + */ +static int aspeed_acry_operand_offset(int region, int op_byte) +{ + int byte_in_dword; + int op_dword; + int offset; + int block; + int lane; + + op_dword = op_byte / ASPEED_ACRY_BYTES_PER_DWORD; + byte_in_dword = op_byte % ASPEED_ACRY_BYTES_PER_DWORD; + block = op_dword / ASPEED_ACRY_LANES_PER_BLOCK; + lane = op_dword % ASPEED_ACRY_LANES_PER_BLOCK; + + offset = (block * ASPEED_ACRY_DWORDS_PER_BLOCK + region + lane) + * ASPEED_ACRY_BYTES_PER_DWORD + byte_in_dword; + assert(offset < ASPEED_ACRY_SRAM_SIZE); + + return offset; +} + +/* + * Read one operand out of the buffer as a big-endian magnitude. + * + * The operand's bytes are scattered through buf; byte k (significance level + * k, k = 0 = least significant) is at aspeed_acry_operand_offset(region, k). + * Walk from the top down, drop leading zero bytes, and write the result most + * significant byte first into out[]. Returns the number of bytes written + * (the value 0 yields a single 0x00 byte, so always >= 1). + */ +static int aspeed_acry_extract_be(const uint8_t *buf, int region, + int max_bytes, uint8_t *out) +{ + int offset; + int msb; + int len; + int k; + + /* Highest significance level holding a non-zero byte (skip leading 0s). */ + for (msb = max_bytes - 1; msb >= 0; msb--) { + offset = aspeed_acry_operand_offset(region, msb); + if (buf[offset] != 0) { + break; + } + } + + /* All bytes zero: the value is 0. */ + if (msb < 0) { + out[0] = 0; + return 1; + } + + /* Copy most significant byte first: level msb down to level 0. */ + len = 0; + for (k = msb; k >= 0; k--) { + offset = aspeed_acry_operand_offset(region, k); + out[len++] = buf[offset]; + } + + return len; +} + +/* + * Return a DER INTEGER body for the unsigned big-endian magnitude 'be'. + * + * DER INTEGERs are signed, so if the top byte has bit 7 set the value + * would decode as negative; prepend a 0x00 guard byte in that case. + * + * The padded copy is written into 'pad_buf' (caller-owned, sized len + 1) + * rather than a local, because qcrypto_der_encode_int() only stores the + * pointer we hand it - the bytes are not copied until + * qcrypto_der_encode_ctx_flush_and_free() - so the body must stay valid + * until then. Returns a pointer into 'be' or 'pad_buf' as appropriate, + * with the body length in *body_len. + */ +static const uint8_t *aspeed_acry_der_uint_body(const uint8_t *be, size_t len, + uint8_t *pad_buf, + size_t *body_len) +{ + if (be[0] & 0x80) { + pad_buf[0] = 0x00; + memcpy(pad_buf + 1, be, len); + *body_len = len + 1; + return pad_buf; + } + + *body_len = len; + return be; +} + +/* + * DER-encode a "RsaPubKey ::= SEQUENCE { n INTEGER, e INTEGER }" (see + * crypto/rsakey.h), the format expected by qcrypto_akcipher_new(). n and e + * are minimal big-endian magnitudes (as produced by + * aspeed_acry_extract_be()); the engine does a raw modexp, so the guest's + * exponent is always encoded here as the public 'e'. + */ +static uint8_t *aspeed_acry_der_encode_pubkey(const uint8_t *n, size_t n_len, + const uint8_t *e, size_t e_len, + size_t *out_len) +{ + QCryptoEncodeContext *ctx = qcrypto_der_encode_ctx_new(); + uint8_t n_pad[ASPEED_ACRY_MAX_BYTES + 1]; + uint8_t e_pad[ASPEED_ACRY_MAX_BYTES + 1]; + const uint8_t *n_body; + const uint8_t *e_body; + size_t n_body_len; + size_t e_body_len; + uint8_t *buf; + + n_body = aspeed_acry_der_uint_body(n, n_len, n_pad, &n_body_len); + e_body = aspeed_acry_der_uint_body(e, e_len, e_pad, &e_body_len); + + qcrypto_der_encode_seq_begin(ctx); + qcrypto_der_encode_int(ctx, n_body, n_body_len); + qcrypto_der_encode_int(ctx, e_body, e_body_len); + qcrypto_der_encode_seq_end(ctx); + + *out_len = qcrypto_der_encode_ctx_buffer_len(ctx); + buf = g_malloc(*out_len); + qcrypto_der_encode_ctx_flush_and_free(ctx, buf); + + return buf; +} + +/* + * Store the RSA result into the output SRAM data region as 'n_len' bytes + * (the key size): the low 'result_len' bytes are result_be (big-endian), + * the rest is zero. sram_as is a 0-based AddressSpace over the SRAM, so the + * offset from aspeed_acry_operand_offset() is used directly; each data dword + * is written as a little-endian word. + */ +static bool aspeed_acry_store_result(AspeedACRYState *s, + const uint8_t *result_be, + int result_len, int n_len, Error **errp) +{ + uint32_t result_word; + MemTxResult res; + int offset; + int src; + int i; + int j; + + /* result_be is MSB-first; take bytes from its LSB end. */ + src = result_len - 1; + for (i = 0; i < n_len / ASPEED_ACRY_BYTES_PER_DWORD; i++) { + /* Pack up to 4 result bytes (LSB first) into a little-endian dword. */ + result_word = 0; + for (j = 0; j < ASPEED_ACRY_BYTES_PER_DWORD; j++) { + if (src >= 0) { + result_word |= (uint32_t)result_be[src--] << (8 * j); + } + } + + offset = aspeed_acry_operand_offset(ASPEED_ACRY_DATA_OFFSET, + ASPEED_ACRY_BYTES_PER_DWORD * i); + address_space_stl_le(&s->sram_as, offset, result_word, + MEMTXATTRS_UNSPECIFIED, &res); + if (res != MEMTX_OK) { + error_setg(errp, "failed to write result to SRAM"); + return false; + } + } + + return true; +} + +static void aspeed_acry_clear_result(AspeedACRYState *s) +{ + MemTxResult res; + int offset; + int i; + + for (i = 0; i < ASPEED_ACRY_MAX_BYTES / ASPEED_ACRY_BYTES_PER_DWORD; i++) { + offset = aspeed_acry_operand_offset(ASPEED_ACRY_DATA_OFFSET, + ASPEED_ACRY_BYTES_PER_DWORD * i); + address_space_stl_le(&s->sram_as, offset, 0, + MEMTXATTRS_UNSPECIFIED, &res); + if (res != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to clear result\n", + __func__); + return; + } + } +} + +static bool aspeed_acry_do_rsa(AspeedACRYState *s, Error **errp) +{ + QCryptoAkCipherOptions opts = { + .alg = QCRYPTO_AK_CIPHER_ALGO_RSA, + .u.rsa = { + .padding_alg = QCRYPTO_RSA_PADDING_ALGO_RAW, + }, + }; + uint32_t len = FIELD_EX32(s->regs[R_ACRY_DMA_LEN], ACRY_DMA_LEN, DATALEN); + g_autofree uint8_t *src_buf = g_malloc0(ASPEED_ACRY_SRAM_SIZE); + g_autofree uint8_t *data = g_malloc0(ASPEED_ACRY_DATA_MAX_LEN); + uint8_t result[ASPEED_ACRY_MAX_BYTES] = { 0 }; + uint64_t src_addr = s->regs[R_ACRY_DMA_SRC]; + g_autofree uint8_t *der_key = NULL; + uint8_t n[ASPEED_ACRY_MAX_BYTES]; + uint8_t e[ASPEED_ACRY_MAX_BYTES]; + QCryptoAkCipher *cipher = NULL; + int result_len = 0; + size_t der_len; + int data_len; + int n_len; + int e_len; + + if (!qcrypto_akcipher_supports(&opts)) { + error_setg(errp, "RSA ModExp not supported by the crypto backend"); + return false; + } + + if (len == 0 || len > ASPEED_ACRY_SRAM_SIZE) { + error_setg(errp, "invalid DMA length %u", len); + return false; + } + + trace_aspeed_acry_rsa_trigger(src_addr, len); + + if (address_space_read(&s->dram_as, src_addr, MEMTXATTRS_UNSPECIFIED, + src_buf, len) != MEMTX_OK) { + error_setg(errp, "failed to read DMA buffer at 0x%" PRIx64, src_addr); + return false; + } + + n_len = aspeed_acry_extract_be(src_buf, ASPEED_ACRY_MOD_OFFSET, + ASPEED_ACRY_MAX_BYTES, n); + e_len = aspeed_acry_extract_be(src_buf, ASPEED_ACRY_EXP_OFFSET, + ASPEED_ACRY_MAX_BYTES, e); + data_len = aspeed_acry_extract_be(src_buf, ASPEED_ACRY_DATA_OFFSET, + ASPEED_ACRY_DATA_MAX_LEN, data); + + if (trace_event_get_state_backends(TRACE_ASPEED_ACRY_HEXDUMP)) { + aspeed_acry_hexdump("buf", src_buf, len); + aspeed_acry_hexdump("n", n, n_len); + aspeed_acry_hexdump("e", e, e_len); + aspeed_acry_hexdump("data", data, data_len); + } + + der_key = aspeed_acry_der_encode_pubkey(n, n_len, e, e_len, &der_len); + cipher = qcrypto_akcipher_new(&opts, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC, + der_key, der_len, errp); + if (!cipher) { + error_prepend(errp, "failed to create RSA cipher: "); + return false; + } + + result_len = qcrypto_akcipher_encrypt(cipher, data, data_len, + result, sizeof(result), errp); + qcrypto_akcipher_free(cipher); + if (result_len < 0) { + error_prepend(errp, "RSA modexp failed: "); + return false; + } + + if (!aspeed_acry_store_result(s, result, result_len, n_len, errp)) { + return false; + } + + if (trace_event_get_state_backends(TRACE_ASPEED_ACRY_HEXDUMP)) { + aspeed_acry_hexdump("result", result, result_len); + } + + return true; +} + +static uint64_t aspeed_acry_read(void *opaque, hwaddr offset, unsigned int size) +{ + AspeedACRYState *s = ASPEED_ACRY(opaque); + uint32_t reg = offset >> 2; + + trace_aspeed_acry_read(offset, s->regs[reg]); + + return s->regs[reg]; +} + +static void aspeed_acry_write(void *opaque, hwaddr offset, uint64_t data, + unsigned int size) +{ + AspeedACRYState *s = ASPEED_ACRY(opaque); + uint32_t reg = offset >> 2; + Error *local_err = NULL; + + trace_aspeed_acry_write(offset, data); + + switch (reg) { + case R_ACRY_DMA_SRC: + /* + * The DMA source register holds a CPU-visible DRAM address (e.g. + * 0x8xxxxxxx on AST2600); the engine addresses DRAM from offset 0, + * so mask off the top bit to get the DRAM-relative offset. + */ + data &= 0x7FFFFFFF; + break; + case R_ACRY_STATUS: + data = s->regs[R_ACRY_STATUS] & ~data; + if (!(data & (R_ACRY_STATUS_RSA_ENG_DONE_MASK | + R_ACRY_STATUS_RSA_DMA_DONE_MASK))) { + qemu_irq_lower(s->irq); + } + break; + case R_ACRY_TRIGGER: + if (FIELD_EX32(data, ACRY_TRIGGER, RSA_START)) { + if (!aspeed_acry_do_rsa(s, &local_err)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s\n", __func__, + error_get_pretty(local_err)); + error_free(local_err); + aspeed_acry_clear_result(s); + } + + s->regs[R_ACRY_STATUS] |= R_ACRY_STATUS_RSA_ENG_DONE_MASK | + R_ACRY_STATUS_RSA_DMA_DONE_MASK; + if (s->regs[R_ACRY_INT_MASK] & + (R_ACRY_INT_MASK_RSA_ENG_MASK_MASK | + R_ACRY_INT_MASK_RSA_DMA_MASK_MASK)) { + qemu_irq_raise(s->irq); + } + } + break; + default: + break; + } + + s->regs[reg] = data; +} + +static const MemoryRegionOps aspeed_acry_ops = { + .read = aspeed_acry_read, + .write = aspeed_acry_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void aspeed_acry_reset_hold(Object *obj, ResetType type) +{ + AspeedACRYState *s = ASPEED_ACRY(obj); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static void aspeed_acry_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + AspeedACRYState *s = ASPEED_ACRY(dev); + + if (!s->dram_mr) { + error_setg(errp, TYPE_ASPEED_ACRY ": 'dram' link not set"); + return; + } + + if (!s->sram_mr) { + error_setg(errp, TYPE_ASPEED_ACRY ": 'sram' link not set"); + return; + } + + address_space_init(&s->dram_as, s->dram_mr, "dram"); + address_space_init(&s->sram_as, s->sram_mr, "sram"); + + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_acry_ops, s, + TYPE_ASPEED_ACRY, ASPEED_ACRY_NR_REGS << 2); + sysbus_init_mmio(sbd, &s->iomem); + + sysbus_init_irq(sbd, &s->irq); +} + +static const Property aspeed_acry_properties[] = { + DEFINE_PROP_LINK("dram", AspeedACRYState, dram_mr, + TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_PROP_LINK("sram", AspeedACRYState, sram_mr, + TYPE_MEMORY_REGION, MemoryRegion *), +}; + +static void aspeed_acry_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->desc = "ASPEED ACRY Engine"; + dc->realize = aspeed_acry_realize; + rc->phases.hold = aspeed_acry_reset_hold; + device_class_set_props(dc, aspeed_acry_properties); +} + +static const TypeInfo aspeed_acry_types[] = { + { + .name = TYPE_ASPEED_ACRY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedACRYState), + .class_init = aspeed_acry_class_init, + }, +}; + +DEFINE_TYPES(aspeed_acry_types) diff --git a/hw/misc/aspeed_sbc.c b/hw/misc/aspeed_sbc.c index 1dfcf14e5b..5c193d9086 100644 --- a/hw/misc/aspeed_sbc.c +++ b/hw/misc/aspeed_sbc.c @@ -10,11 +10,13 @@ #include "qemu/osdep.h" #include "qemu/log.h" +#include "qemu/cutils.h" #include "qemu/error-report.h" #include "hw/core/qdev-properties.h" #include "hw/misc/aspeed_sbc.h" #include "qapi/error.h" #include "migration/vmstate.h" +#include "crypto/akcipher.h" #include "trace.h" #define R_PROT (0x000 / 4) @@ -24,8 +26,22 @@ #define R_CAMP1 (0x020 / 4) #define R_CAMP2 (0x024 / 4) #define R_QSR (0x040 / 4) +#define R_SEC_TRIGGER (0x0bc / 4) + +/* + * SEC SRAM layout for a secp384r1 ECDSA verify operation. All operands are + * 48-byte big-endian values. + */ +#define ECDSA_SRAM_QX 0x2080 +#define ECDSA_SRAM_QY 0x20c0 +#define ECDSA_SRAM_R 0x21c0 +#define ECDSA_SRAM_S 0x2200 +#define ECDSA_SRAM_M 0x2240 +#define ECDSA_P384_COORD_LEN 48 /* R_STATUS */ +#define ECDSA_VERIFY_PASS BIT(21) +#define ECDSA_VERIFY_DONE BIT(20) #define ABR_EN BIT(14) /* Mirrors SCU510[11] */ #define ABR_IMAGE_SOURCE BIT(13) #define SPI_ABR_IMAGE_SOURCE BIT(12) @@ -42,6 +58,10 @@ #define OTP_MEM_IDLE BIT(1) #define OTP_COMPARE_STATUS BIT(0) +/* R_SEC_TRIGGER */ +#define ECDSA_CMD_TRIGGER BIT(1) +#define RSA_CMD_TRIGGER BIT(0) + /* QSR */ #define QSR_RSA_MASK (0x3 << 12) #define QSR_HASH_MASK (0x3 << 10) @@ -220,10 +240,111 @@ static void aspeed_sbc_handle_command(void *opaque, uint32_t cmd) s->regs[R_STATUS] |= (OTP_MEM_IDLE | OTP_IDLE); } +static void sbc_ecdsa_hexdump(const char *desc, const char *buf, size_t size) +{ + g_autoptr(GString) str = g_string_sized_new(64); + size_t len; + size_t i; + + for (i = 0; i < size; i += len) { + len = MIN(16, size - i); + g_string_truncate(str, 0); + qemu_hexdump_line(str, buf + i, len, 1, 4); + trace_aspeed_sbc_ecdsa_hexdump(desc, i, str->str); + } +} + +/* + * The hardware only supports ECDSA secp384r1 (NIST P-384). The firmware has + * already staged the public key, signature and digest in the SEC SRAM; read + * them out and defer the actual verification to the crypto backend. + */ +static bool aspeed_sbc_ecdsa_verify(AspeedSBCState *s) +{ + QCryptoAkCipherOptions opts = { + .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA, + .u.ecdsa.curve_id = QCRYPTO_CURVE_ID_SECP384R1, + }; + g_autoptr(QCryptoAkCipher) akcipher = NULL; + uint8_t pubkey[ECDSA_P384_COORD_LEN * 2]; + uint8_t sig[ECDSA_P384_COORD_LEN * 2]; + uint8_t dgst[ECDSA_P384_COORD_LEN]; + Error *err = NULL; + + if (!qcrypto_akcipher_supports(&opts)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: ECDSA secp384r1 is not supported by the crypto " + "backend\n", __func__); + return false; + } + + if (address_space_read(&s->sram_as, ECDSA_SRAM_QX, + MEMTXATTRS_UNSPECIFIED, pubkey, + ECDSA_P384_COORD_LEN) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to read ECDSA QX from SEC SRAM\n", __func__); + return false; + } + if (address_space_read(&s->sram_as, ECDSA_SRAM_QY, + MEMTXATTRS_UNSPECIFIED, + pubkey + ECDSA_P384_COORD_LEN, + ECDSA_P384_COORD_LEN) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to read ECDSA QY from SEC SRAM\n", __func__); + return false; + } + if (address_space_read(&s->sram_as, ECDSA_SRAM_R, + MEMTXATTRS_UNSPECIFIED, sig, + ECDSA_P384_COORD_LEN) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to read ECDSA R from SEC SRAM\n", __func__); + return false; + } + if (address_space_read(&s->sram_as, ECDSA_SRAM_S, + MEMTXATTRS_UNSPECIFIED, sig + ECDSA_P384_COORD_LEN, + ECDSA_P384_COORD_LEN) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to read ECDSA S from SEC SRAM\n", __func__); + return false; + } + if (address_space_read(&s->sram_as, ECDSA_SRAM_M, + MEMTXATTRS_UNSPECIFIED, dgst, + ECDSA_P384_COORD_LEN) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to read ECDSA M from SEC SRAM\n", + __func__); + return false; + } + + if (trace_event_get_state_backends(TRACE_ASPEED_SBC_ECDSA_HEXDUMP)) { + sbc_ecdsa_hexdump("pubkey", (char *)pubkey, sizeof(pubkey)); + sbc_ecdsa_hexdump("signature", (char *)sig, sizeof(sig)); + sbc_ecdsa_hexdump("digest", (char *)dgst, sizeof(dgst)); + } + + akcipher = qcrypto_akcipher_new(&opts, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC, + pubkey, sizeof(pubkey), &err); + if (!akcipher) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: %s\n", __func__, + error_get_pretty(err)); + error_free(err); + return false; + } + + if (qcrypto_akcipher_verify(akcipher, sig, sizeof(sig), + dgst, sizeof(dgst), &err) != 0) { + error_free(err); + return false; + } + + return true; +} + static void aspeed_sbc_write(void *opaque, hwaddr addr, uint64_t data, unsigned int size) { AspeedSBCState *s = ASPEED_SBC(opaque); + AspeedSBCClass *sc = ASPEED_SBC_GET_CLASS(s); addr >>= 2; @@ -244,6 +365,26 @@ static void aspeed_sbc_write(void *opaque, hwaddr addr, uint64_t data, case R_CMD: aspeed_sbc_handle_command(opaque, data); return; + case R_SEC_TRIGGER: + if (data & RSA_CMD_TRIGGER) { + qemu_log_mask(LOG_UNIMP, + "%s: RSA is not supported\n", __func__); + } + if (data & ECDSA_CMD_TRIGGER) { + if (!sc->has_ecdsa) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: ECDSA is not supported\n", __func__); + return; + } + s->regs[R_STATUS] &= ~(ECDSA_VERIFY_DONE | ECDSA_VERIFY_PASS); + if (aspeed_sbc_ecdsa_verify(s)) { + s->regs[R_STATUS] |= ECDSA_VERIFY_PASS; + } + s->regs[R_STATUS] |= ECDSA_VERIFY_DONE; + trace_aspeed_sbc_ecdsa_verify( + (s->regs[R_STATUS] & ECDSA_VERIFY_PASS) ? "pass" : "fail"); + } + return; default: break; } @@ -306,16 +447,24 @@ static void aspeed_sbc_realize(DeviceState *dev, Error **errp) } } + if (sc->has_ecdsa) { + if (!s->sram) { + error_setg(errp, TYPE_ASPEED_SBC ": 'sram' link not set"); + return; + } + address_space_init(&s->sram_as, s->sram, TYPE_ASPEED_SBC ".sram"); + } + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_sbc_ops, s, - TYPE_ASPEED_SBC, 0x1000); + TYPE_ASPEED_SBC, ASPEED_SBC_NR_REGS << 2); sysbus_init_mmio(sbd, &s->iomem); } static const VMStateDescription vmstate_aspeed_sbc = { .name = TYPE_ASPEED_SBC, - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (const VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, AspeedSBCState, ASPEED_SBC_NR_REGS), VMSTATE_END_OF_LIST(), @@ -325,6 +474,8 @@ static const VMStateDescription vmstate_aspeed_sbc = { static const Property aspeed_sbc_properties[] = { DEFINE_PROP_BOOL("emmc-abr", AspeedSBCState, emmc_abr, 0), DEFINE_PROP_UINT32("signing-settings", AspeedSBCState, signing_settings, 0), + DEFINE_PROP_LINK("sram", AspeedSBCState, sram, + TYPE_MEMORY_REGION, MemoryRegion *), }; static void aspeed_sbc_class_init(ObjectClass *klass, const void *data) @@ -355,6 +506,7 @@ static void aspeed_ast10x0_sbc_class_init(ObjectClass *klass, const void *data) dc->desc = "AST10X0 Secure Boot Controller"; sc->has_otp = true; + sc->has_ecdsa = true; } static const TypeInfo aspeed_sbc_types[] = { diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index ca93c3699d..77f9af2ae5 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -130,6 +130,10 @@ #define AST2600_HW_STRAP2_PROT TO_REG(0x518) #define AST2600_RNG_CTRL TO_REG(0x520) #define AST2600_RNG_DATA TO_REG(0x524) +#define AST2600_RNG2_CTRL TO_REG(0x530) +#define AST2600_RNG2_CTRL_MASK 0x3F +#define AST2600_RNG2_CTRL_VLD BIT(31) +#define AST2600_RNG2_DATA TO_REG(0x534) #define AST2600_CHIP_ID0 TO_REG(0x5B0) #define AST2600_CHIP_ID1 TO_REG(0x5B4) @@ -679,13 +683,14 @@ static uint64_t aspeed_ast2600_scu_read(void *opaque, hwaddr offset, /* PLLs are always "locked" */ return s->regs[reg] | BIT(31); case AST2600_RNG_DATA: + case AST2600_RNG2_DATA: /* * On hardware, RNG_DATA works regardless of the state of the * enable bit in RNG_CTRL * * TODO: Check this is true for ast2600 */ - s->regs[AST2600_RNG_DATA] = aspeed_scu_get_random(); + s->regs[reg] = aspeed_scu_get_random(); break; } @@ -756,6 +761,7 @@ static void aspeed_ast2600_scu_write(void *opaque, hwaddr offset, return; case AST2600_RNG_DATA: + case AST2600_RNG2_DATA: case AST2600_SILICON_REV: case AST2600_SILICON_REV2: case AST2600_CHIP_ID0: @@ -765,6 +771,14 @@ static void aspeed_ast2600_scu_write(void *opaque, hwaddr offset, "%s: Write to read-only offset 0x%" HWADDR_PRIx "\n", __func__, offset); return; + case AST2600_RNG2_CTRL: + data &= AST2600_RNG2_CTRL_MASK; + if (data & BIT(4)) { + data |= AST2600_RNG2_CTRL_VLD; + } else { + data &= ~AST2600_RNG2_CTRL_VLD; + } + break; } s->regs[reg] = data; @@ -803,6 +817,7 @@ static const uint32_t ast2600_a3_resets[ASPEED_AST2600_SCU_NR_REGS] = { [AST2600_HUARTCLK] = 0x000145C0, [AST2600_CHIP_ID0] = 0x1234ABCD, [AST2600_CHIP_ID1] = 0x88884444, + [AST2600_RNG2_CTRL] = 0x8000000E, }; static void aspeed_ast2600_scu_reset_hold(Object *obj, ResetType type) @@ -821,7 +836,8 @@ static void aspeed_ast2600_scu_reset_hold(Object *obj, ResetType type) s->regs[AST2600_SILICON_REV2] = s->silicon_rev; s->regs[AST2600_HW_STRAP1] = s->hw_strap1; s->regs[AST2600_HW_STRAP2] = s->hw_strap2; - s->regs[PROT_KEY] = s->hw_prot_key; + s->regs[AST2600_PROT_KEY] = s->hw_prot_key == ASPEED_SCU_PROT_KEY; + s->regs[AST2600_PROT_KEY2] = s->hw_prot_key == ASPEED_SCU_PROT_KEY; } static void aspeed_2600_scu_class_init(ObjectClass *klass, const void *data) @@ -1122,6 +1138,7 @@ static void aspeed_ast1030_scu_reset_hold(Object *obj, ResetType type) s->regs[AST2600_HW_STRAP1] = s->hw_strap1; s->regs[AST2600_HW_STRAP2] = s->hw_strap2; s->regs[PROT_KEY] = s->hw_prot_key; + s->regs[AST2600_RNG2_CTRL] = 0x8000000E; } static void aspeed_1030_scu_class_init(ObjectClass *klass, const void *data) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 54e07aacda..9790afa104 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -139,6 +139,7 @@ system_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c')) system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( + 'aspeed_acry.c', 'aspeed_hace.c', 'aspeed_lpc.c', 'aspeed_ltpi.c', diff --git a/hw/misc/trace-events b/hw/misc/trace-events index 0b8be3d0f2..6db4934dc5 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -95,6 +95,8 @@ aspeed_sbc_ignore_cmd(uint32_t cmd) "Ignoring command 0x%" PRIx32 aspeed_sbc_handle_cmd(uint32_t cmd, uint32_t addr, bool ret) "Handling command 0x%" PRIx32 " for OTP addr 0x%" PRIx32 " Result: %d" aspeed_sbc_otp_read(uint32_t addr, uint32_t value) "OTP Memory read: addr 0x%" PRIx32 " value 0x%" PRIx32 aspeed_sbc_otp_prog(uint32_t addr, uint32_t value) "OTP Memory write: addr 0x%" PRIx32 " value 0x%" PRIx32 +aspeed_sbc_ecdsa_verify(const char *result) "ECDSA verify done: %s" +aspeed_sbc_ecdsa_hexdump(const char *desc, uint32_t offset, const char *s) "%s: 0x%08x: %s" # aspeed_scu.c aspeed_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 @@ -331,6 +333,12 @@ aspeed_peci_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" aspeed_peci_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 aspeed_peci_raise_interrupt(uint32_t ctrl, uint32_t status) "ctrl 0x%" PRIx32 " status 0x%" PRIx32 +# aspeed_acry.c +aspeed_acry_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 +aspeed_acry_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 +aspeed_acry_rsa_trigger(uint64_t src_addr, uint32_t len) "src_addr 0x%" PRIx64 " len 0x%" PRIx32 +aspeed_acry_hexdump(const char *desc, uint32_t offset, const char *s) "%s: 0x%08x: %s" + # aspeed_hace.c aspeed_hace_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 aspeed_hace_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c index c5089d74f4..ece0650d70 100644 --- a/hw/sensor/tmp105.c +++ b/hw/sensor/tmp105.c @@ -1,5 +1,5 @@ /* - * Texas Instruments TMP105 temperature sensor. + * Texas Instruments TMP105/TMP75/TMP175/LM75B temperature sensor. * * Copyright (C) 2008 Nokia Corporation * Written by Andrzej Zaborowski <andrew@openedhand.com> @@ -16,19 +16,76 @@ * * You should have received a copy of the GNU General Public License along * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Limitations: + * - The SMBus Alert Response Address protocol and the general-call commands + * are not implemented. + * - The over-limit comparison uses the full 8.8 fixed-point temperature and a + * ">=" boundary for every variant. The LM75B's 9-bit comparison quantisation + * and strict-exceed boundary are therefore only approximated. */ #include "qemu/osdep.h" -#include "hw/i2c/i2c.h" -#include "hw/core/irq.h" -#include "migration/vmstate.h" -#include "hw/sensor/tmp105.h" +#include "qemu/module.h" #include "qapi/error.h" #include "qapi/visitor.h" -#include "qemu/module.h" +#include "qom/object.h" +#include "hw/sensor/tmp105.h" +#include "hw/sensor/tmp105_regs.h" +#include "hw/core/irq.h" #include "hw/core/registerfields.h" +#include "hw/i2c/i2c.h" +#include "migration/vmstate.h" #include "trace.h" +OBJECT_DECLARE_TYPE(TMP105State, TMP105Class, TMP105) + +struct TMP105State { + /*< private >*/ + I2CSlave parent_obj; + /*< public >*/ + + uint8_t len; + uint8_t buf[2]; + qemu_irq pin; + + uint8_t pointer; + uint8_t config; + int16_t temperature; + int16_t limit[2]; + uint8_t faults; + uint8_t fault_count; + uint8_t alarm; + /* + * The TMP105 initially looks for a temperature rising above T_high; + * once this is detected, the condition it looks for next is the + * temperature falling below T_low. This flag is false when initially + * looking for T_high, true when looking for T_low. + */ + bool detect_falling; +}; + +/* + * Per-device-model parameters. The TMP105, TMP75, TMP175 and LM75B share the + * same register map and control semantics; they differ only in a handful of + * details captured here. + * + * @faultq: fault-queue length table selected by Config bits + * @config_wmask: writable Config bits. The LM75B has no resolution (R1:R0) or + * one-shot (OS) bits. + * @fixed_res: converter resolution field pinned by the device, or -1 when it is + * software-selectable. + * @limit_lsb_mask: low-byte mask applied to the T_LOW/T_HIGH limit registers. + */ +struct TMP105Class { + I2CSlaveClass parent_class; + const uint8_t *faultq; + uint8_t config_wmask; + int8_t fixed_res; + uint8_t limit_lsb_mask; + bool tm_change_clears_alert; +}; + FIELD(CONFIG, SHUTDOWN_MODE, 0, 1) FIELD(CONFIG, THERMOSTAT_MODE, 1, 1) FIELD(CONFIG, POLARITY, 2, 1) @@ -43,43 +100,40 @@ static void tmp105_interrupt_update(TMP105State *s) static void tmp105_alarm_update(TMP105State *s, bool one_shot) { + bool fault; + if (FIELD_EX8(s->config, CONFIG, SHUTDOWN_MODE) && !one_shot) { return; } - if (FIELD_EX8(s->config, CONFIG, THERMOSTAT_MODE)) { - /* - * TM == 1 : Interrupt mode. We signal Alert when the - * temperature rises above T_high, and expect the guest to clear - * it (eg by reading a device register). - */ - if (s->detect_falling) { - if (s->temperature < s->limit[0]) { - s->alarm = 1; - s->detect_falling = false; - } - } else { - if (s->temperature >= s->limit[1]) { - s->alarm = 1; - s->detect_falling = true; - } - } + /* + * A fault is a conversion that lies outside the limit currently being + * watched: above T_high while looking for the alarm to trip, below T_low + * afterwards. + */ + if (s->detect_falling) { + fault = s->temperature < s->limit[0]; } else { - /* - * TM == 0 : Comparator mode. We signal Alert when the temperature - * rises above T_high, and stop signalling it when the temperature - * falls below T_low. - */ + fault = s->temperature >= s->limit[1]; + } + + if (!fault) { + s->fault_count = 0; + } else if (++s->fault_count >= s->faults) { + s->fault_count = 0; if (s->detect_falling) { - if (s->temperature < s->limit[0]) { - s->alarm = 0; - s->detect_falling = false; - } + /* + * Temperature fell back below T_low. In comparator mode (TM == 0) + * the alarm is released; in interrupt mode (TM == 1) it is + * asserted again and the guest is expected to clear it by reading + * a register. + */ + s->alarm = FIELD_EX8(s->config, CONFIG, THERMOSTAT_MODE); + s->detect_falling = false; } else { - if (s->temperature >= s->limit[1]) { - s->alarm = 1; - s->detect_falling = true; - } + /* Temperature rose to or above T_high: assert the alarm. */ + s->alarm = 1; + s->detect_falling = true; } } @@ -119,10 +173,11 @@ static void tmp105_set_temperature(Object *obj, Visitor *v, const char *name, tmp105_alarm_update(s, false); } -static const int tmp105_faultq[4] = { 1, 2, 4, 6 }; - static void tmp105_read(TMP105State *s) { + const TMP105Class *tc = TMP105_GET_CLASS(s); + int res; + s->len = 0; if (FIELD_EX8(s->config, CONFIG, THERMOSTAT_MODE)) { @@ -132,9 +187,11 @@ static void tmp105_read(TMP105State *s) switch (s->pointer & 3) { case TMP105_REG_TEMPERATURE: + res = tc->fixed_res >= 0 ? tc->fixed_res : + FIELD_EX8(s->config, CONFIG, CONVERTER_RESOLUTION); s->buf[s->len++] = (((uint16_t) s->temperature) >> 8); s->buf[s->len++] = (((uint16_t) s->temperature) >> 0) & - (0xf0 << (FIELD_EX8(~s->config, CONFIG, CONVERTER_RESOLUTION))); + (0xf0 << (3 - res)); break; case TMP105_REG_CONFIG: @@ -152,33 +209,56 @@ static void tmp105_read(TMP105State *s) break; } - trace_tmp105_read(s->i2c.address, s->pointer); + trace_tmp105_read(s->parent_obj.address, s->pointer); } static void tmp105_write(TMP105State *s) { - trace_tmp105_write(s->i2c.address, s->pointer); + const TMP105Class *tc = TMP105_GET_CLASS(s); + uint8_t config, one_shot; + bool waking; + + trace_tmp105_write(s->parent_obj.address, s->pointer); switch (s->pointer & 3) { case TMP105_REG_TEMPERATURE: break; case TMP105_REG_CONFIG: - if (FIELD_EX8(s->buf[0] & ~s->config, CONFIG, SHUTDOWN_MODE)) { - trace_tmp105_write_shutdown(s->i2c.address); + config = s->buf[0] & tc->config_wmask; + if (FIELD_EX8(config & ~s->config, CONFIG, SHUTDOWN_MODE)) { + trace_tmp105_write_shutdown(s->parent_obj.address); + if (FIELD_EX8(config, CONFIG, THERMOSTAT_MODE)) { + s->alarm = 0; + } + } + if (tc->tm_change_clears_alert && + FIELD_EX8(config ^ s->config, CONFIG, THERMOSTAT_MODE)) { + s->alarm = 0; + s->fault_count = 0; + s->detect_falling = false; + } + waking = FIELD_EX8(s->config & ~config, CONFIG, SHUTDOWN_MODE); + one_shot = FIELD_EX8(config, CONFIG, ONE_SHOT); + s->config = FIELD_DP8(config, CONFIG, ONE_SHOT, 0); + s->faults = tc->faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)]; + if (one_shot && FIELD_EX8(s->config, CONFIG, SHUTDOWN_MODE)) { + tmp105_alarm_update(s, true); + } else if (waking) { + tmp105_alarm_update(s, false); + } else { + tmp105_interrupt_update(s); } - s->config = FIELD_DP8(s->buf[0], CONFIG, ONE_SHOT, 0); - s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)]; - tmp105_alarm_update(s, FIELD_EX8(s->buf[0], CONFIG, ONE_SHOT)); break; case TMP105_REG_T_LOW: case TMP105_REG_T_HIGH: if (s->len >= 3) { s->limit[s->pointer & 1] = (int16_t) - ((((uint16_t) s->buf[0]) << 8) | (s->buf[1] & 0xf0)); + ((((uint16_t) s->buf[0]) << 8) | + (s->buf[1] & tc->limit_lsb_mask)); } - tmp105_alarm_update(s, false); + tmp105_interrupt_update(s); break; } } @@ -227,8 +307,9 @@ static int tmp105_event(I2CSlave *i2c, enum i2c_event event) static int tmp105_post_load(void *opaque, int version_id) { TMP105State *s = opaque; + const TMP105Class *tc = TMP105_GET_CLASS(s); - s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)]; + s->faults = tc->faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)]; tmp105_interrupt_update(s); return 0; @@ -246,6 +327,13 @@ static bool detect_falling_needed(void *opaque) return s->detect_falling; } +static bool fault_count_needed(void *opaque) +{ + const TMP105State *s = opaque; + + return s->fault_count != 0; +} + static const VMStateDescription vmstate_tmp105_detect_falling = { .name = "TMP105/detect-falling", .version_id = 1, @@ -257,6 +345,17 @@ static const VMStateDescription vmstate_tmp105_detect_falling = { } }; +static const VMStateDescription vmstate_tmp105_fault_count = { + .name = "TMP105/fault-count", + .version_id = 1, + .minimum_version_id = 1, + .needed = fault_count_needed, + .fields = (const VMStateField[]) { + VMSTATE_UINT8(fault_count, TMP105State), + VMSTATE_END_OF_LIST() + } +}; + static const VMStateDescription vmstate_tmp105 = { .name = "TMP105", .version_id = 0, @@ -270,23 +369,26 @@ static const VMStateDescription vmstate_tmp105 = { VMSTATE_INT16(temperature, TMP105State), VMSTATE_INT16_ARRAY(limit, TMP105State, 2), VMSTATE_UINT8(alarm, TMP105State), - VMSTATE_I2C_SLAVE(i2c, TMP105State), + VMSTATE_I2C_SLAVE(parent_obj, TMP105State), VMSTATE_END_OF_LIST() }, .subsections = (const VMStateDescription * const []) { &vmstate_tmp105_detect_falling, + &vmstate_tmp105_fault_count, NULL } }; -static void tmp105_reset(I2CSlave *i2c) +static void tmp105_reset_hold(Object *obj, ResetType type) { - TMP105State *s = TMP105(i2c); + TMP105State *s = TMP105(obj); + const TMP105Class *tc = TMP105_GET_CLASS(s); s->temperature = 0; s->pointer = 0; s->config = 0; - s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)]; + s->faults = tc->faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)]; + s->fault_count = 0; s->alarm = 0; s->detect_falling = false; @@ -302,8 +404,6 @@ static void tmp105_realize(DeviceState *dev, Error **errp) TMP105State *s = TMP105(i2c); qdev_init_gpio_out(&i2c->qdev, &s->pin, 1); - - tmp105_reset(&s->i2c); } static void tmp105_initfn(Object *obj) @@ -311,31 +411,101 @@ static void tmp105_initfn(Object *obj) object_property_add(obj, "temperature", "int", tmp105_get_temperature, tmp105_set_temperature, NULL, NULL); + object_property_set_description(obj, "temperature", + "Temperature, in millidegrees Celsius"); } +/* + * Fault-queue length selected by Config F1:F0. Row 0 (1/2/4/6) is used by the + * TMP105, TMP175 and LM75B; row 1 (1/2/3/4) by the TMP75. + */ +static const uint8_t tmp105_faultq[][4] = { + { 1, 2, 4, 6 }, + { 1, 2, 3, 4 }, +}; + +/* The F1:F0 field must never index past a fault-queue table row. */ +QEMU_BUILD_BUG_ON((1 << R_CONFIG_FAULT_QUEUE_LENGTH) - 1 >= + ARRAY_SIZE(tmp105_faultq[0])); + static void tmp105_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + TMP105Class *tc = TMP105_CLASS(klass); dc->realize = tmp105_realize; k->event = tmp105_event; k->recv = tmp105_rx; k->send = tmp105_tx; + rc->phases.hold = tmp105_reset_hold; dc->vmsd = &vmstate_tmp105; + + tc->faultq = tmp105_faultq[0]; + tc->config_wmask = 0xff; + tc->fixed_res = -1; + tc->limit_lsb_mask = 0xf0; + tc->tm_change_clears_alert = false; } -static const TypeInfo tmp105_info = { - .name = TYPE_TMP105, - .parent = TYPE_I2C_SLAVE, - .instance_size = sizeof(TMP105State), - .instance_init = tmp105_initfn, - .class_init = tmp105_class_init, -}; +static void tmp175_class_init(ObjectClass *klass, const void *data) +{ + TMP105Class *tc = TMP105_CLASS(klass); + + tc->faultq = tmp105_faultq[0]; + tc->config_wmask = 0xff; + tc->fixed_res = -1; + tc->limit_lsb_mask = 0xf0; + tc->tm_change_clears_alert = false; +} + +static void tmp75_class_init(ObjectClass *klass, const void *data) +{ + TMP105Class *tc = TMP105_CLASS(klass); + + tc->faultq = tmp105_faultq[1]; + tc->config_wmask = 0xff; + tc->fixed_res = -1; + tc->limit_lsb_mask = 0xf0; + tc->tm_change_clears_alert = true; +} -static void tmp105_register_types(void) +static void lm75b_class_init(ObjectClass *klass, const void *data) { - type_register_static(&tmp105_info); + TMP105Class *tc = TMP105_CLASS(klass); + + tc->faultq = tmp105_faultq[0]; + tc->config_wmask = 0x1f; + tc->fixed_res = 2; + tc->limit_lsb_mask = 0x80; + tc->tm_change_clears_alert = false; } -type_init(tmp105_register_types) +static const TypeInfo tmp105_types[] = { + { + .name = TYPE_TMP105, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(TMP105State), + .class_size = sizeof(TMP105Class), + .instance_init = tmp105_initfn, + .class_init = tmp105_class_init, + }, + { + .name = TYPE_TMP175, + .parent = TYPE_TMP105, + .class_init = tmp175_class_init, + }, + { + .name = TYPE_TMP75, + .parent = TYPE_TMP105, + .class_init = tmp75_class_init, + }, + { + .name = TYPE_LM75B, + .parent = TYPE_TMP105, + .class_init = lm75b_class_init, + }, +}; + +DEFINE_TYPES(tmp105_types) diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index de95686720..e8c00f813a 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -146,3 +146,7 @@ config XLNX_USB_SUBSYS config USB_CHIPIDEA bool select USB_EHCI_SYSBUS + +config ASPEED_UDC + bool + select USB diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c new file mode 100644 index 0000000000..4c90b4f4c2 --- /dev/null +++ b/hw/usb/aspeed-udc.c @@ -0,0 +1,1058 @@ +/* + * ASPEED USB Device Controller (UDC) + * + * Copyright (c) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Models the ASPEED USB Device Controller (UDC). It implements one control + * endpoint (EP0) and 4 programmable endpoints. + * + * The model has two faces: + * - a SysBus device exposing the MMIO register interface, the interrupt and + * the integrated DMA engine to the guest gadget driver; + * - a USBDevice presented on a host controller's bus, which forwards host + * transactions to the guest gadget driver by raising the matching + * controller interrupts and completes them once the guest gadget driver + * responds via MMIO. + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "hw/usb/aspeed-udc.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "system/dma.h" +#include "system/address-spaces.h" +#include "trace.h" + +/* Root / Global registers (offset from the controller base) */ +REG32(UDC_FUNC_CTRL, 0x00) + FIELD(UDC_FUNC_CTRL, UPSTREAM_EN, 0, 1) +REG32(UDC_IER, 0x08) +REG32(UDC_ISR, 0x0C) + FIELD(UDC_ISR, EP_POOL_ACK, 16, 1) + FIELD(UDC_ISR, BUS_RESET, 6, 1) + FIELD(UDC_ISR, EP0_IN_ACK, 3, 1) + FIELD(UDC_ISR, EP0_OUT_ACK, 1, 1) + FIELD(UDC_ISR, EP0_SETUP, 0, 1) +REG32(UDC_EP_ACK_IER, 0x10) +REG32(UDC_EP_NAK_IER, 0x14) +REG32(UDC_EP_ACK_ISR, 0x18) +REG32(UDC_EP_NAK_ISR, 0x1C) +REG32(UDC_DEV_RESET, 0x20) + FIELD(UDC_DEV_RESET, EP_POOL, 9, 1) + FIELD(UDC_DEV_RESET, DMA, 8, 1) + FIELD(UDC_DEV_RESET, ROOT, 0, 1) +REG32(UDC_STS, 0x24) + FIELD(UDC_STS, HIGHSPEED, 27, 1) +REG32(UDC_EP0_CTRL, 0x30) + FIELD(UDC_EP0_CTRL, RX_LEN, 16, 7) + FIELD(UDC_EP0_CTRL, TX_LEN, 8, 7) + FIELD(UDC_EP0_CTRL, RX_RDY, 2, 1) + FIELD(UDC_EP0_CTRL, TX_RDY, 1, 1) + FIELD(UDC_EP0_CTRL, STALL, 0, 1) +REG32(UDC_EP0_DATA_BUFF, 0x34) + FIELD(UDC_EP0_DATA_BUFF, BASE_ADDR, 0, 31) +/* EP0 SETUP packet buffer: SETUP0 = bytes 0...3, SETUP1 = bytes 4...7 */ +REG32(UDC_SETUP0, 0x80) +REG32(UDC_SETUP1, 0x84) + +/* Per programmable-endpoint registers (offset from the EP register base) */ +REG32(EP_CONFIG, 0x00) + FIELD(EP_CONFIG, MAX_PKT, 16, 10) + FIELD(EP_CONFIG, EP_NUM, 8, 4) + FIELD(EP_CONFIG, DIR_OUT, 4, 1) + FIELD(EP_CONFIG, ENABLE, 0, 1) +REG32(EP_DMA_CTRL, 0x04) + FIELD(EP_DMA_CTRL, PROC_STS, 4, 4) + FIELD(EP_DMA_CTRL, DESC_OP_EN, 0, 1) +REG32(EP_DMA_BUFF, 0x08) + FIELD(EP_DMA_BUFF, BASE_ADDR, 0, 31) +REG32(EP_DMA_STS, 0x0C) + FIELD(EP_DMA_STS, PKT_SIZE, 16, 11) + FIELD(EP_DMA_STS, RPTR, 8, 8) + FIELD(EP_DMA_STS, WPTR, 0, 8) + +#define ASPEED_UDC_EP0_MAXPKT 64 +#define ASPEED_UDC_EP_MAXPKT 1024 + +/* DMA descriptor ring (256-stage mode) and descriptor data limits */ +#define ASPEED_UDC_DESCS_COUNT 256 +#define ASPEED_UDC_DESC_MAX_LEN 4096 + +/* DMA processing-status idle codes */ +#define EP_DMA_CTRL_STS_RX_IDLE 0x0 +#define EP_DMA_CTRL_STS_TX_IDLE 0x8 + +/* DMA descriptor (DES1) fields, in guest memory */ +#define ASPEED_EP_DESC1_IN_LEN(ctrl) ((ctrl) & 0x1fff) +/* interrupt-on-completion */ +#define ASPEED_EP_DESC1_INTR BIT(31) + +/* Result of moving a host data packet through an endpoint's DMA */ +typedef enum { + /* whole packet transferred */ + ASPEED_UDC_XFER_DONE, + /* not finished, keep parked */ + ASPEED_UDC_XFER_MORE, + /* DMA failed */ + ASPEED_UDC_XFER_ERROR, +} AspeedUDCXferResult; + +static void aspeed_udc_update_irq(AspeedUDCState *s) +{ + bool level; + + level = (s->regs[R_UDC_ISR] & s->regs[R_UDC_IER]) || + (s->regs[R_UDC_EP_ACK_ISR] & s->regs[R_UDC_EP_ACK_IER]) || + (s->regs[R_UDC_EP_NAK_ISR] & s->regs[R_UDC_EP_NAK_IER]); + + trace_aspeed_udc_irq(s->regs[R_UDC_ISR], s->regs[R_UDC_IER], level); + qemu_set_irq(s->irq, level); +} + +static void aspeed_udc_raise_isr(AspeedUDCState *s, uint32_t mask) +{ + s->regs[R_UDC_ISR] |= mask; + aspeed_udc_update_irq(s); +} + +static void aspeed_udc_raise_ep_ack(AspeedUDCState *s, int ep) +{ + trace_aspeed_udc_ep_ack(ep); + s->regs[R_UDC_EP_ACK_ISR] |= BIT(ep); + s->regs[R_UDC_ISR] |= R_UDC_ISR_EP_POOL_ACK_MASK; + aspeed_udc_update_irq(s); +} + +/* + * System bus device: MMIO register interface (guest gadget-driver facing) + */ + +/* Connect/disconnect the gadget device from the host bus */ +static void aspeed_udc_set_pullup(AspeedUDCState *s, bool on) +{ + USBDevice *udev; + Error *err = NULL; + + if (!s->usbgadget) { + /* no gadget device bound to this controller */ + return; + } + + udev = USB_DEVICE(s->usbgadget); + if (!udev->port) { + /* not attached to a host controller bus */ + return; + } + + trace_aspeed_udc_pullup(on, udev->attached); + if (on && !udev->attached) { + usb_device_attach(udev, &err); + if (err) { + warn_report_err(err); + } + } else if (!on && udev->attached) { + usb_device_detach(udev); + } +} + +/* Complete the in-flight EP0 control transfer back to the host */ +static void aspeed_udc_ep0_complete(AspeedUDCState *s, uint32_t len) +{ + USBPacket *p = s->ep0_packet; + + if (!p) { + return; + } + + s->ep0_packet = NULL; + p->actual_length = s->ep0_dir_in ? MIN(len, s->ep0_setup_len) + : s->ep0_setup_len; + p->status = USB_RET_SUCCESS; + trace_aspeed_udc_ep0_complete(s->ep0_dir_in, p->actual_length); + usb_generic_async_ctrl_complete(USB_DEVICE(s->usbgadget), p); +} + +static void aspeed_udc_ep0_tx_ready(AspeedUDCState *s, uint32_t val) +{ + uint32_t txlen = FIELD_EX32(val, UDC_EP0_CTRL, TX_LEN); + uint32_t data_buf_addr = s->regs[R_UDC_EP0_DATA_BUFF]; + USBPacket *p; + uint32_t n; + + if (!s->ep0_dir_in) { + /* Status stage IN (zero length) for an OUT / no-data transfer */ + aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_IN_ACK_MASK); + aspeed_udc_ep0_complete(s, s->ep0_offset); + return; + } + /* no control transfer is waiting: nothing to send */ + if (!s->ep0_packet) { + return; + } + + /* IN data stage: copy from the guest gadget driver's DMA buffer */ + n = MIN(txlen, s->ep0_setup_len - s->ep0_offset); + if (n && address_space_read(&s->dram_as, data_buf_addr, + MEMTXATTRS_UNSPECIFIED, + s->ep0_data + s->ep0_offset, + n) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: EP0 IN DMA read failed\n", __func__); + p = s->ep0_packet; + s->ep0_packet = NULL; + p->status = USB_RET_IOERROR; + usb_generic_async_ctrl_complete(USB_DEVICE(s->usbgadget), p); + return; + } + s->ep0_offset += n; + aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_IN_ACK_MASK); + if (txlen < ASPEED_UDC_EP0_MAXPKT || s->ep0_offset >= s->ep0_setup_len) { + aspeed_udc_ep0_complete(s, s->ep0_offset); + } +} + +static void aspeed_udc_ep0_rx_ready(AspeedUDCState *s) +{ + uint32_t data_buf_addr = s->regs[R_UDC_EP0_DATA_BUFF]; + USBPacket *p; + uint32_t n; + + if (s->ep0_dir_in) { + /* Status stage OUT (zero length) for an IN transfer */ + aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_OUT_ACK_MASK); + return; + } + /* no control transfer is waiting: nothing to receive */ + if (!s->ep0_packet) { + return; + } + + /* OUT data stage: hand host data to the guest gadget driver */ + n = MIN(s->ep0_setup_len - s->ep0_offset, ASPEED_UDC_EP0_MAXPKT); + if (n && address_space_write(&s->dram_as, data_buf_addr, + MEMTXATTRS_UNSPECIFIED, + s->ep0_data + s->ep0_offset, + n) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: EP0 OUT DMA write failed\n", __func__); + p = s->ep0_packet; + s->ep0_packet = NULL; + p->status = USB_RET_IOERROR; + usb_generic_async_ctrl_complete(USB_DEVICE(s->usbgadget), p); + return; + } + s->ep0_offset += n; + s->regs[R_UDC_EP0_CTRL] = FIELD_DP32(s->regs[R_UDC_EP0_CTRL], + UDC_EP0_CTRL, RX_LEN, n); + aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_OUT_ACK_MASK); +} + +/* + * The guest gadget driver drives EP0 by writing UDC_EP0_CTRL. Translate + * those writes into data movement to/from the deferred host control packet + * plus the matching ACK interrupts the guest gadget driver expects. + */ +static void aspeed_udc_ep0_ctrl_write(AspeedUDCState *s, uint32_t val) +{ + USBPacket *p; + + trace_aspeed_udc_ep0_ctrl_write(val, s->ep0_dir_in, s->ep0_offset); + + if (val & R_UDC_EP0_CTRL_STALL_MASK) { + /* Gadget stalled EP0: fail the pending control transfer */ + if (s->ep0_packet) { + p = s->ep0_packet; + s->ep0_packet = NULL; + p->status = USB_RET_STALL; + usb_generic_async_ctrl_complete(USB_DEVICE(s->usbgadget), p); + } + } else if (val & R_UDC_EP0_CTRL_TX_RDY_MASK) { + s->regs[R_UDC_EP0_CTRL] &= ~R_UDC_EP0_CTRL_TX_RDY_MASK; + aspeed_udc_ep0_tx_ready(s, val); + } else if (val & R_UDC_EP0_CTRL_RX_RDY_MASK) { + s->regs[R_UDC_EP0_CTRL] &= ~R_UDC_EP0_CTRL_RX_RDY_MASK; + aspeed_udc_ep0_rx_ready(s); + } +} + +/* The upstream-enable bit connects/disconnects the gadget device */ +static void aspeed_udc_func_ctrl_write(AspeedUDCState *s, uint32_t val) +{ + bool was_on = FIELD_EX32(s->regs[R_UDC_FUNC_CTRL], + UDC_FUNC_CTRL, UPSTREAM_EN); + bool now_on = FIELD_EX32(val, UDC_FUNC_CTRL, UPSTREAM_EN); + + if (now_on != was_on) { + aspeed_udc_set_pullup(s, now_on); + } +} + +static uint64_t aspeed_udc_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedUDCState *s = ASPEED_UDC(opaque); + uint32_t reg = offset >> 2; + uint32_t val; + + val = s->regs[reg]; + trace_aspeed_udc_read(offset, val); + + return val; +} + +static void aspeed_udc_write(void *opaque, hwaddr offset, uint64_t data, + unsigned size) +{ + AspeedUDCState *s = ASPEED_UDC(opaque); + uint32_t reg = offset >> 2; + uint32_t val = data; + + trace_aspeed_udc_write(offset, val); + + switch (reg) { + case R_UDC_FUNC_CTRL: + val &= 0x000e1fff; + aspeed_udc_func_ctrl_write(s, val); + s->regs[R_UDC_FUNC_CTRL] = val; + break; + case R_UDC_IER: + case R_UDC_EP_ACK_IER: + case R_UDC_EP_NAK_IER: + s->regs[reg] = val; + aspeed_udc_update_irq(s); + break; + case R_UDC_ISR: + case R_UDC_EP_ACK_ISR: + case R_UDC_EP_NAK_ISR: + s->regs[reg] &= ~val; + aspeed_udc_update_irq(s); + break; + case R_UDC_EP0_CTRL: + s->regs[reg] = val & (R_UDC_EP0_CTRL_STALL_MASK | + R_UDC_EP0_CTRL_TX_RDY_MASK | + R_UDC_EP0_CTRL_RX_RDY_MASK | + R_UDC_EP0_CTRL_TX_LEN_MASK); + aspeed_udc_ep0_ctrl_write(s, val); + break; + case R_UDC_EP0_DATA_BUFF: + s->regs[reg] = val & R_UDC_EP0_DATA_BUFF_BASE_ADDR_MASK; + break; + default: + s->regs[reg] = val; + break; + } +} + +static const MemoryRegionOps aspeed_udc_ops = { + .read = aspeed_udc_read, + .write = aspeed_udc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +/* + * Copy len bytes from guest memory at addr into the IN packet, going through + * a bounce buffer one buf-full at a time. Returns false on DMA failure. + */ +static bool aspeed_udc_ep_copy_to_pkt(AspeedUDCState *s, int ep, uint32_t addr, + uint32_t len, USBPacket *p) +{ + uint8_t buf[ASPEED_UDC_EP_MAXPKT]; + uint32_t copied = 0; + uint32_t seg; + + while (copied < len) { + seg = MIN(len - copied, sizeof(buf)); + if (address_space_read(&s->dram_as, addr + copied, + MEMTXATTRS_UNSPECIFIED, buf, seg) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: ep %d IN data DMA read failed\n", __func__, + ep); + return false; + } + usb_packet_copy(p, buf, seg); + copied += seg; + } + + return true; +} + +/* + * IN transfer: send data to the host by filling its IN packet from the + * buffers the guest gadget driver queued in the descriptor ring (from the + * read pointer to the write pointer). + * + * One host packet can be bigger than one descriptor's buffer, so we copy from + * several descriptors in a row until the packet is full or the ring is empty. + * If a descriptor is too big for the space left in the packet, we copy only + * part of it now and copy the rest on the next call; desc_off remembers how + * far we got. We move the read pointer to the next descriptor only after a + * descriptor is fully copied, so the guest gadget driver can read the pointer + * and see how much was sent. + * + * This function raises the endpoint ACK by itself when the ring becomes empty + * or when a descriptor asks for an interrupt. + */ +static AspeedUDCXferResult aspeed_udc_ep_xfer_in(AspeedUDCState *s, int ep, + USBPacket *p) +{ + QEMUIOVector *pktiov = p->combined ? &p->combined->iov : &p->iov; + AspeedUDCEP *e = &s->ep[ep]; + uint32_t mps = FIELD_EX32(e->regs[R_EP_CONFIG], EP_CONFIG, MAX_PKT); + uint32_t wptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR); + uint32_t rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR); + uint32_t desc_base = e->regs[R_EP_DMA_BUFF]; + uint32_t desc_addr; + uint32_t remaining; + uint32_t desc_ctrl; + uint32_t pkt_space; + /* des_0: data buffer base address, des_1: control/status */ + uint32_t desc[2]; + uint32_t offset; + uint32_t chunk; + uint32_t dlen; + bool done = false; + bool ack = false; + + if (mps == 0) { + /* a MAX_PKT field of 0 means the maximum packet size */ + mps = ASPEED_UDC_EP_MAXPKT; + } + + trace_aspeed_udc_ep_data_in(ep, rptr, wptr, pktiov->size); + + /* walk the queued descriptors, filling the packet */ + while (rptr != wptr) { + if (address_space_read(&s->dram_as, desc_base + rptr * sizeof(desc), + MEMTXATTRS_UNSPECIFIED, desc, + sizeof(desc)) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: ep %d descriptor DMA read failed\n", + __func__, ep); + return ASPEED_UDC_XFER_ERROR; + } + desc_addr = le32_to_cpu(desc[0]) & R_EP_DMA_BUFF_BASE_ADDR_MASK; + desc_ctrl = le32_to_cpu(desc[1]); + dlen = ASPEED_EP_DESC1_IN_LEN(desc_ctrl); + offset = e->desc_off; + /* how much to copy: min(descriptor bytes left, packet space left) */ + remaining = dlen > offset ? dlen - offset : 0; + pkt_space = pktiov->size > (uint32_t)p->actual_length ? + pktiov->size - (uint32_t)p->actual_length : 0; + chunk = MIN(remaining, pkt_space); + + if (!aspeed_udc_ep_copy_to_pkt(s, ep, desc_addr + offset, chunk, p)) { + return ASPEED_UDC_XFER_ERROR; + } + e->desc_off += chunk; + + if (e->desc_off < dlen) { + /* + * The packet ran out of space in the middle of this descriptor, + * so only part of it was copied. Stop here, and leave the read + * pointer on this descriptor: the next call resumes copying the + * rest (desc_off remembers how far we got). + */ + done = true; + break; + } + + /* + * This descriptor was copied in full. Advance the read pointer to the + * next descriptor and reset desc_off so it starts from the beginning. + */ + rptr = (rptr + 1) % ASPEED_UDC_DESCS_COUNT; + e->desc_off = 0; + if (desc_ctrl & ASPEED_EP_DESC1_INTR) { + ack = true; + } + /* + * This descriptor is shorter than the max packet size, i.e. a short + * (or zero-length) packet. In USB that marks the end of the transfer, + * so stop here. + */ + if (dlen < mps) { + done = true; + break; + } + /* + * The packet is now completely full, so the host has received all the + * data it asked for. Stop here. + */ + if ((uint32_t)p->actual_length >= pktiov->size) { + done = true; + break; + } + } + + e->regs[R_EP_DMA_STS] = FIELD_DP32(e->regs[R_EP_DMA_STS], EP_DMA_STS, + RPTR, rptr); + e->regs[R_EP_DMA_CTRL] = FIELD_DP32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, + PROC_STS, EP_DMA_CTRL_STS_TX_IDLE); + /* The guest gadget driver completes its request when the ring drains */ + if (rptr == wptr) { + ack = true; + } + if (ack) { + aspeed_udc_raise_ep_ack(s, ep); + } + + return done ? ASPEED_UDC_XFER_DONE : ASPEED_UDC_XFER_MORE; +} + +/* + * OUT transfer: receive data from the host by copying its OUT packet into the + * buffer the guest gadget driver set up (single-stage mode). + * + * A host packet can be bigger than one buffer, so we copy at most PKT_SIZE + * bytes per call, continuing from where the last call stopped + * (p->actual_length). The caller keeps the packet parked until it is fully + * copied. + */ +static AspeedUDCXferResult aspeed_udc_ep_xfer_out(AspeedUDCState *s, int ep, + USBPacket *p) +{ + AspeedUDCEP *e = &s->ep[ep]; + uint32_t chunk = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, PKT_SIZE); + uint32_t remaining = p->iov.size - (uint32_t)p->actual_length; + uint32_t data_buf_addr = e->regs[R_EP_DMA_BUFF]; + uint32_t len = MIN(remaining, chunk); + g_autofree uint8_t *buf = g_malloc(len); + + if (data_buf_addr && len) { + usb_packet_copy(p, buf, len); + if (address_space_write(&s->dram_as, data_buf_addr, + MEMTXATTRS_UNSPECIFIED, buf, + len) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: ep %d OUT data DMA write failed\n", + __func__, ep); + return ASPEED_UDC_XFER_ERROR; + } + } + + e->regs[R_EP_DMA_STS] = FIELD_DP32(e->regs[R_EP_DMA_STS], + EP_DMA_STS, PKT_SIZE, len); + e->regs[R_EP_DMA_STS] = FIELD_DP32(e->regs[R_EP_DMA_STS], + EP_DMA_STS, WPTR, 0); + e->regs[R_EP_DMA_CTRL] = FIELD_DP32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, + PROC_STS, EP_DMA_CTRL_STS_RX_IDLE); + aspeed_udc_raise_ep_ack(s, ep); + + if ((uint32_t)p->actual_length >= p->iov.size) { + return ASPEED_UDC_XFER_DONE; + } + + return ASPEED_UDC_XFER_MORE; +} + +/* + * IN kick: the guest gadget driver wrote EP_DMA_STS to tell us it queued more + * IN data to send to the host. If a host IN request is already waiting + * (parked because there was no data before), send the data now and finish it. + * If the request needs more data than was queued, keep it parked and wait for + * the next kick. + */ +static void aspeed_udc_ep_in_kick(AspeedUDCState *s, int ep, uint32_t val) +{ + AspeedUDCEP *e = &s->ep[ep]; + uint32_t cur_rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR); + uint32_t new_rptr = FIELD_EX32(val, EP_DMA_STS, RPTR); + uint32_t new_wptr = FIELD_EX32(val, EP_DMA_STS, WPTR); + USBPacket *p = e->pkt; + + /* + * A normal kick only sets the write pointer and leaves the read-pointer + * field 0 (the read pointer is ours to advance). The guest resets the ring + * by writing a read pointer that is non-zero and equal to the write + * pointer. + * + * We check non-zero as well as equal: on a normal kick whose write pointer + * just wrapped back to 0, both fields would be 0, so an "equal" test alone + * would look like a reset by mistake. + */ + if (new_rptr != 0 && new_rptr == new_wptr) { + cur_rptr = new_rptr; + e->desc_off = 0; + } + /* store the guest's write, but keep our own read pointer */ + e->regs[R_EP_DMA_STS] = FIELD_DP32(val, EP_DMA_STS, RPTR, cur_rptr); + + /* nothing to do unless an IN packet is waiting and the ring has data */ + if (!p || cur_rptr == new_wptr) { + return; + } + + switch (aspeed_udc_ep_xfer_in(s, ep, p)) { + case ASPEED_UDC_XFER_DONE: + e->pkt = NULL; + p->status = USB_RET_SUCCESS; + usb_packet_complete(USB_DEVICE(s->usbgadget), p); + break; + case ASPEED_UDC_XFER_ERROR: + e->pkt = NULL; + p->status = USB_RET_IOERROR; + usb_packet_complete(USB_DEVICE(s->usbgadget), p); + break; + case ASPEED_UDC_XFER_MORE: + break; + } +} + +/* + * OUT kick: the guest gadget driver wrote EP_DMA_STS to give us a buffer for + * OUT data. If an OUT packet is already waiting (parked because there was no + * buffer before), copy its data into the buffer now and finish it. If the + * packet has more data than fits, keep it parked and wait for the next buffer. + */ +static void aspeed_udc_ep_out_kick(AspeedUDCState *s, int ep) +{ + AspeedUDCEP *e = &s->ep[ep]; + USBPacket *p = e->pkt; + + /* nothing to do unless an OUT packet is waiting and a buffer is ready */ + if (!p || !FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR)) { + return; + } + + switch (aspeed_udc_ep_xfer_out(s, ep, p)) { + case ASPEED_UDC_XFER_DONE: + e->pkt = NULL; + p->status = USB_RET_SUCCESS; + usb_packet_complete(USB_DEVICE(s->usbgadget), p); + break; + case ASPEED_UDC_XFER_ERROR: + e->pkt = NULL; + p->status = USB_RET_IOERROR; + usb_packet_complete(USB_DEVICE(s->usbgadget), p); + break; + case ASPEED_UDC_XFER_MORE: + break; + } +} + +static uint64_t aspeed_udc_ep_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedUDCEP *e = opaque; + uint32_t reg = offset >> 2; + uint32_t val; + + val = e->regs[reg]; + trace_aspeed_udc_ep_read(e->index, offset, val); + + return val; +} + +static void aspeed_udc_ep_write(void *opaque, hwaddr offset, uint64_t data, + unsigned size) +{ + AspeedUDCEP *e = opaque; + AspeedUDCState *s = container_of(e - e->index, AspeedUDCState, ep[0]); + uint32_t reg = offset >> 2; + uint32_t val = data; + + trace_aspeed_udc_ep_write(e->index, offset, val); + + switch (reg) { + case R_EP_DMA_BUFF: + e->regs[reg] = val & R_EP_DMA_BUFF_BASE_ADDR_MASK; + break; + case R_EP_DMA_STS: + val &= 0x77ffffff; + if (FIELD_EX32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, DESC_OP_EN)) { + /* IN, descriptor-list mode */ + aspeed_udc_ep_in_kick(s, e->index, val); + } else { + /* OUT, single-stage mode */ + e->regs[reg] = val; + aspeed_udc_ep_out_kick(s, e->index); + } + break; + default: + e->regs[reg] = val; + break; + } +} + +static const MemoryRegionOps aspeed_udc_ep_ops = { + .read = aspeed_udc_ep_read, + .write = aspeed_udc_ep_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void aspeed_udc_reset_hold(Object *obj, ResetType type) +{ + AspeedUDCState *s = ASPEED_UDC(obj); + USBDevice *udev; + int i; + + memset(s->regs, 0, sizeof(s->regs)); + for (i = 0; i < ASPEED_UDC_NUM_EP; i++) { + memset(s->ep[i].regs, 0, sizeof(s->ep[i].regs)); + s->ep[i].pkt = NULL; + s->ep[i].desc_off = 0; + } + + /* Device-reset default: root, DMA and EP-pool soft-reset bits set */ + s->regs[R_UDC_DEV_RESET] = (R_UDC_DEV_RESET_ROOT_MASK | + R_UDC_DEV_RESET_DMA_MASK | + R_UDC_DEV_RESET_EP_POOL_MASK); + s->ep0_packet = NULL; + + /* + * A guest reboot resets the controller but leaves the USB device + * attached to the host bus with no guest gadget driver behind it. + * Detach it, otherwise the rebooted host fails to re-enumerate the + * driverless gadget device; it re-attaches when the new driver asserts + * pull-up. + */ + if (s->usbgadget) { + udev = USB_DEVICE(s->usbgadget); + if (udev->attached) { + usb_device_detach(udev); + } + } +} + +static void aspeed_udc_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + AspeedUDCState *s = ASPEED_UDC(dev); + int i; + + if (!s->dram_mr) { + error_setg(errp, TYPE_ASPEED_UDC ": 'dram' link not set"); + return; + } + address_space_init(&s->dram_as, s->dram_mr, "dram"); + + memory_region_init(&s->udc_container, OBJECT(s), TYPE_ASPEED_UDC, + ASPEED_UDC_MEM_SIZE); + memory_region_init_io(&s->root_mr, OBJECT(s), &aspeed_udc_ops, s, + TYPE_ASPEED_UDC ".root", + ASPEED_UDC_ROOT_NR_REGS << 2); + memory_region_add_subregion(&s->udc_container, 0, &s->root_mr); + + /* Each programmable endpoint has its own register bank */ + for (i = 0; i < ASPEED_UDC_NUM_EP; i++) { + g_autofree char *name = g_strdup_printf(TYPE_ASPEED_UDC ".ep%d", i); + + s->ep[i].index = i; + memory_region_init_io(&s->ep[i].mr, OBJECT(s), &aspeed_udc_ep_ops, + &s->ep[i], name, ASPEED_UDC_EP_NR_REGS << 2); + memory_region_add_subregion(&s->udc_container, + ASPEED_UDC_EP_REG_BASE + + i * (ASPEED_UDC_EP_NR_REGS << 2), + &s->ep[i].mr); + } + + sysbus_init_mmio(sbd, &s->udc_container); + sysbus_init_irq(sbd, &s->irq); +} + +static const Property aspeed_udc_properties[] = { + DEFINE_PROP_LINK("dram", AspeedUDCState, dram_mr, + TYPE_MEMORY_REGION, MemoryRegion *), +}; + +static void aspeed_udc_init(Object *obj) +{ + AspeedUDCState *s = ASPEED_UDC(obj); + + object_property_add_link(obj, "usbgadget", TYPE_ASPEED_UDC_GADGET, + (Object **)&s->usbgadget, + object_property_allow_set_link, 0); +} + +static void aspeed_udc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->desc = "ASPEED USB Device Controller"; + dc->realize = aspeed_udc_realize; + rc->phases.hold = aspeed_udc_reset_hold; + device_class_set_props(dc, aspeed_udc_properties); +} + +/* + * USB device: gadget device presented on a host controller's bus + * + * These callbacks run in the context of the host controller. They translate + * host transactions into the controller interrupts/state the guest gadget + * driver expects, then defer (USB_RET_ASYNC) until the driver responds + * through the MMIO register interface above. + */ + +static int aspeed_udc_find_ep(AspeedUDCState *s, int ep_nr, bool is_out) +{ + uint32_t cfg; + int i; + + for (i = 0; i < ASPEED_UDC_NUM_EP; i++) { + cfg = s->ep[i].regs[R_EP_CONFIG]; + + if (!FIELD_EX32(cfg, EP_CONFIG, ENABLE) || + FIELD_EX32(cfg, EP_CONFIG, EP_NUM) != ep_nr) { + continue; + } + if (FIELD_EX32(cfg, EP_CONFIG, DIR_OUT) == is_out) { + return i; + } + } + + return -1; +} + +static void aspeed_udc_ep_data_in(AspeedUDCState *s, int ep, USBPacket *p) +{ + AspeedUDCEP *e = &s->ep[ep]; + uint32_t rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR); + uint32_t wptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR); + + if (rptr == wptr) { + /* + * No IN data is queued yet. Save the packet and return ASYNC + * instead of NAK. A NAK would make the host retry slowly. + * aspeed_udc_ep_in_kick() serves and completes this packet later, + * once the guest gadget driver queues descriptors. + */ + e->pkt = p; + p->status = USB_RET_ASYNC; + return; + } + + switch (aspeed_udc_ep_xfer_in(s, ep, p)) { + case ASPEED_UDC_XFER_DONE: + p->status = USB_RET_SUCCESS; + break; + case ASPEED_UDC_XFER_MORE: + /* not fully sent yet: save the packet, wait for more descriptors */ + e->pkt = p; + p->status = USB_RET_ASYNC; + break; + case ASPEED_UDC_XFER_ERROR: + p->status = USB_RET_IOERROR; + break; + } +} + +static void aspeed_udc_ep_data_out(AspeedUDCState *s, int ep, USBPacket *p) +{ + AspeedUDCEP *e = &s->ep[ep]; + uint32_t sts = e->regs[R_EP_DMA_STS]; + + trace_aspeed_udc_ep_data_out(ep, FIELD_EX32(sts, EP_DMA_STS, WPTR), + FIELD_EX32(sts, EP_DMA_STS, PKT_SIZE), + p->iov.size); + if (!FIELD_EX32(sts, EP_DMA_STS, WPTR)) { + /* + * No OUT buffer is ready yet. Save the packet and return ASYNC + * instead of NAK. Writing now could use an old buffer address and + * lose the data (for example a mass-storage CBW). A NAK would make + * the host retry slowly. aspeed_udc_ep_out_kick() delivers this + * packet later, once the guest gadget driver sets up a buffer. + */ + e->pkt = p; + p->status = USB_RET_ASYNC; + return; + } + + switch (aspeed_udc_ep_xfer_out(s, ep, p)) { + case ASPEED_UDC_XFER_DONE: + p->status = USB_RET_SUCCESS; + break; + case ASPEED_UDC_XFER_MORE: + /* not fully received yet: save the packet, wait for the next buffer */ + e->pkt = p; + p->status = USB_RET_ASYNC; + break; + case ASPEED_UDC_XFER_ERROR: + p->status = USB_RET_IOERROR; + break; + } +} + +static void aspeed_udc_gadget_handle_reset(USBDevice *udev) +{ + AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc; + + s->ep0_packet = NULL; + s->ep0_offset = 0; + /* The EHCI host is High-Speed; advertise it to the guest gadget driver */ + s->regs[R_UDC_STS] = R_UDC_STS_HIGHSPEED_MASK; + trace_aspeed_udc_reset(s->regs[R_UDC_IER]); + aspeed_udc_raise_isr(s, R_UDC_ISR_BUS_RESET_MASK); +} + +static void aspeed_udc_gadget_handle_control(USBDevice *udev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc; + uint8_t req = request & 0xff; + uint8_t type = request >> 8; + + /* + * Reconstruct the 8-byte SETUP packet into the SETUP data buffer where + * the guest gadget driver reads it from. + */ + s->regs[R_UDC_SETUP0] = type | (req << 8) | ((value & 0xffff) << 16); + s->regs[R_UDC_SETUP1] = (index & 0xffff) | ((length & 0xffff) << 16); + + /* A new SETUP clears the EP0 STALL condition */ + s->regs[R_UDC_EP0_CTRL] &= ~R_UDC_EP0_CTRL_STALL_MASK; + + s->ep0_packet = p; + s->ep0_data = data; + s->ep0_setup_len = length; + s->ep0_offset = 0; + s->ep0_dir_in = (type & USB_DIR_IN); + + trace_aspeed_udc_ep0_setup(type, req, value, index, length, + s->ep0_dir_in, udev->addr); + + /* + * SET_ADDRESS is delivered while the device still answers at the default + * address 0 and carries the new address in wValue. The host controller + * keeps this transfer's queue bound to address 0 until it completes, so + * apply the new address synchronously as the transfer completes. + * Completing it asynchronously (USB_RET_ASYNC) would change udev->addr + * while the queue is still bound to 0; the host controller sees the + * mismatch, tears the queue down and enumeration breaks. The guest gadget + * driver is still notified so its state machine advances. + */ + if (type == 0 && req == USB_REQ_SET_ADDRESS) { + udev->addr = value; + s->ep0_packet = NULL; + aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_SETUP_MASK); + p->status = USB_RET_SUCCESS; + return; + } + + aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_SETUP_MASK); + p->status = USB_RET_ASYNC; +} + +static void aspeed_udc_gadget_handle_data(USBDevice *udev, USBPacket *p) +{ + AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc; + bool is_out = (p->pid == USB_TOKEN_OUT); + int ep = aspeed_udc_find_ep(s, p->ep->nr, is_out); + + trace_aspeed_udc_handle_data(p->ep->nr, is_out ? "OUT" : "IN", + p->iov.size, ep); + if (ep < 0) { + p->status = USB_RET_STALL; + return; + } + + if (is_out) { + aspeed_udc_ep_data_out(s, ep, p); + } else { + aspeed_udc_ep_data_in(s, ep, p); + } +} + +static void aspeed_udc_gadget_cancel_packet(USBDevice *udev, USBPacket *p) +{ + AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc; + int i; + + if (s->ep0_packet == p) { + s->ep0_packet = NULL; + } + for (i = 0; i < ASPEED_UDC_NUM_EP; i++) { + if (s->ep[i].pkt == p) { + s->ep[i].pkt = NULL; + } + } +} + +static void aspeed_udc_gadget_realize(USBDevice *udev, Error **errp) +{ + AspeedUDCGadget *dev = ASPEED_UDC_GADGET(udev); + + if (!dev->udc) { + error_setg(errp, TYPE_ASPEED_UDC_GADGET ": 'udc' link is not set"); + return; + } + /* Bind this gadget to its controller through the link property */ + object_property_set_link(OBJECT(dev->udc), "usbgadget", OBJECT(dev), + &error_abort); + + udev->auto_attach = 0; + /* The ASPEED UDC is USB 2.0, so it only runs at High-Speed for now */ + udev->speed = USB_SPEED_HIGH; + udev->speedmask = USB_SPEED_MASK_HIGH; +} + +static void aspeed_udc_gadget_unrealize(USBDevice *udev) +{ + AspeedUDCGadget *dev = ASPEED_UDC_GADGET(udev); + + if (dev->udc && dev->udc->usbgadget == dev) { + object_property_set_link(OBJECT(dev->udc), "usbgadget", NULL, + &error_abort); + } +} + +static const Property aspeed_udc_gadget_props[] = { + DEFINE_PROP_LINK("udc", AspeedUDCGadget, udc, TYPE_ASPEED_UDC, + AspeedUDCState *), +}; + +static void aspeed_udc_gadget_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + dc->desc = "ASPEED UDC gadget device"; + uc->product_desc = "ASPEED UDC gadget"; + uc->realize = aspeed_udc_gadget_realize; + uc->unrealize = aspeed_udc_gadget_unrealize; + uc->handle_reset = aspeed_udc_gadget_handle_reset; + uc->handle_control = aspeed_udc_gadget_handle_control; + uc->handle_data = aspeed_udc_gadget_handle_data; + uc->cancel_packet = aspeed_udc_gadget_cancel_packet; + device_class_set_props(dc, aspeed_udc_gadget_props); +} + +static const TypeInfo aspeed_udc_types[] = { + { + .name = TYPE_ASPEED_UDC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedUDCState), + .instance_init = aspeed_udc_init, + .class_init = aspeed_udc_class_init, + }, + { + .name = TYPE_ASPEED_UDC_GADGET, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(AspeedUDCGadget), + .class_init = aspeed_udc_gadget_class_init, + }, +}; + +DEFINE_TYPES(aspeed_udc_types) diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index 451a918e9f..f371e567f3 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c @@ -416,47 +416,41 @@ static inline bool ehci_periodic_enabled(EHCIState *s) } /* Get an array of dwords from main memory */ -static inline int get_dwords(EHCIState *ehci, uint64_t addr, - uint32_t *buf, int num) +static inline bool get_dwords(EHCIState *ehci, uint64_t addr, + uint32_t *buf, int num) { int i; - if (!ehci->as) { - ehci_raise_irq(ehci, USBSTS_HSE); - ehci->usbcmd &= ~USBCMD_RUNSTOP; - trace_usb_ehci_dma_error(); - return -1; - } - for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { - dma_memory_read(ehci->as, addr, buf, sizeof(*buf), - MEMTXATTRS_UNSPECIFIED); + if (dma_memory_read(ehci->as, addr, buf, sizeof(*buf), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + ehci_raise_irq(ehci, USBSTS_HSE); + ehci->usbcmd &= ~USBCMD_RUNSTOP; + trace_usb_ehci_dma_error(); + return false; + } *buf = le32_to_cpu(*buf); } - return num; + return true; } /* Put an array of dwords in to main memory */ -static inline int put_dwords(EHCIState *ehci, uint64_t addr, - uint32_t *buf, int num) +static inline void put_dwords(EHCIState *ehci, uint64_t addr, + uint32_t *buf, int num) { int i; - if (!ehci->as) { - ehci_raise_irq(ehci, USBSTS_HSE); - ehci->usbcmd &= ~USBCMD_RUNSTOP; - trace_usb_ehci_dma_error(); - return -1; - } - for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { uint32_t tmp = cpu_to_le32(*buf); - dma_memory_write(ehci->as, addr, &tmp, sizeof(tmp), - MEMTXATTRS_UNSPECIFIED); + if (dma_memory_write(ehci->as, addr, &tmp, sizeof(tmp), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + ehci_raise_irq(ehci, USBSTS_HSE); + ehci->usbcmd &= ~USBCMD_RUNSTOP; + trace_usb_ehci_dma_error(); + return; + } } - - return num; } static int ehci_get_pid(EHCIqtd *qtd) @@ -533,11 +527,11 @@ static void ehci_writeback_async_complete_packet(EHCIPacket *p) /* 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, ehci_qh_dwords(q->ehci)); - get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), - (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci)); - if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) { + if (!get_dwords(q->ehci, NLPTR_GET(q->qhaddr), + (uint32_t *) &qh, ehci_qh_dwords(q->ehci)) || + !get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), + (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci)) || + !ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) { p->async = EHCI_ASYNC_INITIALIZED; ehci_free_packet(p); return; @@ -1604,8 +1598,8 @@ static int ehci_state_waitlisthead(EHCIState *ehci, int 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, - ehci_qh_dwords(ehci)) < 0) { + if (!get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh, + ehci_qh_dwords(ehci))) { return 0; } ehci_trace_qh(NULL, NLPTR_GET(entry), &qh); @@ -1707,8 +1701,8 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) } memset(&qh, 0, sizeof(qh)); - if (get_dwords(ehci, NLPTR_GET(q->qhaddr), - (uint32_t *) &qh, ehci_qh_dwords(ehci)) < 0) { + if (!get_dwords(ehci, NLPTR_GET(q->qhaddr), + (uint32_t *) &qh, ehci_qh_dwords(ehci))) { q = NULL; goto out; } @@ -1785,8 +1779,8 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async) entry = ehci_get_fetch_addr(ehci, async); memset(&itd, 0, sizeof(itd)); - if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, - ehci_itd_dwords(ehci)) < 0) { + if (!get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, + ehci_itd_dwords(ehci))) { return -1; } ehci_trace_itd(ehci, entry, &itd); @@ -1811,8 +1805,8 @@ static int ehci_state_fetchsitd(EHCIState *ehci, int async) assert(!async); entry = ehci_get_fetch_addr(ehci, async); - if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd, - sizeof(EHCIsitd) >> 2) < 0) { + if (!get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd, + sizeof(EHCIsitd) >> 2)) { return 0; } ehci_trace_sitd(ehci, entry, &sitd); @@ -1872,18 +1866,18 @@ static int ehci_state_fetchqtd(EHCIQueue *q) uint64_t addr; addr = NLPTR_GET(q->qtdaddr); - if (get_dwords(q->ehci, addr + 8, &qtd.token, 1) < 0) { + if (!get_dwords(q->ehci, addr + 8, &qtd.token, 1)) { 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 || + if (!get_dwords(q->ehci, addr + 0, &qtd.next, 1) || + !get_dwords(q->ehci, addr + 4, &qtd.altnext, 1) || + !get_dwords(q->ehci, addr + 12, qtd.bufptr, + ARRAY_SIZE(qtd.bufptr)) || (q->ehci->caps_64bit_addr && - get_dwords(q->ehci, addr + offsetof(EHCIqtd, bufptr_hi), - qtd.bufptr_hi, ARRAY_SIZE(qtd.bufptr_hi)) < 0)) { + !get_dwords(q->ehci, addr + offsetof(EHCIqtd, bufptr_hi), + qtd.bufptr_hi, ARRAY_SIZE(qtd.bufptr_hi)))) { return 0; } ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd); @@ -1975,8 +1969,8 @@ static int ehci_fill_queue(EHCIPacket *p) } } memset(qtd.bufptr_hi, 0, sizeof(qtd.bufptr_hi)); - if (get_dwords(q->ehci, NLPTR_GET(qtdaddr), - (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci)) < 0) { + if (!get_dwords(q->ehci, NLPTR_GET(qtdaddr), + (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci))) { return -1; } ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd); @@ -2296,7 +2290,7 @@ static void ehci_advance_periodic_state(EHCIState *ehci) } list |= ((ehci->frindex & 0x1ff8) >> 1); list64 = ehci_get_desc_addr(ehci, list); - if (get_dwords(ehci, list64, &entry, 1) < 0) { + if (!get_dwords(ehci, list64, &entry, 1)) { break; } entry64 = ehci_get_desc_addr(ehci, entry); diff --git a/hw/usb/meson.build b/hw/usb/meson.build index ba55c28ef6..d4ba60a91c 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -27,6 +27,7 @@ system_ss.add(when: 'CONFIG_USB_XHCI_NEC', if_true: files('hcd-xhci-nec.c')) system_ss.add(when: 'CONFIG_USB_DWC2', if_true: files('hcd-dwc2.c')) system_ss.add(when: 'CONFIG_USB_DWC3', if_true: files('hcd-dwc3.c')) system_ss.add(when: 'CONFIG_USB_CHIPIDEA', if_true: files('chipidea.c')) +system_ss.add(when: 'CONFIG_ASPEED_UDC', if_true: files('aspeed-udc.c')) system_ss.add(when: 'CONFIG_IMX_USBPHY', if_true: files('imx-usb-phy.c')) system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c')) diff --git a/hw/usb/trace-events b/hw/usb/trace-events index 67249d69c2..80ead23358 100644 --- a/hw/usb/trace-events +++ b/hw/usb/trace-events @@ -377,3 +377,19 @@ canokey_handle_data_out(uint8_t ep_out, uint32_t out_len) "ep %d len %d" canokey_handle_data_in(uint8_t ep_in, uint32_t in_len) "ep %d len %d" canokey_realize(void) canokey_unrealize(void) + +# aspeed-udc.c +aspeed_udc_read(uint64_t offset, uint32_t value) "offset 0x%" PRIx64 " value 0x%x" +aspeed_udc_write(uint64_t offset, uint32_t value) "offset 0x%" PRIx64 " value 0x%x" +aspeed_udc_ep_read(int ep, uint64_t offset, uint32_t value) "ep %d, offset 0x%" PRIx64 " value 0x%x" +aspeed_udc_ep_write(int ep, uint64_t offset, uint32_t value) "ep %d, offset 0x%" PRIx64 " value 0x%x" +aspeed_udc_pullup(int on, int attached) "on %d, attached %d" +aspeed_udc_irq(uint32_t isr, uint32_t ier, int level) "isr 0x%x, ier 0x%x, level %d" +aspeed_udc_reset(uint32_t ier) "bus reset, ier 0x%x" +aspeed_udc_ep0_setup(uint8_t type, uint8_t req, uint16_t value, uint16_t index, uint16_t length, int dir_in, int addr) "bmRequestType 0x%02x, bRequest 0x%02x, wValue 0x%04x, wIndex 0x%04x, wLength %d, dir_in %d, addr %d" +aspeed_udc_ep0_ctrl_write(uint32_t val, int dir_in, uint32_t offset) "val 0x%x, dir_in %d, off %u" +aspeed_udc_ep0_complete(int dir_in, int actual) "dir_in %d, actual %d" +aspeed_udc_handle_data(int ep_nr, const char *dir, uint32_t iov, int ep_idx) "ep_nr %d, %s, iov %u, ep_idx %d" +aspeed_udc_ep_data_in(unsigned ep, uint32_t rptr, uint32_t wptr, uint32_t iov) "ep %u, rptr %u, wptr %u, iov %u" +aspeed_udc_ep_data_out(unsigned ep, uint32_t wptr, uint32_t avail, uint32_t iov) "ep %u, wptr %u, avail %u, iov %u" +aspeed_udc_ep_ack(unsigned ep) "ep %u" diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index cd68c7f1ca..38226d9b80 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -25,6 +25,7 @@ #include "hw/i2c/aspeed_i2c.h" #include "hw/i3c/aspeed_i3c.h" #include "hw/ssi/aspeed_smc.h" +#include "hw/misc/aspeed_acry.h" #include "hw/misc/aspeed_hace.h" #include "hw/misc/aspeed_sbc.h" #include "hw/misc/aspeed_sli.h" @@ -36,6 +37,8 @@ #include "hw/gpio/aspeed_sgpio.h" #include "hw/sd/aspeed_sdhci.h" #include "hw/usb/hcd-ehci.h" +#include "hw/usb/aspeed-udc.h" +#include "hw/core/or-irq.h" #include "qom/object.h" #include "hw/misc/aspeed_lpc.h" #include "hw/misc/unimp.h" @@ -69,6 +72,7 @@ struct AspeedSoCState { MemoryRegion *dram_mr; MemoryRegion dram_container; MemoryRegion sram[ASPEED_SRAM_NUM]; + MemoryRegion sram_container[ASPEED_SRAM_NUM]; MemoryRegion spi_boot_container; MemoryRegion spi_boot; MemoryRegion vbootrom; @@ -81,6 +85,7 @@ struct AspeedSoCState { AspeedSCUState scu; AspeedSCUState scuio; AspeedHACEState hace; + AspeedACRYState acry; AspeedXDMAState xdma; AspeedADCState adc; AspeedSMCState fmc; @@ -89,7 +94,6 @@ struct AspeedSoCState { AspeedSBCState sbc; AspeedSLIState sli; AspeedSLIState sliio; - UnimplementedDeviceState sbc_unimplemented; AspeedSDMCState sdmc; AspeedPWMState pwm; AspeedWDTState wdt[ASPEED_WDTS_NUM]; @@ -138,6 +142,8 @@ struct Aspeed2600SoCState { A15MPPrivState a7mpcore; ARMCPU cpu[ASPEED_CPUS_NUM]; /* XXX belong to a7mpcore */ + AspeedUDCState udc; + OrIRQState ehci2_udc_orgate; }; #define TYPE_ASPEED2600_SOC "aspeed2600-soc" @@ -270,6 +276,7 @@ enum { ASPEED_DEV_EMMC, ASPEED_DEV_KCS, ASPEED_DEV_HACE, + ASPEED_DEV_ACRY, ASPEED_DEV_DPMCU, ASPEED_DEV_DP, ASPEED_DEV_I3C, diff --git a/include/hw/i2c/aspeed_i2c.h b/include/hw/i2c/aspeed_i2c.h index 05937a7a0b..480c6418fe 100644 --- a/include/hw/i2c/aspeed_i2c.h +++ b/include/hw/i2c/aspeed_i2c.h @@ -231,6 +231,8 @@ REG32(I2CS_DMA_TX_ADDR_HI, 0x68) 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_BYTE_DATA_LOG, 0x84) + FIELD(I2CC_BYTE_DATA_LOG, RX_BUF, 0, 8) REG32(I2CC_VERSION_CTRL, 0x94) FIELD(I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN, 2, 1) @@ -302,6 +304,7 @@ struct AspeedI2CClass { bool has_share_pool; uint64_t mem_size; bool has_dma64; + bool has_byte_data_log; uint32_t dma_addr_lo_mask; }; diff --git a/include/hw/misc/aspeed_acry.h b/include/hw/misc/aspeed_acry.h new file mode 100644 index 0000000000..5ca80deec4 --- /dev/null +++ b/include/hw/misc/aspeed_acry.h @@ -0,0 +1,39 @@ +/* + * ASPEED ACRY Engine + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ASPEED_ACRY_H +#define ASPEED_ACRY_H + +#include "hw/core/sysbus.h" +#include "system/memory.h" + +#define TYPE_ASPEED_ACRY "aspeed.acry" +OBJECT_DECLARE_SIMPLE_TYPE(AspeedACRYState, ASPEED_ACRY) + +#define ASPEED_ACRY_NR_REGS (0x400 >> 2) +/* Max size of the "data" (message) field within the SRAM buffer. */ +#define ASPEED_ACRY_DATA_MAX_LEN 0x800 +#define ASPEED_ACRY_MAX_BITS 4096 +/* Max exponent/modulus size for a 4096-bit RSA key, in bytes. */ +#define ASPEED_ACRY_MAX_BYTES (ASPEED_ACRY_MAX_BITS / 8) + +struct AspeedACRYState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + + uint32_t regs[ASPEED_ACRY_NR_REGS]; + + MemoryRegion *dram_mr; + MemoryRegion *sram_mr; + AddressSpace dram_as; + AddressSpace sram_as; +}; + +#endif /* ASPEED_ACRY_H */ diff --git a/include/hw/misc/aspeed_sbc.h b/include/hw/misc/aspeed_sbc.h index 07c7c22a86..756c612356 100644 --- a/include/hw/misc/aspeed_sbc.h +++ b/include/hw/misc/aspeed_sbc.h @@ -17,7 +17,7 @@ #define TYPE_ASPEED_AST10X0_SBC TYPE_ASPEED_SBC "-ast10x0" OBJECT_DECLARE_TYPE(AspeedSBCState, AspeedSBCClass, ASPEED_SBC) -#define ASPEED_SBC_NR_REGS (0x93c >> 2) +#define ASPEED_SBC_NR_REGS (0x1000 >> 2) #define QSR_AES BIT(27) #define QSR_RSA1024 (0x0 << 12) @@ -40,12 +40,16 @@ struct AspeedSBCState { uint32_t regs[ASPEED_SBC_NR_REGS]; AspeedOTPState otp; + + MemoryRegion *sram; + AddressSpace sram_as; }; struct AspeedSBCClass { SysBusDeviceClass parent_class; bool has_otp; + bool has_ecdsa; }; #endif /* ASPEED_SBC_H */ diff --git a/include/hw/sensor/tmp105.h b/include/hw/sensor/tmp105.h index 244e2989fe..0698aeead7 100644 --- a/include/hw/sensor/tmp105.h +++ b/include/hw/sensor/tmp105.h @@ -1,5 +1,5 @@ /* - * Texas Instruments TMP105 Temperature Sensor + * Texas Instruments TMP105/TMP75/TMP175/LM75B Temperature Sensor * * Browse the data sheet: * @@ -11,45 +11,13 @@ * 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 QEMU_TMP105_H -#define QEMU_TMP105_H - -#include "hw/i2c/i2c.h" -#include "hw/sensor/tmp105_regs.h" -#include "qom/object.h" +#ifndef HW_SENSOR_TMP105_H +#define HW_SENSOR_TMP105_H +/* TMP75, TMP175 and NXP LM75B are register-compatible with TMP105. */ #define TYPE_TMP105 "tmp105" -OBJECT_DECLARE_SIMPLE_TYPE(TMP105State, TMP105) - -/** - * TMP105State: - * @config: Bits 5 and 6 (value 32 and 64) determine the precision of the - * temperature. See Table 8 in the data sheet. - * - * @see_also: http://www.ti.com/lit/gpn/tmp105 - */ -struct TMP105State { - /*< private >*/ - I2CSlave i2c; - /*< public >*/ - - uint8_t len; - uint8_t buf[2]; - qemu_irq pin; - - uint8_t pointer; - uint8_t config; - int16_t temperature; - int16_t limit[2]; - int faults; - uint8_t alarm; - /* - * The TMP105 initially looks for a temperature rising above T_high; - * once this is detected, the condition it looks for next is the - * temperature falling below T_low. This flag is false when initially - * looking for T_high, true when looking for T_low. - */ - bool detect_falling; -}; +#define TYPE_TMP175 "tmp175" +#define TYPE_TMP75 "tmp75" +#define TYPE_LM75B "lm75b" #endif diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h new file mode 100644 index 0000000000..7701c1aa34 --- /dev/null +++ b/include/hw/usb/aspeed-udc.h @@ -0,0 +1,85 @@ +/* + * ASPEED USB Device Controller (UDC) + * + * Copyright (c) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_USB_ASPEED_UDC_H +#define HW_USB_ASPEED_UDC_H + +#include "hw/core/sysbus.h" +#include "hw/usb/usb.h" +#include "qom/object.h" + +#define TYPE_ASPEED_UDC "aspeed.udc" +OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCState, ASPEED_UDC) + +/* + * The gadget side of the controller is presented to a USB host controller's + * bus as a single USB device that delegates back to the AspeedUDCState. + */ +#define TYPE_ASPEED_UDC_GADGET "aspeed.udc-gadget" +OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCGadget, ASPEED_UDC_GADGET) + +/* + * Register map: root/global block at 0x000 - 0x087, then one 0x10 byte bank + * per programmable endpoint from 0x200. + */ +#define ASPEED_UDC_MEM_SIZE 0x300 +#define ASPEED_UDC_ROOT_NR_REGS (0x88 >> 2) +#define ASPEED_UDC_EP_REG_BASE 0x200 +#define ASPEED_UDC_EP_NR_REGS (0x10 >> 2) + +/* + * EP0 (control) is served through the root registers (UDC_EP0_*), so only + * the 4 programmable endpoints get their own register bank / ep[] entry. + */ +#define ASPEED_UDC_NUM_EP 4 + +typedef struct AspeedUDCEP { + MemoryRegion mr; + uint32_t regs[ASPEED_UDC_EP_NR_REGS]; + int index; + + /* + * host packet parked until the guest gadget driver queues (IN) or + * arms (OUT) data + */ + USBPacket *pkt; + /* bytes of the current IN descriptor already served */ + uint32_t desc_off; +} AspeedUDCEP; + +struct AspeedUDCGadget { + USBDevice parent_obj; + AspeedUDCState *udc; +}; + +struct AspeedUDCState { + SysBusDevice parent_obj; + + MemoryRegion udc_container; + MemoryRegion root_mr; + MemoryRegion *dram_mr; + AddressSpace dram_as; + uint32_t regs[ASPEED_UDC_ROOT_NR_REGS]; + AspeedUDCEP ep[ASPEED_UDC_NUM_EP]; + qemu_irq irq; + + /* gadget USB device bound to this controller (set at its realize) */ + AspeedUDCGadget *usbgadget; + + /* + * In-flight EP0 control transfer (host side), deferred until the guest + * gadget driver responds via MMIO. + */ + USBPacket *ep0_packet; + uint32_t ep0_setup_len; + uint32_t ep0_offset; + uint8_t *ep0_data; + bool ep0_dir_in; +}; + +#endif /* HW_USB_ASPEED_UDC_H */ diff --git a/qapi/crypto.json b/qapi/crypto.json index 6e3a98ff68..cae2c678cb 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -557,10 +557,26 @@ # # @rsa: RSA algorithm # +# @ecdsa: ECDSA algorithm (Since 11.2) +# # Since: 7.1 ## { 'enum': 'QCryptoAkCipherAlgo', - 'data': ['rsa']} + 'data': ['rsa', 'ecdsa']} + +## +# @QCryptoCurveID: +# +# The supported elliptic curves. +# +# @prime256v1: NIST P-256 curve (prime256v1) +# +# @secp384r1: NIST P-384 curve (secp384r1) +# +# Since: 11.2 +## +{ 'enum': 'QCryptoCurveID', + 'data': ['prime256v1', 'secp384r1']} ## # @QCryptoAkCipherKeyType: @@ -606,6 +622,18 @@ 'padding-alg': 'QCryptoRSAPaddingAlgo'}} ## +# @QCryptoAkCipherOptionsECDSA: +# +# Specific parameters for the ECDSA algorithm. +# +# @curve-id: the elliptic curve to use +# +# Since: 11.2 +## +{ 'struct': 'QCryptoAkCipherOptionsECDSA', + 'data': { 'curve-id': 'QCryptoCurveID' }} + +## # @QCryptoAkCipherOptions: # # The options that are available for all asymmetric key algorithms @@ -618,4 +646,5 @@ { 'union': 'QCryptoAkCipherOptions', 'base': { 'alg': 'QCryptoAkCipherAlgo' }, 'discriminator': 'alg', - 'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }} + 'data': { 'rsa': 'QCryptoAkCipherOptionsRSA', + 'ecdsa': 'QCryptoAkCipherOptionsECDSA' }} diff --git a/tests/functional/aspeed.py b/tests/functional/aspeed.py index 076da1036c..08af6b4839 100644 --- a/tests/functional/aspeed.py +++ b/tests/functional/aspeed.py @@ -8,7 +8,8 @@ from qemu_test import LinuxKernelTest class AspeedTest(LinuxKernelTest): def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', - cpu_id='0x0', soc='AST2500 rev A1'): + cpu_id='0x0', soc='AST2500 rev A1', + dt_model=None): self.set_machine(machine) self.vm.set_console() self.vm.add_args('-drive', f'file={image},if=mtd,format=raw', @@ -19,6 +20,8 @@ class AspeedTest(LinuxKernelTest): self.wait_for_console_pattern('## Loading kernel from FIT Image') self.wait_for_console_pattern('Starting kernel ...') self.wait_for_console_pattern(f'Booting Linux on physical CPU {cpu_id}') + if dt_model: + self.wait_for_console_pattern(f'Machine model: {dt_model}') self.wait_for_console_pattern(f'ASPEED {soc}') self.wait_for_console_pattern('/init as init process') self.wait_for_boot_complete() diff --git a/tests/qtest/aspeed-acry-test.c b/tests/qtest/aspeed-acry-test.c new file mode 100644 index 0000000000..ec348c2206 --- /dev/null +++ b/tests/qtest/aspeed-acry-test.c @@ -0,0 +1,406 @@ +/* + * QTest testcase for the ASPEED ACRY Engine + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/bitops.h" +#include "crypto/akcipher.h" + +#define ACRY_TRIGGER 0x000 +#define ACRY_TRIGGER_RSA_DMA_DATA BIT(1) +#define ACRY_TRIGGER_RSA_START BIT(0) +#define ACRY_DMA_CMD 0x048 +#define ACRY_DMA_CMD_DMEM_AHB BIT(8) +#define ACRY_DMA_CMD_SRAM_MODE_RSA (0x3 << 4) +#define ACRY_DMA_SRC 0x04C +#define ACRY_DMA_LEN 0x050 +#define ACRY_RSA_KEY_LEN 0x058 +#define ACRY_INT_MASK 0x3F8 +#define ACRY_INT_MASK_RSA_DMA_MASK BIT(2) +#define ACRY_INT_MASK_RSA_ENG_MASK BIT(1) +#define ACRY_STATUS 0x3FC +#define ACRY_STATUS_RSA_DMA_DONE BIT(2) +#define ACRY_STATUS_RSA_ENG_DONE BIT(1) +#define ACRY_STATUS_RSA_DONE (ACRY_STATUS_RSA_ENG_DONE | \ + ACRY_STATUS_RSA_DMA_DONE) + +#define ACRY_DATA_MAX_LEN 0x800 +#define ACRY_SRAM_SIZE (3 * ACRY_DATA_MAX_LEN) +#define ACRY_MAX_BITS 4096 +#define ACRY_MAX_BYTES (ACRY_MAX_BITS / 8) + +#define ACRY_BYTES_PER_DWORD 4 +#define ACRY_LANES_PER_BLOCK 4 +/* Each block holds 3 regions (exp, mod, data) of LANES_PER_BLOCK dwords. */ +#define ACRY_DWORDS_PER_BLOCK (3 * ACRY_LANES_PER_BLOCK) + +/* Dwords into each block where each operand's region starts. */ +#define ACRY_EXP_OFFSET (0 * ACRY_LANES_PER_BLOCK) +#define ACRY_MOD_OFFSET (1 * ACRY_LANES_PER_BLOCK) +#define ACRY_DATA_OFFSET (2 * ACRY_LANES_PER_BLOCK) + +/* + * Raw (unpadded) RSA known-answer vectors: c = m^e mod n with + * e = 0x10001 (65537) - exactly what the "rsa" akcipher transform backed by + * the ACRY FW computes. PKCS1 padding is a separate "pkcs1pad(rsa)" template + * layered on top in Linux, not done by the ACRY hardware. + * + * rsa2048_* and rsa4096_* are from the Linux kernel crypto/testmgr.h (v6.18), + * rsa_tv_template[2] and [3]. Grep that file's own comment to find them: + * + * RSA test vectors. Borrowed from openSSL. + * + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/crypto/testmgr.h?h=v6.18 + * + * There the modulus n is DER-encoded in the .key field (not a raw field), the + * ciphertext is .c, and every entry reuses the same 8-byte .m message. + */ +/* rsa_tv_template[].m, shared by every entry */ +static const uint8_t rsa_m[8] = { + 0x54, 0x85, 0x9B, 0x34, 0x2C, 0x49, 0xEA, 0x2A, +}; +/* public exponent e = 65537, DER-encoded in every .key */ +static const uint8_t rsa_e[3] = { 0x01, 0x00, 0x01 }; + +/* rsa_tv_template[2].key, modulus n */ +static const uint8_t rsa2048_n[256] = { + 0xDB, 0x10, 0x1A, 0xC2, 0xA3, 0xF1, 0xDC, 0xFF, 0x13, 0x6B, 0xED, 0x44, + 0xDF, 0xF0, 0x02, 0x6D, 0x13, 0xC7, 0x88, 0xDA, 0x70, 0x6B, 0x54, 0xF1, + 0xE8, 0x27, 0xDC, 0xC3, 0x0F, 0x99, 0x6A, 0xFA, 0xC6, 0x67, 0xFF, 0x1D, + 0x1E, 0x3C, 0x1D, 0xC1, 0xB5, 0x5F, 0x6C, 0xC0, 0xB2, 0x07, 0x3A, 0x6D, + 0x41, 0xE4, 0x25, 0x99, 0xAC, 0xFC, 0xD2, 0x0F, 0x02, 0xD3, 0xD1, 0x54, + 0x06, 0x1A, 0x51, 0x77, 0xBD, 0xB6, 0xBF, 0xEA, 0xA7, 0x5C, 0x06, 0xA9, + 0x5D, 0x69, 0x84, 0x45, 0xD7, 0xF5, 0x05, 0xBA, 0x47, 0xF0, 0x1B, 0xD7, + 0x2B, 0x24, 0xEC, 0xCB, 0x9B, 0x1B, 0x10, 0x8D, 0x81, 0xA0, 0xBE, 0xB1, + 0x8C, 0x33, 0xE4, 0x36, 0xB8, 0x43, 0xEB, 0x19, 0x2A, 0x81, 0x8D, 0xDE, + 0x81, 0x0A, 0x99, 0x48, 0xB6, 0xF6, 0xBC, 0xCD, 0x49, 0x34, 0x3A, 0x8F, + 0x26, 0x94, 0xE3, 0x28, 0x82, 0x1A, 0x7C, 0x8F, 0x59, 0x9F, 0x45, 0xE8, + 0x5D, 0x1A, 0x45, 0x76, 0x04, 0x56, 0x05, 0xA1, 0xD0, 0x1B, 0x8C, 0x77, + 0x6D, 0xAF, 0x53, 0xFA, 0x71, 0xE2, 0x67, 0xE0, 0x9A, 0xFE, 0x03, 0xA9, + 0x85, 0xD2, 0xC9, 0xAA, 0xBA, 0x2A, 0xBC, 0xF4, 0xA0, 0x08, 0xF5, 0x13, + 0x98, 0x13, 0x5D, 0xF0, 0xD9, 0x33, 0x34, 0x2A, 0x61, 0xC3, 0x89, 0x55, + 0xF0, 0xAE, 0x1A, 0x9C, 0x22, 0xEE, 0x19, 0x05, 0x8D, 0x32, 0xFE, 0xEC, + 0x9C, 0x84, 0xBA, 0xB7, 0xF9, 0x6C, 0x3A, 0x4F, 0x07, 0xFC, 0x45, 0xEB, + 0x12, 0xE5, 0x7B, 0xFD, 0x55, 0xE6, 0x29, 0x69, 0xD1, 0xC2, 0xE8, 0xB9, + 0x78, 0x59, 0xF6, 0x79, 0x10, 0xC6, 0x4E, 0xEB, 0x6A, 0x5E, 0xB9, 0x9A, + 0xC7, 0xC4, 0x5B, 0x63, 0xDA, 0xA3, 0x3F, 0x5E, 0x92, 0x7A, 0x81, 0x5E, + 0xD6, 0xB0, 0xE2, 0x62, 0x8F, 0x74, 0x26, 0xC2, 0x0C, 0xD3, 0x9A, 0x17, + 0x47, 0xE6, 0x8E, 0xAB, +}; +/* rsa_tv_template[2].c */ +static const uint8_t rsa2048_c[256] = { + 0xB2, 0x97, 0x76, 0xB4, 0xAE, 0x3E, 0x38, 0x3C, 0x7E, 0x64, 0x1F, 0xCC, + 0xA2, 0x7F, 0xF6, 0xBE, 0xCF, 0x49, 0xBC, 0x48, 0xD3, 0x6C, 0x8F, 0x0A, + 0x0E, 0xC1, 0x73, 0xBD, 0x7B, 0x55, 0x79, 0x36, 0x0E, 0xA1, 0x87, 0x88, + 0xB9, 0x2C, 0x90, 0xA6, 0x53, 0x5E, 0xE9, 0xEF, 0xC4, 0xE2, 0x4D, 0xDD, + 0xF7, 0xA6, 0x69, 0x82, 0x3F, 0x56, 0xA4, 0x7B, 0xFB, 0x62, 0xE0, 0xAE, + 0xB8, 0xD3, 0x04, 0xB3, 0xAC, 0x5A, 0x15, 0x2A, 0xE3, 0x19, 0x9B, 0x03, + 0x9A, 0x0B, 0x41, 0xDA, 0x64, 0xEC, 0x0A, 0x69, 0xFC, 0xF2, 0x10, 0x92, + 0xF3, 0xC1, 0xBF, 0x84, 0x7F, 0xFD, 0x2C, 0xAE, 0xC8, 0xB5, 0xF6, 0x41, + 0x70, 0xC5, 0x47, 0x03, 0x8A, 0xF8, 0xFF, 0x6F, 0x3F, 0xD2, 0x6F, 0x09, + 0xB4, 0x22, 0xF3, 0x30, 0xBE, 0xA9, 0x85, 0xCB, 0x9C, 0x8D, 0xF9, 0x8F, + 0xEB, 0x32, 0x91, 0xA2, 0x25, 0x84, 0x8F, 0xF5, 0xDC, 0xC7, 0x06, 0x9C, + 0x2D, 0xE5, 0x11, 0x2C, 0x09, 0x09, 0x87, 0x09, 0xA9, 0xF6, 0x33, 0x73, + 0x90, 0xF1, 0x60, 0xF2, 0x65, 0xDD, 0x30, 0xA5, 0x66, 0xCE, 0x62, 0x7B, + 0xD0, 0xF8, 0x2D, 0x3D, 0x19, 0x82, 0x77, 0xE3, 0x0A, 0x5F, 0x75, 0x2F, + 0x8E, 0xB1, 0xE5, 0xE8, 0x91, 0x35, 0x1B, 0x3B, 0x33, 0xB7, 0x66, 0x92, + 0xD1, 0xF2, 0x8E, 0x6F, 0xE5, 0x75, 0x0C, 0xAD, 0x36, 0xFB, 0x4E, 0xD0, + 0x66, 0x61, 0xBD, 0x49, 0xFE, 0xF4, 0x1A, 0xA2, 0x2B, 0x49, 0xFE, 0x03, + 0x4C, 0x74, 0x47, 0x8D, 0x9A, 0x66, 0xB2, 0x49, 0x46, 0x4D, 0x77, 0xEA, + 0x33, 0x4D, 0x6B, 0x3C, 0xB4, 0x49, 0x4A, 0xC6, 0x7D, 0x3D, 0xB5, 0xB9, + 0x56, 0x41, 0x15, 0x67, 0x0F, 0x94, 0x3C, 0x93, 0x65, 0x27, 0xE0, 0x21, + 0x5D, 0x59, 0xC3, 0x62, 0xD5, 0xA6, 0xDA, 0x38, 0x26, 0x22, 0x5E, 0x34, + 0x1C, 0x94, 0xAF, 0x98, +}; + +/* rsa_tv_template[3].key, modulus n */ +static const uint8_t rsa4096_n[512] = { + 0xC3, 0x8B, 0x55, 0x7B, 0x73, 0x4D, 0xFF, 0xE9, 0x9B, 0xC6, 0xDC, 0x67, + 0x3C, 0xB4, 0x8E, 0xA0, 0x86, 0xED, 0xF2, 0xB9, 0x50, 0x5C, 0x54, 0x5C, + 0xBA, 0xE4, 0xA1, 0xB2, 0xA7, 0xAE, 0x2F, 0x1B, 0x7D, 0xF1, 0xFB, 0xAC, + 0x79, 0xC5, 0xDF, 0x1A, 0x00, 0xC9, 0xB2, 0xC1, 0x61, 0x25, 0x33, 0xE6, + 0x9C, 0xE9, 0xCF, 0xD6, 0x27, 0xC4, 0x4E, 0x44, 0x30, 0x44, 0x5E, 0x08, + 0xA1, 0x87, 0x52, 0xCC, 0x6B, 0x97, 0x70, 0x8C, 0xBC, 0xA5, 0x06, 0x31, + 0x0C, 0xD4, 0x2F, 0xD5, 0x7D, 0x26, 0x24, 0xA2, 0xE2, 0xAC, 0x78, 0xF4, + 0x53, 0x14, 0xCE, 0xF7, 0x19, 0x2E, 0xD7, 0xF7, 0xE6, 0x0C, 0xB9, 0x56, + 0x7F, 0x0B, 0xF1, 0xB1, 0xE2, 0x43, 0x70, 0xBD, 0x86, 0x1D, 0xA1, 0xCC, + 0x2B, 0x19, 0x08, 0x76, 0xEF, 0x91, 0xAC, 0xBF, 0x20, 0x24, 0x0D, 0x38, + 0xC0, 0x89, 0xB8, 0x9A, 0x70, 0xB3, 0x64, 0xD9, 0x8F, 0x80, 0x41, 0x10, + 0x5B, 0x9F, 0xB1, 0xCB, 0x76, 0x43, 0x00, 0x21, 0x25, 0x36, 0xD4, 0x19, + 0xFC, 0x55, 0x95, 0x10, 0xE4, 0x26, 0x74, 0x98, 0x2C, 0xD9, 0xBD, 0x0B, + 0x2B, 0x04, 0xC2, 0xAC, 0x82, 0x38, 0xB4, 0xDD, 0x4C, 0x04, 0x7E, 0x51, + 0x36, 0x40, 0x1E, 0x0B, 0xC4, 0x7C, 0x25, 0xDD, 0x4B, 0xB2, 0xE7, 0x20, + 0x0A, 0x57, 0xF9, 0xB4, 0x94, 0xC3, 0x08, 0x33, 0x22, 0x6F, 0x8B, 0x48, + 0xDB, 0x03, 0x68, 0x5A, 0x5B, 0xBA, 0xAE, 0xF3, 0xAD, 0xCF, 0xC3, 0x6D, + 0xBA, 0xF1, 0x28, 0x67, 0x7E, 0x6C, 0x79, 0x07, 0xDE, 0xFC, 0xED, 0xE7, + 0x96, 0xE3, 0x6C, 0xE0, 0x2C, 0x87, 0xF8, 0x02, 0x01, 0x28, 0x38, 0x43, + 0x21, 0x53, 0x84, 0x69, 0x75, 0x78, 0x15, 0x7E, 0xEE, 0xD2, 0x1B, 0xB9, + 0x23, 0x40, 0xA8, 0x86, 0x1E, 0x38, 0x83, 0xB2, 0x73, 0x1D, 0x53, 0xFB, + 0x9E, 0x2A, 0x8A, 0xB2, 0x75, 0x35, 0x01, 0xC3, 0xC3, 0xC4, 0x94, 0xE8, + 0x84, 0x86, 0x64, 0x81, 0xF4, 0x42, 0xAA, 0x3C, 0x0E, 0xD6, 0x4F, 0xBC, + 0x0A, 0x09, 0x2D, 0xE7, 0x1B, 0xD4, 0x10, 0xA8, 0x54, 0xEA, 0x89, 0x84, + 0x8A, 0xCB, 0xF7, 0x5A, 0x3C, 0xCA, 0x76, 0x08, 0x29, 0x62, 0xB4, 0x6A, + 0x22, 0xDF, 0x14, 0x95, 0x71, 0xFD, 0xB6, 0x86, 0x39, 0xB8, 0x8B, 0xF8, + 0x91, 0x7F, 0x38, 0xAA, 0x14, 0xCD, 0xE5, 0xF5, 0x1D, 0xC2, 0x6D, 0x53, + 0x69, 0x52, 0x84, 0x7F, 0xA3, 0x1A, 0x5E, 0x26, 0x04, 0x83, 0x06, 0x73, + 0x52, 0x56, 0xCF, 0x76, 0x26, 0xC9, 0xDD, 0x75, 0xD7, 0xFC, 0xF4, 0x69, + 0xD8, 0x7B, 0x55, 0xB7, 0x68, 0x13, 0x53, 0xB9, 0xE7, 0x89, 0xC3, 0xE8, + 0xD6, 0x6E, 0xA7, 0x6D, 0xEA, 0x81, 0xFD, 0xC4, 0xB7, 0x05, 0x5A, 0xB7, + 0x41, 0x0A, 0x23, 0x8E, 0x03, 0x8A, 0x1C, 0xAE, 0xD3, 0x1E, 0xCE, 0xE3, + 0x5E, 0xFC, 0x19, 0x4A, 0xEE, 0x61, 0x9B, 0x8E, 0xE5, 0xE5, 0xDD, 0x85, + 0xF9, 0x41, 0xEC, 0x14, 0x53, 0x92, 0xF7, 0xDD, 0x06, 0x85, 0x02, 0x91, + 0xE3, 0xEB, 0x6C, 0x43, 0x03, 0xB1, 0x36, 0x7B, 0x89, 0x5A, 0xA8, 0xEB, + 0xFC, 0xD5, 0xA8, 0x35, 0xDC, 0x81, 0xD9, 0x5C, 0xBD, 0xCA, 0xDC, 0x9B, + 0x98, 0x0B, 0x06, 0x5D, 0x0C, 0x5B, 0xEE, 0xF3, 0xD5, 0xCC, 0x57, 0xC9, + 0x71, 0x2F, 0x90, 0x3B, 0x3C, 0xF0, 0x8E, 0x4E, 0x35, 0x48, 0xAE, 0x63, + 0x74, 0xA9, 0xFC, 0x72, 0x75, 0x8E, 0x34, 0xA8, 0xF2, 0x1F, 0xEA, 0xDF, + 0x3A, 0x37, 0x2D, 0xE5, 0x39, 0x39, 0xF8, 0x57, 0x58, 0x3C, 0x04, 0xFE, + 0x87, 0x06, 0x98, 0xBC, 0x7B, 0xD3, 0x21, 0x36, 0x60, 0x25, 0x54, 0xA7, + 0x3D, 0xFA, 0x91, 0xCC, 0xA8, 0x0B, 0x92, 0x8E, 0xB4, 0xF7, 0x06, 0xFF, + 0x1E, 0x95, 0xCB, 0x07, 0x76, 0x97, 0x3B, 0x9D, +}; +/* rsa_tv_template[3].c */ +static const uint8_t rsa4096_c[512] = { + 0x5C, 0xCE, 0x9C, 0xD7, 0x9A, 0x9E, 0xA1, 0xFE, 0x7A, 0x82, 0x3C, 0x68, + 0x27, 0x98, 0xE3, 0x5D, 0xD5, 0xD7, 0x07, 0x29, 0xF5, 0xFB, 0xC3, 0x1A, + 0x7F, 0x63, 0x1E, 0x62, 0x31, 0x3B, 0x19, 0x87, 0x79, 0x4F, 0xEC, 0x7B, + 0xF3, 0xCB, 0xEA, 0x9B, 0x95, 0x52, 0x3A, 0x40, 0xE5, 0x87, 0x7B, 0x72, + 0xD1, 0x72, 0xC9, 0xFB, 0x54, 0x63, 0xD8, 0xC9, 0xD7, 0x2C, 0xFC, 0x7B, + 0xC3, 0x14, 0x1E, 0xBC, 0x18, 0xB4, 0x34, 0xA1, 0xBF, 0x14, 0xB1, 0x37, + 0x31, 0x6E, 0xF0, 0x1B, 0x35, 0x19, 0x54, 0x07, 0xF7, 0x99, 0xEC, 0x3E, + 0x63, 0xE2, 0xCD, 0x61, 0x28, 0x65, 0xC3, 0xCD, 0xB1, 0x38, 0x36, 0xA5, + 0xB2, 0xD7, 0xB0, 0xDC, 0x1F, 0xF5, 0xEF, 0x19, 0xC7, 0x53, 0x32, 0x2D, + 0x1C, 0x26, 0xDA, 0xE4, 0x0D, 0xD6, 0x90, 0x7E, 0x28, 0xD8, 0xDC, 0xE4, + 0x61, 0x05, 0xD2, 0x25, 0x90, 0x01, 0xD3, 0x96, 0x6D, 0xA6, 0xCF, 0x58, + 0x20, 0xBB, 0x03, 0xF4, 0x01, 0xBC, 0x79, 0xB9, 0x18, 0xD8, 0xB8, 0xBA, + 0xBD, 0x93, 0xFC, 0xF2, 0x62, 0x5D, 0x8C, 0x66, 0x1E, 0x0E, 0x84, 0x59, + 0x93, 0xDD, 0xE2, 0x93, 0xA2, 0x62, 0x7D, 0x08, 0x82, 0x7A, 0xDD, 0xFC, + 0xB8, 0xBC, 0xC5, 0x4F, 0x9C, 0x4E, 0xBF, 0xB4, 0xFC, 0xF4, 0xC5, 0x01, + 0xE8, 0x00, 0x70, 0x4D, 0x28, 0x26, 0xCC, 0x2E, 0xFE, 0x0E, 0x58, 0x41, + 0x8B, 0xEC, 0xAF, 0x7C, 0x4B, 0x54, 0xD0, 0xA0, 0x64, 0xF9, 0x32, 0xF4, + 0x2E, 0x47, 0x65, 0x0A, 0x67, 0x88, 0x39, 0x3A, 0xDB, 0xB2, 0xDB, 0x7B, + 0xB5, 0xF6, 0x17, 0xA8, 0xD9, 0xC6, 0x5E, 0x28, 0x13, 0x82, 0x8A, 0x99, + 0xDB, 0x60, 0x08, 0xA5, 0x23, 0x37, 0xFA, 0x88, 0x90, 0x31, 0xC8, 0x9D, + 0x8F, 0xEC, 0xFB, 0x85, 0x9F, 0xB1, 0xCE, 0xA6, 0x24, 0x50, 0x46, 0x44, + 0x47, 0xCB, 0x65, 0xD1, 0xDF, 0xC0, 0xB1, 0x6C, 0x90, 0x1F, 0x99, 0x8E, + 0x4D, 0xD5, 0x9E, 0x31, 0x07, 0x66, 0x87, 0xDF, 0x01, 0xAA, 0x56, 0x3C, + 0x71, 0xE0, 0x2B, 0x6F, 0x67, 0x3B, 0x23, 0xED, 0xC2, 0xBD, 0x03, 0x30, + 0x79, 0x76, 0x02, 0x10, 0x10, 0x98, 0x85, 0x8A, 0xFF, 0xFD, 0x0B, 0xDA, + 0xA5, 0xD9, 0x32, 0x48, 0x02, 0xA0, 0x0B, 0xB9, 0x2A, 0x8A, 0x18, 0xCA, + 0xC6, 0x8F, 0x3F, 0xBB, 0x16, 0xB2, 0xAA, 0x98, 0x27, 0xE3, 0x60, 0x43, + 0xED, 0x15, 0x70, 0xD4, 0x57, 0x15, 0xFE, 0x19, 0xD4, 0x9B, 0x13, 0x78, + 0x8A, 0xF7, 0x21, 0xF1, 0xA2, 0xA2, 0x2D, 0xB3, 0x09, 0xCF, 0x44, 0x91, + 0x6E, 0x08, 0x3A, 0x30, 0x81, 0x3E, 0x90, 0x93, 0x8A, 0x67, 0x33, 0x00, + 0x59, 0x54, 0x9A, 0x25, 0xD3, 0x49, 0x8E, 0x9F, 0xC1, 0x4B, 0xE5, 0x86, + 0xF3, 0x50, 0x4C, 0xBC, 0xC5, 0xD3, 0xF5, 0x3A, 0x54, 0xE1, 0x36, 0x3F, + 0xE2, 0x5A, 0xB4, 0x37, 0xC0, 0xEB, 0x70, 0x35, 0xEC, 0xF6, 0xB7, 0xE8, + 0x44, 0x3B, 0x7B, 0xF3, 0xF1, 0xF2, 0x1E, 0xDB, 0x60, 0x7D, 0xD5, 0xBE, + 0xF0, 0x71, 0x34, 0x90, 0x4C, 0xCB, 0xD4, 0x35, 0x51, 0xC7, 0xDD, 0xD8, + 0xC9, 0x81, 0xF5, 0x5D, 0x57, 0x46, 0x2C, 0xB1, 0x7B, 0x9B, 0xAA, 0xCB, + 0xD1, 0x22, 0x25, 0x49, 0x44, 0xA3, 0xD4, 0x6B, 0x29, 0x7B, 0xD8, 0xB2, + 0x07, 0x93, 0xBF, 0x3D, 0x52, 0x49, 0x84, 0x79, 0xEF, 0xB8, 0xE5, 0xC4, + 0xAD, 0xCA, 0xA8, 0xC6, 0xF6, 0xA6, 0x76, 0x70, 0x5B, 0x0B, 0xE5, 0x83, + 0xC6, 0x0E, 0xEF, 0x55, 0xF2, 0xE7, 0xFF, 0x04, 0xEA, 0xE6, 0x13, 0xBE, + 0x40, 0xE1, 0x40, 0x45, 0x48, 0x66, 0x75, 0x31, 0xAE, 0x35, 0x64, 0x91, + 0x11, 0x6F, 0xDA, 0xEE, 0x26, 0x86, 0x45, 0x6F, 0x0B, 0xD5, 0x9F, 0x03, + 0xB1, 0x65, 0x5B, 0xDB, 0xA4, 0xE4, 0xF9, 0x45, +}; + +/* + * Offset in the scattered buffer of byte 'op_byte' (0 = least significant) + * of the operand whose region starts 'region' dwords into each 12-dword + * block (0 = exp, 4 = mod, 8 = data). Reproduces the byte layout the ACRY + * engine mandates, so the test lays out its DMA input and decodes the + * result the same way the hardware does. + */ +static int acry_operand_offset(int region, int op_byte) +{ + int byte_in_dword; + int op_dword; + int offset; + int block; + int lane; + + op_dword = op_byte / ACRY_BYTES_PER_DWORD; + byte_in_dword = op_byte % ACRY_BYTES_PER_DWORD; + block = op_dword / ACRY_LANES_PER_BLOCK; + lane = op_dword % ACRY_LANES_PER_BLOCK; + + offset = (block * ACRY_DWORDS_PER_BLOCK + region + lane) + * ACRY_BYTES_PER_DWORD + byte_in_dword; + g_assert_cmpint(offset, <, ACRY_SRAM_SIZE); + + return offset; +} + +/* + * Write a big-endian (most significant byte first) bignum of 'be_len' bytes + * into the scattered buffer region 'region' (exp, mod, or data), placing + * significance level k at acry_operand_offset(region, k). + */ +static void put_bignum_be_bytes(uint8_t *buf, int region, + const uint8_t *be, int be_len) +{ + int be_index; + int offset; + int k; + + /* be[0] (MSB) maps to the highest level; be_index walks up from 0. */ + be_index = 0; + for (k = be_len - 1; k >= 0; k--) { + offset = acry_operand_offset(region, k); + buf[offset] = be[be_index++]; + } +} + +/* Inverse of put_bignum_be_bytes(): gather a big-endian bignum back out. */ +static void get_bignum_be_bytes(const uint8_t *buf, int region, + uint8_t *out_be, int be_len) +{ + int be_index; + int offset; + int k; + + /* Inverse of put_bignum_be_bytes(): highest level -> out_be[0] (MSB). */ + be_index = 0; + for (k = be_len - 1; k >= 0; k--) { + offset = acry_operand_offset(region, k); + out_be[be_index++] = buf[offset]; + } +} + +typedef struct AspeedACRYModExp { + const char *name; + const uint8_t *n; + size_t n_len; + const uint8_t *e; + size_t e_len; + const uint8_t *m; + size_t m_len; + const uint8_t *c; + size_t c_len; +} AspeedACRYModExp; + +static const AspeedACRYModExp acry_modexp_tests[] = { + { + .name = "modexp_rsa2048", + .n = rsa2048_n, + .n_len = sizeof(rsa2048_n), + .e = rsa_e, + .e_len = sizeof(rsa_e), + .m = rsa_m, + .m_len = sizeof(rsa_m), + .c = rsa2048_c, + .c_len = sizeof(rsa2048_c), + }, + { + .name = "modexp_rsa4096", + .n = rsa4096_n, + .n_len = sizeof(rsa4096_n), + .e = rsa_e, + .e_len = sizeof(rsa_e), + .m = rsa_m, + .m_len = sizeof(rsa_m), + .c = rsa4096_c, + .c_len = sizeof(rsa4096_c), + }, +}; + +typedef struct AspeedACRYTest { + const char *machine; + uint64_t dram_addr; + uint64_t sram_addr; + uint64_t acry_addr; + int index; +} AspeedACRYTest; + +static void test_modexp_rsa(const void *opaque) +{ + const AspeedACRYTest *c = opaque; + const AspeedACRYModExp *t = &acry_modexp_tests[c->index]; + QCryptoAkCipherOptions opts = { + .alg = QCRYPTO_AK_CIPHER_ALGO_RSA, + .u.rsa.padding_alg = QCRYPTO_RSA_PADDING_ALGO_RAW, + }; + uint8_t dram_buf[ACRY_SRAM_SIZE] = { 0 }; + uint8_t sram_buf[ACRY_SRAM_SIZE] = { 0 }; + uint8_t result[ACRY_MAX_BYTES] = { 0 }; + QTestState *qts; + + if (!qcrypto_akcipher_supports(&opts)) { + g_test_skip("raw RSA not supported by the crypto backend"); + return; + } + + qts = qtest_init(c->machine); + + g_assert_cmpuint(t->c_len, <=, sizeof(result)); + + put_bignum_be_bytes(dram_buf, ACRY_EXP_OFFSET, t->e, t->e_len); + put_bignum_be_bytes(dram_buf, ACRY_MOD_OFFSET, t->n, t->n_len); + put_bignum_be_bytes(dram_buf, ACRY_DATA_OFFSET, t->m, t->m_len); + + qtest_memwrite(qts, c->dram_addr, dram_buf, sizeof(dram_buf)); + + qtest_writel(qts, c->acry_addr + ACRY_DMA_CMD, ACRY_DMA_CMD_DMEM_AHB); + qtest_writel(qts, c->acry_addr + ACRY_DMA_SRC, c->dram_addr); + qtest_writel(qts, c->acry_addr + ACRY_RSA_KEY_LEN, + ((uint32_t)(t->e_len * 8) << 16) | (uint32_t)(t->n_len * 8)); + qtest_writel(qts, c->acry_addr + ACRY_DMA_LEN, ACRY_SRAM_SIZE); + qtest_writel(qts, c->acry_addr + ACRY_INT_MASK, + ACRY_INT_MASK_RSA_ENG_MASK | ACRY_INT_MASK_RSA_DMA_MASK); + qtest_writel(qts, c->acry_addr + ACRY_DMA_CMD, ACRY_DMA_CMD_SRAM_MODE_RSA); + qtest_writel(qts, c->acry_addr + ACRY_TRIGGER, + ACRY_TRIGGER_RSA_START | ACRY_TRIGGER_RSA_DMA_DATA); + + /* Completion requires both RSA_ENG_DONE and RSA_DMA_DONE to be asserted. */ + g_assert_cmphex(qtest_readl(qts, c->acry_addr + ACRY_STATUS), ==, + ACRY_STATUS_RSA_DONE); + + qtest_memread(qts, c->sram_addr, sram_buf, sizeof(sram_buf)); + get_bignum_be_bytes(sram_buf, ACRY_DATA_OFFSET, result, t->c_len); + g_assert_cmpmem(result, t->c_len, t->c, t->c_len); + + /* Clear IRQ status and check it is deasserted */ + qtest_writel(qts, c->acry_addr + ACRY_STATUS, ACRY_STATUS_RSA_DONE); + g_assert_cmphex(qtest_readl(qts, c->acry_addr + ACRY_STATUS), ==, 0); + + qtest_quit(qts); +} + +static void aspeed_add_acry_tests(const char *prefix, const char *machine, + uint64_t acry_addr, uint64_t sram_addr, + uint64_t dram_addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(acry_modexp_tests); i++) { + g_autofree char *path = NULL; + AspeedACRYTest *t; + + path = g_strdup_printf("%s/acry/%s", prefix, + acry_modexp_tests[i].name); + t = g_new0(AspeedACRYTest, 1); + t->machine = machine; + t->acry_addr = acry_addr; + t->sram_addr = sram_addr; + t->dram_addr = dram_addr; + t->index = i; + qtest_add_data_func_full(path, t, test_modexp_rsa, g_free); + } +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + aspeed_add_acry_tests("ast2600", "-machine ast2600-evb", + 0x1e6fa000, 0x1e710000, 0x80001000); + + return g_test_run(); +} diff --git a/tests/qtest/aspeed-hace-utils.c b/tests/qtest/aspeed-hace-utils.c index 260eec043c..d20562fac9 100644 --- a/tests/qtest/aspeed-hace-utils.c +++ b/tests/qtest/aspeed-hace-utils.c @@ -1248,11 +1248,18 @@ 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); + QTestState *s; uint8_t out[64]; uint8_t iv[16]; size_t iv_off; + if (!qcrypto_cipher_supports(t->alg, t->mode)) { + g_test_skip("cipher not supported by the crypto backend"); + return; + } + + s = qtest_init(c->machine); + g_assert_cmpuint(t->len, <=, sizeof(out)); /* Encrypt: ptext -> ctext */ @@ -1284,10 +1291,17 @@ 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); + QTestState *s; uint8_t out[64]; uint8_t tag[16]; + if (!qcrypto_cipher_supports(t->alg, t->mode)) { + g_test_skip("cipher not supported by the crypto backend"); + return; + } + + s = qtest_init(c->machine); + g_assert_cmpuint(t->len, <=, sizeof(out)); /* Encrypt: ptext -> ctext, then check the authentication tag. */ @@ -1318,12 +1332,6 @@ void aspeed_add_crypto_tests(const char *prefix, const char *machine, 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); diff --git a/tests/qtest/aspeed-sbc-test.c b/tests/qtest/aspeed-sbc-test.c new file mode 100644 index 0000000000..c445dac47a --- /dev/null +++ b/tests/qtest/aspeed-sbc-test.c @@ -0,0 +1,194 @@ +/* + * QTest testcase for the ASPEED Secure Boot Controller (SBC) + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/bitops.h" +#include "crypto/akcipher.h" + +/* SBC register block */ +#define SBC_STATUS 0x014 +#define SBC_ECDSA_VERIFY_PASS BIT(21) +#define SBC_ECDSA_VERIFY_DONE BIT(20) +#define SBC_SEC_TRIGGER 0x0bc +#define SBC_ECDSA_CMD_TRIGGER BIT(1) + +/* + * SEC SRAM operand offsets for a secp384r1 ECDSA verify. Every operand is a + * 48-byte big-endian integer. + */ +#define ECDSA_SRAM_QX 0x2080 +#define ECDSA_SRAM_QY 0x20c0 +#define ECDSA_SRAM_R 0x21c0 +#define ECDSA_SRAM_S 0x2200 +#define ECDSA_SRAM_M 0x2240 + +/* + * ECDSA secp384r1 / SHA-384 known-answer vector from the Linux kernel crypto + * self-test manager (ecdsa_nist_p384_tv_template, sha384 entry in + * crypto/testmgr.h, v6.18), decoded into raw big-endian form: public key + * Qx || Qy, signature r || s and the SHA-384 message digest. + * + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/crypto/testmgr.h?h=v6.18 + */ +static const uint8_t ecdsa_p384_pubkey[96] = { + /* Qx */ + 0x3a, 0x2f, 0x62, 0xe7, 0x1a, 0xcf, 0x24, 0xd0, + 0x0b, 0x7c, 0xe0, 0xed, 0x46, 0x0a, 0x4f, 0x74, + 0x16, 0x43, 0xe9, 0x1a, 0x25, 0x7c, 0x55, 0xff, + 0xf0, 0x29, 0x68, 0x66, 0x20, 0x91, 0xf9, 0xdb, + 0x2b, 0xf6, 0xb3, 0x6c, 0x54, 0x01, 0xca, 0xc7, + 0x6a, 0x5c, 0x0d, 0xeb, 0x68, 0xd9, 0x3c, 0xf1, + /* Qy */ + 0x01, 0x74, 0x1f, 0xf9, 0x6c, 0xe5, 0x5b, 0x60, + 0xe9, 0x7f, 0x5d, 0xb3, 0x12, 0x80, 0x2a, 0xd8, + 0x67, 0x92, 0xc9, 0x0e, 0x4c, 0x4c, 0x6b, 0xa1, + 0xb2, 0xa8, 0x1e, 0xac, 0x1c, 0x97, 0xd9, 0x21, + 0x67, 0xe5, 0x1b, 0x5a, 0x52, 0x31, 0x68, 0xd6, + 0xee, 0xf0, 0x19, 0xb0, 0x55, 0xed, 0x89, 0x9e, +}; + +static const uint8_t ecdsa_p384_signature[96] = { + /* r */ + 0x9b, 0x28, 0x68, 0xc0, 0xa1, 0xea, 0x8c, 0x50, + 0xee, 0x2e, 0x62, 0x35, 0x46, 0xfa, 0x00, 0xd8, + 0x2d, 0x7a, 0x91, 0x5f, 0x49, 0x2d, 0x22, 0x08, + 0x29, 0xe6, 0xfb, 0xca, 0x8c, 0xd6, 0xb6, 0xb4, + 0x3b, 0x1f, 0x07, 0x8f, 0x15, 0x02, 0xfe, 0x1d, + 0xa2, 0xa4, 0xc8, 0xf2, 0xea, 0x9d, 0x11, 0x1f, + /* s */ + 0xfc, 0x50, 0xf6, 0x43, 0xbd, 0x50, 0x82, 0x0e, + 0xbf, 0xe3, 0x75, 0x24, 0x49, 0xac, 0xfb, 0xc8, + 0x71, 0xcd, 0x8f, 0x18, 0x99, 0xf0, 0x0f, 0x13, + 0x44, 0x92, 0x8c, 0x86, 0x99, 0x65, 0xb3, 0x97, + 0x96, 0x17, 0x04, 0xc9, 0x05, 0x77, 0xf1, 0x8e, + 0xab, 0x8d, 0x4e, 0xde, 0xe6, 0x6d, 0x9b, 0x66, +}; + +static const uint8_t ecdsa_p384_dgst[48] = { + 0x8d, 0xf2, 0xc0, 0xe9, 0xa8, 0xf3, 0x8e, 0x44, + 0xc4, 0x8c, 0x1a, 0xa0, 0xb8, 0xd7, 0x17, 0xdf, + 0xf2, 0x37, 0x1b, 0xc6, 0xe3, 0xf5, 0x62, 0xcc, + 0x68, 0xf5, 0xd5, 0x0b, 0xbf, 0x73, 0x2b, 0xb1, + 0xb0, 0x4c, 0x04, 0x00, 0x31, 0xab, 0xfe, 0xc8, + 0xd6, 0x09, 0xc8, 0xf2, 0xea, 0xd3, 0x28, 0xff, +}; + +typedef struct AspeedSBCECDSA { + const char *name; + QCryptoCurveID curve_id; + const uint8_t *pubkey; + const uint8_t *signature; + const uint8_t *dgst; + size_t coord_len; +} AspeedSBCECDSA; + +static const AspeedSBCECDSA sbc_ecdsa_tests[] = { + { + .name = "secp384r1", + .curve_id = QCRYPTO_CURVE_ID_SECP384R1, + .pubkey = ecdsa_p384_pubkey, + .signature = ecdsa_p384_signature, + .dgst = ecdsa_p384_dgst, + .coord_len = 48, + }, +}; + +typedef struct AspeedSBCTest { + const char *machine; + uint32_t sec_addr; + uint64_t sram_addr; + int index; +} AspeedSBCTest; + +static void sbc_ecdsa_stage(QTestState *qts, const AspeedSBCTest *c, + const AspeedSBCECDSA *t) +{ + uint32_t len = t->coord_len; + + qtest_memwrite(qts, c->sram_addr + ECDSA_SRAM_QX, t->pubkey, len); + qtest_memwrite(qts, c->sram_addr + ECDSA_SRAM_QY, t->pubkey + len, len); + qtest_memwrite(qts, c->sram_addr + ECDSA_SRAM_R, t->signature, len); + qtest_memwrite(qts, c->sram_addr + ECDSA_SRAM_S, t->signature + len, len); + qtest_memwrite(qts, c->sram_addr + ECDSA_SRAM_M, t->dgst, len); +} + +/* + * Drive one ECDSA verify through the engine the way the firmware does: + * stage the operands in the SEC SRAM, trigger the command register and read + * back the done/pass bits in the status register. + */ +static void test_ecdsa_verify(const void *opaque) +{ + const AspeedSBCTest *c = opaque; + const AspeedSBCECDSA *t = &sbc_ecdsa_tests[c->index]; + QCryptoAkCipherOptions opts = { + .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA, + .u.ecdsa.curve_id = t->curve_id, + }; + QTestState *qts; + uint32_t status; + uint8_t bad; + + if (!qcrypto_akcipher_supports(&opts)) { + g_test_skip("ECDSA is not supported by the crypto backend"); + return; + } + + qts = qtest_init(c->machine); + + /* A valid signature verifies */ + sbc_ecdsa_stage(qts, c, t); + qtest_writel(qts, c->sec_addr + SBC_SEC_TRIGGER, SBC_ECDSA_CMD_TRIGGER); + status = qtest_readl(qts, c->sec_addr + SBC_STATUS); + g_assert_cmphex(status & (SBC_ECDSA_VERIFY_DONE | SBC_ECDSA_VERIFY_PASS), + ==, SBC_ECDSA_VERIFY_DONE | SBC_ECDSA_VERIFY_PASS); + + /* A tampered signature must fail */ + bad = t->signature[0] ^ 0xff; + qtest_memwrite(qts, c->sram_addr + ECDSA_SRAM_R, &bad, 1); + qtest_writel(qts, c->sec_addr + SBC_SEC_TRIGGER, SBC_ECDSA_CMD_TRIGGER); + status = qtest_readl(qts, c->sec_addr + SBC_STATUS); + g_assert_cmphex(status & SBC_ECDSA_VERIFY_DONE, ==, SBC_ECDSA_VERIFY_DONE); + g_assert_cmphex(status & SBC_ECDSA_VERIFY_PASS, ==, 0); + + qtest_quit(qts); +} + +static void aspeed_add_sbc_ecdsa_tests(const char *prefix, const char *machine, + uint32_t sec_addr, uint64_t sram_addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sbc_ecdsa_tests); i++) { + g_autofree char *path = NULL; + AspeedSBCTest *t; + + path = g_strdup_printf("%s/sbc/ecdsa/%s", prefix, + sbc_ecdsa_tests[i].name); + t = g_new0(AspeedSBCTest, 1); + t->machine = machine; + t->sec_addr = sec_addr; + t->sram_addr = sram_addr; + t->index = i; + qtest_add_data_func_full(path, t, test_ecdsa_verify, g_free); + } +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + aspeed_add_sbc_ecdsa_tests("ast1030", "-machine ast1030-evb", + 0x7e6f2000, 0x79000000); + + aspeed_add_sbc_ecdsa_tests("ast1060", "-machine ast1060-evb", + 0x7e6f2000, 0x79000000); + + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 17870e339b..c3593f7530 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -230,8 +230,10 @@ qtests_npcm7xx = \ qtests_npcm8xx = \ ['npcm_gmac-test'] qtests_aspeed = \ - ['aspeed_gpio-test', + ['aspeed-acry-test', + 'aspeed_gpio-test', 'aspeed_hace-test', + 'aspeed-sbc-test', 'aspeed_scu-test', 'aspeed_smc-test'] qtests_aspeed64 = \ @@ -402,8 +404,11 @@ if get_option('replication').allowed() endif qtests = { + 'aspeed-acry-test': [files('aspeed-acry-test.c'), + crypto], 'aspeed_hace-test': [files('aspeed-hace-utils.c', 'aspeed_hace-test.c'), crypto], + 'aspeed-sbc-test': [files('aspeed-sbc-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'), crypto], diff --git a/tests/qtest/tmp105-test.c b/tests/qtest/tmp105-test.c index 3b114a50f5..504f6b7202 100644 --- a/tests/qtest/tmp105-test.c +++ b/tests/qtest/tmp105-test.c @@ -17,6 +17,24 @@ #define TMP105_TEST_ID "tmp105-test" #define TMP105_TEST_ADDR 0x49 +#define TMP105_TEST_PATH "/machine/peripheral/" TMP105_TEST_ID + +#define TMP75_TEST_ID "tmp75-test" +#define TMP75_TEST_PATH "/machine/peripheral/" TMP75_TEST_ID + +#define TMP175_TEST_ID "tmp175-test" +#define TMP175_TEST_PATH "/machine/peripheral/" TMP175_TEST_ID + +#define LM75B_TEST_ID "lm75b-test" +#define LM75B_TEST_PATH "/machine/peripheral/" LM75B_TEST_ID + +#define TMP105_CONFIG_POL (1 << 2) /* ALERT active-high when set */ +#define TMP105_CONFIG_TM (1 << 1) /* interrupt (thermostat) mode */ +#define TMP105_CONFIG_FQ_1 (0 << 3) /* fault queue: 1 consecutive fault */ +#define TMP105_CONFIG_FQ_4 (2 << 3) /* fault queue: 4 consecutive faults */ +#define TMP105_CONFIG_FQ(f) ((f) << 3) /* raw F1:F0 fault-queue field value */ +#define TMP105_CONFIG_SD (1 << 0) /* shutdown mode */ +#define TMP105_CONFIG_OS (1 << 7) /* one-shot conversion */ static int qmp_tmp105_get_temperature(const char *id) { @@ -105,6 +123,302 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc) g_assert_cmphex(i2c_get16(i2cdev, TMP105_REG_T_HIGH), ==, 0x4230); } +/* + * The TMP105 exposes its alarm state only through the ALERT pin. + */ +static void test_alert_single_fault(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + + qtest_irq_intercept_out(global_qtest, TMP105_TEST_PATH); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | TMP105_CONFIG_FQ_1); + g_assert_false(get_irq(0)); + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_true(get_irq(0)); + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 70000); + g_assert_false(get_irq(0)); +} + +static void test_fault_queue(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + int i; + + qtest_irq_intercept_out(global_qtest, TMP105_TEST_PATH); + + /* Comparator mode, active-high ALERT, fault queue of four. */ + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | TMP105_CONFIG_FQ_4); + g_assert_false(get_irq(0)); + + for (i = 0; i < 3; i++) { + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_false(get_irq(0)); + } + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 25000); + g_assert_false(get_irq(0)); + + for (i = 0; i < 3; i++) { + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_false(get_irq(0)); + } + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_true(get_irq(0)); + + for (i = 0; i < 3; i++) { + qmp_tmp105_set_temperature(TMP105_TEST_ID, 70000); + g_assert_true(get_irq(0)); + } + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 70000); + g_assert_false(get_irq(0)); +} + +/* + * Drive @need consecutive over-limit conversions and check that the ALERT pin + * only asserts on the last one. This exercises the fault-queue length. + */ +static void check_fault_queue(QI2CDevice *i2cdev, const char *id, + const char *path, uint8_t fq_field, int need) +{ + int i; + + qtest_irq_intercept_out(global_qtest, path); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ(fq_field)); + g_assert_false(get_irq(0)); + + for (i = 0; i < need - 1; i++) { + qmp_tmp105_set_temperature(id, 85000); + g_assert_false(get_irq(0)); + } + qmp_tmp105_set_temperature(id, 85000); + g_assert_true(get_irq(0)); +} + +/* + * The one-shot (OS) bit starts a conversion only in shutdown mode. In + * continuous mode it is ignored, so writing it must not advance the fault + * queue; in shutdown each OS write performs one conversion that does. + */ +static void test_one_shot(void *obj, void *data, QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + int i; + + qtest_irq_intercept_out(global_qtest, TMP105_TEST_PATH); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | TMP105_CONFIG_FQ_4); + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_false(get_irq(0)); + + for (i = 0; i < 8; i++) { + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ_4 | TMP105_CONFIG_OS); + g_assert_false(get_irq(0)); + } + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ_4 | TMP105_CONFIG_SD); + for (i = 0; i < 2; i++) { + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | + TMP105_CONFIG_FQ_4 | TMP105_CONFIG_SD | TMP105_CONFIG_OS); + g_assert_false(get_irq(0)); + } + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | + TMP105_CONFIG_FQ_4 | TMP105_CONFIG_SD | TMP105_CONFIG_OS); + g_assert_true(get_irq(0)); +} + +/* + * Configuration and limit-register writes are not conversions and must not + * advance the fault queue. + */ +static void test_fault_queue_ignores_writes(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + int i; + + qtest_irq_intercept_out(global_qtest, TMP105_TEST_PATH); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | TMP105_CONFIG_FQ_4); + g_assert_false(get_irq(0)); + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_false(get_irq(0)); + + for (i = 0; i < 8; i++) { + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ_4); + i2c_set16(i2cdev, TMP105_REG_T_HIGH, 0x5000); + i2c_set16(i2cdev, TMP105_REG_T_LOW, 0x4b00); + g_assert_false(get_irq(0)); + } + + for (i = 0; i < 2; i++) { + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_false(get_irq(0)); + } + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_true(get_irq(0)); +} + +/* + * Leaving shutdown (SD 1->0) resumes continuous conversion, which must + * re-evaluate the current temperature against the limits. + */ +static void test_wake_from_shutdown(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + + qtest_irq_intercept_out(global_qtest, TMP105_TEST_PATH); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | TMP105_CONFIG_FQ_1); + g_assert_false(get_irq(0)); + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 85000); + g_assert_true(get_irq(0)); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ_1 | TMP105_CONFIG_SD); + g_assert_true(get_irq(0)); + + qmp_tmp105_set_temperature(TMP105_TEST_ID, 70000); + g_assert_true(get_irq(0)); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | TMP105_CONFIG_FQ_1); + g_assert_false(get_irq(0)); +} + +/* The TMP75 maps F1:F0 = 10b to 3 consecutive faults. */ +static void test_tmp75_fault_queue(void *obj, void *data, + QGuestAllocator *alloc) +{ + check_fault_queue(obj, TMP75_TEST_ID, TMP75_TEST_PATH, 2, 3); +} + +/* The TMP175 keeps the TMP105 mapping: F1:F0 = 10b means 4 faults. */ +static void test_tmp175_fault_queue(void *obj, void *data, + QGuestAllocator *alloc) +{ + check_fault_queue(obj, TMP175_TEST_ID, TMP175_TEST_PATH, 2, 4); +} + +/* + * Toggling the thermostat mode (TM) bit clears any active alert on the TMP75. + */ +static void test_tmp75_tm_clears_alert(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + int i; + + qtest_irq_intercept_out(global_qtest, TMP75_TEST_PATH); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ(3)); + for (i = 0; i < 4; i++) { + qmp_tmp105_set_temperature(TMP75_TEST_ID, 85000); + } + g_assert_true(get_irq(0)); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ(3) | TMP105_CONFIG_TM); + g_assert_false(get_irq(0)); +} + +/* + * Entering shutdown clears the ALERT in interrupt mode but leaves it asserted + * in comparator mode. + */ +static void test_tmp75_shutdown_clears_alert(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + int i; + + qtest_irq_intercept_out(global_qtest, TMP75_TEST_PATH); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_TM | TMP105_CONFIG_FQ(3)); + for (i = 0; i < 4; i++) { + qmp_tmp105_set_temperature(TMP75_TEST_ID, 85000); + } + g_assert_true(get_irq(0)); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_TM | TMP105_CONFIG_FQ(3) | + TMP105_CONFIG_SD); + g_assert_false(get_irq(0)); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ(3)); + for (i = 0; i < 4; i++) { + qmp_tmp105_set_temperature(TMP75_TEST_ID, 85000); + } + g_assert_true(get_irq(0)); + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_FQ(3) | TMP105_CONFIG_SD); + g_assert_true(get_irq(0)); +} + +/* + * The LM75B has a fixed 11-bit (0.125 C) converter: the resolution and one-shot + * Config bits are reserved (read/write as zero) and the temperature register is + * always masked to 11 bits regardless of what is written to Config. + */ +static void test_lm75b_resolution(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + uint16_t value; + + i2c_set8(i2cdev, TMP105_REG_CONFIG, 0x60); + g_assert_cmphex(i2c_get8(i2cdev, TMP105_REG_CONFIG), ==, 0x00); + + qmp_tmp105_set_temperature(LM75B_TEST_ID, 20938); + value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE); + g_assert_cmphex(value, ==, 0x14e0); +} + +/* The LM75B set-point registers store only 9 bits. */ +static void test_lm75b_limits(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + + i2c_set16(i2cdev, TMP105_REG_T_HIGH, 0x4231); + g_assert_cmphex(i2c_get16(i2cdev, TMP105_REG_T_HIGH), ==, 0x4200); + + i2c_set16(i2cdev, TMP105_REG_T_LOW, 0x12b4); + g_assert_cmphex(i2c_get16(i2cdev, TMP105_REG_T_LOW), ==, 0x1280); +} + +/* The LM75B likewise resets its OS output on shutdown in interrupt mode. */ +static void test_lm75b_shutdown_clears_alert(void *obj, void *data, + QGuestAllocator *alloc) +{ + QI2CDevice *i2cdev = (QI2CDevice *)obj; + + qtest_irq_intercept_out(global_qtest, LM75B_TEST_PATH); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, TMP105_CONFIG_POL | TMP105_CONFIG_TM); + qmp_tmp105_set_temperature(LM75B_TEST_ID, 85000); + g_assert_true(get_irq(0)); + + i2c_set8(i2cdev, TMP105_REG_CONFIG, + TMP105_CONFIG_POL | TMP105_CONFIG_TM | TMP105_CONFIG_SD); + g_assert_false(get_irq(0)); +} + static void tmp105_register_nodes(void) { QOSGraphEdgeOptions opts = { @@ -116,5 +430,50 @@ static void tmp105_register_nodes(void) qos_node_consumes("tmp105", "i2c-bus", &opts); qos_add_test("tx-rx", "tmp105", send_and_receive, NULL); + qos_add_test("alert-single-fault", "tmp105", test_alert_single_fault, NULL); + qos_add_test("fault-queue", "tmp105", test_fault_queue, NULL); + qos_add_test("fault-queue-ignores-writes", "tmp105", + test_fault_queue_ignores_writes, NULL); + qos_add_test("one-shot", "tmp105", test_one_shot, NULL); + qos_add_test("wake-from-shutdown", "tmp105", test_wake_from_shutdown, NULL); + + /* TMP75: register-compatible, but with a 1/2/3/4 fault queue. */ + QOSGraphEdgeOptions tmp75_opts = { + .extra_device_opts = "id=" TMP75_TEST_ID ",address=0x48" + }; + add_qi2c_address(&tmp75_opts, &(QI2CAddress) { 0x48 }); + + qos_node_create_driver("tmp75", i2c_device_create); + qos_node_consumes("tmp75", "i2c-bus", &tmp75_opts); + + qos_add_test("fault-queue", "tmp75", test_tmp75_fault_queue, NULL); + qos_add_test("tm-clears-alert", "tmp75", test_tmp75_tm_clears_alert, NULL); + qos_add_test("shutdown-clears-alert", "tmp75", + test_tmp75_shutdown_clears_alert, NULL); + + /* TMP175: like the TMP105, with a 1/2/4/6 fault queue. */ + QOSGraphEdgeOptions tmp175_opts = { + .extra_device_opts = "id=" TMP175_TEST_ID ",address=0x4a" + }; + add_qi2c_address(&tmp175_opts, &(QI2CAddress) { 0x4a }); + + qos_node_create_driver("tmp175", i2c_device_create); + qos_node_consumes("tmp175", "i2c-bus", &tmp175_opts); + + qos_add_test("fault-queue", "tmp175", test_tmp175_fault_queue, NULL); + + /* LM75B: fixed 11-bit conversion and 9-bit set-point registers. */ + QOSGraphEdgeOptions lm75b_opts = { + .extra_device_opts = "id=" LM75B_TEST_ID ",address=0x4c" + }; + add_qi2c_address(&lm75b_opts, &(QI2CAddress) { 0x4c }); + + qos_node_create_driver("lm75b", i2c_device_create); + qos_node_consumes("lm75b", "i2c-bus", &lm75b_opts); + + qos_add_test("resolution", "lm75b", test_lm75b_resolution, NULL); + qos_add_test("limits", "lm75b", test_lm75b_limits, NULL); + qos_add_test("shutdown-clears-alert", "lm75b", + test_lm75b_shutdown_clears_alert, NULL); } libqos_init(tmp105_register_nodes); diff --git a/tests/unit/test-crypto-akcipher.c b/tests/unit/test-crypto-akcipher.c index 53c2211ba8..fb1a6163e9 100644 --- a/tests/unit/test-crypto-akcipher.c +++ b/tests/unit/test-crypto-akcipher.c @@ -658,6 +658,117 @@ static const uint8_t rsa_public_key_extra_elem[] = { 0x02, 0x06, 0xe1, 0x22, 0xdb, 0xe1, 0x22, 0xdb, }; +/* + * ECDSA known-answer vectors from RFC 6979 (Deterministic ECDSA), message + * "sample", decoded into raw big-endian form. The same key pair drives both + * the verify test (against the RFC's known signature) and the sign round-trip + * test: + * - private key: the scalar d (curve size) + * - public key: Qx || Qy (2 * curve size) + * - dgst: the message digest (curve size) + * - signature: r || s (2 * curve size) + * + * prime256v1 / SHA-256 (RFC 6979 A.2.5): + */ +static const uint8_t ecdsa_p256_privkey[32] = { + 0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16, + 0x6b, 0x5c, 0x21, 0x57, 0x67, 0xb1, 0xd6, 0x93, + 0x4e, 0x50, 0xc3, 0xdb, 0x36, 0xe8, 0x9b, 0x12, + 0x7b, 0x8a, 0x62, 0x2b, 0x12, 0x0f, 0x67, 0x21, +}; + +static const uint8_t ecdsa_p256_pubkey[64] = { + 0x60, 0xfe, 0xd4, 0xba, 0x25, 0x5a, 0x9d, 0x31, + 0xc9, 0x61, 0xeb, 0x74, 0xc6, 0x35, 0x6d, 0x68, + 0xc0, 0x49, 0xb8, 0x92, 0x3b, 0x61, 0xfa, 0x6c, + 0xe6, 0x69, 0x62, 0x2e, 0x60, 0xf2, 0x9f, 0xb6, + 0x79, 0x03, 0xfe, 0x10, 0x08, 0xb8, 0xbc, 0x99, + 0xa4, 0x1a, 0xe9, 0xe9, 0x56, 0x28, 0xbc, 0x64, + 0xf2, 0xf1, 0xb2, 0x0c, 0x2d, 0x7e, 0x9f, 0x51, + 0x77, 0xa3, 0xc2, 0x94, 0xd4, 0x46, 0x22, 0x99, +}; + +static const uint8_t ecdsa_p256_dgst[32] = { + 0xaf, 0x2b, 0xdb, 0xe1, 0xaa, 0x9b, 0x6e, 0xc1, + 0xe2, 0xad, 0xe1, 0xd6, 0x94, 0xf4, 0x1f, 0xc7, + 0x1a, 0x83, 0x1d, 0x02, 0x68, 0xe9, 0x89, 0x15, + 0x62, 0x11, 0x3d, 0x8a, 0x62, 0xad, 0xd1, 0xbf, +}; + +static const uint8_t ecdsa_p256_signature[64] = { + 0xef, 0xd4, 0x8b, 0x2a, 0xac, 0xb6, 0xa8, 0xfd, + 0x11, 0x40, 0xdd, 0x9c, 0xd4, 0x5e, 0x81, 0xd6, + 0x9d, 0x2c, 0x87, 0x7b, 0x56, 0xaa, 0xf9, 0x91, + 0xc3, 0x4d, 0x0e, 0xa8, 0x4e, 0xaf, 0x37, 0x16, + 0xf7, 0xcb, 0x1c, 0x94, 0x2d, 0x65, 0x7c, 0x41, + 0xd4, 0x36, 0xc7, 0xa1, 0xb6, 0xe2, 0x9f, 0x65, + 0xf3, 0xe9, 0x00, 0xdb, 0xb9, 0xaf, 0xf4, 0x06, + 0x4d, 0xc4, 0xab, 0x2f, 0x84, 0x3a, 0xcd, 0xa8, +}; + +/* secp384r1 / SHA-384 (RFC 6979 A.2.6): */ +static const uint8_t ecdsa_p384_privkey[48] = { + 0x6b, 0x9d, 0x3d, 0xad, 0x2e, 0x1b, 0x8c, 0x1c, + 0x05, 0xb1, 0x98, 0x75, 0xb6, 0x65, 0x9f, 0x4d, + 0xe2, 0x3c, 0x3b, 0x66, 0x7b, 0xf2, 0x97, 0xba, + 0x9a, 0xa4, 0x77, 0x40, 0x78, 0x71, 0x37, 0xd8, + 0x96, 0xd5, 0x72, 0x4e, 0x4c, 0x70, 0xa8, 0x25, + 0xf8, 0x72, 0xc9, 0xea, 0x60, 0xd2, 0xed, 0xf5, +}; + +static const uint8_t ecdsa_p384_pubkey[96] = { + 0xec, 0x3a, 0x4e, 0x41, 0x5b, 0x4e, 0x19, 0xa4, + 0x56, 0x86, 0x18, 0x02, 0x9f, 0x42, 0x7f, 0xa5, + 0xda, 0x9a, 0x8b, 0xc4, 0xae, 0x92, 0xe0, 0x2e, + 0x06, 0xaa, 0xe5, 0x28, 0x6b, 0x30, 0x0c, 0x64, + 0xde, 0xf8, 0xf0, 0xea, 0x90, 0x55, 0x86, 0x60, + 0x64, 0xa2, 0x54, 0x51, 0x54, 0x80, 0xbc, 0x13, + 0x80, 0x15, 0xd9, 0xb7, 0x2d, 0x7d, 0x57, 0x24, + 0x4e, 0xa8, 0xef, 0x9a, 0xc0, 0xc6, 0x21, 0x89, + 0x67, 0x08, 0xa5, 0x93, 0x67, 0xf9, 0xdf, 0xb9, + 0xf5, 0x4c, 0xa8, 0x4b, 0x3f, 0x1c, 0x9d, 0xb1, + 0x28, 0x8b, 0x23, 0x1c, 0x3a, 0xe0, 0xd4, 0xfe, + 0x73, 0x44, 0xfd, 0x25, 0x33, 0x26, 0x47, 0x20, +}; + +static const uint8_t ecdsa_p384_dgst[48] = { + 0x9a, 0x90, 0x83, 0x50, 0x5b, 0xc9, 0x22, 0x76, + 0xae, 0xc4, 0xbe, 0x31, 0x26, 0x96, 0xef, 0x7b, + 0xf3, 0xbf, 0x60, 0x3f, 0x4b, 0xbd, 0x38, 0x11, + 0x96, 0xa0, 0x29, 0xf3, 0x40, 0x58, 0x53, 0x12, + 0x31, 0x3b, 0xca, 0x4a, 0x9b, 0x5b, 0x89, 0x0e, + 0xfe, 0xe4, 0x2c, 0x77, 0xb1, 0xee, 0x25, 0xfe, +}; + +static const uint8_t ecdsa_p384_signature[96] = { + 0x94, 0xed, 0xbb, 0x92, 0xa5, 0xec, 0xb8, 0xaa, + 0xd4, 0x73, 0x6e, 0x56, 0xc6, 0x91, 0x91, 0x6b, + 0x3f, 0x88, 0x14, 0x06, 0x66, 0xce, 0x9f, 0xa7, + 0x3d, 0x64, 0xc4, 0xea, 0x95, 0xad, 0x13, 0x3c, + 0x81, 0xa6, 0x48, 0x15, 0x2e, 0x44, 0xac, 0xf9, + 0x6e, 0x36, 0xdd, 0x1e, 0x80, 0xfa, 0xbe, 0x46, + 0x99, 0xef, 0x4a, 0xeb, 0x15, 0xf1, 0x78, 0xce, + 0xa1, 0xfe, 0x40, 0xdb, 0x26, 0x03, 0x13, 0x8f, + 0x13, 0x0e, 0x74, 0x0a, 0x19, 0x62, 0x45, 0x26, + 0x20, 0x3b, 0x63, 0x51, 0xd0, 0xa3, 0xa9, 0x4f, + 0xa3, 0x29, 0xc1, 0x45, 0x78, 0x6e, 0x67, 0x9e, + 0x7b, 0x82, 0xc7, 0x1a, 0x38, 0x62, 0x8a, 0xc8, +}; + +typedef struct QCryptoECDSATestData QCryptoECDSATestData; +struct QCryptoECDSATestData { + const char *path; + QCryptoCurveID curve_id; + const uint8_t *priv_key; + size_t priv_key_len; + const uint8_t *pub_key; + size_t pub_key_len; + const uint8_t *dgst; + size_t dlen; + const uint8_t *signature; + size_t slen; +}; + typedef struct QCryptoRSAKeyTestData QCryptoRSAKeyTestData; struct QCryptoRSAKeyTestData { const char *path; @@ -969,6 +1080,120 @@ static void test_rsakey(const void *opaque) g_assert(qcrypto_akcipher_max_dgst_len(key) == data->exp_key_len); } +static QCryptoECDSATestData ecdsa_test_data[] = { + { + .path = "/crypto/akcipher/ecdsa-prime256v1", + .curve_id = QCRYPTO_CURVE_ID_PRIME256V1, + .priv_key = ecdsa_p256_privkey, + .priv_key_len = sizeof(ecdsa_p256_privkey), + .pub_key = ecdsa_p256_pubkey, + .pub_key_len = sizeof(ecdsa_p256_pubkey), + .dgst = ecdsa_p256_dgst, + .dlen = sizeof(ecdsa_p256_dgst), + .signature = ecdsa_p256_signature, + .slen = sizeof(ecdsa_p256_signature), + }, + { + .path = "/crypto/akcipher/ecdsa-secp384r1", + .curve_id = QCRYPTO_CURVE_ID_SECP384R1, + .priv_key = ecdsa_p384_privkey, + .priv_key_len = sizeof(ecdsa_p384_privkey), + .pub_key = ecdsa_p384_pubkey, + .pub_key_len = sizeof(ecdsa_p384_pubkey), + .dgst = ecdsa_p384_dgst, + .dlen = sizeof(ecdsa_p384_dgst), + .signature = ecdsa_p384_signature, + .slen = sizeof(ecdsa_p384_signature), + }, + /* Add more curves here as they gain backend support. */ +}; + +static void test_ecdsa_verify(const void *opaque) +{ + const QCryptoECDSATestData *data = opaque; + QCryptoAkCipherOptions opt = { + .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA, + .u.ecdsa = { + .curve_id = data->curve_id, + }, + }; + g_autoptr(QCryptoAkCipher) pub_key = NULL; + g_autofree uint8_t *signature = NULL; + g_autofree uint8_t *dgst = NULL; + + if (!qcrypto_akcipher_supports(&opt)) { + g_test_skip("ECDSA is not supported by the crypto backend"); + return; + } + + pub_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC, + data->pub_key, data->pub_key_len, + &error_abort); + g_assert(pub_key != NULL); + g_assert(qcrypto_akcipher_max_signature_len(pub_key) == data->slen); + g_assert(qcrypto_akcipher_max_dgst_len(pub_key) == data->dlen); + + /* A valid signature must verify. */ + g_assert(qcrypto_akcipher_verify(pub_key, data->signature, data->slen, + data->dgst, data->dlen, + &error_abort) == 0); + + /* A tampered signature must fail (error is expected, so ignore it). */ + signature = g_memdup2(data->signature, data->slen); + signature[0]++; + g_assert(qcrypto_akcipher_verify(pub_key, signature, data->slen, + data->dgst, data->dlen, NULL) != 0); + + /* A tampered digest must also fail. */ + dgst = g_memdup2(data->dgst, data->dlen); + dgst[0]++; + g_assert(qcrypto_akcipher_verify(pub_key, data->signature, data->slen, + dgst, data->dlen, NULL) != 0); +} + +static void test_ecdsa_sign(const void *opaque) +{ + const QCryptoECDSATestData *data = opaque; + QCryptoAkCipherOptions opt = { + .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA, + .u.ecdsa = { + .curve_id = data->curve_id, + }, + }; + g_autoptr(QCryptoAkCipher) priv_key = NULL; + g_autoptr(QCryptoAkCipher) pub_key = NULL; + g_autofree uint8_t *signature = NULL; + int slen; + + if (!qcrypto_akcipher_supports(&opt)) { + g_test_skip("ECDSA is not supported by the crypto backend"); + return; + } + + priv_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE, + data->priv_key, data->priv_key_len, + &error_abort); + g_assert(priv_key != NULL); + + /* + * Sign the digest. + * ECDSA signatures are randomized so only length is fixed. + */ + slen = qcrypto_akcipher_max_signature_len(priv_key); + signature = g_new0(uint8_t, slen); + g_assert(qcrypto_akcipher_sign(priv_key, data->dgst, data->dlen, + signature, slen, &error_abort) == slen); + + /* The freshly produced signature must verify with the public key. */ + pub_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC, + data->pub_key, data->pub_key_len, + &error_abort); + g_assert(pub_key != NULL); + g_assert(qcrypto_akcipher_verify(pub_key, signature, slen, + data->dgst, data->dlen, + &error_abort) == 0); +} + int main(int argc, char **argv) { size_t i; @@ -985,6 +1210,17 @@ int main(int argc, char **argv) &rsakey_test_data[i], test_rsakey); } + for (i = 0; i < G_N_ELEMENTS(ecdsa_test_data); i++) { + g_autofree char *verify_path = + g_strdup_printf("%s-verify", ecdsa_test_data[i].path); + g_autofree char *sign_path = + g_strdup_printf("%s-sign", ecdsa_test_data[i].path); + + g_test_add_data_func(verify_path, &ecdsa_test_data[i], + test_ecdsa_verify); + g_test_add_data_func(sign_path, &ecdsa_test_data[i], + test_ecdsa_sign); + } return g_test_run(); } |
