Coverage Report

Created: 2026-03-11 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/plugins/acpi-phat/fu-acpi-phat.c
Line
Count
Source
1
/*
2
 * Copyright 2021 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include <string.h>
10
11
#include "fu-acpi-phat-health-record.h"
12
#include "fu-acpi-phat-struct.h"
13
#include "fu-acpi-phat-version-record.h"
14
#include "fu-acpi-phat.h"
15
16
struct _FuAcpiPhat {
17
  FuFirmware parent_instance;
18
  gchar *oem_id;
19
};
20
21
3.68k
G_DEFINE_TYPE(FuAcpiPhat, fu_acpi_phat, FU_TYPE_FIRMWARE)
22
3.68k
23
3.68k
static void
24
3.68k
fu_acpi_phat_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
25
3.68k
{
26
0
  FuAcpiPhat *self = FU_ACPI_PHAT(firmware);
27
0
  if (self->oem_id != NULL)
28
0
    fu_xmlb_builder_insert_kv(bn, "oem_id", self->oem_id);
29
0
}
30
31
static gboolean
32
fu_acpi_phat_record_parse(FuAcpiPhat *self,
33
        GInputStream *stream,
34
        gsize *offset,
35
        FuFirmwareParseFlags flags,
36
        GError **error)
37
19.1k
{
38
19.1k
  guint16 record_length = 0;
39
19.1k
  guint16 record_type = 0;
40
19.1k
  guint8 revision;
41
19.1k
  g_autoptr(FuFirmware) firmware_rcd = NULL;
42
43
  /* common header */
44
19.1k
  if (!fu_input_stream_read_u16(stream, *offset, &record_type, G_LITTLE_ENDIAN, error))
45
11
    return FALSE;
46
19.1k
  if (!fu_input_stream_read_u16(stream, *offset + 2, &record_length, G_LITTLE_ENDIAN, error))
47
10
    return FALSE;
48
19.1k
  if (record_length < 5) {
49
14
    g_set_error(error,
50
14
          FWUPD_ERROR,
51
14
          FWUPD_ERROR_NOT_SUPPORTED,
52
14
          "PHAT record length invalid, got 0x%x",
53
14
          record_length);
54
14
    return FALSE;
55
14
  }
56
19.1k
  if (!fu_input_stream_read_u8(stream, *offset + 4, &revision, error))
57
17
    return FALSE;
58
59
  /* firmware version data record */
60
19.1k
  if (record_type == FU_ACPI_PHAT_RECORD_TYPE_VERSION) {
61
16.1k
    firmware_rcd = fu_acpi_phat_version_record_new();
62
16.1k
  } else if (record_type == FU_ACPI_PHAT_RECORD_TYPE_HEALTH) {
63
1.58k
    firmware_rcd = fu_acpi_phat_health_record_new();
64
1.58k
  }
65
66
  /* supported record type */
67
19.1k
  if (firmware_rcd != NULL) {
68
17.7k
    g_autoptr(GInputStream) partial_stream = NULL;
69
17.7k
    partial_stream = fu_partial_input_stream_new(stream, *offset, record_length, error);
70
17.7k
    if (partial_stream == NULL)
71
53
      return FALSE;
72
17.6k
    fu_firmware_set_size(firmware_rcd, record_length);
73
17.6k
    fu_firmware_set_offset(firmware_rcd, *offset);
74
17.6k
    fu_firmware_set_version_raw(firmware_rcd, revision);
75
17.6k
    if (!fu_firmware_parse_stream(firmware_rcd, partial_stream, 0x0, flags, error))
76
356
      return FALSE;
77
17.3k
    if (!fu_firmware_add_image(FU_FIRMWARE(self), firmware_rcd, error))
78
1
      return FALSE;
79
17.3k
  }
80
81
18.7k
  *offset += record_length;
82
18.7k
  return TRUE;
83
19.1k
}
84
85
static void
86
fu_acpi_phat_set_oem_id(FuAcpiPhat *self, const gchar *oem_id)
87
967
{
88
967
  g_free(self->oem_id);
89
967
  self->oem_id = g_strdup(oem_id);
90
967
}
91
92
static gboolean
93
fu_acpi_phat_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
94
1.08k
{
95
1.08k
  return fu_struct_acpi_phat_hdr_validate_stream(stream, offset, error);
96
1.08k
}
97
98
static gboolean
99
fu_acpi_phat_parse(FuFirmware *firmware,
100
       GInputStream *stream,
101
       FuFirmwareParseFlags flags,
102
       GError **error)
103
1.02k
{
104
1.02k
  FuAcpiPhat *self = FU_ACPI_PHAT(firmware);
105
1.02k
  gchar oem_id[6] = {'\0'};
106
1.02k
  gchar oem_table_id[8] = {'\0'};
107
1.02k
  gsize streamsz = 0;
108
1.02k
  guint32 length = 0;
109
1.02k
  guint32 oem_revision = 0;
110
1.02k
  g_autofree gchar *oem_id_safe = NULL;
111
1.02k
  g_autofree gchar *oem_table_id_safe = NULL;
112
113
  /* parse table */
114
1.02k
  if (!fu_input_stream_size(stream, &streamsz, error))
115
0
    return FALSE;
116
1.02k
  if (!fu_input_stream_read_u32(stream, 4, &length, G_LITTLE_ENDIAN, error))
117
3
    return FALSE;
118
1.02k
  if (streamsz < length) {
119
35
    g_set_error(error,
120
35
          FWUPD_ERROR,
121
35
          FWUPD_ERROR_NOT_SUPPORTED,
122
35
          "PHAT table invalid size, got 0x%x, expected 0x%x",
123
35
          (guint)streamsz,
124
35
          length);
125
35
    return FALSE;
126
35
  }
127
128
  /* spec revision */
129
991
  if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
130
991
    guint8 revision = 0;
131
991
    if (!fu_input_stream_read_u8(stream, 8, &revision, error))
132
3
      return FALSE;
133
988
    if (revision != FU_ACPI_PHAT_REVISION) {
134
17
      g_set_error(error,
135
17
            FWUPD_ERROR,
136
17
            FWUPD_ERROR_NOT_SUPPORTED,
137
17
            "PHAT table revision invalid, got 0x%x, expected 0x%x",
138
17
            revision,
139
17
            (guint)FU_ACPI_PHAT_REVISION);
140
17
      return FALSE;
141
17
    }
142
988
  }
143
144
  /* verify checksum */
145
971
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
146
0
    guint8 checksum = 0;
147
0
    g_autoptr(GInputStream) stream_tmp =
148
0
        fu_partial_input_stream_new(stream, 0, length, error);
149
0
    if (stream_tmp == NULL)
150
0
      return FALSE;
151
0
    if (!fu_input_stream_compute_sum8(stream_tmp, &checksum, error))
152
0
      return FALSE;
153
0
    if (checksum != 0x00) {
154
0
      g_set_error(error,
155
0
            FWUPD_ERROR,
156
0
            FWUPD_ERROR_NOT_SUPPORTED,
157
0
            "PHAT table checksum invalid, got 0x%x",
158
0
            checksum);
159
0
      return FALSE;
160
0
    }
161
0
  }
162
163
  /* OEMID */
164
971
  if (!fu_input_stream_read_safe(stream,
165
971
               (guint8 *)oem_id,
166
971
               sizeof(oem_id),
167
971
               0x0, /* dst */
168
971
               10,  /* src */
169
971
               sizeof(oem_id),
170
971
               error))
171
4
    return FALSE;
172
967
  oem_id_safe = fu_strsafe((const gchar *)oem_id, sizeof(oem_id));
173
967
  fu_acpi_phat_set_oem_id(self, oem_id_safe);
174
175
  /* OEM Table ID */
176
967
  if (!fu_input_stream_read_safe(stream,
177
967
               (guint8 *)oem_table_id,
178
967
               sizeof(oem_table_id),
179
967
               0x0, /* dst */
180
967
               16,  /* src */
181
967
               sizeof(oem_table_id),
182
967
               error))
183
5
    return FALSE;
184
962
  oem_table_id_safe = fu_strsafe((const gchar *)oem_table_id, sizeof(oem_table_id));
185
962
  fu_firmware_set_id(firmware, oem_table_id_safe);
186
962
  if (!fu_input_stream_read_u32(stream, 24, &oem_revision, G_LITTLE_ENDIAN, error))
187
6
    return FALSE;
188
956
  fu_firmware_set_version_raw(firmware, oem_revision);
189
190
  /* platform telemetry records */
191
19.6k
  for (gsize offset_tmp = 36; offset_tmp < length;) {
192
19.1k
    if (!fu_acpi_phat_record_parse(self, stream, &offset_tmp, flags, error))
193
462
      return FALSE;
194
19.1k
  }
195
196
  /* success */
197
494
  return TRUE;
198
956
}
199
200
static GByteArray *
201
fu_acpi_phat_write(FuFirmware *firmware, GError **error)
202
494
{
203
494
  FuAcpiPhat *self = FU_ACPI_PHAT(firmware);
204
494
  const gchar *oem_table_id_str = fu_firmware_get_id(firmware);
205
494
  guint8 creator_id[] = {'F', 'W', 'U', 'P'};
206
494
  guint8 creator_rev[] = {'0', '0', '0', '0'};
207
494
  guint8 oem_id[6] = {'\0'};
208
494
  guint8 oem_table_id[8] = {'\0'};
209
494
  guint8 signature[] = {'P', 'H', 'A', 'T'};
210
494
  g_autoptr(GByteArray) buf = g_byte_array_new();
211
494
  g_autoptr(GByteArray) buf2 = g_byte_array_new();
212
494
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
213
214
  /* write each image so we get the total size */
215
10.3k
  for (guint i = 0; i < images->len; i++) {
216
9.84k
    FuFirmware *img = g_ptr_array_index(images, i);
217
9.84k
    g_autoptr(GBytes) blob = fu_firmware_write(img, error);
218
9.84k
    if (blob == NULL)
219
0
      return NULL;
220
9.84k
    fu_byte_array_append_bytes(buf2, blob);
221
9.84k
  }
222
223
  /* header */
224
494
  g_byte_array_append(buf, signature, sizeof(signature));
225
494
  fu_byte_array_append_uint32(buf, buf2->len + 36, G_LITTLE_ENDIAN);
226
494
  fu_byte_array_append_uint8(buf, fu_firmware_get_version_raw(firmware));
227
494
  fu_byte_array_append_uint8(buf, 0xFF); /* will fixup */
228
494
  if (self->oem_id != NULL) {
229
276
    gsize oem_id_strlen = strlen(self->oem_id);
230
276
    if (!fu_memcpy_safe(oem_id,
231
276
            sizeof(oem_id),
232
276
            0x0, /* dst */
233
276
            (const guint8 *)self->oem_id,
234
276
            oem_id_strlen,
235
276
            0x0, /* src */
236
276
            oem_id_strlen,
237
276
            error))
238
0
      return NULL;
239
276
  }
240
494
  g_byte_array_append(buf, oem_id, sizeof(oem_id));
241
494
  if (oem_table_id_str != NULL) {
242
233
    gsize oem_table_id_strlen = strlen(oem_table_id_str);
243
233
    if (!fu_memcpy_safe(oem_table_id,
244
233
            sizeof(oem_table_id),
245
233
            0x0, /* dst */
246
233
            (const guint8 *)oem_table_id_str,
247
233
            oem_table_id_strlen,
248
233
            0x0, /* src */
249
233
            oem_table_id_strlen,
250
233
            error))
251
0
      return NULL;
252
233
  }
253
494
  g_byte_array_append(buf, oem_table_id, sizeof(oem_table_id));
254
494
  fu_byte_array_append_uint32(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN);
255
494
  g_byte_array_append(buf, creator_id, sizeof(creator_id));
256
494
  g_byte_array_append(buf, creator_rev, sizeof(creator_rev));
257
494
  g_byte_array_append(buf, buf2->data, buf2->len);
258
259
  /* fixup checksum */
260
494
  buf->data[9] = 0xFF - fu_sum8(buf->data, buf->len);
261
262
  /* success */
263
494
  return g_steal_pointer(&buf);
264
494
}
265
266
static gboolean
267
fu_acpi_phat_build(FuFirmware *firmware, XbNode *n, GError **error)
268
0
{
269
0
  FuAcpiPhat *self = FU_ACPI_PHAT(firmware);
270
0
  const gchar *tmp;
271
272
  /* optional properties */
273
0
  tmp = xb_node_query_text(n, "oem_id", NULL);
274
0
  if (tmp != NULL)
275
0
    fu_acpi_phat_set_oem_id(self, tmp);
276
277
  /* success */
278
0
  return TRUE;
279
0
}
280
281
static gboolean
282
fu_acpi_phat_to_report_string_cb(XbBuilderNode *bn, gpointer user_data)
283
0
{
284
0
  if (g_strcmp0(xb_builder_node_get_element(bn), "offset") == 0 ||
285
0
      g_strcmp0(xb_builder_node_get_element(bn), "flags") == 0 ||
286
0
      g_strcmp0(xb_builder_node_get_element(bn), "size") == 0)
287
0
    xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE);
288
0
  return FALSE;
289
0
}
290
291
gchar *
292
fu_acpi_phat_to_report_string(FuAcpiPhat *self)
293
0
{
294
0
  g_autoptr(XbBuilderNode) bn = xb_builder_node_new("firmware");
295
0
  fu_firmware_export(FU_FIRMWARE(self), FU_FIRMWARE_EXPORT_FLAG_NONE, bn);
296
0
  xb_builder_node_traverse(bn,
297
0
         G_PRE_ORDER,
298
0
         G_TRAVERSE_ALL,
299
0
         3,
300
0
         fu_acpi_phat_to_report_string_cb,
301
0
         NULL);
302
0
  return xb_builder_node_export(bn,
303
0
              XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE |
304
0
            XB_NODE_EXPORT_FLAG_FORMAT_INDENT,
305
0
              NULL);
306
0
}
307
308
static void
309
fu_acpi_phat_init(FuAcpiPhat *self)
310
1.08k
{
311
1.08k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 2000);
312
1.08k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
313
1.08k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_ACPI_PHAT_HEALTH_RECORD);
314
1.08k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_ACPI_PHAT_VERSION_RECORD);
315
1.08k
}
316
317
static void
318
fu_acpi_phat_finalize(GObject *object)
319
1.08k
{
320
1.08k
  FuAcpiPhat *self = FU_ACPI_PHAT(object);
321
1.08k
  g_free(self->oem_id);
322
1.08k
  G_OBJECT_CLASS(fu_acpi_phat_parent_class)->finalize(object);
323
1.08k
}
324
325
static void
326
fu_acpi_phat_class_init(FuAcpiPhatClass *klass)
327
1
{
328
1
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
329
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
330
1
  object_class->finalize = fu_acpi_phat_finalize;
331
1
  firmware_class->validate = fu_acpi_phat_validate;
332
1
  firmware_class->parse = fu_acpi_phat_parse;
333
1
  firmware_class->write = fu_acpi_phat_write;
334
1
  firmware_class->export = fu_acpi_phat_export;
335
1
  firmware_class->build = fu_acpi_phat_build;
336
1
}
337
338
FuFirmware *
339
fu_acpi_phat_new(void)
340
0
{
341
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_PHAT, NULL));
342
0
}