Coverage Report

Created: 2026-06-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-acpi-table.c
Line
Count
Source
1
/*
2
 * Copyright 2023 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include "fu-acpi-table-struct.h"
10
#include "fu-acpi-table.h"
11
#include "fu-byte-array.h"
12
#include "fu-common.h"
13
#include "fu-input-stream.h"
14
#include "fu-partial-input-stream.h"
15
#include "fu-sum.h"
16
17
/**
18
 * FuAcpiTable:
19
 *
20
 * An generic ACPI table.
21
 *
22
 * See also: [class@FuFirmware]
23
 */
24
25
typedef struct {
26
  guint8 revision;
27
  gchar *oem_id;
28
  gchar *oem_table_id;
29
  guint32 oem_revision;
30
  GInputStream *payload;
31
} FuAcpiTablePrivate;
32
33
0
G_DEFINE_TYPE_WITH_PRIVATE(FuAcpiTable, fu_acpi_table, FU_TYPE_FIRMWARE)
34
0
35
0
#define GET_PRIVATE(o) (fu_acpi_table_get_instance_private(o))
36
37
static void
38
fu_acpi_table_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
39
0
{
40
0
  FuAcpiTable *self = FU_ACPI_TABLE(firmware);
41
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
42
0
  fu_xmlb_builder_insert_kx(bn, "revision", priv->revision);
43
0
  fu_xmlb_builder_insert_kv(bn, "oem_id", priv->oem_id);
44
0
  fu_xmlb_builder_insert_kv(bn, "oem_table_id", priv->oem_table_id);
45
0
  fu_xmlb_builder_insert_kx(bn, "oem_revision", priv->oem_revision);
46
0
}
47
48
/**
49
 * fu_acpi_table_get_revision:
50
 * @self: a #FuAcpiTable
51
 *
52
 * Gets the revision of the table.
53
 *
54
 * Returns: integer, default 0x0
55
 *
56
 * Since: 1.8.11
57
 **/
58
guint8
59
fu_acpi_table_get_revision(FuAcpiTable *self)
60
0
{
61
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
62
0
  g_return_val_if_fail(FU_IS_ACPI_TABLE(self), G_MAXUINT8);
63
0
  return priv->revision;
64
0
}
65
66
/**
67
 * fu_acpi_table_get_oem_id:
68
 * @self: a #FuAcpiTable
69
 *
70
 * Gets an optional OEM ID.
71
 *
72
 * Returns: a string, or %NULL
73
 *
74
 * Since: 1.8.11
75
 **/
76
const gchar *
77
fu_acpi_table_get_oem_id(FuAcpiTable *self)
78
0
{
79
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
80
0
  g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL);
81
0
  return priv->oem_id;
82
0
}
83
84
/**
85
 * fu_acpi_table_get_oem_table_id:
86
 * @self: a #FuAcpiTable
87
 *
88
 * Gets an optional OEM table ID.
89
 *
90
 * Returns: a string, or %NULL
91
 *
92
 * Since: 1.8.11
93
 **/
94
const gchar *
95
fu_acpi_table_get_oem_table_id(FuAcpiTable *self)
96
0
{
97
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
98
0
  g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL);
99
0
  return priv->oem_table_id;
100
0
}
101
102
/**
103
 * fu_acpi_table_get_oem_revision:
104
 * @self: a #FuAcpiTable
105
 *
106
 * Gets the OEM revision.
107
 *
108
 * Returns: integer, default 0x0
109
 *
110
 * Since: 1.8.11
111
 **/
112
guint32
113
fu_acpi_table_get_oem_revision(FuAcpiTable *self)
114
0
{
115
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
116
0
  g_return_val_if_fail(FU_IS_ACPI_TABLE(self), G_MAXUINT32);
117
0
  return priv->oem_revision;
118
0
}
119
120
/**
121
 * fu_acpi_table_get_payload:
122
 * @self: a #FuAcpiTable
123
 *
124
 * Gets the payload after the ACPI header. This function will fail if there is no payload.
125
 *
126
 * Returns: (transfer full): a #GInputStream, or %NULL on error
127
 *
128
 * Since: 2.1.3
129
 **/
130
GInputStream *
131
fu_acpi_table_get_payload(FuAcpiTable *self, GError **error)
132
0
{
133
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
134
0
  g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL);
135
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
136
0
  if (priv->payload == NULL) {
137
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "no payload");
138
0
    return NULL;
139
0
  }
140
0
  return g_object_ref(priv->payload);
141
0
}
142
143
static gboolean
144
fu_acpi_table_parse(FuFirmware *firmware,
145
        GInputStream *stream,
146
        FuFirmwareParseFlags flags,
147
        GError **error)
148
0
{
149
0
  FuAcpiTable *self = FU_ACPI_TABLE(firmware);
150
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
151
0
  gsize streamsz = 0;
152
0
  guint32 length;
153
0
  g_autofree gchar *id = NULL;
154
0
  g_autoptr(FuStructAcpiTable) st = NULL;
155
156
  /* parse */
157
0
  st = fu_struct_acpi_table_parse_stream(stream, 0x0, error);
158
0
  if (st == NULL)
159
0
    return FALSE;
160
0
  id = fu_struct_acpi_table_get_signature(st);
161
0
  fu_firmware_set_id(FU_FIRMWARE(self), id);
162
0
  priv->revision = fu_struct_acpi_table_get_revision(st);
163
0
  priv->oem_id = fu_struct_acpi_table_get_oem_id(st);
164
0
  priv->oem_table_id = fu_struct_acpi_table_get_oem_table_id(st);
165
0
  priv->oem_revision = fu_struct_acpi_table_get_oem_revision(st);
166
167
  /* length */
168
0
  length = fu_struct_acpi_table_get_length(st);
169
0
  if (!fu_input_stream_size(stream, &streamsz, error))
170
0
    return FALSE;
171
0
  if (length > streamsz || length < st->buf->len) {
172
0
    g_set_error(error,
173
0
          FWUPD_ERROR,
174
0
          FWUPD_ERROR_INVALID_DATA,
175
0
          "table length not valid: got 0x%x but expected 0x%x",
176
0
          (guint)streamsz,
177
0
          (guint)length);
178
0
    return FALSE;
179
0
  }
180
0
  fu_firmware_set_size(firmware, length);
181
182
  /* checksum */
183
0
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
184
0
    guint8 checksum_actual = 0;
185
0
    if (!fu_input_stream_compute_sum8(stream, &checksum_actual, error))
186
0
      return FALSE;
187
0
    if (checksum_actual != 0x0) {
188
0
      guint8 checksum = fu_struct_acpi_table_get_checksum(st);
189
0
      g_set_error(error,
190
0
            FWUPD_ERROR,
191
0
            FWUPD_ERROR_INTERNAL,
192
0
            "CRC failed, expected 0x%02x, got 0x%02x",
193
0
            (guint)checksum - checksum_actual,
194
0
            checksum);
195
0
      return FALSE;
196
0
    }
197
0
  }
198
199
  /* optional payload */
200
0
  if ((flags & FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM) > 0 &&
201
0
      length != FU_STRUCT_ACPI_TABLE_SIZE) {
202
0
    priv->payload = fu_partial_input_stream_new(stream,
203
0
                  FU_STRUCT_ACPI_TABLE_SIZE,
204
0
                  length - FU_STRUCT_ACPI_TABLE_SIZE,
205
0
                  error);
206
0
    if (priv->payload == NULL)
207
0
      return FALSE;
208
0
  }
209
210
  /* success */
211
0
  return TRUE;
212
0
}
213
214
static GByteArray *
215
fu_acpi_table_write(FuFirmware *firmware, GError **error)
216
0
{
217
0
  FuAcpiTable *self = FU_ACPI_TABLE(firmware);
218
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
219
0
  g_autoptr(FuStructAcpiTable) st = fu_struct_acpi_table_new();
220
0
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
221
0
  g_autoptr(GBytes) payload_blob = NULL;
222
223
  /* generate from images if required */
224
0
  if (images->len > 0) {
225
0
    g_autoptr(GByteArray) payload = g_byte_array_new();
226
0
    for (guint i = 0; i < images->len; i++) {
227
0
      FuFirmware *img = g_ptr_array_index(images, i);
228
0
      g_autoptr(GBytes) fw = fu_firmware_write(img, error);
229
0
      if (fw == NULL)
230
0
        return NULL;
231
0
      fu_byte_array_append_bytes(payload, fw);
232
0
    }
233
0
    payload_blob = g_byte_array_free_to_bytes(g_steal_pointer(&payload));
234
0
  } else if (priv->payload != NULL) {
235
0
    payload_blob =
236
0
        fu_input_stream_read_bytes(priv->payload, 0x0, G_MAXSIZE, NULL, error);
237
0
    if (payload_blob == NULL)
238
0
      return NULL;
239
0
  } else {
240
0
    payload_blob = g_bytes_new(NULL, 0);
241
0
  }
242
243
  /* pack */
244
0
  if (!fu_struct_acpi_table_set_signature(st, fu_firmware_get_id(firmware), error))
245
0
    return NULL;
246
0
  fu_struct_acpi_table_set_length(st,
247
0
          FU_STRUCT_ACPI_TABLE_SIZE + g_bytes_get_size(payload_blob));
248
0
  fu_struct_acpi_table_set_revision(st, priv->revision);
249
0
  if (!fu_struct_acpi_table_set_oem_id(st, priv->oem_id, error))
250
0
    return NULL;
251
0
  if (!fu_struct_acpi_table_set_oem_table_id(st, priv->oem_table_id, error))
252
0
    return NULL;
253
0
  fu_struct_acpi_table_set_oem_revision(st, priv->oem_revision);
254
0
  if (!fu_struct_acpi_table_set_creator_id(st, "FWPD", error))
255
0
    return NULL;
256
0
  fu_struct_acpi_table_set_creator_revision(st, 0x1);
257
258
  /* payload */
259
0
  fu_byte_array_append_bytes(st->buf, payload_blob);
260
261
  /* fixup checksum */
262
0
  fu_struct_acpi_table_set_checksum(st, 0x100 - fu_sum8(st->buf->data, st->buf->len));
263
264
  /* success */
265
0
  return g_steal_pointer(&st->buf);
266
0
}
267
268
static void
269
fu_acpi_table_init(FuAcpiTable *self)
270
0
{
271
0
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
272
0
  fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_MB);
273
0
  fu_firmware_set_images_max(FU_FIRMWARE(self), 2000);
274
0
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE);
275
0
}
276
277
static void
278
fu_acpi_table_finalize(GObject *object)
279
0
{
280
0
  FuAcpiTable *self = FU_ACPI_TABLE(object);
281
0
  FuAcpiTablePrivate *priv = GET_PRIVATE(self);
282
283
0
  if (priv->payload != NULL)
284
0
    g_object_unref(priv->payload);
285
0
  g_free(priv->oem_table_id);
286
0
  g_free(priv->oem_id);
287
0
  G_OBJECT_CLASS(fu_acpi_table_parent_class)->finalize(object);
288
0
}
289
290
static void
291
fu_acpi_table_class_init(FuAcpiTableClass *klass)
292
0
{
293
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
294
0
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
295
0
  object_class->finalize = fu_acpi_table_finalize;
296
0
  firmware_class->parse = fu_acpi_table_parse;
297
0
  firmware_class->export = fu_acpi_table_export;
298
0
  firmware_class->write = fu_acpi_table_write;
299
0
}
300
301
/**
302
 * fu_acpi_table_new:
303
 *
304
 * Creates a new #FuFirmware
305
 *
306
 * Since: 1.8.11
307
 **/
308
FuFirmware *
309
fu_acpi_table_new(void)
310
0
{
311
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_TABLE, NULL));
312
0
}