All of lore.kernel.org
 help / color / mirror / Atom feed
* [PULL 00/83] aspeed queue
@ 2026-08-11 16:28 Cédric Le Goater
  2026-08-11 16:28 ` [PULL 01/83] hw/arm/aspeed: Add missing PCI_EXPRESS -> PCIE_PORT Kconfig dependency Cédric Le Goater
                   ` (83 more replies)
  0 siblings, 84 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Cédric Le Goater

The following changes since commit 84f07211cc5b4fc6a371559bf8a5de4fb068e648:

  Update version for v11.1.0 release (2026-08-11 10:04:46 -0400)

are available in the Git repository at:

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

for you to fetch changes up to 0375f6498eee6188089924bb766c653492ff8857:

  tests/qtest/aspeed-hace: Test the crypto command on the AST2700 (2026-08-11 18:25:58 +0200)

----------------------------------------------------------------
aspeed queue:

* Fixes missing Kconfig dependencies for Aspeed boards
* Adds 64-bit addressing support to the EHCI USB controller model.
  Enable it on the AST2700
* Extends Aspeed SMC qtest coverage with fast-read, DOR and QOR
  read modes
* Introduces a separate Aspeed2700SCUState type and shares the SCUIO,
  FMC and SCU instances across the AST2700 PSP, SSP and TSP
  coprocessors
* Adds Data FIFO-based flash access for the AST2700 FMC controller
* Adds the ADC128D818 12-bit 8-channel ADC sensor device with tests,
  wired up on the Anacapa board
* Reworks the PCA9552/PCA9555 GPIO/LED driver: polarity inversion,
  datasheet-conformant command handling, GPIO QOM properties, reset
  via the Resettable interface, and extensive qtest coverage
* Reworks the PCA9554 GPIO driver: output-to-input reflection,
  PCA9536 support, pin direction property, and qtest coverage
* Adds PCA9555 IO expanders and temperature sensors to the Catalina
  board
* Adds AST2700 I2C master buffer mode support
* Updates ASPEED functional tests to SDK v11.03 and Zephyr SDK v03.08
* Adds AES-GCM support to the QEMU crypto cipher layer (gcrypt,
  nettle, gnutls backends) with unit tests
* Adds crypto (AES) command emulation to the Aspeed HACE model:
  direct access, scatter-gather, CTR, GCM modes, 64-bit DMA, with
  qtest coverage on AST2500, AST2600, AST1030 and AST2700

----------------------------------------------------------------
Carlo Marcelo Arenas Belón (1):
      hw/arm/aspeed: avoid sign mismatch on sscanf for uart property

Cédric Le Goater (4):
      tests/qtest: aspeed_smc: Introduce read_page_mem_fn for page read helpers
      tests/qtest: aspeed_smc: Add fast-read test coverage
      tests/qtest: aspeed_smc: Add Dual Output Read (DOR) test coverage
      tests/qtest: aspeed_smc: Add Quad Output Read (QOR) test coverage

Emmanuel Blot (32):
      hw/sensor: adc128d818: add 12-bit 8-channel ADC device
      tests/qtest: adc128d818: add test harness and register access
      tests/qtest: adc128d818: test voltage and temperature conversion
      tests/qtest: adc128d818: test limit interrupts
      tests/qtest: adc128d818: test operating modes and power control
      hw/arm/aspeed: anacapa: use ASCII in comments
      hw/arm: anacapa: add ADC128D818 devices
      hw/gpio: pca9552: register types with DEFINE_TYPES()
      hw/gpio: pca9552: move PCA955xState definition out of the header
      hw/gpio: pca9552: rename I2CSlave member to parent_obj
      hw/gpio: pca9552: default description to the instantiated type name
      hw/gpio: pca9552: declare pca9555 device as an alias of pca9535 device
      hw/gpio: pca9552: use the Resettable interface instead of legacy reset
      hw/gpio: pca9552: apply input polarity inversion on read
      hw/gpio: pca9552: conform GPIO command handling to the datasheet
      hw/gpio: pca9552: expose GPIO pins as pin%d QOM properties
      tests/qtest: add PCA9555 register access tests
      tests/qtest: pca9555: test output-to-input reflection and pull-ups
      tests/qtest: pca9555: test polarity inversion
      tests/qtest: pca9555: test auto-increment and command wrapping
      tests/qtest: pca9552: test behaviour specific to the LED variant
      hw/gpio: pca9554: add PCA9536 support
      hw/gpio: pca9554: add hw-dir property honoring the configured pin direction
      hw/gpio: pca9554: reflect push-pull outputs in the input register
      hw/gpio: pca9554: expose pin%d as a string property
      tests/qtest: add PCA9554 register access tests
      tests/qtest: pca9554: test output-to-input reflection and pull-ups
      tests/qtest: pca9554: test polarity inversion
      tests/qtest: pca9554: test absence of command auto-increment
      tests/qtest: pca9554: test the PCA9536 4-bit variant
      hw/arm: catalina: model PCA9555 IO expanders with their own type
      hw/arm: catalina: add NIC and FIO temperature sensors

Jamin Lin (43):
      hw/usb/hcd-ehci: Change descriptor addresses to 64-bit with migration compatibility
      hw/usb/hcd-ehci: Add property to advertise 64-bit addressing capability
      hw/usb/hcd-ehci: Implement 64-bit QH descriptor addressing
      hw/usb/hcd-ehci: Implement 64-bit qTD descriptor addressing
      hw/usb/hcd-ehci: Implement 64-bit iTD descriptor addressing
      hw/usb/hcd-ehci: Implement 64-bit siTD descriptor addressing
      hw/usb/hcd-ehci: Add ctrldssegment-default property
      hw/arm/aspeed_ast27x0: Set EHCI ctrldssegment-default
      hw/arm/aspeed_ast27x0: Enable 64-bit EHCI DMA addressing
      tests/functional/aarch64/test_aspeed_ast2700: Add USB EHCI test for AST2700 A1/A2
      hw/misc/aspeed_scu: Introduce Aspeed2700SCUState
      hw/arm/aspeed: Use Aspeed2700SCUState for AST2700 users
      hw/arm/aspeed_ast27x0: Move SCU link into AST27x0 coprocessors
      hw/misc/aspeed_scu: Add separate reset handler for AST2700 SCUIO
      hw/arm/aspeed_ast27x0: Pass realized PSP SoC to SSP/TSP initialization
      hw/arm/ast27x0: Share single SCUIO instance across PSP, SSP, and TSP
      hw/arm/ast27x0: Share FMC controller with SSP and TSP
      hw/ssi/aspeed_smc: Add Data FIFO-based flash access support for AST2700
      tests/qtest/ast2700-smc-test: Add Data FIFO mode test
      hw/i2c/aspeed_i2c: Support the AST2700 master buffer mode
      tests/functional/aarch64/test_aspeed_ast2700a2: Update ASPEED SDK v11.03
      tests/functional/aarch64/test_aspeed_ast2700a1: Update ASPEED SDK v11.03
      tests/functional/aarch64/test_aspeed_ast2700fc: Update ASPEED SDK v11.03
      tests/functional/arm/test_aspeed_ast2600_sdk: Update ASPEED SDK v11.03
      tests/functional/arm/test_aspeed_ast2500_sdk: Update ASPEED SDK v11.03
      tests/functional/arm/test_aspeed_ast1030: Update ASPEED Zephyr SDK v03.08
      tests/functional/arm/test_aspeed_ast1060: Update ASPEED ZEPHYR PROJECT v03.07
      hw/misc/aspeed_hace: Support the crypto command in direct access mode
      tests/qtest/aspeed-hace: Test the crypto command on the AST2500
      hw/misc/aspeed_hace: Support scatter-gather mode for the crypto command
      hw/misc/aspeed_hace: Support the CTR mode for the crypto command
      tests/qtest/aspeed-hace: Test the crypto command on the AST2600
      tests/qtest/aspeed-hace: Test the crypto command on the AST1030
      crypto/cipher: Add GCM to QCryptoCipherMode
      crypto/cipher: Add setaad/gettag for AEAD modes
      crypto/cipher-gcrypt: Implement AES-GCM
      crypto/cipher-nettle: Implement AES-GCM
      crypto/cipher-gnutls: Implement AES-GCM
      tests/unit/test-crypto-cipher: Test AES-GCM mode
      hw/misc/aspeed_hace: Support 64-bit DMA for the crypto command
      hw/misc/aspeed_hace: Support the AES-GCM mode for the crypto command
      hw/misc/aspeed_hace: Enable the crypto command on the AST2700
      tests/qtest/aspeed-hace: Test the crypto command on the AST2700

Philippe Mathieu-Daudé (3):
      hw/arm/aspeed: Add missing PCI_EXPRESS -> PCIE_PORT Kconfig dependency
      hw/arm/aspeed: Add missing Kconfig dependencies on required components
      hw/arm/aspeed: Add missing Kconfig dependencies on optional components

 qapi/crypto.json                                   |   4 +-
 crypto/cipherpriv.h                                |   8 +
 hw/usb/hcd-ehci.h                                  |  42 +-
 include/crypto/cipher.h                            |  36 +
 include/hw/arm/aspeed_ast1700.h                    |   2 +-
 include/hw/arm/aspeed_coprocessor.h                |  16 +-
 include/hw/arm/aspeed_soc.h                        |   1 +
 include/hw/gpio/pca9552.h                          |  31 +-
 include/hw/gpio/pca9554.h                          |   3 +
 include/hw/i2c/aspeed_i2c.h                        |   2 +
 include/hw/misc/aspeed_hace.h                      |   1 -
 include/hw/misc/aspeed_scu.h                       |   5 +
 include/hw/sensor/adc128d818.h                     |  14 +
 include/hw/ssi/aspeed_smc.h                        |   3 +-
 tests/qtest/aspeed-hace-utils.h                    |  20 +
 tests/qtest/aspeed-smc-utils.h                     |  35 +-
 crypto/cipher.c                                    |  32 +
 hw/arm/aspeed.c                                    |   2 +-
 hw/arm/aspeed_ast2600_anacapa.c                    |  27 +-
 hw/arm/aspeed_ast2600_catalina.c                   |  54 +-
 hw/arm/aspeed_ast27x0-fc.c                         |  38 +-
 hw/arm/aspeed_ast27x0-ssp.c                        |  56 +-
 hw/arm/aspeed_ast27x0-tsp.c                        |  56 +-
 hw/arm/aspeed_ast27x0.c                            |  21 +-
 hw/arm/aspeed_coprocessor_common.c                 |   2 -
 hw/core/machine.c                                  |   2 +
 hw/gpio/pca9552.c                                  | 256 ++++--
 hw/gpio/pca9554.c                                  | 175 +++--
 hw/i2c/aspeed_i2c.c                                |  79 ++
 hw/misc/aspeed_hace.c                              | 461 ++++++++++-
 hw/misc/aspeed_scu.c                               |  20 +-
 hw/sensor/adc128d818.c                             | 696 +++++++++++++++++
 hw/ssi/aspeed_smc.c                                | 117 ++-
 hw/usb/hcd-ehci.c                                  | 214 ++++--
 tests/qtest/adc128d818-test.c                      | 856 +++++++++++++++++++++
 tests/qtest/aspeed-hace-utils.c                    | 693 +++++++++++++++++
 tests/qtest/aspeed-smc-utils.c                     | 294 ++++++-
 tests/qtest/aspeed_hace-test.c                     |  18 +
 tests/qtest/aspeed_smc-test.c                      |  71 +-
 tests/qtest/ast2700-hace-test.c                    |   9 +
 tests/qtest/ast2700-smc-test.c                     |  16 +
 tests/qtest/pca9552-test.c                         |  76 ++
 tests/qtest/pca9554-test.c                         | 223 ++++++
 tests/qtest/pca9555-test.c                         | 251 ++++++
 tests/unit/test-crypto-cipher.c                    | 240 ++++++
 crypto/cipher-gcrypt.c.inc                         | 101 +++
 crypto/cipher-gnutls.c.inc                         | 154 ++++
 crypto/cipher-nettle.c.inc                         | 128 +++
 hw/arm/Kconfig                                     |   7 +-
 hw/pci-host/Kconfig                                |   1 +
 hw/sensor/Kconfig                                  |   4 +
 hw/sensor/meson.build                              |   1 +
 hw/sensor/trace-events                             |   8 +
 hw/usb/trace-events                                |  26 +-
 tests/functional/aarch64/test_aspeed_ast2700a1.py  |  34 +-
 tests/functional/aarch64/test_aspeed_ast2700a2.py  |  34 +-
 tests/functional/aarch64/test_aspeed_ast2700fc.py  |  21 +-
 tests/functional/arm/test_aspeed_ast1030.py        |  12 +-
 tests/functional/arm/test_aspeed_ast1060.py        |  14 +-
 tests/functional/arm/test_aspeed_ast2500_sdk.py    |   8 +-
 .../functional/arm/test_aspeed_ast2500_sdk_515.py  |   8 +-
 tests/functional/arm/test_aspeed_ast2600_sdk.py    |   8 +-
 .../functional/arm/test_aspeed_ast2600_sdk_515.py  |   8 +-
 .../functional/arm/test_aspeed_ast2600_sdk_otp.py  |   8 +-
 tests/qtest/meson.build                            |   9 +-
 65 files changed, 5404 insertions(+), 468 deletions(-)
 create mode 100644 include/hw/sensor/adc128d818.h
 create mode 100644 hw/sensor/adc128d818.c
 create mode 100644 tests/qtest/adc128d818-test.c
 create mode 100644 tests/qtest/pca9554-test.c
 create mode 100644 tests/qtest/pca9555-test.c



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

* [PULL 01/83] hw/arm/aspeed: Add missing PCI_EXPRESS -> PCIE_PORT Kconfig dependency
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 02/83] hw/arm/aspeed: Add missing Kconfig dependencies on required components Cédric Le Goater
                   ` (82 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Philippe Mathieu-Daudé, qemu-stable, Cédric Le Goater

From: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>

Add the missing Kconfig dependency on PCIE_PORT to avoid the
following runtime error:

  Type 'aspeed.pcie-root-port' is missing its parent 'pcie-root-port-base'

No need to have ASPEED_SOC select PCI_EXPRESS since it is
already selected by PCI_EXPRESS_ASPEED.

Cc: qemu-stable@nongnu.org
Fixes: 2af56518fa9 ("hw/pci-host/aspeed: Add AST2600 PCIe Root Port and make address configurable")
Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260709164103.37614-2-philmd@oss.qualcomm.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/Kconfig      | 1 -
 hw/pci-host/Kconfig | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 82e0bc2e7004..00e2d9380332 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -557,7 +557,6 @@ config ASPEED_SOC
     select MAX31785
     select FSI_APB2OPB_ASPEED
     select AT24C
-    select PCI_EXPRESS
     select PCI_EXPRESS_ASPEED
 
 config MPS2
diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig
index 8cbb8304a3ff..bda6e161f1c8 100644
--- a/hw/pci-host/Kconfig
+++ b/hw/pci-host/Kconfig
@@ -49,6 +49,7 @@ config PCI_I440FX
 config PCI_EXPRESS_ASPEED
     bool
     select PCI_EXPRESS
+    select PCIE_PORT
 
 config PCI_EXPRESS_Q35
     bool
-- 
2.55.0



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

* [PULL 02/83] hw/arm/aspeed: Add missing Kconfig dependencies on required components
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
  2026-08-11 16:28 ` [PULL 01/83] hw/arm/aspeed: Add missing PCI_EXPRESS -> PCIE_PORT Kconfig dependency Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 03/83] hw/arm/aspeed: Add missing Kconfig dependencies on optional components Cédric Le Goater
                   ` (81 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Philippe Mathieu-Daudé, qemu-stable, Cédric Le Goater

From: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>

Add various missing Kconfig dependencies of the Aspeed SoCs:

- USB EHCI (commit bfdd34f1caa "hw/arm: ast2400/ast2500: Wire up
  EHCI controllers")
- OR-IRQ (commit d831c5fd868 "aspeed/intc: Add AST2700 support")
- SDHCI (commit 2bea128c3d0 "hw/sd/aspeed_sdhci: New device"
  and f25c0ae1079 "aspeed/soc: Add AST2600 support")

This fixes the following runtime issues:

  qemu-system-aarch64: unknown type 'platform-ehci-usb'
  qemu-system-aarch64: unknown type 'or-irq'
  qemu-system-aarch64: unknown type 'generic-sdhci'

Cc: qemu-stable@nongnu.org
Fixes: 2bea128c3d0 ("hw/sd/aspeed_sdhci: New device")
Fixes: f25c0ae1079 ("aspeed/soc: Add AST2600 support")
Fixes: d831c5fd868 ("aspeed/intc: Add AST2700 support")
Fixes: bfdd34f1caa ("hw/arm: ast2400/ast2500: Wire up EHCI controllers")
Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260709164103.37614-3-philmd@oss.qualcomm.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/Kconfig | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 00e2d9380332..6f5b2add66ed 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -551,6 +551,7 @@ config ASPEED_SOC
     select TMP105
     select TMP421
     select EMC141X
+    select OR_IRQ
     select UNIMP
     select LED
     select PMBUS
@@ -558,6 +559,8 @@ config ASPEED_SOC
     select FSI_APB2OPB_ASPEED
     select AT24C
     select PCI_EXPRESS_ASPEED
+    select USB_EHCI_SYSBUS
+    select SDHCI
 
 config MPS2
     bool
-- 
2.55.0



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

* [PULL 03/83] hw/arm/aspeed: Add missing Kconfig dependencies on optional components
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
  2026-08-11 16:28 ` [PULL 01/83] hw/arm/aspeed: Add missing PCI_EXPRESS -> PCIE_PORT Kconfig dependency Cédric Le Goater
  2026-08-11 16:28 ` [PULL 02/83] hw/arm/aspeed: Add missing Kconfig dependencies on required components Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 04/83] hw/usb/hcd-ehci: Change descriptor addresses to 64-bit with migration compatibility Cédric Le Goater
                   ` (80 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Philippe Mathieu-Daudé, Cédric Le Goater

From: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>

Add missing Kconfig optional dependencies to avoid the
following runtime error:

  qemu-system-aarch64: -device loader,force-raw=on,addr=0x400000000,file=./u-boot.bin: 'loader' is not a valid device model name
  qemu-system-aarch64: -device e1000e,netdev=net1,bus=pcie.2: 'e1000e' is not a valid device model name

Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260709164103.37614-4-philmd@oss.qualcomm.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/Kconfig | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 6f5b2add66ed..5869f0f9e895 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -533,7 +533,9 @@ config ASPEED_SOC
     bool
     default y
     depends on TCG && ARM
+    imply GENERIC_LOADER
     imply PCI_DEVICES
+    imply E1000E_PCI_EXPRESS
     select DS1338
     select FTGMAC100
     select I2C
-- 
2.55.0



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

* [PULL 04/83] hw/usb/hcd-ehci: Change descriptor addresses to 64-bit with migration compatibility
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (2 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 03/83] hw/arm/aspeed: Add missing Kconfig dependencies on optional components Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 05/83] hw/usb/hcd-ehci: Add property to advertise 64-bit addressing capability Cédric Le Goater
                   ` (79 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Philippe Mathieu-Daudé

From: Jamin Lin <jamin_lin@aspeedtech.com>

Change internal EHCI descriptor addresses from uint32_t to uint64_t.

The following fields are updated:
- EHCIPacket::qtdaddr
- EHCIQueue::{qhaddr, qtdaddr}
- EHCIState::{a_fetch_addr, p_fetch_addr}

Update get_dwords() and put_dwords() to take 64-bit addresses and
propagate the type change through the descriptor traversal paths.

Adjust NLPTR_GET() to operate on 64-bit values:

    #define NLPTR_GET(x) ((x) & ~0x1fULL)

so that link pointer masking works correctly when descriptor
addresses exceed 32-bit space. The previous mask (0xffffffe0)
implicitly truncated addresses to 32 bits.

This patch does not change the on-wire descriptor layout yet.
It only removes the internal 32-bit address limit and prepares
for later patches that will add full 64-bit QH/qTD/iTD/siTD support.

Update the EHCI trace-events prototypes for QH, qTD, iTD, and siTD to
use uint64_t for the address argument and print it with PRIx64. This
ensures full 64-bit addresses are shown in trace output and improves
debugging of queue heads and transfer descriptors.

Migration compatibility:

To preserve backward migration compatibility, keep the legacy 32-bit
fetch address fields (a_fetch_addr_32, p_fetch_addr_32) alongside the
new 64-bit fields.

Migration format is selected using a machine compat property
"x-migrate-fetch-addr-64bit":

- Old machine types migrate 32-bit fetch addresses
- New machine types migrate full 64-bit fetch addresses

This is implemented using VMSTATE_UINT32_TEST() and
VMSTATE_UINT64_TEST() so that only the appropriate format is migrated.

In pre_save, the 32-bit shadow fields are populated when migrating
to old machine types. In post_load, the 32-bit values are restored
into the 64-bit fields when loading old migration streams.

No functional change.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.h   | 30 +++++++++++++-----
 hw/core/machine.c   |  2 ++
 hw/usb/hcd-ehci.c   | 74 +++++++++++++++++++++++++++++++--------------
 hw/usb/trace-events | 24 +++++++--------
 4 files changed, 89 insertions(+), 41 deletions(-)

diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
index d038ee1e310c..cbc32c296df6 100644
--- a/hw/usb/hcd-ehci.h
+++ b/hw/usb/hcd-ehci.h
@@ -208,7 +208,7 @@ struct EHCIPacket {
     QTAILQ_ENTRY(EHCIPacket) next;
 
     EHCIqtd qtd;           /* copy of current QTD (being worked on) */
-    uint32_t qtdaddr;      /* address QTD read from                 */
+    uint64_t qtdaddr;      /* address QTD read from                 */
 
     USBPacket packet;
     QEMUSGList sgl;
@@ -229,8 +229,8 @@ struct EHCIQueue {
      * when guest removes an entry (doorbell, handshake sequence)
      */
     EHCIqh qh;             /* copy of current QH (being worked on) */
-    uint32_t qhaddr;       /* address QH read from                 */
-    uint32_t qtdaddr;      /* address QTD read from                */
+    uint64_t qhaddr;       /* address QH read from                 */
+    uint64_t qtdaddr;      /* address QTD read from                */
     int last_pid;          /* pid of last packet executed          */
     USBDevice *dev;
     QTAILQ_HEAD(, EHCIPacket) packets;
@@ -256,6 +256,11 @@ struct EHCIState {
 
     /* properties */
     uint32_t maxframes;
+    /*
+     * Controls migration stream compatibility for old machine types.
+     * Old machine types only transfer 32-bit fetch addresses.
+     */
+    bool migrate_fetch_addr_64bit;
 
     /*
      *  EHCI spec version 1.0 Section 2.3
@@ -293,9 +298,18 @@ struct EHCIState {
     EHCIQueueHead aqueues;
     EHCIQueueHead pqueues;
 
-    /* which address to look at next */
-    uint32_t a_fetch_addr;
-    uint32_t p_fetch_addr;
+    /*
+     * which address to look at next
+     *
+     * Migration compatibility fields for old machine types that only
+     * support 32-bit fetch addresses in the migration stream.
+     *
+     * New machine types migrate the full 64-bit runtime fetch address.
+     */
+    uint32_t migrate_a_fetch_addr;
+    uint32_t migrate_p_fetch_addr;
+    uint64_t a_fetch_addr;
+    uint64_t p_fetch_addr;
 
     USBPacket ipacket;
     QEMUSGList isgl;
@@ -308,7 +322,9 @@ struct EHCIState {
 };
 
 #define DEFINE_EHCI_COMMON_PROPERTIES(_state) \
-    DEFINE_PROP_UINT32("maxframes", _state, ehci.maxframes, 128)
+    DEFINE_PROP_UINT32("maxframes", _state, ehci.maxframes, 128), \
+    DEFINE_PROP_BOOL("x-migrate-fetch-addr-64bit", _state, \
+                     ehci.migrate_fetch_addr_64bit, true)
 
 extern const VMStateDescription vmstate_ehci;
 
diff --git a/hw/core/machine.c b/hw/core/machine.c
index 73b4d82b4a1d..eb5e3809d6d1 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -51,6 +51,8 @@ GlobalProperty hw_compat_11_0[] = {
     { TYPE_ARM_SMMUV3, "ssidsize", "0" },
     { TYPE_ARM_SMMUV3, "oas", "44" },
     { "migration", "switchover-ack-legacy", "on" },
+    { "sysbus-ehci-usb", "x-migrate-fetch-addr-64bit", "off" },
+    { "pci-ehci-usb", "x-migrate-fetch-addr-64bit", "off" },
 };
 const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
 
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 28a60e4c1a78..1a0c58011539 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -72,7 +72,7 @@ typedef enum {
 } EHCI_STATES;
 
 /* macros for accessing fields within next link pointer entry */
-#define NLPTR_GET(x)             ((x) & 0xffffffe0)
+#define NLPTR_GET(x)             ((x) & ~0x1fULL)
 #define NLPTR_TYPE_GET(x)        (((x) >> 1) & 3)
 #define NLPTR_TBIT(x)            ((x) & 1)  /* 1=invalid, 0=valid */
 
@@ -287,7 +287,7 @@ static int ehci_get_state(EHCIState *s, int async)
     return async ? s->astate : s->pstate;
 }
 
-static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
+static void ehci_set_fetch_addr(EHCIState *s, int async, uint64_t addr)
 {
     if (async) {
         s->a_fetch_addr = addr;
@@ -296,7 +296,7 @@ static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
     }
 }
 
-static int ehci_get_fetch_addr(EHCIState *s, int async)
+static uint64_t ehci_get_fetch_addr(EHCIState *s, int async)
 {
     return async ? s->a_fetch_addr : s->p_fetch_addr;
 }
@@ -373,7 +373,7 @@ static inline bool ehci_periodic_enabled(EHCIState *s)
 }
 
 /* Get an array of dwords from main memory */
-static inline int get_dwords(EHCIState *ehci, uint32_t addr,
+static inline int get_dwords(EHCIState *ehci, uint64_t addr,
                              uint32_t *buf, int num)
 {
     int i;
@@ -395,7 +395,7 @@ static inline int get_dwords(EHCIState *ehci, uint32_t addr,
 }
 
 /* Put an array of dwords in to main memory */
-static inline int put_dwords(EHCIState *ehci, uint32_t addr,
+static inline int put_dwords(EHCIState *ehci, uint64_t addr,
                              uint32_t *buf, int num)
 {
     int i;
@@ -549,7 +549,7 @@ static void ehci_free_packet(EHCIPacket *p)
 
 /* queue management */
 
-static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async)
+static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint64_t addr, int async)
 {
     EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
     EHCIQueue *q;
@@ -622,7 +622,7 @@ static void ehci_free_queue(EHCIQueue *q, const char *warn)
     g_free(q);
 }
 
-static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
+static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint64_t addr,
                                         int async)
 {
     EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
@@ -1135,7 +1135,7 @@ static void ehci_flush_qh(EHCIQueue *q)
 {
     uint32_t *qh = (uint32_t *) &q->qh;
     uint32_t dwords = sizeof(EHCIqh) >> 2;
-    uint32_t addr = NLPTR_GET(q->qhaddr);
+    uint64_t addr = NLPTR_GET(q->qhaddr);
 
     put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
 }
@@ -1406,12 +1406,13 @@ static int ehci_execute(EHCIPacket *p, const char *action)
 /* 4.7.2 */
 static int ehci_process_itd(EHCIState *ehci,
                             EHCIitd *itd,
-                            uint32_t addr)
+                            uint64_t addr)
 {
     USBDevice *dev;
     USBEndpoint *ep;
     uint32_t i, len, pid, dir, devaddr, endp;
-    uint32_t pg, off, ptr1, ptr2, max, mult;
+    uint32_t pg, off, max, mult;
+    uint64_t ptr1, ptr2;
 
     ehci->periodic_sched_active = PERIODIC_ACTIVE;
 
@@ -1528,7 +1529,7 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
     EHCIqh qh;
     int i = 0;
     int again = 0;
-    uint32_t entry = ehci->asynclistaddr;
+    uint64_t entry = ehci->asynclistaddr;
 
     /* set reclamation flag at start event (4.8.6) */
     if (async) {
@@ -1578,7 +1579,7 @@ out:
 static int ehci_state_fetchentry(EHCIState *ehci, int async)
 {
     int again = 0;
-    uint32_t entry = ehci_get_fetch_addr(ehci, async);
+    uint64_t entry = ehci_get_fetch_addr(ehci, async);
 
     if (NLPTR_TBIT(entry)) {
         ehci_set_state(ehci, async, EST_ACTIVE);
@@ -1611,8 +1612,8 @@ static int ehci_state_fetchentry(EHCIState *ehci, int async)
     default:
         /* TODO: handle FSTN type */
         qemu_log_mask(LOG_GUEST_ERROR,
-                      "FETCHENTRY: entry at 0x%x is of type %u "
-                      "which is not supported yet\n",
+                      "FETCHENTRY: entry at %" PRIx64 " is of type %" PRIu64
+                      " which is not supported yet\n",
                       entry, NLPTR_TYPE_GET(entry));
         return -1;
     }
@@ -1623,7 +1624,7 @@ out:
 
 static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
 {
-    uint32_t entry;
+    uint64_t entry;
     EHCIQueue *q;
     EHCIqh qh;
 
@@ -1712,7 +1713,7 @@ out:
 
 static int ehci_state_fetchitd(EHCIState *ehci, int async)
 {
-    uint32_t entry;
+    uint64_t entry;
     EHCIitd itd;
 
     assert(!async);
@@ -1738,7 +1739,7 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async)
 
 static int ehci_state_fetchsitd(EHCIState *ehci, int async)
 {
-    uint32_t entry;
+    uint64_t entry;
     EHCIsitd sitd;
 
     assert(!async);
@@ -1802,7 +1803,7 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
     EHCIqtd qtd;
     EHCIPacket *p;
     int again = 1;
-    uint32_t addr;
+    uint64_t addr;
 
     addr = NLPTR_GET(q->qtdaddr);
     if (get_dwords(q->ehci, addr +  8, &qtd.token,   1) < 0) {
@@ -1885,7 +1886,7 @@ static int ehci_fill_queue(EHCIPacket *p)
     USBEndpoint *ep = p->packet.ep;
     EHCIQueue *q = p->queue;
     EHCIqtd qtd = p->qtd;
-    uint32_t qtdaddr;
+    uint64_t qtdaddr;
 
     for (;;) {
         if (NLPTR_TBIT(qtd.next) != 0) {
@@ -2008,7 +2009,8 @@ static int ehci_state_executing(EHCIQueue *q)
 static int ehci_state_writeback(EHCIQueue *q)
 {
     EHCIPacket *p = QTAILQ_FIRST(&q->packets);
-    uint32_t *qtd, addr;
+    uint32_t *qtd;
+    uint64_t addr;
     int again = 0;
 
     /*  Write back the QTD from the QH area */
@@ -2414,6 +2416,18 @@ static USBBusOps ehci_bus_ops_standalone = {
     .wakeup_endpoint = ehci_wakeup_endpoint,
 };
 
+static bool ehci_fetch_addr_64_needed(void *opaque, int version_id)
+{
+    EHCIState *s = opaque;
+
+    return s->migrate_fetch_addr_64bit;
+}
+
+static bool ehci_fetch_addr_32_needed(void *opaque, int version_id)
+{
+    return !ehci_fetch_addr_64_needed(opaque, version_id);
+}
+
 static int usb_ehci_pre_save(void *opaque)
 {
     EHCIState *ehci = opaque;
@@ -2424,6 +2438,11 @@ static int usb_ehci_pre_save(void *opaque)
     ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS;
     ehci->frindex = new_frindex;
 
+    if (!ehci->migrate_fetch_addr_64bit) {
+        ehci->migrate_a_fetch_addr = ehci->a_fetch_addr;
+        ehci->migrate_p_fetch_addr = ehci->p_fetch_addr;
+    }
+
     return 0;
 }
 
@@ -2444,6 +2463,11 @@ static int usb_ehci_post_load(void *opaque, int version_id)
         }
     }
 
+    if (!s->migrate_fetch_addr_64bit) {
+        s->a_fetch_addr = s->migrate_a_fetch_addr;
+        s->p_fetch_addr = s->migrate_p_fetch_addr;
+    }
+
     return 0;
 }
 
@@ -2504,8 +2528,14 @@ const VMStateDescription vmstate_ehci = {
         /* schedule state */
         VMSTATE_UINT32(astate, EHCIState),
         VMSTATE_UINT32(pstate, EHCIState),
-        VMSTATE_UINT32(a_fetch_addr, EHCIState),
-        VMSTATE_UINT32(p_fetch_addr, EHCIState),
+        VMSTATE_UINT32_TEST(migrate_a_fetch_addr, EHCIState,
+                            ehci_fetch_addr_32_needed),
+        VMSTATE_UINT32_TEST(migrate_p_fetch_addr, EHCIState,
+                            ehci_fetch_addr_32_needed),
+        VMSTATE_UINT64_TEST(a_fetch_addr, EHCIState,
+                            ehci_fetch_addr_64_needed),
+        VMSTATE_UINT64_TEST(p_fetch_addr, EHCIState,
+                            ehci_fetch_addr_64_needed),
         VMSTATE_END_OF_LIST()
     }
 };
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 0d4318dcf143..8c90688bb3e7 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -86,15 +86,15 @@ usb_ehci_portsc_write(uint32_t addr, uint32_t port, uint32_t val) "wr mmio 0x%04
 usb_ehci_portsc_change(uint32_t addr, uint32_t port, uint32_t new, uint32_t old) "ch mmio 0x%04x [port %d] = 0x%x (old: 0x%x)"
 usb_ehci_usbsts(const char *sts, int state) "usbsts %s %d"
 usb_ehci_state(const char *schedule, const char *state) "%s schedule %s"
-usb_ehci_qh_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ 0x%08x: next 0x%08x qtds 0x%08x,0x%08x,0x%08x"
-usb_ehci_qh_fields(uint32_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ 0x%08x - rl %d, mplen %d, eps %d, ep %d, dev %d"
-usb_ehci_qh_bits(uint32_t addr, int c, int h, int dtc, int i) "QH @ 0x%08x - c %d, h %d, dtc %d, i %d"
+usb_ehci_qh_ptrs(void *q, uint64_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ 0x%" PRIx64 ": next 0x%08x qtds 0x%08x,0x%08x,0x%08x"
+usb_ehci_qh_fields(uint64_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ 0x%" PRIx64 " - rl %d, mplen %d, eps %d, ep %d, dev %d"
+usb_ehci_qh_bits(uint64_t addr, int c, int h, int dtc, int i) "QH @ 0x%" PRIx64 " - c %d, h %d, dtc %d, i %d"
 usb_ehci_qh_tbytes(uint32_t tbytes) "updating tbytes to %d"
-usb_ehci_qtd_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ 0x%08x: next 0x%08x altnext 0x%08x"
-usb_ehci_qtd_fields(uint32_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ 0x%08x - tbytes %d, cpage %d, cerr %d, pid %d"
-usb_ehci_qtd_bits(uint32_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ 0x%08x - ioc %d, active %d, halt %d, babble %d, xacterr %d"
-usb_ehci_itd(uint32_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ 0x%08x: next 0x%08x - mplen %d, mult %d, ep %d, dev %d"
-usb_ehci_sitd(uint32_t addr, uint32_t nxt, uint32_t active) "ITD @ 0x%08x: next 0x%08x - active %d"
+usb_ehci_qtd_ptrs(void *q, uint64_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ 0x%" PRIx64 ": next 0x%08x altnext 0x%08x"
+usb_ehci_qtd_fields(uint64_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ 0x%" PRIx64 " - tbytes %d, cpage %d, cerr %d, pid %d"
+usb_ehci_qtd_bits(uint64_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ 0x%" PRIx64 " - ioc %d, active %d, halt %d, babble %d, xacterr %d"
+usb_ehci_itd(uint64_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ 0x%" PRIx64 ": next 0x%08x - mplen %d, mult %d, ep %d, dev %d"
+usb_ehci_sitd(uint64_t addr, uint32_t nxt, uint32_t active) "SITD @ 0x%" PRIx64 ": next 0x%08x - active %d"
 usb_ehci_port_attach(uint32_t port, const char *owner, const char *device) "attach port #%d, owner %s, device %s"
 usb_ehci_port_detach(uint32_t port, const char *owner) "detach port #%d, owner %s"
 usb_ehci_port_reset(uint32_t port, int enable) "reset port #%d - %d"
@@ -104,15 +104,15 @@ usb_ehci_port_resume(uint32_t port) "port #%d"
 usb_ehci_port_disable(uint32_t port) "port #%d"
 usb_ehci_queue_action(void *q, const char *action) "q %p: %s"
 usb_ehci_packet_action(void *q, void *p, const char *action) "q %p p %p: %s"
-usb_ehci_packet_submit(uint32_t qhaddr, uint32_t next, uint32_t qtdaddr, int pid, size_t len, int endp, int status, int actual_length) "qh=0x%x, next=0x%x, qtd=0x%x, pid=0x%x, len=%zd, endp=0x%x, status=%d, actual_length=%d"
+usb_ehci_packet_submit(uint64_t qhaddr, uint32_t next, uint64_t qtdaddr, int pid, size_t len, int endp, int status, int actual_length) "qh=0x%" PRIx64 ", next=0x%x, qtd=0x%" PRIx64 ", pid=0x%x, len=%zd, endp=0x%x, status=%d, actual_length=%d"
 usb_ehci_irq(uint32_t level, uint32_t frindex, uint32_t sts, uint32_t mask) "level %d, frindex 0x%04x, sts 0x%x, mask 0x%x"
 usb_ehci_guest_bug(const char *reason) "%s"
 usb_ehci_doorbell_ring(void) ""
 usb_ehci_doorbell_ack(void) ""
 usb_ehci_dma_error(void) ""
-usb_ehci_execute_complete(uint32_t qhaddr, uint32_t next, uint32_t qtdaddr, int status, int actual_length) "qhaddr=0x%x, next=0x%x, qtdaddr=0x%x, status=%d, actual_length=%d"
-usb_ehci_fetchqh_reclaim_done(uint32_t qhaddr) "QH 0x%08x H-bit set, reclamation status reset - done processing"
-usb_ehci_fetchqh_dbg(uint32_t qhaddr, uint32_t h, uint32_t halt, uint32_t active, uint32_t next) "QH 0x%08x (h 0x%x halt 0x%x active 0x%x) next 0x%08x"
+usb_ehci_execute_complete(uint64_t qhaddr, uint32_t next, uint64_t qtdaddr, int status, int actual_length) "qhaddr=0x%" PRIx64 ", next=0x%x, qtdaddr=0x%" PRIx64 ", status=%d, actual_length=%d"
+usb_ehci_fetchqh_reclaim_done(uint64_t qhaddr) "QH 0x%" PRIx64 " H-bit set, reclamation status reset - done processing"
+usb_ehci_fetchqh_dbg(uint64_t qhaddr, uint32_t h, uint32_t halt, uint32_t active, uint32_t next) "QH 0x%" PRIx64 " (h 0x%x halt 0x%x active 0x%x) next 0x%08x"
 usb_ehci_periodic_state_advance(uint32_t frame, uint32_t list, uint32_t entry) "frame=%d, list=0x%x, entry=0x%x"
 usb_ehci_skipped_uframes(uint64_t skipped_uframes) "skipped %" PRIu64 " uframes"
 usb_ehci_log(const char *msg) "%s"
-- 
2.55.0



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

* [PULL 05/83] hw/usb/hcd-ehci: Add property to advertise 64-bit addressing capability
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (3 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 04/83] hw/usb/hcd-ehci: Change descriptor addresses to 64-bit with migration compatibility Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 06/83] hw/usb/hcd-ehci: Implement 64-bit QH descriptor addressing Cédric Le Goater
                   ` (78 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Philippe Mathieu-Daudé

From: Jamin Lin <jamin_lin@aspeedtech.com>

Introduce a new boolean property, "caps-64bit-addr", to control
HCCPARAMS[0] (64-bit Addressing Capability).

When enabled, the EHCI controller advertises support for 64-bit
address memory pointers as defined in the EHCI specification
(Table 2-7, HCCPARAMS). This allows software to use the 64-bit
data structure formats described in Appendix B.

When disabled (default), the controller reports 32-bit addressing
capability and uses the standard 32-bit data structures.

The EHCI CTRLDSSEGMENT register provides the upper 32 bits [63:32] used to
form 64-bit addresses for EHCI control data structures. Per EHCI 1.0
spec section 2.3.5, when the HCCPARAMS 64-bit Addressing Capability bit
is zero, CTRLDSSEGMENT is not used: software cannot write it and reads
must return zero.

Add a capability check in the operational register write handler and
reject guest writes to CTRLDSSEGMENT when 64-bit addressing is
not enabled.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.h |  5 ++++-
 hw/usb/hcd-ehci.c | 14 +++++++++++++-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
index cbc32c296df6..f739e6371b23 100644
--- a/hw/usb/hcd-ehci.h
+++ b/hw/usb/hcd-ehci.h
@@ -261,6 +261,7 @@ struct EHCIState {
      * Old machine types only transfer 32-bit fetch addresses.
      */
     bool migrate_fetch_addr_64bit;
+    bool caps_64bit_addr;
 
     /*
      *  EHCI spec version 1.0 Section 2.3
@@ -324,7 +325,9 @@ struct EHCIState {
 #define DEFINE_EHCI_COMMON_PROPERTIES(_state) \
     DEFINE_PROP_UINT32("maxframes", _state, ehci.maxframes, 128), \
     DEFINE_PROP_BOOL("x-migrate-fetch-addr-64bit", _state, \
-                     ehci.migrate_fetch_addr_64bit, true)
+                     ehci.migrate_fetch_addr_64bit, true), \
+    DEFINE_PROP_BOOL("caps-64bit-addr", _state, \
+                     ehci.caps_64bit_addr, false)
 
 extern const VMStateDescription vmstate_ehci;
 
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 1a0c58011539..e65ea825b021 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -1109,6 +1109,15 @@ static void ehci_opreg_write(void *ptr, hwaddr addr,
         }
         break;
 
+    case CTRLDSSEGMENT:
+        if (!s->caps_64bit_addr) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "ehci: write to CTRLDSSEGMENT while "
+                          "64-bit addressing capability is disabled\n");
+            return;
+        }
+        break;
+
     case ASYNCLISTADDR:
         if (ehci_async_enabled(s)) {
             qemu_log_mask(LOG_GUEST_ERROR,
@@ -2554,6 +2563,9 @@ void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp)
                    s->maxframes);
         return;
     }
+    if (s->caps_64bit_addr) {
+        s->caps[0x08] |= BIT(0);
+    }
 
     memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
     memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
@@ -2613,7 +2625,7 @@ void usb_ehci_init(EHCIState *s, DeviceState *dev)
     s->caps[0x05] = 0x00;        /* No companion ports at present */
     s->caps[0x06] = 0x00;
     s->caps[0x07] = 0x00;
-    s->caps[0x08] = 0x80;        /* We can cache whole frame, no 64-bit */
+    s->caps[0x08] = 0x80;        /* We can cache whole frame */
     s->caps[0x0a] = 0x00;
     s->caps[0x0b] = 0x00;
 
-- 
2.55.0



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

* [PULL 06/83] hw/usb/hcd-ehci: Implement 64-bit QH descriptor addressing
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (4 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 05/83] hw/usb/hcd-ehci: Add property to advertise 64-bit addressing capability Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 07/83] hw/usb/hcd-ehci: Implement 64-bit qTD " Cédric Le Goater
                   ` (77 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

EHCI supports 64-bit control data structure addressing when the
64-bit Addressing Capability bit in HCCPARAMS is set. In that mode,
the CTRLDSSEGMENT register supplies the upper 32 bits which are
concatenated with 32-bit link pointer fields to form full 64-bit
descriptor addresses (EHCI 1.0, section 2.3.5 and Appendix B).

The current implementation assumes 32-bit QH descriptor addresses
and directly uses link pointer values without applying the
CTRLDSSEGMENT upper dword.

Introduce a helper, ehci_get_desc_addr(), to construct full 64-bit
descriptor addresses when 64-bit capability is enabled. Update QH
traversal paths (async list walk, horizontal QH link, and periodic
schedule entry handling) to use the translated 64-bit addresses.

EHCI 64-bit buffer pointer fields are defined in Appendix B as
split 32-bit low/high parts located at separate offsets, rather
than a single contiguous 64-bit field. Therefore, the buffer
pointers cannot be represented as uint64_t bufptr[5] without
violating the descriptor layout defined by the specification.

Introduce ehci_get_buf_addr() to construct full 64-bit buffer
addresses from bufptr[] and bufptr_hi[] fields. Use this helper
when calculating transfer buffer addresses so that data buffers
above 4GB are correctly handled.

Also add bufptr_hi[5] to EHCIqh to support 64-bit buffer pointer
fields as defined in Appendix B.

When 64-bit capability is disabled, descriptor addresses remain
32-bit and existing behaviour is unchanged.

Note: Similar split 64-bit buffer pointer handling is required for
qTD, iTD and siTD descriptors, which will be addressed in follow-up
changes.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.h   |  4 +++
 hw/usb/hcd-ehci.c   | 72 +++++++++++++++++++++++++++++++++++----------
 hw/usb/trace-events |  2 +-
 3 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
index f739e6371b23..8732264fdebb 100644
--- a/hw/usb/hcd-ehci.h
+++ b/hw/usb/hcd-ehci.h
@@ -141,6 +141,9 @@ typedef struct EHCIqtd {
 #define QTD_BUFPTR_SH                 12
 } EHCIqtd;
 
+/* QH overlay: altnext_qtd, token, bufptr[5], bufptr_hi[5] */
+#define EHCI_QH_OVERLAY_COUNT 12
+
 /*
  * EHCI spec version 1.0 Section 3.6
  */
@@ -194,6 +197,7 @@ typedef struct EHCIqh {
 #define BUFPTR_FRAMETAG_MASK          0x0000001f
 #define BUFPTR_SBYTES_MASK            0x00000fe0
 #define BUFPTR_SBYTES_SH              5
+    uint32_t bufptr_hi[5];
 } EHCIqh;
 
 enum async_state {
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index e65ea825b021..fd5be7430ef6 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -96,6 +96,15 @@ typedef enum {
     *data = val; \
     } while (0)
 
+/*
+ * EHCIqh / EHCIqtd / EHCIitd are sized to always include the extended
+ * high buffer pointer fields from EHCI 1.0 Appendix B. When 64-bit
+ * addressing capability is not advertised to the guest, the descriptors
+ * in guest memory only have the classic 32-bit layout, so DMA transfers
+ * must not read or write past that boundary.
+ */
+#define EHCI_QH_DWORDS_32   (offsetof(EHCIqh, bufptr_hi) / sizeof(uint32_t))
+
 static const char *ehci_state_names[] = {
     [EST_INACTIVE]     = "INACTIVE",
     [EST_ACTIVE]       = "ACTIVE",
@@ -147,6 +156,28 @@ static const char *addr2str(hwaddr addr)
     return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr);
 }
 
+static uint64_t ehci_get_buf_addr(const EHCIState *s, uint32_t hi,
+                                  uint32_t lo, uint32_t lo_mask)
+{
+    uint64_t addr = lo & lo_mask;
+
+    if (s->caps_64bit_addr) {
+        addr = deposit64(addr, 32, 32, hi);
+    }
+
+    return addr;
+}
+
+static uint64_t ehci_get_desc_addr(const EHCIState *s, uint32_t lo)
+{
+    return ehci_get_buf_addr(s, s->ctrldssegment, lo, UINT32_MAX);
+}
+
+static uint32_t ehci_qh_dwords(const EHCIState *s)
+{
+    return s->caps_64bit_addr ? (sizeof(EHCIqh) >> 2) : EHCI_QH_DWORDS_32;
+}
+
 static void ehci_trace_usbsts(uint32_t mask, int state)
 {
     /* interrupts */
@@ -440,7 +471,7 @@ static bool ehci_verify_qh(EHCIQueue *q, EHCIqh *qh)
         (qh->current_qtd != q->qh.current_qtd) ||
         (q->async && qh->next_qtd != q->qh.next_qtd) ||
         (memcmp(&qh->altnext_qtd, &q->qh.altnext_qtd,
-                                 7 * sizeof(uint32_t)) != 0) ||
+                EHCI_QH_OVERLAY_COUNT * sizeof(uint32_t)) != 0) ||
         (q->dev != NULL && q->dev->addr != devaddr)) {
         return false;
     } else {
@@ -487,8 +518,9 @@ static void ehci_writeback_async_complete_packet(EHCIPacket *p)
     int state;
 
     /* Verify the qh + qtd, like we do when going through fetchqh & fetchqtd */
+    memset(&qh, 0, sizeof(qh));
     get_dwords(q->ehci, NLPTR_GET(q->qhaddr),
-               (uint32_t *) &qh, sizeof(EHCIqh) >> 2);
+               (uint32_t *) &qh, ehci_qh_dwords(q->ehci));
     get_dwords(q->ehci, NLPTR_GET(q->qtdaddr),
                (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2);
     if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) {
@@ -1143,7 +1175,7 @@ static void ehci_opreg_write(void *ptr, hwaddr addr,
 static void ehci_flush_qh(EHCIQueue *q)
 {
     uint32_t *qh = (uint32_t *) &q->qh;
-    uint32_t dwords = sizeof(EHCIqh) >> 2;
+    uint32_t dwords = ehci_qh_dwords(q->ehci);
     uint64_t addr = NLPTR_GET(q->qhaddr);
 
     put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
@@ -1538,7 +1570,9 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
     EHCIqh qh;
     int i = 0;
     int again = 0;
-    uint64_t entry = ehci->asynclistaddr;
+    uint64_t entry = 0;
+
+    entry = ehci_get_desc_addr(ehci, ehci->asynclistaddr);
 
     /* set reclamation flag at start event (4.8.6) */
     if (async) {
@@ -1548,9 +1582,10 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
     ehci_queues_rip_unused(ehci, async);
 
     /*  Find the head of the list (4.9.1.1) */
+    memset(&qh, 0, sizeof(qh));
     for (i = 0; i < MAX_QH; i++) {
         if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
-                       sizeof(EHCIqh) >> 2) < 0) {
+                       ehci_qh_dwords(ehci)) < 0) {
             return 0;
         }
         ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
@@ -1566,8 +1601,8 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
             goto out;
         }
 
-        entry = qh.next;
-        if (entry == ehci->asynclistaddr) {
+        entry = ehci_get_desc_addr(ehci, qh.next);
+        if (entry == ehci_get_desc_addr(ehci, ehci->asynclistaddr)) {
             break;
         }
     }
@@ -1651,8 +1686,9 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
         goto out;
     }
 
+    memset(&qh, 0, sizeof(qh));
     if (get_dwords(ehci, NLPTR_GET(q->qhaddr),
-                   (uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) {
+                   (uint32_t *) &qh, ehci_qh_dwords(ehci)) < 0) {
         q = NULL;
         goto out;
     }
@@ -1693,7 +1729,7 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
     }
 
     if (trace_event_get_state_backends(TRACE_USB_EHCI_FETCHQH_DBG)) {
-        if (q->qhaddr != q->qh.next) {
+        if (q->qhaddr != ehci_get_desc_addr(ehci, q->qh.next)) {
             trace_usb_ehci_fetchqh_dbg(q->qhaddr,
                                        q->qh.epchar & QH_EPCHAR_H,
                                        q->qh.token & QTD_TOKEN_HALT,
@@ -1876,10 +1912,12 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
 
 static int ehci_state_horizqh(EHCIQueue *q)
 {
+    uint64_t addr;
     int again = 0;
 
-    if (ehci_get_fetch_addr(q->ehci, q->async) != q->qh.next) {
-        ehci_set_fetch_addr(q->ehci, q->async, q->qh.next);
+    addr = ehci_get_desc_addr(q->ehci, q->qh.next);
+    if (ehci_get_fetch_addr(q->ehci, q->async) != addr) {
+        ehci_set_fetch_addr(q->ehci, q->async, addr);
         ehci_set_state(q->ehci, q->async, EST_FETCHENTRY);
         again = 1;
     } else {
@@ -2205,6 +2243,8 @@ static void ehci_advance_periodic_state(EHCIState *ehci)
     uint32_t entry;
     uint32_t list;
     const int async = 0;
+    uint64_t entry64;
+    uint64_t list64;
 
     /* 4.6 */
 
@@ -2229,12 +2269,14 @@ static void ehci_advance_periodic_state(EHCIState *ehci)
             break;
         }
         list |= ((ehci->frindex & 0x1ff8) >> 1);
-
-        if (get_dwords(ehci, list, &entry, 1) < 0) {
+        list64 = ehci_get_desc_addr(ehci, list);
+        if (get_dwords(ehci, list64, &entry, 1) < 0) {
             break;
         }
-        trace_usb_ehci_periodic_state_advance(ehci->frindex / 8, list, entry);
-        ehci_set_fetch_addr(ehci, async, entry);
+        entry64 = ehci_get_desc_addr(ehci, entry);
+        trace_usb_ehci_periodic_state_advance(ehci->frindex / 8,
+                                              list64, entry64);
+        ehci_set_fetch_addr(ehci, async, entry64);
         ehci_set_state(ehci, async, EST_FETCHENTRY);
         ehci_advance_state(ehci, async);
         ehci_queues_rip_unused(ehci, async);
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 8c90688bb3e7..67249d69c2f6 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -113,7 +113,7 @@ usb_ehci_dma_error(void) ""
 usb_ehci_execute_complete(uint64_t qhaddr, uint32_t next, uint64_t qtdaddr, int status, int actual_length) "qhaddr=0x%" PRIx64 ", next=0x%x, qtdaddr=0x%" PRIx64 ", status=%d, actual_length=%d"
 usb_ehci_fetchqh_reclaim_done(uint64_t qhaddr) "QH 0x%" PRIx64 " H-bit set, reclamation status reset - done processing"
 usb_ehci_fetchqh_dbg(uint64_t qhaddr, uint32_t h, uint32_t halt, uint32_t active, uint32_t next) "QH 0x%" PRIx64 " (h 0x%x halt 0x%x active 0x%x) next 0x%08x"
-usb_ehci_periodic_state_advance(uint32_t frame, uint32_t list, uint32_t entry) "frame=%d, list=0x%x, entry=0x%x"
+usb_ehci_periodic_state_advance(uint32_t frame, uint64_t list, uint64_t entry) "frame=%d, list=0x%" PRIx64 ", entry=0x%" PRIx64
 usb_ehci_skipped_uframes(uint64_t skipped_uframes) "skipped %" PRIu64 " uframes"
 usb_ehci_log(const char *msg) "%s"
 
-- 
2.55.0



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

* [PULL 07/83] hw/usb/hcd-ehci: Implement 64-bit qTD descriptor addressing
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (5 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 06/83] hw/usb/hcd-ehci: Implement 64-bit QH descriptor addressing Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 08/83] hw/usb/hcd-ehci: Implement 64-bit iTD " Cédric Le Goater
                   ` (76 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

EHCI supports 64-bit addressing when the 64-bit Addressing Capability
bit in HCCPARAMS is set. In that mode, the CTRLDSSEGMENT register
provides the upper 32 bits that are concatenated with 32-bit link
pointer values to form 64-bit control data structure addresses
(EHCI 1.0, section 2.3.5 and Appendix B).

qTD link pointers (current_qtd/next_qtd/altnext_qtd and qTD.next)
are stored as 32-bit values in the data structures and must be
expanded to full 64-bit descriptor addresses when 64-bit mode is
enabled. Update the qTD traversal paths to use ehci_get_desc_addr()
when following link pointers.

Appendix B also defines high dword fields for qTD buffer pointers.
Add bufptr_hi[5] to EHCIqtd and extend qTD fetch and QH overlay
handling to load and propagate the high buffer pointer fields.

When 64-bit capability is disabled, descriptor and buffer addresses
remain 32-bit and existing behaviour is unchanged.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-5-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.h |  1 +
 hw/usb/hcd-ehci.c | 33 ++++++++++++++++++++++++---------
 2 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
index 8732264fdebb..7fed20690683 100644
--- a/hw/usb/hcd-ehci.h
+++ b/hw/usb/hcd-ehci.h
@@ -139,6 +139,7 @@ typedef struct EHCIqtd {
     uint32_t bufptr[5];               /* Standard buffer pointer */
 #define QTD_BUFPTR_MASK               0xfffff000
 #define QTD_BUFPTR_SH                 12
+    uint32_t bufptr_hi[5];
 } EHCIqtd;
 
 /* QH overlay: altnext_qtd, token, bufptr[5], bufptr_hi[5] */
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index fd5be7430ef6..947af86ac420 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -104,6 +104,7 @@ typedef enum {
  * must not read or write past that boundary.
  */
 #define EHCI_QH_DWORDS_32   (offsetof(EHCIqh, bufptr_hi) / sizeof(uint32_t))
+#define EHCI_QTD_DWORDS_32  (offsetof(EHCIqtd, bufptr_hi) / sizeof(uint32_t))
 
 static const char *ehci_state_names[] = {
     [EST_INACTIVE]     = "INACTIVE",
@@ -178,6 +179,11 @@ static uint32_t ehci_qh_dwords(const EHCIState *s)
     return s->caps_64bit_addr ? (sizeof(EHCIqh) >> 2) : EHCI_QH_DWORDS_32;
 }
 
+static uint32_t ehci_qtd_dwords(const EHCIState *s)
+{
+    return s->caps_64bit_addr ? (sizeof(EHCIqtd) >> 2) : EHCI_QTD_DWORDS_32;
+}
+
 static void ehci_trace_usbsts(uint32_t mask, int state)
 {
     /* interrupts */
@@ -486,7 +492,8 @@ static bool ehci_verify_qtd(EHCIPacket *p, EHCIqtd *qtd)
             (p->qtd.next != qtd->next)) ||
         (!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd->altnext)) ||
         p->qtd.token != qtd->token ||
-        p->qtd.bufptr[0] != qtd->bufptr[0]) {
+        p->qtd.bufptr[0] != qtd->bufptr[0] ||
+        p->qtd.bufptr_hi[0] != qtd->bufptr_hi[0]) {
         return false;
     } else {
         return true;
@@ -519,10 +526,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, sizeof(EHCIqtd) >> 2);
+               (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci));
     if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) {
         p->async = EHCI_ASYNC_INITIALIZED;
         ehci_free_packet(p);
@@ -1215,6 +1223,7 @@ static int ehci_qh_do_overlay(EHCIQueue *q)
 
     for (i = 0; i < 5; i++) {
         q->qh.bufptr[i] = p->qtd.bufptr[i];
+        q->qh.bufptr_hi[i] = p->qtd.bufptr_hi[i];
     }
 
     if (!(q->qh.epchar & QH_EPCHAR_DTC)) {
@@ -1248,7 +1257,8 @@ static int ehci_init_transfer(EHCIPacket *p)
             return -1;
         }
 
-        page  = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
+        page = ehci_get_buf_addr(p->queue->ehci, p->qtd.bufptr_hi[cpage],
+                                 p->qtd.bufptr[cpage], QTD_BUFPTR_MASK);
         page += offset;
         plen  = bytes;
         if (plen > 4096 - offset) {
@@ -1744,7 +1754,7 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
     } else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
                (NLPTR_TBIT(q->qh.current_qtd) == 0) &&
                (q->qh.current_qtd != 0)) {
-        q->qtdaddr = q->qh.current_qtd;
+        q->qtdaddr = ehci_get_desc_addr(ehci, q->qh.current_qtd);
         ehci_set_state(ehci, async, EST_FETCHQTD);
 
     } else {
@@ -1822,14 +1832,14 @@ static int ehci_state_advqueue(EHCIQueue *q)
      */
     if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
         (NLPTR_TBIT(q->qh.altnext_qtd) == 0)) {
-        q->qtdaddr = q->qh.altnext_qtd;
+        q->qtdaddr = ehci_get_desc_addr(q->ehci, q->qh.altnext_qtd);
         ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
 
     /*
      *  next qTD is valid
      */
     } else if (NLPTR_TBIT(q->qh.next_qtd) == 0) {
-        q->qtdaddr = q->qh.next_qtd;
+        q->qtdaddr = ehci_get_desc_addr(q->ehci, q->qh.next_qtd);
         ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
 
     /*
@@ -1855,10 +1865,14 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
         return 0;
     }
     barrier();
+    memset(qtd.bufptr_hi, 0, sizeof(qtd.bufptr_hi));
     if (get_dwords(q->ehci, addr +  0, &qtd.next,    1) < 0 ||
         get_dwords(q->ehci, addr +  4, &qtd.altnext, 1) < 0 ||
         get_dwords(q->ehci, addr + 12, qtd.bufptr,
-                   ARRAY_SIZE(qtd.bufptr)) < 0) {
+                   ARRAY_SIZE(qtd.bufptr)) < 0 ||
+        (q->ehci->caps_64bit_addr &&
+         get_dwords(q->ehci, addr + offsetof(EHCIqtd, bufptr_hi),
+                    qtd.bufptr_hi, ARRAY_SIZE(qtd.bufptr_hi)) < 0)) {
         return 0;
     }
     ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
@@ -1939,7 +1953,7 @@ static int ehci_fill_queue(EHCIPacket *p)
         if (NLPTR_TBIT(qtd.next) != 0) {
             break;
         }
-        qtdaddr = qtd.next;
+        qtdaddr = ehci_get_desc_addr(q->ehci, qtd.next);
         /*
          * Detect circular td lists, Windows creates these, counting on the
          * active bit going low after execution to make the queue stop.
@@ -1949,8 +1963,9 @@ static int ehci_fill_queue(EHCIPacket *p)
                 goto leave;
             }
         }
+        memset(qtd.bufptr_hi, 0, sizeof(qtd.bufptr_hi));
         if (get_dwords(q->ehci, NLPTR_GET(qtdaddr),
-                       (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2) < 0) {
+                       (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci)) < 0) {
             return -1;
         }
         ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd);
-- 
2.55.0



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

* [PULL 08/83] hw/usb/hcd-ehci: Implement 64-bit iTD descriptor addressing
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (6 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 07/83] hw/usb/hcd-ehci: Implement 64-bit qTD " Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 09/83] hw/usb/hcd-ehci: Implement 64-bit siTD " Cédric Le Goater
                   ` (75 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

EHCI supports 64-bit control data structure addressing when the
64-bit Addressing Capability bit in HCCPARAMS is set. In that mode,
the CTRLDSSEGMENT register provides the upper 32 bits that are
concatenated with 32-bit link pointer values to form full 64-bit
descriptor addresses (EHCI 1.0, section 2.3.5 and Appendix B).

iTD link pointers are stored as 32-bit values and must be expanded
to full 64-bit descriptor addresses when 64-bit mode is enabled.
Update the iTD traversal path to use ehci_get_desc_addr() when
following link pointers.

Appendix B also defines high dword fields for iTD buffer pointers.
Add bufptr_hi[7] to EHCIitd and use ehci_get_buf_addr() to construct
full 64-bit buffer addresses from bufptr[] and bufptr_hi[] fields
when processing isochronous transfers. This allows buffers above
4GB to be handled correctly.

When 64-bit capability is disabled, descriptor and buffer addresses
remain 32-bit and existing behaviour is unchanged.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-6-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.h |  1 +
 hw/usb/hcd-ehci.c | 20 +++++++++++++++-----
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
index 7fed20690683..49176f3fd282 100644
--- a/hw/usb/hcd-ehci.h
+++ b/hw/usb/hcd-ehci.h
@@ -63,6 +63,7 @@ typedef struct EHCIitd {
 #define ITD_BUFPTR_MAXPKT_SH     0
 #define ITD_BUFPTR_MULT_MASK     0x00000003
 #define ITD_BUFPTR_MULT_SH       0
+    uint32_t bufptr_hi[7];
 } EHCIitd;
 
 /*
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 947af86ac420..1a286c32e690 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -105,6 +105,7 @@ typedef enum {
  */
 #define EHCI_QH_DWORDS_32   (offsetof(EHCIqh, bufptr_hi) / sizeof(uint32_t))
 #define EHCI_QTD_DWORDS_32  (offsetof(EHCIqtd, bufptr_hi) / sizeof(uint32_t))
+#define EHCI_ITD_DWORDS_32  (offsetof(EHCIitd, bufptr_hi) / sizeof(uint32_t))
 
 static const char *ehci_state_names[] = {
     [EST_INACTIVE]     = "INACTIVE",
@@ -184,6 +185,11 @@ static uint32_t ehci_qtd_dwords(const EHCIState *s)
     return s->caps_64bit_addr ? (sizeof(EHCIqtd) >> 2) : EHCI_QTD_DWORDS_32;
 }
 
+static uint32_t ehci_itd_dwords(const EHCIState *s)
+{
+    return s->caps_64bit_addr ? (sizeof(EHCIitd) >> 2) : EHCI_ITD_DWORDS_32;
+}
+
 static void ehci_trace_usbsts(uint32_t mask, int state)
 {
     /* interrupts */
@@ -1486,7 +1492,8 @@ static int ehci_process_itd(EHCIState *ehci,
                 return -1;
             }
 
-            ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK);
+            ptr1 = ehci_get_buf_addr(ehci, itd->bufptr_hi[pg],
+                                     itd->bufptr[pg], ITD_BUFPTR_MASK);
             qemu_sglist_init(&ehci->isgl, ehci->device, 2, ehci->as);
             if (off + len > 4096) {
                 /* transfer crosses page border */
@@ -1494,7 +1501,9 @@ static int ehci_process_itd(EHCIState *ehci,
                     qemu_sglist_destroy(&ehci->isgl);
                     return -1;  /* avoid page pg + 1 */
                 }
-                ptr2 = (itd->bufptr[pg + 1] & ITD_BUFPTR_MASK);
+                ptr2 = ehci_get_buf_addr(ehci, itd->bufptr_hi[pg + 1],
+                                         itd->bufptr[pg + 1],
+                                         ITD_BUFPTR_MASK);
                 uint32_t len2 = off + len - 4096;
                 uint32_t len1 = len - len2;
                 qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
@@ -1774,8 +1783,9 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async)
     assert(!async);
     entry = ehci_get_fetch_addr(ehci, async);
 
+    memset(&itd, 0, sizeof(itd));
     if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
-                   sizeof(EHCIitd) >> 2) < 0) {
+                   ehci_itd_dwords(ehci)) < 0) {
         return -1;
     }
     ehci_trace_itd(ehci, entry, &itd);
@@ -1785,8 +1795,8 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async)
     }
 
     put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
-               sizeof(EHCIitd) >> 2);
-    ehci_set_fetch_addr(ehci, async, itd.next);
+               ehci_itd_dwords(ehci));
+    ehci_set_fetch_addr(ehci, async, ehci_get_desc_addr(ehci, itd.next));
     ehci_set_state(ehci, async, EST_FETCHENTRY);
 
     return 1;
-- 
2.55.0



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

* [PULL 09/83] hw/usb/hcd-ehci: Implement 64-bit siTD descriptor addressing
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (7 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 08/83] hw/usb/hcd-ehci: Implement 64-bit iTD " Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 10/83] hw/usb/hcd-ehci: Add ctrldssegment-default property Cédric Le Goater
                   ` (74 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

EHCI supports 64-bit control data structure addressing when the
64-bit Addressing Capability bit in HCCPARAMS is set. In that mode,
the CTRLDSSEGMENT register provides the upper 32 bits that are
concatenated with 32-bit link pointer values to form full 64-bit
descriptor addresses (EHCI 1.0, section 2.3.5 and Appendix B).

siTD link pointers are stored as 32-bit values and must be expanded
to full 64-bit descriptor addresses when 64-bit mode is enabled.
Update the siTD traversal path to use ehci_get_desc_addr() when
following link pointers.

When 64-bit capability is disabled, descriptor addresses remain
32-bit and existing behaviour is unchanged.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-7-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 1a286c32e690..9a80c74972c3 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -1823,7 +1823,7 @@ static int ehci_state_fetchsitd(EHCIState *ehci, int async)
         warn_report("Skipping active siTD");
     }
 
-    ehci_set_fetch_addr(ehci, async, sitd.next);
+    ehci_set_fetch_addr(ehci, async, ehci_get_desc_addr(ehci, sitd.next));
     ehci_set_state(ehci, async, EST_FETCHENTRY);
     return 1;
 }
-- 
2.55.0



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

* [PULL 10/83] hw/usb/hcd-ehci: Add ctrldssegment-default property
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (8 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 09/83] hw/usb/hcd-ehci: Implement 64-bit siTD " Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 11/83] hw/arm/aspeed_ast27x0: Set EHCI ctrldssegment-default Cédric Le Goater
                   ` (73 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Philippe Mathieu-Daudé

From: Jamin Lin <jamin_lin@aspeedtech.com>

When 64-bit addressing is supported, the Linux EHCI driver programs the
segment register to zero. See ehci_run function:
https://github.com/torvalds/linux/blob/master/drivers/usb/host/ehci-hcd.c

The driver comment also notes that descriptor structures allocated from
the DMA pool use segment zero semantics.

Descriptor memory is allocated using the DMA API. The platform driver
configures a 64-bit DMA mask so memory can be allocated above 4GB.
See ehci_platform_probe function:
https://github.com/torvalds/linux/blob/master/drivers/usb/host/ehci-platform.c

On AST2700 platforms, system DRAM is mapped above 4GB at 0x400000000.
As a result, descriptor addresses constructed directly from the guest
EHCI registers do not match the actual system address used by the
controller when fetching queue heads (QH) and queue element transfer
descriptors (qTD).

Add a ctrldssegment-default property so platforms can provide a
descriptor address offset when constructing descriptor addresses.
This allows systems where DRAM resides above 4GB to access EHCI
descriptors correctly.

The default value is zero, so existing machines are not affected.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-8-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/usb/hcd-ehci.h | 5 ++++-
 hw/usb/hcd-ehci.c | 1 +
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
index 49176f3fd282..b5ac9c8670d9 100644
--- a/hw/usb/hcd-ehci.h
+++ b/hw/usb/hcd-ehci.h
@@ -268,6 +268,7 @@ struct EHCIState {
      */
     bool migrate_fetch_addr_64bit;
     bool caps_64bit_addr;
+    uint32_t ctrldssegment_default;
 
     /*
      *  EHCI spec version 1.0 Section 2.3
@@ -333,7 +334,9 @@ struct EHCIState {
     DEFINE_PROP_BOOL("x-migrate-fetch-addr-64bit", _state, \
                      ehci.migrate_fetch_addr_64bit, true), \
     DEFINE_PROP_BOOL("caps-64bit-addr", _state, \
-                     ehci.caps_64bit_addr, false)
+                     ehci.caps_64bit_addr, false), \
+    DEFINE_PROP_UINT32("ctrldssegment-default", _state, \
+                       ehci.ctrldssegment_default, 0)
 
 extern const VMStateDescription vmstate_ehci;
 
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 9a80c74972c3..451a918e9f5f 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -1162,6 +1162,7 @@ static void ehci_opreg_write(void *ptr, hwaddr addr,
                           "64-bit addressing capability is disabled\n");
             return;
         }
+        val |= s->ctrldssegment_default;
         break;
 
     case ASYNCLISTADDR:
-- 
2.55.0



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

* [PULL 11/83] hw/arm/aspeed_ast27x0: Set EHCI ctrldssegment-default
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (9 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 10/83] hw/usb/hcd-ehci: Add ctrldssegment-default property Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 12/83] hw/arm/aspeed_ast27x0: Enable 64-bit EHCI DMA addressing Cédric Le Goater
                   ` (72 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Philippe Mathieu-Daudé

From: Jamin Lin <jamin_lin@aspeedtech.com>

On AST2700 platforms, system DRAM is mapped above 4GB with a base
address at 0x400000000.

The Linux EHCI driver programs the segment register to zero when
64-bit addressing is supported. As a result, descriptor addresses
derived from the EHCI registers do not include the DRAM base
address.

Descriptor memory is allocated through the DMA API with a 64-bit
DMA mask, allowing descriptors to reside in DRAM above 4GB. On
AST2700, EHCI queue heads (QH) and queue element transfer
descriptors (qTD) are therefore placed at addresses starting from
0x400000000.

Set the ctrldssegment-default property to "sc->memmap[ASPEED_DEV_SDRAM] >> 32"
so the upper 32 bits of descriptor addresses are adjusted accordingly. This
allows the emulated EHCI controller to construct correct system
addresses when accessing descriptors in DRAM above 4GB.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-9-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast27x0.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index dddd7d21060c..0b56d2bb6704 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -870,6 +870,9 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
 
     /* EHCI */
     for (i = 0; i < sc->ehcis_num; i++) {
+        object_property_set_int(OBJECT(&s->ehci[i]), "ctrldssegment-default",
+                                sc->memmap[ASPEED_DEV_SDRAM] >> 32,
+                                &error_abort);
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->ehci[i]), errp)) {
             return;
         }
-- 
2.55.0



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

* [PULL 12/83] hw/arm/aspeed_ast27x0: Enable 64-bit EHCI DMA addressing
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (10 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 11/83] hw/arm/aspeed_ast27x0: Set EHCI ctrldssegment-default Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 13/83] tests/functional/aarch64/test_aspeed_ast2700: Add USB EHCI test for AST2700 A1/A2 Cédric Le Goater
                   ` (71 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Philippe Mathieu-Daudé

From: Jamin Lin <jamin_lin@aspeedtech.com>

AST2700 supports a 64-bit DRAM address space. Therefore, DMA
transactions must be capable of accessing 64-bit addresses.

Enable the "caps-64bit-addr" property for the EHCI controllers
on AST2700 so that USB DMA operations can correctly handle
64-bit memory addresses.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-10-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast27x0.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index 0b56d2bb6704..7c062580f2b1 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -873,6 +873,8 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
         object_property_set_int(OBJECT(&s->ehci[i]), "ctrldssegment-default",
                                 sc->memmap[ASPEED_DEV_SDRAM] >> 32,
                                 &error_abort);
+        object_property_set_bool(OBJECT(&s->ehci[i]), "caps-64bit-addr", true,
+                                 &error_abort);
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->ehci[i]), errp)) {
             return;
         }
-- 
2.55.0



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

* [PULL 13/83] tests/functional/aarch64/test_aspeed_ast2700: Add USB EHCI test for AST2700 A1/A2
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (11 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 12/83] hw/arm/aspeed_ast27x0: Enable 64-bit EHCI DMA addressing Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 14/83] tests/qtest: aspeed_smc: Introduce read_page_mem_fn for page read helpers Cédric Le Goater
                   ` (70 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

Add a functional test to verify USB EHCI support on the AST2700 A2/A1
by attaching a USB keyboard device and checking its
enumeration via lsusb.

This introduces a helper routine that runs lsusb in the guest
and validates that the emulated "QEMU USB Keyboard" is detected.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260713032704.3583103-11-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/functional/aarch64/test_aspeed_ast2700a1.py | 7 +++++++
 tests/functional/aarch64/test_aspeed_ast2700a2.py | 7 +++++++
 2 files changed, 14 insertions(+)

diff --git a/tests/functional/aarch64/test_aspeed_ast2700a1.py b/tests/functional/aarch64/test_aspeed_ast2700a1.py
index ddddd2d47a95..adbbef5cf11e 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700a1.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700a1.py
@@ -118,6 +118,11 @@ def do_ast2700_pcie_test(self):
             'ip addr show dev eth2',
             'inet 10.0.2.15/24')
 
+    def do_ast2700_usb_ehci_test(self):
+        exec_command_and_wait_for_pattern(self,
+            'lsusb',
+            'QEMU QEMU USB Keyboard')
+
     def start_ast2700_test(self, name, bus_id):
         num_cpu = 4
         load_images_list = [
@@ -161,10 +166,12 @@ def test_aarch64_ast2700a1_evb_sdk_v11_01(self):
         self.archive_extract(self.ASSET_SDK_V1101_AST2700A1)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
+        self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3')
         self.start_ast2700_test('ast2700-a1-image', 1)
         self.verify_openbmc_boot_and_login('ast2700-a1')
         self.do_ast2700_i2c_test(1)
         self.do_ast2700_pcie_test()
+        self.do_ast2700_usb_ehci_test()
 
     def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_01(self):
         self.set_machine('ast2700a1-evb')
diff --git a/tests/functional/aarch64/test_aspeed_ast2700a2.py b/tests/functional/aarch64/test_aspeed_ast2700a2.py
index 0fe04a6eca7f..5f9073c89754 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700a2.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700a2.py
@@ -121,6 +121,11 @@ def do_ast2700_pcie_test(self):
             'ip addr show dev eth2',
             'inet 10.0.2.15/24')
 
+    def do_ast2700_usb_ehci_test(self):
+        exec_command_and_wait_for_pattern(self,
+            'lsusb',
+            'QEMU QEMU USB Keyboard')
+
     def start_ast2700_test(self, name, bus_id):
         num_cpu = 4
         load_images_list = [
@@ -164,10 +169,12 @@ def test_aarch64_ast2700a2_evb_sdk_v11_01(self):
         self.archive_extract(self.ASSET_SDK_V1101_AST2700A2)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
+        self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3')
         self.start_ast2700_test('ast2700-default-image', 1)
         self.verify_openbmc_boot_and_login('ast2700-default')
         self.do_ast2700_i2c_test(1)
         self.do_ast2700_pcie_test()
+        self.do_ast2700_usb_ehci_test()
 
     def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_01(self):
         self.set_machine('ast2700a2-evb')
-- 
2.55.0



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

* [PULL 14/83] tests/qtest: aspeed_smc: Introduce read_page_mem_fn for page read helpers
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (12 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 13/83] tests/functional/aarch64/test_aspeed_ast2700: Add USB EHCI test for AST2700 A1/A2 Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 15/83] tests/qtest: aspeed_smc: Add fast-read test coverage Cédric Le Goater
                   ` (69 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Cédric Le Goater, Bin Meng

This to prepare for fast-read variants. No functional change.

Reviewed-by: Bin Meng <bin.meng@processmission.com>
Link: https://lore.kernel.org/qemu-devel/20260714124621.522948-2-clg@redhat.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-smc-utils.c | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/tests/qtest/aspeed-smc-utils.c b/tests/qtest/aspeed-smc-utils.c
index c27d09e767e5..ed125741eb36 100644
--- a/tests/qtest/aspeed-smc-utils.c
+++ b/tests/qtest/aspeed-smc-utils.c
@@ -186,6 +186,9 @@ static void read_page_mem(const AspeedSMCTestData *data, uint32_t addr,
     }
 }
 
+typedef void (*read_page_mem_fn)(const AspeedSMCTestData *data,
+                                 uint32_t addr, uint32_t *page);
+
 static void write_page_mem(const AspeedSMCTestData *data, uint32_t addr,
                            uint32_t write_value)
 {
@@ -327,9 +330,9 @@ void aspeed_smc_test_erase_all(const void *data)
     flash_reset(test_data);
 }
 
-void aspeed_smc_test_write_page(const void *data)
+static void test_write_page(const AspeedSMCTestData *test_data,
+                            read_page_mem_fn reader)
 {
-    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
     uint32_t my_page_addr = test_data->page_addr;
     uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE;
     uint32_t page[FLASH_PAGE_SIZE / 4];
@@ -350,13 +353,13 @@ void aspeed_smc_test_write_page(const void *data)
     spi_ctrl_stop_user(test_data);
 
     /* Check what was written */
-    read_page(test_data, my_page_addr, page);
+    reader(test_data, my_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
     }
 
     /* Check some other page. It should be full of 0xff */
-    read_page(test_data, some_page_addr, page);
+    reader(test_data, some_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, 0xffffffff);
     }
@@ -364,9 +367,14 @@ void aspeed_smc_test_write_page(const void *data)
     flash_reset(test_data);
 }
 
-void aspeed_smc_test_read_page_mem(const void *data)
+void aspeed_smc_test_write_page(const void *data)
+{
+    test_write_page(data, read_page);
+}
+
+static void test_read_page_mem(const AspeedSMCTestData *test_data,
+                               read_page_mem_fn reader)
 {
-    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
     uint32_t my_page_addr = test_data->page_addr;
     uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE;
     uint32_t page[FLASH_PAGE_SIZE / 4];
@@ -393,13 +401,13 @@ void aspeed_smc_test_read_page_mem(const void *data)
     spi_conf_remove(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs));
 
     /* Check what was written */
-    read_page_mem(test_data, my_page_addr, page);
+    reader(test_data, my_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
     }
 
     /* Check some other page. It should be full of 0xff */
-    read_page_mem(test_data, some_page_addr, page);
+    reader(test_data, some_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, 0xffffffff);
     }
@@ -407,6 +415,11 @@ void aspeed_smc_test_read_page_mem(const void *data)
     flash_reset(test_data);
 }
 
+void aspeed_smc_test_read_page_mem(const void *data)
+{
+    test_read_page_mem(data, read_page_mem);
+}
+
 void aspeed_smc_test_write_page_mem(const void *data)
 {
     const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
-- 
2.55.0



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

* [PULL 15/83] tests/qtest: aspeed_smc: Add fast-read test coverage
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (13 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 14/83] tests/qtest: aspeed_smc: Introduce read_page_mem_fn for page read helpers Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 16/83] tests/qtest: aspeed_smc: Add Dual Output Read (DOR) " Cédric Le Goater
                   ` (68 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Cédric Le Goater, Bin Meng

Introduce a spi_ctrl_set_fast_read() helper and add
read_page_mem_fast_read (CTRL_FREADMODE with dummy byte) and
write_page_fast_read (user-mode FAST_READ) tests.

While at it, replace the license boilerplate with SPDX identifier.

Reviewed-by: Bin Meng <bin.meng@processmission.com>
Link: https://lore.kernel.org/qemu-devel/20260714124621.522948-3-clg@redhat.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-smc-utils.h | 23 +++--------
 tests/qtest/aspeed-smc-utils.c | 75 ++++++++++++++++++++++++++--------
 tests/qtest/aspeed_smc-test.c  | 39 ++++++++++--------
 tests/qtest/ast2700-smc-test.c |  4 ++
 4 files changed, 90 insertions(+), 51 deletions(-)

diff --git a/tests/qtest/aspeed-smc-utils.h b/tests/qtest/aspeed-smc-utils.h
index e2fd8ff1bd16..7504f5a525b4 100644
--- a/tests/qtest/aspeed-smc-utils.h
+++ b/tests/qtest/aspeed-smc-utils.h
@@ -4,23 +4,7 @@
  *
  * Copyright (C) 2016 IBM Corp.
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * SPDX-License-Identifier: MIT
  */
 
 #ifndef TESTS_ASPEED_SMC_UTILS_H
@@ -44,6 +28,8 @@
 #define   CTRL_FREADMODE       0x1
 #define   CTRL_WRITEMODE       0x2
 #define   CTRL_USERMODE        0x3
+#define   CTRL_DUMMY_LOW_SHIFT   6
+#define   CTRL_DUMMY_HIGH_SHIFT  14
 #define SR_WEL BIT(1)
 
 /*
@@ -55,6 +41,7 @@ enum {
     WRDI = 0x4,
     BULK_ERASE = 0xc7,
     READ = 0x03,
+    FAST_READ = 0x0b,
     PP = 0x02,
     WRSR = 0x1,
     WREN = 0x6,
@@ -90,5 +77,7 @@ void aspeed_smc_test_status_reg_write_protection(const void *data);
 void aspeed_smc_test_write_block_protect(const void *data);
 void aspeed_smc_test_write_block_protect_bottom_bit(const void *data);
 void aspeed_smc_test_write_page_qpi(const void *data);
+void aspeed_smc_test_read_page_mem_fast_read(const void *data);
+void aspeed_smc_test_write_page_fast_read(const void *data);
 
 #endif /* TESTS_ASPEED_SMC_UTILS_H */
diff --git a/tests/qtest/aspeed-smc-utils.c b/tests/qtest/aspeed-smc-utils.c
index ed125741eb36..07e65e0173d9 100644
--- a/tests/qtest/aspeed-smc-utils.c
+++ b/tests/qtest/aspeed-smc-utils.c
@@ -4,23 +4,7 @@
  *
  * Copyright (C) 2016 IBM Corp.
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * SPDX-License-Identifier: MIT
  */
 
 #include "qemu/osdep.h"
@@ -107,6 +91,23 @@ static void spi_ctrl_setmode(const AspeedSMCTestData *data, uint8_t mode,
     spi_writel(data, ctrl_reg, ctrl);
 }
 
+/* Set FREADMODE with a fast read command and 1 dummy byte */
+static void spi_ctrl_set_fast_read(const AspeedSMCTestData *data, uint8_t cmd)
+{
+    uint32_t ctrl_reg = R_CTRL0 + data->cs * 4;
+    uint32_t ctrl = spi_readl(data, ctrl_reg);
+    uint32_t iomode = 0;
+
+    ctrl &= ~(CTRL_USERMODE | (0xff << 16) |
+              (0x3 << CTRL_DUMMY_LOW_SHIFT) |
+              (0x1 << CTRL_DUMMY_HIGH_SHIFT) |
+              CTRL_IO_MODE_MASK);
+    ctrl |= CTRL_FREADMODE | (cmd << 16) |
+            (1 << CTRL_DUMMY_LOW_SHIFT) |
+            iomode;
+    spi_writel(data, ctrl_reg, ctrl);
+}
+
 static void spi_ctrl_start_user(const AspeedSMCTestData *data)
 {
     uint32_t ctrl_reg = R_CTRL0 + data->cs * 4;
@@ -697,3 +698,43 @@ void aspeed_smc_test_write_page_qpi(const void *data)
     flash_reset(test_data);
 }
 
+static void read_page_mem_fast_read(const AspeedSMCTestData *data,
+                                uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_set_fast_read(data, FAST_READ);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, addr + i * 4));
+    }
+}
+
+void aspeed_smc_test_read_page_mem_fast_read(const void *data)
+{
+    test_read_page_mem(data, read_page_mem_fast_read);
+}
+
+static void read_page_fast_read(const AspeedSMCTestData *data,
+                                uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_start_user(data);
+
+    flash_writeb(data, 0, EN_4BYTE_ADDR);
+    flash_writeb(data, 0, FAST_READ);
+    flash_writel(data, 0, make_be32(addr));
+    /* 1 dummy byte for standard SPI fast-read */
+    flash_writeb(data, 0, 0x00);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, 0));
+    }
+    spi_ctrl_stop_user(data);
+}
+
+void aspeed_smc_test_write_page_fast_read(const void *data)
+{
+    test_write_page(data, read_page_fast_read);
+}
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 39af1df0ed75..4697c493a813 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -4,23 +4,7 @@
  *
  * Copyright (C) 2016 IBM Corp.
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * SPDX-License-Identifier: MIT
  */
 
 #include "qemu/osdep.h"
@@ -68,6 +52,15 @@ static void test_palmetto_bmc(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2400/smc/status_reg_write_protection",
                         data, aspeed_smc_test_status_reg_write_protection);
+    qtest_add_data_func("/ast2400/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2400/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
+    /*
+     * Block protect tests must be run last because the block protect
+     * state is not cleared by reset_memory() and silently prevents
+     * subsequent flash writes.
+     */
     qtest_add_data_func("/ast2400/smc/write_block_protect",
                         data, aspeed_smc_test_write_block_protect);
     qtest_add_data_func("/ast2400/smc/write_block_protect_bottom_bit",
@@ -115,6 +108,10 @@ static void test_ast2500_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2500/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast2500/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2500/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
 }
 
 static void test_ast2600_evb(AspeedSMCTestData *data)
@@ -158,6 +155,10 @@ static void test_ast2600_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2600/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast2600/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2600/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
 }
 
 static void test_ast1030_evb(AspeedSMCTestData *data)
@@ -201,6 +202,10 @@ static void test_ast1030_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast1030/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast1030/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast1030/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
 }
 
 int main(int argc, char **argv)
diff --git a/tests/qtest/ast2700-smc-test.c b/tests/qtest/ast2700-smc-test.c
index 33fc47230ee5..9ad04b574c0d 100644
--- a/tests/qtest/ast2700-smc-test.c
+++ b/tests/qtest/ast2700-smc-test.c
@@ -52,6 +52,10 @@ static void test_ast2700_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2700/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast2700/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2700/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
 }
 
 int main(int argc, char **argv)
-- 
2.55.0



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

* [PULL 16/83] tests/qtest: aspeed_smc: Add Dual Output Read (DOR) test coverage
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (14 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 15/83] tests/qtest: aspeed_smc: Add fast-read test coverage Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 17/83] tests/qtest: aspeed_smc: Add Quad Output Read (QOR) " Cédric Le Goater
                   ` (67 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Cédric Le Goater, Bin Meng

Add read_page_mem_dor (CTRL_FREADMODE with DOR command and dual data
IO mode) and write_page_dor (user-mode DOR) tests.

Reviewed-by: Bin Meng <bin.meng@processmission.com>
Link: https://lore.kernel.org/qemu-devel/20260714124621.522948-4-clg@redhat.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-smc-utils.h |  4 +++
 tests/qtest/aspeed-smc-utils.c | 45 ++++++++++++++++++++++++++++++++++
 tests/qtest/aspeed_smc-test.c  | 16 ++++++++++++
 tests/qtest/ast2700-smc-test.c |  4 +++
 4 files changed, 69 insertions(+)

diff --git a/tests/qtest/aspeed-smc-utils.h b/tests/qtest/aspeed-smc-utils.h
index 7504f5a525b4..04d3c7db5f7b 100644
--- a/tests/qtest/aspeed-smc-utils.h
+++ b/tests/qtest/aspeed-smc-utils.h
@@ -28,6 +28,7 @@
 #define   CTRL_FREADMODE       0x1
 #define   CTRL_WRITEMODE       0x2
 #define   CTRL_USERMODE        0x3
+#define   CTRL_IO_DUAL_DATA     BIT(29)
 #define   CTRL_DUMMY_LOW_SHIFT   6
 #define   CTRL_DUMMY_HIGH_SHIFT  14
 #define SR_WEL BIT(1)
@@ -42,6 +43,7 @@ enum {
     BULK_ERASE = 0xc7,
     READ = 0x03,
     FAST_READ = 0x0b,
+    DOR = 0x3b,
     PP = 0x02,
     WRSR = 0x1,
     WREN = 0x6,
@@ -79,5 +81,7 @@ void aspeed_smc_test_write_block_protect_bottom_bit(const void *data);
 void aspeed_smc_test_write_page_qpi(const void *data);
 void aspeed_smc_test_read_page_mem_fast_read(const void *data);
 void aspeed_smc_test_write_page_fast_read(const void *data);
+void aspeed_smc_test_read_page_mem_dor(const void *data);
+void aspeed_smc_test_write_page_dor(const void *data);
 
 #endif /* TESTS_ASPEED_SMC_UTILS_H */
diff --git a/tests/qtest/aspeed-smc-utils.c b/tests/qtest/aspeed-smc-utils.c
index 07e65e0173d9..454224ec28b4 100644
--- a/tests/qtest/aspeed-smc-utils.c
+++ b/tests/qtest/aspeed-smc-utils.c
@@ -98,6 +98,10 @@ static void spi_ctrl_set_fast_read(const AspeedSMCTestData *data, uint8_t cmd)
     uint32_t ctrl = spi_readl(data, ctrl_reg);
     uint32_t iomode = 0;
 
+    if (cmd == DOR) {
+        iomode = CTRL_IO_DUAL_DATA;
+    }
+
     ctrl &= ~(CTRL_USERMODE | (0xff << 16) |
               (0x3 << CTRL_DUMMY_LOW_SHIFT) |
               (0x1 << CTRL_DUMMY_HIGH_SHIFT) |
@@ -738,3 +742,44 @@ void aspeed_smc_test_write_page_fast_read(const void *data)
 {
     test_write_page(data, read_page_fast_read);
 }
+
+static void read_page_mem_dor(const AspeedSMCTestData *data,
+                              uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_set_fast_read(data, DOR);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, addr + i * 4));
+    }
+}
+
+void aspeed_smc_test_read_page_mem_dor(const void *data)
+{
+    test_read_page_mem(data, read_page_mem_dor);
+}
+
+static void read_page_dor(const AspeedSMCTestData *data,
+                          uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_start_user(data);
+
+    flash_writeb(data, 0, EN_4BYTE_ADDR);
+    flash_writeb(data, 0, DOR);
+    flash_writel(data, 0, make_be32(addr));
+    /* 1 dummy byte for standard SPI DOR */
+    flash_writeb(data, 0, 0x00);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, 0));
+    }
+    spi_ctrl_stop_user(data);
+}
+
+void aspeed_smc_test_write_page_dor(const void *data)
+{
+    test_write_page(data, read_page_dor);
+}
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 4697c493a813..a67cc5a999ca 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -56,6 +56,10 @@ static void test_palmetto_bmc(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_fast_read);
     qtest_add_data_func("/ast2400/smc/write_page_fast_read",
                         data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2400/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2400/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
     /*
      * Block protect tests must be run last because the block protect
      * state is not cleared by reset_memory() and silently prevents
@@ -112,6 +116,10 @@ static void test_ast2500_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_fast_read);
     qtest_add_data_func("/ast2500/smc/write_page_fast_read",
                         data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2500/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2500/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
 }
 
 static void test_ast2600_evb(AspeedSMCTestData *data)
@@ -159,6 +167,10 @@ static void test_ast2600_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_fast_read);
     qtest_add_data_func("/ast2600/smc/write_page_fast_read",
                         data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2600/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2600/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
 }
 
 static void test_ast1030_evb(AspeedSMCTestData *data)
@@ -206,6 +218,10 @@ static void test_ast1030_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_fast_read);
     qtest_add_data_func("/ast1030/smc/write_page_fast_read",
                         data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast1030/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast1030/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
 }
 
 int main(int argc, char **argv)
diff --git a/tests/qtest/ast2700-smc-test.c b/tests/qtest/ast2700-smc-test.c
index 9ad04b574c0d..bd9d3f7905b3 100644
--- a/tests/qtest/ast2700-smc-test.c
+++ b/tests/qtest/ast2700-smc-test.c
@@ -56,6 +56,10 @@ static void test_ast2700_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_fast_read);
     qtest_add_data_func("/ast2700/smc/write_page_fast_read",
                         data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2700/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2700/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
 }
 
 int main(int argc, char **argv)
-- 
2.55.0



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

* [PULL 17/83] tests/qtest: aspeed_smc: Add Quad Output Read (QOR) test coverage
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (15 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 16/83] tests/qtest: aspeed_smc: Add Dual Output Read (DOR) " Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 18/83] hw/misc/aspeed_scu: Introduce Aspeed2700SCUState Cédric Le Goater
                   ` (66 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Cédric Le Goater, Bin Meng

Add read_page_mem_qor (CTRL_FREADMODE with QOR command and quad data
IO mode) and write_page_qor (user-mode QOR) tests.

Reviewed-by: Bin Meng <bin.meng@processmission.com>
Link: https://lore.kernel.org/qemu-devel/20260714124621.522948-5-clg@redhat.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-smc-utils.h |  4 ++++
 tests/qtest/aspeed-smc-utils.c | 43 ++++++++++++++++++++++++++++++++++
 tests/qtest/aspeed_smc-test.c  | 16 +++++++++++++
 tests/qtest/ast2700-smc-test.c |  4 ++++
 4 files changed, 67 insertions(+)

diff --git a/tests/qtest/aspeed-smc-utils.h b/tests/qtest/aspeed-smc-utils.h
index 04d3c7db5f7b..19c557b822da 100644
--- a/tests/qtest/aspeed-smc-utils.h
+++ b/tests/qtest/aspeed-smc-utils.h
@@ -28,6 +28,7 @@
 #define   CTRL_FREADMODE       0x1
 #define   CTRL_WRITEMODE       0x2
 #define   CTRL_USERMODE        0x3
+#define   CTRL_IO_QUAD_DATA     BIT(30)
 #define   CTRL_IO_DUAL_DATA     BIT(29)
 #define   CTRL_DUMMY_LOW_SHIFT   6
 #define   CTRL_DUMMY_HIGH_SHIFT  14
@@ -44,6 +45,7 @@ enum {
     READ = 0x03,
     FAST_READ = 0x0b,
     DOR = 0x3b,
+    QOR = 0x6b,
     PP = 0x02,
     WRSR = 0x1,
     WREN = 0x6,
@@ -83,5 +85,7 @@ void aspeed_smc_test_read_page_mem_fast_read(const void *data);
 void aspeed_smc_test_write_page_fast_read(const void *data);
 void aspeed_smc_test_read_page_mem_dor(const void *data);
 void aspeed_smc_test_write_page_dor(const void *data);
+void aspeed_smc_test_read_page_mem_qor(const void *data);
+void aspeed_smc_test_write_page_qor(const void *data);
 
 #endif /* TESTS_ASPEED_SMC_UTILS_H */
diff --git a/tests/qtest/aspeed-smc-utils.c b/tests/qtest/aspeed-smc-utils.c
index 454224ec28b4..6d75a95578e7 100644
--- a/tests/qtest/aspeed-smc-utils.c
+++ b/tests/qtest/aspeed-smc-utils.c
@@ -100,6 +100,8 @@ static void spi_ctrl_set_fast_read(const AspeedSMCTestData *data, uint8_t cmd)
 
     if (cmd == DOR) {
         iomode = CTRL_IO_DUAL_DATA;
+    } else if (cmd == QOR) {
+        iomode = CTRL_IO_QUAD_DATA;
     }
 
     ctrl &= ~(CTRL_USERMODE | (0xff << 16) |
@@ -783,3 +785,44 @@ void aspeed_smc_test_write_page_dor(const void *data)
 {
     test_write_page(data, read_page_dor);
 }
+
+static void read_page_mem_qor(const AspeedSMCTestData *data,
+                              uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_set_fast_read(data, QOR);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, addr + i * 4));
+    }
+}
+
+void aspeed_smc_test_read_page_mem_qor(const void *data)
+{
+    test_read_page_mem(data, read_page_mem_qor);
+}
+
+static void read_page_qor(const AspeedSMCTestData *data,
+                          uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_start_user(data);
+
+    flash_writeb(data, 0, EN_4BYTE_ADDR);
+    flash_writeb(data, 0, QOR);
+    flash_writel(data, 0, make_be32(addr));
+    /* 1 dummy byte for standard SPI QOR */
+    flash_writeb(data, 0, 0x00);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, 0));
+    }
+    spi_ctrl_stop_user(data);
+}
+
+void aspeed_smc_test_write_page_qor(const void *data)
+{
+    test_write_page(data, read_page_qor);
+}
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index a67cc5a999ca..59c96bd68ae8 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -60,6 +60,10 @@ static void test_palmetto_bmc(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_dor);
     qtest_add_data_func("/ast2400/smc/write_page_dor",
                         data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2400/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2400/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
     /*
      * Block protect tests must be run last because the block protect
      * state is not cleared by reset_memory() and silently prevents
@@ -120,6 +124,10 @@ static void test_ast2500_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_dor);
     qtest_add_data_func("/ast2500/smc/write_page_dor",
                         data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2500/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2500/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
 }
 
 static void test_ast2600_evb(AspeedSMCTestData *data)
@@ -171,6 +179,10 @@ static void test_ast2600_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_dor);
     qtest_add_data_func("/ast2600/smc/write_page_dor",
                         data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2600/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2600/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
 }
 
 static void test_ast1030_evb(AspeedSMCTestData *data)
@@ -222,6 +234,10 @@ static void test_ast1030_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_dor);
     qtest_add_data_func("/ast1030/smc/write_page_dor",
                         data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast1030/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast1030/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
 }
 
 int main(int argc, char **argv)
diff --git a/tests/qtest/ast2700-smc-test.c b/tests/qtest/ast2700-smc-test.c
index bd9d3f7905b3..f85077e04ff4 100644
--- a/tests/qtest/ast2700-smc-test.c
+++ b/tests/qtest/ast2700-smc-test.c
@@ -60,6 +60,10 @@ static void test_ast2700_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_dor);
     qtest_add_data_func("/ast2700/smc/write_page_dor",
                         data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2700/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2700/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
 }
 
 int main(int argc, char **argv)
-- 
2.55.0



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

* [PULL 18/83] hw/misc/aspeed_scu: Introduce Aspeed2700SCUState
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (16 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 17/83] tests/qtest: aspeed_smc: Add Quad Output Read (QOR) " Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 19/83] hw/arm/aspeed: Use Aspeed2700SCUState for AST2700 users Cédric Le Goater
                   ` (65 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

Introduce Aspeed2700SCUState as an AST2700-specific subclass of
AspeedSCUState.

Currently, AST1700 and AST2700 reuse the generic AspeedSCUState.
However, AST2700 requires SCU functionality that is specific to the
platform, particularly for interactions with its coprocessors.

Introduce a dedicated Aspeed2700SCUState to provide an extension point
for AST2700-specific functionality while keeping the generic
AspeedSCUState unchanged.

Subsequent patches will migrate AST2700 users to the new subclass and
move AST2700-specific code into it.

No functional change.

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>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/misc/aspeed_scu.h | 5 +++++
 hw/misc/aspeed_scu.c         | 8 +++++++-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h
index c30940ab7683..904549465fd1 100644
--- a/include/hw/misc/aspeed_scu.h
+++ b/include/hw/misc/aspeed_scu.h
@@ -20,6 +20,7 @@ OBJECT_DECLARE_TYPE(AspeedSCUState, AspeedSCUClass, ASPEED_SCU)
 #define TYPE_ASPEED_2500_SCU TYPE_ASPEED_SCU "-ast2500"
 #define TYPE_ASPEED_2600_SCU TYPE_ASPEED_SCU "-ast2600"
 #define TYPE_ASPEED_2700_SCU TYPE_ASPEED_SCU "-ast2700"
+OBJECT_DECLARE_SIMPLE_TYPE(Aspeed2700SCUState, ASPEED_2700_SCU)
 #define TYPE_ASPEED_2700_SCUIO TYPE_ASPEED_SCU "io" "-ast2700"
 #define TYPE_ASPEED_1030_SCU TYPE_ASPEED_SCU "-ast1030"
 
@@ -41,6 +42,10 @@ struct AspeedSCUState {
     uint32_t hw_prot_key;
 };
 
+struct Aspeed2700SCUState {
+    AspeedSCUState parent_obj;
+};
+
 #define AST2400_A1_SILICON_REV   0x02010303U
 #define AST2500_A1_SILICON_REV   0x04010303U
 #define AST2600_A3_SILICON_REV   0x05030303U
diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c
index 5dbf81c0cec9..efe1d6315b25 100644
--- a/hw/misc/aspeed_scu.c
+++ b/hw/misc/aspeed_scu.c
@@ -930,6 +930,11 @@ static void aspeed_ast2700_scu_reset_hold(Object *obj, ResetType type)
     s->regs[AST2700_HW_STRAP1] = s->hw_strap1;
 }
 
+static void aspeed_2700_scu_realize(DeviceState *dev, Error **errp)
+{
+    aspeed_scu_realize(dev, errp);
+}
+
 static void aspeed_2700_scu_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -937,6 +942,7 @@ static void aspeed_2700_scu_class_init(ObjectClass *klass, const void *data)
     AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
 
     dc->desc = "ASPEED 2700 System Control Unit";
+    dc->realize = aspeed_2700_scu_realize;
     rc->phases.hold = aspeed_ast2700_scu_reset_hold;
     asc->resets = ast2700_a0_resets;
     asc->calc_hpll = aspeed_2600_scu_calc_hpll;
@@ -1161,7 +1167,7 @@ static const TypeInfo aspeed_scu_types[] = {
     {
         .name = TYPE_ASPEED_2700_SCU,
         .parent = TYPE_ASPEED_SCU,
-        .instance_size = sizeof(AspeedSCUState),
+        .instance_size = sizeof(Aspeed2700SCUState),
         .class_init = aspeed_2700_scu_class_init,
     },
     {
-- 
2.55.0



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

* [PULL 19/83] hw/arm/aspeed: Use Aspeed2700SCUState for AST2700 users
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (17 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 18/83] hw/misc/aspeed_scu: Introduce Aspeed2700SCUState Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 20/83] hw/arm/aspeed_ast27x0: Move SCU link into AST27x0 coprocessors Cédric Le Goater
                   ` (64 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

Now that Aspeed2700SCUState has been introduced, update the AST1700 and
AST27x0 SoCs to instantiate the AST2700-specific SCU subclass instead of
the generic AspeedSCUState.

Also update the AST27x0 FC board to link the SSP/TSP coprocessors to the
AST2700 SCU instance.

This prepares the AST2700 platform for subsequent patches that move
AST2700-specific SCU functionality into the subclass.

No functional change.

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>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_ast1700.h |  2 +-
 include/hw/arm/aspeed_soc.h     |  1 +
 hw/arm/aspeed_ast27x0-fc.c      |  4 ++--
 hw/arm/aspeed_ast27x0.c         | 16 ++++++++--------
 4 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/include/hw/arm/aspeed_ast1700.h b/include/hw/arm/aspeed_ast1700.h
index f7bd4e8650ba..39c5977cf109 100644
--- a/include/hw/arm/aspeed_ast1700.h
+++ b/include/hw/arm/aspeed_ast1700.h
@@ -41,7 +41,7 @@ struct AspeedAST1700SoCState {
     MemoryRegion sram;
     AspeedSMCState spi;
     AspeedADCState adc;
-    AspeedSCUState scu;
+    Aspeed2700SCUState scu;
     AspeedGPIOState gpio;
     AspeedSGPIOState sgpiom[AST1700_SGPIO_NUM];
     AspeedI2CState i2c;
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index 41dc04e2930f..cd68c7f1caa2 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -151,6 +151,7 @@ struct Aspeed27x0SoCState {
     AspeedINTCState intcioexp[ASPEED_IOEXP_NUM];
     GICv3State gic;
     MemoryRegion dram_empty;
+    Aspeed2700SCUState scu;
 };
 
 #define TYPE_ASPEED27X0_SOC "aspeed27x0-soc"
diff --git a/hw/arm/aspeed_ast27x0-fc.c b/hw/arm/aspeed_ast27x0-fc.c
index 7d9fade68dab..8d49bb95d67b 100644
--- a/hw/arm/aspeed_ast27x0-fc.c
+++ b/hw/arm/aspeed_ast27x0-fc.c
@@ -158,7 +158,7 @@ static bool ast2700fc_ssp_init(MachineState *machine, Error **errp)
     object_property_set_link(OBJECT(&s->ssp), "sram",
                              OBJECT(&psp->sram), &error_abort);
     object_property_set_link(OBJECT(&s->ssp), "scu",
-                             OBJECT(&psp->scu), &error_abort);
+                             OBJECT(&s->ca35.scu), &error_abort);
     if (!qdev_realize(DEVICE(&s->ssp), NULL, errp)) {
         return false;
     }
@@ -190,7 +190,7 @@ static bool ast2700fc_tsp_init(MachineState *machine, Error **errp)
     object_property_set_link(OBJECT(&s->tsp), "sram",
                              OBJECT(&psp->sram), &error_abort);
     object_property_set_link(OBJECT(&s->tsp), "scu",
-                             OBJECT(&psp->scu), &error_abort);
+                             OBJECT(&s->ca35.scu), &error_abort);
     if (!qdev_realize(DEVICE(&s->tsp), NULL, errp)) {
         return false;
     }
diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index 7c062580f2b1..6365dbd638b5 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -435,12 +435,12 @@ static void aspeed_soc_ast2700_init(Object *obj)
 
     object_initialize_child(obj, "gic", &a->gic, gicv3_class_name());
 
-    object_initialize_child(obj, "scu", &s->scu, TYPE_ASPEED_2700_SCU);
-    qdev_prop_set_uint32(DEVICE(&s->scu), "silicon-rev",
+    object_initialize_child(obj, "scu", &a->scu, TYPE_ASPEED_2700_SCU);
+    qdev_prop_set_uint32(DEVICE(&a->scu), "silicon-rev",
                          sc->silicon_rev);
-    object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu),
+    object_property_add_alias(obj, "hw-strap1", OBJECT(&a->scu),
                               "hw-strap1");
-    object_property_add_alias(obj, "hw-prot-key", OBJECT(&s->scu),
+    object_property_add_alias(obj, "hw-prot-key", OBJECT(&a->scu),
                               "hw-prot-key");
 
     object_initialize_child(obj, "scuio", &s->scuio, TYPE_ASPEED_2700_SCUIO);
@@ -808,10 +808,10 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
                                 sc->memmap[ASPEED_DEV_VBOOTROM], &s->vbootrom);
 
     /* SCU */
-    if (!sysbus_realize(SYS_BUS_DEVICE(&s->scu), errp)) {
+    if (!sysbus_realize(SYS_BUS_DEVICE(&a->scu), errp)) {
         return;
     }
-    aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->scu), 0,
+    aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&a->scu), 0,
                     sc->memmap[ASPEED_DEV_SCU]);
 
     /* SCU1 */
@@ -934,7 +934,7 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
         AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(&s->wdt[i]);
         hwaddr wdt_offset = sc->memmap[ASPEED_DEV_WDT] + i * awc->iosize;
 
-        object_property_set_link(OBJECT(&s->wdt[i]), "scu", OBJECT(&s->scu),
+        object_property_set_link(OBJECT(&s->wdt[i]), "scu", OBJECT(&a->scu),
                                  &error_abort);
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) {
             return;
@@ -1037,7 +1037,7 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp)
                        aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_EMMC));
 
     /* Timer */
-    object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&s->scu),
+    object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&a->scu),
                              &error_abort);
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->timerctrl), errp)) {
         return;
-- 
2.55.0



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

* [PULL 20/83] hw/arm/aspeed_ast27x0: Move SCU link into AST27x0 coprocessors
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (18 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 19/83] hw/arm/aspeed: Use Aspeed2700SCUState for AST2700 users Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 21/83] hw/misc/aspeed_scu: Add separate reset handler for AST2700 SCUIO Cédric Le Goater
                   ` (63 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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 SCU link is only needed by the AST27x0 SSP/TSP coprocessors for their
AST2700-specific SCU alias window.

Move the link property from the common AspeedCoprocessorState into
Aspeed27x0CoprocessorState, so the generic coprocessor model no longer
contains an AST2700-specific dependency.

Also validate that the SCU link has been provided during device realize
before accessing it.

No functional change.

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>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_coprocessor.h |  5 +++--
 hw/arm/aspeed_ast27x0-ssp.c         | 20 ++++++++++++++++----
 hw/arm/aspeed_ast27x0-tsp.c         | 20 ++++++++++++++++----
 hw/arm/aspeed_coprocessor_common.c  |  2 --
 4 files changed, 35 insertions(+), 12 deletions(-)

diff --git a/include/hw/arm/aspeed_coprocessor.h b/include/hw/arm/aspeed_coprocessor.h
index ac58a5f424ff..adfc3c451239 100644
--- a/include/hw/arm/aspeed_coprocessor.h
+++ b/include/hw/arm/aspeed_coprocessor.h
@@ -20,10 +20,8 @@ struct AspeedCoprocessorState {
     MemoryRegion *sram;
     MemoryRegion sram_alias;
     MemoryRegion uart_alias;
-    MemoryRegion scu_alias;
     Clock *sysclk;
 
-    AspeedSCUState *scu;
     AspeedSCUState scuio;
     AspeedTimerCtrlState timerctrl;
     SerialMM *uart;
@@ -52,6 +50,9 @@ struct Aspeed27x0CoprocessorState {
     UnimplementedDeviceState otp;
 
     ARMv7MState armv7m;
+
+    MemoryRegion scu_alias;
+    Aspeed2700SCUState *scu;
 };
 
 #define TYPE_ASPEED27X0SSP_COPROCESSOR "aspeed27x0ssp-coprocessor"
diff --git a/hw/arm/aspeed_ast27x0-ssp.c b/hw/arm/aspeed_ast27x0-ssp.c
index 68a8ab26f7ac..28bb59c8c56a 100644
--- a/hw/arm/aspeed_ast27x0-ssp.c
+++ b/hw/arm/aspeed_ast27x0-ssp.c
@@ -167,6 +167,12 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
         return;
     }
 
+    if (!a->scu) {
+        error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR
+                   ": 'scu' link is not set");
+        return;
+    }
+
     /* AST27X0 SSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -195,11 +201,11 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
                                 &s->sram_alias);
 
     /* SCU */
-    memory_region_init_alias(&s->scu_alias, OBJECT(s), "scu.alias",
-                             &s->scu->iomem, 0,
-                             memory_region_size(&s->scu->iomem));
+    memory_region_init_alias(&a->scu_alias, OBJECT(a), "scu.alias",
+                             &a->scu->parent_obj.iomem, 0,
+                             memory_region_size(&a->scu->parent_obj.iomem));
     memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU],
-                                &s->scu_alias);
+                                &a->scu_alias);
 
     /* INTC */
     if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) {
@@ -275,6 +281,11 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
                                   sc->memmap[ASPEED_DEV_OTP], 0x800);
 }
 
+static const Property aspeed_27x0_coprocessor_properties[] = {
+    DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu,
+                     TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
+};
+
 static void aspeed_soc_ast27x0ssp_class_init(ObjectClass *klass,
                                              const void *data)
 {
@@ -288,6 +299,7 @@ static void aspeed_soc_ast27x0ssp_class_init(ObjectClass *klass,
     /* Reason: The Aspeed Coprocessor can only be instantiated from a board */
     dc->user_creatable = false;
     dc->realize = aspeed_soc_ast27x0ssp_realize;
+    device_class_set_props(dc, aspeed_27x0_coprocessor_properties);
 
     sc->valid_cpu_types = valid_cpu_types;
     sc->irqmap = aspeed_soc_ast27x0ssp_irqmap;
diff --git a/hw/arm/aspeed_ast27x0-tsp.c b/hw/arm/aspeed_ast27x0-tsp.c
index b8a4f7c91d60..f5c4a021616b 100644
--- a/hw/arm/aspeed_ast27x0-tsp.c
+++ b/hw/arm/aspeed_ast27x0-tsp.c
@@ -167,6 +167,12 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
         return;
     }
 
+    if (!a->scu) {
+        error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR
+                   ": 'scu' link is not set");
+        return;
+    }
+
     /* AST27X0 TSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -195,11 +201,11 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
                                 &s->sram_alias);
 
     /* SCU */
-    memory_region_init_alias(&s->scu_alias, OBJECT(s), "scu.alias",
-                             &s->scu->iomem, 0,
-                             memory_region_size(&s->scu->iomem));
+    memory_region_init_alias(&a->scu_alias, OBJECT(a), "scu.alias",
+                             &a->scu->parent_obj.iomem, 0,
+                             memory_region_size(&a->scu->parent_obj.iomem));
     memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU],
-                                &s->scu_alias);
+                                &a->scu_alias);
 
     /* INTC */
     if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) {
@@ -275,6 +281,11 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
                                   sc->memmap[ASPEED_DEV_OTP], 0x800);
 }
 
+static const Property aspeed_27x0_coprocessor_properties[] = {
+    DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu,
+                     TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
+};
+
 static void aspeed_soc_ast27x0tsp_class_init(ObjectClass *klass,
                                              const void *data)
 {
@@ -288,6 +299,7 @@ static void aspeed_soc_ast27x0tsp_class_init(ObjectClass *klass,
     /* Reason: The Aspeed Coprocessor can only be instantiated from a board */
     dc->user_creatable = false;
     dc->realize = aspeed_soc_ast27x0tsp_realize;
+    device_class_set_props(dc, aspeed_27x0_coprocessor_properties);
 
     sc->valid_cpu_types = valid_cpu_types;
     sc->irqmap = aspeed_soc_ast27x0tsp_irqmap;
diff --git a/hw/arm/aspeed_coprocessor_common.c b/hw/arm/aspeed_coprocessor_common.c
index a0a4c73d08d3..43026d2a555a 100644
--- a/hw/arm/aspeed_coprocessor_common.c
+++ b/hw/arm/aspeed_coprocessor_common.c
@@ -27,8 +27,6 @@ static const Property aspeed_coprocessor_properties[] = {
                      TYPE_MEMORY_REGION, MemoryRegion *),
     DEFINE_PROP_LINK("sram", AspeedCoprocessorState, sram, TYPE_MEMORY_REGION,
                      MemoryRegion *),
-    DEFINE_PROP_LINK("scu", AspeedCoprocessorState, scu, TYPE_ASPEED_SCU,
-                     AspeedSCUState *),
     DEFINE_PROP_LINK("uart", AspeedCoprocessorState, uart, TYPE_SERIAL_MM,
                      SerialMM *),
     DEFINE_PROP_INT32("uart-dev", AspeedCoprocessorState, uart_dev, 0),
-- 
2.55.0



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

* [PULL 21/83] hw/misc/aspeed_scu: Add separate reset handler for AST2700 SCUIO
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (19 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 20/83] hw/arm/aspeed_ast27x0: Move SCU link into AST27x0 coprocessors Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 22/83] hw/arm/aspeed_ast27x0: Pass realized PSP SoC to SSP/TSP initialization Cédric Le Goater
                   ` (62 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

Introduce a dedicated reset handler for SCUIO.

Previously, SCU and SCUIO shared the same reset handler. This no longer
fits the AST2700 design, where SCU uses the Aspeed2700SCUState subclass
and will handle coprocessor-related control in future changes.

Since these controls are defined in SCU (not SCUIO), SCU and SCUIO
should not share the same reset logic.

This change gives SCUIO its own reset handler and prepares for upcoming
SCU-specific functionality.

No functional change.

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>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-5-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_scu.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c
index efe1d6315b25..ca93c3699d81 100644
--- a/hw/misc/aspeed_scu.c
+++ b/hw/misc/aspeed_scu.c
@@ -1069,6 +1069,16 @@ static const uint32_t ast2700_a0_resets_io[ASPEED_AST2700_SCU_NR_REGS] = {
     [AST2700_SCUIO_FREQ_CNT_CTL]        = 0x00000080,
 };
 
+static void aspeed_ast2700_scuio_reset_hold(Object *obj, ResetType type)
+{
+    AspeedSCUState *s = ASPEED_SCU(obj);
+    AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(obj);
+
+    memcpy(s->regs, asc->resets, asc->nr_regs * 4);
+    s->regs[AST2700_SILICON_REV] = s->silicon_rev;
+    s->regs[AST2700_HW_STRAP1] = s->hw_strap1;
+}
+
 static void aspeed_2700_scuio_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -1076,7 +1086,7 @@ static void aspeed_2700_scuio_class_init(ObjectClass *klass, const void *data)
     AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
 
     dc->desc = "ASPEED 2700 System Control Unit I/O";
-    rc->phases.hold = aspeed_ast2700_scu_reset_hold;
+    rc->phases.hold = aspeed_ast2700_scuio_reset_hold;
     asc->resets = ast2700_a0_resets_io;
     asc->calc_hpll = aspeed_2600_scu_calc_hpll;
     asc->get_apb = aspeed_2700_scuio_get_apb_freq;
-- 
2.55.0



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

* [PULL 22/83] hw/arm/aspeed_ast27x0: Pass realized PSP SoC to SSP/TSP initialization
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (20 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 21/83] hw/misc/aspeed_scu: Add separate reset handler for AST2700 SCUIO Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 23/83] hw/arm/ast27x0: Share single SCUIO instance across PSP, SSP, and TSP Cédric Le Goater
                   ` (61 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

Pass the realized PSP SoC to the SSP/TSP initialization helpers instead
of retrieving it from the MachineState.

This makes the dependency explicit, since the SSP and TSP coprocessors
use resources owned by the PSP SoC, including the UARTs, SRAM, SCU and
SCUIO. The PSP SoC must therefore be realized before the coprocessors
are initialized.

No functional change.

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>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-6-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast27x0-fc.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/hw/arm/aspeed_ast27x0-fc.c b/hw/arm/aspeed_ast27x0-fc.c
index 8d49bb95d67b..58d0b4d7cd59 100644
--- a/hw/arm/aspeed_ast27x0-fc.c
+++ b/hw/arm/aspeed_ast27x0-fc.c
@@ -134,11 +134,9 @@ static bool ast2700fc_ca35_init(MachineState *machine, Error **errp)
     return true;
 }
 
-static bool ast2700fc_ssp_init(MachineState *machine, Error **errp)
+static bool ast2700fc_ssp_init(Ast2700FCState *s, AspeedSoCState *psp,
+                               Error **errp)
 {
-    Ast2700FCState *s = AST2700FC(machine);
-    AspeedSoCState *psp = ASPEED_SOC(&s->ca35);
-
     s->ssp_sysclk = clock_new(OBJECT(s), "SSP_SYSCLK");
     clock_set_hz(s->ssp_sysclk, 200000000ULL);
 
@@ -166,11 +164,9 @@ static bool ast2700fc_ssp_init(MachineState *machine, Error **errp)
     return true;
 }
 
-static bool ast2700fc_tsp_init(MachineState *machine, Error **errp)
+static bool ast2700fc_tsp_init(Ast2700FCState *s, AspeedSoCState *psp,
+                               Error **errp)
 {
-    Ast2700FCState *s = AST2700FC(machine);
-    AspeedSoCState *psp = ASPEED_SOC(&s->ca35);
-
     s->tsp_sysclk = clock_new(OBJECT(s), "TSP_SYSCLK");
     clock_set_hz(s->tsp_sysclk, 200000000ULL);
 
@@ -200,9 +196,19 @@ static bool ast2700fc_tsp_init(MachineState *machine, Error **errp)
 
 static void ast2700fc_init(MachineState *machine)
 {
+    Ast2700FCState *s = AST2700FC(machine);
+    AspeedSoCState *psp;
+
     ast2700fc_ca35_init(machine, &error_abort);
-    ast2700fc_ssp_init(machine, &error_abort);
-    ast2700fc_tsp_init(machine, &error_abort);
+
+    /*
+     * SSP and TSP use resources owned by the PSP SoC, such as UART,
+     * SRAM, SCU and SCUIO.  Therefore the PSP SoC must be realized
+     * before the coprocessors are initialized.
+     */
+    psp = ASPEED_SOC(&s->ca35);
+    ast2700fc_ssp_init(s, psp, &error_abort);
+    ast2700fc_tsp_init(s, psp, &error_abort);
 }
 
 static void ast2700fc_class_init(ObjectClass *oc, const void *data)
-- 
2.55.0



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

* [PULL 23/83] hw/arm/ast27x0: Share single SCUIO instance across PSP, SSP, and TSP
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (21 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 22/83] hw/arm/aspeed_ast27x0: Pass realized PSP SoC to SSP/TSP initialization Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 24/83] hw/arm/ast27x0: Share FMC controller with SSP " Cédric Le Goater
                   ` (60 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

AST2700 has a single SCUIO hardware block, memory-mapped at
0x14C02000–0x14C03FFF from the perspective of the main CA35 processor (PSP).
The SSP and TSP coprocessors access this same SCUIO block at different
addresses: 0x74C02000–0x74C03FFF.

Previously, each subsystem (PSP, SSP, and TSP) instantiated its own SCUIO
device, resulting in three independent SCUIO instances in the QEMU model.
In real hardware, however, only a single SCUIO exists and is shared among
all processors.

This commit reworks the SCUIO model to correctly reflect the hardware
behavior by allowing SSP and TSP to reference the PSP’s SCUIO instance.
The following changes are introduced:

- Add a scuio property to Aspeed27x0CoprocessorState for linking the
  coprocessor to the PSP’s SCUIO instance.
- Replace per-coprocessor SCUIO instantiation with a shared SCUIO link.
- Add "MemoryRegion scuio_alias" to model address remapping for SSP and TSP.
- Create SCUIO alias regions in both SSP and TSP coprocessors and map
  them at 0x74C02000 to mirror the PSP’s SCUIO registers.
- Ensure the SCUIO device in PSP is realized before SSP/TSP alias setup.

With this change, PSP, SSP, and TSP now share a consistent SCUIO state,
matching the single-SCUIO hardware design of AST2700.

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>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-7-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_coprocessor.h |  4 ++--
 hw/arm/aspeed_ast27x0-fc.c          |  4 ++++
 hw/arm/aspeed_ast27x0-ssp.c         | 20 +++++++++++++++-----
 hw/arm/aspeed_ast27x0-tsp.c         | 20 +++++++++++++++-----
 4 files changed, 36 insertions(+), 12 deletions(-)

diff --git a/include/hw/arm/aspeed_coprocessor.h b/include/hw/arm/aspeed_coprocessor.h
index adfc3c451239..b77ea06e7f68 100644
--- a/include/hw/arm/aspeed_coprocessor.h
+++ b/include/hw/arm/aspeed_coprocessor.h
@@ -22,7 +22,6 @@ struct AspeedCoprocessorState {
     MemoryRegion uart_alias;
     Clock *sysclk;
 
-    AspeedSCUState scuio;
     AspeedTimerCtrlState timerctrl;
     SerialMM *uart;
     int uart_dev;
@@ -45,14 +44,15 @@ struct Aspeed27x0CoprocessorState {
     AspeedCoprocessorState parent;
     AspeedINTCState intc[2];
     UnimplementedDeviceState ipc[2];
-    UnimplementedDeviceState scuio;
     UnimplementedDeviceState pric[2];
     UnimplementedDeviceState otp;
 
     ARMv7MState armv7m;
 
     MemoryRegion scu_alias;
+    MemoryRegion scuio_alias;
     Aspeed2700SCUState *scu;
+    AspeedSCUState *scuio;
 };
 
 #define TYPE_ASPEED27X0SSP_COPROCESSOR "aspeed27x0ssp-coprocessor"
diff --git a/hw/arm/aspeed_ast27x0-fc.c b/hw/arm/aspeed_ast27x0-fc.c
index 58d0b4d7cd59..d14a42ebca3f 100644
--- a/hw/arm/aspeed_ast27x0-fc.c
+++ b/hw/arm/aspeed_ast27x0-fc.c
@@ -157,6 +157,8 @@ static bool ast2700fc_ssp_init(Ast2700FCState *s, AspeedSoCState *psp,
                              OBJECT(&psp->sram), &error_abort);
     object_property_set_link(OBJECT(&s->ssp), "scu",
                              OBJECT(&s->ca35.scu), &error_abort);
+    object_property_set_link(OBJECT(&s->ssp), "scuio",
+                             OBJECT(&psp->scuio), &error_abort);
     if (!qdev_realize(DEVICE(&s->ssp), NULL, errp)) {
         return false;
     }
@@ -187,6 +189,8 @@ static bool ast2700fc_tsp_init(Ast2700FCState *s, AspeedSoCState *psp,
                              OBJECT(&psp->sram), &error_abort);
     object_property_set_link(OBJECT(&s->tsp), "scu",
                              OBJECT(&s->ca35.scu), &error_abort);
+    object_property_set_link(OBJECT(&s->tsp), "scuio",
+                             OBJECT(&psp->scuio), &error_abort);
     if (!qdev_realize(DEVICE(&s->tsp), NULL, errp)) {
         return false;
     }
diff --git a/hw/arm/aspeed_ast27x0-ssp.c b/hw/arm/aspeed_ast27x0-ssp.c
index 28bb59c8c56a..fa492f179732 100644
--- a/hw/arm/aspeed_ast27x0-ssp.c
+++ b/hw/arm/aspeed_ast27x0-ssp.c
@@ -142,8 +142,6 @@ static void aspeed_soc_ast27x0ssp_init(Object *obj)
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "ipc1", &a->ipc[1],
                             TYPE_UNIMPLEMENTED_DEVICE);
-    object_initialize_child(obj, "scuio", &a->scuio,
-                            TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric0", &a->pric[0],
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric1", &a->pric[1],
@@ -173,6 +171,12 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
         return;
     }
 
+    if (!a->scuio) {
+        error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR
+                   ": 'scuio' link is not set");
+        return;
+    }
+
     /* AST27X0 SSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -207,6 +211,13 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
     memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU],
                                 &a->scu_alias);
 
+    /* SCUIO */
+    memory_region_init_alias(&a->scuio_alias, OBJECT(a), "scuio.alias",
+                             &a->scuio->iomem, 0,
+                             memory_region_size(&a->scuio->iomem));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCUIO],
+                                &a->scuio_alias);
+
     /* INTC */
     if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) {
         return;
@@ -267,9 +278,6 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->ipc[1]),
                                   "aspeed.ipc1",
                                   sc->memmap[ASPEED_DEV_IPC1], 0x1000);
-    aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->scuio),
-                                  "aspeed.scuio",
-                                  sc->memmap[ASPEED_DEV_SCUIO], 0x1000);
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->pric[0]),
                                   "aspeed.pric0",
                                   sc->memmap[ASPEED_DEV_PRIC0], 0x1000);
@@ -284,6 +292,8 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
 static const Property aspeed_27x0_coprocessor_properties[] = {
     DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu,
                      TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
+    DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio,
+                     TYPE_ASPEED_SCU, AspeedSCUState *),
 };
 
 static void aspeed_soc_ast27x0ssp_class_init(ObjectClass *klass,
diff --git a/hw/arm/aspeed_ast27x0-tsp.c b/hw/arm/aspeed_ast27x0-tsp.c
index f5c4a021616b..3a98bd232c0b 100644
--- a/hw/arm/aspeed_ast27x0-tsp.c
+++ b/hw/arm/aspeed_ast27x0-tsp.c
@@ -142,8 +142,6 @@ static void aspeed_soc_ast27x0tsp_init(Object *obj)
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "ipc1", &a->ipc[1],
                             TYPE_UNIMPLEMENTED_DEVICE);
-    object_initialize_child(obj, "scuio", &a->scuio,
-                            TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric0", &a->pric[0],
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric1", &a->pric[1],
@@ -173,6 +171,12 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
         return;
     }
 
+    if (!a->scuio) {
+        error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR
+                   ": 'scuio' link is not set");
+        return;
+    }
+
     /* AST27X0 TSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -207,6 +211,13 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
     memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU],
                                 &a->scu_alias);
 
+    /* SCUIO */
+    memory_region_init_alias(&a->scuio_alias, OBJECT(a), "scuio.alias",
+                             &a->scuio->iomem, 0,
+                             memory_region_size(&a->scuio->iomem));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCUIO],
+                                &a->scuio_alias);
+
     /* INTC */
     if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) {
         return;
@@ -267,9 +278,6 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->ipc[1]),
                                   "aspeed.ipc1",
                                   sc->memmap[ASPEED_DEV_IPC1], 0x1000);
-    aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->scuio),
-                                  "aspeed.scuio",
-                                  sc->memmap[ASPEED_DEV_SCUIO], 0x1000);
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->pric[0]),
                                   "aspeed.pric0",
                                   sc->memmap[ASPEED_DEV_PRIC0], 0x1000);
@@ -284,6 +292,8 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
 static const Property aspeed_27x0_coprocessor_properties[] = {
     DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu,
                      TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
+    DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio,
+                     TYPE_ASPEED_SCU, AspeedSCUState *),
 };
 
 static void aspeed_soc_ast27x0tsp_class_init(ObjectClass *klass,
-- 
2.55.0



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

* [PULL 24/83] hw/arm/ast27x0: Share FMC controller with SSP and TSP
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (22 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 23/83] hw/arm/ast27x0: Share single SCUIO instance across PSP, SSP, and TSP Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 25/83] hw/ssi/aspeed_smc: Add Data FIFO-based flash access support for AST2700 Cédric Le Goater
                   ` (59 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 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>

AST2700 provides a single FMC controller shared by the main CA35 processor
(PSP) and the SSP/TSP coprocessors.

>From the PSP perspective, the FMC controller is memory-mapped at
0x14000000–0x140000FF. The SSP and TSP access the same controller through
a different address window at 0x74000000–0x740000FF.

This change allows the SSP and TSP SoC models to reference the existing
PSP FMC instance instead of creating independent controllers. An MMIO
alias is added in the SSP and TSP address spaces to map their FMC access
window to the shared FMC device.

This ensures consistent FMC state across PSP, SSP, and TSP and matches
the AST2700 hardware design.

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>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-8-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/arm/aspeed_coprocessor.h |  7 +++++++
 hw/arm/aspeed_ast27x0-fc.c          |  4 ++++
 hw/arm/aspeed_ast27x0-ssp.c         | 16 ++++++++++++++++
 hw/arm/aspeed_ast27x0-tsp.c         | 16 ++++++++++++++++
 4 files changed, 43 insertions(+)

diff --git a/include/hw/arm/aspeed_coprocessor.h b/include/hw/arm/aspeed_coprocessor.h
index b77ea06e7f68..acb8b0645777 100644
--- a/include/hw/arm/aspeed_coprocessor.h
+++ b/include/hw/arm/aspeed_coprocessor.h
@@ -49,10 +49,17 @@ struct Aspeed27x0CoprocessorState {
 
     ARMv7MState armv7m;
 
+    /*
+     * SCU, SCUIO and FMC are not owned by this coprocessor: they are
+     * shared with the main PSP SoC, and only aliased into this
+     * coprocessor's own address space here.
+     */
     MemoryRegion scu_alias;
     MemoryRegion scuio_alias;
+    MemoryRegion fmc_alias;
     Aspeed2700SCUState *scu;
     AspeedSCUState *scuio;
+    AspeedSMCState *fmc;
 };
 
 #define TYPE_ASPEED27X0SSP_COPROCESSOR "aspeed27x0ssp-coprocessor"
diff --git a/hw/arm/aspeed_ast27x0-fc.c b/hw/arm/aspeed_ast27x0-fc.c
index d14a42ebca3f..058cea42edca 100644
--- a/hw/arm/aspeed_ast27x0-fc.c
+++ b/hw/arm/aspeed_ast27x0-fc.c
@@ -159,6 +159,8 @@ static bool ast2700fc_ssp_init(Ast2700FCState *s, AspeedSoCState *psp,
                              OBJECT(&s->ca35.scu), &error_abort);
     object_property_set_link(OBJECT(&s->ssp), "scuio",
                              OBJECT(&psp->scuio), &error_abort);
+    object_property_set_link(OBJECT(&s->ssp), "fmc",
+                             OBJECT(&psp->fmc), &error_abort);
     if (!qdev_realize(DEVICE(&s->ssp), NULL, errp)) {
         return false;
     }
@@ -191,6 +193,8 @@ static bool ast2700fc_tsp_init(Ast2700FCState *s, AspeedSoCState *psp,
                              OBJECT(&s->ca35.scu), &error_abort);
     object_property_set_link(OBJECT(&s->tsp), "scuio",
                              OBJECT(&psp->scuio), &error_abort);
+    object_property_set_link(OBJECT(&s->tsp), "fmc",
+                             OBJECT(&psp->fmc), &error_abort);
     if (!qdev_realize(DEVICE(&s->tsp), NULL, errp)) {
         return false;
     }
diff --git a/hw/arm/aspeed_ast27x0-ssp.c b/hw/arm/aspeed_ast27x0-ssp.c
index fa492f179732..e03653086c88 100644
--- a/hw/arm/aspeed_ast27x0-ssp.c
+++ b/hw/arm/aspeed_ast27x0-ssp.c
@@ -27,6 +27,7 @@ static const hwaddr aspeed_soc_ast27x0ssp_memmap[] = {
     [ASPEED_DEV_TIMER1]    =  0x72C10000,
     [ASPEED_DEV_UART4]     =  0x72C1A000,
     [ASPEED_DEV_IPC0]      =  0x72C1C000,
+    [ASPEED_DEV_FMC]       =  0x74000000,
     [ASPEED_DEV_PRIC1]     =  0x74100000,
     [ASPEED_DEV_SCUIO]     =  0x74C02000,
     [ASPEED_DEV_OTP]       =  0x74C07000,
@@ -177,6 +178,12 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
         return;
     }
 
+    if (!a->fmc) {
+        error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR
+                   ": 'fmc' link is not set");
+        return;
+    }
+
     /* AST27X0 SSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -269,6 +276,13 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(s->uart), 0,
                        aspeed_soc_ast27x0ssp_get_irq(s, s->uart_dev));
 
+    /* FMC */
+    memory_region_init_alias(&a->fmc_alias, OBJECT(a), "fmc.alias",
+                             &a->fmc->mmio, 0,
+                             memory_region_size(&a->fmc->mmio));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_FMC],
+                                &a->fmc_alias);
+
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->timerctrl),
                                   "aspeed.timerctrl",
                                   sc->memmap[ASPEED_DEV_TIMER1], 0x200);
@@ -294,6 +308,8 @@ static const Property aspeed_27x0_coprocessor_properties[] = {
                      TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
     DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio,
                      TYPE_ASPEED_SCU, AspeedSCUState *),
+    DEFINE_PROP_LINK("fmc", Aspeed27x0CoprocessorState, fmc, TYPE_ASPEED_SMC,
+                     AspeedSMCState *),
 };
 
 static void aspeed_soc_ast27x0ssp_class_init(ObjectClass *klass,
diff --git a/hw/arm/aspeed_ast27x0-tsp.c b/hw/arm/aspeed_ast27x0-tsp.c
index 3a98bd232c0b..39ba062a20f7 100644
--- a/hw/arm/aspeed_ast27x0-tsp.c
+++ b/hw/arm/aspeed_ast27x0-tsp.c
@@ -27,6 +27,7 @@ static const hwaddr aspeed_soc_ast27x0tsp_memmap[] = {
     [ASPEED_DEV_TIMER1]    =  0x72C10000,
     [ASPEED_DEV_UART4]     =  0x72C1A000,
     [ASPEED_DEV_IPC0]      =  0x72C1C000,
+    [ASPEED_DEV_FMC]       =  0x74000000,
     [ASPEED_DEV_PRIC1]     =  0x74100000,
     [ASPEED_DEV_SCUIO]     =  0x74C02000,
     [ASPEED_DEV_OTP]       =  0x74C07000,
@@ -177,6 +178,12 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
         return;
     }
 
+    if (!a->fmc) {
+        error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR
+                   ": 'fmc' link is not set");
+        return;
+    }
+
     /* AST27X0 TSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -269,6 +276,13 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(s->uart), 0,
                        aspeed_soc_ast27x0tsp_get_irq(s, s->uart_dev));
 
+    /* FMC */
+    memory_region_init_alias(&a->fmc_alias, OBJECT(a), "fmc.alias",
+                             &a->fmc->mmio, 0,
+                             memory_region_size(&a->fmc->mmio));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_FMC],
+                                &a->fmc_alias);
+
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->timerctrl),
                                   "aspeed.timerctrl",
                                   sc->memmap[ASPEED_DEV_TIMER1], 0x200);
@@ -294,6 +308,8 @@ static const Property aspeed_27x0_coprocessor_properties[] = {
                      TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
     DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio,
                      TYPE_ASPEED_SCU, AspeedSCUState *),
+    DEFINE_PROP_LINK("fmc", Aspeed27x0CoprocessorState, fmc, TYPE_ASPEED_SMC,
+                     AspeedSMCState *),
 };
 
 static void aspeed_soc_ast27x0tsp_class_init(ObjectClass *klass,
-- 
2.55.0



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

* [PULL 25/83] hw/ssi/aspeed_smc: Add Data FIFO-based flash access support for AST2700
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (23 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 24/83] hw/arm/ast27x0: Share FMC controller with SSP " Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 26/83] tests/qtest/ast2700-smc-test: Add Data FIFO mode test Cédric Le Goater
                   ` (58 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Cédric Le Goater, Philippe Mathieu-Daudé

From: Jamin Lin <jamin_lin@aspeedtech.com>

AST2700 supports a Data FIFO mode where flash accesses can be performed
directly through Data FIFO MMIO offsets. The Data FIFO start offset
increments by one for every 16MB of flash address space, allowing the
chip select (CS) to be decoded from the Data FIFO offset.

This change adds Data FIFO support to the Aspeed SMC model and introduces
a class callback to translate Data FIFO offsets into CS indices. For
AST2700, the Data FIFO offset is matched against the segment start address
of each CS to determine the target flash device.

The SMC register region size (nregs) is also extended dynamically
based on the number of supported chip selects to cover all possible
Data FIFO regions.

This breaks migration compatibility with older QEMU builds for the
affected models, even though Aspeed machines are not officially
covered by migration compatibility guarantees.

Bump version_id to 4 and minimum_version_id to 2 to reflect the
incompatible format.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Link: https://lore.kernel.org/qemu-devel/20260717084559.3477061-9-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/ssi/aspeed_smc.h |   3 +-
 hw/ssi/aspeed_smc.c         | 117 +++++++++++++++++++++++++++++++++---
 2 files changed, 109 insertions(+), 11 deletions(-)

diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h
index a273365689ea..5f391fc24ef3 100644
--- a/include/hw/ssi/aspeed_smc.h
+++ b/include/hw/ssi/aspeed_smc.h
@@ -47,7 +47,7 @@ struct AspeedSMCFlash {
 #define TYPE_ASPEED_SMC "aspeed.smc"
 OBJECT_DECLARE_TYPE(AspeedSMCState, AspeedSMCClass, ASPEED_SMC)
 
-#define ASPEED_SMC_R_MAX        (0x100 / 4)
+#define ASPEED_SMC_R_MAX        (0x300 / 4)
 #define ASPEED_SMC_CS_MAX       5
 
 struct AspeedSMCState {
@@ -114,6 +114,7 @@ struct AspeedSMCClass {
                            AspeedSegments *seg);
     void (*dma_ctrl)(AspeedSMCState *s, uint32_t value);
     int (*addr_width)(const AspeedSMCState *s);
+    int (*data_fifo_offset_to_cs)(const AspeedSMCState *s, uint32_t offset);
     const MemoryRegionOps *reg_ops;
 };
 
diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
index c8cc6cfa56e4..bf596f7b2d68 100644
--- a/hw/ssi/aspeed_smc.c
+++ b/hw/ssi/aspeed_smc.c
@@ -163,6 +163,9 @@
 /* Read Timing Compensation Register */
 #define R_TIMINGS         (0x94 / 4)
 
+/* Data fifo */
+#define R_DATA_FIFO       (0x200 / 4)
+
 /* SPI controller registers and bits (AST2400) */
 #define R_SPI_CONF        (0x00 / 4)
 #define   SPI_CONF_ENABLE_W0   0
@@ -209,6 +212,7 @@ static const AspeedSegments aspeed_2500_spi2_segments[];
 #define ASPEED_SMC_FEATURE_DMA_GRANT 0x2
 #define ASPEED_SMC_FEATURE_WDT_CONTROL 0x4
 #define ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH 0x08
+#define ASPEED_SMC_FEATURE_DATA_FIFO 0x10
 
 static inline bool aspeed_smc_has_dma(const AspeedSMCClass *asc)
 {
@@ -225,6 +229,11 @@ static inline bool aspeed_smc_has_dma64(const AspeedSMCClass *asc)
     return !!(asc->features & ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH);
 }
 
+static inline bool aspeed_smc_has_data_fifo(const AspeedSMCClass *asc)
+{
+    return !!(asc->features & ASPEED_SMC_FEATURE_DATA_FIFO);
+}
+
 #define aspeed_smc_error(fmt, ...)                                      \
     qemu_log_mask(LOG_GUEST_ERROR, "%s: " fmt "\n", __func__, ## __VA_ARGS__)
 
@@ -664,6 +673,7 @@ static MemTxResult aspeed_smc_read(void *opaque, hwaddr addr, uint64_t *data,
 {
     AspeedSMCState *s = ASPEED_SMC(opaque);
     AspeedSMCClass *asc = ASPEED_SMC_GET_CLASS(opaque);
+    int cs;
 
     addr >>= 2;
 
@@ -689,6 +699,18 @@ static MemTxResult aspeed_smc_read(void *opaque, hwaddr addr, uint64_t *data,
         trace_aspeed_smc_read(addr << 2, size, s->regs[addr]);
 
         *data = s->regs[addr];
+    } else if (aspeed_smc_has_data_fifo(asc) && addr >= R_DATA_FIFO) {
+        cs = asc->data_fifo_offset_to_cs(s, addr << 2);
+        if (cs >= 0) {
+            /*
+             * Data fifo mode only supports SPI user mode.
+             * The flash address is provided by the SPI command/address cycles,
+             * the MMIO addr parameter is ignored.
+             */
+            return aspeed_smc_flash_read(&s->flashes[cs], 0, data, size, attrs);
+        }
+        aspeed_smc_error("Invalid data fifo offset %" HWADDR_PRIx, addr << 2);
+        return MEMTX_ERROR;
     } else {
         qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n",
                       __func__, addr);
@@ -1063,6 +1085,19 @@ static MemTxResult aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data,
     } else if (aspeed_smc_has_dma(asc) && aspeed_smc_has_dma64(asc) &&
                addr == R_DMA_DRAM_ADDR_HIGH) {
         s->regs[addr] = DMA_DRAM_ADDR_HIGH(value);
+    } else if (aspeed_smc_has_data_fifo(asc) && addr >= R_DATA_FIFO) {
+        int cs = asc->data_fifo_offset_to_cs(s, addr << 2);
+        if (cs >= 0) {
+            /*
+             * Data fifo mode only supports SPI user mode.
+             * The flash address is provided by the SPI command/address cycles,
+             * the MMIO addr parameter is ignored.
+             */
+            return aspeed_smc_flash_write(&s->flashes[cs], 0, data, size,
+                                          attrs);
+        }
+        aspeed_smc_error("Invalid data fifo offset %" HWADDR_PRIx, addr << 2);
+        return MEMTX_ERROR;
     } else {
         qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n",
                       __func__, addr);
@@ -1183,8 +1218,8 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp)
 
 static const VMStateDescription vmstate_aspeed_smc = {
     .name = "aspeed.smc",
-    .version_id = 3,
-    .minimum_version_id = 1,
+    .version_id = 4,
+    .minimum_version_id = 2,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32_ARRAY(regs, AspeedSMCState, ASPEED_SMC_R_MAX),
         VMSTATE_UNUSED_V(2, 2), /* was snoop_index/snoop_dummies */
@@ -1808,6 +1843,39 @@ static void aspeed_2700_smc_reg_to_segment(const AspeedSMCState *s,
     }
 }
 
+/*
+ * Convert a data fifo offset to a chip select (CS).
+ *
+ * Data fifo access starts at 0x200. The data fifo offset index is
+ * calculated by subtracting the data fifo base offset from the MMIO address.
+ *
+ * The data fifo offset index increments by 1 for every 16MB of flash address
+ * space. Each offset step therefore represents a 16MB address decode range.
+ *
+ * The CS is determined by matching the data fifo offset index against the
+ * segment start address of each CS.
+ *
+ * Returns the CS index on success, or -1 if the offset is invalid.
+ */
+static int aspeed_2700_smc_data_fifo_offset_to_cs(const AspeedSMCState *s,
+                                                  uint32_t offset)
+{
+    AspeedSMCClass *asc = ASPEED_SMC_GET_CLASS(s);
+    uint32_t start_offset;
+    uint32_t fifo_offset;
+    int i;
+
+    for (i = 0; i < asc->cs_num_max; i++) {
+        start_offset = (s->regs[R_SEG_ADDR0 + i] & 0x0000ffff) << 16;
+        fifo_offset = start_offset / 0x1000000;
+        if (fifo_offset == offset - (R_DATA_FIFO << 2)) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
 static const uint32_t aspeed_2700_fmc_resets[ASPEED_SMC_R_MAX] = {
     [R_CONF] = (CONF_FLASH_TYPE_SPI << CONF_FLASH_TYPE0 |
             CONF_FLASH_TYPE_SPI << CONF_FLASH_TYPE1),
@@ -1842,6 +1910,27 @@ static const AspeedSegments aspeed_2700_fmc_segments[] = {
     { 0x0, 0 }, /* disabled */
 };
 
+/*
+ * AST2700 supports data fifo mode with a base data fifo start offset of 0x200.
+ *
+ * The data fifo start offset increments by 1 for every 16MB of flash address
+ * space. Each offset step therefore represents a 16MB address decode range.
+ *
+ * Assuming each chip select (CS) can use the maximum flash size of 256MB:
+ *   256MB / 16MB = 0x10 offset steps per CS.
+ *
+ * Data fifo start offset for CSn:
+ *   0x200 + (n * 0x10)
+ *
+ * Examples:
+ *   CS0: 0x200
+ *   CS1: 0x210
+ *   CS2: 0x220
+ *   CS3: 0x230
+ *
+ * asc->nregs should be set to: 0x200 + (asc->cs_num_max * 0x10)
+ * to cover all possible data fifo regions.
+ */
 static void aspeed_2700_fmc_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -1861,14 +1950,16 @@ static void aspeed_2700_fmc_class_init(ObjectClass *klass, const void *data)
     asc->flash_window_base = 0x100000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x2FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
@@ -1896,14 +1987,16 @@ static void aspeed_2700_spi0_class_init(ObjectClass *klass, const void *data)
     asc->flash_window_base = 0x180000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x2FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
@@ -1930,14 +2023,16 @@ static void aspeed_2700_spi1_class_init(ObjectClass *klass, const void *data)
     asc->flash_window_base = 0x200000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x2FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
@@ -1964,14 +2059,16 @@ static void aspeed_2700_spi2_class_init(ObjectClass *klass, const void *data)
     asc->flash_window_base = 0x280000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x0FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
-- 
2.55.0



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

* [PULL 26/83] tests/qtest/ast2700-smc-test: Add Data FIFO mode test
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (24 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 25/83] hw/ssi/aspeed_smc: Add Data FIFO-based flash access support for AST2700 Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 27/83] hw/sensor: adc128d818: add 12-bit 8-channel ADC device Cédric Le Goater
                   ` (57 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Add two qtest cases exercising the new AST2700 Data FIFO-based flash
access path (R_DATA_FIFO at spi_base + 0x200).

Write_page_datafifo sends the page-program command and data through
the FIFO port, then verifies the result via the regular read path.
Read_page_datafifo writes a page the regular way, then reads it back
through the FIFO port, so both directions are checked independently.

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/20260717084559.3477061-10-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-smc-utils.h |   4 ++
 tests/qtest/aspeed-smc-utils.c | 102 +++++++++++++++++++++++++++++++++
 tests/qtest/ast2700-smc-test.c |   4 ++
 3 files changed, 110 insertions(+)

diff --git a/tests/qtest/aspeed-smc-utils.h b/tests/qtest/aspeed-smc-utils.h
index 19c557b822da..e4f538e5794b 100644
--- a/tests/qtest/aspeed-smc-utils.h
+++ b/tests/qtest/aspeed-smc-utils.h
@@ -33,6 +33,8 @@
 #define   CTRL_DUMMY_LOW_SHIFT   6
 #define   CTRL_DUMMY_HIGH_SHIFT  14
 #define SR_WEL BIT(1)
+/* Data fifo */
+#define R_DATA_FIFO 0x200
 
 /*
  * Flash commands
@@ -87,5 +89,7 @@ void aspeed_smc_test_read_page_mem_dor(const void *data);
 void aspeed_smc_test_write_page_dor(const void *data);
 void aspeed_smc_test_read_page_mem_qor(const void *data);
 void aspeed_smc_test_write_page_qor(const void *data);
+void aspeed_smc_test_write_page_datafifo(const void *data);
+void aspeed_smc_test_read_page_datafifo(const void *data);
 
 #endif /* TESTS_ASPEED_SMC_UTILS_H */
diff --git a/tests/qtest/aspeed-smc-utils.c b/tests/qtest/aspeed-smc-utils.c
index 6d75a95578e7..146332240d66 100644
--- a/tests/qtest/aspeed-smc-utils.c
+++ b/tests/qtest/aspeed-smc-utils.c
@@ -57,6 +57,28 @@ static inline uint32_t flash_readl(const AspeedSMCTestData *data,
     return qtest_readl(data->s, data->flash_base + offset);
 }
 
+/*
+ * Data FIFO port, in spi_base's register bank (not flash_base). Accesses
+ * through the FIFO require the complete user-mode transaction (opcode,
+ * address, and data). Assumes CS0, whose FIFO slot is at R_DATA_FIFO.
+ */
+static inline void datafifo_writeb(const AspeedSMCTestData *data,
+                                   uint8_t value)
+{
+    qtest_writeb(data->s, data->spi_base + R_DATA_FIFO, value);
+}
+
+static inline void datafifo_writel(const AspeedSMCTestData *data,
+                                   uint32_t value)
+{
+    spi_writel(data, R_DATA_FIFO, value);
+}
+
+static inline uint32_t datafifo_readl(const AspeedSMCTestData *data)
+{
+    return spi_readl(data, R_DATA_FIFO);
+}
+
 static void spi_conf(const AspeedSMCTestData *data, uint32_t value)
 {
     uint32_t conf = spi_readl(data, R_CONF);
@@ -826,3 +848,83 @@ void aspeed_smc_test_write_page_qor(const void *data)
 {
     test_write_page(data, read_page_qor);
 }
+
+void aspeed_smc_test_write_page_datafifo(const void *data)
+{
+    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
+    uint32_t my_page_addr = test_data->page_addr;
+    uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE;
+    uint32_t page[FLASH_PAGE_SIZE / 4];
+    int i;
+
+    spi_conf(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs));
+
+    /*
+     * Send the complete user-mode transaction (opcode, address, data)
+     * through the Data FIFO port.
+     */
+    spi_ctrl_start_user(test_data);
+    datafifo_writeb(test_data, EN_4BYTE_ADDR);
+    datafifo_writeb(test_data, WREN);
+    datafifo_writeb(test_data, PP);
+    datafifo_writel(test_data, make_be32(my_page_addr));
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        datafifo_writel(test_data, make_be32(my_page_addr + i * 4));
+    }
+    spi_ctrl_stop_user(test_data);
+
+    /* Check what was written, using the regular read path */
+    read_page(test_data, my_page_addr, page);
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
+    }
+
+    /* Check some other page. It should be full of 0xff */
+    read_page(test_data, some_page_addr, page);
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        g_assert_cmphex(page[i], ==, 0xffffffff);
+    }
+
+    flash_reset(test_data);
+}
+
+void aspeed_smc_test_read_page_datafifo(const void *data)
+{
+    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
+    uint32_t my_page_addr = test_data->page_addr;
+    uint32_t page[FLASH_PAGE_SIZE / 4];
+    int i;
+
+    spi_conf(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs));
+
+    /* Write the page the regular way */
+    spi_ctrl_start_user(test_data);
+    flash_writeb(test_data, 0, EN_4BYTE_ADDR);
+    flash_writeb(test_data, 0, WREN);
+    flash_writeb(test_data, 0, PP);
+    flash_writel(test_data, 0, make_be32(my_page_addr));
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        flash_writel(test_data, 0, make_be32(my_page_addr + i * 4));
+    }
+    spi_ctrl_stop_user(test_data);
+
+    /*
+     * Read it back through the data FIFO port, again sending the whole
+     * transaction (opcode, address, data) through it.
+     */
+    spi_ctrl_start_user(test_data);
+    datafifo_writeb(test_data, EN_4BYTE_ADDR);
+    datafifo_writeb(test_data, READ);
+    datafifo_writel(test_data, make_be32(my_page_addr));
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(datafifo_readl(test_data));
+    }
+    spi_ctrl_stop_user(test_data);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
+    }
+
+    flash_reset(test_data);
+}
diff --git a/tests/qtest/ast2700-smc-test.c b/tests/qtest/ast2700-smc-test.c
index f85077e04ff4..925dbcfaaf63 100644
--- a/tests/qtest/ast2700-smc-test.c
+++ b/tests/qtest/ast2700-smc-test.c
@@ -64,6 +64,10 @@ static void test_ast2700_evb(AspeedSMCTestData *data)
                         data, aspeed_smc_test_read_page_mem_qor);
     qtest_add_data_func("/ast2700/smc/write_page_qor",
                         data, aspeed_smc_test_write_page_qor);
+    qtest_add_data_func("/ast2700/smc/write_page_datafifo",
+                        data, aspeed_smc_test_write_page_datafifo);
+    qtest_add_data_func("/ast2700/smc/read_page_datafifo",
+                        data, aspeed_smc_test_read_page_datafifo);
 }
 
 int main(int argc, char **argv)
-- 
2.55.0



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

* [PULL 27/83] hw/sensor: adc128d818: add 12-bit 8-channel ADC device
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (25 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 26/83] tests/qtest/ast2700-smc-test: Add Data FIFO mode test Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 28/83] tests/qtest: adc128d818: add test harness and register access Cédric Le Goater
                   ` (56 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Emmanuel Blot, Alexander Hansen, Cédric Le Goater

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

The ADC128D818 is a TI 12-bit, 8-channel I2C ADC used on several
OpenBMC platforms for voltage and temperature monitoring.

Implement the device with:
 - four operating modes
 - 12-bit voltage conversion from QOM inputs
 - 9-bit temperature conversion from milli-degree Celsius QOM inputs
 - switchable internal or external voltage reference
 - per-channel high/low limit registers
 - interrupt support
 - software reset
 - one-shot conversion support in shutdown mode

Reviewed-by: Alexander Hansen <alexander.hansen@9elements.com>
Tested-by: Alexander Hansen <alexander.hansen@9elements.com>
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260707091609.97759-2-emmanuel.blot@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/sensor/adc128d818.h |  14 +
 hw/sensor/adc128d818.c         | 696 +++++++++++++++++++++++++++++++++
 hw/sensor/Kconfig              |   4 +
 hw/sensor/meson.build          |   1 +
 hw/sensor/trace-events         |   8 +
 5 files changed, 723 insertions(+)
 create mode 100644 include/hw/sensor/adc128d818.h
 create mode 100644 hw/sensor/adc128d818.c

diff --git a/include/hw/sensor/adc128d818.h b/include/hw/sensor/adc128d818.h
new file mode 100644
index 000000000000..10c34b964645
--- /dev/null
+++ b/include/hw/sensor/adc128d818.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_ADC128D818_H
+#define HW_SENSOR_ADC128D818_H
+
+#define TYPE_ADC128D818 "adc128d818"
+
+#endif
diff --git a/hw/sensor/adc128d818.c b/hw/sensor/adc128d818.c
new file mode 100644
index 000000000000..c65508cba144
--- /dev/null
+++ b/hw/sensor/adc128d818.c
@@ -0,0 +1,696 @@
+/*
+ * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/adc128d818.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+
+/* Register addresses */
+#define REG_CONFIG              0x00
+#define REG_INT_STATUS          0x01
+#define REG_INT_MASK            0x03
+#define REG_CONV_RATE           0x07
+#define REG_CH_DISABLE          0x08
+#define REG_ONE_SHOT            0x09
+#define REG_DEEP_SHUTDOWN       0x0a
+#define REG_ADV_CONFIG          0x0b
+#define REG_BUSY_STATUS         0x0c
+
+/* Channel Reading Registers (16-bit, read-only) */
+#define REG_CH_READING_BASE     0x20
+#define REG_CH_READING_LAST     0x27
+
+/* Limit Registers (8-bit, read/write) */
+#define REG_LIMIT_BASE          0x2a
+#define REG_LIMIT_LAST          0x39
+
+/* ID Registers (read-only) */
+#define REG_MANUFACTURER_ID     0x3e
+#define REG_REVISION_ID         0x3f
+
+/* Configuration Register (0x00) bitfields */
+#define CONFIG_START            BIT(0)
+#define CONFIG_INT_ENABLE       BIT(1)
+#define CONFIG_INT_CLEAR        BIT(3)
+#define CONFIG_INITIALIZATION   BIT(7)
+#define CONFIG_WR_MASK \
+    (CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR)
+
+/* Advanced Configuration Register (0x0B) bitfields */
+#define ADV_CONFIG_EXT_REF_EN   BIT(0)
+#define ADV_CONFIG_MODE_SHIFT   1
+#define ADV_CONFIG_MODE_MASK    (0x3 << ADV_CONFIG_MODE_SHIFT)
+#define ADV_CONFIG_WR_MASK \
+    (ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_MASK)
+
+/* Busy Status Register (0x0C) bitfields */
+#define BUSY_STATUS_NOT_READY   BIT(1)
+
+/* Conversion Rate Register (0x07) bitfields */
+#define CONV_RATE_MASK          0x01
+
+/* Deep Shutdown Register (0x0A) bitfields */
+#define DEEP_SHUTDOWN_EN        0x01
+
+/* Device constants */
+#define ADC128D818_NUM_CHANNELS         8
+#define ADC128D818_NUM_REGS             0x40
+
+#define ADC128D818_INTERNAL_VREF_MV     2560
+#define ADC128D818_MAX_VDD_MV           5500
+#define ADC128D818_MANUFACTURER_ID_VAL  0x01
+#define ADC128D818_REVISION_ID_VAL      0x09
+
+/* ADC resolution */
+#define ADC128D818_ADC_RESOLUTION       4096
+#define ADC128D818_ADC_MAX              4095
+
+/* Temperature: 0.5 deg C per LSb = 500 milli-degrees per LSb */
+#define ADC128D818_TEMP_LSB_MC          500
+#define ADC128D818_TEMP_RAW_MIN         (-256)
+#define ADC128D818_TEMP_RAW_MAX         255
+
+
+OBJECT_DECLARE_SIMPLE_TYPE(ADC128D818State, ADC128D818)
+
+struct ADC128D818State {
+    I2CSlave parent_obj;
+
+    qemu_irq irq;
+
+    uint8_t len;
+    uint8_t pointer;
+    uint8_t rx_byte;
+
+    uint8_t regs[ADC128D818_NUM_REGS];
+    uint16_t channel[ADC128D818_NUM_CHANNELS];
+
+    int16_t ain[ADC128D818_NUM_CHANNELS]; /* mV */
+    int32_t temperature; /* milli-degrees Celsius */
+    uint16_t ext_vref; /* mV, 0 means not connected */
+    bool temp_alarm; /* temperature high-limit alarm latched */
+
+    char *description;
+};
+
+static uint16_t adc128d818_get_vref(const ADC128D818State *s)
+{
+    if (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_EXT_REF_EN) {
+        if (s->ext_vref > 0u) {
+            return s->ext_vref;
+        }
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: external VREF selected but not"
+                      " connected, falling back to internal\n",
+                      __func__, s->description);
+    }
+
+    return ADC128D818_INTERNAL_VREF_MV;
+}
+
+static uint8_t adc128d818_get_mode(const ADC128D818State *s)
+{
+    return (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_MODE_MASK) >>
+           ADV_CONFIG_MODE_SHIFT;
+}
+
+static bool adc128d818_is_temp_channel(const ADC128D818State *s, unsigned ch)
+{
+    if (ch != 7u) {
+        return false;
+    }
+
+    return adc128d818_get_mode(s) != 1u;
+}
+
+static bool adc128d818_is_reserved_channel(const ADC128D818State *s,
+                                           unsigned ch)
+{
+    switch (adc128d818_get_mode(s)) {
+    case 2u:
+        return ch >= 4u && ch <= 6u;
+    case 3u:
+        return ch == 6u;
+    default:
+        return false;
+    }
+}
+
+static int16_t adc128d818_channel_voltage(const ADC128D818State *s, unsigned ch)
+{
+    switch (adc128d818_get_mode(s)) {
+    case 2u:
+        switch (ch) {
+        case 0u:
+            return (int16_t)(s->ain[0] - s->ain[1]);
+        case 1u:
+            return (int16_t)(s->ain[3] - s->ain[2]);
+        case 2u:
+            return (int16_t)(s->ain[4] - s->ain[5]);
+        case 3u:
+            return (int16_t)(s->ain[7] - s->ain[6]);
+        default:
+            return 0;
+        }
+    case 3u:
+        switch (ch) {
+        case 4u:
+            return (int16_t)(s->ain[4] - s->ain[5]);
+        case 5u:
+            return (int16_t)(s->ain[7] - s->ain[6]);
+        default:
+            return s->ain[ch];
+        }
+    default:
+        return s->ain[ch];
+    }
+}
+
+static void adc128d818_update_irq(ADC128D818State *s)
+{
+    uint8_t cfg = s->regs[REG_CONFIG];
+    uint8_t active;
+    bool level;
+
+    active = s->regs[REG_INT_STATUS] & ~s->regs[REG_INT_MASK];
+
+    /* INT pin is active-low */
+    level = !((cfg & CONFIG_INT_ENABLE) && !(cfg & CONFIG_INT_CLEAR) &&
+              (active != 0u));
+
+    trace_adc128d818_irq(s->description, level);
+    qemu_set_irq(s->irq, level);
+}
+
+static bool adc128d818_monitoring_active(const ADC128D818State *s)
+{
+    if (s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN) {
+        return false;
+    }
+    if (!(s->regs[REG_CONFIG] & CONFIG_START)) {
+        return false;
+    }
+    if (s->regs[REG_CONFIG] & CONFIG_INT_CLEAR) {
+        return false;
+    }
+
+    return true;
+}
+
+static void adc128d818_check_limits(ADC128D818State *s)
+{
+    uint8_t disabled = s->regs[REG_CH_DISABLE];
+    uint8_t int_status = 0u;
+
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        if ((disabled & (1u << ch)) ||
+            adc128d818_is_reserved_channel(s, ch)) {
+            continue;
+        }
+
+        if (adc128d818_is_temp_channel(s, ch)) {
+            int raw = s->temperature / ADC128D818_TEMP_LSB_MC;
+            int thot;
+            int thyst;
+
+            raw = MAX(ADC128D818_TEMP_RAW_MIN,
+                      MIN(ADC128D818_TEMP_RAW_MAX, raw));
+            thot = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u] * 2;
+            thyst = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u + 1u] * 2;
+
+            if (raw > thot) {
+                s->temp_alarm = true;
+            } else if (raw <= thyst) {
+                s->temp_alarm = false;
+            }
+            if (s->temp_alarm) {
+                int_status |= (1u << ch);
+            }
+        } else {
+            uint8_t msb = (uint8_t)(s->channel[ch] >> 8u);
+            uint8_t high_lim = s->regs[REG_LIMIT_BASE + ch * 2u];
+            uint8_t low_lim = s->regs[REG_LIMIT_BASE + ch * 2u + 1u];
+
+            if (msb > high_lim || msb <= low_lim) {
+                int_status |= (1u << ch);
+            }
+        }
+    }
+
+    s->regs[REG_INT_STATUS] = int_status;
+    adc128d818_update_irq(s);
+}
+
+static void adc128d818_convert(ADC128D818State *s)
+{
+    uint8_t disabled;
+    uint16_t vref;
+
+    disabled = s->regs[REG_CH_DISABLE];
+    vref = adc128d818_get_vref(s);
+
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        if ((disabled & (1u << ch)) ||
+            adc128d818_is_reserved_channel(s, ch)) {
+            continue;
+        }
+
+        if (adc128d818_is_temp_channel(s, ch)) {
+            int32_t raw = s->temperature / ADC128D818_TEMP_LSB_MC;
+
+            raw =
+                MAX(ADC128D818_TEMP_RAW_MIN, MIN(ADC128D818_TEMP_RAW_MAX, raw));
+            s->channel[ch] = (uint16_t)(((unsigned)raw & 0x1FFu) << 7u);
+        } else {
+            int16_t vin = adc128d818_channel_voltage(s, ch);
+            int32_t dout;
+
+            dout = vin * (int32_t)ADC128D818_ADC_RESOLUTION / vref;
+            dout = MAX(0, MIN((int32_t)ADC128D818_ADC_MAX, dout));
+            s->channel[ch] = (uint16_t)(dout << 4u);
+        }
+
+        trace_adc128d818_convert(s->description, ch, s->channel[ch]);
+    }
+
+    s->regs[REG_BUSY_STATUS] &= ~BUSY_STATUS_NOT_READY;
+
+    adc128d818_check_limits(s);
+}
+
+static uint8_t adc128d818_read_channel(ADC128D818State *s, unsigned ch)
+{
+    uint8_t val;
+
+    if (s->rx_byte == 0u) {
+        val = (uint8_t)(s->channel[ch] >> 8u);
+        trace_adc128d818_read_channel(s->description, ch, s->channel[ch]);
+    } else {
+        val = (uint8_t)(s->channel[ch] & 0xFFu);
+    }
+    s->rx_byte ^= 1u;
+
+    return val;
+}
+
+static uint8_t adc128d818_read_reg(ADC128D818State *s, uint8_t reg)
+{
+    uint8_t val;
+
+    switch (reg) {
+    case REG_INT_STATUS:
+        val = s->regs[REG_INT_STATUS];
+        s->regs[REG_INT_STATUS] = 0x00u;
+        if (adc128d818_monitoring_active(s)) {
+            adc128d818_check_limits(s);
+        } else {
+            adc128d818_update_irq(s);
+        }
+        trace_adc128d818_read(s->description, reg, val);
+        return val;
+    case REG_CONFIG:
+    case REG_INT_MASK:
+    case REG_CONV_RATE:
+    case REG_CH_DISABLE:
+    case REG_ONE_SHOT:
+    case REG_DEEP_SHUTDOWN:
+    case REG_ADV_CONFIG:
+    case REG_BUSY_STATUS:
+    case REG_LIMIT_BASE ... REG_LIMIT_LAST:
+    case REG_MANUFACTURER_ID:
+    case REG_REVISION_ID:
+        trace_adc128d818_read(s->description, reg, s->regs[reg]);
+        return s->regs[reg];
+    case REG_CH_READING_BASE ... REG_CH_READING_LAST:
+        return adc128d818_read_channel(s, reg - REG_CH_READING_BASE);
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: read from undefined register 0x%02x\n",
+                      __func__, s->description, reg);
+        return 0x00u;
+    }
+}
+
+static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val);
+
+static void adc128d818_reset_regs(ADC128D818State *s)
+{
+    memset(s->regs, 0, sizeof(s->regs));
+    memset(s->channel, 0, sizeof(s->channel));
+    s->temp_alarm = false;
+
+    s->regs[REG_CONFIG] = 0x08u;
+    s->regs[REG_BUSY_STATUS] = 0x02u;
+    s->regs[REG_MANUFACTURER_ID] = ADC128D818_MANUFACTURER_ID_VAL;
+    s->regs[REG_REVISION_ID] = ADC128D818_REVISION_ID_VAL;
+
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        s->regs[REG_LIMIT_BASE + ch * 2u] = 0xFFu;
+    }
+
+    s->pointer = 0x00u;
+    s->len = 0u;
+    s->rx_byte = 0u;
+
+    adc128d818_update_irq(s);
+}
+
+static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val)
+{
+    trace_adc128d818_write(s->description, reg, val);
+
+    switch (reg) {
+    case REG_CONFIG:
+        if (val & CONFIG_INITIALIZATION) {
+            trace_adc128d818_reset(s->description, "reg");
+            adc128d818_reset_regs(s);
+            break;
+        }
+        s->regs[REG_CONFIG] = val & CONFIG_WR_MASK;
+        if ((val & CONFIG_START) && !(val & CONFIG_INT_CLEAR) &&
+            !(s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN)) {
+            adc128d818_convert(s);
+        }
+        adc128d818_update_irq(s);
+        break;
+    case REG_INT_MASK:
+        s->regs[REG_INT_MASK] = val;
+        adc128d818_update_irq(s);
+        break;
+    case REG_CONV_RATE:
+        if (s->regs[REG_CONFIG] & CONFIG_START) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: CONV_RATE written while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_CONV_RATE] = val & CONV_RATE_MASK;
+        break;
+    case REG_CH_DISABLE:
+        if (s->regs[REG_CONFIG] & CONFIG_START) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: CH_DISABLE written while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_CH_DISABLE] = val;
+        memset(s->channel, 0, sizeof(s->channel));
+        s->regs[REG_INT_STATUS] = 0x00u;
+        s->temp_alarm = false;
+        adc128d818_update_irq(s);
+        break;
+    case REG_ONE_SHOT:
+        if (!(s->regs[REG_CONFIG] & CONFIG_START)) {
+            adc128d818_convert(s);
+        }
+        break;
+    case REG_DEEP_SHUTDOWN:
+        if ((val & DEEP_SHUTDOWN_EN) && (s->regs[REG_CONFIG] & CONFIG_START)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: DEEP_SHUTDOWN set while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_DEEP_SHUTDOWN] = val & DEEP_SHUTDOWN_EN;
+        break;
+    case REG_ADV_CONFIG:
+        if (s->regs[REG_CONFIG] & CONFIG_START) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: ADV_CONFIG written while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_ADV_CONFIG] = val & ADV_CONFIG_WR_MASK;
+        memset(s->channel, 0, sizeof(s->channel));
+        s->regs[REG_INT_STATUS] = 0x00u;
+        s->temp_alarm = false;
+        adc128d818_update_irq(s);
+        break;
+    case REG_LIMIT_BASE ... REG_LIMIT_LAST:
+        s->regs[reg] = val;
+        break;
+    case REG_INT_STATUS:
+    case REG_BUSY_STATUS:
+    case REG_MANUFACTURER_ID:
+    case REG_REVISION_ID:
+    case REG_CH_READING_BASE ... REG_CH_READING_LAST:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: write to read-only register 0x%02x\n",
+                      __func__, s->description, reg);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: write to undefined register 0x%02x\n",
+                      __func__, s->description, reg);
+        break;
+    }
+}
+
+static uint8_t adc128d818_recv(I2CSlave *i2c)
+{
+    ADC128D818State *s = ADC128D818(i2c);
+
+    return adc128d818_read_reg(s, s->pointer);
+}
+
+static int adc128d818_send(I2CSlave *i2c, uint8_t data)
+{
+    ADC128D818State *s = ADC128D818(i2c);
+
+    if (s->len == 0u) {
+        s->pointer = data;
+        s->len++;
+    } else {
+        adc128d818_write_reg(s, s->pointer, data);
+    }
+
+    return 0;
+}
+
+static int adc128d818_event(I2CSlave *i2c, enum i2c_event event)
+{
+    ADC128D818State *s = ADC128D818(i2c);
+
+    s->len = 0u;
+    s->rx_byte = 0u;
+
+    return 0;
+}
+
+static void adc128d818_get_ain(Object *obj, Visitor *v, const char *name,
+                               void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+    int ch_num;
+    int rc;
+
+    rc = sscanf(name, "ain%d", &ch_num);
+    if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) {
+        error_setg(errp, "%s: %s: invalid channel '%s'", __func__,
+                   s->description, name);
+        return;
+    }
+
+    value = s->ain[ch_num];
+    visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_ain(Object *obj, Visitor *v, const char *name,
+                               void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+    int ch_num;
+    int rc;
+
+    if (!visit_type_int(v, name, &value, errp)) {
+        return;
+    }
+
+    rc = sscanf(name, "ain%d", &ch_num);
+    if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) {
+        error_setg(errp, "%s: %s: invalid channel '%s'", __func__,
+                   s->description, name);
+        return;
+    }
+
+    if (value < INT16_MIN || value > INT16_MAX) {
+        error_setg(errp, "%s: %s: value %" PRId64 " out of range for '%s'",
+                   __func__, s->description, value, name);
+        return;
+    }
+
+    s->ain[ch_num] = (int16_t)value;
+
+    if (adc128d818_monitoring_active(s)) {
+        adc128d818_convert(s);
+    }
+}
+
+static void adc128d818_get_temperature(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value = s->temperature;
+
+    visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_temperature(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+
+    if (!visit_type_int(v, name, &value, errp)) {
+        return;
+    }
+
+    if (value < INT32_MIN || value > INT32_MAX) {
+        error_setg(errp, "%s: %s: value %" PRId64 " out of range", __func__,
+                   s->description, value);
+        return;
+    }
+
+    s->temperature = (int32_t)value;
+
+    if (adc128d818_monitoring_active(s)) {
+        adc128d818_convert(s);
+    }
+}
+
+static const VMStateDescription adc128d818_vmstate = {
+    .name = "ADC128D818",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(len, ADC128D818State),
+        VMSTATE_UINT8(pointer, ADC128D818State),
+        VMSTATE_UINT8(rx_byte, ADC128D818State),
+        VMSTATE_UINT8_ARRAY(regs, ADC128D818State,
+                            ADC128D818_NUM_REGS),
+        VMSTATE_UINT16_ARRAY(channel, ADC128D818State,
+                             ADC128D818_NUM_CHANNELS),
+        VMSTATE_INT16_ARRAY(ain, ADC128D818State,
+                            ADC128D818_NUM_CHANNELS),
+        VMSTATE_INT32(temperature, ADC128D818State),
+        VMSTATE_UINT16(ext_vref, ADC128D818State),
+        VMSTATE_BOOL(temp_alarm, ADC128D818State),
+        VMSTATE_I2C_SLAVE(parent_obj, ADC128D818State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void adc128d818_reset_hold(Object *obj, ResetType type)
+{
+    ADC128D818State *s = ADC128D818(obj);
+
+    trace_adc128d818_reset(s->description, "hw");
+    adc128d818_reset_regs(s);
+}
+
+static void adc128d818_get_ext_vref(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value = (int64_t)s->ext_vref;
+
+    visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_ext_vref(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+
+    if (!visit_type_int(v, name, &value, errp)) {
+        return;
+    }
+
+    if (value < 0 || value > ADC128D818_MAX_VDD_MV) {
+        error_setg(errp,
+                   "%s: %s: ext-vref-mv %" PRId64 " out of range (0..%u mV)",
+                   __func__, s->description, value, ADC128D818_MAX_VDD_MV);
+        return;
+    }
+
+    s->ext_vref = (uint16_t)value;
+
+    if (adc128d818_monitoring_active(s)) {
+        adc128d818_convert(s);
+    }
+}
+
+static void adc128d818_initfn(Object *obj)
+{
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        char *name = g_strdup_printf("ain%u", ch);
+
+        object_property_add(obj, name, "int", adc128d818_get_ain,
+                            adc128d818_set_ain, NULL, NULL);
+        g_free(name);
+    }
+
+    object_property_add(obj, "temperature", "int", adc128d818_get_temperature,
+                        adc128d818_set_temperature, NULL, NULL);
+    object_property_add(obj, "ext-vref-mv", "int", adc128d818_get_ext_vref,
+                        adc128d818_set_ext_vref, NULL, NULL);
+}
+
+static void adc128d818_realize(DeviceState *dev, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(dev);
+
+    if (!s->description) {
+        s->description = g_strdup(object_get_typename(OBJECT(dev)));
+    }
+
+    qdev_init_gpio_out(dev, &s->irq, 1u);
+}
+
+static const Property adc128d818_properties[] = {
+    DEFINE_PROP_STRING("description", ADC128D818State, description),
+};
+
+static void adc128d818_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    I2CSlaveClass *ic = I2C_SLAVE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    ic->event = adc128d818_event;
+    ic->recv = adc128d818_recv;
+    ic->send = adc128d818_send;
+    dc->realize = adc128d818_realize;
+    rc->phases.hold = adc128d818_reset_hold;
+    dc->vmsd = &adc128d818_vmstate;
+    device_class_set_props(dc, adc128d818_properties);
+}
+
+static const TypeInfo adc128d818_types[] = {
+    {
+        .name          = TYPE_ADC128D818,
+        .parent        = TYPE_I2C_SLAVE,
+        .instance_init = adc128d818_initfn,
+        .instance_size = sizeof(ADC128D818State),
+        .class_init    = adc128d818_class_init,
+    },
+};
+
+DEFINE_TYPES(adc128d818_types)
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b4abbd..b459ac224076 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -1,3 +1,7 @@
+config ADC128D818
+    bool
+    depends on I2C
+
 config TMP105
     bool
     depends on I2C
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc335927..fe36c9ef9109 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,3 +1,4 @@
+system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
 system_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
 system_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
 system_ss.add(when: 'CONFIG_DPS310', if_true: files('dps310.c'))
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events
index a3fe54fa6dc1..5a3630f7bbd6 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -1,5 +1,13 @@
 # See docs/devel/tracing.rst for syntax documentation.
 
+# adc128d818.c
+adc128d818_read(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x"
+adc128d818_read_channel(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x"
+adc128d818_write(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x"
+adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x"
+adc128d818_irq(const char *id, bool level) "%s level %u"
+adc128d818_reset(const char *id, const char *source) "%s %s"
+
 # tmp105.c
 tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
 tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
-- 
2.55.0



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

* [PULL 28/83] tests/qtest: adc128d818: add test harness and register access
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (26 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 27/83] hw/sensor: adc128d818: add 12-bit 8-channel ADC device Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 29/83] tests/qtest: adc128d818: test voltage and temperature conversion Cédric Le Goater
                   ` (55 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

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

Introduce the QOS test node and QMP property helpers for the
ADC128D818, and cover basic register access: manufacturer and
revision IDs, power-on-reset defaults, software reset, and the
ain and temperature property readback.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260707091609.97759-3-emmanuel.blot@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/adc128d818-test.c | 169 ++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build       |   1 +
 2 files changed, 170 insertions(+)
 create mode 100644 tests/qtest/adc128d818-test.c

diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
new file mode 100644
index 000000000000..9a8811256f66
--- /dev/null
+++ b/tests/qtest/adc128d818-test.c
@@ -0,0 +1,169 @@
+/*
+ * QTest testcase for the ADC128D818 ADC
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define ADC128D818_TEST_ID      "adc128d818-test"
+#define ADC128D818_TEST_ADDR    0x1f
+
+/* Register addresses */
+#define REG_CONFIG              0x00
+#define REG_INT_STATUS          0x01
+#define REG_INT_MASK            0x03
+#define REG_CONV_RATE           0x07
+#define REG_CH_DISABLE          0x08
+#define REG_ONE_SHOT            0x09
+#define REG_DEEP_SHUTDOWN       0x0a
+#define REG_ADV_CONFIG          0x0b
+#define REG_BUSY_STATUS         0x0c
+
+/* Channel Reading Registers (16-bit, read-only) */
+#define REG_CH_READING_BASE     0x20
+
+/* Limit Registers (8-bit, read/write) */
+#define REG_LIMIT_BASE          0x2a
+
+/* ID Registers (read-only) */
+#define REG_MANUFACTURER_ID     0x3e
+#define REG_REVISION_ID         0x3f
+
+/* Configuration Register (0x00) bitfields */
+#define CONFIG_START            BIT(0)
+#define CONFIG_INT_ENABLE       BIT(1)
+#define CONFIG_INT_CLEAR        BIT(3)
+#define CONFIG_INITIALIZATION   BIT(7)
+
+/* Advanced Configuration Register (0x0b) bitfields */
+#define ADV_CONFIG_EXT_REF_EN   BIT(0)
+#define ADV_CONFIG_MODE_1       (1 << 1)
+#define ADV_CONFIG_MODE_2       (2 << 1)
+#define ADV_CONFIG_MODE_3       (3 << 1)
+
+/* Number of channels */
+#define NUM_CHANNELS            8
+
+/* Internal VREF in mV */
+#define INTERNAL_VREF_MV        2560
+
+/* QMP helpers for setting device properties */
+
+static void qmp_adc128d818_set(const char *property, int value)
+{
+    QDict *resp;
+
+    resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+               " { 'path': %s, 'property': %s, 'value': %d } }",
+               ADC128D818_TEST_ID, property, value);
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+}
+
+static int qmp_adc128d818_get(const char *property)
+{
+    QDict *resp;
+    int ret;
+
+    resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+               " { 'path': %s, 'property': %s } }",
+               ADC128D818_TEST_ID, property);
+    g_assert(qdict_haskey(resp, "return"));
+    ret = qdict_get_int(resp, "return");
+    qobject_unref(resp);
+    return ret;
+}
+
+/* Manufacturer and Revision ID registers */
+static void test_id_registers(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, REG_MANUFACTURER_ID), ==, 0x01);
+    g_assert_cmphex(i2c_get8(dev, REG_REVISION_ID), ==, 0x09);
+}
+
+/* Power-on-reset default values */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    unsigned ch;
+
+    g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
+    g_assert_cmphex(i2c_get8(dev, REG_INT_STATUS), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_ADV_CONFIG), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
+
+    for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+        g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u), ==, 0xFF);
+        g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u + 1u), ==, 0x00);
+    }
+}
+
+/* Software reset via INITIALIZATION bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, REG_INT_MASK, 0xAA);
+    i2c_set8(dev, REG_CH_DISABLE, 0x55);
+    i2c_set8(dev, REG_LIMIT_BASE, 0x42);
+
+    g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0xAA);
+    g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x55);
+    g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x42);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
+    g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
+}
+
+/* Verify ain property readback via QMP */
+static void test_ain_property(void *obj, void *data, QGuestAllocator *alloc)
+{
+    int value;
+
+    qmp_adc128d818_set("ain3", 1500);
+    value = qmp_adc128d818_get("ain3");
+    g_test_message("Set ain3 = 1500 mV, readback = %d mV", value);
+    g_assert_cmpint(value, ==, 1500);
+
+    qmp_adc128d818_set("temperature", 37500);
+    value = qmp_adc128d818_get("temperature");
+    g_test_message("Set temperature = 37500 mC, readback = %d mC", value);
+    g_assert_cmpint(value, ==, 37500);
+}
+
+static void adc128d818_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "id=" ADC128D818_TEST_ID
+                             ",address=0x1f"
+    };
+    add_qi2c_address(&opts, &(QI2CAddress) { ADC128D818_TEST_ADDR });
+
+    qos_node_create_driver("adc128d818", i2c_device_create);
+    qos_node_consumes("adc128d818", "i2c-bus", &opts);
+
+    qos_add_test("id-registers", "adc128d818", test_id_registers, NULL);
+    qos_add_test("defaults", "adc128d818", test_defaults, NULL);
+    qos_add_test("soft-reset", "adc128d818", test_soft_reset, NULL);
+    qos_add_test("ain-property", "adc128d818", test_ain_property, NULL);
+}
+libqos_init(adc128d818_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e216c..5e1fc597e6d1 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -304,6 +304,7 @@ qtests_hexagon = ['boot-serial-test']
 qos_test_ss = ss.source_set()
 qos_test_ss.add(
   'ac97-test.c',
+  'adc128d818-test.c',
   'adm1272-test.c',
   'adm1266-test.c',
   'ds1338-test.c',
-- 
2.55.0



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

* [PULL 29/83] tests/qtest: adc128d818: test voltage and temperature conversion
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (27 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 28/83] tests/qtest: adc128d818: add test harness and register access Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 30/83] tests/qtest: adc128d818: test limit interrupts Cédric Le Goater
                   ` (54 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

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

Cover single-ended voltage conversion across all channels, voltage
and temperature boundary and clamping cases, and scaling against an
external voltage reference.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260707091609.97759-4-emmanuel.blot@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/adc128d818-test.c | 189 ++++++++++++++++++++++++++++++++++
 1 file changed, 189 insertions(+)

diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
index 9a8811256f66..891189f0bd8c 100644
--- a/tests/qtest/adc128d818-test.c
+++ b/tests/qtest/adc128d818-test.c
@@ -150,6 +150,186 @@ static void test_ain_property(void *obj, void *data, QGuestAllocator *alloc)
     g_assert_cmpint(value, ==, 37500);
 }
 
+/* Voltage conversion */
+static void test_voltage_conversion(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    qmp_adc128d818_set("ain0", 1280);
+    g_test_message("Injected ain0 = 1280 mV");
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain1", 2560);
+    g_test_message("Injected ain1 = 2560 mV");
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Read ch1: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0xFFF0);
+
+    qmp_adc128d818_set("ain2", 0);
+    g_test_message("Injected ain2 = 0 mV");
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+    g_test_message("Read ch2: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Temperature conversion (mode 0, channel 7 = temperature) */
+static void
+test_temperature_conversion(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    qmp_adc128d818_set("temperature", 25000);
+    g_test_message("Injected temperature = 25000 mC (25.0 deg C)");
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("Read ch7: raw 0x%04x -> %d mC", reading,
+             (int16_t)(reading & 0xFF80u) * 500 / 128);
+    g_assert_cmphex(reading, ==, 0x1900);
+}
+
+/* Channels with distinct voltages */
+static void test_all_channels(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    static const uint16_t ain_mv[NUM_CHANNELS] = {
+        0, 320, 640, 960, 1280, 1920, 2240, 2560
+    };
+    static const uint16_t expect[NUM_CHANNELS] = {
+        0x0000, 0x2000, 0x4000, 0x6000, 0x8000, 0xC000, 0xE000, 0xFFF0
+    };
+    uint16_t reading;
+    unsigned ch;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+    for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+        char name[8];
+        snprintf(name, sizeof(name), "ain%u", ch);
+        qmp_adc128d818_set(name, ain_mv[ch]);
+    }
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+        reading = i2c_get16(dev, REG_CH_READING_BASE + ch);
+        g_test_message("ch%u: ain %u mV -> raw 0x%04x (expect 0x%04x)",
+                 ch, ain_mv[ch], reading, expect[ch]);
+        g_assert_cmphex(reading, ==, expect[ch]);
+    }
+}
+
+/* Voltage conversion edge cases */
+static void test_voltage_edges(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+    qmp_adc128d818_set("ain0", 3000);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Over-range 3000 mV: raw 0x%04x (expect 0xFFF0)", reading);
+    g_assert_cmphex(reading, ==, 0xFFF0);
+
+    qmp_adc128d818_set("ain1", 1);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("1 mV: raw 0x%04x (expect 0x0010)", reading);
+    g_assert_cmphex(reading, ==, 0x0010);
+
+    qmp_adc128d818_set("ain2", 640);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+    g_test_message("640 mV (quarter): raw 0x%04x (expect 0x4000)", reading);
+    g_assert_cmphex(reading, ==, 0x4000);
+
+    qmp_adc128d818_set("ain3", 1920);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
+    g_test_message("1920 mV (3/4): raw 0x%04x (expect 0xC000)", reading);
+    g_assert_cmphex(reading, ==, 0xC000);
+}
+
+/* Temperature conversion edge cases */
+static void test_temperature_edges(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("temperature", 0);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("0 C: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("temperature", -25000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("-25 C: raw 0x%04x (expect 0xE700)", reading);
+    g_assert_cmphex(reading, ==, 0xE700);
+
+    qmp_adc128d818_set("temperature", 127500);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("+127.5 C: raw 0x%04x (expect 0x7F80)", reading);
+    g_assert_cmphex(reading, ==, 0x7F80);
+
+    qmp_adc128d818_set("temperature", -128000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("-128 C: raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("temperature", 200000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("200 C (clamped): raw 0x%04x (expect 0x7F80)", reading);
+    g_assert_cmphex(reading, ==, 0x7F80);
+
+    qmp_adc128d818_set("temperature", -200000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("-200 C (clamped): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* External voltage reference */
+static void test_ext_vref(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+    qmp_adc128d818_set("ext-vref-mv", 4096);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_1);
+
+    qmp_adc128d818_set("ain0", 1000);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("1000 mV / 4096 mV VREF: raw 0x%04x (expect 0x3E80)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x3E80);
+
+    qmp_adc128d818_set("ain1", 2048);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("2048 mV / 4096 mV VREF: raw 0x%04x (expect 0x8000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
 static void adc128d818_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -165,5 +345,14 @@ static void adc128d818_register_nodes(void)
     qos_add_test("defaults", "adc128d818", test_defaults, NULL);
     qos_add_test("soft-reset", "adc128d818", test_soft_reset, NULL);
     qos_add_test("ain-property", "adc128d818", test_ain_property, NULL);
+    qos_add_test("voltage-conversion", "adc128d818", test_voltage_conversion,
+                 NULL);
+    qos_add_test("temperature-conversion", "adc128d818",
+                 test_temperature_conversion, NULL);
+    qos_add_test("all-channels", "adc128d818", test_all_channels, NULL);
+    qos_add_test("voltage-edges", "adc128d818", test_voltage_edges, NULL);
+    qos_add_test("temperature-edges", "adc128d818", test_temperature_edges,
+                 NULL);
+    qos_add_test("ext-vref", "adc128d818", test_ext_vref, NULL);
 }
 libqos_init(adc128d818_register_nodes);
-- 
2.55.0



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

* [PULL 30/83] tests/qtest: adc128d818: test limit interrupts
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (28 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 29/83] tests/qtest: adc128d818: test voltage and temperature conversion Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 31/83] tests/qtest: adc128d818: test operating modes and power control Cédric Le Goater
                   ` (53 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

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

Cover per-channel high- and low-limit interrupt status, the
INT_CLEAR bit gating the monitoring loop, and the temperature
high-limit alarm with hysteresis.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260707091609.97759-5-emmanuel.blot@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/adc128d818-test.c | 121 ++++++++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)

diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
index 891189f0bd8c..3e3fabb8d9f1 100644
--- a/tests/qtest/adc128d818-test.c
+++ b/tests/qtest/adc128d818-test.c
@@ -330,6 +330,123 @@ static void test_ext_vref(void *obj, void *data, QGuestAllocator *alloc)
     g_assert_cmphex(reading, ==, 0x8000);
 }
 
+/* Interrupt status set on limit violation; persists while fault remains */
+static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+    g_test_message("Set ch0 high limit = 0x10");
+
+    qmp_adc128d818_set("ain0", 2560);
+    g_test_message("Injected ain0 = 2560 mV (exceeds limit)");
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS = 0x%02x (expect bit 0 set)", status);
+    g_assert_cmphex(status & 0x01u, ==, 0x01);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS after re-read = 0x%02x (expect bit 0 still set)",
+             status);
+    g_assert_cmphex(status & 0x01u, ==, 0x01);
+
+    qmp_adc128d818_set("ain0", 80);
+    g_test_message("Injected ain0 = 80 mV (within limit)");
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS after fault cleared = 0x%02x "
+                   "(expect bit 0 clear)", status);
+    g_assert_cmphex(status & 0x01u, ==, 0x00);
+}
+
+/* INT_CLEAR stops the round-robin monitoring loop */
+static void test_int_clear(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+    qmp_adc128d818_set("ain0", 2560);
+
+    i2c_set8(dev, REG_CONFIG,
+             CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS with INT_CLEAR set = 0x%02x (expect 0x00)",
+                   status);
+    g_assert_cmphex(status, ==, 0x00);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS after INT_CLEAR cleared = 0x%02x (expect bit 0)",
+             status);
+    g_assert_cmphex(status & 0x01u, ==, 0x01);
+}
+
+/* Low-limit interrupt triggers correctly */
+static void test_low_limit(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    i2c_set8(dev, REG_LIMIT_BASE + 3u, 0x80);
+    g_test_message("Set ch1 low limit = 0x80");
+
+    i2c_set8(dev, REG_LIMIT_BASE + 5u, 0x80);
+    g_test_message("Set ch2 low limit = 0x80");
+
+    qmp_adc128d818_set("ain1", 640);
+    qmp_adc128d818_set("ain2", 1280);
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS = 0x%02x (expect bits 1 and 2 set)", status);
+    g_assert_cmphex(status & 0x02u, ==, 0x02);
+    g_assert_cmphex(status & 0x04u, ==, 0x04);
+
+    g_assert_cmphex(status & 0x01u, ==, 0x00);
+}
+
+/* Temperature high-limit interrupt with hysteresis */
+static void test_temp_hysteresis(void *obj, void *data,
+                                 QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u, 0x32);
+    i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u + 1u, 0x28);
+
+    qmp_adc128d818_set("temperature", 25000);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("25 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x00);
+
+    qmp_adc128d818_set("temperature", 55000);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("55 C: INT_STATUS = 0x%02x (temp bit expect set)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x80);
+
+    qmp_adc128d818_set("temperature", 45000);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("45 C (hysteresis): INT_STATUS = 0x%02x "
+                   "(temp bit expect set)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x80);
+
+    qmp_adc128d818_set("temperature", 35000);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("35 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x00);
+}
+
 static void adc128d818_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -354,5 +471,9 @@ static void adc128d818_register_nodes(void)
     qos_add_test("temperature-edges", "adc128d818", test_temperature_edges,
                  NULL);
     qos_add_test("ext-vref", "adc128d818", test_ext_vref, NULL);
+    qos_add_test("interrupt-status", "adc128d818", test_interrupt_status, NULL);
+    qos_add_test("int-clear", "adc128d818", test_int_clear, NULL);
+    qos_add_test("low-limit", "adc128d818", test_low_limit, NULL);
+    qos_add_test("temp-hysteresis", "adc128d818", test_temp_hysteresis, NULL);
 }
 libqos_init(adc128d818_register_nodes);
-- 
2.55.0



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

* [PULL 31/83] tests/qtest: adc128d818: test operating modes and power control
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (29 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 30/83] tests/qtest: adc128d818: test limit interrupts Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 32/83] hw/arm/aspeed: anacapa: use ASCII in comments Cédric Le Goater
                   ` (52 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

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

Cover advanced-configuration mode selection (single-ended,
pseudo-differential pairs, and mixed) and the reset of readings on
reconfiguration, plus channel disable, one-shot conversion, deep
shutdown, BUSY_STATUS lifecycle, and conversion-rate gating.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260707091609.97759-6-emmanuel.blot@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/adc128d818-test.c | 377 ++++++++++++++++++++++++++++++++++
 1 file changed, 377 insertions(+)

diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
index 3e3fabb8d9f1..91eda5ca74d0 100644
--- a/tests/qtest/adc128d818-test.c
+++ b/tests/qtest/adc128d818-test.c
@@ -447,6 +447,368 @@ static void test_temp_hysteresis(void *obj, void *data,
     g_assert_cmphex(status & 0x80u, ==, 0x00);
 }
 
+/* Channel disable prevents conversion */
+static void test_channel_disable(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CH_DISABLE, 0x01);
+    g_test_message("Disabled channel 0");
+
+    qmp_adc128d818_set("ain0", 1280);
+    g_test_message("Injected ain0 = 1280 mV (disabled)");
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0 (disabled): raw 0x%04x", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("ain1", 1280);
+    g_test_message("Injected ain1 = 1280 mV (enabled)");
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Read ch1 (enabled): raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* One-shot conversion in shutdown mode */
+static void test_one_shot(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    qmp_adc128d818_set("ain0", 1280);
+    g_test_message("Injected ain0 = 1280 mV (device stopped)");
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0 before one-shot: raw 0x%04x", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    g_assert_cmphex(i2c_get8(dev, REG_ONE_SHOT), ==, 0x00);
+
+    i2c_set8(dev, REG_ONE_SHOT, 0x00);
+    g_test_message("Triggered one-shot conversion with value 0x00");
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0 after one-shot: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* Mode 1 makes channel 7 a voltage input instead of temperature */
+static void test_mode_selection(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+    g_test_message("Set mode 1 (all voltage channels)");
+
+    qmp_adc128d818_set("ain7", 1280);
+    qmp_adc128d818_set("temperature", 50000);
+    g_test_message("Injected ain7 = 1280 mV, temperature = 50000 mC");
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("Read ch7 (mode 1): raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* Mode 2 - 4 pseudo-differential pairs */
+static void test_mode2_diff(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+    g_test_message("Set mode 2 (4 pseudo-differential pairs)");
+
+    qmp_adc128d818_set("ain0", 2000);
+    qmp_adc128d818_set("ain1", 720);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Pair 0 (IN0-IN1): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain3", 1920);
+    qmp_adc128d818_set("ain2", 640);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Pair 1 (IN3-IN2): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain4", 1500);
+    qmp_adc128d818_set("ain5", 220);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+    g_test_message("Pair 2 (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain7", 2560);
+    qmp_adc128d818_set("ain6", 1280);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
+    g_test_message("Pair 3 (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
+    g_test_message("Reserved ch4: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("ain0", 500);
+    qmp_adc128d818_set("ain1", 1000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Pair 0 negative dV: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Mode 3 - 4 single-ended + 2 pseudo-differential pairs */
+static void test_mode3_mixed(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_3);
+    g_test_message("Set mode 3 (4 single-ended + 2 differential)");
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Ch0 single-ended: raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain4", 1500);
+    qmp_adc128d818_set("ain5", 220);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
+    g_test_message("Ch4 diff (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain7", 2560);
+    qmp_adc128d818_set("ain6", 1280);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 5u);
+    g_test_message("Ch5 diff (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 6u);
+    g_test_message("Reserved ch6: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("temperature", 25000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("Ch7 temperature: raw 0x%04x (expect 0x1900)", reading);
+    g_assert_cmphex(reading, ==, 0x1900);
+}
+
+/* Mode change resets channel readings and interrupt status */
+static void test_mode_change_reset(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+    uint8_t status;
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Before mode change, ch0: raw 0x%04x", reading);
+    g_assert_cmphex(reading, !=, 0x0000);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+    qmp_adc128d818_set("ain0", 2560);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After mode change, ch0: raw 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("After mode change, INT_STATUS: 0x%02x (expect 0x00)",
+                   status);
+    g_assert_cmphex(status, ==, 0x00);
+
+    g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x10);
+    g_test_message("Limit register preserved after mode change");
+}
+
+/* QOM property changes trigger correct differential conversion */
+static void test_diff_qom_trigger(void *obj, void *data,
+                                  QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+
+    qmp_adc128d818_set("ain0", 0);
+    qmp_adc128d818_set("ain1", 0);
+    qmp_adc128d818_set("ain2", 0);
+    qmp_adc128d818_set("ain3", 0);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    qmp_adc128d818_set("ain0", 2000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After ain0=2000, ain1=0: pair0 = 0x%04x (expect 0xC800)",
+             reading);
+    g_assert_cmphex(reading, ==, 0xC800);
+
+    qmp_adc128d818_set("ain1", 720);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After ain1=720: pair0 = 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain3", 1920);
+    qmp_adc128d818_set("ain2", 640);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Pair 1 (IN3-IN2) via QOM: 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* One-shot conversion works in deep shutdown */
+static void test_deep_shutdown(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
+    g_test_message("DEEP_SHUTDOWN write while running rejected");
+    g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
+    qmp_adc128d818_set("ain0", 0);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Deep shutdown, no one-shot: raw 0x%04x (expect 0x8000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    i2c_set8(dev, REG_ONE_SHOT, 0x01);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Deep shutdown one-shot: raw 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x01);
+
+    i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x00);
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_ONE_SHOT, 0x01);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After exit shutdown: raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* BUSY_STATUS NOT_READY clears after first conversion */
+static void test_busy_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x02);
+    g_test_message("After reset: BUSY_STATUS = 0x%02x (NOT_READY set)",
+             i2c_get8(dev, REG_BUSY_STATUS));
+
+    qmp_adc128d818_set("ain0", 0);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x00);
+    g_test_message("After conversion: BUSY_STATUS = 0x%02x (NOT_READY cleared)",
+             i2c_get8(dev, REG_BUSY_STATUS));
+}
+
+/* Programming Channel Disable resets channel readings */
+static void test_chan_disable_clears(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_CH_DISABLE, 0x02);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After CH_DISABLE write: ch0 raw 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Programming Advanced Configuration always resets channel readings */
+static void test_adv_config_clears(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_ADV_CONFIG, 0x00);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After same-mode ADV_CONFIG: ch0 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    qmp_adc128d818_set("ext-vref-mv", 4096);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After ext-vref toggle: ch0 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Conversion Rate register may only be programmed while in shutdown */
+static void test_conv_rate(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    i2c_set8(dev, REG_CONV_RATE, 0x01);
+    g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    i2c_set8(dev, REG_CONV_RATE, 0x00);
+    g_test_message("CONV_RATE while running: 0x%02x (expect unchanged 0x01)",
+             i2c_get8(dev, REG_CONV_RATE));
+    g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
+}
+
 static void adc128d818_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -475,5 +837,20 @@ static void adc128d818_register_nodes(void)
     qos_add_test("int-clear", "adc128d818", test_int_clear, NULL);
     qos_add_test("low-limit", "adc128d818", test_low_limit, NULL);
     qos_add_test("temp-hysteresis", "adc128d818", test_temp_hysteresis, NULL);
+    qos_add_test("channel-disable", "adc128d818", test_channel_disable, NULL);
+    qos_add_test("one-shot", "adc128d818", test_one_shot, NULL);
+    qos_add_test("mode-selection", "adc128d818", test_mode_selection, NULL);
+    qos_add_test("mode2-diff", "adc128d818", test_mode2_diff, NULL);
+    qos_add_test("mode3-mixed", "adc128d818", test_mode3_mixed, NULL);
+    qos_add_test("mode-change-reset", "adc128d818", test_mode_change_reset,
+                 NULL);
+    qos_add_test("diff-qom-trigger", "adc128d818", test_diff_qom_trigger, NULL);
+    qos_add_test("deep-shutdown", "adc128d818", test_deep_shutdown, NULL);
+    qos_add_test("busy-status", "adc128d818", test_busy_status, NULL);
+    qos_add_test("chan-disable-clears", "adc128d818", test_chan_disable_clears,
+                 NULL);
+    qos_add_test("adv-config-clears", "adc128d818", test_adv_config_clears,
+                 NULL);
+    qos_add_test("conv-rate", "adc128d818", test_conv_rate, NULL);
 }
 libqos_init(adc128d818_register_nodes);
-- 
2.55.0



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

* [PULL 32/83] hw/arm/aspeed: anacapa: use ASCII in comments
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (30 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 31/83] tests/qtest: adc128d818: test operating modes and power control Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 33/83] hw/arm: anacapa: add ADC128D818 devices Cédric Le Goater
                   ` (51 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

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

The anacapa machine source contains a few comments using the U+2014 EM
DASH character. Replace them with plain ASCII hyphens so the file stays
ASCII-only.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260707091609.97759-8-emmanuel.blot@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_anacapa.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/hw/arm/aspeed_ast2600_anacapa.c b/hw/arm/aspeed_ast2600_anacapa.c
index a1c8111a9344..bd6cc0068602 100644
--- a/hw/arm/aspeed_ast2600_anacapa.c
+++ b/hw/arm/aspeed_ast2600_anacapa.c
@@ -242,7 +242,7 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
     /* &i2c1 */
     /* eeprom@50 */
     at24c_eeprom_init(i2c[1], 0x50, 256 * KiB);
-    /* i2c-mux@70 (PCA9546) — 4 channels, empty */
+    /* i2c-mux@70 (PCA9546) - 4 channels, empty */
     i2c_slave_create_simple(i2c[1], TYPE_PCA9546, 0x70);
 
     /* &i2c4 */
@@ -259,7 +259,7 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
     i2c_mux = i2c_slave_create_simple(i2c[8], TYPE_PCA9546, 0x72);
 
     /* i2c8mux ch0 */
-    /* adc128d818@1f — no model */
+    /* adc128d818@1f - no model */
     /* pca9555@22 */
     i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
                             TYPE_PCA9552, 0x22);
@@ -305,7 +305,7 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
     /* i2c-mux@71 (PCA9548) */
     i2c_mux = i2c_slave_create_simple(i2c[11], TYPE_PCA9548, 0x71);
 
-    /* i2c11mux ch0-ch4 — empty */
+    /* i2c11mux ch0-ch4 - empty */
 
     /* i2c11mux ch5 */
     /* pca9555@22 */
@@ -328,7 +328,7 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
                           hpm_brd_id_eeprom, hpm_brd_id_eeprom_len);
 
     /* i2c13mux ch7 */
-    /* nfc@28 — no model */
+    /* nfc@28 - no model */
 }
 
 static void aspeed_machine_anacapa_class_init(ObjectClass *oc,
-- 
2.55.0



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

* [PULL 33/83] hw/arm: anacapa: add ADC128D818 devices
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (31 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 32/83] hw/arm/aspeed: anacapa: use ASCII in comments Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 34/83] hw/gpio: pca9552: register types with DEFINE_TYPES() Cédric Le Goater
                   ` (50 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

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

Wire up the two ADC128D818 instances that appear in the Anacapa DTS:
one on i2c8 mux channel 0 and one on i2c13 mux channel 3.

Reviewed-by: Cédric Le Goater <clg@redhat.com>
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Link: https://lore.kernel.org/qemu-devel/20260707091609.97759-9-emmanuel.blot@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_anacapa.c | 21 +++++++++++++++++----
 hw/arm/Kconfig                  |  1 +
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/hw/arm/aspeed_ast2600_anacapa.c b/hw/arm/aspeed_ast2600_anacapa.c
index bd6cc0068602..65d6b0faa2de 100644
--- a/hw/arm/aspeed_ast2600_anacapa.c
+++ b/hw/arm/aspeed_ast2600_anacapa.c
@@ -1,13 +1,14 @@
 /*
  * Facebook Anacapa
  *
- * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
 
 #include "qemu/osdep.h"
 #include "qapi/error.h"
+#include "hw/sensor/adc128d818.h"
 #include "hw/arm/machines-qom.h"
 #include "hw/arm/aspeed.h"
 #include "hw/arm/aspeed_soc.h"
@@ -15,7 +16,6 @@
 #include "hw/gpio/pca9552.h"
 #include "hw/nvram/eeprom_at24c.h"
 
-/* Anacapa hardware value */
 #define ANACAPA_BMC_HW_STRAP1 0x00002002
 #define ANACAPA_BMC_HW_STRAP2 0x00000000
 #define ANACAPA_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB)
@@ -221,6 +221,17 @@ static const uint8_t hpm_brd_id_eeprom[] = {
 };
 static const size_t hpm_brd_id_eeprom_len = sizeof(hpm_brd_id_eeprom);
 
+static void anacapa_add_adc128d818(I2CBus *bus, uint8_t addr,
+                                   const char *description)
+{
+    DeviceState *dev = DEVICE(i2c_slave_new(TYPE_ADC128D818, addr));
+    g_autofree char *childname = g_strdup_printf("0x%02x", addr);
+
+    qdev_prop_set_string(dev, "description", description);
+    object_property_add_child(OBJECT(bus), childname, OBJECT(dev));
+    i2c_slave_realize_and_unref(I2C_SLAVE(dev), bus, &error_fatal);
+}
+
 static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
 {
     /* Reference: aspeed-bmc-facebook-anacapa.dts */
@@ -259,7 +270,8 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
     i2c_mux = i2c_slave_create_simple(i2c[8], TYPE_PCA9546, 0x72);
 
     /* i2c8mux ch0 */
-    /* adc128d818@1f - no model */
+    /* adc128d818@1f - R-PDB ADC (mode 1: 8 voltage channels) */
+    anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 0), 0x1f, "i2c8:0:1f");
     /* pca9555@22 */
     i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
                             TYPE_PCA9552, 0x22);
@@ -320,7 +332,8 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
     i2c_mux = i2c_slave_create_simple(i2c[13], TYPE_PCA9548, 0x70);
 
     /* i2c13mux ch3 */
-    /* adc128d818@1f - no model */
+    /* adc128d818@1f - MB ADC (mode 1: 8 voltage channels) */
+    anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 3), 0x1f, "i2c13:3:1f");
 
     /* i2c13mux ch4 */
     /* eeprom@51 */
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5869f0f9e895..c502e8284769 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -558,6 +558,7 @@ config ASPEED_SOC
     select LED
     select PMBUS
     select MAX31785
+    select ADC128D818
     select FSI_APB2OPB_ASPEED
     select AT24C
     select PCI_EXPRESS_ASPEED
-- 
2.55.0



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

* [PULL 34/83] hw/gpio: pca9552: register types with DEFINE_TYPES()
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (32 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 33/83] hw/arm: anacapa: add ADC128D818 devices Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 35/83] hw/gpio: pca9552: move PCA955xState definition out of the header Cédric Le Goater
                   ` (49 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Replace the separate TypeInfo definitions and pca955x_register_types()
registration function with a single type array registered through the
DEFINE_TYPES() macro, to prepare addition of new PCA955x-derived devices.

No functional change.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-1-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9552.c | 49 ++++++++++++++++++++---------------------------
 1 file changed, 21 insertions(+), 28 deletions(-)

diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index b13ac9fd9ce5..50e868a6e9a7 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -492,16 +492,6 @@ static void pca955x_class_init(ObjectClass *klass, const void *data)
     device_class_set_props(dc, pca955x_properties);
 }
 
-static const TypeInfo pca955x_info = {
-    .name          = TYPE_PCA955X,
-    .parent        = TYPE_I2C_SLAVE,
-    .instance_init = pca955x_initfn,
-    .instance_size = sizeof(PCA955xState),
-    .class_init    = pca955x_class_init,
-    .class_size    = sizeof(PCA955xClass),
-    .abstract      = true,
-};
-
 static void pca9552_class_init(ObjectClass *oc, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
@@ -526,23 +516,26 @@ static void pca9535_class_init(ObjectClass *oc, const void *data)
     pc->has_led_support = false;
 }
 
-static const TypeInfo pca9552_info = {
-    .name          = TYPE_PCA9552,
-    .parent        = TYPE_PCA955X,
-    .class_init    = pca9552_class_init,
-};
-
-static const TypeInfo pca9535_info = {
-    .name          = TYPE_PCA9535,
-    .parent        = TYPE_PCA955X,
-    .class_init    = pca9535_class_init,
+static const TypeInfo pca955x_types[] = {
+    {
+        .name          = TYPE_PCA955X,
+        .parent        = TYPE_I2C_SLAVE,
+        .instance_init = pca955x_initfn,
+        .instance_size = sizeof(PCA955xState),
+        .class_init    = pca955x_class_init,
+        .class_size    = sizeof(PCA955xClass),
+        .abstract      = true,
+    },
+    {
+        .name          = TYPE_PCA9552,
+        .parent        = TYPE_PCA955X,
+        .class_init    = pca9552_class_init,
+    },
+    {
+        .name          = TYPE_PCA9535,
+        .parent        = TYPE_PCA955X,
+        .class_init    = pca9535_class_init,
+    }
 };
 
-static void pca955x_register_types(void)
-{
-    type_register_static(&pca955x_info);
-    type_register_static(&pca9552_info);
-    type_register_static(&pca9535_info);
-}
-
-type_init(pca955x_register_types)
+DEFINE_TYPES(pca955x_types)
-- 
2.55.0



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

* [PULL 35/83] hw/gpio: pca9552: move PCA955xState definition out of the header
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (33 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 34/83] hw/gpio: pca9552: register types with DEFINE_TYPES() Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 36/83] hw/gpio: pca9552: rename I2CSlave member to parent_obj Cédric Le Goater
                   ` (48 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Nothing outside pca9552.c uses the PCA955xState structure, its instance
checker, or the PCA955X_NR_REGS/PCA955X_PIN_COUNT_MAX defines: the board
files and qtests only rely on the TYPE_* name macros (and the register
macros in pca9552_regs.h).

Move the state structure and the size defines into pca9552.c, leaving
pca9552.h with just the type-name macros. While at it, replace the
separate DECLARE_INSTANCE_CHECKER and DECLARE_CLASS_CHECKERS declarations
with a single OBJECT_DECLARE_TYPE().

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-2-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/gpio/pca9552.h | 30 ++++--------------------------
 hw/gpio/pca9552.c         | 29 ++++++++++++++++++++++++-----
 2 files changed, 28 insertions(+), 31 deletions(-)

diff --git a/include/hw/gpio/pca9552.h b/include/hw/gpio/pca9552.h
index 43b175235d2f..71479ea0006e 100644
--- a/include/hw/gpio/pca9552.h
+++ b/include/hw/gpio/pca9552.h
@@ -1,39 +1,17 @@
 /*
- * PCA9552 I2C LED blinker
+ * PCA955X I2C LED blinker and I/O expanders
  *
  * Copyright (c) 2017-2018, IBM Corporation.
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or
  * later. See the COPYING file in the top-level directory.
  */
-#ifndef PCA9552_H
-#define PCA9552_H
 
-#include "hw/i2c/i2c.h"
-#include "qom/object.h"
+#ifndef HW_GPIO_PCA9552_H
+#define HW_GPIO_PCA9552_H
 
-#define TYPE_PCA9552 "pca9552"
 #define TYPE_PCA955X "pca955x"
+#define TYPE_PCA9552 "pca9552"
 #define TYPE_PCA9535 "pca9535"
-typedef struct PCA955xState PCA955xState;
-DECLARE_INSTANCE_CHECKER(PCA955xState, PCA955X,
-                         TYPE_PCA955X)
-
-#define PCA955X_NR_REGS 10
-#define PCA955X_PIN_COUNT_MAX 16
-
-struct PCA955xState {
-    /*< private >*/
-    I2CSlave i2c;
-    /*< public >*/
-
-    uint8_t len;
-    uint8_t pointer;
-
-    uint8_t regs[PCA955X_NR_REGS];
-    qemu_irq gpio_out[PCA955X_PIN_COUNT_MAX];
-    uint8_t ext_state[PCA955X_PIN_COUNT_MAX];
-    char *description; /* For debugging purpose only */
-};
 
 #endif
diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index 50e868a6e9a7..36bc4f86edb1 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -1,7 +1,10 @@
 /*
- * PCA9552 I2C LED blinker
+ * PCA955X I2C LED blinker and I/O expanders
  *
  *     https://www.nxp.com/docs/en/application-note/AN264.pdf
+ *     https://www.nxp.com/docs/en/data-sheet/PCA9552.pdf
+ *     https://www.nxp.com/docs/en/data-sheet/PCA9555.pdf
+ *     https://www.nxp.com/docs/en/data-sheet/PCA9535_PCA9535C.pdf
  *
  * Copyright (c) 2017-2018, IBM Corporation.
  * Copyright (c) 2020 Philippe Mathieu-Daudé
@@ -12,9 +15,9 @@
 
 #include "qemu/osdep.h"
 #include "qemu/log.h"
-#include "qemu/module.h"
 #include "qemu/bitops.h"
 #include "hw/core/qdev-properties.h"
+#include "hw/i2c/i2c.h"
 #include "hw/gpio/pca9552.h"
 #include "hw/gpio/pca9552_regs.h"
 #include "hw/core/irq.h"
@@ -24,6 +27,25 @@
 #include "trace.h"
 #include "qom/object.h"
 
+#define PCA955X_NR_REGS 10
+#define PCA955X_PIN_COUNT_MAX 16
+
+OBJECT_DECLARE_TYPE(PCA955xState, PCA955xClass, PCA955X)
+
+struct PCA955xState {
+    /*< private >*/
+    I2CSlave i2c;
+    /*< public >*/
+
+    uint8_t len;
+    uint8_t pointer;
+
+    uint8_t regs[PCA955X_NR_REGS];
+    qemu_irq gpio_out[PCA955X_PIN_COUNT_MAX];
+    uint8_t ext_state[PCA955X_PIN_COUNT_MAX];
+    char *description; /* For debugging purpose only */
+};
+
 struct PCA955xClass {
     /*< private >*/
     I2CSlaveClass parent_class;
@@ -33,10 +55,7 @@ struct PCA955xClass {
     uint8_t max_reg;
     bool has_led_support;
 };
-typedef struct PCA955xClass PCA955xClass;
 
-DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X,
-                       TYPE_PCA955X)
 /*
  * Note:  The LED_ON and LED_OFF configuration values for the PCA955X
  *        chips are the reverse of the PCA953X family of chips.
-- 
2.55.0



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

* [PULL 36/83] hw/gpio: pca9552: rename I2CSlave member to parent_obj
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (34 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 35/83] hw/gpio: pca9552: move PCA955xState definition out of the header Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 37/83] hw/gpio: pca9552: default description to the instantiated type name Cédric Le Goater
                   ` (47 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Use the conventional parent_obj name for the embedded I2CSlave instance.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Signed-off-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-3-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9552.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index 36bc4f86edb1..da21e56aabdf 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -34,7 +34,7 @@ OBJECT_DECLARE_TYPE(PCA955xState, PCA955xClass, PCA955X)
 
 struct PCA955xState {
     /*< private >*/
-    I2CSlave i2c;
+    I2CSlave parent_obj;
     /*< public >*/
 
     uint8_t len;
@@ -401,7 +401,7 @@ static const VMStateDescription pca9552_vmstate = {
         VMSTATE_UINT8(pointer, PCA955xState),
         VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS),
         VMSTATE_UINT8_ARRAY(ext_state, PCA955xState, PCA955X_PIN_COUNT_MAX),
-        VMSTATE_I2C_SLAVE(i2c, PCA955xState),
+        VMSTATE_I2C_SLAVE(parent_obj, PCA955xState),
         VMSTATE_END_OF_LIST()
     }
 };
-- 
2.55.0



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

* [PULL 37/83] hw/gpio: pca9552: default description to the instantiated type name
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (35 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 36/83] hw/gpio: pca9552: rename I2CSlave member to parent_obj Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 38/83] hw/gpio: pca9552: declare pca9555 device as an alias of pca9535 device Cédric Le Goater
                   ` (46 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

When no description is supplied, fall back to the actual QOM type name
(pca9552 / pca9535 / pca9555) via object_get_typename() instead of the
opaque "pca-unspecified" placeholder, matching the PCA9554 model and
giving meaningful device labels in traces.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-4-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9552.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index da21e56aabdf..ffeb438d2ab4 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -488,7 +488,7 @@ static void pca955x_realize(DeviceState *dev, Error **errp)
     PCA955xState *s = PCA955X(dev);
 
     if (!s->description) {
-        s->description = g_strdup("pca-unspecified");
+        s->description = g_strdup(object_get_typename(OBJECT(dev)));
     }
 
     qdev_init_gpio_out(dev, s->gpio_out, k->pin_count);
-- 
2.55.0



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

* [PULL 38/83] hw/gpio: pca9552: declare pca9555 device as an alias of pca9535 device
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (36 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 37/83] hw/gpio: pca9552: default description to the instantiated type name Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 39/83] hw/gpio: pca9552: use the Resettable interface instead of legacy reset Cédric Le Goater
                   ` (45 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

PCA9555 HW is mostly identical to PCA9535.
PCA9555 HW features pull-up resistors that are not available on PCA9535.

Pull-up are not handled by current PCA955x implementation and PCA9535
already initializes input as Hi-Z.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-5-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/gpio/pca9552.h | 1 +
 hw/gpio/pca9552.c         | 9 +++++++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/include/hw/gpio/pca9552.h b/include/hw/gpio/pca9552.h
index 71479ea0006e..5299c13829b3 100644
--- a/include/hw/gpio/pca9552.h
+++ b/include/hw/gpio/pca9552.h
@@ -13,5 +13,6 @@
 #define TYPE_PCA955X "pca955x"
 #define TYPE_PCA9552 "pca9552"
 #define TYPE_PCA9535 "pca9535"
+#define TYPE_PCA9555 "pca9555"
 
 #endif
diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index ffeb438d2ab4..3b9b63eb1766 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -523,7 +523,7 @@ static void pca9552_class_init(ObjectClass *oc, const void *data)
     pc->has_led_support = true;
 }
 
-static void pca9535_class_init(ObjectClass *oc, const void *data)
+static void pca95x5_class_init(ObjectClass *oc, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
     PCA955xClass *pc = PCA955X_CLASS(oc);
@@ -553,7 +553,12 @@ static const TypeInfo pca955x_types[] = {
     {
         .name          = TYPE_PCA9535,
         .parent        = TYPE_PCA955X,
-        .class_init    = pca9535_class_init,
+        .class_init    = pca95x5_class_init,
+    },
+    {
+        .name          = TYPE_PCA9555,
+        .parent        = TYPE_PCA955X,
+        .class_init    = pca95x5_class_init,
     }
 };
 
-- 
2.55.0



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

* [PULL 39/83] hw/gpio: pca9552: use the Resettable interface instead of legacy reset
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (37 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 38/83] hw/gpio: pca9552: declare pca9555 device as an alias of pca9535 device Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 40/83] hw/gpio: pca9552: apply input polarity inversion on read Cédric Le Goater
                   ` (44 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Convert the PCA9552 and PCA9535/PCA9555 reset handlers from the legacy
device reset hook to the Resettable interface: move each reset body into
a ResettableHoldPhase handler and register it through the class's
ResettableClass::phases.hold instead of device_class_set_legacy_reset().

No functional change.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-6-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9552.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index 3b9b63eb1766..66f699bf6a0b 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -406,9 +406,9 @@ static const VMStateDescription pca9552_vmstate = {
     }
 };
 
-static void pca9552_reset(DeviceState *dev)
+static void pca9552_reset_hold(Object *obj, ResetType type)
 {
-    PCA955xState *s = PCA955X(dev);
+    PCA955xState *s = PCA955X(obj);
 
     s->regs[PCA9552_PSC0] = 0xFF;
     s->regs[PCA9552_PWM0] = 0x80;
@@ -426,9 +426,9 @@ static void pca9552_reset(DeviceState *dev)
     s->len = 0;
 }
 
-static void pca9535_reset(DeviceState *dev)
+static void pca9535_reset_hold(Object *obj, ResetType type)
 {
-    PCA955xState *s = PCA955X(dev);
+    PCA955xState *s = PCA955X(obj);
 
     s->regs[PCA9535_INPUT0] = 0xFF;   /* All inputs high (pull-ups) */
     s->regs[PCA9535_INPUT1] = 0xFF;   /* All inputs high (pull-ups) */
@@ -514,9 +514,10 @@ static void pca955x_class_init(ObjectClass *klass, const void *data)
 static void pca9552_class_init(ObjectClass *oc, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
+    ResettableClass *rc = RESETTABLE_CLASS(oc);
     PCA955xClass *pc = PCA955X_CLASS(oc);
 
-    device_class_set_legacy_reset(dc, pca9552_reset);
+    rc->phases.hold = pca9552_reset_hold;
     dc->vmsd = &pca9552_vmstate;
     pc->max_reg = PCA9552_LS3;
     pc->pin_count = 16;
@@ -526,9 +527,10 @@ static void pca9552_class_init(ObjectClass *oc, const void *data)
 static void pca95x5_class_init(ObjectClass *oc, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
+    ResettableClass *rc = RESETTABLE_CLASS(oc);
     PCA955xClass *pc = PCA955X_CLASS(oc);
 
-    device_class_set_legacy_reset(dc, pca9535_reset);
+    rc->phases.hold = pca9535_reset_hold;
     dc->vmsd = &pca9552_vmstate;
     pc->max_reg = PCA9535_CONFIG1;
     pc->pin_count = 16;
-- 
2.55.0



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

* [PULL 40/83] hw/gpio: pca9552: apply input polarity inversion on read
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (38 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 39/83] hw/gpio: pca9552: use the Resettable interface instead of legacy reset Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 41/83] hw/gpio: pca9552: conform GPIO command handling to the datasheet Cédric Le Goater
                   ` (43 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The PCA9535 polarity inversion register inverts the value read back from
the input port for every pin, regardless of its direction, and does not
affect the output drive or the physical pin level.

Store the raw pin level in the input register and apply the polarity
inversion when the input port is read, instead of XORing it into the
stored value of output-configured pins only. The interrupt output now
reflects the raw pin level, matching the datasheet.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-7-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9552.c | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index 66f699bf6a0b..24f646541582 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -167,9 +167,12 @@ static void pca955x_update_pin_input(PCA955xState *s)
             /* PCA9535: Simple GPIO behavior */
             uint8_t config_reg = PCA9535_CONFIG0 + (i / 8);
             uint8_t output_reg = PCA9535_OUTPUT0 + (i / 8);
-            uint8_t polarity_reg = PCA9535_POLARITY0 + (i / 8);
 
-            /* Check if pin is configured as input */
+            /*
+             * The input register holds the raw pin logic level; the
+             * polarity inversion register is only applied when the input
+             * port is read (see pca955x_read()).
+             */
             if (s->regs[config_reg] & bit_mask) {
                 /* Input mode - reflect external state */
                 if (s->ext_state[i] == PCA9552_PIN_LOW) {
@@ -179,12 +182,8 @@ static void pca955x_update_pin_input(PCA955xState *s)
                 }
             } else {
                 /* Output mode - reflect output register value */
-                uint8_t output_bit = s->regs[output_reg] & bit_mask;
-                uint8_t polarity_bit = s->regs[polarity_reg] & bit_mask;
-
-                /* Apply polarity inversion if set */
                 s->regs[input_reg] = (s->regs[input_reg] & ~bit_mask) |
-                                    ((output_bit ^ polarity_bit) & bit_mask);
+                                     (s->regs[output_reg] & bit_mask);
             }
         }
 
@@ -206,6 +205,18 @@ static uint8_t pca955x_read(PCA955xState *s, uint8_t reg)
         return 0xFF;
     }
 
+    /*
+     * On the GPIO variants, reading an input port returns the raw pin
+     * levels XORed with the polarity inversion register, as specified by
+     * the datasheet.
+     */
+    if (!k->has_led_support &&
+        (reg == PCA9535_INPUT0 || reg == PCA9535_INPUT1)) {
+        uint8_t polarity_reg = PCA9535_POLARITY0 + (reg - PCA9535_INPUT0);
+
+        return s->regs[reg] ^ s->regs[polarity_reg];
+    }
+
     return s->regs[reg];
 }
 
-- 
2.55.0



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

* [PULL 41/83] hw/gpio: pca9552: conform GPIO command handling to the datasheet
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (39 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 40/83] hw/gpio: pca9552: apply input polarity inversion on read Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 42/83] hw/gpio: pca9552: expose GPIO pins as pin%d QOM properties Cédric Le Goater
                   ` (42 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The PCA9535/PCA9555 GPIO expanders share the PCA955X command dispatch
path with the PCA9552 LED blinker, but their register access differs from
the LED variant:

  - Auto-increment happens on every access and toggles bit 0 so the
    pointer stays within the addressed register pair (input, output,
    polarity, config); there is no AI enable bit.

  - The command byte only decodes 3 bits, so addresses beyond the last
    register alias back into the 8-register window instead of faulting.

Branch the auto-increment and command-decode logic on has_led_support so
the GPIO variants follow their datasheet while the PCA9552 behaviour is
left untouched.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-8-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9552.c | 37 +++++++++++++++++++++++++++++++------
 1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index 24f646541582..54a5945d9252 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -259,14 +259,26 @@ static void pca955x_write(PCA955xState *s, uint8_t reg, uint8_t data)
 }
 
 /*
- * When Auto-Increment is on, the register address is incremented
- * after each byte is sent to or received by the device. The index
- * rollovers to 0 when the maximum register address is reached.
+ * Advance the command pointer after each byte sent to or received from the
+ * device.
+ *
+ * The LED variant auto-increments only when the AI bit (bit 4) is set in the
+ * command byte, rolling over to 0 once the maximum register address is
+ * reached.
+ *
+ * The GPIO variants auto-increment on every access, toggling bit 0 so the
+ * pointer stays within the addressed register pair
+ * (input/output/polarity/config), as specified by their datasheet.
  */
 static void pca955x_autoinc(PCA955xState *s)
 {
     PCA955xClass *k = PCA955X_GET_CLASS(s);
 
+    if (!k->has_led_support) {
+        s->pointer ^= 0x1;
+        return;
+    }
+
     if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
         uint8_t reg = s->pointer & 0xf;
 
@@ -275,12 +287,25 @@ static void pca955x_autoinc(PCA955xState *s)
     }
 }
 
+/*
+ * The LED variant addresses its registers with a 4-bit command field, while
+ * the GPIO variants only decode 3 bits (the command wraps into the 8-register
+ * window).
+ */
+static inline uint8_t pca955x_cmd_reg(PCA955xState *s)
+{
+    PCA955xClass *k = PCA955X_GET_CLASS(s);
+
+    return s->pointer & (k->has_led_support ? 0xf : 0x7);
+}
+
 static uint8_t pca955x_recv(I2CSlave *i2c)
 {
     PCA955xState *s = PCA955X(i2c);
+    PCA955xClass *k = PCA955X_GET_CLASS(s);
     uint8_t ret;
 
-    ret = pca955x_read(s, s->pointer & 0xf);
+    ret = pca955x_read(s, pca955x_cmd_reg(s));
 
     /*
      * From the Specs:
@@ -292,7 +317,7 @@ static uint8_t pca955x_recv(I2CSlave *i2c)
      * I don't know what should be done in this case, so throw an
      * error.
      */
-    if (s->pointer == PCA9552_AUTOINC) {
+    if (k->has_led_support && s->pointer == PCA9552_AUTOINC) {
         qemu_log_mask(LOG_GUEST_ERROR,
                       "%s: Autoincrement read starting with register 0\n",
                       __func__);
@@ -312,7 +337,7 @@ static int pca955x_send(I2CSlave *i2c, uint8_t data)
         s->pointer = data;
         s->len++;
     } else {
-        pca955x_write(s, s->pointer & 0xf, data);
+        pca955x_write(s, pca955x_cmd_reg(s), data);
 
         pca955x_autoinc(s);
     }
-- 
2.55.0



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

* [PULL 42/83] hw/gpio: pca9552: expose GPIO pins as pin%d QOM properties
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (40 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 41/83] hw/gpio: pca9552: conform GPIO command handling to the datasheet Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 43/83] tests/qtest: add PCA9555 register access tests Cédric Le Goater
                   ` (41 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The PCA9552 exposes its LED channels as led%d QOM string properties, but
the GPIO variants (PCA9535/PCA9555) inherited the same led%d interface,
which drives the LED selector registers and is meaningless for a plain
I/O expander.

Add pin%d string properties ("low"/"high") for the GPIO variants,
mirroring the standalone pca9555 model:

  - reading returns the raw pin logic level from the INPUT register;
  - writing drives the external input level, but only for pins the guest
    has configured as inputs (writes to output pins are ignored with a
    LOG_UNIMP message).

The LED variant keeps its led%d properties.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-9-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9552.c | 91 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 86 insertions(+), 5 deletions(-)

diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index 54a5945d9252..719149b7174b 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -68,6 +68,7 @@ struct PCA955xClass {
 #define PCA9552_PIN_HIZ  0x1
 
 static const char *led_state[] = {"on", "off", "pwm0", "pwm1"};
+static const char *pin_state[] = {"low", "high"};
 
 static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin)
 {
@@ -428,6 +429,79 @@ static void pca955x_set_led(Object *obj, Visitor *v, const char *name,
     pca955x_write(s, reg, val);
 }
 
+static void pca955x_set_ext_state(PCA955xState *s, int pin, int level);
+
+static void pca955x_get_pin(Object *obj, Visitor *v, const char *name,
+                            void *opaque, Error **errp)
+{
+    PCA955xClass *k = PCA955X_GET_CLASS(obj);
+    PCA955xState *s = PCA955X(obj);
+    int pin, rc;
+    uint8_t input_reg, state;
+
+    rc = sscanf(name, "pin%2d", &pin);
+    if (rc != 1) {
+        error_setg(errp, "%s: error reading %s", __func__, name);
+        return;
+    }
+    if (pin < 0 || pin >= k->pin_count) {
+        error_setg(errp, "%s invalid pin %s", __func__, name);
+        return;
+    }
+
+    /*
+     * Report the raw pin logic level; polarity inversion is a read-time
+     * transform applied to the INPUT register, not to the pin state itself.
+     */
+    input_reg = PCA9535_INPUT0 + (pin / 8);
+    state = (s->regs[input_reg] >> (pin % 8)) & 0x1;
+    visit_type_str(v, name, (char **)&pin_state[state], errp);
+}
+
+static void pca955x_set_pin(Object *obj, Visitor *v, const char *name,
+                            void *opaque, Error **errp)
+{
+    PCA955xClass *k = PCA955X_GET_CLASS(obj);
+    PCA955xState *s = PCA955X(obj);
+    int pin, rc;
+    uint8_t state, config_reg;
+    g_autofree char *state_str = NULL;
+
+    if (!visit_type_str(v, name, &state_str, errp)) {
+        return;
+    }
+    rc = sscanf(name, "pin%2d", &pin);
+    if (rc != 1) {
+        error_setg(errp, "%s: error reading %s", __func__, name);
+        return;
+    }
+    if (pin < 0 || pin >= k->pin_count) {
+        error_setg(errp, "%s invalid pin %s", __func__, name);
+        return;
+    }
+
+    for (state = 0; state < ARRAY_SIZE(pin_state); state++) {
+        if (!strcmp(state_str, pin_state[state])) {
+            break;
+        }
+    }
+    if (state >= ARRAY_SIZE(pin_state)) {
+        error_setg(errp, "%s invalid pin state %s", __func__, state_str);
+        return;
+    }
+
+    /* Only input-configured pins can be driven by an external device. */
+    config_reg = PCA9535_CONFIG0 + (pin / 8);
+    if (!((s->regs[config_reg] >> (pin % 8)) & 0x1)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: pin %d is configured as output, ignoring set\n",
+                      s->description, pin);
+        return;
+    }
+
+    pca955x_set_ext_state(s, pin, state != PCA9552_PIN_LOW);
+}
+
 static const VMStateDescription pca9552_vmstate = {
     .name = "PCA9552",
     .version_id = 0,
@@ -485,15 +559,22 @@ static void pca9535_reset_hold(Object *obj, ResetType type)
 static void pca955x_initfn(Object *obj)
 {
     PCA955xClass *k = PCA955X_GET_CLASS(obj);
-    int led;
 
     assert(k->pin_count <= PCA955X_PIN_COUNT_MAX);
-    for (led = 0; led < k->pin_count; led++) {
+    for (int ix = 0; ix < k->pin_count; ix++) {
         char *name;
 
-        name = g_strdup_printf("led%d", led);
-        object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led,
-                            NULL, NULL);
+        if (k->has_led_support) {
+            /* LED variant: expose the LED selector state as led%d. */
+            name = g_strdup_printf("led%d", ix);
+            object_property_add(obj, name, "bool",
+                                pca955x_get_led, pca955x_set_led, NULL, NULL);
+        } else {
+            /* GPIO variant: expose the pin logic level as pin%d. */
+            name = g_strdup_printf("pin%d", ix);
+            object_property_add(obj, name, "str",
+                                pca955x_get_pin, pca955x_set_pin, NULL, NULL);
+        }
         g_free(name);
     }
 }
-- 
2.55.0



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

* [PULL 43/83] tests/qtest: add PCA9555 register access tests
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (41 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 42/83] hw/gpio: pca9552: expose GPIO pins as pin%d QOM properties Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:28 ` [PULL 44/83] tests/qtest: pca9555: test output-to-input reflection and pull-ups Cédric Le Goater
                   ` (40 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Introduce a qtest for the PCA9555 16-bit I/O port expander.

This first set covers the power-on reset defaults and the read/write
behaviour of the OUTPUT, CONFIG and POLARITY register pairs.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-10-814575bc076b@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9555-test.c | 44 ++++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build    |  1 +
 2 files changed, 45 insertions(+)
 create mode 100644 tests/qtest/pca9555-test.c

diff --git a/tests/qtest/pca9555-test.c b/tests/qtest/pca9555-test.c
new file mode 100644
index 000000000000..5945c3441e92
--- /dev/null
+++ b/tests/qtest/pca9555-test.c
@@ -0,0 +1,44 @@
+/*
+ * QTest testcase for the PCA9555 16-bit I/O port expander
+ *
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/pca9552_regs.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+
+#define PCA9555_TEST_ADDR 0x20
+
+/* Verify power-on reset defaults match the PCA9555 datasheet. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY1), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF);
+}
+
+static void pca9555_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "address=0x20"
+    };
+    add_qi2c_address(&opts, &(QI2CAddress) { PCA9555_TEST_ADDR });
+
+    qos_node_create_driver("pca9555", i2c_device_create);
+    qos_node_consumes("pca9555", "i2c-bus", &opts);
+
+    qos_add_test("reset-defaults", "pca9555", test_reset_defaults, NULL);
+}
+
+libqos_init(pca9555_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 5e1fc597e6d1..e92130bd04dd 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -319,6 +319,7 @@ qos_test_ss.add(
   'tulip-test.c',
   'nvme-test.c',
   'pca9552-test.c',
+  'pca9555-test.c',
   'pci-test.c',
   'pcnet-test.c',
   'rs5c372-test.c',
-- 
2.55.0



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

* [PULL 44/83] tests/qtest: pca9555: test output-to-input reflection and pull-ups
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (42 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 43/83] tests/qtest: add PCA9555 register access tests Cédric Le Goater
@ 2026-08-11 16:28 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 45/83] tests/qtest: pca9555: test polarity inversion Cédric Le Goater
                   ` (39 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:28 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Add tests covering the pin I/O semantics of the expander: output-driven
pins reflected in the input register, the pull-up seen on input-configured
pins, and the independence of the two 8-bit ports.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-11-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9555-test.c | 75 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)

diff --git a/tests/qtest/pca9555-test.c b/tests/qtest/pca9555-test.c
index 5945c3441e92..6b085702c3c8 100644
--- a/tests/qtest/pca9555-test.c
+++ b/tests/qtest/pca9555-test.c
@@ -28,6 +28,77 @@ static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
     g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF);
 }
 
+/*
+ * When a pin is configured as output and driven low (output=0, config=0),
+ * the input register should reflect 0 for that pin.
+ * When driven high (output=1, config=0), input should reflect 1.
+ * When configured as input (config=1), PCA5555 pull-up makes it read 1.
+ */
+static void test_output_drives_input(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0xF0);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xFA);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFA);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+}
+
+/*
+ * When all pins are inputs (config=0xFF) and no external driver,
+ * PCA9555 pull-ups should make the input register read all ones.
+ * Switching a pin to output mode with output=0 should drive it low.
+ */
+static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+}
+
+/*
+ * Test that both ports are independent: changing port 0 registers
+ * should not affect port 1 and vice versa.
+ */
+static void test_port_independence(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_CONFIG1, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT1, 0xAA);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xAA);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xAA);
+}
+
 static void pca9555_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -39,6 +110,10 @@ static void pca9555_register_nodes(void)
     qos_node_consumes("pca9555", "i2c-bus", &opts);
 
     qos_add_test("reset-defaults", "pca9555", test_reset_defaults, NULL);
+    qos_add_test("output-drives-input", "pca9555", test_output_drives_input,
+                 NULL);
+    qos_add_test("input-pullup", "pca9555", test_input_pullup, NULL);
+    qos_add_test("port-independence", "pca9555", test_port_independence, NULL);
 }
 
 libqos_init(pca9555_register_nodes);
-- 
2.55.0



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

* [PULL 45/83] tests/qtest: pca9555: test polarity inversion
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (43 preceding siblings ...)
  2026-08-11 16:28 ` [PULL 44/83] tests/qtest: pca9555: test output-to-input reflection and pull-ups Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 46/83] tests/qtest: pca9555: test auto-increment and command wrapping Cédric Le Goater
                   ` (38 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Add tests for the polarity inversion register: the inversion is applied
when reading the INPUT register, both for input pins (pull-up) and for
output-driven pins, and it does not affect the OUTPUT register readback.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-12-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9555-test.c | 44 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/tests/qtest/pca9555-test.c b/tests/qtest/pca9555-test.c
index 6b085702c3c8..2afa061bd2e0 100644
--- a/tests/qtest/pca9555-test.c
+++ b/tests/qtest/pca9555-test.c
@@ -99,6 +99,46 @@ static void test_port_independence(void *obj, void *data,
     g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xAA);
 }
 
+/*
+ * Polarity inversion: reading INPUT with polarity bits set should
+ * return the XOR of the actual input state and the polarity register.
+ */
+static void test_polarity_inversion(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_POLARITY0, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+
+    i2c_set8(dev, PCA9535_POLARITY0, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xF0);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_POLARITY1, 0xAA);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0x55);
+}
+
+/* Polarity inversion combined with output-driven pins. */
+static void test_polarity_with_output(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xA5);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xA5);
+
+    i2c_set8(dev, PCA9535_POLARITY0, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x5A);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xA5);
+}
+
 static void pca9555_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -114,6 +154,10 @@ static void pca9555_register_nodes(void)
                  NULL);
     qos_add_test("input-pullup", "pca9555", test_input_pullup, NULL);
     qos_add_test("port-independence", "pca9555", test_port_independence, NULL);
+    qos_add_test("polarity-inversion", "pca9555", test_polarity_inversion,
+                 NULL);
+    qos_add_test("polarity-with-output", "pca9555", test_polarity_with_output,
+                 NULL);
 }
 
 libqos_init(pca9555_register_nodes);
-- 
2.55.0



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

* [PULL 46/83] tests/qtest: pca9555: test auto-increment and command wrapping
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (44 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 45/83] tests/qtest: pca9555: test polarity inversion Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 47/83] tests/qtest: pca9552: test behaviour specific to the LED variant Cédric Le Goater
                   ` (37 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Add tests for the I2C command protocol of the GPIO variant: the
auto-increment that toggles bit 0 within a register pair on reads and
writes, and the 3-bit command wrapping that aliases out-of-range register
addresses back into the register window.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-13-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9555-test.c | 88 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/tests/qtest/pca9555-test.c b/tests/qtest/pca9555-test.c
index 2afa061bd2e0..84d771bcbb81 100644
--- a/tests/qtest/pca9555-test.c
+++ b/tests/qtest/pca9555-test.c
@@ -139,6 +139,87 @@ static void test_polarity_with_output(void *obj, void *data,
     g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xA5);
 }
 
+/*
+ * The PCA9555 auto-increments by toggling bit 0 of the command pointer
+ * within a register pair. Reading two bytes from INPUT0 should yield
+ * INPUT0 then INPUT1.
+ */
+static void test_auto_increment_read(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_CONFIG1, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xAA);
+    i2c_set8(dev, PCA9535_OUTPUT1, 0x55);
+
+    i2c_read_block(dev, PCA9535_INPUT0, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0xAA);
+    g_assert_cmphex(buf[1], ==, 0x55);
+
+    i2c_read_block(dev, PCA9535_OUTPUT0, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0xAA);
+    g_assert_cmphex(buf[1], ==, 0x55);
+}
+
+/*
+ * Auto-increment write: writing two data bytes after a command byte
+ * should write to port 0 then port 1 of the addressed register pair.
+ */
+static void test_auto_increment_write(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    buf[0] = 0x12;
+    buf[1] = 0x34;
+    i2c_write_block(dev, PCA9535_OUTPUT0, buf, 2);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x12);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0x34);
+
+    buf[0] = 0x0F;
+    buf[1] = 0xF0;
+    i2c_write_block(dev, PCA9535_CONFIG0, buf, 2);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xF0);
+}
+
+/*
+ * Auto-increment toggles within the pair: starting from port 1 should
+ * wrap back to port 0 (toggle bit 0).
+ */
+static void test_auto_increment_toggle(void *obj, void *data,
+                                       QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xAA);
+    i2c_set8(dev, PCA9535_OUTPUT1, 0x55);
+
+    i2c_read_block(dev, PCA9535_OUTPUT1, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0x55);
+    g_assert_cmphex(buf[1], ==, 0xAA);
+}
+
+/*
+ * Verify the command byte wraps at 3 bits: register addresses
+ * beyond 7 should alias to the same register (bits [2:0] only).
+ */
+static void test_command_wrapping(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x42);
+
+    g_assert_cmphex(i2c_get8(dev, 0x0A), ==, 0x42);
+}
+
 static void pca9555_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -158,6 +239,13 @@ static void pca9555_register_nodes(void)
                  NULL);
     qos_add_test("polarity-with-output", "pca9555", test_polarity_with_output,
                  NULL);
+    qos_add_test("auto-increment-read", "pca9555", test_auto_increment_read,
+                 NULL);
+    qos_add_test("auto-increment-write", "pca9555", test_auto_increment_write,
+                 NULL);
+    qos_add_test("auto-increment-toggle", "pca9555", test_auto_increment_toggle,
+                 NULL);
+    qos_add_test("command-wrapping", "pca9555", test_command_wrapping, NULL);
 }
 
 libqos_init(pca9555_register_nodes);
-- 
2.55.0



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

* [PULL 47/83] tests/qtest: pca9552: test behaviour specific to the LED variant
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (45 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 46/83] tests/qtest: pca9555: test auto-increment and command wrapping Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 48/83] hw/gpio: pca9554: add PCA9536 support Cédric Le Goater
                   ` (36 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The PCA9552 shares its device model with the PCA9535/PCA9555 GPIO
expanders but decodes registers differently. Add tests for the behaviour
that is specific to the LED variant and diverges from the PCA9555:

  - the power-on reset defaults of the prescaler, PWM and LED-selector
    registers;
  - the prescaler/PWM registers (2-5), which are OUTPUT/POLARITY on the
    PCA9555, as plain read/write storage;
  - the auto-increment, which only advances when the AI command bit is set
    and wraps modulo the full 10-register map (rather than toggling bit 0
    within a register pair);
  - the 4-bit command decode, where an out-of-range register reads back
    0xFF instead of aliasing into the register window.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-14-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9552-test.c | 76 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/tests/qtest/pca9552-test.c b/tests/qtest/pca9552-test.c
index 747495769239..3718dfbd2277 100644
--- a/tests/qtest/pca9552-test.c
+++ b/tests/qtest/pca9552-test.c
@@ -77,6 +77,76 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
     g_assert_cmphex(value, ==, 0xEF);
 }
 
+/* Verify the power-on reset defaults. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+    /* Prescalers, PWM duty cycles and LED selectors (all LEDs off) */
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PSC0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PWM0), ==, 0x80);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PSC1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PWM1), ==, 0x80);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS0), ==, 0x55);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS1), ==, 0x55);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS2), ==, 0x55);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS3), ==, 0x55);
+
+    /* All LEDs off, so every pin floats high through its pull-up */
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_INPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_INPUT1), ==, 0xFF);
+}
+
+/*
+ * The PCA9552 only advances the command pointer when the AI bit is set, and
+ * it wraps modulo the full 10-register map.
+ */
+static void test_autoinc_requires_ai_bit(void *obj, void *data,
+                                         QGuestAllocator *alloc)
+{
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+    uint8_t reg;
+    uint8_t resp;
+
+    /*
+     * With the AI bit, reading from LS3 (register 9) rolls over to INPUT0
+     * (register 0), not to a sibling in a register pair. All LEDs are off
+     * after reset so the input ports read 0xFF.
+     */
+    reg = PCA9552_LS3 | PCA9552_AUTOINC;
+    qi2c_send(i2cdev, &reg, 1);
+    qi2c_recv(i2cdev, &resp, 1); /* LS3 */
+    g_assert_cmphex(resp, ==, 0x55);
+    qi2c_recv(i2cdev, &resp, 1); /* wraps to INPUT0 */
+    g_assert_cmphex(resp, ==, 0xFF);
+    qi2c_recv(i2cdev, &resp, 1); /* INPUT1 */
+    g_assert_cmphex(resp, ==, 0xFF);
+
+    /*
+     * Without the AI bit the pointer must not advance: repeated reads keep
+     * returning the same register.
+     */
+    i2c_set8(i2cdev, PCA9552_LS0, 0x54);
+    reg = PCA9552_LS0;
+    qi2c_send(i2cdev, &reg, 1);
+    qi2c_recv(i2cdev, &resp, 1);
+    g_assert_cmphex(resp, ==, 0x54);
+    qi2c_recv(i2cdev, &resp, 1);
+    g_assert_cmphex(resp, ==, 0x54);
+}
+
+/*
+ * The PCA9552 decodes a 4-bit command and has no register past LS3 (9), so
+ * addressing register 0x0A reads back 0xFF.
+ */
+static void test_command_out_of_range(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(i2cdev, 0x0A), ==, 0xFF);
+}
+
 static void pca9552_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -89,5 +159,11 @@ static void pca9552_register_nodes(void)
 
     qos_add_test("tx-rx", "pca9552", send_and_receive, NULL);
     qos_add_test("rx-autoinc", "pca9552", receive_autoinc, NULL);
+    qos_add_test("reset-defaults", "pca9552", test_reset_defaults, NULL);
+    qos_add_test("autoinc-requires-ai-bit", "pca9552",
+                 test_autoinc_requires_ai_bit, NULL);
+    qos_add_test("command-out-of-range", "pca9552", test_command_out_of_range,
+                 NULL);
 }
+
 libqos_init(pca9552_register_nodes);
-- 
2.55.0



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

* [PULL 48/83] hw/gpio: pca9554: add PCA9536 support
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (46 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 47/83] tests/qtest: pca9552: test behaviour specific to the LED variant Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 49/83] hw/gpio: pca9554: add hw-dir property honoring the configured pin direction Cédric Le Goater
                   ` (35 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The PCA9536 is a 4-bit I/O expander that's register-compatible with the
PCA9554 but only has 4 pins.  Rather than duplicating the whole driver,
make the existing PCA9554 model parameterizable and register PCA9536 as
a subtype.

Introduce a PCA9554Class with a pin_count property, and replace every
hard-coded PCA9554_PIN_COUNT reference in the driver with the class
value.  The reset function now computes the correct pin mask from
pin_count instead of assuming 0xFF.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-15-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/gpio/pca9554.h |  2 +
 hw/gpio/pca9554.c         | 79 ++++++++++++++++++++++++++-------------
 2 files changed, 54 insertions(+), 27 deletions(-)

diff --git a/include/hw/gpio/pca9554.h b/include/hw/gpio/pca9554.h
index 54bfc4c4c7a0..c09108e8b650 100644
--- a/include/hw/gpio/pca9554.h
+++ b/include/hw/gpio/pca9554.h
@@ -12,12 +12,14 @@
 #include "qom/object.h"
 
 #define TYPE_PCA9554 "pca9554"
+#define TYPE_PCA9536 "pca9536"
 typedef struct PCA9554State PCA9554State;
 DECLARE_INSTANCE_CHECKER(PCA9554State, PCA9554,
                          TYPE_PCA9554)
 
 #define PCA9554_NR_REGS 4
 #define PCA9554_PIN_COUNT 8
+#define PCA9536_PIN_COUNT 4
 
 struct PCA9554State {
     /*< private >*/
diff --git a/hw/gpio/pca9554.c b/hw/gpio/pca9554.c
index 8427e01e9b23..b44ec0d9991c 100644
--- a/hw/gpio/pca9554.c
+++ b/hw/gpio/pca9554.c
@@ -24,6 +24,8 @@ struct PCA9554Class {
     /*< private >*/
     I2CSlaveClass parent_class;
     /*< public >*/
+
+    uint8_t pin_count;
 };
 typedef struct PCA9554Class PCA9554Class;
 
@@ -37,12 +39,13 @@ static const char *pin_state[] = {"low", "high"};
 
 static void pca9554_update_pin_input(PCA9554State *s)
 {
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
     int i;
     uint8_t config = s->regs[PCA9554_CONFIG];
     uint8_t output = s->regs[PCA9554_OUTPUT];
     uint8_t internal_state = config | output;
 
-    for (i = 0; i < PCA9554_PIN_COUNT; i++) {
+    for (i = 0; i < pc->pin_count; i++) {
         uint8_t bit_mask = 1 << i;
         uint8_t internal_pin_state = (internal_state >> i) & 0x1;
         uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask;
@@ -67,7 +70,7 @@ static void pca9554_update_pin_input(PCA9554State *s)
             break;
         }
 
-        /* update irq state only if pin state changed */
+        /* drive the per-pin GPIO output only if the pin level changed */
         new_value = s->regs[PCA9554_INPUT] & bit_mask;
         if (new_value != old_value) {
             if (new_value) {
@@ -99,6 +102,12 @@ static uint8_t pca9554_read(PCA9554State *s, uint8_t reg)
 
 static void pca9554_write(PCA9554State *s, uint8_t reg, uint8_t data)
 {
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
+    uint8_t pin_mask = (1 << pc->pin_count) - 1;
+
+    /* Variants narrower than 8 bits ignore the unimplemented upper pins. */
+    data &= pin_mask;
+
     switch (reg) {
     case PCA9554_OUTPUT:
     case PCA9554_CONFIG:
@@ -157,7 +166,7 @@ static void pca9554_get_pin(Object *obj, Visitor *v, const char *name,
         error_setg(errp, "%s: error reading %s", __func__, name);
         return;
     }
-    if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
+    if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) {
         error_setg(errp, "%s invalid pin %s", __func__, name);
         return;
     }
@@ -184,7 +193,7 @@ static void pca9554_set_pin(Object *obj, Visitor *v, const char *name,
         error_setg(errp, "%s: error reading %s", __func__, name);
         return;
     }
-    if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
+    if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) {
         error_setg(errp, "%s invalid pin %s", __func__, name);
         return;
     }
@@ -232,13 +241,15 @@ static const VMStateDescription pca9554_vmstate = {
 static void pca9554_reset(DeviceState *dev)
 {
     PCA9554State *s = PCA9554(dev);
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
+    uint8_t pin_mask = (1 << pc->pin_count) - 1;
 
-    s->regs[PCA9554_INPUT] = 0xFF;
-    s->regs[PCA9554_OUTPUT] = 0xFF;
+    s->regs[PCA9554_INPUT] = pin_mask;
+    s->regs[PCA9554_OUTPUT] = pin_mask;
     s->regs[PCA9554_POLARITY] = 0x0; /* No pins are inverted */
-    s->regs[PCA9554_CONFIG] = 0xFF; /* All pins are inputs */
+    s->regs[PCA9554_CONFIG] = pin_mask; /* All pins are inputs */
 
-    memset(s->ext_state, PCA9554_PIN_HIZ, PCA9554_PIN_COUNT);
+    memset(s->ext_state, PCA9554_PIN_HIZ, pc->pin_count);
     pca9554_update_pin_input(s);
 
     s->pointer = 0x0;
@@ -247,9 +258,10 @@ static void pca9554_reset(DeviceState *dev)
 
 static void pca9554_initfn(Object *obj)
 {
+    PCA9554Class *pc = PCA9554_GET_CLASS(obj);
     int pin;
 
-    for (pin = 0; pin < PCA9554_PIN_COUNT; pin++) {
+    for (pin = 0; pin < pc->pin_count; pin++) {
         char *name;
 
         name = g_strdup_printf("pin%d", pin);
@@ -269,23 +281,24 @@ static void pca9554_set_ext_state(PCA9554State *s, int pin, int level)
 
 static void pca9554_gpio_in_handler(void *opaque, int pin, int level)
 {
-
     PCA9554State *s = PCA9554(opaque);
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
 
-    assert((pin >= 0) && (pin < PCA9554_PIN_COUNT));
+    assert((pin >= 0) && (pin < pc->pin_count));
     pca9554_set_ext_state(s, pin, level);
 }
 
 static void pca9554_realize(DeviceState *dev, Error **errp)
 {
     PCA9554State *s = PCA9554(dev);
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
 
     if (!s->description) {
-        s->description = g_strdup("pca9554");
+        s->description = g_strdup(object_get_typename(OBJECT(dev)));
     }
 
-    qdev_init_gpio_out(dev, s->gpio_out, PCA9554_PIN_COUNT);
-    qdev_init_gpio_in(dev, pca9554_gpio_in_handler, PCA9554_PIN_COUNT);
+    qdev_init_gpio_out(dev, s->gpio_out, pc->pin_count);
+    qdev_init_gpio_in(dev, pca9554_gpio_in_handler, pc->pin_count);
 }
 
 static const Property pca9554_properties[] = {
@@ -296,6 +309,7 @@ static void pca9554_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+    PCA9554Class *pc = PCA9554_CLASS(klass);
 
     k->event = pca9554_event;
     k->recv = pca9554_recv;
@@ -304,21 +318,32 @@ static void pca9554_class_init(ObjectClass *klass, const void *data)
     device_class_set_legacy_reset(dc, pca9554_reset);
     dc->vmsd = &pca9554_vmstate;
     device_class_set_props(dc, pca9554_properties);
-}
 
-static const TypeInfo pca9554_info = {
-    .name          = TYPE_PCA9554,
-    .parent        = TYPE_I2C_SLAVE,
-    .instance_init = pca9554_initfn,
-    .instance_size = sizeof(PCA9554State),
-    .class_init    = pca9554_class_init,
-    .class_size    = sizeof(PCA9554Class),
-    .abstract      = false,
-};
+    pc->pin_count = PCA9554_PIN_COUNT;
+}
 
-static void pca9554_register_types(void)
+static void pca9536_class_init(ObjectClass *klass, const void *data)
 {
-    type_register_static(&pca9554_info);
+    PCA9554Class *pc = PCA9554_CLASS(klass);
+
+    pc->pin_count = PCA9536_PIN_COUNT;
 }
 
-type_init(pca9554_register_types)
+static const TypeInfo pca9554_types[] = {
+    {
+        .name          = TYPE_PCA9554,
+        .parent        = TYPE_I2C_SLAVE,
+        .instance_init = pca9554_initfn,
+        .instance_size = sizeof(PCA9554State),
+        .class_init    = pca9554_class_init,
+        .class_size    = sizeof(PCA9554Class),
+        .abstract      = false,
+    },
+    {
+        .name          = TYPE_PCA9536,
+        .parent        = TYPE_PCA9554,
+        .class_init    = pca9536_class_init,
+    }
+};
+
+DEFINE_TYPES(pca9554_types);
-- 
2.55.0



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

* [PULL 49/83] hw/gpio: pca9554: add hw-dir property honoring the configured pin direction
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (47 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 48/83] hw/gpio: pca9554: add PCA9536 support Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 50/83] hw/gpio: pca9554: reflect push-pull outputs in the input register Cédric Le Goater
                   ` (34 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The pinN QOM accessors are meant to let external agents observe and
stimulate the expander's pins, but their default behaviour does not
match real hardware:

 - to "drive" a pin, set_pin writes the OUTPUT register and then clears
   the pin's Configuration bit to force it into output mode. On a real
   device the pin direction is owned solely by the host (programmed
   through the Configuration register over I2C); an external agent can
   neither flip a pin's direction nor impose a level on a pin the host
   drives as an output -- the latter is a voltage conflict, not a legal
   operation.
 - get_pin returns a CONFIG|OUTPUT composite, i.e. the guest's intent,
   rather than the level actually sampled on the pin.

The PCA9555 GPIO variant (hw/gpio/pca9552.c) already models this
correctly and unconditionally: only input-configured pins can be driven
from outside, and reads return the sampled INPUT register.

Add a "hw-dir" property to bring the pca9554 pin accessors in line with
the hardware (and with the PCA9555 model), without changing the
behaviour seen by existing users:

 - hw-dir=true: set_pin only drives pins the guest has configured as
   inputs; a set on an output pin is refused with a LOG_UNIMP warning.
   get_pin returns the sampled INPUT register.
 - hw-dir=false (default): keeps the legacy, non-conformant behaviour
   for backward compatibility.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-16-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/gpio/pca9554.h |  1 +
 hw/gpio/pca9554.c         | 65 +++++++++++++++++++++++++--------------
 2 files changed, 43 insertions(+), 23 deletions(-)

diff --git a/include/hw/gpio/pca9554.h b/include/hw/gpio/pca9554.h
index c09108e8b650..ac835371aaca 100644
--- a/include/hw/gpio/pca9554.h
+++ b/include/hw/gpio/pca9554.h
@@ -33,6 +33,7 @@ struct PCA9554State {
     qemu_irq gpio_out[PCA9554_PIN_COUNT];
     uint8_t ext_state[PCA9554_PIN_COUNT];
     char *description; /* For debugging purpose only */
+    bool hw_dir; /* Honor pin direction */
 };
 
 #endif
diff --git a/hw/gpio/pca9554.c b/hw/gpio/pca9554.c
index b44ec0d9991c..ed38fe102b18 100644
--- a/hw/gpio/pca9554.c
+++ b/hw/gpio/pca9554.c
@@ -154,6 +154,14 @@ static int pca9554_event(I2CSlave *i2c, enum i2c_event event)
     return 0;
 }
 
+static void pca9554_set_ext_state(PCA9554State *s, int pin, int level)
+{
+    if (s->ext_state[pin] != level) {
+        s->ext_state[pin] = level;
+        pca9554_update_pin_input(s);
+    }
+}
+
 static void pca9554_get_pin(Object *obj, Visitor *v, const char *name,
                             void *opaque, Error **errp)
 {
@@ -171,9 +179,13 @@ static void pca9554_get_pin(Object *obj, Visitor *v, const char *name,
         return;
     }
 
-    state = pca9554_read(s, PCA9554_CONFIG);
-    state |= pca9554_read(s, PCA9554_OUTPUT);
-    state = (state >> pin) & 0x1;
+    /*
+     * Report the physical pin level. The input register is kept in sync by
+     * pca9554_update_pin_input(): output pins mirror the OUTPUT register and
+     * input pins reflect the externally driven (or pulled-up) level, so it
+     * holds the wire level regardless of the configured direction.
+     */
+    state = (s->regs[PCA9554_INPUT] >> pin) & 0x1;
     visit_type_str(v, name, (char **)&pin_state[state], errp);
 }
 
@@ -208,20 +220,34 @@ static void pca9554_set_pin(Object *obj, Visitor *v, const char *name,
         return;
     }
 
-    /* First, modify the output register bit */
-    val = pca9554_read(s, PCA9554_OUTPUT);
-    mask = 0x1 << pin;
-    if (state == PCA9554_PIN_LOW) {
-        val &= ~(mask);
+    if (s->hw_dir) {
+        /* Warn and ignore if the guest has configured this pin as output */
+        if (!((s->regs[PCA9554_CONFIG] >> pin) & 0x1)) {
+            qemu_log_mask(LOG_UNIMP,
+                          "%s: pin %d is configured as output, "
+                          "ignoring external set\n",
+                          s->description, pin);
+            return;
+        }
+        /* Drive the external input level */
+        pca9554_set_ext_state(s, pin, state != PCA9554_PIN_LOW);
     } else {
-        val |= mask;
-    }
-    pca9554_write(s, PCA9554_OUTPUT, val);
+        /* Legacy behavior: force output mode and drive */
+        /* First, modify the output register bit */
+        val = pca9554_read(s, PCA9554_OUTPUT);
+        mask = 0x1 << pin;
+        if (state == PCA9554_PIN_LOW) {
+            val &= ~(mask);
+        } else {
+            val |= mask;
+        }
+        pca9554_write(s, PCA9554_OUTPUT, val);
 
-    /* Then, clear the config register bit for output mode */
-    val = pca9554_read(s, PCA9554_CONFIG);
-    val &= ~mask;
-    pca9554_write(s, PCA9554_CONFIG, val);
+        /* Then, clear the config register bit for output mode */
+        val = pca9554_read(s, PCA9554_CONFIG);
+        val &= ~mask;
+        pca9554_write(s, PCA9554_CONFIG, val);
+    }
 }
 
 static const VMStateDescription pca9554_vmstate = {
@@ -271,14 +297,6 @@ static void pca9554_initfn(Object *obj)
     }
 }
 
-static void pca9554_set_ext_state(PCA9554State *s, int pin, int level)
-{
-    if (s->ext_state[pin] != level) {
-        s->ext_state[pin] = level;
-        pca9554_update_pin_input(s);
-    }
-}
-
 static void pca9554_gpio_in_handler(void *opaque, int pin, int level)
 {
     PCA9554State *s = PCA9554(opaque);
@@ -303,6 +321,7 @@ static void pca9554_realize(DeviceState *dev, Error **errp)
 
 static const Property pca9554_properties[] = {
     DEFINE_PROP_STRING("description", PCA9554State, description),
+    DEFINE_PROP_BOOL("hw-dir", PCA9554State, hw_dir, false),
 };
 
 static void pca9554_class_init(ObjectClass *klass, const void *data)
-- 
2.55.0



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

* [PULL 50/83] hw/gpio: pca9554: reflect push-pull outputs in the input register
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (48 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 49/83] hw/gpio: pca9554: add hw-dir property honoring the configured pin direction Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 51/83] hw/gpio: pca9554: expose pin%d as a string property Cédric Le Goater
                   ` (33 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

pca9554_update_pin_input() derived the pin level from CONFIG | OUTPUT,
which treated an output driven high as Hi-Z and let ext_state pull it
low. The PCA9554/PCA9536 output stage is push-pull, so a pin configured
as an output drives the OUTPUT register level regardless of any external
agent. Reflect the output value directly for output pins and keep the
pull-up/ext_state behaviour for input pins, matching the PCA9555 GPIO
variant.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-17-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9554.c | 29 +++++++++--------------------
 1 file changed, 9 insertions(+), 20 deletions(-)

diff --git a/hw/gpio/pca9554.c b/hw/gpio/pca9554.c
index ed38fe102b18..d4746719f916 100644
--- a/hw/gpio/pca9554.c
+++ b/hw/gpio/pca9554.c
@@ -43,43 +43,32 @@ static void pca9554_update_pin_input(PCA9554State *s)
     int i;
     uint8_t config = s->regs[PCA9554_CONFIG];
     uint8_t output = s->regs[PCA9554_OUTPUT];
-    uint8_t internal_state = config | output;
 
     for (i = 0; i < pc->pin_count; i++) {
         uint8_t bit_mask = 1 << i;
-        uint8_t internal_pin_state = (internal_state >> i) & 0x1;
         uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask;
         uint8_t new_value;
 
-        switch (internal_pin_state) {
-        case PCA9554_PIN_LOW:
-            s->regs[PCA9554_INPUT] &= ~bit_mask;
-            break;
-        case PCA9554_PIN_HIZ:
+        if (config & bit_mask) {
             /*
-             * pullup sets it to a logical 1 unless
-             * external device drives it low.
+             * Input: the pin is Hi-Z with a pull-up, so it reads high
+             * unless an external device drives it low.
              */
             if (s->ext_state[i] == PCA9554_PIN_LOW) {
                 s->regs[PCA9554_INPUT] &= ~bit_mask;
             } else {
-                s->regs[PCA9554_INPUT] |=  bit_mask;
+                s->regs[PCA9554_INPUT] |= bit_mask;
             }
-            break;
-        default:
-            break;
+        } else {
+            /* Output: the push-pull stage drives the output register level. */
+            s->regs[PCA9554_INPUT] = (s->regs[PCA9554_INPUT] & ~bit_mask) |
+                                     (output & bit_mask);
         }
 
         /* drive the per-pin GPIO output only if the pin level changed */
         new_value = s->regs[PCA9554_INPUT] & bit_mask;
         if (new_value != old_value) {
-            if (new_value) {
-                /* changed from 0 to 1 */
-                qemu_set_irq(s->gpio_out[i], 1);
-            } else {
-                /* changed from 1 to 0 */
-                qemu_set_irq(s->gpio_out[i], 0);
-            }
+            qemu_set_irq(s->gpio_out[i], !!new_value);
         }
     }
 }
-- 
2.55.0



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

* [PULL 51/83] hw/gpio: pca9554: expose pin%d as a string property
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (49 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 50/83] hw/gpio: pca9554: reflect push-pull outputs in the input register Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 52/83] tests/qtest: add PCA9554 register access tests Cédric Le Goater
                   ` (32 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The pinN properties are accessed with visit_type_str() (values "low" and
"high"), but were registered as type "bool", so introspection advertised
a boolean while the accessors require a string. Register them as "str",
matching the PCA9555 GPIO variant.

Fixes: de0c7d543bca ("misc: Add a pca9554 GPIO device model")
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-18-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/gpio/pca9554.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/gpio/pca9554.c b/hw/gpio/pca9554.c
index d4746719f916..904698cdce85 100644
--- a/hw/gpio/pca9554.c
+++ b/hw/gpio/pca9554.c
@@ -280,7 +280,7 @@ static void pca9554_initfn(Object *obj)
         char *name;
 
         name = g_strdup_printf("pin%d", pin);
-        object_property_add(obj, name, "bool", pca9554_get_pin, pca9554_set_pin,
+        object_property_add(obj, name, "str", pca9554_get_pin, pca9554_set_pin,
                             NULL, NULL);
         g_free(name);
     }
-- 
2.55.0



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

* [PULL 52/83] tests/qtest: add PCA9554 register access tests
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (50 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 51/83] hw/gpio: pca9554: expose pin%d as a string property Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 53/83] tests/qtest: pca9554: test output-to-input reflection and pull-ups Cédric Le Goater
                   ` (31 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Add a qtest for the PCA9554 8-bit I/O port expander exercising the basic
register access: power-on reset defaults and read/write of the OUTPUT,
CONFIG and POLARITY registers.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-19-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9554-test.c | 41 ++++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build    |  1 +
 2 files changed, 42 insertions(+)
 create mode 100644 tests/qtest/pca9554-test.c

diff --git a/tests/qtest/pca9554-test.c b/tests/qtest/pca9554-test.c
new file mode 100644
index 000000000000..a5f213a59cc6
--- /dev/null
+++ b/tests/qtest/pca9554-test.c
@@ -0,0 +1,41 @@
+/*
+ * QTest testcase for the PCA9554/PCA9536 I/O port expanders
+ *
+ * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/pca9554_regs.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+
+#define PCA9554_TEST_ADDR 0x20
+
+/* Verify power-on reset defaults match the PCA9554 datasheet. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    /* All pins are inputs, pulled high, with no polarity inversion. */
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0xFF);
+}
+
+static void pca9554_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "address=0x20"
+    };
+    add_qi2c_address(&opts, &(QI2CAddress) { PCA9554_TEST_ADDR });
+
+    qos_node_create_driver("pca9554", i2c_device_create);
+    qos_node_consumes("pca9554", "i2c-bus", &opts);
+
+    qos_add_test("reset-defaults", "pca9554", test_reset_defaults, NULL);
+}
+
+libqos_init(pca9554_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e92130bd04dd..4d81857174ae 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -319,6 +319,7 @@ qos_test_ss.add(
   'tulip-test.c',
   'nvme-test.c',
   'pca9552-test.c',
+  'pca9554-test.c',
   'pca9555-test.c',
   'pci-test.c',
   'pcnet-test.c',
-- 
2.55.0



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

* [PULL 53/83] tests/qtest: pca9554: test output-to-input reflection and pull-ups
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (51 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 52/83] tests/qtest: add PCA9554 register access tests Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 54/83] tests/qtest: pca9554: test polarity inversion Cédric Le Goater
                   ` (30 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Check that a pin configured as output drives its OUTPUT register level
onto the pin (push-pull) as reflected by the INPUT register, and that a
pin configured as input floats high through its pull-up.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-20-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9554-test.c | 45 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/tests/qtest/pca9554-test.c b/tests/qtest/pca9554-test.c
index a5f213a59cc6..ce6e90f3f881 100644
--- a/tests/qtest/pca9554-test.c
+++ b/tests/qtest/pca9554-test.c
@@ -25,6 +25,48 @@ static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
     g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0xFF);
 }
 
+/*
+ * A pin configured as output (config=0) drives its OUTPUT register level onto
+ * the pin (push-pull), which the INPUT register reflects. A pin configured as
+ * input (config=1) floats high through its pull-up.
+ */
+static void test_output_drives_input(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    /* Low nibble output, high nibble input (pull-up). */
+    i2c_set8(dev, PCA9554_CONFIG, 0xF0);
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFA);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFA);
+
+    /* All outputs, driven low then high. */
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    i2c_set8(dev, PCA9554_OUTPUT, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+}
+
+/*
+ * With all pins configured as inputs the pull-ups make the INPUT register read
+ * all ones regardless of the OUTPUT register; switching a pin to output with
+ * output=0 drives it low.
+ */
+static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+}
+
 static void pca9554_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -36,6 +78,9 @@ static void pca9554_register_nodes(void)
     qos_node_consumes("pca9554", "i2c-bus", &opts);
 
     qos_add_test("reset-defaults", "pca9554", test_reset_defaults, NULL);
+    qos_add_test("output-drives-input", "pca9554", test_output_drives_input,
+                 NULL);
+    qos_add_test("input-pullup", "pca9554", test_input_pullup, NULL);
 }
 
 libqos_init(pca9554_register_nodes);
-- 
2.55.0



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

* [PULL 54/83] tests/qtest: pca9554: test polarity inversion
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (52 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 53/83] tests/qtest: pca9554: test output-to-input reflection and pull-ups Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 55/83] tests/qtest: pca9554: test absence of command auto-increment Cédric Le Goater
                   ` (29 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

Verify that the polarity register inverts the value read back from the
INPUT register, both on pulled-up inputs and on output-driven pins, while
leaving the OUTPUT register itself unchanged.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-21-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9554-test.c | 39 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/tests/qtest/pca9554-test.c b/tests/qtest/pca9554-test.c
index ce6e90f3f881..74237df611ea 100644
--- a/tests/qtest/pca9554-test.c
+++ b/tests/qtest/pca9554-test.c
@@ -67,6 +67,41 @@ static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc)
     g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
 }
 
+/*
+ * Polarity inversion: reading INPUT returns the XOR of the pin levels and the
+ * polarity register.
+ */
+static void test_polarity_inversion(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xF0);
+}
+
+/* Polarity inversion combined with output-driven pins. */
+static void test_polarity_with_output(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    i2c_set8(dev, PCA9554_OUTPUT, 0xA5);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xA5);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x5A);
+
+    /* Inversion only affects the INPUT read, not the OUTPUT register. */
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xA5);
+}
+
 static void pca9554_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -81,6 +116,10 @@ static void pca9554_register_nodes(void)
     qos_add_test("output-drives-input", "pca9554", test_output_drives_input,
                  NULL);
     qos_add_test("input-pullup", "pca9554", test_input_pullup, NULL);
+    qos_add_test("polarity-inversion", "pca9554", test_polarity_inversion,
+                 NULL);
+    qos_add_test("polarity-with-output", "pca9554", test_polarity_with_output,
+                 NULL);
 }
 
 libqos_init(pca9554_register_nodes);
-- 
2.55.0



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

* [PULL 55/83] tests/qtest: pca9554: test absence of command auto-increment
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (53 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 54/83] tests/qtest: pca9554: test polarity inversion Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 56/83] tests/qtest: pca9554: test the PCA9536 4-bit variant Cédric Le Goater
                   ` (28 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The PCA9554 selects one of its four registers with a single command byte
and does not auto-increment the register pointer, so a multi-byte I2C
transfer keeps addressing the register chosen by the command byte instead
of walking through the register map.

Add a test covering this: a two-byte read returns the addressed register
twice, and a two-byte write updates only that register, leaving its
neighbour untouched.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-22-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9554-test.c | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/tests/qtest/pca9554-test.c b/tests/qtest/pca9554-test.c
index 74237df611ea..0da1f3304b9d 100644
--- a/tests/qtest/pca9554-test.c
+++ b/tests/qtest/pca9554-test.c
@@ -102,6 +102,33 @@ static void test_polarity_with_output(void *obj, void *data,
     g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xA5);
 }
 
+/*
+ * The PCA9554 has no auto-increment: the command pointer never advances, so
+ * multi-byte reads and writes all target the addressed register.
+ */
+static void test_no_autoincrement(void *obj, void *data,
+                                  QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    /* Distinct values in adjacent registers. */
+    i2c_set8(dev, PCA9554_OUTPUT, 0xAA);
+    i2c_set8(dev, PCA9554_POLARITY, 0x33);
+
+    /* Two reads from OUTPUT return OUTPUT twice, not OUTPUT then POLARITY. */
+    i2c_read_block(dev, PCA9554_OUTPUT, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0xAA);
+    g_assert_cmphex(buf[1], ==, 0xAA);
+
+    /* The second written byte overwrites OUTPUT; POLARITY is untouched. */
+    buf[0] = 0x12;
+    buf[1] = 0x34;
+    i2c_write_block(dev, PCA9554_OUTPUT, buf, 2);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x34);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x33);
+}
+
 static void pca9554_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -120,6 +147,7 @@ static void pca9554_register_nodes(void)
                  NULL);
     qos_add_test("polarity-with-output", "pca9554", test_polarity_with_output,
                  NULL);
+    qos_add_test("no-autoincrement", "pca9554", test_no_autoincrement, NULL);
 }
 
 libqos_init(pca9554_register_nodes);
-- 
2.55.0



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

* [PULL 56/83] tests/qtest: pca9554: test the PCA9536 4-bit variant
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (54 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 55/83] tests/qtest: pca9554: test absence of command auto-increment Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 57/83] hw/arm: catalina: model PCA9555 IO expanders with their own type Cédric Le Goater
                   ` (27 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Glenn Miles, Cédric Le Goater

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

The PCA9536 shares the PCA9554 register map and code path but exposes
only four pins. Add a pca9536 node and check its reset defaults and
output-to-input reflection are masked to the low nibble.

Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-23-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/pca9554-test.c | 70 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/tests/qtest/pca9554-test.c b/tests/qtest/pca9554-test.c
index 0da1f3304b9d..5366e719845c 100644
--- a/tests/qtest/pca9554-test.c
+++ b/tests/qtest/pca9554-test.c
@@ -129,6 +129,66 @@ static void test_no_autoincrement(void *obj, void *data,
     g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x33);
 }
 
+/*
+ * The PCA9536 shares the PCA9554 register map but only has four pins, so its
+ * reset defaults and pin logic are masked to the low nibble.
+ */
+static void test_pca9536_reset_defaults(void *obj, void *data,
+                                        QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x0F);
+}
+
+/* Only the four low pins are driven; the upper nibble stays low. */
+static void test_pca9536_output_drives_input(void *obj, void *data,
+                                             QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0x0A);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0A);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+}
+
+/*
+ * The four upper bits address pins that do not exist on the PCA9536, so writes
+ * to the register map discard them: the writable registers read back with bits
+ * [7:4] cleared, and driving them onto the pins never surfaces in INPUT.
+ */
+static void test_pca9536_ignores_upper_bits(void *obj, void *data,
+                                            QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    /* Bits [7:4] are dropped on write; bits [3:0] survive. */
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFA);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0A);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0xF5);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x05);
+
+    i2c_set8(dev, PCA9554_CONFIG, 0xF3);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x03);
+
+    /*
+     * With all four pins as outputs, driving 0xFF only affects the low
+     * nibble.
+     */
+    i2c_set8(dev, PCA9554_POLARITY, 0x00);
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F);
+}
+
 static void pca9554_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -148,6 +208,16 @@ static void pca9554_register_nodes(void)
     qos_add_test("polarity-with-output", "pca9554", test_polarity_with_output,
                  NULL);
     qos_add_test("no-autoincrement", "pca9554", test_no_autoincrement, NULL);
+
+    qos_node_create_driver("pca9536", i2c_device_create);
+    qos_node_consumes("pca9536", "i2c-bus", &opts);
+
+    qos_add_test("reset-defaults", "pca9536", test_pca9536_reset_defaults,
+                 NULL);
+    qos_add_test("output-drives-input", "pca9536",
+                 test_pca9536_output_drives_input, NULL);
+    qos_add_test("ignores-upper-bits", "pca9536",
+                 test_pca9536_ignores_upper_bits, NULL);
 }
 
 libqos_init(pca9554_register_nodes);
-- 
2.55.0



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

* [PULL 57/83] hw/arm: catalina: model PCA9555 IO expanders with their own type
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (55 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 56/83] tests/qtest: pca9554: test the PCA9536 4-bit variant Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 58/83] hw/arm: catalina: add NIC and FIO temperature sensors Cédric Le Goater
                   ` (26 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 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 device tree describes several IO expanders as nxp,pca9555.
These were previously instantiated as PCA9552 devices as no PCA9555 model
existed. Now that a dedicated PCA9555 device is available, use it so the
emulated IO expanders match the hardware.

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/20260709-catalina-upgrade-v1-24-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_catalina.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index 65495a524ea5..f3b22e9aa9a0 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -533,7 +533,7 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
                             TYPE_PCA9554, 0x27);
     /* io_expander6 - pca9555@25 */
     i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 6),
-                            TYPE_PCA9552, 0x25);
+                            TYPE_PCA9555, 0x25);
     /* eeprom@51 */
     at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, 6), 0x51, 8 * KiB,
                           osfp_eeprom, osfp_eeprom_len);
@@ -547,11 +547,11 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
 
     /* &i2c2 */
     /* io_expander0 - pca9555@20 */
-    i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x20);
+    i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x20);
     /* io_expander0 - pca9555@21 */
-    i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x21);
+    i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x21);
     /* io_expander0 - pca9555@27 */
-    i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x27);
+    i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x27);
     /* eeprom@50 */
     at24c_eeprom_init(i2c[2], 0x50, 8 * KiB);
     /* eeprom@51 */
@@ -572,13 +572,13 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
 
     /* &i2c6 */
     /* io_expander3 - pca9555@21 */
-    i2c_slave_create_simple(i2c[6], TYPE_PCA9552, 0x21);
+    i2c_slave_create_simple(i2c[6], TYPE_PCA9555, 0x21);
     /* rtc@6f - nct3018y */
     i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x6f);
 
     /* &i2c9 */
     /* io_expander4 - pca9555@4f */
-    i2c_slave_create_simple(i2c[9], TYPE_PCA9552, 0x4f);
+    i2c_slave_create_simple(i2c[9], TYPE_PCA9555, 0x4f);
     /* temperature-sensor@4b - tpm75 */
     i2c_slave_create_simple(i2c[9], TYPE_TMP75, 0x4b);
     /* eeprom@50 */
@@ -615,17 +615,17 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
 
     /* &i2c14 */
     /* io_expander9 - pca9555@10 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x10);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x10);
     /* io_expander10 - pca9555@11 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x11);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x11);
     /* io_expander11 - pca9555@12 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x12);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x12);
     /* io_expander12 - pca9555@13 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x13);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x13);
     /* io_expander13 - pca9555@14 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x14);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x14);
     /* io_expander14 - pca9555@15 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x15);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x15);
 
     /* &i2c15 */
     /* temperature-sensor@1f - tmp421 */
-- 
2.55.0



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

* [PULL 58/83] hw/arm: catalina: add NIC and FIO temperature sensors
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (56 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 57/83] hw/arm: catalina: model PCA9555 IO expanders with their own type Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 59/83] hw/i2c/aspeed_i2c: Support the AST2700 master buffer mode Cédric Le Goater
                   ` (25 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Emmanuel Blot, Cédric Le Goater

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

Model the temperature sensors described by the Catalina device tree that
have existing QEMU device models but were not yet instantiated: the four
IOB NIC TMP421 sensors behind the i2c0 PCA9546 muxes at 0x71 and 0x75, and
the FIO remote TMP75 sensor at 0x4f on the i2c1 mux.

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/20260709-catalina-upgrade-v1-25-82a63fead90c@free.fr
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed_ast2600_catalina.c | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index f3b22e9aa9a0..f714c9d1b32b 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -472,7 +472,16 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
 
     /* &i2c0 */
     /* i2c-mux@71 (PCA9546) on i2c0 */
-    i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x71);
+    i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x71);
+
+    /* i2c0mux0ch0 */
+    /* IOB0 NIC0 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
+                            TYPE_TMP421, 0x1f);
+    /* i2c0mux0ch2 */
+    /* IOB0 NIC1 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 2),
+                            TYPE_TMP421, 0x1f);
 
     /* i2c-mux@72 (PCA9546) on i2c0 */
     i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x72);
@@ -489,7 +498,16 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
     i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x73);
 
     /* i2c-mux@75 (PCA9546) on i2c0 */
-    i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x75);
+    i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x75);
+
+    /* i2c0mux3ch0 */
+    /* IOB1 NIC0 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
+                            TYPE_TMP421, 0x1f);
+    /* i2c0mux3ch2 */
+    /* IOB1 NIC1 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 2),
+                            TYPE_TMP421, 0x1f);
 
     /* i2c-mux@76 (PCA9546) on i2c0 */
     i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x76);
@@ -544,6 +562,8 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
                           fio_eeprom, fio_eeprom_len);
     /* temperature-sensor@4b - tmp75 */
     i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 7), TYPE_TMP75, 0x4b);
+    /* temperature-sensor@4f - tmp75 (FIO remote) */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 7), TYPE_TMP75, 0x4f);
 
     /* &i2c2 */
     /* io_expander0 - pca9555@20 */
@@ -564,11 +584,7 @@ static void catalina_bmc_i2c_init(AspeedMachineState *bmc)
     /* eeprom@52 */
     at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, 6), 0x52, 8 * KiB,
                           hdd_eeprom, hdd_eeprom_len);
-    /* i2c5mux0ch7 */
-    /* ina230@40 - no model */
-    /* ina230@41 - no model */
-    /* ina230@44 - no model */
-    /* ina230@45 - no model */
+    /* i2c5mux0ch7 - empty */
 
     /* &i2c6 */
     /* io_expander3 - pca9555@21 */
-- 
2.55.0



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

* [PULL 59/83] hw/i2c/aspeed_i2c: Support the AST2700 master buffer mode
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (57 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 58/83] hw/arm: catalina: add NIC and FIO temperature sensors Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 60/83] tests/functional/aarch64/test_aspeed_ast2700a2: Update ASPEED SDK v11.03 Cédric Le Goater
                   ` (24 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST2700 I2C controller can move master DMA payloads through its
internal SRAM pool rather than DRAM. The Linux driver calls this "buffer
mode" and selects it by default. Buffer mode reuses the master DMA
command bits (TX/RX_DMA_EN) and the DMA length registers, so the only
difference from a DRAM transfer is where the data comes from and goes
to: an offset into the pool programmed in I2CM_DMA_TX/RX_ADDR. The
I2CC_VERSION_CTRL FUNC_CFG_DMA_EN bit selects between the two.

Implement I2CC_VERSION_CTRL and, when FUNC_CFG_DMA_EN is clear, move the
payload through the pool buffer instead of DRAM.

I2CC_VERSION_CTRL resets to all ones, so guests that never program it
keep targeting DRAM and behave as before. The register sits above the
register window of the earlier SoCs, which are therefore unaffected.

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/20260804081955.1563537-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/i2c/aspeed_i2c.h |  2 +
 hw/i2c/aspeed_i2c.c         | 79 +++++++++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+)

diff --git a/include/hw/i2c/aspeed_i2c.h b/include/hw/i2c/aspeed_i2c.h
index 156998e7c14e..05937a7a0b49 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_VERSION_CTRL, 0x94)
+    FIELD(I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN, 2, 1)
 
 struct AspeedI2CState;
 
diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
index 27afcaeceedc..68bdcd0e25a6 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_VERSION_CTRL:
         value = bus->regs[offset / sizeof(*bus->regs)];
         break;
     case A_I2CC_DMA_ADDR:
@@ -295,6 +296,65 @@ static int aspeed_i2c_dma_read(AspeedI2CBus *bus, uint8_t *data)
     return 0;
 }
 
+/*
+ * In AST2700 buffer mode the master DMA command bits (TX/RX_DMA_EN) and the
+ * DMA length registers are reused, but data is moved through the controller
+ * internal SRAM pool at the offset programmed in I2CM_DMA_TX/RX_ADDR instead
+ * of DRAM. FUNC_CFG_DMA_EN selects between the two (set = DRAM).
+ */
+static bool aspeed_i2c_bus_dma_to_pool(AspeedI2CBus *bus)
+{
+    return aspeed_i2c_is_new_mode(bus->controller) &&
+           !ARRAY_FIELD_EX32(bus->regs, I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN);
+}
+
+static int aspeed_i2c_bus_send_dma_pool(AspeedI2CBus *bus)
+{
+    AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+    uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
+    uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
+    uint32_t offset = bus->regs[R_I2CM_DMA_TX_ADDR];
+    uint8_t *pool_base = aic->bus_pool_base(bus);
+    int ret = -1;
+    int i;
+
+    ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0);
+    for (i = 0; bus->regs[reg_dma_len] &&
+                offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) {
+        trace_aspeed_i2c_bus_send("BUFF", i + 1, bus->regs[reg_dma_len],
+                                  pool_base[offset + i]);
+        ret = i2c_send(bus->bus, pool_base[offset + i]);
+        bus->regs[reg_dma_len]--;
+        ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, i + 1);
+        if (ret) {
+            break;
+        }
+    }
+    SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_DMA_EN, 0);
+    return ret;
+}
+
+static void aspeed_i2c_bus_recv_dma_pool(AspeedI2CBus *bus)
+{
+    AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+    uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
+    uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
+    uint32_t offset = bus->regs[R_I2CM_DMA_RX_ADDR];
+    uint8_t *pool_base = aic->bus_pool_base(bus);
+    int i;
+
+    ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0);
+    for (i = 0; bus->regs[reg_dma_len] &&
+                offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) {
+        pool_base[offset + i] = i2c_recv(bus->bus);
+        trace_aspeed_i2c_bus_recv("BUFF", i + 1, bus->regs[reg_dma_len],
+                                  pool_base[offset + i]);
+        bus->regs[reg_dma_len]--;
+        ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, i + 1);
+    }
+    SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_DMA_EN, 0);
+}
+
 static int aspeed_i2c_bus_send(AspeedI2CBus *bus)
 {
     AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
@@ -320,6 +380,10 @@ static int aspeed_i2c_bus_send(AspeedI2CBus *bus)
         }
         SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_BUFF_EN, 0);
     } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN)) {
+        /* In buffer mode the DMA moves data through the pool, not DRAM */
+        if (aspeed_i2c_bus_dma_to_pool(bus)) {
+            return aspeed_i2c_bus_send_dma_pool(bus);
+        }
         /* In new mode, clear how many bytes we TXed */
         if (aspeed_i2c_is_new_mode(bus->controller)) {
             ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0);
@@ -385,6 +449,11 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
         SHARED_ARRAY_FIELD_DP32(bus->regs, reg_pool_ctrl, RX_COUNT, i & 0xff);
         SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_BUFF_EN, 0);
     } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN)) {
+        /* In buffer mode the DMA moves data through the pool, not DRAM */
+        if (aspeed_i2c_bus_dma_to_pool(bus)) {
+            aspeed_i2c_bus_recv_dma_pool(bus);
+            return;
+        }
         /* In new mode, clear how many bytes we RXed */
         if (aspeed_i2c_is_new_mode(bus->controller)) {
             ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0);
@@ -854,6 +923,9 @@ static void aspeed_i2c_bus_new_write(AspeedI2CBus *bus, hwaddr offset,
                                                       I2CS_DMA_RX_ADDR_HI,
                                                       ADDR_HI);
         break;
+    case A_I2CC_VERSION_CTRL:
+        bus->regs[R_I2CC_VERSION_CTRL] = value;
+        break;
     default:
         qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
                       __func__, offset);
@@ -1497,6 +1569,13 @@ static void aspeed_i2c_bus_reset_hold(Object *obj, ResetType type)
     memset(s->regs, 0, sizeof(s->regs));
     s->pending_intr_sts = 0;
     i2c_end_transfer(s->bus);
+    /*
+     * I2CC_VERSION_CTRL resets to all-ones. FUNC_CFG_DMA_EN is therefore set,
+     * so master DMA targets DRAM unless the guest clears it to select buffer
+     * mode. Guests unaware of buffer mode never touch this register and keep
+     * doing DRAM DMA.
+     */
+    s->regs[R_I2CC_VERSION_CTRL] = 0xffffffff;
 }
 
 static void aspeed_i2c_bus_realize(DeviceState *dev, Error **errp)
-- 
2.55.0



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

* [PULL 60/83] tests/functional/aarch64/test_aspeed_ast2700a2: Update ASPEED SDK v11.03
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (58 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 59/83] hw/i2c/aspeed_i2c: Support the AST2700 master buffer mode Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 61/83] tests/functional/aarch64/test_aspeed_ast2700a1: " Cédric Le Goater
                   ` (23 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

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/20260804081955.1563537-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 .../aarch64/test_aspeed_ast2700a2.py          | 27 ++++++++++---------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/tests/functional/aarch64/test_aspeed_ast2700a2.py b/tests/functional/aarch64/test_aspeed_ast2700a2.py
index 5f9073c89754..01c7c4fdbe35 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700a2.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700a2.py
@@ -85,13 +85,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True):
         exec_command_and_wait_for_pattern(self, 'root', 'Password:')
         exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#')
 
-    ASSET_SDK_V1101_AST2700A2 = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz',
-            'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487')
+    ASSET_SDK_V1103_AST2700A2 = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-default-image.tar.gz',
+            'b91450d53da234591060cfb926fa30f7534ce20eaab766cb0f80ec332f8f0adb')
 
-    ASSET_SDK_V1101_AST2700A2_DCSCM = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-dcscm-image.tar.gz',
-            'b92ece9ca733dfd7a20193a12582f743b77f1898116b6d6f1abe57ac8db01c56')
+    ASSET_SDK_V1103_AST2700A2_DCSCM = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-dcscm-image.tar.gz',
+            '7afd8323fc95097c14872b90d68c5cae5d078530c704591b192b74bd892c1dbf')
 
     def do_ast2700_i2c_test(self, bus_id):
         bus_str = str(bus_id)
@@ -135,7 +135,8 @@ def start_ast2700_test(self, name, bus_id):
             },
             {
                 'addr': '0x430000000',
-                'file': self.scratch_file(name, 'bl31.bin')
+                'file': self.scratch_file(name, 'trusted-firmware-a',
+                                          'bl31.bin')
             },
             {
                 'addr': '0x430080000',
@@ -162,11 +163,11 @@ def start_ast2700_test_vbootrom(self, name, bus_id):
         self.do_test_aarch64_aspeed_sdk_start(
                 self.scratch_file(name, 'image-bmc'), bus_id)
 
-    def test_aarch64_ast2700a2_evb_sdk_v11_01(self):
+    def test_aarch64_ast2700a2_evb_sdk_v11_03(self):
         self.set_machine('ast2700a2-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A2)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A2)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
         self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3')
@@ -176,22 +177,22 @@ def test_aarch64_ast2700a2_evb_sdk_v11_01(self):
         self.do_ast2700_pcie_test()
         self.do_ast2700_usb_ehci_test()
 
-    def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_01(self):
+    def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_03(self):
         self.set_machine('ast2700a2-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A2)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A2)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
         self.start_ast2700_test_vbootrom('ast2700-default-image', 1)
         self.verify_vbootrom_firmware_flow()
         self.verify_openbmc_boot_start()
 
-    def test_aarch64_ast2700a2_evb_ioexp_v11_01(self):
+    def test_aarch64_ast2700a2_evb_ioexp_v11_03(self):
         self.set_machine('ast2700a2-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A2_DCSCM)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A2_DCSCM)
         self.vm.set_machine('ast2700a2-evb,fmc-model=w25q512jv')
         self.vm.add_args('-device',
                          'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16')
-- 
2.55.0



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

* [PULL 61/83] tests/functional/aarch64/test_aspeed_ast2700a1: Update ASPEED SDK v11.03
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (59 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 60/83] tests/functional/aarch64/test_aspeed_ast2700a2: Update ASPEED SDK v11.03 Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 62/83] tests/functional/aarch64/test_aspeed_ast2700fc: " Cédric Le Goater
                   ` (22 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

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/20260804081955.1563537-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 .../aarch64/test_aspeed_ast2700a1.py          | 27 ++++++++++---------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/tests/functional/aarch64/test_aspeed_ast2700a1.py b/tests/functional/aarch64/test_aspeed_ast2700a1.py
index adbbef5cf11e..2113c78db202 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700a1.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700a1.py
@@ -85,13 +85,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True):
         exec_command_and_wait_for_pattern(self, 'root', 'Password:')
         exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#')
 
-    ASSET_SDK_V1101_AST2700A1 = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-image.tar.gz',
-            '859808828531a51931aad3b4e70b28143eebb3cde1838ba7d8e7a2b844c8a1ab')
+    ASSET_SDK_V1103_AST2700A1 = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-a1-image.tar.gz',
+            '540961dc380709d852e957c5817cd7ee0dbb0a66f3aa17413eac7b67518afbfe')
 
-    ASSET_SDK_V1101_AST2700A1_DCSCM = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-dcscm-image.tar.gz',
-            '4654eabad75da3fd33635cd6d29b7635181daefee7294b68feb124b9d4c24116')
+    ASSET_SDK_V1103_AST2700A1_DCSCM = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-a1-dcscm-image.tar.gz',
+            '5f7c139330fcefa6025bc7565a20fd5ea42cacebf810d56dc9a76b07cf69b3e1')
 
     def do_ast2700_i2c_test(self, bus_id):
         bus_str = str(bus_id)
@@ -132,7 +132,8 @@ def start_ast2700_test(self, name, bus_id):
             },
             {
                 'addr': '0x430000000',
-                'file': self.scratch_file(name, 'bl31.bin')
+                'file': self.scratch_file(name, 'trusted-firmware-a',
+                                          'bl31.bin')
             },
             {
                 'addr': '0x430080000',
@@ -159,11 +160,11 @@ def start_ast2700_test_vbootrom(self, name, bus_id):
         self.do_test_aarch64_aspeed_sdk_start(
                 self.scratch_file(name, 'image-bmc'), bus_id)
 
-    def test_aarch64_ast2700a1_evb_sdk_v11_01(self):
+    def test_aarch64_ast2700a1_evb_sdk_v11_03(self):
         self.set_machine('ast2700a1-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A1)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A1)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
         self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3')
@@ -173,22 +174,22 @@ def test_aarch64_ast2700a1_evb_sdk_v11_01(self):
         self.do_ast2700_pcie_test()
         self.do_ast2700_usb_ehci_test()
 
-    def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_01(self):
+    def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_03(self):
         self.set_machine('ast2700a1-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A1)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A1)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
         self.start_ast2700_test_vbootrom('ast2700-a1-image', 1)
         self.verify_vbootrom_firmware_flow()
         self.verify_openbmc_boot_start()
 
-    def test_aarch64_ast2700a1_evb_ioexp_v11_01(self):
+    def test_aarch64_ast2700a1_evb_ioexp_v11_03(self):
         self.set_machine('ast2700a1-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A1_DCSCM)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A1_DCSCM)
         self.vm.set_machine('ast2700a1-evb,fmc-model=w25q512jv')
         self.vm.add_args('-device',
                          'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16')
-- 
2.55.0



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

* [PULL 62/83] tests/functional/aarch64/test_aspeed_ast2700fc: Update ASPEED SDK v11.03
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (60 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 61/83] tests/functional/aarch64/test_aspeed_ast2700a1: " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 63/83] tests/functional/arm/test_aspeed_ast2600_sdk: " Cédric Le Goater
                   ` (21 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

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/20260804081955.1563537-5-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 .../aarch64/test_aspeed_ast2700fc.py          | 21 ++++++++++---------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/tests/functional/aarch64/test_aspeed_ast2700fc.py b/tests/functional/aarch64/test_aspeed_ast2700fc.py
index 86270e6111f3..704477d7c583 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700fc.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700fc.py
@@ -66,9 +66,9 @@ def load_ast2700fc_coprocessor(self, name):
             self.vm.add_args('-device',
                              f'loader,file={file},cpu-num={cpu_num}')
 
-    ASSET_SDK_V1101_AST2700 = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz',
-            'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487')
+    ASSET_SDK_V1103_AST2700 = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-default-image.tar.gz',
+            'b91450d53da234591060cfb926fa30f7534ce20eaab766cb0f80ec332f8f0adb')
 
     def do_ast2700_i2c_test(self):
         exec_command_and_wait_for_pattern(self,
@@ -101,7 +101,7 @@ def do_ast2700fc_ssp_test(self):
 
         exec_command_and_wait_for_pattern(self, '\012', 'ssp_tsp:~$')
         exec_command_and_wait_for_pattern(self, 'version',
-                                          'Zephyr version 3.7.1')
+                                          'Zephyr version 3.7.2')
         exec_command_and_wait_for_pattern(self, 'md 72c02000 1',
                                           '[72c02000] 06020103')
 
@@ -112,7 +112,7 @@ def do_ast2700fc_tsp_test(self):
 
         exec_command_and_wait_for_pattern(self, '\012', 'tsp:~$')
         exec_command_and_wait_for_pattern(self, 'version',
-                                          'Zephyr version 3.7.1')
+                                          'Zephyr version 3.7.2')
         exec_command_and_wait_for_pattern(self, 'md 72c02000 1',
                                           '[72c02000] 06020103')
 
@@ -125,7 +125,8 @@ def start_ast2700fc_test(self, name):
             },
             {
                 'addr': '0x430000000',
-                'file': self.scratch_file(name, 'bl31.bin')
+                'file': self.scratch_file(name, 'trusted-firmware-a',
+                                          'bl31.bin')
             },
             {
                 'addr': '0x430080000',
@@ -153,11 +154,11 @@ def start_ast2700fc_test_vbootrom(self, name):
         self.do_test_aarch64_aspeed_sdk_start(
                 self.scratch_file(name, 'image-bmc'))
 
-    def test_aarch64_ast2700fc_sdk_v11_01(self):
+    def test_aarch64_ast2700fc_sdk_v11_03(self):
         self.set_machine('ast2700fc')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700)
         self.start_ast2700fc_test('ast2700-default-image')
         self.verify_openbmc_boot_and_login('ast2700-default')
         self.do_ast2700_i2c_test()
@@ -165,10 +166,10 @@ def test_aarch64_ast2700fc_sdk_v11_01(self):
         self.do_ast2700fc_ssp_test()
         self.do_ast2700fc_tsp_test()
 
-    def test_aarch64_ast2700fc_sdk_vbootrom_v11_01(self):
+    def test_aarch64_ast2700fc_sdk_vbootrom_v11_03(self):
         self.set_machine('ast2700fc')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700)
         self.start_ast2700fc_test_vbootrom('ast2700-default-image')
         self.verify_openbmc_boot_and_login('ast2700-default')
         self.do_ast2700fc_ssp_test()
-- 
2.55.0



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

* [PULL 63/83] tests/functional/arm/test_aspeed_ast2600_sdk: Update ASPEED SDK v11.03
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (61 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 62/83] tests/functional/aarch64/test_aspeed_ast2700fc: " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 64/83] tests/functional/arm/test_aspeed_ast2500_sdk: " Cédric Le Goater
                   ` (20 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

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/20260804081955.1563537-6-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/functional/arm/test_aspeed_ast2600_sdk.py     | 8 ++++----
 tests/functional/arm/test_aspeed_ast2600_sdk_515.py | 8 ++++----
 tests/functional/arm/test_aspeed_ast2600_sdk_otp.py | 8 ++++----
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk.py b/tests/functional/arm/test_aspeed_ast2600_sdk.py
index cabbe230c4cc..4fc594dfd5a1 100755
--- a/tests/functional/arm/test_aspeed_ast2600_sdk.py
+++ b/tests/functional/arm/test_aspeed_ast2600_sdk.py
@@ -14,9 +14,9 @@
 
 class AST2600Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2600 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz',
-        '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c')
+    ASSET_SDK_V1103_AST2600 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-image.tar.gz',
+        '47e3656a14bf7a4de28d3dfbf48bc2325443bc42d270f3bc82646f92f6dea165')
 
     def do_ast2600_pcie_test(self):
         exec_command_and_wait_for_pattern(self,
@@ -49,7 +49,7 @@ def test_arm_ast2600_evb_sdk(self):
         self.set_machine('ast2600-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2600)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2600)
 
         self.vm.add_args('-device',
             'tmp105,bus=aspeed.i2c.bus.5,address=0x4d,id=tmp-test')
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py
index a8e7faff4890..f5b14de083a7 100755
--- a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py
+++ b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py
@@ -10,14 +10,14 @@
 
 class AST2600Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2600_515 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-515-image.tar.gz',
-        'f3ccf1c08db71cf891637fc73131b80b2c0c0e005c06d5dcae0cf74fc458b43c')
+    ASSET_SDK_V1103_AST2600_515 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-515-image.tar.gz',
+        'c79d0197106f146476e82bb878e5438f6569bd30f3b53fbb520b59bc54f6b7dc')
 
     def test_arm_ast2600_evb_sdk_515(self):
         self.set_machine('ast2600-evb')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2600_515)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2600_515)
 
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2600-default-515-image", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py
index f24dea1e8f03..5813c59bd6db 100755
--- a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py
+++ b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py
@@ -12,15 +12,15 @@
 
 class AST2600Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2600 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz',
-        '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c')
+    ASSET_SDK_V1103_AST2600 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-image.tar.gz',
+        '47e3656a14bf7a4de28d3dfbf48bc2325443bc42d270f3bc82646f92f6dea165')
 
     def test_arm_ast2600_otp_blockdev_device(self):
         self.vm.set_machine("ast2600-evb")
         self.require_netdev('user')
 
-        image_path = self.archive_extract(self.ASSET_SDK_V1101_AST2600)
+        image_path = self.archive_extract(self.ASSET_SDK_V1103_AST2600)
         otp_img = self.generate_otpmem_image()
 
         self.vm.set_console()
-- 
2.55.0



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

* [PULL 64/83] tests/functional/arm/test_aspeed_ast2500_sdk: Update ASPEED SDK v11.03
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (62 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 63/83] tests/functional/arm/test_aspeed_ast2600_sdk: " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 65/83] tests/functional/arm/test_aspeed_ast1030: Update ASPEED Zephyr SDK v03.08 Cédric Le Goater
                   ` (19 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

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/20260804081955.1563537-7-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/functional/arm/test_aspeed_ast2500_sdk.py     | 8 ++++----
 tests/functional/arm/test_aspeed_ast2500_sdk_515.py | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk.py b/tests/functional/arm/test_aspeed_ast2500_sdk.py
index 6ab498b3ff54..95df32b84ff8 100755
--- a/tests/functional/arm/test_aspeed_ast2500_sdk.py
+++ b/tests/functional/arm/test_aspeed_ast2500_sdk.py
@@ -10,14 +10,14 @@
 
 class AST2500Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2500 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-obmc.tar.gz',
-        '3faa1188198da2216837be4b53861c483a58c3ad63784089720bf8421e157da1')
+    ASSET_SDK_V1103_AST2500 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2500-default-obmc.tar.gz',
+        '8e20cafddca04d73b799918d6f35b08c83c9f024e223a317b0ad71b97b84842f')
 
     def test_arm_ast2500_evb_sdk(self):
         self.set_machine('ast2500-evb')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2500)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2500)
 
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2500-default", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py
index 8d39dc65daaa..516e96e52db7 100755
--- a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py
+++ b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py
@@ -10,14 +10,14 @@
 
 class AST2500Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2500_515 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-515-obmc.tar.gz',
-        'b848ff620d2e9c83e2fb4736b4d1c39b82fdb041058cd42be42c3b177bf38eb9')
+    ASSET_SDK_V1103_AST2500_515 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2500-default-515-obmc.tar.gz',
+        'f17d3b0a5157bcf73c21c4981f838ea0b76c6406cc4a6409267d57d61758ebb6')
 
     def test_arm_ast2500_evb_sdk_515(self):
         self.set_machine('ast2500-evb')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2500_515)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2500_515)
 
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2500-default-515", "image-bmc"))
-- 
2.55.0



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

* [PULL 65/83] tests/functional/arm/test_aspeed_ast1030: Update ASPEED Zephyr SDK v03.08
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (63 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 64/83] tests/functional/arm/test_aspeed_ast2500_sdk: " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 66/83] tests/functional/arm/test_aspeed_ast1060: Update ASPEED ZEPHYR PROJECT v03.07 Cédric Le Goater
                   ` (18 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

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/20260804081955.1563537-8-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/functional/arm/test_aspeed_ast1030.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/tests/functional/arm/test_aspeed_ast1030.py b/tests/functional/arm/test_aspeed_ast1030.py
index 03fee55b5f8e..83a96ec322fa 100755
--- a/tests/functional/arm/test_aspeed_ast1030.py
+++ b/tests/functional/arm/test_aspeed_ast1030.py
@@ -12,17 +12,17 @@
 
 class AST1030Machine(AspeedTest):
 
-    ASSET_ZEPHYR_3_06 = Asset(
+    ASSET_ZEPHYR_3_08 = Asset(
         ('https://github.com/AspeedTech-BMC'
-         '/zephyr/releases/download/v00.03.06/ast1030-evb-demo.zip'),
-         '056f37fcd9f165308cedca3a08f2bed37ed40c0a1402c4fa515613b80a369f38')
+         '/zephyr/releases/download/v00.03.08/ast1030-evb-demo.zip'),
+         '9eac3691bc7bce1b912bbe2ae4e36608a6532ff8d607f4d1e44b88407a48d4e5')
 
-    def test_arm_ast1030_zephyros_3_06(self):
+    def test_arm_ast1030_zephyros_3_08(self):
         self.set_machine('ast1030-evb')
 
         kernel_name = "ast1030-evb-demo/zephyr.elf"
         kernel_file = self.archive_extract(
-            self.ASSET_ZEPHYR_3_06, member=kernel_name)
+            self.ASSET_ZEPHYR_3_08, member=kernel_name)
 
         self.vm.set_console()
         self.vm.add_args('-kernel', kernel_file, '-nographic')
@@ -72,7 +72,7 @@ def test_arm_ast1030_otp_blockdev_device(self):
         self.vm.set_machine("ast1030-evb")
 
         kernel_name = "ast1030-evb-demo/zephyr.elf"
-        kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_06,
+        kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_08,
                                            member=kernel_name)
         otp_img = self.generate_otpmem_image()
 
-- 
2.55.0



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

* [PULL 66/83] tests/functional/arm/test_aspeed_ast1060: Update ASPEED ZEPHYR PROJECT v03.07
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (64 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 65/83] tests/functional/arm/test_aspeed_ast1030: Update ASPEED Zephyr SDK v03.08 Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 67/83] hw/arm/aspeed: avoid sign mismatch on sscanf for uart property Cédric Le Goater
                   ` (17 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

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/20260804081955.1563537-9-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/functional/arm/test_aspeed_ast1060.py | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/tests/functional/arm/test_aspeed_ast1060.py b/tests/functional/arm/test_aspeed_ast1060.py
index 833cfb8272e7..d7158259e647 100755
--- a/tests/functional/arm/test_aspeed_ast1060.py
+++ b/tests/functional/arm/test_aspeed_ast1060.py
@@ -11,18 +11,18 @@
 
 
 class AST1060Machine(AspeedTest):
-    ASSET_ASPEED_AST1060_PROT_3_05 = Asset(
+    ASSET_ASPEED_AST1060_PROT_3_07 = Asset(
         ('https://github.com/AspeedTech-BMC'
-         '/aspeed-zephyr-project/releases/download/v03.05'
-         '/ast1060_prot_v03.05.tgz'),
-         '63b36d7420290726ca80477de254474b7cb79539a42819bb1fe2665d598dadb5')
+         '/aspeed-zephyr-project/releases/download/v03.07'
+         '/ast1060_prot_v03.07.tgz'),
+         '55a7f51f0b77051a0ef2ada993a16c5033768e1b8e8be3babfc52c303eecd07f')
 
-    def test_arm_ast1060_prot_3_05(self):
+    def test_arm_ast1060_prot_3_07(self):
         self.set_machine('ast1060-evb')
 
         kernel_name = "ast1060_prot/zephyr.bin"
         kernel_file = self.archive_extract(
-            self.ASSET_ASPEED_AST1060_PROT_3_05, member=kernel_name)
+            self.ASSET_ASPEED_AST1060_PROT_3_07, member=kernel_name)
 
         self.vm.set_console()
         self.vm.add_args('-kernel', kernel_file, '-nographic')
@@ -35,7 +35,7 @@ def test_arm_ast1060_otp_blockdev_device(self):
         self.vm.set_machine("ast1060-evb")
 
         kernel_name = "ast1060_prot/zephyr.bin"
-        kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_05,
+        kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_07,
                                            member=kernel_name)
         otp_img = self.generate_otpmem_image()
 
-- 
2.55.0



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

* [PULL 67/83] hw/arm/aspeed: avoid sign mismatch on sscanf for uart property
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (65 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 66/83] tests/functional/arm/test_aspeed_ast1060: Update ASPEED ZEPHYR PROJECT v03.07 Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 68/83] hw/misc/aspeed_hace: Support the crypto command in direct access mode Cédric Le Goater
                   ` (16 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Carlo Marcelo Arenas Belón, Cédric Le Goater

From: Carlo Marcelo Arenas Belón <carenas@gmail.com>

using "%u" with sscanf() was likely meant to indicate that a
negative value was unexpected, but with a signed variable it
could result in undefined behaviour.

use "%d" and check for a negative input explicitly.

Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260802162828.16880-1-carenas@gmail.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/arm/aspeed.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c
index a48c44205837..a9238e6217dd 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -327,7 +327,7 @@ static void aspeed_set_bmc_console(Object *obj, const char *value, Error **errp)
     int uart_first = aspeed_uart_first(sc->uarts_base);
     int uart_last = aspeed_uart_last(sc->uarts_base, sc->uarts_num);
 
-    if (sscanf(value, "uart%u", &val) != 1) {
+    if (sscanf(value, "uart%d", &val) != 1 || val < 0) {
         error_setg(errp, "Bad value for \"uart\" property");
         return;
     }
-- 
2.55.0



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

* [PULL 68/83] hw/misc/aspeed_hace: Support the crypto command in direct access mode
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (66 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 67/83] hw/arm/aspeed: avoid sign mismatch on sscanf for uart property Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 69/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2500 Cédric Le Goater
                   ` (15 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The crypt command register was previously stubbed out. Implement it for
the direct access mode, where HACE00/HACE04 point directly at contiguous
source and destination buffers. AES-128/192/256, DES and 3DES are
supported in ECB and CBC modes via the qcrypto cipher API; the IV and
key are read from the context buffer (HACE08) and, for CBC, the
resulting chaining IV is written back to the context.

The completion interrupt is now raised for every HACE variant as the
hardware does, which fixes the crypt command hang on the AST2500, AST2600
and AST1030. The AST2700 crypto engine still needs 64-bit DMA and
AES-GCM, which are added later, so it keeps its temporary interrupt-only
workaround until then.

For debugging, the context, source and destination buffers are dumped
through the existing aspeed_hace_hexdump trace event (disabled by
default). CTR mode, scatter-gather mode and AES-GCM are added separately.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-2-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_hace.c | 283 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 268 insertions(+), 15 deletions(-)

diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index c61efe50c4ff..0f35d74137b1 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -18,11 +18,43 @@
 #include "qapi/error.h"
 #include "migration/vmstate.h"
 #include "crypto/hash.h"
+#include "crypto/cipher.h"
 #include "hw/core/qdev-properties.h"
 #include "hw/core/irq.h"
 #include "trace.h"
 
-#define R_CRYPT_CMD     (0x10 / 4)
+/* Crypto engine registers */
+#define R_CRYPT_SRC         (0x00 / 4)
+#define R_CRYPT_DEST        (0x04 / 4)
+#define R_CRYPT_CONTEXT     (0x08 / 4)
+#define R_CRYPT_DATA_LEN    (0x0c / 4)
+/* HACE0C[27:0] holds the crypto data length */
+#define  CRYPT_DATA_LEN_MASK    0x0FFFFFFF
+#define R_CRYPT_CMD         (0x10 / 4)
+/* Crypto engine command register (HACE10) bits */
+#define  CRYPT_CMD_ENCRYPT          BIT(7)
+#define  CRYPT_CMD_ISR_EN           BIT(12)
+#define  CRYPT_CMD_DES_SELECT       BIT(16)
+#define  CRYPT_CMD_TRIPLE_DES       BIT(17)
+#define  CRYPT_CMD_SRC_SG_CTRL      BIT(18)
+/* Operation mode HACE10[6:4] */
+#define  CRYPT_CMD_OP_MODE_MASK     (0x7 << 4)
+#define  CRYPT_CMD_ECB              (0x0 << 4)
+#define  CRYPT_CMD_CBC              (0x1 << 4)
+/* AES key length HACE10[3:2] */
+#define  CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
+#define  CRYPT_CMD_AES256           (0x2 << 2)
+#define  CRYPT_CMD_AES192           (0x1 << 2)
+#define  CRYPT_CMD_AES128           (0x0 << 2)
+
+/*
+ * Crypto context buffer layout (HACE08). The IV is at the start of the buffer
+ * (DES places its 8 byte IV at offset 8) and the cipher key at offset 0x10.
+ */
+#define CRYPT_CTX_IV_OFFSET         0x00
+#define CRYPT_CTX_DES_IV_OFFSET     0x08
+#define CRYPT_CTX_KEY_OFFSET        0x10
+#define CRYPT_CTX_SIZE              0x30
 
 #define R_STATUS        (0x1c / 4)
 #define HASH_IRQ        BIT(9)
@@ -65,7 +97,6 @@
 /* Other cmd bits */
 #define  HASH_IRQ_EN                    BIT(9)
 #define  HASH_SG_EN                     BIT(18)
-#define  CRYPT_IRQ_EN                   BIT(12)
 /* Scatter-gather data list */
 #define SG_LIST_LEN_SIZE                4
 #define SG_LIST_LEN_MASK                0x0FFFFFFF
@@ -501,6 +532,216 @@ static void do_hash_operation(AspeedHACEState *s, int algo, bool sg_mode,
     }
 }
 
+static bool crypt_aes_alg(uint32_t cmd, QCryptoCipherAlgo *alg, size_t *keylen)
+{
+    switch (cmd & CRYPT_CMD_AES_KEY_LEN_MASK) {
+    case CRYPT_CMD_AES128:
+        *alg = QCRYPTO_CIPHER_ALGO_AES_128;
+        *keylen = 16;
+        break;
+    case CRYPT_CMD_AES192:
+        *alg = QCRYPTO_CIPHER_ALGO_AES_192;
+        *keylen = 24;
+        break;
+    case CRYPT_CMD_AES256:
+        *alg = QCRYPTO_CIPHER_ALGO_AES_256;
+        *keylen = 32;
+        break;
+    default:
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * Decode the crypto command register into a libqcrypto algorithm/mode pair
+ * and the block/IV geometry. Returns false for unsupported selections.
+ */
+static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg,
+                             QCryptoCipherMode *mode, size_t *keylen,
+                             size_t *blocklen, size_t *iv_offset)
+{
+    if (cmd & CRYPT_CMD_DES_SELECT) {
+        *blocklen = 8;
+        *iv_offset = CRYPT_CTX_DES_IV_OFFSET;
+        if (cmd & CRYPT_CMD_TRIPLE_DES) {
+            *alg = QCRYPTO_CIPHER_ALGO_3DES;
+            *keylen = 24;
+        } else {
+            *alg = QCRYPTO_CIPHER_ALGO_DES;
+            *keylen = 8;
+        }
+    } else {
+        *blocklen = 16;
+        *iv_offset = CRYPT_CTX_IV_OFFSET;
+        if (!crypt_aes_alg(cmd, alg, keylen)) {
+            return false;
+        }
+    }
+
+    switch (cmd & CRYPT_CMD_OP_MODE_MASK) {
+    case CRYPT_CMD_ECB:
+        *mode = QCRYPTO_CIPHER_MODE_ECB;
+        break;
+    case CRYPT_CMD_CBC:
+        *mode = QCRYPTO_CIPHER_MODE_CBC;
+        break;
+    default:
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * Direct access mode: the source/destination register (HACE00/HACE04) points
+ * at a single contiguous buffer in DRAM. Copy @len bytes between it and the
+ * bounce buffer @buf; when @to_dram is true @buf is written out, otherwise it
+ * is read in. Returns true on success.
+ */
+static bool crypt_prepare_direct(AspeedHACEState *s, uint64_t addr,
+                                 uint8_t *buf, uint32_t len, bool to_dram)
+{
+    return !address_space_rw(&s->dram_as, addr, MEMTXATTRS_UNSPECIFIED,
+                             buf, len, to_dram);
+}
+
+/*
+ * Perform an AES/DES/3DES ECB/CBC operation in direct access mode: the source
+ * and destination are single contiguous buffers (HACE00/HACE04) and the IV/key
+ * come from the context buffer (HACE08). For CBC the resulting chaining IV is
+ * written back to the context buffer so the driver can continue the chain.
+ */
+static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
+{
+    uint32_t len = s->regs[R_CRYPT_DATA_LEN];
+    bool encrypt = cmd & CRYPT_CMD_ENCRYPT;
+    g_autoptr(QCryptoCipher) cipher = NULL;
+    g_autofree uint8_t *src_buf = NULL;
+    g_autofree uint8_t *dst_buf = NULL;
+    uint8_t ctx[CRYPT_CTX_SIZE];
+    Error *local_err = NULL;
+    QCryptoCipherMode mode;
+    QCryptoCipherAlgo alg;
+    const uint8_t *next_iv;
+    uint64_t ctx_addr;
+    uint64_t src_addr;
+    uint64_t dst_addr;
+    size_t iv_offset;
+    size_t blocklen;
+    size_t keylen;
+
+    if (len == 0) {
+        return;
+    }
+
+    if (!crypt_decode_cmd(cmd, &alg, &mode, &keylen, &blocklen, &iv_offset)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: Unsupported crypt command 0x%x\n", __func__, cmd);
+        return;
+    }
+
+    if (!qcrypto_cipher_supports(alg, mode)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: cipher mode not supported by the crypto backend\n",
+                      __func__);
+        return;
+    }
+
+    /* Fetch the IV and key from the context buffer in DRAM. */
+    ctx_addr = s->regs[R_CRYPT_CONTEXT];
+    if (address_space_read(&s->dram_as, ctx_addr, MEMTXATTRS_UNSPECIFIED,
+                           ctx, sizeof(ctx))) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Failed to read context, addr=0x%" HWADDR_PRIx "\n",
+                      __func__, ctx_addr);
+        return;
+    }
+
+    if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) {
+        hace_hexdump("context", (char *)ctx, sizeof(ctx));
+    }
+
+    cipher = qcrypto_cipher_new(alg, mode, ctx + CRYPT_CTX_KEY_OFFSET, keylen,
+                                &local_err);
+    if (cipher == NULL) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher new failed: %s\n",
+                      __func__, error_get_pretty(local_err));
+        error_free(local_err);
+        return;
+    }
+
+    if (mode != QCRYPTO_CIPHER_MODE_ECB &&
+        qcrypto_cipher_setiv(cipher, ctx + iv_offset, blocklen,
+                             &local_err) < 0) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv failed: %s\n",
+                      __func__, error_get_pretty(local_err));
+        error_free(local_err);
+        return;
+    }
+
+    src_buf = g_malloc0(len);
+    dst_buf = g_malloc0(len);
+
+    src_addr = s->regs[R_CRYPT_SRC];
+    if (!crypt_prepare_direct(s, src_addr, src_buf, len, false)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Failed to read src, addr=0x%" HWADDR_PRIx "\n",
+                      __func__, src_addr);
+        return;
+    }
+
+    if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) {
+        hace_hexdump("src", (char *)src_buf, len);
+    }
+
+    if (encrypt) {
+        if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, len,
+                                   &local_err) < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: encrypt failed: %s\n",
+                          __func__, error_get_pretty(local_err));
+            error_free(local_err);
+            return;
+        }
+    } else {
+        if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, len,
+                                   &local_err) < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: decrypt failed: %s\n",
+                          __func__, error_get_pretty(local_err));
+            error_free(local_err);
+            return;
+        }
+    }
+
+    dst_addr = s->regs[R_CRYPT_DEST];
+    if (!crypt_prepare_direct(s, dst_addr, dst_buf, len, true)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Failed to write dst, addr=0x%" HWADDR_PRIx "\n",
+                      __func__, dst_addr);
+        return;
+    }
+
+    if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) {
+        hace_hexdump("dst", (char *)dst_buf, len);
+    }
+
+    if (mode == QCRYPTO_CIPHER_MODE_CBC) {
+        /*
+         * CBC chains on the last ciphertext block: the final block of the
+         * output when encrypting, or of the input when decrypting. Write it
+         * back as the IV for the next request.
+         */
+        next_iv = (encrypt ? dst_buf : src_buf) + len - blocklen;
+        if (address_space_write(&s->dram_as, ctx_addr + iv_offset,
+                                MEMTXATTRS_UNSPECIFIED, next_iv, blocklen)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
+                          __func__, ctx_addr + iv_offset);
+        }
+    }
+}
+
 static uint64_t aspeed_hace_read(void *opaque, hwaddr addr, unsigned int size)
 {
     AspeedHACEState *s = ASPEED_HACE(opaque);
@@ -531,16 +772,22 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
                 qemu_irq_lower(s->irq);
             }
         }
-        if (ahc->raise_crypt_interrupt_workaround) {
-            if (data & CRYPT_IRQ) {
-                data &= ~CRYPT_IRQ;
+        if (data & CRYPT_IRQ) {
+            data &= ~CRYPT_IRQ;
 
-                if (s->regs[addr] & CRYPT_IRQ) {
-                    qemu_irq_lower(s->irq);
-                }
+            if (s->regs[addr] & CRYPT_IRQ) {
+                qemu_irq_lower(s->irq);
             }
         }
         break;
+    case R_CRYPT_SRC:
+    case R_CRYPT_DEST:
+    case R_CRYPT_CONTEXT:
+        data &= ahc->src_mask;
+        break;
+    case R_CRYPT_DATA_LEN:
+        data &= CRYPT_DATA_LEN_MASK;
+        break;
     case R_HASH_SRC:
         data &= ahc->src_mask;
         break;
@@ -589,13 +836,19 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
         break;
     }
     case R_CRYPT_CMD:
-        qemu_log_mask(LOG_UNIMP, "%s: Crypt commands not implemented\n",
-                       __func__);
-        if (ahc->raise_crypt_interrupt_workaround) {
-            s->regs[R_STATUS] |= CRYPT_IRQ;
-            if (data & CRYPT_IRQ_EN) {
-                qemu_irq_raise(s->irq);
-            }
+        /*
+         * The AST2700 crypto engine needs 64-bit DMA and AES-GCM, which are
+         * added later; until then it keeps the temporary workaround of only
+         * raising the completion interrupt without running the command.
+         */
+        if (!ahc->raise_crypt_interrupt_workaround) {
+            do_crypt_operation(s, data);
+        }
+
+        /* Hardware raises the crypt interrupt once the command finishes. */
+        s->regs[R_STATUS] |= CRYPT_IRQ;
+        if (data & CRYPT_CMD_ISR_EN) {
+            qemu_irq_raise(s->irq);
         }
         break;
     case R_HASH_SRC_HI:
-- 
2.55.0



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

* [PULL 69/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2500
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (67 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 68/83] hw/misc/aspeed_hace: Support the crypto command in direct access mode Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 70/83] hw/misc/aspeed_hace: Support scatter-gather mode for the crypto command Cédric Le Goater
                   ` (14 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Add a crypto known-answer test harness and exercise the AST2500, which
uses the crypto engine's direct access mode. Each mode (AES/DES/3DES in
ECB and CBC) is a separate test that checks the ciphertext, the
plaintext round-trip and, for CBC, the chaining IV written back to the
context buffer.

The key/IV/plaintext/ciphertext values are taken verbatim from the Linux
kernel crypto self-test templates in crypto/testmgr.h.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-hace-utils.h |  16 ++
 tests/qtest/aspeed-hace-utils.c | 325 ++++++++++++++++++++++++++++++++
 tests/qtest/aspeed_hace-test.c  |   6 +
 tests/qtest/meson.build         |   6 +-
 4 files changed, 351 insertions(+), 2 deletions(-)

diff --git a/tests/qtest/aspeed-hace-utils.h b/tests/qtest/aspeed-hace-utils.h
index 27ab2bb97585..13feaa61e446 100644
--- a/tests/qtest/aspeed-hace-utils.h
+++ b/tests/qtest/aspeed-hace-utils.h
@@ -79,5 +79,21 @@ void aspeed_test_sha512_accum(const char *machine, const uint32_t base,
 void aspeed_test_addresses(const char *machine, const uint32_t base,
                            const struct AspeedMasks *expected);
 
+/*
+ * Cipher modes a SoC's crypto engine supports, for aspeed_add_crypto_tests().
+ */
+enum {
+    CRYPT_MODE_ECB = 1 << 0,
+    CRYPT_MODE_CBC = 1 << 1,
+};
+
+/*
+ * Register the crypto known-answer tests that @modes selects (a mask of
+ * CRYPT_MODE_*) for the given machine. Each test is named
+ * "<prefix>/hace/crypto/<mode>".
+ */
+void aspeed_add_crypto_tests(const char *prefix, const char *machine,
+                             uint32_t base, uint64_t dram, uint32_t modes);
+
 #endif /* TESTS_ASPEED_HACE_UTILS_H */
 
diff --git a/tests/qtest/aspeed-hace-utils.c b/tests/qtest/aspeed-hace-utils.c
index 25450a296bf4..0355dd47af15 100644
--- a/tests/qtest/aspeed-hace-utils.c
+++ b/tests/qtest/aspeed-hace-utils.c
@@ -9,6 +9,7 @@
 #include "libqtest.h"
 #include "qemu/bitops.h"
 #include "qemu/bswap.h"
+#include "crypto/cipher.h"
 #include "aspeed-hace-utils.h"
 
 /*
@@ -645,3 +646,327 @@ void aspeed_test_addresses(const char *machine, const uint32_t base,
     qtest_quit(s);
 }
 
+/*
+ * Crypto engine register layout (offsets from the HACE base).
+ */
+#define HACE_CRYPTO_SRC          0x00
+#define HACE_CRYPTO_DEST         0x04
+#define HACE_CRYPTO_CONTEXT      0x08
+#define HACE_CRYPTO_DATA_LEN     0x0c
+#define HACE_CRYPTO_CMD          0x10
+
+/* Crypto command bits */
+#define HACE_CMD_ENCRYPT         BIT(7)
+#define HACE_CMD_ISR_EN          BIT(12)
+#define HACE_CMD_DES_SELECT      BIT(16)
+#define HACE_CMD_TRIPLE_DES      BIT(17)
+#define HACE_CMD_SRC_SG_CTRL     BIT(18)
+#define HACE_CMD_DST_SG_CTRL     BIT(19)
+#define HACE_CMD_OP_MODE_MASK    (0x7 << 4)
+#define HACE_CMD_ECB             (0x0 << 4)
+#define HACE_CMD_CBC             (0x1 << 4)
+#define HACE_CMD_AES128          (0x0 << 2)
+
+/* Context buffer layout: IV (DES at +8), key at +0x10 */
+#define HACE_CTX_KEY_OFFSET      0x10
+#define HACE_CTX_SIZE            0x30
+
+/*
+ * Crypto known-answer test vectors, taken verbatim from the Linux kernel
+ * crypto self-test templates in crypto/testmgr.h:
+ *
+ *   https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/crypto/testmgr.h?h=v6.18
+ *
+ * The originating template is noted above each block. CTR and the longer CBC
+ * vectors are truncated to a single block (still a valid known-answer test as
+ * the first block only depends on the IV).
+ */
+
+/* aes_tv_template[0] (FIPS-197) */
+static const uint8_t aes128_ecb_key[16] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
+static const uint8_t aes128_ecb_ptext[16] = {
+    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
+static const uint8_t aes128_ecb_ctext[16] = {
+    0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30,
+    0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a };
+
+/* aes_cbc_tv_template[0] (RFC 3602) */
+static const uint8_t aes128_cbc_key[16] = {
+    0x06, 0xa9, 0x21, 0x40, 0x36, 0xb8, 0xa1, 0x5b,
+    0x51, 0x2e, 0x03, 0xd5, 0x34, 0x12, 0x00, 0x06 };
+static const uint8_t aes128_cbc_iv[16] = {
+    0x3d, 0xaf, 0xba, 0x42, 0x9d, 0x9e, 0xb4, 0x30,
+    0xb4, 0x22, 0xda, 0x80, 0x2c, 0x9f, 0xac, 0x41 };
+static const uint8_t aes128_cbc_ptext[16] = {
+    0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62,
+    0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 };
+static const uint8_t aes128_cbc_ctext[16] = {
+    0xe3, 0x53, 0x77, 0x9c, 0x10, 0x79, 0xae, 0xb8,
+    0x27, 0x08, 0x94, 0x2d, 0xbe, 0x77, 0x18, 0x1a };
+static const uint8_t aes128_cbc_ivout[16] = {
+    0xe3, 0x53, 0x77, 0x9c, 0x10, 0x79, 0xae, 0xb8,
+    0x27, 0x08, 0x94, 0x2d, 0xbe, 0x77, 0x18, 0x1a };
+
+/* des_tv_template[0] (Applied Cryptography) */
+static const uint8_t des_ecb_key[8] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+static const uint8_t des_ecb_ptext[8] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xe7 };
+static const uint8_t des_ecb_ctext[8] = {
+    0xc9, 0x57, 0x44, 0x25, 0x6a, 0x5e, 0xd3, 0x1d };
+
+/* des_cbc_tv_template[0] (OpenSSL), first block */
+static const uint8_t des_cbc_key[8] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+static const uint8_t des_cbc_iv[8] = {
+    0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 };
+static const uint8_t des_cbc_ptext[8] = {
+    0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x20 };
+static const uint8_t des_cbc_ctext[8] = {
+    0xcc, 0xd1, 0x73, 0xff, 0xab, 0x20, 0x39, 0xf4 };
+
+/* des3_ede_tv_template[0] (OpenSSL) */
+static const uint8_t tdes_ecb_key[24] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+    0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 };
+static const uint8_t tdes_ecb_ptext[8] = {
+    0x73, 0x6f, 0x6d, 0x65, 0x64, 0x61, 0x74, 0x61 };
+static const uint8_t tdes_ecb_ctext[8] = {
+    0x18, 0xd7, 0x48, 0xe5, 0x63, 0x62, 0x05, 0x72 };
+
+/* des3_ede_cbc_tv_template[0] (OpenSSL), first block */
+static const uint8_t tdes_cbc_key[24] = {
+    0xe9, 0xc0, 0xff, 0x2e, 0x76, 0x0b, 0x64, 0x24,
+    0x44, 0x4d, 0x99, 0x5a, 0x12, 0xd6, 0x40, 0xc0,
+    0xea, 0xc2, 0x84, 0xe8, 0x14, 0x95, 0xdb, 0xe8 };
+static const uint8_t tdes_cbc_iv[8] = {
+    0x7d, 0x33, 0x88, 0x93, 0x0f, 0x93, 0xb2, 0x42 };
+static const uint8_t tdes_cbc_ptext[8] = {
+    0x6f, 0x54, 0x20, 0x6f, 0x61, 0x4d, 0x79, 0x6e };
+static const uint8_t tdes_cbc_ctext[8] = {
+    0x0e, 0x2d, 0xb6, 0x97, 0x3c, 0x56, 0x33, 0xf4 };
+
+typedef struct CryptTest {
+    QCryptoCipherMode mode;
+    QCryptoCipherAlgo alg;
+    /* expected context IV after encrypt, or NULL */
+    const uint8_t *iv_out;
+    const uint8_t *ptext;
+    const uint8_t *ctext;
+    const uint8_t *key;
+    const uint8_t *iv;
+    const char *name;
+    size_t keylen;
+    /* algorithm | mode | key size selection */
+    uint32_t cmd;
+    size_t ivlen;
+    size_t len;
+} CryptTest;
+
+static const CryptTest crypt_tests[] = {
+    {
+        .name = "aes128-ecb",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_ECB,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = aes128_ecb_key,
+        .keylen = sizeof(aes128_ecb_key),
+        .ptext = aes128_ecb_ptext,
+        .ctext = aes128_ecb_ctext,
+        .len = sizeof(aes128_ecb_ptext),
+    },
+    {
+        .name = "aes128-cbc",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_CBC,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = aes128_cbc_key,
+        .keylen = sizeof(aes128_cbc_key),
+        .iv = aes128_cbc_iv,
+        .ivlen = sizeof(aes128_cbc_iv),
+        .ptext = aes128_cbc_ptext,
+        .ctext = aes128_cbc_ctext,
+        .iv_out = aes128_cbc_ivout,
+        .len = sizeof(aes128_cbc_ptext),
+    },
+    {
+        .name = "des-ecb",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_ECB,
+        .alg = QCRYPTO_CIPHER_ALGO_DES,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = des_ecb_key,
+        .keylen = sizeof(des_ecb_key),
+        .ptext = des_ecb_ptext,
+        .ctext = des_ecb_ctext,
+        .len = sizeof(des_ecb_ptext),
+    },
+    {
+        .name = "des-cbc",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_CBC,
+        .alg = QCRYPTO_CIPHER_ALGO_DES,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = des_cbc_key,
+        .keylen = sizeof(des_cbc_key),
+        .iv = des_cbc_iv,
+        .ivlen = sizeof(des_cbc_iv),
+        .ptext = des_cbc_ptext,
+        .ctext = des_cbc_ctext,
+        .len = sizeof(des_cbc_ptext),
+    },
+    {
+        .name = "des3_ede-ecb",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_ECB,
+        .alg = QCRYPTO_CIPHER_ALGO_3DES,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = tdes_ecb_key,
+        .keylen = sizeof(tdes_ecb_key),
+        .ptext = tdes_ecb_ptext,
+        .ctext = tdes_ecb_ctext,
+        .len = sizeof(tdes_ecb_ptext),
+    },
+    {
+        .name = "des3_ede-cbc",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_CBC,
+        .alg = QCRYPTO_CIPHER_ALGO_3DES,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = tdes_cbc_key,
+        .keylen = sizeof(tdes_cbc_key),
+        .iv = tdes_cbc_iv,
+        .ivlen = sizeof(tdes_cbc_iv),
+        .ptext = tdes_cbc_ptext,
+        .ctext = tdes_cbc_ctext,
+        .len = sizeof(tdes_cbc_ptext),
+    },
+};
+
+/* DRAM offsets for the crypto test source, destination and context buffers. */
+#define CRYPT_OFF_SRC   0x10000
+#define CRYPT_OFF_DST   0x20000
+#define CRYPT_OFF_CTX   0x30000
+
+/* Describes one registered crypto test (qtest_add_data_func() data pointer). */
+typedef struct AspeedCryptoTest {
+    const char *machine;
+    uint64_t dram;
+    uint32_t base;
+    int index;
+} AspeedCryptoTest;
+
+/* Map a command's operation mode (HACE10[6:4]) to a CRYPT_MODE_* flag. */
+static uint32_t crypt_mode_flag(uint32_t cmd)
+{
+    switch (cmd & HACE_CMD_OP_MODE_MASK) {
+    case HACE_CMD_ECB:
+        return CRYPT_MODE_ECB;
+    case HACE_CMD_CBC:
+        return CRYPT_MODE_CBC;
+    default:
+        return 0;
+    }
+}
+
+static void crypt_write_ctx(QTestState *s, uint64_t ctx_addr,
+                            const CryptTest *t)
+{
+    size_t iv_off = (t->cmd & HACE_CMD_DES_SELECT) ? 8 : 0;
+    uint8_t ctx[HACE_CTX_SIZE] = { 0 };
+
+    if (t->iv) {
+        memcpy(ctx + iv_off, t->iv, t->ivlen);
+    }
+    memcpy(ctx + HACE_CTX_KEY_OFFSET, t->key, t->keylen);
+    qtest_memwrite(s, ctx_addr, ctx, sizeof(ctx));
+}
+
+/* Run one crypto operation in direct access mode and read back the result. */
+static void crypt_run_direct(QTestState *s, uint32_t base, uint64_t dram,
+                             const CryptTest *t, bool encrypt, uint8_t *out)
+{
+    const uint8_t *in = encrypt ? t->ptext : t->ctext;
+    uint32_t cmd = t->cmd | HACE_CMD_ISR_EN;
+    uint64_t src = dram + CRYPT_OFF_SRC;
+    uint64_t dst = dram + CRYPT_OFF_DST;
+    uint64_t ctx = dram + CRYPT_OFF_CTX;
+
+    if (encrypt) {
+        cmd |= HACE_CMD_ENCRYPT;
+    }
+
+    crypt_write_ctx(s, ctx, t);
+    qtest_memwrite(s, src, in, t->len);
+
+    qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src);
+    qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst);
+    qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx);
+    qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len);
+    qtest_writel(s, base + HACE_CRYPTO_CMD, cmd);
+
+    g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==,
+                    HACE_CRYPTO_ISR);
+    qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR);
+
+    qtest_memread(s, dst, out, t->len);
+}
+
+static void aspeed_test_crypto_direct(const void *data)
+{
+    const AspeedCryptoTest *c = data;
+    const CryptTest *t = &crypt_tests[c->index];
+    QTestState *s = qtest_init(c->machine);
+    uint8_t out[64];
+    uint8_t iv[16];
+    size_t iv_off;
+
+    g_assert_cmpuint(t->len, <=, sizeof(out));
+
+    /* Encrypt: ptext -> ctext */
+    crypt_run_direct(s, c->base, c->dram, t, true, out);
+    g_assert_cmpmem(out, t->len, t->ctext, t->len);
+
+    if (t->iv_out) {
+        iv_off = (t->cmd & HACE_CMD_DES_SELECT) ? 8 : 0;
+        qtest_memread(s, c->dram + CRYPT_OFF_CTX + iv_off, iv, t->ivlen);
+        g_assert_cmpmem(iv, t->ivlen, t->iv_out, t->ivlen);
+    }
+
+    /* Decrypt: ctext -> ptext */
+    crypt_run_direct(s, c->base, c->dram, t, false, out);
+    g_assert_cmpmem(out, t->len, t->ptext, t->len);
+
+    qtest_quit(s);
+}
+
+void aspeed_add_crypto_tests(const char *prefix, const char *machine,
+                             uint32_t base, uint64_t dram, uint32_t modes)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(crypt_tests); i++) {
+        g_autofree char *path = NULL;
+        AspeedCryptoTest *t;
+
+        if (!(modes & crypt_mode_flag(crypt_tests[i].cmd))) {
+            continue;
+        }
+
+        if (!qcrypto_cipher_supports(crypt_tests[i].alg,
+                                     crypt_tests[i].mode)) {
+            g_printerr("# skip unsupported %s\n", crypt_tests[i].name);
+            continue;
+        }
+
+        path = g_strdup_printf("%s/hace/crypto/%s", prefix,
+                               crypt_tests[i].name);
+        t = g_new0(AspeedCryptoTest, 1);
+        t->machine = machine;
+        t->base = base;
+        t->dram = dram;
+        t->index = i;
+        qtest_add_data_func_full(path, t, aspeed_test_crypto_direct, g_free);
+    }
+}
+
diff --git a/tests/qtest/aspeed_hace-test.c b/tests/qtest/aspeed_hace-test.c
index 38777020ca34..4cb4c475e9d8 100644
--- a/tests/qtest/aspeed_hace-test.c
+++ b/tests/qtest/aspeed_hace-test.c
@@ -229,6 +229,12 @@ int main(int argc, char **argv)
     qtest_add_func("ast2500/hace/sha256", test_sha256_ast2500);
     qtest_add_func("ast2500/hace/md5", test_md5_ast2500);
 
+    /*
+     * The AST2500 crypto engine uses direct access mode and supports ECB/CBC.
+     */
+    aspeed_add_crypto_tests("ast2500", "-machine ast2500-evb", 0x1e6e3000,
+                            0x80000000, CRYPT_MODE_ECB | CRYPT_MODE_CBC);
+
     qtest_add_func("ast2400/hace/addresses", test_addresses_ast2400);
     qtest_add_func("ast2400/hace/sha512", test_sha512_ast2400);
     qtest_add_func("ast2400/hace/sha256", test_sha256_ast2400);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 4d81857174ae..dcdbfe288931 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -392,9 +392,11 @@ if get_option('replication').allowed()
 endif
 
 qtests = {
-  'aspeed_hace-test': files('aspeed-hace-utils.c', 'aspeed_hace-test.c'),
+  'aspeed_hace-test': [files('aspeed-hace-utils.c', 'aspeed_hace-test.c'),
+                       crypto],
   'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'),
-  'ast2700-hace-test': files('aspeed-hace-utils.c', 'ast2700-hace-test.c'),
+  'ast2700-hace-test': [files('aspeed-hace-utils.c', 'ast2700-hace-test.c'),
+                        crypto],
   'ast2700-smc-test': files('aspeed-smc-utils.c', 'ast2700-smc-test.c'),
   'bios-tables-test': [io, 'boot-sector.c', 'acpi-utils.c', 'tpm-emu.c'],
   'cdrom-test': files('boot-sector.c'),
-- 
2.55.0



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

* [PULL 70/83] hw/misc/aspeed_hace: Support scatter-gather mode for the crypto command
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (68 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 69/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2500 Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 71/83] hw/misc/aspeed_hace: Support the CTR " Cédric Le Goater
                   ` (13 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST2600 and later crypto engines drive the source and destination
through scatter-gather lists (HACE10[18]/[19]) rather than the single
contiguous buffers used by the AST2500 direct access mode. Each SG list
entry is a length word (SG_LIST_LEN_LAST marks the final entry) followed
by a DRAM address, matching the hash engine layout.

Add a crypt_prepare_sg() helper that gathers the source into / scatters
the destination out of the bounce buffer by walking the SG list, and
select it or the existing crypt_prepare_direct() from do_crypt_operation
based on HACE10[18], mirroring the hash engine's direct/scatter-gather
dispatch.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-4-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_hace.c | 70 +++++++++++++++++++++++++++++++++++++++----
 1 file changed, 64 insertions(+), 6 deletions(-)

diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index 0f35d74137b1..646ff504762d 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -608,13 +608,58 @@ static bool crypt_prepare_direct(AspeedHACEState *s, uint64_t addr,
 }
 
 /*
- * Perform an AES/DES/3DES ECB/CBC operation in direct access mode: the source
- * and destination are single contiguous buffers (HACE00/HACE04) and the IV/key
- * come from the context buffer (HACE08). For CBC the resulting chaining IV is
- * written back to the context buffer so the driver can continue the chain.
+ * Scatter-gather mode: the source/destination register points at an SG list
+ * whose entries are a length word (SG_LIST_LEN_LAST flags the final entry)
+ * followed by a DRAM address, matching the hash engine layout. Gather @len
+ * bytes into @buf, or scatter @buf back out when @to_dram is true.
+ * Returns true on success.
+ */
+static bool crypt_prepare_sg(AspeedHACEState *s, uint64_t addr,
+                             uint8_t *buf, uint32_t len, bool to_dram)
+{
+    uint32_t copied = 0;
+    uint32_t sg_addr;
+    uint32_t sg_len;
+    uint32_t entry;
+    int i;
+
+    for (i = 0; i < ASPEED_HACE_MAX_SG && copied < len; i++) {
+        entry = address_space_ldl_le(&s->dram_as, addr,
+                                     MEMTXATTRS_UNSPECIFIED, NULL);
+        sg_addr = address_space_ldl_le(&s->dram_as, addr + SG_LIST_LEN_SIZE,
+                                       MEMTXATTRS_UNSPECIFIED, NULL);
+        sg_len = entry & SG_LIST_LEN_MASK;
+
+        sg_addr &= SG_LIST_ADDR_MASK;
+        addr += SG_LIST_ENTRY_SIZE;
+
+        if (sg_len > len - copied) {
+            sg_len = len - copied;
+        }
+        if (address_space_rw(&s->dram_as, sg_addr, MEMTXATTRS_UNSPECIFIED,
+                             buf + copied, sg_len, to_dram)) {
+            return false;
+        }
+        copied += sg_len;
+
+        if (entry & SG_LIST_LEN_LAST) {
+            break;
+        }
+    }
+
+    return copied == len;
+}
+
+/*
+ * Perform an AES/DES/3DES ECB/CBC operation. The source and destination are
+ * either single contiguous buffers (direct access mode) or scatter-gather
+ * lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the IV/key come from
+ * the context buffer (HACE08). For CBC the resulting chaining IV is written
+ * back to the context buffer so the driver can continue the chain.
  */
 static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
 {
+    bool sg_mode = cmd & CRYPT_CMD_SRC_SG_CTRL;
     uint32_t len = s->regs[R_CRYPT_DATA_LEN];
     bool encrypt = cmd & CRYPT_CMD_ENCRYPT;
     g_autoptr(QCryptoCipher) cipher = NULL;
@@ -631,6 +676,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     size_t iv_offset;
     size_t blocklen;
     size_t keylen;
+    bool status;
 
     if (len == 0) {
         return;
@@ -684,8 +730,14 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     src_buf = g_malloc0(len);
     dst_buf = g_malloc0(len);
 
+    /* Gather the source into the bounce buffer, per the selected mode. */
     src_addr = s->regs[R_CRYPT_SRC];
-    if (!crypt_prepare_direct(s, src_addr, src_buf, len, false)) {
+    if (sg_mode) {
+        status = crypt_prepare_sg(s, src_addr, src_buf, len, false);
+    } else {
+        status = crypt_prepare_direct(s, src_addr, src_buf, len, false);
+    }
+    if (!status) {
         qemu_log_mask(LOG_GUEST_ERROR,
                       "%s: Failed to read src, addr=0x%" HWADDR_PRIx "\n",
                       __func__, src_addr);
@@ -714,8 +766,14 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
         }
     }
 
+    /* Scatter the result back out, per the selected mode. */
     dst_addr = s->regs[R_CRYPT_DEST];
-    if (!crypt_prepare_direct(s, dst_addr, dst_buf, len, true)) {
+    if (sg_mode) {
+        status = crypt_prepare_sg(s, dst_addr, dst_buf, len, true);
+    } else {
+        status = crypt_prepare_direct(s, dst_addr, dst_buf, len, true);
+    }
+    if (!status) {
         qemu_log_mask(LOG_GUEST_ERROR,
                       "%s: Failed to write dst, addr=0x%" HWADDR_PRIx "\n",
                       __func__, dst_addr);
-- 
2.55.0



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

* [PULL 71/83] hw/misc/aspeed_hace: Support the CTR mode for the crypto command
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (69 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 70/83] hw/misc/aspeed_hace: Support scatter-gather mode for the crypto command Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 72/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2600 Cédric Le Goater
                   ` (12 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST2600, AST1030 and later crypto engines add AES/DES/3DES CTR mode
(HACE10[6:4] = 0b100) on top of the ECB/CBC modes shared with the
AST2500. Decode the CTR selection, round the working buffers up to a
whole block so the stream-like final block is still processed a block at
a time, and write the counter advanced by the number of blocks consumed
back to the context buffer so the driver can continue across requests.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-5-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_hace.c | 50 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 45 insertions(+), 5 deletions(-)

diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index 646ff504762d..d84f7f9cec1a 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -41,6 +41,7 @@
 #define  CRYPT_CMD_OP_MODE_MASK     (0x7 << 4)
 #define  CRYPT_CMD_ECB              (0x0 << 4)
 #define  CRYPT_CMD_CBC              (0x1 << 4)
+#define  CRYPT_CMD_CTR              (0x4 << 4)
 /* AES key length HACE10[3:2] */
 #define  CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
 #define  CRYPT_CMD_AES256           (0x2 << 2)
@@ -587,6 +588,9 @@ static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg,
     case CRYPT_CMD_CBC:
         *mode = QCRYPTO_CIPHER_MODE_CBC;
         break;
+    case CRYPT_CMD_CTR:
+        *mode = QCRYPTO_CIPHER_MODE_CTR;
+        break;
     default:
         return false;
     }
@@ -650,6 +654,22 @@ static bool crypt_prepare_sg(AspeedHACEState *s, uint64_t addr,
     return copied == len;
 }
 
+/*
+ * Add @add to the big-endian counter block @ctr (@len bytes) in place, so the
+ * CTR mode counter can be advanced by the number of blocks just consumed.
+ */
+static void crypt_be_add(uint8_t *ctr, size_t len, uint64_t add)
+{
+    size_t i = len;
+
+    while (i > 0 && add) {
+        i--;
+        add += ctr[i];
+        ctr[i] = add & 0xff;
+        add >>= 8;
+    }
+}
+
 /*
  * Perform an AES/DES/3DES ECB/CBC operation. The source and destination are
  * either single contiguous buffers (direct access mode) or scatter-gather
@@ -675,6 +695,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     uint64_t dst_addr;
     size_t iv_offset;
     size_t blocklen;
+    size_t buf_len;
     size_t keylen;
     bool status;
 
@@ -727,8 +748,14 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
         return;
     }
 
-    src_buf = g_malloc0(len);
-    dst_buf = g_malloc0(len);
+    /*
+     * Round the working buffers up to a whole block. Block modes are already
+     * block-aligned; the stream-like CTR mode may leave a partial final block
+     * that the engine still processes a full block at a time.
+     */
+    buf_len = QEMU_ALIGN_UP(len, blocklen);
+    src_buf = g_malloc0(buf_len);
+    dst_buf = g_malloc0(buf_len);
 
     /* Gather the source into the bounce buffer, per the selected mode. */
     src_addr = s->regs[R_CRYPT_SRC];
@@ -749,7 +776,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     }
 
     if (encrypt) {
-        if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, len,
+        if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, buf_len,
                                    &local_err) < 0) {
             qemu_log_mask(LOG_GUEST_ERROR, "%s: encrypt failed: %s\n",
                           __func__, error_get_pretty(local_err));
@@ -757,7 +784,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
             return;
         }
     } else {
-        if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, len,
+        if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, buf_len,
                                    &local_err) < 0) {
             qemu_log_mask(LOG_GUEST_ERROR, "%s: decrypt failed: %s\n",
                           __func__, error_get_pretty(local_err));
@@ -790,13 +817,26 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
          * output when encrypting, or of the input when decrypting. Write it
          * back as the IV for the next request.
          */
-        next_iv = (encrypt ? dst_buf : src_buf) + len - blocklen;
+        next_iv = (encrypt ? dst_buf : src_buf) + buf_len - blocklen;
         if (address_space_write(&s->dram_as, ctx_addr + iv_offset,
                                 MEMTXATTRS_UNSPECIFIED, next_iv, blocklen)) {
             qemu_log_mask(LOG_GUEST_ERROR,
                           "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
                           __func__, ctx_addr + iv_offset);
         }
+    } else if (mode == QCRYPTO_CIPHER_MODE_CTR) {
+        /*
+         * CTR chains on the counter, which advances by one per block. Add the
+         * number of blocks processed (buf_len / blocklen) and write it back.
+         */
+        crypt_be_add(ctx + iv_offset, blocklen, buf_len / blocklen);
+        if (address_space_write(&s->dram_as, ctx_addr + iv_offset,
+                                MEMTXATTRS_UNSPECIFIED, ctx + iv_offset,
+                                blocklen)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
+                          __func__, ctx_addr + iv_offset);
+        }
     }
 }
 
-- 
2.55.0



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

* [PULL 72/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2600
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (70 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 71/83] hw/misc/aspeed_hace: Support the CTR " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 73/83] tests/qtest/aspeed-hace: Test the crypto command on the AST1030 Cédric Le Goater
                   ` (11 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Extend the crypto known-answer tests to cover the AST2600 crypto engine,
which drives the source and destination through scatter-gather lists and
adds CTR mode on top of the ECB/CBC modes shared with the AST2500.

Add a scatter-gather runner that describes each buffer with three
non-adjacent fragments to exercise the gather/scatter path, add
AES/DES/3DES CTR vectors (verifying the counter written back to the
context buffer), and give aspeed_add_crypto_tests() a mode mask and a
scatter-gather flag so each SoC registers exactly the modes and transfer
method it supports. Register the AST2600 with ECB/CBC/CTR in
scatter-gather mode.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-6-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-hace-utils.h |   7 +-
 tests/qtest/aspeed-hace-utils.c | 217 +++++++++++++++++++++++++++++++-
 tests/qtest/aspeed_hace-test.c  |   8 +-
 3 files changed, 224 insertions(+), 8 deletions(-)

diff --git a/tests/qtest/aspeed-hace-utils.h b/tests/qtest/aspeed-hace-utils.h
index 13feaa61e446..82b0b3f93d77 100644
--- a/tests/qtest/aspeed-hace-utils.h
+++ b/tests/qtest/aspeed-hace-utils.h
@@ -85,15 +85,18 @@ void aspeed_test_addresses(const char *machine, const uint32_t base,
 enum {
     CRYPT_MODE_ECB = 1 << 0,
     CRYPT_MODE_CBC = 1 << 1,
+    CRYPT_MODE_CTR = 1 << 2,
 };
 
 /*
  * Register the crypto known-answer tests that @modes selects (a mask of
  * CRYPT_MODE_*) for the given machine. Each test is named
- * "<prefix>/hace/crypto/<mode>".
+ * "<prefix>/hace/crypto/<mode>". @sg selects scatter-gather mode (used by the
+ * AST2600 and later) instead of the AST2500 direct access mode.
  */
 void aspeed_add_crypto_tests(const char *prefix, const char *machine,
-                             uint32_t base, uint64_t dram, uint32_t modes);
+                             uint32_t base, uint64_t dram, uint32_t modes,
+                             bool sg);
 
 #endif /* TESTS_ASPEED_HACE_UTILS_H */
 
diff --git a/tests/qtest/aspeed-hace-utils.c b/tests/qtest/aspeed-hace-utils.c
index 0355dd47af15..f582c88ef515 100644
--- a/tests/qtest/aspeed-hace-utils.c
+++ b/tests/qtest/aspeed-hace-utils.c
@@ -665,6 +665,7 @@ void aspeed_test_addresses(const char *machine, const uint32_t base,
 #define HACE_CMD_OP_MODE_MASK    (0x7 << 4)
 #define HACE_CMD_ECB             (0x0 << 4)
 #define HACE_CMD_CBC             (0x1 << 4)
+#define HACE_CMD_CTR             (0x4 << 4)
 #define HACE_CMD_AES128          (0x0 << 2)
 
 /* Context buffer layout: IV (DES at +8), key at +0x10 */
@@ -750,6 +751,49 @@ static const uint8_t tdes_cbc_ptext[8] = {
 static const uint8_t tdes_cbc_ctext[8] = {
     0x0e, 0x2d, 0xb6, 0x97, 0x3c, 0x56, 0x33, 0xf4 };
 
+/* aes_ctr_tv_template[0] (NIST SP800-38A F.5.1), first block */
+static const uint8_t aes128_ctr_key[16] = {
+    0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+    0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
+static const uint8_t aes128_ctr_iv[16] = {
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
+static const uint8_t aes128_ctr_ptext[16] = {
+    0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+    0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a };
+static const uint8_t aes128_ctr_ctext[16] = {
+    0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+    0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce };
+static const uint8_t aes128_ctr_ivout[16] = {
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xff, 0x00 };
+
+/* des_ctr_tv_template[0] (Crypto++), first block */
+static const uint8_t des_ctr_key[8] = {
+    0xc9, 0x83, 0xa6, 0xc9, 0xec, 0x0f, 0x32, 0x55 };
+static const uint8_t des_ctr_iv[8] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd };
+static const uint8_t des_ctr_ptext[8] = {
+    0x50, 0xb9, 0x22, 0xae, 0x17, 0x80, 0x0c, 0x75 };
+static const uint8_t des_ctr_ctext[8] = {
+    0x2f, 0x96, 0x06, 0x0f, 0x50, 0xc9, 0x68, 0x03 };
+static const uint8_t des_ctr_ivout[8] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe };
+
+/* des3_ede_ctr_tv_template[0] (Crypto++), first block */
+static const uint8_t tdes_ctr_key[24] = {
+    0x9c, 0xd6, 0xf3, 0x9c, 0xb9, 0x5a, 0x67, 0x00,
+    0x5a, 0x67, 0x00, 0x2d, 0xce, 0xeb, 0x2d, 0xce,
+    0xeb, 0xb4, 0x51, 0x72, 0xb4, 0x51, 0x72, 0x1f };
+static const uint8_t tdes_ctr_iv[8] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+static const uint8_t tdes_ctr_ptext[8] = {
+    0x05, 0xec, 0x77, 0xfb, 0x42, 0xd5, 0x59, 0x20 };
+static const uint8_t tdes_ctr_ctext[8] = {
+    0x07, 0xc2, 0x08, 0x20, 0x72, 0x1f, 0x49, 0xef };
+static const uint8_t tdes_ctr_ivout[8] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
 typedef struct CryptTest {
     QCryptoCipherMode mode;
     QCryptoCipherAlgo alg;
@@ -841,12 +885,65 @@ static const CryptTest crypt_tests[] = {
         .ctext = tdes_cbc_ctext,
         .len = sizeof(tdes_cbc_ptext),
     },
+    {
+        .name = "aes128-ctr",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_CTR,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_CTR,
+        .key = aes128_ctr_key,
+        .keylen = sizeof(aes128_ctr_key),
+        .iv = aes128_ctr_iv,
+        .ivlen = sizeof(aes128_ctr_iv),
+        .ptext = aes128_ctr_ptext,
+        .ctext = aes128_ctr_ctext,
+        .iv_out = aes128_ctr_ivout,
+        .len = sizeof(aes128_ctr_ptext),
+    },
+    {
+        .name = "des-ctr",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_CTR,
+        .alg = QCRYPTO_CIPHER_ALGO_DES,
+        .mode = QCRYPTO_CIPHER_MODE_CTR,
+        .key = des_ctr_key,
+        .keylen = sizeof(des_ctr_key),
+        .iv = des_ctr_iv,
+        .ivlen = sizeof(des_ctr_iv),
+        .ptext = des_ctr_ptext,
+        .ctext = des_ctr_ctext,
+        .iv_out = des_ctr_ivout,
+        .len = sizeof(des_ctr_ptext),
+    },
+    {
+        .name = "des3_ede-ctr",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_CTR,
+        .alg = QCRYPTO_CIPHER_ALGO_3DES,
+        .mode = QCRYPTO_CIPHER_MODE_CTR,
+        .key = tdes_ctr_key,
+        .keylen = sizeof(tdes_ctr_key),
+        .iv = tdes_ctr_iv,
+        .ivlen = sizeof(tdes_ctr_iv),
+        .ptext = tdes_ctr_ptext,
+        .ctext = tdes_ctr_ctext,
+        .iv_out = tdes_ctr_ivout,
+        .len = sizeof(tdes_ctr_ptext),
+    },
 };
 
 /* DRAM offsets for the crypto test source, destination and context buffers. */
 #define CRYPT_OFF_SRC   0x10000
 #define CRYPT_OFF_DST   0x20000
 #define CRYPT_OFF_CTX   0x30000
+/* Scatter-gather list offsets (each list has CRYPT_SG_FRAGS entries). */
+#define CRYPT_OFF_SRC_SG  0x40000
+#define CRYPT_OFF_DST_SG  0x50000
+/*
+ * The scatter-gather tests split each buffer into CRYPT_SG_FRAGS fragments,
+ * each placed CRYPT_SG_FRAG_STRIDE apart so the fragments never abut. The gaps
+ * make the test fail if the engine ignores the list and reads one contiguous
+ * block.
+ */
+#define CRYPT_SG_FRAGS         3
+#define CRYPT_SG_FRAG_STRIDE   0x1000
 
 /* Describes one registered crypto test (qtest_add_data_func() data pointer). */
 typedef struct AspeedCryptoTest {
@@ -854,6 +951,7 @@ typedef struct AspeedCryptoTest {
     uint64_t dram;
     uint32_t base;
     int index;
+    bool sg;
 } AspeedCryptoTest;
 
 /* Map a command's operation mode (HACE10[6:4]) to a CRYPT_MODE_* flag. */
@@ -864,6 +962,8 @@ static uint32_t crypt_mode_flag(uint32_t cmd)
         return CRYPT_MODE_ECB;
     case HACE_CMD_CBC:
         return CRYPT_MODE_CBC;
+    case HACE_CMD_CTR:
+        return CRYPT_MODE_CTR;
     default:
         return 0;
     }
@@ -912,7 +1012,104 @@ static void crypt_run_direct(QTestState *s, uint32_t base, uint64_t dram,
     qtest_memread(s, dst, out, t->len);
 }
 
-static void aspeed_test_crypto_direct(const void *data)
+/*
+ * Byte range [*frag_off, *frag_off + *frag_len) of fragment @index when an
+ * @len-byte buffer is split into CRYPT_SG_FRAGS pieces; the last piece takes
+ * the remainder of an uneven split.
+ */
+static void crypt_frag_range(uint32_t len, int index,
+                             uint32_t *frag_off, uint32_t *frag_len)
+{
+    uint32_t base = len / CRYPT_SG_FRAGS;
+
+    *frag_off = base * index;
+    *frag_len = (index == CRYPT_SG_FRAGS - 1) ? len - *frag_off : base;
+}
+
+/*
+ * Scatter [in, len) across CRYPT_SG_FRAGS buffers based at @base_off and spaced
+ * CRYPT_SG_FRAG_STRIDE apart, then build the SG list describing them at @list.
+ * When @in is NULL only the list is built (used for the destination, which the
+ * engine fills in).
+ */
+static void crypt_make_sg(QTestState *s, uint64_t dram, uint32_t base_off,
+                          uint64_t list, const uint8_t *in, uint32_t len)
+{
+    struct AspeedSgList sg[CRYPT_SG_FRAGS];
+    uint32_t frag_off;
+    uint32_t frag_len;
+    uint64_t buf;
+    int i;
+
+    for (i = 0; i < CRYPT_SG_FRAGS; i++) {
+        crypt_frag_range(len, i, &frag_off, &frag_len);
+        buf = dram + base_off + i * CRYPT_SG_FRAG_STRIDE;
+
+        if (in) {
+            qtest_memwrite(s, buf, in + frag_off, frag_len);
+        }
+        sg[i].len = cpu_to_le32(frag_len | (i == CRYPT_SG_FRAGS - 1 ?
+                                            SG_LIST_LEN_LAST : 0));
+        sg[i].addr = cpu_to_le32((uint32_t)buf);
+    }
+
+    qtest_memwrite(s, list, sg, sizeof(sg));
+}
+
+/* Gather a scatter-gathered result back from the CRYPT_SG_FRAGS buffers. */
+static void crypt_gather_sg(QTestState *s, uint64_t dram, uint32_t base_off,
+                            uint8_t *out, uint32_t len)
+{
+    uint32_t frag_off;
+    uint32_t frag_len;
+    int i;
+
+    for (i = 0; i < CRYPT_SG_FRAGS; i++) {
+        crypt_frag_range(len, i, &frag_off, &frag_len);
+        qtest_memread(s, dram + base_off + i * CRYPT_SG_FRAG_STRIDE,
+                      out + frag_off, frag_len);
+    }
+}
+
+/*
+ * Run one block-cipher (ECB/CBC/CTR) operation in scatter-gather mode and read
+ * back the result. The source and destination are each split across
+ * CRYPT_SG_FRAGS non-adjacent DRAM buffers described by an SG list; the gaps
+ * ensure the test fails if the engine ignores the list and reads one
+ * contiguous block.
+ */
+static void crypt_run_sg(QTestState *s, uint32_t base, uint64_t dram,
+                         const CryptTest *t, bool encrypt, uint8_t *out)
+{
+    const uint8_t *in = encrypt ? t->ptext : t->ctext;
+    uint64_t src_sg = dram + CRYPT_OFF_SRC_SG;
+    uint64_t dst_sg = dram + CRYPT_OFF_DST_SG;
+    uint64_t ctx = dram + CRYPT_OFF_CTX;
+    uint32_t cmd = t->cmd | HACE_CMD_ISR_EN | HACE_CMD_SRC_SG_CTRL |
+                   HACE_CMD_DST_SG_CTRL;
+
+    if (encrypt) {
+        cmd |= HACE_CMD_ENCRYPT;
+    }
+
+    crypt_write_ctx(s, ctx, t);
+    crypt_make_sg(s, dram, CRYPT_OFF_SRC, src_sg, in, t->len);
+    crypt_make_sg(s, dram, CRYPT_OFF_DST, dst_sg, NULL, t->len);
+
+    qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src_sg);
+    qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst_sg);
+    qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx);
+    qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len);
+    qtest_writel(s, base + HACE_CRYPTO_CMD, cmd);
+
+    g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==,
+                    HACE_CRYPTO_ISR);
+    qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR);
+
+    crypt_gather_sg(s, dram, CRYPT_OFF_DST, out, t->len);
+}
+
+static void aspeed_test_crypto(const void *data)
 {
     const AspeedCryptoTest *c = data;
     const CryptTest *t = &crypt_tests[c->index];
@@ -924,7 +1121,11 @@ static void aspeed_test_crypto_direct(const void *data)
     g_assert_cmpuint(t->len, <=, sizeof(out));
 
     /* Encrypt: ptext -> ctext */
-    crypt_run_direct(s, c->base, c->dram, t, true, out);
+    if (c->sg) {
+        crypt_run_sg(s, c->base, c->dram, t, true, out);
+    } else {
+        crypt_run_direct(s, c->base, c->dram, t, true, out);
+    }
     g_assert_cmpmem(out, t->len, t->ctext, t->len);
 
     if (t->iv_out) {
@@ -934,14 +1135,19 @@ static void aspeed_test_crypto_direct(const void *data)
     }
 
     /* Decrypt: ctext -> ptext */
-    crypt_run_direct(s, c->base, c->dram, t, false, out);
+    if (c->sg) {
+        crypt_run_sg(s, c->base, c->dram, t, false, out);
+    } else {
+        crypt_run_direct(s, c->base, c->dram, t, false, out);
+    }
     g_assert_cmpmem(out, t->len, t->ptext, t->len);
 
     qtest_quit(s);
 }
 
 void aspeed_add_crypto_tests(const char *prefix, const char *machine,
-                             uint32_t base, uint64_t dram, uint32_t modes)
+                             uint32_t base, uint64_t dram, uint32_t modes,
+                             bool sg)
 {
     int i;
 
@@ -966,7 +1172,8 @@ void aspeed_add_crypto_tests(const char *prefix, const char *machine,
         t->base = base;
         t->dram = dram;
         t->index = i;
-        qtest_add_data_func_full(path, t, aspeed_test_crypto_direct, g_free);
+        t->sg = sg;
+        qtest_add_data_func_full(path, t, aspeed_test_crypto, g_free);
     }
 }
 
diff --git a/tests/qtest/aspeed_hace-test.c b/tests/qtest/aspeed_hace-test.c
index 4cb4c475e9d8..61a3e3feb514 100644
--- a/tests/qtest/aspeed_hace-test.c
+++ b/tests/qtest/aspeed_hace-test.c
@@ -224,6 +224,12 @@ int main(int argc, char **argv)
     qtest_add_func("ast2600/hace/sha384_accum", test_sha384_accum_ast2600);
     qtest_add_func("ast2600/hace/sha256_accum", test_sha256_accum_ast2600);
 
+    /* The AST2600 crypto engine uses scatter-gather mode and adds CTR. */
+    aspeed_add_crypto_tests("ast2600", "-machine ast2600-evb", 0x1e6d0000,
+                            0x80000000,
+                            CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR,
+                            true);
+
     qtest_add_func("ast2500/hace/addresses", test_addresses_ast2500);
     qtest_add_func("ast2500/hace/sha512", test_sha512_ast2500);
     qtest_add_func("ast2500/hace/sha256", test_sha256_ast2500);
@@ -233,7 +239,7 @@ int main(int argc, char **argv)
      * The AST2500 crypto engine uses direct access mode and supports ECB/CBC.
      */
     aspeed_add_crypto_tests("ast2500", "-machine ast2500-evb", 0x1e6e3000,
-                            0x80000000, CRYPT_MODE_ECB | CRYPT_MODE_CBC);
+                            0x80000000, CRYPT_MODE_ECB | CRYPT_MODE_CBC, false);
 
     qtest_add_func("ast2400/hace/addresses", test_addresses_ast2400);
     qtest_add_func("ast2400/hace/sha512", test_sha512_ast2400);
-- 
2.55.0



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

* [PULL 73/83] tests/qtest/aspeed-hace: Test the crypto command on the AST1030
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (71 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 72/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2600 Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 74/83] crypto/cipher: Add GCM to QCryptoCipherMode Cédric Le Goater
                   ` (10 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST1030 reuses the AST2600 crypto engine, so it drives the same
scatter-gather transfers and supports the same ECB/CBC/CTR modes. Reuse
the crypto known-answer tests to cover it, registering the AST1030 with
the same modes and scatter-gather flag as the AST2600.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-7-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed_hace-test.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/tests/qtest/aspeed_hace-test.c b/tests/qtest/aspeed_hace-test.c
index 61a3e3feb514..42130df1e239 100644
--- a/tests/qtest/aspeed_hace-test.c
+++ b/tests/qtest/aspeed_hace-test.c
@@ -210,6 +210,12 @@ int main(int argc, char **argv)
     qtest_add_func("ast1030/hace/sha384_accum", test_sha384_accum_ast1030);
     qtest_add_func("ast1030/hace/sha256_accum", test_sha256_accum_ast1030);
 
+    /* The AST1030 reuses the AST2600 crypto engine (scatter-gather, CTR). */
+    aspeed_add_crypto_tests("ast1030", "-machine ast1030-evb", 0x7e6d0000,
+                            0x00000000,
+                            CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR,
+                            true);
+
     qtest_add_func("ast2600/hace/addresses", test_addresses_ast2600);
     qtest_add_func("ast2600/hace/sha512", test_sha512_ast2600);
     qtest_add_func("ast2600/hace/sha384", test_sha384_ast2600);
-- 
2.55.0



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

* [PULL 74/83] crypto/cipher: Add GCM to QCryptoCipherMode
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (72 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 73/83] tests/qtest/aspeed-hace: Test the crypto command on the AST1030 Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 75/83] crypto/cipher: Add setaad/gettag for AEAD modes Cédric Le Goater
                   ` (9 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Daniel P. Berrangé, Markus Armbruster,
	Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Introduce the GCM cipher mode so authenticated encryption can be built
on top of the existing qcrypto_cipher API. GCM is an IV-based mode, so
register it in mode_need_iv. No backend advertises it yet, so it stays
unsupported until a backend and the AAD/tag helpers are added in the
following patches.

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

diff --git a/qapi/crypto.json b/qapi/crypto.json
index 2b55befef977..6e3a98ff68b3 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -121,10 +121,12 @@
 #
 # @ctr: Counter (Since 2.8)
 #
+# @gcm: Galois/Counter Mode (Since 11.2)
+#
 # Since: 2.6
 ##
 { 'enum': 'QCryptoCipherMode',
-  'data': ['ecb', 'cbc', 'xts', 'ctr']}
+  'data': ['ecb', 'cbc', 'xts', 'ctr', 'gcm']}
 
 ##
 # @QCryptoIVGenAlgo:
diff --git a/crypto/cipher.c b/crypto/cipher.c
index 229710f76b46..64c2339d1798 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -66,6 +66,7 @@ static const bool mode_need_iv[QCRYPTO_CIPHER_MODE__MAX] = {
     [QCRYPTO_CIPHER_MODE_CBC] = true,
     [QCRYPTO_CIPHER_MODE_XTS] = true,
     [QCRYPTO_CIPHER_MODE_CTR] = true,
+    [QCRYPTO_CIPHER_MODE_GCM] = true,
 };
 
 
-- 
2.55.0



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

* [PULL 75/83] crypto/cipher: Add setaad/gettag for AEAD modes
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (73 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 74/83] crypto/cipher: Add GCM to QCryptoCipherMode Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 76/83] crypto/cipher-gcrypt: Implement AES-GCM Cédric Le Goater
                   ` (8 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel
  Cc: Jamin Lin, Daniel P. Berrangé, Kane Chen,
	Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

AEAD modes such as GCM authenticate optional associated data (AAD) and
produce an authentication tag, which the block-cipher encrypt/decrypt
interface cannot express. Add qcrypto_cipher_setaad() and
qcrypto_cipher_gettag() plus the matching backend driver hooks. The
generic front-end reports an error when the selected mode's driver does
not implement them, so calling them on a non-AEAD mode fails cleanly.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-9-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 crypto/cipherpriv.h     |  8 ++++++++
 include/crypto/cipher.h | 36 ++++++++++++++++++++++++++++++++++++
 crypto/cipher.c         | 31 +++++++++++++++++++++++++++++++
 3 files changed, 75 insertions(+)

diff --git a/crypto/cipherpriv.h b/crypto/cipherpriv.h
index 64737ce96153..c4f995f36910 100644
--- a/crypto/cipherpriv.h
+++ b/crypto/cipherpriv.h
@@ -34,6 +34,14 @@ struct QCryptoCipherDriver {
                         const uint8_t *iv, size_t niv,
                         Error **errp);
 
+    int (*cipher_setaad)(QCryptoCipher *cipher,
+                         const uint8_t *aad, size_t len,
+                         Error **errp);
+
+    int (*cipher_gettag)(QCryptoCipher *cipher,
+                         uint8_t *tag, size_t len,
+                         Error **errp);
+
     void (*cipher_free)(QCryptoCipher *cipher);
 };
 
diff --git a/include/crypto/cipher.h b/include/crypto/cipher.h
index 92939310ef09..2e361411b949 100644
--- a/include/crypto/cipher.h
+++ b/include/crypto/cipher.h
@@ -235,4 +235,40 @@ int qcrypto_cipher_setiv(QCryptoCipher *cipher,
                          const uint8_t *iv, size_t niv,
                          Error **errp);
 
+/**
+ * qcrypto_cipher_setaad:
+ * @cipher: the cipher object
+ * @aad: the associated data to authenticate
+ * @len: the length of @aad
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * For AEAD modes such as GCM, feed the associated data (AAD) that is
+ * authenticated but not encrypted. It must be called after
+ * qcrypto_cipher_setiv() and before the first encrypt/decrypt call. It is
+ * an error to call this on a mode that is not an AEAD mode.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_cipher_setaad(QCryptoCipher *cipher,
+                          const uint8_t *aad, size_t len,
+                          Error **errp);
+
+/**
+ * qcrypto_cipher_gettag:
+ * @cipher: the cipher object
+ * @tag: buffer to receive the authentication tag
+ * @len: the length of @tag
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * For AEAD modes such as GCM, read back the authentication tag computed
+ * over the associated data and the message. It must be called after the
+ * encrypt/decrypt operation. It is an error to call this on a mode that is
+ * not an AEAD mode.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_cipher_gettag(QCryptoCipher *cipher,
+                          uint8_t *tag, size_t len,
+                          Error **errp);
+
 #endif /* QCRYPTO_CIPHER_H */
diff --git a/crypto/cipher.c b/crypto/cipher.c
index 64c2339d1798..1dc912b2178e 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -205,6 +205,37 @@ int qcrypto_cipher_setiv(QCryptoCipher *cipher,
 }
 
 
+int qcrypto_cipher_setaad(QCryptoCipher *cipher,
+                          const uint8_t *aad, size_t len,
+                          Error **errp)
+{
+    const QCryptoCipherDriver *drv = cipher->driver;
+
+    if (!drv->cipher_setaad) {
+        error_setg(errp, "The cipher mode does not support associated data");
+        return -1;
+    }
+
+    return drv->cipher_setaad(cipher, aad, len, errp);
+}
+
+
+int qcrypto_cipher_gettag(QCryptoCipher *cipher,
+                          uint8_t *tag, size_t len,
+                          Error **errp)
+{
+    const QCryptoCipherDriver *drv = cipher->driver;
+
+    if (!drv->cipher_gettag) {
+        error_setg(errp,
+                   "The cipher mode does not produce an authentication tag");
+        return -1;
+    }
+
+    return drv->cipher_gettag(cipher, tag, len, errp);
+}
+
+
 void qcrypto_cipher_free(QCryptoCipher *cipher)
 {
     if (cipher) {
-- 
2.55.0



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

* [PULL 76/83] crypto/cipher-gcrypt: Implement AES-GCM
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (74 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 75/83] crypto/cipher: Add setaad/gettag for AEAD modes Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 77/83] crypto/cipher-nettle: " Cédric Le Goater
                   ` (7 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 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>

Map QCRYPTO_CIPHER_MODE_GCM to GCRY_CIPHER_MODE_GCM and advertise it in
qcrypto_cipher_supports() for 128-bit block ciphers. Add a GCM driver
whose setiv accepts the (typically 96-bit) nonce, whose encrypt/decrypt
do not require block-aligned lengths, and which implements setaad via
gcry_cipher_authenticate() and gettag via gcry_cipher_gettag().

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

diff --git a/crypto/cipher-gcrypt.c.inc b/crypto/cipher-gcrypt.c.inc
index 12eb9ddb5a6f..fce09a3c7717 100644
--- a/crypto/cipher-gcrypt.c.inc
+++ b/crypto/cipher-gcrypt.c.inc
@@ -65,6 +65,8 @@ static int qcrypto_cipher_mode_to_gcry_mode(QCryptoCipherMode mode)
         return GCRY_CIPHER_MODE_CBC;
     case QCRYPTO_CIPHER_MODE_CTR:
         return GCRY_CIPHER_MODE_CTR;
+    case QCRYPTO_CIPHER_MODE_GCM:
+        return GCRY_CIPHER_MODE_GCM;
     default:
         return GCRY_CIPHER_MODE_NONE;
     }
@@ -104,6 +106,10 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgo alg,
     case QCRYPTO_CIPHER_MODE_XTS:
     case QCRYPTO_CIPHER_MODE_CTR:
         return true;
+    case QCRYPTO_CIPHER_MODE_GCM:
+        /* GCM requires a 128-bit block cipher. */
+        return gcry_cipher_get_algo_blklen(
+                   qcrypto_cipher_alg_to_gcry_alg(alg)) == 16;
     default:
         return false;
     }
@@ -228,6 +234,99 @@ static const struct QCryptoCipherDriver qcrypto_gcrypt_ctr_driver = {
     .cipher_free = qcrypto_gcrypt_ctx_free,
 };
 
+/*
+ * GCM is an AEAD stream mode: the IV/nonce need not match the block size,
+ * the message length need not be a multiple of the block size, associated
+ * data is fed with gcry_cipher_authenticate() and the authentication tag is
+ * read back with gcry_cipher_gettag().
+ */
+static int qcrypto_gcrypt_gcm_setiv(QCryptoCipher *cipher,
+                                    const uint8_t *iv, size_t niv,
+                                    Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    gcry_cipher_reset(ctx->handle);
+    err = gcry_cipher_setiv(ctx->handle, iv, niv);
+    if (err != 0) {
+        error_setg(errp, "Cannot set IV: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_setaad(QCryptoCipher *cipher,
+                                     const uint8_t *aad, size_t len,
+                                     Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_authenticate(ctx->handle, aad, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot set AAD: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_encrypt(QCryptoCipher *cipher, const void *in,
+                                      void *out, size_t len, Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_encrypt(ctx->handle, out, len, in, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot encrypt data: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_decrypt(QCryptoCipher *cipher, const void *in,
+                                      void *out, size_t len, Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_decrypt(ctx->handle, out, len, in, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot decrypt data: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_gettag(QCryptoCipher *cipher,
+                                     uint8_t *tag, size_t len, Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_gettag(ctx->handle, tag, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot get tag: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static const struct QCryptoCipherDriver qcrypto_gcrypt_gcm_driver = {
+    .cipher_encrypt = qcrypto_gcrypt_gcm_encrypt,
+    .cipher_decrypt = qcrypto_gcrypt_gcm_decrypt,
+    .cipher_setiv = qcrypto_gcrypt_gcm_setiv,
+    .cipher_setaad = qcrypto_gcrypt_gcm_setaad,
+    .cipher_gettag = qcrypto_gcrypt_gcm_gettag,
+    .cipher_free = qcrypto_gcrypt_ctx_free,
+};
+
 static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg,
                                              QCryptoCipherMode mode,
                                              const uint8_t *key,
@@ -259,6 +358,8 @@ static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg,
 
     if (mode == QCRYPTO_CIPHER_MODE_CTR) {
         drv = &qcrypto_gcrypt_ctr_driver;
+    } else if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        drv = &qcrypto_gcrypt_gcm_driver;
     } else {
         drv = &qcrypto_gcrypt_driver;
     }
-- 
2.55.0



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

* [PULL 77/83] crypto/cipher-nettle: Implement AES-GCM
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (75 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 76/83] crypto/cipher-gcrypt: Implement AES-GCM Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 78/83] crypto/cipher-gnutls: " Cédric Le Goater
                   ` (6 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 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 the AES-GCM AEAD mode to the nettle backend so it is available when
QEMU is built with nettle instead of gcrypt. GCM is driven through
nettle's generic gcm_* interface, using the AES encrypt function for both
directions: gcm_set_iv() sets the (typically 96-bit) nonce, gcm_update()
feeds the associated data, gcm_encrypt()/gcm_decrypt() need not be block
aligned, and gcm_digest() produces the authentication tag.

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

diff --git a/crypto/cipher-nettle.c.inc b/crypto/cipher-nettle.c.inc
index 1afdc391b49b..d4847f0efeee 100644
--- a/crypto/cipher-nettle.c.inc
+++ b/crypto/cipher-nettle.c.inc
@@ -27,6 +27,7 @@
 #include <nettle/twofish.h>
 #include <nettle/ctr.h>
 #include <nettle/xts.h>
+#include <nettle/gcm.h>
 #ifdef CONFIG_CRYPTO_SM4
 #include <nettle/sm4.h>
 #endif
@@ -410,6 +411,125 @@ DEFINE_ECB(qcrypto_nettle_sm4,
            sm4_encrypt_native, sm4_decrypt_native)
 #endif
 
+/*
+ * GCM is an AEAD mode built on AES (128-bit block only). Drive it through the
+ * generic gcm_* interface, using the block cipher's encrypt function for both
+ * directions; associated data is fed with gcm_update() and the authentication
+ * tag is produced by gcm_digest().
+ */
+typedef struct QCryptoNettleAESGCM {
+    QCryptoCipher base;
+    struct gcm_key gcm_key;
+    struct gcm_ctx gcm_ctx;
+    union {
+        struct aes128_ctx aes128;
+        struct aes192_ctx aes192;
+        struct aes256_ctx aes256;
+    } cipher;
+    nettle_cipher_func *encrypt;
+} QCryptoNettleAESGCM;
+
+static int qcrypto_nettle_aes_gcm_setiv(QCryptoCipher *cipher,
+                                        const uint8_t *iv, size_t niv,
+                                        Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_set_iv(&ctx->gcm_ctx, &ctx->gcm_key, niv, iv);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_setaad(QCryptoCipher *cipher,
+                                         const uint8_t *aad, size_t len,
+                                         Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_update(&ctx->gcm_ctx, &ctx->gcm_key, len, aad);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_encrypt(QCryptoCipher *cipher,
+                                          const void *in, void *out,
+                                          size_t len, Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_encrypt(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt,
+                len, out, in);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_decrypt(QCryptoCipher *cipher,
+                                          const void *in, void *out,
+                                          size_t len, Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_decrypt(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt,
+                len, out, in);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_gettag(QCryptoCipher *cipher,
+                                         uint8_t *tag, size_t len,
+                                         Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_digest(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt,
+               len, tag);
+    return 0;
+}
+
+static const struct QCryptoCipherDriver qcrypto_nettle_aes_gcm_driver = {
+    .cipher_encrypt = qcrypto_nettle_aes_gcm_encrypt,
+    .cipher_decrypt = qcrypto_nettle_aes_gcm_decrypt,
+    .cipher_setiv = qcrypto_nettle_aes_gcm_setiv,
+    .cipher_setaad = qcrypto_nettle_aes_gcm_setaad,
+    .cipher_gettag = qcrypto_nettle_aes_gcm_gettag,
+    .cipher_free = qcrypto_cipher_ctx_free,
+};
+
+static QCryptoCipher *qcrypto_nettle_aes_gcm_ctx_new(QCryptoCipherAlgo alg,
+                                                     const uint8_t *key,
+                                                     size_t nkey,
+                                                     Error **errp)
+{
+    QCryptoNettleAESGCM *ctx;
+
+    if (!qcrypto_cipher_validate_key_length(alg, QCRYPTO_CIPHER_MODE_GCM,
+                                            nkey, errp)) {
+        return NULL;
+    }
+
+    ctx = g_new0(QCryptoNettleAESGCM, 1);
+    ctx->base.driver = &qcrypto_nettle_aes_gcm_driver;
+
+    switch (alg) {
+    case QCRYPTO_CIPHER_ALGO_AES_128:
+        aes128_set_encrypt_key(&ctx->cipher.aes128, key);
+        ctx->encrypt = aes128_encrypt_native;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_192:
+        aes192_set_encrypt_key(&ctx->cipher.aes192, key);
+        ctx->encrypt = aes192_encrypt_native;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_256:
+        aes256_set_encrypt_key(&ctx->cipher.aes256, key);
+        ctx->encrypt = aes256_encrypt_native;
+        break;
+    default:
+        error_setg(errp, "Unsupported cipher algorithm %s with GCM mode",
+                   QCryptoCipherAlgo_str(alg));
+        g_free(ctx);
+        return NULL;
+    }
+
+    gcm_set_key(&ctx->gcm_key, &ctx->cipher, ctx->encrypt);
+    return &ctx->base;
+}
+
 bool qcrypto_cipher_supports(QCryptoCipherAlgo alg,
                              QCryptoCipherMode mode)
 {
@@ -440,6 +560,10 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgo alg,
     case QCRYPTO_CIPHER_MODE_XTS:
     case QCRYPTO_CIPHER_MODE_CTR:
         return true;
+    case QCRYPTO_CIPHER_MODE_GCM:
+        return alg == QCRYPTO_CIPHER_ALGO_AES_128 ||
+               alg == QCRYPTO_CIPHER_ALGO_AES_192 ||
+               alg == QCRYPTO_CIPHER_ALGO_AES_256;
     default:
         return false;
     }
@@ -451,6 +575,10 @@ static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg,
                                              size_t nkey,
                                              Error **errp)
 {
+    if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        return qcrypto_nettle_aes_gcm_ctx_new(alg, key, nkey, errp);
+    }
+
     switch (mode) {
     case QCRYPTO_CIPHER_MODE_ECB:
     case QCRYPTO_CIPHER_MODE_CBC:
-- 
2.55.0



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

* [PULL 78/83] crypto/cipher-gnutls: Implement AES-GCM
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (76 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 77/83] crypto/cipher-nettle: " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 79/83] tests/unit/test-crypto-cipher: Test AES-GCM mode Cédric Le Goater
                   ` (5 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 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 the AES-GCM AEAD mode to the gnutls backend so it is available when
QEMU is built with gnutls (neither gcrypt nor nettle). GCM uses the
incremental gnutls_cipher_* API with the GNUTLS_CIPHER_AES_*_GCM
algorithms: gnutls_cipher_set_iv() sets the nonce, gnutls_cipher_add_auth()
feeds the associated data, gnutls_cipher_encrypt2()/decrypt2() process the
message, and gnutls_cipher_tag() reads back the authentication tag.

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

diff --git a/crypto/cipher-gnutls.c.inc b/crypto/cipher-gnutls.c.inc
index a8263fff6d15..963b328fa12f 100644
--- a/crypto/cipher-gnutls.c.inc
+++ b/crypto/cipher-gnutls.c.inc
@@ -48,6 +48,15 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgo alg,
         default:
             return false;
         }
+    case QCRYPTO_CIPHER_MODE_GCM:
+        switch (alg) {
+        case QCRYPTO_CIPHER_ALGO_AES_128:
+        case QCRYPTO_CIPHER_ALGO_AES_192:
+        case QCRYPTO_CIPHER_ALGO_AES_256:
+            return true;
+        default:
+            return false;
+        }
     default:
         return false;
     }
@@ -223,6 +232,147 @@ static struct QCryptoCipherDriver gnutls_driver = {
     .cipher_free = qcrypto_gnutls_cipher_free,
 };
 
+/*
+ * GCM is an AEAD stream mode: the nonce need not match the block size, the
+ * message length need not be a multiple of the block size, associated data is
+ * fed with gnutls_cipher_add_auth() and the authentication tag is read back
+ * with gnutls_cipher_tag().
+ */
+static int
+qcrypto_gnutls_cipher_encrypt_gcm(QCryptoCipher *cipher,
+                                  const void *in, void *out,
+                                  size_t len, Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_encrypt2(ctx->handle, in, len, out, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot encrypt data: %s", gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_decrypt_gcm(QCryptoCipher *cipher,
+                                  const void *in, void *out,
+                                  size_t len, Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_decrypt2(ctx->handle, in, len, out, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot decrypt data: %s", gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_setiv_gcm(QCryptoCipher *cipher,
+                                const uint8_t *iv, size_t niv,
+                                Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+
+    gnutls_cipher_set_iv(ctx->handle, (void *)iv, niv);
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_setaad_gcm(QCryptoCipher *cipher,
+                                 const uint8_t *aad, size_t len,
+                                 Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_add_auth(ctx->handle, aad, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot add associated data: %s",
+                   gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_gettag_gcm(QCryptoCipher *cipher,
+                                 uint8_t *tag, size_t len,
+                                 Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_tag(ctx->handle, tag, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot get authentication tag: %s",
+                   gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static struct QCryptoCipherDriver gnutls_gcm_driver = {
+    .cipher_encrypt = qcrypto_gnutls_cipher_encrypt_gcm,
+    .cipher_decrypt = qcrypto_gnutls_cipher_decrypt_gcm,
+    .cipher_setiv = qcrypto_gnutls_cipher_setiv_gcm,
+    .cipher_setaad = qcrypto_gnutls_cipher_setaad_gcm,
+    .cipher_gettag = qcrypto_gnutls_cipher_gettag_gcm,
+    .cipher_free = qcrypto_gnutls_cipher_free,
+};
+
+static QCryptoCipher *
+qcrypto_gnutls_aes_gcm_ctx_new(QCryptoCipherAlgo alg, const uint8_t *key,
+                               size_t nkey, Error **errp)
+{
+    gnutls_datum_t gkey = { (unsigned char *)key, nkey };
+    gnutls_cipher_algorithm_t galg = GNUTLS_CIPHER_UNKNOWN;
+    QCryptoCipherGnutls *ctx;
+    int err;
+
+    switch (alg) {
+    case QCRYPTO_CIPHER_ALGO_AES_128:
+        galg = GNUTLS_CIPHER_AES_128_GCM;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_192:
+        galg = GNUTLS_CIPHER_AES_192_GCM;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_256:
+        galg = GNUTLS_CIPHER_AES_256_GCM;
+        break;
+    default:
+        error_setg(errp, "Unsupported cipher algorithm %s with GCM mode",
+                   QCryptoCipherAlgo_str(alg));
+        return NULL;
+    }
+
+    if (!qcrypto_cipher_validate_key_length(alg, QCRYPTO_CIPHER_MODE_GCM,
+                                            nkey, errp)) {
+        return NULL;
+    }
+
+    ctx = g_new0(QCryptoCipherGnutls, 1);
+    ctx->base.driver = &gnutls_gcm_driver;
+    ctx->blocksize = 16;
+
+    err = gnutls_cipher_init(&ctx->handle, galg, &gkey, NULL);
+    if (err != 0) {
+        error_setg(errp, "Cannot initialize cipher: %s", gnutls_strerror(err));
+        g_free(ctx);
+        return NULL;
+    }
+
+    return &ctx->base;
+}
+
 static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg,
                                              QCryptoCipherMode mode,
                                              const uint8_t *key,
@@ -234,6 +384,10 @@ static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg,
     gnutls_cipher_algorithm_t galg = GNUTLS_CIPHER_UNKNOWN;
     int err;
 
+    if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        return qcrypto_gnutls_aes_gcm_ctx_new(alg, key, nkey, errp);
+    }
+
     switch (mode) {
     case QCRYPTO_CIPHER_MODE_XTS:
         switch (alg) {
-- 
2.55.0



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

* [PULL 79/83] tests/unit/test-crypto-cipher: Test AES-GCM mode
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (77 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 78/83] crypto/cipher-gnutls: " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 80/83] hw/misc/aspeed_hace: Support 64-bit DMA for the crypto command Cédric Le Goater
                   ` (4 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 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>

Exercise the new GCM mode and the setaad/gettag helpers with the
canonical AES-GCM test vectors from the GCM specification (McGrew &
Viega, also NIST SP 800-38D): AES-128 and AES-256, with and without
associated data. Each vector is run through encrypt (checking the
ciphertext and the generated tag) and decrypt (checking the recovered
plaintext and the recomputed tag).

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

diff --git a/tests/unit/test-crypto-cipher.c b/tests/unit/test-crypto-cipher.c
index 1331d558cf25..420c826df95b 100644
--- a/tests/unit/test-crypto-cipher.c
+++ b/tests/unit/test-crypto-cipher.c
@@ -810,6 +810,230 @@ static void test_cipher_short_plaintext(void)
     qcrypto_cipher_free(cipher);
 }
 
+typedef struct QCryptoCipherGcmTestData QCryptoCipherGcmTestData;
+struct QCryptoCipherGcmTestData {
+    const char *path;
+    QCryptoCipherAlgo alg;
+    const char *key;
+    const char *iv;
+    /* associated data, or NULL for none */
+    const char *aad;
+    const char *plaintext;
+    const char *ciphertext;
+    const char *tag;
+};
+
+/*
+ * AES-GCM test vectors from "The Galois/Counter Mode of Operation (GCM)"
+ * (McGrew & Viega, also NIST SP 800-38D), with a 96-bit IV and a 128-bit
+ * tag. Each entry's "Test case N" label is the numbered test case from that
+ * document (Appendix B / the GCM specification's test vectors).
+ */
+static QCryptoCipherGcmTestData gcm_test_data[] = {
+    {
+        /* Test case 2 */
+        .path = "/crypto/cipher/aes-gcm-128-2",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .key = "00000000000000000000000000000000",
+        .iv = "000000000000000000000000",
+        .plaintext = "00000000000000000000000000000000",
+        .ciphertext = "0388dace60b6a392f328c2b971b2fe78",
+        .tag = "ab6e47d42cec13bdf53a67b21257bddf",
+    },
+    {
+        /* Test case 3 (no AAD) */
+        .path = "/crypto/cipher/aes-gcm-128-3",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .key = "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b391aafd255",
+        .ciphertext =
+            "42831ec2217774244b7221b784d0d49c"
+            "e3aa212f2c02a4e035c17e2329aca12e"
+            "21d514b25466931c7d8f6a5aac84aa05"
+            "1ba30b396a0aac973d58e091473f5985",
+        .tag = "4d5c2af327cd64a62cf35abd2ba6fab4",
+    },
+    {
+        /* Test case 4 (with AAD) */
+        .path = "/crypto/cipher/aes-gcm-128-4",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .key = "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b39",
+        .ciphertext =
+            "42831ec2217774244b7221b784d0d49c"
+            "e3aa212f2c02a4e035c17e2329aca12e"
+            "21d514b25466931c7d8f6a5aac84aa05"
+            "1ba30b396a0aac973d58e091",
+        .tag = "5bc94fbc3221a5db94fae95ae7121a47",
+    },
+    {
+        /* Test case 15 (AES-256, no AAD) */
+        .path = "/crypto/cipher/aes-gcm-256-15",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_256,
+        .key =
+            "feffe9928665731c6d6a8f9467308308"
+            "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b391aafd255",
+        .ciphertext =
+            "522dc1f099567d07f47f37a32a84427d"
+            "643a8cdcbfe5c0c97598a2bd2555d1aa"
+            "8cb08e48590dbb3da7b08b1056828838"
+            "c5f61e6393ba7a0abcc9f662898015ad",
+        .tag = "b094dac5d93471bdec1a502270e3cc6c",
+    },
+    {
+        /* Test case 16 (AES-256, with AAD) */
+        .path = "/crypto/cipher/aes-gcm-256-16",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_256,
+        .key =
+            "feffe9928665731c6d6a8f9467308308"
+            "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b39",
+        .ciphertext =
+            "522dc1f099567d07f47f37a32a84427d"
+            "643a8cdcbfe5c0c97598a2bd2555d1aa"
+            "8cb08e48590dbb3da7b08b1056828838"
+            "c5f61e6393ba7a0abcc9f662",
+        .tag = "76fc6ece0f4e1768cddf8853bb2d551b",
+    },
+};
+
+static void test_cipher_gcm(const void *opaque)
+{
+    const QCryptoCipherGcmTestData *data = opaque;
+    g_autofree uint8_t *key = NULL;
+    g_autofree uint8_t *iv = NULL;
+    g_autofree uint8_t *aad = NULL;
+    g_autofree uint8_t *ptext = NULL;
+    g_autofree uint8_t *ctext = NULL;
+    g_autofree uint8_t *tagexp = NULL;
+    g_autofree uint8_t *out = NULL;
+    uint8_t tag[16];
+    size_t nkey;
+    size_t niv;
+    size_t naad = 0;
+    size_t nptext;
+    size_t nctext;
+    size_t ntag;
+    QCryptoCipher *cipher;
+
+    nkey = unhex_string(data->key, &key);
+    niv = unhex_string(data->iv, &iv);
+    nptext = unhex_string(data->plaintext, &ptext);
+    nctext = unhex_string(data->ciphertext, &ctext);
+    ntag = unhex_string(data->tag, &tagexp);
+    if (data->aad) {
+        naad = unhex_string(data->aad, &aad);
+    }
+
+    g_assert_cmpint(nptext, ==, nctext);
+    g_assert_cmpint(ntag, ==, sizeof(tag));
+    out = g_new0(uint8_t, nptext);
+
+    /* Encrypt: plaintext -> ciphertext, then read back the tag. */
+    cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
+                                key, nkey, &error_abort);
+    g_assert(cipher != NULL);
+    g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
+    if (naad) {
+        g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
+    }
+    g_assert(qcrypto_cipher_encrypt(cipher, ptext, out, nptext,
+                                    &error_abort) == 0);
+    g_assert_cmpmem(out, nptext, ctext, nctext);
+    g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
+                                   &error_abort) == 0);
+    g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag);
+    qcrypto_cipher_free(cipher);
+
+    /* Decrypt: ciphertext -> plaintext, recomputed tag must match. */
+    memset(out, 0, nptext);
+    cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
+                                key, nkey, &error_abort);
+    g_assert(cipher != NULL);
+    g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
+    if (naad) {
+        g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
+    }
+    g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext,
+                                    &error_abort) == 0);
+    g_assert_cmpmem(out, nctext, ptext, nptext);
+    g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
+                                   &error_abort) == 0);
+    g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag);
+    qcrypto_cipher_free(cipher);
+}
+
+/*
+ * Corrupt one ciphertext byte and confirm the recomputed GCM tag no longer
+ * matches: the authentication tag must detect tampering.
+ */
+static void test_cipher_gcm_tamper(const void *opaque)
+{
+    const QCryptoCipherGcmTestData *data = opaque;
+    g_autofree uint8_t *key = NULL;
+    g_autofree uint8_t *iv = NULL;
+    g_autofree uint8_t *aad = NULL;
+    g_autofree uint8_t *ctext = NULL;
+    g_autofree uint8_t *tagexp = NULL;
+    g_autofree uint8_t *out = NULL;
+    uint8_t tag[16];
+    size_t nkey;
+    size_t niv;
+    size_t naad = 0;
+    size_t nctext;
+    size_t ntag;
+    QCryptoCipher *cipher;
+
+    nkey = unhex_string(data->key, &key);
+    niv = unhex_string(data->iv, &iv);
+    nctext = unhex_string(data->ciphertext, &ctext);
+    ntag = unhex_string(data->tag, &tagexp);
+    if (data->aad) {
+        naad = unhex_string(data->aad, &aad);
+    }
+    out = g_new0(uint8_t, nctext);
+
+    /* Flip one ciphertext bit before decrypting. */
+    ctext[0] ^= 0x01;
+
+    cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
+                                key, nkey, &error_abort);
+    g_assert(cipher != NULL);
+    g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
+    if (naad) {
+        g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
+    }
+    g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext,
+                                    &error_abort) == 0);
+    g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
+                                   &error_abort) == 0);
+    g_assert(memcmp(tag, tagexp, ntag) != 0);
+    qcrypto_cipher_free(cipher);
+}
+
 int main(int argc, char **argv)
 {
     size_t i;
@@ -828,6 +1052,22 @@ int main(int argc, char **argv)
         }
     }
 
+    for (i = 0; i < G_N_ELEMENTS(gcm_test_data); i++) {
+        if (qcrypto_cipher_supports(gcm_test_data[i].alg,
+                                    QCRYPTO_CIPHER_MODE_GCM)) {
+            g_autofree char *tamper = g_strdup_printf("%s/tamper",
+                                                      gcm_test_data[i].path);
+
+            g_test_add_data_func(gcm_test_data[i].path, &gcm_test_data[i],
+                                 test_cipher_gcm);
+            g_test_add_data_func(tamper, &gcm_test_data[i],
+                                 test_cipher_gcm_tamper);
+        } else {
+            g_printerr("# skip unsupported %s:gcm\n",
+                       QCryptoCipherAlgo_str(gcm_test_data[i].alg));
+        }
+    }
+
     if (qcrypto_cipher_supports(QCRYPTO_CIPHER_ALGO_AES_256,
                                 QCRYPTO_CIPHER_MODE_CBC)) {
         g_test_add_func("/crypto/cipher/null-iv",
-- 
2.55.0



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

* [PULL 80/83] hw/misc/aspeed_hace: Support 64-bit DMA for the crypto command
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (78 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 79/83] tests/unit/test-crypto-cipher: Test AES-GCM mode Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 81/83] hw/misc/aspeed_hace: Support the AES-GCM mode " Cédric Le Goater
                   ` (3 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

The AST2700 crypto engine addresses DRAM with 64 bits, supplying the high
half of the source, destination and context addresses through HACE80,
HACE84 and HACE88. Add those registers and a crypt_get_addr() helper that
combines the low and high halves when the SoC has 64-bit DMA, mirroring
the hash engine. SoCs without 64-bit DMA (AST2500/AST2600/AST1030) ignore
the high registers, so their behaviour is unchanged.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-14-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_hace.c | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index d84f7f9cec1a..0a6e2fa82914 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -57,6 +57,11 @@
 #define CRYPT_CTX_KEY_OFFSET        0x10
 #define CRYPT_CTX_SIZE              0x30
 
+/* AST2700 64-bit DMA high address registers for the crypto command */
+#define R_CRYPT_SRC_HI      (0x80 / 4)
+#define R_CRYPT_DEST_HI     (0x84 / 4)
+#define R_CRYPT_CONTEXT_HI  (0x88 / 4)
+
 #define R_STATUS        (0x1c / 4)
 #define HASH_IRQ        BIT(9)
 #define CRYPT_IRQ       BIT(12)
@@ -670,6 +675,19 @@ static void crypt_be_add(uint8_t *ctr, size_t len, uint64_t add)
     }
 }
 
+static uint64_t crypt_get_addr(AspeedHACEState *s, int reg, int reg_hi)
+{
+    AspeedHACEClass *ahc = ASPEED_HACE_GET_CLASS(s);
+    uint64_t addr;
+
+    addr = deposit64(0, 0, 32, s->regs[reg]);
+    if (ahc->has_dma64) {
+        addr = deposit64(addr, 32, 32, s->regs[reg_hi]);
+    }
+
+    return addr;
+}
+
 /*
  * Perform an AES/DES/3DES ECB/CBC operation. The source and destination are
  * either single contiguous buffers (direct access mode) or scatter-gather
@@ -717,7 +735,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     }
 
     /* Fetch the IV and key from the context buffer in DRAM. */
-    ctx_addr = s->regs[R_CRYPT_CONTEXT];
+    ctx_addr = crypt_get_addr(s, R_CRYPT_CONTEXT, R_CRYPT_CONTEXT_HI);
     if (address_space_read(&s->dram_as, ctx_addr, MEMTXATTRS_UNSPECIFIED,
                            ctx, sizeof(ctx))) {
         qemu_log_mask(LOG_GUEST_ERROR,
@@ -758,7 +776,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     dst_buf = g_malloc0(buf_len);
 
     /* Gather the source into the bounce buffer, per the selected mode. */
-    src_addr = s->regs[R_CRYPT_SRC];
+    src_addr = crypt_get_addr(s, R_CRYPT_SRC, R_CRYPT_SRC_HI);
     if (sg_mode) {
         status = crypt_prepare_sg(s, src_addr, src_buf, len, false);
     } else {
@@ -794,7 +812,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     }
 
     /* Scatter the result back out, per the selected mode. */
-    dst_addr = s->regs[R_CRYPT_DEST];
+    dst_addr = crypt_get_addr(s, R_CRYPT_DEST, R_CRYPT_DEST_HI);
     if (sg_mode) {
         status = crypt_prepare_sg(s, dst_addr, dst_buf, len, true);
     } else {
@@ -958,6 +976,15 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
     case R_HASH_KEY_BUFF_HI:
         data &= ahc->key_hi_mask;
         break;
+    case R_CRYPT_SRC_HI:
+        data &= ahc->src_hi_mask;
+        break;
+    case R_CRYPT_DEST_HI:
+        data &= ahc->dest_hi_mask;
+        break;
+    case R_CRYPT_CONTEXT_HI:
+        data &= ahc->key_hi_mask;
+        break;
     default:
         break;
     }
-- 
2.55.0



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

* [PULL 81/83] hw/misc/aspeed_hace: Support the AES-GCM mode for the crypto command
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (79 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 80/83] hw/misc/aspeed_hace: Support 64-bit DMA for the crypto command Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 82/83] hw/misc/aspeed_hace: Enable the crypto command on the AST2700 Cédric Le Goater
                   ` (2 subsequent siblings)
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Implement the AES-GCM mode (HACE10[6:4] = 0b101) used by the AST2700
crypto engine: decode the GCM selection, read the 96-bit IV from the
context buffer, operate on the exact data length (GCM handles a partial
final block itself), and write the 128-bit authentication tag to the tag
buffer (HACE18/HACE8C). The hardware GCM path is only used without
associated data (the driver falls back to software otherwise), so AAD is
not modelled and a non-zero HACE14 is reported as unimplemented.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-15-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 hw/misc/aspeed_hace.c | 70 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 62 insertions(+), 8 deletions(-)

diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index 0a6e2fa82914..23dacc9be85e 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -31,6 +31,9 @@
 /* HACE0C[27:0] holds the crypto data length */
 #define  CRYPT_DATA_LEN_MASK    0x0FFFFFFF
 #define R_CRYPT_CMD         (0x10 / 4)
+/* AES-GCM associated data length (HACE14) and tag write buffer (HACE18) */
+#define R_CRYPT_GCM_ADD_LEN (0x14 / 4)
+#define R_CRYPT_GCM_TAG     (0x18 / 4)
 /* Crypto engine command register (HACE10) bits */
 #define  CRYPT_CMD_ENCRYPT          BIT(7)
 #define  CRYPT_CMD_ISR_EN           BIT(12)
@@ -42,6 +45,7 @@
 #define  CRYPT_CMD_ECB              (0x0 << 4)
 #define  CRYPT_CMD_CBC              (0x1 << 4)
 #define  CRYPT_CMD_CTR              (0x4 << 4)
+#define  CRYPT_CMD_GCM              (0x5 << 4)
 /* AES key length HACE10[3:2] */
 #define  CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
 #define  CRYPT_CMD_AES256           (0x2 << 2)
@@ -57,10 +61,15 @@
 #define CRYPT_CTX_KEY_OFFSET        0x10
 #define CRYPT_CTX_SIZE              0x30
 
+/* AES-GCM uses a 96-bit IV and a 128-bit authentication tag */
+#define CRYPT_GCM_IV_LEN            12
+#define CRYPT_GCM_TAG_LEN           16
+
 /* AST2700 64-bit DMA high address registers for the crypto command */
 #define R_CRYPT_SRC_HI      (0x80 / 4)
 #define R_CRYPT_DEST_HI     (0x84 / 4)
 #define R_CRYPT_CONTEXT_HI  (0x88 / 4)
+#define R_CRYPT_GCM_TAG_HI  (0x8c / 4)
 
 #define R_STATUS        (0x1c / 4)
 #define HASH_IRQ        BIT(9)
@@ -596,6 +605,9 @@ static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg,
     case CRYPT_CMD_CTR:
         *mode = QCRYPTO_CIPHER_MODE_CTR;
         break;
+    case CRYPT_CMD_GCM:
+        *mode = QCRYPTO_CIPHER_MODE_GCM;
+        break;
     default:
         return false;
     }
@@ -689,11 +701,12 @@ static uint64_t crypt_get_addr(AspeedHACEState *s, int reg, int reg_hi)
 }
 
 /*
- * Perform an AES/DES/3DES ECB/CBC operation. The source and destination are
- * either single contiguous buffers (direct access mode) or scatter-gather
- * lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the IV/key come from
- * the context buffer (HACE08). For CBC the resulting chaining IV is written
- * back to the context buffer so the driver can continue the chain.
+ * Perform an AES/DES/3DES ECB/CBC/CTR or AES-GCM operation. The source and
+ * destination are either single contiguous buffers (direct access mode) or
+ * scatter-gather lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the
+ * IV/key come from the context buffer (HACE08). For CBC and CTR the resulting
+ * chaining state is written back to the context buffer so the driver can
+ * continue; for GCM the authentication tag is written to the tag buffer.
  */
 static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
 {
@@ -703,6 +716,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     g_autoptr(QCryptoCipher) cipher = NULL;
     g_autofree uint8_t *src_buf = NULL;
     g_autofree uint8_t *dst_buf = NULL;
+    uint8_t tag[CRYPT_GCM_TAG_LEN];
     uint8_t ctx[CRYPT_CTX_SIZE];
     Error *local_err = NULL;
     QCryptoCipherMode mode;
@@ -711,10 +725,13 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     uint64_t ctx_addr;
     uint64_t src_addr;
     uint64_t dst_addr;
+    uint64_t tag_addr;
+    uint32_t aad_len;
     size_t iv_offset;
     size_t blocklen;
     size_t buf_len;
     size_t keylen;
+    size_t ivlen;
     bool status;
 
     if (len == 0) {
@@ -734,6 +751,20 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
         return;
     }
 
+    /* GCM uses a 96-bit IV; the block modes use a full-block IV. */
+    ivlen = (mode == QCRYPTO_CIPHER_MODE_GCM) ? CRYPT_GCM_IV_LEN : blocklen;
+
+    /*
+     * The hardware GCM path is only exercised without associated data (the
+     * driver falls back to software when there is any), so AAD is not modelled.
+     */
+    aad_len = s->regs[R_CRYPT_GCM_ADD_LEN];
+    if (mode == QCRYPTO_CIPHER_MODE_GCM && aad_len != 0) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: GCM associated data is not implemented\n", __func__);
+        return;
+    }
+
     /* Fetch the IV and key from the context buffer in DRAM. */
     ctx_addr = crypt_get_addr(s, R_CRYPT_CONTEXT, R_CRYPT_CONTEXT_HI);
     if (address_space_read(&s->dram_as, ctx_addr, MEMTXATTRS_UNSPECIFIED,
@@ -758,7 +789,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     }
 
     if (mode != QCRYPTO_CIPHER_MODE_ECB &&
-        qcrypto_cipher_setiv(cipher, ctx + iv_offset, blocklen,
+        qcrypto_cipher_setiv(cipher, ctx + iv_offset, ivlen,
                              &local_err) < 0) {
         qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv failed: %s\n",
                       __func__, error_get_pretty(local_err));
@@ -769,9 +800,11 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
     /*
      * Round the working buffers up to a whole block. Block modes are already
      * block-aligned; the stream-like CTR mode may leave a partial final block
-     * that the engine still processes a full block at a time.
+     * that the engine still processes a full block at a time. GCM handles a
+     * partial final block itself, so it operates on the exact length.
      */
-    buf_len = QEMU_ALIGN_UP(len, blocklen);
+    buf_len = (mode == QCRYPTO_CIPHER_MODE_GCM) ?
+              len : QEMU_ALIGN_UP(len, blocklen);
     src_buf = g_malloc0(buf_len);
     dst_buf = g_malloc0(buf_len);
 
@@ -855,6 +888,24 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
                           "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
                           __func__, ctx_addr + iv_offset);
         }
+    } else if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        /*
+         * GCM authenticates the message and writes the resulting tag to the
+         * dedicated tag buffer (HACE18/HACE8C).
+         */
+        if (qcrypto_cipher_gettag(cipher, tag, sizeof(tag), &local_err) < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher gettag failed: "
+                          "%s\n", __func__, error_get_pretty(local_err));
+            error_free(local_err);
+            return;
+        }
+        tag_addr = crypt_get_addr(s, R_CRYPT_GCM_TAG, R_CRYPT_GCM_TAG_HI);
+        if (address_space_write(&s->dram_as, tag_addr, MEMTXATTRS_UNSPECIFIED,
+                                tag, sizeof(tag))) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write tag, addr=0x%" HWADDR_PRIx "\n",
+                          __func__, tag_addr);
+        }
     }
 }
 
@@ -899,9 +950,11 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
     case R_CRYPT_SRC:
     case R_CRYPT_DEST:
     case R_CRYPT_CONTEXT:
+    case R_CRYPT_GCM_TAG:
         data &= ahc->src_mask;
         break;
     case R_CRYPT_DATA_LEN:
+    case R_CRYPT_GCM_ADD_LEN:
         data &= CRYPT_DATA_LEN_MASK;
         break;
     case R_HASH_SRC:
@@ -980,6 +1033,7 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
         data &= ahc->src_hi_mask;
         break;
     case R_CRYPT_DEST_HI:
+    case R_CRYPT_GCM_TAG_HI:
         data &= ahc->dest_hi_mask;
         break;
     case R_CRYPT_CONTEXT_HI:
-- 
2.55.0



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

* [PULL 82/83] hw/misc/aspeed_hace: Enable the crypto command on the AST2700
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (80 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 81/83] hw/misc/aspeed_hace: Support the AES-GCM mode " Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-11 16:29 ` [PULL 83/83] tests/qtest/aspeed-hace: Test " Cédric Le Goater
  2026-08-12 16:15 ` [PULL 00/83] aspeed queue Richard Henderson
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

With direct/scatter-gather access, 64-bit DMA and AES-GCM all in place,
the AST2700 crypto engine is now fully modelled. Drop its temporary
interrupt-only workaround so the crypto command runs for real, like the
other HACE variants.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-16-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 include/hw/misc/aspeed_hace.h |  1 -
 hw/misc/aspeed_hace.c         | 15 +--------------
 2 files changed, 1 insertion(+), 15 deletions(-)

diff --git a/include/hw/misc/aspeed_hace.h b/include/hw/misc/aspeed_hace.h
index b5416b0cb5d3..9b0e7683fa88 100644
--- a/include/hw/misc/aspeed_hace.h
+++ b/include/hw/misc/aspeed_hace.h
@@ -49,7 +49,6 @@ struct AspeedHACEClass {
     uint32_t key_mask;
     uint32_t hash_mask;
     uint64_t nr_regs;
-    bool raise_crypt_interrupt_workaround;
     uint32_t src_hi_mask;
     uint32_t dest_hi_mask;
     uint32_t key_hi_mask;
diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index 23dacc9be85e..8de05a9d98b3 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -1005,14 +1005,7 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
         break;
     }
     case R_CRYPT_CMD:
-        /*
-         * The AST2700 crypto engine needs 64-bit DMA and AES-GCM, which are
-         * added later; until then it keeps the temporary workaround of only
-         * raising the completion interrupt without running the command.
-         */
-        if (!ahc->raise_crypt_interrupt_workaround) {
-            do_crypt_operation(s, data);
-        }
+        do_crypt_operation(s, data);
 
         /* Hardware raises the crypt interrupt once the command finishes. */
         s->regs[R_STATUS] |= CRYPT_IRQ;
@@ -1214,12 +1207,6 @@ static void aspeed_ast2700_hace_class_init(ObjectClass *klass, const void *data)
     ahc->dest_hi_mask = 0x00000003;
     ahc->key_hi_mask = 0x00000003;
 
-    /*
-     * Currently, it does not support the CRYPT command. Instead, it only
-     * sends an interrupt to notify the firmware that the crypt command
-     * has completed. It is a temporary workaround.
-     */
-    ahc->raise_crypt_interrupt_workaround = true;
     ahc->has_dma64 = true;
 }
 
-- 
2.55.0



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

* [PULL 83/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2700
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (81 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 82/83] hw/misc/aspeed_hace: Enable the crypto command on the AST2700 Cédric Le Goater
@ 2026-08-11 16:29 ` Cédric Le Goater
  2026-08-12 16:15 ` [PULL 00/83] aspeed queue Richard Henderson
  83 siblings, 0 replies; 85+ messages in thread
From: Cédric Le Goater @ 2026-08-11 16:29 UTC (permalink / raw)
  To: qemu-arm, qemu-devel; +Cc: Jamin Lin, Kane Chen, Cédric Le Goater

From: Jamin Lin <jamin_lin@aspeedtech.com>

Cover the AST2700 crypto engine, which drives 64-bit scatter-gather DMA
and adds AES-GCM on top of the ECB/CBC/CTR modes shared with the AST2600.
Add AES-128 and AES-256 GCM known-answer vectors (GCM specification /
NIST SP 800-38D, no associated data) and a dedicated GCM runner that
programs the tag buffer and reads the tag back, checking it after both
encryption and decryption. Register the AST2700 with all four modes.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-17-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 tests/qtest/aspeed-hace-utils.h |   1 +
 tests/qtest/aspeed-hace-utils.c | 163 +++++++++++++++++++++++++++++++-
 tests/qtest/ast2700-hace-test.c |   9 ++
 3 files changed, 172 insertions(+), 1 deletion(-)

diff --git a/tests/qtest/aspeed-hace-utils.h b/tests/qtest/aspeed-hace-utils.h
index 82b0b3f93d77..a5601a3d65fc 100644
--- a/tests/qtest/aspeed-hace-utils.h
+++ b/tests/qtest/aspeed-hace-utils.h
@@ -86,6 +86,7 @@ enum {
     CRYPT_MODE_ECB = 1 << 0,
     CRYPT_MODE_CBC = 1 << 1,
     CRYPT_MODE_CTR = 1 << 2,
+    CRYPT_MODE_GCM = 1 << 3,
 };
 
 /*
diff --git a/tests/qtest/aspeed-hace-utils.c b/tests/qtest/aspeed-hace-utils.c
index f582c88ef515..260eec043c73 100644
--- a/tests/qtest/aspeed-hace-utils.c
+++ b/tests/qtest/aspeed-hace-utils.c
@@ -654,6 +654,8 @@ void aspeed_test_addresses(const char *machine, const uint32_t base,
 #define HACE_CRYPTO_CONTEXT      0x08
 #define HACE_CRYPTO_DATA_LEN     0x0c
 #define HACE_CRYPTO_CMD          0x10
+#define HACE_CRYPTO_GCM_ADD_LEN  0x14
+#define HACE_CRYPTO_GCM_TAG      0x18
 
 /* Crypto command bits */
 #define HACE_CMD_ENCRYPT         BIT(7)
@@ -666,7 +668,9 @@ void aspeed_test_addresses(const char *machine, const uint32_t base,
 #define HACE_CMD_ECB             (0x0 << 4)
 #define HACE_CMD_CBC             (0x1 << 4)
 #define HACE_CMD_CTR             (0x4 << 4)
+#define HACE_CMD_GCM             (0x5 << 4)
 #define HACE_CMD_AES128          (0x0 << 2)
+#define HACE_CMD_AES256          (0x2 << 2)
 
 /* Context buffer layout: IV (DES at +8), key at +0x10 */
 #define HACE_CTX_KEY_OFFSET      0x10
@@ -794,6 +798,59 @@ static const uint8_t tdes_ctr_ctext[8] = {
 static const uint8_t tdes_ctr_ivout[8] = {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
 
+/*
+ * aes_gcm_tv_template[2] (AES-128) and [9] (AES-256), from the McGrew & Viega
+ * GCM spec (also NIST SP 800-38D), no AAD. Both cases share this plaintext/IV.
+ */
+static const uint8_t aes_gcm_ptext[64] = {
+    0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+    0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+    0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+    0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+    0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+    0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+    0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+    0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 };
+static const uint8_t aes_gcm_iv[12] = {
+    0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+    0xde, 0xca, 0xf8, 0x88 };
+
+/* aes_gcm_tv_template[2] (AES-128) */
+static const uint8_t aes128_gcm_key[16] = {
+    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 };
+static const uint8_t aes128_gcm_ctext[64] = {
+    0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+    0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+    0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+    0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+    0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+    0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+    0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+    0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 };
+static const uint8_t aes128_gcm_tag[16] = {
+    0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6,
+    0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4 };
+
+/* aes_gcm_tv_template[9] (AES-256) */
+static const uint8_t aes256_gcm_key[32] = {
+    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 };
+static const uint8_t aes256_gcm_ctext[64] = {
+    0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07,
+    0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d,
+    0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9,
+    0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa,
+    0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d,
+    0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38,
+    0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a,
+    0xbc, 0xc9, 0xf6, 0x62, 0x89, 0x80, 0x15, 0xad };
+static const uint8_t aes256_gcm_tag[16] = {
+    0xb0, 0x94, 0xda, 0xc5, 0xd9, 0x34, 0x71, 0xbd,
+    0xec, 0x1a, 0x50, 0x22, 0x70, 0xe3, 0xcc, 0x6c };
+
 typedef struct CryptTest {
     QCryptoCipherMode mode;
     QCryptoCipherAlgo alg;
@@ -801,10 +858,13 @@ typedef struct CryptTest {
     const uint8_t *iv_out;
     const uint8_t *ptext;
     const uint8_t *ctext;
+    /* expected GCM authentication tag, or NULL for non-AEAD modes */
+    const uint8_t *tag;
     const uint8_t *key;
     const uint8_t *iv;
     const char *name;
     size_t keylen;
+    size_t taglen;
     /* algorithm | mode | key size selection */
     uint32_t cmd;
     size_t ivlen;
@@ -927,6 +987,36 @@ static const CryptTest crypt_tests[] = {
         .iv_out = tdes_ctr_ivout,
         .len = sizeof(tdes_ctr_ptext),
     },
+    {
+        .name = "aes128-gcm",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_GCM,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_GCM,
+        .key = aes128_gcm_key,
+        .keylen = sizeof(aes128_gcm_key),
+        .iv = aes_gcm_iv,
+        .ivlen = sizeof(aes_gcm_iv),
+        .ptext = aes_gcm_ptext,
+        .ctext = aes128_gcm_ctext,
+        .tag = aes128_gcm_tag,
+        .taglen = sizeof(aes128_gcm_tag),
+        .len = sizeof(aes_gcm_ptext),
+    },
+    {
+        .name = "aes256-gcm",
+        .cmd = HACE_CMD_AES256 | HACE_CMD_GCM,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_256,
+        .mode = QCRYPTO_CIPHER_MODE_GCM,
+        .key = aes256_gcm_key,
+        .keylen = sizeof(aes256_gcm_key),
+        .iv = aes_gcm_iv,
+        .ivlen = sizeof(aes_gcm_iv),
+        .ptext = aes_gcm_ptext,
+        .ctext = aes256_gcm_ctext,
+        .tag = aes256_gcm_tag,
+        .taglen = sizeof(aes256_gcm_tag),
+        .len = sizeof(aes_gcm_ptext),
+    },
 };
 
 /* DRAM offsets for the crypto test source, destination and context buffers. */
@@ -944,6 +1034,8 @@ static const CryptTest crypt_tests[] = {
  */
 #define CRYPT_SG_FRAGS         3
 #define CRYPT_SG_FRAG_STRIDE   0x1000
+/* DRAM offset for the AES-GCM authentication tag write buffer. */
+#define CRYPT_OFF_TAG          0x60000
 
 /* Describes one registered crypto test (qtest_add_data_func() data pointer). */
 typedef struct AspeedCryptoTest {
@@ -964,6 +1056,8 @@ static uint32_t crypt_mode_flag(uint32_t cmd)
         return CRYPT_MODE_CBC;
     case HACE_CMD_CTR:
         return CRYPT_MODE_CTR;
+    case HACE_CMD_GCM:
+        return CRYPT_MODE_GCM;
     default:
         return 0;
     }
@@ -1109,6 +1203,47 @@ static void crypt_run_sg(QTestState *s, uint32_t base, uint64_t dram,
     crypt_gather_sg(s, dram, CRYPT_OFF_DST, out, t->len);
 }
 
+/*
+ * Run one AES-GCM operation in scatter-gather mode: like crypt_run_sg() but
+ * also program the tag write buffer (HACE18) with no associated data, and read
+ * the authentication tag back into @out_tag.
+ */
+static void crypt_run_gcm(QTestState *s, uint32_t base, uint64_t dram,
+                          const CryptTest *t, bool encrypt, uint8_t *out,
+                          uint8_t *out_tag)
+{
+    const uint8_t *in = encrypt ? t->ptext : t->ctext;
+    uint64_t src_sg = dram + CRYPT_OFF_SRC_SG;
+    uint64_t dst_sg = dram + CRYPT_OFF_DST_SG;
+    uint64_t ctx = dram + CRYPT_OFF_CTX;
+    uint32_t cmd = t->cmd | HACE_CMD_ISR_EN | HACE_CMD_SRC_SG_CTRL |
+                   HACE_CMD_DST_SG_CTRL;
+
+    if (encrypt) {
+        cmd |= HACE_CMD_ENCRYPT;
+    }
+
+    crypt_write_ctx(s, ctx, t);
+    crypt_make_sg(s, dram, CRYPT_OFF_SRC, src_sg, in, t->len);
+    crypt_make_sg(s, dram, CRYPT_OFF_DST, dst_sg, NULL, t->len);
+
+    qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src_sg);
+    qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst_sg);
+    qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx);
+    qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len);
+    qtest_writel(s, base + HACE_CRYPTO_GCM_ADD_LEN, 0);
+    qtest_writel(s, base + HACE_CRYPTO_GCM_TAG,
+                 (uint32_t)(dram + CRYPT_OFF_TAG));
+    qtest_writel(s, base + HACE_CRYPTO_CMD, cmd);
+
+    g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==,
+                    HACE_CRYPTO_ISR);
+    qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR);
+
+    crypt_gather_sg(s, dram, CRYPT_OFF_DST, out, t->len);
+    qtest_memread(s, dram + CRYPT_OFF_TAG, out_tag, t->taglen);
+}
+
 static void aspeed_test_crypto(const void *data)
 {
     const AspeedCryptoTest *c = data;
@@ -1145,6 +1280,29 @@ static void aspeed_test_crypto(const void *data)
     qtest_quit(s);
 }
 
+static void aspeed_test_crypto_gcm(const void *data)
+{
+    const AspeedCryptoTest *c = data;
+    const CryptTest *t = &crypt_tests[c->index];
+    QTestState *s = qtest_init(c->machine);
+    uint8_t out[64];
+    uint8_t tag[16];
+
+    g_assert_cmpuint(t->len, <=, sizeof(out));
+
+    /* Encrypt: ptext -> ctext, then check the authentication tag. */
+    crypt_run_gcm(s, c->base, c->dram, t, true, out, tag);
+    g_assert_cmpmem(out, t->len, t->ctext, t->len);
+    g_assert_cmpmem(tag, t->taglen, t->tag, t->taglen);
+
+    /* Decrypt: ctext -> ptext, the recomputed tag must match. */
+    crypt_run_gcm(s, c->base, c->dram, t, false, out, tag);
+    g_assert_cmpmem(out, t->len, t->ptext, t->len);
+    g_assert_cmpmem(tag, t->taglen, t->tag, t->taglen);
+
+    qtest_quit(s);
+}
+
 void aspeed_add_crypto_tests(const char *prefix, const char *machine,
                              uint32_t base, uint64_t dram, uint32_t modes,
                              bool sg)
@@ -1152,6 +1310,7 @@ void aspeed_add_crypto_tests(const char *prefix, const char *machine,
     int i;
 
     for (i = 0; i < ARRAY_SIZE(crypt_tests); i++) {
+        bool is_gcm = crypt_tests[i].mode == QCRYPTO_CIPHER_MODE_GCM;
         g_autofree char *path = NULL;
         AspeedCryptoTest *t;
 
@@ -1173,7 +1332,9 @@ void aspeed_add_crypto_tests(const char *prefix, const char *machine,
         t->dram = dram;
         t->index = i;
         t->sg = sg;
-        qtest_add_data_func_full(path, t, aspeed_test_crypto, g_free);
+        qtest_add_data_func_full(path, t,
+                                 is_gcm ? aspeed_test_crypto_gcm :
+                                 aspeed_test_crypto, g_free);
     }
 }
 
diff --git a/tests/qtest/ast2700-hace-test.c b/tests/qtest/ast2700-hace-test.c
index 508a34dd6c65..3f0217d63510 100644
--- a/tests/qtest/ast2700-hace-test.c
+++ b/tests/qtest/ast2700-hace-test.c
@@ -94,5 +94,14 @@ int main(int argc, char **argv)
     qtest_add_func("ast2700/hace/sha384_accum", test_sha384_accum_ast2700);
     qtest_add_func("ast2700/hace/sha256_accum", test_sha256_accum_ast2700);
 
+    /*
+     * The AST2700 crypto engine uses scatter-gather with 64-bit DMA and adds
+     * AES-GCM on top of the ECB/CBC/CTR modes shared with the AST2600.
+     */
+    aspeed_add_crypto_tests("ast2700", "-machine ast2700-evb", 0x12070000,
+                            0x400000000,
+                            CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR |
+                            CRYPT_MODE_GCM, true);
+
     return g_test_run();
 }
-- 
2.55.0



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

* Re: [PULL 00/83] aspeed queue
  2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
                   ` (82 preceding siblings ...)
  2026-08-11 16:29 ` [PULL 83/83] tests/qtest/aspeed-hace: Test " Cédric Le Goater
@ 2026-08-12 16:15 ` Richard Henderson
  83 siblings, 0 replies; 85+ messages in thread
From: Richard Henderson @ 2026-08-12 16:15 UTC (permalink / raw)
  To: qemu-devel

On 8/11/26 09:28, Cédric Le Goater wrote:
> The following changes since commit 84f07211cc5b4fc6a371559bf8a5de4fb068e648:
> 
>    Update version for v11.1.0 release (2026-08-11 10:04:46 -0400)
> 
> are available in the Git repository at:
> 
>    https://github.com/legoater/qemu/ tags/pull-aspeed-20260811
> 
> for you to fetch changes up to 0375f6498eee6188089924bb766c653492ff8857:
> 
>    tests/qtest/aspeed-hace: Test the crypto command on the AST2700 (2026-08-11 18:25:58 +0200)
> 
> ----------------------------------------------------------------
> aspeed queue:
> 
> * Fixes missing Kconfig dependencies for Aspeed boards
> * Adds 64-bit addressing support to the EHCI USB controller model.
>    Enable it on the AST2700
> * Extends Aspeed SMC qtest coverage with fast-read, DOR and QOR
>    read modes
> * Introduces a separate Aspeed2700SCUState type and shares the SCUIO,
>    FMC and SCU instances across the AST2700 PSP, SSP and TSP
>    coprocessors
> * Adds Data FIFO-based flash access for the AST2700 FMC controller
> * Adds the ADC128D818 12-bit 8-channel ADC sensor device with tests,
>    wired up on the Anacapa board
> * Reworks the PCA9552/PCA9555 GPIO/LED driver: polarity inversion,
>    datasheet-conformant command handling, GPIO QOM properties, reset
>    via the Resettable interface, and extensive qtest coverage
> * Reworks the PCA9554 GPIO driver: output-to-input reflection,
>    PCA9536 support, pin direction property, and qtest coverage
> * Adds PCA9555 IO expanders and temperature sensors to the Catalina
>    board
> * Adds AST2700 I2C master buffer mode support
> * Updates ASPEED functional tests to SDK v11.03 and Zephyr SDK v03.08
> * Adds AES-GCM support to the QEMU crypto cipher layer (gcrypt,
>    nettle, gnutls backends) with unit tests
> * Adds crypto (AES) command emulation to the Aspeed HACE model:
>    direct access, scatter-gather, CTR, GCM modes, 64-bit DMA, with
>    qtest coverage on AST2500, AST2600, AST1030 and AST2700

Applied, thanks.  Please update https://wiki.qemu.org/ChangeLog/11.2 as appropriate.

r~


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

end of thread, other threads:[~2026-08-12 16:16 UTC | newest]

Thread overview: 85+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 16:28 [PULL 00/83] aspeed queue Cédric Le Goater
2026-08-11 16:28 ` [PULL 01/83] hw/arm/aspeed: Add missing PCI_EXPRESS -> PCIE_PORT Kconfig dependency Cédric Le Goater
2026-08-11 16:28 ` [PULL 02/83] hw/arm/aspeed: Add missing Kconfig dependencies on required components Cédric Le Goater
2026-08-11 16:28 ` [PULL 03/83] hw/arm/aspeed: Add missing Kconfig dependencies on optional components Cédric Le Goater
2026-08-11 16:28 ` [PULL 04/83] hw/usb/hcd-ehci: Change descriptor addresses to 64-bit with migration compatibility Cédric Le Goater
2026-08-11 16:28 ` [PULL 05/83] hw/usb/hcd-ehci: Add property to advertise 64-bit addressing capability Cédric Le Goater
2026-08-11 16:28 ` [PULL 06/83] hw/usb/hcd-ehci: Implement 64-bit QH descriptor addressing Cédric Le Goater
2026-08-11 16:28 ` [PULL 07/83] hw/usb/hcd-ehci: Implement 64-bit qTD " Cédric Le Goater
2026-08-11 16:28 ` [PULL 08/83] hw/usb/hcd-ehci: Implement 64-bit iTD " Cédric Le Goater
2026-08-11 16:28 ` [PULL 09/83] hw/usb/hcd-ehci: Implement 64-bit siTD " Cédric Le Goater
2026-08-11 16:28 ` [PULL 10/83] hw/usb/hcd-ehci: Add ctrldssegment-default property Cédric Le Goater
2026-08-11 16:28 ` [PULL 11/83] hw/arm/aspeed_ast27x0: Set EHCI ctrldssegment-default Cédric Le Goater
2026-08-11 16:28 ` [PULL 12/83] hw/arm/aspeed_ast27x0: Enable 64-bit EHCI DMA addressing Cédric Le Goater
2026-08-11 16:28 ` [PULL 13/83] tests/functional/aarch64/test_aspeed_ast2700: Add USB EHCI test for AST2700 A1/A2 Cédric Le Goater
2026-08-11 16:28 ` [PULL 14/83] tests/qtest: aspeed_smc: Introduce read_page_mem_fn for page read helpers Cédric Le Goater
2026-08-11 16:28 ` [PULL 15/83] tests/qtest: aspeed_smc: Add fast-read test coverage Cédric Le Goater
2026-08-11 16:28 ` [PULL 16/83] tests/qtest: aspeed_smc: Add Dual Output Read (DOR) " Cédric Le Goater
2026-08-11 16:28 ` [PULL 17/83] tests/qtest: aspeed_smc: Add Quad Output Read (QOR) " Cédric Le Goater
2026-08-11 16:28 ` [PULL 18/83] hw/misc/aspeed_scu: Introduce Aspeed2700SCUState Cédric Le Goater
2026-08-11 16:28 ` [PULL 19/83] hw/arm/aspeed: Use Aspeed2700SCUState for AST2700 users Cédric Le Goater
2026-08-11 16:28 ` [PULL 20/83] hw/arm/aspeed_ast27x0: Move SCU link into AST27x0 coprocessors Cédric Le Goater
2026-08-11 16:28 ` [PULL 21/83] hw/misc/aspeed_scu: Add separate reset handler for AST2700 SCUIO Cédric Le Goater
2026-08-11 16:28 ` [PULL 22/83] hw/arm/aspeed_ast27x0: Pass realized PSP SoC to SSP/TSP initialization Cédric Le Goater
2026-08-11 16:28 ` [PULL 23/83] hw/arm/ast27x0: Share single SCUIO instance across PSP, SSP, and TSP Cédric Le Goater
2026-08-11 16:28 ` [PULL 24/83] hw/arm/ast27x0: Share FMC controller with SSP " Cédric Le Goater
2026-08-11 16:28 ` [PULL 25/83] hw/ssi/aspeed_smc: Add Data FIFO-based flash access support for AST2700 Cédric Le Goater
2026-08-11 16:28 ` [PULL 26/83] tests/qtest/ast2700-smc-test: Add Data FIFO mode test Cédric Le Goater
2026-08-11 16:28 ` [PULL 27/83] hw/sensor: adc128d818: add 12-bit 8-channel ADC device Cédric Le Goater
2026-08-11 16:28 ` [PULL 28/83] tests/qtest: adc128d818: add test harness and register access Cédric Le Goater
2026-08-11 16:28 ` [PULL 29/83] tests/qtest: adc128d818: test voltage and temperature conversion Cédric Le Goater
2026-08-11 16:28 ` [PULL 30/83] tests/qtest: adc128d818: test limit interrupts Cédric Le Goater
2026-08-11 16:28 ` [PULL 31/83] tests/qtest: adc128d818: test operating modes and power control Cédric Le Goater
2026-08-11 16:28 ` [PULL 32/83] hw/arm/aspeed: anacapa: use ASCII in comments Cédric Le Goater
2026-08-11 16:28 ` [PULL 33/83] hw/arm: anacapa: add ADC128D818 devices Cédric Le Goater
2026-08-11 16:28 ` [PULL 34/83] hw/gpio: pca9552: register types with DEFINE_TYPES() Cédric Le Goater
2026-08-11 16:28 ` [PULL 35/83] hw/gpio: pca9552: move PCA955xState definition out of the header Cédric Le Goater
2026-08-11 16:28 ` [PULL 36/83] hw/gpio: pca9552: rename I2CSlave member to parent_obj Cédric Le Goater
2026-08-11 16:28 ` [PULL 37/83] hw/gpio: pca9552: default description to the instantiated type name Cédric Le Goater
2026-08-11 16:28 ` [PULL 38/83] hw/gpio: pca9552: declare pca9555 device as an alias of pca9535 device Cédric Le Goater
2026-08-11 16:28 ` [PULL 39/83] hw/gpio: pca9552: use the Resettable interface instead of legacy reset Cédric Le Goater
2026-08-11 16:28 ` [PULL 40/83] hw/gpio: pca9552: apply input polarity inversion on read Cédric Le Goater
2026-08-11 16:28 ` [PULL 41/83] hw/gpio: pca9552: conform GPIO command handling to the datasheet Cédric Le Goater
2026-08-11 16:28 ` [PULL 42/83] hw/gpio: pca9552: expose GPIO pins as pin%d QOM properties Cédric Le Goater
2026-08-11 16:28 ` [PULL 43/83] tests/qtest: add PCA9555 register access tests Cédric Le Goater
2026-08-11 16:28 ` [PULL 44/83] tests/qtest: pca9555: test output-to-input reflection and pull-ups Cédric Le Goater
2026-08-11 16:29 ` [PULL 45/83] tests/qtest: pca9555: test polarity inversion Cédric Le Goater
2026-08-11 16:29 ` [PULL 46/83] tests/qtest: pca9555: test auto-increment and command wrapping Cédric Le Goater
2026-08-11 16:29 ` [PULL 47/83] tests/qtest: pca9552: test behaviour specific to the LED variant Cédric Le Goater
2026-08-11 16:29 ` [PULL 48/83] hw/gpio: pca9554: add PCA9536 support Cédric Le Goater
2026-08-11 16:29 ` [PULL 49/83] hw/gpio: pca9554: add hw-dir property honoring the configured pin direction Cédric Le Goater
2026-08-11 16:29 ` [PULL 50/83] hw/gpio: pca9554: reflect push-pull outputs in the input register Cédric Le Goater
2026-08-11 16:29 ` [PULL 51/83] hw/gpio: pca9554: expose pin%d as a string property Cédric Le Goater
2026-08-11 16:29 ` [PULL 52/83] tests/qtest: add PCA9554 register access tests Cédric Le Goater
2026-08-11 16:29 ` [PULL 53/83] tests/qtest: pca9554: test output-to-input reflection and pull-ups Cédric Le Goater
2026-08-11 16:29 ` [PULL 54/83] tests/qtest: pca9554: test polarity inversion Cédric Le Goater
2026-08-11 16:29 ` [PULL 55/83] tests/qtest: pca9554: test absence of command auto-increment Cédric Le Goater
2026-08-11 16:29 ` [PULL 56/83] tests/qtest: pca9554: test the PCA9536 4-bit variant Cédric Le Goater
2026-08-11 16:29 ` [PULL 57/83] hw/arm: catalina: model PCA9555 IO expanders with their own type Cédric Le Goater
2026-08-11 16:29 ` [PULL 58/83] hw/arm: catalina: add NIC and FIO temperature sensors Cédric Le Goater
2026-08-11 16:29 ` [PULL 59/83] hw/i2c/aspeed_i2c: Support the AST2700 master buffer mode Cédric Le Goater
2026-08-11 16:29 ` [PULL 60/83] tests/functional/aarch64/test_aspeed_ast2700a2: Update ASPEED SDK v11.03 Cédric Le Goater
2026-08-11 16:29 ` [PULL 61/83] tests/functional/aarch64/test_aspeed_ast2700a1: " Cédric Le Goater
2026-08-11 16:29 ` [PULL 62/83] tests/functional/aarch64/test_aspeed_ast2700fc: " Cédric Le Goater
2026-08-11 16:29 ` [PULL 63/83] tests/functional/arm/test_aspeed_ast2600_sdk: " Cédric Le Goater
2026-08-11 16:29 ` [PULL 64/83] tests/functional/arm/test_aspeed_ast2500_sdk: " Cédric Le Goater
2026-08-11 16:29 ` [PULL 65/83] tests/functional/arm/test_aspeed_ast1030: Update ASPEED Zephyr SDK v03.08 Cédric Le Goater
2026-08-11 16:29 ` [PULL 66/83] tests/functional/arm/test_aspeed_ast1060: Update ASPEED ZEPHYR PROJECT v03.07 Cédric Le Goater
2026-08-11 16:29 ` [PULL 67/83] hw/arm/aspeed: avoid sign mismatch on sscanf for uart property Cédric Le Goater
2026-08-11 16:29 ` [PULL 68/83] hw/misc/aspeed_hace: Support the crypto command in direct access mode Cédric Le Goater
2026-08-11 16:29 ` [PULL 69/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2500 Cédric Le Goater
2026-08-11 16:29 ` [PULL 70/83] hw/misc/aspeed_hace: Support scatter-gather mode for the crypto command Cédric Le Goater
2026-08-11 16:29 ` [PULL 71/83] hw/misc/aspeed_hace: Support the CTR " Cédric Le Goater
2026-08-11 16:29 ` [PULL 72/83] tests/qtest/aspeed-hace: Test the crypto command on the AST2600 Cédric Le Goater
2026-08-11 16:29 ` [PULL 73/83] tests/qtest/aspeed-hace: Test the crypto command on the AST1030 Cédric Le Goater
2026-08-11 16:29 ` [PULL 74/83] crypto/cipher: Add GCM to QCryptoCipherMode Cédric Le Goater
2026-08-11 16:29 ` [PULL 75/83] crypto/cipher: Add setaad/gettag for AEAD modes Cédric Le Goater
2026-08-11 16:29 ` [PULL 76/83] crypto/cipher-gcrypt: Implement AES-GCM Cédric Le Goater
2026-08-11 16:29 ` [PULL 77/83] crypto/cipher-nettle: " Cédric Le Goater
2026-08-11 16:29 ` [PULL 78/83] crypto/cipher-gnutls: " Cédric Le Goater
2026-08-11 16:29 ` [PULL 79/83] tests/unit/test-crypto-cipher: Test AES-GCM mode Cédric Le Goater
2026-08-11 16:29 ` [PULL 80/83] hw/misc/aspeed_hace: Support 64-bit DMA for the crypto command Cédric Le Goater
2026-08-11 16:29 ` [PULL 81/83] hw/misc/aspeed_hace: Support the AES-GCM mode " Cédric Le Goater
2026-08-11 16:29 ` [PULL 82/83] hw/misc/aspeed_hace: Enable the crypto command on the AST2700 Cédric Le Goater
2026-08-11 16:29 ` [PULL 83/83] tests/qtest/aspeed-hace: Test " Cédric Le Goater
2026-08-12 16:15 ` [PULL 00/83] aspeed queue Richard Henderson

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.