Reviewed-by: Glenn Miles <[email protected]>

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 <[email protected]>
> ---
>  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',
> 


Reply via email to