Coverage Report

Created: 2026-01-09 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-intel-thunderbolt-nvm.c
Line
Count
Source
1
/*
2
 * Copyright 2021 Dell Inc.
3
 * Copyright 2020 Richard Hughes <richard@hughsie.com>
4
 * Copyright 2020 Mario Limonciello <mario.limonciello@dell.com>
5
 * Copyright 2017 Intel Corporation.
6
 *
7
 * SPDX-License-Identifier: LGPL-2.1-or-later
8
 */
9
10
0
#define G_LOG_DOMAIN "FuFirmware"
11
12
#include "config.h"
13
14
#include "fu-byte-array.h"
15
#include "fu-common.h"
16
#include "fu-input-stream.h"
17
#include "fu-intel-thunderbolt-nvm.h"
18
#include "fu-intel-thunderbolt-struct.h"
19
#include "fu-string.h"
20
#include "fu-version-common.h"
21
22
/**
23
 * FuIntelThunderboltNvm:
24
 *
25
 * The Non-Volatile-Memory device specification. This is what you would find on the device SPI chip.
26
 *
27
 * See also: [class@FuFirmware]
28
 */
29
30
typedef struct {
31
  guint32 sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_LAST];
32
  FuIntelThunderboltNvmFamily family;
33
  gboolean is_host;
34
  gboolean is_native;
35
  gboolean has_pd;
36
  guint16 vendor_id;
37
  guint16 device_id;
38
  guint16 model_id;
39
  guint gen;
40
  guint ports;
41
  guint8 flash_size;
42
} FuIntelThunderboltNvmPrivate;
43
44
497
G_DEFINE_TYPE_WITH_PRIVATE(FuIntelThunderboltNvm, fu_intel_thunderbolt_nvm, FU_TYPE_FIRMWARE)
45
1.53k
#define GET_PRIVATE(o) (fu_intel_thunderbolt_nvm_get_instance_private(o))
46
47
/**
48
 * fu_intel_thunderbolt_nvm_get_vendor_id:
49
 * @self: a #FuFirmware
50
 *
51
 * Gets the vendor ID.
52
 *
53
 * Returns: an integer, or 0x0 for unset
54
 *
55
 * Since: 1.8.5
56
 **/
57
guint16
58
fu_intel_thunderbolt_nvm_get_vendor_id(FuIntelThunderboltNvm *self)
59
0
{
60
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
61
0
  g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), G_MAXUINT16);
62
0
  return priv->vendor_id;
63
0
}
64
65
/**
66
 * fu_intel_thunderbolt_nvm_get_device_id:
67
 * @self: a #FuFirmware
68
 *
69
 * Gets the device ID.
70
 *
71
 * Returns: an integer, or 0x0 for unset
72
 *
73
 * Since: 1.8.5
74
 **/
75
guint16
76
fu_intel_thunderbolt_nvm_get_device_id(FuIntelThunderboltNvm *self)
77
0
{
78
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
79
0
  return priv->device_id;
80
0
}
81
82
/**
83
 * fu_intel_thunderbolt_nvm_is_host:
84
 * @self: a #FuFirmware
85
 *
86
 * Gets if the firmware is designed for a host controller rather than a device.
87
 *
88
 * Returns: %TRUE for controller, %FALSE for device
89
 *
90
 * Since: 1.8.5
91
 **/
92
gboolean
93
fu_intel_thunderbolt_nvm_is_host(FuIntelThunderboltNvm *self)
94
0
{
95
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
96
0
  g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE);
97
0
  return priv->is_host;
98
0
}
99
100
/**
101
 * fu_intel_thunderbolt_nvm_is_native:
102
 * @self: a #FuFirmware
103
 *
104
 * Gets if the device is native, i.e. not in recovery mode.
105
 *
106
 * Returns: %TRUE if set
107
 *
108
 * Since: 1.8.5
109
 **/
110
gboolean
111
fu_intel_thunderbolt_nvm_is_native(FuIntelThunderboltNvm *self)
112
0
{
113
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
114
0
  g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE);
115
0
  return priv->is_native;
116
0
}
117
118
/**
119
 * fu_intel_thunderbolt_nvm_has_pd:
120
 * @self: a #FuFirmware
121
 *
122
 * Gets if the device has power delivery capability.
123
 *
124
 * Returns: %TRUE if set
125
 *
126
 * Since: 1.8.5
127
 **/
128
gboolean
129
fu_intel_thunderbolt_nvm_has_pd(FuIntelThunderboltNvm *self)
130
0
{
131
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
132
0
  g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE);
133
0
  return priv->has_pd;
134
0
}
135
136
/**
137
 * fu_intel_thunderbolt_nvm_get_model_id:
138
 * @self: a #FuFirmware
139
 *
140
 * Gets the model ID.
141
 *
142
 * Returns: an integer, or 0x0 for unset
143
 *
144
 * Since: 1.8.5
145
 **/
146
guint16
147
fu_intel_thunderbolt_nvm_get_model_id(FuIntelThunderboltNvm *self)
148
0
{
149
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
150
0
  g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), 0x0);
151
0
  return priv->model_id;
152
0
}
153
154
/**
155
 * fu_intel_thunderbolt_nvm_get_flash_size:
156
 * @self: a #FuFirmware
157
 *
158
 * Gets the flash size.
159
 *
160
 * NOTE: This does not correspond to a size in bytes, or a power of 2 and is only useful for
161
 * comparison between firmware and device.
162
 *
163
 * Returns: an integer, or 0x0 for unset
164
 *
165
 * Since: 1.8.5
166
 **/
167
guint8
168
fu_intel_thunderbolt_nvm_get_flash_size(FuIntelThunderboltNvm *self)
169
0
{
170
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
171
0
  g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), 0x0);
172
0
  return priv->flash_size;
173
0
}
174
175
static void
176
fu_intel_thunderbolt_nvm_export(FuFirmware *firmware,
177
        FuFirmwareExportFlags flags,
178
        XbBuilderNode *bn)
179
0
{
180
0
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
181
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
182
0
  fu_xmlb_builder_insert_kx(bn, "vendor_id", priv->vendor_id);
183
0
  fu_xmlb_builder_insert_kx(bn, "device_id", priv->device_id);
184
0
  fu_xmlb_builder_insert_kx(bn, "model_id", priv->model_id);
185
0
  fu_xmlb_builder_insert_kv(bn,
186
0
          "family",
187
0
          fu_intel_thunderbolt_nvm_family_to_string(priv->family));
188
0
  fu_xmlb_builder_insert_kb(bn, "is_host", priv->is_host);
189
0
  fu_xmlb_builder_insert_kb(bn, "is_native", priv->is_native);
190
0
  fu_xmlb_builder_insert_kx(bn, "flash_size", priv->flash_size);
191
0
  fu_xmlb_builder_insert_kx(bn, "generation", priv->gen);
192
0
  fu_xmlb_builder_insert_kx(bn, "ports", priv->ports);
193
0
  fu_xmlb_builder_insert_kb(bn, "has_pd", priv->has_pd);
194
0
  for (guint i = 0; i < FU_INTEL_THUNDERBOLT_NVM_SECTION_LAST; i++) {
195
0
    if (priv->sections[i] != 0x0) {
196
0
      g_autofree gchar *tmp = g_strdup_printf("0x%x", priv->sections[i]);
197
0
      g_autoptr(XbBuilderNode) bc =
198
0
          xb_builder_node_insert(bn,
199
0
               "section",
200
0
               "type",
201
0
               fu_intel_thunderbolt_nvm_section_to_string(i),
202
0
               "offset",
203
0
               tmp,
204
0
               NULL);
205
0
      g_return_if_fail(bc != NULL);
206
0
    }
207
0
  }
208
0
}
209
210
static inline gboolean
211
fu_intel_thunderbolt_nvm_valid_pd_pointer(guint32 pointer)
212
68
{
213
68
  return pointer != 0 && pointer != 0xFFFFFFFF;
214
68
}
215
216
/*
217
 * Size of ucode sections is uint16 value saved at the start of the section,
218
 * it's in DWORDS (4-bytes) units and it doesn't include itself. We need the
219
 * offset to the next section, so we translate it to bytes and add 2 for the
220
 * size field itself.
221
 *
222
 * offset parameter must be relative to digital section
223
 */
224
static gboolean
225
fu_intel_thunderbolt_nvm_read_ucode_section_len(FuIntelThunderboltNvm *self,
226
            GInputStream *stream,
227
            guint32 offset,
228
            guint16 *value,
229
            GError **error)
230
471
{
231
471
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
232
471
  if (!fu_input_stream_read_u16(stream,
233
471
              priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
234
471
            offset,
235
471
              value,
236
471
              G_LITTLE_ENDIAN,
237
471
              error)) {
238
12
    g_prefix_error_literal(error, "failed to read ucode section len: ");
239
12
    return FALSE;
240
12
  }
241
459
  *value *= sizeof(guint32);
242
459
  *value += sizeof(guint16);
243
459
  return TRUE;
244
471
}
245
246
/* assumes sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL].offset is already set */
247
static gboolean
248
fu_intel_thunderbolt_nvm_read_sections(FuIntelThunderboltNvm *self,
249
               GInputStream *stream,
250
               GError **error)
251
301
{
252
301
  guint32 offset;
253
301
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
254
255
301
  if (priv->gen >= 3 || priv->gen == 0) {
256
252
    if (!fu_input_stream_read_u32(
257
252
      stream,
258
252
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
259
252
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM,
260
252
      &offset,
261
252
      G_LITTLE_ENDIAN,
262
252
      error))
263
17
      return FALSE;
264
235
    priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] =
265
235
        offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL];
266
267
235
    if (!fu_input_stream_read_u32(
268
235
      stream,
269
235
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
270
235
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS,
271
235
      &offset,
272
235
      G_LITTLE_ENDIAN,
273
235
      error))
274
0
      return FALSE;
275
235
    priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] =
276
235
        offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL];
277
235
  }
278
279
284
  if (priv->is_host && priv->gen > 2) {
280
    /*
281
     * To find the DRAM section, we have to jump from section to
282
     * section in a chain of sections.
283
     * available_sections location tells what sections exist at all
284
     * (with a flag per section).
285
     * ee_ucode_start_addr location tells the offset of the first
286
     * section in the list relatively to the digital section start.
287
     * After having the offset of the first section, we have a loop
288
     * over the section list. If the section exists, we read its
289
     * length (2 bytes at section start) and add it to current
290
     * offset to find the start of the next section. Otherwise, we
291
     * already have the next section offset...
292
     */
293
167
    guint16 ucode_offset;
294
167
    guint8 available_sections = 0;
295
296
167
    if (!fu_input_stream_read_u8(
297
167
      stream,
298
167
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
299
167
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS,
300
167
      &available_sections,
301
167
      error)) {
302
0
      g_prefix_error_literal(error, "failed to read available sections: ");
303
0
      return FALSE;
304
0
    }
305
167
    if (!fu_input_stream_read_u16(
306
167
      stream,
307
167
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
308
167
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE,
309
167
      &ucode_offset,
310
167
      G_LITTLE_ENDIAN,
311
167
      error)) {
312
0
      g_prefix_error_literal(error, "failed to read ucode offset: ");
313
0
      return FALSE;
314
0
    }
315
167
    offset = ucode_offset;
316
167
    if ((available_sections & FU_INTEL_THUNDERBOLT_NVM_SECTION_FLAG_DRAM) == 0) {
317
1
      g_set_error_literal(error,
318
1
              FWUPD_ERROR,
319
1
              FWUPD_ERROR_INVALID_FILE,
320
1
              "cannot find needed FW sections in the FW image file");
321
1
      return FALSE;
322
1
    }
323
324
1.12k
    for (guint8 i = 1; i < FU_INTEL_THUNDERBOLT_NVM_SECTION_FLAG_DRAM; i <<= 1) {
325
975
      if (available_sections & i) {
326
471
        if (!fu_intel_thunderbolt_nvm_read_ucode_section_len(self,
327
471
                         stream,
328
471
                         offset,
329
471
                         &ucode_offset,
330
471
                         error))
331
12
          return FALSE;
332
459
        offset += ucode_offset;
333
459
      }
334
975
    }
335
154
    priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DRAM_UCODE] =
336
154
        offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL];
337
154
  }
338
339
271
  return TRUE;
340
284
}
341
342
static gboolean
343
fu_intel_thunderbolt_nvm_missing_needed_drom(FuIntelThunderboltNvm *self)
344
271
{
345
271
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
346
271
  if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0)
347
216
    return FALSE;
348
55
  if (priv->is_host && priv->gen < 3)
349
48
    return FALSE;
350
7
  return TRUE;
351
55
}
352
353
static gboolean
354
fu_intel_thunderbolt_nvm_parse(FuFirmware *firmware,
355
             GInputStream *stream,
356
             FuFirmwareParseFlags flags,
357
             GError **error)
358
409
{
359
409
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
360
409
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
361
409
  guint8 tmp = 0;
362
409
  guint16 version_raw = 0;
363
409
  struct {
364
409
    guint16 device_id;
365
409
    guint gen;
366
409
    FuIntelThunderboltNvmFamily family;
367
409
    guint ports;
368
409
  } hw_info_arr[] = {{0x156D, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FALCON_RIDGE, 2},
369
409
         {0x156B, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FALCON_RIDGE, 1},
370
409
         {0x157E, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_WIN_RIDGE, 1},
371
409
         {0x1578, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 2},
372
409
         {0x1576, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 1},
373
409
         {0x15C0, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 1},
374
409
         {0x15D3, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C, 2},
375
409
         {0x15DA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C, 1},
376
409
         {0x15E7, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 1},
377
409
         {0x15EA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 2},
378
409
         {0x15EF, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 2},
379
409
         {0x15EE, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_BB, 0},
380
409
         {0x0B26, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_GOSHEN_RIDGE, 2},
381
409
         {0x5786, 5, FU_INTEL_THUNDERBOLT_NVM_FAMILY_BARLOW_RIDGE, 2},
382
         /* Maple ridge devices
383
          * NOTE: These are expected to be flashed via UEFI capsules *not*
384
          * Thunderbolt plugin Flashing via fwupd will require matching kernel
385
          * work. They're left here only for parsing the binaries
386
          */
387
409
         {0x1136, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MAPLE_RIDGE, 2},
388
409
         {0x1137, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MAPLE_RIDGE, 2},
389
409
         {0}};
390
409
  g_autoptr(FuFirmware) img_payload = fu_firmware_new();
391
409
  gsize streamsz = 0;
392
393
  /* add this straight away */
394
409
  priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] = 0x0;
395
396
  /* is native */
397
409
  if (!fu_input_stream_read_u8(stream,
398
409
             priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
399
409
           FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE,
400
409
             &tmp,
401
409
             error)) {
402
16
    g_prefix_error_literal(error, "failed to read native: ");
403
16
    return FALSE;
404
16
  }
405
393
  priv->is_native = tmp & 0x20;
406
407
  /* we're only reading the first chunk */
408
393
  if (!fu_input_stream_size(stream, &streamsz, error))
409
0
    return FALSE;
410
393
  if (streamsz == 0x80)
411
52
    return TRUE;
412
413
  /* host or device */
414
341
  if (!fu_input_stream_read_u8(stream,
415
341
             priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
416
341
           FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST,
417
341
             &tmp,
418
341
             error)) {
419
0
    g_prefix_error_literal(error, "failed to read is-host: ");
420
0
    return FALSE;
421
0
  }
422
341
  priv->is_host = tmp & (1 << 1);
423
424
  /* device ID */
425
341
  if (!fu_input_stream_read_u16(stream,
426
341
              priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
427
341
            FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID,
428
341
              &priv->device_id,
429
341
              G_LITTLE_ENDIAN,
430
341
              error)) {
431
0
    g_prefix_error_literal(error, "failed to read device-id: ");
432
0
    return FALSE;
433
0
  }
434
435
  /* this is best-effort */
436
3.62k
  for (guint i = 0; hw_info_arr[i].device_id != 0; i++) {
437
3.58k
    if (hw_info_arr[i].device_id == priv->device_id) {
438
303
      priv->family = hw_info_arr[i].family;
439
303
      priv->gen = hw_info_arr[i].gen;
440
303
      priv->ports = hw_info_arr[i].ports;
441
303
      break;
442
303
    }
443
3.58k
  }
444
341
  if (priv->family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN) {
445
38
    g_set_error_literal(error,
446
38
            FWUPD_ERROR,
447
38
            FWUPD_ERROR_INVALID_DATA,
448
38
            "unknown NVM family");
449
38
    return FALSE;
450
38
  }
451
303
  if (priv->ports == 0 && priv->is_host) {
452
2
    g_set_error(error,
453
2
          FWUPD_ERROR,
454
2
          FWUPD_ERROR_NOT_SUPPORTED,
455
2
          "unknown controller: %x",
456
2
          priv->device_id);
457
2
    return FALSE;
458
2
  }
459
460
  /* read sections from file */
461
301
  if (!fu_intel_thunderbolt_nvm_read_sections(self, stream, error))
462
30
    return FALSE;
463
271
  if (fu_intel_thunderbolt_nvm_missing_needed_drom(self)) {
464
7
    g_set_error_literal(error,
465
7
            FWUPD_ERROR,
466
7
            FWUPD_ERROR_READ,
467
7
            "cannot find required drom section");
468
7
    return FALSE;
469
7
  }
470
471
  /* vendor:model */
472
264
  if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0x0) {
473
216
    if (!fu_input_stream_read_u16(
474
216
      stream,
475
216
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] +
476
216
          FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID,
477
216
      &priv->vendor_id,
478
216
      G_LITTLE_ENDIAN,
479
216
      error)) {
480
60
      g_prefix_error_literal(error, "failed to read vendor-id: ");
481
60
      return FALSE;
482
60
    }
483
156
    if (!fu_input_stream_read_u16(
484
156
      stream,
485
156
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] +
486
156
          FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID,
487
156
      &priv->model_id,
488
156
      G_LITTLE_ENDIAN,
489
156
      error)) {
490
2
      g_prefix_error_literal(error, "failed to read model-id: ");
491
2
      return FALSE;
492
2
    }
493
156
  }
494
495
  /* versions */
496
202
  switch (priv->family) {
497
52
  case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE:
498
132
  case FU_INTEL_THUNDERBOLT_NVM_FAMILY_GOSHEN_RIDGE:
499
144
  case FU_INTEL_THUNDERBOLT_NVM_FAMILY_BARLOW_RIDGE:
500
144
    if (!fu_input_stream_read_u16(
501
144
      stream,
502
144
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
503
144
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION,
504
144
      &version_raw,
505
144
      G_LITTLE_ENDIAN,
506
144
      error)) {
507
0
      g_prefix_error_literal(error, "failed to read version: ");
508
0
      return FALSE;
509
0
    }
510
144
    fu_firmware_set_version_raw(FU_FIRMWARE(self), version_raw);
511
144
    break;
512
58
  default:
513
58
    break;
514
202
  }
515
516
202
  if (priv->is_host) {
517
158
    switch (priv->family) {
518
5
    case FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE:
519
7
    case FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C:
520
27
    case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE:
521
      /* used for comparison between old and new image, not a raw number */
522
27
      if (!fu_input_stream_read_u8(
523
27
        stream,
524
27
        priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
525
27
            FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE,
526
27
        &tmp,
527
27
        error)) {
528
0
        g_prefix_error_literal(error, "failed to read flash size: ");
529
0
        return FALSE;
530
0
      }
531
27
      priv->flash_size = tmp & 0x07;
532
27
      break;
533
131
    default:
534
131
      break;
535
158
    }
536
158
  }
537
538
  /* we're only reading enough to get the vendor-id and model-id */
539
202
  if (streamsz < priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS])
540
44
    return TRUE;
541
542
  /* has PD */
543
158
  if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] != 0x0) {
544
91
    guint32 pd_pointer = 0x0;
545
91
    if (!fu_input_stream_read_u32(
546
91
      stream,
547
91
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] +
548
91
          FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER,
549
91
      &pd_pointer,
550
91
      G_LITTLE_ENDIAN,
551
91
      error)) {
552
23
      g_prefix_error_literal(error, "failed to read pd-pointer: ");
553
23
      return FALSE;
554
23
    }
555
68
    priv->has_pd = fu_intel_thunderbolt_nvm_valid_pd_pointer(pd_pointer);
556
68
  }
557
558
  /* as as easy-to-grab payload blob */
559
135
  if (!fu_firmware_parse_stream(img_payload, stream, 0x0, flags, error))
560
0
    return FALSE;
561
135
  fu_firmware_set_id(img_payload, FU_FIRMWARE_ID_PAYLOAD);
562
135
  if (!fu_firmware_add_image(firmware, img_payload, error))
563
0
    return FALSE;
564
565
  /* success */
566
135
  return TRUE;
567
135
}
568
569
/* can only write version 3 NVM */
570
static GByteArray *
571
fu_intel_thunderbolt_nvm_write(FuFirmware *firmware, GError **error)
572
87
{
573
87
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
574
87
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
575
87
  g_autoptr(FuIntelThunderboltNvmDigital) st = fu_intel_thunderbolt_nvm_digital_new();
576
87
  g_autoptr(FuIntelThunderboltNvmDrom) st_drom = fu_intel_thunderbolt_nvm_drom_new();
577
87
  g_autoptr(FuIntelThunderboltNvmArcParams) st_arc =
578
87
      fu_intel_thunderbolt_nvm_arc_params_new();
579
87
  g_autoptr(FuIntelThunderboltNvmDram) st_dram = fu_intel_thunderbolt_nvm_dram_new();
580
581
  /* digital section */
582
87
  fu_intel_thunderbolt_nvm_digital_set_available_sections(
583
87
      st,
584
87
      FU_INTEL_THUNDERBOLT_NVM_SECTION_FLAG_DRAM);
585
87
  fu_intel_thunderbolt_nvm_digital_set_device_id(st, priv->device_id);
586
87
  fu_intel_thunderbolt_nvm_digital_set_version(st, fu_firmware_get_version_raw(firmware));
587
87
  fu_intel_thunderbolt_nvm_digital_set_flags_host(st, priv->is_host ? 0x2 : 0x0);
588
87
  fu_intel_thunderbolt_nvm_digital_set_flash_size(st, priv->flash_size);
589
87
  fu_intel_thunderbolt_nvm_digital_set_flags_is_native(st, priv->is_native ? 0x20 : 0x0);
590
591
  /* drom section */
592
87
  fu_intel_thunderbolt_nvm_digital_set_drom(st, st->buf->len);
593
87
  fu_intel_thunderbolt_nvm_drom_set_vendor_id(st_drom, priv->vendor_id);
594
87
  fu_intel_thunderbolt_nvm_drom_set_model_id(st_drom, priv->model_id);
595
87
  fu_byte_array_append_array(st->buf, st_drom->buf);
596
597
  /* ARC param section */
598
87
  fu_intel_thunderbolt_nvm_digital_set_arc_params(st, st->buf->len);
599
87
  fu_intel_thunderbolt_nvm_arc_params_set_pd_pointer(st_arc, priv->has_pd ? 0x1 : 0x0);
600
87
  fu_byte_array_append_array(st->buf, st_arc->buf);
601
602
  /* dram section */
603
87
  fu_intel_thunderbolt_nvm_digital_set_ucode(st, st->buf->len);
604
87
  fu_byte_array_append_array(st->buf, st_dram->buf);
605
606
  /* success */
607
87
  return g_steal_pointer(&st->buf);
608
87
}
609
610
static gboolean
611
fu_intel_thunderbolt_nvm_check_compatible(FuFirmware *firmware,
612
            FuFirmware *firmware_other,
613
            FuFirmwareParseFlags flags,
614
            GError **error)
615
0
{
616
0
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
617
0
  FuIntelThunderboltNvm *other = FU_INTEL_THUNDERBOLT_NVM(firmware_other);
618
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
619
0
  FuIntelThunderboltNvmPrivate *priv_other = GET_PRIVATE(other);
620
621
0
  if (priv->is_host != priv_other->is_host) {
622
0
    g_set_error(error,
623
0
          FWUPD_ERROR,
624
0
          FWUPD_ERROR_INVALID_FILE,
625
0
          "incorrect firmware mode, got %s, expected %s",
626
0
          priv->is_host ? "host" : "device",
627
0
          priv_other->is_host ? "host" : "device");
628
0
    return FALSE;
629
0
  }
630
0
  if (priv->vendor_id != priv_other->vendor_id) {
631
0
    g_set_error(error,
632
0
          FWUPD_ERROR,
633
0
          FWUPD_ERROR_INVALID_FILE,
634
0
          "incorrect device vendor, got 0x%04x, expected 0x%04x",
635
0
          priv->vendor_id,
636
0
          priv_other->vendor_id);
637
0
    return FALSE;
638
0
  }
639
0
  if (priv->device_id != priv_other->device_id) {
640
0
    g_set_error(error,
641
0
          FWUPD_ERROR,
642
0
          FWUPD_ERROR_INVALID_FILE,
643
0
          "incorrect device type, got 0x%04x, expected 0x%04x",
644
0
          priv->device_id,
645
0
          priv_other->device_id);
646
0
    return FALSE;
647
0
  }
648
0
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_VID_PID) == 0) {
649
0
    if (priv->model_id != priv_other->model_id) {
650
0
      g_set_error(error,
651
0
            FWUPD_ERROR,
652
0
            FWUPD_ERROR_INVALID_FILE,
653
0
            "incorrect device model, got 0x%04x, expected 0x%04x",
654
0
            priv->model_id,
655
0
            priv_other->model_id);
656
0
      return FALSE;
657
0
    }
658
    /* old firmware has PD but new doesn't (we don't care about other way around) */
659
0
    if (priv->has_pd && !priv_other->has_pd) {
660
0
      g_set_error_literal(error,
661
0
              FWUPD_ERROR,
662
0
              FWUPD_ERROR_INVALID_FILE,
663
0
              "incorrect PD section");
664
0
      return FALSE;
665
0
    }
666
0
    if (priv->flash_size != priv_other->flash_size) {
667
0
      g_set_error(error,
668
0
            FWUPD_ERROR,
669
0
            FWUPD_ERROR_INVALID_FILE,
670
0
            "incorrect flash size, got 0x%x and expected 0x%x",
671
0
            priv->flash_size,
672
0
            priv_other->flash_size);
673
0
      return FALSE;
674
0
    }
675
0
  }
676
0
  return TRUE;
677
0
}
678
679
static gboolean
680
fu_intel_thunderbolt_nvm_build(FuFirmware *firmware, XbNode *n, GError **error)
681
0
{
682
0
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
683
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
684
0
  const gchar *tmp;
685
686
  /* simple properties */
687
0
  tmp = xb_node_query_text(n, "vendor_id", NULL);
688
0
  if (tmp != NULL) {
689
0
    guint64 val = 0;
690
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
691
0
      return FALSE;
692
0
    priv->vendor_id = val;
693
0
  }
694
0
  tmp = xb_node_query_text(n, "device_id", NULL);
695
0
  if (tmp != NULL) {
696
0
    guint64 val = 0;
697
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
698
0
      return FALSE;
699
0
    priv->device_id = val;
700
0
  }
701
0
  tmp = xb_node_query_text(n, "model_id", NULL);
702
0
  if (tmp != NULL) {
703
0
    guint64 val = 0;
704
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
705
0
      return FALSE;
706
0
    priv->model_id = val;
707
0
  }
708
0
  tmp = xb_node_query_text(n, "family", NULL);
709
0
  if (tmp != NULL) {
710
0
    priv->family = fu_intel_thunderbolt_nvm_family_from_string(tmp);
711
0
    if (priv->family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN) {
712
0
      g_set_error(error,
713
0
            FWUPD_ERROR,
714
0
            FWUPD_ERROR_INVALID_DATA,
715
0
            "unknown family: %s",
716
0
            tmp);
717
0
      return FALSE;
718
0
    }
719
0
  }
720
0
  tmp = xb_node_query_text(n, "flash_size", NULL);
721
0
  if (tmp != NULL) {
722
0
    guint64 val = 0;
723
0
    if (!fu_strtoull(tmp, &val, 0x0, 0x07, FU_INTEGER_BASE_AUTO, error))
724
0
      return FALSE;
725
0
    priv->flash_size = val;
726
0
  }
727
0
  tmp = xb_node_query_text(n, "is_host", NULL);
728
0
  if (tmp != NULL) {
729
0
    if (!fu_strtobool(tmp, &priv->is_host, error))
730
0
      return FALSE;
731
0
  }
732
0
  tmp = xb_node_query_text(n, "is_native", NULL);
733
0
  if (tmp != NULL) {
734
0
    if (!fu_strtobool(tmp, &priv->is_native, error))
735
0
      return FALSE;
736
0
  }
737
738
  /* success */
739
0
  return TRUE;
740
0
}
741
742
static gchar *
743
fu_intel_thunderbolt_nvm_convert_version(FuFirmware *firmware, guint64 version_raw)
744
144
{
745
144
  return fu_version_from_uint16(version_raw, fu_firmware_get_version_format(firmware));
746
144
}
747
748
static void
749
fu_intel_thunderbolt_nvm_init(FuIntelThunderboltNvm *self)
750
496
{
751
496
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID);
752
496
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE);
753
496
  fu_firmware_set_images_max(FU_FIRMWARE(self), 1024);
754
496
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_BCD);
755
496
}
756
757
static void
758
fu_intel_thunderbolt_nvm_class_init(FuIntelThunderboltNvmClass *klass)
759
1
{
760
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
761
1
  firmware_class->convert_version = fu_intel_thunderbolt_nvm_convert_version;
762
1
  firmware_class->export = fu_intel_thunderbolt_nvm_export;
763
1
  firmware_class->parse = fu_intel_thunderbolt_nvm_parse;
764
1
  firmware_class->write = fu_intel_thunderbolt_nvm_write;
765
1
  firmware_class->build = fu_intel_thunderbolt_nvm_build;
766
1
  firmware_class->check_compatible = fu_intel_thunderbolt_nvm_check_compatible;
767
1
}
768
769
/**
770
 * fu_intel_thunderbolt_nvm_new:
771
 *
772
 * Creates a new #FuFirmware of Intel NVM format
773
 *
774
 * Since: 1.8.5
775
 **/
776
FuFirmware *
777
fu_intel_thunderbolt_nvm_new(void)
778
0
{
779
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_INTEL_THUNDERBOLT_NVM, NULL));
780
0
}