All of lore.kernel.org
 help / color / mirror / Atom feed
* [PULL 00/40] aspeed queue
@ 2026-09-06 17:09 Cédric Le Goater
  2026-09-06 17:09 ` [PULL 01/40] hw/misc/aspeed_scu: Set both AST2600 protection key registers on reset Cédric Le Goater
                   ` (40 more replies)
  0 siblings, 41 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Cédric Le Goater

The following changes since commit ff1d2d19d7e24893e2012d879f8e73077e17b9bd:

  Merge tag 'pull-11.1-check-tcg-meson-040926-2' of https://gitlab.com/stsquad/qemu into staging (2026-09-04 16:52:58 +0100)

are available in the Git repository at:

  https://github.com/legoater/qemu/ tags/pull-aspeed-20260906

for you to fetch changes up to 1c05d7e94da04129ea16147074fe73158fb85ff9:

  tests/functional: aspeed: optionally check the device tree model on boot (2026-09-05 07:05:11 +0200)

----------------------------------------------------------------
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

----------------------------------------------------------------
Andrew Jeffery (1):
      hw/misc/aspeed_scu: Set both AST2600 protection key registers on reset

Emmanuel Blot (16):
      hw/sensor: tmp105: make device state private to the implementation
      hw/sensor: tmp105: name the parent object field parent_obj
      hw/sensor: tmp105: implement Resettable reset
      hw/sensor: tmp105: enforce the configurable fault queue
      hw/sensor: tmp105: describe the temperature property
      hw/arm: aspeed: guard board-local temperature-sensor aliases
      hw/sensor: tmp105: add TMP75, TMP175 and LM75B variants
      tests/qtest: tmp105: cover the ALERT fault queue
      tests/qtest: tmp105: cover the TMP75, TMP175 and LM75B variants
      tests/qtest: tmp105: cover one-shot and fault-queue write immunity
      tests/qtest: tmp105: cover shutdown clearing the ALERT across variants
      hw/arm: sanmiguel: add Facebook SanMiguel BMC machine
      hw/arm: sanmiguel: populate EEPROM data
      hw/arm: catalina: use the real TMP75 model
      hw/arm: fuji: use the real TMP75 and LM75B temperature sensors
      tests/functional: aspeed: optionally check the device tree model on boot

Jamin Lin (23):
      hw/i2c/aspeed_i2c: Latch received bytes for SMBus block reads
      hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords()
      hw/usb/hcd-ehci: Make get_dwords() return bool
      hw/usb/hcd-ehci: Handle get_dwords() failures in async writeback
      hw/misc/aspeed_scu: Support the second random number generator
      tests/qtest/aspeed-hace-utils: Replace g_printerr() with g_test_skip()
      hw/misc/aspeed_acry: Add ASPEED ACRY model
      hw/arm/aspeed_ast2600: Introduce the ACRY SRAM
      hw/arm/aspeed_ast2600: Wire up the ACRY model
      tests/qtest/aspeed-acry-test: Add RSA ModExp tests
      qapi/crypto: Add ECDSA algorithm and curve id
      crypto/akcipher: Support ECDSA sign/verify with gcrypt
      crypto/akcipher: Support ECDSA sign/verify with nettle
      tests/crypto: Add ECDSA sign/verify tests
      hw/arm/aspeed_ast10x0: Remove obsolete unimplemented SBC mapping
      hw/misc/aspeed_sbc: Increase register space to 0x1000
      hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model
      hw/misc/aspeed_sbc: Support the ECDSA verify command
      tests/qtest: Add ASPEED SBC ECDSA engine test
      hw/usb/aspeed-udc: Add ASPEED UDC device controller
      hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device
      hw/usb/aspeed-udc: Add programmable endpoint DMA transfers
      hw/arm/aspeed_ast2600: Wire up the UDC

 qapi/crypto.json                  |   33 +-
 include/hw/arm/aspeed_soc.h       |    9 +-
 include/hw/i2c/aspeed_i2c.h       |    3 +
 include/hw/misc/aspeed_acry.h     |   39 ++
 include/hw/misc/aspeed_sbc.h      |    6 +-
 include/hw/sensor/tmp105.h        |   46 +-
 include/hw/usb/aspeed-udc.h       |   85 +++
 hw/arm/aspeed_ast10x0.c           |   14 +-
 hw/arm/aspeed_ast2600.c           |   71 ++-
 hw/arm/aspeed_ast2600_catalina.c  |    1 -
 hw/arm/aspeed_ast2600_fuji.c      |   16 +-
 hw/arm/aspeed_ast2600_sanmiguel.c |  403 ++++++++++++++
 hw/i2c/aspeed_i2c.c               |   38 +-
 hw/misc/aspeed_acry.c             |  481 +++++++++++++++++
 hw/misc/aspeed_sbc.c              |  158 +++++-
 hw/misc/aspeed_scu.c              |   21 +-
 hw/sensor/tmp105.c                |  300 ++++++++---
 hw/usb/aspeed-udc.c               | 1058 +++++++++++++++++++++++++++++++++++++
 hw/usb/hcd-ehci.c                 |   90 ++--
 tests/qtest/aspeed-acry-test.c    |  406 ++++++++++++++
 tests/qtest/aspeed-hace-utils.c   |   24 +-
 tests/qtest/aspeed-sbc-test.c     |  194 +++++++
 tests/qtest/tmp105-test.c         |  359 +++++++++++++
 tests/unit/test-crypto-akcipher.c |  236 +++++++++
 crypto/akcipher-gcrypt.c.inc      |  340 +++++++++++-
 crypto/akcipher-nettle.c.inc      |  272 ++++++++++
 hw/arm/Kconfig                    |    1 +
 hw/arm/meson.build                |    1 +
 hw/misc/meson.build               |    1 +
 hw/misc/trace-events              |    8 +
 hw/usb/Kconfig                    |    4 +
 hw/usb/meson.build                |    1 +
 hw/usb/trace-events               |   16 +
 tests/functional/aspeed.py        |    5 +-
 tests/qtest/meson.build           |    7 +-
 35 files changed, 4552 insertions(+), 195 deletions(-)
 create mode 100644 include/hw/misc/aspeed_acry.h
 create mode 100644 include/hw/usb/aspeed-udc.h
 create mode 100644 hw/arm/aspeed_ast2600_sanmiguel.c
 create mode 100644 hw/misc/aspeed_acry.c
 create mode 100644 hw/usb/aspeed-udc.c
 create mode 100644 tests/qtest/aspeed-acry-test.c
 create mode 100644 tests/qtest/aspeed-sbc-test.c



^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PULL 01/40] hw/misc/aspeed_scu: Set both AST2600 protection key registers on reset
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 02/40] hw/i2c/aspeed_i2c: Latch received bytes for SMBus block reads Cédric Le Goater
                   ` (39 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Andrew Jeffery, Jamin Lin, Cédric Le Goater

From: Andrew Jeffery <andrew@codeconstruct.com.au>

Currently, booting Linux on the ast2600-evb machine via -kernel results
in a lot of warnings about failing to apply pinmux configuration when
binding drivers:

    [    7.339307][    T1] Creating 5 MTD partitions on "bmc":
    [    7.340402][    T1] 0x000000000000-0x0000000e0000 : "u-boot"
    [    7.513755][    T1] 0x0000000e0000-0x000000100000 : "u-boot-env"
    [    7.664219][    T1] 0x000000100000-0x000000a00000 : "kernel"
    [    7.826974][    T1] 0x000000a00000-0x000002a00000 : "rofs"
    [    8.026666][    T1] 0x000002a00000-0x000004000000 : "rwfs"
    [    8.201612][    T1] spi-aspeed-smc 1e630000.spi: error -EPERM: Error applying setting, reverse things back
    [    8.370835][    T1] spi-aspeed-smc 1e630000.spi: Calibration area too uniform, using low speed
    [    8.371131][    T1] spi-aspeed-smc 1e630000.spi: Force to dts configuration 100000kHz.
    [    8.371500][    T1] spi-aspeed-smc 1e630000.spi: CE0 read buswidth:4 [0x406c0741]
    [    8.766797][    T1] mdio-aspeed 1e650000.mdio: error -EPERM: Error applying setting, reverse things back
    [    8.906008][    T1] mdio-aspeed 1e650008.mdio: error -EPERM: Error applying setting, reverse things back
    [    9.039240][    T1] mdio-aspeed 1e650010.mdio: error -EPERM: Error applying setting, reverse things back
    [    9.147942][    T1] mdio-aspeed 1e650018.mdio: error -EPERM: Error applying setting, reverse things back

The pinmux configuration fails because the SCU is not unlocked.

Set both SCU protection keys for the AST2600. Further, set them
such that they behave accordance with the read value documented
by the datasheet. The new arrangement now upholds the comment in
hw/arm/aspeed.c regarding passing -kernel on the qemu command-line.

    [    7.568170][    T1] Creating 5 MTD partitions on "bmc":
    [    7.569330][    T1] 0x000000000000-0x0000000e0000 : "u-boot"
    [    7.740372][    T1] 0x0000000e0000-0x000000100000 : "u-boot-env"
    [    7.883570][    T1] 0x000000100000-0x000000a00000 : "kernel"
    [    8.041004][    T1] 0x000000a00000-0x000002a00000 : "rofs"
    [    8.196824][    T1] 0x000002a00000-0x000004000000 : "rwfs"
    [    8.547037][    T1] spi-aspeed-smc 1e630000.spi: Calibration area too uniform, using low speed
    [    8.547320][    T1] spi-aspeed-smc 1e630000.spi: Force to dts configuration 100000kHz.
    [    8.547642][    T1] spi-aspeed-smc 1e630000.spi: CE0 read buswidth:4 [0x406c0741]
    [    9.494235][    T1] ftgmac100 1e660000.ethernet: Read MAC address 52:54:00:12:34:56 from chip
    [    9.531186][    T1] RTL8211E Gigabit Ethernet 1e650000.mdio-1:00: attached PHY driver (mii_bus:phy_addr=1e650000.mdio-1:00,

Fixes: e09cf36321f6 ("hw: aspeed_scu: Add AST2600 support")
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260818-dev-ast2600-hw-prot-key-reset-v1-1-5dc3de64c247@codeconstruct.com.au
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_scu.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c
index ca93c3699d81..1e3db6d03f91 100644
--- a/hw/misc/aspeed_scu.c
+++ b/hw/misc/aspeed_scu.c
@@ -821,7 +821,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)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 02/40] hw/i2c/aspeed_i2c: Latch received bytes for SMBus block reads
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
  2026-09-06 17:09 ` [PULL 01/40] hw/misc/aspeed_scu: Set both AST2600 protection key registers on reset Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 03/40] hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords() Cédric Le Goater
                   ` (38 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Mikail Sadic, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

An SMBus block read takes the block length from the first byte of the
transfer, and firmware reads that byte back from a register rather than
from the transfer buffer. The receive paths never updated those
registers, so block reads reported a bogus length.

On AST2600 the driver reads the length from the receive byte buffer,
I2CC_MS_TXRX_BYTE_BUF[15:8]. The datasheet documents that field as valid
while the DMA buffer is not enabled. The byte mode receive path already
updated it, but the pool buffer path did not, and the driver selects
buffer mode by default.

On AST2700 the driver reads the length from offset 0x84 instead.

Add I2CC_BYTE_DATA_LOG at 0x84 and latch received bytes into it. The pool
buffer, DMA-to-pool and DMA-to-DRAM paths latch their first byte, the byte
mode path latches every byte. Each latch also updates the receive byte
buffer unless RX_DMA_EN is set, which is the datasheet condition and does
not depend on FUNC_CFG_DMA_EN. The byte data log only exists on AST2700
and AST1040, so it is gated on a class flag.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Tested-by: Mikail Sadic <mikail.sadic@ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260814020836.3119613-1-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/i2c/aspeed_i2c.h |  3 +++
 hw/i2c/aspeed_i2c.c         | 38 +++++++++++++++++++++++++++++++++++--
 2 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/include/hw/i2c/aspeed_i2c.h b/include/hw/i2c/aspeed_i2c.h
index 05937a7a0b49..480c6418feee 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/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
index 68bdcd0e25a6..0bc4bb6fbe92 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;
 }
 
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 03/40] hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords()
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
  2026-09-06 17:09 ` [PULL 01/40] hw/misc/aspeed_scu: Set both AST2600 protection key registers on reset Cédric Le Goater
  2026-09-06 17:09 ` [PULL 02/40] hw/i2c/aspeed_i2c: Latch received bytes for SMBus block reads Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 04/40] hw/usb/hcd-ehci: Make get_dwords() return bool Cédric Le Goater
                   ` (37 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Peter Maydell, Philippe Mathieu-Daudé,
	Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

get_dwords() and put_dwords() return -1 when ehci->as is NULL, but that
can no longer happen. The sysbus variants set it in instance_init
(ehci_sysbus_init()), the PCI variant sets it in realize
(usb_ehci_pci_realize()), and usb_ehci_pci_write_config() only switches
between the bus master address space and address_space_memory.

Meanwhile the results of dma_memory_read() and dma_memory_write() are
ignored, so a failed guest memory access is silently treated as success.

Drop the dead NULL test and check the MemTxResult of each access
instead, so that the existing -1 error path reports real DMA errors:
raise USBSTS_HSE, clear USBCMD_RUNSTOP and stop processing the
descriptor, as the NULL path used to do. All 11 get_dwords() callers
already check the return value.

put_dwords() now returns void: none of its four callers (ehci_flush_qh(),
ehci_state_fetchitd() and twice in ehci_state_writeback()) looks at the
status.

Suggested-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260817055318.3826428-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.c | 38 ++++++++++++++++----------------------
 1 file changed, 16 insertions(+), 22 deletions(-)

diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 451a918e9f5f..ecf98c4e199f 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -421,16 +421,14 @@ static inline int get_dwords(EHCIState *ehci, uint64_t addr,
 {
     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 -1;
+        }
         *buf = le32_to_cpu(*buf);
     }
 
@@ -438,25 +436,21 @@ static inline int get_dwords(EHCIState *ehci, uint64_t addr,
 }
 
 /* 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)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 04/40] hw/usb/hcd-ehci: Make get_dwords() return bool
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (2 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 03/40] hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords() Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 05/40] hw/usb/hcd-ehci: Handle get_dwords() failures in async writeback Cédric Le Goater
                   ` (36 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Philippe Mathieu-Daudé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

get_dwords() returns the number of dwords it read, but every caller only
tests it for failure and none of them uses the count. Return a plain
bool instead, which matches how the function is actually used.

Suggested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260817055318.3826428-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.c | 44 ++++++++++++++++++++++----------------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index ecf98c4e199f..5187ecc7e4f9 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -416,8 +416,8 @@ 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;
 
@@ -427,12 +427,12 @@ static inline int get_dwords(EHCIState *ehci, uint64_t addr,
             ehci_raise_irq(ehci, USBSTS_HSE);
             ehci->usbcmd &= ~USBCMD_RUNSTOP;
             trace_usb_ehci_dma_error();
-            return -1;
+            return false;
         }
         *buf = le32_to_cpu(*buf);
     }
 
-    return num;
+    return true;
 }
 
 /* Put an array of dwords in to main memory */
@@ -1598,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);
@@ -1701,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;
     }
@@ -1779,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);
@@ -1805,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);
@@ -1866,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);
@@ -1969,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);
@@ -2290,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);
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 05/40] hw/usb/hcd-ehci: Handle get_dwords() failures in async writeback
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (3 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 04/40] hw/usb/hcd-ehci: Make get_dwords() return bool Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 06/40] hw/misc/aspeed_scu: Support the second random number generator Cédric Le Goater
                   ` (35 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Philippe Mathieu-Daudé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Coverity reports that ehci_writeback_async_complete_packet() ignores
the return value of get_dwords() when reading the QH and qTD.

Handle read failures in the same way as QH and qTD verification
failures by freeing the packet and returning early.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Resolves: Coverity CID 1685236
Fixes: 2b3de6ada5d ("ehci: writeback_async_complete_packet: verify qh and qtd")
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260817055318.3826428-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 5187ecc7e4f9..f371e567f38b 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -527,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;
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 06/40] hw/misc/aspeed_scu: Support the second random number generator
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (4 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 05/40] hw/usb/hcd-ehci: Handle get_dwords() failures in async writeback Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 07/40] tests/qtest/aspeed-hace-utils: Replace g_printerr() with g_test_skip() Cédric Le Goater
                   ` (34 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Implement SCU530 and SCU534, the control and data registers of the
AST2600 second random number generator.

Per the AST2600 datasheet, only SCU530[5:0] are writable and the
read-only SCU530[31] reports that random data is valid, which is only
meaningful while SCU530[4] is set. Report it as set whenever SCU530[4]
is, and initialise SCU530 to its datasheet reset value.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@kaod.org>
Link: https://lore.kernel.org/qemu-devel/20260821080408.2503092-1-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_scu.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c
index 1e3db6d03f91..77f9af2ae50e 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)
@@ -1123,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)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 07/40] tests/qtest/aspeed-hace-utils: Replace g_printerr() with g_test_skip()
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (5 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 06/40] hw/misc/aspeed_scu: Support the second random number generator Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 08/40] hw/misc/aspeed_acry: Add ASPEED ACRY model Cédric Le Goater
                   ` (33 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Philippe Mathieu-Daudé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Skip a cipher unsupported by the crypto backend with g_test_skip() from
the test, instead of not registering it and printing a TAP "# skip" line,
so the qtest does not assume TAP output.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260831050213.381443-1-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-hace-utils.c | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/tests/qtest/aspeed-hace-utils.c b/tests/qtest/aspeed-hace-utils.c
index 260eec043c73..d20562fac9cc 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);
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 08/40] hw/misc/aspeed_acry: Add ASPEED ACRY model
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (6 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 07/40] tests/qtest/aspeed-hace-utils: Replace g_printerr() with g_test_skip() Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 09/40] hw/arm/aspeed_ast2600: Introduce the ACRY SRAM Cédric Le Goater
                   ` (32 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Introduce a ASPEED ACRY model, which performs RSA modular
exponentiation. The datasheet documents the engine as
supporting both RSA and ECDSA, but ECDSA is broken on this
hardware, so only RSA is modelled.

The engine DMAs its operands (data, exponent, modulus) from a guest
DRAM buffer and writes the result back into a memory-mapped SRAM
region. Both regions share the same interleaved byte/dword layout:
repeating 12-dword blocks of [4 dwords exponent][4 dwords modulus][4
dwords data], index 0 holding the least-significant word/byte of each
value.

The engine accesses DRAM by relative offset, so the CPU-visible
address written to the DMA source register has its top (base) bit
masked off.

The modular exponentiation itself is delegated to QEMU's generic
akcipher crypto API (crypto/akcipher.c) using raw (unpadded) RSA,
matching what the real hardware performs - PKCS1 padding is handled
by the guest's software crypto stack, not by this engine.

The RSA public-key operand DER encoding needed by that API is built with
crypto/der.h's generic encoder. Raw (unpadded) RSA is only implemented
by that API's libgcrypt backend (its nettle backend rejects raw
padding).

When that support is missing, the engine still completes and raises its
completion IRQ as real hardware would, but produces an all-zero result
so that whatever signature check the guest performs on it fails cleanly
instead of the guest hanging forever waiting for an interrupt that
would otherwise never come.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901081531.898176-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/misc/aspeed_acry.h |  39 +++
 hw/misc/aspeed_acry.c         | 481 ++++++++++++++++++++++++++++++++++
 hw/misc/meson.build           |   1 +
 hw/misc/trace-events          |   6 +
 4 files changed, 527 insertions(+)
 create mode 100644 include/hw/misc/aspeed_acry.h
 create mode 100644 hw/misc/aspeed_acry.c

diff --git a/include/hw/misc/aspeed_acry.h b/include/hw/misc/aspeed_acry.h
new file mode 100644
index 000000000000..5ca80deec4d1
--- /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/hw/misc/aspeed_acry.c b/hw/misc/aspeed_acry.c
new file mode 100644
index 000000000000..41eac7295fd7
--- /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/meson.build b/hw/misc/meson.build
index 54e07aacda1d..9790afa1044f 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 0b8be3d0f226..0053de29ade5 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -331,6 +331,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
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 09/40] hw/arm/aspeed_ast2600: Introduce the ACRY SRAM
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (7 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 08/40] hw/misc/aspeed_acry: Add ASPEED ACRY model Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 10/40] hw/arm/aspeed_ast2600: Wire up the ACRY model Cédric Le Goater
                   ` (31 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Reuse the existing SRAM array convention on AspeedSoCState to add the
64 KiB ACRY SRAM region to the AST2600 at its real-silicon address
(0x1e710000-0x1e71ffff).

Wrap the RAM in a container mapped at offset 0 so the ACRY engine wired
up in a later patch can address the SRAM by relative offset without the
device model knowing its mapping address.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@kaod.org>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901081531.898176-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_soc.h |  1 +
 hw/arm/aspeed_ast2600.c     | 17 +++++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index cd68c7f1caa2..46e7854727c6 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -69,6 +69,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;
diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c
index d1f18e471aa0..f23a51c8a46f 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,
@@ -361,6 +363,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 +447,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",
@@ -765,6 +781,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;
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 10/40] hw/arm/aspeed_ast2600: Wire up the ACRY model
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (8 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 09/40] hw/arm/aspeed_ast2600: Introduce the ACRY SRAM Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 11/40] tests/qtest/aspeed-acry-test: Add RSA ModExp tests Cédric Le Goater
                   ` (30 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Philippe Mathieu-Daudé,
	Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Introduce the ASPEED_DEV_ACRY enum slot and add the ACRY model state
to AspeedSoCState.

Instantiate the new ACRY model on the AST2600 SoC using its
real-silicon register address and IRQ:

- ACRY register region 0x1e6fa000-0x1e6fafff
- IRQ 160

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@kaod.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901081531.898176-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_soc.h |  3 +++
 hw/arm/aspeed_ast2600.c     | 17 +++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index 46e7854727c6..8d80f1670587 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"
@@ -82,6 +83,7 @@ struct AspeedSoCState {
     AspeedSCUState scu;
     AspeedSCUState scuio;
     AspeedHACEState hace;
+    AspeedACRYState acry;
     AspeedXDMAState xdma;
     AspeedADCState adc;
     AspeedSMCState fmc;
@@ -271,6 +273,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/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c
index f23a51c8a46f..f7c7ec7fb1d4 100644
--- a/hw/arm/aspeed_ast2600.c
+++ b/hw/arm/aspeed_ast2600.c
@@ -52,6 +52,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,
@@ -144,6 +145,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)
@@ -269,6 +271,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);
@@ -726,6 +730,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;
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 11/40] tests/qtest/aspeed-acry-test: Add RSA ModExp tests
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (9 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 10/40] hw/arm/aspeed_ast2600: Wire up the ACRY model Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 12/40] qapi/crypto: Add ECDSA algorithm and curve id Cédric Le Goater
                   ` (29 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Add qtest cases exercising the AST2600 ACRY RSA ModExp engine end to
end. Each case lays out the exponent, modulus and message in the
scattered SRAM byte layout the engine expects, triggers the
engine, checks that both completion bits (RSA_ENG_DONE and
RSA_DMA_DONE) are asserted together in the status register, reads the
result back from the result SRAM, and checks the status bits clear
afterwards.

Comparing the result against a known-answer ciphertext
also validates the SRAM byte mapping end to end.

The cases cover raw (unpadded) public-exponent RSA, c = m^e mod n -
exactly what the "rsa" akcipher transform backed by the ACRY FW
performs, since PKCS1 padding is applied by a separate template
layered on top in Linux, not by the ACRY hardware. QEMU's akcipher
backend only implements raw RSA via libgcrypt, so the tests skip
rather than fail when the build lacks it.

The 2048-bit and 4096-bit vectors are rsa_tv_template[2] and [3] from the
Linux kernel crypto self-test suite (crypto/testmgr.h, v6.18).

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901081531.898176-5-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-acry-test.c | 406 +++++++++++++++++++++++++++++++++
 tests/qtest/meson.build        |   5 +-
 2 files changed, 410 insertions(+), 1 deletion(-)
 create mode 100644 tests/qtest/aspeed-acry-test.c

diff --git a/tests/qtest/aspeed-acry-test.c b/tests/qtest/aspeed-acry-test.c
new file mode 100644
index 000000000000..ec348c220664
--- /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/meson.build b/tests/qtest/meson.build
index 17870e339b4c..6e7ffcdbd09c 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -230,7 +230,8 @@ qtests_npcm7xx = \
 qtests_npcm8xx = \
   ['npcm_gmac-test']
 qtests_aspeed = \
-  ['aspeed_gpio-test',
+  ['aspeed-acry-test',
+   'aspeed_gpio-test',
    'aspeed_hace-test',
    'aspeed_scu-test',
    'aspeed_smc-test']
@@ -402,6 +403,8 @@ 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_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'),
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 12/40] qapi/crypto: Add ECDSA algorithm and curve id
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (10 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 11/40] tests/qtest/aspeed-acry-test: Add RSA ModExp tests Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 13/40] crypto/akcipher: Support ECDSA sign/verify with gcrypt Cédric Le Goater
                   ` (28 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Markus Armbruster, Daniel P. Berrangé,
	Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The ASPEED AST10x0 Secure Boot Controller has a hardware ECDSA engine
that guest firmware uses to verify secp384r1 signatures. Emulating it
requires ECDSA support in the crypto akcipher framework, which today
only implements RSA.

Introduce a new asymmetric cipher algorithm, ECDSA, a curve identifier
enum (prime256v1 / NIST P-256 and secp384r1 / NIST P-384) and the
per-algorithm ECDSA options. This is the QAPI groundwork consumed by the
crypto akcipher backends in the following patches.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Acked-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 qapi/crypto.json | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/qapi/crypto.json b/qapi/crypto.json
index 6e3a98ff68b3..cae2c678cb22 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:
@@ -605,6 +621,18 @@
   'data': { 'hash-alg':'QCryptoHashAlgo',
             '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:
 #
@@ -618,4 +646,5 @@
 { 'union': 'QCryptoAkCipherOptions',
   'base': { 'alg': 'QCryptoAkCipherAlgo' },
   'discriminator': 'alg',
-  'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
+  'data': { 'rsa': 'QCryptoAkCipherOptionsRSA',
+            'ecdsa': 'QCryptoAkCipherOptionsECDSA' }}
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 13/40] crypto/akcipher: Support ECDSA sign/verify with gcrypt
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (11 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 12/40] qapi/crypto: Add ECDSA algorithm and curve id Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 14/40] crypto/akcipher: Support ECDSA sign/verify with nettle Cédric Le Goater
                   ` (27 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Daniel P. Berrangé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Implement ECDSA signing and verification for the gcrypt backend, for the
prime256v1 (NIST P-256) and secp384r1 (NIST P-384) curves. The
encrypt/decrypt driver ops return an error, as ECDSA is a signature
algorithm. The public key is provided as the raw affine coordinates
Qx || Qy, the private key as the raw scalar d, and the signature as the
raw pair r || s (each half the curve size, big-endian); the input to
sign/verify is a pre-computed message digest.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 crypto/akcipher-gcrypt.c.inc | 340 ++++++++++++++++++++++++++++++++++-
 1 file changed, 339 insertions(+), 1 deletion(-)

diff --git a/crypto/akcipher-gcrypt.c.inc b/crypto/akcipher-gcrypt.c.inc
index bcf030fdec45..3e49cc150849 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;
     }
 }
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 14/40] crypto/akcipher: Support ECDSA sign/verify with nettle
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (12 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 13/40] crypto/akcipher: Support ECDSA sign/verify with gcrypt Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 15/40] tests/crypto: Add ECDSA sign/verify tests Cédric Le Goater
                   ` (26 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Daniel P. Berrangé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Implement ECDSA signing and verification for the nettle/hogweed backend,
for the prime256v1 (NIST P-256) and secp384r1 (NIST P-384) curves.
A public key (Qx || Qy) is loaded into an ecc_point for verify;
a private key (the scalar d) into an ecc_scalar for sign,
using nettle's ecdsa_sign() / ecdsa_verify().

The encrypt/decrypt driver ops return an error.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 crypto/akcipher-nettle.c.inc | 272 +++++++++++++++++++++++++++++++++++
 1 file changed, 272 insertions(+)

diff --git a/crypto/akcipher-nettle.c.inc b/crypto/akcipher-nettle.c.inc
index 1d4bd6960e08..a34845af5d11 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;
     }
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 15/40] tests/crypto: Add ECDSA sign/verify tests
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (13 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 14/40] crypto/akcipher: Support ECDSA sign/verify with nettle Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 16/40] hw/arm/aspeed_ast10x0: Remove obsolete unimplemented SBC mapping Cédric Le Goater
                   ` (25 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Daniel P. Berrangé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Add unit tests for ECDSA prime256v1 and secp384r1, using the
RFC 6979 (Deterministic ECDSA) "sample" known-answer vectors. The same
key pair drives the verify test (against the RFC's known signature) and
the sign test, which signs the digest with the private key and checks
that the result verifies with the public key. Each test skips
(g_test_skip) when the crypto backend does not support ECDSA, via
qcrypto_akcipher_supports().

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-5-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/unit/test-crypto-akcipher.c | 236 ++++++++++++++++++++++++++++++
 1 file changed, 236 insertions(+)

diff --git a/tests/unit/test-crypto-akcipher.c b/tests/unit/test-crypto-akcipher.c
index 53c2211ba8ea..fb1a6163e926 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();
 }
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 16/40] hw/arm/aspeed_ast10x0: Remove obsolete unimplemented SBC mapping
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (14 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 15/40] tests/crypto: Add ECDSA sign/verify tests Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 17/40] hw/misc/aspeed_sbc: Increase register space to 0x1000 Cédric Le Goater
                   ` (24 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Philippe Mathieu-Daudé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST10x0 SoC now implements the SBC device model. However, the
old unimplemented SBC mapping was left in place.

Remove the obsolete mapping now that the SBC device is implemented.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-6-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_soc.h | 1 -
 hw/arm/aspeed_ast10x0.c     | 7 -------
 2 files changed, 8 deletions(-)

diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index 8d80f1670587..cba8b7cdca58 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -92,7 +92,6 @@ struct AspeedSoCState {
     AspeedSBCState sbc;
     AspeedSLIState sli;
     AspeedSLIState sliio;
-    UnimplementedDeviceState sbc_unimplemented;
     AspeedSDMCState sdmc;
     AspeedPWMState pwm;
     AspeedWDTState wdt[ASPEED_WDTS_NUM];
diff --git a/hw/arm/aspeed_ast10x0.c b/hw/arm/aspeed_ast10x0.c
index 93c81195b56a..5165dcce5912 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);
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 17/40] hw/misc/aspeed_sbc: Increase register space to 0x1000
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (15 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 16/40] hw/arm/aspeed_ast10x0: Remove obsolete unimplemented SBC mapping Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:09 ` [PULL 18/40] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Cédric Le Goater
                   ` (23 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The SBC controller register space is 0x1000.
Increase ASPEED_SBC_NR_REGS accordingly to cover the
full register space.

This changes the size of the VMState register array and
therefore breaks migration compatibility with older QEMU
versions. Simply bump the VMState version to 2,
as Aspeed machines are not intended for
virtualization use cases.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-7-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/misc/aspeed_sbc.h | 2 +-
 hw/misc/aspeed_sbc.c         | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/include/hw/misc/aspeed_sbc.h b/include/hw/misc/aspeed_sbc.h
index 07c7c22a86ab..eea6e2b27fbf 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)
diff --git a/hw/misc/aspeed_sbc.c b/hw/misc/aspeed_sbc.c
index 1dfcf14e5ba6..7397d9bbf066 100644
--- a/hw/misc/aspeed_sbc.c
+++ b/hw/misc/aspeed_sbc.c
@@ -307,15 +307,15 @@ static void aspeed_sbc_realize(DeviceState *dev, Error **errp)
     }
 
     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(),
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 18/40] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (16 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 17/40] hw/misc/aspeed_sbc: Increase register space to 0x1000 Cédric Le Goater
@ 2026-09-06 17:09 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 19/40] hw/misc/aspeed_sbc: Support the ECDSA verify command Cédric Le Goater
                   ` (22 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:09 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Introduce the "has_ecdsa" class attribute and enable it for the AST10x0 SBC,
as ECDSA is only supported on this platform.

Add an "sram" link property to the SBC model and initialize a dedicated address
space for accessing the SEC SRAM. This will be used by the ECDSA verify command
to read the public key, signature, and digest from SRAM.

Wrap the SEC SRAM in a container mapped at offset 0. This allows the ECDSA
engine added in a later patch to access the SRAM using relative offsets without
requiring the SBC model to know the SRAM's system address.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-8-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/misc/aspeed_sbc.h |  4 ++++
 hw/arm/aspeed_ast10x0.c      |  7 ++++++-
 hw/misc/aspeed_sbc.c         | 11 +++++++++++
 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/include/hw/misc/aspeed_sbc.h b/include/hw/misc/aspeed_sbc.h
index eea6e2b27fbf..756c61235697 100644
--- a/include/hw/misc/aspeed_sbc.h
+++ b/include/hw/misc/aspeed_sbc.h
@@ -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/hw/arm/aspeed_ast10x0.c b/hw/arm/aspeed_ast10x0.c
index 5165dcce5912..aeb5a4423dd3 100644
--- a/hw/arm/aspeed_ast10x0.c
+++ b/hw/arm/aspeed_ast10x0.c
@@ -250,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)) {
@@ -350,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/misc/aspeed_sbc.c b/hw/misc/aspeed_sbc.c
index 7397d9bbf066..f10f7ac578d8 100644
--- a/hw/misc/aspeed_sbc.c
+++ b/hw/misc/aspeed_sbc.c
@@ -306,6 +306,14 @@ 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, ASPEED_SBC_NR_REGS << 2);
 
@@ -325,6 +333,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 +365,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[] = {
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 19/40] hw/misc/aspeed_sbc: Support the ECDSA verify command
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (17 preceding siblings ...)
  2026-09-06 17:09 ` [PULL 18/40] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 20/40] tests/qtest: Add ASPEED SBC ECDSA engine test Cédric Le Goater
                   ` (21 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST10x0 secure boot controller register block also hosts an ECDSA
engine. Emulate its secp384r1 "verify" command: on a trigger write to
the command register, read the public key, signature and SHA-384 digest
that the firmware staged in the SEC SRAM and defer the verification to the
crypto akcipher backend, reporting the result through the status
register.

The model reads its operands from the SEC SRAM through a dedicated
address space, using a 'sram' link. The SEC SRAM is mapped at offset 0
of that address space, so the engine addresses each operand directly
with its SRAM-relative offset.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-9-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_sbc.c | 141 +++++++++++++++++++++++++++++++++++++++++++
 hw/misc/trace-events |   2 +
 2 files changed, 143 insertions(+)

diff --git a/hw/misc/aspeed_sbc.c b/hw/misc/aspeed_sbc.c
index f10f7ac578d8..5c193d9086ed 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;
     }
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 0053de29ade5..6db4934dc585 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
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 20/40] tests/qtest: Add ASPEED SBC ECDSA engine test
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (18 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 19/40] hw/misc/aspeed_sbc: Support the ECDSA verify command Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 21/40] hw/usb/aspeed-udc: Add ASPEED UDC device controller Cédric Le Goater
                   ` (20 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Add a qtest for the ASPEED secure boot controller ECDSA engine. It stages
the public key, signature and SHA-384 digest of a secp384r1 known-answer
vector (from the Linux kernel crypto self-test manager,
ecdsa_nist_p384_tv_template) into the SEC SRAM, triggers the engine's
verify command and checks the status register: a valid signature reports
done + pass, and a tampered signature reports done without pass.

The test is skipped when the crypto backend does not support ECDSA, via
qcrypto_akcipher_supports().

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-10-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-sbc-test.c | 194 ++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build       |   2 +
 2 files changed, 196 insertions(+)
 create mode 100644 tests/qtest/aspeed-sbc-test.c

diff --git a/tests/qtest/aspeed-sbc-test.c b/tests/qtest/aspeed-sbc-test.c
new file mode 100644
index 000000000000..c445dac47a6e
--- /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 6e7ffcdbd09c..c3593f753049 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -233,6 +233,7 @@ qtests_aspeed = \
   ['aspeed-acry-test',
    'aspeed_gpio-test',
    'aspeed_hace-test',
+   'aspeed-sbc-test',
    'aspeed_scu-test',
    'aspeed_smc-test']
 qtests_aspeed64 = \
@@ -407,6 +408,7 @@ qtests = {
                        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],
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 21/40] hw/usb/aspeed-udc: Add ASPEED UDC device controller
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (19 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 20/40] tests/qtest: Add ASPEED SBC ECDSA engine test Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 22/40] hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device Cédric Le Goater
                   ` (19 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Philippe Mathieu-Daudé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST2600 has a USB 2.0 Device Controller (UDC) at 0x1e6a2000 with one
control endpoint and four programmable endpoints.

Add the controller as a sysbus (system) device: the MMIO register map
described with the registerfields macros, the interrupt line and the
soft reset. This is only the register/system side.

Note: this "device controller" is the system-bus device (TYPE_ASPEED_UDC).
It is not the gadget USB device (TYPE_ASPEED_UDC_GADGET) that a host
controller enumerates, which is added in the next patch.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260902021542.3194812-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/usb/aspeed-udc.h |  49 ++++++++
 hw/usb/aspeed-udc.c         | 236 ++++++++++++++++++++++++++++++++++++
 hw/arm/Kconfig              |   1 +
 hw/usb/Kconfig              |   3 +
 hw/usb/meson.build          |   1 +
 hw/usb/trace-events         |   7 ++
 6 files changed, 297 insertions(+)
 create mode 100644 include/hw/usb/aspeed-udc.h
 create mode 100644 hw/usb/aspeed-udc.c

diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
new file mode 100644
index 000000000000..58fed5f9a2d2
--- /dev/null
+++ b/include/hw/usb/aspeed-udc.h
@@ -0,0 +1,49 @@
+/*
+ * 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 "qom/object.h"
+
+#define TYPE_ASPEED_UDC "aspeed.udc"
+OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCState, ASPEED_UDC)
+
+/*
+ * 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;
+} AspeedUDCEP;
+
+struct AspeedUDCState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion udc_container;
+    MemoryRegion root_mr;
+    uint32_t regs[ASPEED_UDC_ROOT_NR_REGS];
+    AspeedUDCEP ep[ASPEED_UDC_NUM_EP];
+    qemu_irq irq;
+};
+
+#endif /* HW_USB_ASPEED_UDC_H */
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
new file mode 100644
index 000000000000..cd773e979b60
--- /dev/null
+++ b/hw/usb/aspeed-udc.c
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ *
+ * This file is the system-bus side of the controller: the MMIO register map,
+ * the interrupt and the soft reset. The gadget USB device presented to a host
+ * controller (and the endpoint data path) is added on top of this.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/core/registerfields.h"
+#include "hw/usb/aspeed-udc.h"
+#include "qemu/module.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)
+/* 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)
+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)
+
+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 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_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;
+    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,
+    },
+};
+
+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;
+    uint32_t reg = offset >> 2;
+
+    trace_aspeed_udc_ep_write(e->index, offset, data);
+    e->regs[reg] = data;
+}
+
+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);
+    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));
+    }
+
+    /* 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);
+}
+
+static void aspeed_udc_realize(DeviceState *dev, Error **errp)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    AspeedUDCState *s = ASPEED_UDC(dev);
+    int i;
+
+    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 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;
+}
+
+static const TypeInfo aspeed_udc_types[] = {
+    {
+        .name          = TYPE_ASPEED_UDC,
+        .parent        = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(AspeedUDCState),
+        .class_init    = aspeed_udc_class_init,
+    },
+};
+
+DEFINE_TYPES(aspeed_udc_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5db7ce057f4a..eae06369b02f 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/usb/Kconfig b/hw/usb/Kconfig
index de95686720cb..e4df15fe7c1f 100644
--- a/hw/usb/Kconfig
+++ b/hw/usb/Kconfig
@@ -146,3 +146,6 @@ config XLNX_USB_SUBSYS
 config USB_CHIPIDEA
     bool
     select USB_EHCI_SYSBUS
+
+config ASPEED_UDC
+    bool
diff --git a/hw/usb/meson.build b/hw/usb/meson.build
index ba55c28ef69e..d4ba60a91c4d 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 67249d69c2f6..ed05304520a2 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -377,3 +377,10 @@ 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_irq(uint32_t isr, uint32_t ier, int level) "isr 0x%x, ier 0x%x, level %d"
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 22/40] hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (20 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 21/40] hw/usb/aspeed-udc: Add ASPEED UDC device controller Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 23/40] hw/usb/aspeed-udc: Add programmable endpoint DMA transfers Cédric Le Goater
                   ` (18 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Philippe Mathieu-Daudé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Present the UDC gadget side to a USB host controller as a USB device
(TYPE_ASPEED_UDC_GADGET). This is a normal QEMU USB device, so it can be
attached to any USB host controller bus, not only the BMC's own EHCI. It
links back to its controller through the "udc" property.

This patch implements the control endpoint (EP0), which is enough for the
host to enumerate the gadget. Host control transfers are handled
asynchronously: the SETUP packet is mirrored into the SETUP data buffer,
the EP0 interrupt is raised and the host packet is parked (USB_RET_ASYNC).
The guest gadget driver then drives the data and status stages by writing
UDC_EP0_CTRL; that moves data to/from the driver's DMA buffer and completes
the parked packet back to the host.

SET_ADDRESS is the exception: it is applied synchronously, because the host
controller keeps the transfer bound to address 0 until it completes.

The gadget connects to / disconnects from the host bus when the driver
sets or clears the upstream-enable (pull-up) bit, and is detached on reset.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260902021542.3194812-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/usb/aspeed-udc.h |  28 +++
 hw/usb/aspeed-udc.c         | 382 +++++++++++++++++++++++++++++++++++-
 hw/usb/Kconfig              |   1 +
 hw/usb/trace-events         |   5 +
 4 files changed, 413 insertions(+), 3 deletions(-)

diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
index 58fed5f9a2d2..ab9d016c613d 100644
--- a/include/hw/usb/aspeed-udc.h
+++ b/include/hw/usb/aspeed-udc.h
@@ -10,11 +10,19 @@
 #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.
@@ -36,14 +44,34 @@ typedef struct AspeedUDCEP {
     int index;
 } 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/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index cd773e979b60..70a22960622f 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -8,16 +8,26 @@
  * Models the ASPEED USB Device Controller (UDC). It implements one control
  * endpoint (EP0) and 4 programmable endpoints.
  *
- * This file is the system-bus side of the controller: the MMIO register map,
- * the interrupt and the soft reset. The gadget USB device presented to a host
- * controller (and the endpoint data path) is added on top of this.
+ * 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) */
@@ -47,6 +57,7 @@ REG32(UDC_EP0_CTRL, 0x30)
     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)
@@ -66,6 +77,8 @@ REG32(EP_DMA_STS, 0x0C)
     FIELD(EP_DMA_STS, RPTR,                 8, 8)
     FIELD(EP_DMA_STS, WPTR,                 0, 8)
 
+#define ASPEED_UDC_EP0_MAXPKT      64
+
 static void aspeed_udc_update_irq(AspeedUDCState *s)
 {
     bool level;
@@ -78,6 +91,176 @@ static void aspeed_udc_update_irq(AspeedUDCState *s)
     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);
+}
+
+/*
+ * 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);
@@ -100,6 +283,11 @@ static void aspeed_udc_write(void *opaque, hwaddr offset, uint64_t 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:
@@ -112,6 +300,16 @@ static void aspeed_udc_write(void *opaque, hwaddr offset, uint64_t data,
         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;
@@ -171,6 +369,7 @@ static const MemoryRegionOps aspeed_udc_ep_ops = {
 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));
@@ -182,6 +381,21 @@ static void aspeed_udc_reset_hold(Object *obj, ResetType type)
     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)
@@ -190,6 +404,12 @@ static void aspeed_udc_realize(DeviceState *dev, Error **errp)
     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,
@@ -214,6 +434,20 @@ static void aspeed_udc_realize(DeviceState *dev, Error **errp)
     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);
@@ -222,6 +456,141 @@ static void aspeed_udc_class_init(ObjectClass *klass, const void *data)
     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 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)
+{
+    /* Programmable endpoint (bulk) transfers are added in a later patch. */
+    p->status = USB_RET_STALL;
+}
+
+static void aspeed_udc_gadget_cancel_packet(USBDevice *udev, USBPacket *p)
+{
+    AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc;
+
+    if (s->ep0_packet == p) {
+        s->ep0_packet = 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[] = {
@@ -229,8 +598,15 @@ 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/Kconfig b/hw/usb/Kconfig
index e4df15fe7c1f..e8c00f813a6a 100644
--- a/hw/usb/Kconfig
+++ b/hw/usb/Kconfig
@@ -149,3 +149,4 @@ config USB_CHIPIDEA
 
 config ASPEED_UDC
     bool
+    select USB
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index ed05304520a2..098c3d617952 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -383,4 +383,9 @@ aspeed_udc_read(uint64_t offset, uint32_t value) "offset 0x%" PRIx64 " value 0x%
 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"
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 23/40] hw/usb/aspeed-udc: Add programmable endpoint DMA transfers
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (21 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 22/40] hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 24/40] hw/arm/aspeed_ast2600: Wire up the UDC Cédric Le Goater
                   ` (17 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Add the bulk data plane for the four programmable endpoints. The gadget
driver queues IN data through the descriptor-list DMA ring and arms OUT
buffers through the single-stage DMA registers; host bulk transactions
are served from / delivered into those.

The DMA mode is taken from EP_DMA_CTRL.DESC_OP_EN: IN endpoints use the
descriptor-list ring, OUT endpoints use single-stage buffers.

A transfer larger than one host packet is served across several polls,
with the host packet parked (USB_RET_ASYNC) until the gadget queues (IN)
or arms (OUT) more data, then completed from the matching DMA kick.

With this the gadget data endpoints work, e.g. a mass-storage gadget can
be enumerated and read/written end to end.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260902021542.3194812-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/usb/aspeed-udc.h |   8 +
 hw/usb/aspeed-udc.c         | 456 +++++++++++++++++++++++++++++++++++-
 hw/usb/trace-events         |   4 +
 3 files changed, 463 insertions(+), 5 deletions(-)

diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
index ab9d016c613d..7701c1aa342d 100644
--- a/include/hw/usb/aspeed-udc.h
+++ b/include/hw/usb/aspeed-udc.h
@@ -42,6 +42,14 @@ 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 {
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 70a22960622f..4c90b4f4c239 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -72,12 +72,37 @@ 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_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)
 {
@@ -97,6 +122,14 @@ static void aspeed_udc_raise_isr(AspeedUDCState *s, uint32_t 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)
  */
@@ -330,6 +363,287 @@ static const MemoryRegionOps aspeed_udc_ops = {
     },
 };
 
+/*
+ * 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;
@@ -346,10 +660,31 @@ 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, data);
-    e->regs[reg] = 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 = {
@@ -375,6 +710,8 @@ static void aspeed_udc_reset_hold(Object *obj, ResetType type)
     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 */
@@ -468,6 +805,95 @@ static void aspeed_udc_class_init(ObjectClass *klass, const void *data)
  * 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;
@@ -531,17 +957,37 @@ static void aspeed_udc_gadget_handle_control(USBDevice *udev, USBPacket *p,
 
 static void aspeed_udc_gadget_handle_data(USBDevice *udev, USBPacket *p)
 {
-    /* Programmable endpoint (bulk) transfers are added in a later patch. */
-    p->status = USB_RET_STALL;
+    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)
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 098c3d617952..80ead23358f5 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -389,3 +389,7 @@ 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"
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 24/40] hw/arm/aspeed_ast2600: Wire up the UDC
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (22 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 23/40] hw/usb/aspeed-udc: Add programmable endpoint DMA transfers Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 25/40] hw/sensor: tmp105: make device state private to the implementation Cédric Le Goater
                   ` (16 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Philippe Mathieu-Daudé, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Create the USB Device Controller (UDC) at 0x1e6a2000 on the AST2600 SoC
and map its registers.

The UDC and the second EHCI host controller (EHCI2) share one SoC
interrupt line. Route both through an OR gate whose output drives that
GIC input, so either device can raise the interrupt without the other
clobbering the line.

The gadget USB device is not created by the SoC. It is a separate,
user-creatable "aspeed.udc-gadget" USB device that the user plugs onto a
USB host controller's bus; it finds its controller through the "udc"
link property, e.g.

  -device aspeed.udc-gadget,udc=/machine/soc/udc

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260902021542.3194812-5-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_soc.h |  4 ++++
 hw/arm/aspeed_ast2600.c     | 37 ++++++++++++++++++++++++++++++++++---
 2 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index cba8b7cdca58..38226d9b8049 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -37,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"
@@ -140,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"
diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c
index f7c7ec7fb1d4..1a10d0fe1ecc 100644
--- a/hw/arm/aspeed_ast2600.c
+++ b/hw/arm/aspeed_ast2600.c
@@ -34,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,
@@ -218,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),
@@ -581,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)) {
@@ -588,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)) {
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 25/40] hw/sensor: tmp105: make device state private to the implementation
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (23 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 24/40] hw/arm/aspeed_ast2600: Wire up the UDC Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 26/40] hw/sensor: tmp105: name the parent object field parent_obj Cédric Le Goater
                   ` (15 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

The TMP105State struct, its cast macro and typedef are used only within
tmp105.c. Move them out of the public header into the source file and
drop the now-unused includes; the header is reduced to the TYPE_TMP105
name, with its guard renamed to the path-derived form per current QEMU
convention.

While here, normalize the source file include order to match the other
hw/sensor devices.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-1-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/sensor/tmp105.h | 40 ++-------------------------------
 hw/sensor/tmp105.c         | 45 +++++++++++++++++++++++++++++++++-----
 2 files changed, 42 insertions(+), 43 deletions(-)

diff --git a/include/hw/sensor/tmp105.h b/include/hw/sensor/tmp105.h
index 244e2989febd..daece592e9bc 100644
--- a/include/hw/sensor/tmp105.h
+++ b/include/hw/sensor/tmp105.h
@@ -11,45 +11,9 @@
  * 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
 
 #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;
-};
 
 #endif
diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c
index c5089d74f4bf..6069c0905d91 100644
--- a/hw/sensor/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -19,16 +19,51 @@
  */
 
 #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_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;
+};
+
 FIELD(CONFIG, SHUTDOWN_MODE,        0, 1)
 FIELD(CONFIG, THERMOSTAT_MODE,      1, 1)
 FIELD(CONFIG, POLARITY,             2, 1)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 26/40] hw/sensor: tmp105: name the parent object field parent_obj
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (24 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 25/40] hw/sensor: tmp105: make device state private to the implementation Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 27/40] hw/sensor: tmp105: implement Resettable reset Cédric Le Goater
                   ` (14 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

QEMU coding conventions expect the embedded parent object of a QOM type
to be named parent_obj. Rename the TMP105State I2CSlave member
accordingly and update its references; the change is purely cosmetic.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-2-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/sensor/tmp105.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c
index 6069c0905d91..aabeb34f0837 100644
--- a/hw/sensor/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -42,7 +42,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(TMP105State, TMP105)
  */
 struct TMP105State {
     /*< private >*/
-    I2CSlave i2c;
+    I2CSlave parent_obj;
     /*< public >*/
 
     uint8_t len;
@@ -187,12 +187,12 @@ 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);
+    trace_tmp105_write(s->parent_obj.address, s->pointer);
 
     switch (s->pointer & 3) {
     case TMP105_REG_TEMPERATURE:
@@ -200,7 +200,7 @@ static void tmp105_write(TMP105State *s)
 
     case TMP105_REG_CONFIG:
         if (FIELD_EX8(s->buf[0] & ~s->config, CONFIG, SHUTDOWN_MODE)) {
-            trace_tmp105_write_shutdown(s->i2c.address);
+            trace_tmp105_write_shutdown(s->parent_obj.address);
         }
         s->config = FIELD_DP8(s->buf[0], CONFIG, ONE_SHOT, 0);
         s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)];
@@ -305,7 +305,7 @@ 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 []) {
@@ -338,7 +338,7 @@ static void tmp105_realize(DeviceState *dev, Error **errp)
 
     qdev_init_gpio_out(&i2c->qdev, &s->pin, 1);
 
-    tmp105_reset(&s->i2c);
+    tmp105_reset(&s->parent_obj);
 }
 
 static void tmp105_initfn(Object *obj)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 27/40] hw/sensor: tmp105: implement Resettable reset
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (25 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 26/40] hw/sensor: tmp105: name the parent object field parent_obj Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 28/40] hw/sensor: tmp105: enforce the configurable fault queue Cédric Le Goater
                   ` (13 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Previously the device reset routine was invoked once from realize() and
never registered with the reset subsystem, so a system or bus reset left
the TMP105 registers untouched. Convert tmp105_reset() into a proper
Resettable hold phase (tmp105_reset_hold) registered through
ResettableClass::phases.hold, and drop the manual call from realize() as
the reset framework now drives it. This matches the scheme used by the
other hw/sensor I2C devices.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-3-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/sensor/tmp105.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c
index aabeb34f0837..f7d0c738caa8 100644
--- a/hw/sensor/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -314,9 +314,9 @@ static const VMStateDescription vmstate_tmp105 = {
     }
 };
 
-static void tmp105_reset(I2CSlave *i2c)
+static void tmp105_reset_hold(Object *obj, ResetType type)
 {
-    TMP105State *s = TMP105(i2c);
+    TMP105State *s = TMP105(obj);
 
     s->temperature = 0;
     s->pointer = 0;
@@ -337,8 +337,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->parent_obj);
 }
 
 static void tmp105_initfn(Object *obj)
@@ -352,11 +350,13 @@ 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);
 
     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;
 }
 
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 28/40] hw/sensor: tmp105: enforce the configurable fault queue
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (26 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 27/40] hw/sensor: tmp105: implement Resettable reset Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 29/40] hw/sensor: tmp105: describe the temperature property Cédric Le Goater
                   ` (12 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

The fault-queue depth selected in the configuration register was decoded
but never consulted, so the ALERT pin tripped on the very first
out-of-limit conversion regardless of the programmed depth.

Track the consecutive-fault count and only change the ALERT state once
it reaches the programmed depth; any in-range conversion clears the
count. The default configuration keeps the previous behaviour. The count
is migrated through an optional subsection so existing streams stay
compatible.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-4-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/sensor/tmp105.c | 87 +++++++++++++++++++++++++++++-----------------
 1 file changed, 55 insertions(+), 32 deletions(-)

diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c
index f7d0c738caa8..5c77f991cc20 100644
--- a/hw/sensor/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -54,6 +54,7 @@ struct TMP105State {
     int16_t temperature;
     int16_t limit[2];
     int faults;
+    uint8_t fault_count;
     uint8_t alarm;
     /*
      * The TMP105 initially looks for a temperature rising above T_high;
@@ -78,43 +79,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;
         }
     }
 
@@ -204,7 +202,12 @@ static void tmp105_write(TMP105State *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));
+        if (FIELD_EX8(s->buf[0], CONFIG, ONE_SHOT) &&
+            FIELD_EX8(s->config, CONFIG, SHUTDOWN_MODE)) {
+            tmp105_alarm_update(s, true);
+        } else {
+            tmp105_interrupt_update(s);
+        }
         break;
 
     case TMP105_REG_T_LOW:
@@ -213,7 +216,7 @@ static void tmp105_write(TMP105State *s)
             s->limit[s->pointer & 1] = (int16_t)
                     ((((uint16_t) s->buf[0]) << 8) | (s->buf[1] & 0xf0));
         }
-        tmp105_alarm_update(s, false);
+        tmp105_interrupt_update(s);
         break;
     }
 }
@@ -281,6 +284,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,
@@ -292,6 +302,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,
@@ -310,6 +331,7 @@ static const VMStateDescription vmstate_tmp105 = {
     },
     .subsections = (const VMStateDescription * const []) {
         &vmstate_tmp105_detect_falling,
+        &vmstate_tmp105_fault_count,
         NULL
     }
 };
@@ -322,6 +344,7 @@ static void tmp105_reset_hold(Object *obj, ResetType type)
     s->pointer = 0;
     s->config = 0;
     s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)];
+    s->fault_count = 0;
     s->alarm = 0;
     s->detect_falling = false;
 
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 29/40] hw/sensor: tmp105: describe the temperature property
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (27 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 28/40] hw/sensor: tmp105: enforce the configurable fault queue Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 30/40] hw/arm: aspeed: guard board-local temperature-sensor aliases Cédric Le Goater
                   ` (11 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Attach a description to the dynamic "temperature" QOM property so it is
documented in the QOM introspection output (qom-list-properties). The
value is expressed in millidegrees Celsius.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-5-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/sensor/tmp105.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c
index 5c77f991cc20..fefa66171170 100644
--- a/hw/sensor/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -367,6 +367,8 @@ 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");
 }
 
 static void tmp105_class_init(ObjectClass *klass, const void *data)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 30/40] hw/arm: aspeed: guard board-local temperature-sensor aliases
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (28 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 29/40] hw/sensor: tmp105: describe the temperature property Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 31/40] hw/sensor: tmp105: add TMP75, TMP175 and LM75B variants Cédric Le Goater
                   ` (10 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

The Catalina and Fuji machines define TYPE_TMP75 (and, on Fuji,
TYPE_LM75) as local aliases of TYPE_TMP105 because no distinct model
existed. A following change adds a canonical TYPE_TMP75 to
hw/sensor/tmp105.h that would clash with these aliases.

Guard each alias with an #undef so the header definition can be
introduced without a redefinition warning, ahead of switching the boards
to the real models. No functional change.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-6-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_catalina.c | 5 +++++
 hw/arm/aspeed_ast2600_fuji.c     | 6 ++++++
 2 files changed, 11 insertions(+)

diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index 23636ab6985e..d3cc0fc5c0ac 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -21,6 +21,11 @@
 #define CATALINA_BMC_HW_STRAP2 0x00000800
 #define CATALINA_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB)
 
+/*
+ * Guard the alias against the TYPE_TMP75 that hw/sensor/tmp105.h now
+ * defines.
+ */
+#undef TYPE_TMP75
 #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 8a87c2dbee33..6318aa606b6b 100644
--- a/hw/arm/aspeed_ast2600_fuji.c
+++ b/hw/arm/aspeed_ast2600_fuji.c
@@ -14,7 +14,13 @@
 #include "hw/sensor/tmp105.h"
 #include "hw/nvram/eeprom_at24c.h"
 
+/*
+ * Guard the aliases against the TYPE_TMP75 that hw/sensor/tmp105.h now defines;
+ * TYPE_LM75 is guarded too for consistency (no LM75 model exists yet).
+ */
+#undef TYPE_LM75
 #define TYPE_LM75 TYPE_TMP105
+#undef TYPE_TMP75
 #define TYPE_TMP75 TYPE_TMP105
 #define TYPE_TMP422 "tmp422"
 
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 31/40] hw/sensor: tmp105: add TMP75, TMP175 and LM75B variants
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (29 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 30/40] hw/arm: aspeed: guard board-local temperature-sensor aliases Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 32/40] tests/qtest: tmp105: cover the ALERT fault queue Cédric Le Goater
                   ` (9 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

The TI TMP75/TMP175 and the NXP LM75B share the TMP105 register map and
control semantics, differing only in a few details. Model them as
variants of the TMP105, parameterised by a small per-type class
descriptor covering the fault-queue depths, the writable config bits,
the converter resolution, the set-point masks and the TMP75's
alert-clear on thermostat-mode change.

The variant is class data, not migrated state, so the wire format is
unchanged and all variants share the existing vmstate.

While here, align the shared shutdown handling with the hardware:
entering shutdown now clears the ALERT/OS output in interrupt mode,
correcting the base TMP105 as well.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-7-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/sensor/tmp105.h |   6 +-
 hw/sensor/tmp105.c         | 172 ++++++++++++++++++++++++++++++-------
 2 files changed, 146 insertions(+), 32 deletions(-)

diff --git a/include/hw/sensor/tmp105.h b/include/hw/sensor/tmp105.h
index daece592e9bc..0698aeead78c 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:
  *
@@ -14,6 +14,10 @@
 #ifndef HW_SENSOR_TMP105_H
 #define HW_SENSOR_TMP105_H
 
+/* TMP75, TMP175 and NXP LM75B are register-compatible with TMP105. */
 #define TYPE_TMP105 "tmp105"
+#define TYPE_TMP175 "tmp175"
+#define TYPE_TMP75  "tmp75"
+#define TYPE_LM75B  "lm75b"
 
 #endif
diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c
index fefa66171170..ece0650d703f 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,6 +16,13 @@
  *
  * 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"
@@ -31,15 +38,8 @@
 #include "migration/vmstate.h"
 #include "trace.h"
 
-OBJECT_DECLARE_SIMPLE_TYPE(TMP105State, TMP105)
+OBJECT_DECLARE_TYPE(TMP105State, TMP105Class, 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 parent_obj;
@@ -53,7 +53,7 @@ struct TMP105State {
     uint8_t config;
     int16_t temperature;
     int16_t limit[2];
-    int faults;
+    uint8_t faults;
     uint8_t fault_count;
     uint8_t alarm;
     /*
@@ -65,6 +65,27 @@ struct TMP105State {
     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)
@@ -152,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)) {
@@ -165,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:
@@ -190,6 +214,10 @@ static void tmp105_read(TMP105State *s)
 
 static void tmp105_write(TMP105State *s)
 {
+    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) {
@@ -197,14 +225,27 @@ static void tmp105_write(TMP105State *s)
         break;
 
     case TMP105_REG_CONFIG:
-        if (FIELD_EX8(s->buf[0] & ~s->config, CONFIG, SHUTDOWN_MODE)) {
+        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;
         }
-        s->config = FIELD_DP8(s->buf[0], CONFIG, ONE_SHOT, 0);
-        s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)];
-        if (FIELD_EX8(s->buf[0], CONFIG, ONE_SHOT) &&
-            FIELD_EX8(s->config, CONFIG, SHUTDOWN_MODE)) {
+        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);
         }
@@ -214,7 +255,8 @@ static void tmp105_write(TMP105State *s)
     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_interrupt_update(s);
         break;
@@ -265,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;
@@ -339,11 +382,12 @@ static const VMStateDescription vmstate_tmp105 = {
 static void tmp105_reset_hold(Object *obj, ResetType type)
 {
     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;
@@ -371,11 +415,25 @@ static void tmp105_initfn(Object *obj)
                                     "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;
@@ -383,19 +441,71 @@ static void tmp105_class_init(ObjectClass *klass, const void *data)
     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);
 
-static void tmp105_register_types(void)
+    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)
 {
-    type_register_static(&tmp105_info);
+    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;
 }
 
-type_init(tmp105_register_types)
+static void lm75b_class_init(ObjectClass *klass, const void *data)
+{
+    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;
+}
+
+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)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 32/40] tests/qtest: tmp105: cover the ALERT fault queue
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (30 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 31/40] hw/sensor: tmp105: add TMP75, TMP175 and LM75B variants Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 33/40] tests/qtest: tmp105: cover the TMP75, TMP175 and LM75B variants Cédric Le Goater
                   ` (8 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Add two qgraph tests exercising the ALERT pin, the only way the TMP105
exposes its alarm state. One checks the default single-fault behaviour;
the other checks a deeper fault queue, including the running-count reset
on an in-range conversion and its effect on both asserting and releasing
the alarm.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-8-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/tmp105-test.c | 63 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)

diff --git a/tests/qtest/tmp105-test.c b/tests/qtest/tmp105-test.c
index 3b114a50f550..29a031fb7bfe 100644
--- a/tests/qtest/tmp105-test.c
+++ b/tests/qtest/tmp105-test.c
@@ -17,6 +17,11 @@
 
 #define TMP105_TEST_ID   "tmp105-test"
 #define TMP105_TEST_ADDR 0x49
+#define TMP105_TEST_PATH "/machine/peripheral/" TMP105_TEST_ID
+
+#define TMP105_CONFIG_POL   (1 << 2)   /* ALERT active-high when set */
+#define TMP105_CONFIG_FQ_1  (0 << 3)   /* fault queue: 1 consecutive fault */
+#define TMP105_CONFIG_FQ_4  (2 << 3)   /* fault queue: 4 consecutive faults */
 
 static int qmp_tmp105_get_temperature(const char *id)
 {
@@ -105,6 +110,62 @@ 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));
+}
+
 static void tmp105_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -116,5 +177,7 @@ 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);
 }
 libqos_init(tmp105_register_nodes);
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 33/40] tests/qtest: tmp105: cover the TMP75, TMP175 and LM75B variants
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (31 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 32/40] tests/qtest: tmp105: cover the ALERT fault queue Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 34/40] tests/qtest: tmp105: cover one-shot and fault-queue write immunity Cédric Le Goater
                   ` (7 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Exercise the per-variant differences added to the model: the TMP75 and
TMP175 fault-queue depths, the TMP75 alert-clear on thermostat-mode
change, and the LM75B's reserved config bits and fixed set-point and
temperature resolutions.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-9-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/tmp105-test.c | 138 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)

diff --git a/tests/qtest/tmp105-test.c b/tests/qtest/tmp105-test.c
index 29a031fb7bfe..ea6803715a39 100644
--- a/tests/qtest/tmp105-test.c
+++ b/tests/qtest/tmp105-test.c
@@ -19,9 +19,20 @@
 #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 */
 
 static int qmp_tmp105_get_temperature(const char *id)
 {
@@ -166,6 +177,98 @@ static void test_fault_queue(void *obj, void *data, QGuestAllocator *alloc)
     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 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));
+}
+
+/*
+ * 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);
+}
+
 static void tmp105_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -179,5 +282,40 @@ static void tmp105_register_nodes(void)
     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);
+
+    /* 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);
+
+    /* 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);
 }
 libqos_init(tmp105_register_nodes);
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 34/40] tests/qtest: tmp105: cover one-shot and fault-queue write immunity
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (32 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 33/40] tests/qtest: tmp105: cover the TMP75, TMP175 and LM75B variants Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 35/40] tests/qtest: tmp105: cover shutdown clearing the ALERT across variants Cédric Le Goater
                   ` (6 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Verify that the one-shot (OS) bit triggers a conversion only in shutdown
mode and is ignored in continuous mode, and that
configuration/limit-register writes never advance the fault queue — only
real conversions do.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-10-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/tmp105-test.c | 102 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/tests/qtest/tmp105-test.c b/tests/qtest/tmp105-test.c
index ea6803715a39..e586a634d8fb 100644
--- a/tests/qtest/tmp105-test.c
+++ b/tests/qtest/tmp105-test.c
@@ -33,6 +33,8 @@
 #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)
 {
@@ -200,6 +202,102 @@ static void check_fault_queue(QI2CDevice *i2cdev, const char *id,
     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)
@@ -282,6 +380,10 @@ static void tmp105_register_nodes(void)
     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 = {
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 35/40] tests/qtest: tmp105: cover shutdown clearing the ALERT across variants
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (33 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 34/40] tests/qtest: tmp105: cover one-shot and fault-queue write immunity Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 36/40] hw/arm: sanmiguel: add Facebook SanMiguel BMC machine Cédric Le Goater
                   ` (5 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Verify that entering shutdown clears the ALERT/OS output in interrupt
mode but leaves it asserted in comparator mode, for both the TMP75 and
the LM75B.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-11-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/tmp105-test.c | 56 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/tests/qtest/tmp105-test.c b/tests/qtest/tmp105-test.c
index e586a634d8fb..504f6b720224 100644
--- a/tests/qtest/tmp105-test.c
+++ b/tests/qtest/tmp105-test.c
@@ -335,6 +335,41 @@ static void test_tmp75_tm_clears_alert(void *obj, void *data,
     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
@@ -367,6 +402,23 @@ static void test_lm75b_limits(void *obj, void *data,
     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 = {
@@ -396,6 +448,8 @@ static void tmp105_register_nodes(void)
 
     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 = {
@@ -419,5 +473,7 @@ static void tmp105_register_nodes(void)
 
     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);
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 36/40] hw/arm: sanmiguel: add Facebook SanMiguel BMC machine
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (34 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 35/40] tests/qtest: tmp105: cover shutdown clearing the ALERT across variants Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 37/40] hw/arm: sanmiguel: populate EEPROM data Cédric Le Goater
                   ` (4 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Add an AST2600-based machine for the Facebook SanMiguel BMC, derived
from the aspeed-bmc-facebook-sanmiguel.dts device tree.

The board wires up three TMP75 temperature sensors across its I2C buses,
alongside IO expanders, FRU EEPROMs, an I2C mux and an RTC, with 2 GiB
RAM, dual FMC flash and RGMII networking.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-12-1266926ba769@free.fr
[ clg: - adjusted on upstream ]
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_sanmiguel.c | 140 ++++++++++++++++++++++++++++++
 hw/arm/meson.build                |   1 +
 2 files changed, 141 insertions(+)
 create mode 100644 hw/arm/aspeed_ast2600_sanmiguel.c

diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
new file mode 100644
index 000000000000..ca07a0deaada
--- /dev/null
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -0,0 +1,140 @@
+/*
+ * 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"
+
+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(i2c[5], 0x50, 16 * KiB);
+    /* 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(i2c[9], 0x50, 16 * KiB);
+
+    /* &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(i2c[10], 0x50, 16 * KiB);
+
+    /* &i2c11 — Switch Config */
+    /* sw_config: eeprom@50 (24c64 = 8 KiB) */
+    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(i2c[13], 0x55, 16 * KiB);
+
+    /* &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(i2c[14], 0x50, 8 * KiB);
+
+    /* &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 78acb04c8b9f..bcc068de0403 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',
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 37/40] hw/arm: sanmiguel: populate EEPROM data
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (35 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 36/40] hw/arm: sanmiguel: add Facebook SanMiguel BMC machine Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 38/40] hw/arm: catalina: use the real TMP75 model Cédric Le Goater
                   ` (3 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Populate AT24C FRU EEPROMs with real data from physical device.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-13-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_sanmiguel.c | 275 +++++++++++++++++++++++++++++-
 1 file changed, 269 insertions(+), 6 deletions(-)

diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
index ca07a0deaada..599ef1701fae 100644
--- a/hw/arm/aspeed_ast2600_sanmiguel.c
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -22,6 +22,267 @@
 
 #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 */
@@ -55,7 +316,8 @@ static void sanmiguel_bmc_i2c_init(AspeedMachineState *bmc)
     /* smm_temp: lm75@48 */
     i2c_slave_create_simple(i2c[5], TYPE_TMP75, 0x48);
     /* smm_fru: eeprom@50 (24c128 = 16 KiB) */
-    at24c_eeprom_init(i2c[5], 0x50, 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);
 
@@ -80,16 +342,16 @@ static void sanmiguel_bmc_i2c_init(AspeedMachineState *bmc)
     /* pdb_temp: lm75@4e */
     i2c_slave_create_simple(i2c[9], TYPE_TMP75, 0x4e);
     /* pdb_fru: eeprom@50 (24c128 = 16 KiB) */
-    at24c_eeprom_init(i2c[9], 0x50, 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(i2c[10], 0x50, 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) */
+    /* sw_config: eeprom@50 (24c64 = 8 KiB) — blank (content not modelled) */
     at24c_eeprom_init(i2c[11], 0x50, 8 * KiB);
 
     /* &i2c12 — empty */
@@ -99,13 +361,14 @@ static void sanmiguel_bmc_i2c_init(AspeedMachineState *bmc)
     /* 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(i2c[13], 0x55, 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(i2c[14], 0x50, 8 * KiB);
+    at24c_eeprom_init_rom(i2c[14], 0x50, 8 * KiB, fio_eeprom, fio_eeprom_len);
 
     /* &i2c15 — empty */
 }
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 38/40] hw/arm: catalina: use the real TMP75 model
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (36 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 37/40] hw/arm: sanmiguel: populate EEPROM data Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 39/40] hw/arm: fuji: use the real TMP75 and LM75B temperature sensors Cédric Le Goater
                   ` (2 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

The Catalina BMC instantiates several ti,tmp75 temperature sensors. Now
that hw/sensor/tmp105.h provides a proper TMP75 model, drop the local
TYPE_TMP75 alias (and its guard) so the board instantiates the accurate
device rather than a plain TMP105.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-14-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_catalina.c | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index d3cc0fc5c0ac..ad2953dfeae4 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -21,12 +21,6 @@
 #define CATALINA_BMC_HW_STRAP2 0x00000800
 #define CATALINA_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB)
 
-/*
- * Guard the alias against the TYPE_TMP75 that hw/sensor/tmp105.h now
- * defines.
- */
-#undef TYPE_TMP75
-#define TYPE_TMP75 TYPE_TMP105
 #define TYPE_TMP421 "tmp421"
 #define TYPE_DS1338 "ds1338"
 
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 39/40] hw/arm: fuji: use the real TMP75 and LM75B temperature sensors
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (37 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 38/40] hw/arm: catalina: use the real TMP75 model Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-06 17:10 ` [PULL 40/40] tests/functional: aspeed: optionally check the device tree model on boot Cédric Le Goater
  2026-09-07 16:35 ` [PULL 00/40] aspeed queue Peter Maydell
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

The Fuji BMC instantiates many ti,tmp75 sensors and several NXP LM75B
sensors; the original board code approximated both with a plain TMP105
via local TYPE_TMP75 and TYPE_LM75 aliases (the latter mislabelling the
LM75B parts as LM75).

Now that hw/sensor/tmp105.h provides proper TMP75 and LM75B models, drop
the local aliases (and their guards) and instantiate the accurate
devices.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-15-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_fuji.c | 22 +++++++---------------
 1 file changed, 7 insertions(+), 15 deletions(-)

diff --git a/hw/arm/aspeed_ast2600_fuji.c b/hw/arm/aspeed_ast2600_fuji.c
index 6318aa606b6b..dae023938793 100644
--- a/hw/arm/aspeed_ast2600_fuji.c
+++ b/hw/arm/aspeed_ast2600_fuji.c
@@ -14,14 +14,6 @@
 #include "hw/sensor/tmp105.h"
 #include "hw/nvram/eeprom_at24c.h"
 
-/*
- * Guard the aliases against the TYPE_TMP75 that hw/sensor/tmp105.h now defines;
- * TYPE_LM75 is guarded too for consistency (no LM75 model exists yet).
- */
-#undef TYPE_LM75
-#define TYPE_LM75 TYPE_TMP105
-#undef TYPE_TMP75
-#define TYPE_TMP75 TYPE_TMP105
 #define TYPE_TMP422 "tmp422"
 
 /* Fuji hardware value */
@@ -62,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
@@ -73,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);
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PULL 40/40] tests/functional: aspeed: optionally check the device tree model on boot
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (38 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 39/40] hw/arm: fuji: use the real TMP75 and LM75B temperature sensors Cédric Le Goater
@ 2026-09-06 17:10 ` Cédric Le Goater
  2026-09-07 16:35 ` [PULL 00/40] aspeed queue Peter Maydell
  40 siblings, 0 replies; 42+ messages in thread
From: Cédric Le Goater @ 2026-09-06 17:10 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

From: Emmanuel Blot <emmanuel.blot@free.fr>

Add an optional 'dt_model' argument to do_test_arm_aspeed_openbmc().
When
provided, the helper also waits for the kernel's "Machine model: ..."
line,
letting a test confirm the booted image actually matches the expected
board
device tree rather than only the SoC revision.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260731-sanmiguel-bmc-locator-v2-16-1266926ba769@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/functional/aspeed.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tests/functional/aspeed.py b/tests/functional/aspeed.py
index 076da1036cfd..08af6b4839cf 100644
--- a/tests/functional/aspeed.py
+++ b/tests/functional/aspeed.py
@@ -8,7 +8,8 @@
 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 @@ def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04',
         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()
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PULL 00/40] aspeed queue
  2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
                   ` (39 preceding siblings ...)
  2026-09-06 17:10 ` [PULL 40/40] tests/functional: aspeed: optionally check the device tree model on boot Cédric Le Goater
@ 2026-09-07 16:35 ` Peter Maydell
  40 siblings, 0 replies; 42+ messages in thread
From: Peter Maydell @ 2026-09-07 16:35 UTC (permalink / raw)
  To: Cédric Le Goater; +Cc: qemu-arm, qemu-devel

On Sun, 6 Sept 2026 at 18:10, Cédric Le Goater <clg@redhat.com> wrote:
>
> The following changes since commit ff1d2d19d7e24893e2012d879f8e73077e17b9bd:
>
>   Merge tag 'pull-11.1-check-tcg-meson-040926-2' of https://gitlab.com/stsquad/qemu into staging (2026-09-04 16:52:58 +0100)
>
> are available in the Git repository at:
>
>   https://github.com/legoater/qemu/ tags/pull-aspeed-20260906
>
> for you to fetch changes up to 1c05d7e94da04129ea16147074fe73158fb85ff9:
>
>   tests/functional: aspeed: optionally check the device tree model on boot (2026-09-05 07:05:11 +0200)
>
> ----------------------------------------------------------------
> 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
>



Applied, thanks.

Please update the changelog at https://wiki.qemu.org/ChangeLog/11.2
for any user-visible changes.

-- PMM


^ permalink raw reply	[flat|nested] 42+ messages in thread

end of thread, other threads:[~2026-09-07 16:36 UTC | newest]

Thread overview: 42+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
2026-09-06 17:09 ` [PULL 01/40] hw/misc/aspeed_scu: Set both AST2600 protection key registers on reset Cédric Le Goater
2026-09-06 17:09 ` [PULL 02/40] hw/i2c/aspeed_i2c: Latch received bytes for SMBus block reads Cédric Le Goater
2026-09-06 17:09 ` [PULL 03/40] hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords() Cédric Le Goater
2026-09-06 17:09 ` [PULL 04/40] hw/usb/hcd-ehci: Make get_dwords() return bool Cédric Le Goater
2026-09-06 17:09 ` [PULL 05/40] hw/usb/hcd-ehci: Handle get_dwords() failures in async writeback Cédric Le Goater
2026-09-06 17:09 ` [PULL 06/40] hw/misc/aspeed_scu: Support the second random number generator Cédric Le Goater
2026-09-06 17:09 ` [PULL 07/40] tests/qtest/aspeed-hace-utils: Replace g_printerr() with g_test_skip() Cédric Le Goater
2026-09-06 17:09 ` [PULL 08/40] hw/misc/aspeed_acry: Add ASPEED ACRY model Cédric Le Goater
2026-09-06 17:09 ` [PULL 09/40] hw/arm/aspeed_ast2600: Introduce the ACRY SRAM Cédric Le Goater
2026-09-06 17:09 ` [PULL 10/40] hw/arm/aspeed_ast2600: Wire up the ACRY model Cédric Le Goater
2026-09-06 17:09 ` [PULL 11/40] tests/qtest/aspeed-acry-test: Add RSA ModExp tests Cédric Le Goater
2026-09-06 17:09 ` [PULL 12/40] qapi/crypto: Add ECDSA algorithm and curve id Cédric Le Goater
2026-09-06 17:09 ` [PULL 13/40] crypto/akcipher: Support ECDSA sign/verify with gcrypt Cédric Le Goater
2026-09-06 17:09 ` [PULL 14/40] crypto/akcipher: Support ECDSA sign/verify with nettle Cédric Le Goater
2026-09-06 17:09 ` [PULL 15/40] tests/crypto: Add ECDSA sign/verify tests Cédric Le Goater
2026-09-06 17:09 ` [PULL 16/40] hw/arm/aspeed_ast10x0: Remove obsolete unimplemented SBC mapping Cédric Le Goater
2026-09-06 17:09 ` [PULL 17/40] hw/misc/aspeed_sbc: Increase register space to 0x1000 Cédric Le Goater
2026-09-06 17:09 ` [PULL 18/40] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Cédric Le Goater
2026-09-06 17:10 ` [PULL 19/40] hw/misc/aspeed_sbc: Support the ECDSA verify command Cédric Le Goater
2026-09-06 17:10 ` [PULL 20/40] tests/qtest: Add ASPEED SBC ECDSA engine test Cédric Le Goater
2026-09-06 17:10 ` [PULL 21/40] hw/usb/aspeed-udc: Add ASPEED UDC device controller Cédric Le Goater
2026-09-06 17:10 ` [PULL 22/40] hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device Cédric Le Goater
2026-09-06 17:10 ` [PULL 23/40] hw/usb/aspeed-udc: Add programmable endpoint DMA transfers Cédric Le Goater
2026-09-06 17:10 ` [PULL 24/40] hw/arm/aspeed_ast2600: Wire up the UDC Cédric Le Goater
2026-09-06 17:10 ` [PULL 25/40] hw/sensor: tmp105: make device state private to the implementation Cédric Le Goater
2026-09-06 17:10 ` [PULL 26/40] hw/sensor: tmp105: name the parent object field parent_obj Cédric Le Goater
2026-09-06 17:10 ` [PULL 27/40] hw/sensor: tmp105: implement Resettable reset Cédric Le Goater
2026-09-06 17:10 ` [PULL 28/40] hw/sensor: tmp105: enforce the configurable fault queue Cédric Le Goater
2026-09-06 17:10 ` [PULL 29/40] hw/sensor: tmp105: describe the temperature property Cédric Le Goater
2026-09-06 17:10 ` [PULL 30/40] hw/arm: aspeed: guard board-local temperature-sensor aliases Cédric Le Goater
2026-09-06 17:10 ` [PULL 31/40] hw/sensor: tmp105: add TMP75, TMP175 and LM75B variants Cédric Le Goater
2026-09-06 17:10 ` [PULL 32/40] tests/qtest: tmp105: cover the ALERT fault queue Cédric Le Goater
2026-09-06 17:10 ` [PULL 33/40] tests/qtest: tmp105: cover the TMP75, TMP175 and LM75B variants Cédric Le Goater
2026-09-06 17:10 ` [PULL 34/40] tests/qtest: tmp105: cover one-shot and fault-queue write immunity Cédric Le Goater
2026-09-06 17:10 ` [PULL 35/40] tests/qtest: tmp105: cover shutdown clearing the ALERT across variants Cédric Le Goater
2026-09-06 17:10 ` [PULL 36/40] hw/arm: sanmiguel: add Facebook SanMiguel BMC machine Cédric Le Goater
2026-09-06 17:10 ` [PULL 37/40] hw/arm: sanmiguel: populate EEPROM data Cédric Le Goater
2026-09-06 17:10 ` [PULL 38/40] hw/arm: catalina: use the real TMP75 model Cédric Le Goater
2026-09-06 17:10 ` [PULL 39/40] hw/arm: fuji: use the real TMP75 and LM75B temperature sensors Cédric Le Goater
2026-09-06 17:10 ` [PULL 40/40] tests/functional: aspeed: optionally check the device tree model on boot Cédric Le Goater
2026-09-07 16:35 ` [PULL 00/40] aspeed queue Peter Maydell

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.