Coverage Report

Created: 2025-07-01 07:09

/src/fwupd/plugins/acpi-phat/fu-acpi-phat.c
Line
Count
Source (jump to first uncovered line)
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
G_DEFINE_TYPE(FuAcpiPhat, fu_acpi_phat, FU_TYPE_FIRMWARE)
22
23
static void
24
fu_acpi_phat_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
25
0
{
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(FuFirmware *firmware,
33
        GInputStream *stream,
34
        gsize *offset,
35
        FuFirmwareParseFlags flags,
36
        GError **error)
37
0
{
38
0
  guint16 record_length = 0;
39
0
  guint16 record_type = 0;
40
0
  guint8 revision;
41
0
  g_autoptr(FuFirmware) firmware_rcd = NULL;
42
43
  /* common header */
44
0
  if (!fu_input_stream_read_u16(stream, *offset, &record_type, G_LITTLE_ENDIAN, error))
45
0
    return FALSE;
46
0
  if (!fu_input_stream_read_u16(stream, *offset + 2, &record_length, G_LITTLE_ENDIAN, error))
47
0
    return FALSE;
48
0
  if (record_length < 5) {
49
0
    g_set_error(error,
50
0
          FWUPD_ERROR,
51
0
          FWUPD_ERROR_NOT_SUPPORTED,
52
0
          "PHAT record length invalid, got 0x%x",
53
0
          record_length);
54
0
    return FALSE;
55
0
  }
56
0
  if (!fu_input_stream_read_u8(stream, *offset + 4, &revision, error))
57
0
    return FALSE;
58
59
  /* firmware version data record */
60
0
  if (record_type == FU_ACPI_PHAT_RECORD_TYPE_VERSION) {
61
0
    firmware_rcd = fu_acpi_phat_version_record_new();
62
0
  } else if (record_type == FU_ACPI_PHAT_RECORD_TYPE_HEALTH) {
63
0
    firmware_rcd = fu_acpi_phat_health_record_new();
64
0
  }
65
66
  /* supported record type */
67
0
  if (firmware_rcd != NULL) {
68
0
    g_autoptr(GInputStream) partial_stream = NULL;
69
0
    partial_stream = fu_partial_input_stream_new(stream, *offset, record_length, error);
70
0
    if (partial_stream == NULL)
71
0
      return FALSE;
72
0
    fu_firmware_set_size(firmware_rcd, record_length);
73
0
    fu_firmware_set_offset(firmware_rcd, *offset);
74
0
    fu_firmware_set_version_raw(firmware_rcd, revision);
75
0
    if (!fu_firmware_parse_stream(firmware_rcd, partial_stream, 0x0, flags, error))
76
0
      return FALSE;
77
0
    if (!fu_firmware_add_image_full(firmware, firmware_rcd, error))
78
0
      return FALSE;
79
0
  }
80
81
0
  *offset += record_length;
82
0
  return TRUE;
83
0
}
84
85
static void
86
fu_acpi_phat_set_oem_id(FuAcpiPhat *self, const gchar *oem_id)
87
0
{
88
0
  g_free(self->oem_id);
89
0
  self->oem_id = g_strdup(oem_id);
90
0
}
91
92
static gboolean
93
fu_acpi_phat_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
94
0
{
95
0
  return fu_struct_acpi_phat_hdr_validate_stream(stream, offset, error);
96
0
}
97
98
static gboolean
99
fu_acpi_phat_parse(FuFirmware *firmware,
100
       GInputStream *stream,
101
       FuFirmwareParseFlags flags,
102
       GError **error)
103
0
{
104
0
  FuAcpiPhat *self = FU_ACPI_PHAT(firmware);
105
0
  gchar oem_id[6] = {'\0'};
106
0
  gchar oem_table_id[8] = {'\0'};
107
0
  gsize streamsz = 0;
108
0
  guint32 length = 0;
109
0
  guint32 oem_revision = 0;
110
0
  g_autofree gchar *oem_id_safe = NULL;
111
0
  g_autofree gchar *oem_table_id_safe = NULL;
112
113
  /* parse table */
114
0
  if (!fu_input_stream_size(stream, &streamsz, error))
115
0
    return FALSE;
116
0
  if (!fu_input_stream_read_u32(stream, 4, &length, G_LITTLE_ENDIAN, error))
117
0
    return FALSE;
118
0
  if (streamsz < length) {
119
0
    g_set_error(error,
120
0
          FWUPD_ERROR,
121
0
          FWUPD_ERROR_NOT_SUPPORTED,
122
0
          "PHAT table invalid size, got 0x%x, expected 0x%x",
123
0
          (guint)streamsz,
124
0
          length);
125
0
    return FALSE;
126
0
  }
127
128
  /* spec revision */
129
0
  if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
130
0
    guint8 revision = 0;
131
0
    if (!fu_input_stream_read_u8(stream, 8, &revision, error))
132
0
      return FALSE;
133
0
    if (revision != FU_ACPI_PHAT_REVISION) {
134
0
      g_set_error(error,
135
0
            FWUPD_ERROR,
136
0
            FWUPD_ERROR_NOT_SUPPORTED,
137
0
            "PHAT table revision invalid, got 0x%x, expected 0x%x",
138
0
            revision,
139
0
            (guint)FU_ACPI_PHAT_REVISION);
140
0
      return FALSE;
141
0
    }
142
0
  }
143
144
  /* verify checksum */
145
0
  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
0
  if (!fu_input_stream_read_safe(stream,
165
0
               (guint8 *)oem_id,
166
0
               sizeof(oem_id),
167
0
               0x0, /* dst */
168
0
               10,  /* src */
169
0
               sizeof(oem_id),
170
0
               error))
171
0
    return FALSE;
172
0
  oem_id_safe = fu_strsafe((const gchar *)oem_id, sizeof(oem_id));
173
0
  fu_acpi_phat_set_oem_id(self, oem_id_safe);
174
175
  /* OEM Table ID */
176
0
  if (!fu_input_stream_read_safe(stream,
177
0
               (guint8 *)oem_table_id,
178
0
               sizeof(oem_table_id),
179
0
               0x0, /* dst */
180
0
               16,  /* src */
181
0
               sizeof(oem_table_id),
182
0
               error))
183
0
    return FALSE;
184
0
  oem_table_id_safe = fu_strsafe((const gchar *)oem_table_id, sizeof(oem_table_id));
185
0
  fu_firmware_set_id(firmware, oem_table_id_safe);
186
0
  if (!fu_input_stream_read_u32(stream, 24, &oem_revision, G_LITTLE_ENDIAN, error))
187
0
    return FALSE;
188
0
  fu_firmware_set_version_raw(firmware, oem_revision);
189
190
  /* platform telemetry records */
191
0
  for (gsize offset_tmp = 36; offset_tmp < length;) {
192
0
    if (!fu_acpi_phat_record_parse(firmware, stream, &offset_tmp, flags, error))
193
0
      return FALSE;
194
0
  }
195
196
  /* success */
197
0
  return TRUE;
198
0
}
199
200
static GByteArray *
201
fu_acpi_phat_write(FuFirmware *firmware, GError **error)
202
0
{
203
0
  FuAcpiPhat *self = FU_ACPI_PHAT(firmware);
204
0
  const gchar *oem_table_id_str = fu_firmware_get_id(firmware);
205
0
  guint8 creator_id[] = {'F', 'W', 'U', 'P'};
206
0
  guint8 creator_rev[] = {'0', '0', '0', '0'};
207
0
  guint8 oem_id[6] = {'\0'};
208
0
  guint8 oem_table_id[8] = {'\0'};
209
0
  guint8 signature[] = {'P', 'H', 'A', 'T'};
210
0
  g_autoptr(GByteArray) buf = g_byte_array_new();
211
0
  g_autoptr(GByteArray) buf2 = g_byte_array_new();
212
0
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
213
214
  /* write each image so we get the total size */
215
0
  for (guint i = 0; i < images->len; i++) {
216
0
    FuFirmware *img = g_ptr_array_index(images, i);
217
0
    g_autoptr(GBytes) blob = fu_firmware_write(img, error);
218
0
    if (blob == NULL)
219
0
      return NULL;
220
0
    fu_byte_array_append_bytes(buf2, blob);
221
0
  }
222
223
  /* header */
224
0
  g_byte_array_append(buf, signature, sizeof(signature));
225
0
  fu_byte_array_append_uint32(buf, buf2->len + 36, G_LITTLE_ENDIAN);
226
0
  fu_byte_array_append_uint8(buf, fu_firmware_get_version_raw(firmware));
227
0
  fu_byte_array_append_uint8(buf, 0xFF); /* will fixup */
228
0
  if (self->oem_id != NULL) {
229
0
    gsize oem_id_strlen = strlen(self->oem_id);
230
0
    if (!fu_memcpy_safe(oem_id,
231
0
            sizeof(oem_id),
232
0
            0x0, /* dst */
233
0
            (const guint8 *)self->oem_id,
234
0
            oem_id_strlen,
235
0
            0x0, /* src */
236
0
            oem_id_strlen,
237
0
            error))
238
0
      return NULL;
239
0
  }
240
0
  g_byte_array_append(buf, oem_id, sizeof(oem_id));
241
0
  if (oem_table_id_str != NULL) {
242
0
    gsize oem_table_id_strlen = strlen(oem_table_id_str);
243
0
    if (!fu_memcpy_safe(oem_table_id,
244
0
            sizeof(oem_table_id),
245
0
            0x0, /* dst */
246
0
            (const guint8 *)oem_table_id_str,
247
0
            oem_table_id_strlen,
248
0
            0x0, /* src */
249
0
            oem_table_id_strlen,
250
0
            error))
251
0
      return NULL;
252
0
  }
253
0
  g_byte_array_append(buf, oem_table_id, sizeof(oem_table_id));
254
0
  fu_byte_array_append_uint32(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN);
255
0
  g_byte_array_append(buf, creator_id, sizeof(creator_id));
256
0
  g_byte_array_append(buf, creator_rev, sizeof(creator_rev));
257
0
  g_byte_array_append(buf, buf2->data, buf2->len);
258
259
  /* fixup checksum */
260
0
  buf->data[9] = 0xFF - fu_sum8(buf->data, buf->len);
261
262
  /* success */
263
0
  return g_steal_pointer(&buf);
264
0
}
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
0
{
311
0
  fu_firmware_set_images_max(FU_FIRMWARE(self), 2000);
312
0
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
313
0
  g_type_ensure(FU_TYPE_ACPI_PHAT_HEALTH_RECORD);
314
0
  g_type_ensure(FU_TYPE_ACPI_PHAT_VERSION_RECORD);
315
0
}
316
317
static void
318
fu_acpi_phat_finalize(GObject *object)
319
0
{
320
0
  FuAcpiPhat *self = FU_ACPI_PHAT(object);
321
0
  g_free(self->oem_id);
322
0
  G_OBJECT_CLASS(fu_acpi_phat_parent_class)->finalize(object);
323
0
}
324
325
static void
326
fu_acpi_phat_class_init(FuAcpiPhatClass *klass)
327
0
{
328
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
329
0
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
330
0
  object_class->finalize = fu_acpi_phat_finalize;
331
0
  firmware_class->validate = fu_acpi_phat_validate;
332
0
  firmware_class->parse = fu_acpi_phat_parse;
333
0
  firmware_class->write = fu_acpi_phat_write;
334
0
  firmware_class->export = fu_acpi_phat_export;
335
0
  firmware_class->build = fu_acpi_phat_build;
336
0
}
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
}