The register form the stream carries only resolves a sixteenth of a
degree, so a temperature set through the property does not survive a
round trip unless it happens to fall on that grid.

Add a subsection holding the measurement itself, sent only when the
register form cannot reproduce it. A stream from a sensor holding an
expressible value -- the usual case, boards preset whole degrees --
therefore stays loadable by a QEMU that knows nothing of it.

Signed-off-by: Emmanuel Blot <[email protected]>
---
 hw/sensor/tmp421.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 58 insertions(+), 2 deletions(-)

diff --git a/hw/sensor/tmp421.c b/hw/sensor/tmp421.c
index 1e061045cb..e391bb20e1 100644
--- a/hw/sensor/tmp421.c
+++ b/hw/sensor/tmp421.c
@@ -59,6 +59,7 @@ struct TMP421State {
 
     /* migration only: the register form the stream carries */
     int16_t temperature[4];
+    bool temp_mc_loaded;
 
     uint8_t status;
     uint8_t config[2];
@@ -379,6 +380,56 @@ static const VMStateDescription vmstate_tmp421_n_correct = 
{
     }
 };
 
+/*
+ * That form only resolves a sixteenth of a degree, so the measurement itself
+ * travels in a subsection -- sent only when the register form cannot
+ * reproduce it, which leaves the usual stream loadable by a QEMU that does
+ * not know the subsection.
+ */
+static bool tmp421_temp_mc_needed(void *opaque)
+{
+    TMP421State *s = opaque;
+    bool ext_range = s->config[0] & TMP421_CONFIG_RANGE;
+    int32_t offset = ext_range ? TMP421_EXT_OFFSET : 0;
+
+    /*
+     * The register form reproduces a measurement when the conversion divides
+     * exactly and the encoder had nothing to clamp. Asking the decoder
+     * instead would settle the question with the function whose result the
+     * subsection exists to protect, and hide any error it has.
+     */
+    for (unsigned channel = 0; channel < ARRAY_SIZE(s->temp_mc); channel++) {
+        int32_t scaled = s->temp_mc[channel] * 256;
+
+        if (scaled % 1000 ||
+            (uint16_t)(scaled / 1000 + offset) != tmp421_encode(s, channel)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static int tmp421_temp_mc_post_load(void *opaque, int version_id)
+{
+    TMP421State *s = opaque;
+
+    s->temp_mc_loaded = true;
+    return 0;
+}
+
+static const VMStateDescription vmstate_tmp421_temp_mc = {
+    .name = "TMP421/temperature-mc",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = tmp421_temp_mc_needed,
+    .post_load = tmp421_temp_mc_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_INT32_ARRAY(temp_mc, TMP421State, 4),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
 /*
  * The stream carries the register form of the measurement: that is what a
  * QEMU predating the millidegree state writes, and what it expects back.
@@ -398,9 +449,13 @@ static int tmp421_post_load(void *opaque, int version_id)
 {
     TMP421State *s = opaque;
 
-    for (unsigned channel = 0; channel < ARRAY_SIZE(s->temp_mc); channel++) {
-        s->temp_mc[channel] = tmp421_decode(s, s->temperature[channel]);
+    if (!s->temp_mc_loaded) {
+        for (unsigned channel = 0; channel < ARRAY_SIZE(s->temp_mc);
+             channel++) {
+            s->temp_mc[channel] = tmp421_decode(s, s->temperature[channel]);
+        }
     }
+    s->temp_mc_loaded = false;
 
     return 0;
 }
@@ -423,6 +478,7 @@ static const VMStateDescription vmstate_tmp421 = {
         VMSTATE_END_OF_LIST()
     },
     .subsections = (const VMStateDescription * const []) {
+        &vmstate_tmp421_temp_mc,
         &vmstate_tmp421_n_correct,
         NULL
     }

-- 
2.53.0-Meta


Reply via email to