summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2026-09-07 13:33:12 +0100
committerPeter Maydell <peter.maydell@linaro.org>2026-09-07 13:33:12 +0100
commit35500e5c41aec76cde59befe750600dac7a9e37a (patch)
tree108af14ab7700f9c29bc0b7ba8f513fbc5bace8a
parent61e537b7ef2f8b899827c72126cada6161e5803d (diff)
parent1c05d7e94da04129ea16147074fe73158fb85ff9 (diff)
downloadqemu-staging.tar.gz
qemu-staging.zip
Merge tag 'pull-aspeed-20260906' of https://github.com/legoater/qemu into stagingHEADstagingmaster
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>
-rw-r--r--crypto/akcipher-gcrypt.c.inc340
-rw-r--r--crypto/akcipher-nettle.c.inc272
-rw-r--r--hw/arm/Kconfig1
-rw-r--r--hw/arm/aspeed_ast10x0.c14
-rw-r--r--hw/arm/aspeed_ast2600.c71
-rw-r--r--hw/arm/aspeed_ast2600_catalina.c1
-rw-r--r--hw/arm/aspeed_ast2600_fuji.c16
-rw-r--r--hw/arm/aspeed_ast2600_sanmiguel.c403
-rw-r--r--hw/arm/meson.build1
-rw-r--r--hw/i2c/aspeed_i2c.c38
-rw-r--r--hw/misc/aspeed_acry.c481
-rw-r--r--hw/misc/aspeed_sbc.c158
-rw-r--r--hw/misc/aspeed_scu.c21
-rw-r--r--hw/misc/meson.build1
-rw-r--r--hw/misc/trace-events8
-rw-r--r--hw/sensor/tmp105.c300
-rw-r--r--hw/usb/Kconfig4
-rw-r--r--hw/usb/aspeed-udc.c1058
-rw-r--r--hw/usb/hcd-ehci.c90
-rw-r--r--hw/usb/meson.build1
-rw-r--r--hw/usb/trace-events16
-rw-r--r--include/hw/arm/aspeed_soc.h9
-rw-r--r--include/hw/i2c/aspeed_i2c.h3
-rw-r--r--include/hw/misc/aspeed_acry.h39
-rw-r--r--include/hw/misc/aspeed_sbc.h6
-rw-r--r--include/hw/sensor/tmp105.h46
-rw-r--r--include/hw/usb/aspeed-udc.h85
-rw-r--r--qapi/crypto.json33
-rw-r--r--tests/functional/aspeed.py5
-rw-r--r--tests/qtest/aspeed-acry-test.c406
-rw-r--r--tests/qtest/aspeed-hace-utils.c24
-rw-r--r--tests/qtest/aspeed-sbc-test.c194
-rw-r--r--tests/qtest/meson.build7
-rw-r--r--tests/qtest/tmp105-test.c359
-rw-r--r--tests/unit/test-crypto-akcipher.c236
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();
}