Coverage Report

Created: 2026-07-16 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-oprom-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2022 Richard Hughes <richard@hughsie.com>
3
 * Copyright 2022 Intel
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 */
7
8
0
#define G_LOG_DOMAIN "FuFirmware"
9
10
#include "config.h"
11
12
#include <string.h>
13
14
#include "fu-byte-array.h"
15
#include "fu-common.h"
16
#include "fu-ifwi-cpd-firmware.h"
17
#include "fu-input-stream.h"
18
#include "fu-oprom-firmware.h"
19
#include "fu-string.h"
20
21
/**
22
 * FuOpromFirmware:
23
 *
24
 * An OptionROM can be found in nearly every PCI device. Multiple OptionROM images may be appended.
25
 *
26
 * See also: [class@FuFirmware]
27
 */
28
29
typedef struct {
30
  FuOpromMachineType machine_type;
31
  FuOpromSubsystem subsystem;
32
  FuOpromCompressionType compression_type;
33
  guint16 vendor_id;
34
  guint16 device_id;
35
} FuOpromFirmwarePrivate;
36
37
2.37k
G_DEFINE_TYPE_WITH_PRIVATE(FuOpromFirmware, fu_oprom_firmware, FU_TYPE_FIRMWARE)
38
2.37k
#define GET_PRIVATE(o) (fu_oprom_firmware_get_instance_private(o))
39
40
2.13k
#define FU_OPROM_FIRMWARE_ALIGN_LEN 512u
41
42
/**
43
 * fu_oprom_firmware_get_machine_type:
44
 * @self: a #FuFirmware
45
 *
46
 * Gets the machine type.
47
 *
48
 * Returns: a #FuOpromMachineType
49
 *
50
 * Since: 1.8.2
51
 **/
52
FuOpromMachineType
53
fu_oprom_firmware_get_machine_type(FuOpromFirmware *self)
54
0
{
55
0
  FuOpromFirmwarePrivate *priv = GET_PRIVATE(self);
56
0
  g_return_val_if_fail(FU_IS_OPROM_FIRMWARE(self), G_MAXUINT16);
57
0
  return priv->machine_type;
58
0
}
59
60
/**
61
 * fu_oprom_firmware_get_subsystem:
62
 * @self: a #FuFirmware
63
 *
64
 * Gets the machine type.
65
 *
66
 * Returns: a #FuOpromSubsystem
67
 *
68
 * Since: 1.8.2
69
 **/
70
FuOpromSubsystem
71
fu_oprom_firmware_get_subsystem(FuOpromFirmware *self)
72
0
{
73
0
  FuOpromFirmwarePrivate *priv = GET_PRIVATE(self);
74
0
  g_return_val_if_fail(FU_IS_OPROM_FIRMWARE(self), G_MAXUINT16);
75
0
  return priv->subsystem;
76
0
}
77
78
/**
79
 * fu_oprom_firmware_get_compression_type:
80
 * @self: a #FuFirmware
81
 *
82
 * Gets the machine type.
83
 *
84
 * Returns: a #FuOpromCompressionType
85
 *
86
 * Since: 1.8.2
87
 **/
88
FuOpromCompressionType
89
fu_oprom_firmware_get_compression_type(FuOpromFirmware *self)
90
0
{
91
0
  FuOpromFirmwarePrivate *priv = GET_PRIVATE(self);
92
0
  g_return_val_if_fail(FU_IS_OPROM_FIRMWARE(self), G_MAXUINT16);
93
0
  return priv->compression_type;
94
0
}
95
96
static void
97
fu_oprom_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
98
0
{
99
0
  FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware);
100
0
  FuOpromFirmwarePrivate *priv = GET_PRIVATE(self);
101
0
  fu_xmlb_builder_insert_kx(bn, "machine_type", priv->machine_type);
102
0
  fu_xmlb_builder_insert_kx(bn, "subsystem", priv->subsystem);
103
0
  fu_xmlb_builder_insert_kx(bn, "compression_type", priv->compression_type);
104
0
  fu_xmlb_builder_insert_kx(bn, "vendor_id", priv->vendor_id);
105
0
  fu_xmlb_builder_insert_kx(bn, "device_id", priv->device_id);
106
0
}
107
108
static gboolean
109
fu_oprom_firmware_validate(FuFirmware *firmware,
110
         FuInputStream *stream,
111
         gsize offset,
112
         GError **error)
113
857
{
114
857
  return fu_struct_oprom_validate_stream(stream, offset, error);
115
857
}
116
117
static gboolean
118
fu_oprom_firmware_parse(FuFirmware *firmware,
119
      FuInputStream *stream,
120
      FuFirmwareParseFlags flags,
121
      GError **error)
122
805
{
123
805
  FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware);
124
805
  FuOpromFirmwarePrivate *priv = GET_PRIVATE(self);
125
805
  guint16 expansion_header_offset = 0;
126
805
  guint16 pci_header_offset;
127
805
  guint16 image_length = 0;
128
805
  gsize image_size = 0;
129
805
  g_autoptr(FuStructOprom) st_hdr = NULL;
130
805
  g_autoptr(FuStructOpromPci) st_pci = NULL;
131
132
  /* parse header */
133
805
  st_hdr = fu_struct_oprom_parse_stream(stream, 0x0, error);
134
805
  if (st_hdr == NULL)
135
0
    return FALSE;
136
805
  priv->subsystem = fu_struct_oprom_get_subsystem(st_hdr);
137
805
  priv->compression_type = fu_struct_oprom_get_compression_type(st_hdr);
138
805
  priv->machine_type = fu_struct_oprom_get_machine_type(st_hdr);
139
140
  /* get PCI offset */
141
805
  pci_header_offset = fu_struct_oprom_get_pci_header_offset(st_hdr);
142
805
  if (pci_header_offset == 0x0) {
143
1
    g_set_error_literal(error,
144
1
            FWUPD_ERROR,
145
1
            FWUPD_ERROR_INVALID_DATA,
146
1
            "no PCI data structure offset provided");
147
1
    return FALSE;
148
1
  }
149
150
  /* verify signature */
151
804
  st_pci = fu_struct_oprom_pci_parse_stream(stream, pci_header_offset, error);
152
804
  if (st_pci == NULL)
153
81
    return FALSE;
154
723
  priv->vendor_id = fu_struct_oprom_pci_get_vendor_id(st_pci);
155
723
  priv->device_id = fu_struct_oprom_pci_get_device_id(st_pci);
156
157
  /* get length */
158
723
  image_length = fu_struct_oprom_pci_get_image_length(st_pci);
159
723
  if (image_length == 0x0) {
160
1
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "invalid image");
161
1
    return FALSE;
162
1
  }
163
722
  if (!fu_size_checked_inc_product(&image_size,
164
722
           image_length,
165
722
           FU_OPROM_FIRMWARE_ALIGN_LEN,
166
722
           error)) {
167
0
    g_prefix_error_literal(error, "image size overflow: ");
168
0
    return FALSE;
169
0
  }
170
722
  fu_firmware_set_size(firmware, image_size);
171
722
  fu_firmware_set_idx(firmware, fu_struct_oprom_pci_get_code_type(st_pci));
172
173
  /* is last image */
174
722
  if (fu_struct_oprom_pci_get_indicator(st_pci) & FU_OPROM_INDICATOR_FLAG_LAST)
175
269
    fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_IS_LAST_IMAGE);
176
177
  /* get CPD offset */
178
722
  expansion_header_offset = fu_struct_oprom_get_expansion_header_offset(st_hdr);
179
722
  if (expansion_header_offset != 0x0) {
180
721
    g_autoptr(FuFirmware) img = NULL;
181
721
    img = fu_firmware_new_from_gtypes(stream,
182
721
              expansion_header_offset,
183
721
              flags,
184
721
              error,
185
721
              FU_TYPE_IFWI_CPD_FIRMWARE,
186
721
              FU_TYPE_FIRMWARE,
187
721
              G_TYPE_INVALID);
188
721
    if (img == NULL) {
189
16
      g_prefix_error_literal(error, "failed to build firmware: ");
190
16
      return FALSE;
191
16
    }
192
705
    fu_firmware_set_id(img, "cpd");
193
705
    fu_firmware_set_offset(img, expansion_header_offset);
194
705
    if (!fu_firmware_add_image(firmware, img, error))
195
0
      return FALSE;
196
705
  }
197
198
  /* success */
199
706
  return TRUE;
200
722
}
201
202
static GByteArray *
203
fu_oprom_firmware_write(FuFirmware *firmware, GError **error)
204
706
{
205
706
  FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware);
206
706
  FuOpromFirmwarePrivate *priv = GET_PRIVATE(self);
207
706
  gsize image_size = 0;
208
706
  g_autoptr(GByteArray) buf = g_byte_array_new();
209
706
  g_autoptr(FuStructOprom) st_hdr = fu_struct_oprom_new();
210
706
  g_autoptr(FuStructOpromPci) st_pci = fu_struct_oprom_pci_new();
211
706
  g_autoptr(GBytes) blob_cpd = NULL;
212
213
  /* the smallest each image (and header) can be is 512 bytes */
214
706
  image_size = fu_common_align_up(st_hdr->buf->len, FU_FIRMWARE_ALIGNMENT_512);
215
706
  blob_cpd = fu_firmware_get_image_by_id_bytes(firmware, "cpd", NULL);
216
706
  if (blob_cpd != NULL) {
217
1
    gsize cpd_size =
218
1
        fu_common_align_up(g_bytes_get_size(blob_cpd), FU_FIRMWARE_ALIGNMENT_512);
219
1
    if (!fu_size_checked_inc(&image_size, cpd_size, error)) {
220
0
      g_prefix_error_literal(error, "overflow calculating OptionROM size: ");
221
0
      return NULL;
222
0
    }
223
1
  }
224
225
  /* write the header */
226
706
  fu_struct_oprom_set_image_size(st_hdr, image_size / FU_OPROM_FIRMWARE_ALIGN_LEN);
227
706
  fu_struct_oprom_set_subsystem(st_hdr, priv->subsystem);
228
706
  fu_struct_oprom_set_machine_type(st_hdr, priv->machine_type);
229
706
  fu_struct_oprom_set_compression_type(st_hdr, priv->compression_type);
230
706
  if (blob_cpd != NULL) {
231
1
    fu_struct_oprom_set_expansion_header_offset(st_hdr,
232
1
                  image_size -
233
1
                FU_OPROM_FIRMWARE_ALIGN_LEN);
234
1
  }
235
706
  g_byte_array_append(buf, st_hdr->buf->data, st_hdr->buf->len);
236
237
  /* add PCI section */
238
706
  fu_struct_oprom_pci_set_vendor_id(st_pci, priv->vendor_id);
239
706
  fu_struct_oprom_pci_set_device_id(st_pci, priv->device_id);
240
706
  fu_struct_oprom_pci_set_image_length(st_pci, image_size / FU_OPROM_FIRMWARE_ALIGN_LEN);
241
706
  fu_struct_oprom_pci_set_code_type(st_pci, fu_firmware_get_idx(firmware));
242
706
  if (fu_firmware_has_flag(firmware, FU_FIRMWARE_FLAG_IS_LAST_IMAGE))
243
262
    fu_struct_oprom_pci_set_indicator(st_pci, FU_OPROM_INDICATOR_FLAG_LAST);
244
706
  fu_byte_array_append_array(buf, st_pci->buf);
245
706
  fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_512, 0xFF);
246
247
  /* add CPD */
248
706
  if (blob_cpd != NULL) {
249
1
    fu_byte_array_append_bytes(buf, blob_cpd);
250
1
    fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_512, 0xFF);
251
1
  }
252
253
  /* success */
254
706
  return g_steal_pointer(&buf);
255
706
}
256
257
static gboolean
258
fu_oprom_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
259
0
{
260
0
  FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware);
261
0
  FuOpromFirmwarePrivate *priv = GET_PRIVATE(self);
262
0
  const gchar *tmp;
263
264
  /* simple properties */
265
0
  tmp = xb_node_query_text(n, "machine_type", NULL);
266
0
  if (tmp != NULL) {
267
0
    guint64 val = 0;
268
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
269
0
      return FALSE;
270
0
    priv->machine_type = val;
271
0
  }
272
0
  tmp = xb_node_query_text(n, "subsystem", NULL);
273
0
  if (tmp != NULL) {
274
0
    guint64 val = 0;
275
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
276
0
      return FALSE;
277
0
    priv->subsystem = val;
278
0
  }
279
0
  tmp = xb_node_query_text(n, "compression_type", NULL);
280
0
  if (tmp != NULL) {
281
0
    guint64 val = 0;
282
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
283
0
      return FALSE;
284
0
    priv->compression_type = val;
285
0
  }
286
0
  tmp = xb_node_query_text(n, "vendor_id", NULL);
287
0
  if (tmp != NULL) {
288
0
    guint64 val = 0;
289
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
290
0
      return FALSE;
291
0
    priv->vendor_id = val;
292
0
  }
293
0
  tmp = xb_node_query_text(n, "device_id", NULL);
294
0
  if (tmp != NULL) {
295
0
    guint64 val = 0;
296
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
297
0
      return FALSE;
298
0
    priv->device_id = val;
299
0
  }
300
301
  /* success */
302
0
  return TRUE;
303
0
}
304
305
static void
306
fu_oprom_firmware_init(FuOpromFirmware *self)
307
857
{
308
857
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE);
309
857
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_ALLOW_LINEAR);
310
857
}
311
312
static void
313
fu_oprom_firmware_class_init(FuOpromFirmwareClass *klass)
314
1
{
315
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
316
1
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_FIRMWARE);
317
1
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_IFWI_CPD_FIRMWARE);
318
1
  fu_firmware_set_size_max(firmware_class, 128 * FU_MB);
319
1
  firmware_class->validate = fu_oprom_firmware_validate;
320
1
  firmware_class->export = fu_oprom_firmware_export;
321
1
  firmware_class->parse = fu_oprom_firmware_parse;
322
1
  firmware_class->write = fu_oprom_firmware_write;
323
1
  firmware_class->build = fu_oprom_firmware_build;
324
1
}
325
326
/**
327
 * fu_oprom_firmware_new:
328
 *
329
 * Creates a new #FuFirmware of OptionROM format
330
 *
331
 * Since: 1.8.2
332
 **/
333
FuFirmware *
334
fu_oprom_firmware_new(void)
335
0
{
336
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_OPROM_FIRMWARE, NULL));
337
0
}