* [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine
@ 2026-10-12 14:31 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add the minerva-bmc machine and the I2C device models it needs.
Based on master, at:
615ece3c40 ("Merge tag 'for-upstream' of
https://gitlab.com/bonzini/qemu into staging")
New models:
INA230 TI 36V current-shunt and power monitor
INA238 TI 85V current-shunt, power and temperature monitor
DS1339 RTC, a DS1338 variant
PCA9545 4-channel I2C mux with per-channel interrupt inputs
The INA devices derive current and power from the shunt voltage and
their calibration registers. ALERT output is supported. Measurements
come in through QOM properties; however, conversion delay is not
modelled.
Introduce a device class to manage different variants of the DS1338
register map, such as the DS1339. Improve the DS1338 implementation
with a Resettable reset, support for the clock-halt bit, and a
"persist-on-reset" property keeping the registers across a reset.
The minerva-bmc machine is derived from the platform device tree and
instantiates every device that is supported in QEMU. FRU EEPROMs are
sanitised images taken from real hardware.
New qtests for the DS1339, INA230, INA238 and PCA9545, plus DS1338
cases for the register map, and a functional test booting OpenBMC on
minerva-bmc.
The docs now mention the previously missing catalina-bmc and
sanmiguel-bmc machines, and the title has been simplified.
One thing worth a second opinion: the PCA954x muxes now have a
VMStateDescription, which changes the migration stream for the
existing PCA9546 and PCA9548. The DS1338 vmstate also goes from
version 2 to 3, but older streams still load fine.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
Changes in v2:
- Rebase onto master, which now contains the v1 base.
- minerva: drop ASPEED_RAM_SIZE, removed upstream.
- No other changes; v1 received no review comments.
- Link to v1: https://lore.kernel.org/qemu-devel/20260907-bmc-minerva-v1-0-0d83ad610c3b@free.fr
---
Emmanuel Blot (50):
docs/system/arm/aspeed: add the catalina-bmc machine
docs/system/arm/aspeed: add the sanmiguel-bmc machine
hw/arm: sanmiguel: remove the second SPI flash controller
hw/i2c: pca954x: drop unused header inclusions
hw/i2c: pca954x: add PCA9545 4-channel mux model
hw/i2c: pca954x: migrate mux state
tests/qtest: pca9545: add mux test
hw/rtc: ds1338: move TYPE_DS1338 to a public header
hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
hw/rtc: ds1338: implement Resettable reset
hw/rtc: ds1338: prefix the file-local helpers with ds1338_
hw/rtc: ds1338: parameterise the register map via a device class
hw/rtc: ds1338: program the clock from the time registers
hw/rtc: ds1338: implement the DS1338 clock-halt bit
hw/rtc: ds1338: refresh the user registers on a read rollover only
hw/rtc: ds1338: add a persist-on-reset property
tests/qtest: ds1338: cover the DS1338-specific register map
tests/qtest: ds1338: cover the power-on register defaults
tests/qtest: ds1338: cover the clock-halt bit
tests/qtest: ds1338: cover persist-on-reset
tests/qtest: ds1338: cover the reserved bits of the time registers
hw/rtc: ds1338: add DS1339 variant
tests/qtest: ds1339: add RTC test
tests/qtest: ds1339: cover power-on defaults and reserved-bit masking
tests/qtest: ds1339: cover oscillator stop/restart and time programming
tests/qtest: ds1339: cover the Century bit
tests/qtest: ds1339: cover the end-of-month rollover
tests/qtest: ds1339: cover the 12-hour format
tests/qtest: ds1339: cover the day-of-week counter
tests/qtest: ds1339: cover persist-on-reset
tests/qtest: ds1339: cover the migration round trip
hw/sensor: ina230: add current/voltage/power monitor
tests/qtest: ina230: add current/voltage/power monitor test
tests/qtest: ina230: cover voltage injection and power derivation
tests/qtest: ina230: cover the ALERT limit functions and CVRF
tests/qtest: ina230: cover the injection-property limits
tests/qtest: ina230: cover operating modes and triggered conversions
tests/qtest: ina230: cover ALERT latch persistence
tests/qtest: ina230: cover a cleared calibration per channel
hw/sensor: ina238: add current/voltage/power monitor
tests/qtest: ina238: add current/voltage/power monitor test
tests/qtest: ina238: cover voltage/temperature injection and derivation
tests/qtest: ina238: cover the ALERT path and conversion-ready flag
tests/qtest: ina238: cover the injection-property limits
tests/qtest: ina238: cover the operating modes
tests/qtest: ina238: cover conversion-ready and alert-latch semantics
tests/qtest: ina238: cover a cleared calibration per channel
hw/arm: minerva: add Facebook BMC machine
hw/arm: minerva: populate EEPROM data
tests/functional/arm: minerva: add BMC boot test
docs/system/arm/aspeed.rst | 7 +-
hw/arm/Kconfig | 2 +
hw/arm/aspeed_ast2400_palmetto.c | 4 +-
hw/arm/aspeed_ast2400_supermicrox11.c | 4 +-
hw/arm/aspeed_ast2500_romulus.c | 4 +-
hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +-
hw/arm/aspeed_ast2500_witherspoon.c | 4 +-
hw/arm/aspeed_ast2600_bletchley.c | 3 +-
hw/arm/aspeed_ast2600_catalina.c | 2 +-
hw/arm/aspeed_ast2600_minerva.c | 856 ++++++++++++++++++++++++++++
hw/arm/aspeed_ast2600_sanmiguel.c | 7 +-
hw/arm/meson.build | 1 +
hw/arm/realview.c | 3 +-
hw/arm/versatilepb.c | 3 +-
hw/i2c/i2c_mux_pca954x.c | 98 +++-
hw/ppc/e500.c | 3 +-
hw/riscv/tt_atlantis.c | 3 +-
hw/rtc/ds1338.c | 427 +++++++++++---
hw/sensor/Kconfig | 8 +
hw/sensor/ina230.c | 597 +++++++++++++++++++
hw/sensor/ina238.c | 802 ++++++++++++++++++++++++++
hw/sensor/meson.build | 2 +
hw/sensor/trace-events | 14 +
include/hw/i2c/i2c_mux_pca954x.h | 1 +
include/hw/rtc/ds1338.h | 21 +
include/hw/sensor/ina230.h | 14 +
include/hw/sensor/ina238.h | 14 +
tests/functional/arm/meson.build | 2 +
tests/functional/arm/test_aspeed_minerva.py | 30 +
tests/qtest/ds1338-test.c | 191 ++++++-
tests/qtest/ds1339-test.c | 581 +++++++++++++++++++
tests/qtest/ina230-test.c | 602 +++++++++++++++++++
tests/qtest/ina238-test.c | 806 ++++++++++++++++++++++++++
tests/qtest/meson.build | 4 +
tests/qtest/pca9545-test.c | 116 ++++
35 files changed, 5125 insertions(+), 115 deletions(-)
---
base-commit: 615ece3c406b262996e569f29ea6eed0d81c4d8d
change-id: 20260907-bmc-minerva-cd13a718c90b
Best regards,
--
Emmanuel Blot <emmanuel.blot@free.fr>
^ permalink raw reply [flat|nested] 188+ messages in thread
* [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine
@ 2026-10-12 14:31 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add the minerva-bmc machine and the I2C device models it needs.
Based on master, at:
615ece3c40 ("Merge tag 'for-upstream' of
https://gitlab.com/bonzini/qemu into staging")
New models:
INA230 TI 36V current-shunt and power monitor
INA238 TI 85V current-shunt, power and temperature monitor
DS1339 RTC, a DS1338 variant
PCA9545 4-channel I2C mux with per-channel interrupt inputs
The INA devices derive current and power from the shunt voltage and
their calibration registers. ALERT output is supported. Measurements
come in through QOM properties; however, conversion delay is not
modelled.
Introduce a device class to manage different variants of the DS1338
register map, such as the DS1339. Improve the DS1338 implementation
with a Resettable reset, support for the clock-halt bit, and a
"persist-on-reset" property keeping the registers across a reset.
The minerva-bmc machine is derived from the platform device tree and
instantiates every device that is supported in QEMU. FRU EEPROMs are
sanitised images taken from real hardware.
New qtests for the DS1339, INA230, INA238 and PCA9545, plus DS1338
cases for the register map, and a functional test booting OpenBMC on
minerva-bmc.
The docs now mention the previously missing catalina-bmc and
sanmiguel-bmc machines, and the title has been simplified.
One thing worth a second opinion: the PCA954x muxes now have a
VMStateDescription, which changes the migration stream for the
existing PCA9546 and PCA9548. The DS1338 vmstate also goes from
version 2 to 3, but older streams still load fine.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
Changes in v2:
- Rebase onto master, which now contains the v1 base.
- minerva: drop ASPEED_RAM_SIZE, removed upstream.
- No other changes; v1 received no review comments.
- Link to v1: https://lore.kernel.org/qemu-devel/20260907-bmc-minerva-v1-0-0d83ad610c3b@free.fr
---
Emmanuel Blot (50):
docs/system/arm/aspeed: add the catalina-bmc machine
docs/system/arm/aspeed: add the sanmiguel-bmc machine
hw/arm: sanmiguel: remove the second SPI flash controller
hw/i2c: pca954x: drop unused header inclusions
hw/i2c: pca954x: add PCA9545 4-channel mux model
hw/i2c: pca954x: migrate mux state
tests/qtest: pca9545: add mux test
hw/rtc: ds1338: move TYPE_DS1338 to a public header
hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
hw/rtc: ds1338: implement Resettable reset
hw/rtc: ds1338: prefix the file-local helpers with ds1338_
hw/rtc: ds1338: parameterise the register map via a device class
hw/rtc: ds1338: program the clock from the time registers
hw/rtc: ds1338: implement the DS1338 clock-halt bit
hw/rtc: ds1338: refresh the user registers on a read rollover only
hw/rtc: ds1338: add a persist-on-reset property
tests/qtest: ds1338: cover the DS1338-specific register map
tests/qtest: ds1338: cover the power-on register defaults
tests/qtest: ds1338: cover the clock-halt bit
tests/qtest: ds1338: cover persist-on-reset
tests/qtest: ds1338: cover the reserved bits of the time registers
hw/rtc: ds1338: add DS1339 variant
tests/qtest: ds1339: add RTC test
tests/qtest: ds1339: cover power-on defaults and reserved-bit masking
tests/qtest: ds1339: cover oscillator stop/restart and time programming
tests/qtest: ds1339: cover the Century bit
tests/qtest: ds1339: cover the end-of-month rollover
tests/qtest: ds1339: cover the 12-hour format
tests/qtest: ds1339: cover the day-of-week counter
tests/qtest: ds1339: cover persist-on-reset
tests/qtest: ds1339: cover the migration round trip
hw/sensor: ina230: add current/voltage/power monitor
tests/qtest: ina230: add current/voltage/power monitor test
tests/qtest: ina230: cover voltage injection and power derivation
tests/qtest: ina230: cover the ALERT limit functions and CVRF
tests/qtest: ina230: cover the injection-property limits
tests/qtest: ina230: cover operating modes and triggered conversions
tests/qtest: ina230: cover ALERT latch persistence
tests/qtest: ina230: cover a cleared calibration per channel
hw/sensor: ina238: add current/voltage/power monitor
tests/qtest: ina238: add current/voltage/power monitor test
tests/qtest: ina238: cover voltage/temperature injection and derivation
tests/qtest: ina238: cover the ALERT path and conversion-ready flag
tests/qtest: ina238: cover the injection-property limits
tests/qtest: ina238: cover the operating modes
tests/qtest: ina238: cover conversion-ready and alert-latch semantics
tests/qtest: ina238: cover a cleared calibration per channel
hw/arm: minerva: add Facebook BMC machine
hw/arm: minerva: populate EEPROM data
tests/functional/arm: minerva: add BMC boot test
docs/system/arm/aspeed.rst | 7 +-
hw/arm/Kconfig | 2 +
hw/arm/aspeed_ast2400_palmetto.c | 4 +-
hw/arm/aspeed_ast2400_supermicrox11.c | 4 +-
hw/arm/aspeed_ast2500_romulus.c | 4 +-
hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +-
hw/arm/aspeed_ast2500_witherspoon.c | 4 +-
hw/arm/aspeed_ast2600_bletchley.c | 3 +-
hw/arm/aspeed_ast2600_catalina.c | 2 +-
hw/arm/aspeed_ast2600_minerva.c | 856 ++++++++++++++++++++++++++++
hw/arm/aspeed_ast2600_sanmiguel.c | 7 +-
hw/arm/meson.build | 1 +
hw/arm/realview.c | 3 +-
hw/arm/versatilepb.c | 3 +-
hw/i2c/i2c_mux_pca954x.c | 98 +++-
hw/ppc/e500.c | 3 +-
hw/riscv/tt_atlantis.c | 3 +-
hw/rtc/ds1338.c | 427 +++++++++++---
hw/sensor/Kconfig | 8 +
hw/sensor/ina230.c | 597 +++++++++++++++++++
hw/sensor/ina238.c | 802 ++++++++++++++++++++++++++
hw/sensor/meson.build | 2 +
hw/sensor/trace-events | 14 +
include/hw/i2c/i2c_mux_pca954x.h | 1 +
include/hw/rtc/ds1338.h | 21 +
include/hw/sensor/ina230.h | 14 +
include/hw/sensor/ina238.h | 14 +
tests/functional/arm/meson.build | 2 +
tests/functional/arm/test_aspeed_minerva.py | 30 +
tests/qtest/ds1338-test.c | 191 ++++++-
tests/qtest/ds1339-test.c | 581 +++++++++++++++++++
tests/qtest/ina230-test.c | 602 +++++++++++++++++++
tests/qtest/ina238-test.c | 806 ++++++++++++++++++++++++++
tests/qtest/meson.build | 4 +
tests/qtest/pca9545-test.c | 116 ++++
35 files changed, 5125 insertions(+), 115 deletions(-)
---
base-commit: 615ece3c406b262996e569f29ea6eed0d81c4d8d
change-id: 20260907-bmc-minerva-cd13a718c90b
Best regards,
--
Emmanuel Blot <emmanuel.blot@free.fr>
^ permalink raw reply [flat|nested] 188+ messages in thread
* [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine
@ 2026-10-12 14:31 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add the minerva-bmc machine and the I2C device models it needs.
Based on master, at:
615ece3c40 ("Merge tag 'for-upstream' of
https://gitlab.com/bonzini/qemu into staging")
New models:
INA230 TI 36V current-shunt and power monitor
INA238 TI 85V current-shunt, power and temperature monitor
DS1339 RTC, a DS1338 variant
PCA9545 4-channel I2C mux with per-channel interrupt inputs
The INA devices derive current and power from the shunt voltage and
their calibration registers. ALERT output is supported. Measurements
come in through QOM properties; however, conversion delay is not
modelled.
Introduce a device class to manage different variants of the DS1338
register map, such as the DS1339. Improve the DS1338 implementation
with a Resettable reset, support for the clock-halt bit, and a
"persist-on-reset" property keeping the registers across a reset.
The minerva-bmc machine is derived from the platform device tree and
instantiates every device that is supported in QEMU. FRU EEPROMs are
sanitised images taken from real hardware.
New qtests for the DS1339, INA230, INA238 and PCA9545, plus DS1338
cases for the register map, and a functional test booting OpenBMC on
minerva-bmc.
The docs now mention the previously missing catalina-bmc and
sanmiguel-bmc machines, and the title has been simplified.
One thing worth a second opinion: the PCA954x muxes now have a
VMStateDescription, which changes the migration stream for the
existing PCA9546 and PCA9548. The DS1338 vmstate also goes from
version 2 to 3, but older streams still load fine.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
Changes in v2:
- Rebase onto master, which now contains the v1 base.
- minerva: drop ASPEED_RAM_SIZE, removed upstream.
- No other changes; v1 received no review comments.
- Link to v1: https://lore.kernel.org/qemu-devel/20260907-bmc-minerva-v1-0-0d83ad610c3b@free.fr
---
Emmanuel Blot (50):
docs/system/arm/aspeed: add the catalina-bmc machine
docs/system/arm/aspeed: add the sanmiguel-bmc machine
hw/arm: sanmiguel: remove the second SPI flash controller
hw/i2c: pca954x: drop unused header inclusions
hw/i2c: pca954x: add PCA9545 4-channel mux model
hw/i2c: pca954x: migrate mux state
tests/qtest: pca9545: add mux test
hw/rtc: ds1338: move TYPE_DS1338 to a public header
hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
hw/rtc: ds1338: implement Resettable reset
hw/rtc: ds1338: prefix the file-local helpers with ds1338_
hw/rtc: ds1338: parameterise the register map via a device class
hw/rtc: ds1338: program the clock from the time registers
hw/rtc: ds1338: implement the DS1338 clock-halt bit
hw/rtc: ds1338: refresh the user registers on a read rollover only
hw/rtc: ds1338: add a persist-on-reset property
tests/qtest: ds1338: cover the DS1338-specific register map
tests/qtest: ds1338: cover the power-on register defaults
tests/qtest: ds1338: cover the clock-halt bit
tests/qtest: ds1338: cover persist-on-reset
tests/qtest: ds1338: cover the reserved bits of the time registers
hw/rtc: ds1338: add DS1339 variant
tests/qtest: ds1339: add RTC test
tests/qtest: ds1339: cover power-on defaults and reserved-bit masking
tests/qtest: ds1339: cover oscillator stop/restart and time programming
tests/qtest: ds1339: cover the Century bit
tests/qtest: ds1339: cover the end-of-month rollover
tests/qtest: ds1339: cover the 12-hour format
tests/qtest: ds1339: cover the day-of-week counter
tests/qtest: ds1339: cover persist-on-reset
tests/qtest: ds1339: cover the migration round trip
hw/sensor: ina230: add current/voltage/power monitor
tests/qtest: ina230: add current/voltage/power monitor test
tests/qtest: ina230: cover voltage injection and power derivation
tests/qtest: ina230: cover the ALERT limit functions and CVRF
tests/qtest: ina230: cover the injection-property limits
tests/qtest: ina230: cover operating modes and triggered conversions
tests/qtest: ina230: cover ALERT latch persistence
tests/qtest: ina230: cover a cleared calibration per channel
hw/sensor: ina238: add current/voltage/power monitor
tests/qtest: ina238: add current/voltage/power monitor test
tests/qtest: ina238: cover voltage/temperature injection and derivation
tests/qtest: ina238: cover the ALERT path and conversion-ready flag
tests/qtest: ina238: cover the injection-property limits
tests/qtest: ina238: cover the operating modes
tests/qtest: ina238: cover conversion-ready and alert-latch semantics
tests/qtest: ina238: cover a cleared calibration per channel
hw/arm: minerva: add Facebook BMC machine
hw/arm: minerva: populate EEPROM data
tests/functional/arm: minerva: add BMC boot test
docs/system/arm/aspeed.rst | 7 +-
hw/arm/Kconfig | 2 +
hw/arm/aspeed_ast2400_palmetto.c | 4 +-
hw/arm/aspeed_ast2400_supermicrox11.c | 4 +-
hw/arm/aspeed_ast2500_romulus.c | 4 +-
hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +-
hw/arm/aspeed_ast2500_witherspoon.c | 4 +-
hw/arm/aspeed_ast2600_bletchley.c | 3 +-
hw/arm/aspeed_ast2600_catalina.c | 2 +-
hw/arm/aspeed_ast2600_minerva.c | 856 ++++++++++++++++++++++++++++
hw/arm/aspeed_ast2600_sanmiguel.c | 7 +-
hw/arm/meson.build | 1 +
hw/arm/realview.c | 3 +-
hw/arm/versatilepb.c | 3 +-
hw/i2c/i2c_mux_pca954x.c | 98 +++-
hw/ppc/e500.c | 3 +-
hw/riscv/tt_atlantis.c | 3 +-
hw/rtc/ds1338.c | 427 +++++++++++---
hw/sensor/Kconfig | 8 +
hw/sensor/ina230.c | 597 +++++++++++++++++++
hw/sensor/ina238.c | 802 ++++++++++++++++++++++++++
hw/sensor/meson.build | 2 +
hw/sensor/trace-events | 14 +
include/hw/i2c/i2c_mux_pca954x.h | 1 +
include/hw/rtc/ds1338.h | 21 +
include/hw/sensor/ina230.h | 14 +
include/hw/sensor/ina238.h | 14 +
tests/functional/arm/meson.build | 2 +
tests/functional/arm/test_aspeed_minerva.py | 30 +
tests/qtest/ds1338-test.c | 191 ++++++-
tests/qtest/ds1339-test.c | 581 +++++++++++++++++++
tests/qtest/ina230-test.c | 602 +++++++++++++++++++
tests/qtest/ina238-test.c | 806 ++++++++++++++++++++++++++
tests/qtest/meson.build | 4 +
tests/qtest/pca9545-test.c | 116 ++++
35 files changed, 5125 insertions(+), 115 deletions(-)
---
base-commit: 615ece3c406b262996e569f29ea6eed0d81c4d8d
change-id: 20260907-bmc-minerva-cd13a718c90b
Best regards,
--
Emmanuel Blot <emmanuel.blot@free.fr>
^ permalink raw reply [flat|nested] 188+ messages in thread
* [PATCH v2 01/50] docs/system/arm/aspeed: add the catalina-bmc machine
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:31 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Facebook Catalina BMC machine is supported by hw/arm but has never
been listed in the Aspeed documentation. Add it to the AST2600 machine
table.
While at it, shorten the document title. Listing every machine name in
the title duplicates the tables right below it, and made the title (and
its underline) grow past 350 columns. The machine list stays available
where it is readable, in the per-SoC tables.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 2d51ceeb84..933df41f96 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -1,5 +1,5 @@
-Aspeed family boards (``anacapa-bmc``, ``ast2500-evb``, ``ast2600-evb``, ``bletchley-bmc``, ``fuji-bmc``, ``gb200nvl-bmc``, ``fby35-bmc``, ``g220a-bmc``, ``palmetto-bmc``, ``quanta-q71l-bmc``, ``rainier-bmc``, ``romulus-bmc``, ``supermicrox11-bmc``, ``supermicrox11spi-bmc``, ``tiogapass-bmc``, ``witherspoon-bmc``, ``yosemitev2-bmc``)
-===============================================================================================================================================================================================================================================================================================================================================
+Aspeed family boards
+====================
The QEMU Aspeed machines model BMCs of various OpenPOWER systems and
Aspeed evaluation boards. They are based on different releases of the
@@ -33,6 +33,7 @@ AST2600 SoC based machines :
- ``fuji-bmc`` Facebook Fuji BMC
- ``bletchley-bmc`` Facebook Bletchley BMC
- ``fby35-bmc`` Facebook fby35 BMC
+- ``catalina-bmc`` Facebook Catalina BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 01/50] docs/system/arm/aspeed: add the catalina-bmc machine
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Facebook Catalina BMC machine is supported by hw/arm but has never
been listed in the Aspeed documentation. Add it to the AST2600 machine
table.
While at it, shorten the document title. Listing every machine name in
the title duplicates the tables right below it, and made the title (and
its underline) grow past 350 columns. The machine list stays available
where it is readable, in the per-SoC tables.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 2d51ceeb84..933df41f96 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -1,5 +1,5 @@
-Aspeed family boards (``anacapa-bmc``, ``ast2500-evb``, ``ast2600-evb``, ``bletchley-bmc``, ``fuji-bmc``, ``gb200nvl-bmc``, ``fby35-bmc``, ``g220a-bmc``, ``palmetto-bmc``, ``quanta-q71l-bmc``, ``rainier-bmc``, ``romulus-bmc``, ``supermicrox11-bmc``, ``supermicrox11spi-bmc``, ``tiogapass-bmc``, ``witherspoon-bmc``, ``yosemitev2-bmc``)
-===============================================================================================================================================================================================================================================================================================================================================
+Aspeed family boards
+====================
The QEMU Aspeed machines model BMCs of various OpenPOWER systems and
Aspeed evaluation boards. They are based on different releases of the
@@ -33,6 +33,7 @@ AST2600 SoC based machines :
- ``fuji-bmc`` Facebook Fuji BMC
- ``bletchley-bmc`` Facebook Bletchley BMC
- ``fby35-bmc`` Facebook fby35 BMC
+- ``catalina-bmc`` Facebook Catalina BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 01/50] docs/system/arm/aspeed: add the catalina-bmc machine
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Facebook Catalina BMC machine is supported by hw/arm but has never
been listed in the Aspeed documentation. Add it to the AST2600 machine
table.
While at it, shorten the document title. Listing every machine name in
the title duplicates the tables right below it, and made the title (and
its underline) grow past 350 columns. The machine list stays available
where it is readable, in the per-SoC tables.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 2d51ceeb84..933df41f96 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -1,5 +1,5 @@
-Aspeed family boards (``anacapa-bmc``, ``ast2500-evb``, ``ast2600-evb``, ``bletchley-bmc``, ``fuji-bmc``, ``gb200nvl-bmc``, ``fby35-bmc``, ``g220a-bmc``, ``palmetto-bmc``, ``quanta-q71l-bmc``, ``rainier-bmc``, ``romulus-bmc``, ``supermicrox11-bmc``, ``supermicrox11spi-bmc``, ``tiogapass-bmc``, ``witherspoon-bmc``, ``yosemitev2-bmc``)
-===============================================================================================================================================================================================================================================================================================================================================
+Aspeed family boards
+====================
The QEMU Aspeed machines model BMCs of various OpenPOWER systems and
Aspeed evaluation boards. They are based on different releases of the
@@ -33,6 +33,7 @@ AST2600 SoC based machines :
- ``fuji-bmc`` Facebook Fuji BMC
- ``bletchley-bmc`` Facebook Bletchley BMC
- ``fby35-bmc`` Facebook fby35 BMC
+- ``catalina-bmc`` Facebook Catalina BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 02/50] docs/system/arm/aspeed: add the sanmiguel-bmc machine
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:31 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Facebook SanMiguel BMC machine is supported by hw/arm but has never
been listed in the Aspeed documentation. Add it to the AST2600 machine
table.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 933df41f96..f38f33cbe1 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -34,6 +34,7 @@ AST2600 SoC based machines :
- ``bletchley-bmc`` Facebook Bletchley BMC
- ``fby35-bmc`` Facebook fby35 BMC
- ``catalina-bmc`` Facebook Catalina BMC
+- ``sanmiguel-bmc`` Facebook SanMiguel BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 02/50] docs/system/arm/aspeed: add the sanmiguel-bmc machine
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Facebook SanMiguel BMC machine is supported by hw/arm but has never
been listed in the Aspeed documentation. Add it to the AST2600 machine
table.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 933df41f96..f38f33cbe1 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -34,6 +34,7 @@ AST2600 SoC based machines :
- ``bletchley-bmc`` Facebook Bletchley BMC
- ``fby35-bmc`` Facebook fby35 BMC
- ``catalina-bmc`` Facebook Catalina BMC
+- ``sanmiguel-bmc`` Facebook SanMiguel BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 02/50] docs/system/arm/aspeed: add the sanmiguel-bmc machine
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Facebook SanMiguel BMC machine is supported by hw/arm but has never
been listed in the Aspeed documentation. Add it to the AST2600 machine
table.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 933df41f96..f38f33cbe1 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -34,6 +34,7 @@ AST2600 SoC based machines :
- ``bletchley-bmc`` Facebook Bletchley BMC
- ``fby35-bmc`` Facebook fby35 BMC
- ``catalina-bmc`` Facebook Catalina BMC
+- ``sanmiguel-bmc`` Facebook SanMiguel BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 03/50] hw/arm: sanmiguel: remove the second SPI flash controller
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:31 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The board only populates the flash device attached to the FMC
controller; the SoC SPI controllers are left unused. Advertising a
part there presents the guest with a flash device the hardware does
not have.
While here, add the missing year to the copyright line.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2600_sanmiguel.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
index 345a9c9416..ac2aef740a 100644
--- a/hw/arm/aspeed_ast2600_sanmiguel.c
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -1,7 +1,7 @@
/*
* Facebook SanMiguel BMC
*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -384,7 +384,7 @@ static void aspeed_machine_sanmiguel_class_init(ObjectClass *oc,
amc->hw_strap1 = SANMIGUEL_BMC_HW_STRAP1;
amc->hw_strap2 = SANMIGUEL_BMC_HW_STRAP2;
amc->fmc_model = "mx66l1g45g";
- amc->spi_model = "mx66l1g45g";
+ amc->spi_model = NULL;
amc->num_cs = 2;
amc->macs_mask = ASPEED_MAC0_ON;
amc->i2c_init = sanmiguel_bmc_i2c_init;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 03/50] hw/arm: sanmiguel: remove the second SPI flash controller
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The board only populates the flash device attached to the FMC
controller; the SoC SPI controllers are left unused. Advertising a
part there presents the guest with a flash device the hardware does
not have.
While here, add the missing year to the copyright line.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2600_sanmiguel.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
index 345a9c9416..ac2aef740a 100644
--- a/hw/arm/aspeed_ast2600_sanmiguel.c
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -1,7 +1,7 @@
/*
* Facebook SanMiguel BMC
*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -384,7 +384,7 @@ static void aspeed_machine_sanmiguel_class_init(ObjectClass *oc,
amc->hw_strap1 = SANMIGUEL_BMC_HW_STRAP1;
amc->hw_strap2 = SANMIGUEL_BMC_HW_STRAP2;
amc->fmc_model = "mx66l1g45g";
- amc->spi_model = "mx66l1g45g";
+ amc->spi_model = NULL;
amc->num_cs = 2;
amc->macs_mask = ASPEED_MAC0_ON;
amc->i2c_init = sanmiguel_bmc_i2c_init;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 03/50] hw/arm: sanmiguel: remove the second SPI flash controller
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The board only populates the flash device attached to the FMC
controller; the SoC SPI controllers are left unused. Advertising a
part there presents the guest with a flash device the hardware does
not have.
While here, add the missing year to the copyright line.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2600_sanmiguel.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
index 345a9c9416..ac2aef740a 100644
--- a/hw/arm/aspeed_ast2600_sanmiguel.c
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -1,7 +1,7 @@
/*
* Facebook SanMiguel BMC
*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -384,7 +384,7 @@ static void aspeed_machine_sanmiguel_class_init(ObjectClass *oc,
amc->hw_strap1 = SANMIGUEL_BMC_HW_STRAP1;
amc->hw_strap2 = SANMIGUEL_BMC_HW_STRAP2;
amc->fmc_model = "mx66l1g45g";
- amc->spi_model = "mx66l1g45g";
+ amc->spi_model = NULL;
amc->num_cs = 2;
amc->macs_mask = ASPEED_MAC0_ON;
amc->i2c_init = sanmiguel_bmc_i2c_init;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 04/50] hw/i2c: pca954x: drop unused header inclusions
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:31 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The qapi/error.h, hw/core/sysbus.h and qemu/module.h headers are
included but not used by i2c_mux_pca954x.c. Remove them.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index c603446028..c8e75da8b2 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -15,15 +15,12 @@
*/
#include "qemu/osdep.h"
-#include "qapi/error.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_slave.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
-#include "hw/core/sysbus.h"
#include "qemu/log.h"
-#include "qemu/module.h"
#include "qemu/queue.h"
#include "qom/object.h"
#include "trace.h"
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 04/50] hw/i2c: pca954x: drop unused header inclusions
@ 2026-10-12 14:31 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The qapi/error.h, hw/core/sysbus.h and qemu/module.h headers are
included but not used by i2c_mux_pca954x.c. Remove them.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index c603446028..c8e75da8b2 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -15,15 +15,12 @@
*/
#include "qemu/osdep.h"
-#include "qapi/error.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_slave.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
-#include "hw/core/sysbus.h"
#include "qemu/log.h"
-#include "qemu/module.h"
#include "qemu/queue.h"
#include "qom/object.h"
#include "trace.h"
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 04/50] hw/i2c: pca954x: drop unused header inclusions
@ 2026-10-12 14:31 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The qapi/error.h, hw/core/sysbus.h and qemu/module.h headers are
included but not used by i2c_mux_pca954x.c. Remove them.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index c603446028..c8e75da8b2 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -15,15 +15,12 @@
*/
#include "qemu/osdep.h"
-#include "qapi/error.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_slave.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
-#include "hw/core/sysbus.h"
#include "qemu/log.h"
-#include "qemu/module.h"
#include "qemu/queue.h"
#include "qom/object.h"
#include "trace.h"
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 05/50] hw/i2c: pca954x: add PCA9545 4-channel mux model
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:31 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The PCA9545 is a 4-channel I2C mux/switch. What sets it apart from the
plain PCA9546 is its interrupt logic: one INTn input per channel and a
shared INT output, both active low, asserted whenever any channel
interrupt is active.
Model this behind a new has_irq class flag. The PCA9545 gains four named
"interrupt" GPIO inputs and an "interrupt-out" output, and its control
read-back merges the latched status nibble; the plain PCA9546/PCA9548
switches keep their previous behaviour.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 71 ++++++++++++++++++++++++++++++++++++++--
include/hw/i2c/i2c_mux_pca954x.h | 1 +
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index c8e75da8b2..4bb078a03c 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -12,12 +12,20 @@
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
+ *
+ * Limitations:
+ * - A control write longer than one byte is rejected rather than retaining the
+ * last received byte as the datasheet specifies.
+ * - On reset the cached per-channel interrupt state is cleared; a downstream
+ * INT input that stays asserted across the reset is not re-sampled until it
+ * next toggles.
*/
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_slave.h"
+#include "hw/core/irq.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
#include "qemu/log.h"
@@ -26,11 +34,15 @@
#include "trace.h"
#define PCA9548_CHANNEL_COUNT 8
+#define PCA9545_CHANNEL_COUNT 4
#define PCA9546_CHANNEL_COUNT 4
/*
* struct Pca954xState - The pca954x state object.
* @control: The value written to the mux control.
+ * @int_status: Per-channel interrupt status, for parts with interrupt logic.
+ * @int_out: The mux INT output, driven low while any channel interrupt is
+ * active.
* @channel: The set of i2c channel buses that act as channels which own the
* i2c children.
*/
@@ -38,6 +50,8 @@ typedef struct Pca954xState {
SMBusDevice parent;
uint8_t control;
+ uint8_t int_status;
+ qemu_irq int_out;
bool enabled[PCA9548_CHANNEL_COUNT];
I2CBus *bus[PCA9548_CHANNEL_COUNT];
@@ -48,11 +62,13 @@ typedef struct Pca954xState {
/*
* struct Pca954xClass - The pca954x class object.
* @nchans: The number of i2c channels this device has.
+ * @has_irq: Whether the part implements interrupt logic.
*/
typedef struct Pca954xClass {
SMBusDeviceClass parent;
uint8_t nchans;
+ bool has_irq;
} Pca954xClass;
#define TYPE_PCA954X "pca954x"
@@ -116,8 +132,10 @@ static void pca954x_enable_channel(Pca954xState *s, uint8_t enable_mask)
static void pca954x_write(Pca954xState *s, uint8_t data)
{
- s->control = data;
- pca954x_enable_channel(s, data);
+ Pca954xClass *c = PCA954X_GET_CLASS(s);
+
+ s->control = c->has_irq ? data & ((1 << c->nchans) - 1) : data;
+ pca954x_enable_channel(s, s->control);
trace_pca954x_write_bytes(data);
}
@@ -148,16 +166,45 @@ static int pca954x_write_data(SMBusDevice *d, uint8_t *buf, uint8_t len)
static uint8_t pca954x_read_byte(SMBusDevice *d)
{
Pca954xState *s = PCA954X(d);
+ Pca954xClass *c = PCA954X_GET_CLASS(s);
uint8_t data = s->control;
+
+ /*
+ * On parts with interrupt logic the read-back byte carries the per-channel
+ * interrupt status in the upper nibble.
+ */
+ if (c->has_irq) {
+ data = (s->int_status << 4) | (s->control & 0x0f);
+ }
+
trace_pca954x_read_data(data);
return data;
}
+/*
+ * A downstream device drives one of the INTn inputs. INTn and INT are active
+ * low, and both carry the physical level: 0 is asserted, 1 is idle.
+ */
+static void pca954x_irq_handler(void *opaque, int n, int level)
+{
+ Pca954xState *s = PCA954X(opaque);
+
+ if (level) {
+ s->int_status &= ~(1 << n);
+ } else {
+ s->int_status |= (1 << n);
+ }
+
+ qemu_set_irq(s->int_out, s->int_status == 0);
+}
+
static void pca954x_enter_reset(Object *obj, ResetType type)
{
Pca954xState *s = PCA954X(obj);
/* Reset will disable all channels. */
pca954x_write(s, 0);
+ s->int_status = 0;
+ qemu_set_irq(s->int_out, 1); /* INT released */
}
I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
@@ -169,6 +216,13 @@ I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
return pca954x->bus[channel];
}
+static void pca9545_class_init(ObjectClass *klass, const void *data)
+{
+ Pca954xClass *s = PCA954X_CLASS(klass);
+ s->nchans = PCA9545_CHANNEL_COUNT;
+ s->has_irq = true;
+}
+
static void pca9546_class_init(ObjectClass *klass, const void *data)
{
Pca954xClass *s = PCA954X_CLASS(klass);
@@ -184,12 +238,20 @@ static void pca9548_class_init(ObjectClass *klass, const void *data)
static void pca954x_realize(DeviceState *dev, Error **errp)
{
Pca954xState *s = PCA954X(dev);
+ Pca954xClass *c = PCA954X_GET_CLASS(dev);
DeviceState *d = DEVICE(s);
if (s->name) {
d->id = g_strdup(s->name);
} else {
d->id = g_strdup_printf("pca954x[%x]", s->parent.i2c.address);
}
+
+ if (c->has_irq) {
+ /* One INTn input per channel, plus the shared INT output. */
+ qdev_init_gpio_in_named(dev, pca954x_irq_handler, "interrupt",
+ c->nchans);
+ qdev_init_gpio_out_named(dev, &s->int_out, "interrupt-out", 1);
+ }
}
static void pca954x_init(Object *obj)
@@ -242,6 +304,11 @@ static const TypeInfo pca954x_info[] = {
.class_init = pca954x_class_init,
.abstract = true,
},
+ {
+ .name = TYPE_PCA9545,
+ .parent = TYPE_PCA954X,
+ .class_init = pca9545_class_init,
+ },
{
.name = TYPE_PCA9546,
.parent = TYPE_PCA954X,
diff --git a/include/hw/i2c/i2c_mux_pca954x.h b/include/hw/i2c/i2c_mux_pca954x.h
index 3dd25ec983..19a1050505 100644
--- a/include/hw/i2c/i2c_mux_pca954x.h
+++ b/include/hw/i2c/i2c_mux_pca954x.h
@@ -3,6 +3,7 @@
#include "hw/i2c/i2c.h"
+#define TYPE_PCA9545 "pca9545"
#define TYPE_PCA9546 "pca9546"
#define TYPE_PCA9548 "pca9548"
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 05/50] hw/i2c: pca954x: add PCA9545 4-channel mux model
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The PCA9545 is a 4-channel I2C mux/switch. What sets it apart from the
plain PCA9546 is its interrupt logic: one INTn input per channel and a
shared INT output, both active low, asserted whenever any channel
interrupt is active.
Model this behind a new has_irq class flag. The PCA9545 gains four named
"interrupt" GPIO inputs and an "interrupt-out" output, and its control
read-back merges the latched status nibble; the plain PCA9546/PCA9548
switches keep their previous behaviour.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 71 ++++++++++++++++++++++++++++++++++++++--
include/hw/i2c/i2c_mux_pca954x.h | 1 +
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index c8e75da8b2..4bb078a03c 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -12,12 +12,20 @@
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
+ *
+ * Limitations:
+ * - A control write longer than one byte is rejected rather than retaining the
+ * last received byte as the datasheet specifies.
+ * - On reset the cached per-channel interrupt state is cleared; a downstream
+ * INT input that stays asserted across the reset is not re-sampled until it
+ * next toggles.
*/
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_slave.h"
+#include "hw/core/irq.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
#include "qemu/log.h"
@@ -26,11 +34,15 @@
#include "trace.h"
#define PCA9548_CHANNEL_COUNT 8
+#define PCA9545_CHANNEL_COUNT 4
#define PCA9546_CHANNEL_COUNT 4
/*
* struct Pca954xState - The pca954x state object.
* @control: The value written to the mux control.
+ * @int_status: Per-channel interrupt status, for parts with interrupt logic.
+ * @int_out: The mux INT output, driven low while any channel interrupt is
+ * active.
* @channel: The set of i2c channel buses that act as channels which own the
* i2c children.
*/
@@ -38,6 +50,8 @@ typedef struct Pca954xState {
SMBusDevice parent;
uint8_t control;
+ uint8_t int_status;
+ qemu_irq int_out;
bool enabled[PCA9548_CHANNEL_COUNT];
I2CBus *bus[PCA9548_CHANNEL_COUNT];
@@ -48,11 +62,13 @@ typedef struct Pca954xState {
/*
* struct Pca954xClass - The pca954x class object.
* @nchans: The number of i2c channels this device has.
+ * @has_irq: Whether the part implements interrupt logic.
*/
typedef struct Pca954xClass {
SMBusDeviceClass parent;
uint8_t nchans;
+ bool has_irq;
} Pca954xClass;
#define TYPE_PCA954X "pca954x"
@@ -116,8 +132,10 @@ static void pca954x_enable_channel(Pca954xState *s, uint8_t enable_mask)
static void pca954x_write(Pca954xState *s, uint8_t data)
{
- s->control = data;
- pca954x_enable_channel(s, data);
+ Pca954xClass *c = PCA954X_GET_CLASS(s);
+
+ s->control = c->has_irq ? data & ((1 << c->nchans) - 1) : data;
+ pca954x_enable_channel(s, s->control);
trace_pca954x_write_bytes(data);
}
@@ -148,16 +166,45 @@ static int pca954x_write_data(SMBusDevice *d, uint8_t *buf, uint8_t len)
static uint8_t pca954x_read_byte(SMBusDevice *d)
{
Pca954xState *s = PCA954X(d);
+ Pca954xClass *c = PCA954X_GET_CLASS(s);
uint8_t data = s->control;
+
+ /*
+ * On parts with interrupt logic the read-back byte carries the per-channel
+ * interrupt status in the upper nibble.
+ */
+ if (c->has_irq) {
+ data = (s->int_status << 4) | (s->control & 0x0f);
+ }
+
trace_pca954x_read_data(data);
return data;
}
+/*
+ * A downstream device drives one of the INTn inputs. INTn and INT are active
+ * low, and both carry the physical level: 0 is asserted, 1 is idle.
+ */
+static void pca954x_irq_handler(void *opaque, int n, int level)
+{
+ Pca954xState *s = PCA954X(opaque);
+
+ if (level) {
+ s->int_status &= ~(1 << n);
+ } else {
+ s->int_status |= (1 << n);
+ }
+
+ qemu_set_irq(s->int_out, s->int_status == 0);
+}
+
static void pca954x_enter_reset(Object *obj, ResetType type)
{
Pca954xState *s = PCA954X(obj);
/* Reset will disable all channels. */
pca954x_write(s, 0);
+ s->int_status = 0;
+ qemu_set_irq(s->int_out, 1); /* INT released */
}
I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
@@ -169,6 +216,13 @@ I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
return pca954x->bus[channel];
}
+static void pca9545_class_init(ObjectClass *klass, const void *data)
+{
+ Pca954xClass *s = PCA954X_CLASS(klass);
+ s->nchans = PCA9545_CHANNEL_COUNT;
+ s->has_irq = true;
+}
+
static void pca9546_class_init(ObjectClass *klass, const void *data)
{
Pca954xClass *s = PCA954X_CLASS(klass);
@@ -184,12 +238,20 @@ static void pca9548_class_init(ObjectClass *klass, const void *data)
static void pca954x_realize(DeviceState *dev, Error **errp)
{
Pca954xState *s = PCA954X(dev);
+ Pca954xClass *c = PCA954X_GET_CLASS(dev);
DeviceState *d = DEVICE(s);
if (s->name) {
d->id = g_strdup(s->name);
} else {
d->id = g_strdup_printf("pca954x[%x]", s->parent.i2c.address);
}
+
+ if (c->has_irq) {
+ /* One INTn input per channel, plus the shared INT output. */
+ qdev_init_gpio_in_named(dev, pca954x_irq_handler, "interrupt",
+ c->nchans);
+ qdev_init_gpio_out_named(dev, &s->int_out, "interrupt-out", 1);
+ }
}
static void pca954x_init(Object *obj)
@@ -242,6 +304,11 @@ static const TypeInfo pca954x_info[] = {
.class_init = pca954x_class_init,
.abstract = true,
},
+ {
+ .name = TYPE_PCA9545,
+ .parent = TYPE_PCA954X,
+ .class_init = pca9545_class_init,
+ },
{
.name = TYPE_PCA9546,
.parent = TYPE_PCA954X,
diff --git a/include/hw/i2c/i2c_mux_pca954x.h b/include/hw/i2c/i2c_mux_pca954x.h
index 3dd25ec983..19a1050505 100644
--- a/include/hw/i2c/i2c_mux_pca954x.h
+++ b/include/hw/i2c/i2c_mux_pca954x.h
@@ -3,6 +3,7 @@
#include "hw/i2c/i2c.h"
+#define TYPE_PCA9545 "pca9545"
#define TYPE_PCA9546 "pca9546"
#define TYPE_PCA9548 "pca9548"
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 05/50] hw/i2c: pca954x: add PCA9545 4-channel mux model
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The PCA9545 is a 4-channel I2C mux/switch. What sets it apart from the
plain PCA9546 is its interrupt logic: one INTn input per channel and a
shared INT output, both active low, asserted whenever any channel
interrupt is active.
Model this behind a new has_irq class flag. The PCA9545 gains four named
"interrupt" GPIO inputs and an "interrupt-out" output, and its control
read-back merges the latched status nibble; the plain PCA9546/PCA9548
switches keep their previous behaviour.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 71 ++++++++++++++++++++++++++++++++++++++--
include/hw/i2c/i2c_mux_pca954x.h | 1 +
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index c8e75da8b2..4bb078a03c 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -12,12 +12,20 @@
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
+ *
+ * Limitations:
+ * - A control write longer than one byte is rejected rather than retaining the
+ * last received byte as the datasheet specifies.
+ * - On reset the cached per-channel interrupt state is cleared; a downstream
+ * INT input that stays asserted across the reset is not re-sampled until it
+ * next toggles.
*/
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_slave.h"
+#include "hw/core/irq.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
#include "qemu/log.h"
@@ -26,11 +34,15 @@
#include "trace.h"
#define PCA9548_CHANNEL_COUNT 8
+#define PCA9545_CHANNEL_COUNT 4
#define PCA9546_CHANNEL_COUNT 4
/*
* struct Pca954xState - The pca954x state object.
* @control: The value written to the mux control.
+ * @int_status: Per-channel interrupt status, for parts with interrupt logic.
+ * @int_out: The mux INT output, driven low while any channel interrupt is
+ * active.
* @channel: The set of i2c channel buses that act as channels which own the
* i2c children.
*/
@@ -38,6 +50,8 @@ typedef struct Pca954xState {
SMBusDevice parent;
uint8_t control;
+ uint8_t int_status;
+ qemu_irq int_out;
bool enabled[PCA9548_CHANNEL_COUNT];
I2CBus *bus[PCA9548_CHANNEL_COUNT];
@@ -48,11 +62,13 @@ typedef struct Pca954xState {
/*
* struct Pca954xClass - The pca954x class object.
* @nchans: The number of i2c channels this device has.
+ * @has_irq: Whether the part implements interrupt logic.
*/
typedef struct Pca954xClass {
SMBusDeviceClass parent;
uint8_t nchans;
+ bool has_irq;
} Pca954xClass;
#define TYPE_PCA954X "pca954x"
@@ -116,8 +132,10 @@ static void pca954x_enable_channel(Pca954xState *s, uint8_t enable_mask)
static void pca954x_write(Pca954xState *s, uint8_t data)
{
- s->control = data;
- pca954x_enable_channel(s, data);
+ Pca954xClass *c = PCA954X_GET_CLASS(s);
+
+ s->control = c->has_irq ? data & ((1 << c->nchans) - 1) : data;
+ pca954x_enable_channel(s, s->control);
trace_pca954x_write_bytes(data);
}
@@ -148,16 +166,45 @@ static int pca954x_write_data(SMBusDevice *d, uint8_t *buf, uint8_t len)
static uint8_t pca954x_read_byte(SMBusDevice *d)
{
Pca954xState *s = PCA954X(d);
+ Pca954xClass *c = PCA954X_GET_CLASS(s);
uint8_t data = s->control;
+
+ /*
+ * On parts with interrupt logic the read-back byte carries the per-channel
+ * interrupt status in the upper nibble.
+ */
+ if (c->has_irq) {
+ data = (s->int_status << 4) | (s->control & 0x0f);
+ }
+
trace_pca954x_read_data(data);
return data;
}
+/*
+ * A downstream device drives one of the INTn inputs. INTn and INT are active
+ * low, and both carry the physical level: 0 is asserted, 1 is idle.
+ */
+static void pca954x_irq_handler(void *opaque, int n, int level)
+{
+ Pca954xState *s = PCA954X(opaque);
+
+ if (level) {
+ s->int_status &= ~(1 << n);
+ } else {
+ s->int_status |= (1 << n);
+ }
+
+ qemu_set_irq(s->int_out, s->int_status == 0);
+}
+
static void pca954x_enter_reset(Object *obj, ResetType type)
{
Pca954xState *s = PCA954X(obj);
/* Reset will disable all channels. */
pca954x_write(s, 0);
+ s->int_status = 0;
+ qemu_set_irq(s->int_out, 1); /* INT released */
}
I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
@@ -169,6 +216,13 @@ I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
return pca954x->bus[channel];
}
+static void pca9545_class_init(ObjectClass *klass, const void *data)
+{
+ Pca954xClass *s = PCA954X_CLASS(klass);
+ s->nchans = PCA9545_CHANNEL_COUNT;
+ s->has_irq = true;
+}
+
static void pca9546_class_init(ObjectClass *klass, const void *data)
{
Pca954xClass *s = PCA954X_CLASS(klass);
@@ -184,12 +238,20 @@ static void pca9548_class_init(ObjectClass *klass, const void *data)
static void pca954x_realize(DeviceState *dev, Error **errp)
{
Pca954xState *s = PCA954X(dev);
+ Pca954xClass *c = PCA954X_GET_CLASS(dev);
DeviceState *d = DEVICE(s);
if (s->name) {
d->id = g_strdup(s->name);
} else {
d->id = g_strdup_printf("pca954x[%x]", s->parent.i2c.address);
}
+
+ if (c->has_irq) {
+ /* One INTn input per channel, plus the shared INT output. */
+ qdev_init_gpio_in_named(dev, pca954x_irq_handler, "interrupt",
+ c->nchans);
+ qdev_init_gpio_out_named(dev, &s->int_out, "interrupt-out", 1);
+ }
}
static void pca954x_init(Object *obj)
@@ -242,6 +304,11 @@ static const TypeInfo pca954x_info[] = {
.class_init = pca954x_class_init,
.abstract = true,
},
+ {
+ .name = TYPE_PCA9545,
+ .parent = TYPE_PCA954X,
+ .class_init = pca9545_class_init,
+ },
{
.name = TYPE_PCA9546,
.parent = TYPE_PCA954X,
diff --git a/include/hw/i2c/i2c_mux_pca954x.h b/include/hw/i2c/i2c_mux_pca954x.h
index 3dd25ec983..19a1050505 100644
--- a/include/hw/i2c/i2c_mux_pca954x.h
+++ b/include/hw/i2c/i2c_mux_pca954x.h
@@ -3,6 +3,7 @@
#include "hw/i2c/i2c.h"
+#define TYPE_PCA9545 "pca9545"
#define TYPE_PCA9546 "pca9546"
#define TYPE_PCA9548 "pca9548"
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 06/50] hw/i2c: pca954x: migrate mux state
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:31 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The pca954x model had no VMStateDescription, so neither the channel
selection nor the PCA9545 interrupt status survived a migration.
This changes the migration stream of the pre-existing PCA9546 and
PCA9548, which an older QEMU rejects.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index 4bb078a03c..597e57d99a 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -28,6 +28,7 @@
#include "hw/core/irq.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/queue.h"
#include "qom/object.h"
@@ -270,6 +271,28 @@ static void pca954x_init(Object *obj)
}
}
+static int pca954x_post_load(void *opaque, int version_id)
+{
+ Pca954xState *s = PCA954X(opaque);
+
+ pca954x_enable_channel(s, s->control);
+ qemu_set_irq(s->int_out, s->int_status == 0);
+ return 0;
+}
+
+static const VMStateDescription vmstate_pca954x = {
+ .name = "pca954x",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pca954x_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_SMBUS_DEVICE(parent, Pca954xState),
+ VMSTATE_UINT8(control, Pca954xState),
+ VMSTATE_UINT8(int_status, Pca954xState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
static const Property pca954x_props[] = {
DEFINE_PROP_STRING("name", Pca954xState, name),
};
@@ -287,6 +310,7 @@ static void pca954x_class_init(ObjectClass *klass, const void *data)
dc->desc = "Pca954x i2c-mux";
dc->realize = pca954x_realize;
+ dc->vmsd = &vmstate_pca954x;
k->write_data = pca954x_write_data;
k->receive_byte = pca954x_read_byte;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 06/50] hw/i2c: pca954x: migrate mux state
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The pca954x model had no VMStateDescription, so neither the channel
selection nor the PCA9545 interrupt status survived a migration.
This changes the migration stream of the pre-existing PCA9546 and
PCA9548, which an older QEMU rejects.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index 4bb078a03c..597e57d99a 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -28,6 +28,7 @@
#include "hw/core/irq.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/queue.h"
#include "qom/object.h"
@@ -270,6 +271,28 @@ static void pca954x_init(Object *obj)
}
}
+static int pca954x_post_load(void *opaque, int version_id)
+{
+ Pca954xState *s = PCA954X(opaque);
+
+ pca954x_enable_channel(s, s->control);
+ qemu_set_irq(s->int_out, s->int_status == 0);
+ return 0;
+}
+
+static const VMStateDescription vmstate_pca954x = {
+ .name = "pca954x",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pca954x_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_SMBUS_DEVICE(parent, Pca954xState),
+ VMSTATE_UINT8(control, Pca954xState),
+ VMSTATE_UINT8(int_status, Pca954xState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
static const Property pca954x_props[] = {
DEFINE_PROP_STRING("name", Pca954xState, name),
};
@@ -287,6 +310,7 @@ static void pca954x_class_init(ObjectClass *klass, const void *data)
dc->desc = "Pca954x i2c-mux";
dc->realize = pca954x_realize;
+ dc->vmsd = &vmstate_pca954x;
k->write_data = pca954x_write_data;
k->receive_byte = pca954x_read_byte;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 06/50] hw/i2c: pca954x: migrate mux state
@ 2026-10-12 14:31 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:31 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The pca954x model had no VMStateDescription, so neither the channel
selection nor the PCA9545 interrupt status survived a migration.
This changes the migration stream of the pre-existing PCA9546 and
PCA9548, which an older QEMU rejects.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/i2c_mux_pca954x.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index 4bb078a03c..597e57d99a 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -28,6 +28,7 @@
#include "hw/core/irq.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/queue.h"
#include "qom/object.h"
@@ -270,6 +271,28 @@ static void pca954x_init(Object *obj)
}
}
+static int pca954x_post_load(void *opaque, int version_id)
+{
+ Pca954xState *s = PCA954X(opaque);
+
+ pca954x_enable_channel(s, s->control);
+ qemu_set_irq(s->int_out, s->int_status == 0);
+ return 0;
+}
+
+static const VMStateDescription vmstate_pca954x = {
+ .name = "pca954x",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pca954x_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_SMBUS_DEVICE(parent, Pca954xState),
+ VMSTATE_UINT8(control, Pca954xState),
+ VMSTATE_UINT8(int_status, Pca954xState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
static const Property pca954x_props[] = {
DEFINE_PROP_STRING("name", Pca954xState, name),
};
@@ -287,6 +310,7 @@ static void pca954x_class_init(ObjectClass *klass, const void *data)
dc->desc = "Pca954x i2c-mux";
dc->realize = pca954x_realize;
+ dc->vmsd = &vmstate_pca954x;
k->write_data = pca954x_write_data;
k->receive_byte = pca954x_read_byte;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 07/50] tests/qtest: pca9545: add mux test
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Exercise the PCA9545 4-channel I2C mux: channel selection through the
control byte, upper-nibble read-back, and interrupt logic.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/meson.build | 1 +
tests/qtest/pca9545-test.c | 116 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 117 insertions(+)
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 200abf6f36..e05d8a07b9 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -336,6 +336,7 @@ qos_test_ss.add(
'ne2000-test.c',
'tulip-test.c',
'nvme-test.c',
+ 'pca9545-test.c',
'pca9552-test.c',
'pca9554-test.c',
'pca9555-test.c',
diff --git a/tests/qtest/pca9545-test.c b/tests/qtest/pca9545-test.c
new file mode 100644
index 0000000000..91f56db92f
--- /dev/null
+++ b/tests/qtest/pca9545-test.c
@@ -0,0 +1,116 @@
+/*
+ * QTest testcase for the PCA9545 4-channel I2C switch with interrupt logic
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+
+#define PCA9545_TEST_ID "pca9545-test"
+#define PCA9545_TEST_ADDR 0x70
+#define PCA9545_TEST_PATH "/machine/peripheral/" PCA9545_TEST_ID
+
+/* INTn and INT are active low: level 0 is asserted. */
+#define INT_ASSERTED 0
+#define INT_RELEASED 1
+
+/* The mux has no register pointer: a read/write moves a single control byte. */
+static uint8_t pca9545_get(QI2CDevice *dev)
+{
+ uint8_t val;
+
+ qi2c_recv(dev, &val, 1);
+ return val;
+}
+
+static void pca9545_set(QI2CDevice *dev, uint8_t val)
+{
+ qi2c_send(dev, &val, 1);
+}
+
+static void pca9545_set_int(unsigned chan, int level)
+{
+ qtest_set_irq_in(global_qtest, PCA9545_TEST_PATH, "interrupt", chan, level);
+}
+
+/* Channel selection is the lower nibble; the upper nibble reads back 0. */
+static void test_channel_select(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(pca9545_get(dev), ==, 0x00);
+
+ pca9545_set(dev, 0x05); /* channels 0 and 2 */
+ g_assert_cmphex(pca9545_get(dev), ==, 0x05);
+
+ pca9545_set(dev, 0x0f); /* all channels */
+ g_assert_cmphex(pca9545_get(dev), ==, 0x0f);
+
+ pca9545_set(dev, 0x00);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x00);
+}
+
+/*
+ * The interrupt status is reported in the upper nibble (INT3..INT0) and is
+ * latched regardless of which channels are selected (PCA9545A Table 5).
+ */
+static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ pca9545_set_int(1, INT_ASSERTED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x20);
+
+ pca9545_set(dev, 0x01);
+ pca9545_set_int(3, INT_ASSERTED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0xa1); /* INT3 | INT1 | channel 0 */
+
+ pca9545_set_int(1, INT_RELEASED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x81); /* INT3 | channel 0 */
+}
+
+/* The INT output is pulled low while any channel interrupt is active. */
+static void test_interrupt_output(void *obj, void *data, QGuestAllocator *alloc)
+{
+ qtest_irq_intercept_out_named(global_qtest, PCA9545_TEST_PATH,
+ "interrupt-out");
+
+ /*
+ * Interception starts with no recorded level, so drive an idle input
+ * first to make the mux publish the released state of its output.
+ */
+ pca9545_set_int(0, INT_RELEASED);
+ g_assert_true(get_irq(0));
+
+ pca9545_set_int(0, INT_ASSERTED);
+ g_assert_false(get_irq(0));
+
+ pca9545_set_int(2, INT_ASSERTED);
+ g_assert_false(get_irq(0));
+
+ pca9545_set_int(0, INT_RELEASED);
+ g_assert_false(get_irq(0));
+ pca9545_set_int(2, INT_RELEASED);
+ g_assert_true(get_irq(0));
+}
+
+static void pca9545_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" PCA9545_TEST_ID ",address=0x70"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { PCA9545_TEST_ADDR });
+
+ qos_node_create_driver("pca9545", i2c_device_create);
+ qos_node_consumes("pca9545", "i2c-bus", &opts);
+
+ qos_add_test("channel-select", "pca9545", test_channel_select, NULL);
+ qos_add_test("interrupt-status", "pca9545", test_interrupt_status, NULL);
+ qos_add_test("interrupt-output", "pca9545", test_interrupt_output, NULL);
+}
+libqos_init(pca9545_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 07/50] tests/qtest: pca9545: add mux test
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Exercise the PCA9545 4-channel I2C mux: channel selection through the
control byte, upper-nibble read-back, and interrupt logic.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/meson.build | 1 +
tests/qtest/pca9545-test.c | 116 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 117 insertions(+)
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 200abf6f36..e05d8a07b9 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -336,6 +336,7 @@ qos_test_ss.add(
'ne2000-test.c',
'tulip-test.c',
'nvme-test.c',
+ 'pca9545-test.c',
'pca9552-test.c',
'pca9554-test.c',
'pca9555-test.c',
diff --git a/tests/qtest/pca9545-test.c b/tests/qtest/pca9545-test.c
new file mode 100644
index 0000000000..91f56db92f
--- /dev/null
+++ b/tests/qtest/pca9545-test.c
@@ -0,0 +1,116 @@
+/*
+ * QTest testcase for the PCA9545 4-channel I2C switch with interrupt logic
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+
+#define PCA9545_TEST_ID "pca9545-test"
+#define PCA9545_TEST_ADDR 0x70
+#define PCA9545_TEST_PATH "/machine/peripheral/" PCA9545_TEST_ID
+
+/* INTn and INT are active low: level 0 is asserted. */
+#define INT_ASSERTED 0
+#define INT_RELEASED 1
+
+/* The mux has no register pointer: a read/write moves a single control byte. */
+static uint8_t pca9545_get(QI2CDevice *dev)
+{
+ uint8_t val;
+
+ qi2c_recv(dev, &val, 1);
+ return val;
+}
+
+static void pca9545_set(QI2CDevice *dev, uint8_t val)
+{
+ qi2c_send(dev, &val, 1);
+}
+
+static void pca9545_set_int(unsigned chan, int level)
+{
+ qtest_set_irq_in(global_qtest, PCA9545_TEST_PATH, "interrupt", chan, level);
+}
+
+/* Channel selection is the lower nibble; the upper nibble reads back 0. */
+static void test_channel_select(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(pca9545_get(dev), ==, 0x00);
+
+ pca9545_set(dev, 0x05); /* channels 0 and 2 */
+ g_assert_cmphex(pca9545_get(dev), ==, 0x05);
+
+ pca9545_set(dev, 0x0f); /* all channels */
+ g_assert_cmphex(pca9545_get(dev), ==, 0x0f);
+
+ pca9545_set(dev, 0x00);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x00);
+}
+
+/*
+ * The interrupt status is reported in the upper nibble (INT3..INT0) and is
+ * latched regardless of which channels are selected (PCA9545A Table 5).
+ */
+static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ pca9545_set_int(1, INT_ASSERTED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x20);
+
+ pca9545_set(dev, 0x01);
+ pca9545_set_int(3, INT_ASSERTED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0xa1); /* INT3 | INT1 | channel 0 */
+
+ pca9545_set_int(1, INT_RELEASED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x81); /* INT3 | channel 0 */
+}
+
+/* The INT output is pulled low while any channel interrupt is active. */
+static void test_interrupt_output(void *obj, void *data, QGuestAllocator *alloc)
+{
+ qtest_irq_intercept_out_named(global_qtest, PCA9545_TEST_PATH,
+ "interrupt-out");
+
+ /*
+ * Interception starts with no recorded level, so drive an idle input
+ * first to make the mux publish the released state of its output.
+ */
+ pca9545_set_int(0, INT_RELEASED);
+ g_assert_true(get_irq(0));
+
+ pca9545_set_int(0, INT_ASSERTED);
+ g_assert_false(get_irq(0));
+
+ pca9545_set_int(2, INT_ASSERTED);
+ g_assert_false(get_irq(0));
+
+ pca9545_set_int(0, INT_RELEASED);
+ g_assert_false(get_irq(0));
+ pca9545_set_int(2, INT_RELEASED);
+ g_assert_true(get_irq(0));
+}
+
+static void pca9545_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" PCA9545_TEST_ID ",address=0x70"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { PCA9545_TEST_ADDR });
+
+ qos_node_create_driver("pca9545", i2c_device_create);
+ qos_node_consumes("pca9545", "i2c-bus", &opts);
+
+ qos_add_test("channel-select", "pca9545", test_channel_select, NULL);
+ qos_add_test("interrupt-status", "pca9545", test_interrupt_status, NULL);
+ qos_add_test("interrupt-output", "pca9545", test_interrupt_output, NULL);
+}
+libqos_init(pca9545_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 07/50] tests/qtest: pca9545: add mux test
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Exercise the PCA9545 4-channel I2C mux: channel selection through the
control byte, upper-nibble read-back, and interrupt logic.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/meson.build | 1 +
tests/qtest/pca9545-test.c | 116 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 117 insertions(+)
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 200abf6f36..e05d8a07b9 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -336,6 +336,7 @@ qos_test_ss.add(
'ne2000-test.c',
'tulip-test.c',
'nvme-test.c',
+ 'pca9545-test.c',
'pca9552-test.c',
'pca9554-test.c',
'pca9555-test.c',
diff --git a/tests/qtest/pca9545-test.c b/tests/qtest/pca9545-test.c
new file mode 100644
index 0000000000..91f56db92f
--- /dev/null
+++ b/tests/qtest/pca9545-test.c
@@ -0,0 +1,116 @@
+/*
+ * QTest testcase for the PCA9545 4-channel I2C switch with interrupt logic
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+
+#define PCA9545_TEST_ID "pca9545-test"
+#define PCA9545_TEST_ADDR 0x70
+#define PCA9545_TEST_PATH "/machine/peripheral/" PCA9545_TEST_ID
+
+/* INTn and INT are active low: level 0 is asserted. */
+#define INT_ASSERTED 0
+#define INT_RELEASED 1
+
+/* The mux has no register pointer: a read/write moves a single control byte. */
+static uint8_t pca9545_get(QI2CDevice *dev)
+{
+ uint8_t val;
+
+ qi2c_recv(dev, &val, 1);
+ return val;
+}
+
+static void pca9545_set(QI2CDevice *dev, uint8_t val)
+{
+ qi2c_send(dev, &val, 1);
+}
+
+static void pca9545_set_int(unsigned chan, int level)
+{
+ qtest_set_irq_in(global_qtest, PCA9545_TEST_PATH, "interrupt", chan, level);
+}
+
+/* Channel selection is the lower nibble; the upper nibble reads back 0. */
+static void test_channel_select(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(pca9545_get(dev), ==, 0x00);
+
+ pca9545_set(dev, 0x05); /* channels 0 and 2 */
+ g_assert_cmphex(pca9545_get(dev), ==, 0x05);
+
+ pca9545_set(dev, 0x0f); /* all channels */
+ g_assert_cmphex(pca9545_get(dev), ==, 0x0f);
+
+ pca9545_set(dev, 0x00);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x00);
+}
+
+/*
+ * The interrupt status is reported in the upper nibble (INT3..INT0) and is
+ * latched regardless of which channels are selected (PCA9545A Table 5).
+ */
+static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ pca9545_set_int(1, INT_ASSERTED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x20);
+
+ pca9545_set(dev, 0x01);
+ pca9545_set_int(3, INT_ASSERTED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0xa1); /* INT3 | INT1 | channel 0 */
+
+ pca9545_set_int(1, INT_RELEASED);
+ g_assert_cmphex(pca9545_get(dev), ==, 0x81); /* INT3 | channel 0 */
+}
+
+/* The INT output is pulled low while any channel interrupt is active. */
+static void test_interrupt_output(void *obj, void *data, QGuestAllocator *alloc)
+{
+ qtest_irq_intercept_out_named(global_qtest, PCA9545_TEST_PATH,
+ "interrupt-out");
+
+ /*
+ * Interception starts with no recorded level, so drive an idle input
+ * first to make the mux publish the released state of its output.
+ */
+ pca9545_set_int(0, INT_RELEASED);
+ g_assert_true(get_irq(0));
+
+ pca9545_set_int(0, INT_ASSERTED);
+ g_assert_false(get_irq(0));
+
+ pca9545_set_int(2, INT_ASSERTED);
+ g_assert_false(get_irq(0));
+
+ pca9545_set_int(0, INT_RELEASED);
+ g_assert_false(get_irq(0));
+ pca9545_set_int(2, INT_RELEASED);
+ g_assert_true(get_irq(0));
+}
+
+static void pca9545_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" PCA9545_TEST_ID ",address=0x70"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { PCA9545_TEST_ADDR });
+
+ qos_node_create_driver("pca9545", i2c_device_create);
+ qos_node_consumes("pca9545", "i2c-bus", &opts);
+
+ qos_add_test("channel-select", "pca9545", test_channel_select, NULL);
+ qos_add_test("interrupt-status", "pca9545", test_interrupt_status, NULL);
+ qos_add_test("interrupt-output", "pca9545", test_interrupt_output, NULL);
+}
+libqos_init(pca9545_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 08/50] hw/rtc: ds1338: move TYPE_DS1338 to a public header
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Move the TYPE_DS1338 type name definition from ds1338.c into a new
include/hw/rtc/ds1338.h header so that boards instantiating the device
no longer need to redefine the type name string locally.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 2 +-
include/hw/rtc/ds1338.h | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 5f1ee2e410..f8b3340bf0 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -12,6 +12,7 @@
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
+#include "hw/rtc/ds1338.h"
#include "migration/vmstate.h"
#include "qemu/bcd.h"
#include "qom/object.h"
@@ -29,7 +30,6 @@
#define HOURS_PM 0x20
#define CTRL_OSF 0x20
-#define TYPE_DS1338 "ds1338"
OBJECT_DECLARE_SIMPLE_TYPE(DS1338State, DS1338)
struct DS1338State {
diff --git a/include/hw/rtc/ds1338.h b/include/hw/rtc/ds1338.h
new file mode 100644
index 0000000000..f4cfe6a8e7
--- /dev/null
+++ b/include/hw/rtc/ds1338.h
@@ -0,0 +1,20 @@
+/*
+ * MAXIM DS1338 I2C RTC+NVRAM
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RTC_DS1338_H
+#define HW_RTC_DS1338_H
+
+#define TYPE_DS1338 "ds1338"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 08/50] hw/rtc: ds1338: move TYPE_DS1338 to a public header
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Move the TYPE_DS1338 type name definition from ds1338.c into a new
include/hw/rtc/ds1338.h header so that boards instantiating the device
no longer need to redefine the type name string locally.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 2 +-
include/hw/rtc/ds1338.h | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 5f1ee2e410..f8b3340bf0 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -12,6 +12,7 @@
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
+#include "hw/rtc/ds1338.h"
#include "migration/vmstate.h"
#include "qemu/bcd.h"
#include "qom/object.h"
@@ -29,7 +30,6 @@
#define HOURS_PM 0x20
#define CTRL_OSF 0x20
-#define TYPE_DS1338 "ds1338"
OBJECT_DECLARE_SIMPLE_TYPE(DS1338State, DS1338)
struct DS1338State {
diff --git a/include/hw/rtc/ds1338.h b/include/hw/rtc/ds1338.h
new file mode 100644
index 0000000000..f4cfe6a8e7
--- /dev/null
+++ b/include/hw/rtc/ds1338.h
@@ -0,0 +1,20 @@
+/*
+ * MAXIM DS1338 I2C RTC+NVRAM
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RTC_DS1338_H
+#define HW_RTC_DS1338_H
+
+#define TYPE_DS1338 "ds1338"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 08/50] hw/rtc: ds1338: move TYPE_DS1338 to a public header
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Move the TYPE_DS1338 type name definition from ds1338.c into a new
include/hw/rtc/ds1338.h header so that boards instantiating the device
no longer need to redefine the type name string locally.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 2 +-
include/hw/rtc/ds1338.h | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 5f1ee2e410..f8b3340bf0 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -12,6 +12,7 @@
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
+#include "hw/rtc/ds1338.h"
#include "migration/vmstate.h"
#include "qemu/bcd.h"
#include "qom/object.h"
@@ -29,7 +30,6 @@
#define HOURS_PM 0x20
#define CTRL_OSF 0x20
-#define TYPE_DS1338 "ds1338"
OBJECT_DECLARE_SIMPLE_TYPE(DS1338State, DS1338)
struct DS1338State {
diff --git a/include/hw/rtc/ds1338.h b/include/hw/rtc/ds1338.h
new file mode 100644
index 0000000000..f4cfe6a8e7
--- /dev/null
+++ b/include/hw/rtc/ds1338.h
@@ -0,0 +1,20 @@
+/*
+ * MAXIM DS1338 I2C RTC+NVRAM
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RTC_DS1338_H
+#define HW_RTC_DS1338_H
+
+#define TYPE_DS1338 "ds1338"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 09/50] hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Now that TYPE_DS1338 is exposed by include/hw/rtc/ds1338.h, use it in
the machines that instantiate the RTC by its raw type name string.
No functional change.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2400_palmetto.c | 4 +++-
hw/arm/aspeed_ast2400_supermicrox11.c | 4 +++-
hw/arm/aspeed_ast2500_romulus.c | 4 +++-
hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +++-
hw/arm/aspeed_ast2500_witherspoon.c | 4 +++-
hw/arm/aspeed_ast2600_bletchley.c | 3 ++-
hw/arm/aspeed_ast2600_catalina.c | 2 +-
hw/arm/aspeed_ast2600_sanmiguel.c | 3 +--
hw/arm/realview.c | 3 ++-
hw/arm/versatilepb.c | 3 ++-
hw/ppc/e500.c | 3 ++-
hw/riscv/tt_atlantis.c | 3 ++-
12 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/hw/arm/aspeed_ast2400_palmetto.c b/hw/arm/aspeed_ast2400_palmetto.c
index e2350e5b6c..185185f0bb 100644
--- a/hw/arm/aspeed_ast2400_palmetto.c
+++ b/hw/arm/aspeed_ast2400_palmetto.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* Palmetto hardware value: 0x120CE416 */
#define PALMETTO_BMC_HW_STRAP1 ( \
@@ -36,7 +37,8 @@ static void palmetto_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2400_supermicrox11.c b/hw/arm/aspeed_ast2400_supermicrox11.c
index c75898cc6d..051e072c5a 100644
--- a/hw/arm/aspeed_ast2400_supermicrox11.c
+++ b/hw/arm/aspeed_ast2400_supermicrox11.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* TODO: Find the actual hardware value */
#define SUPERMICROX11_BMC_HW_STRAP1 ( \
@@ -36,7 +37,8 @@ static void supermicrox11_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2500_romulus.c b/hw/arm/aspeed_ast2500_romulus.c
index 340a74408b..87f90ed361 100644
--- a/hw/arm/aspeed_ast2500_romulus.c
+++ b/hw/arm/aspeed_ast2500_romulus.c
@@ -10,6 +10,7 @@
#include "qapi/error.h"
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
+#include "hw/rtc/ds1338.h"
/* Romulus hardware value: 0xF10AD206 */
#define ROMULUS_BMC_HW_STRAP1 ( \
@@ -29,7 +30,8 @@ static void romulus_bmc_i2c_init(AspeedMachineState *bmc)
* The romulus board expects Epson RX8900 I2C RTC but a ds1338 is
* good enough
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
+ 0x32);
}
static void aspeed_machine_romulus_class_init(ObjectClass *oc,
diff --git a/hw/arm/aspeed_ast2500_supermicro-x11spi.c b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
index 5a7e49d6cc..dc0248422d 100644
--- a/hw/arm/aspeed_ast2500_supermicro-x11spi.c
+++ b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* TODO: Find the actual hardware value */
#define SUPERMICRO_X11SPI_BMC_HW_STRAP1 ( \
@@ -32,7 +33,8 @@ static void supermicro_x11spi_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2500_witherspoon.c b/hw/arm/aspeed_ast2500_witherspoon.c
index 26ddb67347..c69065846c 100644
--- a/hw/arm/aspeed_ast2500_witherspoon.c
+++ b/hw/arm/aspeed_ast2500_witherspoon.c
@@ -14,6 +14,7 @@
#include "hw/sensor/tmp105.h"
#include "hw/i2c/smbus_eeprom.h"
#include "hw/gpio/pca9552.h"
+#include "hw/rtc/ds1338.h"
/* Witherspoon hardware value: 0xF10AD216 */
#define WITHERSPOON_BMC_HW_STRAP1 ( \
@@ -70,7 +71,8 @@ static void witherspoon_bmc_i2c_init(AspeedMachineState *bmc)
* The witherspoon board expects Epson RX8900 I2C RTC but a ds1338 is
* good enough
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
+ 0x32);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 11), 0x51,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2600_bletchley.c b/hw/arm/aspeed_ast2600_bletchley.c
index 6fa606c047..0e746055a2 100644
--- a/hw/arm/aspeed_ast2600_bletchley.c
+++ b/hw/arm/aspeed_ast2600_bletchley.c
@@ -12,6 +12,7 @@
#include "hw/arm/aspeed_soc.h"
#include "hw/gpio/pca9552.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#define TYPE_TMP421 "tmp421"
/* Bletchley hardware value */
@@ -44,7 +45,7 @@ static void bletchley_bmc_i2c_init(AspeedMachineState *bmc)
/* Bus 6 */
at24c_eeprom_init(i2c[6], 0x56, 65536);
/* Missing model: nxp,pcf85263 @ 0x51 , but ds1338 works enough */
- i2c_slave_create_simple(i2c[6], "ds1338", 0x51);
+ i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x51);
/* Bus 7 */
diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index 4f2b396f6f..b15d932086 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -14,6 +14,7 @@
#include "hw/gpio/pca9552.h"
#include "hw/gpio/pca9554.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sensor/tmp105.h"
/* Catalina hardware value */
@@ -22,7 +23,6 @@
#define CATALINA_BMC_RAM_SIZE (2 * GiB)
#define TYPE_TMP421 "tmp421"
-#define TYPE_DS1338 "ds1338"
/*
* "BMC Storage Module" FRU data. Generated with frugen.
diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
index ac2aef740a..b0935683e2 100644
--- a/hw/arm/aspeed_ast2600_sanmiguel.c
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -13,6 +13,7 @@
#include "hw/gpio/pca9552.h"
#include "hw/gpio/pca9554.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sensor/tmp105.h"
/* SanMiguel hardware values */
@@ -20,8 +21,6 @@
#define SANMIGUEL_BMC_HW_STRAP2 0x00000000
#define SANMIGUEL_BMC_RAM_SIZE (2 * GiB)
-#define TYPE_DS1338 "ds1338"
-
/*
* "Front IO" FRU data. Generated with frugen.
*
diff --git a/hw/arm/realview.c b/hw/arm/realview.c
index ceba0331b0..4cdf939511 100644
--- a/hw/arm/realview.c
+++ b/hw/arm/realview.c
@@ -28,6 +28,7 @@
#include "hw/intc/realview_gic.h"
#include "hw/core/irq.h"
#include "hw/i2c/arm_sbcon_i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sd/sd.h"
#include "target/arm/cpu-qom.h"
@@ -323,7 +324,7 @@ static void realview_init(MachineState *machine,
dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
- i2c_slave_create_simple(i2c, "ds1338", 0x68);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
/* Memory map for RealView Emulation Baseboard: */
/* 0x10000000 System registers. */
diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c
index f94bf96599..737cd21c27 100644
--- a/hw/arm/versatilepb.c
+++ b/hw/arm/versatilepb.c
@@ -18,6 +18,7 @@
#include "hw/pci/pci.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/arm_sbcon_i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/core/irq.h"
#include "hw/core/boards.h"
#include "hw/block/flash.h"
@@ -346,7 +347,7 @@ static void versatile_init(MachineState *machine, int board_id)
dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
- i2c_slave_create_simple(i2c, "ds1338", 0x68);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
/* Add PL041 AACI Interface to the LM4549 codec */
pl041 = qdev_new("pl041");
diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c
index 1876882a04..469cef9d2f 100644
--- a/hw/ppc/e500.c
+++ b/hw/ppc/e500.c
@@ -50,6 +50,7 @@
#include "hw/core/platform-bus.h"
#include "hw/net/fsl_etsec/etsec.h"
#include "hw/i2c/i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/core/irq.h"
#include "hw/sd/sdhci.h"
#include "hw/misc/unimp.h"
@@ -1025,7 +1026,7 @@ void ppce500_init(MachineState *machine)
memory_region_add_subregion(ccsr_addr_space, MPC8544_I2C_REGS_OFFSET,
sysbus_mmio_get_region(s, 0));
i2c = I2C_BUS(qdev_get_child_bus(dev, "i2c"));
- i2c_slave_create_simple(i2c, "ds1338", RTC_REGS_OFFSET);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, RTC_REGS_OFFSET);
/* eSDHC */
if (pmc->has_esdhc) {
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
index 8a219afef7..910713c6bb 100644
--- a/hw/riscv/tt_atlantis.c
+++ b/hw/riscv/tt_atlantis.c
@@ -27,6 +27,7 @@
#include "hw/char/serial-mm.h"
#include "hw/intc/riscv_aclint.h"
#include "hw/misc/unimp.h"
+#include "hw/rtc/ds1338.h"
#include "system/system.h"
#include "system/device_tree.h"
@@ -657,7 +658,7 @@ static void tt_atlantis_machine_init(MachineState *machine)
qdev_realize(DEVICE(&ams->soc), NULL, &error_fatal);
/* I2C peripherals: qemu specific */
- i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f);
+ i2c_slave_create_simple(i2c_get_bus(s, 0), TYPE_DS1338, 0x6f);
i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48);
/* Load or create device tree */
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 09/50] hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Now that TYPE_DS1338 is exposed by include/hw/rtc/ds1338.h, use it in
the machines that instantiate the RTC by its raw type name string.
No functional change.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2400_palmetto.c | 4 +++-
hw/arm/aspeed_ast2400_supermicrox11.c | 4 +++-
hw/arm/aspeed_ast2500_romulus.c | 4 +++-
hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +++-
hw/arm/aspeed_ast2500_witherspoon.c | 4 +++-
hw/arm/aspeed_ast2600_bletchley.c | 3 ++-
hw/arm/aspeed_ast2600_catalina.c | 2 +-
hw/arm/aspeed_ast2600_sanmiguel.c | 3 +--
hw/arm/realview.c | 3 ++-
hw/arm/versatilepb.c | 3 ++-
hw/ppc/e500.c | 3 ++-
hw/riscv/tt_atlantis.c | 3 ++-
12 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/hw/arm/aspeed_ast2400_palmetto.c b/hw/arm/aspeed_ast2400_palmetto.c
index e2350e5b6c..185185f0bb 100644
--- a/hw/arm/aspeed_ast2400_palmetto.c
+++ b/hw/arm/aspeed_ast2400_palmetto.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* Palmetto hardware value: 0x120CE416 */
#define PALMETTO_BMC_HW_STRAP1 ( \
@@ -36,7 +37,8 @@ static void palmetto_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2400_supermicrox11.c b/hw/arm/aspeed_ast2400_supermicrox11.c
index c75898cc6d..051e072c5a 100644
--- a/hw/arm/aspeed_ast2400_supermicrox11.c
+++ b/hw/arm/aspeed_ast2400_supermicrox11.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* TODO: Find the actual hardware value */
#define SUPERMICROX11_BMC_HW_STRAP1 ( \
@@ -36,7 +37,8 @@ static void supermicrox11_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2500_romulus.c b/hw/arm/aspeed_ast2500_romulus.c
index 340a74408b..87f90ed361 100644
--- a/hw/arm/aspeed_ast2500_romulus.c
+++ b/hw/arm/aspeed_ast2500_romulus.c
@@ -10,6 +10,7 @@
#include "qapi/error.h"
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
+#include "hw/rtc/ds1338.h"
/* Romulus hardware value: 0xF10AD206 */
#define ROMULUS_BMC_HW_STRAP1 ( \
@@ -29,7 +30,8 @@ static void romulus_bmc_i2c_init(AspeedMachineState *bmc)
* The romulus board expects Epson RX8900 I2C RTC but a ds1338 is
* good enough
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
+ 0x32);
}
static void aspeed_machine_romulus_class_init(ObjectClass *oc,
diff --git a/hw/arm/aspeed_ast2500_supermicro-x11spi.c b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
index 5a7e49d6cc..dc0248422d 100644
--- a/hw/arm/aspeed_ast2500_supermicro-x11spi.c
+++ b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* TODO: Find the actual hardware value */
#define SUPERMICRO_X11SPI_BMC_HW_STRAP1 ( \
@@ -32,7 +33,8 @@ static void supermicro_x11spi_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2500_witherspoon.c b/hw/arm/aspeed_ast2500_witherspoon.c
index 26ddb67347..c69065846c 100644
--- a/hw/arm/aspeed_ast2500_witherspoon.c
+++ b/hw/arm/aspeed_ast2500_witherspoon.c
@@ -14,6 +14,7 @@
#include "hw/sensor/tmp105.h"
#include "hw/i2c/smbus_eeprom.h"
#include "hw/gpio/pca9552.h"
+#include "hw/rtc/ds1338.h"
/* Witherspoon hardware value: 0xF10AD216 */
#define WITHERSPOON_BMC_HW_STRAP1 ( \
@@ -70,7 +71,8 @@ static void witherspoon_bmc_i2c_init(AspeedMachineState *bmc)
* The witherspoon board expects Epson RX8900 I2C RTC but a ds1338 is
* good enough
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
+ 0x32);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 11), 0x51,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2600_bletchley.c b/hw/arm/aspeed_ast2600_bletchley.c
index 6fa606c047..0e746055a2 100644
--- a/hw/arm/aspeed_ast2600_bletchley.c
+++ b/hw/arm/aspeed_ast2600_bletchley.c
@@ -12,6 +12,7 @@
#include "hw/arm/aspeed_soc.h"
#include "hw/gpio/pca9552.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#define TYPE_TMP421 "tmp421"
/* Bletchley hardware value */
@@ -44,7 +45,7 @@ static void bletchley_bmc_i2c_init(AspeedMachineState *bmc)
/* Bus 6 */
at24c_eeprom_init(i2c[6], 0x56, 65536);
/* Missing model: nxp,pcf85263 @ 0x51 , but ds1338 works enough */
- i2c_slave_create_simple(i2c[6], "ds1338", 0x51);
+ i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x51);
/* Bus 7 */
diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index 4f2b396f6f..b15d932086 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -14,6 +14,7 @@
#include "hw/gpio/pca9552.h"
#include "hw/gpio/pca9554.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sensor/tmp105.h"
/* Catalina hardware value */
@@ -22,7 +23,6 @@
#define CATALINA_BMC_RAM_SIZE (2 * GiB)
#define TYPE_TMP421 "tmp421"
-#define TYPE_DS1338 "ds1338"
/*
* "BMC Storage Module" FRU data. Generated with frugen.
diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
index ac2aef740a..b0935683e2 100644
--- a/hw/arm/aspeed_ast2600_sanmiguel.c
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -13,6 +13,7 @@
#include "hw/gpio/pca9552.h"
#include "hw/gpio/pca9554.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sensor/tmp105.h"
/* SanMiguel hardware values */
@@ -20,8 +21,6 @@
#define SANMIGUEL_BMC_HW_STRAP2 0x00000000
#define SANMIGUEL_BMC_RAM_SIZE (2 * GiB)
-#define TYPE_DS1338 "ds1338"
-
/*
* "Front IO" FRU data. Generated with frugen.
*
diff --git a/hw/arm/realview.c b/hw/arm/realview.c
index ceba0331b0..4cdf939511 100644
--- a/hw/arm/realview.c
+++ b/hw/arm/realview.c
@@ -28,6 +28,7 @@
#include "hw/intc/realview_gic.h"
#include "hw/core/irq.h"
#include "hw/i2c/arm_sbcon_i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sd/sd.h"
#include "target/arm/cpu-qom.h"
@@ -323,7 +324,7 @@ static void realview_init(MachineState *machine,
dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
- i2c_slave_create_simple(i2c, "ds1338", 0x68);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
/* Memory map for RealView Emulation Baseboard: */
/* 0x10000000 System registers. */
diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c
index f94bf96599..737cd21c27 100644
--- a/hw/arm/versatilepb.c
+++ b/hw/arm/versatilepb.c
@@ -18,6 +18,7 @@
#include "hw/pci/pci.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/arm_sbcon_i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/core/irq.h"
#include "hw/core/boards.h"
#include "hw/block/flash.h"
@@ -346,7 +347,7 @@ static void versatile_init(MachineState *machine, int board_id)
dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
- i2c_slave_create_simple(i2c, "ds1338", 0x68);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
/* Add PL041 AACI Interface to the LM4549 codec */
pl041 = qdev_new("pl041");
diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c
index 1876882a04..469cef9d2f 100644
--- a/hw/ppc/e500.c
+++ b/hw/ppc/e500.c
@@ -50,6 +50,7 @@
#include "hw/core/platform-bus.h"
#include "hw/net/fsl_etsec/etsec.h"
#include "hw/i2c/i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/core/irq.h"
#include "hw/sd/sdhci.h"
#include "hw/misc/unimp.h"
@@ -1025,7 +1026,7 @@ void ppce500_init(MachineState *machine)
memory_region_add_subregion(ccsr_addr_space, MPC8544_I2C_REGS_OFFSET,
sysbus_mmio_get_region(s, 0));
i2c = I2C_BUS(qdev_get_child_bus(dev, "i2c"));
- i2c_slave_create_simple(i2c, "ds1338", RTC_REGS_OFFSET);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, RTC_REGS_OFFSET);
/* eSDHC */
if (pmc->has_esdhc) {
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
index 8a219afef7..910713c6bb 100644
--- a/hw/riscv/tt_atlantis.c
+++ b/hw/riscv/tt_atlantis.c
@@ -27,6 +27,7 @@
#include "hw/char/serial-mm.h"
#include "hw/intc/riscv_aclint.h"
#include "hw/misc/unimp.h"
+#include "hw/rtc/ds1338.h"
#include "system/system.h"
#include "system/device_tree.h"
@@ -657,7 +658,7 @@ static void tt_atlantis_machine_init(MachineState *machine)
qdev_realize(DEVICE(&ams->soc), NULL, &error_fatal);
/* I2C peripherals: qemu specific */
- i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f);
+ i2c_slave_create_simple(i2c_get_bus(s, 0), TYPE_DS1338, 0x6f);
i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48);
/* Load or create device tree */
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 09/50] hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Now that TYPE_DS1338 is exposed by include/hw/rtc/ds1338.h, use it in
the machines that instantiate the RTC by its raw type name string.
No functional change.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2400_palmetto.c | 4 +++-
hw/arm/aspeed_ast2400_supermicrox11.c | 4 +++-
hw/arm/aspeed_ast2500_romulus.c | 4 +++-
hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +++-
hw/arm/aspeed_ast2500_witherspoon.c | 4 +++-
hw/arm/aspeed_ast2600_bletchley.c | 3 ++-
hw/arm/aspeed_ast2600_catalina.c | 2 +-
hw/arm/aspeed_ast2600_sanmiguel.c | 3 +--
hw/arm/realview.c | 3 ++-
hw/arm/versatilepb.c | 3 ++-
hw/ppc/e500.c | 3 ++-
hw/riscv/tt_atlantis.c | 3 ++-
12 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/hw/arm/aspeed_ast2400_palmetto.c b/hw/arm/aspeed_ast2400_palmetto.c
index e2350e5b6c..185185f0bb 100644
--- a/hw/arm/aspeed_ast2400_palmetto.c
+++ b/hw/arm/aspeed_ast2400_palmetto.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* Palmetto hardware value: 0x120CE416 */
#define PALMETTO_BMC_HW_STRAP1 ( \
@@ -36,7 +37,8 @@ static void palmetto_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2400_supermicrox11.c b/hw/arm/aspeed_ast2400_supermicrox11.c
index c75898cc6d..051e072c5a 100644
--- a/hw/arm/aspeed_ast2400_supermicrox11.c
+++ b/hw/arm/aspeed_ast2400_supermicrox11.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* TODO: Find the actual hardware value */
#define SUPERMICROX11_BMC_HW_STRAP1 ( \
@@ -36,7 +37,8 @@ static void supermicrox11_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2500_romulus.c b/hw/arm/aspeed_ast2500_romulus.c
index 340a74408b..87f90ed361 100644
--- a/hw/arm/aspeed_ast2500_romulus.c
+++ b/hw/arm/aspeed_ast2500_romulus.c
@@ -10,6 +10,7 @@
#include "qapi/error.h"
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
+#include "hw/rtc/ds1338.h"
/* Romulus hardware value: 0xF10AD206 */
#define ROMULUS_BMC_HW_STRAP1 ( \
@@ -29,7 +30,8 @@ static void romulus_bmc_i2c_init(AspeedMachineState *bmc)
* The romulus board expects Epson RX8900 I2C RTC but a ds1338 is
* good enough
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
+ 0x32);
}
static void aspeed_machine_romulus_class_init(ObjectClass *oc,
diff --git a/hw/arm/aspeed_ast2500_supermicro-x11spi.c b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
index 5a7e49d6cc..dc0248422d 100644
--- a/hw/arm/aspeed_ast2500_supermicro-x11spi.c
+++ b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/i2c/smbus_eeprom.h"
+#include "hw/rtc/ds1338.h"
/* TODO: Find the actual hardware value */
#define SUPERMICRO_X11SPI_BMC_HW_STRAP1 ( \
@@ -32,7 +33,8 @@ static void supermicro_x11spi_bmc_i2c_init(AspeedMachineState *bmc)
* The palmetto platform expects a ds3231 RTC but a ds1338 is
* enough to provide basic RTC features. Alarms will be missing
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
+ 0x68);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2500_witherspoon.c b/hw/arm/aspeed_ast2500_witherspoon.c
index 26ddb67347..c69065846c 100644
--- a/hw/arm/aspeed_ast2500_witherspoon.c
+++ b/hw/arm/aspeed_ast2500_witherspoon.c
@@ -14,6 +14,7 @@
#include "hw/sensor/tmp105.h"
#include "hw/i2c/smbus_eeprom.h"
#include "hw/gpio/pca9552.h"
+#include "hw/rtc/ds1338.h"
/* Witherspoon hardware value: 0xF10AD216 */
#define WITHERSPOON_BMC_HW_STRAP1 ( \
@@ -70,7 +71,8 @@ static void witherspoon_bmc_i2c_init(AspeedMachineState *bmc)
* The witherspoon board expects Epson RX8900 I2C RTC but a ds1338 is
* good enough
*/
- i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
+ i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
+ 0x32);
smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 11), 0x51,
eeprom_buf);
diff --git a/hw/arm/aspeed_ast2600_bletchley.c b/hw/arm/aspeed_ast2600_bletchley.c
index 6fa606c047..0e746055a2 100644
--- a/hw/arm/aspeed_ast2600_bletchley.c
+++ b/hw/arm/aspeed_ast2600_bletchley.c
@@ -12,6 +12,7 @@
#include "hw/arm/aspeed_soc.h"
#include "hw/gpio/pca9552.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#define TYPE_TMP421 "tmp421"
/* Bletchley hardware value */
@@ -44,7 +45,7 @@ static void bletchley_bmc_i2c_init(AspeedMachineState *bmc)
/* Bus 6 */
at24c_eeprom_init(i2c[6], 0x56, 65536);
/* Missing model: nxp,pcf85263 @ 0x51 , but ds1338 works enough */
- i2c_slave_create_simple(i2c[6], "ds1338", 0x51);
+ i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x51);
/* Bus 7 */
diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index 4f2b396f6f..b15d932086 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -14,6 +14,7 @@
#include "hw/gpio/pca9552.h"
#include "hw/gpio/pca9554.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sensor/tmp105.h"
/* Catalina hardware value */
@@ -22,7 +23,6 @@
#define CATALINA_BMC_RAM_SIZE (2 * GiB)
#define TYPE_TMP421 "tmp421"
-#define TYPE_DS1338 "ds1338"
/*
* "BMC Storage Module" FRU data. Generated with frugen.
diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
index ac2aef740a..b0935683e2 100644
--- a/hw/arm/aspeed_ast2600_sanmiguel.c
+++ b/hw/arm/aspeed_ast2600_sanmiguel.c
@@ -13,6 +13,7 @@
#include "hw/gpio/pca9552.h"
#include "hw/gpio/pca9554.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sensor/tmp105.h"
/* SanMiguel hardware values */
@@ -20,8 +21,6 @@
#define SANMIGUEL_BMC_HW_STRAP2 0x00000000
#define SANMIGUEL_BMC_RAM_SIZE (2 * GiB)
-#define TYPE_DS1338 "ds1338"
-
/*
* "Front IO" FRU data. Generated with frugen.
*
diff --git a/hw/arm/realview.c b/hw/arm/realview.c
index ceba0331b0..4cdf939511 100644
--- a/hw/arm/realview.c
+++ b/hw/arm/realview.c
@@ -28,6 +28,7 @@
#include "hw/intc/realview_gic.h"
#include "hw/core/irq.h"
#include "hw/i2c/arm_sbcon_i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/sd/sd.h"
#include "target/arm/cpu-qom.h"
@@ -323,7 +324,7 @@ static void realview_init(MachineState *machine,
dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
- i2c_slave_create_simple(i2c, "ds1338", 0x68);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
/* Memory map for RealView Emulation Baseboard: */
/* 0x10000000 System registers. */
diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c
index f94bf96599..737cd21c27 100644
--- a/hw/arm/versatilepb.c
+++ b/hw/arm/versatilepb.c
@@ -18,6 +18,7 @@
#include "hw/pci/pci.h"
#include "hw/i2c/i2c.h"
#include "hw/i2c/arm_sbcon_i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/core/irq.h"
#include "hw/core/boards.h"
#include "hw/block/flash.h"
@@ -346,7 +347,7 @@ static void versatile_init(MachineState *machine, int board_id)
dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
- i2c_slave_create_simple(i2c, "ds1338", 0x68);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
/* Add PL041 AACI Interface to the LM4549 codec */
pl041 = qdev_new("pl041");
diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c
index 1876882a04..469cef9d2f 100644
--- a/hw/ppc/e500.c
+++ b/hw/ppc/e500.c
@@ -50,6 +50,7 @@
#include "hw/core/platform-bus.h"
#include "hw/net/fsl_etsec/etsec.h"
#include "hw/i2c/i2c.h"
+#include "hw/rtc/ds1338.h"
#include "hw/core/irq.h"
#include "hw/sd/sdhci.h"
#include "hw/misc/unimp.h"
@@ -1025,7 +1026,7 @@ void ppce500_init(MachineState *machine)
memory_region_add_subregion(ccsr_addr_space, MPC8544_I2C_REGS_OFFSET,
sysbus_mmio_get_region(s, 0));
i2c = I2C_BUS(qdev_get_child_bus(dev, "i2c"));
- i2c_slave_create_simple(i2c, "ds1338", RTC_REGS_OFFSET);
+ i2c_slave_create_simple(i2c, TYPE_DS1338, RTC_REGS_OFFSET);
/* eSDHC */
if (pmc->has_esdhc) {
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
index 8a219afef7..910713c6bb 100644
--- a/hw/riscv/tt_atlantis.c
+++ b/hw/riscv/tt_atlantis.c
@@ -27,6 +27,7 @@
#include "hw/char/serial-mm.h"
#include "hw/intc/riscv_aclint.h"
#include "hw/misc/unimp.h"
+#include "hw/rtc/ds1338.h"
#include "system/system.h"
#include "system/device_tree.h"
@@ -657,7 +658,7 @@ static void tt_atlantis_machine_init(MachineState *machine)
qdev_realize(DEVICE(&ams->soc), NULL, &error_fatal);
/* I2C peripherals: qemu specific */
- i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f);
+ i2c_slave_create_simple(i2c_get_bus(s, 0), TYPE_DS1338, 0x6f);
i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48);
/* Load or create device tree */
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 10/50] hw/rtc: ds1338: implement Resettable reset
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The device relied on device_class_set_legacy_reset(), a compatibility
shim around the legacy DeviceState reset hook. Convert ds1338_reset()
into a proper Resettable hold phase, dropping the legacy shim.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index f8b3340bf0..74aee6c746 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -208,9 +208,9 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
-static void ds1338_reset(DeviceState *dev)
+static void ds1338_reset_hold(Object *obj, ResetType type)
{
- DS1338State *s = DS1338(dev);
+ DS1338State *s = DS1338(obj);
/* The clock is running and synchronized with the host */
s->offset = 0;
@@ -224,11 +224,12 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
k->event = ds1338_event;
k->recv = ds1338_recv;
k->send = ds1338_send;
- device_class_set_legacy_reset(dc, ds1338_reset);
+ rc->phases.hold = ds1338_reset_hold;
dc->vmsd = &vmstate_ds1338;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 10/50] hw/rtc: ds1338: implement Resettable reset
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The device relied on device_class_set_legacy_reset(), a compatibility
shim around the legacy DeviceState reset hook. Convert ds1338_reset()
into a proper Resettable hold phase, dropping the legacy shim.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index f8b3340bf0..74aee6c746 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -208,9 +208,9 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
-static void ds1338_reset(DeviceState *dev)
+static void ds1338_reset_hold(Object *obj, ResetType type)
{
- DS1338State *s = DS1338(dev);
+ DS1338State *s = DS1338(obj);
/* The clock is running and synchronized with the host */
s->offset = 0;
@@ -224,11 +224,12 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
k->event = ds1338_event;
k->recv = ds1338_recv;
k->send = ds1338_send;
- device_class_set_legacy_reset(dc, ds1338_reset);
+ rc->phases.hold = ds1338_reset_hold;
dc->vmsd = &vmstate_ds1338;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 10/50] hw/rtc: ds1338: implement Resettable reset
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The device relied on device_class_set_legacy_reset(), a compatibility
shim around the legacy DeviceState reset hook. Convert ds1338_reset()
into a proper Resettable hold phase, dropping the legacy shim.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index f8b3340bf0..74aee6c746 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -208,9 +208,9 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
-static void ds1338_reset(DeviceState *dev)
+static void ds1338_reset_hold(Object *obj, ResetType type)
{
- DS1338State *s = DS1338(dev);
+ DS1338State *s = DS1338(obj);
/* The clock is running and synchronized with the host */
s->offset = 0;
@@ -224,11 +224,12 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
k->event = ds1338_event;
k->recv = ds1338_recv;
k->send = ds1338_send;
- device_class_set_legacy_reset(dc, ds1338_reset);
+ rc->phases.hold = ds1338_reset_hold;
dc->vmsd = &vmstate_ds1338;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 11/50] hw/rtc: ds1338: prefix the file-local helpers with ds1338_
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Two static helpers predate the rest of the file and carry no device
prefix.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 74aee6c746..1755a3a37c 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -57,7 +57,7 @@ static const VMStateDescription vmstate_ds1338 = {
}
};
-static void capture_current_time(DS1338State *s)
+static void ds1338_capture_current_time(DS1338State *s)
{
/* Capture the current time into the secondary registers
* which will be actually read by the data transfer operation.
@@ -85,7 +85,7 @@ static void capture_current_time(DS1338State *s)
s->nvram[6] = to_bcd(now.tm_year - 100);
}
-static void inc_regptr(DS1338State *s)
+static void ds1338_inc_regptr(DS1338State *s)
{
/* The register pointer wraps around after 0x3F; wraparound
* causes the current time/date to be retransferred into
@@ -93,7 +93,7 @@ static void inc_regptr(DS1338State *s)
*/
s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
if (!s->ptr) {
- capture_current_time(s);
+ ds1338_capture_current_time(s);
}
}
@@ -108,7 +108,7 @@ static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
* START_SEND, because the guest can't get at that data
* without going through a START_RECV which would overwrite it.
*/
- capture_current_time(s);
+ ds1338_capture_current_time(s);
break;
case I2C_START_SEND:
s->addr_byte = true;
@@ -129,7 +129,7 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
trace_ds1338_recv(s->ptr, res);
- inc_regptr(s);
+ ds1338_inc_regptr(s);
return res;
}
@@ -204,7 +204,7 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
} else {
s->nvram[s->ptr] = data;
}
- inc_regptr(s);
+ ds1338_inc_regptr(s);
return 0;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 11/50] hw/rtc: ds1338: prefix the file-local helpers with ds1338_
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Two static helpers predate the rest of the file and carry no device
prefix.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 74aee6c746..1755a3a37c 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -57,7 +57,7 @@ static const VMStateDescription vmstate_ds1338 = {
}
};
-static void capture_current_time(DS1338State *s)
+static void ds1338_capture_current_time(DS1338State *s)
{
/* Capture the current time into the secondary registers
* which will be actually read by the data transfer operation.
@@ -85,7 +85,7 @@ static void capture_current_time(DS1338State *s)
s->nvram[6] = to_bcd(now.tm_year - 100);
}
-static void inc_regptr(DS1338State *s)
+static void ds1338_inc_regptr(DS1338State *s)
{
/* The register pointer wraps around after 0x3F; wraparound
* causes the current time/date to be retransferred into
@@ -93,7 +93,7 @@ static void inc_regptr(DS1338State *s)
*/
s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
if (!s->ptr) {
- capture_current_time(s);
+ ds1338_capture_current_time(s);
}
}
@@ -108,7 +108,7 @@ static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
* START_SEND, because the guest can't get at that data
* without going through a START_RECV which would overwrite it.
*/
- capture_current_time(s);
+ ds1338_capture_current_time(s);
break;
case I2C_START_SEND:
s->addr_byte = true;
@@ -129,7 +129,7 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
trace_ds1338_recv(s->ptr, res);
- inc_regptr(s);
+ ds1338_inc_regptr(s);
return res;
}
@@ -204,7 +204,7 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
} else {
s->nvram[s->ptr] = data;
}
- inc_regptr(s);
+ ds1338_inc_regptr(s);
return 0;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 11/50] hw/rtc: ds1338: prefix the file-local helpers with ds1338_
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Two static helpers predate the rest of the file and carry no device
prefix.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 74aee6c746..1755a3a37c 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -57,7 +57,7 @@ static const VMStateDescription vmstate_ds1338 = {
}
};
-static void capture_current_time(DS1338State *s)
+static void ds1338_capture_current_time(DS1338State *s)
{
/* Capture the current time into the secondary registers
* which will be actually read by the data transfer operation.
@@ -85,7 +85,7 @@ static void capture_current_time(DS1338State *s)
s->nvram[6] = to_bcd(now.tm_year - 100);
}
-static void inc_regptr(DS1338State *s)
+static void ds1338_inc_regptr(DS1338State *s)
{
/* The register pointer wraps around after 0x3F; wraparound
* causes the current time/date to be retransferred into
@@ -93,7 +93,7 @@ static void inc_regptr(DS1338State *s)
*/
s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
if (!s->ptr) {
- capture_current_time(s);
+ ds1338_capture_current_time(s);
}
}
@@ -108,7 +108,7 @@ static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
* START_SEND, because the guest can't get at that data
* without going through a START_RECV which would overwrite it.
*/
- capture_current_time(s);
+ ds1338_capture_current_time(s);
break;
case I2C_START_SEND:
s->addr_byte = true;
@@ -129,7 +129,7 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
trace_ds1338_recv(s->ptr, res);
- inc_regptr(s);
+ ds1338_inc_regptr(s);
return res;
}
@@ -204,7 +204,7 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
} else {
s->nvram[s->ptr] = data;
}
- inc_regptr(s);
+ ds1338_inc_regptr(s);
return 0;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 12/50] hw/rtc: ds1338: parameterise the register map via a device class
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
In preparation for a register-compatible variant, refactor DS1338
register map. Introduce DS1338Class, carrying the register-pointer wrap
boundary and the control/OSF register location and masks, and drive
inc_regptr() and the control-register handling.
The control-register power-on value is parameterised along with them,
so the part comes up holding the contents its datasheet specifies
rather than zero, and its oscillator-stop flag now reads set at
power-on.
Encode the year register modulo 100 (tm_year % 100) instead of
tm_year - 100.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 71 ++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 53 insertions(+), 18 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 1755a3a37c..7eaa663113 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -30,7 +30,14 @@
#define HOURS_PM 0x20
#define CTRL_OSF 0x20
-OBJECT_DECLARE_SIMPLE_TYPE(DS1338State, DS1338)
+/* DS1338 register map */
+#define DS1338_NUM_REGS NVRAM_SIZE /* 0x00..0x3f, then wraps */
+#define DS1338_CONTROL 0x07
+#define DS1338_CTRL_MASK 0xb3 /* bits 2, 3 and 6 read back as zero */
+/* POR: OUT, OSF, SQWE and both rate-select bits come up set. */
+#define DS1338_CTRL_RESET 0xb3
+
+OBJECT_DECLARE_TYPE(DS1338State, DS1338Class, DS1338)
struct DS1338State {
I2CSlave parent_obj;
@@ -42,6 +49,17 @@ struct DS1338State {
bool addr_byte;
};
+struct DS1338Class {
+ I2CSlaveClass parent_class;
+
+ uint8_t num_regs; /* register-pointer wrap boundary */
+ uint8_t ctrl_addr; /* control register address */
+ uint8_t ctrl_mask; /* writable/read-back bits of the control reg */
+ uint8_t osf_addr; /* register holding the OSF flag */
+ uint8_t osf_mask; /* OSF bit within osf_addr */
+ uint8_t ctrl_reset; /* control-register power-on value */
+};
+
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
.version_id = 2,
@@ -82,16 +100,19 @@ static void ds1338_capture_current_time(DS1338State *s)
s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
s->nvram[4] = to_bcd(now.tm_mday);
s->nvram[5] = to_bcd(now.tm_mon + 1);
- s->nvram[6] = to_bcd(now.tm_year - 100);
+ s->nvram[6] = to_bcd(now.tm_year % 100);
}
static void ds1338_inc_regptr(DS1338State *s)
{
- /* The register pointer wraps around after 0x3F; wraparound
- * causes the current time/date to be retransferred into
- * the secondary registers.
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ /*
+ * The register pointer wraps around after the last register; wraparound
+ * causes the current time/date to be retransferred into the secondary
+ * registers.
*/
- s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
+ s->ptr = (s->ptr + 1) % k->num_regs;
if (!s->ptr) {
ds1338_capture_current_time(s);
}
@@ -136,11 +157,12 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
static int ds1338_send(I2CSlave *i2c, uint8_t data)
{
DS1338State *s = DS1338(i2c);
+ DS1338Class *k = DS1338_GET_CLASS(i2c);
trace_ds1338_send(s->ptr, data);
if (s->addr_byte) {
- s->ptr = data & (NVRAM_SIZE - 1);
+ s->ptr = data % k->num_regs;
s->addr_byte = false;
return 0;
}
@@ -190,18 +212,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
break;
}
s->offset = qemu_timedate_diff(&now);
- } else if (s->ptr == 7) {
- /* Control register. */
-
- /* Ensure bits 2, 3 and 6 will read back as zero. */
- data &= 0xB3;
-
- /* Attempting to write the OSF flag to logic 1 leaves the
- value unchanged. */
- data = (data & ~CTRL_OSF) | (data & s->nvram[s->ptr] & CTRL_OSF);
-
- s->nvram[s->ptr] = data;
} else {
+ if (s->ptr == k->ctrl_addr) {
+ /* Control register: reserved bits read back as zero. */
+ data &= k->ctrl_mask;
+ }
+ if (s->ptr == k->osf_addr) {
+ /*
+ * Attempting to write the OSF flag to logic 1 leaves the
+ * value unchanged.
+ */
+ data = (data & ~k->osf_mask) |
+ (data & s->nvram[s->ptr] & k->osf_mask);
+ }
s->nvram[s->ptr] = data;
}
ds1338_inc_regptr(s);
@@ -211,11 +234,13 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
static void ds1338_reset_hold(Object *obj, ResetType type)
{
DS1338State *s = DS1338(obj);
+ DS1338Class *k = DS1338_GET_CLASS(s);
/* The clock is running and synchronized with the host */
s->offset = 0;
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
+ s->nvram[k->ctrl_addr] = k->ctrl_reset;
s->ptr = 0;
s->addr_byte = false;
}
@@ -225,12 +250,21 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DS1338Class *dsc = DS1338_CLASS(klass);
k->event = ds1338_event;
k->recv = ds1338_recv;
k->send = ds1338_send;
rc->phases.hold = ds1338_reset_hold;
+ dc->desc = "DS1338 I2C RTC with 56-byte NV RAM";
dc->vmsd = &vmstate_ds1338;
+
+ dsc->num_regs = DS1338_NUM_REGS;
+ dsc->ctrl_addr = DS1338_CONTROL;
+ dsc->ctrl_mask = DS1338_CTRL_MASK;
+ dsc->osf_addr = DS1338_CONTROL;
+ dsc->osf_mask = CTRL_OSF;
+ dsc->ctrl_reset = DS1338_CTRL_RESET;
}
static const TypeInfo ds1338_types[] = {
@@ -238,6 +272,7 @@ static const TypeInfo ds1338_types[] = {
.name = TYPE_DS1338,
.parent = TYPE_I2C_SLAVE,
.instance_size = sizeof(DS1338State),
+ .class_size = sizeof(DS1338Class),
.class_init = ds1338_class_init,
},
};
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 12/50] hw/rtc: ds1338: parameterise the register map via a device class
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
In preparation for a register-compatible variant, refactor DS1338
register map. Introduce DS1338Class, carrying the register-pointer wrap
boundary and the control/OSF register location and masks, and drive
inc_regptr() and the control-register handling.
The control-register power-on value is parameterised along with them,
so the part comes up holding the contents its datasheet specifies
rather than zero, and its oscillator-stop flag now reads set at
power-on.
Encode the year register modulo 100 (tm_year % 100) instead of
tm_year - 100.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 71 ++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 53 insertions(+), 18 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 1755a3a37c..7eaa663113 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -30,7 +30,14 @@
#define HOURS_PM 0x20
#define CTRL_OSF 0x20
-OBJECT_DECLARE_SIMPLE_TYPE(DS1338State, DS1338)
+/* DS1338 register map */
+#define DS1338_NUM_REGS NVRAM_SIZE /* 0x00..0x3f, then wraps */
+#define DS1338_CONTROL 0x07
+#define DS1338_CTRL_MASK 0xb3 /* bits 2, 3 and 6 read back as zero */
+/* POR: OUT, OSF, SQWE and both rate-select bits come up set. */
+#define DS1338_CTRL_RESET 0xb3
+
+OBJECT_DECLARE_TYPE(DS1338State, DS1338Class, DS1338)
struct DS1338State {
I2CSlave parent_obj;
@@ -42,6 +49,17 @@ struct DS1338State {
bool addr_byte;
};
+struct DS1338Class {
+ I2CSlaveClass parent_class;
+
+ uint8_t num_regs; /* register-pointer wrap boundary */
+ uint8_t ctrl_addr; /* control register address */
+ uint8_t ctrl_mask; /* writable/read-back bits of the control reg */
+ uint8_t osf_addr; /* register holding the OSF flag */
+ uint8_t osf_mask; /* OSF bit within osf_addr */
+ uint8_t ctrl_reset; /* control-register power-on value */
+};
+
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
.version_id = 2,
@@ -82,16 +100,19 @@ static void ds1338_capture_current_time(DS1338State *s)
s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
s->nvram[4] = to_bcd(now.tm_mday);
s->nvram[5] = to_bcd(now.tm_mon + 1);
- s->nvram[6] = to_bcd(now.tm_year - 100);
+ s->nvram[6] = to_bcd(now.tm_year % 100);
}
static void ds1338_inc_regptr(DS1338State *s)
{
- /* The register pointer wraps around after 0x3F; wraparound
- * causes the current time/date to be retransferred into
- * the secondary registers.
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ /*
+ * The register pointer wraps around after the last register; wraparound
+ * causes the current time/date to be retransferred into the secondary
+ * registers.
*/
- s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
+ s->ptr = (s->ptr + 1) % k->num_regs;
if (!s->ptr) {
ds1338_capture_current_time(s);
}
@@ -136,11 +157,12 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
static int ds1338_send(I2CSlave *i2c, uint8_t data)
{
DS1338State *s = DS1338(i2c);
+ DS1338Class *k = DS1338_GET_CLASS(i2c);
trace_ds1338_send(s->ptr, data);
if (s->addr_byte) {
- s->ptr = data & (NVRAM_SIZE - 1);
+ s->ptr = data % k->num_regs;
s->addr_byte = false;
return 0;
}
@@ -190,18 +212,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
break;
}
s->offset = qemu_timedate_diff(&now);
- } else if (s->ptr == 7) {
- /* Control register. */
-
- /* Ensure bits 2, 3 and 6 will read back as zero. */
- data &= 0xB3;
-
- /* Attempting to write the OSF flag to logic 1 leaves the
- value unchanged. */
- data = (data & ~CTRL_OSF) | (data & s->nvram[s->ptr] & CTRL_OSF);
-
- s->nvram[s->ptr] = data;
} else {
+ if (s->ptr == k->ctrl_addr) {
+ /* Control register: reserved bits read back as zero. */
+ data &= k->ctrl_mask;
+ }
+ if (s->ptr == k->osf_addr) {
+ /*
+ * Attempting to write the OSF flag to logic 1 leaves the
+ * value unchanged.
+ */
+ data = (data & ~k->osf_mask) |
+ (data & s->nvram[s->ptr] & k->osf_mask);
+ }
s->nvram[s->ptr] = data;
}
ds1338_inc_regptr(s);
@@ -211,11 +234,13 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
static void ds1338_reset_hold(Object *obj, ResetType type)
{
DS1338State *s = DS1338(obj);
+ DS1338Class *k = DS1338_GET_CLASS(s);
/* The clock is running and synchronized with the host */
s->offset = 0;
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
+ s->nvram[k->ctrl_addr] = k->ctrl_reset;
s->ptr = 0;
s->addr_byte = false;
}
@@ -225,12 +250,21 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DS1338Class *dsc = DS1338_CLASS(klass);
k->event = ds1338_event;
k->recv = ds1338_recv;
k->send = ds1338_send;
rc->phases.hold = ds1338_reset_hold;
+ dc->desc = "DS1338 I2C RTC with 56-byte NV RAM";
dc->vmsd = &vmstate_ds1338;
+
+ dsc->num_regs = DS1338_NUM_REGS;
+ dsc->ctrl_addr = DS1338_CONTROL;
+ dsc->ctrl_mask = DS1338_CTRL_MASK;
+ dsc->osf_addr = DS1338_CONTROL;
+ dsc->osf_mask = CTRL_OSF;
+ dsc->ctrl_reset = DS1338_CTRL_RESET;
}
static const TypeInfo ds1338_types[] = {
@@ -238,6 +272,7 @@ static const TypeInfo ds1338_types[] = {
.name = TYPE_DS1338,
.parent = TYPE_I2C_SLAVE,
.instance_size = sizeof(DS1338State),
+ .class_size = sizeof(DS1338Class),
.class_init = ds1338_class_init,
},
};
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 12/50] hw/rtc: ds1338: parameterise the register map via a device class
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
In preparation for a register-compatible variant, refactor DS1338
register map. Introduce DS1338Class, carrying the register-pointer wrap
boundary and the control/OSF register location and masks, and drive
inc_regptr() and the control-register handling.
The control-register power-on value is parameterised along with them,
so the part comes up holding the contents its datasheet specifies
rather than zero, and its oscillator-stop flag now reads set at
power-on.
Encode the year register modulo 100 (tm_year % 100) instead of
tm_year - 100.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 71 ++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 53 insertions(+), 18 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 1755a3a37c..7eaa663113 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -30,7 +30,14 @@
#define HOURS_PM 0x20
#define CTRL_OSF 0x20
-OBJECT_DECLARE_SIMPLE_TYPE(DS1338State, DS1338)
+/* DS1338 register map */
+#define DS1338_NUM_REGS NVRAM_SIZE /* 0x00..0x3f, then wraps */
+#define DS1338_CONTROL 0x07
+#define DS1338_CTRL_MASK 0xb3 /* bits 2, 3 and 6 read back as zero */
+/* POR: OUT, OSF, SQWE and both rate-select bits come up set. */
+#define DS1338_CTRL_RESET 0xb3
+
+OBJECT_DECLARE_TYPE(DS1338State, DS1338Class, DS1338)
struct DS1338State {
I2CSlave parent_obj;
@@ -42,6 +49,17 @@ struct DS1338State {
bool addr_byte;
};
+struct DS1338Class {
+ I2CSlaveClass parent_class;
+
+ uint8_t num_regs; /* register-pointer wrap boundary */
+ uint8_t ctrl_addr; /* control register address */
+ uint8_t ctrl_mask; /* writable/read-back bits of the control reg */
+ uint8_t osf_addr; /* register holding the OSF flag */
+ uint8_t osf_mask; /* OSF bit within osf_addr */
+ uint8_t ctrl_reset; /* control-register power-on value */
+};
+
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
.version_id = 2,
@@ -82,16 +100,19 @@ static void ds1338_capture_current_time(DS1338State *s)
s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
s->nvram[4] = to_bcd(now.tm_mday);
s->nvram[5] = to_bcd(now.tm_mon + 1);
- s->nvram[6] = to_bcd(now.tm_year - 100);
+ s->nvram[6] = to_bcd(now.tm_year % 100);
}
static void ds1338_inc_regptr(DS1338State *s)
{
- /* The register pointer wraps around after 0x3F; wraparound
- * causes the current time/date to be retransferred into
- * the secondary registers.
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ /*
+ * The register pointer wraps around after the last register; wraparound
+ * causes the current time/date to be retransferred into the secondary
+ * registers.
*/
- s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
+ s->ptr = (s->ptr + 1) % k->num_regs;
if (!s->ptr) {
ds1338_capture_current_time(s);
}
@@ -136,11 +157,12 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
static int ds1338_send(I2CSlave *i2c, uint8_t data)
{
DS1338State *s = DS1338(i2c);
+ DS1338Class *k = DS1338_GET_CLASS(i2c);
trace_ds1338_send(s->ptr, data);
if (s->addr_byte) {
- s->ptr = data & (NVRAM_SIZE - 1);
+ s->ptr = data % k->num_regs;
s->addr_byte = false;
return 0;
}
@@ -190,18 +212,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
break;
}
s->offset = qemu_timedate_diff(&now);
- } else if (s->ptr == 7) {
- /* Control register. */
-
- /* Ensure bits 2, 3 and 6 will read back as zero. */
- data &= 0xB3;
-
- /* Attempting to write the OSF flag to logic 1 leaves the
- value unchanged. */
- data = (data & ~CTRL_OSF) | (data & s->nvram[s->ptr] & CTRL_OSF);
-
- s->nvram[s->ptr] = data;
} else {
+ if (s->ptr == k->ctrl_addr) {
+ /* Control register: reserved bits read back as zero. */
+ data &= k->ctrl_mask;
+ }
+ if (s->ptr == k->osf_addr) {
+ /*
+ * Attempting to write the OSF flag to logic 1 leaves the
+ * value unchanged.
+ */
+ data = (data & ~k->osf_mask) |
+ (data & s->nvram[s->ptr] & k->osf_mask);
+ }
s->nvram[s->ptr] = data;
}
ds1338_inc_regptr(s);
@@ -211,11 +234,13 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
static void ds1338_reset_hold(Object *obj, ResetType type)
{
DS1338State *s = DS1338(obj);
+ DS1338Class *k = DS1338_GET_CLASS(s);
/* The clock is running and synchronized with the host */
s->offset = 0;
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
+ s->nvram[k->ctrl_addr] = k->ctrl_reset;
s->ptr = 0;
s->addr_byte = false;
}
@@ -225,12 +250,21 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DS1338Class *dsc = DS1338_CLASS(klass);
k->event = ds1338_event;
k->recv = ds1338_recv;
k->send = ds1338_send;
rc->phases.hold = ds1338_reset_hold;
+ dc->desc = "DS1338 I2C RTC with 56-byte NV RAM";
dc->vmsd = &vmstate_ds1338;
+
+ dsc->num_regs = DS1338_NUM_REGS;
+ dsc->ctrl_addr = DS1338_CONTROL;
+ dsc->ctrl_mask = DS1338_CTRL_MASK;
+ dsc->osf_addr = DS1338_CONTROL;
+ dsc->osf_mask = CTRL_OSF;
+ dsc->ctrl_reset = DS1338_CTRL_RESET;
}
static const TypeInfo ds1338_types[] = {
@@ -238,6 +272,7 @@ static const TypeInfo ds1338_types[] = {
.name = TYPE_DS1338,
.parent = TYPE_I2C_SLAVE,
.instance_size = sizeof(DS1338State),
+ .class_size = sizeof(DS1338Class),
.class_init = ds1338_class_init,
},
};
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 13/50] hw/rtc: ds1338: program the clock from the time registers
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
A write to a time register rebuilt a struct tm from the current date,
substituted the field being written and folded the result back into
the offset. Every byte of a multi-byte transfer was therefore
normalised against the fields the guest had not written yet: a date of
31 programmed while the month register still held a 30-day month moved
the clock into the following month.
Keep the written value in the register file and derive the offset from
the file as a whole. This needs the registers refreshed on a
START_SEND, as the hardware does on any START.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 116 ++++++++++++++++++++++++++++++--------------------------
1 file changed, 63 insertions(+), 53 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 7eaa663113..862e03bd32 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -8,6 +8,12 @@
*
* Contributions after 2012-01-13 are licensed under the terms of the
* GNU GPL, version 2 or (at your option) any later version.
+ *
+ * Limitations:
+ * - Writing the seconds register does not reset the internal countdown chain,
+ * so the sub-second phase of the clock is not modelled.
+ * - Leap years follow the Gregorian rule, where the parts stop at 2100: they
+ * derive the leap year from the two-digit year alone.
*/
#include "qemu/osdep.h"
@@ -75,6 +81,41 @@ static const VMStateDescription vmstate_ds1338 = {
}
};
+/* Reconstruct a struct tm from the BCD time registers in nvram. */
+static void ds1338_time_from_regs(DS1338State *s, struct tm *now)
+{
+ qemu_get_timedate(now, s->offset);
+ now->tm_sec = from_bcd(s->nvram[0] & 0x7f);
+ now->tm_min = from_bcd(s->nvram[1] & 0x7f);
+ if (s->nvram[2] & HOURS_12) {
+ int tmp = from_bcd(s->nvram[2] & (HOURS_PM - 1));
+ if (s->nvram[2] & HOURS_PM) {
+ tmp += 12;
+ }
+ if (tmp % 12 == 0) {
+ tmp -= 12;
+ }
+ now->tm_hour = tmp;
+ } else {
+ now->tm_hour = from_bcd(s->nvram[2] & (HOURS_12 - 1));
+ }
+ now->tm_mday = from_bcd(s->nvram[4] & 0x3f);
+ now->tm_mon = from_bcd(s->nvram[5] & 0x1f) - 1;
+ now->tm_year = from_bcd(s->nvram[6]) + 100;
+}
+
+static void ds1338_resync_from_regs(DS1338State *s)
+{
+ struct tm now;
+ int user_wday;
+
+ ds1338_time_from_regs(s, &now);
+ s->offset = qemu_timedate_diff(&now);
+ qemu_get_timedate(&now, s->offset);
+ user_wday = (s->nvram[3] & 7) - 1;
+ s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
+}
+
static void ds1338_capture_current_time(DS1338State *s)
{
/* Capture the current time into the secondary registers
@@ -123,16 +164,16 @@ static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
DS1338State *s = DS1338(i2c);
switch (event) {
- case I2C_START_RECV:
- /* In h/w, capture happens on any START condition, not just a
- * START_RECV, but there is no need to actually capture on
- * START_SEND, because the guest can't get at that data
- * without going through a START_RECV which would overwrite it.
- */
- ds1338_capture_current_time(s);
- break;
case I2C_START_SEND:
s->addr_byte = true;
+ /* fall through */
+ case I2C_START_RECV:
+ /*
+ * In h/w the time is transferred to the user registers on any START
+ * condition, so a write modifies the running time rather than
+ * whatever the last read left behind.
+ */
+ ds1338_capture_current_time(s);
break;
default:
break;
@@ -167,51 +208,20 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
if (s->ptr < 7) {
- /* Time register. */
- struct tm now;
- qemu_get_timedate(&now, s->offset);
- switch(s->ptr) {
- case 0:
- /* TODO: Implement CH (stop) bit. */
- now.tm_sec = from_bcd(data & 0x7f);
- break;
- case 1:
- now.tm_min = from_bcd(data & 0x7f);
- break;
- case 2:
- if (data & HOURS_12) {
- int tmp = from_bcd(data & (HOURS_PM - 1));
- if (data & HOURS_PM) {
- tmp += 12;
- }
- if (tmp % 12 == 0) {
- tmp -= 12;
- }
- now.tm_hour = tmp;
- } else {
- now.tm_hour = from_bcd(data & (HOURS_12 - 1));
- }
- break;
- case 3:
- {
- /* The day field is supposed to contain a value in
- the range 1-7. Otherwise behavior is undefined.
- */
- int user_wday = (data & 7) - 1;
- s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
- }
- break;
- case 4:
- now.tm_mday = from_bcd(data & 0x3f);
- break;
- case 5:
- now.tm_mon = from_bcd(data & 0x1f) - 1;
- break;
- case 6:
- now.tm_year = from_bcd(data) + 100;
- break;
- }
- s->offset = qemu_timedate_diff(&now);
+ /*
+ * Time register. The write lands in the register file and the clock
+ * is then rebuilt from it as a whole: a transfer that programs the
+ * date before the month it belongs to must not be normalised away
+ * against the month still held from the previous date.
+ *
+ * TODO: Implement CH (stop) bit.
+ */
+ static const uint8_t valid[7] = {
+ 0x7f, 0x7f, 0x7f, 0x07, 0x3f, 0x1f, 0xff
+ };
+
+ s->nvram[s->ptr] = data & valid[s->ptr];
+ ds1338_resync_from_regs(s);
} else {
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 13/50] hw/rtc: ds1338: program the clock from the time registers
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
A write to a time register rebuilt a struct tm from the current date,
substituted the field being written and folded the result back into
the offset. Every byte of a multi-byte transfer was therefore
normalised against the fields the guest had not written yet: a date of
31 programmed while the month register still held a 30-day month moved
the clock into the following month.
Keep the written value in the register file and derive the offset from
the file as a whole. This needs the registers refreshed on a
START_SEND, as the hardware does on any START.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 116 ++++++++++++++++++++++++++++++--------------------------
1 file changed, 63 insertions(+), 53 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 7eaa663113..862e03bd32 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -8,6 +8,12 @@
*
* Contributions after 2012-01-13 are licensed under the terms of the
* GNU GPL, version 2 or (at your option) any later version.
+ *
+ * Limitations:
+ * - Writing the seconds register does not reset the internal countdown chain,
+ * so the sub-second phase of the clock is not modelled.
+ * - Leap years follow the Gregorian rule, where the parts stop at 2100: they
+ * derive the leap year from the two-digit year alone.
*/
#include "qemu/osdep.h"
@@ -75,6 +81,41 @@ static const VMStateDescription vmstate_ds1338 = {
}
};
+/* Reconstruct a struct tm from the BCD time registers in nvram. */
+static void ds1338_time_from_regs(DS1338State *s, struct tm *now)
+{
+ qemu_get_timedate(now, s->offset);
+ now->tm_sec = from_bcd(s->nvram[0] & 0x7f);
+ now->tm_min = from_bcd(s->nvram[1] & 0x7f);
+ if (s->nvram[2] & HOURS_12) {
+ int tmp = from_bcd(s->nvram[2] & (HOURS_PM - 1));
+ if (s->nvram[2] & HOURS_PM) {
+ tmp += 12;
+ }
+ if (tmp % 12 == 0) {
+ tmp -= 12;
+ }
+ now->tm_hour = tmp;
+ } else {
+ now->tm_hour = from_bcd(s->nvram[2] & (HOURS_12 - 1));
+ }
+ now->tm_mday = from_bcd(s->nvram[4] & 0x3f);
+ now->tm_mon = from_bcd(s->nvram[5] & 0x1f) - 1;
+ now->tm_year = from_bcd(s->nvram[6]) + 100;
+}
+
+static void ds1338_resync_from_regs(DS1338State *s)
+{
+ struct tm now;
+ int user_wday;
+
+ ds1338_time_from_regs(s, &now);
+ s->offset = qemu_timedate_diff(&now);
+ qemu_get_timedate(&now, s->offset);
+ user_wday = (s->nvram[3] & 7) - 1;
+ s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
+}
+
static void ds1338_capture_current_time(DS1338State *s)
{
/* Capture the current time into the secondary registers
@@ -123,16 +164,16 @@ static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
DS1338State *s = DS1338(i2c);
switch (event) {
- case I2C_START_RECV:
- /* In h/w, capture happens on any START condition, not just a
- * START_RECV, but there is no need to actually capture on
- * START_SEND, because the guest can't get at that data
- * without going through a START_RECV which would overwrite it.
- */
- ds1338_capture_current_time(s);
- break;
case I2C_START_SEND:
s->addr_byte = true;
+ /* fall through */
+ case I2C_START_RECV:
+ /*
+ * In h/w the time is transferred to the user registers on any START
+ * condition, so a write modifies the running time rather than
+ * whatever the last read left behind.
+ */
+ ds1338_capture_current_time(s);
break;
default:
break;
@@ -167,51 +208,20 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
if (s->ptr < 7) {
- /* Time register. */
- struct tm now;
- qemu_get_timedate(&now, s->offset);
- switch(s->ptr) {
- case 0:
- /* TODO: Implement CH (stop) bit. */
- now.tm_sec = from_bcd(data & 0x7f);
- break;
- case 1:
- now.tm_min = from_bcd(data & 0x7f);
- break;
- case 2:
- if (data & HOURS_12) {
- int tmp = from_bcd(data & (HOURS_PM - 1));
- if (data & HOURS_PM) {
- tmp += 12;
- }
- if (tmp % 12 == 0) {
- tmp -= 12;
- }
- now.tm_hour = tmp;
- } else {
- now.tm_hour = from_bcd(data & (HOURS_12 - 1));
- }
- break;
- case 3:
- {
- /* The day field is supposed to contain a value in
- the range 1-7. Otherwise behavior is undefined.
- */
- int user_wday = (data & 7) - 1;
- s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
- }
- break;
- case 4:
- now.tm_mday = from_bcd(data & 0x3f);
- break;
- case 5:
- now.tm_mon = from_bcd(data & 0x1f) - 1;
- break;
- case 6:
- now.tm_year = from_bcd(data) + 100;
- break;
- }
- s->offset = qemu_timedate_diff(&now);
+ /*
+ * Time register. The write lands in the register file and the clock
+ * is then rebuilt from it as a whole: a transfer that programs the
+ * date before the month it belongs to must not be normalised away
+ * against the month still held from the previous date.
+ *
+ * TODO: Implement CH (stop) bit.
+ */
+ static const uint8_t valid[7] = {
+ 0x7f, 0x7f, 0x7f, 0x07, 0x3f, 0x1f, 0xff
+ };
+
+ s->nvram[s->ptr] = data & valid[s->ptr];
+ ds1338_resync_from_regs(s);
} else {
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 13/50] hw/rtc: ds1338: program the clock from the time registers
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
A write to a time register rebuilt a struct tm from the current date,
substituted the field being written and folded the result back into
the offset. Every byte of a multi-byte transfer was therefore
normalised against the fields the guest had not written yet: a date of
31 programmed while the month register still held a 30-day month moved
the clock into the following month.
Keep the written value in the register file and derive the offset from
the file as a whole. This needs the registers refreshed on a
START_SEND, as the hardware does on any START.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 116 ++++++++++++++++++++++++++++++--------------------------
1 file changed, 63 insertions(+), 53 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 7eaa663113..862e03bd32 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -8,6 +8,12 @@
*
* Contributions after 2012-01-13 are licensed under the terms of the
* GNU GPL, version 2 or (at your option) any later version.
+ *
+ * Limitations:
+ * - Writing the seconds register does not reset the internal countdown chain,
+ * so the sub-second phase of the clock is not modelled.
+ * - Leap years follow the Gregorian rule, where the parts stop at 2100: they
+ * derive the leap year from the two-digit year alone.
*/
#include "qemu/osdep.h"
@@ -75,6 +81,41 @@ static const VMStateDescription vmstate_ds1338 = {
}
};
+/* Reconstruct a struct tm from the BCD time registers in nvram. */
+static void ds1338_time_from_regs(DS1338State *s, struct tm *now)
+{
+ qemu_get_timedate(now, s->offset);
+ now->tm_sec = from_bcd(s->nvram[0] & 0x7f);
+ now->tm_min = from_bcd(s->nvram[1] & 0x7f);
+ if (s->nvram[2] & HOURS_12) {
+ int tmp = from_bcd(s->nvram[2] & (HOURS_PM - 1));
+ if (s->nvram[2] & HOURS_PM) {
+ tmp += 12;
+ }
+ if (tmp % 12 == 0) {
+ tmp -= 12;
+ }
+ now->tm_hour = tmp;
+ } else {
+ now->tm_hour = from_bcd(s->nvram[2] & (HOURS_12 - 1));
+ }
+ now->tm_mday = from_bcd(s->nvram[4] & 0x3f);
+ now->tm_mon = from_bcd(s->nvram[5] & 0x1f) - 1;
+ now->tm_year = from_bcd(s->nvram[6]) + 100;
+}
+
+static void ds1338_resync_from_regs(DS1338State *s)
+{
+ struct tm now;
+ int user_wday;
+
+ ds1338_time_from_regs(s, &now);
+ s->offset = qemu_timedate_diff(&now);
+ qemu_get_timedate(&now, s->offset);
+ user_wday = (s->nvram[3] & 7) - 1;
+ s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
+}
+
static void ds1338_capture_current_time(DS1338State *s)
{
/* Capture the current time into the secondary registers
@@ -123,16 +164,16 @@ static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
DS1338State *s = DS1338(i2c);
switch (event) {
- case I2C_START_RECV:
- /* In h/w, capture happens on any START condition, not just a
- * START_RECV, but there is no need to actually capture on
- * START_SEND, because the guest can't get at that data
- * without going through a START_RECV which would overwrite it.
- */
- ds1338_capture_current_time(s);
- break;
case I2C_START_SEND:
s->addr_byte = true;
+ /* fall through */
+ case I2C_START_RECV:
+ /*
+ * In h/w the time is transferred to the user registers on any START
+ * condition, so a write modifies the running time rather than
+ * whatever the last read left behind.
+ */
+ ds1338_capture_current_time(s);
break;
default:
break;
@@ -167,51 +208,20 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
if (s->ptr < 7) {
- /* Time register. */
- struct tm now;
- qemu_get_timedate(&now, s->offset);
- switch(s->ptr) {
- case 0:
- /* TODO: Implement CH (stop) bit. */
- now.tm_sec = from_bcd(data & 0x7f);
- break;
- case 1:
- now.tm_min = from_bcd(data & 0x7f);
- break;
- case 2:
- if (data & HOURS_12) {
- int tmp = from_bcd(data & (HOURS_PM - 1));
- if (data & HOURS_PM) {
- tmp += 12;
- }
- if (tmp % 12 == 0) {
- tmp -= 12;
- }
- now.tm_hour = tmp;
- } else {
- now.tm_hour = from_bcd(data & (HOURS_12 - 1));
- }
- break;
- case 3:
- {
- /* The day field is supposed to contain a value in
- the range 1-7. Otherwise behavior is undefined.
- */
- int user_wday = (data & 7) - 1;
- s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
- }
- break;
- case 4:
- now.tm_mday = from_bcd(data & 0x3f);
- break;
- case 5:
- now.tm_mon = from_bcd(data & 0x1f) - 1;
- break;
- case 6:
- now.tm_year = from_bcd(data) + 100;
- break;
- }
- s->offset = qemu_timedate_diff(&now);
+ /*
+ * Time register. The write lands in the register file and the clock
+ * is then rebuilt from it as a whole: a transfer that programs the
+ * date before the month it belongs to must not be normalised away
+ * against the month still held from the previous date.
+ *
+ * TODO: Implement CH (stop) bit.
+ */
+ static const uint8_t valid[7] = {
+ 0x7f, 0x7f, 0x7f, 0x07, 0x3f, 0x1f, 0xff
+ };
+
+ s->nvram[s->ptr] = data & valid[s->ptr];
+ ds1338_resync_from_regs(s);
} else {
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 14/50] hw/rtc: ds1338: implement the DS1338 clock-halt bit
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Bit 7 of the seconds register disables the oscillator, and the guest is
expected to clear it before trusting the time. The bit was masked away
on every write, so it always read back zero and the clock never
stopped.
The reset value stays clear rather than set as on real hardware,
keeping the model's convention that the clock comes up running and
synchronised with the host.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 36 +++++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 862e03bd32..79bcff4599 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -14,6 +14,7 @@
* so the sub-second phase of the clock is not modelled.
* - Leap years follow the Gregorian rule, where the parts stop at 2100: they
* derive the leap year from the two-digit year alone.
+ * - The model comes up with the clock running; real hardware comes up halted.
*/
#include "qemu/osdep.h"
@@ -53,6 +54,7 @@ struct DS1338State {
uint8_t nvram[NVRAM_SIZE];
int32_t ptr;
bool addr_byte;
+ bool osc_stopped; /* oscillator halted: time is frozen */
};
struct DS1338Class {
@@ -64,11 +66,12 @@ struct DS1338Class {
uint8_t osf_addr; /* register holding the OSF flag */
uint8_t osf_mask; /* OSF bit within osf_addr */
uint8_t ctrl_reset; /* control-register power-on value */
+ uint8_t ch_mask; /* Clock-halt bit in the seconds reg */
};
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
- .version_id = 2,
+ .version_id = 3,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
@@ -77,6 +80,7 @@ static const VMStateDescription vmstate_ds1338 = {
VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
VMSTATE_INT32(ptr, DS1338State),
VMSTATE_BOOL(addr_byte, DS1338State),
+ VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
VMSTATE_END_OF_LIST()
}
};
@@ -122,6 +126,11 @@ static void ds1338_capture_current_time(DS1338State *s)
* which will be actually read by the data transfer operation.
*/
struct tm now;
+
+ if (s->osc_stopped) {
+ return;
+ }
+
qemu_get_timedate(&now, s->offset);
s->nvram[0] = to_bcd(now.tm_sec);
s->nvram[1] = to_bcd(now.tm_min);
@@ -213,15 +222,30 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
* is then rebuilt from it as a whole: a transfer that programs the
* date before the month it belongs to must not be normalised away
* against the month still held from the previous date.
- *
- * TODO: Implement CH (stop) bit.
*/
static const uint8_t valid[7] = {
0x7f, 0x7f, 0x7f, 0x07, 0x3f, 0x1f, 0xff
};
+ uint8_t mask = valid[s->ptr];
+ bool halt = s->osc_stopped;
- s->nvram[s->ptr] = data & valid[s->ptr];
- ds1338_resync_from_regs(s);
+ if (s->ptr == 0) {
+ mask |= k->ch_mask;
+ if (k->ch_mask) {
+ halt = data & k->ch_mask;
+ }
+ }
+
+ if (halt && !s->osc_stopped) {
+ /* CH set: freeze the counters before the register is stored. */
+ ds1338_capture_current_time(s);
+ s->nvram[k->osf_addr] |= k->osf_mask;
+ }
+ s->nvram[s->ptr] = data & mask;
+ s->osc_stopped = halt;
+ if (!halt) {
+ ds1338_resync_from_regs(s);
+ }
} else {
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
@@ -253,6 +277,7 @@ static void ds1338_reset_hold(Object *obj, ResetType type)
s->nvram[k->ctrl_addr] = k->ctrl_reset;
s->ptr = 0;
s->addr_byte = false;
+ s->osc_stopped = false;
}
static void ds1338_class_init(ObjectClass *klass, const void *data)
@@ -275,6 +300,7 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
dsc->osf_addr = DS1338_CONTROL;
dsc->osf_mask = CTRL_OSF;
dsc->ctrl_reset = DS1338_CTRL_RESET;
+ dsc->ch_mask = SECONDS_CH;
}
static const TypeInfo ds1338_types[] = {
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 14/50] hw/rtc: ds1338: implement the DS1338 clock-halt bit
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Bit 7 of the seconds register disables the oscillator, and the guest is
expected to clear it before trusting the time. The bit was masked away
on every write, so it always read back zero and the clock never
stopped.
The reset value stays clear rather than set as on real hardware,
keeping the model's convention that the clock comes up running and
synchronised with the host.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 36 +++++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 862e03bd32..79bcff4599 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -14,6 +14,7 @@
* so the sub-second phase of the clock is not modelled.
* - Leap years follow the Gregorian rule, where the parts stop at 2100: they
* derive the leap year from the two-digit year alone.
+ * - The model comes up with the clock running; real hardware comes up halted.
*/
#include "qemu/osdep.h"
@@ -53,6 +54,7 @@ struct DS1338State {
uint8_t nvram[NVRAM_SIZE];
int32_t ptr;
bool addr_byte;
+ bool osc_stopped; /* oscillator halted: time is frozen */
};
struct DS1338Class {
@@ -64,11 +66,12 @@ struct DS1338Class {
uint8_t osf_addr; /* register holding the OSF flag */
uint8_t osf_mask; /* OSF bit within osf_addr */
uint8_t ctrl_reset; /* control-register power-on value */
+ uint8_t ch_mask; /* Clock-halt bit in the seconds reg */
};
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
- .version_id = 2,
+ .version_id = 3,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
@@ -77,6 +80,7 @@ static const VMStateDescription vmstate_ds1338 = {
VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
VMSTATE_INT32(ptr, DS1338State),
VMSTATE_BOOL(addr_byte, DS1338State),
+ VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
VMSTATE_END_OF_LIST()
}
};
@@ -122,6 +126,11 @@ static void ds1338_capture_current_time(DS1338State *s)
* which will be actually read by the data transfer operation.
*/
struct tm now;
+
+ if (s->osc_stopped) {
+ return;
+ }
+
qemu_get_timedate(&now, s->offset);
s->nvram[0] = to_bcd(now.tm_sec);
s->nvram[1] = to_bcd(now.tm_min);
@@ -213,15 +222,30 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
* is then rebuilt from it as a whole: a transfer that programs the
* date before the month it belongs to must not be normalised away
* against the month still held from the previous date.
- *
- * TODO: Implement CH (stop) bit.
*/
static const uint8_t valid[7] = {
0x7f, 0x7f, 0x7f, 0x07, 0x3f, 0x1f, 0xff
};
+ uint8_t mask = valid[s->ptr];
+ bool halt = s->osc_stopped;
- s->nvram[s->ptr] = data & valid[s->ptr];
- ds1338_resync_from_regs(s);
+ if (s->ptr == 0) {
+ mask |= k->ch_mask;
+ if (k->ch_mask) {
+ halt = data & k->ch_mask;
+ }
+ }
+
+ if (halt && !s->osc_stopped) {
+ /* CH set: freeze the counters before the register is stored. */
+ ds1338_capture_current_time(s);
+ s->nvram[k->osf_addr] |= k->osf_mask;
+ }
+ s->nvram[s->ptr] = data & mask;
+ s->osc_stopped = halt;
+ if (!halt) {
+ ds1338_resync_from_regs(s);
+ }
} else {
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
@@ -253,6 +277,7 @@ static void ds1338_reset_hold(Object *obj, ResetType type)
s->nvram[k->ctrl_addr] = k->ctrl_reset;
s->ptr = 0;
s->addr_byte = false;
+ s->osc_stopped = false;
}
static void ds1338_class_init(ObjectClass *klass, const void *data)
@@ -275,6 +300,7 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
dsc->osf_addr = DS1338_CONTROL;
dsc->osf_mask = CTRL_OSF;
dsc->ctrl_reset = DS1338_CTRL_RESET;
+ dsc->ch_mask = SECONDS_CH;
}
static const TypeInfo ds1338_types[] = {
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 14/50] hw/rtc: ds1338: implement the DS1338 clock-halt bit
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Bit 7 of the seconds register disables the oscillator, and the guest is
expected to clear it before trusting the time. The bit was masked away
on every write, so it always read back zero and the clock never
stopped.
The reset value stays clear rather than set as on real hardware,
keeping the model's convention that the clock comes up running and
synchronised with the host.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 36 +++++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 862e03bd32..79bcff4599 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -14,6 +14,7 @@
* so the sub-second phase of the clock is not modelled.
* - Leap years follow the Gregorian rule, where the parts stop at 2100: they
* derive the leap year from the two-digit year alone.
+ * - The model comes up with the clock running; real hardware comes up halted.
*/
#include "qemu/osdep.h"
@@ -53,6 +54,7 @@ struct DS1338State {
uint8_t nvram[NVRAM_SIZE];
int32_t ptr;
bool addr_byte;
+ bool osc_stopped; /* oscillator halted: time is frozen */
};
struct DS1338Class {
@@ -64,11 +66,12 @@ struct DS1338Class {
uint8_t osf_addr; /* register holding the OSF flag */
uint8_t osf_mask; /* OSF bit within osf_addr */
uint8_t ctrl_reset; /* control-register power-on value */
+ uint8_t ch_mask; /* Clock-halt bit in the seconds reg */
};
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
- .version_id = 2,
+ .version_id = 3,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
@@ -77,6 +80,7 @@ static const VMStateDescription vmstate_ds1338 = {
VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
VMSTATE_INT32(ptr, DS1338State),
VMSTATE_BOOL(addr_byte, DS1338State),
+ VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
VMSTATE_END_OF_LIST()
}
};
@@ -122,6 +126,11 @@ static void ds1338_capture_current_time(DS1338State *s)
* which will be actually read by the data transfer operation.
*/
struct tm now;
+
+ if (s->osc_stopped) {
+ return;
+ }
+
qemu_get_timedate(&now, s->offset);
s->nvram[0] = to_bcd(now.tm_sec);
s->nvram[1] = to_bcd(now.tm_min);
@@ -213,15 +222,30 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
* is then rebuilt from it as a whole: a transfer that programs the
* date before the month it belongs to must not be normalised away
* against the month still held from the previous date.
- *
- * TODO: Implement CH (stop) bit.
*/
static const uint8_t valid[7] = {
0x7f, 0x7f, 0x7f, 0x07, 0x3f, 0x1f, 0xff
};
+ uint8_t mask = valid[s->ptr];
+ bool halt = s->osc_stopped;
- s->nvram[s->ptr] = data & valid[s->ptr];
- ds1338_resync_from_regs(s);
+ if (s->ptr == 0) {
+ mask |= k->ch_mask;
+ if (k->ch_mask) {
+ halt = data & k->ch_mask;
+ }
+ }
+
+ if (halt && !s->osc_stopped) {
+ /* CH set: freeze the counters before the register is stored. */
+ ds1338_capture_current_time(s);
+ s->nvram[k->osf_addr] |= k->osf_mask;
+ }
+ s->nvram[s->ptr] = data & mask;
+ s->osc_stopped = halt;
+ if (!halt) {
+ ds1338_resync_from_regs(s);
+ }
} else {
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
@@ -253,6 +277,7 @@ static void ds1338_reset_hold(Object *obj, ResetType type)
s->nvram[k->ctrl_addr] = k->ctrl_reset;
s->ptr = 0;
s->addr_byte = false;
+ s->osc_stopped = false;
}
static void ds1338_class_init(ObjectClass *klass, const void *data)
@@ -275,6 +300,7 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
dsc->osf_addr = DS1338_CONTROL;
dsc->osf_mask = CTRL_OSF;
dsc->ctrl_reset = DS1338_CTRL_RESET;
+ dsc->ch_mask = SECONDS_CH;
}
static const TypeInfo ds1338_types[] = {
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 15/50] hw/rtc: ds1338: refresh the user registers on a read rollover only
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Synchronising the user buffers when the register pointer rolls over to
zero is a property of reading them; hardware routes a write to the
counters themselves. Doing it from the shared pointer increment meant a
write transfer long enough to wrap overwrote the bytes the guest had
just sent with the current time.
Move the refresh to the read path.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 79bcff4599..c5fc74e6ca 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -12,6 +12,8 @@
* Limitations:
* - Writing the seconds register does not reset the internal countdown chain,
* so the sub-second phase of the clock is not modelled.
+ * - The user registers are refreshed on a START and on a read that wraps the
+ * register pointer, but not on a STOP.
* - Leap years follow the Gregorian rule, where the parts stop at 2100: they
* derive the leap year from the two-digit year alone.
* - The model comes up with the clock running; real hardware comes up halted.
@@ -157,15 +159,7 @@ static void ds1338_inc_regptr(DS1338State *s)
{
DS1338Class *k = DS1338_GET_CLASS(s);
- /*
- * The register pointer wraps around after the last register; wraparound
- * causes the current time/date to be retransferred into the secondary
- * registers.
- */
s->ptr = (s->ptr + 1) % k->num_regs;
- if (!s->ptr) {
- ds1338_capture_current_time(s);
- }
}
static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
@@ -201,6 +195,10 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
trace_ds1338_recv(s->ptr, res);
ds1338_inc_regptr(s);
+ if (!s->ptr) {
+ /* Read-only: a wrapping write must keep the bytes it just sent. */
+ ds1338_capture_current_time(s);
+ }
return res;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 15/50] hw/rtc: ds1338: refresh the user registers on a read rollover only
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Synchronising the user buffers when the register pointer rolls over to
zero is a property of reading them; hardware routes a write to the
counters themselves. Doing it from the shared pointer increment meant a
write transfer long enough to wrap overwrote the bytes the guest had
just sent with the current time.
Move the refresh to the read path.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 79bcff4599..c5fc74e6ca 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -12,6 +12,8 @@
* Limitations:
* - Writing the seconds register does not reset the internal countdown chain,
* so the sub-second phase of the clock is not modelled.
+ * - The user registers are refreshed on a START and on a read that wraps the
+ * register pointer, but not on a STOP.
* - Leap years follow the Gregorian rule, where the parts stop at 2100: they
* derive the leap year from the two-digit year alone.
* - The model comes up with the clock running; real hardware comes up halted.
@@ -157,15 +159,7 @@ static void ds1338_inc_regptr(DS1338State *s)
{
DS1338Class *k = DS1338_GET_CLASS(s);
- /*
- * The register pointer wraps around after the last register; wraparound
- * causes the current time/date to be retransferred into the secondary
- * registers.
- */
s->ptr = (s->ptr + 1) % k->num_regs;
- if (!s->ptr) {
- ds1338_capture_current_time(s);
- }
}
static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
@@ -201,6 +195,10 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
trace_ds1338_recv(s->ptr, res);
ds1338_inc_regptr(s);
+ if (!s->ptr) {
+ /* Read-only: a wrapping write must keep the bytes it just sent. */
+ ds1338_capture_current_time(s);
+ }
return res;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 15/50] hw/rtc: ds1338: refresh the user registers on a read rollover only
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Synchronising the user buffers when the register pointer rolls over to
zero is a property of reading them; hardware routes a write to the
counters themselves. Doing it from the shared pointer increment meant a
write transfer long enough to wrap overwrote the bytes the guest had
just sent with the current time.
Move the refresh to the read path.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 79bcff4599..c5fc74e6ca 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -12,6 +12,8 @@
* Limitations:
* - Writing the seconds register does not reset the internal countdown chain,
* so the sub-second phase of the clock is not modelled.
+ * - The user registers are refreshed on a START and on a read that wraps the
+ * register pointer, but not on a STOP.
* - Leap years follow the Gregorian rule, where the parts stop at 2100: they
* derive the leap year from the two-digit year alone.
* - The model comes up with the clock running; real hardware comes up halted.
@@ -157,15 +159,7 @@ static void ds1338_inc_regptr(DS1338State *s)
{
DS1338Class *k = DS1338_GET_CLASS(s);
- /*
- * The register pointer wraps around after the last register; wraparound
- * causes the current time/date to be retransferred into the secondary
- * registers.
- */
s->ptr = (s->ptr + 1) % k->num_regs;
- if (!s->ptr) {
- ds1338_capture_current_time(s);
- }
}
static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
@@ -201,6 +195,10 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
trace_ds1338_recv(s->ptr, res);
ds1338_inc_regptr(s);
+ if (!s->ptr) {
+ /* Read-only: a wrapping write must keep the bytes it just sent. */
+ ds1338_capture_current_time(s);
+ }
return res;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 16/50] hw/rtc: ds1338: add a persist-on-reset property
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
A real RTC keeps counting across a reboot of the machine it sits in,
and its register file comes back with it. The model instead wiped the
registers on every reset and resynchronised the clock with the host.
Add a property selecting between the two. It defaults off, leaving
every board as it was.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index c5fc74e6ca..21deb94689 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -22,6 +22,7 @@
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/rtc/ds1338.h"
+#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "qemu/bcd.h"
#include "qom/object.h"
@@ -56,7 +57,8 @@ struct DS1338State {
uint8_t nvram[NVRAM_SIZE];
int32_t ptr;
bool addr_byte;
- bool osc_stopped; /* oscillator halted: time is frozen */
+ bool osc_stopped; /* oscillator halted: time is frozen */
+ bool persist_on_reset; /* keep the registers across a reset */
};
struct DS1338Class {
@@ -263,9 +265,8 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
-static void ds1338_reset_hold(Object *obj, ResetType type)
+static void ds1338_power_on(DS1338State *s)
{
- DS1338State *s = DS1338(obj);
DS1338Class *k = DS1338_GET_CLASS(s);
/* The clock is running and synchronized with the host */
@@ -273,11 +274,29 @@ static void ds1338_reset_hold(Object *obj, ResetType type)
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
s->nvram[k->ctrl_addr] = k->ctrl_reset;
+ s->osc_stopped = false;
+}
+
+static void ds1338_realize(DeviceState *dev, Error **errp)
+{
+ ds1338_power_on(DS1338(dev));
+}
+
+static void ds1338_reset_hold(Object *obj, ResetType type)
+{
+ DS1338State *s = DS1338(obj);
+
+ if (!s->persist_on_reset) {
+ ds1338_power_on(s);
+ }
s->ptr = 0;
s->addr_byte = false;
- s->osc_stopped = false;
}
+static const Property ds1338_properties[] = {
+ DEFINE_PROP_BOOL("persist-on-reset", DS1338State, persist_on_reset, false),
+};
+
static void ds1338_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
@@ -289,6 +308,8 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
k->recv = ds1338_recv;
k->send = ds1338_send;
rc->phases.hold = ds1338_reset_hold;
+ dc->realize = ds1338_realize;
+ device_class_set_props(dc, ds1338_properties);
dc->desc = "DS1338 I2C RTC with 56-byte NV RAM";
dc->vmsd = &vmstate_ds1338;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 16/50] hw/rtc: ds1338: add a persist-on-reset property
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
A real RTC keeps counting across a reboot of the machine it sits in,
and its register file comes back with it. The model instead wiped the
registers on every reset and resynchronised the clock with the host.
Add a property selecting between the two. It defaults off, leaving
every board as it was.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index c5fc74e6ca..21deb94689 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -22,6 +22,7 @@
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/rtc/ds1338.h"
+#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "qemu/bcd.h"
#include "qom/object.h"
@@ -56,7 +57,8 @@ struct DS1338State {
uint8_t nvram[NVRAM_SIZE];
int32_t ptr;
bool addr_byte;
- bool osc_stopped; /* oscillator halted: time is frozen */
+ bool osc_stopped; /* oscillator halted: time is frozen */
+ bool persist_on_reset; /* keep the registers across a reset */
};
struct DS1338Class {
@@ -263,9 +265,8 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
-static void ds1338_reset_hold(Object *obj, ResetType type)
+static void ds1338_power_on(DS1338State *s)
{
- DS1338State *s = DS1338(obj);
DS1338Class *k = DS1338_GET_CLASS(s);
/* The clock is running and synchronized with the host */
@@ -273,11 +274,29 @@ static void ds1338_reset_hold(Object *obj, ResetType type)
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
s->nvram[k->ctrl_addr] = k->ctrl_reset;
+ s->osc_stopped = false;
+}
+
+static void ds1338_realize(DeviceState *dev, Error **errp)
+{
+ ds1338_power_on(DS1338(dev));
+}
+
+static void ds1338_reset_hold(Object *obj, ResetType type)
+{
+ DS1338State *s = DS1338(obj);
+
+ if (!s->persist_on_reset) {
+ ds1338_power_on(s);
+ }
s->ptr = 0;
s->addr_byte = false;
- s->osc_stopped = false;
}
+static const Property ds1338_properties[] = {
+ DEFINE_PROP_BOOL("persist-on-reset", DS1338State, persist_on_reset, false),
+};
+
static void ds1338_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
@@ -289,6 +308,8 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
k->recv = ds1338_recv;
k->send = ds1338_send;
rc->phases.hold = ds1338_reset_hold;
+ dc->realize = ds1338_realize;
+ device_class_set_props(dc, ds1338_properties);
dc->desc = "DS1338 I2C RTC with 56-byte NV RAM";
dc->vmsd = &vmstate_ds1338;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 16/50] hw/rtc: ds1338: add a persist-on-reset property
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
A real RTC keeps counting across a reboot of the machine it sits in,
and its register file comes back with it. The model instead wiped the
registers on every reset and resynchronised the clock with the host.
Add a property selecting between the two. It defaults off, leaving
every board as it was.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index c5fc74e6ca..21deb94689 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -22,6 +22,7 @@
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/rtc/ds1338.h"
+#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "qemu/bcd.h"
#include "qom/object.h"
@@ -56,7 +57,8 @@ struct DS1338State {
uint8_t nvram[NVRAM_SIZE];
int32_t ptr;
bool addr_byte;
- bool osc_stopped; /* oscillator halted: time is frozen */
+ bool osc_stopped; /* oscillator halted: time is frozen */
+ bool persist_on_reset; /* keep the registers across a reset */
};
struct DS1338Class {
@@ -263,9 +265,8 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
return 0;
}
-static void ds1338_reset_hold(Object *obj, ResetType type)
+static void ds1338_power_on(DS1338State *s)
{
- DS1338State *s = DS1338(obj);
DS1338Class *k = DS1338_GET_CLASS(s);
/* The clock is running and synchronized with the host */
@@ -273,11 +274,29 @@ static void ds1338_reset_hold(Object *obj, ResetType type)
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
s->nvram[k->ctrl_addr] = k->ctrl_reset;
+ s->osc_stopped = false;
+}
+
+static void ds1338_realize(DeviceState *dev, Error **errp)
+{
+ ds1338_power_on(DS1338(dev));
+}
+
+static void ds1338_reset_hold(Object *obj, ResetType type)
+{
+ DS1338State *s = DS1338(obj);
+
+ if (!s->persist_on_reset) {
+ ds1338_power_on(s);
+ }
s->ptr = 0;
s->addr_byte = false;
- s->osc_stopped = false;
}
+static const Property ds1338_properties[] = {
+ DEFINE_PROP_BOOL("persist-on-reset", DS1338State, persist_on_reset, false),
+};
+
static void ds1338_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
@@ -289,6 +308,8 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
k->recv = ds1338_recv;
k->send = ds1338_send;
rc->phases.hold = ds1338_reset_hold;
+ dc->realize = ds1338_realize;
+ device_class_set_props(dc, ds1338_properties);
dc->desc = "DS1338 I2C RTC with 56-byte NV RAM";
dc->vmsd = &vmstate_ds1338;
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 17/50] tests/qtest: ds1338: cover the DS1338-specific register map
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The register map is a set of per-model class properties. Pin the
values it holds for this part, so a later change cannot alter them
unnoticed.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index b8d0e65ec4..6066a34846 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -24,6 +24,15 @@
#define DS1338_ADDR 0x68
+/* DS1338 register map */
+#define DS1338_SECONDS 0x00
+#define DS1338_CONTROL 0x07
+#define DS1338_NVRAM 0x08
+#define DS1338_NUM_REGS 0x40
+
+#define DS1338_CTRL_OSF 0x20
+
+/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *i2cdev = (QI2CDevice *)obj;
@@ -32,7 +41,7 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
time_t now = time(NULL);
struct tm *tm_ptr = gmtime(&now);
- i2c_read_block(i2cdev, 0, resp, sizeof(resp));
+ i2c_read_block(i2cdev, DS1338_SECONDS, resp, sizeof(resp));
/* check retrieved time against local time */
g_assert_cmpuint(from_bcd(resp[4]), == , tm_ptr->tm_mday);
@@ -40,6 +49,56 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(2000 + from_bcd(resp[6]), == , 1900 + tm_ptr->tm_year);
}
+/* Writable control bits round-trip; the reserved ones read back zero. */
+static void test_control_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x93);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x93);
+
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x4c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+}
+
+/* The oscillator-stop flag can only be cleared by a write, never set. */
+static void test_osf_write_protect(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+ i2c_set8(i2cdev, DS1338_CONTROL, DS1338_CTRL_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+}
+
+/* The user RAM returns what the guest wrote to it. */
+static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, 0x08, 0xaa);
+ i2c_set8(i2cdev, 0x20, 0x55);
+ i2c_set8(i2cdev, 0x3f, 0xc3);
+
+ g_assert_cmphex(i2c_get8(i2cdev, 0x08), ==, 0xaa);
+ g_assert_cmphex(i2c_get8(i2cdev, 0x20), ==, 0x55);
+ g_assert_cmphex(i2c_get8(i2cdev, 0x3f), ==, 0xc3);
+}
+
+/* The register pointer wraps at the end of the map. */
+static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1338_NVRAM, 0x5a);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM + DS1338_NUM_REGS), ==, 0x5a);
+}
+
static void ds1338_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -49,6 +108,12 @@ static void ds1338_register_nodes(void)
qos_node_create_driver("ds1338", i2c_device_create);
qos_node_consumes("ds1338", "i2c-bus", &opts);
+
qos_add_test("tx-rx", "ds1338", send_and_receive, NULL);
+ qos_add_test("control-register", "ds1338", test_control_register, NULL);
+ qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
+ qos_add_test("nvram", "ds1338", test_nvram, NULL);
+ qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
}
+
libqos_init(ds1338_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 17/50] tests/qtest: ds1338: cover the DS1338-specific register map
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The register map is a set of per-model class properties. Pin the
values it holds for this part, so a later change cannot alter them
unnoticed.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index b8d0e65ec4..6066a34846 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -24,6 +24,15 @@
#define DS1338_ADDR 0x68
+/* DS1338 register map */
+#define DS1338_SECONDS 0x00
+#define DS1338_CONTROL 0x07
+#define DS1338_NVRAM 0x08
+#define DS1338_NUM_REGS 0x40
+
+#define DS1338_CTRL_OSF 0x20
+
+/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *i2cdev = (QI2CDevice *)obj;
@@ -32,7 +41,7 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
time_t now = time(NULL);
struct tm *tm_ptr = gmtime(&now);
- i2c_read_block(i2cdev, 0, resp, sizeof(resp));
+ i2c_read_block(i2cdev, DS1338_SECONDS, resp, sizeof(resp));
/* check retrieved time against local time */
g_assert_cmpuint(from_bcd(resp[4]), == , tm_ptr->tm_mday);
@@ -40,6 +49,56 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(2000 + from_bcd(resp[6]), == , 1900 + tm_ptr->tm_year);
}
+/* Writable control bits round-trip; the reserved ones read back zero. */
+static void test_control_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x93);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x93);
+
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x4c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+}
+
+/* The oscillator-stop flag can only be cleared by a write, never set. */
+static void test_osf_write_protect(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+ i2c_set8(i2cdev, DS1338_CONTROL, DS1338_CTRL_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+}
+
+/* The user RAM returns what the guest wrote to it. */
+static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, 0x08, 0xaa);
+ i2c_set8(i2cdev, 0x20, 0x55);
+ i2c_set8(i2cdev, 0x3f, 0xc3);
+
+ g_assert_cmphex(i2c_get8(i2cdev, 0x08), ==, 0xaa);
+ g_assert_cmphex(i2c_get8(i2cdev, 0x20), ==, 0x55);
+ g_assert_cmphex(i2c_get8(i2cdev, 0x3f), ==, 0xc3);
+}
+
+/* The register pointer wraps at the end of the map. */
+static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1338_NVRAM, 0x5a);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM + DS1338_NUM_REGS), ==, 0x5a);
+}
+
static void ds1338_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -49,6 +108,12 @@ static void ds1338_register_nodes(void)
qos_node_create_driver("ds1338", i2c_device_create);
qos_node_consumes("ds1338", "i2c-bus", &opts);
+
qos_add_test("tx-rx", "ds1338", send_and_receive, NULL);
+ qos_add_test("control-register", "ds1338", test_control_register, NULL);
+ qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
+ qos_add_test("nvram", "ds1338", test_nvram, NULL);
+ qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
}
+
libqos_init(ds1338_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 17/50] tests/qtest: ds1338: cover the DS1338-specific register map
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The register map is a set of per-model class properties. Pin the
values it holds for this part, so a later change cannot alter them
unnoticed.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index b8d0e65ec4..6066a34846 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -24,6 +24,15 @@
#define DS1338_ADDR 0x68
+/* DS1338 register map */
+#define DS1338_SECONDS 0x00
+#define DS1338_CONTROL 0x07
+#define DS1338_NVRAM 0x08
+#define DS1338_NUM_REGS 0x40
+
+#define DS1338_CTRL_OSF 0x20
+
+/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *i2cdev = (QI2CDevice *)obj;
@@ -32,7 +41,7 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
time_t now = time(NULL);
struct tm *tm_ptr = gmtime(&now);
- i2c_read_block(i2cdev, 0, resp, sizeof(resp));
+ i2c_read_block(i2cdev, DS1338_SECONDS, resp, sizeof(resp));
/* check retrieved time against local time */
g_assert_cmpuint(from_bcd(resp[4]), == , tm_ptr->tm_mday);
@@ -40,6 +49,56 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(2000 + from_bcd(resp[6]), == , 1900 + tm_ptr->tm_year);
}
+/* Writable control bits round-trip; the reserved ones read back zero. */
+static void test_control_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x93);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x93);
+
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x4c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+}
+
+/* The oscillator-stop flag can only be cleared by a write, never set. */
+static void test_osf_write_protect(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+ i2c_set8(i2cdev, DS1338_CONTROL, DS1338_CTRL_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+}
+
+/* The user RAM returns what the guest wrote to it. */
+static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, 0x08, 0xaa);
+ i2c_set8(i2cdev, 0x20, 0x55);
+ i2c_set8(i2cdev, 0x3f, 0xc3);
+
+ g_assert_cmphex(i2c_get8(i2cdev, 0x08), ==, 0xaa);
+ g_assert_cmphex(i2c_get8(i2cdev, 0x20), ==, 0x55);
+ g_assert_cmphex(i2c_get8(i2cdev, 0x3f), ==, 0xc3);
+}
+
+/* The register pointer wraps at the end of the map. */
+static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1338_NVRAM, 0x5a);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM + DS1338_NUM_REGS), ==, 0x5a);
+}
+
static void ds1338_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -49,6 +108,12 @@ static void ds1338_register_nodes(void)
qos_node_create_driver("ds1338", i2c_device_create);
qos_node_consumes("ds1338", "i2c-bus", &opts);
+
qos_add_test("tx-rx", "ds1338", send_and_receive, NULL);
+ qos_add_test("control-register", "ds1338", test_control_register, NULL);
+ qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
+ qos_add_test("nvram", "ds1338", test_nvram, NULL);
+ qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
}
+
libqos_init(ds1338_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 18/50] tests/qtest: ds1338: cover the power-on register defaults
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing pinned the reset value of the control register, which is how it
sat at zero rather than at its documented power-on state.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 6066a34846..fad0ca53d4 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -31,6 +31,7 @@
#define DS1338_NUM_REGS 0x40
#define DS1338_CTRL_OSF 0x20
+#define DS1338_CTRL_POR 0xb3
/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
@@ -49,6 +50,14 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(2000 + from_bcd(resp[6]), == , 1900 + tm_ptr->tm_year);
}
+/* The control register comes up in its documented power-on state. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, DS1338_CTRL_POR);
+}
+
/* Writable control bits round-trip; the reserved ones read back zero. */
static void test_control_register(void *obj, void *data,
QGuestAllocator *alloc)
@@ -110,6 +119,7 @@ static void ds1338_register_nodes(void)
qos_node_consumes("ds1338", "i2c-bus", &opts);
qos_add_test("tx-rx", "ds1338", send_and_receive, NULL);
+ qos_add_test("reset-defaults", "ds1338", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 18/50] tests/qtest: ds1338: cover the power-on register defaults
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing pinned the reset value of the control register, which is how it
sat at zero rather than at its documented power-on state.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 6066a34846..fad0ca53d4 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -31,6 +31,7 @@
#define DS1338_NUM_REGS 0x40
#define DS1338_CTRL_OSF 0x20
+#define DS1338_CTRL_POR 0xb3
/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
@@ -49,6 +50,14 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(2000 + from_bcd(resp[6]), == , 1900 + tm_ptr->tm_year);
}
+/* The control register comes up in its documented power-on state. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, DS1338_CTRL_POR);
+}
+
/* Writable control bits round-trip; the reserved ones read back zero. */
static void test_control_register(void *obj, void *data,
QGuestAllocator *alloc)
@@ -110,6 +119,7 @@ static void ds1338_register_nodes(void)
qos_node_consumes("ds1338", "i2c-bus", &opts);
qos_add_test("tx-rx", "ds1338", send_and_receive, NULL);
+ qos_add_test("reset-defaults", "ds1338", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 18/50] tests/qtest: ds1338: cover the power-on register defaults
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing pinned the reset value of the control register, which is how it
sat at zero rather than at its documented power-on state.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 6066a34846..fad0ca53d4 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -31,6 +31,7 @@
#define DS1338_NUM_REGS 0x40
#define DS1338_CTRL_OSF 0x20
+#define DS1338_CTRL_POR 0xb3
/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
@@ -49,6 +50,14 @@ static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(2000 + from_bcd(resp[6]), == , 1900 + tm_ptr->tm_year);
}
+/* The control register comes up in its documented power-on state. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, DS1338_CTRL_POR);
+}
+
/* Writable control bits round-trip; the reserved ones read back zero. */
static void test_control_register(void *obj, void *data,
QGuestAllocator *alloc)
@@ -110,6 +119,7 @@ static void ds1338_register_nodes(void)
qos_node_consumes("ds1338", "i2c-bus", &opts);
qos_add_test("tx-rx", "ds1338", send_and_receive, NULL);
+ qos_add_test("reset-defaults", "ds1338", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 19/50] tests/qtest: ds1338: cover the clock-halt bit
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The clock-halt bit had no coverage, nor did its effect on the
oscillator-stop flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index fad0ca53d4..1b73dca98d 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -32,6 +32,7 @@
#define DS1338_CTRL_OSF 0x20
#define DS1338_CTRL_POR 0xb3
+#define DS1338_SEC_CH 0x80
/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
@@ -85,6 +86,42 @@ static void test_osf_write_protect(void *obj, void *data,
g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
}
+/* Halting the clock freezes the counters; clearing the halt resumes them. */
+static void test_clock_halt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t frozen[7], again[7];
+ unsigned idx;
+
+ /* Clear OSF first, so that halting the clock is what sets it again. */
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+
+ i2c_set8(i2cdev, DS1338_SECONDS, DS1338_SEC_CH | 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_SECONDS), ==,
+ DS1338_SEC_CH | 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+
+ i2c_read_block(i2cdev, DS1338_SECONDS, frozen, sizeof(frozen));
+ g_usleep(1200 * 1000);
+ i2c_read_block(i2cdev, DS1338_SECONDS, again, sizeof(again));
+ g_assert_cmpmem(frozen, sizeof(frozen), again, sizeof(again));
+
+ i2c_set8(i2cdev, DS1338_SECONDS, 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_SECONDS) & DS1338_SEC_CH, ==, 0);
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1338_SECONDS) != 0x30) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmpuint(from_bcd(i2c_get8(i2cdev, DS1338_SECONDS)), >, 30);
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+}
+
/* The user RAM returns what the guest wrote to it. */
static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -122,6 +159,7 @@ static void ds1338_register_nodes(void)
qos_add_test("reset-defaults", "ds1338", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
+ qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 19/50] tests/qtest: ds1338: cover the clock-halt bit
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The clock-halt bit had no coverage, nor did its effect on the
oscillator-stop flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index fad0ca53d4..1b73dca98d 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -32,6 +32,7 @@
#define DS1338_CTRL_OSF 0x20
#define DS1338_CTRL_POR 0xb3
+#define DS1338_SEC_CH 0x80
/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
@@ -85,6 +86,42 @@ static void test_osf_write_protect(void *obj, void *data,
g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
}
+/* Halting the clock freezes the counters; clearing the halt resumes them. */
+static void test_clock_halt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t frozen[7], again[7];
+ unsigned idx;
+
+ /* Clear OSF first, so that halting the clock is what sets it again. */
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+
+ i2c_set8(i2cdev, DS1338_SECONDS, DS1338_SEC_CH | 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_SECONDS), ==,
+ DS1338_SEC_CH | 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+
+ i2c_read_block(i2cdev, DS1338_SECONDS, frozen, sizeof(frozen));
+ g_usleep(1200 * 1000);
+ i2c_read_block(i2cdev, DS1338_SECONDS, again, sizeof(again));
+ g_assert_cmpmem(frozen, sizeof(frozen), again, sizeof(again));
+
+ i2c_set8(i2cdev, DS1338_SECONDS, 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_SECONDS) & DS1338_SEC_CH, ==, 0);
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1338_SECONDS) != 0x30) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmpuint(from_bcd(i2c_get8(i2cdev, DS1338_SECONDS)), >, 30);
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+}
+
/* The user RAM returns what the guest wrote to it. */
static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -122,6 +159,7 @@ static void ds1338_register_nodes(void)
qos_add_test("reset-defaults", "ds1338", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
+ qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 19/50] tests/qtest: ds1338: cover the clock-halt bit
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The clock-halt bit had no coverage, nor did its effect on the
oscillator-stop flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index fad0ca53d4..1b73dca98d 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -32,6 +32,7 @@
#define DS1338_CTRL_OSF 0x20
#define DS1338_CTRL_POR 0xb3
+#define DS1338_SEC_CH 0x80
/* The clock and calendar come up on the host time. */
static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
@@ -85,6 +86,42 @@ static void test_osf_write_protect(void *obj, void *data,
g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
}
+/* Halting the clock freezes the counters; clearing the halt resumes them. */
+static void test_clock_halt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t frozen[7], again[7];
+ unsigned idx;
+
+ /* Clear OSF first, so that halting the clock is what sets it again. */
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF, ==, 0);
+
+ i2c_set8(i2cdev, DS1338_SECONDS, DS1338_SEC_CH | 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_SECONDS), ==,
+ DS1338_SEC_CH | 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+
+ i2c_read_block(i2cdev, DS1338_SECONDS, frozen, sizeof(frozen));
+ g_usleep(1200 * 1000);
+ i2c_read_block(i2cdev, DS1338_SECONDS, again, sizeof(again));
+ g_assert_cmpmem(frozen, sizeof(frozen), again, sizeof(again));
+
+ i2c_set8(i2cdev, DS1338_SECONDS, 0x30);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_SECONDS) & DS1338_SEC_CH, ==, 0);
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1338_SECONDS) != 0x30) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmpuint(from_bcd(i2c_get8(i2cdev, DS1338_SECONDS)), >, 30);
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL) & DS1338_CTRL_OSF,
+ ==, DS1338_CTRL_OSF);
+}
+
/* The user RAM returns what the guest wrote to it. */
static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -122,6 +159,7 @@ static void ds1338_register_nodes(void)
qos_add_test("reset-defaults", "ds1338", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
+ qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
}
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 20/50] tests/qtest: ds1338: cover persist-on-reset
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The property had no coverage in either of its two settings.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 1b73dca98d..1b9c02d931 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -20,12 +20,15 @@
#include "qemu/osdep.h"
#include "qemu/bcd.h"
#include "libqtest.h"
+#include "libqtest-single.h"
#include "libqos/i2c.h"
+#include "qobject/qdict.h"
#define DS1338_ADDR 0x68
/* DS1338 register map */
#define DS1338_SECONDS 0x00
+#define DS1338_YEAR 0x06
#define DS1338_CONTROL 0x07
#define DS1338_NVRAM 0x08
#define DS1338_NUM_REGS 0x40
@@ -136,6 +139,50 @@ static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get8(i2cdev, 0x3f), ==, 0xc3);
}
+/* A reset restores the power-on state, unless persist-on-reset is set. */
+static void check_reset(QI2CDevice *i2cdev, bool persists)
+{
+ uint8_t year = i2c_get8(i2cdev, DS1338_YEAR);
+ /* A year the clock is not already showing, so the check discriminates. */
+ uint8_t marker = year == 0x42 ? 0x77 : 0x42;
+ QDict *rsp;
+
+ i2c_set8(i2cdev, DS1338_NVRAM, 0xa5);
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ i2c_set8(i2cdev, DS1338_YEAR, marker);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, marker);
+
+ rsp = qmp("{ 'execute': 'system_reset' }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ qmp_eventwait("RESET");
+
+ if (persists) {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, marker);
+ } else {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, DS1338_CTRL_POR);
+ /* Back on host time, which is where the year came from. */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, year);
+ }
+}
+
+/* Without persist-on-reset, a reset takes the part back to power-on. */
+static void test_reset_clears(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, false);
+}
+
+/* With persist-on-reset, the registers and the clock survive a reset. */
+static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, true);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -162,6 +209,13 @@ static void ds1338_register_nodes(void)
qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
+ qos_add_test("reset-clears", "ds1338", test_reset_clears, NULL);
+
+ opts.extra_device_opts = "address=0x68,persist-on-reset=on";
+ qos_node_create_driver_named("ds1338-persist", "ds1338", i2c_device_create);
+ qos_node_consumes("ds1338-persist", "i2c-bus", &opts);
+
+ qos_add_test("reset-persists", "ds1338-persist", test_reset_persists, NULL);
}
libqos_init(ds1338_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 20/50] tests/qtest: ds1338: cover persist-on-reset
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The property had no coverage in either of its two settings.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 1b73dca98d..1b9c02d931 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -20,12 +20,15 @@
#include "qemu/osdep.h"
#include "qemu/bcd.h"
#include "libqtest.h"
+#include "libqtest-single.h"
#include "libqos/i2c.h"
+#include "qobject/qdict.h"
#define DS1338_ADDR 0x68
/* DS1338 register map */
#define DS1338_SECONDS 0x00
+#define DS1338_YEAR 0x06
#define DS1338_CONTROL 0x07
#define DS1338_NVRAM 0x08
#define DS1338_NUM_REGS 0x40
@@ -136,6 +139,50 @@ static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get8(i2cdev, 0x3f), ==, 0xc3);
}
+/* A reset restores the power-on state, unless persist-on-reset is set. */
+static void check_reset(QI2CDevice *i2cdev, bool persists)
+{
+ uint8_t year = i2c_get8(i2cdev, DS1338_YEAR);
+ /* A year the clock is not already showing, so the check discriminates. */
+ uint8_t marker = year == 0x42 ? 0x77 : 0x42;
+ QDict *rsp;
+
+ i2c_set8(i2cdev, DS1338_NVRAM, 0xa5);
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ i2c_set8(i2cdev, DS1338_YEAR, marker);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, marker);
+
+ rsp = qmp("{ 'execute': 'system_reset' }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ qmp_eventwait("RESET");
+
+ if (persists) {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, marker);
+ } else {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, DS1338_CTRL_POR);
+ /* Back on host time, which is where the year came from. */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, year);
+ }
+}
+
+/* Without persist-on-reset, a reset takes the part back to power-on. */
+static void test_reset_clears(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, false);
+}
+
+/* With persist-on-reset, the registers and the clock survive a reset. */
+static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, true);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -162,6 +209,13 @@ static void ds1338_register_nodes(void)
qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
+ qos_add_test("reset-clears", "ds1338", test_reset_clears, NULL);
+
+ opts.extra_device_opts = "address=0x68,persist-on-reset=on";
+ qos_node_create_driver_named("ds1338-persist", "ds1338", i2c_device_create);
+ qos_node_consumes("ds1338-persist", "i2c-bus", &opts);
+
+ qos_add_test("reset-persists", "ds1338-persist", test_reset_persists, NULL);
}
libqos_init(ds1338_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 20/50] tests/qtest: ds1338: cover persist-on-reset
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The property had no coverage in either of its two settings.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 1b73dca98d..1b9c02d931 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -20,12 +20,15 @@
#include "qemu/osdep.h"
#include "qemu/bcd.h"
#include "libqtest.h"
+#include "libqtest-single.h"
#include "libqos/i2c.h"
+#include "qobject/qdict.h"
#define DS1338_ADDR 0x68
/* DS1338 register map */
#define DS1338_SECONDS 0x00
+#define DS1338_YEAR 0x06
#define DS1338_CONTROL 0x07
#define DS1338_NVRAM 0x08
#define DS1338_NUM_REGS 0x40
@@ -136,6 +139,50 @@ static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get8(i2cdev, 0x3f), ==, 0xc3);
}
+/* A reset restores the power-on state, unless persist-on-reset is set. */
+static void check_reset(QI2CDevice *i2cdev, bool persists)
+{
+ uint8_t year = i2c_get8(i2cdev, DS1338_YEAR);
+ /* A year the clock is not already showing, so the check discriminates. */
+ uint8_t marker = year == 0x42 ? 0x77 : 0x42;
+ QDict *rsp;
+
+ i2c_set8(i2cdev, DS1338_NVRAM, 0xa5);
+ i2c_set8(i2cdev, DS1338_CONTROL, 0x00);
+ i2c_set8(i2cdev, DS1338_YEAR, marker);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, marker);
+
+ rsp = qmp("{ 'execute': 'system_reset' }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ qmp_eventwait("RESET");
+
+ if (persists) {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, marker);
+ } else {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_NVRAM), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_CONTROL), ==, DS1338_CTRL_POR);
+ /* Back on host time, which is where the year came from. */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1338_YEAR), ==, year);
+ }
+}
+
+/* Without persist-on-reset, a reset takes the part back to power-on. */
+static void test_reset_clears(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, false);
+}
+
+/* With persist-on-reset, the registers and the clock survive a reset. */
+static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, true);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -162,6 +209,13 @@ static void ds1338_register_nodes(void)
qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
+ qos_add_test("reset-clears", "ds1338", test_reset_clears, NULL);
+
+ opts.extra_device_opts = "address=0x68,persist-on-reset=on";
+ qos_node_create_driver_named("ds1338-persist", "ds1338", i2c_device_create);
+ qos_node_consumes("ds1338-persist", "i2c-bus", &opts);
+
+ qos_add_test("reset-persists", "ds1338-persist", test_reset_persists, NULL);
}
libqos_init(ds1338_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 21/50] tests/qtest: ds1338: cover the reserved bits of the time registers
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing pinned which bits of a time register survive a write.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 1b9c02d931..9e2e257307 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -125,6 +125,26 @@ static void test_clock_halt(void *obj, void *data, QGuestAllocator *alloc)
==, DS1338_CTRL_OSF);
}
+/* Reserved time-register bits read back zero even with the clock halted. */
+static void test_stopped_reserved_bits(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t all_ones[7] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ uint8_t resp[7];
+
+ i2c_write_block(i2cdev, DS1338_SECONDS, all_ones, sizeof(all_ones));
+
+ i2c_read_block(i2cdev, DS1338_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0xff); /* the halt bit is writable here */
+ g_assert_cmphex(resp[1], ==, 0x7f);
+ g_assert_cmphex(resp[2], ==, 0x7f);
+ g_assert_cmphex(resp[3], ==, 0x07);
+ g_assert_cmphex(resp[4], ==, 0x3f);
+ g_assert_cmphex(resp[5], ==, 0x1f); /* no century bit on this part */
+ g_assert_cmphex(resp[6], ==, 0xff);
+}
+
/* The user RAM returns what the guest wrote to it. */
static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -207,6 +227,8 @@ static void ds1338_register_nodes(void)
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
+ qos_add_test("stopped-reserved-bits", "ds1338",
+ test_stopped_reserved_bits, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
qos_add_test("reset-clears", "ds1338", test_reset_clears, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 21/50] tests/qtest: ds1338: cover the reserved bits of the time registers
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing pinned which bits of a time register survive a write.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 1b9c02d931..9e2e257307 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -125,6 +125,26 @@ static void test_clock_halt(void *obj, void *data, QGuestAllocator *alloc)
==, DS1338_CTRL_OSF);
}
+/* Reserved time-register bits read back zero even with the clock halted. */
+static void test_stopped_reserved_bits(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t all_ones[7] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ uint8_t resp[7];
+
+ i2c_write_block(i2cdev, DS1338_SECONDS, all_ones, sizeof(all_ones));
+
+ i2c_read_block(i2cdev, DS1338_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0xff); /* the halt bit is writable here */
+ g_assert_cmphex(resp[1], ==, 0x7f);
+ g_assert_cmphex(resp[2], ==, 0x7f);
+ g_assert_cmphex(resp[3], ==, 0x07);
+ g_assert_cmphex(resp[4], ==, 0x3f);
+ g_assert_cmphex(resp[5], ==, 0x1f); /* no century bit on this part */
+ g_assert_cmphex(resp[6], ==, 0xff);
+}
+
/* The user RAM returns what the guest wrote to it. */
static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -207,6 +227,8 @@ static void ds1338_register_nodes(void)
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
+ qos_add_test("stopped-reserved-bits", "ds1338",
+ test_stopped_reserved_bits, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
qos_add_test("reset-clears", "ds1338", test_reset_clears, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 21/50] tests/qtest: ds1338: cover the reserved bits of the time registers
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing pinned which bits of a time register survive a write.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1338-test.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
index 1b9c02d931..9e2e257307 100644
--- a/tests/qtest/ds1338-test.c
+++ b/tests/qtest/ds1338-test.c
@@ -125,6 +125,26 @@ static void test_clock_halt(void *obj, void *data, QGuestAllocator *alloc)
==, DS1338_CTRL_OSF);
}
+/* Reserved time-register bits read back zero even with the clock halted. */
+static void test_stopped_reserved_bits(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t all_ones[7] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ uint8_t resp[7];
+
+ i2c_write_block(i2cdev, DS1338_SECONDS, all_ones, sizeof(all_ones));
+
+ i2c_read_block(i2cdev, DS1338_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0xff); /* the halt bit is writable here */
+ g_assert_cmphex(resp[1], ==, 0x7f);
+ g_assert_cmphex(resp[2], ==, 0x7f);
+ g_assert_cmphex(resp[3], ==, 0x07);
+ g_assert_cmphex(resp[4], ==, 0x3f);
+ g_assert_cmphex(resp[5], ==, 0x1f); /* no century bit on this part */
+ g_assert_cmphex(resp[6], ==, 0xff);
+}
+
/* The user RAM returns what the guest wrote to it. */
static void test_nvram(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -207,6 +227,8 @@ static void ds1338_register_nodes(void)
qos_add_test("control-register", "ds1338", test_control_register, NULL);
qos_add_test("osf-write-protect", "ds1338", test_osf_write_protect, NULL);
qos_add_test("clock-halt", "ds1338", test_clock_halt, NULL);
+ qos_add_test("stopped-reserved-bits", "ds1338",
+ test_stopped_reserved_bits, NULL);
qos_add_test("nvram", "ds1338", test_nvram, NULL);
qos_add_test("address-wrap", "ds1338", test_address_wrap, NULL);
qos_add_test("reset-clears", "ds1338", test_reset_clears, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 22/50] hw/rtc: ds1338: add DS1339 variant
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Dallas/Maxim DS1339 register map is only partially compatible with
the DS1338: it shares the clock/calendar time registers but differs
elsewhere. It has no general-purpose NVRAM, moves the control register,
and relocates the oscillator-stop flag.
Add a 'ds1339' variant, modelling the EOSC oscillator stop, the
OSF/A2F/A1F status register and the century bit. The alarm,
square-wave and trickle-charger functions are not modelled.
A migrated register pointer is now range-checked; it used to index the
register array unvalidated.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 180 ++++++++++++++++++++++++++++++++++++++++++++----
include/hw/rtc/ds1338.h | 3 +-
2 files changed, 168 insertions(+), 15 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 21deb94689..63a049292e 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -1,5 +1,5 @@
/*
- * MAXIM DS1338 I2C RTC+NVRAM
+ * MAXIM DS1338/DS1339 I2C RTC+NVRAM
*
* Copyright (c) 2009 CodeSourcery.
* Written by Paul Brook
@@ -10,6 +10,13 @@
* GNU GPL, version 2 or (at your option) any later version.
*
* Limitations:
+ * - The DS1339 alarm registers, the alarm-enable control bits, the A1F/A2F
+ * status flags and the trickle charger are stored but never evaluated: no
+ * alarm matching is performed, no flag is ever set by hardware and no
+ * charging happens.
+ * - The INT#/SQW output pin is not exposed: neither alarm interrupts nor
+ * square-wave generation are produced. No board wires this pin and the guest
+ * does not use the RTC wakealarm here.
* - Writing the seconds register does not reset the internal countdown chain,
* so the sub-second phase of the clock is not modelled.
* - The user registers are refreshed on a START and on a read that wraps the
@@ -47,6 +54,23 @@
/* POR: OUT, OSF, SQWE and both rate-select bits come up set. */
#define DS1338_CTRL_RESET 0xb3
+/* Sentinel register address meaning "this variant has no such register". */
+#define DS1338_NO_REG 0xff
+
+/* DS1339 register map: 0x00..0x10 only, no general-purpose NVRAM */
+#define DS1339_NUM_REGS 0x11 /* 0x00..0x10, then wraps */
+#define DS1339_CONTROL 0x0e
+#define DS1339_CTRL_MASK 0xbf /* bit 6 is reserved, reads back zero */
+#define DS1339_CTRL_EOSC 0x80 /* EOSC: 1 stops the oscillator */
+#define DS1339_CTRL_RESET 0x18 /* POR: RS2=RS1=1 (32 kHz), rest zero */
+#define DS1339_MONTH_CENTURY 0x80 /* Century is bit 7 of the month reg */
+#define DS1339_STATUS 0x0f
+#define DS1339_STATUS_OSF 0x80 /* OSF is bit 7 of the status reg */
+/* Status bits that exist (OSF | A2F | A1F); bits 6..2 are reserved, read 0. */
+#define DS1339_STATUS_MASK 0x83
+/* All three status flags are hardware-set and clear-only (write 0). */
+#define DS1339_STATUS_W0 0x83
+
OBJECT_DECLARE_TYPE(DS1338State, DS1338Class, DS1338)
struct DS1338State {
@@ -58,6 +82,7 @@ struct DS1338State {
int32_t ptr;
bool addr_byte;
bool osc_stopped; /* oscillator halted: time is frozen */
+ uint8_t prev_century; /* century index (tm_year/100) at last capture */
bool persist_on_reset; /* keep the registers across a reset */
};
@@ -69,24 +94,70 @@ struct DS1338Class {
uint8_t ctrl_mask; /* writable/read-back bits of the control reg */
uint8_t osf_addr; /* register holding the OSF flag */
uint8_t osf_mask; /* OSF bit within osf_addr */
+ uint8_t status_addr; /* status register address */
+ uint8_t status_mask; /* bits that exist; reserved bits read 0 */
+ uint8_t status_w0_mask; /* flags that can only be cleared */
uint8_t ctrl_reset; /* control-register power-on value */
+ uint8_t status_reset; /* status-register power-on value */
+ uint8_t eosc_mask; /* oscillator-disable bit in the control reg */
+ uint8_t century_mask; /* Century bit in the month register */
uint8_t ch_mask; /* Clock-halt bit in the seconds reg */
};
+static int ds1338_post_load(void *opaque, int version_id)
+{
+ DS1338State *s = opaque;
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ /* Both accessors index nvram[] with ptr before incrementing it. */
+ if (s->ptr < 0 || s->ptr >= k->num_regs) {
+ return -EINVAL;
+ }
+
+ /*
+ * prev_century did not exist before version 3, so an older incoming stream
+ * leaves it zero.
+ */
+ if (version_id < 3) {
+ struct tm now;
+
+ qemu_get_timedate(&now, s->offset);
+ s->prev_century = now.tm_year / 100;
+ }
+ return 0;
+}
+
+static const VMStateField vmstate_ds1338_fields[] = {
+ VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
+ VMSTATE_INT64(offset, DS1338State),
+ VMSTATE_UINT8_V(wday_offset, DS1338State, 2),
+ VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
+ VMSTATE_INT32(ptr, DS1338State),
+ VMSTATE_BOOL(addr_byte, DS1338State),
+ VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
+ VMSTATE_UINT8_V(prev_century, DS1338State, 3),
+ VMSTATE_END_OF_LIST()
+};
+
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
.version_id = 3,
.minimum_version_id = 1,
- .fields = (const VMStateField[]) {
- VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
- VMSTATE_INT64(offset, DS1338State),
- VMSTATE_UINT8_V(wday_offset, DS1338State, 2),
- VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
- VMSTATE_INT32(ptr, DS1338State),
- VMSTATE_BOOL(addr_byte, DS1338State),
- VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
- VMSTATE_END_OF_LIST()
- }
+ .post_load = ds1338_post_load,
+ .fields = vmstate_ds1338_fields,
+};
+
+/*
+ * The DS1339 reuses the DS1338 migration field list, so it migrates the full
+ * NVRAM_SIZE-byte nvram[] array even though it only implements registers
+ * 0x00..0x10.
+ */
+static const VMStateDescription vmstate_ds1339 = {
+ .name = "ds1339",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .post_load = ds1338_post_load,
+ .fields = vmstate_ds1338_fields,
};
/* Reconstruct a struct tm from the BCD time registers in nvram. */
@@ -109,7 +180,7 @@ static void ds1338_time_from_regs(DS1338State *s, struct tm *now)
}
now->tm_mday = from_bcd(s->nvram[4] & 0x3f);
now->tm_mon = from_bcd(s->nvram[5] & 0x1f) - 1;
- now->tm_year = from_bcd(s->nvram[6]) + 100;
+ now->tm_year = s->prev_century * 100 + from_bcd(s->nvram[6]);
}
static void ds1338_resync_from_regs(DS1338State *s)
@@ -129,13 +200,22 @@ static void ds1338_capture_current_time(DS1338State *s)
/* Capture the current time into the secondary registers
* which will be actually read by the data transfer operation.
*/
+ DS1338Class *k = DS1338_GET_CLASS(s);
struct tm now;
+ uint8_t century;
if (s->osc_stopped) {
return;
}
qemu_get_timedate(&now, s->offset);
+
+ century = now.tm_year / 100;
+ if (k->century_mask && ((century ^ s->prev_century) & 1)) {
+ s->nvram[5] ^= k->century_mask;
+ }
+ s->prev_century = century;
+
s->nvram[0] = to_bcd(now.tm_sec);
s->nvram[1] = to_bcd(now.tm_min);
if (s->nvram[2] & HOURS_12) {
@@ -153,7 +233,7 @@ static void ds1338_capture_current_time(DS1338State *s)
}
s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
s->nvram[4] = to_bcd(now.tm_mday);
- s->nvram[5] = to_bcd(now.tm_mon + 1);
+ s->nvram[5] = to_bcd(now.tm_mon + 1) | (s->nvram[5] & k->century_mask);
s->nvram[6] = to_bcd(now.tm_year % 100);
}
@@ -204,6 +284,16 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
return res;
}
+/* OSF is raised by hardware whenever the oscillator stops. */
+static void ds1338_raise_osf(DS1338State *s)
+{
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ if (k->osf_addr != DS1338_NO_REG) {
+ s->nvram[k->osf_addr] |= k->osf_mask;
+ }
+}
+
static int ds1338_send(I2CSlave *i2c, uint8_t data)
{
DS1338State *s = DS1338(i2c);
@@ -234,12 +324,14 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
if (k->ch_mask) {
halt = data & k->ch_mask;
}
+ } else if (s->ptr == 5) {
+ mask |= k->century_mask;
}
if (halt && !s->osc_stopped) {
/* CH set: freeze the counters before the register is stored. */
ds1338_capture_current_time(s);
- s->nvram[k->osf_addr] |= k->osf_mask;
+ ds1338_raise_osf(s);
}
s->nvram[s->ptr] = data & mask;
s->osc_stopped = halt;
@@ -250,6 +342,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
data &= k->ctrl_mask;
+ if (k->eosc_mask) {
+ bool now_stop = data & k->eosc_mask;
+ bool was_stop = s->nvram[k->ctrl_addr] & k->eosc_mask;
+ if (now_stop && !was_stop) {
+ ds1338_capture_current_time(s);
+ s->osc_stopped = true;
+ ds1338_raise_osf(s);
+ } else if (!now_stop && was_stop) {
+ /* EOSC cleared: resume counting from the frozen time. */
+ ds1338_resync_from_regs(s);
+ s->osc_stopped = false;
+ }
+ }
}
if (s->ptr == k->osf_addr) {
/*
@@ -259,6 +364,13 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
data = (data & ~k->osf_mask) |
(data & s->nvram[s->ptr] & k->osf_mask);
}
+ if (s->ptr == k->status_addr) {
+ /* Reserved bits read zero; flags are clear-only, OSF included. */
+ uint8_t cur = s->nvram[s->ptr];
+ data &= k->status_mask;
+ data = (data & ~k->status_w0_mask) |
+ (data & cur & k->status_w0_mask);
+ }
s->nvram[s->ptr] = data;
}
ds1338_inc_regptr(s);
@@ -268,13 +380,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
static void ds1338_power_on(DS1338State *s)
{
DS1338Class *k = DS1338_GET_CLASS(s);
+ struct tm now;
/* The clock is running and synchronized with the host */
s->offset = 0;
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
s->nvram[k->ctrl_addr] = k->ctrl_reset;
+ if (k->status_addr != DS1338_NO_REG) {
+ s->nvram[k->status_addr] = k->status_reset;
+ }
s->osc_stopped = false;
+ qemu_get_timedate(&now, 0);
+ s->prev_century = now.tm_year / 100;
}
static void ds1338_realize(DeviceState *dev, Error **errp)
@@ -318,10 +436,39 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
dsc->ctrl_mask = DS1338_CTRL_MASK;
dsc->osf_addr = DS1338_CONTROL;
dsc->osf_mask = CTRL_OSF;
+ dsc->status_addr = DS1338_NO_REG; /* the DS1338 has no status register */
+ dsc->status_mask = 0;
+ dsc->status_w0_mask = 0;
dsc->ctrl_reset = DS1338_CTRL_RESET;
+ dsc->status_reset = 0;
+ dsc->eosc_mask = 0;
+ dsc->century_mask = 0;
dsc->ch_mask = SECONDS_CH;
}
+static void ds1339_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ DS1338Class *dsc = DS1338_CLASS(klass);
+
+ dc->desc = "DS1339 I2C serial real-time clock";
+ dc->vmsd = &vmstate_ds1339;
+
+ dsc->num_regs = DS1339_NUM_REGS;
+ dsc->ctrl_addr = DS1339_CONTROL;
+ dsc->ctrl_mask = DS1339_CTRL_MASK;
+ dsc->osf_addr = DS1339_STATUS; /* OSF lives in the status register */
+ dsc->osf_mask = DS1339_STATUS_OSF;
+ dsc->status_addr = DS1339_STATUS;
+ dsc->status_mask = DS1339_STATUS_MASK;
+ dsc->status_w0_mask = DS1339_STATUS_W0;
+ dsc->ctrl_reset = DS1339_CTRL_RESET;
+ dsc->status_reset = DS1339_STATUS_OSF; /* OSF set at power-on */
+ dsc->eosc_mask = DS1339_CTRL_EOSC;
+ dsc->century_mask = DS1339_MONTH_CENTURY;
+ dsc->ch_mask = 0; /* bit 7 of the seconds register reads back zero */
+}
+
static const TypeInfo ds1338_types[] = {
{
.name = TYPE_DS1338,
@@ -330,6 +477,11 @@ static const TypeInfo ds1338_types[] = {
.class_size = sizeof(DS1338Class),
.class_init = ds1338_class_init,
},
+ {
+ .name = TYPE_DS1339,
+ .parent = TYPE_DS1338,
+ .class_init = ds1339_class_init,
+ },
};
DEFINE_TYPES(ds1338_types)
diff --git a/include/hw/rtc/ds1338.h b/include/hw/rtc/ds1338.h
index f4cfe6a8e7..fff04c225d 100644
--- a/include/hw/rtc/ds1338.h
+++ b/include/hw/rtc/ds1338.h
@@ -1,5 +1,5 @@
/*
- * MAXIM DS1338 I2C RTC+NVRAM
+ * MAXIM DS1338/DS1339 I2C RTC+NVRAM
*
* Copyright (c) 2009 CodeSourcery.
* Written by Paul Brook
@@ -16,5 +16,6 @@
#define HW_RTC_DS1338_H
#define TYPE_DS1338 "ds1338"
+#define TYPE_DS1339 "ds1339"
#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 22/50] hw/rtc: ds1338: add DS1339 variant
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Dallas/Maxim DS1339 register map is only partially compatible with
the DS1338: it shares the clock/calendar time registers but differs
elsewhere. It has no general-purpose NVRAM, moves the control register,
and relocates the oscillator-stop flag.
Add a 'ds1339' variant, modelling the EOSC oscillator stop, the
OSF/A2F/A1F status register and the century bit. The alarm,
square-wave and trickle-charger functions are not modelled.
A migrated register pointer is now range-checked; it used to index the
register array unvalidated.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 180 ++++++++++++++++++++++++++++++++++++++++++++----
include/hw/rtc/ds1338.h | 3 +-
2 files changed, 168 insertions(+), 15 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 21deb94689..63a049292e 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -1,5 +1,5 @@
/*
- * MAXIM DS1338 I2C RTC+NVRAM
+ * MAXIM DS1338/DS1339 I2C RTC+NVRAM
*
* Copyright (c) 2009 CodeSourcery.
* Written by Paul Brook
@@ -10,6 +10,13 @@
* GNU GPL, version 2 or (at your option) any later version.
*
* Limitations:
+ * - The DS1339 alarm registers, the alarm-enable control bits, the A1F/A2F
+ * status flags and the trickle charger are stored but never evaluated: no
+ * alarm matching is performed, no flag is ever set by hardware and no
+ * charging happens.
+ * - The INT#/SQW output pin is not exposed: neither alarm interrupts nor
+ * square-wave generation are produced. No board wires this pin and the guest
+ * does not use the RTC wakealarm here.
* - Writing the seconds register does not reset the internal countdown chain,
* so the sub-second phase of the clock is not modelled.
* - The user registers are refreshed on a START and on a read that wraps the
@@ -47,6 +54,23 @@
/* POR: OUT, OSF, SQWE and both rate-select bits come up set. */
#define DS1338_CTRL_RESET 0xb3
+/* Sentinel register address meaning "this variant has no such register". */
+#define DS1338_NO_REG 0xff
+
+/* DS1339 register map: 0x00..0x10 only, no general-purpose NVRAM */
+#define DS1339_NUM_REGS 0x11 /* 0x00..0x10, then wraps */
+#define DS1339_CONTROL 0x0e
+#define DS1339_CTRL_MASK 0xbf /* bit 6 is reserved, reads back zero */
+#define DS1339_CTRL_EOSC 0x80 /* EOSC: 1 stops the oscillator */
+#define DS1339_CTRL_RESET 0x18 /* POR: RS2=RS1=1 (32 kHz), rest zero */
+#define DS1339_MONTH_CENTURY 0x80 /* Century is bit 7 of the month reg */
+#define DS1339_STATUS 0x0f
+#define DS1339_STATUS_OSF 0x80 /* OSF is bit 7 of the status reg */
+/* Status bits that exist (OSF | A2F | A1F); bits 6..2 are reserved, read 0. */
+#define DS1339_STATUS_MASK 0x83
+/* All three status flags are hardware-set and clear-only (write 0). */
+#define DS1339_STATUS_W0 0x83
+
OBJECT_DECLARE_TYPE(DS1338State, DS1338Class, DS1338)
struct DS1338State {
@@ -58,6 +82,7 @@ struct DS1338State {
int32_t ptr;
bool addr_byte;
bool osc_stopped; /* oscillator halted: time is frozen */
+ uint8_t prev_century; /* century index (tm_year/100) at last capture */
bool persist_on_reset; /* keep the registers across a reset */
};
@@ -69,24 +94,70 @@ struct DS1338Class {
uint8_t ctrl_mask; /* writable/read-back bits of the control reg */
uint8_t osf_addr; /* register holding the OSF flag */
uint8_t osf_mask; /* OSF bit within osf_addr */
+ uint8_t status_addr; /* status register address */
+ uint8_t status_mask; /* bits that exist; reserved bits read 0 */
+ uint8_t status_w0_mask; /* flags that can only be cleared */
uint8_t ctrl_reset; /* control-register power-on value */
+ uint8_t status_reset; /* status-register power-on value */
+ uint8_t eosc_mask; /* oscillator-disable bit in the control reg */
+ uint8_t century_mask; /* Century bit in the month register */
uint8_t ch_mask; /* Clock-halt bit in the seconds reg */
};
+static int ds1338_post_load(void *opaque, int version_id)
+{
+ DS1338State *s = opaque;
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ /* Both accessors index nvram[] with ptr before incrementing it. */
+ if (s->ptr < 0 || s->ptr >= k->num_regs) {
+ return -EINVAL;
+ }
+
+ /*
+ * prev_century did not exist before version 3, so an older incoming stream
+ * leaves it zero.
+ */
+ if (version_id < 3) {
+ struct tm now;
+
+ qemu_get_timedate(&now, s->offset);
+ s->prev_century = now.tm_year / 100;
+ }
+ return 0;
+}
+
+static const VMStateField vmstate_ds1338_fields[] = {
+ VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
+ VMSTATE_INT64(offset, DS1338State),
+ VMSTATE_UINT8_V(wday_offset, DS1338State, 2),
+ VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
+ VMSTATE_INT32(ptr, DS1338State),
+ VMSTATE_BOOL(addr_byte, DS1338State),
+ VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
+ VMSTATE_UINT8_V(prev_century, DS1338State, 3),
+ VMSTATE_END_OF_LIST()
+};
+
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
.version_id = 3,
.minimum_version_id = 1,
- .fields = (const VMStateField[]) {
- VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
- VMSTATE_INT64(offset, DS1338State),
- VMSTATE_UINT8_V(wday_offset, DS1338State, 2),
- VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
- VMSTATE_INT32(ptr, DS1338State),
- VMSTATE_BOOL(addr_byte, DS1338State),
- VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
- VMSTATE_END_OF_LIST()
- }
+ .post_load = ds1338_post_load,
+ .fields = vmstate_ds1338_fields,
+};
+
+/*
+ * The DS1339 reuses the DS1338 migration field list, so it migrates the full
+ * NVRAM_SIZE-byte nvram[] array even though it only implements registers
+ * 0x00..0x10.
+ */
+static const VMStateDescription vmstate_ds1339 = {
+ .name = "ds1339",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .post_load = ds1338_post_load,
+ .fields = vmstate_ds1338_fields,
};
/* Reconstruct a struct tm from the BCD time registers in nvram. */
@@ -109,7 +180,7 @@ static void ds1338_time_from_regs(DS1338State *s, struct tm *now)
}
now->tm_mday = from_bcd(s->nvram[4] & 0x3f);
now->tm_mon = from_bcd(s->nvram[5] & 0x1f) - 1;
- now->tm_year = from_bcd(s->nvram[6]) + 100;
+ now->tm_year = s->prev_century * 100 + from_bcd(s->nvram[6]);
}
static void ds1338_resync_from_regs(DS1338State *s)
@@ -129,13 +200,22 @@ static void ds1338_capture_current_time(DS1338State *s)
/* Capture the current time into the secondary registers
* which will be actually read by the data transfer operation.
*/
+ DS1338Class *k = DS1338_GET_CLASS(s);
struct tm now;
+ uint8_t century;
if (s->osc_stopped) {
return;
}
qemu_get_timedate(&now, s->offset);
+
+ century = now.tm_year / 100;
+ if (k->century_mask && ((century ^ s->prev_century) & 1)) {
+ s->nvram[5] ^= k->century_mask;
+ }
+ s->prev_century = century;
+
s->nvram[0] = to_bcd(now.tm_sec);
s->nvram[1] = to_bcd(now.tm_min);
if (s->nvram[2] & HOURS_12) {
@@ -153,7 +233,7 @@ static void ds1338_capture_current_time(DS1338State *s)
}
s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
s->nvram[4] = to_bcd(now.tm_mday);
- s->nvram[5] = to_bcd(now.tm_mon + 1);
+ s->nvram[5] = to_bcd(now.tm_mon + 1) | (s->nvram[5] & k->century_mask);
s->nvram[6] = to_bcd(now.tm_year % 100);
}
@@ -204,6 +284,16 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
return res;
}
+/* OSF is raised by hardware whenever the oscillator stops. */
+static void ds1338_raise_osf(DS1338State *s)
+{
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ if (k->osf_addr != DS1338_NO_REG) {
+ s->nvram[k->osf_addr] |= k->osf_mask;
+ }
+}
+
static int ds1338_send(I2CSlave *i2c, uint8_t data)
{
DS1338State *s = DS1338(i2c);
@@ -234,12 +324,14 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
if (k->ch_mask) {
halt = data & k->ch_mask;
}
+ } else if (s->ptr == 5) {
+ mask |= k->century_mask;
}
if (halt && !s->osc_stopped) {
/* CH set: freeze the counters before the register is stored. */
ds1338_capture_current_time(s);
- s->nvram[k->osf_addr] |= k->osf_mask;
+ ds1338_raise_osf(s);
}
s->nvram[s->ptr] = data & mask;
s->osc_stopped = halt;
@@ -250,6 +342,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
data &= k->ctrl_mask;
+ if (k->eosc_mask) {
+ bool now_stop = data & k->eosc_mask;
+ bool was_stop = s->nvram[k->ctrl_addr] & k->eosc_mask;
+ if (now_stop && !was_stop) {
+ ds1338_capture_current_time(s);
+ s->osc_stopped = true;
+ ds1338_raise_osf(s);
+ } else if (!now_stop && was_stop) {
+ /* EOSC cleared: resume counting from the frozen time. */
+ ds1338_resync_from_regs(s);
+ s->osc_stopped = false;
+ }
+ }
}
if (s->ptr == k->osf_addr) {
/*
@@ -259,6 +364,13 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
data = (data & ~k->osf_mask) |
(data & s->nvram[s->ptr] & k->osf_mask);
}
+ if (s->ptr == k->status_addr) {
+ /* Reserved bits read zero; flags are clear-only, OSF included. */
+ uint8_t cur = s->nvram[s->ptr];
+ data &= k->status_mask;
+ data = (data & ~k->status_w0_mask) |
+ (data & cur & k->status_w0_mask);
+ }
s->nvram[s->ptr] = data;
}
ds1338_inc_regptr(s);
@@ -268,13 +380,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
static void ds1338_power_on(DS1338State *s)
{
DS1338Class *k = DS1338_GET_CLASS(s);
+ struct tm now;
/* The clock is running and synchronized with the host */
s->offset = 0;
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
s->nvram[k->ctrl_addr] = k->ctrl_reset;
+ if (k->status_addr != DS1338_NO_REG) {
+ s->nvram[k->status_addr] = k->status_reset;
+ }
s->osc_stopped = false;
+ qemu_get_timedate(&now, 0);
+ s->prev_century = now.tm_year / 100;
}
static void ds1338_realize(DeviceState *dev, Error **errp)
@@ -318,10 +436,39 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
dsc->ctrl_mask = DS1338_CTRL_MASK;
dsc->osf_addr = DS1338_CONTROL;
dsc->osf_mask = CTRL_OSF;
+ dsc->status_addr = DS1338_NO_REG; /* the DS1338 has no status register */
+ dsc->status_mask = 0;
+ dsc->status_w0_mask = 0;
dsc->ctrl_reset = DS1338_CTRL_RESET;
+ dsc->status_reset = 0;
+ dsc->eosc_mask = 0;
+ dsc->century_mask = 0;
dsc->ch_mask = SECONDS_CH;
}
+static void ds1339_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ DS1338Class *dsc = DS1338_CLASS(klass);
+
+ dc->desc = "DS1339 I2C serial real-time clock";
+ dc->vmsd = &vmstate_ds1339;
+
+ dsc->num_regs = DS1339_NUM_REGS;
+ dsc->ctrl_addr = DS1339_CONTROL;
+ dsc->ctrl_mask = DS1339_CTRL_MASK;
+ dsc->osf_addr = DS1339_STATUS; /* OSF lives in the status register */
+ dsc->osf_mask = DS1339_STATUS_OSF;
+ dsc->status_addr = DS1339_STATUS;
+ dsc->status_mask = DS1339_STATUS_MASK;
+ dsc->status_w0_mask = DS1339_STATUS_W0;
+ dsc->ctrl_reset = DS1339_CTRL_RESET;
+ dsc->status_reset = DS1339_STATUS_OSF; /* OSF set at power-on */
+ dsc->eosc_mask = DS1339_CTRL_EOSC;
+ dsc->century_mask = DS1339_MONTH_CENTURY;
+ dsc->ch_mask = 0; /* bit 7 of the seconds register reads back zero */
+}
+
static const TypeInfo ds1338_types[] = {
{
.name = TYPE_DS1338,
@@ -330,6 +477,11 @@ static const TypeInfo ds1338_types[] = {
.class_size = sizeof(DS1338Class),
.class_init = ds1338_class_init,
},
+ {
+ .name = TYPE_DS1339,
+ .parent = TYPE_DS1338,
+ .class_init = ds1339_class_init,
+ },
};
DEFINE_TYPES(ds1338_types)
diff --git a/include/hw/rtc/ds1338.h b/include/hw/rtc/ds1338.h
index f4cfe6a8e7..fff04c225d 100644
--- a/include/hw/rtc/ds1338.h
+++ b/include/hw/rtc/ds1338.h
@@ -1,5 +1,5 @@
/*
- * MAXIM DS1338 I2C RTC+NVRAM
+ * MAXIM DS1338/DS1339 I2C RTC+NVRAM
*
* Copyright (c) 2009 CodeSourcery.
* Written by Paul Brook
@@ -16,5 +16,6 @@
#define HW_RTC_DS1338_H
#define TYPE_DS1338 "ds1338"
+#define TYPE_DS1339 "ds1339"
#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 22/50] hw/rtc: ds1338: add DS1339 variant
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Dallas/Maxim DS1339 register map is only partially compatible with
the DS1338: it shares the clock/calendar time registers but differs
elsewhere. It has no general-purpose NVRAM, moves the control register,
and relocates the oscillator-stop flag.
Add a 'ds1339' variant, modelling the EOSC oscillator stop, the
OSF/A2F/A1F status register and the century bit. The alarm,
square-wave and trickle-charger functions are not modelled.
A migrated register pointer is now range-checked; it used to index the
register array unvalidated.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/rtc/ds1338.c | 180 ++++++++++++++++++++++++++++++++++++++++++++----
include/hw/rtc/ds1338.h | 3 +-
2 files changed, 168 insertions(+), 15 deletions(-)
diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
index 21deb94689..63a049292e 100644
--- a/hw/rtc/ds1338.c
+++ b/hw/rtc/ds1338.c
@@ -1,5 +1,5 @@
/*
- * MAXIM DS1338 I2C RTC+NVRAM
+ * MAXIM DS1338/DS1339 I2C RTC+NVRAM
*
* Copyright (c) 2009 CodeSourcery.
* Written by Paul Brook
@@ -10,6 +10,13 @@
* GNU GPL, version 2 or (at your option) any later version.
*
* Limitations:
+ * - The DS1339 alarm registers, the alarm-enable control bits, the A1F/A2F
+ * status flags and the trickle charger are stored but never evaluated: no
+ * alarm matching is performed, no flag is ever set by hardware and no
+ * charging happens.
+ * - The INT#/SQW output pin is not exposed: neither alarm interrupts nor
+ * square-wave generation are produced. No board wires this pin and the guest
+ * does not use the RTC wakealarm here.
* - Writing the seconds register does not reset the internal countdown chain,
* so the sub-second phase of the clock is not modelled.
* - The user registers are refreshed on a START and on a read that wraps the
@@ -47,6 +54,23 @@
/* POR: OUT, OSF, SQWE and both rate-select bits come up set. */
#define DS1338_CTRL_RESET 0xb3
+/* Sentinel register address meaning "this variant has no such register". */
+#define DS1338_NO_REG 0xff
+
+/* DS1339 register map: 0x00..0x10 only, no general-purpose NVRAM */
+#define DS1339_NUM_REGS 0x11 /* 0x00..0x10, then wraps */
+#define DS1339_CONTROL 0x0e
+#define DS1339_CTRL_MASK 0xbf /* bit 6 is reserved, reads back zero */
+#define DS1339_CTRL_EOSC 0x80 /* EOSC: 1 stops the oscillator */
+#define DS1339_CTRL_RESET 0x18 /* POR: RS2=RS1=1 (32 kHz), rest zero */
+#define DS1339_MONTH_CENTURY 0x80 /* Century is bit 7 of the month reg */
+#define DS1339_STATUS 0x0f
+#define DS1339_STATUS_OSF 0x80 /* OSF is bit 7 of the status reg */
+/* Status bits that exist (OSF | A2F | A1F); bits 6..2 are reserved, read 0. */
+#define DS1339_STATUS_MASK 0x83
+/* All three status flags are hardware-set and clear-only (write 0). */
+#define DS1339_STATUS_W0 0x83
+
OBJECT_DECLARE_TYPE(DS1338State, DS1338Class, DS1338)
struct DS1338State {
@@ -58,6 +82,7 @@ struct DS1338State {
int32_t ptr;
bool addr_byte;
bool osc_stopped; /* oscillator halted: time is frozen */
+ uint8_t prev_century; /* century index (tm_year/100) at last capture */
bool persist_on_reset; /* keep the registers across a reset */
};
@@ -69,24 +94,70 @@ struct DS1338Class {
uint8_t ctrl_mask; /* writable/read-back bits of the control reg */
uint8_t osf_addr; /* register holding the OSF flag */
uint8_t osf_mask; /* OSF bit within osf_addr */
+ uint8_t status_addr; /* status register address */
+ uint8_t status_mask; /* bits that exist; reserved bits read 0 */
+ uint8_t status_w0_mask; /* flags that can only be cleared */
uint8_t ctrl_reset; /* control-register power-on value */
+ uint8_t status_reset; /* status-register power-on value */
+ uint8_t eosc_mask; /* oscillator-disable bit in the control reg */
+ uint8_t century_mask; /* Century bit in the month register */
uint8_t ch_mask; /* Clock-halt bit in the seconds reg */
};
+static int ds1338_post_load(void *opaque, int version_id)
+{
+ DS1338State *s = opaque;
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ /* Both accessors index nvram[] with ptr before incrementing it. */
+ if (s->ptr < 0 || s->ptr >= k->num_regs) {
+ return -EINVAL;
+ }
+
+ /*
+ * prev_century did not exist before version 3, so an older incoming stream
+ * leaves it zero.
+ */
+ if (version_id < 3) {
+ struct tm now;
+
+ qemu_get_timedate(&now, s->offset);
+ s->prev_century = now.tm_year / 100;
+ }
+ return 0;
+}
+
+static const VMStateField vmstate_ds1338_fields[] = {
+ VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
+ VMSTATE_INT64(offset, DS1338State),
+ VMSTATE_UINT8_V(wday_offset, DS1338State, 2),
+ VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
+ VMSTATE_INT32(ptr, DS1338State),
+ VMSTATE_BOOL(addr_byte, DS1338State),
+ VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
+ VMSTATE_UINT8_V(prev_century, DS1338State, 3),
+ VMSTATE_END_OF_LIST()
+};
+
static const VMStateDescription vmstate_ds1338 = {
.name = "ds1338",
.version_id = 3,
.minimum_version_id = 1,
- .fields = (const VMStateField[]) {
- VMSTATE_I2C_SLAVE(parent_obj, DS1338State),
- VMSTATE_INT64(offset, DS1338State),
- VMSTATE_UINT8_V(wday_offset, DS1338State, 2),
- VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE),
- VMSTATE_INT32(ptr, DS1338State),
- VMSTATE_BOOL(addr_byte, DS1338State),
- VMSTATE_BOOL_V(osc_stopped, DS1338State, 3),
- VMSTATE_END_OF_LIST()
- }
+ .post_load = ds1338_post_load,
+ .fields = vmstate_ds1338_fields,
+};
+
+/*
+ * The DS1339 reuses the DS1338 migration field list, so it migrates the full
+ * NVRAM_SIZE-byte nvram[] array even though it only implements registers
+ * 0x00..0x10.
+ */
+static const VMStateDescription vmstate_ds1339 = {
+ .name = "ds1339",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .post_load = ds1338_post_load,
+ .fields = vmstate_ds1338_fields,
};
/* Reconstruct a struct tm from the BCD time registers in nvram. */
@@ -109,7 +180,7 @@ static void ds1338_time_from_regs(DS1338State *s, struct tm *now)
}
now->tm_mday = from_bcd(s->nvram[4] & 0x3f);
now->tm_mon = from_bcd(s->nvram[5] & 0x1f) - 1;
- now->tm_year = from_bcd(s->nvram[6]) + 100;
+ now->tm_year = s->prev_century * 100 + from_bcd(s->nvram[6]);
}
static void ds1338_resync_from_regs(DS1338State *s)
@@ -129,13 +200,22 @@ static void ds1338_capture_current_time(DS1338State *s)
/* Capture the current time into the secondary registers
* which will be actually read by the data transfer operation.
*/
+ DS1338Class *k = DS1338_GET_CLASS(s);
struct tm now;
+ uint8_t century;
if (s->osc_stopped) {
return;
}
qemu_get_timedate(&now, s->offset);
+
+ century = now.tm_year / 100;
+ if (k->century_mask && ((century ^ s->prev_century) & 1)) {
+ s->nvram[5] ^= k->century_mask;
+ }
+ s->prev_century = century;
+
s->nvram[0] = to_bcd(now.tm_sec);
s->nvram[1] = to_bcd(now.tm_min);
if (s->nvram[2] & HOURS_12) {
@@ -153,7 +233,7 @@ static void ds1338_capture_current_time(DS1338State *s)
}
s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
s->nvram[4] = to_bcd(now.tm_mday);
- s->nvram[5] = to_bcd(now.tm_mon + 1);
+ s->nvram[5] = to_bcd(now.tm_mon + 1) | (s->nvram[5] & k->century_mask);
s->nvram[6] = to_bcd(now.tm_year % 100);
}
@@ -204,6 +284,16 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
return res;
}
+/* OSF is raised by hardware whenever the oscillator stops. */
+static void ds1338_raise_osf(DS1338State *s)
+{
+ DS1338Class *k = DS1338_GET_CLASS(s);
+
+ if (k->osf_addr != DS1338_NO_REG) {
+ s->nvram[k->osf_addr] |= k->osf_mask;
+ }
+}
+
static int ds1338_send(I2CSlave *i2c, uint8_t data)
{
DS1338State *s = DS1338(i2c);
@@ -234,12 +324,14 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
if (k->ch_mask) {
halt = data & k->ch_mask;
}
+ } else if (s->ptr == 5) {
+ mask |= k->century_mask;
}
if (halt && !s->osc_stopped) {
/* CH set: freeze the counters before the register is stored. */
ds1338_capture_current_time(s);
- s->nvram[k->osf_addr] |= k->osf_mask;
+ ds1338_raise_osf(s);
}
s->nvram[s->ptr] = data & mask;
s->osc_stopped = halt;
@@ -250,6 +342,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
if (s->ptr == k->ctrl_addr) {
/* Control register: reserved bits read back as zero. */
data &= k->ctrl_mask;
+ if (k->eosc_mask) {
+ bool now_stop = data & k->eosc_mask;
+ bool was_stop = s->nvram[k->ctrl_addr] & k->eosc_mask;
+ if (now_stop && !was_stop) {
+ ds1338_capture_current_time(s);
+ s->osc_stopped = true;
+ ds1338_raise_osf(s);
+ } else if (!now_stop && was_stop) {
+ /* EOSC cleared: resume counting from the frozen time. */
+ ds1338_resync_from_regs(s);
+ s->osc_stopped = false;
+ }
+ }
}
if (s->ptr == k->osf_addr) {
/*
@@ -259,6 +364,13 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
data = (data & ~k->osf_mask) |
(data & s->nvram[s->ptr] & k->osf_mask);
}
+ if (s->ptr == k->status_addr) {
+ /* Reserved bits read zero; flags are clear-only, OSF included. */
+ uint8_t cur = s->nvram[s->ptr];
+ data &= k->status_mask;
+ data = (data & ~k->status_w0_mask) |
+ (data & cur & k->status_w0_mask);
+ }
s->nvram[s->ptr] = data;
}
ds1338_inc_regptr(s);
@@ -268,13 +380,19 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
static void ds1338_power_on(DS1338State *s)
{
DS1338Class *k = DS1338_GET_CLASS(s);
+ struct tm now;
/* The clock is running and synchronized with the host */
s->offset = 0;
s->wday_offset = 0;
memset(s->nvram, 0, NVRAM_SIZE);
s->nvram[k->ctrl_addr] = k->ctrl_reset;
+ if (k->status_addr != DS1338_NO_REG) {
+ s->nvram[k->status_addr] = k->status_reset;
+ }
s->osc_stopped = false;
+ qemu_get_timedate(&now, 0);
+ s->prev_century = now.tm_year / 100;
}
static void ds1338_realize(DeviceState *dev, Error **errp)
@@ -318,10 +436,39 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
dsc->ctrl_mask = DS1338_CTRL_MASK;
dsc->osf_addr = DS1338_CONTROL;
dsc->osf_mask = CTRL_OSF;
+ dsc->status_addr = DS1338_NO_REG; /* the DS1338 has no status register */
+ dsc->status_mask = 0;
+ dsc->status_w0_mask = 0;
dsc->ctrl_reset = DS1338_CTRL_RESET;
+ dsc->status_reset = 0;
+ dsc->eosc_mask = 0;
+ dsc->century_mask = 0;
dsc->ch_mask = SECONDS_CH;
}
+static void ds1339_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ DS1338Class *dsc = DS1338_CLASS(klass);
+
+ dc->desc = "DS1339 I2C serial real-time clock";
+ dc->vmsd = &vmstate_ds1339;
+
+ dsc->num_regs = DS1339_NUM_REGS;
+ dsc->ctrl_addr = DS1339_CONTROL;
+ dsc->ctrl_mask = DS1339_CTRL_MASK;
+ dsc->osf_addr = DS1339_STATUS; /* OSF lives in the status register */
+ dsc->osf_mask = DS1339_STATUS_OSF;
+ dsc->status_addr = DS1339_STATUS;
+ dsc->status_mask = DS1339_STATUS_MASK;
+ dsc->status_w0_mask = DS1339_STATUS_W0;
+ dsc->ctrl_reset = DS1339_CTRL_RESET;
+ dsc->status_reset = DS1339_STATUS_OSF; /* OSF set at power-on */
+ dsc->eosc_mask = DS1339_CTRL_EOSC;
+ dsc->century_mask = DS1339_MONTH_CENTURY;
+ dsc->ch_mask = 0; /* bit 7 of the seconds register reads back zero */
+}
+
static const TypeInfo ds1338_types[] = {
{
.name = TYPE_DS1338,
@@ -330,6 +477,11 @@ static const TypeInfo ds1338_types[] = {
.class_size = sizeof(DS1338Class),
.class_init = ds1338_class_init,
},
+ {
+ .name = TYPE_DS1339,
+ .parent = TYPE_DS1338,
+ .class_init = ds1339_class_init,
+ },
};
DEFINE_TYPES(ds1338_types)
diff --git a/include/hw/rtc/ds1338.h b/include/hw/rtc/ds1338.h
index f4cfe6a8e7..fff04c225d 100644
--- a/include/hw/rtc/ds1338.h
+++ b/include/hw/rtc/ds1338.h
@@ -1,5 +1,5 @@
/*
- * MAXIM DS1338 I2C RTC+NVRAM
+ * MAXIM DS1338/DS1339 I2C RTC+NVRAM
*
* Copyright (c) 2009 CodeSourcery.
* Written by Paul Brook
@@ -16,5 +16,6 @@
#define HW_RTC_DS1338_H
#define TYPE_DS1338 "ds1338"
+#define TYPE_DS1339 "ds1339"
#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 23/50] tests/qtest: ds1339: add RTC test
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The variant had no test of its own. Cover its register map, the
write-protection of the oscillator-stop flag, and the wrap of the
register pointer at the end of the map.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 167 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
new file mode 100644
index 0000000000..08fc16acda
--- /dev/null
+++ b/tests/qtest/ds1339-test.c
@@ -0,0 +1,166 @@
+/*
+ * QTest testcase for the DS1339 RTC
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bcd.h"
+#include "libqos/i2c.h"
+
+#define DS1339_ADDR 0x68
+
+/* DS1339 register map */
+#define DS1339_SECONDS 0x00
+#define DS1339_MONTH 0x05
+#define DS1339_YEAR 0x06
+#define DS1339_ALARM1 0x07
+#define DS1339_ALARM2 0x0b
+#define DS1339_CONTROL 0x0e
+#define DS1339_STATUS 0x0f
+#define DS1339_TRICKLE 0x10
+#define DS1339_NUM_REGS 0x11
+
+#define DS1339_STATUS_OSF 0x80
+
+/* The clock and calendar come up on the host time. */
+static void test_time(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t resp[7];
+ time_t now = time(NULL);
+ struct tm *tm_ptr = gmtime(&now);
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+
+ g_assert_cmpuint(from_bcd(resp[4]), ==, tm_ptr->tm_mday);
+ g_assert_cmpuint(from_bcd(resp[5]), ==, 1 + tm_ptr->tm_mon);
+ g_assert_cmpuint(2000 + from_bcd(resp[6]), ==, 1900 + tm_ptr->tm_year);
+}
+
+/* Writable control bits round-trip; the reserved one reads back zero. */
+static void test_control_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x1c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x1c);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0xff);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0xbf);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x00);
+}
+
+/* The oscillator-stop flag can only be cleared by a write, never set. */
+static void test_osf_write_protect(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+ i2c_set8(i2cdev, DS1339_STATUS, DS1339_STATUS_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+ i2c_set8(i2cdev, DS1339_STATUS, DS1339_STATUS_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+}
+
+/* The register pointer wraps at the end of the map. */
+static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x2a);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL + DS1339_NUM_REGS),
+ ==, 0x2a);
+}
+
+/* A block transfer off the end of the map carries on from the start. */
+static void test_block_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* The alarm 2 block, then on into the time registers once it wraps. */
+ const uint8_t out[11] = {
+ 0x0b, 0x0c, 0x0d, /* alarm 2 */
+ 0x18, /* control */
+ 0x00, /* status */
+ 0xa5, /* trickle charger */
+ 0x30, 0x20, 0x10, 0x05, 0x12, /* 10:20:30, day 5, the 12th */
+ };
+ uint8_t resp[11];
+
+ /* A month and year the wrapping write never reaches. */
+ i2c_set8(i2cdev, DS1339_MONTH, 0x07);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x31);
+
+ i2c_write_block(i2cdev, DS1339_ALARM2, out, sizeof(out));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, 7);
+ g_assert_cmphex(resp[2], ==, 0x10);
+ g_assert_cmphex(resp[3], ==, 0x05);
+ g_assert_cmphex(resp[4], ==, 0x12);
+ g_assert_cmphex(resp[5] & 0x1f, ==, 0x07);
+ g_assert_cmphex(resp[6], ==, 0x31);
+
+ i2c_read_block(i2cdev, DS1339_ALARM2, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0x0b);
+ g_assert_cmphex(resp[1], ==, 0x0c);
+ g_assert_cmphex(resp[2], ==, 0x0d);
+ g_assert_cmphex(resp[3], ==, 0x18);
+ g_assert_cmphex(resp[4], ==, 0x00);
+ g_assert_cmphex(resp[5], ==, 0xa5);
+ g_assert_cmphex(resp[8], ==, 0x10);
+ g_assert_cmphex(resp[9], ==, 0x05);
+ g_assert_cmphex(resp[10], ==, 0x12);
+}
+
+/* The alarm and trickle registers are stored but never evaluated. */
+static void test_alarm_registers(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ static const uint8_t regs[] = {
+ DS1339_ALARM1, 0x08, 0x09, 0x0a, /* alarm 1 */
+ DS1339_ALARM2, 0x0c, 0x0d, /* alarm 2 */
+ DS1339_TRICKLE,
+ };
+ static const uint8_t vals[] = { 0xff, 0x00, 0x5a, 0xa5 };
+ unsigned reg, val;
+
+ for (reg = 0; reg < ARRAY_SIZE(regs); reg++) {
+ for (val = 0; val < ARRAY_SIZE(vals); val++) {
+ i2c_set8(i2cdev, regs[reg], vals[val]);
+ g_assert_cmphex(i2c_get8(i2cdev, regs[reg]), ==, vals[val]);
+ }
+ }
+}
+
+static void ds1339_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "address=0x68"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { DS1339_ADDR });
+
+ qos_node_create_driver("ds1339", i2c_device_create);
+ qos_node_consumes("ds1339", "i2c-bus", &opts);
+
+ qos_add_test("time", "ds1339", test_time, NULL);
+ qos_add_test("control-register", "ds1339", test_control_register, NULL);
+ qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
+ qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
+ qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
+ NULL);
+}
+
+libqos_init(ds1339_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e05d8a07b9..e23eb9bbca 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -326,6 +326,7 @@ qos_test_ss.add(
'adm1272-test.c',
'adm1266-test.c',
'ds1338-test.c',
+ 'ds1339-test.c',
'e1000-test.c',
'eepro100-test.c',
'es1370-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 23/50] tests/qtest: ds1339: add RTC test
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The variant had no test of its own. Cover its register map, the
write-protection of the oscillator-stop flag, and the wrap of the
register pointer at the end of the map.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 167 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
new file mode 100644
index 0000000000..08fc16acda
--- /dev/null
+++ b/tests/qtest/ds1339-test.c
@@ -0,0 +1,166 @@
+/*
+ * QTest testcase for the DS1339 RTC
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bcd.h"
+#include "libqos/i2c.h"
+
+#define DS1339_ADDR 0x68
+
+/* DS1339 register map */
+#define DS1339_SECONDS 0x00
+#define DS1339_MONTH 0x05
+#define DS1339_YEAR 0x06
+#define DS1339_ALARM1 0x07
+#define DS1339_ALARM2 0x0b
+#define DS1339_CONTROL 0x0e
+#define DS1339_STATUS 0x0f
+#define DS1339_TRICKLE 0x10
+#define DS1339_NUM_REGS 0x11
+
+#define DS1339_STATUS_OSF 0x80
+
+/* The clock and calendar come up on the host time. */
+static void test_time(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t resp[7];
+ time_t now = time(NULL);
+ struct tm *tm_ptr = gmtime(&now);
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+
+ g_assert_cmpuint(from_bcd(resp[4]), ==, tm_ptr->tm_mday);
+ g_assert_cmpuint(from_bcd(resp[5]), ==, 1 + tm_ptr->tm_mon);
+ g_assert_cmpuint(2000 + from_bcd(resp[6]), ==, 1900 + tm_ptr->tm_year);
+}
+
+/* Writable control bits round-trip; the reserved one reads back zero. */
+static void test_control_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x1c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x1c);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0xff);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0xbf);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x00);
+}
+
+/* The oscillator-stop flag can only be cleared by a write, never set. */
+static void test_osf_write_protect(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+ i2c_set8(i2cdev, DS1339_STATUS, DS1339_STATUS_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+ i2c_set8(i2cdev, DS1339_STATUS, DS1339_STATUS_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+}
+
+/* The register pointer wraps at the end of the map. */
+static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x2a);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL + DS1339_NUM_REGS),
+ ==, 0x2a);
+}
+
+/* A block transfer off the end of the map carries on from the start. */
+static void test_block_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* The alarm 2 block, then on into the time registers once it wraps. */
+ const uint8_t out[11] = {
+ 0x0b, 0x0c, 0x0d, /* alarm 2 */
+ 0x18, /* control */
+ 0x00, /* status */
+ 0xa5, /* trickle charger */
+ 0x30, 0x20, 0x10, 0x05, 0x12, /* 10:20:30, day 5, the 12th */
+ };
+ uint8_t resp[11];
+
+ /* A month and year the wrapping write never reaches. */
+ i2c_set8(i2cdev, DS1339_MONTH, 0x07);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x31);
+
+ i2c_write_block(i2cdev, DS1339_ALARM2, out, sizeof(out));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, 7);
+ g_assert_cmphex(resp[2], ==, 0x10);
+ g_assert_cmphex(resp[3], ==, 0x05);
+ g_assert_cmphex(resp[4], ==, 0x12);
+ g_assert_cmphex(resp[5] & 0x1f, ==, 0x07);
+ g_assert_cmphex(resp[6], ==, 0x31);
+
+ i2c_read_block(i2cdev, DS1339_ALARM2, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0x0b);
+ g_assert_cmphex(resp[1], ==, 0x0c);
+ g_assert_cmphex(resp[2], ==, 0x0d);
+ g_assert_cmphex(resp[3], ==, 0x18);
+ g_assert_cmphex(resp[4], ==, 0x00);
+ g_assert_cmphex(resp[5], ==, 0xa5);
+ g_assert_cmphex(resp[8], ==, 0x10);
+ g_assert_cmphex(resp[9], ==, 0x05);
+ g_assert_cmphex(resp[10], ==, 0x12);
+}
+
+/* The alarm and trickle registers are stored but never evaluated. */
+static void test_alarm_registers(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ static const uint8_t regs[] = {
+ DS1339_ALARM1, 0x08, 0x09, 0x0a, /* alarm 1 */
+ DS1339_ALARM2, 0x0c, 0x0d, /* alarm 2 */
+ DS1339_TRICKLE,
+ };
+ static const uint8_t vals[] = { 0xff, 0x00, 0x5a, 0xa5 };
+ unsigned reg, val;
+
+ for (reg = 0; reg < ARRAY_SIZE(regs); reg++) {
+ for (val = 0; val < ARRAY_SIZE(vals); val++) {
+ i2c_set8(i2cdev, regs[reg], vals[val]);
+ g_assert_cmphex(i2c_get8(i2cdev, regs[reg]), ==, vals[val]);
+ }
+ }
+}
+
+static void ds1339_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "address=0x68"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { DS1339_ADDR });
+
+ qos_node_create_driver("ds1339", i2c_device_create);
+ qos_node_consumes("ds1339", "i2c-bus", &opts);
+
+ qos_add_test("time", "ds1339", test_time, NULL);
+ qos_add_test("control-register", "ds1339", test_control_register, NULL);
+ qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
+ qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
+ qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
+ NULL);
+}
+
+libqos_init(ds1339_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e05d8a07b9..e23eb9bbca 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -326,6 +326,7 @@ qos_test_ss.add(
'adm1272-test.c',
'adm1266-test.c',
'ds1338-test.c',
+ 'ds1339-test.c',
'e1000-test.c',
'eepro100-test.c',
'es1370-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 23/50] tests/qtest: ds1339: add RTC test
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The variant had no test of its own. Cover its register map, the
write-protection of the oscillator-stop flag, and the wrap of the
register pointer at the end of the map.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 167 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
new file mode 100644
index 0000000000..08fc16acda
--- /dev/null
+++ b/tests/qtest/ds1339-test.c
@@ -0,0 +1,166 @@
+/*
+ * QTest testcase for the DS1339 RTC
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bcd.h"
+#include "libqos/i2c.h"
+
+#define DS1339_ADDR 0x68
+
+/* DS1339 register map */
+#define DS1339_SECONDS 0x00
+#define DS1339_MONTH 0x05
+#define DS1339_YEAR 0x06
+#define DS1339_ALARM1 0x07
+#define DS1339_ALARM2 0x0b
+#define DS1339_CONTROL 0x0e
+#define DS1339_STATUS 0x0f
+#define DS1339_TRICKLE 0x10
+#define DS1339_NUM_REGS 0x11
+
+#define DS1339_STATUS_OSF 0x80
+
+/* The clock and calendar come up on the host time. */
+static void test_time(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t resp[7];
+ time_t now = time(NULL);
+ struct tm *tm_ptr = gmtime(&now);
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+
+ g_assert_cmpuint(from_bcd(resp[4]), ==, tm_ptr->tm_mday);
+ g_assert_cmpuint(from_bcd(resp[5]), ==, 1 + tm_ptr->tm_mon);
+ g_assert_cmpuint(2000 + from_bcd(resp[6]), ==, 1900 + tm_ptr->tm_year);
+}
+
+/* Writable control bits round-trip; the reserved one reads back zero. */
+static void test_control_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x1c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x1c);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0xff);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0xbf);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x00);
+}
+
+/* The oscillator-stop flag can only be cleared by a write, never set. */
+static void test_osf_write_protect(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+ i2c_set8(i2cdev, DS1339_STATUS, DS1339_STATUS_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+ i2c_set8(i2cdev, DS1339_STATUS, DS1339_STATUS_OSF);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+}
+
+/* The register pointer wraps at the end of the map. */
+static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x2a);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL + DS1339_NUM_REGS),
+ ==, 0x2a);
+}
+
+/* A block transfer off the end of the map carries on from the start. */
+static void test_block_wrap(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* The alarm 2 block, then on into the time registers once it wraps. */
+ const uint8_t out[11] = {
+ 0x0b, 0x0c, 0x0d, /* alarm 2 */
+ 0x18, /* control */
+ 0x00, /* status */
+ 0xa5, /* trickle charger */
+ 0x30, 0x20, 0x10, 0x05, 0x12, /* 10:20:30, day 5, the 12th */
+ };
+ uint8_t resp[11];
+
+ /* A month and year the wrapping write never reaches. */
+ i2c_set8(i2cdev, DS1339_MONTH, 0x07);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x31);
+
+ i2c_write_block(i2cdev, DS1339_ALARM2, out, sizeof(out));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, 7);
+ g_assert_cmphex(resp[2], ==, 0x10);
+ g_assert_cmphex(resp[3], ==, 0x05);
+ g_assert_cmphex(resp[4], ==, 0x12);
+ g_assert_cmphex(resp[5] & 0x1f, ==, 0x07);
+ g_assert_cmphex(resp[6], ==, 0x31);
+
+ i2c_read_block(i2cdev, DS1339_ALARM2, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0x0b);
+ g_assert_cmphex(resp[1], ==, 0x0c);
+ g_assert_cmphex(resp[2], ==, 0x0d);
+ g_assert_cmphex(resp[3], ==, 0x18);
+ g_assert_cmphex(resp[4], ==, 0x00);
+ g_assert_cmphex(resp[5], ==, 0xa5);
+ g_assert_cmphex(resp[8], ==, 0x10);
+ g_assert_cmphex(resp[9], ==, 0x05);
+ g_assert_cmphex(resp[10], ==, 0x12);
+}
+
+/* The alarm and trickle registers are stored but never evaluated. */
+static void test_alarm_registers(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ static const uint8_t regs[] = {
+ DS1339_ALARM1, 0x08, 0x09, 0x0a, /* alarm 1 */
+ DS1339_ALARM2, 0x0c, 0x0d, /* alarm 2 */
+ DS1339_TRICKLE,
+ };
+ static const uint8_t vals[] = { 0xff, 0x00, 0x5a, 0xa5 };
+ unsigned reg, val;
+
+ for (reg = 0; reg < ARRAY_SIZE(regs); reg++) {
+ for (val = 0; val < ARRAY_SIZE(vals); val++) {
+ i2c_set8(i2cdev, regs[reg], vals[val]);
+ g_assert_cmphex(i2c_get8(i2cdev, regs[reg]), ==, vals[val]);
+ }
+ }
+}
+
+static void ds1339_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "address=0x68"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { DS1339_ADDR });
+
+ qos_node_create_driver("ds1339", i2c_device_create);
+ qos_node_consumes("ds1339", "i2c-bus", &opts);
+
+ qos_add_test("time", "ds1339", test_time, NULL);
+ qos_add_test("control-register", "ds1339", test_control_register, NULL);
+ qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
+ qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
+ qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
+ NULL);
+}
+
+libqos_init(ds1339_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e05d8a07b9..e23eb9bbca 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -326,6 +326,7 @@ qos_test_ss.add(
'adm1272-test.c',
'adm1266-test.c',
'ds1338-test.c',
+ 'ds1339-test.c',
'e1000-test.c',
'eepro100-test.c',
'es1370-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 24/50] tests/qtest: ds1339: cover power-on defaults and reserved-bit masking
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Neither the power-on defaults nor the reserved bits of the register
file were pinned.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 08fc16acda..e9dcdc36da 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -74,6 +74,53 @@ static void test_osf_write_protect(void *obj, void *data,
g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
}
+/* The control and status registers come up in their power-on state. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+}
+
+/* Neither the reserved status bits nor the alarm flags are guest-writable. */
+static void test_status_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x7c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x03);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0xff);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+}
+
+/* Reserved time-register bits read back zero even with the clock stopped. */
+static void test_stopped_reserved_bits(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t all_ones[7] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ uint8_t resp[7];
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, all_ones, sizeof(all_ones));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0x7f);
+ g_assert_cmphex(resp[1], ==, 0x7f);
+ g_assert_cmphex(resp[2], ==, 0x7f);
+ g_assert_cmphex(resp[3], ==, 0x07);
+ g_assert_cmphex(resp[4], ==, 0x3f);
+ g_assert_cmphex(resp[5], ==, 0x9f);
+ g_assert_cmphex(resp[6], ==, 0xff);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -155,8 +202,12 @@ static void ds1339_register_nodes(void)
qos_node_consumes("ds1339", "i2c-bus", &opts);
qos_add_test("time", "ds1339", test_time, NULL);
+ qos_add_test("reset-defaults", "ds1339", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1339", test_control_register, NULL);
+ qos_add_test("status-register", "ds1339", test_status_register, NULL);
qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("stopped-reserved-bits", "ds1339",
+ test_stopped_reserved_bits, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 24/50] tests/qtest: ds1339: cover power-on defaults and reserved-bit masking
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Neither the power-on defaults nor the reserved bits of the register
file were pinned.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 08fc16acda..e9dcdc36da 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -74,6 +74,53 @@ static void test_osf_write_protect(void *obj, void *data,
g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
}
+/* The control and status registers come up in their power-on state. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+}
+
+/* Neither the reserved status bits nor the alarm flags are guest-writable. */
+static void test_status_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x7c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x03);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0xff);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+}
+
+/* Reserved time-register bits read back zero even with the clock stopped. */
+static void test_stopped_reserved_bits(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t all_ones[7] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ uint8_t resp[7];
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, all_ones, sizeof(all_ones));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0x7f);
+ g_assert_cmphex(resp[1], ==, 0x7f);
+ g_assert_cmphex(resp[2], ==, 0x7f);
+ g_assert_cmphex(resp[3], ==, 0x07);
+ g_assert_cmphex(resp[4], ==, 0x3f);
+ g_assert_cmphex(resp[5], ==, 0x9f);
+ g_assert_cmphex(resp[6], ==, 0xff);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -155,8 +202,12 @@ static void ds1339_register_nodes(void)
qos_node_consumes("ds1339", "i2c-bus", &opts);
qos_add_test("time", "ds1339", test_time, NULL);
+ qos_add_test("reset-defaults", "ds1339", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1339", test_control_register, NULL);
+ qos_add_test("status-register", "ds1339", test_status_register, NULL);
qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("stopped-reserved-bits", "ds1339",
+ test_stopped_reserved_bits, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 24/50] tests/qtest: ds1339: cover power-on defaults and reserved-bit masking
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Neither the power-on defaults nor the reserved bits of the register
file were pinned.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 08fc16acda..e9dcdc36da 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -74,6 +74,53 @@ static void test_osf_write_protect(void *obj, void *data,
g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
}
+/* The control and status registers come up in their power-on state. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+}
+
+/* Neither the reserved status bits nor the alarm flags are guest-writable. */
+static void test_status_register(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x7c);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x03);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0xff);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+}
+
+/* Reserved time-register bits read back zero even with the clock stopped. */
+static void test_stopped_reserved_bits(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t all_ones[7] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ uint8_t resp[7];
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, all_ones, sizeof(all_ones));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[0], ==, 0x7f);
+ g_assert_cmphex(resp[1], ==, 0x7f);
+ g_assert_cmphex(resp[2], ==, 0x7f);
+ g_assert_cmphex(resp[3], ==, 0x07);
+ g_assert_cmphex(resp[4], ==, 0x3f);
+ g_assert_cmphex(resp[5], ==, 0x9f);
+ g_assert_cmphex(resp[6], ==, 0xff);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -155,8 +202,12 @@ static void ds1339_register_nodes(void)
qos_node_consumes("ds1339", "i2c-bus", &opts);
qos_add_test("time", "ds1339", test_time, NULL);
+ qos_add_test("reset-defaults", "ds1339", test_reset_defaults, NULL);
qos_add_test("control-register", "ds1339", test_control_register, NULL);
+ qos_add_test("status-register", "ds1339", test_status_register, NULL);
qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("stopped-reserved-bits", "ds1339",
+ test_stopped_reserved_bits, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 25/50] tests/qtest: ds1339: cover oscillator stop/restart and time programming
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Stopping and restarting the oscillator had no coverage, nor did the
time the clock resumes from.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index e9dcdc36da..ac19ebd71d 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -121,6 +121,54 @@ static void test_stopped_reserved_bits(void *obj, void *data,
g_assert_cmphex(resp[6], ==, 0xff);
}
+/* Setting EOSC stops the oscillator and sets OSF; OSF outlives the restart. */
+static void test_eosc(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+}
+
+/* A stopped clock resumes from the time it was programmed with. */
+static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 10:20:30 on the 12th of July 2031, the day counter reading 2. */
+ const uint8_t tod[7] = { 0x30, 0x20, 0x10, 0x02, 0x12, 0x07, 0x31 };
+ uint8_t resp[7];
+ unsigned idx;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpmem(resp, sizeof(resp), tod, sizeof(tod));
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ /* The seconds counter steps within a second of the restart. */
+ for (idx = 0; idx < 15 && i2c_get8(i2cdev, DS1339_SECONDS) == 0x30; idx++) {
+ g_usleep(100 * 1000);
+ }
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpuint(from_bcd(resp[0]), >, 30);
+ g_assert_cmphex(resp[2], ==, 0x10);
+ g_assert_cmphex(resp[4], ==, 0x12);
+ g_assert_cmphex(resp[5] & 0x1f, ==, 0x07);
+ g_assert_cmphex(resp[6], ==, 0x31);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -206,6 +254,8 @@ static void ds1339_register_nodes(void)
qos_add_test("control-register", "ds1339", test_control_register, NULL);
qos_add_test("status-register", "ds1339", test_status_register, NULL);
qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("eosc", "ds1339", test_eosc, NULL);
+ qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 25/50] tests/qtest: ds1339: cover oscillator stop/restart and time programming
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Stopping and restarting the oscillator had no coverage, nor did the
time the clock resumes from.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index e9dcdc36da..ac19ebd71d 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -121,6 +121,54 @@ static void test_stopped_reserved_bits(void *obj, void *data,
g_assert_cmphex(resp[6], ==, 0xff);
}
+/* Setting EOSC stops the oscillator and sets OSF; OSF outlives the restart. */
+static void test_eosc(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+}
+
+/* A stopped clock resumes from the time it was programmed with. */
+static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 10:20:30 on the 12th of July 2031, the day counter reading 2. */
+ const uint8_t tod[7] = { 0x30, 0x20, 0x10, 0x02, 0x12, 0x07, 0x31 };
+ uint8_t resp[7];
+ unsigned idx;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpmem(resp, sizeof(resp), tod, sizeof(tod));
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ /* The seconds counter steps within a second of the restart. */
+ for (idx = 0; idx < 15 && i2c_get8(i2cdev, DS1339_SECONDS) == 0x30; idx++) {
+ g_usleep(100 * 1000);
+ }
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpuint(from_bcd(resp[0]), >, 30);
+ g_assert_cmphex(resp[2], ==, 0x10);
+ g_assert_cmphex(resp[4], ==, 0x12);
+ g_assert_cmphex(resp[5] & 0x1f, ==, 0x07);
+ g_assert_cmphex(resp[6], ==, 0x31);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -206,6 +254,8 @@ static void ds1339_register_nodes(void)
qos_add_test("control-register", "ds1339", test_control_register, NULL);
qos_add_test("status-register", "ds1339", test_status_register, NULL);
qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("eosc", "ds1339", test_eosc, NULL);
+ qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 25/50] tests/qtest: ds1339: cover oscillator stop/restart and time programming
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Stopping and restarting the oscillator had no coverage, nor did the
time the clock resumes from.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index e9dcdc36da..ac19ebd71d 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -121,6 +121,54 @@ static void test_stopped_reserved_bits(void *obj, void *data,
g_assert_cmphex(resp[6], ==, 0xff);
}
+/* Setting EOSC stops the oscillator and sets OSF; OSF outlives the restart. */
+static void test_eosc(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF, ==, 0);
+}
+
+/* A stopped clock resumes from the time it was programmed with. */
+static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 10:20:30 on the 12th of July 2031, the day counter reading 2. */
+ const uint8_t tod[7] = { 0x30, 0x20, 0x10, 0x02, 0x12, 0x07, 0x31 };
+ uint8_t resp[7];
+ unsigned idx;
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpmem(resp, sizeof(resp), tod, sizeof(tod));
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ /* The seconds counter steps within a second of the restart. */
+ for (idx = 0; idx < 15 && i2c_get8(i2cdev, DS1339_SECONDS) == 0x30; idx++) {
+ g_usleep(100 * 1000);
+ }
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpuint(from_bcd(resp[0]), >, 30);
+ g_assert_cmphex(resp[2], ==, 0x10);
+ g_assert_cmphex(resp[4], ==, 0x12);
+ g_assert_cmphex(resp[5] & 0x1f, ==, 0x07);
+ g_assert_cmphex(resp[6], ==, 0x31);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -206,6 +254,8 @@ static void ds1339_register_nodes(void)
qos_add_test("control-register", "ds1339", test_control_register, NULL);
qos_add_test("status-register", "ds1339", test_status_register, NULL);
qos_add_test("osf-write-protect", "ds1339", test_osf_write_protect, NULL);
+ qos_add_test("eosc", "ds1339", test_eosc, NULL);
+ qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 26/50] tests/qtest: ds1339: cover the Century bit
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Century bit had no coverage: neither its guest read/write
behaviour, nor the rule that only an elapsed rollover toggles it.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index ac19ebd71d..8c144579e6 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -169,6 +169,62 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* The Century bit is guest-writable and survives an oscillator restart. */
+static void test_century_bit(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x86); /* Century | June */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06); /* June, century clear */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+}
+
+/* A guest write never toggles the Century bit. */
+static void test_century_write_no_toggle(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x86);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x99);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x99);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+}
+
+/* A clock-driven year rollover from 99 to 00 toggles the Century bit. */
+static void test_century_rollover(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x01, 0x31, 0x12, 0x99 };
+ unsigned idx;
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_YEAR) == 0x00) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -258,6 +314,10 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("century-bit", "ds1339", test_century_bit, NULL);
+ qos_add_test("century-write-no-toggle", "ds1339",
+ test_century_write_no_toggle, NULL);
+ qos_add_test("century-rollover", "ds1339", test_century_rollover, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 26/50] tests/qtest: ds1339: cover the Century bit
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Century bit had no coverage: neither its guest read/write
behaviour, nor the rule that only an elapsed rollover toggles it.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index ac19ebd71d..8c144579e6 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -169,6 +169,62 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* The Century bit is guest-writable and survives an oscillator restart. */
+static void test_century_bit(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x86); /* Century | June */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06); /* June, century clear */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+}
+
+/* A guest write never toggles the Century bit. */
+static void test_century_write_no_toggle(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x86);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x99);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x99);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+}
+
+/* A clock-driven year rollover from 99 to 00 toggles the Century bit. */
+static void test_century_rollover(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x01, 0x31, 0x12, 0x99 };
+ unsigned idx;
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_YEAR) == 0x00) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -258,6 +314,10 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("century-bit", "ds1339", test_century_bit, NULL);
+ qos_add_test("century-write-no-toggle", "ds1339",
+ test_century_write_no_toggle, NULL);
+ qos_add_test("century-rollover", "ds1339", test_century_rollover, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 26/50] tests/qtest: ds1339: cover the Century bit
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The Century bit had no coverage: neither its guest read/write
behaviour, nor the rule that only an elapsed rollover toggles it.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index ac19ebd71d..8c144579e6 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -169,6 +169,62 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* The Century bit is guest-writable and survives an oscillator restart. */
+static void test_century_bit(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x86); /* Century | June */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06); /* June, century clear */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+}
+
+/* A guest write never toggles the Century bit. */
+static void test_century_write_no_toggle(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x86);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x99);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x99);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x00);
+}
+
+/* A clock-driven year rollover from 99 to 00 toggles the Century bit. */
+static void test_century_rollover(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x01, 0x31, 0x12, 0x99 };
+ unsigned idx;
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_YEAR) == 0x00) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_MONTH) & 0x80, ==, 0x80);
+}
+
/* The register pointer wraps at the end of the map. */
static void test_address_wrap(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -258,6 +314,10 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("century-bit", "ds1339", test_century_bit, NULL);
+ qos_add_test("century-write-no-toggle", "ds1339",
+ test_century_write_no_toggle, NULL);
+ qos_add_test("century-rollover", "ds1339", test_century_rollover, NULL);
qos_add_test("address-wrap", "ds1339", test_address_wrap, NULL);
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 27/50] tests/qtest: ds1339: cover the end-of-month rollover
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The end-of-month rollover was untested, for any of the lengths the
calendar gives a month.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 8c144579e6..7eec163049 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -14,6 +14,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
+#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
#define DS1339_ALARM1 0x07
@@ -169,6 +170,61 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* Program 23:59:59 on the last day of a month and run the clock past it. */
+static void check_month_rollover(QI2CDevice *i2cdev,
+ uint8_t date, uint8_t month, uint8_t year,
+ uint8_t next_date, uint8_t next_month,
+ uint8_t next_year)
+{
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x01, date, month, year };
+ uint8_t resp[7];
+ unsigned idx;
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_DATE) != date) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[4], ==, next_date);
+ g_assert_cmphex(resp[5] & 0x1f, ==, next_month);
+ g_assert_cmphex(resp[6], ==, next_year);
+}
+
+/* A 31-day month rolls over to the first of the next. */
+static void test_month_31_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x31, 0x01, 0x23, 0x01, 0x02, 0x23);
+}
+
+/* A 30-day month rolls over to the first of the next. */
+static void test_month_30_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x30, 0x04, 0x23, 0x01, 0x05, 0x23);
+}
+
+/* Outside a leap year, February rolls over after the 28th. */
+static void test_month_28_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x28, 0x02, 0x23, 0x01, 0x03, 0x23);
+}
+
+/* In a leap year the 28th is not the last day of February. */
+static void test_month_29_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ check_month_rollover(i2cdev, 0x28, 0x02, 0x24, 0x29, 0x02, 0x24);
+ check_month_rollover(i2cdev, 0x29, 0x02, 0x24, 0x01, 0x03, 0x24);
+}
+
/* The Century bit is guest-writable and survives an oscillator restart. */
static void test_century_bit(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -314,6 +370,10 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
+ qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
+ qos_add_test("month-30-days", "ds1339", test_month_30_days, NULL);
+ qos_add_test("month-31-days", "ds1339", test_month_31_days, NULL);
qos_add_test("century-bit", "ds1339", test_century_bit, NULL);
qos_add_test("century-write-no-toggle", "ds1339",
test_century_write_no_toggle, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 27/50] tests/qtest: ds1339: cover the end-of-month rollover
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The end-of-month rollover was untested, for any of the lengths the
calendar gives a month.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 8c144579e6..7eec163049 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -14,6 +14,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
+#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
#define DS1339_ALARM1 0x07
@@ -169,6 +170,61 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* Program 23:59:59 on the last day of a month and run the clock past it. */
+static void check_month_rollover(QI2CDevice *i2cdev,
+ uint8_t date, uint8_t month, uint8_t year,
+ uint8_t next_date, uint8_t next_month,
+ uint8_t next_year)
+{
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x01, date, month, year };
+ uint8_t resp[7];
+ unsigned idx;
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_DATE) != date) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[4], ==, next_date);
+ g_assert_cmphex(resp[5] & 0x1f, ==, next_month);
+ g_assert_cmphex(resp[6], ==, next_year);
+}
+
+/* A 31-day month rolls over to the first of the next. */
+static void test_month_31_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x31, 0x01, 0x23, 0x01, 0x02, 0x23);
+}
+
+/* A 30-day month rolls over to the first of the next. */
+static void test_month_30_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x30, 0x04, 0x23, 0x01, 0x05, 0x23);
+}
+
+/* Outside a leap year, February rolls over after the 28th. */
+static void test_month_28_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x28, 0x02, 0x23, 0x01, 0x03, 0x23);
+}
+
+/* In a leap year the 28th is not the last day of February. */
+static void test_month_29_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ check_month_rollover(i2cdev, 0x28, 0x02, 0x24, 0x29, 0x02, 0x24);
+ check_month_rollover(i2cdev, 0x29, 0x02, 0x24, 0x01, 0x03, 0x24);
+}
+
/* The Century bit is guest-writable and survives an oscillator restart. */
static void test_century_bit(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -314,6 +370,10 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
+ qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
+ qos_add_test("month-30-days", "ds1339", test_month_30_days, NULL);
+ qos_add_test("month-31-days", "ds1339", test_month_31_days, NULL);
qos_add_test("century-bit", "ds1339", test_century_bit, NULL);
qos_add_test("century-write-no-toggle", "ds1339",
test_century_write_no_toggle, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 27/50] tests/qtest: ds1339: cover the end-of-month rollover
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The end-of-month rollover was untested, for any of the lengths the
calendar gives a month.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 8c144579e6..7eec163049 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -14,6 +14,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
+#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
#define DS1339_ALARM1 0x07
@@ -169,6 +170,61 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* Program 23:59:59 on the last day of a month and run the clock past it. */
+static void check_month_rollover(QI2CDevice *i2cdev,
+ uint8_t date, uint8_t month, uint8_t year,
+ uint8_t next_date, uint8_t next_month,
+ uint8_t next_year)
+{
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x01, date, month, year };
+ uint8_t resp[7];
+ unsigned idx;
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_DATE) != date) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmphex(resp[4], ==, next_date);
+ g_assert_cmphex(resp[5] & 0x1f, ==, next_month);
+ g_assert_cmphex(resp[6], ==, next_year);
+}
+
+/* A 31-day month rolls over to the first of the next. */
+static void test_month_31_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x31, 0x01, 0x23, 0x01, 0x02, 0x23);
+}
+
+/* A 30-day month rolls over to the first of the next. */
+static void test_month_30_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x30, 0x04, 0x23, 0x01, 0x05, 0x23);
+}
+
+/* Outside a leap year, February rolls over after the 28th. */
+static void test_month_28_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_month_rollover((QI2CDevice *)obj,
+ 0x28, 0x02, 0x23, 0x01, 0x03, 0x23);
+}
+
+/* In a leap year the 28th is not the last day of February. */
+static void test_month_29_days(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ check_month_rollover(i2cdev, 0x28, 0x02, 0x24, 0x29, 0x02, 0x24);
+ check_month_rollover(i2cdev, 0x29, 0x02, 0x24, 0x01, 0x03, 0x24);
+}
+
/* The Century bit is guest-writable and survives an oscillator restart. */
static void test_century_bit(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -314,6 +370,10 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
+ qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
+ qos_add_test("month-30-days", "ds1339", test_month_30_days, NULL);
+ qos_add_test("month-31-days", "ds1339", test_month_31_days, NULL);
qos_add_test("century-bit", "ds1339", test_century_bit, NULL);
qos_add_test("century-write-no-toggle", "ds1339",
test_century_write_no_toggle, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 28/50] tests/qtest: ds1339: cover the 12-hour format
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Both directions of the 12-hour conversion carry a fixup for midnight
and noon, and neither had been exercised.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 7eec163049..3259fa1df3 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -14,6 +14,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
+#define DS1339_HOURS 0x02
#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
@@ -26,6 +27,9 @@
#define DS1339_STATUS_OSF 0x80
+#define DS1339_HOURS_12 0x40
+#define DS1339_HOURS_PM 0x20
+
/* The clock and calendar come up on the host time. */
static void test_time(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -170,6 +174,46 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* Midnight and noon are the hours the 12-hour encoding special-cases. */
+static void test_hour_mode_12(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t tod[3] = {
+ 0x59, 0x59, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x11 /* 11:59:59 PM */
+ };
+ unsigned idx;
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | 0x12);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x12); /* 12 AM is midnight */
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x12);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | DS1339_HOURS_PM | 0x12); /* 12 PM noon */
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x01);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | DS1339_HOURS_PM | 0x01);
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | 0x11);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x11);
+
+ i2c_set8(i2cdev, DS1339_HOURS, 0x13);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==, 0x13);
+
+ /* An elapsed midnight moves 11 PM on to 12 AM, not to 00 or 13. */
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_HOURS) != tod[2]) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x12);
+}
+
/* Program 23:59:59 on the last day of a month and run the clock past it. */
static void check_month_rollover(QI2CDevice *i2cdev,
uint8_t date, uint8_t month, uint8_t year,
@@ -370,6 +414,7 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("hour-mode-12", "ds1339", test_hour_mode_12, NULL);
qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
qos_add_test("month-30-days", "ds1339", test_month_30_days, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 28/50] tests/qtest: ds1339: cover the 12-hour format
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Both directions of the 12-hour conversion carry a fixup for midnight
and noon, and neither had been exercised.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 7eec163049..3259fa1df3 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -14,6 +14,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
+#define DS1339_HOURS 0x02
#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
@@ -26,6 +27,9 @@
#define DS1339_STATUS_OSF 0x80
+#define DS1339_HOURS_12 0x40
+#define DS1339_HOURS_PM 0x20
+
/* The clock and calendar come up on the host time. */
static void test_time(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -170,6 +174,46 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* Midnight and noon are the hours the 12-hour encoding special-cases. */
+static void test_hour_mode_12(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t tod[3] = {
+ 0x59, 0x59, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x11 /* 11:59:59 PM */
+ };
+ unsigned idx;
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | 0x12);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x12); /* 12 AM is midnight */
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x12);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | DS1339_HOURS_PM | 0x12); /* 12 PM noon */
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x01);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | DS1339_HOURS_PM | 0x01);
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | 0x11);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x11);
+
+ i2c_set8(i2cdev, DS1339_HOURS, 0x13);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==, 0x13);
+
+ /* An elapsed midnight moves 11 PM on to 12 AM, not to 00 or 13. */
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_HOURS) != tod[2]) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x12);
+}
+
/* Program 23:59:59 on the last day of a month and run the clock past it. */
static void check_month_rollover(QI2CDevice *i2cdev,
uint8_t date, uint8_t month, uint8_t year,
@@ -370,6 +414,7 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("hour-mode-12", "ds1339", test_hour_mode_12, NULL);
qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
qos_add_test("month-30-days", "ds1339", test_month_30_days, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 28/50] tests/qtest: ds1339: cover the 12-hour format
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Both directions of the 12-hour conversion carry a fixup for midnight
and noon, and neither had been exercised.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 7eec163049..3259fa1df3 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -14,6 +14,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
+#define DS1339_HOURS 0x02
#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
@@ -26,6 +27,9 @@
#define DS1339_STATUS_OSF 0x80
+#define DS1339_HOURS_12 0x40
+#define DS1339_HOURS_PM 0x20
+
/* The clock and calendar come up on the host time. */
static void test_time(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -170,6 +174,46 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* Midnight and noon are the hours the 12-hour encoding special-cases. */
+static void test_hour_mode_12(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ const uint8_t tod[3] = {
+ 0x59, 0x59, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x11 /* 11:59:59 PM */
+ };
+ unsigned idx;
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | 0x12);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x12); /* 12 AM is midnight */
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x12);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | DS1339_HOURS_PM | 0x12); /* 12 PM noon */
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | DS1339_HOURS_PM | 0x01);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | DS1339_HOURS_PM | 0x01);
+
+ i2c_set8(i2cdev, DS1339_HOURS, DS1339_HOURS_12 | 0x11);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x11);
+
+ i2c_set8(i2cdev, DS1339_HOURS, 0x13);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==, 0x13);
+
+ /* An elapsed midnight moves 11 PM on to 12 AM, not to 00 or 13. */
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_HOURS) != tod[2]) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_HOURS), ==,
+ DS1339_HOURS_12 | 0x12);
+}
+
/* Program 23:59:59 on the last day of a month and run the clock past it. */
static void check_month_rollover(QI2CDevice *i2cdev,
uint8_t date, uint8_t month, uint8_t year,
@@ -370,6 +414,7 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("hour-mode-12", "ds1339", test_hour_mode_12, NULL);
qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
qos_add_test("month-30-days", "ds1339", test_month_30_days, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 29/50] tests/qtest: ds1339: cover the day-of-week counter
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The day-of-week register and the offset behind it had no coverage at
all.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 3259fa1df3..12562eff5f 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -15,6 +15,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
#define DS1339_HOURS 0x02
+#define DS1339_DAY 0x03
#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
@@ -174,6 +175,36 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* The day counter is the guest's own numbering, and steps at midnight. */
+static void test_day_of_week(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 23:59:59 on 14 June 2024, the day counter reading 7. */
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x07, 0x14, 0x06, 0x24 };
+ unsigned idx;
+
+ for (idx = 1; idx <= 7; idx++) {
+ i2c_set8(i2cdev, DS1339_DAY, idx);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, idx);
+ }
+
+ i2c_set8(i2cdev, DS1339_DAY, 0x03);
+ i2c_set8(i2cdev, DS1339_DATE, 0x14);
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x24);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, 0x03);
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_DAY) != 0x07) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, 0x01);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DATE), ==, 0x15);
+}
+
/* Midnight and noon are the hours the 12-hour encoding special-cases. */
static void test_hour_mode_12(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -414,6 +445,7 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("day-of-week", "ds1339", test_day_of_week, NULL);
qos_add_test("hour-mode-12", "ds1339", test_hour_mode_12, NULL);
qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 29/50] tests/qtest: ds1339: cover the day-of-week counter
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The day-of-week register and the offset behind it had no coverage at
all.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 3259fa1df3..12562eff5f 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -15,6 +15,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
#define DS1339_HOURS 0x02
+#define DS1339_DAY 0x03
#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
@@ -174,6 +175,36 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* The day counter is the guest's own numbering, and steps at midnight. */
+static void test_day_of_week(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 23:59:59 on 14 June 2024, the day counter reading 7. */
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x07, 0x14, 0x06, 0x24 };
+ unsigned idx;
+
+ for (idx = 1; idx <= 7; idx++) {
+ i2c_set8(i2cdev, DS1339_DAY, idx);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, idx);
+ }
+
+ i2c_set8(i2cdev, DS1339_DAY, 0x03);
+ i2c_set8(i2cdev, DS1339_DATE, 0x14);
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x24);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, 0x03);
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_DAY) != 0x07) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, 0x01);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DATE), ==, 0x15);
+}
+
/* Midnight and noon are the hours the 12-hour encoding special-cases. */
static void test_hour_mode_12(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -414,6 +445,7 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("day-of-week", "ds1339", test_day_of_week, NULL);
qos_add_test("hour-mode-12", "ds1339", test_hour_mode_12, NULL);
qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 29/50] tests/qtest: ds1339: cover the day-of-week counter
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The day-of-week register and the offset behind it had no coverage at
all.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 3259fa1df3..12562eff5f 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -15,6 +15,7 @@
/* DS1339 register map */
#define DS1339_SECONDS 0x00
#define DS1339_HOURS 0x02
+#define DS1339_DAY 0x03
#define DS1339_DATE 0x04
#define DS1339_MONTH 0x05
#define DS1339_YEAR 0x06
@@ -174,6 +175,36 @@ static void test_eosc_restart(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(resp[6], ==, 0x31);
}
+/* The day counter is the guest's own numbering, and steps at midnight. */
+static void test_day_of_week(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 23:59:59 on 14 June 2024, the day counter reading 7. */
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x07, 0x14, 0x06, 0x24 };
+ unsigned idx;
+
+ for (idx = 1; idx <= 7; idx++) {
+ i2c_set8(i2cdev, DS1339_DAY, idx);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, idx);
+ }
+
+ i2c_set8(i2cdev, DS1339_DAY, 0x03);
+ i2c_set8(i2cdev, DS1339_DATE, 0x14);
+ i2c_set8(i2cdev, DS1339_MONTH, 0x06);
+ i2c_set8(i2cdev, DS1339_YEAR, 0x24);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, 0x03);
+
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ for (idx = 0; idx < 50; idx++) {
+ if (i2c_get8(i2cdev, DS1339_DAY) != 0x07) {
+ break;
+ }
+ g_usleep(100 * 1000);
+ }
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DAY), ==, 0x01);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_DATE), ==, 0x15);
+}
+
/* Midnight and noon are the hours the 12-hour encoding special-cases. */
static void test_hour_mode_12(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -414,6 +445,7 @@ static void ds1339_register_nodes(void)
qos_add_test("eosc-restart", "ds1339", test_eosc_restart, NULL);
qos_add_test("stopped-reserved-bits", "ds1339",
test_stopped_reserved_bits, NULL);
+ qos_add_test("day-of-week", "ds1339", test_day_of_week, NULL);
qos_add_test("hour-mode-12", "ds1339", test_hour_mode_12, NULL);
qos_add_test("month-28-days", "ds1339", test_month_28_days, NULL);
qos_add_test("month-29-days", "ds1339", test_month_29_days, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 30/50] tests/qtest: ds1339: cover persist-on-reset
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The property was untested on this variant.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 12562eff5f..a1b5afb331 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -8,7 +8,10 @@
#include "qemu/osdep.h"
#include "qemu/bcd.h"
+#include "libqtest.h"
+#include "libqtest-single.h"
#include "libqos/i2c.h"
+#include "qobject/qdict.h"
#define DS1339_ADDR 0x68
@@ -426,6 +429,52 @@ static void test_alarm_registers(void *obj, void *data,
}
}
+/* A reset restores the power-on state, unless persist-on-reset is set. */
+static void check_reset(QI2CDevice *i2cdev, bool persists)
+{
+ uint8_t year = i2c_get8(i2cdev, DS1339_YEAR);
+ /* A year the clock is not already showing, so the check discriminates. */
+ uint8_t marker = year == 0x42 ? 0x77 : 0x42;
+ QDict *rsp;
+
+ i2c_set8(i2cdev, DS1339_ALARM1, 0xa5);
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ i2c_set8(i2cdev, DS1339_YEAR, marker);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, marker);
+
+ rsp = qmp("{ 'execute': 'system_reset' }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ qmp_eventwait("RESET");
+
+ if (persists) {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, marker);
+ } else {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==,
+ DS1339_STATUS_OSF);
+ /* Back on host time, which is where the year came from. */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, year);
+ }
+}
+
+/* Without persist-on-reset, a reset takes the part back to power-on. */
+static void test_reset_clears(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, false);
+}
+
+/* With persist-on-reset, the registers and the clock survive a reset. */
+static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, true);
+}
+
static void ds1339_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -459,6 +508,13 @@ static void ds1339_register_nodes(void)
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
NULL);
+ qos_add_test("reset-clears", "ds1339", test_reset_clears, NULL);
+
+ opts.extra_device_opts = "address=0x68,persist-on-reset=on";
+ qos_node_create_driver_named("ds1339-persist", "ds1339", i2c_device_create);
+ qos_node_consumes("ds1339-persist", "i2c-bus", &opts);
+
+ qos_add_test("reset-persists", "ds1339-persist", test_reset_persists, NULL);
}
libqos_init(ds1339_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 30/50] tests/qtest: ds1339: cover persist-on-reset
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The property was untested on this variant.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 12562eff5f..a1b5afb331 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -8,7 +8,10 @@
#include "qemu/osdep.h"
#include "qemu/bcd.h"
+#include "libqtest.h"
+#include "libqtest-single.h"
#include "libqos/i2c.h"
+#include "qobject/qdict.h"
#define DS1339_ADDR 0x68
@@ -426,6 +429,52 @@ static void test_alarm_registers(void *obj, void *data,
}
}
+/* A reset restores the power-on state, unless persist-on-reset is set. */
+static void check_reset(QI2CDevice *i2cdev, bool persists)
+{
+ uint8_t year = i2c_get8(i2cdev, DS1339_YEAR);
+ /* A year the clock is not already showing, so the check discriminates. */
+ uint8_t marker = year == 0x42 ? 0x77 : 0x42;
+ QDict *rsp;
+
+ i2c_set8(i2cdev, DS1339_ALARM1, 0xa5);
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ i2c_set8(i2cdev, DS1339_YEAR, marker);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, marker);
+
+ rsp = qmp("{ 'execute': 'system_reset' }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ qmp_eventwait("RESET");
+
+ if (persists) {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, marker);
+ } else {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==,
+ DS1339_STATUS_OSF);
+ /* Back on host time, which is where the year came from. */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, year);
+ }
+}
+
+/* Without persist-on-reset, a reset takes the part back to power-on. */
+static void test_reset_clears(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, false);
+}
+
+/* With persist-on-reset, the registers and the clock survive a reset. */
+static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, true);
+}
+
static void ds1339_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -459,6 +508,13 @@ static void ds1339_register_nodes(void)
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
NULL);
+ qos_add_test("reset-clears", "ds1339", test_reset_clears, NULL);
+
+ opts.extra_device_opts = "address=0x68,persist-on-reset=on";
+ qos_node_create_driver_named("ds1339-persist", "ds1339", i2c_device_create);
+ qos_node_consumes("ds1339-persist", "i2c-bus", &opts);
+
+ qos_add_test("reset-persists", "ds1339-persist", test_reset_persists, NULL);
}
libqos_init(ds1339_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 30/50] tests/qtest: ds1339: cover persist-on-reset
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The property was untested on this variant.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index 12562eff5f..a1b5afb331 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -8,7 +8,10 @@
#include "qemu/osdep.h"
#include "qemu/bcd.h"
+#include "libqtest.h"
+#include "libqtest-single.h"
#include "libqos/i2c.h"
+#include "qobject/qdict.h"
#define DS1339_ADDR 0x68
@@ -426,6 +429,52 @@ static void test_alarm_registers(void *obj, void *data,
}
}
+/* A reset restores the power-on state, unless persist-on-reset is set. */
+static void check_reset(QI2CDevice *i2cdev, bool persists)
+{
+ uint8_t year = i2c_get8(i2cdev, DS1339_YEAR);
+ /* A year the clock is not already showing, so the check discriminates. */
+ uint8_t marker = year == 0x42 ? 0x77 : 0x42;
+ QDict *rsp;
+
+ i2c_set8(i2cdev, DS1339_ALARM1, 0xa5);
+ i2c_set8(i2cdev, DS1339_STATUS, 0x00);
+ i2c_set8(i2cdev, DS1339_YEAR, marker);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, marker);
+
+ rsp = qmp("{ 'execute': 'system_reset' }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ qmp_eventwait("RESET");
+
+ if (persists) {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, marker);
+ } else {
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS), ==,
+ DS1339_STATUS_OSF);
+ /* Back on host time, which is where the year came from. */
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_YEAR), ==, year);
+ }
+}
+
+/* Without persist-on-reset, a reset takes the part back to power-on. */
+static void test_reset_clears(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, false);
+}
+
+/* With persist-on-reset, the registers and the clock survive a reset. */
+static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
+{
+ check_reset((QI2CDevice *)obj, true);
+}
+
static void ds1339_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -459,6 +508,13 @@ static void ds1339_register_nodes(void)
qos_add_test("block-wrap", "ds1339", test_block_wrap, NULL);
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
NULL);
+ qos_add_test("reset-clears", "ds1339", test_reset_clears, NULL);
+
+ opts.extra_device_opts = "address=0x68,persist-on-reset=on";
+ qos_node_create_driver_named("ds1339-persist", "ds1339", i2c_device_create);
+ qos_node_consumes("ds1339-persist", "i2c-bus", &opts);
+
+ qos_add_test("reset-persists", "ds1339-persist", test_reset_persists, NULL);
}
libqos_init(ds1339_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 31/50] tests/qtest: ds1339: cover the migration round trip
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing covered the migration of the register file or the oscillator
state, so a dropped field or a wrong version gate would have gone
unnoticed.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index a1b5afb331..ca2378aa34 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -7,9 +7,13 @@
*/
#include "qemu/osdep.h"
+
+#include <glib/gstdio.h>
+
#include "qemu/bcd.h"
#include "libqtest.h"
#include "libqtest-single.h"
+#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qobject/qdict.h"
@@ -475,6 +479,62 @@ static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
check_reset((QI2CDevice *)obj, true);
}
+/* The register file, the Century bit and a stopped clock all migrate. */
+static void test_migration(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 23:59:59 on the last day of 2099, Century set. */
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x03, 0x31, 0x8c, 0x99 };
+ g_autofree char *tmpfs = NULL;
+ g_autofree char *mig_path = NULL;
+ g_autofree char *uri = NULL;
+ GString *dest_cmdline;
+ GError *err = NULL;
+ QTestState *from, *to;
+ uint8_t resp[7];
+ QDict *rsp;
+
+ tmpfs = g_dir_make_tmp("ds1339-test-XXXXXX", &err);
+ g_assert_no_error(err);
+ g_assert(tmpfs);
+
+ mig_path = g_strdup_printf("%s/socket.mig", tmpfs);
+ uri = g_strdup_printf("unix:%s", mig_path);
+
+ /* Stop the clock first, so both sides have the same time to compare. */
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ i2c_set8(i2cdev, DS1339_ALARM1, 0xa5);
+
+ dest_cmdline = g_string_new(qos_get_current_command_line());
+ g_string_append_printf(dest_cmdline, " -incoming %s", uri);
+ to = qtest_init(dest_cmdline->str);
+
+ rsp = qmp("{ 'execute': 'migrate', 'arguments': { 'uri': %s } }", uri);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+
+ qmp_eventwait("STOP");
+ qtest_qmp_eventwait(to, "RESUME");
+
+ from = i2cdev->bus->qts;
+ i2cdev->bus->qts = to;
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpmem(resp, sizeof(resp), tod, sizeof(tod));
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18 | 0x80);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2cdev->bus->qts = from;
+
+ qtest_quit(to);
+ g_unlink(mig_path);
+ g_rmdir(tmpfs);
+ g_string_free(dest_cmdline, true);
+}
+
static void ds1339_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -509,6 +569,7 @@ static void ds1339_register_nodes(void)
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
NULL);
qos_add_test("reset-clears", "ds1339", test_reset_clears, NULL);
+ qos_add_test("migration", "ds1339", test_migration, NULL);
opts.extra_device_opts = "address=0x68,persist-on-reset=on";
qos_node_create_driver_named("ds1339-persist", "ds1339", i2c_device_create);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 31/50] tests/qtest: ds1339: cover the migration round trip
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing covered the migration of the register file or the oscillator
state, so a dropped field or a wrong version gate would have gone
unnoticed.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index a1b5afb331..ca2378aa34 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -7,9 +7,13 @@
*/
#include "qemu/osdep.h"
+
+#include <glib/gstdio.h>
+
#include "qemu/bcd.h"
#include "libqtest.h"
#include "libqtest-single.h"
+#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qobject/qdict.h"
@@ -475,6 +479,62 @@ static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
check_reset((QI2CDevice *)obj, true);
}
+/* The register file, the Century bit and a stopped clock all migrate. */
+static void test_migration(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 23:59:59 on the last day of 2099, Century set. */
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x03, 0x31, 0x8c, 0x99 };
+ g_autofree char *tmpfs = NULL;
+ g_autofree char *mig_path = NULL;
+ g_autofree char *uri = NULL;
+ GString *dest_cmdline;
+ GError *err = NULL;
+ QTestState *from, *to;
+ uint8_t resp[7];
+ QDict *rsp;
+
+ tmpfs = g_dir_make_tmp("ds1339-test-XXXXXX", &err);
+ g_assert_no_error(err);
+ g_assert(tmpfs);
+
+ mig_path = g_strdup_printf("%s/socket.mig", tmpfs);
+ uri = g_strdup_printf("unix:%s", mig_path);
+
+ /* Stop the clock first, so both sides have the same time to compare. */
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ i2c_set8(i2cdev, DS1339_ALARM1, 0xa5);
+
+ dest_cmdline = g_string_new(qos_get_current_command_line());
+ g_string_append_printf(dest_cmdline, " -incoming %s", uri);
+ to = qtest_init(dest_cmdline->str);
+
+ rsp = qmp("{ 'execute': 'migrate', 'arguments': { 'uri': %s } }", uri);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+
+ qmp_eventwait("STOP");
+ qtest_qmp_eventwait(to, "RESUME");
+
+ from = i2cdev->bus->qts;
+ i2cdev->bus->qts = to;
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpmem(resp, sizeof(resp), tod, sizeof(tod));
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18 | 0x80);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2cdev->bus->qts = from;
+
+ qtest_quit(to);
+ g_unlink(mig_path);
+ g_rmdir(tmpfs);
+ g_string_free(dest_cmdline, true);
+}
+
static void ds1339_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -509,6 +569,7 @@ static void ds1339_register_nodes(void)
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
NULL);
qos_add_test("reset-clears", "ds1339", test_reset_clears, NULL);
+ qos_add_test("migration", "ds1339", test_migration, NULL);
opts.extra_device_opts = "address=0x68,persist-on-reset=on";
qos_node_create_driver_named("ds1339-persist", "ds1339", i2c_device_create);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 31/50] tests/qtest: ds1339: cover the migration round trip
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Nothing covered the migration of the register file or the oscillator
state, so a dropped field or a wrong version gate would have gone
unnoticed.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ds1339-test.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/tests/qtest/ds1339-test.c b/tests/qtest/ds1339-test.c
index a1b5afb331..ca2378aa34 100644
--- a/tests/qtest/ds1339-test.c
+++ b/tests/qtest/ds1339-test.c
@@ -7,9 +7,13 @@
*/
#include "qemu/osdep.h"
+
+#include <glib/gstdio.h>
+
#include "qemu/bcd.h"
#include "libqtest.h"
#include "libqtest-single.h"
+#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qobject/qdict.h"
@@ -475,6 +479,62 @@ static void test_reset_persists(void *obj, void *data, QGuestAllocator *alloc)
check_reset((QI2CDevice *)obj, true);
}
+/* The register file, the Century bit and a stopped clock all migrate. */
+static void test_migration(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ /* 23:59:59 on the last day of 2099, Century set. */
+ const uint8_t tod[7] = { 0x59, 0x59, 0x23, 0x03, 0x31, 0x8c, 0x99 };
+ g_autofree char *tmpfs = NULL;
+ g_autofree char *mig_path = NULL;
+ g_autofree char *uri = NULL;
+ GString *dest_cmdline;
+ GError *err = NULL;
+ QTestState *from, *to;
+ uint8_t resp[7];
+ QDict *rsp;
+
+ tmpfs = g_dir_make_tmp("ds1339-test-XXXXXX", &err);
+ g_assert_no_error(err);
+ g_assert(tmpfs);
+
+ mig_path = g_strdup_printf("%s/socket.mig", tmpfs);
+ uri = g_strdup_printf("unix:%s", mig_path);
+
+ /* Stop the clock first, so both sides have the same time to compare. */
+ i2c_set8(i2cdev, DS1339_CONTROL, 0x18 | 0x80);
+ i2c_write_block(i2cdev, DS1339_SECONDS, tod, sizeof(tod));
+ i2c_set8(i2cdev, DS1339_ALARM1, 0xa5);
+
+ dest_cmdline = g_string_new(qos_get_current_command_line());
+ g_string_append_printf(dest_cmdline, " -incoming %s", uri);
+ to = qtest_init(dest_cmdline->str);
+
+ rsp = qmp("{ 'execute': 'migrate', 'arguments': { 'uri': %s } }", uri);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+
+ qmp_eventwait("STOP");
+ qtest_qmp_eventwait(to, "RESUME");
+
+ from = i2cdev->bus->qts;
+ i2cdev->bus->qts = to;
+
+ i2c_read_block(i2cdev, DS1339_SECONDS, resp, sizeof(resp));
+ g_assert_cmpmem(resp, sizeof(resp), tod, sizeof(tod));
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_ALARM1), ==, 0xa5);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_CONTROL), ==, 0x18 | 0x80);
+ g_assert_cmphex(i2c_get8(i2cdev, DS1339_STATUS) & DS1339_STATUS_OSF,
+ ==, DS1339_STATUS_OSF);
+
+ i2cdev->bus->qts = from;
+
+ qtest_quit(to);
+ g_unlink(mig_path);
+ g_rmdir(tmpfs);
+ g_string_free(dest_cmdline, true);
+}
+
static void ds1339_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -509,6 +569,7 @@ static void ds1339_register_nodes(void)
qos_add_test("alarm-registers", "ds1339", test_alarm_registers,
NULL);
qos_add_test("reset-clears", "ds1339", test_reset_clears, NULL);
+ qos_add_test("migration", "ds1339", test_migration, NULL);
opts.extra_device_opts = "address=0x68,persist-on-reset=on";
qos_node_create_driver_named("ds1339-persist", "ds1339", i2c_device_create);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 32/50] hw/sensor: ina230: add current/voltage/power monitor
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a model for the Texas Instruments INA230, a 36V, 16-bit I2C
current-shunt and power monitor.
The device measures a differential shunt voltage and a bus voltage; the
current and power registers are not measured directly but derived by the
device from the shunt voltage and the guest-programmed Calibration
register. The alert is modelled.
Shunt and bus voltages can be injected through the "shunt-voltage" and
"bus-voltage" QOM properties (in nV and uV respectively).
Note: Conversion is instantaneous: the averaging and conversion-time
fields are stored but have no timing effect.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/ina230.c | 597 +++++++++++++++++++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 7 +
include/hw/sensor/ina230.h | 14 ++
6 files changed, 624 insertions(+)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 4445f16f45..116d1c64ac 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -539,6 +539,7 @@ config ASPEED_SOC
select TMP105
select TMP421
select EMC141X
+ select INA230
select OR_IRQ
select UNIMP
select LED
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index b459ac2240..da519159ac 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -2,6 +2,10 @@ config ADC128D818
bool
depends on I2C
+config INA230
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/sensor/ina230.c b/hw/sensor/ina230.c
new file mode 100644
index 0000000000..988d12a6c5
--- /dev/null
+++ b/hw/sensor/ina230.c
@@ -0,0 +1,597 @@
+/*
+ * Texas Instruments INA230 36V, 16-bit current/voltage/power monitor
+ * with an I2C interface.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The INA230 measures a differential shunt voltage and a bus voltage.
+ * Current and power are not measured directly: they are derived by the
+ * device from the measured shunt voltage.
+ *
+ * Limitations:
+ * - Conversion timing is instantaneous. A conversion runs synchronously when an
+ * input, the calibration or the configuration changes; the per-channel
+ * conversion times (VSH_CT/VBUS_CT), the averaging count (AVG) and continuous
+ * re-conversion on a timer are not modelled. A read always returns the value
+ * derived from the current inputs and CVRF is set once a conversion has run.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/ina230.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define INA230_REG_CONFIG 0x00
+#define INA230_REG_SHUNT 0x01
+#define INA230_REG_BUS 0x02
+#define INA230_REG_POWER 0x03
+#define INA230_REG_CURRENT 0x04
+#define INA230_REG_CALIBRATION 0x05
+#define INA230_REG_MASK_ENABLE 0x06
+#define INA230_REG_ALERT_LIMIT 0x07
+#define INA230_REG_DIE_ID 0xFF
+
+FIELD(INA230_CONFIG, MODE, 0, 3)
+FIELD(INA230_CONFIG, VSH_CT, 3, 3)
+FIELD(INA230_CONFIG, VBUS_CT, 6, 3)
+FIELD(INA230_CONFIG, AVG, 9, 3)
+FIELD(INA230_CONFIG, RST, 15, 1)
+
+#define INA230_CONFIG_POR 0x4127
+
+FIELD(INA230_MASK_ENABLE, LEN, 0, 1) /* alert latch enable */
+FIELD(INA230_MASK_ENABLE, APOL, 1, 1) /* alert polarity */
+FIELD(INA230_MASK_ENABLE, OVF, 2, 1) /* math overflow flag (status) */
+FIELD(INA230_MASK_ENABLE, CVRF, 3, 1) /* conversion ready flag (status) */
+FIELD(INA230_MASK_ENABLE, AFF, 4, 1) /* alert function flag (status) */
+FIELD(INA230_MASK_ENABLE, CNVR, 10, 1) /* conversion ready alert */
+FIELD(INA230_MASK_ENABLE, POL, 11, 1) /* power over-limit */
+FIELD(INA230_MASK_ENABLE, BUL, 12, 1) /* bus under-limit */
+FIELD(INA230_MASK_ENABLE, BOL, 13, 1) /* bus over-limit */
+FIELD(INA230_MASK_ENABLE, SUL, 14, 1) /* shunt under-limit */
+FIELD(INA230_MASK_ENABLE, SOL, 15, 1) /* shunt over-limit */
+
+/* Writable Mask/Enable bits: alert-function selects + APOL + LEN */
+#define INA230_ME_WMASK (R_INA230_MASK_ENABLE_SOL_MASK | \
+ R_INA230_MASK_ENABLE_SUL_MASK | \
+ R_INA230_MASK_ENABLE_BOL_MASK | \
+ R_INA230_MASK_ENABLE_BUL_MASK | \
+ R_INA230_MASK_ENABLE_POL_MASK | \
+ R_INA230_MASK_ENABLE_CNVR_MASK | \
+ R_INA230_MASK_ENABLE_APOL_MASK | \
+ R_INA230_MASK_ENABLE_LEN_MASK)
+/* Read-only status bits: AFF, CVRF, OVF */
+#define INA230_ME_STATUS_MASK (R_INA230_MASK_ENABLE_AFF_MASK | \
+ R_INA230_MASK_ENABLE_CVRF_MASK | \
+ R_INA230_MASK_ENABLE_OVF_MASK)
+
+FIELD(INA230_CALIBRATION, FS, 0, 15)
+#define INA230_CAL_MASK R_INA230_CALIBRATION_FS_MASK
+
+#define INA230_SHUNT_LSB_NV 2500 /* 2.5 uV per LSB */
+#define INA230_BUS_LSB_UV 1250 /* 1.25 mV per LSB */
+#define INA230_SHUNT_MIN_NV (-81920000) /* -32768 * 2.5 uV */
+#define INA230_SHUNT_MAX_NV 81917500 /* 32767 * 2.5 uV */
+/*
+ * A real rail should not drive the pin past 36 V, so bus codes above 0x7080
+ * are intentionally unreachable in the model.
+ */
+#define INA230_BUS_MAX_UV 36000000 /* bus pin rated 0..36 V */
+
+#define INA230_CURRENT_DIV 2048
+#define INA230_POWER_DIV 20000
+
+#define INA230_DIE_ID_VAL 0x2310
+
+OBJECT_DECLARE_SIMPLE_TYPE(INA230State, INA230)
+
+struct INA230State {
+ I2CSlave parent_obj;
+
+ qemu_irq alert; /* ALERT open-drain output */
+
+ /* I2C transfer state */
+ uint8_t ptr; /* register pointer */
+ uint8_t len; /* bytes received in current transfer */
+ uint8_t wr_hi; /* pending write MSB */
+ uint8_t rx_byte; /* 0: return MSB next, 1: return LSB next */
+ uint16_t rx_word; /* value latched at the start of a read */
+
+ /* Register file */
+ uint16_t config;
+ uint16_t calibration;
+ uint16_t mask_enable;
+ uint16_t alert_limit;
+
+ /* Derived measurement registers */
+ uint16_t shunt_reg;
+ uint16_t bus_reg;
+ uint16_t current_reg;
+ uint16_t power_reg;
+
+ /* Injected physical inputs */
+ int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
+ uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
+
+ /*
+ * In latch mode AFF and the ALERT pin stay asserted until the
+ * Configuration register is written or the Mask/Enable register is read
+ * (datasheet 7.3.3).
+ */
+ bool alert_latched;
+
+ char *description;
+};
+
+/*
+ * Evaluate the selected alert function. @shunt_fresh and @bus_fresh tell which
+ * of the measurement registers hold a fresh value; a function fed by a channel
+ * that did not convert cannot assert.
+ */
+static bool ina230_limit_alert(const INA230State *s, bool shunt_fresh,
+ bool bus_fresh)
+{
+ uint16_t me = s->mask_enable;
+ int16_t shunt = (int16_t)s->shunt_reg;
+ int16_t limit_s = (int16_t)s->alert_limit;
+ uint16_t limit_u = s->alert_limit;
+
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, SOL)) {
+ return shunt_fresh && shunt > limit_s;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, SUL)) {
+ return shunt_fresh && shunt < limit_s;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, BOL)) {
+ return bus_fresh && s->bus_reg > limit_u;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, BUL)) {
+ return bus_fresh && s->bus_reg < limit_u;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, POL)) {
+ return bus_fresh && s->power_reg > limit_u;
+ }
+ return false;
+}
+
+static void ina230_refresh_alert(INA230State *s)
+{
+ bool latch = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN);
+ bool aff = latch ? s->alert_latched : ina230_limit_alert(s, true, true);
+ bool cnvr;
+ bool active;
+ int level;
+
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, AFF, aff);
+
+ cnvr = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CNVR) &&
+ FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CVRF);
+
+ active = aff || cnvr;
+
+ level = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, APOL) ?
+ active : !active;
+
+ trace_ina230_alert(s->description, level);
+ qemu_set_irq(s->alert, level);
+}
+
+static void ina230_sample_alert(INA230State *s, bool shunt_fresh,
+ bool bus_fresh)
+{
+ if (FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN) &&
+ ina230_limit_alert(s, shunt_fresh, bus_fresh)) {
+ s->alert_latched = true;
+ }
+ ina230_refresh_alert(s);
+}
+
+static void ina230_convert(INA230State *s)
+{
+ uint8_t mode = FIELD_EX16(s->config, INA230_CONFIG, MODE);
+ bool shunt_en = mode & 0x1;
+ bool bus_en = mode & 0x2;
+ int64_t sr, br, cur, pwr;
+ bool ovf = false;
+
+ if (!shunt_en && !bus_en) {
+ return;
+ }
+
+ if (shunt_en) {
+ sr = s->shunt_nv / INA230_SHUNT_LSB_NV;
+ sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
+ s->shunt_reg = (uint16_t)(int16_t)sr;
+
+ cur = (int64_t)(int16_t)s->shunt_reg * s->calibration /
+ INA230_CURRENT_DIV;
+ if (cur > INT16_MAX || cur < INT16_MIN) {
+ ovf = true;
+ cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
+ }
+ s->current_reg = (uint16_t)(int16_t)cur;
+ }
+
+ if (bus_en) {
+ br = s->bus_uv / INA230_BUS_LSB_UV;
+ br = MAX(0, MIN(INT16_MAX, br));
+ s->bus_reg = (uint16_t)br;
+ }
+
+ /*
+ * In bus-only mode the current register is retained from the last shunt
+ * conversion rather than measured anew, but the power register is still
+ * refreshed.
+ */
+ if (bus_en) {
+ pwr = (int64_t)(int16_t)s->current_reg * s->bus_reg / INA230_POWER_DIV;
+ /* POWER is an unsigned, positive-only register (datasheet). */
+ if (pwr < 0) {
+ pwr = 0;
+ } else if (pwr > UINT16_MAX) {
+ ovf = true;
+ pwr = UINT16_MAX;
+ }
+ s->power_reg = (uint16_t)pwr;
+ }
+
+ /* Without a calibration the device reports neither current nor power. */
+ if (s->calibration == 0) {
+ s->current_reg = 0;
+ s->power_reg = 0;
+ }
+
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, OVF, ovf);
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, CVRF, 1);
+
+ trace_ina230_convert(s->description, s->shunt_reg, s->bus_reg,
+ s->current_reg, s->power_reg);
+
+ ina230_sample_alert(s, shunt_en, bus_en);
+}
+
+/*
+ * A physical input or the calibration changed.
+ * - in continuous mode the device keeps converting and immediately tracks
+ * the new value
+ * - in a triggered mode it performs exactly one conversion per trigger
+ */
+static void ina230_input_update(INA230State *s)
+{
+ if (FIELD_EX16(s->config, INA230_CONFIG, MODE) & 0x4) {
+ ina230_convert(s);
+ }
+}
+
+static void ina230_reset_regs(INA230State *s)
+{
+ s->config = INA230_CONFIG_POR;
+ s->calibration = 0;
+ s->mask_enable = 0;
+ s->alert_limit = 0;
+ s->alert_latched = false;
+
+ s->ptr = 0;
+ s->len = 0;
+ s->wr_hi = 0;
+ s->rx_byte = 0;
+ s->rx_word = 0;
+
+ s->shunt_reg = 0;
+ s->bus_reg = 0;
+ s->current_reg = 0;
+ s->power_reg = 0;
+
+ ina230_refresh_alert(s);
+}
+
+static uint16_t ina230_read_reg(INA230State *s, uint8_t reg)
+{
+ uint16_t val;
+
+ switch (reg) {
+ case INA230_REG_CONFIG:
+ val = s->config;
+ break;
+ case INA230_REG_SHUNT:
+ val = s->shunt_reg;
+ break;
+ case INA230_REG_BUS:
+ val = s->bus_reg;
+ break;
+ case INA230_REG_POWER:
+ val = s->power_reg;
+ break;
+ case INA230_REG_CURRENT:
+ val = s->current_reg;
+ break;
+ case INA230_REG_CALIBRATION:
+ val = s->calibration;
+ break;
+ case INA230_REG_MASK_ENABLE:
+ val = s->mask_enable;
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE,
+ CVRF, 0);
+ s->alert_latched = false;
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_ALERT_LIMIT:
+ val = s->alert_limit;
+ break;
+ case INA230_REG_DIE_ID:
+ val = INA230_DIE_ID_VAL;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: read from undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ val = 0;
+ break;
+ }
+
+ trace_ina230_read(s->description, reg, val);
+ return val;
+}
+
+static void ina230_write_reg(INA230State *s, uint8_t reg, uint16_t val)
+{
+ trace_ina230_write(s->description, reg, val);
+
+ switch (reg) {
+ case INA230_REG_CONFIG:
+ if (val & R_INA230_CONFIG_RST_MASK) {
+ trace_ina230_reset(s->description, "reg");
+ ina230_reset_regs(s);
+ break;
+ }
+ s->config = val & ~R_INA230_CONFIG_RST_MASK;
+ /*
+ * The write itself releases a latched alert, so the release reaches
+ * the ALERT pin before the conversion it triggers can relatch it.
+ */
+ s->alert_latched = false;
+ ina230_refresh_alert(s);
+ ina230_convert(s);
+ break;
+ case INA230_REG_CALIBRATION:
+ s->calibration = val & INA230_CAL_MASK;
+ ina230_input_update(s);
+ break;
+ case INA230_REG_MASK_ENABLE:
+ s->mask_enable = (val & INA230_ME_WMASK) |
+ (s->mask_enable & INA230_ME_STATUS_MASK);
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_ALERT_LIMIT:
+ s->alert_limit = val;
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_SHUNT:
+ case INA230_REG_BUS:
+ case INA230_REG_POWER:
+ case INA230_REG_CURRENT:
+ case INA230_REG_DIE_ID:
+ 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 ina230_recv(I2CSlave *i2c)
+{
+ INA230State *s = INA230(i2c);
+ uint8_t byte;
+
+ if (s->rx_byte == 0) {
+ s->rx_word = ina230_read_reg(s, s->ptr);
+ byte = s->rx_word >> 8;
+ s->rx_byte = 1;
+ } else {
+ byte = s->rx_word & 0xff;
+ s->rx_byte = 0;
+ }
+
+ return byte;
+}
+
+static int ina230_send(I2CSlave *i2c, uint8_t data)
+{
+ INA230State *s = INA230(i2c);
+
+ if (s->len == 0) {
+ s->ptr = data;
+ } else if ((s->len & 1) == 1) {
+ s->wr_hi = data;
+ } else {
+ ina230_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
+ }
+ s->len++;
+
+ return 0;
+}
+
+static int ina230_event(I2CSlave *i2c, enum i2c_event event)
+{
+ INA230State *s = INA230(i2c);
+
+ s->len = 0;
+ s->rx_byte = 0;
+
+ return 0;
+}
+
+static void ina230_get_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value = s->shunt_nv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina230_set_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA230_SHUNT_MIN_NV || value > INA230_SHUNT_MAX_NV) {
+ error_setg(errp,
+ "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
+ s->description, value,
+ INA230_SHUNT_MIN_NV, INA230_SHUNT_MAX_NV);
+ return;
+ }
+
+ s->shunt_nv = (int32_t)value;
+ ina230_input_update(s);
+}
+
+static void ina230_get_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value = s->bus_uv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina230_set_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < 0 || value > INA230_BUS_MAX_UV) {
+ error_setg(errp,
+ "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
+ s->description, value, INA230_BUS_MAX_UV);
+ return;
+ }
+
+ s->bus_uv = (uint32_t)value;
+ ina230_input_update(s);
+}
+
+static void ina230_reset_hold(Object *obj, ResetType type)
+{
+ INA230State *s = INA230(obj);
+
+ trace_ina230_reset(s->description, "hw");
+ ina230_reset_regs(s);
+}
+
+static int ina230_post_load(void *opaque, int version_id)
+{
+ INA230State *s = opaque;
+
+ ina230_refresh_alert(s);
+
+ return 0;
+}
+
+static const VMStateDescription ina230_vmstate = {
+ .name = "INA230",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ina230_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8(ptr, INA230State),
+ VMSTATE_UINT8(len, INA230State),
+ VMSTATE_UINT8(wr_hi, INA230State),
+ VMSTATE_UINT8(rx_byte, INA230State),
+ VMSTATE_UINT16(rx_word, INA230State),
+ VMSTATE_UINT16(config, INA230State),
+ VMSTATE_UINT16(calibration, INA230State),
+ VMSTATE_UINT16(mask_enable, INA230State),
+ VMSTATE_UINT16(alert_limit, INA230State),
+ VMSTATE_UINT16(shunt_reg, INA230State),
+ VMSTATE_UINT16(bus_reg, INA230State),
+ VMSTATE_UINT16(current_reg, INA230State),
+ VMSTATE_UINT16(power_reg, INA230State),
+ VMSTATE_INT32(shunt_nv, INA230State),
+ VMSTATE_UINT32(bus_uv, INA230State),
+ VMSTATE_BOOL(alert_latched, INA230State),
+ VMSTATE_I2C_SLAVE(parent_obj, INA230State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ina230_initfn(Object *obj)
+{
+ object_property_add(obj, "shunt-voltage", "int", ina230_get_shunt,
+ ina230_set_shunt, NULL, NULL);
+ object_property_set_description(obj, "shunt-voltage",
+ "Injected shunt (differential) voltage, "
+ "in nanovolts");
+ object_property_add(obj, "bus-voltage", "int", ina230_get_bus,
+ ina230_set_bus, NULL, NULL);
+ object_property_set_description(obj, "bus-voltage",
+ "Injected bus voltage, in microvolts");
+}
+
+static void ina230_realize(DeviceState *dev, Error **errp)
+{
+ INA230State *s = INA230(dev);
+
+ if (!s->description) {
+ s->description = g_strdup(object_get_typename(OBJECT(dev)));
+ }
+
+ qdev_init_gpio_out(dev, &s->alert, 1);
+}
+
+static const Property ina230_properties[] = {
+ DEFINE_PROP_STRING("description", INA230State, description),
+};
+
+static void ina230_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 = ina230_event;
+ ic->recv = ina230_recv;
+ ic->send = ina230_send;
+ dc->realize = ina230_realize;
+ rc->phases.hold = ina230_reset_hold;
+ dc->vmsd = &ina230_vmstate;
+ device_class_set_props(dc, ina230_properties);
+}
+
+static const TypeInfo ina230_types[] = {
+ {
+ .name = TYPE_INA230,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = ina230_initfn,
+ .instance_size = sizeof(INA230State),
+ .class_init = ina230_class_init,
+ },
+};
+
+DEFINE_TYPES(ina230_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index fe36c9ef91..b81f001fdf 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,4 +1,5 @@
system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
+system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.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 5a3630f7bb..f809b6b506 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -8,6 +8,13 @@ adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u va
adc128d818_irq(const char *id, bool level) "%s level %u"
adc128d818_reset(const char *id, const char *source) "%s %s"
+# ina230.c
+ina230_read(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina230_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, uint16_t power) "%s shunt 0x%04x bus 0x%04x current 0x%04x power 0x%04x"
+ina230_alert(const char *id, bool level) "%s level %u"
+ina230_reset(const char *id, const char *source) "%s %s"
+
# tmp105.c
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/include/hw/sensor/ina230.h b/include/hw/sensor/ina230.h
new file mode 100644
index 0000000000..4394727d47
--- /dev/null
+++ b/include/hw/sensor/ina230.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments INA230 current/voltage/power monitor with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_INA230_H
+#define HW_SENSOR_INA230_H
+
+#define TYPE_INA230 "ina230"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 32/50] hw/sensor: ina230: add current/voltage/power monitor
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a model for the Texas Instruments INA230, a 36V, 16-bit I2C
current-shunt and power monitor.
The device measures a differential shunt voltage and a bus voltage; the
current and power registers are not measured directly but derived by the
device from the shunt voltage and the guest-programmed Calibration
register. The alert is modelled.
Shunt and bus voltages can be injected through the "shunt-voltage" and
"bus-voltage" QOM properties (in nV and uV respectively).
Note: Conversion is instantaneous: the averaging and conversion-time
fields are stored but have no timing effect.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/ina230.c | 597 +++++++++++++++++++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 7 +
include/hw/sensor/ina230.h | 14 ++
6 files changed, 624 insertions(+)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 4445f16f45..116d1c64ac 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -539,6 +539,7 @@ config ASPEED_SOC
select TMP105
select TMP421
select EMC141X
+ select INA230
select OR_IRQ
select UNIMP
select LED
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index b459ac2240..da519159ac 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -2,6 +2,10 @@ config ADC128D818
bool
depends on I2C
+config INA230
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/sensor/ina230.c b/hw/sensor/ina230.c
new file mode 100644
index 0000000000..988d12a6c5
--- /dev/null
+++ b/hw/sensor/ina230.c
@@ -0,0 +1,597 @@
+/*
+ * Texas Instruments INA230 36V, 16-bit current/voltage/power monitor
+ * with an I2C interface.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The INA230 measures a differential shunt voltage and a bus voltage.
+ * Current and power are not measured directly: they are derived by the
+ * device from the measured shunt voltage.
+ *
+ * Limitations:
+ * - Conversion timing is instantaneous. A conversion runs synchronously when an
+ * input, the calibration or the configuration changes; the per-channel
+ * conversion times (VSH_CT/VBUS_CT), the averaging count (AVG) and continuous
+ * re-conversion on a timer are not modelled. A read always returns the value
+ * derived from the current inputs and CVRF is set once a conversion has run.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/ina230.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define INA230_REG_CONFIG 0x00
+#define INA230_REG_SHUNT 0x01
+#define INA230_REG_BUS 0x02
+#define INA230_REG_POWER 0x03
+#define INA230_REG_CURRENT 0x04
+#define INA230_REG_CALIBRATION 0x05
+#define INA230_REG_MASK_ENABLE 0x06
+#define INA230_REG_ALERT_LIMIT 0x07
+#define INA230_REG_DIE_ID 0xFF
+
+FIELD(INA230_CONFIG, MODE, 0, 3)
+FIELD(INA230_CONFIG, VSH_CT, 3, 3)
+FIELD(INA230_CONFIG, VBUS_CT, 6, 3)
+FIELD(INA230_CONFIG, AVG, 9, 3)
+FIELD(INA230_CONFIG, RST, 15, 1)
+
+#define INA230_CONFIG_POR 0x4127
+
+FIELD(INA230_MASK_ENABLE, LEN, 0, 1) /* alert latch enable */
+FIELD(INA230_MASK_ENABLE, APOL, 1, 1) /* alert polarity */
+FIELD(INA230_MASK_ENABLE, OVF, 2, 1) /* math overflow flag (status) */
+FIELD(INA230_MASK_ENABLE, CVRF, 3, 1) /* conversion ready flag (status) */
+FIELD(INA230_MASK_ENABLE, AFF, 4, 1) /* alert function flag (status) */
+FIELD(INA230_MASK_ENABLE, CNVR, 10, 1) /* conversion ready alert */
+FIELD(INA230_MASK_ENABLE, POL, 11, 1) /* power over-limit */
+FIELD(INA230_MASK_ENABLE, BUL, 12, 1) /* bus under-limit */
+FIELD(INA230_MASK_ENABLE, BOL, 13, 1) /* bus over-limit */
+FIELD(INA230_MASK_ENABLE, SUL, 14, 1) /* shunt under-limit */
+FIELD(INA230_MASK_ENABLE, SOL, 15, 1) /* shunt over-limit */
+
+/* Writable Mask/Enable bits: alert-function selects + APOL + LEN */
+#define INA230_ME_WMASK (R_INA230_MASK_ENABLE_SOL_MASK | \
+ R_INA230_MASK_ENABLE_SUL_MASK | \
+ R_INA230_MASK_ENABLE_BOL_MASK | \
+ R_INA230_MASK_ENABLE_BUL_MASK | \
+ R_INA230_MASK_ENABLE_POL_MASK | \
+ R_INA230_MASK_ENABLE_CNVR_MASK | \
+ R_INA230_MASK_ENABLE_APOL_MASK | \
+ R_INA230_MASK_ENABLE_LEN_MASK)
+/* Read-only status bits: AFF, CVRF, OVF */
+#define INA230_ME_STATUS_MASK (R_INA230_MASK_ENABLE_AFF_MASK | \
+ R_INA230_MASK_ENABLE_CVRF_MASK | \
+ R_INA230_MASK_ENABLE_OVF_MASK)
+
+FIELD(INA230_CALIBRATION, FS, 0, 15)
+#define INA230_CAL_MASK R_INA230_CALIBRATION_FS_MASK
+
+#define INA230_SHUNT_LSB_NV 2500 /* 2.5 uV per LSB */
+#define INA230_BUS_LSB_UV 1250 /* 1.25 mV per LSB */
+#define INA230_SHUNT_MIN_NV (-81920000) /* -32768 * 2.5 uV */
+#define INA230_SHUNT_MAX_NV 81917500 /* 32767 * 2.5 uV */
+/*
+ * A real rail should not drive the pin past 36 V, so bus codes above 0x7080
+ * are intentionally unreachable in the model.
+ */
+#define INA230_BUS_MAX_UV 36000000 /* bus pin rated 0..36 V */
+
+#define INA230_CURRENT_DIV 2048
+#define INA230_POWER_DIV 20000
+
+#define INA230_DIE_ID_VAL 0x2310
+
+OBJECT_DECLARE_SIMPLE_TYPE(INA230State, INA230)
+
+struct INA230State {
+ I2CSlave parent_obj;
+
+ qemu_irq alert; /* ALERT open-drain output */
+
+ /* I2C transfer state */
+ uint8_t ptr; /* register pointer */
+ uint8_t len; /* bytes received in current transfer */
+ uint8_t wr_hi; /* pending write MSB */
+ uint8_t rx_byte; /* 0: return MSB next, 1: return LSB next */
+ uint16_t rx_word; /* value latched at the start of a read */
+
+ /* Register file */
+ uint16_t config;
+ uint16_t calibration;
+ uint16_t mask_enable;
+ uint16_t alert_limit;
+
+ /* Derived measurement registers */
+ uint16_t shunt_reg;
+ uint16_t bus_reg;
+ uint16_t current_reg;
+ uint16_t power_reg;
+
+ /* Injected physical inputs */
+ int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
+ uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
+
+ /*
+ * In latch mode AFF and the ALERT pin stay asserted until the
+ * Configuration register is written or the Mask/Enable register is read
+ * (datasheet 7.3.3).
+ */
+ bool alert_latched;
+
+ char *description;
+};
+
+/*
+ * Evaluate the selected alert function. @shunt_fresh and @bus_fresh tell which
+ * of the measurement registers hold a fresh value; a function fed by a channel
+ * that did not convert cannot assert.
+ */
+static bool ina230_limit_alert(const INA230State *s, bool shunt_fresh,
+ bool bus_fresh)
+{
+ uint16_t me = s->mask_enable;
+ int16_t shunt = (int16_t)s->shunt_reg;
+ int16_t limit_s = (int16_t)s->alert_limit;
+ uint16_t limit_u = s->alert_limit;
+
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, SOL)) {
+ return shunt_fresh && shunt > limit_s;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, SUL)) {
+ return shunt_fresh && shunt < limit_s;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, BOL)) {
+ return bus_fresh && s->bus_reg > limit_u;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, BUL)) {
+ return bus_fresh && s->bus_reg < limit_u;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, POL)) {
+ return bus_fresh && s->power_reg > limit_u;
+ }
+ return false;
+}
+
+static void ina230_refresh_alert(INA230State *s)
+{
+ bool latch = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN);
+ bool aff = latch ? s->alert_latched : ina230_limit_alert(s, true, true);
+ bool cnvr;
+ bool active;
+ int level;
+
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, AFF, aff);
+
+ cnvr = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CNVR) &&
+ FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CVRF);
+
+ active = aff || cnvr;
+
+ level = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, APOL) ?
+ active : !active;
+
+ trace_ina230_alert(s->description, level);
+ qemu_set_irq(s->alert, level);
+}
+
+static void ina230_sample_alert(INA230State *s, bool shunt_fresh,
+ bool bus_fresh)
+{
+ if (FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN) &&
+ ina230_limit_alert(s, shunt_fresh, bus_fresh)) {
+ s->alert_latched = true;
+ }
+ ina230_refresh_alert(s);
+}
+
+static void ina230_convert(INA230State *s)
+{
+ uint8_t mode = FIELD_EX16(s->config, INA230_CONFIG, MODE);
+ bool shunt_en = mode & 0x1;
+ bool bus_en = mode & 0x2;
+ int64_t sr, br, cur, pwr;
+ bool ovf = false;
+
+ if (!shunt_en && !bus_en) {
+ return;
+ }
+
+ if (shunt_en) {
+ sr = s->shunt_nv / INA230_SHUNT_LSB_NV;
+ sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
+ s->shunt_reg = (uint16_t)(int16_t)sr;
+
+ cur = (int64_t)(int16_t)s->shunt_reg * s->calibration /
+ INA230_CURRENT_DIV;
+ if (cur > INT16_MAX || cur < INT16_MIN) {
+ ovf = true;
+ cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
+ }
+ s->current_reg = (uint16_t)(int16_t)cur;
+ }
+
+ if (bus_en) {
+ br = s->bus_uv / INA230_BUS_LSB_UV;
+ br = MAX(0, MIN(INT16_MAX, br));
+ s->bus_reg = (uint16_t)br;
+ }
+
+ /*
+ * In bus-only mode the current register is retained from the last shunt
+ * conversion rather than measured anew, but the power register is still
+ * refreshed.
+ */
+ if (bus_en) {
+ pwr = (int64_t)(int16_t)s->current_reg * s->bus_reg / INA230_POWER_DIV;
+ /* POWER is an unsigned, positive-only register (datasheet). */
+ if (pwr < 0) {
+ pwr = 0;
+ } else if (pwr > UINT16_MAX) {
+ ovf = true;
+ pwr = UINT16_MAX;
+ }
+ s->power_reg = (uint16_t)pwr;
+ }
+
+ /* Without a calibration the device reports neither current nor power. */
+ if (s->calibration == 0) {
+ s->current_reg = 0;
+ s->power_reg = 0;
+ }
+
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, OVF, ovf);
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, CVRF, 1);
+
+ trace_ina230_convert(s->description, s->shunt_reg, s->bus_reg,
+ s->current_reg, s->power_reg);
+
+ ina230_sample_alert(s, shunt_en, bus_en);
+}
+
+/*
+ * A physical input or the calibration changed.
+ * - in continuous mode the device keeps converting and immediately tracks
+ * the new value
+ * - in a triggered mode it performs exactly one conversion per trigger
+ */
+static void ina230_input_update(INA230State *s)
+{
+ if (FIELD_EX16(s->config, INA230_CONFIG, MODE) & 0x4) {
+ ina230_convert(s);
+ }
+}
+
+static void ina230_reset_regs(INA230State *s)
+{
+ s->config = INA230_CONFIG_POR;
+ s->calibration = 0;
+ s->mask_enable = 0;
+ s->alert_limit = 0;
+ s->alert_latched = false;
+
+ s->ptr = 0;
+ s->len = 0;
+ s->wr_hi = 0;
+ s->rx_byte = 0;
+ s->rx_word = 0;
+
+ s->shunt_reg = 0;
+ s->bus_reg = 0;
+ s->current_reg = 0;
+ s->power_reg = 0;
+
+ ina230_refresh_alert(s);
+}
+
+static uint16_t ina230_read_reg(INA230State *s, uint8_t reg)
+{
+ uint16_t val;
+
+ switch (reg) {
+ case INA230_REG_CONFIG:
+ val = s->config;
+ break;
+ case INA230_REG_SHUNT:
+ val = s->shunt_reg;
+ break;
+ case INA230_REG_BUS:
+ val = s->bus_reg;
+ break;
+ case INA230_REG_POWER:
+ val = s->power_reg;
+ break;
+ case INA230_REG_CURRENT:
+ val = s->current_reg;
+ break;
+ case INA230_REG_CALIBRATION:
+ val = s->calibration;
+ break;
+ case INA230_REG_MASK_ENABLE:
+ val = s->mask_enable;
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE,
+ CVRF, 0);
+ s->alert_latched = false;
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_ALERT_LIMIT:
+ val = s->alert_limit;
+ break;
+ case INA230_REG_DIE_ID:
+ val = INA230_DIE_ID_VAL;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: read from undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ val = 0;
+ break;
+ }
+
+ trace_ina230_read(s->description, reg, val);
+ return val;
+}
+
+static void ina230_write_reg(INA230State *s, uint8_t reg, uint16_t val)
+{
+ trace_ina230_write(s->description, reg, val);
+
+ switch (reg) {
+ case INA230_REG_CONFIG:
+ if (val & R_INA230_CONFIG_RST_MASK) {
+ trace_ina230_reset(s->description, "reg");
+ ina230_reset_regs(s);
+ break;
+ }
+ s->config = val & ~R_INA230_CONFIG_RST_MASK;
+ /*
+ * The write itself releases a latched alert, so the release reaches
+ * the ALERT pin before the conversion it triggers can relatch it.
+ */
+ s->alert_latched = false;
+ ina230_refresh_alert(s);
+ ina230_convert(s);
+ break;
+ case INA230_REG_CALIBRATION:
+ s->calibration = val & INA230_CAL_MASK;
+ ina230_input_update(s);
+ break;
+ case INA230_REG_MASK_ENABLE:
+ s->mask_enable = (val & INA230_ME_WMASK) |
+ (s->mask_enable & INA230_ME_STATUS_MASK);
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_ALERT_LIMIT:
+ s->alert_limit = val;
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_SHUNT:
+ case INA230_REG_BUS:
+ case INA230_REG_POWER:
+ case INA230_REG_CURRENT:
+ case INA230_REG_DIE_ID:
+ 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 ina230_recv(I2CSlave *i2c)
+{
+ INA230State *s = INA230(i2c);
+ uint8_t byte;
+
+ if (s->rx_byte == 0) {
+ s->rx_word = ina230_read_reg(s, s->ptr);
+ byte = s->rx_word >> 8;
+ s->rx_byte = 1;
+ } else {
+ byte = s->rx_word & 0xff;
+ s->rx_byte = 0;
+ }
+
+ return byte;
+}
+
+static int ina230_send(I2CSlave *i2c, uint8_t data)
+{
+ INA230State *s = INA230(i2c);
+
+ if (s->len == 0) {
+ s->ptr = data;
+ } else if ((s->len & 1) == 1) {
+ s->wr_hi = data;
+ } else {
+ ina230_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
+ }
+ s->len++;
+
+ return 0;
+}
+
+static int ina230_event(I2CSlave *i2c, enum i2c_event event)
+{
+ INA230State *s = INA230(i2c);
+
+ s->len = 0;
+ s->rx_byte = 0;
+
+ return 0;
+}
+
+static void ina230_get_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value = s->shunt_nv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina230_set_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA230_SHUNT_MIN_NV || value > INA230_SHUNT_MAX_NV) {
+ error_setg(errp,
+ "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
+ s->description, value,
+ INA230_SHUNT_MIN_NV, INA230_SHUNT_MAX_NV);
+ return;
+ }
+
+ s->shunt_nv = (int32_t)value;
+ ina230_input_update(s);
+}
+
+static void ina230_get_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value = s->bus_uv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina230_set_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < 0 || value > INA230_BUS_MAX_UV) {
+ error_setg(errp,
+ "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
+ s->description, value, INA230_BUS_MAX_UV);
+ return;
+ }
+
+ s->bus_uv = (uint32_t)value;
+ ina230_input_update(s);
+}
+
+static void ina230_reset_hold(Object *obj, ResetType type)
+{
+ INA230State *s = INA230(obj);
+
+ trace_ina230_reset(s->description, "hw");
+ ina230_reset_regs(s);
+}
+
+static int ina230_post_load(void *opaque, int version_id)
+{
+ INA230State *s = opaque;
+
+ ina230_refresh_alert(s);
+
+ return 0;
+}
+
+static const VMStateDescription ina230_vmstate = {
+ .name = "INA230",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ina230_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8(ptr, INA230State),
+ VMSTATE_UINT8(len, INA230State),
+ VMSTATE_UINT8(wr_hi, INA230State),
+ VMSTATE_UINT8(rx_byte, INA230State),
+ VMSTATE_UINT16(rx_word, INA230State),
+ VMSTATE_UINT16(config, INA230State),
+ VMSTATE_UINT16(calibration, INA230State),
+ VMSTATE_UINT16(mask_enable, INA230State),
+ VMSTATE_UINT16(alert_limit, INA230State),
+ VMSTATE_UINT16(shunt_reg, INA230State),
+ VMSTATE_UINT16(bus_reg, INA230State),
+ VMSTATE_UINT16(current_reg, INA230State),
+ VMSTATE_UINT16(power_reg, INA230State),
+ VMSTATE_INT32(shunt_nv, INA230State),
+ VMSTATE_UINT32(bus_uv, INA230State),
+ VMSTATE_BOOL(alert_latched, INA230State),
+ VMSTATE_I2C_SLAVE(parent_obj, INA230State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ina230_initfn(Object *obj)
+{
+ object_property_add(obj, "shunt-voltage", "int", ina230_get_shunt,
+ ina230_set_shunt, NULL, NULL);
+ object_property_set_description(obj, "shunt-voltage",
+ "Injected shunt (differential) voltage, "
+ "in nanovolts");
+ object_property_add(obj, "bus-voltage", "int", ina230_get_bus,
+ ina230_set_bus, NULL, NULL);
+ object_property_set_description(obj, "bus-voltage",
+ "Injected bus voltage, in microvolts");
+}
+
+static void ina230_realize(DeviceState *dev, Error **errp)
+{
+ INA230State *s = INA230(dev);
+
+ if (!s->description) {
+ s->description = g_strdup(object_get_typename(OBJECT(dev)));
+ }
+
+ qdev_init_gpio_out(dev, &s->alert, 1);
+}
+
+static const Property ina230_properties[] = {
+ DEFINE_PROP_STRING("description", INA230State, description),
+};
+
+static void ina230_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 = ina230_event;
+ ic->recv = ina230_recv;
+ ic->send = ina230_send;
+ dc->realize = ina230_realize;
+ rc->phases.hold = ina230_reset_hold;
+ dc->vmsd = &ina230_vmstate;
+ device_class_set_props(dc, ina230_properties);
+}
+
+static const TypeInfo ina230_types[] = {
+ {
+ .name = TYPE_INA230,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = ina230_initfn,
+ .instance_size = sizeof(INA230State),
+ .class_init = ina230_class_init,
+ },
+};
+
+DEFINE_TYPES(ina230_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index fe36c9ef91..b81f001fdf 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,4 +1,5 @@
system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
+system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.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 5a3630f7bb..f809b6b506 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -8,6 +8,13 @@ adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u va
adc128d818_irq(const char *id, bool level) "%s level %u"
adc128d818_reset(const char *id, const char *source) "%s %s"
+# ina230.c
+ina230_read(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina230_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, uint16_t power) "%s shunt 0x%04x bus 0x%04x current 0x%04x power 0x%04x"
+ina230_alert(const char *id, bool level) "%s level %u"
+ina230_reset(const char *id, const char *source) "%s %s"
+
# tmp105.c
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/include/hw/sensor/ina230.h b/include/hw/sensor/ina230.h
new file mode 100644
index 0000000000..4394727d47
--- /dev/null
+++ b/include/hw/sensor/ina230.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments INA230 current/voltage/power monitor with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_INA230_H
+#define HW_SENSOR_INA230_H
+
+#define TYPE_INA230 "ina230"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 32/50] hw/sensor: ina230: add current/voltage/power monitor
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a model for the Texas Instruments INA230, a 36V, 16-bit I2C
current-shunt and power monitor.
The device measures a differential shunt voltage and a bus voltage; the
current and power registers are not measured directly but derived by the
device from the shunt voltage and the guest-programmed Calibration
register. The alert is modelled.
Shunt and bus voltages can be injected through the "shunt-voltage" and
"bus-voltage" QOM properties (in nV and uV respectively).
Note: Conversion is instantaneous: the averaging and conversion-time
fields are stored but have no timing effect.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/ina230.c | 597 +++++++++++++++++++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 7 +
include/hw/sensor/ina230.h | 14 ++
6 files changed, 624 insertions(+)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 4445f16f45..116d1c64ac 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -539,6 +539,7 @@ config ASPEED_SOC
select TMP105
select TMP421
select EMC141X
+ select INA230
select OR_IRQ
select UNIMP
select LED
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index b459ac2240..da519159ac 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -2,6 +2,10 @@ config ADC128D818
bool
depends on I2C
+config INA230
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/sensor/ina230.c b/hw/sensor/ina230.c
new file mode 100644
index 0000000000..988d12a6c5
--- /dev/null
+++ b/hw/sensor/ina230.c
@@ -0,0 +1,597 @@
+/*
+ * Texas Instruments INA230 36V, 16-bit current/voltage/power monitor
+ * with an I2C interface.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The INA230 measures a differential shunt voltage and a bus voltage.
+ * Current and power are not measured directly: they are derived by the
+ * device from the measured shunt voltage.
+ *
+ * Limitations:
+ * - Conversion timing is instantaneous. A conversion runs synchronously when an
+ * input, the calibration or the configuration changes; the per-channel
+ * conversion times (VSH_CT/VBUS_CT), the averaging count (AVG) and continuous
+ * re-conversion on a timer are not modelled. A read always returns the value
+ * derived from the current inputs and CVRF is set once a conversion has run.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/ina230.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define INA230_REG_CONFIG 0x00
+#define INA230_REG_SHUNT 0x01
+#define INA230_REG_BUS 0x02
+#define INA230_REG_POWER 0x03
+#define INA230_REG_CURRENT 0x04
+#define INA230_REG_CALIBRATION 0x05
+#define INA230_REG_MASK_ENABLE 0x06
+#define INA230_REG_ALERT_LIMIT 0x07
+#define INA230_REG_DIE_ID 0xFF
+
+FIELD(INA230_CONFIG, MODE, 0, 3)
+FIELD(INA230_CONFIG, VSH_CT, 3, 3)
+FIELD(INA230_CONFIG, VBUS_CT, 6, 3)
+FIELD(INA230_CONFIG, AVG, 9, 3)
+FIELD(INA230_CONFIG, RST, 15, 1)
+
+#define INA230_CONFIG_POR 0x4127
+
+FIELD(INA230_MASK_ENABLE, LEN, 0, 1) /* alert latch enable */
+FIELD(INA230_MASK_ENABLE, APOL, 1, 1) /* alert polarity */
+FIELD(INA230_MASK_ENABLE, OVF, 2, 1) /* math overflow flag (status) */
+FIELD(INA230_MASK_ENABLE, CVRF, 3, 1) /* conversion ready flag (status) */
+FIELD(INA230_MASK_ENABLE, AFF, 4, 1) /* alert function flag (status) */
+FIELD(INA230_MASK_ENABLE, CNVR, 10, 1) /* conversion ready alert */
+FIELD(INA230_MASK_ENABLE, POL, 11, 1) /* power over-limit */
+FIELD(INA230_MASK_ENABLE, BUL, 12, 1) /* bus under-limit */
+FIELD(INA230_MASK_ENABLE, BOL, 13, 1) /* bus over-limit */
+FIELD(INA230_MASK_ENABLE, SUL, 14, 1) /* shunt under-limit */
+FIELD(INA230_MASK_ENABLE, SOL, 15, 1) /* shunt over-limit */
+
+/* Writable Mask/Enable bits: alert-function selects + APOL + LEN */
+#define INA230_ME_WMASK (R_INA230_MASK_ENABLE_SOL_MASK | \
+ R_INA230_MASK_ENABLE_SUL_MASK | \
+ R_INA230_MASK_ENABLE_BOL_MASK | \
+ R_INA230_MASK_ENABLE_BUL_MASK | \
+ R_INA230_MASK_ENABLE_POL_MASK | \
+ R_INA230_MASK_ENABLE_CNVR_MASK | \
+ R_INA230_MASK_ENABLE_APOL_MASK | \
+ R_INA230_MASK_ENABLE_LEN_MASK)
+/* Read-only status bits: AFF, CVRF, OVF */
+#define INA230_ME_STATUS_MASK (R_INA230_MASK_ENABLE_AFF_MASK | \
+ R_INA230_MASK_ENABLE_CVRF_MASK | \
+ R_INA230_MASK_ENABLE_OVF_MASK)
+
+FIELD(INA230_CALIBRATION, FS, 0, 15)
+#define INA230_CAL_MASK R_INA230_CALIBRATION_FS_MASK
+
+#define INA230_SHUNT_LSB_NV 2500 /* 2.5 uV per LSB */
+#define INA230_BUS_LSB_UV 1250 /* 1.25 mV per LSB */
+#define INA230_SHUNT_MIN_NV (-81920000) /* -32768 * 2.5 uV */
+#define INA230_SHUNT_MAX_NV 81917500 /* 32767 * 2.5 uV */
+/*
+ * A real rail should not drive the pin past 36 V, so bus codes above 0x7080
+ * are intentionally unreachable in the model.
+ */
+#define INA230_BUS_MAX_UV 36000000 /* bus pin rated 0..36 V */
+
+#define INA230_CURRENT_DIV 2048
+#define INA230_POWER_DIV 20000
+
+#define INA230_DIE_ID_VAL 0x2310
+
+OBJECT_DECLARE_SIMPLE_TYPE(INA230State, INA230)
+
+struct INA230State {
+ I2CSlave parent_obj;
+
+ qemu_irq alert; /* ALERT open-drain output */
+
+ /* I2C transfer state */
+ uint8_t ptr; /* register pointer */
+ uint8_t len; /* bytes received in current transfer */
+ uint8_t wr_hi; /* pending write MSB */
+ uint8_t rx_byte; /* 0: return MSB next, 1: return LSB next */
+ uint16_t rx_word; /* value latched at the start of a read */
+
+ /* Register file */
+ uint16_t config;
+ uint16_t calibration;
+ uint16_t mask_enable;
+ uint16_t alert_limit;
+
+ /* Derived measurement registers */
+ uint16_t shunt_reg;
+ uint16_t bus_reg;
+ uint16_t current_reg;
+ uint16_t power_reg;
+
+ /* Injected physical inputs */
+ int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
+ uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
+
+ /*
+ * In latch mode AFF and the ALERT pin stay asserted until the
+ * Configuration register is written or the Mask/Enable register is read
+ * (datasheet 7.3.3).
+ */
+ bool alert_latched;
+
+ char *description;
+};
+
+/*
+ * Evaluate the selected alert function. @shunt_fresh and @bus_fresh tell which
+ * of the measurement registers hold a fresh value; a function fed by a channel
+ * that did not convert cannot assert.
+ */
+static bool ina230_limit_alert(const INA230State *s, bool shunt_fresh,
+ bool bus_fresh)
+{
+ uint16_t me = s->mask_enable;
+ int16_t shunt = (int16_t)s->shunt_reg;
+ int16_t limit_s = (int16_t)s->alert_limit;
+ uint16_t limit_u = s->alert_limit;
+
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, SOL)) {
+ return shunt_fresh && shunt > limit_s;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, SUL)) {
+ return shunt_fresh && shunt < limit_s;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, BOL)) {
+ return bus_fresh && s->bus_reg > limit_u;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, BUL)) {
+ return bus_fresh && s->bus_reg < limit_u;
+ }
+ if (FIELD_EX16(me, INA230_MASK_ENABLE, POL)) {
+ return bus_fresh && s->power_reg > limit_u;
+ }
+ return false;
+}
+
+static void ina230_refresh_alert(INA230State *s)
+{
+ bool latch = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN);
+ bool aff = latch ? s->alert_latched : ina230_limit_alert(s, true, true);
+ bool cnvr;
+ bool active;
+ int level;
+
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, AFF, aff);
+
+ cnvr = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CNVR) &&
+ FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CVRF);
+
+ active = aff || cnvr;
+
+ level = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, APOL) ?
+ active : !active;
+
+ trace_ina230_alert(s->description, level);
+ qemu_set_irq(s->alert, level);
+}
+
+static void ina230_sample_alert(INA230State *s, bool shunt_fresh,
+ bool bus_fresh)
+{
+ if (FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN) &&
+ ina230_limit_alert(s, shunt_fresh, bus_fresh)) {
+ s->alert_latched = true;
+ }
+ ina230_refresh_alert(s);
+}
+
+static void ina230_convert(INA230State *s)
+{
+ uint8_t mode = FIELD_EX16(s->config, INA230_CONFIG, MODE);
+ bool shunt_en = mode & 0x1;
+ bool bus_en = mode & 0x2;
+ int64_t sr, br, cur, pwr;
+ bool ovf = false;
+
+ if (!shunt_en && !bus_en) {
+ return;
+ }
+
+ if (shunt_en) {
+ sr = s->shunt_nv / INA230_SHUNT_LSB_NV;
+ sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
+ s->shunt_reg = (uint16_t)(int16_t)sr;
+
+ cur = (int64_t)(int16_t)s->shunt_reg * s->calibration /
+ INA230_CURRENT_DIV;
+ if (cur > INT16_MAX || cur < INT16_MIN) {
+ ovf = true;
+ cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
+ }
+ s->current_reg = (uint16_t)(int16_t)cur;
+ }
+
+ if (bus_en) {
+ br = s->bus_uv / INA230_BUS_LSB_UV;
+ br = MAX(0, MIN(INT16_MAX, br));
+ s->bus_reg = (uint16_t)br;
+ }
+
+ /*
+ * In bus-only mode the current register is retained from the last shunt
+ * conversion rather than measured anew, but the power register is still
+ * refreshed.
+ */
+ if (bus_en) {
+ pwr = (int64_t)(int16_t)s->current_reg * s->bus_reg / INA230_POWER_DIV;
+ /* POWER is an unsigned, positive-only register (datasheet). */
+ if (pwr < 0) {
+ pwr = 0;
+ } else if (pwr > UINT16_MAX) {
+ ovf = true;
+ pwr = UINT16_MAX;
+ }
+ s->power_reg = (uint16_t)pwr;
+ }
+
+ /* Without a calibration the device reports neither current nor power. */
+ if (s->calibration == 0) {
+ s->current_reg = 0;
+ s->power_reg = 0;
+ }
+
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, OVF, ovf);
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, CVRF, 1);
+
+ trace_ina230_convert(s->description, s->shunt_reg, s->bus_reg,
+ s->current_reg, s->power_reg);
+
+ ina230_sample_alert(s, shunt_en, bus_en);
+}
+
+/*
+ * A physical input or the calibration changed.
+ * - in continuous mode the device keeps converting and immediately tracks
+ * the new value
+ * - in a triggered mode it performs exactly one conversion per trigger
+ */
+static void ina230_input_update(INA230State *s)
+{
+ if (FIELD_EX16(s->config, INA230_CONFIG, MODE) & 0x4) {
+ ina230_convert(s);
+ }
+}
+
+static void ina230_reset_regs(INA230State *s)
+{
+ s->config = INA230_CONFIG_POR;
+ s->calibration = 0;
+ s->mask_enable = 0;
+ s->alert_limit = 0;
+ s->alert_latched = false;
+
+ s->ptr = 0;
+ s->len = 0;
+ s->wr_hi = 0;
+ s->rx_byte = 0;
+ s->rx_word = 0;
+
+ s->shunt_reg = 0;
+ s->bus_reg = 0;
+ s->current_reg = 0;
+ s->power_reg = 0;
+
+ ina230_refresh_alert(s);
+}
+
+static uint16_t ina230_read_reg(INA230State *s, uint8_t reg)
+{
+ uint16_t val;
+
+ switch (reg) {
+ case INA230_REG_CONFIG:
+ val = s->config;
+ break;
+ case INA230_REG_SHUNT:
+ val = s->shunt_reg;
+ break;
+ case INA230_REG_BUS:
+ val = s->bus_reg;
+ break;
+ case INA230_REG_POWER:
+ val = s->power_reg;
+ break;
+ case INA230_REG_CURRENT:
+ val = s->current_reg;
+ break;
+ case INA230_REG_CALIBRATION:
+ val = s->calibration;
+ break;
+ case INA230_REG_MASK_ENABLE:
+ val = s->mask_enable;
+ s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE,
+ CVRF, 0);
+ s->alert_latched = false;
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_ALERT_LIMIT:
+ val = s->alert_limit;
+ break;
+ case INA230_REG_DIE_ID:
+ val = INA230_DIE_ID_VAL;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: read from undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ val = 0;
+ break;
+ }
+
+ trace_ina230_read(s->description, reg, val);
+ return val;
+}
+
+static void ina230_write_reg(INA230State *s, uint8_t reg, uint16_t val)
+{
+ trace_ina230_write(s->description, reg, val);
+
+ switch (reg) {
+ case INA230_REG_CONFIG:
+ if (val & R_INA230_CONFIG_RST_MASK) {
+ trace_ina230_reset(s->description, "reg");
+ ina230_reset_regs(s);
+ break;
+ }
+ s->config = val & ~R_INA230_CONFIG_RST_MASK;
+ /*
+ * The write itself releases a latched alert, so the release reaches
+ * the ALERT pin before the conversion it triggers can relatch it.
+ */
+ s->alert_latched = false;
+ ina230_refresh_alert(s);
+ ina230_convert(s);
+ break;
+ case INA230_REG_CALIBRATION:
+ s->calibration = val & INA230_CAL_MASK;
+ ina230_input_update(s);
+ break;
+ case INA230_REG_MASK_ENABLE:
+ s->mask_enable = (val & INA230_ME_WMASK) |
+ (s->mask_enable & INA230_ME_STATUS_MASK);
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_ALERT_LIMIT:
+ s->alert_limit = val;
+ ina230_refresh_alert(s);
+ break;
+ case INA230_REG_SHUNT:
+ case INA230_REG_BUS:
+ case INA230_REG_POWER:
+ case INA230_REG_CURRENT:
+ case INA230_REG_DIE_ID:
+ 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 ina230_recv(I2CSlave *i2c)
+{
+ INA230State *s = INA230(i2c);
+ uint8_t byte;
+
+ if (s->rx_byte == 0) {
+ s->rx_word = ina230_read_reg(s, s->ptr);
+ byte = s->rx_word >> 8;
+ s->rx_byte = 1;
+ } else {
+ byte = s->rx_word & 0xff;
+ s->rx_byte = 0;
+ }
+
+ return byte;
+}
+
+static int ina230_send(I2CSlave *i2c, uint8_t data)
+{
+ INA230State *s = INA230(i2c);
+
+ if (s->len == 0) {
+ s->ptr = data;
+ } else if ((s->len & 1) == 1) {
+ s->wr_hi = data;
+ } else {
+ ina230_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
+ }
+ s->len++;
+
+ return 0;
+}
+
+static int ina230_event(I2CSlave *i2c, enum i2c_event event)
+{
+ INA230State *s = INA230(i2c);
+
+ s->len = 0;
+ s->rx_byte = 0;
+
+ return 0;
+}
+
+static void ina230_get_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value = s->shunt_nv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina230_set_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA230_SHUNT_MIN_NV || value > INA230_SHUNT_MAX_NV) {
+ error_setg(errp,
+ "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
+ s->description, value,
+ INA230_SHUNT_MIN_NV, INA230_SHUNT_MAX_NV);
+ return;
+ }
+
+ s->shunt_nv = (int32_t)value;
+ ina230_input_update(s);
+}
+
+static void ina230_get_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value = s->bus_uv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina230_set_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA230State *s = INA230(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < 0 || value > INA230_BUS_MAX_UV) {
+ error_setg(errp,
+ "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
+ s->description, value, INA230_BUS_MAX_UV);
+ return;
+ }
+
+ s->bus_uv = (uint32_t)value;
+ ina230_input_update(s);
+}
+
+static void ina230_reset_hold(Object *obj, ResetType type)
+{
+ INA230State *s = INA230(obj);
+
+ trace_ina230_reset(s->description, "hw");
+ ina230_reset_regs(s);
+}
+
+static int ina230_post_load(void *opaque, int version_id)
+{
+ INA230State *s = opaque;
+
+ ina230_refresh_alert(s);
+
+ return 0;
+}
+
+static const VMStateDescription ina230_vmstate = {
+ .name = "INA230",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ina230_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8(ptr, INA230State),
+ VMSTATE_UINT8(len, INA230State),
+ VMSTATE_UINT8(wr_hi, INA230State),
+ VMSTATE_UINT8(rx_byte, INA230State),
+ VMSTATE_UINT16(rx_word, INA230State),
+ VMSTATE_UINT16(config, INA230State),
+ VMSTATE_UINT16(calibration, INA230State),
+ VMSTATE_UINT16(mask_enable, INA230State),
+ VMSTATE_UINT16(alert_limit, INA230State),
+ VMSTATE_UINT16(shunt_reg, INA230State),
+ VMSTATE_UINT16(bus_reg, INA230State),
+ VMSTATE_UINT16(current_reg, INA230State),
+ VMSTATE_UINT16(power_reg, INA230State),
+ VMSTATE_INT32(shunt_nv, INA230State),
+ VMSTATE_UINT32(bus_uv, INA230State),
+ VMSTATE_BOOL(alert_latched, INA230State),
+ VMSTATE_I2C_SLAVE(parent_obj, INA230State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ina230_initfn(Object *obj)
+{
+ object_property_add(obj, "shunt-voltage", "int", ina230_get_shunt,
+ ina230_set_shunt, NULL, NULL);
+ object_property_set_description(obj, "shunt-voltage",
+ "Injected shunt (differential) voltage, "
+ "in nanovolts");
+ object_property_add(obj, "bus-voltage", "int", ina230_get_bus,
+ ina230_set_bus, NULL, NULL);
+ object_property_set_description(obj, "bus-voltage",
+ "Injected bus voltage, in microvolts");
+}
+
+static void ina230_realize(DeviceState *dev, Error **errp)
+{
+ INA230State *s = INA230(dev);
+
+ if (!s->description) {
+ s->description = g_strdup(object_get_typename(OBJECT(dev)));
+ }
+
+ qdev_init_gpio_out(dev, &s->alert, 1);
+}
+
+static const Property ina230_properties[] = {
+ DEFINE_PROP_STRING("description", INA230State, description),
+};
+
+static void ina230_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 = ina230_event;
+ ic->recv = ina230_recv;
+ ic->send = ina230_send;
+ dc->realize = ina230_realize;
+ rc->phases.hold = ina230_reset_hold;
+ dc->vmsd = &ina230_vmstate;
+ device_class_set_props(dc, ina230_properties);
+}
+
+static const TypeInfo ina230_types[] = {
+ {
+ .name = TYPE_INA230,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = ina230_initfn,
+ .instance_size = sizeof(INA230State),
+ .class_init = ina230_class_init,
+ },
+};
+
+DEFINE_TYPES(ina230_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index fe36c9ef91..b81f001fdf 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,4 +1,5 @@
system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
+system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.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 5a3630f7bb..f809b6b506 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -8,6 +8,13 @@ adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u va
adc128d818_irq(const char *id, bool level) "%s level %u"
adc128d818_reset(const char *id, const char *source) "%s %s"
+# ina230.c
+ina230_read(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina230_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, uint16_t power) "%s shunt 0x%04x bus 0x%04x current 0x%04x power 0x%04x"
+ina230_alert(const char *id, bool level) "%s level %u"
+ina230_reset(const char *id, const char *source) "%s %s"
+
# tmp105.c
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/include/hw/sensor/ina230.h b/include/hw/sensor/ina230.h
new file mode 100644
index 0000000000..4394727d47
--- /dev/null
+++ b/include/hw/sensor/ina230.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments INA230 current/voltage/power monitor with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_INA230_H
+#define HW_SENSOR_INA230_H
+
+#define TYPE_INA230 "ina230"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 33/50] tests/qtest: ina230: add current/voltage/power monitor test
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Introduce the INA230 qtest: the qgraph node and the register map. Cover
the power-on-reset defaults and Die ID, the software reset, and the
Configuration register write mask. The QMP voltage-injection helpers
arrive with the following commit that exercises them.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 116 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
new file mode 100644
index 0000000000..f7aefd2d97
--- /dev/null
+++ b/tests/qtest/ina230-test.c
@@ -0,0 +1,115 @@
+/*
+ * QTest testcase for the INA230 current/voltage/power monitor
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define INA230_TEST_ID "ina230-test"
+#define INA230_TEST_ADDR 0x40
+
+/* Register pointer addresses */
+#define REG_CONFIG 0x00
+#define REG_SHUNT 0x01
+#define REG_BUS 0x02
+#define REG_POWER 0x03
+#define REG_CURRENT 0x04
+#define REG_CALIBRATION 0x05
+#define REG_MASK_ENABLE 0x06
+#define REG_ALERT_LIMIT 0x07
+#define REG_DIE_ID 0xFF
+
+/* Configuration register bits */
+#define CONFIG_POR 0x4127
+#define CONFIG_RST 0x8000
+
+/* Mask/Enable register bits */
+#define ME_LEN 0x0001
+#define ME_APOL 0x0002
+#define ME_OVF 0x0004
+#define ME_CVRF 0x0008
+#define ME_AFF 0x0010
+#define ME_CNVR 0x0400
+#define ME_POL 0x0800
+#define ME_BUL 0x1000
+#define ME_BOL 0x2000
+#define ME_SUL 0x4000
+#define ME_SOL 0x8000
+
+/* Injected-input scaling (datasheet fixed LSBs) */
+#define SHUNT_LSB_NV 2500
+#define BUS_LSB_UV 1250
+
+#define DIE_ID_VAL 0x2310
+
+/* Power-on-reset default values and the Die ID */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIE_ID), ==, DIE_ID_VAL);
+}
+
+/* Software reset via the Configuration RST bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CALIBRATION, 0x1234);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x0500);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x1234);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0500);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
+}
+
+/* Every bit except the self-clearing RST is writable and reads back */
+static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ /* Reserved bits are not forced: the guest value is retained */
+ i2c_set16(dev, REG_CONFIG, 0x0007);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x0007);
+
+ i2c_set16(dev, REG_CONFIG, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
+}
+
+static void ina230_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" INA230_TEST_ID ",address=0x40"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { INA230_TEST_ADDR });
+
+ qos_node_create_driver("ina230", i2c_device_create);
+ qos_node_consumes("ina230", "i2c-bus", &opts);
+
+ qos_add_test("defaults", "ina230", test_defaults, NULL);
+ qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
+ qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+}
+libqos_init(ina230_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e23eb9bbca..e413a0cc75 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -330,6 +330,7 @@ qos_test_ss.add(
'e1000-test.c',
'eepro100-test.c',
'es1370-test.c',
+ 'ina230-test.c',
'lsm303dlhc-mag-test.c',
'isl_pmbus_vr-test.c',
'max34451-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 33/50] tests/qtest: ina230: add current/voltage/power monitor test
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Introduce the INA230 qtest: the qgraph node and the register map. Cover
the power-on-reset defaults and Die ID, the software reset, and the
Configuration register write mask. The QMP voltage-injection helpers
arrive with the following commit that exercises them.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 116 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
new file mode 100644
index 0000000000..f7aefd2d97
--- /dev/null
+++ b/tests/qtest/ina230-test.c
@@ -0,0 +1,115 @@
+/*
+ * QTest testcase for the INA230 current/voltage/power monitor
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define INA230_TEST_ID "ina230-test"
+#define INA230_TEST_ADDR 0x40
+
+/* Register pointer addresses */
+#define REG_CONFIG 0x00
+#define REG_SHUNT 0x01
+#define REG_BUS 0x02
+#define REG_POWER 0x03
+#define REG_CURRENT 0x04
+#define REG_CALIBRATION 0x05
+#define REG_MASK_ENABLE 0x06
+#define REG_ALERT_LIMIT 0x07
+#define REG_DIE_ID 0xFF
+
+/* Configuration register bits */
+#define CONFIG_POR 0x4127
+#define CONFIG_RST 0x8000
+
+/* Mask/Enable register bits */
+#define ME_LEN 0x0001
+#define ME_APOL 0x0002
+#define ME_OVF 0x0004
+#define ME_CVRF 0x0008
+#define ME_AFF 0x0010
+#define ME_CNVR 0x0400
+#define ME_POL 0x0800
+#define ME_BUL 0x1000
+#define ME_BOL 0x2000
+#define ME_SUL 0x4000
+#define ME_SOL 0x8000
+
+/* Injected-input scaling (datasheet fixed LSBs) */
+#define SHUNT_LSB_NV 2500
+#define BUS_LSB_UV 1250
+
+#define DIE_ID_VAL 0x2310
+
+/* Power-on-reset default values and the Die ID */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIE_ID), ==, DIE_ID_VAL);
+}
+
+/* Software reset via the Configuration RST bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CALIBRATION, 0x1234);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x0500);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x1234);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0500);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
+}
+
+/* Every bit except the self-clearing RST is writable and reads back */
+static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ /* Reserved bits are not forced: the guest value is retained */
+ i2c_set16(dev, REG_CONFIG, 0x0007);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x0007);
+
+ i2c_set16(dev, REG_CONFIG, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
+}
+
+static void ina230_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" INA230_TEST_ID ",address=0x40"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { INA230_TEST_ADDR });
+
+ qos_node_create_driver("ina230", i2c_device_create);
+ qos_node_consumes("ina230", "i2c-bus", &opts);
+
+ qos_add_test("defaults", "ina230", test_defaults, NULL);
+ qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
+ qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+}
+libqos_init(ina230_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e23eb9bbca..e413a0cc75 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -330,6 +330,7 @@ qos_test_ss.add(
'e1000-test.c',
'eepro100-test.c',
'es1370-test.c',
+ 'ina230-test.c',
'lsm303dlhc-mag-test.c',
'isl_pmbus_vr-test.c',
'max34451-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 33/50] tests/qtest: ina230: add current/voltage/power monitor test
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Introduce the INA230 qtest: the qgraph node and the register map. Cover
the power-on-reset defaults and Die ID, the software reset, and the
Configuration register write mask. The QMP voltage-injection helpers
arrive with the following commit that exercises them.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 116 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
new file mode 100644
index 0000000000..f7aefd2d97
--- /dev/null
+++ b/tests/qtest/ina230-test.c
@@ -0,0 +1,115 @@
+/*
+ * QTest testcase for the INA230 current/voltage/power monitor
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define INA230_TEST_ID "ina230-test"
+#define INA230_TEST_ADDR 0x40
+
+/* Register pointer addresses */
+#define REG_CONFIG 0x00
+#define REG_SHUNT 0x01
+#define REG_BUS 0x02
+#define REG_POWER 0x03
+#define REG_CURRENT 0x04
+#define REG_CALIBRATION 0x05
+#define REG_MASK_ENABLE 0x06
+#define REG_ALERT_LIMIT 0x07
+#define REG_DIE_ID 0xFF
+
+/* Configuration register bits */
+#define CONFIG_POR 0x4127
+#define CONFIG_RST 0x8000
+
+/* Mask/Enable register bits */
+#define ME_LEN 0x0001
+#define ME_APOL 0x0002
+#define ME_OVF 0x0004
+#define ME_CVRF 0x0008
+#define ME_AFF 0x0010
+#define ME_CNVR 0x0400
+#define ME_POL 0x0800
+#define ME_BUL 0x1000
+#define ME_BOL 0x2000
+#define ME_SUL 0x4000
+#define ME_SOL 0x8000
+
+/* Injected-input scaling (datasheet fixed LSBs) */
+#define SHUNT_LSB_NV 2500
+#define BUS_LSB_UV 1250
+
+#define DIE_ID_VAL 0x2310
+
+/* Power-on-reset default values and the Die ID */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIE_ID), ==, DIE_ID_VAL);
+}
+
+/* Software reset via the Configuration RST bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CALIBRATION, 0x1234);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x0500);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x1234);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0500);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
+}
+
+/* Every bit except the self-clearing RST is writable and reads back */
+static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ /* Reserved bits are not forced: the guest value is retained */
+ i2c_set16(dev, REG_CONFIG, 0x0007);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x0007);
+
+ i2c_set16(dev, REG_CONFIG, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
+}
+
+static void ina230_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" INA230_TEST_ID ",address=0x40"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { INA230_TEST_ADDR });
+
+ qos_node_create_driver("ina230", i2c_device_create);
+ qos_node_consumes("ina230", "i2c-bus", &opts);
+
+ qos_add_test("defaults", "ina230", test_defaults, NULL);
+ qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
+ qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+}
+libqos_init(ina230_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e23eb9bbca..e413a0cc75 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -330,6 +330,7 @@ qos_test_ss.add(
'e1000-test.c',
'eepro100-test.c',
'es1370-test.c',
+ 'ina230-test.c',
'lsm303dlhc-mag-test.c',
'isl_pmbus_vr-test.c',
'max34451-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 34/50] tests/qtest: ina230: cover voltage injection and power derivation
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt- and bus-voltage injection properties and the current
and power values the device derives from them through the Calibration
register, including the zero-calibration case and the clamp/overflow
edges.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index f7aefd2d97..b01f9445d1 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -49,6 +49,33 @@
#define DIE_ID_VAL 0x2310
+/* QMP helpers for injecting the physical inputs */
+
+static void qmp_ina230_set(const char *property, int value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %d } }",
+ INA230_TEST_ID, property, value);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+static int qmp_ina230_get(const char *property)
+{
+ QDict *resp;
+ int ret;
+
+ resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': %s } }",
+ INA230_TEST_ID, property);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_int(resp, "return");
+ qobject_unref(resp);
+ return ret;
+}
+
/* Power-on-reset default values and the Die ID */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -98,6 +125,96 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
}
+/* Shunt voltage register, including two's-complement negatives */
+static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+
+ qmp_ina230_set("shunt-voltage", -80000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8300);
+
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, -80000000);
+}
+
+/* Bus voltage register */
+static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
+
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+}
+
+/* Calibration-derived current, using the datasheet worked example */
+static void test_calibration_current(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+}
+
+/* Calibration-derived power, using the datasheet worked example */
+static void test_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x12B8);
+}
+
+/* With Calibration == 0 the Current and Power registers stay at zero */
+static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+}
+
+/* Clamping at full scale and the OVF (math overflow) flag */
+static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 81917500);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
+ qmp_ina230_set("shunt-voltage", -81920000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+
+ qmp_ina230_set("bus-voltage", 36000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_CALIBRATION, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -111,5 +228,12 @@ static void ina230_register_nodes(void)
qos_add_test("defaults", "ina230", test_defaults, NULL);
qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+ qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
+ qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
+ qos_add_test("calibration-current", "ina230", test_calibration_current,
+ NULL);
+ qos_add_test("power", "ina230", test_power, NULL);
+ qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
+ qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
}
libqos_init(ina230_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 34/50] tests/qtest: ina230: cover voltage injection and power derivation
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt- and bus-voltage injection properties and the current
and power values the device derives from them through the Calibration
register, including the zero-calibration case and the clamp/overflow
edges.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index f7aefd2d97..b01f9445d1 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -49,6 +49,33 @@
#define DIE_ID_VAL 0x2310
+/* QMP helpers for injecting the physical inputs */
+
+static void qmp_ina230_set(const char *property, int value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %d } }",
+ INA230_TEST_ID, property, value);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+static int qmp_ina230_get(const char *property)
+{
+ QDict *resp;
+ int ret;
+
+ resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': %s } }",
+ INA230_TEST_ID, property);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_int(resp, "return");
+ qobject_unref(resp);
+ return ret;
+}
+
/* Power-on-reset default values and the Die ID */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -98,6 +125,96 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
}
+/* Shunt voltage register, including two's-complement negatives */
+static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+
+ qmp_ina230_set("shunt-voltage", -80000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8300);
+
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, -80000000);
+}
+
+/* Bus voltage register */
+static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
+
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+}
+
+/* Calibration-derived current, using the datasheet worked example */
+static void test_calibration_current(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+}
+
+/* Calibration-derived power, using the datasheet worked example */
+static void test_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x12B8);
+}
+
+/* With Calibration == 0 the Current and Power registers stay at zero */
+static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+}
+
+/* Clamping at full scale and the OVF (math overflow) flag */
+static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 81917500);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
+ qmp_ina230_set("shunt-voltage", -81920000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+
+ qmp_ina230_set("bus-voltage", 36000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_CALIBRATION, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -111,5 +228,12 @@ static void ina230_register_nodes(void)
qos_add_test("defaults", "ina230", test_defaults, NULL);
qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+ qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
+ qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
+ qos_add_test("calibration-current", "ina230", test_calibration_current,
+ NULL);
+ qos_add_test("power", "ina230", test_power, NULL);
+ qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
+ qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
}
libqos_init(ina230_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 34/50] tests/qtest: ina230: cover voltage injection and power derivation
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt- and bus-voltage injection properties and the current
and power values the device derives from them through the Calibration
register, including the zero-calibration case and the clamp/overflow
edges.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index f7aefd2d97..b01f9445d1 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -49,6 +49,33 @@
#define DIE_ID_VAL 0x2310
+/* QMP helpers for injecting the physical inputs */
+
+static void qmp_ina230_set(const char *property, int value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %d } }",
+ INA230_TEST_ID, property, value);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+static int qmp_ina230_get(const char *property)
+{
+ QDict *resp;
+ int ret;
+
+ resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': %s } }",
+ INA230_TEST_ID, property);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_int(resp, "return");
+ qobject_unref(resp);
+ return ret;
+}
+
/* Power-on-reset default values and the Die ID */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -98,6 +125,96 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
}
+/* Shunt voltage register, including two's-complement negatives */
+static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+
+ qmp_ina230_set("shunt-voltage", -80000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8300);
+
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, -80000000);
+}
+
+/* Bus voltage register */
+static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
+
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+}
+
+/* Calibration-derived current, using the datasheet worked example */
+static void test_calibration_current(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+}
+
+/* Calibration-derived power, using the datasheet worked example */
+static void test_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x12B8);
+}
+
+/* With Calibration == 0 the Current and Power registers stay at zero */
+static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+}
+
+/* Clamping at full scale and the OVF (math overflow) flag */
+static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 81917500);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
+ qmp_ina230_set("shunt-voltage", -81920000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+
+ qmp_ina230_set("bus-voltage", 36000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_CALIBRATION, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -111,5 +228,12 @@ static void ina230_register_nodes(void)
qos_add_test("defaults", "ina230", test_defaults, NULL);
qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+ qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
+ qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
+ qos_add_test("calibration-current", "ina230", test_calibration_current,
+ NULL);
+ qos_add_test("power", "ina230", test_power, NULL);
+ qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
+ qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
}
libqos_init(ina230_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 35/50] tests/qtest: ina230: cover the ALERT limit functions and CVRF
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt/bus/power alert-limit functions, the alert latch
behaviour and the conversion-ready flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index b01f9445d1..1c55028fcb 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -215,6 +215,98 @@ static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
}
+/* Shunt over-limit alert (transparent mode) sets the AFF flag */
+static void test_alert_shunt_over(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
+
+ qmp_ina230_set("shunt-voltage", 2500000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ qmp_ina230_set("shunt-voltage", 2500000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Latch mode holds the alert until the Mask/Enable register is read */
+static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t me;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("shunt-voltage", 2500000);
+
+ me = i2c_get16(dev, REG_MASK_ENABLE);
+ g_assert_cmphex(me & ME_AFF, ==, ME_AFF);
+
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Bus over-limit alert */
+static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_BOL);
+
+ qmp_ina230_set("bus-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* Power over-limit alert */
+static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_POL);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Conversion Ready Flag: set on conversion, cleared by reading Mask/Enable */
+static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ /* A conversion (triggered by injection) sets CVRF */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, ME_CVRF);
+
+ /* The previous read cleared CVRF; no conversion since, so it stays clear */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -235,5 +327,10 @@ static void ina230_register_nodes(void)
qos_add_test("power", "ina230", test_power, NULL);
qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
+ qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
+ qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
+ qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
+ qos_add_test("alert-power", "ina230", test_alert_power, NULL);
+ qos_add_test("cvrf", "ina230", test_cvrf, NULL);
}
libqos_init(ina230_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 35/50] tests/qtest: ina230: cover the ALERT limit functions and CVRF
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt/bus/power alert-limit functions, the alert latch
behaviour and the conversion-ready flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index b01f9445d1..1c55028fcb 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -215,6 +215,98 @@ static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
}
+/* Shunt over-limit alert (transparent mode) sets the AFF flag */
+static void test_alert_shunt_over(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
+
+ qmp_ina230_set("shunt-voltage", 2500000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ qmp_ina230_set("shunt-voltage", 2500000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Latch mode holds the alert until the Mask/Enable register is read */
+static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t me;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("shunt-voltage", 2500000);
+
+ me = i2c_get16(dev, REG_MASK_ENABLE);
+ g_assert_cmphex(me & ME_AFF, ==, ME_AFF);
+
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Bus over-limit alert */
+static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_BOL);
+
+ qmp_ina230_set("bus-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* Power over-limit alert */
+static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_POL);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Conversion Ready Flag: set on conversion, cleared by reading Mask/Enable */
+static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ /* A conversion (triggered by injection) sets CVRF */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, ME_CVRF);
+
+ /* The previous read cleared CVRF; no conversion since, so it stays clear */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -235,5 +327,10 @@ static void ina230_register_nodes(void)
qos_add_test("power", "ina230", test_power, NULL);
qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
+ qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
+ qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
+ qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
+ qos_add_test("alert-power", "ina230", test_alert_power, NULL);
+ qos_add_test("cvrf", "ina230", test_cvrf, NULL);
}
libqos_init(ina230_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 35/50] tests/qtest: ina230: cover the ALERT limit functions and CVRF
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt/bus/power alert-limit functions, the alert latch
behaviour and the conversion-ready flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index b01f9445d1..1c55028fcb 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -215,6 +215,98 @@ static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
}
+/* Shunt over-limit alert (transparent mode) sets the AFF flag */
+static void test_alert_shunt_over(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
+
+ qmp_ina230_set("shunt-voltage", 2500000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ qmp_ina230_set("shunt-voltage", 2500000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Latch mode holds the alert until the Mask/Enable register is read */
+static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t me;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("shunt-voltage", 2500000);
+
+ me = i2c_get16(dev, REG_MASK_ENABLE);
+ g_assert_cmphex(me & ME_AFF, ==, ME_AFF);
+
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Bus over-limit alert */
+static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_BOL);
+
+ qmp_ina230_set("bus-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* Power over-limit alert */
+static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_POL);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/* Conversion Ready Flag: set on conversion, cleared by reading Mask/Enable */
+static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ /* A conversion (triggered by injection) sets CVRF */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, ME_CVRF);
+
+ /* The previous read cleared CVRF; no conversion since, so it stays clear */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -235,5 +327,10 @@ static void ina230_register_nodes(void)
qos_add_test("power", "ina230", test_power, NULL);
qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
+ qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
+ qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
+ qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
+ qos_add_test("alert-power", "ina230", test_alert_power, NULL);
+ qos_add_test("cvrf", "ina230", test_cvrf, NULL);
}
libqos_init(ina230_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 36/50] tests/qtest: ina230: cover the injection-property limits
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that the extremes of the shunt- and bus-voltage injection ranges
round-trip through the QOM property without truncation or wraparound,
and that values just outside are rejected rather than silently wrapped.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 1c55028fcb..36c899efee 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -47,6 +47,11 @@
#define SHUNT_LSB_NV 2500
#define BUS_LSB_UV 1250
+/* Injection-property limits (see ina230.c) */
+#define SHUNT_MIN_NV (-81920000)
+#define SHUNT_MAX_NV 81917500
+#define BUS_MAX_UV 36000000
+
#define DIE_ID_VAL 0x2310
/* QMP helpers for injecting the physical inputs */
@@ -76,6 +81,18 @@ static int qmp_ina230_get(const char *property)
return ret;
}
+/* A qom-set expected to be rejected (value outside the supported range) */
+static void qmp_ina230_set_fail(const char *property, int64_t value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %lld } }",
+ INA230_TEST_ID, property, (long long)value);
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
+
/* Power-on-reset default values and the Die ID */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -307,6 +324,46 @@ static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
}
+/* Shunt-voltage injection limits */
+static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", SHUNT_MAX_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MAX_NV);
+
+ qmp_ina230_set("shunt-voltage", SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+
+ qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
+ qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+}
+
+/* Bus-voltage injection limits. */
+static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("bus-voltage", 0);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
+
+ qmp_ina230_set("bus-voltage", BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
+ g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
+
+ qmp_ina230_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
+ qmp_ina230_set_fail("bus-voltage", -1);
+ g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -322,6 +379,8 @@ static void ina230_register_nodes(void)
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
+ qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
+ qos_add_test("bus-limits", "ina230", test_bus_limits, NULL);
qos_add_test("calibration-current", "ina230", test_calibration_current,
NULL);
qos_add_test("power", "ina230", test_power, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 36/50] tests/qtest: ina230: cover the injection-property limits
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that the extremes of the shunt- and bus-voltage injection ranges
round-trip through the QOM property without truncation or wraparound,
and that values just outside are rejected rather than silently wrapped.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 1c55028fcb..36c899efee 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -47,6 +47,11 @@
#define SHUNT_LSB_NV 2500
#define BUS_LSB_UV 1250
+/* Injection-property limits (see ina230.c) */
+#define SHUNT_MIN_NV (-81920000)
+#define SHUNT_MAX_NV 81917500
+#define BUS_MAX_UV 36000000
+
#define DIE_ID_VAL 0x2310
/* QMP helpers for injecting the physical inputs */
@@ -76,6 +81,18 @@ static int qmp_ina230_get(const char *property)
return ret;
}
+/* A qom-set expected to be rejected (value outside the supported range) */
+static void qmp_ina230_set_fail(const char *property, int64_t value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %lld } }",
+ INA230_TEST_ID, property, (long long)value);
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
+
/* Power-on-reset default values and the Die ID */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -307,6 +324,46 @@ static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
}
+/* Shunt-voltage injection limits */
+static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", SHUNT_MAX_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MAX_NV);
+
+ qmp_ina230_set("shunt-voltage", SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+
+ qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
+ qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+}
+
+/* Bus-voltage injection limits. */
+static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("bus-voltage", 0);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
+
+ qmp_ina230_set("bus-voltage", BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
+ g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
+
+ qmp_ina230_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
+ qmp_ina230_set_fail("bus-voltage", -1);
+ g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -322,6 +379,8 @@ static void ina230_register_nodes(void)
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
+ qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
+ qos_add_test("bus-limits", "ina230", test_bus_limits, NULL);
qos_add_test("calibration-current", "ina230", test_calibration_current,
NULL);
qos_add_test("power", "ina230", test_power, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 36/50] tests/qtest: ina230: cover the injection-property limits
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that the extremes of the shunt- and bus-voltage injection ranges
round-trip through the QOM property without truncation or wraparound,
and that values just outside are rejected rather than silently wrapped.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 1c55028fcb..36c899efee 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -47,6 +47,11 @@
#define SHUNT_LSB_NV 2500
#define BUS_LSB_UV 1250
+/* Injection-property limits (see ina230.c) */
+#define SHUNT_MIN_NV (-81920000)
+#define SHUNT_MAX_NV 81917500
+#define BUS_MAX_UV 36000000
+
#define DIE_ID_VAL 0x2310
/* QMP helpers for injecting the physical inputs */
@@ -76,6 +81,18 @@ static int qmp_ina230_get(const char *property)
return ret;
}
+/* A qom-set expected to be rejected (value outside the supported range) */
+static void qmp_ina230_set_fail(const char *property, int64_t value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %lld } }",
+ INA230_TEST_ID, property, (long long)value);
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
+
/* Power-on-reset default values and the Die ID */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -307,6 +324,46 @@ static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
}
+/* Shunt-voltage injection limits */
+static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("shunt-voltage", SHUNT_MAX_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MAX_NV);
+
+ qmp_ina230_set("shunt-voltage", SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+
+ qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
+ qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
+ g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
+}
+
+/* Bus-voltage injection limits. */
+static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina230_set("bus-voltage", 0);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
+
+ qmp_ina230_set("bus-voltage", BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
+ g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
+
+ qmp_ina230_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
+ qmp_ina230_set_fail("bus-voltage", -1);
+ g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
+}
+
static void ina230_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -322,6 +379,8 @@ static void ina230_register_nodes(void)
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
+ qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
+ qos_add_test("bus-limits", "ina230", test_bus_limits, NULL);
qos_add_test("calibration-current", "ina230", test_calibration_current,
NULL);
qos_add_test("power", "ina230", test_power, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 37/50] tests/qtest: ina230: cover operating modes and triggered conversions
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the MODE field: power-down retains the last results,
the shunt/bus channel-enable bits gate which registers track new inputs,
triggered modes convert once per Config write, and bus-only mode still
derives power from the retained current.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 94 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 36c899efee..f8b982686a 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -142,7 +142,97 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
}
-/* Shunt voltage register, including two's-complement negatives */
+/* Operating modes */
+static void test_modes(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const struct {
+ uint8_t mode;
+ uint16_t shunt, bus, current, power;
+ } cases[] = {
+ /* Power-down: no conversion, every register retains the baseline */
+ { 0x0, 0x1F40, 0x2570, 0x2710, 0x12B8 },
+ { 0x4, 0x1F40, 0x2570, 0x2710, 0x12B8 },
+ /* Shunt only (continuous): shunt and current track the probe */
+ { 0x5, 0x0FA0, 0x2570, 0x1388, 0x12B8 },
+ /* Shunt and bus (continuous): every register tracks the probe */
+ { 0x7, 0x0FA0, 0x12C0, 0x1388, 0x04B0 },
+ };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(cases); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ i2c_set16(dev, REG_CONFIG, cases[idx].mode);
+ qmp_ina230_set("shunt-voltage", 10000000);
+ qmp_ina230_set("bus-voltage", 6000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, cases[idx].shunt);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, cases[idx].bus);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, cases[idx].current);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, cases[idx].power);
+ }
+}
+
+/*
+ * Triggered modes perform exactly one conversion per trigger, where the
+ * trigger is a Configuration-register write. Injected input changes
+ * between triggers are ignored until the next write.
+ */
+static void test_triggered(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ i2c_set16(dev, REG_CONFIG, 0x03);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+
+ qmp_ina230_set("shunt-voltage", 10000000);
+ qmp_ina230_set("bus-voltage", 6000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+
+ i2c_set16(dev, REG_CONFIG, 0x03);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
+
+ i2c_set16(dev, REG_CONFIG, 0x07);
+ qmp_ina230_set("shunt-voltage", 20000000); /* A */
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+}
+
+/*
+ * In a bus-only mode the current register is retained, but the power register
+ * is still refreshed on each bus conversion from that retained current.
+ */
+static void test_bus_only_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+
+ i2c_set16(dev, REG_CONFIG, 0x06);
+ qmp_ina230_set("bus-voltage", 6000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0960);
+}
+
+/* Shunt voltage register */
static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
@@ -377,6 +467,9 @@ static void ina230_register_nodes(void)
qos_add_test("defaults", "ina230", test_defaults, NULL);
qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+ qos_add_test("modes", "ina230", test_modes, NULL);
+ qos_add_test("triggered", "ina230", test_triggered, NULL);
+ qos_add_test("bus-only-power", "ina230", test_bus_only_power, NULL);
qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 37/50] tests/qtest: ina230: cover operating modes and triggered conversions
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the MODE field: power-down retains the last results,
the shunt/bus channel-enable bits gate which registers track new inputs,
triggered modes convert once per Config write, and bus-only mode still
derives power from the retained current.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 94 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 36c899efee..f8b982686a 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -142,7 +142,97 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
}
-/* Shunt voltage register, including two's-complement negatives */
+/* Operating modes */
+static void test_modes(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const struct {
+ uint8_t mode;
+ uint16_t shunt, bus, current, power;
+ } cases[] = {
+ /* Power-down: no conversion, every register retains the baseline */
+ { 0x0, 0x1F40, 0x2570, 0x2710, 0x12B8 },
+ { 0x4, 0x1F40, 0x2570, 0x2710, 0x12B8 },
+ /* Shunt only (continuous): shunt and current track the probe */
+ { 0x5, 0x0FA0, 0x2570, 0x1388, 0x12B8 },
+ /* Shunt and bus (continuous): every register tracks the probe */
+ { 0x7, 0x0FA0, 0x12C0, 0x1388, 0x04B0 },
+ };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(cases); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ i2c_set16(dev, REG_CONFIG, cases[idx].mode);
+ qmp_ina230_set("shunt-voltage", 10000000);
+ qmp_ina230_set("bus-voltage", 6000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, cases[idx].shunt);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, cases[idx].bus);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, cases[idx].current);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, cases[idx].power);
+ }
+}
+
+/*
+ * Triggered modes perform exactly one conversion per trigger, where the
+ * trigger is a Configuration-register write. Injected input changes
+ * between triggers are ignored until the next write.
+ */
+static void test_triggered(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ i2c_set16(dev, REG_CONFIG, 0x03);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+
+ qmp_ina230_set("shunt-voltage", 10000000);
+ qmp_ina230_set("bus-voltage", 6000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+
+ i2c_set16(dev, REG_CONFIG, 0x03);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
+
+ i2c_set16(dev, REG_CONFIG, 0x07);
+ qmp_ina230_set("shunt-voltage", 20000000); /* A */
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+}
+
+/*
+ * In a bus-only mode the current register is retained, but the power register
+ * is still refreshed on each bus conversion from that retained current.
+ */
+static void test_bus_only_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+
+ i2c_set16(dev, REG_CONFIG, 0x06);
+ qmp_ina230_set("bus-voltage", 6000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0960);
+}
+
+/* Shunt voltage register */
static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
@@ -377,6 +467,9 @@ static void ina230_register_nodes(void)
qos_add_test("defaults", "ina230", test_defaults, NULL);
qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+ qos_add_test("modes", "ina230", test_modes, NULL);
+ qos_add_test("triggered", "ina230", test_triggered, NULL);
+ qos_add_test("bus-only-power", "ina230", test_bus_only_power, NULL);
qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 37/50] tests/qtest: ina230: cover operating modes and triggered conversions
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the MODE field: power-down retains the last results,
the shunt/bus channel-enable bits gate which registers track new inputs,
triggered modes convert once per Config write, and bus-only mode still
derives power from the retained current.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 94 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 36c899efee..f8b982686a 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -142,7 +142,97 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
}
-/* Shunt voltage register, including two's-complement negatives */
+/* Operating modes */
+static void test_modes(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const struct {
+ uint8_t mode;
+ uint16_t shunt, bus, current, power;
+ } cases[] = {
+ /* Power-down: no conversion, every register retains the baseline */
+ { 0x0, 0x1F40, 0x2570, 0x2710, 0x12B8 },
+ { 0x4, 0x1F40, 0x2570, 0x2710, 0x12B8 },
+ /* Shunt only (continuous): shunt and current track the probe */
+ { 0x5, 0x0FA0, 0x2570, 0x1388, 0x12B8 },
+ /* Shunt and bus (continuous): every register tracks the probe */
+ { 0x7, 0x0FA0, 0x12C0, 0x1388, 0x04B0 },
+ };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(cases); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ i2c_set16(dev, REG_CONFIG, cases[idx].mode);
+ qmp_ina230_set("shunt-voltage", 10000000);
+ qmp_ina230_set("bus-voltage", 6000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, cases[idx].shunt);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, cases[idx].bus);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, cases[idx].current);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, cases[idx].power);
+ }
+}
+
+/*
+ * Triggered modes perform exactly one conversion per trigger, where the
+ * trigger is a Configuration-register write. Injected input changes
+ * between triggers are ignored until the next write.
+ */
+static void test_triggered(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+
+ i2c_set16(dev, REG_CONFIG, 0x03);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+
+ qmp_ina230_set("shunt-voltage", 10000000);
+ qmp_ina230_set("bus-voltage", 6000000);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
+
+ i2c_set16(dev, REG_CONFIG, 0x03);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
+
+ i2c_set16(dev, REG_CONFIG, 0x07);
+ qmp_ina230_set("shunt-voltage", 20000000); /* A */
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
+}
+
+/*
+ * In a bus-only mode the current register is retained, but the power register
+ * is still refreshed on each bus conversion from that retained current.
+ */
+static void test_bus_only_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+
+ i2c_set16(dev, REG_CONFIG, 0x06);
+ qmp_ina230_set("bus-voltage", 6000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0960);
+}
+
+/* Shunt voltage register */
static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
@@ -377,6 +467,9 @@ static void ina230_register_nodes(void)
qos_add_test("defaults", "ina230", test_defaults, NULL);
qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
+ qos_add_test("modes", "ina230", test_modes, NULL);
+ qos_add_test("triggered", "ina230", test_triggered, NULL);
+ qos_add_test("bus-only-power", "ina230", test_bus_only_power, NULL);
qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 38/50] tests/qtest: ina230: cover ALERT latch persistence
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify what releases a latched alert and what can bring it back: a
Mask/Enable read and a Configuration write both clear it, and only a
conversion that re-samples the fault on a channel it converted asserts
it again.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index f8b982686a..591927e551 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -364,6 +364,88 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
}
/* Bus over-limit alert */
+static void test_alert_latch_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+
+ /* First read shows the latched AFF and clears the latch */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ /* Fault still present but no new conversion: AFF stays cleared */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* A Configuration write releases the latch as a Mask/Enable read does. */
+static void test_alert_latch_config(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ /* Latch a fault, then bring the input back below the limit */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("shunt-voltage", 2500000);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ /* With the fault present, the reconversion latches it again */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* The releasing write need not reconvert: a power-down leaves it clear. */
+static void test_alert_latch_shutdown(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+
+ i2c_set16(dev, REG_CONFIG, 0x0000); /* power-down */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/*
+ * A conversion only re-samples the alert of a channel it converted: the bus
+ * register a shunt-only conversion left stale must not re-latch.
+ */
+static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_BOL | ME_LEN);
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ /* Shunt-only mode: the bus register keeps its over-limit value */
+ i2c_set16(dev, REG_CONFIG, 0x0005);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
@@ -481,6 +563,13 @@ static void ina230_register_nodes(void)
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
+ qos_add_test("alert-latch-persist", "ina230", test_alert_latch_persist,
+ NULL);
+ qos_add_test("alert-latch-config", "ina230", test_alert_latch_config,
+ NULL);
+ qos_add_test("alert-latch-shutdown", "ina230", test_alert_latch_shutdown,
+ NULL);
+ qos_add_test("alert-channel", "ina230", test_alert_channel, NULL);
qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
qos_add_test("alert-power", "ina230", test_alert_power, NULL);
qos_add_test("cvrf", "ina230", test_cvrf, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 38/50] tests/qtest: ina230: cover ALERT latch persistence
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify what releases a latched alert and what can bring it back: a
Mask/Enable read and a Configuration write both clear it, and only a
conversion that re-samples the fault on a channel it converted asserts
it again.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index f8b982686a..591927e551 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -364,6 +364,88 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
}
/* Bus over-limit alert */
+static void test_alert_latch_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+
+ /* First read shows the latched AFF and clears the latch */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ /* Fault still present but no new conversion: AFF stays cleared */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* A Configuration write releases the latch as a Mask/Enable read does. */
+static void test_alert_latch_config(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ /* Latch a fault, then bring the input back below the limit */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("shunt-voltage", 2500000);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ /* With the fault present, the reconversion latches it again */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* The releasing write need not reconvert: a power-down leaves it clear. */
+static void test_alert_latch_shutdown(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+
+ i2c_set16(dev, REG_CONFIG, 0x0000); /* power-down */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/*
+ * A conversion only re-samples the alert of a channel it converted: the bus
+ * register a shunt-only conversion left stale must not re-latch.
+ */
+static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_BOL | ME_LEN);
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ /* Shunt-only mode: the bus register keeps its over-limit value */
+ i2c_set16(dev, REG_CONFIG, 0x0005);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
@@ -481,6 +563,13 @@ static void ina230_register_nodes(void)
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
+ qos_add_test("alert-latch-persist", "ina230", test_alert_latch_persist,
+ NULL);
+ qos_add_test("alert-latch-config", "ina230", test_alert_latch_config,
+ NULL);
+ qos_add_test("alert-latch-shutdown", "ina230", test_alert_latch_shutdown,
+ NULL);
+ qos_add_test("alert-channel", "ina230", test_alert_channel, NULL);
qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
qos_add_test("alert-power", "ina230", test_alert_power, NULL);
qos_add_test("cvrf", "ina230", test_cvrf, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 38/50] tests/qtest: ina230: cover ALERT latch persistence
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify what releases a latched alert and what can bring it back: a
Mask/Enable read and a Configuration write both clear it, and only a
conversion that re-samples the fault on a channel it converted asserts
it again.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index f8b982686a..591927e551 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -364,6 +364,88 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
}
/* Bus over-limit alert */
+static void test_alert_latch_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+
+ /* First read shows the latched AFF and clears the latch */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ /* Fault still present but no new conversion: AFF stays cleared */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* A Configuration write releases the latch as a Mask/Enable read does. */
+static void test_alert_latch_config(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ /* Latch a fault, then bring the input back below the limit */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("shunt-voltage", 2500000);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+
+ /* With the fault present, the reconversion latches it again */
+ qmp_ina230_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+}
+
+/* The releasing write need not reconvert: a power-down leaves it clear. */
+static void test_alert_latch_shutdown(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
+
+ qmp_ina230_set("shunt-voltage", 20000000);
+
+ i2c_set16(dev, REG_CONFIG, 0x0000); /* power-down */
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
+/*
+ * A conversion only re-samples the alert of a channel it converted: the bus
+ * register a shunt-only conversion left stale must not re-latch.
+ */
+static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
+ i2c_set16(dev, REG_MASK_ENABLE, ME_BOL | ME_LEN);
+
+ qmp_ina230_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
+
+ /* Shunt-only mode: the bus register keeps its over-limit value */
+ i2c_set16(dev, REG_CONFIG, 0x0005);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
+ g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
+}
+
static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
@@ -481,6 +563,13 @@ static void ina230_register_nodes(void)
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
+ qos_add_test("alert-latch-persist", "ina230", test_alert_latch_persist,
+ NULL);
+ qos_add_test("alert-latch-config", "ina230", test_alert_latch_config,
+ NULL);
+ qos_add_test("alert-latch-shutdown", "ina230", test_alert_latch_shutdown,
+ NULL);
+ qos_add_test("alert-channel", "ina230", test_alert_channel, NULL);
qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
qos_add_test("alert-power", "ina230", test_alert_power, NULL);
qos_add_test("cvrf", "ina230", test_cvrf, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 39/50] tests/qtest: ina230: cover a cleared calibration per channel
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that without a calibration value the current and power
registers read zero, whichever channels the mode converts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 591927e551..5749b0bd46 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -301,6 +301,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
}
+/* Clearing the Calibration zeroes both derived registers in any mode */
+static void test_zero_cal_channels(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const uint8_t modes[] = { 0x5, 0x6, 0x7 };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), !=, 0x0000);
+
+ i2c_set16(dev, REG_CONFIG, modes[idx]);
+ i2c_set16(dev, REG_CALIBRATION, 0x0000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+ }
+}
+
/* Clamping at full scale and the OVF (math overflow) flag */
static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -560,6 +584,7 @@ static void ina230_register_nodes(void)
NULL);
qos_add_test("power", "ina230", test_power, NULL);
qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
+ qos_add_test("zero-cal-channels", "ina230", test_zero_cal_channels, NULL);
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 39/50] tests/qtest: ina230: cover a cleared calibration per channel
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that without a calibration value the current and power
registers read zero, whichever channels the mode converts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 591927e551..5749b0bd46 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -301,6 +301,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
}
+/* Clearing the Calibration zeroes both derived registers in any mode */
+static void test_zero_cal_channels(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const uint8_t modes[] = { 0x5, 0x6, 0x7 };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), !=, 0x0000);
+
+ i2c_set16(dev, REG_CONFIG, modes[idx]);
+ i2c_set16(dev, REG_CALIBRATION, 0x0000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+ }
+}
+
/* Clamping at full scale and the OVF (math overflow) flag */
static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -560,6 +584,7 @@ static void ina230_register_nodes(void)
NULL);
qos_add_test("power", "ina230", test_power, NULL);
qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
+ qos_add_test("zero-cal-channels", "ina230", test_zero_cal_channels, NULL);
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 39/50] tests/qtest: ina230: cover a cleared calibration per channel
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that without a calibration value the current and power
registers read zero, whichever channels the mode converts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina230-test.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
index 591927e551..5749b0bd46 100644
--- a/tests/qtest/ina230-test.c
+++ b/tests/qtest/ina230-test.c
@@ -301,6 +301,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
}
+/* Clearing the Calibration zeroes both derived registers in any mode */
+static void test_zero_cal_channels(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const uint8_t modes[] = { 0x5, 0x6, 0x7 };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_CALIBRATION, 0x0A00);
+ qmp_ina230_set("shunt-voltage", 20000000);
+ qmp_ina230_set("bus-voltage", 11980000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), !=, 0x0000);
+
+ i2c_set16(dev, REG_CONFIG, modes[idx]);
+ i2c_set16(dev, REG_CALIBRATION, 0x0000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
+ }
+}
+
/* Clamping at full scale and the OVF (math overflow) flag */
static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -560,6 +584,7 @@ static void ina230_register_nodes(void)
NULL);
qos_add_test("power", "ina230", test_power, NULL);
qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
+ qos_add_test("zero-cal-channels", "ina230", test_zero_cal_channels, NULL);
qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 40/50] hw/sensor: ina238: add current/voltage/power monitor
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a model for the Texas Instruments INA238, an 85V, 16-bit I2C
current-shunt, power and temperature monitor.
The device measures a differential shunt voltage, a bus voltage and its
own die temperature; the current and power registers are not measured
directly but derived by the device from the shunt voltage and the
guest-programmed Shunt Calibration register. The alert is modelled.
Shunt and bus voltages and the die temperature can be injected through
the "shunt-voltage", "bus-voltage" and "die-temperature" QOM properties.
Note: Conversion is instantaneous: the averaging and conversion-time
fields are stored but have no timing effect.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/ina238.c | 802 +++++++++++++++++++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 7 +
include/hw/sensor/ina238.h | 14 +
6 files changed, 829 insertions(+)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 116d1c64ac..3abe249ed5 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -540,6 +540,7 @@ config ASPEED_SOC
select TMP421
select EMC141X
select INA230
+ select INA238
select OR_IRQ
select UNIMP
select LED
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index da519159ac..9b7c76fcf5 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -6,6 +6,10 @@ config INA230
bool
depends on I2C
+config INA238
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/sensor/ina238.c b/hw/sensor/ina238.c
new file mode 100644
index 0000000000..e4033771b0
--- /dev/null
+++ b/hw/sensor/ina238.c
@@ -0,0 +1,802 @@
+/*
+ * Texas Instruments INA238 85V, 16-bit current/voltage/power monitor
+ * with an I2C interface.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The INA238 measures a differential shunt voltage, a bus voltage and its own
+ * die temperature. Current and power are not measured directly: they are
+ * derived by the device from the measured shunt voltage and the programmable
+ * Shunt Calibration register. The INA238 exposes six independent limit
+ * registers, all monitored simultaneously (multi-alert), a die-temperature
+ * sensor, a 24-bit power register and an ADCRANGE-dependent shunt LSB.
+ *
+ * Limitations (not modelled):
+ * - Conversion timing is instantaneous. Conversions run synchronously when an
+ * input, the calibration, the ADC range or the ADC configuration changes; the
+ * ADC conversion times (VBUSCT/VSHCT/VTCT), the averaging count (AVG) and the
+ * CONVDLY start delay are not modelled, and continuous mode does not
+ * re-convert on a timer. A read always returns the value derived from the
+ * current inputs and CNVRF is set once a conversion has run.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/ina238.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define INA238_REG_CONFIG 0x00
+#define INA238_REG_ADC_CONFIG 0x01
+#define INA238_REG_SHUNT_CAL 0x02
+#define INA238_REG_VSHUNT 0x04
+#define INA238_REG_VBUS 0x05
+#define INA238_REG_DIETEMP 0x06
+#define INA238_REG_CURRENT 0x07
+#define INA238_REG_POWER 0x08
+#define INA238_REG_DIAG_ALRT 0x0B
+#define INA238_REG_SOVL 0x0C
+#define INA238_REG_SUVL 0x0D
+#define INA238_REG_BOVL 0x0E
+#define INA238_REG_BUVL 0x0F
+#define INA238_REG_TEMP_LIMIT 0x10
+#define INA238_REG_PWR_LIMIT 0x11
+#define INA238_REG_MANUFACTURER_ID 0x3E
+#define INA238_REG_DEVICE_ID 0x3F
+
+FIELD(INA238_CONFIG, ADCRANGE, 4, 1)
+FIELD(INA238_CONFIG, CONVDLY, 6, 8)
+FIELD(INA238_CONFIG, RST, 15, 1)
+
+#define INA238_CONFIG_POR 0x0000
+#define INA238_CONFIG_WMASK (R_INA238_CONFIG_ADCRANGE_MASK | \
+ R_INA238_CONFIG_CONVDLY_MASK)
+
+FIELD(INA238_ADC_CONFIG, AVG, 0, 3)
+FIELD(INA238_ADC_CONFIG, VTCT, 3, 3)
+FIELD(INA238_ADC_CONFIG, VSHCT, 6, 3)
+FIELD(INA238_ADC_CONFIG, VBUSCT, 9, 3)
+FIELD(INA238_ADC_CONFIG, MODE, 12, 4)
+
+#define INA238_ADC_CONFIG_POR 0xFB68
+
+#define INA238_SHUNT_CAL_POR 0x1000
+#define INA238_SHUNT_CAL_MASK 0x7FFF /* bit 15 reserved */
+
+FIELD(INA238_DIAG_ALRT, MEMSTAT, 0, 1) /* memory checksum ok (always 1) */
+FIELD(INA238_DIAG_ALRT, CNVRF, 1, 1) /* conversion ready flag */
+FIELD(INA238_DIAG_ALRT, POL, 2, 1) /* power over-limit */
+FIELD(INA238_DIAG_ALRT, BUSUL, 3, 1) /* bus under-limit */
+FIELD(INA238_DIAG_ALRT, BUSOL, 4, 1) /* bus over-limit */
+FIELD(INA238_DIAG_ALRT, SHNTUL, 5, 1) /* shunt under-limit */
+FIELD(INA238_DIAG_ALRT, SHNTOL, 6, 1) /* shunt over-limit */
+FIELD(INA238_DIAG_ALRT, TMPOL, 7, 1) /* over temperature */
+FIELD(INA238_DIAG_ALRT, MATHOF, 9, 1) /* arithmetic overflow */
+FIELD(INA238_DIAG_ALRT, APOL, 12, 1) /* alert polarity */
+FIELD(INA238_DIAG_ALRT, SLOWALERT, 13, 1) /* alert on averaged value */
+FIELD(INA238_DIAG_ALRT, CNVR, 14, 1) /* conversion-ready on ALERT pin */
+FIELD(INA238_DIAG_ALRT, ALATCH, 15, 1) /* alert latch enable */
+
+#define INA238_DIAG_ALRT_POR 0x0001 /* MEMSTAT = 1 */
+
+/* Control (writable) bits */
+#define INA238_DIAG_CTRL_MASK (R_INA238_DIAG_ALRT_ALATCH_MASK | \
+ R_INA238_DIAG_ALRT_CNVR_MASK | \
+ R_INA238_DIAG_ALRT_SLOWALERT_MASK | \
+ R_INA238_DIAG_ALRT_APOL_MASK)
+/* Read-only status bits (preserved across a control-bit write) */
+#define INA238_DIAG_STATUS_MASK (R_INA238_DIAG_ALRT_MATHOF_MASK | \
+ R_INA238_DIAG_ALRT_TMPOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTUL_MASK | \
+ R_INA238_DIAG_ALRT_BUSOL_MASK | \
+ R_INA238_DIAG_ALRT_BUSUL_MASK | \
+ R_INA238_DIAG_ALRT_POL_MASK | \
+ R_INA238_DIAG_ALRT_CNVRF_MASK | \
+ R_INA238_DIAG_ALRT_MEMSTAT_MASK)
+/* Threshold flags cleared when the DIAG_ALRT register is read */
+#define INA238_DIAG_LATCH_MASK (R_INA238_DIAG_ALRT_TMPOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTUL_MASK | \
+ R_INA238_DIAG_ALRT_BUSOL_MASK | \
+ R_INA238_DIAG_ALRT_BUSUL_MASK | \
+ R_INA238_DIAG_ALRT_POL_MASK)
+
+#define INA238_SOVL_POR 0x7FFF
+#define INA238_SUVL_POR 0x8000
+#define INA238_BOVL_POR 0x7FFF
+#define INA238_BUVL_POR 0x0000
+/*
+ * The datasheet is self-inconsistent on the TEMP_LIMIT reset value.
+ * Writes mask with INA238_TEMP_LIMIT_MASK and the alert comparison shifts both
+ * operands >>4.
+ */
+#define INA238_TEMP_LIMIT_POR 0x7FF0
+#define INA238_PWR_LIMIT_POR 0xFFFF
+
+#define INA238_BUS_LIMIT_MASK 0x7FFF /* BOVL/BUVL are 15-bit, positive */
+#define INA238_TEMP_LIMIT_MASK 0xFFF0 /* TOL occupies bits [15:4] */
+
+#define INA238_MANUFACTURER_ID_VAL 0x5449 /* "TI" */
+#define INA238_DEVICE_ID_VAL 0x2381 /* DIEID 0x238, REV 0x1 */
+
+/* Shunt voltage LSB depends on ADCRANGE; bus and temperature are fixed */
+#define INA238_SHUNT_LSB_NV_R0 5000 /* 5 uV per LSB (ADCRANGE = 0) */
+#define INA238_SHUNT_LSB_NV_R1 1250 /* 1.25 uV per LSB (ADCRANGE = 1) */
+#define INA238_BUS_LSB_UV 3125 /* 3.125 mV per LSB */
+#define INA238_TEMP_LSB_MC 125 /* 125 m-degC per LSB */
+
+/*
+ * current = shunt_reg * K / SHUNT_CAL. K derives from the fixed 819.2e6
+ * internal constant and the ADCRANGE=0 shunt LSB: 5e-6 * 819.2e6 = 4096.
+ * power = current * bus / 64 (0.2 / 3.125mV expressed in the register domain).
+ */
+#define INA238_CURRENT_K 4096
+#define INA238_POWER_DIV 64
+#define INA238_POWER_MAX 0xFFFFFF /* POWER is a 24-bit register */
+
+/* Injection property ranges */
+#define INA238_SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
+#define INA238_SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
+#define INA238_BUS_MAX_UV 85000000 /* 85 V full common-mode range */
+#define INA238_TEMP_MIN_MC (-40000) /* package limited */
+#define INA238_TEMP_MAX_MC 125000
+
+OBJECT_DECLARE_SIMPLE_TYPE(INA238State, INA238)
+
+struct INA238State {
+ I2CSlave parent_obj;
+
+ qemu_irq alert; /* ALERT open-drain output */
+
+ /* I2C transfer state */
+ uint8_t ptr; /* register pointer */
+ uint8_t len; /* bytes received in current transfer */
+ uint8_t wr_hi; /* pending write MSB */
+ uint8_t rx_byte; /* index of the next byte to return within rx_val */
+ uint8_t rx_width; /* width in bytes of the register being read (2 or 3) */
+ uint32_t rx_val; /* value latched at the start of a read */
+
+ /* Register file */
+ uint16_t config;
+ uint16_t adc_config;
+ uint16_t shunt_cal;
+ uint16_t diag_alrt;
+ uint16_t sovl;
+ uint16_t suvl;
+ uint16_t bovl;
+ uint16_t buvl;
+ uint16_t temp_limit;
+ uint16_t pwr_limit;
+
+ /* Derived measurement registers */
+ uint16_t shunt_reg;
+ uint16_t bus_reg;
+ uint16_t dietemp_reg;
+ uint16_t current_reg;
+ uint32_t power_reg; /* 24-bit */
+
+ /* Injected physical inputs */
+ int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
+ uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
+ int32_t temp_mc; /* die temperature, millidegrees Celsius (signed) */
+
+ char *description;
+};
+
+/*
+ * Update the _field_ limit flag in _diag_ from the current comparison _cond_.
+ * In latch mode (_latch_) a flag that is already set stays set until the
+ * register is read; in transparent mode it tracks _cond_ directly. A flag
+ * whose measurement register was not refreshed (_fresh_) keeps its value.
+ */
+#define INA238_UPD_FLAG(_diag_, _latch_, _fresh_, _field_, _cond_) \
+ do { \
+ if (_fresh_) { \
+ uint16_t _old_ = FIELD_EX16(_diag_, INA238_DIAG_ALRT, _field_); \
+ uint16_t _new_ = (uint16_t)((_cond_) || ((_latch_) && _old_)); \
+ (_diag_) = FIELD_DP16(_diag_, INA238_DIAG_ALRT, _field_, _new_); \
+ } \
+ } while (0)
+
+static void ina238_refresh_alert(INA238State *s)
+{
+ uint16_t diag = s->diag_alrt;
+ bool active;
+ bool cnvr;
+ int level;
+
+ /* MATHOF is a diagnostic; it does not drive the ALERT pin. */
+ active = FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTUL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, BUSOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, BUSUL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, TMPOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, POL);
+
+ cnvr = FIELD_EX16(diag, INA238_DIAG_ALRT, CNVR) &&
+ FIELD_EX16(diag, INA238_DIAG_ALRT, CNVRF);
+
+ active = active || cnvr;
+ /* APOL: 0 = active-low (default), 1 = active-high */
+ level = FIELD_EX16(diag, INA238_DIAG_ALRT, APOL) ? active : !active;
+
+ trace_ina238_alert(s->description, level);
+ qemu_set_irq(s->alert, level);
+}
+
+/*
+ * @shunt_fresh, @bus_fresh and @temp_fresh tell which measurement registers
+ * hold a fresh value: after a partial conversion the flags fed by the other
+ * channels must not be re-evaluated against their stale registers.
+ */
+static void ina238_update_alert(INA238State *s, bool shunt_fresh,
+ bool bus_fresh, bool temp_fresh,
+ bool power_fresh)
+{
+ bool latch = FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH);
+ int16_t shunt = (int16_t)s->shunt_reg;
+ int16_t temp = (int16_t)s->dietemp_reg >> 4; /* sign-extend [15:4] */
+ int16_t temp_lim = (int16_t)s->temp_limit >> 4;
+ uint16_t diag = s->diag_alrt;
+
+ INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTOL, shunt > (int16_t)s->sovl);
+ INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTUL, shunt < (int16_t)s->suvl);
+ INA238_UPD_FLAG(diag, latch, bus_fresh, BUSOL,
+ s->bus_reg > (s->bovl & INA238_BUS_LIMIT_MASK));
+ INA238_UPD_FLAG(diag, latch, bus_fresh, BUSUL,
+ s->bus_reg < (s->buvl & INA238_BUS_LIMIT_MASK));
+ INA238_UPD_FLAG(diag, latch, temp_fresh, TMPOL, temp > temp_lim);
+ INA238_UPD_FLAG(diag, latch, power_fresh, POL,
+ s->power_reg > ((uint32_t)s->pwr_limit << 8));
+
+ s->diag_alrt = diag;
+
+ ina238_refresh_alert(s);
+}
+
+static void ina238_convert(INA238State *s)
+{
+ uint8_t mode = FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE);
+ bool bus_en = mode & 0x1;
+ bool shunt_en = mode & 0x2;
+ bool temp_en = mode & 0x4;
+ bool adcrange = FIELD_EX16(s->config, INA238_CONFIG, ADCRANGE);
+ int shunt_lsb = adcrange ? INA238_SHUNT_LSB_NV_R1 : INA238_SHUNT_LSB_NV_R0;
+ int64_t sr, br, tr, cur, pwr;
+ uint16_t mathof = 0;
+ bool power_fresh = bus_en;
+
+ if (!bus_en && !shunt_en && !temp_en) {
+ return;
+ }
+
+ if (shunt_en) {
+ sr = s->shunt_nv / shunt_lsb;
+ sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
+ s->shunt_reg = (uint16_t)(int16_t)sr;
+
+ if (s->shunt_cal == 0) {
+ cur = 0;
+ } else {
+ cur = (int64_t)(int16_t)s->shunt_reg * INA238_CURRENT_K /
+ s->shunt_cal;
+ }
+ if (cur > INT16_MAX || cur < INT16_MIN) {
+ mathof = 1;
+ cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
+ }
+ s->current_reg = (uint16_t)(int16_t)cur;
+ }
+
+ if (bus_en) {
+ br = s->bus_uv / INA238_BUS_LSB_UV;
+ br = MAX(0, MIN(INT16_MAX, br));
+ s->bus_reg = (uint16_t)br;
+ }
+
+ if (temp_en) {
+ /* Die temperature: 12-bit signed value stored in bits [15:4] */
+ tr = s->temp_mc / INA238_TEMP_LSB_MC;
+ tr = MAX(-2048, MIN(2047, tr));
+ s->dietemp_reg = ((uint16_t)(int16_t)tr << 4) & INA238_TEMP_LIMIT_MASK;
+ }
+
+ /*
+ * In bus-only mode the current register is retained from the last shunt
+ * conversion rather than measured anew, but the power register is still
+ * refreshed.
+ */
+ if (bus_en) {
+ cur = (int16_t)s->current_reg;
+ if (cur < 0) {
+ cur = -cur;
+ }
+ pwr = cur * (int64_t)s->bus_reg / INA238_POWER_DIV;
+ if (pwr > INA238_POWER_MAX) {
+ mathof = 1;
+ pwr = INA238_POWER_MAX;
+ }
+ s->power_reg = (uint32_t)pwr;
+ }
+
+ /* Without a calibration the device reports neither current nor power. */
+ if (s->shunt_cal == 0) {
+ s->current_reg = 0;
+ s->power_reg = 0;
+ power_fresh = true;
+ }
+
+ s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, MATHOF, mathof);
+ s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, CNVRF, 1);
+
+ trace_ina238_convert(s->description, s->shunt_reg, s->bus_reg,
+ s->dietemp_reg, s->current_reg, s->power_reg);
+
+ ina238_update_alert(s, shunt_en, bus_en, temp_en, power_fresh);
+}
+
+/*
+ * A physical input, the calibration or the ADC range changed.
+ * - in continuous mode the device keeps converting and immediately tracks
+ * the new value
+ * - in a triggered mode it performs exactly one conversion per trigger
+ */
+static void ina238_input_update(INA238State *s)
+{
+ if (FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE) & 0x8) {
+ ina238_convert(s);
+ }
+}
+
+static void ina238_reset_regs(INA238State *s)
+{
+ s->config = INA238_CONFIG_POR;
+ s->adc_config = INA238_ADC_CONFIG_POR;
+ s->shunt_cal = INA238_SHUNT_CAL_POR;
+ s->diag_alrt = INA238_DIAG_ALRT_POR;
+ s->sovl = INA238_SOVL_POR;
+ s->suvl = INA238_SUVL_POR;
+ s->bovl = INA238_BOVL_POR;
+ s->buvl = INA238_BUVL_POR;
+ s->temp_limit = INA238_TEMP_LIMIT_POR;
+ s->pwr_limit = INA238_PWR_LIMIT_POR;
+
+ s->ptr = 0;
+ s->len = 0;
+ s->wr_hi = 0;
+ s->rx_byte = 0;
+ s->rx_width = 0;
+ s->rx_val = 0;
+
+ /*
+ * The measurement registers power up at zero and stay zero until the next
+ * conversion. The injected physical inputs are retained, but no conversion
+ * is performed here, so a software reset does not resurrect a previous
+ * measurement.
+ */
+ s->shunt_reg = 0;
+ s->bus_reg = 0;
+ s->dietemp_reg = 0;
+ s->current_reg = 0;
+ s->power_reg = 0;
+
+ ina238_update_alert(s, true, true, true, true);
+}
+
+static uint32_t ina238_read_reg(INA238State *s, uint8_t reg)
+{
+ uint32_t val;
+
+ switch (reg) {
+ case INA238_REG_CONFIG:
+ val = s->config;
+ break;
+ case INA238_REG_ADC_CONFIG:
+ val = s->adc_config;
+ break;
+ case INA238_REG_SHUNT_CAL:
+ val = s->shunt_cal;
+ break;
+ case INA238_REG_VSHUNT:
+ val = s->shunt_reg;
+ break;
+ case INA238_REG_VBUS:
+ val = s->bus_reg;
+ break;
+ case INA238_REG_DIETEMP:
+ val = s->dietemp_reg;
+ break;
+ case INA238_REG_CURRENT:
+ val = s->current_reg;
+ break;
+ case INA238_REG_POWER:
+ val = s->power_reg;
+ break;
+ case INA238_REG_DIAG_ALRT:
+ val = s->diag_alrt;
+ /*
+ * A read always clears CNVRF; the latched limit flags clear too, but
+ * only in latch mode.
+ */
+ s->diag_alrt &= ~R_INA238_DIAG_ALRT_CNVRF_MASK;
+ if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
+ s->diag_alrt &= ~INA238_DIAG_LATCH_MASK;
+ }
+ ina238_refresh_alert(s);
+ break;
+ case INA238_REG_SOVL:
+ val = s->sovl;
+ break;
+ case INA238_REG_SUVL:
+ val = s->suvl;
+ break;
+ case INA238_REG_BOVL:
+ val = s->bovl;
+ break;
+ case INA238_REG_BUVL:
+ val = s->buvl;
+ break;
+ case INA238_REG_TEMP_LIMIT:
+ val = s->temp_limit;
+ break;
+ case INA238_REG_PWR_LIMIT:
+ val = s->pwr_limit;
+ break;
+ case INA238_REG_MANUFACTURER_ID:
+ val = INA238_MANUFACTURER_ID_VAL;
+ break;
+ case INA238_REG_DEVICE_ID:
+ val = INA238_DEVICE_ID_VAL;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: read from undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ val = 0;
+ break;
+ }
+
+ trace_ina238_read(s->description, reg, val);
+ return val;
+}
+
+static void ina238_write_reg(INA238State *s, uint8_t reg, uint16_t val)
+{
+ trace_ina238_write(s->description, reg, val);
+
+ switch (reg) {
+ case INA238_REG_CONFIG:
+ if (val & R_INA238_CONFIG_RST_MASK) {
+ trace_ina238_reset(s->description, "reg");
+ ina238_reset_regs(s);
+ break;
+ }
+ s->config = val & INA238_CONFIG_WMASK;
+ ina238_input_update(s);
+ break;
+ case INA238_REG_ADC_CONFIG:
+ s->adc_config = val;
+ /* Writing the MODE bits (re)starts a conversion: it is the trigger. */
+ ina238_convert(s);
+ break;
+ case INA238_REG_SHUNT_CAL:
+ s->shunt_cal = val & INA238_SHUNT_CAL_MASK;
+ ina238_input_update(s);
+ break;
+ case INA238_REG_DIAG_ALRT: {
+ uint16_t status = s->diag_alrt & INA238_DIAG_STATUS_MASK;
+ /*
+ * In latch mode a write clears the latched limit flags and the
+ * conversion-ready flag. In transparent mode the flags track the live
+ * condition and are preserved. The pin is only refreshed, so a cleared
+ * flag re-asserts on the next conversion rather than immediately.
+ */
+ if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
+ status &= ~(R_INA238_DIAG_ALRT_CNVRF_MASK | INA238_DIAG_LATCH_MASK);
+ }
+ s->diag_alrt = (val & INA238_DIAG_CTRL_MASK) | status;
+ ina238_refresh_alert(s);
+ break;
+ }
+ case INA238_REG_SOVL:
+ s->sovl = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_SUVL:
+ s->suvl = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_BOVL:
+ s->bovl = val & INA238_BUS_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_BUVL:
+ s->buvl = val & INA238_BUS_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_TEMP_LIMIT:
+ s->temp_limit = val & INA238_TEMP_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_PWR_LIMIT:
+ s->pwr_limit = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_VSHUNT:
+ case INA238_REG_VBUS:
+ case INA238_REG_DIETEMP:
+ case INA238_REG_CURRENT:
+ case INA238_REG_POWER:
+ case INA238_REG_MANUFACTURER_ID:
+ case INA238_REG_DEVICE_ID:
+ 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 ina238_recv(I2CSlave *i2c)
+{
+ INA238State *s = INA238(i2c);
+ uint8_t byte;
+
+ if (s->rx_byte == 0) {
+ s->rx_val = ina238_read_reg(s, s->ptr);
+ s->rx_width = (s->ptr == INA238_REG_POWER) ? 3 : 2;
+ }
+
+ byte = (s->rx_val >> (8 * (s->rx_width - 1 - s->rx_byte))) & 0xff;
+ s->rx_byte++;
+ if (s->rx_byte >= s->rx_width) {
+ s->rx_byte = 0;
+ }
+
+ return byte;
+}
+
+static int ina238_send(I2CSlave *i2c, uint8_t data)
+{
+ INA238State *s = INA238(i2c);
+
+ if (s->len == 0) {
+ s->ptr = data;
+ } else if ((s->len & 1) == 1) {
+ s->wr_hi = data;
+ } else {
+ ina238_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
+ }
+ s->len++;
+
+ return 0;
+}
+
+static int ina238_event(I2CSlave *i2c, enum i2c_event event)
+{
+ INA238State *s = INA238(i2c);
+
+ s->len = 0;
+ s->rx_byte = 0;
+
+ return 0;
+}
+
+static void ina238_get_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->shunt_nv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA238_SHUNT_MIN_NV || value > INA238_SHUNT_MAX_NV) {
+ error_setg(errp,
+ "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
+ s->description, value,
+ INA238_SHUNT_MIN_NV, INA238_SHUNT_MAX_NV);
+ return;
+ }
+
+ s->shunt_nv = (int32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_get_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->bus_uv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < 0 || value > INA238_BUS_MAX_UV) {
+ error_setg(errp,
+ "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
+ s->description, value, INA238_BUS_MAX_UV);
+ return;
+ }
+
+ s->bus_uv = (uint32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_get_temp(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->temp_mc;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_temp(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA238_TEMP_MIN_MC || value > INA238_TEMP_MAX_MC) {
+ error_setg(errp,
+ "%s: die-temperature %" PRId64
+ " out of range (%d..%d m-degC)",
+ s->description, value,
+ INA238_TEMP_MIN_MC, INA238_TEMP_MAX_MC);
+ return;
+ }
+
+ s->temp_mc = (int32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_reset_hold(Object *obj, ResetType type)
+{
+ INA238State *s = INA238(obj);
+
+ trace_ina238_reset(s->description, "hw");
+ ina238_reset_regs(s);
+}
+
+static int ina238_post_load(void *opaque, int version_id)
+{
+ INA238State *s = opaque;
+
+ ina238_refresh_alert(s);
+
+ return 0;
+}
+
+static const VMStateDescription ina238_vmstate = {
+ .name = "INA238",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ina238_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8(ptr, INA238State),
+ VMSTATE_UINT8(len, INA238State),
+ VMSTATE_UINT8(wr_hi, INA238State),
+ VMSTATE_UINT8(rx_byte, INA238State),
+ VMSTATE_UINT8(rx_width, INA238State),
+ VMSTATE_UINT32(rx_val, INA238State),
+ VMSTATE_UINT16(config, INA238State),
+ VMSTATE_UINT16(adc_config, INA238State),
+ VMSTATE_UINT16(shunt_cal, INA238State),
+ VMSTATE_UINT16(diag_alrt, INA238State),
+ VMSTATE_UINT16(sovl, INA238State),
+ VMSTATE_UINT16(suvl, INA238State),
+ VMSTATE_UINT16(bovl, INA238State),
+ VMSTATE_UINT16(buvl, INA238State),
+ VMSTATE_UINT16(temp_limit, INA238State),
+ VMSTATE_UINT16(pwr_limit, INA238State),
+ VMSTATE_UINT16(shunt_reg, INA238State),
+ VMSTATE_UINT16(bus_reg, INA238State),
+ VMSTATE_UINT16(dietemp_reg, INA238State),
+ VMSTATE_UINT16(current_reg, INA238State),
+ VMSTATE_UINT32(power_reg, INA238State),
+ VMSTATE_INT32(shunt_nv, INA238State),
+ VMSTATE_UINT32(bus_uv, INA238State),
+ VMSTATE_INT32(temp_mc, INA238State),
+ VMSTATE_I2C_SLAVE(parent_obj, INA238State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ina238_initfn(Object *obj)
+{
+ object_property_add(obj, "shunt-voltage", "int", ina238_get_shunt,
+ ina238_set_shunt, NULL, NULL);
+ object_property_set_description(obj, "shunt-voltage",
+ "Injected shunt (differential) voltage, "
+ "in nanovolts");
+ object_property_add(obj, "bus-voltage", "int", ina238_get_bus,
+ ina238_set_bus, NULL, NULL);
+ object_property_set_description(obj, "bus-voltage",
+ "Injected bus voltage, in microvolts");
+ object_property_add(obj, "die-temperature", "int", ina238_get_temp,
+ ina238_set_temp, NULL, NULL);
+ object_property_set_description(obj, "die-temperature",
+ "Injected die temperature, "
+ "in millidegrees Celsius");
+}
+
+static void ina238_realize(DeviceState *dev, Error **errp)
+{
+ INA238State *s = INA238(dev);
+
+ if (!s->description) {
+ s->description = g_strdup(object_get_typename(OBJECT(dev)));
+ }
+
+ qdev_init_gpio_out(dev, &s->alert, 1);
+}
+
+static const Property ina238_properties[] = {
+ DEFINE_PROP_STRING("description", INA238State, description),
+};
+
+static void ina238_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 = ina238_event;
+ ic->recv = ina238_recv;
+ ic->send = ina238_send;
+ dc->realize = ina238_realize;
+ rc->phases.hold = ina238_reset_hold;
+ dc->vmsd = &ina238_vmstate;
+ device_class_set_props(dc, ina238_properties);
+}
+
+static const TypeInfo ina238_types[] = {
+ {
+ .name = TYPE_INA238,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = ina238_initfn,
+ .instance_size = sizeof(INA238State),
+ .class_init = ina238_class_init,
+ },
+};
+
+DEFINE_TYPES(ina238_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index b81f001fdf..7e6fd64488 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,5 +1,6 @@
system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.c'))
+system_ss.add(when: 'CONFIG_INA238', if_true: files('ina238.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 f809b6b506..98158e37c6 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -15,6 +15,13 @@ ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, u
ina230_alert(const char *id, bool level) "%s level %u"
ina230_reset(const char *id, const char *source) "%s %s"
+# ina238.c
+ina238_read(const char *id, uint8_t reg, uint32_t value) "%s reg 0x%02x val 0x%06x"
+ina238_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina238_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t dietemp, uint16_t current, uint32_t power) "%s shunt 0x%04x bus 0x%04x dietemp 0x%04x current 0x%04x power 0x%06x"
+ina238_alert(const char *id, bool level) "%s level %u"
+ina238_reset(const char *id, const char *source) "%s %s"
+
# tmp105.c
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/include/hw/sensor/ina238.h b/include/hw/sensor/ina238.h
new file mode 100644
index 0000000000..3bc3c02073
--- /dev/null
+++ b/include/hw/sensor/ina238.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments INA238 current/voltage/power monitor with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_INA238_H
+#define HW_SENSOR_INA238_H
+
+#define TYPE_INA238 "ina238"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 40/50] hw/sensor: ina238: add current/voltage/power monitor
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a model for the Texas Instruments INA238, an 85V, 16-bit I2C
current-shunt, power and temperature monitor.
The device measures a differential shunt voltage, a bus voltage and its
own die temperature; the current and power registers are not measured
directly but derived by the device from the shunt voltage and the
guest-programmed Shunt Calibration register. The alert is modelled.
Shunt and bus voltages and the die temperature can be injected through
the "shunt-voltage", "bus-voltage" and "die-temperature" QOM properties.
Note: Conversion is instantaneous: the averaging and conversion-time
fields are stored but have no timing effect.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/ina238.c | 802 +++++++++++++++++++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 7 +
include/hw/sensor/ina238.h | 14 +
6 files changed, 829 insertions(+)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 116d1c64ac..3abe249ed5 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -540,6 +540,7 @@ config ASPEED_SOC
select TMP421
select EMC141X
select INA230
+ select INA238
select OR_IRQ
select UNIMP
select LED
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index da519159ac..9b7c76fcf5 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -6,6 +6,10 @@ config INA230
bool
depends on I2C
+config INA238
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/sensor/ina238.c b/hw/sensor/ina238.c
new file mode 100644
index 0000000000..e4033771b0
--- /dev/null
+++ b/hw/sensor/ina238.c
@@ -0,0 +1,802 @@
+/*
+ * Texas Instruments INA238 85V, 16-bit current/voltage/power monitor
+ * with an I2C interface.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The INA238 measures a differential shunt voltage, a bus voltage and its own
+ * die temperature. Current and power are not measured directly: they are
+ * derived by the device from the measured shunt voltage and the programmable
+ * Shunt Calibration register. The INA238 exposes six independent limit
+ * registers, all monitored simultaneously (multi-alert), a die-temperature
+ * sensor, a 24-bit power register and an ADCRANGE-dependent shunt LSB.
+ *
+ * Limitations (not modelled):
+ * - Conversion timing is instantaneous. Conversions run synchronously when an
+ * input, the calibration, the ADC range or the ADC configuration changes; the
+ * ADC conversion times (VBUSCT/VSHCT/VTCT), the averaging count (AVG) and the
+ * CONVDLY start delay are not modelled, and continuous mode does not
+ * re-convert on a timer. A read always returns the value derived from the
+ * current inputs and CNVRF is set once a conversion has run.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/ina238.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define INA238_REG_CONFIG 0x00
+#define INA238_REG_ADC_CONFIG 0x01
+#define INA238_REG_SHUNT_CAL 0x02
+#define INA238_REG_VSHUNT 0x04
+#define INA238_REG_VBUS 0x05
+#define INA238_REG_DIETEMP 0x06
+#define INA238_REG_CURRENT 0x07
+#define INA238_REG_POWER 0x08
+#define INA238_REG_DIAG_ALRT 0x0B
+#define INA238_REG_SOVL 0x0C
+#define INA238_REG_SUVL 0x0D
+#define INA238_REG_BOVL 0x0E
+#define INA238_REG_BUVL 0x0F
+#define INA238_REG_TEMP_LIMIT 0x10
+#define INA238_REG_PWR_LIMIT 0x11
+#define INA238_REG_MANUFACTURER_ID 0x3E
+#define INA238_REG_DEVICE_ID 0x3F
+
+FIELD(INA238_CONFIG, ADCRANGE, 4, 1)
+FIELD(INA238_CONFIG, CONVDLY, 6, 8)
+FIELD(INA238_CONFIG, RST, 15, 1)
+
+#define INA238_CONFIG_POR 0x0000
+#define INA238_CONFIG_WMASK (R_INA238_CONFIG_ADCRANGE_MASK | \
+ R_INA238_CONFIG_CONVDLY_MASK)
+
+FIELD(INA238_ADC_CONFIG, AVG, 0, 3)
+FIELD(INA238_ADC_CONFIG, VTCT, 3, 3)
+FIELD(INA238_ADC_CONFIG, VSHCT, 6, 3)
+FIELD(INA238_ADC_CONFIG, VBUSCT, 9, 3)
+FIELD(INA238_ADC_CONFIG, MODE, 12, 4)
+
+#define INA238_ADC_CONFIG_POR 0xFB68
+
+#define INA238_SHUNT_CAL_POR 0x1000
+#define INA238_SHUNT_CAL_MASK 0x7FFF /* bit 15 reserved */
+
+FIELD(INA238_DIAG_ALRT, MEMSTAT, 0, 1) /* memory checksum ok (always 1) */
+FIELD(INA238_DIAG_ALRT, CNVRF, 1, 1) /* conversion ready flag */
+FIELD(INA238_DIAG_ALRT, POL, 2, 1) /* power over-limit */
+FIELD(INA238_DIAG_ALRT, BUSUL, 3, 1) /* bus under-limit */
+FIELD(INA238_DIAG_ALRT, BUSOL, 4, 1) /* bus over-limit */
+FIELD(INA238_DIAG_ALRT, SHNTUL, 5, 1) /* shunt under-limit */
+FIELD(INA238_DIAG_ALRT, SHNTOL, 6, 1) /* shunt over-limit */
+FIELD(INA238_DIAG_ALRT, TMPOL, 7, 1) /* over temperature */
+FIELD(INA238_DIAG_ALRT, MATHOF, 9, 1) /* arithmetic overflow */
+FIELD(INA238_DIAG_ALRT, APOL, 12, 1) /* alert polarity */
+FIELD(INA238_DIAG_ALRT, SLOWALERT, 13, 1) /* alert on averaged value */
+FIELD(INA238_DIAG_ALRT, CNVR, 14, 1) /* conversion-ready on ALERT pin */
+FIELD(INA238_DIAG_ALRT, ALATCH, 15, 1) /* alert latch enable */
+
+#define INA238_DIAG_ALRT_POR 0x0001 /* MEMSTAT = 1 */
+
+/* Control (writable) bits */
+#define INA238_DIAG_CTRL_MASK (R_INA238_DIAG_ALRT_ALATCH_MASK | \
+ R_INA238_DIAG_ALRT_CNVR_MASK | \
+ R_INA238_DIAG_ALRT_SLOWALERT_MASK | \
+ R_INA238_DIAG_ALRT_APOL_MASK)
+/* Read-only status bits (preserved across a control-bit write) */
+#define INA238_DIAG_STATUS_MASK (R_INA238_DIAG_ALRT_MATHOF_MASK | \
+ R_INA238_DIAG_ALRT_TMPOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTUL_MASK | \
+ R_INA238_DIAG_ALRT_BUSOL_MASK | \
+ R_INA238_DIAG_ALRT_BUSUL_MASK | \
+ R_INA238_DIAG_ALRT_POL_MASK | \
+ R_INA238_DIAG_ALRT_CNVRF_MASK | \
+ R_INA238_DIAG_ALRT_MEMSTAT_MASK)
+/* Threshold flags cleared when the DIAG_ALRT register is read */
+#define INA238_DIAG_LATCH_MASK (R_INA238_DIAG_ALRT_TMPOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTUL_MASK | \
+ R_INA238_DIAG_ALRT_BUSOL_MASK | \
+ R_INA238_DIAG_ALRT_BUSUL_MASK | \
+ R_INA238_DIAG_ALRT_POL_MASK)
+
+#define INA238_SOVL_POR 0x7FFF
+#define INA238_SUVL_POR 0x8000
+#define INA238_BOVL_POR 0x7FFF
+#define INA238_BUVL_POR 0x0000
+/*
+ * The datasheet is self-inconsistent on the TEMP_LIMIT reset value.
+ * Writes mask with INA238_TEMP_LIMIT_MASK and the alert comparison shifts both
+ * operands >>4.
+ */
+#define INA238_TEMP_LIMIT_POR 0x7FF0
+#define INA238_PWR_LIMIT_POR 0xFFFF
+
+#define INA238_BUS_LIMIT_MASK 0x7FFF /* BOVL/BUVL are 15-bit, positive */
+#define INA238_TEMP_LIMIT_MASK 0xFFF0 /* TOL occupies bits [15:4] */
+
+#define INA238_MANUFACTURER_ID_VAL 0x5449 /* "TI" */
+#define INA238_DEVICE_ID_VAL 0x2381 /* DIEID 0x238, REV 0x1 */
+
+/* Shunt voltage LSB depends on ADCRANGE; bus and temperature are fixed */
+#define INA238_SHUNT_LSB_NV_R0 5000 /* 5 uV per LSB (ADCRANGE = 0) */
+#define INA238_SHUNT_LSB_NV_R1 1250 /* 1.25 uV per LSB (ADCRANGE = 1) */
+#define INA238_BUS_LSB_UV 3125 /* 3.125 mV per LSB */
+#define INA238_TEMP_LSB_MC 125 /* 125 m-degC per LSB */
+
+/*
+ * current = shunt_reg * K / SHUNT_CAL. K derives from the fixed 819.2e6
+ * internal constant and the ADCRANGE=0 shunt LSB: 5e-6 * 819.2e6 = 4096.
+ * power = current * bus / 64 (0.2 / 3.125mV expressed in the register domain).
+ */
+#define INA238_CURRENT_K 4096
+#define INA238_POWER_DIV 64
+#define INA238_POWER_MAX 0xFFFFFF /* POWER is a 24-bit register */
+
+/* Injection property ranges */
+#define INA238_SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
+#define INA238_SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
+#define INA238_BUS_MAX_UV 85000000 /* 85 V full common-mode range */
+#define INA238_TEMP_MIN_MC (-40000) /* package limited */
+#define INA238_TEMP_MAX_MC 125000
+
+OBJECT_DECLARE_SIMPLE_TYPE(INA238State, INA238)
+
+struct INA238State {
+ I2CSlave parent_obj;
+
+ qemu_irq alert; /* ALERT open-drain output */
+
+ /* I2C transfer state */
+ uint8_t ptr; /* register pointer */
+ uint8_t len; /* bytes received in current transfer */
+ uint8_t wr_hi; /* pending write MSB */
+ uint8_t rx_byte; /* index of the next byte to return within rx_val */
+ uint8_t rx_width; /* width in bytes of the register being read (2 or 3) */
+ uint32_t rx_val; /* value latched at the start of a read */
+
+ /* Register file */
+ uint16_t config;
+ uint16_t adc_config;
+ uint16_t shunt_cal;
+ uint16_t diag_alrt;
+ uint16_t sovl;
+ uint16_t suvl;
+ uint16_t bovl;
+ uint16_t buvl;
+ uint16_t temp_limit;
+ uint16_t pwr_limit;
+
+ /* Derived measurement registers */
+ uint16_t shunt_reg;
+ uint16_t bus_reg;
+ uint16_t dietemp_reg;
+ uint16_t current_reg;
+ uint32_t power_reg; /* 24-bit */
+
+ /* Injected physical inputs */
+ int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
+ uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
+ int32_t temp_mc; /* die temperature, millidegrees Celsius (signed) */
+
+ char *description;
+};
+
+/*
+ * Update the _field_ limit flag in _diag_ from the current comparison _cond_.
+ * In latch mode (_latch_) a flag that is already set stays set until the
+ * register is read; in transparent mode it tracks _cond_ directly. A flag
+ * whose measurement register was not refreshed (_fresh_) keeps its value.
+ */
+#define INA238_UPD_FLAG(_diag_, _latch_, _fresh_, _field_, _cond_) \
+ do { \
+ if (_fresh_) { \
+ uint16_t _old_ = FIELD_EX16(_diag_, INA238_DIAG_ALRT, _field_); \
+ uint16_t _new_ = (uint16_t)((_cond_) || ((_latch_) && _old_)); \
+ (_diag_) = FIELD_DP16(_diag_, INA238_DIAG_ALRT, _field_, _new_); \
+ } \
+ } while (0)
+
+static void ina238_refresh_alert(INA238State *s)
+{
+ uint16_t diag = s->diag_alrt;
+ bool active;
+ bool cnvr;
+ int level;
+
+ /* MATHOF is a diagnostic; it does not drive the ALERT pin. */
+ active = FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTUL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, BUSOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, BUSUL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, TMPOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, POL);
+
+ cnvr = FIELD_EX16(diag, INA238_DIAG_ALRT, CNVR) &&
+ FIELD_EX16(diag, INA238_DIAG_ALRT, CNVRF);
+
+ active = active || cnvr;
+ /* APOL: 0 = active-low (default), 1 = active-high */
+ level = FIELD_EX16(diag, INA238_DIAG_ALRT, APOL) ? active : !active;
+
+ trace_ina238_alert(s->description, level);
+ qemu_set_irq(s->alert, level);
+}
+
+/*
+ * @shunt_fresh, @bus_fresh and @temp_fresh tell which measurement registers
+ * hold a fresh value: after a partial conversion the flags fed by the other
+ * channels must not be re-evaluated against their stale registers.
+ */
+static void ina238_update_alert(INA238State *s, bool shunt_fresh,
+ bool bus_fresh, bool temp_fresh,
+ bool power_fresh)
+{
+ bool latch = FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH);
+ int16_t shunt = (int16_t)s->shunt_reg;
+ int16_t temp = (int16_t)s->dietemp_reg >> 4; /* sign-extend [15:4] */
+ int16_t temp_lim = (int16_t)s->temp_limit >> 4;
+ uint16_t diag = s->diag_alrt;
+
+ INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTOL, shunt > (int16_t)s->sovl);
+ INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTUL, shunt < (int16_t)s->suvl);
+ INA238_UPD_FLAG(diag, latch, bus_fresh, BUSOL,
+ s->bus_reg > (s->bovl & INA238_BUS_LIMIT_MASK));
+ INA238_UPD_FLAG(diag, latch, bus_fresh, BUSUL,
+ s->bus_reg < (s->buvl & INA238_BUS_LIMIT_MASK));
+ INA238_UPD_FLAG(diag, latch, temp_fresh, TMPOL, temp > temp_lim);
+ INA238_UPD_FLAG(diag, latch, power_fresh, POL,
+ s->power_reg > ((uint32_t)s->pwr_limit << 8));
+
+ s->diag_alrt = diag;
+
+ ina238_refresh_alert(s);
+}
+
+static void ina238_convert(INA238State *s)
+{
+ uint8_t mode = FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE);
+ bool bus_en = mode & 0x1;
+ bool shunt_en = mode & 0x2;
+ bool temp_en = mode & 0x4;
+ bool adcrange = FIELD_EX16(s->config, INA238_CONFIG, ADCRANGE);
+ int shunt_lsb = adcrange ? INA238_SHUNT_LSB_NV_R1 : INA238_SHUNT_LSB_NV_R0;
+ int64_t sr, br, tr, cur, pwr;
+ uint16_t mathof = 0;
+ bool power_fresh = bus_en;
+
+ if (!bus_en && !shunt_en && !temp_en) {
+ return;
+ }
+
+ if (shunt_en) {
+ sr = s->shunt_nv / shunt_lsb;
+ sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
+ s->shunt_reg = (uint16_t)(int16_t)sr;
+
+ if (s->shunt_cal == 0) {
+ cur = 0;
+ } else {
+ cur = (int64_t)(int16_t)s->shunt_reg * INA238_CURRENT_K /
+ s->shunt_cal;
+ }
+ if (cur > INT16_MAX || cur < INT16_MIN) {
+ mathof = 1;
+ cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
+ }
+ s->current_reg = (uint16_t)(int16_t)cur;
+ }
+
+ if (bus_en) {
+ br = s->bus_uv / INA238_BUS_LSB_UV;
+ br = MAX(0, MIN(INT16_MAX, br));
+ s->bus_reg = (uint16_t)br;
+ }
+
+ if (temp_en) {
+ /* Die temperature: 12-bit signed value stored in bits [15:4] */
+ tr = s->temp_mc / INA238_TEMP_LSB_MC;
+ tr = MAX(-2048, MIN(2047, tr));
+ s->dietemp_reg = ((uint16_t)(int16_t)tr << 4) & INA238_TEMP_LIMIT_MASK;
+ }
+
+ /*
+ * In bus-only mode the current register is retained from the last shunt
+ * conversion rather than measured anew, but the power register is still
+ * refreshed.
+ */
+ if (bus_en) {
+ cur = (int16_t)s->current_reg;
+ if (cur < 0) {
+ cur = -cur;
+ }
+ pwr = cur * (int64_t)s->bus_reg / INA238_POWER_DIV;
+ if (pwr > INA238_POWER_MAX) {
+ mathof = 1;
+ pwr = INA238_POWER_MAX;
+ }
+ s->power_reg = (uint32_t)pwr;
+ }
+
+ /* Without a calibration the device reports neither current nor power. */
+ if (s->shunt_cal == 0) {
+ s->current_reg = 0;
+ s->power_reg = 0;
+ power_fresh = true;
+ }
+
+ s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, MATHOF, mathof);
+ s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, CNVRF, 1);
+
+ trace_ina238_convert(s->description, s->shunt_reg, s->bus_reg,
+ s->dietemp_reg, s->current_reg, s->power_reg);
+
+ ina238_update_alert(s, shunt_en, bus_en, temp_en, power_fresh);
+}
+
+/*
+ * A physical input, the calibration or the ADC range changed.
+ * - in continuous mode the device keeps converting and immediately tracks
+ * the new value
+ * - in a triggered mode it performs exactly one conversion per trigger
+ */
+static void ina238_input_update(INA238State *s)
+{
+ if (FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE) & 0x8) {
+ ina238_convert(s);
+ }
+}
+
+static void ina238_reset_regs(INA238State *s)
+{
+ s->config = INA238_CONFIG_POR;
+ s->adc_config = INA238_ADC_CONFIG_POR;
+ s->shunt_cal = INA238_SHUNT_CAL_POR;
+ s->diag_alrt = INA238_DIAG_ALRT_POR;
+ s->sovl = INA238_SOVL_POR;
+ s->suvl = INA238_SUVL_POR;
+ s->bovl = INA238_BOVL_POR;
+ s->buvl = INA238_BUVL_POR;
+ s->temp_limit = INA238_TEMP_LIMIT_POR;
+ s->pwr_limit = INA238_PWR_LIMIT_POR;
+
+ s->ptr = 0;
+ s->len = 0;
+ s->wr_hi = 0;
+ s->rx_byte = 0;
+ s->rx_width = 0;
+ s->rx_val = 0;
+
+ /*
+ * The measurement registers power up at zero and stay zero until the next
+ * conversion. The injected physical inputs are retained, but no conversion
+ * is performed here, so a software reset does not resurrect a previous
+ * measurement.
+ */
+ s->shunt_reg = 0;
+ s->bus_reg = 0;
+ s->dietemp_reg = 0;
+ s->current_reg = 0;
+ s->power_reg = 0;
+
+ ina238_update_alert(s, true, true, true, true);
+}
+
+static uint32_t ina238_read_reg(INA238State *s, uint8_t reg)
+{
+ uint32_t val;
+
+ switch (reg) {
+ case INA238_REG_CONFIG:
+ val = s->config;
+ break;
+ case INA238_REG_ADC_CONFIG:
+ val = s->adc_config;
+ break;
+ case INA238_REG_SHUNT_CAL:
+ val = s->shunt_cal;
+ break;
+ case INA238_REG_VSHUNT:
+ val = s->shunt_reg;
+ break;
+ case INA238_REG_VBUS:
+ val = s->bus_reg;
+ break;
+ case INA238_REG_DIETEMP:
+ val = s->dietemp_reg;
+ break;
+ case INA238_REG_CURRENT:
+ val = s->current_reg;
+ break;
+ case INA238_REG_POWER:
+ val = s->power_reg;
+ break;
+ case INA238_REG_DIAG_ALRT:
+ val = s->diag_alrt;
+ /*
+ * A read always clears CNVRF; the latched limit flags clear too, but
+ * only in latch mode.
+ */
+ s->diag_alrt &= ~R_INA238_DIAG_ALRT_CNVRF_MASK;
+ if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
+ s->diag_alrt &= ~INA238_DIAG_LATCH_MASK;
+ }
+ ina238_refresh_alert(s);
+ break;
+ case INA238_REG_SOVL:
+ val = s->sovl;
+ break;
+ case INA238_REG_SUVL:
+ val = s->suvl;
+ break;
+ case INA238_REG_BOVL:
+ val = s->bovl;
+ break;
+ case INA238_REG_BUVL:
+ val = s->buvl;
+ break;
+ case INA238_REG_TEMP_LIMIT:
+ val = s->temp_limit;
+ break;
+ case INA238_REG_PWR_LIMIT:
+ val = s->pwr_limit;
+ break;
+ case INA238_REG_MANUFACTURER_ID:
+ val = INA238_MANUFACTURER_ID_VAL;
+ break;
+ case INA238_REG_DEVICE_ID:
+ val = INA238_DEVICE_ID_VAL;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: read from undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ val = 0;
+ break;
+ }
+
+ trace_ina238_read(s->description, reg, val);
+ return val;
+}
+
+static void ina238_write_reg(INA238State *s, uint8_t reg, uint16_t val)
+{
+ trace_ina238_write(s->description, reg, val);
+
+ switch (reg) {
+ case INA238_REG_CONFIG:
+ if (val & R_INA238_CONFIG_RST_MASK) {
+ trace_ina238_reset(s->description, "reg");
+ ina238_reset_regs(s);
+ break;
+ }
+ s->config = val & INA238_CONFIG_WMASK;
+ ina238_input_update(s);
+ break;
+ case INA238_REG_ADC_CONFIG:
+ s->adc_config = val;
+ /* Writing the MODE bits (re)starts a conversion: it is the trigger. */
+ ina238_convert(s);
+ break;
+ case INA238_REG_SHUNT_CAL:
+ s->shunt_cal = val & INA238_SHUNT_CAL_MASK;
+ ina238_input_update(s);
+ break;
+ case INA238_REG_DIAG_ALRT: {
+ uint16_t status = s->diag_alrt & INA238_DIAG_STATUS_MASK;
+ /*
+ * In latch mode a write clears the latched limit flags and the
+ * conversion-ready flag. In transparent mode the flags track the live
+ * condition and are preserved. The pin is only refreshed, so a cleared
+ * flag re-asserts on the next conversion rather than immediately.
+ */
+ if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
+ status &= ~(R_INA238_DIAG_ALRT_CNVRF_MASK | INA238_DIAG_LATCH_MASK);
+ }
+ s->diag_alrt = (val & INA238_DIAG_CTRL_MASK) | status;
+ ina238_refresh_alert(s);
+ break;
+ }
+ case INA238_REG_SOVL:
+ s->sovl = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_SUVL:
+ s->suvl = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_BOVL:
+ s->bovl = val & INA238_BUS_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_BUVL:
+ s->buvl = val & INA238_BUS_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_TEMP_LIMIT:
+ s->temp_limit = val & INA238_TEMP_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_PWR_LIMIT:
+ s->pwr_limit = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_VSHUNT:
+ case INA238_REG_VBUS:
+ case INA238_REG_DIETEMP:
+ case INA238_REG_CURRENT:
+ case INA238_REG_POWER:
+ case INA238_REG_MANUFACTURER_ID:
+ case INA238_REG_DEVICE_ID:
+ 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 ina238_recv(I2CSlave *i2c)
+{
+ INA238State *s = INA238(i2c);
+ uint8_t byte;
+
+ if (s->rx_byte == 0) {
+ s->rx_val = ina238_read_reg(s, s->ptr);
+ s->rx_width = (s->ptr == INA238_REG_POWER) ? 3 : 2;
+ }
+
+ byte = (s->rx_val >> (8 * (s->rx_width - 1 - s->rx_byte))) & 0xff;
+ s->rx_byte++;
+ if (s->rx_byte >= s->rx_width) {
+ s->rx_byte = 0;
+ }
+
+ return byte;
+}
+
+static int ina238_send(I2CSlave *i2c, uint8_t data)
+{
+ INA238State *s = INA238(i2c);
+
+ if (s->len == 0) {
+ s->ptr = data;
+ } else if ((s->len & 1) == 1) {
+ s->wr_hi = data;
+ } else {
+ ina238_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
+ }
+ s->len++;
+
+ return 0;
+}
+
+static int ina238_event(I2CSlave *i2c, enum i2c_event event)
+{
+ INA238State *s = INA238(i2c);
+
+ s->len = 0;
+ s->rx_byte = 0;
+
+ return 0;
+}
+
+static void ina238_get_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->shunt_nv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA238_SHUNT_MIN_NV || value > INA238_SHUNT_MAX_NV) {
+ error_setg(errp,
+ "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
+ s->description, value,
+ INA238_SHUNT_MIN_NV, INA238_SHUNT_MAX_NV);
+ return;
+ }
+
+ s->shunt_nv = (int32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_get_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->bus_uv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < 0 || value > INA238_BUS_MAX_UV) {
+ error_setg(errp,
+ "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
+ s->description, value, INA238_BUS_MAX_UV);
+ return;
+ }
+
+ s->bus_uv = (uint32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_get_temp(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->temp_mc;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_temp(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA238_TEMP_MIN_MC || value > INA238_TEMP_MAX_MC) {
+ error_setg(errp,
+ "%s: die-temperature %" PRId64
+ " out of range (%d..%d m-degC)",
+ s->description, value,
+ INA238_TEMP_MIN_MC, INA238_TEMP_MAX_MC);
+ return;
+ }
+
+ s->temp_mc = (int32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_reset_hold(Object *obj, ResetType type)
+{
+ INA238State *s = INA238(obj);
+
+ trace_ina238_reset(s->description, "hw");
+ ina238_reset_regs(s);
+}
+
+static int ina238_post_load(void *opaque, int version_id)
+{
+ INA238State *s = opaque;
+
+ ina238_refresh_alert(s);
+
+ return 0;
+}
+
+static const VMStateDescription ina238_vmstate = {
+ .name = "INA238",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ina238_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8(ptr, INA238State),
+ VMSTATE_UINT8(len, INA238State),
+ VMSTATE_UINT8(wr_hi, INA238State),
+ VMSTATE_UINT8(rx_byte, INA238State),
+ VMSTATE_UINT8(rx_width, INA238State),
+ VMSTATE_UINT32(rx_val, INA238State),
+ VMSTATE_UINT16(config, INA238State),
+ VMSTATE_UINT16(adc_config, INA238State),
+ VMSTATE_UINT16(shunt_cal, INA238State),
+ VMSTATE_UINT16(diag_alrt, INA238State),
+ VMSTATE_UINT16(sovl, INA238State),
+ VMSTATE_UINT16(suvl, INA238State),
+ VMSTATE_UINT16(bovl, INA238State),
+ VMSTATE_UINT16(buvl, INA238State),
+ VMSTATE_UINT16(temp_limit, INA238State),
+ VMSTATE_UINT16(pwr_limit, INA238State),
+ VMSTATE_UINT16(shunt_reg, INA238State),
+ VMSTATE_UINT16(bus_reg, INA238State),
+ VMSTATE_UINT16(dietemp_reg, INA238State),
+ VMSTATE_UINT16(current_reg, INA238State),
+ VMSTATE_UINT32(power_reg, INA238State),
+ VMSTATE_INT32(shunt_nv, INA238State),
+ VMSTATE_UINT32(bus_uv, INA238State),
+ VMSTATE_INT32(temp_mc, INA238State),
+ VMSTATE_I2C_SLAVE(parent_obj, INA238State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ina238_initfn(Object *obj)
+{
+ object_property_add(obj, "shunt-voltage", "int", ina238_get_shunt,
+ ina238_set_shunt, NULL, NULL);
+ object_property_set_description(obj, "shunt-voltage",
+ "Injected shunt (differential) voltage, "
+ "in nanovolts");
+ object_property_add(obj, "bus-voltage", "int", ina238_get_bus,
+ ina238_set_bus, NULL, NULL);
+ object_property_set_description(obj, "bus-voltage",
+ "Injected bus voltage, in microvolts");
+ object_property_add(obj, "die-temperature", "int", ina238_get_temp,
+ ina238_set_temp, NULL, NULL);
+ object_property_set_description(obj, "die-temperature",
+ "Injected die temperature, "
+ "in millidegrees Celsius");
+}
+
+static void ina238_realize(DeviceState *dev, Error **errp)
+{
+ INA238State *s = INA238(dev);
+
+ if (!s->description) {
+ s->description = g_strdup(object_get_typename(OBJECT(dev)));
+ }
+
+ qdev_init_gpio_out(dev, &s->alert, 1);
+}
+
+static const Property ina238_properties[] = {
+ DEFINE_PROP_STRING("description", INA238State, description),
+};
+
+static void ina238_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 = ina238_event;
+ ic->recv = ina238_recv;
+ ic->send = ina238_send;
+ dc->realize = ina238_realize;
+ rc->phases.hold = ina238_reset_hold;
+ dc->vmsd = &ina238_vmstate;
+ device_class_set_props(dc, ina238_properties);
+}
+
+static const TypeInfo ina238_types[] = {
+ {
+ .name = TYPE_INA238,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = ina238_initfn,
+ .instance_size = sizeof(INA238State),
+ .class_init = ina238_class_init,
+ },
+};
+
+DEFINE_TYPES(ina238_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index b81f001fdf..7e6fd64488 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,5 +1,6 @@
system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.c'))
+system_ss.add(when: 'CONFIG_INA238', if_true: files('ina238.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 f809b6b506..98158e37c6 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -15,6 +15,13 @@ ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, u
ina230_alert(const char *id, bool level) "%s level %u"
ina230_reset(const char *id, const char *source) "%s %s"
+# ina238.c
+ina238_read(const char *id, uint8_t reg, uint32_t value) "%s reg 0x%02x val 0x%06x"
+ina238_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina238_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t dietemp, uint16_t current, uint32_t power) "%s shunt 0x%04x bus 0x%04x dietemp 0x%04x current 0x%04x power 0x%06x"
+ina238_alert(const char *id, bool level) "%s level %u"
+ina238_reset(const char *id, const char *source) "%s %s"
+
# tmp105.c
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/include/hw/sensor/ina238.h b/include/hw/sensor/ina238.h
new file mode 100644
index 0000000000..3bc3c02073
--- /dev/null
+++ b/include/hw/sensor/ina238.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments INA238 current/voltage/power monitor with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_INA238_H
+#define HW_SENSOR_INA238_H
+
+#define TYPE_INA238 "ina238"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 40/50] hw/sensor: ina238: add current/voltage/power monitor
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a model for the Texas Instruments INA238, an 85V, 16-bit I2C
current-shunt, power and temperature monitor.
The device measures a differential shunt voltage, a bus voltage and its
own die temperature; the current and power registers are not measured
directly but derived by the device from the shunt voltage and the
guest-programmed Shunt Calibration register. The alert is modelled.
Shunt and bus voltages and the die temperature can be injected through
the "shunt-voltage", "bus-voltage" and "die-temperature" QOM properties.
Note: Conversion is instantaneous: the averaging and conversion-time
fields are stored but have no timing effect.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/ina238.c | 802 +++++++++++++++++++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 7 +
include/hw/sensor/ina238.h | 14 +
6 files changed, 829 insertions(+)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 116d1c64ac..3abe249ed5 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -540,6 +540,7 @@ config ASPEED_SOC
select TMP421
select EMC141X
select INA230
+ select INA238
select OR_IRQ
select UNIMP
select LED
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index da519159ac..9b7c76fcf5 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -6,6 +6,10 @@ config INA230
bool
depends on I2C
+config INA238
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/sensor/ina238.c b/hw/sensor/ina238.c
new file mode 100644
index 0000000000..e4033771b0
--- /dev/null
+++ b/hw/sensor/ina238.c
@@ -0,0 +1,802 @@
+/*
+ * Texas Instruments INA238 85V, 16-bit current/voltage/power monitor
+ * with an I2C interface.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The INA238 measures a differential shunt voltage, a bus voltage and its own
+ * die temperature. Current and power are not measured directly: they are
+ * derived by the device from the measured shunt voltage and the programmable
+ * Shunt Calibration register. The INA238 exposes six independent limit
+ * registers, all monitored simultaneously (multi-alert), a die-temperature
+ * sensor, a 24-bit power register and an ADCRANGE-dependent shunt LSB.
+ *
+ * Limitations (not modelled):
+ * - Conversion timing is instantaneous. Conversions run synchronously when an
+ * input, the calibration, the ADC range or the ADC configuration changes; the
+ * ADC conversion times (VBUSCT/VSHCT/VTCT), the averaging count (AVG) and the
+ * CONVDLY start delay are not modelled, and continuous mode does not
+ * re-convert on a timer. A read always returns the value derived from the
+ * current inputs and CNVRF is set once a conversion has run.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/ina238.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define INA238_REG_CONFIG 0x00
+#define INA238_REG_ADC_CONFIG 0x01
+#define INA238_REG_SHUNT_CAL 0x02
+#define INA238_REG_VSHUNT 0x04
+#define INA238_REG_VBUS 0x05
+#define INA238_REG_DIETEMP 0x06
+#define INA238_REG_CURRENT 0x07
+#define INA238_REG_POWER 0x08
+#define INA238_REG_DIAG_ALRT 0x0B
+#define INA238_REG_SOVL 0x0C
+#define INA238_REG_SUVL 0x0D
+#define INA238_REG_BOVL 0x0E
+#define INA238_REG_BUVL 0x0F
+#define INA238_REG_TEMP_LIMIT 0x10
+#define INA238_REG_PWR_LIMIT 0x11
+#define INA238_REG_MANUFACTURER_ID 0x3E
+#define INA238_REG_DEVICE_ID 0x3F
+
+FIELD(INA238_CONFIG, ADCRANGE, 4, 1)
+FIELD(INA238_CONFIG, CONVDLY, 6, 8)
+FIELD(INA238_CONFIG, RST, 15, 1)
+
+#define INA238_CONFIG_POR 0x0000
+#define INA238_CONFIG_WMASK (R_INA238_CONFIG_ADCRANGE_MASK | \
+ R_INA238_CONFIG_CONVDLY_MASK)
+
+FIELD(INA238_ADC_CONFIG, AVG, 0, 3)
+FIELD(INA238_ADC_CONFIG, VTCT, 3, 3)
+FIELD(INA238_ADC_CONFIG, VSHCT, 6, 3)
+FIELD(INA238_ADC_CONFIG, VBUSCT, 9, 3)
+FIELD(INA238_ADC_CONFIG, MODE, 12, 4)
+
+#define INA238_ADC_CONFIG_POR 0xFB68
+
+#define INA238_SHUNT_CAL_POR 0x1000
+#define INA238_SHUNT_CAL_MASK 0x7FFF /* bit 15 reserved */
+
+FIELD(INA238_DIAG_ALRT, MEMSTAT, 0, 1) /* memory checksum ok (always 1) */
+FIELD(INA238_DIAG_ALRT, CNVRF, 1, 1) /* conversion ready flag */
+FIELD(INA238_DIAG_ALRT, POL, 2, 1) /* power over-limit */
+FIELD(INA238_DIAG_ALRT, BUSUL, 3, 1) /* bus under-limit */
+FIELD(INA238_DIAG_ALRT, BUSOL, 4, 1) /* bus over-limit */
+FIELD(INA238_DIAG_ALRT, SHNTUL, 5, 1) /* shunt under-limit */
+FIELD(INA238_DIAG_ALRT, SHNTOL, 6, 1) /* shunt over-limit */
+FIELD(INA238_DIAG_ALRT, TMPOL, 7, 1) /* over temperature */
+FIELD(INA238_DIAG_ALRT, MATHOF, 9, 1) /* arithmetic overflow */
+FIELD(INA238_DIAG_ALRT, APOL, 12, 1) /* alert polarity */
+FIELD(INA238_DIAG_ALRT, SLOWALERT, 13, 1) /* alert on averaged value */
+FIELD(INA238_DIAG_ALRT, CNVR, 14, 1) /* conversion-ready on ALERT pin */
+FIELD(INA238_DIAG_ALRT, ALATCH, 15, 1) /* alert latch enable */
+
+#define INA238_DIAG_ALRT_POR 0x0001 /* MEMSTAT = 1 */
+
+/* Control (writable) bits */
+#define INA238_DIAG_CTRL_MASK (R_INA238_DIAG_ALRT_ALATCH_MASK | \
+ R_INA238_DIAG_ALRT_CNVR_MASK | \
+ R_INA238_DIAG_ALRT_SLOWALERT_MASK | \
+ R_INA238_DIAG_ALRT_APOL_MASK)
+/* Read-only status bits (preserved across a control-bit write) */
+#define INA238_DIAG_STATUS_MASK (R_INA238_DIAG_ALRT_MATHOF_MASK | \
+ R_INA238_DIAG_ALRT_TMPOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTUL_MASK | \
+ R_INA238_DIAG_ALRT_BUSOL_MASK | \
+ R_INA238_DIAG_ALRT_BUSUL_MASK | \
+ R_INA238_DIAG_ALRT_POL_MASK | \
+ R_INA238_DIAG_ALRT_CNVRF_MASK | \
+ R_INA238_DIAG_ALRT_MEMSTAT_MASK)
+/* Threshold flags cleared when the DIAG_ALRT register is read */
+#define INA238_DIAG_LATCH_MASK (R_INA238_DIAG_ALRT_TMPOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTOL_MASK | \
+ R_INA238_DIAG_ALRT_SHNTUL_MASK | \
+ R_INA238_DIAG_ALRT_BUSOL_MASK | \
+ R_INA238_DIAG_ALRT_BUSUL_MASK | \
+ R_INA238_DIAG_ALRT_POL_MASK)
+
+#define INA238_SOVL_POR 0x7FFF
+#define INA238_SUVL_POR 0x8000
+#define INA238_BOVL_POR 0x7FFF
+#define INA238_BUVL_POR 0x0000
+/*
+ * The datasheet is self-inconsistent on the TEMP_LIMIT reset value.
+ * Writes mask with INA238_TEMP_LIMIT_MASK and the alert comparison shifts both
+ * operands >>4.
+ */
+#define INA238_TEMP_LIMIT_POR 0x7FF0
+#define INA238_PWR_LIMIT_POR 0xFFFF
+
+#define INA238_BUS_LIMIT_MASK 0x7FFF /* BOVL/BUVL are 15-bit, positive */
+#define INA238_TEMP_LIMIT_MASK 0xFFF0 /* TOL occupies bits [15:4] */
+
+#define INA238_MANUFACTURER_ID_VAL 0x5449 /* "TI" */
+#define INA238_DEVICE_ID_VAL 0x2381 /* DIEID 0x238, REV 0x1 */
+
+/* Shunt voltage LSB depends on ADCRANGE; bus and temperature are fixed */
+#define INA238_SHUNT_LSB_NV_R0 5000 /* 5 uV per LSB (ADCRANGE = 0) */
+#define INA238_SHUNT_LSB_NV_R1 1250 /* 1.25 uV per LSB (ADCRANGE = 1) */
+#define INA238_BUS_LSB_UV 3125 /* 3.125 mV per LSB */
+#define INA238_TEMP_LSB_MC 125 /* 125 m-degC per LSB */
+
+/*
+ * current = shunt_reg * K / SHUNT_CAL. K derives from the fixed 819.2e6
+ * internal constant and the ADCRANGE=0 shunt LSB: 5e-6 * 819.2e6 = 4096.
+ * power = current * bus / 64 (0.2 / 3.125mV expressed in the register domain).
+ */
+#define INA238_CURRENT_K 4096
+#define INA238_POWER_DIV 64
+#define INA238_POWER_MAX 0xFFFFFF /* POWER is a 24-bit register */
+
+/* Injection property ranges */
+#define INA238_SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
+#define INA238_SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
+#define INA238_BUS_MAX_UV 85000000 /* 85 V full common-mode range */
+#define INA238_TEMP_MIN_MC (-40000) /* package limited */
+#define INA238_TEMP_MAX_MC 125000
+
+OBJECT_DECLARE_SIMPLE_TYPE(INA238State, INA238)
+
+struct INA238State {
+ I2CSlave parent_obj;
+
+ qemu_irq alert; /* ALERT open-drain output */
+
+ /* I2C transfer state */
+ uint8_t ptr; /* register pointer */
+ uint8_t len; /* bytes received in current transfer */
+ uint8_t wr_hi; /* pending write MSB */
+ uint8_t rx_byte; /* index of the next byte to return within rx_val */
+ uint8_t rx_width; /* width in bytes of the register being read (2 or 3) */
+ uint32_t rx_val; /* value latched at the start of a read */
+
+ /* Register file */
+ uint16_t config;
+ uint16_t adc_config;
+ uint16_t shunt_cal;
+ uint16_t diag_alrt;
+ uint16_t sovl;
+ uint16_t suvl;
+ uint16_t bovl;
+ uint16_t buvl;
+ uint16_t temp_limit;
+ uint16_t pwr_limit;
+
+ /* Derived measurement registers */
+ uint16_t shunt_reg;
+ uint16_t bus_reg;
+ uint16_t dietemp_reg;
+ uint16_t current_reg;
+ uint32_t power_reg; /* 24-bit */
+
+ /* Injected physical inputs */
+ int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
+ uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
+ int32_t temp_mc; /* die temperature, millidegrees Celsius (signed) */
+
+ char *description;
+};
+
+/*
+ * Update the _field_ limit flag in _diag_ from the current comparison _cond_.
+ * In latch mode (_latch_) a flag that is already set stays set until the
+ * register is read; in transparent mode it tracks _cond_ directly. A flag
+ * whose measurement register was not refreshed (_fresh_) keeps its value.
+ */
+#define INA238_UPD_FLAG(_diag_, _latch_, _fresh_, _field_, _cond_) \
+ do { \
+ if (_fresh_) { \
+ uint16_t _old_ = FIELD_EX16(_diag_, INA238_DIAG_ALRT, _field_); \
+ uint16_t _new_ = (uint16_t)((_cond_) || ((_latch_) && _old_)); \
+ (_diag_) = FIELD_DP16(_diag_, INA238_DIAG_ALRT, _field_, _new_); \
+ } \
+ } while (0)
+
+static void ina238_refresh_alert(INA238State *s)
+{
+ uint16_t diag = s->diag_alrt;
+ bool active;
+ bool cnvr;
+ int level;
+
+ /* MATHOF is a diagnostic; it does not drive the ALERT pin. */
+ active = FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTUL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, BUSOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, BUSUL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, TMPOL) ||
+ FIELD_EX16(diag, INA238_DIAG_ALRT, POL);
+
+ cnvr = FIELD_EX16(diag, INA238_DIAG_ALRT, CNVR) &&
+ FIELD_EX16(diag, INA238_DIAG_ALRT, CNVRF);
+
+ active = active || cnvr;
+ /* APOL: 0 = active-low (default), 1 = active-high */
+ level = FIELD_EX16(diag, INA238_DIAG_ALRT, APOL) ? active : !active;
+
+ trace_ina238_alert(s->description, level);
+ qemu_set_irq(s->alert, level);
+}
+
+/*
+ * @shunt_fresh, @bus_fresh and @temp_fresh tell which measurement registers
+ * hold a fresh value: after a partial conversion the flags fed by the other
+ * channels must not be re-evaluated against their stale registers.
+ */
+static void ina238_update_alert(INA238State *s, bool shunt_fresh,
+ bool bus_fresh, bool temp_fresh,
+ bool power_fresh)
+{
+ bool latch = FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH);
+ int16_t shunt = (int16_t)s->shunt_reg;
+ int16_t temp = (int16_t)s->dietemp_reg >> 4; /* sign-extend [15:4] */
+ int16_t temp_lim = (int16_t)s->temp_limit >> 4;
+ uint16_t diag = s->diag_alrt;
+
+ INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTOL, shunt > (int16_t)s->sovl);
+ INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTUL, shunt < (int16_t)s->suvl);
+ INA238_UPD_FLAG(diag, latch, bus_fresh, BUSOL,
+ s->bus_reg > (s->bovl & INA238_BUS_LIMIT_MASK));
+ INA238_UPD_FLAG(diag, latch, bus_fresh, BUSUL,
+ s->bus_reg < (s->buvl & INA238_BUS_LIMIT_MASK));
+ INA238_UPD_FLAG(diag, latch, temp_fresh, TMPOL, temp > temp_lim);
+ INA238_UPD_FLAG(diag, latch, power_fresh, POL,
+ s->power_reg > ((uint32_t)s->pwr_limit << 8));
+
+ s->diag_alrt = diag;
+
+ ina238_refresh_alert(s);
+}
+
+static void ina238_convert(INA238State *s)
+{
+ uint8_t mode = FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE);
+ bool bus_en = mode & 0x1;
+ bool shunt_en = mode & 0x2;
+ bool temp_en = mode & 0x4;
+ bool adcrange = FIELD_EX16(s->config, INA238_CONFIG, ADCRANGE);
+ int shunt_lsb = adcrange ? INA238_SHUNT_LSB_NV_R1 : INA238_SHUNT_LSB_NV_R0;
+ int64_t sr, br, tr, cur, pwr;
+ uint16_t mathof = 0;
+ bool power_fresh = bus_en;
+
+ if (!bus_en && !shunt_en && !temp_en) {
+ return;
+ }
+
+ if (shunt_en) {
+ sr = s->shunt_nv / shunt_lsb;
+ sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
+ s->shunt_reg = (uint16_t)(int16_t)sr;
+
+ if (s->shunt_cal == 0) {
+ cur = 0;
+ } else {
+ cur = (int64_t)(int16_t)s->shunt_reg * INA238_CURRENT_K /
+ s->shunt_cal;
+ }
+ if (cur > INT16_MAX || cur < INT16_MIN) {
+ mathof = 1;
+ cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
+ }
+ s->current_reg = (uint16_t)(int16_t)cur;
+ }
+
+ if (bus_en) {
+ br = s->bus_uv / INA238_BUS_LSB_UV;
+ br = MAX(0, MIN(INT16_MAX, br));
+ s->bus_reg = (uint16_t)br;
+ }
+
+ if (temp_en) {
+ /* Die temperature: 12-bit signed value stored in bits [15:4] */
+ tr = s->temp_mc / INA238_TEMP_LSB_MC;
+ tr = MAX(-2048, MIN(2047, tr));
+ s->dietemp_reg = ((uint16_t)(int16_t)tr << 4) & INA238_TEMP_LIMIT_MASK;
+ }
+
+ /*
+ * In bus-only mode the current register is retained from the last shunt
+ * conversion rather than measured anew, but the power register is still
+ * refreshed.
+ */
+ if (bus_en) {
+ cur = (int16_t)s->current_reg;
+ if (cur < 0) {
+ cur = -cur;
+ }
+ pwr = cur * (int64_t)s->bus_reg / INA238_POWER_DIV;
+ if (pwr > INA238_POWER_MAX) {
+ mathof = 1;
+ pwr = INA238_POWER_MAX;
+ }
+ s->power_reg = (uint32_t)pwr;
+ }
+
+ /* Without a calibration the device reports neither current nor power. */
+ if (s->shunt_cal == 0) {
+ s->current_reg = 0;
+ s->power_reg = 0;
+ power_fresh = true;
+ }
+
+ s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, MATHOF, mathof);
+ s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, CNVRF, 1);
+
+ trace_ina238_convert(s->description, s->shunt_reg, s->bus_reg,
+ s->dietemp_reg, s->current_reg, s->power_reg);
+
+ ina238_update_alert(s, shunt_en, bus_en, temp_en, power_fresh);
+}
+
+/*
+ * A physical input, the calibration or the ADC range changed.
+ * - in continuous mode the device keeps converting and immediately tracks
+ * the new value
+ * - in a triggered mode it performs exactly one conversion per trigger
+ */
+static void ina238_input_update(INA238State *s)
+{
+ if (FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE) & 0x8) {
+ ina238_convert(s);
+ }
+}
+
+static void ina238_reset_regs(INA238State *s)
+{
+ s->config = INA238_CONFIG_POR;
+ s->adc_config = INA238_ADC_CONFIG_POR;
+ s->shunt_cal = INA238_SHUNT_CAL_POR;
+ s->diag_alrt = INA238_DIAG_ALRT_POR;
+ s->sovl = INA238_SOVL_POR;
+ s->suvl = INA238_SUVL_POR;
+ s->bovl = INA238_BOVL_POR;
+ s->buvl = INA238_BUVL_POR;
+ s->temp_limit = INA238_TEMP_LIMIT_POR;
+ s->pwr_limit = INA238_PWR_LIMIT_POR;
+
+ s->ptr = 0;
+ s->len = 0;
+ s->wr_hi = 0;
+ s->rx_byte = 0;
+ s->rx_width = 0;
+ s->rx_val = 0;
+
+ /*
+ * The measurement registers power up at zero and stay zero until the next
+ * conversion. The injected physical inputs are retained, but no conversion
+ * is performed here, so a software reset does not resurrect a previous
+ * measurement.
+ */
+ s->shunt_reg = 0;
+ s->bus_reg = 0;
+ s->dietemp_reg = 0;
+ s->current_reg = 0;
+ s->power_reg = 0;
+
+ ina238_update_alert(s, true, true, true, true);
+}
+
+static uint32_t ina238_read_reg(INA238State *s, uint8_t reg)
+{
+ uint32_t val;
+
+ switch (reg) {
+ case INA238_REG_CONFIG:
+ val = s->config;
+ break;
+ case INA238_REG_ADC_CONFIG:
+ val = s->adc_config;
+ break;
+ case INA238_REG_SHUNT_CAL:
+ val = s->shunt_cal;
+ break;
+ case INA238_REG_VSHUNT:
+ val = s->shunt_reg;
+ break;
+ case INA238_REG_VBUS:
+ val = s->bus_reg;
+ break;
+ case INA238_REG_DIETEMP:
+ val = s->dietemp_reg;
+ break;
+ case INA238_REG_CURRENT:
+ val = s->current_reg;
+ break;
+ case INA238_REG_POWER:
+ val = s->power_reg;
+ break;
+ case INA238_REG_DIAG_ALRT:
+ val = s->diag_alrt;
+ /*
+ * A read always clears CNVRF; the latched limit flags clear too, but
+ * only in latch mode.
+ */
+ s->diag_alrt &= ~R_INA238_DIAG_ALRT_CNVRF_MASK;
+ if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
+ s->diag_alrt &= ~INA238_DIAG_LATCH_MASK;
+ }
+ ina238_refresh_alert(s);
+ break;
+ case INA238_REG_SOVL:
+ val = s->sovl;
+ break;
+ case INA238_REG_SUVL:
+ val = s->suvl;
+ break;
+ case INA238_REG_BOVL:
+ val = s->bovl;
+ break;
+ case INA238_REG_BUVL:
+ val = s->buvl;
+ break;
+ case INA238_REG_TEMP_LIMIT:
+ val = s->temp_limit;
+ break;
+ case INA238_REG_PWR_LIMIT:
+ val = s->pwr_limit;
+ break;
+ case INA238_REG_MANUFACTURER_ID:
+ val = INA238_MANUFACTURER_ID_VAL;
+ break;
+ case INA238_REG_DEVICE_ID:
+ val = INA238_DEVICE_ID_VAL;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: read from undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ val = 0;
+ break;
+ }
+
+ trace_ina238_read(s->description, reg, val);
+ return val;
+}
+
+static void ina238_write_reg(INA238State *s, uint8_t reg, uint16_t val)
+{
+ trace_ina238_write(s->description, reg, val);
+
+ switch (reg) {
+ case INA238_REG_CONFIG:
+ if (val & R_INA238_CONFIG_RST_MASK) {
+ trace_ina238_reset(s->description, "reg");
+ ina238_reset_regs(s);
+ break;
+ }
+ s->config = val & INA238_CONFIG_WMASK;
+ ina238_input_update(s);
+ break;
+ case INA238_REG_ADC_CONFIG:
+ s->adc_config = val;
+ /* Writing the MODE bits (re)starts a conversion: it is the trigger. */
+ ina238_convert(s);
+ break;
+ case INA238_REG_SHUNT_CAL:
+ s->shunt_cal = val & INA238_SHUNT_CAL_MASK;
+ ina238_input_update(s);
+ break;
+ case INA238_REG_DIAG_ALRT: {
+ uint16_t status = s->diag_alrt & INA238_DIAG_STATUS_MASK;
+ /*
+ * In latch mode a write clears the latched limit flags and the
+ * conversion-ready flag. In transparent mode the flags track the live
+ * condition and are preserved. The pin is only refreshed, so a cleared
+ * flag re-asserts on the next conversion rather than immediately.
+ */
+ if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
+ status &= ~(R_INA238_DIAG_ALRT_CNVRF_MASK | INA238_DIAG_LATCH_MASK);
+ }
+ s->diag_alrt = (val & INA238_DIAG_CTRL_MASK) | status;
+ ina238_refresh_alert(s);
+ break;
+ }
+ case INA238_REG_SOVL:
+ s->sovl = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_SUVL:
+ s->suvl = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_BOVL:
+ s->bovl = val & INA238_BUS_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_BUVL:
+ s->buvl = val & INA238_BUS_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_TEMP_LIMIT:
+ s->temp_limit = val & INA238_TEMP_LIMIT_MASK;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_PWR_LIMIT:
+ s->pwr_limit = val;
+ ina238_update_alert(s, true, true, true, true);
+ break;
+ case INA238_REG_VSHUNT:
+ case INA238_REG_VBUS:
+ case INA238_REG_DIETEMP:
+ case INA238_REG_CURRENT:
+ case INA238_REG_POWER:
+ case INA238_REG_MANUFACTURER_ID:
+ case INA238_REG_DEVICE_ID:
+ 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 ina238_recv(I2CSlave *i2c)
+{
+ INA238State *s = INA238(i2c);
+ uint8_t byte;
+
+ if (s->rx_byte == 0) {
+ s->rx_val = ina238_read_reg(s, s->ptr);
+ s->rx_width = (s->ptr == INA238_REG_POWER) ? 3 : 2;
+ }
+
+ byte = (s->rx_val >> (8 * (s->rx_width - 1 - s->rx_byte))) & 0xff;
+ s->rx_byte++;
+ if (s->rx_byte >= s->rx_width) {
+ s->rx_byte = 0;
+ }
+
+ return byte;
+}
+
+static int ina238_send(I2CSlave *i2c, uint8_t data)
+{
+ INA238State *s = INA238(i2c);
+
+ if (s->len == 0) {
+ s->ptr = data;
+ } else if ((s->len & 1) == 1) {
+ s->wr_hi = data;
+ } else {
+ ina238_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
+ }
+ s->len++;
+
+ return 0;
+}
+
+static int ina238_event(I2CSlave *i2c, enum i2c_event event)
+{
+ INA238State *s = INA238(i2c);
+
+ s->len = 0;
+ s->rx_byte = 0;
+
+ return 0;
+}
+
+static void ina238_get_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->shunt_nv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_shunt(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA238_SHUNT_MIN_NV || value > INA238_SHUNT_MAX_NV) {
+ error_setg(errp,
+ "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
+ s->description, value,
+ INA238_SHUNT_MIN_NV, INA238_SHUNT_MAX_NV);
+ return;
+ }
+
+ s->shunt_nv = (int32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_get_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->bus_uv;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_bus(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < 0 || value > INA238_BUS_MAX_UV) {
+ error_setg(errp,
+ "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
+ s->description, value, INA238_BUS_MAX_UV);
+ return;
+ }
+
+ s->bus_uv = (uint32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_get_temp(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value = s->temp_mc;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void ina238_set_temp(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ INA238State *s = INA238(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INA238_TEMP_MIN_MC || value > INA238_TEMP_MAX_MC) {
+ error_setg(errp,
+ "%s: die-temperature %" PRId64
+ " out of range (%d..%d m-degC)",
+ s->description, value,
+ INA238_TEMP_MIN_MC, INA238_TEMP_MAX_MC);
+ return;
+ }
+
+ s->temp_mc = (int32_t)value;
+ ina238_input_update(s);
+}
+
+static void ina238_reset_hold(Object *obj, ResetType type)
+{
+ INA238State *s = INA238(obj);
+
+ trace_ina238_reset(s->description, "hw");
+ ina238_reset_regs(s);
+}
+
+static int ina238_post_load(void *opaque, int version_id)
+{
+ INA238State *s = opaque;
+
+ ina238_refresh_alert(s);
+
+ return 0;
+}
+
+static const VMStateDescription ina238_vmstate = {
+ .name = "INA238",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ina238_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8(ptr, INA238State),
+ VMSTATE_UINT8(len, INA238State),
+ VMSTATE_UINT8(wr_hi, INA238State),
+ VMSTATE_UINT8(rx_byte, INA238State),
+ VMSTATE_UINT8(rx_width, INA238State),
+ VMSTATE_UINT32(rx_val, INA238State),
+ VMSTATE_UINT16(config, INA238State),
+ VMSTATE_UINT16(adc_config, INA238State),
+ VMSTATE_UINT16(shunt_cal, INA238State),
+ VMSTATE_UINT16(diag_alrt, INA238State),
+ VMSTATE_UINT16(sovl, INA238State),
+ VMSTATE_UINT16(suvl, INA238State),
+ VMSTATE_UINT16(bovl, INA238State),
+ VMSTATE_UINT16(buvl, INA238State),
+ VMSTATE_UINT16(temp_limit, INA238State),
+ VMSTATE_UINT16(pwr_limit, INA238State),
+ VMSTATE_UINT16(shunt_reg, INA238State),
+ VMSTATE_UINT16(bus_reg, INA238State),
+ VMSTATE_UINT16(dietemp_reg, INA238State),
+ VMSTATE_UINT16(current_reg, INA238State),
+ VMSTATE_UINT32(power_reg, INA238State),
+ VMSTATE_INT32(shunt_nv, INA238State),
+ VMSTATE_UINT32(bus_uv, INA238State),
+ VMSTATE_INT32(temp_mc, INA238State),
+ VMSTATE_I2C_SLAVE(parent_obj, INA238State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ina238_initfn(Object *obj)
+{
+ object_property_add(obj, "shunt-voltage", "int", ina238_get_shunt,
+ ina238_set_shunt, NULL, NULL);
+ object_property_set_description(obj, "shunt-voltage",
+ "Injected shunt (differential) voltage, "
+ "in nanovolts");
+ object_property_add(obj, "bus-voltage", "int", ina238_get_bus,
+ ina238_set_bus, NULL, NULL);
+ object_property_set_description(obj, "bus-voltage",
+ "Injected bus voltage, in microvolts");
+ object_property_add(obj, "die-temperature", "int", ina238_get_temp,
+ ina238_set_temp, NULL, NULL);
+ object_property_set_description(obj, "die-temperature",
+ "Injected die temperature, "
+ "in millidegrees Celsius");
+}
+
+static void ina238_realize(DeviceState *dev, Error **errp)
+{
+ INA238State *s = INA238(dev);
+
+ if (!s->description) {
+ s->description = g_strdup(object_get_typename(OBJECT(dev)));
+ }
+
+ qdev_init_gpio_out(dev, &s->alert, 1);
+}
+
+static const Property ina238_properties[] = {
+ DEFINE_PROP_STRING("description", INA238State, description),
+};
+
+static void ina238_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 = ina238_event;
+ ic->recv = ina238_recv;
+ ic->send = ina238_send;
+ dc->realize = ina238_realize;
+ rc->phases.hold = ina238_reset_hold;
+ dc->vmsd = &ina238_vmstate;
+ device_class_set_props(dc, ina238_properties);
+}
+
+static const TypeInfo ina238_types[] = {
+ {
+ .name = TYPE_INA238,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = ina238_initfn,
+ .instance_size = sizeof(INA238State),
+ .class_init = ina238_class_init,
+ },
+};
+
+DEFINE_TYPES(ina238_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index b81f001fdf..7e6fd64488 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,5 +1,6 @@
system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.c'))
+system_ss.add(when: 'CONFIG_INA238', if_true: files('ina238.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 f809b6b506..98158e37c6 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -15,6 +15,13 @@ ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, u
ina230_alert(const char *id, bool level) "%s level %u"
ina230_reset(const char *id, const char *source) "%s %s"
+# ina238.c
+ina238_read(const char *id, uint8_t reg, uint32_t value) "%s reg 0x%02x val 0x%06x"
+ina238_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
+ina238_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t dietemp, uint16_t current, uint32_t power) "%s shunt 0x%04x bus 0x%04x dietemp 0x%04x current 0x%04x power 0x%06x"
+ina238_alert(const char *id, bool level) "%s level %u"
+ina238_reset(const char *id, const char *source) "%s %s"
+
# tmp105.c
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/include/hw/sensor/ina238.h b/include/hw/sensor/ina238.h
new file mode 100644
index 0000000000..3bc3c02073
--- /dev/null
+++ b/include/hw/sensor/ina238.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments INA238 current/voltage/power monitor with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_INA238_H
+#define HW_SENSOR_INA238_H
+
+#define TYPE_INA238 "ina238"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 41/50] tests/qtest: ina238: add current/voltage/power monitor test
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Introduce the INA238 qtest: the qgraph node, the QMP injection helpers
and the register map. Cover the reset defaults, the software reset, and
the Configuration register write mask.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 178 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
new file mode 100644
index 0000000000..6e59ed30b5
--- /dev/null
+++ b/tests/qtest/ina238-test.c
@@ -0,0 +1,177 @@
+/*
+ * QTest testcase for the INA238 current/voltage/power monitor
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define INA238_TEST_ID "ina238-test"
+#define INA238_TEST_ADDR 0x40
+
+/* Register pointer addresses */
+#define REG_CONFIG 0x00
+#define REG_ADC_CONFIG 0x01
+#define REG_SHUNT_CAL 0x02
+#define REG_VSHUNT 0x04
+#define REG_VBUS 0x05
+#define REG_DIETEMP 0x06
+#define REG_CURRENT 0x07
+#define REG_POWER 0x08
+#define REG_DIAG_ALRT 0x0B
+#define REG_SOVL 0x0C
+#define REG_SUVL 0x0D
+#define REG_BOVL 0x0E
+#define REG_BUVL 0x0F
+#define REG_TEMP_LIMIT 0x10
+#define REG_PWR_LIMIT 0x11
+#define REG_MANUFACTURER_ID 0x3E
+#define REG_DEVICE_ID 0x3F
+
+/* Configuration register bits */
+#define CONFIG_RST 0x8000
+#define CONFIG_ADCRANGE 0x0010
+
+/* DIAG_ALRT register bits */
+#define DIAG_MEMSTAT 0x0001
+#define DIAG_CNVRF 0x0002
+#define DIAG_POL 0x0004
+#define DIAG_BUSUL 0x0008
+#define DIAG_BUSOL 0x0010
+#define DIAG_SHNTUL 0x0020
+#define DIAG_SHNTOL 0x0040
+#define DIAG_TMPOL 0x0080
+#define DIAG_MATHOF 0x0200
+#define DIAG_APOL 0x1000
+#define DIAG_SLOWALERT 0x2000
+#define DIAG_CNVR 0x4000
+#define DIAG_ALATCH 0x8000
+
+/* Reset values */
+#define CONFIG_POR 0x0000
+#define ADC_CONFIG_POR 0xFB68
+#define SHUNT_CAL_POR 0x1000
+#define DIAG_ALRT_POR 0x0001
+#define SOVL_POR 0x7FFF
+#define SUVL_POR 0x8000
+#define BOVL_POR 0x7FFF
+#define BUVL_POR 0x0000
+#define TEMP_LIMIT_POR 0x7FF0
+#define PWR_LIMIT_POR 0xFFFF
+#define MANUFACTURER_ID_VAL 0x5449
+#define DEVICE_ID_VAL 0x2381
+
+/* Read the 24-bit POWER register (MSB first) */
+static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
+{
+ uint8_t resp[3];
+
+ i2c_read_block(dev, reg, resp, sizeof(resp));
+ return (resp[0] << 16) | (resp[1] << 8) | resp[2];
+}
+
+/* QMP helpers for injecting the physical inputs */
+
+static void qmp_ina238_set(const char *property, int value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %d } }",
+ INA238_TEST_ID, property, value);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+/* Power-on-reset default values and the ID registers */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_ADC_CONFIG), ==, ADC_CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get24(dev, REG_POWER), ==, 0x000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SUVL), ==, SUVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_BOVL), ==, BOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_BUVL), ==, BUVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_PWR_LIMIT), ==, PWR_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MANUFACTURER_ID), ==,
+ MANUFACTURER_ID_VAL);
+ g_assert_cmphex(i2c_get16(dev, REG_DEVICE_ID), ==, DEVICE_ID_VAL);
+}
+
+/* Software reset via the Configuration RST bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1234);
+ i2c_set16(dev, REG_SOVL, 0x0500);
+ i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ qmp_ina238_set("die-temperature", 25000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, 0x1234);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, 0x0500);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+ g_assert_cmpuint(i2c_get16(dev, REG_CURRENT), !=, 0);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+}
+
+/* CONFIG keeps only the writable ADCRANGE/CONVDLY bits and self-clears RST */
+static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE | 0x0040);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_ADCRANGE | 0x0040);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+}
+
+static void ina238_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" INA238_TEST_ID ",address=0x40"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { INA238_TEST_ADDR });
+
+ qos_node_create_driver("ina238", i2c_device_create);
+ qos_node_consumes("ina238", "i2c-bus", &opts);
+
+ qos_add_test("defaults", "ina238", test_defaults, NULL);
+ qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
+ qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
+}
+libqos_init(ina238_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e413a0cc75..7005509d4d 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -331,6 +331,7 @@ qos_test_ss.add(
'eepro100-test.c',
'es1370-test.c',
'ina230-test.c',
+ 'ina238-test.c',
'lsm303dlhc-mag-test.c',
'isl_pmbus_vr-test.c',
'max34451-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 41/50] tests/qtest: ina238: add current/voltage/power monitor test
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Introduce the INA238 qtest: the qgraph node, the QMP injection helpers
and the register map. Cover the reset defaults, the software reset, and
the Configuration register write mask.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 178 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
new file mode 100644
index 0000000000..6e59ed30b5
--- /dev/null
+++ b/tests/qtest/ina238-test.c
@@ -0,0 +1,177 @@
+/*
+ * QTest testcase for the INA238 current/voltage/power monitor
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define INA238_TEST_ID "ina238-test"
+#define INA238_TEST_ADDR 0x40
+
+/* Register pointer addresses */
+#define REG_CONFIG 0x00
+#define REG_ADC_CONFIG 0x01
+#define REG_SHUNT_CAL 0x02
+#define REG_VSHUNT 0x04
+#define REG_VBUS 0x05
+#define REG_DIETEMP 0x06
+#define REG_CURRENT 0x07
+#define REG_POWER 0x08
+#define REG_DIAG_ALRT 0x0B
+#define REG_SOVL 0x0C
+#define REG_SUVL 0x0D
+#define REG_BOVL 0x0E
+#define REG_BUVL 0x0F
+#define REG_TEMP_LIMIT 0x10
+#define REG_PWR_LIMIT 0x11
+#define REG_MANUFACTURER_ID 0x3E
+#define REG_DEVICE_ID 0x3F
+
+/* Configuration register bits */
+#define CONFIG_RST 0x8000
+#define CONFIG_ADCRANGE 0x0010
+
+/* DIAG_ALRT register bits */
+#define DIAG_MEMSTAT 0x0001
+#define DIAG_CNVRF 0x0002
+#define DIAG_POL 0x0004
+#define DIAG_BUSUL 0x0008
+#define DIAG_BUSOL 0x0010
+#define DIAG_SHNTUL 0x0020
+#define DIAG_SHNTOL 0x0040
+#define DIAG_TMPOL 0x0080
+#define DIAG_MATHOF 0x0200
+#define DIAG_APOL 0x1000
+#define DIAG_SLOWALERT 0x2000
+#define DIAG_CNVR 0x4000
+#define DIAG_ALATCH 0x8000
+
+/* Reset values */
+#define CONFIG_POR 0x0000
+#define ADC_CONFIG_POR 0xFB68
+#define SHUNT_CAL_POR 0x1000
+#define DIAG_ALRT_POR 0x0001
+#define SOVL_POR 0x7FFF
+#define SUVL_POR 0x8000
+#define BOVL_POR 0x7FFF
+#define BUVL_POR 0x0000
+#define TEMP_LIMIT_POR 0x7FF0
+#define PWR_LIMIT_POR 0xFFFF
+#define MANUFACTURER_ID_VAL 0x5449
+#define DEVICE_ID_VAL 0x2381
+
+/* Read the 24-bit POWER register (MSB first) */
+static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
+{
+ uint8_t resp[3];
+
+ i2c_read_block(dev, reg, resp, sizeof(resp));
+ return (resp[0] << 16) | (resp[1] << 8) | resp[2];
+}
+
+/* QMP helpers for injecting the physical inputs */
+
+static void qmp_ina238_set(const char *property, int value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %d } }",
+ INA238_TEST_ID, property, value);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+/* Power-on-reset default values and the ID registers */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_ADC_CONFIG), ==, ADC_CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get24(dev, REG_POWER), ==, 0x000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SUVL), ==, SUVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_BOVL), ==, BOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_BUVL), ==, BUVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_PWR_LIMIT), ==, PWR_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MANUFACTURER_ID), ==,
+ MANUFACTURER_ID_VAL);
+ g_assert_cmphex(i2c_get16(dev, REG_DEVICE_ID), ==, DEVICE_ID_VAL);
+}
+
+/* Software reset via the Configuration RST bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1234);
+ i2c_set16(dev, REG_SOVL, 0x0500);
+ i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ qmp_ina238_set("die-temperature", 25000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, 0x1234);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, 0x0500);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+ g_assert_cmpuint(i2c_get16(dev, REG_CURRENT), !=, 0);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+}
+
+/* CONFIG keeps only the writable ADCRANGE/CONVDLY bits and self-clears RST */
+static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE | 0x0040);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_ADCRANGE | 0x0040);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+}
+
+static void ina238_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" INA238_TEST_ID ",address=0x40"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { INA238_TEST_ADDR });
+
+ qos_node_create_driver("ina238", i2c_device_create);
+ qos_node_consumes("ina238", "i2c-bus", &opts);
+
+ qos_add_test("defaults", "ina238", test_defaults, NULL);
+ qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
+ qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
+}
+libqos_init(ina238_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e413a0cc75..7005509d4d 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -331,6 +331,7 @@ qos_test_ss.add(
'eepro100-test.c',
'es1370-test.c',
'ina230-test.c',
+ 'ina238-test.c',
'lsm303dlhc-mag-test.c',
'isl_pmbus_vr-test.c',
'max34451-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 41/50] tests/qtest: ina238: add current/voltage/power monitor test
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Introduce the INA238 qtest: the qgraph node, the QMP injection helpers
and the register map. Cover the reset defaults, the software reset, and
the Configuration register write mask.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 178 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
new file mode 100644
index 0000000000..6e59ed30b5
--- /dev/null
+++ b/tests/qtest/ina238-test.c
@@ -0,0 +1,177 @@
+/*
+ * QTest testcase for the INA238 current/voltage/power monitor
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define INA238_TEST_ID "ina238-test"
+#define INA238_TEST_ADDR 0x40
+
+/* Register pointer addresses */
+#define REG_CONFIG 0x00
+#define REG_ADC_CONFIG 0x01
+#define REG_SHUNT_CAL 0x02
+#define REG_VSHUNT 0x04
+#define REG_VBUS 0x05
+#define REG_DIETEMP 0x06
+#define REG_CURRENT 0x07
+#define REG_POWER 0x08
+#define REG_DIAG_ALRT 0x0B
+#define REG_SOVL 0x0C
+#define REG_SUVL 0x0D
+#define REG_BOVL 0x0E
+#define REG_BUVL 0x0F
+#define REG_TEMP_LIMIT 0x10
+#define REG_PWR_LIMIT 0x11
+#define REG_MANUFACTURER_ID 0x3E
+#define REG_DEVICE_ID 0x3F
+
+/* Configuration register bits */
+#define CONFIG_RST 0x8000
+#define CONFIG_ADCRANGE 0x0010
+
+/* DIAG_ALRT register bits */
+#define DIAG_MEMSTAT 0x0001
+#define DIAG_CNVRF 0x0002
+#define DIAG_POL 0x0004
+#define DIAG_BUSUL 0x0008
+#define DIAG_BUSOL 0x0010
+#define DIAG_SHNTUL 0x0020
+#define DIAG_SHNTOL 0x0040
+#define DIAG_TMPOL 0x0080
+#define DIAG_MATHOF 0x0200
+#define DIAG_APOL 0x1000
+#define DIAG_SLOWALERT 0x2000
+#define DIAG_CNVR 0x4000
+#define DIAG_ALATCH 0x8000
+
+/* Reset values */
+#define CONFIG_POR 0x0000
+#define ADC_CONFIG_POR 0xFB68
+#define SHUNT_CAL_POR 0x1000
+#define DIAG_ALRT_POR 0x0001
+#define SOVL_POR 0x7FFF
+#define SUVL_POR 0x8000
+#define BOVL_POR 0x7FFF
+#define BUVL_POR 0x0000
+#define TEMP_LIMIT_POR 0x7FF0
+#define PWR_LIMIT_POR 0xFFFF
+#define MANUFACTURER_ID_VAL 0x5449
+#define DEVICE_ID_VAL 0x2381
+
+/* Read the 24-bit POWER register (MSB first) */
+static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
+{
+ uint8_t resp[3];
+
+ i2c_read_block(dev, reg, resp, sizeof(resp));
+ return (resp[0] << 16) | (resp[1] << 8) | resp[2];
+}
+
+/* QMP helpers for injecting the physical inputs */
+
+static void qmp_ina238_set(const char *property, int value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %d } }",
+ INA238_TEST_ID, property, value);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+/* Power-on-reset default values and the ID registers */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_ADC_CONFIG), ==, ADC_CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmphex(i2c_get24(dev, REG_POWER), ==, 0x000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SUVL), ==, SUVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_BOVL), ==, BOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_BUVL), ==, BUVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_PWR_LIMIT), ==, PWR_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_MANUFACTURER_ID), ==,
+ MANUFACTURER_ID_VAL);
+ g_assert_cmphex(i2c_get16(dev, REG_DEVICE_ID), ==, DEVICE_ID_VAL);
+}
+
+/* Software reset via the Configuration RST bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1234);
+ i2c_set16(dev, REG_SOVL, 0x0500);
+ i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ qmp_ina238_set("die-temperature", 25000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, 0x1234);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, 0x0500);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+ g_assert_cmpuint(i2c_get16(dev, REG_CURRENT), !=, 0);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+}
+
+/* CONFIG keeps only the writable ADCRANGE/CONVDLY bits and self-clears RST */
+static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE | 0x0040);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_ADCRANGE | 0x0040);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
+}
+
+static void ina238_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" INA238_TEST_ID ",address=0x40"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { INA238_TEST_ADDR });
+
+ qos_node_create_driver("ina238", i2c_device_create);
+ qos_node_consumes("ina238", "i2c-bus", &opts);
+
+ qos_add_test("defaults", "ina238", test_defaults, NULL);
+ qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
+ qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
+}
+libqos_init(ina238_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e413a0cc75..7005509d4d 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -331,6 +331,7 @@ qos_test_ss.add(
'eepro100-test.c',
'es1370-test.c',
'ina230-test.c',
+ 'ina238-test.c',
'lsm303dlhc-mag-test.c',
'isl_pmbus_vr-test.c',
'max34451-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 42/50] tests/qtest: ina238: cover voltage/temperature injection and derivation
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt-, bus- and die-temperature injection properties and the
current and power values derived through the Shunt Calibration register,
including the zero-calibration case, the ADCRANGE rescaling and the
clamp/overflow edges.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 6e59ed30b5..693139f988 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -89,6 +89,20 @@ static void qmp_ina238_set(const char *property, int value)
qobject_unref(resp);
}
+static int qmp_ina238_get(const char *property)
+{
+ QDict *resp;
+ int ret;
+
+ resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': %s } }",
+ INA238_TEST_ID, property);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_int(resp, "return");
+ qobject_unref(resp);
+ return ret;
+}
+
/* Power-on-reset default values and the ID registers */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -160,6 +174,139 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
}
+/* Shunt voltage register */
+static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+
+ qmp_ina238_set("shunt-voltage", -80000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0xC180);
+
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, -80000000);
+}
+
+/* Bus voltage register */
+static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x3C00);
+
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+}
+
+/* Die temperature register */
+static void test_temp_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("die-temperature", 25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0C80);
+
+ qmp_ina238_set("die-temperature", -25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xF380);
+
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, -25000);
+}
+
+/* Calibration-derived current */
+static void test_calibration_current(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x4CCC);
+}
+
+/* Calibration-derived power */
+static void test_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 4718400);
+}
+
+/* With SHUNT_CAL == 0 the current and power registers stay zero */
+static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+}
+
+/* ADCRANGE changes the shunt LSB, not the current divisor. */
+static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0800);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x3E80);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7D00);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x2000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
+}
+
+/* Clamping at full scale and the MATHOF flag */
+static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
+ qmp_ina238_set("shunt-voltage", -97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ qmp_ina238_set("shunt-voltage", 163835000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0001);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_MATHOF, ==,
+ DIAG_MATHOF);
+}
+
+/* Shunt over-limit alert (transparent mode) */
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -173,5 +320,14 @@ static void ina238_register_nodes(void)
qos_add_test("defaults", "ina238", test_defaults, NULL);
qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
+ qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
+ qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
+ qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
+ qos_add_test("calibration-current", "ina238", test_calibration_current,
+ NULL);
+ qos_add_test("power", "ina238", test_power, NULL);
+ qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
+ qos_add_test("adcrange", "ina238", test_adcrange, NULL);
+ qos_add_test("overflow", "ina238", test_overflow, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 42/50] tests/qtest: ina238: cover voltage/temperature injection and derivation
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt-, bus- and die-temperature injection properties and the
current and power values derived through the Shunt Calibration register,
including the zero-calibration case, the ADCRANGE rescaling and the
clamp/overflow edges.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 6e59ed30b5..693139f988 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -89,6 +89,20 @@ static void qmp_ina238_set(const char *property, int value)
qobject_unref(resp);
}
+static int qmp_ina238_get(const char *property)
+{
+ QDict *resp;
+ int ret;
+
+ resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': %s } }",
+ INA238_TEST_ID, property);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_int(resp, "return");
+ qobject_unref(resp);
+ return ret;
+}
+
/* Power-on-reset default values and the ID registers */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -160,6 +174,139 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
}
+/* Shunt voltage register */
+static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+
+ qmp_ina238_set("shunt-voltage", -80000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0xC180);
+
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, -80000000);
+}
+
+/* Bus voltage register */
+static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x3C00);
+
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+}
+
+/* Die temperature register */
+static void test_temp_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("die-temperature", 25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0C80);
+
+ qmp_ina238_set("die-temperature", -25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xF380);
+
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, -25000);
+}
+
+/* Calibration-derived current */
+static void test_calibration_current(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x4CCC);
+}
+
+/* Calibration-derived power */
+static void test_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 4718400);
+}
+
+/* With SHUNT_CAL == 0 the current and power registers stay zero */
+static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+}
+
+/* ADCRANGE changes the shunt LSB, not the current divisor. */
+static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0800);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x3E80);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7D00);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x2000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
+}
+
+/* Clamping at full scale and the MATHOF flag */
+static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
+ qmp_ina238_set("shunt-voltage", -97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ qmp_ina238_set("shunt-voltage", 163835000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0001);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_MATHOF, ==,
+ DIAG_MATHOF);
+}
+
+/* Shunt over-limit alert (transparent mode) */
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -173,5 +320,14 @@ static void ina238_register_nodes(void)
qos_add_test("defaults", "ina238", test_defaults, NULL);
qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
+ qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
+ qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
+ qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
+ qos_add_test("calibration-current", "ina238", test_calibration_current,
+ NULL);
+ qos_add_test("power", "ina238", test_power, NULL);
+ qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
+ qos_add_test("adcrange", "ina238", test_adcrange, NULL);
+ qos_add_test("overflow", "ina238", test_overflow, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 42/50] tests/qtest: ina238: cover voltage/temperature injection and derivation
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt-, bus- and die-temperature injection properties and the
current and power values derived through the Shunt Calibration register,
including the zero-calibration case, the ADCRANGE rescaling and the
clamp/overflow edges.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 6e59ed30b5..693139f988 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -89,6 +89,20 @@ static void qmp_ina238_set(const char *property, int value)
qobject_unref(resp);
}
+static int qmp_ina238_get(const char *property)
+{
+ QDict *resp;
+ int ret;
+
+ resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': %s } }",
+ INA238_TEST_ID, property);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_int(resp, "return");
+ qobject_unref(resp);
+ return ret;
+}
+
/* Power-on-reset default values and the ID registers */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -160,6 +174,139 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
}
+/* Shunt voltage register */
+static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+
+ qmp_ina238_set("shunt-voltage", -80000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0xC180);
+
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, -80000000);
+}
+
+/* Bus voltage register */
+static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x3C00);
+
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+}
+
+/* Die temperature register */
+static void test_temp_injection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("die-temperature", 25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0C80);
+
+ qmp_ina238_set("die-temperature", -25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xF380);
+
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, -25000);
+}
+
+/* Calibration-derived current */
+static void test_calibration_current(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x4CCC);
+}
+
+/* Calibration-derived power */
+static void test_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 4718400);
+}
+
+/* With SHUNT_CAL == 0 the current and power registers stay zero */
+static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+}
+
+/* ADCRANGE changes the shunt LSB, not the current divisor. */
+static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0800);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x3E80);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7D00);
+
+ i2c_set16(dev, REG_SHUNT_CAL, 0x2000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
+}
+
+/* Clamping at full scale and the MATHOF flag */
+static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
+ qmp_ina238_set("shunt-voltage", -97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_POR);
+ qmp_ina238_set("shunt-voltage", 163835000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0001);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_MATHOF, ==,
+ DIAG_MATHOF);
+}
+
+/* Shunt over-limit alert (transparent mode) */
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -173,5 +320,14 @@ static void ina238_register_nodes(void)
qos_add_test("defaults", "ina238", test_defaults, NULL);
qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
+ qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
+ qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
+ qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
+ qos_add_test("calibration-current", "ina238", test_calibration_current,
+ NULL);
+ qos_add_test("power", "ina238", test_power, NULL);
+ qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
+ qos_add_test("adcrange", "ina238", test_adcrange, NULL);
+ qos_add_test("overflow", "ina238", test_overflow, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 43/50] tests/qtest: ina238: cover the ALERT path and conversion-ready flag
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt/bus/temperature/power alert thresholds, the multi-alert
OR behaviour, the alert latch, the DIAG_ALRT control bits and the
conversion-ready (CNVRF) flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 146 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 145 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 693139f988..08813377c9 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -306,7 +306,143 @@ static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
DIAG_MATHOF);
}
-/* Shunt over-limit alert (transparent mode) */
+/* Shunt over-limit alert */
+static void test_alert_shunt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* Bus over-limit alert */
+static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_BOVL, 0x1000);
+
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, 0);
+
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, DIAG_BUSOL);
+}
+
+/* Over-temperature alert */
+static void test_alert_temp(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
+
+ qmp_ina238_set("die-temperature", 10000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, 0);
+
+ qmp_ina238_set("die-temperature", 25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, DIAG_TMPOL);
+}
+
+/* Power over-limit alert */
+static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+
+ i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
+
+ i2c_set16(dev, REG_PWR_LIMIT, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
+}
+
+/* Two thresholds crossed at once are reported simultaneously (multi-alert) */
+static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t diag;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_BOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+
+ diag = i2c_get16(dev, REG_DIAG_ALRT);
+ g_assert_cmphex(diag & DIAG_SHNTOL, ==, DIAG_SHNTOL);
+ g_assert_cmphex(diag & DIAG_BUSOL, ==, DIAG_BUSOL);
+}
+
+/* Latch mode holds the flag until the DIAG_ALRT register is read */
+static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("shunt-voltage", 5000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* DIAG_ALRT control bits are writable; status bits are read-only */
+static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t diag;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_DIAG_ALRT,
+ DIAG_ALATCH | DIAG_CNVR | DIAG_SLOWALERT | DIAG_APOL);
+ diag = i2c_get16(dev, REG_DIAG_ALRT);
+ g_assert_cmphex(diag & DIAG_ALATCH, ==, DIAG_ALATCH);
+ g_assert_cmphex(diag & DIAG_CNVR, ==, DIAG_CNVR);
+ g_assert_cmphex(diag & DIAG_SLOWALERT, ==, DIAG_SLOWALERT);
+ g_assert_cmphex(diag & DIAG_APOL, ==, DIAG_APOL);
+ /* MEMSTAT stays set */
+ g_assert_cmphex(diag & DIAG_MEMSTAT, ==, DIAG_MEMSTAT);
+}
+
+/* Every DIAG_ALRT read clears CNVRF, whatever the alert-latch mode. */
+static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+}
+
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -329,5 +465,13 @@ static void ina238_register_nodes(void)
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
qos_add_test("overflow", "ina238", test_overflow, NULL);
+ qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
+ qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
+ qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
+ qos_add_test("alert-power", "ina238", test_alert_power, NULL);
+ qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
+ qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
+ qos_add_test("diag-control", "ina238", test_diag_control, NULL);
+ qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 43/50] tests/qtest: ina238: cover the ALERT path and conversion-ready flag
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt/bus/temperature/power alert thresholds, the multi-alert
OR behaviour, the alert latch, the DIAG_ALRT control bits and the
conversion-ready (CNVRF) flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 146 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 145 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 693139f988..08813377c9 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -306,7 +306,143 @@ static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
DIAG_MATHOF);
}
-/* Shunt over-limit alert (transparent mode) */
+/* Shunt over-limit alert */
+static void test_alert_shunt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* Bus over-limit alert */
+static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_BOVL, 0x1000);
+
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, 0);
+
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, DIAG_BUSOL);
+}
+
+/* Over-temperature alert */
+static void test_alert_temp(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
+
+ qmp_ina238_set("die-temperature", 10000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, 0);
+
+ qmp_ina238_set("die-temperature", 25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, DIAG_TMPOL);
+}
+
+/* Power over-limit alert */
+static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+
+ i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
+
+ i2c_set16(dev, REG_PWR_LIMIT, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
+}
+
+/* Two thresholds crossed at once are reported simultaneously (multi-alert) */
+static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t diag;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_BOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+
+ diag = i2c_get16(dev, REG_DIAG_ALRT);
+ g_assert_cmphex(diag & DIAG_SHNTOL, ==, DIAG_SHNTOL);
+ g_assert_cmphex(diag & DIAG_BUSOL, ==, DIAG_BUSOL);
+}
+
+/* Latch mode holds the flag until the DIAG_ALRT register is read */
+static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("shunt-voltage", 5000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* DIAG_ALRT control bits are writable; status bits are read-only */
+static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t diag;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_DIAG_ALRT,
+ DIAG_ALATCH | DIAG_CNVR | DIAG_SLOWALERT | DIAG_APOL);
+ diag = i2c_get16(dev, REG_DIAG_ALRT);
+ g_assert_cmphex(diag & DIAG_ALATCH, ==, DIAG_ALATCH);
+ g_assert_cmphex(diag & DIAG_CNVR, ==, DIAG_CNVR);
+ g_assert_cmphex(diag & DIAG_SLOWALERT, ==, DIAG_SLOWALERT);
+ g_assert_cmphex(diag & DIAG_APOL, ==, DIAG_APOL);
+ /* MEMSTAT stays set */
+ g_assert_cmphex(diag & DIAG_MEMSTAT, ==, DIAG_MEMSTAT);
+}
+
+/* Every DIAG_ALRT read clears CNVRF, whatever the alert-latch mode. */
+static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+}
+
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -329,5 +465,13 @@ static void ina238_register_nodes(void)
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
qos_add_test("overflow", "ina238", test_overflow, NULL);
+ qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
+ qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
+ qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
+ qos_add_test("alert-power", "ina238", test_alert_power, NULL);
+ qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
+ qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
+ qos_add_test("diag-control", "ina238", test_diag_control, NULL);
+ qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 43/50] tests/qtest: ina238: cover the ALERT path and conversion-ready flag
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Cover the shunt/bus/temperature/power alert thresholds, the multi-alert
OR behaviour, the alert latch, the DIAG_ALRT control bits and the
conversion-ready (CNVRF) flag.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 146 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 145 insertions(+), 1 deletion(-)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 693139f988..08813377c9 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -306,7 +306,143 @@ static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
DIAG_MATHOF);
}
-/* Shunt over-limit alert (transparent mode) */
+/* Shunt over-limit alert */
+static void test_alert_shunt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* Bus over-limit alert */
+static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_BOVL, 0x1000);
+
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, 0);
+
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, DIAG_BUSOL);
+}
+
+/* Over-temperature alert */
+static void test_alert_temp(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
+
+ qmp_ina238_set("die-temperature", 10000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, 0);
+
+ qmp_ina238_set("die-temperature", 25000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, DIAG_TMPOL);
+}
+
+/* Power over-limit alert */
+static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+
+ i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
+
+ i2c_set16(dev, REG_PWR_LIMIT, 0x7FFF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
+}
+
+/* Two thresholds crossed at once are reported simultaneously (multi-alert) */
+static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t diag;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_BOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+
+ diag = i2c_get16(dev, REG_DIAG_ALRT);
+ g_assert_cmphex(diag & DIAG_SHNTOL, ==, DIAG_SHNTOL);
+ g_assert_cmphex(diag & DIAG_BUSOL, ==, DIAG_BUSOL);
+}
+
+/* Latch mode holds the flag until the DIAG_ALRT register is read */
+static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("shunt-voltage", 5000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* DIAG_ALRT control bits are writable; status bits are read-only */
+static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t diag;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ i2c_set16(dev, REG_DIAG_ALRT,
+ DIAG_ALATCH | DIAG_CNVR | DIAG_SLOWALERT | DIAG_APOL);
+ diag = i2c_get16(dev, REG_DIAG_ALRT);
+ g_assert_cmphex(diag & DIAG_ALATCH, ==, DIAG_ALATCH);
+ g_assert_cmphex(diag & DIAG_CNVR, ==, DIAG_CNVR);
+ g_assert_cmphex(diag & DIAG_SLOWALERT, ==, DIAG_SLOWALERT);
+ g_assert_cmphex(diag & DIAG_APOL, ==, DIAG_APOL);
+ /* MEMSTAT stays set */
+ g_assert_cmphex(diag & DIAG_MEMSTAT, ==, DIAG_MEMSTAT);
+}
+
+/* Every DIAG_ALRT read clears CNVRF, whatever the alert-latch mode. */
+static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+}
+
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -329,5 +465,13 @@ static void ina238_register_nodes(void)
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
qos_add_test("overflow", "ina238", test_overflow, NULL);
+ qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
+ qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
+ qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
+ qos_add_test("alert-power", "ina238", test_alert_power, NULL);
+ qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
+ qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
+ qos_add_test("diag-control", "ina238", test_diag_control, NULL);
+ qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 44/50] tests/qtest: ina238: cover the injection-property limits
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that the extremes of the shunt-voltage, bus-voltage and
die-temperature injection ranges round-trip through the QOM property
without truncation or wraparound, and that values just outside are
rejected rather than silently wrapped.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 08813377c9..74912424c0 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -67,6 +67,13 @@
#define MANUFACTURER_ID_VAL 0x5449
#define DEVICE_ID_VAL 0x2381
+/* Injection-property limits (see ina238.c) */
+#define SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
+#define SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
+#define BUS_MAX_UV 85000000 /* 27200 * 3.125 mV */
+#define TEMP_MIN_MC (-40000) /* -320 * 125 m-degC */
+#define TEMP_MAX_MC 125000 /* 1000 * 125 m-degC */
+
/* Read the 24-bit POWER register (MSB first) */
static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
{
@@ -103,6 +110,18 @@ static int qmp_ina238_get(const char *property)
return ret;
}
+/* A qom-set expected to be rejected (value outside the supported range) */
+static void qmp_ina238_set_fail(const char *property, int64_t value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %lld } }",
+ INA238_TEST_ID, property, (long long)value);
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
+
/* Power-on-reset default values and the ID registers */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -443,6 +462,68 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
}
+/* Shunt-voltage injection limits */
+static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", SHUNT_MAX_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MAX_NV);
+
+ qmp_ina238_set("shunt-voltage", SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+
+ qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
+ qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+}
+
+/* Bus-voltage injection limits */
+static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("bus-voltage", 0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+
+ qmp_ina238_set("bus-voltage", BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
+ g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
+
+ qmp_ina238_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
+ qmp_ina238_set_fail("bus-voltage", -1);
+ g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
+}
+
+/* Die-temperature injection limits */
+static void test_temp_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("die-temperature", TEMP_MAX_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x3E80);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MAX_MC);
+
+ qmp_ina238_set("die-temperature", TEMP_MIN_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
+
+ qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MAX_MC + 1);
+ qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MIN_MC - 1);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
+}
+
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -459,6 +540,9 @@ static void ina238_register_nodes(void)
qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
+ qos_add_test("shunt-limits", "ina238", test_shunt_limits, NULL);
+ qos_add_test("bus-limits", "ina238", test_bus_limits, NULL);
+ qos_add_test("temp-limits", "ina238", test_temp_limits, NULL);
qos_add_test("calibration-current", "ina238", test_calibration_current,
NULL);
qos_add_test("power", "ina238", test_power, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 44/50] tests/qtest: ina238: cover the injection-property limits
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that the extremes of the shunt-voltage, bus-voltage and
die-temperature injection ranges round-trip through the QOM property
without truncation or wraparound, and that values just outside are
rejected rather than silently wrapped.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 08813377c9..74912424c0 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -67,6 +67,13 @@
#define MANUFACTURER_ID_VAL 0x5449
#define DEVICE_ID_VAL 0x2381
+/* Injection-property limits (see ina238.c) */
+#define SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
+#define SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
+#define BUS_MAX_UV 85000000 /* 27200 * 3.125 mV */
+#define TEMP_MIN_MC (-40000) /* -320 * 125 m-degC */
+#define TEMP_MAX_MC 125000 /* 1000 * 125 m-degC */
+
/* Read the 24-bit POWER register (MSB first) */
static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
{
@@ -103,6 +110,18 @@ static int qmp_ina238_get(const char *property)
return ret;
}
+/* A qom-set expected to be rejected (value outside the supported range) */
+static void qmp_ina238_set_fail(const char *property, int64_t value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %lld } }",
+ INA238_TEST_ID, property, (long long)value);
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
+
/* Power-on-reset default values and the ID registers */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -443,6 +462,68 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
}
+/* Shunt-voltage injection limits */
+static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", SHUNT_MAX_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MAX_NV);
+
+ qmp_ina238_set("shunt-voltage", SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+
+ qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
+ qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+}
+
+/* Bus-voltage injection limits */
+static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("bus-voltage", 0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+
+ qmp_ina238_set("bus-voltage", BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
+ g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
+
+ qmp_ina238_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
+ qmp_ina238_set_fail("bus-voltage", -1);
+ g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
+}
+
+/* Die-temperature injection limits */
+static void test_temp_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("die-temperature", TEMP_MAX_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x3E80);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MAX_MC);
+
+ qmp_ina238_set("die-temperature", TEMP_MIN_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
+
+ qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MAX_MC + 1);
+ qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MIN_MC - 1);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
+}
+
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -459,6 +540,9 @@ static void ina238_register_nodes(void)
qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
+ qos_add_test("shunt-limits", "ina238", test_shunt_limits, NULL);
+ qos_add_test("bus-limits", "ina238", test_bus_limits, NULL);
+ qos_add_test("temp-limits", "ina238", test_temp_limits, NULL);
qos_add_test("calibration-current", "ina238", test_calibration_current,
NULL);
qos_add_test("power", "ina238", test_power, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 44/50] tests/qtest: ina238: cover the injection-property limits
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that the extremes of the shunt-voltage, bus-voltage and
die-temperature injection ranges round-trip through the QOM property
without truncation or wraparound, and that values just outside are
rejected rather than silently wrapped.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 08813377c9..74912424c0 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -67,6 +67,13 @@
#define MANUFACTURER_ID_VAL 0x5449
#define DEVICE_ID_VAL 0x2381
+/* Injection-property limits (see ina238.c) */
+#define SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
+#define SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
+#define BUS_MAX_UV 85000000 /* 27200 * 3.125 mV */
+#define TEMP_MIN_MC (-40000) /* -320 * 125 m-degC */
+#define TEMP_MAX_MC 125000 /* 1000 * 125 m-degC */
+
/* Read the 24-bit POWER register (MSB first) */
static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
{
@@ -103,6 +110,18 @@ static int qmp_ina238_get(const char *property)
return ret;
}
+/* A qom-set expected to be rejected (value outside the supported range) */
+static void qmp_ina238_set_fail(const char *property, int64_t value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %lld } }",
+ INA238_TEST_ID, property, (long long)value);
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
+
/* Power-on-reset default values and the ID registers */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -443,6 +462,68 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
}
+/* Shunt-voltage injection limits */
+static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("shunt-voltage", SHUNT_MAX_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MAX_NV);
+
+ qmp_ina238_set("shunt-voltage", SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+
+ qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
+ qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
+ g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
+}
+
+/* Bus-voltage injection limits */
+static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("bus-voltage", 0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
+
+ qmp_ina238_set("bus-voltage", BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
+ g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
+
+ qmp_ina238_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
+ qmp_ina238_set_fail("bus-voltage", -1);
+ g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
+}
+
+/* Die-temperature injection limits */
+static void test_temp_limits(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+
+ qmp_ina238_set("die-temperature", TEMP_MAX_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x3E80);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MAX_MC);
+
+ qmp_ina238_set("die-temperature", TEMP_MIN_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
+
+ qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MAX_MC + 1);
+ qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MIN_MC - 1);
+ g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
+ g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
+}
+
static void ina238_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -459,6 +540,9 @@ static void ina238_register_nodes(void)
qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
+ qos_add_test("shunt-limits", "ina238", test_shunt_limits, NULL);
+ qos_add_test("bus-limits", "ina238", test_bus_limits, NULL);
+ qos_add_test("temp-limits", "ina238", test_temp_limits, NULL);
qos_add_test("calibration-current", "ina238", test_calibration_current,
NULL);
qos_add_test("power", "ina238", test_power, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 45/50] tests/qtest: ina238: cover the operating modes
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the ADC_CONFIG MODE field: shutdown modes perform no
conversion and leave the measurement registers and CNVRF untouched,
triggered modes convert once per ADC_CONFIG write, and continuous
channel-select modes refresh only the enabled measurement registers.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 74912424c0..fb7bcc265c 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -304,6 +304,73 @@ static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
}
+/* Shutdown converts nothing: registers are held, CNVRF stays clear. */
+static void test_mode_shutdown(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+
+ /* Clear CNVRF, then enter shutdown (MODE = 0). */
+ (void)i2c_get16(dev, REG_DIAG_ALRT);
+ i2c_set16(dev, REG_ADC_CONFIG, 0x0000);
+
+ /* Injecting while shut down performs no conversion: registers are held. */
+ qmp_ina238_set("shunt-voltage", 40000000);
+ qmp_ina238_set("bus-voltage", 24000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+
+ /* No conversion completed, so CNVRF stays clear. */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+}
+
+/* Triggered modes convert once per ADC_CONFIG write. */
+static void test_mode_triggered(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+
+ /* Enter triggered bus+shunt+temp mode: one conversion. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ /* A new input does not convert until the next trigger. */
+ qmp_ina238_set("shunt-voltage", 40000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ /* Re-triggering with another ADC_CONFIG write samples the new input. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x1F40);
+}
+
+/* A bus-only mode refreshes the bus register alone; the rest are held. */
+static void test_mode_channels(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ qmp_ina238_set("bus-voltage", 12000000);
+
+ /* Continuous bus-only mode. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
+ qmp_ina238_set("shunt-voltage", 40000000);
+ qmp_ina238_set("bus-voltage", 24000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x1E00);
+}
+
/* Clamping at full scale and the MATHOF flag */
static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -548,6 +615,9 @@ static void ina238_register_nodes(void)
qos_add_test("power", "ina238", test_power, NULL);
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
+ qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
+ qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
+ qos_add_test("mode-channels", "ina238", test_mode_channels, NULL);
qos_add_test("overflow", "ina238", test_overflow, NULL);
qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 45/50] tests/qtest: ina238: cover the operating modes
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the ADC_CONFIG MODE field: shutdown modes perform no
conversion and leave the measurement registers and CNVRF untouched,
triggered modes convert once per ADC_CONFIG write, and continuous
channel-select modes refresh only the enabled measurement registers.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 74912424c0..fb7bcc265c 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -304,6 +304,73 @@ static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
}
+/* Shutdown converts nothing: registers are held, CNVRF stays clear. */
+static void test_mode_shutdown(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+
+ /* Clear CNVRF, then enter shutdown (MODE = 0). */
+ (void)i2c_get16(dev, REG_DIAG_ALRT);
+ i2c_set16(dev, REG_ADC_CONFIG, 0x0000);
+
+ /* Injecting while shut down performs no conversion: registers are held. */
+ qmp_ina238_set("shunt-voltage", 40000000);
+ qmp_ina238_set("bus-voltage", 24000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+
+ /* No conversion completed, so CNVRF stays clear. */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+}
+
+/* Triggered modes convert once per ADC_CONFIG write. */
+static void test_mode_triggered(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+
+ /* Enter triggered bus+shunt+temp mode: one conversion. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ /* A new input does not convert until the next trigger. */
+ qmp_ina238_set("shunt-voltage", 40000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ /* Re-triggering with another ADC_CONFIG write samples the new input. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x1F40);
+}
+
+/* A bus-only mode refreshes the bus register alone; the rest are held. */
+static void test_mode_channels(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ qmp_ina238_set("bus-voltage", 12000000);
+
+ /* Continuous bus-only mode. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
+ qmp_ina238_set("shunt-voltage", 40000000);
+ qmp_ina238_set("bus-voltage", 24000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x1E00);
+}
+
/* Clamping at full scale and the MATHOF flag */
static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -548,6 +615,9 @@ static void ina238_register_nodes(void)
qos_add_test("power", "ina238", test_power, NULL);
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
+ qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
+ qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
+ qos_add_test("mode-channels", "ina238", test_mode_channels, NULL);
qos_add_test("overflow", "ina238", test_overflow, NULL);
qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 45/50] tests/qtest: ina238: cover the operating modes
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the ADC_CONFIG MODE field: shutdown modes perform no
conversion and leave the measurement registers and CNVRF untouched,
triggered modes convert once per ADC_CONFIG write, and continuous
channel-select modes refresh only the enabled measurement registers.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index 74912424c0..fb7bcc265c 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -304,6 +304,73 @@ static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
}
+/* Shutdown converts nothing: registers are held, CNVRF stays clear. */
+static void test_mode_shutdown(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ qmp_ina238_set("bus-voltage", 12000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+
+ /* Clear CNVRF, then enter shutdown (MODE = 0). */
+ (void)i2c_get16(dev, REG_DIAG_ALRT);
+ i2c_set16(dev, REG_ADC_CONFIG, 0x0000);
+
+ /* Injecting while shut down performs no conversion: registers are held. */
+ qmp_ina238_set("shunt-voltage", 40000000);
+ qmp_ina238_set("bus-voltage", 24000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
+
+ /* No conversion completed, so CNVRF stays clear. */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+}
+
+/* Triggered modes convert once per ADC_CONFIG write. */
+static void test_mode_triggered(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+
+ /* Enter triggered bus+shunt+temp mode: one conversion. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ /* A new input does not convert until the next trigger. */
+ qmp_ina238_set("shunt-voltage", 40000000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+
+ /* Re-triggering with another ADC_CONFIG write samples the new input. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x1F40);
+}
+
+/* A bus-only mode refreshes the bus register alone; the rest are held. */
+static void test_mode_channels(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ qmp_ina238_set("bus-voltage", 12000000);
+
+ /* Continuous bus-only mode. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
+ qmp_ina238_set("shunt-voltage", 40000000);
+ qmp_ina238_set("bus-voltage", 24000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
+ g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x1E00);
+}
+
/* Clamping at full scale and the MATHOF flag */
static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -548,6 +615,9 @@ static void ina238_register_nodes(void)
qos_add_test("power", "ina238", test_power, NULL);
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
+ qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
+ qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
+ qos_add_test("mode-channels", "ina238", test_mode_channels, NULL);
qos_add_test("overflow", "ina238", test_overflow, NULL);
qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 46/50] tests/qtest: ina238: cover conversion-ready and alert-latch semantics
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the DIAG_ALRT flag-clearing rules: which of a read and
a write clears CNVRF and the limit flags in each alert mode, and that a
conversion only re-evaluates the flags of the channels it converted.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index fb7bcc265c..e7dd50a822 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -494,6 +494,106 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
}
+/* In latch mode a read releases the flag even while the fault persists. */
+static void test_alert_latch_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ /* First read shows the latched flag and clears it */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Fault still present but no new conversion: the flag stays cleared */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+}
+
+/*
+ * In transparent mode a limit flag follows the live condition: a read leaves
+ * it set while the fault persists.
+ */
+static void test_alert_transparent_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Repeated reads do not clear the flag while the fault is still present. */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* A conversion with the fault gone clears it. */
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* A DIAG_ALRT write releases a latched limit flag as a read does. */
+static void test_alert_latch_write_clears(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* The read above cleared it; re-latch it with another conversion. */
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ /* A write (keeping latch mode) clears the flag while the fault persists. */
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ /* The next conversion re-asserts the still-present fault. */
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+}
+
+/*
+ * A conversion only re-evaluates the flags of the channels it converted: the
+ * shunt register a bus-only conversion left stale must not resurrect one.
+ */
+static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Continuous bus-only mode */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
+ qmp_ina238_set("bus-voltage", 12000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
/* DIAG_ALRT control bits are writable; status bits are read-only */
static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -529,6 +629,25 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
}
+/* A DIAG_ALRT write clears CNVRF only in latch mode. */
+static void test_cnvrf_write(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ /* Latch mode: a write clears CNVRF. */
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+
+ /* Transparent mode: a write does not clear CNVRF. */
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_DIAG_ALRT, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+}
+
/* Shunt-voltage injection limits */
static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -625,7 +744,15 @@ static void ina238_register_nodes(void)
qos_add_test("alert-power", "ina238", test_alert_power, NULL);
qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
+ qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
+ NULL);
+ qos_add_test("alert-transparent-persist", "ina238",
+ test_alert_transparent_persist, NULL);
+ qos_add_test("alert-latch-write-clears", "ina238",
+ test_alert_latch_write_clears, NULL);
+ qos_add_test("alert-channel", "ina238", test_alert_channel, NULL);
qos_add_test("diag-control", "ina238", test_diag_control, NULL);
qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
+ qos_add_test("cnvrf-write", "ina238", test_cnvrf_write, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 46/50] tests/qtest: ina238: cover conversion-ready and alert-latch semantics
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the DIAG_ALRT flag-clearing rules: which of a read and
a write clears CNVRF and the limit flags in each alert mode, and that a
conversion only re-evaluates the flags of the channels it converted.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index fb7bcc265c..e7dd50a822 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -494,6 +494,106 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
}
+/* In latch mode a read releases the flag even while the fault persists. */
+static void test_alert_latch_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ /* First read shows the latched flag and clears it */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Fault still present but no new conversion: the flag stays cleared */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+}
+
+/*
+ * In transparent mode a limit flag follows the live condition: a read leaves
+ * it set while the fault persists.
+ */
+static void test_alert_transparent_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Repeated reads do not clear the flag while the fault is still present. */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* A conversion with the fault gone clears it. */
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* A DIAG_ALRT write releases a latched limit flag as a read does. */
+static void test_alert_latch_write_clears(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* The read above cleared it; re-latch it with another conversion. */
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ /* A write (keeping latch mode) clears the flag while the fault persists. */
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ /* The next conversion re-asserts the still-present fault. */
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+}
+
+/*
+ * A conversion only re-evaluates the flags of the channels it converted: the
+ * shunt register a bus-only conversion left stale must not resurrect one.
+ */
+static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Continuous bus-only mode */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
+ qmp_ina238_set("bus-voltage", 12000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
/* DIAG_ALRT control bits are writable; status bits are read-only */
static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -529,6 +629,25 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
}
+/* A DIAG_ALRT write clears CNVRF only in latch mode. */
+static void test_cnvrf_write(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ /* Latch mode: a write clears CNVRF. */
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+
+ /* Transparent mode: a write does not clear CNVRF. */
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_DIAG_ALRT, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+}
+
/* Shunt-voltage injection limits */
static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -625,7 +744,15 @@ static void ina238_register_nodes(void)
qos_add_test("alert-power", "ina238", test_alert_power, NULL);
qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
+ qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
+ NULL);
+ qos_add_test("alert-transparent-persist", "ina238",
+ test_alert_transparent_persist, NULL);
+ qos_add_test("alert-latch-write-clears", "ina238",
+ test_alert_latch_write_clears, NULL);
+ qos_add_test("alert-channel", "ina238", test_alert_channel, NULL);
qos_add_test("diag-control", "ina238", test_diag_control, NULL);
qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
+ qos_add_test("cnvrf-write", "ina238", test_cnvrf_write, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 46/50] tests/qtest: ina238: cover conversion-ready and alert-latch semantics
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add coverage for the DIAG_ALRT flag-clearing rules: which of a read and
a write clears CNVRF and the limit flags in each alert mode, and that a
conversion only re-evaluates the flags of the channels it converted.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index fb7bcc265c..e7dd50a822 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -494,6 +494,106 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
}
+/* In latch mode a read releases the flag even while the fault persists. */
+static void test_alert_latch_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ /* First read shows the latched flag and clears it */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Fault still present but no new conversion: the flag stays cleared */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+}
+
+/*
+ * In transparent mode a limit flag follows the live condition: a read leaves
+ * it set while the fault persists.
+ */
+static void test_alert_transparent_persist(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Repeated reads do not clear the flag while the fault is still present. */
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* A conversion with the fault gone clears it. */
+ qmp_ina238_set("shunt-voltage", 5000000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
+/* A DIAG_ALRT write releases a latched limit flag as a read does. */
+static void test_alert_latch_write_clears(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* The read above cleared it; re-latch it with another conversion. */
+ qmp_ina238_set("shunt-voltage", 97200000);
+
+ /* A write (keeping latch mode) clears the flag while the fault persists. */
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+
+ /* The next conversion re-asserts the still-present fault. */
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+}
+
+/*
+ * A conversion only re-evaluates the flags of the channels it converted: the
+ * shunt register a bus-only conversion left stale must not resurrect one.
+ */
+static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SOVL, 0x1000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+
+ qmp_ina238_set("shunt-voltage", 97200000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
+ DIAG_SHNTOL);
+
+ /* Continuous bus-only mode */
+ i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
+ qmp_ina238_set("bus-voltage", 12000000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
+}
+
/* DIAG_ALRT control bits are writable; status bits are read-only */
static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -529,6 +629,25 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
}
+/* A DIAG_ALRT write clears CNVRF only in latch mode. */
+static void test_cnvrf_write(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ /* Latch mode: a write clears CNVRF. */
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
+
+ /* Transparent mode: a write does not clear CNVRF. */
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 20000000);
+ i2c_set16(dev, REG_DIAG_ALRT, 0x0000);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
+}
+
/* Shunt-voltage injection limits */
static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -625,7 +744,15 @@ static void ina238_register_nodes(void)
qos_add_test("alert-power", "ina238", test_alert_power, NULL);
qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
+ qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
+ NULL);
+ qos_add_test("alert-transparent-persist", "ina238",
+ test_alert_transparent_persist, NULL);
+ qos_add_test("alert-latch-write-clears", "ina238",
+ test_alert_latch_write_clears, NULL);
+ qos_add_test("alert-channel", "ina238", test_alert_channel, NULL);
qos_add_test("diag-control", "ina238", test_diag_control, NULL);
qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
+ qos_add_test("cnvrf-write", "ina238", test_cnvrf_write, NULL);
}
libqos_init(ina238_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 47/50] tests/qtest: ina238: cover a cleared calibration per channel
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that without a calibration value the current and power
registers read zero, whichever channels the mode converts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index e7dd50a822..4e4146461c 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -282,6 +282,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
}
+/* Clearing SHUNT_CAL zeroes both derived registers in any mode */
+static void test_zero_cal_channels(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const uint16_t modes[] = { 0x9000, 0xA000, 0xF000 };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), !=, 0);
+
+ i2c_set16(dev, REG_ADC_CONFIG, modes[idx]);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+ }
+}
+
/* ADCRANGE changes the shunt LSB, not the current divisor. */
static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -458,6 +482,27 @@ static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
}
+/* Clearing SHUNT_CAL drops the power alert with the power reading */
+static void test_alert_power_zero_cal(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
+
+ /* Continuous shunt-only: the power register is not reconverted. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0xA000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
+}
+
/* Two thresholds crossed at once are reported simultaneously (multi-alert) */
static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -733,6 +778,7 @@ static void ina238_register_nodes(void)
NULL);
qos_add_test("power", "ina238", test_power, NULL);
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
+ qos_add_test("zero-cal-channels", "ina238", test_zero_cal_channels, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
@@ -742,6 +788,8 @@ static void ina238_register_nodes(void)
qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
qos_add_test("alert-power", "ina238", test_alert_power, NULL);
+ qos_add_test("alert-power-zero-cal", "ina238", test_alert_power_zero_cal,
+ NULL);
qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 47/50] tests/qtest: ina238: cover a cleared calibration per channel
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that without a calibration value the current and power
registers read zero, whichever channels the mode converts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index e7dd50a822..4e4146461c 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -282,6 +282,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
}
+/* Clearing SHUNT_CAL zeroes both derived registers in any mode */
+static void test_zero_cal_channels(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const uint16_t modes[] = { 0x9000, 0xA000, 0xF000 };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), !=, 0);
+
+ i2c_set16(dev, REG_ADC_CONFIG, modes[idx]);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+ }
+}
+
/* ADCRANGE changes the shunt LSB, not the current divisor. */
static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -458,6 +482,27 @@ static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
}
+/* Clearing SHUNT_CAL drops the power alert with the power reading */
+static void test_alert_power_zero_cal(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
+
+ /* Continuous shunt-only: the power register is not reconverted. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0xA000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
+}
+
/* Two thresholds crossed at once are reported simultaneously (multi-alert) */
static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -733,6 +778,7 @@ static void ina238_register_nodes(void)
NULL);
qos_add_test("power", "ina238", test_power, NULL);
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
+ qos_add_test("zero-cal-channels", "ina238", test_zero_cal_channels, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
@@ -742,6 +788,8 @@ static void ina238_register_nodes(void)
qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
qos_add_test("alert-power", "ina238", test_alert_power, NULL);
+ qos_add_test("alert-power-zero-cal", "ina238", test_alert_power_zero_cal,
+ NULL);
qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 47/50] tests/qtest: ina238: cover a cleared calibration per channel
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Verify that without a calibration value the current and power
registers read zero, whichever channels the mode converts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/ina238-test.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
index e7dd50a822..4e4146461c 100644
--- a/tests/qtest/ina238-test.c
+++ b/tests/qtest/ina238-test.c
@@ -282,6 +282,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
}
+/* Clearing SHUNT_CAL zeroes both derived registers in any mode */
+static void test_zero_cal_channels(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const uint16_t modes[] = { 0x9000, 0xA000, 0xF000 };
+ size_t idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), !=, 0);
+
+ i2c_set16(dev, REG_ADC_CONFIG, modes[idx]);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+
+ g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+ }
+}
+
/* ADCRANGE changes the shunt LSB, not the current divisor. */
static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -458,6 +482,27 @@ static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
}
+/* Clearing SHUNT_CAL drops the power alert with the power reading */
+static void test_alert_power_zero_cal(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set16(dev, REG_CONFIG, CONFIG_RST);
+ qmp_ina238_set("shunt-voltage", 97200000);
+ qmp_ina238_set("bus-voltage", 48000000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
+ i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
+
+ /* Continuous shunt-only: the power register is not reconverted. */
+ i2c_set16(dev, REG_ADC_CONFIG, 0xA000);
+ i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
+
+ g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
+ g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
+}
+
/* Two thresholds crossed at once are reported simultaneously (multi-alert) */
static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
{
@@ -733,6 +778,7 @@ static void ina238_register_nodes(void)
NULL);
qos_add_test("power", "ina238", test_power, NULL);
qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
+ qos_add_test("zero-cal-channels", "ina238", test_zero_cal_channels, NULL);
qos_add_test("adcrange", "ina238", test_adcrange, NULL);
qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
@@ -742,6 +788,8 @@ static void ina238_register_nodes(void)
qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
qos_add_test("alert-power", "ina238", test_alert_power, NULL);
+ qos_add_test("alert-power-zero-cal", "ina238", test_alert_power_zero_cal,
+ NULL);
qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 48/50] hw/arm: minerva: add Facebook BMC machine
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a machine for the Facebook Minerva Chassis Management Module (BMC),
an AST2600-based board modelled from its device tree
(aspeed-bmc-facebook-minerva.dts).
The board uses INA230 and INA238 power monitors, TMP75 temperature
sensors, PCA9555 IO expanders, PCA9545 and PCA9548 I2C muxes, AT24C
EEPROMs and a DS1339 RTC.
Every device the Minerva device tree describes and QEMU already models
is instantiated. The remaining ones (LTC4287, XDP710, MAX31790, PCF8563
and IPMB) have no QEMU model and are left documented as comments.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 1 +
hw/arm/aspeed_ast2600_minerva.c | 180 ++++++++++++++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
3 files changed, 182 insertions(+)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index f38f33cbe1..bbd3d9cdf4 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -36,6 +36,7 @@ AST2600 SoC based machines :
- ``catalina-bmc`` Facebook Catalina BMC
- ``sanmiguel-bmc`` Facebook SanMiguel BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
+- ``minerva-bmc`` Facebook Minerva BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
Supported devices
diff --git a/hw/arm/aspeed_ast2600_minerva.c b/hw/arm/aspeed_ast2600_minerva.c
new file mode 100644
index 0000000000..93abc64770
--- /dev/null
+++ b/hw/arm/aspeed_ast2600_minerva.c
@@ -0,0 +1,180 @@
+/*
+ * Facebook Minerva Chassis Management Module
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/arm/aspeed.h"
+#include "hw/arm/aspeed_soc.h"
+#include "hw/i2c/i2c_mux_pca954x.h"
+#include "hw/gpio/pca9552.h"
+#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
+#include "hw/sensor/ina230.h"
+#include "hw/sensor/ina238.h"
+#include "hw/sensor/tmp105.h"
+
+/*
+ * Minerva hardware values.
+ */
+#define MINERVA_BMC_HW_STRAP1 0x00002002
+#define MINERVA_BMC_HW_STRAP2 0x00000800
+#define MINERVA_BMC_RAM_SIZE (2 * GiB)
+
+/*
+ * Fan Controller Board (FCB). The Minerva BMC device tree describes six
+ * identical FCBs, each hanging off one channel of the PCA9548 on i2c2.
+ */
+static void minerva_fcb_i2c_init(I2CBus *bus)
+{
+ /* eeprom@50 - atmel,24c128 */
+ at24c_eeprom_init(bus, 0x50, 16 * KiB);
+ /* pwm@5e - maxim,max31790 (no model) */
+ /* power-sensor@40/41/44/45 - ti,ina238 */
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x40);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x41);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x44);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x45);
+ /* temperature-sensor@4b - tmp75 */
+ i2c_slave_create_simple(bus, TYPE_TMP75, 0x4b);
+ /* gpio@11/12/13/17 - pca9555 */
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x11);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x12);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x13);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x17);
+}
+
+static void minerva_pca9545_eeprom_init(I2CBus *bus, uint8_t addr,
+ unsigned neeprom)
+{
+ I2CSlave *i2c_mux = i2c_slave_create_simple(bus, TYPE_PCA9545, addr);
+
+ for (unsigned idx = 0; idx < neeprom; idx++) {
+ at24c_eeprom_init(pca954x_i2c_get_bus(i2c_mux, idx), 0x50, 8 * KiB);
+ }
+}
+
+static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
+{
+ /* Reference: aspeed-bmc-facebook-minerva.dts */
+
+ AspeedSoCState *soc = bmc->soc;
+ I2CBus *i2c[16] = {};
+ I2CSlave *i2c_mux;
+
+ for (unsigned idx = 0; idx < ARRAY_SIZE(i2c); idx++) {
+ i2c[idx] = aspeed_i2c_get_bus(&soc->i2c, idx);
+ }
+
+ /* &i2c0 */
+ /* power-monitor@40/41 - ti,ina230 */
+ i2c_slave_create_simple(i2c[0], TYPE_INA230, 0x40);
+ i2c_slave_create_simple(i2c[0], TYPE_INA230, 0x41);
+ /* power-monitor@44 - lltc,ltc4287 (no model) */
+ /* power-monitor@43 - infineon,xdp710 (no model) */
+ /* leds_gpio: gpio@19 - pca9555 */
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x19);
+ /* gpio@11/12/13 - pca9555 */
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x11);
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x12);
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x13);
+
+ /* &i2c1 */
+ /* temperature-sensor@4b/4f - tmp75 */
+ i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4b);
+ i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4f);
+ /* eeprom@54 - atmel,24c128 */
+ at24c_eeprom_init(i2c[1], 0x54, 16 * KiB);
+
+ /* &i2c2 */
+ /* i2c-mux@77 (PCA9548) - six Fan Controller Boards */
+ i2c_mux = i2c_slave_create_simple(i2c[2], TYPE_PCA9548, 0x77);
+ /* FCB 1 - imux16 (channel 1) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1));
+ /* FCB 2 - imux17 (channel 0) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0));
+ /* FCB 3 - imux18 (channel 3) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3));
+ /* FCB 4 - imux19 (channel 2) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2));
+ /* FCB 5 - imux20 (channel 4) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4));
+ /* FCB 6 - imux21 (channel 5) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5));
+ /* imux22/imux23 (channels 6/7) - empty */
+
+ /* &i2c3 */
+ /* i2c-mux@72 (PCA9545) - imux24-27, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[3], 0x72, 4);
+
+ /* &i2c4 */
+ /* i2c-mux@72 (PCA9545) - imux28-31, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[4], 0x72, 4);
+
+ /* &i2c5 */
+ /* i2c-mux@72 (PCA9545) - imux32-35, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[5], 0x72, 4);
+
+ /* &i2c6 */
+ /* i2c-mux@72 (PCA9545) - imux36-39, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[6], 0x72, 4);
+
+ /* &i2c7 - empty */
+ /* &i2c8 - empty */
+
+ /* &i2c9 */
+ /* eeprom@50 - atmel,24c64 */
+ at24c_eeprom_init(i2c[9], 0x50, 8 * KiB);
+ /* rtc@51 - nxp,pcf8563 (no model) */
+ /* rtc@68 - dallas,ds1339 */
+ i2c_slave_create_simple(i2c[9], TYPE_DS1339, 0x68);
+
+ /* &i2c12 */
+ /* i2c-mux@70 (PCA9545) - imux40-42 eeprom@50, imux43 empty */
+ minerva_pca9545_eeprom_init(i2c[12], 0x70, 3);
+
+ /* &i2c13 */
+ /* i2c-mux@70 (PCA9545) - imux44-46 eeprom@50, imux47 empty */
+ minerva_pca9545_eeprom_init(i2c[13], 0x70, 3);
+
+ /* &i2c14 */
+ /* ipmb@10 - ipmb-dev (no model) */
+
+ /* &i2c15 */
+ /* eeprom@50 - atmel,24c128 */
+ at24c_eeprom_init(i2c[15], 0x50, 16 * KiB);
+ /* eeprom@56 - atmel,24c64 */
+ at24c_eeprom_init(i2c[15], 0x56, 8 * KiB);
+}
+
+static void aspeed_machine_minerva_class_init(ObjectClass *oc,
+ const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc);
+
+ mc->desc = "Facebook Minerva BMC (Cortex-A7)";
+ amc->soc_name = "ast2600-a3";
+ amc->hw_strap1 = MINERVA_BMC_HW_STRAP1;
+ amc->hw_strap2 = MINERVA_BMC_HW_STRAP2;
+ amc->fmc_model = "mx66l1g45g";
+ amc->spi_model = NULL;
+ amc->num_cs = 2;
+ amc->macs_mask = ASPEED_MAC3_ON;
+ amc->i2c_init = minerva_bmc_i2c_init;
+ mc->default_ram_size = MINERVA_BMC_RAM_SIZE;
+ aspeed_machine_class_init_cpus_defaults(mc);
+}
+
+static const TypeInfo aspeed_ast2600_minerva_types[] = {
+ {
+ .name = MACHINE_TYPE_NAME("minerva-bmc"),
+ .parent = TYPE_ASPEED_MACHINE,
+ .class_init = aspeed_machine_minerva_class_init,
+ }
+};
+
+DEFINE_TYPES(aspeed_ast2600_minerva_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index bcc068de04..a9e8270b85 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -63,6 +63,7 @@ arm_common_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
'aspeed_ast2600_fby35.c',
'aspeed_ast2600_fuji.c',
'aspeed_ast2600_gb200nvl.c',
+ 'aspeed_ast2600_minerva.c',
'aspeed_ast2600_rainier.c',
'aspeed_ast2600_sanmiguel.c',
'aspeed_ast10x0.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 48/50] hw/arm: minerva: add Facebook BMC machine
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a machine for the Facebook Minerva Chassis Management Module (BMC),
an AST2600-based board modelled from its device tree
(aspeed-bmc-facebook-minerva.dts).
The board uses INA230 and INA238 power monitors, TMP75 temperature
sensors, PCA9555 IO expanders, PCA9545 and PCA9548 I2C muxes, AT24C
EEPROMs and a DS1339 RTC.
Every device the Minerva device tree describes and QEMU already models
is instantiated. The remaining ones (LTC4287, XDP710, MAX31790, PCF8563
and IPMB) have no QEMU model and are left documented as comments.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 1 +
hw/arm/aspeed_ast2600_minerva.c | 180 ++++++++++++++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
3 files changed, 182 insertions(+)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index f38f33cbe1..bbd3d9cdf4 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -36,6 +36,7 @@ AST2600 SoC based machines :
- ``catalina-bmc`` Facebook Catalina BMC
- ``sanmiguel-bmc`` Facebook SanMiguel BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
+- ``minerva-bmc`` Facebook Minerva BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
Supported devices
diff --git a/hw/arm/aspeed_ast2600_minerva.c b/hw/arm/aspeed_ast2600_minerva.c
new file mode 100644
index 0000000000..93abc64770
--- /dev/null
+++ b/hw/arm/aspeed_ast2600_minerva.c
@@ -0,0 +1,180 @@
+/*
+ * Facebook Minerva Chassis Management Module
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/arm/aspeed.h"
+#include "hw/arm/aspeed_soc.h"
+#include "hw/i2c/i2c_mux_pca954x.h"
+#include "hw/gpio/pca9552.h"
+#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
+#include "hw/sensor/ina230.h"
+#include "hw/sensor/ina238.h"
+#include "hw/sensor/tmp105.h"
+
+/*
+ * Minerva hardware values.
+ */
+#define MINERVA_BMC_HW_STRAP1 0x00002002
+#define MINERVA_BMC_HW_STRAP2 0x00000800
+#define MINERVA_BMC_RAM_SIZE (2 * GiB)
+
+/*
+ * Fan Controller Board (FCB). The Minerva BMC device tree describes six
+ * identical FCBs, each hanging off one channel of the PCA9548 on i2c2.
+ */
+static void minerva_fcb_i2c_init(I2CBus *bus)
+{
+ /* eeprom@50 - atmel,24c128 */
+ at24c_eeprom_init(bus, 0x50, 16 * KiB);
+ /* pwm@5e - maxim,max31790 (no model) */
+ /* power-sensor@40/41/44/45 - ti,ina238 */
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x40);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x41);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x44);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x45);
+ /* temperature-sensor@4b - tmp75 */
+ i2c_slave_create_simple(bus, TYPE_TMP75, 0x4b);
+ /* gpio@11/12/13/17 - pca9555 */
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x11);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x12);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x13);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x17);
+}
+
+static void minerva_pca9545_eeprom_init(I2CBus *bus, uint8_t addr,
+ unsigned neeprom)
+{
+ I2CSlave *i2c_mux = i2c_slave_create_simple(bus, TYPE_PCA9545, addr);
+
+ for (unsigned idx = 0; idx < neeprom; idx++) {
+ at24c_eeprom_init(pca954x_i2c_get_bus(i2c_mux, idx), 0x50, 8 * KiB);
+ }
+}
+
+static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
+{
+ /* Reference: aspeed-bmc-facebook-minerva.dts */
+
+ AspeedSoCState *soc = bmc->soc;
+ I2CBus *i2c[16] = {};
+ I2CSlave *i2c_mux;
+
+ for (unsigned idx = 0; idx < ARRAY_SIZE(i2c); idx++) {
+ i2c[idx] = aspeed_i2c_get_bus(&soc->i2c, idx);
+ }
+
+ /* &i2c0 */
+ /* power-monitor@40/41 - ti,ina230 */
+ i2c_slave_create_simple(i2c[0], TYPE_INA230, 0x40);
+ i2c_slave_create_simple(i2c[0], TYPE_INA230, 0x41);
+ /* power-monitor@44 - lltc,ltc4287 (no model) */
+ /* power-monitor@43 - infineon,xdp710 (no model) */
+ /* leds_gpio: gpio@19 - pca9555 */
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x19);
+ /* gpio@11/12/13 - pca9555 */
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x11);
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x12);
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x13);
+
+ /* &i2c1 */
+ /* temperature-sensor@4b/4f - tmp75 */
+ i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4b);
+ i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4f);
+ /* eeprom@54 - atmel,24c128 */
+ at24c_eeprom_init(i2c[1], 0x54, 16 * KiB);
+
+ /* &i2c2 */
+ /* i2c-mux@77 (PCA9548) - six Fan Controller Boards */
+ i2c_mux = i2c_slave_create_simple(i2c[2], TYPE_PCA9548, 0x77);
+ /* FCB 1 - imux16 (channel 1) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1));
+ /* FCB 2 - imux17 (channel 0) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0));
+ /* FCB 3 - imux18 (channel 3) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3));
+ /* FCB 4 - imux19 (channel 2) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2));
+ /* FCB 5 - imux20 (channel 4) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4));
+ /* FCB 6 - imux21 (channel 5) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5));
+ /* imux22/imux23 (channels 6/7) - empty */
+
+ /* &i2c3 */
+ /* i2c-mux@72 (PCA9545) - imux24-27, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[3], 0x72, 4);
+
+ /* &i2c4 */
+ /* i2c-mux@72 (PCA9545) - imux28-31, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[4], 0x72, 4);
+
+ /* &i2c5 */
+ /* i2c-mux@72 (PCA9545) - imux32-35, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[5], 0x72, 4);
+
+ /* &i2c6 */
+ /* i2c-mux@72 (PCA9545) - imux36-39, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[6], 0x72, 4);
+
+ /* &i2c7 - empty */
+ /* &i2c8 - empty */
+
+ /* &i2c9 */
+ /* eeprom@50 - atmel,24c64 */
+ at24c_eeprom_init(i2c[9], 0x50, 8 * KiB);
+ /* rtc@51 - nxp,pcf8563 (no model) */
+ /* rtc@68 - dallas,ds1339 */
+ i2c_slave_create_simple(i2c[9], TYPE_DS1339, 0x68);
+
+ /* &i2c12 */
+ /* i2c-mux@70 (PCA9545) - imux40-42 eeprom@50, imux43 empty */
+ minerva_pca9545_eeprom_init(i2c[12], 0x70, 3);
+
+ /* &i2c13 */
+ /* i2c-mux@70 (PCA9545) - imux44-46 eeprom@50, imux47 empty */
+ minerva_pca9545_eeprom_init(i2c[13], 0x70, 3);
+
+ /* &i2c14 */
+ /* ipmb@10 - ipmb-dev (no model) */
+
+ /* &i2c15 */
+ /* eeprom@50 - atmel,24c128 */
+ at24c_eeprom_init(i2c[15], 0x50, 16 * KiB);
+ /* eeprom@56 - atmel,24c64 */
+ at24c_eeprom_init(i2c[15], 0x56, 8 * KiB);
+}
+
+static void aspeed_machine_minerva_class_init(ObjectClass *oc,
+ const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc);
+
+ mc->desc = "Facebook Minerva BMC (Cortex-A7)";
+ amc->soc_name = "ast2600-a3";
+ amc->hw_strap1 = MINERVA_BMC_HW_STRAP1;
+ amc->hw_strap2 = MINERVA_BMC_HW_STRAP2;
+ amc->fmc_model = "mx66l1g45g";
+ amc->spi_model = NULL;
+ amc->num_cs = 2;
+ amc->macs_mask = ASPEED_MAC3_ON;
+ amc->i2c_init = minerva_bmc_i2c_init;
+ mc->default_ram_size = MINERVA_BMC_RAM_SIZE;
+ aspeed_machine_class_init_cpus_defaults(mc);
+}
+
+static const TypeInfo aspeed_ast2600_minerva_types[] = {
+ {
+ .name = MACHINE_TYPE_NAME("minerva-bmc"),
+ .parent = TYPE_ASPEED_MACHINE,
+ .class_init = aspeed_machine_minerva_class_init,
+ }
+};
+
+DEFINE_TYPES(aspeed_ast2600_minerva_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index bcc068de04..a9e8270b85 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -63,6 +63,7 @@ arm_common_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
'aspeed_ast2600_fby35.c',
'aspeed_ast2600_fuji.c',
'aspeed_ast2600_gb200nvl.c',
+ 'aspeed_ast2600_minerva.c',
'aspeed_ast2600_rainier.c',
'aspeed_ast2600_sanmiguel.c',
'aspeed_ast10x0.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 48/50] hw/arm: minerva: add Facebook BMC machine
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a machine for the Facebook Minerva Chassis Management Module (BMC),
an AST2600-based board modelled from its device tree
(aspeed-bmc-facebook-minerva.dts).
The board uses INA230 and INA238 power monitors, TMP75 temperature
sensors, PCA9555 IO expanders, PCA9545 and PCA9548 I2C muxes, AT24C
EEPROMs and a DS1339 RTC.
Every device the Minerva device tree describes and QEMU already models
is instantiated. The remaining ones (LTC4287, XDP710, MAX31790, PCF8563
and IPMB) have no QEMU model and are left documented as comments.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
docs/system/arm/aspeed.rst | 1 +
hw/arm/aspeed_ast2600_minerva.c | 180 ++++++++++++++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
3 files changed, 182 insertions(+)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index f38f33cbe1..bbd3d9cdf4 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -36,6 +36,7 @@ AST2600 SoC based machines :
- ``catalina-bmc`` Facebook Catalina BMC
- ``sanmiguel-bmc`` Facebook SanMiguel BMC
- ``anacapa-bmc`` Facebook Anacapa BMC
+- ``minerva-bmc`` Facebook Minerva BMC
- ``gb200nvl-bmc`` Nvidia GB200nvl BMC
Supported devices
diff --git a/hw/arm/aspeed_ast2600_minerva.c b/hw/arm/aspeed_ast2600_minerva.c
new file mode 100644
index 0000000000..93abc64770
--- /dev/null
+++ b/hw/arm/aspeed_ast2600_minerva.c
@@ -0,0 +1,180 @@
+/*
+ * Facebook Minerva Chassis Management Module
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/arm/aspeed.h"
+#include "hw/arm/aspeed_soc.h"
+#include "hw/i2c/i2c_mux_pca954x.h"
+#include "hw/gpio/pca9552.h"
+#include "hw/nvram/eeprom_at24c.h"
+#include "hw/rtc/ds1338.h"
+#include "hw/sensor/ina230.h"
+#include "hw/sensor/ina238.h"
+#include "hw/sensor/tmp105.h"
+
+/*
+ * Minerva hardware values.
+ */
+#define MINERVA_BMC_HW_STRAP1 0x00002002
+#define MINERVA_BMC_HW_STRAP2 0x00000800
+#define MINERVA_BMC_RAM_SIZE (2 * GiB)
+
+/*
+ * Fan Controller Board (FCB). The Minerva BMC device tree describes six
+ * identical FCBs, each hanging off one channel of the PCA9548 on i2c2.
+ */
+static void minerva_fcb_i2c_init(I2CBus *bus)
+{
+ /* eeprom@50 - atmel,24c128 */
+ at24c_eeprom_init(bus, 0x50, 16 * KiB);
+ /* pwm@5e - maxim,max31790 (no model) */
+ /* power-sensor@40/41/44/45 - ti,ina238 */
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x40);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x41);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x44);
+ i2c_slave_create_simple(bus, TYPE_INA238, 0x45);
+ /* temperature-sensor@4b - tmp75 */
+ i2c_slave_create_simple(bus, TYPE_TMP75, 0x4b);
+ /* gpio@11/12/13/17 - pca9555 */
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x11);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x12);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x13);
+ i2c_slave_create_simple(bus, TYPE_PCA9555, 0x17);
+}
+
+static void minerva_pca9545_eeprom_init(I2CBus *bus, uint8_t addr,
+ unsigned neeprom)
+{
+ I2CSlave *i2c_mux = i2c_slave_create_simple(bus, TYPE_PCA9545, addr);
+
+ for (unsigned idx = 0; idx < neeprom; idx++) {
+ at24c_eeprom_init(pca954x_i2c_get_bus(i2c_mux, idx), 0x50, 8 * KiB);
+ }
+}
+
+static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
+{
+ /* Reference: aspeed-bmc-facebook-minerva.dts */
+
+ AspeedSoCState *soc = bmc->soc;
+ I2CBus *i2c[16] = {};
+ I2CSlave *i2c_mux;
+
+ for (unsigned idx = 0; idx < ARRAY_SIZE(i2c); idx++) {
+ i2c[idx] = aspeed_i2c_get_bus(&soc->i2c, idx);
+ }
+
+ /* &i2c0 */
+ /* power-monitor@40/41 - ti,ina230 */
+ i2c_slave_create_simple(i2c[0], TYPE_INA230, 0x40);
+ i2c_slave_create_simple(i2c[0], TYPE_INA230, 0x41);
+ /* power-monitor@44 - lltc,ltc4287 (no model) */
+ /* power-monitor@43 - infineon,xdp710 (no model) */
+ /* leds_gpio: gpio@19 - pca9555 */
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x19);
+ /* gpio@11/12/13 - pca9555 */
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x11);
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x12);
+ i2c_slave_create_simple(i2c[0], TYPE_PCA9555, 0x13);
+
+ /* &i2c1 */
+ /* temperature-sensor@4b/4f - tmp75 */
+ i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4b);
+ i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4f);
+ /* eeprom@54 - atmel,24c128 */
+ at24c_eeprom_init(i2c[1], 0x54, 16 * KiB);
+
+ /* &i2c2 */
+ /* i2c-mux@77 (PCA9548) - six Fan Controller Boards */
+ i2c_mux = i2c_slave_create_simple(i2c[2], TYPE_PCA9548, 0x77);
+ /* FCB 1 - imux16 (channel 1) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1));
+ /* FCB 2 - imux17 (channel 0) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0));
+ /* FCB 3 - imux18 (channel 3) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3));
+ /* FCB 4 - imux19 (channel 2) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2));
+ /* FCB 5 - imux20 (channel 4) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4));
+ /* FCB 6 - imux21 (channel 5) */
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5));
+ /* imux22/imux23 (channels 6/7) - empty */
+
+ /* &i2c3 */
+ /* i2c-mux@72 (PCA9545) - imux24-27, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[3], 0x72, 4);
+
+ /* &i2c4 */
+ /* i2c-mux@72 (PCA9545) - imux28-31, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[4], 0x72, 4);
+
+ /* &i2c5 */
+ /* i2c-mux@72 (PCA9545) - imux32-35, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[5], 0x72, 4);
+
+ /* &i2c6 */
+ /* i2c-mux@72 (PCA9545) - imux36-39, eeprom@50 on each channel */
+ minerva_pca9545_eeprom_init(i2c[6], 0x72, 4);
+
+ /* &i2c7 - empty */
+ /* &i2c8 - empty */
+
+ /* &i2c9 */
+ /* eeprom@50 - atmel,24c64 */
+ at24c_eeprom_init(i2c[9], 0x50, 8 * KiB);
+ /* rtc@51 - nxp,pcf8563 (no model) */
+ /* rtc@68 - dallas,ds1339 */
+ i2c_slave_create_simple(i2c[9], TYPE_DS1339, 0x68);
+
+ /* &i2c12 */
+ /* i2c-mux@70 (PCA9545) - imux40-42 eeprom@50, imux43 empty */
+ minerva_pca9545_eeprom_init(i2c[12], 0x70, 3);
+
+ /* &i2c13 */
+ /* i2c-mux@70 (PCA9545) - imux44-46 eeprom@50, imux47 empty */
+ minerva_pca9545_eeprom_init(i2c[13], 0x70, 3);
+
+ /* &i2c14 */
+ /* ipmb@10 - ipmb-dev (no model) */
+
+ /* &i2c15 */
+ /* eeprom@50 - atmel,24c128 */
+ at24c_eeprom_init(i2c[15], 0x50, 16 * KiB);
+ /* eeprom@56 - atmel,24c64 */
+ at24c_eeprom_init(i2c[15], 0x56, 8 * KiB);
+}
+
+static void aspeed_machine_minerva_class_init(ObjectClass *oc,
+ const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc);
+
+ mc->desc = "Facebook Minerva BMC (Cortex-A7)";
+ amc->soc_name = "ast2600-a3";
+ amc->hw_strap1 = MINERVA_BMC_HW_STRAP1;
+ amc->hw_strap2 = MINERVA_BMC_HW_STRAP2;
+ amc->fmc_model = "mx66l1g45g";
+ amc->spi_model = NULL;
+ amc->num_cs = 2;
+ amc->macs_mask = ASPEED_MAC3_ON;
+ amc->i2c_init = minerva_bmc_i2c_init;
+ mc->default_ram_size = MINERVA_BMC_RAM_SIZE;
+ aspeed_machine_class_init_cpus_defaults(mc);
+}
+
+static const TypeInfo aspeed_ast2600_minerva_types[] = {
+ {
+ .name = MACHINE_TYPE_NAME("minerva-bmc"),
+ .parent = TYPE_ASPEED_MACHINE,
+ .class_init = aspeed_machine_minerva_class_init,
+ }
+};
+
+DEFINE_TYPES(aspeed_ast2600_minerva_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index bcc068de04..a9e8270b85 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -63,6 +63,7 @@ arm_common_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
'aspeed_ast2600_fby35.c',
'aspeed_ast2600_fuji.c',
'aspeed_ast2600_gb200nvl.c',
+ 'aspeed_ast2600_minerva.c',
'aspeed_ast2600_rainier.c',
'aspeed_ast2600_sanmiguel.c',
'aspeed_ast10x0.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 49/50] hw/arm: minerva: populate EEPROM data
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The board carries 31 FRU EEPROMs: three on the management module
itself, one per fan controller board, and the rest on the compute and
switch boards reached through the I2C muxes.
Populate them from a sanitized dump read from hardware, so guest
firmware can identify the chassis inventory instead of finding blank
parts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2600_minerva.c | 714 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 695 insertions(+), 19 deletions(-)
diff --git a/hw/arm/aspeed_ast2600_minerva.c b/hw/arm/aspeed_ast2600_minerva.c
index 93abc64770..4b6731e279 100644
--- a/hw/arm/aspeed_ast2600_minerva.c
+++ b/hw/arm/aspeed_ast2600_minerva.c
@@ -24,14 +24,667 @@
#define MINERVA_BMC_HW_STRAP2 0x00000800
#define MINERVA_BMC_RAM_SIZE (2 * GiB)
+/* FRU EEPROM content meaningful size */
+#define MINERVA_FRU_LEN 192
+
+/* Motherboards behind each of the four i2c3..i2c6 muxes */
+#define MINERVA_MB_PER_MUX 4
+
+/* Minerva CMM PVT */
+static const uint8_t cmm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x08, 0x11, 0x00, 0x00, 0xe5, 0x01, 0x07, 0x17, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc0, 0xcc, 0x39, 0x30, 0x38, 0x32, 0x43, 0x33,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x13, 0x01, 0x09, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51,
+ 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcf, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76,
+ 0x61, 0x20, 0x43, 0x4d, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32,
+ 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31,
+ 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
+ 0x35, 0xc1, 0x00, 0xef, 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e,
+ 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41, 0x41, 0x30,
+ 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* Minerva_CMM_SCM PVT */
+static const uint8_t scm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd3, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x5f, 0x43, 0x4d,
+ 0x4d, 0x5f, 0x53, 0x43, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34,
+ 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x32,
+ 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
+ 0x30, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x01, 0x08, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
+ 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* BMC Storage Module PVT */
+static const uint8_t bsm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd6, 0x42, 0x4d, 0x43, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+ 0x65, 0x20, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x50, 0x56, 0x54,
+ 0xce, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x35, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x41, 0x33, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x47, 0x01, 0x08, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
+ 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* Minerva Fan Board 1 PVT */
+static const uint8_t fcb1_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x36, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa6, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 2 PVT */
+static const uint8_t fcb2_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x37, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 3 PVT */
+static const uint8_t fcb3_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x38, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 4 PVT */
+static const uint8_t fcb4_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x39, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 5 PVT */
+static const uint8_t fcb5_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x31, 0x30, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xab, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board6 PVT */
+static const uint8_t fcb6_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x31, 0x31, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xaa, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Harma motherboards, in i2c3/i2c4/i2c5/i2c6 mux channel order */
+static const uint8_t minerva_mb_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x23,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x21,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1f,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1d,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x24,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x22,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x20,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x17, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1e,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x15, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1c,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x23,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x21,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1f,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1d,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x14, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1b,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x34, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x22,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x34, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x20,
+ },
+};
+
+/* The four muxes take a quarter of the table each, by hand-written index. */
+QEMU_BUILD_BUG_ON(ARRAY_SIZE(minerva_mb_eeprom) != 4 * MINERVA_MB_PER_MUX);
+
+/* Switch boards on the i2c12 mux */
+static const uint8_t minerva_smb12_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x34, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xcc, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x36, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xca, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x38, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbc, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+};
+
+/* Switch boards on the i2c13 mux */
+static const uint8_t minerva_smb13_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x30, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc3, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x32, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc1, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x34, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbf, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+};
+
/*
* Fan Controller Board (FCB). The Minerva BMC device tree describes six
* identical FCBs, each hanging off one channel of the PCA9548 on i2c2.
*/
-static void minerva_fcb_i2c_init(I2CBus *bus)
+static void minerva_fcb_i2c_init(I2CBus *bus, const uint8_t *fru,
+ uint32_t fru_size)
{
/* eeprom@50 - atmel,24c128 */
- at24c_eeprom_init(bus, 0x50, 16 * KiB);
+ at24c_eeprom_init_rom(bus, 0x50, 16 * KiB, fru, fru_size);
/* pwm@5e - maxim,max31790 (no model) */
/* power-sensor@40/41/44/45 - ti,ina238 */
i2c_slave_create_simple(bus, TYPE_INA238, 0x40);
@@ -48,12 +701,14 @@ static void minerva_fcb_i2c_init(I2CBus *bus)
}
static void minerva_pca9545_eeprom_init(I2CBus *bus, uint8_t addr,
+ const uint8_t fru[][MINERVA_FRU_LEN],
unsigned neeprom)
{
I2CSlave *i2c_mux = i2c_slave_create_simple(bus, TYPE_PCA9545, addr);
for (unsigned idx = 0; idx < neeprom; idx++) {
- at24c_eeprom_init(pca954x_i2c_get_bus(i2c_mux, idx), 0x50, 8 * KiB);
+ at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, idx), 0x50,
+ 8 * KiB, fru[idx], MINERVA_FRU_LEN);
}
}
@@ -87,46 +742,61 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4b);
i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4f);
/* eeprom@54 - atmel,24c128 */
- at24c_eeprom_init(i2c[1], 0x54, 16 * KiB);
+ at24c_eeprom_init_rom(i2c[1], 0x54, 16 * KiB,
+ cmm_eeprom, sizeof(cmm_eeprom));
/* &i2c2 */
/* i2c-mux@77 (PCA9548) - six Fan Controller Boards */
i2c_mux = i2c_slave_create_simple(i2c[2], TYPE_PCA9548, 0x77);
/* FCB 1 - imux16 (channel 1) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1),
+ fcb1_eeprom, sizeof(fcb1_eeprom));
/* FCB 2 - imux17 (channel 0) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0),
+ fcb2_eeprom, sizeof(fcb2_eeprom));
/* FCB 3 - imux18 (channel 3) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3),
+ fcb3_eeprom, sizeof(fcb3_eeprom));
/* FCB 4 - imux19 (channel 2) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2),
+ fcb4_eeprom, sizeof(fcb4_eeprom));
/* FCB 5 - imux20 (channel 4) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4),
+ fcb5_eeprom, sizeof(fcb5_eeprom));
/* FCB 6 - imux21 (channel 5) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5),
+ fcb6_eeprom, sizeof(fcb6_eeprom));
/* imux22/imux23 (channels 6/7) - empty */
/* &i2c3 */
/* i2c-mux@72 (PCA9545) - imux24-27, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[3], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[3], 0x72,
+ &minerva_mb_eeprom[0 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c4 */
/* i2c-mux@72 (PCA9545) - imux28-31, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[4], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[4], 0x72,
+ &minerva_mb_eeprom[1 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c5 */
/* i2c-mux@72 (PCA9545) - imux32-35, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[5], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[5], 0x72,
+ &minerva_mb_eeprom[2 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c6 */
/* i2c-mux@72 (PCA9545) - imux36-39, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[6], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[6], 0x72,
+ &minerva_mb_eeprom[3 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c7 - empty */
/* &i2c8 - empty */
/* &i2c9 */
- /* eeprom@50 - atmel,24c64 */
+ /* eeprom@50 - atmel,24c64 (no FRU data captured) */
at24c_eeprom_init(i2c[9], 0x50, 8 * KiB);
/* rtc@51 - nxp,pcf8563 (no model) */
/* rtc@68 - dallas,ds1339 */
@@ -134,20 +804,26 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
/* &i2c12 */
/* i2c-mux@70 (PCA9545) - imux40-42 eeprom@50, imux43 empty */
- minerva_pca9545_eeprom_init(i2c[12], 0x70, 3);
+ minerva_pca9545_eeprom_init(i2c[12], 0x70,
+ minerva_smb12_eeprom,
+ ARRAY_SIZE(minerva_smb12_eeprom));
/* &i2c13 */
/* i2c-mux@70 (PCA9545) - imux44-46 eeprom@50, imux47 empty */
- minerva_pca9545_eeprom_init(i2c[13], 0x70, 3);
+ minerva_pca9545_eeprom_init(i2c[13], 0x70,
+ minerva_smb13_eeprom,
+ ARRAY_SIZE(minerva_smb13_eeprom));
/* &i2c14 */
/* ipmb@10 - ipmb-dev (no model) */
/* &i2c15 */
/* eeprom@50 - atmel,24c128 */
- at24c_eeprom_init(i2c[15], 0x50, 16 * KiB);
+ at24c_eeprom_init_rom(i2c[15], 0x50, 16 * KiB,
+ scm_eeprom, sizeof(scm_eeprom));
/* eeprom@56 - atmel,24c64 */
- at24c_eeprom_init(i2c[15], 0x56, 8 * KiB);
+ at24c_eeprom_init_rom(i2c[15], 0x56, 8 * KiB,
+ bsm_eeprom, sizeof(bsm_eeprom));
}
static void aspeed_machine_minerva_class_init(ObjectClass *oc,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 49/50] hw/arm: minerva: populate EEPROM data
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The board carries 31 FRU EEPROMs: three on the management module
itself, one per fan controller board, and the rest on the compute and
switch boards reached through the I2C muxes.
Populate them from a sanitized dump read from hardware, so guest
firmware can identify the chassis inventory instead of finding blank
parts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2600_minerva.c | 714 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 695 insertions(+), 19 deletions(-)
diff --git a/hw/arm/aspeed_ast2600_minerva.c b/hw/arm/aspeed_ast2600_minerva.c
index 93abc64770..4b6731e279 100644
--- a/hw/arm/aspeed_ast2600_minerva.c
+++ b/hw/arm/aspeed_ast2600_minerva.c
@@ -24,14 +24,667 @@
#define MINERVA_BMC_HW_STRAP2 0x00000800
#define MINERVA_BMC_RAM_SIZE (2 * GiB)
+/* FRU EEPROM content meaningful size */
+#define MINERVA_FRU_LEN 192
+
+/* Motherboards behind each of the four i2c3..i2c6 muxes */
+#define MINERVA_MB_PER_MUX 4
+
+/* Minerva CMM PVT */
+static const uint8_t cmm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x08, 0x11, 0x00, 0x00, 0xe5, 0x01, 0x07, 0x17, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc0, 0xcc, 0x39, 0x30, 0x38, 0x32, 0x43, 0x33,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x13, 0x01, 0x09, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51,
+ 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcf, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76,
+ 0x61, 0x20, 0x43, 0x4d, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32,
+ 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31,
+ 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
+ 0x35, 0xc1, 0x00, 0xef, 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e,
+ 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41, 0x41, 0x30,
+ 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* Minerva_CMM_SCM PVT */
+static const uint8_t scm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd3, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x5f, 0x43, 0x4d,
+ 0x4d, 0x5f, 0x53, 0x43, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34,
+ 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x32,
+ 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
+ 0x30, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x01, 0x08, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
+ 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* BMC Storage Module PVT */
+static const uint8_t bsm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd6, 0x42, 0x4d, 0x43, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+ 0x65, 0x20, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x50, 0x56, 0x54,
+ 0xce, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x35, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x41, 0x33, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x47, 0x01, 0x08, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
+ 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* Minerva Fan Board 1 PVT */
+static const uint8_t fcb1_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x36, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa6, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 2 PVT */
+static const uint8_t fcb2_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x37, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 3 PVT */
+static const uint8_t fcb3_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x38, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 4 PVT */
+static const uint8_t fcb4_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x39, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 5 PVT */
+static const uint8_t fcb5_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x31, 0x30, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xab, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board6 PVT */
+static const uint8_t fcb6_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x31, 0x31, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xaa, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Harma motherboards, in i2c3/i2c4/i2c5/i2c6 mux channel order */
+static const uint8_t minerva_mb_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x23,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x21,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1f,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1d,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x24,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x22,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x20,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x17, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1e,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x15, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1c,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x23,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x21,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1f,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1d,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x14, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1b,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x34, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x22,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x34, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x20,
+ },
+};
+
+/* The four muxes take a quarter of the table each, by hand-written index. */
+QEMU_BUILD_BUG_ON(ARRAY_SIZE(minerva_mb_eeprom) != 4 * MINERVA_MB_PER_MUX);
+
+/* Switch boards on the i2c12 mux */
+static const uint8_t minerva_smb12_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x34, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xcc, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x36, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xca, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x38, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbc, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+};
+
+/* Switch boards on the i2c13 mux */
+static const uint8_t minerva_smb13_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x30, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc3, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x32, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc1, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x34, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbf, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+};
+
/*
* Fan Controller Board (FCB). The Minerva BMC device tree describes six
* identical FCBs, each hanging off one channel of the PCA9548 on i2c2.
*/
-static void minerva_fcb_i2c_init(I2CBus *bus)
+static void minerva_fcb_i2c_init(I2CBus *bus, const uint8_t *fru,
+ uint32_t fru_size)
{
/* eeprom@50 - atmel,24c128 */
- at24c_eeprom_init(bus, 0x50, 16 * KiB);
+ at24c_eeprom_init_rom(bus, 0x50, 16 * KiB, fru, fru_size);
/* pwm@5e - maxim,max31790 (no model) */
/* power-sensor@40/41/44/45 - ti,ina238 */
i2c_slave_create_simple(bus, TYPE_INA238, 0x40);
@@ -48,12 +701,14 @@ static void minerva_fcb_i2c_init(I2CBus *bus)
}
static void minerva_pca9545_eeprom_init(I2CBus *bus, uint8_t addr,
+ const uint8_t fru[][MINERVA_FRU_LEN],
unsigned neeprom)
{
I2CSlave *i2c_mux = i2c_slave_create_simple(bus, TYPE_PCA9545, addr);
for (unsigned idx = 0; idx < neeprom; idx++) {
- at24c_eeprom_init(pca954x_i2c_get_bus(i2c_mux, idx), 0x50, 8 * KiB);
+ at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, idx), 0x50,
+ 8 * KiB, fru[idx], MINERVA_FRU_LEN);
}
}
@@ -87,46 +742,61 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4b);
i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4f);
/* eeprom@54 - atmel,24c128 */
- at24c_eeprom_init(i2c[1], 0x54, 16 * KiB);
+ at24c_eeprom_init_rom(i2c[1], 0x54, 16 * KiB,
+ cmm_eeprom, sizeof(cmm_eeprom));
/* &i2c2 */
/* i2c-mux@77 (PCA9548) - six Fan Controller Boards */
i2c_mux = i2c_slave_create_simple(i2c[2], TYPE_PCA9548, 0x77);
/* FCB 1 - imux16 (channel 1) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1),
+ fcb1_eeprom, sizeof(fcb1_eeprom));
/* FCB 2 - imux17 (channel 0) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0),
+ fcb2_eeprom, sizeof(fcb2_eeprom));
/* FCB 3 - imux18 (channel 3) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3),
+ fcb3_eeprom, sizeof(fcb3_eeprom));
/* FCB 4 - imux19 (channel 2) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2),
+ fcb4_eeprom, sizeof(fcb4_eeprom));
/* FCB 5 - imux20 (channel 4) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4),
+ fcb5_eeprom, sizeof(fcb5_eeprom));
/* FCB 6 - imux21 (channel 5) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5),
+ fcb6_eeprom, sizeof(fcb6_eeprom));
/* imux22/imux23 (channels 6/7) - empty */
/* &i2c3 */
/* i2c-mux@72 (PCA9545) - imux24-27, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[3], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[3], 0x72,
+ &minerva_mb_eeprom[0 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c4 */
/* i2c-mux@72 (PCA9545) - imux28-31, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[4], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[4], 0x72,
+ &minerva_mb_eeprom[1 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c5 */
/* i2c-mux@72 (PCA9545) - imux32-35, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[5], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[5], 0x72,
+ &minerva_mb_eeprom[2 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c6 */
/* i2c-mux@72 (PCA9545) - imux36-39, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[6], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[6], 0x72,
+ &minerva_mb_eeprom[3 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c7 - empty */
/* &i2c8 - empty */
/* &i2c9 */
- /* eeprom@50 - atmel,24c64 */
+ /* eeprom@50 - atmel,24c64 (no FRU data captured) */
at24c_eeprom_init(i2c[9], 0x50, 8 * KiB);
/* rtc@51 - nxp,pcf8563 (no model) */
/* rtc@68 - dallas,ds1339 */
@@ -134,20 +804,26 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
/* &i2c12 */
/* i2c-mux@70 (PCA9545) - imux40-42 eeprom@50, imux43 empty */
- minerva_pca9545_eeprom_init(i2c[12], 0x70, 3);
+ minerva_pca9545_eeprom_init(i2c[12], 0x70,
+ minerva_smb12_eeprom,
+ ARRAY_SIZE(minerva_smb12_eeprom));
/* &i2c13 */
/* i2c-mux@70 (PCA9545) - imux44-46 eeprom@50, imux47 empty */
- minerva_pca9545_eeprom_init(i2c[13], 0x70, 3);
+ minerva_pca9545_eeprom_init(i2c[13], 0x70,
+ minerva_smb13_eeprom,
+ ARRAY_SIZE(minerva_smb13_eeprom));
/* &i2c14 */
/* ipmb@10 - ipmb-dev (no model) */
/* &i2c15 */
/* eeprom@50 - atmel,24c128 */
- at24c_eeprom_init(i2c[15], 0x50, 16 * KiB);
+ at24c_eeprom_init_rom(i2c[15], 0x50, 16 * KiB,
+ scm_eeprom, sizeof(scm_eeprom));
/* eeprom@56 - atmel,24c64 */
- at24c_eeprom_init(i2c[15], 0x56, 8 * KiB);
+ at24c_eeprom_init_rom(i2c[15], 0x56, 8 * KiB,
+ bsm_eeprom, sizeof(bsm_eeprom));
}
static void aspeed_machine_minerva_class_init(ObjectClass *oc,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 49/50] hw/arm: minerva: populate EEPROM data
@ 2026-10-12 14:32 ` Emmanuel Blot via qemu development
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
The board carries 31 FRU EEPROMs: three on the management module
itself, one per fan controller board, and the rest on the compute and
switch boards reached through the I2C muxes.
Populate them from a sanitized dump read from hardware, so guest
firmware can identify the chassis inventory instead of finding blank
parts.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/aspeed_ast2600_minerva.c | 714 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 695 insertions(+), 19 deletions(-)
diff --git a/hw/arm/aspeed_ast2600_minerva.c b/hw/arm/aspeed_ast2600_minerva.c
index 93abc64770..4b6731e279 100644
--- a/hw/arm/aspeed_ast2600_minerva.c
+++ b/hw/arm/aspeed_ast2600_minerva.c
@@ -24,14 +24,667 @@
#define MINERVA_BMC_HW_STRAP2 0x00000800
#define MINERVA_BMC_RAM_SIZE (2 * GiB)
+/* FRU EEPROM content meaningful size */
+#define MINERVA_FRU_LEN 192
+
+/* Motherboards behind each of the four i2c3..i2c6 muxes */
+#define MINERVA_MB_PER_MUX 4
+
+/* Minerva CMM PVT */
+static const uint8_t cmm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x08, 0x11, 0x00, 0x00, 0xe5, 0x01, 0x07, 0x17, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc0, 0xcc, 0x39, 0x30, 0x38, 0x32, 0x43, 0x33,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x13, 0x01, 0x09, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51,
+ 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcf, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76,
+ 0x61, 0x20, 0x43, 0x4d, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32,
+ 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31,
+ 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
+ 0x35, 0xc1, 0x00, 0xef, 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e,
+ 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41, 0x41, 0x30,
+ 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* Minerva_CMM_SCM PVT */
+static const uint8_t scm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd3, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x5f, 0x43, 0x4d,
+ 0x4d, 0x5f, 0x53, 0x43, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34,
+ 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x32,
+ 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
+ 0x30, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x01, 0x08, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
+ 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* BMC Storage Module PVT */
+static const uint8_t bsm_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd6, 0x42, 0x4d, 0x43, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+ 0x65, 0x20, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x50, 0x56, 0x54,
+ 0xce, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x35, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x41, 0x33, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x47, 0x01, 0x08, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
+ 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
+ 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
+};
+
+/* Minerva Fan Board 1 PVT */
+static const uint8_t fcb1_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x36, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa6, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 2 PVT */
+static const uint8_t fcb2_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x37, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 3 PVT */
+static const uint8_t fcb3_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x38, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 4 PVT */
+static const uint8_t fcb4_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x39, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board 5 PVT */
+static const uint8_t fcb5_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x31, 0x30, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xab, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Minerva Fan Board6 PVT */
+static const uint8_t fcb6_eeprom[] = {
+ 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
+ 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
+ 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
+ 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x31, 0x31, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
+ 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xaa, 0x01, 0x05, 0x19, 0xc6,
+ 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
+ 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
+ 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
+};
+
+/* Harma motherboards, in i2c3/i2c4/i2c5/i2c6 mux channel order */
+static const uint8_t minerva_mb_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x23,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x21,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1f,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x31, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1d,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x24,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x22,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x20,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x17, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1e,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x15, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x32, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1c,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x23,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x21,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x34, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1f,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x36, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1d,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x38, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x14, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x33, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x1b,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x34, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x22,
+ },
+ { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
+ 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
+ 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
+ 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
+ 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
+ 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
+ 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x32, 0xcb, 0x30, 0x41,
+ 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
+ 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
+ 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
+ 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
+ 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
+ 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
+ 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
+ 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x34, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
+ 0xc1, 0x20,
+ },
+};
+
+/* The four muxes take a quarter of the table each, by hand-written index. */
+QEMU_BUILD_BUG_ON(ARRAY_SIZE(minerva_mb_eeprom) != 4 * MINERVA_MB_PER_MUX);
+
+/* Switch boards on the i2c12 mux */
+static const uint8_t minerva_smb12_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x34, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xcc, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x36, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xca, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x34, 0x38, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbc, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x34, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+};
+
+/* Switch boards on the i2c13 mux */
+static const uint8_t minerva_smb13_eeprom[][MINERVA_FRU_LEN] = {
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x30, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc3, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x32, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc1, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+ { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
+ 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
+ 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
+ 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
+ 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x35, 0x34, 0xce, 0x41, 0x30, 0x30,
+ 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
+ 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
+ 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbf, 0x01, 0x09,
+ 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
+ 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
+ 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
+ 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
+ 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x35, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
+ 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ },
+};
+
/*
* Fan Controller Board (FCB). The Minerva BMC device tree describes six
* identical FCBs, each hanging off one channel of the PCA9548 on i2c2.
*/
-static void minerva_fcb_i2c_init(I2CBus *bus)
+static void minerva_fcb_i2c_init(I2CBus *bus, const uint8_t *fru,
+ uint32_t fru_size)
{
/* eeprom@50 - atmel,24c128 */
- at24c_eeprom_init(bus, 0x50, 16 * KiB);
+ at24c_eeprom_init_rom(bus, 0x50, 16 * KiB, fru, fru_size);
/* pwm@5e - maxim,max31790 (no model) */
/* power-sensor@40/41/44/45 - ti,ina238 */
i2c_slave_create_simple(bus, TYPE_INA238, 0x40);
@@ -48,12 +701,14 @@ static void minerva_fcb_i2c_init(I2CBus *bus)
}
static void minerva_pca9545_eeprom_init(I2CBus *bus, uint8_t addr,
+ const uint8_t fru[][MINERVA_FRU_LEN],
unsigned neeprom)
{
I2CSlave *i2c_mux = i2c_slave_create_simple(bus, TYPE_PCA9545, addr);
for (unsigned idx = 0; idx < neeprom; idx++) {
- at24c_eeprom_init(pca954x_i2c_get_bus(i2c_mux, idx), 0x50, 8 * KiB);
+ at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, idx), 0x50,
+ 8 * KiB, fru[idx], MINERVA_FRU_LEN);
}
}
@@ -87,46 +742,61 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4b);
i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4f);
/* eeprom@54 - atmel,24c128 */
- at24c_eeprom_init(i2c[1], 0x54, 16 * KiB);
+ at24c_eeprom_init_rom(i2c[1], 0x54, 16 * KiB,
+ cmm_eeprom, sizeof(cmm_eeprom));
/* &i2c2 */
/* i2c-mux@77 (PCA9548) - six Fan Controller Boards */
i2c_mux = i2c_slave_create_simple(i2c[2], TYPE_PCA9548, 0x77);
/* FCB 1 - imux16 (channel 1) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1),
+ fcb1_eeprom, sizeof(fcb1_eeprom));
/* FCB 2 - imux17 (channel 0) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0),
+ fcb2_eeprom, sizeof(fcb2_eeprom));
/* FCB 3 - imux18 (channel 3) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3),
+ fcb3_eeprom, sizeof(fcb3_eeprom));
/* FCB 4 - imux19 (channel 2) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2),
+ fcb4_eeprom, sizeof(fcb4_eeprom));
/* FCB 5 - imux20 (channel 4) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4),
+ fcb5_eeprom, sizeof(fcb5_eeprom));
/* FCB 6 - imux21 (channel 5) */
- minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5));
+ minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5),
+ fcb6_eeprom, sizeof(fcb6_eeprom));
/* imux22/imux23 (channels 6/7) - empty */
/* &i2c3 */
/* i2c-mux@72 (PCA9545) - imux24-27, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[3], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[3], 0x72,
+ &minerva_mb_eeprom[0 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c4 */
/* i2c-mux@72 (PCA9545) - imux28-31, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[4], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[4], 0x72,
+ &minerva_mb_eeprom[1 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c5 */
/* i2c-mux@72 (PCA9545) - imux32-35, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[5], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[5], 0x72,
+ &minerva_mb_eeprom[2 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c6 */
/* i2c-mux@72 (PCA9545) - imux36-39, eeprom@50 on each channel */
- minerva_pca9545_eeprom_init(i2c[6], 0x72, 4);
+ minerva_pca9545_eeprom_init(i2c[6], 0x72,
+ &minerva_mb_eeprom[3 * MINERVA_MB_PER_MUX],
+ MINERVA_MB_PER_MUX);
/* &i2c7 - empty */
/* &i2c8 - empty */
/* &i2c9 */
- /* eeprom@50 - atmel,24c64 */
+ /* eeprom@50 - atmel,24c64 (no FRU data captured) */
at24c_eeprom_init(i2c[9], 0x50, 8 * KiB);
/* rtc@51 - nxp,pcf8563 (no model) */
/* rtc@68 - dallas,ds1339 */
@@ -134,20 +804,26 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
/* &i2c12 */
/* i2c-mux@70 (PCA9545) - imux40-42 eeprom@50, imux43 empty */
- minerva_pca9545_eeprom_init(i2c[12], 0x70, 3);
+ minerva_pca9545_eeprom_init(i2c[12], 0x70,
+ minerva_smb12_eeprom,
+ ARRAY_SIZE(minerva_smb12_eeprom));
/* &i2c13 */
/* i2c-mux@70 (PCA9545) - imux44-46 eeprom@50, imux47 empty */
- minerva_pca9545_eeprom_init(i2c[13], 0x70, 3);
+ minerva_pca9545_eeprom_init(i2c[13], 0x70,
+ minerva_smb13_eeprom,
+ ARRAY_SIZE(minerva_smb13_eeprom));
/* &i2c14 */
/* ipmb@10 - ipmb-dev (no model) */
/* &i2c15 */
/* eeprom@50 - atmel,24c128 */
- at24c_eeprom_init(i2c[15], 0x50, 16 * KiB);
+ at24c_eeprom_init_rom(i2c[15], 0x50, 16 * KiB,
+ scm_eeprom, sizeof(scm_eeprom));
/* eeprom@56 - atmel,24c64 */
- at24c_eeprom_init(i2c[15], 0x56, 8 * KiB);
+ at24c_eeprom_init_rom(i2c[15], 0x56, 8 * KiB,
+ bsm_eeprom, sizeof(bsm_eeprom));
}
static void aspeed_machine_minerva_class_init(ObjectClass *oc,
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 50/50] tests/functional/arm: minerva: add BMC boot test
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
@ 2026-10-12 14:32 ` Emmanuel Blot via
-1 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a functional test booting the Facebook Minerva BMC machine.
Note: The boot image currently lives on a personal fork; a TODO in the
test notes it should be relocated under legoater/qemu-aspeed-boot before
upstreaming.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/functional/arm/meson.build | 2 ++
tests/functional/arm/test_aspeed_minerva.py | 30 +++++++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/tests/functional/arm/meson.build b/tests/functional/arm/meson.build
index 61b00932db..dd1ddf7f6f 100644
--- a/tests/functional/arm/meson.build
+++ b/tests/functional/arm/meson.build
@@ -16,6 +16,7 @@ test_arm_timeouts = {
'aspeed_bletchley' : 480,
'aspeed_catalina' : 480,
'aspeed_gb200nvl_bmc' : 480,
+ 'aspeed_minerva' : 480,
'aspeed_rainier' : 480,
'bpim2u' : 500,
'collie' : 180,
@@ -51,6 +52,7 @@ tests_arm_system_thorough = [
'aspeed_bletchley',
'aspeed_catalina',
'aspeed_gb200nvl_bmc',
+ 'aspeed_minerva',
'aspeed_rainier',
'bpim2u',
'canona1100',
diff --git a/tests/functional/arm/test_aspeed_minerva.py b/tests/functional/arm/test_aspeed_minerva.py
new file mode 100644
index 0000000000..925a82b139
--- /dev/null
+++ b/tests/functional/arm/test_aspeed_minerva.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots the ASPEED machines
+#
+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from qemu_test import Asset
+from aspeed import AspeedTest
+
+
+class MinervaMachine(AspeedTest):
+
+ # TODO: the boot image currently lives on a personal fork; move it under
+ # legoater/qemu-aspeed-boot before upstreaming.
+ ASSET_MINERVA_FLASH = Asset(
+ 'https://github.com/eblot/qemu-aspeed-boot/raw/refs/heads/minerva-bmc/images/minerva-bmc/openbmc-20260720025047/'
+ 'obmc-phosphor-image-minerva-20260720025047.static.mtd.xz',
+ 'bd713df39d3c09846299a2377315465187a4dcd33c29d3b3d683ef13b59f36bb')
+
+ def test_arm_ast2600_minerva_openbmc(self):
+ image_path = self.uncompress(self.ASSET_MINERVA_FLASH)
+
+ self.do_test_arm_aspeed_openbmc('minerva-bmc', image=image_path,
+ uboot='2019.04', cpu_id='0xf00',
+ soc='AST2600 rev A3')
+
+if __name__ == '__main__':
+ AspeedTest.main()
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 50/50] tests/functional/arm: minerva: add BMC boot test
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a functional test booting the Facebook Minerva BMC machine.
Note: The boot image currently lives on a personal fork; a TODO in the
test notes it should be relocated under legoater/qemu-aspeed-boot before
upstreaming.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/functional/arm/meson.build | 2 ++
tests/functional/arm/test_aspeed_minerva.py | 30 +++++++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/tests/functional/arm/meson.build b/tests/functional/arm/meson.build
index 61b00932db..dd1ddf7f6f 100644
--- a/tests/functional/arm/meson.build
+++ b/tests/functional/arm/meson.build
@@ -16,6 +16,7 @@ test_arm_timeouts = {
'aspeed_bletchley' : 480,
'aspeed_catalina' : 480,
'aspeed_gb200nvl_bmc' : 480,
+ 'aspeed_minerva' : 480,
'aspeed_rainier' : 480,
'bpim2u' : 500,
'collie' : 180,
@@ -51,6 +52,7 @@ tests_arm_system_thorough = [
'aspeed_bletchley',
'aspeed_catalina',
'aspeed_gb200nvl_bmc',
+ 'aspeed_minerva',
'aspeed_rainier',
'bpim2u',
'canona1100',
diff --git a/tests/functional/arm/test_aspeed_minerva.py b/tests/functional/arm/test_aspeed_minerva.py
new file mode 100644
index 0000000000..925a82b139
--- /dev/null
+++ b/tests/functional/arm/test_aspeed_minerva.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots the ASPEED machines
+#
+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from qemu_test import Asset
+from aspeed import AspeedTest
+
+
+class MinervaMachine(AspeedTest):
+
+ # TODO: the boot image currently lives on a personal fork; move it under
+ # legoater/qemu-aspeed-boot before upstreaming.
+ ASSET_MINERVA_FLASH = Asset(
+ 'https://github.com/eblot/qemu-aspeed-boot/raw/refs/heads/minerva-bmc/images/minerva-bmc/openbmc-20260720025047/'
+ 'obmc-phosphor-image-minerva-20260720025047.static.mtd.xz',
+ 'bd713df39d3c09846299a2377315465187a4dcd33c29d3b3d683ef13b59f36bb')
+
+ def test_arm_ast2600_minerva_openbmc(self):
+ image_path = self.uncompress(self.ASSET_MINERVA_FLASH)
+
+ self.do_test_arm_aspeed_openbmc('minerva-bmc', image=image_path,
+ uboot='2019.04', cpu_id='0xf00',
+ soc='AST2600 rev A3')
+
+if __name__ == '__main__':
+ AspeedTest.main()
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* [PATCH v2 50/50] tests/functional/arm: minerva: add BMC boot test
@ 2026-10-12 14:32 ` Emmanuel Blot via
0 siblings, 0 replies; 188+ messages in thread
From: Emmanuel Blot via qemu development @ 2026-10-12 14:32 UTC (permalink / raw)
To: qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé, Emmanuel Blot
Add a functional test booting the Facebook Minerva BMC machine.
Note: The boot image currently lives on a personal fork; a TODO in the
test notes it should be relocated under legoater/qemu-aspeed-boot before
upstreaming.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/functional/arm/meson.build | 2 ++
tests/functional/arm/test_aspeed_minerva.py | 30 +++++++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/tests/functional/arm/meson.build b/tests/functional/arm/meson.build
index 61b00932db..dd1ddf7f6f 100644
--- a/tests/functional/arm/meson.build
+++ b/tests/functional/arm/meson.build
@@ -16,6 +16,7 @@ test_arm_timeouts = {
'aspeed_bletchley' : 480,
'aspeed_catalina' : 480,
'aspeed_gb200nvl_bmc' : 480,
+ 'aspeed_minerva' : 480,
'aspeed_rainier' : 480,
'bpim2u' : 500,
'collie' : 180,
@@ -51,6 +52,7 @@ tests_arm_system_thorough = [
'aspeed_bletchley',
'aspeed_catalina',
'aspeed_gb200nvl_bmc',
+ 'aspeed_minerva',
'aspeed_rainier',
'bpim2u',
'canona1100',
diff --git a/tests/functional/arm/test_aspeed_minerva.py b/tests/functional/arm/test_aspeed_minerva.py
new file mode 100644
index 0000000000..925a82b139
--- /dev/null
+++ b/tests/functional/arm/test_aspeed_minerva.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots the ASPEED machines
+#
+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from qemu_test import Asset
+from aspeed import AspeedTest
+
+
+class MinervaMachine(AspeedTest):
+
+ # TODO: the boot image currently lives on a personal fork; move it under
+ # legoater/qemu-aspeed-boot before upstreaming.
+ ASSET_MINERVA_FLASH = Asset(
+ 'https://github.com/eblot/qemu-aspeed-boot/raw/refs/heads/minerva-bmc/images/minerva-bmc/openbmc-20260720025047/'
+ 'obmc-phosphor-image-minerva-20260720025047.static.mtd.xz',
+ 'bd713df39d3c09846299a2377315465187a4dcd33c29d3b3d683ef13b59f36bb')
+
+ def test_arm_ast2600_minerva_openbmc(self):
+ image_path = self.uncompress(self.ASSET_MINERVA_FLASH)
+
+ self.do_test_arm_aspeed_openbmc('minerva-bmc', image=image_path,
+ uboot='2019.04', cpu_id='0xf00',
+ soc='AST2600 rev A3')
+
+if __name__ == '__main__':
+ AspeedTest.main()
--
2.50.1
^ permalink raw reply related [flat|nested] 188+ messages in thread
* Re: [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine
2026-10-12 14:31 ` Emmanuel Blot via qemu development
` (51 preceding siblings ...)
(?)
@ 2026-10-13 6:23 ` Cédric Le Goater
2026-10-13 6:32 ` Jamin Lin
-1 siblings, 1 reply; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 6:23 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé, Glenn Miles
Jamin, Glenn,
Could you please help reviewing some patches of this series ?
Thanks,
C.
On 10/12/26 16:31, Emmanuel Blot wrote:
> Add the minerva-bmc machine and the I2C device models it needs.
>
> Based on master, at:
> 615ece3c40 ("Merge tag 'for-upstream' of
> https://gitlab.com/bonzini/qemu into staging")
>
> New models:
>
> INA230 TI 36V current-shunt and power monitor
> INA238 TI 85V current-shunt, power and temperature monitor
> DS1339 RTC, a DS1338 variant
> PCA9545 4-channel I2C mux with per-channel interrupt inputs
>
> The INA devices derive current and power from the shunt voltage and
> their calibration registers. ALERT output is supported. Measurements
> come in through QOM properties; however, conversion delay is not
> modelled.
>
> Introduce a device class to manage different variants of the DS1338
> register map, such as the DS1339. Improve the DS1338 implementation
> with a Resettable reset, support for the clock-halt bit, and a
> "persist-on-reset" property keeping the registers across a reset.
>
> The minerva-bmc machine is derived from the platform device tree and
> instantiates every device that is supported in QEMU. FRU EEPROMs are
> sanitised images taken from real hardware.
>
> New qtests for the DS1339, INA230, INA238 and PCA9545, plus DS1338
> cases for the register map, and a functional test booting OpenBMC on
> minerva-bmc.
>
> The docs now mention the previously missing catalina-bmc and
> sanmiguel-bmc machines, and the title has been simplified.
>
> One thing worth a second opinion: the PCA954x muxes now have a
> VMStateDescription, which changes the migration stream for the
> existing PCA9546 and PCA9548. The DS1338 vmstate also goes from
> version 2 to 3, but older streams still load fine.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> Changes in v2:
> - Rebase onto master, which now contains the v1 base.
> - minerva: drop ASPEED_RAM_SIZE, removed upstream.
> - No other changes; v1 received no review comments.
> - Link to v1: https://lore.kernel.org/qemu-devel/20260907-bmc-minerva-v1-0-0d83ad610c3b@free.fr
>
> ---
> Emmanuel Blot (50):
> docs/system/arm/aspeed: add the catalina-bmc machine
> docs/system/arm/aspeed: add the sanmiguel-bmc machine
> hw/arm: sanmiguel: remove the second SPI flash controller
> hw/i2c: pca954x: drop unused header inclusions
> hw/i2c: pca954x: add PCA9545 4-channel mux model
> hw/i2c: pca954x: migrate mux state
> tests/qtest: pca9545: add mux test
> hw/rtc: ds1338: move TYPE_DS1338 to a public header
> hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
> hw/rtc: ds1338: implement Resettable reset
> hw/rtc: ds1338: prefix the file-local helpers with ds1338_
> hw/rtc: ds1338: parameterise the register map via a device class
> hw/rtc: ds1338: program the clock from the time registers
> hw/rtc: ds1338: implement the DS1338 clock-halt bit
> hw/rtc: ds1338: refresh the user registers on a read rollover only
> hw/rtc: ds1338: add a persist-on-reset property
> tests/qtest: ds1338: cover the DS1338-specific register map
> tests/qtest: ds1338: cover the power-on register defaults
> tests/qtest: ds1338: cover the clock-halt bit
> tests/qtest: ds1338: cover persist-on-reset
> tests/qtest: ds1338: cover the reserved bits of the time registers
> hw/rtc: ds1338: add DS1339 variant
> tests/qtest: ds1339: add RTC test
> tests/qtest: ds1339: cover power-on defaults and reserved-bit masking
> tests/qtest: ds1339: cover oscillator stop/restart and time programming
> tests/qtest: ds1339: cover the Century bit
> tests/qtest: ds1339: cover the end-of-month rollover
> tests/qtest: ds1339: cover the 12-hour format
> tests/qtest: ds1339: cover the day-of-week counter
> tests/qtest: ds1339: cover persist-on-reset
> tests/qtest: ds1339: cover the migration round trip
> hw/sensor: ina230: add current/voltage/power monitor
> tests/qtest: ina230: add current/voltage/power monitor test
> tests/qtest: ina230: cover voltage injection and power derivation
> tests/qtest: ina230: cover the ALERT limit functions and CVRF
> tests/qtest: ina230: cover the injection-property limits
> tests/qtest: ina230: cover operating modes and triggered conversions
> tests/qtest: ina230: cover ALERT latch persistence
> tests/qtest: ina230: cover a cleared calibration per channel
> hw/sensor: ina238: add current/voltage/power monitor
> tests/qtest: ina238: add current/voltage/power monitor test
> tests/qtest: ina238: cover voltage/temperature injection and derivation
> tests/qtest: ina238: cover the ALERT path and conversion-ready flag
> tests/qtest: ina238: cover the injection-property limits
> tests/qtest: ina238: cover the operating modes
> tests/qtest: ina238: cover conversion-ready and alert-latch semantics
> tests/qtest: ina238: cover a cleared calibration per channel
> hw/arm: minerva: add Facebook BMC machine
> hw/arm: minerva: populate EEPROM data
> tests/functional/arm: minerva: add BMC boot test
>
> docs/system/arm/aspeed.rst | 7 +-
> hw/arm/Kconfig | 2 +
> hw/arm/aspeed_ast2400_palmetto.c | 4 +-
> hw/arm/aspeed_ast2400_supermicrox11.c | 4 +-
> hw/arm/aspeed_ast2500_romulus.c | 4 +-
> hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +-
> hw/arm/aspeed_ast2500_witherspoon.c | 4 +-
> hw/arm/aspeed_ast2600_bletchley.c | 3 +-
> hw/arm/aspeed_ast2600_catalina.c | 2 +-
> hw/arm/aspeed_ast2600_minerva.c | 856 ++++++++++++++++++++++++++++
> hw/arm/aspeed_ast2600_sanmiguel.c | 7 +-
> hw/arm/meson.build | 1 +
> hw/arm/realview.c | 3 +-
> hw/arm/versatilepb.c | 3 +-
> hw/i2c/i2c_mux_pca954x.c | 98 +++-
> hw/ppc/e500.c | 3 +-
> hw/riscv/tt_atlantis.c | 3 +-
> hw/rtc/ds1338.c | 427 +++++++++++---
> hw/sensor/Kconfig | 8 +
> hw/sensor/ina230.c | 597 +++++++++++++++++++
> hw/sensor/ina238.c | 802 ++++++++++++++++++++++++++
> hw/sensor/meson.build | 2 +
> hw/sensor/trace-events | 14 +
> include/hw/i2c/i2c_mux_pca954x.h | 1 +
> include/hw/rtc/ds1338.h | 21 +
> include/hw/sensor/ina230.h | 14 +
> include/hw/sensor/ina238.h | 14 +
> tests/functional/arm/meson.build | 2 +
> tests/functional/arm/test_aspeed_minerva.py | 30 +
> tests/qtest/ds1338-test.c | 191 ++++++-
> tests/qtest/ds1339-test.c | 581 +++++++++++++++++++
> tests/qtest/ina230-test.c | 602 +++++++++++++++++++
> tests/qtest/ina238-test.c | 806 ++++++++++++++++++++++++++
> tests/qtest/meson.build | 4 +
> tests/qtest/pca9545-test.c | 116 ++++
> 35 files changed, 5125 insertions(+), 115 deletions(-)
> ---
> base-commit: 615ece3c406b262996e569f29ea6eed0d81c4d8d
> change-id: 20260907-bmc-minerva-cd13a718c90b
>
> Best regards,
> --
> Emmanuel Blot <emmanuel.blot@free.fr>
^ permalink raw reply [flat|nested] 188+ messages in thread
* RE: [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine
2026-10-13 6:23 ` [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine Cédric Le Goater
@ 2026-10-13 6:32 ` Jamin Lin
2026-10-13 6:40 ` Cédric Le Goater
0 siblings, 1 reply; 188+ messages in thread
From: Jamin Lin @ 2026-10-13 6:32 UTC (permalink / raw)
To: Cédric Le Goater, Emmanuel Blot, qemu-devel@nongnu.org
Cc: Peter Maydell, Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery,
Joel Stanley, Pierrick Bouvier, qemu-arm@nongnu.org, Patrick Leis,
Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-ppc@nongnu.org,
qemu-riscv@nongnu.org, Philippe Mathieu-Daudé, Glenn Miles
Hi Cédric, Emmanuel
Due to an issue with the ASPEED mail server, we cannot receive patches distributed through the QEMU mailing list. We can only receive emails sent directly to our ASPEED addresses,
for example, by including --to jamin_lin@aspeedtech.com, --cc jamin_lin@aspeedtech.com or --to-cmd "./scripts/get_maintainer.pl *.patch" in the git send-email command.
I will use my personal GMAIL to reply this patch series.
jaminlin1207@gmail.com
Sorry for you inconvenience
Jamin
> -----Original Message-----
> From: Cédric Le Goater <clg@kaod.org>
> Sent: Tuesday, October 13, 2026 2:24 PM
> To: Emmanuel Blot <emmanuel.blot@free.fr>; qemu-devel@nongnu.org
> Cc: Peter Maydell <peter.maydell@linaro.org>; Steven Lee
> <steven_lee@aspeedtech.com>; Troy Lee <leetroy@gmail.com>; Jamin Lin
> <jamin_lin@aspeedtech.com>; Kane Chen <kane_chen@aspeedtech.com>;
> Andrew Jeffery <andrew@codeconstruct.com.au>; Joel Stanley
> <joel@jms.id.au>; Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>;
> qemu-arm@nongnu.org; Patrick Leis <venture@google.com>; Fabiano Rosas
> <farosas@suse.de>; Laurent Vivier <lvivier@redhat.com>; Paolo Bonzini
> <pbonzini@redhat.com>; qemu-ppc@nongnu.org; qemu-riscv@nongnu.org;
> Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>; Glenn Miles
> <milesg@linux.ibm.com>
> Subject: Re: [PATCH v2 00/50] Aspeed: I2C device models and the Minerva
> BMC machine
>
> Jamin, Glenn,
>
> Could you please help reviewing some patches of this series ?
>
> Thanks,
>
> C.
>
>
>
> On 10/12/26 16:31, Emmanuel Blot wrote:
> > Add the minerva-bmc machine and the I2C device models it needs.
> >
> > Based on master, at:
> > 615ece3c40 ("Merge tag 'for-upstream' of
> > https://gitlab.com/bonzini/qemu into staging")
> >
> > New models:
> >
> > INA230 TI 36V current-shunt and power monitor
> > INA238 TI 85V current-shunt, power and temperature monitor
> > DS1339 RTC, a DS1338 variant
> > PCA9545 4-channel I2C mux with per-channel interrupt inputs
> >
> > The INA devices derive current and power from the shunt voltage and
> > their calibration registers. ALERT output is supported. Measurements
> > come in through QOM properties; however, conversion delay is not
> > modelled.
> >
> > Introduce a device class to manage different variants of the DS1338
> > register map, such as the DS1339. Improve the DS1338 implementation
> > with a Resettable reset, support for the clock-halt bit, and a
> > "persist-on-reset" property keeping the registers across a reset.
> >
> > The minerva-bmc machine is derived from the platform device tree and
> > instantiates every device that is supported in QEMU. FRU EEPROMs are
> > sanitised images taken from real hardware.
> >
> > New qtests for the DS1339, INA230, INA238 and PCA9545, plus DS1338
> > cases for the register map, and a functional test booting OpenBMC on
> > minerva-bmc.
> >
> > The docs now mention the previously missing catalina-bmc and
> > sanmiguel-bmc machines, and the title has been simplified.
> >
> > One thing worth a second opinion: the PCA954x muxes now have a
> > VMStateDescription, which changes the migration stream for the
> > existing PCA9546 and PCA9548. The DS1338 vmstate also goes from
> > version 2 to 3, but older streams still load fine.
> >
> > Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> > ---
> > Changes in v2:
> > - Rebase onto master, which now contains the v1 base.
> > - minerva: drop ASPEED_RAM_SIZE, removed upstream.
> > - No other changes; v1 received no review comments.
> > - Link to v1:
> > https://lore.kernel.org/qemu-devel/20260907-bmc-minerva-v1-0-0d83ad610
> > c3b@free.fr
> >
> > ---
> > Emmanuel Blot (50):
> > docs/system/arm/aspeed: add the catalina-bmc machine
> > docs/system/arm/aspeed: add the sanmiguel-bmc machine
> > hw/arm: sanmiguel: remove the second SPI flash controller
> > hw/i2c: pca954x: drop unused header inclusions
> > hw/i2c: pca954x: add PCA9545 4-channel mux model
> > hw/i2c: pca954x: migrate mux state
> > tests/qtest: pca9545: add mux test
> > hw/rtc: ds1338: move TYPE_DS1338 to a public header
> > hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338"
> string
> > hw/rtc: ds1338: implement Resettable reset
> > hw/rtc: ds1338: prefix the file-local helpers with ds1338_
> > hw/rtc: ds1338: parameterise the register map via a device class
> > hw/rtc: ds1338: program the clock from the time registers
> > hw/rtc: ds1338: implement the DS1338 clock-halt bit
> > hw/rtc: ds1338: refresh the user registers on a read rollover only
> > hw/rtc: ds1338: add a persist-on-reset property
> > tests/qtest: ds1338: cover the DS1338-specific register map
> > tests/qtest: ds1338: cover the power-on register defaults
> > tests/qtest: ds1338: cover the clock-halt bit
> > tests/qtest: ds1338: cover persist-on-reset
> > tests/qtest: ds1338: cover the reserved bits of the time registers
> > hw/rtc: ds1338: add DS1339 variant
> > tests/qtest: ds1339: add RTC test
> > tests/qtest: ds1339: cover power-on defaults and reserved-bit
> masking
> > tests/qtest: ds1339: cover oscillator stop/restart and time
> programming
> > tests/qtest: ds1339: cover the Century bit
> > tests/qtest: ds1339: cover the end-of-month rollover
> > tests/qtest: ds1339: cover the 12-hour format
> > tests/qtest: ds1339: cover the day-of-week counter
> > tests/qtest: ds1339: cover persist-on-reset
> > tests/qtest: ds1339: cover the migration round trip
> > hw/sensor: ina230: add current/voltage/power monitor
> > tests/qtest: ina230: add current/voltage/power monitor test
> > tests/qtest: ina230: cover voltage injection and power derivation
> > tests/qtest: ina230: cover the ALERT limit functions and CVRF
> > tests/qtest: ina230: cover the injection-property limits
> > tests/qtest: ina230: cover operating modes and triggered
> conversions
> > tests/qtest: ina230: cover ALERT latch persistence
> > tests/qtest: ina230: cover a cleared calibration per channel
> > hw/sensor: ina238: add current/voltage/power monitor
> > tests/qtest: ina238: add current/voltage/power monitor test
> > tests/qtest: ina238: cover voltage/temperature injection and
> derivation
> > tests/qtest: ina238: cover the ALERT path and conversion-ready flag
> > tests/qtest: ina238: cover the injection-property limits
> > tests/qtest: ina238: cover the operating modes
> > tests/qtest: ina238: cover conversion-ready and alert-latch
> semantics
> > tests/qtest: ina238: cover a cleared calibration per channel
> > hw/arm: minerva: add Facebook BMC machine
> > hw/arm: minerva: populate EEPROM data
> > tests/functional/arm: minerva: add BMC boot test
> >
> > docs/system/arm/aspeed.rst | 7 +-
> > hw/arm/Kconfig | 2 +
> > hw/arm/aspeed_ast2400_palmetto.c | 4 +-
> > hw/arm/aspeed_ast2400_supermicrox11.c | 4 +-
> > hw/arm/aspeed_ast2500_romulus.c | 4 +-
> > hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +-
> > hw/arm/aspeed_ast2500_witherspoon.c | 4 +-
> > hw/arm/aspeed_ast2600_bletchley.c | 3 +-
> > hw/arm/aspeed_ast2600_catalina.c | 2 +-
> > hw/arm/aspeed_ast2600_minerva.c | 856
> ++++++++++++++++++++++++++++
> > hw/arm/aspeed_ast2600_sanmiguel.c | 7 +-
> > hw/arm/meson.build | 1 +
> > hw/arm/realview.c | 3 +-
> > hw/arm/versatilepb.c | 3 +-
> > hw/i2c/i2c_mux_pca954x.c | 98 +++-
> > hw/ppc/e500.c | 3 +-
> > hw/riscv/tt_atlantis.c | 3 +-
> > hw/rtc/ds1338.c | 427 +++++++++++---
> > hw/sensor/Kconfig | 8 +
> > hw/sensor/ina230.c | 597
> +++++++++++++++++++
> > hw/sensor/ina238.c | 802
> ++++++++++++++++++++++++++
> > hw/sensor/meson.build | 2 +
> > hw/sensor/trace-events | 14 +
> > include/hw/i2c/i2c_mux_pca954x.h | 1 +
> > include/hw/rtc/ds1338.h | 21 +
> > include/hw/sensor/ina230.h | 14 +
> > include/hw/sensor/ina238.h | 14 +
> > tests/functional/arm/meson.build | 2 +
> > tests/functional/arm/test_aspeed_minerva.py | 30 +
> > tests/qtest/ds1338-test.c | 191 ++++++-
> > tests/qtest/ds1339-test.c | 581
> +++++++++++++++++++
> > tests/qtest/ina230-test.c | 602
> +++++++++++++++++++
> > tests/qtest/ina238-test.c | 806
> ++++++++++++++++++++++++++
> > tests/qtest/meson.build | 4 +
> > tests/qtest/pca9545-test.c | 116 ++++
> > 35 files changed, 5125 insertions(+), 115 deletions(-)
> > ---
> > base-commit: 615ece3c406b262996e569f29ea6eed0d81c4d8d
> > change-id: 20260907-bmc-minerva-cd13a718c90b
> >
> > Best regards,
> > --
> > Emmanuel Blot <emmanuel.blot@free.fr>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine
2026-10-13 6:32 ` Jamin Lin
@ 2026-10-13 6:40 ` Cédric Le Goater
0 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 6:40 UTC (permalink / raw)
To: Jamin Lin, Emmanuel Blot, qemu-devel@nongnu.org
Cc: Peter Maydell, Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery,
Joel Stanley, Pierrick Bouvier, qemu-arm@nongnu.org, Patrick Leis,
Fabiano Rosas, Laurent Vivier, Paolo Bonzini, qemu-ppc@nongnu.org,
qemu-riscv@nongnu.org, Philippe Mathieu-Daudé, Glenn Miles
On 10/13/26 08:32, Jamin Lin wrote:
> Hi Cédric, Emmanuel
>
> Due to an issue with the ASPEED mail server, we cannot receive patches distributed through the QEMU mailing list. We can only receive emails sent directly to our ASPEED addresses,
Take a look at lei :
https://people.kernel.org/monsieuricon/lore-lei-part-1-getting-started
It's a very useful tool to collect mailing list discussions.
> for example, by including --to jamin_lin@aspeedtech.com, --cc jamin_lin@aspeedtech.com or --to-cmd "./scripts/get_maintainer.pl *.patch" in the git send-email command.
> I will use my personal GMAIL to reply this patch series.
> jaminlin1207@gmail.com
Thanks a lot,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 01/50] docs/system/arm/aspeed: add the catalina-bmc machine
2026-10-12 14:31 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-13 6:56 ` 林建明
-1 siblings, 0 replies; 188+ messages in thread
From: 林建明 @ 2026-10-13 6:56 UTC (permalink / raw)
To: Emmanuel Blot
Cc: qemu-devel, Cédric Le Goater, Peter Maydell, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Emmanuel Blot via qemu development <qemu-devel@nongnu.org> 於
2026年10月12日週一 下午10:55寫道:
>
> The Facebook Catalina BMC machine is supported by hw/arm but has never
> been listed in the Aspeed documentation. Add it to the AST2600 machine
> table.
>
> While at it, shorten the document title. Listing every machine name in
> the title duplicates the tables right below it, and made the title (and
> its underline) grow past 350 columns. The machine list stays available
> where it is readable, in the per-SoC tables.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> docs/system/arm/aspeed.rst | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
> index 2d51ceeb84..933df41f96 100644
> --- a/docs/system/arm/aspeed.rst
> +++ b/docs/system/arm/aspeed.rst
> @@ -1,5 +1,5 @@
> -Aspeed family boards (``anacapa-bmc``, ``ast2500-evb``, ``ast2600-evb``, ``bletchley-bmc``, ``fuji-bmc``, ``gb200nvl-bmc``, ``fby35-bmc``, ``g220a-bmc``, ``palmetto-bmc``, ``quanta-q71l-bmc``, ``rainier-bmc``, ``romulus-bmc``, ``supermicrox11-bmc``, ``supermicrox11spi-bmc``, ``tiogapass-bmc``, ``witherspoon-bmc``, ``yosemitev2-bmc``)
> -===============================================================================================================================================================================================================================================================================================================================================
> +Aspeed family boards
> +====================
>
> The QEMU Aspeed machines model BMCs of various OpenPOWER systems and
> Aspeed evaluation boards. They are based on different releases of the
> @@ -33,6 +33,7 @@ AST2600 SoC based machines :
> - ``fuji-bmc`` Facebook Fuji BMC
> - ``bletchley-bmc`` Facebook Bletchley BMC
> - ``fby35-bmc`` Facebook fby35 BMC
> +- ``catalina-bmc`` Facebook Catalina BMC
> - ``anacapa-bmc`` Facebook Anacapa BMC
> - ``gb200nvl-bmc`` Nvidia GB200nvl BMC
>
>
> --
> 2.50.1
>
>
Reviewed-by: Jamin Lin <jaminlin1207@gmail.com>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 02/50] docs/system/arm/aspeed: add the sanmiguel-bmc machine
2026-10-12 14:31 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-13 6:59 ` 林建明
-1 siblings, 0 replies; 188+ messages in thread
From: 林建明 @ 2026-10-13 6:59 UTC (permalink / raw)
To: Emmanuel Blot
Cc: qemu-devel, Cédric Le Goater, Peter Maydell, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Emmanuel Blot via qemu development <qemu-devel@nongnu.org>:
>
> The Facebook SanMiguel BMC machine is supported by hw/arm but has never
> been listed in the Aspeed documentation. Add it to the AST2600 machine
> table.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> docs/system/arm/aspeed.rst | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
> index 933df41f96..f38f33cbe1 100644
> --- a/docs/system/arm/aspeed.rst
> +++ b/docs/system/arm/aspeed.rst
> @@ -34,6 +34,7 @@ AST2600 SoC based machines :
> - ``bletchley-bmc`` Facebook Bletchley BMC
> - ``fby35-bmc`` Facebook fby35 BMC
> - ``catalina-bmc`` Facebook Catalina BMC
> +- ``sanmiguel-bmc`` Facebook SanMiguel BMC
> - ``anacapa-bmc`` Facebook Anacapa BMC
> - ``gb200nvl-bmc`` Nvidia GB200nvl BMC
>
>
> --
> 2.50.1
>
>
Reviewed-by: Jamin Lin <jaminlin1207@gmail.com>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 03/50] hw/arm: sanmiguel: remove the second SPI flash controller
2026-10-12 14:31 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-13 7:00 ` 林建明
-1 siblings, 0 replies; 188+ messages in thread
From: 林建明 @ 2026-10-13 7:00 UTC (permalink / raw)
To: Emmanuel Blot
Cc: qemu-devel, Cédric Le Goater, Peter Maydell, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
>
> The board only populates the flash device attached to the FMC
> controller; the SoC SPI controllers are left unused. Advertising a
> part there presents the guest with a flash device the hardware does
> not have.
>
> While here, add the missing year to the copyright line.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/arm/aspeed_ast2600_sanmiguel.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
> index 345a9c9416..ac2aef740a 100644
> --- a/hw/arm/aspeed_ast2600_sanmiguel.c
> +++ b/hw/arm/aspeed_ast2600_sanmiguel.c
> @@ -1,7 +1,7 @@
> /*
> * Facebook SanMiguel BMC
> *
> - * Copyright (c) Meta Platforms, Inc. and affiliates.
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> *
> * SPDX-License-Identifier: GPL-2.0-or-later
> */
> @@ -384,7 +384,7 @@ static void aspeed_machine_sanmiguel_class_init(ObjectClass *oc,
> amc->hw_strap1 = SANMIGUEL_BMC_HW_STRAP1;
> amc->hw_strap2 = SANMIGUEL_BMC_HW_STRAP2;
> amc->fmc_model = "mx66l1g45g";
> - amc->spi_model = "mx66l1g45g";
> + amc->spi_model = NULL;
> amc->num_cs = 2;
> amc->macs_mask = ASPEED_MAC0_ON;
> amc->i2c_init = sanmiguel_bmc_i2c_init;
>
> --
> 2.50.1
>
>
Reviewed-by: Jamin Lin <jaminlin1207@gmail.com>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 09/50] hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-13 7:04 ` 林建明
-1 siblings, 0 replies; 188+ messages in thread
From: 林建明 @ 2026-10-13 7:04 UTC (permalink / raw)
To: Emmanuel Blot
Cc: qemu-devel, Cédric Le Goater, Peter Maydell, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
> Now that TYPE_DS1338 is exposed by include/hw/rtc/ds1338.h, use it in
> the machines that instantiate the RTC by its raw type name string.
>
> No functional change.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/arm/aspeed_ast2400_palmetto.c | 4 +++-
> hw/arm/aspeed_ast2400_supermicrox11.c | 4 +++-
> hw/arm/aspeed_ast2500_romulus.c | 4 +++-
> hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +++-
> hw/arm/aspeed_ast2500_witherspoon.c | 4 +++-
> hw/arm/aspeed_ast2600_bletchley.c | 3 ++-
> hw/arm/aspeed_ast2600_catalina.c | 2 +-
> hw/arm/aspeed_ast2600_sanmiguel.c | 3 +--
> hw/arm/realview.c | 3 ++-
> hw/arm/versatilepb.c | 3 ++-
> hw/ppc/e500.c | 3 ++-
> hw/riscv/tt_atlantis.c | 3 ++-
> 12 files changed, 27 insertions(+), 13 deletions(-)
>
> diff --git a/hw/arm/aspeed_ast2400_palmetto.c b/hw/arm/aspeed_ast2400_palmetto.c
> index e2350e5b6c..185185f0bb 100644
> --- a/hw/arm/aspeed_ast2400_palmetto.c
> +++ b/hw/arm/aspeed_ast2400_palmetto.c
> @@ -11,6 +11,7 @@
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> #include "hw/i2c/smbus_eeprom.h"
> +#include "hw/rtc/ds1338.h"
>
> /* Palmetto hardware value: 0x120CE416 */
> #define PALMETTO_BMC_HW_STRAP1 ( \
> @@ -36,7 +37,8 @@ static void palmetto_bmc_i2c_init(AspeedMachineState *bmc)
> * The palmetto platform expects a ds3231 RTC but a ds1338 is
> * enough to provide basic RTC features. Alarms will be missing
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
> + 0x68);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2400_supermicrox11.c b/hw/arm/aspeed_ast2400_supermicrox11.c
> index c75898cc6d..051e072c5a 100644
> --- a/hw/arm/aspeed_ast2400_supermicrox11.c
> +++ b/hw/arm/aspeed_ast2400_supermicrox11.c
> @@ -11,6 +11,7 @@
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> #include "hw/i2c/smbus_eeprom.h"
> +#include "hw/rtc/ds1338.h"
>
> /* TODO: Find the actual hardware value */
> #define SUPERMICROX11_BMC_HW_STRAP1 ( \
> @@ -36,7 +37,8 @@ static void supermicrox11_bmc_i2c_init(AspeedMachineState *bmc)
> * The palmetto platform expects a ds3231 RTC but a ds1338 is
> * enough to provide basic RTC features. Alarms will be missing
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
> + 0x68);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2500_romulus.c b/hw/arm/aspeed_ast2500_romulus.c
> index 340a74408b..87f90ed361 100644
> --- a/hw/arm/aspeed_ast2500_romulus.c
> +++ b/hw/arm/aspeed_ast2500_romulus.c
> @@ -10,6 +10,7 @@
> #include "qapi/error.h"
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> +#include "hw/rtc/ds1338.h"
>
> /* Romulus hardware value: 0xF10AD206 */
> #define ROMULUS_BMC_HW_STRAP1 ( \
> @@ -29,7 +30,8 @@ static void romulus_bmc_i2c_init(AspeedMachineState *bmc)
> * The romulus board expects Epson RX8900 I2C RTC but a ds1338 is
> * good enough
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
> + 0x32);
> }
>
> static void aspeed_machine_romulus_class_init(ObjectClass *oc,
> diff --git a/hw/arm/aspeed_ast2500_supermicro-x11spi.c b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
> index 5a7e49d6cc..dc0248422d 100644
> --- a/hw/arm/aspeed_ast2500_supermicro-x11spi.c
> +++ b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
> @@ -11,6 +11,7 @@
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> #include "hw/i2c/smbus_eeprom.h"
> +#include "hw/rtc/ds1338.h"
>
> /* TODO: Find the actual hardware value */
> #define SUPERMICRO_X11SPI_BMC_HW_STRAP1 ( \
> @@ -32,7 +33,8 @@ static void supermicro_x11spi_bmc_i2c_init(AspeedMachineState *bmc)
> * The palmetto platform expects a ds3231 RTC but a ds1338 is
> * enough to provide basic RTC features. Alarms will be missing
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
> + 0x68);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2500_witherspoon.c b/hw/arm/aspeed_ast2500_witherspoon.c
> index 26ddb67347..c69065846c 100644
> --- a/hw/arm/aspeed_ast2500_witherspoon.c
> +++ b/hw/arm/aspeed_ast2500_witherspoon.c
> @@ -14,6 +14,7 @@
> #include "hw/sensor/tmp105.h"
> #include "hw/i2c/smbus_eeprom.h"
> #include "hw/gpio/pca9552.h"
> +#include "hw/rtc/ds1338.h"
>
> /* Witherspoon hardware value: 0xF10AD216 */
> #define WITHERSPOON_BMC_HW_STRAP1 ( \
> @@ -70,7 +71,8 @@ static void witherspoon_bmc_i2c_init(AspeedMachineState *bmc)
> * The witherspoon board expects Epson RX8900 I2C RTC but a ds1338 is
> * good enough
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
> + 0x32);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 11), 0x51,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2600_bletchley.c b/hw/arm/aspeed_ast2600_bletchley.c
> index 6fa606c047..0e746055a2 100644
> --- a/hw/arm/aspeed_ast2600_bletchley.c
> +++ b/hw/arm/aspeed_ast2600_bletchley.c
> @@ -12,6 +12,7 @@
> #include "hw/arm/aspeed_soc.h"
> #include "hw/gpio/pca9552.h"
> #include "hw/nvram/eeprom_at24c.h"
> +#include "hw/rtc/ds1338.h"
>
> #define TYPE_TMP421 "tmp421"
> /* Bletchley hardware value */
> @@ -44,7 +45,7 @@ static void bletchley_bmc_i2c_init(AspeedMachineState *bmc)
> /* Bus 6 */
> at24c_eeprom_init(i2c[6], 0x56, 65536);
> /* Missing model: nxp,pcf85263 @ 0x51 , but ds1338 works enough */
> - i2c_slave_create_simple(i2c[6], "ds1338", 0x51);
> + i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x51);
>
>
> /* Bus 7 */
> diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
> index 4f2b396f6f..b15d932086 100644
> --- a/hw/arm/aspeed_ast2600_catalina.c
> +++ b/hw/arm/aspeed_ast2600_catalina.c
> @@ -14,6 +14,7 @@
> #include "hw/gpio/pca9552.h"
> #include "hw/gpio/pca9554.h"
> #include "hw/nvram/eeprom_at24c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/sensor/tmp105.h"
>
> /* Catalina hardware value */
> @@ -22,7 +23,6 @@
> #define CATALINA_BMC_RAM_SIZE (2 * GiB)
>
> #define TYPE_TMP421 "tmp421"
> -#define TYPE_DS1338 "ds1338"
>
> /*
> * "BMC Storage Module" FRU data. Generated with frugen.
> diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
> index ac2aef740a..b0935683e2 100644
> --- a/hw/arm/aspeed_ast2600_sanmiguel.c
> +++ b/hw/arm/aspeed_ast2600_sanmiguel.c
> @@ -13,6 +13,7 @@
> #include "hw/gpio/pca9552.h"
> #include "hw/gpio/pca9554.h"
> #include "hw/nvram/eeprom_at24c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/sensor/tmp105.h"
>
> /* SanMiguel hardware values */
> @@ -20,8 +21,6 @@
> #define SANMIGUEL_BMC_HW_STRAP2 0x00000000
> #define SANMIGUEL_BMC_RAM_SIZE (2 * GiB)
>
> -#define TYPE_DS1338 "ds1338"
> -
> /*
> * "Front IO" FRU data. Generated with frugen.
> *
> diff --git a/hw/arm/realview.c b/hw/arm/realview.c
> index ceba0331b0..4cdf939511 100644
> --- a/hw/arm/realview.c
> +++ b/hw/arm/realview.c
> @@ -28,6 +28,7 @@
> #include "hw/intc/realview_gic.h"
> #include "hw/core/irq.h"
> #include "hw/i2c/arm_sbcon_i2c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/sd/sd.h"
> #include "target/arm/cpu-qom.h"
>
> @@ -323,7 +324,7 @@ static void realview_init(MachineState *machine,
>
> dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
> i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
> - i2c_slave_create_simple(i2c, "ds1338", 0x68);
> + i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
>
> /* Memory map for RealView Emulation Baseboard: */
> /* 0x10000000 System registers. */
> diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c
> index f94bf96599..737cd21c27 100644
> --- a/hw/arm/versatilepb.c
> +++ b/hw/arm/versatilepb.c
> @@ -18,6 +18,7 @@
> #include "hw/pci/pci.h"
> #include "hw/i2c/i2c.h"
> #include "hw/i2c/arm_sbcon_i2c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/core/irq.h"
> #include "hw/core/boards.h"
> #include "hw/block/flash.h"
> @@ -346,7 +347,7 @@ static void versatile_init(MachineState *machine, int board_id)
>
> dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
> i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
> - i2c_slave_create_simple(i2c, "ds1338", 0x68);
> + i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
>
> /* Add PL041 AACI Interface to the LM4549 codec */
> pl041 = qdev_new("pl041");
> diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c
> index 1876882a04..469cef9d2f 100644
> --- a/hw/ppc/e500.c
> +++ b/hw/ppc/e500.c
> @@ -50,6 +50,7 @@
> #include "hw/core/platform-bus.h"
> #include "hw/net/fsl_etsec/etsec.h"
> #include "hw/i2c/i2c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/core/irq.h"
> #include "hw/sd/sdhci.h"
> #include "hw/misc/unimp.h"
> @@ -1025,7 +1026,7 @@ void ppce500_init(MachineState *machine)
> memory_region_add_subregion(ccsr_addr_space, MPC8544_I2C_REGS_OFFSET,
> sysbus_mmio_get_region(s, 0));
> i2c = I2C_BUS(qdev_get_child_bus(dev, "i2c"));
> - i2c_slave_create_simple(i2c, "ds1338", RTC_REGS_OFFSET);
> + i2c_slave_create_simple(i2c, TYPE_DS1338, RTC_REGS_OFFSET);
>
> /* eSDHC */
> if (pmc->has_esdhc) {
> diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
> index 8a219afef7..910713c6bb 100644
> --- a/hw/riscv/tt_atlantis.c
> +++ b/hw/riscv/tt_atlantis.c
> @@ -27,6 +27,7 @@
> #include "hw/char/serial-mm.h"
> #include "hw/intc/riscv_aclint.h"
> #include "hw/misc/unimp.h"
> +#include "hw/rtc/ds1338.h"
>
> #include "system/system.h"
> #include "system/device_tree.h"
> @@ -657,7 +658,7 @@ static void tt_atlantis_machine_init(MachineState *machine)
> qdev_realize(DEVICE(&ams->soc), NULL, &error_fatal);
>
> /* I2C peripherals: qemu specific */
> - i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f);
> + i2c_slave_create_simple(i2c_get_bus(s, 0), TYPE_DS1338, 0x6f);
> i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48);
>
> /* Load or create device tree */
>
> --
> 2.50.1
>
>
LGTM
Reviewed-by: Jamin Lin <jaminlin1207@gmail.com>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 49/50] hw/arm: minerva: populate EEPROM data
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-13 7:12 ` 林建明
-1 siblings, 0 replies; 188+ messages in thread
From: 林建明 @ 2026-10-13 7:12 UTC (permalink / raw)
To: Emmanuel Blot
Cc: qemu-devel, Cédric Le Goater, Peter Maydell, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
>
> The board carries 31 FRU EEPROMs: three on the management module
> itself, one per fan controller board, and the rest on the compute and
> switch boards reached through the I2C muxes.
>
> Populate them from a sanitized dump read from hardware, so guest
> firmware can identify the chassis inventory instead of finding blank
> parts.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/arm/aspeed_ast2600_minerva.c | 714 ++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 695 insertions(+), 19 deletions(-)
>
> diff --git a/hw/arm/aspeed_ast2600_minerva.c b/hw/arm/aspeed_ast2600_minerva.c
> index 93abc64770..4b6731e279 100644
> --- a/hw/arm/aspeed_ast2600_minerva.c
> +++ b/hw/arm/aspeed_ast2600_minerva.c
> @@ -24,14 +24,667 @@
> #define MINERVA_BMC_HW_STRAP2 0x00000800
> #define MINERVA_BMC_RAM_SIZE (2 * GiB)
>
> +/* FRU EEPROM content meaningful size */
> +#define MINERVA_FRU_LEN 192
> +
> +/* Motherboards behind each of the four i2c3..i2c6 muxes */
> +#define MINERVA_MB_PER_MUX 4
> +
> +/* Minerva CMM PVT */
> +static const uint8_t cmm_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x08, 0x11, 0x00, 0x00, 0xe5, 0x01, 0x07, 0x17, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc0, 0xcc, 0x39, 0x30, 0x38, 0x32, 0x43, 0x33,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x13, 0x01, 0x09, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51,
> + 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcf, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76,
> + 0x61, 0x20, 0x43, 0x4d, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
> + 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32,
> + 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31,
> + 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
> + 0x35, 0xc1, 0x00, 0xef, 0x01, 0x08, 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e,
> + 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72,
> + 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41, 0x41, 0x30,
> + 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33,
> + 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
> +};
> +
> +/* Minerva_CMM_SCM PVT */
> +static const uint8_t scm_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd3, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x5f, 0x43, 0x4d,
> + 0x4d, 0x5f, 0x53, 0x43, 0x4d, 0x20, 0x50, 0x56, 0x54, 0xce, 0x41, 0x41,
> + 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34,
> + 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x32,
> + 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e, 0x31,
> + 0x30, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x01, 0x08, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
> + 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
> + 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
> +};
> +
> +/* BMC Storage Module PVT */
> +static const uint8_t bsm_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd6, 0x42, 0x4d, 0x43, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
> + 0x65, 0x20, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x50, 0x56, 0x54,
> + 0xce, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x35, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x41, 0x33, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
> + 0x30, 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x47, 0x01, 0x08, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41,
> + 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41,
> + 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x65,
> +};
> +
> +/* Minerva Fan Board 1 PVT */
> +static const uint8_t fcb1_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
> + 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x36, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
> + 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa6, 0x01, 0x05, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
> + 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
> +};
> +
> +/* Minerva Fan Board 2 PVT */
> +static const uint8_t fcb2_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
> + 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x37, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
> + 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x05, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
> + 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
> +};
> +
> +/* Minerva Fan Board 3 PVT */
> +static const uint8_t fcb3_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
> + 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x38, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
> + 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x05, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
> + 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
> +};
> +
> +/* Minerva Fan Board 4 PVT */
> +static const uint8_t fcb4_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
> + 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x39, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
> + 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x05, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
> + 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
> +};
> +
> +/* Minerva Fan Board 5 PVT */
> +static const uint8_t fcb5_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
> + 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x31, 0x30, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
> + 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xab, 0x01, 0x05, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
> + 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
> +};
> +
> +/* Minerva Fan Board6 PVT */
> +static const uint8_t fcb6_eeprom[] = {
> + 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05, 0x00, 0xcb,
> + 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x31, 0xd0,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2,
> + 0x01, 0x0a, 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd5, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20, 0x46, 0x61,
> + 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x50, 0x56, 0x54, 0xce,
> + 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x31, 0x31, 0xcb, 0x30, 0x41, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x30, 0x34, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30,
> + 0x2e, 0x31, 0x30, 0xc1, 0x00, 0x00, 0x00, 0xaa, 0x01, 0x05, 0x19, 0xc6,
> + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d,
> + 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0xc0, 0xc3, 0x50, 0x56, 0x54, 0xc0,
> + 0xc4, 0x51, 0x45, 0x4d, 0x55, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x83,
> +};
> +
> +/* Harma motherboards, in i2c3/i2c4/i2c5/i2c6 mux channel order */
> +static const uint8_t minerva_mb_eeprom[][MINERVA_FRU_LEN] = {
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x32, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x31, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x23,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x31, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x21,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x36, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x31, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x1f,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x31, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x1d,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x30, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x32, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x24,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x32, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x32, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x22,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x34, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x32, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x20,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x36, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x17, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x32, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x1e,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x38, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x15, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x32, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x1c,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x33, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x23,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x32, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x33, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x21,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x34, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x33, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x1f,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x36, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x33, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x1d,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x38, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x14, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x33, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x1b,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x34, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x22,
> + },
> + { 0x01, 0x00, 0x01, 0x06, 0x10, 0x00, 0x00, 0xe8, 0x01, 0x05,
> + 0x17, 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30,
> + 0x30, 0x30, 0x31, 0xd0, 0x41, 0x41, 0x41, 0x41, 0x30, 0x41,
> + 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31,
> + 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74,
> + 0x61, 0xd4, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61, 0x20,
> + 0x48, 0x61, 0x72, 0x6d, 0x61, 0x20, 0x4d, 0x42, 0x20, 0x50,
> + 0x56, 0x54, 0xce, 0x41, 0x41, 0x41, 0x41, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x32, 0xcb, 0x30, 0x41,
> + 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30, 0x41, 0x35, 0xcc,
> + 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20, 0x30, 0x2e,
> + 0x31, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x08,
> + 0x19, 0xc6, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x61, 0xcb, 0x54,
> + 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65, 0x72, 0x76, 0x61,
> + 0xcb, 0x30, 0x30, 0x41, 0x30, 0x41, 0x41, 0x41, 0x30, 0x30,
> + 0x41, 0x32, 0xc3, 0x50, 0x56, 0x54, 0xd1, 0x41, 0x41, 0x41,
> + 0x41, 0x30, 0x41, 0x30, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x34, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55, 0x00,
> + 0xc1, 0x20,
> + },
> +};
> +
> +/* The four muxes take a quarter of the table each, by hand-written index. */
> +QEMU_BUILD_BUG_ON(ARRAY_SIZE(minerva_mb_eeprom) != 4 * MINERVA_MB_PER_MUX);
> +
> +/* Switch boards on the i2c12 mux */
> +static const uint8_t minerva_smb12_eeprom[][MINERVA_FRU_LEN] = {
> + { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
> + 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
> + 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
> + 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x34, 0x34, 0xce, 0x41, 0x30, 0x30,
> + 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
> + 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
> + 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xcc, 0x01, 0x09,
> + 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
> + 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
> + 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
> + 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
> + 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x34, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
> + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00,
> + },
> + { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
> + 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
> + 0x76, 0x61, 0x20, 0x4a, 0x61, 0x6e, 0x67, 0x61, 0x20, 0x53,
> + 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x34, 0x36, 0xce, 0x41, 0x30, 0x30,
> + 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
> + 0x36, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
> + 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xca, 0x01, 0x09,
> + 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
> + 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
> + 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
> + 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x33, 0xc2, 0x4d,
> + 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x34, 0x37, 0xc4, 0x51, 0x45, 0x4d, 0x55,
> + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00,
> + },
> + { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
> + 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
> + 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
> + 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x34, 0x38, 0xce, 0x41, 0x30, 0x30,
> + 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
> + 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
> + 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbc, 0x01, 0x09,
> + 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
> + 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
> + 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
> + 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
> + 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x34, 0x39, 0xc4, 0x51, 0x45, 0x4d, 0x55,
> + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00,
> + },
> +};
> +
> +/* Switch boards on the i2c13 mux */
> +static const uint8_t minerva_smb13_eeprom[][MINERVA_FRU_LEN] = {
> + { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
> + 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
> + 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
> + 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x35, 0x30, 0xce, 0x41, 0x30, 0x30,
> + 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
> + 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
> + 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc3, 0x01, 0x09,
> + 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
> + 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
> + 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
> + 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
> + 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x35, 0x31, 0xc4, 0x51, 0x45, 0x4d, 0x55,
> + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00,
> + },
> + { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
> + 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
> + 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
> + 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x35, 0x32, 0xce, 0x41, 0x30, 0x30,
> + 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
> + 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
> + 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xc1, 0x01, 0x09,
> + 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
> + 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
> + 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
> + 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
> + 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x35, 0x33, 0xc4, 0x51, 0x45, 0x4d, 0x55,
> + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00,
> + },
> + { 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a,
> + 0x19, 0xc0, 0xc6, 0xf0, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73,
> + 0x74, 0x69, 0x63, 0x61, 0xd1, 0x4d, 0x69, 0x6e, 0x65, 0x72,
> + 0x76, 0x61, 0x20, 0x54, 0x61, 0x68, 0x61, 0x6e, 0x20, 0x53,
> + 0x4d, 0x42, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x30, 0x35, 0x34, 0xce, 0x41, 0x30, 0x30,
> + 0x30, 0x30, 0x2d, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x30,
> + 0x37, 0xcc, 0x46, 0x52, 0x55, 0x20, 0x56, 0x65, 0x72, 0x20,
> + 0x30, 0x2e, 0x30, 0x32, 0xc1, 0x00, 0x00, 0xbf, 0x01, 0x09,
> + 0x19, 0xc9, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x63,
> + 0x61, 0xcb, 0x54, 0x32, 0x31, 0x20, 0x4d, 0x69, 0x6e, 0x65,
> + 0x72, 0x76, 0x61, 0xce, 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d,
> + 0x41, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x41, 0x34, 0xc2, 0x4d,
> + 0x50, 0xcd, 0x41, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
> + 0x30, 0x30, 0x30, 0x35, 0x35, 0xc4, 0x51, 0x45, 0x4d, 0x55,
> + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00,
> + },
> +};
> +
> /*
> * Fan Controller Board (FCB). The Minerva BMC device tree describes six
> * identical FCBs, each hanging off one channel of the PCA9548 on i2c2.
> */
> -static void minerva_fcb_i2c_init(I2CBus *bus)
> +static void minerva_fcb_i2c_init(I2CBus *bus, const uint8_t *fru,
> + uint32_t fru_size)
> {
> /* eeprom@50 - atmel,24c128 */
> - at24c_eeprom_init(bus, 0x50, 16 * KiB);
> + at24c_eeprom_init_rom(bus, 0x50, 16 * KiB, fru, fru_size);
> /* pwm@5e - maxim,max31790 (no model) */
> /* power-sensor@40/41/44/45 - ti,ina238 */
> i2c_slave_create_simple(bus, TYPE_INA238, 0x40);
> @@ -48,12 +701,14 @@ static void minerva_fcb_i2c_init(I2CBus *bus)
> }
>
> static void minerva_pca9545_eeprom_init(I2CBus *bus, uint8_t addr,
> + const uint8_t fru[][MINERVA_FRU_LEN],
> unsigned neeprom)
> {
> I2CSlave *i2c_mux = i2c_slave_create_simple(bus, TYPE_PCA9545, addr);
>
> for (unsigned idx = 0; idx < neeprom; idx++) {
> - at24c_eeprom_init(pca954x_i2c_get_bus(i2c_mux, idx), 0x50, 8 * KiB);
> + at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, idx), 0x50,
> + 8 * KiB, fru[idx], MINERVA_FRU_LEN);
> }
> }
>
> @@ -87,46 +742,61 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
> i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4b);
> i2c_slave_create_simple(i2c[1], TYPE_TMP75, 0x4f);
> /* eeprom@54 - atmel,24c128 */
> - at24c_eeprom_init(i2c[1], 0x54, 16 * KiB);
> + at24c_eeprom_init_rom(i2c[1], 0x54, 16 * KiB,
> + cmm_eeprom, sizeof(cmm_eeprom));
>
> /* &i2c2 */
> /* i2c-mux@77 (PCA9548) - six Fan Controller Boards */
> i2c_mux = i2c_slave_create_simple(i2c[2], TYPE_PCA9548, 0x77);
> /* FCB 1 - imux16 (channel 1) */
> - minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1));
> + minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 1),
> + fcb1_eeprom, sizeof(fcb1_eeprom));
> /* FCB 2 - imux17 (channel 0) */
> - minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0));
> + minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 0),
> + fcb2_eeprom, sizeof(fcb2_eeprom));
> /* FCB 3 - imux18 (channel 3) */
> - minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3));
> + minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 3),
> + fcb3_eeprom, sizeof(fcb3_eeprom));
> /* FCB 4 - imux19 (channel 2) */
> - minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2));
> + minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 2),
> + fcb4_eeprom, sizeof(fcb4_eeprom));
> /* FCB 5 - imux20 (channel 4) */
> - minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4));
> + minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 4),
> + fcb5_eeprom, sizeof(fcb5_eeprom));
> /* FCB 6 - imux21 (channel 5) */
> - minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5));
> + minerva_fcb_i2c_init(pca954x_i2c_get_bus(i2c_mux, 5),
> + fcb6_eeprom, sizeof(fcb6_eeprom));
> /* imux22/imux23 (channels 6/7) - empty */
>
> /* &i2c3 */
> /* i2c-mux@72 (PCA9545) - imux24-27, eeprom@50 on each channel */
> - minerva_pca9545_eeprom_init(i2c[3], 0x72, 4);
> + minerva_pca9545_eeprom_init(i2c[3], 0x72,
> + &minerva_mb_eeprom[0 * MINERVA_MB_PER_MUX],
> + MINERVA_MB_PER_MUX);
>
> /* &i2c4 */
> /* i2c-mux@72 (PCA9545) - imux28-31, eeprom@50 on each channel */
> - minerva_pca9545_eeprom_init(i2c[4], 0x72, 4);
> + minerva_pca9545_eeprom_init(i2c[4], 0x72,
> + &minerva_mb_eeprom[1 * MINERVA_MB_PER_MUX],
> + MINERVA_MB_PER_MUX);
>
> /* &i2c5 */
> /* i2c-mux@72 (PCA9545) - imux32-35, eeprom@50 on each channel */
> - minerva_pca9545_eeprom_init(i2c[5], 0x72, 4);
> + minerva_pca9545_eeprom_init(i2c[5], 0x72,
> + &minerva_mb_eeprom[2 * MINERVA_MB_PER_MUX],
> + MINERVA_MB_PER_MUX);
>
> /* &i2c6 */
> /* i2c-mux@72 (PCA9545) - imux36-39, eeprom@50 on each channel */
> - minerva_pca9545_eeprom_init(i2c[6], 0x72, 4);
> + minerva_pca9545_eeprom_init(i2c[6], 0x72,
> + &minerva_mb_eeprom[3 * MINERVA_MB_PER_MUX],
> + MINERVA_MB_PER_MUX);
>
> /* &i2c7 - empty */
> /* &i2c8 - empty */
>
> /* &i2c9 */
> - /* eeprom@50 - atmel,24c64 */
> + /* eeprom@50 - atmel,24c64 (no FRU data captured) */
> at24c_eeprom_init(i2c[9], 0x50, 8 * KiB);
> /* rtc@51 - nxp,pcf8563 (no model) */
> /* rtc@68 - dallas,ds1339 */
> @@ -134,20 +804,26 @@ static void minerva_bmc_i2c_init(AspeedMachineState *bmc)
>
> /* &i2c12 */
> /* i2c-mux@70 (PCA9545) - imux40-42 eeprom@50, imux43 empty */
> - minerva_pca9545_eeprom_init(i2c[12], 0x70, 3);
> + minerva_pca9545_eeprom_init(i2c[12], 0x70,
> + minerva_smb12_eeprom,
> + ARRAY_SIZE(minerva_smb12_eeprom));
>
> /* &i2c13 */
> /* i2c-mux@70 (PCA9545) - imux44-46 eeprom@50, imux47 empty */
> - minerva_pca9545_eeprom_init(i2c[13], 0x70, 3);
> + minerva_pca9545_eeprom_init(i2c[13], 0x70,
> + minerva_smb13_eeprom,
> + ARRAY_SIZE(minerva_smb13_eeprom));
>
> /* &i2c14 */
> /* ipmb@10 - ipmb-dev (no model) */
>
> /* &i2c15 */
> /* eeprom@50 - atmel,24c128 */
> - at24c_eeprom_init(i2c[15], 0x50, 16 * KiB);
> + at24c_eeprom_init_rom(i2c[15], 0x50, 16 * KiB,
> + scm_eeprom, sizeof(scm_eeprom));
> /* eeprom@56 - atmel,24c64 */
> - at24c_eeprom_init(i2c[15], 0x56, 8 * KiB);
> + at24c_eeprom_init_rom(i2c[15], 0x56, 8 * KiB,
> + bsm_eeprom, sizeof(bsm_eeprom));
> }
>
> static void aspeed_machine_minerva_class_init(ObjectClass *oc,
>
> --
> 2.50.1
>
>
Reviewed-by: Jamin Lin <jaminlin1207@gmail.com>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 04/50] hw/i2c: pca954x: drop unused header inclusions
2026-10-12 14:31 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-13 16:26 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:26 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:31, Emmanuel Blot wrote:
> The qapi/error.h, hw/core/sysbus.h and qemu/module.h headers are
> included but not used by i2c_mux_pca954x.c. Remove them.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/i2c/i2c_mux_pca954x.c | 3 ---
> 1 file changed, 3 deletions(-)
>
> diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
> index c603446028..c8e75da8b2 100644
> --- a/hw/i2c/i2c_mux_pca954x.c
> +++ b/hw/i2c/i2c_mux_pca954x.c
> @@ -15,15 +15,12 @@
> */
>
> #include "qemu/osdep.h"
> -#include "qapi/error.h"
> #include "hw/i2c/i2c.h"
> #include "hw/i2c/i2c_mux_pca954x.h"
> #include "hw/i2c/smbus_slave.h"
> #include "hw/core/qdev.h"
> #include "hw/core/qdev-properties.h"
> -#include "hw/core/sysbus.h"
> #include "qemu/log.h"
> -#include "qemu/module.h"
> #include "qemu/queue.h"
> #include "qom/object.h"
> #include "trace.h"
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 05/50] hw/i2c: pca954x: add PCA9545 4-channel mux model
2026-10-12 14:31 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-13 16:26 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:26 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:31, Emmanuel Blot wrote:
> The PCA9545 is a 4-channel I2C mux/switch. What sets it apart from the
> plain PCA9546 is its interrupt logic: one INTn input per channel and a
> shared INT output, both active low, asserted whenever any channel
> interrupt is active.
>
> Model this behind a new has_irq class flag. The PCA9545 gains four named
> "interrupt" GPIO inputs and an "interrupt-out" output, and its control
> read-back merges the latched status nibble; the plain PCA9546/PCA9548
> switches keep their previous behaviour.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/i2c/i2c_mux_pca954x.c | 71 ++++++++++++++++++++++++++++++++++++++--
> include/hw/i2c/i2c_mux_pca954x.h | 1 +
> 2 files changed, 70 insertions(+), 2 deletions(-)
>
> diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
> index c8e75da8b2..4bb078a03c 100644
> --- a/hw/i2c/i2c_mux_pca954x.c
> +++ b/hw/i2c/i2c_mux_pca954x.c
> @@ -12,12 +12,20 @@
> * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> * for more details.
> + *
> + * Limitations:
> + * - A control write longer than one byte is rejected rather than retaining the
> + * last received byte as the datasheet specifies.
> + * - On reset the cached per-channel interrupt state is cleared; a downstream
> + * INT input that stays asserted across the reset is not re-sampled until it
> + * next toggles.
> */
>
> #include "qemu/osdep.h"
> #include "hw/i2c/i2c.h"
> #include "hw/i2c/i2c_mux_pca954x.h"
> #include "hw/i2c/smbus_slave.h"
> +#include "hw/core/irq.h"
> #include "hw/core/qdev.h"
> #include "hw/core/qdev-properties.h"
> #include "qemu/log.h"
> @@ -26,11 +34,15 @@
> #include "trace.h"
>
> #define PCA9548_CHANNEL_COUNT 8
> +#define PCA9545_CHANNEL_COUNT 4
> #define PCA9546_CHANNEL_COUNT 4
>
> /*
> * struct Pca954xState - The pca954x state object.
> * @control: The value written to the mux control.
> + * @int_status: Per-channel interrupt status, for parts with interrupt logic.
> + * @int_out: The mux INT output, driven low while any channel interrupt is
> + * active.
> * @channel: The set of i2c channel buses that act as channels which own the
> * i2c children.
> */
> @@ -38,6 +50,8 @@ typedef struct Pca954xState {
> SMBusDevice parent;
>
> uint8_t control;
> + uint8_t int_status;
> + qemu_irq int_out;
>
> bool enabled[PCA9548_CHANNEL_COUNT];
> I2CBus *bus[PCA9548_CHANNEL_COUNT];
> @@ -48,11 +62,13 @@ typedef struct Pca954xState {
> /*
> * struct Pca954xClass - The pca954x class object.
> * @nchans: The number of i2c channels this device has.
> + * @has_irq: Whether the part implements interrupt logic.
> */
> typedef struct Pca954xClass {
> SMBusDeviceClass parent;
>
> uint8_t nchans;
> + bool has_irq;
> } Pca954xClass;
>
> #define TYPE_PCA954X "pca954x"
> @@ -116,8 +132,10 @@ static void pca954x_enable_channel(Pca954xState *s, uint8_t enable_mask)
>
> static void pca954x_write(Pca954xState *s, uint8_t data)
> {
> - s->control = data;
> - pca954x_enable_channel(s, data);
> + Pca954xClass *c = PCA954X_GET_CLASS(s);
> +
> + s->control = c->has_irq ? data & ((1 << c->nchans) - 1) : data;
> + pca954x_enable_channel(s, s->control);
>
> trace_pca954x_write_bytes(data);
> }
> @@ -148,16 +166,45 @@ static int pca954x_write_data(SMBusDevice *d, uint8_t *buf, uint8_t len)
> static uint8_t pca954x_read_byte(SMBusDevice *d)
> {
> Pca954xState *s = PCA954X(d);
> + Pca954xClass *c = PCA954X_GET_CLASS(s);
> uint8_t data = s->control;
> +
> + /*
> + * On parts with interrupt logic the read-back byte carries the per-channel
> + * interrupt status in the upper nibble.
> + */
> + if (c->has_irq) {
> + data = (s->int_status << 4) | (s->control & 0x0f);
> + }
> +
> trace_pca954x_read_data(data);
> return data;
> }
>
> +/*
> + * A downstream device drives one of the INTn inputs. INTn and INT are active
> + * low, and both carry the physical level: 0 is asserted, 1 is idle.
> + */
> +static void pca954x_irq_handler(void *opaque, int n, int level)
> +{
> + Pca954xState *s = PCA954X(opaque);
> +
> + if (level) {
> + s->int_status &= ~(1 << n);
> + } else {
> + s->int_status |= (1 << n);
> + }
> +
> + qemu_set_irq(s->int_out, s->int_status == 0);
> +}
> +
> static void pca954x_enter_reset(Object *obj, ResetType type)
> {
> Pca954xState *s = PCA954X(obj);
> /* Reset will disable all channels. */
> pca954x_write(s, 0);
> + s->int_status = 0;
> + qemu_set_irq(s->int_out, 1); /* INT released */
> }
>
> I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
> @@ -169,6 +216,13 @@ I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
> return pca954x->bus[channel];
> }
>
> +static void pca9545_class_init(ObjectClass *klass, const void *data)
> +{
> + Pca954xClass *s = PCA954X_CLASS(klass);
> + s->nchans = PCA9545_CHANNEL_COUNT;
> + s->has_irq = true;
> +}
> +
> static void pca9546_class_init(ObjectClass *klass, const void *data)
> {
> Pca954xClass *s = PCA954X_CLASS(klass);
> @@ -184,12 +238,20 @@ static void pca9548_class_init(ObjectClass *klass, const void *data)
> static void pca954x_realize(DeviceState *dev, Error **errp)
> {
> Pca954xState *s = PCA954X(dev);
> + Pca954xClass *c = PCA954X_GET_CLASS(dev);
> DeviceState *d = DEVICE(s);
> if (s->name) {
> d->id = g_strdup(s->name);
> } else {
> d->id = g_strdup_printf("pca954x[%x]", s->parent.i2c.address);
> }
> +
> + if (c->has_irq) {
> + /* One INTn input per channel, plus the shared INT output. */
> + qdev_init_gpio_in_named(dev, pca954x_irq_handler, "interrupt",
> + c->nchans);
> + qdev_init_gpio_out_named(dev, &s->int_out, "interrupt-out", 1);
> + }
> }
>
> static void pca954x_init(Object *obj)
> @@ -242,6 +304,11 @@ static const TypeInfo pca954x_info[] = {
> .class_init = pca954x_class_init,
> .abstract = true,
> },
> + {
> + .name = TYPE_PCA9545,
> + .parent = TYPE_PCA954X,
> + .class_init = pca9545_class_init,
> + },
> {
> .name = TYPE_PCA9546,
> .parent = TYPE_PCA954X,
> diff --git a/include/hw/i2c/i2c_mux_pca954x.h b/include/hw/i2c/i2c_mux_pca954x.h
> index 3dd25ec983..19a1050505 100644
> --- a/include/hw/i2c/i2c_mux_pca954x.h
> +++ b/include/hw/i2c/i2c_mux_pca954x.h
> @@ -3,6 +3,7 @@
>
> #include "hw/i2c/i2c.h"
>
> +#define TYPE_PCA9545 "pca9545"
> #define TYPE_PCA9546 "pca9546"
> #define TYPE_PCA9548 "pca9548"
>
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 06/50] hw/i2c: pca954x: migrate mux state
2026-10-12 14:31 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-13 16:26 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:26 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:31, Emmanuel Blot wrote:
> The pca954x model had no VMStateDescription, so neither the channel
> selection nor the PCA9545 interrupt status survived a migration.
>
> This changes the migration stream of the pre-existing PCA9546 and
> PCA9548, which an older QEMU rejects.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/i2c/i2c_mux_pca954x.c | 24 ++++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
>
> diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
> index 4bb078a03c..597e57d99a 100644
> --- a/hw/i2c/i2c_mux_pca954x.c
> +++ b/hw/i2c/i2c_mux_pca954x.c
> @@ -28,6 +28,7 @@
> #include "hw/core/irq.h"
> #include "hw/core/qdev.h"
> #include "hw/core/qdev-properties.h"
> +#include "migration/vmstate.h"
> #include "qemu/log.h"
> #include "qemu/queue.h"
> #include "qom/object.h"
> @@ -270,6 +271,28 @@ static void pca954x_init(Object *obj)
> }
> }
>
> +static int pca954x_post_load(void *opaque, int version_id)
> +{
> + Pca954xState *s = PCA954X(opaque);
> +
> + pca954x_enable_channel(s, s->control);
> + qemu_set_irq(s->int_out, s->int_status == 0);
> + return 0;
> +}
> +
> +static const VMStateDescription vmstate_pca954x = {
> + .name = "pca954x",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .post_load = pca954x_post_load,
> + .fields = (const VMStateField[]) {
> + VMSTATE_SMBUS_DEVICE(parent, Pca954xState),
> + VMSTATE_UINT8(control, Pca954xState),
> + VMSTATE_UINT8(int_status, Pca954xState),
> + VMSTATE_END_OF_LIST()
> + },
> +};
> +
> static const Property pca954x_props[] = {
> DEFINE_PROP_STRING("name", Pca954xState, name),
> };
> @@ -287,6 +310,7 @@ static void pca954x_class_init(ObjectClass *klass, const void *data)
>
> dc->desc = "Pca954x i2c-mux";
> dc->realize = pca954x_realize;
> + dc->vmsd = &vmstate_pca954x;
>
> k->write_data = pca954x_write_data;
> k->receive_byte = pca954x_read_byte;
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 07/50] tests/qtest: pca9545: add mux test
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-13 16:26 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:26 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:32, Emmanuel Blot wrote:
> Exercise the PCA9545 4-channel I2C mux: channel selection through the
> control byte, upper-nibble read-back, and interrupt logic.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/meson.build | 1 +
> tests/qtest/pca9545-test.c | 116 +++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 117 insertions(+)
>
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 200abf6f36..e05d8a07b9 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -336,6 +336,7 @@ qos_test_ss.add(
> 'ne2000-test.c',
> 'tulip-test.c',
> 'nvme-test.c',
> + 'pca9545-test.c',
> 'pca9552-test.c',
> 'pca9554-test.c',
> 'pca9555-test.c',
> diff --git a/tests/qtest/pca9545-test.c b/tests/qtest/pca9545-test.c
> new file mode 100644
> index 0000000000..91f56db92f
> --- /dev/null
> +++ b/tests/qtest/pca9545-test.c
> @@ -0,0 +1,116 @@
> +/*
> + * QTest testcase for the PCA9545 4-channel I2C switch with interrupt logic
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest-single.h"
> +#include "libqos/qgraph.h"
> +#include "libqos/i2c.h"
> +
> +#define PCA9545_TEST_ID "pca9545-test"
> +#define PCA9545_TEST_ADDR 0x70
> +#define PCA9545_TEST_PATH "/machine/peripheral/" PCA9545_TEST_ID
> +
> +/* INTn and INT are active low: level 0 is asserted. */
> +#define INT_ASSERTED 0
> +#define INT_RELEASED 1
> +
> +/* The mux has no register pointer: a read/write moves a single control byte. */
> +static uint8_t pca9545_get(QI2CDevice *dev)
> +{
> + uint8_t val;
> +
> + qi2c_recv(dev, &val, 1);
> + return val;
> +}
> +
> +static void pca9545_set(QI2CDevice *dev, uint8_t val)
> +{
> + qi2c_send(dev, &val, 1);
> +}
> +
> +static void pca9545_set_int(unsigned chan, int level)
> +{
> + qtest_set_irq_in(global_qtest, PCA9545_TEST_PATH, "interrupt", chan, level);
> +}
> +
> +/* Channel selection is the lower nibble; the upper nibble reads back 0. */
> +static void test_channel_select(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + g_assert_cmphex(pca9545_get(dev), ==, 0x00);
> +
> + pca9545_set(dev, 0x05); /* channels 0 and 2 */
> + g_assert_cmphex(pca9545_get(dev), ==, 0x05);
> +
> + pca9545_set(dev, 0x0f); /* all channels */
> + g_assert_cmphex(pca9545_get(dev), ==, 0x0f);
> +
> + pca9545_set(dev, 0x00);
> + g_assert_cmphex(pca9545_get(dev), ==, 0x00);
> +}
> +
> +/*
> + * The interrupt status is reported in the upper nibble (INT3..INT0) and is
> + * latched regardless of which channels are selected (PCA9545A Table 5).
> + */
> +static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + pca9545_set_int(1, INT_ASSERTED);
> + g_assert_cmphex(pca9545_get(dev), ==, 0x20);
> +
> + pca9545_set(dev, 0x01);
> + pca9545_set_int(3, INT_ASSERTED);
> + g_assert_cmphex(pca9545_get(dev), ==, 0xa1); /* INT3 | INT1 | channel 0 */
> +
> + pca9545_set_int(1, INT_RELEASED);
> + g_assert_cmphex(pca9545_get(dev), ==, 0x81); /* INT3 | channel 0 */
> +}
> +
> +/* The INT output is pulled low while any channel interrupt is active. */
> +static void test_interrupt_output(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + qtest_irq_intercept_out_named(global_qtest, PCA9545_TEST_PATH,
> + "interrupt-out");
> +
> + /*
> + * Interception starts with no recorded level, so drive an idle input
> + * first to make the mux publish the released state of its output.
> + */
> + pca9545_set_int(0, INT_RELEASED);
> + g_assert_true(get_irq(0));
> +
> + pca9545_set_int(0, INT_ASSERTED);
> + g_assert_false(get_irq(0));
> +
> + pca9545_set_int(2, INT_ASSERTED);
> + g_assert_false(get_irq(0));
> +
> + pca9545_set_int(0, INT_RELEASED);
> + g_assert_false(get_irq(0));
> + pca9545_set_int(2, INT_RELEASED);
> + g_assert_true(get_irq(0));
> +}
> +
> +static void pca9545_register_nodes(void)
> +{
> + QOSGraphEdgeOptions opts = {
> + .extra_device_opts = "id=" PCA9545_TEST_ID ",address=0x70"
> + };
> + add_qi2c_address(&opts, &(QI2CAddress) { PCA9545_TEST_ADDR });
> +
> + qos_node_create_driver("pca9545", i2c_device_create);
> + qos_node_consumes("pca9545", "i2c-bus", &opts);
> +
> + qos_add_test("channel-select", "pca9545", test_channel_select, NULL);
> + qos_add_test("interrupt-status", "pca9545", test_interrupt_status, NULL);
> + qos_add_test("interrupt-output", "pca9545", test_interrupt_output, NULL);
> +}
> +libqos_init(pca9545_register_nodes);
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 08/50] hw/rtc: ds1338: move TYPE_DS1338 to a public header
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-13 16:26 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:26 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:32, Emmanuel Blot wrote:
> Move the TYPE_DS1338 type name definition from ds1338.c into a new
> include/hw/rtc/ds1338.h header so that boards instantiating the device
> no longer need to redefine the type name string locally.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/rtc/ds1338.c | 2 +-
> include/hw/rtc/ds1338.h | 20 ++++++++++++++++++++
> 2 files changed, 21 insertions(+), 1 deletion(-)
>
> diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
> index 5f1ee2e410..f8b3340bf0 100644
> --- a/hw/rtc/ds1338.c
> +++ b/hw/rtc/ds1338.c
> @@ -12,6 +12,7 @@
>
> #include "qemu/osdep.h"
> #include "hw/i2c/i2c.h"
> +#include "hw/rtc/ds1338.h"
> #include "migration/vmstate.h"
> #include "qemu/bcd.h"
> #include "qom/object.h"
> @@ -29,7 +30,6 @@
> #define HOURS_PM 0x20
> #define CTRL_OSF 0x20
>
> -#define TYPE_DS1338 "ds1338"
> OBJECT_DECLARE_SIMPLE_TYPE(DS1338State, DS1338)
>
> struct DS1338State {
> diff --git a/include/hw/rtc/ds1338.h b/include/hw/rtc/ds1338.h
> new file mode 100644
> index 0000000000..f4cfe6a8e7
> --- /dev/null
> +++ b/include/hw/rtc/ds1338.h
> @@ -0,0 +1,20 @@
> +/*
> + * MAXIM DS1338 I2C RTC+NVRAM
> + *
> + * Copyright (c) 2009 CodeSourcery.
> + * Written by Paul Brook
> + *
> + * This code is licensed under the GNU GPL v2.
> + *
> + * Contributions after 2012-01-13 are licensed under the terms of the
> + * GNU GPL, version 2 or (at your option) any later version.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_RTC_DS1338_H
> +#define HW_RTC_DS1338_H
> +
> +#define TYPE_DS1338 "ds1338"
> +
> +#endif
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 09/50] hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string
2026-10-12 14:32 ` Emmanuel Blot via
` (2 preceding siblings ...)
(?)
@ 2026-10-13 16:27 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:27 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:32, Emmanuel Blot wrote:
> Now that TYPE_DS1338 is exposed by include/hw/rtc/ds1338.h, use it in
> the machines that instantiate the RTC by its raw type name string.
>
> No functional change.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/arm/aspeed_ast2400_palmetto.c | 4 +++-
> hw/arm/aspeed_ast2400_supermicrox11.c | 4 +++-
> hw/arm/aspeed_ast2500_romulus.c | 4 +++-
> hw/arm/aspeed_ast2500_supermicro-x11spi.c | 4 +++-
> hw/arm/aspeed_ast2500_witherspoon.c | 4 +++-
> hw/arm/aspeed_ast2600_bletchley.c | 3 ++-
> hw/arm/aspeed_ast2600_catalina.c | 2 +-
> hw/arm/aspeed_ast2600_sanmiguel.c | 3 +--
> hw/arm/realview.c | 3 ++-
> hw/arm/versatilepb.c | 3 ++-
> hw/ppc/e500.c | 3 ++-
> hw/riscv/tt_atlantis.c | 3 ++-
> 12 files changed, 27 insertions(+), 13 deletions(-)
>
> diff --git a/hw/arm/aspeed_ast2400_palmetto.c b/hw/arm/aspeed_ast2400_palmetto.c
> index e2350e5b6c..185185f0bb 100644
> --- a/hw/arm/aspeed_ast2400_palmetto.c
> +++ b/hw/arm/aspeed_ast2400_palmetto.c
> @@ -11,6 +11,7 @@
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> #include "hw/i2c/smbus_eeprom.h"
> +#include "hw/rtc/ds1338.h"
>
> /* Palmetto hardware value: 0x120CE416 */
> #define PALMETTO_BMC_HW_STRAP1 ( \
> @@ -36,7 +37,8 @@ static void palmetto_bmc_i2c_init(AspeedMachineState *bmc)
> * The palmetto platform expects a ds3231 RTC but a ds1338 is
> * enough to provide basic RTC features. Alarms will be missing
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
> + 0x68);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2400_supermicrox11.c b/hw/arm/aspeed_ast2400_supermicrox11.c
> index c75898cc6d..051e072c5a 100644
> --- a/hw/arm/aspeed_ast2400_supermicrox11.c
> +++ b/hw/arm/aspeed_ast2400_supermicrox11.c
> @@ -11,6 +11,7 @@
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> #include "hw/i2c/smbus_eeprom.h"
> +#include "hw/rtc/ds1338.h"
>
> /* TODO: Find the actual hardware value */
> #define SUPERMICROX11_BMC_HW_STRAP1 ( \
> @@ -36,7 +37,8 @@ static void supermicrox11_bmc_i2c_init(AspeedMachineState *bmc)
> * The palmetto platform expects a ds3231 RTC but a ds1338 is
> * enough to provide basic RTC features. Alarms will be missing
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
> + 0x68);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2500_romulus.c b/hw/arm/aspeed_ast2500_romulus.c
> index 340a74408b..87f90ed361 100644
> --- a/hw/arm/aspeed_ast2500_romulus.c
> +++ b/hw/arm/aspeed_ast2500_romulus.c
> @@ -10,6 +10,7 @@
> #include "qapi/error.h"
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> +#include "hw/rtc/ds1338.h"
>
> /* Romulus hardware value: 0xF10AD206 */
> #define ROMULUS_BMC_HW_STRAP1 ( \
> @@ -29,7 +30,8 @@ static void romulus_bmc_i2c_init(AspeedMachineState *bmc)
> * The romulus board expects Epson RX8900 I2C RTC but a ds1338 is
> * good enough
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
> + 0x32);
> }
>
> static void aspeed_machine_romulus_class_init(ObjectClass *oc,
> diff --git a/hw/arm/aspeed_ast2500_supermicro-x11spi.c b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
> index 5a7e49d6cc..dc0248422d 100644
> --- a/hw/arm/aspeed_ast2500_supermicro-x11spi.c
> +++ b/hw/arm/aspeed_ast2500_supermicro-x11spi.c
> @@ -11,6 +11,7 @@
> #include "hw/arm/aspeed.h"
> #include "hw/arm/aspeed_soc.h"
> #include "hw/i2c/smbus_eeprom.h"
> +#include "hw/rtc/ds1338.h"
>
> /* TODO: Find the actual hardware value */
> #define SUPERMICRO_X11SPI_BMC_HW_STRAP1 ( \
> @@ -32,7 +33,8 @@ static void supermicro_x11spi_bmc_i2c_init(AspeedMachineState *bmc)
> * The palmetto platform expects a ds3231 RTC but a ds1338 is
> * enough to provide basic RTC features. Alarms will be missing
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), "ds1338", 0x68);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 0), TYPE_DS1338,
> + 0x68);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 0), 0x50,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2500_witherspoon.c b/hw/arm/aspeed_ast2500_witherspoon.c
> index 26ddb67347..c69065846c 100644
> --- a/hw/arm/aspeed_ast2500_witherspoon.c
> +++ b/hw/arm/aspeed_ast2500_witherspoon.c
> @@ -14,6 +14,7 @@
> #include "hw/sensor/tmp105.h"
> #include "hw/i2c/smbus_eeprom.h"
> #include "hw/gpio/pca9552.h"
> +#include "hw/rtc/ds1338.h"
>
> /* Witherspoon hardware value: 0xF10AD216 */
> #define WITHERSPOON_BMC_HW_STRAP1 ( \
> @@ -70,7 +71,8 @@ static void witherspoon_bmc_i2c_init(AspeedMachineState *bmc)
> * The witherspoon board expects Epson RX8900 I2C RTC but a ds1338 is
> * good enough
> */
> - i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), "ds1338", 0x32);
> + i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 11), TYPE_DS1338,
> + 0x32);
>
> smbus_eeprom_init_one(aspeed_i2c_get_bus(&soc->i2c, 11), 0x51,
> eeprom_buf);
> diff --git a/hw/arm/aspeed_ast2600_bletchley.c b/hw/arm/aspeed_ast2600_bletchley.c
> index 6fa606c047..0e746055a2 100644
> --- a/hw/arm/aspeed_ast2600_bletchley.c
> +++ b/hw/arm/aspeed_ast2600_bletchley.c
> @@ -12,6 +12,7 @@
> #include "hw/arm/aspeed_soc.h"
> #include "hw/gpio/pca9552.h"
> #include "hw/nvram/eeprom_at24c.h"
> +#include "hw/rtc/ds1338.h"
>
> #define TYPE_TMP421 "tmp421"
> /* Bletchley hardware value */
> @@ -44,7 +45,7 @@ static void bletchley_bmc_i2c_init(AspeedMachineState *bmc)
> /* Bus 6 */
> at24c_eeprom_init(i2c[6], 0x56, 65536);
> /* Missing model: nxp,pcf85263 @ 0x51 , but ds1338 works enough */
> - i2c_slave_create_simple(i2c[6], "ds1338", 0x51);
> + i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x51);
>
>
> /* Bus 7 */
> diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
> index 4f2b396f6f..b15d932086 100644
> --- a/hw/arm/aspeed_ast2600_catalina.c
> +++ b/hw/arm/aspeed_ast2600_catalina.c
> @@ -14,6 +14,7 @@
> #include "hw/gpio/pca9552.h"
> #include "hw/gpio/pca9554.h"
> #include "hw/nvram/eeprom_at24c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/sensor/tmp105.h"
>
> /* Catalina hardware value */
> @@ -22,7 +23,6 @@
> #define CATALINA_BMC_RAM_SIZE (2 * GiB)
>
> #define TYPE_TMP421 "tmp421"
> -#define TYPE_DS1338 "ds1338"
>
> /*
> * "BMC Storage Module" FRU data. Generated with frugen.
> diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
> index ac2aef740a..b0935683e2 100644
> --- a/hw/arm/aspeed_ast2600_sanmiguel.c
> +++ b/hw/arm/aspeed_ast2600_sanmiguel.c
> @@ -13,6 +13,7 @@
> #include "hw/gpio/pca9552.h"
> #include "hw/gpio/pca9554.h"
> #include "hw/nvram/eeprom_at24c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/sensor/tmp105.h"
>
> /* SanMiguel hardware values */
> @@ -20,8 +21,6 @@
> #define SANMIGUEL_BMC_HW_STRAP2 0x00000000
> #define SANMIGUEL_BMC_RAM_SIZE (2 * GiB)
>
> -#define TYPE_DS1338 "ds1338"
> -
> /*
> * "Front IO" FRU data. Generated with frugen.
> *
> diff --git a/hw/arm/realview.c b/hw/arm/realview.c
> index ceba0331b0..4cdf939511 100644
> --- a/hw/arm/realview.c
> +++ b/hw/arm/realview.c
> @@ -28,6 +28,7 @@
> #include "hw/intc/realview_gic.h"
> #include "hw/core/irq.h"
> #include "hw/i2c/arm_sbcon_i2c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/sd/sd.h"
> #include "target/arm/cpu-qom.h"
>
> @@ -323,7 +324,7 @@ static void realview_init(MachineState *machine,
>
> dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
> i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
> - i2c_slave_create_simple(i2c, "ds1338", 0x68);
> + i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
>
> /* Memory map for RealView Emulation Baseboard: */
> /* 0x10000000 System registers. */
> diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c
> index f94bf96599..737cd21c27 100644
> --- a/hw/arm/versatilepb.c
> +++ b/hw/arm/versatilepb.c
> @@ -18,6 +18,7 @@
> #include "hw/pci/pci.h"
> #include "hw/i2c/i2c.h"
> #include "hw/i2c/arm_sbcon_i2c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/core/irq.h"
> #include "hw/core/boards.h"
> #include "hw/block/flash.h"
> @@ -346,7 +347,7 @@ static void versatile_init(MachineState *machine, int board_id)
>
> dev = sysbus_create_simple(TYPE_ARM_SBCON_I2C, 0x10002000, NULL);
> i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c");
> - i2c_slave_create_simple(i2c, "ds1338", 0x68);
> + i2c_slave_create_simple(i2c, TYPE_DS1338, 0x68);
>
> /* Add PL041 AACI Interface to the LM4549 codec */
> pl041 = qdev_new("pl041");
> diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c
> index 1876882a04..469cef9d2f 100644
> --- a/hw/ppc/e500.c
> +++ b/hw/ppc/e500.c
> @@ -50,6 +50,7 @@
> #include "hw/core/platform-bus.h"
> #include "hw/net/fsl_etsec/etsec.h"
> #include "hw/i2c/i2c.h"
> +#include "hw/rtc/ds1338.h"
> #include "hw/core/irq.h"
> #include "hw/sd/sdhci.h"
> #include "hw/misc/unimp.h"
> @@ -1025,7 +1026,7 @@ void ppce500_init(MachineState *machine)
> memory_region_add_subregion(ccsr_addr_space, MPC8544_I2C_REGS_OFFSET,
> sysbus_mmio_get_region(s, 0));
> i2c = I2C_BUS(qdev_get_child_bus(dev, "i2c"));
> - i2c_slave_create_simple(i2c, "ds1338", RTC_REGS_OFFSET);
> + i2c_slave_create_simple(i2c, TYPE_DS1338, RTC_REGS_OFFSET);
>
> /* eSDHC */
> if (pmc->has_esdhc) {
> diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
> index 8a219afef7..910713c6bb 100644
> --- a/hw/riscv/tt_atlantis.c
> +++ b/hw/riscv/tt_atlantis.c
> @@ -27,6 +27,7 @@
> #include "hw/char/serial-mm.h"
> #include "hw/intc/riscv_aclint.h"
> #include "hw/misc/unimp.h"
> +#include "hw/rtc/ds1338.h"
>
> #include "system/system.h"
> #include "system/device_tree.h"
> @@ -657,7 +658,7 @@ static void tt_atlantis_machine_init(MachineState *machine)
> qdev_realize(DEVICE(&ams->soc), NULL, &error_fatal);
>
> /* I2C peripherals: qemu specific */
> - i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f);
> + i2c_slave_create_simple(i2c_get_bus(s, 0), TYPE_DS1338, 0x6f);
> i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48);
>
> /* Load or create device tree */
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 01/50] docs/system/arm/aspeed: add the catalina-bmc machine
2026-10-12 14:31 ` Emmanuel Blot via
` (2 preceding siblings ...)
(?)
@ 2026-10-13 16:27 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:27 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:31, Emmanuel Blot wrote:
> The Facebook Catalina BMC machine is supported by hw/arm but has never
> been listed in the Aspeed documentation. Add it to the AST2600 machine
> table.
>
> While at it, shorten the document title. Listing every machine name in
> the title duplicates the tables right below it, and made the title (and
> its underline) grow past 350 columns. The machine list stays available
> where it is readable, in the per-SoC tables.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> docs/system/arm/aspeed.rst | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
> index 2d51ceeb84..933df41f96 100644
> --- a/docs/system/arm/aspeed.rst
> +++ b/docs/system/arm/aspeed.rst
> @@ -1,5 +1,5 @@
> -Aspeed family boards (``anacapa-bmc``, ``ast2500-evb``, ``ast2600-evb``, ``bletchley-bmc``, ``fuji-bmc``, ``gb200nvl-bmc``, ``fby35-bmc``, ``g220a-bmc``, ``palmetto-bmc``, ``quanta-q71l-bmc``, ``rainier-bmc``, ``romulus-bmc``, ``supermicrox11-bmc``, ``supermicrox11spi-bmc``, ``tiogapass-bmc``, ``witherspoon-bmc``, ``yosemitev2-bmc``)
> -===============================================================================================================================================================================================================================================================================================================================================
> +Aspeed family boards
> +====================
>
> The QEMU Aspeed machines model BMCs of various OpenPOWER systems and
> Aspeed evaluation boards. They are based on different releases of the
> @@ -33,6 +33,7 @@ AST2600 SoC based machines :
> - ``fuji-bmc`` Facebook Fuji BMC
> - ``bletchley-bmc`` Facebook Bletchley BMC
> - ``fby35-bmc`` Facebook fby35 BMC
> +- ``catalina-bmc`` Facebook Catalina BMC
> - ``anacapa-bmc`` Facebook Anacapa BMC
> - ``gb200nvl-bmc`` Nvidia GB200nvl BMC
>
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 02/50] docs/system/arm/aspeed: add the sanmiguel-bmc machine
2026-10-12 14:31 ` Emmanuel Blot via
` (2 preceding siblings ...)
(?)
@ 2026-10-13 16:27 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:27 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:31, Emmanuel Blot wrote:
> The Facebook SanMiguel BMC machine is supported by hw/arm but has never
> been listed in the Aspeed documentation. Add it to the AST2600 machine
> table.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> docs/system/arm/aspeed.rst | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
> index 933df41f96..f38f33cbe1 100644
> --- a/docs/system/arm/aspeed.rst
> +++ b/docs/system/arm/aspeed.rst
> @@ -34,6 +34,7 @@ AST2600 SoC based machines :
> - ``bletchley-bmc`` Facebook Bletchley BMC
> - ``fby35-bmc`` Facebook fby35 BMC
> - ``catalina-bmc`` Facebook Catalina BMC
> +- ``sanmiguel-bmc`` Facebook SanMiguel BMC
> - ``anacapa-bmc`` Facebook Anacapa BMC
> - ``gb200nvl-bmc`` Nvidia GB200nvl BMC
>
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 03/50] hw/arm: sanmiguel: remove the second SPI flash controller
2026-10-12 14:31 ` Emmanuel Blot via
` (2 preceding siblings ...)
(?)
@ 2026-10-13 16:27 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:27 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:31, Emmanuel Blot wrote:
> The board only populates the flash device attached to the FMC
> controller; the SoC SPI controllers are left unused. Advertising a
> part there presents the guest with a flash device the hardware does
> not have.
>
> While here, add the missing year to the copyright line.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/arm/aspeed_ast2600_sanmiguel.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/hw/arm/aspeed_ast2600_sanmiguel.c b/hw/arm/aspeed_ast2600_sanmiguel.c
> index 345a9c9416..ac2aef740a 100644
> --- a/hw/arm/aspeed_ast2600_sanmiguel.c
> +++ b/hw/arm/aspeed_ast2600_sanmiguel.c
> @@ -1,7 +1,7 @@
> /*
> * Facebook SanMiguel BMC
> *
> - * Copyright (c) Meta Platforms, Inc. and affiliates.
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> *
> * SPDX-License-Identifier: GPL-2.0-or-later
> */
> @@ -384,7 +384,7 @@ static void aspeed_machine_sanmiguel_class_init(ObjectClass *oc,
> amc->hw_strap1 = SANMIGUEL_BMC_HW_STRAP1;
> amc->hw_strap2 = SANMIGUEL_BMC_HW_STRAP2;
> amc->fmc_model = "mx66l1g45g";
> - amc->spi_model = "mx66l1g45g";
> + amc->spi_model = NULL;
> amc->num_cs = 2;
> amc->macs_mask = ASPEED_MAC0_ON;
> amc->i2c_init = sanmiguel_bmc_i2c_init;
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 10/50] hw/rtc: ds1338: implement Resettable reset
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-13 16:27 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:27 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:32, Emmanuel Blot wrote:
> The device relied on device_class_set_legacy_reset(), a compatibility
> shim around the legacy DeviceState reset hook. Convert ds1338_reset()
> into a proper Resettable hold phase, dropping the legacy shim.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/rtc/ds1338.c | 7 ++++---
> 1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
> index f8b3340bf0..74aee6c746 100644
> --- a/hw/rtc/ds1338.c
> +++ b/hw/rtc/ds1338.c
> @@ -208,9 +208,9 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
> return 0;
> }
>
> -static void ds1338_reset(DeviceState *dev)
> +static void ds1338_reset_hold(Object *obj, ResetType type)
> {
> - DS1338State *s = DS1338(dev);
> + DS1338State *s = DS1338(obj);
>
> /* The clock is running and synchronized with the host */
> s->offset = 0;
> @@ -224,11 +224,12 @@ static void ds1338_class_init(ObjectClass *klass, const void *data)
> {
> DeviceClass *dc = DEVICE_CLASS(klass);
> I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
>
> k->event = ds1338_event;
> k->recv = ds1338_recv;
> k->send = ds1338_send;
> - device_class_set_legacy_reset(dc, ds1338_reset);
> + rc->phases.hold = ds1338_reset_hold;
> dc->vmsd = &vmstate_ds1338;
> }
>
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 11/50] hw/rtc: ds1338: prefix the file-local helpers with ds1338_
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-13 16:27 ` Cédric Le Goater
-1 siblings, 0 replies; 188+ messages in thread
From: Cédric Le Goater @ 2026-10-13 16:27 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Pierrick Bouvier, qemu-arm,
Patrick Leis, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
qemu-ppc, qemu-riscv, Philippe Mathieu-Daudé
On 10/12/26 16:32, Emmanuel Blot wrote:
> Two static helpers predate the rest of the file and carry no device
> prefix.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/rtc/ds1338.c | 12 ++++++------
> 1 file changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/hw/rtc/ds1338.c b/hw/rtc/ds1338.c
> index 74aee6c746..1755a3a37c 100644
> --- a/hw/rtc/ds1338.c
> +++ b/hw/rtc/ds1338.c
> @@ -57,7 +57,7 @@ static const VMStateDescription vmstate_ds1338 = {
> }
> };
>
> -static void capture_current_time(DS1338State *s)
> +static void ds1338_capture_current_time(DS1338State *s)
> {
> /* Capture the current time into the secondary registers
> * which will be actually read by the data transfer operation.
> @@ -85,7 +85,7 @@ static void capture_current_time(DS1338State *s)
> s->nvram[6] = to_bcd(now.tm_year - 100);
> }
>
> -static void inc_regptr(DS1338State *s)
> +static void ds1338_inc_regptr(DS1338State *s)
> {
> /* The register pointer wraps around after 0x3F; wraparound
> * causes the current time/date to be retransferred into
> @@ -93,7 +93,7 @@ static void inc_regptr(DS1338State *s)
> */
> s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
> if (!s->ptr) {
> - capture_current_time(s);
> + ds1338_capture_current_time(s);
> }
> }
>
> @@ -108,7 +108,7 @@ static int ds1338_event(I2CSlave *i2c, enum i2c_event event)
> * START_SEND, because the guest can't get at that data
> * without going through a START_RECV which would overwrite it.
> */
> - capture_current_time(s);
> + ds1338_capture_current_time(s);
> break;
> case I2C_START_SEND:
> s->addr_byte = true;
> @@ -129,7 +129,7 @@ static uint8_t ds1338_recv(I2CSlave *i2c)
>
> trace_ds1338_recv(s->ptr, res);
>
> - inc_regptr(s);
> + ds1338_inc_regptr(s);
> return res;
> }
>
> @@ -204,7 +204,7 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data)
> } else {
> s->nvram[s->ptr] = data;
> }
> - inc_regptr(s);
> + ds1338_inc_regptr(s);
> return 0;
> }
>
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 37/50] tests/qtest: ina230: cover operating modes and triggered conversions
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:01 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:01 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Add coverage for the MODE field: power-down retains the last results,
> the shunt/bus channel-enable bits gate which registers track new inputs,
> triggered modes convert once per Config write, and bus-only mode still
> derives power from the retained current.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina230-test.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 94 insertions(+), 1 deletion(-)
>
> diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
> index 36c899efee..f8b982686a 100644
> --- a/tests/qtest/ina230-test.c
> +++ b/tests/qtest/ina230-test.c
> @@ -142,7 +142,97 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
> }
>
> -/* Shunt voltage register, including two's-complement negatives */
> +/* Operating modes */
> +static void test_modes(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> + static const struct {
> + uint8_t mode;
> + uint16_t shunt, bus, current, power;
> + } cases[] = {
> + /* Power-down: no conversion, every register retains the baseline */
> + { 0x0, 0x1F40, 0x2570, 0x2710, 0x12B8 },
> + { 0x4, 0x1F40, 0x2570, 0x2710, 0x12B8 },
> + /* Shunt only (continuous): shunt and current track the probe */
> + { 0x5, 0x0FA0, 0x2570, 0x1388, 0x12B8 },
> + /* Shunt and bus (continuous): every register tracks the probe */
> + { 0x7, 0x0FA0, 0x12C0, 0x1388, 0x04B0 },
> + };
> + size_t idx;
> +
> + for (idx = 0; idx < ARRAY_SIZE(cases); idx++) {
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_CALIBRATION, 0x0A00);
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("bus-voltage", 11980000);
> +
> + i2c_set16(dev, REG_CONFIG, cases[idx].mode);
> + qmp_ina230_set("shunt-voltage", 10000000);
> + qmp_ina230_set("bus-voltage", 6000000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, cases[idx].shunt);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, cases[idx].bus);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, cases[idx].current);
> + g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, cases[idx].power);
> + }
> +}
> +
> +/*
> + * Triggered modes perform exactly one conversion per trigger, where the
> + * trigger is a Configuration-register write. Injected input changes
> + * between triggers are ignored until the next write.
> + */
> +static void test_triggered(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_CALIBRATION, 0x0A00);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("bus-voltage", 11980000);
> +
> + i2c_set16(dev, REG_CONFIG, 0x03);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
> +
> + qmp_ina230_set("shunt-voltage", 10000000);
> + qmp_ina230_set("bus-voltage", 6000000);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
> +
> + i2c_set16(dev, REG_CONFIG, 0x03);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0FA0);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
> +
> + i2c_set16(dev, REG_CONFIG, 0x07);
> + qmp_ina230_set("shunt-voltage", 20000000); /* A */
Is the /* A */ comment intentional?
Thanks,
Glenn
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
> +}
> +
> +/*
> + * In a bus-only mode the current register is retained, but the power register
> + * is still refreshed on each bus conversion from that retained current.
> + */
> +static void test_bus_only_power(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_CALIBRATION, 0x0A00);
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("bus-voltage", 11980000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
> +
> + i2c_set16(dev, REG_CONFIG, 0x06);
> + qmp_ina230_set("bus-voltage", 6000000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x12C0);
> + g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0960);
> +}
> +
> +/* Shunt voltage register */
> static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
> {
> QI2CDevice *dev = (QI2CDevice *)obj;
> @@ -377,6 +467,9 @@ static void ina230_register_nodes(void)
> qos_add_test("defaults", "ina230", test_defaults, NULL);
> qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
> qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
> + qos_add_test("modes", "ina230", test_modes, NULL);
> + qos_add_test("triggered", "ina230", test_triggered, NULL);
> + qos_add_test("bus-only-power", "ina230", test_bus_only_power, NULL);
> qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
> qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
> qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 32/50] hw/sensor: ina230: add current/voltage/power monitor
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:02 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:02 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Add a model for the Texas Instruments INA230, a 36V, 16-bit I2C
> current-shunt and power monitor.
>
> The device measures a differential shunt voltage and a bus voltage; the
> current and power registers are not measured directly but derived by the
> device from the shunt voltage and the guest-programmed Calibration
> register. The alert is modelled.
>
> Shunt and bus voltages can be injected through the "shunt-voltage" and
> "bus-voltage" QOM properties (in nV and uV respectively).
>
> Note: Conversion is instantaneous: the averaging and conversion-time
> fields are stored but have no timing effect.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/arm/Kconfig | 1 +
> hw/sensor/Kconfig | 4 +
> hw/sensor/ina230.c | 597 +++++++++++++++++++++++++++++++++++++++++++++
> hw/sensor/meson.build | 1 +
> hw/sensor/trace-events | 7 +
> include/hw/sensor/ina230.h | 14 ++
> 6 files changed, 624 insertions(+)
>
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 4445f16f45..116d1c64ac 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -539,6 +539,7 @@ config ASPEED_SOC
> select TMP105
> select TMP421
> select EMC141X
> + select INA230
> select OR_IRQ
> select UNIMP
> select LED
> diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
> index b459ac2240..da519159ac 100644
> --- a/hw/sensor/Kconfig
> +++ b/hw/sensor/Kconfig
> @@ -2,6 +2,10 @@ config ADC128D818
> bool
> depends on I2C
>
> +config INA230
> + bool
> + depends on I2C
> +
> config TMP105
> bool
> depends on I2C
> diff --git a/hw/sensor/ina230.c b/hw/sensor/ina230.c
> new file mode 100644
> index 0000000000..988d12a6c5
> --- /dev/null
> +++ b/hw/sensor/ina230.c
> @@ -0,0 +1,597 @@
> +/*
> + * Texas Instruments INA230 36V, 16-bit current/voltage/power monitor
> + * with an I2C interface.
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * The INA230 measures a differential shunt voltage and a bus voltage.
> + * Current and power are not measured directly: they are derived by the
> + * device from the measured shunt voltage.
> + *
> + * Limitations:
> + * - Conversion timing is instantaneous. A conversion runs synchronously when an
> + * input, the calibration or the configuration changes; the per-channel
> + * conversion times (VSH_CT/VBUS_CT), the averaging count (AVG) and continuous
> + * re-conversion on a timer are not modelled. A read always returns the value
> + * derived from the current inputs and CVRF is set once a conversion has run.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "qapi/visitor.h"
> +#include "qom/object.h"
> +#include "hw/sensor/ina230.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/registerfields.h"
> +#include "hw/i2c/i2c.h"
> +#include "migration/vmstate.h"
> +#include "trace.h"
> +
> +#define INA230_REG_CONFIG 0x00
> +#define INA230_REG_SHUNT 0x01
> +#define INA230_REG_BUS 0x02
> +#define INA230_REG_POWER 0x03
> +#define INA230_REG_CURRENT 0x04
> +#define INA230_REG_CALIBRATION 0x05
> +#define INA230_REG_MASK_ENABLE 0x06
> +#define INA230_REG_ALERT_LIMIT 0x07
> +#define INA230_REG_DIE_ID 0xFF
> +
> +FIELD(INA230_CONFIG, MODE, 0, 3)
> +FIELD(INA230_CONFIG, VSH_CT, 3, 3)
> +FIELD(INA230_CONFIG, VBUS_CT, 6, 3)
> +FIELD(INA230_CONFIG, AVG, 9, 3)
> +FIELD(INA230_CONFIG, RST, 15, 1)
> +
> +#define INA230_CONFIG_POR 0x4127
> +
> +FIELD(INA230_MASK_ENABLE, LEN, 0, 1) /* alert latch enable */
> +FIELD(INA230_MASK_ENABLE, APOL, 1, 1) /* alert polarity */
> +FIELD(INA230_MASK_ENABLE, OVF, 2, 1) /* math overflow flag (status) */
> +FIELD(INA230_MASK_ENABLE, CVRF, 3, 1) /* conversion ready flag (status) */
> +FIELD(INA230_MASK_ENABLE, AFF, 4, 1) /* alert function flag (status) */
> +FIELD(INA230_MASK_ENABLE, CNVR, 10, 1) /* conversion ready alert */
> +FIELD(INA230_MASK_ENABLE, POL, 11, 1) /* power over-limit */
> +FIELD(INA230_MASK_ENABLE, BUL, 12, 1) /* bus under-limit */
> +FIELD(INA230_MASK_ENABLE, BOL, 13, 1) /* bus over-limit */
> +FIELD(INA230_MASK_ENABLE, SUL, 14, 1) /* shunt under-limit */
> +FIELD(INA230_MASK_ENABLE, SOL, 15, 1) /* shunt over-limit */
> +
> +/* Writable Mask/Enable bits: alert-function selects + APOL + LEN */
> +#define INA230_ME_WMASK (R_INA230_MASK_ENABLE_SOL_MASK | \
> + R_INA230_MASK_ENABLE_SUL_MASK | \
> + R_INA230_MASK_ENABLE_BOL_MASK | \
> + R_INA230_MASK_ENABLE_BUL_MASK | \
> + R_INA230_MASK_ENABLE_POL_MASK | \
> + R_INA230_MASK_ENABLE_CNVR_MASK | \
> + R_INA230_MASK_ENABLE_APOL_MASK | \
> + R_INA230_MASK_ENABLE_LEN_MASK)
> +/* Read-only status bits: AFF, CVRF, OVF */
> +#define INA230_ME_STATUS_MASK (R_INA230_MASK_ENABLE_AFF_MASK | \
> + R_INA230_MASK_ENABLE_CVRF_MASK | \
> + R_INA230_MASK_ENABLE_OVF_MASK)
> +
> +FIELD(INA230_CALIBRATION, FS, 0, 15)
> +#define INA230_CAL_MASK R_INA230_CALIBRATION_FS_MASK
> +
> +#define INA230_SHUNT_LSB_NV 2500 /* 2.5 uV per LSB */
> +#define INA230_BUS_LSB_UV 1250 /* 1.25 mV per LSB */
> +#define INA230_SHUNT_MIN_NV (-81920000) /* -32768 * 2.5 uV */
> +#define INA230_SHUNT_MAX_NV 81917500 /* 32767 * 2.5 uV */
> +/*
> + * A real rail should not drive the pin past 36 V, so bus codes above 0x7080
> + * are intentionally unreachable in the model.
> + */
> +#define INA230_BUS_MAX_UV 36000000 /* bus pin rated 0..36 V */
> +
> +#define INA230_CURRENT_DIV 2048
> +#define INA230_POWER_DIV 20000
> +
> +#define INA230_DIE_ID_VAL 0x2310
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(INA230State, INA230)
> +
> +struct INA230State {
> + I2CSlave parent_obj;
> +
> + qemu_irq alert; /* ALERT open-drain output */
> +
> + /* I2C transfer state */
> + uint8_t ptr; /* register pointer */
> + uint8_t len; /* bytes received in current transfer */
> + uint8_t wr_hi; /* pending write MSB */
> + uint8_t rx_byte; /* 0: return MSB next, 1: return LSB next */
> + uint16_t rx_word; /* value latched at the start of a read */
> +
> + /* Register file */
> + uint16_t config;
> + uint16_t calibration;
> + uint16_t mask_enable;
> + uint16_t alert_limit;
> +
> + /* Derived measurement registers */
> + uint16_t shunt_reg;
> + uint16_t bus_reg;
> + uint16_t current_reg;
> + uint16_t power_reg;
> +
> + /* Injected physical inputs */
> + int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
> + uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
> +
> + /*
> + * In latch mode AFF and the ALERT pin stay asserted until the
> + * Configuration register is written or the Mask/Enable register is read
> + * (datasheet 7.3.3).
> + */
> + bool alert_latched;
> +
> + char *description;
> +};
> +
> +/*
> + * Evaluate the selected alert function. @shunt_fresh and @bus_fresh tell which
> + * of the measurement registers hold a fresh value; a function fed by a channel
> + * that did not convert cannot assert.
> + */
> +static bool ina230_limit_alert(const INA230State *s, bool shunt_fresh,
> + bool bus_fresh)
> +{
> + uint16_t me = s->mask_enable;
> + int16_t shunt = (int16_t)s->shunt_reg;
> + int16_t limit_s = (int16_t)s->alert_limit;
> + uint16_t limit_u = s->alert_limit;
> +
> + if (FIELD_EX16(me, INA230_MASK_ENABLE, SOL)) {
> + return shunt_fresh && shunt > limit_s;
> + }
> + if (FIELD_EX16(me, INA230_MASK_ENABLE, SUL)) {
> + return shunt_fresh && shunt < limit_s;
> + }
> + if (FIELD_EX16(me, INA230_MASK_ENABLE, BOL)) {
> + return bus_fresh && s->bus_reg > limit_u;
> + }
> + if (FIELD_EX16(me, INA230_MASK_ENABLE, BUL)) {
> + return bus_fresh && s->bus_reg < limit_u;
> + }
> + if (FIELD_EX16(me, INA230_MASK_ENABLE, POL)) {
> + return bus_fresh && s->power_reg > limit_u;
> + }
> + return false;
> +}
> +
> +static void ina230_refresh_alert(INA230State *s)
> +{
> + bool latch = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN);
> + bool aff = latch ? s->alert_latched : ina230_limit_alert(s, true, true);
> + bool cnvr;
> + bool active;
> + int level;
> +
> + s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, AFF, aff);
> +
> + cnvr = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CNVR) &&
> + FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CVRF);
> +
> + active = aff || cnvr;
> +
> + level = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, APOL) ?
> + active : !active;
> +
> + trace_ina230_alert(s->description, level);
> + qemu_set_irq(s->alert, level);
> +}
> +
> +static void ina230_sample_alert(INA230State *s, bool shunt_fresh,
> + bool bus_fresh)
> +{
> + if (FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN) &&
> + ina230_limit_alert(s, shunt_fresh, bus_fresh)) {
> + s->alert_latched = true;
> + }
> + ina230_refresh_alert(s);
> +}
> +
> +static void ina230_convert(INA230State *s)
> +{
> + uint8_t mode = FIELD_EX16(s->config, INA230_CONFIG, MODE);
> + bool shunt_en = mode & 0x1;
> + bool bus_en = mode & 0x2;
> + int64_t sr, br, cur, pwr;
> + bool ovf = false;
> +
> + if (!shunt_en && !bus_en) {
> + return;
> + }
> +
> + if (shunt_en) {
> + sr = s->shunt_nv / INA230_SHUNT_LSB_NV;
> + sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
> + s->shunt_reg = (uint16_t)(int16_t)sr;
> +
> + cur = (int64_t)(int16_t)s->shunt_reg * s->calibration /
> + INA230_CURRENT_DIV;
> + if (cur > INT16_MAX || cur < INT16_MIN) {
> + ovf = true;
> + cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
> + }
> + s->current_reg = (uint16_t)(int16_t)cur;
> + }
> +
> + if (bus_en) {
> + br = s->bus_uv / INA230_BUS_LSB_UV;
> + br = MAX(0, MIN(INT16_MAX, br));
> + s->bus_reg = (uint16_t)br;
> + }
> +
> + /*
> + * In bus-only mode the current register is retained from the last shunt
> + * conversion rather than measured anew, but the power register is still
> + * refreshed.
> + */
> + if (bus_en) {
> + pwr = (int64_t)(int16_t)s->current_reg * s->bus_reg / INA230_POWER_DIV;
> + /* POWER is an unsigned, positive-only register (datasheet). */
> + if (pwr < 0) {
> + pwr = 0;
> + } else if (pwr > UINT16_MAX) {
> + ovf = true;
> + pwr = UINT16_MAX;
> + }
> + s->power_reg = (uint16_t)pwr;
> + }
> +
> + /* Without a calibration the device reports neither current nor power. */
> + if (s->calibration == 0) {
> + s->current_reg = 0;
> + s->power_reg = 0;
> + }
> +
> + s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, OVF, ovf);
> + s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, CVRF, 1);
> +
> + trace_ina230_convert(s->description, s->shunt_reg, s->bus_reg,
> + s->current_reg, s->power_reg);
> +
> + ina230_sample_alert(s, shunt_en, bus_en);
> +}
> +
> +/*
> + * A physical input or the calibration changed.
> + * - in continuous mode the device keeps converting and immediately tracks
> + * the new value
> + * - in a triggered mode it performs exactly one conversion per trigger
> + */
> +static void ina230_input_update(INA230State *s)
> +{
> + if (FIELD_EX16(s->config, INA230_CONFIG, MODE) & 0x4) {
> + ina230_convert(s);
> + }
> +}
> +
> +static void ina230_reset_regs(INA230State *s)
> +{
> + s->config = INA230_CONFIG_POR;
> + s->calibration = 0;
> + s->mask_enable = 0;
> + s->alert_limit = 0;
> + s->alert_latched = false;
> +
> + s->ptr = 0;
> + s->len = 0;
> + s->wr_hi = 0;
> + s->rx_byte = 0;
> + s->rx_word = 0;
> +
> + s->shunt_reg = 0;
> + s->bus_reg = 0;
> + s->current_reg = 0;
> + s->power_reg = 0;
> +
> + ina230_refresh_alert(s);
> +}
> +
> +static uint16_t ina230_read_reg(INA230State *s, uint8_t reg)
> +{
> + uint16_t val;
> +
> + switch (reg) {
> + case INA230_REG_CONFIG:
> + val = s->config;
> + break;
> + case INA230_REG_SHUNT:
> + val = s->shunt_reg;
> + break;
> + case INA230_REG_BUS:
> + val = s->bus_reg;
> + break;
> + case INA230_REG_POWER:
> + val = s->power_reg;
> + break;
> + case INA230_REG_CURRENT:
> + val = s->current_reg;
> + break;
> + case INA230_REG_CALIBRATION:
> + val = s->calibration;
> + break;
> + case INA230_REG_MASK_ENABLE:
> + val = s->mask_enable;
> + s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE,
> + CVRF, 0);
> + s->alert_latched = false;
> + ina230_refresh_alert(s);
> + break;
> + case INA230_REG_ALERT_LIMIT:
> + val = s->alert_limit;
> + break;
> + case INA230_REG_DIE_ID:
> + val = INA230_DIE_ID_VAL;
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: %s: read from undefined register 0x%02x\n",
> + __func__, s->description, reg);
> + val = 0;
> + break;
> + }
> +
> + trace_ina230_read(s->description, reg, val);
> + return val;
> +}
> +
> +static void ina230_write_reg(INA230State *s, uint8_t reg, uint16_t val)
> +{
> + trace_ina230_write(s->description, reg, val);
> +
> + switch (reg) {
> + case INA230_REG_CONFIG:
> + if (val & R_INA230_CONFIG_RST_MASK) {
> + trace_ina230_reset(s->description, "reg");
> + ina230_reset_regs(s);
> + break;
> + }
> + s->config = val & ~R_INA230_CONFIG_RST_MASK;
> + /*
> + * The write itself releases a latched alert, so the release reaches
> + * the ALERT pin before the conversion it triggers can relatch it.
> + */
> + s->alert_latched = false;
> + ina230_refresh_alert(s);
> + ina230_convert(s);
> + break;
> + case INA230_REG_CALIBRATION:
> + s->calibration = val & INA230_CAL_MASK;
> + ina230_input_update(s);
> + break;
> + case INA230_REG_MASK_ENABLE:
> + s->mask_enable = (val & INA230_ME_WMASK) |
> + (s->mask_enable & INA230_ME_STATUS_MASK);
> + ina230_refresh_alert(s);
> + break;
> + case INA230_REG_ALERT_LIMIT:
> + s->alert_limit = val;
> + ina230_refresh_alert(s);
> + break;
> + case INA230_REG_SHUNT:
> + case INA230_REG_BUS:
> + case INA230_REG_POWER:
> + case INA230_REG_CURRENT:
> + case INA230_REG_DIE_ID:
> + 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 ina230_recv(I2CSlave *i2c)
> +{
> + INA230State *s = INA230(i2c);
> + uint8_t byte;
> +
> + if (s->rx_byte == 0) {
> + s->rx_word = ina230_read_reg(s, s->ptr);
> + byte = s->rx_word >> 8;
> + s->rx_byte = 1;
> + } else {
> + byte = s->rx_word & 0xff;
> + s->rx_byte = 0;
> + }
> +
> + return byte;
> +}
> +
> +static int ina230_send(I2CSlave *i2c, uint8_t data)
> +{
> + INA230State *s = INA230(i2c);
> +
> + if (s->len == 0) {
> + s->ptr = data;
> + } else if ((s->len & 1) == 1) {
> + s->wr_hi = data;
> + } else {
> + ina230_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
> + }
> + s->len++;
> +
> + return 0;
> +}
> +
> +static int ina230_event(I2CSlave *i2c, enum i2c_event event)
> +{
> + INA230State *s = INA230(i2c);
> +
> + s->len = 0;
> + s->rx_byte = 0;
> +
> + return 0;
> +}
> +
> +static void ina230_get_shunt(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA230State *s = INA230(obj);
> + int64_t value = s->shunt_nv;
> +
> + visit_type_int(v, name, &value, errp);
> +}
> +
> +static void ina230_set_shunt(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA230State *s = INA230(obj);
> + int64_t value;
> +
> + if (!visit_type_int(v, name, &value, errp)) {
> + return;
> + }
> +
> + if (value < INA230_SHUNT_MIN_NV || value > INA230_SHUNT_MAX_NV) {
> + error_setg(errp,
> + "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
> + s->description, value,
> + INA230_SHUNT_MIN_NV, INA230_SHUNT_MAX_NV);
> + return;
> + }
> +
> + s->shunt_nv = (int32_t)value;
> + ina230_input_update(s);
> +}
> +
> +static void ina230_get_bus(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA230State *s = INA230(obj);
> + int64_t value = s->bus_uv;
> +
> + visit_type_int(v, name, &value, errp);
> +}
> +
> +static void ina230_set_bus(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA230State *s = INA230(obj);
> + int64_t value;
> +
> + if (!visit_type_int(v, name, &value, errp)) {
> + return;
> + }
> +
> + if (value < 0 || value > INA230_BUS_MAX_UV) {
> + error_setg(errp,
> + "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
> + s->description, value, INA230_BUS_MAX_UV);
> + return;
> + }
> +
> + s->bus_uv = (uint32_t)value;
> + ina230_input_update(s);
> +}
> +
> +static void ina230_reset_hold(Object *obj, ResetType type)
> +{
> + INA230State *s = INA230(obj);
> +
> + trace_ina230_reset(s->description, "hw");
> + ina230_reset_regs(s);
> +}
> +
> +static int ina230_post_load(void *opaque, int version_id)
> +{
> + INA230State *s = opaque;
> +
> + ina230_refresh_alert(s);
> +
> + return 0;
> +}
> +
> +static const VMStateDescription ina230_vmstate = {
> + .name = "INA230",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .post_load = ina230_post_load,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT8(ptr, INA230State),
> + VMSTATE_UINT8(len, INA230State),
> + VMSTATE_UINT8(wr_hi, INA230State),
> + VMSTATE_UINT8(rx_byte, INA230State),
> + VMSTATE_UINT16(rx_word, INA230State),
> + VMSTATE_UINT16(config, INA230State),
> + VMSTATE_UINT16(calibration, INA230State),
> + VMSTATE_UINT16(mask_enable, INA230State),
> + VMSTATE_UINT16(alert_limit, INA230State),
> + VMSTATE_UINT16(shunt_reg, INA230State),
> + VMSTATE_UINT16(bus_reg, INA230State),
> + VMSTATE_UINT16(current_reg, INA230State),
> + VMSTATE_UINT16(power_reg, INA230State),
> + VMSTATE_INT32(shunt_nv, INA230State),
> + VMSTATE_UINT32(bus_uv, INA230State),
> + VMSTATE_BOOL(alert_latched, INA230State),
> + VMSTATE_I2C_SLAVE(parent_obj, INA230State),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void ina230_initfn(Object *obj)
> +{
> + object_property_add(obj, "shunt-voltage", "int", ina230_get_shunt,
> + ina230_set_shunt, NULL, NULL);
> + object_property_set_description(obj, "shunt-voltage",
> + "Injected shunt (differential) voltage, "
> + "in nanovolts");
> + object_property_add(obj, "bus-voltage", "int", ina230_get_bus,
> + ina230_set_bus, NULL, NULL);
> + object_property_set_description(obj, "bus-voltage",
> + "Injected bus voltage, in microvolts");
> +}
> +
> +static void ina230_realize(DeviceState *dev, Error **errp)
> +{
> + INA230State *s = INA230(dev);
> +
> + if (!s->description) {
> + s->description = g_strdup(object_get_typename(OBJECT(dev)));
> + }
> +
> + qdev_init_gpio_out(dev, &s->alert, 1);
> +}
> +
> +static const Property ina230_properties[] = {
> + DEFINE_PROP_STRING("description", INA230State, description),
> +};
> +
> +static void ina230_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 = ina230_event;
> + ic->recv = ina230_recv;
> + ic->send = ina230_send;
> + dc->realize = ina230_realize;
> + rc->phases.hold = ina230_reset_hold;
> + dc->vmsd = &ina230_vmstate;
> + device_class_set_props(dc, ina230_properties);
> +}
> +
> +static const TypeInfo ina230_types[] = {
> + {
> + .name = TYPE_INA230,
> + .parent = TYPE_I2C_SLAVE,
> + .instance_init = ina230_initfn,
> + .instance_size = sizeof(INA230State),
> + .class_init = ina230_class_init,
> + },
> +};
> +
> +DEFINE_TYPES(ina230_types)
> diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
> index fe36c9ef91..b81f001fdf 100644
> --- a/hw/sensor/meson.build
> +++ b/hw/sensor/meson.build
> @@ -1,4 +1,5 @@
> system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
> +system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.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 5a3630f7bb..f809b6b506 100644
> --- a/hw/sensor/trace-events
> +++ b/hw/sensor/trace-events
> @@ -8,6 +8,13 @@ adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u va
> adc128d818_irq(const char *id, bool level) "%s level %u"
> adc128d818_reset(const char *id, const char *source) "%s %s"
>
> +# ina230.c
> +ina230_read(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
> +ina230_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
> +ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, uint16_t power) "%s shunt 0x%04x bus 0x%04x current 0x%04x power 0x%04x"
> +ina230_alert(const char *id, bool level) "%s level %u"
> +ina230_reset(const char *id, const char *source) "%s %s"
> +
> # tmp105.c
> tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
> tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
> diff --git a/include/hw/sensor/ina230.h b/include/hw/sensor/ina230.h
> new file mode 100644
> index 0000000000..4394727d47
> --- /dev/null
> +++ b/include/hw/sensor/ina230.h
> @@ -0,0 +1,14 @@
> +/*
> + * Texas Instruments INA230 current/voltage/power monitor with I2C interface
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_SENSOR_INA230_H
> +#define HW_SENSOR_INA230_H
> +
> +#define TYPE_INA230 "ina230"
> +
> +#endif
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 33/50] tests/qtest: ina230: add current/voltage/power monitor test
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-14 20:04 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:04 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Introduce the INA230 qtest: the qgraph node and the register map. Cover
> the power-on-reset defaults and Die ID, the software reset, and the
> Configuration register write mask. The QMP voltage-injection helpers
> arrive with the following commit that exercises them.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina230-test.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++
> tests/qtest/meson.build | 1 +
> 2 files changed, 116 insertions(+)
>
> diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
> new file mode 100644
> index 0000000000..f7aefd2d97
> --- /dev/null
> +++ b/tests/qtest/ina230-test.c
> @@ -0,0 +1,115 @@
> +/*
> + * QTest testcase for the INA230 current/voltage/power monitor
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqos/i2c.h"
> +#include "libqos/qgraph.h"
> +#include "libqtest-single.h"
> +#include "qobject/qdict.h"
> +
> +#define INA230_TEST_ID "ina230-test"
> +#define INA230_TEST_ADDR 0x40
> +
> +/* Register pointer addresses */
> +#define REG_CONFIG 0x00
> +#define REG_SHUNT 0x01
> +#define REG_BUS 0x02
> +#define REG_POWER 0x03
> +#define REG_CURRENT 0x04
> +#define REG_CALIBRATION 0x05
> +#define REG_MASK_ENABLE 0x06
> +#define REG_ALERT_LIMIT 0x07
> +#define REG_DIE_ID 0xFF
> +
> +/* Configuration register bits */
> +#define CONFIG_POR 0x4127
> +#define CONFIG_RST 0x8000
> +
> +/* Mask/Enable register bits */
> +#define ME_LEN 0x0001
> +#define ME_APOL 0x0002
> +#define ME_OVF 0x0004
> +#define ME_CVRF 0x0008
> +#define ME_AFF 0x0010
> +#define ME_CNVR 0x0400
> +#define ME_POL 0x0800
> +#define ME_BUL 0x1000
> +#define ME_BOL 0x2000
> +#define ME_SUL 0x4000
> +#define ME_SOL 0x8000
> +
> +/* Injected-input scaling (datasheet fixed LSBs) */
> +#define SHUNT_LSB_NV 2500
> +#define BUS_LSB_UV 1250
> +
> +#define DIE_ID_VAL 0x2310
> +
> +/* Power-on-reset default values and the Die ID */
> +static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIE_ID), ==, DIE_ID_VAL);
> +}
> +
> +/* Software reset via the Configuration RST bit */
> +static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CALIBRATION, 0x1234);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x0500);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x1234);
> + g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0500);
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_CALIBRATION), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_ALERT_LIMIT), ==, 0x0000);
> +}
> +
> +/* Every bit except the self-clearing RST is writable and reads back */
> +static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + /* Reserved bits are not forced: the guest value is retained */
> + i2c_set16(dev, REG_CONFIG, 0x0007);
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x0007);
> +
> + i2c_set16(dev, REG_CONFIG, 0x7FFF);
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
> +}
> +
> +static void ina230_register_nodes(void)
> +{
> + QOSGraphEdgeOptions opts = {
> + .extra_device_opts = "id=" INA230_TEST_ID ",address=0x40"
> + };
> + add_qi2c_address(&opts, &(QI2CAddress) { INA230_TEST_ADDR });
> +
> + qos_node_create_driver("ina230", i2c_device_create);
> + qos_node_consumes("ina230", "i2c-bus", &opts);
> +
> + qos_add_test("defaults", "ina230", test_defaults, NULL);
> + qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
> + qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
> +}
> +libqos_init(ina230_register_nodes);
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index e23eb9bbca..e413a0cc75 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -330,6 +330,7 @@ qos_test_ss.add(
> 'e1000-test.c',
> 'eepro100-test.c',
> 'es1370-test.c',
> + 'ina230-test.c',
> 'lsm303dlhc-mag-test.c',
> 'isl_pmbus_vr-test.c',
> 'max34451-test.c',
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 34/50] tests/qtest: ina230: cover voltage injection and power derivation
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:04 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:04 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Cover the shunt- and bus-voltage injection properties and the current
> and power values the device derives from them through the Calibration
> register, including the zero-calibration case and the clamp/overflow
> edges.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina230-test.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 124 insertions(+)
>
> diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
> index f7aefd2d97..b01f9445d1 100644
> --- a/tests/qtest/ina230-test.c
> +++ b/tests/qtest/ina230-test.c
> @@ -49,6 +49,33 @@
>
> #define DIE_ID_VAL 0x2310
>
> +/* QMP helpers for injecting the physical inputs */
> +
> +static void qmp_ina230_set(const char *property, int value)
> +{
> + QDict *resp;
> +
> + resp = qmp("{ 'execute': 'qom-set', 'arguments':"
> + " { 'path': %s, 'property': %s, 'value': %d } }",
> + INA230_TEST_ID, property, value);
> + g_assert(qdict_haskey(resp, "return"));
> + qobject_unref(resp);
> +}
> +
> +static int qmp_ina230_get(const char *property)
> +{
> + QDict *resp;
> + int ret;
> +
> + resp = qmp("{ 'execute': 'qom-get', 'arguments':"
> + " { 'path': %s, 'property': %s } }",
> + INA230_TEST_ID, property);
> + g_assert(qdict_haskey(resp, "return"));
> + ret = qdict_get_int(resp, "return");
> + qobject_unref(resp);
> + return ret;
> +}
> +
> /* Power-on-reset default values and the Die ID */
> static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -98,6 +125,96 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, 0x7FFF);
> }
>
> +/* Shunt voltage register, including two's-complement negatives */
> +static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x1F40);
> +
> + qmp_ina230_set("shunt-voltage", -80000000);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8300);
> +
> + g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, -80000000);
> +}
> +
> +/* Bus voltage register */
> +static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + qmp_ina230_set("bus-voltage", 12000000);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
> +
> + qmp_ina230_set("bus-voltage", 11980000);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2570);
> +}
> +
> +/* Calibration-derived current, using the datasheet worked example */
> +static void test_calibration_current(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> +
> + i2c_set16(dev, REG_CALIBRATION, 0x0A00);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x2710);
> +}
> +
> +/* Calibration-derived power, using the datasheet worked example */
> +static void test_power(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("bus-voltage", 11980000);
> + i2c_set16(dev, REG_CALIBRATION, 0x0A00);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x12B8);
> +}
> +
> +/* With Calibration == 0 the Current and Power registers stay at zero */
> +static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("bus-voltage", 11980000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
> +}
> +
> +/* Clamping at full scale and the OVF (math overflow) flag */
> +static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina230_set("shunt-voltage", 81917500);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
> + qmp_ina230_set("shunt-voltage", -81920000);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
> +
> + qmp_ina230_set("bus-voltage", 36000000);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + i2c_set16(dev, REG_CALIBRATION, 0x7FFF);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
> +}
> +
> static void ina230_register_nodes(void)
> {
> QOSGraphEdgeOptions opts = {
> @@ -111,5 +228,12 @@ static void ina230_register_nodes(void)
> qos_add_test("defaults", "ina230", test_defaults, NULL);
> qos_add_test("soft-reset", "ina230", test_soft_reset, NULL);
> qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
> + qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
> + qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
> + qos_add_test("calibration-current", "ina230", test_calibration_current,
> + NULL);
> + qos_add_test("power", "ina230", test_power, NULL);
> + qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
> + qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
> }
> libqos_init(ina230_register_nodes);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 35/50] tests/qtest: ina230: cover the ALERT limit functions and CVRF
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:05 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:05 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Cover the shunt/bus/power alert-limit functions, the alert latch
> behaviour and the conversion-ready flag.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina230-test.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 97 insertions(+)
>
> diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
> index b01f9445d1..1c55028fcb 100644
> --- a/tests/qtest/ina230-test.c
> +++ b/tests/qtest/ina230-test.c
> @@ -215,6 +215,98 @@ static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_OVF, ==, ME_OVF);
> }
>
> +/* Shunt over-limit alert (transparent mode) sets the AFF flag */
> +static void test_alert_shunt_over(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_SOL);
> +
> + qmp_ina230_set("shunt-voltage", 2500000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
> +
> + qmp_ina230_set("shunt-voltage", 2500000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +}
> +
> +/* Latch mode holds the alert until the Mask/Enable register is read */
> +static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> + uint16_t me;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("shunt-voltage", 2500000);
> +
> + me = i2c_get16(dev, REG_MASK_ENABLE);
> + g_assert_cmphex(me & ME_AFF, ==, ME_AFF);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +}
> +
> +/* Bus over-limit alert */
> +static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_BOL);
> +
> + qmp_ina230_set("bus-voltage", 5000000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +
> + qmp_ina230_set("bus-voltage", 12000000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
> +}
> +
> +/* Power over-limit alert */
> +static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("bus-voltage", 11980000);
> + i2c_set16(dev, REG_CALIBRATION, 0x0A00);
> +
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_POL);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
> +
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +}
> +
> +/* Conversion Ready Flag: set on conversion, cleared by reading Mask/Enable */
> +static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + /* A conversion (triggered by injection) sets CVRF */
> + qmp_ina230_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, ME_CVRF);
> +
> + /* The previous read cleared CVRF; no conversion since, so it stays clear */
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
> +}
> +
> static void ina230_register_nodes(void)
> {
> QOSGraphEdgeOptions opts = {
> @@ -235,5 +327,10 @@ static void ina230_register_nodes(void)
> qos_add_test("power", "ina230", test_power, NULL);
> qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
> qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
> + qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
> + qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
> + qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
> + qos_add_test("alert-power", "ina230", test_alert_power, NULL);
> + qos_add_test("cvrf", "ina230", test_cvrf, NULL);
> }
> libqos_init(ina230_register_nodes);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 36/50] tests/qtest: ina230: cover the injection-property limits
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:06 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:06 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Verify that the extremes of the shunt- and bus-voltage injection ranges
> round-trip through the QOM property without truncation or wraparound,
> and that values just outside are rejected rather than silently wrapped.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina230-test.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 59 insertions(+)
>
> diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
> index 1c55028fcb..36c899efee 100644
> --- a/tests/qtest/ina230-test.c
> +++ b/tests/qtest/ina230-test.c
> @@ -47,6 +47,11 @@
> #define SHUNT_LSB_NV 2500
> #define BUS_LSB_UV 1250
>
> +/* Injection-property limits (see ina230.c) */
> +#define SHUNT_MIN_NV (-81920000)
> +#define SHUNT_MAX_NV 81917500
> +#define BUS_MAX_UV 36000000
> +
> #define DIE_ID_VAL 0x2310
>
> /* QMP helpers for injecting the physical inputs */
> @@ -76,6 +81,18 @@ static int qmp_ina230_get(const char *property)
> return ret;
> }
>
> +/* A qom-set expected to be rejected (value outside the supported range) */
> +static void qmp_ina230_set_fail(const char *property, int64_t value)
> +{
> + QDict *resp;
> +
> + resp = qmp("{ 'execute': 'qom-set', 'arguments':"
> + " { 'path': %s, 'property': %s, 'value': %lld } }",
> + INA230_TEST_ID, property, (long long)value);
> + g_assert(qdict_haskey(resp, "error"));
> + qobject_unref(resp);
> +}
> +
> /* Power-on-reset default values and the Die ID */
> static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -307,6 +324,46 @@ static void test_cvrf(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_CVRF, ==, 0);
> }
>
> +/* Shunt-voltage injection limits */
> +static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina230_set("shunt-voltage", SHUNT_MAX_NV);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x7FFF);
> + g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MAX_NV);
> +
> + qmp_ina230_set("shunt-voltage", SHUNT_MIN_NV);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
> + g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
> +
> + qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
> + qmp_ina230_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
> + g_assert_cmpint(qmp_ina230_get("shunt-voltage"), ==, SHUNT_MIN_NV);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT), ==, 0x8000);
> +}
> +
> +/* Bus-voltage injection limits. */
> +static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina230_set("bus-voltage", 0);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x0000);
> +
> + qmp_ina230_set("bus-voltage", BUS_MAX_UV);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x7080);
> + g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
> +
> + qmp_ina230_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
> + qmp_ina230_set_fail("bus-voltage", -1);
> + g_assert_cmpint(qmp_ina230_get("bus-voltage"), ==, BUS_MAX_UV);
> +}
> +
> static void ina230_register_nodes(void)
> {
> QOSGraphEdgeOptions opts = {
> @@ -322,6 +379,8 @@ static void ina230_register_nodes(void)
> qos_add_test("config-wmask", "ina230", test_config_wmask, NULL);
> qos_add_test("shunt-injection", "ina230", test_shunt_injection, NULL);
> qos_add_test("bus-injection", "ina230", test_bus_injection, NULL);
> + qos_add_test("shunt-limits", "ina230", test_shunt_limits, NULL);
> + qos_add_test("bus-limits", "ina230", test_bus_limits, NULL);
> qos_add_test("calibration-current", "ina230", test_calibration_current,
> NULL);
> qos_add_test("power", "ina230", test_power, NULL);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 38/50] tests/qtest: ina230: cover ALERT latch persistence
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-14 20:06 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:06 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Verify what releases a latched alert and what can bring it back: a
> Mask/Enable read and a Configuration write both clear it, and only a
> conversion that re-samples the fault on a channel it converted asserts
> it again.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina230-test.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 89 insertions(+)
>
> diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
> index f8b982686a..591927e551 100644
> --- a/tests/qtest/ina230-test.c
> +++ b/tests/qtest/ina230-test.c
> @@ -364,6 +364,88 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
> }
>
> /* Bus over-limit alert */
> +static void test_alert_latch_persist(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> +
> + /* First read shows the latched AFF and clears the latch */
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
> +
> + /* Fault still present but no new conversion: AFF stays cleared */
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
> +}
> +
> +/* A Configuration write releases the latch as a Mask/Enable read does. */
> +static void test_alert_latch_config(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
> +
> + /* Latch a fault, then bring the input back below the limit */
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("shunt-voltage", 2500000);
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +
> + /* With the fault present, the reconversion latches it again */
> + qmp_ina230_set("shunt-voltage", 20000000);
> + i2c_set16(dev, REG_CONFIG, CONFIG_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
> +}
> +
> +/* The releasing write need not reconvert: a power-down leaves it clear. */
> +static void test_alert_latch_shutdown(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x1000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_SOL | ME_LEN);
> +
> + qmp_ina230_set("shunt-voltage", 20000000);
> +
> + i2c_set16(dev, REG_CONFIG, 0x0000); /* power-down */
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +}
> +
> +/*
> + * A conversion only re-samples the alert of a channel it converted: the bus
> + * register a shunt-only conversion left stale must not re-latch.
> + */
> +static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_ALERT_LIMIT, 0x2000);
> + i2c_set16(dev, REG_MASK_ENABLE, ME_BOL | ME_LEN);
> +
> + qmp_ina230_set("bus-voltage", 12000000);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, ME_AFF);
> +
> + /* Shunt-only mode: the bus register keeps its over-limit value */
> + i2c_set16(dev, REG_CONFIG, 0x0005);
> + qmp_ina230_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_BUS), ==, 0x2580);
> + g_assert_cmphex(i2c_get16(dev, REG_MASK_ENABLE) & ME_AFF, ==, 0);
> +}
> +
> static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
> {
> QI2CDevice *dev = (QI2CDevice *)obj;
> @@ -481,6 +563,13 @@ static void ina230_register_nodes(void)
> qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
> qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
> qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
> + qos_add_test("alert-latch-persist", "ina230", test_alert_latch_persist,
> + NULL);
> + qos_add_test("alert-latch-config", "ina230", test_alert_latch_config,
> + NULL);
> + qos_add_test("alert-latch-shutdown", "ina230", test_alert_latch_shutdown,
> + NULL);
> + qos_add_test("alert-channel", "ina230", test_alert_channel, NULL);
> qos_add_test("alert-bus", "ina230", test_alert_bus, NULL);
> qos_add_test("alert-power", "ina230", test_alert_power, NULL);
> qos_add_test("cvrf", "ina230", test_cvrf, NULL);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 39/50] tests/qtest: ina230: cover a cleared calibration per channel
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:07 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:07 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Verify that without a calibration value the current and power
> registers read zero, whichever channels the mode converts.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina230-test.c | 25 +++++++++++++++++++++++++
> 1 file changed, 25 insertions(+)
>
> diff --git a/tests/qtest/ina230-test.c b/tests/qtest/ina230-test.c
> index 591927e551..5749b0bd46 100644
> --- a/tests/qtest/ina230-test.c
> +++ b/tests/qtest/ina230-test.c
> @@ -301,6 +301,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
> }
>
> +/* Clearing the Calibration zeroes both derived registers in any mode */
> +static void test_zero_cal_channels(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> + static const uint8_t modes[] = { 0x5, 0x6, 0x7 };
> + size_t idx;
> +
> + for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_CALIBRATION, 0x0A00);
> + qmp_ina230_set("shunt-voltage", 20000000);
> + qmp_ina230_set("bus-voltage", 11980000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_POWER), !=, 0x0000);
> +
> + i2c_set16(dev, REG_CONFIG, modes[idx]);
> + i2c_set16(dev, REG_CALIBRATION, 0x0000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_POWER), ==, 0x0000);
> + }
> +}
> +
> /* Clamping at full scale and the OVF (math overflow) flag */
> static void test_edges_overflow(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -560,6 +584,7 @@ static void ina230_register_nodes(void)
> NULL);
> qos_add_test("power", "ina230", test_power, NULL);
> qos_add_test("zero-cal", "ina230", test_zero_cal, NULL);
> + qos_add_test("zero-cal-channels", "ina230", test_zero_cal_channels, NULL);
> qos_add_test("edges-overflow", "ina230", test_edges_overflow, NULL);
> qos_add_test("alert-shunt-over", "ina230", test_alert_shunt_over, NULL);
> qos_add_test("alert-latch", "ina230", test_alert_latch, NULL);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 40/50] hw/sensor: ina238: add current/voltage/power monitor
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-14 20:08 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:08 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Add a model for the Texas Instruments INA238, an 85V, 16-bit I2C
> current-shunt, power and temperature monitor.
>
> The device measures a differential shunt voltage, a bus voltage and its
> own die temperature; the current and power registers are not measured
> directly but derived by the device from the shunt voltage and the
> guest-programmed Shunt Calibration register. The alert is modelled.
>
> Shunt and bus voltages and the die temperature can be injected through
> the "shunt-voltage", "bus-voltage" and "die-temperature" QOM properties.
>
> Note: Conversion is instantaneous: the averaging and conversion-time
> fields are stored but have no timing effect.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/arm/Kconfig | 1 +
> hw/sensor/Kconfig | 4 +
> hw/sensor/ina238.c | 802 +++++++++++++++++++++++++++++++++++++++++++++
> hw/sensor/meson.build | 1 +
> hw/sensor/trace-events | 7 +
> include/hw/sensor/ina238.h | 14 +
> 6 files changed, 829 insertions(+)
>
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 116d1c64ac..3abe249ed5 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -540,6 +540,7 @@ config ASPEED_SOC
> select TMP421
> select EMC141X
> select INA230
> + select INA238
> select OR_IRQ
> select UNIMP
> select LED
> diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
> index da519159ac..9b7c76fcf5 100644
> --- a/hw/sensor/Kconfig
> +++ b/hw/sensor/Kconfig
> @@ -6,6 +6,10 @@ config INA230
> bool
> depends on I2C
>
> +config INA238
> + bool
> + depends on I2C
> +
> config TMP105
> bool
> depends on I2C
> diff --git a/hw/sensor/ina238.c b/hw/sensor/ina238.c
> new file mode 100644
> index 0000000000..e4033771b0
> --- /dev/null
> +++ b/hw/sensor/ina238.c
> @@ -0,0 +1,802 @@
> +/*
> + * Texas Instruments INA238 85V, 16-bit current/voltage/power monitor
> + * with an I2C interface.
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * The INA238 measures a differential shunt voltage, a bus voltage and its own
> + * die temperature. Current and power are not measured directly: they are
> + * derived by the device from the measured shunt voltage and the programmable
> + * Shunt Calibration register. The INA238 exposes six independent limit
> + * registers, all monitored simultaneously (multi-alert), a die-temperature
> + * sensor, a 24-bit power register and an ADCRANGE-dependent shunt LSB.
> + *
> + * Limitations (not modelled):
> + * - Conversion timing is instantaneous. Conversions run synchronously when an
> + * input, the calibration, the ADC range or the ADC configuration changes; the
> + * ADC conversion times (VBUSCT/VSHCT/VTCT), the averaging count (AVG) and the
> + * CONVDLY start delay are not modelled, and continuous mode does not
> + * re-convert on a timer. A read always returns the value derived from the
> + * current inputs and CNVRF is set once a conversion has run.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "qapi/visitor.h"
> +#include "qom/object.h"
> +#include "hw/sensor/ina238.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/registerfields.h"
> +#include "hw/i2c/i2c.h"
> +#include "migration/vmstate.h"
> +#include "trace.h"
> +
> +#define INA238_REG_CONFIG 0x00
> +#define INA238_REG_ADC_CONFIG 0x01
> +#define INA238_REG_SHUNT_CAL 0x02
> +#define INA238_REG_VSHUNT 0x04
> +#define INA238_REG_VBUS 0x05
> +#define INA238_REG_DIETEMP 0x06
> +#define INA238_REG_CURRENT 0x07
> +#define INA238_REG_POWER 0x08
> +#define INA238_REG_DIAG_ALRT 0x0B
> +#define INA238_REG_SOVL 0x0C
> +#define INA238_REG_SUVL 0x0D
> +#define INA238_REG_BOVL 0x0E
> +#define INA238_REG_BUVL 0x0F
> +#define INA238_REG_TEMP_LIMIT 0x10
> +#define INA238_REG_PWR_LIMIT 0x11
> +#define INA238_REG_MANUFACTURER_ID 0x3E
> +#define INA238_REG_DEVICE_ID 0x3F
> +
> +FIELD(INA238_CONFIG, ADCRANGE, 4, 1)
> +FIELD(INA238_CONFIG, CONVDLY, 6, 8)
> +FIELD(INA238_CONFIG, RST, 15, 1)
> +
> +#define INA238_CONFIG_POR 0x0000
> +#define INA238_CONFIG_WMASK (R_INA238_CONFIG_ADCRANGE_MASK | \
> + R_INA238_CONFIG_CONVDLY_MASK)
> +
> +FIELD(INA238_ADC_CONFIG, AVG, 0, 3)
> +FIELD(INA238_ADC_CONFIG, VTCT, 3, 3)
> +FIELD(INA238_ADC_CONFIG, VSHCT, 6, 3)
> +FIELD(INA238_ADC_CONFIG, VBUSCT, 9, 3)
> +FIELD(INA238_ADC_CONFIG, MODE, 12, 4)
> +
> +#define INA238_ADC_CONFIG_POR 0xFB68
> +
> +#define INA238_SHUNT_CAL_POR 0x1000
> +#define INA238_SHUNT_CAL_MASK 0x7FFF /* bit 15 reserved */
> +
> +FIELD(INA238_DIAG_ALRT, MEMSTAT, 0, 1) /* memory checksum ok (always 1) */
> +FIELD(INA238_DIAG_ALRT, CNVRF, 1, 1) /* conversion ready flag */
> +FIELD(INA238_DIAG_ALRT, POL, 2, 1) /* power over-limit */
> +FIELD(INA238_DIAG_ALRT, BUSUL, 3, 1) /* bus under-limit */
> +FIELD(INA238_DIAG_ALRT, BUSOL, 4, 1) /* bus over-limit */
> +FIELD(INA238_DIAG_ALRT, SHNTUL, 5, 1) /* shunt under-limit */
> +FIELD(INA238_DIAG_ALRT, SHNTOL, 6, 1) /* shunt over-limit */
> +FIELD(INA238_DIAG_ALRT, TMPOL, 7, 1) /* over temperature */
> +FIELD(INA238_DIAG_ALRT, MATHOF, 9, 1) /* arithmetic overflow */
> +FIELD(INA238_DIAG_ALRT, APOL, 12, 1) /* alert polarity */
> +FIELD(INA238_DIAG_ALRT, SLOWALERT, 13, 1) /* alert on averaged value */
> +FIELD(INA238_DIAG_ALRT, CNVR, 14, 1) /* conversion-ready on ALERT pin */
> +FIELD(INA238_DIAG_ALRT, ALATCH, 15, 1) /* alert latch enable */
> +
> +#define INA238_DIAG_ALRT_POR 0x0001 /* MEMSTAT = 1 */
> +
> +/* Control (writable) bits */
> +#define INA238_DIAG_CTRL_MASK (R_INA238_DIAG_ALRT_ALATCH_MASK | \
> + R_INA238_DIAG_ALRT_CNVR_MASK | \
> + R_INA238_DIAG_ALRT_SLOWALERT_MASK | \
> + R_INA238_DIAG_ALRT_APOL_MASK)
> +/* Read-only status bits (preserved across a control-bit write) */
> +#define INA238_DIAG_STATUS_MASK (R_INA238_DIAG_ALRT_MATHOF_MASK | \
> + R_INA238_DIAG_ALRT_TMPOL_MASK | \
> + R_INA238_DIAG_ALRT_SHNTOL_MASK | \
> + R_INA238_DIAG_ALRT_SHNTUL_MASK | \
> + R_INA238_DIAG_ALRT_BUSOL_MASK | \
> + R_INA238_DIAG_ALRT_BUSUL_MASK | \
> + R_INA238_DIAG_ALRT_POL_MASK | \
> + R_INA238_DIAG_ALRT_CNVRF_MASK | \
> + R_INA238_DIAG_ALRT_MEMSTAT_MASK)
> +/* Threshold flags cleared when the DIAG_ALRT register is read */
> +#define INA238_DIAG_LATCH_MASK (R_INA238_DIAG_ALRT_TMPOL_MASK | \
> + R_INA238_DIAG_ALRT_SHNTOL_MASK | \
> + R_INA238_DIAG_ALRT_SHNTUL_MASK | \
> + R_INA238_DIAG_ALRT_BUSOL_MASK | \
> + R_INA238_DIAG_ALRT_BUSUL_MASK | \
> + R_INA238_DIAG_ALRT_POL_MASK)
> +
> +#define INA238_SOVL_POR 0x7FFF
> +#define INA238_SUVL_POR 0x8000
> +#define INA238_BOVL_POR 0x7FFF
> +#define INA238_BUVL_POR 0x0000
> +/*
> + * The datasheet is self-inconsistent on the TEMP_LIMIT reset value.
> + * Writes mask with INA238_TEMP_LIMIT_MASK and the alert comparison shifts both
> + * operands >>4.
> + */
> +#define INA238_TEMP_LIMIT_POR 0x7FF0
> +#define INA238_PWR_LIMIT_POR 0xFFFF
> +
> +#define INA238_BUS_LIMIT_MASK 0x7FFF /* BOVL/BUVL are 15-bit, positive */
> +#define INA238_TEMP_LIMIT_MASK 0xFFF0 /* TOL occupies bits [15:4] */
> +
> +#define INA238_MANUFACTURER_ID_VAL 0x5449 /* "TI" */
> +#define INA238_DEVICE_ID_VAL 0x2381 /* DIEID 0x238, REV 0x1 */
> +
> +/* Shunt voltage LSB depends on ADCRANGE; bus and temperature are fixed */
> +#define INA238_SHUNT_LSB_NV_R0 5000 /* 5 uV per LSB (ADCRANGE = 0) */
> +#define INA238_SHUNT_LSB_NV_R1 1250 /* 1.25 uV per LSB (ADCRANGE = 1) */
> +#define INA238_BUS_LSB_UV 3125 /* 3.125 mV per LSB */
> +#define INA238_TEMP_LSB_MC 125 /* 125 m-degC per LSB */
> +
> +/*
> + * current = shunt_reg * K / SHUNT_CAL. K derives from the fixed 819.2e6
> + * internal constant and the ADCRANGE=0 shunt LSB: 5e-6 * 819.2e6 = 4096.
> + * power = current * bus / 64 (0.2 / 3.125mV expressed in the register domain).
> + */
> +#define INA238_CURRENT_K 4096
> +#define INA238_POWER_DIV 64
> +#define INA238_POWER_MAX 0xFFFFFF /* POWER is a 24-bit register */
> +
> +/* Injection property ranges */
> +#define INA238_SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
> +#define INA238_SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
> +#define INA238_BUS_MAX_UV 85000000 /* 85 V full common-mode range */
> +#define INA238_TEMP_MIN_MC (-40000) /* package limited */
> +#define INA238_TEMP_MAX_MC 125000
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(INA238State, INA238)
> +
> +struct INA238State {
> + I2CSlave parent_obj;
> +
> + qemu_irq alert; /* ALERT open-drain output */
> +
> + /* I2C transfer state */
> + uint8_t ptr; /* register pointer */
> + uint8_t len; /* bytes received in current transfer */
> + uint8_t wr_hi; /* pending write MSB */
> + uint8_t rx_byte; /* index of the next byte to return within rx_val */
> + uint8_t rx_width; /* width in bytes of the register being read (2 or 3) */
> + uint32_t rx_val; /* value latched at the start of a read */
> +
> + /* Register file */
> + uint16_t config;
> + uint16_t adc_config;
> + uint16_t shunt_cal;
> + uint16_t diag_alrt;
> + uint16_t sovl;
> + uint16_t suvl;
> + uint16_t bovl;
> + uint16_t buvl;
> + uint16_t temp_limit;
> + uint16_t pwr_limit;
> +
> + /* Derived measurement registers */
> + uint16_t shunt_reg;
> + uint16_t bus_reg;
> + uint16_t dietemp_reg;
> + uint16_t current_reg;
> + uint32_t power_reg; /* 24-bit */
> +
> + /* Injected physical inputs */
> + int32_t shunt_nv; /* shunt voltage, nanovolts (signed: bidirectional) */
> + uint32_t bus_uv; /* bus voltage, microvolts (never negative) */
> + int32_t temp_mc; /* die temperature, millidegrees Celsius (signed) */
> +
> + char *description;
> +};
> +
> +/*
> + * Update the _field_ limit flag in _diag_ from the current comparison _cond_.
> + * In latch mode (_latch_) a flag that is already set stays set until the
> + * register is read; in transparent mode it tracks _cond_ directly. A flag
> + * whose measurement register was not refreshed (_fresh_) keeps its value.
> + */
> +#define INA238_UPD_FLAG(_diag_, _latch_, _fresh_, _field_, _cond_) \
> + do { \
> + if (_fresh_) { \
> + uint16_t _old_ = FIELD_EX16(_diag_, INA238_DIAG_ALRT, _field_); \
> + uint16_t _new_ = (uint16_t)((_cond_) || ((_latch_) && _old_)); \
> + (_diag_) = FIELD_DP16(_diag_, INA238_DIAG_ALRT, _field_, _new_); \
> + } \
> + } while (0)
> +
> +static void ina238_refresh_alert(INA238State *s)
> +{
> + uint16_t diag = s->diag_alrt;
> + bool active;
> + bool cnvr;
> + int level;
> +
> + /* MATHOF is a diagnostic; it does not drive the ALERT pin. */
> + active = FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTOL) ||
> + FIELD_EX16(diag, INA238_DIAG_ALRT, SHNTUL) ||
> + FIELD_EX16(diag, INA238_DIAG_ALRT, BUSOL) ||
> + FIELD_EX16(diag, INA238_DIAG_ALRT, BUSUL) ||
> + FIELD_EX16(diag, INA238_DIAG_ALRT, TMPOL) ||
> + FIELD_EX16(diag, INA238_DIAG_ALRT, POL);
> +
> + cnvr = FIELD_EX16(diag, INA238_DIAG_ALRT, CNVR) &&
> + FIELD_EX16(diag, INA238_DIAG_ALRT, CNVRF);
> +
> + active = active || cnvr;
> + /* APOL: 0 = active-low (default), 1 = active-high */
> + level = FIELD_EX16(diag, INA238_DIAG_ALRT, APOL) ? active : !active;
> +
> + trace_ina238_alert(s->description, level);
> + qemu_set_irq(s->alert, level);
> +}
> +
> +/*
> + * @shunt_fresh, @bus_fresh and @temp_fresh tell which measurement registers
> + * hold a fresh value: after a partial conversion the flags fed by the other
> + * channels must not be re-evaluated against their stale registers.
> + */
> +static void ina238_update_alert(INA238State *s, bool shunt_fresh,
> + bool bus_fresh, bool temp_fresh,
> + bool power_fresh)
> +{
> + bool latch = FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH);
> + int16_t shunt = (int16_t)s->shunt_reg;
> + int16_t temp = (int16_t)s->dietemp_reg >> 4; /* sign-extend [15:4] */
> + int16_t temp_lim = (int16_t)s->temp_limit >> 4;
> + uint16_t diag = s->diag_alrt;
> +
> + INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTOL, shunt > (int16_t)s->sovl);
> + INA238_UPD_FLAG(diag, latch, shunt_fresh, SHNTUL, shunt < (int16_t)s->suvl);
> + INA238_UPD_FLAG(diag, latch, bus_fresh, BUSOL,
> + s->bus_reg > (s->bovl & INA238_BUS_LIMIT_MASK));
> + INA238_UPD_FLAG(diag, latch, bus_fresh, BUSUL,
> + s->bus_reg < (s->buvl & INA238_BUS_LIMIT_MASK));
> + INA238_UPD_FLAG(diag, latch, temp_fresh, TMPOL, temp > temp_lim);
> + INA238_UPD_FLAG(diag, latch, power_fresh, POL,
> + s->power_reg > ((uint32_t)s->pwr_limit << 8));
> +
> + s->diag_alrt = diag;
> +
> + ina238_refresh_alert(s);
> +}
> +
> +static void ina238_convert(INA238State *s)
> +{
> + uint8_t mode = FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE);
> + bool bus_en = mode & 0x1;
> + bool shunt_en = mode & 0x2;
> + bool temp_en = mode & 0x4;
> + bool adcrange = FIELD_EX16(s->config, INA238_CONFIG, ADCRANGE);
> + int shunt_lsb = adcrange ? INA238_SHUNT_LSB_NV_R1 : INA238_SHUNT_LSB_NV_R0;
> + int64_t sr, br, tr, cur, pwr;
> + uint16_t mathof = 0;
> + bool power_fresh = bus_en;
> +
> + if (!bus_en && !shunt_en && !temp_en) {
> + return;
> + }
> +
> + if (shunt_en) {
> + sr = s->shunt_nv / shunt_lsb;
> + sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
> + s->shunt_reg = (uint16_t)(int16_t)sr;
> +
> + if (s->shunt_cal == 0) {
> + cur = 0;
> + } else {
> + cur = (int64_t)(int16_t)s->shunt_reg * INA238_CURRENT_K /
> + s->shunt_cal;
> + }
> + if (cur > INT16_MAX || cur < INT16_MIN) {
> + mathof = 1;
> + cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
> + }
> + s->current_reg = (uint16_t)(int16_t)cur;
> + }
> +
> + if (bus_en) {
> + br = s->bus_uv / INA238_BUS_LSB_UV;
> + br = MAX(0, MIN(INT16_MAX, br));
> + s->bus_reg = (uint16_t)br;
> + }
> +
> + if (temp_en) {
> + /* Die temperature: 12-bit signed value stored in bits [15:4] */
> + tr = s->temp_mc / INA238_TEMP_LSB_MC;
> + tr = MAX(-2048, MIN(2047, tr));
> + s->dietemp_reg = ((uint16_t)(int16_t)tr << 4) & INA238_TEMP_LIMIT_MASK;
> + }
> +
> + /*
> + * In bus-only mode the current register is retained from the last shunt
> + * conversion rather than measured anew, but the power register is still
> + * refreshed.
> + */
> + if (bus_en) {
> + cur = (int16_t)s->current_reg;
> + if (cur < 0) {
> + cur = -cur;
> + }
> + pwr = cur * (int64_t)s->bus_reg / INA238_POWER_DIV;
> + if (pwr > INA238_POWER_MAX) {
> + mathof = 1;
> + pwr = INA238_POWER_MAX;
> + }
> + s->power_reg = (uint32_t)pwr;
> + }
> +
> + /* Without a calibration the device reports neither current nor power. */
> + if (s->shunt_cal == 0) {
> + s->current_reg = 0;
> + s->power_reg = 0;
> + power_fresh = true;
> + }
> +
> + s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, MATHOF, mathof);
> + s->diag_alrt = FIELD_DP16(s->diag_alrt, INA238_DIAG_ALRT, CNVRF, 1);
> +
> + trace_ina238_convert(s->description, s->shunt_reg, s->bus_reg,
> + s->dietemp_reg, s->current_reg, s->power_reg);
> +
> + ina238_update_alert(s, shunt_en, bus_en, temp_en, power_fresh);
> +}
> +
> +/*
> + * A physical input, the calibration or the ADC range changed.
> + * - in continuous mode the device keeps converting and immediately tracks
> + * the new value
> + * - in a triggered mode it performs exactly one conversion per trigger
> + */
> +static void ina238_input_update(INA238State *s)
> +{
> + if (FIELD_EX16(s->adc_config, INA238_ADC_CONFIG, MODE) & 0x8) {
> + ina238_convert(s);
> + }
> +}
> +
> +static void ina238_reset_regs(INA238State *s)
> +{
> + s->config = INA238_CONFIG_POR;
> + s->adc_config = INA238_ADC_CONFIG_POR;
> + s->shunt_cal = INA238_SHUNT_CAL_POR;
> + s->diag_alrt = INA238_DIAG_ALRT_POR;
> + s->sovl = INA238_SOVL_POR;
> + s->suvl = INA238_SUVL_POR;
> + s->bovl = INA238_BOVL_POR;
> + s->buvl = INA238_BUVL_POR;
> + s->temp_limit = INA238_TEMP_LIMIT_POR;
> + s->pwr_limit = INA238_PWR_LIMIT_POR;
> +
> + s->ptr = 0;
> + s->len = 0;
> + s->wr_hi = 0;
> + s->rx_byte = 0;
> + s->rx_width = 0;
> + s->rx_val = 0;
> +
> + /*
> + * The measurement registers power up at zero and stay zero until the next
> + * conversion. The injected physical inputs are retained, but no conversion
> + * is performed here, so a software reset does not resurrect a previous
> + * measurement.
> + */
> + s->shunt_reg = 0;
> + s->bus_reg = 0;
> + s->dietemp_reg = 0;
> + s->current_reg = 0;
> + s->power_reg = 0;
> +
> + ina238_update_alert(s, true, true, true, true);
> +}
> +
> +static uint32_t ina238_read_reg(INA238State *s, uint8_t reg)
> +{
> + uint32_t val;
> +
> + switch (reg) {
> + case INA238_REG_CONFIG:
> + val = s->config;
> + break;
> + case INA238_REG_ADC_CONFIG:
> + val = s->adc_config;
> + break;
> + case INA238_REG_SHUNT_CAL:
> + val = s->shunt_cal;
> + break;
> + case INA238_REG_VSHUNT:
> + val = s->shunt_reg;
> + break;
> + case INA238_REG_VBUS:
> + val = s->bus_reg;
> + break;
> + case INA238_REG_DIETEMP:
> + val = s->dietemp_reg;
> + break;
> + case INA238_REG_CURRENT:
> + val = s->current_reg;
> + break;
> + case INA238_REG_POWER:
> + val = s->power_reg;
> + break;
> + case INA238_REG_DIAG_ALRT:
> + val = s->diag_alrt;
> + /*
> + * A read always clears CNVRF; the latched limit flags clear too, but
> + * only in latch mode.
> + */
> + s->diag_alrt &= ~R_INA238_DIAG_ALRT_CNVRF_MASK;
> + if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
> + s->diag_alrt &= ~INA238_DIAG_LATCH_MASK;
> + }
> + ina238_refresh_alert(s);
> + break;
> + case INA238_REG_SOVL:
> + val = s->sovl;
> + break;
> + case INA238_REG_SUVL:
> + val = s->suvl;
> + break;
> + case INA238_REG_BOVL:
> + val = s->bovl;
> + break;
> + case INA238_REG_BUVL:
> + val = s->buvl;
> + break;
> + case INA238_REG_TEMP_LIMIT:
> + val = s->temp_limit;
> + break;
> + case INA238_REG_PWR_LIMIT:
> + val = s->pwr_limit;
> + break;
> + case INA238_REG_MANUFACTURER_ID:
> + val = INA238_MANUFACTURER_ID_VAL;
> + break;
> + case INA238_REG_DEVICE_ID:
> + val = INA238_DEVICE_ID_VAL;
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: %s: read from undefined register 0x%02x\n",
> + __func__, s->description, reg);
> + val = 0;
> + break;
> + }
> +
> + trace_ina238_read(s->description, reg, val);
> + return val;
> +}
> +
> +static void ina238_write_reg(INA238State *s, uint8_t reg, uint16_t val)
> +{
> + trace_ina238_write(s->description, reg, val);
> +
> + switch (reg) {
> + case INA238_REG_CONFIG:
> + if (val & R_INA238_CONFIG_RST_MASK) {
> + trace_ina238_reset(s->description, "reg");
> + ina238_reset_regs(s);
> + break;
> + }
> + s->config = val & INA238_CONFIG_WMASK;
> + ina238_input_update(s);
> + break;
> + case INA238_REG_ADC_CONFIG:
> + s->adc_config = val;
> + /* Writing the MODE bits (re)starts a conversion: it is the trigger. */
> + ina238_convert(s);
> + break;
> + case INA238_REG_SHUNT_CAL:
> + s->shunt_cal = val & INA238_SHUNT_CAL_MASK;
> + ina238_input_update(s);
> + break;
> + case INA238_REG_DIAG_ALRT: {
> + uint16_t status = s->diag_alrt & INA238_DIAG_STATUS_MASK;
> + /*
> + * In latch mode a write clears the latched limit flags and the
> + * conversion-ready flag. In transparent mode the flags track the live
> + * condition and are preserved. The pin is only refreshed, so a cleared
> + * flag re-asserts on the next conversion rather than immediately.
> + */
> + if (FIELD_EX16(s->diag_alrt, INA238_DIAG_ALRT, ALATCH)) {
> + status &= ~(R_INA238_DIAG_ALRT_CNVRF_MASK | INA238_DIAG_LATCH_MASK);
> + }
> + s->diag_alrt = (val & INA238_DIAG_CTRL_MASK) | status;
> + ina238_refresh_alert(s);
> + break;
> + }
> + case INA238_REG_SOVL:
> + s->sovl = val;
> + ina238_update_alert(s, true, true, true, true);
> + break;
> + case INA238_REG_SUVL:
> + s->suvl = val;
> + ina238_update_alert(s, true, true, true, true);
> + break;
> + case INA238_REG_BOVL:
> + s->bovl = val & INA238_BUS_LIMIT_MASK;
> + ina238_update_alert(s, true, true, true, true);
> + break;
> + case INA238_REG_BUVL:
> + s->buvl = val & INA238_BUS_LIMIT_MASK;
> + ina238_update_alert(s, true, true, true, true);
> + break;
> + case INA238_REG_TEMP_LIMIT:
> + s->temp_limit = val & INA238_TEMP_LIMIT_MASK;
> + ina238_update_alert(s, true, true, true, true);
> + break;
> + case INA238_REG_PWR_LIMIT:
> + s->pwr_limit = val;
> + ina238_update_alert(s, true, true, true, true);
> + break;
> + case INA238_REG_VSHUNT:
> + case INA238_REG_VBUS:
> + case INA238_REG_DIETEMP:
> + case INA238_REG_CURRENT:
> + case INA238_REG_POWER:
> + case INA238_REG_MANUFACTURER_ID:
> + case INA238_REG_DEVICE_ID:
> + 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 ina238_recv(I2CSlave *i2c)
> +{
> + INA238State *s = INA238(i2c);
> + uint8_t byte;
> +
> + if (s->rx_byte == 0) {
> + s->rx_val = ina238_read_reg(s, s->ptr);
> + s->rx_width = (s->ptr == INA238_REG_POWER) ? 3 : 2;
> + }
> +
> + byte = (s->rx_val >> (8 * (s->rx_width - 1 - s->rx_byte))) & 0xff;
> + s->rx_byte++;
> + if (s->rx_byte >= s->rx_width) {
> + s->rx_byte = 0;
> + }
> +
> + return byte;
> +}
> +
> +static int ina238_send(I2CSlave *i2c, uint8_t data)
> +{
> + INA238State *s = INA238(i2c);
> +
> + if (s->len == 0) {
> + s->ptr = data;
> + } else if ((s->len & 1) == 1) {
> + s->wr_hi = data;
> + } else {
> + ina238_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
> + }
> + s->len++;
> +
> + return 0;
> +}
> +
> +static int ina238_event(I2CSlave *i2c, enum i2c_event event)
> +{
> + INA238State *s = INA238(i2c);
> +
> + s->len = 0;
> + s->rx_byte = 0;
> +
> + return 0;
> +}
> +
> +static void ina238_get_shunt(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA238State *s = INA238(obj);
> + int64_t value = s->shunt_nv;
> +
> + visit_type_int(v, name, &value, errp);
> +}
> +
> +static void ina238_set_shunt(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA238State *s = INA238(obj);
> + int64_t value;
> +
> + if (!visit_type_int(v, name, &value, errp)) {
> + return;
> + }
> +
> + if (value < INA238_SHUNT_MIN_NV || value > INA238_SHUNT_MAX_NV) {
> + error_setg(errp,
> + "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
> + s->description, value,
> + INA238_SHUNT_MIN_NV, INA238_SHUNT_MAX_NV);
> + return;
> + }
> +
> + s->shunt_nv = (int32_t)value;
> + ina238_input_update(s);
> +}
> +
> +static void ina238_get_bus(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA238State *s = INA238(obj);
> + int64_t value = s->bus_uv;
> +
> + visit_type_int(v, name, &value, errp);
> +}
> +
> +static void ina238_set_bus(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA238State *s = INA238(obj);
> + int64_t value;
> +
> + if (!visit_type_int(v, name, &value, errp)) {
> + return;
> + }
> +
> + if (value < 0 || value > INA238_BUS_MAX_UV) {
> + error_setg(errp,
> + "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
> + s->description, value, INA238_BUS_MAX_UV);
> + return;
> + }
> +
> + s->bus_uv = (uint32_t)value;
> + ina238_input_update(s);
> +}
> +
> +static void ina238_get_temp(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA238State *s = INA238(obj);
> + int64_t value = s->temp_mc;
> +
> + visit_type_int(v, name, &value, errp);
> +}
> +
> +static void ina238_set_temp(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + INA238State *s = INA238(obj);
> + int64_t value;
> +
> + if (!visit_type_int(v, name, &value, errp)) {
> + return;
> + }
> +
> + if (value < INA238_TEMP_MIN_MC || value > INA238_TEMP_MAX_MC) {
> + error_setg(errp,
> + "%s: die-temperature %" PRId64
> + " out of range (%d..%d m-degC)",
> + s->description, value,
> + INA238_TEMP_MIN_MC, INA238_TEMP_MAX_MC);
> + return;
> + }
> +
> + s->temp_mc = (int32_t)value;
> + ina238_input_update(s);
> +}
> +
> +static void ina238_reset_hold(Object *obj, ResetType type)
> +{
> + INA238State *s = INA238(obj);
> +
> + trace_ina238_reset(s->description, "hw");
> + ina238_reset_regs(s);
> +}
> +
> +static int ina238_post_load(void *opaque, int version_id)
> +{
> + INA238State *s = opaque;
> +
> + ina238_refresh_alert(s);
> +
> + return 0;
> +}
> +
> +static const VMStateDescription ina238_vmstate = {
> + .name = "INA238",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .post_load = ina238_post_load,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT8(ptr, INA238State),
> + VMSTATE_UINT8(len, INA238State),
> + VMSTATE_UINT8(wr_hi, INA238State),
> + VMSTATE_UINT8(rx_byte, INA238State),
> + VMSTATE_UINT8(rx_width, INA238State),
> + VMSTATE_UINT32(rx_val, INA238State),
> + VMSTATE_UINT16(config, INA238State),
> + VMSTATE_UINT16(adc_config, INA238State),
> + VMSTATE_UINT16(shunt_cal, INA238State),
> + VMSTATE_UINT16(diag_alrt, INA238State),
> + VMSTATE_UINT16(sovl, INA238State),
> + VMSTATE_UINT16(suvl, INA238State),
> + VMSTATE_UINT16(bovl, INA238State),
> + VMSTATE_UINT16(buvl, INA238State),
> + VMSTATE_UINT16(temp_limit, INA238State),
> + VMSTATE_UINT16(pwr_limit, INA238State),
> + VMSTATE_UINT16(shunt_reg, INA238State),
> + VMSTATE_UINT16(bus_reg, INA238State),
> + VMSTATE_UINT16(dietemp_reg, INA238State),
> + VMSTATE_UINT16(current_reg, INA238State),
> + VMSTATE_UINT32(power_reg, INA238State),
> + VMSTATE_INT32(shunt_nv, INA238State),
> + VMSTATE_UINT32(bus_uv, INA238State),
> + VMSTATE_INT32(temp_mc, INA238State),
> + VMSTATE_I2C_SLAVE(parent_obj, INA238State),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void ina238_initfn(Object *obj)
> +{
> + object_property_add(obj, "shunt-voltage", "int", ina238_get_shunt,
> + ina238_set_shunt, NULL, NULL);
> + object_property_set_description(obj, "shunt-voltage",
> + "Injected shunt (differential) voltage, "
> + "in nanovolts");
> + object_property_add(obj, "bus-voltage", "int", ina238_get_bus,
> + ina238_set_bus, NULL, NULL);
> + object_property_set_description(obj, "bus-voltage",
> + "Injected bus voltage, in microvolts");
> + object_property_add(obj, "die-temperature", "int", ina238_get_temp,
> + ina238_set_temp, NULL, NULL);
> + object_property_set_description(obj, "die-temperature",
> + "Injected die temperature, "
> + "in millidegrees Celsius");
> +}
> +
> +static void ina238_realize(DeviceState *dev, Error **errp)
> +{
> + INA238State *s = INA238(dev);
> +
> + if (!s->description) {
> + s->description = g_strdup(object_get_typename(OBJECT(dev)));
> + }
> +
> + qdev_init_gpio_out(dev, &s->alert, 1);
> +}
> +
> +static const Property ina238_properties[] = {
> + DEFINE_PROP_STRING("description", INA238State, description),
> +};
> +
> +static void ina238_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 = ina238_event;
> + ic->recv = ina238_recv;
> + ic->send = ina238_send;
> + dc->realize = ina238_realize;
> + rc->phases.hold = ina238_reset_hold;
> + dc->vmsd = &ina238_vmstate;
> + device_class_set_props(dc, ina238_properties);
> +}
> +
> +static const TypeInfo ina238_types[] = {
> + {
> + .name = TYPE_INA238,
> + .parent = TYPE_I2C_SLAVE,
> + .instance_init = ina238_initfn,
> + .instance_size = sizeof(INA238State),
> + .class_init = ina238_class_init,
> + },
> +};
> +
> +DEFINE_TYPES(ina238_types)
> diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
> index b81f001fdf..7e6fd64488 100644
> --- a/hw/sensor/meson.build
> +++ b/hw/sensor/meson.build
> @@ -1,5 +1,6 @@
> system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
> system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.c'))
> +system_ss.add(when: 'CONFIG_INA238', if_true: files('ina238.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 f809b6b506..98158e37c6 100644
> --- a/hw/sensor/trace-events
> +++ b/hw/sensor/trace-events
> @@ -15,6 +15,13 @@ ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t current, u
> ina230_alert(const char *id, bool level) "%s level %u"
> ina230_reset(const char *id, const char *source) "%s %s"
>
> +# ina238.c
> +ina238_read(const char *id, uint8_t reg, uint32_t value) "%s reg 0x%02x val 0x%06x"
> +ina238_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 0x%04x"
> +ina238_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t dietemp, uint16_t current, uint32_t power) "%s shunt 0x%04x bus 0x%04x dietemp 0x%04x current 0x%04x power 0x%06x"
> +ina238_alert(const char *id, bool level) "%s level %u"
> +ina238_reset(const char *id, const char *source) "%s %s"
> +
> # tmp105.c
> tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
> tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
> diff --git a/include/hw/sensor/ina238.h b/include/hw/sensor/ina238.h
> new file mode 100644
> index 0000000000..3bc3c02073
> --- /dev/null
> +++ b/include/hw/sensor/ina238.h
> @@ -0,0 +1,14 @@
> +/*
> + * Texas Instruments INA238 current/voltage/power monitor with I2C interface
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_SENSOR_INA238_H
> +#define HW_SENSOR_INA238_H
> +
> +#define TYPE_INA238 "ina238"
> +
> +#endif
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 41/50] tests/qtest: ina238: add current/voltage/power monitor test
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:08 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:08 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Introduce the INA238 qtest: the qgraph node, the QMP injection helpers
> and the register map. Cover the reset defaults, the software reset, and
> the Configuration register write mask.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina238-test.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++
> tests/qtest/meson.build | 1 +
> 2 files changed, 178 insertions(+)
>
> diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
> new file mode 100644
> index 0000000000..6e59ed30b5
> --- /dev/null
> +++ b/tests/qtest/ina238-test.c
> @@ -0,0 +1,177 @@
> +/*
> + * QTest testcase for the INA238 current/voltage/power monitor
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqos/i2c.h"
> +#include "libqos/qgraph.h"
> +#include "libqtest-single.h"
> +#include "qobject/qdict.h"
> +
> +#define INA238_TEST_ID "ina238-test"
> +#define INA238_TEST_ADDR 0x40
> +
> +/* Register pointer addresses */
> +#define REG_CONFIG 0x00
> +#define REG_ADC_CONFIG 0x01
> +#define REG_SHUNT_CAL 0x02
> +#define REG_VSHUNT 0x04
> +#define REG_VBUS 0x05
> +#define REG_DIETEMP 0x06
> +#define REG_CURRENT 0x07
> +#define REG_POWER 0x08
> +#define REG_DIAG_ALRT 0x0B
> +#define REG_SOVL 0x0C
> +#define REG_SUVL 0x0D
> +#define REG_BOVL 0x0E
> +#define REG_BUVL 0x0F
> +#define REG_TEMP_LIMIT 0x10
> +#define REG_PWR_LIMIT 0x11
> +#define REG_MANUFACTURER_ID 0x3E
> +#define REG_DEVICE_ID 0x3F
> +
> +/* Configuration register bits */
> +#define CONFIG_RST 0x8000
> +#define CONFIG_ADCRANGE 0x0010
> +
> +/* DIAG_ALRT register bits */
> +#define DIAG_MEMSTAT 0x0001
> +#define DIAG_CNVRF 0x0002
> +#define DIAG_POL 0x0004
> +#define DIAG_BUSUL 0x0008
> +#define DIAG_BUSOL 0x0010
> +#define DIAG_SHNTUL 0x0020
> +#define DIAG_SHNTOL 0x0040
> +#define DIAG_TMPOL 0x0080
> +#define DIAG_MATHOF 0x0200
> +#define DIAG_APOL 0x1000
> +#define DIAG_SLOWALERT 0x2000
> +#define DIAG_CNVR 0x4000
> +#define DIAG_ALATCH 0x8000
> +
> +/* Reset values */
> +#define CONFIG_POR 0x0000
> +#define ADC_CONFIG_POR 0xFB68
> +#define SHUNT_CAL_POR 0x1000
> +#define DIAG_ALRT_POR 0x0001
> +#define SOVL_POR 0x7FFF
> +#define SUVL_POR 0x8000
> +#define BOVL_POR 0x7FFF
> +#define BUVL_POR 0x0000
> +#define TEMP_LIMIT_POR 0x7FF0
> +#define PWR_LIMIT_POR 0xFFFF
> +#define MANUFACTURER_ID_VAL 0x5449
> +#define DEVICE_ID_VAL 0x2381
> +
> +/* Read the 24-bit POWER register (MSB first) */
> +static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
> +{
> + uint8_t resp[3];
> +
> + i2c_read_block(dev, reg, resp, sizeof(resp));
> + return (resp[0] << 16) | (resp[1] << 8) | resp[2];
> +}
> +
> +/* QMP helpers for injecting the physical inputs */
> +
> +static void qmp_ina238_set(const char *property, int value)
> +{
> + QDict *resp;
> +
> + resp = qmp("{ 'execute': 'qom-set', 'arguments':"
> + " { 'path': %s, 'property': %s, 'value': %d } }",
> + INA238_TEST_ID, property, value);
> + g_assert(qdict_haskey(resp, "return"));
> + qobject_unref(resp);
> +}
> +
> +/* Power-on-reset default values and the ID registers */
> +static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_ADC_CONFIG), ==, ADC_CONFIG_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> + g_assert_cmphex(i2c_get24(dev, REG_POWER), ==, 0x000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_SUVL), ==, SUVL_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_BOVL), ==, BOVL_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_BUVL), ==, BUVL_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_PWR_LIMIT), ==, PWR_LIMIT_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_MANUFACTURER_ID), ==,
> + MANUFACTURER_ID_VAL);
> + g_assert_cmphex(i2c_get16(dev, REG_DEVICE_ID), ==, DEVICE_ID_VAL);
> +}
> +
> +/* Software reset via the Configuration RST bit */
> +static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_SHUNT_CAL, 0x1234);
> + i2c_set16(dev, REG_SOVL, 0x0500);
> + i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("bus-voltage", 48000000);
> + qmp_ina238_set("die-temperature", 25000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, 0x1234);
> + g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, 0x0500);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
> + g_assert_cmpuint(i2c_get16(dev, REG_CURRENT), !=, 0);
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> + g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
> +}
> +
> +/* CONFIG keeps only the writable ADCRANGE/CONVDLY bits and self-clears RST */
> +static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE | 0x0040);
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_ADCRANGE | 0x0040);
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
> +}
> +
> +static void ina238_register_nodes(void)
> +{
> + QOSGraphEdgeOptions opts = {
> + .extra_device_opts = "id=" INA238_TEST_ID ",address=0x40"
> + };
> + add_qi2c_address(&opts, &(QI2CAddress) { INA238_TEST_ADDR });
> +
> + qos_node_create_driver("ina238", i2c_device_create);
> + qos_node_consumes("ina238", "i2c-bus", &opts);
> +
> + qos_add_test("defaults", "ina238", test_defaults, NULL);
> + qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
> + qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
> +}
> +libqos_init(ina238_register_nodes);
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index e413a0cc75..7005509d4d 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -331,6 +331,7 @@ qos_test_ss.add(
> 'eepro100-test.c',
> 'es1370-test.c',
> 'ina230-test.c',
> + 'ina238-test.c',
> 'lsm303dlhc-mag-test.c',
> 'isl_pmbus_vr-test.c',
> 'max34451-test.c',
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 42/50] tests/qtest: ina238: cover voltage/temperature injection and derivation
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:09 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:09 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Cover the shunt-, bus- and die-temperature injection properties and the
> current and power values derived through the Shunt Calibration register,
> including the zero-calibration case, the ADCRANGE rescaling and the
> clamp/overflow edges.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina238-test.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 156 insertions(+)
>
> diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
> index 6e59ed30b5..693139f988 100644
> --- a/tests/qtest/ina238-test.c
> +++ b/tests/qtest/ina238-test.c
> @@ -89,6 +89,20 @@ static void qmp_ina238_set(const char *property, int value)
> qobject_unref(resp);
> }
>
> +static int qmp_ina238_get(const char *property)
> +{
> + QDict *resp;
> + int ret;
> +
> + resp = qmp("{ 'execute': 'qom-get', 'arguments':"
> + " { 'path': %s, 'property': %s } }",
> + INA238_TEST_ID, property);
> + g_assert(qdict_haskey(resp, "return"));
> + ret = qdict_get_int(resp, "return");
> + qobject_unref(resp);
> + return ret;
> +}
> +
> /* Power-on-reset default values and the ID registers */
> static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -160,6 +174,139 @@ static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR);
> }
>
> +/* Shunt voltage register */
> +static void test_shunt_injection(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
> +
> + qmp_ina238_set("shunt-voltage", -80000000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0xC180);
> +
> + g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, -80000000);
> +}
> +
> +/* Bus voltage register */
> +static void test_bus_injection(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina238_set("bus-voltage", 48000000);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x3C00);
> +
> + qmp_ina238_set("bus-voltage", 12000000);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
> +}
> +
> +/* Die temperature register */
> +static void test_temp_injection(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina238_set("die-temperature", 25000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0C80);
> +
> + qmp_ina238_set("die-temperature", -25000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xF380);
> +
> + g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, -25000);
> +}
> +
> +/* Calibration-derived current */
> +static void test_calibration_current(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + qmp_ina238_set("shunt-voltage", 97200000);
> +
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> +
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x4CCC);
> +}
> +
> +/* Calibration-derived power */
> +static void test_power(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("bus-voltage", 48000000);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
> +
> + g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 4718400);
> +}
> +
> +/* With SHUNT_CAL == 0 the current and power registers stay zero */
> +static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("bus-voltage", 48000000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> + g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
> +}
> +
> +/* ADCRANGE changes the shunt LSB, not the current divisor. */
> +static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina238_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
> +
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0800);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x3E80);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7D00);
> +
> + i2c_set16(dev, REG_SHUNT_CAL, 0x2000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
> +}
> +
> +/* Clamping at full scale and the MATHOF flag */
> +static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE);
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
> + qmp_ina238_set("shunt-voltage", -97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_POR);
> + qmp_ina238_set("shunt-voltage", 163835000);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0001);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x7FFF);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_MATHOF, ==,
> + DIAG_MATHOF);
> +}
> +
> +/* Shunt over-limit alert (transparent mode) */
> static void ina238_register_nodes(void)
> {
> QOSGraphEdgeOptions opts = {
> @@ -173,5 +320,14 @@ static void ina238_register_nodes(void)
> qos_add_test("defaults", "ina238", test_defaults, NULL);
> qos_add_test("soft-reset", "ina238", test_soft_reset, NULL);
> qos_add_test("config-wmask", "ina238", test_config_wmask, NULL);
> + qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
> + qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
> + qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
> + qos_add_test("calibration-current", "ina238", test_calibration_current,
> + NULL);
> + qos_add_test("power", "ina238", test_power, NULL);
> + qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
> + qos_add_test("adcrange", "ina238", test_adcrange, NULL);
> + qos_add_test("overflow", "ina238", test_overflow, NULL);
> }
> libqos_init(ina238_register_nodes);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 43/50] tests/qtest: ina238: cover the ALERT path and conversion-ready flag
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-14 20:09 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:09 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Cover the shunt/bus/temperature/power alert thresholds, the multi-alert
> OR behaviour, the alert latch, the DIAG_ALRT control bits and the
> conversion-ready (CNVRF) flag.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina238-test.c | 146 +++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 145 insertions(+), 1 deletion(-)
>
> diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
> index 693139f988..08813377c9 100644
> --- a/tests/qtest/ina238-test.c
> +++ b/tests/qtest/ina238-test.c
> @@ -306,7 +306,143 @@ static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
> DIAG_MATHOF);
> }
>
> -/* Shunt over-limit alert (transparent mode) */
> +/* Shunt over-limit alert */
> +static void test_alert_shunt(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SOVL, 0x1000);
> +
> + qmp_ina238_set("shunt-voltage", 5000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +
> + qmp_ina238_set("shunt-voltage", 5000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> +}
> +
> +/* Bus over-limit alert */
> +static void test_alert_bus(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_BOVL, 0x1000);
> +
> + qmp_ina238_set("bus-voltage", 12000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, 0);
> +
> + qmp_ina238_set("bus-voltage", 48000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_BUSOL, ==, DIAG_BUSOL);
> +}
> +
> +/* Over-temperature alert */
> +static void test_alert_temp(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00);
> +
> + qmp_ina238_set("die-temperature", 10000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, 0);
> +
> + qmp_ina238_set("die-temperature", 25000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_TMPOL, ==, DIAG_TMPOL);
> +}
> +
> +/* Power over-limit alert */
> +static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("bus-voltage", 48000000);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
> +
> + i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
> +
> + i2c_set16(dev, REG_PWR_LIMIT, 0x7FFF);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
> +}
> +
> +/* Two thresholds crossed at once are reported simultaneously (multi-alert) */
> +static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> + uint16_t diag;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SOVL, 0x1000);
> + i2c_set16(dev, REG_BOVL, 0x1000);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("bus-voltage", 48000000);
> +
> + diag = i2c_get16(dev, REG_DIAG_ALRT);
> + g_assert_cmphex(diag & DIAG_SHNTOL, ==, DIAG_SHNTOL);
> + g_assert_cmphex(diag & DIAG_BUSOL, ==, DIAG_BUSOL);
> +}
> +
> +/* Latch mode holds the flag until the DIAG_ALRT register is read */
> +static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SOVL, 0x1000);
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("shunt-voltage", 5000000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> +}
> +
> +/* DIAG_ALRT control bits are writable; status bits are read-only */
> +static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> + uint16_t diag;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + i2c_set16(dev, REG_DIAG_ALRT,
> + DIAG_ALATCH | DIAG_CNVR | DIAG_SLOWALERT | DIAG_APOL);
> + diag = i2c_get16(dev, REG_DIAG_ALRT);
> + g_assert_cmphex(diag & DIAG_ALATCH, ==, DIAG_ALATCH);
> + g_assert_cmphex(diag & DIAG_CNVR, ==, DIAG_CNVR);
> + g_assert_cmphex(diag & DIAG_SLOWALERT, ==, DIAG_SLOWALERT);
> + g_assert_cmphex(diag & DIAG_APOL, ==, DIAG_APOL);
> + /* MEMSTAT stays set */
> + g_assert_cmphex(diag & DIAG_MEMSTAT, ==, DIAG_MEMSTAT);
> +}
> +
> +/* Every DIAG_ALRT read clears CNVRF, whatever the alert-latch mode. */
> +static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + qmp_ina238_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
> +
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> + qmp_ina238_set("shunt-voltage", 20000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
> +}
> +
> static void ina238_register_nodes(void)
> {
> QOSGraphEdgeOptions opts = {
> @@ -329,5 +465,13 @@ static void ina238_register_nodes(void)
> qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
> qos_add_test("adcrange", "ina238", test_adcrange, NULL);
> qos_add_test("overflow", "ina238", test_overflow, NULL);
> + qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
> + qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
> + qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
> + qos_add_test("alert-power", "ina238", test_alert_power, NULL);
> + qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
> + qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
> + qos_add_test("diag-control", "ina238", test_diag_control, NULL);
> + qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
> }
> libqos_init(ina238_register_nodes);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 44/50] tests/qtest: ina238: cover the injection-property limits
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:10 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:10 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Verify that the extremes of the shunt-voltage, bus-voltage and
> die-temperature injection ranges round-trip through the QOM property
> without truncation or wraparound, and that values just outside are
> rejected rather than silently wrapped.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina238-test.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 84 insertions(+)
>
> diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
> index 08813377c9..74912424c0 100644
> --- a/tests/qtest/ina238-test.c
> +++ b/tests/qtest/ina238-test.c
> @@ -67,6 +67,13 @@
> #define MANUFACTURER_ID_VAL 0x5449
> #define DEVICE_ID_VAL 0x2381
>
> +/* Injection-property limits (see ina238.c) */
> +#define SHUNT_MIN_NV (-163840000) /* -32768 * 5 uV */
> +#define SHUNT_MAX_NV 163835000 /* 32767 * 5 uV */
> +#define BUS_MAX_UV 85000000 /* 27200 * 3.125 mV */
> +#define TEMP_MIN_MC (-40000) /* -320 * 125 m-degC */
> +#define TEMP_MAX_MC 125000 /* 1000 * 125 m-degC */
> +
> /* Read the 24-bit POWER register (MSB first) */
> static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg)
> {
> @@ -103,6 +110,18 @@ static int qmp_ina238_get(const char *property)
> return ret;
> }
>
> +/* A qom-set expected to be rejected (value outside the supported range) */
> +static void qmp_ina238_set_fail(const char *property, int64_t value)
> +{
> + QDict *resp;
> +
> + resp = qmp("{ 'execute': 'qom-set', 'arguments':"
> + " { 'path': %s, 'property': %s, 'value': %lld } }",
> + INA238_TEST_ID, property, (long long)value);
> + g_assert(qdict_haskey(resp, "error"));
> + qobject_unref(resp);
> +}
> +
> /* Power-on-reset default values and the ID registers */
> static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -443,6 +462,68 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
> }
>
> +/* Shunt-voltage injection limits */
> +static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina238_set("shunt-voltage", SHUNT_MAX_NV);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x7FFF);
> + g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MAX_NV);
> +
> + qmp_ina238_set("shunt-voltage", SHUNT_MIN_NV);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
> + g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
> +
> + qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MAX_NV + 1);
> + qmp_ina238_set_fail("shunt-voltage", (int64_t)SHUNT_MIN_NV - 1);
> + g_assert_cmpint(qmp_ina238_get("shunt-voltage"), ==, SHUNT_MIN_NV);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x8000);
> +}
> +
> +/* Bus-voltage injection limits */
> +static void test_bus_limits(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina238_set("bus-voltage", 0);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000);
> +
> + qmp_ina238_set("bus-voltage", BUS_MAX_UV);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
> + g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
> +
> + qmp_ina238_set_fail("bus-voltage", (int64_t)BUS_MAX_UV + 1);
> + qmp_ina238_set_fail("bus-voltage", -1);
> + g_assert_cmpint(qmp_ina238_get("bus-voltage"), ==, BUS_MAX_UV);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x6A40);
> +}
> +
> +/* Die-temperature injection limits */
> +static void test_temp_limits(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> +
> + qmp_ina238_set("die-temperature", TEMP_MAX_MC);
> + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x3E80);
> + g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MAX_MC);
> +
> + qmp_ina238_set("die-temperature", TEMP_MIN_MC);
> + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
> + g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
> +
> + qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MAX_MC + 1);
> + qmp_ina238_set_fail("die-temperature", (int64_t)TEMP_MIN_MC - 1);
> + g_assert_cmpint(qmp_ina238_get("die-temperature"), ==, TEMP_MIN_MC);
> + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0xEC00);
> +}
> +
> static void ina238_register_nodes(void)
> {
> QOSGraphEdgeOptions opts = {
> @@ -459,6 +540,9 @@ static void ina238_register_nodes(void)
> qos_add_test("shunt-injection", "ina238", test_shunt_injection, NULL);
> qos_add_test("bus-injection", "ina238", test_bus_injection, NULL);
> qos_add_test("temp-injection", "ina238", test_temp_injection, NULL);
> + qos_add_test("shunt-limits", "ina238", test_shunt_limits, NULL);
> + qos_add_test("bus-limits", "ina238", test_bus_limits, NULL);
> + qos_add_test("temp-limits", "ina238", test_temp_limits, NULL);
> qos_add_test("calibration-current", "ina238", test_calibration_current,
> NULL);
> qos_add_test("power", "ina238", test_power, NULL);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 45/50] tests/qtest: ina238: cover the operating modes
2026-10-12 14:32 ` Emmanuel Blot via qemu development
(?)
(?)
@ 2026-10-14 20:11 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:11 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Add coverage for the ADC_CONFIG MODE field: shutdown modes perform no
> conversion and leave the measurement registers and CNVRF untouched,
> triggered modes convert once per ADC_CONFIG write, and continuous
> channel-select modes refresh only the enabled measurement registers.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina238-test.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 70 insertions(+)
>
> diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
> index 74912424c0..fb7bcc265c 100644
> --- a/tests/qtest/ina238-test.c
> +++ b/tests/qtest/ina238-test.c
> @@ -304,6 +304,73 @@ static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x1F40);
> }
>
> +/* Shutdown converts nothing: registers are held, CNVRF stays clear. */
> +static void test_mode_shutdown(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
> + qmp_ina238_set("shunt-voltage", 20000000);
> + qmp_ina238_set("bus-voltage", 12000000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
> +
> + /* Clear CNVRF, then enter shutdown (MODE = 0). */
> + (void)i2c_get16(dev, REG_DIAG_ALRT);
> + i2c_set16(dev, REG_ADC_CONFIG, 0x0000);
> +
> + /* Injecting while shut down performs no conversion: registers are held. */
> + qmp_ina238_set("shunt-voltage", 40000000);
> + qmp_ina238_set("bus-voltage", 24000000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0F00);
> +
> + /* No conversion completed, so CNVRF stays clear. */
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
> +}
> +
> +/* Triggered modes convert once per ADC_CONFIG write. */
> +static void test_mode_triggered(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
> + qmp_ina238_set("shunt-voltage", 20000000);
> +
> + /* Enter triggered bus+shunt+temp mode: one conversion. */
> + i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
> +
> + /* A new input does not convert until the next trigger. */
> + qmp_ina238_set("shunt-voltage", 40000000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
> +
> + /* Re-triggering with another ADC_CONFIG write samples the new input. */
> + i2c_set16(dev, REG_ADC_CONFIG, 0x7000);
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x1F40);
> +}
> +
> +/* A bus-only mode refreshes the bus register alone; the rest are held. */
> +static void test_mode_channels(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x1000);
> + qmp_ina238_set("shunt-voltage", 20000000);
> + qmp_ina238_set("bus-voltage", 12000000);
> +
> + /* Continuous bus-only mode. */
> + i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
> + qmp_ina238_set("shunt-voltage", 40000000);
> + qmp_ina238_set("bus-voltage", 24000000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0FA0);
> + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x1E00);
> +}
> +
> /* Clamping at full scale and the MATHOF flag */
> static void test_overflow(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -548,6 +615,9 @@ static void ina238_register_nodes(void)
> qos_add_test("power", "ina238", test_power, NULL);
> qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
> qos_add_test("adcrange", "ina238", test_adcrange, NULL);
> + qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
> + qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
> + qos_add_test("mode-channels", "ina238", test_mode_channels, NULL);
> qos_add_test("overflow", "ina238", test_overflow, NULL);
> qos_add_test("alert-shunt", "ina238", test_alert_shunt, NULL);
> qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 46/50] tests/qtest: ina238: cover conversion-ready and alert-latch semantics
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:11 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:11 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Add coverage for the DIAG_ALRT flag-clearing rules: which of a read and
> a write clears CNVRF and the limit flags in each alert mode, and that a
> conversion only re-evaluates the flags of the channels it converted.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina238-test.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 127 insertions(+)
>
> diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
> index fb7bcc265c..e7dd50a822 100644
> --- a/tests/qtest/ina238-test.c
> +++ b/tests/qtest/ina238-test.c
> @@ -494,6 +494,106 @@ static void test_alert_latch(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> }
>
> +/* In latch mode a read releases the flag even while the fault persists. */
> +static void test_alert_latch_persist(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SOVL, 0x1000);
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> +
> + /* First read shows the latched flag and clears it */
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +
> + /* Fault still present but no new conversion: the flag stays cleared */
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +}
> +
> +/*
> + * In transparent mode a limit flag follows the live condition: a read leaves
> + * it set while the fault persists.
> + */
> +static void test_alert_transparent_persist(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SOVL, 0x1000);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +
> + /* Repeated reads do not clear the flag while the fault is still present. */
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +
> + /* A conversion with the fault gone clears it. */
> + qmp_ina238_set("shunt-voltage", 5000000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> +}
> +
> +/* A DIAG_ALRT write releases a latched limit flag as a read does. */
> +static void test_alert_latch_write_clears(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SOVL, 0x1000);
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +
> + /* The read above cleared it; re-latch it with another conversion. */
> + qmp_ina238_set("shunt-voltage", 97200000);
> +
> + /* A write (keeping latch mode) clears the flag while the fault persists. */
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> +
> + /* The next conversion re-asserts the still-present fault. */
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +}
> +
> +/*
> + * A conversion only re-evaluates the flags of the channels it converted: the
> + * shunt register a bus-only conversion left stale must not resurrect one.
> + */
> +static void test_alert_channel(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SOVL, 0x1000);
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> +
> + qmp_ina238_set("shunt-voltage", 97200000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==,
> + DIAG_SHNTOL);
> +
> + /* Continuous bus-only mode */
> + i2c_set16(dev, REG_ADC_CONFIG, 0x9000);
> + qmp_ina238_set("bus-voltage", 12000000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_SHNTOL, ==, 0);
> +}
> +
> /* DIAG_ALRT control bits are writable; status bits are read-only */
> static void test_diag_control(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -529,6 +629,25 @@ static void test_cnvrf(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
> }
>
> +/* A DIAG_ALRT write clears CNVRF only in latch mode. */
> +static void test_cnvrf_write(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + /* Latch mode: a write clears CNVRF. */
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> + qmp_ina238_set("shunt-voltage", 20000000);
> + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, 0);
> +
> + /* Transparent mode: a write does not clear CNVRF. */
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + qmp_ina238_set("shunt-voltage", 20000000);
> + i2c_set16(dev, REG_DIAG_ALRT, 0x0000);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_CNVRF, ==, DIAG_CNVRF);
> +}
> +
> /* Shunt-voltage injection limits */
> static void test_shunt_limits(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -625,7 +744,15 @@ static void ina238_register_nodes(void)
> qos_add_test("alert-power", "ina238", test_alert_power, NULL);
> qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
> qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
> + qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
> + NULL);
> + qos_add_test("alert-transparent-persist", "ina238",
> + test_alert_transparent_persist, NULL);
> + qos_add_test("alert-latch-write-clears", "ina238",
> + test_alert_latch_write_clears, NULL);
> + qos_add_test("alert-channel", "ina238", test_alert_channel, NULL);
> qos_add_test("diag-control", "ina238", test_diag_control, NULL);
> qos_add_test("cnvrf", "ina238", test_cnvrf, NULL);
> + qos_add_test("cnvrf-write", "ina238", test_cnvrf_write, NULL);
> }
> libqos_init(ina238_register_nodes);
>
^ permalink raw reply [flat|nested] 188+ messages in thread
* Re: [PATCH v2 47/50] tests/qtest: ina238: cover a cleared calibration per channel
2026-10-12 14:32 ` Emmanuel Blot via
(?)
(?)
@ 2026-10-14 20:12 ` Miles Glenn
-1 siblings, 0 replies; 188+ messages in thread
From: Miles Glenn @ 2026-10-14 20:12 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Pierrick Bouvier, qemu-arm, Patrick Leis, Fabiano Rosas,
Laurent Vivier, Paolo Bonzini, qemu-ppc, qemu-riscv,
Philippe Mathieu-Daudé
Reviewed-by: Glenn Miles <milesg@linux.ibm.com>
Thanks,
Glenn
On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Verify that without a calibration value the current and power
> registers read zero, whichever channels the mode converts.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/qtest/ina238-test.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 48 insertions(+)
>
> diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c
> index e7dd50a822..4e4146461c 100644
> --- a/tests/qtest/ina238-test.c
> +++ b/tests/qtest/ina238-test.c
> @@ -282,6 +282,30 @@ static void test_zero_cal(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
> }
>
> +/* Clearing SHUNT_CAL zeroes both derived registers in any mode */
> +static void test_zero_cal_channels(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> + static const uint16_t modes[] = { 0x9000, 0xA000, 0xF000 };
> + size_t idx;
> +
> + for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("bus-voltage", 48000000);
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), !=, 0x0000);
> + g_assert_cmpuint(i2c_get24(dev, REG_POWER), !=, 0);
> +
> + i2c_set16(dev, REG_ADC_CONFIG, modes[idx]);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
> +
> + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000);
> + g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
> + }
> +}
> +
> /* ADCRANGE changes the shunt LSB, not the current divisor. */
> static void test_adcrange(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -458,6 +482,27 @@ static void test_alert_power(void *obj, void *data, QGuestAllocator *alloc)
> g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
> }
>
> +/* Clearing SHUNT_CAL drops the power alert with the power reading */
> +static void test_alert_power_zero_cal(void *obj, void *data,
> + QGuestAllocator *alloc)
> +{
> + QI2CDevice *dev = (QI2CDevice *)obj;
> +
> + i2c_set16(dev, REG_CONFIG, CONFIG_RST);
> + qmp_ina238_set("shunt-voltage", 97200000);
> + qmp_ina238_set("bus-voltage", 48000000);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0FD2);
> + i2c_set16(dev, REG_PWR_LIMIT, 0x0100);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, DIAG_POL);
> +
> + /* Continuous shunt-only: the power register is not reconverted. */
> + i2c_set16(dev, REG_ADC_CONFIG, 0xA000);
> + i2c_set16(dev, REG_SHUNT_CAL, 0x0000);
> +
> + g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0);
> + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT) & DIAG_POL, ==, 0);
> +}
> +
> /* Two thresholds crossed at once are reported simultaneously (multi-alert) */
> static void test_alert_multi(void *obj, void *data, QGuestAllocator *alloc)
> {
> @@ -733,6 +778,7 @@ static void ina238_register_nodes(void)
> NULL);
> qos_add_test("power", "ina238", test_power, NULL);
> qos_add_test("zero-cal", "ina238", test_zero_cal, NULL);
> + qos_add_test("zero-cal-channels", "ina238", test_zero_cal_channels, NULL);
> qos_add_test("adcrange", "ina238", test_adcrange, NULL);
> qos_add_test("mode-shutdown", "ina238", test_mode_shutdown, NULL);
> qos_add_test("mode-triggered", "ina238", test_mode_triggered, NULL);
> @@ -742,6 +788,8 @@ static void ina238_register_nodes(void)
> qos_add_test("alert-bus", "ina238", test_alert_bus, NULL);
> qos_add_test("alert-temp", "ina238", test_alert_temp, NULL);
> qos_add_test("alert-power", "ina238", test_alert_power, NULL);
> + qos_add_test("alert-power-zero-cal", "ina238", test_alert_power_zero_cal,
> + NULL);
> qos_add_test("alert-multi", "ina238", test_alert_multi, NULL);
> qos_add_test("alert-latch", "ina238", test_alert_latch, NULL);
> qos_add_test("alert-latch-persist", "ina238", test_alert_latch_persist,
>
^ permalink raw reply [flat|nested] 188+ messages in thread
end of thread, other threads:[~2026-10-14 20:13 UTC | newest]
Thread overview: 188+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12 14:31 [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via qemu development
2026-10-12 14:31 ` [PATCH v2 01/50] docs/system/arm/aspeed: add the catalina-bmc machine Emmanuel Blot via qemu development
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-13 6:56 ` 林建明
2026-10-13 16:27 ` Cédric Le Goater
2026-10-12 14:31 ` [PATCH v2 02/50] docs/system/arm/aspeed: add the sanmiguel-bmc machine Emmanuel Blot via qemu development
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-13 6:59 ` 林建明
2026-10-13 16:27 ` Cédric Le Goater
2026-10-12 14:31 ` [PATCH v2 03/50] hw/arm: sanmiguel: remove the second SPI flash controller Emmanuel Blot via qemu development
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-13 7:00 ` 林建明
2026-10-13 16:27 ` Cédric Le Goater
2026-10-12 14:31 ` [PATCH v2 04/50] hw/i2c: pca954x: drop unused header inclusions Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via qemu development
2026-10-13 16:26 ` Cédric Le Goater
2026-10-12 14:31 ` [PATCH v2 05/50] hw/i2c: pca954x: add PCA9545 4-channel mux model Emmanuel Blot via qemu development
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-13 16:26 ` Cédric Le Goater
2026-10-12 14:31 ` [PATCH v2 06/50] hw/i2c: pca954x: migrate mux state Emmanuel Blot via
2026-10-12 14:31 ` Emmanuel Blot via qemu development
2026-10-12 14:31 ` Emmanuel Blot via
2026-10-13 16:26 ` Cédric Le Goater
2026-10-12 14:32 ` [PATCH v2 07/50] tests/qtest: pca9545: add mux test Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-13 16:26 ` Cédric Le Goater
2026-10-12 14:32 ` [PATCH v2 08/50] hw/rtc: ds1338: move TYPE_DS1338 to a public header Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-13 16:26 ` Cédric Le Goater
2026-10-12 14:32 ` [PATCH v2 09/50] hw: ds1338: use the TYPE_DS1338 constant instead of the "ds1338" string Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-13 7:04 ` 林建明
2026-10-13 16:27 ` Cédric Le Goater
2026-10-12 14:32 ` [PATCH v2 10/50] hw/rtc: ds1338: implement Resettable reset Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-13 16:27 ` Cédric Le Goater
2026-10-12 14:32 ` [PATCH v2 11/50] hw/rtc: ds1338: prefix the file-local helpers with ds1338_ Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-13 16:27 ` Cédric Le Goater
2026-10-12 14:32 ` [PATCH v2 12/50] hw/rtc: ds1338: parameterise the register map via a device class Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 13/50] hw/rtc: ds1338: program the clock from the time registers Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` [PATCH v2 14/50] hw/rtc: ds1338: implement the DS1338 clock-halt bit Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 15/50] hw/rtc: ds1338: refresh the user registers on a read rollover only Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 16/50] hw/rtc: ds1338: add a persist-on-reset property Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 17/50] tests/qtest: ds1338: cover the DS1338-specific register map Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 18/50] tests/qtest: ds1338: cover the power-on register defaults Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 19/50] tests/qtest: ds1338: cover the clock-halt bit Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 20/50] tests/qtest: ds1338: cover persist-on-reset Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 21/50] tests/qtest: ds1338: cover the reserved bits of the time registers Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 22/50] hw/rtc: ds1338: add DS1339 variant Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 23/50] tests/qtest: ds1339: add RTC test Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 24/50] tests/qtest: ds1339: cover power-on defaults and reserved-bit masking Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 25/50] tests/qtest: ds1339: cover oscillator stop/restart and time programming Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` [PATCH v2 26/50] tests/qtest: ds1339: cover the Century bit Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 27/50] tests/qtest: ds1339: cover the end-of-month rollover Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 28/50] tests/qtest: ds1339: cover the 12-hour format Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 29/50] tests/qtest: ds1339: cover the day-of-week counter Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` [PATCH v2 30/50] tests/qtest: ds1339: cover persist-on-reset Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` [PATCH v2 31/50] tests/qtest: ds1339: cover the migration round trip Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` [PATCH v2 32/50] hw/sensor: ina230: add current/voltage/power monitor Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:02 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 33/50] tests/qtest: ina230: add current/voltage/power monitor test Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-14 20:04 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 34/50] tests/qtest: ina230: cover voltage injection and power derivation Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:04 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 35/50] tests/qtest: ina230: cover the ALERT limit functions and CVRF Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:05 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 36/50] tests/qtest: ina230: cover the injection-property limits Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:06 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 37/50] tests/qtest: ina230: cover operating modes and triggered conversions Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:01 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 38/50] tests/qtest: ina230: cover ALERT latch persistence Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-14 20:06 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 39/50] tests/qtest: ina230: cover a cleared calibration per channel Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:07 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 40/50] hw/sensor: ina238: add current/voltage/power monitor Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-14 20:08 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 41/50] tests/qtest: ina238: add current/voltage/power monitor test Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:08 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 42/50] tests/qtest: ina238: cover voltage/temperature injection and derivation Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:09 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 43/50] tests/qtest: ina238: cover the ALERT path and conversion-ready flag Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-14 20:09 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 44/50] tests/qtest: ina238: cover the injection-property limits Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:10 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 45/50] tests/qtest: ina238: cover the operating modes Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-14 20:11 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 46/50] tests/qtest: ina238: cover conversion-ready and alert-latch semantics Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:11 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 47/50] tests/qtest: ina238: cover a cleared calibration per channel Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-14 20:12 ` Miles Glenn
2026-10-12 14:32 ` [PATCH v2 48/50] hw/arm: minerva: add Facebook BMC machine Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` [PATCH v2 49/50] hw/arm: minerva: populate EEPROM data Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-13 7:12 ` 林建明
2026-10-12 14:32 ` [PATCH v2 50/50] tests/functional/arm: minerva: add BMC boot test Emmanuel Blot via
2026-10-12 14:32 ` Emmanuel Blot via qemu development
2026-10-12 14:32 ` Emmanuel Blot via
2026-10-13 6:23 ` [PATCH v2 00/50] Aspeed: I2C device models and the Minerva BMC machine Cédric Le Goater
2026-10-13 6:32 ` Jamin Lin
2026-10-13 6:40 ` Cédric Le Goater
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.