From: Titus Rwantare <titusr@google.com>
To: philmd@linaro.org, wuhaotsh@google.com, bhillier@google.com
Cc: qemu-arm@nongnu.org, qemu-devel@nongnu.org, kfting@nuvoton.com,
Titus Rwantare <titusr@google.com>
Subject: [PATCH 2/2] hw/sensor: adm1266: add blackbox read and clear support
Date: Mon, 8 Sep 2025 17:15:57 +0000 [thread overview]
Message-ID: <20250908171557.1510538-2-titusr@google.com> (raw)
In-Reply-To: <20250908171557.1510538-1-titusr@google.com>
This commit adds some support for blackbox reads and clearing,
triggering writes to the blackbox based on the sensor status is not yet
implemented.
Signed-off-by: Titus Rwantare <titusr@google.com>
---
hw/sensor/adm1266.c | 139 ++++++++++++++++++++----------------
include/hw/sensor/adm1266.h | 102 ++++++++++++++++++++++++++
tests/qtest/adm1266-test.c | 90 +++++++++++++++++------
3 files changed, 247 insertions(+), 84 deletions(-)
create mode 100644 include/hw/sensor/adm1266.h
diff --git a/hw/sensor/adm1266.c b/hw/sensor/adm1266.c
index 1112f78d19..e259bb4d67 100644
--- a/hw/sensor/adm1266.c
+++ b/hw/sensor/adm1266.c
@@ -10,77 +10,31 @@
*/
#include "qemu/osdep.h"
-#include "hw/i2c/pmbus_device.h"
+#include "hw/sensor/adm1266.h"
+
#include "hw/irq.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
+#include "qemu/bswap.h"
#include "qemu/log.h"
#include "qemu/module.h"
+#include "qemu/timer.h"
-#define TYPE_ADM1266 "adm1266"
-OBJECT_DECLARE_SIMPLE_TYPE(ADM1266State, ADM1266)
-
-#define ADM1266_BLACKBOX_CONFIG 0xD3
-#define ADM1266_PDIO_CONFIG 0xD4
-#define ADM1266_READ_STATE 0xD9
-#define ADM1266_READ_BLACKBOX 0xDE
-#define ADM1266_SET_RTC 0xDF
-#define ADM1266_GPIO_SYNC_CONFIGURATION 0xE1
-#define ADM1266_BLACKBOX_INFORMATION 0xE6
-#define ADM1266_PDIO_STATUS 0xE9
-#define ADM1266_GPIO_STATUS 0xEA
-
-/* Defaults */
-#define ADM1266_OPERATION_DEFAULT 0x80
-#define ADM1266_CAPABILITY_DEFAULT 0xA0
-#define ADM1266_CAPABILITY_NO_PEC 0x20
-#define ADM1266_PMBUS_REVISION_DEFAULT 0x22
-#define ADM1266_MFR_ID_DEFAULT "ADI"
-#define ADM1266_MFR_ID_DEFAULT_LEN 32
-#define ADM1266_MFR_MODEL_DEFAULT "ADM1266-A1"
-#define ADM1266_MFR_MODEL_DEFAULT_LEN 32
-#define ADM1266_MFR_REVISION_DEFAULT "25"
-#define ADM1266_MFR_REVISION_DEFAULT_LEN 8
-
-#define ADM1266_NUM_PAGES 17
-/**
- * PAGE Index
- * Page 0 VH1.
- * Page 1 VH2.
- * Page 2 VH3.
- * Page 3 VH4.
- * Page 4 VP1.
- * Page 5 VP2.
- * Page 6 VP3.
- * Page 7 VP4.
- * Page 8 VP5.
- * Page 9 VP6.
- * Page 10 VP7.
- * Page 11 VP8.
- * Page 12 VP9.
- * Page 13 VP10.
- * Page 14 VP11.
- * Page 15 VP12.
- * Page 16 VP13.
- */
-typedef struct ADM1266State {
- PMBusDevice parent;
-
- char mfr_id[32];
- char mfr_model[32];
- char mfr_rev[8];
-} ADM1266State;
static const uint8_t adm1266_ic_device_id[] = {0x03, 0x41, 0x12, 0x66};
static const uint8_t adm1266_ic_device_rev[] = {0x08, 0x01, 0x08, 0x07, 0x0,
0x0, 0x07, 0x41, 0x30};
-static const uint8_t adm1266_blackbox_info[] = {
- 0x04, /* length */
- 0x00, 0x00, /* latest ID */
- 0x00, /* latest record index */
- 0x00 /* latest record count */
-};
+
+static void adm1266_clear_blackbox(ADM1266State *s)
+{
+ memset(s->blackbox_records, 0, sizeof(s->blackbox_records));
+ for (int i = 0; i < ADM1266_BLACKBOX_RECORD_NUM_MAX; i++) {
+ s->blackbox_records[i].flags = ADM1266_RECORD_EMPTY;
+ }
+ s->blackbox_info.record_count = 0;
+ s->blackbox_info.logic_index = 0;
+}
static void adm1266_exit_reset(Object *obj, ResetType type)
{
@@ -101,6 +55,18 @@ static void adm1266_exit_reset(Object *obj, ResetType type)
pmdev->pages[i].revision = ADM1266_PMBUS_REVISION_DEFAULT;
}
+ adm1266_clear_blackbox(s);
+ for (int i = 0; i < ADM1266_RECORD_COUNT_DEFAULT; i++) {
+ s->blackbox_records[i].id = cpu_to_le16(i);
+ s->blackbox_records[i].flags = ADM1266_RECORD_USED;
+ s->blackbox_records[i].powerup_counter = cpu_to_le16(i);
+ s->blackbox_records[i].timestamp =
+ cpu_to_le64((qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 1000) << 8);
+ s->blackbox_info.logic_index = i;
+ }
+ s->partial_read = false;
+ s->blackbox_info.record_count = ADM1266_RECORD_COUNT_DEFAULT;
+
strncpy(s->mfr_id, ADM1266_MFR_ID_DEFAULT, 4);
strncpy(s->mfr_model, ADM1266_MFR_MODEL_DEFAULT, 11);
strncpy(s->mfr_rev, ADM1266_MFR_REVISION_DEFAULT, 3);
@@ -109,6 +75,7 @@ static void adm1266_exit_reset(Object *obj, ResetType type)
static uint8_t adm1266_read_byte(PMBusDevice *pmdev)
{
ADM1266State *s = ADM1266(pmdev);
+ uint8_t *raw_record;
switch (pmdev->code) {
case PMBUS_MFR_ID: /* R/W block */
@@ -132,14 +99,29 @@ static uint8_t adm1266_read_byte(PMBusDevice *pmdev)
break;
case ADM1266_BLACKBOX_CONFIG:
+ pmbus_send16(pmdev, s->blackbox_config &
+ ADM1266_BLACKBOX_CONFIG_CYCLIC);
break;
case ADM1266_READ_BLACKBOX:
+ raw_record =
+ (uint8_t *)&s->blackbox_records[s->blackbox_info.logic_index];
+ /* The full black box record exceeds SMBUS_DATA_MAX_LEN */
+ if (!s->partial_read) {
+ s->partial_read = true;
+ } else {
+ raw_record += sizeof(ADM1266BlackboxRecord) / 2;
+ s->partial_read = false;
+ }
+ pmbus_send(pmdev, raw_record,
+ sizeof(ADM1266BlackboxRecord) / 2); /* data */
+ pmbus_send8(pmdev, sizeof(ADM1266BlackboxRecord) / 2); /* len */
break;
case ADM1266_BLACKBOX_INFORMATION:
- pmbus_send(pmdev, adm1266_blackbox_info,
- sizeof(adm1266_blackbox_info));
+ pmbus_send(pmdev, (const uint8_t *)&s->blackbox_info,
+ sizeof(s->blackbox_info)); /* data */
+ pmbus_send8(pmdev, sizeof(ADM1266BlackboxInfo)); /* len */
break;
default:
@@ -156,6 +138,7 @@ static int adm1266_write_data(PMBusDevice *pmdev, const uint8_t *buf,
uint8_t len)
{
ADM1266State *s = ADM1266(pmdev);
+ uint8_t blackbox_request[2];
switch (pmdev->code) {
case PMBUS_MFR_ID: /* R/W block */
@@ -172,8 +155,40 @@ static int adm1266_write_data(PMBusDevice *pmdev, const uint8_t *buf,
break;
case ADM1266_BLACKBOX_CONFIG:
+ s->blackbox_config = pmbus_receive16(pmdev) &
+ ADM1266_BLACKBOX_CONFIG_CYCLIC;
+ break;
+
+ /*
+ * Writes to READ_BLACKBOX come in two forms.
+ * Read requests are a two byte block write, {0x1, record_index}
+ * Clearing records is a three byte block write, {0x2, 0xFE, 0x00}
+ */
case ADM1266_READ_BLACKBOX:
+ blackbox_request[0] = pmbus_receive_block(pmdev,
+ (uint8_t *)&blackbox_request[1],
+ sizeof(blackbox_request));
+ if (blackbox_request[0] == 1) {
+ s->blackbox_info.logic_index = blackbox_request[1];
+ s->partial_read = false;
+ break;
+ }
+ /* erase blackbox records */
+ if (blackbox_request[0] == 2 && blackbox_request[1] == 0xFE) {
+ adm1266_clear_blackbox(s);
+ break;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid write to READ_BLACKBOX: 0x%02x%02x",
+ __func__, blackbox_request[0], blackbox_request[1]);
+ break;
+
case ADM1266_BLACKBOX_INFORMATION:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to read-only register",
+ __func__);
+ break;
+
case ADM1266_SET_RTC: /* do nothing */
break;
diff --git a/include/hw/sensor/adm1266.h b/include/hw/sensor/adm1266.h
new file mode 100644
index 0000000000..67b78e6ffe
--- /dev/null
+++ b/include/hw/sensor/adm1266.h
@@ -0,0 +1,102 @@
+/*
+ * Analog Devices ADM1266 Cascadable Super Sequencer with Margin Control and
+ * Fault Recording with PMBus
+ *
+ * https://www.analog.com/media/en/technical-documentation/data-sheets/adm1266.pdf
+ *
+ * Copyright 2023 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_ADM1266_H
+#define HW_SENSOR_ADM1266_H
+
+#include "hw/i2c/pmbus_device.h"
+
+#define TYPE_ADM1266 "adm1266"
+OBJECT_DECLARE_SIMPLE_TYPE(ADM1266State, ADM1266)
+
+#define ADM1266_BLACKBOX_CONFIG 0xD3
+#define ADM1266_BLACKBOX_CONFIG_CYCLIC 0x01
+#define ADM1266_PDIO_CONFIG 0xD4
+#define ADM1266_READ_STATE 0xD9
+#define ADM1266_READ_BLACKBOX 0xDE
+#define ADM1266_SET_RTC 0xDF
+#define ADM1266_GPIO_SYNC_CONFIGURATION 0xE1
+#define ADM1266_BLACKBOX_INFORMATION 0xE6
+#define ADM1266_PDIO_STATUS 0xE9
+#define ADM1266_GPIO_STATUS 0xEA
+
+/* Defaults */
+#define ADM1266_OPERATION_DEFAULT 0x80
+#define ADM1266_CAPABILITY_DEFAULT 0xA0
+#define ADM1266_CAPABILITY_NO_PEC 0x20
+#define ADM1266_PMBUS_REVISION_DEFAULT 0x22
+#define ADM1266_MFR_ID_DEFAULT "ADI"
+#define ADM1266_MFR_ID_DEFAULT_LEN 32
+#define ADM1266_MFR_MODEL_DEFAULT "ADM1266-A1"
+#define ADM1266_MFR_MODEL_DEFAULT_LEN 32
+#define ADM1266_MFR_REVISION_DEFAULT "25"
+#define ADM1266_MFR_REVISION_DEFAULT_LEN 8
+#define ADM1266_RECORD_COUNT_DEFAULT 8
+
+#define ADM1266_NUM_PAGES 17
+
+typedef struct ADM1266BlackboxInfo {
+#define ADM1266_BLACKBOX_INFO_LEN 4
+ uint8_t id;
+ uint8_t reserved;
+ uint8_t logic_index; /* Index of the last record read/written */
+ uint8_t record_count;
+} QEMU_PACKED ADM1266BlackboxInfo;
+QEMU_BUILD_BUG_ON(sizeof(ADM1266BlackboxInfo) != ADM1266_BLACKBOX_INFO_LEN);
+
+/* All the entries in the blackbox record are little-endian */
+typedef struct ADM1266BlackboxRecord {
+#define ADM1266_BLACKBOX_RECORD_LEN 64
+#define ADM1266_BLACKBOX_RECORD_NUM_MAX 32
+ uint16_t id;
+#define ADM1266_RECORD_USED 0
+#define ADM1266_RECORD_EMPTY 1
+ uint8_t flags;
+ uint8_t action_index;
+ uint8_t rule_index;
+ uint8_t voltage_status; /* Undervoltage or overvoltage status */
+ uint16_t current_state;
+ uint16_t last_state;
+ uint16_t vp_ov_status; /* Overvoltage status of the VPx pins */
+ uint16_t vp_uv_status; /* Undervoltage status of the VPx pins */
+ uint16_t gpio_in_status;
+ uint16_t gpio_out_status;
+ uint16_t pdio_in_status;
+ uint16_t pdio_out_status;
+ uint16_t powerup_counter;
+ uint64_t timestamp;
+ uint8_t reserved[31];
+ uint8_t crc;
+} QEMU_PACKED ADM1266BlackboxRecord;
+QEMU_BUILD_BUG_ON(sizeof(ADM1266BlackboxRecord) != ADM1266_BLACKBOX_RECORD_LEN);
+
+typedef struct ADM1266State {
+ PMBusDevice parent;
+
+ char mfr_id[32];
+ char mfr_model[32];
+ char mfr_rev[8];
+
+ /* Blackbox state */
+ bool partial_read;
+ uint8_t record_index;
+ uint16_t blackbox_config;
+ ADM1266BlackboxInfo blackbox_info;
+ /*
+ * In single mode, the black box can store up to 32 fault records. When
+ * full,no writes are accepted until the records are erased.
+ * In cyclic mode, we still have 32 records, but they are overwritten in a
+ * circular buffer.
+ */
+ ADM1266BlackboxRecord blackbox_records[ADM1266_BLACKBOX_RECORD_NUM_MAX];
+} ADM1266State;
+
+#endif /* HW_SENSOR_ADM1266_H */
diff --git a/tests/qtest/adm1266-test.c b/tests/qtest/adm1266-test.c
index 5ae8206234..04b74b0ccb 100644
--- a/tests/qtest/adm1266-test.c
+++ b/tests/qtest/adm1266-test.c
@@ -8,11 +8,13 @@
*/
#include "qemu/osdep.h"
-#include <math.h>
+#include "hw/sensor/adm1266.h"
+
#include "hw/i2c/pmbus_device.h"
#include "libqtest-single.h"
#include "libqos/qgraph.h"
#include "libqos/i2c.h"
+#include "qemu/bswap.h"
#include "qobject/qdict.h"
#include "qobject/qnum.h"
#include "qemu/bitops.h"
@@ -20,27 +22,6 @@
#define TEST_ID "adm1266-test"
#define TEST_ADDR (0x12)
-#define ADM1266_BLACKBOX_CONFIG 0xD3
-#define ADM1266_PDIO_CONFIG 0xD4
-#define ADM1266_READ_STATE 0xD9
-#define ADM1266_READ_BLACKBOX 0xDE
-#define ADM1266_SET_RTC 0xDF
-#define ADM1266_GPIO_SYNC_CONFIGURATION 0xE1
-#define ADM1266_BLACKBOX_INFORMATION 0xE6
-#define ADM1266_PDIO_STATUS 0xE9
-#define ADM1266_GPIO_STATUS 0xEA
-
-/* Defaults */
-#define ADM1266_OPERATION_DEFAULT 0x80
-#define ADM1266_CAPABILITY_DEFAULT 0xA0
-#define ADM1266_CAPABILITY_NO_PEC 0x20
-#define ADM1266_PMBUS_REVISION_DEFAULT 0x22
-#define ADM1266_MFR_ID_DEFAULT "ADI"
-#define ADM1266_MFR_ID_DEFAULT_LEN 32
-#define ADM1266_MFR_MODEL_DEFAULT "ADM1266-A1"
-#define ADM1266_MFR_MODEL_DEFAULT_LEN 32
-#define ADM1266_MFR_REVISION_DEFAULT "25"
-#define ADM1266_MFR_REVISION_DEFAULT_LEN 8
#define TEST_STRING_A "a sample"
#define TEST_STRING_B "b sample"
#define TEST_STRING_C "rev c"
@@ -105,6 +86,70 @@ static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
sizeof(TEST_STRING_C));
}
+static void read_blackbox(QI2CDevice *i2cdev, ADM1266BlackboxRecord *record,
+ uint8_t index)
+{
+ /* record is sent in two halves + len */
+ uint8_t buf[2][(sizeof(ADM1266BlackboxRecord) / 2) + 1];
+ uint8_t read_request[] = {0x1, index};
+
+ i2c_write_block(i2cdev, ADM1266_READ_BLACKBOX, read_request,
+ sizeof(read_request));
+ i2c_read_block(i2cdev, ADM1266_READ_BLACKBOX, buf[0], sizeof(buf[0]));
+ i2c_read_block(i2cdev, ADM1266_READ_BLACKBOX, buf[1], sizeof(buf[1]));
+ /* verify lengths */
+ g_assert_cmpuint(buf[0][0] + buf[1][0], ==, sizeof(ADM1266BlackboxRecord));
+
+ memcpy(record, &buf[0][1], sizeof(buf[0]) - 1);
+ /* all the bytes in the second half are unused except the last one */
+ record->crc = buf[1][sizeof(buf[1]) - 1];
+}
+
+/* test blackbox records */
+static void test_blackbox(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t bb_info_buf[5];
+ ADM1266BlackboxRecord blackbox_record;
+ ADM1266BlackboxInfo *blackbox_info =
+ (ADM1266BlackboxInfo *)(bb_info_buf + 1);
+ uint8_t clear_request[] = {0x2, 0xFE, 0x00};
+ uint8_t final_record;
+
+ /* First, the blackbox shouldn't be empty, read blackbox information */
+ i2c_read_block(i2cdev, ADM1266_BLACKBOX_INFORMATION, bb_info_buf,
+ sizeof(bb_info_buf));
+ g_assert_cmpuint(bb_info_buf[0], ==, ADM1266_BLACKBOX_INFO_LEN);
+ g_assert_cmpuint(blackbox_info->record_count, >, 0);
+ g_assert_cmpuint(blackbox_info->logic_index, <,
+ blackbox_info->record_count);
+
+ /* Get one of the records and check its index */
+ /* Read final record */
+ final_record = blackbox_info->record_count - 1;
+ read_blackbox(i2cdev, &blackbox_record, final_record);
+
+ /* Verify record ID matches what we expect */
+ g_assert_cmpuint(le16_to_cpu(blackbox_record.id), ==, final_record);
+
+ /* flags should be USED initially */
+ g_assert_cmpuint(blackbox_record.flags, ==, ADM1266_RECORD_USED);
+
+ /* Test clearing blackbox records */
+ i2c_write_block(i2cdev, ADM1266_READ_BLACKBOX, clear_request,
+ sizeof(clear_request));
+
+ /* Check info, record count should now be zero */
+ i2c_read_block(i2cdev, ADM1266_BLACKBOX_INFORMATION, bb_info_buf,
+ sizeof(bb_info_buf));
+ g_assert_cmpuint(bb_info_buf[0], ==, ADM1266_BLACKBOX_INFO_LEN);
+ g_assert_cmpuint(blackbox_info->record_count, ==, 0);
+
+ /* Read the same record again to verify the record is flagged as empty */
+ read_blackbox(i2cdev, &blackbox_record, final_record);
+ g_assert_cmpuint(blackbox_record.flags, ==, ADM1266_RECORD_EMPTY);
+}
+
static void adm1266_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -117,6 +162,7 @@ static void adm1266_register_nodes(void)
qos_add_test("test_defaults", "adm1266", test_defaults, NULL);
qos_add_test("test_rw_regs", "adm1266", test_rw_regs, NULL);
+ qos_add_test("test_blackbox", "adm1266", test_blackbox, NULL);
}
libqos_init(adm1266_register_nodes);
--
2.51.0.384.g4c02a37b29-goog
prev parent reply other threads:[~2025-09-08 17:17 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-08 17:15 [PATCH 1/2] hw/sensor: add stub for ADM1266 blackbox Titus Rwantare
2025-09-08 17:15 ` Titus Rwantare [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250908171557.1510538-2-titusr@google.com \
--to=titusr@google.com \
--cc=bhillier@google.com \
--cc=kfting@nuvoton.com \
--cc=philmd@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=wuhaotsh@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).