qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
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



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